init
This commit is contained in:
118
src/features/admin/components/create-tournament.tsx
Normal file
118
src/features/admin/components/create-tournament.tsx
Normal file
@@ -0,0 +1,118 @@
|
||||
import { Stack, TextInput, Textarea } from "@mantine/core";
|
||||
import { useForm, UseFormInput } from "@mantine/form";
|
||||
import { LinkIcon } from "@phosphor-icons/react";
|
||||
import SlidePanel, { SlidePanelField } from "@/components/sheet/slide-panel";
|
||||
import { TournamentFormInput } from "@/features/tournaments/types";
|
||||
import { DateTimePicker } from "./date-time-picker";
|
||||
import { isNotEmpty } from "@mantine/form";
|
||||
import useCreateTournament from "../hooks/use-create-tournament";
|
||||
|
||||
const CreateTournament = ({ close }: { close: () => void }) => {
|
||||
|
||||
const config: UseFormInput<TournamentFormInput> = {
|
||||
initialValues: { // TODO : Remove fake initial values
|
||||
name: 'Test Tournament',
|
||||
location: 'Test Location',
|
||||
desc: 'Test Description',
|
||||
logo_url: 'https://en.wikipedia.org/wiki/Trophy#/media/File:1934_Melbourne_Cup,_National_Museum_of_Australia.jpg',
|
||||
start_time: '2025-01-01T00:00:00Z',
|
||||
enroll_time: '2025-01-01T00:00:00Z',
|
||||
},
|
||||
onSubmitPreventDefault: 'always',
|
||||
validate: {
|
||||
name: isNotEmpty('Name is required'),
|
||||
location: isNotEmpty('Location is required'),
|
||||
start_time: isNotEmpty('Start time is required'),
|
||||
enroll_time: isNotEmpty('Enrollment time is required'),
|
||||
}
|
||||
}
|
||||
|
||||
const form = useForm(config);
|
||||
|
||||
const { mutate: createTournament, isPending } = useCreateTournament();
|
||||
|
||||
const handleSubmit = async (values: TournamentFormInput) => {
|
||||
createTournament(values, {
|
||||
onSuccess: () => {
|
||||
close();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<SlidePanel
|
||||
onSubmit={form.onSubmit(handleSubmit)}
|
||||
onCancel={close}
|
||||
submitText="Create Tournament"
|
||||
cancelText="Cancel"
|
||||
loading={isPending}
|
||||
>
|
||||
<Stack>
|
||||
<TextInput
|
||||
label="Name"
|
||||
withAsterisk
|
||||
key={form.key('name')}
|
||||
{...form.getInputProps('name')}
|
||||
/>
|
||||
<TextInput
|
||||
label="Location"
|
||||
withAsterisk
|
||||
key={form.key('location')}
|
||||
{...form.getInputProps('location')}
|
||||
/>
|
||||
|
||||
<TextInput
|
||||
label="Short Description"
|
||||
key={form.key('desc')}
|
||||
{...form.getInputProps('desc')}
|
||||
/>
|
||||
|
||||
<TextInput
|
||||
key={form.key('logo_url')}
|
||||
accept="image/*"
|
||||
label="Logo"
|
||||
leftSection={<LinkIcon size={16} />}
|
||||
{...form.getInputProps('logo_url')}
|
||||
/>
|
||||
|
||||
<SlidePanelField
|
||||
key={form.key('start_time')}
|
||||
{...form.getInputProps('start_time')}
|
||||
Component={DateTimePicker}
|
||||
title="Select Start Date"
|
||||
label="Start Date"
|
||||
withAsterisk
|
||||
formatValue={(date) => new Date(date).toLocaleDateString('en-US', {
|
||||
weekday: 'short',
|
||||
year: 'numeric',
|
||||
month: 'short',
|
||||
day: 'numeric',
|
||||
hour: 'numeric',
|
||||
minute: 'numeric',
|
||||
hour12: true
|
||||
})}
|
||||
/>
|
||||
|
||||
<SlidePanelField
|
||||
key={form.key('enroll_time')}
|
||||
{...form.getInputProps('enroll_time')}
|
||||
Component={DateTimePicker}
|
||||
title="Select Enrollment Due Date"
|
||||
label="Enrollment Due"
|
||||
withAsterisk
|
||||
formatValue={(date) => new Date(date).toLocaleDateString('en-US', {
|
||||
weekday: 'short',
|
||||
year: 'numeric',
|
||||
month: 'short',
|
||||
day: 'numeric',
|
||||
hour: 'numeric',
|
||||
minute: 'numeric',
|
||||
hour12: true
|
||||
})}
|
||||
/>
|
||||
</Stack>
|
||||
</SlidePanel>
|
||||
);
|
||||
};
|
||||
|
||||
export default CreateTournament;
|
||||
90
src/features/admin/components/date-time-picker.tsx
Normal file
90
src/features/admin/components/date-time-picker.tsx
Normal file
@@ -0,0 +1,90 @@
|
||||
import { DatePicker, TimeInput } from "@mantine/dates";
|
||||
import { ActionIcon, Stack } from "@mantine/core";
|
||||
import { useRef } from "react";
|
||||
import { ClockIcon } from "@phosphor-icons/react";
|
||||
|
||||
interface DateTimePickerProps {
|
||||
value: Date | null;
|
||||
onChange: (date: string | null) => void;
|
||||
label?: string;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
const DateTimePicker = ({ value, onChange, label, ...rest }: DateTimePickerProps) => {
|
||||
const timeRef = useRef<HTMLInputElement>(null);
|
||||
const currentDate = value ? new Date(value) : null;
|
||||
|
||||
const formatDate = (date: Date | null): string => {
|
||||
if (!date) return "";
|
||||
return date.toISOString().split('T')[0];
|
||||
};
|
||||
|
||||
const formatTime = (date: Date | null): string => {
|
||||
if (!date) return "";
|
||||
return date.toTimeString().slice(0, 5);
|
||||
};
|
||||
|
||||
const handleDateChange = (dateString: string | null) => {
|
||||
if (!dateString) {
|
||||
onChange('');
|
||||
return;
|
||||
}
|
||||
|
||||
const newDate = new Date(dateString + 'T00:00:00');
|
||||
|
||||
if (currentDate) {
|
||||
newDate.setHours(currentDate.getHours());
|
||||
newDate.setMinutes(currentDate.getMinutes());
|
||||
}
|
||||
|
||||
onChange(newDate.toISOString());
|
||||
};
|
||||
|
||||
const handleTimeChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const timeValue = event.target.value;
|
||||
if (!timeValue) return;
|
||||
|
||||
const [hours, minutes] = timeValue.split(':').map(Number);
|
||||
if (isNaN(hours) || isNaN(minutes)) return;
|
||||
|
||||
const baseDate = currentDate || new Date();
|
||||
const newDate = new Date(baseDate);
|
||||
|
||||
newDate.setHours(hours);
|
||||
newDate.setMinutes(minutes);
|
||||
newDate.setSeconds(0);
|
||||
newDate.setMilliseconds(0);
|
||||
|
||||
onChange(newDate.toISOString());
|
||||
};
|
||||
|
||||
return (
|
||||
<Stack>
|
||||
<DatePicker
|
||||
size="md"
|
||||
value={formatDate(currentDate)}
|
||||
onChange={handleDateChange}
|
||||
{...rest}
|
||||
/>
|
||||
<TimeInput
|
||||
ref={timeRef}
|
||||
label="Time"
|
||||
size="md"
|
||||
value={formatTime(currentDate)}
|
||||
onChange={handleTimeChange}
|
||||
rightSection={
|
||||
<ActionIcon
|
||||
variant="subtle"
|
||||
color="gray"
|
||||
onClick={() => timeRef.current?.showPicker()}
|
||||
>
|
||||
<ClockIcon size={16} />
|
||||
</ActionIcon>
|
||||
}
|
||||
{...rest}
|
||||
/>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
export { DateTimePicker };
|
||||
37
src/features/admin/hooks/use-create-tournament.ts
Normal file
37
src/features/admin/hooks/use-create-tournament.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
import { useMutation } from "@tanstack/react-query";
|
||||
import { useNavigate } from "@tanstack/react-router";
|
||||
import { createTournament } from "@/features/tournaments/server";
|
||||
import toast from '@/lib/sonner';
|
||||
import { TournamentInput } from "@/features/tournaments/types";
|
||||
import { logger } from "../";
|
||||
|
||||
const useCreateTournament = () => {
|
||||
const navigate = useNavigate();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: (data: TournamentInput) => createTournament({ data }),
|
||||
onMutate: (data) => {
|
||||
logger.info('Creating tournament', data);
|
||||
},
|
||||
onSuccess: (data) => {
|
||||
if (!data) {
|
||||
toast.error('There was an issue creating your tournament. Please try again later.');
|
||||
logger.error('Error creating tournament', data);
|
||||
} else {
|
||||
toast.success('Tournament created successfully!');
|
||||
logger.info('Tournament created successfully', data);
|
||||
navigate({ to: '/tournaments' });
|
||||
}
|
||||
},
|
||||
onError: (error: any) => {
|
||||
logger.error('Error creating tournament', error);
|
||||
if (error.message) {
|
||||
toast.error(error.message);
|
||||
} else {
|
||||
toast.error('An unexpected error occurred when trying to create a tournament. Please try again later.');
|
||||
}
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export default useCreateTournament;
|
||||
3
src/features/admin/index.ts
Normal file
3
src/features/admin/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
import { Logger } from "@/lib/logger";
|
||||
|
||||
export const logger = new Logger('Admin');
|
||||
Reference in New Issue
Block a user