From e446db893ecb9f08f588a5763a10034ccb0ec224 Mon Sep 17 00:00:00 2001
From: Rushil Umaretiya <rushilwiz@gmail.com>
Date: Mon, 20 Sep 2021 05:56:11 -0400
Subject: [PATCH] feat: finished frontend, added detail page, and categories

---
 Pipfile                                       |  1 +
 Pipfile.lock                                  | 96 ++++++++++++-------
 hunt/apps/main/admin.py                       |  3 +-
 hunt/apps/main/migrations/0003_category.py    | 23 +++++
 .../migrations/0004_auto_20210920_0804.py     | 23 +++++
 .../migrations/0005_auto_20210920_0818.py     | 25 +++++
 .../main/migrations/0006_category_category.py | 19 ++++
 .../migrations/0007_challenge_available.py    | 18 ++++
 ...08_rename_available_challenge_unblocked.py | 18 ++++
 hunt/apps/main/models.py                      | 16 +++-
 hunt/apps/main/urls.py                        |  1 +
 hunt/apps/main/views.py                       | 47 ++++++---
 hunt/settings/__init__.py                     |  1 +
 hunt/static/css/detail.css                    | 11 +++
 hunt/static/css/index.css                     | 10 ++
 hunt/templates/base_with_nav.html             |  5 +-
 hunt/templates/main/detail.html               | 55 +++++++++++
 hunt/templates/main/index.html                | 72 +++++++++-----
 18 files changed, 368 insertions(+), 76 deletions(-)
 create mode 100644 hunt/apps/main/migrations/0003_category.py
 create mode 100644 hunt/apps/main/migrations/0004_auto_20210920_0804.py
 create mode 100644 hunt/apps/main/migrations/0005_auto_20210920_0818.py
 create mode 100644 hunt/apps/main/migrations/0006_category_category.py
 create mode 100644 hunt/apps/main/migrations/0007_challenge_available.py
 create mode 100644 hunt/apps/main/migrations/0008_rename_available_challenge_unblocked.py
 create mode 100644 hunt/static/css/detail.css
 create mode 100644 hunt/templates/main/detail.html

diff --git a/Pipfile b/Pipfile
index 2f58703..70d2d4f 100644
--- a/Pipfile
+++ b/Pipfile
@@ -9,3 +9,4 @@ psycopg2-binary = "~=2.8.6"
 social-auth-app-django = "~=4.0.0"
 gunicorn = "*"
 whitenoise = "*"
