mirror of
https://github.com/pacnpal/thrillwiki_django_no_react.git
synced 2025-12-20 06:31:09 -05:00
feat: major project restructure - move Django to backend dir and fix critical imports
- Restructure project: moved Django backend to backend/ directory - Add frontend/ directory for future Next.js application - Add shared/ directory for common resources - Fix critical Django import errors: - Add missing sys.path modification for apps directory - Fix undefined CATEGORY_CHOICES imports in rides module - Fix media migration undefined references - Remove unused imports and f-strings without placeholders - Install missing django-environ dependency - Django server now runs without ModuleNotFoundError - Update .gitignore and README for new structure - Add pnpm workspace configuration for monorepo setup
This commit is contained in:
@@ -1,65 +0,0 @@
|
||||
{% extends "base/base.html" %}
|
||||
{% load i18n %}
|
||||
{% load account socialaccount %}
|
||||
{% load static %}
|
||||
|
||||
{% block title %}Login - ThrillWiki{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="flex items-center justify-center py-8 md:py-12">
|
||||
<div class="w-full max-w-lg px-4">
|
||||
<div class="auth-card">
|
||||
<h1 class="auth-title">{% trans "Welcome Back" %}</h1>
|
||||
|
||||
{% get_providers as socialaccount_providers %}
|
||||
{% if socialaccount_providers %}
|
||||
<div class="space-y-3">
|
||||
{% for provider in socialaccount_providers %}
|
||||
<a
|
||||
href="{% provider_login_url provider.id process='login' %}"
|
||||
class="btn-social {% if provider.id == 'discord' %}btn-discord{% elif provider.id == 'google' %}btn-google{% endif %}"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
onkeydown="if(event.key === 'Enter' || event.key === ' ') { this.click(); event.preventDefault(); }"
|
||||
>
|
||||
{% if provider.id == 'google' %}
|
||||
<img
|
||||
src="{% static 'images/google-icon.svg' %}"
|
||||
alt="Google"
|
||||
class="w-5 h-5 mr-3"
|
||||
/>
|
||||
<span>Continue with Google</span>
|
||||
{% elif provider.id == 'discord' %}
|
||||
<img
|
||||
src="{% static 'images/discord-icon.svg' %}"
|
||||
alt="Discord"
|
||||
class="w-5 h-5 mr-3"
|
||||
/>
|
||||
<span>Continue with Discord</span>
|
||||
{% endif %}
|
||||
</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
<div class="auth-divider">
|
||||
<span>Or continue with email</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% include "account/partials/login_form.html" %}
|
||||
|
||||
<div class="mt-6 text-sm text-center">
|
||||
<p class="text-gray-600 dark:text-gray-400">
|
||||
{% trans "Don't have an account?" %}
|
||||
<a
|
||||
href="{% url 'account_signup' %}"
|
||||
class="ml-1 font-medium transition-colors text-primary hover:text-primary/80 focus:outline-hidden focus:underline"
|
||||
>
|
||||
{% trans "Sign up" %}
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -1,101 +0,0 @@
|
||||
{% load i18n %}
|
||||
{% load account socialaccount %}
|
||||
{% load turnstile_tags %}
|
||||
|
||||
<form
|
||||
class="space-y-6"
|
||||
hx-post="{% url 'account_login' %}"
|
||||
hx-target="this"
|
||||
hx-swap="outerHTML"
|
||||
hx-indicator="#login-indicator"
|
||||
>
|
||||
{% csrf_token %}
|
||||
{% if form.non_field_errors %}
|
||||
<div class="alert alert-error">
|
||||
<div class="text-sm">{{ form.non_field_errors }}</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div>
|
||||
<label for="id_login" class="form-label">
|
||||
{% trans "Username or Email" %}
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
name="login"
|
||||
id="id_login"
|
||||
required
|
||||
autocomplete="username email"
|
||||
class="form-input"
|
||||
/>
|
||||
{% if form.login.errors %}
|
||||
<p class="form-error">{{ form.login.errors }}</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="id_password" class="form-label">
|
||||
{% trans "Password" %}
|
||||
</label>
|
||||
<input
|
||||
type="password"
|
||||
name="password"
|
||||
id="id_password"
|
||||
required
|
||||
autocomplete="current-password"
|
||||
class="form-input"
|
||||
/>
|
||||
{% if form.password.errors %}
|
||||
<p class="form-error">{{ form.password.errors }}</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center">
|
||||
<input
|
||||
type="checkbox"
|
||||
name="remember"
|
||||
id="id_remember"
|
||||
class="w-4 h-4 border-gray-300 rounded text-primary focus:ring-primary/50 dark:border-gray-700"
|
||||
/>
|
||||
<label
|
||||
for="id_remember"
|
||||
class="block ml-2 text-sm text-gray-700 dark:text-gray-300"
|
||||
>
|
||||
{% trans "Remember me" %}
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="text-sm">
|
||||
<a
|
||||
href="{% url 'account_reset_password' %}"
|
||||
class="font-medium transition-colors text-primary hover:text-primary/80 focus:outline-hidden focus:underline"
|
||||
>
|
||||
{% trans "Forgot Password?" %}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% turnstile_widget %}
|
||||
{% if redirect_field_value %}
|
||||
<input
|
||||
type="hidden"
|
||||
name="{{ redirect_field_name }}"
|
||||
value="{{ redirect_field_value }}"
|
||||
/>
|
||||
{% endif %}
|
||||
|
||||
<div>
|
||||
<button type="submit" class="w-full btn-primary">
|
||||
<i class="mr-2 fas fa-sign-in-alt"></i>
|
||||
{% trans "Sign In" %}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<!-- Loading indicator -->
|
||||
<div id="login-indicator" class="htmx-indicator">
|
||||
<div class="flex items-center justify-center w-full py-4">
|
||||
<div class="w-8 h-8 border-4 rounded-full border-primary border-t-transparent animate-spin"></div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,71 +0,0 @@
|
||||
{% load i18n %}
|
||||
{% load account socialaccount %}
|
||||
{% load static %}
|
||||
|
||||
<div
|
||||
id="login-modal"
|
||||
class="fixed inset-0 z-50 flex items-center justify-center p-4 overflow-y-auto bg-black/50 backdrop-blur-xs"
|
||||
>
|
||||
<div class="w-full max-w-lg my-auto bg-white rounded-lg shadow-xl dark:bg-gray-800 max-h-[90vh] overflow-y-auto">
|
||||
<div class="sticky top-0 flex justify-between p-6 bg-white border-b dark:bg-gray-800 dark:border-gray-700">
|
||||
<h2 class="text-2xl font-bold text-gray-900 dark:text-white">{% trans "Welcome Back" %}</h2>
|
||||
<button
|
||||
onclick="this.closest('#login-modal').remove()"
|
||||
class="text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200"
|
||||
>
|
||||
<i class="fas fa-times"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="p-6">
|
||||
{% get_providers as socialaccount_providers %}
|
||||
{% if socialaccount_providers %}
|
||||
<div class="space-y-3">
|
||||
{% for provider in socialaccount_providers %}
|
||||
<a
|
||||
href="{% provider_login_url provider.id process='login' %}"
|
||||
class="btn-social {% if provider.id == 'discord' %}btn-discord{% elif provider.id == 'google' %}btn-google{% endif %}"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
onkeydown="if(event.key === 'Enter' || event.key === ' ') { this.click(); event.preventDefault(); }"
|
||||
>
|
||||
{% if provider.id == 'google' %}
|
||||
<img
|
||||
src="{% static 'images/google-icon.svg' %}"
|
||||
alt="Google"
|
||||
class="w-5 h-5 mr-3"
|
||||
/>
|
||||
<span>Continue with Google</span>
|
||||
{% elif provider.id == 'discord' %}
|
||||
<img
|
||||
src="{% static 'images/discord-icon.svg' %}"
|
||||
alt="Discord"
|
||||
class="w-5 h-5 mr-3"
|
||||
/>
|
||||
<span>Continue with Discord</span>
|
||||
{% endif %}
|
||||
</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
<div class="auth-divider">
|
||||
<span>Or continue with email</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% include "account/partials/login_form.html" %}
|
||||
|
||||
<div class="mt-6 text-sm text-center">
|
||||
<p class="text-gray-600 dark:text-gray-400">
|
||||
{% trans "Don't have an account?" %}
|
||||
<a
|
||||
href="{% url 'account_signup' %}"
|
||||
class="ml-1 font-medium transition-colors text-primary hover:text-primary/80 focus:outline-hidden focus:underline"
|
||||
>
|
||||
{% trans "Sign up" %}
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,263 +0,0 @@
|
||||
{% load i18n %}
|
||||
{% load account socialaccount %}
|
||||
{% load static %}
|
||||
{% load turnstile_tags %}
|
||||
|
||||
<div
|
||||
id="signup-modal"
|
||||
class="fixed inset-0 z-50 flex items-center justify-center p-4 overflow-y-auto bg-black/50 backdrop-blur-xs"
|
||||
@click.away="document.getElementById('signup-modal').remove()"
|
||||
>
|
||||
<div class="w-full max-w-lg my-auto bg-white rounded-lg shadow-xl dark:bg-gray-800 max-h-[90vh] overflow-y-auto">
|
||||
<div class="sticky top-0 flex justify-between p-6 bg-white border-b dark:bg-gray-800 dark:border-gray-700">
|
||||
<h2 class="text-2xl font-bold text-gray-900 dark:text-white">{% trans "Create Account" %}</h2>
|
||||
<button
|
||||
onclick="this.closest('#signup-modal').remove()"
|
||||
class="text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200"
|
||||
>
|
||||
<i class="fas fa-times"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="p-6">
|
||||
{% get_providers as socialaccount_providers %}
|
||||
{% if socialaccount_providers %}
|
||||
<div class="space-y-3">
|
||||
{% for provider in socialaccount_providers %}
|
||||
<a
|
||||
href="{% provider_login_url provider.id process='signup' %}"
|
||||
class="btn-social {% if provider.id == 'discord' %}btn-discord{% elif provider.id == 'google' %}btn-google{% endif %}"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
onkeydown="if(event.key === 'Enter' || event.key === ' ') { this.click(); event.preventDefault(); }"
|
||||
>
|
||||
{% if provider.id == 'google' %}
|
||||
<img
|
||||
src="{% static 'images/google-icon.svg' %}"
|
||||
alt="Google"
|
||||
class="w-5 h-5 mr-3"
|
||||
/>
|
||||
<span>Continue with Google</span>
|
||||
{% elif provider.id == 'discord' %}
|
||||
<img
|
||||
src="{% static 'images/discord-icon.svg' %}"
|
||||
alt="Discord"
|
||||
class="w-5 h-5 mr-3"
|
||||
/>
|
||||
<span>Continue with Discord</span>
|
||||
{% endif %}
|
||||
</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
<div class="auth-divider">
|
||||
<span>Or continue with email</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<form
|
||||
class="space-y-6"
|
||||
hx-post="{% url 'account_signup' %}"
|
||||
hx-target="this"
|
||||
hx-swap="outerHTML"
|
||||
hx-indicator="#signup-indicator"
|
||||
>
|
||||
{% csrf_token %}
|
||||
{% if form.non_field_errors %}
|
||||
<div class="alert alert-error">
|
||||
<div class="text-sm">{{ form.non_field_errors }}</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div>
|
||||
<label for="id_username" class="form-label">
|
||||
{% trans "Username" %}
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
name="username"
|
||||
id="id_username"
|
||||
required
|
||||
autocomplete="username"
|
||||
class="form-input"
|
||||
/>
|
||||
{% if form.username.errors %}
|
||||
<p class="form-error">{{ form.username.errors }}</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="id_email" class="form-label">{% trans "Email" %}</label>
|
||||
<input
|
||||
type="email"
|
||||
name="email"
|
||||
id="id_email"
|
||||
required
|
||||
autocomplete="email"
|
||||
class="form-input"
|
||||
/>
|
||||
{% if form.email.errors %}
|
||||
<p class="form-error">{{ form.email.errors }}</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="id_password1" class="form-label">
|
||||
{% trans "Password" %}
|
||||
</label>
|
||||
<input
|
||||
type="password"
|
||||
name="password1"
|
||||
id="id_password1"
|
||||
required
|
||||
autocomplete="new-password"
|
||||
class="form-input"
|
||||
oninput="validatePassword(this.value)"
|
||||
/>
|
||||
{% if form.password1.errors %}
|
||||
<p class="form-error">{{ form.password1.errors }}</p>
|
||||
{% endif %}
|
||||
<div class="mt-3 password-requirements">
|
||||
<ul id="passwordRequirements">
|
||||
<li class="invalid" id="req-length">
|
||||
<i class="text-xs fas fa-circle"></i>
|
||||
<span>Must be at least 8 characters long</span>
|
||||
</li>
|
||||
<li class="invalid" id="req-similar">
|
||||
<i class="text-xs fas fa-circle"></i>
|
||||
<span>Can't be too similar to your personal information</span>
|
||||
</li>
|
||||
<li class="invalid" id="req-common">
|
||||
<i class="text-xs fas fa-circle"></i>
|
||||
<span>Can't be a commonly used password</span>
|
||||
</li>
|
||||
<li class="invalid" id="req-numeric">
|
||||
<i class="text-xs fas fa-circle"></i>
|
||||
<span>Can't be entirely numeric</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="id_password2" class="form-label">
|
||||
{% trans "Confirm Password" %}
|
||||
</label>
|
||||
<input
|
||||
type="password"
|
||||
name="password2"
|
||||
id="id_password2"
|
||||
required
|
||||
autocomplete="new-password"
|
||||
class="form-input"
|
||||
oninput="validatePasswordMatch()"
|
||||
/>
|
||||
{% if form.password2.errors %}
|
||||
<p class="form-error">{{ form.password2.errors }}</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{% turnstile_widget %}
|
||||
{% if redirect_field_value %}
|
||||
<input
|
||||
type="hidden"
|
||||
name="{{ redirect_field_name }}"
|
||||
value="{{ redirect_field_value }}"
|
||||
/>
|
||||
{% endif %}
|
||||
|
||||
<div>
|
||||
<button type="submit" class="w-full btn-primary">
|
||||
<i class="mr-2 fas fa-user-plus"></i>
|
||||
{% trans "Create Account" %}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<!-- Loading indicator -->
|
||||
<div id="signup-indicator" class="htmx-indicator">
|
||||
<div class="flex items-center justify-center w-full py-4">
|
||||
<div class="w-8 h-8 border-4 rounded-full border-primary border-t-transparent animate-spin"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-6 text-sm text-center">
|
||||
<p class="text-gray-600 dark:text-gray-400">
|
||||
{% trans "Already have an account?" %}
|
||||
<a
|
||||
href="{% url 'account_login' %}"
|
||||
class="ml-1 font-medium transition-colors text-primary hover:text-primary/80 focus:outline-hidden focus:underline"
|
||||
onkeydown="if(event.key === 'Enter') { this.click(); }"
|
||||
>
|
||||
{% trans "Sign in" %}
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function validatePassword(password) {
|
||||
// Length requirement
|
||||
const lengthReq = document.getElementById("req-length");
|
||||
if (password.length >= 8) {
|
||||
lengthReq.classList.remove("invalid");
|
||||
lengthReq.classList.add("valid");
|
||||
lengthReq.querySelector("i").classList.remove("fa-circle");
|
||||
lengthReq.querySelector("i").classList.add("fa-check");
|
||||
} else {
|
||||
lengthReq.classList.remove("valid");
|
||||
lengthReq.classList.add("invalid");
|
||||
lengthReq.querySelector("i").classList.remove("fa-check");
|
||||
lengthReq.querySelector("i").classList.add("fa-circle");
|
||||
}
|
||||
|
||||
// Numeric requirement
|
||||
const numericReq = document.getElementById("req-numeric");
|
||||
if (!/^\d+$/.test(password)) {
|
||||
numericReq.classList.remove("invalid");
|
||||
numericReq.classList.add("valid");
|
||||
numericReq.querySelector("i").classList.remove("fa-circle");
|
||||
numericReq.querySelector("i").classList.add("fa-check");
|
||||
} else {
|
||||
numericReq.classList.remove("valid");
|
||||
numericReq.classList.add("invalid");
|
||||
numericReq.querySelector("i").classList.remove("fa-check");
|
||||
numericReq.querySelector("i").classList.add("fa-circle");
|
||||
}
|
||||
|
||||
// Common password check (basic)
|
||||
const commonReq = document.getElementById("req-common");
|
||||
const commonPasswords = ["password", "12345678", "qwerty", "letmein"];
|
||||
if (!commonPasswords.includes(password.toLowerCase())) {
|
||||
commonReq.classList.remove("invalid");
|
||||
commonReq.classList.add("valid");
|
||||
commonReq.querySelector("i").classList.remove("fa-circle");
|
||||
commonReq.querySelector("i").classList.add("fa-check");
|
||||
} else {
|
||||
commonReq.classList.remove("valid");
|
||||
commonReq.classList.add("invalid");
|
||||
commonReq.querySelector("i").classList.remove("fa-check");
|
||||
commonReq.querySelector("i").classList.add("fa-circle");
|
||||
}
|
||||
}
|
||||
|
||||
function validatePasswordMatch() {
|
||||
const password1 = document.getElementById("id_password1").value;
|
||||
const password2 = document.getElementById("id_password2").value;
|
||||
const password2Input = document.getElementById("id_password2");
|
||||
|
||||
if (password2.length > 0) {
|
||||
if (password1 === password2) {
|
||||
password2Input.classList.remove("border-red-500");
|
||||
password2Input.classList.add("border-green-500");
|
||||
} else {
|
||||
password2Input.classList.remove("border-green-500");
|
||||
password2Input.classList.add("border-red-500");
|
||||
}
|
||||
} else {
|
||||
password2Input.classList.remove("border-green-500", "border-red-500");
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -1,243 +0,0 @@
|
||||
{% extends "base/base.html" %}
|
||||
{% load i18n %}
|
||||
{% load account socialaccount %}
|
||||
{% load static %}
|
||||
{% load turnstile_tags %}
|
||||
|
||||
{% block title %}Register - ThrillWiki{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="flex items-center justify-center py-8 md:py-12">
|
||||
<div class="w-full max-w-lg px-4">
|
||||
<div class="auth-card">
|
||||
<h1 class="auth-title">{% trans "Create Account" %}</h1>
|
||||
|
||||
{% get_providers as socialaccount_providers %}
|
||||
{% if socialaccount_providers %}
|
||||
<div class="space-y-3">
|
||||
{% for provider in socialaccount_providers %}
|
||||
<a
|
||||
href="{% provider_login_url provider.id process='signup' %}"
|
||||
class="btn-social {% if provider.id == 'discord' %}btn-discord{% elif provider.id == 'google' %}btn-google{% endif %}"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
onkeydown="if(event.key === 'Enter' || event.key === ' ') { this.click(); event.preventDefault(); }"
|
||||
>
|
||||
{% if provider.id == 'google' %}
|
||||
<img
|
||||
src="{% static 'images/google-icon.svg' %}"
|
||||
alt="Google"
|
||||
class="w-5 h-5 mr-3"
|
||||
/>
|
||||
<span>Continue with Google</span>
|
||||
{% elif provider.id == 'discord' %}
|
||||
<img
|
||||
src="{% static 'images/discord-icon.svg' %}"
|
||||
alt="Discord"
|
||||
class="w-5 h-5 mr-3"
|
||||
/>
|
||||
<span>Continue with Discord</span>
|
||||
{% endif %}
|
||||
</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
<div class="auth-divider">
|
||||
<span>Or continue with email</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<form class="space-y-6" method="POST" action="{% url 'account_signup' %}">
|
||||
{% csrf_token %}
|
||||
{% if form.non_field_errors %}
|
||||
<div class="alert alert-error">
|
||||
<div class="text-sm">{{ form.non_field_errors }}</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div>
|
||||
<label for="id_username" class="form-label">
|
||||
{% trans "Username" %}
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
name="username"
|
||||
id="id_username"
|
||||
required
|
||||
autocomplete="username"
|
||||
class="form-input"
|
||||
/>
|
||||
{% if form.username.errors %}
|
||||
<p class="form-error">{{ form.username.errors }}</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="id_email" class="form-label">{% trans "Email" %}</label>
|
||||
<input
|
||||
type="email"
|
||||
name="email"
|
||||
id="id_email"
|
||||
required
|
||||
autocomplete="email"
|
||||
class="form-input"
|
||||
/>
|
||||
{% if form.email.errors %}
|
||||
<p class="form-error">{{ form.email.errors }}</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="id_password1" class="form-label">
|
||||
{% trans "Password" %}
|
||||
</label>
|
||||
<input
|
||||
type="password"
|
||||
name="password1"
|
||||
id="id_password1"
|
||||
required
|
||||
autocomplete="new-password"
|
||||
class="form-input"
|
||||
oninput="validatePassword(this.value)"
|
||||
/>
|
||||
{% if form.password1.errors %}
|
||||
<p class="form-error">{{ form.password1.errors }}</p>
|
||||
{% endif %}
|
||||
<div class="mt-3 password-requirements">
|
||||
<ul id="passwordRequirements">
|
||||
<li class="invalid" id="req-length">
|
||||
<i class="text-xs fas fa-circle"></i>
|
||||
<span>Must be at least 8 characters long</span>
|
||||
</li>
|
||||
<li class="invalid" id="req-similar">
|
||||
<i class="text-xs fas fa-circle"></i>
|
||||
<span>Can't be too similar to your personal information</span>
|
||||
</li>
|
||||
<li class="invalid" id="req-common">
|
||||
<i class="text-xs fas fa-circle"></i>
|
||||
<span>Can't be a commonly used password</span>
|
||||
</li>
|
||||
<li class="invalid" id="req-numeric">
|
||||
<i class="text-xs fas fa-circle"></i>
|
||||
<span>Can't be entirely numeric</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="id_password2" class="form-label">
|
||||
{% trans "Confirm Password" %}
|
||||
</label>
|
||||
<input
|
||||
type="password"
|
||||
name="password2"
|
||||
id="id_password2"
|
||||
required
|
||||
autocomplete="new-password"
|
||||
class="form-input"
|
||||
oninput="validatePasswordMatch()"
|
||||
/>
|
||||
{% if form.password2.errors %}
|
||||
<p class="form-error">{{ form.password2.errors }}</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{% turnstile_widget %}
|
||||
{% if redirect_field_value %}
|
||||
<input
|
||||
type="hidden"
|
||||
name="{{ redirect_field_name }}"
|
||||
value="{{ redirect_field_value }}"
|
||||
/>
|
||||
{% endif %}
|
||||
|
||||
<div>
|
||||
<button type="submit" class="w-full btn-primary">
|
||||
<i class="mr-2 fas fa-user-plus"></i>
|
||||
{% trans "Create Account" %}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div class="mt-6 text-sm text-center">
|
||||
<p class="text-gray-600 dark:text-gray-400">
|
||||
{% trans "Already have an account?" %}
|
||||
<a
|
||||
href="{% url 'account_login' %}"
|
||||
class="ml-1 font-medium transition-colors text-primary hover:text-primary/80 focus:outline-hidden focus:underline"
|
||||
onkeydown="if(event.key === 'Enter') { this.click(); }"
|
||||
>
|
||||
{% trans "Sign in" %}
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function validatePassword(password) {
|
||||
// Length requirement
|
||||
const lengthReq = document.getElementById("req-length");
|
||||
if (password.length >= 8) {
|
||||
lengthReq.classList.remove("invalid");
|
||||
lengthReq.classList.add("valid");
|
||||
lengthReq.querySelector("i").classList.remove("fa-circle");
|
||||
lengthReq.querySelector("i").classList.add("fa-check");
|
||||
} else {
|
||||
lengthReq.classList.remove("valid");
|
||||
lengthReq.classList.add("invalid");
|
||||
lengthReq.querySelector("i").classList.remove("fa-check");
|
||||
lengthReq.querySelector("i").classList.add("fa-circle");
|
||||
}
|
||||
|
||||
// Numeric requirement
|
||||
const numericReq = document.getElementById("req-numeric");
|
||||
if (!/^\d+$/.test(password)) {
|
||||
numericReq.classList.remove("invalid");
|
||||
numericReq.classList.add("valid");
|
||||
numericReq.querySelector("i").classList.remove("fa-circle");
|
||||
numericReq.querySelector("i").classList.add("fa-check");
|
||||
} else {
|
||||
numericReq.classList.remove("valid");
|
||||
numericReq.classList.add("invalid");
|
||||
numericReq.querySelector("i").classList.remove("fa-check");
|
||||
numericReq.querySelector("i").classList.add("fa-circle");
|
||||
}
|
||||
|
||||
// Common password check (basic)
|
||||
const commonReq = document.getElementById("req-common");
|
||||
const commonPasswords = ["password", "12345678", "qwerty", "letmein"];
|
||||
if (!commonPasswords.includes(password.toLowerCase())) {
|
||||
commonReq.classList.remove("invalid");
|
||||
commonReq.classList.add("valid");
|
||||
commonReq.querySelector("i").classList.remove("fa-circle");
|
||||
commonReq.querySelector("i").classList.add("fa-check");
|
||||
} else {
|
||||
commonReq.classList.remove("valid");
|
||||
commonReq.classList.add("invalid");
|
||||
commonReq.querySelector("i").classList.remove("fa-check");
|
||||
commonReq.querySelector("i").classList.add("fa-circle");
|
||||
}
|
||||
}
|
||||
|
||||
function validatePasswordMatch() {
|
||||
const password1 = document.getElementById("id_password1").value;
|
||||
const password2 = document.getElementById("id_password2").value;
|
||||
const password2Input = document.getElementById("id_password2");
|
||||
|
||||
if (password2.length > 0) {
|
||||
if (password1 === password2) {
|
||||
password2Input.classList.remove("border-red-500");
|
||||
password2Input.classList.add("border-green-500");
|
||||
} else {
|
||||
password2Input.classList.remove("border-green-500");
|
||||
password2Input.classList.add("border-red-500");
|
||||
}
|
||||
} else {
|
||||
password2Input.classList.remove("border-green-500", "border-red-500");
|
||||
}
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user