fix(entree): connexion visible, porte qui reconnaît le compte, libellés

- Démo : le bouton « Se connecter » ouvre la connexion, plus l'inscription
- Porte de l'arbre : quand la personne est connectée, un bandeau dit quel
  compte est ouvert et qu'il faut taper une fois le mot de passe reçu avec
  le lien. Il reste affiché au retour d'une connexion faite depuis la porte.
- Landing : lien « Se connecter » dans la navigation et le pied de page
  (vers ?account=login, sans source, le rendu au build n'a pas de window)
- Libellés de la porte et de l'inscription, en français et en anglais

Vérifié : vitest 154/154, eslint frontend, vite build, landing (test et
build). La landing n'a pas de configuration eslint dans le dépôt.

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
This commit is contained in:
IliesAllaliandClaude Haiku 5.5 committed 2026-10-18 20:05:18 +02:00
1 parent f8d4b4113d
commit f1ecb3951c
8 files changed
+56 -15

No files matched your search

+2
View File
@@ -2751,6 +2751,8 @@ function App() {
treeNotFound={treeNotFound}
loading={tree.gateLoading}
errorMessage={tree.gateError}
connected={Boolean(auth.userAuth.token)}
connectedName={accountUser?.firstName || ''}
onSubmit={handleGateUnlock}
onUseDemo={handleUseDemo}
onUseAccount={handleUseAccount}
+24 -7
View File
@@ -23,6 +23,8 @@ const AccessGate = ({
treeNotFound = false,
loading,
errorMessage,
connected = false,
connectedName = '',
onSubmit,
onUseAccount,
}) => {
@@ -86,6 +88,21 @@ const AccessGate = ({
{!treeNotFound && (
<div className="pz-auth-form">
{connected && (
<div className="access-gate-session" role="status">
{connectedName ? (
/^[aeiouyàâäéèêëîïôöùûüh]/i.test(connectedName)
? t("Vous êtes sur le compte d'{name}", { name: connectedName })
: t('Vous êtes sur le compte de {name}', { name: connectedName })
) : (
t('Vous êtes sur votre compte.')
)}
<span className="access-gate-session-step">
{t("Tapez une fois le mot de passe reçu avec le lien. Ensuite, l'arbre s'ouvrira directement.")}
</span>
</div>
)}
{!initialTreeId && (
<div className="pz-field">
<label htmlFor="treeId">{t("Code de l'arbre")}</label>
@@ -103,7 +120,7 @@ const AccessGate = ({
)}
<div className="pz-field">
<label htmlFor="visitorFirstName">{t('Votre prénom')}</label>
<label htmlFor="visitorFirstName">{t('Votre prénom (facultatif)')}</label>
<input
id="visitorFirstName"
type="text"
@@ -113,11 +130,11 @@ const AccessGate = ({
autoComplete="given-name"
maxLength={60}
/>
<p className="pz-hint">{t("Facultatif. Il sert à savoir qui est venu voir l'arbre.")}</p>
<p className="pz-hint">{t("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>
<label htmlFor="sharePassword">{t('Mot de passe reçu avec le lien')}</label>
<input
id="sharePassword"
type="password"
@@ -135,13 +152,13 @@ const AccessGate = ({
{!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>
<PzBusy busy={loading} busyLabel={t("Ouverture de l'arbre")}>{t("Voir 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')}
{t('Créer mon propre arbre')}
</button>
)}
</form>
@@ -149,10 +166,10 @@ const AccessGate = ({
{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')}
{t("J'ai déjà 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')}
{t("Je n'ai pas de compte")}
</button>
</div>
)}
+3 -3
View File
@@ -203,19 +203,19 @@ const AccountDashboard = ({
disabled={loading}
>
<KeyRound size={16} aria-hidden="true" />
{t('Ouvrir un arbre partagé avec moi')}
{t("J'ai reçu un lien de famille")}
</button>
</div>
<p className="pz-auth-switch pz-small">
{isRegister ? t('Vous avez déjà un compte ?') : t('Pas encore de compte ?')}{' '}
{isRegister ? t('Déjà inscrit ?') : t('Pas encore de compte ?')}{' '}
<button
type="button"
className="pz-link account-signup-toggle"
onClick={() => setMode(isRegister ? 'login' : 'register')}
disabled={loading}
>
{isRegister ? t('Me connecter') : t('Créer un compte')}
{isRegister ? t('Se connecter') : t('Créer un compte')}
</button>
</p>
@@ -41,6 +41,10 @@
.pz-gate-owner-text { display: grid; gap: 2px; min-width: 0; }
.pz-gate-owner-text strong { color: var(--pz-ink); font-weight: 500; }
/* Porte, personne déjà connectée : dit quel compte est ouvert et ce qu'il faut faire une fois */
.access-gate-session { display: grid; gap: 4px; padding: 12px 14px; border-radius: var(--pz-r-m); background: var(--pz-a50); border: 1px solid var(--pz-a100); color: var(--pz-ink); font: 400 14px/1.45 var(--pz-sans); }
.access-gate-session-step { color: var(--pz-ink-2); }
/* Fenêtre « Mon compte » (personne liée) */
.ap-linked { display: flex; gap: 12px; align-items: center; padding: 12px 14px; border-radius: var(--pz-r-m); background: var(--pz-a50); border: 1px solid var(--pz-a100); }
.ap-avatar { width: 44px; height: 44px; flex: none; display: grid; place-items: center; overflow: hidden; border-radius: 50%; background: var(--pz-a100); color: var(--pz-a700); font: 400 20px/1 var(--pz-serif); }
@@ -307,7 +307,7 @@ function ContextualNavbar({
<NavButton
icon={<User size={20} strokeWidth={2} />}
label={authenticated ? t('Mon compte') : t('Se connecter')}
onClick={() => onProfileClick?.(authenticated ? 'login' : 'register')}
onClick={() => onProfileClick?.('login')}
/>
</>
)
+12
View File
@@ -205,4 +205,16 @@ export default {
"Visiteur": "Visitor",
"Exemple": "Example",
"Arbre exemple": "Sample tree",
"Votre prénom (facultatif)": "Your first name (optional)",
"Il sert à savoir qui est venu voir l'arbre.": "It shows who came to see the tree.",
"Mot de passe reçu avec le lien": "Password received with the link",
"Créer mon propre arbre": "Create my own tree",
"J'ai déjà un compte": "I already have an account",
"Je n'ai pas de compte": "I don't have an account",
"J'ai reçu un lien de famille": "I received a family link",
"Déjà inscrit ?": "Already signed up?",
"Vous êtes sur le compte d'{name}": "You are signed in as {name}",
"Vous êtes sur le compte de {name}": "You are signed in as {name}",
"Vous êtes sur votre compte.": "You are signed in to your account.",
"Tapez une fois le mot de passe reçu avec le lien. Ensuite, l'arbre s'ouvrira directement.": "Enter the password you received with the link once. After that, the tree opens directly.",
}
+6 -2
View File
@@ -769,7 +769,7 @@ function HeroGraph({ className = '', label }) {
// ─── Header ───────────────────────────────────────────────────────────────────
function Header({ t, onCta, elevated }) {
function Header({ t, onCta, loginHref, elevated }) {
return (
<header className={`site-nav${elevated ? ' is-elevated' : ''}`}>
<div className="wrap nav-in">
@@ -778,6 +778,7 @@ function Header({ t, onCta, elevated }) {
<a href="#comment">{t.nav.how}</a>
<a href="#demo">{t.nav.demo}</a>
{t.nav.privacy && <a href={t.privacyHref}>{t.nav.privacy}</a>}
<a href={loginHref}>{t.nav.login}</a>
</nav>
<button type="button" onClick={onCta} className="btn p sm">{t.cta}</button>
</div>
@@ -910,6 +911,8 @@ const appSource = () => {
// Les CTA mènent à la création de compte dans l'app
const appSignupUrl = () => `https://app.parenthese.io/?account=register&source=${appSource()}`
// Un compte déjà créé : la connexion, pas l'inscription (sans source : rendu au build, pas de window)
const appLoginUrl = () => 'https://app.parenthese.io/?account=login'
export default function App({ locale = 'fr' }) {
const t = COPY[locale]
@@ -985,7 +988,7 @@ export default function App({ locale = 'fr' }) {
return (
<div className="min-h-screen">
<Header t={t} onCta={() => goToSignup('header')} elevated={headerElevated} />
<Header t={t} onCta={() => goToSignup('header')} loginHref={withAppQuery(appLoginUrl(), locale)} elevated={headerElevated} />
<main>
{/* ─── HERO ────────────────────────────────────────────────────── */}
@@ -1122,6 +1125,7 @@ export default function App({ locale = 'fr' }) {
<Logo height={18} />
<nav aria-label={t.footer.label}>
{t.footer.guides && <a href="#guides">{t.footer.guides}</a>}
<a href={withAppQuery(appLoginUrl(), locale)}>{t.footer.login}</a>
<a href={t.privacyHref}>{t.footer.privacy}</a>
<a href="https://github.com/IliesAllali/parenthese" rel="noopener noreferrer">{t.footer.source}</a>
<a href="https://iliesallali.design/">{t.footer.madeBy}</a>
+4 -2
View File
@@ -29,7 +29,7 @@ export const COPY = {
appQuery: '',
otherLang: { href: '/en/', hrefLang: 'en', label: 'EN', title: 'English version' },
logoHome: 'Parenthèse, accueil',
nav: { how: 'Comment ça marche', demo: 'Un exemple', privacy: 'Vie privée' },
nav: { how: 'Comment ça marche', demo: 'Un exemple', privacy: 'Vie privée', login: 'Se connecter' },
privacyHref: '/donnees-et-vie-privee/',
cta: 'Créer mon arbre',
heroCanvas: 'Arbre de famille animé',
@@ -116,6 +116,7 @@ export const COPY = {
footer: {
label: 'Pied de page',
guides: 'Guides',
login: 'Se connecter',
privacy: 'Vie privée',
source: 'Code source',
madeBy: 'Fait par Ilies Allali',
@@ -138,7 +139,7 @@ export const COPY = {
appQuery: 'lang=en',
otherLang: { href: '/', hrefLang: 'fr', label: 'FR', title: 'Version française' },
logoHome: 'Parenthèse, home',
nav: { how: 'How it works', demo: 'An example', privacy: 'Privacy' },
nav: { how: 'How it works', demo: 'An example', privacy: 'Privacy', login: 'Sign in' },
privacyHref: '/en/privacy/',
cta: 'Start your tree',
heroCanvas: 'Animated family tree',
@@ -225,6 +226,7 @@ export const COPY = {
footer: {
label: 'Footer',
guides: 'Guides',
login: 'Sign in',
privacy: 'Privacy',
source: 'Source code',
madeBy: 'Made by Ilies Allali',