+django-ckeditor = "*"
\ No newline at end of file
diff --git a/Pipfile.lock b/Pipfile.lock
index 3b5f4c3..56161cb 100644
--- a/Pipfile.lock
+++ b/Pipfile.lock
@@ -1,7 +1,7 @@
 {
     "_meta": {
         "hash": {
-            "sha256": "b4008672fb38a3e89baf1655aa6b39685d638b0b7f6012ef5fb8da7b48ed33c2"
+            "sha256": "91d9b3fd6ebf46b5342b7ad4b05cbcb362e289f9ec1abf7e931ab806a9751afe"
         },
         "pipfile-spec": 6,
         "requires": {},
@@ -19,7 +19,6 @@
                 "sha256:4ef1ab46b484e3c706329cedeff284a5d40824200638503f5768edb6de7d58e9",
                 "sha256:ffc141aa908e6f175673e7b1b3b7af4fdb0ecb738fc5c8b88f69f055c2415214"
             ],
-            "markers": "python_version >= '3.6'",
             "version": "==3.4.1"
         },
         "certifi": {
@@ -81,47 +80,72 @@
         },
         "charset-normalizer": {
             "hashes": [
-                "sha256:0c8911edd15d19223366a194a513099a302055a962bca2cec0f54b8b63175d8b",
-                "sha256:f23667ebe1084be45f6ae0538e4a5a865206544097e4e8bbcacf42cd02a348f3"
+                "sha256:5d209c0a931f215cee683b6445e2d77677e7e75e159f78def0db09d68fafcaa6",
+                "sha256:5ec46d183433dcbd0ab716f2d7f29d8dee50505b3fdb40c6b985c7c4f5a3591f"
             ],
             "markers": "python_version >= '3'",
-            "version": "==2.0.4"
+            "version": "==2.0.6"
         },
         "cryptography": {
             "hashes": [
-                "sha256:0f1212a66329c80d68aeeb39b8a16d54ef57071bf22ff4e521657b27372e327d",
-                "sha256:1e056c28420c072c5e3cb36e2b23ee55e260cb04eee08f702e0edfec3fb51959",
-                "sha256:240f5c21aef0b73f40bb9f78d2caff73186700bf1bc6b94285699aff98cc16c6",
-                "sha256:26965837447f9c82f1855e0bc8bc4fb910240b6e0d16a664bb722df3b5b06873",
-                "sha256:37340614f8a5d2fb9aeea67fd159bfe4f5f4ed535b1090ce8ec428b2f15a11f2",
-                "sha256:3d10de8116d25649631977cb37da6cbdd2d6fa0e0281d014a5b7d337255ca713",
-                "sha256:3d8427734c781ea5f1b41d6589c293089704d4759e34597dce91014ac125aad1",
-                "sha256:7ec5d3b029f5fa2b179325908b9cd93db28ab7b85bb6c1db56b10e0b54235177",
-                "sha256:8e56e16617872b0957d1c9742a3f94b43533447fd78321514abbe7db216aa250",
-                "sha256:b01fd6f2737816cb1e08ed4807ae194404790eac7ad030b34f2ce72b332f5586",
-                "sha256:bf40af59ca2465b24e54f671b2de2c59257ddc4f7e5706dbd6930e26823668d3",
-                "sha256:de4e5f7f68220d92b7637fc99847475b59154b7a1b3868fb7385337af54ac9ca",
-                "sha256:eb8cc2afe8b05acbd84a43905832ec78e7b3873fb124ca190f574dca7389a87d",
-                "sha256:ee77aa129f481be46f8d92a1a7db57269a2f23052d5f2433b4621bb457081cc9"
+                "sha256:0a7dcbcd3f1913f664aca35d47c1331fce738d44ec34b7be8b9d332151b0b01e",
+                "sha256:1eb7bb0df6f6f583dd8e054689def236255161ebbcf62b226454ab9ec663746b",
+                "sha256:21ca464b3a4b8d8e86ba0ee5045e103a1fcfac3b39319727bc0fc58c09c6aff7",
+                "sha256:34dae04a0dce5730d8eb7894eab617d8a70d0c97da76b905de9efb7128ad7085",
+                "sha256:3520667fda779eb788ea00080124875be18f2d8f0848ec00733c0ec3bb8219fc",
+                "sha256:3fa3a7ccf96e826affdf1a0a9432be74dc73423125c8f96a909e3835a5ef194a",
+                "sha256:5b0fbfae7ff7febdb74b574055c7466da334a5371f253732d7e2e7525d570498",
+                "sha256:695104a9223a7239d155d7627ad912953b540929ef97ae0c34c7b8bf30857e89",
+                "sha256:8695456444f277af73a4877db9fc979849cd3ee74c198d04fc0776ebc3db52b9",
+                "sha256:94cc5ed4ceaefcbe5bf38c8fba6a21fc1d365bb8fb826ea1688e3370b2e24a1c",
+                "sha256:94fff993ee9bc1b2440d3b7243d488c6a3d9724cc2b09cdb297f6a886d040ef7",
+                "sha256:9965c46c674ba8cc572bc09a03f4c649292ee73e1b683adb1ce81e82e9a6a0fb",
+                "sha256:a00cf305f07b26c351d8d4e1af84ad7501eca8a342dedf24a7acb0e7b7406e14",
+                "sha256:a305600e7a6b7b855cd798e00278161b681ad6e9b7eca94c721d5f588ab212af",
+                "sha256:cd65b60cfe004790c795cc35f272e41a3df4631e2fb6b35aa7ac6ef2859d554e",
+                "sha256:d2a6e5ef66503da51d2110edf6c403dc6b494cc0082f85db12f54e9c5d4c3ec5",
+                "sha256:d9ec0e67a14f9d1d48dd87a2531009a9b251c02ea42851c060b25c782516ff06",
+                "sha256:f44d141b8c4ea5eb4dbc9b3ad992d45580c1d22bf5e24363f2fbf50c2d7ae8a7"
             ],
-            "markers": "python_version >= '3.6'",
-            "version": "==3.4.7"
+            "version": "==3.4.8"
         },
         "defusedxml": {
             "hashes": [
                 "sha256:1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69",
                 "sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61"
             ],
-            "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
             "version": "==0.7.1"
         },
         "django": {
             "hashes": [
-                "sha256:7f92413529aa0e291f3be78ab19be31aefb1e1c9a52cd59e130f505f27a51f13",
-                "sha256:f27f8544c9d4c383bbe007c57e3235918e258364577373d4920e9162837be022"
+                "sha256:95b318319d6997bac3595517101ad9cc83fe5672ac498ba48d1a410f47afecd2",
+                "sha256:e93c93565005b37ddebf2396b4dc4b6913c1838baa82efdfb79acedd5816c240"
             ],
             "index": "pypi",
-            "version": "==3.2.6"
+            "version": "==3.2.7"
+        },
+        "django-ckeditor": {
+            "hashes": [
+                "sha256:346b26b9d60dc8a88524d0eaaf406f4e91a4b3c22d208ae87aa032bf500b251c",
+                "sha256:f0d108f67a81a04e26d8de11255fe314f51026eaf8eb0534a807512ae3c21620"
+            ],
+            "index": "pypi",
+            "version": "==6.1.0"
+        },
+        "django-js-asset": {
+            "hashes": [
+                "sha256:8ec12017f26eec524cab436c64ae73033368a372970af4cf42d9354fcb166bdd",
+                "sha256:c163ae80d2e0b22d8fb598047cd0dcef31f81830e127cfecae278ad574167260"
+            ],
+            "version": "==1.2.2"
+        },
+        "gunicorn": {
+            "hashes": [
+                "sha256:9dcc4547dbb1cb284accfb15ab5667a0e5d1881cc443e0677b4882a4067a807e",
+                "sha256:e0a968b5ba15f8a328fdfd7ab1fcb5af4470c28aaf7e55df02a99bc13138e6e8"
+            ],
+            "index": "pypi",
+            "version": "==20.1.0"
         },
         "idna": {
             "hashes": [
@@ -136,7 +160,6 @@
                 "sha256:42bf6354c2ed8c6acb54d971fce6f88193d97297e18602a3a886603f9d7730cc",
                 "sha256:8f0215fcc533dd8dd1bee6f4c412d4f0cd7297307d43ac61666389e3bc3198a3"
             ],
-            "markers": "python_version >= '3.6'",
             "version": "==3.1.1"
         },
         "psycopg2-binary": {
@@ -185,7 +208,6 @@
                 "sha256:2d475327684562c3a96cc71adf7dc8c4f0565175cf86b6d7a404ff4c771f15f0",
                 "sha256:7582ad22678f0fcd81102833f60ef8d0e57288b6b5fb00323d101be910e35705"
             ],
