Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 17 additions & 14 deletions apps/shell/src/auth/AuthWizard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,9 @@ import {
type AtomAccountType,
} from "./hostedAccount.js";
import {
chooserActionButtonLabel,
chooserActions,
chooserConflictCopy,
emailsEqualIgnoreCase,
mayClearLocalSignupState,
resolveChooserIdentity,
Expand Down Expand Up @@ -1950,15 +1952,20 @@ export function AuthWizard({ mode, onClose, embedded = false }: AuthWizardProps)
{chooserPrimaryEmail || "Your account"}
</h3>
{chooserConflictEmail ? (
<p className="atom-note">
Signed in as <strong>{chooserPrimaryEmail}</strong>. A different
unfinished signup for <strong>{chooserConflictEmail}</strong> will
be discarded if you continue.
</p>
<>
<p className="atom-note">
{chooserConflictCopy(chooserPrimaryEmail, chooserConflictEmail)}
</p>
{chooserFinishPayHint ? (
<p className="auth-slide-desc">
You’re signed in — finish payment to open Atom.
</p>
) : null}
</>
) : (
<p className="auth-slide-desc">
{chooserFinishPayHint
? "Payment isn’t finished for this account."
? "You’re signed in — finish payment to open Atom."
: "You’re already signed in on this device."}
</p>
)}
Expand All @@ -1976,14 +1983,10 @@ export function AuthWizard({ mode, onClose, embedded = false }: AuthWizardProps)
disabled={chooserBusy}
onClick={() => void handleChooserAction(action.id)}
>
{action.label}
{action.id === "complete_signup" && chooserPrimaryEmail
? ` for ${chooserPrimaryEmail}`
: action.id === "continue_session" && chooserPrimaryEmail
? ` as ${chooserPrimaryEmail}`
: action.id === "resume_pending" && chooserPrimaryEmail
? ` for ${chooserPrimaryEmail}`
: ""}
{chooserActionButtonLabel({
action,
primaryEmail: chooserPrimaryEmail,
})}
</button>
))}
</div>
Expand Down
29 changes: 27 additions & 2 deletions apps/shell/src/auth/accountChooser.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { describe, expect, it } from "vitest";
import {
chooserActionButtonLabel,
chooserActions,
chooserConflictCopy,
emailsEqualIgnoreCase,
mayClearLocalSignupState,
resolveChooserIdentity,
Expand Down Expand Up @@ -62,14 +64,15 @@ describe("resolveChooserIdentity", () => {
});

describe("chooserActions", () => {
it("unpaid register session offers Complete + Different", () => {
it("unpaid register session offers Continue as + Different (not Complete signup)", () => {
const actions = chooserActions({
mode: "register",
hasSession: true,
pendingKind: "register",
provisionable: false,
});
expect(actions.map((a) => a.id)).toEqual(["complete_signup", "different_account"]);
expect(actions[0]?.label).toBe("Continue as");
});

it("pending-only offers Resume + Start over, not Log in as", () => {
Expand All @@ -82,14 +85,36 @@ describe("chooserActions", () => {
expect(actions.map((a) => a.id)).toEqual(["resume_pending", "start_over"]);
});

it("unpaid login session offers Finish payment then Different", () => {
it("unpaid login session offers Continue as then Different", () => {
const actions = chooserActions({
mode: "login",
hasSession: true,
pendingKind: null,
provisionable: false,
});
expect(actions.map((a) => a.id)).toEqual(["complete_signup", "different_account"]);
expect(actions[0]?.label).toBe("Continue as");
});
});

describe("chooserConflictCopy", () => {
it("explains signed-in wins and draft is discarded", () => {
const copy = chooserConflictCopy("a@x.com", "b@y.com");
expect(copy).toContain("signed in as a@x.com");
expect(copy).toContain("b@y.com");
expect(copy).toContain("discards that draft");
expect(copy.toLowerCase()).not.toContain("complete signup");
});
});

describe("chooserActionButtonLabel", () => {
it("uses Continue as email for signed-in primary", () => {
expect(
chooserActionButtonLabel({
action: { id: "complete_signup", label: "Continue as", primary: true },
primaryEmail: "a@x.com",
}),
).toBe("Continue as a@x.com");
});
});
Comment on lines +110 to 119

Expand Down
67 changes: 38 additions & 29 deletions apps/shell/src/auth/accountChooser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,35 +76,19 @@ export function chooserActions(opts: {
const actions: ChooserAction[] = [];

if (opts.hasSession) {
if (opts.mode === "register") {
if (unpaid) {
actions.push({
id: "complete_signup",
label: "Complete signup",
primary: true,
});
} else {
actions.push({
id: "continue_session",
label: "Continue setup",
primary: true,
});
}
// Already signed in — never say "Complete signup for …" (reads as register again).
if (unpaid) {
actions.push({
id: "complete_signup",
label: "Continue as",
primary: true,
});
} else {
// login
if (unpaid) {
actions.push({
id: "complete_signup",
label: "Finish payment",
primary: true,
});
} else {
actions.push({
id: "continue_session",
label: "Continue",
primary: true,
});
}
actions.push({
id: "continue_session",
label: "Continue as",
primary: true,
});
}
actions.push({ id: "different_account", label: "Use a different account" });
return actions;
Expand All @@ -114,14 +98,39 @@ export function chooserActions(opts: {
if (opts.pendingKind === "register") {
actions.push({
id: "resume_pending",
label: "Resume signup",
label: "Resume signup for",
primary: true,
});
}
actions.push({ id: "start_over", label: "Start over", primary: actions.length === 0 });
return actions;
}

/** Body copy when signed-in email ≠ unfinished local draft. */
export function chooserConflictCopy(sessionEmail: string, draftEmail: string): string {
return (
`You’re signed in as ${sessionEmail}. This device also has an unfinished signup draft ` +
`for ${draftEmail}. Continuing uses the signed-in account and discards that draft.`
);
}

/** Button caption: session actions use the email after “Continue as”. */
export function chooserActionButtonLabel(opts: {
action: ChooserAction;
primaryEmail: string;
}): string {
const email = opts.primaryEmail.trim();
if (!email) return opts.action.label;
if (
opts.action.id === "complete_signup" ||
opts.action.id === "continue_session" ||
opts.action.id === "resume_pending"
) {
return `${opts.action.label} ${email}`;
}
return opts.action.label;
Comment on lines +122 to +131
}

/** Fail-closed: only clear local signup state after sign-out succeeded. */
export function mayClearLocalSignupState(opts: {
signOutSucceeded: boolean;
Expand Down
Loading