Skip to content

Commit 0fe8de5

Browse files
committed
refactor(webapp): tidy the magic-link confirmation page and login divider
- /login/magic is confirmation-only now (the email form lives on /login), so drop the dead form branch and its unused imports and render any flashed auth:error inline on the confirmation. - /login: only render the "or" divider when a social/SSO option is shown above it, so it never appears with nothing before it.
1 parent 7bac7f1 commit 0fe8de5

3 files changed

Lines changed: 60 additions & 143 deletions

File tree

.server-changes/login-sso-ui.md

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,6 @@ area: webapp
33
type: improvement
44
---
55

6-
Rework the login page and SSO sign-in screens. On `/login`, SSO is now a
7-
button (after Google) and email sign-in is an inline magic-link form under an
8-
"or" divider that posts to the `/login/magic` action, reusing its existing
9-
logic. `/login/magic` becomes confirmation-only and redirects to `/login`
10-
otherwise, so the inline form is the single source of truth and old links
11-
don't 404. `/login/sso` gets refreshed copy, an "Enterprise email address"
12-
placeholder, and an "Ask about SSO" link. The Documentation link is removed
13-
from the shared login layout. Both email fields validate inline with the
14-
standard styled form error instead of the browser's native tooltip. The
15-
"magic link sent" confirmation names the address the link was sent to, and its
16-
"Re-enter email" action returns to `/login`.
6+
Rework the login and SSO sign-in screens: inline magic-link email form on
7+
`/login`, SSO as a button, `/login/magic` is now confirmation-only, and
8+
refreshed `/login/sso` copy.

