@hexclave/next 1.0.5 → 1.0.8

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 (91) hide show
  1. package/dist/components-page/account-settings/payments/payments-panel.js +3 -3
  2. package/dist/components-page/account-settings/payments/payments-panel.js.map +1 -1
  3. package/dist/components-page/hexclave-handler-client.d.ts +13 -1
  4. package/dist/components-page/hexclave-handler-client.d.ts.map +1 -1
  5. package/dist/components-page/hexclave-handler-client.js +44 -9
  6. package/dist/components-page/hexclave-handler-client.js.map +1 -1
  7. package/dist/components-page/hexclave-handler-client.test.d.ts +1 -0
  8. package/dist/components-page/hexclave-handler-client.test.js +51 -0
  9. package/dist/components-page/hexclave-handler-client.test.js.map +1 -0
  10. package/dist/dev-tool/dev-tool-core.js +2 -2
  11. package/dist/dev-tool/dev-tool-core.js.map +1 -1
  12. package/dist/esm/components-page/account-settings/payments/payments-panel.js +2 -2
  13. package/dist/esm/components-page/account-settings/payments/payments-panel.js.map +1 -1
  14. package/dist/esm/components-page/hexclave-handler-client.d.ts +12 -1
  15. package/dist/esm/components-page/hexclave-handler-client.d.ts.map +1 -1
  16. package/dist/esm/components-page/hexclave-handler-client.js +46 -12
  17. package/dist/esm/components-page/hexclave-handler-client.js.map +1 -1
  18. package/dist/esm/components-page/hexclave-handler-client.test.d.ts +1 -0
  19. package/dist/esm/components-page/hexclave-handler-client.test.js +51 -0
  20. package/dist/esm/components-page/hexclave-handler-client.test.js.map +1 -0
  21. package/dist/esm/dev-tool/dev-tool-core.js +2 -2
  22. package/dist/esm/dev-tool/dev-tool-core.js.map +1 -1
  23. package/dist/esm/generated/env.d.ts +26 -0
  24. package/dist/esm/{lib → generated}/env.d.ts.map +1 -1
  25. package/dist/esm/generated/env.js +67 -0
  26. package/dist/esm/generated/env.js.map +1 -0
  27. package/dist/esm/generated/quetzal-translations.d.ts +2 -2
  28. package/dist/esm/global.d.ts +8 -1
  29. package/dist/esm/global.d.ts.map +1 -0
  30. package/dist/esm/lib/hexclave-app/apps/implementations/admin-app-impl.d.ts.map +1 -1
  31. package/dist/esm/lib/hexclave-app/apps/implementations/client-app-impl.cross-domain.test.js +263 -3
  32. package/dist/esm/lib/hexclave-app/apps/implementations/client-app-impl.cross-domain.test.js.map +1 -1
  33. package/dist/esm/lib/hexclave-app/apps/implementations/client-app-impl.d.ts +3 -1
  34. package/dist/esm/lib/hexclave-app/apps/implementations/client-app-impl.d.ts.map +1 -1
  35. package/dist/esm/lib/hexclave-app/apps/implementations/client-app-impl.js +53 -26
  36. package/dist/esm/lib/hexclave-app/apps/implementations/client-app-impl.js.map +1 -1
  37. package/dist/esm/lib/hexclave-app/apps/implementations/common.d.ts +8 -8
  38. package/dist/esm/lib/hexclave-app/apps/implementations/common.d.ts.map +1 -1
  39. package/dist/esm/lib/hexclave-app/apps/implementations/common.js +28 -14
  40. package/dist/esm/lib/hexclave-app/apps/implementations/common.js.map +1 -1
  41. package/dist/esm/lib/hexclave-app/apps/implementations/server-app-impl.d.ts +1 -1
  42. package/dist/esm/lib/hexclave-app/apps/implementations/server-app-impl.js +1 -1
  43. package/dist/esm/lib/hexclave-app/url-targets.d.ts.map +1 -1
  44. package/dist/esm/lib/hexclave-app/url-targets.js +25 -11
  45. package/dist/esm/lib/hexclave-app/url-targets.js.map +1 -1
  46. package/dist/esm/lib/hexclave-app/url-targets.test.js +12 -0
  47. package/dist/esm/lib/hexclave-app/url-targets.test.js.map +1 -1
  48. package/dist/generated/env.d.ts +26 -0
  49. package/dist/{lib → generated}/env.d.ts.map +1 -1
  50. package/dist/generated/env.js +69 -0
  51. package/dist/generated/env.js.map +1 -0
  52. package/dist/generated/quetzal-translations.d.ts +2 -2
  53. package/dist/global.d.ts +8 -1
  54. package/dist/global.d.ts.map +1 -0
  55. package/dist/lib/hexclave-app/apps/implementations/admin-app-impl.d.ts.map +1 -1
  56. package/dist/lib/hexclave-app/apps/implementations/client-app-impl.cross-domain.test.js +263 -3
  57. package/dist/lib/hexclave-app/apps/implementations/client-app-impl.cross-domain.test.js.map +1 -1
  58. package/dist/lib/hexclave-app/apps/implementations/client-app-impl.d.ts +3 -1
  59. package/dist/lib/hexclave-app/apps/implementations/client-app-impl.d.ts.map +1 -1
  60. package/dist/lib/hexclave-app/apps/implementations/client-app-impl.js +52 -25
  61. package/dist/lib/hexclave-app/apps/implementations/client-app-impl.js.map +1 -1
  62. package/dist/lib/hexclave-app/apps/implementations/common.d.ts +8 -8
  63. package/dist/lib/hexclave-app/apps/implementations/common.d.ts.map +1 -1
  64. package/dist/lib/hexclave-app/apps/implementations/common.js +28 -14
  65. package/dist/lib/hexclave-app/apps/implementations/common.js.map +1 -1
  66. package/dist/lib/hexclave-app/apps/implementations/server-app-impl.d.ts +1 -1
  67. package/dist/lib/hexclave-app/apps/implementations/server-app-impl.js +1 -1
  68. package/dist/lib/hexclave-app/url-targets.d.ts.map +1 -1
  69. package/dist/lib/hexclave-app/url-targets.js +25 -11
  70. package/dist/lib/hexclave-app/url-targets.js.map +1 -1
  71. package/dist/lib/hexclave-app/url-targets.test.js +12 -0
  72. package/dist/lib/hexclave-app/url-targets.test.js.map +1 -1
  73. package/package.json +9 -7
  74. package/src/components-page/account-settings/payments/payments-panel.tsx +2 -2
  75. package/src/components-page/hexclave-handler-client.test.tsx +64 -0
  76. package/src/components-page/hexclave-handler-client.tsx +50 -11
  77. package/src/dev-tool/dev-tool-core.ts +2 -2
  78. package/src/generated/.gitignore +1 -1
  79. package/src/global.d.ts +8 -1
  80. package/src/lib/hexclave-app/apps/implementations/client-app-impl.cross-domain.test.ts +316 -3
  81. package/src/lib/hexclave-app/apps/implementations/client-app-impl.ts +69 -25
  82. package/src/lib/hexclave-app/apps/implementations/common.ts +34 -14
  83. package/src/lib/hexclave-app/url-targets.test.ts +17 -0
  84. package/src/lib/hexclave-app/url-targets.ts +25 -7
  85. package/dist/esm/lib/env.d.ts +0 -42
  86. package/dist/esm/lib/env.js +0 -93
  87. package/dist/esm/lib/env.js.map +0 -1
  88. package/dist/lib/env.d.ts +0 -42
  89. package/dist/lib/env.js +0 -95
  90. package/dist/lib/env.js.map +0 -1
  91. package/src/lib/env.ts +0 -93
@@ -13,7 +13,7 @@ let ___section_js = require("../section.js");
13
13
  let _________index_js = require("../../../index.js");
14
14
  let _stripe_react_stripe_js = require("@stripe/react-stripe-js");
15
15
  let _stripe_stripe_js = require("@stripe/stripe-js");
16
- let _________lib_env_js = require("../../../lib/env.js");
16
+ let _________generated_env_js = require("../../../generated/env.js");
17
17
 
18
18
  //#region src/components-page/account-settings/payments/payments-panel.tsx
19
19
  function formatPaymentMethod(pm) {
@@ -192,7 +192,7 @@ function RealPaymentsPanel(props) {
192
192
  const [switchToProductId, setSwitchToProductId] = (0, react.useState)(null);
193
193
  const stripePromise = (0, react.useMemo)(() => {
194
194
  if (!setupIntentStripeAccountId) return null;
195
- const publishableKey = _________lib_env_js.envVars.NEXT_PUBLIC_STACK_STRIPE_PUBLISHABLE_KEY;
195
+ const publishableKey = _________generated_env_js.envVars.HEXCLAVE_STRIPE_PUBLISHABLE_KEY;
196
196
  if (!publishableKey) return null;
197
197
  return (0, _stripe_stripe_js.loadStripe)(publishableKey, { stripeAccount: setupIntentStripeAccountId });
198
198
  }, [setupIntentStripeAccountId]);
@@ -205,7 +205,7 @@ function RealPaymentsPanel(props) {
205
205
  });
206
206
  return;
207
207
  }
208
- alert(`An unhandled error occurred. Please ${_________lib_env_js.envVars.NODE_ENV === "development" ? "check the browser console for the full error." : "report this to the developer."}\n\n${error}`);
208
+ alert(`An unhandled error occurred. Please ${_________generated_env_js.envVars.NODE_ENV === "development" ? "check the browser console for the full error." : "report this to the developer."}\n\n${error}`);
209
209
  };
