Update and refine alert modals
This commit is contained in:
@@ -11,6 +11,7 @@ import { useCreateFlow } from "./context/CreateFlowContext";
|
||||
import { parseCreateFlowScreenFromPathname } from "./utils/flowSteps";
|
||||
import { saveDraftToServer } from "../../../lib/create/api";
|
||||
import messages from "../../../messages/en/index";
|
||||
import Alert from "../../components/modals/Alert";
|
||||
|
||||
const SYNC_ENABLED = process.env.NEXT_PUBLIC_ENABLE_BACKEND_SYNC === "true";
|
||||
|
||||
@@ -139,12 +140,27 @@ export function PostLoginDraftTransfer({
|
||||
|
||||
if (!transferError) return null;
|
||||
|
||||
const [titleLine, ...rest] = transferError.split(/\n\n+/);
|
||||
const title = (titleLine ?? transferError).trim();
|
||||
const description = rest.join("\n\n").trim() || undefined;
|
||||
|
||||
return (
|
||||
<div
|
||||
role="alert"
|
||||
className="mx-auto max-w-[640px] px-5 py-3 text-center font-inter text-sm text-[var(--color-border-default-utility-negative)]"
|
||||
>
|
||||
{transferError}
|
||||
<div className="pointer-events-none fixed inset-x-0 bottom-4 z-[150] flex justify-center px-5 md:bottom-6">
|
||||
<div className="pointer-events-auto w-full max-w-[640px]">
|
||||
<Alert
|
||||
type="banner"
|
||||
status="danger"
|
||||
size="s"
|
||||
title={title}
|
||||
description={description}
|
||||
hasBodyText={Boolean(description)}
|
||||
hasLeadingIcon
|
||||
onClose={() => {
|
||||
setTransferError(null);
|
||||
}}
|
||||
className="w-full"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
import { useCreateFlow } from "./context/CreateFlowContext";
|
||||
import { fetchDraftFromServer } from "../../../lib/create/api";
|
||||
import messages from "../../../messages/en/index";
|
||||
import Alert from "../../components/modals/Alert";
|
||||
import {
|
||||
isValidStep,
|
||||
parseCreateFlowScreenFromPathname,
|
||||
@@ -119,12 +120,19 @@ export function SignedInDraftHydration({
|
||||
if (!loadingHydration) return null;
|
||||
|
||||
return (
|
||||
<div
|
||||
role="status"
|
||||
aria-live="polite"
|
||||
className="w-full shrink-0 px-[var(--spacing-measures-spacing-500,20px)] py-[var(--spacing-measures-spacing-200,8px)] md:px-[var(--measures-spacing-1800,64px)] text-center font-inter text-sm text-[var(--color-text-default-secondary,#a3a3a3)]"
|
||||
>
|
||||
{messages.create.draftHydration.loadingSavedProgress}
|
||||
<div className="pointer-events-none fixed left-0 right-0 top-14 z-[170] flex justify-center px-[var(--spacing-measures-spacing-500,20px)] pt-2 md:top-16 md:px-[var(--measures-spacing-1800,64px)]">
|
||||
<div className="pointer-events-auto w-full max-w-[960px]">
|
||||
<Alert
|
||||
type="banner"
|
||||
status="default"
|
||||
size="s"
|
||||
title={messages.create.draftHydration.loadingSavedProgress}
|
||||
hasBodyText={false}
|
||||
hasLeadingIcon={false}
|
||||
hasTrailingIcon={false}
|
||||
className="w-full"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -86,19 +86,28 @@ export default function ReviewTemplatePage({ params }: PageProps) {
|
||||
|
||||
if (error || !template) {
|
||||
return (
|
||||
<CreateFlowStepShell variant="wideGrid" contentTopBelowMd="space-800">
|
||||
<>
|
||||
<div
|
||||
className={`flex shrink-0 flex-col gap-4 pb-8 ${CREATE_FLOW_MD_UP_COLUMN_MAX_CLASS}`}
|
||||
className="pointer-events-none fixed left-0 right-0 top-14 z-[120] flex justify-center px-5 pt-3 md:top-20 md:px-12"
|
||||
aria-live="polite"
|
||||
>
|
||||
<Alert
|
||||
type="banner"
|
||||
status="danger"
|
||||
title={t("errors.loadFailed")}
|
||||
description={error ?? t("errors.notFound")}
|
||||
className="w-full"
|
||||
/>
|
||||
<div className="pointer-events-auto w-full max-w-[960px]">
|
||||
<Alert
|
||||
type="banner"
|
||||
status="danger"
|
||||
title={t("errors.loadFailed")}
|
||||
description={error ?? t("errors.notFound")}
|
||||
className="w-full"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</CreateFlowStepShell>
|
||||
<CreateFlowStepShell variant="wideGrid" contentTopBelowMd="space-800">
|
||||
<div
|
||||
className={`min-h-[40vh] shrink-0 ${CREATE_FLOW_MD_UP_COLUMN_MAX_CLASS}`}
|
||||
aria-hidden
|
||||
/>
|
||||
</CreateFlowStepShell>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -62,6 +62,7 @@ export default function ProfilePageClient() {
|
||||
const [emailChangeModalError, setEmailChangeModalError] = useState<
|
||||
string | null
|
||||
>(null);
|
||||
const [emailChangeRequestSent, setEmailChangeRequestSent] = useState(false);
|
||||
const [profileSuccessMessage, setProfileSuccessMessage] = useState<
|
||||
string | null
|
||||
>(null);
|
||||
@@ -131,6 +132,7 @@ export default function ProfilePageClient() {
|
||||
setActionError(null);
|
||||
setProfileSuccessMessage(null);
|
||||
setEmailChangeModalError(null);
|
||||
setEmailChangeRequestSent(false);
|
||||
setEmailChangeInput(user.email);
|
||||
setEmailChangeOpen(true);
|
||||
}, [user]);
|
||||
@@ -138,8 +140,25 @@ export default function ProfilePageClient() {
|
||||
const handleCloseEmailChange = useCallback(() => {
|
||||
if (emailChangeBusy) return;
|
||||
setEmailChangeOpen(false);
|
||||
setEmailChangeRequestSent(false);
|
||||
}, [emailChangeBusy]);
|
||||
|
||||
const handleDismissProfileSuccess = useCallback(() => {
|
||||
setProfileSuccessMessage(null);
|
||||
}, []);
|
||||
|
||||
const handleDismissActionError = useCallback(() => {
|
||||
setActionError(null);
|
||||
}, []);
|
||||
|
||||
const handleDismissRulesError = useCallback(() => {
|
||||
setRulesError(false);
|
||||
}, []);
|
||||
|
||||
const handleDismissEmailChangeModalError = useCallback(() => {
|
||||
setEmailChangeModalError(null);
|
||||
}, []);
|
||||
|
||||
const handleSubmitEmailChange = useCallback(async () => {
|
||||
const trimmed = emailChangeInput.trim();
|
||||
if (!trimmed || emailChangeBusy) return;
|
||||
@@ -157,8 +176,7 @@ export default function ProfilePageClient() {
|
||||
setEmailChangeModalError(res.error);
|
||||
}
|
||||
} else {
|
||||
setEmailChangeOpen(false);
|
||||
setProfileSuccessMessage(t("emailChangeRequestSent"));
|
||||
setEmailChangeRequestSent(true);
|
||||
}
|
||||
}, [emailChangeBusy, emailChangeInput, t]);
|
||||
|
||||
@@ -318,7 +336,12 @@ export default function ProfilePageClient() {
|
||||
emailChangeValue={emailChangeInput}
|
||||
onEmailChangeValueChange={(value) => setEmailChangeInput(value)}
|
||||
emailChangeBusy={emailChangeBusy}
|
||||
emailChangeRequestSent={emailChangeRequestSent}
|
||||
emailChangeModalError={emailChangeModalError}
|
||||
onDismissProfileSuccess={handleDismissProfileSuccess}
|
||||
onDismissActionError={handleDismissActionError}
|
||||
onDismissRulesError={handleDismissRulesError}
|
||||
onDismissEmailChangeModalError={handleDismissEmailChangeModalError}
|
||||
onOpenEmailChange={handleOpenEmailChange}
|
||||
onCloseEmailChange={handleCloseEmailChange}
|
||||
onSubmitEmailChange={handleSubmitEmailChange}
|
||||
|
||||
@@ -6,7 +6,9 @@ import RuleCard from "../../../components/cards/RuleCard";
|
||||
import TextInput from "../../../components/controls/TextInput";
|
||||
import List from "../../../components/layout/List";
|
||||
import type { ListItem, ListSize } from "../../../components/layout/List";
|
||||
import Icon from "../../../components/asset/Icon";
|
||||
import Dialog from "../../../components/modals/Dialog";
|
||||
import Alert from "../../../components/modals/Alert";
|
||||
import HeaderLockup from "../../../components/type/HeaderLockup";
|
||||
import { useTranslation } from "../../../contexts/MessagesContext";
|
||||
import type { CreateFlowState } from "../../create/types";
|
||||
@@ -49,7 +51,9 @@ export type ProfilePageViewProps = {
|
||||
emailChangeValue: string;
|
||||
onEmailChangeValueChange: (value: string) => void;
|
||||
emailChangeBusy: boolean;
|
||||
emailChangeRequestSent: boolean;
|
||||
emailChangeModalError: string | null;
|
||||
onDismissEmailChangeModalError: () => void;
|
||||
onOpenEmailChange: () => void;
|
||||
onCloseEmailChange: () => void;
|
||||
onSubmitEmailChange: () => void;
|
||||
@@ -65,6 +69,9 @@ export type ProfilePageViewProps = {
|
||||
onOpenDeleteAccount: () => void;
|
||||
onCloseDeleteAccount: () => void;
|
||||
onConfirmDeleteAccount: () => void;
|
||||
onDismissProfileSuccess: () => void;
|
||||
onDismissActionError: () => void;
|
||||
onDismissRulesError: () => void;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -171,7 +178,9 @@ export function ProfilePageView({
|
||||
emailChangeValue,
|
||||
onEmailChangeValueChange,
|
||||
emailChangeBusy,
|
||||
emailChangeRequestSent,
|
||||
emailChangeModalError,
|
||||
onDismissEmailChangeModalError,
|
||||
onOpenEmailChange,
|
||||
onCloseEmailChange,
|
||||
onSubmitEmailChange,
|
||||
@@ -187,8 +196,12 @@ export function ProfilePageView({
|
||||
onOpenDeleteAccount,
|
||||
onCloseDeleteAccount,
|
||||
onConfirmDeleteAccount,
|
||||
onDismissProfileSuccess,
|
||||
onDismissActionError,
|
||||
onDismissRulesError,
|
||||
}: ProfilePageViewProps) {
|
||||
const t = useTranslation("pages.profile");
|
||||
const tLogin = useTranslation("pages.login");
|
||||
const titleId = useId();
|
||||
const welcomeTitle = t("welcomeTitle").replace(/\{\{name\}\}/g, userEmail);
|
||||
const welcomeBody =
|
||||
@@ -277,30 +290,6 @@ export function ProfilePageView({
|
||||
)}
|
||||
</header>
|
||||
|
||||
{profileSuccessMessage ? (
|
||||
<p
|
||||
className="rounded-lg border border-[var(--color-border-default-secondary)] bg-[var(--color-surface-default-secondary)] px-4 py-3 font-inter text-sm text-[var(--color-content-default-primary)]"
|
||||
role="status"
|
||||
>
|
||||
{profileSuccessMessage}
|
||||
</p>
|
||||
) : null}
|
||||
|
||||
{actionError ? (
|
||||
<p
|
||||
className="rounded-lg border border-[var(--color-border-default-secondary)] bg-[var(--color-surface-default-tertiary)] px-4 py-3 font-inter text-sm text-[var(--color-content-default-primary)]"
|
||||
role="alert"
|
||||
>
|
||||
{actionError}
|
||||
</p>
|
||||
) : null}
|
||||
|
||||
{rulesError ? (
|
||||
<p className="font-inter text-sm text-[var(--color-content-default-tertiary)]">
|
||||
{t("actionError")}
|
||||
</p>
|
||||
) : null}
|
||||
|
||||
<div className="flex flex-col gap-8 lg:flex-row lg:flex-nowrap lg:items-start lg:gap-8">
|
||||
<section
|
||||
className="flex min-w-0 w-full flex-col gap-3 lg:min-w-0 lg:flex-1 lg:gap-6"
|
||||
@@ -519,53 +508,136 @@ export function ProfilePageView({
|
||||
if (!emailChangeBusy) onCloseEmailChange();
|
||||
}}
|
||||
backdropVariant="blurredYellow"
|
||||
title={t("emailChangeModalTitle")}
|
||||
description={t("emailChangeModalDescription")}
|
||||
title={
|
||||
emailChangeRequestSent
|
||||
? tLogin("successTitle")
|
||||
: t("emailChangeModalTitle")
|
||||
}
|
||||
description={
|
||||
emailChangeRequestSent
|
||||
? tLogin("successBody")
|
||||
: t("emailChangeModalDescription")
|
||||
}
|
||||
footer={
|
||||
<>
|
||||
emailChangeRequestSent ? (
|
||||
<Button
|
||||
type="button"
|
||||
size="medium"
|
||||
buttonType="outline"
|
||||
palette="default"
|
||||
onClick={onCloseEmailChange}
|
||||
disabled={emailChangeBusy}
|
||||
>
|
||||
{t("emailChangeCancel")}
|
||||
{t("emailChangeConfirmationClose")}
|
||||
</Button>
|
||||
<Button
|
||||
type="button"
|
||||
size="medium"
|
||||
buttonType="filled"
|
||||
palette="default"
|
||||
onClick={onSubmitEmailChange}
|
||||
disabled={emailChangeBusy}
|
||||
>
|
||||
{t("emailChangeSubmit")}
|
||||
</Button>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Button
|
||||
type="button"
|
||||
size="medium"
|
||||
buttonType="outline"
|
||||
palette="default"
|
||||
onClick={onCloseEmailChange}
|
||||
disabled={emailChangeBusy}
|
||||
>
|
||||
{t("emailChangeCancel")}
|
||||
</Button>
|
||||
<Button
|
||||
type="button"
|
||||
size="medium"
|
||||
buttonType="filled"
|
||||
palette="default"
|
||||
onClick={onSubmitEmailChange}
|
||||
disabled={emailChangeBusy}
|
||||
>
|
||||
{t("emailChangeSubmit")}
|
||||
</Button>
|
||||
</>
|
||||
)
|
||||
}
|
||||
>
|
||||
{emailChangeModalError ? (
|
||||
<p
|
||||
className="font-inter text-sm text-[var(--color-content-default-primary)]"
|
||||
role="alert"
|
||||
>
|
||||
{emailChangeModalError}
|
||||
</p>
|
||||
) : null}
|
||||
<TextInput
|
||||
type="email"
|
||||
inputSize="medium"
|
||||
label={t("emailChangeNewEmailLabel")}
|
||||
placeholder={t("emailChangeNewEmailPlaceholder")}
|
||||
value={emailChangeValue}
|
||||
onChange={(e) => onEmailChangeValueChange(e.target.value)}
|
||||
disabled={emailChangeBusy}
|
||||
error={Boolean(emailChangeModalError)}
|
||||
autoComplete="email"
|
||||
/>
|
||||
{emailChangeRequestSent ? (
|
||||
<div className="flex flex-col gap-3 pt-2">
|
||||
<div className="relative flex h-12 w-12 shrink-0 items-center justify-center rounded-full bg-[var(--color-surface-inverse-brand-primary)]">
|
||||
<Icon name="mail" size={22} aria-hidden />
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<TextInput
|
||||
type="email"
|
||||
inputSize="medium"
|
||||
label={t("emailChangeNewEmailLabel")}
|
||||
placeholder={t("emailChangeNewEmailPlaceholder")}
|
||||
value={emailChangeValue}
|
||||
onChange={(e) => onEmailChangeValueChange(e.target.value)}
|
||||
disabled={emailChangeBusy}
|
||||
error={Boolean(emailChangeModalError)}
|
||||
autoComplete="email"
|
||||
/>
|
||||
)}
|
||||
</Dialog>
|
||||
|
||||
{(profileSuccessMessage || actionError || rulesError) && (
|
||||
<div
|
||||
className="pointer-events-none fixed left-0 right-0 top-[41px] z-[60] flex justify-center px-4 pt-3 md:px-8 lg:top-[85px] lg:px-16 xl:top-[89px]"
|
||||
aria-live="polite"
|
||||
>
|
||||
<div className="pointer-events-auto flex w-full max-w-[640px] flex-col gap-2">
|
||||
{profileSuccessMessage ? (
|
||||
<Alert
|
||||
type="banner"
|
||||
status="positive"
|
||||
size="s"
|
||||
title={profileSuccessMessage}
|
||||
hasBodyText={false}
|
||||
hasLeadingIcon
|
||||
onClose={onDismissProfileSuccess}
|
||||
className="w-full"
|
||||
/>
|
||||
) : null}
|
||||
{actionError ? (
|
||||
<Alert
|
||||
type="banner"
|
||||
status="danger"
|
||||
size="s"
|
||||
title={actionError}
|
||||
hasBodyText={false}
|
||||
hasLeadingIcon
|
||||
onClose={onDismissActionError}
|
||||
className="w-full"
|
||||
/>
|
||||
) : null}
|
||||
{rulesError ? (
|
||||
<Alert
|
||||
type="banner"
|
||||
status="warning"
|
||||
size="s"
|
||||
title={t("rulesLoadBannerTitle")}
|
||||
description={t("rulesLoadBannerDescription")}
|
||||
hasLeadingIcon
|
||||
onClose={onDismissRulesError}
|
||||
className="w-full"
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{emailChangeOpen && emailChangeModalError ? (
|
||||
<div className="pointer-events-none fixed inset-x-0 top-6 z-[10001] flex justify-center px-4 md:top-10">
|
||||
<div className="pointer-events-auto w-full max-w-[min(480px,calc(100%-2rem))]">
|
||||
<Alert
|
||||
type="banner"
|
||||
status="danger"
|
||||
size="s"
|
||||
title={emailChangeModalError}
|
||||
hasBodyText={false}
|
||||
hasLeadingIcon
|
||||
onClose={onDismissEmailChangeModalError}
|
||||
className="w-full"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user