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>
This commit was merged in pull request #27.
This commit is contained in:
@@ -22,10 +22,10 @@ export default async function MatchDetailPage({ params }: PageProps) {
|
||||
const match = await prisma.match.findUnique({
|
||||
where: { id: matchId },
|
||||
include: {
|
||||
team1P1: true,
|
||||
team1P2: true,
|
||||
team2P1: true,
|
||||
team2P2: true,
|
||||
player1P1: true,
|
||||
player1P2: true,
|
||||
player2P1: true,
|
||||
player2P2: true,
|
||||
event: true,
|
||||
eloSnapshots: {
|
||||
include: {
|
||||
@@ -93,13 +93,13 @@ export default async function MatchDetailPage({ params }: PageProps) {
|
||||
<div className="absolute top-0 left-0 right-0 h-1/2 flex flex-col justify-end items-center pb-8">
|
||||
<div className="text-center bg-white/80 rounded-lg px-3 py-2 shadow-sm -mt-[20px]">
|
||||
<p className="text-base font-semibold text-amber-900">
|
||||
{match.team1P1.name}
|
||||
{match.player1P1?.name}
|
||||
</p>
|
||||
<p className="text-xs text-amber-700">
|
||||
Elo: {match.team1P1.currentElo}
|
||||
{eloChanges[match.team1P1.id] !== undefined && (
|
||||
<span className={eloChanges[match.team1P1.id] >= 0 ? "text-green-600 ml-1" : "text-red-600 ml-1"}>
|
||||
({eloChanges[match.team1P1.id] >= 0 ? "+" : ""}{eloChanges[match.team1P1.id]})
|
||||
Elo: {match.player1P1?.currentElo}
|
||||
{match.player1P1 && eloChanges[match.player1P1.id] !== undefined && (
|
||||
<span className={eloChanges[match.player1P1.id] >= 0 ? "text-green-600 ml-1" : "text-red-600 ml-1"}>
|
||||
({eloChanges[match.player1P1.id] >= 0 ? "+" : ""}{eloChanges[match.player1P1.id]})
|
||||
</span>
|
||||
)}
|
||||
</p>
|
||||
@@ -112,13 +112,13 @@ export default async function MatchDetailPage({ params }: PageProps) {
|
||||
<div className="text-xs text-amber-600 mb-2 font-medium">Team 1</div>
|
||||
<div className="text-center bg-white/80 rounded-lg px-3 py-2 shadow-sm mt-[10px]">
|
||||
<p className="text-base font-semibold text-amber-900">
|
||||
{match.team1P2.name}
|
||||
{match.player1P2?.name}
|
||||
</p>
|
||||
<p className="text-xs text-amber-700">
|
||||
Elo: {match.team1P2.currentElo}
|
||||
{eloChanges[match.team1P2.id] !== undefined && (
|
||||
<span className={eloChanges[match.team1P2.id] >= 0 ? "text-green-600 ml-1" : "text-red-600 ml-1"}>
|
||||
({eloChanges[match.team1P2.id] >= 0 ? "+" : ""}{eloChanges[match.team1P2.id]})
|
||||
Elo: {match.player1P2?.currentElo}
|
||||
{match.player1P2 && eloChanges[match.player1P2.id] !== undefined && (
|
||||
<span className={eloChanges[match.player1P2.id] >= 0 ? "text-green-600 ml-1" : "text-red-600 ml-1"}>
|
||||
({eloChanges[match.player1P2.id] >= 0 ? "+" : ""}{eloChanges[match.player1P2.id]})
|
||||
</span>
|
||||
)}
|
||||
</p>
|
||||
@@ -129,13 +129,13 @@ export default async function MatchDetailPage({ params }: PageProps) {
|
||||
<div className="absolute left-0 top-0 bottom-0 w-1/2 flex flex-col justify-center items-center pl-8">
|
||||
<div className="text-center bg-white/80 rounded-lg px-3 py-2 shadow-sm rotate-[-90deg] -ml-[20px]">
|
||||
<p className="text-sm font-semibold text-red-900">
|
||||
{match.team2P1.name}
|
||||
{match.player2P1?.name}
|
||||
</p>
|
||||
<p className="text-[11px] text-red-700">
|
||||
Elo: {match.team2P1.currentElo}
|
||||
{eloChanges[match.team2P1.id] !== undefined && (
|
||||
<span className={eloChanges[match.team2P1.id] >= 0 ? "text-green-600" : "text-red-600"}>
|
||||
({eloChanges[match.team2P1.id] >= 0 ? "+" : ""}{eloChanges[match.team2P1.id]})
|
||||
Elo: {match.player2P1?.currentElo}
|
||||
{match.player2P1 && eloChanges[match.player2P1.id] !== undefined && (
|
||||
<span className={eloChanges[match.player2P1.id] >= 0 ? "text-green-600" : "text-red-600"}>
|
||||
({eloChanges[match.player2P1.id] >= 0 ? "+" : ""}{eloChanges[match.player2P1.id]})
|
||||
</span>
|
||||
)}
|
||||
</p>
|
||||
@@ -146,13 +146,13 @@ export default async function MatchDetailPage({ params }: PageProps) {
|
||||
<div className="absolute right-0 top-0 bottom-0 w-1/2 flex flex-col justify-center items-center pr-8">
|
||||
<div className="text-center bg-white/80 rounded-lg px-3 py-2 shadow-sm rotate-[90deg] -mr-[20px]">
|
||||
<p className="text-sm font-semibold text-red-900">
|
||||
{match.team2P2.name}
|
||||
{match.player2P2?.name}
|
||||
</p>
|
||||
<p className="text-[11px] text-red-700">
|
||||
Elo: {match.team2P2.currentElo}
|
||||
{eloChanges[match.team2P2.id] !== undefined && (
|
||||
<span className={eloChanges[match.team2P2.id] >= 0 ? "text-green-600" : "text-red-600"}>
|
||||
({eloChanges[match.team2P2.id] >= 0 ? "+" : ""}{eloChanges[match.team2P2.id]})
|
||||
Elo: {match.player2P2?.currentElo}
|
||||
{match.player2P2 && eloChanges[match.player2P2.id] !== undefined && (
|
||||
<span className={eloChanges[match.player2P2.id] >= 0 ? "text-green-600" : "text-red-600"}>
|
||||
({eloChanges[match.player2P2.id] >= 0 ? "+" : ""}{eloChanges[match.player2P2.id]})
|
||||
</span>
|
||||
)}
|
||||
</p>
|
||||
@@ -246,16 +246,20 @@ export default async function MatchDetailPage({ params }: PageProps) {
|
||||
<h3 className="font-medium text-amber-900 mb-3">Team 1</h3>
|
||||
<div className="space-y-2">
|
||||
<div className="flex justify-between items-center">
|
||||
<span>{match.team1P1.name}</span>
|
||||
<span className={eloChanges[match.team1P1.id] >= 0 ? "text-green-600" : "text-red-600"}>
|
||||
{eloChanges[match.team1P1.id] >= 0 ? "+" : ""}{eloChanges[match.team1P1.id]}
|
||||
</span>
|
||||
<span>{match.player1P1?.name}</span>
|
||||
{match.player1P1 && (
|
||||
<span className={eloChanges[match.player1P1.id] >= 0 ? "text-green-600" : "text-red-600"}>
|
||||
{eloChanges[match.player1P1.id] >= 0 ? "+" : ""}{eloChanges[match.player1P1.id]}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex justify-between items-center">
|
||||
<span>{match.team1P2.name}</span>
|
||||
<span className={eloChanges[match.team1P2.id] >= 0 ? "text-green-600" : "text-red-600"}>
|
||||
{eloChanges[match.team1P2.id] >= 0 ? "+" : ""}{eloChanges[match.team1P2.id]}
|
||||
</span>
|
||||
<span>{match.player1P2?.name}</span>
|
||||
{match.player1P2 && (
|
||||
<span className={eloChanges[match.player1P2.id] >= 0 ? "text-green-600" : "text-red-600"}>
|
||||
{eloChanges[match.player1P2.id] >= 0 ? "+" : ""}{eloChanges[match.player1P2.id]}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -265,16 +269,20 @@ export default async function MatchDetailPage({ params }: PageProps) {
|
||||
<h3 className="font-medium text-red-900 mb-3">Team 2</h3>
|
||||
<div className="space-y-2">
|
||||
<div className="flex justify-between items-center">
|
||||
<span>{match.team2P1.name}</span>
|
||||
<span className={eloChanges[match.team2P1.id] >= 0 ? "text-green-600" : "text-red-600"}>
|
||||
{eloChanges[match.team2P1.id] >= 0 ? "+" : ""}{eloChanges[match.team2P1.id]}
|
||||
</span>
|
||||
<span>{match.player2P1?.name}</span>
|
||||
{match.player2P1 && (
|
||||
<span className={eloChanges[match.player2P1.id] >= 0 ? "text-green-600" : "text-red-600"}>
|
||||
{eloChanges[match.player2P1.id] >= 0 ? "+" : ""}{eloChanges[match.player2P1.id]}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex justify-between items-center">
|
||||
<span>{match.team2P2.name}</span>
|
||||
<span className={eloChanges[match.team2P2.id] >= 0 ? "text-green-600" : "text-red-600"}>
|
||||
{eloChanges[match.team2P2.id] >= 0 ? "+" : ""}{eloChanges[match.team2P2.id]}
|
||||
</span>
|
||||
<span>{match.player2P2?.name}</span>
|
||||
{match.player2P2 && (
|
||||
<span className={eloChanges[match.player2P2.id] >= 0 ? "text-green-600" : "text-red-600"}>
|
||||
{eloChanges[match.player2P2.id] >= 0 ? "+" : ""}{eloChanges[match.player2P2.id]}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user