This commit is contained in:
149
src/lib/components/AuthForm.svelte
Normal file
149
src/lib/components/AuthForm.svelte
Normal file
@@ -0,0 +1,149 @@
|
||||
<script lang="ts">
|
||||
import { authClient } from '$lib/auth-client';
|
||||
import { onMount } from 'svelte';
|
||||
import { goto } from '$app/navigation';
|
||||
import { resolve } from '$app/paths';
|
||||
let {
|
||||
mode,
|
||||
providers,
|
||||
returnTo,
|
||||
oauthError
|
||||
}: {
|
||||
mode: 'login' | 'register';
|
||||
providers: { google: boolean; osu: boolean };
|
||||
returnTo: string;
|
||||
oauthError: boolean;
|
||||
} = $props();
|
||||
let name = $state('');
|
||||
let email = $state('');
|
||||
let password = $state('');
|
||||
let busy = $state(false);
|
||||
let ready = $state(false);
|
||||
onMount(() => {
|
||||
ready = true;
|
||||
});
|
||||
let message = $state('');
|
||||
async function submit(event: SubmitEvent) {
|
||||
event.preventDefault();
|
||||
const destination = `${resolve('/')}${returnTo.slice(1)}`;
|
||||
busy = true;
|
||||
message = '';
|
||||
try {
|
||||
const result =
|
||||
mode === 'register'
|
||||
? await authClient.signUp.email({ name: name.trim(), email, password })
|
||||
: await authClient.signIn.email({ email, password });
|
||||
if (result.error) {
|
||||
message = result.error.message || 'Unable to sign in. Please try again.';
|
||||
return;
|
||||
}
|
||||
// Destination is a validated local path, prefixed by resolve('/') above.
|
||||
// eslint-disable-next-line svelte/no-navigation-without-resolve
|
||||
await goto(destination, { invalidateAll: true });
|
||||
} catch {
|
||||
message = 'Unable to connect. Please try again.';
|
||||
} finally {
|
||||
busy = false;
|
||||
}
|
||||
}
|
||||
async function social(provider: 'google' | 'osu') {
|
||||
busy = true;
|
||||
message = '';
|
||||
try {
|
||||
const result = await authClient.signIn.social({
|
||||
provider,
|
||||
callbackURL: returnTo,
|
||||
errorCallbackURL: `/login?returnTo=${encodeURIComponent(returnTo)}`
|
||||
});
|
||||
if (result.error) {
|
||||
message = result.error.message || 'Unable to connect to this provider.';
|
||||
busy = false;
|
||||
}
|
||||
} catch {
|
||||
message = 'Unable to connect. Please try again.';
|
||||
busy = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<section class="mx-auto my-12 max-w-md px-4 sm:my-20">
|
||||
<h1 class="mb-8 text-3xl font-semibold tracking-tight">
|
||||
{mode === 'register' ? 'create account' : 'sign in'}
|
||||
</h1>
|
||||
{#if message || oauthError}
|
||||
<p role="alert" class="mb-4 rounded bg-red-50 p-3 text-red-800">
|
||||
{message ||
|
||||
'Provider sign-in failed or was cancelled. Try again, or use your existing login and link the provider from your account.'}
|
||||
</p>
|
||||
{/if}
|
||||
<form method="POST" onsubmit={submit} class="flex flex-col gap-4">
|
||||
{#if mode === 'register'}
|
||||
<label class="flex flex-col gap-1"
|
||||
>Name<input
|
||||
class="rounded border border-[var(--site-border)] bg-[var(--site-bg)] px-3 py-2"
|
||||
disabled={!ready || busy}
|
||||
name="name"
|
||||
autocomplete="name"
|
||||
required
|
||||
maxlength="100"
|
||||
bind:value={name}
|
||||
/></label
|
||||
>
|
||||
{/if}
|
||||
<label class="flex flex-col gap-1"
|
||||
>Email<input
|
||||
class="rounded border border-[var(--site-border)] bg-[var(--site-bg)] px-3 py-2"
|
||||
disabled={!ready || busy}
|
||||
name="email"
|
||||
type="email"
|
||||
autocomplete="email"
|
||||
required
|
||||
bind:value={email}
|
||||
/></label
|
||||
>
|
||||
<label class="flex flex-col gap-1"
|
||||
>Password<input
|
||||
class="rounded border border-[var(--site-border)] bg-[var(--site-bg)] px-3 py-2"
|
||||
disabled={!ready || busy}
|
||||
name="password"
|
||||
type="password"
|
||||
autocomplete={mode === 'register' ? 'new-password' : 'current-password'}
|
||||
required
|
||||
minlength={mode === 'register' ? 12 : undefined}
|
||||
maxlength="128"
|
||||
bind:value={password}
|
||||
/></label
|
||||
>
|
||||
{#if mode === 'register'}<p class="text-sm text-gray-600">Use at least 12 characters.</p>{/if}
|
||||
<button
|
||||
disabled={!ready || busy}
|
||||
class="rounded bg-[var(--site-text)] px-4 py-2 font-semibold text-[var(--site-bg)] disabled:opacity-50"
|
||||
>{busy ? 'please wait…' : mode === 'register' ? 'create account' : 'sign in'}</button
|
||||
>
|
||||
</form>
|
||||
{#if providers.google || providers.osu}
|
||||
<div class="my-6 flex flex-col gap-3 border-t border-[var(--site-border)] pt-4">
|
||||
{#if providers.google}<button
|
||||
disabled={!ready || busy}
|
||||
onclick={() => social('google')}
|
||||
class="rounded border border-[var(--site-border)] px-4 py-2 disabled:opacity-50"
|
||||
>Continue with Google</button
|
||||
>{/if}
|
||||
{#if providers.osu}<button
|
||||
disabled={!ready || busy}
|
||||
onclick={() => social('osu')}
|
||||
class="rounded border border-[var(--site-border)] px-4 py-2 disabled:opacity-50"
|
||||
>Continue with osu!</button
|
||||
>{/if}
|
||||
</div>
|
||||
{/if}
|
||||
<p class="mt-6 text-base">
|
||||
<a
|
||||
class="underline underline-offset-4"
|
||||
href={resolve(
|
||||
`${mode === 'register' ? '/login' : '/register'}?returnTo=${encodeURIComponent(returnTo)}`
|
||||
)}>{mode === 'register' ? 'sign in' : 'Create account'}</a
|
||||
>
|
||||
</p>
|
||||
<noscript><p class="mt-4">Enable JavaScript to securely sign in or register.</p></noscript>
|
||||
</section>
|
||||
Reference in New Issue
Block a user