updated
This commit is contained in:
parent
28774efff1
commit
d1c0d9297c
19
.env
19
.env
@ -9,19 +9,20 @@ SHARE_CODE_SECRET_KEY=6f9d4a2951b8eae35cdd3fb28e1a74550d177c3900ad1111c8e48b4e3b
|
||||
SHARE_CODE_IV=9f1d67b8a3c4d261fa2b7c44a1d4f9c8
|
||||
STEAM_API_KEY=0B3B2BF79ECD1E9262BB118A7FEF1973
|
||||
NEXTAUTH_SECRET=ironieopen
|
||||
NEXTAUTH_URL=https://new.ironieopen.de
|
||||
NEXT_PUBLIC_APP_URL=https://new.ironieopen.de
|
||||
#NEXTAUTH_URL=https://new.ironieopen.de
|
||||
#NEXT_PUBLIC_APP_URL=https://new.ironieopen.de
|
||||
NEXTAUTH_URL=https://ironieopen.local
|
||||
NEXT_PUBLIC_APP_URL=https://ironieopen.local
|
||||
AUTH_SECRET="57AUHXa+UmFrlnIEKxtrk8fLo+aZMtsa/oV6fklXkcE=" # Added by `npx auth`. Read more: https://cli.authjs.dev
|
||||
ALLSTAR_TOKEN=ed033ac0-5df7-482e-a322-e2b4601955d3
|
||||
PTERODACTYL_APP_API=ptla_O6Je82OvlCBFITDRgB1ZJ95AIyUSXYnVGgwRF6pO6d9
|
||||
PTERODACTYL_CLIENT_API=ptlc_c31BKDEXy63fHUxeQDahk6eeC3CL19TpG2rgao7mUl5
|
||||
PTERODACTYL_APP_API=ptla_6IcEHfK0CMiA5clzFSGXPEhczC9jTRZz7pr8sNn1iSB
|
||||
PTERODACTYL_CLIENT_API=ptlc_hSYxGaVlp7dklvSWAcuGjjGfcBiCaAedKXYnI3SKMV3
|
||||
PTERODACTYL_PANEL_URL=https://panel.ironieopen.de
|
||||
PTERO_SERVER_SFTP_URL=sftp://panel.ironieopen.de:2022
|
||||
PTERO_SERVER_SFTP_USER=army.37a11489
|
||||
PTERO_SERVER_SFTP_USER=army.acdef8fc
|
||||
PTERO_SERVER_SFTP_PASSWORD=IJHoYHTXQvJkCxkycTYM
|
||||
PTERO_SERVER_ID=37a11489
|
||||
|
||||
NEXT_PUBLIC_SSE_URL=https://new.ironieopen.de/events
|
||||
PTERO_SERVER_ID=acdef8fc
|
||||
TRUST_PROXY=1
|
||||
|
||||
# META (vom CS2-Server-Plugin)
|
||||
NEXT_PUBLIC_CS2_TELEMETRY_WS_HOST=new.ironieopen.de
|
||||
@ -37,4 +38,4 @@ NEXT_PUBLIC_CS2_GAME_WS_SCHEME=wss
|
||||
|
||||
NEXT_PUBLIC_CONNECT_HREF="steam://connect/94.130.66.149:27015/0000"
|
||||
|
||||
FACEIT_API_KEY=28ff4916-65da-4415-ba67-3d6d6b5dc850
|
||||
FACEIT_API_KEY=28ff4916-65da-4415-ba67-3d6d6b5dc850
|
||||
@ -4,7 +4,6 @@ import type { NextConfig } from 'next'
|
||||
import createNextIntlPlugin from 'next-intl/plugin';
|
||||
|
||||
const nextConfig: NextConfig = {
|
||||
output: 'standalone',
|
||||
images: {
|
||||
remotePatterns: [
|
||||
{
|
||||
|
||||
@ -35,29 +35,31 @@ export default function TeamAdminClient({ teamId }: Props) {
|
||||
const steamId = session?.user?.steamId
|
||||
if (!steamId) return
|
||||
|
||||
const base = process.env.NEXT_PUBLIC_SSE_URL
|
||||
const url = `${base}/events?steamId=${encodeURIComponent(steamId)}`
|
||||
let es: EventSource | null = new EventSource(url, { withCredentials: false })
|
||||
const raw = process.env.NEXT_PUBLIC_APP_URL?.trim()
|
||||
let origin: string
|
||||
try {
|
||||
origin = raw && /^https?:\/\//i.test(raw) ? new URL(raw).origin : window.location.origin
|
||||
} catch {
|
||||
origin = window.location.origin
|
||||
}
|
||||
|
||||
const u = new URL('/events', origin) // überschreibt jeden Pfad
|
||||
u.searchParams.set('steamId', steamId)
|
||||
|
||||
let es: EventSource | null = new EventSource(u.toString(), { withCredentials: false })
|
||||
|
||||
// Listener als EventListener typisieren
|
||||
const onTeamUpdated: EventListener = (ev) => {
|
||||
try {
|
||||
const msg = JSON.parse((ev as MessageEvent).data as string)
|
||||
if (msg.teamId === teamId) {
|
||||
fetchTeam()
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('[SSE] parse error:', e)
|
||||
}
|
||||
if (msg.teamId === teamId) fetchTeam()
|
||||
} catch (e) { console.error('[SSE] parse error:', e) }
|
||||
}
|
||||
|
||||
es.addEventListener('team-updated', onTeamUpdated)
|
||||
|
||||
es.onerror = () => {
|
||||
es?.close()
|
||||
es = null
|
||||
es?.close(); es = null
|
||||
setTimeout(() => {
|
||||
const next = new EventSource(url, { withCredentials: false })
|
||||
const next = new EventSource(u.toString(), { withCredentials: false })
|
||||
next.addEventListener('team-updated', onTeamUpdated)
|
||||
next.onerror = () => { next.close() }
|
||||
es = next
|
||||
|
||||
@ -76,6 +76,16 @@ function combineLocalDateTime(dateStr: string, timeStr: string) {
|
||||
return dt.toISOString();
|
||||
}
|
||||
|
||||
/** HH:MM auf 5-Minuten-Raster snappen (floor) */
|
||||
function snapTo5(timeStr: string) {
|
||||
const [hhRaw, mmRaw] = (timeStr || '00:00').split(':');
|
||||
const hh = Math.max(0, Math.min(23, Number(hhRaw) || 0));
|
||||
const mm = Math.max(0, Math.min(59, Number(mmRaw) || 0));
|
||||
const mm5 = Math.floor(mm / 5) * 5;
|
||||
const pad2 = (n:number)=>String(n).padStart(2,'0');
|
||||
return `${pad2(hh)}:${pad2(mm5)}`;
|
||||
}
|
||||
|
||||
export default function EditMatchMetaModal({
|
||||
show,
|
||||
onClose,
|
||||
@ -104,6 +114,7 @@ export default function EditMatchMetaModal({
|
||||
const pad2 = (n:number)=>String(n).padStart(2,'0');
|
||||
const hours = Array.from({ length: 24 }, (_, i) => i);
|
||||
const quarters = [0, 15, 30, 45];
|
||||
const minutes5 = [0,5,10,15,20,25,30,35,40,45,50,55];
|
||||
|
||||
// Map-Vote öffnet: Datum & Uhrzeit (lokal)
|
||||
const [voteOpenDateStr, setVoteOpenDateStr] = useState<string>(''); // YYYY-MM-DD
|
||||
@ -202,7 +213,7 @@ export default function EditMatchMetaModal({
|
||||
const openISO = new Date(new Date(matchISO).getTime() - leadMin * 60_000).toISOString();
|
||||
const openDT = isoToLocalDateTimeStrings(openISO, userTZ);
|
||||
setVoteOpenDateStr(openDT.dateStr);
|
||||
setVoteOpenTimeStr(openDT.timeStr);
|
||||
setVoteOpenTimeStr(snapTo5(openDT.timeStr));
|
||||
|
||||
const boFromMeta = normalizeBestOf(j?.bestOf)
|
||||
setBestOf(boFromMeta)
|
||||
@ -450,18 +461,19 @@ export default function EditMatchMetaModal({
|
||||
{hours.map(h => <option key={h} value={pad2(h)}>{pad2(h)}</option>)}
|
||||
</select>
|
||||
|
||||
{/* Minuten: 00/15/30/45 */}
|
||||
{/* Minuten: 5er-Raster */}
|
||||
<select
|
||||
value={pad2(Math.round(Number((voteOpenTimeStr.split(':')[1] || '0')) / 15) * 15 % 60)}
|
||||
value={pad2(Number((voteOpenTimeStr.split(':')[1] || '0')))}
|
||||
onChange={(e) => {
|
||||
const [hh] = (voteOpenTimeStr || '00:00').split(':');
|
||||
setVoteOpenTimeStr(`${hh}:${pad2(Number(e.target.value))}`);
|
||||
setVoteOpenTimeStr(`${pad2(Number(hh)||0)}:${pad2(Number(e.target.value))}`);
|
||||
}}
|
||||
className="w-full rounded-md border px-3 py-2 bg-white/70 dark:bg-neutral-900/50"
|
||||
disabled={loadingMeta}
|
||||
>
|
||||
{[0,15,30,45].map(q => <option key={q} value={pad2(q)}>{pad2(q)}</option>)}
|
||||
{minutes5.map(m => <option key={m} value={pad2(m)}>{pad2(m)}</option>)}
|
||||
</select>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -1,19 +1,17 @@
|
||||
// /src/app/[locale]/components/EditMatchPlayersModal.tsx
|
||||
'use client'
|
||||
|
||||
import { useEffect, useState, useMemo } from 'react'
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import { useSession } from 'next-auth/react'
|
||||
import { DndContext, closestCenter, DragOverlay, type DragStartEvent, type DragEndEvent } from '@dnd-kit/core'
|
||||
import { SortableContext, verticalListSortingStrategy } from '@dnd-kit/sortable'
|
||||
|
||||
import Modal from '../components/Modal'
|
||||
import Modal from '../components/Modal'
|
||||
import SortableMiniCard from '../components/SortableMiniCard'
|
||||
import LoadingSpinner from '../components/LoadingSpinner'
|
||||
import LoadingSpinner from '../components/LoadingSpinner'
|
||||
import { DroppableZone } from '../components/DroppableZone'
|
||||
|
||||
import type { Player, Team } from '../../../types/team'
|
||||
|
||||
/* ───────────────────────── Typen ────────────────────────── */
|
||||
export type EditSide = 'A' | 'B'
|
||||
|
||||
interface Props {
|
||||
@ -22,61 +20,75 @@ interface Props {
|
||||
matchId : string
|
||||
teamA : Team
|
||||
teamB : Team
|
||||
side : EditSide // welches Team wird editiert?
|
||||
initialA: string[] // bereits eingesetzte Spieler-IDs
|
||||
side : EditSide
|
||||
initialA: string[]
|
||||
initialB: string[]
|
||||
onSaved?: () => void
|
||||
}
|
||||
|
||||
/* ───────────────────── Komponente ──────────────────────── */
|
||||
export default function EditMatchPlayersModal (props: Props) {
|
||||
const {
|
||||
show, onClose, matchId,
|
||||
teamA, teamB, side,
|
||||
initialA, initialB,
|
||||
onSaved,
|
||||
} = props
|
||||
const { show, onClose, matchId, teamA, teamB, side, initialA, initialB, onSaved } = props
|
||||
|
||||
/* ---- Rollen-Check --------------------------------------- */
|
||||
const { data: session } = useSession()
|
||||
const meSteam = session?.user?.steamId
|
||||
const isAdmin = session?.user?.isAdmin
|
||||
const isAdmin = !!session?.user?.isAdmin
|
||||
const isLeader = side === 'A'
|
||||
? meSteam === teamA.leader?.steamId
|
||||
: meSteam === teamB.leader?.steamId
|
||||
const canEdit = isAdmin || isLeader
|
||||
|
||||
/* ---- Team-Info ------------------------------------------ */
|
||||
const team = side === 'A' ? teamA : teamB
|
||||
const other = side === 'A' ? teamB : teamA
|
||||
const otherInit = side === 'A' ? initialB : initialA
|
||||
const myInit = side === 'A' ? initialA : initialB
|
||||
|
||||
/* ---- States --------------------------------------------- */
|
||||
const [players, setPlayers] = useState<Player[]>([])
|
||||
const [selected, setSelected] = useState<string[]>([])
|
||||
const [dragItem, setDragItem] = useState<Player | null>(null)
|
||||
const [saving, setSaving] = useState(false)
|
||||
const [saved, setSaved] = useState(false)
|
||||
const [players, setPlayers] = useState<Player[]>([])
|
||||
const [selected, setSelected] = useState<string[]>([])
|
||||
const [dragItem, setDragItem] = useState<Player | null>(null)
|
||||
const [saving, setSaving] = useState(false)
|
||||
const [saved, setSaved] = useState(false)
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
|
||||
/* ---- Team-Info ------------------------------------------ */
|
||||
const team = side === 'A' ? teamA : teamB
|
||||
const other = side === 'A' ? teamB : teamA
|
||||
const otherInit = side === 'A' ? initialB : initialA
|
||||
const myInit = side === 'A' ? initialA : initialB
|
||||
// 🧷 Snapshots, die NUR beim Öffnen gesetzt werden
|
||||
const myInitSnapRef = useRef<string[]>([])
|
||||
const otherInitSnapRef = useRef<string[]>([])
|
||||
const otherInitSetRef = useRef<Set<string>>(new Set())
|
||||
|
||||
// 🔧 NEU: schnelles Lookup der "verbotenen" Spieler (bereits im anderen Team)
|
||||
const otherInitSet = useMemo(() => new Set(otherInit), [otherInit])
|
||||
/* ---- Initialisierung NUR beim Öffnen -------------------- */
|
||||
useEffect(() => {
|
||||
if (!show) return
|
||||
// Inhalte zum Zeitpunkt des Öffnens sichern
|
||||
myInitSnapRef.current = Array.isArray(myInit) ? [...myInit] : []
|
||||
otherInitSnapRef.current = Array.isArray(otherInit) ? [...otherInit] : []
|
||||
otherInitSetRef.current = new Set(otherInitSnapRef.current)
|
||||
|
||||
/* ---- Komplett-Spielerliste laden ------------------------ */
|
||||
// Selected EINMAL setzen (und nicht mehr durch Polling überschreiben)
|
||||
const initialSelected = myInitSnapRef.current
|
||||
.filter(id => !otherInitSetRef.current.has(id))
|
||||
.slice(0, 5)
|
||||
|
||||
setSelected(initialSelected)
|
||||
setSaved(false)
|
||||
setError(null)
|
||||
// bewusst NUR an "show" hängen (Snapshot beim Öffnen)
|
||||
}, [show]) // eslint-disable-line react-hooks/exhaustive-deps
|
||||
|
||||
/* ---- Team laden – nur an show + team.id koppeln --------- */
|
||||
useEffect(() => {
|
||||
if (!show) return
|
||||
|
||||
if (!team?.id) {
|
||||
// ❗ Kein verknüpftes Team – zeig einen klaren Hinweis
|
||||
setPlayers([])
|
||||
setSelected([])
|
||||
setError('Kein Team mit diesem Match verknüpft (fehlende Team-ID).')
|
||||
setLoading(false)
|
||||
return
|
||||
}
|
||||
|
||||
const ctrl = new AbortController()
|
||||
setLoading(true)
|
||||
setError(null)
|
||||
|
||||
@ -84,6 +96,7 @@ export default function EditMatchPlayersModal (props: Props) {
|
||||
try {
|
||||
const res = await fetch(`/api/team/${encodeURIComponent(team.id)}`, {
|
||||
cache: 'no-store',
|
||||
signal: ctrl.signal,
|
||||
})
|
||||
if (!res.ok) {
|
||||
setError(`Team-API: ${res.status}`)
|
||||
@ -96,28 +109,32 @@ export default function EditMatchPlayersModal (props: Props) {
|
||||
...(data.activePlayers ?? []),
|
||||
...(data.inactivePlayers ?? []),
|
||||
]
|
||||
.filter((p: Player) => !!p?.steamId)
|
||||
.filter((p: Player, i: number, arr: Player[]) => arr.findIndex(x => x.steamId === p.steamId) === i)
|
||||
.filter((p: Player) => !!p?.steamId)
|
||||
.filter((p: Player, i: number, arr: Player[]) => arr.findIndex(x => x.steamId === p.steamId) === i)
|
||||
|
||||
// 🔧 NEU: Spieler entfernen, die im anderen Team bereits gesetzt sind
|
||||
// mit Snapshot des anderen Teams filtern (stabil während des Editierens)
|
||||
const otherSet = otherInitSetRef.current
|
||||
const all = allRaw
|
||||
.filter((p: Player) => !otherInitSet.has(p.steamId))
|
||||
.filter((p: Player) => !otherSet.has(p.steamId))
|
||||
.sort((a: Player, b: Player) => (a.name || '').localeCompare(b.name || ''))
|
||||
|
||||
setPlayers(all)
|
||||
setSelected(myInit.filter(id => !otherInitSet.has(id)))
|
||||
setSaved(false)
|
||||
} catch (e) {
|
||||
console.error('[EditMatchPlayersModal] load error:', e)
|
||||
setError('Laden fehlgeschlagen')
|
||||
setPlayers([])
|
||||
} catch (e: unknown) {
|
||||
const name = (e as { name?: unknown })?.name
|
||||
if (name !== 'AbortError') {
|
||||
console.error('[EditMatchPlayersModal] load error:', e)
|
||||
setError('Laden fehlgeschlagen')
|
||||
setPlayers([])
|
||||
}
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
})()
|
||||
}, [show, team?.id, myInit, otherInitSet])
|
||||
|
||||
/* ---- Drag’n’Drop-Handler -------------------------------- */
|
||||
return () => ctrl.abort()
|
||||
}, [show, team?.id]) // ⚠️ keine Abhängigkeit von myInit/otherInit oder Sets (Refs sind stabil)
|
||||
|
||||
/* ---- Drag’n’Drop ---------------------------------------- */
|
||||
const onDragStart = ({ active }: DragStartEvent) => {
|
||||
setDragItem(players.find(p => p.steamId === active.id) ?? null)
|
||||
}
|
||||
@ -125,19 +142,13 @@ export default function EditMatchPlayersModal (props: Props) {
|
||||
const onDragEnd = ({ active, over }: DragEndEvent) => {
|
||||
setDragItem(null)
|
||||
if (!over) return
|
||||
const id = active.id as string
|
||||
const dropZone = over.id as string // "active" | "inactive"
|
||||
const already = selected.includes(id)
|
||||
const toActive = dropZone === 'active'
|
||||
if ((toActive && already) || (!toActive && !already)) return
|
||||
|
||||
const id = active.id as string
|
||||
const dropZone = over.id as string // "active" | "inactive"
|
||||
const already = selected.includes(id)
|
||||
const toActive = dropZone === 'active'
|
||||
|
||||
if ( (toActive && already) || (!toActive && !already) ) return
|
||||
|
||||
setSelected(sel =>
|
||||
toActive
|
||||
? [...sel, id].slice(0, 5) // max 5 einsatzfähig
|
||||
: sel.filter(x => x !== id),
|
||||
)
|
||||
setSelected(sel => toActive ? [...sel, id].slice(0, 5) : sel.filter(x => x !== id))
|
||||
}
|
||||
|
||||
/* ---- Speichern ------------------------------------------ */
|
||||
@ -147,23 +158,18 @@ export default function EditMatchPlayersModal (props: Props) {
|
||||
const body = {
|
||||
players: [
|
||||
...selected.map(steamId => ({ steamId, teamId: team.id })),
|
||||
...otherInit.map(steamId => ({ steamId, teamId: other.id })),
|
||||
...otherInitSnapRef.current.map(steamId => ({ steamId, teamId: other.id })),
|
||||
],
|
||||
}
|
||||
|
||||
const res = await fetch(`/api/matches/${matchId}`, {
|
||||
method : 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body : JSON.stringify(body),
|
||||
})
|
||||
if (!res.ok) throw new Error()
|
||||
|
||||
setSaved(true)
|
||||
|
||||
// ⏳ 3 Sekunden warten, dann schließen und danach refreshen
|
||||
setTimeout(() => {
|
||||
onClose?.()
|
||||
// onSaved (z. B. router.refresh) im nächsten Tick nach dem Schließen
|
||||
setTimeout(() => { onSaved?.() }, 0)
|
||||
}, 1500)
|
||||
} catch (e) {
|
||||
@ -173,13 +179,11 @@ export default function EditMatchPlayersModal (props: Props) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* ---- Listen trennen ------------------------------------- */
|
||||
/* ---- Listen --------------------------------------------- */
|
||||
const active = players.filter(p => selected.includes(p.steamId))
|
||||
const inactive = players.filter(p => !selected.includes(p.steamId))
|
||||
|
||||
/* ---- UI -------------------------------------------------- */
|
||||
|
||||
return (
|
||||
<Modal
|
||||
id="edit-match-players"
|
||||
@ -187,9 +191,7 @@ export default function EditMatchPlayersModal (props: Props) {
|
||||
show={show}
|
||||
onClose={onClose}
|
||||
onSave={handleSave}
|
||||
closeButtonTitle={
|
||||
saved ? '✓ Gespeichert' : saving ? 'Speichern …' : 'Speichern'
|
||||
}
|
||||
closeButtonTitle={ saved ? '✓ Gespeichert' : saving ? 'Speichern …' : 'Speichern' }
|
||||
closeButtonColor={saved ? 'green' : 'blue'}
|
||||
disableSave={!canEdit || saving || !team?.id}
|
||||
maxWidth='sm:max-w-2xl'
|
||||
@ -201,78 +203,65 @@ export default function EditMatchPlayersModal (props: Props) {
|
||||
)}
|
||||
|
||||
{canEdit && (
|
||||
<>
|
||||
{loading && <LoadingSpinner />}
|
||||
<>
|
||||
{loading && <LoadingSpinner />}
|
||||
|
||||
{!loading && error && (
|
||||
<p className="text-sm text-red-600">Fehler: {error}</p>
|
||||
)}
|
||||
{!loading && error && (
|
||||
<p className="text-sm text-red-600">Fehler: {error}</p>
|
||||
)}
|
||||
|
||||
{!loading && !error && players.length === 0 && (
|
||||
<p className="text-sm text-gray-500">Keine Spieler gefunden.</p>
|
||||
)}
|
||||
{!loading && !error && players.length === 0 && (
|
||||
<p className="text-sm text-gray-500">Keine Spieler gefunden.</p>
|
||||
)}
|
||||
|
||||
{!loading && !error && players.length > 0 && (
|
||||
<DndContext
|
||||
collisionDetection={closestCenter}
|
||||
onDragStart={onDragStart}
|
||||
onDragEnd={onDragEnd}
|
||||
>
|
||||
{/* --- Zone: Aktuell eingestellte Spieler ------------- */}
|
||||
{!loading && !error && players.length > 0 && (
|
||||
<DndContext
|
||||
collisionDetection={closestCenter}
|
||||
onDragStart={onDragStart}
|
||||
onDragEnd={onDragEnd}
|
||||
>
|
||||
<DroppableZone
|
||||
id="active"
|
||||
className="mb-4"
|
||||
label={`Eingesetzte Spieler (${active.length} / 5)`}
|
||||
activeDragItem={dragItem}
|
||||
>
|
||||
<SortableContext
|
||||
items={active.map(p => p.steamId)}
|
||||
strategy={verticalListSortingStrategy}
|
||||
>
|
||||
<SortableContext items={active.map(p => p.steamId)} strategy={verticalListSortingStrategy}>
|
||||
{active.map(p => (
|
||||
<SortableMiniCard
|
||||
key={p.steamId}
|
||||
player={p}
|
||||
currentUserSteamId={meSteam ?? ''}
|
||||
teamLeaderSteamId={team.leader?.steamId}
|
||||
isAdmin={!!session?.user?.isAdmin}
|
||||
isAdmin={isAdmin}
|
||||
hideOverlay
|
||||
/>
|
||||
))}
|
||||
</SortableContext>
|
||||
</DroppableZone>
|
||||
|
||||
{/* --- Zone: Verfügbar (restliche) ------------------- */}
|
||||
<DroppableZone
|
||||
id="inactive"
|
||||
label="Verfügbare Spieler"
|
||||
activeDragItem={dragItem}
|
||||
>
|
||||
<SortableContext
|
||||
items={inactive.map(p => p.steamId)}
|
||||
strategy={verticalListSortingStrategy}
|
||||
>
|
||||
<DroppableZone id="inactive" label="Verfügbare Spieler" activeDragItem={dragItem}>
|
||||
<SortableContext items={inactive.map(p => p.steamId)} strategy={verticalListSortingStrategy}>
|
||||
{inactive.map(p => (
|
||||
<SortableMiniCard
|
||||
key={p.steamId}
|
||||
player={p}
|
||||
currentUserSteamId={meSteam ?? ''}
|
||||
teamLeaderSteamId={team.leader?.steamId}
|
||||
isAdmin={!!session?.user?.isAdmin}
|
||||
isAdmin={isAdmin}
|
||||
hideOverlay
|
||||
/>
|
||||
))}
|
||||
</SortableContext>
|
||||
</DroppableZone>
|
||||
|
||||
{/* Drag-Overlay */}
|
||||
<DragOverlay>
|
||||
{dragItem && (
|
||||
<SortableMiniCard
|
||||
player={dragItem}
|
||||
currentUserSteamId={meSteam ?? ''}
|
||||
teamLeaderSteamId={team.leader?.steamId}
|
||||
isAdmin={!!session?.user?.isAdmin}
|
||||
isAdmin={isAdmin}
|
||||
hideOverlay
|
||||
/>
|
||||
)}
|
||||
|
||||
@ -1069,6 +1069,11 @@ export default function MapVotePanel({ match }: Props) {
|
||||
const taken = !!status
|
||||
const isAvailable = !taken && isMyTurn && isOpen && !state?.locked
|
||||
|
||||
const bgOpacityClass =
|
||||
!taken && leftIsActiveTurn
|
||||
? 'opacity-70' // sichtbarer, „weniger transparent“
|
||||
: 'opacity-30' // Standard
|
||||
|
||||
const intent = isAvailable ? currentStep?.action : null
|
||||
const intentStyles =
|
||||
intent === 'ban'
|
||||
@ -1132,7 +1137,10 @@ export default function MapVotePanel({ match }: Props) {
|
||||
onTouchEnd={onTouchEnd(map)}
|
||||
onTouchCancel={onTouchEnd(map)}
|
||||
>
|
||||
<div className="absolute inset-0 bg-center bg-cover filter opacity-30 transition-opacity duration-300" style={{ backgroundImage: `url('${bg}')` }} />
|
||||
<div
|
||||
className={`absolute inset-0 bg-center bg-cover filter ${bgOpacityClass} transition-opacity duration-300`}
|
||||
style={{ backgroundImage: `url('${bg}')` }}
|
||||
/>
|
||||
{showProgress && (
|
||||
<span aria-hidden className={`absolute inset-y-0 left-0 rounded-md ${intentStyles.progress} pointer-events-none z-10`} style={{ width: `${Math.round(progress * 100)}%` }} />
|
||||
)}
|
||||
|
||||
@ -5,6 +5,7 @@
|
||||
import { useState, useEffect, useMemo, useRef } from 'react'
|
||||
import { useRouter } from 'next/navigation'
|
||||
import { useSession } from 'next-auth/react'
|
||||
import MapVoteBanner from './MapVoteBanner'
|
||||
import Table from './Table'
|
||||
import PremierRankBadge from './PremierRankBadge'
|
||||
import CompRankBadge from './CompRankBadge'
|
||||
@ -14,7 +15,6 @@ import type { EditSide } from './EditMatchPlayersModal'
|
||||
import type { Match, MatchPlayer } from '../../../types/match'
|
||||
import Button from './Button'
|
||||
import { MAP_OPTIONS } from '@/lib/mapOptions'
|
||||
import MapVoteBanner from './MapVoteBanner'
|
||||
import { useSSEStore } from '@/lib/useSSEStore'
|
||||
import { Team } from '../../../types/team'
|
||||
import Alert from './Alert'
|
||||
@ -911,10 +911,14 @@ export function MatchDetails({match, initialNow}: { match: Match; initialNow: nu
|
||||
{/* Team A */}
|
||||
<div className="min-w-0">
|
||||
<div className="flex items-center gap-3">
|
||||
{isCommunity && match.teamA?.logo && (
|
||||
{isCommunity && (
|
||||
<Image
|
||||
src={match.teamA.logo ? `/assets/img/logos/${match.teamA.logo}` : `/assets/img/logos/cs2.webp`}
|
||||
alt={match.teamA.name ?? 'Team A'}
|
||||
src={match.teamA?.logo
|
||||
? `/assets/img/logos/${match.teamA.logo}`
|
||||
: `/assets/img/logos/cs2.webp`}
|
||||
alt={match.teamA?.name ?? 'Team A'}
|
||||
width={48} // ✅ feste Abmessungen angeben
|
||||
height={48}
|
||||
className="h-10 w-10 rounded-md object-cover ring-1 ring-white/20 bg-black/20 sm:h-12 sm:w-12"
|
||||
/>
|
||||
)}
|
||||
@ -951,10 +955,14 @@ export function MatchDetails({match, initialNow}: { match: Match; initialNow: nu
|
||||
{match.teamB?.name ?? 'Unbekannt'}
|
||||
</div>
|
||||
</div>
|
||||
{isCommunity && match.teamB?.logo && (
|
||||
{isCommunity && (
|
||||
<Image
|
||||
src={match.teamB.logo ? `/assets/img/logos/${match.teamB.logo}` : `/assets/img/logos/cs2.webp`}
|
||||
alt={match.teamB.name ?? 'Team B'}
|
||||
src={match.teamB?.logo
|
||||
? `/assets/img/logos/${match.teamB.logo}`
|
||||
: `/assets/img/logos/cs2.webp`}
|
||||
alt={match.teamB?.name ?? 'Team B'}
|
||||
width={48} // ✅ feste Abmessungen angeben
|
||||
height={48}
|
||||
className="h-10 w-10 rounded-md object-cover ring-1 ring-white/20 bg-black/20 sm:h-12 sm:w-12"
|
||||
/>
|
||||
)}
|
||||
|
||||
@ -519,6 +519,7 @@ export default function MatchReadyOverlay({
|
||||
className="absolute inset-0 object-cover"
|
||||
decoding="async"
|
||||
priority
|
||||
unoptimized
|
||||
/>
|
||||
<div className="absolute inset-0 bg-[radial-gradient(circle_at_50%_20%,rgba(255,255,255,0.08),transparent_60%)]" />
|
||||
</div>
|
||||
|
||||
@ -20,6 +20,13 @@ function parseTeams(json: TeamsJson): TeamLike[] {
|
||||
return []
|
||||
}
|
||||
|
||||
const resolveLogoSrc = (logo?: string | null): string => {
|
||||
if (!logo) return '/assets/img/logos/cs2.webp' // Fallback im /public
|
||||
if (logo.startsWith('http://') || logo.startsWith('https://')) return logo
|
||||
if (logo.startsWith('/')) return logo // bereits ab Root
|
||||
return `/assets/img/logos/${logo}` // Dateiname -> public-Pfad
|
||||
}
|
||||
|
||||
export default function Dashboard() {
|
||||
const t = useTranslations('dashboard')
|
||||
|
||||
@ -156,15 +163,21 @@ export default function Dashboard() {
|
||||
<ul className="grid grid-cols-1 gap-4 sm:grid-cols-2 xl:grid-cols-3">
|
||||
{teams.map((t) => {
|
||||
const label = t.teamname ?? t.name ?? 'Unbenannt'
|
||||
const logoPath = t.logo ? `/assets/img/logos/${t.logo}` : '/assets/img/logos/cs2.webp'
|
||||
const logoSrc = resolveLogoSrc(t.logo)
|
||||
return (
|
||||
<li
|
||||
key={t.id ?? label}
|
||||
className="group rounded-lg border border-gray-200 p-4 transition hover:shadow-sm dark:border-neutral-800"
|
||||
>
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="relative h-10 w-10 overflow-hidden rounded-full ring-1 ring-gray-200 dark:ring-neutral-700">
|
||||
<Image src={logoPath} alt={label} fill className="object-cover" sizes="40px" />
|
||||
<div className="overflow-hidden rounded-full ring-1 ring-gray-200 dark:ring-neutral-700">
|
||||
<Image
|
||||
src={logoSrc}
|
||||
alt={label}
|
||||
width={40}
|
||||
height={40}
|
||||
className="h-10 w-10 object-cover"
|
||||
/>
|
||||
</div>
|
||||
<div className="min-w-0">
|
||||
<div className="truncate text-sm font-medium text-gray-900 dark:text-white">{label}</div>
|
||||
|
||||
@ -26,8 +26,14 @@ export default function MatchesPage() {
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<Card maxWidth='auto'>
|
||||
<CommunityMatchList matchType="community" />
|
||||
</Card>
|
||||
<div className="h-[calc(100vh-32px)] min-h-0"> {/* Höhe vorgeben + min-h-0 */}
|
||||
<Card
|
||||
maxWidth="auto"
|
||||
className="h-full flex flex-col min-h-0 " // Card selber als Flex-Container
|
||||
bodyScrollable // falls dein Card das Flag braucht
|
||||
>
|
||||
<CommunityMatchList matchType="community" />
|
||||
</Card>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@ -30,9 +30,9 @@ export async function POST(req: NextRequest) {
|
||||
|
||||
// Panel-URL aus ENV (wie in mapvote)
|
||||
const panelBase =
|
||||
(process.env.PTERO_PANEL_URL ?? process.env.NEXT_PUBLIC_PTERO_PANEL_URL ?? '').trim()
|
||||
(process.env.PTERODACTYL_PANEL_URL ?? process.env.NEXT_PUBLIC_PTERODACTYL_PANEL_URL ?? '').trim()
|
||||
if (!panelBase) {
|
||||
return NextResponse.json({ error: 'PTERO_PANEL_URL not set' }, { status: 500 })
|
||||
return NextResponse.json({ error: 'PTERODACTYL_PANEL_URL not set' }, { status: 500 })
|
||||
}
|
||||
|
||||
// Server-ID aus DB (optional via Body überschreibbar)
|
||||
|
||||
@ -119,11 +119,11 @@ function buildPteroClientUrl(base: string, serverId: string) {
|
||||
async function sendServerCommand(command: string) {
|
||||
try {
|
||||
const panelBase =
|
||||
process.env.PTERO_PANEL_URL ||
|
||||
process.env.NEXT_PUBLIC_PTERO_PANEL_URL ||
|
||||
process.env.PTERODACTYL_PANEL_URL ||
|
||||
process.env.NEXT_PUBLIC_PTERODACTYL_PANEL_URL ||
|
||||
''
|
||||
if (!panelBase) {
|
||||
console.warn('[mapvote] PTERO_PANEL_URL fehlt – Command wird nicht gesendet.')
|
||||
console.warn('[mapvote] PTERODACTYL_PANEL_URL fehlt – Command wird nicht gesendet.')
|
||||
return
|
||||
}
|
||||
|
||||
@ -419,6 +419,24 @@ type MatchLike = {
|
||||
type MapVoteStep = { action: 'ban' | 'pick' | 'decider'; map?: string | null; teamId?: string | null }
|
||||
type MapVoteStateForExport = { bestOf?: number; steps: MapVoteStep[]; locked?: boolean }
|
||||
|
||||
const SPECTATORS = [
|
||||
['76561198006541937', 'Mützchen'],
|
||||
['76561197971310489', 'Fail'],
|
||||
['76561198006697116', 'Schlomo'],
|
||||
['76561198153867594', 'Luther'],
|
||||
['76561198022207129', 'Lizec'],
|
||||
['76561198132260879', 'SuperDuperDaria'],
|
||||
['76561198006470215', 'Jagarion'],
|
||||
['76561198986703551', 'captainanni'],
|
||||
['76561198063413621', 'Litboi'],
|
||||
['76561198133191395', 'Windy'],
|
||||
['76561198094445415', 'Torrul'],
|
||||
['76561199026888445', 'Flix Geduldnix'],
|
||||
] as const;
|
||||
|
||||
const SPECTATORS_MAP: Record<string, string> =
|
||||
Object.fromEntries(SPECTATORS) as Record<string, string>;
|
||||
|
||||
function playersMapFromList(list: PlayerLike[] | undefined) {
|
||||
const out: Record<string, string> = {}
|
||||
for (const p of list ?? []) {
|
||||
@ -453,17 +471,21 @@ function buildMatchJson(match: MatchLike, state: MapVoteStateForExport) {
|
||||
const team1Players = playersMapFromList(match.teamA?.players)
|
||||
const team2Players = playersMapFromList(match.teamB?.players)
|
||||
|
||||
// 👇 hier neu: zufällige Integer-ID
|
||||
const rndId = makeRandomMatchId()
|
||||
|
||||
return {
|
||||
matchid: rndId, // vorher: ""
|
||||
matchid: rndId,
|
||||
team1: { name: team1Name, players: team1Players },
|
||||
team2: { name: team2Name, players: team2Players },
|
||||
num_maps: bestOf,
|
||||
maplist,
|
||||
map_sides,
|
||||
spectators: { players: {} as Record<string, string> },
|
||||
|
||||
// ⬇️ NEU: feste Spectators mitsenden
|
||||
spectators: {
|
||||
players: SPECTATORS_MAP,
|
||||
},
|
||||
|
||||
clinch_series: true,
|
||||
players_per_team: 5,
|
||||
cvars: {
|
||||
@ -473,6 +495,7 @@ function buildMatchJson(match: MatchLike, state: MapVoteStateForExport) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
async function exportMatchToSftpDirect(match: MatchDb, vote: VoteDb) {
|
||||
try {
|
||||
const SFTPClient = (await import('ssh2-sftp-client')).default
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
|
||||
import { NextResponse } from 'next/server'
|
||||
|
||||
const PANEL = process.env.PTERO_PANEL_URL!
|
||||
const PANEL = process.env.PTERODACTYL_PANEL_URL!
|
||||
const KEY = process.env.PTERODACTYL_CLIENT_API!
|
||||
const SID = process.env.PTERO_SERVER_ID!
|
||||
|
||||
|
||||
@ -5,6 +5,10 @@ import { sessionAuthOptions } from '@/lib/auth'
|
||||
import { prisma } from '@/lib/prisma'
|
||||
import { sendServerSSEMessage } from '@/lib/sse-server-client'
|
||||
|
||||
export const runtime = 'nodejs';
|
||||
export const dynamic = 'force-dynamic';
|
||||
import 'server-only';
|
||||
|
||||
export async function POST() {
|
||||
const session = await getServerSession(sessionAuthOptions)
|
||||
const steamId = session?.user?.steamId // <-- hier definieren
|
||||
|
||||
@ -439,7 +439,8 @@ const config = {
|
||||
"isCustomOutput": true
|
||||
},
|
||||
"relativeEnvPaths": {
|
||||
"rootEnvPath": null
|
||||
"rootEnvPath": null,
|
||||
"schemaEnvPath": "../../../.env"
|
||||
},
|
||||
"relativePath": "../../../prisma",
|
||||
"clientVersion": "6.17.1",
|
||||
@ -448,7 +449,6 @@ const config = {
|
||||
"db"
|
||||
],
|
||||
"activeProvider": "postgresql",
|
||||
"postinstall": false,
|
||||
"inlineDatasources": {
|
||||
"db": {
|
||||
"url": {
|
||||
|
||||
@ -440,7 +440,8 @@ const config = {
|
||||
"isCustomOutput": true
|
||||
},
|
||||
"relativeEnvPaths": {
|
||||
"rootEnvPath": null
|
||||
"rootEnvPath": null,
|
||||
"schemaEnvPath": "../../../.env"
|
||||
},
|
||||
"relativePath": "../../../prisma",
|
||||
"clientVersion": "6.17.1",
|
||||
@ -449,7 +450,6 @@ const config = {
|
||||
"db"
|
||||
],
|
||||
"activeProvider": "postgresql",
|
||||
"postinstall": false,
|
||||
"inlineDatasources": {
|
||||
"db": {
|
||||
"url": {
|
||||
|
||||
@ -439,7 +439,8 @@ const config = {
|
||||
"isCustomOutput": true
|
||||
},
|
||||
"relativeEnvPaths": {
|
||||
"rootEnvPath": null
|
||||
"rootEnvPath": null,
|
||||
"schemaEnvPath": "../../../.env"
|
||||
},
|
||||
"relativePath": "../../../prisma",
|
||||
"clientVersion": "6.17.1",
|
||||
@ -448,7 +449,6 @@ const config = {
|
||||
"db"
|
||||
],
|
||||
"activeProvider": "postgresql",
|
||||
"postinstall": false,
|
||||
"inlineDatasources": {
|
||||
"db": {
|
||||
"url": {
|
||||
|
||||
@ -2,8 +2,6 @@
|
||||
|
||||
import {createNavigation} from 'next-intl/navigation';
|
||||
import {routing} from './routing';
|
||||
|
||||
// Lightweight wrappers around Next.js' navigation
|
||||
// APIs that consider the routing configuration
|
||||
|
||||
export const {Link, redirect, usePathname, useRouter, getPathname} =
|
||||
createNavigation(routing);
|
||||
createNavigation(routing);
|
||||
|
||||
@ -1,15 +1,14 @@
|
||||
// /src/i18n/request.ts
|
||||
export const runtime = 'nodejs'; // wichtig: nicht Edge
|
||||
import {getRequestConfig} from 'next-intl/server'
|
||||
import {hasLocale} from 'next-intl'
|
||||
import {routing} from './routing'
|
||||
|
||||
import {getRequestConfig} from 'next-intl/server';
|
||||
import {hasLocale} from 'next-intl';
|
||||
import {routing} from './routing';
|
||||
export default getRequestConfig(async ({requestLocale}) => {
|
||||
const requested = await requestLocale
|
||||
const locale = hasLocale(routing.locales, requested) ? (requested as string) : routing.defaultLocale
|
||||
|
||||
export default getRequestConfig(async ({locale}) => {
|
||||
const effective =
|
||||
hasLocale(routing.locales, locale) ? (locale as string) : routing.defaultLocale;
|
||||
// ⬇️ Eine JSON pro Locale laden
|
||||
const messages = (await import(`../messages/${locale}.json`)).default
|
||||
|
||||
const messages = (await import(`../messages/${effective}.json`)).default;
|
||||
|
||||
return { locale: effective, messages };
|
||||
});
|
||||
return { locale, messages }
|
||||
})
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
// /src/lib/sse-server-client.ts
|
||||
|
||||
const host = process.env.NEXT_PUBLIC_SSE_URL
|
||||
const host = process.env.NEXTAUTH_URL
|
||||
|
||||
export type ServerSSEMessage = {
|
||||
type: string
|
||||
@ -8,8 +8,8 @@ export type ServerSSEMessage = {
|
||||
|
||||
export async function sendServerSSEMessage(message: ServerSSEMessage): Promise<void> {
|
||||
try {
|
||||
console.log('Sending message:', message)
|
||||
const res = await fetch(`http://${host}/send`, {
|
||||
//console.log('Sending message:', message)
|
||||
const res = await fetch(`${host}/send`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(message),
|
||||
|
||||
@ -84,7 +84,7 @@ export const useSSEStore = create<SSEState>((set, get) => {
|
||||
set({ source: null, isConnected: false });
|
||||
}
|
||||
|
||||
const base = process.env.NEXT_PUBLIC_SSE_URL
|
||||
const base = process.env.NEXT_PUBLIC_APP_URL
|
||||
const url = `${base}/events?steamId=${encodeURIComponent(steamId)}`;
|
||||
const source = new EventSource(url);
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
// /src/middleware.ts
|
||||
// middleware.ts
|
||||
import { NextResponse } from 'next/server';
|
||||
import type { NextRequest } from 'next/server';
|
||||
import createIntlMiddleware from 'next-intl/middleware';
|
||||
@ -7,10 +7,12 @@ import { routing } from './i18n/routing';
|
||||
|
||||
const handleI18n = createIntlMiddleware(routing);
|
||||
|
||||
// ---- Type-Guard: prüft, ob ein Objekt eine boolsche isAdmin-Property hat
|
||||
function hasAdminFlag(v: unknown): v is { isAdmin: boolean } {
|
||||
return typeof v === 'object' && v !== null &&
|
||||
typeof (v as Record<string, unknown>).isAdmin === 'boolean';
|
||||
}
|
||||
|
||||
function getCurrentLocaleFromPath(pathname: string, locales: readonly string[], fallback: string) {
|
||||
const first = pathname.split('/')[1];
|
||||
return locales.includes(first) ? first : fallback;
|
||||
@ -26,10 +28,10 @@ function stripLeadingLocale(pathname: string, locales: readonly string[]) {
|
||||
}
|
||||
function isProtectedPath(pathnameNoLocale: string) {
|
||||
return (
|
||||
pathnameNoLocale === '/' ||
|
||||
pathnameNoLocale.startsWith('/settings') ||
|
||||
pathnameNoLocale.startsWith('/matches') ||
|
||||
pathnameNoLocale.startsWith('/team') ||
|
||||
pathnameNoLocale.startsWith('/') ||
|
||||
pathnameNoLocale.startsWith('/settings') ||
|
||||
pathnameNoLocale.startsWith('/matches') ||
|
||||
pathnameNoLocale.startsWith('/team') ||
|
||||
pathnameNoLocale.startsWith('/admin')
|
||||
);
|
||||
}
|
||||
@ -52,31 +54,10 @@ export default async function middleware(req: NextRequest) {
|
||||
}
|
||||
|
||||
const i18nRes = handleI18n(req);
|
||||
|
||||
// 1) Rewrites -> immer auf aktuelle öffentliche Origin
|
||||
const rew = i18nRes.headers.get('x-middleware-rewrite');
|
||||
if (rew) {
|
||||
const t = new URL(rew, req.url);
|
||||
const out = new URL(req.url);
|
||||
out.pathname = t.pathname;
|
||||
out.search = t.search;
|
||||
return NextResponse.rewrite(out);
|
||||
}
|
||||
|
||||
// 2) Redirects (Location) -> ebenfalls auf aktuelle Origin mappen
|
||||
const loc = i18nRes.headers.get('location');
|
||||
if (loc) {
|
||||
const t = new URL(loc, req.url);
|
||||
if (t.hostname !== req.nextUrl.hostname) {
|
||||
const out = new URL(req.url);
|
||||
out.pathname = t.pathname;
|
||||
out.search = t.search;
|
||||
return NextResponse.redirect(out); // 307
|
||||
}
|
||||
if (i18nRes.headers.get('location') || i18nRes.headers.get('x-middleware-rewrite')) {
|
||||
return i18nRes;
|
||||
}
|
||||
|
||||
// 3) Geschützte Bereiche
|
||||
const { locales, defaultLocale } = routing;
|
||||
const url = req.nextUrl;
|
||||
const pathnameNoLocale = stripLeadingLocale(pathname, locales);
|
||||
@ -86,6 +67,7 @@ export default async function middleware(req: NextRequest) {
|
||||
|
||||
const token = await getToken({ req, secret: process.env.NEXTAUTH_SECRET });
|
||||
|
||||
// Adminschutz (ohne any)
|
||||
if (pathnameNoLocale.startsWith('/admin')) {
|
||||
const isAdmin = hasAdminFlag(token) && token.isAdmin === true;
|
||||
if (!isAdmin) {
|
||||
@ -96,6 +78,7 @@ export default async function middleware(req: NextRequest) {
|
||||
}
|
||||
}
|
||||
|
||||
// Allgemeiner Auth-Schutz
|
||||
if (!token) {
|
||||
const loginUrl = new URL('/api/auth/signin', req.url);
|
||||
loginUrl.searchParams.set('callbackUrl', url.toString());
|
||||
|
||||
@ -12,7 +12,7 @@ export const CS2_DOWNLOADER_URL =
|
||||
process.env.CS2_DOWNLOADER_URL ?? 'http://localhost:4000';
|
||||
|
||||
export const CS2_INTERNAL_API_URL =
|
||||
process.env.CS2_INTERNAL_API_URL ?? 'http://localhost:3000';
|
||||
process.env.CS2_INTERNAL_API_URL ?? 'http://localhost:30000';
|
||||
|
||||
export const DEMOS_ROOT =
|
||||
process.env.DEMOS_ROOT ?? 'demos';
|
||||
|
||||
@ -36,7 +36,7 @@ export async function parseDemoViaGo(
|
||||
|
||||
const parserPath = path.resolve(
|
||||
__dirname,
|
||||
'../../../../ironie-cs2-parser/parser_cs2-win.exe'
|
||||
'../../../../ironie-cs2-parser/parser_cs2-linux'
|
||||
);
|
||||
const decoded = decodeMatchShareCode(shareCode);
|
||||
const matchId = decoded.matchId.toString();
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user