@fayz-ai/saas 0.1.7 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/LICENSE +21 -0
  2. package/dist/crud/CrudDetailPage.d.ts.map +1 -1
  3. package/dist/crud/CrudFormPage.d.ts.map +1 -1
  4. package/dist/crud/archetypes/PersonDetailTabs.d.ts +12 -0
  5. package/dist/crud/archetypes/PersonDetailTabs.d.ts.map +1 -1
  6. package/dist/crud/fieldToColumn.d.ts.map +1 -1
  7. package/dist/crud/index.d.ts +1 -0
  8. package/dist/crud/index.d.ts.map +1 -1
  9. package/dist/crud/relation-field.d.ts +28 -0
  10. package/dist/crud/relation-field.d.ts.map +1 -0
  11. package/dist/db.cjs +14 -0
  12. package/dist/db.cjs.map +1 -0
  13. package/dist/db.d.ts +2 -0
  14. package/dist/db.d.ts.map +1 -0
  15. package/dist/db.js +3 -0
  16. package/dist/db.js.map +1 -0
  17. package/dist/hooks/useModuleNavigation.d.ts +2 -0
  18. package/dist/hooks/useModuleNavigation.d.ts.map +1 -1
  19. package/dist/index.cjs +747 -379
  20. package/dist/index.cjs.map +1 -1
  21. package/dist/index.d.ts +5 -1
  22. package/dist/index.d.ts.map +1 -1
  23. package/dist/index.js +552 -204
  24. package/dist/index.js.map +1 -1
  25. package/dist/shell/components/plugins/ConnectorsHub.d.ts +6 -0
  26. package/dist/shell/components/plugins/ConnectorsHub.d.ts.map +1 -0
  27. package/dist/shell/components/plugins/PluginSettingsPanel.d.ts +4 -2
  28. package/dist/shell/components/plugins/PluginSettingsPanel.d.ts.map +1 -1
  29. package/dist/shell/createSaasApp.d.ts.map +1 -1
  30. package/dist/shell/hooks/useModuleNavigation.d.ts +2 -0
  31. package/dist/shell/hooks/useModuleNavigation.d.ts.map +1 -1
  32. package/dist/shell/types/crud.d.ts +12 -1
  33. package/dist/shell/types/crud.d.ts.map +1 -1
  34. package/dist/ui.cjs +14 -0
  35. package/dist/ui.cjs.map +1 -0
  36. package/dist/ui.d.ts +2 -0
  37. package/dist/ui.d.ts.map +1 -0
  38. package/dist/ui.js +3 -0
  39. package/dist/ui.js.map +1 -0
  40. package/package.json +18 -4
  41. package/src/crud/CrudDetailPage.tsx +5 -0
  42. package/src/crud/CrudFormPage.tsx +12 -0
  43. package/src/crud/archetypes/PersonDetailTabs.tsx +22 -0
  44. package/src/crud/fieldToColumn.tsx +3 -0
  45. package/src/crud/index.ts +1 -0
  46. package/src/crud/relation-field.tsx +115 -0
  47. package/src/db.ts +12 -0
  48. package/src/hooks/useModuleNavigation.ts +27 -1
  49. package/src/index.ts +17 -0
  50. package/src/shell/components/plugins/ConnectorsHub.tsx +163 -0
  51. package/src/shell/components/plugins/PluginSettingsPanel.tsx +106 -27
  52. package/src/shell/createSaasApp.tsx +22 -1
  53. package/src/shell/hooks/useModuleNavigation.ts +27 -1
  54. package/src/shell/types/crud.ts +13 -0
  55. package/src/ui.ts +7 -0
package/dist/index.js CHANGED
@@ -4,19 +4,21 @@ import { PermissionsProvider, usePermissionsStore, usePermissionOptional } from
4
4
  export { PermissionsProvider, useHasPermission, usePermission, usePermissions, usePermissionsStore } from './chunk-XNWCGJYH.js';
5
5
  import { useBillingStore } from './chunk-5GYQK5IW.js';
6
6
  export { useBillingStore } from './chunk-5GYQK5IW.js';
7
- import * as React40 from 'react';
8
- import React40__default, { createContext, useMemo, useState, useRef, useCallback, useEffect, useContext, useSyncExternalStore } from 'react';
9
- import { hashRouterAdapter, registerScaffold, registerEntity, deriveEntityKey, resolveDataProvider, setCurrentLocale, resolvePluginRuntime, PluginRuntimeProvider, I18nProvider, useTranslation as useTranslation$1, getSupabaseClientOptional as getSupabaseClientOptional$1, setDefaultCurrency, registerTranslations, getComponent, getPluginFactory, mergeTranslations, coreTranslations, usePluginRuntime, resolvePluginComponent, exportCSV, usePluginRuntimeOptional, getCurrentLocale, getWidgetsForZone, formatDateTime as formatDateTime$1, formatDate as formatDate$1, formatCurrency as formatCurrency$1, defineApp, createMockProvider } from '@fayz-ai/core';
7
+ import * as React41 from 'react';
8
+ import React41__default, { createContext, useMemo, useState, useRef, useCallback, useEffect, useContext, useSyncExternalStore } from 'react';
9
+ import { hashRouterAdapter, registerScaffold, registerEntity, deriveEntityKey, resolveDataProvider, setCurrentLocale, resolvePluginRuntime, PluginRuntimeProvider, I18nProvider, useTranslation as useTranslation$1, getSupabaseClientOptional as getSupabaseClientOptional$1, setDefaultCurrency, registerTranslations, getComponent, getPluginFactory, mergeTranslations, coreTranslations, usePluginRuntime, resolvePluginComponent, exportCSV, recordNavigation, usePluginRuntimeOptional, getCurrentLocale, getWidgetsForZone, formatDateTime as formatDateTime$1, formatDate as formatDate$1, formatCurrency as formatCurrency$1, getActiveTenantId, defineApp, useConnectorsForPlugin, createMockProvider } from '@fayz-ai/core';
10
+ export { getActiveTenantId, getSupabaseClientOptional, renderApp, setCurrentLocale } from '@fayz-ai/core';
10
11
  import { AuthProvider, useAuth, createSupabaseAuthAdapter, createMockAuthAdapter } from '@fayz-ai/auth';
11
12
  import { ToastProvider, NavTransitionProvider, toast, Card, Button, cn, Input, ModuleLayoutProvider, AppShell, PageTransition, Dropdown, DropdownTrigger, DropdownContent, DropdownItem, DataTable, Modal, ModalContent, ModalHeader, ModalTitle, ModalDescription, ModalFooter, ModalBody, createFayzTheme, Avatar, AvatarImage, AvatarFallback, DropdownLabel, DropdownSeparator, Badge, CardHeader, CardTitle, CardDescription, CardContent, CardFooter, useLayoutStore, Select, SelectTrigger, SelectValue, SelectContent, SelectItem, Checkbox, useSaveBar, useBackHandler, Breadcrumb, Separator, Tabs, TabsList, TabsTrigger, TabsContent, ListView, useModuleLayout, ICON_MAP as ICON_MAP$1, Popover, PopoverTrigger, PopoverContent, CurrencyInput, Skeleton, TooltipProvider, Tooltip, TooltipTrigger, TooltipContent, DatePicker, TimePicker } from '@fayz-ai/ui';
12
13
  export { createFayzTheme, fayzThemePresets } from '@fayz-ai/ui';
13
14
  import * as LucideIcons4 from 'lucide-react';
14
- import { X, MessageCircle, Settings2, ArrowUp, MoreVertical, Upload, Download, Building2, User, Shield, Palette, ArrowLeft, Pencil, BookOpen, Sparkles, Users, ShieldCheck, MapPin, SlidersHorizontal, Puzzle, Trash2, Check, FileSpreadsheet, ArrowRight, AlertCircle, ChevronDown, Loader2, Calendar, ChevronRight, Plus, WifiOff, Eye, RefreshCw, UserPlus, MoreHorizontal, Mail, Clock, Save, Camera, KeyRound, Search, Box, List, Filter, Contact, Handshake, UtensilsCrossed, Tag, Percent, Globe, UserCog, Briefcase, ClipboardList, Wrench, Target, ShoppingCart, Megaphone, DollarSign, FileText, BarChart3, Package, Bell, CreditCard, Settings, Home, ChevronsUpDown, RotateCcw, Edit, ShoppingBag, CalendarDays, ExternalLink, Phone, Cake, ChevronLeft, CircleCheckBig, UserX, Ban, CircleAlert, CircleEllipsis, CircleDashed, ImagePlus, Truck, CalendarOff, CalendarPlus, Copy } from 'lucide-react';
15
+ import { X, MessageCircle, Settings2, ArrowUp, MoreVertical, Upload, Download, Building2, User, Shield, Palette, ArrowLeft, Pencil, BookOpen, Sparkles, Users, ShieldCheck, MapPin, SlidersHorizontal, Puzzle, Trash2, Check, FileSpreadsheet, ArrowRight, AlertCircle, ChevronDown, Loader2, Calendar, ChevronRight, Plus, WifiOff, Eye, RefreshCw, UserPlus, MoreHorizontal, Mail, Clock, Save, Camera, KeyRound, Search, Box, List, Filter, Contact, Handshake, UtensilsCrossed, Tag, Percent, Globe, UserCog, Briefcase, ClipboardList, Wrench, Target, ShoppingCart, Megaphone, DollarSign, FileText, BarChart3, Package, Bell, CreditCard, Settings, Home, ChevronsUpDown, RotateCcw, Edit, ShoppingBag, CalendarDays, ExternalLink, Phone, Cake, Plug, TableProperties, ChevronLeft, CircleCheckBig, UserX, Ban, CircleAlert, CircleEllipsis, CircleDashed, CheckCircle2, Power, ImagePlus, Truck, CalendarOff, CalendarPlus, Copy } from 'lucide-react';
15
16
  import { create, useStore } from 'zustand';
16
17
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
17
18
  import { createPortal } from 'react-dom';
18
19
  import { Command } from 'cmdk';
19
20
  import * as Dialog from '@radix-ui/react-dialog';
21
+ export { fayz } from '@fayz-ai/sdk';
20
22
 