-            "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
             "version": "==2.20"
         },
         "pyjwt": {
@@ -193,7 +215,6 @@
                 "sha256:934d73fbba91b0483d3857d1aff50e96b2a892384ee2c17417ed3203f173fca1",
                 "sha256:fba44e7898bbca160a2b2b501f492824fc8382485d3a6f11ba5d0c1937ce6130"
             ],
-            "markers": "python_version >= '3.6'",
             "version": "==2.1.0"
         },
         "python3-openid": {
@@ -215,7 +236,6 @@
                 "sha256:6c1246513ecd5ecd4528a0906f910e8f0f9c6b8ec72030dc9fd154dc1a6efd24",
                 "sha256:b8aa58f8cf793ffd8782d3d8cb19e66ef36f7aba4353eec859e74678b01b07a7"
             ],
-            "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'",
             "version": "==2.26.0"
         },
         "requests-oauthlib": {
@@ -231,7 +251,6 @@
                 "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926",
                 "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"
             ],
-            "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
             "version": "==1.16.0"
         },
         "social-auth-app-django": {
@@ -248,24 +267,29 @@
                 "sha256:5ab43b3b15dce5f059db69cc3082c216574739f0edbc98629c8c6e8769c67eb4",
                 "sha256:983b53167ac56e7ba4909db555602a6e7a98c97ca47183bb222eb85ba627bf2b"
             ],
