@fayz-ai/saas 0.6.0 → 0.7.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 (96) hide show
  1. package/README.md +1 -1
  2. package/dist/app/AdminShell.d.ts +14 -1
  3. package/dist/app/AdminShell.d.ts.map +1 -1
  4. package/dist/app/LoginPage.d.ts +2 -16
  5. package/dist/app/LoginPage.d.ts.map +1 -1
  6. package/dist/app/admin-app.d.ts +21 -0
  7. package/dist/app/admin-app.d.ts.map +1 -0
  8. package/dist/app/config.d.ts +40 -3
  9. package/dist/app/config.d.ts.map +1 -1
  10. package/dist/app/scaffold.d.ts +1 -2
  11. package/dist/app/scaffold.d.ts.map +1 -1
  12. package/dist/{chunk-DJW67TCY.js → chunk-DCRT47W4.js} +155 -28
  13. package/dist/chunk-DCRT47W4.js.map +1 -0
  14. package/dist/{chunk-QGMDVLLW.cjs → chunk-SRIUD35R.cjs} +155 -28
  15. package/dist/chunk-SRIUD35R.cjs.map +1 -0
  16. package/dist/crud/CrudDetailPage.d.ts.map +1 -1
  17. package/dist/hooks/useModuleNavigation.d.ts.map +1 -1
  18. package/dist/index.cjs +1131 -5792
  19. package/dist/index.cjs.map +1 -1
  20. package/dist/index.d.ts +4 -3
  21. package/dist/index.d.ts.map +1 -1
  22. package/dist/index.js +844 -5510
  23. package/dist/index.js.map +1 -1
  24. package/dist/org/adapters/supabase.d.ts +18 -0
  25. package/dist/org/adapters/supabase.d.ts.map +1 -1
  26. package/dist/org/context.d.ts.map +1 -1
  27. package/dist/org/index.cjs +11 -10
  28. package/dist/org/index.js +2 -1
  29. package/dist/shell/components/crud/archetypes/ClientOrdersTab.d.ts +4 -2
  30. package/dist/shell/components/crud/archetypes/ClientOrdersTab.d.ts.map +1 -1
  31. package/dist/shell/components/layout/BottomNav.d.ts.map +1 -1
  32. package/dist/shell/components/layout/CommandPalette.d.ts.map +1 -1
  33. package/dist/shell/components/organization/InviteMemberDialog.d.ts.map +1 -1
  34. package/dist/shell/components/organization/PermissionMatrixEditor.d.ts +6 -1
  35. package/dist/shell/components/organization/PermissionMatrixEditor.d.ts.map +1 -1
  36. package/dist/shell/components/organization/PermissionProfilesTab.d.ts.map +1 -1
  37. package/dist/shell/components/organization/TeamTab.d.ts.map +1 -1
  38. package/dist/shell/components/plugins/PluginSettingsPanel.d.ts.map +1 -1
  39. package/dist/shell/components/settings/ConnectedUserProfile.d.ts +2 -1
  40. package/dist/shell/components/settings/ConnectedUserProfile.d.ts.map +1 -1
  41. package/dist/shell/components/settings/SettingsPage.d.ts +8 -1
  42. package/dist/shell/components/settings/SettingsPage.d.ts.map +1 -1
  43. package/dist/shell/config/theme/tokens.d.ts +12 -1
  44. package/dist/shell/config/theme/tokens.d.ts.map +1 -1
  45. package/dist/shell/config/theme/utils.d.ts +8 -0
  46. package/dist/shell/config/theme/utils.d.ts.map +1 -1
  47. package/dist/shell/hooks/useModuleNavigation.d.ts.map +1 -1
  48. package/dist/shell/lib/i18n.d.ts.map +1 -1
  49. package/dist/shell/types/client-orders.d.ts +4 -0
  50. package/dist/shell/types/client-orders.d.ts.map +1 -1
  51. package/dist/shell/types/crud.d.ts +4 -0
  52. package/dist/shell/types/crud.d.ts.map +1 -1
  53. package/package.json +6 -5
  54. package/src/app/AdminShell.tsx +103 -35
  55. package/src/app/LoginPage.tsx +2 -264
  56. package/src/app/{createFayzApp.tsx → admin-app.tsx} +98 -40
  57. package/src/app/config.ts +44 -2
  58. package/src/app/scaffold.tsx +13 -26
  59. package/src/crud/CrudDetailPage.tsx +43 -9
  60. package/src/crud/CrudPage.tsx +1 -1
  61. package/src/hooks/useModuleNavigation.ts +7 -1
  62. package/src/index.ts +12 -5
  63. package/src/org/adapters/supabase.ts +176 -28
  64. package/src/org/context.tsx +33 -2
  65. package/src/shell/components/crud/archetypes/ClientOrdersTab.tsx +12 -4
  66. package/src/shell/components/layout/BottomNav.tsx +39 -4
  67. package/src/shell/components/layout/CommandPalette.tsx +14 -10
  68. package/src/shell/components/organization/InviteMemberDialog.tsx +14 -3
  69. package/src/shell/components/organization/PermissionMatrixEditor.tsx +48 -10
  70. package/src/shell/components/organization/PermissionProfilesTab.tsx +65 -23
  71. package/src/shell/components/organization/TeamTab.tsx +36 -9
  72. package/src/shell/components/plugins/PluginSettingsPanel.tsx +5 -3
  73. package/src/shell/components/settings/ConnectedUserProfile.tsx +68 -25
  74. package/src/shell/components/settings/SettingsPage.tsx +12 -3
  75. package/src/shell/config/theme/tokens.ts +13 -1
  76. package/src/shell/config/theme/utils.ts +76 -15
  77. package/src/shell/hooks/useModuleNavigation.ts +7 -1
  78. package/src/shell/lib/i18n.ts +4 -0
  79. package/src/shell/types/client-orders.ts +5 -0
  80. package/src/shell/types/crud.ts +4 -0
  81. package/dist/app/createFayzApp.d.ts +0 -11
  82. package/dist/app/createFayzApp.d.ts.map +0 -1
  83. package/dist/chunk-DJW67TCY.js.map +0 -1
  84. package/dist/chunk-QGMDVLLW.cjs.map +0 -1
  85. package/dist/shell/components/organization/ImpersonationBanner.d.ts +0 -3
  86. package/dist/shell/components/organization/ImpersonationBanner.d.ts.map +0 -1
  87. package/dist/shell/components/settings/ConnectedHolidaysSettings.d.ts +0 -3
  88. package/dist/shell/components/settings/ConnectedHolidaysSettings.d.ts.map +0 -1
  89. package/dist/shell/components/settings/ConnectedLocationsOverview.d.ts +0 -3
  90. package/dist/shell/components/settings/ConnectedLocationsOverview.d.ts.map +0 -1
  91. package/dist/shell/createSaasApp.d.ts +0 -145
  92. package/dist/shell/createSaasApp.d.ts.map +0 -1
  93. package/src/shell/components/organization/ImpersonationBanner.tsx +0 -29
  94. package/src/shell/components/settings/ConnectedHolidaysSettings.tsx +0 -99
  95. package/src/shell/components/settings/ConnectedLocationsOverview.tsx +0 -57
  96. package/src/shell/createSaasApp.tsx +0 -943
