Files
cloudflare-domain-manager/apps/web/src/components/login-form.tsx
T
Denozordec b1467575c5
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
Refactor project structure to use pnpm monorepo; update Dockerfile and related configurations for frontend build process. Adjust .dockerignore and .gitignore to reflect new paths. Modify .env.example for cron job timing. Update CONTRIBUTING.md and README.md for new development instructions.
2026-06-15 15:37:36 +07:00

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>
)
}