Files
parenthese/frontend/src/components/AccessGate.jsx
T
IliesAllaliandClaude Opus 5.5 6f28918dde Interface en anglais : traduction, bascule FR · EN, accents corrigés
Module frontend/src/i18n : le texte français sert de clé, dictionnaires anglais
par zone, langue du navigateur puis choix mémorisé, ?lang=en pour forcer.
Bascule dans le menu du compte, la pastille visiteur, le menu Plus, l'écran
d'accès et la connexion. Démo traduite (noms et lieux gardés). Liens d'aide et
de vie privée selon la langue. Textes français sans accents corrigés.
Test : chaque texte passé à t() a sa traduction.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 16:01:15 +02:00

167 lines
6.3 KiB
React

import { Fragment, useEffect, useState } from 'react'
import logoUrl from '../assets/parenthese-logo.svg?url'
import './AccountScreens.css'
import PzBusy from './PzBusy.jsx'
import LanguageSwitch from './navbar/LanguageSwitch'
import { t } from '../i18n/index.js'
import { readContributorName, saveContributorName } from '../utils/contributorName.js'
// Phrase traduite d'un bloc : les passages entre astérisques passent en <em>, {slot} reçoit un nœud.
function richText(text, slotNode = null) {
return text.split(/(\{slot\})/).flatMap((chunk, chunkIndex) => (
chunk === '{slot}'
? [<Fragment key={`s${chunkIndex}`}>{slotNode}</Fragment>]
: chunk.split('*').map((part, index) => (index % 2 ? <em key={`${chunkIndex}-${index}`}>{part}</em> : part))
))
}
const AccessGate = ({
initialTreeId,
treeName,
treeDescription = '',
treeOwnerName = '',
treeNotFound = false,
loading,
errorMessage,
onSubmit,
onUseAccount,
}) => {
const [treeId, setTreeId] = useState(initialTreeId || '')
const [password, setPassword] = useState('')
// Facultatif : la personne qui partage l'arbre voit qui est passé (onglet Visites)
const [firstName, setFirstName] = useState(() => readContributorName())
useEffect(() => {
setTreeId(initialTreeId || '')
}, [initialTreeId])
const handleSubmit = async (event) => {
event.preventDefault()
if (!treeId || !password || loading) {
return
}
saveContributorName(firstName)
await onSubmit(treeId.trim(), password)
}
// Jamais d'adresse email avant le mot de passe : seul un nom explicite peut s'afficher
const ownerLabel = treeOwnerName || ''
return (
<div className="access-gate pz-screen">
<div className="pz-screen-inner">
<img src={logoUrl} alt="Parenthèse" className="pz-screen-logo" />
<form className="pz-card pz-screen-card" onSubmit={handleSubmit}>
<div className="pz-auth-head">
<p className="pz-eyebrow">{t('Arbre partagé')}</p>
{treeNotFound ? (
<h1 className="pz-title">{richText(t('Cet arbre est *introuvable*'))}</h1>
) : (
<h1 className="pz-title">
{treeName ? richText(t('Ouvrir {slot}'), <em>{treeName}</em>) : richText(t('Ouvrir un arbre *partagé*'))}
</h1>
)}
<p className="pz-sub">
{treeNotFound
? t("Le lien est peut-être incomplet, ou l'arbre a été supprimé. Demandez un nouveau lien à la personne qui vous l'a envoyé.")
: treeName
? t('Entrez le mot de passe reçu avec le lien. Pas besoin de compte pour regarder.')
: t('Entrez le code et le mot de passe reçus avec votre lien de partage.')}
</p>
</div>
{!treeNotFound && (ownerLabel || treeDescription) && (
<div className="pz-gate-owner">
{ownerLabel && (
<span className="pz-gate-mono" aria-hidden="true">{ownerLabel.trim().charAt(0).toUpperCase()}</span>
)}
<div className="pz-gate-owner-text">
{ownerLabel && <p className="access-gate-tree-meta pz-small">{richText(t('Partagé par {slot}'), <strong>{ownerLabel}</strong>)}</p>}
{treeDescription && <p className="pz-small">{treeDescription}</p>}
</div>
</div>
)}
{!treeNotFound && (
<div className="pz-auth-form">
{!initialTreeId && (
<div className="pz-field">
<label htmlFor="treeId">{t("Code de l'arbre")}</label>
<input
id="treeId"
type="text"
value={treeId}
onChange={(event) => setTreeId(event.target.value)}
disabled={loading}
placeholder={t('Il commence souvent par cm')}
autoComplete="off"
required
/>
</div>
)}
<div className="pz-field">
<label htmlFor="visitorFirstName">{t('Votre prénom')}</label>
<input
id="visitorFirstName"
type="text"
value={firstName}
onChange={(event) => setFirstName(event.target.value)}
disabled={loading}
autoComplete="given-name"
maxLength={60}
/>
<p className="pz-hint">{t("Facultatif. Il sert à savoir qui est venu voir l'arbre.")}</p>
</div>
<div className="pz-field">
<label htmlFor="sharePassword">{t('Mot de passe de partage')}</label>
<input
id="sharePassword"
type="password"
value={password}
onChange={(event) => setPassword(event.target.value)}
disabled={loading}
autoFocus={Boolean(initialTreeId)}
required
/>
</div>
</div>
)}
{errorMessage && <div className="access-gate-error pz-error" role="alert">{errorMessage}</div>}
{!treeNotFound && (
<button type="submit" className="pz-btn pz-btn--primary pz-btn--block" disabled={loading}>
<PzBusy busy={loading} busyLabel={t("Ouverture de l'arbre")}>{t("Ouvrir l'arbre")}</PzBusy>
</button>
)}
{treeNotFound && onUseAccount && (
<button type="button" className="pz-btn pz-btn--primary pz-btn--block" onClick={() => onUseAccount('register')} disabled={loading}>
{t('Créer mon arbre')}
</button>
)}
</form>
{onUseAccount && !treeNotFound && (
<div className="pz-account-foot">
<button type="button" className="pz-btn pz-btn--ghost pz-btn--sm" onClick={() => onUseAccount('login')} disabled={loading}>
{t('Se connecter avec un compte')}
</button>
<button type="button" className="pz-btn pz-btn--ghost pz-btn--sm" onClick={() => onUseAccount('register')} disabled={loading}>
{t('Créer un compte')}
</button>
</div>
)}
<LanguageSwitch className="pz-lang--screen" />
</div>
</div>
)
}
export default AccessGate