panel-redesign: Sign in and Create administrator on the kit

Stage 2, step 1. Written by Sonnet, reviewed by Opus.

  - templates/login.html, setup.html: written from the mockups as the
    content of layout_signed_out - form.sp-form, kicker, h1, Bulma fields
    with icons, the full-width primary button. Actions, field names,
    autocomplete and autofocus are the old ones.
  - view/signin.go: typed Login and Setup (Meta carries a title only, so
    Engine.Render takes the signed-out layout); auth handlers pass them
    instead of maps.
  - login and setup are in kitPages, have fixtures, and are off
    legacy_pages.txt.

States the mockups do not draw:
  - a refused sign-in / setup is the kit's flash (notification is-danger
    is-light) above the fields;
  - login before any administrator exists shows the old sentence in a
    form.sp-form that has no action and no control: the signed-out
    stylesheet sizes and centres its column through "form" alone. A
    workaround, listed for the owner.

Differences from the mockups, for the owner:
  - the kicker is <p><span class="sp-kicker"> (the kit's partial is a
    span; a page may not write the class), the mockup has <p
    class="sp-kicker">: the block sits about 3px lower;
  - login's kicker is the configured host name and is absent when there
    is none; setup's is the mockup's fixed text;
  - the footer is the layout's legal line (full copyright holder name);
  - the user name field has autofocus, so it shows its focus ring.

Evidence
  - docs/assets/panel-redesign/evidence/login.png, setup.png: 1440x900,
    headless Edge, from the real templates served locally with their
    fixtures - NOT from the container (no Docker on this machine).
  - panel/audit.js on login, login with an error, login without an
    administrator, setup, setup with an error, at 1440 and 1280:
    118 texts checked, problems: [] ; no page is taller than 900px.
  - go test -count=1 ./internal/web/...
      ok  internal/web
      ok  internal/web/auth
      ok  internal/web/handlers
      ok  internal/web/validate
      ok  internal/web/view

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
mix
2026-10-09 23:23:10 +03:00
co-authored by Claude Opus 5.5
parent 783dabbea5
commit ef8a82e4c3
14 changed files with 242 additions and 83 deletions
+30 -22
View File
@@ -1,24 +1,32 @@
{{/* Sign-in (mockup login.html): the form column of the signed-out screen. The
layout draws the brand half and the legal notice; this is only what stands
between them. Before the first administrator exists there is nothing to sign
in as, so the form gives way to a pointer at the setup link. */}}
{{define "content"}}
{{/* The full mark, on the two pages that carry no navigation: with no bar above
it, this is the only thing telling an administrator which service just asked
them for a password. */}}
<img class="mark" src="/static/logo.svg" width="330" height="150" alt="SelfPost">
<h1>Sign in</h1>
<div class="card narrow">
{{if .SetupHint}}
<p class="muted">No administrator has been created yet. Open the one-time
setup link printed in the container log to get started.</p>
{{else}}
{{if .Error}}<p class="error">{{.Error}}</p>{{end}}
<form method="post" action="/login">
<label for="username">Username</label>
<input id="username" name="username" autocomplete="username" autofocus required>
<label for="password">Password</label>
<input id="password" name="password" type="password" autocomplete="current-password" required>
<button type="submit">Sign in</button>
</form>
{{end}}
</div>
{{if .SetupHint}}
{{/* The signed-out layout sizes and centres its column through "form" alone, so
this state, which has no fields, keeps the element; it has no action and no
control and cannot be submitted. */}}
<form class="sp-form">
{{if .Host}}<p>{{template "kicker" .Host}}</p>{{end}}
<h1 class="title is-3">Sign in</h1>
<p class="sp-muted">No administrator has been created yet. Open the one-time
setup link printed in the container log to get started.</p>
</form>
{{else}}
<form class="sp-form" method="post" action="/login">
{{if .Host}}<p>{{template "kicker" .Host}}</p>{{end}}
<h1 class="title is-3">Sign in</h1>
{{if .Error}}{{template "flash" .Notice}}{{end}}
<div class="field">
<label class="label" for="username">Username</label>
<div class="control has-icons-left"><input class="input" id="username" name="username" autocomplete="username" autofocus required><span class="icon is-left"><i class="ti ti-user"></i></span></div>
</div>
<div class="field">
<label class="label" for="password">Password</label>
<div class="control has-icons-left"><input class="input" id="password" name="password" type="password" autocomplete="current-password" required><span class="icon is-left"><i class="ti ti-lock"></i></span></div>
</div>
<div class="field mt-5"><button type="submit" class="button is-primary is-fullwidth">Sign in</button></div>
</form>
{{end}}
{{end}}