Let guests keep a published rule via Save & Exit and a post-finalize sign-in prompt.
Email is optional so they can continue without saving; skip from Save & Exit leaves the flow instead of returning to completed. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -31,7 +31,10 @@ export function LoginView({
|
||||
<div
|
||||
ref={backdropRef}
|
||||
className={`fixed inset-0 z-[9998] flex flex-col items-center justify-center gap-6 overflow-y-auto px-4 py-8 ${backdropClasses[backdropVariant]}`}
|
||||
onClick={onClose}
|
||||
onPointerDown={(event) => {
|
||||
if (event.target !== event.currentTarget) return;
|
||||
onClose();
|
||||
}}
|
||||
role="presentation"
|
||||
>
|
||||
<div
|
||||
|
||||
@@ -44,17 +44,51 @@ function MailIconInline() {
|
||||
|
||||
const EMAIL_PATTERN = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||
|
||||
export type LoginFormVariant = "default" | "saveProgress";
|
||||
export type LoginFormVariant = "default" | "saveProgress" | "keepRule";
|
||||
|
||||
function loginFormHeading(
|
||||
variant: LoginFormVariant,
|
||||
sent: boolean,
|
||||
t: (_key: string) => string,
|
||||
): { title: string; description: string } {
|
||||
if (sent) {
|
||||
return { title: t("successTitle"), description: t("successBody") };
|
||||
}
|
||||
if (variant === "saveProgress") {
|
||||
return {
|
||||
title: t("saveProgressTitle"),
|
||||
description: t("saveProgressSubtitle"),
|
||||
};
|
||||
}
|
||||
if (variant === "keepRule") {
|
||||
return {
|
||||
title: t("keepRuleTitle"),
|
||||
description: t("keepRuleSubtitle"),
|
||||
};
|
||||
}
|
||||
return { title: t("title"), description: t("subtitle") };
|
||||
}
|
||||
|
||||
function shouldAttachCreateFlowDraft(
|
||||
variant: LoginFormVariant,
|
||||
nextPath: string,
|
||||
): boolean {
|
||||
if (variant === "keepRule") return false;
|
||||
return variant === "saveProgress" || nextPath.includes("syncDraft=1");
|
||||
}
|
||||
|
||||
export type LoginFormProps = {
|
||||
variant?: LoginFormVariant;
|
||||
/** Overrides URL `next` for `requestMagicLink` (e.g. create-flow exit modal). */
|
||||
magicLinkNextPath?: string;
|
||||
/** `keepRule`: Continue without saving. */
|
||||
onDismiss?: () => void;
|
||||
};
|
||||
|
||||
export default function LoginForm({
|
||||
variant = "default",
|
||||
magicLinkNextPath,
|
||||
onDismiss,
|
||||
}: LoginFormProps) {
|
||||
const t = useTranslation("pages.login");
|
||||
const tFooter = useTranslation("footer");
|
||||
@@ -72,7 +106,7 @@ export default function LoginForm({
|
||||
const nextParam = searchParams.get("next");
|
||||
const errorParam = searchParams.get("error");
|
||||
|
||||
const isSaveProgress = variant === "saveProgress";
|
||||
const heading = loginFormHeading(variant, sent, t);
|
||||
|
||||
/** Drop `error` from the URL so URL-driven messages don’t linger after a new attempt. */
|
||||
const stripErrorQuery = useCallback(() => {
|
||||
@@ -96,8 +130,7 @@ export default function LoginForm({
|
||||
try {
|
||||
const rawNext = magicLinkNextPath ?? nextParam;
|
||||
const nextPath = safeInternalPath(rawNext);
|
||||
const shouldAttachDraft =
|
||||
isSaveProgress || nextPath.includes("syncDraft=1");
|
||||
const shouldAttachDraft = shouldAttachCreateFlowDraft(variant, nextPath);
|
||||
const localDraft = readAnonymousCreateFlowState();
|
||||
const draft =
|
||||
shouldAttachDraft && Object.keys(localDraft).length > 0
|
||||
@@ -115,7 +148,7 @@ export default function LoginForm({
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (isSaveProgress || nextPath.includes("syncDraft=1")) {
|
||||
if (shouldAttachDraft) {
|
||||
setTransferPendingFlag();
|
||||
}
|
||||
setEmail(trimmed);
|
||||
@@ -127,11 +160,11 @@ export default function LoginForm({
|
||||
}
|
||||
}, [
|
||||
email,
|
||||
isSaveProgress,
|
||||
magicLinkNextPath,
|
||||
nextParam,
|
||||
stripErrorQuery,
|
||||
t,
|
||||
variant,
|
||||
]);
|
||||
|
||||
const urlErrorMessage =
|
||||
@@ -155,20 +188,8 @@ export default function LoginForm({
|
||||
</div>
|
||||
<ContentLockup
|
||||
titleId={titleId}
|
||||
title={
|
||||
sent
|
||||
? t("successTitle")
|
||||
: isSaveProgress
|
||||
? t("saveProgressTitle")
|
||||
: t("title")
|
||||
}
|
||||
description={
|
||||
sent
|
||||
? t("successBody")
|
||||
: isSaveProgress
|
||||
? t("saveProgressSubtitle")
|
||||
: t("subtitle")
|
||||
}
|
||||
title={heading.title}
|
||||
description={heading.description}
|
||||
variant="login"
|
||||
alignment="left"
|
||||
/>
|
||||
@@ -255,6 +276,19 @@ export default function LoginForm({
|
||||
>
|
||||
{t("sendMagicLink")}
|
||||
</Button>
|
||||
{onDismiss ? (
|
||||
<Button
|
||||
type="button"
|
||||
size="large"
|
||||
buttonType="ghost"
|
||||
palette="default"
|
||||
disabled={submitting}
|
||||
onClick={onDismiss}
|
||||
className="w-full !justify-center text-center px-[var(--spacing-scale-016)] py-[var(--spacing-scale-012)]"
|
||||
>
|
||||
{t("continueWithoutSaving")}
|
||||
</Button>
|
||||
) : null}
|
||||
<p className="text-center text-small-paragraph text-[var(--color-content-default-tertiary)]">
|
||||
{t("legalPrefix")}
|
||||
<Link
|
||||
|
||||
Reference in New Issue
Block a user