initial
This commit is contained in:
115
frontend/src/pages/CreateMarathonPage.tsx
Normal file
115
frontend/src/pages/CreateMarathonPage.tsx
Normal file
@@ -0,0 +1,115 @@
|
||||
import { useState } from 'react'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
import { useForm } from 'react-hook-form'
|
||||
import { zodResolver } from '@hookform/resolvers/zod'
|
||||
import { z } from 'zod'
|
||||
import { marathonsApi } from '@/api'
|
||||
import { Button, Input, Card, CardHeader, CardTitle, CardContent } from '@/components/ui'
|
||||
|
||||
const createSchema = z.object({
|
||||
title: z.string().min(1, 'Название обязательно').max(100),
|
||||
description: z.string().optional(),
|
||||
start_date: z.string().min(1, 'Дата начала обязательна'),
|
||||
duration_days: z.number().min(1).max(365).default(30),
|
||||
})
|
||||
|
||||
type CreateForm = z.infer<typeof createSchema>
|
||||
|
||||
export function CreateMarathonPage() {
|
||||
const navigate = useNavigate()
|
||||
const [isLoading, setIsLoading] = useState(false)
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
|
||||
const {
|
||||
register,
|
||||
handleSubmit,
|
||||
formState: { errors },
|
||||
} = useForm<CreateForm>({
|
||||
resolver: zodResolver(createSchema),
|
||||
defaultValues: {
|
||||
duration_days: 30,
|
||||
},
|
||||
})
|
||||
|
||||
const onSubmit = async (data: CreateForm) => {
|
||||
setIsLoading(true)
|
||||
setError(null)
|
||||
try {
|
||||
const marathon = await marathonsApi.create({
|
||||
...data,
|
||||
start_date: new Date(data.start_date).toISOString(),
|
||||
})
|
||||
navigate(`/marathons/${marathon.id}/lobby`)
|
||||
} catch (err: unknown) {
|
||||
const apiError = err as { response?: { data?: { detail?: string } } }
|
||||
setError(apiError.response?.data?.detail || 'Не удалось создать марафон')
|
||||
} finally {
|
||||
setIsLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="max-w-lg mx-auto">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Создать марафон</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<form onSubmit={handleSubmit(onSubmit)} className="space-y-4">
|
||||
{error && (
|
||||
<div className="p-3 bg-red-500/20 border border-red-500 rounded-lg text-red-500 text-sm">
|
||||
{error}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<Input
|
||||
label="Название"
|
||||
placeholder="Введите название марафона"
|
||||
error={errors.title?.message}
|
||||
{...register('title')}
|
||||
/>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-300 mb-1">
|
||||
Описание (необязательно)
|
||||
</label>
|
||||
<textarea
|
||||
className="input min-h-[100px] resize-none"
|
||||
placeholder="Введите описание"
|
||||
{...register('description')}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Input
|
||||
label="Дата начала"
|
||||
type="datetime-local"
|
||||
error={errors.start_date?.message}
|
||||
{...register('start_date')}
|
||||
/>
|
||||
|
||||
<Input
|
||||
label="Длительность (дней)"
|
||||
type="number"
|
||||
error={errors.duration_days?.message}
|
||||
{...register('duration_days', { valueAsNumber: true })}
|
||||
/>
|
||||
|
||||
<div className="flex gap-3 pt-4">
|
||||
<Button
|
||||
type="button"
|
||||
variant="secondary"
|
||||
className="flex-1"
|
||||
onClick={() => navigate('/marathons')}
|
||||
>
|
||||
Отмена
|
||||
</Button>
|
||||
<Button type="submit" className="flex-1" isLoading={isLoading}>
|
||||
Создать
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user