-            "markers": "python_version >= '3.6'",
             "version": "==4.1.0"
         },
         "sqlparse": {
             "hashes": [
-                "sha256:017cde379adbd6a1f15a61873f43e8274179378e95ef3fede90b5aa64d304ed0",
-                "sha256:0f91fd2e829c44362cbcfab3e9ae12e22badaa8a29ad5ff599f9ec109f0454e8"
+                "sha256:0c00730c74263a94e5a9919ade150dfc3b19c574389985446148402998287dae",
+                "sha256:48719e356bb8b42991bdbb1e8b83223757b93789c00910a616a071910ca4a64d"
             ],
-            "markers": "python_version >= '3.5'",
-            "version": "==0.4.1"
+            "version": "==0.4.2"
         },
         "urllib3": {
             "hashes": [
                 "sha256:39fb8672126159acb139a7718dd10806104dec1e2f0f6c88aab05d17df10c8d4",
                 "sha256:f57b4c16c62fa2760b7e3d97c35b255512fb6b59a259730f36ba32ce9f8e342f"
             ],
-            "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_version < '4'",
             "version": "==1.26.6"
+        },
+        "whitenoise": {
+            "hashes": [
+                "sha256:d234b871b52271ae7ed6d9da47ffe857c76568f11dd30e28e18c5869dbd11e12",
+                "sha256:d963ef25639d1417e8a247be36e6aedd8c7c6f0a08adcb5a89146980a96b577c"
+            ],
+            "index": "pypi",
+            "version": "==5.3.0"
         }
     },
     "develop": {}
diff --git a/hunt/apps/main/admin.py b/hunt/apps/main/admin.py
index b91b2e1..82cdaf7 100644
--- a/hunt/apps/main/admin.py
+++ b/hunt/apps/main/admin.py
@@ -1,6 +1,7 @@
 from django.contrib import admin
 
-from .models import Challenge, Class
+from .models import Challenge, Class, Category
 
+admin.site.register(Category)
 admin.site.register(Challenge)
 admin.site.register(Class)
