You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

83 lines
2.8 KiB
TypeScript

import { type Component, createSignal, onMount, Match, Switch } from 'solid-js';
import { useSearchParams, useNavigate, A } from '@solidjs/router';
import { CheckCircle, XCircle } from 'lucide-solid';
import { authApi } from '~/api/auth';
import { useAuth } from '~/contexts/AuthContext';
import { useI18n } from '~/i18n';
import LoadingSpinner from '~/components/ui/LoadingSpinner';
/**
* Magic-link verification page reached via `/auth/verify?token=...`.
*
* On mount, the `token` query parameter is extracted from the URL. If present,
* it is POSTed to the backend's verify endpoint which sets a session cookie.
* On success the auth context is refreshed and the user is redirected home
* after a 1.5-second delay. Missing or invalid tokens display an error with
* a link back to the login page.
*/
const AuthVerify: Component = () => {
const [searchParams] = useSearchParams();
const navigate = useNavigate();
const { refreshUser } = useAuth();
const { t } = useI18n();
const [status, setStatus] = createSignal<'verifying' | 'success' | 'error'>(
'verifying',
);
onMount(async () => {
const token = searchParams.token;
if (!token || typeof token !== 'string') {
setStatus('error');
return;
}
try {
await authApi.verify(token);
await refreshUser();
setStatus('success');
setTimeout(() => navigate('/', { replace: true }), 1500);
} catch {
setStatus('error');
}
});
return (
<div class="min-h-screen flex items-center justify-center bg-gray-50 py-12 px-4">
<div class="max-w-md w-full text-center">
<Switch>
<Match when={status() === 'verifying'}>
<LoadingSpinner fullPage={false} />
<p class="mt-4 text-sm text-gray-500">{t('auth.verifying')}</p>
</Match>
<Match when={status() === 'success'}>
<CheckCircle class="mx-auto h-12 w-12 text-green-500" />
<h2 class="mt-4 text-2xl font-bold text-gray-900">
{t('auth.verifySuccess')}
</h2>
</Match>
<Match when={status() === 'error'}>
<XCircle class="mx-auto h-12 w-12 text-red-500" />
<h2 class="mt-4 text-2xl font-bold text-gray-900">
{t('auth.verifyError')}
</h2>
<p class="mt-2 text-sm text-gray-500">
{t('auth.verifyErrorDescription')}
</p>
<A
href="/login"
class="mt-6 inline-flex items-center px-4 py-2 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
>
{t('auth.backToLogin')}
</A>
</Match>
</Switch>
</div>
</div>
);
};
export default AuthVerify;