mirror of
https://github.com/pacnpal/thrillwiki_django_no_react.git
synced 2025-12-20 12:31:22 -05:00
Add a new reusable authentication modal component to the platform
Integrate a new Django component for the authentication modal, ensuring parity with existing React frontend functionality, and add a corresponding test view for comparison. Replit-Commit-Author: Agent Replit-Commit-Session-Id: dcfff319-6e91-4220-98a9-8295b87755b7 Replit-Commit-Checkpoint-Type: intermediate_checkpoint
This commit is contained in:
@@ -12,7 +12,7 @@ Matches React frontend AuthDialog functionality with modal-based auth
|
|||||||
x-data="authModal"
|
x-data="authModal"
|
||||||
x-show="open"
|
x-show="open"
|
||||||
x-cloak
|
x-cloak
|
||||||
x-init="window.authModal = $data"
|
x-init="window.authModalOriginal = $data"
|
||||||
class="fixed inset-0 z-50 flex items-center justify-center"
|
class="fixed inset-0 z-50 flex items-center justify-center"
|
||||||
@keydown.escape.window="close()"
|
@keydown.escape.window="close()"
|
||||||
>
|
>
|
||||||
|
|||||||
397
backend/templates/cotton/auth_modal.html
Normal file
397
backend/templates/cotton/auth_modal.html
Normal file
@@ -0,0 +1,397 @@
|
|||||||
|
{% 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>
|
||||||
511
backend/templates/test_auth_modal_comparison.html
Normal file
511
backend/templates/test_auth_modal_comparison.html
Normal file
@@ -0,0 +1,511 @@
|
|||||||
|
{% load static %}
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Auth Modal Component Comparison Test</title>
|
||||||
|
<link href="{% static 'css/tailwind.css' %}" rel="stylesheet">
|
||||||
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.test-section {
|
||||||
|
margin: 2rem 0;
|
||||||
|
padding: 1rem;
|
||||||
|
border: 2px solid #e5e7eb;
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
.modal-test-container {
|
||||||
|
display: flex;
|
||||||
|
gap: 2rem;
|
||||||
|
margin: 1rem 0;
|
||||||
|
align-items: flex-start;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
.modal-test-group {
|
||||||
|
border: 1px solid #d1d5db;
|
||||||
|
padding: 1rem;
|
||||||
|
border-radius: 8px;
|
||||||
|
position: relative;
|
||||||
|
min-width: 300px;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
.modal-test-group::before {
|
||||||
|
content: attr(data-label);
|
||||||
|
position: absolute;
|
||||||
|
top: -10px;
|
||||||
|
left: 12px;
|
||||||
|
background: white;
|
||||||
|
padding: 0 8px;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #374151;
|
||||||
|
}
|
||||||
|
.test-description {
|
||||||
|
font-weight: bold;
|
||||||
|
color: #374151;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
.feature-list {
|
||||||
|
background: #f9fafb;
|
||||||
|
border: 1px solid #e5e7eb;
|
||||||
|
padding: 1rem;
|
||||||
|
margin: 0.5rem 0;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
.test-button {
|
||||||
|
background: #3b82f6;
|
||||||
|
color: white;
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
border-radius: 0.375rem;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
margin: 0.5rem;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
.test-button:hover {
|
||||||
|
background: #2563eb;
|
||||||
|
}
|
||||||
|
.test-button.secondary {
|
||||||
|
background: #6b7280;
|
||||||
|
}
|
||||||
|
.test-button.secondary:hover {
|
||||||
|
background: #4b5563;
|
||||||
|
}
|
||||||
|
.instructions {
|
||||||
|
background: #dbeafe;
|
||||||
|
border: 1px solid #93c5fd;
|
||||||
|
padding: 1rem;
|
||||||
|
border-radius: 6px;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
}
|
||||||
|
.status-indicator {
|
||||||
|
display: inline-block;
|
||||||
|
width: 12px;
|
||||||
|
height: 12px;
|
||||||
|
border-radius: 50%;
|
||||||
|
margin-right: 8px;
|
||||||
|
}
|
||||||
|
.status-pass { background: #10b981; }
|
||||||
|
.status-fail { background: #ef4444; }
|
||||||
|
.status-pending { background: #f59e0b; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body class="bg-gray-50 p-8">
|
||||||
|
<div class="max-w-7xl mx-auto">
|
||||||
|
<h1 class="text-3xl font-bold mb-8 text-center">Auth Modal Component Comparison Test</h1>
|
||||||
|
<p class="text-center text-gray-600 mb-8">Comparing original include method vs new cotton component for Auth Modal with full Alpine.js functionality</p>
|
||||||
|
|
||||||
|
<div class="instructions">
|
||||||
|
<h3 class="font-bold mb-2">Test Instructions:</h3>
|
||||||
|
<ol class="list-decimal list-inside space-y-1">
|
||||||
|
<li>Click test buttons to open both modals and compare behavior</li>
|
||||||
|
<li>Test modal opening/closing with both buttons and ESC key</li>
|
||||||
|
<li>Switch between login and register forms in both modals</li>
|
||||||
|
<li>Test form validation and error states</li>
|
||||||
|
<li>Verify all Alpine.js transitions and animations work</li>
|
||||||
|
<li>Check that styling and layout are identical</li>
|
||||||
|
<li>Test password visibility toggle functionality</li>
|
||||||
|
<li>Verify social provider buttons render correctly</li>
|
||||||
|
</ol>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Test 1: Basic Modal Functionality -->
|
||||||
|
<div class="test-section">
|
||||||
|
<h2 class="text-xl font-semibold mb-4">Test 1: Basic Modal Open/Close Functionality</h2>
|
||||||
|
<div class="test-description">Modal Opening, Closing, and Overlay Interaction</div>
|
||||||
|
|
||||||
|
<div class="modal-test-container">
|
||||||
|
<div class="modal-test-group" data-label="Original Include Version">
|
||||||
|
<button class="test-button" onclick="if(window.authModalOriginal) window.authModalOriginal.open = true">
|
||||||
|
Open Original Auth Modal
|
||||||
|
</button>
|
||||||
|
<div class="feature-list">
|
||||||
|
Features to test:
|
||||||
|
<ul class="list-disc list-inside mt-2 text-sm">
|
||||||
|
<li>Modal opens with fade-in animation</li>
|
||||||
|
<li>Background overlay blocks interaction</li>
|
||||||
|
<li>Close button works (top-right X)</li>
|
||||||
|
<li>ESC key closes modal</li>
|
||||||
|
<li>Click outside modal closes it</li>
|
||||||
|
<li>Modal content scales properly</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="modal-test-group" data-label="Cotton Component Version">
|
||||||
|
<button class="test-button" onclick="if(window.authModalCotton) window.authModalCotton.open = true">
|
||||||
|
Open Cotton Auth Modal
|
||||||
|
</button>
|
||||||
|
<div class="feature-list">
|
||||||
|
Features to test:
|
||||||
|
<ul class="list-disc list-inside mt-2 text-sm">
|
||||||
|
<li>Modal opens with fade-in animation</li>
|
||||||
|
<li>Background overlay blocks interaction</li>
|
||||||
|
<li>Close button works (top-right X)</li>
|
||||||
|
<li>ESC key closes modal</li>
|
||||||
|
<li>Click outside modal closes it</li>
|
||||||
|
<li>Modal content scales properly</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Test 2: Form Switching -->
|
||||||
|
<div class="test-section">
|
||||||
|
<h2 class="text-xl font-semibold mb-4">Test 2: Login/Register Form Switching</h2>
|
||||||
|
<div class="test-description">Form Mode Switching and Content Display</div>
|
||||||
|
|
||||||
|
<div class="modal-test-container">
|
||||||
|
<div class="modal-test-group" data-label="Original Include Version">
|
||||||
|
<button class="test-button" onclick="openOriginalModalInMode('login')">
|
||||||
|
Open in Login Mode
|
||||||
|
</button>
|
||||||
|
<button class="test-button secondary" onclick="openOriginalModalInMode('register')">
|
||||||
|
Open in Register Mode
|
||||||
|
</button>
|
||||||
|
<div class="feature-list">
|
||||||
|
Test form switching:
|
||||||
|
<ul class="list-disc list-inside mt-2 text-sm">
|
||||||
|
<li>Default mode is login</li>
|
||||||
|
<li>"Sign up" link switches to register</li>
|
||||||
|
<li>"Sign in" link switches to login</li>
|
||||||
|
<li>Form fields update correctly</li>
|
||||||
|
<li>Titles and descriptions change</li>
|
||||||
|
<li>Social button text updates</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="modal-test-group" data-label="Cotton Component Version">
|
||||||
|
<button class="test-button" onclick="openCottonModalInMode('login')">
|
||||||
|
Open in Login Mode
|
||||||
|
</button>
|
||||||
|
<button class="test-button secondary" onclick="openCottonModalInMode('register')">
|
||||||
|
Open in Register Mode
|
||||||
|
</button>
|
||||||
|
<div class="feature-list">
|
||||||
|
Test form switching:
|
||||||
|
<ul class="list-disc list-inside mt-2 text-sm">
|
||||||
|
<li>Default mode is login</li>
|
||||||
|
<li>"Sign up" link switches to register</li>
|
||||||
|
<li>"Sign in" link switches to login</li>
|
||||||
|
<li>Form fields update correctly</li>
|
||||||
|
<li>Titles and descriptions change</li>
|
||||||
|
<li>Social button text updates</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Test 3: Alpine.js Interactive Elements -->
|
||||||
|
<div class="test-section">
|
||||||
|
<h2 class="text-xl font-semibold mb-4">Test 3: Alpine.js Interactive Elements</h2>
|
||||||
|
<div class="test-description">Password Visibility, Loading States, and Error Handling</div>
|
||||||
|
|
||||||
|
<div class="modal-test-container">
|
||||||
|
<div class="modal-test-group" data-label="Original Include Version">
|
||||||
|
<button class="test-button" onclick="testOriginalInteractivity()">
|
||||||
|
Test Original Interactions
|
||||||
|
</button>
|
||||||
|
<div class="feature-list">
|
||||||
|
Interactive features:
|
||||||
|
<ul class="list-disc list-inside mt-2 text-sm">
|
||||||
|
<li>Password visibility toggle (eye icon)</li>
|
||||||
|
<li>Form validation states</li>
|
||||||
|
<li>Loading spinner on submit</li>
|
||||||
|
<li>Error message display</li>
|
||||||
|
<li>Social provider button states</li>
|
||||||
|
<li>Form field x-model binding</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="modal-test-group" data-label="Cotton Component Version">
|
||||||
|
<button class="test-button" onclick="testCottonInteractivity()">
|
||||||
|
Test Cotton Interactions
|
||||||
|
</button>
|
||||||
|
<div class="feature-list">
|
||||||
|
Interactive features:
|
||||||
|
<ul class="list-disc list-inside mt-2 text-sm">
|
||||||
|
<li>Password visibility toggle (eye icon)</li>
|
||||||
|
<li>Form validation states</li>
|
||||||
|
<li>Loading spinner on submit</li>
|
||||||
|
<li>Error message display</li>
|
||||||
|
<li>Social provider button states</li>
|
||||||
|
<li>Form field x-model binding</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Test 4: Visual Styling Comparison -->
|
||||||
|
<div class="test-section">
|
||||||
|
<h2 class="text-xl font-semibold mb-4">Test 4: Visual Styling and Layout</h2>
|
||||||
|
<div class="test-description">CSS Classes, Gradients, and Responsive Design</div>
|
||||||
|
|
||||||
|
<div class="modal-test-container">
|
||||||
|
<div class="modal-test-group" data-label="Styling Verification">
|
||||||
|
<button class="test-button" onclick="compareModalStyling()">
|
||||||
|
Compare Both Modals Side by Side
|
||||||
|
</button>
|
||||||
|
<div class="feature-list">
|
||||||
|
Visual elements to verify:
|
||||||
|
<ul class="list-disc list-inside mt-2 text-sm">
|
||||||
|
<li>Gradient title text (blue to purple)</li>
|
||||||
|
<li>Button styling and hover states</li>
|
||||||
|
<li>Input field styling and focus states</li>
|
||||||
|
<li>Modal size and positioning</li>
|
||||||
|
<li>Social provider button colors</li>
|
||||||
|
<li>Divider lines and spacing</li>
|
||||||
|
<li>Error message styling</li>
|
||||||
|
<li>Responsive layout behavior</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Test 5: Custom Configuration -->
|
||||||
|
<div class="test-section">
|
||||||
|
<h2 class="text-xl font-semibold mb-4">Test 5: Cotton Component Configuration</h2>
|
||||||
|
<div class="test-description">Testing Cotton c-vars Configuration Options</div>
|
||||||
|
|
||||||
|
<div class="modal-test-container">
|
||||||
|
<div class="modal-test-group" data-label="Custom Configuration Test">
|
||||||
|
<button class="test-button" onclick="testCustomConfiguration()">
|
||||||
|
Test Custom Cotton Config
|
||||||
|
</button>
|
||||||
|
<div class="feature-list">
|
||||||
|
Cotton c-vars to test:
|
||||||
|
<ul class="list-disc list-inside mt-2 text-sm">
|
||||||
|
<li>Custom login/register titles</li>
|
||||||
|
<li>Custom subtitle text</li>
|
||||||
|
<li>Custom divider text</li>
|
||||||
|
<li>Show/hide social providers</li>
|
||||||
|
<li>Show/hide forgot password</li>
|
||||||
|
<li>Custom CSS classes</li>
|
||||||
|
<li>Custom forgot password URL</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Status Checklist -->
|
||||||
|
<div class="test-section">
|
||||||
|
<h2 class="text-xl font-semibold mb-4">Test Results Checklist</h2>
|
||||||
|
<div class="feature-list">
|
||||||
|
<div class="space-y-2">
|
||||||
|
<div><span class="status-indicator status-pending"></span>Modal opens and closes identically</div>
|
||||||
|
<div><span class="status-indicator status-pending"></span>Form switching works the same</div>
|
||||||
|
<div><span class="status-indicator status-pending"></span>Alpine.js directives function identically</div>
|
||||||
|
<div><span class="status-indicator status-pending"></span>Visual styling is identical</div>
|
||||||
|
<div><span class="status-indicator status-pending"></span>Password toggle works the same</div>
|
||||||
|
<div><span class="status-indicator status-pending"></span>Error states display identically</div>
|
||||||
|
<div><span class="status-indicator status-pending"></span>Social provider buttons render the same</div>
|
||||||
|
<div><span class="status-indicator status-pending"></span>Keyboard shortcuts work (ESC key)</div>
|
||||||
|
<div><span class="status-indicator status-pending"></span>Form validation behaves identically</div>
|
||||||
|
<div><span class="status-indicator status-pending"></span>Cotton c-vars configuration works</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Include Original Auth Modal -->
|
||||||
|
<div x-data="{ originalModalId: 'original' }">
|
||||||
|
{% include 'components/auth/auth-modal.html' %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Include Cotton Auth Modal -->
|
||||||
|
<c-auth_modal window_key="authModalCotton" />
|
||||||
|
|
||||||
|
<!-- Custom Cotton Auth Modal with Configuration -->
|
||||||
|
<div id="custom-cotton-modal" style="display: none;">
|
||||||
|
<c-auth_modal
|
||||||
|
window_key="authModalCustom"
|
||||||
|
login_title="Custom Sign In"
|
||||||
|
register_title="Custom Registration"
|
||||||
|
login_subtitle="Welcome back! Please sign in to your account"
|
||||||
|
register_subtitle="Join our amazing community of theme park enthusiasts"
|
||||||
|
social_divider_text_login="Or use your credentials"
|
||||||
|
social_divider_text_register="Or create with email"
|
||||||
|
show_social_providers="true"
|
||||||
|
show_forgot_password="true"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Alpine.js -->
|
||||||
|
<script src="{% static 'js/alpine.min.js' %}" defer></script>
|
||||||
|
|
||||||
|
<!-- Auth Modal Alpine.js Component -->
|
||||||
|
<script>
|
||||||
|
document.addEventListener('alpine:init', () => {
|
||||||
|
Alpine.data('authModal', () => ({
|
||||||
|
open: false,
|
||||||
|
mode: 'login',
|
||||||
|
showPassword: false,
|
||||||
|
socialProviders: [
|
||||||
|
{ id: 'google', name: 'Google' },
|
||||||
|
{ id: 'discord', name: 'Discord' }
|
||||||
|
],
|
||||||
|
socialLoading: false,
|
||||||
|
loginLoading: false,
|
||||||
|
registerLoading: false,
|
||||||
|
loginError: '',
|
||||||
|
registerError: '',
|
||||||
|
loginForm: {
|
||||||
|
username: '',
|
||||||
|
password: ''
|
||||||
|
},
|
||||||
|
registerForm: {
|
||||||
|
first_name: '',
|
||||||
|
last_name: '',
|
||||||
|
email: '',
|
||||||
|
username: '',
|
||||||
|
password1: '',
|
||||||
|
password2: ''
|
||||||
|
},
|
||||||
|
|
||||||
|
close() {
|
||||||
|
this.open = false;
|
||||||
|
this.resetForms();
|
||||||
|
},
|
||||||
|
|
||||||
|
switchToLogin() {
|
||||||
|
this.mode = 'login';
|
||||||
|
this.showPassword = false;
|
||||||
|
this.registerError = '';
|
||||||
|
},
|
||||||
|
|
||||||
|
switchToRegister() {
|
||||||
|
this.mode = 'register';
|
||||||
|
this.showPassword = false;
|
||||||
|
this.loginError = '';
|
||||||
|
},
|
||||||
|
|
||||||
|
resetForms() {
|
||||||
|
this.loginForm = { username: '', password: '' };
|
||||||
|
this.registerForm = { first_name: '', last_name: '', email: '', username: '', password1: '', password2: '' };
|
||||||
|
this.loginError = '';
|
||||||
|
this.registerError = '';
|
||||||
|
this.showPassword = false;
|
||||||
|
},
|
||||||
|
|
||||||
|
handleLogin() {
|
||||||
|
this.loginLoading = true;
|
||||||
|
this.loginError = '';
|
||||||
|
|
||||||
|
// Simulate API call
|
||||||
|
setTimeout(() => {
|
||||||
|
this.loginLoading = false;
|
||||||
|
if (!this.loginForm.username || !this.loginForm.password) {
|
||||||
|
this.loginError = 'Please fill in all fields';
|
||||||
|
} else {
|
||||||
|
this.loginError = 'Demo: Login functionality would work here';
|
||||||
|
}
|
||||||
|
}, 1500);
|
||||||
|
},
|
||||||
|
|
||||||
|
handleRegister() {
|
||||||
|
this.registerLoading = true;
|
||||||
|
this.registerError = '';
|
||||||
|
|
||||||
|
// Simulate API call
|
||||||
|
setTimeout(() => {
|
||||||
|
this.registerLoading = false;
|
||||||
|
if (this.registerForm.password1 !== this.registerForm.password2) {
|
||||||
|
this.registerError = 'Passwords do not match';
|
||||||
|
} else if (!this.registerForm.email || !this.registerForm.username) {
|
||||||
|
this.registerError = 'Please fill in all required fields';
|
||||||
|
} else {
|
||||||
|
this.registerError = 'Demo: Registration functionality would work here';
|
||||||
|
}
|
||||||
|
}, 1500);
|
||||||
|
},
|
||||||
|
|
||||||
|
handleSocialLogin(providerId) {
|
||||||
|
this.socialLoading = true;
|
||||||
|
setTimeout(() => {
|
||||||
|
this.socialLoading = false;
|
||||||
|
alert(`Demo: ${providerId} login would work here`);
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
// Store references to both modal instances
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
// Wait for Alpine.js to initialize and modal instances to be created
|
||||||
|
setTimeout(() => {
|
||||||
|
// Both modals should now be available with their respective window keys
|
||||||
|
console.log('Auth Modal References:', {
|
||||||
|
original: window.authModalOriginal,
|
||||||
|
cotton: window.authModalCotton,
|
||||||
|
custom: window.authModalCustom
|
||||||
|
});
|
||||||
|
}, 500);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Test functions
|
||||||
|
function openOriginalModalInMode(mode) {
|
||||||
|
if (window.authModalOriginal) {
|
||||||
|
window.authModalOriginal.mode = mode;
|
||||||
|
window.authModalOriginal.open = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function openCottonModalInMode(mode) {
|
||||||
|
if (window.authModalCotton) {
|
||||||
|
window.authModalCotton.mode = mode;
|
||||||
|
window.authModalCotton.open = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function testOriginalInteractivity() {
|
||||||
|
if (window.authModalOriginal) {
|
||||||
|
window.authModalOriginal.open = true;
|
||||||
|
window.authModalOriginal.mode = 'login';
|
||||||
|
setTimeout(() => {
|
||||||
|
window.authModalOriginal.loginError = 'Test error message';
|
||||||
|
window.authModalOriginal.showPassword = true;
|
||||||
|
}, 500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function testCottonInteractivity() {
|
||||||
|
if (window.authModalCotton) {
|
||||||
|
window.authModalCotton.open = true;
|
||||||
|
window.authModalCotton.mode = 'login';
|
||||||
|
setTimeout(() => {
|
||||||
|
window.authModalCotton.loginError = 'Test error message';
|
||||||
|
window.authModalCotton.showPassword = true;
|
||||||
|
}, 500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function compareModalStyling() {
|
||||||
|
if (window.authModalOriginal && window.authModalCotton) {
|
||||||
|
window.authModalOriginal.open = true;
|
||||||
|
setTimeout(() => {
|
||||||
|
window.authModalCotton.open = true;
|
||||||
|
}, 200);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function testCustomConfiguration() {
|
||||||
|
// Show the custom cotton modal
|
||||||
|
const customModal = document.getElementById('custom-cotton-modal');
|
||||||
|
customModal.style.display = 'block';
|
||||||
|
|
||||||
|
// You would implement custom Alpine.js instance here
|
||||||
|
alert('Custom configuration test - check the modal titles and text changes');
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -101,8 +101,9 @@ urlpatterns = [
|
|||||||
views.environment_and_settings_view,
|
views.environment_and_settings_view,
|
||||||
name="environment_and_settings",
|
name="environment_and_settings",
|
||||||
),
|
),
|
||||||
# Button component testing
|
# Component testing
|
||||||
path("test-button/", views.test_button_comparison, name="test_button_comparison"),
|
path("test-button/", views.test_button_comparison, name="test_button_comparison"),
|
||||||
|
path("test-auth-modal/", views.test_auth_modal_comparison, name="test_auth_modal_comparison"),
|
||||||
]
|
]
|
||||||
|
|
||||||
# Add autocomplete URLs if available
|
# Add autocomplete URLs if available
|
||||||
|
|||||||
@@ -164,3 +164,11 @@ def test_button_comparison(request):
|
|||||||
Renders a comprehensive test page with all button variants and combinations.
|
Renders a comprehensive test page with all button variants and combinations.
|
||||||
"""
|
"""
|
||||||
return render(request, "test_button_comparison.html")
|
return render(request, "test_button_comparison.html")
|
||||||
|
|
||||||
|
|
||||||
|
def test_auth_modal_comparison(request):
|
||||||
|
"""
|
||||||
|
Test view to compare cotton auth modal component with original include version.
|
||||||
|
Renders a comprehensive test page to verify Alpine.js functionality, styling, and behavior parity.
|
||||||
|
"""
|
||||||
|
return render(request, "test_auth_modal_comparison.html")
|
||||||
|
|||||||
Reference in New Issue
Block a user