Commit 0b08bcd6 authored by Kourser's avatar Kourser
Browse files

Le formulaire d'amorçage retrouve le milieu de l'écran

Il était collé à gauche, et la cause tenait à un seul attribut : « maxWidth »
posé sur le conteneur centreur lui-même. « .centre » est une boîte flex qui
centre ce qu'elle contient ; la contraindre à 460 pixels ne rétrécissait pas
son contenu mais elle, et une boîte de 460 pixels dans une page se pose à
gauche.

Le motif de la maison existait déjà, et Connexion comme SecondFacteur le
suivent : « .centre » pour centrer, « .panneau » à l'intérieur pour la largeur
— width: min(420px, 100%) — puis « .marque », « .accroche » et « .champ ». Cet
écran s'y range, et cesse d'inventer sa propre mise en page à coups de styles
en ligne.

Les libellés gagnent au passage leur « htmlFor », les champs leur
identifiant : cliquer un libellé donne le focus, ce qui n'est pas de
l'esthétique quand on saisit un mot de passe qu'on ne voit pas.

Contrôlé de l'œil, à deux largeurs
  Le compte d'essai a été mis un instant en parcours d'amorçage pour atteindre
  l'écran, puis remis en état. Centré en 800 pixels comme en 375, panneau
  identique à celui de la connexion. 938 garanties, aucune en échec.

Co-Authored-By: default avatarClaude Opus 5 <noreply@anthropic.com>
parent 490d70a0
Loading
Loading
Loading
Loading
Loading
+77 −57
Changes for apps/web/src/ecrans/Amorce.tsx: 77 added lines, 57 removed lines.
Original line number Diff line number Diff line
@@ -57,59 +57,65 @@ export function Amorce() {
    };

    return (
      <main className="centre" style={{ maxWidth: 460 }}>
        <h1 style={{ fontSize: 20 }}>Posez votre mot de passe</h1>
        <p className="discret">
          Celui qui vous a été remis à l'installation a traversé un terminal, et
          peut-être une épaule. Il ne sert qu'à cette première connexion.
      <div className="centre">
        <div className="panneau">
          <div className="marque">Posez votre mot de passe</div>
          <p className="accroche">
            Celui qui vous a été remis à l'installation a traversé un terminal,
            et peut-être une épaule. Il ne sert qu'à cette première connexion.
          </p>
          {erreur && <div className="erreur">{erreur}</div>}

          <form onSubmit={soumettre}>
          <label>
            Mot de passe actuel
            <input type="password" value={ancien} autoComplete="current-password"
            <div className="champ">
              <label htmlFor="ancien">Mot de passe actuel</label>
              <input id="ancien" type="password" value={ancien}
                autoComplete="current-password"
                onChange={(e) => definirAncien(e.target.value)} />
          </label>
          <p className="discret" style={{ fontSize: 12, marginTop: -6 }}>
            Laissez vide si vous êtes arrivé par un lien reçu par courriel : vous
            n'en avez alors jamais eu.
              <p className="aide">
                Laissez vide si vous êtes arrivé par un lien reçu par courriel :
                vous n'en avez alors jamais eu.
              </p>

          <label>
            Nouveau mot de passe
            <input type="password" value={nouveau} autoComplete="new-password" autoFocus
            </div>
            <div className="champ">
              <label htmlFor="nouveau">Nouveau mot de passe</label>
              <input id="nouveau" type="password" value={nouveau} required autoFocus
                autoComplete="new-password"
                onChange={(e) => definirNouveau(e.target.value)} />
          </label>
          <label>
            Répétez-le
            <input type="password" value={repete} autoComplete="new-password"
              <p className="aide">Douze caractères au moins.</p>
            </div>
            <div className="champ">
              <label htmlFor="repete">Répétez-le</label>
              <input id="repete" type="password" value={repete} required
                autoComplete="new-password"
                onChange={(e) => definirRepete(e.target.value)} />
          </label>

          {erreur && <p className="erreur">{erreur}</p>}

          <button className="accent" type="submit" disabled={envoi}>
            </div>
            <button className="accent" type="submit" disabled={envoi}
              style={{ width: "100%", justifyContent: "center" }}>
              {envoi ? "…" : "Enregistrer"}
            </button>
          </form>
      </main>
        </div>
      </div>
    );
  }

  // Puis le second facteur, qui est aussi ce qui déclenche le droit root.
  return (
    <main className="centre" style={{ maxWidth: 460 }}>
      <h1 style={{ fontSize: 20 }}>Activez votre second facteur</h1>
      <p className="discret">
    <div className="centre">
      <div className="panneau">
        <div className="marque">Activez votre second facteur</div>
        <p className="accroche">
          {amorce.destineARoot
          ? "Le droit d'administration vous sera accordé au bout de cette étape. "
            + "Il ne l'est pas avant : un administrateur sans second facteur ne "
            + "pourrait pas entrer dans la console, et croirait disposer d'un "
            + "droit inexerçable."
            ? "Le droit d'administration vous sera accordé au bout de cette "
              + "étape. Il ne l'est pas avant : un administrateur sans second "
              + "facteur ne pourrait pas entrer dans la console, et croirait "
              + "disposer d'un droit inexerçable."
            : "Cette instance exige un second facteur pour ce compte."}
        </p>
        <ActiverSecondFacteur />
    </main>
      </div>
    </div>
  );
}

