@fayz-ai/saas 0.5.0 → 0.6.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (91) hide show
  1. package/dist/app/AdminShell.d.ts +14 -1
  2. package/dist/app/AdminShell.d.ts.map +1 -1
  3. package/dist/app/LoginPage.d.ts +2 -16
  4. package/dist/app/LoginPage.d.ts.map +1 -1
  5. package/dist/app/admin-app.d.ts +21 -0
  6. package/dist/app/admin-app.d.ts.map +1 -0
  7. package/dist/app/config.d.ts +40 -3
  8. package/dist/app/config.d.ts.map +1 -1
  9. package/dist/app/scaffold.d.ts +1 -2
  10. package/dist/app/scaffold.d.ts.map +1 -1
  11. package/dist/{chunk-DJW67TCY.js → chunk-DCRT47W4.js} +155 -28
  12. package/dist/chunk-DCRT47W4.js.map +1 -0
  13. package/dist/{chunk-QGMDVLLW.cjs → chunk-SRIUD35R.cjs} +155 -28
  14. package/dist/chunk-SRIUD35R.cjs.map +1 -0
  15. package/dist/crud/CrudDetailPage.d.ts.map +1 -1
  16. package/dist/index.cjs +1127 -5792
  17. package/dist/index.cjs.map +1 -1
  18. package/dist/index.d.ts +4 -3
  19. package/dist/index.d.ts.map +1 -1
  20. package/dist/index.js +839 -5509
  21. package/dist/index.js.map +1 -1
  22. package/dist/org/adapters/supabase.d.ts +18 -0
  23. package/dist/org/adapters/supabase.d.ts.map +1 -1
  24. package/dist/org/context.d.ts.map +1 -1
  25. package/dist/org/index.cjs +11 -10
  26. package/dist/org/index.js +2 -1
  27. package/dist/shell/components/crud/archetypes/ClientOrdersTab.d.ts +4 -2
  28. package/dist/shell/components/crud/archetypes/ClientOrdersTab.d.ts.map +1 -1
  29. package/dist/shell/components/layout/BottomNav.d.ts.map +1 -1
  30. package/dist/shell/components/layout/CommandPalette.d.ts.map +1 -1
  31. package/dist/shell/components/organization/InviteMemberDialog.d.ts.map +1 -1
  32. package/dist/shell/components/organization/PermissionMatrixEditor.d.ts +6 -1
  33. package/dist/shell/components/organization/PermissionMatrixEditor.d.ts.map +1 -1
  34. package/dist/shell/components/organization/PermissionProfilesTab.d.ts.map +1 -1
  35. package/dist/shell/components/organization/TeamTab.d.ts.map +1 -1
  36. package/dist/shell/components/plugins/PluginSettingsPanel.d.ts.map +1 -1
  37. package/dist/shell/components/settings/ConnectedUserProfile.d.ts +2 -1
  38. package/dist/shell/components/settings/ConnectedUserProfile.d.ts.map +1 -1
  39. package/dist/shell/components/settings/SettingsPage.d.ts +8 -1
  40. package/dist/shell/components/settings/SettingsPage.d.ts.map +1 -1
  41. package/dist/shell/config/theme/tokens.d.ts +12 -1
  42. package/dist/shell/config/theme/tokens.d.ts.map +1 -1
  43. package/dist/shell/config/theme/utils.d.ts +8 -0
  44. package/dist/shell/config/theme/utils.d.ts.map +1 -1
  45. package/dist/shell/lib/i18n.d.ts.map +1 -1
  46. package/dist/shell/types/client-orders.d.ts +4 -0
  47. package/dist/shell/types/client-orders.d.ts.map +1 -1
  48. package/dist/shell/types/crud.d.ts +4 -0
  49. package/dist/shell/types/crud.d.ts.map +1 -1
  50. package/package.json +7 -9
  51. package/src/app/AdminShell.tsx +103 -35
  52. package/src/app/LoginPage.tsx +2 -264
  53. package/src/app/{createFayzApp.tsx → admin-app.tsx} +98 -40
  54. package/src/app/config.ts +44 -2
  55. package/src/app/scaffold.tsx +13 -26
  56. package/src/crud/CrudDetailPage.tsx +43 -9
  57. package/src/crud/CrudPage.tsx +1 -1
  58. package/src/index.ts +12 -5
  59. package/src/org/adapters/supabase.ts +176 -28
  60. package/src/org/context.tsx +33 -2
  61. package/src/shell/components/crud/archetypes/ClientOrdersTab.tsx +12 -4
  62. package/src/shell/components/layout/BottomNav.tsx +39 -4
  63. package/src/shell/components/layout/CommandPalette.tsx +14 -10
  64. package/src/shell/components/organization/InviteMemberDialog.tsx +14 -3
  65. package/src/shell/components/organization/PermissionMatrixEditor.tsx +48 -10
  66. package/src/shell/components/organization/PermissionProfilesTab.tsx +65 -23
  67. package/src/shell/components/organization/TeamTab.tsx +36 -9
  68. package/src/shell/components/plugins/PluginSettingsPanel.tsx +5 -3
  69. package/src/shell/components/settings/ConnectedUserProfile.tsx +68 -25
  70. package/src/shell/components/settings/SettingsPage.tsx +12 -3
  71. package/src/shell/config/theme/tokens.ts +13 -1
  72. package/src/shell/config/theme/utils.ts +76 -15
  73. package/src/shell/lib/i18n.ts +4 -0
  74. package/src/shell/types/client-orders.ts +5 -0
  75. package/src/shell/types/crud.ts +4 -0
  76. package/dist/app/createFayzApp.d.ts +0 -11
  77. package/dist/app/createFayzApp.d.ts.map +0 -1
  78. package/dist/chunk-DJW67TCY.js.map +0 -1
  79. package/dist/chunk-QGMDVLLW.cjs.map +0 -1
  80. package/dist/shell/components/organization/ImpersonationBanner.d.ts +0 -3
  81. package/dist/shell/components/organization/ImpersonationBanner.d.ts.map +0 -1
  82. package/dist/shell/components/settings/ConnectedHolidaysSettings.d.ts +0 -3
  83. package/dist/shell/components/settings/ConnectedHolidaysSettings.d.ts.map +0 -1
  84. package/dist/shell/components/settings/ConnectedLocationsOverview.d.ts +0 -3
  85. package/dist/shell/components/settings/ConnectedLocationsOverview.d.ts.map +0 -1
  86. package/dist/shell/createSaasApp.d.ts +0 -145
  87. package/dist/shell/createSaasApp.d.ts.map +0 -1
  88. package/src/shell/components/organization/ImpersonationBanner.tsx +0 -29
  89. package/src/shell/components/settings/ConnectedHolidaysSettings.tsx +0 -99
  90. package/src/shell/components/settings/ConnectedLocationsOverview.tsx +0 -57
  91. package/src/shell/createSaasApp.tsx +0 -943
