Sign-in form
Drop into src/blocks/SignInForm.tsx. Wire onSubmit to your auth
backend.
'use client';
import { useState, type FormEvent } from 'react';import { Button, Card, Field, Stack, Text, TextInput,} from '@helixui/core';import { GoogleColor, AppleFilled } from '@helixui/icons';
export interface SignInFormProps { onSubmit: (creds: { email: string; password: string }) => Promise<void>; onOAuth?: (provider: 'google' | 'apple') => void; forgotHref?: string; signUpHref?: string;}
export function SignInForm({ onSubmit, onOAuth, forgotHref = '/forgot', signUpHref = '/signup' }: SignInFormProps) { const [email, setEmail] = useState(''); const [password, setPassword] = useState(''); const [busy, setBusy] = useState(false); const [error, setError] = useState<string | null>(null);
const submit = async (e: FormEvent) => { e.preventDefault(); setError(null); setBusy(true); try { await onSubmit({ email, password }); } catch (err) { setError(err instanceof Error ? err.message : 'Sign-in failed.'); } finally { setBusy(false); } };
return ( <Card variant="elevated" style={{ padding: 'var(--helixui-space-8)', maxWidth: 380, width: '100%' }}> <Stack gap={5}> <Stack gap={1}> <Text size="2xl" weight="semibold">Welcome back</Text> <Text size="sm" tone="muted">Sign in to continue to your workspace.</Text> </Stack>
{onOAuth ? ( <Stack gap={2}> <Button variant="outline" tone="neutral" onClick={() => onOAuth('google')}> <GoogleColor /> Continue with Google </Button> <Button variant="outline" tone="neutral" onClick={() => onOAuth('apple')}> <AppleFilled /> Continue with Apple </Button> <Stack direction="row" align="center" gap={2}> <div style={{ flex: 1, height: 1, background: 'var(--helixui-color-border-subtle)' }} /> <Text size="xs" tone="muted">or</Text> <div style={{ flex: 1, height: 1, background: 'var(--helixui-color-border-subtle)' }} /> </Stack> </Stack> ) : null}
<form onSubmit={submit}> <Stack gap={3}> <Field label="Email"> <TextInput type="email" autoComplete="email" value={email} onChange={(e) => setEmail(e.target.value)} required /> </Field> <Field label="Password" hint={<a href={forgotHref}>Forgot?</a>}> <TextInput type="password" autoComplete="current-password" value={password} onChange={(e) => setPassword(e.target.value)} required minLength={8} /> </Field> {error ? <Text size="sm" tone="danger">{error}</Text> : null} <Button type="submit" loading={busy}>Sign in</Button> </Stack> </form>
<Text size="sm" tone="muted" align="center"> Don't have an account? <a href={signUpHref}>Create one</a> </Text> </Stack> </Card> );}Usage
<SignInForm onSubmit={async ({ email, password }) => { await auth.signIn({ email, password }); }} onOAuth={(provider) => auth.signInWithOAuth(provider)}/>What this is and isn’t
- It’s a pattern. Tweak the copy, the OAuth provider list, the field validation.
- It’s not a finished auth flow. You wire it to your provider (Supabase Auth, Clerk, Auth.js, your own backend).
- It assumes
@helixui/iconsshipsGoogleColorandAppleFilled— if you’re on an older version, swap in your own SVGs.