diff --git a/hunt/apps/main/migrations/0003_category.py b/hunt/apps/main/migrations/0003_category.py
new file mode 100644
index 0000000..d5f40fe
--- /dev/null
+++ b/hunt/apps/main/migrations/0003_category.py
@@ -0,0 +1,23 @@
+# Generated by Django 3.2.7 on 2021-09-20 08:01
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('main', '0002_alter_class_challenges_completed'),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='Category',
+            fields=[
+                ('id', models.AutoField(primary_key=True, serialize=False)),
+                ('name', models.CharField(max_length=200)),
+                ('slug', models.SlugField(max_length=200)),
+                ('category', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='challenges', to='main.category')),
+            ],
+        ),
+    ]
diff --git a/hunt/apps/main/migrations/0004_auto_20210920_0804.py b/hunt/apps/main/migrations/0004_auto_20210920_0804.py
new file mode 100644
index 0000000..1117930
--- /dev/null
+++ b/hunt/apps/main/migrations/0004_auto_20210920_0804.py
@@ -0,0 +1,23 @@
+# Generated by Django 3.2.7 on 2021-09-20 08:04
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('main', '0003_category'),
+    ]
+
+    operations = [
+        migrations.RemoveField(
+            model_name='category',
+            name='category',
+        ),
+        migrations.AddField(
+            model_name='challenge',
+            name='category',
+            field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='challenges', to='main.category'),
+        ),
+    ]
diff --git a/hunt/apps/main/migrations/0005_auto_20210920_0818.py b/hunt/apps/main/migrations/0005_auto_20210920_0818.py
new file mode 100644
index 0000000..2bdfc86
--- /dev/null
+++ b/hunt/apps/main/migrations/0005_auto_20210920_0818.py
@@ -0,0 +1,25 @@
+# Generated by Django 3.2.7 on 2021-09-20 08:18
+
+import ckeditor.fields
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('main', '0004_auto_20210920_0804'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='challenge',
+            name='short_description',
+            field=models.CharField(default='', max_length=500),
+            preserve_default=False,
+        ),
+        migrations.AlterField(
+            model_name='challenge',
+            name='description',
+            field=ckeditor.fields.RichTextField(),
+        ),
+    ]
diff --git a/hunt/apps/main/migrations/0006_category_category.py b/hunt/apps/main/migrations/0006_category_category.py
new file mode 100644
index 0000000..b7b41aa
--- /dev/null
+++ b/hunt/apps/main/migrations/0006_category_category.py
@@ -0,0 +1,19 @@
+# Generated by Django 3.2.7 on 2021-09-20 08:21
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('main', '0005_auto_20210920_0818'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='category',
+            name='category',
+            field=models.CharField(default='', max_length=200),
+            preserve_default=False,
+        ),
+    ]
diff --git a/hunt/apps/main/migrations/0007_challenge_available.py b/hunt/apps/main/migrations/0007_challenge_available.py
new file mode 100644
index 0000000..2da7223
--- /dev/null
+++ b/hunt/apps/main/migrations/0007_challenge_available.py
@@ -0,0 +1,18 @@
+# Generated by Django 3.2.7 on 2021-09-20 08:36
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('main', '0006_category_category'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='challenge',
+            name='available',
+            field=models.BooleanField(default=False),
+        ),
+    ]
diff --git a/hunt/apps/main/migrations/0008_rename_available_challenge_unblocked.py b/hunt/apps/main/migrations/0008_rename_available_challenge_unblocked.py
new file mode 100644
index 0000000..651d59d
--- /dev/null
+++ b/hunt/apps/main/migrations/0008_rename_available_challenge_unblocked.py
@@ -0,0 +1,18 @@
+# Generated by Django 3.2.7 on 2021-09-20 08:39
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('main', '0007_challenge_available'),
+    ]
+
+    operations = [
+        migrations.RenameField(
+            model_name='challenge',
+            old_name='available',
+            new_name='unblocked',
+        ),
+    ]
diff --git a/hunt/apps/main/models.py b/hunt/apps/main/models.py
index de21a72..b485800 100644
--- a/hunt/apps/main/models.py
+++ b/hunt/apps/main/models.py
@@ -1,15 +1,26 @@
 from django.db import models
 from django.db.models.fields.related import ManyToManyField
+from ckeditor.fields import RichTextField
 
+class Category(models.Model):
+    id = models.AutoField(primary_key=True, null=False, blank=False)
+    name = models.CharField(max_length=200, null=False, blank=False)
+    category = models.CharField(max_length=200, null=False, blank=False)
+
+    def __str__(self):
+        return self.name
 
 class Challenge(models.Model):
     id = models.AutoField(primary_key=True, null=False, blank=False)
     name = models.CharField(max_length=100, null=False, blank=False)
-    description = models.CharField(max_length=500, null=False, blank=False)
+    short_description = models.CharField(max_length=500, null=False, blank=False)
+    description = RichTextField(null=False, blank=False)
     flag = models.CharField(max_length=50, null=False, blank=False)
     points = models.IntegerField(null=False, blank=False)
     exclusive = models.BooleanField(default=False)
     locked = models.BooleanField(default=False)
+    unblocked = models.BooleanField(default=False)
+    category = models.ForeignKey(Category, null=True, blank=True, related_name='challenges', on_delete=models.SET_NULL)
 
     def __str__(self):
         return "{} ({})".format(self.name, self.id)
@@ -30,5 +41,4 @@ class Class(models.Model):
         sum = 0
         for c in self.challenges_completed.all():
             sum += c.points
-        return sum
-    
\ No newline at end of file
+        return sum
\ No newline at end of file
diff --git a/hunt/apps/main/urls.py b/hunt/apps/main/urls.py
index 91306a0..2298a1c 100644
--- a/hunt/apps/main/urls.py
+++ b/hunt/apps/main/urls.py
@@ -8,4 +8,5 @@ urlpatterns = [
     path("", views.index, name="index"),
     path("overview/", views.overview, name="overview"),
     path("validate/", views.validate_flag, name="validate_flag"),
+    path("challenge/<int:challenge_id>", views.challenge_detail, name="challenge_detail")
 ]
