Translate App into German

This commit is contained in:
2026-03-13 14:12:00 +01:00
parent 6360600eca
commit 780afb0c56
34 changed files with 477 additions and 304 deletions

View File

@@ -1,7 +1,7 @@
name,tag,description name,tag,description
Team Alpha,ALP,A competitive esports team Team Alpha,ALP,Ein wettbewerbsorientiertes E-Sport-Team
Team Beta,BET,New challengers on the scene Team Beta,BET,Neue Herausforderer in der Szene
Team Gamma,GAM,Veteran tournament winners Team Gamma,GAM,Erfahrene Turniersieger
Team Delta,DEL,Rising stars in the community Team Delta,DEL,Aufstrebende Stars der Community
Team Epsilon,EPS,Strategic gameplay specialists Team Epsilon,EPS,Spezialisten für strategisches Gameplay
Team Zeta,ZET,Fast-paced aggressive players Team Zeta,ZET,Schnelle und aggressive Spieler
1 name tag description
2 Team Alpha ALP A competitive esports team Ein wettbewerbsorientiertes E-Sport-Team
3 Team Beta BET New challengers on the scene Neue Herausforderer in der Szene
4 Team Gamma GAM Veteran tournament winners Erfahrene Turniersieger
5 Team Delta DEL Rising stars in the community Aufstrebende Stars der Community
6 Team Epsilon EPS Strategic gameplay specialists Spezialisten für strategisches Gameplay
7 Team Zeta ZET Fast-paced aggressive players Schnelle und aggressive Spieler

View File

@@ -1,4 +1,4 @@
name,description,maxTeamAmount,currentTeamAmount,registrationStartDate,registrationEndDate name,description,maxTeamAmount,currentTeamAmount,registrationStartDate,registrationEndDate
Demo Tournament ,This is a demo tournament, 2, 0, 2026-02-01, 2026-02-28 Demo-Turnier,Dies ist ein Demo-Turnier, 2, 0, 2026-02-01, 2026-02-28
Demo Tournament 2,This is a second demo tournament, 4, 5, 2026-03-01, 2026-03-15 Demo-Turnier 2,Dies ist ein zweites Demo-Turnier, 4, 5, 2026-03-01, 2026-03-15
Demo Tournament 3,This is a third demo tournament, 8, 8, 2026-03-15, 2026-03-20 Demo-Turnier 3,Dies ist ein drittes Demo-Turnier, 8, 8, 2026-03-15, 2026-03-20
1 name description maxTeamAmount currentTeamAmount registrationStartDate registrationEndDate
2 Demo Tournament Demo-Turnier This is a demo tournament Dies ist ein Demo-Turnier 2 0 2026-02-01 2026-02-28
3 Demo Tournament 2 Demo-Turnier 2 This is a second demo tournament Dies ist ein zweites Demo-Turnier 4 5 2026-03-01 2026-03-15
4 Demo Tournament 3 Demo-Turnier 3 This is a third demo tournament Dies ist ein drittes Demo-Turnier 8 8 2026-03-15 2026-03-20

View File

@@ -37,7 +37,7 @@ app.get('/tournaments', async (req: Request, res: Response) => {
app.get('/tournaments/:id', async (req: Request, res: Response) => { app.get('/tournaments/:id', async (req: Request, res: Response) => {
const tournament = await tournamentService.getTournamentById(+req.params.id); const tournament = await tournamentService.getTournamentById(+req.params.id);
if (!tournament) { if (!tournament) {
return res.status(404).send({error: 'Tournament not found'}); return res.status(404).send({error: 'Turnier nicht gefunden'});
} }
res.send(tournament); res.send(tournament);
}); });
@@ -48,7 +48,7 @@ app.post('/tournaments', authMiddleware, async (req: Request, res: Response) =>
res.status(201).send(); res.status(201).send();
} catch (err){ } catch (err){
console.log(err); console.log(err);
res.status(400).send({error: 'Failed to create tournament'}); res.status(400).send({error: 'Turnier konnte nicht erstellt werden'});
} }
}); });
@@ -56,18 +56,18 @@ app.put('/tournaments/:id', authMiddleware, async (req: Request, res: Response)
try { try {
await tournamentService.updateTournament(+req.params.id, req.body); await tournamentService.updateTournament(+req.params.id, req.body);
} catch (err) { } catch (err) {
return res.status(400).send({error: 'Failed to update Tournament'}); return res.status(400).send({error: 'Turnier konnte nicht aktualisiert werden'});
} }
res.status(200).send({message: 'Tournament updated successfully'}); res.status(200).send({message: 'Turnier erfolgreich aktualisiert'});
}); });
app.delete('/tournaments/:id', authMiddleware, async (req: Request, res: Response) => { app.delete('/tournaments/:id', authMiddleware, async (req: Request, res: Response) => {
try { try {
await tournamentService.deleteTournament(+req.params.id); await tournamentService.deleteTournament(+req.params.id);
} catch (err) { } catch (err) {
return res.status(400).send({error: 'Failed to delete Tournament'}); return res.status(400).send({error: 'Turnier konnte nicht gelöscht werden'});
} }
res.status(200).send({message: 'Tournament deleted successfully'}); res.status(200).send({message: 'Turnier erfolgreich gelöscht'});
}); });
app.post('/tournaments/:id/bracket', authMiddleware, async (req: Request, res: Response) => { app.post('/tournaments/:id/bracket', authMiddleware, async (req: Request, res: Response) => {
@@ -77,14 +77,14 @@ app.post('/tournaments/:id/bracket', authMiddleware, async (req: Request, res: R
const teamIds = teams.map(team => team.id); const teamIds = teams.map(team => team.id);
if (teamIds.length < 2) { if (teamIds.length < 2) {
return res.status(400).send({error: 'At least 2 teams are required to initialize bracket'}); return res.status(400).send({error: 'Mindestens 2 Teams sind erforderlich, um den Turnierbaum zu initialisieren'});
} }
await matchService.initializeBracket(tournamentId, teamIds); await matchService.initializeBracket(tournamentId, teamIds);
res.status(201).send({message: 'Bracket initialized successfully'}); res.status(201).send({message: 'Turnierbaum erfolgreich initialisiert'});
} catch (err) { } catch (err) {
console.log(err); console.log(err);
res.status(400).send({error: 'Failed to initialize bracket'}); res.status(400).send({error: 'Turnierbaum konnte nicht initialisiert werden'});
} }
}); });
@@ -94,7 +94,7 @@ app.get('/tournaments/:id/matches', async (req: Request, res: Response) => {
res.send(matches); res.send(matches);
} catch (err) { } catch (err) {
console.log(err); console.log(err);
res.status(400).send({error: 'Failed to get matches'}); res.status(400).send({error: 'Matches konnten nicht geladen werden'});
} }
}); });
@@ -102,23 +102,23 @@ app.put('/matches/:id/winner', authMiddleware, async (req: Request, res: Respons
try { try {
const {winnerId} = req.body; const {winnerId} = req.body;
if (!winnerId) { if (!winnerId) {
return res.status(400).send({error: 'winnerId is required'}); return res.status(400).send({error: 'winnerId ist erforderlich'});
} }
await matchService.setMatchWinner(+req.params.id, +winnerId); await matchService.setMatchWinner(+req.params.id, +winnerId);
res.status(200).send({message: 'Winner set successfully'}); res.status(200).send({message: 'Sieger erfolgreich festgelegt'});
} catch (err: any) { } catch (err: any) {
console.log(err); console.log(err);
res.status(400).send({error: err.message || 'Failed to set winner'}); res.status(400).send({error: err.message || 'Sieger konnte nicht festgelegt werden'});
} }
}); });
app.delete('/matches/:id/winner', authMiddleware, async (req: Request, res: Response) => { app.delete('/matches/:id/winner', authMiddleware, async (req: Request, res: Response) => {
try { try {
await matchService.resetMatch(+req.params.id); await matchService.resetMatch(+req.params.id);
res.status(200).send({message: 'Match reset successfully'}); res.status(200).send({message: 'Match erfolgreich zurückgesetzt'});
} catch (err) { } catch (err) {
console.log(err); console.log(err);
res.status(400).send({error: 'Failed to reset match'}); res.status(400).send({error: 'Match konnte nicht zurückgesetzt werden'});
} }
}); });
@@ -130,7 +130,7 @@ app.get('/teams', async (req: Request, res: Response) => {
app.get('/teams/:id', async (req: Request, res: Response) => { app.get('/teams/:id', async (req: Request, res: Response) => {
const team = await teamService.getTeamById(+req.params.id); const team = await teamService.getTeamById(+req.params.id);
if (!team) { if (!team) {
return res.status(404).send({error: 'Team not found'}); return res.status(404).send({error: 'Team nicht gefunden'});
} }
res.send(team); res.send(team);
}); });
@@ -138,10 +138,10 @@ app.get('/teams/:id', async (req: Request, res: Response) => {
app.post('/teams', authMiddleware, async (req: Request, res: Response) => { app.post('/teams', authMiddleware, async (req: Request, res: Response) => {
const {name, tag, description} = req.body; const {name, tag, description} = req.body;
if (!name || !tag) { if (!name || !tag) {
return res.status(400).send({error: 'name and tag are required'}); return res.status(400).send({error: 'Name und Kürzel sind erforderlich'});
} }
if (tag.length > 3) { if (tag.length > 3) {
return res.status(400).send({error: 'tag must be at most 3 characters'}); return res.status(400).send({error: 'Das Kürzel darf höchstens 3 Zeichen lang sein'});
} }
try { try {
const team = await teamService.addTeam({name, tag, description: description ?? ''}); const team = await teamService.addTeam({name, tag, description: description ?? ''});
@@ -151,30 +151,30 @@ app.post('/teams', authMiddleware, async (req: Request, res: Response) => {
res.status(201).send(team); res.status(201).send(team);
} catch (err) { } catch (err) {
console.log(err); console.log(err);
res.status(400).send({error: 'Failed to create team'}); res.status(400).send({error: 'Team konnte nicht erstellt werden'});
} }
}); });
app.put('/teams/:id', authMiddleware, async (req: Request, res: Response) => { app.put('/teams/:id', authMiddleware, async (req: Request, res: Response) => {
const {tag} = req.body; const {tag} = req.body;
if (tag && tag.length > 3) { if (tag && tag.length > 3) {
return res.status(400).send({error: 'tag must be at most 3 characters'}); return res.status(400).send({error: 'Das Kürzel darf höchstens 3 Zeichen lang sein'});
} }
try { try {
await teamService.updateTeam(+req.params.id, req.body); await teamService.updateTeam(+req.params.id, req.body);
} catch (err) { } catch (err) {
return res.status(400).send({error: 'Failed to update team'}); return res.status(400).send({error: 'Team konnte nicht aktualisiert werden'});
} }
res.status(200).send({message: 'Team updated successfully'}); res.status(200).send({message: 'Team erfolgreich aktualisiert'});
}); });
app.delete('/teams/:id', authMiddleware, async (req: Request, res: Response) => { app.delete('/teams/:id', authMiddleware, async (req: Request, res: Response) => {
try { try {
await teamService.deleteTeam(+req.params.id); await teamService.deleteTeam(+req.params.id);
} catch (err) { } catch (err) {
return res.status(400).send({error: 'Failed to delete team'}); return res.status(400).send({error: 'Team konnte nicht gelöscht werden'});
} }
res.status(200).send({message: 'Team deleted successfully'}); res.status(200).send({message: 'Team erfolgreich gelöscht'});
}); });
app.get('/tournaments/:id/teams', async (req: Request, res: Response) => { app.get('/tournaments/:id/teams', async (req: Request, res: Response) => {
@@ -185,16 +185,16 @@ app.get('/tournaments/:id/teams', async (req: Request, res: Response) => {
app.post('/tournaments/:id/teams', authMiddleware, async (req: Request, res: Response) => { app.post('/tournaments/:id/teams', authMiddleware, async (req: Request, res: Response) => {
const {teamId} = req.body; const {teamId} = req.body;
if (!teamId) { if (!teamId) {
return res.status(400).send({error: 'teamId is required'}); return res.status(400).send({error: 'teamId ist erforderlich'});
} }
try { try {
const entry = await teamService.registerTeamForTournament(+req.params.id, +teamId); const entry = await teamService.registerTeamForTournament(+req.params.id, +teamId);
res.status(201).send(entry); res.status(201).send(entry);
} catch (err: any) { } catch (err: any) {
if (err.message?.includes('UNIQUE constraint failed')) { if (err.message?.includes('UNIQUE constraint failed')) {
return res.status(409).send({error: 'Team is already registered for this tournament'}); return res.status(409).send({error: 'Das Team ist bereits für dieses Turnier angemeldet'});
} }
res.status(400).send({error: 'Failed to register team'}); res.status(400).send({error: 'Team konnte nicht für das Turnier angemeldet werden'});
} }
}); });
@@ -202,9 +202,9 @@ app.delete('/tournaments/:id/teams/:teamId', authMiddleware, async (req: Request
try { try {
await teamService.removeTeamFromTournament(+req.params.id, +req.params.teamId); await teamService.removeTeamFromTournament(+req.params.id, +req.params.teamId);
} catch (err) { } catch (err) {
return res.status(400).send({error: 'Failed to remove team from tournament'}); return res.status(400).send({error: 'Team konnte nicht aus dem Turnier entfernt werden'});
} }
res.status(200).send({message: 'Team removed from tournament'}); res.status(200).send({message: 'Team aus dem Turnier entfernt'});
}); });
app.get('/teams/:id/tournaments', async (req: Request, res: Response) => { app.get('/teams/:id/tournaments', async (req: Request, res: Response) => {
@@ -220,7 +220,7 @@ app.get('/users/me/teams', authMiddleware, async (req: Request, res: Response) =
res.send(teams); res.send(teams);
} catch (err) { } catch (err) {
console.log(err); console.log(err);
res.status(400).send({error: 'Failed to get user teams'}); res.status(400).send({error: 'Eigene Teams konnten nicht geladen werden'});
} }
}); });
@@ -232,14 +232,14 @@ app.post('/teams/:id/members', authMiddleware, async (req: Request, res: Respons
const isInTeam = await teamService.isUserInTeam(teamId, userId); const isInTeam = await teamService.isUserInTeam(teamId, userId);
if (isInTeam) { if (isInTeam) {
return res.status(409).send({error: 'User is already a member of this team'}); return res.status(409).send({error: 'Du bist bereits Mitglied dieses Teams'});
} }
const member = await teamService.addTeamMember(teamId, userId, 'member'); const member = await teamService.addTeamMember(teamId, userId, 'member');
res.status(201).send(member); res.status(201).send(member);
} catch (err) { } catch (err) {
console.log(err); console.log(err);
res.status(400).send({error: 'Failed to join team'}); res.status(400).send({error: 'Beitritt zum Team fehlgeschlagen'});
} }
}); });
@@ -249,10 +249,10 @@ app.delete('/teams/:id/members/me', authMiddleware, async (req: Request, res: Re
const userId = req.user.id; const userId = req.user.id;
const teamId = +req.params.id; const teamId = +req.params.id;
await teamService.removeTeamMember(teamId, userId); await teamService.removeTeamMember(teamId, userId);
res.status(200).send({message: 'Left team successfully'}); res.status(200).send({message: 'Team erfolgreich verlassen'});
} catch (err) { } catch (err) {
console.log(err); console.log(err);
res.status(400).send({error: 'Failed to leave team'}); res.status(400).send({error: 'Team konnte nicht verlassen werden'});
} }
}); });
@@ -262,36 +262,36 @@ app.get('/teams/:id/members', async (req: Request, res: Response) => {
res.send(members); res.send(members);
} catch (err) { } catch (err) {
console.log(err); console.log(err);
res.status(400).send({error: 'Failed to get team members'}); res.status(400).send({error: 'Teammitglieder konnten nicht geladen werden'});
} }
}); });
app.post('/register', async (req: Request, res: Response) => { app.post('/register', async (req: Request, res: Response) => {
const { username, password } = req.body; const { username, password } = req.body;
if (!username || !password) { if (!username || !password) {
return res.status(400).send({ error: 'Username and password are required' }); return res.status(400).send({ error: 'Benutzername und Passwort sind erforderlich' });
} }
try { try {
const user = await userService.register(username, password); const user = await userService.register(username, password);
res.status(201).send(user); res.status(201).send(user);
} catch (err: any) { } catch (err: any) {
if (err.message?.includes('UNIQUE constraint failed')) { if (err.message?.includes('UNIQUE constraint failed')) {
return res.status(409).send({ error: 'Username already exists' }); return res.status(409).send({ error: 'Benutzername existiert bereits' });
} }
res.status(500).send({ error: 'Registration failed' }); res.status(500).send({ error: 'Registrierung fehlgeschlagen' });
} }
}); });
app.post('/login', async (req: Request, res: Response) => { app.post('/login', async (req: Request, res: Response) => {
const { username, password } = req.body; const { username, password } = req.body;
if (!username || !password) { if (!username || !password) {
return res.status(400).send({ error: 'Username and password are required' }); return res.status(400).send({ error: 'Benutzername und Passwort sind erforderlich' });
} }
try { try {
const user = await userService.login(username, password); const user = await userService.login(username, password);
res.status(200).send(user); res.status(200).send(user);
} catch (err: any) { } catch (err: any) {
res.status(401).send({ error: err.message || 'Login failed' }); res.status(401).send({ error: err.message || 'Anmeldung fehlgeschlagen' });
} }
}); });

