@open-mercato/onboarding 0.5.1-develop.2856.35de414092 → 0.5.1-develop.2874.77704bccbd
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/modules/onboarding/frontend/onboarding/OnboardingPageClient.js +9 -9
- package/dist/modules/onboarding/frontend/onboarding/OnboardingPageClient.js.map +2 -2
- package/dist/modules/onboarding/frontend/onboarding/preparing/PreparingPageClient.js +1 -1
- package/dist/modules/onboarding/frontend/onboarding/preparing/PreparingPageClient.js.map +1 -1
- package/package.json +3 -3
- package/src/modules/onboarding/frontend/onboarding/OnboardingPageClient.tsx +9 -9
- package/src/modules/onboarding/frontend/onboarding/preparing/PreparingPageClient.tsx +1 -1
|
@@ -128,8 +128,8 @@ function OnboardingPageClient({ onboardingEnabled }) {
|
|
|
128
128
|
const onboardingDisabled = !onboardingEnabled;
|
|
129
129
|
const submitting = state === "loading";
|
|
130
130
|
const disabled = onboardingDisabled || submitting || state === "success";
|
|
131
|
-
return /* @__PURE__ */ jsx("div", { className: "relative flex min-h-svh items-center justify-center bg-muted/
|
|
132
|
-
onboardingDisabled ? /* @__PURE__ */ jsx("div", { className: "absolute inset-0 z-10 flex items-center justify-center rounded-lg bg-background/
|
|
131
|
+
return /* @__PURE__ */ jsx("div", { className: "relative flex min-h-svh items-center justify-center bg-muted/50 px-4 pb-24", children: /* @__PURE__ */ jsxs(Card, { className: "relative w-full max-w-lg overflow-hidden shadow-lg", children: [
|
|
132
|
+
onboardingDisabled ? /* @__PURE__ */ jsx("div", { className: "absolute inset-0 z-10 flex items-center justify-center rounded-lg bg-background/80 p-6 backdrop-blur-[2px]", children: /* @__PURE__ */ jsxs(
|
|
133
133
|
"div",
|
|
134
134
|
{
|
|
135
135
|
className: "max-w-sm rounded-xl border border-border/70 bg-background/95 px-5 py-4 text-center shadow-sm",
|
|
@@ -170,7 +170,7 @@ function OnboardingPageClient({ onboardingEnabled }) {
|
|
|
170
170
|
autoComplete: "email",
|
|
171
171
|
"aria-invalid": Boolean(fieldErrors.email),
|
|
172
172
|
"aria-describedby": fieldErrors.email ? "email-error" : void 0,
|
|
173
|
-
className: fieldErrors.email ? "border-red-500
|
|
173
|
+
className: fieldErrors.email ? "border-red-500 aria-invalid:ring-destructive" : void 0
|
|
174
174
|
}
|
|
175
175
|
),
|
|
176
176
|
fieldErrors.email && /* @__PURE__ */ jsx("p", { id: "email-error", className: "text-xs text-red-600", children: fieldErrors.email })
|
|
@@ -189,7 +189,7 @@ function OnboardingPageClient({ onboardingEnabled }) {
|
|
|
189
189
|
autoComplete: "given-name",
|
|
190
190
|
"aria-invalid": Boolean(fieldErrors.firstName),
|
|
191
191
|
"aria-describedby": fieldErrors.firstName ? "firstName-error" : void 0,
|
|
192
|
-
className: fieldErrors.firstName ? "border-red-500
|
|
192
|
+
className: fieldErrors.firstName ? "border-red-500 aria-invalid:ring-destructive" : void 0
|
|
193
193
|
}
|
|
194
194
|
),
|
|
195
195
|
fieldErrors.firstName && /* @__PURE__ */ jsx("p", { id: "firstName-error", className: "text-xs text-red-600", children: fieldErrors.firstName })
|
|
@@ -207,7 +207,7 @@ function OnboardingPageClient({ onboardingEnabled }) {
|
|
|
207
207
|
autoComplete: "family-name",
|
|
208
208
|
"aria-invalid": Boolean(fieldErrors.lastName),
|
|
209
209
|
"aria-describedby": fieldErrors.lastName ? "lastName-error" : void 0,
|
|
210
|
-
className: fieldErrors.lastName ? "border-red-500
|
|
210
|
+
className: fieldErrors.lastName ? "border-red-500 aria-invalid:ring-destructive" : void 0
|
|
211
211
|
}
|
|
212
212
|
),
|
|
213
213
|
fieldErrors.lastName && /* @__PURE__ */ jsx("p", { id: "lastName-error", className: "text-xs text-red-600", children: fieldErrors.lastName })
|
|
@@ -226,7 +226,7 @@ function OnboardingPageClient({ onboardingEnabled }) {
|
|
|
226
226
|
autoComplete: "organization",
|
|
227
227
|
"aria-invalid": Boolean(fieldErrors.organizationName),
|
|
228
228
|
"aria-describedby": fieldErrors.organizationName ? "organizationName-error" : void 0,
|
|
229
|
-
className: fieldErrors.organizationName ? "border-red-500
|
|
229
|
+
className: fieldErrors.organizationName ? "border-red-500 aria-invalid:ring-destructive" : void 0
|
|
230
230
|
}
|
|
231
231
|
),
|
|
232
232
|
fieldErrors.organizationName && /* @__PURE__ */ jsx("p", { id: "organizationName-error", className: "text-xs text-red-600", children: fieldErrors.organizationName })
|
|
@@ -245,7 +245,7 @@ function OnboardingPageClient({ onboardingEnabled }) {
|
|
|
245
245
|
minLength: passwordPolicy.minLength,
|
|
246
246
|
"aria-invalid": Boolean(fieldErrors.password),
|
|
247
247
|
"aria-describedby": fieldErrors.password ? "password-error" : void 0,
|
|
248
|
-
className: fieldErrors.password ? "border-red-500
|
|
248
|
+
className: fieldErrors.password ? "border-red-500 aria-invalid:ring-destructive" : void 0
|
|
249
249
|
}
|
|
250
250
|
),
|
|
251
251
|
passwordDescription ? /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: passwordDescription }) : null,
|
|
@@ -264,7 +264,7 @@ function OnboardingPageClient({ onboardingEnabled }) {
|
|
|
264
264
|
autoComplete: "new-password",
|
|
265
265
|
"aria-invalid": Boolean(fieldErrors.confirmPassword),
|
|
266
266
|
"aria-describedby": fieldErrors.confirmPassword ? "confirmPassword-error" : void 0,
|
|
267
|
-
className: fieldErrors.confirmPassword ? "border-red-500
|
|
267
|
+
className: fieldErrors.confirmPassword ? "border-red-500 aria-invalid:ring-destructive" : void 0
|
|
268
268
|
}
|
|
269
269
|
),
|
|
270
270
|
fieldErrors.confirmPassword && /* @__PURE__ */ jsx("p", { id: "confirmPassword-error", className: "text-xs text-red-600", children: fieldErrors.confirmPassword })
|
|
@@ -322,7 +322,7 @@ function OnboardingPageClient({ onboardingEnabled }) {
|
|
|
322
322
|
{
|
|
323
323
|
type: "submit",
|
|
324
324
|
disabled,
|
|
325
|
-
className: "mt-2 h-11 bg-foreground text-background transition hover:opacity-
|
|
325
|
+
className: "mt-2 h-11 bg-foreground text-background transition hover:opacity-80 disabled:cursor-not-allowed disabled:opacity-50",
|
|
326
326
|
children: submitting ? translate("onboarding.form.loading", "Sending...") : translate("onboarding.form.submit", "Send verification email")
|
|
327
327
|
}
|
|
328
328
|
)
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../src/modules/onboarding/frontend/onboarding/OnboardingPageClient.tsx"],
|
|
4
|
-
"sourcesContent": ["'use client'\n\nimport Image from 'next/image'\nimport Link from 'next/link'\nimport { useState } from 'react'\nimport { useLocale, useT } from '@open-mercato/shared/lib/i18n/context'\nimport { translateWithFallback } from '@open-mercato/shared/lib/i18n/translate'\nimport { formatPasswordRequirements, getPasswordPolicy } from '@open-mercato/shared/lib/auth/passwordPolicy'\nimport { onboardingStartSchema } from '@open-mercato/onboarding/modules/onboarding/data/validators'\nimport { apiCall } from '@open-mercato/ui/backend/utils/apiCall'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@open-mercato/ui/primitives/card'\nimport { Checkbox } from '@open-mercato/ui/primitives/checkbox'\nimport { Input } from '@open-mercato/ui/primitives/input'\nimport { Label } from '@open-mercato/ui/primitives/label'\n\ntype SubmissionState = 'idle' | 'loading' | 'success'\ntype FieldErrors = Partial<Record<\n 'email' | 'firstName' | 'lastName' | 'organizationName' | 'password' | 'confirmPassword' | 'termsAccepted' | 'marketingConsent',\n string\n>>\n\ntype Props = {\n onboardingEnabled: boolean\n}\n\nexport default function OnboardingPageClient({ onboardingEnabled }: Props) {\n const t = useT()\n const translate = (key: string, fallback: string, params?: Record<string, string | number>) =>\n translateWithFallback(t, key, fallback, params)\n const locale = useLocale()\n const [state, setState] = useState<SubmissionState>('idle')\n const [globalError, setGlobalError] = useState<string | null>(null)\n const [fieldErrors, setFieldErrors] = useState<FieldErrors>({})\n const [termsAccepted, setTermsAccepted] = useState(false)\n const [marketingConsent, setMarketingConsent] = useState(false)\n const [emailSubmitted, setEmailSubmitted] = useState<string | null>(null)\n const passwordPolicy = getPasswordPolicy()\n const passwordRequirements = formatPasswordRequirements(passwordPolicy, translate, 'onboarding.password.requirements')\n const passwordDescription = passwordRequirements\n ? translate(\n 'onboarding.password.requirements.help',\n 'Password requirements: {requirements}',\n { requirements: passwordRequirements },\n )\n : ''\n\n async function onSubmit(event: React.FormEvent<HTMLFormElement>) {\n event.preventDefault()\n if (!onboardingEnabled) return\n\n setState('loading')\n setGlobalError(null)\n setFieldErrors({})\n\n const form = new FormData(event.currentTarget)\n const payload = {\n email: String(form.get('email') ?? '').trim(),\n firstName: String(form.get('firstName') ?? '').trim(),\n lastName: String(form.get('lastName') ?? '').trim(),\n organizationName: String(form.get('organizationName') ?? '').trim(),\n password: String(form.get('password') ?? ''),\n confirmPassword: String(form.get('confirmPassword') ?? ''),\n termsAccepted,\n marketingConsent,\n locale,\n }\n\n const parsed = onboardingStartSchema.safeParse(payload)\n if (!parsed.success) {\n const issueMap: FieldErrors = {}\n parsed.error.issues.forEach((issue) => {\n const path = issue.path[0]\n if (!path) return\n switch (path) {\n case 'email':\n issueMap.email = translate('onboarding.errors.emailInvalid', 'Enter a valid work email.')\n break\n case 'firstName':\n issueMap.firstName = translate('onboarding.errors.firstNameRequired', 'First name is required.')\n break\n case 'lastName':\n issueMap.lastName = translate('onboarding.errors.lastNameRequired', 'Last name is required.')\n break\n case 'organizationName':\n issueMap.organizationName = translate('onboarding.errors.organizationNameRequired', 'Organization name is required.')\n break\n case 'password':\n issueMap.password = translate(\n 'onboarding.errors.passwordRequired',\n 'Password must meet the requirements: {requirements}.',\n { requirements: passwordRequirements },\n )\n break\n case 'confirmPassword':\n issueMap.confirmPassword = translate('onboarding.errors.passwordMismatch', 'Passwords must match.')\n break\n case 'termsAccepted':\n issueMap.termsAccepted = translate('onboarding.form.termsRequired', 'Please accept the terms to continue.')\n break\n default:\n break\n }\n })\n if (!issueMap.termsAccepted && !termsAccepted) {\n issueMap.termsAccepted = translate('onboarding.form.termsRequired', 'Please accept the terms to continue.')\n }\n setFieldErrors(issueMap)\n setState('idle')\n return\n }\n\n try {\n const call = await apiCall<{ ok?: boolean; error?: string; email?: string; fieldErrors?: Record<string, string> }>(\n '/api/onboarding/onboarding',\n {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ ...parsed.data, termsAccepted: true, marketingConsent }),\n },\n )\n const data = call.result ?? {}\n if (!call.ok || data.ok === false) {\n if (data.fieldErrors && typeof data.fieldErrors === 'object') {\n const mapped: FieldErrors = {}\n for (const key of Object.keys(data.fieldErrors)) {\n const value = data.fieldErrors[key]\n if (typeof value === 'string' && value.trim()) {\n mapped[key as keyof FieldErrors] = value\n }\n }\n setFieldErrors(mapped)\n }\n const message = typeof data.error === 'string' && data.error.trim()\n ? data.error\n : translate('onboarding.form.genericError', 'Something went wrong. Please try again.')\n setGlobalError(message)\n setState('idle')\n return\n }\n setEmailSubmitted(data.email ?? parsed.data.email)\n setState('success')\n } catch (err) {\n const message = err instanceof Error ? err.message : ''\n setGlobalError(message || translate('onboarding.form.genericError', 'Something went wrong. Please try again.'))\n setState('idle')\n }\n }\n\n const onboardingDisabled = !onboardingEnabled\n const submitting = state === 'loading'\n const disabled = onboardingDisabled || submitting || state === 'success'\n\n return (\n <div className=\"relative flex min-h-svh items-center justify-center bg-muted/40 px-4 pb-24\">\n <Card className=\"relative w-full max-w-lg overflow-hidden shadow-lg\">\n {onboardingDisabled ? (\n <div className=\"absolute inset-0 z-10 flex items-center justify-center rounded-lg bg-background/70 p-6 backdrop-blur-[2px]\">\n <div\n className=\"max-w-sm rounded-xl border border-border/70 bg-background/95 px-5 py-4 text-center shadow-sm\"\n role=\"alert\"\n aria-live=\"polite\"\n >\n <p className=\"text-base font-semibold text-foreground\">\n {translate('onboarding.disabled.title', 'Self-service onboarding is currently unavailable')}\n </p>\n <p className=\"mt-2 text-sm text-muted-foreground\">\n {translate(\n 'onboarding.disabled.description',\n 'Workspace creation is currently handled manually. Contact the Open Mercato team or your administrator to request access before continuing.',\n )}\n </p>\n <div className=\"mt-4 flex justify-center\">\n <Button asChild variant=\"outline\">\n <Link href=\"/login\">\n {translate('onboarding.disabled.cta', 'Go to login')}\n </Link>\n </Button>\n </div>\n </div>\n </div>\n ) : null}\n <CardHeader className=\"flex flex-col gap-4 p-10 text-center\">\n <div className=\"flex flex-col items-center gap-3\">\n <Image alt=\"Open Mercato\" src=\"/open-mercato.svg\" width={120} height={120} priority />\n <CardTitle className=\"text-2xl font-semibold\">\n {translate('onboarding.title', 'Create your Open Mercato workspace')}\n </CardTitle>\n <CardDescription>\n {translate('onboarding.subtitle', 'Tell us a bit about you and we will set everything up.')}\n </CardDescription>\n </div>\n </CardHeader>\n <CardContent className={onboardingDisabled ? 'pb-10 opacity-45' : 'pb-10'}>\n {state === 'success' && emailSubmitted && (\n <div className=\"mb-6 rounded-md border border-emerald-200 bg-emerald-50 px-4 py-3 text-sm text-emerald-900\" role=\"status\" aria-live=\"polite\">\n <strong className=\"block text-sm font-medium\">\n {translate('onboarding.form.successTitle', 'Check your inbox')}\n </strong>\n <p>\n {translate('onboarding.form.successBody', 'We sent a verification link to {email}. Confirm it within 24 hours to activate your workspace.', { email: emailSubmitted })}\n </p>\n </div>\n )}\n {state !== 'success' && globalError && (\n <div className=\"mb-4 rounded-md border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700\" role=\"alert\" aria-live=\"assertive\">\n {globalError}\n </div>\n )}\n <form className=\"grid gap-4\" onSubmit={onSubmit} noValidate>\n <div className=\"grid gap-1\">\n <Label htmlFor=\"email\">{translate('onboarding.form.email', 'Work email')}</Label>\n <Input\n id=\"email\"\n name=\"email\"\n type=\"email\"\n required\n disabled={disabled}\n autoComplete=\"email\"\n aria-invalid={Boolean(fieldErrors.email)}\n aria-describedby={fieldErrors.email ? 'email-error' : undefined}\n className={fieldErrors.email ? 'border-red-500 focus-visible:ring-red-500' : undefined}\n />\n {fieldErrors.email && (\n <p id=\"email-error\" className=\"text-xs text-red-600\">{fieldErrors.email}</p>\n )}\n </div>\n <div className=\"grid gap-1 sm:grid-cols-2 sm:gap-4\">\n <div className=\"grid gap-1\">\n <Label htmlFor=\"firstName\">{translate('onboarding.form.firstName', 'First name')}</Label>\n <Input\n id=\"firstName\"\n name=\"firstName\"\n type=\"text\"\n required\n disabled={disabled}\n autoComplete=\"given-name\"\n aria-invalid={Boolean(fieldErrors.firstName)}\n aria-describedby={fieldErrors.firstName ? 'firstName-error' : undefined}\n className={fieldErrors.firstName ? 'border-red-500 focus-visible:ring-red-500' : undefined}\n />\n {fieldErrors.firstName && (\n <p id=\"firstName-error\" className=\"text-xs text-red-600\">{fieldErrors.firstName}</p>\n )}\n </div>\n <div className=\"grid gap-1\">\n <Label htmlFor=\"lastName\">{translate('onboarding.form.lastName', 'Last name')}</Label>\n <Input\n id=\"lastName\"\n name=\"lastName\"\n type=\"text\"\n required\n disabled={disabled}\n autoComplete=\"family-name\"\n aria-invalid={Boolean(fieldErrors.lastName)}\n aria-describedby={fieldErrors.lastName ? 'lastName-error' : undefined}\n className={fieldErrors.lastName ? 'border-red-500 focus-visible:ring-red-500' : undefined}\n />\n {fieldErrors.lastName && (\n <p id=\"lastName-error\" className=\"text-xs text-red-600\">{fieldErrors.lastName}</p>\n )}\n </div>\n </div>\n <div className=\"grid gap-1\">\n <Label htmlFor=\"organizationName\">{translate('onboarding.form.organizationName', 'Organization name')}</Label>\n <Input\n id=\"organizationName\"\n name=\"organizationName\"\n type=\"text\"\n required\n disabled={disabled}\n autoComplete=\"organization\"\n aria-invalid={Boolean(fieldErrors.organizationName)}\n aria-describedby={fieldErrors.organizationName ? 'organizationName-error' : undefined}\n className={fieldErrors.organizationName ? 'border-red-500 focus-visible:ring-red-500' : undefined}\n />\n {fieldErrors.organizationName && (\n <p id=\"organizationName-error\" className=\"text-xs text-red-600\">{fieldErrors.organizationName}</p>\n )}\n </div>\n <div className=\"grid gap-1\">\n <Label htmlFor=\"password\">{translate('onboarding.form.password', 'Password')}</Label>\n <Input\n id=\"password\"\n name=\"password\"\n type=\"password\"\n required\n disabled={disabled}\n autoComplete=\"new-password\"\n minLength={passwordPolicy.minLength}\n aria-invalid={Boolean(fieldErrors.password)}\n aria-describedby={fieldErrors.password ? 'password-error' : undefined}\n className={fieldErrors.password ? 'border-red-500 focus-visible:ring-red-500' : undefined}\n />\n {passwordDescription ? (\n <p className=\"text-xs text-muted-foreground\">{passwordDescription}</p>\n ) : null}\n {fieldErrors.password && (\n <p id=\"password-error\" className=\"text-xs text-red-600\">{fieldErrors.password}</p>\n )}\n </div>\n <div className=\"grid gap-1\">\n <Label htmlFor=\"confirmPassword\">{translate('onboarding.form.confirmPassword', 'Confirm password')}</Label>\n <Input\n id=\"confirmPassword\"\n name=\"confirmPassword\"\n type=\"password\"\n required\n disabled={disabled}\n autoComplete=\"new-password\"\n aria-invalid={Boolean(fieldErrors.confirmPassword)}\n aria-describedby={fieldErrors.confirmPassword ? 'confirmPassword-error' : undefined}\n className={fieldErrors.confirmPassword ? 'border-red-500 focus-visible:ring-red-500' : undefined}\n />\n {fieldErrors.confirmPassword && (\n <p id=\"confirmPassword-error\" className=\"text-xs text-red-600\">{fieldErrors.confirmPassword}</p>\n )}\n </div>\n <label className=\"flex items-start gap-3 text-sm text-muted-foreground\">\n <Checkbox\n id=\"terms\"\n checked={termsAccepted}\n disabled={disabled}\n onCheckedChange={(value: boolean | 'indeterminate') => {\n setTermsAccepted(value === true)\n if (value === true) {\n setFieldErrors((prev) => {\n const next = { ...prev }\n delete next.termsAccepted\n return next\n })\n }\n }}\n aria-invalid={Boolean(fieldErrors.termsAccepted)}\n />\n <span>\n {translate('onboarding.form.termsLabel', 'I have read and accept the ')}\n <a className=\"underline hover:text-foreground\" href=\"/terms\" target=\"_blank\" rel=\"noreferrer\">\n {translate('onboarding.form.termsLink', 'Terms of Service')}\n </a>\n {translate('onboarding.form.termsAnd', ' and ')}\n <a className=\"underline hover:text-foreground\" href=\"/privacy\" target=\"_blank\" rel=\"noreferrer\">\n {translate('onboarding.form.privacyLink', 'Privacy Policy')}\n </a>\n {fieldErrors.termsAccepted && (\n <span className=\"mt-1 block text-xs text-red-600\">{fieldErrors.termsAccepted}</span>\n )}\n </span>\n </label>\n <label className=\"flex items-start gap-3 text-sm text-muted-foreground\">\n <Checkbox\n id=\"marketingConsent\"\n checked={marketingConsent}\n disabled={disabled}\n onCheckedChange={(value: boolean | 'indeterminate') => {\n setMarketingConsent(value === true)\n }}\n />\n <span>\n {translate(\n 'onboarding.form.marketingLabel',\n \"I consent to receiving direct marketing from CT Tornado by email at the address I provide. I'm aware that I can withdraw my consent at any time (e.g., via the unsubscribe link), and that CT Tornado may also process my email address under its legitimate interests to manage its mailing list, keep proof of my consent, and record opt-outs. See our {termsLink} and {privacyLink}.\",\n )\n .split(/{termsLink}|{privacyLink}/)\n .map((part, index, parts) => (\n <span key={index}>\n {part}\n {index < parts.length - 1 && (\n index === 0 ? (\n <a className=\"underline hover:text-foreground\" href=\"/terms\" target=\"_blank\" rel=\"noreferrer\">\n {translate('onboarding.form.termsLink', 'Terms of Service')}\n </a>\n ) : (\n <a className=\"underline hover:text-foreground\" href=\"/privacy\" target=\"_blank\" rel=\"noreferrer\">\n {translate('onboarding.form.privacyLink', 'Privacy Policy')}\n </a>\n )\n )}\n </span>\n ))}\n </span>\n </label>\n <Button\n type=\"submit\"\n disabled={disabled}\n className=\"mt-2 h-11 bg-foreground text-background transition hover:opacity-90 disabled:cursor-not-allowed disabled:opacity-60\"\n >\n {submitting\n ? translate('onboarding.form.loading', 'Sending...')\n : translate('onboarding.form.submit', 'Send verification email')}\n </Button>\n </form>\n </CardContent>\n </Card>\n </div>\n )\n}\n"],
|
|
5
|
-
"mappings": "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
|
|
4
|
+
"sourcesContent": ["'use client'\n\nimport Image from 'next/image'\nimport Link from 'next/link'\nimport { useState } from 'react'\nimport { useLocale, useT } from '@open-mercato/shared/lib/i18n/context'\nimport { translateWithFallback } from '@open-mercato/shared/lib/i18n/translate'\nimport { formatPasswordRequirements, getPasswordPolicy } from '@open-mercato/shared/lib/auth/passwordPolicy'\nimport { onboardingStartSchema } from '@open-mercato/onboarding/modules/onboarding/data/validators'\nimport { apiCall } from '@open-mercato/ui/backend/utils/apiCall'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@open-mercato/ui/primitives/card'\nimport { Checkbox } from '@open-mercato/ui/primitives/checkbox'\nimport { Input } from '@open-mercato/ui/primitives/input'\nimport { Label } from '@open-mercato/ui/primitives/label'\n\ntype SubmissionState = 'idle' | 'loading' | 'success'\ntype FieldErrors = Partial<Record<\n 'email' | 'firstName' | 'lastName' | 'organizationName' | 'password' | 'confirmPassword' | 'termsAccepted' | 'marketingConsent',\n string\n>>\n\ntype Props = {\n onboardingEnabled: boolean\n}\n\nexport default function OnboardingPageClient({ onboardingEnabled }: Props) {\n const t = useT()\n const translate = (key: string, fallback: string, params?: Record<string, string | number>) =>\n translateWithFallback(t, key, fallback, params)\n const locale = useLocale()\n const [state, setState] = useState<SubmissionState>('idle')\n const [globalError, setGlobalError] = useState<string | null>(null)\n const [fieldErrors, setFieldErrors] = useState<FieldErrors>({})\n const [termsAccepted, setTermsAccepted] = useState(false)\n const [marketingConsent, setMarketingConsent] = useState(false)\n const [emailSubmitted, setEmailSubmitted] = useState<string | null>(null)\n const passwordPolicy = getPasswordPolicy()\n const passwordRequirements = formatPasswordRequirements(passwordPolicy, translate, 'onboarding.password.requirements')\n const passwordDescription = passwordRequirements\n ? translate(\n 'onboarding.password.requirements.help',\n 'Password requirements: {requirements}',\n { requirements: passwordRequirements },\n )\n : ''\n\n async function onSubmit(event: React.FormEvent<HTMLFormElement>) {\n event.preventDefault()\n if (!onboardingEnabled) return\n\n setState('loading')\n setGlobalError(null)\n setFieldErrors({})\n\n const form = new FormData(event.currentTarget)\n const payload = {\n email: String(form.get('email') ?? '').trim(),\n firstName: String(form.get('firstName') ?? '').trim(),\n lastName: String(form.get('lastName') ?? '').trim(),\n organizationName: String(form.get('organizationName') ?? '').trim(),\n password: String(form.get('password') ?? ''),\n confirmPassword: String(form.get('confirmPassword') ?? ''),\n termsAccepted,\n marketingConsent,\n locale,\n }\n\n const parsed = onboardingStartSchema.safeParse(payload)\n if (!parsed.success) {\n const issueMap: FieldErrors = {}\n parsed.error.issues.forEach((issue) => {\n const path = issue.path[0]\n if (!path) return\n switch (path) {\n case 'email':\n issueMap.email = translate('onboarding.errors.emailInvalid', 'Enter a valid work email.')\n break\n case 'firstName':\n issueMap.firstName = translate('onboarding.errors.firstNameRequired', 'First name is required.')\n break\n case 'lastName':\n issueMap.lastName = translate('onboarding.errors.lastNameRequired', 'Last name is required.')\n break\n case 'organizationName':\n issueMap.organizationName = translate('onboarding.errors.organizationNameRequired', 'Organization name is required.')\n break\n case 'password':\n issueMap.password = translate(\n 'onboarding.errors.passwordRequired',\n 'Password must meet the requirements: {requirements}.',\n { requirements: passwordRequirements },\n )\n break\n case 'confirmPassword':\n issueMap.confirmPassword = translate('onboarding.errors.passwordMismatch', 'Passwords must match.')\n break\n case 'termsAccepted':\n issueMap.termsAccepted = translate('onboarding.form.termsRequired', 'Please accept the terms to continue.')\n break\n default:\n break\n }\n })\n if (!issueMap.termsAccepted && !termsAccepted) {\n issueMap.termsAccepted = translate('onboarding.form.termsRequired', 'Please accept the terms to continue.')\n }\n setFieldErrors(issueMap)\n setState('idle')\n return\n }\n\n try {\n const call = await apiCall<{ ok?: boolean; error?: string; email?: string; fieldErrors?: Record<string, string> }>(\n '/api/onboarding/onboarding',\n {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ ...parsed.data, termsAccepted: true, marketingConsent }),\n },\n )\n const data = call.result ?? {}\n if (!call.ok || data.ok === false) {\n if (data.fieldErrors && typeof data.fieldErrors === 'object') {\n const mapped: FieldErrors = {}\n for (const key of Object.keys(data.fieldErrors)) {\n const value = data.fieldErrors[key]\n if (typeof value === 'string' && value.trim()) {\n mapped[key as keyof FieldErrors] = value\n }\n }\n setFieldErrors(mapped)\n }\n const message = typeof data.error === 'string' && data.error.trim()\n ? data.error\n : translate('onboarding.form.genericError', 'Something went wrong. Please try again.')\n setGlobalError(message)\n setState('idle')\n return\n }\n setEmailSubmitted(data.email ?? parsed.data.email)\n setState('success')\n } catch (err) {\n const message = err instanceof Error ? err.message : ''\n setGlobalError(message || translate('onboarding.form.genericError', 'Something went wrong. Please try again.'))\n setState('idle')\n }\n }\n\n const onboardingDisabled = !onboardingEnabled\n const submitting = state === 'loading'\n const disabled = onboardingDisabled || submitting || state === 'success'\n\n return (\n <div className=\"relative flex min-h-svh items-center justify-center bg-muted/50 px-4 pb-24\">\n <Card className=\"relative w-full max-w-lg overflow-hidden shadow-lg\">\n {onboardingDisabled ? (\n <div className=\"absolute inset-0 z-10 flex items-center justify-center rounded-lg bg-background/80 p-6 backdrop-blur-[2px]\">\n <div\n className=\"max-w-sm rounded-xl border border-border/70 bg-background/95 px-5 py-4 text-center shadow-sm\"\n role=\"alert\"\n aria-live=\"polite\"\n >\n <p className=\"text-base font-semibold text-foreground\">\n {translate('onboarding.disabled.title', 'Self-service onboarding is currently unavailable')}\n </p>\n <p className=\"mt-2 text-sm text-muted-foreground\">\n {translate(\n 'onboarding.disabled.description',\n 'Workspace creation is currently handled manually. Contact the Open Mercato team or your administrator to request access before continuing.',\n )}\n </p>\n <div className=\"mt-4 flex justify-center\">\n <Button asChild variant=\"outline\">\n <Link href=\"/login\">\n {translate('onboarding.disabled.cta', 'Go to login')}\n </Link>\n </Button>\n </div>\n </div>\n </div>\n ) : null}\n <CardHeader className=\"flex flex-col gap-4 p-10 text-center\">\n <div className=\"flex flex-col items-center gap-3\">\n <Image alt=\"Open Mercato\" src=\"/open-mercato.svg\" width={120} height={120} priority />\n <CardTitle className=\"text-2xl font-semibold\">\n {translate('onboarding.title', 'Create your Open Mercato workspace')}\n </CardTitle>\n <CardDescription>\n {translate('onboarding.subtitle', 'Tell us a bit about you and we will set everything up.')}\n </CardDescription>\n </div>\n </CardHeader>\n <CardContent className={onboardingDisabled ? 'pb-10 opacity-45' : 'pb-10'}>\n {state === 'success' && emailSubmitted && (\n <div className=\"mb-6 rounded-md border border-emerald-200 bg-emerald-50 px-4 py-3 text-sm text-emerald-900\" role=\"status\" aria-live=\"polite\">\n <strong className=\"block text-sm font-medium\">\n {translate('onboarding.form.successTitle', 'Check your inbox')}\n </strong>\n <p>\n {translate('onboarding.form.successBody', 'We sent a verification link to {email}. Confirm it within 24 hours to activate your workspace.', { email: emailSubmitted })}\n </p>\n </div>\n )}\n {state !== 'success' && globalError && (\n <div className=\"mb-4 rounded-md border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700\" role=\"alert\" aria-live=\"assertive\">\n {globalError}\n </div>\n )}\n <form className=\"grid gap-4\" onSubmit={onSubmit} noValidate>\n <div className=\"grid gap-1\">\n <Label htmlFor=\"email\">{translate('onboarding.form.email', 'Work email')}</Label>\n <Input\n id=\"email\"\n name=\"email\"\n type=\"email\"\n required\n disabled={disabled}\n autoComplete=\"email\"\n aria-invalid={Boolean(fieldErrors.email)}\n aria-describedby={fieldErrors.email ? 'email-error' : undefined}\n className={fieldErrors.email ? 'border-red-500 aria-invalid:ring-destructive' : undefined}\n />\n {fieldErrors.email && (\n <p id=\"email-error\" className=\"text-xs text-red-600\">{fieldErrors.email}</p>\n )}\n </div>\n <div className=\"grid gap-1 sm:grid-cols-2 sm:gap-4\">\n <div className=\"grid gap-1\">\n <Label htmlFor=\"firstName\">{translate('onboarding.form.firstName', 'First name')}</Label>\n <Input\n id=\"firstName\"\n name=\"firstName\"\n type=\"text\"\n required\n disabled={disabled}\n autoComplete=\"given-name\"\n aria-invalid={Boolean(fieldErrors.firstName)}\n aria-describedby={fieldErrors.firstName ? 'firstName-error' : undefined}\n className={fieldErrors.firstName ? 'border-red-500 aria-invalid:ring-destructive' : undefined}\n />\n {fieldErrors.firstName && (\n <p id=\"firstName-error\" className=\"text-xs text-red-600\">{fieldErrors.firstName}</p>\n )}\n </div>\n <div className=\"grid gap-1\">\n <Label htmlFor=\"lastName\">{translate('onboarding.form.lastName', 'Last name')}</Label>\n <Input\n id=\"lastName\"\n name=\"lastName\"\n type=\"text\"\n required\n disabled={disabled}\n autoComplete=\"family-name\"\n aria-invalid={Boolean(fieldErrors.lastName)}\n aria-describedby={fieldErrors.lastName ? 'lastName-error' : undefined}\n className={fieldErrors.lastName ? 'border-red-500 aria-invalid:ring-destructive' : undefined}\n />\n {fieldErrors.lastName && (\n <p id=\"lastName-error\" className=\"text-xs text-red-600\">{fieldErrors.lastName}</p>\n )}\n </div>\n </div>\n <div className=\"grid gap-1\">\n <Label htmlFor=\"organizationName\">{translate('onboarding.form.organizationName', 'Organization name')}</Label>\n <Input\n id=\"organizationName\"\n name=\"organizationName\"\n type=\"text\"\n required\n disabled={disabled}\n autoComplete=\"organization\"\n aria-invalid={Boolean(fieldErrors.organizationName)}\n aria-describedby={fieldErrors.organizationName ? 'organizationName-error' : undefined}\n className={fieldErrors.organizationName ? 'border-red-500 aria-invalid:ring-destructive' : undefined}\n />\n {fieldErrors.organizationName && (\n <p id=\"organizationName-error\" className=\"text-xs text-red-600\">{fieldErrors.organizationName}</p>\n )}\n </div>\n <div className=\"grid gap-1\">\n <Label htmlFor=\"password\">{translate('onboarding.form.password', 'Password')}</Label>\n <Input\n id=\"password\"\n name=\"password\"\n type=\"password\"\n required\n disabled={disabled}\n autoComplete=\"new-password\"\n minLength={passwordPolicy.minLength}\n aria-invalid={Boolean(fieldErrors.password)}\n aria-describedby={fieldErrors.password ? 'password-error' : undefined}\n className={fieldErrors.password ? 'border-red-500 aria-invalid:ring-destructive' : undefined}\n />\n {passwordDescription ? (\n <p className=\"text-xs text-muted-foreground\">{passwordDescription}</p>\n ) : null}\n {fieldErrors.password && (\n <p id=\"password-error\" className=\"text-xs text-red-600\">{fieldErrors.password}</p>\n )}\n </div>\n <div className=\"grid gap-1\">\n <Label htmlFor=\"confirmPassword\">{translate('onboarding.form.confirmPassword', 'Confirm password')}</Label>\n <Input\n id=\"confirmPassword\"\n name=\"confirmPassword\"\n type=\"password\"\n required\n disabled={disabled}\n autoComplete=\"new-password\"\n aria-invalid={Boolean(fieldErrors.confirmPassword)}\n aria-describedby={fieldErrors.confirmPassword ? 'confirmPassword-error' : undefined}\n className={fieldErrors.confirmPassword ? 'border-red-500 aria-invalid:ring-destructive' : undefined}\n />\n {fieldErrors.confirmPassword && (\n <p id=\"confirmPassword-error\" className=\"text-xs text-red-600\">{fieldErrors.confirmPassword}</p>\n )}\n </div>\n <label className=\"flex items-start gap-3 text-sm text-muted-foreground\">\n <Checkbox\n id=\"terms\"\n checked={termsAccepted}\n disabled={disabled}\n onCheckedChange={(value: boolean | 'indeterminate') => {\n setTermsAccepted(value === true)\n if (value === true) {\n setFieldErrors((prev) => {\n const next = { ...prev }\n delete next.termsAccepted\n return next\n })\n }\n }}\n aria-invalid={Boolean(fieldErrors.termsAccepted)}\n />\n <span>\n {translate('onboarding.form.termsLabel', 'I have read and accept the ')}\n <a className=\"underline hover:text-foreground\" href=\"/terms\" target=\"_blank\" rel=\"noreferrer\">\n {translate('onboarding.form.termsLink', 'Terms of Service')}\n </a>\n {translate('onboarding.form.termsAnd', ' and ')}\n <a className=\"underline hover:text-foreground\" href=\"/privacy\" target=\"_blank\" rel=\"noreferrer\">\n {translate('onboarding.form.privacyLink', 'Privacy Policy')}\n </a>\n {fieldErrors.termsAccepted && (\n <span className=\"mt-1 block text-xs text-red-600\">{fieldErrors.termsAccepted}</span>\n )}\n </span>\n </label>\n <label className=\"flex items-start gap-3 text-sm text-muted-foreground\">\n <Checkbox\n id=\"marketingConsent\"\n checked={marketingConsent}\n disabled={disabled}\n onCheckedChange={(value: boolean | 'indeterminate') => {\n setMarketingConsent(value === true)\n }}\n />\n <span>\n {translate(\n 'onboarding.form.marketingLabel',\n \"I consent to receiving direct marketing from CT Tornado by email at the address I provide. I'm aware that I can withdraw my consent at any time (e.g., via the unsubscribe link), and that CT Tornado may also process my email address under its legitimate interests to manage its mailing list, keep proof of my consent, and record opt-outs. See our {termsLink} and {privacyLink}.\",\n )\n .split(/{termsLink}|{privacyLink}/)\n .map((part, index, parts) => (\n <span key={index}>\n {part}\n {index < parts.length - 1 && (\n index === 0 ? (\n <a className=\"underline hover:text-foreground\" href=\"/terms\" target=\"_blank\" rel=\"noreferrer\">\n {translate('onboarding.form.termsLink', 'Terms of Service')}\n </a>\n ) : (\n <a className=\"underline hover:text-foreground\" href=\"/privacy\" target=\"_blank\" rel=\"noreferrer\">\n {translate('onboarding.form.privacyLink', 'Privacy Policy')}\n </a>\n )\n )}\n </span>\n ))}\n </span>\n </label>\n <Button\n type=\"submit\"\n disabled={disabled}\n className=\"mt-2 h-11 bg-foreground text-background transition hover:opacity-80 disabled:cursor-not-allowed disabled:opacity-50\"\n >\n {submitting\n ? translate('onboarding.form.loading', 'Sending...')\n : translate('onboarding.form.submit', 'Send verification email')}\n </Button>\n </form>\n </CardContent>\n </Card>\n </div>\n )\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -64,7 +64,7 @@ function PreparingPageClient() {
|
|
|
64
64
|
if (timeoutId) clearTimeout(timeoutId);
|
|
65
65
|
};
|
|
66
66
|
}, [router, tenantId]);
|
|
67
|
-
return /* @__PURE__ */ jsx("div", { className: "relative flex min-h-svh items-center justify-center bg-muted/
|
|
67
|
+
return /* @__PURE__ */ jsx("div", { className: "relative flex min-h-svh items-center justify-center bg-muted/50 px-4 pb-24", children: /* @__PURE__ */ jsxs(Card, { className: "relative w-full max-w-lg overflow-hidden shadow-lg", children: [
|
|
68
68
|
/* @__PURE__ */ jsx(CardHeader, { className: "flex flex-col gap-4 p-10 text-center", children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center gap-4", children: [
|
|
69
69
|
/* @__PURE__ */ jsx(Image, { alt: "Open Mercato", src: "/open-mercato.svg", width: 120, height: 120, priority: true }),
|
|
70
70
|
/* @__PURE__ */ jsx("span", { className: "inline-flex h-16 w-16 items-center justify-center rounded-full bg-foreground/5", children: /* @__PURE__ */ jsx(Spinner, { size: "lg" }) }),
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../../src/modules/onboarding/frontend/onboarding/preparing/PreparingPageClient.tsx"],
|
|
4
|
-
"sourcesContent": ["'use client'\n\nimport Image from 'next/image'\nimport Link from 'next/link'\nimport { useEffect, useState } from 'react'\nimport { useRouter, useSearchParams } from 'next/navigation'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { translateWithFallback } from '@open-mercato/shared/lib/i18n/translate'\nimport { apiCall } from '@open-mercato/ui/backend/utils/apiCall'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@open-mercato/ui/primitives/card'\nimport { Spinner } from '@open-mercato/ui/primitives/spinner'\n\nexport default function PreparingPageClient() {\n const t = useT()\n const router = useRouter()\n const translate = (key: string, fallback: string, params?: Record<string, string | number>) =>\n translateWithFallback(t, key, fallback, params)\n const searchParams = useSearchParams()\n const tenantId = (searchParams.get('tenant') || '').trim()\n const [tenantName, setTenantName] = useState<string | null>(null)\n const [redirecting, setRedirecting] = useState(false)\n\n useEffect(() => {\n if (!tenantId) {\n setTenantName(null)\n return\n }\n let active = true\n apiCall<{ ok?: boolean; tenant?: { id: string; name: string } }>(\n `/api/directory/tenants/lookup?tenantId=${encodeURIComponent(tenantId)}`,\n )\n .then(({ result }) => {\n if (!active) return\n setTenantName(result?.ok && result.tenant ? result.tenant.name : null)\n })\n .catch(() => {\n if (!active) return\n setTenantName(null)\n })\n return () => {\n active = false\n }\n }, [tenantId])\n\n useEffect(() => {\n if (!tenantId) return\n let active = true\n let timeoutId: ReturnType<typeof setTimeout> | null = null\n\n const poll = async () => {\n try {\n const { result } = await apiCall<{\n ok?: boolean\n ready?: boolean\n loginUrl?: string | null\n }>(`/api/onboarding/onboarding/status?tenantId=${encodeURIComponent(tenantId)}`)\n if (!active || !result?.ok) return\n if (result.ready && result.loginUrl) {\n setRedirecting(true)\n router.replace(result.loginUrl)\n return\n }\n } catch {\n if (!active) return\n }\n if (!active) return\n timeoutId = setTimeout(() => {\n void poll()\n }, 3000)\n }\n\n void poll()\n\n return () => {\n active = false\n if (timeoutId) clearTimeout(timeoutId)\n }\n }, [router, tenantId])\n\n return (\n <div className=\"relative flex min-h-svh items-center justify-center bg-muted/
|
|
4
|
+
"sourcesContent": ["'use client'\n\nimport Image from 'next/image'\nimport Link from 'next/link'\nimport { useEffect, useState } from 'react'\nimport { useRouter, useSearchParams } from 'next/navigation'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { translateWithFallback } from '@open-mercato/shared/lib/i18n/translate'\nimport { apiCall } from '@open-mercato/ui/backend/utils/apiCall'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@open-mercato/ui/primitives/card'\nimport { Spinner } from '@open-mercato/ui/primitives/spinner'\n\nexport default function PreparingPageClient() {\n const t = useT()\n const router = useRouter()\n const translate = (key: string, fallback: string, params?: Record<string, string | number>) =>\n translateWithFallback(t, key, fallback, params)\n const searchParams = useSearchParams()\n const tenantId = (searchParams.get('tenant') || '').trim()\n const [tenantName, setTenantName] = useState<string | null>(null)\n const [redirecting, setRedirecting] = useState(false)\n\n useEffect(() => {\n if (!tenantId) {\n setTenantName(null)\n return\n }\n let active = true\n apiCall<{ ok?: boolean; tenant?: { id: string; name: string } }>(\n `/api/directory/tenants/lookup?tenantId=${encodeURIComponent(tenantId)}`,\n )\n .then(({ result }) => {\n if (!active) return\n setTenantName(result?.ok && result.tenant ? result.tenant.name : null)\n })\n .catch(() => {\n if (!active) return\n setTenantName(null)\n })\n return () => {\n active = false\n }\n }, [tenantId])\n\n useEffect(() => {\n if (!tenantId) return\n let active = true\n let timeoutId: ReturnType<typeof setTimeout> | null = null\n\n const poll = async () => {\n try {\n const { result } = await apiCall<{\n ok?: boolean\n ready?: boolean\n loginUrl?: string | null\n }>(`/api/onboarding/onboarding/status?tenantId=${encodeURIComponent(tenantId)}`)\n if (!active || !result?.ok) return\n if (result.ready && result.loginUrl) {\n setRedirecting(true)\n router.replace(result.loginUrl)\n return\n }\n } catch {\n if (!active) return\n }\n if (!active) return\n timeoutId = setTimeout(() => {\n void poll()\n }, 3000)\n }\n\n void poll()\n\n return () => {\n active = false\n if (timeoutId) clearTimeout(timeoutId)\n }\n }, [router, tenantId])\n\n return (\n <div className=\"relative flex min-h-svh items-center justify-center bg-muted/50 px-4 pb-24\">\n <Card className=\"relative w-full max-w-lg overflow-hidden shadow-lg\">\n <CardHeader className=\"flex flex-col gap-4 p-10 text-center\">\n <div className=\"flex flex-col items-center gap-4\">\n <Image alt=\"Open Mercato\" src=\"/open-mercato.svg\" width={120} height={120} priority />\n <span className=\"inline-flex h-16 w-16 items-center justify-center rounded-full bg-foreground/5\">\n <Spinner size=\"lg\" />\n </span>\n <CardTitle className=\"text-2xl font-semibold\">\n {translate('onboarding.preparing.title', 'We are preparing your workspace')}\n </CardTitle>\n <CardDescription className=\"max-w-md text-balance\">\n {tenantName\n ? translate(\n 'onboarding.preparing.descriptionWithTenant',\n 'We are finishing the demo environment for {tenant}. We will send you an email with the correct tenant login link as soon as it is ready.',\n { tenant: tenantName },\n )\n : translate(\n 'onboarding.preparing.description',\n 'We are finishing your demo environment. We will send you an email with the correct tenant login link as soon as it is ready.',\n )}\n </CardDescription>\n </div>\n </CardHeader>\n <CardContent className=\"space-y-6 pb-10 text-center\">\n <div className=\"rounded-xl border border-border/70 bg-background/80 px-4 py-4 text-sm text-muted-foreground\">\n {redirecting\n ? translate(\n 'onboarding.preparing.redirecting',\n 'Your workspace is ready. Redirecting you to the tenant login page now.',\n )\n : translate(\n 'onboarding.preparing.emailNotice',\n 'You do not need to keep this page open. We will email you when everything is ready.',\n )}\n </div>\n <div className=\"flex flex-col items-center justify-center gap-3 sm:flex-row\">\n <Button asChild>\n <Link href=\"/\">\n {translate('onboarding.preparing.homeCta', 'Go to home page')}\n </Link>\n </Button>\n </div>\n </CardContent>\n </Card>\n </div>\n )\n}\n"],
|
|
5
5
|
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@open-mercato/onboarding",
|
|
3
|
-
"version": "0.5.1-develop.
|
|
3
|
+
"version": "0.5.1-develop.2874.77704bccbd",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -69,10 +69,10 @@
|
|
|
69
69
|
}
|
|
70
70
|
},
|
|
71
71
|
"peerDependencies": {
|
|
72
|
-
"@open-mercato/shared": "0.5.1-develop.
|
|
72
|
+
"@open-mercato/shared": "0.5.1-develop.2874.77704bccbd"
|
|
73
73
|
},
|
|
74
74
|
"devDependencies": {
|
|
75
|
-
"@open-mercato/shared": "0.5.1-develop.
|
|
75
|
+
"@open-mercato/shared": "0.5.1-develop.2874.77704bccbd",
|
|
76
76
|
"@types/jest": "^30.0.0",
|
|
77
77
|
"jest": "^30.3.0",
|
|
78
78
|
"ts-jest": "^29.4.9"
|
|
@@ -152,10 +152,10 @@ export default function OnboardingPageClient({ onboardingEnabled }: Props) {
|
|
|
152
152
|
const disabled = onboardingDisabled || submitting || state === 'success'
|
|
153
153
|
|
|
154
154
|
return (
|
|
155
|
-
<div className="relative flex min-h-svh items-center justify-center bg-muted/
|
|
155
|
+
<div className="relative flex min-h-svh items-center justify-center bg-muted/50 px-4 pb-24">
|
|
156
156
|
<Card className="relative w-full max-w-lg overflow-hidden shadow-lg">
|
|
157
157
|
{onboardingDisabled ? (
|
|
158
|
-
<div className="absolute inset-0 z-10 flex items-center justify-center rounded-lg bg-background/
|
|
158
|
+
<div className="absolute inset-0 z-10 flex items-center justify-center rounded-lg bg-background/80 p-6 backdrop-blur-[2px]">
|
|
159
159
|
<div
|
|
160
160
|
className="max-w-sm rounded-xl border border-border/70 bg-background/95 px-5 py-4 text-center shadow-sm"
|
|
161
161
|
role="alert"
|
|
@@ -219,7 +219,7 @@ export default function OnboardingPageClient({ onboardingEnabled }: Props) {
|
|
|
219
219
|
autoComplete="email"
|
|
220
220
|
aria-invalid={Boolean(fieldErrors.email)}
|
|
221
221
|
aria-describedby={fieldErrors.email ? 'email-error' : undefined}
|
|
222
|
-
className={fieldErrors.email ? 'border-red-500
|
|
222
|
+
className={fieldErrors.email ? 'border-red-500 aria-invalid:ring-destructive' : undefined}
|
|
223
223
|
/>
|
|
224
224
|
{fieldErrors.email && (
|
|
225
225
|
<p id="email-error" className="text-xs text-red-600">{fieldErrors.email}</p>
|
|
@@ -237,7 +237,7 @@ export default function OnboardingPageClient({ onboardingEnabled }: Props) {
|
|
|
237
237
|
autoComplete="given-name"
|
|
238
238
|
aria-invalid={Boolean(fieldErrors.firstName)}
|
|
239
239
|
aria-describedby={fieldErrors.firstName ? 'firstName-error' : undefined}
|
|
240
|
-
className={fieldErrors.firstName ? 'border-red-500
|
|
240
|
+
className={fieldErrors.firstName ? 'border-red-500 aria-invalid:ring-destructive' : undefined}
|
|
241
241
|
/>
|
|
242
242
|
{fieldErrors.firstName && (
|
|
243
243
|
<p id="firstName-error" className="text-xs text-red-600">{fieldErrors.firstName}</p>
|
|
@@ -254,7 +254,7 @@ export default function OnboardingPageClient({ onboardingEnabled }: Props) {
|
|
|
254
254
|
autoComplete="family-name"
|
|
255
255
|
aria-invalid={Boolean(fieldErrors.lastName)}
|
|
256
256
|
aria-describedby={fieldErrors.lastName ? 'lastName-error' : undefined}
|
|
257
|
-
className={fieldErrors.lastName ? 'border-red-500
|
|
257
|
+
className={fieldErrors.lastName ? 'border-red-500 aria-invalid:ring-destructive' : undefined}
|
|
258
258
|
/>
|
|
259
259
|
{fieldErrors.lastName && (
|
|
260
260
|
<p id="lastName-error" className="text-xs text-red-600">{fieldErrors.lastName}</p>
|
|
@@ -272,7 +272,7 @@ export default function OnboardingPageClient({ onboardingEnabled }: Props) {
|
|
|
272
272
|
autoComplete="organization"
|
|
273
273
|
aria-invalid={Boolean(fieldErrors.organizationName)}
|
|
274
274
|
aria-describedby={fieldErrors.organizationName ? 'organizationName-error' : undefined}
|
|
275
|
-
className={fieldErrors.organizationName ? 'border-red-500
|
|
275
|
+
className={fieldErrors.organizationName ? 'border-red-500 aria-invalid:ring-destructive' : undefined}
|
|
276
276
|
/>
|
|
277
277
|
{fieldErrors.organizationName && (
|
|
278
278
|
<p id="organizationName-error" className="text-xs text-red-600">{fieldErrors.organizationName}</p>
|
|
@@ -290,7 +290,7 @@ export default function OnboardingPageClient({ onboardingEnabled }: Props) {
|
|
|
290
290
|
minLength={passwordPolicy.minLength}
|
|
291
291
|
aria-invalid={Boolean(fieldErrors.password)}
|
|
292
292
|
aria-describedby={fieldErrors.password ? 'password-error' : undefined}
|
|
293
|
-
className={fieldErrors.password ? 'border-red-500
|
|
293
|
+
className={fieldErrors.password ? 'border-red-500 aria-invalid:ring-destructive' : undefined}
|
|
294
294
|
/>
|
|
295
295
|
{passwordDescription ? (
|
|
296
296
|
<p className="text-xs text-muted-foreground">{passwordDescription}</p>
|
|
@@ -310,7 +310,7 @@ export default function OnboardingPageClient({ onboardingEnabled }: Props) {
|
|
|
310
310
|
autoComplete="new-password"
|
|
311
311
|
aria-invalid={Boolean(fieldErrors.confirmPassword)}
|
|
312
312
|
aria-describedby={fieldErrors.confirmPassword ? 'confirmPassword-error' : undefined}
|
|
313
|
-
className={fieldErrors.confirmPassword ? 'border-red-500
|
|
313
|
+
className={fieldErrors.confirmPassword ? 'border-red-500 aria-invalid:ring-destructive' : undefined}
|
|
314
314
|
/>
|
|
315
315
|
{fieldErrors.confirmPassword && (
|
|
316
316
|
<p id="confirmPassword-error" className="text-xs text-red-600">{fieldErrors.confirmPassword}</p>
|
|
@@ -383,7 +383,7 @@ export default function OnboardingPageClient({ onboardingEnabled }: Props) {
|
|
|
383
383
|
<Button
|
|
384
384
|
type="submit"
|
|
385
385
|
disabled={disabled}
|
|
386
|
-
className="mt-2 h-11 bg-foreground text-background transition hover:opacity-
|
|
386
|
+
className="mt-2 h-11 bg-foreground text-background transition hover:opacity-80 disabled:cursor-not-allowed disabled:opacity-50"
|
|
387
387
|
>
|
|
388
388
|
{submitting
|
|
389
389
|
? translate('onboarding.form.loading', 'Sending...')
|
|
@@ -79,7 +79,7 @@ export default function PreparingPageClient() {
|
|
|
79
79
|
}, [router, tenantId])
|
|
80
80
|
|
|
81
81
|
return (
|
|
82
|
-
<div className="relative flex min-h-svh items-center justify-center bg-muted/
|
|
82
|
+
<div className="relative flex min-h-svh items-center justify-center bg-muted/50 px-4 pb-24">
|
|
83
83
|
<Card className="relative w-full max-w-lg overflow-hidden shadow-lg">
|
|
84
84
|
<CardHeader className="flex flex-col gap-4 p-10 text-center">
|
|
85
85
|
<div className="flex flex-col items-center gap-4">
|