significant refactor

This commit is contained in:
2025-08-30 01:42:23 -05:00
parent 7136f646a3
commit 052f53444e
106 changed files with 1994 additions and 1701 deletions

View File

@@ -2,106 +2,124 @@ 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 { TournamentFormInput } from "@/features/tournaments/types";
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 toast from "@/lib/sonner";
import { logger } from "..";
import { useQueryClient } from "@tanstack/react-query";
import { tournamentQueries } from "@/features/tournaments/queries";
import { tournamentKeys } from "@/features/tournaments/queries";
import { DateTimePicker } from "@mantine/dates";
import { useCallback } from "react";
interface TournamentFormProps {
close: () => void;
initialValues?: Partial<TournamentFormInput>;
initialValues?: Partial<TournamentInput>;
tournamentId?: string;
}
const TournamentForm = ({ close, initialValues, tournamentId }: TournamentFormProps) => {
const TournamentForm = ({
close,
initialValues,
tournamentId,
}: TournamentFormProps) => {
const isEditMode = !!tournamentId;
const config: UseFormInput<TournamentFormInput> = {
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 || '',
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',
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'),
}
}
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 { mutate: createTournament, isPending: createPending } =
useCreateTournament();
const { mutate: updateTournament, isPending: updatePending } =
useUpdateTournament(tournamentId || "");
const isPending = createPending || updatePending;
const handleSubmit = useCallback(async (values: TournamentFormInput) => {
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 handleSubmit = useCallback(
async (values: TournamentInput) => {
const { logo, ...tournamentData } = values;
const response = await fetch('/api/tournaments/upload-logo', {
method: 'POST',
body: formData,
});
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";
if (!response.ok) {
const error = await response.json();
throw new Error(error.error || 'Failed to upload logo');
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);
}
const result = await response.json();
queryClient.invalidateQueries({ queryKey: tournamentQueries.list().queryKey });
queryClient.invalidateQueries({ queryKey: tournamentQueries.details(result.tournament!.id).queryKey });
queryClient.setQueryData(
tournamentQueries.details(result.tournament!.id).queryKey,
result.tournament
);
} else {
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]);
close();
},
onError: (error: any) => {
toast.error(`${errorMessage}: ${error.message}`);
logger.error(
`Tournament ${isEditMode ? "update" : "create"} error`,
error
);
},
});
},
[isEditMode, createTournament, updateTournament, queryClient]
);
return (
<SlidePanel
@@ -115,83 +133,91 @@ const TournamentForm = ({ close, initialValues, tournamentId }: TournamentFormPr
<TextInput
label="Name"
withAsterisk
key={form.key('name')}
{...form.getInputProps('name')}
key={form.key("name")}
{...form.getInputProps("name")}
/>
<TextInput
label="Location"
withAsterisk
key={form.key('location')}
{...form.getInputProps('location')}
key={form.key("location")}
{...form.getInputProps("location")}
/>
<Textarea
label="Description"
key={form.key('desc')}
{...form.getInputProps('desc')}
key={form.key("desc")}
{...form.getInputProps("desc")}
minRows={3}
/>
<FileInput
key={form.key('logo')}
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')}
{...form.getInputProps("logo")}
/>
<SlidePanelField
key={form.key('start_time')}
{...form.getInputProps('start_time')}
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
})}
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')}
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
})}
formatValue={(date) =>
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')}
key={form.key("end_time")}
{...form.getInputProps("end_time")}
Component={DateTimePicker}
title="Select End Date"
label="End Date (Optional)"
formatValue={(date) => date ? new Date(date).toLocaleDateString('en-US', {
weekday: 'short',
year: 'numeric',
month: 'short',
day: 'numeric',
hour: 'numeric',
minute: 'numeric',
hour12: true
}) : 'Not set'}
formatValue={(date) =>
date
? new Date(date).toLocaleDateString("en-US", {
weekday: "short",
year: "numeric",
month: "short",
day: "numeric",
hour: "numeric",
minute: "numeric",
hour12: true,
})
: "Not set"
}
/>
)}
</Stack>
@@ -199,4 +225,4 @@ const TournamentForm = ({ close, initialValues, tournamentId }: TournamentFormPr
);
};
export default TournamentForm;
export default TournamentForm;