-
-
+
+
{children}
diff --git a/src/app/api/schedule/route.ts b/src/app/api/schedule/route.ts
index e2635d7..ab33e63 100644
--- a/src/app/api/schedule/route.ts
+++ b/src/app/api/schedule/route.ts
@@ -2,7 +2,7 @@
'use server'
import { NextResponse } from 'next/server'
-import { prisma } from '../../lib/prisma'
+import { prisma } from '@/lib/prisma'
// Helper: Prisma-User -> Player
const toPlayer = (u: any) => ({
diff --git a/src/app/api/team/available-users/route.ts b/src/app/api/team/available-users/route.ts
index c8ebd35..ea398a7 100644
--- a/src/app/api/team/available-users/route.ts
+++ b/src/app/api/team/available-users/route.ts
@@ -1,60 +1,49 @@
-// /src/app/api/team/available-users/route.ts
import { NextRequest, NextResponse } from 'next/server'
import { prisma } from '@/lib/prisma'
export async function GET(req: NextRequest) {
try {
const { searchParams } = new URL(req.url)
- const teamId = searchParams.get('teamId') ?? undefined
+ const teamId = searchParams.get('teamId')
+ if (!teamId) {
+ return NextResponse.json({ message: 'teamId fehlt' }, { status: 400 })
+ }
- // 1) Nur Pending-Invites DIESES Teams laden (damit wir doppelte Einladungen dieses Teams vermeiden)
+ // 1) Dieses Team laden, um bestehende Mitglieder zu kennen
+ const team = await prisma.team.findUnique({
+ where: { id: teamId },
+ select: { activePlayers: true, inactivePlayers: true }
+ })
+ if (!team) {
+ return NextResponse.json({ message: 'Team nicht gefunden' }, { status: 404 })
+ }
+ const membersOfThisTeam = new Set([
+ ...team.activePlayers,
+ ...team.inactivePlayers
+ ])
+
+ // 2) Pending-Invites DIESES Teams
const pendingInvites = await prisma.teamInvite.findMany({
- where: teamId ? { teamId } : undefined,
- include: {
- user: {
- select: {
- steamId : true,
- name : true,
- avatar : true,
- location : true,
- premierRank: true,
- team : true,
- },
- },
- },
+ where: { teamId },
+ select: { steamId: true }
})
+ const invitedByThisTeam = new Set(pendingInvites.map(i => i.steamId))
- // 2) Nur die von DIESEM Team bereits eingeladenen Steam-IDs
- const invitedByThisTeam = new Set(
- pendingInvites.map(inv => inv.user?.steamId).filter(Boolean) as string[]
- )
-
- // 3) (Optional/robust) Mitglieder aller Teams sammeln – doppelt gemoppelt zu where: { team: null },
- // aber falls du später das where lockerst, bleibt es sicher.
- const teams = await prisma.team.findMany({
- select: { activePlayers: true, inactivePlayers: true },
- })
- const teamMemberIds = new Set(
- teams.flatMap(t => [...t.activePlayers, ...t.inactivePlayers])
- )
-
- // 4) Nur Nutzer ohne Team laden
+ // 3) Alle User (oder mit Suche filtern, wenn du willst)
const allUsers = await prisma.user.findMany({
- where: { team: null }, // hat noch kein Team
select: {
steamId : true,
name : true,
avatar : true,
location : true,
- premierRank: true,
+ premierRank: true
},
- orderBy: { name: 'asc' },
+ orderBy: { name: 'asc' }
})
- // 5) Verfügbar = kein Mitglied + NICHT bereits von DIESEM Team eingeladen
+ // 4) Verfügbar = NICHT schon Mitglied DIESES Teams + NICHT von DIESEM Team eingeladen
const availableUsers = allUsers.filter(u =>
- !teamMemberIds.has(u.steamId) &&
- !invitedByThisTeam.has(u.steamId)
+ !membersOfThisTeam.has(u.steamId) && !invitedByThisTeam.has(u.steamId)
)
return NextResponse.json({ users: availableUsers })
diff --git a/src/app/api/team/transfer-leader/route.ts b/src/app/api/team/transfer-leader/route.ts
index 32a91e7..72fd8f9 100644
--- a/src/app/api/team/transfer-leader/route.ts
+++ b/src/app/api/team/transfer-leader/route.ts
@@ -8,7 +8,6 @@ export const dynamic = 'force-dynamic'
export async function POST(req: NextRequest) {
try {
const { teamId, newLeaderSteamId } = await req.json()
-
if (!teamId || !newLeaderSteamId) {
return NextResponse.json({ message: 'Fehlende Parameter' }, { status: 400 })
}
@@ -17,28 +16,36 @@ export async function POST(req: NextRequest) {
where: { id: teamId },
select: { id: true, name: true, leaderId: true, activePlayers: true, inactivePlayers: true },
})
- if (!team) {
- return NextResponse.json({ message: 'Team nicht gefunden.' }, { status: 404 })
+ if (!team) return NextResponse.json({ message: 'Team nicht gefunden.' }, { status: 404 })
+
+ // ❗ Bereits Leader eines anderen Teams?
+ const otherLedTeam = await prisma.team.findFirst({
+ where: { leaderId: newLeaderSteamId, NOT: { id: teamId } },
+ select: { id: true, name: true }
+ })
+ if (otherLedTeam) {
+ return NextResponse.json(
+ { message: `Dieser Spieler ist bereits Leader von "${otherLedTeam.name}".` },
+ { status: 400 }
+ )
}
const allPlayerIds = Array.from(new Set([
...(team.activePlayers ?? []),
...(team.inactivePlayers ?? []),
- team.leaderId, // alter Leader (kann null sein)
+ team.leaderId,
].filter(Boolean) as string[]))
- // Neuer Leader muss Mitglied sein
if (!allPlayerIds.includes(newLeaderSteamId)) {
return NextResponse.json({ message: 'Neuer Leader ist kein Teammitglied.' }, { status: 400 })
}
- // Leader setzen
await prisma.team.update({
where: { id: teamId },
data : { leaderId: newLeaderSteamId },
})
- // Namen neuer Leader
+ // --- Benachrichtigung & SSE unverändert ---
const newLeader = await prisma.user.findUnique({
where : { steamId: newLeaderSteamId },
select: { name: true },
@@ -47,7 +54,6 @@ export async function POST(req: NextRequest) {
const textForOthers =
`${newLeader?.name ?? 'Ein Spieler'} ist jetzt Teamleader von "${team.name}".`
- // 1) Notification an neuen Leader (sichtbar + live)
const leaderNote = await prisma.notification.create({
data: {
steamId : newLeaderSteamId,
@@ -58,47 +64,37 @@ export async function POST(req: NextRequest) {
},
})
await sendServerSSEMessage({
- type : 'notification',
+ type: 'notification',
targetUserIds: [newLeaderSteamId],
- message : leaderNote.message,
- id : leaderNote.id,
- actionType : leaderNote.actionType ?? undefined,
- actionData : leaderNote.actionData ?? undefined,
- createdAt : leaderNote.createdAt.toISOString(),
+ message: leaderNote.message,
+ id: leaderNote.id,
+ actionType: leaderNote.actionType ?? undefined,
+ actionData: leaderNote.actionData ?? undefined,
+ createdAt: leaderNote.createdAt.toISOString(),
})
- // 2) Info an alle anderen (sichtbar + live)
const others = allPlayerIds.filter(id => id !== newLeaderSteamId)
if (others.length) {
- const notes = await Promise.all(
- others.map(steamId =>
- prisma.notification.create({
- data: {
- steamId,
- title: 'Neuer Teamleader',
- message: textForOthers,
- actionType: 'team-leader-changed',
- actionData: newLeaderSteamId,
- },
- })
- )
- )
-
- await Promise.all(
- notes.map(n =>
- sendServerSSEMessage({
- type: 'notification',
- targetUserIds: [n.steamId],
- message: n.message,
- id: n.id,
- actionType: n.actionType ?? undefined,
- actionData: n.actionData ?? undefined,
- createdAt: n.createdAt.toISOString(),
- })
- )
- )
-
- // zusätzliches Team-Event (für SSEHandler → soft reload)
+ const notes = await Promise.all(others.map(steamId =>
+ prisma.notification.create({
+ data: {
+ steamId,
+ title: 'Neuer Teamleader',
+ message: textForOthers,
+ actionType: 'team-leader-changed',
+ actionData: newLeaderSteamId,
+ },
+ })
+ ))
+ await Promise.all(notes.map(n => sendServerSSEMessage({
+ type: 'notification',
+ targetUserIds: [n.steamId],
+ message: n.message,
+ id: n.id,
+ actionType: n.actionType ?? undefined,
+ actionData: n.actionData ?? undefined,
+ createdAt: n.createdAt.toISOString(),
+ })))
await sendServerSSEMessage({
type: 'team-leader-changed',
targetUserIds: others,
@@ -108,18 +104,20 @@ export async function POST(req: NextRequest) {
})
}
- // 3) Zielgerichtetes “team-updated” an ALLE (inkl. neuem Leader)
const reloadTargets = Array.from(new Set([...allPlayerIds, newLeaderSteamId]))
if (reloadTargets.length) {
- await sendServerSSEMessage({
- type: 'team-updated',
- targetUserIds: reloadTargets,
- teamId,
- })
+ await sendServerSSEMessage({ type: 'team-updated', targetUserIds: reloadTargets, teamId })
}
return NextResponse.json({ message: 'Leader erfolgreich übertragen.' })
- } catch (error) {
+ } catch (error: any) {
+ // Falls du zusätzlich im Prisma-Schema @@unique([leaderId]) gesetzt hast:
+ if (error?.code === 'P2002' && error?.meta?.target?.includes('leaderId')) {
+ return NextResponse.json(
+ { message: 'Dieser Spieler ist bereits Leader eines anderen Teams.' },
+ { status: 400 }
+ )
+ }
console.error('Fehler beim Leaderwechsel:', error)
return NextResponse.json({ message: 'Serverfehler beim Leaderwechsel.' }, { status: 500 })
}
diff --git a/src/app/api/teams/route.ts b/src/app/api/teams/route.ts
index 37b7186..286eba4 100644
--- a/src/app/api/teams/route.ts
+++ b/src/app/api/teams/route.ts
@@ -3,52 +3,85 @@ import { NextResponse } from 'next/server'
import { prisma } from '@/lib/prisma'
import type { Player } from '../../../types/team'
-export const dynamic = 'force-dynamic' // optional: Caching aus
-// export const revalidate = 0
+export const dynamic = 'force-dynamic'
export async function GET() {
try {
const teams = await prisma.team.findMany({
- select: { id: true, name: true, logo: true, leaderId: true, createdAt: true,
- activePlayers: true, inactivePlayers: true },
+ select: {
+ id: true,
+ name: true,
+ logo: true,
+ leaderId: true,
+ createdAt: true,
+ activePlayers: true,
+ inactivePlayers: true
+ },
+ orderBy: { name: 'asc' }
})
+ // Alle benötigten SteamIDs sammeln (aktive, inaktive, Leader)
const uniqueIds = new Set()
- teams.forEach(t => {
+ for (const t of teams) {
t.activePlayers.forEach(id => uniqueIds.add(id))
t.inactivePlayers.forEach(id => uniqueIds.add(id))
- })
+ if (t.leaderId) uniqueIds.add(t.leaderId)
+ }
+ // Nutzer-Daten für alle IDs holen
const users = await prisma.user.findMany({
where: { steamId: { in: [...uniqueIds] } },
- select: { steamId: true, name: true, avatar: true, location: true, premierRank: true },
+ select: {
+ steamId: true,
+ name: true,
+ avatar: true,
+ location: true,
+ premierRank: true
+ }
})
+ // Lookup-Map aufbauen
const byId: Record = {}
const DEFAULT_AVATAR = '/assets/img/avatars/default.png'
const UNKNOWN_NAME = 'Unbekannt'
- users.forEach(u => {
+
+ for (const u of users) {
byId[u.steamId] = {
steamId: u.steamId,
name: u.name ?? UNKNOWN_NAME,
avatar: u.avatar ?? DEFAULT_AVATAR,
location: u.location ?? '',
- premierRank: u.premierRank ?? 0,
+ premierRank: u.premierRank ?? 0
+ }
+ }
+
+ // Ergebnis formen – Leader als komplettes Player-Objekt mitsenden
+ const result = teams.map(t => {
+ const leaderPlayer: Player | undefined =
+ t.leaderId
+ ? (byId[t.leaderId] ?? {
+ steamId: t.leaderId,
+ name: UNKNOWN_NAME,
+ avatar: DEFAULT_AVATAR,
+ location: '',
+ premierRank: 0
+ })
+ : undefined
+
+ return {
+ id: t.id,
+ name: t.name,
+ logo: t.logo,
+ createdAt: t.createdAt,
+ leaderId: t.leaderId,
+ leader: leaderPlayer, // ⬅️ voll befüllt
+ activePlayers: t.activePlayers.map(id => byId[id]).filter(Boolean) as Player[],
+ inactivePlayers: t.inactivePlayers.map(id => byId[id]).filter(Boolean) as Player[]
}
})
- const result = teams.map(t => ({
- id: t.id,
- name: t.name,
- logo: t.logo,
- leaderId: t.leaderId,
- createdAt: t.createdAt,
- activePlayers: t.activePlayers .map(id => byId[id]).filter(Boolean) as Player[],
- inactivePlayers:t.inactivePlayers.map(id => byId[id]).filter(Boolean) as Player[],
- }))
-
return NextResponse.json(
- { items: result, hasMore: false },
+ { teams: result, hasMore: false },
{ headers: { 'Cache-Control': 'no-store' } }
)
} catch (err) {
diff --git a/src/app/api/user/route.ts b/src/app/api/user/route.ts
index 94d9051..188d157 100644
--- a/src/app/api/user/route.ts
+++ b/src/app/api/user/route.ts
@@ -1,95 +1,137 @@
// /src/app/api/user/route.ts
-import { NextResponse, type NextRequest } from 'next/server'
-import { getServerSession } from 'next-auth'
-import { authOptions } from '@/lib/auth'
-import { prisma } from '@/lib/prisma'
+import { NextResponse, type NextRequest } from 'next/server';
+import { getServerSession } from 'next-auth';
+import { authOptions } from '@/lib/auth';
+import { prisma } from '@/lib/prisma';
+
+export const dynamic = 'force-dynamic'; // kein Static Caching
+
+type SlimPlayer = {
+ steamId: string;
+ name: string;
+ avatar: string;
+ location?: string | null;
+ premierRank?: number | null;
+};
+
+// Hilfstyp für Session, damit TS weiß, dass es user?.steamId gibt
+type SessionShape = { user?: { steamId?: string } } | null;
export async function GET(req: NextRequest) {
- const session = await getServerSession(authOptions(req))
- const steamId = session?.user?.steamId
+ // ⚠️ Typen fixen: getServerSession korrekt casten
+ const session = (await getServerSession(authOptions(req) as any)) as SessionShape;
+ const steamId = session?.user?.steamId;
if (!steamId) {
- return NextResponse.json({ error: 'Nicht eingeloggt' }, { status: 401 })
+ return NextResponse.json({ error: 'Nicht eingeloggt' }, { status: 401 });
}
- // 1) User + Team (nur skalare Felder + Leader-Relation laden)
- const userRaw = await prisma.user.findUnique({
+ // 1) Basisdaten des Users
+ const me = await prisma.user.findUnique({
where: { steamId },
select: {
- name: true,
steamId: true,
+ name: true,
avatar: true,
+ location: true,
premierRank: true,
isAdmin: true,
status: true,
- team: {
- select: {
- id: true,
- name: true,
- logo: true,
- leaderId: true,
- leader: {
- select: {
- steamId: true,
- name: true,
- avatar: true,
- },
- },
- activePlayers: true,
- inactivePlayers: true,
- },
- },
},
- })
+ });
- if (!userRaw) {
- return NextResponse.json({ error: 'User nicht gefunden' }, { status: 404 })
+ if (!me) {
+ return NextResponse.json({ error: 'User nicht gefunden' }, { status: 404 });
}
- // 2) Falls Team vorhanden: active/inactive IDs in User-Objekte auflösen
- let teamResolved: any = null
- if (userRaw.team) {
- const activeIds = userRaw.team.activePlayers ?? []
- const inactiveIds = userRaw.team.inactivePlayers ?? []
+ // 2) Alle Teams laden, in denen der User Leader/aktiv/inaktiv ist
+ const teamsRaw = await prisma.team.findMany({
+ where: {
+ OR: [
+ { leaderId: steamId },
+ { activePlayers: { has: steamId } },
+ { inactivePlayers: { has: steamId } },
+ ],
+ },
+ select: {
+ id: true,
+ name: true,
+ logo: true,
+ leaderId: true,
+ createdAt: true,
+ activePlayers: true,
+ inactivePlayers: true,
+ },
+ });
- // findMany mit leeren Arrays ist ok und liefert []
- const [activeUsers, inactiveUsers] = await Promise.all([
- prisma.user.findMany({
- where: { steamId: { in: activeIds } },
- select: { steamId: true, name: true, avatar: true, premierRank: true },
- }),
- prisma.user.findMany({
- where: { steamId: { in: inactiveIds } },
- select: { steamId: true, name: true, avatar: true, premierRank: true },
- }),
+ // 3) Einmalig alle benötigten Steam-IDs sammeln und als User ziehen
+ const ids = new Set();
+ for (const t of teamsRaw) {
+ t.activePlayers.forEach(ids.add, ids);
+ t.inactivePlayers.forEach(ids.add, ids);
+ if (t.leaderId) ids.add(t.leaderId);
+ }
+
+ const users = ids.size
+ ? await prisma.user.findMany({
+ where: { steamId: { in: [...ids] } },
+ select: {
+ steamId: true,
+ name: true,
+ avatar: true,
+ location: true,
+ premierRank: true,
+ },
+ })
+ : [];
+
+ const DEFAULT_AVATAR = '/assets/img/avatars/default.png';
+ const UNKNOWN_NAME = 'Unbekannt';
+
+ const byId: Record = Object.fromEntries(
+ users.map((u) => [
+ u.steamId,
+ {
+ steamId: u.steamId,
+ name: u.name ?? UNKNOWN_NAME,
+ avatar: u.avatar ?? DEFAULT_AVATAR,
+ location: u.location ?? null,
+ premierRank: u.premierRank ?? 0,
+ },
])
+ );
- // Optional: Reihenfolge gemäß IDs beibehalten
- const byId = (ids: string[], users: any[]) => {
- const map = new Map(users.map((u) => [u.steamId, u]))
- return ids.map((id) => map.get(id)).filter(Boolean)
- }
+ const mapIds = (arr: string[]) =>
+ arr.map((id) => byId[id]).filter(Boolean) as SlimPlayer[];
- teamResolved = {
- id: userRaw.team.id,
- name: userRaw.team.name,
- logo: userRaw.team.logo,
- leader: userRaw.team.leader ?? null,
- activePlayers: byId(activeIds, activeUsers),
- inactivePlayers: byId(inactiveIds, inactiveUsers),
- }
- }
+ // 4) Teams auflösen (Leader + aktive/inaktive Spieler)
+ const teams = teamsRaw.map((t) => ({
+ id: t.id,
+ name: t.name,
+ logo: t.logo,
+ createdAt: t.createdAt,
+ leader: t.leaderId
+ ? byId[t.leaderId] ?? { steamId: t.leaderId, name: UNKNOWN_NAME, avatar: DEFAULT_AVATAR }
+ : null,
+ activePlayers: mapIds(t.activePlayers),
+ inactivePlayers: mapIds(t.inactivePlayers),
+ }));
- // 3) Antwort formen (Team ersetzt durch aufgelöste Struktur)
- const response = {
- name: userRaw.name,
- steamId: userRaw.steamId,
- avatar: userRaw.avatar,
- premierRank: userRaw.premierRank,
- isAdmin: userRaw.isAdmin,
- status: userRaw.status ?? 'offline',
- team: teamResolved,
- }
+ const teamIds = teams.map((t) => t.id);
- return NextResponse.json(response, { headers: { 'Cache-Control': 'no-store' } })
+ // 5) Antwort (ohne Abwärtskompatibilität: KEIN `team`-Feld mehr)
+ return NextResponse.json(
+ {
+ steamId: me.steamId,
+ name: me.name,
+ avatar: me.avatar ?? DEFAULT_AVATAR,
+ location: me.location ?? null,
+ premierRank: me.premierRank ?? 0,
+ isAdmin: me.isAdmin,
+ status: me.status ?? 'offline',
+ teams, // vollständige Liste
+ teamIds, // praktische ID-Liste
+ },
+ { headers: { 'Cache-Control': 'no-store' } }
+ );
}
diff --git a/src/i18n/request.ts b/src/i18n/request.ts
index 6638e82..ced241a 100644
--- a/src/i18n/request.ts
+++ b/src/i18n/request.ts
@@ -1,18 +1,42 @@
// /src/i18n/request.ts
-
import {getRequestConfig} from 'next-intl/server';
import {hasLocale} from 'next-intl';
import {routing} from './routing';
-
+
+const namespaces = [
+ 'common',
+ 'nav',
+ 'sidebar',
+ 'settings',
+ 'teams',
+ 'matches',
+ 'dashboard'
+] as const;
+type Namespace = (typeof namespaces)[number];
+
+async function tryImport(p: string): Promise {
+ try { const mod = await import(p as any); return (mod as any).default as T; }
+ catch { return null; }
+}
+
+async function loadMessages(locale: string) {
+ const entries = await Promise.all(
+ namespaces.map(async (ns) => {
+ // ⬇️ neue Struktur: /messages//.json
+ const obj = await tryImport>(
+ `../messages/${ns}/${locale}.json`
+ );
+ return [ns, obj ?? {}] as const;
+ })
+ );
+
+ const merged = {} as Record>;
+ for (const [ns, obj] of entries) merged[ns] = obj;
+ return merged;
+}
+
export default getRequestConfig(async ({requestLocale}) => {
- // Typically corresponds to the `[locale]` segment
const requested = await requestLocale;
- const locale = hasLocale(routing.locales, requested)
- ? requested
- : routing.defaultLocale;
-
- return {
- locale,
- messages: (await import(`../../messages/${locale}.json`)).default
- };
-});
\ No newline at end of file
+ const locale = hasLocale(routing.locales, requested) ? (requested as string) : routing.defaultLocale;
+ return { locale, messages: await loadMessages(locale) };
+});
diff --git a/src/jobs/runDownloaderForUser.ts b/src/jobs/runDownloaderForUser.ts
index 296e361..866c9e9 100644
--- a/src/jobs/runDownloaderForUser.ts
+++ b/src/jobs/runDownloaderForUser.ts
@@ -3,7 +3,7 @@ import path from 'path';
import { Match, User } from '@/generated/prisma';
import { parseAndStoreDemo } from './parseAndStoreDemo';
import { log } from '../../scripts/cs2-cron-runner.js';
-import { prisma } from '../app/lib/prisma.js';
+import { prisma } from '@/lib/prisma';
export async function runDownloaderForUser(user: User): Promise<{
newMatches: Match[];
diff --git a/src/messages/dashboard/de.json b/src/messages/dashboard/de.json
new file mode 100644
index 0000000..0d28188
--- /dev/null
+++ b/src/messages/dashboard/de.json
@@ -0,0 +1,3 @@
+{
+ "title": "Willkommen!"
+}
diff --git a/src/messages/dashboard/en.json b/src/messages/dashboard/en.json
new file mode 100644
index 0000000..7a8ffd7
--- /dev/null
+++ b/src/messages/dashboard/en.json
@@ -0,0 +1,3 @@
+{
+ "title": "Welcome!"
+}
diff --git a/src/messages/nav/de.json b/src/messages/nav/de.json
new file mode 100644
index 0000000..740598d
--- /dev/null
+++ b/src/messages/nav/de.json
@@ -0,0 +1,14 @@
+{
+ "dashboard": "Dashboard",
+ "teams": {
+ "label": "Teams",
+ "overview": "Übersicht",
+ "manage": "Teamverwaltung"
+ },
+ "players": {
+ "label": "Spieler",
+ "overview": "Übersicht",
+ "stats": "Statistiken"
+ },
+ "schedule": "Spielplan"
+}
diff --git a/src/messages/nav/en.json b/src/messages/nav/en.json
new file mode 100644
index 0000000..740598d
--- /dev/null
+++ b/src/messages/nav/en.json
@@ -0,0 +1,14 @@
+{
+ "dashboard": "Dashboard",
+ "teams": {
+ "label": "Teams",
+ "overview": "Übersicht",
+ "manage": "Teamverwaltung"
+ },
+ "players": {
+ "label": "Spieler",
+ "overview": "Übersicht",
+ "stats": "Statistiken"
+ },
+ "schedule": "Spielplan"
+}
diff --git a/src/messages/settings/de.json b/src/messages/settings/de.json
new file mode 100644
index 0000000..a195aa7
--- /dev/null
+++ b/src/messages/settings/de.json
@@ -0,0 +1,20 @@
+{
+ "account": {
+ "title": "Kontoeinstellungen",
+ "appearance": {
+ "name": "Darstellung",
+ "description": "Wähle dein bevorzugtes Design. Du kannst einen festen Stil verwenden oder das Systemverhalten übernehmen."
+ },
+ "authCode": {
+ "name": "Authentifizierungscode",
+ "question": "Was ist der Authentifizierungscode?",
+ "description": "Drittanbieter-Webseiten und Apps können mit diesem Code auf deine Match-Historie zugreifen..."
+ },
+ "shareCode": {
+ "name": "Match-Share-Code",
+ "question": "Was ist der Match-Share-Code?",
+ "findCodeRich": "Du findest deinen Code hier.",
+ "helpUrl": "https://help.steampowered.com/de/wizard/HelpWithGameIssue/?appid=730&issueid=128"
+ }
+ }
+}
diff --git a/src/messages/settings/en.json b/src/messages/settings/en.json
new file mode 100644
index 0000000..9f71d1d
--- /dev/null
+++ b/src/messages/settings/en.json
@@ -0,0 +1,20 @@
+{
+ "account": {
+ "title": "Account Settings",
+ "appearance": {
+ "name": "Appearance",
+ "description": "Choose your preferred theme. You can use a fixed style or follow your system setting."
+ },
+ "authCode": {
+ "name": "Authentication Code",
+ "question": "What is the Authentication Code?",
+ "description": "Third-party websites and applications can use this authentication code to access your match history..."
+ },
+ "shareCode": {
+ "name": "Match Share Code",
+ "question": "What is the Match Share Code?",
+ "findCodeRich": "You can find your code here.",
+ "helpUrl": "https://help.steampowered.com/en/wizard/HelpWithGameIssue/?appid=730&issueid=128"
+ }
+ }
+}
diff --git a/src/messages/sidebar/de.json b/src/messages/sidebar/de.json
new file mode 100644
index 0000000..dbfb490
--- /dev/null
+++ b/src/messages/sidebar/de.json
@@ -0,0 +1,7 @@
+{
+ "brand": "Iron:e",
+ "language": {
+ "de": "Deutsch",
+ "en": "Englisch"
+ }
+}
diff --git a/src/messages/sidebar/en.json b/src/messages/sidebar/en.json
new file mode 100644
index 0000000..b4041be
--- /dev/null
+++ b/src/messages/sidebar/en.json
@@ -0,0 +1,7 @@
+{
+ "brand": "Iron:e",
+ "language": {
+ "de": "German",
+ "en": "English"
+ }
+}
diff --git a/src/middleware.ts b/src/middleware.ts
index f84730b..edd07f1 100644
--- a/src/middleware.ts
+++ b/src/middleware.ts
@@ -1,15 +1,13 @@
-// /src/middleware.ts
import {NextResponse} from 'next/server';
import type {NextRequest} from 'next/server';
import createIntlMiddleware from 'next-intl/middleware';
import {getToken} from 'next-auth/jwt';
-
import {routing} from './i18n/routing';
// 1) i18n-Middleware vorbereiten
const handleI18n = createIntlMiddleware(routing);
-// Kleine Helfer
+// Helpers
function getCurrentLocaleFromPath(pathname: string, locales: readonly string[], fallback: string) {
const first = pathname.split('/')[1];
return locales.includes(first) ? first : fallback;
@@ -30,19 +28,17 @@ function isProtectedPath(pathnameNoLocale: string) {
pathnameNoLocale.startsWith('/dashboard') ||
pathnameNoLocale.startsWith('/settings') ||
pathnameNoLocale.startsWith('/matches') ||
+ pathnameNoLocale.startsWith('/team') || // ← hinzugefügt
pathnameNoLocale.startsWith('/admin')
);
}
export default async function middleware(req: NextRequest) {
- // 2) Erst i18n arbeiten lassen (Locale auflösen, ggf. redirect/rewrite)
+ // 2) Erst i18n arbeiten lassen
const i18nRes = handleI18n(req);
- // Falls i18n gerade einen Redirect/Rewrite veranlasst, das Ergebnis direkt zurückgeben.
- // (Erkennbar an Location-Header (redirect) oder x-middleware-rewrite (rewrite))
- const isRedirect = i18nRes.headers.get('location') != null;
- const isRewrite = i18nRes.headers.get('x-middleware-rewrite') != null;
- if (isRedirect || isRewrite) {
+ // Wenn i18n bereits redirect/rewrite auslöst, direkt zurückgeben
+ if (i18nRes.headers.get('location') || i18nRes.headers.get('x-middleware-rewrite')) {
return i18nRes;
}
@@ -54,7 +50,6 @@ export default async function middleware(req: NextRequest) {
// 3) Nur für geschützte Pfade Auth prüfen
if (!isProtectedPath(pathnameNoLocale)) {
- // Nichts weiter zu tun → i18n-Result weiterreichen
return i18nRes;
}
@@ -62,7 +57,7 @@ export default async function middleware(req: NextRequest) {
// Adminschutz
if (pathnameNoLocale.startsWith('/admin')) {
- if (!token || !token.isAdmin) {
+ if (!token || !(token as any).isAdmin) {
const redirectUrl = url.clone();
redirectUrl.pathname = `/${currentLocale}/dashboard`;
return NextResponse.redirect(redirectUrl);
@@ -72,15 +67,15 @@ export default async function middleware(req: NextRequest) {
// Allgemeiner Auth-Schutz
if (!token) {
const loginUrl = new URL('/api/auth/signin', req.url);
- // Callback auf die aktuelle URL (inkl. Locale) setzen
- loginUrl.searchParams.set('callbackUrl', url.toString());
+ loginUrl.searchParams.set('callbackUrl', url.toString()); // komplette Ziel-URL inkl. Locale
return NextResponse.redirect(loginUrl);
}
- // Alles gut → weiter mit i18n-Result (entspricht NextResponse.next())
+ // Alles gut → weiter
return i18nRes;
}
export const config = {
+ // Standard: alles außer /api, _next, statische Dateien
matcher: '/((?!api|trpc|_next|_vercel|.*\\..*).*)'
};