aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBobby <[email protected]>2024-08-25 21:22:22 -0400
committerBobby <[email protected]>2024-08-25 21:22:22 -0400
commita99b3870b362bda483fc1009895447c58cabfff4 (patch)
treeac6ec4e3e7444af15b4e8db33d6bc186376b42d2
parentc6c9c18c39b94cbb1db1c54a5993045c56e49c7e (diff)
downloadyugen-a99b3870b362bda483fc1009895447c58cabfff4.tar.xz
yugen-a99b3870b362bda483fc1009895447c58cabfff4.zip
profile page design
-rw-r--r--authentication/migrations/0001_initial.py10
-rw-r--r--authentication/migrations/0002_user_discord_access_token_user_discord_refresh_token_and_more.py30
-rw-r--r--authentication/migrations/0003_alter_user_discord_avatar_alter_user_discord_banner.py25
-rw-r--r--authentication/models.py5
-rw-r--r--authentication/utils.py31
-rw-r--r--middleware/authentication.py2
-rw-r--r--middleware/preferences.py15
-rw-r--r--static/css/input.css7
-rw-r--r--static/css/main.css233
-rw-r--r--templates/partials/navbar.html20
-rw-r--r--templates/user_profile/user_profile.html280
-rw-r--r--user_profile/admin.py4
-rw-r--r--user_profile/migrations/0001_initial.py11
-rw-r--r--user_profile/models.py3
-rw-r--r--yugen/settings.py1
15 files changed, 541 insertions, 136 deletions
diff --git a/authentication/migrations/0001_initial.py b/authentication/migrations/0001_initial.py
index 843cdda..af0d675 100644
--- a/authentication/migrations/0001_initial.py
+++ b/authentication/migrations/0001_initial.py
@@ -1,4 +1,4 @@
-# Generated by Django 5.1 on 2024-08-25 00:23
+# Generated by Django 5.1 on 2024-08-25 23:18
import django.contrib.auth.models
import django.contrib.auth.validators
@@ -97,10 +97,14 @@ class Migration(migrations.Migration):
),
),
("discord_id", models.CharField(max_length=255, unique=True)),
+ ("discord_access_token", models.CharField(max_length=255)),
+ ("discord_refresh_token", models.CharField(max_length=255)),
+ ("discord_token_type", models.CharField(max_length=255)),
("discord_username", models.CharField(max_length=255, unique=True)),
- ("discord_avatar", models.CharField(max_length=255)),
- ("discord_banner", models.CharField(max_length=255)),
+ ("discord_avatar", models.CharField(blank=True, max_length=255)),
+ ("discord_banner", models.CharField(blank=True, max_length=255)),
("discord_global_name", models.CharField(max_length=255)),
+ ("discord_guild_name", models.CharField(blank=True, max_length=255)),
(
"groups",
models.ManyToManyField(
diff --git a/authentication/migrations/0002_user_discord_access_token_user_discord_refresh_token_and_more.py b/authentication/migrations/0002_user_discord_access_token_user_discord_refresh_token_and_more.py
deleted file mode 100644
index 4ad12c7..0000000
--- a/authentication/migrations/0002_user_discord_access_token_user_discord_refresh_token_and_more.py
+++ /dev/null
@@ -1,30 +0,0 @@
-# Generated by Django 5.1 on 2024-08-25 00:32
-
-from django.db import migrations, models
-
-
-class Migration(migrations.Migration):
- dependencies = [
- ("authentication", "0001_initial"),
- ]
-
- operations = [
- migrations.AddField(
- model_name="user",
- name="discord_access_token",
- field=models.CharField(default="", max_length=255),
- preserve_default=False,
- ),
- migrations.AddField(
- model_name="user",
- name="discord_refresh_token",
- field=models.CharField(default="", max_length=255),
- preserve_default=False,
- ),
- migrations.AddField(
- model_name="user",
- name="discord_token_type",
- field=models.CharField(default="", max_length=255),
- preserve_default=False,
- ),
- ]
diff --git a/authentication/migrations/0003_alter_user_discord_avatar_alter_user_discord_banner.py b/authentication/migrations/0003_alter_user_discord_avatar_alter_user_discord_banner.py
deleted file mode 100644
index e58e420..0000000
--- a/authentication/migrations/0003_alter_user_discord_avatar_alter_user_discord_banner.py
+++ /dev/null
@@ -1,25 +0,0 @@
-# Generated by Django 5.1 on 2024-08-25 07:00
-
-from django.db import migrations, models
-
-
-class Migration(migrations.Migration):
- dependencies = [
- (
- "authentication",
- "0002_user_discord_access_token_user_discord_refresh_token_and_more",
- ),
- ]
-
- operations = [
- migrations.AlterField(
- model_name="user",
- name="discord_avatar",
- field=models.CharField(max_length=255, null=True),
- ),
- migrations.AlterField(
- model_name="user",
- name="discord_banner",
- field=models.CharField(max_length=255, null=True),
- ),
- ]
diff --git a/authentication/models.py b/authentication/models.py
index 352cfa8..8b310b7 100644
--- a/authentication/models.py
+++ b/authentication/models.py
@@ -9,6 +9,7 @@ class User(AbstractUser):
discord_refresh_token = models.CharField(max_length=255)
discord_token_type = models.CharField(max_length=255)
discord_username = models.CharField(max_length=255, unique=True)
- discord_avatar = models.CharField(max_length=255, null=True)
- discord_banner = models.CharField(max_length=255, null=True)
+ discord_avatar = models.CharField(max_length=255, blank=True)
+ discord_banner = models.CharField(max_length=255, blank=True)
discord_global_name = models.CharField(max_length=255)
+ discord_guild_name = models.CharField(max_length=255, blank=True)
diff --git a/authentication/utils.py b/authentication/utils.py
index f9a225a..cd9db87 100644
--- a/authentication/utils.py
+++ b/authentication/utils.py
@@ -2,6 +2,7 @@ import dotenv
import os
import requests
from authentication.models import User
+from user_profile.models import UserPreferences
dotenv.load_dotenv()
@@ -10,7 +11,7 @@ def get_redirect_uri():
# Only Authenticated Users who are in our Discord Server can access the website
discord_client_id = os.environ.get("DISCORD_CLIENT_ID")
discord_redirect_uri = os.environ.get("DISCORD_REDIRECT_URI")
- discord_scope = "identify guilds email"
+ discord_scope = "identify guilds guilds.members.read"
redirect_uri = f"https://discord.com/oauth2/authorize?client_id={discord_client_id}&response_type=code&redirect_uri={discord_redirect_uri}&scope={discord_scope}"
return redirect_uri
@@ -26,7 +27,7 @@ def exchange_code(code):
"grant_type": "authorization_code",
"code": code,
"redirect_uri": discord_redirect_uri,
- "scope": "identify email guilds",
+ "scope": "identify guilds guilds.members.read",
}
headers = {"Content-Type": "application/x-www-form-urlencoded"}
@@ -38,22 +39,29 @@ def exchange_code(code):
def get_discord_user(access_token, token_type):
- user = requests.get(
- "https://discord.com/api/users/@me",
- headers={"Authorization": f"{token_type} {access_token}"},
- ).json()
guilds = requests.get(
"https://discord.com/api/users/@me/guilds",
headers={"Authorization": f"{token_type} {access_token}"},
).json()
authorized_guilds = os.environ.get("DISCORD_AUTHORIZED_GUILDS").split(",")
+ user = {}
user["is_authorized"] = False
if isinstance(guilds, list):
for guild in guilds:
if guild["id"] in authorized_guilds:
+ # get the user's guild display name
+ member = requests.get(
+ f"https://discord.com/api//users/@me/guilds/{guild['id']}/member",
+ headers={"Authorization": f"{token_type} {access_token}"},
+ ).json()
+
+ user = member["user"]
user["is_authorized"] = True
+ user["guild_name"] = member["nick"] if member["nick"] is not None else ""
+
break
+
else:
print(guilds)
print(user)
@@ -73,32 +81,35 @@ def authenticate_user(exchange_response):
discord_id=discord_user["id"],
defaults={
"username": discord_user["username"],
- "email": discord_user["email"],
"discord_id": discord_user["id"],
"discord_access_token": access_token,
"discord_refresh_token": refresh_token,
"discord_token_type": token_type,
"discord_username": discord_user["username"],
"discord_avatar": discord_user["avatar"],
- "discord_banner": discord_user["banner"],
+ "discord_banner": discord_user["banner"] if discord_user["banner"] is not None else "",
"discord_global_name": discord_user["global_name"],
+ "discord_guild_name": discord_user["guild_name"],
},
)
+ print(discord_user["banner"])
if not created:
user.username = discord_user["username"]
- user.email = discord_user["email"]
user.discord_access_token = access_token
user.discord_refresh_token = refresh_token
user.discord_token_type = token_type
user.discord_username = discord_user["username"]
user.discord_avatar = discord_user["avatar"]
user.discord_banner = (
- discord_user["banner"] if "banner" in discord_user else ""
+ discord_user["banner"] if discord_user["banner"] is not None else ""
)
user.discord_global_name = discord_user["global_name"]
+ user.discord_guild_name = discord_user["guild_name"]
user.save()
+ UserPreferences.objects.get_or_create(user=user)
+
return user
return None
diff --git a/middleware/authentication.py b/middleware/authentication.py
index 001a3a8..c651f1a 100644
--- a/middleware/authentication.py
+++ b/middleware/authentication.py
@@ -18,6 +18,7 @@ class AuthMiddleware:
if (
"admin" in request.path
or "auth" in request.path
+ or "favicon.ico" in request.path
or not hasattr(request, "user")
):
response = self.get_response(request)
@@ -31,7 +32,6 @@ class AuthMiddleware:
logout(request)
request.session["next"] = request.get_full_path()
return render(request, "messages/unauthorized.html", {"redirect_uri": get_redirect_uri()})
- # return redirect(get_redirect_uri())
# Check the verification cookie
verification_cookie = request.COOKIES.get("guild_verified")
diff --git a/middleware/preferences.py b/middleware/preferences.py
new file mode 100644
index 0000000..90d459e
--- /dev/null
+++ b/middleware/preferences.py
@@ -0,0 +1,15 @@
+from user_profile.models import UserPreferences
+
+class PreferencesMiddleware:
+ def __init__(self, get_response):
+ self.get_response = get_response
+
+ def __call__(self, request):
+ if request.user.is_authenticated:
+ user_preferences = UserPreferences.objects.get_or_create(user=request.user)[0]
+ request.user.preferences = user_preferences
+
+ response = self.get_response(request)
+ return response
+
+ \ No newline at end of file
diff --git a/static/css/input.css b/static/css/input.css
index ccde68d..f17e7ea 100644
--- a/static/css/input.css
+++ b/static/css/input.css
@@ -36,3 +36,10 @@ main {
0.8
) !important; /* Light gray for inactive bullets */
}
+
+.peer:checked + div .toggle-dot {
+ transform: translateX(100%);
+}
+.toggle-dot {
+ transition: transform 0.1s ease-in-out;
+}
diff --git a/static/css/main.css b/static/css/main.css
index 93a6afe..5b62e7d 100644
--- a/static/css/main.css
+++ b/static/css/main.css
@@ -554,6 +554,10 @@ video {
--tw-contain-style: ;
}
+.pointer-events-none {
+ pointer-events: none;
+}
+
.static {
position: static;
}
@@ -570,38 +574,35 @@ video {
inset: 0px;
}
-.left-1\/2 {
- left: 50%;
+.inset-y-0 {
+ top: 0px;
+ bottom: 0px;
}
.bottom-0 {
bottom: 0px;
}
-.left-0 {
- left: 0px;
+.bottom-10 {
+ bottom: 2.5rem;
+}
+
+.left-1\/2 {
+ left: 50%;
}
-.-bottom-10 {
- bottom: -2.5rem;
+.left-10 {
+ left: 2.5rem;
}
.right-0 {
right: 0px;
}
-.bottom-10 {
- bottom: 2.5rem;
-}
-
.right-10 {
right: 2.5rem;
}
-.left-10 {
- left: 2.5rem;
-}
-
.z-10 {
z-index: 10;
}
@@ -611,6 +612,11 @@ video {
margin-right: auto;
}
+.my-4 {
+ margin-top: 1rem;
+ margin-bottom: 1rem;
+}
+
.mb-2 {
margin-bottom: 0.5rem;
}
@@ -631,6 +637,10 @@ video {
margin-right: 0.5rem;
}
+.mt-1 {
+ margin-top: 0.25rem;
+}
+
.mt-2 {
margin-top: 0.5rem;
}
@@ -639,6 +649,14 @@ video {
margin-top: 1rem;
}
+.mt-8 {
+ margin-top: 2rem;
+}
+
+.mt-10 {
+ margin-top: 2.5rem;
+}
+
.block {
display: block;
}
@@ -674,10 +692,22 @@ video {
height: 1.5rem;
}
+.h-24 {
+ height: 6rem;
+}
+
+.h-4 {
+ height: 1rem;
+}
+
.h-6 {
height: 1.5rem;
}
+.h-72 {
+ height: 18rem;
+}
+
.h-8 {
height: 2rem;
}
@@ -694,40 +724,40 @@ video {
height: 100%;
}
-.h-40 {
- height: 10rem;
+.max-h-24 {
+ max-height: 6rem;
}
-.h-72 {
- height: 18rem;
+.w-1\/2 {
+ width: 50%;
}
-.h-24 {
- height: 6rem;
+.w-10 {
+ width: 2.5rem;
}
-.h-32 {
- height: 8rem;
+.w-11\/12 {
+ width: 91.666667%;
}
-.max-h-24 {
- max-height: 6rem;
-}
-
-.w-1\/2 {
- width: 50%;
+.w-24 {
+ width: 6rem;
}
.w-32 {
width: 8rem;
}
+.w-4 {
+ width: 1rem;
+}
+
.w-56 {
width: 14rem;
}
-.w-6 {
- width: 1.5rem;
+.w-64 {
+ width: 16rem;
}
.w-8 {
@@ -747,8 +777,8 @@ video {
width: max-content;
}
-.w-24 {
- width: 6rem;
+.max-w-7xl {
+ max-width: 80rem;
}
.max-w-max {
@@ -756,10 +786,6 @@ video {
max-width: max-content;
}
-.max-w-7xl {
- max-width: 80rem;
-}
-
.-translate-x-1\/2 {
--tw-translate-x: -50%;
transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
@@ -774,6 +800,16 @@ video {
transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
}
+.cursor-pointer {
+ cursor: pointer;
+}
+
+.select-none {
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ user-select: none;
+}
+
.flex-row {
flex-direction: row;
}
@@ -830,6 +866,12 @@ video {
gap: 2rem;
}
+.space-x-10 > :not([hidden]) ~ :not([hidden]) {
+ --tw-space-x-reverse: 0;
+ margin-right: calc(2.5rem * var(--tw-space-x-reverse));
+ margin-left: calc(2.5rem * calc(1 - var(--tw-space-x-reverse)));
+}
+
.overflow-auto {
overflow: auto;
}
@@ -863,6 +905,15 @@ video {
border-color: rgb(38 38 38 / var(--tw-border-opacity));
}
+.border-white {
+ --tw-border-opacity: 1;
+ border-color: rgb(255 255 255 / var(--tw-border-opacity));
+}
+
+.border-opacity-10 {
+ --tw-border-opacity: 0.1;
+}
+
.bg-black {
--tw-bg-opacity: 1;
background-color: rgb(0 0 0 / var(--tw-bg-opacity));
@@ -883,6 +934,11 @@ video {
background-color: rgb(224 231 255 / var(--tw-bg-opacity));
}
+.bg-neutral-700 {
+ --tw-bg-opacity: 1;
+ background-color: rgb(64 64 64 / var(--tw-bg-opacity));
+}
+
.bg-neutral-900 {
--tw-bg-opacity: 1;
background-color: rgb(23 23 23 / var(--tw-bg-opacity));
@@ -933,16 +989,6 @@ video {
background-color: rgb(254 249 195 / var(--tw-bg-opacity));
}
-.bg-gray-800 {
- --tw-bg-opacity: 1;
- background-color: rgb(31 41 55 / var(--tw-bg-opacity));
-}
-
-.bg-blue-500 {
- --tw-bg-opacity: 1;
- background-color: rgb(59 130 246 / var(--tw-bg-opacity));
-}
-
.bg-opacity-10 {
--tw-bg-opacity: 0.1;
}
@@ -968,11 +1014,19 @@ video {
background-position: center;
}
+.fill-current {
+ fill: currentColor;
+}
+
.object-cover {
-o-object-fit: cover;
object-fit: cover;
}
+.p-1 {
+ padding: 0.25rem;
+}
+
.p-2 {
padding: 0.5rem;
}
@@ -1016,6 +1070,11 @@ video {
padding-bottom: 0.75rem;
}
+.py-4 {
+ padding-top: 1rem;
+ padding-bottom: 1rem;
+}
+
.py-8 {
padding-top: 2rem;
padding-bottom: 2rem;
@@ -1029,12 +1088,12 @@ video {
padding-right: 3rem;
}
-.pt-1 {
- padding-top: 0.25rem;
+.pr-8 {
+ padding-right: 2rem;
}
-.pb-4 {
- padding-bottom: 1rem;
+.pt-1 {
+ padding-top: 0.25rem;
}
.text-center {
@@ -1061,6 +1120,11 @@ video {
line-height: 1.25rem;
}
+.text-xl {
+ font-size: 1.25rem;
+ line-height: 1.75rem;
+}
+
.text-xs {
font-size: 0.75rem;
line-height: 1rem;
@@ -1074,6 +1138,14 @@ video {
text-transform: uppercase;
}
+.capitalize {
+ text-transform: capitalize;
+}
+
+.leading-tight {
+ line-height: 1.25;
+}
+
.text-blue-300 {
--tw-text-opacity: 1;
color: rgb(147 197 253 / var(--tw-text-opacity));
@@ -1129,11 +1201,21 @@ video {
color: rgb(192 132 252 / var(--tw-text-opacity));
}
+.text-purple-500 {
+ --tw-text-opacity: 1;
+ color: rgb(168 85 247 / var(--tw-text-opacity));
+}
+
.text-red-300 {
--tw-text-opacity: 1;
color: rgb(252 165 165 / var(--tw-text-opacity));
}
+.text-red-500 {
+ --tw-text-opacity: 1;
+ color: rgb(239 68 68 / var(--tw-text-opacity));
+}
+
.text-teal-300 {
--tw-text-opacity: 1;
color: rgb(94 234 212 / var(--tw-text-opacity));
@@ -1158,17 +1240,46 @@ video {
color: rgb(234 179 8 / var(--tw-text-opacity));
}
+.text-orange-500 {
+ --tw-text-opacity: 1;
+ color: rgb(249 115 22 / var(--tw-text-opacity));
+}
+
.shadow-lg {
--tw-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);
--tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color), 0 4px 6px -4px var(--tw-shadow-color);
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
}
+.shadow-md {
+ --tw-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);
+ --tw-shadow-colored: 0 4px 6px -1px var(--tw-shadow-color), 0 2px 4px -2px var(--tw-shadow-color);
+ box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
+}
+
.outline-none {
outline: 2px solid transparent;
outline-offset: 2px;
}
+.transition-colors {
+ transition-property: color, background-color, border-color, text-decoration-color, fill, stroke;
+ transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
+ transition-duration: 150ms;
+}
+
+.duration-300 {
+ transition-duration: 300ms;
+}
+
+.duration-100 {
+ transition-duration: 100ms;
+}
+
+.ease-in-out {
+ transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
+}
+
/* Hide scrollbar for Chrome, Safari and Opera */
.no-scrollbar::-webkit-scrollbar {
@@ -1211,9 +1322,17 @@ main {
/* Light gray for inactive bullets */
}
-.hover\:bg-blue-700:hover {
+.peer:checked + div .toggle-dot {
+ transform: translateX(100%);
+}
+
+.toggle-dot {
+ transition: transform 0.1s ease-in-out;
+}
+
+.hover\:bg-purple-600:hover {
--tw-bg-opacity: 1;
- background-color: rgb(29 78 216 / var(--tw-bg-opacity));
+ background-color: rgb(147 51 234 / var(--tw-bg-opacity));
}
.hover\:bg-opacity-20:hover {
@@ -1230,6 +1349,11 @@ main {
outline-offset: 2px;
}
+.peer:checked ~ .peer-checked\:bg-purple-600 {
+ --tw-bg-opacity: 1;
+ background-color: rgb(147 51 234 / var(--tw-bg-opacity));
+}
+
@media (min-width: 640px) {
.sm\:px-6 {
padding-left: 1.5rem;
@@ -1246,6 +1370,11 @@ main {
width: 25%;
}
+ .lg\:w-max {
+ width: -moz-max-content;
+ width: max-content;
+ }
+
.lg\:flex-row {
flex-direction: row;
}
diff --git a/templates/partials/navbar.html b/templates/partials/navbar.html
index 6eb42d7..e7ece9e 100644
--- a/templates/partials/navbar.html
+++ b/templates/partials/navbar.html
@@ -159,12 +159,18 @@
alt="Profile"
class="h-8 w-8 rounded-full"
/>
- <span class="text-xs">{{ request.user.discord_global_name }}</span>
+ <span class="text-xs {% if request.path == "/profile/" %}text-purple-400{% endif %}">
+ {% if request.user.preferences.display_guild_name_instead_of_username and request.user.discord_guild_name %}
+ {{ request.user.discord_guild_name }}
+ {% else %}
+ {{ request.user.discord_global_name }}
+ {% endif %}
+ </span>
{% else %}
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="size-6">
<path fill-rule="evenodd" d="M7.5 6a4.5 4.5 0 1 1 9 0 4.5 4.5 0 0 1-9 0ZM3.751 20.105a8.25 8.25 0 0 1 16.498 0 .75.75 0 0 1-.437.695A18.683 18.683 0 0 1 12 22.5c-2.786 0-5.433-.608-7.812-1.7a.75.75 0 0 1-.437-.695Z" clip-rule="evenodd" />
</svg>
- <span class="text-xs">Profile</span>
+ <span class="text-xs {% if request.path == "/profile/" %}text-purple-400{% endif %}">Profile</span>
{% endif %}
</a>
</div>
@@ -380,12 +386,18 @@
alt="Profile"
class="h-8 w-8 rounded-full"
/>
- <span class="text-xs">{{ request.user.discord_global_name }}</span>
+ <span class="text-xs {% if request.path == "/profile/" %}text-purple-400{% endif %}">
+ {% if request.user.preferences.display_guild_name_instead_of_username and request.user.discord_guild_name %}
+ {{ request.user.discord_guild_name }}
+ {% else %}
+ {{ request.user.discord_global_name }}
+ {% endif %}
+ </span>
{% else %}
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="size-6">
<path fill-rule="evenodd" d="M7.5 6a4.5 4.5 0 1 1 9 0 4.5 4.5 0 0 1-9 0ZM3.751 20.105a8.25 8.25 0 0 1 16.498 0 .75.75 0 0 1-.437.695A18.683 18.683 0 0 1 12 22.5c-2.786 0-5.433-.608-7.812-1.7a.75.75 0 0 1-.437-.695Z" clip-rule="evenodd" />
</svg>
- <span class="text-xs">Profile</span>
+ <span class="text-xs {% if request.path == "/profile/" %}text-purple-400{% endif %}">Profile</span>
{% endif %}
</a>
</div>
diff --git a/templates/user_profile/user_profile.html b/templates/user_profile/user_profile.html
index 38435db..3a40d4d 100644
--- a/templates/user_profile/user_profile.html
+++ b/templates/user_profile/user_profile.html
@@ -14,10 +14,284 @@
Logout
</a>
</div>
+<section class="w-full flex flex-col max-w-7xl mx-auto my-4 p-2">
+ <section class="inline-flex w-11/12 lg:w-max flex-row rounded-full bg-white bg-opacity-10 mx-auto">
+ <a class="flex flex-row items-center focus:outline-none gap-2 text-sm font-bold py-2 px-4 rounded-full {% if request.GET.category == "preferences" or not request.GET.category %}bg-purple-600{% endif %}" href="{% url "user_profile:user_profile" %}?category=preferences">
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="size-4">
+ <path d="M18.75 12.75h1.5a.75.75 0 0 0 0-1.5h-1.5a.75.75 0 0 0 0 1.5ZM12 6a.75.75 0 0 1 .75-.75h7.5a.75.75 0 0 1 0 1.5h-7.5A.75.75 0 0 1 12 6ZM12 18a.75.75 0 0 1 .75-.75h7.5a.75.75 0 0 1 0 1.5h-7.5A.75.75 0 0 1 12 18ZM3.75 6.75h1.5a.75.75 0 1 0 0-1.5h-1.5a.75.75 0 0 0 0 1.5ZM5.25 18.75h-1.5a.75.75 0 0 1 0-1.5h1.5a.75.75 0 0 1 0 1.5ZM3 12a.75.75 0 0 1 .75-.75h7.5a.75.75 0 0 1 0 1.5h-7.5A.75.75 0 0 1 3 12ZM9 3.75a2.25 2.25 0 1 0 0 4.5 2.25 2.25 0 0 0 0-4.5ZM12.75 12a2.25 2.25 0 1 1 4.5 0 2.25 2.25 0 0 1-4.5 0ZM9 15.75a2.25 2.25 0 1 0 0 4.5 2.25 2.25 0 0 0 0-4.5Z" />
+ </svg>
+ <span>Preferences</span>
+ </a>
+ <a class="flex flex-row items-center focus:outline-none gap-2 text-sm font-bold py-2 px-4 rounded-full {% if request.GET.category == "anime_list" %}bg-purple-600{% endif %}" href="{% url "user_profile:user_profile" %}?category=anime_list">
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="size-4">
+ <path d="M19.5 6h-15v9h15V6Z" />
+ <path fill-rule="evenodd" d="M3.375 3C2.339 3 1.5 3.84 1.5 4.875v11.25C1.5 17.16 2.34 18 3.375 18H9.75v1.5H6A.75.75 0 0 0 6 21h12a.75.75 0 0 0 0-1.5h-3.75V18h6.375c1.035 0 1.875-.84 1.875-1.875V4.875C22.5 3.839 21.66 3 20.625 3H3.375Zm0 13.5h17.25a.375.375 0 0 0 .375-.375V4.875a.375.375 0 0 0-.375-.375H3.375A.375.375 0 0 0 3 4.875v11.25c0 .207.168.375.375.375Z" clip-rule="evenodd" />
+ </svg>
+ <span>Anime List</span>
+ </a>
+ <a class="flex flex-row items-center focus:outline-none gap-2 text-sm font-bold py-2 px-4 rounded-full {% if request.GET.category == "user_directory" %}bg-purple-600{% endif %}" href="{% url "user_profile:user_profile" %}?category=user_directory">
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="size-4">
+ <path d="M18.375 2.25c-1.035 0-1.875.84-1.875 1.875v15.75c0 1.035.84 1.875 1.875 1.875h.75c1.035 0 1.875-.84 1.875-1.875V4.125c0-1.036-.84-1.875-1.875-1.875h-.75ZM9.75 8.625c0-1.036.84-1.875 1.875-1.875h.75c1.036 0 1.875.84 1.875 1.875v11.25c0 1.035-.84 1.875-1.875 1.875h-.75a1.875 1.875 0 0 1-1.875-1.875V8.625ZM3 13.125c0-1.036.84-1.875 1.875-1.875h.75c1.036 0 1.875.84 1.875 1.875v6.75c0 1.035-.84 1.875-1.875 1.875h-.75A1.875 1.875 0 0 1 3 19.875v-6.75Z" />
+ </svg>
+ <span>User Directory</span>
+ </a>
+ </section>
+ {% if request.GET.category == "anime_list" %}
+ {% comment %} TODO: anime list {% endcomment %}
+ {% elif request.GET.category == "user_directory" %}
+ {% comment %} TODO: user directory {% endcomment %}
+ {% else %}
+ <h2 class="text-2xl font-bold text-center mt-8 flex gap-2 items-center">
+ <svg stroke="currentColor" fill="none" stroke-width="2" viewBox="0 0 24 24" aria-hidden="true" class="text-purple-500" height="1.4rem" width="1.4rem" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" d="M7 21a4 4 0 01-4-4V5a2 2 0 012-2h4a2 2 0 012 2v12a4 4 0 01-4 4zm0 0h12a2 2 0 002-2v-4a2 2 0 00-2-2h-2.343M11 7.343l1.657-1.657a2 2 0 012.828 0l2.829 2.829a2 2 0 010 2.828l-8.486 8.485M7 17h.01"></path></svg>
+ <span>Appearance</span>
+ </h2>
+ <div class="w-full py-4 flex flex-row justify-between items-center border-b border-white border-opacity-10">
+ <div>
+ <h3 class="text-xl">Card Appearance</h3>
+ <small class="text-gray-400 text-sm">Change the appearance of the cards in the anime list.</small>
+ </div>
+ <div class="relative w-64 custom-select text-sm" data-select="card_layout">
+ <div class="select-none cursor-pointer bg-neutral-900 py-2 px-4 pr-8 rounded leading-tight focus:outline-none capitalize" id="card_layout">
+ {{ user.preferences.card_layout }}
+ </div>
+ <div class="pointer-events-none absolute inset-y-0 right-0 flex items-center px-2 text-white">
+ <svg class="fill-current h-4 w-4" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20">
+ <path d="M9.293 12.95l.707.707L15.657 8l-1.414-1.414L10 10.828 5.757 6.586 4.343 8z"/>
+ </svg>
+ </div>
+ <div class="absolute z-10 w-full mt-1 bg-neutral-900 rounded shadow-lg hidden" id="card_layout_options">
+ <div class="py-1">
+ <div class="cursor-pointer px-4 py-2 text-white hover:bg-purple-600 capitalize" data-value="classic">classic</div>
+ <div class="cursor-pointer px-4 py-2 text-white hover:bg-purple-600 capitalize" data-value="wide">wide</div>
+ <div class="cursor-pointer px-4 py-2 text-white hover:bg-purple-600 capitalize" data-value="compact">compact</div>
+ </div>
+ </div>
+ </div>
+ </div>
+ <h2 class="text-2xl font-bold text-center mt-8 flex gap-2 items-center">
+ <svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 640 512" class="text-blue-500" height="1.75rem" width="1.75rem" xmlns="http://www.w3.org/2000/svg"><path d="M152.1 236.2c-3.5-12.1-7.8-33.2-7.8-33.2h-.5s-4.3 21.1-7.8 33.2l-11.1 37.5H163zM616 96H336v320h280c13.3 0 24-10.7 24-24V120c0-13.3-10.7-24-24-24zm-24 120c0 6.6-5.4 12-12 12h-11.4c-6.9 23.6-21.7 47.4-42.7 69.9 8.4 6.4 17.1 12.5 26.1 18 5.5 3.4 7.3 10.5 4.1 16.2l-7.9 13.9c-3.4 5.9-10.9 7.8-16.7 4.3-12.6-7.8-24.5-16.1-35.4-24.9-10.9 8.7-22.7 17.1-35.4 24.9-5.8 3.5-13.3 1.6-16.7-4.3l-7.9-13.9c-3.2-5.6-1.4-12.8 4.2-16.2 9.3-5.7 18-11.7 26.1-18-7.9-8.4-14.9-17-21-25.7-4-5.7-2.2-13.6 3.7-17.1l6.5-3.9 7.3-4.3c5.4-3.2 12.4-1.7 16 3.4 5 7 10.8 14 17.4 20.9 13.5-14.2 23.8-28.9 30-43.2H412c-6.6 0-12-5.4-12-12v-16c0-6.6 5.4-12 12-12h64v-16c0-6.6 5.4-12 12-12h16c6.6 0 12 5.4 12 12v16h64c6.6 0 12 5.4 12 12zM0 120v272c0 13.3 10.7 24 24 24h280V96H24c-13.3 0-24 10.7-24 24zm58.9 216.1L116.4 167c1.7-4.9 6.2-8.1 11.4-8.1h32.5c5.1 0 9.7 3.3 11.4 8.1l57.5 169.1c2.6 7.8-3.1 15.9-11.4 15.9h-22.9a12 12 0 0 1-11.5-8.6l-9.4-31.9h-60.2l-9.1 31.8c-1.5 5.1-6.2 8.7-11.5 8.7H70.3c-8.2 0-14-8.1-11.4-15.9z"></path></svg>
+ <span>Display Language</span>
+ </h2>
+ <div class="w-full py-4 flex flex-row justify-between items-center border-b border-white border-opacity-10">
+ <div>
+ <h3 class="text-xl">Title Language</h3>
+ <small class="text-gray-400 text-sm">Change the language in which the titles are displayed.</small>
+ </div>
+ <div class="relative w-64 custom-select text-sm" data-select="title_language">
+ <div class="select-none cursor-pointer bg-neutral-900 py-2 px-4 pr-8 rounded leading-tight focus:outline-none capitalize" id="title_language">
+ {{ user.preferences.title_language }}
+ </div>
+ <div class="pointer-events-none absolute inset-y-0 right-0 flex items-center px-2 text-white">
+ <svg class="fill-current h-4 w-4" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20">
+ <path d="M9.293 12.95l.707.707L15.657 8l-1.414-1.414L10 10.828 5.757 6.586 4.343 8z"/>
+ </svg>
+ </div>
+ <div class="absolute z-10 w-full mt-1 bg-neutral-900 rounded shadow-lg hidden" id="title_language_options">
+ <div class="py-1">
+ <div class="cursor-pointer px-4 py-2 text-white hover:bg-purple-600 capitalize" data-value="english">english (Attack on Titan)</div>
+ <div class="cursor-pointer px-4 py-2 text-white hover:bg-purple-600 capitalize" data-value="romaji">romaji (Shingeki no Kyojin)</div>
+ <div class="cursor-pointer px-4 py-2 text-white hover:bg-purple-600 capitalize" data-value="native">native (進撃の巨人)</div>
+ </div>
+ </div>
+ </div>
+ </div>
+ <div class="w-full py-4 flex flex-row justify-between items-center border-b border-white border-opacity-10">
+ <div>
+ <h3 class="text-xl">Character Name Language</h3>
+ <small class="text-gray-400 text-sm">Change the language in which the character names are displayed.</small>
+ </div>
+ <div class="relative w-64 custom-select text-sm" data-select="character_name_language">
+ <div class="select-none cursor-pointer bg-neutral-900 py-2 px-4 pr-8 rounded leading-tight focus:outline-none capitalize" id="character_name_language">
+ {{ user.preferences.character_name_language }}
+ </div>
+ <div class="pointer-events-none absolute inset-y-0 right-0 flex items-center px-2 text-white">
+ <svg class="fill-current h-4 w-4" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20">
+ <path d="M9.293 12.95l.707.707L15.657 8l-1.414-1.414L10 10.828 5.757 6.586 4.343 8z"/>
+ </svg>
+ </div>
+ <div class="absolute z-10 w-full mt-1 bg-neutral-900 rounded shadow-lg hidden" id="character_name_language_options">
+ <div class="py-1">
+ <div class="cursor-pointer px-4 py-2 text-white hover:bg-purple-600 capitalize" data-value="romaji">romaji</div>
+ <div class="cursor-pointer px-4 py-2 text-white hover:bg-purple-600 capitalize" data-value="native">native</div>
+ </div>
+ </div>
+ </div>
+ </div>
+ <h2 class="text-2xl font-bold text-center mt-8 flex gap-2 items-center">
+ <svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 24 24" class="text-red-500" height="1.4rem" width="1.4rem" xmlns="http://www.w3.org/2000/svg"><path fill="none" d="M0 0h24v24H0V0z"></path><path d="M13.05 9.79 10 7.5v9l3.05-2.29L16 12l-2.95-2.21zm0 0L10 7.5v9l3.05-2.29L16 12l-2.95-2.21zm0 0L10 7.5v9l3.05-2.29L16 12l-2.95-2.21zM11 4.07V2.05c-2.01.2-3.84 1-5.32 2.21L7.1 5.69A7.941 7.941 0 0 1 11 4.07zM5.69 7.1 4.26 5.68A9.949 9.949 0 0 0 2.05 11h2.02c.18-1.46.76-2.79 1.62-3.9zM4.07 13H2.05c.2 2.01 1 3.84 2.21 5.32l1.43-1.43A7.868 7.868 0 0 1 4.07 13zm1.61 6.74A9.981 9.981 0 0 0 11 21.95v-2.02a7.941 7.941 0 0 1-3.9-1.62l-1.42 1.43zM22 12c0 5.16-3.92 9.42-8.95 9.95v-2.02C16.97 19.41 20 16.05 20 12s-3.03-7.41-6.95-7.93V2.05C18.08 2.58 22 6.84 22 12z"></path></svg>
+ <span>Media Settings</span>
+ </h2>
+ <div class="w-full py-4 flex flex-row justify-between items-center border-b border-white border-opacity-10">
+ <div>
+ <h3 class="text-xl">Playback Language</h3>
+ <small class="text-gray-400 text-sm">Change the default language for media playback.</small>
+ </div>
+ <div class="relative w-64 custom-select text-sm" data-select="default_language">
+ <div class="select-none cursor-pointer bg-neutral-900 py-2 px-4 pr-8 rounded leading-tight focus:outline-none capitalize" id="default_language">
+ {{ user.preferences.default_language }}
+ </div>
+ <div class="pointer-events-none absolute inset-y-0 right-0 flex items-center px-2 text-white">
+ <svg class="fill-current h-4 w-4" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20">
+ <path d="M9.293 12.95l.707.707L15.657 8l-1.414-1.414L10 10.828 5.757 6.586 4.343 8z"/>
+ </svg>
+ </div>
+ <div class="absolute z-10 w-full mt-1 bg-neutral-900 rounded shadow-lg hidden" id="default_language_options">
+ <div class="py-1">
+ <div class="cursor-pointer px-4 py-2 text-white hover:bg-purple-600 capitalize" data-value="sub">sub</div>
+ <div class="cursor-pointer px-4 py-2 text-white hover:bg-purple-600 capitalize" data-value="dub">dub</div>
+ </div>
+ </div>
+ </div>
+ </div>
+ <div class="w-full py-4 flex flex-row justify-between items-center border-b border-white border-opacity-10">
+ <div>
+ <h3 class="text-xl">Default Provider</h3>
+ <small class="text-gray-400 text-sm">Change the preferred media provider.</small>
+ </div>
+ <div class="relative w-64 custom-select text-sm" data-select="default_provider">
+ <div class="select-none cursor-pointer bg-neutral-900 py-2 px-4 pr-8 rounded leading-tight focus:outline-none capitalize" id="default_provider">
+ {{ user.preferences.default_provider }}
+ </div>
+ <div class="pointer-events-none absolute inset-y-0 right-0 flex items-center px-2 text-white">
+ <svg class="fill-current h-4 w-4" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20">
+ <path d="M9.293 12.95l.707.707L15.657 8l-1.414-1.414L10 10.828 5.757 6.586 4.343 8z"/>
+ </svg>
+ </div>
+ <div class="absolute z-10 w-full mt-1 bg-neutral-900 rounded shadow-lg hidden" id="default_provider_options">
+ <div class="py-1">
+ <div class="cursor-pointer px-4 py-2 text-white hover:bg-purple-600 capitalize" data-value="zoro">zoro</div>
+ <div class="cursor-pointer px-4 py-2 text-white hover:bg-purple-600 capitalize" data-value="gogoanime">gogoanime</div>
+ </div>
+ </div>
+ </div>
+ </div>
+ <div class="w-full py-4 flex flex-row justify-between items-center border-b border-white border-opacity-10">
+ <div>
+ <h3 class="text-xl">Default Media Page</h3>
+ <small class="text-gray-400 text-sm">Choose whether to go to the watch page or the anime detail page when clicking on a card.</small>
+ </div>
+ <div class="relative w-64 custom-select text-sm" data-select="default_watch_page">
+ <div class="select-none cursor-pointer bg-neutral-900 py-2 px-4 pr-8 rounded leading-tight focus:outline-none capitalize" id="default_watch_page">
+ {{ user.preferences.default_watch_page }}
+ </div>
+ <div class="pointer-events-none absolute inset-y-0 right-0 flex items-center px-2 text-white">
+ <svg class="fill-current h-4 w-4" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20">
+ <path d="M9.293 12.95l.707.707L15.657 8l-1.414-1.414L10 10.828 5.757 6.586 4.343 8z"/>
+ </svg>
+ </div>
+ <div class="absolute z-10 w-full mt-1 bg-neutral-900 rounded shadow-lg hidden" id="default_watch_page_options">
+ <div class="py-1">
+ <div class="cursor-pointer px-4 py-2 text-white hover:bg-purple-600 capitalize" data-value="watch">watch</div>
+ <div class="cursor-pointer px-4 py-2 text-white hover:bg-purple-600 capitalize" data-value="detail">detail</div>
+ </div>
+ </div>
+ </div>
+ </div>
+ <div class="w-full py-4 flex flex-row justify-between items-center border-b border-white border-opacity-10">
+ <div>
+ <h3 class="text-xl">Show History on Home</h3>
+ <small class="text-gray-400 text-sm">Display your watch history on the home page.</small>
+ </div>
+ <div class="relative">
+ <label class="flex items-center cursor-pointer">
+ <input type="checkbox" class="hidden peer" id="show_history_on_home_checkbox" {% if user.preferences.show_history_on_home %}checked{% endif %}>
+ <div class="w-10 h-6 bg-neutral-700 rounded-full p-1 flex items-center peer-checked:bg-purple-600 transition-colors duration-300">
+ <div class="toggle-dot w-4 h-4 bg-white rounded-full shadow-md transform duration-100 ease-in-out"></div>
+ </div>
+ </label>
+ </div>
+ </div>
+ <div class="w-full py-4 flex flex-row justify-between items-center border-b border-white border-opacity-10">
+ <div>
+ <h3 class="text-xl">Automatically Skip Intro</h3>
+ <small class="text-gray-400 text-sm">Automatically skip the intro and outro of episodes.</small>
+ </div>
+ <div class="relative">
+ <label class="flex items-center cursor-pointer">
+ <input type="checkbox" class="hidden peer" id="auto_skip_intro_checkbox" {% if user.preferences.auto_skip_intro %}checked{% endif %}>
+ <div class="w-10 h-6 bg-neutral-700 rounded-full p-1 flex items-center peer-checked:bg-purple-600 transition-colors duration-300">
+ <div class="toggle-dot w-4 h-4 bg-white rounded-full shadow-md transform duration-100 ease-in-out"></div>
+ </div>
+ </label>
+ </div>
+ </div>
+ <div class="w-full py-4 flex flex-row justify-between items-center border-b border-white border-opacity-10">
+ <div>
+ <h3 class="text-xl">Automatically Play Video</h3>
+ <small class="text-gray-400 text-sm">Automatically start the media playback when opening the watch page.</small>
+ </div>
+ <div class="relative">
+ <label class="flex items-center cursor-pointer">
+ <input type="checkbox" class="hidden peer" id="auto_play_video_checkbox" {% if user.preferences.auto_play_video %}checked{% endif %}>
+ <div class="w-10 h-6 bg-neutral-700 rounded-full p-1 flex items-center peer-checked:bg-purple-600 transition-colors duration-300">
+ <div class="toggle-dot w-4 h-4 bg-white rounded-full shadow-md transform duration-100 ease-in-out"></div>
+ </div>
+ </label>
+ </div>
+ </div>
+ <div class="w-full py-4 flex flex-row justify-between items-center border-b border-white border-opacity-10">
+ <div>
+ <h3 class="text-xl">Automatically Play Next Episode</h3>
+ <small class="text-gray-400 text-sm">Automatically start the next episode when the current one ends.</small>
+ </div>
+ <div class="relative">
+ <label class="flex items-center cursor-pointer">
+ <input type="checkbox" class="hidden peer" id="auto_next_episode_checkbox" {% if user.preferences.auto_next_episode %}checked{% endif %}>
+ <div class="w-10 h-6 bg-neutral-700 rounded-full p-1 flex items-center peer-checked:bg-purple-600 transition-colors duration-300">
+ <div class="toggle-dot w-4 h-4 bg-white rounded-full shadow-md transform duration-100 ease-in-out"></div>
+ </div>
+ </label>
+ </div>
+ </div>
+ <h2 class="text-2xl font-bold text-center mt-8 flex gap-2 items-center">
+ <svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 24 24" class="text-orange-500" height="1.4rem" width="1.4rem" xmlns="http://www.w3.org/2000/svg"><path d="M5.32943 3.27152C6.56252 2.83314 7.9923 3.10743 8.97927 4.0944C9.96652 5.08165 10.2407 6.51196 9.80178 7.74529L20.6465 18.5901L18.5252 20.7114L7.67936 9.86703C6.44627 10.3054 5.01649 10.0311 4.02952 9.04415C3.04227 8.0569 2.7681 6.62659 3.20701 5.39326L5.44373 7.62994C6.02952 8.21572 6.97927 8.21572 7.56505 7.62994C8.15084 7.04415 8.15084 6.0944 7.56505 5.50862L5.32943 3.27152ZM15.6968 5.15506L18.8788 3.38729L20.293 4.80151L18.5252 7.98349L16.7574 8.33704L14.6361 10.4584L13.2219 9.04415L15.3432 6.92283L15.6968 5.15506ZM8.62572 12.9332L10.747 15.0546L5.79729 20.0043C5.2115 20.5901 4.26175 20.5901 3.67597 20.0043C3.12464 19.453 3.09221 18.5792 3.57867 17.99L3.67597 17.883L8.62572 12.9332Z"></path></svg>
+ <span>Site Settings</span>
+ </h2>
+ <div class="w-full py-4 flex flex-row justify-between items-center border-b border-white border-opacity-10">
+ <div>
+ <h3 class="text-xl">Display Guild Nickname Instead of Username</h3>
+ <small class="text-gray-400 text-sm">Display your guild nickname instead of your username in profile and other places.</small>
+ </div>
+ <div class="relative">
+ <label class="flex items-center cursor-pointer">
+ <input type="checkbox" class="hidden peer" id="display_guild_name_instead_of_username_checkbox" {% if user.preferences.display_guild_name_instead_of_username %}checked{% endif %}>
+ <div class="w-10 h-6 bg-neutral-700 rounded-full p-1 flex items-center peer-checked:bg-purple-600 transition-colors duration-300">
+ <div class="toggle-dot w-4 h-4 bg-white rounded-full shadow-md transform duration-100 ease-in-out"></div>
+ </div>
+ </label>
+ </div>
+ </div>
+ {% endif %}
+</section>
+{% endblock content %}
+{% block scripts %}
+<script>
+ const customSelects = document.querySelectorAll('.custom-select')
+ customSelects.forEach(customSelect => {
+ const selectId = customSelect.getAttribute('data-select')
+ const customSelectDisplay = document.getElementById(selectId)
+ const customSelectOptions = document.getElementById(`${selectId}_options`)
+ let selectedValue = '';
+ customSelectDisplay.addEventListener('click', function() {
+ customSelectOptions.classList.toggle('hidden');
+ });
+ customSelectOptions.addEventListener('click', function(e) {
+ if (e.target.hasAttribute('data-value')) {
+ selectedValue = e.target.getAttribute('data-value');
+ customSelectDisplay.textContent = selectedValue;
+ customSelectOptions.classList.add('hidden');
+ }
+ });
-
-
-{% endblock content %}
+ // Close the dropdown when clicking outside
+ document.addEventListener('click', function(e) {
+ if (!customSelectDisplay.contains(e.target) && !customSelectOptions.contains(e.target)) {
+ customSelectOptions.classList.add('hidden');
+ }
+ });
+ });
+</script>
+{% endblock scripts %}
diff --git a/user_profile/admin.py b/user_profile/admin.py
index 8c38f3f..d7a4281 100644
--- a/user_profile/admin.py
+++ b/user_profile/admin.py
@@ -1,3 +1,7 @@
from django.contrib import admin
+from .models import UserPreferences, UserHistory, UserAnimeList
# Register your models here.
+admin.site.register(UserPreferences)
+admin.site.register(UserHistory)
+admin.site.register(UserAnimeList) \ No newline at end of file
diff --git a/user_profile/migrations/0001_initial.py b/user_profile/migrations/0001_initial.py
index 8a88389..465b17f 100644
--- a/user_profile/migrations/0001_initial.py
+++ b/user_profile/migrations/0001_initial.py
@@ -1,4 +1,4 @@
-# Generated by Django 5.1 on 2024-08-25 21:21
+# Generated by Django 5.1 on 2024-08-25 23:18
import django.db.models.deletion
from django.conf import settings
@@ -89,10 +89,7 @@ class Migration(migrations.Migration):
models.CharField(default="romaji", max_length=16),
),
("default_language", models.CharField(default="sub", max_length=16)),
- (
- "default_provider",
- models.CharField(default="gogoanime", max_length=16),
- ),
+ ("default_provider", models.CharField(default="zoro", max_length=16)),
(
"default_watch_page",
models.CharField(default="watch", max_length=16),
@@ -102,6 +99,10 @@ class Migration(migrations.Migration):
("auto_play_video", models.BooleanField(default=False)),
("auto_next_episode", models.BooleanField(default=False)),
(
+ "display_guild_name_instead_of_username",
+ models.BooleanField(default=True),
+ ),
+ (
"user",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
diff --git a/user_profile/models.py b/user_profile/models.py
index 12b770a..486c74e 100644
--- a/user_profile/models.py
+++ b/user_profile/models.py
@@ -8,12 +8,13 @@ class UserPreferences(models.Model):
title_language = models.CharField(max_length=16, default="english") # english, romaji, native
character_name_language = models.CharField(max_length=16, default="romaji") # romaji, native
default_language = models.CharField(max_length=16, default="sub") # sub, dub
- default_provider = models.CharField(max_length=16, default="gogoanime") # gogoanime, zoro
+ default_provider = models.CharField(max_length=16, default="zoro") # gogoanime, zoro
default_watch_page = models.CharField(max_length=16, default="watch") # detail, watch
show_history_on_home = models.BooleanField(default=True)
auto_skip_intro = models.BooleanField(default=False)
auto_play_video = models.BooleanField(default=False)
auto_next_episode = models.BooleanField(default=False)
+ display_guild_name_instead_of_username = models.BooleanField(default=True)
def __str__(self):
return f"{self.user.username}'s preferences"
diff --git a/yugen/settings.py b/yugen/settings.py
index 7c39361..f1d9422 100644
--- a/yugen/settings.py
+++ b/yugen/settings.py
@@ -58,6 +58,7 @@ MIDDLEWARE = [
"django.middleware.clickjacking.XFrameOptionsMiddleware",
"django.middleware.gzip.GZipMiddleware",
"middleware.authentication.AuthMiddleware",
+ "middleware.preferences.PreferencesMiddleware",
]
ROOT_URLCONF = "yugen.urls"