mirror of
https://github.com/pacnpal/thrillwiki_django_no_react.git
synced 2025-12-20 10:11:09 -05:00
397 lines
14 KiB
HTML
397 lines
14 KiB
HTML
{% comment %}
|
|
Auth Modal Component - Django Cotton Version
|
|
Enhanced Authentication Modal Component that matches React frontend AuthDialog functionality
|
|
Preserves EXACT Alpine.js behavior, styling, and functionality
|
|
|
|
Usage: <c-auth_modal login_title="Sign In" register_title="Create Account" />
|
|
|
|
Critical: ALL Alpine.js directives, transitions, and behaviors are preserved exactly
|
|
{% endcomment %}
|
|
|
|
{% load static %}
|
|
{% load i18n %}
|
|
{% load account socialaccount %}
|
|
|
|
<c-vars
|
|
login_title="Sign In"
|
|
login_subtitle="Enter your credentials to access your account"
|
|
register_title="Create Account"
|
|
register_subtitle="Join ThrillWiki to start exploring theme parks"
|
|
modal_classes=""
|
|
overlay_classes=""
|
|
content_classes=""
|
|
close_button_classes=""
|
|
form_classes=""
|
|
social_divider_text_login="Or continue with"
|
|
social_divider_text_register="Or continue with email"
|
|
forgot_password_url=""
|
|
x_data_override=""
|
|
window_key="authModal"
|
|
show_social_providers="true"
|
|
show_forgot_password="true"
|
|
/>
|
|
|
|
<!-- Auth Modal Component -->
|
|
<div
|
|
x-data="{{ x_data_override|default:'authModal' }}"
|
|
x-show="open"
|
|
x-cloak
|
|
x-init="window.{{ window_key }} = $data"
|
|
class="fixed inset-0 z-50 flex items-center justify-center {{ modal_classes }}"
|
|
@keydown.escape.window="close()"
|
|
>
|
|
<!-- Modal Overlay -->
|
|
<div
|
|
x-show="open"
|
|
x-transition:enter="transition-opacity ease-linear duration-300"
|
|
x-transition:enter-start="opacity-0"
|
|
x-transition:enter-end="opacity-100"
|
|
x-transition:leave="transition-opacity ease-linear duration-300"
|
|
x-transition:leave-start="opacity-100"
|
|
x-transition:leave-end="opacity-0"
|
|
class="fixed inset-0 bg-background/80 backdrop-blur-sm {{ overlay_classes }}"
|
|
@click="close()"
|
|
></div>
|
|
|
|
<!-- Modal Content -->
|
|
<div
|
|
x-show="open"
|
|
x-transition:enter="transition ease-out duration-300"
|
|
x-transition:enter-start="transform opacity-0 scale-95"
|
|
x-transition:enter-end="transform opacity-100 scale-100"
|
|
x-transition:leave="transition ease-in duration-200"
|
|
x-transition:leave-start="transform opacity-100 scale-100"
|
|
x-transition:leave-end="transform opacity-0 scale-95"
|
|
class="relative w-full max-w-md mx-4 bg-background border rounded-lg shadow-lg {{ content_classes }}"
|
|
@click.stop
|
|
>
|
|
<!-- Close Button -->
|
|
<button
|
|
@click="close()"
|
|
class="absolute top-4 right-4 p-2 text-muted-foreground hover:text-foreground rounded-md hover:bg-accent transition-colors {{ close_button_classes }}"
|
|
>
|
|
<i class="fas fa-times w-4 h-4"></i>
|
|
</button>
|
|
|
|
<!-- Login Form -->
|
|
<div x-show="mode === 'login'" class="p-6">
|
|
<div class="text-center mb-6">
|
|
<h2 class="text-2xl font-bold text-transparent bg-clip-text bg-gradient-to-r from-blue-600 to-purple-700">
|
|
{{ login_title }}
|
|
</h2>
|
|
<p class="text-sm text-muted-foreground mt-2">
|
|
{{ login_subtitle }}
|
|
</p>
|
|
</div>
|
|
|
|
<!-- Social Login Buttons -->
|
|
{% if show_social_providers == "true" %}
|
|
<div x-show="socialProviders.length > 0" class="mb-6">
|
|
<div class="grid grid-cols-2 gap-4" x-show="!socialLoading">
|
|
<template x-for="provider in socialProviders" :key="provider.id">
|
|
<button
|
|
@click="handleSocialLogin(provider.id)"
|
|
class="flex items-center justify-center px-4 py-2 text-sm font-medium text-white rounded-md transition-colors"
|
|
:class="{
|
|
'bg-[#4285F4] hover:bg-[#357AE8]': provider.id === 'google',
|
|
'bg-[#5865F2] hover:bg-[#4752C4]': provider.id === 'discord',
|
|
'bg-primary hover:bg-primary/90': !['google', 'discord'].includes(provider.id)
|
|
}"
|
|
>
|
|
<i
|
|
class="mr-2 w-4 h-4"
|
|
:class="{
|
|
'fab fa-google': provider.id === 'google',
|
|
'fab fa-discord': provider.id === 'discord'
|
|
}"
|
|
></i>
|
|
<span x-text="provider.name"></span>
|
|
</button>
|
|
</template>
|
|
</div>
|
|
|
|
<div x-show="socialLoading" class="grid grid-cols-2 gap-4">
|
|
<div class="h-10 bg-muted animate-pulse rounded-md"></div>
|
|
<div class="h-10 bg-muted animate-pulse rounded-md"></div>
|
|
</div>
|
|
|
|
<!-- Divider -->
|
|
<div class="relative my-6">
|
|
<div class="absolute inset-0 flex items-center">
|
|
<div class="w-full border-t border-muted"></div>
|
|
</div>
|
|
<div class="relative flex justify-center text-xs uppercase">
|
|
<span class="bg-background px-2 text-muted-foreground">
|
|
{{ social_divider_text_login }}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
|
|
<!-- Login Form -->
|
|
<form
|
|
@submit.prevent="handleLogin()"
|
|
class="space-y-4 {{ form_classes }}"
|
|
>
|
|
<div class="space-y-2">
|
|
<label for="login-username" class="text-sm font-medium">
|
|
Email or Username
|
|
</label>
|
|
<input
|
|
id="login-username"
|
|
type="text"
|
|
x-model="loginForm.username"
|
|
placeholder="Enter your email or username"
|
|
class="input w-full"
|
|
required
|
|
/>
|
|
</div>
|
|
|
|
<div class="space-y-2">
|
|
<label for="login-password" class="text-sm font-medium">
|
|
Password
|
|
</label>
|
|
<div class="relative">
|
|
<input
|
|
id="login-password"
|
|
:type="showPassword ? 'text' : 'password'"
|
|
x-model="loginForm.password"
|
|
placeholder="Enter your password"
|
|
class="input w-full pr-10"
|
|
required
|
|
/>
|
|
<button
|
|
type="button"
|
|
@click="showPassword = !showPassword"
|
|
class="absolute right-3 top-1/2 transform -translate-y-1/2 text-muted-foreground hover:text-foreground"
|
|
>
|
|
<i :class="showPassword ? 'fas fa-eye-slash' : 'fas fa-eye'" class="w-4 h-4"></i>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
{% if show_forgot_password == "true" %}
|
|
<div class="flex items-center justify-between">
|
|
<a
|
|
href="{% if forgot_password_url %}{{ forgot_password_url }}{% else %}{% url 'account_reset_password' %}{% endif %}"
|
|
class="text-sm text-primary hover:text-primary/80 underline-offset-4 hover:underline font-medium"
|
|
>
|
|
Forgot password?
|
|
</a>
|
|
</div>
|
|
{% endif %}
|
|
|
|
<!-- Error Messages -->
|
|
<div x-show="loginError" class="p-3 text-sm text-destructive-foreground bg-destructive/10 border border-destructive/20 rounded-md">
|
|
<span x-text="loginError"></span>
|
|
</div>
|
|
|
|
<button
|
|
type="submit"
|
|
:disabled="loginLoading"
|
|
class="btn btn-default w-full bg-gradient-to-r from-blue-600 to-purple-700 hover:from-blue-700 hover:to-purple-800 text-white"
|
|
>
|
|
<span x-show="!loginLoading">{{ login_title }}</span>
|
|
<span x-show="loginLoading" class="flex items-center">
|
|
<i class="fas fa-spinner fa-spin mr-2"></i>
|
|
Signing in...
|
|
</span>
|
|
</button>
|
|
</form>
|
|
|
|
<!-- Switch to Register -->
|
|
<div class="text-center text-sm text-muted-foreground mt-6">
|
|
Don't have an account?
|
|
<button
|
|
@click="switchToRegister()"
|
|
class="text-primary hover:underline font-medium ml-1"
|
|
type="button"
|
|
>
|
|
Sign up
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Register Form -->
|
|
<div x-show="mode === 'register'" class="p-6">
|
|
<div class="text-center mb-6">
|
|
<h2 class="text-2xl font-bold text-transparent bg-clip-text bg-gradient-to-r from-blue-600 to-purple-700">
|
|
{{ register_title }}
|
|
</h2>
|
|
<p class="text-sm text-muted-foreground mt-2">
|
|
{{ register_subtitle }}
|
|
</p>
|
|
</div>
|
|
|
|
<!-- Social Registration Buttons -->
|
|
{% if show_social_providers == "true" %}
|
|
<div x-show="socialProviders.length > 0" class="mb-6">
|
|
<div class="grid grid-cols-2 gap-4" x-show="!socialLoading">
|
|
<template x-for="provider in socialProviders" :key="provider.id">
|
|
<button
|
|
@click="handleSocialLogin(provider.id)"
|
|
class="flex items-center justify-center px-4 py-2 text-sm font-medium text-white rounded-md transition-colors"
|
|
:class="{
|
|
'bg-[#4285F4] hover:bg-[#357AE8]': provider.id === 'google',
|
|
'bg-[#5865F2] hover:bg-[#4752C4]': provider.id === 'discord',
|
|
'bg-primary hover:bg-primary/90': !['google', 'discord'].includes(provider.id)
|
|
}"
|
|
>
|
|
<i
|
|
class="mr-2 w-4 h-4"
|
|
:class="{
|
|
'fab fa-google': provider.id === 'google',
|
|
'fab fa-discord': provider.id === 'discord'
|
|
}"
|
|
></i>
|
|
<span x-text="provider.name"></span>
|
|
</button>
|
|
</template>
|
|
</div>
|
|
|
|
<!-- Divider -->
|
|
<div class="relative my-6">
|
|
<div class="absolute inset-0 flex items-center">
|
|
<div class="w-full border-t border-muted"></div>
|
|
</div>
|
|
<div class="relative flex justify-center text-xs uppercase">
|
|
<span class="bg-background px-2 text-muted-foreground">
|
|
{{ social_divider_text_register }}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
|
|
<!-- Register Form -->
|
|
<form
|
|
@submit.prevent="handleRegister()"
|
|
class="space-y-4 {{ form_classes }}"
|
|
>
|
|
<div class="grid grid-cols-2 gap-4">
|
|
<div class="space-y-2">
|
|
<label for="register-first-name" class="text-sm font-medium">
|
|
First Name
|
|
</label>
|
|
<input
|
|
id="register-first-name"
|
|
type="text"
|
|
x-model="registerForm.first_name"
|
|
placeholder="First name"
|
|
class="input w-full"
|
|
required
|
|
/>
|
|
</div>
|
|
<div class="space-y-2">
|
|
<label for="register-last-name" class="text-sm font-medium">
|
|
Last Name
|
|
</label>
|
|
<input
|
|
id="register-last-name"
|
|
type="text"
|
|
x-model="registerForm.last_name"
|
|
placeholder="Last name"
|
|
class="input w-full"
|
|
required
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="space-y-2">
|
|
<label for="register-email" class="text-sm font-medium">
|
|
Email
|
|
</label>
|
|
<input
|
|
id="register-email"
|
|
type="email"
|
|
x-model="registerForm.email"
|
|
placeholder="Enter your email"
|
|
class="input w-full"
|
|
required
|
|
/>
|
|
</div>
|
|
|
|
<div class="space-y-2">
|
|
<label for="register-username" class="text-sm font-medium">
|
|
Username
|
|
</label>
|
|
<input
|
|
id="register-username"
|
|
type="text"
|
|
x-model="registerForm.username"
|
|
placeholder="Choose a username"
|
|
class="input w-full"
|
|
required
|
|
/>
|
|
</div>
|
|
|
|
<div class="space-y-2">
|
|
<label for="register-password" class="text-sm font-medium">
|
|
Password
|
|
</label>
|
|
<div class="relative">
|
|
<input
|
|
id="register-password"
|
|
:type="showPassword ? 'text' : 'password'"
|
|
x-model="registerForm.password1"
|
|
placeholder="Create a password"
|
|
class="input w-full pr-10"
|
|
required
|
|
/>
|
|
<button
|
|
type="button"
|
|
@click="showPassword = !showPassword"
|
|
class="absolute right-3 top-1/2 transform -translate-y-1/2 text-muted-foreground hover:text-foreground"
|
|
>
|
|
<i :class="showPassword ? 'fas fa-eye-slash' : 'fas fa-eye'" class="w-4 h-4"></i>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="space-y-2">
|
|
<label for="register-password2" class="text-sm font-medium">
|
|
Confirm Password
|
|
</label>
|
|
<input
|
|
id="register-password2"
|
|
:type="showPassword ? 'text' : 'password'"
|
|
x-model="registerForm.password2"
|
|
placeholder="Confirm your password"
|
|
class="input w-full"
|
|
required
|
|
/>
|
|
</div>
|
|
|
|
<!-- Error Messages -->
|
|
<div x-show="registerError" class="p-3 text-sm text-destructive-foreground bg-destructive/10 border border-destructive/20 rounded-md">
|
|
<span x-text="registerError"></span>
|
|
</div>
|
|
|
|
<button
|
|
type="submit"
|
|
:disabled="registerLoading"
|
|
class="btn btn-default w-full bg-gradient-to-r from-blue-600 to-purple-700 hover:from-blue-700 hover:to-purple-800 text-white"
|
|
>
|
|
<span x-show="!registerLoading">{{ register_title }}</span>
|
|
<span x-show="registerLoading" class="flex items-center">
|
|
<i class="fas fa-spinner fa-spin mr-2"></i>
|
|
Creating account...
|
|
</span>
|
|
</button>
|
|
</form>
|
|
|
|
<!-- Switch to Login -->
|
|
<div class="text-center text-sm text-muted-foreground mt-6">
|
|
Already have an account?
|
|
<button
|
|
@click="switchToLogin()"
|
|
class="text-primary hover:underline font-medium ml-1"
|
|
type="button"
|
|
>
|
|
Sign in
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div> |