Files
bibdle/src/lib/components/AuthModal.svelte
2026-03-14 18:40:57 -04:00

237 lines
7.1 KiB
Svelte
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script lang="ts">
import { enhance } from '$app/forms';
import { browser } from '$app/environment';
import Container from './Container.svelte';
let {
isOpen = $bindable(),
anonymousId = ''
}: {
isOpen: boolean;
anonymousId: string;
} = $props();
let mode = $state<'signin' | 'signup'>('signin');
let loading = $state(false);
let error = $state('');
let success = $state('');
let email = $state('');
let password = $state('');
let firstName = $state('');
let lastName = $state('');
function resetForm() {
email = '';
password = '';
firstName = '';
lastName = '';
error = '';
success = '';
}
function switchMode() {
mode = mode === 'signin' ? 'signup' : 'signin';
resetForm();
}
function closeModal() {
isOpen = false;
resetForm();
}
function handleKeydown(event: KeyboardEvent) {
if (event.key === 'Escape') {
closeModal();
}
}
function handleSubmit() {
loading = true;
error = '';
success = '';
}
function handleResult(event: any) {
loading = false;
const result = event.result;
if (result.type === 'success') {
if (result.data?.success) {
success = mode === 'signin' ? 'Signed in successfully!' : 'Account created successfully!';
setTimeout(() => {
if (browser) {
window.location.reload();
}
}, 1000);
} else if (result.data?.error) {
error = result.data.error;
}
} else if (result.type === 'failure') {
error = result.data?.error || 'An error occurred. Please try again.';
}
}
</script>
<svelte:window onkeydown={handleKeydown} />
{#if isOpen}
<div class="fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/70 backdrop-blur-sm">
<Container class="w-full max-w-md p-6 relative">
<button
type="button"
onclick={closeModal}
class="absolute top-4 right-4 text-white hover:text-gray-300 text-2xl leading-none"
>
×
</button>
<div class="mb-6">
<h2 class="text-2xl font-bold text-white">
{mode === 'signin' ? 'Sign In' : 'Create Account'}
</h2>
</div>
<form method="POST" action="/auth/apple">
<input type="hidden" name="anonymousId" value={anonymousId} />
<button
type="submit"
class="w-full flex items-center justify-center gap-2 px-4 py-2.5 bg-white text-black rounded-md hover:bg-gray-100 transition-colors font-medium"
data-umami-event="Sign in with Apple"
>
<svg class="w-5 h-5" viewBox="0 0 24 24" fill="currentColor">
<path d="M17.05 20.28c-.98.95-2.05.88-3.08.4-1.09-.5-2.08-.48-3.24 0-1.44.62-2.2.44-3.06-.4C2.79 15.25 3.51 7.59 9.05 7.31c1.35.07 2.29.74 3.08.8 1.18-.24 2.31-.93 3.57-.84 1.51.12 2.65.72 3.4 1.8-3.12 1.87-2.38 5.98.48 7.13-.57 1.5-1.31 2.99-2.54 4.09zM12.03 7.25c-.15-2.23 1.66-4.07 3.74-4.25.29 2.58-2.34 4.5-3.74 4.25z"/>
</svg>
Sign in with Apple
</button>
</form>
<div class="flex items-center my-4">
<div class="flex-1 border-t border-white/20"></div>
<span class="px-3 text-sm text-white/60">or</span>
<div class="flex-1 border-t border-white/20"></div>
</div>
<form
method="POST"
action={mode === 'signin' ? '/auth/signin' : '/auth/signup'}
use:enhance={({ formData }) => {
if (anonymousId) {
formData.append('anonymousId', anonymousId);
}
handleSubmit();
return handleResult;
}}
>
<div class="space-y-4">
{#if mode === 'signup'}
<div class="grid grid-cols-2 gap-4">
<div>
<label for="firstName" class="block text-sm font-medium text-white mb-1">
First Name
</label>
<input
id="firstName"
name="firstName"
type="text"
bind:value={firstName}
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent text-white bg-transparent placeholder-white/60"
placeholder="John"
/>
</div>
<div>
<label for="lastName" class="block text-sm font-medium text-white mb-1">
Last Name
</label>
<input
id="lastName"
name="lastName"
type="text"
bind:value={lastName}
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent text-white bg-transparent placeholder-white/60"
placeholder="Doe"
/>
</div>
</div>
{/if}
<div>
<label for="email" class="block text-sm font-medium text-white mb-1">
Email
</label>
<input
id="email"
name="email"
type="email"
required
bind:value={email}
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent text-white bg-transparent placeholder-white/60"
placeholder="john@example.com"
/>
</div>
<div>
<label for="password" class="block text-sm font-medium text-white mb-1">
Password
</label>
<input
id="password"
name="password"
type="password"
required
bind:value={password}
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent text-white bg-transparent placeholder-white/60"
placeholder="••••••••"
minlength="6"
/>
{#if mode === 'signup'}
<p class="text-xs text-white/80 mt-1">Minimum 6 characters</p>
{/if}
</div>
</div>
{#if error}
<div class="mt-4 p-3 bg-red-50 border border-red-200 rounded-md">
<p class="text-sm text-red-600">{error}</p>
</div>
{/if}
{#if success}
<div class="mt-4 p-3 bg-green-50 border border-green-200 rounded-md">
<p class="text-sm text-green-600">{success}</p>
</div>
{/if}
<button
type="submit"
disabled={loading}
class="w-full mt-6 px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
>
{#if loading}
<span class="inline-flex items-center">
<svg class="animate-spin -ml-1 mr-3 h-5 w-5 text-white" xmlns="http://www.w3.org/2000/svg" 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>
{mode === 'signin' ? 'Signing in...' : 'Creating account...'}
</span>
{:else}
{mode === 'signin' ? 'Sign In' : 'Create Account'}
{/if}
</button>
</form>
<div class="mt-6 text-center">
<p class="text-sm text-white">
{mode === 'signin' ? "Don't have an account?" : 'Already have an account?'}
<button
type="button"
onclick={switchMode}
class="text-blue-300 hover:text-blue-200 font-medium ml-1"
>
{mode === 'signin' ? 'Create one' : 'Sign in'}
</button>
</p>
</div>
</Container>
</div>
{/if}