import { useState, type ReactNode } from 'react'; import { ApiError } from '../api/client'; import { usePublicConfig } from '../api/hooks'; import { useI18n } from '../i18n'; import { useSession } from '../state/session'; import { useDismissSsr } from '../state/ssr'; import { goTo, workspacePath } from '../state/route'; /** * The sign-in card, and on an install that is licensed rather than sold, the * page you land on at the root as well — see `FrontDoor`. So it has to answer * the question a stranger arrives with on its own, without a page in front of * it: how do I get in, and what do I do if I cannot. */ export function LoginScreen(): ReactNode { const session = useSession(); const install = usePublicConfig(); const { t } = useI18n(); useDismissSsr(); const signupsOpen = install.data?.signupsOpen ?? true; const [email, setEmail] = useState(''); const [password, setPassword] = useState(''); const [error, setError] = useState(null); const [busy, setBusy] = useState(false); async function submit(event: React.FormEvent) { event.preventDefault(); setBusy(true); setError(null); try { const workspaces = await session.signIn(email.trim(), password); // Signing in at `/w/acme` re-renders straight into it. Signing in at `/` // does not, and showing someone with exactly one workspace a list of one // is a step with nothing in it. const only = workspaces.length === 1 ? workspaces[0] : undefined; if (only && !window.location.pathname.startsWith('/w/')) { goTo(workspacePath(only.slug)); return; } } catch (err) { setError( err instanceof ApiError && err.status === 401 ? t('auth.badCredentials') : err instanceof Error ? err.message : t('auth.signInFailed'), ); setPassword(''); } finally { setBusy(false); } } return (
Wellformed
{t('auth.loginTagline')}
void submit(e)}> {error && (
{error}
)}

{t('auth.forgotPassword')}

{/* An invitation-only install has nowhere to send someone who has no account, so it says that plainly rather than offering a link that answers 403. The person who runs it is the way in. */}

{signupsOpen ? ( <> {t('auth.noAccountBefore')} {t('auth.noAccountLink')}. ) : ( t('auth.inviteOnly') )}

); }