230 lines
7.2 KiB
TypeScript
230 lines
7.2 KiB
TypeScript
import { FileInput, 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 { TournamentInput } from "@/features/tournaments/types";
|
|
import { isNotEmpty } from "@mantine/form";
|
|
import useCreateTournament from "../hooks/use-create-tournament";
|
|
import useUpdateTournament from "../hooks/use-update-tournament";
|
|
import toast from "@/lib/sonner";
|
|
import { logger } from "..";
|
|
import { useQueryClient } from "@tanstack/react-query";
|
|
import { tournamentKeys } from "@/features/tournaments/queries";
|
|
import { useCallback } from "react";
|
|
import { DateTimePicker } from "@/components/date-time-picker";
|
|
|
|
interface TournamentFormProps {
|
|
close: () => void;
|
|
initialValues?: Partial<TournamentInput>;
|
|
tournamentId?: string;
|
|
}
|
|
|
|
const TournamentForm = ({
|
|
close,
|
|
initialValues,
|
|
tournamentId,
|
|
}: TournamentFormProps) => {
|
|
const isEditMode = !!tournamentId;
|
|
|
|
const config: UseFormInput<TournamentInput> = {
|
|
initialValues: {
|
|
name: initialValues?.name || "",
|
|
location: initialValues?.location || "",
|
|
desc: initialValues?.desc || "",
|
|
start_time: initialValues?.start_time || "",
|
|
enroll_time: initialValues?.enroll_time || "",
|
|
end_time: initialValues?.end_time || "",
|
|
logo: undefined,
|
|
},
|
|
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 queryClient = useQueryClient();
|
|
|
|
const { mutate: createTournament, isPending: createPending } =
|
|
useCreateTournament();
|
|
const { mutate: updateTournament, isPending: updatePending } =
|
|
useUpdateTournament(tournamentId || "");
|
|
|
|
const isPending = createPending || updatePending;
|
|
|
|
const handleSubmit = useCallback(
|
|
async (values: TournamentInput) => {
|
|
const { logo, ...tournamentData } = values;
|
|
|
|
const mutation = isEditMode ? updateTournament : createTournament;
|
|
const successMessage = isEditMode
|
|
? "Tournament updated successfully!"
|
|
: "Tournament created successfully!";
|
|
const errorMessage = isEditMode
|
|
? "Failed to update tournament"
|
|
: "Failed to create tournament";
|
|
|
|
mutation(tournamentData, {
|
|
onSuccess: async (tournament) => {
|
|
if (logo && tournament) {
|
|
try {
|
|
const formData = new FormData();
|
|
formData.append("tournamentId", tournament.id);
|
|
formData.append("logo", logo);
|
|
|
|
const response = await fetch("/api/tournaments/upload-logo", {
|
|
method: "POST",
|
|
body: formData,
|
|
});
|
|
|
|
if (!response.ok) {
|
|
const error = await response.json();
|
|
throw new Error(error.error || "Failed to upload logo");
|
|
}
|
|
|
|
const result = await response.json();
|
|
|
|
queryClient.invalidateQueries({ queryKey: tournamentKeys.list });
|
|
queryClient.invalidateQueries({
|
|
queryKey: tournamentKeys.details(result.tournament!.id),
|
|
});
|
|
queryClient.setQueryData(
|
|
tournamentKeys.details(result.tournament!.id),
|
|
result.tournament
|
|
);
|
|
|
|
toast.success(successMessage);
|
|
} catch (error: any) {
|
|
const logoErrorMessage = isEditMode
|
|
? `Tournament updated but logo upload failed: ${error.message}`
|
|
: `Tournament created but logo upload failed: ${error.message}`;
|
|
toast.error(logoErrorMessage);
|
|
logger.error("Tournament logo upload error", error);
|
|
}
|
|
} else {
|
|
toast.success(successMessage);
|
|
}
|
|
close();
|
|
},
|
|
onError: (error: any) => {
|
|
toast.error(`${errorMessage}: ${error.message}`);
|
|
logger.error(
|
|
`Tournament ${isEditMode ? "update" : "create"} error`,
|
|
error
|
|
);
|
|
},
|
|
});
|
|
},
|
|
[isEditMode, createTournament, updateTournament, queryClient]
|
|
);
|
|
|
|
return (
|
|
<SlidePanel
|
|
onSubmit={form.onSubmit(handleSubmit)}
|
|
onCancel={close}
|
|
submitText={isEditMode ? "Update Tournament" : "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")}
|
|
/>
|
|
|
|
<Textarea
|
|
label="Description"
|
|
key={form.key("desc")}
|
|
{...form.getInputProps("desc")}
|
|
minRows={3}
|
|
/>
|
|
|
|
<FileInput
|
|
key={form.key("logo")}
|
|
accept="image/png,image/jpeg,image/gif,image/jpg"
|
|
label={isEditMode ? "Change Logo" : "Logo"}
|
|
leftSection={<LinkIcon size={16} />}
|
|
{...form.getInputProps("logo")}
|
|
/>
|
|
|
|
<SlidePanelField
|
|
key={form.key("start_time")}
|
|
{...form.getInputProps("start_time")}
|
|
Component={DateTimePicker}
|
|
title="Select Start Date"
|
|
label="Start Date"
|
|
withAsterisk
|
|
formatValue={(date) =>
|
|
!date ? 'Select a time' :
|
|
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) =>
|
|
!date ? 'Select a time' :
|
|
new Date(date).toLocaleDateString("en-US", {
|
|
weekday: "short",
|
|
year: "numeric",
|
|
month: "short",
|
|
day: "numeric",
|
|
hour: "numeric",
|
|
minute: "numeric",
|
|
hour12: true,
|
|
})
|
|
}
|
|
/>
|
|
|
|
{isEditMode && (
|
|
<SlidePanelField
|
|
key={form.key("end_time")}
|
|
{...form.getInputProps("end_time")}
|
|
Component={DateTimePicker}
|
|
title="Select End Date"
|
|
label="End Date (Optional)"
|
|
formatValue={(date) =>
|
|
!date ? 'Select a time' :
|
|
new Date(date).toLocaleDateString("en-US", {
|
|
weekday: "short",
|
|
year: "numeric",
|
|
month: "short",
|
|
day: "numeric",
|
|
hour: "numeric",
|
|
minute: "numeric",
|
|
hour12: true,
|
|
})
|
|
}
|
|
/>
|
|
)}
|
|
</Stack>
|
|
</SlidePanel>
|
|
);
|
|
};
|
|
|
|
export default TournamentForm;
|