207 lines
7.8 KiB
TypeScript
207 lines
7.8 KiB
TypeScript
import { FC, useEffect, useState } from 'react';
|
||
import { Modal } from '../../../components/modal/Modal';
|
||
import { PrimaryButton } from '../../../components/button/PrimaryButton';
|
||
import { SecondaryButton } from '../../../components/button/SecondaryButton';
|
||
import { Input } from '../../../components/input/Input';
|
||
import { useAppDispatch, useAppSelector } from '../../../redux/hooks';
|
||
import {
|
||
createContest,
|
||
setContestStatus,
|
||
} from '../../../redux/slices/contests';
|
||
import { CreateContestBody } from '../../../redux/slices/contests';
|
||
import DateRangeInput from '../../../components/input/DateRangeInput';
|
||
import { useNavigate } from 'react-router-dom';
|
||
|
||
interface ModalCreateContestProps {
|
||
active: boolean;
|
||
setActive: (value: boolean) => void;
|
||
}
|
||
|
||
const ModalCreateContest: FC<ModalCreateContestProps> = ({
|
||
active,
|
||
setActive,
|
||
}) => {
|
||
const dispatch = useAppDispatch();
|
||
const navigate = useNavigate();
|
||
const status = useAppSelector(
|
||
(state) => state.contests.createContest.status,
|
||
);
|
||
|
||
const [form, setForm] = useState<CreateContestBody>({
|
||
name: '',
|
||
description: '',
|
||
scheduleType: 'AlwaysOpen',
|
||
visibility: 'Public',
|
||
startsAt: '',
|
||
endsAt: '',
|
||
attemptDurationMinutes: 0,
|
||
maxAttempts: 0,
|
||
allowEarlyFinish: false,
|
||
missionIds: [],
|
||
articleIds: [],
|
||
});
|
||
|
||
const contest = useAppSelector(
|
||
(state) => state.contests.createContest.contest,
|
||
);
|
||
|
||
useEffect(() => {
|
||
if (status === 'successful') {
|
||
dispatch(
|
||
setContestStatus({ key: 'createContest', status: 'idle' }),
|
||
);
|
||
navigate(
|
||
`/contest/create?back=/home/account/contests&contestId=${contest.id}`,
|
||
);
|
||
}
|
||
}, [status]);
|
||
|
||
const handleChange = (key: keyof CreateContestBody, value: any) => {
|
||
setForm((prev) => ({ ...prev, [key]: value }));
|
||
};
|
||
|
||
const handleSubmit = () => {
|
||
dispatch(createContest(form));
|
||
};
|
||
|
||
return (
|
||
<Modal
|
||
className="bg-liquid-background border-liquid-lighter border-[2px] p-[25px] rounded-[20px] text-liquid-white"
|
||
onOpenChange={setActive}
|
||
open={active}
|
||
backdrop="blur"
|
||
>
|
||
<div className="w-[550px]">
|
||
<div className="font-bold text-[30px] mb-[10px]">
|
||
Создать контест
|
||
</div>
|
||
|
||
<Input
|
||
name="name"
|
||
type="text"
|
||
label="Название"
|
||
className="mt-[10px]"
|
||
placeholder="Введите название"
|
||
onChange={(v) => handleChange('name', v)}
|
||
/>
|
||
|
||
<Input
|
||
name="description"
|
||
type="text"
|
||
label="Описание"
|
||
className="mt-[10px]"
|
||
placeholder="Введите описание"
|
||
onChange={(v) => handleChange('description', v)}
|
||
/>
|
||
|
||
<div className="grid grid-cols-2 gap-[10px] mt-[10px]">
|
||
<div>
|
||
<label className="block text-sm mb-1">
|
||
Тип расписания
|
||
</label>
|
||
<select
|
||
className="w-full p-2 rounded-md bg-liquid-darker border border-liquid-lighter"
|
||
value={form.scheduleType}
|
||
onChange={(e) =>
|
||
handleChange(
|
||
'scheduleType',
|
||
e.target
|
||
.value as CreateContestBody['scheduleType'],
|
||
)
|
||
}
|
||
>
|
||
<option value="AlwaysOpen">Всегда открыт</option>
|
||
<option value="FixedWindow">
|
||
Фиксированные даты
|
||
</option>
|
||
<option value="RollingWindow">
|
||
Скользящее окно
|
||
</option>
|
||
</select>
|
||
</div>
|
||
|
||
<div>
|
||
<label className="block text-sm mb-1">Видимость</label>
|
||
<select
|
||
className="w-full p-2 rounded-md bg-liquid-darker border border-liquid-lighter"
|
||
value={form.visibility}
|
||
onChange={(e) =>
|
||
handleChange(
|
||
'visibility',
|
||
e.target
|
||
.value as CreateContestBody['visibility'],
|
||
)
|
||
}
|
||
>
|
||
<option value="Public">Публичный</option>
|
||
<option value="GroupPrivate">Групповой</option>
|
||
</select>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Даты начала и конца */}
|
||
<div className="grid grid-cols-2 gap-[10px] mt-[10px]">
|
||
<DateRangeInput
|
||
startValue={form.startsAt || ''}
|
||
endValue={form.endsAt || ''}
|
||
onChange={handleChange}
|
||
className="mt-[10px]"
|
||
/>
|
||
</div>
|
||
|
||
{/* Продолжительность и лимиты */}
|
||
<div className="grid grid-cols-2 gap-[10px] mt-[10px]">
|
||
<Input
|
||
name="attemptDurationMinutes"
|
||
type="number"
|
||
label="Длительность попытки (мин)"
|
||
placeholder="Например: 60"
|
||
onChange={(v) =>
|
||
handleChange('attemptDurationMinutes', Number(v))
|
||
}
|
||
/>
|
||
<Input
|
||
name="maxAttempts"
|
||
type="number"
|
||
label="Макс. попыток"
|
||
placeholder="Например: 3"
|
||
onChange={(v) => handleChange('maxAttempts', Number(v))}
|
||
/>
|
||
</div>
|
||
|
||
{/* Разрешить раннее завершение */}
|
||
<div className="flex items-center gap-[10px] mt-[15px]">
|
||
<input
|
||
id="allowEarlyFinish"
|
||
type="checkbox"
|
||
checked={!!form.allowEarlyFinish}
|
||
onChange={(e) =>
|
||
handleChange('allowEarlyFinish', e.target.checked)
|
||
}
|
||
/>
|
||
<label htmlFor="allowEarlyFinish">
|
||
Разрешить раннее завершение
|
||
</label>
|
||
</div>
|
||
|
||
{/* Кнопки */}
|
||
<div className="flex flex-row w-full items-center justify-end mt-[20px] gap-[20px]">
|
||
<PrimaryButton
|
||
onClick={() => {
|
||
handleSubmit();
|
||
}}
|
||
text="Создать"
|
||
disabled={status === 'loading'}
|
||
/>
|
||
<SecondaryButton
|
||
onClick={() => setActive(false)}
|
||
text="Отмена"
|
||
/>
|
||
</div>
|
||
</div>
|
||
</Modal>
|
||
);
|
||
};
|
||
|
||
export default ModalCreateContest;
|