aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBobby <[email protected]>2024-08-25 23:27:40 -0400
committerBobby <[email protected]>2024-08-25 23:27:40 -0400
commitced2c54b4e1d2aa1757c1a58a0b624a36bda5c21 (patch)
tree2f1e5047c13cb15df6311e42d57c830d11d77364
parenta99b3870b362bda483fc1009895447c58cabfff4 (diff)
downloadyugen-ced2c54b4e1d2aa1757c1a58a0b624a36bda5c21.tar.xz
yugen-ced2c54b4e1d2aa1757c1a58a0b624a36bda5c21.zip
Ability to save preferences
-rw-r--r--authentication/urls.py1
-rw-r--r--authentication/views.py12
-rw-r--r--static/css/main.css151
-rw-r--r--templates/partials/base.html2
-rw-r--r--templates/partials/navbar.html2
-rw-r--r--templates/user_profile/user_profile.html134
-rw-r--r--user_profile/urls.py1
-rw-r--r--user_profile/views.py50
8 files changed, 337 insertions, 16 deletions
diff --git a/authentication/urls.py b/authentication/urls.py
index f238dfa..f9ce23c 100644
--- a/authentication/urls.py
+++ b/authentication/urls.py
@@ -6,4 +6,5 @@ app_name = "auth"
urlpatterns = [
path("callback", views.callback, name="callback"),
path("logout", views.logout_user, name="logout"),
+ path("unauthorized", views.unauthorized, name="unauthorized"),
]
diff --git a/authentication/views.py b/authentication/views.py
index a2b407b..781aa99 100644
--- a/authentication/views.py
+++ b/authentication/views.py
@@ -5,7 +5,9 @@ from django.shortcuts import redirect, render
def callback(request):
- # Coming from Discord OAuth2
+ if request.user.is_authenticated:
+ return redirect("home:index")
+
code = request.GET.get("code")
if not code:
return render(request, "messages/unauthorized.html", {"error": "You can't access the site if you keep cancelling the login!", "redirect_uri": get_redirect_uri()})
@@ -18,7 +20,7 @@ def callback(request):
user = authenticate_user(exchange_response=response)
if not user:
- return render(request, "messages/unauthorized.html", {"redirect_uri": get_redirect_uri()})
+ return redirect("auth:unauthorized")
next_url = request.session.pop("next", None)
# login the user and redirect to the referrer
@@ -28,3 +30,9 @@ def callback(request):
def logout_user(request):
logout(request)
return HttpResponseRedirect(request.META.get("HTTP_REFERER"))
+
+def unauthorized(request):
+ if request.user.is_authenticated:
+ return redirect("home:index")
+
+ return render(request, "messages/unauthorized.html", {"redirect_uri": get_redirect_uri(), "error": "You are not part of our elite cult!"})
diff --git a/static/css/main.css b/static/css/main.css
index 5b62e7d..309af7f 100644
--- a/static/css/main.css
+++ b/static/css/main.css
@@ -554,6 +554,40 @@ video {
--tw-contain-style: ;
}
+.container {
+ width: 100%;
+}
+
+@media (min-width: 640px) {
+ .container {
+ max-width: 640px;
+ }
+}
+
+@media (min-width: 768px) {
+ .container {
+ max-width: 768px;
+ }
+}
+
+@media (min-width: 1024px) {
+ .container {
+ max-width: 1024px;
+ }
+}
+
+@media (min-width: 1280px) {
+ .container {
+ max-width: 1280px;
+ }
+}
+
+@media (min-width: 1536px) {
+ .container {
+ max-width: 1536px;
+ }
+}
+
.pointer-events-none {
pointer-events: none;
}
@@ -562,6 +596,10 @@ video {
position: static;
}
+.fixed {
+ position: fixed;
+}
+
.absolute {
position: absolute;
}
@@ -570,6 +608,10 @@ video {
position: relative;
}
+.sticky {
+ position: sticky;
+}
+
.inset-0 {
inset: 0px;
}
@@ -603,10 +645,30 @@ video {
right: 2.5rem;
}
+.top-0 {
+ top: 0px;
+}
+
+.right-4 {
+ right: 1rem;
+}
+
+.top-4 {
+ top: 1rem;
+}
+
+.bottom-4 {
+ bottom: 1rem;
+}
+
.z-10 {
z-index: 10;
}
+.z-50 {
+ z-index: 50;
+}
+
.mx-auto {
margin-left: auto;
margin-right: auto;
@@ -657,6 +719,18 @@ video {
margin-top: 2.5rem;
}
+.-ml-1 {
+ margin-left: -0.25rem;
+}
+
+.mr-3 {
+ margin-right: 0.75rem;
+}
+
+.ml-2 {
+ margin-left: 0.5rem;
+}
+
.block {
display: block;
}
@@ -724,6 +798,10 @@ video {
height: 100%;
}
+.h-5 {
+ height: 1.25rem;
+}
+
.max-h-24 {
max-height: 6rem;
}
@@ -777,6 +855,14 @@ video {
width: max-content;
}
+.w-5 {
+ width: 1.25rem;
+}
+
+.w-6 {
+ width: 1.5rem;
+}
+
.max-w-7xl {
max-width: 80rem;
}
@@ -786,6 +872,10 @@ video {
max-width: max-content;
}
+.max-w-xs {
+ max-width: 20rem;
+}
+
.-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));
@@ -800,10 +890,24 @@ 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));
}
+@keyframes spin {
+ to {
+ transform: rotate(360deg);
+ }
+}
+
+.animate-spin {
+ animation: spin 1s linear infinite;
+}
+
.cursor-pointer {
cursor: pointer;
}
+.cursor-not-allowed {
+ cursor: not-allowed;
+}
+
.select-none {
-webkit-user-select: none;
-moz-user-select: none;
@@ -834,6 +938,10 @@ video {
align-items: center;
}
+.justify-start {
+ justify-content: flex-start;
+}
+
.justify-end {
justify-content: flex-end;
}
@@ -872,6 +980,12 @@ video {
margin-left: calc(2.5rem * calc(1 - var(--tw-space-x-reverse)));
}
+.space-y-2 > :not([hidden]) ~ :not([hidden]) {
+ --tw-space-y-reverse: 0;
+ margin-top: calc(0.5rem * calc(1 - var(--tw-space-y-reverse)));
+ margin-bottom: calc(0.5rem * var(--tw-space-y-reverse));
+}
+
.overflow-auto {
overflow: auto;
}
@@ -892,6 +1006,10 @@ video {
border-radius: 0.75rem;
}
+.rounded-md {
+ border-radius: 0.375rem;
+}
+
.border {
border-width: 1px;
}
@@ -1245,6 +1363,24 @@ video {
color: rgb(249 115 22 / var(--tw-text-opacity));
}
+.text-green-700 {
+ --tw-text-opacity: 1;
+ color: rgb(21 128 61 / var(--tw-text-opacity));
+}
+
+.text-red-700 {
+ --tw-text-opacity: 1;
+ color: rgb(185 28 28 / var(--tw-text-opacity));
+}
+
+.opacity-25 {
+ opacity: 0.25;
+}
+
+.opacity-75 {
+ opacity: 0.75;
+}
+
.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);
@@ -1268,6 +1404,12 @@ video {
transition-duration: 150ms;
}
+.transition-opacity {
+ transition-property: opacity;
+ transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
+ transition-duration: 150ms;
+}
+
.duration-300 {
transition-duration: 300ms;
}
@@ -1276,6 +1418,10 @@ video {
transition-duration: 100ms;
}
+.duration-500 {
+ transition-duration: 500ms;
+}
+
.ease-in-out {
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
}
@@ -1335,6 +1481,11 @@ main {
background-color: rgb(147 51 234 / var(--tw-bg-opacity));
}
+.hover\:bg-purple-700:hover {
+ --tw-bg-opacity: 1;
+ background-color: rgb(126 34 206 / var(--tw-bg-opacity));
+}
+
.hover\:bg-opacity-20:hover {
--tw-bg-opacity: 0.2;
}
diff --git a/templates/partials/base.html b/templates/partials/base.html
index 6977c0b..f7fa72c 100644
--- a/templates/partials/base.html
+++ b/templates/partials/base.html
@@ -14,7 +14,7 @@
<title>Yugen</title>
</head>
<body class="bg-black text-white">
- <header>{% include "partials/navbar.html" %}</header>
+ {% include "partials/navbar.html" %}
<main class="mx-auto">{% block content %} {% endblock %}</main>
</body>
{% block scripts %} {% endblock %}
diff --git a/templates/partials/navbar.html b/templates/partials/navbar.html
index e7ece9e..009c75a 100644
--- a/templates/partials/navbar.html
+++ b/templates/partials/navbar.html
@@ -1,5 +1,5 @@
{% load static %}
-<nav class="bg-neutral-950 px-4 py-3 border-b border-neutral-800">
+<nav class="bg-neutral-950 px-4 py-3 border-b border-neutral-800 sticky top-0 z-50 shadow-lg">
<!-- Mobile UI: Icons (Top) Search (Botton)-->
<div class="xl:hidden flex flex-col gap-4">
<!-- Icons (Top) with Hidden Logo -->
diff --git a/templates/user_profile/user_profile.html b/templates/user_profile/user_profile.html
index 3a40d4d..41c61f5 100644
--- a/templates/user_profile/user_profile.html
+++ b/templates/user_profile/user_profile.html
@@ -5,7 +5,13 @@
<div class="absolute inset-0" style="background: linear-gradient(45deg, rgb(8, 8, 8) 15%, transparent 60%), linear-gradient(0deg, rgb(8, 8, 8) 0%, transparent 60%);"></div>
<div class="flex flex-col items-center gap-2 absolute bottom-0 left-10">
<img src="https://cdn.discordapp.com/avatars/{{ request.user.discord_id }}/{{ request.user.discord_avatar }}?size=256" alt="avatar" class="rounded-full w-24 h-24">
- <h1 class="text-2xl font-bold">{{ user.discord_global_name }}</h1>
+ <h1 class="text-2xl font-bold">
+ {% 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 %}
+ </h1>
</div>
<a href="{% url "auth:logout" %}" class="bg-white bg-opacity-10 text-white text-sm font-bold py-3 px-6 rounded-full flex items-center gap-2 absolute bottom-10 right-10">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="size-6">
@@ -16,29 +22,29 @@
</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">
+ <a class="flex flex-row items-center focus:outline-none gap-2 text-sm font-bold py-2 px-4 rounded-full {% if req_category == "preferences" %}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">
+ <a class="flex flex-row items-center focus:outline-none gap-2 text-sm font-bold py-2 px-4 rounded-full {% if req_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">
+ <a class="flex flex-row items-center focus:outline-none gap-2 text-sm font-bold py-2 px-4 rounded-full {% if req_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" %}
+ {% if req_category == "anime_list" %}
{% comment %} TODO: anime list {% endcomment %}
- {% elif request.GET.category == "user_directory" %}
+ {% elif req_category == "user_directory" %}
{% comment %} TODO: user directory {% endcomment %}
{% else %}
<h2 class="text-2xl font-bold text-center mt-8 flex gap-2 items-center">
@@ -195,7 +201,7 @@
<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="w-10 h-6 bg-neutral-700 rounded-full p-1 flex items-center peer-checked:bg-purple-600 transition-colors duration-100">
<div class="toggle-dot w-4 h-4 bg-white rounded-full shadow-md transform duration-100 ease-in-out"></div>
</div>
</label>
@@ -209,7 +215,7 @@
<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="w-10 h-6 bg-neutral-700 rounded-full p-1 flex items-center peer-checked:bg-purple-600 transition-colors duration-100">
<div class="toggle-dot w-4 h-4 bg-white rounded-full shadow-md transform duration-100 ease-in-out"></div>
</div>
</label>
@@ -223,7 +229,7 @@
<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="w-10 h-6 bg-neutral-700 rounded-full p-1 flex items-center peer-checked:bg-purple-600 transition-colors duration-100">
<div class="toggle-dot w-4 h-4 bg-white rounded-full shadow-md transform duration-100 ease-in-out"></div>
</div>
</label>
@@ -237,7 +243,7 @@
<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="w-10 h-6 bg-neutral-700 rounded-full p-1 flex items-center peer-checked:bg-purple-600 transition-colors duration-100">
<div class="toggle-dot w-4 h-4 bg-white rounded-full shadow-md transform duration-100 ease-in-out"></div>
</div>
</label>
@@ -255,12 +261,23 @@
<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="w-10 h-6 bg-neutral-700 rounded-full p-1 flex items-center peer-checked:bg-purple-600 transition-colors duration-100">
<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>
+ <!-- Save Button -->
+ <div class="w-full py-4 flex flex-row justify-start items-center">
+ <button id="saveButton" class="bg-purple-600 hover:bg-purple-700 text-white font-bold py-2 px-4 rounded-full focus:outline-none flex items-center transition-colors duration-100 ease-in-out" onclick="handleSave()">
+ <svg id="spinner" class="hidden animate-spin mr-2 h-5 w-5 text-white" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
+ <path d="M12,1A11,11,0,1,0,23,12,11,11,0,0,0,12,1Zm0,19a8,8,0,1,1,8-8A8,8,0,0,1,12,20Z" class="opacity-25 fill-current"/>
+ <path d="M10.14,1.16a11,11,0,0,0-9,8.92A1.59,1.59,0,0,0,2.46,12,1.52,1.52,0,0,0,4.11,10.7a8,8,0,0,1,6.66-6.61A1.42,1.42,0,0,0,12,2.69h0A1.57,1.57,0,0,0,10.14,1.16Z" class="fill-current"/>
+ </svg>
+ <span id="buttonText">Save Preferences</span>
+ </button>
+ </div>
+ <div id="toastContainer" class="fixed bottom-4 left-1/2 transform -translate-x-1/2 z-50 flex flex-col space-y-2"></div>
{% endif %}
</section>
{% endblock content %}
@@ -293,5 +310,100 @@
});
});
</script>
+<script>
+ function showToast(message, isSuccess) {
+ const toast = document.createElement('div');
+ toast.className = `flex items-center p-4 rounded-md shadow-lg transition-opacity duration-500 ease-in-out animate__animated ${
+ isSuccess ? 'bg-green-100 text-green-700 animate__fadeInUp' : 'bg-red-100 text-red-700 animate__fadeInUp'
+ }`;
+
+ const checkSVG = `<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-4"><path stroke-linecap="round" stroke-linejoin="round" d="m4.5 12.75 6 6 9-13.5" /></svg>`
+
+ const errorSVG = `<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6"> <path stroke-linecap="round" stroke-linejoin="round" d="M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0L2.697 16.126ZM12 15.75h.007v.008H12v-.008Z" /></svg>`
+
+ toast.innerHTML = `
+ <div class="flex items-center">
+ ${isSuccess ? checkSVG : errorSVG}
+ <span class="ml-2">${message}</span>
+ </div>
+ `;
+
+ // Append the toast to the container
+ toastContainer.appendChild(toast);
+
+ // Remove the toast after 3 seconds
+ setTimeout(() => {
+ toast.classList.add('animate__fadeOutDown');
+ setTimeout(() => {
+ toastContainer.removeChild(toast);
+ }, 500);
+ }, 3000);
+ }
+
+ function handleSave() {
+ const button = document.getElementById('saveButton');
+ const spinner = document.getElementById('spinner');
+ const buttonText = document.getElementById('buttonText');
+
+ // Show the spinner and disable the button
+ spinner.classList.remove('hidden');
+ buttonText.textContent = 'Saving...';
+ button.classList.add('bg-white', 'bg-opacity-20', 'cursor-not-allowed');
+ button.classList.remove('bg-purple-600', 'hover:bg-purple-700');
+ button.disabled = true;
+
+ // Gather preferences
+ const cardLayout = document.getElementById('card_layout').textContent.trim().toLowerCase();
+ const titleLanguage = document.getElementById('title_language').textContent.trim().toLowerCase();
+ const characterNameLanguage = document.getElementById('character_name_language').textContent.trim().toLowerCase();
+ const defaultLanguage = document.getElementById('default_language').textContent.trim().toLowerCase();
+ const defaultProvider = document.getElementById('default_provider').textContent.trim().toLowerCase();
+ const defaultWatchPage = document.getElementById('default_watch_page').textContent.trim().toLowerCase();
+ const showHistoryOnHome = document.getElementById('show_history_on_home_checkbox').checked;
+ const autoSkipIntro = document.getElementById('auto_skip_intro_checkbox').checked;
+ const autoPlayVideo = document.getElementById('auto_play_video_checkbox').checked;
+ const autoNextEpisode = document.getElementById('auto_next_episode_checkbox').checked;
+ const displayGuildNameInsteadOfUsername = document.getElementById('display_guild_name_instead_of_username_checkbox').checked;
+
+ const body = {
+ cardLayout,
+ titleLanguage,
+ characterNameLanguage,
+ defaultLanguage,
+ defaultProvider,
+ defaultWatchPage,
+ showHistoryOnHome,
+ autoSkipIntro,
+ autoPlayVideo,
+ autoNextEpisode,
+ displayGuildNameInsteadOfUsername
+ };
+
+ // Send a POST request to save the preferences
+ fetch('{% url "profile:save_user_preferences" %}', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ 'X-CSRFToken': '{{ csrf_token }}'
+ },
+ body: JSON.stringify(body)
+ }).then(response => {
+ if (response.ok) {
+ showToast('Preferences saved successfully!', true);
+ } else {
+ showToast('An error occurred while saving preferences.', false);
+ }
+ }).catch(error => {
+ showToast('An error occurred while saving preferences.', false);
+ }).finally(() => {
+ // Hide the spinner and enable the button
+ spinner.classList.add('hidden');
+ buttonText.textContent = 'Save Preferences';
+ button.classList.remove('bg-white', 'bg-opacity-20', 'cursor-not-allowed');
+ button.classList.add('bg-purple-600', 'hover:bg-purple-700');
+ button.disabled = false;
+ });
+ }
+ </script>
{% endblock scripts %}
diff --git a/user_profile/urls.py b/user_profile/urls.py
index e2f6d47..6afb7ef 100644
--- a/user_profile/urls.py
+++ b/user_profile/urls.py
@@ -5,4 +5,5 @@ from . import views
app_name = "user_profile"
urlpatterns = [
path("", views.user_profile, name="user_profile"),
+ path("save_user_preferences", views.save_user_preferences, name="save_user_preferences"),
] \ No newline at end of file
diff --git a/user_profile/views.py b/user_profile/views.py
index 25d7bd2..ef3c8e3 100644
--- a/user_profile/views.py
+++ b/user_profile/views.py
@@ -1,8 +1,56 @@
+import json
from django.shortcuts import render
+from django.http import JsonResponse
+from user_profile.models import UserPreferences
def user_profile(request):
+ category = request.GET.get("category", "preferences")
+
+ supported_categories = ["preferences", "anime_list", "user_directory"]
+ if category not in supported_categories:
+ category = "preferences"
+
+ print(category)
+
+ context = {
+ "req_category": category
+ }
+
+ return render(request, "user_profile/user_profile.html", context)
+
+
+def save_user_preferences(request):
+ if request.method != "POST":
+ return JsonResponse({"error": "Invalid request method"}, status=400)
+ user = request.user
+
+ data = json.loads(request.body)
+ card_layout = data.get("cardLayout")
+ title_language = data.get("titleLanguage")
+ character_name_language = data.get("characterNameLanguage")
+ default_language = data.get("defaultLanguage")
+ default_provider = data.get("defaultProvider")
+ default_watch_page = data.get("defaultWatchPage")
+ show_history_on_home = data.get("showHistoryOnHome")
+ auto_skip_intro = data.get("autoSkipIntro")
+ auto_play_video = data.get("autoPlayVideo")
+ auto_next_episode = data.get("autoNextEpisode")
+ display_guild_name_instead_of_username = data.get("displayGuildNameInsteadOfUsername")
+ user_preferences, created = UserPreferences.objects.get_or_create(user=user)
+ user_preferences.card_layout = card_layout
+ user_preferences.title_language = title_language
+ user_preferences.character_name_language = character_name_language
+ user_preferences.default_language = default_language
+ user_preferences.default_provider = default_provider
+ user_preferences.default_watch_page = default_watch_page
+ user_preferences.show_history_on_home = show_history_on_home
+ user_preferences.auto_skip_intro = auto_skip_intro
+ user_preferences.auto_play_video = auto_play_video
+ user_preferences.auto_next_episode = auto_next_episode
+ user_preferences.display_guild_name_instead_of_username = display_guild_name_instead_of_username
+ user_preferences.save()
- return render(request, "user_profile/user_profile.html") \ No newline at end of file
+ return JsonResponse({"success": "User preferences saved"}, status=200)