diff options
-rw-r--r-- | pysite/mixins.py | 3 | ||||
-rw-r--r-- | pysite/tables.py | 11 | ||||
-rw-r--r-- | pysite/views/api/bot/user.py | 13 | ||||
-rw-r--r-- | pysite/views/main/jams/join.py | 188 | ||||
-rw-r--r-- | pysite/views/main/jams/profile.py | 59 | ||||
-rw-r--r-- | pysite/views/main/jams/signup.py | 9 | ||||
-rw-r--r-- | pysite/views/staff/jams/actions.py | 3 | ||||
-rw-r--r-- | static/style.css | 11 | ||||
-rw-r--r-- | templates/main/about/privacy.html | 10 | ||||
-rw-r--r-- | templates/main/jams/already.html | 26 | ||||
-rw-r--r-- | templates/main/jams/banned.html | 44 | ||||
-rw-r--r-- | templates/main/jams/index.html | 2 | ||||
-rw-r--r-- | templates/main/jams/join.html | 357 | ||||
-rw-r--r-- | templates/main/jams/profile.html | 106 | ||||
-rw-r--r-- | templates/main/jams/signup.html | 15 | ||||
-rw-r--r-- | templates/main/jams/thanks.html | 25 | ||||
-rw-r--r-- | templates/main/navigation.html | 10 | ||||
-rw-r--r-- | templates/staff/jams/edit_basics.html | 1 | ||||
-rw-r--r-- | templates/staff/jams/infractions/view.html | 10 |
19 files changed, 861 insertions, 42 deletions
diff --git a/pysite/mixins.py b/pysite/mixins.py index a3edc4f2..6e5032ab 100644 --- a/pysite/mixins.py +++ b/pysite/mixins.py @@ -4,6 +4,7 @@ from flask import Blueprint from rethinkdb.ast import Table from pysite.database import RethinkDB +from pysite.oauth import OauthBackend class DBMixin: @@ -98,5 +99,5 @@ class OauthMixin: return self.oauth.user_data() @property - def oauth(self): + def oauth(self) -> OauthBackend: return self._oauth() diff --git a/pysite/tables.py b/pysite/tables.py index c592333d..e99ca989 100644 --- a/pysite/tables.py +++ b/pysite/tables.py @@ -79,8 +79,9 @@ TABLES = { primary_key="id", keys=sorted([ "id", # uuid + "snowflake", # str "jam", # int - "answers", # dict {question, answer, metadata} + "answers", # list [{question, answer, metadata}] "approved" # bool ]) ), @@ -100,16 +101,16 @@ TABLES = { "id", # uuid "participant", # str "reason", # str - "number" # int (optionally -1 for permanent) + "number", # int (optionally -1 for permanent) + "decremented_for" # list[int] ]) ), "code_jam_participants": Table( # Info for each participant primary_key="id", keys=sorted([ - "snowflake", # int - "skill_level", # str - "age", # str + "id", # str + "dob", # str "github_username", # str "timezone" # str ]) diff --git a/pysite/views/api/bot/user.py b/pysite/views/api/bot/user.py index 12f5a2c7..a353ccfe 100644 --- a/pysite/views/api/bot/user.py +++ b/pysite/views/api/bot/user.py @@ -32,6 +32,7 @@ class UserView(APIView, DBMixin): name = "bot.users" table_name = "users" oauth_table_name = "oauth_data" + participants_table = "code_jam_participants" @api_key @api_params(schema=SCHEMA, validation_type=ValidationTypes.json) @@ -40,6 +41,8 @@ class UserView(APIView, DBMixin): deletions = 0 oauth_deletions = 0 + profile_deletions = 0 + user_ids = [user["user_id"] for user in data] all_users = self.db.run(self.db.query(self.table_name), coerce=list) @@ -54,7 +57,9 @@ class UserView(APIView, DBMixin): for item in all_oauth_data: if item["snowflake"] not in user_ids: self.db.delete(self.oauth_table_name, item["id"], durability="soft") + self.db.delete(self.participants_table, item["id"], durability="soft") oauth_deletions += 1 + profile_deletions += 1 del user_ids @@ -68,6 +73,7 @@ class UserView(APIView, DBMixin): changes["deleted"] = deletions changes["deleted_oauth"] = oauth_deletions + changes["deleted_jam_profiles"] = profile_deletions return jsonify(changes) # pragma: no cover @@ -98,6 +104,13 @@ class UserView(APIView, DBMixin): .delete() ).get("deleted", 0) + profile_deletions = self.db.run( + self.db.query(self.participants_table) + .get_all(*user_ids) + .delete() + ) + changes["deleted_oauth"] = oauth_deletions + changes["deleted_jam_profiles"] = profile_deletions return jsonify(changes) # pragma: no cover diff --git a/pysite/views/main/jams/join.py b/pysite/views/main/jams/join.py new file mode 100644 index 00000000..83013a01 --- /dev/null +++ b/pysite/views/main/jams/join.py @@ -0,0 +1,188 @@ +from email.utils import parseaddr + +from flask import redirect, request, url_for +from werkzeug.exceptions import BadRequest, NotFound + +from pysite.base_route import RouteView +from pysite.decorators import csrf +from pysite.mixins import DBMixin, OauthMixin + + +class JamsJoinView(RouteView, DBMixin, OauthMixin): + path = "/jams/join/<int:jam>" + name = "jams.join" + + table_name = "code_jams" + forms_table = "code_jam_forms" + questions_table = "code_jam_questions" + responses_table = "code_jam_responses" + participants_table = "code_jam_participants" + infractions_table = "code_jam_infractions" + + def get(self, jam): + jam_obj = self.db.get(self.table_name, jam) + + if not jam_obj: + return NotFound() + + if not self.user_data: + return redirect(url_for("discord.login")) + + infractions = self.get_infractions(self.user_data["user_id"]) + + for infraction in infractions: + if infraction["number"] == -1: # Indefinite ban + return self.render("main/jams/banned.html", infraction=infraction, jam=jam_obj) + + if infraction["number"]: # Got some jams left + if jam not in infraction["decremented_for"]: + # Make sure they haven't already tried to apply for this jam + infraction["number"] -= 1 + infraction["decremented_for"].append(jam) + + self.db.insert(self.infractions_table, infraction, conflict="replace") + + return self.render("main/jams/banned.html", infraction=infraction, jam=jam_obj) + + if jam in infraction["decremented_for"]: + # They already tried to apply for this jam + return self.render("main/jams/banned.html", infraction=infraction, jam=jam_obj) + + participant = self.db.get(self.participants_table, self.user_data["user_id"]) + + if not participant: + return redirect(url_for("info.jams.profile")) + + if self.get_response(jam, self.user_data["user_id"]): + return self.render("main/jams/already.html", jam=jam_obj) + + form_obj = self.db.get(self.forms_table, jam) + questions = [] + + if form_obj: + for question in form_obj["questions"]: + questions.append(self.db.get(self.questions_table, question)) + + return self.render( + "main/jams/join.html", jam=jam_obj, form=form_obj, + questions=questions, question_ids=[q["id"] for q in questions] + ) + + @csrf + def post(self, jam): + jam_obj = self.db.get(self.table_name, jam) + + if not jam_obj: + return NotFound() + + if not self.user_data: + return redirect(url_for("discord.login")) + + infractions = self.get_infractions(self.user_data["user_id"]) + + for infraction in infractions: + if infraction["number"] == -1: # Indefinite ban + return self.render("main/jams/banned.html", infraction=infraction) + + if infraction["number"]: # Got some jams left + if jam not in infraction["decremented_for"]: + # Make sure they haven't already tried to apply for this jam + infraction["number"] -= 1 + infraction["decremented_for"].append(jam) + + self.db.insert(self.infractions_table, infraction, conflict="replace") + + return self.render("main/jams/banned.html", infraction=infraction, jam=jam_obj) + + if jam in infraction["decremented_for"]: + # They already tried to apply for this jam + return self.render("main/jams/banned.html", infraction=infraction, jam=jam_obj) + + participant = self.db.get(self.participants_table, self.user_data["user_id"]) + + if not participant: + return redirect(url_for("info.jams.profile")) + + if self.get_response(jam, self.user_data["user_id"]): + return self.render("main/jams/already.html", jam=jam_obj) + + form_obj = self.db.get(self.forms_table, jam) + + if not form_obj: + return NotFound() + + questions = [] + + for question in form_obj["questions"]: + questions.append(self.db.get(self.questions_table, question)) + + answers = [] + + for question in questions: + value = request.form.get(question["id"]) + answer = {"question": question["id"]} + + if not question["optional"] and value is None: + return BadRequest() + + if question["type"] == "checkbox": + if value == "on": + answer["value"] = True + elif not question["optional"]: + return BadRequest() + else: + answer["value"] = False + + elif question["type"] == "email": + if value: + address = parseaddr(value) + + if address == ("", ""): + return BadRequest() + + answer["value"] = value + + elif question["type"] in ["number", "range", "slider"]: + if value is not None: + value = int(value) + + if value > int(question["data"]["max"]) or value < int(question["data"]["min"]): + return BadRequest() + + answer["value"] = value + + elif question["type"] == "radio": + if value: + if value not in question["data"]["options"]: + return BadRequest() + + answer["value"] = value + + elif question["type"] in ["text", "textarea"]: + answer["value"] = value + + answers.append(answer) + + user_id = self.user_data["user_id"] + + response = { + "snowflake": user_id, + "jam": jam, + "approved": False, + "answers": answers + } + + self.db.insert(self.responses_table, response) + return self.render("main/jams/thanks.html", jam=jam_obj) + + def get_response(self, jam, user_id): + query = self.db.query(self.responses_table).filter({"jam": jam, "snowflake": user_id}) + result = self.db.run(query, coerce=list) + + if result: + return result[0] + return None + + def get_infractions(self, user_id): + query = self.db.query(self.infractions_table).filter({"participant": user_id}) + return self.db.run(query, coerce=list) diff --git a/pysite/views/main/jams/profile.py b/pysite/views/main/jams/profile.py new file mode 100644 index 00000000..407f842e --- /dev/null +++ b/pysite/views/main/jams/profile.py @@ -0,0 +1,59 @@ +import datetime + +from flask import redirect, request, url_for +from werkzeug.exceptions import BadRequest + +from pysite.base_route import RouteView +from pysite.decorators import csrf +from pysite.mixins import DBMixin, OauthMixin + + +class JamsProfileView(RouteView, DBMixin, OauthMixin): + path = "/jams/profile" + name = "jams.profile" + + table_name = "code_jam_participants" + + def get(self): + if not self.user_data: + return redirect(url_for("discord.login")) + + participant = self.db.get(self.table_name, self.user_data["user_id"]) + + if not participant: + participant = {"id": self.user_data["user_id"]} + + return self.render( + "main/jams/profile.html", participant=participant + ) + + @csrf + def post(self): + if not self.user_data: + return redirect(url_for("discord.login")) + + participant = self.db.get(self.table_name, self.user_data["user_id"]) + + if not participant: + participant = {"id": self.user_data["user_id"]} + + dob = request.form.get("dob") + github_username = request.form.get("github_username") + timezone = request.form.get("timezone") + + if not dob or not github_username or not timezone: + return BadRequest() + + # Convert given datetime strings into actual objects, adding timezones to keep rethinkdb happy + dob = datetime.datetime.strptime(dob, "%Y-%m-%d") + dob = dob.replace(tzinfo=datetime.timezone.utc) + + participant["dob"] = dob + participant["github_username"] = github_username + participant["timezone"] = timezone + + self.db.insert(self.table_name, participant, conflict="replace") + + return self.render( + "main/jams/profile.html", participant=participant, done=True + ) diff --git a/pysite/views/main/jams/signup.py b/pysite/views/main/jams/signup.py deleted file mode 100644 index 632da6d6..00000000 --- a/pysite/views/main/jams/signup.py +++ /dev/null @@ -1,9 +0,0 @@ -from pysite.base_route import RouteView - - -class JamsSignupView(RouteView): - path = "/jams/signup" - name = "jams.signup" - - def get(self): - return self.render("main/jams/signup.html") diff --git a/pysite/views/staff/jams/actions.py b/pysite/views/staff/jams/actions.py index f08b3635..1af215a5 100644 --- a/pysite/views/staff/jams/actions.py +++ b/pysite/views/staff/jams/actions.py @@ -183,7 +183,8 @@ class ActionView(APIView, DBMixin): result = self.db.insert(self.infractions_table, { "participant": participant, "reason": reason, - "number": number + "number": number, + "decremented_for": [] }) return jsonify({"id": result["generated_keys"][0]}) diff --git a/static/style.css b/static/style.css index 2b9b329c..1f3f9f58 100644 --- a/static/style.css +++ b/static/style.css @@ -179,4 +179,15 @@ select { left: auto !important; -webkit-appearance: unset !important; opacity: 1 !important; +} + +div.danger-input * { + /*border-radius: 5px;*/ + /*padding: 0.8rem;*/ + /*min-height: 3.5rem;*/ + color: red; + border-color: red !important; + + transition: color 0.5s ease, + border-color 0.5s ease; }
\ No newline at end of file diff --git a/templates/main/about/privacy.html b/templates/main/about/privacy.html index 1a3260a1..870b75a8 100644 --- a/templates/main/about/privacy.html +++ b/templates/main/about/privacy.html @@ -53,6 +53,16 @@ <li>An access token and refresh token</li> </ul> + <p> + Should you set up your code jam profile, we additionally collect... + </p> + + <ul> + <li>Your date of birth</li> + <li>Your GitHub username</li> + <li>Your timezone</li> + </ul> + <h1 class="uk-article-title hover-title" id="usage"> How We Use Your Data diff --git a/templates/main/jams/already.html b/templates/main/jams/already.html new file mode 100644 index 00000000..0baaf4a9 --- /dev/null +++ b/templates/main/jams/already.html @@ -0,0 +1,26 @@ +{% extends "main/base.html" %} +{% block title %}Code Jams | Banned{% endblock %} +{% block og_title %}Code Jams | Banned{% endblock %} + +{% block content %} +<div class="uk-section"> + <div class="uk-container uk-container-small"> + <h1 class="uk-header uk-article-title"> + Code Jam {{ jam.number }}: {{ jam.title }} + </h1> + <p class="uk-article-meta"> + Bring the thunder! + </p> + + <p class="uk-alert uk-alert-danger"> + Thanks for your interest in this code jam! It looks like we already have an application here for you, + so please just sit back, relax, and we'll let you know whether you've been selected for this code + jam when the time comes. + </p> + + <a class="uk-button uk-button-secondary uk-align-center" href="{{ url_for("main.jams.index") }}"> + <i class="uk-icon fa-fw far fa-arrow-left"></i> Back to all code jams + </a> + </div> +</div> +{% endblock %} diff --git a/templates/main/jams/banned.html b/templates/main/jams/banned.html new file mode 100644 index 00000000..fa47c1ec --- /dev/null +++ b/templates/main/jams/banned.html @@ -0,0 +1,44 @@ +{% extends "main/base.html" %} +{% block title %}Code Jams | Banned{% endblock %} +{% block og_title %}Code Jams | Banned{% endblock %} + +{% block content %} +<div class="uk-section"> + <div class="uk-container uk-container-small"> + <h1 class="uk-header uk-article-title"> + Code Jam {{ jam.number }}: {{ jam.title }} + </h1> + <p class="uk-article-meta"> + Bring the thunder! + </p> + + {% if infraction.number == -1 %} + <p class="uk-alert uk-alert-danger"> + Thanks for your interest in this code jam! Unfortunately, due to your previous actions, you have been + permanently banned from participating in our code jams. + <br /> + <br /> + The reason given is: <strong>{{ infraction.reason }}</strong> + <br /> + <br /> + If you feel that this is a mistake, please feel free to contact one of the admins on Discord. + </p> + {% else %} + <p class="uk-alert uk-alert-danger"> + Thanks for your interest in this code jam! Unfortunately, due to your previous actions, you have been + temporarily banned from participating in our code jams. + <br /> + <br /> + The reason given is: <strong>{{ infraction.reason }}</strong> + <br /> + <br /> + If you feel that this is a mistake, please feel free to contact one of the admins on Discord. + </p> + {% endif %} + + <a class="uk-button uk-button-secondary uk-align-center" href="{{ url_for("main.jams.index") }}"> + <i class="uk-icon fa-fw far fa-arrow-left"></i> Back to all code jams + </a> + </div> +</div> +{% endblock %} diff --git a/templates/main/jams/index.html b/templates/main/jams/index.html index a5d2a5d0..71bce999 100644 --- a/templates/main/jams/index.html +++ b/templates/main/jams/index.html @@ -61,7 +61,7 @@ Code Jam {{ jam.number }}: {{ jam.title }} <span class="uk-float-right"> {% if jam.state == "announced" %} - <a class="uk-button uk-button-primary" target="_blank" href="{{ jam.repo }}"> + <a class="uk-button uk-button-primary" href="{{ url_for("main.jams.join", jam=jam.number) }}"> <i class="uk-icon fa-fw far fa-plus"></i> Join </a> {% else %} diff --git a/templates/main/jams/join.html b/templates/main/jams/join.html new file mode 100644 index 00000000..ffa80cb5 --- /dev/null +++ b/templates/main/jams/join.html @@ -0,0 +1,357 @@ +{% extends "main/base.html" %} +{% block title %}Code Jams | Join{% endblock %} +{% block og_title %}Code Jams | Join{% endblock %} + +{% macro show_question(question) %} + <div id="div-{{ question.id }}"> + <div class="uk-form-label"> + {% if question.optional %} + <label class="uk-form-label" style="margin-top: 0" for="{{ question.id }}"> + <strong>{{ question.title }}</strong> + <br /> + <span class="uk-text-meta">You may skip this question</span> + </label> + {% else %} + <label class="uk-form-label" style="margin-top: 0" for="{{ question.id }}"> + <strong>{{ question.title }}</strong> + <br /> + <span class="uk-text-meta">This question is required</span> + </label> + {% endif %} + </div> + <div class="uk-form-controls uk-form-controls-text"> + {% if question.type == "checkbox" %} + {% if question.optional %} + <input class="uk-checkbox" type="checkbox" name="{{ question.id }}" id="{{ question.id }}"> + <label for="{{ question.id }}" style="padding-left: 0.7rem;">Confirm</label> + {% else %} + <input class="uk-checkbox" type="checkbox" name="{{ question.id }}" id="{{ question.id }}" required> + <label for="{{ question.id }}" style="padding-left: 0.7rem;">Confirm</label> + {% endif %} + + {% elif question.type == "email" %} + {% if question.optional %} + <input class="uk-input" type="email" name="{{ question.id }}" id="{{ question.id }}" placeholder="[email protected]"> + {% else %} + <input class="uk-input" type="email" name="{{ question.id }}" id="{{ question.id }}" placeholder="[email protected]" required> + {% endif %} + + {% elif question.type == "number" %} + {% if question.optional %} + <input class="uk-input" type="number" max="{{ question.data.max }}" min="{{ question.data.min }}" name="{{ question.id }}" id="{{ question.id }}" value="{{ question.data.min }}"> + {% else %} + <input class="uk-input" type="number" max="{{ question.data.max }}" min="{{ question.data.min }}" name="{{ question.id }}" id="{{ question.id }}" value="{{ question.data.min }}" required> + {% endif %} + + {% elif question.type == "radio" %} + {% if question.optional %} + {% for option in question.data.options %} + <input class="uk-radio radio-{{ question.id }}" type="radio" name="{{ question.id }}" id="{{ question.id }}-{{ option }}" value="{{ option }}"> + <label style="padding-left: 0.7rem; padding-right: 1rem;" for="{{ question.id }}-{{ option }}">{{ option }}</label> + {% endfor %} + {% else %} + {% for option in question.data.options %} + <input class="uk-radio radio-{{ question.id }}" type="radio" name="{{ question.id }}" id="{{ question.id }}-{{ option }}" value="{{ option }}" required> + <label style="padding-left: 0.7rem; padding-right: 1rem;" for="{{ question.id }}-{{ option }}">{{ option }}</label> + {% endfor %} + {% endif %} + + {% elif question.type == "range" %} + <div class="uk-flex uk-flex-between"> + {% if question.optional %} + {% for num in range(question.data.min, question.data.max + 1) %} + <span> + <input class="uk-radio radio-{{ question.id }}" type="radio" name="{{ question.id }}" id="{{ question.id }}-{{ num }}" value="{{ num }}"> + <label style="padding-left: 0.3rem;" for="{{ question.id }}-{{ num }}">{{ num }}</label> + </span> + {% endfor %} + {% else %} + {% for num in range(question.data.min, question.data.max + 1) %} + <span> + <input class="uk-radio radio-{{ question.id }}" type="radio" name="{{ question.id }}" id="{{ question.id }}-{{ num }}" value="{{ num }}" required> + <label style="padding-left: 0.3rem;" for="{{ question.id }}-{{ num }}">{{ num }}</label> + </span> + {% endfor %} + {% endif %} + </div> + + {% elif question.type == "text" %} + {% if question.optional %} + <input class="uk-input" type="text" name="{{ question.id }}" id="{{ question.id }}"> + {% else %} + <input class="uk-input" type="text" name="{{ question.id }}" id="{{ question.id }}" required> + {% endif %} + + {% elif question.type == "textarea" %} + {% if question.optional %} + <textarea class="uk-input uk-textarea" name="{{ question.id }}" id="{{ question.id }}" style="resize: vertical; min-height: 15rem; font-family: monospace;"></textarea> + {% else %} + <textarea class="uk-input uk-textarea" name="{{ question.id }}" id="{{ question.id }}" style="resize: vertical; min-height: 15rem; font-family: monospace;" required></textarea> + {% endif %} + + {% elif question.type == "slider" %} + <div class="uk-flex uk-flex-between"> + <label class="uk-label" style="margin-right: 1rem;" for="{{ question.id }}" id="{{ question.id }}-slider-value">{{ question.data.min }}</label> + <input class="uk-range range-slider" name="{{ question.id }}" id="{{ question.id }}" min="{{ question.data.min }}" max="{{ question.data.max }}" step="1" value="{{ question.data.min }}" type="range"> + </div> + + {% endif %} + </div> + </div> +{% endmacro %} + +{% block content %} +<div class="uk-section"> + <div class="uk-container uk-container-small"> + <h1 class="uk-header uk-article-title"> + Code Jam {{ jam.number }}: {{ jam.title }} + </h1> + <p class="uk-article-meta"> + Bring the thunder! + </p> + <p> + Please fill out the form below to apply for this code jam. Once you've submitted your application and the + application window has closed, we'll review it and let you know whether you've been entered! + </p> + <p> + Please note that you will not be able to edit your application after you've submitted it. + </p> + <hr class="uk-divider-icon" /> + + {% if jam.state != "announced" %} + <p class="uk-alert uk-alert-primary"> + Unfortunately, we're not accepting applications for this code jam right now - but we appreciate your + interest. Keep an eye on <code>#announcements</code> on Discord for information on the next jam! + </p> + {% else %} + <form action="{{ url_for("main.jams.join", jam=jam.number) }}" method="post" class="uk-form-horizontal"> + {% for question in questions %} + {{ show_question(question) }} + <br /> + {% endfor %} + <br /> + + <input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/> + + <div class="uk-text-center"> + <a class="uk-button uk-button-default" href="{{ url_for("main.jams.index") }}"> + <i class="uk-icon fa-fw far fa-arrow-left"></i> Back + </a> + <button type="submit" class="uk-button uk-button-primary" name="submit" id="submit" disabled> + <i class="uk-icon fa-fw far fa-check"></i> Apply + </button> + </div> + </form> + + {% endif %} + </div> +</div> + +<script type="application/javascript"> + "use strict"; + + // noinspection JSAnnotator (It thinks I'm not assigning this for some reason) + const questions = {{ questions | tojson }}; + const button = document.getElementById("submit"); + + function validateEmail(email) { + let re = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; + return re.test(String(email).toLowerCase()); + } + + function isNum(value) { + return !isNaN(parseInt(value)); + } + + function checkInputs() { + let input, inputs, div; + let disabled = false; + + for (let question of questions) { + div = document.getElementById("div-" + question.id); + + switch (question.type) { + case "checkbox": + if (!question.optional) { + let input = document.getElementById(question.id); + + if (!input.checked) { + disabled = true; + div.classList.add("danger-input"); + } else { + div.classList.remove("danger-input"); + } + } + break; + case "email": + input = document.getElementById(question.id); + + if (!question.optional || input.value.length > 0) { + if (input.value.length < 5 || !validateEmail(input.value)) { + disabled = true; + div.classList.add("danger-input"); + } else { + div.classList.remove("danger-input"); + } + } else { + div.classList.remove("danger-input"); + } + break; + case "number": + input = document.getElementById(question.id); + + if (!question.optional || input.value.length > 0) { + if (input.value.length < 1 || !isNum(input.value)) { + disabled = true; + div.classList.add("danger-input"); + } else { + let val = parseInt(input.value); + + if (val < question.data.min || val > question.data.max) { + disabled = true; + div.classList.add("danger-input"); + } else { + div.classList.remove("danger-input"); + } + } + } + break; + case "radio": + if (! question.optional) { + inputs = document.getElementsByClassName("radio-" + question.id); + let selected = null; + + for (let inner of inputs) { + if (inner.checked) { + selected = inner; + } + } + + if (selected === null) { + disabled = true; + div.classList.add("danger-input"); + } else { + div.classList.remove("danger-input"); + } + } + + break; + case "range": + if (! question.optional) { + inputs = document.getElementsByClassName("radio-" + question.id); + let selected = null; + + for (let inner of inputs) { + if (inner.checked) { + selected = inner; + } + } + + if (selected === null) { + disabled = true; + + div.classList.add("danger-input"); + } else { + div.classList.remove("danger-input"); + } + } + + break; + case "text": + if (!question.optional) { + input = document.getElementById(question.id); + + if (input.value.length < 1) { + disabled = true; + div.classList.add("danger-input"); + } else { + div.classList.remove("danger-input"); + } + } + + break; + case "textarea": + if (!question.optional) { + input = document.getElementById(question.id); + + if (input.value.length < 1) { + disabled = true; + div.classList.add("danger-input"); + } else { + div.classList.remove("danger-input"); + } + } + + break; + case "slider": + break; + } + } + + button.disabled = disabled; + } + + function setUpChecks() { + let input, inputs, label; + + for (let question of questions) { + switch (question.type) { + case "checkbox": + input = document.getElementById(question.id); + input.onchange = checkInputs; + + break; + case "email": + input = document.getElementById(question.id); + input.oninput = checkInputs; + + break; + case "number": + input = document.getElementById(question.id); + input.oninput = checkInputs; + input.onchange = checkInputs; + + break; + case "radio": + inputs = document.getElementsByClassName("radio-" + question.id); + + for (let inner of inputs) { + inner.onchange = checkInputs; + } + + break; + case "range": + inputs = document.getElementsByClassName("radio-" + question.id); + + for (let inner of inputs) { + inner.onchange = checkInputs; + } + + break; + case "text": + input = document.getElementById(question.id); + input.oninput = checkInputs; + + break; + case "textarea": + input = document.getElementById(question.id); + input.oninput = checkInputs; + + break; + case "slider": + input = document.getElementById(question.id); + label = document.getElementById(question.id + "-slider-value"); + + input.oninput = function() { + label.textContent = this.value; + checkInputs(); + }; + break; + } + } + } + + setUpChecks(); + checkInputs(); +</script> +{% endblock %} diff --git a/templates/main/jams/profile.html b/templates/main/jams/profile.html new file mode 100644 index 00000000..6cf315d0 --- /dev/null +++ b/templates/main/jams/profile.html @@ -0,0 +1,106 @@ +{% extends "main/base.html" %} +{% block title %}Code Jams | Banned{% endblock %} +{% block og_title %}Code Jams | Banned{% endblock %} + +{% block extra_head %} + <script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.22.1/moment.min.js" type="application/javascript"></script> + <script src="https://cdnjs.cloudflare.com/ajax/libs/moment-timezone/0.5.17/moment-timezone.min.js" type="application/javascript"></script> +{% endblock %} + +{% block content %} +<div class="uk-section"> + <div class="uk-container uk-container-small"> + <h1 class="uk-header uk-article-title"> + Code Jams: My Profile + </h1> + + {% if done %} + <p class="uk-alert uk-alert-success"> + Thanks - your data has been saved! + </p> + {% else %} + <p class="uk-alert uk-alert-primary"> + Please make sure you've filled this out correctly, as we do use this data when evaluating your code jam + application. + <br /> + <br /> + You may come back here and edit your data at any time. + </p> + {% endif %} + + <form class="uk-form-horizontal" action="{{ url_for("main.jams.profile") }}" method="post"> + <div> + <div class="uk-form-label"> + <label class="uk-form-label" for="dob">Date of Birth</label> + </div> + <div class="uk-form-controls-text uk-form-controls"> + <input class="uk-input" type="text" name="dob" id="dob" value="{{ participant.dob }}" required> + </div> + </div> + <div> + <div class="uk-form-label"> + <label class="uk-form-label" for="github_username">GitHub Username</label> + </div> + <div class="uk-form-controls-text uk-form-controls"> + <input class="uk-input" type="text" name="github_username" id="github_username" value="{{ participant.github_username }}" required> + </div> + </div> + <div> + <div class="uk-form-label"> + <label class="uk-form-label" for="timezone">Timezone</label> + </div> + <div class="uk-form-controls-text uk-form-controls"> + <input class="uk-input" type="text" name="timezone" id="timezone" value="{{ participant.timezone }}" required> + </div> + </div> + <input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/> + <br /> + + <div class="uk-text-center"> + <a class="uk-button uk-button-default" href="{{ url_for("main.jams.index") }}"> + <i class="uk-icon fa-fw far fa-arrow-left"></i> Back + </a> + <button type="submit" class="uk-button uk-button-primary" id="submit"> + <i class="uk-icon fa-fw far fa-check"></i> Save + </button> + </div> + </form> + </div> +</div> + + + +<script type="application/javascript"> + const date = flatpickr("#dob", {enableTime: false, altInput: true}); + const tz = moment().format("Z"); + + const dob_input = document.getElementById("dob"); + const github_input = document.getElementById("github_username"); + const tz_input = document.getElementById("timezone"); + + const submit_button = document.getElementById("submit"); + + function checkInputs() { + if (dob_input.value.length < 1) + return submit_button.disabled = true; + + if (github_input.value.length < 1) + return submit_button.disabled = true; + + if (tz_input.value.length < 1) + return submit_button.disabled = true; + + submit_button.disabled = false; + } + + dob_input.oninput = checkInputs; + github_input.oninput = checkInputs; + tz_input.oninput = checkInputs; + + if (tz_input.value.length < 1) { + document.getElementById("timezone").value = "UTC" + tz; + } + + checkInputs(); +</script> +{% endblock %} diff --git a/templates/main/jams/signup.html b/templates/main/jams/signup.html deleted file mode 100644 index ddb48733..00000000 --- a/templates/main/jams/signup.html +++ /dev/null @@ -1,15 +0,0 @@ -{% extends "main/base.html" %} -{% block title %}Home{% endblock %} -{% block og_title %}Home{% endblock %} -{% block content %} -<div class="uk-section"> - <div class="uk-container uk-container-small uk-text-center"> - <h1 class="uk-header"> - Sign Up - </h1> - <p class="uk-article-meta"> - # TODO - </p> - </div> -</div> -{% endblock %} diff --git a/templates/main/jams/thanks.html b/templates/main/jams/thanks.html new file mode 100644 index 00000000..e6709485 --- /dev/null +++ b/templates/main/jams/thanks.html @@ -0,0 +1,25 @@ +{% extends "main/base.html" %} +{% block title %}Code Jams | Banned{% endblock %} +{% block og_title %}Code Jams | Banned{% endblock %} + +{% block content %} +<div class="uk-section"> + <div class="uk-container uk-container-small"> + <h1 class="uk-header uk-article-title"> + Code Jam {{ jam.number }}: {{ jam.title }} + </h1> + <p class="uk-article-meta"> + Bring the thunder! + </p> + + <p class="uk-alert uk-alert-success"> + Thanks for your application! Just sit back, relax, and we'll let you know whether you've been selected + for this code jam when the time comes. + </p> + + <a class="uk-button uk-button-secondary uk-align-center" href="{{ url_for("main.jams.index") }}"> + <i class="uk-icon fa-fw far fa-arrow-left"></i> Back to all code jams + </a> + </div> +</div> +{% endblock %} diff --git a/templates/main/navigation.html b/templates/main/navigation.html index 391bbcf5..3130747b 100644 --- a/templates/main/navigation.html +++ b/templates/main/navigation.html @@ -102,15 +102,15 @@ {% endif %} {% if current_page == "main.jams.index" %} - <li class="uk-active"><a href="{{ url_for('main.jams.index') }}">Info</a></li> + <li class="uk-active"><a href="{{ url_for('main.jams.index') }}">All Jams</a></li> {% else %} - <li><a href="{{ url_for('main.jams.index') }}">Info</a></li> + <li><a href="{{ url_for('main.jams.index') }}">All Jams</a></li> {% endif %} - {% if current_page == "main.jams.signup" %} - <li class="uk-active"><a href="{{ url_for('main.jams.signup') }}">Sign Up</a></li> + {% if current_page == "main.jams.profile" %} + <li class="uk-active"><a href="{{ url_for('main.jams.profile') }}">My Profile</a></li> {% else %} - <li><a href="{{ url_for('main.jams.signup') }}">Sign Up</a></li> + <li><a href="{{ url_for('main.jams.profile') }}">My Profile</a></li> {% endif %} <li class="uk-nav-divider"></li> diff --git a/templates/staff/jams/edit_basics.html b/templates/staff/jams/edit_basics.html index 59d69b77..e9bc69e1 100644 --- a/templates/staff/jams/edit_basics.html +++ b/templates/staff/jams/edit_basics.html @@ -66,7 +66,6 @@ </button> </div> </form> - </div> <script type="application/javascript"> diff --git a/templates/staff/jams/infractions/view.html b/templates/staff/jams/infractions/view.html index 0b515cdb..cace2369 100644 --- a/templates/staff/jams/infractions/view.html +++ b/templates/staff/jams/infractions/view.html @@ -51,9 +51,11 @@ <td class="uk-text-truncate" title="{{ infraction.id }}">{{ infraction.id }}</td> <td class="uk-table-shrink"> {% if infraction.participant is not string %} - {{ infraction.participant.username }}#{{ infraction.participant.discriminator }} ({{ infraction.participant.id }}) + <code>{{ infraction.participant.user_id }}</code> + <br /> + ({{ infraction.participant.username }}#{{ infraction.participant.discriminator }}) {% else %} - {{ infraction.participant }} + <code>{{ infraction.participant.user_id }}</code> {% endif %} </td> <td title="{{ infraction.reason }}">{{ infraction.reason }}</td> @@ -314,10 +316,10 @@ } const row = ` <td class="uk-table-shrink"> - <button class="uk-button-small uk-button uk-button-danger delete-infraction-button" style="padding-left: 5px; padding-right: 5px;" data-question-id="${infraction.id}"><i class="uk-icon fa-fw far fa-times"></i></button> + <button class="uk-button-small uk-button uk-button-danger delete-infraction-button" style="padding-left: 5px; padding-right: 5px;" data-infraction-id="${infraction.id}"><i class="uk-icon fa-fw far fa-trash"></i></button> </td> <td class="uk-text-truncate" title="${infraction.id}">${infraction.id}</td> - <td class="uk-table-shrink">${participant}</td> + <td class="uk-table-shrink"><code>${participant}</code></td> <td title="${infraction.reason}">${infraction.reason}</td> <td class="uk-table-shrink">${infraction.number}</td> `; |