Skip to content
File

Blob: worker/src/client/ui/login.tsx

typescript51 lines
1import { useActionState } from 'react';
2import { errorText } from '@/client/radio/api.ts';
3 
4export function Login({ onLogin }: { onLogin: (password: string) => Promise<void> }) {
5 const [error, submit, pending] = useActionState(async (_previous: string, form: FormData) => {
6 try {
7 await onLogin(String(form.get('password') ?? ''));
8 return '';
9 } catch (error) {
10 return errorText(error);
11 }
12 }, '');
13 return (
14 <form
15 action={submit}
16 className="mb-5 flex flex-wrap items-center gap-3 rounded-lg border border-line p-4 sm:p-5 [&>div]:min-w-[200px] [&>div]:flex-1 [&>div]:basis-full lg:[&>div]:basis-auto [&_strong]:text-base [&_strong]:font-medium [&_p]:mt-1 [&_p]:text-xs [&_p]:leading-relaxed [&_p]:text-muted [&_label]:min-w-32 [&_label]:flex-1 lg:[&_label]:flex-none [&_input]:w-full [&_input]:rounded-md [&_input]:border [&_input]:border-line [&_input]:bg-leaf [&_input]:px-3 [&_input]:py-3 [&_input]:text-sm"
17 aria-label="Private radio access"
18 >
19 <div>
20 <strong>Private access</strong>
21 <p>Enter the password to listen and use the controls.</p>
22 </div>
23 <label>
24 <span className="sr-only">Viewer password</span>
25 <input
26 name="password"
27 type="password"
28 autoComplete="current-password"
29 placeholder="Viewer password"
30 aria-label="Viewer password"
31 required
32 maxLength={256}
33 disabled={pending}
34 />
35 </label>
36 <button
37 type="submit"
38 className="inline-flex min-h-11 items-center justify-center gap-2 rounded-md border border-line px-3 py-2 text-xs font-medium whitespace-nowrap border-ink bg-ink text-paper enabled:hover:bg-ink-hover"
39 disabled={pending}
40 >
41 {pending ? 'Unlocking…' : 'Unlock exhibit'}
42 </button>
43 {error ? (
44 <p className="basis-full text-error!" role="alert">
45 {error}
46 </p>
47 ) : null}
48 </form>
49 );
50}