update
This commit is contained in:
parent
82a59d3da1
commit
7d204fe836
@ -1,18 +1,18 @@
|
||||
// src/app/components/TelemetryBanner.tsx
|
||||
// src/app/components/GameBanner.tsx
|
||||
'use client'
|
||||
|
||||
import React, { useEffect, useRef } from 'react'
|
||||
import Link from 'next/link'
|
||||
import Button from '../components/Button'
|
||||
import Button from './Button'
|
||||
import { useUiChromeStore } from '@/lib/useUiChromeStore'
|
||||
import { MAP_OPTIONS } from '@/lib/mapOptions'
|
||||
import { useRouter, usePathname } from '@/i18n/navigation'
|
||||
import { useTranslations, useLocale } from 'next-intl'
|
||||
|
||||
export type TelemetryBannerVariant = 'connected' | 'disconnected'
|
||||
export type GameBannerVariant = 'connected' | 'disconnected'
|
||||
|
||||
type Props = {
|
||||
variant: TelemetryBannerVariant
|
||||
variant: GameBannerVariant
|
||||
visible: boolean
|
||||
zIndex?: number
|
||||
// gemeinsam
|
||||
@ -54,7 +54,7 @@ function pickMapIcon(mapKey?: string): string | null {
|
||||
}
|
||||
|
||||
/* ---------- component ---------- */
|
||||
export default function TelemetryBanner({
|
||||
export default function GameBanner({
|
||||
variant,
|
||||
visible,
|
||||
zIndex = 9999,
|
||||
@ -72,15 +72,12 @@ export default function TelemetryBanner({
|
||||
inline = false,
|
||||
}: Props) {
|
||||
const ref = useRef<HTMLDivElement | null>(null)
|
||||
const setBannerPx = useUiChromeStore(s => s.setTelemetryBannerPx)
|
||||
const setBannerPx = useUiChromeStore(s => s.setGameBannerPx)
|
||||
|
||||
// ▼ Phase normalisieren und Sichtbarkeit nur erlauben, wenn nicht "unknown"
|
||||
const phaseStr = String(phase ?? 'unknown').toLowerCase()
|
||||
const show = visible && phaseStr !== 'unknown'
|
||||
|
||||
const router = useRouter()
|
||||
const pathname = usePathname()
|
||||
|
||||
// Übersetzungen
|
||||
const tGameBanner = useTranslations('game-banner')
|
||||
|
||||
@ -96,7 +93,7 @@ export default function TelemetryBanner({
|
||||
}, [show, setBannerPx])
|
||||
|
||||
// Ableitungen vor dem Guard
|
||||
const outerBase = inline ? '' : 'fixed inset-x-0 bottom-0'
|
||||
const outerBase = inline ? '' : 'fixed right-0 bottom-0 left-0 sm:left-[16rem]'
|
||||
const outerStyle = inline ? {} : { zIndex }
|
||||
|
||||
const wrapperClass =
|
||||
@ -121,8 +118,8 @@ export default function TelemetryBanner({
|
||||
if (!show) return null
|
||||
|
||||
return (
|
||||
<div className={`${outerBase} h-full w-full`} style={outerStyle} ref={ref}>
|
||||
<div className={`relative overflow-hidden h-full shadow-lg ${wrapperClass} transition duration-300 ease-in-out`}>
|
||||
<div className={outerBase} style={outerStyle} ref={ref}>
|
||||
<div className={`relative overflow-hidden shadow-lg ${wrapperClass} transition duration-300 ease-in-out`}>
|
||||
{/* Subtiler Map-Hintergrund */}
|
||||
{bgUrl && (
|
||||
<div
|
||||
@ -149,7 +146,7 @@ export default function TelemetryBanner({
|
||||
/>
|
||||
|
||||
{/* Inhalt */}
|
||||
<div className="relative h-full p-3 flex items-center gap-3">
|
||||
<div className="relative p-3 flex items-center gap-3">
|
||||
{/* Icon links */}
|
||||
{iconUrl ? (
|
||||
<div className="shrink-0 relative z-[1]">
|
||||
10
src/app/[locale]/components/GameBannerSpacer.tsx
Normal file
10
src/app/[locale]/components/GameBannerSpacer.tsx
Normal file
@ -0,0 +1,10 @@
|
||||
// src/app/components/GameBannerSpacer.tsx
|
||||
'use client'
|
||||
import {useEffect, useState} from 'react'
|
||||
import {useUiChromeStore} from '@/lib/useUiChromeStore'
|
||||
|
||||
export default function GameBannerSpacer() {
|
||||
const bannerPx = useUiChromeStore(s => s.gameBannerPx) // oder passender Selector
|
||||
// Nur Höhe setzen, wenn inline-Banner genutzt wird – sonst 0
|
||||
return <div style={{height: bannerPx ?? 0}} aria-hidden />
|
||||
}
|
||||
@ -7,7 +7,7 @@ import { useReadyOverlayStore } from '@/lib/useReadyOverlayStore'
|
||||
import { usePresenceStore } from '@/lib/usePresenceStore'
|
||||
import { useTelemetryStore } from '@/lib/useTelemetryStore'
|
||||
import { useMatchRosterStore } from '@/lib/useMatchRosterStore'
|
||||
import TelemetryBanner from './TelemetryBanner'
|
||||
import TelemetryBanner from './GameBanner'
|
||||
import { MAP_OPTIONS } from '@/lib/mapOptions'
|
||||
import { useSSEStore } from '@/lib/useSSEStore'
|
||||
|
||||
@ -107,7 +107,7 @@ export default function TelemetrySocket() {
|
||||
const [dockEl, setDockEl] = useState<HTMLElement | null>(null)
|
||||
useEffect(() => {
|
||||
if (typeof window === 'undefined') return
|
||||
setDockEl(document.getElementById('telemetry-banner-dock') as HTMLElement | null)
|
||||
setDockEl(document.getElementById('game-banner-dock') as HTMLElement | null)
|
||||
}, [])
|
||||
|
||||
// connect href from API
|
||||
|
||||
@ -34,7 +34,7 @@ export default function AppearanceSettings() {
|
||||
|
||||
<div className="sm:col-span-8 xl:col-span-6 2xl:col-span-5">
|
||||
<p className="text-sm text-gray-500 dark:text-neutral-500">
|
||||
Wähle dein bevorzugtes Design. Du kannst einen festen Stil verwenden oder das Systemverhalten übernehmen.
|
||||
{tSettings("tabs.account.page.AppearanceSettings.description")}
|
||||
</p>
|
||||
|
||||
<h3 className="mt-3 text-sm font-semibold text-gray-800 dark:text-neutral-200">
|
||||
|
||||
@ -18,6 +18,7 @@ import UserActivityTracker from './components/UserActivityTracker';
|
||||
import AudioPrimer from './components/AudioPrimer';
|
||||
import ReadyOverlayHost from './components/ReadyOverlayHost';
|
||||
import TelemetrySocket from './components/TelemetrySocket';
|
||||
import GameBannerSpacer from './components/GameBannerSpacer';
|
||||
|
||||
const geistSans = Geist({variable: '--font-geist-sans', subsets: ['latin']});
|
||||
const geistMono = Geist_Mono({variable: '--font-geist-mono', subsets: ['latin']});
|
||||
@ -36,7 +37,6 @@ export default async function RootLayout({children, params}: Props) {
|
||||
const {locale} = await params;
|
||||
if (!hasLocale(routing.locales, locale)) notFound();
|
||||
|
||||
// ⬇️ holt die von request.ts gemergten Namespaces
|
||||
const messages = await getMessages();
|
||||
const lang = await getLocale();
|
||||
|
||||
@ -44,6 +44,7 @@ export default async function RootLayout({children, params}: Props) {
|
||||
<html lang={lang} suppressHydrationWarning>
|
||||
<body className={`antialiased bg-white dark:bg-black min-h-dvh ${geistSans.variable} ${geistMono.variable}`}>
|
||||
<ThemeProvider attribute="class" defaultTheme="system" enableSystem disableTransitionOnChange>
|
||||
{/* ⬇️ EIN globaler i18n-Provider um ALLES */}
|
||||
<NextIntlClientProvider locale={lang} messages={messages}>
|
||||
<Providers>
|
||||
<SSEHandler />
|
||||
@ -51,15 +52,17 @@ export default async function RootLayout({children, params}: Props) {
|
||||
<AudioPrimer />
|
||||
<ReadyOverlayHost />
|
||||
<TelemetrySocket />
|
||||
|
||||
<div className="min-h-dvh grid grid-cols-1 sm:grid-cols-[16rem_1fr]">
|
||||
<Sidebar />
|
||||
<div className="min-w-0 flex flex-col">
|
||||
<main className="flex-1 in-w-0 overflow-hidden">
|
||||
<main className="flex-1 min-w-0 overflow-hidden">
|
||||
<div className="h-full box-border p-4 sm:p-6">{children}</div>
|
||||
</main>
|
||||
<div id="telemetry-banner-dock" className="h-full max-h-[65px]" />
|
||||
<GameBannerSpacer />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<NotificationBell />
|
||||
</Providers>
|
||||
</NextIntlClientProvider>
|
||||
|
||||
@ -1,42 +1,14 @@
|
||||
// /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<T>(p: string): Promise<T | null> {
|
||||
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/<namespace>/<locale>.json
|
||||
const obj = await tryImport<Record<string, unknown>>(
|
||||
`../messages/${ns}/${locale}.json`
|
||||
);
|
||||
return [ns, obj ?? {}] as const;
|
||||
})
|
||||
);
|
||||
|
||||
const merged = {} as Record<Namespace, Record<string, unknown>>;
|
||||
for (const [ns, obj] of entries) merged[ns] = obj;
|
||||
return merged;
|
||||
}
|
||||
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;
|
||||
return { locale, messages: await loadMessages(locale) };
|
||||
});
|
||||
const requested = await requestLocale
|
||||
const locale = hasLocale(routing.locales, requested) ? (requested as string) : routing.defaultLocale
|
||||
|
||||
// ⬇️ Eine JSON pro Locale laden
|
||||
const messages = (await import(`../messages/${locale}.json`)).default
|
||||
|
||||
return { locale, messages }
|
||||
})
|
||||
|
||||
@ -2,11 +2,11 @@
|
||||
import { create } from 'zustand'
|
||||
|
||||
type UiChromeState = {
|
||||
telemetryBannerPx: number // aktuelle Bannerhöhe in Pixel (0 wenn unsichtbar)
|
||||
setTelemetryBannerPx: (px: number) => void
|
||||
gameBannerPx: number // aktuelle Bannerhöhe in Pixel (0 wenn unsichtbar)
|
||||
setGameBannerPx: (px: number) => void
|
||||
}
|
||||
|
||||
export const useUiChromeStore = create<UiChromeState>((set) => ({
|
||||
telemetryBannerPx: 0,
|
||||
setTelemetryBannerPx: (px) => set({ telemetryBannerPx: Math.max(0, Math.floor(px)) }),
|
||||
gameBannerPx: 0,
|
||||
setGameBannerPx: (px) => set({ gameBannerPx: Math.max(0, Math.floor(px)) }),
|
||||
}))
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
{
|
||||
"disconnect": "Verbindung trennen",
|
||||
"nav": {
|
||||
"dashboard": "Dashboard",
|
||||
"teams": {
|
||||
@ -8,13 +9,13 @@
|
||||
},
|
||||
"players": {
|
||||
"label": "Spieler",
|
||||
"overview": "Übersicht",
|
||||
"overview": "Überblick",
|
||||
"stats": "Statistiken"
|
||||
},
|
||||
"schedule": "Spielplan"
|
||||
"schedule": "Spielpan"
|
||||
},
|
||||
"dashboard": {
|
||||
"title": "Willkommen im Dashboard!"
|
||||
"title": "Willkommen!"
|
||||
},
|
||||
"sidebar": {
|
||||
"brand": "Iron:e",
|
||||
@ -31,17 +32,51 @@
|
||||
"signout": "Abmelden"
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"tabs": {
|
||||
"account": {
|
||||
"title": "Accounteinstellungen",
|
||||
"short": "Account",
|
||||
"description": "Verwalte deine Kontoeinstellungen und verbundenen Dienste.",
|
||||
"find-code": "Deinen Code findest du",
|
||||
"here": "hier",
|
||||
"url": "https://help.steampowered.com/de/wizard/HelpWithGameIssue/?appid=730&issueid=128",
|
||||
"page": {
|
||||
"AuthCodeSettings": {
|
||||
"name": "Authentifizierungscode",
|
||||
"question": "Was ist der Authentifizierungscode?",
|
||||
"description": "Drittanbieter-Webseiten und -Anwendungen können diesen Authentifizierungscode nutzen, um auf deine Match-Historie, deine Gesamtleistung in diesen Matches zuzugreifen, Wiederholungen herunterzuladen und dein Gameplay zu analysieren.",
|
||||
"button-disconnect": "Trennen"
|
||||
},
|
||||
"ShareCodeSettings": {
|
||||
"name": "Austauschcode",
|
||||
"question": "Was ist der Austauschcode?",
|
||||
"description": "Mit dem Austauschcode können Anwendungen dein zuletzt ausgetragenes offizielles Match finden und analysieren."
|
||||
},
|
||||
"AppearanceSettings": {
|
||||
"name": "Erscheinungsbild",
|
||||
"description": "Wähle dein bevorzugtes Design. Du kannst einen festen Stil verwenden oder die Systemeinstellung übernehmen."
|
||||
}
|
||||
}
|
||||
},
|
||||
"privacy": {
|
||||
"title": "Datenschutzeinstellungen",
|
||||
"short": "Datenschutz",
|
||||
"description": "Verwalte deine Datenschutzeinstellungen."
|
||||
}
|
||||
}
|
||||
},
|
||||
"game-banner": {
|
||||
"disconnected": "Verbindung getrennt",
|
||||
"disconnected": "Nicht verbunden",
|
||||
"player-connected": "Spieler verbunden",
|
||||
"open-game": "Spiel öffnen",
|
||||
"quit": "Verlassen",
|
||||
"reconnect": "Neu verbinden"
|
||||
"open-game": "Spiel starten",
|
||||
"quit": "Beenden",
|
||||
"reconnect": "Erneut verbinden"
|
||||
},
|
||||
"matches": {
|
||||
"title": "Geplante Matches",
|
||||
"description": "Keine Matches geplant.",
|
||||
"filter": "Nur mein Team anzeigen",
|
||||
"create-match": "Neues Match erstellen"
|
||||
}
|
||||
}
|
||||
"create-match": "Match erstellen"
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
{
|
||||
"disconnect": "Disconnect",
|
||||
"nav": {
|
||||
"dashboard": "Dashboard",
|
||||
"teams": {
|
||||
@ -23,7 +24,7 @@
|
||||
"en": "English"
|
||||
},
|
||||
"footer": {
|
||||
"login": "Login with Steam",
|
||||
"login": "Sign in with Steam",
|
||||
"profile": "Profile",
|
||||
"team": "Team",
|
||||
"settings": "Settings",
|
||||
@ -31,17 +32,51 @@
|
||||
"signout": "Sign out"
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"tabs": {
|
||||
"account": {
|
||||
"title": "Account Settings",
|
||||
"short": "Account",
|
||||
"description": "Manage your account settings and connected services.",
|
||||
"find-code": "You can find your code",
|
||||
"here": "here",
|
||||
"url": "https://help.steampowered.com/en/wizard/HelpWithGameIssue/?appid=730&issueid=128",
|
||||
"page": {
|
||||
"AuthCodeSettings": {
|
||||
"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, your overall performance in those matches, download replays of your matches, and analyze your gameplay.",
|
||||
"button-disconnect": "Disconnect"
|
||||
},
|
||||
"ShareCodeSettings": {
|
||||
"name": "Match Share Code",
|
||||
"question": "What is the Match Share Code?",
|
||||
"description": "With the share code, applications can find and analyze your most recent official match."
|
||||
},
|
||||
"AppearanceSettings": {
|
||||
"name": "Appearance",
|
||||
"description": "Choose your preferred theme. You can use a fixed style or follow your system setting."
|
||||
}
|
||||
}
|
||||
},
|
||||
"privacy": {
|
||||
"title": "Privacy Settings",
|
||||
"short": "Privacy",
|
||||
"description": "Manage your privacy preferences."
|
||||
}
|
||||
}
|
||||
},
|
||||
"game-banner": {
|
||||
"disconnected": "Disconnected",
|
||||
"player-connected": "Players connected",
|
||||
"open-game": "Open game",
|
||||
"open-game": "Open Game",
|
||||
"quit": "Quit",
|
||||
"reconnect": "Reconnect"
|
||||
},
|
||||
"matches": {
|
||||
"title": "Scheduled matches",
|
||||
"title": "Scheduled Matches",
|
||||
"description": "No matches scheduled.",
|
||||
"filter": "Show my team only",
|
||||
"create-match": "Create new match"
|
||||
}
|
||||
"create-match": "Create Match"
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user