\ No newline at end of file
diff --git a/hunt/apps/main/views.py b/hunt/apps/main/views.py
index 9cda674..f4180d7 100644
--- a/hunt/apps/main/views.py
+++ b/hunt/apps/main/views.py
@@ -4,12 +4,12 @@ from django.http.response import JsonResponse
 from django.shortcuts import get_object_or_404, redirect, render
 from django.urls import reverse
 
-from .models import Challenge, Class
+from .models import Challenge, Class, Category
 
 
 @login_required
 def index(request):
-    if request.user.is_participant():
+    if request.user.is_participant() or request.user.is_staff:
         """
         Challenges fall into one of three statuses with respect to the user:
             - available (user can complete)
@@ -17,15 +17,18 @@ def index(request):
             - locked (can only be completed by one class and has been completed)
         """
         challenges_completed_by_class = set(Class.objects.get(year=str(request.user.graduation_year)).challenges_completed.all())
-        challenges_dict = dict()
-        for c in Challenge.objects.all():
-            if c in challenges_completed_by_class:
-                challenges_dict[c.id] = [c, "completed"]
-            elif c.locked:
-                challenges_dict[c.id] = [c, "locked"]
-            else:
-                challenges_dict[c.id] = [c, "available"]
-        return render(request, 'main/index.html', context={"challenges_dict": challenges_dict})
+        categories_dict = dict()
+        for category in Category.objects.all():    
+            challenges_dict = dict()
+            for c in category.challenges.all():
+                if c in challenges_completed_by_class:
+                    challenges_dict[c.id] = [c, "completed"]
+                elif c.locked:
+                    challenges_dict[c.id] = [c, "locked"]
+                else:
+                    challenges_dict[c.id] = [c, "available"]
+            categories_dict[category.id] = [category, challenges_dict]
+        return render(request, "main/index.html", context={"categories": categories_dict})
     else:
         return redirect(reverse("main:overview"))
 
@@ -34,9 +37,26 @@ def overview(request):
     data = sorted([(c.year, c.get_points()) for c in Class.objects.all()])
     return render(request, 'main/overview.html', context={'data': data})
     
+@login_required
+def challenge_detail(request, challenge_id):
+    if request.user.is_participant() or request.user.is_staff:
+        c = get_object_or_404(Challenge, pk=challenge_id)
+        challenges_completed_by_class = set(Class.objects.get(year=str(request.user.graduation_year)).challenges_completed.all())
+        if not c.unblocked:
+            status = 'blocked'
+        elif c in challenges_completed_by_class:
+            status = 'completed'
+        elif c.locked:
+            status = 'locked'
+        else:
+            status = 'available'
+        return render(request, 'main/detail.html', context={'status': status, 'challenge': c})
+    else:
+        return redirect(reverse("main:overview"))
+
 @login_required
 def validate_flag(request):
-    if request.is_ajax() and request.user.is_participant():
+    if request.is_ajax() and (request.user.is_participant() or request.user.is_staff):
         challenge = get_object_or_404(Challenge, id=int(request.POST.get("challenge_id")))
         flag = request.POST.get("flag")
         if flag == challenge.flag:
@@ -53,4 +73,5 @@ def validate_flag(request):
             response = {"result": "failure"}
         return JsonResponse(response)
     else:
