nerdware/apps/web/src/pages/join.tsx
AyoubBenziza 1e5ef2be90 feat(web): i18n (FR/EN) — homemade typed dictionary
- lightweight, dependency-free i18n: typed fr/en dictionaries (fr is the source
  of truth, en must match its shape → compile error on drift), I18nProvider +
  useI18n hook, browser detection + localStorage persistence
- FR/EN language switcher (home + room header)
- translated the whole player-facing flow: home, join, pseudo, room shell,
  lobby (incl. settings, categories, bots, track submission, history),
  quiz, blindtest, player-cards, round transitions, end screen (podium, awards,
  tracks, rounds recap), and server-error messages (mapped by code)
- history dates localized to the active language

Content (questions/categories) intentionally left as-is. Back-office (admin)
not translated yet — internal, token-gated tool.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-11 19:03:28 +02:00

43 lines
1.4 KiB
TypeScript

import { useEffect, useRef, useState } from "react"
import { Link, useLocation } from "wouter"
import { Button } from "@workspace/ui/components/button"
import { useRoomStore } from "@/store/room"
import { useI18n } from "@/i18n/context"
import { errorMessage } from "@/i18n/error"
/** Lien d'invitation /join/:code → rejoint la room puis redirige vers le lobby. */
export function JoinPage({ code }: { code: string }) {
const [, navigate] = useLocation()
const { t } = useI18n()
const joinRoom = useRoomStore((s) => s.joinRoom)
const connected = useRoomStore((s) => s.connected)
const [error, setError] = useState<string | null>(null)
const tried = useRef(false)
useEffect(() => {
if (!connected || tried.current) {
return
}
tried.current = true
joinRoom(code)
.then(({ roomCode }) => navigate(`/room/${roomCode}`, { replace: true }))
.catch((err) => setError(errorMessage(t, err)))
}, [connected, code, joinRoom, navigate, t])
return (
<div className="flex min-h-svh flex-col items-center justify-center gap-4 p-6 text-center">
{error ? (
<>
<p className="text-muted-foreground text-sm">{error}</p>
<Link href="/">
<Button variant="secondary">{t.common.back}</Button>
</Link>
</>
) : (
<p className="text-muted-foreground text-sm">
{t.joinPage.connecting(code)}
</p>
)}
</div>
)
}