@fayz-ai/saas 0.1.6
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/app/AdminShell.d.ts +31 -0
- package/dist/app/AdminShell.d.ts.map +1 -0
- package/dist/app/LoginPage.d.ts +17 -0
- package/dist/app/LoginPage.d.ts.map +1 -0
- package/dist/app/config.d.ts +83 -0
- package/dist/app/config.d.ts.map +1 -0
- package/dist/app/createFayzApp.d.ts +11 -0
- package/dist/app/createFayzApp.d.ts.map +1 -0
- package/dist/app/routing.d.ts +10 -0
- package/dist/app/routing.d.ts.map +1 -0
- package/dist/app/scaffold.d.ts +17 -0
- package/dist/app/scaffold.d.ts.map +1 -0
- package/dist/archetype-lookup.d.ts +30 -0
- package/dist/archetype-lookup.d.ts.map +1 -0
- package/dist/billing/index.cjs +12 -0
- package/dist/billing/index.cjs.map +1 -0
- package/dist/billing/index.d.ts +3 -0
- package/dist/billing/index.d.ts.map +1 -0
- package/dist/billing/index.js +3 -0
- package/dist/billing/index.js.map +1 -0
- package/dist/billing/store.d.ts +35 -0
- package/dist/billing/store.d.ts.map +1 -0
- package/dist/chunk-4XTQ4V5L.cjs +124 -0
- package/dist/chunk-4XTQ4V5L.cjs.map +1 -0
- package/dist/chunk-5GYQK5IW.js +21 -0
- package/dist/chunk-5GYQK5IW.js.map +1 -0
- package/dist/chunk-DJW67TCY.js +781 -0
- package/dist/chunk-DJW67TCY.js.map +1 -0
- package/dist/chunk-KYID2MR6.cjs +23 -0
- package/dist/chunk-KYID2MR6.cjs.map +1 -0
- package/dist/chunk-QGMDVLLW.cjs +816 -0
- package/dist/chunk-QGMDVLLW.cjs.map +1 -0
- package/dist/chunk-XNWCGJYH.js +97 -0
- package/dist/chunk-XNWCGJYH.js.map +1 -0
- package/dist/components/shared/PersonLink.d.ts +18 -0
- package/dist/components/shared/PersonLink.d.ts.map +1 -0
- package/dist/crud/CrudCardGrid.d.ts +13 -0
- package/dist/crud/CrudCardGrid.d.ts.map +1 -0
- package/dist/crud/CrudDetailPage.d.ts +18 -0
- package/dist/crud/CrudDetailPage.d.ts.map +1 -0
- package/dist/crud/CrudFormPage.d.ts +18 -0
- package/dist/crud/CrudFormPage.d.ts.map +1 -0
- package/dist/crud/CrudListView.d.ts +51 -0
- package/dist/crud/CrudListView.d.ts.map +1 -0
- package/dist/crud/CrudPage.d.ts +20 -0
- package/dist/crud/CrudPage.d.ts.map +1 -0
- package/dist/crud/DeleteConfirmDialog.d.ts +11 -0
- package/dist/crud/DeleteConfirmDialog.d.ts.map +1 -0
- package/dist/crud/EntityOverview.d.ts +14 -0
- package/dist/crud/EntityOverview.d.ts.map +1 -0
- package/dist/crud/ImportWizard.d.ts +18 -0
- package/dist/crud/ImportWizard.d.ts.map +1 -0
- package/dist/crud/archetypes/ActiveToggle.d.ts +8 -0
- package/dist/crud/archetypes/ActiveToggle.d.ts.map +1 -0
- package/dist/crud/archetypes/ArchetypeStatusBar.d.ts +11 -0
- package/dist/crud/archetypes/ArchetypeStatusBar.d.ts.map +1 -0
- package/dist/crud/archetypes/BlockSettingsPopover.d.ts +15 -0
- package/dist/crud/archetypes/BlockSettingsPopover.d.ts.map +1 -0
- package/dist/crud/archetypes/EntityLink.d.ts +32 -0
- package/dist/crud/archetypes/EntityLink.d.ts.map +1 -0
- package/dist/crud/archetypes/LocationDetailTabs.d.ts +11 -0
- package/dist/crud/archetypes/LocationDetailTabs.d.ts.map +1 -0
- package/dist/crud/archetypes/LocationFormLayout.d.ts +12 -0
- package/dist/crud/archetypes/LocationFormLayout.d.ts.map +1 -0
- package/dist/crud/archetypes/PersonDetailTabs.d.ts +22 -0
- package/dist/crud/archetypes/PersonDetailTabs.d.ts.map +1 -0
- package/dist/crud/archetypes/PersonFormLayout.d.ts +13 -0
- package/dist/crud/archetypes/PersonFormLayout.d.ts.map +1 -0
- package/dist/crud/archetypes/PersonPicker.d.ts +23 -0
- package/dist/crud/archetypes/PersonPicker.d.ts.map +1 -0
- package/dist/crud/archetypes/ProductDetailTabs.d.ts +11 -0
- package/dist/crud/archetypes/ProductDetailTabs.d.ts.map +1 -0
- package/dist/crud/archetypes/ProductFormLayout.d.ts +12 -0
- package/dist/crud/archetypes/ProductFormLayout.d.ts.map +1 -0
- package/dist/crud/archetypes/ScheduleEditor.d.ts +7 -0
- package/dist/crud/archetypes/ScheduleEditor.d.ts.map +1 -0
- package/dist/crud/archetypes/ServiceDetailTabs.d.ts +10 -0
- package/dist/crud/archetypes/ServiceDetailTabs.d.ts.map +1 -0
- package/dist/crud/archetypes/ServiceFormLayout.d.ts +12 -0
- package/dist/crud/archetypes/ServiceFormLayout.d.ts.map +1 -0
- package/dist/crud/archetypes/SubjectDetailTabs.d.ts +12 -0
- package/dist/crud/archetypes/SubjectDetailTabs.d.ts.map +1 -0
- package/dist/crud/archetypes/SubjectFormLayout.d.ts +14 -0
- package/dist/crud/archetypes/SubjectFormLayout.d.ts.map +1 -0
- package/dist/crud/createCrudPage.d.ts +20 -0
- package/dist/crud/createCrudPage.d.ts.map +1 -0
- package/dist/crud/csv-export.d.ts +3 -0
- package/dist/crud/csv-export.d.ts.map +1 -0
- package/dist/crud/fieldToColumn.d.ts +14 -0
- package/dist/crud/fieldToColumn.d.ts.map +1 -0
- package/dist/crud/index.d.ts +12 -0
- package/dist/crud/index.d.ts.map +1 -0
- package/dist/hooks/useFieldRules.d.ts +5 -0
- package/dist/hooks/useFieldRules.d.ts.map +1 -0
- package/dist/hooks/useModuleNavigation.d.ts +25 -0
- package/dist/hooks/useModuleNavigation.d.ts.map +1 -0
- package/dist/hooks/usePluginPrefs.d.ts +23 -0
- package/dist/hooks/usePluginPrefs.d.ts.map +1 -0
- package/dist/index.cjs +13880 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +46 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +13730 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/currency.d.ts +13 -0
- package/dist/lib/currency.d.ts.map +1 -0
- package/dist/lib/entity-routes.d.ts +24 -0
- package/dist/lib/entity-routes.d.ts.map +1 -0
- package/dist/lib/schedule-config.d.ts +32 -0
- package/dist/lib/schedule-config.d.ts.map +1 -0
- package/dist/lib/schedule-service.d.ts +76 -0
- package/dist/lib/schedule-service.d.ts.map +1 -0
- package/dist/org/adapters/mock.d.ts +4 -0
- package/dist/org/adapters/mock.d.ts.map +1 -0
- package/dist/org/adapters/supabase.d.ts +7 -0
- package/dist/org/adapters/supabase.d.ts.map +1 -0
- package/dist/org/context.d.ts +29 -0
- package/dist/org/context.d.ts.map +1 -0
- package/dist/org/index.cjs +44 -0
- package/dist/org/index.cjs.map +1 -0
- package/dist/org/index.d.ts +8 -0
- package/dist/org/index.d.ts.map +1 -0
- package/dist/org/index.js +3 -0
- package/dist/org/index.js.map +1 -0
- package/dist/org/store.d.ts +16 -0
- package/dist/org/store.d.ts.map +1 -0
- package/dist/permissions/PermissionGate.d.ts +12 -0
- package/dist/permissions/PermissionGate.d.ts.map +1 -0
- package/dist/permissions/context.d.ts +37 -0
- package/dist/permissions/context.d.ts.map +1 -0
- package/dist/permissions/index.cjs +32 -0
- package/dist/permissions/index.cjs.map +1 -0
- package/dist/permissions/index.d.ts +5 -0
- package/dist/permissions/index.d.ts.map +1 -0
- package/dist/permissions/index.js +3 -0
- package/dist/permissions/index.js.map +1 -0
- package/dist/permissions/store.d.ts +21 -0
- package/dist/permissions/store.d.ts.map +1 -0
- package/dist/placeholder.d.ts +7 -0
- package/dist/placeholder.d.ts.map +1 -0
- package/dist/plugins/ModuleActionBar.d.ts +27 -0
- package/dist/plugins/ModuleActionBar.d.ts.map +1 -0
- package/dist/plugins/PluginRegistryManager.d.ts +13 -0
- package/dist/plugins/PluginRegistryManager.d.ts.map +1 -0
- package/dist/plugins/QuickActionsButton.d.ts +12 -0
- package/dist/plugins/QuickActionsButton.d.ts.map +1 -0
- package/dist/plugins/SettingsGroup.d.ts +25 -0
- package/dist/plugins/SettingsGroup.d.ts.map +1 -0
- package/dist/plugins/WidgetSlot.d.ts +8 -0
- package/dist/plugins/WidgetSlot.d.ts.map +1 -0
- package/dist/plugins/createPluginContext.d.ts +28 -0
- package/dist/plugins/createPluginContext.d.ts.map +1 -0
- package/dist/plugins/createViewRouter.d.ts +38 -0
- package/dist/plugins/createViewRouter.d.ts.map +1 -0
- package/dist/shell/components/auth/CenteredLogin.d.ts +13 -0
- package/dist/shell/components/auth/CenteredLogin.d.ts.map +1 -0
- package/dist/shell/components/auth/LoginForm.d.ts +12 -0
- package/dist/shell/components/auth/LoginForm.d.ts.map +1 -0
- package/dist/shell/components/auth/LoginPage.d.ts +15 -0
- package/dist/shell/components/auth/LoginPage.d.ts.map +1 -0
- package/dist/shell/components/auth/OAuthButtons.d.ts +10 -0
- package/dist/shell/components/auth/OAuthButtons.d.ts.map +1 -0
- package/dist/shell/components/auth/ProtectedRoute.d.ts +11 -0
- package/dist/shell/components/auth/ProtectedRoute.d.ts.map +1 -0
- package/dist/shell/components/auth/RecoveryForm.d.ts +9 -0
- package/dist/shell/components/auth/RecoveryForm.d.ts.map +1 -0
- package/dist/shell/components/auth/SignupForm.d.ts +10 -0
- package/dist/shell/components/auth/SignupForm.d.ts.map +1 -0
- package/dist/shell/components/auth/SplitLogin.d.ts +14 -0
- package/dist/shell/components/auth/SplitLogin.d.ts.map +1 -0
- package/dist/shell/components/auth/index.d.ts +7 -0
- package/dist/shell/components/auth/index.d.ts.map +1 -0
- package/dist/shell/components/billing/BillingPage.d.ts +7 -0
- package/dist/shell/components/billing/BillingPage.d.ts.map +1 -0
- package/dist/shell/components/billing/InvoiceList.d.ts +9 -0
- package/dist/shell/components/billing/InvoiceList.d.ts.map +1 -0
- package/dist/shell/components/billing/PaywallGate.d.ts +11 -0
- package/dist/shell/components/billing/PaywallGate.d.ts.map +1 -0
- package/dist/shell/components/billing/PlanSelector.d.ts +13 -0
- package/dist/shell/components/billing/PlanSelector.d.ts.map +1 -0
- package/dist/shell/components/billing/SubscriptionCard.d.ts +12 -0
- package/dist/shell/components/billing/SubscriptionCard.d.ts.map +1 -0
- package/dist/shell/components/billing/index.d.ts +6 -0
- package/dist/shell/components/billing/index.d.ts.map +1 -0
- package/dist/shell/components/chat/ChatFab.d.ts +9 -0
- package/dist/shell/components/chat/ChatFab.d.ts.map +1 -0
- package/dist/shell/components/chat/ChatPanel.d.ts +10 -0
- package/dist/shell/components/chat/ChatPanel.d.ts.map +1 -0
- package/dist/shell/components/chat/ChatSuggestions.d.ts +14 -0
- package/dist/shell/components/chat/ChatSuggestions.d.ts.map +1 -0
- package/dist/shell/components/chat/index.d.ts +3 -0
- package/dist/shell/components/chat/index.d.ts.map +1 -0
- package/dist/shell/components/crud/archetypes/ClientOrdersTab.d.ts +16 -0
- package/dist/shell/components/crud/archetypes/ClientOrdersTab.d.ts.map +1 -0
- package/dist/shell/components/layout/BottomNav.d.ts +24 -0
- package/dist/shell/components/layout/BottomNav.d.ts.map +1 -0
- package/dist/shell/components/layout/CommandPalette.d.ts +29 -0
- package/dist/shell/components/layout/CommandPalette.d.ts.map +1 -0
- package/dist/shell/components/layout/LocationSwitchOverlay.d.ts +3 -0
- package/dist/shell/components/layout/LocationSwitchOverlay.d.ts.map +1 -0
- package/dist/shell/components/layout/MockModeBanner.d.ts +3 -0
- package/dist/shell/components/layout/MockModeBanner.d.ts.map +1 -0
- package/dist/shell/components/layout/PrintChrome.d.ts +10 -0
- package/dist/shell/components/layout/PrintChrome.d.ts.map +1 -0
- package/dist/shell/components/layout/index.d.ts +6 -0
- package/dist/shell/components/layout/index.d.ts.map +1 -0
- package/dist/shell/components/notifications/ChangelogFeed.d.ts +8 -0
- package/dist/shell/components/notifications/ChangelogFeed.d.ts.map +1 -0
- package/dist/shell/components/notifications/NotificationBell.d.ts +9 -0
- package/dist/shell/components/notifications/NotificationBell.d.ts.map +1 -0
- package/dist/shell/components/notifications/NotificationInbox.d.ts +13 -0
- package/dist/shell/components/notifications/NotificationInbox.d.ts.map +1 -0
- package/dist/shell/components/notifications/ToastProvider.d.ts +2 -0
- package/dist/shell/components/notifications/ToastProvider.d.ts.map +1 -0
- package/dist/shell/components/notifications/index.d.ts +5 -0
- package/dist/shell/components/notifications/index.d.ts.map +1 -0
- package/dist/shell/components/organization/ImpersonationBanner.d.ts +3 -0
- package/dist/shell/components/organization/ImpersonationBanner.d.ts.map +1 -0
- package/dist/shell/components/organization/InviteMemberDialog.d.ts +10 -0
- package/dist/shell/components/organization/InviteMemberDialog.d.ts.map +1 -0
- package/dist/shell/components/organization/OrgInitializer.d.ts +7 -0
- package/dist/shell/components/organization/OrgInitializer.d.ts.map +1 -0
- package/dist/shell/components/organization/OrgSwitcher.d.ts +7 -0
- package/dist/shell/components/organization/OrgSwitcher.d.ts.map +1 -0
- package/dist/shell/components/organization/PermissionGate.d.ts +11 -0
- package/dist/shell/components/organization/PermissionGate.d.ts.map +1 -0
- package/dist/shell/components/organization/PermissionMatrixEditor.d.ts +17 -0
- package/dist/shell/components/organization/PermissionMatrixEditor.d.ts.map +1 -0
- package/dist/shell/components/organization/PermissionProfilesTab.d.ts +3 -0
- package/dist/shell/components/organization/PermissionProfilesTab.d.ts.map +1 -0
- package/dist/shell/components/organization/TeamTab.d.ts +3 -0
- package/dist/shell/components/organization/TeamTab.d.ts.map +1 -0
- package/dist/shell/components/organization/TenantOnboarding.d.ts +9 -0
- package/dist/shell/components/organization/TenantOnboarding.d.ts.map +1 -0
- package/dist/shell/components/organization/index.d.ts +8 -0
- package/dist/shell/components/organization/index.d.ts.map +1 -0
- package/dist/shell/components/plugins/PluginSettingsPanel.d.ts +23 -0
- package/dist/shell/components/plugins/PluginSettingsPanel.d.ts.map +1 -0
- package/dist/shell/components/settings/BrandingSettings.d.ts +12 -0
- package/dist/shell/components/settings/BrandingSettings.d.ts.map +1 -0
- package/dist/shell/components/settings/CompanySettings.d.ts +13 -0
- package/dist/shell/components/settings/CompanySettings.d.ts.map +1 -0
- package/dist/shell/components/settings/ConnectedBrandingSettings.d.ts +2 -0
- package/dist/shell/components/settings/ConnectedBrandingSettings.d.ts.map +1 -0
- package/dist/shell/components/settings/ConnectedCompanySettings.d.ts +2 -0
- package/dist/shell/components/settings/ConnectedCompanySettings.d.ts.map +1 -0
- package/dist/shell/components/settings/ConnectedFieldRulesSettings.d.ts +2 -0
- package/dist/shell/components/settings/ConnectedFieldRulesSettings.d.ts.map +1 -0
- package/dist/shell/components/settings/ConnectedHolidaysSettings.d.ts +3 -0
- package/dist/shell/components/settings/ConnectedHolidaysSettings.d.ts.map +1 -0
- package/dist/shell/components/settings/ConnectedLocationsOverview.d.ts +3 -0
- package/dist/shell/components/settings/ConnectedLocationsOverview.d.ts.map +1 -0
- package/dist/shell/components/settings/ConnectedSecuritySettings.d.ts +2 -0
- package/dist/shell/components/settings/ConnectedSecuritySettings.d.ts.map +1 -0
- package/dist/shell/components/settings/ConnectedUserProfile.d.ts +2 -0
- package/dist/shell/components/settings/ConnectedUserProfile.d.ts.map +1 -0
- package/dist/shell/components/settings/FieldRulesSettings.d.ts +11 -0
- package/dist/shell/components/settings/FieldRulesSettings.d.ts.map +1 -0
- package/dist/shell/components/settings/HolidaysSettings.d.ts +21 -0
- package/dist/shell/components/settings/HolidaysSettings.d.ts.map +1 -0
- package/dist/shell/components/settings/LocationsCrudPage.d.ts +6 -0
- package/dist/shell/components/settings/LocationsCrudPage.d.ts.map +1 -0
- package/dist/shell/components/settings/LocationsOverview.d.ts +19 -0
- package/dist/shell/components/settings/LocationsOverview.d.ts.map +1 -0
- package/dist/shell/components/settings/SecuritySettings.d.ts +17 -0
- package/dist/shell/components/settings/SecuritySettings.d.ts.map +1 -0
- package/dist/shell/components/settings/SettingsPage.d.ts +18 -0
- package/dist/shell/components/settings/SettingsPage.d.ts.map +1 -0
- package/dist/shell/components/settings/UserProfile.d.ts +12 -0
- package/dist/shell/components/settings/UserProfile.d.ts.map +1 -0
- package/dist/shell/components/settings/index.d.ts +11 -0
- package/dist/shell/components/settings/index.d.ts.map +1 -0
- package/dist/shell/config/index.d.ts +7 -0
- package/dist/shell/config/index.d.ts.map +1 -0
- package/dist/shell/config/permissions.d.ts +7 -0
- package/dist/shell/config/permissions.d.ts.map +1 -0
- package/dist/shell/config/tailwind-preset.d.ts +4 -0
- package/dist/shell/config/tailwind-preset.d.ts.map +1 -0
- package/dist/shell/config/theme/dark.d.ts +3 -0
- package/dist/shell/config/theme/dark.d.ts.map +1 -0
- package/dist/shell/config/theme/index.d.ts +6 -0
- package/dist/shell/config/theme/index.d.ts.map +1 -0
- package/dist/shell/config/theme/light.d.ts +3 -0
- package/dist/shell/config/theme/light.d.ts.map +1 -0
- package/dist/shell/config/theme/tokens.d.ts +97 -0
- package/dist/shell/config/theme/tokens.d.ts.map +1 -0
- package/dist/shell/config/theme/utils.d.ts +23 -0
- package/dist/shell/config/theme/utils.d.ts.map +1 -0
- package/dist/shell/createSaasApp.d.ts +145 -0
- package/dist/shell/createSaasApp.d.ts.map +1 -0
- package/dist/shell/hooks/index.d.ts +19 -0
- package/dist/shell/hooks/index.d.ts.map +1 -0
- package/dist/shell/hooks/useAITools.d.ts +21 -0
- package/dist/shell/hooks/useAITools.d.ts.map +1 -0
- package/dist/shell/hooks/useAuth.d.ts +16 -0
- package/dist/shell/hooks/useAuth.d.ts.map +1 -0
- package/dist/shell/hooks/useBilling.d.ts +14 -0
- package/dist/shell/hooks/useBilling.d.ts.map +1 -0
- package/dist/shell/hooks/useChat.d.ts +16 -0
- package/dist/shell/hooks/useChat.d.ts.map +1 -0
- package/dist/shell/hooks/useFieldRules.d.ts +6 -0
- package/dist/shell/hooks/useFieldRules.d.ts.map +1 -0
- package/dist/shell/hooks/useFormatters.d.ts +7 -0
- package/dist/shell/hooks/useFormatters.d.ts.map +1 -0
- package/dist/shell/hooks/useLayout.d.ts +16 -0
- package/dist/shell/hooks/useLayout.d.ts.map +1 -0
- package/dist/shell/hooks/useModuleNavigation.d.ts +25 -0
- package/dist/shell/hooks/useModuleNavigation.d.ts.map +1 -0
- package/dist/shell/hooks/useNotifications.d.ts +11 -0
- package/dist/shell/hooks/useNotifications.d.ts.map +1 -0
- package/dist/shell/hooks/usePermission.d.ts +16 -0
- package/dist/shell/hooks/usePermission.d.ts.map +1 -0
- package/dist/shell/hooks/usePluginPrefs.d.ts +23 -0
- package/dist/shell/hooks/usePluginPrefs.d.ts.map +1 -0
- package/dist/shell/hooks/usePlugins.d.ts +5 -0
- package/dist/shell/hooks/usePlugins.d.ts.map +1 -0
- package/dist/shell/hooks/useTenant.d.ts +23 -0
- package/dist/shell/hooks/useTenant.d.ts.map +1 -0
- package/dist/shell/hooks/useTenantPlugins.d.ts +10 -0
- package/dist/shell/hooks/useTenantPlugins.d.ts.map +1 -0
- package/dist/shell/hooks/useTranslation.d.ts +5 -0
- package/dist/shell/hooks/useTranslation.d.ts.map +1 -0
- package/dist/shell/lib/api.d.ts +28 -0
- package/dist/shell/lib/api.d.ts.map +1 -0
- package/dist/shell/lib/apply-field-rules.d.ts +4 -0
- package/dist/shell/lib/apply-field-rules.d.ts.map +1 -0
- package/dist/shell/lib/auth-adapters/mock.d.ts +3 -0
- package/dist/shell/lib/auth-adapters/mock.d.ts.map +1 -0
- package/dist/shell/lib/auth-adapters/supabase.d.ts +3 -0
- package/dist/shell/lib/auth-adapters/supabase.d.ts.map +1 -0
- package/dist/shell/lib/auth-context.d.ts +5 -0
- package/dist/shell/lib/auth-context.d.ts.map +1 -0
- package/dist/shell/lib/cache.d.ts +18 -0
- package/dist/shell/lib/cache.d.ts.map +1 -0
- package/dist/shell/lib/cn.d.ts +2 -0
- package/dist/shell/lib/cn.d.ts.map +1 -0
- package/dist/shell/lib/core-ai-tools.d.ts +15 -0
- package/dist/shell/lib/core-ai-tools.d.ts.map +1 -0
- package/dist/shell/lib/create-client-orders-provider.d.ts +3 -0
- package/dist/shell/lib/create-client-orders-provider.d.ts.map +1 -0
- package/dist/shell/lib/csv.d.ts +14 -0
- package/dist/shell/lib/csv.d.ts.map +1 -0
- package/dist/shell/lib/dedup.d.ts +2 -0
- package/dist/shell/lib/dedup.d.ts.map +1 -0
- package/dist/shell/lib/entity-registry.d.ts +19 -0
- package/dist/shell/lib/entity-registry.d.ts.map +1 -0
- package/dist/shell/lib/entity-routes.d.ts +24 -0
- package/dist/shell/lib/entity-routes.d.ts.map +1 -0
- package/dist/shell/lib/format.d.ts +7 -0
- package/dist/shell/lib/format.d.ts.map +1 -0
- package/dist/shell/lib/i18n.d.ts +4 -0
- package/dist/shell/lib/i18n.d.ts.map +1 -0
- package/dist/shell/lib/locale-config.d.ts +8 -0
- package/dist/shell/lib/locale-config.d.ts.map +1 -0
- package/dist/shell/lib/org-adapters/mock.d.ts +4 -0
- package/dist/shell/lib/org-adapters/mock.d.ts.map +1 -0
- package/dist/shell/lib/org-adapters/supabase.d.ts +3 -0
- package/dist/shell/lib/org-adapters/supabase.d.ts.map +1 -0
- package/dist/shell/lib/org-context.d.ts +5 -0
- package/dist/shell/lib/org-context.d.ts.map +1 -0
- package/dist/shell/lib/plugins.d.ts +2 -0
- package/dist/shell/lib/plugins.d.ts.map +1 -0
- package/dist/shell/lib/router.d.ts +15 -0
- package/dist/shell/lib/router.d.ts.map +1 -0
- package/dist/shell/lib/schedule-config.d.ts +32 -0
- package/dist/shell/lib/schedule-config.d.ts.map +1 -0
- package/dist/shell/lib/schedule-service.d.ts +76 -0
- package/dist/shell/lib/schedule-service.d.ts.map +1 -0
- package/dist/shell/lib/supabase.d.ts +9 -0
- package/dist/shell/lib/supabase.d.ts.map +1 -0
- package/dist/shell/stores/auth.store.d.ts +12 -0
- package/dist/shell/stores/auth.store.d.ts.map +1 -0
- package/dist/shell/stores/billing.store.d.ts +2 -0
- package/dist/shell/stores/billing.store.d.ts.map +1 -0
- package/dist/shell/stores/chat.store.d.ts +20 -0
- package/dist/shell/stores/chat.store.d.ts.map +1 -0
- package/dist/shell/stores/index.d.ts +13 -0
- package/dist/shell/stores/index.d.ts.map +1 -0
- package/dist/shell/stores/invite.store.d.ts +11 -0
- package/dist/shell/stores/invite.store.d.ts.map +1 -0
- package/dist/shell/stores/layout.store.d.ts +2 -0
- package/dist/shell/stores/layout.store.d.ts.map +1 -0
- package/dist/shell/stores/locale.store.d.ts +7 -0
- package/dist/shell/stores/locale.store.d.ts.map +1 -0
- package/dist/shell/stores/location.store.d.ts +20 -0
- package/dist/shell/stores/location.store.d.ts.map +1 -0
- package/dist/shell/stores/notifications.store.d.ts +12 -0
- package/dist/shell/stores/notifications.store.d.ts.map +1 -0
- package/dist/shell/stores/organization.store.d.ts +2 -0
- package/dist/shell/stores/organization.store.d.ts.map +1 -0
- package/dist/shell/stores/permissions.store.d.ts +2 -0
- package/dist/shell/stores/permissions.store.d.ts.map +1 -0
- package/dist/shell/stores/plugin.store.d.ts +24 -0
- package/dist/shell/stores/plugin.store.d.ts.map +1 -0
- package/dist/shell/stores/tenant.store.d.ts +11 -0
- package/dist/shell/stores/tenant.store.d.ts.map +1 -0
- package/dist/shell/stores/theme.store.d.ts +17 -0
- package/dist/shell/stores/theme.store.d.ts.map +1 -0
- package/dist/shell/test-setup.d.ts +2 -0
- package/dist/shell/test-setup.d.ts.map +1 -0
- package/dist/shell/types/auth-adapter.d.ts +14 -0
- package/dist/shell/types/auth-adapter.d.ts.map +1 -0
- package/dist/shell/types/auth.d.ts +34 -0
- package/dist/shell/types/auth.d.ts.map +1 -0
- package/dist/shell/types/billing.d.ts +74 -0
- package/dist/shell/types/billing.d.ts.map +1 -0
- package/dist/shell/types/client-orders.d.ts +43 -0
- package/dist/shell/types/client-orders.d.ts.map +1 -0
- package/dist/shell/types/crud.d.ts +122 -0
- package/dist/shell/types/crud.d.ts.map +1 -0
- package/dist/shell/types/entities.d.ts +150 -0
- package/dist/shell/types/entities.d.ts.map +1 -0
- package/dist/shell/types/entity-lookup.d.ts +18 -0
- package/dist/shell/types/entity-lookup.d.ts.map +1 -0
- package/dist/shell/types/index.d.ts +17 -0
- package/dist/shell/types/index.d.ts.map +1 -0
- package/dist/shell/types/integrations.d.ts +48 -0
- package/dist/shell/types/integrations.d.ts.map +1 -0
- package/dist/shell/types/invite.d.ts +23 -0
- package/dist/shell/types/invite.d.ts.map +1 -0
- package/dist/shell/types/layout.d.ts +26 -0
- package/dist/shell/types/layout.d.ts.map +1 -0
- package/dist/shell/types/notifications.d.ts +31 -0
- package/dist/shell/types/notifications.d.ts.map +1 -0
- package/dist/shell/types/org-adapter.d.ts +72 -0
- package/dist/shell/types/org-adapter.d.ts.map +1 -0
- package/dist/shell/types/permissions.d.ts +39 -0
- package/dist/shell/types/permissions.d.ts.map +1 -0
- package/dist/shell/types/platform.d.ts +25 -0
- package/dist/shell/types/platform.d.ts.map +1 -0
- package/dist/shell/types/plugins.d.ts +308 -0
- package/dist/shell/types/plugins.d.ts.map +1 -0
- package/dist/shell/types/tenant.d.ts +74 -0
- package/dist/shell/types/tenant.d.ts.map +1 -0
- package/dist/stores/createCrudStore.d.ts +32 -0
- package/dist/stores/createCrudStore.d.ts.map +1 -0
- package/dist/supabase/client.d.ts +18 -0
- package/dist/supabase/client.d.ts.map +1 -0
- package/package.json +72 -0
- package/src/app/AdminShell.tsx +449 -0
- package/src/app/LoginPage.tsx +264 -0
- package/src/app/config.ts +146 -0
- package/src/app/createFayzApp.tsx +254 -0
- package/src/app/routing.tsx +65 -0
- package/src/app/scaffold.tsx +175 -0
- package/src/archetype-lookup.ts +160 -0
- package/src/billing/index.ts +7 -0
- package/src/billing/store.ts +62 -0
- package/src/components/shared/PersonLink.tsx +199 -0
- package/src/crud/CrudCardGrid.tsx +92 -0
- package/src/crud/CrudDetailPage.tsx +366 -0
- package/src/crud/CrudFormPage.tsx +467 -0
- package/src/crud/CrudListView.tsx +223 -0
- package/src/crud/CrudPage.tsx +491 -0
- package/src/crud/DeleteConfirmDialog.tsx +41 -0
- package/src/crud/EntityOverview.tsx +230 -0
- package/src/crud/ImportWizard.tsx +690 -0
- package/src/crud/archetypes/ActiveToggle.tsx +23 -0
- package/src/crud/archetypes/ArchetypeStatusBar.tsx +58 -0
- package/src/crud/archetypes/BlockSettingsPopover.tsx +322 -0
- package/src/crud/archetypes/EntityLink.tsx +74 -0
- package/src/crud/archetypes/LocationDetailTabs.tsx +72 -0
- package/src/crud/archetypes/LocationFormLayout.tsx +173 -0
- package/src/crud/archetypes/PersonDetailTabs.tsx +87 -0
- package/src/crud/archetypes/PersonFormLayout.tsx +184 -0
- package/src/crud/archetypes/PersonPicker.tsx +299 -0
- package/src/crud/archetypes/ProductDetailTabs.tsx +72 -0
- package/src/crud/archetypes/ProductFormLayout.tsx +197 -0
- package/src/crud/archetypes/ScheduleEditor.tsx +796 -0
- package/src/crud/archetypes/ServiceDetailTabs.tsx +60 -0
- package/src/crud/archetypes/ServiceFormLayout.tsx +155 -0
- package/src/crud/archetypes/SubjectDetailTabs.tsx +142 -0
- package/src/crud/archetypes/SubjectFormLayout.tsx +164 -0
- package/src/crud/createCrudPage.tsx +65 -0
- package/src/crud/csv-export.ts +17 -0
- package/src/crud/fieldToColumn.tsx +109 -0
- package/src/crud/index.ts +11 -0
- package/src/hooks/useFieldRules.ts +10 -0
- package/src/hooks/useModuleNavigation.ts +124 -0
- package/src/hooks/usePluginPrefs.ts +113 -0
- package/src/index.ts +168 -0
- package/src/lib/currency.ts +19 -0
- package/src/lib/entity-routes.ts +53 -0
- package/src/lib/schedule-config.ts +42 -0
- package/src/lib/schedule-service.ts +307 -0
- package/src/org/adapters/mock.ts +353 -0
- package/src/org/adapters/supabase.ts +509 -0
- package/src/org/context.tsx +174 -0
- package/src/org/index.ts +19 -0
- package/src/org/store.ts +70 -0
- package/src/permissions/PermissionGate.tsx +17 -0
- package/src/permissions/context.tsx +126 -0
- package/src/permissions/index.ts +13 -0
- package/src/permissions/store.ts +68 -0
- package/src/placeholder.ts +32 -0
- package/src/plugins/ModuleActionBar.tsx +57 -0
- package/src/plugins/PluginRegistryManager.tsx +110 -0
- package/src/plugins/QuickActionsButton.tsx +73 -0
- package/src/plugins/SettingsGroup.tsx +100 -0
- package/src/plugins/WidgetSlot.tsx +38 -0
- package/src/plugins/createPluginContext.tsx +68 -0
- package/src/plugins/createViewRouter.tsx +45 -0
- package/src/shell/components/auth/CenteredLogin.tsx +86 -0
- package/src/shell/components/auth/LoginForm.tsx +129 -0
- package/src/shell/components/auth/LoginPage.tsx +22 -0
- package/src/shell/components/auth/OAuthButtons.tsx +66 -0
- package/src/shell/components/auth/ProtectedRoute.tsx +52 -0
- package/src/shell/components/auth/RecoveryForm.tsx +110 -0
- package/src/shell/components/auth/SignupForm.tsx +170 -0
- package/src/shell/components/auth/SplitLogin.tsx +124 -0
- package/src/shell/components/auth/index.ts +6 -0
- package/src/shell/components/billing/BillingPage.tsx +56 -0
- package/src/shell/components/billing/InvoiceList.tsx +123 -0
- package/src/shell/components/billing/PaywallGate.tsx +80 -0
- package/src/shell/components/billing/PlanSelector.tsx +125 -0
- package/src/shell/components/billing/SubscriptionCard.tsx +108 -0
- package/src/shell/components/billing/index.ts +5 -0
- package/src/shell/components/chat/ChatFab.tsx +195 -0
- package/src/shell/components/chat/ChatPanel.tsx +170 -0
- package/src/shell/components/chat/ChatSuggestions.tsx +102 -0
- package/src/shell/components/chat/index.ts +2 -0
- package/src/shell/components/crud/archetypes/ClientOrdersTab.tsx +212 -0
- package/src/shell/components/layout/BottomNav.tsx +131 -0
- package/src/shell/components/layout/CommandPalette.tsx +257 -0
- package/src/shell/components/layout/LocationSwitchOverlay.tsx +64 -0
- package/src/shell/components/layout/MockModeBanner.tsx +17 -0
- package/src/shell/components/layout/PrintChrome.tsx +200 -0
- package/src/shell/components/layout/index.ts +5 -0
- package/src/shell/components/notifications/ChangelogFeed.tsx +85 -0
- package/src/shell/components/notifications/NotificationBell.tsx +44 -0
- package/src/shell/components/notifications/NotificationInbox.tsx +175 -0
- package/src/shell/components/notifications/ToastProvider.tsx +2 -0
- package/src/shell/components/notifications/index.ts +4 -0
- package/src/shell/components/organization/ImpersonationBanner.tsx +29 -0
- package/src/shell/components/organization/InviteMemberDialog.tsx +161 -0
- package/src/shell/components/organization/OrgInitializer.tsx +179 -0
- package/src/shell/components/organization/OrgSwitcher.tsx +177 -0
- package/src/shell/components/organization/PermissionGate.tsx +16 -0
- package/src/shell/components/organization/PermissionMatrixEditor.tsx +206 -0
- package/src/shell/components/organization/PermissionProfilesTab.tsx +148 -0
- package/src/shell/components/organization/TeamTab.tsx +243 -0
- package/src/shell/components/organization/TenantOnboarding.tsx +179 -0
- package/src/shell/components/organization/index.ts +7 -0
- package/src/shell/components/plugins/PluginSettingsPanel.tsx +189 -0
- package/src/shell/components/settings/BrandingSettings.tsx +255 -0
- package/src/shell/components/settings/CompanySettings.tsx +188 -0
- package/src/shell/components/settings/ConnectedBrandingSettings.tsx +82 -0
- package/src/shell/components/settings/ConnectedCompanySettings.tsx +63 -0
- package/src/shell/components/settings/ConnectedFieldRulesSettings.tsx +47 -0
- package/src/shell/components/settings/ConnectedHolidaysSettings.tsx +99 -0
- package/src/shell/components/settings/ConnectedLocationsOverview.tsx +57 -0
- package/src/shell/components/settings/ConnectedSecuritySettings.tsx +59 -0
- package/src/shell/components/settings/ConnectedUserProfile.tsx +59 -0
- package/src/shell/components/settings/FieldRulesSettings.tsx +511 -0
- package/src/shell/components/settings/HolidaysSettings.tsx +141 -0
- package/src/shell/components/settings/LocationsCrudPage.ts +36 -0
- package/src/shell/components/settings/LocationsOverview.tsx +171 -0
- package/src/shell/components/settings/SecuritySettings.tsx +208 -0
- package/src/shell/components/settings/SettingsPage.tsx +162 -0
- package/src/shell/components/settings/UserProfile.tsx +151 -0
- package/src/shell/components/settings/index.ts +10 -0
- package/src/shell/config/index.ts +6 -0
- package/src/shell/config/permissions.ts +31 -0
- package/src/shell/config/tailwind-preset.ts +164 -0
- package/src/shell/config/theme/dark.ts +63 -0
- package/src/shell/config/theme/index.ts +5 -0
- package/src/shell/config/theme/light.ts +63 -0
- package/src/shell/config/theme/tokens.ts +105 -0
- package/src/shell/config/theme/utils.ts +249 -0
- package/src/shell/createSaasApp.tsx +922 -0
- package/src/shell/hooks/index.ts +18 -0
- package/src/shell/hooks/useAITools.ts +139 -0
- package/src/shell/hooks/useAuth.ts +109 -0
- package/src/shell/hooks/useBilling.ts +151 -0
- package/src/shell/hooks/useChat.ts +84 -0
- package/src/shell/hooks/useFieldRules.ts +19 -0
- package/src/shell/hooks/useFormatters.ts +56 -0
- package/src/shell/hooks/useLayout.ts +76 -0
- package/src/shell/hooks/useModuleNavigation.ts +124 -0
- package/src/shell/hooks/useNotifications.ts +114 -0
- package/src/shell/hooks/usePermission.ts +101 -0
- package/src/shell/hooks/usePluginPrefs.ts +113 -0
- package/src/shell/hooks/usePlugins.ts +18 -0
- package/src/shell/hooks/useTenant.ts +106 -0
- package/src/shell/hooks/useTenantPlugins.ts +103 -0
- package/src/shell/hooks/useTranslation.ts +10 -0
- package/src/shell/lib/api.ts +67 -0
- package/src/shell/lib/apply-field-rules.ts +14 -0
- package/src/shell/lib/auth-adapters/mock.ts +97 -0
- package/src/shell/lib/auth-adapters/supabase.ts +96 -0
- package/src/shell/lib/auth-context.ts +18 -0
- package/src/shell/lib/cache.ts +124 -0
- package/src/shell/lib/cn.ts +1 -0
- package/src/shell/lib/core-ai-tools.ts +90 -0
- package/src/shell/lib/create-client-orders-provider.ts +105 -0
- package/src/shell/lib/csv.ts +54 -0
- package/src/shell/lib/dedup.ts +17 -0
- package/src/shell/lib/entity-registry.ts +39 -0
- package/src/shell/lib/entity-routes.ts +53 -0
- package/src/shell/lib/format.ts +58 -0
- package/src/shell/lib/i18n.ts +3723 -0
- package/src/shell/lib/locale-config.ts +14 -0
- package/src/shell/lib/org-adapters/mock.ts +318 -0
- package/src/shell/lib/org-adapters/supabase.ts +533 -0
- package/src/shell/lib/org-context.ts +18 -0
- package/src/shell/lib/plugins.ts +11 -0
- package/src/shell/lib/router.ts +86 -0
- package/src/shell/lib/schedule-config.ts +42 -0
- package/src/shell/lib/schedule-service.ts +307 -0
- package/src/shell/lib/supabase.ts +19 -0
- package/src/shell/stores/auth.store.ts +29 -0
- package/src/shell/stores/billing.store.ts +1 -0
- package/src/shell/stores/chat.store.ts +45 -0
- package/src/shell/stores/index.ts +12 -0
- package/src/shell/stores/invite.store.ts +18 -0
- package/src/shell/stores/layout.store.ts +1 -0
- package/src/shell/stores/locale.store.ts +24 -0
- package/src/shell/stores/location.store.ts +89 -0
- package/src/shell/stores/notifications.store.ts +48 -0
- package/src/shell/stores/organization.store.ts +3 -0
- package/src/shell/stores/permissions.store.ts +1 -0
- package/src/shell/stores/plugin.store.ts +50 -0
- package/src/shell/stores/tenant.store.ts +26 -0
- package/src/shell/stores/theme.store.ts +162 -0
- package/src/shell/styles.css +433 -0
- package/src/shell/test-setup.ts +1 -0
- package/src/shell/types/auth-adapter.ts +11 -0
- package/src/shell/types/auth.ts +38 -0
- package/src/shell/types/billing.ts +90 -0
- package/src/shell/types/client-orders.ts +58 -0
- package/src/shell/types/crud.ts +134 -0
- package/src/shell/types/entities.ts +169 -0
- package/src/shell/types/entity-lookup.ts +25 -0
- package/src/shell/types/index.ts +16 -0
- package/src/shell/types/integrations.ts +51 -0
- package/src/shell/types/invite.ts +23 -0
- package/src/shell/types/layout.ts +29 -0
- package/src/shell/types/notifications.ts +33 -0
- package/src/shell/types/org-adapter.ts +72 -0
- package/src/shell/types/permissions.ts +64 -0
- package/src/shell/types/platform.ts +25 -0
- package/src/shell/types/plugins.ts +359 -0
- package/src/shell/types/tenant.ts +79 -0
- package/src/stores/createCrudStore.ts +138 -0
- package/src/supabase/client.ts +54 -0
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import * as React from 'react'
|
|
2
|
+
import { hashRouterAdapter } from '@fayz-ai/core'
|
|
3
|
+
|
|
4
|
+
// ---------------------------------------------------------------------------
|
|
5
|
+
// Admin hash router — the same lightweight approach the storefront uses. The
|
|
6
|
+
// admin shell collects routes from the plugin runtime + manifest pages and
|
|
7
|
+
// switches on the current hash path. Deliberately not react-router: an SDK
|
|
8
|
+
// shell shipping a router creates singleton hazards under source-alias dev.
|
|
9
|
+
// ---------------------------------------------------------------------------
|
|
10
|
+
|
|
11
|
+
const adapter = hashRouterAdapter()
|
|
12
|
+
|
|
13
|
+
export function normalizeAdminPath(path: string): string {
|
|
14
|
+
const clean = `/${path}`.replace(/\/+/g, '/')
|
|
15
|
+
return clean.length > 1 ? clean.replace(/\/+$/, '') : clean
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function navigateTo(to: string): void {
|
|
19
|
+
adapter.navigate(normalizeAdminPath(to))
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function useAdminPath(): string {
|
|
23
|
+
const [path, setPath] = React.useState(() => normalizeAdminPath(adapter.getCurrentPath() || '/'))
|
|
24
|
+
React.useEffect(() => {
|
|
25
|
+
return adapter.onPathChange((p) => {
|
|
26
|
+
setPath(normalizeAdminPath(p || '/'))
|
|
27
|
+
if (typeof window !== 'undefined') window.scrollTo(0, 0)
|
|
28
|
+
})
|
|
29
|
+
}, [])
|
|
30
|
+
return path
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Match '/courses/:id' against '/courses/abc' → { id: 'abc' }; supports a
|
|
34
|
+
* trailing '/*' wildcard (matches the base and any deeper path). */
|
|
35
|
+
export function matchRoute(pattern: string, path: string): Record<string, string> | null {
|
|
36
|
+
const cleanPath = (path.split('?')[0] ?? '').replace(/\/+$/, '') || '/'
|
|
37
|
+
if (pattern.endsWith('/*')) {
|
|
38
|
+
const base = pattern.slice(0, -2)
|
|
39
|
+
if (cleanPath === base || cleanPath.startsWith(`${base}/`)) return {}
|
|
40
|
+
return null
|
|
41
|
+
}
|
|
42
|
+
const patternParts = pattern.split('/').filter(Boolean)
|
|
43
|
+
const pathParts = cleanPath.split('/').filter(Boolean)
|
|
44
|
+
if (patternParts.length !== pathParts.length) return null
|
|
45
|
+
const params: Record<string, string> = {}
|
|
46
|
+
for (let i = 0; i < patternParts.length; i++) {
|
|
47
|
+
const pat = patternParts[i]!
|
|
48
|
+
const part = pathParts[i]!
|
|
49
|
+
if (pat.startsWith(':')) params[pat.slice(1)] = decodeURIComponent(part)
|
|
50
|
+
else if (pat !== part) return null
|
|
51
|
+
}
|
|
52
|
+
return params
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** Score a route pattern for specificity so the most specific match wins
|
|
56
|
+
* (static segments beat params; longer beats shorter; wildcard loses). */
|
|
57
|
+
export function routeScore(pattern: string): number {
|
|
58
|
+
const isWildcard = pattern.endsWith('/*')
|
|
59
|
+
const routePattern = isWildcard ? pattern.slice(0, -2) : pattern
|
|
60
|
+
const parts = routePattern.split('/').filter(Boolean)
|
|
61
|
+
let score = parts.length * 10
|
|
62
|
+
for (const p of parts) if (!p.startsWith(':')) score += 5
|
|
63
|
+
if (isWildcard) score -= 1
|
|
64
|
+
return score
|
|
65
|
+
}
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import * as React from 'react'
|
|
2
|
+
import {
|
|
3
|
+
registerScaffold,
|
|
4
|
+
defineApp,
|
|
5
|
+
getPluginFactory,
|
|
6
|
+
getComponent,
|
|
7
|
+
} from '@fayz-ai/core'
|
|
8
|
+
import type {
|
|
9
|
+
AppManifest,
|
|
10
|
+
PluginManifest,
|
|
11
|
+
LocaleConfig,
|
|
12
|
+
PermissionsConfig,
|
|
13
|
+
BillingConfig as ManifestBillingConfig,
|
|
14
|
+
} from '@fayz-ai/core'
|
|
15
|
+
import { AdminProviders } from './createFayzApp'
|
|
16
|
+
import { AdminShell } from './AdminShell'
|
|
17
|
+
import type { FayzAppConfig, CustomPage } from './config'
|
|
18
|
+
import { createSaasApp, type SaasAppConfig } from '../shell/createSaasApp'
|
|
19
|
+
|
|
20
|
+
// ---------------------------------------------------------------------------
|
|
21
|
+
// Admin scaffold — renders an admin app from a pure-data AppManifest. Mirrors
|
|
22
|
+
// the storefront scaffold's dual path: defineSaas turns a FayzAppConfig into a
|
|
23
|
+
// manifest (the sugar→data direction), and AdminScaffold turns a manifest back
|
|
24
|
+
// into the provider stack + shell (the data→render direction). Plugins live in
|
|
25
|
+
// the manifest as PluginRefs (id + JSON config) and are resolved to live
|
|
26
|
+
// PluginManifests through the plugin-factory registry, exactly the contract a
|
|
27
|
+
// generated app's plugins.generated.ts populates.
|
|
28
|
+
// ---------------------------------------------------------------------------
|
|
29
|
+
|
|
30
|
+
function slug(name: string): string {
|
|
31
|
+
return name.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '') || 'app'
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const liveConfigOption = '__fayzLiveConfigRef'
|
|
35
|
+
const liveSaasConfigs = new Map<string, FayzAppConfig | SaasAppConfig>()
|
|
36
|
+
|
|
37
|
+
function hasLegacyShellConfig(config: FayzAppConfig | SaasAppConfig): config is SaasAppConfig {
|
|
38
|
+
return 'organization' in config || 'settingsTabs' in config || 'bottomNav' in config || 'pluginRuntime' in config
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function getAuthRequireAuth(config: FayzAppConfig | SaasAppConfig): boolean {
|
|
42
|
+
return config.auth?.requireAuth ?? true
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function getLiveConfigRef(id: string): string {
|
|
46
|
+
return `code:${id}`
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Fayz/Saas config → AppManifest (sugar → data). During the Beauty/Resto
|
|
50
|
+
* migration, config-authored apps keep a live config ref so renderApp can
|
|
51
|
+
* preserve code-backed plugins and custom pages while the JSON manifest path
|
|
52
|
+
* matures. Serialized manifests still resolve through plugin/page registries. */
|
|
53
|
+
export function defineSaas(config: FayzAppConfig | SaasAppConfig): AppManifest {
|
|
54
|
+
const id = slug(config.name)
|
|
55
|
+
const configRef = getLiveConfigRef(id)
|
|
56
|
+
liveSaasConfigs.set(configRef, config)
|
|
57
|
+
return defineApp({
|
|
58
|
+
id,
|
|
59
|
+
name: config.name,
|
|
60
|
+
backend: config.supabaseUrl
|
|
61
|
+
? { provider: 'supabase', url: config.supabaseUrl }
|
|
62
|
+
: { provider: 'mock' },
|
|
63
|
+
locale: config.locale as LocaleConfig | undefined,
|
|
64
|
+
theme: config.theme as Record<string, unknown> | undefined,
|
|
65
|
+
permissions: config.permissions,
|
|
66
|
+
billing: config.billing as ManifestBillingConfig | undefined,
|
|
67
|
+
surfaces: {
|
|
68
|
+
admin: {
|
|
69
|
+
scaffold: 'admin',
|
|
70
|
+
plugins: (config.plugins ?? []).map((p) => ({ id: p.id })),
|
|
71
|
+
options: {
|
|
72
|
+
[liveConfigOption]: configRef,
|
|
73
|
+
layout: config.layout ?? 'sidebar',
|
|
74
|
+
requireAuth: getAuthRequireAuth(config),
|
|
75
|
+
},
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
})
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** Resolve a manifest's PluginRefs into live PluginManifests via the
|
|
82
|
+
* plugin-factory registry. Unknown ids are skipped with a console warning so a
|
|
83
|
+
* partial install degrades instead of crashing. */
|
|
84
|
+
function resolvePlugins(manifest: AppManifest, surfaceName: string): PluginManifest[] {
|
|
85
|
+
const refs = manifest.surfaces[surfaceName]?.plugins ?? []
|
|
86
|
+
const plugins: PluginManifest[] = []
|
|
87
|
+
for (const ref of refs) {
|
|
88
|
+
if (ref.enabled === false) continue
|
|
89
|
+
const factory = getPluginFactory(ref.id)
|
|
90
|
+
if (!factory) {
|
|
91
|
+
// eslint-disable-next-line no-console
|
|
92
|
+
console.warn(`[@fayz-ai/saas] No plugin factory registered for "${ref.id}". Did plugins.generated.ts run?`)
|
|
93
|
+
continue
|
|
94
|
+
}
|
|
95
|
+
plugins.push(factory(ref.config) as PluginManifest)
|
|
96
|
+
}
|
|
97
|
+
return plugins
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** Resolve manifest pages that reference a registered component id into the
|
|
101
|
+
* CustomPage shape the shell renders. (entity/blocks pages are handled by the
|
|
102
|
+
* plugins that own them; component pages are the app-level escape hatch.) */
|
|
103
|
+
function resolvePages(manifest: AppManifest, surfaceName: string): CustomPage[] {
|
|
104
|
+
const pages = manifest.surfaces[surfaceName]?.pages ?? []
|
|
105
|
+
const out: CustomPage[] = []
|
|
106
|
+
for (const p of pages) {
|
|
107
|
+
if (!p.component) continue
|
|
108
|
+
const Component = getComponent(p.component) as React.ComponentType | undefined
|
|
109
|
+
if (!Component) {
|
|
110
|
+
// eslint-disable-next-line no-console
|
|
111
|
+
console.warn(`[@fayz-ai/saas] Manifest page "${p.path}" references unregistered component "${p.component}".`)
|
|
112
|
+
continue
|
|
113
|
+
}
|
|
114
|
+
out.push({ path: p.path, label: p.label, icon: p.icon, section: p.section, component: Component })
|
|
115
|
+
}
|
|
116
|
+
return out
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/** AppManifest → FayzAppConfig (data → render). */
|
|
120
|
+
function manifestToFayzConfig(manifest: AppManifest, surfaceName: string): FayzAppConfig {
|
|
121
|
+
const options = (manifest.surfaces[surfaceName]?.options ?? {}) as Record<string, unknown>
|
|
122
|
+
const liveConfigRef = options[liveConfigOption]
|
|
123
|
+
const liveConfig = typeof liveConfigRef === 'string' ? liveSaasConfigs.get(liveConfigRef) : undefined
|
|
124
|
+
if (liveConfig && !hasLegacyShellConfig(liveConfig)) return liveConfig
|
|
125
|
+
|
|
126
|
+
return {
|
|
127
|
+
name: manifest.name,
|
|
128
|
+
supabaseUrl: manifest.backend?.provider === 'supabase' ? manifest.backend.url : undefined,
|
|
129
|
+
plugins: resolvePlugins(manifest, surfaceName),
|
|
130
|
+
pages: resolvePages(manifest, surfaceName),
|
|
131
|
+
theme: manifest.theme as FayzAppConfig['theme'],
|
|
132
|
+
layout: (options.layout as FayzAppConfig['layout']) ?? 'sidebar',
|
|
133
|
+
locale: manifest.locale as LocaleConfig | undefined,
|
|
134
|
+
permissions: manifest.permissions as PermissionsConfig | undefined,
|
|
135
|
+
billing: manifest.billing as FayzAppConfig['billing'],
|
|
136
|
+
auth: { requireAuth: (options.requireAuth as boolean) ?? true },
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export function AdminScaffold({ manifest, surface }: { manifest: AppManifest; surface: string }) {
|
|
141
|
+
const liveConfigRef = manifest.surfaces[surface]?.options?.[liveConfigOption]
|
|
142
|
+
const liveConfig = typeof liveConfigRef === 'string' ? liveSaasConfigs.get(liveConfigRef) : undefined
|
|
143
|
+
const LegacyApp = React.useMemo(() => {
|
|
144
|
+
if (!liveConfig || !hasLegacyShellConfig(liveConfig)) return null
|
|
145
|
+
return createSaasApp(liveConfig)
|
|
146
|
+
}, [liveConfig])
|
|
147
|
+
const config = React.useMemo(() => manifestToFayzConfig(manifest, surface), [manifest, surface])
|
|
148
|
+
|
|
149
|
+
if (LegacyApp) return <LegacyApp />
|
|
150
|
+
|
|
151
|
+
return (
|
|
152
|
+
<AdminProviders config={config}>
|
|
153
|
+
<AdminShell
|
|
154
|
+
appName={config.name}
|
|
155
|
+
logo={config.logo}
|
|
156
|
+
layout={config.layout}
|
|
157
|
+
pages={config.pages}
|
|
158
|
+
requireAuth={config.auth?.requireAuth}
|
|
159
|
+
loginTagline={config.auth?.loginTagline}
|
|
160
|
+
loginDescription={config.auth?.loginDescription}
|
|
161
|
+
loginLogo={config.auth?.loginLogo}
|
|
162
|
+
loginLayout={config.auth?.loginLayout}
|
|
163
|
+
showOAuth={config.auth?.showOAuth}
|
|
164
|
+
oauthProviders={config.auth?.oauthProviders}
|
|
165
|
+
showSettings
|
|
166
|
+
showOrgSettings={Boolean(config.org)}
|
|
167
|
+
/>
|
|
168
|
+
</AdminProviders>
|
|
169
|
+
)
|
|
170
|
+
}
|
|
171
|
+
AdminScaffold.displayName = 'AdminScaffold'
|
|
172
|
+
|
|
173
|
+
// Self-register so renderApp(manifest) can resolve the 'admin' scaffold once
|
|
174
|
+
// this package is imported.
|
|
175
|
+
registerScaffold('admin', AdminScaffold, { source: 'sdk', label: 'Admin' })
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import { getSupabaseClientOptional } from '@fayz-ai/core'
|
|
2
|
+
|
|
3
|
+
// ---------------------------------------------------------------------------
|
|
4
|
+
// Types
|
|
5
|
+
// ---------------------------------------------------------------------------
|
|
6
|
+
|
|
7
|
+
export interface EntityLookupResult {
|
|
8
|
+
id: string
|
|
9
|
+
label: string
|
|
10
|
+
subtitle?: string
|
|
11
|
+
group?: string
|
|
12
|
+
icon?: string
|
|
13
|
+
price?: number
|
|
14
|
+
data?: Record<string, unknown>
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export type EntityLookupMap = Record<string, EntityLookup>
|
|
18
|
+
|
|
19
|
+
export interface EntityLookup {
|
|
20
|
+
search(query: string): Promise<EntityLookupResult[]>
|
|
21
|
+
getById(id: string): Promise<EntityLookupResult | null>
|
|
22
|
+
list?(): Promise<EntityLookupResult[]>
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export type ArchetypeType = 'person' | 'product' | 'service' | 'location'
|
|
26
|
+
|
|
27
|
+
// ---------------------------------------------------------------------------
|
|
28
|
+
// Archetype display config
|
|
29
|
+
// ---------------------------------------------------------------------------
|
|
30
|
+
|
|
31
|
+
const ARCHETYPE_DISPLAY: Record<ArchetypeType, {
|
|
32
|
+
table: string
|
|
33
|
+
labelField: string
|
|
34
|
+
subtitleFields: string[]
|
|
35
|
+
priceField?: string
|
|
36
|
+
groupField?: string
|
|
37
|
+
}> = {
|
|
38
|
+
person: { table: 'persons', labelField: 'name', subtitleFields: ['phone'], groupField: 'kind' },
|
|
39
|
+
product: { table: 'products', labelField: 'name', subtitleFields: ['sku', 'description'], priceField: 'price' },
|
|
40
|
+
service: { table: 'services', labelField: 'name', subtitleFields: ['duration_minutes', 'description'], priceField: 'price' },
|
|
41
|
+
location: { table: 'locations', labelField: 'name', subtitleFields: ['city', 'address'], groupField: 'kind' },
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
interface ArchetypeLookupConfig {
|
|
45
|
+
archetype: ArchetypeType
|
|
46
|
+
kind?: string | string[]
|
|
47
|
+
kindLabels?: Record<string, string>
|
|
48
|
+
limit?: number
|
|
49
|
+
subtitleFields?: string[]
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function toResult(
|
|
53
|
+
row: Record<string, unknown>,
|
|
54
|
+
display: typeof ARCHETYPE_DISPLAY[ArchetypeType],
|
|
55
|
+
kindLabels?: Record<string, string>,
|
|
56
|
+
): EntityLookupResult {
|
|
57
|
+
const label = String(row[display.labelField] ?? row.name ?? '')
|
|
58
|
+
const subtitleParts = display.subtitleFields
|
|
59
|
+
.map((f) => row[f])
|
|
60
|
+
.filter(Boolean)
|
|
61
|
+
.map((v) => {
|
|
62
|
+
if (typeof v === 'number') return `${v}min`
|
|
63
|
+
return String(v)
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
let group: string | undefined
|
|
67
|
+
if (display.groupField && row[display.groupField]) {
|
|
68
|
+
const rawGroup = String(row[display.groupField])
|
|
69
|
+
group = kindLabels?.[rawGroup] ?? rawGroup
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return {
|
|
73
|
+
id: String(row.id),
|
|
74
|
+
label,
|
|
75
|
+
subtitle: subtitleParts.length > 0 ? subtitleParts.join(' · ') : undefined,
|
|
76
|
+
group,
|
|
77
|
+
price: display.priceField ? Number(row[display.priceField]) || undefined : undefined,
|
|
78
|
+
data: row,
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Create an EntityLookup that queries a saas_core archetype table via Supabase.
|
|
84
|
+
* Returns empty results if Supabase is not configured.
|
|
85
|
+
*/
|
|
86
|
+
export function createArchetypeLookup(config: ArchetypeLookupConfig): EntityLookup {
|
|
87
|
+
const baseDisplay = ARCHETYPE_DISPLAY[config.archetype]
|
|
88
|
+
const display = config.subtitleFields
|
|
89
|
+
? { ...baseDisplay, subtitleFields: config.subtitleFields }
|
|
90
|
+
: baseDisplay
|
|
91
|
+
const limit = config.limit ?? 20
|
|
92
|
+
const kinds = config.kind
|
|
93
|
+
? Array.isArray(config.kind) ? config.kind : [config.kind]
|
|
94
|
+
: undefined
|
|
95
|
+
|
|
96
|
+
function applyKindFilter(qb: any) {
|
|
97
|
+
if (kinds && kinds.length === 1) return qb.eq('kind', kinds[0])
|
|
98
|
+
if (kinds && kinds.length > 1) return qb.in('kind', kinds)
|
|
99
|
+
return qb
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return {
|
|
103
|
+
async search(query: string) {
|
|
104
|
+
const supabase = getSupabaseClientOptional() as any
|
|
105
|
+
if (!supabase) return []
|
|
106
|
+
|
|
107
|
+
let qb = supabase
|
|
108
|
+
.schema('saas_core')
|
|
109
|
+
.from(display.table)
|
|
110
|
+
.select('*')
|
|
111
|
+
.eq('is_active', true)
|
|
112
|
+
.order(display.labelField, { ascending: true })
|
|
113
|
+
.limit(limit)
|
|
114
|
+
|
|
115
|
+
if (query) qb = qb.ilike(display.labelField, `%${query}%`)
|
|
116
|
+
qb = applyKindFilter(qb)
|
|
117
|
+
|
|
118
|
+
const { data } = await qb
|
|
119
|
+
return (data ?? []).map((row: Record<string, unknown>) =>
|
|
120
|
+
toResult(row, display, config.kindLabels),
|
|
121
|
+
)
|
|
122
|
+
},
|
|
123
|
+
|
|
124
|
+
async list() {
|
|
125
|
+
const supabase = getSupabaseClientOptional() as any
|
|
126
|
+
if (!supabase) return []
|
|
127
|
+
|
|
128
|
+
let qb = supabase
|
|
129
|
+
.schema('saas_core')
|
|
130
|
+
.from(display.table)
|
|
131
|
+
.select('*')
|
|
132
|
+
.eq('is_active', true)
|
|
133
|
+
.order(display.labelField, { ascending: true })
|
|
134
|
+
.limit(limit)
|
|
135
|
+
|
|
136
|
+
qb = applyKindFilter(qb)
|
|
137
|
+
|
|
138
|
+
const { data } = await qb
|
|
139
|
+
return (data ?? []).map((row: Record<string, unknown>) =>
|
|
140
|
+
toResult(row, display, config.kindLabels),
|
|
141
|
+
)
|
|
142
|
+
},
|
|
143
|
+
|
|
144
|
+
async getById(id: string) {
|
|
145
|
+
const supabase = getSupabaseClientOptional() as any
|
|
146
|
+
if (!supabase) return null
|
|
147
|
+
|
|
148
|
+
const { data } = await supabase
|
|
149
|
+
.schema('saas_core')
|
|
150
|
+
.from(display.table)
|
|
151
|
+
.select('*')
|
|
152
|
+
.eq('id', id)
|
|
153
|
+
.single()
|
|
154
|
+
|
|
155
|
+
return data
|
|
156
|
+
? toResult(data as Record<string, unknown>, display, config.kindLabels)
|
|
157
|
+
: null
|
|
158
|
+
},
|
|
159
|
+
}
|
|
160
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { create } from 'zustand'
|
|
2
|
+
import type { Plan } from '@fayz-ai/core'
|
|
3
|
+
|
|
4
|
+
// ---------------------------------------------------------------------------
|
|
5
|
+
// Types
|
|
6
|
+
// ---------------------------------------------------------------------------
|
|
7
|
+
|
|
8
|
+
export interface Subscription {
|
|
9
|
+
id: string
|
|
10
|
+
orgId: string
|
|
11
|
+
planId: string
|
|
12
|
+
status: 'active' | 'trialing' | 'past_due' | 'canceled' | 'incomplete'
|
|
13
|
+
currentPeriodStart: string
|
|
14
|
+
currentPeriodEnd: string
|
|
15
|
+
cancelAtPeriodEnd?: boolean
|
|
16
|
+
stripeSubscriptionId?: string
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface Invoice {
|
|
20
|
+
id: string
|
|
21
|
+
orgId: string
|
|
22
|
+
amount: number
|
|
23
|
+
currency: string
|
|
24
|
+
status: 'paid' | 'open' | 'void' | 'uncollectible'
|
|
25
|
+
pdfUrl?: string
|
|
26
|
+
createdAt: string
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface BillingState {
|
|
30
|
+
subscription: Subscription | null
|
|
31
|
+
invoices: Invoice[]
|
|
32
|
+
plans: Plan[]
|
|
33
|
+
loading: boolean
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// ---------------------------------------------------------------------------
|
|
37
|
+
// Store
|
|
38
|
+
// ---------------------------------------------------------------------------
|
|
39
|
+
|
|
40
|
+
export interface BillingStore extends BillingState {
|
|
41
|
+
setSubscription: (subscription: Subscription | null) => void
|
|
42
|
+
setInvoices: (invoices: Invoice[]) => void
|
|
43
|
+
setPlans: (plans: Plan[]) => void
|
|
44
|
+
setLoading: (loading: boolean) => void
|
|
45
|
+
reset: () => void
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const initialState: BillingState = {
|
|
49
|
+
subscription: null,
|
|
50
|
+
invoices: [],
|
|
51
|
+
plans: [],
|
|
52
|
+
loading: false,
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export const useBillingStore = create<BillingStore>((set) => ({
|
|
56
|
+
...initialState,
|
|
57
|
+
setSubscription: (subscription) => set({ subscription }),
|
|
58
|
+
setInvoices: (invoices) => set({ invoices }),
|
|
59
|
+
setPlans: (plans) => set({ plans }),
|
|
60
|
+
setLoading: (loading) => set({ loading }),
|
|
61
|
+
reset: () => set(initialState),
|
|
62
|
+
}))
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
import React, { useState, useCallback, useRef } from 'react'
|
|
2
|
+
import { Mail, Phone, ExternalLink, Cake, Calendar } from 'lucide-react'
|
|
3
|
+
import { Popover, PopoverTrigger, PopoverContent } from '@fayz-ai/ui'
|
|
4
|
+
import { createArchetypeLookup } from '../../archetype-lookup'
|
|
5
|
+
import type { EntityLookupResult } from '../../archetype-lookup'
|
|
6
|
+
import { cn } from '@fayz-ai/ui'
|
|
7
|
+
import { resolveEntityHref } from '../../lib/entity-routes'
|
|
8
|
+
|
|
9
|
+
// Singleton person lookup — shared across all PersonLink instances
|
|
10
|
+
const personLookup = createArchetypeLookup({ archetype: 'person' })
|
|
11
|
+
|
|
12
|
+
interface PersonLinkProps {
|
|
13
|
+
/** Person UUID — required for fetching details */
|
|
14
|
+
personId?: string | null
|
|
15
|
+
/** Display name (shown as the clickable text) */
|
|
16
|
+
name: string
|
|
17
|
+
/** Optional href to navigate to the person's profile */
|
|
18
|
+
profileHref?: string
|
|
19
|
+
/** Optional tab to open on the detail page (e.g. "schedule") */
|
|
20
|
+
tab?: string
|
|
21
|
+
/** Size variant */
|
|
22
|
+
size?: 'sm' | 'default'
|
|
23
|
+
/** Additional className for the trigger */
|
|
24
|
+
className?: string
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function formatAge(dob: string): string | null {
|
|
28
|
+
try {
|
|
29
|
+
const birth = new Date(dob)
|
|
30
|
+
const now = new Date()
|
|
31
|
+
let age = now.getFullYear() - birth.getFullYear()
|
|
32
|
+
if (now.getMonth() < birth.getMonth() || (now.getMonth() === birth.getMonth() && now.getDate() < birth.getDate())) age--
|
|
33
|
+
return `${age}y`
|
|
34
|
+
} catch { return null }
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function PersonLink({ personId, name, profileHref, tab, size = 'default', className }: PersonLinkProps) {
|
|
38
|
+
const [person, setPerson] = useState<EntityLookupResult | null>(null)
|
|
39
|
+
const [loading, setLoading] = useState(false)
|
|
40
|
+
const [fetched, setFetched] = useState(false)
|
|
41
|
+
const [open, setOpen] = useState(false)
|
|
42
|
+
const hoverTimer = useRef<ReturnType<typeof setTimeout> | null>(null)
|
|
43
|
+
|
|
44
|
+
const fetchPerson = useCallback(() => {
|
|
45
|
+
if (!fetched && personId) {
|
|
46
|
+
setLoading(true)
|
|
47
|
+
personLookup.getById(personId).then((result) => {
|
|
48
|
+
setPerson(result)
|
|
49
|
+
setFetched(true)
|
|
50
|
+
setLoading(false)
|
|
51
|
+
}).catch(() => {
|
|
52
|
+
setFetched(true)
|
|
53
|
+
setLoading(false)
|
|
54
|
+
})
|
|
55
|
+
}
|
|
56
|
+
}, [personId, fetched])
|
|
57
|
+
|
|
58
|
+
const handleOpenChange = useCallback((v: boolean) => {
|
|
59
|
+
setOpen(v)
|
|
60
|
+
if (v) fetchPerson()
|
|
61
|
+
}, [fetchPerson])
|
|
62
|
+
|
|
63
|
+
function handleMouseEnter() {
|
|
64
|
+
hoverTimer.current = setTimeout(() => { setOpen(true); fetchPerson() }, 800)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function handleMouseLeave() {
|
|
68
|
+
if (hoverTimer.current) { clearTimeout(hoverTimer.current); hoverTimer.current = null }
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// If no personId, just render the name as plain text
|
|
72
|
+
if (!personId) {
|
|
73
|
+
return <span className={cn(size === 'sm' ? 'text-xs' : 'text-sm', className)}>{name}</span>
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const data = person?.data as Record<string, unknown> | undefined
|
|
77
|
+
const email = data?.email as string | undefined
|
|
78
|
+
const phone = data?.phone as string | undefined
|
|
79
|
+
const kind = data?.kind as string | undefined ?? person?.group
|
|
80
|
+
const isActive = data?.is_active as boolean | undefined
|
|
81
|
+
const dob = data?.date_of_birth as string | undefined
|
|
82
|
+
const createdAt = data?.created_at as string | undefined
|
|
83
|
+
const age = dob ? formatAge(dob) : null
|
|
84
|
+
const initial = name.charAt(0).toUpperCase()
|
|
85
|
+
|
|
86
|
+
// Auto-derive profile href from person kind if not explicitly provided
|
|
87
|
+
const resolvedHref = profileHref ?? (personId ? '#' + resolveEntityHref(personId, 'person', kind ?? undefined, tab) : undefined)
|
|
88
|
+
|
|
89
|
+
return (
|
|
90
|
+
<Popover open={open} onOpenChange={handleOpenChange}>
|
|
91
|
+
<PopoverTrigger asChild>
|
|
92
|
+
<button
|
|
93
|
+
type="button"
|
|
94
|
+
onClick={(e) => e.stopPropagation()}
|
|
95
|
+
onMouseEnter={handleMouseEnter}
|
|
96
|
+
onMouseLeave={handleMouseLeave}
|
|
97
|
+
className={cn(
|
|
98
|
+
'font-medium text-foreground underline decoration-dashed decoration-muted-foreground/40 underline-offset-2 hover:decoration-primary hover:text-primary transition-colors truncate text-left',
|
|
99
|
+
size === 'sm' ? 'text-xs' : 'text-sm',
|
|
100
|
+
className
|
|
101
|
+
)}
|
|
102
|
+
>
|
|
103
|
+
{name}
|
|
104
|
+
</button>
|
|
105
|
+
</PopoverTrigger>
|
|
106
|
+
<PopoverContent side="bottom" align="start" sideOffset={6}>
|
|
107
|
+
{loading ? (
|
|
108
|
+
<div className="p-4 space-y-3">
|
|
109
|
+
<div className="flex items-center gap-3">
|
|
110
|
+
<div className="h-10 w-10 rounded-full bg-muted/40 animate-pulse" />
|
|
111
|
+
<div className="space-y-1.5 flex-1">
|
|
112
|
+
<div className="h-3.5 w-24 rounded bg-muted/40 animate-pulse" />
|
|
113
|
+
<div className="h-2.5 w-16 rounded bg-muted/30 animate-pulse" />
|
|
114
|
+
</div>
|
|
115
|
+
</div>
|
|
116
|
+
<div className="space-y-2 pt-1">
|
|
117
|
+
<div className="h-3 w-36 rounded bg-muted/30 animate-pulse" />
|
|
118
|
+
<div className="h-3 w-28 rounded bg-muted/30 animate-pulse" />
|
|
119
|
+
</div>
|
|
120
|
+
</div>
|
|
121
|
+
) : (
|
|
122
|
+
<div>
|
|
123
|
+
{/* Header with profile link */}
|
|
124
|
+
<div className="flex items-start gap-3 p-4 pb-3">
|
|
125
|
+
<div className="flex h-10 w-10 shrink-0 items-center justify-center rounded-full bg-primary/10 text-primary text-sm font-bold">
|
|
126
|
+
{initial}
|
|
127
|
+
</div>
|
|
128
|
+
<div className="min-w-0 flex-1">
|
|
129
|
+
<p className="text-sm font-semibold truncate">{name}</p>
|
|
130
|
+
<div className="flex items-center gap-1.5 mt-0.5">
|
|
131
|
+
{kind && (
|
|
132
|
+
<span className="inline-flex items-center rounded-full bg-muted px-2 py-0.5 text-[10px] font-medium text-muted-foreground capitalize">
|
|
133
|
+
{kind}
|
|
134
|
+
</span>
|
|
135
|
+
)}
|
|
136
|
+
{isActive !== undefined && (
|
|
137
|
+
<span className={`inline-flex items-center gap-1 text-[10px] ${isActive ? 'text-success' : 'text-muted-foreground/50'}`}>
|
|
138
|
+
<span className={`h-1.5 w-1.5 rounded-full ${isActive ? 'bg-success' : 'bg-muted-foreground/30'}`} />
|
|
139
|
+
{isActive ? 'Active' : 'Inactive'}
|
|
140
|
+
</span>
|
|
141
|
+
)}
|
|
142
|
+
</div>
|
|
143
|
+
</div>
|
|
144
|
+
{resolvedHref && (
|
|
145
|
+
<a
|
|
146
|
+
href={resolvedHref}
|
|
147
|
+
onClick={(e) => { e.preventDefault(); window.location.hash = resolvedHref.replace(/^#/, '') }}
|
|
148
|
+
className="flex h-7 w-7 items-center justify-center rounded-md text-muted-foreground hover:bg-muted hover:text-foreground transition-colors shrink-0"
|
|
149
|
+
title="View profile"
|
|
150
|
+
>
|
|
151
|
+
<ExternalLink className="h-3.5 w-3.5" />
|
|
152
|
+
</a>
|
|
153
|
+
)}
|
|
154
|
+
</div>
|
|
155
|
+
|
|
156
|
+
{/* Contact details */}
|
|
157
|
+
{(email || phone || age) && (
|
|
158
|
+
<div className="px-4 pb-3 space-y-1.5">
|
|
159
|
+
{email && (
|
|
160
|
+
<a href={`mailto:${email}`} className="flex items-center gap-2 text-xs text-muted-foreground hover:text-primary transition-colors">
|
|
161
|
+
<Mail className="h-3 w-3 shrink-0" />
|
|
162
|
+
<span className="truncate">{email}</span>
|
|
163
|
+
</a>
|
|
164
|
+
)}
|
|
165
|
+
{phone && (
|
|
166
|
+
<a href={`tel:${phone}`} className="flex items-center gap-2 text-xs text-muted-foreground hover:text-primary transition-colors">
|
|
167
|
+
<Phone className="h-3 w-3 shrink-0" />
|
|
168
|
+
<span>{phone}</span>
|
|
169
|
+
</a>
|
|
170
|
+
)}
|
|
171
|
+
{dob && (
|
|
172
|
+
<div className="flex items-center gap-2 text-xs text-muted-foreground">
|
|
173
|
+
<Cake className="h-3 w-3 shrink-0" />
|
|
174
|
+
<span>{new Date(dob).toLocaleDateString()}{age ? ` (${age})` : ''}</span>
|
|
175
|
+
</div>
|
|
176
|
+
)}
|
|
177
|
+
</div>
|
|
178
|
+
)}
|
|
179
|
+
|
|
180
|
+
{/* No details found */}
|
|
181
|
+
{!person && fetched && (
|
|
182
|
+
<div className="px-4 pb-3">
|
|
183
|
+
<p className="text-xs text-muted-foreground">No additional details available</p>
|
|
184
|
+
</div>
|
|
185
|
+
)}
|
|
186
|
+
|
|
187
|
+
{/* Footer — created at */}
|
|
188
|
+
{createdAt && (
|
|
189
|
+
<div className="border-t px-4 py-2 flex items-center gap-1.5 text-[10px] text-muted-foreground/50">
|
|
190
|
+
<Calendar className="h-2.5 w-2.5" />
|
|
191
|
+
<span>Added {new Date(createdAt).toLocaleDateString()}</span>
|
|
192
|
+
</div>
|
|
193
|
+
)}
|
|
194
|
+
</div>
|
|
195
|
+
)}
|
|
196
|
+
</PopoverContent>
|
|
197
|
+
</Popover>
|
|
198
|
+
)
|
|
199
|
+
}
|