View File

@@ -6,7 +6,7 @@ const JWT_SECRET = process.env.JWT_SECRET || "key"
export const authMiddleware = (req: Request, res: Response, next: NextFunction) => { export const authMiddleware = (req: Request, res: Response, next: NextFunction) => {
const authHeader = req.headers.authorization; const authHeader = req.headers.authorization;
if (!authHeader || !authHeader.startsWith('Bearer ')) { if (!authHeader || !authHeader.startsWith('Bearer ')) {
return res.status(401).send({ error: 'Unauthorized: No token provided' }); return res.status(401).send({ error: 'Nicht autorisiert: Kein Token angegeben' });
} }
const token = authHeader.split(' ')[1]; const token = authHeader.split(' ')[1];
try { try {
@@ -17,6 +17,6 @@ export const authMiddleware = (req: Request, res: Response, next: NextFunction)
next(); next();
} catch (err) { } catch (err) {
return res.status(401).send({ error: 'Unauthorized: Invalid token' }); return res.status(401).send({ error: 'Nicht autorisiert: Ungültiger Token' });
} }
}; };

View File

@@ -120,11 +120,11 @@ export class MatchService {
[matchId], [matchId],
(err, match: any) => { (err, match: any) => {
if (err) return reject(err); if (err) return reject(err);
if (!match) return reject(new Error('Match not found')); if (!match) return reject(new Error('Match nicht gefunden'));
// Validate that the winnerId is one of the participants // Validate that the winnerId is one of the participants
if (match.team1Id !== winnerId && match.team2Id !== winnerId) { if (match.team1Id !== winnerId && match.team2Id !== winnerId) {
return reject(new Error('Winner must be one of the match participants')); return reject(new Error('Der Sieger muss eines der Teams dieses Matches sein'));
} }
// Update the match with winner // Update the match with winner
@@ -182,7 +182,7 @@ export class MatchService {
[matchId], [matchId],
(err, match: any) => { (err, match: any) => {
if (err) return reject(err); if (err) return reject(err);
if (!match) return reject(new Error('Match not found')); if (!match) return reject(new Error('Match nicht gefunden'));
const previousWinnerId = match.winnerId; const previousWinnerId = match.winnerId;

View File

@@ -183,7 +183,7 @@ export class TeamService {
(err: Error | null, row: any) => { (err: Error | null, row: any) => {
if (err) return reject(err); if (err) return reject(err);
if (row.count >= 4) { if (row.count >= 4) {
return reject(new Error('Team already has maximum of 4 members')); return reject(new Error('Das Team hat bereits die maximale Anzahl von 4 Mitgliedern'));
} }
const stmt = this.db.prepare(`INSERT INTO TeamMembers (teamId, userId, role) VALUES (?, ?, ?)`); const stmt = this.db.prepare(`INSERT INTO TeamMembers (teamId, userId, role) VALUES (?, ?, ?)`);

View File

@@ -74,12 +74,12 @@ export class UserService {
return reject(err); return reject(err);
} }
if (!user) { if (!user) {
return reject(new Error('User not found')); return reject(new Error('Benutzer nicht gefunden'));
} }
try { try {
const valid = await argon2.verify(user.password, password); const valid = await argon2.verify(user.password, password);
if (!valid) { if (!valid) {
return reject(new Error('Invalid password')); return reject(new Error('Ungültiges Passwort'));
} }
const token = jwt.sign({ id: user.id, username: user.username }, JWT_SECRET, { expiresIn: '7d' }); const token = jwt.sign({ id: user.id, username: user.username }, JWT_SECRET, { expiresIn: '7d' });
resolve({ id: user.id, username: user.username, token }); resolve({ id: user.id, username: user.username, token });

View File

@@ -110,3 +110,41 @@ Folgende Dateien wurden in diesem Prompt verändert:
- frontend_splatournament_manager/lib/providers/match_provider.dart (neu erstellt) - frontend_splatournament_manager/lib/providers/match_provider.dart (neu erstellt)
- frontend_splatournament_manager/lib/main.dart - frontend_splatournament_manager/lib/main.dart
- frontend_splatournament_manager/lib/pages/tournament_bracket_page.dart - frontend_splatournament_manager/lib/pages/tournament_bracket_page.dart
## 13.03.2026
- Translate the entire app into German, and also translate the relevant DB seeding CSVs.
Folgende Dateien wurden in diesem Prompt verändert:
- backend_splatournament_manager/src/app.ts
- backend_splatournament_manager/src/middlewares/auth-middleware.ts
- backend_splatournament_manager/src/services/user-service.ts
- backend_splatournament_manager/src/services/team-service.ts
- backend_splatournament_manager/src/services/match-service.ts
- backend_splatournament_manager/dist/csv/teams.csv
- backend_splatournament_manager/dist/csv/tournaments.csv
- frontend_splatournament_manager/pubspec.yaml
- frontend_splatournament_manager/lib/main.dart
- frontend_splatournament_manager/lib/pages/login_page.dart
- frontend_splatournament_manager/lib/pages/home_page.dart
- frontend_splatournament_manager/lib/pages/create_team_page.dart
- frontend_splatournament_manager/lib/pages/create_tournament_page.dart
- frontend_splatournament_manager/lib/pages/settings_page.dart
- frontend_splatournament_manager/lib/pages/tournament_detail_page.dart
- frontend_splatournament_manager/lib/pages/tournament_bracket_page.dart
- frontend_splatournament_manager/lib/providers/tournament_provider.dart
- frontend_splatournament_manager/lib/providers/match_provider.dart
- frontend_splatournament_manager/lib/services/auth_service.dart
- frontend_splatournament_manager/lib/services/team_service.dart
- frontend_splatournament_manager/lib/widgets/available_tournament_list.dart
- frontend_splatournament_manager/lib/widgets/profile_widget.dart
- frontend_splatournament_manager/lib/widgets/teams_list_widget.dart
- frontend_splatournament_manager/lib/widgets/theme_selector_widget.dart
- frontend_splatournament_manager/lib/widgets/my_teams_widget.dart
- frontend_splatournament_manager/lib/widgets/my_tournaments_carousel.dart
- frontend_splatournament_manager/web/index.html
- frontend_splatournament_manager/web/manifest.json
- frontend_splatournament_manager/android/app/src/main/AndroidManifest.xml
- frontend_splatournament_manager/ios/Runner/Info.plist
- frontend_splatournament_manager/linux/runner/my_application.cc
- frontend_splatournament_manager/windows/runner/main.cpp
- frontend_splatournament_manager/windows/runner/Runner.rc

View File

@@ -2,7 +2,7 @@
<uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.INTERNET"/>
<application <application
android:label="frontend_splatournament_manager" android:label="Splatournament Manager"
android:name="${applicationName}" android:name="${applicationName}"
android:icon="@mipmap/ic_launcher"> android:icon="@mipmap/ic_launcher">
<activity <activity

View File

@@ -7,7 +7,7 @@
<key>CFBundleDevelopmentRegion</key> <key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string> <string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key> <key>CFBundleDisplayName</key>
<string>Frontend Splatournament Manager</string> <string>Splatournament Manager</string>
<key>CFBundleExecutable</key> <key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string> <string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key> <key>CFBundleIdentifier</key>
@@ -15,7 +15,7 @@
<key>CFBundleInfoDictionaryVersion</key> <key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string> <string>6.0</string>
<key>CFBundleName</key> <key>CFBundleName</key>
<string>frontend_splatournament_manager</string> <string>Splatournament Manager</string>
<key>CFBundlePackageType</key> <key>CFBundlePackageType</key>
<string>APPL</string> <string>APPL</string>
<key>CFBundleShortVersionString</key> <key>CFBundleShortVersionString</key>

View File

@@ -1,4 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:frontend_splatournament_manager/pages/home_page.dart'; import 'package:frontend_splatournament_manager/pages/home_page.dart';
import 'package:frontend_splatournament_manager/pages/login_page.dart'; import 'package:frontend_splatournament_manager/pages/login_page.dart';
import 'package:frontend_splatournament_manager/pages/settings_page.dart'; import 'package:frontend_splatournament_manager/pages/settings_page.dart';
@@ -34,6 +35,13 @@ class SplatournamentApp extends StatelessWidget {
return MaterialApp.router( return MaterialApp.router(
title: 'Splatournament Manager', title: 'Splatournament Manager',
routerConfig: routes, routerConfig: routes,
locale: const Locale('de', 'DE'),
supportedLocales: const [Locale('de', 'DE')],
localizationsDelegates: const [
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
themeMode: themeProvider.themeMode, themeMode: themeProvider.themeMode,
theme: themeProvider.lightTheme, theme: themeProvider.lightTheme,
darkTheme: themeProvider.darkTheme, darkTheme: themeProvider.darkTheme,

View File

@@ -23,9 +23,13 @@ class _CreateTeamPageState extends State<CreateTeamPage> {
@override @override
void initState() { void initState() {
super.initState(); super.initState();
_nameController = TextEditingController(text: widget.teamToEdit?.name ?? ''); _nameController = TextEditingController(
text: widget.teamToEdit?.name ?? '',
);
_tagController = TextEditingController(text: widget.teamToEdit?.tag ?? ''); _tagController = TextEditingController(text: widget.teamToEdit?.tag ?? '');
_descriptionController = TextEditingController(text: widget.teamToEdit?.description ?? ''); _descriptionController = TextEditingController(
text: widget.teamToEdit?.description ?? '',
);
} }
void _submitForm() async { void _submitForm() async {
@@ -42,9 +46,9 @@ class _CreateTeamPageState extends State<CreateTeamPage> {
tag: _tagController.text, tag: _tagController.text,
description: _descriptionController.text, description: _descriptionController.text,
); );
if (!context.mounted) return; if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Team updated successfully')), const SnackBar(content: Text('Team erfolgreich aktualisiert')),
); );
} else { } else {
await provider.createTeam( await provider.createTeam(
@@ -52,20 +56,24 @@ class _CreateTeamPageState extends State<CreateTeamPage> {
tag: _tagController.text, tag: _tagController.text,
description: _descriptionController.text, description: _descriptionController.text,
); );
if (!context.mounted) return; if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Team created successfully')), const SnackBar(content: Text('Team erfolgreich erstellt')),
); );
} }
if (!context.mounted) return; if (!mounted) return;
Navigator.pop(context); Navigator.pop(context);
} catch (e) { } catch (e) {
if (!context.mounted) return; if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Error: $e')), SnackBar(
content: Text(
'Fehler: ${e.toString().replaceFirst('Exception: ', '')}',
),
),
); );
} finally { } finally {
if (context.mounted) setState(() => _isLoading = false); if (mounted) setState(() => _isLoading = false);
} }
} }
@@ -82,7 +90,7 @@ class _CreateTeamPageState extends State<CreateTeamPage> {
final isEditing = widget.teamToEdit != null; final isEditing = widget.teamToEdit != null;
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: Text(isEditing ? 'Edit Team' : 'Create Team'), title: Text(isEditing ? 'Team bearbeiten' : 'Team erstellen'),
), ),
body: SingleChildScrollView( body: SingleChildScrollView(
padding: const EdgeInsets.all(16.0), padding: const EdgeInsets.all(16.0),
@@ -94,12 +102,12 @@ class _CreateTeamPageState extends State<CreateTeamPage> {
TextFormField( TextFormField(
controller: _nameController, controller: _nameController,
decoration: const InputDecoration( decoration: const InputDecoration(
labelText: 'Team Name', labelText: 'Teamname',
hintText: 'Enter team name', hintText: 'Teamname eingeben',
), ),
validator: (value) { validator: (value) {
if (value == null || value.isEmpty) { if (value == null || value.isEmpty) {
return 'Team name is required'; return 'Der Teamname ist erforderlich';
} }
return null; return null;
}, },
@@ -108,17 +116,17 @@ class _CreateTeamPageState extends State<CreateTeamPage> {
TextFormField( TextFormField(
controller: _tagController, controller: _tagController,
decoration: const InputDecoration( decoration: const InputDecoration(
labelText: 'Team Tag', labelText: 'Teamkürzel',
hintText: 'Enter team tag (max 3 characters)', hintText: 'Teamkürzel eingeben (max. 3 Zeichen)',
), ),
maxLength: 3, maxLength: 3,
textCapitalization: TextCapitalization.characters, textCapitalization: TextCapitalization.characters,
validator: (value) { validator: (value) {
if (value == null || value.isEmpty) { if (value == null || value.isEmpty) {
return 'Team tag is required'; return 'Das Teamkürzel ist erforderlich';
} }
if (value.length > 3) { if (value.length > 3) {
return 'Tag must be at most 3 characters'; return 'Das Kürzel darf höchstens 3 Zeichen lang sein';
} }
return null; return null;
}, },
@@ -127,8 +135,8 @@ class _CreateTeamPageState extends State<CreateTeamPage> {
TextFormField( TextFormField(
controller: _descriptionController, controller: _descriptionController,
decoration: const InputDecoration( decoration: const InputDecoration(
labelText: 'Description', labelText: 'Beschreibung',
hintText: 'Enter team description (optional)', hintText: 'Teambeschreibung eingeben (optional)',
), ),
maxLines: 3, maxLines: 3,
), ),
@@ -137,7 +145,7 @@ class _CreateTeamPageState extends State<CreateTeamPage> {
onPressed: _isLoading ? null : _submitForm, onPressed: _isLoading ? null : _submitForm,
child: _isLoading child: _isLoading
? const CircularProgressIndicator() ? const CircularProgressIndicator()
: Text(isEditing ? 'Update Team' : 'Create Team'), : Text(isEditing ? 'Team aktualisieren' : 'Team erstellen'),
), ),
], ],
), ),

View File

@@ -22,12 +22,13 @@ class _CreateTournamentPageState extends State<CreateTournamentPage> {
bool _isLoading = false; bool _isLoading = false;
final DateFormat _dateFormat = DateFormat('yyyy-MM-dd'); final DateFormat _dateFormat = DateFormat('dd.MM.yyyy', 'de_DE');
Future<void> _selectDate(BuildContext context, bool isStart) async { Future<void> _selectDate(BuildContext context, bool isStart) async {
final initialDate = DateTime.now(); final initialDate = DateTime.now();
final picked = await showDatePicker( final picked = await showDatePicker(
context: context, context: context,
locale: const Locale('de', 'DE'),
initialDate: initialDate, initialDate: initialDate,
firstDate: initialDate, firstDate: initialDate,
lastDate: DateTime(2101), lastDate: DateTime(2101),
@@ -47,14 +48,20 @@ class _CreateTournamentPageState extends State<CreateTournamentPage> {
if (!_formKey.currentState!.validate()) return; if (!_formKey.currentState!.validate()) return;
if (_startDate == null || _endDate == null) { if (_startDate == null || _endDate == null) {
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Please select both start and end dates.')), const SnackBar(
content: Text(
'Bitte wähle sowohl ein Start- als auch ein Enddatum aus.',
),
),
); );
return; return;
} }
if (_endDate!.isBefore(_startDate!)) { if (_endDate!.isBefore(_startDate!)) {
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('End date cannot be before start date.')), const SnackBar(
content: Text('Das Enddatum darf nicht vor dem Startdatum liegen.'),
),
); );
return; return;
} }
@@ -70,15 +77,19 @@ class _CreateTournamentPageState extends State<CreateTournamentPage> {
_startDate!, _startDate!,
_endDate!, _endDate!,
); );
if (!context.mounted) return; if (!mounted) return;
Navigator.pop(context); Navigator.pop(context);
} catch (e) { } catch (e) {
if (!context.mounted) return; if (!mounted) return;
ScaffoldMessenger.of( ScaffoldMessenger.of(context).showSnackBar(
context, SnackBar(
).showSnackBar(SnackBar(content: Text('Error: $e'))); content: Text(
'Fehler: ${e.toString().replaceFirst('Exception: ', '')}',
),
),
);
} finally { } finally {
if (context.mounted) setState(() => _isLoading = false); if (mounted) setState(() => _isLoading = false);
} }
} }
@@ -92,7 +103,7 @@ class _CreateTournamentPageState extends State<CreateTournamentPage> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: AppBar(title: const Text('Create Tournament')), appBar: AppBar(title: const Text('Turnier erstellen')),
body: SingleChildScrollView( body: SingleChildScrollView(
padding: const EdgeInsets.all(16.0), padding: const EdgeInsets.all(16.0),
child: Form( child: Form(
@@ -104,20 +115,20 @@ class _CreateTournamentPageState extends State<CreateTournamentPage> {
controller: _nameController, controller: _nameController,
decoration: const InputDecoration(labelText: 'Name'), decoration: const InputDecoration(labelText: 'Name'),
validator: (value) => validator: (value) =>
value == null || value.isEmpty ? 'Required' : null, value == null || value.isEmpty ? 'Pflichtfeld' : null,
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
TextFormField( TextFormField(
controller: _descriptionController, controller: _descriptionController,
decoration: const InputDecoration(labelText: 'Description'), decoration: const InputDecoration(labelText: 'Beschreibung'),
maxLines: 3, maxLines: 3,
validator: (value) => validator: (value) =>
value == null || value.isEmpty ? 'Required' : null, value == null || value.isEmpty ? 'Pflichtfeld' : null,
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
DropdownButtonFormField<int>( DropdownButtonFormField<int>(
initialValue: _maxTeamAmount, initialValue: _maxTeamAmount,
decoration: const InputDecoration(labelText: 'Max Teams'), decoration: const InputDecoration(labelText: 'Maximale Teams'),
items: [2, 4, 8].map((int value) { items: [2, 4, 8].map((int value) {
return DropdownMenuItem<int>( return DropdownMenuItem<int>(
value: value, value: value,
@@ -140,7 +151,7 @@ class _CreateTournamentPageState extends State<CreateTournamentPage> {
onPressed: () => _selectDate(context, true), onPressed: () => _selectDate(context, true),
child: Text( child: Text(
_startDate == null _startDate == null
? 'Select Start Date' ? 'Startdatum wählen'
: 'Start: ${_dateFormat.format(_startDate!)}', : 'Start: ${_dateFormat.format(_startDate!)}',
), ),
), ),
@@ -151,8 +162,8 @@ class _CreateTournamentPageState extends State<CreateTournamentPage> {
onPressed: () => _selectDate(context, false), onPressed: () => _selectDate(context, false),
child: Text( child: Text(
_endDate == null _endDate == null
? 'Select End Date' ? 'Enddatum wählen'
: 'End: ${_dateFormat.format(_endDate!)}', : 'Ende: ${_dateFormat.format(_endDate!)}',
), ),
), ),
), ),
@@ -163,7 +174,7 @@ class _CreateTournamentPageState extends State<CreateTournamentPage> {
onPressed: _isLoading ? null : _submitForm, onPressed: _isLoading ? null : _submitForm,
child: _isLoading child: _isLoading
? const CircularProgressIndicator() ? const CircularProgressIndicator()
: const Text('Create Tournament'), : const Text('Turnier erstellen'),
), ),
], ],
), ),

View File

@@ -42,7 +42,7 @@ class _HomePageState extends State<HomePage>
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: Text(_selectedIndex == 0 ? "Tournaments" : "Teams"), title: Text(_selectedIndex == 0 ? 'Turniere' : 'Teams'),
bottom: _selectedIndex == 1 bottom: _selectedIndex == 1
? TabBar( ? TabBar(
controller: _tabController, controller: _tabController,
@@ -54,8 +54,8 @@ class _HomePageState extends State<HomePage>
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
), ),
tabs: const [ tabs: const [
Tab(text: 'All Teams'), Tab(text: 'Alle Teams'),
Tab(text: 'My Teams'), Tab(text: 'Meine Teams'),
], ],
) )
: null, : null,
@@ -81,7 +81,7 @@ class _HomePageState extends State<HomePage>
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
SnackBar( SnackBar(
content: Text( content: Text(
'Failed to refresh ${_selectedIndex == 0 ? "tournaments" : "teams"}', 'Aktualisierung der ${_selectedIndex == 0 ? "Turniere" : "Teams"} fehlgeschlagen',
), ),
), ),
); );
@@ -95,7 +95,9 @@ class _HomePageState extends State<HomePage>
}, },
offset: const Offset(0, 48), offset: const Offset(0, 48),
itemBuilder: (context) { itemBuilder: (context) {
return [const PopupMenuItem(value: 1, child: Text("Settings"))]; return [
const PopupMenuItem(value: 1, child: Text('Einstellungen')),
];
}, },
), ),
], ],
@@ -127,7 +129,7 @@ class _HomePageState extends State<HomePage>
items: const [ items: const [
BottomNavigationBarItem( BottomNavigationBarItem(
icon: Icon(Icons.emoji_events), icon: Icon(Icons.emoji_events),
label: 'Tournaments', label: 'Turniere',
), ),
BottomNavigationBarItem(icon: Icon(Icons.groups), label: 'Teams'), BottomNavigationBarItem(icon: Icon(Icons.groups), label: 'Teams'),
], ],

