Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 36 additions & 26 deletions app/(landing)/organizations/[id]/hackathons/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ export default function HackathonsPage() {
const [hackathonToDelete, setHackathonToDelete] = useState<{
id: string;
title: string;
type: 'draft' | 'hackathon';
} | null>(null);

const { hackathons, hackathonsLoading, drafts, draftsLoading, refetchAll } =
Expand All @@ -96,13 +97,12 @@ export default function HackathonsPage() {
// Use the separate delete hook
const { isDeleting, deleteHackathon } = useDeleteHackathon({
organizationId,
hackathonId: hackathonToDelete?.id || '', // This will be set when we have a hackathon to delete
hackathonId: hackathonToDelete?.id || '',
type: hackathonToDelete?.type ?? 'hackathon',
suppressToast: true,
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', {
Expand Down Expand Up @@ -165,37 +165,47 @@ export default function HackathonsPage() {
return { published, drafts: drafts.length, total };
}, [hackathons, drafts]);

const handleDeleteClick = (hackathonId: string) => {
// 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);
}
Expand Down Expand Up @@ -465,7 +475,7 @@ export default function HackathonsPage() {
<button
onClick={e => {
e.stopPropagation();
handleDeleteClick(hackathon.id);
handleDeleteClick(hackathon.id, 'hackathon');
}}
className='flex h-9 w-9 items-center justify-center rounded-lg border border-zinc-800 bg-zinc-900/70 text-zinc-400 hover:border-red-600 hover:text-red-500'
title='Delete Hackathon'
Expand Down Expand Up @@ -581,7 +591,7 @@ export default function HackathonsPage() {
<button
onClick={e => {
e.stopPropagation();
handleDeleteClick(draft.id);
handleDeleteClick(draft.id, 'draft');
}}
className='flex h-9 w-9 items-center justify-center rounded-lg border border-zinc-800 bg-zinc-900/70 text-zinc-400 hover:border-red-600 hover:text-red-500'
title='Delete Draft'
Expand Down
25 changes: 21 additions & 4 deletions hooks/hackathon/use-delete-hackathon.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,28 @@

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;
suppressToast?: boolean;
}

export function useDeleteHackathon({
organizationId,
hackathonId,
type,
onSuccess,
onError,
suppressToast = false,
}: UseDeleteHackathonOptions) {
const { isAuthenticated } = useAuthStatus();
const [isDeleting, setIsDeleting] = useState(false);
Expand All @@ -37,10 +44,18 @@ 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');
if (!suppressToast) {
toast.success('Hackathon deleted successfully');
}
onSuccess?.();
return response.data;
} else {
Expand All @@ -50,13 +65,15 @@ export function useDeleteHackathon({
const errorMessage =
err instanceof Error ? err.message : 'Failed to delete hackathon';
setError(errorMessage);
toast.error(errorMessage);
if (!suppressToast) {
toast.error(errorMessage);
}
onError?.(errorMessage);
throw err;
} finally {
setIsDeleting(false);
}
}, [organizationId, hackathonId, isAuthenticated, onSuccess, onError]);
}, [organizationId, hackathonId, isAuthenticated, type, onSuccess, onError]);

return {
isDeleting,
Expand Down
24 changes: 24 additions & 0 deletions lib/api/hackathons/draft.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,12 @@ export interface PublishHackathonResponse extends ApiResponse<Hackathon> {
message: string;
}

export interface DeleteHackathonResponse extends ApiResponse<null> {
success: true;
message: string;
data: null;
}

/**
* Initialize a new hackathon draft
*/
Expand Down Expand Up @@ -86,6 +92,24 @@ export const publishDraft = async (
return res.data.data as PublishHackathonResponse;
};

/**
* Delete Draft Hackathon
*/
export const deleteDraft = async (
draftId: string,
organizationId: string
): Promise<DeleteHackathonResponse> => {
const res = await api.delete<DeleteHackathonResponse>(
`/organizations/${organizationId}/hackathons/draft/${draftId}`
);

if (res.status === 204) {
return { success: true, message: 'Deleted successfully', data: null };
}

return res.data as DeleteHackathonResponse;
};

/**
* Get a single hackathon draft by ID
*/
Expand Down
1 change: 0 additions & 1 deletion lib/api/hackathons/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,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';
Expand Down
Loading