@@ -157,41 +163,55 @@ function ActiverSecondFacteur() {
  // qu'après les avoir vus, et le bouton le dit.
  if (codes) {
    return (
      <div>
        <p>
          Second facteur activé. Voici vos codes de secours — ils ne seront
          plus affichés. Imprimez-les et rangez-les hors du système
          d'information.
      <>
        <p className="accroche">
          Second facteur activé. Voici vos codes de secours — ils ne seront plus
          affichés. Imprimez-les et rangez-les hors du système d'information.
        </p>
        <pre>{codes.join("\n")}</pre>
        <button className="accent" onClick={() => rafraichir()}>
        <pre style={{ overflowWrap: "anywhere", whiteSpace: "pre-wrap" }}>
          {codes.join("\n")}
        </pre>
        <button className="accent" onClick={() => rafraichir()}
          style={{ width: "100%", justifyContent: "center" }}>
          Je les ai notés, continuer
        </button>
      </div>
      </>
    );
  }

  if (!secret) {
    return (
      <div>
        {erreur && <p className="erreur">{erreur}</p>}
        <button className="accent" onClick={() => void preparer()}>Commencer</button>
      </div>
      <>
        {erreur && <div className="erreur">{erreur}</div>}
        <button className="accent" onClick={() => void preparer()}
          style={{ width: "100%", justifyContent: "center" }}>
          Commencer
        </button>
      </>
    );
  }

  return (
    <div>
      {qr && <img src={qr} alt="Code à photographier avec votre application" />}
      <p className="discret">Ou saisissez ce secret à la main :</p>
      <pre>{secret}</pre>
      <label>
        Code à six chiffres
        <input value={code} inputMode="numeric" autoFocus
          onChange={(e) => definirCode(e.target.value)} />
      </label>
      {erreur && <p className="erreur">{erreur}</p>}
      <button className="accent" onClick={() => void activer()}>Activer</button>
    <>
      {qr && (
        <img src={qr} alt="Code à photographier avec votre application"
          style={{ display: "block", margin: "0 auto 12px" }} />
      )}
      <p className="aide">Ou saisissez ce secret à la main :</p>
      <pre style={{ overflowWrap: "anywhere", whiteSpace: "pre-wrap" }}>{secret}</pre>
      {erreur && <div className="erreur">{erreur}</div>}
      <div className="champ">
        <label htmlFor="totp">Code à six chiffres</label>
        <input id="totp" value={code} inputMode="numeric" autoFocus
          autoComplete="one-time-code"
          onChange={(e) => definirCode(e.target.value)}
          style={{ fontFamily: '"JetBrains Mono", monospace', letterSpacing: "0.08em" }} />
      </div>
      <button className="accent" onClick={() => void activer()}
        disabled={code.length < 6}
        style={{ width: "100%", justifyContent: "center" }}>
        Activer
      </button>
    </>
  );
}