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
28 changes: 22 additions & 6 deletions app/api/hackathons/[id]/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,27 +113,35 @@ export async function DELETE(_request: NextRequest, { params }: RouteContext) {
try {
const { id } = await params

console.log('🗑️ DELETE request for hackathon:', id)

// Check authentication
const supabase = await createClient()
const { data: { user } } = await supabase.auth.getUser()
const { data: { user }, error: authError } = await supabase.auth.getUser()

if (!user) {
if (authError || !user) {
console.error('❌ Authentication error:', authError)
return NextResponse.json(
{ error: 'Unauthorized: Authentication required' },
{ status: 401 }
)
}

console.log('✅ User authenticated:', user.id)

// Get the existing hackathon to check company_id
const existingHackathon = await hackathonsService.getHackathonBySlug(id)

if (!existingHackathon) {
console.error('❌ Hackathon not found:', id)
return NextResponse.json(
{ error: 'Hackathon not found' },
{ status: 404 }
)
}

console.log('✅ Hackathon found:', existingHackathon.id, existingHackathon.title)

let isAuthorized = false

// Check if user is admin
Expand All @@ -145,6 +153,7 @@ export async function DELETE(_request: NextRequest, { params }: RouteContext) {

if (profile?.is_admin) {
isAuthorized = true
console.log('✅ User is admin')
}

// If not admin, check if user is a member of the company
Expand All @@ -159,23 +168,30 @@ export async function DELETE(_request: NextRequest, { params }: RouteContext) {

if (membership) {
isAuthorized = true
console.log('✅ User is company member with role:', membership.role)
}
}

if (!isAuthorized) {
console.error('❌ User not authorized to delete hackathon')
return NextResponse.json(
{ error: 'Unauthorized: You must be a company member or admin to delete this hackathon' },
{ status: 401 }
{ status: 403 }
)
}

console.log('🗑️ Attempting to delete hackathon...')
await hackathonsService.deleteHackathon(id)
console.log('✅ Hackathon deleted successfully')

return NextResponse.json({ message: 'Hackathon deleted successfully' })
return NextResponse.json({
success: true,
message: 'Hackathon deleted successfully'
})
} catch (error) {
console.error('Error in DELETE /api/hackathons/[id]:', error)
console.error('Error in DELETE /api/hackathons/[id]:', error)
return NextResponse.json(
{ error: 'Failed to delete hackathon' },
{ error: error instanceof Error ? error.message : 'Failed to delete hackathon' },
{ status: 500 }
)
}
Expand Down
85 changes: 82 additions & 3 deletions app/dashboard/company/[slug]/hackathons/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,19 @@ import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/com
import { Badge } from '@/components/ui/badge'
import { Input } from '@/components/ui/input'
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table'
import { Trophy, Search, Plus, Edit, Eye, Clock, CheckCircle, XCircle, AlertCircle } from 'lucide-react'
import { Trophy, Search, Plus, Edit, Eye, Clock, CheckCircle, XCircle, AlertCircle, Trash2 } from 'lucide-react'
import { toast } from 'sonner'
import Link from 'next/link'
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
} from '@/components/ui/alert-dialog'

interface Hackathon {
id: string
Expand All @@ -34,6 +44,9 @@ export default function CompanyHackathonsPage() {
const [hackathons, setHackathons] = useState<Hackathon[]>([])
const [loading, setLoading] = useState(true)
const [searchTerm, setSearchTerm] = useState('')
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false)
const [hackathonToDelete, setHackathonToDelete] = useState<Hackathon | null>(null)
const [isDeleting, setIsDeleting] = useState(false)

const canManageEvents = userRole && ['owner', 'admin', 'editor'].includes(userRole)

Expand Down Expand Up @@ -65,6 +78,40 @@ export default function CompanyHackathonsPage() {
}
}, [currentCompany, fetchHackathons])

const handleDeleteClick = (hackathon: Hackathon) => {
setHackathonToDelete(hackathon)
setDeleteDialogOpen(true)
}

const handleDeleteConfirm = async () => {
if (!hackathonToDelete || !currentCompany) return

try {
setIsDeleting(true)
// Use slug instead of id for the API endpoint
const response = await fetch(`/api/hackathons/${hackathonToDelete.slug}`, {
method: 'DELETE',
})

if (!response.ok) {
const errorData = await response.json()
throw new Error(errorData.error || 'Failed to delete hackathon')
}

toast.success('Hackathon deleted successfully')
setDeleteDialogOpen(false)
setHackathonToDelete(null)

// Refresh the list
fetchHackathons()
} catch (error) {
console.error('Error deleting hackathon:', error)
toast.error(error instanceof Error ? error.message : 'Failed to delete hackathon')
} finally {
setIsDeleting(false)
}
}

if (companyLoading || isPendingInvitation) {
return (
<div className="flex items-center justify-center min-h-[60vh]">
Expand Down Expand Up @@ -287,17 +334,26 @@ export default function CompanyHackathonsPage() {
<TableCell>
<div className="flex items-center gap-2">
<Link href={`/dashboard/company/${currentCompany.slug}/hackathons/${hackathon.slug}/edit`}>
<Button variant="outline" size="sm">
<Button variant="outline" size="sm" title="Edit hackathon">
<Edit className="h-4 w-4" />
</Button>
</Link>
{hackathon.approval_status === 'approved' && (
<Link href={`/hackathons/${hackathon.slug}`} target="_blank">
<Button variant="outline" size="sm">
<Button variant="outline" size="sm" title="View public page">
<Eye className="h-4 w-4" />
</Button>
</Link>
)}
<Button
variant="outline"
size="sm"
onClick={() => handleDeleteClick(hackathon)}
className="text-red-600 hover:text-red-700 hover:bg-red-50"
title="Delete hackathon"
>
<Trash2 className="h-4 w-4" />
</Button>
</div>
</TableCell>
) : (
Expand All @@ -318,6 +374,29 @@ export default function CompanyHackathonsPage() {
)}
</CardContent>
</Card>

{/* Delete Confirmation Dialog */}
<AlertDialog open={deleteDialogOpen} onOpenChange={setDeleteDialogOpen}>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>Are you sure?</AlertDialogTitle>
<AlertDialogDescription>
This will permanently delete the hackathon &quot;{hackathonToDelete?.title}&quot;.
This action cannot be undone.
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel disabled={isDeleting}>Cancel</AlertDialogCancel>
<AlertDialogAction
onClick={handleDeleteConfirm}
disabled={isDeleting}
className="bg-red-600 hover:bg-red-700"
>
{isDeleting ? 'Deleting...' : 'Delete'}
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
</div>
)
}
16 changes: 13 additions & 3 deletions lib/services/hackathons.ts
Original file line number Diff line number Diff line change
Expand Up @@ -310,16 +310,26 @@ class HackathonsService {
async deleteHackathon(slug: string) {
const supabase = await createClient()

const { error } = await supabase
console.log('🗑️ Deleting hackathon with slug:', slug)

const { data, error } = await supabase
.from('hackathons')
.delete()
.eq('slug', slug)
.select()

if (error) {
console.error('Error deleting hackathon:', error)
throw new Error('Failed to delete hackathon')
console.error('Error deleting hackathon from database:', error)
throw new Error(`Failed to delete hackathon: ${error.message}`)
}

if (!data || data.length === 0) {
console.warn('⚠️ No hackathon was deleted. Slug might not exist or RLS policy blocked deletion.')
throw new Error('Hackathon not found or you do not have permission to delete it')
}

console.log('✅ Hackathon deleted from database:', data)

// Clear cache after deleting hackathon
cache.clear()
return true
Expand Down
Loading