From ceb15ceb2fd8b6ea9ca48767b5dd9a88f50ea7de Mon Sep 17 00:00:00 2001 From: ryzen_xp Date: Tue, 3 Mar 2026 08:53:54 +0530 Subject: [PATCH 1/3] fix(hackathons): route draft deletion to org-scoped endpoint --- .../organizations/[id]/hackathons/page.tsx | 4 +++- hooks/hackathon/use-delete-hackathon.ts | 13 +++++++++++- lib/api/hackathons/draft.ts | 20 +++++++++++++++++++ lib/api/hackathons/index.ts | 1 - 4 files changed, 35 insertions(+), 3 deletions(-) diff --git a/app/(landing)/organizations/[id]/hackathons/page.tsx b/app/(landing)/organizations/[id]/hackathons/page.tsx index 699c39057..18e44b269 100644 --- a/app/(landing)/organizations/[id]/hackathons/page.tsx +++ b/app/(landing)/organizations/[id]/hackathons/page.tsx @@ -87,6 +87,7 @@ export default function HackathonsPage() { const [hackathonToDelete, setHackathonToDelete] = useState<{ id: string; title: string; + type: 'draft' | 'hackathon'; } | null>(null); const { hackathons, hackathonsLoading, drafts, draftsLoading, refetchAll } = @@ -99,6 +100,7 @@ export default function HackathonsPage() { const { isDeleting, deleteHackathon } = useDeleteHackathon({ organizationId, hackathonId: hackathonToDelete?.id || '', // This will be set when we have a hackathon to delete + type: hackathonToDelete?.type ?? 'hackathon', onSuccess: () => { // Refresh the hackathons list after successful deletion refetchAll(); @@ -186,7 +188,7 @@ export default function HackathonsPage() { ? (hackathon.data as HackathonDraft).data.information?.name || 'Untitled Hackathon' : (hackathon.data as Hackathon).name || 'Untitled Hackathon'; - setHackathonToDelete({ id: hackathonId, title }); + setHackathonToDelete({ id: hackathonId, title, type: hackathon.type }); setDeleteDialogOpen(true); } }; diff --git a/hooks/hackathon/use-delete-hackathon.ts b/hooks/hackathon/use-delete-hackathon.ts index 4ef6d5780..ef04d4c1b 100644 --- a/hooks/hackathon/use-delete-hackathon.ts +++ b/hooks/hackathon/use-delete-hackathon.ts @@ -2,12 +2,16 @@ import { useState, useCallback } from 'react'; import { deleteHackathon } from '@/lib/api/hackathons'; +import { deleteDraft } from '@/lib/api/hackathons/draft'; import { useAuthStatus } from '@/hooks/use-auth'; import { toast } from 'sonner'; +type DeleteType = 'draft' | 'hackathon'; + interface UseDeleteHackathonOptions { organizationId: string; hackathonId: string; + type: DeleteType; onSuccess?: () => void; onError?: (error: string) => void; } @@ -15,6 +19,7 @@ interface UseDeleteHackathonOptions { export function useDeleteHackathon({ organizationId, hackathonId, + type, onSuccess, onError, }: UseDeleteHackathonOptions) { @@ -37,7 +42,13 @@ export function useDeleteHackathon({ setError(null); try { - const response = await deleteHackathon(hackathonId); + let response; + + if (type === 'draft') { + response = await deleteDraft(hackathonId, organizationId); + } else { + response = await deleteHackathon(hackathonId); + } if (response.success) { toast.success('Hackathon deleted successfully'); diff --git a/lib/api/hackathons/draft.ts b/lib/api/hackathons/draft.ts index d0bf77c8a..3ae630668 100644 --- a/lib/api/hackathons/draft.ts +++ b/lib/api/hackathons/draft.ts @@ -38,6 +38,12 @@ export interface PublishHackathonResponse extends ApiResponse { message: string; } +export interface DeleteHackathonResponse extends ApiResponse { + success: true; + message: string; + data: null; +} + /** * Initialize a new hackathon draft */ @@ -86,6 +92,20 @@ export const publishDraft = async ( return res.data.data as PublishHackathonResponse; }; +/** + * Delete Draft Hackathon + */ +export const deleteDraft = async ( + draftId: string, + organizationId: string +): Promise => { + const res = await api.delete( + `/organizations/${organizationId}/hackathons/draft/${draftId}` + ); + + return res.data; +}; + /** * Get a single hackathon draft by ID */ diff --git a/lib/api/hackathons/index.ts b/lib/api/hackathons/index.ts index d3c569839..fb3657aa2 100644 --- a/lib/api/hackathons/index.ts +++ b/lib/api/hackathons/index.ts @@ -72,7 +72,6 @@ export { // Re-export all API functions and response types export * from './core'; -export * from './draft'; export * from './participants'; export * from './judging'; export * from './rewards'; From a8a1c78205a450bd751b70c1ee09a71ac04d5484 Mon Sep 17 00:00:00 2001 From: ryzen_xp Date: Wed, 4 Mar 2026 09:09:44 +0530 Subject: [PATCH 2/3] fix(hackathons): fix delete flow toast duplication and stale state issues --- .../organizations/[id]/hackathons/page.tsx | 65 +++++++++---------- hooks/hackathon/use-delete-hackathon.ts | 2 +- lib/api/hackathons/draft.ts | 6 +- 3 files changed, 38 insertions(+), 35 deletions(-) diff --git a/app/(landing)/organizations/[id]/hackathons/page.tsx b/app/(landing)/organizations/[id]/hackathons/page.tsx index 78ed47082..5ebb7f4f7 100644 --- a/app/(landing)/organizations/[id]/hackathons/page.tsx +++ b/app/(landing)/organizations/[id]/hackathons/page.tsx @@ -102,9 +102,6 @@ export default function HackathonsPage() { onSuccess: () => { // Refresh the hackathons list after successful deletion refetchAll(); - toast.success('Hackathon deleted successfully', { - description: `"${hackathonToDelete?.title}" has been permanently deleted.`, - }); }, onError: error => { toast.error('Failed to delete hackathon', { @@ -167,45 +164,47 @@ export default function HackathonsPage() { return { published, drafts: drafts.length, total }; }, [hackathons, drafts]); - const handleDeleteClick = (hackathonId: string) => { - const hackathon = allHackathons.find(item => item.data.id === hackathonId); - if (hackathon) { - const title = - hackathon.type === 'draft' - ? (hackathon.data as HackathonDraft).data.information?.name || - 'Untitled Hackathon' - : (hackathon.data as Hackathon).name || 'Untitled Hackathon'; - setHackathonToDelete({ id: hackathonId, title, type: hackathon.type }); - // Try to find in published hackathons - const published = publishedHackathons.find(h => h.id === hackathonId); - if (published) { - setHackathonToDelete({ - id: hackathonId, - title: published.name || 'Untitled Hackathon', - }); - setDeleteDialogOpen(true); - return; - } - // Try to find in drafts - const draft = draftHackathons.find(d => d.id === hackathonId); - if (draft) { - setHackathonToDelete({ - id: hackathonId, - title: draft.data.information?.name || 'Untitled Hackathon', - }); - setDeleteDialogOpen(true); + const handleDeleteClick = ( + hackathonId: string, + type: 'draft' | 'hackathon' + ) => { + if (type === 'hackathon') { + const published = publishedHackathons.find(h => h.id === hackathonId); + if (published) { + setHackathonToDelete({ + id: hackathonId, + title: published.name || 'Untitled Hackathon', + type: 'hackathon', + }); + setDeleteDialogOpen(true); + } + } else { + const draft = draftHackathons.find(d => d.id === hackathonId); + if (draft) { + setHackathonToDelete({ + id: hackathonId, + title: draft.data.information?.name || 'Untitled Hackathon', + type: 'draft', + }); + setDeleteDialogOpen(true); + } } }; const handleDeleteConfirm = async () => { if (!hackathonToDelete) return; + const { title } = hackathonToDelete; // ✅ snapshot before state clears + setDeleteDialogOpen(false); try { await deleteHackathon(); + toast.success('Hackathon deleted successfully', { + description: `"${title}" has been permanently deleted.`, + }); } catch { - // Error handled by toast in deleteHackathon hook + // error toast handled by onError in hook } finally { setHackathonToDelete(null); } @@ -475,7 +474,7 @@ export default function HackathonsPage() {