package/README.md CHANGED
@@ -10,7 +10,7 @@ This is where the Fayz pitch becomes a product. `@fayz-ai/saas` is the multi-ten
10
10
  A salon and a clinic ship from the same engine — `@fayz-ai/saas` is the part that makes each one a real, isolated, billable tenant. Plugins snap in; this layer gives them an org to live in, a user to act as, and a shell to render through.
11
11
 
12
12
  ## What's inside
13
- - **App factory** — `defineSaas`, `createSaasApp`, `createFayzApp`, `AdminScaffold`, `AdminShell`, `LoginPage` (manifest-first admin entry)
13
+ - **App factory** — `defineSaas`, `AdminScaffold`, `AdminShell`, `LoginPage` (manifest-first admin entry)
14
14
  - **CRUD engine** — `CrudPage`, `CrudListView`, `CrudFormPage`, `CrudDetailPage`, `CrudCardGrid`, `createCrudPage`, `ImportWizard`, `exportToCSV`
15
15
  - **Org / multi-tenancy** — `createSupabaseOrgAdapter`, `createMockOrgAdapter`, `OrgProvider`, `useTenant`, `useOrganizationStore`
16
16
  - **Permissions** — `PermissionsProvider`, `PermissionGate`, `usePermission`, `useHasPermission`, `usePermissions`
@@ -1,5 +1,5 @@
1
1
  import * as React from 'react';
2
- import { type ModuleNavVariant } from '@fayz-ai/ui';
2
+ import { type BottomNavItem, type MobileHeaderVariant, type ModuleNavVariant } from '@fayz-ai/ui';
3
3
  import type { CustomPage } from './config';
4
4
  import type { AuthProvider } from '@fayz-ai/core';
5
5
  export interface AdminShellProps {
@@ -16,12 +16,25 @@ export interface AdminShellProps {
16
16
  oauthProviders?: Exclude<AuthProvider, 'email'>[];
17
17
  showSettings?: boolean;
18
18
  showOrgSettings?: boolean;
19
+ /** Show the workspace/org switcher at the sidebar top. Default: true.
20
+ * Single-org apps (config.org.multiOrg === false) get false from the
21
+ * scaffold so B2C users never see an organization concept. */
22
+ showOrgSwitcher?: boolean;
23
+ /** Show the branding ("Identidade Visual") + company "Geral" settings tabs.
24
+ * Default: true. B2C apps pass false to hide org-identity settings. */
25
+ showBranding?: boolean;
19
26
  /** Wrap the main content in an inset "framed" card (default: true). The
20
27
  * sidebar is always flush/full-height. */
21
28
  contentFrame?: boolean;
22
29
  /** How module-internal navigation renders. Defaults to 'tabs' for the
23
30
  * 'sidebar' layout and 'rail' for 'topbar'. */
24
31
  moduleNav?: ModuleNavVariant;
32
+ /** Mobile bottom tab bar, passed straight through to AppShell. */
33
+ bottomNav?: BottomNavItem[];
34
+ /** Fired when a bottom-nav `action` item (e.g. the center "+") is tapped. */
35
+ onBottomNavAction?: (id: string) => void;
36
+ /** Mobile header treatment (<md), passed straight through to AppShell. */
37
+ mobileHeader?: MobileHeaderVariant;
25
38
  }
26
39
  /** The shell + an auth gate in front of it. */
27
40
  export declare function AdminShell(props: AdminShellProps): React.JSX.Element;
@@ -1 +1 @@
1
- {"version":3,"file":"AdminShell.d.ts","sourceRoot":"","sources":["../../src/app/AdminShell.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAC9B,OAAO,EAKL,KAAK,gBAAgB,EAUtB,MAAM,aAAa,CAAA;AAOpB,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,UAAU,CAAA;AAC1C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA;AAmBjD,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,MAAM,CAAA;IACf,MAAM,CAAC,EAAE,SAAS,GAAG,QAAQ,GAAG,SAAS,CAAA;IACzC,IAAI,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IACtB,KAAK,CAAC,EAAE,UAAU,EAAE,CAAA;IACpB,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,SAAS,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IAC3B,WAAW,CAAC,EAAE,OAAO,GAAG,UAAU,CAAA;IAClC,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,cAAc,CAAC,EAAE,OAAO,CAAC,YAAY,EAAE,OAAO,CAAC,EAAE,CAAA;IACjD,YAAY,CAAC,EAAE,OAAO,CAAA;IACtB,eAAe,CAAC,EAAE,OAAO,CAAA;IACzB;+CAC2C;IAC3C,YAAY,CAAC,EAAE,OAAO,CAAA;IACtB;oDACgD;IAChD,SAAS,CAAC,EAAE,gBAAgB,CAAA;CAC7B;AAoWD,+CAA+C;AAC/C,wBAAgB,UAAU,CAAC,KAAK,EAAE,eAAe,qBA2BhD;yBA3Be,UAAU"}
1
+ {"version":3,"file":"AdminShell.d.ts","sourceRoot":"","sources":["../../src/app/AdminShell.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAC9B,OAAO,EAGL,KAAK,aAAa,EAClB,KAAK,mBAAmB,EAGxB,KAAK,gBAAgB,EAWtB,MAAM,aAAa,CAAA;AAQpB,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,UAAU,CAAA;AAC1C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA;AAmBjD,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,MAAM,CAAA;IACf,MAAM,CAAC,EAAE,SAAS,GAAG,QAAQ,GAAG,SAAS,CAAA;IACzC,IAAI,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IACtB,KAAK,CAAC,EAAE,UAAU,EAAE,CAAA;IACpB,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,SAAS,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IAC3B,WAAW,CAAC,EAAE,OAAO,GAAG,UAAU,CAAA;IAClC,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,cAAc,CAAC,EAAE,OAAO,CAAC,YAAY,EAAE,OAAO,CAAC,EAAE,CAAA;IACjD,YAAY,CAAC,EAAE,OAAO,CAAA;IACtB,eAAe,CAAC,EAAE,OAAO,CAAA;IACzB;;mEAE+D;IAC/D,eAAe,CAAC,EAAE,OAAO,CAAA;IACzB;4EACwE;IACxE,YAAY,CAAC,EAAE,OAAO,CAAA;IACtB;+CAC2C;IAC3C,YAAY,CAAC,EAAE,OAAO,CAAA;IACtB;oDACgD;IAChD,SAAS,CAAC,EAAE,gBAAgB,CAAA;IAC5B,kEAAkE;IAClE,SAAS,CAAC,EAAE,aAAa,EAAE,CAAA;IAC3B,6EAA6E;IAC7E,iBAAiB,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAA;IACxC,0EAA0E;IAC1E,YAAY,CAAC,EAAE,mBAAmB,CAAA;CACnC;AAkaD,+CAA+C;AAC/C,wBAAgB,UAAU,CAAC,KAAK,EAAE,eAAe,qBAgBhD;yBAhBe,UAAU"}
@@ -1,17 +1,3 @@
1
- import * as React from 'react';
2
- import type { AuthProvider } from '@fayz-ai/core';
3
- export interface LoginPageProps {
4
- appName?: string;
5
- logo?: React.ReactNode;
6
- loginLogo?: React.ReactNode;
7
- layout?: 'split' | 'centered';
8
- tagline?: string;
9
- description?: string;
10
- showOAuth?: boolean;
11
- oauthProviders?: Exclude<AuthProvider, 'email'>[];
12
- }
13
- export declare function LoginPage({ appName, logo, loginLogo, layout, tagline, description, showOAuth, oauthProviders, }: LoginPageProps): React.JSX.Element;
14
- export declare namespace LoginPage {
15
- var displayName: string;
16
- }
1
+ export { LoginPage } from '@fayz-ai/plugin-auth';
2
+ export type { LoginPageProps } from '@fayz-ai/plugin-auth';
17
3
  //# sourceMappingURL=LoginPage.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"LoginPage.d.ts","sourceRoot":"","sources":["../../src/app/LoginPage.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAI9B,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA;AAEjD,MAAM,WAAW,cAAc;IAC7B,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,IAAI,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IACtB,SAAS,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IAC3B,MAAM,CAAC,EAAE,OAAO,GAAG,UAAU,CAAA;IAC7B,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,cAAc,CAAC,EAAE,OAAO,CAAC,YAAY,EAAE,OAAO,CAAC,EAAE,CAAA;CAClD;AAQD,wBAAgB,SAAS,CAAC,EACxB,OAAgB,EAChB,IAAI,EACJ,SAAS,EACT,MAAgB,EAChB,OAAO,EACP,WAAW,EACX,SAAiB,EACjB,cAA2B,GAC5B,EAAE,cAAc,qBAsOhB;yBA/Oe,SAAS"}
1
+ {"version":3,"file":"LoginPage.d.ts","sourceRoot":"","sources":["../../src/app/LoginPage.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAA;AAChD,YAAY,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAA"}
@@ -0,0 +1,21 @@
1
+ import * as React from 'react';
2
+ import { type AuthLayout } from '@fayz-ai/plugin-auth';
3
+ import type { FayzAppConfig } from './config';
4
+ export declare function getAuthShellProps(auth: FayzAppConfig['auth']): {
5
+ requireAuth?: boolean;
6
+ loginTagline?: string;
7
+ loginDescription?: string;
8
+ loginLogo?: React.ReactNode;
9
+ loginLayout?: AuthLayout;
10
+ showOAuth?: boolean;
11
+ oauthProviders?: Exclude<import('@fayz-ai/core').AuthProvider, 'email'>[];
12
+ };
13
+ export declare function AdminProviders({ config, children }: {
14
+ config: FayzAppConfig;
15
+ children: React.ReactNode;
16
+ }): React.JSX.Element;
17
+ export declare namespace AdminProviders {
18
+ var displayName: string;
19
+ }
20
+ export declare function createFayzApp(config: FayzAppConfig): React.ComponentType;
21
+ //# sourceMappingURL=admin-app.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"admin-app.d.ts","sourceRoot":"","sources":["../../src/app/admin-app.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAU9B,OAAO,EAGL,KAAK,UAAU,EAGhB,MAAM,sBAAsB,CAAA;AAiB7B,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,UAAU,CAAA;AAuC7C,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,aAAa,CAAC,MAAM,CAAC,GAAG;IAC9D,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,SAAS,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IAC3B,WAAW,CAAC,EAAE,UAAU,CAAA;IACxB,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,cAAc,CAAC,EAAE,OAAO,CAAC,OAAO,eAAe,EAAE,YAAY,EAAE,OAAO,CAAC,EAAE,CAAA;CAC1E,CAsBA;AAoGD,wBAAgB,cAAc,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,EAAE;IAAE,MAAM,EAAE,aAAa,CAAC;IAAC,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAA;CAAE,qBAwExG;yBAxEe,cAAc;;;AAoF9B,wBAAgB,aAAa,CAAC,MAAM,EAAE,aAAa,GAAG,KAAK,CAAC,aAAa,CAyBxE"}
@@ -1,5 +1,7 @@
1
1
  import type { ComponentType, ReactNode } from 'react';
2
- import type { AuthAdapter, AuthProvider, OrgAdapter, PluginManifest, ThemeMode, LocaleConfig, PermissionsConfig } from '@fayz-ai/core';
2
+ import type { AuthProvider, OrgAdapter, PluginManifest, ThemeMode, LocaleConfig, PermissionsConfig } from '@fayz-ai/core';
3
+ import type { AuthPluginOptions, ResolvedAuthPlugin } from '@fayz-ai/plugin-auth';
4
+ import type { BottomNavItem, MobileHeaderVariant } from '@fayz-ai/ui';
3
5
  import type { SaasTheme } from '../shell/config/theme/tokens';
4
6
  import type { CreateThemeOptions } from '../shell/config/theme/utils';
5
7
  import type { PlanConfig } from '../shell/types/billing';
@@ -19,13 +21,28 @@ export interface CustomPage {
19
21
  feature: string;
20
22
  action: 'read' | 'create' | 'edit' | 'delete';
21
23
  };
24
+ /** Show this page in the sidebar/topbar navigation. Default: true. Set `false`
25
+ * for mobile-only pages reachable via bottomNav/avatar (e.g. a "Mais" overflow
26
+ * hub or a "Perfil" avatar target): the route still works, but the entry is
27
+ * hidden from desktop navigation where those mobile constructs are meaningless. */
28
+ nav?: boolean;
22
29
  children?: CustomPage[];
23
30
  }
24
31
  export interface AuthConfig {
25
32
  /** 'supabase' | 'mock' or bring your own AuthAdapter */
26
- adapter?: 'supabase' | 'mock' | AuthAdapter;
33
+ adapter?: AuthPluginOptions['adapter'];
34
+ /** New plugin-auth provider selector. Existing apps may keep using adapter. */
35
+ provider?: AuthPluginOptions['provider'];
27
36
  /** Redirect to login when no session is found (default: true) */
28
37
  requireAuth?: boolean;
38
+ /** New plugin-auth route and provider options. */
39
+ routes?: AuthPluginOptions['routes'];
40
+ supabase?: AuthPluginOptions['supabase'];
41
+ oauth?: AuthPluginOptions['oauth'];
42
+ layout?: AuthPluginOptions['layout'];
43
+ logo?: ReactNode;
44
+ tagline?: string;
45
+ description?: string;
29
46
  loginLogo?: ReactNode;
30
47
  loginLayout?: 'split' | 'centered';
31
48
  loginTagline?: string;
@@ -33,6 +50,7 @@ export interface AuthConfig {
33
50
  showOAuth?: boolean;
34
51
  oauthProviders?: Exclude<AuthProvider, 'email'>[];
35
52
  }
53
+ export type AppAuthConfig = AuthConfig | ResolvedAuthPlugin;
36
54
  export interface OrgConfig {
37
55
  /** 'supabase' | 'mock' or bring your own OrgAdapter */
38
56
  adapter?: 'supabase' | 'mock' | OrgAdapter;
@@ -58,7 +76,7 @@ export interface FayzAppConfig {
58
76
  logo?: string | ReactNode;
59
77
  supabaseUrl?: string;
60
78
  supabaseAnonKey?: string;
61
- auth?: AuthConfig;
79
+ auth?: AppAuthConfig;
62
80
  org?: OrgConfig;
63
81
  plugins?: PluginManifest[];
64
82
  pages?: CustomPage[];
@@ -66,6 +84,17 @@ export interface FayzAppConfig {
66
84
  defaultThemeMode?: ThemeMode;
67
85
  /** Shell layout variant (default: 'sidebar') */
68
86
  layout?: 'sidebar' | 'topbar' | 'minimal';
87
+ /** Mobile bottom tab bar. Rendered by AppShell on small screens (md:hidden)
88
+ * for every layout variant. Route items map a lucide icon name to a route; an
89
+ * `{ kind: 'action', id, icon }` item renders as a raised center button
90
+ * (Mobills style) that fires `onBottomNavAction(id)` instead of navigating. */
91
+ bottomNav?: BottomNavItem[];
92
+ /** Fired when a bottom-nav `action` item (e.g. the center "+") is tapped. */
93
+ onBottomNavAction?: (id: string) => void;
94
+ /** Mobile header treatment (<md), default 'minimal'. 'transparent' renders no
95
+ * header bar (edge-to-edge content) with a floating profile avatar top-right;
96
+ * 'hidden' renders neither. Desktop keeps its sidebar + user menu. */
97
+ mobileHeader?: MobileHeaderVariant;
69
98
  /** Wrap the main content in an inset "framed" card (default: true). The
70
99
  * sidebar itself is always flush/full-height. */
71
100
  contentFrame?: boolean;
@@ -76,6 +105,14 @@ export interface FayzAppConfig {
76
105
  * view changes). Default: 'slide'. Set per repo/app for a different feel. */
77
106
  navTransition?: 'slide' | 'fade' | 'none';
78
107
  locale?: LocaleConfig;
108
+ /** Show org-level settings tabs (Equipe/Permissões/Localizações/Regras).
109
+ * Defaults to `Boolean(org)` for back-compat. B2C/personal apps that still
110
+ * need an org for the workspace can set `false` to hide these ERP tabs. */
111
+ orgSettings?: boolean;
112
+ /** Show the branding ("Identidade Visual") + company "Geral" settings tabs.
113
+ * Default: `true`. B2C apps set `false` to drop org-identity settings, leaving
114
+ * Perfil + Segurança + plugin settings only. */
115
+ branding?: boolean;
79
116
  permissions?: PermissionsConfig;
80
117
  billing?: FayzBillingConfig;
81
118
  chat?: ChatConfig;
@@ -1 +1 @@
1
- {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/app/config.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AACrD,OAAO,KAAK,EACV,WAAW,EACX,YAAY,EACZ,UAAU,EACV,cAAc,EACd,SAAS,EACT,YAAY,EACZ,iBAAiB,EAClB,MAAM,eAAe,CAAA;AACtB,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,8BAA8B,CAAA;AAC7D,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,6BAA6B,CAAA;AACrE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAA;AAMxD,MAAM,MAAM,WAAW,GAAG,MAAM,GAAG,WAAW,GAAG,UAAU,CAAA;AAE3D,MAAM,WAAW,UAAU;IACzB,kCAAkC;IAClC,IAAI,EAAE,MAAM,CAAA;IACZ,kCAAkC;IAClC,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,gDAAgD;IAChD,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,SAAS,CAAC,EAAE,aAAa,CAAA;IACzB,OAAO,CAAC,EAAE,WAAW,CAAA;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;IACvB,UAAU,CAAC,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,GAAG,QAAQ,GAAG,MAAM,GAAG,QAAQ,CAAA;KAAE,CAAA;IAC/E,QAAQ,CAAC,EAAE,UAAU,EAAE,CAAA;CACxB;AAMD,MAAM,WAAW,UAAU;IACzB,wDAAwD;IACxD,OAAO,CAAC,EAAE,UAAU,GAAG,MAAM,GAAG,WAAW,CAAA;IAC3C,iEAAiE;IACjE,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB,SAAS,CAAC,EAAE,SAAS,CAAA;IACrB,WAAW,CAAC,EAAE,OAAO,GAAG,UAAU,CAAA;IAClC,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,cAAc,CAAC,EAAE,OAAO,CAAC,YAAY,EAAE,OAAO,CAAC,EAAE,CAAA;CAClD;AAED,MAAM,WAAW,SAAS;IACxB,uDAAuD;IACvD,OAAO,CAAC,EAAE,UAAU,GAAG,MAAM,GAAG,UAAU,CAAA;IAC1C,qDAAqD;IACrD,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,gEAAgE;IAChE,UAAU,CAAC,EAAE,OAAO,CAAA;CACrB;AAED,MAAM,WAAW,UAAU;IACzB,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,WAAW,CAAC,EAAE,MAAM,CAAA;CACrB;AAED,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,UAAU,EAAE,CAAA;IACnB,oBAAoB,CAAC,EAAE,MAAM,CAAA;IAC7B,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAMD,MAAM,WAAW,aAAa;IAC5B,+BAA+B;IAC/B,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IAKzB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,eAAe,CAAC,EAAE,MAAM,CAAA;IAKxB,IAAI,CAAC,EAAE,UAAU,CAAA;IAKjB,GAAG,CAAC,EAAE,SAAS,CAAA;IAKf,OAAO,CAAC,EAAE,cAAc,EAAE,CAAA;IAK1B,KAAK,CAAC,EAAE,UAAU,EAAE,CAAA;IAKpB,KAAK,CAAC,EAAE,kBAAkB,GAAG,SAAS,CAAA;IACtC,gBAAgB,CAAC,EAAE,SAAS,CAAA;IAC5B,gDAAgD;IAChD,MAAM,CAAC,EAAE,SAAS,GAAG,QAAQ,GAAG,SAAS,CAAA;IACzC;sDACkD;IAClD,YAAY,CAAC,EAAE,OAAO,CAAA;IACtB;oDACgD;IAChD,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;IAC3B;kFAC8E;IAC9E,aAAa,CAAC,EAAE,OAAO,GAAG,MAAM,GAAG,MAAM,CAAA;IAKzC,MAAM,CAAC,EAAE,YAAY,CAAA;IAKrB,WAAW,CAAC,EAAE,iBAAiB,CAAA;IAK/B,OAAO,CAAC,EAAE,iBAAiB,CAAA;IAK3B,IAAI,CAAC,EAAE,UAAU,CAAA;CAClB"}
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/app/config.ts"],"names":[],"mappings":"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"}
@@ -1,12 +1,11 @@
1
1
  import * as React from 'react';
2
2
  import type { AppManifest } from '@fayz-ai/core';
3
3
  import type { FayzAppConfig } from './config';
4
- import { type SaasAppConfig } from '../shell/createSaasApp';
5
4
  /** Fayz/Saas config → AppManifest (sugar → data). During the Beauty/Resto
6
5
  * migration, config-authored apps keep a live config ref so renderApp can
7
6
  * preserve code-backed plugins and custom pages while the JSON manifest path
8
7
  * matures. Serialized manifests still resolve through plugin/page registries. */
9
- export declare function defineSaas(config: FayzAppConfig | SaasAppConfig): AppManifest;
8
+ export declare function defineSaas(config: FayzAppConfig): AppManifest;
10
9
  export declare function AdminScaffold({ manifest, surface }: {
11
10
  manifest: AppManifest;
12
11
  surface: string;
@@ -1 +1 @@
1
- {"version":3,"file":"scaffold.d.ts","sourceRoot":"","sources":["../../src/app/scaffold.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAO9B,OAAO,KAAK,EACV,WAAW,EAKZ,MAAM,eAAe,CAAA;AAGtB,OAAO,KAAK,EAAE,aAAa,EAAc,MAAM,UAAU,CAAA;AACzD,OAAO,EAAiB,KAAK,aAAa,EAAE,MAAM,wBAAwB,CAAA;AA+B1E;;;iFAGiF;AACjF,wBAAgB,UAAU,CAAC,MAAM,EAAE,aAAa,GAAG,aAAa,GAAG,WAAW,CA0B7E;AA6DD,wBAAgB,aAAa,CAAC,EAAE,QAAQ,EAAE,OAAO,EAAE,EAAE;IAAE,QAAQ,EAAE,WAAW,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,qBA8B9F;yBA9Be,aAAa"}
1
+ {"version":3,"file":"scaffold.d.ts","sourceRoot":"","sources":["../../src/app/scaffold.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAO9B,OAAO,KAAK,EACV,WAAW,EAKZ,MAAM,eAAe,CAAA;AAGtB,OAAO,KAAK,EAAE,aAAa,EAAc,MAAM,UAAU,CAAA;AA2BzD;;;iFAGiF;AACjF,wBAAgB,UAAU,CAAC,MAAM,EAAE,aAAa,GAAG,WAAW,CA0B7D;AA6DD,wBAAgB,aAAa,CAAC,EAAE,QAAQ,EAAE,OAAO,EAAE,EAAE;IAAE,QAAQ,EAAE,WAAW,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,qBAsB9F;yBAtBe,aAAa"}
@@ -1,3 +1,4 @@
1
+ import { usePermissionsStore } from './chunk-XNWCGJYH.js';
1
2
  import { createClient } from '@supabase/supabase-js';
2
3
  import { setActiveTenantId, setGlobalSupabaseClient } from '@fayz-ai/core';
3
4
  import { create } from 'zustand';
@@ -5,7 +6,6 @@ import * as React from 'react';
5
6
  import { useAuthStore } from '@fayz-ai/auth';
6
7
  import { jsx } from 'react/jsx-runtime';
7
8
 
8
- // src/supabase/client.ts
9
9
  var CORE_SCHEMA = "saas_core";
10
10
  var _client = null;
11
11
  function createFayzSupabaseClient(url, anonKey) {
@@ -80,7 +80,8 @@ function mapMemberRow(row) {
80
80
  fullName: profile["full_name"] ?? "",
81
81
  avatarUrl: profile["avatar_url"] ?? void 0
82
82
  },
83
- joinedAt: row["joined_at"]
83
+ // tenant_members has no `joined_at` column — it's `created_at`.
84
+ joinedAt: row["joined_at"] ?? row["created_at"]
84
85
  };
85
86
  }
86
87
  function mapLocationRow(row) {
@@ -113,18 +114,46 @@ function mapInviteRow(row) {
113
114
  expiresAt: row["expires_at"]
114
115
  };
115
116
  }
116
- var SYSTEM_ROLES = ["owner", "admin", "manager", "staff", "viewer"];
117
- function buildPermissionProfiles(permissions, rolePerms, overrides) {
117
+ var DB_ACTION = {
118
+ read: "read",
119
+ create: "create",
120
+ edit: "update",
121
+ write: "create",
122
+ manage: "manage",
123
+ delete: "delete"
124
+ };
125
+ function slugifyRole(name) {
126
+ return name.toLowerCase().normalize("NFD").replace(/[\u0300-\u036f]/g, "").replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "") || "role";
127
+ }
128
+ var DEFAULT_ROLES = [
129
+ { id: "owner", name: "Owner" },
130
+ { id: "admin", name: "Admin" },
131
+ { id: "manager", name: "Manager" },
132
+ { id: "staff", name: "Staff" },
133
+ { id: "viewer", name: "Viewer" }
134
+ ];
135
+ var SYSTEM_PERMISSION_MAP = {
136
+ "team.read": "manage_team",
137
+ "team.invite": "manage_team",
138
+ "team.manage": "manage_team",
139
+ "team.manage_roles": "manage_permissions",
140
+ "billing.read": "manage_billing",
141
+ "billing.manage": "manage_billing",
142
+ "settings.read": "manage_settings",
143
+ "settings.update": "manage_settings"
144
+ };
145
+ var ALL_SYSTEM_PERMISSIONS = ["manage_team", "manage_billing", "manage_settings", "manage_permissions"];
146
+ function buildPermissionProfiles(roles, permissions, rolePerms, overrides) {
118
147
  const overrideMap = /* @__PURE__ */ new Map();
119
148
  for (const o of overrides) {
120
149
  overrideMap.set(`${o.role}:${o.permission_id}`, o.granted);
121
150
  }
122
151
  const rolePermMap = /* @__PURE__ */ new Map();
123
- for (const role of SYSTEM_ROLES) {
124
- rolePermMap.set(role, /* @__PURE__ */ new Set());
152
+ for (const r of roles) {
153
+ rolePermMap.set(r.id, /* @__PURE__ */ new Set());
125
154
  }
126
- for (const p of permissions) {
127
- rolePermMap.get("owner").add(p.id);
155
+ if (rolePermMap.has("owner")) {
156
+ for (const p of permissions) rolePermMap.get("owner").add(p.id);
128
157
  }
129
158
  for (const rp of rolePerms) {
130
159
  rolePermMap.get(rp.role)?.add(rp.permission_id);
@@ -143,14 +172,14 @@ function buildPermissionProfiles(permissions, rolePerms, overrides) {
143
172
  }
144
173
  const actionMap = {
145
174
  read: "read",
146
- create: "write",
147
- update: "write",
148
- manage: "manage",
175
+ create: "create",
176
+ update: "edit",
177
+ manage: "edit",
149
178
  delete: "delete",
150
- configure: "manage",
151
- invite: "write"
179
+ configure: "edit",
180
+ invite: "create"
152
181
  };
153
- return SYSTEM_ROLES.map((role) => {
182
+ return roles.map(({ id: role, name, description, isSystem }) => {
154
183
  const permIds = rolePermMap.get(role);
155
184
  const features = {};
156
185
  for (const permId of permIds) {
@@ -165,16 +194,26 @@ function buildPermissionProfiles(permissions, rolePerms, overrides) {
165
194
  features[category].push(mappedAction);
166
195
  }
167
196
  }
197
+ const systemPermissions = [];
198
+ const source = role === "owner" ? ALL_SYSTEM_PERMISSIONS : [...permIds].map((id) => SYSTEM_PERMISSION_MAP[id]);
199
+ for (const sp of source) {
200
+ if (sp && !systemPermissions.includes(sp)) systemPermissions.push(sp);
201
+ }
168
202
  return {
169
203
  id: role,
170
- name: capitalize(role),
171
- isSystem: true,
204
+ name: name || capitalize(role),
205
+ description,
206
+ isSystem: isSystem ?? true,
207
+ systemPermissions,
172
208
  grants: features
173
209
  };
174
210
  });
175
211
  }
176
212
  function createSupabaseOrgAdapter(config) {
177
213
  const schema = config?.coreSchema ?? CORE_SCHEMA;
214
+ const roles = config?.roles && config.roles.length > 0 ? config.roles : DEFAULT_ROLES;
215
+ const authAdapter = config?.authAdapter;
216
+ const inviteRedirectTo = typeof window !== "undefined" ? `${window.location.origin}/` : void 0;
178
217
  function supabase() {
179
218
  return getFayzSupabaseClient();
180
219
  }
@@ -253,24 +292,64 @@ function createSupabaseOrgAdapter(config) {
253
292
  if (error) throw error;
254
293
  },
255
294
  async listProfiles(orgId) {
256
- const [permsResult, rolePermsResult, overridesResult] = await Promise.all([
295
+ const [permsResult, rolePermsResult, overridesResult, customResult] = await Promise.all([
257
296
  core().from("permissions").select("id, category"),
258
297
  core().from("role_permissions").select("role, permission_id"),
259
- core().from("tenant_role_overrides").select("role, permission_id, granted").eq("tenant_id", orgId)
298
+ core().from("tenant_role_overrides").select("role, permission_id, granted").eq("tenant_id", orgId),
299
+ // Custom roles the tenant created by duplicating a system role. Tolerate a
300
+ // missing table (pre-seed) — just show the system roles in that case.
301
+ core().from("tenant_roles").select("key, name, description").eq("tenant_id", orgId)
260
302
  ]);
261
303
  if (permsResult.error) throw permsResult.error;
262
304
  if (rolePermsResult.error) throw rolePermsResult.error;
263
305
  if (overridesResult.error) throw overridesResult.error;
306
+ const systemRoles = roles.map((r) => ({ ...r, isSystem: true }));
307
+ const customRoles = customResult.error ? [] : (customResult.data ?? []).map((r) => ({
308
+ id: r.key,
309
+ name: r.name,
310
+ description: r.description ?? void 0,
311
+ isSystem: false
312
+ }));
264
313
  return buildPermissionProfiles(
314
+ [...systemRoles, ...customRoles],
265
315
  permsResult.data ?? [],
266
316
  rolePermsResult.data ?? [],
267
317
  overridesResult.data ?? []
268
318
  );
269
319
  },
270
- async createProfile(_orgId, _profile) {
271
- throw new Error(
272
- "Custom roles are not supported. Roles are system-defined (owner, admin, manager, staff, viewer)."
320
+ async createProfile(orgId, profile) {
321
+ const existing = new Set(
322
+ ((await core().from("tenant_roles").select("key").eq("tenant_id", orgId)).data ?? []).map((r) => r.key)
273
323
  );
324
+ let key = slugifyRole(profile.name);
325
+ if (existing.has(key) || roles.some((r) => r.id === key)) {
326
+ let n = 2;
327
+ while (existing.has(`${key}-${n}`) || roles.some((r) => r.id === `${key}-${n}`)) n++;
328
+ key = `${key}-${n}`;
329
+ }
330
+ const insertRole = await core().from("tenant_roles").insert({ tenant_id: orgId, key, name: profile.name, description: profile.description ?? null });
331
+ if (insertRole.error) throw insertRole.error;
332
+ const overrides = Object.entries(profile.grants ?? {}).flatMap(
333
+ ([category, actions]) => actions.map((action) => ({
334
+ tenant_id: orgId,
335
+ role: key,
336
+ permission_id: `${category}.${DB_ACTION[action] ?? action}`,
337
+ granted: true
338
+ }))
339
+ );
340
+ if (overrides.length > 0) {
341
+ const { error } = await core().from("tenant_role_overrides").insert(overrides);
342
+ if (error) throw error;
343
+ }
344
+ const profiles = await this.listProfiles(orgId);
345
+ return profiles.find((p) => p.id === key) ?? {
346
+ id: key,
347
+ name: profile.name,
348
+ description: profile.description,
349
+ isSystem: false,
350
+ systemPermissions: profile.systemPermissions ?? [],
351
+ grants: profile.grants ?? {}
352
+ };
274
353
  },
275
354
  async updateProfile(orgId, profileId, data) {
276
355
  if (data.grants) {
@@ -280,6 +359,8 @@ function createSupabaseOrgAdapter(config) {
280
359
  for (const action of actions) {
281
360
  const dbActionMap = {
282
361
  read: "read",
362
+ create: "create",
363
+ edit: "update",
283
364
  write: "create",
284
365
  manage: "manage",
285
366
  delete: "delete"
@@ -303,8 +384,13 @@ function createSupabaseOrgAdapter(config) {
303
384
  if (!updated) throw new Error(`Profile not found: ${profileId}`);
304
385
  return updated;
305
386
  },
306
- async deleteProfile(_orgId, _profileId) {
307
- throw new Error("System roles cannot be deleted.");
387
+ async deleteProfile(orgId, profileId) {
388
+ const found = await core().from("tenant_roles").select("key").eq("tenant_id", orgId).eq("key", profileId).maybeSingle();
389
+ if (found.error) throw found.error;
390
+ if (!found.data) throw new Error("System roles cannot be deleted.");
391
+ await core().from("tenant_role_overrides").delete().eq("tenant_id", orgId).eq("role", profileId);
392
+ const { error } = await core().from("tenant_roles").delete().eq("tenant_id", orgId).eq("key", profileId);
393
+ if (error) throw error;
308
394
  },
309
395
  async listInvites(orgId) {
310
396
  const { data, error } = await core().from("invitations").select("*").eq("tenant_id", orgId).order("created_at", { ascending: false });
@@ -319,6 +405,10 @@ function createSupabaseOrgAdapter(config) {
319
405
  invited_by: invitedBy
320
406
  }).select().single();
321
407
  if (error) throw error;
408
+ await authAdapter?.inviteUser?.(email, {
409
+ redirectTo: inviteRedirectTo,
410
+ data: { tenant_id: orgId, role: profileId, invited_by: invitedBy }
411
+ });
322
412
  return mapInviteRow(data);
323
413
  },
324
414
  async bulkInvite(orgId, emails, profileId, invitedBy) {
@@ -330,6 +420,14 @@ function createSupabaseOrgAdapter(config) {
330
420
  }));
331
421
  const { data, error } = await core().from("invitations").insert(rows).select();
332
422
  if (error) throw error;
423
+ await Promise.all(
424
+ emails.map(
425
+ (email) => authAdapter?.inviteUser?.(email, {
426
+ redirectTo: inviteRedirectTo,
427
+ data: { tenant_id: orgId, role: profileId, invited_by: invitedBy }
428
+ })
429
+ )
430
+ );
333
431
  return (data ?? []).map(mapInviteRow);
334
432
  },
335
433
  async revokeInvite(orgId, inviteId) {
@@ -342,7 +440,12 @@ function createSupabaseOrgAdapter(config) {
342
440
  expires_at: new Date(Date.now() + 7 * 24 * 60 * 60 * 1e3).toISOString()
343
441
  }).eq("id", inviteId).eq("tenant_id", orgId).select().single();
344
442
  if (error) throw error;
345
- return mapInviteRow(data);
443
+ const row = data;
444
+ await authAdapter?.inviteUser?.(row.email, {
445
+ redirectTo: inviteRedirectTo,
446
+ data: { tenant_id: orgId, role: row.role, invited_by: row.invited_by }
447
+ });
448
+ return mapInviteRow(row);
346
449
  },
347
450
  async listLocations(orgId) {
348
451
  const { data, error } = await core().from("locations").select("*").eq("tenant_id", orgId).order("is_headquarters", { ascending: false }).order("name");
@@ -698,9 +801,27 @@ function useTenantOptional() {
698
801
  }
699
802
  function OrgProvider({ adapter, autoCreate = false, children }) {
700
803
  const { user } = useAuthStore();
701
- const { currentOrg, userOrgs, loading, setCurrentOrg, setUserOrgs, setLoading, reset } = useOrganizationStore();
804
+ const { currentOrg, userOrgs, loading, setCurrentOrg, setUserOrgs, setMembers, setLoading, reset } = useOrganizationStore();
805
+ const setProfiles = usePermissionsStore((s) => s.setProfiles);
806
+ const setCurrentProfile = usePermissionsStore((s) => s.setCurrentProfile);
702
807
  const adapterRef = React.useRef(adapter);
703
808
  adapterRef.current = adapter;
809
+ const hydrateOrg = React.useCallback(
810
+ async (orgId, uid) => {
811
+ try {
812
+ const [members, profiles] = await Promise.all([
813
+ adapterRef.current.listMembers(orgId),
814
+ adapterRef.current.listProfiles(orgId)
815
+ ]);
816
+ setMembers(members);
817
+ setProfiles(profiles);
818
+ const myMembership = uid ? members.find((m) => m.userId === uid) : void 0;
819
+ setCurrentProfile(profiles.find((p) => p.id === myMembership?.profileId) ?? null);
820
+ } catch {
821
+ }
822
+ },
823
+ [setMembers, setProfiles, setCurrentProfile]
824
+ );
704
825
  React.useEffect(() => {
705
826
  if (!user) {
706
827
  reset();
@@ -729,6 +850,7 @@ function OrgProvider({ adapter, autoCreate = false, children }) {
729
850
  }
730
851
  ]);
731
852
  setCurrentOrg(newOrg);
853
+ await hydrateOrg(newOrg.id, user.id);
732
854
  }
733
855
  return;
734
856
  }
@@ -736,7 +858,10 @@ function OrgProvider({ adapter, autoCreate = false, children }) {
736
858
  const target = memberships.find((m) => m.orgId === persistedId) ?? memberships[0];
737
859
  if (target && !cancelled) {
738
860
  const org = await adapterRef.current.getOrg(target.orgId);
739
- if (!cancelled) setCurrentOrg(org);
861
+ if (!cancelled) {
862
+ setCurrentOrg(org);
863
+ await hydrateOrg(org.id, user.id);
864
+ }
740
865
  }
741
866
  } finally {
742
867
  if (!cancelled) setLoading(false);
@@ -752,6 +877,7 @@ function OrgProvider({ adapter, autoCreate = false, children }) {
752
877
  try {
753
878
  const org = await adapterRef.current.getOrg(orgId);
754
879
  setCurrentOrg(org);
880
+ await hydrateOrg(org.id, user?.id);
755
881
  } finally {
756
882
  setLoading(false);
757
883
  }
@@ -762,6 +888,7 @@ function OrgProvider({ adapter, autoCreate = false, children }) {
762
888
  try {
763
889
  const org = await adapterRef.current.getOrg(currentOrg.id);
764
890
  setCurrentOrg(org);
891
+ await hydrateOrg(org.id, user?.id);
765
892
  } finally {
766
893
  setLoading(false);
767
894
  }
@@ -777,5 +904,5 @@ function OrgProvider({ adapter, autoCreate = false, children }) {
777
904
  }
778
905
 
779
906
  export { CORE_SCHEMA, OrgProvider, createFayzSupabaseClient, createMockOrgAdapter, createSupabaseOrgAdapter, getCoreSchemaClient, getFayzSupabaseClient, getFayzSupabaseClientOptional, getPersistedOrgId, useOrgAdapter, useOrgAdapterOptional, useOrganizationStore, useTenant, useTenantOptional };
780
- //# sourceMappingURL=chunk-DJW67TCY.js.map
781
- //# sourceMappingURL=chunk-DJW67TCY.js.map
907
+ //# sourceMappingURL=chunk-DCRT47W4.js.map
908
+ //# sourceMappingURL=chunk-DCRT47W4.js.map