feat: Implement tournament schedule tab and fix E2E tests (#27)
Release / release (push) Failing after 9s
Build CI Images / build-ci-base (push) Failing after 18s

## 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:
2026-04-27 01:59:16 +00:00
committed by david
parent 75358bf20d
commit bb6be245b7
97 changed files with 7950 additions and 1341 deletions
+38 -23
View File
@@ -10,18 +10,31 @@ export default function RegisterPage() {
const router = useRouter()
const { refreshSession } = useSession()
const [error, setError] = useState("")
const [fieldErrors, setFieldErrors] = useState<Record<string, string>>({})
const [loading, setLoading] = useState(false)
async function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
e.preventDefault()
setLoading(true)
setError("")
setFieldErrors({})
const formData = new FormData(e.currentTarget)
const name = formData.get("name") as string
const email = formData.get("email") as string
const password = formData.get("password") as string
const errors: Record<string, string> = {}
if (!name?.trim()) errors.name = "name is required"
if (!email?.trim()) errors.email = "email is required"
if (!password?.trim()) errors.password = "password is required"
if (Object.keys(errors).length > 0) {
setFieldErrors(errors)
setLoading(false)
return
}
try {
console.log("Attempting signup...");
const result = await authClient.signUp.email({
@@ -29,31 +42,27 @@ export default function RegisterPage() {
password,
name,
})
console.log("Signup result:", result);
console.log("Signup result:", JSON.stringify(result, null, 2));
if (result.error) {
console.error("Signup error:", result.error);
setError(result.error.message || "Failed to create account")
} else {
console.log("Signup successful, redirecting...");
console.log("Result data:", result.data);
// Refresh the session after successful registration
try {
await refreshSession()
console.log("Session refreshed successfully");
} catch (err) {
console.error("Failed to refresh session:", err);
}
console.log("About to redirect to /admin");
router.push("/admin")
router.refresh()
console.log("Redirect initiated");
setLoading(false);
return;
}
console.log("Signup successful, redirecting...");
// autoSignIn is enabled in auth config, so session should be established
// Redirect to wordmark-redirect which will determine destination based on role
console.log("About to redirect to /wordmark-redirect");
window.location.href = "/wordmark-redirect";
return; // Stop execution here
} catch (err) {
console.error("Signup exception:", err);
setError("An unexpected error occurred")
} finally {
setLoading(false)
setLoading(false);
}
}
@@ -80,10 +89,12 @@ export default function RegisterPage() {
id="name"
name="name"
type="text"
required
className="appearance-none rounded-none relative block w-full px-3 py-2 border border-gray-300 placeholder-gray-500 text-gray-900 rounded-t-md focus:outline-none focus:ring-green-500 focus:border-green-500 focus:z-10 sm:text-sm"
className={`appearance-none rounded-none relative block w-full px-3 py-2 border placeholder-gray-500 text-gray-900 rounded-t-md focus:outline-none focus:ring-green-500 focus:border-green-500 focus:z-10 sm:text-sm ${fieldErrors.name ? 'border-red-500' : 'border-gray-300'}`}
placeholder="Full Name"
/>
{fieldErrors.name && (
<p className="text-sm text-red-600 mt-1">{fieldErrors.name}</p>
)}
</div>
<div>
<label htmlFor="email" className="sr-only">
@@ -93,10 +104,12 @@ export default function RegisterPage() {
id="email"
name="email"
type="email"
required
className="appearance-none rounded-none relative block w-full px-3 py-2 border border-gray-300 placeholder-gray-500 text-gray-900 focus:outline-none focus:ring-green-500 focus:border-green-500 focus:z-10 sm:text-sm"
className={`appearance-none rounded-none relative block w-full px-3 py-2 border placeholder-gray-500 text-gray-900 focus:outline-none focus:ring-green-500 focus:border-green-500 focus:z-10 sm:text-sm ${fieldErrors.email ? 'border-red-500' : 'border-gray-300'}`}
placeholder="Email address"
/>
{fieldErrors.email && (
<p className="text-sm text-red-600 mt-1">{fieldErrors.email}</p>
)}
</div>
<div>
<label htmlFor="password" className="sr-only">
@@ -106,10 +119,12 @@ export default function RegisterPage() {
id="password"
name="password"
type="password"
required
className="appearance-none rounded-none relative block w-full px-3 py-2 border border-gray-300 placeholder-gray-500 text-gray-900 rounded-b-md focus:outline-none focus:ring-green-500 focus:border-green-500 focus:z-10 sm:text-sm"
className={`appearance-none rounded-none relative block w-full px-3 py-2 border placeholder-gray-500 text-gray-900 rounded-b-md focus:outline-none focus:ring-green-500 focus:border-green-500 focus:z-10 sm:text-sm ${fieldErrors.password ? 'border-red-500' : 'border-gray-300'}`}
placeholder="Password"
/>
{fieldErrors.password && (
<p className="text-sm text-red-600 mt-1">{fieldErrors.password}</p>
)}
</div>
</div>