210
210
  const openPaymentDialog = () => {
211
211
  (0, _hexclave_shared_dist_utils_promises.runAsynchronously)(async () => {
@@ -1 +1 @@
1
- {"version":3,"file":"payments-panel.js","names":["Typography","CardElement","Button","Section","envVars","KnownErrors","ActionDialog","Skeleton","Elements","Result","Select","SelectTrigger","SelectValue","SelectContent","SelectItem","Separator","Table","TableHeader","TableRow","TableHead","TableBody","TableCell"],"sources":["../../../../src/components-page/account-settings/payments/payments-panel.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 { KnownErrors } from \"@hexclave/shared\";\nimport { runAsynchronously } from \"@hexclave/shared/dist/utils/promises\";\nimport { ActionDialog, Button, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, Separator, Skeleton, Table, TableBody, TableCell, TableHead, TableHeader, TableRow, toast, Typography } from \"@hexclave/ui\";\nimport { CardElement, Elements, useElements, useStripe } from \"@stripe/react-stripe-js\";\nimport { loadStripe } from \"@stripe/stripe-js\";\nimport { useMemo, useState } from \"react\";\nimport { useStackApp } from \"../../..\";\nimport { envVars } from \"../../../lib/env\";\nimport { useTranslation } from \"../../../lib/translations\";\nimport { Section } from \"../section\";\nimport { Result } from \"@hexclave/shared/dist/utils/results\";\nimport type { CustomerInvoiceStatus, CustomerInvoicesList, CustomerInvoicesListOptions } from \"../../../lib/hexclave-app/customers\";\n\ntype PaymentMethodSummary = {\n id: string,\n brand: string | null,\n last4: string | null,\n exp_month: number | null,\n exp_year: number | null,\n} | null;\n\nfunction formatPaymentMethod(pm: NonNullable<PaymentMethodSummary>) {\n const details = [\n pm.brand ? pm.brand.toUpperCase() : null,\n pm.last4 ? `•••• ${pm.last4}` : null,\n pm.exp_month && pm.exp_year ? `exp ${pm.exp_month}/${pm.exp_year}` : null,\n ].filter(Boolean);\n return details.join(\" · \");\n}\n\nconst formatInvoiceStatus = (status: CustomerInvoiceStatus, t: (value: string) => string) => {\n if (!status) {\n return t(\"Unknown\");\n }\n switch (status) {\n case \"draft\": {\n return t(\"Draft\");\n }\n case \"open\": {\n return t(\"Open\");\n }\n case \"paid\": {\n return t(\"Paid\");\n }\n case \"uncollectible\": {\n return t(\"Uncollectible\");\n }\n case \"void\": {\n return t(\"Void\");\n }\n default: {\n return t(\"Unknown\");\n }\n }\n};\n\nconst formatInvoiceAmount = (amountTotal: number | null | undefined, t: (value: string) => string) => {\n if (typeof amountTotal !== \"number\" || Number.isNaN(amountTotal)) {\n return t(\"Unknown\");\n }\n const normalized = amountTotal / 100;\n const formatted = new Intl.NumberFormat(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 }).format(normalized);\n return `$${formatted}`;\n};\n\nconst formatInvoiceDate = (date: Date | null | undefined, t: (value: string) => string) => {\n if (!date || Number.isNaN(date.getTime())) {\n return t(\"Unknown\");\n }\n return new Intl.DateTimeFormat(undefined, { year: \"numeric\", month: \"short\", day: \"numeric\" }).format(date);\n};\n\ntype CustomerBilling = {\n hasCustomer: boolean,\n defaultPaymentMethod: PaymentMethodSummary,\n};\n\ntype CustomerPaymentMethodSetupIntent = {\n clientSecret: string,\n stripeAccountId: string,\n};\n\ntype CustomerLike = {\n id: string,\n useBilling: () => CustomerBilling,\n useProducts: () => Array<{\n id: string | null,\n quantity: number,\n displayName: string,\n customerType: \"user\" | \"team\" | \"custom\",\n type?: \"one_time\" | \"subscription\",\n switchOptions?: Array<{\n productId: string,\n displayName: string,\n prices: Record<string, { interval?: [number, \"day\" | \"week\" | \"month\" | \"year\"] }>,\n }>,\n subscription: null | {\n subscriptionId: string | null,\n currentPeriodEnd: Date | null,\n cancelAtPeriodEnd: boolean,\n isCancelable: boolean,\n },\n }>,\n useInvoices: (options?: CustomerInvoicesListOptions) => CustomerInvoicesList,\n createPaymentMethodSetupIntent: () => Promise<CustomerPaymentMethodSetupIntent>,\n setDefaultPaymentMethodFromSetupIntent: (setupIntentId: string) => Promise<PaymentMethodSummary>,\n switchSubscription: (options: { fromProductId: string, toProductId: string, priceId?: string, quantity?: number }) => Promise<void>,\n};\n\nfunction SetDefaultPaymentMethodForm(props: {\n clientSecret: string,\n onSetupIntentSucceeded: (setupIntentId: string) => Promise<void>,\n}) {\n const stripe = useStripe();\n const elements = useElements();\n const [errorMessage, setErrorMessage] = useState<string | null>(null);\n const darkMode = \"color-scheme\" in document.documentElement.style && document.documentElement.style[\"color-scheme\"] === \"dark\";\n\n return (\n <div className=\"space-y-4\">\n <div className=\"space-y-2\">\n <Typography className=\"font-medium\">Card details</Typography>\n <div className=\"rounded-md border border-input p-3\">\n <CardElement options={{ hidePostalCode: true, style: { base: { color: darkMode ? \"white\" : \"black\" } } }} />\n </div>\n </div>\n {errorMessage && (\n <Typography variant=\"secondary\" type=\"footnote\">\n {errorMessage}\n </Typography>\n )}\n <Button\n onClick={async () => {\n if (!stripe || !elements) {\n setErrorMessage(\"Stripe is still loading. Please try again.\");\n return;\n }\n const card = elements.getElement(CardElement);\n if (!card) {\n setErrorMessage(\"Card element not found.\");\n return;\n }\n\n const result = await stripe.confirmCardSetup(props.clientSecret, {\n payment_method: { card },\n });\n if (result.error) {\n setErrorMessage(result.error.message ?? \"Failed to save payment method.\");\n return;\n }\n if (!result.setupIntent.id) {\n setErrorMessage(\"No setup intent returned from Stripe.\");\n return;\n }\n await props.onSetupIntentSucceeded(result.setupIntent.id);\n }}\n >\n Save payment method\n </Button>\n </div>\n );\n}\n\nexport function PaymentsPanel(props: {\n title?: string,\n customer?: CustomerLike,\n customerType?: \"user\" | \"team\",\n mockMode?: boolean,\n}) {\n if (props.mockMode) {\n return <MockPaymentsPanel title={props.title} />;\n }\n if (!props.customer) {\n return null;\n }\n return <RealPaymentsPanel title={props.title} customer={props.customer} customerType={props.customerType ?? \"user\"} />;\n}\n\nfunction MockPaymentsPanel(props: { title?: string }) {\n const { t } = useTranslation();\n const defaultPaymentMethod: PaymentMethodSummary = {\n id: \"pm_mock\",\n brand: \"visa\",\n last4: \"4242\",\n exp_month: 12,\n exp_year: 2030,\n };\n\n return (\n <div className=\"space-y-4\">\n {props.title && <Typography className=\"font-medium\">{props.title}</Typography>}\n <Section\n title={t(\"Payment method\")}\n description={t(\"Manage the default payment method used for subscriptions and invoices.\")}\n >\n <Typography>{formatPaymentMethod(defaultPaymentMethod)}</Typography>\n <Button disabled>\n {t(\"Update payment method\")}\n </Button>\n </Section>\n\n <Section\n title={t(\"Active plans\")}\n description={t(\"View your active plans and purchases.\")}\n >\n <div className=\"space-y-3\">\n <div className=\"flex items-start justify-between gap-4\">\n <div className=\"min-w-0\">\n <Typography className=\"truncate\">{t(\"Pro\")}</Typography>\n <Typography variant=\"secondary\" type=\"footnote\">{t(\"Renews on\")} Jan 1, 2030</Typography>\n </div>\n <Button disabled variant=\"secondary\" color=\"neutral\">\n {t(\"Cancel subscription\")}\n </Button>\n </div>\n <div className=\"flex items-start justify-between gap-4\">\n <div className=\"min-w-0\">\n <Typography className=\"truncate\">{t(\"Credits pack\")}</Typography>\n <Typography variant=\"secondary\" type=\"footnote\">{t(\"One-time purchase\")}</Typography>\n </div>\n </div>\n </div>\n </Section>\n </div>\n );\n}\n\nfunction RealPaymentsPanel(props: { title?: string, customer: CustomerLike, customerType: \"user\" | \"team\" }) {\n const { t } = useTranslation();\n const hexclaveApp = useStackApp();\n const billing = props.customer.useBilling();\n const defaultPaymentMethod = billing.defaultPaymentMethod;\n const products = props.customer.useProducts();\n const invoices = props.customer.useInvoices({ limit: 10 });\n const productsForCustomerType = products.filter(product => product.customerType === props.customerType);\n\n const [paymentDialogOpen, setPaymentDialogOpen] = useState(false);\n const [setupIntentClientSecret, setSetupIntentClientSecret] = useState<string | null>(null);\n const [setupIntentStripeAccountId, setSetupIntentStripeAccountId] = useState<string | null>(null);\n const [cancelTarget, setCancelTarget] = useState<{ productId: string, subscriptionId?: string } | null>(null);\n const [switchFromProductId, setSwitchFromProductId] = useState<string | null>(null);\n const [switchToProductId, setSwitchToProductId] = useState<string | null>(null);\n\n const stripePromise = useMemo(() => {\n if (!setupIntentStripeAccountId) return null;\n const publishableKey = envVars.NEXT_PUBLIC_STACK_STRIPE_PUBLISHABLE_KEY;\n if (!publishableKey) return null;\n return loadStripe(publishableKey, { stripeAccount: setupIntentStripeAccountId });\n }, [setupIntentStripeAccountId]);\n\n const handleAsyncError = (error: unknown) => {\n if (error instanceof KnownErrors.DefaultPaymentMethodRequired) {\n toast({\n title: t(\"No default payment method\"),\n description: t(\"Add a payment method before switching plans.\"),\n variant: \"destructive\",\n });\n return;\n }\n alert(`An unhandled error occurred. Please ${envVars.NODE_ENV === \"development\" ? \"check the browser console for the full error.\" : \"report this to the developer.\"}\\n\\n${error}`);\n };\n\n const openPaymentDialog = () => {\n runAsynchronously(async () => {\n setPaymentDialogOpen(true);\n const res = await props.customer.createPaymentMethodSetupIntent();\n setSetupIntentClientSecret(res.clientSecret);\n setSetupIntentStripeAccountId(res.stripeAccountId);\n }, { onError: handleAsyncError });\n };\n\n const closePaymentDialog = () => {\n setPaymentDialogOpen(false);\n setSetupIntentClientSecret(null);\n setSetupIntentStripeAccountId(null);\n };\n\n const openSwitchDialog = (productId: string, firstOptionId: string | null) => {\n setSwitchFromProductId(productId);\n setSwitchToProductId(firstOptionId);\n };\n\n const closeSwitchDialog = () => {\n setSwitchFromProductId(null);\n setSwitchToProductId(null);\n };\n\n const switchSourceProduct = switchFromProductId\n ? productsForCustomerType.find((product) => product.id === switchFromProductId) ?? null\n : null;\n const switchOptions = switchSourceProduct?.switchOptions ?? [];\n const selectedSwitchOption = switchOptions.find((option) => option.productId === switchToProductId) ?? null;\n const selectedPriceId = selectedSwitchOption ? (Object.keys(selectedSwitchOption.prices)[0] ?? null) : null;\n\n return (\n <div className=\"space-y-4\">\n {props.title && <Typography className=\"font-medium\">{props.title}</Typography>}\n\n {defaultPaymentMethod && (\n <Section\n title={t(\"Payment method\")}\n description={t(\"Manage the default payment method used for subscriptions and invoices.\")}\n >\n <Typography>{formatPaymentMethod(defaultPaymentMethod)}</Typography>\n\n <Button onClick={openPaymentDialog}>\n {t(\"Update payment method\")}\n </Button>\n\n <ActionDialog\n open={paymentDialogOpen}\n onOpenChange={(open) => {\n if (!open) {\n closePaymentDialog();\n } else {\n setPaymentDialogOpen(true);\n }\n }}\n title={t(\"Update payment method\")}\n >\n {!setupIntentClientSecret || !setupIntentStripeAccountId || !stripePromise ? (\n <Skeleton className=\"h-10 w-full\" />\n ) : (\n <Elements\n stripe={stripePromise}\n options={{\n clientSecret: setupIntentClientSecret,\n }}\n >\n <SetDefaultPaymentMethodForm\n clientSecret={setupIntentClientSecret}\n onSetupIntentSucceeded={async (setupIntentId) => {\n await props.customer.setDefaultPaymentMethodFromSetupIntent(setupIntentId);\n closePaymentDialog();\n }}\n />\n </Elements>\n )}\n </ActionDialog>\n </Section>\n )}\n\n {productsForCustomerType.length > 0 && (\n <Section\n title={t(\"Active plans\")}\n description={t(\"View your active plans and purchases.\")}\n >\n <div className=\"space-y-3\">\n {productsForCustomerType.map((product, index) => {\n const quantitySuffix = product.quantity !== 1 ? ` ×${product.quantity}` : \"\";\n const isSubscription = product.type === \"subscription\";\n const isCancelable = isSubscription && !!product.subscription?.isCancelable;\n const canSwitchPlans = isSubscription && defaultPaymentMethod && !!product.id && (product.switchOptions?.length ?? 0) > 0;\n const renewsAt = isSubscription ? (product.subscription?.currentPeriodEnd ?? null) : null;\n const subtitle =\n product.type === \"one_time\"\n ? t(\"One-time purchase\")\n : renewsAt\n ? `${t(\"Renews on\")} ${new Intl.DateTimeFormat(undefined, { year: \"numeric\", month: \"short\", day: \"numeric\" }).format(renewsAt)}`\n : t(\"Subscription\");\n\n return (\n <div key={product.id ?? `${product.displayName}-${index}`} className=\"flex items-start justify-between gap-4\">\n <div className=\"min-w-0\">\n <Typography className=\"truncate\">{product.displayName}{quantitySuffix}</Typography>\n <Typography variant=\"secondary\" type=\"footnote\">{subtitle}</Typography>\n </div>\n\n <div className=\"flex flex-col items-end gap-2\">\n {canSwitchPlans && (\n <Button\n variant=\"secondary\"\n color=\"neutral\"\n onClick={() => openSwitchDialog(product.id!, product.switchOptions?.[0]?.productId ?? null)}\n >\n {t(\"Change plan\")}\n </Button>\n )}\n {isCancelable && (\n <Button\n variant=\"secondary\"\n color=\"neutral\"\n onClick={() => setCancelTarget({ productId: product.id ?? \"_inline\", subscriptionId: product.subscription?.subscriptionId ?? undefined })}\n >\n {t(\"Cancel subscription\")}\n </Button>\n )}\n </div>\n </div>\n );\n })}\n </div>\n\n <ActionDialog\n open={cancelTarget !== null}\n onOpenChange={(open) => {\n if (!open) setCancelTarget(null);\n }}\n title={t(\"Cancel subscription\")}\n description={t(\"Canceling will stop future renewals for this subscription.\")}\n danger\n cancelButton\n okButton={{\n label: t(\"Cancel subscription\"),\n onClick: async () => {\n if (!cancelTarget) return;\n const { productId, subscriptionId } = cancelTarget;\n if (props.customerType === \"team\") {\n await hexclaveApp.cancelSubscription({ teamId: props.customer.id, productId, subscriptionId });\n } else {\n await hexclaveApp.cancelSubscription({ productId, subscriptionId });\n }\n setCancelTarget(null);\n },\n }}\n />\n\n <ActionDialog\n open={switchFromProductId !== null}\n onOpenChange={(open) => {\n if (!open) closeSwitchDialog();\n }}\n title={t(\"Change plan\")}\n description={t(\"Select a new plan from the same product line.\")}\n cancelButton\n okButton={{\n label: t(\"Switch plan\"),\n onClick: async () => {\n const fromProductId = switchFromProductId;\n const toProductId = switchToProductId;\n if (!fromProductId || !toProductId) return;\n if (!selectedPriceId) return;\n const result = await Result.fromThrowingAsync(() => props.customer.switchSubscription({\n fromProductId,\n toProductId,\n priceId: selectedPriceId,\n }));\n if (result.status === \"error\") {\n handleAsyncError(result.error);\n return \"prevent-close\";\n }\n closeSwitchDialog();\n },\n props: {\n disabled: !switchFromProductId || !switchToProductId || !selectedPriceId,\n },\n }}\n >\n <div className=\"space-y-2\">\n {switchOptions.length === 0 ? (\n <Typography variant=\"secondary\" type=\"footnote\">\n {t(\"No other plans available for this subscription.\")}\n </Typography>\n ) : (\n <>\n <Typography type=\"footnote\">{t(\"Choose a plan\")}</Typography>\n <Select\n value={switchToProductId ?? undefined}\n onValueChange={(value) => setSwitchToProductId(value || null)}\n >\n <SelectTrigger className=\"w-full\">\n <SelectValue placeholder={t(\"Choose a plan\")} />\n </SelectTrigger>\n <SelectContent>\n {switchOptions.map((option: NonNullable<typeof switchOptions>[number]) => (\n <SelectItem key={option.productId} value={option.productId}>\n {option.displayName}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </>\n )}\n </div>\n </ActionDialog>\n </Section>\n )\n }\n {invoices.length > 0 && (\n <>\n <Separator />\n <div className=\"space-y-2\">\n <div className=\"space-y-1\">\n <Typography className=\"font-medium\">{t(\"Invoices\")}</Typography>\n <Typography variant=\"secondary\" type=\"footnote\">{t(\"Review past invoices and receipts.\")}</Typography>\n </div>\n <div className=\"border rounded-md\">\n <Table>\n <TableHeader>\n <TableRow>\n <TableHead className=\"w-[140px]\">{t(\"Date\")}</TableHead>\n <TableHead className=\"w-[120px]\">{t(\"Status\")}</TableHead>\n <TableHead className=\"w-[120px]\">{t(\"Amount\")}</TableHead>\n <TableHead className=\"w-[120px] text-right\">{t(\"Invoice\")}</TableHead>\n </TableRow>\n </TableHeader>\n <TableBody>\n {invoices.map((invoice, index) => {\n const createdAtTime = invoice.createdAt.getTime();\n const invoiceKey = Number.isNaN(createdAtTime) ? `invoice-${index}` : `invoice-${createdAtTime}-${index}`;\n return (\n <TableRow key={invoiceKey}>\n <TableCell>\n <Typography>{formatInvoiceDate(invoice.createdAt, t)}</Typography>\n </TableCell>\n <TableCell>\n <Typography>{formatInvoiceStatus(invoice.status, t)}</Typography>\n </TableCell>\n <TableCell>\n <Typography>{formatInvoiceAmount(invoice.amountTotal, t)}</Typography>\n </TableCell>\n <TableCell align=\"right\">\n {invoice.hostedInvoiceUrl ? (\n <Button asChild variant=\"secondary\" color=\"neutral\" size=\"sm\">\n <a href={invoice.hostedInvoiceUrl} target=\"_blank\" rel=\"noreferrer\">\n {t(\"View\")}\n </a>\n </Button>\n ) : (\n <Typography variant=\"secondary\" type=\"footnote\">\n {t(\"Unavailable\")}\n </Typography>\n )}\n </TableCell>\n </TableRow>\n );\n })}\n </TableBody>\n </Table>\n </div>\n </div>\n </>\n )}\n </div >\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"payments-panel.js","names":["Typography","CardElement","Button","Section","envVars","KnownErrors","ActionDialog","Skeleton","Elements","Result","Select","SelectTrigger","SelectValue","SelectContent","SelectItem","Separator","Table","TableHeader","TableRow","TableHead","TableBody","TableCell"],"sources":["../../../../src/components-page/account-settings/payments/payments-panel.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 { KnownErrors } from \"@hexclave/shared\";\nimport { runAsynchronously } from \"@hexclave/shared/dist/utils/promises\";\nimport { ActionDialog, Button, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, Separator, Skeleton, Table, TableBody, TableCell, TableHead, TableHeader, TableRow, toast, Typography } from \"@hexclave/ui\";\nimport { CardElement, Elements, useElements, useStripe } from \"@stripe/react-stripe-js\";\nimport { loadStripe } from \"@stripe/stripe-js\";\nimport { useMemo, useState } from \"react\";\nimport { useStackApp } from \"../../..\";\nimport { envVars } from \"../../../generated/env\";\nimport { useTranslation } from \"../../../lib/translations\";\nimport { Section } from \"../section\";\nimport { Result } from \"@hexclave/shared/dist/utils/results\";\nimport type { CustomerInvoiceStatus, CustomerInvoicesList, CustomerInvoicesListOptions } from \"../../../lib/hexclave-app/customers\";\n\ntype PaymentMethodSummary = {\n id: string,\n brand: string | null,\n last4: string | null,\n exp_month: number | null,\n exp_year: number | null,\n} | null;\n\nfunction formatPaymentMethod(pm: NonNullable<PaymentMethodSummary>) {\n const details = [\n pm.brand ? pm.brand.toUpperCase() : null,\n pm.last4 ? `•••• ${pm.last4}` : null,\n pm.exp_month && pm.exp_year ? `exp ${pm.exp_month}/${pm.exp_year}` : null,\n ].filter(Boolean);\n return details.join(\" · \");\n}\n\nconst formatInvoiceStatus = (status: CustomerInvoiceStatus, t: (value: string) => string) => {\n if (!status) {\n return t(\"Unknown\");\n }\n switch (status) {\n case \"draft\": {\n return t(\"Draft\");\n }\n case \"open\": {\n return t(\"Open\");\n }\n case \"paid\": {\n return t(\"Paid\");\n }\n case \"uncollectible\": {\n return t(\"Uncollectible\");\n }\n case \"void\": {\n return t(\"Void\");\n }\n default: {\n return t(\"Unknown\");\n }\n }\n};\n\nconst formatInvoiceAmount = (amountTotal: number | null | undefined, t: (value: string) => string) => {\n if (typeof amountTotal !== \"number\" || Number.isNaN(amountTotal)) {\n return t(\"Unknown\");\n }\n const normalized = amountTotal / 100;\n const formatted = new Intl.NumberFormat(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 }).format(normalized);\n return `$${formatted}`;\n};\n\nconst formatInvoiceDate = (date: Date | null | undefined, t: (value: string) => string) => {\n if (!date || Number.isNaN(date.getTime())) {\n return t(\"Unknown\");\n }\n return new Intl.DateTimeFormat(undefined, { year: \"numeric\", month: \"short\", day: \"numeric\" }).format(date);\n};\n\ntype CustomerBilling = {\n hasCustomer: boolean,\n defaultPaymentMethod: PaymentMethodSummary,\n};\n\ntype CustomerPaymentMethodSetupIntent = {\n clientSecret: string,\n stripeAccountId: string,\n};\n\ntype CustomerLike = {\n id: string,\n useBilling: () => CustomerBilling,\n useProducts: () => Array<{\n id: string | null,\n quantity: number,\n displayName: string,\n customerType: \"user\" | \"team\" | \"custom\",\n type?: \"one_time\" | \"subscription\",\n switchOptions?: Array<{\n productId: string,\n displayName: string,\n prices: Record<string, { interval?: [number, \"day\" | \"week\" | \"month\" | \"year\"] }>,\n }>,\n subscription: null | {\n subscriptionId: string | null,\n currentPeriodEnd: Date | null,\n cancelAtPeriodEnd: boolean,\n isCancelable: boolean,\n },\n }>,\n useInvoices: (options?: CustomerInvoicesListOptions) => CustomerInvoicesList,\n createPaymentMethodSetupIntent: () => Promise<CustomerPaymentMethodSetupIntent>,\n setDefaultPaymentMethodFromSetupIntent: (setupIntentId: string) => Promise<PaymentMethodSummary>,\n switchSubscription: (options: { fromProductId: string, toProductId: string, priceId?: string, quantity?: number }) => Promise<void>,\n};\n\nfunction SetDefaultPaymentMethodForm(props: {\n clientSecret: string,\n onSetupIntentSucceeded: (setupIntentId: string) => Promise<void>,\n}) {\n const stripe = useStripe();\n const elements = useElements();\n const [errorMessage, setErrorMessage] = useState<string | null>(null);\n const darkMode = \"color-scheme\" in document.documentElement.style && document.documentElement.style[\"color-scheme\"] === \"dark\";\n\n return (\n <div className=\"space-y-4\">\n <div className=\"space-y-2\">\n <Typography className=\"font-medium\">Card details</Typography>\n <div className=\"rounded-md border border-input p-3\">\n <CardElement options={{ hidePostalCode: true, style: { base: { color: darkMode ? \"white\" : \"black\" } } }} />\n </div>\n </div>\n {errorMessage && (\n <Typography variant=\"secondary\" type=\"footnote\">\n {errorMessage}\n </Typography>\n )}\n <Button\n onClick={async () => {\n if (!stripe || !elements) {\n setErrorMessage(\"Stripe is still loading. Please try again.\");\n return;\n }\n const card = elements.getElement(CardElement);\n if (!card) {\n setErrorMessage(\"Card element not found.\");\n return;\n }\n\n const result = await stripe.confirmCardSetup(props.clientSecret, {\n payment_method: { card },\n });\n if (result.error) {\n setErrorMessage(result.error.message ?? \"Failed to save payment method.\");\n return;\n }\n if (!result.setupIntent.id) {\n setErrorMessage(\"No setup intent returned from Stripe.\");\n return;\n }\n await props.onSetupIntentSucceeded(result.setupIntent.id);\n }}\n >\n Save payment method\n </Button>\n </div>\n );\n}\n\nexport function PaymentsPanel(props: {\n title?: string,\n customer?: CustomerLike,\n customerType?: \"user\" | \"team\",\n mockMode?: boolean,\n}) {\n if (props.mockMode) {\n return <MockPaymentsPanel title={props.title} />;\n }\n if (!props.customer) {\n return null;\n }\n return <RealPaymentsPanel title={props.title} customer={props.customer} customerType={props.customerType ?? \"user\"} />;\n}\n\nfunction MockPaymentsPanel(props: { title?: string }) {\n const { t } = useTranslation();\n const defaultPaymentMethod: PaymentMethodSummary = {\n id: \"pm_mock\",\n brand: \"visa\",\n last4: \"4242\",\n exp_month: 12,\n exp_year: 2030,\n };\n\n return (\n <div className=\"space-y-4\">\n {props.title && <Typography className=\"font-medium\">{props.title}</Typography>}\n <Section\n title={t(\"Payment method\")}\n description={t(\"Manage the default payment method used for subscriptions and invoices.\")}\n >\n <Typography>{formatPaymentMethod(defaultPaymentMethod)}</Typography>\n <Button disabled>\n {t(\"Update payment method\")}\n </Button>\n </Section>\n\n <Section\n title={t(\"Active plans\")}\n description={t(\"View your active plans and purchases.\")}\n >\n <div className=\"space-y-3\">\n <div className=\"flex items-start justify-between gap-4\">\n <div className=\"min-w-0\">\n <Typography className=\"truncate\">{t(\"Pro\")}</Typography>\n <Typography variant=\"secondary\" type=\"footnote\">{t(\"Renews on\")} Jan 1, 2030</Typography>\n </div>\n <Button disabled variant=\"secondary\" color=\"neutral\">\n {t(\"Cancel subscription\")}\n </Button>\n </div>\n <div className=\"flex items-start justify-between gap-4\">\n <div className=\"min-w-0\">\n <Typography className=\"truncate\">{t(\"Credits pack\")}</Typography>\n <Typography variant=\"secondary\" type=\"footnote\">{t(\"One-time purchase\")}</Typography>\n </div>\n </div>\n </div>\n </Section>\n </div>\n );\n}\n\nfunction RealPaymentsPanel(props: { title?: string, customer: CustomerLike, customerType: \"user\" | \"team\" }) {\n const { t } = useTranslation();\n const hexclaveApp = useStackApp();\n const billing = props.customer.useBilling();\n const defaultPaymentMethod = billing.defaultPaymentMethod;\n const products = props.customer.useProducts();\n const invoices = props.customer.useInvoices({ limit: 10 });\n const productsForCustomerType = products.filter(product => product.customerType === props.customerType);\n\n const [paymentDialogOpen, setPaymentDialogOpen] = useState(false);\n const [setupIntentClientSecret, setSetupIntentClientSecret] = useState<string | null>(null);\n const [setupIntentStripeAccountId, setSetupIntentStripeAccountId] = useState<string | null>(null);\n const [cancelTarget, setCancelTarget] = useState<{ productId: string, subscriptionId?: string } | null>(null);\n const [switchFromProductId, setSwitchFromProductId] = useState<string | null>(null);\n const [switchToProductId, setSwitchToProductId] = useState<string | null>(null);\n\n const stripePromise = useMemo(() => {\n if (!setupIntentStripeAccountId) return null;\n const publishableKey = envVars.HEXCLAVE_STRIPE_PUBLISHABLE_KEY;\n if (!publishableKey) return null;\n return loadStripe(publishableKey, { stripeAccount: setupIntentStripeAccountId });\n }, [setupIntentStripeAccountId]);\n\n const handleAsyncError = (error: unknown) => {\n if (error instanceof KnownErrors.DefaultPaymentMethodRequired) {\n toast({\n title: t(\"No default payment method\"),\n description: t(\"Add a payment method before switching plans.\"),\n variant: \"destructive\",\n });\n return;\n }\n alert(`An unhandled error occurred. Please ${envVars.NODE_ENV === \"development\" ? \"check the browser console for the full error.\" : \"report this to the developer.\"}\\n\\n${error}`);\n };\n\n const openPaymentDialog = () => {\n runAsynchronously(async () => {\n setPaymentDialogOpen(true);\n const res = await props.customer.createPaymentMethodSetupIntent();\n setSetupIntentClientSecret(res.clientSecret);\n setSetupIntentStripeAccountId(res.stripeAccountId);\n }, { onError: handleAsyncError });\n };\n\n const closePaymentDialog = () => {\n setPaymentDialogOpen(false);\n setSetupIntentClientSecret(null);\n setSetupIntentStripeAccountId(null);\n };\n\n const openSwitchDialog = (productId: string, firstOptionId: string | null) => {\n setSwitchFromProductId(productId);\n setSwitchToProductId(firstOptionId);\n };\n\n const closeSwitchDialog = () => {\n setSwitchFromProductId(null);\n setSwitchToProductId(null);\n };\n\n const switchSourceProduct = switchFromProductId\n ? productsForCustomerType.find((product) => product.id === switchFromProductId) ?? null\n : null;\n const switchOptions = switchSourceProduct?.switchOptions ?? [];\n const selectedSwitchOption = switchOptions.find((option) => option.productId === switchToProductId) ?? null;\n const selectedPriceId = selectedSwitchOption ? (Object.keys(selectedSwitchOption.prices)[0] ?? null) : null;\n\n return (\n <div className=\"space-y-4\">\n {props.title && <Typography className=\"font-medium\">{props.title}</Typography>}\n\n {defaultPaymentMethod && (\n <Section\n title={t(\"Payment method\")}\n description={t(\"Manage the default payment method used for subscriptions and invoices.\")}\n >\n <Typography>{formatPaymentMethod(defaultPaymentMethod)}</Typography>\n\n <Button onClick={openPaymentDialog}>\n {t(\"Update payment method\")}\n </Button>\n\n <ActionDialog\n open={paymentDialogOpen}\n onOpenChange={(open) => {\n if (!open) {\n closePaymentDialog();\n } else {\n setPaymentDialogOpen(true);\n }\n }}\n title={t(\"Update payment method\")}\n >\n {!setupIntentClientSecret || !setupIntentStripeAccountId || !stripePromise ? (\n <Skeleton className=\"h-10 w-full\" />\n ) : (\n <Elements\n stripe={stripePromise}\n options={{\n clientSecret: setupIntentClientSecret,\n }}\n >\n <SetDefaultPaymentMethodForm\n clientSecret={setupIntentClientSecret}\n onSetupIntentSucceeded={async (setupIntentId) => {\n await props.customer.setDefaultPaymentMethodFromSetupIntent(setupIntentId);\n closePaymentDialog();\n }}\n />\n </Elements>\n )}\n </ActionDialog>\n </Section>\n )}\n\n {productsForCustomerType.length > 0 && (\n <Section\n title={t(\"Active plans\")}\n description={t(\"View your active plans and purchases.\")}\n >\n <div className=\"space-y-3\">\n {productsForCustomerType.map((product, index) => {\n const quantitySuffix = product.quantity !== 1 ? ` ×${product.quantity}` : \"\";\n const isSubscription = product.type === \"subscription\";\n const isCancelable = isSubscription && !!product.subscription?.isCancelable;\n const canSwitchPlans = isSubscription && defaultPaymentMethod && !!product.id && (product.switchOptions?.length ?? 0) > 0;\n const renewsAt = isSubscription ? (product.subscription?.currentPeriodEnd ?? null) : null;\n const subtitle =\n product.type === \"one_time\"\n ? t(\"One-time purchase\")\n : renewsAt\n ? `${t(\"Renews on\")} ${new Intl.DateTimeFormat(undefined, { year: \"numeric\", month: \"short\", day: \"numeric\" }).format(renewsAt)}`\n : t(\"Subscription\");\n\n return (\n <div key={product.id ?? `${product.displayName}-${index}`} className=\"flex items-start justify-between gap-4\">\n <div className=\"min-w-0\">\n <Typography className=\"truncate\">{product.displayName}{quantitySuffix}</Typography>\n <Typography variant=\"secondary\" type=\"footnote\">{subtitle}</Typography>\n </div>\n\n <div className=\"flex flex-col items-end gap-2\">\n {canSwitchPlans && (\n <Button\n variant=\"secondary\"\n color=\"neutral\"\n onClick={() => openSwitchDialog(product.id!, product.switchOptions?.[0]?.productId ?? null)}\n >\n {t(\"Change plan\")}\n </Button>\n )}\n {isCancelable && (\n <Button\n variant=\"secondary\"\n color=\"neutral\"\n onClick={() => setCancelTarget({ productId: product.id ?? \"_inline\", subscriptionId: product.subscription?.subscriptionId ?? undefined })}\n >\n {t(\"Cancel subscription\")}\n </Button>\n )}\n </div>\n </div>\n );\n })}\n </div>\n\n <ActionDialog\n open={cancelTarget !== null}\n onOpenChange={(open) => {\n if (!open) setCancelTarget(null);\n }}\n title={t(\"Cancel subscription\")}\n description={t(\"Canceling will stop future renewals for this subscription.\")}\n danger\n cancelButton\n okButton={{\n label: t(\"Cancel subscription\"),\n onClick: async () => {\n if (!cancelTarget) return;\n const { productId, subscriptionId } = cancelTarget;\n if (props.customerType === \"team\") {\n await hexclaveApp.cancelSubscription({ teamId: props.customer.id, productId, subscriptionId });\n } else {\n await hexclaveApp.cancelSubscription({ productId, subscriptionId });\n }\n setCancelTarget(null);\n },\n }}\n />\n\n <ActionDialog\n open={switchFromProductId !== null}\n onOpenChange={(open) => {\n if (!open) closeSwitchDialog();\n }}\n title={t(\"Change plan\")}\n description={t(\"Select a new plan from the same product line.\")}\n cancelButton\n okButton={{\n label: t(\"Switch plan\"),\n onClick: async () => {\n const fromProductId = switchFromProductId;\n const toProductId = switchToProductId;\n if (!fromProductId || !toProductId) return;\n if (!selectedPriceId) return;\n const result = await Result.fromThrowingAsync(() => props.customer.switchSubscription({\n fromProductId,\n toProductId,\n priceId: selectedPriceId,\n }));\n if (result.status === \"error\") {\n handleAsyncError(result.error);\n return \"prevent-close\";\n }\n closeSwitchDialog();\n },\n props: {\n disabled: !switchFromProductId || !switchToProductId || !selectedPriceId,\n },\n }}\n >\n <div className=\"space-y-2\">\n {switchOptions.length === 0 ? (\n <Typography variant=\"secondary\" type=\"footnote\">\n {t(\"No other plans available for this subscription.\")}\n </Typography>\n ) : (\n <>\n <Typography type=\"footnote\">{t(\"Choose a plan\")}</Typography>\n <Select\n value={switchToProductId ?? undefined}\n onValueChange={(value) => setSwitchToProductId(value || null)}\n >\n <SelectTrigger className=\"w-full\">\n <SelectValue placeholder={t(\"Choose a plan\")} />\n </SelectTrigger>\n <SelectContent>\n {switchOptions.map((option: NonNullable<typeof switchOptions>[number]) => (\n <SelectItem key={option.productId} value={option.productId}>\n {option.displayName}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </>\n )}\n </div>\n </ActionDialog>\n </Section>\n )\n }\n {invoices.length > 0 && (\n <>\n <Separator />\n <div className=\"space-y-2\">\n <div className=\"space-y-1\">\n <Typography className=\"font-medium\">{t(\"Invoices\")}</Typography>\n <Typography variant=\"secondary\" type=\"footnote\">{t(\"Review past invoices and receipts.\")}</Typography>\n </div>\n <div className=\"border rounded-md\">\n <Table>\n <TableHeader>\n <TableRow>\n <TableHead className=\"w-[140px]\">{t(\"Date\")}</TableHead>\n <TableHead className=\"w-[120px]\">{t(\"Status\")}</TableHead>\n <TableHead className=\"w-[120px]\">{t(\"Amount\")}</TableHead>\n <TableHead className=\"w-[120px] text-right\">{t(\"Invoice\")}</TableHead>\n </TableRow>\n </TableHeader>\n <TableBody>\n {invoices.map((invoice, index) => {\n const createdAtTime = invoice.createdAt.getTime();\n const invoiceKey = Number.isNaN(createdAtTime) ? `invoice-${index}` : `invoice-${createdAtTime}-${index}`;\n return (\n <TableRow key={invoiceKey}>\n <TableCell>\n <Typography>{formatInvoiceDate(invoice.createdAt, t)}</Typography>\n </TableCell>\n <TableCell>\n <Typography>{formatInvoiceStatus(invoice.status, t)}</Typography>\n </TableCell>\n <TableCell>\n <Typography>{formatInvoiceAmount(invoice.amountTotal, t)}</Typography>\n </TableCell>\n <TableCell align=\"right\">\n {invoice.hostedInvoiceUrl ? (\n <Button asChild variant=\"secondary\" color=\"neutral\" size=\"sm\">\n <a href={invoice.hostedInvoiceUrl} target=\"_blank\" rel=\"noreferrer\">\n {t(\"View\")}\n </a>\n </Button>\n ) : (\n <Typography variant=\"secondary\" type=\"footnote\">\n {t(\"Unavailable\")}\n </Typography>\n )}\n </TableCell>\n </TableRow>\n );\n })}\n </TableBody>\n </Table>\n </div>\n </div>\n </>\n )}\n </div >\n );\n}\n"],"mappings":"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"}
@@ -1,4 +1,6 @@
1
1
  import { AccountSettings } from "./account-settings.js";
2
+ import { HandlerUrls } from "../lib/hexclave-app/common.js";
3
+ import { StackClientApp } from "../lib/hexclave-app/apps/interfaces/client-app.js";
2
4
  import { CliAuthConfirmation } from "./cli-auth-confirm.js";
3
5
  import { EmailVerification } from "./email-verification.js";
4
6
  import { ErrorPage } from "./error-page.js";
@@ -12,6 +14,7 @@ import { SignOut } from "./sign-out.js";
12
14
  import { TeamInvitation } from "./team-invitation.js";
13
15
  import { SignIn } from "./sign-in.js";
14
16
  import { SignUp } from "./sign-up.js";
17
+ import { KnownError } from "@hexclave/shared";
15
18
 
16
19
  //#region src/components-page/hexclave-handler-client.d.ts
17
20
  type Components = {
@@ -38,13 +41,22 @@ type RouteProps = {
38
41
  };
39
42
  searchParams: Promise<Record<string, string>> | Record<string, string>;
40
43
  };
44
+ type RedirectToPageResult = {
45
+ status: "success";
46
+ } | {
47
+ status: "known-error";
48
+ error: KnownError;
49
+ } | {
50
+ status: "unknown-error";
51
+ };
41
52
  type BaseHandlerProps = {
42
53
  fullPage: boolean;
43
54
  componentProps?: { [K in keyof Components]?: Parameters<Components[K]>[0] };
44
55
  };
56
+ declare function getRedirectToPageResult(app: StackClientApp, redirectToPage: keyof HandlerUrls): Promise<RedirectToPageResult>;
45
57
  declare function HexclaveHandlerClient(props: BaseHandlerProps & Partial<RouteProps> & {
46
58
  location?: string;
47
59
  }): any;
48
60
  //#endregion
49
- export { BaseHandlerProps, HexclaveHandlerClient };
61
+ export { BaseHandlerProps, HexclaveHandlerClient, getRedirectToPageResult };
50
62
  //# sourceMappingURL=hexclave-handler-client.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"hexclave-handler-client.d.ts","names":[],"sources":["../../src/components-page/hexclave-handler-client.tsx"],"mappings":";;;;;;;;;;;;;;;;KAgCK,UAAA;EACH,MAAA,SAAe,MAAA;EACf,MAAA,SAAe,MAAA;EACf,iBAAA,SAA0B,iBAAA;EAC1B,aAAA,SAAsB,aAAA;EACtB,cAAA,SAAuB,cAAA;EACvB,OAAA,SAAgB,OAAA;EAChB,aAAA,SAAsB,aAAA;EACtB,iBAAA,SAA0B,iBAAA;EAC1B,cAAA,SAAuB,cAAA;EACvB,SAAA,SAAkB,SAAA;EAClB,eAAA,SAAwB,eAAA;EACxB,mBAAA,SAA4B,mBAAA;EAC5B,GAAA,SAAY,GAAA;EACZ,UAAA,SAAmB,UAAA;AAAA;AAAA,KAGhB,UAAA;EACH,MAAA,EAAQ,OAAA;IAAU,KAAA;EAAA;IAAwB,KAAA;EAAA;EAC1C,YAAA,EAAc,OAAA,CAAQ,MAAA,oBAA0B,MAAA;AAAA;AAAA,KA6BtC,gBAAA;EACV,QAAA;EACA,cAAA,iBACc,UAAA,IAAc,UAAA,CAAW,UAAA,CAAW,CAAA;AAAA;AAAA,iBAgJpC,qBAAA,CAAsB,KAAA,EAAO,gBAAA,GAAmB,OAAA,CAAQ,UAAA;EAAgB,QAAA;AAAA"}
1
+ {"version":3,"file":"hexclave-handler-client.d.ts","names":[],"sources":["../../src/components-page/hexclave-handler-client.tsx"],"mappings":";;;;;;;;;;;;;;;;;;;KAmCK,UAAA;EACH,MAAA,SAAe,MAAA;EACf,MAAA,SAAe,MAAA;EACf,iBAAA,SAA0B,iBAAA;EAC1B,aAAA,SAAsB,aAAA;EACtB,cAAA,SAAuB,cAAA;EACvB,OAAA,SAAgB,OAAA;EAChB,aAAA,SAAsB,aAAA;EACtB,iBAAA,SAA0B,iBAAA;EAC1B,cAAA,SAAuB,cAAA;EACvB,SAAA,SAAkB,SAAA;EAClB,eAAA,SAAwB,eAAA;EACxB,mBAAA,SAA4B,mBAAA;EAC5B,GAAA,SAAY,GAAA;EACZ,UAAA,SAAmB,UAAA;AAAA;AAAA,KAGhB,UAAA;EACH,MAAA,EAAQ,OAAA;IAAU,KAAA;EAAA;IAAwB,KAAA;EAAA;EAC1C,YAAA,EAAc,OAAA,CAAQ,MAAA,oBAA0B,MAAA;AAAA;AAAA,KAG7C,oBAAA;EACC,MAAA;AAAA;EACA,MAAA;EAAuB,KAAA,EAAO,UAAA;AAAA;EAC9B,MAAA;AAAA;AAAA,KA4BM,gBAAA;EACV,QAAA;EACA,cAAA,iBACc,UAAA,IAAc,UAAA,CAAW,UAAA,CAAW,CAAA;AAAA;AAAA,iBAgJ9B,uBAAA,CACpB,GAAA,EAAK,cAAA,EACL,cAAA,QAAsB,WAAA,GACrB,OAAA,CAAQ,oBAAA;AAAA,iBAiCK,qBAAA,CAAsB,KAAA,EAAO,gBAAA,GAAmB,OAAA,CAAQ,UAAA;EAAgB,QAAA;AAAA"}
@@ -3,17 +3,20 @@
3
3
  Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
4
4
  const require_chunk = require('../chunk-BE-pF4vm.js');
5
5
  let _hexclave_shared_dist_utils_errors = require("@hexclave/shared/dist/utils/errors");
6
- let _hexclave_shared_dist_utils_promises = require("@hexclave/shared/dist/utils/promises");
7
6
  let react = require("react");
8
7
  let ___index_js = require("../index.js");
9
8
  let react_jsx_runtime = require("react/jsx-runtime");
10
9
  let ___lib_hooks_js = require("../lib/hooks.js");
10
+ let _hexclave_shared = require("@hexclave/shared");
11
+ let _hexclave_shared_dist_utils_react = require("@hexclave/shared/dist/utils/react");
12
+ let ___components_message_cards_known_error_message_card_js = require("../components/message-cards/known-error-message-card.js");
11
13
  let ___components_message_cards_message_card_js = require("../components/message-cards/message-card.js");
14
+ let ___components_message_cards_predefined_message_card_js = require("../components/message-cards/predefined-message-card.js");
12
15
  let __cli_auth_confirm_js = require("./cli-auth-confirm.js");
16
+ let ___lib_hexclave_app_index_js = require("../lib/hexclave-app/index.js");
13
17
  let _hexclave_shared_dist_utils_objects = require("@hexclave/shared/dist/utils/objects");
14
18
  let _hexclave_shared_dist_utils_urls = require("@hexclave/shared/dist/utils/urls");
15
19
  let next_navigation = require("next/navigation");
16
- let ___lib_hexclave_app_index_js = require("../lib/hexclave-app/index.js");
17
20
  let ___lib_hexclave_app_url_targets_js = require("../lib/hexclave-app/url-targets.js");
18
21
  let __account_settings_js = require("./account-settings.js");
19
22
  let __email_verification_js = require("./email-verification.js");
@@ -153,6 +156,34 @@ function renderComponent(props) {
153
156
  }
154
157
  }
155
158
  }
159
+ async function getRedirectToPageResult(app, redirectToPage) {
160
+ try {
161
+ await app[___lib_hexclave_app_index_js.hexclaveAppInternalsSymbol].redirectToHandler(redirectToPage, { replace: true });
162
+ return { status: "success" };
163
+ } catch (e) {
164
+ if (_hexclave_shared.KnownError.isKnownError(e)) return {
165
+ status: "known-error",
166
+ error: e
167
+ };
168
+ (0, _hexclave_shared_dist_utils_errors.captureError)("<HexclaveHandlerClient redirectToPage />", e);
169
+ return { status: "unknown-error" };
170
+ }
171
+ }
172
+ function RedirectToPage(props) {
173
+ const redirectResult = (0, _hexclave_shared_dist_utils_react.use)((0, react.useMemo)(() => getRedirectToPageResult(props.app, props.redirectToPage), [props.app, props.redirectToPage]));
174
+ if (redirectResult.status === "known-error") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(___components_message_cards_known_error_message_card_js.KnownErrorMessageCard, {
175
+ error: redirectResult.error,
176
+ fullPage: props.fullPage
177
+ });
178
+ if (redirectResult.status === "unknown-error") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(___components_message_cards_predefined_message_card_js.PredefinedMessageCard, {
179
+ type: "unknownError",
180
+ fullPage: props.fullPage
181
+ });
182
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(___components_message_cards_message_card_js.MessageCard, {
183
+ title: "Redirecting...",
184
+ fullPage: props.fullPage
185
+ });
186
+ }
156
187
  function HexclaveHandlerClient(props) {
157
188
  const hexclaveApp = (0, ___lib_hooks_js.useStackApp)();
158
189
  const clientOrigin = useClientOriginAfterHydration();
@@ -199,13 +230,16 @@ function HexclaveHandlerClient(props) {
199
230
  app: hexclaveApp
200
231
  });
201
232
  const redirectToPage = result != null && typeof result === "object" && "redirectToPage" in result ? result.redirectToPage : void 0;
202
- (0, react.useEffect)(() => {
203
- if (redirectToPage == null) return;
204
- (0, _hexclave_shared_dist_utils_promises.runAsynchronouslyWithAlert)(hexclaveApp[___lib_hexclave_app_index_js.hexclaveAppInternalsSymbol].redirectToHandler(redirectToPage, { replace: true }));
205
- }, [redirectToPage, hexclaveApp]);
206
- if (redirectToPage != null) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(___components_message_cards_message_card_js.MessageCard, {
207
- title: "Redirecting...",
208
- fullPage: props.fullPage
233
+ if (redirectToPage != null) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react.Suspense, {
234
+ fallback: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(___components_message_cards_message_card_js.MessageCard, {
235
+ title: "Redirecting...",
236
+ fullPage: props.fullPage
237
+ }),
238
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RedirectToPage, {
239
+ app: hexclaveApp,
240
+ redirectToPage,
241
+ fullPage: props.fullPage
242
+ })
209
243
  });
210
244
  if (result && "redirect" in result) (0, next_navigation.redirect)(result.redirect, next_navigation.RedirectType.replace);
211
245
  return result;
@@ -222,4 +256,5 @@ function useClientOriginAfterHydration() {
222
256
 
223
257
  //#endregion
224
258
  exports.HexclaveHandlerClient = HexclaveHandlerClient;
259
+ exports.getRedirectToPageResult = getRedirectToPageResult;
225
260
  //# sourceMappingURL=hexclave-handler-client.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"hexclave-handler-client.js","names":["SignIn","SignUp","EmailVerification","PasswordReset","ForgotPassword","SignOut","OAuthCallback","MagicLinkCallback","TeamInvitation","AccountSettings","ErrorPage","CliAuthConfirmation","MFA","Onboarding","HexclaveAssertionError","hexclaveAppInternalsSymbol","MessageCard","RedirectType"],"sources":["../../src/components-page/hexclave-handler-client.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 { HexclaveAssertionError } from \"@hexclave/shared/dist/utils/errors\";\nimport { FilterUndefined, filterUndefined } from \"@hexclave/shared/dist/utils/objects\";\nimport { runAsynchronouslyWithAlert } from \"@hexclave/shared/dist/utils/promises\";\nimport { getRelativePart } from \"@hexclave/shared/dist/utils/urls\";\nimport { notFound, redirect, RedirectType, usePathname, useSearchParams } from 'next/navigation'; // THIS_LINE_PLATFORM next\nimport { useEffect, useMemo, useSyncExternalStore } from 'react';\nimport { SignIn, SignUp, StackServerApp } from \"..\";\nimport { useStackApp } from \"../lib/hooks\";\nimport { HandlerUrls, StackClientApp, hexclaveAppInternalsSymbol } from \"../lib/hexclave-app\";\nimport { isLocalHandlerUrlTarget, resolveUnknownHandlerPathFallbackUrl } from \"../lib/hexclave-app/url-targets\";\nimport { AccountSettings } from \"./account-settings\";\nimport { CliAuthConfirmation } from \"./cli-auth-confirm\";\nimport { EmailVerification } from \"./email-verification\";\nimport { ErrorPage } from \"./error-page\";\nimport { ForgotPassword } from \"./forgot-password\";\nimport { MagicLinkCallback } from \"./magic-link-callback\";\nimport { MFA } from \"./mfa\";\nimport { OAuthCallback } from \"./oauth-callback\";\nimport { Onboarding } from \"./onboarding\";\nimport { PasswordReset } from \"./password-reset\";\nimport { SignOut } from \"./sign-out\";\nimport { TeamInvitation } from \"./team-invitation\";\n\nimport { MessageCard } from \"../components/message-cards/message-card\";\n\ntype Components = {\n SignIn: typeof SignIn,\n SignUp: typeof SignUp,\n EmailVerification: typeof EmailVerification,\n PasswordReset: typeof PasswordReset,\n ForgotPassword: typeof ForgotPassword,\n SignOut: typeof SignOut,\n OAuthCallback: typeof OAuthCallback,\n MagicLinkCallback: typeof MagicLinkCallback,\n TeamInvitation: typeof TeamInvitation,\n ErrorPage: typeof ErrorPage,\n AccountSettings: typeof AccountSettings,\n CliAuthConfirmation: typeof CliAuthConfirmation,\n MFA: typeof MFA,\n Onboarding: typeof Onboarding,\n};\n\ntype RouteProps = {\n params: Promise<{ stack?: string[] }> | { stack?: string[] },\n searchParams: Promise<Record<string, string>> | Record<string, string>,\n};\n\nconst availablePaths = {\n signIn: 'sign-in',\n signUp: 'sign-up',\n emailVerification: 'email-verification',\n passwordReset: 'password-reset',\n forgotPassword: 'forgot-password',\n signOut: 'sign-out',\n oauthCallback: 'oauth-callback',\n magicLinkCallback: 'magic-link-callback',\n teamInvitation: 'team-invitation',\n accountSettings: 'account-settings',\n cliAuthConfirm: 'cli-auth-confirm',\n mfa: 'mfa',\n error: 'error',\n onboarding: 'onboarding',\n} as const;\n\nconst placeholderOrigin = \"http://example.com\";\n\nconst pathAliases = {\n // also includes the uppercase and non-dashed versions\n ...Object.fromEntries(Object.entries(availablePaths).map(([key, value]) => [value, value])),\n \"log-in\": availablePaths.signIn,\n \"register\": availablePaths.signUp,\n} as const;\n\nexport type BaseHandlerProps = {\n fullPage: boolean,\n componentProps?: {\n [K in keyof Components]?: Parameters<Components[K]>[0];\n },\n};\n\nfunction renderComponent(props: {\n path: string,\n searchParams: Record<string, string>,\n fullPage: boolean,\n componentProps?: BaseHandlerProps['componentProps'],\n shouldRedirectToPage?: (name: keyof HandlerUrls) => boolean,\n getDefaultUnknownPathUrl?: (path: string) => string | null,\n onNotFound: () => any,\n app: StackClientApp<any> | StackServerApp<any>,\n}) {\n const { path, searchParams, fullPage, componentProps, shouldRedirectToPage, getDefaultUnknownPathUrl, onNotFound, app } = props;\n\n switch (path) {\n case availablePaths.signIn: {\n if (shouldRedirectToPage?.('signIn')) return { redirectToPage: 'signIn' as const };\n return <SignIn\n fullPage={fullPage}\n automaticRedirect\n {...filterUndefinedINU(componentProps?.SignIn)}\n />;\n }\n case availablePaths.signUp: {\n if (shouldRedirectToPage?.('signUp')) return { redirectToPage: 'signUp' as const };\n return <SignUp\n fullPage={fullPage}\n automaticRedirect\n {...filterUndefinedINU(componentProps?.SignUp)}\n />;\n }\n case availablePaths.emailVerification: {\n if (shouldRedirectToPage?.('emailVerification')) return { redirectToPage: 'emailVerification' as const };\n return <EmailVerification\n searchParams={searchParams}\n fullPage={fullPage}\n {...filterUndefinedINU(componentProps?.EmailVerification)}\n />;\n }\n case availablePaths.passwordReset: {\n if (shouldRedirectToPage?.('passwordReset')) return { redirectToPage: 'passwordReset' as const };\n return <PasswordReset\n searchParams={searchParams}\n fullPage={fullPage}\n {...filterUndefinedINU(componentProps?.PasswordReset)}\n />;\n }\n case availablePaths.forgotPassword: {\n if (shouldRedirectToPage?.('forgotPassword')) return { redirectToPage: 'forgotPassword' as const };\n return <ForgotPassword\n fullPage={fullPage}\n {...filterUndefinedINU(componentProps?.ForgotPassword)}\n />;\n }\n case availablePaths.signOut: {\n if (shouldRedirectToPage?.('signOut')) return { redirectToPage: 'signOut' as const };\n return <SignOut\n searchParams={searchParams}\n fullPage={fullPage}\n {...filterUndefinedINU(componentProps?.SignOut)}\n />;\n }\n case availablePaths.oauthCallback: {\n if (shouldRedirectToPage?.('oauthCallback')) return { redirectToPage: 'oauthCallback' as const };\n return <OAuthCallback\n fullPage={fullPage}\n {...filterUndefinedINU(componentProps?.OAuthCallback)}\n />;\n }\n case availablePaths.magicLinkCallback: {\n if (shouldRedirectToPage?.('magicLinkCallback')) return { redirectToPage: 'magicLinkCallback' as const };\n return <MagicLinkCallback\n searchParams={searchParams}\n fullPage={fullPage}\n {...filterUndefinedINU(componentProps?.MagicLinkCallback)}\n />;\n }\n case availablePaths.teamInvitation: {\n if (shouldRedirectToPage?.('teamInvitation')) return { redirectToPage: 'teamInvitation' as const };\n return <TeamInvitation\n searchParams={searchParams}\n fullPage={fullPage}\n {...filterUndefinedINU(componentProps?.TeamInvitation)}\n />;\n }\n case availablePaths.accountSettings: {\n return <AccountSettings\n fullPage={fullPage}\n {...filterUndefinedINU(componentProps?.AccountSettings)}\n />;\n }\n case availablePaths.error: {\n return <ErrorPage\n searchParams={searchParams}\n fullPage={fullPage}\n {...filterUndefinedINU(componentProps?.ErrorPage)}\n />;\n }\n case availablePaths.cliAuthConfirm: {\n if (shouldRedirectToPage?.('cliAuthConfirm')) return { redirectToPage: 'cliAuthConfirm' as const };\n return <CliAuthConfirmation\n fullPage={fullPage}\n {...filterUndefinedINU(componentProps?.CliAuthConfirmation)}\n />;\n }\n case availablePaths.mfa: {\n if (shouldRedirectToPage?.('mfa')) return { redirectToPage: 'mfa' as const };\n return <MFA\n fullPage={fullPage}\n {...filterUndefinedINU(componentProps?.MFA)}\n />;\n }\n case availablePaths.onboarding: {\n if (shouldRedirectToPage?.('onboarding')) return { redirectToPage: 'onboarding' as const };\n return <Onboarding\n fullPage={fullPage}\n {...filterUndefinedINU(componentProps?.Onboarding)}\n />;\n }\n default: {\n if (Object.values(availablePaths).includes(path as any)) {\n throw new HexclaveAssertionError(`Path alias ${path} not included in switch statement, but in availablePaths?`, { availablePaths });\n }\n for (const [key, value] of Object.entries(pathAliases)) {\n if (path.toLowerCase().replaceAll('-', '') === key.toLowerCase().replaceAll('-', '')) {\n const redirectUrl = `${app.urls.handler}/${value}?${new URLSearchParams(searchParams).toString()}`;\n return { redirect: redirectUrl };\n }\n }\n const defaultUnknownPathUrl = getDefaultUnknownPathUrl?.(path);\n if (defaultUnknownPathUrl != null) {\n const defaultUnknownPathUrlObject = new URL(defaultUnknownPathUrl, \"http://example.com\");\n for (const [key, value] of Object.entries(searchParams)) {\n defaultUnknownPathUrlObject.searchParams.set(key, value);\n }\n return { redirect: toAbsoluteOrRelativeRedirectTarget(defaultUnknownPathUrlObject) };\n }\n return onNotFound();\n }\n }\n}\n\nexport function HexclaveHandlerClient(props: BaseHandlerProps & Partial<RouteProps> & { location?: string }) {\n // Use hooks to get app\n const hexclaveApp = useStackApp();\n const clientOrigin = useClientOriginAfterHydration();\n\n const pathname = usePathname();\n const searchParamsFromHook = useSearchParams();\n const currentLocation = pathname;\n const searchParamsSource = searchParamsFromHook;\n\n const { path, searchParams, handlerPath } = useMemo(() => {\n const handlerPath = new URL(hexclaveApp.urls.handler, 'http://example.com').pathname;\n const relativePath = currentLocation.startsWith(handlerPath)\n ? currentLocation.slice(handlerPath.length).replace(/^\\/+/, '')\n : currentLocation.replace(/^\\/+/, '');\n\n return {\n path: relativePath,\n searchParams: Object.fromEntries(searchParamsSource.entries()),\n handlerPath,\n };\n }, [currentLocation, searchParamsSource, hexclaveApp.urls.handler]);\n\n const getDefaultUnknownPathUrl = (unknownPath: string): string | null => {\n return resolveUnknownHandlerPathFallbackUrl({\n defaultTarget: hexclaveApp[hexclaveAppInternalsSymbol].getConstructorOptions().urls?.default,\n projectId: hexclaveApp.projectId,\n unknownPath,\n });\n };\n\n const shouldRedirectToPage = (name: keyof HandlerUrls): boolean => {\n const url = hexclaveApp.urls[name];\n const isCrossDomainLocalOauthCallback = name === \"oauthCallback\" && searchParams.hexclave_cross_domain_auth === \"1\";\n if (isCrossDomainLocalOauthCallback) {\n return false;\n }\n return !isLocalHandlerUrlTarget({\n targetUrl: url,\n handlerPath,\n currentOrigin: clientOrigin,\n });\n };\n\n const result = renderComponent({\n path,\n searchParams,\n fullPage: props.fullPage,\n componentProps: props.componentProps,\n shouldRedirectToPage,\n getDefaultUnknownPathUrl,\n onNotFound: () =>\n notFound()\n ,\n app: hexclaveApp,\n });\n\n const redirectToPage = (result != null && typeof result === 'object' && 'redirectToPage' in result) ? result.redirectToPage : undefined;\n\n useEffect(() => {\n if (redirectToPage == null) return;\n runAsynchronouslyWithAlert(\n hexclaveApp[hexclaveAppInternalsSymbol].redirectToHandler(redirectToPage, { replace: true })\n );\n }, [redirectToPage, hexclaveApp]);\n\n if (redirectToPage != null) {\n return (\n <MessageCard title=\"Redirecting...\" fullPage={props.fullPage} />\n );\n }\n\n if (result && 'redirect' in result) {\n redirect(result.redirect, RedirectType.replace);\n }\n\n\n return result;\n}\n\n// filter undefined values in object. if object itself is undefined, return undefined\nfunction filterUndefinedINU<T extends {}>(value: T | undefined): FilterUndefined<T> | undefined {\n return value === undefined ? value : filterUndefined(value);\n}\n\nfunction toAbsoluteOrRelativeRedirectTarget(url: URL): string {\n return url.origin === \"http://example.com\" ? getRelativePart(url) : url.toString();\n}\n\nfunction useClientOriginAfterHydration(): string | undefined {\n // The first hydrated render must match SSR. After hydration, React re-checks\n // the snapshot and lets us distinguish same-path cross-origin handler URLs.\n return useSyncExternalStore(\n () => () => {},\n () => window.location.origin,\n () => undefined,\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"hexclave-handler-client.js","names":["SignIn","SignUp","EmailVerification","PasswordReset","ForgotPassword","SignOut","OAuthCallback","MagicLinkCallback","TeamInvitation","AccountSettings","ErrorPage","CliAuthConfirmation","MFA","Onboarding","HexclaveAssertionError","hexclaveAppInternalsSymbol","KnownError","KnownErrorMessageCard","PredefinedMessageCard","MessageCard","Suspense","RedirectType"],"sources":["../../src/components-page/hexclave-handler-client.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 { KnownError } from \"@hexclave/shared\";\nimport { captureError, HexclaveAssertionError } from \"@hexclave/shared/dist/utils/errors\";\nimport { FilterUndefined, filterUndefined } from \"@hexclave/shared/dist/utils/objects\";\nimport { use } from \"@hexclave/shared/dist/utils/react\";\nimport { getRelativePart } from \"@hexclave/shared/dist/utils/urls\";\nimport { notFound, redirect, RedirectType, usePathname, useSearchParams } from 'next/navigation'; // THIS_LINE_PLATFORM next\nimport { Suspense, useMemo, useSyncExternalStore } from 'react';\nimport { SignIn, SignUp, StackServerApp } from \"..\";\nimport { useStackApp } from \"../lib/hooks\";\nimport { HandlerUrls, StackClientApp, hexclaveAppInternalsSymbol } from \"../lib/hexclave-app\";\nimport { isLocalHandlerUrlTarget, resolveUnknownHandlerPathFallbackUrl } from \"../lib/hexclave-app/url-targets\";\nimport { AccountSettings } from \"./account-settings\";\nimport { CliAuthConfirmation } from \"./cli-auth-confirm\";\nimport { EmailVerification } from \"./email-verification\";\nimport { ErrorPage } from \"./error-page\";\nimport { ForgotPassword } from \"./forgot-password\";\nimport { MagicLinkCallback } from \"./magic-link-callback\";\nimport { MFA } from \"./mfa\";\nimport { OAuthCallback } from \"./oauth-callback\";\nimport { Onboarding } from \"./onboarding\";\nimport { PasswordReset } from \"./password-reset\";\nimport { SignOut } from \"./sign-out\";\nimport { TeamInvitation } from \"./team-invitation\";\n\nimport { KnownErrorMessageCard } from \"../components/message-cards/known-error-message-card\";\nimport { MessageCard } from \"../components/message-cards/message-card\";\nimport { PredefinedMessageCard } from \"../components/message-cards/predefined-message-card\";\n\ntype Components = {\n SignIn: typeof SignIn,\n SignUp: typeof SignUp,\n EmailVerification: typeof EmailVerification,\n PasswordReset: typeof PasswordReset,\n ForgotPassword: typeof ForgotPassword,\n SignOut: typeof SignOut,\n OAuthCallback: typeof OAuthCallback,\n MagicLinkCallback: typeof MagicLinkCallback,\n TeamInvitation: typeof TeamInvitation,\n ErrorPage: typeof ErrorPage,\n AccountSettings: typeof AccountSettings,\n CliAuthConfirmation: typeof CliAuthConfirmation,\n MFA: typeof MFA,\n Onboarding: typeof Onboarding,\n};\n\ntype RouteProps = {\n params: Promise<{ stack?: string[] }> | { stack?: string[] },\n searchParams: Promise<Record<string, string>> | Record<string, string>,\n};\n\ntype RedirectToPageResult =\n | { status: \"success\" }\n | { status: \"known-error\", error: KnownError }\n | { status: \"unknown-error\" };\n\nconst availablePaths = {\n signIn: 'sign-in',\n signUp: 'sign-up',\n emailVerification: 'email-verification',\n passwordReset: 'password-reset',\n forgotPassword: 'forgot-password',\n signOut: 'sign-out',\n oauthCallback: 'oauth-callback',\n magicLinkCallback: 'magic-link-callback',\n teamInvitation: 'team-invitation',\n accountSettings: 'account-settings',\n cliAuthConfirm: 'cli-auth-confirm',\n mfa: 'mfa',\n error: 'error',\n onboarding: 'onboarding',\n} as const;\n\nconst placeholderOrigin = \"http://example.com\";\n\nconst pathAliases = {\n // also includes the uppercase and non-dashed versions\n ...Object.fromEntries(Object.entries(availablePaths).map(([key, value]) => [value, value])),\n \"log-in\": availablePaths.signIn,\n \"register\": availablePaths.signUp,\n} as const;\n\nexport type BaseHandlerProps = {\n fullPage: boolean,\n componentProps?: {\n [K in keyof Components]?: Parameters<Components[K]>[0];\n },\n};\n\nfunction renderComponent(props: {\n path: string,\n searchParams: Record<string, string>,\n fullPage: boolean,\n componentProps?: BaseHandlerProps['componentProps'],\n shouldRedirectToPage?: (name: keyof HandlerUrls) => boolean,\n getDefaultUnknownPathUrl?: (path: string) => string | null,\n onNotFound: () => any,\n app: StackClientApp<any> | StackServerApp<any>,\n}) {\n const { path, searchParams, fullPage, componentProps, shouldRedirectToPage, getDefaultUnknownPathUrl, onNotFound, app } = props;\n\n switch (path) {\n case availablePaths.signIn: {\n if (shouldRedirectToPage?.('signIn')) return { redirectToPage: 'signIn' as const };\n return <SignIn\n fullPage={fullPage}\n automaticRedirect\n {...filterUndefinedINU(componentProps?.SignIn)}\n />;\n }\n case availablePaths.signUp: {\n if (shouldRedirectToPage?.('signUp')) return { redirectToPage: 'signUp' as const };\n return <SignUp\n fullPage={fullPage}\n automaticRedirect\n {...filterUndefinedINU(componentProps?.SignUp)}\n />;\n }\n case availablePaths.emailVerification: {\n if (shouldRedirectToPage?.('emailVerification')) return { redirectToPage: 'emailVerification' as const };\n return <EmailVerification\n searchParams={searchParams}\n fullPage={fullPage}\n {...filterUndefinedINU(componentProps?.EmailVerification)}\n />;\n }\n case availablePaths.passwordReset: {\n if (shouldRedirectToPage?.('passwordReset')) return { redirectToPage: 'passwordReset' as const };\n return <PasswordReset\n searchParams={searchParams}\n fullPage={fullPage}\n {...filterUndefinedINU(componentProps?.PasswordReset)}\n />;\n }\n case availablePaths.forgotPassword: {\n if (shouldRedirectToPage?.('forgotPassword')) return { redirectToPage: 'forgotPassword' as const };\n return <ForgotPassword\n fullPage={fullPage}\n {...filterUndefinedINU(componentProps?.ForgotPassword)}\n />;\n }\n case availablePaths.signOut: {\n if (shouldRedirectToPage?.('signOut')) return { redirectToPage: 'signOut' as const };\n return <SignOut\n searchParams={searchParams}\n fullPage={fullPage}\n {...filterUndefinedINU(componentProps?.SignOut)}\n />;\n }\n case availablePaths.oauthCallback: {\n if (shouldRedirectToPage?.('oauthCallback')) return { redirectToPage: 'oauthCallback' as const };\n return <OAuthCallback\n fullPage={fullPage}\n {...filterUndefinedINU(componentProps?.OAuthCallback)}\n />;\n }\n case availablePaths.magicLinkCallback: {\n if (shouldRedirectToPage?.('magicLinkCallback')) return { redirectToPage: 'magicLinkCallback' as const };\n return <MagicLinkCallback\n searchParams={searchParams}\n fullPage={fullPage}\n {...filterUndefinedINU(componentProps?.MagicLinkCallback)}\n />;\n }\n case availablePaths.teamInvitation: {\n if (shouldRedirectToPage?.('teamInvitation')) return { redirectToPage: 'teamInvitation' as const };\n return <TeamInvitation\n searchParams={searchParams}\n fullPage={fullPage}\n {...filterUndefinedINU(componentProps?.TeamInvitation)}\n />;\n }\n case availablePaths.accountSettings: {\n return <AccountSettings\n fullPage={fullPage}\n {...filterUndefinedINU(componentProps?.AccountSettings)}\n />;\n }\n case availablePaths.error: {\n return <ErrorPage\n searchParams={searchParams}\n fullPage={fullPage}\n {...filterUndefinedINU(componentProps?.ErrorPage)}\n />;\n }\n case availablePaths.cliAuthConfirm: {\n if (shouldRedirectToPage?.('cliAuthConfirm')) return { redirectToPage: 'cliAuthConfirm' as const };\n return <CliAuthConfirmation\n fullPage={fullPage}\n {...filterUndefinedINU(componentProps?.CliAuthConfirmation)}\n />;\n }\n case availablePaths.mfa: {\n if (shouldRedirectToPage?.('mfa')) return { redirectToPage: 'mfa' as const };\n return <MFA\n fullPage={fullPage}\n {...filterUndefinedINU(componentProps?.MFA)}\n />;\n }\n case availablePaths.onboarding: {\n if (shouldRedirectToPage?.('onboarding')) return { redirectToPage: 'onboarding' as const };\n return <Onboarding\n fullPage={fullPage}\n {...filterUndefinedINU(componentProps?.Onboarding)}\n />;\n }\n default: {\n if (Object.values(availablePaths).includes(path as any)) {\n throw new HexclaveAssertionError(`Path alias ${path} not included in switch statement, but in availablePaths?`, { availablePaths });\n }\n for (const [key, value] of Object.entries(pathAliases)) {\n if (path.toLowerCase().replaceAll('-', '') === key.toLowerCase().replaceAll('-', '')) {\n const redirectUrl = `${app.urls.handler}/${value}?${new URLSearchParams(searchParams).toString()}`;\n return { redirect: redirectUrl };\n }\n }\n const defaultUnknownPathUrl = getDefaultUnknownPathUrl?.(path);\n if (defaultUnknownPathUrl != null) {\n const defaultUnknownPathUrlObject = new URL(defaultUnknownPathUrl, \"http://example.com\");\n for (const [key, value] of Object.entries(searchParams)) {\n defaultUnknownPathUrlObject.searchParams.set(key, value);\n }\n return { redirect: toAbsoluteOrRelativeRedirectTarget(defaultUnknownPathUrlObject) };\n }\n return onNotFound();\n }\n }\n}\n\nexport async function getRedirectToPageResult(\n app: StackClientApp,\n redirectToPage: keyof HandlerUrls,\n): Promise<RedirectToPageResult> {\n try {\n await app[hexclaveAppInternalsSymbol].redirectToHandler(redirectToPage, { replace: true });\n return { status: \"success\" };\n } catch (e) {\n if (KnownError.isKnownError(e)) {\n return { status: \"known-error\", error: e };\n }\n captureError(\"<HexclaveHandlerClient redirectToPage />\", e);\n return { status: \"unknown-error\" };\n }\n}\n\nfunction RedirectToPage(props: {\n app: StackClientApp,\n fullPage?: boolean,\n redirectToPage: keyof HandlerUrls,\n}) {\n const redirectResultPromise = useMemo(\n () => getRedirectToPageResult(props.app, props.redirectToPage),\n [props.app, props.redirectToPage],\n );\n\n const redirectResult = use(redirectResultPromise);\n if (redirectResult.status === \"known-error\") {\n return <KnownErrorMessageCard error={redirectResult.error} fullPage={props.fullPage} />;\n }\n if (redirectResult.status === \"unknown-error\") {\n return <PredefinedMessageCard type=\"unknownError\" fullPage={props.fullPage} />;\n }\n return <MessageCard title=\"Redirecting...\" fullPage={props.fullPage} />;\n}\n\nexport function HexclaveHandlerClient(props: BaseHandlerProps & Partial<RouteProps> & { location?: string }) {\n // Use hooks to get app\n const hexclaveApp = useStackApp();\n const clientOrigin = useClientOriginAfterHydration();\n\n const pathname = usePathname();\n const searchParamsFromHook = useSearchParams();\n const currentLocation = pathname;\n const searchParamsSource = searchParamsFromHook;\n\n const { path, searchParams, handlerPath } = useMemo(() => {\n const handlerPath = new URL(hexclaveApp.urls.handler, 'http://example.com').pathname;\n const relativePath = currentLocation.startsWith(handlerPath)\n ? currentLocation.slice(handlerPath.length).replace(/^\\/+/, '')\n : currentLocation.replace(/^\\/+/, '');\n\n return {\n path: relativePath,\n searchParams: Object.fromEntries(searchParamsSource.entries()),\n handlerPath,\n };\n }, [currentLocation, searchParamsSource, hexclaveApp.urls.handler]);\n\n const getDefaultUnknownPathUrl = (unknownPath: string): string | null => {\n return resolveUnknownHandlerPathFallbackUrl({\n defaultTarget: hexclaveApp[hexclaveAppInternalsSymbol].getConstructorOptions().urls?.default,\n projectId: hexclaveApp.projectId,\n unknownPath,\n });\n };\n\n const shouldRedirectToPage = (name: keyof HandlerUrls): boolean => {\n const url = hexclaveApp.urls[name];\n const isCrossDomainLocalOauthCallback = name === \"oauthCallback\" && searchParams.hexclave_cross_domain_auth === \"1\";\n if (isCrossDomainLocalOauthCallback) {\n return false;\n }\n return !isLocalHandlerUrlTarget({\n targetUrl: url,\n handlerPath,\n currentOrigin: clientOrigin,\n });\n };\n\n const result = renderComponent({\n path,\n searchParams,\n fullPage: props.fullPage,\n componentProps: props.componentProps,\n shouldRedirectToPage,\n getDefaultUnknownPathUrl,\n onNotFound: () =>\n notFound()\n ,\n app: hexclaveApp,\n });\n\n const redirectToPage = (result != null && typeof result === 'object' && 'redirectToPage' in result) ? result.redirectToPage : undefined;\n\n if (redirectToPage != null) {\n return (\n <Suspense fallback={<MessageCard title=\"Redirecting...\" fullPage={props.fullPage} />}>\n <RedirectToPage app={hexclaveApp} redirectToPage={redirectToPage} fullPage={props.fullPage} />\n </Suspense>\n );\n }\n\n if (result && 'redirect' in result) {\n redirect(result.redirect, RedirectType.replace);\n }\n\n\n return result;\n}\n\n// filter undefined values in object. if object itself is undefined, return undefined\nfunction filterUndefinedINU<T extends {}>(value: T | undefined): FilterUndefined<T> | undefined {\n return value === undefined ? value : filterUndefined(value);\n}\n\nfunction toAbsoluteOrRelativeRedirectTarget(url: URL): string {\n return url.origin === \"http://example.com\" ? getRelativePart(url) : url.toString();\n}\n\nfunction useClientOriginAfterHydration(): string | undefined {\n // The first hydrated render must match SSR. After hydration, React re-checks\n // the snapshot and lets us distinguish same-path cross-origin handler URLs.\n return useSyncExternalStore(\n () => () => {},\n () => window.location.origin,\n () => undefined,\n );\n}\n"],"mappings":"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"}
@@ -0,0 +1,51 @@
1
+ const require_chunk = require('../chunk-BE-pF4vm.js');
2
+ let _hexclave_shared = require("@hexclave/shared");
3
+ let vitest = require("vitest");
4
+ let ___lib_hexclave_app_index_js = require("../lib/hexclave-app/index.js");
5
+ let __hexclave_handler_client_js = require("./hexclave-handler-client.js");
6
+
7
+ //#region src/components-page/hexclave-handler-client.test.tsx
8
+ vitest.vi.mock("next/navigation", () => ({
9
+ RedirectType: { replace: "replace" },
10
+ notFound: () => {
11
+ throw new Error("notFound");
12
+ },
13
+ redirect: (url) => {
14
+ throw new Error(`redirect:${url}`);
15
+ },
16
+ usePathname: () => window.location.pathname,
17
+ useSearchParams: () => new URLSearchParams(window.location.search)
18
+ }));
19
+ function createAppTestDouble(options) {
20
+ const projectId = "00000000-0000-4000-8000-000000000000";
21
+ return {
22
+ projectId,
23
+ urls: {
24
+ handler: "http://localhost/handler",
25
+ signIn: `https://${projectId}.example-stack-hosted.test/handler/sign-in`,
26
+ home: "http://localhost"
27
+ },
28
+ redirectToHome: vitest.vi.fn(async () => {}),
29
+ [___lib_hexclave_app_index_js.hexclaveAppInternalsSymbol]: {
30
+ getConstructorOptions: () => ({ urls: {} }),
31
+ redirectToHandler: options.redirectToHandler
32
+ }
33
+ };
34
+ }
35
+ (0, vitest.describe)("HexclaveHandlerClient", () => {
36
+ (0, vitest.it)("returns known cross-domain redirect errors instead of treating them as unhandled async failures", async () => {
37
+ const redirectToHandler = vitest.vi.fn(async () => {
38
+ throw new _hexclave_shared.KnownErrors.RedirectUrlNotWhitelisted();
39
+ });
40
+ const result = await (0, __hexclave_handler_client_js.getRedirectToPageResult)(createAppTestDouble({ redirectToHandler }), "signIn");
41
+ (0, vitest.expect)(redirectToHandler).toHaveBeenCalledWith("signIn", { replace: true });
42
+ (0, vitest.expect)(result.status).toBe("known-error");
43
+ if (result.status === "known-error") {
44
+ (0, vitest.expect)(result.error.errorCode).toBe("REDIRECT_URL_NOT_WHITELISTED");
45
+ (0, vitest.expect)(result.error.message).toContain("Redirect URL not whitelisted");
46
+ }
47
+ });
48
+ });
49
+
50
+ //#endregion
51
+ //# sourceMappingURL=hexclave-handler-client.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hexclave-handler-client.test.js","names":["vi","hexclaveAppInternalsSymbol","KnownErrors"],"sources":["../../src/components-page/hexclave-handler-client.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//===========================================\nimport { KnownErrors } from \"@hexclave/shared\";\nimport { describe, expect, it, vi } from \"vitest\";\nimport { hexclaveAppInternalsSymbol } from \"../lib/hexclave-app\";\nimport type { StackClientApp } from \"../lib/hexclave-app/apps/interfaces/client-app\";\nimport { getRedirectToPageResult } from \"./hexclave-handler-client\";\n\nvi.mock(\"next/navigation\", () => ({\n RedirectType: {\n replace: \"replace\",\n },\n notFound: () => {\n throw new Error(\"notFound\");\n },\n redirect: (url: string) => {\n throw new Error(`redirect:${url}`);\n },\n usePathname: () => window.location.pathname,\n useSearchParams: () => new URLSearchParams(window.location.search),\n}));\n\nfunction createAppTestDouble(options: {\n redirectToHandler: (name: string, options: { replace: true }) => Promise<void>,\n}) {\n const projectId = \"00000000-0000-4000-8000-000000000000\";\n const app = {\n projectId,\n urls: {\n handler: \"http://localhost/handler\",\n signIn: `https://${projectId}.example-stack-hosted.test/handler/sign-in`,\n home: \"http://localhost\",\n },\n redirectToHome: vi.fn(async () => {}),\n [hexclaveAppInternalsSymbol]: {\n getConstructorOptions: () => ({ urls: {} }),\n redirectToHandler: options.redirectToHandler,\n },\n };\n\n // This test double intentionally implements only the StackClientApp surface\n // that HexclaveHandlerClient touches in this redirect path.\n return app as unknown as StackClientApp<true>;\n}\n\ndescribe(\"HexclaveHandlerClient\", () => {\n it(\"returns known cross-domain redirect errors instead of treating them as unhandled async failures\", async () => {\n const redirectToHandler = vi.fn(async () => {\n throw new KnownErrors.RedirectUrlNotWhitelisted();\n });\n const app = createAppTestDouble({ redirectToHandler });\n\n const result = await getRedirectToPageResult(app, \"signIn\");\n\n expect(redirectToHandler).toHaveBeenCalledWith(\"signIn\", { replace: true });\n expect(result.status).toBe(\"known-error\");\n if (result.status === \"known-error\") {\n expect(result.error.errorCode).toBe(\"REDIRECT_URL_NOT_WHITELISTED\");\n expect(result.error.message).toContain(\"Redirect URL not whitelisted\");\n }\n });\n});\n"],"mappings":";;;;;;;AAUAA,UAAG,KAAK,0BAA0B;CAChC,cAAc,EACZ,SAAS,WACV;CACD,gBAAgB;AACd,QAAM,IAAI,MAAM,WAAW;;CAE7B,WAAW,QAAgB;AACzB,QAAM,IAAI,MAAM,YAAY,MAAM;;CAEpC,mBAAmB,OAAO,SAAS;CACnC,uBAAuB,IAAI,gBAAgB,OAAO,SAAS,OAAO;CACnE,EAAE;AAEH,SAAS,oBAAoB,SAE1B;CACD,MAAM,YAAY;AAiBlB,QAhBY;EACV;EACA,MAAM;GACJ,SAAS;GACT,QAAQ,WAAW,UAAU;GAC7B,MAAM;GACP;EACD,gBAAgBA,UAAG,GAAG,YAAY,GAAG;GACpCC,0DAA6B;GAC5B,8BAA8B,EAAE,MAAM,EAAE,EAAE;GAC1C,mBAAmB,QAAQ;GAC5B;EACF;;qBAOM,+BAA+B;AACtC,gBAAG,mGAAmG,YAAY;EAChH,MAAM,oBAAoBD,UAAG,GAAG,YAAY;AAC1C,SAAM,IAAIE,6BAAY,2BAA2B;IACjD;EAGF,MAAM,SAAS,gEAFH,oBAAoB,EAAE,mBAAmB,CAAC,EAEJ,SAAS;AAE3D,qBAAO,kBAAkB,CAAC,qBAAqB,UAAU,EAAE,SAAS,MAAM,CAAC;AAC3E,qBAAO,OAAO,OAAO,CAAC,KAAK,cAAc;AACzC,MAAI,OAAO,WAAW,eAAe;AACnC,sBAAO,OAAO,MAAM,UAAU,CAAC,KAAK,+BAA+B;AACnE,sBAAO,OAAO,MAAM,QAAQ,CAAC,UAAU,+BAA+B;;GAExE;EACF"}
@@ -4,7 +4,7 @@ let _hexclave_shared_dist_utils_promises = require("@hexclave/shared/dist/utils/
4
4
  let ___lib_hexclave_app_common_js = require("../lib/hexclave-app/common.js");
5
5
  let _hexclave_shared_dist_utils_urls = require("@hexclave/shared/dist/utils/urls");
6
6
  let ___lib_hexclave_app_url_targets_js = require("../lib/hexclave-app/url-targets.js");
7
- let ___lib_env_js = require("../lib/env.js");
7
+ let ___generated_env_js = require("../generated/env.js");
8
8
  let ___lib_hexclave_app_apps_implementations_common_js = require("../lib/hexclave-app/apps/implementations/common.js");
9
9
  let __dev_tool_styles_js = require("./dev-tool-styles.js");
10
10
  let __dev_tool_trigger_position_js = require("./dev-tool-trigger-position.js");
@@ -151,7 +151,7 @@ function resolveApiBaseUrl(app) {
151
151
  return (0, ___lib_hexclave_app_apps_implementations_common_js.getBaseUrl)(app[___lib_hexclave_app_common_js.hexclaveAppInternalsSymbol].getConstructorOptions().baseUrl);
152
152
  }
153
153
  function shouldShowDashboardTab(app) {
154
- return ___lib_env_js.envVars.NEXT_PUBLIC_STACK_IS_LOCAL_EMULATOR === "true" && (0, _hexclave_shared_dist_utils_urls.isLocalhost)(resolveApiBaseUrl(app));
154
+ return ___generated_env_js.envVars.HEXCLAVE_IS_LOCAL_EMULATOR === "true" && (0, _hexclave_shared_dist_utils_urls.isLocalhost)(resolveApiBaseUrl(app));
155
155
  }
156
156
  function getTabsForApp(app) {
157
157
  if (shouldShowDashboardTab(app)) return TABS;