View File

@@ -66,13 +66,13 @@ class _LoginPageState extends State<LoginPage> {
TextFormField( TextFormField(
controller: _usernameController, controller: _usernameController,
decoration: const InputDecoration( decoration: const InputDecoration(
labelText: 'Username', labelText: 'Benutzername',
prefixIcon: Icon(Icons.person), prefixIcon: Icon(Icons.person),
border: OutlineInputBorder(), border: OutlineInputBorder(),
), ),
validator: (value) { validator: (value) {
if (value == null || value.trim().isEmpty) { if (value == null || value.trim().isEmpty) {
return 'Please enter a username'; return 'Bitte gib einen Benutzernamen ein';
} }
return null; return null;
}, },
@@ -82,16 +82,16 @@ class _LoginPageState extends State<LoginPage> {
controller: _passwordController, controller: _passwordController,
obscureText: true, obscureText: true,
decoration: const InputDecoration( decoration: const InputDecoration(
labelText: 'Password', labelText: 'Passwort',
prefixIcon: Icon(Icons.lock), prefixIcon: Icon(Icons.lock),
border: OutlineInputBorder(), border: OutlineInputBorder(),
), ),
validator: (value) { validator: (value) {
if (value == null || value.trim().isEmpty) { if (value == null || value.trim().isEmpty) {
return 'Please enter a password'; return 'Bitte gib ein Passwort ein';
} }
if (_isRegistering && value.trim().length < 4) { if (_isRegistering && value.trim().length < 4) {
return 'Password must be at least 6 characters'; return 'Das Passwort muss mindestens 4 Zeichen lang sein';
} }
return null; return null;
}, },
@@ -124,9 +124,13 @@ class _LoginPageState extends State<LoginPage> {
? const SizedBox( ? const SizedBox(
height: 20, height: 20,
width: 20, width: 20,
child: CircularProgressIndicator(strokeWidth: 2), child: CircularProgressIndicator(
strokeWidth: 2,
),
) )
: Text(_isRegistering ? 'Register' : 'Login'), : Text(
_isRegistering ? 'Registrieren' : 'Anmelden',
),
); );
}, },
), ),
@@ -141,8 +145,8 @@ class _LoginPageState extends State<LoginPage> {
}, },
child: Text( child: Text(
_isRegistering _isRegistering
? 'Already have an account? Login' ? 'Bereits ein Konto? Anmelden'
: 'Don\'t have an account? Register', : 'Noch kein Konto? Registrieren',
), ),
), ),
], ],
@@ -154,4 +158,3 @@ class _LoginPageState extends State<LoginPage> {
); );
} }
} }