-        return PermissionDenied
\ No newline at end of file
+        return PermissionDenied
+    
\ No newline at end of file
diff --git a/hunt/settings/__init__.py b/hunt/settings/__init__.py
index 7077fa6..cc0d4e1 100644
--- a/hunt/settings/__init__.py
+++ b/hunt/settings/__init__.py
@@ -38,6 +38,7 @@ INSTALLED_APPS = [
     'django.contrib.messages',
     'django.contrib.staticfiles',
     'social_django',
+    'ckeditor',
     'hunt.apps.main',
     'hunt.apps.auth',
     'hunt.apps.users',
diff --git a/hunt/static/css/detail.css b/hunt/static/css/detail.css
new file mode 100644
index 0000000..6e32241
--- /dev/null
+++ b/hunt/static/css/detail.css
@@ -0,0 +1,11 @@
+.available {
+    background-color: lightgray;
+}
+
+.completed {
+    background-color: lightgreen;
+}
+
+.locked {
+    background-color: lightsalmon;
+}
\ No newline at end of file
diff --git a/hunt/static/css/index.css b/hunt/static/css/index.css
index 61e80c5..6f4cf4d 100644
--- a/hunt/static/css/index.css
+++ b/hunt/static/css/index.css
@@ -21,4 +21,14 @@
 
 .left-div {
     text-align: left;
+}
+
+a {
+    color: inherit;
+    text-decoration: none;
+}
+
+a:hover {
+    color: inherit;
+    text-decoration: none;
 }
\ No newline at end of file
diff --git a/hunt/templates/base_with_nav.html b/hunt/templates/base_with_nav.html
index e77fcec..2c930a1 100644
--- a/hunt/templates/base_with_nav.html
+++ b/hunt/templates/base_with_nav.html
@@ -31,10 +31,13 @@
                         {% endif %}
                     </ul>
                 </div>
-            </div>
+            </div>cd 
         </div>
         <script src='https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js' integrity='sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1' crossorigin='anonymous'></script>
         <script src='https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js' integrity='sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM' crossorigin='anonymous'></script>
         {% block main %}{% endblock %}
+        <div class="footer text-right">
+            This application was developed by Lauren Delwiche (2022) and Rushil Umaretiya (2023).
+        </div>
     </div>
 {% endblock %}
\ No newline at end of file
diff --git a/hunt/templates/main/detail.html b/hunt/templates/main/detail.html
new file mode 100644
index 0000000..18bc70b
--- /dev/null
+++ b/hunt/templates/main/detail.html
@@ -0,0 +1,55 @@
+{% extends 'base_with_nav.html' %}
+
+{% load static %}
+
+{% block head %}
+    <link rel='stylesheet' href="{% static 'css/detail.css' %}">
+    <script>
+        function checkFlag(challenge_id) {
+            $.ajax({
+                type : 'POST',
+                url: "{% url 'main:validate_flag' %}",
+                data: {
+                    csrfmiddlewaretoken: '{{ csrf_token }}',
+                    dataType: 'json',
+                    challenge_id: challenge_id,
+                    flag: $("#" + challenge_id).val(),
+                },
+                success: function(data) {
+                    if (data.result == "success") {
+                        $("#box" + challenge_id).removeClass("available").addClass("completed");                        
+                    } else {
+                        $("#" + challenge_id).css("background-color", "red");
+                    }
+                },
+                failure: function() {
+                    $("#" + challenge_id).css("background-color", "red");
+                }
+            });
+        }
+    </script>
+{% endblock %}
+
+{% block main %}
+<a href="{% url 'main:index' %}"><button class="btn btn-secondary float-left m-md-2">Back</button></a>
+<div class="col-md-4 col-12 m-auto mt-md-3 p-md-5 {% if status == 'available' %}available{% elif status == 'completed' %}completed{% else %}locked{% endif %}">
+    {% if status == 'blocked' %}
+        <h1>This challenge is unavailable. Please try again later.</h1>
+    {% elif status == 'completed' %}
+        <h1>{{ challenge.name }}</h1>
+        <div class="text-left pt-md-2">{{ challenge.description | safe }}</div>
+        <hr>
+        <h3>Solved!</h3>
+    {% elif status == 'locked' %}
+        <h1>This challenge is unavailable. Please try again later.</h1>
+    {% elif status == 'available' %}
+        <h1>{{ challenge.name }}</h1>
+        <div class="text-left pt-md-2">{{ challenge.description | safe }}</div>
+        <hr>
+        <input type="text" id="{{ status.0.id }}" placeholder="Enter the flag here"/>
+        <input type="submit" value="Submit" onclick="checkFlag({{ status.0.id }})" />
+    {% else %}
+        <h1>There's an error with this challenge. Please contact the organizers.</h1>
+    {% endif %}
+</div>
+{% endblock %}
\ No newline at end of file
diff --git a/hunt/templates/main/index.html b/hunt/templates/main/index.html
index 16eda1d..64f3f29 100644
--- a/hunt/templates/main/index.html
+++ b/hunt/templates/main/index.html
@@ -31,29 +31,57 @@
 {% endblock %}
 
 {% block main %}
-    <div class='row'>
-        {% for challenge, status in challenges_dict.items %}
-            <div class='col-lg-3 col-md-4 col-sm-6 col-xs-12'>
-                <div id="box{{ status.0.id }}" class="box {% if status.1 == 'available' %}available{% elif status.1 == 'completed' %}completed{% else %}locked{% endif %}">
-                    <div class="centered-div">
-                        <h4>{{ status.0.name }}</h4>
-                        <p>Points: {{ status.0.points }}</p>
-                    </div>
-                    <div class="left-div">
-                        {% if status.0.exclusive %}
-                            <p>This challenge's points are only available to the first class to complete it.</p>
-                        {% elif status.1 == 'locked' %}
-                            <p>This challenge was exclusive and has already been completed by another class.</p>
-                        {% endif %}
-                        <p>{{ status.0.description }}</p>
-                    </div>
-                    {% if status.1 == 'available' %}
-                        <hr>
-                        <input type="text" id="{{ status.0.id }}" placeholder="Enter the flag here"/>
-                        <input type="submit" value="Submit" onclick="checkFlag({{ status.0.id }})" />
-                    {% endif %}
-                </div>
+    <h1 class="border-bottom p-md-2">Fall Hoco Scavenger Hunt 2021</h1>
+    {% for category, challenges in categories.values %}
+        <div class="col">
+            <div class="row">
+                <h1 class="ml-3">{{ category.name }}</h1>
+                <p style="cursor: pointer;" onclick="document.getElementById('c-{{ category.id }}').style.display = document.getElementById('c-{{ category.id }}').style.display === 'none' ? 'flex' : 'none'" class="ml-auto mr-3 btn btn-primary">toggle</p>
             </div>
+            <div id="c-{{ category.id }}" class='row'>
+                {% for challenge, status in challenges.items %}
+                    {% if status.0.unblocked %}
+                        <a href="challenge/{{ status.0.id }}" class='col-lg-3 col-md-4 col-sm-6 col-xs-12'>
+                            <div id="box{{ status.0.id }}" class="box {% if status.1 == 'available' %}available{% elif status.1 == 'completed' %}completed{% else %}locked{% endif %} p-3">
+                                <div class="centered-div">
+                                    <h4>{{ status.0.name }}</h4>
+                                    <p>Points: {{ status.0.points }}</p>
+                                </div>
+                                <div class="{% if status.1 == 'completed' %}center{% else %}left{% endif %}-div">
+                                    {% if status.0.exclusive %}
+                                        <p>This challenge's points are only available to the first class to complete it.</p>
+                                        <p>{{ status.0.short_description }}</p>    
+                                    {% elif status.1 == 'locked' %}
+                                        <p>This challenge was exclusive and has already been completed by another class.</p>
+                                    {% elif status.1 == 'completed' %}
+                                        <h3>Solved!</h3>
+                                    {% else %}
+                                        <p>{{ status.0.short_description }}</p>
+                                    {% endif %}
+                                </div>
+                                {% if status.1 == 'available' %}
+                                    <hr>
+                                    <input type="text" id="{{ status.0.id }}" placeholder="Enter the flag here"/>
+                                    <input type="submit" value="Submit" onclick="checkFlag({{ status.0.id }})" />
+                                {% endif %}
+                            </div>
+                        </a>
+                    {% else %}
+                        <div class='col-lg-3 col-md-4 col-sm-6 col-xs-12'>
+                            <div id="box{{ status.0.id }}" class="box locked p-3">
+                                <div class="centered-div">
+                                    <h4>{{ status.0.name }}</h4>
+                                    <p>Points: {{ status.0.points }}</p>
+                                </div>
+                                <div class="centered-div">
+                                    <p>This challenge is currently unavailable.</p>
+                                </div>
+                            </div>
+                        </div>
+                    {% endif %}
+                {% endfor %}
+            </div>
+        </div>
         {% endfor %}
     </div>
 {% endblock %}
\ No newline at end of file