apps/webapp/app/routes/login._index/route.tsx

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -253,11 +253,13 @@ export default function LoginPage() {
253253
)}
254254
{!data.isVercelMarketplace && (
255255
<>
256-
<div className="flex w-full items-center gap-3 py-1">
257-
<div className="h-px flex-1 bg-charcoal-700" />
258-
<span className="text-xs uppercase text-text-dimmed">or</span>
259-
<div className="h-px flex-1 bg-charcoal-700" />
260-
</div>
256+
{(data.showGithubAuth || data.showGoogleAuth || data.showSsoAuth) && (
257+
<div className="flex w-full items-center gap-3 py-1">
258+
<div className="h-px flex-1 bg-charcoal-700" />
259+
<span className="text-xs uppercase text-text-dimmed">or</span>
260+
<div className="h-px flex-1 bg-charcoal-700" />
261+
</div>
262+
)}
261263
<div className="w-full">
262264
{/* Posts to the /login/magic action so all magic-link logic
263265
(rate limiting, SSO auto-discovery, send) stays in one place. */}

apps/webapp/app/routes/login.magic/route.tsx

Lines changed: 50 additions & 127 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
1-
import { ArrowLeftIcon, EnvelopeIcon } from "@heroicons/react/20/solid";
1+
import { ArrowLeftIcon } from "@heroicons/react/20/solid";
22
import { InboxArrowDownIcon } from "@heroicons/react/24/solid";
33
import {
44
redirect,
55
type ActionFunctionArgs,
66
type LoaderFunctionArgs,
77
type MetaFunction,
88
} from "@remix-run/node";
9-
import { Form, useNavigation } from "@remix-run/react";
9+
import { Form } from "@remix-run/react";
1010
import { typedjson, useTypedLoaderData } from "remix-typedjson";
1111
import { z } from "zod";
1212
import { LoginPageLayout } from "~/components/LoginPageLayout";
@@ -15,11 +15,7 @@ import { Fieldset } from "~/components/primitives/Fieldset";
1515
import { FormButtons } from "~/components/primitives/FormButtons";
1616
import { FormError } from "~/components/primitives/FormError";
1717
import { Header1 } from "~/components/primitives/Headers";
18-
import { Input } from "~/components/primitives/Input";
19-
import { InputGroup } from "~/components/primitives/InputGroup";
2018
import { Paragraph } from "~/components/primitives/Paragraph";
21-
import { Spinner } from "~/components/primitives/Spinner";
22-
import { TextLink } from "~/components/primitives/TextLink";
2319
import { authenticator } from "~/services/auth.server";
2420
import { commitSession, getUserSession } from "~/services/sessionStorage.server";
2521
import {
@@ -66,13 +62,15 @@ export async function loader({ request }: LoaderFunctionArgs) {
6662
const session = await getUserSession(request);
6763

6864
// The email form now lives inline on /login; this route is only the
69-
// "magic link sent" confirmation. Any visit without a pending magic link
70-
// forwards to /login — keeping the inlined form the single source of truth
71-
// and avoiding an orphaned duplicate page. This also catches the expired-
72-
// link callback in routes/magic.tsx (which redirects here on error): with
73-
// no pending link it lands on /login, where the error surfaces via the
74-
// shared auth:error handling. Checked before reading auth:error so a flashed
75-
// error isn't consumed here before /login can display it.
65+
// "magic link sent" confirmation. A visit without a pending magic link
66+
// forwards to /login — keeping the inlined form the single source of truth,
67+
// avoiding an orphaned duplicate page, and letting /login surface any flashed
68+
// auth:error (e.g. an invalid-email submit that never sent a link). The guard
69+
// runs before reading auth:error so that error isn't consumed here before
70+
// /login can show it. An expired/invalid link click (routes/magic.tsx) is
71+
// different: the email-link strategy only clears the magic-link key on a
72+
// successful verify, so the key is still set and the request lands here — the
73+
// confirmation renders the flashed error as magicLinkError below.
7674
const url = new URL(request.url);
7775
const sanitized = sanitizeRedirectPath(url.searchParams.get("redirectTo"));
7876
// The submitted address is carried on the success redirect so the
@@ -82,7 +80,7 @@ export async function loader({ request }: LoaderFunctionArgs) {
8280
if (!session.has("triggerdotdev:magiclink")) {
8381
// Throw (not return) so the redirect doesn't widen the loader's return
8482
// type — otherwise useTypedLoaderData sees TypedResponse<never> in the
85-
// union and the component can't read magicLinkSent/magicLinkError.
83+
// union and the component can't read magicLinkError/email.
8684
throw redirect(
8785
sanitized === "/" ? "/login" : `/login?redirectTo=${encodeURIComponent(sanitized)}`
8886
);
@@ -111,7 +109,6 @@ export async function loader({ request }: LoaderFunctionArgs) {
111109

112110
return typedjson(
113111
{
114-
magicLinkSent: session.has("triggerdotdev:magiclink"),
115112
magicLinkError,
116113
email,
117114
},
@@ -238,127 +235,53 @@ export async function action({ request }: ActionFunctionArgs) {
238235
}
239236

240237
export default function LoginMagicLinkPage() {
241-
const { magicLinkSent, magicLinkError, email } = useTypedLoaderData<typeof loader>();
242-
const navigate = useNavigation();
243-
244-
const isLoading =
245-
(navigate.state === "loading" || navigate.state === "submitting") &&
246-
navigate.formAction !== undefined &&
247-
navigate.formData?.get("action") === "send";
238+
const { magicLinkError, email } = useTypedLoaderData<typeof loader>();
248239

249240
return (
250241
<LoginPageLayout>
251242
<Form method="post">
252243
<div className="flex flex-col items-center justify-center">
253-
{magicLinkSent ? (
254-
<>
255-
<Header1 className="pb-6 text-center text-xl font-normal leading-7 md:text-xl lg:text-2xl">
256-
We've sent you a magic link!
257-
</Header1>
258-
<Fieldset className="flex w-full flex-col items-center gap-y-2">
259-
<InboxArrowDownIcon className="mb-4 h-12 w-12 text-indigo-500" />
260-
<Paragraph className="mb-6 text-center [text-wrap:balance]">
261-
{email ? (
262-
<>
263-
We emailed a magic link to <span className="text-text-bright">{email}</span>{" "}
264-
to log you in to your account.
265-
</>
266-
) : (
267-
"We emailed you a magic link to log you in to your account."
268-
)}
269-
</Paragraph>
270-
<FormButtons
271-
cancelButton={
272-
<Button
273-
type="submit"
274-
name="action"
275-
value="reset"
276-
variant="minimal/small"
277-
LeadingIcon={ArrowLeftIcon}
278-
leadingIconClassName="text-text-dimmed group-hover:text-text-bright transition"
279-
data-action="re-enter email"
280-
>
281-
Re-enter email
282-
</Button>
283-
}
284-
confirmButton={
285-
<LinkButton
286-
to="/login"
287-
variant="minimal/small"
288-
data-action="log in using another option"
289-
>
290-
Log in using another option
291-
</LinkButton>
292-
}
293-
/>
294-
</Fieldset>
295-
</>
296-
) : (
297-
<>
298-
<Header1 className="pb-4 font-semibold sm:text-2xl md:text-3xl lg:text-4xl">
299-
Welcome
300-
</Header1>
301-
<Paragraph variant="base" className="mb-6 text-center">
302-
Create an account or login using email
303-
</Paragraph>
304-
<Fieldset className="flex w-full flex-col items-center gap-y-2">
305-
<InputGroup>
306-
<Input
307-
type="email"
308-
name="email"
309-
spellCheck={false}
310-
placeholder="Email Address"
311-
variant="large"
312-
required
313-
autoFocus
314-
/>
315-
</InputGroup>
316-
244+
<Header1 className="pb-6 text-center text-xl font-normal leading-7 md:text-xl lg:text-2xl">
245+
We've sent you a magic link!
246+
</Header1>
247+
<Fieldset className="flex w-full flex-col items-center gap-y-2">
248+
<InboxArrowDownIcon className="mb-4 h-12 w-12 text-indigo-500" />
249+
<Paragraph className="mb-6 text-center [text-wrap:balance]">
250+
{email ? (
251+
<>
252+
We emailed a magic link to <span className="text-text-bright">{email}</span> to
253+
log you in to your account.
254+
</>
255+
) : (
256+
"We emailed you a magic link to log you in to your account."
257+
)}
258+
</Paragraph>
259+
{magicLinkError && <FormError>{magicLinkError}</FormError>}
260+
<FormButtons
261+
cancelButton={
317262
<Button
318-
name="action"
319-
value="send"
320263
type="submit"
321-
variant="primary/large"
322-
disabled={isLoading}
323-
fullWidth
324-
data-action="send a magic link"
264+
name="action"
265+
value="reset"
266+
variant="minimal/small"
267+
LeadingIcon={ArrowLeftIcon}
268+
leadingIconClassName="text-text-dimmed group-hover:text-text-bright transition"
269+
data-action="re-enter email"
325270
>
326-
{isLoading ? (
327-
<Spinner className="mr-2 size-5" color="white" />
328-
) : (
329-
<EnvelopeIcon className="mr-2 size-5 text-text-bright" />
330-
)}
331-
{isLoading ? (
332-
<span className="text-text-bright">Sending…</span>
333-
) : (
334-
<span className="text-text-bright">Send a magic link</span>
335-
)}
271+
Re-enter email
336272
</Button>
337-
{magicLinkError && <FormError>{magicLinkError}</FormError>}
338-
</Fieldset>
339-
<Paragraph variant="extra-small" className="mb-4 mt-6 text-center">
340-
By signing up you agree to our{" "}
341-
<TextLink href="https://trigger.dev/legal" target="_blank">
342-
terms
343-
</TextLink>
344-
{" "}and{" "}
345-
<TextLink href="https://trigger.dev/legal/privacy" target="_blank">
346-
privacy
347-
</TextLink>
348-
{" "}policy.
349-
</Paragraph>
350-
351-
<LinkButton
352-
to="/login"
353-
variant={"minimal/small"}
354-
LeadingIcon={ArrowLeftIcon}
355-
leadingIconClassName="text-text-dimmed group-hover:text-text-bright transition"
356-
data-action="all login options"
357-
>
358-
All login options
359-
</LinkButton>
360-
</>
361-
)}
273+
}
274+
confirmButton={
275+
<LinkButton
276+
to="/login"
277+
variant="minimal/small"
278+
data-action="log in using another option"
279+
>
280+
Log in using another option
281+
</LinkButton>
282+
}
283+
/>
284+
</Fieldset>
362285
</div>
363286
</Form>
364287
</LoginPageLayout>

0 commit comments

Comments
 (0)