mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-14 21:01:08 +02:00
refactor(auth-ui): rewrite LoginPage + RegisterPage to use Tailwind CSS
Replace ~700 lines of scoped CSS with Tailwind utility classes for consistency with the rest of the monorepo. Both pages now use identical patterns: Tailwind for layout/sizing/spacing, style: bindings for dynamic dark/light colors, minimal <style> block for keyframe animations. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
b3541957bd
commit
97798e5382
2 changed files with 527 additions and 982 deletions
File diff suppressed because it is too large
Load diff
|
|
@ -156,31 +156,6 @@
|
|||
}
|
||||
}
|
||||
|
||||
// Password validation
|
||||
let passwordRequirements = $derived.by(() => {
|
||||
if (!password) {
|
||||
return {
|
||||
length: false,
|
||||
lowercase: false,
|
||||
uppercase: false,
|
||||
digit: false,
|
||||
special: false,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
length: password.length >= 8,
|
||||
lowercase: /[a-z]/.test(password),
|
||||
uppercase: /[A-Z]/.test(password),
|
||||
digit: /[0-9]/.test(password),
|
||||
special: /[^a-zA-Z0-9]/.test(password),
|
||||
};
|
||||
});
|
||||
|
||||
function getPageBackground() {
|
||||
return isDark ? darkBackground : lightBackground;
|
||||
}
|
||||
|
||||
async function handleRegister() {
|
||||
loading = true;
|
||||
error = null;
|
||||
|
|
@ -266,23 +241,22 @@
|
|||
|
||||
<svelte:head>
|
||||
<title>Create Account - {appName}</title>
|
||||
<meta name="theme-color" content={darkBackground} media="(prefers-color-scheme: dark)" />
|
||||
<meta name="theme-color" content={lightBackground} media="(prefers-color-scheme: light)" />
|
||||
</svelte:head>
|
||||
|
||||
<div
|
||||
class="flex min-h-screen flex-col justify-between"
|
||||
style="background-color: {getPageBackground()}; max-width: 100vw; overflow-x: hidden;"
|
||||
class="flex flex-col min-h-screen min-h-dvh w-full max-w-[100vw] overflow-x-hidden m-0 p-0"
|
||||
style:background-color={isDark ? darkBackground || '#121212' : lightBackground || '#f5f5f5'}
|
||||
>
|
||||
<!-- Theme Toggle - Top Left -->
|
||||
<!-- Theme Toggle -->
|
||||
<button
|
||||
type="button"
|
||||
onclick={toggleTheme}
|
||||
style="position: absolute; top: 1rem; left: 1rem; z-index: 50; display: flex; align-items: center; justify-content: center; width: 2.5rem; height: 2.5rem; border-radius: 0.5rem; border: 1px solid {isDark
|
||||
? 'rgba(255, 255, 255, 0.2)'
|
||||
: 'rgba(0, 0, 0, 0.2)'}; background: {isDark
|
||||
? 'rgba(255, 255, 255, 0.1)'
|
||||
: 'rgba(0, 0, 0, 0.05)'}; color: {isDark
|
||||
? 'rgba(255, 255, 255, 0.7)'
|
||||
: 'rgba(0, 0, 0, 0.7)'}; cursor: pointer; transition: all 0.2s ease;"
|
||||
class="absolute top-4 left-4 z-50 flex items-center justify-center w-10 h-10 rounded-lg border cursor-pointer transition-all"
|
||||
style:border-color={isDark ? 'rgba(255,255,255,0.2)' : 'rgba(0,0,0,0.2)'}
|
||||
style:background-color={isDark ? 'rgba(255,255,255,0.1)' : 'rgba(0,0,0,0.05)'}
|
||||
style:color={isDark ? 'rgba(255,255,255,0.7)' : 'rgba(0,0,0,0.7)'}
|
||||
aria-label={isDark ? 'Switch to light mode' : 'Switch to dark mode'}
|
||||
>
|
||||
{#if isDark}
|
||||
|
|
@ -293,295 +267,345 @@
|
|||
</button>
|
||||
|
||||
{#if headerControls}
|
||||
<div
|
||||
style="position: absolute; top: 1rem; right: 1rem; z-index: 50; opacity: 0.6; display: flex; gap: 0.75rem;"
|
||||
>
|
||||
<div class="absolute top-4 right-4 z-50 opacity-60 flex gap-3">
|
||||
{@render headerControls()}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<!-- Top Section - Logo -->
|
||||
<div class="flex flex-col items-center justify-center pt-16 pb-8">
|
||||
<div
|
||||
class="flex items-center justify-center rounded-full transition-all mb-4"
|
||||
style="width: 120px; height: 120px; border: 3px solid {primaryColor}; background-color: {isDark
|
||||
? '#000'
|
||||
: '#fff'}; box-shadow: {isDark
|
||||
? '0 6px 12px rgba(0, 0, 0, 0.4)'
|
||||
: '0 6px 12px rgba(0, 0, 0, 0.15)'};"
|
||||
>
|
||||
<Logo size={55} color={primaryColor} />
|
||||
</div>
|
||||
<h1 class="text-2xl font-semibold" style="color: {isDark ? '#ffffff' : '#000000'};">
|
||||
{appName}
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
<!-- Middle Section - Register Form -->
|
||||
<div class="flex-1 flex items-start justify-center px-5 pt-8 pb-8">
|
||||
<div
|
||||
class="w-full max-w-md rounded-xl p-6"
|
||||
style="background-color: {isDark
|
||||
? 'rgba(255, 255, 255, 0.08)'
|
||||
: 'rgba(255, 255, 255, 0.7)'}; backdrop-filter: blur(10px); border: 1px solid {isDark
|
||||
? 'rgba(255, 255, 255, 0.1)'
|
||||
: 'rgba(0, 0, 0, 0.1)'};"
|
||||
>
|
||||
<!-- Title -->
|
||||
<h2
|
||||
class="mb-6 text-center text-xl font-semibold"
|
||||
style="color: {isDark ? 'rgba(255, 255, 255, 0.9)' : 'rgba(0, 0, 0, 0.9)'};"
|
||||
<main class="flex-1 flex flex-col">
|
||||
<!-- Logo Section -->
|
||||
<div class="flex flex-col items-center pt-12 max-[480px]:pt-8 px-4 pb-6 anim-fade-in-scale">
|
||||
<div
|
||||
class="w-[100px] h-[100px] max-[480px]:w-[80px] max-[480px]:h-[80px] rounded-full border-[3px] flex items-center justify-center mb-3 cursor-pointer transition-transform shadow-lg hover:scale-105"
|
||||
style:border-color={primaryColor}
|
||||
style:background-color={isDark ? '#000' : '#fff'}
|
||||
>
|
||||
{t.title}
|
||||
</h2>
|
||||
<Logo size={55} color={primaryColor} />
|
||||
</div>
|
||||
<h1 class="text-2xl font-semibold" style:color={isDark ? '#fff' : '#000'}>{appName}</h1>
|
||||
</div>
|
||||
|
||||
<!-- Error Messages -->
|
||||
{#if error}
|
||||
<div class="mb-4 rounded-xl bg-red-500/20 border border-red-500/30 p-3">
|
||||
<p class="text-sm text-red-500">{error}</p>
|
||||
<!-- Form Section -->
|
||||
<div class="flex-1 flex justify-center px-4 pt-4 pb-8">
|
||||
<div
|
||||
class="w-full max-w-[400px] rounded-2xl p-6 max-[480px]:p-5 border backdrop-blur-[10px] anim-fade-in-up"
|
||||
style:background-color={isDark ? 'rgba(255,255,255,0.08)' : 'rgba(255,255,255,0.7)'}
|
||||
style:border-color={isDark ? 'rgba(255,255,255,0.1)' : 'rgba(0,0,0,0.1)'}
|
||||
>
|
||||
<!-- Title -->
|
||||
<div class="text-center mb-6">
|
||||
<h2
|
||||
class="text-xl font-semibold"
|
||||
style:color={isDark ? 'rgba(255,255,255,0.9)' : 'rgba(0,0,0,0.9)'}
|
||||
>
|
||||
{t.title}
|
||||
</h2>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<!-- Verification Email Sent Confirmation -->
|
||||
{#if verificationEmailSent}
|
||||
<div class="mb-4 rounded-xl bg-green-500/20 border border-green-500/30 p-4">
|
||||
<p class="text-sm text-green-500 font-medium">
|
||||
{t.verificationEmailSent}
|
||||
</p>
|
||||
</div>
|
||||
{/if}
|
||||
<!-- Error Message -->
|
||||
{#if error}
|
||||
<div
|
||||
class="flex items-start gap-2 p-3 mb-4 rounded-xl text-sm bg-red-500/15 border border-red-500/30 text-red-500"
|
||||
role="alert"
|
||||
>
|
||||
<span>⚠</span>
|
||||
<p>{error}</p>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<!-- Success Message with Resend Option -->
|
||||
{#if success && needsVerification}
|
||||
<div
|
||||
class="mb-6 rounded-xl p-5"
|
||||
style="background-color: {isDark
|
||||
? 'rgba(34, 197, 94, 0.15)'
|
||||
: 'rgba(34, 197, 94, 0.1)'}; border: 2px solid rgba(34, 197, 94, 0.4);"
|
||||
<!-- Verification Email Sent -->
|
||||
{#if verificationEmailSent}
|
||||
<div
|
||||
class="flex items-center gap-2 p-3 mb-4 rounded-xl text-sm bg-green-500/15 border border-green-500/30 text-green-500"
|
||||
>
|
||||
<span>{t.verificationEmailSent}</span>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<!-- Success: Needs Verification -->
|
||||
{#if success && needsVerification}
|
||||
<div class="mb-6 rounded-xl p-5 bg-green-500/15 border-2 border-green-500/40">
|
||||
<div class="flex items-start gap-3 mb-4">
|
||||
<div
|
||||
class="flex-shrink-0 w-10 h-10 rounded-full flex items-center justify-center bg-green-500/20"
|
||||
>
|
||||
<svg
|
||||
class="w-5 h-5 text-green-500"
|
||||
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>
|
||||
<h3
|
||||
class="font-semibold text-base mb-1"
|
||||
style:color={isDark ? '#22c55e' : '#16a34a'}
|
||||
>
|
||||
{t.checkYourEmail || 'Check your email'}
|
||||
</h3>
|
||||
<p
|
||||
class="text-sm"
|
||||
style:color={isDark ? 'rgba(255,255,255,0.7)' : 'rgba(0,0,0,0.7)'}
|
||||
>
|
||||
{t.accountCreated}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{#if onResendVerification}
|
||||
<div
|
||||
class="pt-3 border-t"
|
||||
style:border-color={isDark ? 'rgba(255,255,255,0.1)' : 'rgba(0,0,0,0.1)'}
|
||||
>
|
||||
<p
|
||||
class="text-xs mb-2"
|
||||
style:color={isDark ? 'rgba(255,255,255,0.5)' : 'rgba(0,0,0,0.5)'}
|
||||
>
|
||||
Didn't receive the email?
|
||||
</p>
|
||||
<button
|
||||
type="button"
|
||||
onclick={handleResendVerification}
|
||||
disabled={resendingVerification}
|
||||
class="w-full flex items-center justify-center gap-2 h-11 rounded-lg font-medium transition-opacity hover:opacity-80 disabled:opacity-50 disabled:cursor-not-allowed border-[1.5px]"
|
||||
style:background-color="{primaryColor}40"
|
||||
style:border-color={primaryColor}
|
||||
style:color={isDark ? '#fff' : '#000'}
|
||||
>
|
||||
{#if resendingVerification}
|
||||
<svg class="animate-spin h-4 w-4" viewBox="0 0 24 24">
|
||||
<circle
|
||||
class="opacity-25"
|
||||
cx="12"
|
||||
cy="12"
|
||||
r="10"
|
||||
stroke="currentColor"
|
||||
stroke-width="4"
|
||||
fill="none"
|
||||
></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>
|
||||
{t.resendingVerification}
|
||||
{:else}
|
||||
{t.resendVerification}
|
||||
{/if}
|
||||
</button>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<!-- Register Form -->
|
||||
<form
|
||||
onsubmit={(e) => {
|
||||
e.preventDefault();
|
||||
handleRegister();
|
||||
}}
|
||||
>
|
||||
<div class="flex items-start gap-3 mb-4">
|
||||
<div
|
||||
class="flex-shrink-0 w-10 h-10 rounded-full flex items-center justify-center"
|
||||
style="background-color: rgba(34, 197, 94, 0.2);"
|
||||
>
|
||||
<svg
|
||||
class="w-5 h-5 text-green-500"
|
||||
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>
|
||||
<h3
|
||||
class="font-semibold text-base mb-1"
|
||||
style="color: {isDark ? '#22c55e' : '#16a34a'};"
|
||||
>
|
||||
{t.checkYourEmail || 'Check your email'}
|
||||
</h3>
|
||||
<p
|
||||
class="text-sm"
|
||||
style="color: {isDark ? 'rgba(255, 255, 255, 0.7)' : 'rgba(0, 0, 0, 0.7)'};"
|
||||
>
|
||||
{t.accountCreated}
|
||||
</p>
|
||||
</div>
|
||||
<!-- Email -->
|
||||
<div class="mb-3">
|
||||
<input
|
||||
type="email"
|
||||
bind:value={email}
|
||||
placeholder={t.emailPlaceholder}
|
||||
required
|
||||
class="w-full h-14 px-4 border rounded-xl text-base transition-colors focus:outline-none focus:ring-2"
|
||||
style:background-color={isDark ? 'rgba(0,0,0,0.2)' : 'rgba(255,255,255,0.8)'}
|
||||
style:border-color={isDark ? 'rgba(255,255,255,0.2)' : 'rgba(0,0,0,0.1)'}
|
||||
style:color={isDark ? '#fff' : '#000'}
|
||||
style:--tw-ring-color={primaryColor}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{#if onResendVerification}
|
||||
<div
|
||||
class="pt-3 border-t"
|
||||
style="border-color: {isDark ? 'rgba(255, 255, 255, 0.1)' : 'rgba(0, 0, 0, 0.1)'};"
|
||||
>
|
||||
<p
|
||||
class="text-xs mb-2"
|
||||
style="color: {isDark ? 'rgba(255, 255, 255, 0.5)' : 'rgba(0, 0, 0, 0.5)'};"
|
||||
>
|
||||
Didn't receive the email?
|
||||
</p>
|
||||
<!-- Password -->
|
||||
<div class="mb-3">
|
||||
<div class="relative">
|
||||
<input
|
||||
type={showPassword ? 'text' : 'password'}
|
||||
bind:value={password}
|
||||
placeholder={t.passwordPlaceholder}
|
||||
required
|
||||
minlength={8}
|
||||
class="w-full h-14 px-4 pr-12 border rounded-xl text-base transition-colors focus:outline-none focus:ring-2"
|
||||
style:background-color={isDark ? 'rgba(0,0,0,0.2)' : 'rgba(255,255,255,0.8)'}
|
||||
style:border-color={isDark ? 'rgba(255,255,255,0.2)' : 'rgba(0,0,0,0.1)'}
|
||||
style:color={isDark ? '#fff' : '#000'}
|
||||
style:--tw-ring-color={primaryColor}
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
onclick={handleResendVerification}
|
||||
disabled={resendingVerification}
|
||||
class="w-full flex items-center justify-center gap-2 h-11 rounded-lg font-medium transition-all hover:opacity-80 disabled:opacity-50"
|
||||
style="background-color: {primaryColor}40; border: 1.5px solid {primaryColor}; color: {isDark
|
||||
? '#ffffff'
|
||||
: '#000000'};"
|
||||
onclick={() => (showPassword = !showPassword)}
|
||||
class="absolute right-0 top-0 h-full w-12 flex items-center justify-center bg-transparent border-none cursor-pointer transition-opacity"
|
||||
style:color={isDark ? 'rgba(255,255,255,0.5)' : 'rgba(0,0,0,0.4)'}
|
||||
aria-label={showPassword ? t.hidePassword : t.showPassword}
|
||||
>
|
||||
{#if resendingVerification}
|
||||
<svg class="animate-spin h-4 w-4" viewBox="0 0 24 24">
|
||||
<circle
|
||||
class="opacity-25"
|
||||
cx="12"
|
||||
cy="12"
|
||||
r="10"
|
||||
stroke="currentColor"
|
||||
stroke-width="4"
|
||||
fill="none"
|
||||
></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>
|
||||
{t.resendingVerification}
|
||||
{#if showPassword}
|
||||
<EyeSlash size={20} />
|
||||
{:else}
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"
|
||||
></path>
|
||||
</svg>
|
||||
{t.resendVerification}
|
||||
<Eye size={20} />
|
||||
{/if}
|
||||
</button>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<!-- Register Form -->
|
||||
<form
|
||||
onsubmit={(e) => {
|
||||
e.preventDefault();
|
||||
handleRegister();
|
||||
}}
|
||||
class="space-y-4"
|
||||
>
|
||||
<div>
|
||||
<input
|
||||
type="email"
|
||||
bind:value={email}
|
||||
placeholder={t.emailPlaceholder}
|
||||
required
|
||||
class="h-14 w-full rounded-xl border px-4 text-lg transition-colors focus:outline-none focus:ring-2"
|
||||
style="background-color: {isDark
|
||||
? 'rgba(0, 0, 0, 0.2)'
|
||||
: 'rgba(255, 255, 255, 0.8)'}; border-color: {isDark
|
||||
? 'rgba(255, 255, 255, 0.2)'
|
||||
: 'rgba(0, 0, 0, 0.1)'}; color: {isDark
|
||||
? '#ffffff'
|
||||
: '#000000'}; --tw-ring-color: {primaryColor};"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class="relative">
|
||||
<input
|
||||
type={showPassword ? 'text' : 'password'}
|
||||
bind:value={password}
|
||||
placeholder={t.passwordPlaceholder}
|
||||
required
|
||||
minlength={8}
|
||||
class="h-14 w-full rounded-xl border px-4 pr-14 text-lg transition-colors focus:outline-none focus:ring-2"
|
||||
style="background-color: {isDark
|
||||
? 'rgba(0, 0, 0, 0.2)'
|
||||
: 'rgba(255, 255, 255, 0.8)'}; border-color: {isDark
|
||||
? 'rgba(255, 255, 255, 0.2)'
|
||||
: 'rgba(0, 0, 0, 0.1)'}; color: {isDark
|
||||
? '#ffffff'
|
||||
: '#000000'}; --tw-ring-color: {primaryColor};"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => (showPassword = !showPassword)}
|
||||
class="absolute inset-y-0 right-0 flex items-center justify-center w-14 text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200 transition-colors"
|
||||
aria-label={showPassword ? t.hidePassword : t.showPassword}
|
||||
>
|
||||
{#if showPassword}
|
||||
<EyeSlash size={20} />
|
||||
{:else}
|
||||
<Eye size={20} />
|
||||
{/if}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<PasswordStrength {password} {primaryColor} />
|
||||
<PasswordStrength {password} {primaryColor} />
|
||||
|
||||
<div>
|
||||
<div class="relative">
|
||||
<input
|
||||
type={showConfirmPassword ? 'text' : 'password'}
|
||||
bind:value={confirmPassword}
|
||||
placeholder={t.confirmPasswordPlaceholder}
|
||||
required
|
||||
minlength={8}
|
||||
class="h-14 w-full rounded-xl border px-4 pr-14 text-lg transition-colors focus:outline-none focus:ring-2"
|
||||
style="background-color: {isDark
|
||||
? 'rgba(0, 0, 0, 0.2)'
|
||||
: 'rgba(255, 255, 255, 0.8)'}; border-color: {isDark
|
||||
? 'rgba(255, 255, 255, 0.2)'
|
||||
: 'rgba(0, 0, 0, 0.1)'}; color: {isDark
|
||||
? '#ffffff'
|
||||
: '#000000'}; --tw-ring-color: {primaryColor};"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => (showConfirmPassword = !showConfirmPassword)}
|
||||
class="absolute inset-y-0 right-0 flex items-center justify-center w-14 text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200 transition-colors"
|
||||
aria-label={showConfirmPassword ? t.hidePassword : t.showPassword}
|
||||
>
|
||||
{#if showConfirmPassword}
|
||||
<EyeSlash size={20} />
|
||||
{:else}
|
||||
<Eye size={20} />
|
||||
{/if}
|
||||
</button>
|
||||
<!-- Confirm Password -->
|
||||
<div class="mb-3">
|
||||
<div class="relative">
|
||||
<input
|
||||
type={showConfirmPassword ? 'text' : 'password'}
|
||||
bind:value={confirmPassword}
|
||||
placeholder={t.confirmPasswordPlaceholder}
|
||||
required
|
||||
minlength={8}
|
||||
class="w-full h-14 px-4 pr-12 border rounded-xl text-base transition-colors focus:outline-none focus:ring-2"
|
||||
style:background-color={isDark ? 'rgba(0,0,0,0.2)' : 'rgba(255,255,255,0.8)'}
|
||||
style:border-color={isDark ? 'rgba(255,255,255,0.2)' : 'rgba(0,0,0,0.1)'}
|
||||
style:color={isDark ? '#fff' : '#000'}
|
||||
style:--tw-ring-color={primaryColor}
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => (showConfirmPassword = !showConfirmPassword)}
|
||||
class="absolute right-0 top-0 h-full w-12 flex items-center justify-center bg-transparent border-none cursor-pointer transition-opacity"
|
||||
style:color={isDark ? 'rgba(255,255,255,0.5)' : 'rgba(0,0,0,0.4)'}
|
||||
aria-label={showConfirmPassword ? t.hidePassword : t.showPassword}
|
||||
>
|
||||
{#if showConfirmPassword}
|
||||
<EyeSlash size={20} />
|
||||
{:else}
|
||||
<Eye size={20} />
|
||||
{/if}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Password Requirements -->
|
||||
<p
|
||||
class="text-xs mb-3"
|
||||
style:color={isDark ? 'rgba(255,255,255,0.5)' : 'rgba(0,0,0,0.5)'}
|
||||
>
|
||||
{t.passwordRequirements}
|
||||
</p>
|
||||
|
||||
<!-- Submit -->
|
||||
<button
|
||||
type="submit"
|
||||
disabled={loading}
|
||||
class="w-full h-14 border-2 rounded-xl font-medium flex items-center justify-center gap-2 cursor-pointer transition-opacity hover:opacity-85 disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
style:background-color="{primaryColor}60"
|
||||
style:border-color={primaryColor}
|
||||
style:color={isDark ? '#fff' : '#000'}
|
||||
>
|
||||
{#if loading}
|
||||
<svg
|
||||
class="w-5 h-5 animate-spin"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
>
|
||||
<circle cx="12" cy="12" r="10" stroke-opacity="0.25" />
|
||||
<path d="M12 2a10 10 0 0 1 10 10" stroke-linecap="round" />
|
||||
</svg>
|
||||
<span>{t.creatingAccount}</span>
|
||||
{:else}
|
||||
<UserPlus size={20} />
|
||||
<span>{t.createAccountButton}</span>
|
||||
{/if}
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<!-- Back to Login -->
|
||||
<div class="text-center mt-4">
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => goto(loginPath)}
|
||||
class="inline-flex items-center gap-2 bg-transparent border-none cursor-pointer font-medium transition-opacity hover:opacity-70"
|
||||
style:color={isDark ? 'rgba(255,255,255,0.7)' : 'rgba(0,0,0,0.7)'}
|
||||
>
|
||||
<ArrowLeft size={20} />
|
||||
{t.backToLogin}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Password Requirements -->
|
||||
<p
|
||||
class="text-xs -mt-2"
|
||||
style="color: {isDark ? 'rgba(255, 255, 255, 0.5)' : 'rgba(0, 0, 0, 0.5)'};"
|
||||
>
|
||||
{t.passwordRequirements}
|
||||
</p>
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
disabled={loading}
|
||||
class="flex h-14 w-full items-center justify-center gap-2 rounded-xl font-medium transition-all hover:opacity-80 disabled:opacity-50 border-2 mt-2"
|
||||
style="background-color: {primaryColor}60; border-color: {primaryColor}; color: {isDark
|
||||
? '#ffffff'
|
||||
: '#000000'};"
|
||||
>
|
||||
<UserPlus size={20} class="inline-block" />
|
||||
{loading ? t.creatingAccount : t.createAccountButton}
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<!-- Back Button -->
|
||||
<div class="mt-4">
|
||||
<button
|
||||
onclick={() => goto(loginPath)}
|
||||
class="flex h-10 w-full items-center justify-center gap-2 rounded-xl font-medium transition-all hover:opacity-80"
|
||||
style="color: {isDark ? '#ffffff' : '#000000'};"
|
||||
>
|
||||
<ArrowLeft size={20} class="inline-block" />
|
||||
{t.backToLogin}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<!-- App Slider -->
|
||||
{#if appSlider}
|
||||
<div class="w-full px-4 pb-8">
|
||||
<footer class="w-full pb-4 anim-fade-in">
|
||||
{@render appSlider()}
|
||||
</div>
|
||||
{:else}
|
||||
<!-- Bottom padding -->
|
||||
<div class="pb-8"></div>
|
||||
</footer>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
:global(html, body) {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
@keyframes fadeInUp {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(20px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
@keyframes fadeInScale {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: scale(0.9);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
@keyframes fadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
.anim-fade-in-up {
|
||||
animation: fadeInUp 0.5s ease-out 0.15s both;
|
||||
}
|
||||
.anim-fade-in-scale {
|
||||
animation: fadeInScale 0.5s ease-out both;
|
||||
}
|
||||
.anim-fade-in {
|
||||
animation: fadeIn 0.5s ease-out 0.3s both;
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.anim-fade-in-up,
|
||||
.anim-fade-in-scale,
|
||||
.anim-fade-in {
|
||||
animation: none;
|
||||
}
|
||||
* {
|
||||
transition-duration: 0.01ms !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue