@hexclave/next 1.0.67 → 1.0.69

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.
Files changed (111) hide show
  1. package/README.md +21 -21
  2. package/dist/{admin-app-impl-C74lwnMu.d.ts → admin-app-impl-DNcnEhc-.d.ts} +15 -10
  3. package/dist/admin-app-impl-DNcnEhc-.d.ts.map +1 -0
  4. package/dist/clickmap/clickmap-core.d.ts +1 -1
  5. package/dist/clickmap/index.d.ts +1 -1
  6. package/dist/{client-app-BlA5Ptxs.d.ts → client-app-CE8FTKgO.d.ts} +13 -6
  7. package/dist/client-app-CE8FTKgO.d.ts.map +1 -0
  8. package/dist/components/elements/sidebar-layout.js +1 -1
  9. package/dist/components/selected-team-switcher.d.ts +1 -1
  10. package/dist/components/team-icon.d.ts +1 -1
  11. package/dist/components/team-switcher.d.ts +1 -1
  12. package/dist/components-page/account-settings/payments/payments-page.d.ts +1 -1
  13. package/dist/components-page/account-settings/teams/leave-team-section.d.ts +1 -1
  14. package/dist/components-page/account-settings/teams/team-api-keys-section.d.ts +1 -1
  15. package/dist/components-page/account-settings/teams/team-display-name-section.d.ts +1 -1
  16. package/dist/components-page/account-settings/teams/team-member-invitation-section.d.ts +1 -1
  17. package/dist/components-page/account-settings/teams/team-member-list-section.d.ts +1 -1
  18. package/dist/components-page/account-settings/teams/team-page.d.ts +1 -1
  19. package/dist/components-page/account-settings/teams/team-profile-image-section.d.ts +1 -1
  20. package/dist/components-page/account-settings/teams/team-profile-user-section.d.ts +1 -1
  21. package/dist/components-page/hexclave-handler-client.d.ts +1 -1
  22. package/dist/components-page/hexclave-handler.d.ts +1 -1
  23. package/dist/components-page/onboarding.js +11 -1
  24. package/dist/components-page/onboarding.js.map +1 -1
  25. package/dist/components-page/onboarding.test.d.ts +1 -0
  26. package/dist/components-page/onboarding.test.js +151 -0
  27. package/dist/components-page/onboarding.test.js.map +1 -0
  28. package/dist/dev-tool/dev-tool-core.d.ts +1 -1
  29. package/dist/dev-tool/index.d.ts +1 -1
  30. package/dist/esm/components/elements/sidebar-layout.js +1 -1
  31. package/dist/esm/components-page/onboarding.js +11 -1
  32. package/dist/esm/components-page/onboarding.js.map +1 -1
  33. package/dist/esm/components-page/onboarding.test.d.ts +1 -0
  34. package/dist/esm/components-page/onboarding.test.js +150 -0
  35. package/dist/esm/components-page/onboarding.test.js.map +1 -0
  36. package/dist/esm/generated/quetzal-translations.d.ts +2 -2
  37. package/dist/esm/lib/hexclave-app/apps/implementations/admin-app-impl.d.ts +4 -4
  38. package/dist/esm/lib/hexclave-app/apps/implementations/admin-app-impl.d.ts.map +1 -1
  39. package/dist/esm/lib/hexclave-app/apps/implementations/client-app-impl.d.ts +9 -4
  40. package/dist/esm/lib/hexclave-app/apps/implementations/client-app-impl.d.ts.map +1 -1
  41. package/dist/esm/lib/hexclave-app/apps/implementations/client-app-impl.js +22 -5
  42. package/dist/esm/lib/hexclave-app/apps/implementations/client-app-impl.js.map +1 -1
  43. package/dist/esm/lib/hexclave-app/apps/implementations/client-app-impl.sign-in-cache-warmup.test.d.ts +1 -0
  44. package/dist/esm/lib/hexclave-app/apps/implementations/client-app-impl.sign-in-cache-warmup.test.js +97 -0
  45. package/dist/esm/lib/hexclave-app/apps/implementations/client-app-impl.sign-in-cache-warmup.test.js.map +1 -0
  46. package/dist/esm/lib/hexclave-app/apps/implementations/common.js +2 -2
  47. package/dist/esm/lib/hexclave-app/apps/implementations/server-app-impl.d.ts.map +1 -1
  48. package/dist/esm/lib/hexclave-app/apps/implementations/server-app-impl.js +1 -4
  49. package/dist/esm/lib/hexclave-app/apps/implementations/server-app-impl.js.map +1 -1
  50. package/dist/esm/lib/hexclave-app/apps/interfaces/client-app.d.ts +1 -1
  51. package/dist/esm/lib/hexclave-app/apps/interfaces/client-app.d.ts.map +1 -1
  52. package/dist/esm/lib/hexclave-app/apps/interfaces/client-app.js.map +1 -1
  53. package/dist/esm/lib/hexclave-app/teams/index.d.ts +1 -0
  54. package/dist/esm/lib/hexclave-app/teams/index.d.ts.map +1 -1
  55. package/dist/esm/lib/hexclave-app/teams/index.js.map +1 -1
  56. package/dist/esm/lib/hexclave-app/users/index.d.ts +10 -4
  57. package/dist/esm/lib/hexclave-app/users/index.d.ts.map +1 -1
  58. package/dist/esm/lib/hexclave-app/users/index.js.map +1 -1
  59. package/dist/generated/quetzal-translations.d.ts +1 -1
  60. package/dist/{hexclave-provider-Dtr9mxju.d.ts → hexclave-provider-Ve1QPx9k.d.ts} +3 -3
  61. package/dist/{hexclave-provider-Dtr9mxju.d.ts.map → hexclave-provider-Ve1QPx9k.d.ts.map} +1 -1
  62. package/dist/{hooks-4BBvIKWJ.d.ts → hooks-BNF4UvGF.d.ts} +2 -2
  63. package/dist/{hooks-4BBvIKWJ.d.ts.map → hooks-BNF4UvGF.d.ts.map} +1 -1
  64. package/dist/{index-DDICHT5X.d.ts → index-g1JoVgIu.d.ts} +2 -2
  65. package/dist/{index-DDICHT5X.d.ts.map → index-g1JoVgIu.d.ts.map} +1 -1
  66. package/dist/index.d.ts +4 -4
  67. package/dist/lib/hexclave-app/apps/implementations/admin-app-impl.d.ts +1 -1
  68. package/dist/lib/hexclave-app/apps/implementations/client-app-impl.d.ts +1 -1
  69. package/dist/lib/hexclave-app/apps/implementations/client-app-impl.js +22 -5
  70. package/dist/lib/hexclave-app/apps/implementations/client-app-impl.js.map +1 -1
  71. package/dist/lib/hexclave-app/apps/implementations/client-app-impl.sign-in-cache-warmup.test.d.ts +1 -0
  72. package/dist/lib/hexclave-app/apps/implementations/client-app-impl.sign-in-cache-warmup.test.js +96 -0
  73. package/dist/lib/hexclave-app/apps/implementations/client-app-impl.sign-in-cache-warmup.test.js.map +1 -0
  74. package/dist/lib/hexclave-app/apps/implementations/common.js +2 -2
  75. package/dist/lib/hexclave-app/apps/implementations/index.d.ts +1 -1
  76. package/dist/lib/hexclave-app/apps/implementations/server-app-impl.d.ts +1 -1
  77. package/dist/lib/hexclave-app/apps/implementations/server-app-impl.js +1 -4
  78. package/dist/lib/hexclave-app/apps/implementations/server-app-impl.js.map +1 -1
  79. package/dist/lib/hexclave-app/apps/index.d.ts +1 -1
  80. package/dist/lib/hexclave-app/apps/interfaces/admin-app.d.ts +1 -1
  81. package/dist/lib/hexclave-app/apps/interfaces/client-app.d.ts +1 -1
  82. package/dist/lib/hexclave-app/apps/interfaces/client-app.js.map +1 -1
  83. package/dist/lib/hexclave-app/apps/interfaces/server-app.d.ts +1 -1
  84. package/dist/lib/hexclave-app/index.d.ts +1 -1
  85. package/dist/lib/hexclave-app/projects/index.d.ts +1 -1
  86. package/dist/lib/hexclave-app/teams/index.d.ts +1 -1
  87. package/dist/lib/hexclave-app/teams/index.js.map +1 -1
  88. package/dist/lib/hexclave-app/users/index.d.ts +1 -1
  89. package/dist/lib/hexclave-app/users/index.js.map +1 -1
  90. package/dist/lib/hooks.d.ts +1 -1
  91. package/dist/providers/hexclave-context.d.ts +1 -1
  92. package/dist/providers/hexclave-provider-client.d.ts +1 -1
  93. package/dist/providers/hexclave-provider.d.ts +1 -1
  94. package/dist/providers/translation-provider.d.ts +1 -1
  95. package/dist/pushed-config-error-overlay/index.d.ts +1 -1
  96. package/dist/{quetzal-translations-DVNj-zsr.d.ts → quetzal-translations-XeH3zQ9l.d.ts} +3 -3
  97. package/dist/{quetzal-translations-DVNj-zsr.d.ts.map → quetzal-translations-XeH3zQ9l.d.ts.map} +1 -1
  98. package/dist/{translation-provider-DPFkLbrw.d.ts → translation-provider-WOaZXqAz.d.ts} +2 -2
  99. package/dist/{translation-provider-DPFkLbrw.d.ts.map → translation-provider-WOaZXqAz.d.ts.map} +1 -1
  100. package/package.json +4 -4
  101. package/src/components-page/onboarding.test.tsx +182 -0
  102. package/src/components-page/onboarding.tsx +17 -0
  103. package/src/integrations/convex/component/README.md +4 -4
  104. package/src/lib/hexclave-app/apps/implementations/client-app-impl.sign-in-cache-warmup.test.ts +94 -0
  105. package/src/lib/hexclave-app/apps/implementations/client-app-impl.ts +33 -8
  106. package/src/lib/hexclave-app/apps/implementations/server-app-impl.ts +1 -10
  107. package/src/lib/hexclave-app/apps/interfaces/client-app.ts +1 -1
  108. package/src/lib/hexclave-app/teams/index.ts +1 -0
  109. package/src/lib/hexclave-app/users/index.ts +9 -4
  110. package/dist/admin-app-impl-C74lwnMu.d.ts.map +0 -1
  111. package/dist/client-app-BlA5Ptxs.d.ts.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"onboarding.js","names":["MessageCard","Typography","Input","FormWarningText","Button"],"sources":["../../src/components-page/onboarding.tsx"],"sourcesContent":["'use client';\n\n\n//===========================================\n// THIS FILE IS AUTO-GENERATED FROM TEMPLATE. DO NOT EDIT IT DIRECTLY UNLESS YOU ALSO EDIT THE CORRESPONDING FILE IN packages/template\n//===========================================\n\nimport { yupResolver } from \"@hookform/resolvers/yup\";\nimport { strictEmailSchema, yupObject } from \"@hexclave/shared/dist/schema-fields\";\nimport { runAsynchronously, runAsynchronouslyWithAlert } from \"@hexclave/shared/dist/utils/promises\";\nimport { Button, Input, Typography } from \"@hexclave/ui\";\nimport { useState } from \"react\";\nimport { useForm } from \"react-hook-form\";\nimport * as yup from \"yup\";\nimport { useStackApp, useUser } from \"..\";\nimport { FormWarningText } from \"../components/elements/form-warning\";\nimport { MessageCard } from \"../components/message-cards/message-card\";\nimport { useTranslation } from \"../lib/translations\";\n\nexport function Onboarding(props: {\n fullPage?: boolean,\n}) {\n const { t } = useTranslation();\n const hexclaveApp = useStackApp();\n const user = useUser({ or: \"return-null\", includeRestricted: true });\n\n // If user is not restricted anymore, redirect to the intended destination\n // redirectToAfterSignIn automatically checks for after_auth_return_to in the URL\n if (user && !user.isRestricted) {\n runAsynchronously(hexclaveApp.redirectToAfterSignIn());\n // TODO: This should return a loading indicator, not just null\n return null;\n }\n\n // If user is anonymous or not logged in, redirect to sign-in\n if (!user || user.isAnonymous) {\n runAsynchronously(hexclaveApp.redirectToSignIn());\n // TODO: This should return a loading indicator, not just null\n return null;\n }\n\n // User is restricted - show appropriate onboarding step based on restricted reason\n const restrictedReason = user.restrictedReason;\n\n if (restrictedReason?.type === \"email_not_verified\") {\n // Check if user has a primary email\n const hasPrimaryEmail = !!user.primaryEmail;\n\n if (!hasPrimaryEmail) {\n // User needs to add an email first\n return <AddEmailForm fullPage={props.fullPage} />;\n }\n\n // User has email but it's not verified\n return <VerifyEmailScreen user={user} email={user.primaryEmail} fullPage={props.fullPage} />;\n }\n\n // Unknown restricted reason - show generic message\n return (\n <MessageCard\n title={t(\"Complete your account setup\")}\n fullPage={!!props.fullPage}\n secondaryButtonText={t(\"Sign out\")}\n secondaryAction={async () => {\n await user.signOut();\n }}\n >\n <p>{t(\"You have not yet completed your account setup. Please reach out to support if you believe this is an error.\")}</p>\n </MessageCard>\n );\n}\n\nfunction AddEmailForm(props: {\n fullPage?: boolean,\n onEmailAdded?: () => void,\n}) {\n const { t } = useTranslation();\n const user = useUser({ or: \"throw\", includeRestricted: true });\n const hexclaveApp = useStackApp();\n const [loading, setLoading] = useState(false);\n\n const emailSchema = yupObject({\n email: strictEmailSchema(t('Please enter a valid email address'))\n .defined()\n .nonEmpty(t('Email is required')),\n });\n\n const { register, handleSubmit, formState: { errors } } = useForm({\n resolver: yupResolver(emailSchema)\n });\n\n const onSubmit = async (data: yup.InferType<typeof emailSchema>) => {\n setLoading(true);\n try {\n await user.update({ primaryEmail: data.email });\n props.onEmailAdded?.();\n } finally {\n setLoading(false);\n }\n };\n\n return (\n <MessageCard\n title={t(\"Add your email address\")}\n fullPage={!!props.fullPage}\n secondaryButtonText={t(\"Sign out\")}\n secondaryAction={async () => {\n await user.signOut();\n }}\n >\n <Typography className=\"mb-4\">\n {t(\"Please add an email address to complete your account setup. We'll send you a verification email.\")}\n </Typography>\n <form\n onSubmit={e => runAsynchronouslyWithAlert(handleSubmit(onSubmit)(e))}\n noValidate\n className='flex flex-col gap-2'\n >\n <Input\n {...register(\"email\")}\n placeholder={t(\"Enter your email\")}\n type=\"email\"\n />\n {errors.email && <FormWarningText text={errors.email.message} />}\n <Button type=\"submit\" loading={loading} className=\"w-full\">\n {t(\"Continue\")}\n </Button>\n </form>\n </MessageCard>\n );\n}\n\nfunction VerifyEmailScreen(props: {\n user: NonNullable<ReturnType<typeof useUser>>,\n email: string,\n fullPage?: boolean,\n}) {\n const { t } = useTranslation();\n const { user, email } = props;\n const [changingEmail, setChangingEmail] = useState(false);\n\n if (changingEmail) {\n return <AddEmailForm fullPage={props.fullPage} onEmailAdded={() => setChangingEmail(false)} />;\n }\n\n return (\n <MessageCard\n title={t(\"Please check your email inbox\")}\n fullPage={!!props.fullPage}\n primaryButtonText={t(\"Resend verification email\")}\n primaryAction={async () => {\n await user.sendVerificationEmail();\n }}\n secondaryButtonText={t(\"Sign out\")}\n secondaryAction={async () => {\n await user.signOut();\n }}\n >\n <Typography>\n {t(\"Please verify your email address \")}\n <span className=\"font-semibold\">{email}</span>\n {\" (\"}\n <button\n type=\"button\"\n className=\"text-primary hover:underline\"\n onClick={() => setChangingEmail(true)}\n >\n {t(\"change\")}\n </button>\n {\"). \"}\n {t(\"Click the button below to resend the verification link.\")}\n </Typography>\n </MessageCard>\n );\n}\n\n"],"mappings":"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"}
1
+ {"version":3,"file":"onboarding.js","names":["MessageCard","Typography","Input","FormWarningText","Button"],"sources":["../../src/components-page/onboarding.tsx"],"sourcesContent":["'use client';\n\n\n//===========================================\n// THIS FILE IS AUTO-GENERATED FROM TEMPLATE. DO NOT EDIT IT DIRECTLY UNLESS YOU ALSO EDIT THE CORRESPONDING FILE IN packages/template\n//===========================================\n\nimport { yupResolver } from \"@hookform/resolvers/yup\";\nimport { strictEmailSchema, yupObject } from \"@hexclave/shared/dist/schema-fields\";\nimport { runAsynchronously, runAsynchronouslyWithAlert } from \"@hexclave/shared/dist/utils/promises\";\nimport { Button, Input, Typography } from \"@hexclave/ui\";\nimport { useState } from \"react\";\nimport { useForm } from \"react-hook-form\";\nimport * as yup from \"yup\";\nimport { useStackApp, useUser } from \"..\";\nimport { FormWarningText } from \"../components/elements/form-warning\";\nimport { MessageCard } from \"../components/message-cards/message-card\";\nimport { useTranslation } from \"../lib/translations\";\n\nexport function Onboarding(props: {\n fullPage?: boolean,\n}) {\n const { t } = useTranslation();\n const hexclaveApp = useStackApp();\n const user = useUser({ or: \"return-null\", includeRestricted: true });\n\n // If user is not restricted anymore, redirect to the intended destination\n // redirectToAfterSignIn automatically checks for after_auth_return_to in the URL\n if (user && !user.isRestricted) {\n runAsynchronously(hexclaveApp.redirectToAfterSignIn());\n // TODO: This should return a loading indicator, not just null\n return null;\n }\n\n // If user is anonymous or not logged in, redirect to sign-in\n if (!user || user.isAnonymous) {\n runAsynchronously(hexclaveApp.redirectToSignIn());\n // TODO: This should return a loading indicator, not just null\n return null;\n }\n\n // User is restricted - show appropriate onboarding step based on restricted reason\n const restrictedReason = user.restrictedReason;\n\n if (restrictedReason?.type === \"email_not_verified\") {\n // Check if user has a primary email\n const hasPrimaryEmail = !!user.primaryEmail;\n\n if (!hasPrimaryEmail) {\n // User needs to add an email first\n return <AddEmailForm fullPage={props.fullPage} />;\n }\n\n // User has email but it's not verified\n return <VerifyEmailScreen user={user} email={user.primaryEmail} fullPage={props.fullPage} />;\n }\n\n if (restrictedReason?.type === \"restricted_by_administrator\") {\n return (\n <MessageCard\n title={t(\"Your account has been restricted\")}\n fullPage={!!props.fullPage}\n secondaryButtonText={t(\"Sign out\")}\n secondaryAction={async () => {\n await user.signOut();\n }}\n >\n {/* The public reason is set by the project's administrators (sign-up rules or manually), so it's the most\n helpful thing we can show; the generic sentence is only a fallback for when they didn't provide one. */}\n <p>{user.restrictedByAdminReason ?? t(\"An administrator has restricted your account. Please reach out to support if you believe this is an error.\")}</p>\n </MessageCard>\n );\n }\n\n // Unknown restricted reason - show generic message\n return (\n <MessageCard\n title={t(\"Complete your account setup\")}\n fullPage={!!props.fullPage}\n secondaryButtonText={t(\"Sign out\")}\n secondaryAction={async () => {\n await user.signOut();\n }}\n >\n <p>{t(\"You have not yet completed your account setup. Please reach out to support if you believe this is an error.\")}</p>\n </MessageCard>\n );\n}\n\nfunction AddEmailForm(props: {\n fullPage?: boolean,\n onEmailAdded?: () => void,\n}) {\n const { t } = useTranslation();\n const user = useUser({ or: \"throw\", includeRestricted: true });\n const hexclaveApp = useStackApp();\n const [loading, setLoading] = useState(false);\n\n const emailSchema = yupObject({\n email: strictEmailSchema(t('Please enter a valid email address'))\n .defined()\n .nonEmpty(t('Email is required')),\n });\n\n const { register, handleSubmit, formState: { errors } } = useForm({\n resolver: yupResolver(emailSchema)\n });\n\n const onSubmit = async (data: yup.InferType<typeof emailSchema>) => {\n setLoading(true);\n try {\n await user.update({ primaryEmail: data.email });\n props.onEmailAdded?.();\n } finally {\n setLoading(false);\n }\n };\n\n return (\n <MessageCard\n title={t(\"Add your email address\")}\n fullPage={!!props.fullPage}\n secondaryButtonText={t(\"Sign out\")}\n secondaryAction={async () => {\n await user.signOut();\n }}\n >\n <Typography className=\"mb-4\">\n {t(\"Please add an email address to complete your account setup. We'll send you a verification email.\")}\n </Typography>\n <form\n onSubmit={e => runAsynchronouslyWithAlert(handleSubmit(onSubmit)(e))}\n noValidate\n className='flex flex-col gap-2'\n >\n <Input\n {...register(\"email\")}\n placeholder={t(\"Enter your email\")}\n type=\"email\"\n />\n {errors.email && <FormWarningText text={errors.email.message} />}\n <Button type=\"submit\" loading={loading} className=\"w-full\">\n {t(\"Continue\")}\n </Button>\n </form>\n </MessageCard>\n );\n}\n\nfunction VerifyEmailScreen(props: {\n user: NonNullable<ReturnType<typeof useUser>>,\n email: string,\n fullPage?: boolean,\n}) {\n const { t } = useTranslation();\n const { user, email } = props;\n const [changingEmail, setChangingEmail] = useState(false);\n\n if (changingEmail) {\n return <AddEmailForm fullPage={props.fullPage} onEmailAdded={() => setChangingEmail(false)} />;\n }\n\n return (\n <MessageCard\n title={t(\"Please check your email inbox\")}\n fullPage={!!props.fullPage}\n primaryButtonText={t(\"Resend verification email\")}\n primaryAction={async () => {\n await user.sendVerificationEmail();\n }}\n secondaryButtonText={t(\"Sign out\")}\n secondaryAction={async () => {\n await user.signOut();\n }}\n >\n <Typography>\n {t(\"Please verify your email address \")}\n <span className=\"font-semibold\">{email}</span>\n {\" (\"}\n <button\n type=\"button\"\n className=\"text-primary hover:underline\"\n onClick={() => setChangingEmail(true)}\n >\n {t(\"change\")}\n </button>\n {\"). \"}\n {t(\"Click the button below to resend the verification link.\")}\n </Typography>\n </MessageCard>\n );\n}\n\n"],"mappings":"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"}
@@ -0,0 +1 @@
1
+ export { };
@@ -0,0 +1,151 @@
1
+ const require_rolldown_runtime = require("../rolldown-runtime-D6vf50IK.js");
2
+ let react = require("react");
3
+ react = require_rolldown_runtime.__toESM(react);
4
+ let react_jsx_runtime = require("react/jsx-runtime");
5
+ let react_dom_client = require("react-dom/client");
6
+ let vitest = require("vitest");
7
+ let __onboarding_js = require("./onboarding.js");
8
+ let ___providers_translation_provider_client_js = require("../providers/translation-provider-client.js");
9
+ //#region src/components-page/onboarding.test.tsx
10
+ // @vitest-environment jsdom
11
+ const appMockState = vitest.vi.hoisted(() => ({
12
+ app: null,
13
+ user: null
14
+ }));
15
+ vitest.vi.mock("..", () => ({
16
+ useStackApp: () => {
17
+ if (appMockState.app == null) throw new Error("Expected test app to be set before rendering.");
18
+ return appMockState.app;
19
+ },
20
+ useUser: () => appMockState.user
21
+ }));
22
+ vitest.vi.mock("@hexclave/ui", async () => {
23
+ await import("react");
24
+ return {
25
+ Button: (props) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
26
+ type: "button",
27
+ onClick: props.onClick,
28
+ children: props.children
29
+ }),
30
+ Input: (props) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", { ...props }),
31
+ Typography: (props) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { children: props.children }),
32
+ cn: (...classes) => classes.filter(Boolean).join(" ")
33
+ };
34
+ });
35
+ const previousActEnvironment = Reflect.get(globalThis, "IS_REACT_ACT_ENVIRONMENT");
36
+ function createAppTestDouble() {
37
+ return {
38
+ redirectToSignIn: vitest.vi.fn(async () => {}),
39
+ redirectToAfterSignIn: vitest.vi.fn(async () => {})
40
+ };
41
+ }
42
+ function createUserTestDouble(user) {
43
+ return {
44
+ isAnonymous: false,
45
+ isRestricted: true,
46
+ restrictedReason: null,
47
+ restrictedByAdmin: false,
48
+ restrictedByAdminReason: null,
49
+ primaryEmail: "user@example.com",
50
+ signOut: vitest.vi.fn(async () => {}),
51
+ ...user
52
+ };
53
+ }
54
+ let root = null;
55
+ let container = null;
56
+ async function renderOnboarding(options) {
57
+ appMockState.app = options.app;
58
+ appMockState.user = options.user;
59
+ container = document.createElement("div");
60
+ document.body.append(container);
61
+ root = (0, react_dom_client.createRoot)(container);
62
+ await (0, react.act)(async () => {
63
+ root?.render(/* @__PURE__ */ (0, react_jsx_runtime.jsx)(___providers_translation_provider_client_js.TranslationProviderClient, {
64
+ quetzalKeys: /* @__PURE__ */ new Map(),
65
+ quetzalLocale: /* @__PURE__ */ new Map(),
66
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__onboarding_js.Onboarding, {})
67
+ }));
68
+ });
69
+ return container;
70
+ }
71
+ (0, vitest.describe)("Onboarding", () => {
72
+ (0, vitest.beforeEach)(() => {
73
+ Reflect.set(globalThis, "IS_REACT_ACT_ENVIRONMENT", true);
74
+ });
75
+ (0, vitest.afterEach)(() => {
76
+ (0, react.act)(() => {
77
+ root?.unmount();
78
+ });
79
+ container?.remove();
80
+ root = null;
81
+ container = null;
82
+ appMockState.app = null;
83
+ appMockState.user = null;
84
+ vitest.vi.restoreAllMocks();
85
+ Reflect.set(globalThis, "IS_REACT_ACT_ENVIRONMENT", previousActEnvironment);
86
+ });
87
+ (0, vitest.it)("redirects signed-out users to sign-in", async () => {
88
+ const app = createAppTestDouble();
89
+ const container = await renderOnboarding({
90
+ app,
91
+ user: null
92
+ });
93
+ (0, vitest.expect)(app.redirectToSignIn).toHaveBeenCalled();
94
+ (0, vitest.expect)(container.textContent).toBe("");
95
+ });
96
+ (0, vitest.it)("redirects anonymous users to sign-in", async () => {
97
+ const app = createAppTestDouble();
98
+ const container = await renderOnboarding({
99
+ app,
100
+ user: createUserTestDouble({
101
+ isAnonymous: true,
102
+ restrictedReason: { type: "anonymous" }
103
+ })
104
+ });
105
+ (0, vitest.expect)(app.redirectToSignIn).toHaveBeenCalled();
106
+ (0, vitest.expect)(container.textContent).toBe("");
107
+ });
108
+ (0, vitest.it)("redirects users that are no longer restricted to their destination", async () => {
109
+ const app = createAppTestDouble();
110
+ await renderOnboarding({
111
+ app,
112
+ user: createUserTestDouble({ isRestricted: false })
113
+ });
114
+ (0, vitest.expect)(app.redirectToAfterSignIn).toHaveBeenCalled();
115
+ (0, vitest.expect)(app.redirectToSignIn).not.toHaveBeenCalled();
116
+ });
117
+ (0, vitest.it)("shows the administrator's public reason to admin-restricted users", async () => {
118
+ const container = await renderOnboarding({
119
+ app: createAppTestDouble(),
120
+ user: createUserTestDouble({
121
+ restrictedReason: { type: "restricted_by_administrator" },
122
+ restrictedByAdmin: true,
123
+ restrictedByAdminReason: "Your sign-up was flagged by our fraud prevention rules."
124
+ })
125
+ });
126
+ (0, vitest.expect)(container.textContent).toContain("Your account has been restricted");
127
+ (0, vitest.expect)(container.textContent).toContain("Your sign-up was flagged by our fraud prevention rules.");
128
+ (0, vitest.expect)(container.textContent).not.toContain("Complete your account setup");
129
+ });
130
+ (0, vitest.it)("falls back to a generic restricted message when no public reason is set", async () => {
131
+ const container = await renderOnboarding({
132
+ app: createAppTestDouble(),
133
+ user: createUserTestDouble({
134
+ restrictedReason: { type: "restricted_by_administrator" },
135
+ restrictedByAdmin: true,
136
+ restrictedByAdminReason: null
137
+ })
138
+ });
139
+ (0, vitest.expect)(container.textContent).toContain("Your account has been restricted");
140
+ (0, vitest.expect)(container.textContent).toContain("An administrator has restricted your account.");
141
+ });
142
+ (0, vitest.it)("shows the setup-required message for unknown restricted reasons", async () => {
143
+ (0, vitest.expect)((await renderOnboarding({
144
+ app: createAppTestDouble(),
145
+ user: createUserTestDouble({ restrictedReason: { type: "some_future_reason" } })
146
+ })).textContent).toContain("Complete your account setup");
147
+ });
148
+ });
149
+ //#endregion
150
+
151
+ //# sourceMappingURL=onboarding.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"onboarding.test.js","names":["vi","TranslationProviderClient","Onboarding"],"sources":["../../src/components-page/onboarding.test.tsx"],"sourcesContent":["\n//===========================================\n// THIS FILE IS AUTO-GENERATED FROM TEMPLATE. DO NOT EDIT IT DIRECTLY UNLESS YOU ALSO EDIT THE CORRESPONDING FILE IN packages/template\n//===========================================\n// @vitest-environment jsdom\n\nimport React, { act } from \"react\";\nimport { createRoot, type Root } from \"react-dom/client\";\nimport { afterEach, beforeEach, describe, expect, it, vi } from \"vitest\";\nimport type { CurrentUser } from \"../lib/hexclave-app\";\nimport type { StackClientApp } from \"../lib/hexclave-app/apps/interfaces/client-app\";\nimport { TranslationProviderClient } from \"../providers/translation-provider-client\";\nimport { Onboarding } from \"./onboarding\";\n\nconst appMockState = vi.hoisted(() => ({\n app: null as unknown,\n user: null as unknown,\n}));\n\nvi.mock(\"..\", () => ({\n useStackApp: () => {\n if (appMockState.app == null) {\n throw new Error(\"Expected test app to be set before rendering.\");\n }\n return appMockState.app;\n },\n useUser: () => appMockState.user,\n}));\n\nvi.mock(\"@hexclave/ui\", async () => {\n const React = await import(\"react\");\n return {\n Button: (props: { children: React.ReactNode, onClick?: () => void }) => (\n <button type=\"button\" onClick={props.onClick}>{props.children}</button>\n ),\n Input: (props: Record<string, unknown>) => <input {...props} />,\n Typography: (props: { children: React.ReactNode }) => <div>{props.children}</div>,\n cn: (...classes: (string | false | null | undefined)[]) => classes.filter(Boolean).join(\" \"),\n };\n});\n\nconst previousActEnvironment = Reflect.get(globalThis, \"IS_REACT_ACT_ENVIRONMENT\");\n\nfunction createAppTestDouble() {\n const app = {\n redirectToSignIn: vi.fn(async () => {}),\n redirectToAfterSignIn: vi.fn(async () => {}),\n };\n\n // This test double intentionally implements only the StackClientApp surface that Onboarding touches.\n return app as unknown as StackClientApp<true> & {\n redirectToSignIn: ReturnType<typeof vi.fn>,\n redirectToAfterSignIn: ReturnType<typeof vi.fn>,\n };\n}\n\nfunction createUserTestDouble(user: Partial<CurrentUser>) {\n // Same as the app test double: only the surface Onboarding touches.\n return {\n isAnonymous: false,\n isRestricted: true,\n restrictedReason: null,\n restrictedByAdmin: false,\n restrictedByAdminReason: null,\n primaryEmail: \"user@example.com\",\n signOut: vi.fn(async () => {}),\n ...user,\n } as unknown as CurrentUser;\n}\n\nlet root: Root | null = null;\nlet container: HTMLDivElement | null = null;\n\nasync function renderOnboarding(options: {\n app: StackClientApp<true>,\n user: unknown,\n}) {\n appMockState.app = options.app;\n appMockState.user = options.user;\n container = document.createElement(\"div\");\n document.body.append(container);\n root = createRoot(container);\n await act(async () => {\n root?.render(\n <TranslationProviderClient quetzalKeys={new Map()} quetzalLocale={new Map()}>\n <Onboarding />\n </TranslationProviderClient>\n );\n });\n return container;\n}\n\ndescribe(\"Onboarding\", () => {\n beforeEach(() => {\n Reflect.set(globalThis, \"IS_REACT_ACT_ENVIRONMENT\", true);\n });\n\n afterEach(() => {\n act(() => {\n root?.unmount();\n });\n container?.remove();\n root = null;\n container = null;\n appMockState.app = null;\n appMockState.user = null;\n vi.restoreAllMocks();\n Reflect.set(globalThis, \"IS_REACT_ACT_ENVIRONMENT\", previousActEnvironment);\n });\n\n it(\"redirects signed-out users to sign-in\", async () => {\n const app = createAppTestDouble();\n\n const container = await renderOnboarding({ app, user: null });\n\n expect(app.redirectToSignIn).toHaveBeenCalled();\n expect(container.textContent).toBe(\"\");\n });\n\n it(\"redirects anonymous users to sign-in\", async () => {\n const app = createAppTestDouble();\n\n const container = await renderOnboarding({ app, user: createUserTestDouble({ isAnonymous: true, restrictedReason: { type: \"anonymous\" } }) });\n\n expect(app.redirectToSignIn).toHaveBeenCalled();\n expect(container.textContent).toBe(\"\");\n });\n\n it(\"redirects users that are no longer restricted to their destination\", async () => {\n const app = createAppTestDouble();\n\n await renderOnboarding({ app, user: createUserTestDouble({ isRestricted: false }) });\n\n expect(app.redirectToAfterSignIn).toHaveBeenCalled();\n expect(app.redirectToSignIn).not.toHaveBeenCalled();\n });\n\n it(\"shows the administrator's public reason to admin-restricted users\", async () => {\n const app = createAppTestDouble();\n\n const container = await renderOnboarding({\n app,\n user: createUserTestDouble({\n restrictedReason: { type: \"restricted_by_administrator\" },\n restrictedByAdmin: true,\n restrictedByAdminReason: \"Your sign-up was flagged by our fraud prevention rules.\",\n }),\n });\n\n expect(container.textContent).toContain(\"Your account has been restricted\");\n expect(container.textContent).toContain(\"Your sign-up was flagged by our fraud prevention rules.\");\n expect(container.textContent).not.toContain(\"Complete your account setup\");\n });\n\n it(\"falls back to a generic restricted message when no public reason is set\", async () => {\n const app = createAppTestDouble();\n\n const container = await renderOnboarding({\n app,\n user: createUserTestDouble({\n restrictedReason: { type: \"restricted_by_administrator\" },\n restrictedByAdmin: true,\n restrictedByAdminReason: null,\n }),\n });\n\n expect(container.textContent).toContain(\"Your account has been restricted\");\n expect(container.textContent).toContain(\"An administrator has restricted your account.\");\n });\n\n it(\"shows the setup-required message for unknown restricted reasons\", async () => {\n const app = createAppTestDouble();\n\n const container = await renderOnboarding({\n app,\n // The reason types are a closed union, so an unknown reason can only come from a newer backend.\n user: createUserTestDouble({ restrictedReason: { type: \"some_future_reason\" } as unknown as CurrentUser[\"restrictedReason\"] }),\n });\n\n expect(container.textContent).toContain(\"Complete your account setup\");\n });\n});\n"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import { a as StackClientApp } from "../client-app-BlA5Ptxs.js";
1
+ import { a as StackClientApp } from "../client-app-CE8FTKgO.js";
2
2
  //#region src/dev-tool/dev-tool-core.d.ts
3
3
  declare function createDevTool(app: StackClientApp<true>): () => void;
4
4
  //#endregion
@@ -1,4 +1,4 @@
1
- import { a as StackClientApp } from "../client-app-BlA5Ptxs.js";
1
+ import { a as StackClientApp } from "../client-app-CE8FTKgO.js";
2
2
  //#region src/dev-tool/index.d.ts
3
3
  /**
4
4
  * Mounts the Hexclave dev tool on the page.
@@ -3,8 +3,8 @@ import { Button, Typography, cn } from "@hexclave/ui";
3
3
  import "react";
4
4
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
5
5
  import { XIcon } from "lucide-react";
6
- import { useStackApp } from "../../index.js";
7
6
  import { useHash } from "@hexclave/shared/dist/hooks/use-hash";
7
+ import { useStackApp } from "../../index.js";
8
8
  //#region src/components/elements/sidebar-layout.tsx
9
9
  function SidebarLayout(props) {
10
10
  const hash = useHash();
@@ -26,7 +26,8 @@ function Onboarding(props) {
26
26
  runAsynchronously(hexclaveApp.redirectToSignIn());
27
27
  return null;
28
28
  }
29
- if (user.restrictedReason?.type === "email_not_verified") {
29
+ const restrictedReason = user.restrictedReason;
30
+ if (restrictedReason?.type === "email_not_verified") {
30
31
  if (!!!user.primaryEmail) return /* @__PURE__ */ jsx(AddEmailForm, { fullPage: props.fullPage });