21
23
  var useChatStore = create((set) => ({
22
24
  messages: [],
@@ -131,7 +133,7 @@ function detectActivePluginId(runtime) {
131
133
  }
132
134
  function useAITools() {
133
135
  const runtime = usePluginRuntimeOptional();
134
- return React40.useMemo(() => {
136
+ return React41.useMemo(() => {
135
137
  const hasPermission = runtime?.context.hasPermission;
136
138
  const verticalId = runtime?.context.tenant?.verticalId;
137
139
  const activePluginId = detectActivePluginId(runtime);
@@ -264,9 +266,9 @@ function useTranslation() {
264
266
  return { t, locale: getCurrentLocale() };
265
267
  }
266
268
  function TypewriterText({ text, speed = 28, onDone }) {
267
- const [displayed, setDisplayed] = React40.useState("");
268
- const indexRef = React40.useRef(0);
269
- React40.useEffect(() => {
269
+ const [displayed, setDisplayed] = React41.useState("");
270
+ const indexRef = React41.useRef(0);
271
+ React41.useEffect(() => {
270
272
  setDisplayed("");
271
273
  indexRef.current = 0;
272
274
  const interval = setInterval(() => {
@@ -311,11 +313,11 @@ function ChatFab({ className, apiEndpoint, systemPrompt }) {
311
313
  const { contextualSuggestions } = useAITools();
312
314
  const { sendMessage } = useChat({ apiEndpoint, systemPrompt });
313
315
  const { t } = useTranslation();
314
- const [phase, setPhase] = React40.useState("idle");
315
- const [activeSuggestion, setActiveSuggestion] = React40.useState(null);
316
- const timersRef = React40.useRef([]);
317
- const prevSuggestionRef = React40.useRef(null);
318
- React40.useEffect(ensureStyles, []);
316
+ const [phase, setPhase] = React41.useState("idle");
317
+ const [activeSuggestion, setActiveSuggestion] = React41.useState(null);
318
+ const timersRef = React41.useRef([]);
319
+ const prevSuggestionRef = React41.useRef(null);
320
+ React41.useEffect(ensureStyles, []);
319
321
  const clearTimers = () => {
320
322
  timersRef.current.forEach(clearTimeout);
321
323
  timersRef.current = [];
@@ -325,7 +327,7 @@ function ChatFab({ className, apiEndpoint, systemPrompt }) {
325
327
  };
326
328
  const topSuggestion = contextualSuggestions[0] ?? null;
327
329
  const topLabel = topSuggestion?.label ?? null;
328
- React40.useEffect(() => {
330
+ React41.useEffect(() => {
329
331
  if (isOpen || !topSuggestion || !topLabel) {
330
332
  if (phase !== "idle") {
331
333
  clearTimers();
@@ -355,7 +357,7 @@ function ChatFab({ className, apiEndpoint, systemPrompt }) {
355
357
  }, 9e3);
356
358
  return clearTimers;
357
359
  }, [topLabel, isOpen]);
358
- React40.useEffect(() => {
360
+ React41.useEffect(() => {
359
361
  if (isOpen && phase !== "idle") {
360
362
  clearTimers();
361
363
  setPhase("idle");
@@ -498,16 +500,16 @@ function ChatPanel({
498
500
  const { sendMessage } = useChat({ apiEndpoint, systemPrompt });
499
501
  const { t } = useTranslation();
500
502
  const { suggestions, toolGroups } = useAITools();
501
- const [input, setInput] = React40.useState("");
502
- const [showTools, setShowTools] = React40.useState(false);
503
- const scrollRef = React40.useRef(null);
504
- const inputRef = React40.useRef(null);
503
+ const [input, setInput] = React41.useState("");
504
+ const [showTools, setShowTools] = React41.useState(false);
505
+ const scrollRef = React41.useRef(null);
506
+ const inputRef = React41.useRef(null);
505
507
  const hasMessages = messages.length > 0;
506
508
  const totalTools = toolGroups.reduce((sum, g) => sum + g.tools.length, 0);
507
- React40.useEffect(() => {
509
+ React41.useEffect(() => {
508
510
  scrollRef.current?.scrollTo({ top: scrollRef.current.scrollHeight, behavior: "smooth" });
509
511
  }, [messages]);
510
- React40.useEffect(() => {
512
+ React41.useEffect(() => {
511
513
  if (isOpen) {
512
514
  const timer = setTimeout(() => inputRef.current?.focus(), 100);
513
515
  return () => clearTimeout(timer);
@@ -1064,8 +1066,8 @@ function navigateTo(to) {
1064
1066
  adapter.navigate(normalizeAdminPath(to));
1065
1067
  }
1066
1068
  function useAdminPath() {
1067
- const [path, setPath] = React40.useState(() => normalizeAdminPath(adapter.getCurrentPath() || "/"));
1068
- React40.useEffect(() => {
1069
+ const [path, setPath] = React41.useState(() => normalizeAdminPath(adapter.getCurrentPath() || "/"));
1070
+ React41.useEffect(() => {
1069
1071
  return adapter.onPathChange((p) => {
1070
1072
  setPath(normalizeAdminPath(p || "/"));
1071
1073
  if (typeof window !== "undefined") window.scrollTo(0, 0);
@@ -1120,12 +1122,12 @@ function LoginPage({
1120
1122
  return !v || v === key || v.includes("{{") ? fallback : v;
1121
1123
  };
1122
1124
  const { signIn, signUp, signInWithOAuth, resetPassword, isLoading, error } = useAuth();
1123
- const [view, setView] = React40.useState("login");
1124
- const [email, setEmail] = React40.useState("");
1125
- const [password, setPassword] = React40.useState("");
1126
- const [fullName, setFullName] = React40.useState("");
1127
- const [notice, setNotice] = React40.useState(null);
1128
- const [submitting, setSubmitting] = React40.useState(false);
1125
+ const [view, setView] = React41.useState("login");
1126
+ const [email, setEmail] = React41.useState("");
1127
+ const [password, setPassword] = React41.useState("");
1128
+ const [fullName, setFullName] = React41.useState("");
1129
+ const [notice, setNotice] = React41.useState(null);
1130
+ const [submitting, setSubmitting] = React41.useState(false);
1129
1131
  const busy = submitting || isLoading;
1130
1132
  const brandLogo = loginLogo ?? logo;
1131
1133
  const heading = view === "login" ? tr("auth.login.title", "Welcome back") : view === "signup" ? tr("auth.signup.title", "Create account") : tr("auth.recovery.title", "Reset password");
@@ -1366,11 +1368,11 @@ var CURRENCIES = [
1366
1368
  function CompanySettings({ tenant, onSave }) {
1367
1369
  const { t } = useTranslation();
1368
1370
  const { locale: uiLocale, setLocale: setUiLocale } = useLocaleStore();
1369
- const [name, setName] = React40.useState(tenant?.name ?? "");
1370
- const [timezone, setTimezone] = React40.useState(tenant?.settings?.timezone ?? "UTC");
1371
- const [currency, setCurrency] = React40.useState(tenant?.settings?.currency ?? "USD");
1372
- const [saving, setSaving] = React40.useState(false);
1373
- React40.useEffect(() => {
1371
+ const [name, setName] = React41.useState(tenant?.name ?? "");
1372
+ const [timezone, setTimezone] = React41.useState(tenant?.settings?.timezone ?? "UTC");
1373
+ const [currency, setCurrency] = React41.useState(tenant?.settings?.currency ?? "USD");
1374
+ const [saving, setSaving] = React41.useState(false);
1375
+ React41.useEffect(() => {
1374
1376
  if (tenant) {
1375
1377
  setName(tenant.name);
1376
1378
  setTimezone(tenant.settings?.timezone ?? "UTC");
@@ -1520,10 +1522,10 @@ function getInitials(name) {
1520
1522
  }
1521
1523
  function UserProfile({ user, onSave, onAvatarChange }) {
1522
1524
  const { t } = useTranslation();
1523
- const [fullName, setFullName] = React40.useState(user?.fullName ?? "");
1524
- const [saving, setSaving] = React40.useState(false);
1525
- const fileInputRef = React40.useRef(null);
1526
- React40.useEffect(() => {
1525
+ const [fullName, setFullName] = React41.useState(user?.fullName ?? "");
1526
+ const [saving, setSaving] = React41.useState(false);
1527
+ const fileInputRef = React41.useRef(null);
1528
+ React41.useEffect(() => {
1527
1529
  if (user) {
1528
1530
  setFullName(user.fullName);
1529
1531
  }
@@ -1732,9 +1734,9 @@ function SecuritySettings({
1732
1734
  onTenantSecurityChange
1733
1735
  }) {
1734
1736
  const { t } = useTranslation();
1735
- const [resetting, setResetting] = React40.useState(false);
1736
- const [resetSent, setResetSent] = React40.useState(false);
1737
- const [savingTenant, setSavingTenant] = React40.useState(false);
1737
+ const [resetting, setResetting] = React41.useState(false);
1738
+ const [resetSent, setResetSent] = React41.useState(false);
1739
+ const [savingTenant, setSavingTenant] = React41.useState(false);
1738
1740
  const handleResetPassword = async () => {
1739
1741
  if (!onResetPassword) return;
1740
1742
  setResetting(true);
@@ -1894,8 +1896,8 @@ function FileUploadField({
1894
1896
  hint
1895
1897
  }) {
1896
1898
  const { t } = useTranslation();
1897
- const inputRef = React40.useRef(null);
1898
- const [preview, setPreview] = React40.useState(null);
1899
+ const inputRef = React41.useRef(null);
1900
+ const [preview, setPreview] = React41.useState(null);
1899
1901
  const handleChange = (e) => {
1900
1902
  const file = e.target.files?.[0];
1901
1903
  if (file) {
@@ -1962,14 +1964,14 @@ function FileUploadField({
1962
1964
  }
1963
1965
  function BrandingSettings({ branding, onSave }) {
1964
1966
  const { t } = useTranslation();
1965
- const [primaryColor, setPrimaryColor] = React40.useState(branding?.primaryColor ?? "#000000");
1966
- const [accentColor, setAccentColor] = React40.useState(branding?.accentColor ?? "#6366f1");
1967
- const [logoFile, setLogoFile] = React40.useState(null);
1968
- const [faviconFile, setFaviconFile] = React40.useState(null);
1969
- const [logoUrl, setLogoUrl] = React40.useState(branding?.logoUrl ?? "");
1970
- const [faviconUrl, setFaviconUrl] = React40.useState(branding?.faviconUrl ?? "");
1971
- const [saving, setSaving] = React40.useState(false);
1972
- React40.useEffect(() => {
1967
+ const [primaryColor, setPrimaryColor] = React41.useState(branding?.primaryColor ?? "#000000");
1968
+ const [accentColor, setAccentColor] = React41.useState(branding?.accentColor ?? "#6366f1");
1969
+ const [logoFile, setLogoFile] = React41.useState(null);
1970
+ const [faviconFile, setFaviconFile] = React41.useState(null);
1971
+ const [logoUrl, setLogoUrl] = React41.useState(branding?.logoUrl ?? "");
1972
+ const [faviconUrl, setFaviconUrl] = React41.useState(branding?.faviconUrl ?? "");
1973
+ const [saving, setSaving] = React41.useState(false);
1974
+ React41.useEffect(() => {
1973
1975
  if (branding) {
1974
1976
  setPrimaryColor(branding.primaryColor ?? "#000000");
1975
1977
  setAccentColor(branding.accentColor ?? "#6366f1");
@@ -2172,7 +2174,7 @@ function SettingsPage({
2172
2174
  afterContent
2173
2175
  }) {
2174
2176
  const { t } = useTranslation();
2175
- const defaultTabs = React40.useMemo(() => [
2177
+ const defaultTabs = React41.useMemo(() => [
2176
2178
  { id: "general", label: t("settings.general"), icon: /* @__PURE__ */ jsx(Building2, { className: "h-4 w-4" }), component: /* @__PURE__ */ jsx(ConnectedCompanySettings, {}) },
2177
2179
  { id: "profile", label: t("settings.profile"), icon: /* @__PURE__ */ jsx(User, { className: "h-4 w-4" }), component: /* @__PURE__ */ jsx(ConnectedUserProfile, {}) },
2178
2180
  { id: "security", label: t("settings.security"), icon: /* @__PURE__ */ jsx(Shield, { className: "h-4 w-4" }), component: /* @__PURE__ */ jsx(ConnectedSecuritySettings, {}) },
@@ -2188,10 +2190,10 @@ function SettingsPage({
2188
2190
  }
2189
2191
  return null;
2190
2192
  }
2191
- const [activeTab, setActiveTab] = React40.useState(() => {
2193
+ const [activeTab, setActiveTab] = React41.useState(() => {
2192
2194
  return getTabFromHash() ?? defaultTab ?? null;
2193
2195
  });
2194
- React40.useEffect(() => {
2196
+ React41.useEffect(() => {
2195
2197
  const handler = () => {
2196
2198
  const tab = getTabFromHash();
2197
2199
  if (tab) setActiveTab(tab);
@@ -2211,7 +2213,7 @@ function SettingsPage({
2211
2213
  setActiveTab(null);
2212
2214
  window.location.hash = "/settings";
2213
2215
  };
2214
- const renderNav = (activeId) => /* @__PURE__ */ jsx("ul", { className: "space-y-0.5", children: resolvedTabs.map((tab, i) => /* @__PURE__ */ jsxs(React40.Fragment, { children: [
2216
+ const renderNav = (activeId) => /* @__PURE__ */ jsx("ul", { className: "space-y-0.5", children: resolvedTabs.map((tab, i) => /* @__PURE__ */ jsxs(React41.Fragment, { children: [
2215
2217
  tab.isPlugin && (i === 0 || !resolvedTabs[i - 1]?.isPlugin) && /* @__PURE__ */ jsxs("li", { className: "py-2", children: [
2216
2218
  /* @__PURE__ */ jsx("div", { className: "border-t" }),
2217
2219
  /* @__PURE__ */ jsx("p", { className: "px-3 pt-2 text-[10px] font-semibold uppercase tracking-wider text-muted-foreground", children: t("common.plugins") })
@@ -2271,10 +2273,10 @@ function InviteMemberDialog({ open, onOpenChange, defaultEmail }) {
2271
2273
  const profiles = usePermissionsStore((s) => s.profiles);
2272
2274
  const setInvites = useInviteStore((s) => s.setInvites);
2273
2275
  const user = useAuthStore((s) => s.user);
2274
- const [emails, setEmails] = React40.useState([defaultEmail ?? ""]);
2275
- const [profileId, setProfileId] = React40.useState(profiles[1]?.id ?? profiles[0]?.id ?? "");
2276
- const [sending, setSending] = React40.useState(false);
2277
- const [sent, setSent] = React40.useState(false);
2276
+ const [emails, setEmails] = React41.useState([defaultEmail ?? ""]);
2277
+ const [profileId, setProfileId] = React41.useState(profiles[1]?.id ?? profiles[0]?.id ?? "");
2278
+ const [sending, setSending] = React41.useState(false);
2279
+ const [sent, setSent] = React41.useState(false);
2278
2280
  const addEmail = () => setEmails((prev) => [...prev, ""]);
2279
2281
  const updateEmail = (idx, value) => {
2280
2282
  setEmails((prev) => {
@@ -2472,8 +2474,8 @@ function TeamTab() {
2472
2474
  const setInvites = useInviteStore((s) => s.setInvites);
2473
2475
  const user = useAuthStore((s) => s.user);
2474
2476
  const { hasSystemPermission } = usePermission2();
2475
- const [inviteOpen, setInviteOpen] = React40.useState(false);
2476
- React40.useEffect(() => {
2477
+ const [inviteOpen, setInviteOpen] = React41.useState(false);
2478
+ React41.useEffect(() => {
2477
2479
  if (adapter2 && currentOrg) {
2478
2480
  dedup("team:invites:" + currentOrg.id, () => adapter2.listInvites(currentOrg.id)).then(setInvites).catch(() => {
2479
2481
  });
@@ -2620,12 +2622,12 @@ var SYSTEM_PERMISSIONS = [
2620
2622
  ];
2621
2623
  function PermissionMatrixEditor({ features, profile, onSave, onCancel, saving }) {
2622
2624
  const { t } = useTranslation();
2623
- const [name, setName] = React40.useState(profile?.name ?? "");
2624
- const [description, setDescription] = React40.useState(profile?.description ?? "");
2625
- const [systemPerms, setSystemPerms] = React40.useState(profile?.systemPermissions ?? []);
2626
- const [grants, setGrants] = React40.useState(profile?.grants ?? {});
2627
- const [collapsed, setCollapsed] = React40.useState(/* @__PURE__ */ new Set());
2628
- const [search, setSearch] = React40.useState("");
2625
+ const [name, setName] = React41.useState(profile?.name ?? "");
2626
+ const [description, setDescription] = React41.useState(profile?.description ?? "");
2627
+ const [systemPerms, setSystemPerms] = React41.useState(profile?.systemPermissions ?? []);
2628
+ const [grants, setGrants] = React41.useState(profile?.grants ?? {});
2629
+ const [collapsed, setCollapsed] = React41.useState(/* @__PURE__ */ new Set());
2630
+ const [search, setSearch] = React41.useState("");
2629
2631
  const toggleSystemPerm = (perm) => {
2630
2632
  setSystemPerms(
2631
2633
  (prev) => prev.includes(perm) ? prev.filter((p) => p !== perm) : [...prev, perm]
@@ -2649,7 +2651,7 @@ function PermissionMatrixEditor({ features, profile, onSave, onCancel, saving })
2649
2651
  if (!name.trim()) return;
2650
2652
  onSave({ name: name.trim(), description: description.trim() || void 0, systemPermissions: systemPerms, grants });
2651
2653
  };
2652
- const groups = React40.useMemo(() => {
2654
+ const groups = React41.useMemo(() => {
2653
2655
  const filtered = search ? features.filter((f) => f.label.toLowerCase().includes(search.toLowerCase())) : features;
2654
2656
  const map = /* @__PURE__ */ new Map();
2655
2657
  for (const f of filtered) {
@@ -2710,7 +2712,7 @@ function PermissionMatrixEditor({ features, profile, onSave, onCancel, saving })
2710
2712
  }
2711
2713
  ) }) })
2712
2714
  ] }, sp.id)),
2713
- Array.from(groups.entries()).map(([groupName, groupFeatures]) => /* @__PURE__ */ jsxs(React40.Fragment, { children: [
2715
+ Array.from(groups.entries()).map(([groupName, groupFeatures]) => /* @__PURE__ */ jsxs(React41.Fragment, { children: [
2714
2716
  /* @__PURE__ */ jsx("tr", { children: /* @__PURE__ */ jsx("td", { colSpan: 5, className: "p-2 px-3", children: /* @__PURE__ */ jsxs(
2715
2717
  "button",
2716
2718
  {
@@ -2755,10 +2757,10 @@ function PermissionProfilesTab() {
2755
2757
  const startImpersonation = usePermissionsStore((s) => s.startImpersonation);
2756
2758
  const canPreview = (realProfile?.systemPermissions ?? []).includes("manage_permissions") ?? false;
2757
2759
  const { t } = useTranslation();
2758
- const [editingProfile, setEditingProfile] = React40.useState(null);
2759
- const [creating, setCreating] = React40.useState(false);
2760
- const [saving, setSaving] = React40.useState(false);
2761
- const memberCountByProfile = React40.useMemo(() => {
2760
+ const [editingProfile, setEditingProfile] = React41.useState(null);
2761
+ const [creating, setCreating] = React41.useState(false);
2762
+ const [saving, setSaving] = React41.useState(false);
2763
+ const memberCountByProfile = React41.useMemo(() => {
2762
2764
  const counts = {};
2763
2765
  for (const m of members) {
2764
2766
  counts[m.profileId] = (counts[m.profileId] ?? 0) + 1;
@@ -3498,7 +3500,7 @@ function SubjectFormLayout({ fields, allFields, fieldGroups, values, onChange, r
3498
3500
  const IconComponent = LucideIcons4[entityIcon ?? "Heart"] ?? LucideIcons4.Heart;
3499
3501
  return /* @__PURE__ */ jsxs("div", { className: "space-y-6", children: [
3500
3502
  /* @__PURE__ */ jsx(Card, { children: /* @__PURE__ */ jsx(CardContent, { className: "pt-5", children: /* @__PURE__ */ jsxs("div", { className: "flex gap-5", children: [
3501
- /* @__PURE__ */ jsx("div", { className: "flex h-20 w-20 shrink-0 items-center justify-center rounded-2xl bg-primary/10 text-primary", children: React40__default.createElement(IconComponent, { className: "h-8 w-8" }) }),
3503
+ /* @__PURE__ */ jsx("div", { className: "flex h-20 w-20 shrink-0 items-center justify-center rounded-2xl bg-primary/10 text-primary", children: React41__default.createElement(IconComponent, { className: "h-8 w-8" }) }),
3502
3504
  /* @__PURE__ */ jsxs("div", { className: "flex-1 space-y-4", children: [
3503
3505
  /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-4", children: [
3504
3506
  nameField && /* @__PURE__ */ jsxs("div", { className: "flex-1 space-y-1.5", children: [
@@ -3542,6 +3544,99 @@ function SubjectFormLayout({ fields, allFields, fieldGroups, values, onChange, r
3542
3544
  notesField && /* @__PURE__ */ jsx(Card, { children: /* @__PURE__ */ jsx(CardContent, { className: "pt-5", children: /* @__PURE__ */ jsx(FieldItem5, { field: notesField, value: values[notesField.key], onChange: (val) => onChange(notesField.key, val), renderField: renderField2 }) }) })
3543
3545
  ] });
3544
3546
  }
3547
+ var cache = /* @__PURE__ */ new Map();
3548
+ function keyOf(r) {
3549
+ const tenant = r.tenantScoped !== false ? getActiveTenantId() ?? "" : "";
3550
+ return JSON.stringify([
3551
+ r.schema ?? "public",
3552
+ r.table,
3553
+ r.valueField ?? "id",
3554
+ r.labelField ?? "name",
3555
+ tenant,
3556
+ r.filter ?? null
3557
+ ]);
3558
+ }
3559
+ async function fetchOptions(r) {
3560
+ const supabase = getSupabaseClientOptional$1();
3561
+ if (!supabase) return [];
3562
+ const valueField = r.valueField ?? "id";
3563
+ const labelField = r.labelField ?? "name";
3564
+ let q = (r.schema ? supabase.schema(r.schema) : supabase).from(r.table).select(`${valueField}, ${labelField}`);
3565
+ if (r.tenantScoped !== false) {
3566
+ const tid = getActiveTenantId();
3567
+ if (tid) q = q.eq("tenant_id", tid);
3568
+ }
3569
+ if (r.filter) for (const [k, v] of Object.entries(r.filter)) q = q.eq(k, v);
3570
+ q = q.order(labelField, { ascending: true });
3571
+ const { data } = await q;
3572
+ return (data ?? []).map((row) => ({
3573
+ value: String(row[valueField]),
3574
+ label: String(row[labelField] ?? row[valueField])
3575
+ }));
3576
+ }
3577
+ function loadRelationOptions(r) {
3578
+ const k = keyOf(r);
3579
+ if (!cache.has(k)) {
3580
+ cache.set(k, fetchOptions(r).catch(() => {
3581
+ cache.delete(k);
3582
+ return [];
3583
+ }));
3584
+ }
3585
+ return cache.get(k);
3586
+ }
3587
+ function useRelationOptions(r) {
3588
+ const [options, setOptions] = useState([]);
3589
+ const [loading, setLoading] = useState(true);
3590
+ const dep = r ? keyOf(r) : "none";
3591
+ useEffect(() => {
3592
+ let alive = true;
3593
+ if (!r) {
3594
+ setLoading(false);
3595
+ return;
3596
+ }
3597
+ setLoading(true);
3598
+ loadRelationOptions(r).then((opts) => {
3599
+ if (alive) {
3600
+ setOptions(opts);
3601
+ setLoading(false);
3602
+ }
3603
+ });
3604
+ return () => {
3605
+ alive = false;
3606
+ };
3607
+ }, [dep]);
3608
+ const labelFor = (value) => options.find((o) => o.value === String(value))?.label ?? "";
3609
+ return { options, loading, labelFor };
3610
+ }
3611
+ function RelationSelect({
3612
+ relation,
3613
+ value,
3614
+ onChange,
3615
+ className,
3616
+ required,
3617
+ placeholder
3618
+ }) {
3619
+ const { options, loading } = useRelationOptions(relation);
3620
+ return /* @__PURE__ */ jsxs(
3621
+ "select",
3622
+ {
3623
+ value: value ?? "",
3624
+ onChange: (e) => onChange(e.target.value || null),
3625
+ required,
3626
+ className,
3627
+ children: [
3628
+ /* @__PURE__ */ jsx("option", { value: "", children: loading ? "Loading\u2026" : placeholder ?? "Select..." }),
3629
+ options.map((o) => /* @__PURE__ */ jsx("option", { value: o.value, children: o.label }, o.value))
3630
+ ]
3631
+ }
3632
+ );
3633
+ }
3634
+ function RelationCell({ relation, value }) {
3635
+ const { labelFor, loading } = useRelationOptions(relation);
3636
+ if (value == null || value === "") return /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: "\u2014" });
3637
+ const label = labelFor(value);
3638
+ return /* @__PURE__ */ jsx(Badge, { variant: "secondary", children: label || (loading ? "\u2026" : String(value)) });
3639
+ }
3545
3640
  function currencySymbolFor(code, locale) {
3546
3641
  if (!code) return "R$";
3547
3642
  try {
@@ -3628,6 +3723,18 @@ function renderField(field, value, onChange, allValues = {}) {
3628
3723
  className: `${baseClass} min-h-[60px] py-1.5`
3629
3724
  }
3630
3725
  );
3726
+ case "relation":
3727
+ return /* @__PURE__ */ jsx(
3728
+ RelationSelect,
3729
+ {
3730
+ relation: field.relation,
3731
+ value,
3732
+ onChange,
3733
+ className: baseClass,
3734
+ required: field.required,
3735
+ placeholder: field.placeholder ?? `Select ${field.label.toLowerCase()}...`
3736
+ }
3737
+ );
3631
3738
  case "select": {
3632
3739
  const options = (field.options ?? []).map(
3633
3740
  (o) => typeof o === "string" ? { label: o, value: o } : o
@@ -3754,7 +3861,7 @@ function CrudFormPage({ entityDef, mode, initialData, onSubmit, onCancel, namePl
3754
3861
  const computeInit = () => mode === "edit" && initialData ? { ...getDefaultValues(formFields), ...initialData } : getDefaultValues(formFields);
3755
3862
  const [values, setValues] = useState(computeInit);
3756
3863
  const [saving, setSaving] = useState(false);
3757
- const initialRef = React40__default.useRef(JSON.stringify(values));
3864
+ const initialRef = React41__default.useRef(JSON.stringify(values));
3758
3865
  useEffect(() => {
3759
3866
  const init = computeInit();
3760
3867
  setValues(init);
@@ -3948,6 +4055,8 @@ function fieldToColumns(fields, options) {
3948
4055
  /* @__PURE__ */ jsx("span", { className: "h-4 w-4 rounded-full border shrink-0", style: { backgroundColor: String(value) } }),
3949
4056
  /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: String(value) })
3950
4057
  ] });
4058
+ case "relation":
4059
+ return /* @__PURE__ */ jsx(RelationCell, { relation: field.relation, value });
3951
4060
  case "select":
3952
4061
  return /* @__PURE__ */ jsx(Badge, { variant: "secondary", children: String(value) });
3953
4062
  case "number":
@@ -5275,6 +5384,25 @@ function DocumentsTab({ item }) {
5275
5384
  }
5276
5385
  );
5277
5386
  }
5387
+ function FinancialTab({ item }) {
5388
+ const runtime = usePluginRuntimeOptional();
5389
+ const widgets = runtime ? getWidgetsForZone(runtime, "person.detail.financial") : [];
5390
+ if (widgets.length > 0 && item) {
5391
+ const w = widgets[0];
5392
+ const Widget = w.component;
5393
+ return /* @__PURE__ */ jsx(
5394
+ Widget,
5395
+ {
5396
+ item,
5397
+ config: w.config,
5398
+ runtime: runtime?.context,
5399
+ plugin: w.plugin,
5400
+ widget: w
5401
+ }
5402
+ );
5403
+ }
5404
+ return null;
5405
+ }
5278
5406
  function ScheduleTab({ item }) {
5279
5407
  const t = useTranslation$1();
5280
5408
  if (!item?.id) {
@@ -5292,6 +5420,9 @@ function ScheduleTab({ item }) {
5292
5420
  var PERSON_DETAIL_TABS = [
5293
5421
  { id: "schedule", label: "Schedule", icon: "CalendarDays", component: ScheduleTab, visibleFor: ["staff"] },
5294
5422
  { id: "documents", label: "Documents", icon: "FileText", component: DocumentsTab },
5423
+ // Per-person financial statement — only shown when the financial plugin contributes
5424
+ // its widget (see requiresWidgetZone). Clients = paid invoices; staff = commissions.
5425
+ { id: "financial", label: "Statement", icon: "DollarSign", component: FinancialTab, requiresWidgetZone: "person.detail.financial" },
5295
5426
  { id: "activity", label: "Activity", icon: "Clock", component: ActivityTab }
5296
5427
  ];
5297
5428
  function ComingSoon2({ icon: Icon, title, description }) {
@@ -5744,8 +5875,9 @@ function CrudDetailPage({
5744
5875
  const subtitleValue = entityDef.subtitleField ? item[entityDef.subtitleField] : null;
5745
5876
  const imageValue = entityDef.imageField ? item[entityDef.imageField] : null;
5746
5877
  const initial = typeof displayValue === "string" ? displayValue.charAt(0).toUpperCase() : "?";
5878
+ const runtime = usePluginRuntimeOptional();
5747
5879
  const archetypeTabs = getArchetypeTabs(entityDef.layout, entityDef.data?.archetypeKind);
5748
- const customTabs = [...archetypeTabs, ...entityDef.detailTabs ?? []];
5880
+ const customTabs = [...archetypeTabs, ...entityDef.detailTabs ?? []].filter((tab) => !tab.requiresWidgetZone || (runtime ? getWidgetsForZone(runtime, tab.requiresWidgetZone).length > 0 : false));
5749
5881
  const entityId = entityDef.name.toLowerCase().replace(/\s+/g, "-");
5750
5882
  const widgetZone = `${entityId}.detail.tabs`;
5751
5883
  const validTabs = ["overview", ...customTabs.map((t2) => t2.id)];
@@ -5801,7 +5933,7 @@ function CrudDetailPage({
5801
5933
  })
5802
5934
  ] }),
5803
5935
  /* @__PURE__ */ jsx(TabsContent, { value: "overview", className: embedded ? "mt-2" : "mt-4", children: /* @__PURE__ */ jsx(OverviewTab, { entityDef, item, compact: embedded }) }),
5804
- customTabs.map((tab) => /* @__PURE__ */ jsx(TabsContent, { value: tab.id, className: "mt-4", children: tab.component ? React40__default.createElement(tab.component, { item, entityDef, ...tab.props ?? {} }) : /* @__PURE__ */ jsx(Card, { children: /* @__PURE__ */ jsx(CardContent, { className: "flex items-center justify-center py-12", children: /* @__PURE__ */ jsx("p", { className: "text-muted-foreground", children: t("common.noContent") }) }) }) }, tab.id))
5936
+ customTabs.map((tab) => /* @__PURE__ */ jsx(TabsContent, { value: tab.id, className: "mt-4", children: tab.component ? React41__default.createElement(tab.component, { item, entityDef, ...tab.props ?? {} }) : /* @__PURE__ */ jsx(Card, { children: /* @__PURE__ */ jsx(CardContent, { className: "flex items-center justify-center py-12", children: /* @__PURE__ */ jsx("p", { className: "text-muted-foreground", children: t("common.noContent") }) }) }) }, tab.id))
5805
5937
  ] }),
5806
5938
  /* @__PURE__ */ jsx(WidgetSlot, { zone: widgetZone })
5807
5939
  ] });
@@ -5926,7 +6058,7 @@ function StepIndicator({ current }) {
5926
6058
  return /* @__PURE__ */ jsx("div", { className: "flex items-center gap-1.5", children: STEPS.map((step, i) => {
5927
6059
  const isDone = i < currentIdx || current === "done";
5928
6060
  const isActive = step === current;
5929
- return /* @__PURE__ */ jsxs(React40__default.Fragment, { children: [
6061
+ return /* @__PURE__ */ jsxs(React41__default.Fragment, { children: [
5930
6062
  i > 0 && /* @__PURE__ */ jsx("div", { className: cn("h-px w-6", isDone ? "bg-primary" : "bg-border") }),
5931
6063
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5", children: [
5932
6064
  /* @__PURE__ */ jsx(
@@ -7051,7 +7183,7 @@ function FieldRulesSettings({ entities, rules: initialRules, onSave }) {
7051
7183
  const [saving, setSaving] = useState(false);
7052
7184
  const [search, setSearch] = useState("");
7053
7185
  const [showMore, setShowMore] = useState(false);
7054
- React40.useEffect(() => {
7186
+ React41.useEffect(() => {
7055
7187
  setDraft(initialRules);
7056
7188
  }, [initialRules]);
7057
7189
  const isDirty = useMemo(
@@ -7407,8 +7539,8 @@ function buildSettingsTabs(pluginTabs, can, t, showOrgSettings) {
7407
7539
  settingsTabs.push({
7408
7540
  id: tab.id,
7409
7541
  label: translated === labelKey ? tab.label : translated,
7410
- icon: React40.createElement(IconComp, { className: "h-4 w-4" }),
7411
- component: React40.createElement(tab.component),
7542
+ icon: React41.createElement(IconComp, { className: "h-4 w-4" }),
7543
+ component: React41.createElement(tab.component),
7412
7544
  // Flag plugin-contributed tabs so SettingsPage draws the core/plugins divider.
7413
7545
  isPlugin: true
7414
7546
  });
@@ -7425,7 +7557,7 @@ function AdminShellInner({ appName, layout = "sidebar", logo, pages = [], showSe
7425
7557
  };
7426
7558
  const path = useAdminPath();
7427
7559
  const { user, signOut } = useAuth();
7428
- const navigation = React40.useMemo(() => {
7560
+ const navigation = React41.useMemo(() => {
7429
7561
  const items = runtime.navigation.map((entry) => ({
7430
7562
  ...navEntryToItem(entry),
7431
7563
  position: entry.position
@@ -7436,7 +7568,7 @@ function AdminShellInner({ appName, layout = "sidebar", logo, pages = [], showSe
7436
7568
  }
7437
7569
  return items.sort(compareNavigation);
7438
7570
  }, [runtime.navigation, pages]);
7439
- const routes = React40.useMemo(() => {
7571
+ const routes = React41.useMemo(() => {
7440
7572
  const list = [];
7441
7573
  const entityRouteMap = /* @__PURE__ */ new Map();
7442
7574
  for (const r of runtime.routes) {
@@ -7452,27 +7584,27 @@ function AdminShellInner({ appName, layout = "sidebar", logo, pages = [], showSe
7452
7584
  setEntityRouteMap(entityRouteMap);
7453
7585
  return list.sort((a, b) => routeScore(b.path) - routeScore(a.path));
7454
7586
  }, [runtime.routes, pages, showSettings]);
7455
- const match = React40.useMemo(() => {
7587
+ const match = React41.useMemo(() => {
7456
7588
  for (const r of routes) {
7457
7589
  const params = matchRoute(r.path, path);
7458
7590
  if (params) return { route: r, params };
7459
7591
  }
7460
7592
  return null;
7461
7593
  }, [routes, path]);
7462
- React40.useEffect(() => {
7594
+ React41.useEffect(() => {
7463
7595
  if (!match && (path === "/" || path === "") && navigation.length > 0) {
7464
7596
  navigateTo(navigation[0].route);
7465
7597
  }
7466
7598
  }, [match, path, navigation]);
7467
7599
  const ActiveComponent = match?.route.Component;
7468
7600
  const activeParams = match?.params ?? {};
7469
- const settingsTabs = React40.useMemo(
7601
+ const settingsTabs = React41.useMemo(
7470
7602
  () => buildSettingsTabs(runtime.settingsTabs, can, t, showOrgSettings),
7471
7603
  [runtime.settingsTabs, can, t, showOrgSettings]
7472
7604
  );
7473
7605
  const shellUser = user ? { fullName: user.fullName ?? user.email, email: user.email, avatarUrl: user.avatarUrl } : void 0;
7474
7606
  const moduleVariant = moduleNav ?? (layout === "sidebar" ? "tabs" : "rail");
7475
- const activePageTitle = React40.useMemo(() => {
7607
+ const activePageTitle = React41.useMemo(() => {
7476
7608
  if (path === "/settings" || path.startsWith("/settings/")) return tr("common.settings", "Settings");
7477
7609
  const candidates = navigation.filter((n) => n.route).sort((a, b) => b.route.length - a.route.length);
7478
7610
  const hit = candidates.find(
@@ -7593,7 +7725,7 @@ function buildI18nConfig(config) {
7593
7725
  }
7594
7726
  function BillingInitializer({ config }) {
7595
7727
  const setPlans = useBillingStore((s) => s.setPlans);
7596
- React40.useEffect(() => {
7728
+ React41.useEffect(() => {
7597
7729
  if (config.billing?.plans) setPlans(config.billing.plans.map(normalizeBillingPlan));
7598
7730
  }, [config.billing, setPlans]);
7599
7731
  return null;
@@ -7615,7 +7747,7 @@ function ThemeInitializer({ config }) {
7615
7747
  const initialize = useThemeStore((s) => s.initialize);
7616
7748
  const setOverrides = useThemeStore((s) => s.setOverrides);
7617
7749
  const setMode = useThemeStore((s) => s.setMode);
7618
- React40.useEffect(() => {
7750
+ React41.useEffect(() => {
7619
7751
  if (config.theme) {
7620
7752
  const isSaasTheme = "brand" in config.theme && ("radius" in config.theme || "sidebar" in config.theme || "font" in config.theme);
7621
7753
  const resolved = isSaasTheme ? resolveTheme(config.theme) : config.theme;
@@ -7630,7 +7762,7 @@ function ThemeInitializer({ config }) {
7630
7762
  }
7631
7763
  function AdminProviders({ config, children }) {
7632
7764
  const currentOrg = useOrganizationStore((s) => s.currentOrg);
7633
- const resolved = React40.useMemo(() => {
7765
+ const resolved = React41.useMemo(() => {
7634
7766
  if (config.supabaseUrl && config.supabaseAnonKey) {
7635
7767
  createFayzSupabaseClient(config.supabaseUrl, config.supabaseAnonKey);
7636
7768
  }
@@ -7644,7 +7776,7 @@ function AdminProviders({ config, children }) {
7644
7776
  };
7645
7777
  }, [config]);
7646
7778
  const path = useAdminPath();
7647
- const runtime = React40.useMemo(
7779
+ const runtime = React41.useMemo(
7648
7780
  () => resolvePluginRuntime({
7649
7781
  plugins: resolved.plugins,
7650
7782
  context: {
@@ -7736,10 +7868,10 @@ function hashRouterAdapter2() {
7736
7868
  window.location.hash = path;
7737
7869
  },
7738
7870
  usePathname: () => {
7739
- const [pathname, setPathname] = React40.useState(
7871
+ const [pathname, setPathname] = React41.useState(
7740
7872
  typeof window !== "undefined" ? window.location.hash.slice(1) || "/" : "/"
7741
7873
  );
7742
- React40.useEffect(() => {
7874
+ React41.useEffect(() => {
7743
7875
  const handler = () => setPathname(window.location.hash.slice(1) || "/");
7744
7876
  window.addEventListener("hashchange", handler);
7745
7877
  return () => window.removeEventListener("hashchange", handler);
@@ -7748,7 +7880,7 @@ function hashRouterAdapter2() {
7748
7880
  }
7749
7881
  };
7750
7882
  }
7751
- var RouterContext = React40.createContext(null);
7883
+ var RouterContext = React41.createContext(null);
7752
7884
  var RouterProvider = RouterContext.Provider;
7753
7885
  function MockModeBanner() {
7754
7886
  const { t } = useTranslation();
@@ -8031,8 +8163,8 @@ function BillingPage({ className }) {
8031
8163
  const { plans, subscription, loading } = useBillingStore();
8032
8164
  const billing = useBilling();
8033
8165
  const { t } = useTranslation();
8034
- const [interval, setInterval2] = React40.useState("monthly");
8035
- React40.useEffect(() => {
8166
+ const [interval, setInterval2] = React41.useState("monthly");
8167
+ React41.useEffect(() => {
8036
8168
  billing.fetchSubscription();
8037
8169
  }, []);
8038
8170
  const currentPlan = plans.find((p) => p.id === subscription?.planId);
@@ -8835,7 +8967,7 @@ function LoadingSkeleton() {
8835
8967
  function ProtectedRoute({ children, requiredRole, fallback, onUnauthenticated }) {
8836
8968
  const { user, loading, initialized } = useAuth3();
8837
8969
  const { requireRole } = usePermission2();
8838
- React40__default.useEffect(() => {
8970
+ React41__default.useEffect(() => {
8839
8971
  if (initialized && !loading && !user && onUnauthenticated) {
8840
8972
  onUnauthenticated();
8841
8973
  }
@@ -9662,9 +9794,9 @@ function OrgSwitcher({ collapsed }) {
9662
9794
  const setCurrentProfile = usePermissionsStore((s) => s.setCurrentProfile);
9663
9795
  const setProfiles = usePermissionsStore((s) => s.setProfiles);
9664
9796
  const user = useAuthStore((s) => s.user);
9665
- const [createOpen, setCreateOpen] = React40.useState(false);
9666
- const [newOrgName, setNewOrgName] = React40.useState("");
9667
- const [creating, setCreating] = React40.useState(false);
9797
+ const [createOpen, setCreateOpen] = React41.useState(false);
9798
+ const [newOrgName, setNewOrgName] = React41.useState("");
9799
+ const [creating, setCreating] = React41.useState(false);
9668
9800
  const { t } = useTranslation();
9669
9801
  if (!adapter2 || !currentOrg) return null;
9670
9802
  const initial = currentOrg.name.charAt(0).toUpperCase();
@@ -9850,11 +9982,11 @@ function TenantOnboarding({ verticalId, appName, logo }) {
9850
9982
  const setMembers = useOrganizationStore((s) => s.setMembers);
9851
9983
  const setProfiles = usePermissionsStore((s) => s.setProfiles);
9852
9984
  const setCurrentProfile = usePermissionsStore((s) => s.setCurrentProfile);
9853
- const [step, setStep] = React40.useState(0);
9854
- const [error, setError] = React40.useState("");
9855
- const [companyName, setCompanyName] = React40.useState("");
9856
- const [timezone, setTimezone] = React40.useState("America/Sao_Paulo");
9857
- const [currency, setCurrency] = React40.useState("BRL");
9985
+ const [step, setStep] = React41.useState(0);
9986
+ const [error, setError] = React41.useState("");
9987
+ const [companyName, setCompanyName] = React41.useState("");
9988
+ const [timezone, setTimezone] = React41.useState("America/Sao_Paulo");
9989
+ const [currency, setCurrency] = React41.useState("BRL");
9858
9990
  const handleCreate = async () => {
9859
9991
  if (!user || !companyName.trim()) return;
9860
9992
  setStep(2);
@@ -12188,8 +12320,8 @@ function createGlobalEntitySearch() {
12188
12320
  }
12189
12321
  function CommandPaletteWrapper({ navigation, routerAdapter }) {
12190
12322
  const { commandPaletteOpen, setCommandPaletteOpen } = useLayoutStore();
12191
- const entitySearch = React40__default.useMemo(() => createGlobalEntitySearch(), []);
12192
- const commands = React40__default.useMemo(() => {
12323
+ const entitySearch = React41__default.useMemo(() => createGlobalEntitySearch(), []);
12324
+ const commands = React41__default.useMemo(() => {
12193
12325
  const items = [];
12194
12326
  for (const nav of navigation) {
12195
12327
  items.push({
@@ -12217,13 +12349,13 @@ function CommandPaletteWrapper({ navigation, routerAdapter }) {
12217
12349
  );
12218
12350
  return items;
12219
12351
  }, [navigation, routerAdapter]);
12220
- const handleEntitySelect = React40__default.useCallback((result) => {
12352
+ const handleEntitySelect = React41__default.useCallback((result) => {
12221
12353
  const archetype = result.data?.archetype ? String(result.data.archetype) : void 0;
12222
12354
  const kind = result.data?.kind ? String(result.data.kind) : void 0;
12223
12355
  const href = resolveEntityHref2(result.id, archetype, kind);
12224
12356
  routerAdapter.navigate(href);
12225
12357
  }, [routerAdapter]);
12226
- return React40__default.createElement(CommandPalette, {
12358
+ return React41__default.createElement(CommandPalette, {
12227
12359
  commands,
12228
12360
  open: commandPaletteOpen,
12229
12361
  onOpenChange: setCommandPaletteOpen,
@@ -12234,13 +12366,13 @@ function CommandPaletteWrapper({ navigation, routerAdapter }) {
12234
12366
  function renderLogo(name, logo) {
12235
12367
  if (logo && typeof logo !== "string") return logo;
12236
12368
  const initial = typeof logo === "string" ? logo : name.charAt(0).toUpperCase();
12237
- return React40__default.createElement(
12369
+ return React41__default.createElement(
12238
12370
  "div",
12239
12371
  { className: "flex items-center gap-3" },
12240
- React40__default.createElement("div", {
12372
+ React41__default.createElement("div", {
12241
12373
  className: "w-8 h-8 rounded-lg bg-primary flex items-center justify-center text-primary-foreground font-bold text-sm"
12242
12374
  }, initial),
12243
- React40__default.createElement("span", { className: "font-bold text-lg" }, name)
12375
+ React41__default.createElement("span", { className: "font-bold text-lg" }, name)
12244
12376
  );
12245
12377
  }
12246
12378
  function resolveAuthAdapter2(config) {
@@ -12269,10 +12401,10 @@ function buildSettingsTabs2(config, pluginTabs, can, t) {
12269
12401
  const settingsTabs = [];
12270
12402
  if (config.organization) {
12271
12403
  settingsTabs.push(
12272
- { id: "team", label: t("settings.team"), icon: React40__default.createElement(Users, { className: "h-4 w-4" }), component: React40__default.createElement(TeamTab) },
12273
- { id: "permissions", label: t("settings.permissions"), icon: React40__default.createElement(ShieldCheck, { className: "h-4 w-4" }), component: React40__default.createElement(PermissionProfilesTab) },
12274
- { id: "locations", label: t("settings.locations"), icon: React40__default.createElement(MapPin, { className: "h-4 w-4" }), component: React40__default.createElement(LocationsCrudPage) },
12275
- { id: "field-rules", label: t("settings.fieldRules"), icon: React40__default.createElement(SlidersHorizontal, { className: "h-4 w-4" }), component: React40__default.createElement(ConnectedFieldRulesSettings) }
12404
+ { id: "team", label: t("settings.team"), icon: React41__default.createElement(Users, { className: "h-4 w-4" }), component: React41__default.createElement(TeamTab) },
12405
+ { id: "permissions", label: t("settings.permissions"), icon: React41__default.createElement(ShieldCheck, { className: "h-4 w-4" }), component: React41__default.createElement(PermissionProfilesTab) },
12406
+ { id: "locations", label: t("settings.locations"), icon: React41__default.createElement(MapPin, { className: "h-4 w-4" }), component: React41__default.createElement(LocationsCrudPage) },
12407
+ { id: "field-rules", label: t("settings.fieldRules"), icon: React41__default.createElement(SlidersHorizontal, { className: "h-4 w-4" }), component: React41__default.createElement(ConnectedFieldRulesSettings) }
12276
12408
  );
12277
12409
  }
12278
12410
  if (config.settingsTabs) {
@@ -12288,8 +12420,8 @@ function buildSettingsTabs2(config, pluginTabs, can, t) {
12288
12420
  settingsTabs.push({
12289
12421
  id: tab.id,
12290
12422
  label: pluginLabel === pluginLabelKey ? tab.label : pluginLabel,
12291
- icon: React40__default.createElement(IconComp, { className: "h-4 w-4" }),
12292
- component: React40__default.createElement(tab.component),
12423
+ icon: React41__default.createElement(IconComp, { className: "h-4 w-4" }),
12424
+ component: React41__default.createElement(tab.component),
12293
12425
  isPlugin: true
12294
12426
  });
12295
12427
  }
@@ -12308,7 +12440,7 @@ function createSaasApp(config) {
12308
12440
  const orgAdapter = resolveOrgAdapter2(config);
12309
12441
  const requireAuth = config.auth?.requireAuth ?? !!authAdapter;
12310
12442
  const LoginPageWrapper = () => {
12311
- return React40__default.createElement(LoginPage2, {
12443
+ return React41__default.createElement(LoginPage2, {
12312
12444
  appName: config.name,
12313
12445
  logo: config.auth?.loginLogo ?? logoNode,
12314
12446
  layout: config.auth?.loginLayout ?? "split",
@@ -12331,7 +12463,7 @@ function createSaasApp(config) {
12331
12463
  const { tenantPlugins: hydratedTenantPlugins } = useTenantPlugins({ enabled: shouldHydrateTenantPlugins });
12332
12464
  const { t } = useTranslation();
12333
12465
  const user = authUser ? { fullName: authUser.fullName, email: authUser.email, avatarUrl: authUser.avatarUrl } : config.user;
12334
- React40__default.useEffect(() => {
12466
+ React41__default.useEffect(() => {
12335
12467
  const pf = config.plugins?.flatMap((p) => p.declaredFeatures ?? []) ?? [];
12336
12468
  if (pf.length === 0) return;
12337
12469
  const store = usePermissionsStore.getState();
@@ -12339,8 +12471,12 @@ function createSaasApp(config) {
12339
12471
  const toAdd = pf.filter((f) => !existing.has(f.id));
12340
12472
  if (toAdd.length > 0) store.setFeatures([...store.features, ...toAdd]);
12341
12473
  }, []);
12474
+ const navLabelRef = React41__default.useRef(() => "");
12475
+ React41__default.useEffect(() => {
12476
+ recordNavigation(route, navLabelRef.current(route));
12477
+ }, [route]);
12342
12478
  if (route === "/login") {
12343
- return React40__default.createElement(LoginPageWrapper);
12479
+ return React41__default.createElement(LoginPageWrapper);
12344
12480
  }
12345
12481
  const tenantPluginBindings = config.pluginRuntime?.resolveTenantPlugins?.({
12346
12482
  tenant: currentOrg ? {
@@ -12395,6 +12531,14 @@ function createSaasApp(config) {
12395
12531
  fullBleed: pluginRoute.fullBleed
12396
12532
  });
12397
12533
  }
12534
+ navLabelRef.current = (path) => {
12535
+ const exact = navigation.find((n) => n.route === path)?.label;
12536
+ if (exact) return exact;
12537
+ for (const n of navigation) {
12538
+ if (n.route !== "/" && path.startsWith(n.route + "/")) return n.label;
12539
+ }
12540
+ return "";
12541
+ };
12398
12542
  let matchedPath = route;
12399
12543
  let routeEntry = routes.get(route);
12400
12544
  if (!routeEntry) {
@@ -12413,13 +12557,13 @@ function createSaasApp(config) {
12413
12557
  matchedPath
12414
12558
  }
12415
12559
  };
12416
- const NotFoundPage = () => React40__default.createElement(
12560
+ const NotFoundPage = () => React41__default.createElement(
12417
12561
  "div",
12418
12562
  { className: "flex flex-col items-center justify-center py-24 text-center" },
12419
- React40__default.createElement("h1", { className: "text-6xl font-bold text-muted-foreground/20 mb-4" }, t("app.notFound.title")),
12420
- React40__default.createElement("h2", { className: "text-xl font-semibold mb-2" }, t("app.notFound.heading")),
12421
- React40__default.createElement("p", { className: "text-sm text-muted-foreground mb-6" }, t("app.notFound.message")),
12422
- React40__default.createElement("button", {
12563
+ React41__default.createElement("h1", { className: "text-6xl font-bold text-muted-foreground/20 mb-4" }, t("app.notFound.title")),
12564
+ React41__default.createElement("h2", { className: "text-xl font-semibold mb-2" }, t("app.notFound.heading")),
12565
+ React41__default.createElement("p", { className: "text-sm text-muted-foreground mb-6" }, t("app.notFound.message")),
12566
+ React41__default.createElement("button", {
12423
12567
  className: "rounded-lg bg-primary px-4 py-2 text-sm font-medium text-primary-foreground hover:bg-primary/90 transition-colors",
12424
12568
  onClick: () => routerAdapter.navigate("/")
12425
12569
  }, t("app.notFound.goToDashboard"))
@@ -12439,67 +12583,67 @@ function createSaasApp(config) {
12439
12583
  routerAdapter.navigate("/login");
12440
12584
  }
12441
12585
  };
12442
- const renderedPage = matchedPath === "/settings" && config.showSettings !== false ? React40__default.createElement(SettingsPage, {
12586
+ const renderedPage = matchedPath === "/settings" && config.showSettings !== false ? React41__default.createElement(SettingsPage, {
12443
12587
  extraTabs: settingsTabs,
12444
- beforeContent: React40__default.createElement(WidgetSlot, {
12588
+ beforeContent: React41__default.createElement(WidgetSlot, {
12445
12589
  zone: "settings.before",
12446
12590
  className: "space-y-4",
12447
12591
  contextOverrides: { matchedPath }
12448
12592
  }),
12449
- afterContent: React40__default.createElement(WidgetSlot, {
12593
+ afterContent: React41__default.createElement(WidgetSlot, {
12450
12594
  zone: "settings.after",
12451
12595
  className: "space-y-4",
12452
12596
  contextOverrides: { matchedPath }
12453
12597
  })
12454
- }) : React40__default.createElement(PageComponent, routeEntry?.plugin ? {
12598
+ }) : React41__default.createElement(PageComponent, routeEntry?.plugin ? {
12455
12599
  plugin: routeEntry.plugin,
12456
12600
  runtime: pluginRuntime.context,
12457
12601
  config: routeEntry.plugin.config
12458
12602
  } : void 0);
12459
12603
  const fullBleed = Boolean(routeEntry?.fullBleed);
12460
- const pageContent = fullBleed ? React40__default.createElement("div", { key: matchedPath, className: "h-full min-h-0 overflow-hidden" }, renderedPage) : React40__default.createElement(
12604
+ const pageContent = fullBleed ? React41__default.createElement("div", { key: matchedPath, className: "h-full min-h-0 overflow-hidden" }, renderedPage) : React41__default.createElement(
12461
12605
  "div",
12462
12606
  { key: matchedPath, className: "saas-page-enter space-y-6 p-6" },
12463
- React40__default.createElement(WidgetSlot, {
12607
+ React41__default.createElement(WidgetSlot, {
12464
12608
  zone: "page.before",
12465
12609
  className: "space-y-4",
12466
12610
  contextOverrides: { matchedPath }
12467
12611
  }),
12468
12612
  renderedPage,
12469
- React40__default.createElement(WidgetSlot, {
12613
+ React41__default.createElement(WidgetSlot, {
12470
12614
  zone: "page.after",
12471
12615
  className: "space-y-4",
12472
12616
  contextOverrides: { matchedPath }
12473
12617
  })
12474
12618
  );
12475
- const pageElement = pagePermission ? React40__default.createElement(
12619
+ const pageElement = pagePermission ? React41__default.createElement(
12476
12620
  PermissionGate,
12477
12621
  {
12478
12622
  feature: pagePermission.feature,
12479
12623
  action: pagePermission.action,
12480
- fallback: React40__default.createElement(
12624
+ fallback: React41__default.createElement(
12481
12625
  "div",
12482
12626
  { className: "flex items-center justify-center py-24" },
12483
- React40__default.createElement(
12627
+ React41__default.createElement(
12484
12628
  "div",
12485
12629
  { className: "text-center" },
12486
- React40__default.createElement("h2", { className: "text-xl font-semibold mb-2" }, t("app.accessDenied.heading")),
12487
- React40__default.createElement("p", { className: "text-muted-foreground" }, t("app.accessDenied.message"))
12630
+ React41__default.createElement("h2", { className: "text-xl font-semibold mb-2" }, t("app.accessDenied.heading")),
12631
+ React41__default.createElement("p", { className: "text-muted-foreground" }, t("app.accessDenied.message"))
12488
12632
  )
12489
12633
  ),
12490
12634
  children: pageContent
12491
12635
  }
12492
12636
  ) : pageContent;
12493
- const orgSwitcherElement = orgAdapter ? React40__default.createElement(OrgSwitcher) : void 0;
12494
- const content = React40__default.createElement(
12637
+ const orgSwitcherElement = orgAdapter ? React41__default.createElement(OrgSwitcher) : void 0;
12638
+ const content = React41__default.createElement(
12495
12639
  PluginRuntimeProvider,
12496
12640
  { value: pluginRuntime },
12497
- React40__default.createElement(
12498
- React40__default.Fragment,
12641
+ React41__default.createElement(
12642
+ React41__default.Fragment,
12499
12643
  null,
12500
- React40__default.createElement(MockModeBanner),
12501
- React40__default.createElement(ImpersonationBanner),
12502
- React40__default.createElement(
12644
+ React41__default.createElement(MockModeBanner),
12645
+ React41__default.createElement(ImpersonationBanner),
12646
+ React41__default.createElement(
12503
12647
  AppShell,
12504
12648
  {
12505
12649
  variant: layout,
@@ -12520,22 +12664,22 @@ function createSaasApp(config) {
12520
12664
  } : void 0,
12521
12665
  logo: logoNode,
12522
12666
  orgSwitcher: orgSwitcherElement,
12523
- topbarStart: React40__default.createElement(WidgetSlot, {
12667
+ topbarStart: React41__default.createElement(WidgetSlot, {
12524
12668
  zone: "shell.topbar.start",
12525
12669
  className: "flex items-center gap-2",
12526
12670
  contextOverrides: { matchedPath }
12527
12671
  }),
12528
- topbarEnd: React40__default.createElement(WidgetSlot, {
12672
+ topbarEnd: React41__default.createElement(WidgetSlot, {
12529
12673
  zone: "shell.topbar.end",
12530
12674
  className: "flex items-center gap-2",
12531
12675
  contextOverrides: { matchedPath }
12532
12676
  }),
12533
- sidebarTopContent: React40__default.createElement(WidgetSlot, {
12677
+ sidebarTopContent: React41__default.createElement(WidgetSlot, {
12534
12678
  zone: "shell.sidebar.before-nav",
12535
12679
  className: "space-y-2",
12536
12680
  contextOverrides: { matchedPath }
12537
12681
  }),
12538
- sidebarFooterContent: React40__default.createElement(WidgetSlot, {
12682
+ sidebarFooterContent: React41__default.createElement(WidgetSlot, {
12539
12683
  zone: "shell.sidebar.footer",
12540
12684
  className: "space-y-2",
12541
12685
  contextOverrides: { matchedPath }
@@ -12545,26 +12689,26 @@ function createSaasApp(config) {
12545
12689
  pageElement
12546
12690
  ),
12547
12691
  // Org initializer
12548
- orgAdapter ? React40__default.createElement(OrgInitializer, { verticalId: config.verticalId }) : null,
12692
+ orgAdapter ? React41__default.createElement(OrgInitializer, { verticalId: config.verticalId }) : null,
12549
12693
  // Location switch overlay
12550
- React40__default.createElement(LocationSwitchOverlay),
12694
+ React41__default.createElement(LocationSwitchOverlay),
12551
12695
  // Chat
12552
- config.chat?.enabled !== false && config.chat ? React40__default.createElement(
12553
- React40__default.Fragment,
12696
+ config.chat?.enabled !== false && config.chat ? React41__default.createElement(
12697
+ React41__default.Fragment,
12554
12698
  null,
12555
- React40__default.createElement(ChatFab, { apiEndpoint: config.chat.apiEndpoint, systemPrompt: config.chat.systemPrompt }),
12556
- React40__default.createElement(ChatPanel, { title: config.chat.title, apiEndpoint: config.chat.apiEndpoint, systemPrompt: config.chat.systemPrompt })
12699
+ React41__default.createElement(ChatFab, { apiEndpoint: config.chat.apiEndpoint, systemPrompt: config.chat.systemPrompt }),
12700
+ React41__default.createElement(ChatPanel, { title: config.chat.title, apiEndpoint: config.chat.apiEndpoint, systemPrompt: config.chat.systemPrompt })
12557
12701
  ) : null,
12558
- React40__default.createElement(WidgetSlot, {
12702
+ React41__default.createElement(WidgetSlot, {
12559
12703
  zone: "shell.floating",
12560
12704
  contextOverrides: { matchedPath }
12561
12705
  }),
12562
12706
  // Command palette
12563
- React40__default.createElement(CommandPaletteWrapper, { navigation, routerAdapter })
12707
+ React41__default.createElement(CommandPaletteWrapper, { navigation, routerAdapter })
12564
12708
  )
12565
12709
  );
12566
12710
  if (requireAuth && authAdapter) {
12567
- return React40__default.createElement(
12711
+ return React41__default.createElement(
12568
12712
  ProtectedRoute,
12569
12713
  { onUnauthenticated: () => {
12570
12714
  routerAdapter.navigate("/login");
@@ -12580,7 +12724,7 @@ function createSaasApp(config) {
12580
12724
  const setPlans = useBillingStore((s) => s.setPlans);
12581
12725
  const setFeatures = usePermissionsStore((s) => s.setFeatures);
12582
12726
  const setMode = useThemeStore((s) => s.setMode);
12583
- React40__default.useEffect(() => {
12727
+ React41__default.useEffect(() => {
12584
12728
  if (config.theme) {
12585
12729
  const isSaasTheme = "brand" in config.theme && ("radius" in config.theme || "sidebar" in config.theme || "font" in config.theme);
12586
12730
  const resolved = isSaasTheme ? resolveTheme(config.theme) : config.theme;
@@ -12591,34 +12735,34 @@ function createSaasApp(config) {
12591
12735
  }
12592
12736
  initialize();
12593
12737
  }, [initialize, setOverrides, setMode]);
12594
- React40__default.useEffect(() => {
12738
+ React41__default.useEffect(() => {
12595
12739
  if (config.billing?.plans) {
12596
12740
  setPlans(config.billing.plans.map(normalizePlanConfig));
12597
12741
  }
12598
12742
  }, [setPlans]);
12599
- React40__default.useEffect(() => {
12743
+ React41__default.useEffect(() => {
12600
12744
  if (config.permissions?.features) {
12601
12745
  setFeatures(config.permissions.features);
12602
12746
  }
12603
12747
  }, [setFeatures]);
12604
- React40__default.useEffect(() => {
12748
+ React41__default.useEffect(() => {
12605
12749
  setCurrentLocale(config.locale?.default ?? "en");
12606
12750
  setDefaultCurrency(config.locale?.currency);
12607
12751
  }, []);
12608
- let inner = React40__default.createElement(
12752
+ let inner = React41__default.createElement(
12609
12753
  RouterProvider,
12610
12754
  { value: routerAdapter },
12611
- React40__default.createElement(ToastProvider, null),
12612
- React40__default.createElement(AppContent)
12755
+ React41__default.createElement(ToastProvider, null),
12756
+ React41__default.createElement(AppContent)
12613
12757
  );
12614
12758
  if (orgAdapter) {
12615
- inner = React40__default.createElement(
12759
+ inner = React41__default.createElement(
12616
12760
  OrgAdapterProvider,
12617
12761
  { value: orgAdapter, children: inner }
12618
12762
  );
12619
12763
  }
12620
12764
  if (authAdapter) {
12621
- inner = React40__default.createElement(
12765
+ inner = React41__default.createElement(
12622
12766
  AuthAdapterProvider,
12623
12767
  { value: authAdapter, children: inner }
12624
12768
  );
@@ -12638,7 +12782,7 @@ function createSaasApp(config) {
12638
12782
  mergedTranslations[loc] = layer;
12639
12783
  }
12640
12784
  registerTranslations(mergedTranslations);
12641
- inner = React40__default.createElement(I18nProvider, {
12785
+ inner = React41__default.createElement(I18nProvider, {
12642
12786
  value: {
12643
12787
  defaultLocale: config.locale?.default ?? "en",
12644
12788
  supported: supportedLocales,
@@ -12737,11 +12881,11 @@ function manifestToFayzConfig(manifest, surfaceName) {
12737
12881
  function AdminScaffold({ manifest, surface }) {
12738
12882
  const liveConfigRef = manifest.surfaces[surface]?.options?.[liveConfigOption];
12739
12883
  const liveConfig = typeof liveConfigRef === "string" ? liveSaasConfigs.get(liveConfigRef) : void 0;
12740
- const LegacyApp = React40.useMemo(() => {
12884
+ const LegacyApp = React41.useMemo(() => {
12741
12885
  if (!liveConfig || !hasLegacyShellConfig(liveConfig)) return null;
12742
12886
  return createSaasApp(liveConfig);
12743
12887
  }, [liveConfig]);
12744
- const config = React40.useMemo(() => manifestToFayzConfig(manifest, surface), [manifest, surface]);
12888
+ const config = React41.useMemo(() => manifestToFayzConfig(manifest, surface), [manifest, surface]);
12745
12889
  if (LegacyApp) return /* @__PURE__ */ jsx(LegacyApp, {});
12746
12890
  return /* @__PURE__ */ jsx(AdminProviders, { config, children: /* @__PURE__ */ jsx(
12747
12891
  AdminShell,
@@ -13076,19 +13220,19 @@ function createArchetypeLookup(config) {
13076
13220
  }
13077
13221
  function createPlaceholder(title, description) {
13078
13222
  return function PlaceholderPage() {
13079
- return React40__default.createElement(
13223
+ return React41__default.createElement(
13080
13224
  "div",
13081
13225
  { className: "mx-auto max-w-4xl" },
13082
- React40__default.createElement("h1", { className: "text-3xl font-bold text-foreground" }, title),
13083
- description ? React40__default.createElement("p", { className: "mt-1 text-muted-foreground" }, description) : null,
13084
- React40__default.createElement(
13226
+ React41__default.createElement("h1", { className: "text-3xl font-bold text-foreground" }, title),
13227
+ description ? React41__default.createElement("p", { className: "mt-1 text-muted-foreground" }, description) : null,
13228
+ React41__default.createElement(
13085
13229
  "div",
13086
13230
  { className: "mt-8 flex items-center justify-center rounded-card border border-dashed border-border bg-card p-16" },
13087
- React40__default.createElement(
13231
+ React41__default.createElement(
13088
13232
  "div",
13089
13233
  { className: "text-center" },
13090
- React40__default.createElement("p", { className: "text-lg font-medium text-muted-foreground" }, "Coming Soon"),
13091
- React40__default.createElement(
13234
+ React41__default.createElement("p", { className: "text-lg font-medium text-muted-foreground" }, "Coming Soon"),
13235
+ React41__default.createElement(
13092
13236
  "p",
13093
13237
  { className: "mt-1 text-sm text-muted-foreground/70" },
13094
13238
  "This module is being built as a plugin."
@@ -13235,9 +13379,9 @@ function ModuleActionBar({
13235
13379
  ] });
13236
13380
  }
13237
13381
  function createPluginContext(displayName) {
13238
- const ConfigContext = React40__default.createContext(null);
13239
- const ProviderContext = React40__default.createContext(null);
13240
- const StoreContext = React40__default.createContext(null);
13382
+ const ConfigContext = React41__default.createContext(null);
13383
+ const ProviderContext = React41__default.createContext(null);
13384
+ const StoreContext = React41__default.createContext(null);
13241
13385
  const ContextProvider = ({
13242
13386
  config,
13243
13387
  provider,
@@ -13246,17 +13390,17 @@ function createPluginContext(displayName) {
13246
13390
  }) => /* @__PURE__ */ jsx(ConfigContext.Provider, { value: config, children: /* @__PURE__ */ jsx(ProviderContext.Provider, { value: provider, children: /* @__PURE__ */ jsx(StoreContext.Provider, { value: store, children }) }) });
13247
13391
  ContextProvider.displayName = `${displayName}ContextProvider`;
13248
13392
  function useConfig() {
13249
- const ctx = React40__default.useContext(ConfigContext);
13393
+ const ctx = React41__default.useContext(ConfigContext);
13250
13394
  if (!ctx) throw new Error(`useConfig must be used within ${displayName}`);
13251
13395
  return ctx;
13252
13396
  }
13253
13397
  function useProvider() {
13254
- const ctx = React40__default.useContext(ProviderContext);
13398
+ const ctx = React41__default.useContext(ProviderContext);
13255
13399
  if (!ctx) throw new Error(`useProvider must be used within ${displayName}`);
13256
13400
  return ctx;
13257
13401
  }
13258
13402
  function useStore$1(selector) {
13259
- const store = React40__default.useContext(StoreContext);
13403
+ const store = React41__default.useContext(StoreContext);
13260
13404
  if (!store) throw new Error(`useStore must be used within ${displayName}`);
13261
13405
  return useStore(store, selector);
13262
13406
  }
@@ -13314,7 +13458,7 @@ function PluginRegistryManager({ registries, routeBase, className }) {
13314
13458
  return registries[0]?.id ?? "";
13315
13459
  };
13316
13460
  const [activeTab, setActiveTab] = useState(getTabFromHash);
13317
- React40__default.useEffect(() => {
13461
+ React41__default.useEffect(() => {
13318
13462
  if (!routeBase) return;
13319
13463
  const handler = () => setActiveTab(getTabFromHash());
13320
13464
  window.addEventListener("hashchange", handler);
@@ -13386,6 +13530,16 @@ function useModuleNavigation(hashBase, depthMap, homeView = "summary") {
13386
13530
  const [view, setView] = useState(getInitialView);
13387
13531
  const [direction, setDirection] = useState("forward");
13388
13532
  const prevDepthRef = useRef(getDepth(getInitialView()));
13533
+ const historyRef = useRef([getInitialView()]);
13534
+ const [previousView, setPreviousView] = useState(null);
13535
+ useEffect(() => {
13536
+ const stack = historyRef.current;
13537
+ if (stack[stack.length - 1] !== view) {
13538
+ stack.push(view);
13539
+ if (stack.length > 30) stack.shift();
13540
+ }
13541
+ setPreviousView(stack.length >= 2 ? stack[stack.length - 2] : null);
13542
+ }, [view]);
13389
13543
  useEffect(() => {
13390
13544
  function handler() {
13391
13545
  const hash = (window.location.hash.slice(1) || "/").split("?")[0];
@@ -13431,8 +13585,15 @@ function useModuleNavigation(hashBase, depthMap, homeView = "summary") {
13431
13585
  }
13432
13586
  }
13433
13587
  }, [hashBase]);
13588
+ const back = useCallback((fallbackView) => {
13589
+ if (historyRef.current.length >= 2) {
13590
+ window.history.back();
13591
+ } else {
13592
+ navigate(fallbackView ?? homeView);
13593
+ }
13594
+ }, [navigate, homeView]);
13434
13595
  const animationClass = direction === "forward" ? "saas-nav-forward" : "saas-nav-back";
13435
- return { view, direction, animationClass, navigate };
13596
+ return { view, direction, animationClass, navigate, previousView, back };
13436
13597
  }
13437
13598
  var personLookup = createArchetypeLookup({ archetype: "person" });
13438
13599
  function formatAge(dob) {
@@ -13619,6 +13780,147 @@ function usePluginPrefs(pluginId, defaults) {
13619
13780
  }, [pluginId, defaults]);
13620
13781
  return useMemo(() => ({ get, set, update, getAll, reset }), [get, set, update, getAll, reset]);
13621
13782
  }
13783
+ function ConnectorCard({ connector }) {
13784
+ const { t } = useTranslation();
13785
+ const [status, setStatus] = useState(null);
13786
+ const [open, setOpen] = useState(false);
13787
+ const [values, setValues] = useState({});
13788
+ const [testing, setTesting] = useState(false);
13789
+ const [saving, setSaving] = useState(false);
13790
+ const [oauthing, setOauthing] = useState(false);
13791
+ const loadStatus = useCallback(async () => {
13792
+ try {
13793
+ setStatus(await connector.getStatus());
13794
+ } catch {
13795
+ setStatus({ connected: false });
13796
+ }
13797
+ }, [connector]);
13798
+ useEffect(() => {
13799
+ void loadStatus();
13800
+ }, [loadStatus]);
13801
+ const connected = !!status?.connected;
13802
+ const Icon = connector.icon ? ICON_MAP$1[connector.icon] ?? Plug : Plug;
13803
+ async function handleTest() {
13804
+ if (!connector.testConnection) return;
13805
+ setTesting(true);
13806
+ try {
13807
+ const res = await connector.testConnection(values);
13808
+ res.ok ? toast.success(res.message ?? t("connectors.connected")) : toast.error(res.message ?? t("common.error"));
13809
+ } catch (e) {
13810
+ toast.error(e?.message ?? t("common.error"));
13811
+ } finally {
13812
+ setTesting(false);
13813
+ }
13814
+ }
13815
+ async function handleSave() {
13816
+ if (!connector.saveConnection) return;
13817
+ setSaving(true);
13818
+ try {
13819
+ await connector.saveConnection(values);
13820
+ toast.success(t("connectors.save"));
13821
+ await loadStatus();
13822
+ } catch (e) {
13823
+ toast.error(e?.message ?? t("common.error"));
13824
+ } finally {
13825
+ setSaving(false);
13826
+ }
13827
+ }
13828
+ async function handleConnectOAuth() {
13829
+ if (!connector.startOAuth) return;
13830
+ setOauthing(true);
13831
+ try {
13832
+ window.location.href = await connector.startOAuth(window.location.href);
13833
+ } catch (e) {
13834
+ toast.error(e?.message ?? t("common.error"));
13835
+ setOauthing(false);
13836
+ }
13837
+ }
13838
+ async function handleDisconnect() {
13839
+ if (!connector.disconnect) return;
13840
+ try {
13841
+ await connector.disconnect();
13842
+ toast.success(t("connectors.disconnect"));
13843
+ await loadStatus();
13844
+ } catch (e) {
13845
+ toast.error(e?.message ?? t("common.error"));
13846
+ }
13847
+ }
13848
+ const ExtraPanel = connector.ExtraPanel;
13849
+ return /* @__PURE__ */ jsxs("div", { className: "rounded-lg border bg-card shadow-sm overflow-hidden", children: [
13850
+ /* @__PURE__ */ jsxs(
13851
+ "button",
13852
+ {
13853
+ onClick: () => setOpen((o) => !o),
13854
+ className: "w-full flex items-center gap-3 px-4 py-3 text-left hover:bg-muted/30 transition-colors",
13855
+ children: [
13856
+ /* @__PURE__ */ jsx("div", { className: "flex h-9 w-9 items-center justify-center rounded-lg bg-muted shrink-0", children: /* @__PURE__ */ jsx(Icon, { className: "h-4 w-4 text-muted-foreground" }) }),
13857
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
13858
+ /* @__PURE__ */ jsx("p", { className: "text-sm font-semibold truncate", children: connector.name }),
13859
+ connector.description && /* @__PURE__ */ jsx("p", { className: "text-[11px] text-muted-foreground truncate", children: connector.description })
13860
+ ] }),
13861
+ /* @__PURE__ */ jsx("span", { className: `inline-flex items-center gap-1 text-[11px] shrink-0 ${connected ? "text-success" : "text-muted-foreground"}`, children: connected ? /* @__PURE__ */ jsxs(Fragment, { children: [
13862
+ /* @__PURE__ */ jsx(CheckCircle2, { className: "h-3.5 w-3.5" }),
13863
+ " ",
13864
+ t("connectors.connected")
13865
+ ] }) : t("connectors.notConnected") }),
13866
+ /* @__PURE__ */ jsx(ChevronDown, { className: `h-4 w-4 text-muted-foreground shrink-0 transition-transform ${open ? "rotate-180" : ""}` })
13867
+ ]
13868
+ }
13869
+ ),
13870
+ open && /* @__PURE__ */ jsxs("div", { className: "border-t px-4 py-4 space-y-4", children: [
13871
+ connector.authKind === "oauth" ? /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
13872
+ /* @__PURE__ */ jsxs(Button, { onClick: handleConnectOAuth, disabled: oauthing, children: [
13873
+ oauthing ? /* @__PURE__ */ jsx(Loader2, { className: "h-4 w-4 animate-spin" }) : /* @__PURE__ */ jsx(Plug, { className: "h-4 w-4" }),
13874
+ connected ? t("connectors.connected") : `${t("connectors.connect")} ${connector.name}`
13875
+ ] }),
13876
+ connected && connector.disconnect && /* @__PURE__ */ jsxs(Button, { variant: "outline", size: "sm", onClick: handleDisconnect, children: [
13877
+ /* @__PURE__ */ jsx(Power, { className: "h-3.5 w-3.5" }),
13878
+ " ",
13879
+ t("connectors.disconnect")
13880
+ ] })
13881
+ ] }) : /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
13882
+ /* @__PURE__ */ jsx("div", { className: "grid gap-3 sm:grid-cols-2", children: (connector.fields ?? []).map((f) => /* @__PURE__ */ jsxs("label", { className: "block", children: [
13883
+ /* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-muted-foreground", children: f.label }),
13884
+ /* @__PURE__ */ jsx(
13885
+ "input",
13886
+ {
13887
+ type: f.type,
13888
+ value: values[f.key] ?? "",
13889
+ onChange: (e) => setValues((v) => ({ ...v, [f.key]: e.target.value })),
13890
+ placeholder: f.placeholder,
13891
+ className: "w-full mt-1 rounded-input border border-input bg-card px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ring"
13892
+ }
13893
+ )
13894
+ ] }, f.key)) }),
13895
+ /* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
13896
+ connector.testConnection && /* @__PURE__ */ jsxs(Button, { variant: "outline", size: "sm", onClick: handleTest, disabled: testing, children: [
13897
+ testing ? /* @__PURE__ */ jsx(Loader2, { className: "h-3.5 w-3.5 animate-spin" }) : /* @__PURE__ */ jsx(Plug, { className: "h-3.5 w-3.5" }),
13898
+ " ",
13899
+ t("connectors.test")
13900
+ ] }),
13901
+ connector.saveConnection && /* @__PURE__ */ jsxs(Button, { size: "sm", onClick: handleSave, disabled: saving, children: [
13902
+ saving ? /* @__PURE__ */ jsx(Loader2, { className: "h-3.5 w-3.5 animate-spin" }) : /* @__PURE__ */ jsx(CheckCircle2, { className: "h-3.5 w-3.5" }),
13903
+ " ",
13904
+ t("connectors.save")
13905
+ ] }),
13906
+ connected && connector.disconnect && /* @__PURE__ */ jsxs(Button, { variant: "outline", size: "sm", onClick: handleDisconnect, children: [
13907
+ /* @__PURE__ */ jsx(Power, { className: "h-3.5 w-3.5" }),
13908
+ " ",
13909
+ t("connectors.disconnect")
13910
+ ] })
13911
+ ] })
13912
+ ] }),
13913
+ ExtraPanel && /* @__PURE__ */ jsx("div", { className: "pt-1", children: /* @__PURE__ */ jsx(ExtraPanel, {}) })
13914
+ ] })
13915
+ ] });
13916
+ }
13917
+ function ConnectorsHub({ connectors }) {
13918
+ const { t } = useTranslation();
13919
+ if (!connectors || connectors.length === 0) {
13920
+ return /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground py-8 text-center", children: t("connectors.empty") });
13921
+ }
13922
+ return /* @__PURE__ */ jsx("div", { className: "space-y-3 max-w-3xl", children: connectors.map((c) => /* @__PURE__ */ jsx(ConnectorCard, { connector: c }, c.id)) });
13923
+ }
13622
13924
  function RegistryCrudView2({ registry: registry2, basePath }) {
13623
13925
  const useStore = useMemo(() => {
13624
13926
  const mockData = registry2.mockData ?? registry2.seedData ?? [];
@@ -13636,8 +13938,59 @@ function RegistryCrudView2({ registry: registry2, basePath }) {
13636
13938
  }
13637
13939
  );
13638
13940
  }
13639
- function PluginSettingsPanel({ title, subtitle, generalSettings, registries, customTabs, routeBase, onClose }) {
13941
+ function PropertiesTab({ registries, routeBase, regLabel }) {
13942
+ function getActive() {
13943
+ const hash = window.location.hash.slice(1);
13944
+ const nested = `${routeBase}/_properties/`;
13945
+ if (hash.startsWith(nested)) {
13946
+ const id = hash.slice(nested.length).split("/")[0];
13947
+ if (registries.find((r) => r.id === id)) return id;
13948
+ }
13949
+ const flat = `${routeBase}/`;
13950
+ if (hash.startsWith(flat)) {
13951
+ const id = hash.slice(flat.length).split("/")[0];
13952
+ if (registries.find((r) => r.id === id)) return id;
13953
+ }
13954
+ return registries[0]?.id ?? "";
13955
+ }
13956
+ const [active, setActive] = useState(getActive);
13957
+ React41__default.useEffect(() => {
13958
+ const handler = () => setActive(getActive());
13959
+ window.addEventListener("hashchange", handler);
13960
+ return () => window.removeEventListener("hashchange", handler);
13961
+ }, [routeBase, registries]);
13962
+ function select(id) {
13963
+ setActive(id);
13964
+ window.location.hash = `${routeBase}/_properties/${id}`;
13965
+ }
13966
+ const activeReg = registries.find((r) => r.id === active) ?? registries[0];
13967
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
13968
+ /* @__PURE__ */ jsx("div", { className: "flex gap-1.5 flex-wrap", children: registries.map((r) => {
13969
+ const Icon = r.icon ? ICON_MAP$1[r.icon] ?? null : null;
13970
+ const on = r.id === activeReg?.id;
13971
+ return /* @__PURE__ */ jsxs(
13972
+ "button",
13973
+ {
13974
+ onClick: () => select(r.id),
13975
+ className: cn(
13976
+ "inline-flex items-center gap-1.5 rounded-full px-3 py-1.5 text-xs font-medium transition-colors",
13977
+ on ? "bg-primary text-primary-foreground" : "bg-muted text-muted-foreground hover:bg-muted/70"
13978
+ ),
13979
+ children: [
13980
+ Icon && /* @__PURE__ */ jsx(Icon, { className: "h-3.5 w-3.5" }),
13981
+ regLabel(r.id, r.entity.name)
13982
+ ]
13983
+ },
13984
+ r.id
13985
+ );
13986
+ }) }),
13987
+ activeReg?.description && /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: regLabel(activeReg.id + ".description", activeReg.description) }),
13988
+ activeReg && /* @__PURE__ */ jsx(RegistryCrudView2, { registry: activeReg, basePath: `${routeBase}/_properties/${activeReg.id}` }, activeReg.id)
13989
+ ] });
13990
+ }
13991
+ function PluginSettingsPanel({ title, subtitle, generalSettings, registries, customTabs, routeBase, hostPluginId, onClose }) {
13640
13992
  const { t } = useTranslation();
13993
+ const connectors = useConnectorsForPlugin(hostPluginId ?? "");
13641
13994
  const regLabel = (id, fallback) => {
13642
13995
  const key = `registry.${id}`;
13643
13996
  const translated = t(key);
@@ -13646,20 +13999,21 @@ function PluginSettingsPanel({ title, subtitle, generalSettings, registries, cus
13646
13999
  const tabs = useMemo(() => {
13647
14000
  const list = [];
13648
14001
  if (generalSettings) {
13649
- list.push({ id: "_general", label: t("settings.general"), icon: "Settings", type: "general" });
14002
+ list.push({ id: "_general", label: t("settings.general"), IconComp: Settings, type: "general" });
13650
14003
  }
13651
14004
  if (customTabs) {
13652
14005
  for (const tab of customTabs) {
13653
14006
  list.push({ id: `_custom_${tab.id}`, label: tab.label, icon: tab.icon, type: "custom" });
13654
14007
  }
13655
14008
  }
13656
- if (registries) {
13657
- for (const reg of registries) {
13658
- list.push({ id: reg.id, label: regLabel(reg.id, reg.entity.name), icon: reg.icon, type: "registry", registryDef: reg });
13659
- }
14009
+ if (connectors.length > 0) {
14010
+ list.push({ id: "_integrations", label: t("connectors.title"), IconComp: Plug, type: "integrations" });
14011
+ }
14012
+ if (registries && registries.length > 0) {
14013
+ list.push({ id: "_properties", label: t("settings.properties"), IconComp: TableProperties, type: "properties" });
13660
14014
  }
13661
14015
  return list;
13662
- }, [generalSettings, customTabs, registries, t]);
14016
+ }, [generalSettings, customTabs, connectors.length, registries, t]);
13663
14017
  const useHashRouting = true;
13664
14018
  function getActiveFromHash() {
13665
14019
  const hash = window.location.hash.slice(1) || "/";
@@ -13667,11 +14021,12 @@ function PluginSettingsPanel({ title, subtitle, generalSettings, registries, cus
13667
14021
  const rest = hash.slice(routeBase.length + 1).split("/")[0];
13668
14022
  const match = tabs.find((t2) => t2.id === rest);
13669
14023
  if (match) return match.id;
14024
+ if (registries?.some((r) => r.id === rest) && tabs.some((t2) => t2.id === "_properties")) return "_properties";
13670
14025
  }
13671
14026
  return tabs[0]?.id ?? "";
13672
14027
  }
13673
14028
  const [activeTab, setActiveTab] = useState(getActiveFromHash);
13674
- React40__default.useEffect(() => {
14029
+ React41__default.useEffect(() => {
13675
14030
  const handler = () => setActiveTab(getActiveFromHash());
13676
14031
  window.addEventListener("hashchange", handler);
13677
14032
  return () => window.removeEventListener("hashchange", handler);
@@ -13693,7 +14048,7 @@ function PluginSettingsPanel({ title, subtitle, generalSettings, registries, cus
13693
14048
  ] })
13694
14049
  ] }),
13695
14050
  /* @__PURE__ */ jsx("div", { className: "flex gap-0.5 overflow-x-auto border-b scrollbar-none", style: { scrollbarWidth: "none" }, children: tabs.map((tab) => {
13696
- const Icon = tab.icon ? ICON_MAP$1[tab.icon] ?? null : null;
14051
+ const Icon = tab.IconComp ?? (tab.icon ? ICON_MAP$1[tab.icon] ?? null : null);
13697
14052
  const active = tab.id === activeTab;
13698
14053
  return /* @__PURE__ */ jsxs(
13699
14054
  "button",
@@ -13711,20 +14066,13 @@ function PluginSettingsPanel({ title, subtitle, generalSettings, registries, cus
13711
14066
  tab.id
13712
14067
  );
13713
14068
  }) }),
13714
- activeTabDef?.type === "registry" && activeTabDef.registryDef?.description && /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: regLabel(activeTabDef.id + ".description", activeTabDef.registryDef.description) }),
13715
14069
  activeTabDef?.type === "general" && generalSettings && /* @__PURE__ */ jsx("div", { children: generalSettings }),
13716
14070
  activeTabDef?.type === "custom" && customTabContent && /* @__PURE__ */ jsx("div", { children: customTabContent.content }),
13717
- activeTabDef?.type === "registry" && activeTabDef.registryDef && /* @__PURE__ */ jsx(
13718
- RegistryCrudView2,
13719
- {
13720
- registry: activeTabDef.registryDef,
13721
- basePath: `${routeBase}/${activeTabDef.id}`
13722
- },
13723
- activeTabDef.id
13724
- )
14071
+ activeTabDef?.type === "integrations" && /* @__PURE__ */ jsx(ConnectorsHub, { connectors }),
14072
+ activeTabDef?.type === "properties" && registries && registries.length > 0 && /* @__PURE__ */ jsx(PropertiesTab, { registries, routeBase, regLabel })
13725
14073
  ] });
13726
14074
  }
13727
14075
 
13728
- export { AdminProviders, AdminScaffold, AdminShell, ClientOrdersTab, CrudCardGrid, CrudDetailPage, CrudFormPage, CrudListView, CrudPage, DeleteConfirmDialog, ImportWizard, LoginPage, ModuleActionBar, PermissionGate, PersonLink2 as PersonLink, PluginRegistryManager, PluginSettingsPanel, QuickActionsButton, SelectRow, SettingsGroup, ToggleRow2 as ToggleRow, WidgetSlot, navigateTo as adminNavigateTo, createArchetypeLookup, createClientOrdersProvider, createCrudPage, createFayzApp, createCrudPage as createNativeCrudPage, createPlaceholder, createPluginContext, createSaasApp, createViewRouter, dedup, defineSaas, exportToCSV, formatCurrency4 as formatCurrency, getScheduleBlockConfig, parseViewId, resolveEntityHref, setScheduleBlockConfig, subscribeScheduleBlockConfig, useAdminPath, useFieldRules, useModuleNavigation, usePluginPrefs };
14076
+ export { AdminProviders, AdminScaffold, AdminShell, ClientOrdersTab, ConnectorsHub, CrudCardGrid, CrudDetailPage, CrudFormPage, CrudListView, CrudPage, DeleteConfirmDialog, ImportWizard, LoginPage, ModuleActionBar, PermissionGate, PersonLink2 as PersonLink, PluginRegistryManager, PluginSettingsPanel, QuickActionsButton, SelectRow, SettingsGroup, ToggleRow2 as ToggleRow, WidgetSlot, navigateTo as adminNavigateTo, createArchetypeLookup, createClientOrdersProvider, createCrudPage, createFayzApp, createCrudPage as createNativeCrudPage, createPlaceholder, createPluginContext, createSaasApp, createViewRouter, dedup, defineSaas, exportToCSV, formatCurrency4 as formatCurrency, getScheduleBlockConfig, parseViewId, resolveEntityHref, setScheduleBlockConfig, subscribeScheduleBlockConfig, useAdminPath, useFieldRules, useModuleNavigation, usePluginPrefs };
13729
14077
  //# sourceMappingURL=index.js.map
13730
14078
  //# sourceMappingURL=index.js.map