diff --git a/src/app/[locale]/admin/facilities/[facility_id]/competitions/[competition_id]/CompetitionDetailComponent.tsx b/src/app/[locale]/admin/facilities/[facility_id]/competitions/[competition_id]/CompetitionDetailComponent.tsx
index 22db764..d416da3 100644
--- a/src/app/[locale]/admin/facilities/[facility_id]/competitions/[competition_id]/CompetitionDetailComponent.tsx
+++ b/src/app/[locale]/admin/facilities/[facility_id]/competitions/[competition_id]/CompetitionDetailComponent.tsx
@@ -18,6 +18,7 @@ import {
ListOrdered,
FileText,
CalendarClock,
+ Trash2,
} from 'lucide-react';
import useTranslation from '@/src/hooks/useTranslation';
import Card from '@/src/components/cards/Card';
@@ -28,7 +29,9 @@ import {
useStartCompetition,
useFinishCompetition,
useCancelCompetition,
+ useDeleteCompetition,
} from '@/src/hooks/mutations/useCompetitionMutations';
+import { useRouter } from 'next/navigation';
import OverviewTab from './tabs/OverviewTab';
import RegistrationsTab from './tabs/RegistrationsTab';
import ParticipantsTab from './tabs/ParticipantsTab';
@@ -71,20 +74,76 @@ function formatDate(dateString: string): string {
function StatusTransitionButton({
status,
competitionId,
+ facilityId,
+ locale,
}: {
status: CompetitionStatus;
competitionId: number;
+ facilityId: number;
+ locale: string;
}) {
+ const router = useRouter();
const publishMutation = usePublishCompetition(competitionId);
const startMutation = useStartCompetition(competitionId);
const finishMutation = useFinishCompetition(competitionId);
const cancelMutation = useCancelCompetition(competitionId);
+ const deleteMutation = useDeleteCompetition(competitionId);
+ const [showDeleteConfirm, setShowDeleteConfirm] = useState(false);
const isPending =
publishMutation.isPending ||
startMutation.isPending ||
finishMutation.isPending ||
- cancelMutation.isPending;
+ cancelMutation.isPending ||
+ deleteMutation.isPending;
+
+ const canDelete = status === 'draft' || status === 'cancelled';
+
+ const handleDelete = () => {
+ deleteMutation.mutate(undefined, {
+ onSuccess: () => {
+ router.push(`/${locale}/admin/facilities/${facilityId}/competitions`);
+ },
+ });
+ };
+
+ const DeleteButton = () => (
+ <>
+ {showDeleteConfirm ? (
+
+ Delete permanently?
+
+
+
+ ) : (
+
+ )}
+ >
+ );
if (status === 'draft') {
return (
@@ -101,6 +160,7 @@ function StatusTransitionButton({
)}
Publish
+
);
}
@@ -155,6 +215,15 @@ function StatusTransitionButton({
);
}
+ // For cancelled status, show delete button
+ if (canDelete) {
+ return (
+
+
+
+ );
+ }
+
return null;
}
@@ -234,7 +303,7 @@ export default function CompetitionDetailComponent({
Save as Template
-
+
diff --git a/src/hooks/mutations/useCompetitionMutations.ts b/src/hooks/mutations/useCompetitionMutations.ts
index 27310d5..d51f020 100644
--- a/src/hooks/mutations/useCompetitionMutations.ts
+++ b/src/hooks/mutations/useCompetitionMutations.ts
@@ -8,6 +8,7 @@ import {
startCompetition,
finishCompetition,
cancelCompetition,
+ deleteCompetition,
saveCompetitionAsTemplate,
approveRegistration,
rejectRegistration,
@@ -237,6 +238,30 @@ export function useCancelCompetition(competitionId: number) {
});
}
+/**
+ * Delete a competition (draft or cancelled only)
+ */
+export function useDeleteCompetition(competitionId: number) {
+ const queryClient = useQueryClient();
+
+ return useMutation({
+ mutationFn: async () => {
+ const result = await deleteCompetition(competitionId);
+ if (!result.success) {
+ throw new Error(result.error.detail);
+ }
+ return result.data;
+ },
+ onSuccess: () => {
+ queryClient.invalidateQueries({ queryKey: competitionQueryKeys.lists() });
+ // Remove the detail query since the competition no longer exists
+ queryClient.removeQueries({
+ queryKey: competitionQueryKeys.detail(competitionId),
+ });
+ },
+ });
+}
+
/**
* Save competition as template
*/
diff --git a/src/lib/api/competition-admin.ts b/src/lib/api/competition-admin.ts
index 5bd2471..d08f5d6 100644
--- a/src/lib/api/competition-admin.ts
+++ b/src/lib/api/competition-admin.ts
@@ -412,6 +412,25 @@ export async function cancelCompetition(
}
}
+/**
+ * DELETE /competitions/{competition_id}
+ * Permanently delete a competition (draft or cancelled only)
+ */
+export async function deleteCompetition(
+ competitionId: number
+): Promise> {
+ try {
+ const response = await apiFetch(`/competitions/${competitionId}`, {
+ method: 'DELETE',
+ headers: { 'Content-Type': 'application/json' },
+ });
+
+ return handleApiResponse(response);
+ } catch (error) {
+ return buildNetworkError(error instanceof Error ? error.message : 'Failed to delete competition');
+ }
+}
+
/**
* POST /competitions/{competition_id}/save-as-template
* Create a template from an existing competition's config