mirror of
https://github.com/pupperpowell/bibdle.git
synced 2026-04-05 17:33:31 -04:00
- Fix AuthModal to pass anonymousId on both signin and signup - Add comprehensive migration logic in signin that moves anonymous completion stats to authenticated user - Implement deduplication algorithm to handle overlapping completion dates - Maintain earliest completion when duplicates exist 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
217 lines
6.1 KiB
Svelte
217 lines
6.1 KiB
Svelte
<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 on:keydown={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={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} |