@fayz-ai/admin 0.18.0 → 0.19.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/CommandPalette-GQ6SPITD.js +297 -0
- package/dist/CommandPalette-GQ6SPITD.js.map +1 -0
- package/dist/{ScheduleEditor-63TVSCJC.js → ScheduleEditor-YZPOCBTW.js} +6 -6
- package/dist/{ScheduleEditor-63TVSCJC.js.map → ScheduleEditor-YZPOCBTW.js.map} +1 -1
- package/dist/SubscriptionPage-JXJ56OOH.js +8 -0
- package/dist/{SubscriptionPage-5PJ25YAD.js.map → SubscriptionPage-JXJ56OOH.js.map} +1 -1
- package/dist/access/headless.d.ts +2 -2
- package/dist/access/limits-registry.d.ts +6 -3
- package/dist/access/limits-registry.d.ts.map +1 -1
- package/dist/app/AdminShell.d.ts.map +1 -1
- package/dist/app/admin-app.d.ts.map +1 -1
- package/dist/app/settings-tabs.store.d.ts +21 -0
- package/dist/app/settings-tabs.store.d.ts.map +1 -0
- package/dist/billing/index.js +2 -2
- package/dist/{chunk-7DFJEXCR.js → chunk-5GWUM4SM.js} +3 -3
- package/dist/{chunk-7DFJEXCR.js.map → chunk-5GWUM4SM.js.map} +1 -1
- package/dist/{chunk-ERDS4OJS.js → chunk-HYDNIBN6.js} +3 -3
- package/dist/{chunk-ERDS4OJS.js.map → chunk-HYDNIBN6.js.map} +1 -1
- package/dist/{chunk-WN6PGQSG.js → chunk-NB6X5O5L.js} +81 -10
- package/dist/chunk-NB6X5O5L.js.map +1 -0
- package/dist/{chunk-LZRLWKJ2.js → chunk-UHHEPU4A.js} +379 -88
- package/dist/chunk-UHHEPU4A.js.map +1 -0
- package/dist/{chunk-VTEP2AQX.js → chunk-WJ3H3RJN.js} +5 -3
- package/dist/chunk-WJ3H3RJN.js.map +1 -0
- package/dist/{chunk-G5PBC5OB.js → chunk-ZNV7Q2TO.js} +8 -8
- package/dist/chunk-ZNV7Q2TO.js.map +1 -0
- package/dist/components/GetStarted.d.ts +45 -0
- package/dist/components/GetStarted.d.ts.map +1 -0
- package/dist/components/RightRail.d.ts.map +1 -1
- package/dist/components/TeachMe.d.ts +83 -0
- package/dist/components/TeachMe.d.ts.map +1 -0
- package/dist/components/auth/SplitLogin.d.ts.map +1 -1
- package/dist/components/billing/SoftLimitProbes.d.ts +5 -0
- package/dist/components/billing/SoftLimitProbes.d.ts.map +1 -0
- package/dist/components/billing/SoftLimitWidget.d.ts +3 -0
- package/dist/components/billing/SoftLimitWidget.d.ts.map +1 -0
- package/dist/components/billing/index.d.ts +3 -1
- package/dist/components/billing/index.d.ts.map +1 -1
- package/dist/components/billing/soft-limit.store.d.ts +23 -0
- package/dist/components/billing/soft-limit.store.d.ts.map +1 -0
- package/dist/components/chat/ChatConversation.d.ts.map +1 -1
- package/dist/components/chat/ChatHistory.d.ts.map +1 -1
- package/dist/components/chat/ChatMessages.d.ts.map +1 -1
- package/dist/components/chat/ConfirmActionCard.d.ts +5 -1
- package/dist/components/chat/ConfirmActionCard.d.ts.map +1 -1
- package/dist/components/chat/index.d.ts +0 -1
- package/dist/components/chat/index.d.ts.map +1 -1
- package/dist/components/connect/ConnectorRuns.d.ts +1 -1
- package/dist/components/connect/ConnectorsPanel.d.ts.map +1 -1
- package/dist/components/connect/ConnectorsTab.d.ts.map +1 -1
- package/dist/components/connect/connector-status.d.ts +1 -1
- package/dist/components/connect/model.d.ts +1 -1
- package/dist/components/layout/CommandPalette.d.ts.map +1 -1
- package/dist/components/organization/PermissionProfilesTab.d.ts.map +1 -1
- package/dist/components/settings/LocationsCrudPage.d.ts.map +1 -1
- package/dist/components/settings/SettingsPage.d.ts.map +1 -1
- package/dist/components/shared/RecordPickerModal.d.ts +24 -1
- package/dist/components/shared/RecordPickerModal.d.ts.map +1 -1
- package/dist/components/survey/PublicSurveyPage.d.ts +8 -0
- package/dist/components/survey/PublicSurveyPage.d.ts.map +1 -0
- package/dist/crud/CrudDetailPage.d.ts.map +1 -1
- package/dist/crud/CrudFormPage.d.ts.map +1 -1
- package/dist/crud/CrudListView.d.ts +3 -1
- package/dist/crud/CrudListView.d.ts.map +1 -1
- package/dist/crud/CrudPage.d.ts.map +1 -1
- package/dist/crud/CrudRecordModal.d.ts.map +1 -1
- package/dist/crud/LookupCreateInline.d.ts.map +1 -1
- package/dist/crud/RelatedListTab.d.ts +10 -0
- package/dist/crud/RelatedListTab.d.ts.map +1 -0
- package/dist/crud/archetypes/BookableToggle.d.ts +12 -0
- package/dist/crud/archetypes/BookableToggle.d.ts.map +1 -0
- package/dist/crud/archetypes/DetailTabHeader.d.ts +23 -0
- package/dist/crud/archetypes/DetailTabHeader.d.ts.map +1 -0
- package/dist/crud/archetypes/KanbanBoard.d.ts +89 -0
- package/dist/crud/archetypes/KanbanBoard.d.ts.map +1 -0
- package/dist/crud/archetypes/OrderAmount.d.ts +25 -0
- package/dist/crud/archetypes/OrderAmount.d.ts.map +1 -0
- package/dist/crud/archetypes/OrderBoardView.d.ts +61 -0
- package/dist/crud/archetypes/OrderBoardView.d.ts.map +1 -0
- package/dist/crud/archetypes/OrderCreateScreen.d.ts +116 -0
- package/dist/crud/archetypes/OrderCreateScreen.d.ts.map +1 -0
- package/dist/crud/archetypes/OrderDetailPage.d.ts +35 -0
- package/dist/crud/archetypes/OrderDetailPage.d.ts.map +1 -0
- package/dist/crud/archetypes/OrderDetailRail.d.ts +97 -0
- package/dist/crud/archetypes/OrderDetailRail.d.ts.map +1 -0
- package/dist/crud/archetypes/OrderDetailView.d.ts.map +1 -1
- package/dist/crud/archetypes/OrderFormView.d.ts +20 -1
- package/dist/crud/archetypes/OrderFormView.d.ts.map +1 -1
- package/dist/crud/archetypes/OrderListView.d.ts +59 -1
- package/dist/crud/archetypes/OrderListView.d.ts.map +1 -1
- package/dist/crud/archetypes/OrderPaymentChip.d.ts +8 -0
- package/dist/crud/archetypes/OrderPaymentChip.d.ts.map +1 -0
- package/dist/crud/archetypes/PaymentStatusBadge.d.ts +47 -0
- package/dist/crud/archetypes/PaymentStatusBadge.d.ts.map +1 -0
- package/dist/crud/archetypes/PersonDetailTabs.d.ts.map +1 -1
- package/dist/crud/archetypes/orderVocabulary.d.ts +19 -0
- package/dist/crud/archetypes/orderVocabulary.d.ts.map +1 -0
- package/dist/crud/archetypes/useOrderEditor.d.ts +20 -0
- package/dist/crud/archetypes/useOrderEditor.d.ts.map +1 -0
- package/dist/crud/archetypes/useOrderReceivable.d.ts +46 -0
- package/dist/crud/archetypes/useOrderReceivable.d.ts.map +1 -0
- package/dist/crud/archetypes/useOrdersPaymentStatus.d.ts +15 -0
- package/dist/crud/archetypes/useOrdersPaymentStatus.d.ts.map +1 -0
- package/dist/crud/createCrudPage.d.ts +3 -0
- package/dist/crud/createCrudPage.d.ts.map +1 -1
- package/dist/crud/relation-field.d.ts.map +1 -1
- package/dist/directory/AppSwitcher.d.ts +48 -0
- package/dist/directory/AppSwitcher.d.ts.map +1 -0
- package/dist/directory/FamilyWordmark.d.ts +20 -0
- package/dist/directory/FamilyWordmark.d.ts.map +1 -0
- package/dist/directory/app-id.d.ts +21 -0
- package/dist/directory/app-id.d.ts.map +1 -0
- package/dist/directory/directory.store.d.ts +20 -0
- package/dist/directory/directory.store.d.ts.map +1 -0
- package/dist/directory/index.d.ts +7 -0
- package/dist/directory/index.d.ts.map +1 -0
- package/dist/directory/types.d.ts +43 -0
- package/dist/directory/types.d.ts.map +1 -0
- package/dist/directory/useAppDirectory.d.ts +14 -0
- package/dist/directory/useAppDirectory.d.ts.map +1 -0
- package/dist/hooks/useAITools.d.ts +7 -0
- package/dist/hooks/useAITools.d.ts.map +1 -1
- package/dist/hooks/useAppConfig.d.ts.map +1 -1
- package/dist/hooks/useChat.d.ts +4 -0
- package/dist/hooks/useChat.d.ts.map +1 -1
- package/dist/hooks/useTenant.d.ts +12 -12
- package/dist/hooks/useTenant.d.ts.map +1 -1
- package/dist/index.d.ts +43 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +25069 -21491
- package/dist/index.js.map +1 -1
- package/dist/lib/agent-staff.d.ts +46 -96
- package/dist/lib/agent-staff.d.ts.map +1 -1
- package/dist/lib/ai-tool-handlers.d.ts +24 -7
- package/dist/lib/ai-tool-handlers.d.ts.map +1 -1
- package/dist/lib/assistant-invite.d.ts.map +1 -1
- package/dist/lib/bookable-service.d.ts +7 -0
- package/dist/lib/bookable-service.d.ts.map +1 -0
- package/dist/lib/core-ai-tools.d.ts +0 -17
- package/dist/lib/core-ai-tools.d.ts.map +1 -1
- package/dist/lib/fayz-agent.d.ts +2 -0
- package/dist/lib/fayz-agent.d.ts.map +1 -1
- package/dist/lib/product-name.d.ts +38 -0
- package/dist/lib/product-name.d.ts.map +1 -0
- package/dist/lib/tool-search.d.ts +82 -0
- package/dist/lib/tool-search.d.ts.map +1 -0
- package/dist/onboarding/ActivationAgentSurface.d.ts.map +1 -1
- package/dist/onboarding/ActivationChecklistCard.d.ts +11 -1
- package/dist/onboarding/ActivationChecklistCard.d.ts.map +1 -1
- package/dist/onboarding/ActivationProvider.d.ts.map +1 -1
- package/dist/onboarding/WorkspaceSetupFlow.d.ts.map +1 -1
- package/dist/onboarding/business-profile.d.ts +67 -0
- package/dist/onboarding/business-profile.d.ts.map +1 -0
- package/dist/onboarding/checklist.d.ts +10 -0
- package/dist/onboarding/checklist.d.ts.map +1 -1
- package/dist/onboarding/decide.d.ts +23 -0
- package/dist/onboarding/decide.d.ts.map +1 -1
- package/dist/onboarding/guided-runner.d.ts +11 -0
- package/dist/onboarding/guided-runner.d.ts.map +1 -1
- package/dist/onboarding/guided.d.ts +21 -1
- package/dist/onboarding/guided.d.ts.map +1 -1
- package/dist/onboarding/index.d.ts +4 -2
- package/dist/onboarding/index.d.ts.map +1 -1
- package/dist/onboarding/packs/retail.d.ts.map +1 -1
- package/dist/onboarding/programs.d.ts +25 -1
- package/dist/onboarding/programs.d.ts.map +1 -1
- package/dist/onboarding/useWorkspaceSetupGate.d.ts.map +1 -1
- package/dist/onboarding/welcome.d.ts +27 -0
- package/dist/onboarding/welcome.d.ts.map +1 -0
- package/dist/org/WorkspaceChooser.d.ts +20 -0
- package/dist/org/WorkspaceChooser.d.ts.map +1 -0
- package/dist/org/WorkspaceChooserGate.d.ts +21 -0
- package/dist/org/WorkspaceChooserGate.d.ts.map +1 -0
- package/dist/org/WorkspaceSwitching.d.ts +25 -0
- package/dist/org/WorkspaceSwitching.d.ts.map +1 -0
- package/dist/org/WorkspaceSwitchingHost.d.ts +11 -0
- package/dist/org/WorkspaceSwitchingHost.d.ts.map +1 -0
- package/dist/org/adapters/supabase.d.ts +1 -1
- package/dist/org/adapters/supabase.d.ts.map +1 -1
- package/dist/org/choice.d.ts +41 -0
- package/dist/org/choice.d.ts.map +1 -0
- package/dist/org/context.d.ts +8 -0
- package/dist/org/context.d.ts.map +1 -1
- package/dist/org/index.d.ts +12 -1
- package/dist/org/index.d.ts.map +1 -1
- package/dist/org/index.js +4 -4
- package/dist/org/self.d.ts.map +1 -1
- package/dist/org/store.d.ts +2 -1
- package/dist/org/store.d.ts.map +1 -1
- package/dist/org/switching.store.d.ts +27 -0
- package/dist/org/switching.store.d.ts.map +1 -0
- package/dist/permissions/context.d.ts +26 -0
- package/dist/permissions/context.d.ts.map +1 -1
- package/dist/permissions/index.js +1 -1
- package/dist/projects/SeuSiteWidget.d.ts.map +1 -1
- package/dist/setup/SetupAgentSurface.d.ts +2 -0
- package/dist/setup/SetupAgentSurface.d.ts.map +1 -0
- package/dist/setup/format.d.ts +21 -0
- package/dist/setup/format.d.ts.map +1 -0
- package/dist/setup/registry.d.ts +29 -0
- package/dist/setup/registry.d.ts.map +1 -0
- package/dist/setup/validate.d.ts +26 -0
- package/dist/setup/validate.d.ts.map +1 -0
- package/dist/stores/chat.store.d.ts +15 -18
- package/dist/stores/chat.store.d.ts.map +1 -1
- package/dist/stores/createCrudStore.d.ts +2 -0
- package/dist/stores/createCrudStore.d.ts.map +1 -1
- package/dist/tasks/components/TaskDetail.d.ts.map +1 -1
- package/dist/units/UnitMenuSection.d.ts.map +1 -1
- package/dist/units/UnitSwitcher.d.ts.map +1 -1
- package/dist/units/UnitSwitching.d.ts +24 -0
- package/dist/units/UnitSwitching.d.ts.map +1 -0
- package/dist/units/UnitSwitchingHost.d.ts +11 -0
- package/dist/units/UnitSwitchingHost.d.ts.map +1 -0
- package/dist/units/data.d.ts +30 -4
- package/dist/units/data.d.ts.map +1 -1
- package/dist/units/index.d.ts +2 -0
- package/dist/units/index.d.ts.map +1 -1
- package/dist/units/switching.store.d.ts +31 -0
- package/dist/units/switching.store.d.ts.map +1 -0
- package/package.json +7 -7
- package/dist/CommandPalette-XY3VOEAV.js +0 -291
- package/dist/CommandPalette-XY3VOEAV.js.map +0 -1
- package/dist/SubscriptionPage-5PJ25YAD.js +0 -8
- package/dist/chunk-G5PBC5OB.js.map +0 -1
- package/dist/chunk-LZRLWKJ2.js.map +0 -1
- package/dist/chunk-VTEP2AQX.js.map +0 -1
- package/dist/chunk-WN6PGQSG.js.map +0 -1
- package/dist/components/billing/SoftLimitBanner.d.ts +0 -5
- package/dist/components/billing/SoftLimitBanner.d.ts.map +0 -1
- package/dist/components/chat/StaffHireView.d.ts +0 -21
- package/dist/components/chat/StaffHireView.d.ts.map +0 -1
- package/dist/components/chat/StaffPitchCard.d.ts +0 -15
- package/dist/components/chat/StaffPitchCard.d.ts.map +0 -1
- package/dist/components/chat/StaffSwitcher.d.ts +0 -23
- package/dist/components/chat/StaffSwitcher.d.ts.map +0 -1
- package/dist/lib/staff-cards.d.ts +0 -11
- package/dist/lib/staff-cards.d.ts.map +0 -1
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The settings tabs this app actually mounted, by id.
|
|
3
|
+
*
|
|
4
|
+
* Published by the shell because only the shell knows: the list is the core
|
|
5
|
+
* tabs, plus the org tabs the signed-in profile may see, plus whatever the
|
|
6
|
+
* plugins contributed, minus whatever the app hid. `PluginRuntime.settingsTabs`
|
|
7
|
+
* is only the third of those, and anything asking "can this app go to
|
|
8
|
+
* /settings/x?" off that alone answers no for every screen the SHELL owns —
|
|
9
|
+
* which is how the activation checklist ended up empty (every base step points
|
|
10
|
+
* at a shell settings page and every base step is `requiresRoute`).
|
|
11
|
+
*/
|
|
12
|
+
interface SettingsTabsState {
|
|
13
|
+
ids: string[];
|
|
14
|
+
setIds: (ids: string[]) => void;
|
|
15
|
+
}
|
|
16
|
+
export declare const useSettingsTabsStore: import("zustand").UseBoundStore<import("zustand").StoreApi<SettingsTabsState>>;
|
|
17
|
+
/** Publish the mounted tabs while the shell is up. Ids only — a tab's component
|
|
18
|
+
* is not something anybody outside the settings page should hold. */
|
|
19
|
+
export declare function usePublishSettingsTabs(ids: string[]): void;
|
|
20
|
+
export {};
|
|
21
|
+
//# sourceMappingURL=settings-tabs.store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"settings-tabs.store.d.ts","sourceRoot":"","sources":["../../src/app/settings-tabs.store.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;GAUG;AACH,UAAU,iBAAiB;IACzB,GAAG,EAAE,MAAM,EAAE,CAAA;IACb,MAAM,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,IAAI,CAAA;CAChC;AAED,eAAO,MAAM,oBAAoB,gFAO9B,CAAA;AAEH;sEACsE;AACtE,wBAAgB,sBAAsB,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,IAAI,CAK1D"}
|
package/dist/billing/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { NO_TRIAL, TrialWidget, effectivePlan, paidPlanIds, resolvePlanBadge, resolveTrial, useBillingStore, useTrial } from '../chunk-
|
|
2
|
-
import '../chunk-
|
|
1
|
+
export { NO_TRIAL, TrialWidget, effectivePlan, paidPlanIds, resolvePlanBadge, resolveTrial, useBillingStore, useTrial } from '../chunk-HYDNIBN6.js';
|
|
2
|
+
import '../chunk-5GWUM4SM.js';
|
|
3
3
|
//# sourceMappingURL=index.js.map
|
|
4
4
|
//# sourceMappingURL=index.js.map
|
|
@@ -43,6 +43,6 @@ var useOrganizationStore = create((set) => ({
|
|
|
43
43
|
}
|
|
44
44
|
}));
|
|
45
45
|
|
|
46
|
-
export { getPersistedOrgId, useOrganizationStore };
|
|
47
|
-
//# sourceMappingURL=chunk-
|
|
48
|
-
//# sourceMappingURL=chunk-
|
|
46
|
+
export { getPersistedOrgId, persistOrgId, useOrganizationStore };
|
|
47
|
+
//# sourceMappingURL=chunk-5GWUM4SM.js.map
|
|
48
|
+
//# sourceMappingURL=chunk-5GWUM4SM.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/org/store.ts"],"names":[],"mappings":";;;;AAGA,IAAM,WAAA,GAAc,kBAAA;AAMpB,SAAS,iBAAA,GAAmC;AAC1C,EAAA,IAAI;AACF,IAAA,IAAI,OAAO,YAAA,KAAiB,WAAA,EAAa,OAAO,IAAA;AAChD,IAAA,OAAO,YAAA,CAAa,QAAQ,WAAW,CAAA;AAAA,EACzC,CAAA,CAAA,MAAQ;AACN,IAAA,OAAO,IAAA;AAAA,EACT;AACF;AAEA,SAAS,aAAa,KAAA,EAA4B;AAChD,EAAA,IAAI;AACF,IAAA,IAAI,OAAO,iBAAiB,WAAA,EAAa;AACzC,IAAA,IAAI,KAAA,EAAO;AACT,MAAA,YAAA,CAAa,OAAA,CAAQ,aAAa,KAAK,CAAA;AAAA,IACzC,CAAA,MAAO;AACL,MAAA,YAAA,CAAa,WAAW,WAAW,CAAA;AAAA,IACrC;AAAA,EACF,CAAA,CAAA,MAAQ;AAAA,EAER;AACF;AAuBO,IAAM,oBAAA,GAAuB,MAAA,CAAiB,CAAC,GAAA,MAAS;AAAA,EAC7D,UAAA,EAAY,IAAA;AAAA,EACZ,UAAU,EAAC;AAAA,EACX,SAAS,EAAC;AAAA,EACV,iBAAiB,EAAC;AAAA,EAClB,OAAA,EAAS,KAAA;AAAA,EAET,aAAA,EAAe,CAAC,GAAA,KAAQ;AACtB,IAAA,iBAAA,CAAkB,KAAK,EAAE,CAAA;AACzB,IAAA,YAAA,CAAa,GAAA,EAAK,MAAM,IAAI,CAAA;AAC5B,IAAA,GAAA,CAAI,EAAE,UAAA,EAAY,GAAA,EAAK,CAAA;AAAA,EACzB,CAAA;AAAA,EACA,aAAa,CAAC,IAAA,KAAS,IAAI,EAAE,QAAA,EAAU,MAAM,CAAA;AAAA,EAC7C,YAAY,CAAC,OAAA,KAAY,GAAA,CAAI,EAAE,SAAS,CAAA;AAAA,EACxC,oBAAoB,CAAC,KAAA,KAAU,IAAI,EAAE,eAAA,EAAiB,OAAO,CAAA;AAAA,EAC7D,YAAY,CAAC,OAAA,KAAY,GAAA,CAAI,EAAE,SAAS,CAAA;AAAA,EACxC,OAAO,MAAM;AACX,IAAA,iBAAA,CAAkB,MAAS,CAAA;AAU3B,IAAA,GAAA,CAAI,EAAE,UAAA,EAAY,IAAA,EAAM,QAAA,EAAU,EAAC,EAAG,OAAA,EAAS,EAAC,EAAG,OAAA,EAAS,KAAA,EAAO,CAAA;AAAA,EACrE;AACF,CAAA,CAAE","file":"chunk-
|
|
1
|
+
{"version":3,"sources":["../src/org/store.ts"],"names":[],"mappings":";;;;AAGA,IAAM,WAAA,GAAc,kBAAA;AAMpB,SAAS,iBAAA,GAAmC;AAC1C,EAAA,IAAI;AACF,IAAA,IAAI,OAAO,YAAA,KAAiB,WAAA,EAAa,OAAO,IAAA;AAChD,IAAA,OAAO,YAAA,CAAa,QAAQ,WAAW,CAAA;AAAA,EACzC,CAAA,CAAA,MAAQ;AACN,IAAA,OAAO,IAAA;AAAA,EACT;AACF;AAEA,SAAS,aAAa,KAAA,EAA4B;AAChD,EAAA,IAAI;AACF,IAAA,IAAI,OAAO,iBAAiB,WAAA,EAAa;AACzC,IAAA,IAAI,KAAA,EAAO;AACT,MAAA,YAAA,CAAa,OAAA,CAAQ,aAAa,KAAK,CAAA;AAAA,IACzC,CAAA,MAAO;AACL,MAAA,YAAA,CAAa,WAAW,WAAW,CAAA;AAAA,IACrC;AAAA,EACF,CAAA,CAAA,MAAQ;AAAA,EAER;AACF;AAuBO,IAAM,oBAAA,GAAuB,MAAA,CAAiB,CAAC,GAAA,MAAS;AAAA,EAC7D,UAAA,EAAY,IAAA;AAAA,EACZ,UAAU,EAAC;AAAA,EACX,SAAS,EAAC;AAAA,EACV,iBAAiB,EAAC;AAAA,EAClB,OAAA,EAAS,KAAA;AAAA,EAET,aAAA,EAAe,CAAC,GAAA,KAAQ;AACtB,IAAA,iBAAA,CAAkB,KAAK,EAAE,CAAA;AACzB,IAAA,YAAA,CAAa,GAAA,EAAK,MAAM,IAAI,CAAA;AAC5B,IAAA,GAAA,CAAI,EAAE,UAAA,EAAY,GAAA,EAAK,CAAA;AAAA,EACzB,CAAA;AAAA,EACA,aAAa,CAAC,IAAA,KAAS,IAAI,EAAE,QAAA,EAAU,MAAM,CAAA;AAAA,EAC7C,YAAY,CAAC,OAAA,KAAY,GAAA,CAAI,EAAE,SAAS,CAAA;AAAA,EACxC,oBAAoB,CAAC,KAAA,KAAU,IAAI,EAAE,eAAA,EAAiB,OAAO,CAAA;AAAA,EAC7D,YAAY,CAAC,OAAA,KAAY,GAAA,CAAI,EAAE,SAAS,CAAA;AAAA,EACxC,OAAO,MAAM;AACX,IAAA,iBAAA,CAAkB,MAAS,CAAA;AAU3B,IAAA,GAAA,CAAI,EAAE,UAAA,EAAY,IAAA,EAAM,QAAA,EAAU,EAAC,EAAG,OAAA,EAAS,EAAC,EAAG,OAAA,EAAS,KAAA,EAAO,CAAA;AAAA,EACrE;AACF,CAAA,CAAE","file":"chunk-5GWUM4SM.js","sourcesContent":["import { create } from 'zustand'\nimport { setActiveTenantId, type Organization, type OrgMember, type OrgMembership } from '@fayz-ai/core'\n\nconst STORAGE_KEY = 'fayz:current-org'\n\n// ---------------------------------------------------------------------------\n// Persistence helpers\n// ---------------------------------------------------------------------------\n\nfunction getPersistedOrgId(): string | null {\n try {\n if (typeof localStorage === 'undefined') return null\n return localStorage.getItem(STORAGE_KEY)\n } catch {\n return null\n }\n}\n\nfunction persistOrgId(orgId: string | null): void {\n try {\n if (typeof localStorage === 'undefined') return\n if (orgId) {\n localStorage.setItem(STORAGE_KEY, orgId)\n } else {\n localStorage.removeItem(STORAGE_KEY)\n }\n } catch {\n // ignore\n }\n}\n\n// ---------------------------------------------------------------------------\n// Store interface\n// ---------------------------------------------------------------------------\n\nexport interface OrgStore {\n currentOrg: Organization | null\n userOrgs: OrgMembership[]\n members: OrgMember[]\n /** Person `kind`s that make up the team (from config.team.personKinds). Drives\n * the Person-first Team screen; empty = legacy membership-only mode. */\n teamPersonKinds: string[]\n loading: boolean\n\n setCurrentOrg: (org: Organization | null) => void\n setUserOrgs: (orgs: OrgMembership[]) => void\n setMembers: (members: OrgMember[]) => void\n setTeamPersonKinds: (kinds: string[]) => void\n setLoading: (loading: boolean) => void\n reset: () => void\n}\n\nexport const useOrganizationStore = create<OrgStore>((set) => ({\n currentOrg: null,\n userOrgs: [],\n members: [],\n teamPersonKinds: [],\n loading: false,\n\n setCurrentOrg: (org) => {\n setActiveTenantId(org?.id)\n persistOrgId(org?.id ?? null)\n set({ currentOrg: org })\n },\n setUserOrgs: (orgs) => set({ userOrgs: orgs }),\n setMembers: (members) => set({ members }),\n setTeamPersonKinds: (kinds) => set({ teamPersonKinds: kinds }),\n setLoading: (loading) => set({ loading }),\n reset: () => {\n setActiveTenantId(undefined)\n // NOTE: do NOT clear the persisted org id here. The org providers call\n // reset() whenever the auth store transiently reports `user === null` —\n // which happens on every fresh load while the Supabase session hydrates\n // asynchronously. Wiping 'fayz:current-org' at that moment drops a\n // multi-org user's workspace selection BEFORE loadOrgs can read it back,\n // so the app always falls to memberships[0] on reload (single-org users\n // never noticed). The pin is a last-selected hint that's re-validated\n // against the fresh membership list on load, so it's safe to keep across\n // sign-out too (a mismatching pin just falls back to memberships[0]).\n set({ currentOrg: null, userOrgs: [], members: [], loading: false })\n },\n}))\n\nexport { getPersistedOrgId, persistOrgId }\n"]}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useOrganizationStore } from './chunk-
|
|
1
|
+
import { useOrganizationStore } from './chunk-5GWUM4SM.js';
|
|
2
2
|
import { create } from 'zustand';
|
|
3
3
|
import * as React from 'react';
|
|
4
4
|
import { Crown, Clock } from 'lucide-react';
|
|
@@ -190,5 +190,5 @@ function TrialWidget({ totalDays = 7, route = "/settings/subscription", classNam
|
|
|
190
190
|
}
|
|
191
191
|
|
|
192
192
|
export { NO_TRIAL, TrialWidget, effectivePlan, matchRoute, navigateTo, normalizeAdminPath, paidPlanIds, resolvePlanBadge, resolveTrial, routeScore, useAdminPath, useBillingStore, useTrial };
|
|
193
|
-
//# sourceMappingURL=chunk-
|
|
194
|
-
//# sourceMappingURL=chunk-
|
|
193
|
+
//# sourceMappingURL=chunk-HYDNIBN6.js.map
|
|
194
|
+
//# sourceMappingURL=chunk-HYDNIBN6.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/billing/store.ts","../src/billing/plan-badge.ts","../src/billing/trial.ts","../src/app/routing.tsx","../src/billing/TrialWidget.tsx"],"names":["paidPlanIds","React2"],"mappings":"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","file":"chunk-ERDS4OJS.js","sourcesContent":["import { create } from 'zustand'\nimport type { Plan, BillingCheckoutFn } from '@fayz-ai/core'\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport interface Subscription {\n id: string\n orgId: string\n planId: string\n status: 'active' | 'trialing' | 'past_due' | 'canceled' | 'incomplete'\n currentPeriodStart: string\n currentPeriodEnd: string\n cancelAtPeriodEnd?: boolean\n stripeSubscriptionId?: string\n}\n\nexport interface Invoice {\n id: string\n orgId: string\n amount: number\n currency: string\n status: 'paid' | 'open' | 'void' | 'uncollectible'\n pdfUrl?: string\n createdAt: string\n}\n\nexport interface BillingState {\n subscription: Subscription | null\n invoices: Invoice[]\n plans: Plan[]\n loading: boolean\n /** Payment-gateway seam seeded from `config.billing.onCheckout` (see core\n * BillingConfig). When set, the Subscription page routes plan changes through\n * it instead of writing `plan` straight onto the org. */\n checkout: BillingCheckoutFn | null\n}\n\n// ---------------------------------------------------------------------------\n// Store\n// ---------------------------------------------------------------------------\n\nexport interface BillingStore extends BillingState {\n setSubscription: (subscription: Subscription | null) => void\n setInvoices: (invoices: Invoice[]) => void\n setPlans: (plans: Plan[]) => void\n setLoading: (loading: boolean) => void\n setCheckout: (checkout: BillingCheckoutFn | null) => void\n reset: () => void\n}\n\nconst initialState: BillingState = {\n subscription: null,\n invoices: [],\n plans: [],\n loading: false,\n checkout: null,\n}\n\nexport const useBillingStore = create<BillingStore>((set) => ({\n ...initialState,\n setSubscription: (subscription) => set({ subscription }),\n setInvoices: (invoices) => set({ invoices }),\n setPlans: (plans) => set({ plans }),\n setLoading: (loading) => set({ loading }),\n setCheckout: (checkout) => set({ checkout }),\n reset: () => set(initialState),\n}))\n","import type { Plan } from '@fayz-ai/core'\n\nexport interface PlanBadgeInfo {\n /** Display label, e.g. 'Free' or 'Pro'. */\n label: string\n /** Resolved plan id (falls back to the raw org.plan string). */\n planId: string\n /** Paid plan (price > 0) — drives the Crown icon + gold styling in the menus. */\n paid: boolean\n}\n\nfunction capitalize(s: string): string {\n return s ? s.charAt(0).toUpperCase() + s.slice(1) : s\n}\n\n/**\n * Resolve an org's `plan` string against the configured billing plans into the\n * badge shown next to the user in both menus. Falls back to a capitalized\n * version of the raw string when the id isn't in config (defensive — e.g. a\n * legacy 'free' tenant before billing was wired).\n */\nexport function resolvePlanBadge(\n planId: string | undefined | null,\n plans: Plan[],\n): PlanBadgeInfo {\n const id = planId || 'free'\n const match = plans.find((p) => p.id === id)\n if (match) {\n return { label: match.name, planId: match.id, paid: match.price > 0 }\n }\n return { label: capitalize(id), planId: id, paid: false }\n}\n","import * as React from 'react'\nimport type { Organization, Plan } from '@fayz-ai/core'\nimport { useOrganizationStore } from '../org/store'\nimport { useBillingStore } from './store'\n\n/**\n * The free trial, as one derived fact.\n *\n * A trial is not a fourth plan or a state machine — it is a WINDOW during which\n * a tenant on the free plan is entitled to a paid one. Modelling it that way is\n * what keeps it out of every gate in the product: the access provider resolves\n * the effective plan once, and the nav crowns, the entitlement gates, the limit\n * caps and the assistant's lock all follow without knowing a trial exists.\n *\n * `trialEndsAt` is written by the database when the tenant is created (see\n * migration 147) and is never written by the client — a trial the browser could\n * extend is not a trial. A tenant with no `trialEndsAt` predates the column and\n * is left alone: status `none`, plan exactly as stored.\n */\n\nexport type TrialStatus = 'none' | 'active' | 'expired'\n\nexport interface TrialState {\n status: TrialStatus\n /** ISO date the window closes. */\n endsAt?: string\n /** Whole days remaining, floored at 0. `0` on the last day, not a negative. */\n daysLeft: number\n /** The last 48 hours, when the banner stops being informational. */\n endingSoon: boolean\n}\n\nexport const NO_TRIAL: TrialState = { status: 'none', daysLeft: 0, endingSoon: false }\n\nconst DAY_MS = 86_400_000\n\n/** Pure — the part worth testing, and it needs neither React nor a clock. */\nexport function resolveTrial(\n org: Pick<Organization, 'plan' | 'trialEndsAt'> | null | undefined,\n paidPlanIds: ReadonlySet<string>,\n now: number = Date.now(),\n): TrialState {\n if (!org?.trialEndsAt) return NO_TRIAL\n // Somebody who is paying is not on a trial, whatever the column still says.\n if (org.plan && paidPlanIds.has(org.plan)) return NO_TRIAL\n\n const ends = Date.parse(org.trialEndsAt)\n if (Number.isNaN(ends)) return NO_TRIAL\n\n const remaining = ends - now\n if (remaining <= 0) return { status: 'expired', endsAt: org.trialEndsAt, daysLeft: 0, endingSoon: false }\n\n const daysLeft = Math.ceil(remaining / DAY_MS)\n return {\n status: 'active',\n endsAt: org.trialEndsAt,\n daysLeft,\n endingSoon: remaining <= 2 * DAY_MS,\n }\n}\n\n/**\n * The plan whose entitlements apply RIGHT NOW.\n *\n * During the trial that is the trial plan — by default the most expensive one\n * configured, because the point of a trial is to be worth losing. After it, the\n * tenant's own plan, which for a free tenant is the free one: that fall is the\n * paywall, and it needs no separate enforcement path.\n */\nexport function effectivePlan(\n org: Pick<Organization, 'plan'> | null | undefined,\n plans: Plan[],\n trial: TrialState,\n trialPlanId?: string,\n): Plan | null {\n if (trial.status === 'active') {\n const named = trialPlanId ? plans.find((p) => p.id === trialPlanId) : undefined\n if (named) return named\n const paid = plans.filter((p) => p.price > 0)\n if (paid.length) return paid.reduce((best, p) => (p.price > best.price ? p : best))\n }\n return (org?.plan ? plans.find((p) => p.id === org.plan) : undefined) ?? null\n}\n\n/** Ids of the plans somebody actually pays for. */\nexport function paidPlanIds(plans: Plan[]): Set<string> {\n return new Set(plans.filter((p) => p.price > 0).map((p) => p.id))\n}\n\n/**\n * Live trial state for the current workspace.\n *\n * Ticks once an hour rather than on a timer per second: nothing on screen\n * counts down in real time, and a banner that says \"2 days left\" only has to be\n * right the next time somebody looks at it.\n */\nexport function useTrial(): TrialState {\n const org = useOrganizationStore((s) => s.currentOrg)\n const plans = useBillingStore((s) => s.plans)\n const [now, setNow] = React.useState(() => Date.now())\n\n React.useEffect(() => {\n const id = window.setInterval(() => setNow(Date.now()), 3_600_000)\n return () => window.clearInterval(id)\n }, [])\n\n return React.useMemo(\n () => resolveTrial(org, paidPlanIds(plans), now),\n [org?.plan, org?.trialEndsAt, plans, now],\n )\n}\n","import * as React from 'react'\nimport { hashRouterAdapter } from '@fayz-ai/core'\n\n// ---------------------------------------------------------------------------\n// Admin hash router — the same lightweight approach the storefront uses. The\n// admin shell collects routes from the plugin runtime + manifest pages and\n// switches on the current hash path. Deliberately not react-router: an SDK\n// shell shipping a router creates singleton hazards under source-alias dev.\n// ---------------------------------------------------------------------------\n\nconst adapter = hashRouterAdapter()\n\nexport function normalizeAdminPath(path: string): string {\n const clean = `/${path}`.replace(/\\/+/g, '/')\n return clean.length > 1 ? clean.replace(/\\/+$/, '') : clean\n}\n\nexport function navigateTo(to: string): void {\n adapter.navigate(normalizeAdminPath(to))\n}\n\nexport function useAdminPath(): string {\n const [path, setPath] = React.useState(() => normalizeAdminPath(adapter.getCurrentPath() || '/'))\n React.useEffect(() => {\n return adapter.onPathChange((p) => {\n setPath(normalizeAdminPath(p || '/'))\n if (typeof window !== 'undefined') window.scrollTo(0, 0)\n })\n }, [])\n return path\n}\n\n/** Match '/courses/:id' against '/courses/abc' → { id: 'abc' }; supports a\n * trailing '/*' wildcard (matches the base and any deeper path). */\nexport function matchRoute(pattern: string, path: string): Record<string, string> | null {\n const cleanPath = (path.split('?')[0] ?? '').replace(/\\/+$/, '') || '/'\n if (pattern.endsWith('/*')) {\n const base = pattern.slice(0, -2)\n if (cleanPath === base || cleanPath.startsWith(`${base}/`)) return {}\n return null\n }\n const patternParts = pattern.split('/').filter(Boolean)\n const pathParts = cleanPath.split('/').filter(Boolean)\n if (patternParts.length !== pathParts.length) return null\n const params: Record<string, string> = {}\n for (let i = 0; i < patternParts.length; i++) {\n const pat = patternParts[i]!\n const part = pathParts[i]!\n if (pat.startsWith(':')) params[pat.slice(1)] = decodeURIComponent(part)\n else if (pat !== part) return null\n }\n return params\n}\n\n/** Score a route pattern for specificity so the most specific match wins\n * (static segments beat params; longer beats shorter; wildcard loses). */\nexport function routeScore(pattern: string): number {\n const isWildcard = pattern.endsWith('/*')\n const routePattern = isWildcard ? pattern.slice(0, -2) : pattern\n const parts = routePattern.split('/').filter(Boolean)\n let score = parts.length * 10\n for (const p of parts) if (!p.startsWith(':')) score += 5\n if (isWildcard) score -= 1\n return score\n}\n","import * as React from 'react'\nimport { Clock, Crown } from 'lucide-react'\nimport { tl } from '@fayz-ai/core'\nimport { cn } from '../lib/cn'\nimport { navigateTo } from '../app/routing'\nimport { useLayoutStore } from '@fayz-ai/ui'\nimport { useTrial } from './trial'\n\nexport interface TrialWidgetProps {\n /** Length of the window, for the progress bar. Matches migration 147. */\n totalDays?: number\n /** Where \"see the plans\" goes. */\n route?: string\n className?: string\n}\n\n/**\n * The trial, in the rail.\n *\n * A countdown is a promise about the future, so it belongs where somebody looks\n * at their own progress — one line under the getting-started checklist, in the\n * menu's own colours, not a banner across the app. Two states only:\n *\n * • RUNNING — informational, and quiet until the last two days.\n * • ENDED — the paywall's face. It stops being a note and becomes a button,\n * because at that point every gated feature in the product is already\n * answering \"not on your plan\" and this is the one place that says why.\n *\n * There is no third state: a paying tenant, or one that predates trials, gets\n * nothing at all.\n */\nexport function TrialWidget({ totalDays = 7, route = '/settings/subscription', className }: TrialWidgetProps) {\n const trial = useTrial()\n const collapsed = useLayoutStore((s) => s.sidebarCollapsed)\n\n if (trial.status === 'none') return null\n\n const expired = trial.status === 'expired'\n const pct = Math.max(0, Math.min(100, (trial.daysLeft / totalDays) * 100))\n\n const label = expired\n ? tl('Trial ended', 'Seu teste terminou')\n : trial.daysLeft <= 1\n ? tl('Last day of your trial', 'Último dia do teste')\n : tl(`${trial.daysLeft} days of trial left`, `${trial.daysLeft} dias de teste`)\n\n if (collapsed) {\n return (\n <button\n type=\"button\"\n onClick={() => navigateTo(route)}\n title={label}\n aria-label={label}\n data-tour=\"billing.trial\"\n className={cn(\n 'flex w-full items-center justify-center rounded-button p-1.5 transition-colors',\n expired\n ? 'bg-primary/15 text-primary hover:bg-primary/25'\n : 'text-sidebar-foreground/70 hover:bg-sidebar-accent/60',\n className,\n )}\n >\n {expired ? <Crown className=\"h-4 w-4\" /> : <Clock className=\"h-4 w-4\" />}\n </button>\n )\n }\n\n return (\n <button\n type=\"button\"\n onClick={() => navigateTo(route)}\n data-tour=\"billing.trial\"\n className={cn(\n 'w-full rounded-button px-1.5 py-1.5 text-left transition-colors',\n expired ? 'bg-primary/15 hover:bg-primary/25' : 'hover:bg-sidebar-accent/50',\n className,\n )}\n >\n <span className=\"flex items-center gap-2\">\n {expired\n ? <Crown className=\"h-3.5 w-3.5 shrink-0 text-primary\" />\n : <Clock className={cn('h-3.5 w-3.5 shrink-0', trial.endingSoon ? 'text-warning' : 'text-sidebar-muted')} />}\n <span\n className={cn(\n 'min-w-0 flex-1 truncate text-[12px] font-medium',\n expired ? 'text-sidebar-foreground' : 'text-sidebar-foreground/90',\n )}\n >\n {label}\n </span>\n </span>\n {expired ? (\n <span className=\"mt-1 block pl-[22px] text-[11px] leading-snug text-sidebar-muted\">\n {tl('Choose a plan to keep the assistant', 'Escolha um plano para manter o assistente')}\n </span>\n ) : (\n <span className=\"mt-1.5 block h-1 overflow-hidden rounded-full bg-sidebar-accent\">\n <span\n className={cn(\n 'block h-full rounded-full transition-all',\n trial.endingSoon ? 'bg-warning' : 'bg-sidebar-foreground/70',\n )}\n style={{ width: `${pct}%` }}\n />\n </span>\n )}\n </button>\n )\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/billing/store.ts","../src/billing/plan-badge.ts","../src/billing/trial.ts","../src/app/routing.tsx","../src/billing/TrialWidget.tsx"],"names":["paidPlanIds","React2"],"mappings":"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","file":"chunk-HYDNIBN6.js","sourcesContent":["import { create } from 'zustand'\nimport type { Plan, BillingCheckoutFn } from '@fayz-ai/core'\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport interface Subscription {\n id: string\n orgId: string\n planId: string\n status: 'active' | 'trialing' | 'past_due' | 'canceled' | 'incomplete'\n currentPeriodStart: string\n currentPeriodEnd: string\n cancelAtPeriodEnd?: boolean\n stripeSubscriptionId?: string\n}\n\nexport interface Invoice {\n id: string\n orgId: string\n amount: number\n currency: string\n status: 'paid' | 'open' | 'void' | 'uncollectible'\n pdfUrl?: string\n createdAt: string\n}\n\nexport interface BillingState {\n subscription: Subscription | null\n invoices: Invoice[]\n plans: Plan[]\n loading: boolean\n /** Payment-gateway seam seeded from `config.billing.onCheckout` (see core\n * BillingConfig). When set, the Subscription page routes plan changes through\n * it instead of writing `plan` straight onto the org. */\n checkout: BillingCheckoutFn | null\n}\n\n// ---------------------------------------------------------------------------\n// Store\n// ---------------------------------------------------------------------------\n\nexport interface BillingStore extends BillingState {\n setSubscription: (subscription: Subscription | null) => void\n setInvoices: (invoices: Invoice[]) => void\n setPlans: (plans: Plan[]) => void\n setLoading: (loading: boolean) => void\n setCheckout: (checkout: BillingCheckoutFn | null) => void\n reset: () => void\n}\n\nconst initialState: BillingState = {\n subscription: null,\n invoices: [],\n plans: [],\n loading: false,\n checkout: null,\n}\n\nexport const useBillingStore = create<BillingStore>((set) => ({\n ...initialState,\n setSubscription: (subscription) => set({ subscription }),\n setInvoices: (invoices) => set({ invoices }),\n setPlans: (plans) => set({ plans }),\n setLoading: (loading) => set({ loading }),\n setCheckout: (checkout) => set({ checkout }),\n reset: () => set(initialState),\n}))\n","import type { Plan } from '@fayz-ai/core'\n\nexport interface PlanBadgeInfo {\n /** Display label, e.g. 'Free' or 'Pro'. */\n label: string\n /** Resolved plan id (falls back to the raw org.plan string). */\n planId: string\n /** Paid plan (price > 0) — drives the Crown icon + gold styling in the menus. */\n paid: boolean\n}\n\nfunction capitalize(s: string): string {\n return s ? s.charAt(0).toUpperCase() + s.slice(1) : s\n}\n\n/**\n * Resolve an org's `plan` string against the configured billing plans into the\n * badge shown next to the user in both menus. Falls back to a capitalized\n * version of the raw string when the id isn't in config (defensive — e.g. a\n * legacy 'free' tenant before billing was wired).\n */\nexport function resolvePlanBadge(\n planId: string | undefined | null,\n plans: Plan[],\n): PlanBadgeInfo {\n const id = planId || 'free'\n const match = plans.find((p) => p.id === id)\n if (match) {\n return { label: match.name, planId: match.id, paid: match.price > 0 }\n }\n return { label: capitalize(id), planId: id, paid: false }\n}\n","import * as React from 'react'\nimport type { Organization, Plan } from '@fayz-ai/core'\nimport { useOrganizationStore } from '../org/store'\nimport { useBillingStore } from './store'\n\n/**\n * The free trial, as one derived fact.\n *\n * A trial is not a fourth plan or a state machine — it is a WINDOW during which\n * a tenant on the free plan is entitled to a paid one. Modelling it that way is\n * what keeps it out of every gate in the product: the access provider resolves\n * the effective plan once, and the nav crowns, the entitlement gates, the limit\n * caps and the assistant's lock all follow without knowing a trial exists.\n *\n * `trialEndsAt` is written by the database when the tenant is created (see\n * migration 147) and is never written by the client — a trial the browser could\n * extend is not a trial. A tenant with no `trialEndsAt` predates the column and\n * is left alone: status `none`, plan exactly as stored.\n */\n\nexport type TrialStatus = 'none' | 'active' | 'expired'\n\nexport interface TrialState {\n status: TrialStatus\n /** ISO date the window closes. */\n endsAt?: string\n /** Whole days remaining, floored at 0. `0` on the last day, not a negative. */\n daysLeft: number\n /** The last 48 hours, when the banner stops being informational. */\n endingSoon: boolean\n}\n\nexport const NO_TRIAL: TrialState = { status: 'none', daysLeft: 0, endingSoon: false }\n\nconst DAY_MS = 86_400_000\n\n/** Pure — the part worth testing, and it needs neither React nor a clock. */\nexport function resolveTrial(\n org: Pick<Organization, 'plan' | 'trialEndsAt'> | null | undefined,\n paidPlanIds: ReadonlySet<string>,\n now: number = Date.now(),\n): TrialState {\n if (!org?.trialEndsAt) return NO_TRIAL\n // Somebody who is paying is not on a trial, whatever the column still says.\n if (org.plan && paidPlanIds.has(org.plan)) return NO_TRIAL\n\n const ends = Date.parse(org.trialEndsAt)\n if (Number.isNaN(ends)) return NO_TRIAL\n\n const remaining = ends - now\n if (remaining <= 0) return { status: 'expired', endsAt: org.trialEndsAt, daysLeft: 0, endingSoon: false }\n\n const daysLeft = Math.ceil(remaining / DAY_MS)\n return {\n status: 'active',\n endsAt: org.trialEndsAt,\n daysLeft,\n endingSoon: remaining <= 2 * DAY_MS,\n }\n}\n\n/**\n * The plan whose entitlements apply RIGHT NOW.\n *\n * During the trial that is the trial plan — by default the most expensive one\n * configured, because the point of a trial is to be worth losing. After it, the\n * tenant's own plan, which for a free tenant is the free one: that fall is the\n * paywall, and it needs no separate enforcement path.\n */\nexport function effectivePlan(\n org: Pick<Organization, 'plan'> | null | undefined,\n plans: Plan[],\n trial: TrialState,\n trialPlanId?: string,\n): Plan | null {\n if (trial.status === 'active') {\n const named = trialPlanId ? plans.find((p) => p.id === trialPlanId) : undefined\n if (named) return named\n const paid = plans.filter((p) => p.price > 0)\n if (paid.length) return paid.reduce((best, p) => (p.price > best.price ? p : best))\n }\n return (org?.plan ? plans.find((p) => p.id === org.plan) : undefined) ?? null\n}\n\n/** Ids of the plans somebody actually pays for. */\nexport function paidPlanIds(plans: Plan[]): Set<string> {\n return new Set(plans.filter((p) => p.price > 0).map((p) => p.id))\n}\n\n/**\n * Live trial state for the current workspace.\n *\n * Ticks once an hour rather than on a timer per second: nothing on screen\n * counts down in real time, and a banner that says \"2 days left\" only has to be\n * right the next time somebody looks at it.\n */\nexport function useTrial(): TrialState {\n const org = useOrganizationStore((s) => s.currentOrg)\n const plans = useBillingStore((s) => s.plans)\n const [now, setNow] = React.useState(() => Date.now())\n\n React.useEffect(() => {\n const id = window.setInterval(() => setNow(Date.now()), 3_600_000)\n return () => window.clearInterval(id)\n }, [])\n\n return React.useMemo(\n () => resolveTrial(org, paidPlanIds(plans), now),\n [org?.plan, org?.trialEndsAt, plans, now],\n )\n}\n","import * as React from 'react'\nimport { hashRouterAdapter } from '@fayz-ai/core'\n\n// ---------------------------------------------------------------------------\n// Admin hash router — the same lightweight approach the storefront uses. The\n// admin shell collects routes from the plugin runtime + manifest pages and\n// switches on the current hash path. Deliberately not react-router: an SDK\n// shell shipping a router creates singleton hazards under source-alias dev.\n// ---------------------------------------------------------------------------\n\nconst adapter = hashRouterAdapter()\n\nexport function normalizeAdminPath(path: string): string {\n const clean = `/${path}`.replace(/\\/+/g, '/')\n return clean.length > 1 ? clean.replace(/\\/+$/, '') : clean\n}\n\nexport function navigateTo(to: string): void {\n adapter.navigate(normalizeAdminPath(to))\n}\n\nexport function useAdminPath(): string {\n const [path, setPath] = React.useState(() => normalizeAdminPath(adapter.getCurrentPath() || '/'))\n React.useEffect(() => {\n return adapter.onPathChange((p) => {\n setPath(normalizeAdminPath(p || '/'))\n if (typeof window !== 'undefined') window.scrollTo(0, 0)\n })\n }, [])\n return path\n}\n\n/** Match '/courses/:id' against '/courses/abc' → { id: 'abc' }; supports a\n * trailing '/*' wildcard (matches the base and any deeper path). */\nexport function matchRoute(pattern: string, path: string): Record<string, string> | null {\n const cleanPath = (path.split('?')[0] ?? '').replace(/\\/+$/, '') || '/'\n if (pattern.endsWith('/*')) {\n const base = pattern.slice(0, -2)\n if (cleanPath === base || cleanPath.startsWith(`${base}/`)) return {}\n return null\n }\n const patternParts = pattern.split('/').filter(Boolean)\n const pathParts = cleanPath.split('/').filter(Boolean)\n if (patternParts.length !== pathParts.length) return null\n const params: Record<string, string> = {}\n for (let i = 0; i < patternParts.length; i++) {\n const pat = patternParts[i]!\n const part = pathParts[i]!\n if (pat.startsWith(':')) params[pat.slice(1)] = decodeURIComponent(part)\n else if (pat !== part) return null\n }\n return params\n}\n\n/** Score a route pattern for specificity so the most specific match wins\n * (static segments beat params; longer beats shorter; wildcard loses). */\nexport function routeScore(pattern: string): number {\n const isWildcard = pattern.endsWith('/*')\n const routePattern = isWildcard ? pattern.slice(0, -2) : pattern\n const parts = routePattern.split('/').filter(Boolean)\n let score = parts.length * 10\n for (const p of parts) if (!p.startsWith(':')) score += 5\n if (isWildcard) score -= 1\n return score\n}\n","import * as React from 'react'\nimport { Clock, Crown } from 'lucide-react'\nimport { tl } from '@fayz-ai/core'\nimport { cn } from '../lib/cn'\nimport { navigateTo } from '../app/routing'\nimport { useLayoutStore } from '@fayz-ai/ui'\nimport { useTrial } from './trial'\n\nexport interface TrialWidgetProps {\n /** Length of the window, for the progress bar. Matches migration 147. */\n totalDays?: number\n /** Where \"see the plans\" goes. */\n route?: string\n className?: string\n}\n\n/**\n * The trial, in the rail.\n *\n * A countdown is a promise about the future, so it belongs where somebody looks\n * at their own progress — one line under the getting-started checklist, in the\n * menu's own colours, not a banner across the app. Two states only:\n *\n * • RUNNING — informational, and quiet until the last two days.\n * • ENDED — the paywall's face. It stops being a note and becomes a button,\n * because at that point every gated feature in the product is already\n * answering \"not on your plan\" and this is the one place that says why.\n *\n * There is no third state: a paying tenant, or one that predates trials, gets\n * nothing at all.\n */\nexport function TrialWidget({ totalDays = 7, route = '/settings/subscription', className }: TrialWidgetProps) {\n const trial = useTrial()\n const collapsed = useLayoutStore((s) => s.sidebarCollapsed)\n\n if (trial.status === 'none') return null\n\n const expired = trial.status === 'expired'\n const pct = Math.max(0, Math.min(100, (trial.daysLeft / totalDays) * 100))\n\n const label = expired\n ? tl('Trial ended', 'Seu teste terminou')\n : trial.daysLeft <= 1\n ? tl('Last day of your trial', 'Último dia do teste')\n : tl(`${trial.daysLeft} days of trial left`, `${trial.daysLeft} dias de teste`)\n\n if (collapsed) {\n return (\n <button\n type=\"button\"\n onClick={() => navigateTo(route)}\n title={label}\n aria-label={label}\n data-tour=\"billing.trial\"\n className={cn(\n 'flex w-full items-center justify-center rounded-button p-1.5 transition-colors',\n expired\n ? 'bg-primary/15 text-primary hover:bg-primary/25'\n : 'text-sidebar-foreground/70 hover:bg-sidebar-accent/60',\n className,\n )}\n >\n {expired ? <Crown className=\"h-4 w-4\" /> : <Clock className=\"h-4 w-4\" />}\n </button>\n )\n }\n\n return (\n <button\n type=\"button\"\n onClick={() => navigateTo(route)}\n data-tour=\"billing.trial\"\n className={cn(\n 'w-full rounded-button px-1.5 py-1.5 text-left transition-colors',\n expired ? 'bg-primary/15 hover:bg-primary/25' : 'hover:bg-sidebar-accent/50',\n className,\n )}\n >\n <span className=\"flex items-center gap-2\">\n {expired\n ? <Crown className=\"h-3.5 w-3.5 shrink-0 text-primary\" />\n : <Clock className={cn('h-3.5 w-3.5 shrink-0', trial.endingSoon ? 'text-warning' : 'text-sidebar-muted')} />}\n <span\n className={cn(\n 'min-w-0 flex-1 truncate text-[12px] font-medium',\n expired ? 'text-sidebar-foreground' : 'text-sidebar-foreground/90',\n )}\n >\n {label}\n </span>\n </span>\n {expired ? (\n <span className=\"mt-1 block pl-[22px] text-[11px] leading-snug text-sidebar-muted\">\n {tl('Choose a plan to keep the assistant', 'Escolha um plano para manter o assistente')}\n </span>\n ) : (\n <span className=\"mt-1.5 block h-1 overflow-hidden rounded-full bg-sidebar-accent\">\n <span\n className={cn(\n 'block h-full rounded-full transition-all',\n trial.endingSoon ? 'bg-warning' : 'bg-sidebar-foreground/70',\n )}\n style={{ width: `${pct}%` }}\n />\n </span>\n )}\n </button>\n )\n}\n"]}
|
|
@@ -1,9 +1,64 @@
|
|
|
1
|
-
import { usePermissionsStore } from './chunk-
|
|
2
|
-
import { useOrganizationStore, getPersistedOrgId } from './chunk-
|
|
1
|
+
import { usePermissionsStore } from './chunk-WJ3H3RJN.js';
|
|
2
|
+
import { useOrganizationStore, getPersistedOrgId } from './chunk-5GWUM4SM.js';
|
|
3
3
|
import * as React from 'react';
|
|
4
|
-
import { useAuthStore } from '@fayz-ai/auth';
|
|
4
|
+
import { useAuthStore, readAppHandoff } from '@fayz-ai/auth';
|
|
5
5
|
import { jsx } from 'react/jsx-runtime';
|
|
6
6
|
|
|
7
|
+
// src/org/choice.ts
|
|
8
|
+
var AUTOPICK_KEY = "fayz:workspace-autopick";
|
|
9
|
+
var SWITCHING_KEY = "fayz:workspace-switching";
|
|
10
|
+
function markWorkspaceSwitch(orgId) {
|
|
11
|
+
try {
|
|
12
|
+
if (typeof sessionStorage === "undefined") return;
|
|
13
|
+
sessionStorage.setItem(SWITCHING_KEY, orgId);
|
|
14
|
+
} catch {
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
function takeWorkspaceSwitch() {
|
|
18
|
+
try {
|
|
19
|
+
if (typeof sessionStorage === "undefined") return null;
|
|
20
|
+
const v = sessionStorage.getItem(SWITCHING_KEY);
|
|
21
|
+
if (v) sessionStorage.removeItem(SWITCHING_KEY);
|
|
22
|
+
return v;
|
|
23
|
+
} catch {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
function getAutoPick() {
|
|
28
|
+
try {
|
|
29
|
+
if (typeof localStorage === "undefined") return false;
|
|
30
|
+
return localStorage.getItem(AUTOPICK_KEY) === "1";
|
|
31
|
+
} catch {
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
function setAutoPick(on) {
|
|
36
|
+
try {
|
|
37
|
+
if (typeof localStorage === "undefined") return;
|
|
38
|
+
if (on) localStorage.setItem(AUTOPICK_KEY, "1");
|
|
39
|
+
else localStorage.removeItem(AUTOPICK_KEY);
|
|
40
|
+
} catch {
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
function decideWorkspaceChoice({
|
|
44
|
+
memberships,
|
|
45
|
+
switchingToId = null,
|
|
46
|
+
handoffId,
|
|
47
|
+
pinnedId,
|
|
48
|
+
autoPick
|
|
49
|
+
}) {
|
|
50
|
+
if (memberships.length === 0) return { kind: "none" };
|
|
51
|
+
const has = (id) => id && memberships.some((m) => m.orgId === id) ? id : null;
|
|
52
|
+
const switching = has(switchingToId);
|
|
53
|
+
if (switching) return { kind: "resolved", orgId: switching };
|
|
54
|
+
const handoff = has(handoffId);
|
|
55
|
+
if (handoff) return { kind: "resolved", orgId: handoff };
|
|
56
|
+
if (memberships.length === 1) return { kind: "resolved", orgId: memberships[0].orgId };
|
|
57
|
+
const pinned = has(pinnedId);
|
|
58
|
+
if (pinned && autoPick) return { kind: "resolved", orgId: pinned };
|
|
59
|
+
return { kind: "ask", suggestedId: pinned };
|
|
60
|
+
}
|
|
61
|
+
|
|
7
62
|
// src/org/cache.ts
|
|
8
63
|
var STORAGE_KEY = "fayz:org-snapshot";
|
|
9
64
|
var VERSION = 1;
|
|
@@ -108,7 +163,11 @@ function OrgProvider({
|
|
|
108
163
|
const [settledFor, setSettledFor] = React.useState(
|
|
109
164
|
() => snapshot?.org ? snapshot.userId : null
|
|
110
165
|
);
|
|
166
|
+
const [orgsReadFor, setOrgsReadFor] = React.useState(
|
|
167
|
+
() => snapshot?.org ? snapshot.userId : null
|
|
168
|
+
);
|
|
111
169
|
const settled = user ? settledFor === user.id : !authLoading;
|
|
170
|
+
const orgsKnown = !!user && orgsReadFor === user.id;
|
|
112
171
|
useIsomorphicLayoutEffect(() => {
|
|
113
172
|
if (!snapshot) return;
|
|
114
173
|
setUserOrgs(snapshot.userOrgs);
|
|
@@ -176,11 +235,13 @@ function OrgProvider({
|
|
|
176
235
|
reset();
|
|
177
236
|
clearOrgSnapshot();
|
|
178
237
|
setSettledFor(null);
|
|
238
|
+
setOrgsReadFor(null);
|
|
179
239
|
return;
|
|
180
240
|
}
|
|
181
241
|
const cached = snapshot?.userId === user.id ? snapshot : null;
|
|
182
242
|
if (cached && isSnapshotFresh(cached, cacheTtlMs)) {
|
|
183
243
|
setSettledFor(user.id);
|
|
244
|
+
setOrgsReadFor(user.id);
|
|
184
245
|
return;
|
|
185
246
|
}
|
|
186
247
|
if (snapshot && snapshot.userId !== user.id && loadedForRef.current === null) {
|
|
@@ -206,6 +267,7 @@ function OrgProvider({
|
|
|
206
267
|
}
|
|
207
268
|
if (cancelled) return;
|
|
208
269
|
setUserOrgs(memberships);
|
|
270
|
+
setOrgsReadFor(user.id);
|
|
209
271
|
if (memberships.length === 0 && accepted === 0 && autoCreate) {
|
|
210
272
|
const newOrg = await adapterRef.current.createOrg(
|
|
211
273
|
user.fullName ? `${user.fullName}'s Organization` : "My Organization",
|
|
@@ -227,10 +289,18 @@ function OrgProvider({
|
|
|
227
289
|
}
|
|
228
290
|
return;
|
|
229
291
|
}
|
|
230
|
-
const
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
292
|
+
const choice = decideWorkspaceChoice({
|
|
293
|
+
memberships,
|
|
294
|
+
// Consumed here, and only here: this is the one boot the marker was
|
|
295
|
+
// written for, and leaving it behind would pin the next one too.
|
|
296
|
+
switchingToId: takeWorkspaceSwitch(),
|
|
297
|
+
handoffId: readAppHandoff()?.tenantId ?? null,
|
|
298
|
+
pinnedId: getPersistedOrgId(),
|
|
299
|
+
autoPick: getAutoPick()
|
|
300
|
+
});
|
|
301
|
+
if (choice.kind !== "resolved") return;
|
|
302
|
+
if (!cancelled) {
|
|
303
|
+
const org = await adapterRef.current.getOrg(choice.orgId);
|
|
234
304
|
if (!cancelled) {
|
|
235
305
|
setCurrentOrg(org);
|
|
236
306
|
await hydrateOrg(org, memberships, user.id);
|
|
@@ -272,12 +342,13 @@ function OrgProvider({
|
|
|
272
342
|
userOrgs,
|
|
273
343
|
loading,
|
|
274
344
|
settled,
|
|
345
|
+
orgsKnown,
|
|
275
346
|
switchOrg,
|
|
276
347
|
refreshOrg
|
|
277
348
|
};
|
|
278
349
|
return /* @__PURE__ */ jsx(OrgAdapterContext.Provider, { value: adapter, children: /* @__PURE__ */ jsx(TenantContext.Provider, { value: tenantValue, children }) });
|
|
279
350
|
}
|
|
280
351
|
|
|
281
|
-
export { ORG_CACHE_TTL_MS, OrgProvider, clearOrgSnapshot, readOrgSnapshot, useOrgAdapter, useOrgAdapterOptional, useTenant, useTenantOptional };
|
|
282
|
-
//# sourceMappingURL=chunk-
|
|
283
|
-
//# sourceMappingURL=chunk-
|
|
352
|
+
export { ORG_CACHE_TTL_MS, OrgProvider, clearOrgSnapshot, decideWorkspaceChoice, getAutoPick, markWorkspaceSwitch, readOrgSnapshot, setAutoPick, takeWorkspaceSwitch, useOrgAdapter, useOrgAdapterOptional, useTenant, useTenantOptional };
|
|
353
|
+
//# sourceMappingURL=chunk-NB6X5O5L.js.map
|
|
354
|
+
//# sourceMappingURL=chunk-NB6X5O5L.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/org/choice.ts","../src/org/cache.ts","../src/org/context.tsx"],"names":[],"mappings":"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","file":"chunk-NB6X5O5L.js","sourcesContent":["import type { OrgMembership } from '@fayz-ai/core'\n\n/**\n * Which workspace this account lands in, when it belongs to more than one.\n *\n * The provider used to answer this by itself — `handoff ?? pinned ?? first` —\n * and that last fallback is a guess with no way to correct it: an account in\n * seven workspaces opened whichever one sorted first and never learnt that the\n * other six existed. The rule below keeps every branch that IS an answer and\n * hands the remaining case to the person, once.\n */\n\nconst AUTOPICK_KEY = 'fayz:workspace-autopick'\n\n/**\n * The workspace a switch already committed to, surviving exactly one reload.\n *\n * Changing workspace ends in a full page reload — the only way to guarantee\n * that every plugin store, cache and open screen is rebuilt against the new\n * tenant instead of showing the previous one's rows. That reload comes back\n * through the same boot path as a fresh login, so without this marker the\n * picker would open and ask a question the user answered one second ago.\n *\n * sessionStorage, not local: it is about THIS navigation, and a tab opened\n * tomorrow should ask again.\n */\nconst SWITCHING_KEY = 'fayz:workspace-switching'\n\n/** Records the destination, for the boot that happens after the reload. */\nexport function markWorkspaceSwitch(orgId: string): void {\n try {\n if (typeof sessionStorage === 'undefined') return\n sessionStorage.setItem(SWITCHING_KEY, orgId)\n } catch {\n // ignore\n }\n}\n\n/** Reads the destination and consumes it — it is good for one boot only. */\nexport function takeWorkspaceSwitch(): string | null {\n try {\n if (typeof sessionStorage === 'undefined') return null\n const v = sessionStorage.getItem(SWITCHING_KEY)\n if (v) sessionStorage.removeItem(SWITCHING_KEY)\n return v\n } catch {\n return null\n }\n}\n\n/** Whether the last explicit choice stands in for every future login. */\nexport function getAutoPick(): boolean {\n try {\n if (typeof localStorage === 'undefined') return false\n return localStorage.getItem(AUTOPICK_KEY) === '1'\n } catch {\n return false\n }\n}\n\nexport function setAutoPick(on: boolean): void {\n try {\n if (typeof localStorage === 'undefined') return\n if (on) localStorage.setItem(AUTOPICK_KEY, '1')\n else localStorage.removeItem(AUTOPICK_KEY)\n } catch {\n // ignore\n }\n}\n\nexport interface WorkspaceChoiceInput {\n /** Every workspace this account belongs to, as the membership list read it. */\n memberships: OrgMembership[]\n /** A switch that is completing across a reload — already answered, once. */\n switchingToId?: string | null\n /** A sibling app's switcher named a workspace on the way in (008). */\n handoffId: string | null\n /** `fayz:current-org` — the last workspace this browser was in. */\n pinnedId: string | null\n /** \"Não perguntar novamente\" is on for this browser. */\n autoPick: boolean\n}\n\nexport type WorkspaceChoice =\n /** Open this one without asking. */\n | { kind: 'resolved'; orgId: string }\n /** Put the picker on screen; `suggestedId` is the row to pre-select. */\n | { kind: 'ask'; suggestedId: string | null }\n /** No workspace at all — the first-run setup flow's business, not ours. */\n | { kind: 'none' }\n\n/**\n * The decision, as a function, so the one piece of logic that decides what a\n * multi-workspace account sees after login can be tested without a browser.\n */\nexport function decideWorkspaceChoice({\n memberships,\n switchingToId = null,\n handoffId,\n pinnedId,\n autoPick,\n}: WorkspaceChoiceInput): WorkspaceChoice {\n if (memberships.length === 0) return { kind: 'none' }\n\n const has = (id: string | null): string | null =>\n id && memberships.some((m) => m.orgId === id) ? id : null\n\n // The freshest answer there is: this reload IS the switch completing.\n const switching = has(switchingToId)\n if (switching) return { kind: 'resolved', orgId: switching }\n\n // A handoff is somebody clicking a specific account in a sibling app's\n // switcher one second ago. That is a fresher answer than anything stored\n // here, and asking again would be asking a question already answered.\n const handoff = has(handoffId)\n if (handoff) return { kind: 'resolved', orgId: handoff }\n\n // One workspace is not a choice.\n if (memberships.length === 1) return { kind: 'resolved', orgId: memberships[0]!.orgId }\n\n // The pin is a HINT, always re-validated against the list we just read: a\n // workspace someone was removed from falls through to the question instead of\n // opening an account they can no longer see.\n const pinned = has(pinnedId)\n if (pinned && autoPick) return { kind: 'resolved', orgId: pinned }\n\n return { kind: 'ask', suggestedId: pinned }\n}\n","import type { Organization, OrgMember, OrgMembership, PermissionProfile } from '@fayz-ai/core'\n\n/**\n * Snapshot of the org bootstrap (org + memberships + members + permission\n * profiles) so a page reload doesn't wait on the whole chain again.\n *\n * Every mount used to pay `acceptPendingInvites → listUserOrgs → getOrg →\n * listMembers + listProfiles` before `org.id` existed, and every screen keyed\n * off the tenant (Conectores, plugin tabs, permission gates) sat idle until\n * then. The snapshot paints that state synchronously and the network is only\n * touched again once it goes stale.\n */\n\nconst STORAGE_KEY = 'fayz:org-snapshot'\nconst VERSION = 1\n\n/** Served without touching the network. */\nexport const ORG_CACHE_TTL_MS = 5 * 60 * 1000\n/** Older than this and the snapshot is dropped instead of painted. */\nconst MAX_AGE_MS = 24 * 60 * 60 * 1000\n/** Bigger than this (huge member lists) and we skip persisting rather than blow the quota. */\nconst MAX_BYTES = 512 * 1024\n\nexport interface OrgSnapshot {\n version: number\n /** Who the snapshot belongs to — a different user gets a cold load. */\n userId: string\n /** When the data last came from the server (store patches don't move it). */\n savedAt: number\n org: Organization\n userOrgs: OrgMembership[]\n members: OrgMember[]\n profiles: PermissionProfile[]\n currentProfileId: string | null\n}\n\nexport type OrgSnapshotInput = Omit<OrgSnapshot, 'version' | 'savedAt'>\n\nfunction storage(): Storage | null {\n try {\n if (typeof localStorage === 'undefined') return null\n return localStorage\n } catch {\n return null\n }\n}\n\nexport function readOrgSnapshot(): OrgSnapshot | null {\n const store = storage()\n if (!store) return null\n try {\n const raw = store.getItem(STORAGE_KEY)\n if (!raw) return null\n const snapshot = JSON.parse(raw) as OrgSnapshot\n if (snapshot?.version !== VERSION || !snapshot.org?.id || !snapshot.userId) return null\n if (snapshotAge(snapshot) > MAX_AGE_MS) {\n store.removeItem(STORAGE_KEY)\n return null\n }\n return snapshot\n } catch {\n return null\n }\n}\n\nexport function snapshotAge(snapshot: OrgSnapshot): number {\n const age = Date.now() - snapshot.savedAt\n // A clock that moved backwards shouldn't hand out an eternally fresh cache.\n return age < 0 ? Number.POSITIVE_INFINITY : age\n}\n\nexport function isSnapshotFresh(snapshot: OrgSnapshot, ttlMs: number): boolean {\n return snapshotAge(snapshot) < ttlMs\n}\n\n/** Record a fresh server read — restarts the TTL window. */\nexport function saveOrgSnapshot(input: OrgSnapshotInput): void {\n write({ ...input, version: VERSION, savedAt: Date.now() })\n}\n\n/**\n * Keep the snapshot in step with store writes made outside the provider\n * (renaming the org in Settings, editing a role) WITHOUT restarting the TTL —\n * otherwise a UI-only change would push the next revalidation out forever.\n */\nexport function patchOrgSnapshot(input: OrgSnapshotInput): void {\n const current = readOrgSnapshot()\n if (!current || current.userId !== input.userId) return\n write({ ...input, version: VERSION, savedAt: current.savedAt })\n}\n\nexport function clearOrgSnapshot(): void {\n try {\n storage()?.removeItem(STORAGE_KEY)\n } catch {\n // ignore\n }\n}\n\nfunction write(snapshot: OrgSnapshot): void {\n const store = storage()\n if (!store) return\n try {\n const raw = JSON.stringify(snapshot)\n if (raw.length > MAX_BYTES) return\n store.setItem(STORAGE_KEY, raw)\n } catch {\n // Quota or serialization failure — the cache is an optimization, not state.\n }\n}\n","import * as React from 'react'\nimport type { OrgAdapter, Organization, OrgMembership } from '@fayz-ai/core'\nimport { useAuthStore, readAppHandoff } from '@fayz-ai/auth'\nimport { useOrganizationStore, getPersistedOrgId } from './store'\nimport { decideWorkspaceChoice, getAutoPick, takeWorkspaceSwitch } from './choice'\nimport { usePermissionsStore } from '../permissions'\nimport {\n ORG_CACHE_TTL_MS,\n clearOrgSnapshot,\n isSnapshotFresh,\n patchOrgSnapshot,\n readOrgSnapshot,\n saveOrgSnapshot,\n type OrgSnapshot,\n} from './cache'\n\n// ---------------------------------------------------------------------------\n// Adapter context\n// ---------------------------------------------------------------------------\n\nconst OrgAdapterContext = React.createContext<OrgAdapter | null>(null)\n\nexport function useOrgAdapter(): OrgAdapter {\n const adapter = React.useContext(OrgAdapterContext)\n if (!adapter) {\n throw new Error('useOrgAdapter must be used within an <OrgProvider>')\n }\n return adapter\n}\n\nexport function useOrgAdapterOptional(): OrgAdapter | null {\n return React.useContext(OrgAdapterContext)\n}\n\n// ---------------------------------------------------------------------------\n// Tenant / org hook\n// ---------------------------------------------------------------------------\n\nexport interface TenantContext {\n /** The currently active organization */\n org: Organization | null\n /** All orgs the current user belongs to */\n userOrgs: OrgMembership[]\n loading: boolean\n /**\n * The bootstrap has finished at least once, so `org === null` now MEANS\n * \"belongs to no workspace\" instead of \"we have not looked yet\".\n *\n * `loading` cannot answer that: it starts `false`, before the effect that\n * loads anything has run. Anything deciding what to render from an empty org\n * — the setup gate — reads this, or it decides during the gap and flips its\n * mind a frame later.\n */\n settled: boolean\n /**\n * The membership list was actually READ for this account, and answered.\n *\n * Distinct from `settled`, which only says the wait is over — it is set in a\n * `finally`, so a failed `listUserOrgs` settles too. Anything that treats\n * \"no orgs\" as a FACT (the first-run setup gate) has to ask this instead.\n */\n orgsKnown: boolean\n /** Switch the active org */\n switchOrg: (orgId: string) => Promise<void>\n /** Refresh the current org from the adapter */\n refreshOrg: () => Promise<void>\n}\n\nconst TenantContext = React.createContext<TenantContext | null>(null)\n\n/**\n * Access the current tenant / organization.\n * Must be used inside <OrgProvider>.\n */\nexport function useTenant(): TenantContext {\n const ctx = React.useContext(TenantContext)\n if (!ctx) {\n throw new Error('useTenant must be used inside <OrgProvider>')\n }\n return ctx\n}\n\nexport function useTenantOptional(): TenantContext | null {\n return React.useContext(TenantContext)\n}\n\n// ---------------------------------------------------------------------------\n// Provider\n// ---------------------------------------------------------------------------\n\nexport interface OrgProviderProps {\n adapter: OrgAdapter\n /** When true, automatically creates an org if the user has none */\n autoCreate?: boolean\n /**\n * How long the cached org bootstrap is served before the provider goes back\n * to the network (default 5 min). A cached load paints synchronously, so\n * screens that key off `org.id` don't wait a round trip on every reload.\n * `0` disables the cache and restores the always-fetch behaviour.\n */\n cacheTtlMs?: number\n children: React.ReactNode\n}\n\nconst useIsomorphicLayoutEffect =\n typeof window !== 'undefined' ? React.useLayoutEffect : React.useEffect\n\nexport function OrgProvider({\n adapter,\n autoCreate = false,\n cacheTtlMs = ORG_CACHE_TTL_MS,\n children,\n}: OrgProviderProps) {\n const { user, isLoading: authLoading } = useAuthStore()\n const { currentOrg, userOrgs, loading, setCurrentOrg, setUserOrgs, setMembers, setLoading, reset } =\n useOrganizationStore()\n const setProfiles = usePermissionsStore((s) => s.setProfiles)\n const setCurrentProfile = usePermissionsStore((s) => s.setCurrentProfile)\n\n const adapterRef = React.useRef(adapter)\n adapterRef.current = adapter\n\n const [snapshot] = React.useState<OrgSnapshot | null>(() =>\n cacheTtlMs > 0 ? readOrgSnapshot() : null,\n )\n // One load per signed-in user per mount — auth reports `isLoading` and `user`\n // in two separate store writes, so the effect would otherwise fire twice.\n const loadedForRef = React.useRef<string | null>(null)\n\n // WHOSE bootstrap has finished, not whether one has. A boolean could not tell\n // the difference between \"we know this account has no workspace\" and \"we knew\n // that about the account that just signed out\" — which is what flashed the\n // setup flow over every login: sign-out settled it, and sign-in kept the\n // stale `true` for the whole round trip.\n //\n // A painted snapshot is already an answer for its own user: that account HAS a\n // workspace, and revalidation will not turn that into \"has none\".\n const [settledFor, setSettledFor] = React.useState<string | null>(() =>\n snapshot?.org ? snapshot.userId : null,\n )\n // Same shape, different question: WHOSE membership list came back. Seeded\n // from a painted snapshot for the same reason — that account's list was read\n // once and cached, and revalidation cannot turn \"has one\" into \"has none\".\n const [orgsReadFor, setOrgsReadFor] = React.useState<string | null>(() =>\n snapshot?.org ? snapshot.userId : null,\n )\n // Signed out there is nothing to wait for; signed in, only this user's answer counts.\n const settled = user ? settledFor === user.id : !authLoading\n const orgsKnown = !!user && orgsReadFor === user.id\n\n // Paint the last known org before the first child renders. The stores are\n // filled in a layout effect, so nothing gets a frame with `org === null`.\n useIsomorphicLayoutEffect(() => {\n if (!snapshot) return\n setUserOrgs(snapshot.userOrgs)\n setCurrentOrg(snapshot.org)\n setMembers(snapshot.members)\n setProfiles(snapshot.profiles)\n setCurrentProfile(snapshot.profiles.find((p) => p.id === snapshot.currentProfileId) ?? null)\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [])\n\n // Keep the snapshot in step with store writes made outside this provider —\n // renaming the org in Settings, editing a role — so the cache never serves a\n // value the user already changed on screen. Patching leaves `savedAt` alone:\n // a UI-only edit must not push the next revalidation further out.\n React.useEffect(() => {\n if (cacheTtlMs <= 0 || !user) return\n const uid = user.id\n const sync = () => {\n const org = useOrganizationStore.getState()\n if (!org.currentOrg) return\n const perms = usePermissionsStore.getState()\n patchOrgSnapshot({\n userId: uid,\n org: org.currentOrg,\n userOrgs: org.userOrgs,\n members: org.members,\n profiles: perms.profiles,\n // realProfile, not currentProfile: an impersonation preview is not\n // something to cache back as the user's own role.\n currentProfileId: perms.realProfile?.id ?? null,\n })\n }\n const unsubscribeOrg = useOrganizationStore.subscribe(sync)\n const unsubscribePerms = usePermissionsStore.subscribe(sync)\n return () => {\n unsubscribeOrg()\n unsubscribePerms()\n }\n }, [user?.id, cacheTtlMs])\n\n // Load the org's members + permission profiles into the shared stores whenever\n // an org resolves. This is what makes the role list, the invite dialog's role\n // dropdown, and permission checks work — previously nothing loaded them here,\n // so those surfaces came up empty. Also resolves the signed-in user's own role.\n const hydrateOrg = React.useCallback(\n async (org: Organization, memberships: OrgMembership[], uid: string | undefined) => {\n try {\n const [members, profiles] = await Promise.all([\n adapterRef.current.listMembers(org.id),\n adapterRef.current.listProfiles(org.id),\n ])\n setMembers(members)\n setProfiles(profiles)\n const myMembership = uid ? members.find((m) => m.userId === uid) : undefined\n const profile = profiles.find((p) => p.id === myMembership?.profileId) ?? null\n setCurrentProfile(profile)\n // Everything the next reload needs, stamped now — this is the only\n // place the TTL window restarts.\n if (cacheTtlMs > 0 && uid) {\n saveOrgSnapshot({\n userId: uid,\n org,\n userOrgs: memberships,\n members,\n profiles,\n currentProfileId: profile?.id ?? null,\n })\n }\n } catch {\n // Non-fatal — leave stores as-is if the load fails.\n }\n },\n [setMembers, setProfiles, setCurrentProfile, cacheTtlMs],\n )\n\n // Load orgs when user is authenticated\n React.useEffect(() => {\n if (!user) {\n // A cold load reports `user === null` while the session hydrates — that's\n // not a sign-out, so the painted snapshot stands until auth settles.\n if (authLoading) return\n loadedForRef.current = null\n reset()\n clearOrgSnapshot()\n setSettledFor(null)\n setOrgsReadFor(null)\n return\n }\n\n const cached = snapshot?.userId === user.id ? snapshot : null\n\n // Already painted and still inside its window: skip the whole bootstrap.\n if (cached && isSnapshotFresh(cached, cacheTtlMs)) {\n setSettledFor(user.id)\n setOrgsReadFor(user.id)\n return\n }\n\n // A snapshot from another account was painted — drop it before loading.\n if (snapshot && snapshot.userId !== user.id && loadedForRef.current === null) {\n reset()\n clearOrgSnapshot()\n }\n\n if (loadedForRef.current === user.id) return\n loadedForRef.current = user.id\n\n // Stale but painted: revalidate quietly, so screens keep the data they're\n // showing instead of flipping back to a skeleton.\n const silent = !!cached\n\n let cancelled = false\n\n async function loadOrgs() {\n if (!silent) setLoading(true)\n try {\n // Redeem invites first — an invitee has no membership until this runs,\n // and without it autoCreate below would hand them a personal org\n // instead of the one that invited them.\n let accepted = 0\n try {\n accepted = (await adapterRef.current.acceptPendingInvites?.()) ?? 0\n } catch {\n // Non-fatal: fall through and load whatever memberships exist.\n }\n\n if (cancelled) return\n\n let memberships = await adapterRef.current.listUserOrgs(user!.id)\n\n // An empty list right after accepting means the read was deduped onto a\n // request that started before the invite landed. Ask once more.\n if (memberships.length === 0 && accepted > 0) {\n memberships = await adapterRef.current.listUserOrgs(user!.id)\n }\n\n if (cancelled) return\n\n setUserOrgs(memberships)\n // The read answered. Everything after this can still fail without\n // turning \"this account has no workspace\" into a guess.\n setOrgsReadFor(user!.id)\n\n // Auto-create org if user has none\n if (memberships.length === 0 && accepted === 0 && autoCreate) {\n const newOrg = await adapterRef.current.createOrg(\n user!.fullName ? `${user!.fullName}'s Organization` : 'My Organization',\n user!.id,\n )\n if (!cancelled) {\n const ownMembership: OrgMembership[] = [\n {\n orgId: newOrg.id,\n orgName: newOrg.name,\n orgSlug: newOrg.slug,\n profileId: 'owner',\n profileName: 'Owner',\n },\n ]\n setUserOrgs(ownMembership)\n setCurrentOrg(newOrg)\n await hydrateOrg(newOrg, ownMembership, user!.id)\n }\n return\n }\n\n // Which workspace to open — or whether to open one at all. The ladder\n // lives in `decideWorkspaceChoice`; the only branch that changed shape\n // here is the last one. It used to be `?? memberships[0]`, which turned\n // \"we do not know which of your seven accounts you meant\" into a silent\n // guess the user could not see, let alone correct.\n const choice = decideWorkspaceChoice({\n memberships,\n // Consumed here, and only here: this is the one boot the marker was\n // written for, and leaving it behind would pin the next one too.\n switchingToId: takeWorkspaceSwitch(),\n handoffId: readAppHandoff()?.tenantId ?? null,\n pinnedId: getPersistedOrgId(),\n autoPick: getAutoPick(),\n })\n\n // Leaving `currentOrg` null IS the ask: the chooser gate reads exactly\n // this state — a membership list we have read, more than one row in it,\n // and no active workspace — so no extra flag has to be kept in sync.\n if (choice.kind !== 'resolved') return\n\n if (!cancelled) {\n const org = await adapterRef.current.getOrg(choice.orgId)\n if (!cancelled) {\n setCurrentOrg(org)\n await hydrateOrg(org, memberships, user!.id)\n }\n }\n } finally {\n if (!cancelled && !silent) setLoading(false)\n // Settled even when the load threw: the answer is then \"we asked and\n // could not tell\", and holding the screen forever is worse than showing\n // the app. It is about the wait being over, not about success.\n if (!cancelled) setSettledFor(user!.id)\n }\n }\n\n void loadOrgs()\n\n return () => {\n cancelled = true\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [user?.id, authLoading])\n\n async function switchOrg(orgId: string): Promise<void> {\n setLoading(true)\n try {\n const org = await adapterRef.current.getOrg(orgId)\n setCurrentOrg(org)\n await hydrateOrg(org, useOrganizationStore.getState().userOrgs, user?.id)\n } finally {\n setLoading(false)\n }\n }\n\n // Explicit refresh always goes to the network and restarts the cache window.\n async function refreshOrg(): Promise<void> {\n if (!currentOrg) return\n setLoading(true)\n try {\n const org = await adapterRef.current.getOrg(currentOrg.id)\n setCurrentOrg(org)\n await hydrateOrg(org, useOrganizationStore.getState().userOrgs, user?.id)\n } finally {\n setLoading(false)\n }\n }\n\n const tenantValue: TenantContext = {\n org: currentOrg,\n userOrgs,\n loading,\n settled,\n orgsKnown,\n switchOrg,\n refreshOrg,\n }\n\n return (\n <OrgAdapterContext.Provider value={adapter}>\n <TenantContext.Provider value={tenantValue}>\n {children}\n </TenantContext.Provider>\n </OrgAdapterContext.Provider>\n )\n}\n"]}
|