diff --git a/package-lock.json b/package-lock.json index ee6504c..cea8f09 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,7 +12,7 @@ "@dnd-kit/sortable": "^10.0.0", "@floating-ui/dom": "^1.6.13", "@floating-ui/react": "^0.27.12", - "@fortawesome/react-fontawesome": "^0.2.2", + "@fortawesome/fontawesome-free": "^7.0.0", "@preline/dropdown": "^3.0.1", "@preline/tooltip": "^3.0.0", "@prisma/client": "^6.13.0", @@ -24,6 +24,7 @@ "date-fns-tz": "^3.2.0", "dropzone": "^6.0.0-beta.2", "flag-icons": "^7.3.2", + "font-awesome": "^4.7.0", "framer-motion": "^12.18.1", "jquery": "^3.7.1", "ky": "^1.8.2", @@ -902,42 +903,15 @@ "tslib": "2" } }, - "node_modules/@fortawesome/fontawesome-common-types": { - "version": "6.7.2", - "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.7.2.tgz", - "integrity": "sha512-Zs+YeHUC5fkt7Mg1l6XTniei3k4bwG/yo3iFUtZWd/pMx9g3fdvkSK9E0FOC+++phXOka78uJcYb8JaFkW52Xg==", - "license": "MIT", - "peer": true, + "node_modules/@fortawesome/fontawesome-free": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free/-/fontawesome-free-7.0.0.tgz", + "integrity": "sha512-X48nISrSOa89zu2VMljC4XaRf8NmgTwQBVHfS2Nu5G00ZwM31oOVrAtGxZF3b6wDYf9lJsf/Eq4cCSFKIkOWPQ==", + "license": "(CC-BY-4.0 AND OFL-1.1 AND MIT)", "engines": { "node": ">=6" } }, - "node_modules/@fortawesome/fontawesome-svg-core": { - "version": "6.7.2", - "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.7.2.tgz", - "integrity": "sha512-yxtOBWDrdi5DD5o1pmVdq3WMCvnobT0LU6R8RyyVXPvFRd2o79/0NCuQoCjNTeZz9EzA9xS3JxNWfv54RIHFEA==", - "license": "MIT", - "peer": true, - "dependencies": { - "@fortawesome/fontawesome-common-types": "6.7.2" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/@fortawesome/react-fontawesome": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/@fortawesome/react-fontawesome/-/react-fontawesome-0.2.2.tgz", - "integrity": "sha512-EnkrprPNqI6SXJl//m29hpaNzOp1bruISWaOiRtkMi/xSvHJlzc2j2JAYS7egxt/EbjSNV/k6Xy0AQI6vB2+1g==", - "license": "MIT", - "dependencies": { - "prop-types": "^15.8.1" - }, - "peerDependencies": { - "@fortawesome/fontawesome-svg-core": "~1 || ~6", - "react": ">=16.3" - } - }, "node_modules/@humanfs/core": { "version": "0.19.1", "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", @@ -4469,6 +4443,15 @@ } } }, + "node_modules/font-awesome": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/font-awesome/-/font-awesome-4.7.0.tgz", + "integrity": "sha512-U6kGnykA/6bFmg1M/oT9EkFeIYv7JlX3bozwQJWiiLz6L0w3F5vBVPxHlwyX/vtNq1ckcpRKOB9f2Qal/VtFpg==", + "license": "(OFL-1.1 AND MIT)", + "engines": { + "node": ">=0.10.3" + } + }, "node_modules/for-each": { "version": "0.3.5", "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", @@ -5400,6 +5383,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "devOptional": true, "license": "MIT" }, "node_modules/js-yaml": { @@ -5799,6 +5783,7 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dev": true, "license": "MIT", "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" @@ -6289,6 +6274,7 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -6779,6 +6765,7 @@ "version": "15.8.1", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "dev": true, "license": "MIT", "dependencies": { "loose-envify": "^1.4.0", @@ -6901,6 +6888,7 @@ "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "dev": true, "license": "MIT" }, "node_modules/read-package-up": { diff --git a/package.json b/package.json index ab3c593..aca8aaa 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,7 @@ "@dnd-kit/sortable": "^10.0.0", "@floating-ui/dom": "^1.6.13", "@floating-ui/react": "^0.27.12", - "@fortawesome/react-fontawesome": "^0.2.2", + "@fortawesome/fontawesome-free": "^7.0.0", "@preline/dropdown": "^3.0.1", "@preline/tooltip": "^3.0.0", "@prisma/client": "^6.13.0", @@ -28,6 +28,7 @@ "date-fns-tz": "^3.2.0", "dropzone": "^6.0.0-beta.2", "flag-icons": "^7.3.2", + "font-awesome": "^4.7.0", "framer-motion": "^12.18.1", "jquery": "^3.7.1", "ky": "^1.8.2", diff --git a/prisma/seed-demo-users.ts b/prisma/seed-demo-users.ts deleted file mode 100644 index 4c38a71..0000000 --- a/prisma/seed-demo-users.ts +++ /dev/null @@ -1,84 +0,0 @@ -import { prisma } from '@/app/lib/prisma' - -async function seedDemoUsers() { - const teamAId = 'cmab28h050001ph4s8w2a0wsc' - const teamBId = 'cmab2acln0003ph4stxe3ad38' - - const usersA = Array.from({ length: 4 }, (_, i) => ({ - steamId: `76561198000000A${i}`, - name: `DemoA${i}`, - avatar: '/default-avatar.png', - location: 'DE', - team: teamAId, - })) - - const usersB = Array.from({ length: 4 }, (_, i) => ({ - steamId: `76561198000000B${i}`, - name: `DemoB${i}`, - avatar: '/default-avatar.png', - location: 'DE', - team: teamBId, - })) - - // Benutzer einfügen oder updaten - const createdUsers = await Promise.all( - [...usersA, ...usersB].map((user) => - prisma.user.upsert({ - where: { steamId: user.steamId }, - update: {}, - create: user, - }) - ) - ) - - // Team-Aktive Spieler setzen - await prisma.team.update({ - where: { id: teamAId }, - data: { activePlayers: usersA.map((u) => u.steamId) }, - }) - - await prisma.team.update({ - where: { id: teamBId }, - data: { activePlayers: usersB.map((u) => u.steamId) }, - }) - - // Zuletzt erstelltes Match mit diesen Teams finden - const match = await prisma.match.findFirst({ - where: { - teamAId, - teamBId, - }, - orderBy: { createdAt: 'desc' }, - }) - - if (!match) { - throw new Error('Kein Match zwischen den beiden Teams gefunden.') - } - - // Spieler zu MatchPlayer hinzufügen - await prisma.matchPlayer.createMany({ - data: [ - ...usersA.map((u) => ({ - matchId: match.id, - userId: u.steamId, - teamId: teamAId, - })), - ...usersB.map((u) => ({ - matchId: match.id, - userId: u.steamId, - teamId: teamBId, - })), - ], - skipDuplicates: true, - }) - - console.log('✅ Demouser erstellt und MatchPlayer zugewiesen.') -} - -seedDemoUsers() - .catch((err) => { - console.error('❌ Fehler beim Seed:', err) - }) - .finally(() => { - prisma.$disconnect() - }) diff --git a/src/app/api/cs2/authcode/route.ts b/src/app/api/cs2/authcode/route.ts new file mode 100644 index 0000000..dc170ad --- /dev/null +++ b/src/app/api/cs2/authcode/route.ts @@ -0,0 +1,62 @@ +// /api/cs2/authcode/route.ts + +import { NextRequest, NextResponse } from 'next/server' +import { getServerSession } from 'next-auth' +import { authOptions } from '@/app/lib/auth' +import { prisma } from '@/app/lib/prisma' +import { decrypt, encrypt } from '@/app/lib/crypto' + +export async function GET(req: NextRequest) { + const session = await getServerSession(authOptions(req)) + const steamId = session?.user?.steamId + + if (!steamId) { + return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) + } + + try { + const user = await prisma.user.findUnique({ + where: { steamId }, + select: { authCode: true }, + }) + + const authCode = user?.authCode ? decrypt(user.authCode) : null + + return NextResponse.json({ authCode }) + } catch (error) { + console.error('[GET /api/cs2/authcode]', error) + return NextResponse.json({ error: 'Fehler beim Abrufen' }, { status: 500 }) + } +} + +export async function PUT(req: NextRequest) { + const session = await getServerSession(authOptions(req)) + const steamId = session?.user?.steamId + + if (!steamId) { + return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) + } + + const { authCode } = await req.json() + + const isValidAuthCode = + !authCode || /^[A-Z0-9]{4}-[A-Z0-9]{5}-[A-Z0-9]{4}$/.test(authCode) + + if (!isValidAuthCode) { + return NextResponse.json({ error: 'invalid-authcode' }, { status: 400 }) + } + + try { + await prisma.user.update({ + where: { steamId }, + data: { + authCode: typeof authCode === 'string' ? encrypt(authCode) : null, + }, + }) + + return new NextResponse(null, { status: 204 }) + } catch (error) { + console.error('[PUT /api/cs2/authcode]', error) + return NextResponse.json({ error: 'Fehler beim Speichern' }, { status: 500 }) + } +} diff --git a/src/app/api/cs2/sharecode/route.ts b/src/app/api/cs2/sharecode/route.ts index dab6adc..9a1af05 100644 --- a/src/app/api/cs2/sharecode/route.ts +++ b/src/app/api/cs2/sharecode/route.ts @@ -4,9 +4,7 @@ import { NextRequest, NextResponse } from 'next/server' import { getServerSession } from 'next-auth' import { authOptions } from '@/app/lib/auth' import { prisma } from '@/app/lib/prisma' -import { decrypt, encrypt } from '@/app/lib/crypto' -// Maximal 30 Tage gültig const EXPIRY_DAYS = 30 export async function GET(req: NextRequest) { @@ -21,13 +19,11 @@ export async function GET(req: NextRequest) { const user = await prisma.user.findUnique({ where: { steamId }, select: { - authCode: true, lastKnownShareCode: true, lastKnownShareCodeDate: true, }, }) - const authCode = user?.authCode ? decrypt(user.authCode) : null const lastKnownShareCode = user?.lastKnownShareCode ?? null const lastKnownShareCodeDate = user?.lastKnownShareCodeDate ?? null @@ -41,7 +37,6 @@ export async function GET(req: NextRequest) { } return NextResponse.json({ - authCode, lastKnownShareCode, lastKnownShareCodeDate, reason, @@ -60,11 +55,10 @@ export async function PUT(req: NextRequest) { return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) } - const { authCode, lastKnownShareCode } = await req.json() + const { lastKnownShareCode } = await req.json() - // Optional: zusätzliche Validierung für authCode - const isValidAuthCode = !authCode || /^[A-Z0-9]{4}-[A-Z0-9]{5}-[A-Z0-9]{4}$/.test(authCode) - const isValidShareCode = !lastKnownShareCode || /^CSGO(-[a-zA-Z0-9]{5}){5}$/.test(lastKnownShareCode) + const isValidShareCode = + !lastKnownShareCode || /^CSGO(-[a-zA-Z0-9]{5}){5}$/.test(lastKnownShareCode) if (!isValidShareCode) { return NextResponse.json({ error: 'expired-sharecode' }, { status: 400 }) @@ -74,9 +68,8 @@ export async function PUT(req: NextRequest) { await prisma.user.update({ where: { steamId }, data: { - authCode: authCode === null ? null : (isValidAuthCode ? encrypt(authCode) : undefined), - lastKnownShareCode: lastKnownShareCode || undefined, - lastKnownShareCodeDate: lastKnownShareCode ? new Date() : undefined, + lastKnownShareCode: lastKnownShareCode ?? null, + lastKnownShareCodeDate: lastKnownShareCode ? new Date() : null, }, }) diff --git a/src/app/components/Sidebar.tsx b/src/app/components/Sidebar.tsx index 15ec61f..4927c97 100644 --- a/src/app/components/Sidebar.tsx +++ b/src/app/components/Sidebar.tsx @@ -1,209 +1,176 @@ 'use client' import { useState } from "react" -import Link from "next/link" +import { usePathname, useRouter } from 'next/navigation' import SidebarFooter from "./SidebarFooter" -import { usePathname } from 'next/navigation' import Button from "./Button" export default function Sidebar({ children }: { children?: React.ReactNode }) { - const [isOpen, setIsOpen] = useState(false) + const router = useRouter() const pathname = usePathname() - const toggleSidebar = () => { - setIsOpen(prev => !prev) + + const [isOpen, setIsOpen] = useState(false) + const [openSubmenu, setOpenSubmenu] = useState<'teams' | 'players' | null>(null) + + const toggleSidebar = () => setIsOpen(prev => !prev) + + const handleSubmenuToggle = (menu: 'teams' | 'players') => { + setOpenSubmenu(prev => (prev === menu ? null : menu)) } - + return ( <> - - -