-
Notifications
You must be signed in to change notification settings - Fork 11
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Announcements and Clarifications (#12)
* SQL models for Announcements and Clarifications * Verify the max length * Frontend template for admin/announcements * Implement admin/announcement endpoints * Implement contestant's view announcements page * Clars have updated_at, not created_at * Add shortcuts for links * Implement endpoints for admin/clarifications * Convert user UI announcements to merged Messaged * Change userID to string * Send clarification form * Reply clarifications form * No clarifications after the contest * Fix formatting * Merge undone JS stuff * Implement updates/notifications for user/messages * Fix copy typo * Some visual tweaks * Simple clarification counter for admin panel * Manage contest IDs in state * Add some announcements and clarifications to test data
- Loading branch information
1 parent
26704fa
commit 1c4932f
Showing
27 changed files
with
1,082 additions
and
16 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
BEGIN TRANSACTION; | ||
|
||
-- An "Announcements" table. | ||
CREATE TABLE announcements ( | ||
id INTEGER NOT NULL PRIMARY KEY, | ||
contest_id INTEGER NOT NULL, | ||
problem_id INTEGER, | ||
content BLOB NOT NULL, | ||
created_at DATETIME NOT NULL, | ||
|
||
FOREIGN KEY (contest_id) REFERENCES contests(id) ON DELETE CASCADE, | ||
FOREIGN KEY (problem_id) REFERENCES problems(id) ON DELETE CASCADE | ||
); | ||
CREATE INDEX announcements_by_contest ON announcements(contest_id ASC, id DESC); | ||
|
||
-- Clarifications table. | ||
CREATE TABLE clarifications ( | ||
id INTEGER NOT NULL PRIMARY KEY, | ||
user_id VARCHAR NOT NULL, | ||
contest_id INTEGER NOT NULL, | ||
problem_id INTEGER, | ||
|
||
content BLOB NOT NULL, | ||
updated_at DATETIME NOT NULL, -- Updated when responded | ||
|
||
response BLOB, | ||
|
||
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE, | ||
FOREIGN KEY (contest_id) REFERENCES contests(id) ON DELETE CASCADE, | ||
FOREIGN KEY (problem_id) REFERENCES problems(id) ON DELETE CASCADE | ||
); | ||
|
||
CREATE INDEX clarifications_by_user ON clarifications(contest_id ASC, user_id ASC, id DESC); | ||
|
||
COMMIT; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
{{ define "admin-title" }}Clarifications{{ end }} | ||
|
||
{{ define "admin-content" }} | ||
|
||
<div class="py-4 mx-auto text-4xl">Clarifications</div> | ||
|
||
<div class="p-2"> | ||
<div class="my-2"> | ||
<input id="show-unanswered" type="checkbox"> | ||
<label for="show-unanswered">Show only unanswered clarifications.</label> | ||
</div> | ||
{{ range .Clarifications }} | ||
<div class="mt-2 p-2 bg-gray-200 rounded border hover:bg-blue-200 clarification" data-answered="{{.Responded}}"> | ||
<div class="flex flex-row justify-between"> | ||
<div> | ||
<div class="text-sm"> | ||
{{ with (index $.Contests .ContestID) }} | ||
{{ $contestLink := .AdminLink }} | ||
Contest: | ||
<a href="{{$contestLink}}" class="font-semibold hover:text-blue-600">{{.Name}}</a> | ||
{{ end }} | ||
</div> | ||
<div class="text-2xl"> | ||
{{ if .ProblemID.Valid }} | ||
{{ with (index $.Problems .ProblemID.Int64) }} | ||
{{ $problemLink := .AdminLink }} | ||
re: | ||
<a href="{{$problemLink}}" class="font-semibold hover:text-blue-600"> | ||
{{.Name}}. {{.DisplayName}} | ||
</a> | ||
{{ end }} | ||
{{ else }} | ||
General Question | ||
{{ end }} | ||
</div> | ||
</div> | ||
<div class="text-right"> | ||
<div class="text-sm italic display-time" data-time="{{.UpdatedAt | time}}"></div> | ||
<div>By: | ||
{{ $user_link := printf "/admin/users/%s" .UserID }} | ||
<a href="{{$user_link}}" class="font-semibold hover:text-blue-600">{{.UserID}}</a> | ||
</div> | ||
</div> | ||
</div> | ||
<pre class="whitespace-pre-wrap m-2">{{- printf "%s" .Content -}}</pre> | ||
{{ if .Response }} | ||
<div> | ||
<div class="text-lg">Response:</div> | ||
<pre class="whitespace-pre-wrap mt-1 mx-2">{{- printf "%s" .Response -}}</pre> | ||
</div> | ||
{{ else }} | ||
<form class="form-block" method="POST" action="{{.AdminLink}}"> | ||
<label for="response" class="text-sm block">Response</label> | ||
<textarea class="form-input text-lg overflow-y-auto whitespace-pre-wrap leading-relaxed h-20" | ||
name="response" maxlength="2048" required></textarea> | ||
|
||
<label for="premade" class="text-sm block">Template Answers</label> | ||
<select class="form-input premade"> | ||
<option value="" selected>None</option> | ||
<option value="Yes">Yes</option> | ||
<option value="No">No</option> | ||
<option value="Read problem description">Read problem description</option> | ||
<option value="No Answer">No Answer</option> | ||
</select> | ||
|
||
<div class="mt-2"> | ||
<input required type="submit" class="form-btn bg-green-200 hover:bg-green-300" value="Submit"> | ||
<input required type="reset" class="form-btn bg-red-200 hover:bg-red-300" value="Reset"> | ||
</div> | ||
</form> | ||
{{ end }} | ||
</div> | ||
<div id="no-unanswered" class="hidden text-center text-lg">No unanswered Clarifications, great!</div> | ||
{{ else }} | ||
<div class="text-center text-lg">No Clarifications</div> | ||
{{ end }} | ||
<script src="../../ts/admin_clarifications.ts"></script> | ||
</div> | ||
|
||
{{ end }} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
{{ define "admin-title" }}Announcements - {{.Contest.Name}}{{ end }} | ||
|
||
{{ define "admin-nav" }} | ||
<nav> | ||
<a href="#new"> | ||
<div class="bg-gray-200 rounded-sm hover:bg-gray-400 m-2 py-1 ml-2 pl-4">New Announcement</div> | ||
</a> | ||
<a href="#list"> | ||
<div class="bg-gray-200 rounded-sm hover:bg-gray-400 m-2 py-1 ml-4 pl-4">Past Announcements</div> | ||
</a> | ||
</nav> | ||
{{ end }} | ||
|
||
{{ define "admin-content" }} | ||
{{ $contest_link := printf "/admin/contests/%d" .Contest.ID }} | ||
<div class="py-4 mx-auto"> | ||
<a class="text-3xl text-gray-600 hover:text-blue-600 cursor-pointer" href="{{$contest_link}}"> | ||
{{.Contest.Name}} | ||
</a> | ||
<span>>></span> | ||
<span class="text-4xl">Announcements</span> | ||
</div> | ||
|
||
<div class="subheader" id="new">New Announcement</div> | ||
{{ template "form-error" .Error }} | ||
<form method="POST" action="{{$contest_link}}/announcements" class="form-block"> | ||
<label for="problem" class="text-sm block">For problem</label> | ||
<select class="form-input" id="problem" name="problem"> | ||
{{ if eq .Form.Problem 0 }} | ||
<option selected value="0">General Annonuncement</option> | ||
{{ else }} | ||
<option value="0">General Annonuncement</option> | ||
{{ end }} | ||
{{ range .Problems }} | ||
{{ if eq $.Form.Problem .ID }} | ||
<option selected value="{{.ID}}">{{.Name}}. {{.DisplayName}}</option> | ||
{{ else }} | ||
<option value="{{.ID}}">{{.Name}}. {{.DisplayName}}</option> | ||
{{ end }} | ||
{{ end }} | ||
</select> | ||
|
||
<label for="content" class="text-sm block">Content</label> | ||
<textarea id="content" class="form-input text-lg overflow-y-auto whitespace-pre-wrap leading-relaxed h-40" | ||
name="content" maxlength="2048" required>{{.Form.Content}}</textarea> | ||
|
||
<div class="mt-2"> | ||
<input required type="submit" class="form-btn bg-green-200 hover:bg-green-300" value="Submit"> | ||
<input required type="reset" class="form-btn bg-red-200 hover:bg-red-300" value="Reset"> | ||
</div> | ||
</form> | ||
|
||
<div class="subheader" id="list">Past Announcements</div> | ||
<div class="p-2"> | ||
{{ range .Announcements }} | ||
<div class="p-2 border rounded-sm bg-gray-200 hover:bg-blue-200 my-2"> | ||
<div class="flex flex-row justify-between"> | ||
<div class="text-2xl"> | ||
{{ if .ProblemID.Valid }} | ||
{{ with (index $.Problems .ProblemID.Int64) }} | ||
{{ $problem_link := printf "/admin/problems/%d" .ID }} | ||
Problem | ||
<a href="{{$problem_link}}" class="font-semibold hover:text-blue-600">{{.Name}}. {{.DisplayName}}</a> | ||
{{ end }} | ||
{{ else }} | ||
<span class="font-semibold">General Announcement</span> | ||
{{ end }} | ||
</div> | ||
<div class="text-sm italic display-time" data-time="{{.CreatedAt | time}}"></div> | ||
</div> | ||
<pre class="mt-2 overflow-y-auto whitespace-pre-wrap"> | ||
{{- printf "%s" .Content -}} | ||
</pre> | ||
</div> | ||
{{ else }} | ||
<div class="text-center">No announcements</div> | ||
{{ end }} | ||
</div> | ||
|
||
{{ end }} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,130 @@ | ||
{{ define "inner-title" }}Messages{{ end }} | ||
|
||
{{ define "content" }} | ||
<div class="text-4xl py-4"><b>{{.Contest.Name}}</b>: Messages</div> | ||
|
||
<div class="text-xl my-2 text-gray-800 timer" data-start="{{.Contest.StartTime | time}}" | ||
data-end="{{.Contest.EndTime | time}}"><span class="font-semibold"></span></div> | ||
|
||
{{ if isFuture .Contest.EndTime }} | ||
<a class="text-lg inline-block rounded p-2 bg-green-200 hover:bg-green-300" href="#send-clarification">Request a | ||
Clarification</a> | ||
{{ end }} | ||
|
||
<div class="subheader">Messages</div> | ||
<div class="p-2"> | ||
{{ range .Messages }} | ||
{{ with .Announcement }} | ||
{{ template "announcement" (zip . $) }} | ||
{{ end }} | ||
{{ with .Clarification }} | ||
{{ template "clarification" (zip . $) }} | ||
{{ end }} | ||
{{ else }} | ||
<div class="text-center">No messages</div> | ||
{{ end }} | ||
</div> | ||
|
||
{{ if isFuture .Contest.EndTime }} | ||
<div class="subheader" id="send-clarification">Request a Clarification</div> | ||
{{ template "form-error" .FormError }} | ||
{{ template "send-clarification" . }} | ||
{{ end }} | ||
|
||
<script> | ||
window.announcements.markUnread(); | ||
window.announcements.setLast(); | ||
</script> | ||
|
||
{{ end }} | ||
|
||
{{ define "send-clarification" }} | ||
{{ $form_link := printf "/contests/%d/messages" .Contest.ID }} | ||
<form class="form-block" method="POST" action="{{$form_link}}"> | ||
<label for="problem" class="text-sm block">For problem</label> | ||
<select class="form-input" id="problem" name="problem"> | ||
{{ if eq .Form.Problem 0 }} | ||
<option selected value="0">General Question</option> | ||
{{ else }} | ||
<option value="0">General Question</option> | ||
{{ end }} | ||
{{ range .Problems }} | ||
{{ if eq $.Form.Problem .ID }} | ||
<option selected value="{{.ID}}">{{.Name}}. {{.DisplayName}}</option> | ||
{{ else }} | ||
<option value="{{.ID}}">{{.Name}}. {{.DisplayName}}</option> | ||
{{ end }} | ||
{{ end }} | ||
</select> | ||
|
||
<label for="content" class="text-sm block">Content</label> | ||
<textarea id="content" class="form-input text-lg overflow-y-auto whitespace-pre-wrap leading-relaxed h-40" | ||
name="content" maxlength="2048" required>{{.Form.Content}}</textarea> | ||
|
||
<div class="mt-2"> | ||
<input required type="submit" class="form-btn bg-green-200 hover:bg-green-300" value="Submit"> | ||
<input required type="reset" class="form-btn bg-red-200 hover:bg-red-300" value="Reset"> | ||
</div> | ||
</form> | ||
{{ end }} | ||
|
||
{{ define "clarification" }} | ||
{{ with (index . 0) }} | ||
<div class="mt-2 p-2 {{if .Responded}}bg-gray-200{{else}}bg-red-200{{end}} rounded border hover:bg-blue-200 clarification" | ||
data-id="{{.ID}}" data-responded="{{.Responded}}"> | ||
<div class="flex flex-row justify-between"> | ||
<div> | ||
<div class="text-2xl"> | ||
{{ if .ProblemID.Valid }} | ||
{{ with (index (index $ 1).ProblemsMap .ProblemID.Int64) }} | ||
{{ $problem_link := .Link }} | ||
re: | ||
<a href="{{$problem_link}}" class="font-semibold hover:text-blue-600"> | ||
{{.Name}}. {{.DisplayName}} | ||
</a> | ||
{{ end }} | ||
{{ else }} | ||
General Question | ||
{{ end }} | ||
</div> | ||
</div> | ||
<div class="text-right"> | ||
<div class="text-sm italic display-time" data-time="{{.UpdatedAt | time}}"></div> | ||
</div> | ||
</div> | ||
<pre class="whitespace-pre-wrap m-2">{{- printf "%s" .Content -}}</pre> | ||
{{ if .Response }} | ||
<div> | ||
<div class="text-lg">Response:</div> | ||
<pre class="whitespace-pre-wrap mt-1 mx-2">{{- printf "%s" .Response -}}</pre> | ||
</div> | ||
{{ else }} | ||
<div class="mt-1 text-bold">No responses yet.</div> | ||
{{ end }} | ||
</div> | ||
{{ end }} | ||
{{ end }} | ||
|
||
{{ define "announcement" }} | ||
{{ with (index . 0) }} | ||
<div class="mt-2 p-2 border rounded-sm bg-gray-200 hover:bg-blue-200 my-2 announcement" data-id="{{.ID}}"> | ||
<div class="flex flex-row justify-between"> | ||
<div class="text-2xl"> | ||
{{ if .ProblemID.Valid }} | ||
{{ with (index (index $ 1).ProblemsMap .ProblemID.Int64) }} | ||
Problem | ||
{{ $problem_link := .Link }} | ||
<a href="{{$problem_link}}" class="font-semibold hover:text-blue-600">{{.Name}}. {{.DisplayName}}</a> | ||
{{ end }} | ||
{{ else }} | ||
<span class="font-semibold">General Announcement</span> | ||
{{ end }} | ||
</div> | ||
<div class="text-sm italic display-time" data-time="{{.CreatedAt | time}}"></div> | ||
</div> | ||
<pre class="mt-2 mx-2 overflow-y-auto whitespace-pre-wrap"> | ||
{{- printf "%s" .Content -}} | ||
</pre> | ||
</div> | ||
{{ end }} | ||
{{ end }} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Binary file not shown.
Oops, something went wrong.