Files
euchre_camp/src/components/DeleteTournamentButton.tsx
T
david bb6be245b7
Release / release (push) Failing after 9s
Build CI Images / build-ci-base (push) Failing after 18s
feat: Implement tournament schedule tab and fix E2E tests (#27)
## Summary

This PR implements the tournament schedule tab functionality and fixes all remaining E2E test failures.

### Changes Included

1. **Tournament Schedule Feature**
   - Added tournament schedule page at `/admin/tournaments/[id]/schedule`
   - Implemented "Generate Schedule" button functionality
   - Added schedule generation logic for round-robin tournaments

2. **E2E Test Fixes**
   - Fixed database connection issues in production builds
   - Improved test reliability with better error handling and debugging
   - Updated test infrastructure to use environment variables instead of hardcoded values

3. **CI/CD Updates**
   - Added E2E test job to PR workflow
   - Configured tests to run against development database
   - Moved database password to Gitea secrets

4. **Code Quality**
   - Removed hardcoded passwords from codebase
   - Improved Prisma client configuration
   - Enhanced authentication and navigation components

### Test Results
All 16 E2E test scenarios are now passing:
- Authentication tests: 
- Registration tests: 
- Tournament schedule tests: 
- Player schedule tests: 
- Admin navigation tests: 

### Database Configuration
- Tests run against `euchre_camp_dev` database
- Production builds use environment variables for database configuration
- Database password stored in Gitea secrets as `DB_PASSWORD`

### CI Pipeline
The PR workflow now includes:
1. Unit tests
2. E2E tests (using production build)
3. Version bump analysis

E2E tests must pass before PR can be merged.

Reviewed-on: #27
Co-authored-by: David Gwilliam <dhgwilliam@gmail.com>
Co-committed-by: David Gwilliam <dhgwilliam@gmail.com>
2026-04-27 01:59:16 +00:00

135 lines
4.5 KiB
TypeScript

"use client"
import { useState } from "react"
interface DeleteTournamentButtonProps {
tournamentId: number
tournamentName: string
matchCount: number
}
export function DeleteTournamentButton({ tournamentId, tournamentName, matchCount }: DeleteTournamentButtonProps) {
const [isOpen, setIsOpen] = useState(false)
const [isDeleting, setIsDeleting] = useState(false)
const [deleteOption, setDeleteOption] = useState<'delete' | 'orphan'>('orphan')
const handleDelete = async () => {
setIsDeleting(true)
try {
const response = await fetch(`/api/tournaments/${tournamentId}`, {
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
deleteMatches: deleteOption === 'delete',
}),
})
if (!response.ok) {
try {
const errorData = await response.json()
alert(`Error: ${errorData.error || 'Failed to delete tournament'}`)
} catch {
alert(`Error: ${response.status} ${response.statusText}`)
}
return
}
const data = await response.json()
if (data.success) {
alert(`Tournament "${tournamentName}" deleted successfully!`)
window.location.href = "/admin/tournaments"
} else {
alert(`Error: ${data.error}`)
}
} catch (err: unknown) {
alert(`Error: ${err instanceof Error ? err.message : 'Unknown error occurred'}`)
} finally {
setIsDeleting(false)
setIsOpen(false)
}
}
return (
<>
<button
type="button"
onClick={() => setIsOpen(true)}
className="px-4 py-2 border border-red-300 rounded-md shadow-sm text-sm font-medium text-red-700 bg-white hover:bg-red-50"
>
Delete Tournament
</button>
{isOpen && (
<div className="fixed inset-0 bg-gray-600 bg-opacity-50 overflow-y-auto h-full w-full z-50 flex items-center justify-center">
<div className="bg-white rounded-lg shadow-xl p-6 m-4 max-w-md w-full">
<h3 className="text-lg font-semibold text-gray-900 mb-4">
Delete Tournament: {tournamentName}
</h3>
<p className="text-sm text-gray-600 mb-4">
This tournament has {matchCount} game{matchCount !== 1 ? 's' : ''} associated with it.
</p>
<div className="space-y-3 mb-6">
<label className="flex items-center">
<input
type="radio"
name="deleteOption"
value="orphan"
checked={deleteOption === 'orphan'}
onChange={() => setDeleteOption('orphan')}
className="mr-3"
/>
<div>
<span className="font-medium">Orphan Games</span>
<p className="text-sm text-gray-500">
Keep games in the database but remove their tournament association
</p>
</div>
</label>
<label className="flex items-center">
<input
type="radio"
name="deleteOption"
value="delete"
checked={deleteOption === 'delete'}
onChange={() => setDeleteOption('delete')}
className="mr-3"
/>
<div>
<span className="font-medium">Delete Games</span>
<p className="text-sm text-gray-500">
Permanently delete all games associated with this tournament
</p>
</div>
</label>
</div>
<div className="flex justify-end space-x-3">
<button
type="button"
onClick={() => setIsOpen(false)}
className="px-4 py-2 border border-gray-300 rounded-md shadow-sm text-sm font-medium text-gray-700 bg-white hover:bg-gray-50"
>
Cancel
</button>
<button
type="button"
onClick={handleDelete}
disabled={isDeleting}
className="px-4 py-2 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-red-600 hover:bg-red-700 disabled:opacity-50"
>
{isDeleting ? 'Deleting...' : 'Delete Tournament'}
</button>
</div>
</div>
</div>
)}
</>
)
}