- Display all 5 backend password validation requirements - Added: minimum 8 characters (existing) - Added: maximum 100 characters - Added: at least one lowercase letter - Added: at least one uppercase letter - Added: at least one number - Each requirement shows real-time validation with green checkmark - Requirements match exactly with backend/src/utils/validators.js feat: add Traefik labels to frontend container - Enable Traefik auto-discovery for frontend service - Configure HTTP routing with Host rule - Setup both web (HTTP) and websecure (HTTPS) entrypoints - Configure load balancer backend port (80) - Include optional TLS/Let's Encrypt configuration - Change edh.example.com to your actual domain feat: configure production compose file to use Traefik network - Generated docker-compose.prod.deployed.yml now uses traefik-network - Frontend service joined to both edh-stats-network and traefik-network - Added Traefik labels for routing and TLS configuration - traefik-network configured as external (must be created by Traefik) - Removed external ports from frontend (Traefik handles routing) - Updated setup instructions to mention Traefik prerequisites - Changed TLS to enabled by default in production refactor: remove frontend port exposure and Traefik labels from dev compose - Removed port 8081 exposure from development docker-compose.yml - Removed Traefik labels from development environment - Development compose now simple and focused on local testing - Production compose (via deploy.sh) handles Traefik routing via DNS - Frontend only accessible through backend API in development - Cleaner separation: dev is simple, prod uses Traefik Switch Postgres data to host path and bump version Update Traefik rule, expose port, bump version feat: add database migration script for PostgreSQL - Created migrate-database.sh script for exporting/importing databases - Supports source and target database configuration via CLI options - Validates connections before migration - Verifies data integrity after import (table and row counts) - Can skip import and just export to file for manual restore - Supports Docker Compose container names - Includes comprehensive error handling and user prompts - Added DATABASE_MIGRATION.md documentation with usage examples - Handles common scenarios: dev→prod, backups, restore - Security considerations for password handling refactor: simplify migration script to run directly in PostgreSQL container - Removed network/remote host configuration (no longer needed) - Script now runs inside PostgreSQL container with docker compose exec - Simplified to use only source-db, target-db, output-file, skip-import options - No external dependencies - uses container's pg_dump and psql - Much simpler usage: docker compose exec postgres /scripts/migrate-database.sh - Updated documentation with container-based examples - Added real-world integration examples (daily backups, deployments, recovery) - Includes troubleshooting and access patterns for backup files feat: mount scripts directory into PostgreSQL container - Added ./scripts:/scripts:ro volume mount to postgres service - Makes migrate-database.sh and other scripts accessible inside container - Read-only mount for security (scripts can't be modified inside container) - Allows running: docker compose exec postgres /scripts/migrate-database.sh - Scripts are shared between host and container for easy access docs: clarify that migration script must run as postgres user - Added -u postgres flag to all docker compose exec commands - Explains why postgres user is required (PostgreSQL role authentication) - Created shell alias for convenience - Updated all scenarios and examples - Updated troubleshooting section - Clarifies connection issues related to user authentication feat: save database backups to host by default - Added ./backups volume mount to postgres container - Changed default backup location from /tmp to /backups - /backups is mounted to ./backups on host for easy access - Script automatically creates /backups directory if needed - Updated help and examples with -u postgres flag - Summary now shows both container and host backup paths - Backups are immediately available on host machine - No need for docker cp to retrieve backups feat: add --skip-export flag for import-only database operations - Allows importing from existing backup files without re-exporting - Added validate_backup_file() function to check backup existence - Updated main() to handle import-only mode with proper validation - Updated summary output to show import-only mode details - Updated help text with import-only example - Prevents using both --skip-import and --skip-export together docs: update database migration guide for import-only mode - Document new --skip-export flag for import-only operations - Add example for quick restore from backup without re-export - Update command options table with mode combinations - Update all scenarios and examples to use /backups mount - Clarify file location and volume mounting (./backups on host) - Add Scenario 5 for quick restore from backup - Simplify examples and fix container paths feat: clear existing data before importing in migration script - Added clear_database() function to drop all tables, views, and sequences - Drops and recreates public schema with proper permissions - Ensures clean import without data conflicts - Updated warning message to clarify data deletion - clear_database() called before import starts - Maintains database integrity and grants docs: update migration guide to explain data clearing on import - Clarify that existing data is deleted before import - Explain the drop/recreate schema process - Add notes to scenarios about data clearing - Document the import process sequence - Update version to 2.2 fix: remove verbose flag from pg_dump to prevent SQL syntax errors - Removed -v flag from pg_dump export command - Verbose output was being included in SQL file as comments - These comments caused 'syntax error at or near pg_dump' errors during import - Backup files will now be clean SQL without pg_dump metadata comments docs: document pg_dump verbose output fix and troubleshooting - Added troubleshooting section for pg_dump syntax errors - Noted that v2.3 fixes this issue - Directed users to create new backups if needed - Updated version to 2.3 - Clarified file location is /backups/ docs: add critical warning about using migration script for imports - Added prominent warning against using psql directly with -f flag - Explained why direct psql causes 'relation already exists' errors - Added troubleshooting section for these errors - Emphasized that script handles data clearing automatically - Clear examples of wrong vs right approach fix: remove pg_dump restrict commands that block data import - Added clean_backup_file() function to remove \restrict and \unrestrict - pg_dump adds these security commands which prevent data loading - Script now automatically cleans backup files before importing - Removes lines starting with \restrict or \unrestrict - Ensures all data (users, games, commanders) imports successfully - Called automatically during import process docs: add troubleshooting for pg_dump restrict commands blocking imports - Document the \restrict and \unrestrict security commands issue - Explain why they block data from being imported - Show that migration script v2.4+ removes them automatically - Update version to 2.4 - Add detailed troubleshooting section for empty imports
558 lines
20 KiB
HTML
558 lines
20 KiB
HTML
<!doctype html>
|
|
<html lang="en" class="h-full bg-gray-50">
|
|
<head>
|
|
<meta charset="UTF-8" />
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
<title>Register - EDH Stats Tracker</title>
|
|
<meta
|
|
name="description"
|
|
content="Create a new account to track your Magic: The Gathering EDH/Commander games"
|
|
/>
|
|
<script src="https://cdn.tailwindcss.com"></script>
|
|
<link rel="stylesheet" href="/css/styles.css" />
|
|
</head>
|
|
<body class="min-h-full flex flex-col py-12 px-4 sm:px-6 lg:px-8">
|
|
<div class="flex items-center justify-center flex-1">
|
|
<div
|
|
class="max-w-md w-full space-y-8"
|
|
x-data="registerForm()"
|
|
x-init="init()"
|
|
>
|
|
<!-- Header -->
|
|
<div class="text-center">
|
|
<h1 class="text-4xl font-bold font-mtg text-edh-primary mb-2">
|
|
EDH Stats
|
|
</h1>
|
|
<h2
|
|
class="text-xl text-gray-600"
|
|
x-text="allowRegistration ? 'Create your account' : 'Registration Disabled'"
|
|
></h2>
|
|
</div>
|
|
|
|
<!-- Registration Disabled Message -->
|
|
<div
|
|
x-show="!allowRegistration"
|
|
x-transition
|
|
class="card bg-yellow-50 border-yellow-200"
|
|
>
|
|
<div class="flex">
|
|
<div class="flex-shrink-0">
|
|
<svg
|
|
class="h-5 w-5 text-yellow-400"
|
|
viewBox="0 0 20 20"
|
|
fill="currentColor"
|
|
>
|
|
<path
|
|
fill-rule="evenodd"
|
|
d="M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z"
|
|
clip-rule="evenodd"
|
|
/>
|
|
</svg>
|
|
</div>
|
|
<div class="ml-3">
|
|
<p class="text-sm text-yellow-800">
|
|
User registration is currently disabled. Please contact an
|
|
administrator if you need to create an account.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
<div class="mt-6 text-center">
|
|
<p class="text-sm text-gray-600">
|
|
If you already have an account,
|
|
<a
|
|
href="/login.html"
|
|
class="font-medium text-edh-accent hover:text-edh-primary"
|
|
>
|
|
sign in here
|
|
</a>
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Register Form -->
|
|
<div class="card" x-show="allowRegistration">
|
|
<form class="space-y-6" @submit.prevent="handleRegister">
|
|
<!-- Username Field -->
|
|
<div>
|
|
<label for="username" class="form-label">Username</label>
|
|
<div class="relative">
|
|
<input
|
|
id="username"
|
|
name="username"
|
|
type="text"
|
|
required
|
|
x-model="formData.username"
|
|
@input="validateUsername()"
|
|
:class="errors.username ? 'border-red-500 focus:ring-red-500' : ''"
|
|
class="form-input pl-10"
|
|
placeholder="Choose a username (3+ characters)"
|
|
/>
|
|
<div
|
|
class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none"
|
|
>
|
|
<svg
|
|
class="h-5 w-5 text-gray-400"
|
|
fill="none"
|
|
stroke="currentColor"
|
|
viewBox="0 0 24 24"
|
|
>
|
|
<path
|
|
stroke-linecap="round"
|
|
stroke-linejoin="round"
|
|
stroke-width="2"
|
|
d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"
|
|
></path>
|
|
</svg>
|
|
</div>
|
|
</div>
|
|
<p
|
|
x-show="errors.username"
|
|
x-text="errors.username"
|
|
class="form-error"
|
|
></p>
|
|
</div>
|
|
|
|
<!-- Email Field -->
|
|
<div>
|
|
<label for="email" class="form-label"
|
|
>Email Address (Optional)</label
|
|
>
|
|
<div class="relative">
|
|
<input
|
|
id="email"
|
|
name="email"
|
|
type="email"
|
|
x-model="formData.email"
|
|
@input="validateEmail()"
|
|
:class="errors.email ? 'border-red-500 focus:ring-red-500' : ''"
|
|
class="form-input pl-10"
|
|
placeholder="Enter your email"
|
|
/>
|
|
<div
|
|
class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none"
|
|
>
|
|
<svg
|
|
class="h-5 w-5 text-gray-400"
|
|
fill="none"
|
|
stroke="currentColor"
|
|
viewBox="0 0 24 24"
|
|
>
|
|
<path
|
|
stroke-linecap="round"
|
|
stroke-linejoin="round"
|
|
stroke-width="2"
|
|
d="M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"
|
|
></path>
|
|
</svg>
|
|
</div>
|
|
</div>
|
|
<p
|
|
x-show="errors.email"
|
|
x-text="errors.email"
|
|
class="form-error"
|
|
></p>
|
|
</div>
|
|
|
|
<!-- Password Field -->
|
|
<div>
|
|
<label for="password" class="form-label">Password</label>
|
|
<div class="relative">
|
|
<input
|
|
id="password"
|
|
name="password"
|
|
:type="showPassword ? 'text' : 'password'"
|
|
required
|
|
x-model="formData.password"
|
|
@input="validatePassword()"
|
|
:class="errors.password ? 'border-red-500 focus:ring-red-500' : ''"
|
|
class="form-input pl-10 pr-10"
|
|
placeholder="Minimum 8 characters"
|
|
/>
|
|
<div
|
|
class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none"
|
|
>
|
|
<svg
|
|
class="h-5 w-5 text-gray-400"
|
|
fill="none"
|
|
stroke="currentColor"
|
|
viewBox="0 0 24 24"
|
|
>
|
|
<path
|
|
stroke-linecap="round"
|
|
stroke-linejoin="round"
|
|
stroke-width="2"
|
|
d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z"
|
|
></path>
|
|
</svg>
|
|
</div>
|
|
<button
|
|
type="button"
|
|
@click="showPassword = !showPassword"
|
|
class="absolute inset-y-0 right-0 pr-3 flex items-center"
|
|
>
|
|
<svg
|
|
x-show="!showPassword"
|
|
class="h-5 w-5 text-gray-400 hover:text-gray-600"
|
|
fill="none"
|
|
stroke="currentColor"
|
|
viewBox="0 0 24 24"
|
|
>
|
|
<path
|
|
stroke-linecap="round"
|
|
stroke-linejoin="round"
|
|
stroke-width="2"
|
|
d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"
|
|
></path>
|
|
<path
|
|
stroke-linecap="round"
|
|
stroke-linejoin="round"
|
|
stroke-width="2"
|
|
d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"
|
|
></path>
|
|
</svg>
|
|
<svg
|
|
x-show="showPassword"
|
|
class="h-5 w-5 text-gray-400 hover:text-gray-600"
|
|
fill="none"
|
|
stroke="currentColor"
|
|
viewBox="0 0 24 24"
|
|
>
|
|
<path
|
|
stroke-linecap="round"
|
|
stroke-linejoin="round"
|
|
stroke-width="2"
|
|
d="M13.875 18.825A10.05 10.05 0 0112 19c-4.478 0-8.268-2.943-9.543-7a9.97 9.97 0 011.563-3.029m5.858.908a3 3 0 114.243 4.243M9.878 9.878l4.242 4.242M9.88 9.88l-3.29-3.29m7.532 7.532l3.29 3.29M3 3l3.59 3.59m0 0A9.953 9.953 0 0112 5c4.478 0 8.268 2.943 9.543 7a10.025 10.025 0 01-4.132 5.411m0 0L21 21"
|
|
></path>
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
<p
|
|
x-show="errors.password"
|
|
x-text="errors.password"
|
|
class="form-error"
|
|
></p>
|
|
</div>
|
|
|
|
<!-- Confirm Password Field -->
|
|
<div>
|
|
<label for="confirmPassword" class="form-label"
|
|
>Confirm Password</label
|
|
>
|
|
<div class="relative">
|
|
<input
|
|
id="confirmPassword"
|
|
name="confirmPassword"
|
|
:type="showConfirmPassword ? 'text' : 'password'"
|
|
required
|
|
x-model="formData.confirmPassword"
|
|
@input="validateConfirmPassword()"
|
|
:class="errors.confirmPassword ? 'border-red-500 focus:ring-red-500' : ''"
|
|
class="form-input pl-10 pr-10"
|
|
placeholder="Re-enter your password"
|
|
/>
|
|
<div
|
|
class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none"
|
|
>
|
|
<svg
|
|
class="h-5 w-5 text-gray-400"
|
|
fill="none"
|
|
stroke="currentColor"
|
|
viewBox="0 0 24 24"
|
|
>
|
|
<path
|
|
stroke-linecap="round"
|
|
stroke-linejoin="round"
|
|
stroke-width="2"
|
|
d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z"
|
|
></path>
|
|
</svg>
|
|
</div>
|
|
<button
|
|
type="button"
|
|
@click="showConfirmPassword = !showConfirmPassword"
|
|
class="absolute inset-y-0 right-0 pr-3 flex items-center"
|
|
>
|
|
<svg
|
|
x-show="!showConfirmPassword"
|
|
class="h-5 w-5 text-gray-400 hover:text-gray-600"
|
|
fill="none"
|
|
stroke="currentColor"
|
|
viewBox="0 0 24 24"
|
|
>
|
|
<path
|
|
stroke-linecap="round"
|
|
stroke-linejoin="round"
|
|
stroke-width="2"
|
|
d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"
|
|
></path>
|
|
<path
|
|
stroke-linecap="round"
|
|
stroke-linejoin="round"
|
|
stroke-width="2"
|
|
d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"
|
|
></path>
|
|
</svg>
|
|
<svg
|
|
x-show="showConfirmPassword"
|
|
class="h-5 w-5 text-gray-400 hover:text-gray-600"
|
|
fill="none"
|
|
stroke="currentColor"
|
|
viewBox="0 0 24 24"
|
|
>
|
|
<path
|
|
stroke-linecap="round"
|
|
stroke-linejoin="round"
|
|
stroke-width="2"
|
|
d="M13.875 18.825A10.05 10.05 0 0112 19c-4.478 0-8.268-2.943-9.543-7a9.97 9.97 0 011.563-3.029m5.858.908a3 3 0 114.243 4.243M9.878 9.878l4.242 4.242M9.88 9.88l-3.29-3.29m7.532 7.532l3.29 3.29M3 3l3.59 3.59m0 0A9.953 9.953 0 0112 5c4.478 0 8.268 2.943 9.543 7a10.025 10.025 0 01-4.132 5.411m0 0L21 21"
|
|
></path>
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
<p
|
|
x-show="errors.confirmPassword"
|
|
x-text="errors.confirmPassword"
|
|
class="form-error"
|
|
></p>
|
|
</div>
|
|
|
|
<!-- Terms & Conditions -->
|
|
<div class="flex items-center">
|
|
<input
|
|
id="terms"
|
|
name="terms"
|
|
type="checkbox"
|
|
x-model="formData.terms"
|
|
@change="validateTerms()"
|
|
:class="errors.terms ? 'border-red-500' : ''"
|
|
class="h-4 w-4 text-edh-accent focus:ring-edh-accent border-gray-300 rounded"
|
|
/>
|
|
<label for="terms" class="ml-2 block text-sm text-gray-900">
|
|
I agree to the
|
|
<a href="#" class="text-edh-accent hover:text-edh-primary"
|
|
>Terms of Service</a
|
|
>
|
|
</label>
|
|
</div>
|
|
<p
|
|
x-show="errors.terms"
|
|
x-text="errors.terms"
|
|
class="form-error"
|
|
></p>
|
|
|
|
<!-- Error Message -->
|
|
<div
|
|
x-show="serverError"
|
|
x-transition
|
|
class="rounded-md bg-red-50 p-4"
|
|
>
|
|
<div class="flex">
|
|
<div class="flex-shrink-0">
|
|
<svg
|
|
class="h-5 w-5 text-red-400"
|
|
viewBox="0 0 20 20"
|
|
fill="currentColor"
|
|
>
|
|
<path
|
|
fill-rule="evenodd"
|
|
d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z"
|
|
clip-rule="evenodd"
|
|
/>
|
|
</svg>
|
|
</div>
|
|
<div class="ml-3">
|
|
<p class="text-sm text-red-800" x-text="serverError"></p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Success Message -->
|
|
<div
|
|
x-show="successMessage"
|
|
x-transition
|
|
class="rounded-md bg-green-50 p-4"
|
|
>
|
|
<div class="flex">
|
|
<div class="flex-shrink-0">
|
|
<svg
|
|
class="h-5 w-5 text-green-400"
|
|
viewBox="0 0 20 20"
|
|
fill="currentColor"
|
|
>
|
|
<path
|
|
fill-rule="evenodd"
|
|
d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z"
|
|
clip-rule="evenodd"
|
|
/>
|
|
</svg>
|
|
</div>
|
|
<div class="ml-3">
|
|
<p class="text-sm text-green-800" x-text="successMessage"></p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Submit Button -->
|
|
<div>
|
|
<button
|
|
type="submit"
|
|
:disabled="loading"
|
|
class="btn btn-primary w-full flex justify-center items-center space-x-2"
|
|
:class="{ 'opacity-50 cursor-not-allowed': loading }"
|
|
>
|
|
<svg
|
|
x-show="!loading"
|
|
class="w-5 h-5"
|
|
fill="none"
|
|
stroke="currentColor"
|
|
viewBox="0 0 24 24"
|
|
>
|
|
<path
|
|
stroke-linecap="round"
|
|
stroke-linejoin="round"
|
|
stroke-width="2"
|
|
d="M18 9v3m0 0v3m0-3h3m-3 0h-3m-2-5a4 4 0 11-8 0 4 4 0 018 0zM3 20a6 6 0 0112 0v1H3v-1z"
|
|
></path>
|
|
</svg>
|
|
<svg
|
|
x-show="loading"
|
|
class="animate-spin h-5 w-5"
|
|
fill="none"
|
|
viewBox="0 0 24 24"
|
|
>
|
|
<circle
|
|
class="opacity-25"
|
|
cx="12"
|
|
cy="12"
|
|
r="10"
|
|
stroke="currentColor"
|
|
stroke-width="4"
|
|
></circle>
|
|
<path
|
|
class="opacity-75"
|
|
fill="currentColor"
|
|
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
|
|
></path>
|
|
</svg>
|
|
<span
|
|
x-text="loading ? 'Creating account...' : 'Create account'"
|
|
></span>
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Login Link -->
|
|
<div class="text-center">
|
|
<p class="text-sm text-gray-600">
|
|
Already have an account?
|
|
<a
|
|
href="/login.html"
|
|
class="font-medium text-edh-accent hover:text-edh-primary"
|
|
>
|
|
Sign in
|
|
</a>
|
|
</p>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
|
|
<!-- Password Requirements Info -->
|
|
<div class="card bg-blue-50 border-blue-200" x-show="allowRegistration">
|
|
<h3 class="text-sm font-medium text-blue-800 mb-2">
|
|
Password Requirements
|
|
</h3>
|
|
<div class="text-xs text-blue-700 space-y-1">
|
|
<p
|
|
class="flex items-center"
|
|
:class="{ 'text-green-700': formData.password.length >= 8 }"
|
|
>
|
|
<svg
|
|
:class="{ 'text-green-500': formData.password.length >= 8 }"
|
|
class="w-4 h-4 mr-2"
|
|
fill="none"
|
|
stroke="currentColor"
|
|
viewBox="0 0 24 24"
|
|
>
|
|
<path
|
|
stroke-linecap="round"
|
|
stroke-linejoin="round"
|
|
stroke-width="2"
|
|
d="M5 13l4 4L19 7"
|
|
></path>
|
|
</svg>
|
|
At least 8 characters
|
|
</p>
|
|
<p
|
|
class="flex items-center"
|
|
:class="{ 'text-green-700': /(?=.*[a-z])/.test(formData.password) }"
|
|
>
|
|
<svg
|
|
:class="{ 'text-green-500': /(?=.*[a-z])/.test(formData.password) }"
|
|
class="w-4 h-4 mr-2"
|
|
fill="none"
|
|
stroke="currentColor"
|
|
viewBox="0 0 24 24"
|
|
>
|
|
<path
|
|
stroke-linecap="round"
|
|
stroke-linejoin="round"
|
|
stroke-width="2"
|
|
d="M5 13l4 4L19 7"
|
|
></path>
|
|
</svg>
|
|
At least one lowercase letter
|
|
</p>
|
|
<p
|
|
class="flex items-center"
|
|
:class="{ 'text-green-700': /(?=.*[A-Z])/.test(formData.password) }"
|
|
>
|
|
<svg
|
|
:class="{ 'text-green-500': /(?=.*[A-Z])/.test(formData.password) }"
|
|
class="w-4 h-4 mr-2"
|
|
fill="none"
|
|
stroke="currentColor"
|
|
viewBox="0 0 24 24"
|
|
>
|
|
<path
|
|
stroke-linecap="round"
|
|
stroke-linejoin="round"
|
|
stroke-width="2"
|
|
d="M5 13l4 4L19 7"
|
|
></path>
|
|
</svg>
|
|
At least one uppercase letter
|
|
</p>
|
|
<p
|
|
class="flex items-center"
|
|
:class="{ 'text-green-700': /(?=.*\d)/.test(formData.password) }"
|
|
>
|
|
<svg
|
|
:class="{ 'text-green-500': /(?=.*\d)/.test(formData.password) }"
|
|
class="w-4 h-4 mr-2"
|
|
fill="none"
|
|
stroke="currentColor"
|
|
viewBox="0 0 24 24"
|
|
>
|
|
<path
|
|
stroke-linecap="round"
|
|
stroke-linejoin="round"
|
|
stroke-width="2"
|
|
d="M5 13l4 4L19 7"
|
|
></path>
|
|
</svg>
|
|
At least one number (0-9)
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Scripts -->
|
|
<script
|
|
defer
|
|
src="https://unpkg.com/alpinejs@3.x.x/dist/cdn.min.js"
|
|
></script>
|
|
<script src="/js/auth.js"></script>
|
|
|
|
<script src="/js/footer-loader.js"></script>
|
|
</body>
|
|
</html>
|