View File

@@ -17,11 +17,11 @@ class _SettingsPageState extends State<SettingsPage> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: AppBar(title: Text("Splatournament")), appBar: AppBar(title: const Text('Einstellungen')),
body: Column( body: Column(
children: [ children: [
SizedBox(height: 24), const SizedBox(height: 24),
ProfileWidget(), const ProfileWidget(),
Column( Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
@@ -29,7 +29,7 @@ class _SettingsPageState extends State<SettingsPage> {
ListTile( ListTile(
leading: const Icon(Icons.logout, color: Colors.red), leading: const Icon(Icons.logout, color: Colors.red),
title: const Text( title: const Text(
'Sign Out', 'Abmelden',
style: TextStyle(color: Colors.red), style: TextStyle(color: Colors.red),
), ),
onTap: () { onTap: () {

View File

@@ -35,13 +35,19 @@ class _TournamentBracketPageState extends State<TournamentBracketPage> {
if (mounted) { if (mounted) {
setState(() {}); setState(() {});
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Bracket initialized successfully')), const SnackBar(
content: Text('Turnierbaum erfolgreich initialisiert'),
),
); );
} }
} catch (e) { } catch (e) {
if (mounted) { if (mounted) {
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Failed to initialize bracket: $e')), SnackBar(
content: Text(
'Initialisierung des Turnierbaums fehlgeschlagen: ${e.toString().replaceFirst('Exception: ', '')}',
),
),
); );
} }
} }
@@ -51,7 +57,7 @@ class _TournamentBracketPageState extends State<TournamentBracketPage> {
final result = await showDialog<int>( final result = await showDialog<int>(
context: context, context: context,
builder: (context) => AlertDialog( builder: (context) => AlertDialog(
title: const Text('Select Winner'), title: const Text('Sieger auswählen'),
content: Column( content: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
@@ -69,11 +75,11 @@ class _TournamentBracketPageState extends State<TournamentBracketPage> {
if (match.hasWinner) if (match.hasWinner)
TextButton( TextButton(
onPressed: () => Navigator.pop(context, -1), // Reset signal onPressed: () => Navigator.pop(context, -1), // Reset signal
child: const Text('Reset'), child: const Text('Zurücksetzen'),
), ),
TextButton( TextButton(
onPressed: () => Navigator.pop(context), onPressed: () => Navigator.pop(context),
child: const Text('Cancel'), child: const Text('Abbrechen'),
), ),
], ],
), ),
@@ -91,14 +97,22 @@ class _TournamentBracketPageState extends State<TournamentBracketPage> {
if (mounted) { if (mounted) {
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
SnackBar( SnackBar(
content: Text(result == -1 ? 'Match reset' : 'Winner set successfully'), content: Text(
result == -1
? 'Match zurückgesetzt'
: 'Sieger erfolgreich festgelegt',
),
), ),
); );
} }
} catch (e) { } catch (e) {
if (mounted) { if (mounted) {
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Error: $e')), SnackBar(
content: Text(
'Fehler: ${e.toString().replaceFirst('Exception: ', '')}',
),
),
); );
} }
} }
@@ -122,7 +136,11 @@ class _TournamentBracketPageState extends State<TournamentBracketPage> {
return const Center(child: CircularProgressIndicator()); return const Center(child: CircularProgressIndicator());
} }
if (snapshot.hasError) { if (snapshot.hasError) {
return Center(child: Text('Error: ${snapshot.error}')); return Center(
child: Text(
'Fehler: ${snapshot.error.toString().replaceFirst('Exception: ', '')}',
),
);
} }
final teams = snapshot.data![0] as List<Team>; final teams = snapshot.data![0] as List<Team>;
@@ -135,19 +153,19 @@ class _TournamentBracketPageState extends State<TournamentBracketPage> {
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
const Text( const Text(
'Bracket not initialized yet', 'Der Turnierbaum wurde noch nicht initialisiert',
style: TextStyle(fontSize: 18), style: TextStyle(fontSize: 18),
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
ElevatedButton( ElevatedButton(
onPressed: teams.length >= 2 ? _initializeBracket : null, onPressed: teams.length >= 2 ? _initializeBracket : null,
child: const Text('Initialize Bracket'), child: const Text('Turnierbaum initialisieren'),
), ),
if (teams.length < 2) if (teams.length < 2)
const Padding( const Padding(
padding: EdgeInsets.only(top: 8), padding: EdgeInsets.only(top: 8),
child: Text( child: Text(
'Need at least 2 teams', 'Mindestens 2 Teams erforderlich',
style: TextStyle(color: Colors.red), style: TextStyle(color: Colors.red),
), ),
), ),
@@ -208,11 +226,11 @@ class _BracketBoard extends StatelessWidget {
}); });
String _roundLabel(int round) { String _roundLabel(int round) {
if (round == roundCount - 1) return 'Winner'; if (round == roundCount - 1) return 'Sieger';
final teamsInRound = bracketSize ~/ (1 << round); final teamsInRound = bracketSize ~/ (1 << round);
if (teamsInRound == 2) return 'Final'; if (teamsInRound == 2) return 'Finale';
if (teamsInRound == 4) return 'Semi-finals'; if (teamsInRound == 4) return 'Halbfinale';
return 'Quarter-finals'; return 'Viertelfinale';
} }
double _cardTop(int round, int index) { double _cardTop(int round, int index) {
@@ -292,14 +310,14 @@ class _BracketBoard extends StatelessWidget {
} }
} }
: match != null && match.hasWinner : match != null && match.hasWinner
? () { ? () {
final team1 = teamMap[match.team1Id]; final team1 = teamMap[match.team1Id];
final team2 = teamMap[match.team2Id]; final team2 = teamMap[match.team2Id];
if (team1 != null && team2 != null) { if (team1 != null && team2 != null) {
onMatchTap(match, team1, team2); onMatchTap(match, team1, team2);
} }
} }
: null, : null,
), ),
), ),
); );
@@ -376,11 +394,7 @@ class _MatchCard extends StatelessWidget {
final Map<int, Team> teamMap; final Map<int, Team> teamMap;
final VoidCallback? onTap; final VoidCallback? onTap;
const _MatchCard({ const _MatchCard({this.match, required this.teamMap, this.onTap});
this.match,
required this.teamMap,
this.onTap,
});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@@ -430,7 +444,7 @@ class _MatchCard extends StatelessWidget {
), ),
const SizedBox(height: 4), const SizedBox(height: 4),
Text( Text(
'vs', 'vs.',
style: TextStyle( style: TextStyle(
fontSize: 10, fontSize: 10,
color: colorScheme.onSurface.withValues(alpha: 0.6), color: colorScheme.onSurface.withValues(alpha: 0.6),

View File

@@ -3,8 +3,15 @@ import 'package:frontend_splatournament_manager/models/team.dart';
import 'package:frontend_splatournament_manager/models/tournament.dart'; import 'package:frontend_splatournament_manager/models/tournament.dart';
import 'package:frontend_splatournament_manager/pages/tournament_bracket_page.dart'; import 'package:frontend_splatournament_manager/pages/tournament_bracket_page.dart';
import 'package:frontend_splatournament_manager/providers/team_provider.dart'; import 'package:frontend_splatournament_manager/providers/team_provider.dart';
import 'package:intl/intl.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
final DateFormat _tournamentDateFormat = DateFormat('dd.MM.yyyy', 'de_DE');
String _formatTournamentDate(String value) {
return _tournamentDateFormat.format(DateTime.parse(value));
}
class TournamentDetailPage extends StatefulWidget { class TournamentDetailPage extends StatefulWidget {
final Tournament tournament; final Tournament tournament;
@@ -29,7 +36,7 @@ class _TournamentDetailPageState extends State<TournamentDetailPage> {
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
const SnackBar( const SnackBar(
content: Text( content: Text(
'You are not a member of any team. Join or create a team first!', 'Du bist in keinem Team. Tritt zuerst einem Team bei oder erstelle eines.',
), ),
), ),
); );
@@ -40,7 +47,7 @@ class _TournamentDetailPageState extends State<TournamentDetailPage> {
context: context, context: context,
builder: (BuildContext dialogContext) { builder: (BuildContext dialogContext) {
return AlertDialog( return AlertDialog(
title: const Text('Select Your Team'), title: const Text('Team auswählen'),
content: SizedBox( content: SizedBox(
width: double.maxFinite, width: double.maxFinite,
child: ListView.builder( child: ListView.builder(
@@ -62,7 +69,7 @@ class _TournamentDetailPageState extends State<TournamentDetailPage> {
actions: [ actions: [
TextButton( TextButton(
onPressed: () => Navigator.pop(dialogContext), onPressed: () => Navigator.pop(dialogContext),
child: const Text('Cancel'), child: const Text('Abbrechen'),
), ),
], ],
); );
@@ -80,7 +87,9 @@ class _TournamentDetailPageState extends State<TournamentDetailPage> {
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
SnackBar( SnackBar(
content: Text('${selectedTeam.name} joined the tournament!'), content: Text(
'${selectedTeam.name} wurde für das Turnier angemeldet.',
),
), ),
); );
@@ -93,7 +102,7 @@ class _TournamentDetailPageState extends State<TournamentDetailPage> {
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
SnackBar( SnackBar(
content: Text( content: Text(
'Failed to join: ${e.toString().replaceAll('Exception: ', '')}', 'Anmeldung fehlgeschlagen: ${e.toString().replaceAll('Exception: ', '')}',
), ),
), ),
); );
@@ -101,9 +110,13 @@ class _TournamentDetailPageState extends State<TournamentDetailPage> {
} }
} catch (e) { } catch (e) {
if (!context.mounted) return; if (!context.mounted) return;
ScaffoldMessenger.of( ScaffoldMessenger.of(context).showSnackBar(
context, SnackBar(
).showSnackBar(SnackBar(content: Text('Failed to load your teams: $e'))); content: Text(
'Deine Teams konnten nicht geladen werden: ${e.toString().replaceFirst('Exception: ', '')}',
),
),
);
} }
} }
@@ -161,10 +174,10 @@ class _TournamentDetailPageState extends State<TournamentDetailPage> {
: null, : null,
child: Text( child: Text(
widget.tournament.isRegistrationFuture widget.tournament.isRegistrationFuture
? "Registration not open yet" ? 'Anmeldung noch nicht geöffnet'
: widget.tournament.isRegistrationPast : widget.tournament.isRegistrationPast
? "Registration closed" ? 'Anmeldung geschlossen'
: "Join with Team", : 'Mit Team anmelden',
), ),
), ),
), ),
@@ -205,7 +218,7 @@ class _TournamentTeamsWidgetState extends State<TournamentTeamsWidget> {
Padding( Padding(
padding: const EdgeInsets.fromLTRB(16, 12, 16, 4), padding: const EdgeInsets.fromLTRB(16, 12, 16, 4),
child: Text( child: Text(
'Registered Teams', 'Angemeldete Teams',
style: TextStyle(fontWeight: FontWeight.w600, fontSize: 17), style: TextStyle(fontWeight: FontWeight.w600, fontSize: 17),
), ),
), ),
@@ -217,11 +230,17 @@ class _TournamentTeamsWidgetState extends State<TournamentTeamsWidget> {
return Center(child: CircularProgressIndicator()); return Center(child: CircularProgressIndicator());
} }
if (snapshot.hasError) { if (snapshot.hasError) {
return Center(child: Text('Error: ${snapshot.error}')); return Center(
child: Text(
'Fehler: ${snapshot.error.toString().replaceFirst('Exception: ', '')}',
),
);
} }
final teams = snapshot.data ?? []; final teams = snapshot.data ?? [];
if (teams.isEmpty) { if (teams.isEmpty) {
return Center(child: Text('No teams registered yet.')); return const Center(
child: Text('Noch keine Teams angemeldet.'),
);
} }
return ListView.builder( return ListView.builder(
itemCount: teams.length, itemCount: teams.length,
@@ -257,7 +276,9 @@ class _TournamentTeamsWidgetState extends State<TournamentTeamsWidget> {
if (!context.mounted) return; if (!context.mounted) return;
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
SnackBar( SnackBar(
content: Text('Failed to remove team: $e'), content: Text(
'Team konnte nicht entfernt werden: ${e.toString().replaceFirst('Exception: ', '')}',
),
), ),
); );
} }
@@ -282,6 +303,9 @@ class TournamentContentWidget extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final registrationPeriod =
'${_formatTournamentDate(tournament.registrationStartDate)} - ${_formatTournamentDate(tournament.registrationEndDate)}';
return Expanded( return Expanded(
child: Column( child: Column(
children: [ children: [
@@ -300,24 +324,22 @@ class TournamentContentWidget extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text( Text(
"Registration Period", 'Anmeldezeitraum',
style: TextStyle( style: TextStyle(
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
fontSize: 17, fontSize: 17,
), ),
), ),
Text( Text(registrationPeriod),
"${tournament.registrationStartDate} - ${tournament.registrationEndDate}",
),
SizedBox(height: 24), SizedBox(height: 24),
Text( Text(
"Format", 'Format',
style: TextStyle( style: TextStyle(
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
fontSize: 17, fontSize: 17,
), ),
), ),
Text("Single Elimination"), const Text('Einfaches K.-o.-System'),
Spacer(), Spacer(),
SizedBox( SizedBox(
width: double.infinity, width: double.infinity,
@@ -331,7 +353,7 @@ class TournamentContentWidget extends StatelessWidget {
), ),
); );
}, },
child: Text("View ongoing"), child: const Text('Turnierbaum ansehen'),
), ),
), ),
], ],
@@ -382,7 +404,7 @@ class DetailHeader extends StatelessWidget {
InputChip( InputChip(
onPressed: () => onTeamsChipClicked(), onPressed: () => onTeamsChipClicked(),
label: Text( label: Text(
"${tournament.currentTeamAmount} out of ${tournament.maxTeamAmount} Teams", '${tournament.currentTeamAmount} von ${tournament.maxTeamAmount} Teams',
), ),
), ),
], ],

