From 81c807cd77ca61407e0f7ec4eacd8c33e419a923 Mon Sep 17 00:00:00 2001 From: Thomas Fransolet Date: Tue, 15 Sep 2026 22:41:40 +0200 Subject: [PATCH] =?UTF-8?q?Studio=20lot=208bis=20:=20pr=C3=A9senter=20les?= =?UTF-8?q?=20personnages=20au=20visiteur?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Écran d'entrée, une fois par session, 11 langues. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_011VxSQeGQYUvPmSEoGdnidA --- src/app/[slug]/[configId]/page.tsx | 2 + src/components/CastingIntro.tsx | 73 ++++++++++++++++++++++++++++++ src/lib/api/types.ts | 8 ++++ src/lib/i18n.ts | 26 +++++++++++ 4 files changed, 109 insertions(+) create mode 100644 src/components/CastingIntro.tsx diff --git a/src/app/[slug]/[configId]/page.tsx b/src/app/[slug]/[configId]/page.tsx index 9cdcae0..b6b09e4 100644 --- a/src/app/[slug]/[configId]/page.tsx +++ b/src/app/[slug]/[configId]/page.tsx @@ -3,6 +3,7 @@ import { getInstanceBySlug, getConfiguration, getSections } from '@/lib/api/clie import SectionList from '@/components/SectionList' import QRScannerButton from '@/components/QRScannerButton' import ProximitySuggestion from '@/components/ProximitySuggestion' +import CastingIntro from '@/components/CastingIntro' export default async function ConfigPage({ params, @@ -39,6 +40,7 @@ export default async function ConfigPage({ configPrimaryColor={config.primaryColor} languages={config.languages ?? ['FR']} /> + {config.casting && config.casting.length >= 2 && } {instance.isQRCodeEnabled !== false && } diff --git a/src/components/CastingIntro.tsx b/src/components/CastingIntro.tsx new file mode 100644 index 0000000..a27a426 --- /dev/null +++ b/src/components/CastingIntro.tsx @@ -0,0 +1,73 @@ +'use client' + +import { useEffect, useState } from 'react' +import Image from 'next/image' +import { useVisitor } from '@/context/VisitorContext' +import { ui } from '@/lib/i18n' +import type { CastingVisitorMemberDTO } from '@/lib/api/types' + +/** + * Écran d'entrée « les personnes que vous allez rencontrer » (plan Studio décision 25). Le serveur + * ne l'envoie qu'avec l'interrupteur armé et au moins deux portraits. Montré une fois par visite : + * le visiteur qui revient à la liste des sections ne le revoit pas. + */ +export default function CastingIntro({ casting, configId }: { casting: CastingVisitorMemberDTO[]; configId: string }) { + const { language } = useVisitor() + const [open, setOpen] = useState(false) + const storageKey = `casting-seen-${configId}` + + useEffect(() => { + try { + if (sessionStorage.getItem(storageKey)) return + } catch { + // Stockage indisponible (navigation privée) : on montre l'écran, sans mémoriser. + } + setOpen(true) + }, [storageKey]) + + function close() { + try { + sessionStorage.setItem(storageKey, '1') + } catch { + // Idem : l'écran reviendra à la prochaine ouverture, ce n'est pas grave. + } + setOpen(false) + } + + if (!open) return null + + return ( +
+
+

+ {ui('castingTitle', language)} +

+
+ {casting.map((member, index) => ( +
+ + {member.name} + {member.role && {member.role}} +
+ ))} +
+ +
+
+ ) +} diff --git a/src/lib/api/types.ts b/src/lib/api/types.ts index 0d90fcb..bc17d8d 100644 --- a/src/lib/api/types.ts +++ b/src/lib/api/types.ts @@ -96,6 +96,14 @@ export interface ConfigurationDTO { isOffline?: boolean sectionIds?: string[] sections?: SectionDTO[] + /** Casting montre au visiteur (decision 25). Absent tant que l'ecran d'entree ne doit pas s'afficher. */ + casting?: CastingVisitorMemberDTO[] +} + +export interface CastingVisitorMemberDTO { + name: string + role?: string + portraitUrl: string } export interface AppConfigurationLinkDTO { diff --git a/src/lib/i18n.ts b/src/lib/i18n.ts index 4208a32..ecdb713 100644 --- a/src/lib/i18n.ts +++ b/src/lib/i18n.ts @@ -24,6 +24,32 @@ const UI_STRINGS: Record> = { UK: 'Зображення, створене ШІ', LB: 'Bild generéiert vun der KI', }, + castingTitle: { + FR: 'Les personnes que vous allez rencontrer', + NL: 'De personen die u zult ontmoeten', + EN: 'The people you will meet', + DE: 'Die Menschen, denen Sie begegnen werden', + ES: 'Las personas que va a conocer', + IT: 'Le persone che incontrerete', + PL: 'Osoby, które spotkasz', + CN: '您将遇到的人物', + AR: 'الشخصيات التي ستلتقيها', + UK: 'Люди, яких ви зустрінете', + LB: "D'Leit, déi Dir kenne léiert", + }, + castingStart: { + FR: 'Commencer la visite', + NL: 'Het bezoek beginnen', + EN: 'Start the visit', + DE: 'Besuch beginnen', + ES: 'Empezar la visita', + IT: 'Inizia la visita', + PL: 'Rozpocznij zwiedzanie', + CN: '开始参观', + AR: 'ابدأ الزيارة', + UK: 'Почати відвідування', + LB: 'Besuch ufänken', + }, noEventsThisMonth: { FR: 'Aucun événement ce mois-ci', NL: 'Geen evenementen deze maand',