Files
test_task_crm/frontend/src/pages/auth/register-page.tsx
T
Artem Kashaev ede064cc11 feat: add initial implementation of Kitchen CRM with authentication and dashboard features
- Create global styles and theme management
- Implement app shell layout with sidebar navigation
- Add authentication layout and pages for login and registration
- Develop dashboard page with placeholder content
- Introduce routing guards for guest-only and authenticated routes
- Set up Zustand for state management of authentication and theme
- Create API types and structures for CRM entities
- Configure Vite with PWA support and Tailwind CSS
2025-12-01 12:29:02 +05:00

156 lines
6.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { zodResolver } from '@hookform/resolvers/zod'
import { useMutation } from '@tanstack/react-query'
import { Loader2 } from 'lucide-react'
import { useForm } from 'react-hook-form'
import { Link, useNavigate } from 'react-router-dom'
import { z } from 'zod'
import { Button } from '@/components/ui/button'
import { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage } from '@/components/ui/form'
import { Input } from '@/components/ui/input'
import { useToast } from '@/components/ui/use-toast'
import { register } from '@/features/auth/api'
import { HttpError } from '@/lib/api-client'
import { authSelectors, useAuthStore } from '@/stores/auth-store'
const registerSchema = z.object({
name: z.string().min(2, 'Введите имя владельца'),
email: z.string().email('Введите корректный email'),
password: z
.string()
.min(8, 'Минимальная длина пароля — 8 символов')
.regex(/[A-Za-z]/, 'Добавьте буквы')
.regex(/\d/, 'Добавьте цифры'),
organization_name: z
.string()
.transform((value) => value.trim())
.refine((value) => value.length <= 120, 'Название слишком длинное'),
})
type RegisterValues = z.infer<typeof registerSchema>
const RegisterPage = () => {
const form = useForm<RegisterValues>({
resolver: zodResolver(registerSchema),
defaultValues: { name: '', email: '', password: '', organization_name: '' },
})
const setSession = useAuthStore((state) => state.setSession)
const navigate = useNavigate()
const { toast } = useToast()
const { mutateAsync, isPending } = useMutation({
mutationFn: register,
})
const handleSubmit = async (values: RegisterValues) => {
form.clearErrors('root')
try {
const payload = {
...values,
organization_name: values.organization_name?.trim() ? values.organization_name.trim() : null,
}
const tokenResponse = await mutateAsync(payload)
const tokens = authSelectors.mapTokens(tokenResponse)
setSession({ tokens, organizations: [], activeOrganizationId: null })
toast({
title: 'Организация создана',
description: 'Вы владелец первой организации. Добавьте коллег на вкладке «Организации».',
})
navigate('/dashboard')
} catch (error) {
const message = error instanceof HttpError ? error.message : 'Не удалось завершить регистрацию'
form.setError('root', { message })
}
}
const rootError = form.formState.errors.root?.message
return (
<div className="space-y-6">
<div className="space-y-1">
<h2 className="text-2xl font-semibold">Создайте организацию</h2>
<p className="text-sm text-muted-foreground">Владелец получит полный доступ и сможет пригласить команду.</p>
</div>
<Form {...form}>
<form onSubmit={form.handleSubmit(handleSubmit)} className="space-y-5">
<FormField
control={form.control}
name="name"
render={({ field }) => (
<FormItem>
<FormLabel>Имя и фамилия</FormLabel>
<FormControl>
<Input placeholder="Алиса Менеджер" autoComplete="name" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="email"
render={({ field }) => (
<FormItem>
<FormLabel>Корпоративный email</FormLabel>
<FormControl>
<Input type="email" placeholder="owner@acme.io" autoComplete="email" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="password"
render={({ field }) => (
<FormItem>
<FormLabel>Пароль</FormLabel>
<FormControl>
<Input type="password" placeholder="Сложный пароль" autoComplete="new-password" {...field} />
</FormControl>
<FormDescription>Используйте минимум 8 символов, буквы и цифры.</FormDescription>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="organization_name"
render={({ field }) => (
<FormItem>
<FormLabel optional>Организация</FormLabel>
<FormControl>
<Input placeholder="Acme Inc" {...field} />
</FormControl>
<FormDescription>
Укажите, чтобы сразу создать компанию и стать владельцем. Можно пропустить и присоединиться позже.
</FormDescription>
<FormMessage />
</FormItem>
)}
/>
{rootError ? <p className="text-sm font-medium text-destructive">{rootError}</p> : null}
<Button type="submit" className="w-full" disabled={isPending}>
{isPending ? (
<span className="flex items-center justify-center gap-2">
<Loader2 className="h-4 w-4 animate-spin" />
Создаём
</span>
) : (
'Зарегистрироваться'
)}
</Button>
</form>
</Form>
<p className="text-center text-sm text-muted-foreground">
Уже есть аккаунт?{' '}
<Link to="/auth/login" className="font-medium text-primary hover:underline">
Войти
</Link>
</p>
</div>
)
}
export default RegisterPage