View File

@@ -7,10 +7,15 @@ import 'package:frontend_splatournament_manager/services/api_client.dart';
class MatchProvider extends ChangeNotifier { class MatchProvider extends ChangeNotifier {
Future<void> initializeBracket(int tournamentId) async { Future<void> initializeBracket(int tournamentId) async {
final response = await ApiClient.post('/tournaments/$tournamentId/bracket', {}); final response = await ApiClient.post(
'/tournaments/$tournamentId/bracket',
{},
);
if (response.statusCode != HttpStatus.created) { if (response.statusCode != HttpStatus.created) {
throw Exception('Failed to initialize bracket (${response.statusCode})'); throw Exception(
'Turnierbaum konnte nicht initialisiert werden (${response.statusCode})',
);
} }
notifyListeners(); notifyListeners();
@@ -20,7 +25,9 @@ class MatchProvider extends ChangeNotifier {
final response = await ApiClient.get('/tournaments/$tournamentId/matches'); final response = await ApiClient.get('/tournaments/$tournamentId/matches');
if (response.statusCode != HttpStatus.ok) { if (response.statusCode != HttpStatus.ok) {
throw Exception('Failed to load matches (${response.statusCode})'); throw Exception(
'Matches konnten nicht geladen werden (${response.statusCode})',
);
} }
final List<dynamic> list = json.decode(response.body); final List<dynamic> list = json.decode(response.body);
@@ -34,7 +41,9 @@ class MatchProvider extends ChangeNotifier {
if (response.statusCode != HttpStatus.ok) { if (response.statusCode != HttpStatus.ok) {
final error = json.decode(response.body); final error = json.decode(response.body);
throw Exception(error['error'] ?? 'Failed to set winner'); throw Exception(
error['error'] ?? 'Sieger konnte nicht festgelegt werden',
);
} }
notifyListeners(); notifyListeners();
@@ -44,7 +53,9 @@ class MatchProvider extends ChangeNotifier {
final response = await ApiClient.delete('/matches/$matchId/winner'); final response = await ApiClient.delete('/matches/$matchId/winner');
if (response.statusCode != HttpStatus.ok) { if (response.statusCode != HttpStatus.ok) {
throw Exception('Failed to reset match (${response.statusCode})'); throw Exception(
'Match konnte nicht zurückgesetzt werden (${response.statusCode})',
);
} }
notifyListeners(); notifyListeners();

View File

@@ -6,7 +6,6 @@ import 'package:frontend_splatournament_manager/models/tournament.dart';
import 'package:frontend_splatournament_manager/services/api_client.dart'; import 'package:frontend_splatournament_manager/services/api_client.dart';
class TournamentProvider extends ChangeNotifier { class TournamentProvider extends ChangeNotifier {
List<Tournament> _availableTournaments = []; List<Tournament> _availableTournaments = [];
Future<List<Tournament>>? _initialLoadFuture; Future<List<Tournament>>? _initialLoadFuture;
@@ -15,7 +14,9 @@ class TournamentProvider extends ChangeNotifier {
Future<List<Tournament>> _fetchTournaments() async { Future<List<Tournament>> _fetchTournaments() async {
final response = await ApiClient.get('/tournaments'); final response = await ApiClient.get('/tournaments');
if (response.statusCode != HttpStatus.ok) { if (response.statusCode != HttpStatus.ok) {
throw Exception('Failed to load tournaments (${response.statusCode})'); throw Exception(
'Turniere konnten nicht geladen werden (${response.statusCode})',
);
} }
final List<dynamic> list = json.decode(response.body); final List<dynamic> list = json.decode(response.body);
@@ -45,19 +46,22 @@ class TournamentProvider extends ChangeNotifier {
DateTime registrationStartDate, DateTime registrationStartDate,
DateTime registrationEndDate, DateTime registrationEndDate,
) async { ) async {
final response = await ApiClient.post( final response = await ApiClient.post('/tournaments', {
'/tournaments', 'name': name,
{ 'description': description,
'name': name, 'maxTeamAmount': maxTeamAmount,
'description': description, 'registrationStartDate': registrationStartDate.toIso8601String().split(
'maxTeamAmount': maxTeamAmount, 'T',
'registrationStartDate': registrationStartDate.toIso8601String().split('T')[0], )[0],
'registrationEndDate': registrationEndDate.toIso8601String().split('T')[0], 'registrationEndDate': registrationEndDate.toIso8601String().split(
}, 'T',
); )[0],
});
if (response.statusCode != HttpStatus.created) { if (response.statusCode != HttpStatus.created) {
throw Exception('Failed to create tournament (${response.statusCode})'); throw Exception(
'Turnier konnte nicht erstellt werden (${response.statusCode})',
);
} }
await refreshAvailableTournaments(); await refreshAvailableTournaments();

View File

@@ -4,7 +4,10 @@ import 'package:http/http.dart' as http;
class AuthService { class AuthService {
final String baseUrl = SplatournamentApp.baseUrl; final String baseUrl = SplatournamentApp.baseUrl;
Future<Map<String, dynamic>> register(String username, String password) async { Future<Map<String, dynamic>> register(
String username,
String password,
) async {
final response = await http.post( final response = await http.post(
Uri.parse('$baseUrl/register'), Uri.parse('$baseUrl/register'),
headers: {'Content-Type': 'application/json'}, headers: {'Content-Type': 'application/json'},
@@ -15,7 +18,7 @@ class AuthService {
return json.decode(response.body); return json.decode(response.body);
} else { } else {
final body = json.decode(response.body); final body = json.decode(response.body);
throw Exception(body['error'] ?? 'Registration failed'); throw Exception(body['error'] ?? 'Registrierung fehlgeschlagen');
} }
} }
@@ -30,8 +33,7 @@ class AuthService {
return json.decode(response.body); return json.decode(response.body);
} else { } else {
final body = json.decode(response.body); final body = json.decode(response.body);
throw Exception(body['error'] ?? 'Login failed'); throw Exception(body['error'] ?? 'Anmeldung fehlgeschlagen');
} }
} }
} }

View File

@@ -5,11 +5,12 @@ import 'package:frontend_splatournament_manager/models/team.dart';
import 'package:frontend_splatournament_manager/services/api_client.dart'; import 'package:frontend_splatournament_manager/services/api_client.dart';
class TeamService { class TeamService {
Future<List<Team>> getAllTeams() async { Future<List<Team>> getAllTeams() async {
final response = await ApiClient.get('/teams'); final response = await ApiClient.get('/teams');
if (response.statusCode != HttpStatus.ok) { if (response.statusCode != HttpStatus.ok) {
throw Exception('Failed to load teams (${response.statusCode})'); throw Exception(
'Teams konnten nicht geladen werden (${response.statusCode})',
);
} }
final List<dynamic> list = json.decode(response.body); final List<dynamic> list = json.decode(response.body);
return list.map((j) => Team.fromJson(j as Map<String, dynamic>)).toList(); return list.map((j) => Team.fromJson(j as Map<String, dynamic>)).toList();
@@ -18,10 +19,12 @@ class TeamService {
Future<Team> getTeamById(int id) async { Future<Team> getTeamById(int id) async {
final response = await ApiClient.get('/teams/$id'); final response = await ApiClient.get('/teams/$id');
if (response.statusCode == HttpStatus.notFound) { if (response.statusCode == HttpStatus.notFound) {
throw Exception('Team not found'); throw Exception('Team nicht gefunden');
} }
if (response.statusCode != HttpStatus.ok) { if (response.statusCode != HttpStatus.ok) {
throw Exception('Failed to load team (${response.statusCode})'); throw Exception(
'Team konnte nicht geladen werden (${response.statusCode})',
);
} }
return Team.fromJson(json.decode(response.body) as Map<String, dynamic>); return Team.fromJson(json.decode(response.body) as Map<String, dynamic>);
} }
@@ -31,13 +34,14 @@ class TeamService {
required String tag, required String tag,
String description = '', String description = '',
}) async { }) async {
final response = await ApiClient.post( final response = await ApiClient.post('/teams', {
'/teams', 'name': name,
{'name': name, 'tag': tag, 'description': description}, 'tag': tag,
); 'description': description,
});
if (response.statusCode != HttpStatus.created) { if (response.statusCode != HttpStatus.created) {
final body = json.decode(response.body); final body = json.decode(response.body);
throw Exception(body['error'] ?? 'Failed to create team'); throw Exception(body['error'] ?? 'Team konnte nicht erstellt werden');
} }
return Team.fromJson(json.decode(response.body) as Map<String, dynamic>); return Team.fromJson(json.decode(response.body) as Map<String, dynamic>);
} }
@@ -48,17 +52,14 @@ class TeamService {
String? tag, String? tag,
String? description, String? description,
}) async { }) async {
final response = await ApiClient.put( final response = await ApiClient.put('/teams/$id', {
'/teams/$id', 'name': name,
{ 'tag': tag,
if (name != null) 'name': name, 'description': description,
if (tag != null) 'tag': tag, });
if (description != null) 'description': description,
},
);
if (response.statusCode != HttpStatus.ok) { if (response.statusCode != HttpStatus.ok) {
final body = json.decode(response.body); final body = json.decode(response.body);
throw Exception(body['error'] ?? 'Failed to update team'); throw Exception(body['error'] ?? 'Team konnte nicht aktualisiert werden');
} }
} }
@@ -66,7 +67,7 @@ class TeamService {
final response = await ApiClient.delete('/teams/$id'); final response = await ApiClient.delete('/teams/$id');
if (response.statusCode != HttpStatus.ok) { if (response.statusCode != HttpStatus.ok) {
final body = json.decode(response.body); final body = json.decode(response.body);
throw Exception(body['error'] ?? 'Failed to delete team'); throw Exception(body['error'] ?? 'Team konnte nicht gelöscht werden');
} }
} }
@@ -74,7 +75,7 @@ class TeamService {
final response = await ApiClient.get('/tournaments/$tournamentId/teams'); final response = await ApiClient.get('/tournaments/$tournamentId/teams');
if (response.statusCode != HttpStatus.ok) { if (response.statusCode != HttpStatus.ok) {
throw Exception( throw Exception(
'Failed to load teams for tournament (${response.statusCode})', 'Teams für das Turnier konnten nicht geladen werden (${response.statusCode})',
); );
} }
final List<dynamic> list = json.decode(response.body); final List<dynamic> list = json.decode(response.body);
@@ -82,24 +83,29 @@ class TeamService {
} }
Future<void> registerTeamForTournament(int tournamentId, int teamId) async { Future<void> registerTeamForTournament(int tournamentId, int teamId) async {
final response = await ApiClient.post( final response = await ApiClient.post('/tournaments/$tournamentId/teams', {
'/tournaments/$tournamentId/teams', 'teamId': teamId,
{'teamId': teamId}, });
);
if (response.statusCode == 409) { if (response.statusCode == 409) {
throw Exception('Team is already registered for this tournament'); throw Exception('Das Team ist bereits für dieses Turnier angemeldet');
} }
if (response.statusCode != HttpStatus.created) { if (response.statusCode != HttpStatus.created) {
final body = json.decode(response.body); final body = json.decode(response.body);
throw Exception(body['error'] ?? 'Failed to register team'); throw Exception(
body['error'] ?? 'Team konnte nicht für das Turnier angemeldet werden',
);
} }
} }
Future<void> removeTeamFromTournament(int tournamentId, int teamId) async { Future<void> removeTeamFromTournament(int tournamentId, int teamId) async {
final response = await ApiClient.delete('/tournaments/$tournamentId/teams/$teamId'); final response = await ApiClient.delete(
'/tournaments/$tournamentId/teams/$teamId',
);
if (response.statusCode != HttpStatus.ok) { if (response.statusCode != HttpStatus.ok) {
final body = json.decode(response.body); final body = json.decode(response.body);
throw Exception(body['error'] ?? 'Failed to remove team from tournament'); throw Exception(
body['error'] ?? 'Team konnte nicht aus dem Turnier entfernt werden',
);
} }
} }
@@ -107,7 +113,7 @@ class TeamService {
final response = await ApiClient.get('/teams/$teamId/tournaments'); final response = await ApiClient.get('/teams/$teamId/tournaments');
if (response.statusCode != HttpStatus.ok) { if (response.statusCode != HttpStatus.ok) {
throw Exception( throw Exception(
'Failed to load tournaments for team (${response.statusCode})', 'Turniere für das Team konnten nicht geladen werden (${response.statusCode})',
); );
} }
final List<dynamic> list = json.decode(response.body); final List<dynamic> list = json.decode(response.body);
@@ -117,7 +123,9 @@ class TeamService {
Future<List<Team>> getUserTeams() async { Future<List<Team>> getUserTeams() async {
final response = await ApiClient.get('/users/me/teams'); final response = await ApiClient.get('/users/me/teams');
if (response.statusCode != HttpStatus.ok) { if (response.statusCode != HttpStatus.ok) {
throw Exception('Failed to load user teams (${response.statusCode})'); throw Exception(
'Eigene Teams konnten nicht geladen werden (${response.statusCode})',
);
} }
final List<dynamic> list = json.decode(response.body); final List<dynamic> list = json.decode(response.body);
return list.map((j) => Team.fromJson(j as Map<String, dynamic>)).toList(); return list.map((j) => Team.fromJson(j as Map<String, dynamic>)).toList();
@@ -126,11 +134,11 @@ class TeamService {
Future<void> joinTeam(int teamId) async { Future<void> joinTeam(int teamId) async {
final response = await ApiClient.post('/teams/$teamId/members', {}); final response = await ApiClient.post('/teams/$teamId/members', {});
if (response.statusCode == 409) { if (response.statusCode == 409) {
throw Exception('You are already a member of this team'); throw Exception('Du bist bereits Mitglied dieses Teams');
} }
if (response.statusCode != HttpStatus.created) { if (response.statusCode != HttpStatus.created) {
final body = json.decode(response.body); final body = json.decode(response.body);
throw Exception(body['error'] ?? 'Failed to join team'); throw Exception(body['error'] ?? 'Beitritt zum Team fehlgeschlagen');
} }
} }
@@ -138,14 +146,16 @@ class TeamService {
final response = await ApiClient.delete('/teams/$teamId/members/me'); final response = await ApiClient.delete('/teams/$teamId/members/me');
if (response.statusCode != HttpStatus.ok) { if (response.statusCode != HttpStatus.ok) {
final body = json.decode(response.body); final body = json.decode(response.body);
throw Exception(body['error'] ?? 'Failed to leave team'); throw Exception(body['error'] ?? 'Team konnte nicht verlassen werden');
} }
} }
Future<List<Map<String, dynamic>>> getTeamMembers(int teamId) async { Future<List<Map<String, dynamic>>> getTeamMembers(int teamId) async {
final response = await ApiClient.get('/teams/$teamId/members'); final response = await ApiClient.get('/teams/$teamId/members');
if (response.statusCode != HttpStatus.ok) { if (response.statusCode != HttpStatus.ok) {
throw Exception('Failed to load team members (${response.statusCode})'); throw Exception(
'Teammitglieder konnten nicht geladen werden (${response.statusCode})',
);
} }
final List<dynamic> list = json.decode(response.body); final List<dynamic> list = json.decode(response.body);
return list.cast<Map<String, dynamic>>(); return list.cast<Map<String, dynamic>>();

View File

@@ -13,7 +13,7 @@ class AvailableTournamentList extends StatelessWidget {
padding: EdgeInsets.fromLTRB(24, 0, 24, 0), padding: EdgeInsets.fromLTRB(24, 0, 24, 0),
child: Column( child: Column(
children: [ children: [
Row(children: [Text("Available Tournaments")]), const Row(children: [Text('Verfügbare Turniere')]),
SizedBox( SizedBox(
width: double.infinity, width: double.infinity,
height: 350, height: 350,
@@ -40,18 +40,21 @@ class TournamentListFutureBuilder extends StatelessWidget {
future: provider.ensureTournamentsLoaded(), future: provider.ensureTournamentsLoaded(),
builder: (context, snapshot) { builder: (context, snapshot) {
final list = provider.availableTournaments; final list = provider.availableTournaments;
print(list);
if (snapshot.connectionState == ConnectionState.waiting && if (snapshot.connectionState == ConnectionState.waiting &&
list.isEmpty) { list.isEmpty) {
return Center(child: CircularProgressIndicator()); return Center(child: CircularProgressIndicator());
} }
if (snapshot.hasError && list.isEmpty) { if (snapshot.hasError && list.isEmpty) {
return Center(child: Text('Error: ${snapshot.error}')); return Center(
child: Text(
'Fehler: ${snapshot.error.toString().replaceFirst('Exception: ', '')}',
),
);
} }
if (list.isEmpty) { if (list.isEmpty) {
return Center(child: Text('No tournaments found')); return const Center(child: Text('Keine Turniere gefunden'));
} }
return ListView.builder( return ListView.builder(

View File

@@ -20,7 +20,10 @@ class _MyTeamsWidgetState extends State<MyTeamsWidget> {
} }
void _loadMyTeams() { void _loadMyTeams() {
_myTeamsFuture = Provider.of<TeamProvider>(context, listen: false).getUserTeams(); _myTeamsFuture = Provider.of<TeamProvider>(
context,
listen: false,
).getUserTeams();
} }
@override @override
@@ -33,13 +36,19 @@ class _MyTeamsWidgetState extends State<MyTeamsWidget> {
} }
if (snapshot.hasError) { if (snapshot.hasError) {
return Center(child: Text('Error: ${snapshot.error}')); return Center(
child: Text(
'Fehler: ${snapshot.error.toString().replaceFirst('Exception: ', '')}',
),
);
} }
final teams = snapshot.data ?? []; final teams = snapshot.data ?? [];
if (teams.isEmpty) { if (teams.isEmpty) {
return const Center( return const Center(
child: Text('You are not in any teams yet\nJoin teams from the All Teams tab'), child: Text(
'Du bist noch in keinem Team.\nTritt einem Team im Tab Alle Teams bei.',
),
); );
} }
@@ -54,9 +63,11 @@ class _MyTeamsWidgetState extends State<MyTeamsWidget> {
Widget _buildTeamCard(Team team) { Widget _buildTeamCard(Team team) {
final memberCountText = team.memberCount != null final memberCountText = team.memberCount != null
? '${team.memberCount}/4 members' ? '${team.memberCount}/4 Mitglieder'
: 'No members'; : 'Keine Mitglieder';
final description = team.description.isEmpty ? 'No description' : team.description; final description = team.description.isEmpty
? 'Keine Beschreibung'
: team.description;
return Card( return Card(
margin: const EdgeInsets.only(bottom: 12), margin: const EdgeInsets.only(bottom: 12),
@@ -79,16 +90,16 @@ class _MyTeamsWidgetState extends State<MyTeamsWidget> {
final confirmed = await showDialog<bool>( final confirmed = await showDialog<bool>(
context: context, context: context,
builder: (context) => AlertDialog( builder: (context) => AlertDialog(
title: const Text('Leave Team?'), title: const Text('Team verlassen?'),
content: Text('Leave "${team.name}"?'), content: Text('Soll "${team.name}" verlassen werden?'),
actions: [ actions: [
TextButton( TextButton(
onPressed: () => Navigator.pop(context, false), onPressed: () => Navigator.pop(context, false),
child: const Text('Cancel'), child: const Text('Abbrechen'),
), ),
TextButton( TextButton(
onPressed: () => Navigator.pop(context, true), onPressed: () => Navigator.pop(context, true),
child: const Text('Leave', style: TextStyle(color: Colors.red)), child: const Text('Verlassen', style: TextStyle(color: Colors.red)),
), ),
], ],
), ),
@@ -96,18 +107,25 @@ class _MyTeamsWidgetState extends State<MyTeamsWidget> {
if (confirmed == true && mounted) { if (confirmed == true && mounted) {
try { try {
await Provider.of<TeamProvider>(context, listen: false).leaveTeam(team.id); await Provider.of<TeamProvider>(
context,
listen: false,
).leaveTeam(team.id);
if (mounted) { if (mounted) {
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(
const SnackBar(content: Text('Left team')), context,
); ).showSnackBar(const SnackBar(content: Text('Team verlassen')));
_loadMyTeams(); _loadMyTeams();
setState(() {}); setState(() {});
} }
} catch (e) { } catch (e) {
if (mounted) { if (mounted) {
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Error: $e')), SnackBar(
content: Text(
'Fehler: ${e.toString().replaceFirst('Exception: ', '')}',
),
),
); );
} }
} }

View File

@@ -38,7 +38,7 @@ class _MyTournamentsCarouselState extends State<MyTournamentsCarousel> {
const Padding( const Padding(
padding: EdgeInsets.fromLTRB(16, 16, 16, 8), padding: EdgeInsets.fromLTRB(16, 16, 16, 8),
child: Text( child: Text(
'My Tournaments', 'Meine Turniere',
style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold), style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
), ),
), ),
@@ -58,7 +58,7 @@ class _MyTournamentsCarouselState extends State<MyTournamentsCarousel> {
), ),
const SizedBox(height: 8), const SizedBox(height: 8),
const Text( const Text(
'No tournaments found', 'Keine Turniere gefunden',
style: TextStyle(fontSize: 16, color: Colors.grey), style: TextStyle(fontSize: 16, color: Colors.grey),
), ),
], ],
@@ -77,7 +77,7 @@ class _MyTournamentsCarouselState extends State<MyTournamentsCarousel> {
const Padding( const Padding(
padding: EdgeInsets.fromLTRB(16, 16, 16, 8), padding: EdgeInsets.fromLTRB(16, 16, 16, 8),
child: Text( child: Text(
'My Tournaments', 'Meine Turniere',
style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold), style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
), ),
), ),
@@ -162,13 +162,13 @@ class _TournamentCard extends StatelessWidget {
const SizedBox(width: 4), const SizedBox(width: 4),
Text( Text(
tournament.isRegistrationOpen tournament.isRegistrationOpen
? 'Registration Open' ? 'Anmeldung offen'
: 'Registration Closed', : 'Anmeldung geschlossen',
style: const TextStyle(fontSize: 14), style: const TextStyle(fontSize: 14),
), ),
const Spacer(), const Spacer(),
Text( Text(
'${tournament.currentTeamAmount}/${tournament.maxTeamAmount} teams', '${tournament.currentTeamAmount}/${tournament.maxTeamAmount} Teams',
style: const TextStyle(fontSize: 14), style: const TextStyle(fontSize: 14),
), ),
], ],

View File

@@ -14,7 +14,7 @@ class _ProfileWidgetState extends State<ProfileWidget> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Consumer<AuthProvider>( return Consumer<AuthProvider>(
builder: (context, provider, child) { builder: (context, provider, child) {
final username = provider.username ?? "Unknown User"; final username = provider.username ?? 'Unbekannter Benutzer';
final avatarText = username.length >= 3 final avatarText = username.length >= 3
? username.substring(0, 3).toUpperCase() ? username.substring(0, 3).toUpperCase()
: username.toUpperCase(); : username.toUpperCase();

View File

@@ -18,21 +18,27 @@ class TeamsListWidget extends StatelessWidget {
builder: (context, snapshot) { builder: (context, snapshot) {
final teams = provider.teams; final teams = provider.teams;
if (snapshot.connectionState == ConnectionState.waiting && teams.isEmpty) { if (snapshot.connectionState == ConnectionState.waiting &&
teams.isEmpty) {
return const Center(child: CircularProgressIndicator()); return const Center(child: CircularProgressIndicator());
} }
if (snapshot.hasError && teams.isEmpty) { if (snapshot.hasError && teams.isEmpty) {
return Center(child: Text('Error: ${snapshot.error}')); return Center(
child: Text(
'Fehler: ${snapshot.error.toString().replaceFirst('Exception: ', '')}',
),
);
} }
if (teams.isEmpty) { if (teams.isEmpty) {
return const Center(child: Text('No teams found')); return const Center(child: Text('Keine Teams gefunden'));
} }
return ListView.builder( return ListView.builder(
itemCount: teams.length, itemCount: teams.length,
itemBuilder: (context, index) => TeamListItem(team: teams[index]), itemBuilder: (context, index) =>
TeamListItem(team: teams[index]),
); );
}, },
); );
@@ -50,9 +56,11 @@ class TeamListItem extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final memberCountText = team.memberCount != null final memberCountText = team.memberCount != null
? '${team.memberCount}/4 members' ? '${team.memberCount}/4 Mitglieder'
: 'No members'; : 'Keine Mitglieder';
final description = team.description.isEmpty ? 'No description' : team.description; final description = team.description.isEmpty
? 'Keine Beschreibung'
: team.description;
return Card( return Card(
margin: const EdgeInsets.only(bottom: 12), margin: const EdgeInsets.only(bottom: 12),
@@ -66,11 +74,11 @@ class TeamListItem extends StatelessWidget {
trailing: PopupMenuButton( trailing: PopupMenuButton(
icon: const Icon(Icons.more_vert), icon: const Icon(Icons.more_vert),
itemBuilder: (context) => [ itemBuilder: (context) => [
const PopupMenuItem(value: 'join', child: Text('Join Team')), const PopupMenuItem(value: 'join', child: Text('Team beitreten')),
const PopupMenuItem(value: 'edit', child: Text('Edit Team')), const PopupMenuItem(value: 'edit', child: Text('Team bearbeiten')),
const PopupMenuItem( const PopupMenuItem(
value: 'delete', value: 'delete',
child: Text('Delete Team', style: TextStyle(color: Colors.red)), child: Text('Team löschen', style: TextStyle(color: Colors.red)),
), ),
], ],
onSelected: (value) async { onSelected: (value) async {
@@ -98,7 +106,7 @@ class TeamListItem extends StatelessWidget {
await Provider.of<TeamProvider>(context, listen: false).joinTeam(team.id); await Provider.of<TeamProvider>(context, listen: false).joinTeam(team.id);
if (context.mounted) { if (context.mounted) {
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Joined ${team.name}!')), SnackBar(content: Text('Du bist ${team.name} beigetreten.')),
); );
} }
} catch (e) { } catch (e) {
@@ -114,16 +122,18 @@ class TeamListItem extends StatelessWidget {
final confirmed = await showDialog<bool>( final confirmed = await showDialog<bool>(
context: context, context: context,
builder: (context) => AlertDialog( builder: (context) => AlertDialog(
title: const Text('Delete Team?'), title: const Text('Team löschen?'),
content: Text('Delete "${team.name}"? This cannot be undone.'), content: Text(
'Soll "${team.name}" gelöscht werden? Das kann nicht rückgängig gemacht werden.',
),
actions: [ actions: [
TextButton( TextButton(
onPressed: () => Navigator.pop(context, false), onPressed: () => Navigator.pop(context, false),
child: const Text('Cancel'), child: const Text('Abbrechen'),
), ),
TextButton( TextButton(
onPressed: () => Navigator.pop(context, true), onPressed: () => Navigator.pop(context, true),
child: const Text('Delete', style: TextStyle(color: Colors.red)), child: const Text('Löschen', style: TextStyle(color: Colors.red)),
), ),
], ],
), ),
@@ -131,16 +141,23 @@ class TeamListItem extends StatelessWidget {
if (confirmed == true && context.mounted) { if (confirmed == true && context.mounted) {
try { try {
await Provider.of<TeamProvider>(context, listen: false).deleteTeam(team.id); await Provider.of<TeamProvider>(
context,
listen: false,
).deleteTeam(team.id);
if (context.mounted) { if (context.mounted) {
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(
const SnackBar(content: Text('Team deleted')), context,
); ).showSnackBar(const SnackBar(content: Text('Team gelöscht')));
} }
} catch (e) { } catch (e) {
if (context.mounted) { if (context.mounted) {
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Error: $e')), SnackBar(
content: Text(
'Fehler: ${e.toString().replaceFirst('Exception: ', '')}',
),
),
); );
} }
} }

View File

@@ -9,21 +9,21 @@ class ThemeSelectorWidget extends StatelessWidget {
final List<DropdownMenuItem<AppThemeOption>> dropdownElements = [ final List<DropdownMenuItem<AppThemeOption>> dropdownElements = [
const DropdownMenuItem( const DropdownMenuItem(
value: AppThemeOption.lightBlue, value: AppThemeOption.lightBlue,
child: Text("Light Blue"), child: Text('Helles Blau'),
), ),
const DropdownMenuItem( const DropdownMenuItem(
value: AppThemeOption.darkPurple, value: AppThemeOption.darkPurple,
child: Text("Dark Purple"), child: Text('Dunkles Lila'),
), ),
const DropdownMenuItem( const DropdownMenuItem(
value: AppThemeOption.lightMint, value: AppThemeOption.lightMint,
child: Text("Light Mint"), child: Text('Helles Mint'),
), ),
const DropdownMenuItem( const DropdownMenuItem(
value: AppThemeOption.darkAmber, value: AppThemeOption.darkAmber,
child: Text("Dark Amber"), child: Text('Dunkles Bernstein'),
), ),
const DropdownMenuItem(value: AppThemeOption.system, child: Text("System")), const DropdownMenuItem(value: AppThemeOption.system, child: Text('System')),
]; ];
@override @override
@@ -39,7 +39,7 @@ class ThemeSelectorWidget extends StatelessWidget {
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Text("Theme"), const Text('Design'),
SizedBox( SizedBox(
width: 250, width: 250,
child: DropdownButtonFormField<AppThemeOption>( child: DropdownButtonFormField<AppThemeOption>(

View File

@@ -45,11 +45,11 @@ static void my_application_activate(GApplication* application) {
if (use_header_bar) { if (use_header_bar) {
GtkHeaderBar* header_bar = GTK_HEADER_BAR(gtk_header_bar_new()); GtkHeaderBar* header_bar = GTK_HEADER_BAR(gtk_header_bar_new());
gtk_widget_show(GTK_WIDGET(header_bar)); gtk_widget_show(GTK_WIDGET(header_bar));
gtk_header_bar_set_title(header_bar, "frontend_splatournament_manager"); gtk_header_bar_set_title(header_bar, "Splatournament Manager");
gtk_header_bar_set_show_close_button(header_bar, TRUE); gtk_header_bar_set_show_close_button(header_bar, TRUE);
gtk_window_set_titlebar(window, GTK_WIDGET(header_bar)); gtk_window_set_titlebar(window, GTK_WIDGET(header_bar));
} else { } else {
gtk_window_set_title(window, "frontend_splatournament_manager"); gtk_window_set_title(window, "Splatournament Manager");
} }
gtk_window_set_default_size(window, 1280, 720); gtk_window_set_default_size(window, 1280, 720);

View File

@@ -41,6 +41,8 @@ dependencies:
flutter_secure_storage: ^10.0.0 flutter_secure_storage: ^10.0.0
jwt_decoder: ^2.0.1 jwt_decoder: ^2.0.1
shared_preferences: ^2.3.3 shared_preferences: ^2.3.3
flutter_localizations:
sdk: flutter
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:

View File

@@ -1,5 +1,5 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html lang="de">
<head> <head>
<!-- <!--
If you are serving your web app in a path other than the root, change the If you are serving your web app in a path other than the root, change the
@@ -18,18 +18,18 @@
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta content="IE=Edge" http-equiv="X-UA-Compatible"> <meta content="IE=Edge" http-equiv="X-UA-Compatible">
<meta name="description" content="A tournament Manager for Splatoon"> <meta name="description" content="Ein Turnier-Manager für Splatoon">
<!-- iOS meta tags & icons --> <!-- iOS meta tags & icons -->
<meta name="mobile-web-app-capable" content="yes"> <meta name="mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black"> <meta name="apple-mobile-web-app-status-bar-style" content="black">
<meta name="apple-mobile-web-app-title" content="frontend_splatournament_manager"> <meta name="apple-mobile-web-app-title" content="Splatournament Manager">
<link rel="apple-touch-icon" href="icons/Icon-192.png"> <link rel="apple-touch-icon" href="icons/Icon-192.png">
<!-- Favicon --> <!-- Favicon -->
<link rel="icon" type="image/png" href="favicon.png"/> <link rel="icon" type="image/png" href="favicon.png"/>
<title>frontend_splatournament_manager</title> <title>Splatournament Manager</title>
<link rel="manifest" href="manifest.json"> <link rel="manifest" href="manifest.json">
</head> </head>
<body> <body>

View File

@@ -1,11 +1,11 @@
{ {
"name": "frontend_splatournament_manager", "name": "Splatournament Manager",
"short_name": "frontend_splatournament_manager", "short_name": "Splatournament",
"start_url": ".", "start_url": ".",
"display": "standalone", "display": "standalone",
"background_color": "#0175C2", "background_color": "#0175C2",
"theme_color": "#0175C2", "theme_color": "#0175C2",
"description": "A tournament Manager for Splatoon", "description": "Ein Turnier-Manager für Splatoon",
"orientation": "portrait-primary", "orientation": "portrait-primary",
"prefer_related_applications": false, "prefer_related_applications": false,
"icons": [ "icons": [

View File

@@ -90,12 +90,12 @@ BEGIN
BLOCK "040904e4" BLOCK "040904e4"
BEGIN BEGIN
VALUE "CompanyName", "com.tikaiz" "\0" VALUE "CompanyName", "com.tikaiz" "\0"
VALUE "FileDescription", "frontend_splatournament_manager" "\0" VALUE "FileDescription", "Splatournament Manager" "\0"
VALUE "FileVersion", VERSION_AS_STRING "\0" VALUE "FileVersion", VERSION_AS_STRING "\0"
VALUE "InternalName", "frontend_splatournament_manager" "\0" VALUE "InternalName", "frontend_splatournament_manager" "\0"
VALUE "LegalCopyright", "Copyright (C) 2026 com.tikaiz. All rights reserved." "\0" VALUE "LegalCopyright", "Copyright (C) 2026 com.tikaiz. All rights reserved." "\0"
VALUE "OriginalFilename", "frontend_splatournament_manager.exe" "\0" VALUE "OriginalFilename", "frontend_splatournament_manager.exe" "\0"
VALUE "ProductName", "frontend_splatournament_manager" "\0" VALUE "ProductName", "Splatournament Manager" "\0"
VALUE "ProductVersion", VERSION_AS_STRING "\0" VALUE "ProductVersion", VERSION_AS_STRING "\0"
END END
END END

View File

@@ -27,7 +27,7 @@ int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev,
FlutterWindow window(project); FlutterWindow window(project);
Win32Window::Point origin(10, 10); Win32Window::Point origin(10, 10);
Win32Window::Size size(1280, 720); Win32Window::Size size(1280, 720);
if (!window.Create(L"frontend_splatournament_manager", origin, size)) { if (!window.Create(L"Splatournament Manager", origin, size)) {
return EXIT_FAILURE; return EXIT_FAILURE;
} }
window.SetQuitOnClose(true); window.SetQuitOnClose(true);