@@ -1,145 +0,0 @@
1
- import React from 'react';
2
- import { type RouterAdapter } from './lib/router';
3
- import type { AuthAdapter } from './types/auth-adapter';
4
- import type { OrgAdapter } from './types/org-adapter';
5
- import type { PlanConfig } from './types/billing';
6
- import type { PluginManifest, TenantPluginBinding } from '@fayz-ai/core';
7
- import type { CreateThemeOptions } from './config/theme/utils';
8
- import type { SaasTheme } from './config/theme/tokens';
9
- import type { AuthProvider } from './types/auth';
10
- import type { PermissionsConfig, PermissionAction } from './types/permissions';
11
- export interface PageConfig {
12
- /** URL path, e.g. '/' or '/clients' */
13
- path: string;
14
- /** Sidebar/nav label */
15
- label: string;
16
- /** Lucide icon name */
17
- icon: string;
18
- /** The page component to render */
19
- component: React.ComponentType;
20
- /** Navigation section (default: 'main') */
21
- section?: 'main' | 'secondary' | 'settings';
22
- /** Sort order within section (auto-incremented if omitted) */
23
- position?: number;
24
- /** Optional badge */
25
- badge?: string | number;
26
- /** Permission required to access this page */
27
- permission?: {
28
- feature: string;
29
- action: PermissionAction;
30
- };
31
- /** Child pages — renders as a dropdown in topbar, collapsible in sidebar.
32
- * Children can omit `component` to be navigation-only links (pointing to routes the parent handles). */
33
- children?: Array<Omit<PageConfig, 'component'> & {
34
- component?: React.ComponentType;
35
- }>;
36
- }
37
- export interface SaasAppConfig {
38
- /** App display name (used in sidebar header) */
39
- name: string;
40
- /** Logo — string renders as initial badge, ReactNode renders as-is */
41
- logo?: string | React.ReactNode;
42
- /** Supabase project URL — one project per SaaS (core in saas_core schema, data in public) */
43
- supabaseUrl?: string;
44
- /** Supabase anon key */
45
- supabaseAnonKey?: string;
46
- /** Theme overrides — use SaasTheme (friendly) or CreateThemeOptions (granular) */
47
- theme?: CreateThemeOptions | SaasTheme;
48
- /** Default theme mode: 'light' or 'dark'. User can toggle. Default: 'light' */
49
- defaultThemeMode?: 'light' | 'dark';
50
- /** Layout variant (default: 'sidebar') */
51
- layout?: 'sidebar' | 'topbar' | 'minimal';
52
- /** Content floats in a rounded frame over the sidebar background (default: true). Set false to disable. */
53
- sidebarFrame?: boolean;
54
- /** Vertical-specific pages */
55
- pages: PageConfig[];
56
- /** Plugins to register */
57
- plugins?: PluginManifest[];
58
- /** Runtime plugin activation for tenant-aware apps */
59
- pluginRuntime?: {
60
- /** Opt into loading tenant plugin bindings from the backend RPC. Static apps should leave this off. */
61
- hydrateTenantPlugins?: boolean;
62
- tenantPlugins?: TenantPluginBinding[];
63
- resolveTenantPlugins?: (context: {
64
- tenant: {
65
- id: string;
66
- slug: string;
67
- verticalId?: string;
68
- plan?: string;
69
- } | null;
70
- user: {
71
- id: string;
72
- } | null;
73
- }) => TenantPluginBinding[] | undefined;
74
- };
75
- /** Router adapter (default: hashRouterAdapter) */
76
- router?: RouterAdapter;
77
- /** Billing configuration */
78
- billing?: {
79
- plans: PlanConfig[];
80
- stripePublishableKey?: string;
81
- };
82
- /** AI chat configuration */
83
- chat?: {
84
- enabled?: boolean;
85
- systemPrompt?: string;
86
- apiEndpoint?: string;
87
- title?: string;
88
- };
89
- /** Notification configuration */
90
- notifications?: {
91
- changelogUrl?: string;
92
- };
93
- /** Locale / i18n configuration */
94
- locale?: {
95
- default?: string;
96
- supported?: string[];
97
- translations?: Record<string, Record<string, string>>;
98
- /** ISO 4217 currency code for all currency fields (e.g. 'BRL'). Inferred from locale when omitted. */
99
- currency?: string;
100
- };
101
- /** Mock user for demo/dev (will be replaced by auth in production) */
102
- user?: {
103
- fullName: string;
104
- email: string;
105
- avatarUrl?: string;
106
- };
107
- /** Auth configuration */
108
- auth?: {
109
- adapter?: AuthAdapter | 'mock' | 'supabase';
110
- requireAuth?: boolean;
111
- loginLogo?: React.ReactNode;
112
- loginLayout?: 'split' | 'centered';
113
- loginTagline?: string;
114
- loginDescription?: string;
115
- showOAuth?: boolean;
116
- oauthProviders?: Exclude<AuthProvider, 'email'>[];
117
- };
118
- /** Organization / multi-tenant configuration */
119
- organization?: {
120
- adapter?: OrgAdapter | 'mock' | 'supabase';
121
- multiOrg?: boolean;
122
- };
123
- /** Vertical/niche ID — auto-set on tenant creation, skips niche selection in onboarding */
124
- verticalId?: string;
125
- /** Permission profiles configuration */
126
- permissions?: PermissionsConfig;
127
- /** Additional settings tabs to merge with built-in ones */
128
- settingsTabs?: {
129
- id: string;
130
- label: string;
131
- component: React.ReactNode;
132
- }[];
133
- /** Set to false to hide built-in Settings page (default: true) */
134
- showSettings?: boolean;
135
- /** Set to false to hide built-in Billing page (default: true when plans provided) */
136
- showBilling?: boolean;
137
- /** Custom mobile bottom nav items (max 5). If omitted, auto-picks first main items. */
138
- bottomNav?: Array<{
139
- label: string;
140
- icon: string;
141
- route: string;
142
- }>;
143
- }
144
- export declare function createSaasApp(config: SaasAppConfig): React.FC;
145
- //# sourceMappingURL=createSaasApp.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"createSaasApp.d.ts","sourceRoot":"","sources":["../../src/shell/createSaasApp.tsx"],"names":[],"mappings":"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"}
@@ -1,29 +0,0 @@
1
- import * as React from 'react'
2
- import { Eye, X } from 'lucide-react'
3
- import { usePermissionsStore } from '../../stores/permissions.store'
4
- import { useTranslation } from '../../hooks/useTranslation'
5
-
6
- export function ImpersonationBanner() {
7
- const isImpersonating = usePermissionsStore((s) => s.isImpersonating)
8
- const impersonatedProfile = usePermissionsStore((s) => s.impersonatedProfile)
9
- const stopImpersonation = usePermissionsStore((s) => s.stopImpersonation)
10
- const { t } = useTranslation()
11
-
12
- if (!isImpersonating || !impersonatedProfile) return null
13
-
14
- return (
15
- <div className="fixed top-0 left-0 right-0 z-[90] flex items-center justify-center gap-3 bg-warning px-4 py-1.5 text-sm font-medium text-amber-950 shadow-sm">
16
- <Eye className="h-3.5 w-3.5" />
17
- <span>
18
- {t('organization.permissions.previewingAs')}: <strong>{impersonatedProfile.name}</strong>
19
- </span>
20
- <button
21
- onClick={stopImpersonation}
22
- className="ml-2 inline-flex items-center gap-1 rounded-md bg-warning/30 px-2 py-0.5 text-xs font-semibold hover:bg-warning/50 transition-colors"
23
- >
24
- <X className="h-3 w-3" />
25
- {t('organization.permissions.exitPreview')}
26
- </button>
27
- </div>
28
- )
29
- }
@@ -1,99 +0,0 @@
1
- import * as React from 'react'
2
- import { HolidaysSettings, type Holiday } from './HolidaysSettings'
3
- import { usePermission } from '../../hooks/usePermission'
4
- import { useOrganizationStore } from '../../stores/organization.store'
5
- import { toast } from '../notifications/ToastProvider'
6
- import { getSupabaseClientOptional } from '../../lib/supabase'
7
-
8
- const TABLE = 'holidays'
9
- const SCHEMA = 'saas_core'
10
-
11
- export function ConnectedHolidaysSettings() {
12
- const { hasSystemPermission } = usePermission()
13
- const currentOrg = useOrganizationStore((s) => s.currentOrg)
14
- const [holidays, setHolidays] = React.useState<Holiday[]>([])
15
- const [loading, setLoading] = React.useState(true)
16
-
17
- const canManage = true // User is already in settings — allow managing
18
- const supabase = getSupabaseClientOptional()
19
-
20
- const fetchHolidays = React.useCallback(async () => {
21
- if (!supabase || !currentOrg) return
22
- try {
23
- const { data, error } = await supabase
24
- .schema(SCHEMA)
25
- .from(TABLE)
26
- .select('*')
27
- .eq('tenant_id', currentOrg.id)
28
- .eq('is_active', true)
29
- .order('date', { ascending: true })
30
- if (error) throw error
31
- setHolidays(
32
- (data ?? []).map((row: any) => ({
33
- id: row.id,
34
- name: row.name,
35
- date: row.date,
36
- recurring: row.recurring ?? false,
37
- description: row.description,
38
- isActive: row.is_active,
39
- }))
40
- )
41
- } catch {
42
- // If table doesn't exist yet, show empty state
43
- setHolidays([])
44
- }
45
- }, [supabase, currentOrg?.id])
46
-
47
- React.useEffect(() => {
48
- fetchHolidays().finally(() => setLoading(false))
49
- }, [fetchHolidays])
50
-
51
- const handleAdd = async (data: Omit<Holiday, 'id' | 'isActive'> & { isActive?: boolean }) => {
52
- if (!supabase || !currentOrg) return
53
- try {
54
- const { error } = await supabase
55
- .schema(SCHEMA)
56
- .from(TABLE)
57
- .insert({
58
- tenant_id: currentOrg.id,
59
- name: data.name,
60
- date: data.date,
61
- recurring: data.recurring,
62
- description: data.description ?? null,
63
- is_active: true,
64
- })
65
- if (error) throw error
66
- await fetchHolidays()
67
- toast.success('Holiday added')
68
- } catch (err: any) {
69
- toast.error('Failed to add holiday', { description: err?.message })
70
- throw err
71
- }
72
- }
73
-
74
- const handleRemove = async (id: string) => {
75
- if (!supabase) return
76
- try {
77
- const { error } = await supabase
78
- .schema(SCHEMA)
79
- .from(TABLE)
80
- .update({ is_active: false })
81
- .eq('id', id)
82
- if (error) throw error
83
- await fetchHolidays()
84
- toast.success('Holiday removed')
85
- } catch (err: any) {
86
- toast.error('Failed to remove holiday', { description: err?.message })
87
- }
88
- }
89
-
90
- return (
91
- <HolidaysSettings
92
- holidays={holidays}
93
- loading={loading}
94
- canManage={canManage}
95
- onAdd={handleAdd}
96
- onRemove={handleRemove}
97
- />
98
- )
99
- }
@@ -1,57 +0,0 @@
1
- import * as React from 'react'
2
- import { LocationsOverview } from './LocationsOverview'
3
- import { useOrgAdapterOptional } from '../../lib/org-context'
4
- import { useOrganizationStore } from '../../stores/organization.store'
5
- import { usePermission } from '../../hooks/usePermission'
6
- import { toast } from '../notifications/ToastProvider'
7
- import { dedup } from '../../lib/dedup'
8
- import type { Location } from '../../types/tenant'
9
-
10
- export function ConnectedLocationsOverview() {
11
- const adapter = useOrgAdapterOptional()
12
- const currentOrg = useOrganizationStore((s) => s.currentOrg)
13
- const { hasSystemPermission } = usePermission()
14
- const [locations, setLocations] = React.useState<Location[]>([])
15
- const [loading, setLoading] = React.useState(true)
16
-
17
- const canManage = hasSystemPermission('manage_settings')
18
-
19
- const fetchLocations = React.useCallback(async () => {
20
- if (!adapter || !currentOrg) return
21
- try {
22
- const data = await dedup('locations:' + currentOrg.id, () => adapter.listLocations(currentOrg.id))
23
- setLocations(data)
24
- } catch {
25
- // ignore
26
- }
27
- }, [adapter, currentOrg?.id])
28
-
29
- React.useEffect(() => {
30
- if (!adapter || !currentOrg) {
31
- setLoading(false)
32
- return
33
- }
34
- fetchLocations().finally(() => setLoading(false))
35
- }, [fetchLocations])
36
-
37
- const handleAdd = async (data: { name: string; address?: string; city?: string; state?: string; country?: string; phone?: string; isHeadquarters?: boolean }) => {
38
- if (!adapter || !currentOrg) return
39
- try {
40
- await adapter.createLocation(currentOrg.id, data)
41
- await fetchLocations()
42
- toast.success('Location added')
43
- } catch (err: any) {
44
- toast.error('Failed to add location', { description: err?.message })
45
- throw err
46
- }
47
- }
48
-
49
- return (
50
- <LocationsOverview
51
- locations={locations}
52
- loading={loading}
53
- canManage={canManage}
54
- onAdd={handleAdd}
55
- />
56
- )
57
- }