chore: server-and-serverless template
This commit is contained in:
105
apps/app/src/components/AuthCard.tsx
Normal file
105
apps/app/src/components/AuthCard.tsx
Normal file
@@ -0,0 +1,105 @@
|
||||
import type { FormEvent } from 'react'
|
||||
import type { I18nNode, I18nString } from '@pikku/react'
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
Card,
|
||||
Container,
|
||||
PasswordInput,
|
||||
Stack,
|
||||
Text,
|
||||
TextInput,
|
||||
Title,
|
||||
} from '@pikku/mantine/core'
|
||||
import { m } from '@/i18n/messages'
|
||||
import { useLocale } from '@/i18n/config'
|
||||
|
||||
type AuthCardProps = {
|
||||
title: I18nString
|
||||
description: I18nString
|
||||
cta: I18nString
|
||||
email: string
|
||||
password: string
|
||||
// 'current-password' on login (let the browser fill a saved password),
|
||||
// 'new-password' on signup (let it suggest a strong one).
|
||||
passwordAutoComplete: 'current-password' | 'new-password'
|
||||
busy: boolean
|
||||
message: I18nString | null
|
||||
error: I18nString | null
|
||||
footer: I18nNode
|
||||
onEmailChange: (value: string) => void
|
||||
onPasswordChange: (value: string) => void
|
||||
onSubmit: (event: FormEvent<HTMLFormElement>) => void
|
||||
}
|
||||
|
||||
export function AuthCard(props: AuthCardProps) {
|
||||
useLocale()
|
||||
|
||||
return (
|
||||
<Box
|
||||
mih="100vh"
|
||||
style={{
|
||||
background:
|
||||
'radial-gradient(circle at top left, rgba(96, 165, 250, 0.16), transparent 30%), linear-gradient(180deg, #10203a 0%, #07111e 56%, #040913 100%)',
|
||||
}}
|
||||
py="xl"
|
||||
>
|
||||
<Container size={560}>
|
||||
<Card radius="xl" padding="xl" withBorder shadow="xl" bg="rgba(10, 20, 36, 0.86)">
|
||||
<Stack gap="lg">
|
||||
<div>
|
||||
<Text
|
||||
tt="uppercase"
|
||||
fw={700}
|
||||
fz="xs"
|
||||
c="blue.3"
|
||||
mb="sm"
|
||||
style={{ letterSpacing: '0.18em' }}
|
||||
>
|
||||
{m.auth__eyebrow()}
|
||||
</Text>
|
||||
<Title order={1}>{props.title}</Title>
|
||||
<Text c="dimmed" mt="sm" maw={440}>
|
||||
{props.description}
|
||||
</Text>
|
||||
</div>
|
||||
|
||||
<form onSubmit={props.onSubmit}>
|
||||
<Stack gap="md">
|
||||
<TextInput
|
||||
label={m.common__email()}
|
||||
placeholder={m.common__email_placeholder()}
|
||||
value={props.email}
|
||||
onChange={(event) => props.onEmailChange(event.currentTarget.value)}
|
||||
required
|
||||
radius="md"
|
||||
type="email"
|
||||
autoComplete="email"
|
||||
/>
|
||||
<PasswordInput
|
||||
label={m.common__password()}
|
||||
placeholder={m.common__password_placeholder()}
|
||||
value={props.password}
|
||||
onChange={(event) => props.onPasswordChange(event.currentTarget.value)}
|
||||
required
|
||||
radius="md"
|
||||
autoComplete={props.passwordAutoComplete}
|
||||
/>
|
||||
<Button type="submit" loading={props.busy} radius="md" size="md">
|
||||
{props.cta}
|
||||
</Button>
|
||||
</Stack>
|
||||
</form>
|
||||
|
||||
{props.message ? <Text c="teal.3">{props.message}</Text> : null}
|
||||
{props.error ? <Text c="red.3">{props.error}</Text> : null}
|
||||
|
||||
<Text c="dimmed" size="sm">
|
||||
{props.footer}
|
||||
</Text>
|
||||
</Stack>
|
||||
</Card>
|
||||
</Container>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user