mirror of
https://github.com/pacnpal/thrillwiki_django_no_react.git
synced 2025-12-20 07:51:09 -05:00
Integrate Django Cotton for modular frontend components
Integrates django-cotton into the project by adding it to INSTALLED_APPS and pyproject.toml, and refactors base.html to use cotton components for the auth modal and toast container, creating new component files for these elements. Replit-Commit-Author: Agent Replit-Commit-Session-Id: eff39de1-3afa-446d-a965-acaf61837fc7 Replit-Commit-Checkpoint-Type: intermediate_checkpoint Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/d6d61dac-164d-45dd-929f-7dcdfd771b64/eff39de1-3afa-446d-a965-acaf61837fc7/jGCMVeA
This commit is contained in:
6
.replit
6
.replit
@@ -1,4 +1,4 @@
|
|||||||
modules = ["bash", "web", "nodejs-20", "python-3.13"]
|
modules = ["bash", "web", "nodejs-20", "python-3.13", "python3"]
|
||||||
|
|
||||||
[nix]
|
[nix]
|
||||||
channel = "stable-25_05"
|
channel = "stable-25_05"
|
||||||
@@ -39,6 +39,10 @@ externalPort = 80
|
|||||||
localPort = 34277
|
localPort = 34277
|
||||||
externalPort = 3000
|
externalPort = 3000
|
||||||
|
|
||||||
|
[[ports]]
|
||||||
|
localPort = 41067
|
||||||
|
externalPort = 3001
|
||||||
|
|
||||||
[deployment]
|
[deployment]
|
||||||
deploymentTarget = "autoscale"
|
deploymentTarget = "autoscale"
|
||||||
run = ["gunicorn", "--bind=0.0.0.0:5000", "--reuse-port", "thrillwiki.wsgi:application"]
|
run = ["gunicorn", "--bind=0.0.0.0:5000", "--reuse-port", "thrillwiki.wsgi:application"]
|
||||||
|
|||||||
@@ -96,6 +96,7 @@ THIRD_PARTY_APPS = [
|
|||||||
"django_celery_beat", # Celery beat scheduler
|
"django_celery_beat", # Celery beat scheduler
|
||||||
"django_celery_results", # Celery result backend
|
"django_celery_results", # Celery result backend
|
||||||
"django_extensions", # Django Extensions for enhanced development tools
|
"django_extensions", # Django Extensions for enhanced development tools
|
||||||
|
"django_cotton", # Django Cotton for component-based templates
|
||||||
]
|
]
|
||||||
|
|
||||||
LOCAL_APPS = [
|
LOCAL_APPS = [
|
||||||
|
|||||||
@@ -62,6 +62,7 @@ dependencies = [
|
|||||||
"djangorestframework-simplejwt>=5.5.1",
|
"djangorestframework-simplejwt>=5.5.1",
|
||||||
"django-forwardemail>=1.0.0",
|
"django-forwardemail>=1.0.0",
|
||||||
"django-cloudflareimages-toolkit>=1.0.6",
|
"django-cloudflareimages-toolkit>=1.0.6",
|
||||||
|
"django-cotton>=2.1.3",
|
||||||
]
|
]
|
||||||
|
|
||||||
[dependency-groups]
|
[dependency-groups]
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
{% load static %}
|
{% load static %}
|
||||||
|
{% load cotton %}
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
@@ -127,11 +128,11 @@
|
|||||||
</div>
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
|
|
||||||
<!-- Global Auth Modal -->
|
<!-- Global Auth Modal (Cotton Component) -->
|
||||||
{% include 'components/auth/auth-modal.html' %}
|
<c-auth.modal />
|
||||||
|
|
||||||
<!-- Global Toast Container -->
|
<!-- Global Toast Container (Cotton Component) -->
|
||||||
{% include 'components/ui/toast-container.html' %}
|
<c-ui.toast />
|
||||||
|
|
||||||
<!-- Custom JavaScript with cache control -->
|
<!-- Custom JavaScript with cache control -->
|
||||||
<script src="{% static 'js/main.js' %}?v={{ version|default:'1.0' }}"></script>
|
<script src="{% static 'js/main.js' %}?v={{ version|default:'1.0' }}"></script>
|
||||||
|
|||||||
377
backend/templates/cotton/auth/modal.html
Normal file
377
backend/templates/cotton/auth/modal.html
Normal file
@@ -0,0 +1,377 @@
|
|||||||
|
{% comment %}
|
||||||
|
Cotton Auth Modal Component
|
||||||
|
Converts the existing auth modal to use Django Cotton's component system
|
||||||
|
{% endcomment %}
|
||||||
|
|
||||||
|
{% load static %}
|
||||||
|
{% load i18n %}
|
||||||
|
{% load account socialaccount %}
|
||||||
|
|
||||||
|
<!-- Cotton Auth Modal Component -->
|
||||||
|
<c-vars
|
||||||
|
modal_classes="{{ modal_classes|default:'fixed inset-0 z-50 flex items-center justify-center' }}"
|
||||||
|
overlay_classes="{{ overlay_classes|default:'fixed inset-0 bg-background/80 backdrop-blur-sm' }}"
|
||||||
|
content_classes="{{ content_classes|default:'relative w-full max-w-md mx-4 bg-background border rounded-lg shadow-lg' }}"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div
|
||||||
|
x-data="authModal"
|
||||||
|
x-show="open"
|
||||||
|
x-cloak
|
||||||
|
x-init="window.authModal = $data"
|
||||||
|
class="{{ 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="{{ 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="{{ 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"
|
||||||
|
>
|
||||||
|
<i class="fas fa-times w-4 h-4"></i>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<!-- Login Form -->
|
||||||
|
<c-slot name="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">
|
||||||
|
Sign In
|
||||||
|
</h2>
|
||||||
|
<p class="text-sm text-muted-foreground mt-2">
|
||||||
|
Enter your credentials to access your account
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Social Login Buttons -->
|
||||||
|
<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">
|
||||||
|
Or continue with
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Login Form -->
|
||||||
|
<form
|
||||||
|
@submit.prevent="handleLogin()"
|
||||||
|
class="space-y-4"
|
||||||
|
>
|
||||||
|
<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>
|
||||||
|
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<a
|
||||||
|
href="{% url 'account_reset_password' %}"
|
||||||
|
class="text-sm text-primary hover:text-primary/80 underline-offset-4 hover:underline font-medium"
|
||||||
|
>
|
||||||
|
Forgot password?
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 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">Sign In</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>
|
||||||
|
</c-slot>
|
||||||
|
|
||||||
|
<!-- Register Form -->
|
||||||
|
<c-slot name="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">
|
||||||
|
Create Account
|
||||||
|
</h2>
|
||||||
|
<p class="text-sm text-muted-foreground mt-2">
|
||||||
|
Join ThrillWiki to start exploring theme parks
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Social Registration Buttons -->
|
||||||
|
<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">
|
||||||
|
Or continue with email
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Register Form -->
|
||||||
|
<form
|
||||||
|
@submit.prevent="handleRegister()"
|
||||||
|
class="space-y-4"
|
||||||
|
>
|
||||||
|
<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">Create Account</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>
|
||||||
|
</c-slot>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
76
backend/templates/cotton/ui/toast.html
Normal file
76
backend/templates/cotton/ui/toast.html
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
{% comment %}
|
||||||
|
Cotton Toast Container Component
|
||||||
|
Converts the existing toast container to use Django Cotton's component system
|
||||||
|
{% endcomment %}
|
||||||
|
|
||||||
|
{% load static %}
|
||||||
|
|
||||||
|
<!-- Cotton Toast Container Component -->
|
||||||
|
<c-vars
|
||||||
|
container_classes="{{ container_classes|default:'fixed top-4 right-4 z-50 space-y-2 max-w-sm' }}"
|
||||||
|
toast_classes="{{ toast_classes|default:'p-4 rounded-lg shadow-lg border backdrop-blur-sm transition-all duration-300 flex items-start gap-3' }}"
|
||||||
|
icon_classes="{{ icon_classes|default:'w-5 h-5 flex-shrink-0 mt-0.5' }}"
|
||||||
|
content_classes="{{ content_classes|default:'flex-grow min-w-0' }}"
|
||||||
|
close_classes="{{ close_classes|default:'p-1 hover:bg-black/10 rounded transition-colors flex-shrink-0' }}"
|
||||||
|
progress_classes="{{ progress_classes|default:'absolute bottom-0 left-0 h-1 bg-current opacity-30 transition-all duration-100 ease-linear' }}"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div
|
||||||
|
x-data="{}"
|
||||||
|
x-show="$store.toast.toasts.length > 0"
|
||||||
|
x-cloak
|
||||||
|
class="{{ container_classes }}"
|
||||||
|
>
|
||||||
|
<template x-for="toast in $store.toast.toasts" :key="toast.id">
|
||||||
|
<div
|
||||||
|
x-show="toast.visible"
|
||||||
|
x-transition:enter="transition ease-out duration-300"
|
||||||
|
x-transition:enter-start="transform opacity-0 scale-95 translate-x-full"
|
||||||
|
x-transition:enter-end="transform opacity-100 scale-100 translate-x-0"
|
||||||
|
x-transition:leave="transition ease-in duration-200"
|
||||||
|
x-transition:leave-start="transform opacity-100 scale-100 translate-x-0"
|
||||||
|
x-transition:leave-end="transform opacity-0 scale-95 translate-x-full"
|
||||||
|
class="{{ toast_classes }}"
|
||||||
|
:class="{
|
||||||
|
'bg-green-50 border-green-200 text-green-800': toast.type === 'success',
|
||||||
|
'bg-red-50 border-red-200 text-red-800': toast.type === 'error',
|
||||||
|
'bg-blue-50 border-blue-200 text-blue-800': toast.type === 'info',
|
||||||
|
'bg-yellow-50 border-yellow-200 text-yellow-800': toast.type === 'warning'
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<!-- Toast Icon -->
|
||||||
|
<div class="{{ icon_classes }}">
|
||||||
|
<i
|
||||||
|
:class="{
|
||||||
|
'fas fa-check-circle text-green-600': toast.type === 'success',
|
||||||
|
'fas fa-exclamation-circle text-red-600': toast.type === 'error',
|
||||||
|
'fas fa-info-circle text-blue-600': toast.type === 'info',
|
||||||
|
'fas fa-exclamation-triangle text-yellow-600': toast.type === 'warning'
|
||||||
|
}"
|
||||||
|
></i>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Toast Content -->
|
||||||
|
<div class="{{ content_classes }}">
|
||||||
|
<p class="font-medium text-sm" x-text="toast.message"></p>
|
||||||
|
<p x-show="toast.description" class="text-xs opacity-90 mt-1" x-text="toast.description"></p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Close Button -->
|
||||||
|
<button
|
||||||
|
@click="$store.toast.remove(toast.id)"
|
||||||
|
class="{{ close_classes }}"
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<i class="fas fa-times w-4 h-4"></i>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<!-- Progress Bar (if duration is set) -->
|
||||||
|
<div
|
||||||
|
x-show="toast.duration"
|
||||||
|
class="{{ progress_classes }}"
|
||||||
|
:style="`width: ${toast.progress}%`"
|
||||||
|
></div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
16
backend/uv.lock
generated
16
backend/uv.lock
generated
@@ -1,5 +1,5 @@
|
|||||||
version = 1
|
version = 1
|
||||||
revision = 3
|
revision = 2
|
||||||
requires-python = ">=3.13"
|
requires-python = ">=3.13"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -642,6 +642,18 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/30/d8/19ed1e47badf477d17fb177c1c19b5a21da0fd2d9f093f23be3fb86c5fab/django_cors_headers-4.9.0-py3-none-any.whl", hash = "sha256:15c7f20727f90044dcee2216a9fd7303741a864865f0c3657e28b7056f61b449", size = 12809, upload-time = "2025-09-18T10:40:50.843Z" },
|
{ url = "https://files.pythonhosted.org/packages/30/d8/19ed1e47badf477d17fb177c1c19b5a21da0fd2d9f093f23be3fb86c5fab/django_cors_headers-4.9.0-py3-none-any.whl", hash = "sha256:15c7f20727f90044dcee2216a9fd7303741a864865f0c3657e28b7056f61b449", size = 12809, upload-time = "2025-09-18T10:40:50.843Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "django-cotton"
|
||||||
|
version = "2.1.3"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "django" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/c3/99/36e318ebd1ace3fc874541a02e7e4149def8e21aab85aceb7bb01e17607b/django_cotton-2.1.3.tar.gz", hash = "sha256:737f9c088549d7febbf78532856ddf1270799675a4bc9fa191a5db0e195a9c13", size = 23432, upload-time = "2025-06-30T17:31:29.29Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ad/ec/5e5318af0304962be43e3b912aef024d8ac08c0f9a9dfcc4f0cd55d0e74e/django_cotton-2.1.3-py3-none-any.whl", hash = "sha256:f33658d05a8f5ecf7448bdf1089e2ad27d2ce42e59c752216129701d7d153c89", size = 22214, upload-time = "2025-06-30T17:31:28.093Z" },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "django-debug-toolbar"
|
name = "django-debug-toolbar"
|
||||||
version = "6.0.0"
|
version = "6.0.0"
|
||||||
@@ -2238,6 +2250,7 @@ dependencies = [
|
|||||||
{ name = "django-cleanup" },
|
{ name = "django-cleanup" },
|
||||||
{ name = "django-cloudflareimages-toolkit" },
|
{ name = "django-cloudflareimages-toolkit" },
|
||||||
{ name = "django-cors-headers" },
|
{ name = "django-cors-headers" },
|
||||||
|
{ name = "django-cotton" },
|
||||||
{ name = "django-debug-toolbar" },
|
{ name = "django-debug-toolbar" },
|
||||||
{ name = "django-environ" },
|
{ name = "django-environ" },
|
||||||
{ name = "django-extensions" },
|
{ name = "django-extensions" },
|
||||||
@@ -2309,6 +2322,7 @@ requires-dist = [
|
|||||||
{ name = "django-cleanup", specifier = ">=8.0.0" },
|
{ name = "django-cleanup", specifier = ">=8.0.0" },
|
||||||
{ name = "django-cloudflareimages-toolkit", specifier = ">=1.0.6" },
|
{ name = "django-cloudflareimages-toolkit", specifier = ">=1.0.6" },
|
||||||
{ name = "django-cors-headers", specifier = ">=4.3.1" },
|
{ name = "django-cors-headers", specifier = ">=4.3.1" },
|
||||||
|
{ name = "django-cotton", specifier = ">=2.1.3" },
|
||||||
{ name = "django-debug-toolbar", specifier = ">=4.0.0" },
|
{ name = "django-debug-toolbar", specifier = ">=4.0.0" },
|
||||||
{ name = "django-environ", specifier = ">=0.12.0" },
|
{ name = "django-environ", specifier = ">=0.12.0" },
|
||||||
{ name = "django-extensions", specifier = ">=4.1" },
|
{ name = "django-extensions", specifier = ">=4.1" },
|
||||||
|
|||||||
Reference in New Issue
Block a user