31
32
  return /* @__PURE__ */ jsx(VerifyEmailScreen, {
32
33
  user,
@@ -34,6 +35,15 @@ function Onboarding(props) {
34
35
  fullPage: props.fullPage
35
36
  });
36
37
  }
38
+ if (restrictedReason?.type === "restricted_by_administrator") return /* @__PURE__ */ jsx(MessageCard$1, {
39
+ title: t("Your account has been restricted"),
40
+ fullPage: !!props.fullPage,
41
+ secondaryButtonText: t("Sign out"),
42
+ secondaryAction: async () => {
43
+ await user.signOut();
44
+ },
45
+ children: /* @__PURE__ */ jsx("p", { children: user.restrictedByAdminReason ?? t("An administrator has restricted your account. Please reach out to support if you believe this is an error.") })
46
+ });
37
47
  return /* @__PURE__ */ jsx(MessageCard$1, {
38
48
  title: t("Complete your account setup"),
39
49
  fullPage: !!props.fullPage,
@@ -1 +1 @@
1
- {"version":3,"file":"onboarding.js","names":["MessageCard"],"sources":["../../../src/components-page/onboarding.tsx"],"sourcesContent":["'use client';\n\n\n//===========================================\n// THIS FILE IS AUTO-GENERATED FROM TEMPLATE. DO NOT EDIT IT DIRECTLY UNLESS YOU ALSO EDIT THE CORRESPONDING FILE IN packages/template\n//===========================================\n\nimport { yupResolver } from \"@hookform/resolvers/yup\";\nimport { strictEmailSchema, yupObject } from \"@hexclave/shared/dist/schema-fields\";\nimport { runAsynchronously, runAsynchronouslyWithAlert } from \"@hexclave/shared/dist/utils/promises\";\nimport { Button, Input, Typography } from \"@hexclave/ui\";\nimport { useState } from \"react\";\nimport { useForm } from \"react-hook-form\";\nimport * as yup from \"yup\";\nimport { useStackApp, useUser } from \"..\";\nimport { FormWarningText } from \"../components/elements/form-warning\";\nimport { MessageCard } from \"../components/message-cards/message-card\";\nimport { useTranslation } from \"../lib/translations\";\n\nexport function Onboarding(props: {\n fullPage?: boolean,\n}) {\n const { t } = useTranslation();\n const hexclaveApp = useStackApp();\n const user = useUser({ or: \"return-null\", includeRestricted: true });\n\n // If user is not restricted anymore, redirect to the intended destination\n // redirectToAfterSignIn automatically checks for after_auth_return_to in the URL\n if (user && !user.isRestricted) {\n runAsynchronously(hexclaveApp.redirectToAfterSignIn());\n // TODO: This should return a loading indicator, not just null\n return null;\n }\n\n // If user is anonymous or not logged in, redirect to sign-in\n if (!user || user.isAnonymous) {\n runAsynchronously(hexclaveApp.redirectToSignIn());\n // TODO: This should return a loading indicator, not just null\n return null;\n }\n\n // User is restricted - show appropriate onboarding step based on restricted reason\n const restrictedReason = user.restrictedReason;\n\n if (restrictedReason?.type === \"email_not_verified\") {\n // Check if user has a primary email\n const hasPrimaryEmail = !!user.primaryEmail;\n\n if (!hasPrimaryEmail) {\n // User needs to add an email first\n return <AddEmailForm fullPage={props.fullPage} />;\n }\n\n // User has email but it's not verified\n return <VerifyEmailScreen user={user} email={user.primaryEmail} fullPage={props.fullPage} />;\n }\n\n // Unknown restricted reason - show generic message\n return (\n <MessageCard\n title={t(\"Complete your account setup\")}\n fullPage={!!props.fullPage}\n secondaryButtonText={t(\"Sign out\")}\n secondaryAction={async () => {\n await user.signOut();\n }}\n >\n <p>{t(\"You have not yet completed your account setup. Please reach out to support if you believe this is an error.\")}</p>\n </MessageCard>\n );\n}\n\nfunction AddEmailForm(props: {\n fullPage?: boolean,\n onEmailAdded?: () => void,\n}) {\n const { t } = useTranslation();\n const user = useUser({ or: \"throw\", includeRestricted: true });\n const hexclaveApp = useStackApp();\n const [loading, setLoading] = useState(false);\n\n const emailSchema = yupObject({\n email: strictEmailSchema(t('Please enter a valid email address'))\n .defined()\n .nonEmpty(t('Email is required')),\n });\n\n const { register, handleSubmit, formState: { errors } } = useForm({\n resolver: yupResolver(emailSchema)\n });\n\n const onSubmit = async (data: yup.InferType<typeof emailSchema>) => {\n setLoading(true);\n try {\n await user.update({ primaryEmail: data.email });\n props.onEmailAdded?.();\n } finally {\n setLoading(false);\n }\n };\n\n return (\n <MessageCard\n title={t(\"Add your email address\")}\n fullPage={!!props.fullPage}\n secondaryButtonText={t(\"Sign out\")}\n secondaryAction={async () => {\n await user.signOut();\n }}\n >\n <Typography className=\"mb-4\">\n {t(\"Please add an email address to complete your account setup. We'll send you a verification email.\")}\n </Typography>\n <form\n onSubmit={e => runAsynchronouslyWithAlert(handleSubmit(onSubmit)(e))}\n noValidate\n className='flex flex-col gap-2'\n >\n <Input\n {...register(\"email\")}\n placeholder={t(\"Enter your email\")}\n type=\"email\"\n />\n {errors.email && <FormWarningText text={errors.email.message} />}\n <Button type=\"submit\" loading={loading} className=\"w-full\">\n {t(\"Continue\")}\n </Button>\n </form>\n </MessageCard>\n );\n}\n\nfunction VerifyEmailScreen(props: {\n user: NonNullable<ReturnType<typeof useUser>>,\n email: string,\n fullPage?: boolean,\n}) {\n const { t } = useTranslation();\n const { user, email } = props;\n const [changingEmail, setChangingEmail] = useState(false);\n\n if (changingEmail) {\n return <AddEmailForm fullPage={props.fullPage} onEmailAdded={() => setChangingEmail(false)} />;\n }\n\n return (\n <MessageCard\n title={t(\"Please check your email inbox\")}\n fullPage={!!props.fullPage}\n primaryButtonText={t(\"Resend verification email\")}\n primaryAction={async () => {\n await user.sendVerificationEmail();\n }}\n secondaryButtonText={t(\"Sign out\")}\n secondaryAction={async () => {\n await user.signOut();\n }}\n >\n <Typography>\n {t(\"Please verify your email address \")}\n <span className=\"font-semibold\">{email}</span>\n {\" (\"}\n <button\n type=\"button\"\n className=\"text-primary hover:underline\"\n onClick={() => setChangingEmail(true)}\n >\n {t(\"change\")}\n </button>\n {\"). \"}\n {t(\"Click the button below to resend the verification link.\")}\n </Typography>\n </MessageCard>\n );\n}\n\n"],"mappings":"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"}
1
+ {"version":3,"file":"onboarding.js","names":["MessageCard"],"sources":["../../../src/components-page/onboarding.tsx"],"sourcesContent":["'use client';\n\n\n//===========================================\n// THIS FILE IS AUTO-GENERATED FROM TEMPLATE. DO NOT EDIT IT DIRECTLY UNLESS YOU ALSO EDIT THE CORRESPONDING FILE IN packages/template\n//===========================================\n\nimport { yupResolver } from \"@hookform/resolvers/yup\";\nimport { strictEmailSchema, yupObject } from \"@hexclave/shared/dist/schema-fields\";\nimport { runAsynchronously, runAsynchronouslyWithAlert } from \"@hexclave/shared/dist/utils/promises\";\nimport { Button, Input, Typography } from \"@hexclave/ui\";\nimport { useState } from \"react\";\nimport { useForm } from \"react-hook-form\";\nimport * as yup from \"yup\";\nimport { useStackApp, useUser } from \"..\";\nimport { FormWarningText } from \"../components/elements/form-warning\";\nimport { MessageCard } from \"../components/message-cards/message-card\";\nimport { useTranslation } from \"../lib/translations\";\n\nexport function Onboarding(props: {\n fullPage?: boolean,\n}) {\n const { t } = useTranslation();\n const hexclaveApp = useStackApp();\n const user = useUser({ or: \"return-null\", includeRestricted: true });\n\n // If user is not restricted anymore, redirect to the intended destination\n // redirectToAfterSignIn automatically checks for after_auth_return_to in the URL\n if (user && !user.isRestricted) {\n runAsynchronously(hexclaveApp.redirectToAfterSignIn());\n // TODO: This should return a loading indicator, not just null\n return null;\n }\n\n // If user is anonymous or not logged in, redirect to sign-in\n if (!user || user.isAnonymous) {\n runAsynchronously(hexclaveApp.redirectToSignIn());\n // TODO: This should return a loading indicator, not just null\n return null;\n }\n\n // User is restricted - show appropriate onboarding step based on restricted reason\n const restrictedReason = user.restrictedReason;\n\n if (restrictedReason?.type === \"email_not_verified\") {\n // Check if user has a primary email\n const hasPrimaryEmail = !!user.primaryEmail;\n\n if (!hasPrimaryEmail) {\n // User needs to add an email first\n return <AddEmailForm fullPage={props.fullPage} />;\n }\n\n // User has email but it's not verified\n return <VerifyEmailScreen user={user} email={user.primaryEmail} fullPage={props.fullPage} />;\n }\n\n if (restrictedReason?.type === \"restricted_by_administrator\") {\n return (\n <MessageCard\n title={t(\"Your account has been restricted\")}\n fullPage={!!props.fullPage}\n secondaryButtonText={t(\"Sign out\")}\n secondaryAction={async () => {\n await user.signOut();\n }}\n >\n {/* The public reason is set by the project's administrators (sign-up rules or manually), so it's the most\n helpful thing we can show; the generic sentence is only a fallback for when they didn't provide one. */}\n <p>{user.restrictedByAdminReason ?? t(\"An administrator has restricted your account. Please reach out to support if you believe this is an error.\")}</p>\n </MessageCard>\n );\n }\n\n // Unknown restricted reason - show generic message\n return (\n <MessageCard\n title={t(\"Complete your account setup\")}\n fullPage={!!props.fullPage}\n secondaryButtonText={t(\"Sign out\")}\n secondaryAction={async () => {\n await user.signOut();\n }}\n >\n <p>{t(\"You have not yet completed your account setup. Please reach out to support if you believe this is an error.\")}</p>\n </MessageCard>\n );\n}\n\nfunction AddEmailForm(props: {\n fullPage?: boolean,\n onEmailAdded?: () => void,\n}) {\n const { t } = useTranslation();\n const user = useUser({ or: \"throw\", includeRestricted: true });\n const hexclaveApp = useStackApp();\n const [loading, setLoading] = useState(false);\n\n const emailSchema = yupObject({\n email: strictEmailSchema(t('Please enter a valid email address'))\n .defined()\n .nonEmpty(t('Email is required')),\n });\n\n const { register, handleSubmit, formState: { errors } } = useForm({\n resolver: yupResolver(emailSchema)\n });\n\n const onSubmit = async (data: yup.InferType<typeof emailSchema>) => {\n setLoading(true);\n try {\n await user.update({ primaryEmail: data.email });\n props.onEmailAdded?.();\n } finally {\n setLoading(false);\n }\n };\n\n return (\n <MessageCard\n title={t(\"Add your email address\")}\n fullPage={!!props.fullPage}\n secondaryButtonText={t(\"Sign out\")}\n secondaryAction={async () => {\n await user.signOut();\n }}\n >\n <Typography className=\"mb-4\">\n {t(\"Please add an email address to complete your account setup. We'll send you a verification email.\")}\n </Typography>\n <form\n onSubmit={e => runAsynchronouslyWithAlert(handleSubmit(onSubmit)(e))}\n noValidate\n className='flex flex-col gap-2'\n >\n <Input\n {...register(\"email\")}\n placeholder={t(\"Enter your email\")}\n type=\"email\"\n />\n {errors.email && <FormWarningText text={errors.email.message} />}\n <Button type=\"submit\" loading={loading} className=\"w-full\">\n {t(\"Continue\")}\n </Button>\n </form>\n </MessageCard>\n );\n}\n\nfunction VerifyEmailScreen(props: {\n user: NonNullable<ReturnType<typeof useUser>>,\n email: string,\n fullPage?: boolean,\n}) {\n const { t } = useTranslation();\n const { user, email } = props;\n const [changingEmail, setChangingEmail] = useState(false);\n\n if (changingEmail) {\n return <AddEmailForm fullPage={props.fullPage} onEmailAdded={() => setChangingEmail(false)} />;\n }\n\n return (\n <MessageCard\n title={t(\"Please check your email inbox\")}\n fullPage={!!props.fullPage}\n primaryButtonText={t(\"Resend verification email\")}\n primaryAction={async () => {\n await user.sendVerificationEmail();\n }}\n secondaryButtonText={t(\"Sign out\")}\n secondaryAction={async () => {\n await user.signOut();\n }}\n >\n <Typography>\n {t(\"Please verify your email address \")}\n <span className=\"font-semibold\">{email}</span>\n {\" (\"}\n <button\n type=\"button\"\n className=\"text-primary hover:underline\"\n onClick={() => setChangingEmail(true)}\n >\n {t(\"change\")}\n </button>\n {\"). \"}\n {t(\"Click the button below to resend the verification link.\")}\n </Typography>\n </MessageCard>\n );\n}\n\n"],"mappings":";;;;;;;;;;;;;AAmBA,SAAgB,WAAW,OAExB;CACD,MAAM,EAAE,MAAM,eAAe;CAC7B,MAAM,cAAc,YAAY;CAChC,MAAM,OAAO,QAAQ;EAAE,IAAI;EAAe,mBAAmB;CAAK,CAAC;CAInE,IAAI,QAAQ,CAAC,KAAK,cAAc;EAC9B,kBAAkB,YAAY,sBAAsB,CAAC;EAErD,OAAO;CACT;CAGA,IAAI,CAAC,QAAQ,KAAK,aAAa;EAC7B,kBAAkB,YAAY,iBAAiB,CAAC;EAEhD,OAAO;CACT;CAGA,MAAM,mBAAmB,KAAK;CAE9B,IAAI,kBAAkB,SAAS,sBAAsB;EAInD,IAAI,CAAC,CAFoB,CAAC,KAAK,cAI7B,OAAO,oBAAC,cAAD,EAAc,UAAU,MAAM,SAAW,CAAA;EAIlD,OAAO,oBAAC,mBAAD;GAAyB;GAAM,OAAO,KAAK;GAAc,UAAU,MAAM;EAAW,CAAA;CAC7F;CAEA,IAAI,kBAAkB,SAAS,+BAC7B,OACE,oBAACA,eAAD;EACE,OAAO,EAAE,kCAAkC;EAC3C,UAAU,CAAC,CAAC,MAAM;EAClB,qBAAqB,EAAE,UAAU;EACjC,iBAAiB,YAAY;GAC3B,MAAM,KAAK,QAAQ;EACrB;YAIA,oBAAC,KAAD,EAAA,UAAI,KAAK,2BAA2B,EAAE,4GAA4G,EAAK,CAAA;CAC5I,CAAA;CAKjB,OACE,oBAACA,eAAD;EACE,OAAO,EAAE,6BAA6B;EACtC,UAAU,CAAC,CAAC,MAAM;EAClB,qBAAqB,EAAE,UAAU;EACjC,iBAAiB,YAAY;GAC3B,MAAM,KAAK,QAAQ;EACrB;YAEA,oBAAC,KAAD,EAAA,UAAI,EAAE,6GAA6G,EAAK,CAAA;CAC7G,CAAA;AAEjB;AAEA,SAAS,aAAa,OAGnB;CACD,MAAM,EAAE,MAAM,eAAe;CAC7B,MAAM,OAAO,QAAQ;EAAE,IAAI;EAAS,mBAAmB;CAAK,CAAC;CACzC,YAAY;CAChC,MAAM,CAAC,SAAS,cAAc,SAAS,KAAK;CAQ5C,MAAM,EAAE,UAAU,cAAc,WAAW,EAAE,aAAa,QAAQ,EAChE,UAAU,YAPQ,UAAU,EAC5B,OAAO,kBAAkB,EAAE,oCAAoC,CAAC,CAAC,CAC9D,QAAQ,CAAC,CACT,SAAS,EAAE,mBAAmB,CAAC,EACpC,CAGkC,CAAC,EACnC,CAAC;CAED,MAAM,WAAW,OAAO,SAA4C;EAClE,WAAW,IAAI;EACf,IAAI;GACF,MAAM,KAAK,OAAO,EAAE,cAAc,KAAK,MAAM,CAAC;GAC9C,MAAM,eAAe;EACvB,UAAU;GACR,WAAW,KAAK;EAClB;CACF;CAEA,OACE,qBAACA,eAAD;EACE,OAAO,EAAE,wBAAwB;EACjC,UAAU,CAAC,CAAC,MAAM;EAClB,qBAAqB,EAAE,UAAU;EACjC,iBAAiB,YAAY;GAC3B,MAAM,KAAK,QAAQ;EACrB;YANF,CAQE,oBAAC,YAAD;GAAY,WAAU;aACnB,EAAE,kGAAkG;EAC3F,CAAA,GACZ,qBAAC,QAAD;GACE,WAAU,MAAK,2BAA2B,aAAa,QAAQ,CAAC,CAAC,CAAC,CAAC;GACnE,YAAA;GACA,WAAU;aAHZ;IAKE,oBAAC,OAAD;KACE,GAAI,SAAS,OAAO;KACpB,aAAa,EAAE,kBAAkB;KACjC,MAAK;IACN,CAAA;IACA,OAAO,SAAS,oBAAC,iBAAD,EAAiB,MAAM,OAAO,MAAM,QAAU,CAAA;IAC/D,oBAAC,QAAD;KAAQ,MAAK;KAAkB;KAAS,WAAU;eAC/C,EAAE,UAAU;IACP,CAAA;GACJ;IACK;;AAEjB;AAEA,SAAS,kBAAkB,OAIxB;CACD,MAAM,EAAE,MAAM,eAAe;CAC7B,MAAM,EAAE,MAAM,UAAU;CACxB,MAAM,CAAC,eAAe,oBAAoB,SAAS,KAAK;CAExD,IAAI,eACF,OAAO,oBAAC,cAAD;EAAc,UAAU,MAAM;EAAU,oBAAoB,iBAAiB,KAAK;CAAI,CAAA;CAG/F,OACE,oBAACA,eAAD;EACE,OAAO,EAAE,+BAA+B;EACxC,UAAU,CAAC,CAAC,MAAM;EAClB,mBAAmB,EAAE,2BAA2B;EAChD,eAAe,YAAY;GACzB,MAAM,KAAK,sBAAsB;EACnC;EACA,qBAAqB,EAAE,UAAU;EACjC,iBAAiB,YAAY;GAC3B,MAAM,KAAK,QAAQ;EACrB;YAEA,qBAAC,YAAD,EAAA,UAAA;GACG,EAAE,mCAAmC;GACtC,oBAAC,QAAD;IAAM,WAAU;cAAiB;GAAY,CAAA;GAC5C;GACD,oBAAC,UAAD;IACE,MAAK;IACL,WAAU;IACV,eAAe,iBAAiB,IAAI;cAEnC,EAAE,QAAQ;GACL,CAAA;GACP;GACA,EAAE,yDAAyD;EAClD,EAAA,CAAA;CACD,CAAA;AAEjB"}
@@ -0,0 +1 @@
1
+ export { };
@@ -0,0 +1,150 @@
1
+ import { act } from "react";
2
+ import { jsx } from "react/jsx-runtime";
3
+ import { createRoot } from "react-dom/client";
4
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
5
+ import { Onboarding } from "./onboarding.js";
6
+ import { TranslationProviderClient } from "../providers/translation-provider-client.js";
7
+ //#region src/components-page/onboarding.test.tsx
8
+ // @vitest-environment jsdom
9
+ const appMockState = vi.hoisted(() => ({
10
+ app: null,
11
+ user: null
12
+ }));
13
+ vi.mock("..", () => ({
14
+ useStackApp: () => {
15
+ if (appMockState.app == null) throw new Error("Expected test app to be set before rendering.");
16
+ return appMockState.app;
17
+ },
18
+ useUser: () => appMockState.user
19
+ }));
20
+ vi.mock("@hexclave/ui", async () => {
21
+ await import("react");
22
+ return {
23
+ Button: (props) => /* @__PURE__ */ jsx("button", {
24
+ type: "button",
25
+ onClick: props.onClick,
26
+ children: props.children
27
+ }),
28
+ Input: (props) => /* @__PURE__ */ jsx("input", { ...props }),
29
+ Typography: (props) => /* @__PURE__ */ jsx("div", { children: props.children }),
30
+ cn: (...classes) => classes.filter(Boolean).join(" ")
31
+ };
32
+ });
33
+ const previousActEnvironment = Reflect.get(globalThis, "IS_REACT_ACT_ENVIRONMENT");
34
+ function createAppTestDouble() {
35
+ return {
36
+ redirectToSignIn: vi.fn(async () => {}),
37
+ redirectToAfterSignIn: vi.fn(async () => {})
38
+ };
39
+ }
40
+ function createUserTestDouble(user) {
41
+ return {
42
+ isAnonymous: false,
43
+ isRestricted: true,
44
+ restrictedReason: null,
45
+ restrictedByAdmin: false,
46
+ restrictedByAdminReason: null,
47
+ primaryEmail: "user@example.com",
48
+ signOut: vi.fn(async () => {}),
49
+ ...user
50
+ };
51
+ }
52
+ let root = null;
53
+ let container = null;
54
+ async function renderOnboarding(options) {
55
+ appMockState.app = options.app;
56
+ appMockState.user = options.user;
57
+ container = document.createElement("div");
58
+ document.body.append(container);
59
+ root = createRoot(container);
60
+ await act(async () => {
61
+ root?.render(/* @__PURE__ */ jsx(TranslationProviderClient, {
62
+ quetzalKeys: /* @__PURE__ */ new Map(),
63
+ quetzalLocale: /* @__PURE__ */ new Map(),
64
+ children: /* @__PURE__ */ jsx(Onboarding, {})
65
+ }));
66
+ });
67
+ return container;
68
+ }
69
+ describe("Onboarding", () => {
70
+ beforeEach(() => {
71
+ Reflect.set(globalThis, "IS_REACT_ACT_ENVIRONMENT", true);
72
+ });
73
+ afterEach(() => {
74
+ act(() => {
75
+ root?.unmount();
76
+ });
77
+ container?.remove();
78
+ root = null;
79
+ container = null;
80
+ appMockState.app = null;
81
+ appMockState.user = null;
82
+ vi.restoreAllMocks();
83
+ Reflect.set(globalThis, "IS_REACT_ACT_ENVIRONMENT", previousActEnvironment);
84
+ });
85
+ it("redirects signed-out users to sign-in", async () => {
86
+ const app = createAppTestDouble();
87
+ const container = await renderOnboarding({
88
+ app,
89
+ user: null
90
+ });
91
+ expect(app.redirectToSignIn).toHaveBeenCalled();
92
+ expect(container.textContent).toBe("");
93
+ });
94
+ it("redirects anonymous users to sign-in", async () => {
95
+ const app = createAppTestDouble();
96
+ const container = await renderOnboarding({
97
+ app,
98
+ user: createUserTestDouble({
99
+ isAnonymous: true,
100
+ restrictedReason: { type: "anonymous" }
101
+ })
102
+ });
103
+ expect(app.redirectToSignIn).toHaveBeenCalled();
104
+ expect(container.textContent).toBe("");
105
+ });
106
+ it("redirects users that are no longer restricted to their destination", async () => {
107
+ const app = createAppTestDouble();
108
+ await renderOnboarding({
109
+ app,
110
+ user: createUserTestDouble({ isRestricted: false })
111
+ });
112
+ expect(app.redirectToAfterSignIn).toHaveBeenCalled();
113
+ expect(app.redirectToSignIn).not.toHaveBeenCalled();
114
+ });
115
+ it("shows the administrator's public reason to admin-restricted users", async () => {
116
+ const container = await renderOnboarding({
117
+ app: createAppTestDouble(),
118
+ user: createUserTestDouble({
119
+ restrictedReason: { type: "restricted_by_administrator" },
120
+ restrictedByAdmin: true,
121
+ restrictedByAdminReason: "Your sign-up was flagged by our fraud prevention rules."
122
+ })
123
+ });
124
+ expect(container.textContent).toContain("Your account has been restricted");
125
+ expect(container.textContent).toContain("Your sign-up was flagged by our fraud prevention rules.");
126
+ expect(container.textContent).not.toContain("Complete your account setup");
127
+ });
128
+ it("falls back to a generic restricted message when no public reason is set", async () => {
129
+ const container = await renderOnboarding({
130
+ app: createAppTestDouble(),
131
+ user: createUserTestDouble({
132
+ restrictedReason: { type: "restricted_by_administrator" },
133
+ restrictedByAdmin: true,
134
+ restrictedByAdminReason: null
135
+ })
136
+ });
137
+ expect(container.textContent).toContain("Your account has been restricted");
138
+ expect(container.textContent).toContain("An administrator has restricted your account.");
139
+ });
140
+ it("shows the setup-required message for unknown restricted reasons", async () => {
141
+ expect((await renderOnboarding({
142
+ app: createAppTestDouble(),
143
+ user: createUserTestDouble({ restrictedReason: { type: "some_future_reason" } })
144
+ })).textContent).toContain("Complete your account setup");
145
+ });
146
+ });
147
+ //#endregion
148
+ export {};
149
+
150
+ //# sourceMappingURL=onboarding.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"onboarding.test.js","names":[],"sources":["../../../src/components-page/onboarding.test.tsx"],"sourcesContent":["\n//===========================================\n// THIS FILE IS AUTO-GENERATED FROM TEMPLATE. DO NOT EDIT IT DIRECTLY UNLESS YOU ALSO EDIT THE CORRESPONDING FILE IN packages/template\n//===========================================\n// @vitest-environment jsdom\n\nimport React, { act } from \"react\";\nimport { createRoot, type Root } from \"react-dom/client\";\nimport { afterEach, beforeEach, describe, expect, it, vi } from \"vitest\";\nimport type { CurrentUser } from \"../lib/hexclave-app\";\nimport type { StackClientApp } from \"../lib/hexclave-app/apps/interfaces/client-app\";\nimport { TranslationProviderClient } from \"../providers/translation-provider-client\";\nimport { Onboarding } from \"./onboarding\";\n\nconst appMockState = vi.hoisted(() => ({\n app: null as unknown,\n user: null as unknown,\n}));\n\nvi.mock(\"..\", () => ({\n useStackApp: () => {\n if (appMockState.app == null) {\n throw new Error(\"Expected test app to be set before rendering.\");\n }\n return appMockState.app;\n },\n useUser: () => appMockState.user,\n}));\n\nvi.mock(\"@hexclave/ui\", async () => {\n const React = await import(\"react\");\n return {\n Button: (props: { children: React.ReactNode, onClick?: () => void }) => (\n <button type=\"button\" onClick={props.onClick}>{props.children}</button>\n ),\n Input: (props: Record<string, unknown>) => <input {...props} />,\n Typography: (props: { children: React.ReactNode }) => <div>{props.children}</div>,\n cn: (...classes: (string | false | null | undefined)[]) => classes.filter(Boolean).join(\" \"),\n };\n});\n\nconst previousActEnvironment = Reflect.get(globalThis, \"IS_REACT_ACT_ENVIRONMENT\");\n\nfunction createAppTestDouble() {\n const app = {\n redirectToSignIn: vi.fn(async () => {}),\n redirectToAfterSignIn: vi.fn(async () => {}),\n };\n\n // This test double intentionally implements only the StackClientApp surface that Onboarding touches.\n return app as unknown as StackClientApp<true> & {\n redirectToSignIn: ReturnType<typeof vi.fn>,\n redirectToAfterSignIn: ReturnType<typeof vi.fn>,\n };\n}\n\nfunction createUserTestDouble(user: Partial<CurrentUser>) {\n // Same as the app test double: only the surface Onboarding touches.\n return {\n isAnonymous: false,\n isRestricted: true,\n restrictedReason: null,\n restrictedByAdmin: false,\n restrictedByAdminReason: null,\n primaryEmail: \"user@example.com\",\n signOut: vi.fn(async () => {}),\n ...user,\n } as unknown as CurrentUser;\n}\n\nlet root: Root | null = null;\nlet container: HTMLDivElement | null = null;\n\nasync function renderOnboarding(options: {\n app: StackClientApp<true>,\n user: unknown,\n}) {\n appMockState.app = options.app;\n appMockState.user = options.user;\n container = document.createElement(\"div\");\n document.body.append(container);\n root = createRoot(container);\n await act(async () => {\n root?.render(\n <TranslationProviderClient quetzalKeys={new Map()} quetzalLocale={new Map()}>\n <Onboarding />\n </TranslationProviderClient>\n );\n });\n return container;\n}\n\ndescribe(\"Onboarding\", () => {\n beforeEach(() => {\n Reflect.set(globalThis, \"IS_REACT_ACT_ENVIRONMENT\", true);\n });\n\n afterEach(() => {\n act(() => {\n root?.unmount();\n });\n container?.remove();\n root = null;\n container = null;\n appMockState.app = null;\n appMockState.user = null;\n vi.restoreAllMocks();\n Reflect.set(globalThis, \"IS_REACT_ACT_ENVIRONMENT\", previousActEnvironment);\n });\n\n it(\"redirects signed-out users to sign-in\", async () => {\n const app = createAppTestDouble();\n\n const container = await renderOnboarding({ app, user: null });\n\n expect(app.redirectToSignIn).toHaveBeenCalled();\n expect(container.textContent).toBe(\"\");\n });\n\n it(\"redirects anonymous users to sign-in\", async () => {\n const app = createAppTestDouble();\n\n const container = await renderOnboarding({ app, user: createUserTestDouble({ isAnonymous: true, restrictedReason: { type: \"anonymous\" } }) });\n\n expect(app.redirectToSignIn).toHaveBeenCalled();\n expect(container.textContent).toBe(\"\");\n });\n\n it(\"redirects users that are no longer restricted to their destination\", async () => {\n const app = createAppTestDouble();\n\n await renderOnboarding({ app, user: createUserTestDouble({ isRestricted: false }) });\n\n expect(app.redirectToAfterSignIn).toHaveBeenCalled();\n expect(app.redirectToSignIn).not.toHaveBeenCalled();\n });\n\n it(\"shows the administrator's public reason to admin-restricted users\", async () => {\n const app = createAppTestDouble();\n\n const container = await renderOnboarding({\n app,\n user: createUserTestDouble({\n restrictedReason: { type: \"restricted_by_administrator\" },\n restrictedByAdmin: true,\n restrictedByAdminReason: \"Your sign-up was flagged by our fraud prevention rules.\",\n }),\n });\n\n expect(container.textContent).toContain(\"Your account has been restricted\");\n expect(container.textContent).toContain(\"Your sign-up was flagged by our fraud prevention rules.\");\n expect(container.textContent).not.toContain(\"Complete your account setup\");\n });\n\n it(\"falls back to a generic restricted message when no public reason is set\", async () => {\n const app = createAppTestDouble();\n\n const container = await renderOnboarding({\n app,\n user: createUserTestDouble({\n restrictedReason: { type: \"restricted_by_administrator\" },\n restrictedByAdmin: true,\n restrictedByAdminReason: null,\n }),\n });\n\n expect(container.textContent).toContain(\"Your account has been restricted\");\n expect(container.textContent).toContain(\"An administrator has restricted your account.\");\n });\n\n it(\"shows the setup-required message for unknown restricted reasons\", async () => {\n const app = createAppTestDouble();\n\n const container = await renderOnboarding({\n app,\n // The reason types are a closed union, so an unknown reason can only come from a newer backend.\n user: createUserTestDouble({ restrictedReason: { type: \"some_future_reason\" } as unknown as CurrentUser[\"restrictedReason\"] }),\n });\n\n expect(container.textContent).toContain(\"Complete your account setup\");\n });\n});\n"],"mappings":";;;;;;;;AAcA,MAAM,eAAe,GAAG,eAAe;CACrC,KAAK;CACL,MAAM;AACR,EAAE;AAEF,GAAG,KAAK,aAAa;CACnB,mBAAmB;EACjB,IAAI,aAAa,OAAO,MACtB,MAAM,IAAI,MAAM,+CAA+C;EAEjE,OAAO,aAAa;CACtB;CACA,eAAe,aAAa;AAC9B,EAAE;AAEF,GAAG,KAAK,gBAAgB,YAAY;CACpB,MAAM,OAAO;CAC3B,OAAO;EACL,SAAS,UACP,oBAAC,UAAD;GAAQ,MAAK;GAAS,SAAS,MAAM;aAAU,MAAM;EAAiB,CAAA;EAExE,QAAQ,UAAmC,oBAAC,SAAD,EAAO,GAAI,MAAQ,CAAA;EAC9D,aAAa,UAAyC,oBAAC,OAAD,EAAA,UAAM,MAAM,SAAc,CAAA;EAChF,KAAK,GAAG,YAAmD,QAAQ,OAAO,OAAO,CAAC,CAAC,KAAK,GAAG;CAC7F;AACF,CAAC;AAED,MAAM,yBAAyB,QAAQ,IAAI,YAAY,0BAA0B;AAEjF,SAAS,sBAAsB;CAO7B,OAAO;EALL,kBAAkB,GAAG,GAAG,YAAY,CAAC,CAAC;EACtC,uBAAuB,GAAG,GAAG,YAAY,CAAC,CAAC;CAIpC;AAIX;AAEA,SAAS,qBAAqB,MAA4B;CAExD,OAAO;EACL,aAAa;EACb,cAAc;EACd,kBAAkB;EAClB,mBAAmB;EACnB,yBAAyB;EACzB,cAAc;EACd,SAAS,GAAG,GAAG,YAAY,CAAC,CAAC;EAC7B,GAAG;CACL;AACF;AAEA,IAAI,OAAoB;AACxB,IAAI,YAAmC;AAEvC,eAAe,iBAAiB,SAG7B;CACD,aAAa,MAAM,QAAQ;CAC3B,aAAa,OAAO,QAAQ;CAC5B,YAAY,SAAS,cAAc,KAAK;CACxC,SAAS,KAAK,OAAO,SAAS;CAC9B,OAAO,WAAW,SAAS;CAC3B,MAAM,IAAI,YAAY;EACpB,MAAM,OACJ,oBAAC,2BAAD;GAA2B,6BAAa,IAAI,IAAI;GAAG,+BAAe,IAAI,IAAI;aACxE,oBAAC,YAAD,CAAa,CAAA;EACY,CAAA,CAC7B;CACF,CAAC;CACD,OAAO;AACT;AAEA,SAAS,oBAAoB;CAC3B,iBAAiB;EACf,QAAQ,IAAI,YAAY,4BAA4B,IAAI;CAC1D,CAAC;CAED,gBAAgB;EACd,UAAU;GACR,MAAM,QAAQ;EAChB,CAAC;EACD,WAAW,OAAO;EAClB,OAAO;EACP,YAAY;EACZ,aAAa,MAAM;EACnB,aAAa,OAAO;EACpB,GAAG,gBAAgB;EACnB,QAAQ,IAAI,YAAY,4BAA4B,sBAAsB;CAC5E,CAAC;CAED,GAAG,yCAAyC,YAAY;EACtD,MAAM,MAAM,oBAAoB;EAEhC,MAAM,YAAY,MAAM,iBAAiB;GAAE;GAAK,MAAM;EAAK,CAAC;EAE5D,OAAO,IAAI,gBAAgB,CAAC,CAAC,iBAAiB;EAC9C,OAAO,UAAU,WAAW,CAAC,CAAC,KAAK,EAAE;CACvC,CAAC;CAED,GAAG,wCAAwC,YAAY;EACrD,MAAM,MAAM,oBAAoB;EAEhC,MAAM,YAAY,MAAM,iBAAiB;GAAE;GAAK,MAAM,qBAAqB;IAAE,aAAa;IAAM,kBAAkB,EAAE,MAAM,YAAY;GAAE,CAAC;EAAE,CAAC;EAE5I,OAAO,IAAI,gBAAgB,CAAC,CAAC,iBAAiB;EAC9C,OAAO,UAAU,WAAW,CAAC,CAAC,KAAK,EAAE;CACvC,CAAC;CAED,GAAG,sEAAsE,YAAY;EACnF,MAAM,MAAM,oBAAoB;EAEhC,MAAM,iBAAiB;GAAE;GAAK,MAAM,qBAAqB,EAAE,cAAc,MAAM,CAAC;EAAE,CAAC;EAEnF,OAAO,IAAI,qBAAqB,CAAC,CAAC,iBAAiB;EACnD,OAAO,IAAI,gBAAgB,CAAC,CAAC,IAAI,iBAAiB;CACpD,CAAC;CAED,GAAG,qEAAqE,YAAY;EAGlF,MAAM,YAAY,MAAM,iBAAiB;GACvC,KAHU,oBAGR;GACF,MAAM,qBAAqB;IACzB,kBAAkB,EAAE,MAAM,8BAA8B;IACxD,mBAAmB;IACnB,yBAAyB;GAC3B,CAAC;EACH,CAAC;EAED,OAAO,UAAU,WAAW,CAAC,CAAC,UAAU,kCAAkC;EAC1E,OAAO,UAAU,WAAW,CAAC,CAAC,UAAU,yDAAyD;EACjG,OAAO,UAAU,WAAW,CAAC,CAAC,IAAI,UAAU,6BAA6B;CAC3E,CAAC;CAED,GAAG,2EAA2E,YAAY;EAGxF,MAAM,YAAY,MAAM,iBAAiB;GACvC,KAHU,oBAGR;GACF,MAAM,qBAAqB;IACzB,kBAAkB,EAAE,MAAM,8BAA8B;IACxD,mBAAmB;IACnB,yBAAyB;GAC3B,CAAC;EACH,CAAC;EAED,OAAO,UAAU,WAAW,CAAC,CAAC,UAAU,kCAAkC;EAC1E,OAAO,UAAU,WAAW,CAAC,CAAC,UAAU,+CAA+C;CACzF,CAAC;CAED,GAAG,mEAAmE,YAAY;EAShF,QAAO,MANiB,iBAAiB;GACvC,KAHU,oBAGR;GAEF,MAAM,qBAAqB,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,EAAgD,CAAC;EAC/H,CAAC,EAAA,CAEgB,WAAW,CAAC,CAAC,UAAU,6BAA6B;CACvE,CAAC;AACH,CAAC"}