feat(auth): draw the sign-in screen in the product's design language

The screen was still the upstream template's: its font, its accent, its
logo, and a theme that tried to serve a light and a dark scheme from one
set of colours by deriving each one from the background's lightness.

That derivation is replaced with stated values, and the page is dark only.
Black, a neutral grey ramp, one brand accent, Mona Sans for the display
line and Geist for anything read or typed; two dashed bands closing into a
box on a wide screen, a dashed vertical either side of the column, the
wordmark, and the line of copy the rest of the product opens with.

The email field keeps the fix from the previous commit and takes the brand
colour on focus, which is the only place it appears besides the wordmark.

Measured against a render of the same design built from its own source:
the band rules, the column rules, the wordmark box and the button height
land on identical pixels.
This commit is contained in:
Wanjohi
2026-09-17 23:05:39 +03:00
parent 4ed36e0d38
commit 74391714d0
4 changed files with 564 additions and 314 deletions

View File

@@ -75,7 +75,20 @@ const DEFAULT_COPY = {
* guesses made. Deliberately one message for both: which of the two it was
* is a fact about somebody else's mailbox.
*/
rate_limited: 'Too many attempts. Wait a moment and start again.'
rate_limited: 'Too many attempts. Wait a moment and start again.',
/**
* The consent line under the action, split around its two links so the
* sentence stays one translatable run rather than being glued together
* from fragments in the markup.
*/
terms_before:
'By continuing, you acknowledge that you have read and understood, and agree to Nestri\u2019s ',
terms_label: 'Terms & Conditions',
terms_url: 'https://nestri.io/terms',
terms_between: ' and ',
privacy_label: 'Privacy Policy',
privacy_url: 'https://nestri.io/privacy',
terms_after: '.'
};
export type CodeUICopy = typeof DEFAULT_COPY;
@@ -130,7 +143,7 @@ export function CodeUI(props: CodeUIOptions): CodeProviderOptions {
<Layout>
<form data-component="form" method="post">
{error?.type === 'invalid_claim' && <FormAlert message={copy.email_invalid} />}
{error?.type === 'rate_limit' && <FormAlert message={copy.rate_limited} />}
{error?.type === 'rate_limit' && <FormAlert message={copy.rate_limited} />}
<input type="hidden" name="action" value="request" />
<input
data-component="input"
@@ -143,7 +156,17 @@ export function CodeUI(props: CodeUIOptions): CodeProviderOptions {
/>
<button data-component="button">{copy.button_continue}</button>
</form>
<p data-component="form-footer">{copy.code_info}</p>
<p data-component="form-footer">
{copy.terms_before}
<a href={copy.terms_url} rel="noopener noreferrer" target="_blank">
{copy.terms_label}
</a>
{copy.terms_between}
<a href={copy.privacy_url} rel="noopener noreferrer" target="_blank">
{copy.privacy_label}
</a>
{copy.terms_after}
</p>
</Layout>
);
return new Response(jsx.toString(), {
@@ -158,7 +181,7 @@ export function CodeUI(props: CodeUIOptions): CodeProviderOptions {
<Layout>
<form data-component="form" class="form" method="post">
{error?.type === 'invalid_code' && <FormAlert message={copy.code_invalid} />}
{error?.type === 'rate_limit' && <FormAlert message={copy.rate_limited} />}
{error?.type === 'rate_limit' && <FormAlert message={copy.rate_limited} />}
{state.type === 'code' && (
<FormAlert
message={(state.resend ? copy.code_resent : copy.code_sent) + state.claims.email}