@fayz-ai/admin 0.62.0 → 0.63.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{CommandPalette-B5PMQFDG.js → CommandPalette-XDJJ5E3O.js} +3 -3
- package/dist/{CommandPalette-B5PMQFDG.js.map → CommandPalette-XDJJ5E3O.js.map} +1 -1
- package/dist/ConnectedBrandingSettings-43ZM3HCI.js +680 -0
- package/dist/ConnectedBrandingSettings-43ZM3HCI.js.map +1 -0
- package/dist/ConnectedCompanySettings-I4MALI2Q.js +197 -0
- package/dist/ConnectedCompanySettings-I4MALI2Q.js.map +1 -0
- package/dist/ConnectedModulesSettings-A2VJ5BMJ.js +563 -0
- package/dist/ConnectedModulesSettings-A2VJ5BMJ.js.map +1 -0
- package/dist/ConnectedSecuritySettings-25AHA4HJ.js +189 -0
- package/dist/ConnectedSecuritySettings-25AHA4HJ.js.map +1 -0
- package/dist/ConnectedUserProfile-E7USJRUE.js +424 -0
- package/dist/ConnectedUserProfile-E7USJRUE.js.map +1 -0
- package/dist/FieldRulesEditor-YGIPKI5T.js +129 -0
- package/dist/FieldRulesEditor-YGIPKI5T.js.map +1 -0
- package/dist/GettingStartedWidget-HMIJ3Y24.js +12 -0
- package/dist/GettingStartedWidget-HMIJ3Y24.js.map +1 -0
- package/dist/PermissionProfilesTab-3ZPRCHMZ.js +548 -0
- package/dist/PermissionProfilesTab-3ZPRCHMZ.js.map +1 -0
- package/dist/PersonTimeline-HQZT527S.js +3 -0
- package/dist/{PersonTimeline-X7JTPUWJ.js.map → PersonTimeline-HQZT527S.js.map} +1 -1
- package/dist/ProjectsSettings-7O2F27MD.js +5 -0
- package/dist/ProjectsSettings-7O2F27MD.js.map +1 -0
- package/dist/ScheduleEditor-54ELWR45.js +8 -0
- package/dist/{ScheduleEditor-L2UJXKMY.js.map → ScheduleEditor-54ELWR45.js.map} +1 -1
- package/dist/SeuSiteWidget-QLSUVC63.js +6 -0
- package/dist/SeuSiteWidget-QLSUVC63.js.map +1 -0
- package/dist/SubscriptionPage-5GE5HFJK.js +17 -0
- package/dist/{SubscriptionPage-IDVEFLZ5.js.map → SubscriptionPage-5GE5HFJK.js.map} +1 -1
- package/dist/TeamTab-6RAULFLW.js +502 -0
- package/dist/TeamTab-6RAULFLW.js.map +1 -0
- package/dist/TrialWidget-Z6CLUCU3.js +7 -0
- package/dist/TrialWidget-Z6CLUCU3.js.map +1 -0
- package/dist/app/AdminShell.d.ts.map +1 -1
- package/dist/app/admin-app.d.ts.map +1 -1
- package/dist/app/config.d.ts +14 -0
- package/dist/app/config.d.ts.map +1 -1
- package/dist/app/routing.d.ts +6 -0
- package/dist/app/routing.d.ts.map +1 -1
- package/dist/app/scaffold.d.ts.map +1 -1
- package/dist/billing/index.js +9 -4
- package/dist/chunk-2H46WDG5.js +53 -0
- package/dist/chunk-2H46WDG5.js.map +1 -0
- package/dist/chunk-3HLX7YB2.js +94 -0
- package/dist/chunk-3HLX7YB2.js.map +1 -0
- package/dist/chunk-3OUD4XBK.js +3 -0
- package/dist/chunk-3OUD4XBK.js.map +1 -0
- package/dist/{chunk-IXSY4GPK.js → chunk-5D5QLMOQ.js} +5 -3
- package/dist/chunk-5D5QLMOQ.js.map +1 -0
- package/dist/chunk-5WR62X2R.js +1081 -0
- package/dist/chunk-5WR62X2R.js.map +1 -0
- package/dist/{chunk-KPYRTOCK.js → chunk-6CQFGMEO.js} +4 -140
- package/dist/chunk-6CQFGMEO.js.map +1 -0
- package/dist/chunk-6MMSYC2I.js +116 -0
- package/dist/chunk-6MMSYC2I.js.map +1 -0
- package/dist/chunk-6QFOE3UD.js +1466 -0
- package/dist/chunk-6QFOE3UD.js.map +1 -0
- package/dist/chunk-6UUFVQ2E.js +283 -0
- package/dist/chunk-6UUFVQ2E.js.map +1 -0
- package/dist/chunk-7WNLL3F6.js +7889 -0
- package/dist/chunk-7WNLL3F6.js.map +1 -0
- package/dist/chunk-A74IHAQE.js +439 -0
- package/dist/chunk-A74IHAQE.js.map +1 -0
- package/dist/{chunk-JOBRLBIQ.js → chunk-AFA6IO23.js} +4 -3
- package/dist/chunk-AFA6IO23.js.map +1 -0
- package/dist/{chunk-P5UIGQFY.js → chunk-ALCSIN3Z.js} +5 -5
- package/dist/chunk-ALCSIN3Z.js.map +1 -0
- package/dist/chunk-AWM4AMBS.js +39 -0
- package/dist/chunk-AWM4AMBS.js.map +1 -0
- package/dist/{chunk-OQARLKZN.js → chunk-BG5Z6KLN.js} +6 -18
- package/dist/chunk-BG5Z6KLN.js.map +1 -0
- package/dist/chunk-DPNFZONC.js +19 -0
- package/dist/chunk-DPNFZONC.js.map +1 -0
- package/dist/chunk-DRCV643L.js +193 -0
- package/dist/chunk-DRCV643L.js.map +1 -0
- package/dist/chunk-E6DF3VR6.js +206 -0
- package/dist/chunk-E6DF3VR6.js.map +1 -0
- package/dist/chunk-ENHCVGXZ.js +120 -0
- package/dist/chunk-ENHCVGXZ.js.map +1 -0
- package/dist/chunk-FK6RHGI7.js +56 -0
- package/dist/chunk-FK6RHGI7.js.map +1 -0
- package/dist/chunk-G5HNA5QK.js +233 -0
- package/dist/chunk-G5HNA5QK.js.map +1 -0
- package/dist/chunk-HCFO4Z6A.js +53 -0
- package/dist/chunk-HCFO4Z6A.js.map +1 -0
- package/dist/chunk-JFIU6NQP.js +51 -0
- package/dist/chunk-JFIU6NQP.js.map +1 -0
- package/dist/chunk-JRZJBMOY.js +3 -0
- package/dist/chunk-JRZJBMOY.js.map +1 -0
- package/dist/{chunk-3YP7KW47.js → chunk-JVVOL4BB.js} +41 -512
- package/dist/chunk-JVVOL4BB.js.map +1 -0
- package/dist/chunk-KAQ3UIAA.js +415 -0
- package/dist/chunk-KAQ3UIAA.js.map +1 -0
- package/dist/{chunk-P6XUDT2P.js → chunk-KJELMULA.js} +4 -51
- package/dist/chunk-KJELMULA.js.map +1 -0
- package/dist/chunk-KLO7DKXX.js +1651 -0
- package/dist/chunk-KLO7DKXX.js.map +1 -0
- package/dist/chunk-M2CMDWUI.js +75 -0
- package/dist/chunk-M2CMDWUI.js.map +1 -0
- package/dist/chunk-PQ47MLTH.js +255 -0
- package/dist/chunk-PQ47MLTH.js.map +1 -0
- package/dist/{chunk-VX3WJLKK.js → chunk-QD7BMUDF.js} +17 -8
- package/dist/chunk-QD7BMUDF.js.map +1 -0
- package/dist/{chunk-E4MESMNR.js → chunk-QTSKCPSW.js} +7 -10
- package/dist/chunk-QTSKCPSW.js.map +1 -0
- package/dist/chunk-S56FQKPL.js +186 -0
- package/dist/chunk-S56FQKPL.js.map +1 -0
- package/dist/chunk-SGAFPNMA.js +75 -0
- package/dist/chunk-SGAFPNMA.js.map +1 -0
- package/dist/{chunk-OD7P4L3D.js → chunk-SOKCLVM4.js} +3 -3
- package/dist/{chunk-OD7P4L3D.js.map → chunk-SOKCLVM4.js.map} +1 -1
- package/dist/chunk-TL42NKTH.js +3 -0
- package/dist/chunk-TL42NKTH.js.map +1 -0
- package/dist/chunk-U6ZCO2PS.js +2964 -0
- package/dist/chunk-U6ZCO2PS.js.map +1 -0
- package/dist/chunk-VSXNROYH.js +16 -0
- package/dist/chunk-VSXNROYH.js.map +1 -0
- package/dist/chunk-WPFZCYZL.js +619 -0
- package/dist/chunk-WPFZCYZL.js.map +1 -0
- package/dist/chunk-XFTNIBDW.js +23 -0
- package/dist/chunk-XFTNIBDW.js.map +1 -0
- package/dist/chunk-Y66JDL6I.js +1020 -0
- package/dist/chunk-Y66JDL6I.js.map +1 -0
- package/dist/chunk-YEHIBPIX.js +137 -0
- package/dist/chunk-YEHIBPIX.js.map +1 -0
- package/dist/chunk-YKPPCV2Z.js +53 -0
- package/dist/chunk-YKPPCV2Z.js.map +1 -0
- package/dist/components/chat/markdown.d.ts.map +1 -1
- package/dist/components/plugins/PluginSettingsPanel.d.ts.map +1 -1
- package/dist/components/settings/SettingsPage.d.ts.map +1 -1
- package/dist/connect-XKQJOWR7.js +18 -0
- package/dist/connect-XKQJOWR7.js.map +1 -0
- package/dist/crud/CrudDetailPage.d.ts.map +1 -1
- package/dist/crud/CrudListView.d.ts.map +1 -1
- package/dist/crud/CrudPage.d.ts.map +1 -1
- package/dist/crud/RelatedListTab.d.ts.map +1 -1
- package/dist/crud/archetypes/EntityLink.d.ts +1 -1
- package/dist/crud/archetypes/EntityLink.d.ts.map +1 -1
- package/dist/crud/record-chrome.d.ts.map +1 -1
- package/dist/entity-routes-PGU7EHAD.js +4 -0
- package/dist/{entity-routes-LXOBW5IB.js.map → entity-routes-PGU7EHAD.js.map} +1 -1
- package/dist/hooks/useModuleNavigation.d.ts.map +1 -1
- package/dist/impersonation/ImpersonateDialog.d.ts.map +1 -1
- package/dist/impersonation/session.d.ts.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +11902 -33551
- package/dist/index.js.map +1 -1
- package/dist/lib/entity-routes.d.ts.map +1 -1
- package/dist/org/index.js +4 -2
- package/dist/plugins/PluginRegistryManager.d.ts.map +1 -1
- package/dist/settings/MessageTemplateRow.d.ts.map +1 -1
- package/dist/timeline/PersonTimeline.d.ts.map +1 -1
- package/dist/timeline/TimelineList.d.ts.map +1 -1
- package/dist/units/UnitRailPage.d.ts.map +1 -1
- package/dist/units/useUnitFilter.d.ts.map +1 -1
- package/package.json +5 -5
- package/dist/PersonTimeline-X7JTPUWJ.js +0 -3
- package/dist/ScheduleEditor-L2UJXKMY.js +0 -6
- package/dist/SubscriptionPage-IDVEFLZ5.js +0 -8
- package/dist/chunk-3YP7KW47.js.map +0 -1
- package/dist/chunk-E4MESMNR.js.map +0 -1
- package/dist/chunk-IXSY4GPK.js.map +0 -1
- package/dist/chunk-JOBRLBIQ.js.map +0 -1
- package/dist/chunk-KPYRTOCK.js.map +0 -1
- package/dist/chunk-OQARLKZN.js.map +0 -1
- package/dist/chunk-P5UIGQFY.js.map +0 -1
- package/dist/chunk-P6XUDT2P.js.map +0 -1
- package/dist/chunk-VX3WJLKK.js.map +0 -1
- package/dist/entity-routes-LXOBW5IB.js +0 -4
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/components/RightRailPage.tsx"],"names":[],"mappings":";;;;;;AAsBO,SAAS,aAAA,CAAc;AAAA,EAC5B,KAAA;AAAA,EACA,IAAA;AAAA,EACA,OAAA;AAAA,EACA,KAAA;AAAA,EACA;AACF,CAAA,EAUG;AACD,EAAA,MAAM,SAAS,kBAAA,EAAmB;AAClC,EAAA,MAAM,IAAA,GAAO,iBAAA,CAAkB,CAAC,CAAA,KAAM,EAAE,QAAQ,CAAA;AAChD,EAAA,MAAM,QAAA,GAAW,iBAAA,CAAkB,CAAC,CAAA,KAAM,EAAE,QAAQ,CAAA;AACpD,EAAA,MAAM,SAAA,GAAY,iBAAA,CAAkB,CAAC,CAAA,KAAM,EAAE,SAAS,CAAA;AAEtD,EAAA,MAAM,SAAS,MAAA,IAAU,IAAA;AACzB,EAAA,MAAM,QAAA,GAAW,iBAAA,CAAkB,CAAC,CAAA,KAAM,EAAE,QAAQ,CAAA;AACpD,EAAA,MAAM,UAAA,GAAmB,aAAO,OAAO,CAAA;AACvC,EAAA,UAAA,CAAW,OAAA,GAAU,OAAA;AAQrB,EAAA,MAAM,OAAA,GAAgB,aAAO,KAAK,CAAA;AAClC,EAAM,gBAAU,MAAM;AACpB,IAAA,IAAI,CAAC,MAAA,EAAQ;AACb,IAAA,QAAA,EAAS;AACT,IAAA,OAAA,CAAQ,OAAA,GAAU,IAAA;AAClB,IAAA,OAAO,MAAM;AACX,MAAA,OAAA,CAAQ,OAAA,GAAU,KAAA;AAClB,MAAA,SAAA,EAAU;AAAA,IACZ,CAAA;AAAA,EACF,CAAA,EAAG,CAAC,MAAA,EAAQ,QAAA,EAAU,SAAS,CAAC,CAAA;AAQhC,EAAM,gBAAU,MAAM;AACpB,IAAA,IAAI,CAAC,QAAQ,OAAA,EAAS;AACtB,IAAA,IAAI,CAAC,iBAAA,CAAkB,QAAA,EAAS,CAAE,QAAA,aAAqB,OAAA,EAAQ;AAAA,EACjE,CAAA,EAAG,CAAC,QAAQ,CAAC,CAAA;AAKb,EAAA,qBAAA,CAAsB,SAAS,EAAE,KAAA,EAAO,IAAA,EAAM,OAAA,KAAY,IAAI,CAAA;AAE9D,EAAA,IAAI,CAAC,MAAM,OAAO,IAAA;AAElB,EAAA,IAAI,MAAA,EAAQ;AAGV,IAAA,OAAO,IAAA,GACH,YAAA;AAAA,sBACA,GAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,8CAAA,EAAgD,QAAA,EAAS,CAAA;AAAA,MACxE;AAAA,KACF,GACE,IAAA;AAAA,EACN;AAEA,EAAA,2BACG,KAAA,EAAA,EAAM,IAAA,EAAI,IAAA,EAAC,YAAA,EAAc,CAAC,IAAA,KAAS;AAAE,IAAA,IAAI,CAAC,MAAM,OAAA,EAAQ;AAAA,EAAE,GACzD,QAAA,kBAAA,GAAA,CAAC,YAAA,EAAA,EAAa,OAAO,KAAA,IAAS,UAAA,EAAa,UAAS,CAAA,EACtD,CAAA;AAEJ","file":"chunk-JFIU6NQP.js","sourcesContent":["import * as React from 'react'\nimport { createPortal } from 'react-dom'\nimport { Sheet, SheetContent } from '@fayz-ai/ui'\nimport { useRightRailStore, useRightRailPageDrill, useRightRailHosted } from './right-rail'\n\n/**\n * A screen's own detail panel, shown in the shell's right column.\n *\n * Every screen that had something to show beside itself — a deal, a connector,\n * a record — used to open its own sheet: its own width, its own header, its own\n * close button, sliding over the app from the same edge the assistant already\n * occupies. Two panels with two vocabularies competing for one side of the\n * screen. This puts them all in the one column, under the one header: the way\n * back on the left, the page's name centred, close on the right.\n *\n * The body stays MOUNTED WHERE IT WAS WRITTEN and portals in, because the rail\n * is rendered far from any plugin's providers — a deal panel re-mounted inside\n * the shell would have no CRM context to read.\n *\n * With no rail in the shell (a minimal layout, a standalone mount) it falls\n * back to the sheet, so a caller never has to ask which shell it is in.\n */\nexport function RightRailPage({\n title,\n open,\n onClose,\n width,\n children,\n}: {\n /** Heads the column. Keep it short — it is centred over a ~380px header. */\n title: string\n open: boolean\n /** Leaving the page. In the rail this returns to the assistant, so the back\n * link says so; in the sheet fallback it simply closes. */\n onClose: () => void\n /** Sheet-fallback width only. The rail is sized by the user's drag. */\n width?: string\n children: React.ReactNode\n}) {\n const hosted = useRightRailHosted()\n const slot = useRightRailStore((s) => s.pageSlot)\n const showPage = useRightRailStore((s) => s.showPage)\n const closePage = useRightRailStore((s) => s.closePage)\n\n const active = hosted && open\n const pageOpen = useRightRailStore((s) => s.pageOpen)\n const onCloseRef = React.useRef(onClose)\n onCloseRef.current = onClose\n\n // Opening claims the column (and opens it if it was shut); leaving SHUTS it.\n //\n // The cleanup runs on both ways out — the page closing itself once a booking\n // is saved, and the screen unmounting because you navigated elsewhere — and\n // neither should end with the assistant sitting open over a screen that never\n // asked for it.\n const claimed = React.useRef(false)\n React.useEffect(() => {\n if (!active) return\n showPage()\n claimed.current = true\n return () => {\n claimed.current = false\n closePage()\n }\n }, [active, showPage, closePage])\n\n // The column's own close button ends the PAGE too. Without this the screen\n // behind it goes on thinking its panel is open — shut the rail on a deal,\n // reopen it, and you get the assistant with a deal stranded behind it.\n //\n // Read live rather than from the render closure: on the first pass this\n // effect sees the pre-claim value and would dismiss the page on arrival.\n React.useEffect(() => {\n if (!claimed.current) return\n if (!useRightRailStore.getState().pageOpen) onCloseRef.current()\n }, [pageOpen])\n\n // No label of its own: the header says \"Back\". Naming the destination read\n // as a second concept (\"back to chat\") next to pages that have nothing to do\n // with the assistant.\n useRightRailPageDrill(active ? { title, back: onClose } : null)\n\n if (!open) return null\n\n if (hosted) {\n // One frame with no slot yet (the column is still opening) renders nothing\n // rather than flashing the sheet fallback on its way in.\n return slot\n ? createPortal(\n <div className=\"flex min-h-0 flex-1 flex-col overflow-hidden\">{children}</div>,\n slot,\n )\n : null\n }\n\n return (\n <Sheet open onOpenChange={(next) => { if (!next) onClose() }}>\n <SheetContent width={width ?? 'max-w-md'}>{children}</SheetContent>\n </Sheet>\n )\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"names":[],"mappings":"","file":"chunk-JRZJBMOY.js","sourcesContent":[]}
|
|
@@ -1,268 +1,21 @@
|
|
|
1
|
+
import { useAppConfig, clearAppConfigSnapshot, MODULE_FAMILIES, moduleFamilyOf } from './chunk-G5HNA5QK.js';
|
|
2
|
+
import { useOfferedPlugins } from './chunk-AWM4AMBS.js';
|
|
3
|
+
import { getPlanEntitlements, parsePlanFeature, PlanFeatureItem } from './chunk-HCFO4Z6A.js';
|
|
1
4
|
import { useTranslation } from './chunk-YUHCC7JY.js';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
5
|
+
import { useAiUsage } from './chunk-M2CMDWUI.js';
|
|
6
|
+
import { PRICING_PLANS, pricingForPlans, quote, aiCreditsFor, planFit, EXTRA_SEAT_PRICE, PRICING_SCENARIOS, packageFor, perUnitModules, moduleFitsTrades, billableModules, PRICE_BOOK, SCENARIO_VERTICAL_LABELS, TRADE_BY_APP, activeModuleKeys, EXTRA_AI_CREDITS, includedIn, UPSELL_KEY } from './chunk-6CQFGMEO.js';
|
|
7
|
+
import { useBillingStore } from './chunk-SGAFPNMA.js';
|
|
8
|
+
import { useOrgAdapterOptional, useUnitStore, useAppConfigStore } from './chunk-5D5QLMOQ.js';
|
|
4
9
|
import { usePermissionsStore } from './chunk-UQ2NTFQC.js';
|
|
5
|
-
import {
|
|
6
|
-
import
|
|
7
|
-
import {
|
|
8
|
-
import
|
|
9
|
-
import {
|
|
10
|
+
import { getSupabaseClientOptional } from './chunk-KJELMULA.js';
|
|
11
|
+
import { cn } from './chunk-JRZJBMOY.js';
|
|
12
|
+
import { useOrganizationStore } from './chunk-YKPPCV2Z.js';
|
|
13
|
+
import * as React3 from 'react';
|
|
14
|
+
import { Crown, Check, Minus, Building2, Users, Blocks, TrendingDown, Calculator, RotateCcw, ChevronDown, Sparkles, AlertCircle, AlertTriangle, ArrowUpRight } from 'lucide-react';
|
|
10
15
|
import { DataTable, toast, Card, CardContent, Button, Skeleton, ConfirmDialog, Badge, Sheet, SheetContent, SheetHeader, SheetTitle, SheetDescription, SheetBody, getLucideIcon, Switch, SheetFooter, ChoiceSelect, Input } from '@fayz-ai/ui';
|
|
11
|
-
import { tl,
|
|
12
|
-
import {
|
|
13
|
-
import { mergeLimitDeclarations, isEntitledByPlan, resolveAccess, resolveLimit } from '@fayz-ai/core/access';
|
|
14
|
-
import { create } from 'zustand';
|
|
16
|
+
import { tl, invalidateAppConfig, formatCurrency, usePluginRuntimeOptional } from '@fayz-ai/core';
|
|
17
|
+
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
15
18
|
|
|
16
|
-
function parsePlanFeature(feature) {
|
|
17
|
-
return feature.startsWith("*") ? { label: feature.slice(1).trim(), highlighted: true } : { label: feature, highlighted: false };
|
|
18
|
-
}
|
|
19
|
-
function PlanFeatureItem({ feature, className, iconClassName }) {
|
|
20
|
-
const { label, highlighted } = parsePlanFeature(feature);
|
|
21
|
-
return /* @__PURE__ */ jsxs("li", { className: cn(
|
|
22
|
-
"flex items-start gap-2",
|
|
23
|
-
className,
|
|
24
|
-
highlighted && "-mx-1.5 rounded-md bg-violet-50 px-1.5 py-1 font-medium text-violet-700 dark:bg-violet-950/60 dark:text-violet-200"
|
|
25
|
-
), children: [
|
|
26
|
-
highlighted ? /* @__PURE__ */ jsx(Sparkles, { className: cn("mt-0.5 shrink-0 text-violet-600 dark:text-violet-300", iconClassName), "aria-hidden": "true" }) : /* @__PURE__ */ jsx(Check, { className: cn("mt-0.5 shrink-0 text-primary", iconClassName), "aria-hidden": "true" }),
|
|
27
|
-
/* @__PURE__ */ jsx("span", { children: label })
|
|
28
|
-
] });
|
|
29
|
-
}
|
|
30
|
-
var CORE_LIMIT_DECLARATIONS = [
|
|
31
|
-
{ key: "users", label: "Users", table: "v_team_members" },
|
|
32
|
-
{ key: "locations", label: "Companies", table: "v_units" }
|
|
33
|
-
];
|
|
34
|
-
function entityDerivedLimitDeclarations(entities) {
|
|
35
|
-
const out = [];
|
|
36
|
-
for (const e of entities) {
|
|
37
|
-
const def = e.entityDef;
|
|
38
|
-
if (def?.limitKey && def.data?.table) {
|
|
39
|
-
out.push({
|
|
40
|
-
key: def.limitKey,
|
|
41
|
-
label: e.labelPlural,
|
|
42
|
-
table: def.data.table,
|
|
43
|
-
kindFilter: def.data.archetypeKind
|
|
44
|
-
});
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
return out;
|
|
48
|
-
}
|
|
49
|
-
var registry = /* @__PURE__ */ new Map();
|
|
50
|
-
function setLimitRegistry(declarations) {
|
|
51
|
-
const next = /* @__PURE__ */ new Map();
|
|
52
|
-
for (const decl of declarations) next.set(decl.key, decl);
|
|
53
|
-
registry = next;
|
|
54
|
-
}
|
|
55
|
-
function getLimitDeclaration(key) {
|
|
56
|
-
return registry.get(key);
|
|
57
|
-
}
|
|
58
|
-
var listeners = /* @__PURE__ */ new Map();
|
|
59
|
-
function subscribeLimit(key, cb) {
|
|
60
|
-
let set = listeners.get(key);
|
|
61
|
-
if (!set) {
|
|
62
|
-
set = /* @__PURE__ */ new Set();
|
|
63
|
-
listeners.set(key, set);
|
|
64
|
-
}
|
|
65
|
-
set.add(cb);
|
|
66
|
-
return () => {
|
|
67
|
-
set?.delete(cb);
|
|
68
|
-
if (set && set.size === 0) listeners.delete(key);
|
|
69
|
-
};
|
|
70
|
-
}
|
|
71
|
-
function invalidateLimit(key) {
|
|
72
|
-
const decl = registry.get(key);
|
|
73
|
-
if (decl) invalidateCount(decl.table);
|
|
74
|
-
listeners.get(key)?.forEach((cb) => cb());
|
|
75
|
-
}
|
|
76
|
-
var useUpgradeModalStore = create((set) => ({
|
|
77
|
-
current: null,
|
|
78
|
-
open: (payload) => set({ current: payload }),
|
|
79
|
-
close: () => set({ current: null })
|
|
80
|
-
}));
|
|
81
|
-
var AccessContext = React.createContext(null);
|
|
82
|
-
function AccessProvider({ children, limitDeclarations, trialPlanId }) {
|
|
83
|
-
const user = useAuthStore((s) => s.user);
|
|
84
|
-
const org = useOrganizationStore((s) => s.currentOrg);
|
|
85
|
-
const profile = usePermissionsStore((s) => s.currentProfile);
|
|
86
|
-
const plans = useBillingStore((s) => s.plans);
|
|
87
|
-
const runtime = usePluginRuntimeOptional();
|
|
88
|
-
const pluginLimits = runtime?.pluginLimits;
|
|
89
|
-
React.useEffect(() => {
|
|
90
|
-
setLimitRegistry(
|
|
91
|
-
mergeLimitDeclarations(
|
|
92
|
-
CORE_LIMIT_DECLARATIONS,
|
|
93
|
-
entityDerivedLimitDeclarations(getAllEntities()),
|
|
94
|
-
pluginLimits,
|
|
95
|
-
limitDeclarations
|
|
96
|
-
)
|
|
97
|
-
);
|
|
98
|
-
}, [pluginLimits, limitDeclarations]);
|
|
99
|
-
const trial = useTrial();
|
|
100
|
-
const plan = React.useMemo(
|
|
101
|
-
() => effectivePlan(org, plans, trial, trialPlanId),
|
|
102
|
-
[plans, org?.plan, trial.status, trialPlanId]
|
|
103
|
-
);
|
|
104
|
-
const session = React.useMemo(
|
|
105
|
-
() => ({ user, org, profile, plan }),
|
|
106
|
-
[user, org, profile, plan]
|
|
107
|
-
);
|
|
108
|
-
const value = React.useMemo(
|
|
109
|
-
() => ({
|
|
110
|
-
session,
|
|
111
|
-
can: (feature, action) => resolveAccess(session, feature, action),
|
|
112
|
-
entitled: (feature) => isEntitledByPlan(session.plan, feature)
|
|
113
|
-
}),
|
|
114
|
-
[session]
|
|
115
|
-
);
|
|
116
|
-
return /* @__PURE__ */ jsx(AccessContext.Provider, { value, children });
|
|
117
|
-
}
|
|
118
|
-
AccessProvider.displayName = "AccessProvider";
|
|
119
|
-
var ALLOW_ALL = {
|
|
120
|
-
can: () => ({ allowed: true }),
|
|
121
|
-
entitled: () => true
|
|
122
|
-
};
|
|
123
|
-
function useAccess() {
|
|
124
|
-
const ctx = React.useContext(AccessContext);
|
|
125
|
-
if (!ctx) throw new Error("useAccess must be used inside <AccessProvider>");
|
|
126
|
-
return { can: ctx.can, entitled: ctx.entitled };
|
|
127
|
-
}
|
|
128
|
-
function useAccessOptional() {
|
|
129
|
-
const ctx = React.useContext(AccessContext);
|
|
130
|
-
return ctx ? { can: ctx.can, entitled: ctx.entitled } : ALLOW_ALL;
|
|
131
|
-
}
|
|
132
|
-
function useAccessSession() {
|
|
133
|
-
return React.useContext(AccessContext)?.session ?? null;
|
|
134
|
-
}
|
|
135
|
-
function resolveCap(plan, key) {
|
|
136
|
-
return plan?.entitlements?.limits?.[key];
|
|
137
|
-
}
|
|
138
|
-
function useLimit(key) {
|
|
139
|
-
const session = useAccessSession();
|
|
140
|
-
const tenantId = session?.org?.id;
|
|
141
|
-
const cap = resolveCap(session?.plan ?? null, key);
|
|
142
|
-
const declaration = getLimitDeclaration(key);
|
|
143
|
-
const unlimited = !declaration || cap === void 0 || cap === -1;
|
|
144
|
-
const [used, setUsed] = React.useState(0);
|
|
145
|
-
const [loading, setLoading] = React.useState(!unlimited);
|
|
146
|
-
const [version, setVersion] = React.useState(0);
|
|
147
|
-
const refresh = React.useCallback(() => setVersion((v) => v + 1), []);
|
|
148
|
-
React.useEffect(() => subscribeLimit(key, refresh), [key, refresh]);
|
|
149
|
-
React.useEffect(() => {
|
|
150
|
-
if (unlimited || !declaration || !tenantId) {
|
|
151
|
-
setUsed(0);
|
|
152
|
-
setLoading(false);
|
|
153
|
-
return;
|
|
154
|
-
}
|
|
155
|
-
let active = true;
|
|
156
|
-
setLoading(true);
|
|
157
|
-
countByTenant(declaration.table, {
|
|
158
|
-
kind: declaration.kindFilter,
|
|
159
|
-
period: declaration.period,
|
|
160
|
-
tenantId,
|
|
161
|
-
fresh: version > 0
|
|
162
|
-
}).then((n) => {
|
|
163
|
-
if (active) {
|
|
164
|
-
setUsed(n);
|
|
165
|
-
setLoading(false);
|
|
166
|
-
}
|
|
167
|
-
}).catch(() => {
|
|
168
|
-
if (active) setLoading(false);
|
|
169
|
-
});
|
|
170
|
-
return () => {
|
|
171
|
-
active = false;
|
|
172
|
-
};
|
|
173
|
-
}, [unlimited, declaration, tenantId, version]);
|
|
174
|
-
const bounds = resolveLimit({ cap, hasDeclaration: Boolean(declaration), used });
|
|
175
|
-
return { key, ...bounds, loading, refresh };
|
|
176
|
-
}
|
|
177
|
-
function useLimitGuard(key) {
|
|
178
|
-
const session = useAccessSession();
|
|
179
|
-
const tenantId = session?.org?.id;
|
|
180
|
-
const plan = session?.plan ?? null;
|
|
181
|
-
const openModal = useUpgradeModalStore((s) => s.open);
|
|
182
|
-
return React.useCallback(
|
|
183
|
-
async (n = 1) => {
|
|
184
|
-
const cap = resolveCap(plan, key);
|
|
185
|
-
const declaration = getLimitDeclaration(key);
|
|
186
|
-
if (!declaration || cap === void 0 || cap === -1 || !tenantId) return "ok";
|
|
187
|
-
const used = await countByTenant(declaration.table, {
|
|
188
|
-
kind: declaration.kindFilter,
|
|
189
|
-
period: declaration.period,
|
|
190
|
-
tenantId,
|
|
191
|
-
fresh: true
|
|
192
|
-
});
|
|
193
|
-
if (used + n > cap) {
|
|
194
|
-
openModal({ limitKey: key });
|
|
195
|
-
return "blocked";
|
|
196
|
-
}
|
|
197
|
-
return "ok";
|
|
198
|
-
},
|
|
199
|
-
[key, plan, tenantId, openModal]
|
|
200
|
-
);
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
// src/components/billing/access-contract.ts
|
|
204
|
-
function getPlanEntitlements(plan) {
|
|
205
|
-
return plan?.entitlements;
|
|
206
|
-
}
|
|
207
|
-
function featureDisplayName(label) {
|
|
208
|
-
if (!label) return "";
|
|
209
|
-
return label.replace(/\s*\([^)]*\)\s*$/, "").trim() || label.trim();
|
|
210
|
-
}
|
|
211
|
-
var CURRENCY_BY_REGION = {
|
|
212
|
-
BR: "BRL",
|
|
213
|
-
PT: "EUR",
|
|
214
|
-
ES: "EUR",
|
|
215
|
-
FR: "EUR",
|
|
216
|
-
DE: "EUR",
|
|
217
|
-
IT: "EUR",
|
|
218
|
-
NL: "EUR",
|
|
219
|
-
IE: "EUR",
|
|
220
|
-
GB: "GBP",
|
|
221
|
-
US: "USD",
|
|
222
|
-
CA: "CAD",
|
|
223
|
-
AU: "AUD",
|
|
224
|
-
MX: "MXN",
|
|
225
|
-
AR: "ARS",
|
|
226
|
-
CL: "CLP",
|
|
227
|
-
CO: "COP"
|
|
228
|
-
};
|
|
229
|
-
function resolvePlanCurrency(planCurrency, locale) {
|
|
230
|
-
if (planCurrency) return planCurrency;
|
|
231
|
-
const region = locale?.split(/[-_]/)[1]?.toUpperCase();
|
|
232
|
-
return region && CURRENCY_BY_REGION[region] || "USD";
|
|
233
|
-
}
|
|
234
|
-
function useAppPreset() {
|
|
235
|
-
const apps = useAppConfigStore((s) => s.config.apps);
|
|
236
|
-
return useMemo(() => {
|
|
237
|
-
const own = (apps ?? []).find((a) => !a.house);
|
|
238
|
-
return own?.preset ?? {};
|
|
239
|
-
}, [apps]);
|
|
240
|
-
}
|
|
241
|
-
function useOfferedPlugins() {
|
|
242
|
-
const apps = useAppConfigStore((s) => s.config.apps);
|
|
243
|
-
return useMemo(() => {
|
|
244
|
-
const ids = /* @__PURE__ */ new Set();
|
|
245
|
-
for (const app of apps ?? []) {
|
|
246
|
-
if (app.house) continue;
|
|
247
|
-
for (const id of app.preset?.modules ?? []) ids.add(id);
|
|
248
|
-
}
|
|
249
|
-
return [...ids];
|
|
250
|
-
}, [apps]);
|
|
251
|
-
}
|
|
252
|
-
function useTenantLedIdentity(hasTenantBrand) {
|
|
253
|
-
return hasTenantBrand || resolveAppId() === null;
|
|
254
|
-
}
|
|
255
|
-
function usePresetLabel(route, fallback) {
|
|
256
|
-
const preset = useAppPreset();
|
|
257
|
-
return (route ? preset.labels?.[route] : void 0) ?? fallback;
|
|
258
|
-
}
|
|
259
|
-
function useAppTheme() {
|
|
260
|
-
const apps = useAppConfigStore((s) => s.config.apps);
|
|
261
|
-
return useMemo(() => {
|
|
262
|
-
const own = (apps ?? []).find((a) => !a.house);
|
|
263
|
-
return own?.theme ?? null;
|
|
264
|
-
}, [apps]);
|
|
265
|
-
}
|
|
266
19
|
var num = (n) => n.toLocaleString("pt-BR");
|
|
267
20
|
var money = (n) => formatCurrency(n, "BRL");
|
|
268
21
|
var FEATURE_LABELS = {
|
|
@@ -273,7 +26,7 @@ var FEATURE_LABELS = {
|
|
|
273
26
|
function AiUsageSection({ included, planName, upgrade }) {
|
|
274
27
|
const state = useAiUsage(included);
|
|
275
28
|
const checkout = useBillingStore((s) => s.checkout);
|
|
276
|
-
const [pack, setPack] =
|
|
29
|
+
const [pack, setPack] = React3.useState(null);
|
|
277
30
|
const buy = async (p) => {
|
|
278
31
|
if (checkout) {
|
|
279
32
|
try {
|
|
@@ -441,62 +194,6 @@ function Breakdown({ title, rows, total }) {
|
|
|
441
194
|
] }, label)) })
|
|
442
195
|
] });
|
|
443
196
|
}
|
|
444
|
-
var MODULE_FAMILIES = [
|
|
445
|
-
{
|
|
446
|
-
id: "sales",
|
|
447
|
-
label: ["Sales & floor", "Vendas e sal\xE3o"],
|
|
448
|
-
line: ["Selling at the counter, the table and online.", "Vender no balc\xE3o, na mesa e online."],
|
|
449
|
-
plugins: ["orders", "tables", "menu", "reservations", "shop", "payments", "sites", "marketplaces"]
|
|
450
|
-
},
|
|
451
|
-
{
|
|
452
|
-
id: "stock",
|
|
453
|
-
label: ["Stock & production", "Estoque e produ\xE7\xE3o"],
|
|
454
|
-
line: ["What comes in, what is made, what goes out.", "O que entra, o que se produz, o que sai."],
|
|
455
|
-
plugins: ["inventory", "production", "kitchen"]
|
|
456
|
-
},
|
|
457
|
-
{
|
|
458
|
-
id: "care",
|
|
459
|
-
label: ["Attendance & schedule", "Atendimento e agenda"],
|
|
460
|
-
line: ["Bookings, records and the service itself.", "Agendamento, prontu\xE1rio e o atendimento em si."],
|
|
461
|
-
plugins: ["agenda", "clinical", "forms", "custom_forms", "scribe", "courses", "tiss", "pet"]
|
|
462
|
-
},
|
|
463
|
-
{
|
|
464
|
-
id: "finance",
|
|
465
|
-
label: ["Finance & tax", "Financeiro e fiscal"],
|
|
466
|
-
line: ["Cash, bills, banks and invoices.", "Caixa, contas, bancos e nota fiscal."],
|
|
467
|
-
plugins: ["financial", "fiscal-br", "banking-br", "openbanking"]
|
|
468
|
-
},
|
|
469
|
-
{
|
|
470
|
-
id: "clients",
|
|
471
|
-
label: ["Clients & marketing", "Clientes e marketing"],
|
|
472
|
-
line: ["Who your clients are and how you talk to them.", "Quem s\xE3o seus clientes e como voc\xEA fala com eles."],
|
|
473
|
-
plugins: ["crm", "conversations", "marketing", "automations", "reputation"]
|
|
474
|
-
},
|
|
475
|
-
{
|
|
476
|
-
id: "team",
|
|
477
|
-
label: ["Team", "Equipe"],
|
|
478
|
-
line: ["Shifts, time clock and tasks.", "Escala, ponto e tarefas."],
|
|
479
|
-
plugins: ["workforce", "tasks"]
|
|
480
|
-
},
|
|
481
|
-
{
|
|
482
|
-
id: "base",
|
|
483
|
-
label: ["Platform", "Base do sistema"],
|
|
484
|
-
line: ["Registries, reports and what every account has.", "Cadastros, relat\xF3rios e o que toda conta tem."],
|
|
485
|
-
plugins: ["registry", "reports", "dashboard", "notifications", "analytics"]
|
|
486
|
-
}
|
|
487
|
-
];
|
|
488
|
-
var FAMILY_OF = new Map(MODULE_FAMILIES.flatMap((f) => f.plugins.map((p) => [p, f.id])));
|
|
489
|
-
function moduleFamilyOf(key) {
|
|
490
|
-
return FAMILY_OF.get(key) ?? FAMILY_OF.get(key.split(".")[0]) ?? "base";
|
|
491
|
-
}
|
|
492
|
-
var UPCOMING_MODULE_INFO = {
|
|
493
|
-
tiss: { line: ["Bill health plans in the TISS standard, guide by guide.", "Fature conv\xEAnios no padr\xE3o TISS, guia por guia."] },
|
|
494
|
-
"pet.grooming": { line: ["Booked by pet, priced by size and coat, from drop-off to pick-up.", "Marcado pelo pet, pre\xE7o por porte e pelagem, do recebido ao entregue."], v1: "ThePet \xB7 /pet/servicos" },
|
|
495
|
-
"pet.vet": { line: ["Vet records, vaccines and returns per pet.", "Prontu\xE1rio, vacinas e retornos de cada pet."], v1: "ThePet" },
|
|
496
|
-
"pet.lodging": { line: ["Hotel and daycare stays with check-in and daily log.", "Hospedagem e creche com entrada, sa\xEDda e di\xE1rio."], v1: "ThePet" },
|
|
497
|
-
"pet.packages": { line: ["Weekly bath plans and pick-up & drop-off.", "Planos de banho semanais e leva-e-traz."], v1: "ThePet \xB7 /pet/planos, /pet/leva-e-traz" },
|
|
498
|
-
marketplaces: { line: ["Sell the same catalog on Mercado Livre, Shopee and Amazon.", "Venda o mesmo cat\xE1logo no Mercado Livre, Shopee e Amazon."] }
|
|
499
|
-
};
|
|
500
197
|
var money2 = (n) => formatCurrency(n, "BRL");
|
|
501
198
|
var split = (key) => {
|
|
502
199
|
const dot = key.indexOf(".");
|
|
@@ -509,16 +206,16 @@ function priceLabel(key, pkg) {
|
|
|
509
206
|
}
|
|
510
207
|
function PlanCheckoutSheet({ plan, pricing, size, trades, currentTotal, onCancel, onConfirm }) {
|
|
511
208
|
const runtime = usePluginRuntimeOptional();
|
|
512
|
-
const initial =
|
|
209
|
+
const initial = React3.useMemo(
|
|
513
210
|
() => new Set(billableModules([...size.modules, ...(size.unitModules ?? []).flatMap((u) => u.modules)]).concat([...size.modules, ...(size.unitModules ?? []).flatMap((u) => u.modules)].filter((k) => PRICE_BOOK[k]))),
|
|
514
211
|
[size]
|
|
515
212
|
);
|
|
516
|
-
const [selected, setSelected] =
|
|
517
|
-
const [busy, setBusy] =
|
|
518
|
-
|
|
213
|
+
const [selected, setSelected] = React3.useState(initial);
|
|
214
|
+
const [busy, setBusy] = React3.useState(false);
|
|
215
|
+
React3.useEffect(() => {
|
|
519
216
|
if (plan) setSelected(new Set(initial));
|
|
520
217
|
}, [plan, initial]);
|
|
521
|
-
const offer =
|
|
218
|
+
const offer = React3.useMemo(() => {
|
|
522
219
|
const plugins = runtime?.plugins ?? [];
|
|
523
220
|
return Object.keys(PRICE_BOOK).filter((key) => {
|
|
524
221
|
if (PRICE_BOOK[key].comingSoon) return false;
|
|
@@ -543,8 +240,8 @@ function PlanCheckoutSheet({ plan, pricing, size, trades, currentTotal, onCancel
|
|
|
543
240
|
}
|
|
544
241
|
return next;
|
|
545
242
|
});
|
|
546
|
-
const pkg =
|
|
547
|
-
const nextSize =
|
|
243
|
+
const pkg = React3.useMemo(() => packageFor({ package: size.package, trades, modules: [...selected] }), [size.package, trades, selected]);
|
|
244
|
+
const nextSize = React3.useMemo(() => {
|
|
548
245
|
const keys = [...selected];
|
|
549
246
|
const perUnit = perUnitModules(keys, pkg);
|
|
550
247
|
return {
|
|
@@ -649,8 +346,8 @@ function useRealOperation() {
|
|
|
649
346
|
const orgId = useOrganizationStore((s) => s.currentOrg?.id);
|
|
650
347
|
const adapter = useOrgAdapterOptional();
|
|
651
348
|
const runtime = usePluginRuntimeOptional();
|
|
652
|
-
const [fetchedMembers, setFetchedMembers] =
|
|
653
|
-
|
|
349
|
+
const [fetchedMembers, setFetchedMembers] = React3.useState(null);
|
|
350
|
+
React3.useEffect(() => {
|
|
654
351
|
if (members.length > 0 || !adapter || !orgId) return;
|
|
655
352
|
let alive = true;
|
|
656
353
|
adapter.listMembers(orgId).then((list2) => {
|
|
@@ -663,11 +360,11 @@ function useRealOperation() {
|
|
|
663
360
|
}, [members.length, adapter, orgId]);
|
|
664
361
|
const apps = useAppConfigStore((s) => s.config.apps);
|
|
665
362
|
const offered = useOfferedPlugins();
|
|
666
|
-
const trades =
|
|
363
|
+
const trades = React3.useMemo(
|
|
667
364
|
() => [...new Set((apps ?? []).filter((a) => !a.house).map((a) => TRADE_BY_APP[a.id]).filter(Boolean))],
|
|
668
365
|
[apps]
|
|
669
366
|
);
|
|
670
|
-
const modules =
|
|
367
|
+
const modules = React3.useMemo(
|
|
671
368
|
() => activeModuleKeys(runtime?.plugins ?? [], { trades, offered }),
|
|
672
369
|
[runtime?.plugins, trades, offered]
|
|
673
370
|
);
|
|
@@ -684,9 +381,9 @@ function useRealOperation() {
|
|
|
684
381
|
}
|
|
685
382
|
function useOperation() {
|
|
686
383
|
const real = useRealOperation();
|
|
687
|
-
const [scenario, setScenarioState] =
|
|
688
|
-
const [users, setUsers] =
|
|
689
|
-
const [footprints, setFootprints] =
|
|
384
|
+
const [scenario, setScenarioState] = React3.useState(MINE);
|
|
385
|
+
const [users, setUsers] = React3.useState(null);
|
|
386
|
+
const [footprints, setFootprints] = React3.useState(null);
|
|
690
387
|
const example = PRICING_SCENARIOS.find((x) => x.id === scenario);
|
|
691
388
|
const base = example ? example.size : real;
|
|
692
389
|
const trades = example ? [example.vertical] : real.trades;
|
|
@@ -751,7 +448,7 @@ function fitReason(fit) {
|
|
|
751
448
|
return fit.limit === "locations" ? tl(`Up to ${fit.max} ${one ? "unit" : "units"} \u2014 you have ${fit.have}`, `At\xE9 ${fit.max} ${one ? "unidade" : "unidades"} \u2014 voc\xEA tem ${fit.have}`) : tl(`Up to ${fit.max} ${one ? "user" : "users"} \u2014 you have ${fit.have}`, `At\xE9 ${fit.max} ${one ? "usu\xE1rio" : "usu\xE1rios"} \u2014 voc\xEA tem ${fit.have}`);
|
|
752
449
|
}
|
|
753
450
|
function PlanCard({ p, units, recommended, current, onChoose, onDetails }) {
|
|
754
|
-
const [allFeatures, setAllFeatures] =
|
|
451
|
+
const [allFeatures, setAllFeatures] = React3.useState(false);
|
|
755
452
|
const reason = fitReason(p.fit);
|
|
756
453
|
const all = [
|
|
757
454
|
`*${tl(`AI assistant \u2014 ${p.aiCredits.toLocaleString("pt-BR")} credits/mo`, `Assistente de IA \u2014 ${p.aiCredits.toLocaleString("pt-BR")} cr\xE9ditos/m\xEAs`)}`,
|
|
@@ -843,7 +540,7 @@ function UnitCards({ op, quoteFor }) {
|
|
|
843
540
|
return /* @__PURE__ */ jsx("div", { className: "grid gap-3 sm:grid-cols-2", children: op.units.map((u) => /* @__PURE__ */ jsx(UnitCard, { op, unit: u, amount: quoteFor?.lines.find((l) => l.key === `unit:${u.id}`)?.amount ?? 0 }, u.id)) });
|
|
844
541
|
}
|
|
845
542
|
function UnitCard({ op, unit, amount }) {
|
|
846
|
-
const [adding, setAdding] =
|
|
543
|
+
const [adding, setAdding] = React3.useState(false);
|
|
847
544
|
const on = op.available.filter((k) => unit.modules.includes(k));
|
|
848
545
|
const off = op.available.filter((k) => !unit.modules.includes(k));
|
|
849
546
|
const chip = (key, active) => /* @__PURE__ */ jsxs(
|
|
@@ -995,12 +692,12 @@ function DetailsSheet({ open, onOpenChange, op, priced, planId, setPlanId }) {
|
|
|
995
692
|
function PricingOverview({ plans, currentPlanId, onChoose, onConfirm, compare }) {
|
|
996
693
|
const op = useOperation();
|
|
997
694
|
const sources = plans?.length ? plans : MODEL_PLANS;
|
|
998
|
-
const [detailsOpen, setDetailsOpen] =
|
|
999
|
-
const [detailsPlan, setDetailsPlan] =
|
|
1000
|
-
const [compareOpen, setCompareOpen] =
|
|
1001
|
-
const [checkout, setCheckout] =
|
|
695
|
+
const [detailsOpen, setDetailsOpen] = React3.useState(false);
|
|
696
|
+
const [detailsPlan, setDetailsPlan] = React3.useState();
|
|
697
|
+
const [compareOpen, setCompareOpen] = React3.useState(false);
|
|
698
|
+
const [checkout, setCheckout] = React3.useState(null);
|
|
1002
699
|
const choose = onConfirm ? (plan) => setCheckout(plan) : onChoose;
|
|
1003
|
-
const priced =
|
|
700
|
+
const priced = React3.useMemo(() => {
|
|
1004
701
|
const pricings = pricingForPlans(sources);
|
|
1005
702
|
return sources.map((plan, i) => {
|
|
1006
703
|
const pricing = pricings[i] ?? null;
|
|
@@ -1128,174 +825,6 @@ function PricingOverview({ plans, currentPlanId, onChoose, onConfirm, compare })
|
|
|
1128
825
|
/* @__PURE__ */ jsx(DetailsSheet, { open: detailsOpen, onOpenChange: setDetailsOpen, op, priced, planId: detailsPlan, setPlanId: setDetailsPlan })
|
|
1129
826
|
] });
|
|
1130
827
|
}
|
|
1131
|
-
|
|
1132
|
-
// src/app/app-config-cache.ts
|
|
1133
|
-
var STORAGE_KEY = "fayz:app-config";
|
|
1134
|
-
var VERSION = 1;
|
|
1135
|
-
var APP_CONFIG_TTL_MS = 5 * 60 * 1e3;
|
|
1136
|
-
var MAX_AGE_MS = 24 * 60 * 60 * 1e3;
|
|
1137
|
-
var MAX_BYTES = 256 * 1024;
|
|
1138
|
-
function storage() {
|
|
1139
|
-
try {
|
|
1140
|
-
return globalThis.localStorage ?? null;
|
|
1141
|
-
} catch {
|
|
1142
|
-
return null;
|
|
1143
|
-
}
|
|
1144
|
-
}
|
|
1145
|
-
var keyFor = (tenantId) => `${STORAGE_KEY}:${tenantId}`;
|
|
1146
|
-
function readAppConfigSnapshot(tenantId) {
|
|
1147
|
-
const store = storage();
|
|
1148
|
-
if (!store) return null;
|
|
1149
|
-
try {
|
|
1150
|
-
const raw = store.getItem(keyFor(tenantId));
|
|
1151
|
-
if (!raw) return null;
|
|
1152
|
-
const snap = JSON.parse(raw);
|
|
1153
|
-
if (snap.version !== VERSION || snap.tenantId !== tenantId) return null;
|
|
1154
|
-
const age = Date.now() - snap.savedAt;
|
|
1155
|
-
if (age > MAX_AGE_MS) {
|
|
1156
|
-
store.removeItem(keyFor(tenantId));
|
|
1157
|
-
return null;
|
|
1158
|
-
}
|
|
1159
|
-
return { config: snap.config, stale: age > APP_CONFIG_TTL_MS };
|
|
1160
|
-
} catch {
|
|
1161
|
-
return null;
|
|
1162
|
-
}
|
|
1163
|
-
}
|
|
1164
|
-
function writeAppConfigSnapshot(tenantId, config) {
|
|
1165
|
-
const store = storage();
|
|
1166
|
-
if (!store) return;
|
|
1167
|
-
try {
|
|
1168
|
-
const payload = JSON.stringify({ version: VERSION, tenantId, savedAt: Date.now(), config });
|
|
1169
|
-
if (payload.length > MAX_BYTES) return;
|
|
1170
|
-
store.setItem(keyFor(tenantId), payload);
|
|
1171
|
-
} catch {
|
|
1172
|
-
}
|
|
1173
|
-
}
|
|
1174
|
-
function clearAppConfigSnapshot(tenantId) {
|
|
1175
|
-
const store = storage();
|
|
1176
|
-
if (!store) return;
|
|
1177
|
-
try {
|
|
1178
|
-
if (tenantId) {
|
|
1179
|
-
store.removeItem(keyFor(tenantId));
|
|
1180
|
-
return;
|
|
1181
|
-
}
|
|
1182
|
-
for (let i = store.length - 1; i >= 0; i -= 1) {
|
|
1183
|
-
const key = store.key(i);
|
|
1184
|
-
if (key?.startsWith(`${STORAGE_KEY}:`)) store.removeItem(key);
|
|
1185
|
-
}
|
|
1186
|
-
} catch {
|
|
1187
|
-
}
|
|
1188
|
-
}
|
|
1189
|
-
var CONFIG_POLL_MS = 6e4;
|
|
1190
|
-
var EMPTY = { tenant_id: null, plugins: [], apps: [], nav: [], permissions: [] };
|
|
1191
|
-
async function loadFor(tenantId, force = false) {
|
|
1192
|
-
const supabase = getSupabaseClientOptional();
|
|
1193
|
-
if (!supabase) return { ...EMPTY, tenant_id: tenantId };
|
|
1194
|
-
return fetchAppConfig(supabase, { tenantId: tenantId ?? void 0, force });
|
|
1195
|
-
}
|
|
1196
|
-
function useAppConfig(options) {
|
|
1197
|
-
const enabled = options?.enabled !== false;
|
|
1198
|
-
const currentOrg = useOrganizationStore((state) => state.currentOrg);
|
|
1199
|
-
const tenantId = currentOrg?.id ?? null;
|
|
1200
|
-
const config = useAppConfigStore((state) => state.config);
|
|
1201
|
-
const configTenantId = useAppConfigStore((state) => state.tenantId);
|
|
1202
|
-
const beginTenant = useAppConfigStore((state) => state.beginTenant);
|
|
1203
|
-
const initializing = useAppConfigStore((state) => state.initializing);
|
|
1204
|
-
const stale = useAppConfigStore((state) => state.stale);
|
|
1205
|
-
const error = useAppConfigStore((state) => state.error);
|
|
1206
|
-
const setConfig = useAppConfigStore((state) => state.setConfig);
|
|
1207
|
-
const setStale = useAppConfigStore((state) => state.setStale);
|
|
1208
|
-
const setError = useAppConfigStore((state) => state.setError);
|
|
1209
|
-
const reset = useAppConfigStore((state) => state.reset);
|
|
1210
|
-
const refresh = useCallback(async () => {
|
|
1211
|
-
setStale(true);
|
|
1212
|
-
try {
|
|
1213
|
-
if (tenantId) {
|
|
1214
|
-
invalidateAppConfig(tenantId);
|
|
1215
|
-
clearAppConfigSnapshot(tenantId);
|
|
1216
|
-
}
|
|
1217
|
-
const next = await loadFor(tenantId, true);
|
|
1218
|
-
if (tenantId) writeAppConfigSnapshot(tenantId, next);
|
|
1219
|
-
setConfig(next, tenantId);
|
|
1220
|
-
} catch (err) {
|
|
1221
|
-
setError(errorMessage(err));
|
|
1222
|
-
} finally {
|
|
1223
|
-
setStale(false);
|
|
1224
|
-
}
|
|
1225
|
-
}, [tenantId, setConfig, setError, setStale]);
|
|
1226
|
-
useLayoutEffect(() => {
|
|
1227
|
-
if (!enabled) return;
|
|
1228
|
-
if (configTenantId !== tenantId) beginTenant(tenantId);
|
|
1229
|
-
}, [enabled, tenantId, configTenantId, beginTenant]);
|
|
1230
|
-
useLayoutEffect(() => {
|
|
1231
|
-
if (!enabled || !tenantId) return;
|
|
1232
|
-
const snap = readAppConfigSnapshot(tenantId);
|
|
1233
|
-
if (!snap) return;
|
|
1234
|
-
setConfig(snap.config, tenantId);
|
|
1235
|
-
if (snap.stale) setStale(true);
|
|
1236
|
-
}, [enabled, tenantId, setConfig, setStale]);
|
|
1237
|
-
useEffect(() => {
|
|
1238
|
-
if (!enabled) {
|
|
1239
|
-
reset();
|
|
1240
|
-
return;
|
|
1241
|
-
}
|
|
1242
|
-
let cancelled = false;
|
|
1243
|
-
void (async () => {
|
|
1244
|
-
try {
|
|
1245
|
-
const next = await loadFor(tenantId);
|
|
1246
|
-
if (cancelled) return;
|
|
1247
|
-
if (tenantId) writeAppConfigSnapshot(tenantId, next);
|
|
1248
|
-
setConfig(next, tenantId);
|
|
1249
|
-
setStale(false);
|
|
1250
|
-
} catch (err) {
|
|
1251
|
-
if (!cancelled) setError(errorMessage(err));
|
|
1252
|
-
}
|
|
1253
|
-
})();
|
|
1254
|
-
return () => {
|
|
1255
|
-
cancelled = true;
|
|
1256
|
-
};
|
|
1257
|
-
}, [enabled, tenantId, setConfig, setError, setStale, reset]);
|
|
1258
|
-
useEffect(() => {
|
|
1259
|
-
if (!enabled || !tenantId) return;
|
|
1260
|
-
const supabase = getSupabaseClientOptional();
|
|
1261
|
-
if (!supabase) return;
|
|
1262
|
-
let cancelled = false;
|
|
1263
|
-
let seen = null;
|
|
1264
|
-
const poll = async () => {
|
|
1265
|
-
try {
|
|
1266
|
-
const { data, error: error2 } = await supabase.rpc("tenant_config_version");
|
|
1267
|
-
if (cancelled || error2 || !data) return;
|
|
1268
|
-
const stamp = String(data);
|
|
1269
|
-
if (seen === null) {
|
|
1270
|
-
seen = stamp;
|
|
1271
|
-
return;
|
|
1272
|
-
}
|
|
1273
|
-
if (stamp !== seen) {
|
|
1274
|
-
seen = stamp;
|
|
1275
|
-
invalidateAppConfig(tenantId);
|
|
1276
|
-
const next = await loadFor(tenantId, true);
|
|
1277
|
-
writeAppConfigSnapshot(tenantId, next);
|
|
1278
|
-
setConfig(next, tenantId);
|
|
1279
|
-
}
|
|
1280
|
-
} catch {
|
|
1281
|
-
}
|
|
1282
|
-
};
|
|
1283
|
-
const timer = setInterval(() => void poll(), CONFIG_POLL_MS);
|
|
1284
|
-
void poll();
|
|
1285
|
-
return () => {
|
|
1286
|
-
cancelled = true;
|
|
1287
|
-
clearInterval(timer);
|
|
1288
|
-
};
|
|
1289
|
-
}, [enabled, tenantId, setConfig]);
|
|
1290
|
-
const activePlugins = useMemo(() => activePluginIds(config), [config]);
|
|
1291
|
-
const bindings = useMemo(() => toTenantPluginBindings(config), [config]);
|
|
1292
|
-
const has = useCallback(
|
|
1293
|
-
(plugin, facet) => hasPlugin(config, plugin, facet),
|
|
1294
|
-
[config]
|
|
1295
|
-
);
|
|
1296
|
-
const ready = !tenantId || configTenantId === tenantId && !initializing;
|
|
1297
|
-
return { config, initializing, ready, stale, error, activePlugins, has, bindings, refresh };
|
|
1298
|
-
}
|
|
1299
828
|
function SubscriptionPage({ className }) {
|
|
1300
829
|
const { t } = useTranslation();
|
|
1301
830
|
const tr = (key, fallback) => {
|
|
@@ -1309,7 +838,7 @@ function SubscriptionPage({ className }) {
|
|
|
1309
838
|
const currentOrg = useOrganizationStore((s) => s.currentOrg);
|
|
1310
839
|
const currentPlanId = currentOrg?.plan || "free";
|
|
1311
840
|
const { refresh: refreshAppConfig } = useAppConfig();
|
|
1312
|
-
const allFeatures =
|
|
841
|
+
const allFeatures = React3.useMemo(() => {
|
|
1313
842
|
const seen = /* @__PURE__ */ new Set();
|
|
1314
843
|
const out = [];
|
|
1315
844
|
for (const plan of plans) {
|
|
@@ -1323,7 +852,7 @@ function SubscriptionPage({ className }) {
|
|
|
1323
852
|
return out;
|
|
1324
853
|
}, [plans]);
|
|
1325
854
|
const featureCatalog = usePermissionsStore((s) => s.features);
|
|
1326
|
-
const entitlementsMatrix =
|
|
855
|
+
const entitlementsMatrix = React3.useMemo(() => {
|
|
1327
856
|
const hasAny = plans.some((p) => getPlanEntitlements(p));
|
|
1328
857
|
if (!hasAny) return null;
|
|
1329
858
|
const featureIds = [];
|
|
@@ -1443,6 +972,6 @@ function SubscriptionPage({ className }) {
|
|
|
1443
972
|
) });
|
|
1444
973
|
}
|
|
1445
974
|
|
|
1446
|
-
export {
|
|
1447
|
-
//# sourceMappingURL=chunk-
|
|
1448
|
-
//# sourceMappingURL=chunk-
|
|
975
|
+
export { SubscriptionPage };
|
|
976
|
+
//# sourceMappingURL=chunk-JVVOL4BB.js.map
|
|
977
|
+
//# sourceMappingURL=chunk-JVVOL4BB.js.map
|