Build, Test, and Push CFDM Docker Image / test (push) Failing after 3h13m2s
Build, Test, and Push CFDM Docker Image / create-release (push) Has been cancelled
Build, Test, and Push CFDM Docker Image / build-and-push (push) Has been cancelled
Build, Test, and Push CFDM Docker Image / update-wiki (push) Has been cancelled
102 lines
3.5 KiB
TypeScript
102 lines
3.5 KiB
TypeScript
import { useForm } from 'react-hook-form'
|
|
import { zodResolver } from '@hookform/resolvers/zod'
|
|
import { useNavigate } from '@tanstack/react-router'
|
|
import { CircleAlertIcon } from 'lucide-react'
|
|
import { cn } from '@cfdm/ui/lib/utils'
|
|
import { api } from '@/lib/api-client'
|
|
import { setToken } from '@/lib/auth'
|
|
import { loginSchema, type LoginInput } from '@/lib/schemas'
|
|
import { Button } from '@cfdm/ui/components/button'
|
|
import {
|
|
Card,
|
|
CardContent,
|
|
CardDescription,
|
|
CardHeader,
|
|
CardTitle,
|
|
} from '@cfdm/ui/components/card'
|
|
import {
|
|
Field,
|
|
FieldGroup,
|
|
FieldLabel,
|
|
} from '@cfdm/ui/components/field'
|
|
import { Input } from '@cfdm/ui/components/input'
|
|
import { Alert, AlertDescription, AlertTitle } from '@cfdm/ui/components/alert'
|
|
import { Spinner } from '@cfdm/ui/components/spinner'
|
|
|
|
type LoginFormProps = React.ComponentProps<'div'>
|
|
|
|
export function LoginForm({ className, ...props }: LoginFormProps) {
|
|
const navigate = useNavigate()
|
|
const form = useForm<LoginInput>({
|
|
resolver: zodResolver(loginSchema),
|
|
defaultValues: { username: 'admin', password: 'admin' },
|
|
})
|
|
|
|
const handleSubmit = form.handleSubmit(async (values) => {
|
|
try {
|
|
const res = await api.post<{ token: string }>('/api/v1/auth/login', values)
|
|
setToken(res.token)
|
|
navigate({ to: '/' })
|
|
} catch (err) {
|
|
form.setError('root', {
|
|
message: err instanceof Error ? err.message : 'Не удалось войти',
|
|
})
|
|
}
|
|
})
|
|
|
|
const rootError = form.formState.errors.root?.message
|
|
const isLoading = form.formState.isSubmitting
|
|
|
|
return (
|
|
<div className={cn('flex flex-col gap-6', className)} {...props}>
|
|
<Card>
|
|
<CardHeader className="text-center">
|
|
<CardTitle className="text-xl">Вход в систему</CardTitle>
|
|
<CardDescription>
|
|
Введите учётные данные для доступа к панели управления
|
|
</CardDescription>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<form onSubmit={handleSubmit}>
|
|
<FieldGroup>
|
|
<Field>
|
|
<FieldLabel htmlFor="username">Имя пользователя</FieldLabel>
|
|
<Input
|
|
id="username"
|
|
placeholder="admin"
|
|
autoComplete="username"
|
|
{...form.register('username')}
|
|
aria-invalid={!!form.formState.errors.username}
|
|
/>
|
|
</Field>
|
|
<Field>
|
|
<FieldLabel htmlFor="password">Пароль</FieldLabel>
|
|
<Input
|
|
id="password"
|
|
type="password"
|
|
autoComplete="current-password"
|
|
{...form.register('password')}
|
|
aria-invalid={!!form.formState.errors.password}
|
|
/>
|
|
</Field>
|
|
{rootError && (
|
|
<Alert variant="destructive">
|
|
<CircleAlertIcon />
|
|
<AlertTitle>Ошибка входа</AlertTitle>
|
|
<AlertDescription>{rootError}</AlertDescription>
|
|
</Alert>
|
|
)}
|
|
<Field>
|
|
<Button type="submit" className="w-full" disabled={isLoading}>
|
|
{isLoading && <Spinner data-icon="inline-start" />}
|
|
{isLoading ? 'Вход…' : 'Войти'}
|
|
</Button>
|
|
</Field>
|
|
</FieldGroup>
|
|
</form>
|
|
</CardContent>
|
|
</Card>
|
|
</div>
|
|
)
|
|
}
|