@burdenoff/microfe-auth 2026.601.5 → 2026.608.1
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/auth/components/CaptchaV2.js +15 -14
- package/dist/auth/components/CaptchaV2.js.map +1 -1
- package/dist/auth/hooks/useSignupCaptcha.js +39 -0
- package/dist/auth/hooks/useSignupCaptcha.js.map +1 -0
- package/dist/auth/pages/RegisterPage.js +208 -203
- package/dist/auth/pages/RegisterPage.js.map +1 -1
- package/dist/auth/providers/AuthProvider.js +8 -7
- package/dist/auth/providers/AuthProvider.js.map +1 -1
- package/package.json +1 -1
|
@@ -3,38 +3,39 @@ import { Suspense as n, lazy as r, useCallback as i, useState as a } from "react
|
|
|
3
3
|
import { useI18n as o } from "@burdenoff/fe-libs/shared/providers/shell";
|
|
4
4
|
import { jsx as s } from "react/jsx-runtime";
|
|
5
5
|
//#region src/auth/components/CaptchaV2.tsx
|
|
6
|
-
var c = r(() => import("../../node_modules/react-google-recaptcha/lib/esm/index.js").then((t) => ({ default: function({ onVerify: n, onExpired: r }) {
|
|
7
|
-
let
|
|
6
|
+
var c = r(() => import("../../node_modules/react-google-recaptcha/lib/esm/index.js").then((t) => ({ default: function({ onVerify: n, onExpired: r, siteKey: o }) {
|
|
7
|
+
let c = t.default, [, l] = a(null), u = o ?? e(), d = i((e) => {
|
|
8
8
|
n(e);
|
|
9
|
-
}, [n]),
|
|
9
|
+
}, [n]), f = i(() => {
|
|
10
10
|
n(null), r?.();
|
|
11
11
|
}, [n, r]);
|
|
12
|
-
return
|
|
12
|
+
return u ? /* @__PURE__ */ s("div", {
|
|
13
13
|
className: "flex justify-center",
|
|
14
|
-
children: /* @__PURE__ */ s(
|
|
15
|
-
ref:
|
|
16
|
-
sitekey:
|
|
17
|
-
onChange:
|
|
18
|
-
onExpired:
|
|
14
|
+
children: /* @__PURE__ */ s(c, {
|
|
15
|
+
ref: l,
|
|
16
|
+
sitekey: u,
|
|
17
|
+
onChange: d,
|
|
18
|
+
onExpired: f
|
|
19
19
|
})
|
|
20
20
|
}) : /* @__PURE__ */ s("div", {
|
|
21
21
|
className: "rounded-lg bg-status-error-bg-subtle px-3 py-2 text-sm text-status-error-text",
|
|
22
22
|
children: "reCAPTCHA site key is not configured."
|
|
23
23
|
});
|
|
24
24
|
} })));
|
|
25
|
-
function l({ onVerify: e, onExpired: r }) {
|
|
26
|
-
let { t:
|
|
27
|
-
return t() ? /* @__PURE__ */ s(n, {
|
|
25
|
+
function l({ onVerify: e, onExpired: r, enabled: i, siteKey: a }) {
|
|
26
|
+
let { t: l } = o();
|
|
27
|
+
return i ?? t() ? /* @__PURE__ */ s(n, {
|
|
28
28
|
fallback: /* @__PURE__ */ s("div", {
|
|
29
29
|
className: "flex justify-center py-2",
|
|
30
30
|
children: ((e, t) => {
|
|
31
|
-
let n =
|
|
31
|
+
let n = l(e);
|
|
32
32
|
return n === e ? t : n;
|
|
33
33
|
})("auth.captcha.loading", "Loading captcha...")
|
|
34
34
|
}),
|
|
35
35
|
children: /* @__PURE__ */ s(c, {
|
|
36
36
|
onVerify: e,
|
|
37
|
-
onExpired: r
|
|
37
|
+
onExpired: r,
|
|
38
|
+
siteKey: a
|
|
38
39
|
})
|
|
39
40
|
}) : null;
|
|
40
41
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CaptchaV2.js","names":[],"sources":["../../../src/auth/components/CaptchaV2.tsx"],"sourcesContent":["import { lazy, Suspense, useState, useCallback } from \"react\";\nimport { useI18n } from \"@burdenoff/fe-libs/shared/providers/shell\";\nimport {\n getRecaptchaSiteKey,\n isCaptchaEnabled as getIsCaptchaEnabled,\n} from \"../utils/captchaConfig\";\n\ninterface CaptchaV2Props {\n onVerify: (token: string | null) => void;\n onExpired?: () => void;\n}\n\n/**\n * Lazily loaded inner component that imports react-google-recaptcha.\n * Only loaded when
|
|
1
|
+
{"version":3,"file":"CaptchaV2.js","names":[],"sources":["../../../src/auth/components/CaptchaV2.tsx"],"sourcesContent":["import { lazy, Suspense, useState, useCallback } from \"react\";\nimport { useI18n } from \"@burdenoff/fe-libs/shared/providers/shell\";\nimport {\n getRecaptchaSiteKey,\n isCaptchaEnabled as getIsCaptchaEnabled,\n} from \"../utils/captchaConfig\";\n\ninterface CaptchaV2Props {\n onVerify: (token: string | null) => void;\n onExpired?: () => void;\n /**\n * Host-driven enable flag. When provided it takes precedence over the\n * build-time env switch (used so a per-product site key can turn the widget\n * on/off without rebuilding). When omitted, falls back to env.\n */\n enabled?: boolean;\n /**\n * reCAPTCHA v2 site key to render with. Overrides the build-time env key.\n * Resolved per-product by the caller (see `useSignupCaptcha`).\n */\n siteKey?: string;\n}\n\ninterface CaptchaWidgetProps {\n onVerify: (token: string | null) => void;\n onExpired?: () => void;\n siteKey?: string;\n}\n\n/**\n * Lazily loaded inner component that imports react-google-recaptcha.\n * Only loaded when captcha is enabled, avoiding import resolution\n * errors in dev environments where the package may not be installed.\n */\nconst LazyCaptchaWidget = lazy(() =>\n import(\"react-google-recaptcha\").then((mod) => ({\n default: function CaptchaWidget({\n onVerify,\n onExpired,\n siteKey: siteKeyProp,\n }: CaptchaWidgetProps) {\n const ReCAPTCHA = mod.default;\n const [, setRef] = useState<InstanceType<typeof ReCAPTCHA> | null>(null);\n const siteKey = siteKeyProp ?? getRecaptchaSiteKey();\n\n const forwardCaptchaToken = useCallback(\n (token: string | null) => {\n onVerify(token);\n },\n [onVerify],\n );\n\n const clearCaptchaToken = useCallback(() => {\n onVerify(null);\n onExpired?.();\n }, [onVerify, onExpired]);\n\n if (!siteKey) {\n return (\n <div className=\"rounded-lg bg-status-error-bg-subtle px-3 py-2 text-sm text-status-error-text\">\n reCAPTCHA site key is not configured.\n </div>\n );\n }\n\n return (\n <div className=\"flex justify-center\">\n <ReCAPTCHA\n ref={setRef}\n sitekey={siteKey}\n onChange={forwardCaptchaToken}\n onExpired={clearCaptchaToken}\n />\n </div>\n );\n },\n })),\n);\n\n/**\n * reCAPTCHA v2 checkbox component.\n *\n * Enablement is host-driven via the `enabled` prop (resolved per-product from\n * the platform catalog). When `enabled` is omitted it falls back to the\n * build-time `VITE_CAPTCHA_ENABLED` env for local source-dev. Renders nothing\n * when disabled and lazily loads the Google reCAPTCHA library only when enabled.\n */\nexport function CaptchaV2({\n onVerify,\n onExpired,\n enabled,\n siteKey,\n}: CaptchaV2Props) {\n const { t } = useI18n();\n const tr = (key: string, fallback: string): string => {\n const translated = t(key);\n return translated === key ? fallback : translated;\n };\n\n const isEnabled = enabled ?? getIsCaptchaEnabled();\n if (!isEnabled) {\n return null;\n }\n\n return (\n <Suspense\n fallback={\n <div className=\"flex justify-center py-2\">\n {tr(\"auth.captcha.loading\", \"Loading captcha...\")}\n </div>\n }\n >\n <LazyCaptchaWidget\n onVerify={onVerify}\n onExpired={onExpired}\n siteKey={siteKey}\n />\n </Suspense>\n );\n}\n"],"mappings":";;;;;AAkCA,IAAM,IAAoB,QACxB,OAAO,8DAA0B,MAAM,OAAS,EAC9C,SAAS,SAAuB,EAC9B,aACA,cACA,SAAS,KACY;CACrB,IAAM,IAAY,EAAI,SAChB,GAAG,KAAU,EAAgD,KAAK,EAClE,IAAU,KAAe,GAAqB,EAE9C,IAAsB,GACzB,MAAyB;AACxB,IAAS,EAAM;IAEjB,CAAC,EAAS,CACX,EAEK,IAAoB,QAAkB;AAE1C,EADA,EAAS,KAAK,EACd,KAAa;IACZ,CAAC,GAAU,EAAU,CAAC;AAUzB,QARK,IASH,kBAAC,OAAD;EAAK,WAAU;YACb,kBAAC,GAAD;GACE,KAAK;GACL,SAAS;GACT,UAAU;GACV,WAAW;GACX,CAAA;EACE,CAAA,GAdJ,kBAAC,OAAD;EAAK,WAAU;YAAgF;EAEzF,CAAA;GAeb,EAAE,CACJ;AAUD,SAAgB,EAAU,EACxB,aACA,cACA,YACA,cACiB;CACjB,IAAM,EAAE,SAAM,GAAS;AAWvB,QALkB,KAAW,GAAqB,GAMhD,kBAAC,GAAD;EACE,UACE,kBAAC,OAAD;GAAK,WAAU;eAbT,GAAa,MAA6B;IACpD,IAAM,IAAa,EAAE,EAAI;AACzB,WAAO,MAAe,IAAM,IAAW;MAY7B,wBAAwB,qBAAqB;GAC7C,CAAA;YAGR,kBAAC,GAAD;GACY;GACC;GACF;GACT,CAAA;EACO,CAAA,GAhBJ"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { useAuth as e } from "../providers/AuthProvider.js";
|
|
2
|
+
import { getRecaptchaSiteKey as t, isCaptchaEnabled as n } from "../utils/captchaConfig.js";
|
|
3
|
+
import { useQuery as r } from "@apollo/client/react";
|
|
4
|
+
import { gql as i } from "@apollo/client";
|
|
5
|
+
//#region src/auth/hooks/useSignupCaptcha.ts
|
|
6
|
+
var a = i`
|
|
7
|
+
query PublicProductCaptcha($slug: String!) {
|
|
8
|
+
publicProduct(slug: $slug) {
|
|
9
|
+
slug
|
|
10
|
+
recaptchaSiteKey
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
`;
|
|
14
|
+
function o() {
|
|
15
|
+
let { productSlug: i } = e(), o = i?.trim(), { data: s, loading: c } = r(a, {
|
|
16
|
+
variables: { slug: o },
|
|
17
|
+
skip: !o,
|
|
18
|
+
fetchPolicy: "cache-first",
|
|
19
|
+
errorPolicy: "all"
|
|
20
|
+
});
|
|
21
|
+
if (!o) {
|
|
22
|
+
let e = t(), r = n() && !!e;
|
|
23
|
+
return {
|
|
24
|
+
enabled: r,
|
|
25
|
+
siteKey: r ? e : void 0,
|
|
26
|
+
loading: !1
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
let l = s?.publicProduct?.recaptchaSiteKey ?? void 0, u = l && l.trim().length > 0 ? l.trim() : void 0;
|
|
30
|
+
return {
|
|
31
|
+
enabled: !!u,
|
|
32
|
+
siteKey: u,
|
|
33
|
+
loading: c
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
//#endregion
|
|
37
|
+
export { o as useSignupCaptcha };
|
|
38
|
+
|
|
39
|
+
//# sourceMappingURL=useSignupCaptcha.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useSignupCaptcha.js","names":[],"sources":["../../../src/auth/hooks/useSignupCaptcha.ts"],"sourcesContent":["import { gql } from \"@apollo/client\";\nimport { useQuery } from \"@apollo/client/react\";\nimport { useAuth } from \"../providers/AuthProvider\";\nimport { getRecaptchaSiteKey, isCaptchaEnabled } from \"../utils/captchaConfig\";\n\n/**\n * Public, unauthenticated lookup of a product's reCAPTCHA v2 site key.\n *\n * The site key is intentionally public (it's embedded in the rendered widget);\n * the matching secret key lives only in the platform product catalog and is\n * verified server-side. `publicProduct` is `@rbac(public: true)`, so this runs\n * before the user has signed in.\n */\nconst PUBLIC_PRODUCT_CAPTCHA_QUERY = gql`\n query PublicProductCaptcha($slug: String!) {\n publicProduct(slug: $slug) {\n slug\n recaptchaSiteKey\n }\n }\n`;\n\ninterface PublicProductCaptchaData {\n publicProduct: {\n slug: string;\n recaptchaSiteKey: string | null;\n } | null;\n}\n\nexport interface SignupCaptchaState {\n /** Whether the reCAPTCHA v2 checkbox should be rendered + required on signup. */\n enabled: boolean;\n /** The reCAPTCHA v2 site key to render the widget with (undefined when disabled). */\n siteKey: string | undefined;\n /** True while the per-product config is still being fetched. */\n loading: boolean;\n}\n\n/**\n * Resolve whether the signup page should show the reCAPTCHA v2 checkbox, and\n * with which site key, for the current host product.\n *\n * Source of truth is the platform product catalog: a product opts into signup\n * captcha simply by having a `recaptchaSiteKey` configured (looked up here via\n * the host-provided `productSlug`). This keeps enablement per-product and\n * data-driven — no rebuild needed to turn it on/off for a product.\n *\n * Falls back to the build-time env (`VITE_CAPTCHA_ENABLED` +\n * `VITE_RECAPTCHA_SITE_KEY`) only for local source-dev where no `productSlug`\n * is provided, preserving the previous behavior.\n */\nexport function useSignupCaptcha(): SignupCaptchaState {\n const { productSlug } = useAuth();\n const slug = productSlug?.trim();\n\n const { data, loading } = useQuery<PublicProductCaptchaData>(\n PUBLIC_PRODUCT_CAPTCHA_QUERY,\n {\n variables: { slug },\n skip: !slug,\n // Site keys change rarely; serve from cache and only block once.\n fetchPolicy: \"cache-first\",\n // A lookup failure must never break the signup form — degrade to \"off\".\n errorPolicy: \"all\",\n },\n );\n\n if (!slug) {\n // Source-dev / hosts that don't pass a slug: honor the legacy env switch.\n const envSiteKey = getRecaptchaSiteKey();\n const envEnabled = isCaptchaEnabled() && Boolean(envSiteKey);\n return {\n enabled: envEnabled,\n siteKey: envEnabled ? envSiteKey : undefined,\n loading: false,\n };\n }\n\n const siteKey = data?.publicProduct?.recaptchaSiteKey ?? undefined;\n const trimmedKey =\n siteKey && siteKey.trim().length > 0 ? siteKey.trim() : undefined;\n\n return {\n enabled: Boolean(trimmedKey),\n siteKey: trimmedKey,\n loading,\n };\n}\n"],"mappings":";;;;;AAaA,IAAM,IAA+B,CAAG;;;;;;;;AAsCxC,SAAgB,IAAuC;CACrD,IAAM,EAAE,mBAAgB,GAAS,EAC3B,IAAO,GAAa,MAAM,EAE1B,EAAE,SAAM,eAAY,EACxB,GACA;EACE,WAAW,EAAE,SAAM;EACnB,MAAM,CAAC;EAEP,aAAa;EAEb,aAAa;EACd,CACF;AAED,KAAI,CAAC,GAAM;EAET,IAAM,IAAa,GAAqB,EAClC,IAAa,GAAkB,IAAI,EAAQ;AACjD,SAAO;GACL,SAAS;GACT,SAAS,IAAa,IAAa,KAAA;GACnC,SAAS;GACV;;CAGH,IAAM,IAAU,GAAM,eAAe,oBAAoB,KAAA,GACnD,IACJ,KAAW,EAAQ,MAAM,CAAC,SAAS,IAAI,EAAQ,MAAM,GAAG,KAAA;AAE1D,QAAO;EACL,SAAS,EAAQ;EACjB,SAAS;EACT;EACD"}
|
|
@@ -1,138 +1,138 @@
|
|
|
1
1
|
import { useAuth as e } from "../providers/AuthProvider.js";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import { useState as
|
|
10
|
-
import { useNavigate as
|
|
11
|
-
import { useI18n as
|
|
12
|
-
import { Fragment as
|
|
2
|
+
import { useAuthOperations as t } from "../hooks/useAuthOperations.js";
|
|
3
|
+
import { useOAuthOperations as n } from "../hooks/useOAuthOperations.js";
|
|
4
|
+
import { AuthLayout as r } from "../components/AuthLayout.js";
|
|
5
|
+
import { AlertIcon as i, CheckIcon as a, EmailIcon as o, EyeIcon as ee, EyeOffIcon as te, GitHubIcon as s, GoogleIcon as ne, LockIcon as re, MicrosoftIcon as c, SpinnerIcon as l, UserIcon as u, XIcon as d } from "../components/AuthIcons.js";
|
|
6
|
+
import { validateEmail as f, validatePassword as p } from "../utils/validation.js";
|
|
7
|
+
import { CaptchaV2 as m } from "../components/CaptchaV2.js";
|
|
8
|
+
import { useSignupCaptcha as ie } from "../hooks/useSignupCaptcha.js";
|
|
9
|
+
import { useState as h } from "react";
|
|
10
|
+
import { useNavigate as ae } from "react-router-dom";
|
|
11
|
+
import { useI18n as oe } from "@burdenoff/fe-libs/shared/providers/shell";
|
|
12
|
+
import { Fragment as se, jsx as g, jsxs as _ } from "react/jsx-runtime";
|
|
13
13
|
//#region src/auth/pages/RegisterPage.tsx
|
|
14
|
-
function
|
|
15
|
-
let { productName:
|
|
16
|
-
let n =
|
|
14
|
+
function v() {
|
|
15
|
+
let { productName: v } = e(), y = ae(), { t: b } = oe(), x = (e, t) => {
|
|
16
|
+
let n = b(e);
|
|
17
17
|
return n === e ? t : n;
|
|
18
|
-
}, { signUp:
|
|
18
|
+
}, { signUp: S, resendVerification: C, isLoading: w, errorCode: T } = t(), { initiateGoogleOAuth: ce, initiateMicrosoftOAuth: le, isProviderConfigured: E } = n(), [D, ue] = h(!1), [O, k] = h({
|
|
19
19
|
email: "",
|
|
20
20
|
username: "",
|
|
21
21
|
password: "",
|
|
22
22
|
firstName: "",
|
|
23
23
|
lastName: ""
|
|
24
|
-
}), [
|
|
25
|
-
let e =
|
|
24
|
+
}), [A, j] = h([]), [M, N] = h(!1), [P, F] = h(null), [I, L] = h(null), [R, z] = h(null), [B, V] = h(0), { enabled: H, siteKey: de } = ie(), [U, W] = h(!1), [G, K] = h(null), q = !!P && T === "ACCOUNT_DEACTIVATED", J = !!P && T === "ACCOUNT_REMOVED", Y = !!P && T === "ACCOUNT_SUSPENDED", X = (() => {
|
|
25
|
+
let e = O.password;
|
|
26
26
|
return [
|
|
27
27
|
{
|
|
28
|
-
label:
|
|
28
|
+
label: x("auth.register.pwMin8", "At least 12 characters"),
|
|
29
29
|
met: e.length >= 12
|
|
30
30
|
},
|
|
31
31
|
{
|
|
32
|
-
label:
|
|
32
|
+
label: x("auth.register.pwUppercase", "One uppercase letter"),
|
|
33
33
|
met: /[A-Z]/.test(e)
|
|
34
34
|
},
|
|
35
35
|
{
|
|
36
|
-
label:
|
|
36
|
+
label: x("auth.register.pwLowercase", "One lowercase letter"),
|
|
37
37
|
met: /[a-z]/.test(e)
|
|
38
38
|
},
|
|
39
39
|
{
|
|
40
|
-
label:
|
|
40
|
+
label: x("auth.register.pwNumber", "One number"),
|
|
41
41
|
met: /\d/.test(e)
|
|
42
42
|
},
|
|
43
43
|
{
|
|
44
|
-
label:
|
|
44
|
+
label: x("auth.register.pwSpecial", "One special character"),
|
|
45
45
|
met: /[!@#$%^&*(),.?":{}|<>]/.test(e)
|
|
46
46
|
}
|
|
47
47
|
];
|
|
48
48
|
})(), Z = (() => {
|
|
49
49
|
let e = X.filter((e) => e.met).length;
|
|
50
50
|
return e <= 2 ? {
|
|
51
|
-
level:
|
|
51
|
+
level: x("auth.register.passwordStrengthWeak", "weak"),
|
|
52
52
|
color: "bg-status-error-bg-subtle0",
|
|
53
53
|
width: "w-1/4"
|
|
54
54
|
} : e <= 3 ? {
|
|
55
|
-
level:
|
|
55
|
+
level: x("auth.register.passwordStrengthFair", "fair"),
|
|
56
56
|
color: "bg-status-warning-text",
|
|
57
57
|
width: "w-2/4"
|
|
58
58
|
} : e <= 4 ? {
|
|
59
|
-
level:
|
|
59
|
+
level: x("auth.register.passwordStrengthGood", "good"),
|
|
60
60
|
color: "bg-status-warning-bg-subtle0",
|
|
61
61
|
width: "w-3/4"
|
|
62
62
|
} : {
|
|
63
|
-
level:
|
|
63
|
+
level: x("auth.register.passwordStrengthStrong", "strong"),
|
|
64
64
|
color: "bg-status-success-text",
|
|
65
65
|
width: "w-full"
|
|
66
66
|
};
|
|
67
|
-
})(),
|
|
67
|
+
})(), fe = async (e) => {
|
|
68
68
|
e.preventDefault();
|
|
69
|
-
let
|
|
70
|
-
|
|
71
|
-
let
|
|
72
|
-
if (
|
|
73
|
-
|
|
69
|
+
let t = [];
|
|
70
|
+
f(O.email) || t.push(x("auth.register.invalidEmail", "Please enter a valid email address")), (!O.username || O.username.length < 3) && t.push(x("auth.register.usernameMin3", "Username must be at least 3 characters")), /^[a-zA-Z0-9_-]+$/.test(O.username) || t.push(x("auth.register.usernameChars", "Username can only contain letters, numbers, underscores, and hyphens"));
|
|
71
|
+
let n = p(O.password);
|
|
72
|
+
if (n.valid || t.push(...n.errors), H && !R && t.push(x("auth.register.completeCaptcha", "Please complete the reCAPTCHA verification")), t.length > 0) {
|
|
73
|
+
j(t);
|
|
74
74
|
return;
|
|
75
75
|
}
|
|
76
|
-
|
|
76
|
+
j([]), F(null);
|
|
77
77
|
try {
|
|
78
|
-
await
|
|
79
|
-
...
|
|
80
|
-
...
|
|
81
|
-
}),
|
|
78
|
+
await S({
|
|
79
|
+
...O,
|
|
80
|
+
...R ? { recaptchaToken: R } : {}
|
|
81
|
+
}), N(!0);
|
|
82
82
|
} catch (e) {
|
|
83
|
-
|
|
83
|
+
F(e instanceof Error ? e.message : x("auth.register.signUpFailed", "Sign up failed")), console.error("Sign up failed:", e), H && (z(null), V((e) => e + 1));
|
|
84
84
|
}
|
|
85
|
-
},
|
|
86
|
-
|
|
85
|
+
}, pe = () => {
|
|
86
|
+
L(null);
|
|
87
87
|
try {
|
|
88
|
-
|
|
88
|
+
ce();
|
|
89
89
|
} catch (e) {
|
|
90
|
-
|
|
90
|
+
L(e instanceof Error ? e.message : x("auth.register.googleSignUpFailed", "Failed to initiate Google sign up"));
|
|
91
91
|
}
|
|
92
|
-
},
|
|
93
|
-
|
|
92
|
+
}, me = () => {
|
|
93
|
+
L(null);
|
|
94
94
|
try {
|
|
95
|
-
|
|
95
|
+
le();
|
|
96
96
|
} catch (e) {
|
|
97
|
-
|
|
97
|
+
L(e instanceof Error ? e.message : x("auth.register.microsoftSignUpFailed", "Failed to initiate Microsoft sign up"));
|
|
98
98
|
}
|
|
99
|
-
}, Q =
|
|
99
|
+
}, Q = E("google"), $ = E("microsoft"), he = [
|
|
100
100
|
{
|
|
101
101
|
name: "Google",
|
|
102
|
-
icon:
|
|
103
|
-
onClick:
|
|
102
|
+
icon: ne,
|
|
103
|
+
onClick: pe,
|
|
104
104
|
disabled: !Q,
|
|
105
|
-
tooltip: Q ? void 0 :
|
|
105
|
+
tooltip: Q ? void 0 : x("auth.register.googleNotConfigured", "Google sign up is not configured")
|
|
106
106
|
},
|
|
107
107
|
{
|
|
108
108
|
name: "GitHub",
|
|
109
|
-
icon:
|
|
109
|
+
icon: s,
|
|
110
110
|
onClick: () => {},
|
|
111
111
|
disabled: !0,
|
|
112
|
-
tooltip:
|
|
112
|
+
tooltip: x("auth.register.githubNotConfigured", "GitHub sign up is not configured")
|
|
113
113
|
},
|
|
114
114
|
{
|
|
115
115
|
name: "Microsoft",
|
|
116
|
-
icon:
|
|
117
|
-
onClick:
|
|
116
|
+
icon: c,
|
|
117
|
+
onClick: me,
|
|
118
118
|
disabled: !$,
|
|
119
|
-
tooltip: $ ? void 0 :
|
|
119
|
+
tooltip: $ ? void 0 : x("auth.register.microsoftNotConfigured", "Microsoft sign up is not configured")
|
|
120
120
|
}
|
|
121
|
-
], [
|
|
122
|
-
return
|
|
123
|
-
title:
|
|
124
|
-
subtitle:
|
|
125
|
-
children: /* @__PURE__ */
|
|
121
|
+
], [ge, _e = ""] = x("auth.register.verificationEmailSentTo", "We've sent a verification email to {{email}}. Please check your inbox and click the verification link to activate your account.").split("{{email}}");
|
|
122
|
+
return M ? /* @__PURE__ */ g(r, {
|
|
123
|
+
title: x("auth.register.checkEmail", "Check your email"),
|
|
124
|
+
subtitle: x("auth.register.verificationLinkSent", "We've sent you a verification link"),
|
|
125
|
+
children: /* @__PURE__ */ _("div", {
|
|
126
126
|
className: "flex flex-col items-center justify-center py-6",
|
|
127
127
|
children: [
|
|
128
|
-
/* @__PURE__ */
|
|
128
|
+
/* @__PURE__ */ g("div", {
|
|
129
129
|
className: "size-20 rounded-full bg-status-success-bg-subtle flex items-center justify-center mb-6",
|
|
130
|
-
children: /* @__PURE__ */
|
|
130
|
+
children: /* @__PURE__ */ g("svg", {
|
|
131
131
|
className: "size-10 text-status-success-text",
|
|
132
132
|
fill: "none",
|
|
133
133
|
viewBox: "0 0 24 24",
|
|
134
134
|
stroke: "currentColor",
|
|
135
|
-
children: /* @__PURE__ */
|
|
135
|
+
children: /* @__PURE__ */ g("path", {
|
|
136
136
|
strokeLinecap: "round",
|
|
137
137
|
strokeLinejoin: "round",
|
|
138
138
|
strokeWidth: 2,
|
|
@@ -140,54 +140,54 @@ function b() {
|
|
|
140
140
|
})
|
|
141
141
|
})
|
|
142
142
|
}),
|
|
143
|
-
/* @__PURE__ */
|
|
143
|
+
/* @__PURE__ */ g("h3", {
|
|
144
144
|
className: "text-lg font-semibold text-[var(--color-text-primary)] mb-2",
|
|
145
|
-
children:
|
|
145
|
+
children: x("auth.register.accountCreated", "Account created successfully!")
|
|
146
146
|
}),
|
|
147
|
-
/* @__PURE__ */
|
|
147
|
+
/* @__PURE__ */ _("p", {
|
|
148
148
|
className: "text-center text-[var(--color-text-secondary)] mb-8 max-w-sm",
|
|
149
149
|
children: [
|
|
150
|
-
|
|
151
|
-
/* @__PURE__ */
|
|
150
|
+
ge,
|
|
151
|
+
/* @__PURE__ */ g("span", {
|
|
152
152
|
className: "font-medium text-[var(--color-text-primary)]",
|
|
153
|
-
children:
|
|
153
|
+
children: O.email
|
|
154
154
|
}),
|
|
155
|
-
|
|
155
|
+
_e
|
|
156
156
|
]
|
|
157
157
|
}),
|
|
158
|
-
/* @__PURE__ */
|
|
158
|
+
/* @__PURE__ */ _("div", {
|
|
159
159
|
className: "space-y-3 w-full",
|
|
160
160
|
children: [
|
|
161
|
-
/* @__PURE__ */
|
|
161
|
+
/* @__PURE__ */ g("button", {
|
|
162
162
|
type: "button",
|
|
163
|
-
onClick: () =>
|
|
163
|
+
onClick: () => y("/auth/login"),
|
|
164
164
|
className: "auth-primary-button w-full px-4 py-3 font-semibold",
|
|
165
|
-
children:
|
|
165
|
+
children: x("auth.register.continueToSignIn", "Continue to sign in")
|
|
166
166
|
}),
|
|
167
|
-
G && /* @__PURE__ */
|
|
167
|
+
G && /* @__PURE__ */ g("p", {
|
|
168
168
|
className: "text-center text-sm text-status-error-text",
|
|
169
169
|
children: G
|
|
170
170
|
}),
|
|
171
|
-
/* @__PURE__ */
|
|
171
|
+
/* @__PURE__ */ _("p", {
|
|
172
172
|
className: "text-center text-sm text-[var(--color-text-tertiary)]",
|
|
173
173
|
children: [
|
|
174
|
-
|
|
174
|
+
x("auth.register.didntReceiveEmail", "Didn't receive the email? Check your spam folder or"),
|
|
175
175
|
" ",
|
|
176
|
-
/* @__PURE__ */
|
|
176
|
+
/* @__PURE__ */ g("button", {
|
|
177
177
|
type: "button",
|
|
178
178
|
disabled: U,
|
|
179
179
|
onClick: async () => {
|
|
180
180
|
K(null), W(!0);
|
|
181
181
|
try {
|
|
182
|
-
await
|
|
182
|
+
await C(O.email);
|
|
183
183
|
} catch (e) {
|
|
184
|
-
K(e instanceof Error ? e.message :
|
|
184
|
+
K(e instanceof Error ? e.message : x("auth.register.resendFailed", "Failed to resend verification email"));
|
|
185
185
|
} finally {
|
|
186
186
|
W(!1);
|
|
187
187
|
}
|
|
188
188
|
},
|
|
189
189
|
className: "text-text-link hover:text-text-linkHover font-medium disabled:opacity-50 disabled:cursor-not-allowed",
|
|
190
|
-
children: U ?
|
|
190
|
+
children: U ? x("auth.register.resending", "resending...") : x("auth.register.tryAgain", "try again")
|
|
191
191
|
})
|
|
192
192
|
]
|
|
193
193
|
})
|
|
@@ -195,219 +195,219 @@ function b() {
|
|
|
195
195
|
})
|
|
196
196
|
]
|
|
197
197
|
})
|
|
198
|
-
}) : /* @__PURE__ */
|
|
199
|
-
title:
|
|
200
|
-
subtitle:
|
|
201
|
-
productName:
|
|
198
|
+
}) : /* @__PURE__ */ _(r, {
|
|
199
|
+
title: x("auth.register.createAccount", "Create your account"),
|
|
200
|
+
subtitle: x("auth.register.startJourney", "Start your journey with {{productName}}").replace("{{productName}}", v || "Burdenoff Workspaces"),
|
|
201
|
+
productName: v,
|
|
202
202
|
children: [
|
|
203
|
-
/* @__PURE__ */
|
|
204
|
-
onSubmit:
|
|
203
|
+
/* @__PURE__ */ _("form", {
|
|
204
|
+
onSubmit: fe,
|
|
205
205
|
className: "space-y-5",
|
|
206
206
|
children: [
|
|
207
|
-
q && /* @__PURE__ */
|
|
207
|
+
q && /* @__PURE__ */ _("div", {
|
|
208
208
|
className: "flex items-start gap-3 p-4 rounded-xl bg-status-warning-bg-subtle border border-status-warning-border",
|
|
209
|
-
children: [/* @__PURE__ */
|
|
209
|
+
children: [/* @__PURE__ */ g(i, { className: "size-5 text-status-warning-text flex-shrink-0 mt-0.5" }), /* @__PURE__ */ _("div", {
|
|
210
210
|
className: "space-y-2",
|
|
211
211
|
children: [
|
|
212
|
-
/* @__PURE__ */
|
|
212
|
+
/* @__PURE__ */ g("p", {
|
|
213
213
|
className: "text-sm font-medium text-status-warning-text",
|
|
214
|
-
children:
|
|
214
|
+
children: x("auth.register.accountDeactivated", "This email belongs to a deactivated account.")
|
|
215
215
|
}),
|
|
216
|
-
/* @__PURE__ */
|
|
216
|
+
/* @__PURE__ */ g("p", {
|
|
217
217
|
className: "text-xs text-status-warning-text/80",
|
|
218
|
-
children:
|
|
218
|
+
children: x("auth.register.accountDeactivatedHelp", "If this is your account, you can reactivate it instead of creating a new one.")
|
|
219
219
|
}),
|
|
220
|
-
/* @__PURE__ */
|
|
220
|
+
/* @__PURE__ */ g("button", {
|
|
221
221
|
type: "button",
|
|
222
|
-
onClick: () =>
|
|
222
|
+
onClick: () => y("/auth/reactivate", { state: { email: O.email } }),
|
|
223
223
|
className: "text-sm font-semibold text-status-warning-text underline underline-offset-2",
|
|
224
|
-
children:
|
|
224
|
+
children: x("auth.register.reactivateCta", "Reactivate my account")
|
|
225
225
|
})
|
|
226
226
|
]
|
|
227
227
|
})]
|
|
228
228
|
}),
|
|
229
|
-
(J || Y) && /* @__PURE__ */
|
|
229
|
+
(J || Y) && /* @__PURE__ */ _("div", {
|
|
230
230
|
className: "flex items-start gap-3 p-4 rounded-xl bg-status-warning-bg-subtle border border-status-warning-border",
|
|
231
|
-
children: [/* @__PURE__ */
|
|
231
|
+
children: [/* @__PURE__ */ g(i, { className: "size-5 text-status-warning-text flex-shrink-0 mt-0.5" }), /* @__PURE__ */ g("p", {
|
|
232
232
|
className: "text-sm text-status-warning-text",
|
|
233
|
-
children:
|
|
233
|
+
children: P
|
|
234
234
|
})]
|
|
235
235
|
}),
|
|
236
|
-
(
|
|
236
|
+
(P && !q && !J && !Y || A.length > 0) && /* @__PURE__ */ _("div", {
|
|
237
237
|
className: "flex items-start gap-3 p-4 rounded-xl bg-status-error-bg-subtle border border-status-error-border",
|
|
238
|
-
children: [/* @__PURE__ */
|
|
238
|
+
children: [/* @__PURE__ */ g(i, { className: "size-5 text-status-error-text flex-shrink-0 mt-0.5" }), /* @__PURE__ */ _("div", {
|
|
239
239
|
className: "space-y-1",
|
|
240
|
-
children: [
|
|
240
|
+
children: [P && !q && !J && !Y && /* @__PURE__ */ g("p", {
|
|
241
241
|
className: "text-sm text-status-error-text",
|
|
242
|
-
children:
|
|
243
|
-
}),
|
|
242
|
+
children: P
|
|
243
|
+
}), A.map((e) => /* @__PURE__ */ g("p", {
|
|
244
244
|
className: "text-sm text-status-error-text",
|
|
245
245
|
children: e
|
|
246
246
|
}, e))]
|
|
247
247
|
})]
|
|
248
248
|
}),
|
|
249
|
-
/* @__PURE__ */
|
|
249
|
+
/* @__PURE__ */ _("div", {
|
|
250
250
|
className: "grid grid-cols-2 gap-4",
|
|
251
|
-
children: [/* @__PURE__ */
|
|
251
|
+
children: [/* @__PURE__ */ _("div", {
|
|
252
252
|
className: "space-y-2",
|
|
253
|
-
children: [/* @__PURE__ */
|
|
253
|
+
children: [/* @__PURE__ */ g("label", {
|
|
254
254
|
htmlFor: "firstName",
|
|
255
255
|
className: "block text-sm font-medium text-[var(--color-text-primary)]",
|
|
256
|
-
children:
|
|
257
|
-
}), /* @__PURE__ */
|
|
256
|
+
children: x("auth.register.firstName", "First Name")
|
|
257
|
+
}), /* @__PURE__ */ g("input", {
|
|
258
258
|
id: "firstName",
|
|
259
259
|
type: "text",
|
|
260
260
|
required: !0,
|
|
261
261
|
"aria-required": "true",
|
|
262
|
-
"aria-label":
|
|
263
|
-
value:
|
|
264
|
-
onChange: (e) =>
|
|
265
|
-
...
|
|
262
|
+
"aria-label": x("auth.register.firstName", "First Name"),
|
|
263
|
+
value: O.firstName,
|
|
264
|
+
onChange: (e) => k({
|
|
265
|
+
...O,
|
|
266
266
|
firstName: e.target.value
|
|
267
267
|
}),
|
|
268
268
|
className: "block w-full px-4 py-3 rounded-xl border border-[var(--color-border-default)] bg-[var(--color-bg-surface)] text-[var(--color-text-primary)] placeholder:text-[var(--color-text-tertiary)] focus:outline-none focus:ring-2 focus:ring-action-primary-bg/20 focus:border-action-primary-bg transition-all",
|
|
269
|
-
placeholder:
|
|
269
|
+
placeholder: x("auth.register.firstNamePlaceholder", "Enter first name")
|
|
270
270
|
})]
|
|
271
|
-
}), /* @__PURE__ */
|
|
271
|
+
}), /* @__PURE__ */ _("div", {
|
|
272
272
|
className: "space-y-2",
|
|
273
|
-
children: [/* @__PURE__ */
|
|
273
|
+
children: [/* @__PURE__ */ g("label", {
|
|
274
274
|
htmlFor: "lastName",
|
|
275
275
|
className: "block text-sm font-medium text-[var(--color-text-primary)]",
|
|
276
|
-
children:
|
|
277
|
-
}), /* @__PURE__ */
|
|
276
|
+
children: x("auth.register.lastName", "Last Name")
|
|
277
|
+
}), /* @__PURE__ */ g("input", {
|
|
278
278
|
id: "lastName",
|
|
279
279
|
type: "text",
|
|
280
280
|
required: !0,
|
|
281
281
|
"aria-required": "true",
|
|
282
|
-
"aria-label":
|
|
283
|
-
value:
|
|
284
|
-
onChange: (e) =>
|
|
285
|
-
...
|
|
282
|
+
"aria-label": x("auth.register.lastName", "Last Name"),
|
|
283
|
+
value: O.lastName,
|
|
284
|
+
onChange: (e) => k({
|
|
285
|
+
...O,
|
|
286
286
|
lastName: e.target.value
|
|
287
287
|
}),
|
|
288
288
|
className: "block w-full px-4 py-3 rounded-xl border border-[var(--color-border-default)] bg-[var(--color-bg-surface)] text-[var(--color-text-primary)] placeholder:text-[var(--color-text-tertiary)] focus:outline-none focus:ring-2 focus:ring-action-primary-bg/20 focus:border-action-primary-bg transition-all",
|
|
289
|
-
placeholder:
|
|
289
|
+
placeholder: x("auth.register.lastNamePlaceholder", "Enter last name")
|
|
290
290
|
})]
|
|
291
291
|
})]
|
|
292
292
|
}),
|
|
293
|
-
/* @__PURE__ */
|
|
293
|
+
/* @__PURE__ */ _("div", {
|
|
294
294
|
className: "space-y-2",
|
|
295
|
-
children: [/* @__PURE__ */
|
|
295
|
+
children: [/* @__PURE__ */ g("label", {
|
|
296
296
|
htmlFor: "username",
|
|
297
297
|
className: "block text-sm font-medium text-[var(--color-text-primary)]",
|
|
298
|
-
children:
|
|
299
|
-
}), /* @__PURE__ */
|
|
298
|
+
children: x("auth.register.username", "Username")
|
|
299
|
+
}), /* @__PURE__ */ _("div", {
|
|
300
300
|
className: "relative",
|
|
301
|
-
children: [/* @__PURE__ */
|
|
301
|
+
children: [/* @__PURE__ */ g("div", {
|
|
302
302
|
className: "absolute inset-y-0 left-0 pl-3.5 flex items-center pointer-events-none",
|
|
303
|
-
children: /* @__PURE__ */
|
|
304
|
-
}), /* @__PURE__ */
|
|
303
|
+
children: /* @__PURE__ */ g(u, { className: "size-5 text-[var(--color-text-tertiary)]" })
|
|
304
|
+
}), /* @__PURE__ */ g("input", {
|
|
305
305
|
id: "username",
|
|
306
306
|
type: "text",
|
|
307
307
|
required: !0,
|
|
308
308
|
"aria-required": "true",
|
|
309
|
-
"aria-label":
|
|
309
|
+
"aria-label": x("auth.register.username", "Username"),
|
|
310
310
|
autoComplete: "username",
|
|
311
|
-
value:
|
|
312
|
-
onChange: (e) =>
|
|
313
|
-
...
|
|
311
|
+
value: O.username,
|
|
312
|
+
onChange: (e) => k({
|
|
313
|
+
...O,
|
|
314
314
|
username: e.target.value
|
|
315
315
|
}),
|
|
316
316
|
className: "block w-full pl-[3.5rem] pr-4 py-3 rounded-xl border border-[var(--color-border-default)] bg-[var(--color-bg-surface)] text-[var(--color-text-primary)] placeholder:text-[var(--color-text-tertiary)] focus:outline-none focus:ring-2 focus:ring-action-primary-bg/20 focus:border-action-primary-bg transition-all",
|
|
317
|
-
placeholder:
|
|
317
|
+
placeholder: x("auth.register.usernamePlaceholder", "Choose a username")
|
|
318
318
|
})]
|
|
319
319
|
})]
|
|
320
320
|
}),
|
|
321
|
-
/* @__PURE__ */
|
|
321
|
+
/* @__PURE__ */ _("div", {
|
|
322
322
|
className: "space-y-2",
|
|
323
|
-
children: [/* @__PURE__ */
|
|
323
|
+
children: [/* @__PURE__ */ g("label", {
|
|
324
324
|
htmlFor: "email",
|
|
325
325
|
className: "block text-sm font-medium text-[var(--color-text-primary)]",
|
|
326
|
-
children:
|
|
327
|
-
}), /* @__PURE__ */
|
|
326
|
+
children: x("auth.register.email", "Email")
|
|
327
|
+
}), /* @__PURE__ */ _("div", {
|
|
328
328
|
className: "relative",
|
|
329
|
-
children: [/* @__PURE__ */
|
|
329
|
+
children: [/* @__PURE__ */ g("div", {
|
|
330
330
|
className: "absolute inset-y-0 left-0 pl-3.5 flex items-center pointer-events-none",
|
|
331
|
-
children: /* @__PURE__ */
|
|
332
|
-
}), /* @__PURE__ */
|
|
331
|
+
children: /* @__PURE__ */ g(o, { className: "size-5 text-[var(--color-text-tertiary)]" })
|
|
332
|
+
}), /* @__PURE__ */ g("input", {
|
|
333
333
|
id: "email",
|
|
334
334
|
type: "email",
|
|
335
335
|
required: !0,
|
|
336
336
|
"aria-required": "true",
|
|
337
|
-
"aria-label":
|
|
337
|
+
"aria-label": x("auth.register.email", "Email"),
|
|
338
338
|
autoComplete: "email",
|
|
339
|
-
value:
|
|
340
|
-
onChange: (e) =>
|
|
341
|
-
...
|
|
339
|
+
value: O.email,
|
|
340
|
+
onChange: (e) => k({
|
|
341
|
+
...O,
|
|
342
342
|
email: e.target.value
|
|
343
343
|
}),
|
|
344
344
|
className: "block w-full pl-[3.5rem] pr-4 py-3 rounded-xl border border-[var(--color-border-default)] bg-[var(--color-bg-surface)] text-[var(--color-text-primary)] placeholder:text-[var(--color-text-tertiary)] focus:outline-none focus:ring-2 focus:ring-action-primary-bg/20 focus:border-action-primary-bg transition-all",
|
|
345
|
-
placeholder:
|
|
345
|
+
placeholder: x("auth.register.emailPlaceholder", "you@example.com")
|
|
346
346
|
})]
|
|
347
347
|
})]
|
|
348
348
|
}),
|
|
349
|
-
/* @__PURE__ */
|
|
349
|
+
/* @__PURE__ */ _("div", {
|
|
350
350
|
className: "space-y-2",
|
|
351
351
|
children: [
|
|
352
|
-
/* @__PURE__ */
|
|
352
|
+
/* @__PURE__ */ g("label", {
|
|
353
353
|
htmlFor: "password",
|
|
354
354
|
className: "block text-sm font-medium text-[var(--color-text-primary)]",
|
|
355
|
-
children:
|
|
355
|
+
children: x("auth.register.password", "Password")
|
|
356
356
|
}),
|
|
357
|
-
/* @__PURE__ */
|
|
357
|
+
/* @__PURE__ */ _("div", {
|
|
358
358
|
className: "relative",
|
|
359
359
|
children: [
|
|
360
|
-
/* @__PURE__ */
|
|
360
|
+
/* @__PURE__ */ g("div", {
|
|
361
361
|
className: "absolute inset-y-0 left-0 pl-3.5 flex items-center pointer-events-none",
|
|
362
|
-
children: /* @__PURE__ */
|
|
362
|
+
children: /* @__PURE__ */ g(re, { className: "size-5 text-[var(--color-text-tertiary)]" })
|
|
363
363
|
}),
|
|
364
|
-
/* @__PURE__ */
|
|
364
|
+
/* @__PURE__ */ g("input", {
|
|
365
365
|
id: "password",
|
|
366
|
-
type:
|
|
366
|
+
type: D ? "text" : "password",
|
|
367
367
|
required: !0,
|
|
368
368
|
"aria-required": "true",
|
|
369
|
-
"aria-label":
|
|
369
|
+
"aria-label": x("auth.register.password", "Password"),
|
|
370
370
|
autoComplete: "new-password",
|
|
371
|
-
value:
|
|
372
|
-
onChange: (e) =>
|
|
373
|
-
...
|
|
371
|
+
value: O.password,
|
|
372
|
+
onChange: (e) => k({
|
|
373
|
+
...O,
|
|
374
374
|
password: e.target.value
|
|
375
375
|
}),
|
|
376
376
|
className: "block w-full pl-[3.5rem] pr-12 py-3 rounded-xl border border-[var(--color-border-default)] bg-[var(--color-bg-surface)] text-[var(--color-text-primary)] placeholder:text-[var(--color-text-tertiary)] focus:outline-none focus:ring-2 focus:ring-action-primary-bg/20 focus:border-action-primary-bg transition-all",
|
|
377
|
-
placeholder:
|
|
377
|
+
placeholder: x("auth.register.passwordPlaceholder", "Create a strong password")
|
|
378
378
|
}),
|
|
379
|
-
/* @__PURE__ */
|
|
379
|
+
/* @__PURE__ */ g("button", {
|
|
380
380
|
type: "button",
|
|
381
|
-
onClick: () =>
|
|
382
|
-
"aria-label":
|
|
381
|
+
onClick: () => ue(!D),
|
|
382
|
+
"aria-label": D ? "Hide password" : "Show password",
|
|
383
383
|
className: "absolute inset-y-0 right-0 pr-3.5 flex items-center text-[var(--color-text-tertiary)] hover:text-[var(--color-text-secondary)] transition-colors",
|
|
384
|
-
children:
|
|
384
|
+
children: g(D ? te : ee, { className: "size-5" })
|
|
385
385
|
})
|
|
386
386
|
]
|
|
387
387
|
}),
|
|
388
|
-
|
|
388
|
+
O.password && /* @__PURE__ */ _("div", {
|
|
389
389
|
className: "space-y-3 pt-2",
|
|
390
|
-
children: [/* @__PURE__ */
|
|
390
|
+
children: [/* @__PURE__ */ _("div", {
|
|
391
391
|
className: "space-y-1",
|
|
392
|
-
children: [/* @__PURE__ */
|
|
392
|
+
children: [/* @__PURE__ */ g("div", {
|
|
393
393
|
className: "h-1.5 bg-[var(--color-bg-sunken)] rounded-full overflow-hidden",
|
|
394
|
-
children: /* @__PURE__ */
|
|
395
|
-
}), /* @__PURE__ */
|
|
394
|
+
children: /* @__PURE__ */ g("div", { className: `h-full ${Z.color} ${Z.width} transition-all duration-300` })
|
|
395
|
+
}), /* @__PURE__ */ _("p", {
|
|
396
396
|
className: "text-xs text-[var(--color-text-tertiary)] capitalize",
|
|
397
397
|
children: [
|
|
398
|
-
|
|
398
|
+
x("auth.register.passwordStrength", "Password strength:"),
|
|
399
399
|
" ",
|
|
400
|
-
/* @__PURE__ */
|
|
400
|
+
/* @__PURE__ */ g("span", {
|
|
401
401
|
className: "font-medium",
|
|
402
402
|
children: Z.level
|
|
403
403
|
})
|
|
404
404
|
]
|
|
405
405
|
})]
|
|
406
|
-
}), /* @__PURE__ */
|
|
406
|
+
}), /* @__PURE__ */ g("div", {
|
|
407
407
|
className: "grid grid-cols-2 gap-2",
|
|
408
|
-
children: X.map((e) => /* @__PURE__ */
|
|
408
|
+
children: X.map((e) => /* @__PURE__ */ _("div", {
|
|
409
409
|
className: "flex items-center gap-2",
|
|
410
|
-
children: [e.met ? /* @__PURE__ */
|
|
410
|
+
children: [e.met ? /* @__PURE__ */ g(a, { className: "size-4 text-status-success-text" }) : /* @__PURE__ */ g(d, { className: "size-4 text-[var(--color-text-tertiary)]" }), /* @__PURE__ */ g("span", {
|
|
411
411
|
className: `text-xs ${e.met ? "text-status-success-text" : "text-[var(--color-text-tertiary)]"}`,
|
|
412
412
|
children: e.label
|
|
413
413
|
})]
|
|
@@ -416,80 +416,85 @@ function b() {
|
|
|
416
416
|
})
|
|
417
417
|
]
|
|
418
418
|
}),
|
|
419
|
-
/* @__PURE__ */
|
|
419
|
+
/* @__PURE__ */ _("p", {
|
|
420
420
|
className: "text-xs text-[var(--color-text-tertiary)] leading-relaxed",
|
|
421
421
|
children: [
|
|
422
|
-
|
|
422
|
+
x("auth.register.agreeToTerms", "By creating an account, you agree to our"),
|
|
423
423
|
" ",
|
|
424
|
-
/* @__PURE__ */
|
|
424
|
+
/* @__PURE__ */ g("a", {
|
|
425
425
|
href: "/legal/terms",
|
|
426
426
|
className: "text-text-link hover:underline",
|
|
427
|
-
children:
|
|
427
|
+
children: x("auth.register.termsOfService", "Terms of Service")
|
|
428
428
|
}),
|
|
429
429
|
" ",
|
|
430
|
-
|
|
430
|
+
x("auth.register.and", "and"),
|
|
431
431
|
" ",
|
|
432
|
-
/* @__PURE__ */
|
|
432
|
+
/* @__PURE__ */ g("a", {
|
|
433
433
|
href: "/legal/privacy",
|
|
434
434
|
className: "text-text-link hover:underline",
|
|
435
|
-
children:
|
|
435
|
+
children: x("auth.register.privacyPolicy", "Privacy Policy")
|
|
436
436
|
}),
|
|
437
437
|
"."
|
|
438
438
|
]
|
|
439
439
|
}),
|
|
440
|
-
/* @__PURE__ */
|
|
441
|
-
|
|
440
|
+
/* @__PURE__ */ g(m, {
|
|
441
|
+
enabled: H,
|
|
442
|
+
siteKey: de,
|
|
443
|
+
onVerify: z,
|
|
444
|
+
onExpired: () => z(null)
|
|
445
|
+
}, B),
|
|
446
|
+
/* @__PURE__ */ g("button", {
|
|
442
447
|
type: "submit",
|
|
443
|
-
disabled:
|
|
448
|
+
disabled: w,
|
|
444
449
|
className: "auth-primary-button flex w-full items-center justify-center gap-2 px-4 py-3 font-semibold",
|
|
445
|
-
children:
|
|
450
|
+
children: w ? /* @__PURE__ */ _(se, { children: [/* @__PURE__ */ g(l, { className: "size-5 animate-spin" }), /* @__PURE__ */ g("span", { children: x("auth.register.creatingAccount", "Creating account...") })] }) : x("auth.register.createAccountBtn", "Create account")
|
|
446
451
|
})
|
|
447
452
|
]
|
|
448
453
|
}),
|
|
449
|
-
/* @__PURE__ */
|
|
454
|
+
/* @__PURE__ */ _("div", {
|
|
450
455
|
className: "relative my-8",
|
|
451
|
-
children: [/* @__PURE__ */
|
|
456
|
+
children: [/* @__PURE__ */ g("div", {
|
|
452
457
|
className: "absolute inset-0 flex items-center",
|
|
453
|
-
children: /* @__PURE__ */
|
|
454
|
-
}), /* @__PURE__ */
|
|
458
|
+
children: /* @__PURE__ */ g("div", { className: "w-full border-t border-[var(--color-border-default)]" })
|
|
459
|
+
}), /* @__PURE__ */ g("div", {
|
|
455
460
|
className: "relative flex justify-center",
|
|
456
|
-
children: /* @__PURE__ */
|
|
461
|
+
children: /* @__PURE__ */ g("span", {
|
|
457
462
|
className: "px-4 bg-[var(--color-bg-canvas)] text-sm text-[var(--color-text-tertiary)]",
|
|
458
|
-
children:
|
|
463
|
+
children: x("auth.register.orSignUpWith", "or sign up with")
|
|
459
464
|
})
|
|
460
465
|
})]
|
|
461
466
|
}),
|
|
462
|
-
|
|
467
|
+
I && /* @__PURE__ */ _("div", {
|
|
463
468
|
className: "flex items-start gap-3 p-4 rounded-xl border bg-status-error-bg-subtle border-status-error-border",
|
|
464
|
-
children: [/* @__PURE__ */
|
|
469
|
+
children: [/* @__PURE__ */ g(i, { className: "size-5 flex-shrink-0 mt-0.5 text-status-error-text" }), /* @__PURE__ */ g("p", {
|
|
465
470
|
className: "text-sm text-status-error-text",
|
|
466
|
-
children:
|
|
471
|
+
children: I
|
|
467
472
|
})]
|
|
468
473
|
}),
|
|
469
|
-
/* @__PURE__ */
|
|
474
|
+
/* @__PURE__ */ g("div", {
|
|
470
475
|
className: "grid grid-cols-3 gap-3",
|
|
471
|
-
children:
|
|
476
|
+
children: he.map((e) => /* @__PURE__ */ _("button", {
|
|
472
477
|
type: "button",
|
|
473
478
|
onClick: e.onClick,
|
|
474
479
|
disabled: e.disabled,
|
|
475
480
|
title: e.tooltip,
|
|
476
481
|
className: `flex items-center justify-center gap-2 px-4 py-3 rounded-xl border transition-all duration-200 group ${e.disabled ? "border-[var(--color-border-subtle)] bg-[var(--color-bg-disabled)] cursor-not-allowed opacity-50" : "border-[var(--color-border-default)] bg-[var(--color-bg-surface)] hover:bg-[var(--color-bg-sunken)] hover:border-[var(--color-border-strong)]"}`,
|
|
477
|
-
children: [/* @__PURE__ */
|
|
482
|
+
children: [/* @__PURE__ */ g(e.icon, { className: `size-5 ${e.disabled ? "opacity-50" : ""}` }), /* @__PURE__ */ g("span", {
|
|
478
483
|
className: `sr-only sm:not-sr-only text-sm font-medium transition-colors hidden sm:inline ${e.disabled ? "text-[var(--color-text-disabled)]" : "text-[var(--color-text-secondary)] group-hover:text-[var(--color-text-primary)]"}`,
|
|
479
484
|
children: e.name
|
|
480
485
|
})]
|
|
481
486
|
}, e.name))
|
|
482
487
|
}),
|
|
483
|
-
/* @__PURE__ */
|
|
488
|
+
/* @__PURE__ */ _("p", {
|
|
484
489
|
className: "mt-8 text-center text-sm text-[var(--color-text-secondary)]",
|
|
485
490
|
children: [
|
|
486
|
-
|
|
491
|
+
x("auth.register.alreadyHaveAccount", "Already have an account?"),
|
|
487
492
|
" ",
|
|
488
|
-
/* @__PURE__ */
|
|
493
|
+
/* @__PURE__ */ g("button", {
|
|
489
494
|
type: "button",
|
|
490
|
-
onClick: () =>
|
|
495
|
+
onClick: () => y("/auth/login"),
|
|
491
496
|
className: "font-semibold text-text-link hover:text-text-linkHover transition-colors",
|
|
492
|
-
children:
|
|
497
|
+
children: x("auth.register.signIn", "Sign in")
|
|
493
498
|
})
|
|
494
499
|
]
|
|
495
500
|
})
|
|
@@ -497,6 +502,6 @@ function b() {
|
|
|
497
502
|
});
|
|
498
503
|
}
|
|
499
504
|
//#endregion
|
|
500
|
-
export {
|
|
505
|
+
export { v as RegisterPage };
|
|
501
506
|
|
|
502
507
|
//# sourceMappingURL=RegisterPage.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RegisterPage.js","names":[],"sources":["../../../src/auth/pages/RegisterPage.tsx"],"sourcesContent":["import { useState } from \"react\";\nimport { useNavigate } from \"react-router-dom\";\nimport { useI18n } from \"@burdenoff/fe-libs/shared/providers/shell\";\nimport { validateEmail, validatePassword } from \"../utils/validation\";\nimport { useAuthOperations } from \"../hooks/useAuthOperations\";\nimport { useOAuthOperations } from \"../hooks/useOAuthOperations\";\nimport { AuthLayout } from \"../components/AuthLayout\";\nimport {\n EmailIcon,\n LockIcon,\n UserIcon,\n EyeIcon,\n EyeOffIcon,\n AlertIcon,\n CheckIcon,\n XIcon,\n SpinnerIcon,\n GoogleIcon,\n GitHubIcon,\n MicrosoftIcon,\n} from \"../components/AuthIcons\";\nimport { useAuth } from \"../providers/AuthProvider\";\nimport { CaptchaV2 } from \"../components/CaptchaV2\";\nimport { isCaptchaEnabled } from \"../utils/captchaConfig\";\nimport type { SignUpInput } from \"../types\";\n\n/**\n * Registration page with professional design\n */\nexport function RegisterPage() {\n const { productName } = useAuth();\n const navigate = useNavigate();\n const { t } = useI18n();\n const tr = (key: string, fallback: string): string => {\n const translated = t(key);\n return translated === key ? fallback : translated;\n };\n const { signUp, resendVerification, isLoading, errorCode } =\n useAuthOperations();\n const { initiateGoogleOAuth, initiateMicrosoftOAuth, isProviderConfigured } =\n useOAuthOperations();\n const [showPassword, setShowPassword] = useState(false);\n const [formData, setFormData] = useState<SignUpInput>({\n email: \"\",\n username: \"\",\n password: \"\",\n firstName: \"\",\n lastName: \"\",\n });\n const [validationErrors, setValidationErrors] = useState<string[]>([]);\n const [signupSuccess, setSignupSuccess] = useState(false);\n const [error, setError] = useState<string | null>(null);\n const [oauthError, setOauthError] = useState<string | null>(null);\n const [captchaToken, setCaptchaToken] = useState<string | null>(null);\n const [isResendingVerification, setIsResendingVerification] = useState(false);\n const [resendError, setResendError] = useState<string | null>(null);\n\n // Signing up with an email that already belongs to a non-active account:\n // route the user to the right recovery path instead of dead-ending.\n const isAccountDeactivated =\n Boolean(error) && errorCode === \"ACCOUNT_DEACTIVATED\";\n const isAccountRemoved = Boolean(error) && errorCode === \"ACCOUNT_REMOVED\";\n const isAccountSuspended =\n Boolean(error) && errorCode === \"ACCOUNT_SUSPENDED\";\n\n // Password strength indicators\n const passwordChecks = (() => {\n const password = formData.password;\n return [\n {\n label: tr(\"auth.register.pwMin8\", \"At least 12 characters\"),\n met: password.length >= 12,\n },\n {\n label: tr(\"auth.register.pwUppercase\", \"One uppercase letter\"),\n met: /[A-Z]/.test(password),\n },\n {\n label: tr(\"auth.register.pwLowercase\", \"One lowercase letter\"),\n met: /[a-z]/.test(password),\n },\n {\n label: tr(\"auth.register.pwNumber\", \"One number\"),\n met: /\\d/.test(password),\n },\n {\n label: tr(\"auth.register.pwSpecial\", \"One special character\"),\n met: /[!@#$%^&*(),.?\":{}|<>]/.test(password),\n },\n ];\n })();\n\n const passwordStrength = (() => {\n const metCount = passwordChecks.filter((c) => c.met).length;\n if (metCount <= 2)\n return {\n level: tr(\"auth.register.passwordStrengthWeak\", \"weak\"),\n color: \"bg-status-error-bg-subtle0\",\n width: \"w-1/4\",\n };\n if (metCount <= 3)\n return {\n level: tr(\"auth.register.passwordStrengthFair\", \"fair\"),\n color: \"bg-status-warning-text\",\n width: \"w-2/4\",\n };\n if (metCount <= 4)\n return {\n level: tr(\"auth.register.passwordStrengthGood\", \"good\"),\n color: \"bg-status-warning-bg-subtle0\",\n width: \"w-3/4\",\n };\n return {\n level: tr(\"auth.register.passwordStrengthStrong\", \"strong\"),\n color: \"bg-status-success-text\",\n width: \"w-full\",\n };\n })();\n\n const handleSubmit = async (e: React.FormEvent) => {\n e.preventDefault();\n\n const errors: string[] = [];\n if (!validateEmail(formData.email)) {\n errors.push(\n tr(\"auth.register.invalidEmail\", \"Please enter a valid email address\"),\n );\n }\n if (!formData.username || formData.username.length < 3) {\n errors.push(\n tr(\n \"auth.register.usernameMin3\",\n \"Username must be at least 3 characters\",\n ),\n );\n }\n if (!/^[a-zA-Z0-9_-]+$/.test(formData.username)) {\n errors.push(\n tr(\n \"auth.register.usernameChars\",\n \"Username can only contain letters, numbers, underscores, and hyphens\",\n ),\n );\n }\n const passwordValidation = validatePassword(formData.password);\n if (!passwordValidation.valid) {\n errors.push(...passwordValidation.errors);\n }\n\n if (isCaptchaEnabled() && !captchaToken) {\n errors.push(\n tr(\n \"auth.register.completeCaptcha\",\n \"Please complete the reCAPTCHA verification\",\n ),\n );\n }\n\n if (errors.length > 0) {\n setValidationErrors(errors);\n return;\n }\n\n setValidationErrors([]);\n setError(null);\n try {\n await signUp({\n ...formData,\n ...(captchaToken ? { recaptchaToken: captchaToken } : {}),\n });\n // Sign up successful - check if email verification is required\n // (Backend returns empty tokens when email verification is needed)\n setSignupSuccess(true);\n } catch (err) {\n const message =\n err instanceof Error\n ? err.message\n : tr(\"auth.register.signUpFailed\", \"Sign up failed\");\n setError(message);\n console.error(\"Sign up failed:\", err);\n }\n };\n\n /**\n * Handle Google OAuth signup\n */\n const handleGoogleSignup = () => {\n setOauthError(null);\n try {\n initiateGoogleOAuth();\n } catch (err) {\n setOauthError(\n err instanceof Error\n ? err.message\n : tr(\n \"auth.register.googleSignUpFailed\",\n \"Failed to initiate Google sign up\",\n ),\n );\n }\n };\n\n /**\n * Handle Microsoft OAuth signup\n */\n const handleMicrosoftSignup = () => {\n setOauthError(null);\n try {\n initiateMicrosoftOAuth();\n } catch (err) {\n setOauthError(\n err instanceof Error\n ? err.message\n : tr(\n \"auth.register.microsoftSignUpFailed\",\n \"Failed to initiate Microsoft sign up\",\n ),\n );\n }\n };\n\n // Check if OAuth providers are configured\n const isGoogleConfigured = isProviderConfigured(\"google\");\n const isMicrosoftConfigured = isProviderConfigured(\"microsoft\");\n\n const oauthProviders = [\n {\n name: \"Google\",\n icon: GoogleIcon,\n onClick: handleGoogleSignup,\n disabled: !isGoogleConfigured,\n tooltip: isGoogleConfigured\n ? undefined\n : tr(\n \"auth.register.googleNotConfigured\",\n \"Google sign up is not configured\",\n ),\n },\n {\n name: \"GitHub\",\n icon: GitHubIcon,\n onClick: () => {},\n disabled: true,\n tooltip: tr(\n \"auth.register.githubNotConfigured\",\n \"GitHub sign up is not configured\",\n ),\n },\n {\n name: \"Microsoft\",\n icon: MicrosoftIcon,\n onClick: handleMicrosoftSignup,\n disabled: !isMicrosoftConfigured,\n tooltip: isMicrosoftConfigured\n ? undefined\n : tr(\n \"auth.register.microsoftNotConfigured\",\n \"Microsoft sign up is not configured\",\n ),\n },\n ];\n\n const verificationEmailMessage = tr(\n \"auth.register.verificationEmailSentTo\",\n \"We've sent a verification email to {{email}}. Please check your inbox and click the verification link to activate your account.\",\n );\n const [verificationEmailPrefix, verificationEmailSuffix = \"\"] =\n verificationEmailMessage.split(\"{{email}}\");\n\n // Show success state after signup\n if (signupSuccess) {\n return (\n <AuthLayout\n title={tr(\"auth.register.checkEmail\", \"Check your email\")}\n subtitle={tr(\n \"auth.register.verificationLinkSent\",\n \"We've sent you a verification link\",\n )}\n >\n <div className=\"flex flex-col items-center justify-center py-6\">\n {/* Success Icon */}\n <div className=\"size-20 rounded-full bg-status-success-bg-subtle flex items-center justify-center mb-6\">\n <svg\n className=\"size-10 text-status-success-text\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z\"\n />\n </svg>\n </div>\n\n <h3 className=\"text-lg font-semibold text-[var(--color-text-primary)] mb-2\">\n {tr(\n \"auth.register.accountCreated\",\n \"Account created successfully!\",\n )}\n </h3>\n\n <p className=\"text-center text-[var(--color-text-secondary)] mb-8 max-w-sm\">\n {verificationEmailPrefix}\n <span className=\"font-medium text-[var(--color-text-primary)]\">\n {formData.email}\n </span>\n {verificationEmailSuffix}\n </p>\n\n <div className=\"space-y-3 w-full\">\n <button\n type=\"button\"\n onClick={() => navigate(\"/auth/login\")}\n className=\"auth-primary-button w-full px-4 py-3 font-semibold\"\n >\n {tr(\"auth.register.continueToSignIn\", \"Continue to sign in\")}\n </button>\n\n {resendError && (\n <p className=\"text-center text-sm text-status-error-text\">\n {resendError}\n </p>\n )}\n <p className=\"text-center text-sm text-[var(--color-text-tertiary)]\">\n {tr(\n \"auth.register.didntReceiveEmail\",\n \"Didn't receive the email? Check your spam folder or\",\n )}{\" \"}\n <button\n type=\"button\"\n disabled={isResendingVerification}\n onClick={async () => {\n setResendError(null);\n setIsResendingVerification(true);\n try {\n await resendVerification(formData.email);\n } catch (err) {\n setResendError(\n err instanceof Error\n ? err.message\n : tr(\n \"auth.register.resendFailed\",\n \"Failed to resend verification email\",\n ),\n );\n } finally {\n setIsResendingVerification(false);\n }\n }}\n className=\"text-text-link hover:text-text-linkHover font-medium disabled:opacity-50 disabled:cursor-not-allowed\"\n >\n {isResendingVerification\n ? tr(\"auth.register.resending\", \"resending...\")\n : tr(\"auth.register.tryAgain\", \"try again\")}\n </button>\n </p>\n </div>\n </div>\n </AuthLayout>\n );\n }\n\n return (\n <AuthLayout\n title={tr(\"auth.register.createAccount\", \"Create your account\")}\n subtitle={tr(\n \"auth.register.startJourney\",\n \"Start your journey with {{productName}}\",\n ).replace(\"{{productName}}\", productName || \"Burdenoff Workspaces\")}\n productName={productName}\n >\n <form onSubmit={handleSubmit} className=\"space-y-5\">\n {/* Deactivated account → offer self-service reactivation */}\n {isAccountDeactivated && (\n <div className=\"flex items-start gap-3 p-4 rounded-xl bg-status-warning-bg-subtle border border-status-warning-border\">\n <AlertIcon className=\"size-5 text-status-warning-text flex-shrink-0 mt-0.5\" />\n <div className=\"space-y-2\">\n <p className=\"text-sm font-medium text-status-warning-text\">\n {tr(\n \"auth.register.accountDeactivated\",\n \"This email belongs to a deactivated account.\",\n )}\n </p>\n <p className=\"text-xs text-status-warning-text/80\">\n {tr(\n \"auth.register.accountDeactivatedHelp\",\n \"If this is your account, you can reactivate it instead of creating a new one.\",\n )}\n </p>\n <button\n type=\"button\"\n onClick={() =>\n navigate(\"/auth/reactivate\", {\n state: { email: formData.email },\n })\n }\n className=\"text-sm font-semibold text-status-warning-text underline underline-offset-2\"\n >\n {tr(\"auth.register.reactivateCta\", \"Reactivate my account\")}\n </button>\n </div>\n </div>\n )}\n\n {/* Admin-removed / suspended → point to support (no self-service path) */}\n {(isAccountRemoved || isAccountSuspended) && (\n <div className=\"flex items-start gap-3 p-4 rounded-xl bg-status-warning-bg-subtle border border-status-warning-border\">\n <AlertIcon className=\"size-5 text-status-warning-text flex-shrink-0 mt-0.5\" />\n <p className=\"text-sm text-status-warning-text\">{error}</p>\n </div>\n )}\n\n {/* Generic error / validation alert */}\n {((error &&\n !isAccountDeactivated &&\n !isAccountRemoved &&\n !isAccountSuspended) ||\n validationErrors.length > 0) && (\n <div className=\"flex items-start gap-3 p-4 rounded-xl bg-status-error-bg-subtle border border-status-error-border\">\n <AlertIcon className=\"size-5 text-status-error-text flex-shrink-0 mt-0.5\" />\n <div className=\"space-y-1\">\n {error &&\n !isAccountDeactivated &&\n !isAccountRemoved &&\n !isAccountSuspended && (\n <p className=\"text-sm text-status-error-text\">{error}</p>\n )}\n {validationErrors.map((err) => (\n <p key={err} className=\"text-sm text-status-error-text\">\n {err}\n </p>\n ))}\n </div>\n </div>\n )}\n\n {/* Name Fields */}\n <div className=\"grid grid-cols-2 gap-4\">\n <div className=\"space-y-2\">\n <label\n htmlFor=\"firstName\"\n className=\"block text-sm font-medium text-[var(--color-text-primary)]\"\n >\n {tr(\"auth.register.firstName\", \"First Name\")}\n </label>\n <input\n id=\"firstName\"\n type=\"text\"\n required\n aria-required=\"true\"\n aria-label={tr(\"auth.register.firstName\", \"First Name\")}\n value={formData.firstName}\n onChange={(e) =>\n setFormData({ ...formData, firstName: e.target.value })\n }\n className=\"block w-full px-4 py-3 rounded-xl border border-[var(--color-border-default)] bg-[var(--color-bg-surface)] text-[var(--color-text-primary)] placeholder:text-[var(--color-text-tertiary)] focus:outline-none focus:ring-2 focus:ring-action-primary-bg/20 focus:border-action-primary-bg transition-all\"\n placeholder={tr(\n \"auth.register.firstNamePlaceholder\",\n \"Enter first name\",\n )}\n />\n </div>\n <div className=\"space-y-2\">\n <label\n htmlFor=\"lastName\"\n className=\"block text-sm font-medium text-[var(--color-text-primary)]\"\n >\n {tr(\"auth.register.lastName\", \"Last Name\")}\n </label>\n <input\n id=\"lastName\"\n type=\"text\"\n required\n aria-required=\"true\"\n aria-label={tr(\"auth.register.lastName\", \"Last Name\")}\n value={formData.lastName}\n onChange={(e) =>\n setFormData({ ...formData, lastName: e.target.value })\n }\n className=\"block w-full px-4 py-3 rounded-xl border border-[var(--color-border-default)] bg-[var(--color-bg-surface)] text-[var(--color-text-primary)] placeholder:text-[var(--color-text-tertiary)] focus:outline-none focus:ring-2 focus:ring-action-primary-bg/20 focus:border-action-primary-bg transition-all\"\n placeholder={tr(\n \"auth.register.lastNamePlaceholder\",\n \"Enter last name\",\n )}\n />\n </div>\n </div>\n\n {/* Username Input */}\n <div className=\"space-y-2\">\n <label\n htmlFor=\"username\"\n className=\"block text-sm font-medium text-[var(--color-text-primary)]\"\n >\n {tr(\"auth.register.username\", \"Username\")}\n </label>\n <div className=\"relative\">\n <div className=\"absolute inset-y-0 left-0 pl-3.5 flex items-center pointer-events-none\">\n <UserIcon className=\"size-5 text-[var(--color-text-tertiary)]\" />\n </div>\n <input\n id=\"username\"\n type=\"text\"\n required\n aria-required=\"true\"\n aria-label={tr(\"auth.register.username\", \"Username\")}\n autoComplete=\"username\"\n value={formData.username}\n onChange={(e) =>\n setFormData({ ...formData, username: e.target.value })\n }\n className=\"block w-full pl-[3.5rem] pr-4 py-3 rounded-xl border border-[var(--color-border-default)] bg-[var(--color-bg-surface)] text-[var(--color-text-primary)] placeholder:text-[var(--color-text-tertiary)] focus:outline-none focus:ring-2 focus:ring-action-primary-bg/20 focus:border-action-primary-bg transition-all\"\n placeholder={tr(\n \"auth.register.usernamePlaceholder\",\n \"Choose a username\",\n )}\n />\n </div>\n </div>\n\n {/* Email Input */}\n <div className=\"space-y-2\">\n <label\n htmlFor=\"email\"\n className=\"block text-sm font-medium text-[var(--color-text-primary)]\"\n >\n {tr(\"auth.register.email\", \"Email\")}\n </label>\n <div className=\"relative\">\n <div className=\"absolute inset-y-0 left-0 pl-3.5 flex items-center pointer-events-none\">\n <EmailIcon className=\"size-5 text-[var(--color-text-tertiary)]\" />\n </div>\n <input\n id=\"email\"\n type=\"email\"\n required\n aria-required=\"true\"\n aria-label={tr(\"auth.register.email\", \"Email\")}\n autoComplete=\"email\"\n value={formData.email}\n onChange={(e) =>\n setFormData({ ...formData, email: e.target.value })\n }\n className=\"block w-full pl-[3.5rem] pr-4 py-3 rounded-xl border border-[var(--color-border-default)] bg-[var(--color-bg-surface)] text-[var(--color-text-primary)] placeholder:text-[var(--color-text-tertiary)] focus:outline-none focus:ring-2 focus:ring-action-primary-bg/20 focus:border-action-primary-bg transition-all\"\n placeholder={tr(\n \"auth.register.emailPlaceholder\",\n \"you@example.com\",\n )}\n />\n </div>\n </div>\n\n {/* Password Input */}\n <div className=\"space-y-2\">\n <label\n htmlFor=\"password\"\n className=\"block text-sm font-medium text-[var(--color-text-primary)]\"\n >\n {tr(\"auth.register.password\", \"Password\")}\n </label>\n <div className=\"relative\">\n <div className=\"absolute inset-y-0 left-0 pl-3.5 flex items-center pointer-events-none\">\n <LockIcon className=\"size-5 text-[var(--color-text-tertiary)]\" />\n </div>\n <input\n id=\"password\"\n type={showPassword ? \"text\" : \"password\"}\n required\n aria-required=\"true\"\n aria-label={tr(\"auth.register.password\", \"Password\")}\n autoComplete=\"new-password\"\n value={formData.password}\n onChange={(e) =>\n setFormData({ ...formData, password: e.target.value })\n }\n className=\"block w-full pl-[3.5rem] pr-12 py-3 rounded-xl border border-[var(--color-border-default)] bg-[var(--color-bg-surface)] text-[var(--color-text-primary)] placeholder:text-[var(--color-text-tertiary)] focus:outline-none focus:ring-2 focus:ring-action-primary-bg/20 focus:border-action-primary-bg transition-all\"\n placeholder={tr(\n \"auth.register.passwordPlaceholder\",\n \"Create a strong password\",\n )}\n />\n <button\n type=\"button\"\n onClick={() => setShowPassword(!showPassword)}\n aria-label={showPassword ? \"Hide password\" : \"Show password\"}\n className=\"absolute inset-y-0 right-0 pr-3.5 flex items-center text-[var(--color-text-tertiary)] hover:text-[var(--color-text-secondary)] transition-colors\"\n >\n {showPassword ? (\n <EyeOffIcon className=\"size-5\" />\n ) : (\n <EyeIcon className=\"size-5\" />\n )}\n </button>\n </div>\n\n {/* Password Strength Indicator */}\n {formData.password && (\n <div className=\"space-y-3 pt-2\">\n {/* Strength Bar */}\n <div className=\"space-y-1\">\n <div className=\"h-1.5 bg-[var(--color-bg-sunken)] rounded-full overflow-hidden\">\n <div\n className={`h-full ${passwordStrength.color} ${passwordStrength.width} transition-all duration-300`}\n />\n </div>\n <p className=\"text-xs text-[var(--color-text-tertiary)] capitalize\">\n {tr(\"auth.register.passwordStrength\", \"Password strength:\")}{\" \"}\n <span className=\"font-medium\">{passwordStrength.level}</span>\n </p>\n </div>\n\n {/* Requirements Checklist */}\n <div className=\"grid grid-cols-2 gap-2\">\n {passwordChecks.map((check) => (\n <div key={check.label} className=\"flex items-center gap-2\">\n {check.met ? (\n <CheckIcon className=\"size-4 text-status-success-text\" />\n ) : (\n <XIcon className=\"size-4 text-[var(--color-text-tertiary)]\" />\n )}\n <span\n className={`text-xs ${\n check.met\n ? \"text-status-success-text\"\n : \"text-[var(--color-text-tertiary)]\"\n }`}\n >\n {check.label}\n </span>\n </div>\n ))}\n </div>\n </div>\n )}\n </div>\n\n {/* Terms Agreement */}\n <p className=\"text-xs text-[var(--color-text-tertiary)] leading-relaxed\">\n {tr(\n \"auth.register.agreeToTerms\",\n \"By creating an account, you agree to our\",\n )}{\" \"}\n <a href=\"/legal/terms\" className=\"text-text-link hover:underline\">\n {tr(\"auth.register.termsOfService\", \"Terms of Service\")}\n </a>{\" \"}\n {tr(\"auth.register.and\", \"and\")}{\" \"}\n <a href=\"/legal/privacy\" className=\"text-text-link hover:underline\">\n {tr(\"auth.register.privacyPolicy\", \"Privacy Policy\")}\n </a>\n .\n </p>\n\n {/* reCAPTCHA v2 Checkbox */}\n <CaptchaV2 onVerify={setCaptchaToken} />\n\n {/* Submit Button */}\n <button\n type=\"submit\"\n disabled={isLoading}\n className=\"auth-primary-button flex w-full items-center justify-center gap-2 px-4 py-3 font-semibold\"\n >\n {isLoading ? (\n <>\n <SpinnerIcon className=\"size-5 animate-spin\" />\n <span>\n {tr(\"auth.register.creatingAccount\", \"Creating account...\")}\n </span>\n </>\n ) : (\n tr(\"auth.register.createAccountBtn\", \"Create account\")\n )}\n </button>\n </form>\n\n {/* Divider */}\n <div className=\"relative my-8\">\n <div className=\"absolute inset-0 flex items-center\">\n <div className=\"w-full border-t border-[var(--color-border-default)]\" />\n </div>\n <div className=\"relative flex justify-center\">\n <span className=\"px-4 bg-[var(--color-bg-canvas)] text-sm text-[var(--color-text-tertiary)]\">\n {tr(\"auth.register.orSignUpWith\", \"or sign up with\")}\n </span>\n </div>\n </div>\n\n {/* OAuth Error */}\n {oauthError && (\n <div className=\"flex items-start gap-3 p-4 rounded-xl border bg-status-error-bg-subtle border-status-error-border\">\n <AlertIcon className=\"size-5 flex-shrink-0 mt-0.5 text-status-error-text\" />\n <p className=\"text-sm text-status-error-text\">{oauthError}</p>\n </div>\n )}\n\n {/* OAuth Buttons */}\n <div className=\"grid grid-cols-3 gap-3\">\n {oauthProviders.map((provider) => (\n <button\n key={provider.name}\n type=\"button\"\n onClick={provider.onClick}\n disabled={provider.disabled}\n title={provider.tooltip}\n className={`flex items-center justify-center gap-2 px-4 py-3 rounded-xl border transition-all duration-200 group ${\n provider.disabled\n ? \"border-[var(--color-border-subtle)] bg-[var(--color-bg-disabled)] cursor-not-allowed opacity-50\"\n : \"border-[var(--color-border-default)] bg-[var(--color-bg-surface)] hover:bg-[var(--color-bg-sunken)] hover:border-[var(--color-border-strong)]\"\n }`}\n >\n <provider.icon\n className={`size-5 ${provider.disabled ? \"opacity-50\" : \"\"}`}\n />\n <span\n className={`sr-only sm:not-sr-only text-sm font-medium transition-colors hidden sm:inline ${\n provider.disabled\n ? \"text-[var(--color-text-disabled)]\"\n : \"text-[var(--color-text-secondary)] group-hover:text-[var(--color-text-primary)]\"\n }`}\n >\n {provider.name}\n </span>\n </button>\n ))}\n </div>\n\n {/* Sign In Link */}\n <p className=\"mt-8 text-center text-sm text-[var(--color-text-secondary)]\">\n {tr(\"auth.register.alreadyHaveAccount\", \"Already have an account?\")}{\" \"}\n <button\n type=\"button\"\n onClick={() => navigate(\"/auth/login\")}\n className=\"font-semibold text-text-link hover:text-text-linkHover transition-colors\"\n >\n {tr(\"auth.register.signIn\", \"Sign in\")}\n </button>\n </p>\n </AuthLayout>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"RegisterPage.js","names":[],"sources":["../../../src/auth/pages/RegisterPage.tsx"],"sourcesContent":["import { useState } from \"react\";\nimport { useNavigate } from \"react-router-dom\";\nimport { useI18n } from \"@burdenoff/fe-libs/shared/providers/shell\";\nimport { validateEmail, validatePassword } from \"../utils/validation\";\nimport { useAuthOperations } from \"../hooks/useAuthOperations\";\nimport { useOAuthOperations } from \"../hooks/useOAuthOperations\";\nimport { AuthLayout } from \"../components/AuthLayout\";\nimport {\n EmailIcon,\n LockIcon,\n UserIcon,\n EyeIcon,\n EyeOffIcon,\n AlertIcon,\n CheckIcon,\n XIcon,\n SpinnerIcon,\n GoogleIcon,\n GitHubIcon,\n MicrosoftIcon,\n} from \"../components/AuthIcons\";\nimport { useAuth } from \"../providers/AuthProvider\";\nimport { CaptchaV2 } from \"../components/CaptchaV2\";\nimport { useSignupCaptcha } from \"../hooks/useSignupCaptcha\";\nimport type { SignUpInput } from \"../types\";\n\n/**\n * Registration page with professional design\n */\nexport function RegisterPage() {\n const { productName } = useAuth();\n const navigate = useNavigate();\n const { t } = useI18n();\n const tr = (key: string, fallback: string): string => {\n const translated = t(key);\n return translated === key ? fallback : translated;\n };\n const { signUp, resendVerification, isLoading, errorCode } =\n useAuthOperations();\n const { initiateGoogleOAuth, initiateMicrosoftOAuth, isProviderConfigured } =\n useOAuthOperations();\n const [showPassword, setShowPassword] = useState(false);\n const [formData, setFormData] = useState<SignUpInput>({\n email: \"\",\n username: \"\",\n password: \"\",\n firstName: \"\",\n lastName: \"\",\n });\n const [validationErrors, setValidationErrors] = useState<string[]>([]);\n const [signupSuccess, setSignupSuccess] = useState(false);\n const [error, setError] = useState<string | null>(null);\n const [oauthError, setOauthError] = useState<string | null>(null);\n const [captchaToken, setCaptchaToken] = useState<string | null>(null);\n // reCAPTCHA v2 tokens are single-use. Bumping this key remounts the widget so\n // a fresh token is issued after a failed submit (the prior token is consumed\n // server-side; reusing it returns timeout-or-duplicate → CAPTCHA_FAILED loop).\n const [captchaResetKey, setCaptchaResetKey] = useState(0);\n const { enabled: captchaEnabled, siteKey: captchaSiteKey } =\n useSignupCaptcha();\n const [isResendingVerification, setIsResendingVerification] = useState(false);\n const [resendError, setResendError] = useState<string | null>(null);\n\n // Signing up with an email that already belongs to a non-active account:\n // route the user to the right recovery path instead of dead-ending.\n const isAccountDeactivated =\n Boolean(error) && errorCode === \"ACCOUNT_DEACTIVATED\";\n const isAccountRemoved = Boolean(error) && errorCode === \"ACCOUNT_REMOVED\";\n const isAccountSuspended =\n Boolean(error) && errorCode === \"ACCOUNT_SUSPENDED\";\n\n // Password strength indicators\n const passwordChecks = (() => {\n const password = formData.password;\n return [\n {\n label: tr(\"auth.register.pwMin8\", \"At least 12 characters\"),\n met: password.length >= 12,\n },\n {\n label: tr(\"auth.register.pwUppercase\", \"One uppercase letter\"),\n met: /[A-Z]/.test(password),\n },\n {\n label: tr(\"auth.register.pwLowercase\", \"One lowercase letter\"),\n met: /[a-z]/.test(password),\n },\n {\n label: tr(\"auth.register.pwNumber\", \"One number\"),\n met: /\\d/.test(password),\n },\n {\n label: tr(\"auth.register.pwSpecial\", \"One special character\"),\n met: /[!@#$%^&*(),.?\":{}|<>]/.test(password),\n },\n ];\n })();\n\n const passwordStrength = (() => {\n const metCount = passwordChecks.filter((c) => c.met).length;\n if (metCount <= 2)\n return {\n level: tr(\"auth.register.passwordStrengthWeak\", \"weak\"),\n color: \"bg-status-error-bg-subtle0\",\n width: \"w-1/4\",\n };\n if (metCount <= 3)\n return {\n level: tr(\"auth.register.passwordStrengthFair\", \"fair\"),\n color: \"bg-status-warning-text\",\n width: \"w-2/4\",\n };\n if (metCount <= 4)\n return {\n level: tr(\"auth.register.passwordStrengthGood\", \"good\"),\n color: \"bg-status-warning-bg-subtle0\",\n width: \"w-3/4\",\n };\n return {\n level: tr(\"auth.register.passwordStrengthStrong\", \"strong\"),\n color: \"bg-status-success-text\",\n width: \"w-full\",\n };\n })();\n\n const handleSubmit = async (e: React.FormEvent) => {\n e.preventDefault();\n\n const errors: string[] = [];\n if (!validateEmail(formData.email)) {\n errors.push(\n tr(\"auth.register.invalidEmail\", \"Please enter a valid email address\"),\n );\n }\n if (!formData.username || formData.username.length < 3) {\n errors.push(\n tr(\n \"auth.register.usernameMin3\",\n \"Username must be at least 3 characters\",\n ),\n );\n }\n if (!/^[a-zA-Z0-9_-]+$/.test(formData.username)) {\n errors.push(\n tr(\n \"auth.register.usernameChars\",\n \"Username can only contain letters, numbers, underscores, and hyphens\",\n ),\n );\n }\n const passwordValidation = validatePassword(formData.password);\n if (!passwordValidation.valid) {\n errors.push(...passwordValidation.errors);\n }\n\n if (captchaEnabled && !captchaToken) {\n errors.push(\n tr(\n \"auth.register.completeCaptcha\",\n \"Please complete the reCAPTCHA verification\",\n ),\n );\n }\n\n if (errors.length > 0) {\n setValidationErrors(errors);\n return;\n }\n\n setValidationErrors([]);\n setError(null);\n try {\n await signUp({\n ...formData,\n ...(captchaToken ? { recaptchaToken: captchaToken } : {}),\n });\n // Sign up successful - check if email verification is required\n // (Backend returns empty tokens when email verification is needed)\n setSignupSuccess(true);\n } catch (err) {\n const message =\n err instanceof Error\n ? err.message\n : tr(\"auth.register.signUpFailed\", \"Sign up failed\");\n setError(message);\n console.error(\"Sign up failed:\", err);\n // The captcha token (if any) was consumed during this attempt — discard it\n // and remount the widget so the user gets a fresh, solvable challenge on\n // retry instead of being trapped in a CAPTCHA_FAILED loop.\n if (captchaEnabled) {\n setCaptchaToken(null);\n setCaptchaResetKey((k) => k + 1);\n }\n }\n };\n\n /**\n * Handle Google OAuth signup\n */\n const handleGoogleSignup = () => {\n setOauthError(null);\n try {\n initiateGoogleOAuth();\n } catch (err) {\n setOauthError(\n err instanceof Error\n ? err.message\n : tr(\n \"auth.register.googleSignUpFailed\",\n \"Failed to initiate Google sign up\",\n ),\n );\n }\n };\n\n /**\n * Handle Microsoft OAuth signup\n */\n const handleMicrosoftSignup = () => {\n setOauthError(null);\n try {\n initiateMicrosoftOAuth();\n } catch (err) {\n setOauthError(\n err instanceof Error\n ? err.message\n : tr(\n \"auth.register.microsoftSignUpFailed\",\n \"Failed to initiate Microsoft sign up\",\n ),\n );\n }\n };\n\n // Check if OAuth providers are configured\n const isGoogleConfigured = isProviderConfigured(\"google\");\n const isMicrosoftConfigured = isProviderConfigured(\"microsoft\");\n\n const oauthProviders = [\n {\n name: \"Google\",\n icon: GoogleIcon,\n onClick: handleGoogleSignup,\n disabled: !isGoogleConfigured,\n tooltip: isGoogleConfigured\n ? undefined\n : tr(\n \"auth.register.googleNotConfigured\",\n \"Google sign up is not configured\",\n ),\n },\n {\n name: \"GitHub\",\n icon: GitHubIcon,\n onClick: () => {},\n disabled: true,\n tooltip: tr(\n \"auth.register.githubNotConfigured\",\n \"GitHub sign up is not configured\",\n ),\n },\n {\n name: \"Microsoft\",\n icon: MicrosoftIcon,\n onClick: handleMicrosoftSignup,\n disabled: !isMicrosoftConfigured,\n tooltip: isMicrosoftConfigured\n ? undefined\n : tr(\n \"auth.register.microsoftNotConfigured\",\n \"Microsoft sign up is not configured\",\n ),\n },\n ];\n\n const verificationEmailMessage = tr(\n \"auth.register.verificationEmailSentTo\",\n \"We've sent a verification email to {{email}}. Please check your inbox and click the verification link to activate your account.\",\n );\n const [verificationEmailPrefix, verificationEmailSuffix = \"\"] =\n verificationEmailMessage.split(\"{{email}}\");\n\n // Show success state after signup\n if (signupSuccess) {\n return (\n <AuthLayout\n title={tr(\"auth.register.checkEmail\", \"Check your email\")}\n subtitle={tr(\n \"auth.register.verificationLinkSent\",\n \"We've sent you a verification link\",\n )}\n >\n <div className=\"flex flex-col items-center justify-center py-6\">\n {/* Success Icon */}\n <div className=\"size-20 rounded-full bg-status-success-bg-subtle flex items-center justify-center mb-6\">\n <svg\n className=\"size-10 text-status-success-text\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z\"\n />\n </svg>\n </div>\n\n <h3 className=\"text-lg font-semibold text-[var(--color-text-primary)] mb-2\">\n {tr(\n \"auth.register.accountCreated\",\n \"Account created successfully!\",\n )}\n </h3>\n\n <p className=\"text-center text-[var(--color-text-secondary)] mb-8 max-w-sm\">\n {verificationEmailPrefix}\n <span className=\"font-medium text-[var(--color-text-primary)]\">\n {formData.email}\n </span>\n {verificationEmailSuffix}\n </p>\n\n <div className=\"space-y-3 w-full\">\n <button\n type=\"button\"\n onClick={() => navigate(\"/auth/login\")}\n className=\"auth-primary-button w-full px-4 py-3 font-semibold\"\n >\n {tr(\"auth.register.continueToSignIn\", \"Continue to sign in\")}\n </button>\n\n {resendError && (\n <p className=\"text-center text-sm text-status-error-text\">\n {resendError}\n </p>\n )}\n <p className=\"text-center text-sm text-[var(--color-text-tertiary)]\">\n {tr(\n \"auth.register.didntReceiveEmail\",\n \"Didn't receive the email? Check your spam folder or\",\n )}{\" \"}\n <button\n type=\"button\"\n disabled={isResendingVerification}\n onClick={async () => {\n setResendError(null);\n setIsResendingVerification(true);\n try {\n await resendVerification(formData.email);\n } catch (err) {\n setResendError(\n err instanceof Error\n ? err.message\n : tr(\n \"auth.register.resendFailed\",\n \"Failed to resend verification email\",\n ),\n );\n } finally {\n setIsResendingVerification(false);\n }\n }}\n className=\"text-text-link hover:text-text-linkHover font-medium disabled:opacity-50 disabled:cursor-not-allowed\"\n >\n {isResendingVerification\n ? tr(\"auth.register.resending\", \"resending...\")\n : tr(\"auth.register.tryAgain\", \"try again\")}\n </button>\n </p>\n </div>\n </div>\n </AuthLayout>\n );\n }\n\n return (\n <AuthLayout\n title={tr(\"auth.register.createAccount\", \"Create your account\")}\n subtitle={tr(\n \"auth.register.startJourney\",\n \"Start your journey with {{productName}}\",\n ).replace(\"{{productName}}\", productName || \"Burdenoff Workspaces\")}\n productName={productName}\n >\n <form onSubmit={handleSubmit} className=\"space-y-5\">\n {/* Deactivated account → offer self-service reactivation */}\n {isAccountDeactivated && (\n <div className=\"flex items-start gap-3 p-4 rounded-xl bg-status-warning-bg-subtle border border-status-warning-border\">\n <AlertIcon className=\"size-5 text-status-warning-text flex-shrink-0 mt-0.5\" />\n <div className=\"space-y-2\">\n <p className=\"text-sm font-medium text-status-warning-text\">\n {tr(\n \"auth.register.accountDeactivated\",\n \"This email belongs to a deactivated account.\",\n )}\n </p>\n <p className=\"text-xs text-status-warning-text/80\">\n {tr(\n \"auth.register.accountDeactivatedHelp\",\n \"If this is your account, you can reactivate it instead of creating a new one.\",\n )}\n </p>\n <button\n type=\"button\"\n onClick={() =>\n navigate(\"/auth/reactivate\", {\n state: { email: formData.email },\n })\n }\n className=\"text-sm font-semibold text-status-warning-text underline underline-offset-2\"\n >\n {tr(\"auth.register.reactivateCta\", \"Reactivate my account\")}\n </button>\n </div>\n </div>\n )}\n\n {/* Admin-removed / suspended → point to support (no self-service path) */}\n {(isAccountRemoved || isAccountSuspended) && (\n <div className=\"flex items-start gap-3 p-4 rounded-xl bg-status-warning-bg-subtle border border-status-warning-border\">\n <AlertIcon className=\"size-5 text-status-warning-text flex-shrink-0 mt-0.5\" />\n <p className=\"text-sm text-status-warning-text\">{error}</p>\n </div>\n )}\n\n {/* Generic error / validation alert */}\n {((error &&\n !isAccountDeactivated &&\n !isAccountRemoved &&\n !isAccountSuspended) ||\n validationErrors.length > 0) && (\n <div className=\"flex items-start gap-3 p-4 rounded-xl bg-status-error-bg-subtle border border-status-error-border\">\n <AlertIcon className=\"size-5 text-status-error-text flex-shrink-0 mt-0.5\" />\n <div className=\"space-y-1\">\n {error &&\n !isAccountDeactivated &&\n !isAccountRemoved &&\n !isAccountSuspended && (\n <p className=\"text-sm text-status-error-text\">{error}</p>\n )}\n {validationErrors.map((err) => (\n <p key={err} className=\"text-sm text-status-error-text\">\n {err}\n </p>\n ))}\n </div>\n </div>\n )}\n\n {/* Name Fields */}\n <div className=\"grid grid-cols-2 gap-4\">\n <div className=\"space-y-2\">\n <label\n htmlFor=\"firstName\"\n className=\"block text-sm font-medium text-[var(--color-text-primary)]\"\n >\n {tr(\"auth.register.firstName\", \"First Name\")}\n </label>\n <input\n id=\"firstName\"\n type=\"text\"\n required\n aria-required=\"true\"\n aria-label={tr(\"auth.register.firstName\", \"First Name\")}\n value={formData.firstName}\n onChange={(e) =>\n setFormData({ ...formData, firstName: e.target.value })\n }\n className=\"block w-full px-4 py-3 rounded-xl border border-[var(--color-border-default)] bg-[var(--color-bg-surface)] text-[var(--color-text-primary)] placeholder:text-[var(--color-text-tertiary)] focus:outline-none focus:ring-2 focus:ring-action-primary-bg/20 focus:border-action-primary-bg transition-all\"\n placeholder={tr(\n \"auth.register.firstNamePlaceholder\",\n \"Enter first name\",\n )}\n />\n </div>\n <div className=\"space-y-2\">\n <label\n htmlFor=\"lastName\"\n className=\"block text-sm font-medium text-[var(--color-text-primary)]\"\n >\n {tr(\"auth.register.lastName\", \"Last Name\")}\n </label>\n <input\n id=\"lastName\"\n type=\"text\"\n required\n aria-required=\"true\"\n aria-label={tr(\"auth.register.lastName\", \"Last Name\")}\n value={formData.lastName}\n onChange={(e) =>\n setFormData({ ...formData, lastName: e.target.value })\n }\n className=\"block w-full px-4 py-3 rounded-xl border border-[var(--color-border-default)] bg-[var(--color-bg-surface)] text-[var(--color-text-primary)] placeholder:text-[var(--color-text-tertiary)] focus:outline-none focus:ring-2 focus:ring-action-primary-bg/20 focus:border-action-primary-bg transition-all\"\n placeholder={tr(\n \"auth.register.lastNamePlaceholder\",\n \"Enter last name\",\n )}\n />\n </div>\n </div>\n\n {/* Username Input */}\n <div className=\"space-y-2\">\n <label\n htmlFor=\"username\"\n className=\"block text-sm font-medium text-[var(--color-text-primary)]\"\n >\n {tr(\"auth.register.username\", \"Username\")}\n </label>\n <div className=\"relative\">\n <div className=\"absolute inset-y-0 left-0 pl-3.5 flex items-center pointer-events-none\">\n <UserIcon className=\"size-5 text-[var(--color-text-tertiary)]\" />\n </div>\n <input\n id=\"username\"\n type=\"text\"\n required\n aria-required=\"true\"\n aria-label={tr(\"auth.register.username\", \"Username\")}\n autoComplete=\"username\"\n value={formData.username}\n onChange={(e) =>\n setFormData({ ...formData, username: e.target.value })\n }\n className=\"block w-full pl-[3.5rem] pr-4 py-3 rounded-xl border border-[var(--color-border-default)] bg-[var(--color-bg-surface)] text-[var(--color-text-primary)] placeholder:text-[var(--color-text-tertiary)] focus:outline-none focus:ring-2 focus:ring-action-primary-bg/20 focus:border-action-primary-bg transition-all\"\n placeholder={tr(\n \"auth.register.usernamePlaceholder\",\n \"Choose a username\",\n )}\n />\n </div>\n </div>\n\n {/* Email Input */}\n <div className=\"space-y-2\">\n <label\n htmlFor=\"email\"\n className=\"block text-sm font-medium text-[var(--color-text-primary)]\"\n >\n {tr(\"auth.register.email\", \"Email\")}\n </label>\n <div className=\"relative\">\n <div className=\"absolute inset-y-0 left-0 pl-3.5 flex items-center pointer-events-none\">\n <EmailIcon className=\"size-5 text-[var(--color-text-tertiary)]\" />\n </div>\n <input\n id=\"email\"\n type=\"email\"\n required\n aria-required=\"true\"\n aria-label={tr(\"auth.register.email\", \"Email\")}\n autoComplete=\"email\"\n value={formData.email}\n onChange={(e) =>\n setFormData({ ...formData, email: e.target.value })\n }\n className=\"block w-full pl-[3.5rem] pr-4 py-3 rounded-xl border border-[var(--color-border-default)] bg-[var(--color-bg-surface)] text-[var(--color-text-primary)] placeholder:text-[var(--color-text-tertiary)] focus:outline-none focus:ring-2 focus:ring-action-primary-bg/20 focus:border-action-primary-bg transition-all\"\n placeholder={tr(\n \"auth.register.emailPlaceholder\",\n \"you@example.com\",\n )}\n />\n </div>\n </div>\n\n {/* Password Input */}\n <div className=\"space-y-2\">\n <label\n htmlFor=\"password\"\n className=\"block text-sm font-medium text-[var(--color-text-primary)]\"\n >\n {tr(\"auth.register.password\", \"Password\")}\n </label>\n <div className=\"relative\">\n <div className=\"absolute inset-y-0 left-0 pl-3.5 flex items-center pointer-events-none\">\n <LockIcon className=\"size-5 text-[var(--color-text-tertiary)]\" />\n </div>\n <input\n id=\"password\"\n type={showPassword ? \"text\" : \"password\"}\n required\n aria-required=\"true\"\n aria-label={tr(\"auth.register.password\", \"Password\")}\n autoComplete=\"new-password\"\n value={formData.password}\n onChange={(e) =>\n setFormData({ ...formData, password: e.target.value })\n }\n className=\"block w-full pl-[3.5rem] pr-12 py-3 rounded-xl border border-[var(--color-border-default)] bg-[var(--color-bg-surface)] text-[var(--color-text-primary)] placeholder:text-[var(--color-text-tertiary)] focus:outline-none focus:ring-2 focus:ring-action-primary-bg/20 focus:border-action-primary-bg transition-all\"\n placeholder={tr(\n \"auth.register.passwordPlaceholder\",\n \"Create a strong password\",\n )}\n />\n <button\n type=\"button\"\n onClick={() => setShowPassword(!showPassword)}\n aria-label={showPassword ? \"Hide password\" : \"Show password\"}\n className=\"absolute inset-y-0 right-0 pr-3.5 flex items-center text-[var(--color-text-tertiary)] hover:text-[var(--color-text-secondary)] transition-colors\"\n >\n {showPassword ? (\n <EyeOffIcon className=\"size-5\" />\n ) : (\n <EyeIcon className=\"size-5\" />\n )}\n </button>\n </div>\n\n {/* Password Strength Indicator */}\n {formData.password && (\n <div className=\"space-y-3 pt-2\">\n {/* Strength Bar */}\n <div className=\"space-y-1\">\n <div className=\"h-1.5 bg-[var(--color-bg-sunken)] rounded-full overflow-hidden\">\n <div\n className={`h-full ${passwordStrength.color} ${passwordStrength.width} transition-all duration-300`}\n />\n </div>\n <p className=\"text-xs text-[var(--color-text-tertiary)] capitalize\">\n {tr(\"auth.register.passwordStrength\", \"Password strength:\")}{\" \"}\n <span className=\"font-medium\">{passwordStrength.level}</span>\n </p>\n </div>\n\n {/* Requirements Checklist */}\n <div className=\"grid grid-cols-2 gap-2\">\n {passwordChecks.map((check) => (\n <div key={check.label} className=\"flex items-center gap-2\">\n {check.met ? (\n <CheckIcon className=\"size-4 text-status-success-text\" />\n ) : (\n <XIcon className=\"size-4 text-[var(--color-text-tertiary)]\" />\n )}\n <span\n className={`text-xs ${\n check.met\n ? \"text-status-success-text\"\n : \"text-[var(--color-text-tertiary)]\"\n }`}\n >\n {check.label}\n </span>\n </div>\n ))}\n </div>\n </div>\n )}\n </div>\n\n {/* Terms Agreement */}\n <p className=\"text-xs text-[var(--color-text-tertiary)] leading-relaxed\">\n {tr(\n \"auth.register.agreeToTerms\",\n \"By creating an account, you agree to our\",\n )}{\" \"}\n <a href=\"/legal/terms\" className=\"text-text-link hover:underline\">\n {tr(\"auth.register.termsOfService\", \"Terms of Service\")}\n </a>{\" \"}\n {tr(\"auth.register.and\", \"and\")}{\" \"}\n <a href=\"/legal/privacy\" className=\"text-text-link hover:underline\">\n {tr(\"auth.register.privacyPolicy\", \"Privacy Policy\")}\n </a>\n .\n </p>\n\n {/* reCAPTCHA v2 Checkbox — enabled per-product via the platform catalog.\n `key` remounts it after a failed submit so a fresh token is issued. */}\n <CaptchaV2\n key={captchaResetKey}\n enabled={captchaEnabled}\n siteKey={captchaSiteKey}\n onVerify={setCaptchaToken}\n onExpired={() => setCaptchaToken(null)}\n />\n\n {/* Submit Button */}\n <button\n type=\"submit\"\n disabled={isLoading}\n className=\"auth-primary-button flex w-full items-center justify-center gap-2 px-4 py-3 font-semibold\"\n >\n {isLoading ? (\n <>\n <SpinnerIcon className=\"size-5 animate-spin\" />\n <span>\n {tr(\"auth.register.creatingAccount\", \"Creating account...\")}\n </span>\n </>\n ) : (\n tr(\"auth.register.createAccountBtn\", \"Create account\")\n )}\n </button>\n </form>\n\n {/* Divider */}\n <div className=\"relative my-8\">\n <div className=\"absolute inset-0 flex items-center\">\n <div className=\"w-full border-t border-[var(--color-border-default)]\" />\n </div>\n <div className=\"relative flex justify-center\">\n <span className=\"px-4 bg-[var(--color-bg-canvas)] text-sm text-[var(--color-text-tertiary)]\">\n {tr(\"auth.register.orSignUpWith\", \"or sign up with\")}\n </span>\n </div>\n </div>\n\n {/* OAuth Error */}\n {oauthError && (\n <div className=\"flex items-start gap-3 p-4 rounded-xl border bg-status-error-bg-subtle border-status-error-border\">\n <AlertIcon className=\"size-5 flex-shrink-0 mt-0.5 text-status-error-text\" />\n <p className=\"text-sm text-status-error-text\">{oauthError}</p>\n </div>\n )}\n\n {/* OAuth Buttons */}\n <div className=\"grid grid-cols-3 gap-3\">\n {oauthProviders.map((provider) => (\n <button\n key={provider.name}\n type=\"button\"\n onClick={provider.onClick}\n disabled={provider.disabled}\n title={provider.tooltip}\n className={`flex items-center justify-center gap-2 px-4 py-3 rounded-xl border transition-all duration-200 group ${\n provider.disabled\n ? \"border-[var(--color-border-subtle)] bg-[var(--color-bg-disabled)] cursor-not-allowed opacity-50\"\n : \"border-[var(--color-border-default)] bg-[var(--color-bg-surface)] hover:bg-[var(--color-bg-sunken)] hover:border-[var(--color-border-strong)]\"\n }`}\n >\n <provider.icon\n className={`size-5 ${provider.disabled ? \"opacity-50\" : \"\"}`}\n />\n <span\n className={`sr-only sm:not-sr-only text-sm font-medium transition-colors hidden sm:inline ${\n provider.disabled\n ? \"text-[var(--color-text-disabled)]\"\n : \"text-[var(--color-text-secondary)] group-hover:text-[var(--color-text-primary)]\"\n }`}\n >\n {provider.name}\n </span>\n </button>\n ))}\n </div>\n\n {/* Sign In Link */}\n <p className=\"mt-8 text-center text-sm text-[var(--color-text-secondary)]\">\n {tr(\"auth.register.alreadyHaveAccount\", \"Already have an account?\")}{\" \"}\n <button\n type=\"button\"\n onClick={() => navigate(\"/auth/login\")}\n className=\"font-semibold text-text-link hover:text-text-linkHover transition-colors\"\n >\n {tr(\"auth.register.signIn\", \"Sign in\")}\n </button>\n </p>\n </AuthLayout>\n );\n}\n"],"mappings":"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"}
|
|
@@ -7,8 +7,8 @@ var a = e(null), o = () => {
|
|
|
7
7
|
let e = t(a);
|
|
8
8
|
if (!e) throw Error("useAuth must be used within AuthProvider");
|
|
9
9
|
return e;
|
|
10
|
-
}, s = ({ children: e, basePath: t, navigate: o, currentUser: s, workspaceId: c, tenantId: l, organizationId: u, apiGatewayUrl: d, authToken: f, productName: p, onAuthSuccess: m, onLogout: h, defaultPostLoginPath: g, oauthClientIds: _ }) => {
|
|
11
|
-
let
|
|
10
|
+
}, s = ({ children: e, basePath: t, navigate: o, currentUser: s, workspaceId: c, tenantId: l, organizationId: u, apiGatewayUrl: d, authToken: f, productName: p, onAuthSuccess: m, onLogout: h, defaultPostLoginPath: g, oauthClientIds: _, productSlug: v }) => {
|
|
11
|
+
let y = {
|
|
12
12
|
basePath: t,
|
|
13
13
|
navigate: o,
|
|
14
14
|
currentUser: s,
|
|
@@ -21,16 +21,17 @@ var a = e(null), o = () => {
|
|
|
21
21
|
onAuthSuccess: m,
|
|
22
22
|
onLogout: h,
|
|
23
23
|
defaultPostLoginPath: g,
|
|
24
|
-
oauthClientIds: _
|
|
25
|
-
|
|
26
|
-
|
|
24
|
+
oauthClientIds: _,
|
|
25
|
+
productSlug: v
|
|
26
|
+
}, b = u ?? l ?? null, x = /* @__PURE__ */ i(a.Provider, {
|
|
27
|
+
value: y,
|
|
27
28
|
children: /* @__PURE__ */ i(r, {
|
|
28
|
-
scopeId:
|
|
29
|
+
scopeId: b,
|
|
29
30
|
gateway: "global",
|
|
30
31
|
children: e
|
|
31
32
|
})
|
|
32
33
|
});
|
|
33
|
-
return t ?
|
|
34
|
+
return t ? x : /* @__PURE__ */ i(n, { children: x });
|
|
34
35
|
};
|
|
35
36
|
//#endregion
|
|
36
37
|
export { s as AuthProvider, o as useAuth };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AuthProvider.js","names":[],"sources":["../../../src/auth/providers/AuthProvider.tsx"],"sourcesContent":["import type { FC, PropsWithChildren } from \"react\";\nimport { createContext, useContext } from \"react\";\nimport { MemoryRouter } from \"react-router-dom\";\nimport { PermissionProvider } from \"@burdenoff/fe-libs/shared/providers/shell\";\nimport type { AuthRootProps } from \"../types\";\n\n/**\n * Context value for Auth microfrontend\n */\ntype AuthContextValue = AuthRootProps;\n\nconst AuthContext = createContext<AuthContextValue | null>(null);\n\n/**\n * Hook to access Auth context\n */\nexport const useAuth = () => {\n const context = useContext(AuthContext);\n if (!context) {\n throw new Error(\"useAuth must be used within AuthProvider\");\n }\n return context;\n};\n\n/**\n * Provider component for Auth microfrontend\n * Sets up routing and context.\n *\n * NOTE: Apollo client is provided by the app shell's MultiGatewayProvider +\n * GatewayRoute. Auth routes are wrapped with <GatewayRoute gateway=\"global\">\n * so all useQuery/useMutation calls automatically use the global gateway client.\n * This MFE does NOT create its own Apollo client.\n */\nexport const AuthProvider: FC<PropsWithChildren<AuthRootProps>> = ({\n children,\n basePath,\n navigate,\n currentUser,\n workspaceId,\n tenantId,\n organizationId,\n apiGatewayUrl,\n authToken,\n productName,\n onAuthSuccess,\n onLogout,\n defaultPostLoginPath,\n oauthClientIds,\n}) => {\n const contextValue: AuthContextValue = {\n basePath,\n navigate,\n currentUser,\n workspaceId,\n tenantId,\n organizationId,\n apiGatewayUrl,\n authToken,\n productName,\n onAuthSuccess,\n onLogout,\n defaultPostLoginPath,\n oauthClientIds,\n };\n\n // global-rbac has no tenant scope; org roles are scoped to organizationId.\n const permissionScopeId = organizationId ?? tenantId ?? null;\n\n const content = (\n <AuthContext.Provider value={contextValue}>\n <PermissionProvider scopeId={permissionScopeId} gateway=\"global\">\n {children}\n </PermissionProvider>\n </AuthContext.Provider>\n );\n\n // When integrated into a shell (basePath provided), the shell provides\n // BrowserRouter AND ApolloProvider — just render the context\n if (basePath) {\n return content;\n }\n\n // Standalone mode: create MemoryRouter for independent operation\n // (Apollo client must be provided externally or via a dev harness)\n return <MemoryRouter>{content}</MemoryRouter>;\n};\n"],"mappings":";;;;;AAWA,IAAM,IAAc,EAAuC,KAAK,EAKnD,UAAgB;CAC3B,IAAM,IAAU,EAAW,EAAY;AACvC,KAAI,CAAC,EACH,OAAU,MAAM,2CAA2C;AAE7D,QAAO;GAYI,KAAsD,EACjE,aACA,aACA,aACA,gBACA,gBACA,aACA,mBACA,kBACA,cACA,gBACA,kBACA,aACA,yBACA,
|
|
1
|
+
{"version":3,"file":"AuthProvider.js","names":[],"sources":["../../../src/auth/providers/AuthProvider.tsx"],"sourcesContent":["import type { FC, PropsWithChildren } from \"react\";\nimport { createContext, useContext } from \"react\";\nimport { MemoryRouter } from \"react-router-dom\";\nimport { PermissionProvider } from \"@burdenoff/fe-libs/shared/providers/shell\";\nimport type { AuthRootProps } from \"../types\";\n\n/**\n * Context value for Auth microfrontend\n */\ntype AuthContextValue = AuthRootProps;\n\nconst AuthContext = createContext<AuthContextValue | null>(null);\n\n/**\n * Hook to access Auth context\n */\nexport const useAuth = () => {\n const context = useContext(AuthContext);\n if (!context) {\n throw new Error(\"useAuth must be used within AuthProvider\");\n }\n return context;\n};\n\n/**\n * Provider component for Auth microfrontend\n * Sets up routing and context.\n *\n * NOTE: Apollo client is provided by the app shell's MultiGatewayProvider +\n * GatewayRoute. Auth routes are wrapped with <GatewayRoute gateway=\"global\">\n * so all useQuery/useMutation calls automatically use the global gateway client.\n * This MFE does NOT create its own Apollo client.\n */\nexport const AuthProvider: FC<PropsWithChildren<AuthRootProps>> = ({\n children,\n basePath,\n navigate,\n currentUser,\n workspaceId,\n tenantId,\n organizationId,\n apiGatewayUrl,\n authToken,\n productName,\n onAuthSuccess,\n onLogout,\n defaultPostLoginPath,\n oauthClientIds,\n productSlug,\n}) => {\n const contextValue: AuthContextValue = {\n basePath,\n navigate,\n currentUser,\n workspaceId,\n tenantId,\n organizationId,\n apiGatewayUrl,\n authToken,\n productName,\n onAuthSuccess,\n onLogout,\n defaultPostLoginPath,\n oauthClientIds,\n productSlug,\n };\n\n // global-rbac has no tenant scope; org roles are scoped to organizationId.\n const permissionScopeId = organizationId ?? tenantId ?? null;\n\n const content = (\n <AuthContext.Provider value={contextValue}>\n <PermissionProvider scopeId={permissionScopeId} gateway=\"global\">\n {children}\n </PermissionProvider>\n </AuthContext.Provider>\n );\n\n // When integrated into a shell (basePath provided), the shell provides\n // BrowserRouter AND ApolloProvider — just render the context\n if (basePath) {\n return content;\n }\n\n // Standalone mode: create MemoryRouter for independent operation\n // (Apollo client must be provided externally or via a dev harness)\n return <MemoryRouter>{content}</MemoryRouter>;\n};\n"],"mappings":";;;;;AAWA,IAAM,IAAc,EAAuC,KAAK,EAKnD,UAAgB;CAC3B,IAAM,IAAU,EAAW,EAAY;AACvC,KAAI,CAAC,EACH,OAAU,MAAM,2CAA2C;AAE7D,QAAO;GAYI,KAAsD,EACjE,aACA,aACA,aACA,gBACA,gBACA,aACA,mBACA,kBACA,cACA,gBACA,kBACA,aACA,yBACA,mBACA,qBACI;CACJ,IAAM,IAAiC;EACrC;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACD,EAGK,IAAoB,KAAkB,KAAY,MAElD,IACJ,kBAAC,EAAY,UAAb;EAAsB,OAAO;YAC3B,kBAAC,GAAD;GAAoB,SAAS;GAAmB,SAAQ;GACrD;GACkB,CAAA;EACA,CAAA;AAWzB,QANI,IACK,IAKF,kBAAC,GAAD,EAAA,UAAe,GAAuB,CAAA"}
|