@schandlergarcia/sf-web-components 2.2.1 → 2.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/CHANGELOG.md +16 -2
  2. package/brands/engine/app/api/graphql-operations-types.ts +11260 -0
  3. package/brands/engine/app/api/graphqlClient.ts +25 -0
  4. package/brands/engine/app/api/partnerQueries.ts +212 -0
  5. package/brands/engine/app/appLayout.tsx +13 -0
  6. package/brands/engine/app/components/AgentforceConversationClient.tsx +201 -0
  7. package/brands/engine/app/components/__inherit_AgentforceConversationClient.tsx +3 -0
  8. package/brands/engine/app/components/alerts/status-alert.tsx +49 -0
  9. package/brands/engine/app/components/layouts/card-layout.tsx +29 -0
  10. package/brands/engine/app/components/workspace/CommandCenter.tsx +16 -0
  11. package/brands/engine/app/config/agentApi.ts +36 -0
  12. package/brands/engine/app/features/object-search/__examples__/api/accountSearchService.ts +46 -0
  13. package/brands/engine/app/features/object-search/__examples__/api/query/distinctAccountIndustries.graphql +19 -0
  14. package/brands/engine/app/features/object-search/__examples__/api/query/distinctAccountTypes.graphql +19 -0
  15. package/brands/engine/app/features/object-search/__examples__/api/query/getAccountDetail.graphql +121 -0
  16. package/brands/engine/app/features/object-search/__examples__/api/query/searchAccounts.graphql +51 -0
  17. package/brands/engine/app/features/object-search/__examples__/pages/AccountObjectDetailPage.tsx +357 -0
  18. package/brands/engine/app/features/object-search/__examples__/pages/AccountSearch.tsx +312 -0
  19. package/brands/engine/app/features/object-search/__examples__/pages/Home.tsx +34 -0
  20. package/brands/engine/app/features/object-search/api/objectSearchService.ts +84 -0
  21. package/brands/engine/app/features/object-search/components/ActiveFilters.tsx +89 -0
  22. package/brands/engine/app/features/object-search/components/FilterContext.tsx +83 -0
  23. package/brands/engine/app/features/object-search/components/ObjectBreadcrumb.tsx +66 -0
  24. package/brands/engine/app/features/object-search/components/PaginationControls.tsx +109 -0
  25. package/brands/engine/app/features/object-search/components/SearchBar.tsx +41 -0
  26. package/brands/engine/app/features/object-search/components/SortControl.tsx +143 -0
  27. package/brands/engine/app/features/object-search/components/filters/BooleanFilter.tsx +78 -0
  28. package/brands/engine/app/features/object-search/components/filters/DateFilter.tsx +128 -0
  29. package/brands/engine/app/features/object-search/components/filters/DateRangeFilter.tsx +70 -0
  30. package/brands/engine/app/features/object-search/components/filters/FilterFieldWrapper.tsx +33 -0
  31. package/brands/engine/app/features/object-search/components/filters/MultiSelectFilter.tsx +97 -0
  32. package/brands/engine/app/features/object-search/components/filters/NumericRangeFilter.tsx +163 -0
  33. package/brands/engine/app/features/object-search/components/filters/SearchFilter.tsx +50 -0
  34. package/brands/engine/app/features/object-search/components/filters/SelectFilter.tsx +97 -0
  35. package/brands/engine/app/features/object-search/components/filters/TextFilter.tsx +91 -0
  36. package/brands/engine/app/features/object-search/hooks/useAsyncData.ts +54 -0
  37. package/brands/engine/app/features/object-search/hooks/useCachedAsyncData.ts +184 -0
  38. package/brands/engine/app/features/object-search/hooks/useDebouncedCallback.ts +34 -0
  39. package/brands/engine/app/features/object-search/hooks/useObjectSearchParams.ts +252 -0
  40. package/brands/engine/app/features/object-search/utils/debounce.ts +25 -0
  41. package/brands/engine/app/features/object-search/utils/fieldUtils.ts +29 -0
  42. package/brands/engine/app/features/object-search/utils/filterUtils.ts +404 -0
  43. package/brands/engine/app/features/object-search/utils/sortUtils.ts +38 -0
  44. package/brands/engine/app/hooks/useEngineLiveData.ts +49 -0
  45. package/brands/engine/app/hooks/useEvaAgent.ts +288 -0
  46. package/brands/engine/app/hooks/usePartnerDashboardData.ts +141 -0
  47. package/brands/engine/app/navigationMenu.tsx +80 -0
  48. package/brands/engine/app/pages/AccountObjectDetailPage.tsx +361 -0
  49. package/brands/engine/app/pages/AccountSearch.tsx +305 -0
  50. package/brands/engine/app/pages/BlankDashboard.tsx +15 -0
  51. package/brands/engine/app/pages/DataTest.tsx +78 -0
  52. package/brands/engine/app/pages/Home.tsx +5 -0
  53. package/brands/engine/app/pages/NotFound.tsx +19 -0
  54. package/brands/engine/app/pages/PartnerHubDashboard.tsx +2010 -0
  55. package/brands/engine/app/pages/Search.tsx +13 -0
  56. package/brands/engine/app/router-utils.tsx +35 -0
  57. package/brands/engine/app/routes.tsx +39 -0
  58. package/brands/engine/app/styles/global.css +270 -0
  59. package/package.json +1 -1
  60. package/scripts/apply-brand.mjs +160 -76
  61. package/scripts/postinstall.mjs +6 -0
@@ -0,0 +1,13 @@
1
+ export default function Search() {
2
+ return (
3
+ <div className="flex min-h-[80vh] items-center justify-center px-4 sm:px-6 lg:px-8 bg-slate-50 dark:bg-slate-950 transition-colors">
4
+ <div className="w-full max-w-4xl">
5
+ <div className="text-center mb-8">
6
+ <h1 className="text-4xl font-bold text-slate-900 dark:text-slate-50 mb-4">Search</h1>
7
+ <p className="text-lg text-slate-600 dark:text-slate-300">Find records across your organization.</p>
8
+ </div>
9
+ <p className="text-center text-slate-500 dark:text-slate-400">Search functionality coming soon...</p>
10
+ </div>
11
+ </div>
12
+ );
13
+ }
@@ -0,0 +1,35 @@
1
+ import type { RouteObject } from 'react-router';
2
+ import { routes } from './routes';
3
+
4
+ export type RouteWithFullPath = RouteObject & { fullPath: string };
5
+
6
+ const flatMapRoutes = (
7
+ route: RouteObject,
8
+ parentPath: string = ''
9
+ ): RouteWithFullPath[] => {
10
+ let fullPath: string;
11
+
12
+ if (route.index) {
13
+ fullPath = parentPath || '/';
14
+ } else if (route.path) {
15
+ if (route.path.startsWith('/')) {
16
+ fullPath = route.path;
17
+ } else {
18
+ fullPath =
19
+ parentPath === '/' ? `/${route.path}` : `${parentPath}/${route.path}`;
20
+ }
21
+ } else {
22
+ fullPath = parentPath;
23
+ }
24
+
25
+ const routeWithPath = { ...route, fullPath };
26
+
27
+ const childRoutes =
28
+ route.children?.flatMap(child => flatMapRoutes(child, fullPath)) || [];
29
+
30
+ return [routeWithPath, ...childRoutes];
31
+ };
32
+
33
+ export const getAllRoutes = (): RouteWithFullPath[] => {
34
+ return routes.flatMap(route => flatMapRoutes(route));
35
+ };
@@ -0,0 +1,39 @@
1
+ import type { RouteObject } from 'react-router';
2
+ import AppLayout from './appLayout';
3
+ import Home from './pages/Home';
4
+ import DataTest from './pages/DataTest';
5
+ import AccountSearch from './features/object-search/__examples__/pages/AccountSearch';
6
+ import AccountObjectDetailPage from './features/object-search/__examples__/pages/AccountObjectDetailPage';
7
+ import NotFound from './pages/NotFound';
8
+
9
+ export const routes: RouteObject[] = [
10
+ {
11
+ path: "/",
12
+ element: <AppLayout />,
13
+ children: [
14
+ {
15
+ index: true,
16
+ element: <Home />,
17
+ handle: { showInNavigation: true, showNavBar: true, label: "Partner Hub" }
18
+ },
19
+ {
20
+ path: 'data-test',
21
+ element: <DataTest />,
22
+ handle: { showInNavigation: true, showNavBar: true, label: "Data Test" }
23
+ },
24
+ {
25
+ path: 'accounts',
26
+ element: <AccountSearch />,
27
+ handle: { showInNavigation: true, showNavBar: true, label: "Accounts" }
28
+ },
29
+ {
30
+ path: 'accounts/:recordId',
31
+ element: <AccountObjectDetailPage />
32
+ },
33
+ {
34
+ path: '*',
35
+ element: <NotFound />
36
+ }
37
+ ]
38
+ }
39
+ ];
@@ -0,0 +1,270 @@
1
+ @import '@heroui/styles';
2
+
3
+ @layer base {
4
+ html,
5
+ body,
6
+ #root {
7
+ @apply min-h-screen;
8
+ }
9
+
10
+ body {
11
+ @apply antialiased bg-white;
12
+ }
13
+
14
+ /* Smooth scrolling */
15
+ html {
16
+ scroll-behavior: smooth;
17
+ }
18
+
19
+ /* Custom animations */
20
+ @keyframes fade-in {
21
+ from {
22
+ opacity: 0;
23
+ transform: translateY(10px);
24
+ }
25
+ to {
26
+ opacity: 1;
27
+ transform: translateY(0);
28
+ }
29
+ }
30
+
31
+ @keyframes slide-up {
32
+ from {
33
+ opacity: 0;
34
+ transform: translateY(20px);
35
+ }
36
+ to {
37
+ opacity: 1;
38
+ transform: translateY(0);
39
+ }
40
+ }
41
+
42
+ .animate-fade-in {
43
+ animation: fade-in 0.6s ease-out;
44
+ }
45
+
46
+ .animate-slide-up {
47
+ animation: slide-up 0.5s ease-out;
48
+ }
49
+ }
50
+
51
+ @import 'tw-animate-css';
52
+ @import 'shadcn/tailwind.css';
53
+
54
+ @custom-variant dark (&:is(.dark *));
55
+
56
+ @source "../components/library";
57
+ @source "../components/pages";
58
+
59
+ @theme inline {
60
+ --color-background: var(--background);
61
+ --color-foreground: var(--foreground);
62
+ --color-card: var(--card);
63
+ --color-card-foreground: var(--card-foreground);
64
+ --color-popover: var(--popover);
65
+ --color-popover-foreground: var(--popover-foreground);
66
+ --color-primary: var(--primary);
67
+ --color-primary-foreground: var(--primary-foreground);
68
+ --color-secondary: var(--secondary);
69
+ --color-secondary-foreground: var(--secondary-foreground);
70
+ --color-muted: var(--muted);
71
+ --color-muted-foreground: var(--muted-foreground);
72
+ --color-accent: var(--accent);
73
+ --color-accent-foreground: var(--accent-foreground);
74
+ --color-destructive: var(--destructive);
75
+ --color-destructive-foreground: var(--destructive-foreground);
76
+ --color-border: var(--border);
77
+ --color-input: var(--input);
78
+ --color-ring: var(--ring);
79
+ --color-chart-1: var(--chart-1);
80
+ --color-chart-2: var(--chart-2);
81
+ --color-chart-3: var(--chart-3);
82
+ --color-chart-4: var(--chart-4);
83
+ --color-chart-5: var(--chart-5);
84
+ --radius-sm: calc(var(--radius) - 4px);
85
+ --radius-md: calc(var(--radius) - 2px);
86
+ --radius-lg: var(--radius);
87
+ --radius-xl: calc(var(--radius) + 4px);
88
+ --color-sidebar: var(--sidebar);
89
+ --color-sidebar-foreground: var(--sidebar-foreground);
90
+ --color-sidebar-primary: var(--sidebar-primary);
91
+ --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
92
+ --color-sidebar-accent: var(--sidebar-accent);
93
+ --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
94
+ --color-sidebar-border: var(--sidebar-border);
95
+ --color-sidebar-ring: var(--sidebar-ring);
96
+
97
+ /* Engine brand palette — official guidelines */
98
+ --color-engine-black: #0D1117;
99
+ --color-engine-cyan: #7DCBD9;
100
+ --color-engine-green: #1E9D6D;
101
+ --color-engine-blue: #157DE5;
102
+ --color-engine-orange: #FD4B23;
103
+ --color-engine-amber: #FFB200;
104
+ --color-engine-bg: #F3F3F4;
105
+ --color-engine-border: #B0B1B3;
106
+ --color-engine-text: #0D1117;
107
+ --color-engine-muted: #616368;
108
+ --color-engine-label: #B0B1B3;
109
+
110
+ /* Engine Cyan brand ramp */
111
+ --color-brand-50: #F0FAFB;
112
+ --color-brand-100: #D9F2F5;
113
+ --color-brand-200: #B3E5EB;
114
+ --color-brand-300: #7DCBD9;
115
+ --color-brand-400: #5BB8CA;
116
+ --color-brand-500: #3AA0B5;
117
+ --color-brand-600: #2D849A;
118
+ --color-brand-700: #266B7E;
119
+ --color-brand-800: #235768;
120
+ --color-brand-900: #1F4858;
121
+ --color-brand-950: #112E3A;
122
+
123
+ --font-sans: 'Inter', ui-sans-serif, system-ui, -apple-system, sans-serif;
124
+ --font-mono: 'JetBrains Mono', ui-monospace, monospace;
125
+
126
+ --radius-pill: 9999px;
127
+ --radius-card: 10px;
128
+ }
129
+
130
+ :root {
131
+ --radius: 0.625rem;
132
+ --background: oklch(1 0 0);
133
+ --foreground: oklch(0.145 0 0);
134
+ --card: oklch(1 0 0);
135
+ --card-foreground: oklch(0.145 0 0);
136
+ --popover: oklch(1 0 0);
137
+ --popover-foreground: oklch(0.145 0 0);
138
+ --primary: oklch(0.205 0 0);
139
+ --primary-foreground: oklch(0.985 0 0);
140
+ --secondary: oklch(0.97 0 0);
141
+ --secondary-foreground: oklch(0.205 0 0);
142
+ --muted: oklch(0.97 0 0);
143
+ --muted-foreground: oklch(0.556 0 0);
144
+ --accent: oklch(0.97 0 0);
145
+ --accent-foreground: oklch(0.205 0 0);
146
+ --destructive: oklch(0.577 0.245 27.325);
147
+ --border: oklch(0.922 0 0);
148
+ --input: oklch(0.922 0 0);
149
+ --ring: oklch(0.708 0 0);
150
+ --chart-1: oklch(0.646 0.222 41.116);
151
+ --chart-2: oklch(0.6 0.118 184.704);
152
+ --chart-3: oklch(0.398 0.07 227.392);
153
+ --chart-4: oklch(0.828 0.189 84.429);
154
+ --chart-5: oklch(0.769 0.188 70.08);
155
+ --sidebar: oklch(0.985 0 0);
156
+ --sidebar-foreground: oklch(0.145 0 0);
157
+ --sidebar-primary: oklch(0.205 0 0);
158
+ --sidebar-primary-foreground: oklch(0.985 0 0);
159
+ --sidebar-accent: oklch(0.97 0 0);
160
+ --sidebar-accent-foreground: oklch(0.205 0 0);
161
+ --sidebar-border: oklch(0.922 0 0);
162
+ --sidebar-ring: oklch(0.708 0 0);
163
+ }
164
+
165
+ .dark {
166
+ --background: oklch(0.145 0 0);
167
+ --foreground: oklch(0.985 0 0);
168
+ --card: oklch(0.205 0 0);
169
+ --card-foreground: oklch(0.985 0 0);
170
+ --popover: oklch(0.205 0 0);
171
+ --popover-foreground: oklch(0.985 0 0);
172
+ --primary: oklch(0.922 0 0);
173
+ --primary-foreground: oklch(0.205 0 0);
174
+ --secondary: oklch(0.269 0 0);
175
+ --secondary-foreground: oklch(0.985 0 0);
176
+ --muted: oklch(0.269 0 0);
177
+ --muted-foreground: oklch(0.708 0 0);
178
+ --accent: oklch(0.269 0 0);
179
+ --accent-foreground: oklch(0.985 0 0);
180
+ --destructive: oklch(0.704 0.191 22.216);
181
+ --border: oklch(1 0 0 / 10%);
182
+ --input: oklch(1 0 0 / 15%);
183
+ --ring: oklch(0.556 0 0);
184
+ --chart-1: oklch(0.488 0.243 264.376);
185
+ --chart-2: oklch(0.696 0.17 162.48);
186
+ --chart-3: oklch(0.769 0.188 70.08);
187
+ --chart-4: oklch(0.627 0.265 303.9);
188
+ --chart-5: oklch(0.645 0.246 16.439);
189
+ --sidebar: oklch(0.205 0 0);
190
+ --sidebar-foreground: oklch(0.985 0 0);
191
+ --sidebar-primary: oklch(0.488 0.243 264.376);
192
+ --sidebar-primary-foreground: oklch(0.985 0 0);
193
+ --sidebar-accent: oklch(0.269 0 0);
194
+ --sidebar-accent-foreground: oklch(0.985 0 0);
195
+ --sidebar-border: oklch(1 0 0 / 10%);
196
+ --sidebar-ring: oklch(0.556 0 0);
197
+ }
198
+
199
+ @layer base {
200
+ * {
201
+ @apply border-border outline-ring/50;
202
+ }
203
+ body {
204
+ @apply bg-background text-foreground;
205
+ }
206
+ }
207
+
208
+ /*
209
+ * Restore HeroUI theme variables inside the Command Center scope.
210
+ * Uses Engine brand colors: Cyan #7DCBD9 as secondary, Orange-Red #FD4B23 as danger.
211
+ */
212
+ .heroui-scope {
213
+ --primary: #0D1117;
214
+ --primary-foreground: oklch(0.9911 0 0);
215
+ --secondary: #7DCBD9;
216
+ --secondary-foreground: #0D1117;
217
+ --success: #1E9D6D;
218
+ --success-foreground: oklch(0.9911 0 0);
219
+ --warning: #FFB200;
220
+ --warning-foreground: #0D1117;
221
+ --danger: #FD4B23;
222
+ --danger-foreground: oklch(0.9911 0 0);
223
+
224
+ --muted: oklch(0.5517 0.0138 285.94);
225
+ --accent: oklch(0.6204 0.195 253.83);
226
+ --accent-foreground: oklch(0.9911 0 0);
227
+ --background: #F3F3F4;
228
+ --foreground: #0D1117;
229
+ --default: oklch(94% 0.001 286.375);
230
+ --default-foreground: #0D1117;
231
+ --border: #B0B1B3;
232
+ --separator: oklch(92% 0.004 286.32);
233
+ --segment: oklch(100% 0 0);
234
+ --segment-foreground: #0D1117;
235
+ --surface: oklch(100% 0 0);
236
+ --surface-foreground: #0D1117;
237
+ --overlay: oklch(100% 0 0);
238
+ --overlay-foreground: #0D1117;
239
+ --focus: #157DE5;
240
+ --link: #0D1117;
241
+ }
242
+
243
+ /* ChatBar expanded overlay — horizontal padding so it doesn't hit window edges */
244
+ body > .fixed.inset-x-0.rounded-2xl {
245
+ left: 1.5rem !important;
246
+ right: 1.5rem !important;
247
+ }
248
+
249
+ .dark .heroui-scope,
250
+ .heroui-scope.dark {
251
+ --muted: oklch(70.5% 0.015 286.067);
252
+ --background: oklch(12% 0.005 285.823);
253
+ --foreground: oklch(0.9911 0 0);
254
+ --default: oklch(27.4% 0.006 286.033);
255
+ --default-foreground: oklch(0.9911 0 0);
256
+ --border: oklch(28% 0.006 286.033);
257
+ --separator: oklch(25% 0.006 286.033);
258
+ --segment: oklch(0.3964 0.01 285.93);
259
+ --segment-foreground: oklch(0.9911 0 0);
260
+ --surface: oklch(0.2103 0.0059 285.89);
261
+ --surface-foreground: oklch(0.9911 0 0);
262
+ --overlay: oklch(0.2103 0.0059 285.89);
263
+ --overlay-foreground: oklch(0.9911 0 0);
264
+ --warning: oklch(0.8203 0.1388 76.34);
265
+ --warning-foreground: #0D1117;
266
+ --danger: oklch(0.594 0.1967 24.63);
267
+ --danger-foreground: oklch(0.9911 0 0);
268
+ --focus: #157DE5;
269
+ --link: oklch(0.9911 0 0);
270
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@schandlergarcia/sf-web-components",
3
- "version": "2.2.1",
3
+ "version": "2.3.1",
4
4
  "description": "Reusable Salesforce web components library with Tailwind CSS v4 and shadcn/ui",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -1,15 +1,17 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  /**
4
- * apply-brand.mjs — Apply a brand theme to the project.
4
+ * apply-brand.mjs — Apply a brand theme or install a full demo app.
5
5
  *
6
6
  * Usage:
7
- * node scripts/apply-brand.mjs engine # Apply Engine brand
8
- * node scripts/apply-brand.mjs --list # List available brands
9
- * node scripts/apply-brand.mjs --reset # Remove brand, restore neutral theme
7
+ * node scripts/apply-brand.mjs engine # Apply Engine brand colors only
8
+ * node scripts/apply-brand.mjs --demo engine # Install full Engine demo app (neutral theme)
9
+ * node scripts/apply-brand.mjs --list # List available brands
10
+ * node scripts/apply-brand.mjs --reset # Remove brand, restore neutral theme
10
11
  *
11
- * When run from a consuming project (via npm run brand:engine), the script
12
- * detects the package location automatically.
12
+ * The demo command installs the full app (dashboard, routes, hooks, queries,
13
+ * features) but keeps the neutral color palette. Run brand:engine afterwards
14
+ * to switch to Engine colors.
13
15
  */
14
16
 
15
17
  import fs from 'fs';
@@ -19,10 +21,17 @@ import { fileURLToPath } from 'url';
19
21
  const __filename = fileURLToPath(import.meta.url);
20
22
  const __dirname = path.dirname(__filename);
21
23
 
22
- const arg = process.argv[2];
24
+ const args = process.argv.slice(2);
25
+ const isDemo = args.includes('--demo');
26
+ const positionalArgs = args.filter(a => !a.startsWith('--'));
27
+ const flagArgs = args.filter(a => a.startsWith('--'));
23
28
 
24
- if (!arg) {
25
- console.error('Usage: node scripts/apply-brand.mjs <brand-name|--list|--reset>');
29
+ if (args.length === 0) {
30
+ console.error('Usage:');
31
+ console.error(' node scripts/apply-brand.mjs <brand> # Apply brand colors');
32
+ console.error(' node scripts/apply-brand.mjs --demo <brand> # Install full demo app');
33
+ console.error(' node scripts/apply-brand.mjs --list # List brands');
34
+ console.error(' node scripts/apply-brand.mjs --reset # Reset to neutral');
26
35
  process.exit(1);
27
36
  }
28
37
 
@@ -47,7 +56,30 @@ if (!packageRoot) {
47
56
 
48
57
  const brandsDir = path.join(packageRoot, 'brands');
49
58
 
50
- if (arg === '--list') {
59
+ // ── Helpers ─────────────────────────────────────────────────────────────────
60
+
61
+ function copyDirectoryRecursive(source, target) {
62
+ if (!fs.existsSync(source)) return 0;
63
+ let count = 0;
64
+ if (!fs.existsSync(target)) fs.mkdirSync(target, { recursive: true });
65
+
66
+ for (const item of fs.readdirSync(source)) {
67
+ if (item.startsWith('.')) continue;
68
+ const srcPath = path.join(source, item);
69
+ const dstPath = path.join(target, item);
70
+ if (fs.statSync(srcPath).isDirectory()) {
71
+ count += copyDirectoryRecursive(srcPath, dstPath);
72
+ } else {
73
+ fs.copyFileSync(srcPath, dstPath);
74
+ count++;
75
+ }
76
+ }
77
+ return count;
78
+ }
79
+
80
+ // ── --list ──────────────────────────────────────────────────────────────────
81
+
82
+ if (flagArgs.includes('--list')) {
51
83
  if (!fs.existsSync(brandsDir)) {
52
84
  console.log('No brands available.');
53
85
  process.exit(0);
@@ -56,10 +88,16 @@ if (arg === '--list') {
56
88
  fs.statSync(path.join(brandsDir, d)).isDirectory()
57
89
  );
58
90
  console.log(`Available brands: ${brands.join(', ') || '(none)'}`);
91
+ for (const b of brands) {
92
+ const hasApp = fs.existsSync(path.join(brandsDir, b, 'app'));
93
+ console.log(` ${b}${hasApp ? ' (has demo app)' : ''}`);
94
+ }
59
95
  process.exit(0);
60
96
  }
61
97
 
62
- if (arg === '--reset') {
98
+ // ── --reset ─────────────────────────────────────────────────────────────────
99
+
100
+ if (flagArgs.includes('--reset')) {
63
101
  const neutralCSS = path.join(packageRoot, 'src/styles/global.css');
64
102
  const targetCSS = path.join(cwd, 'src/styles/global.css');
65
103
  if (fs.existsSync(neutralCSS)) {
@@ -72,7 +110,14 @@ if (arg === '--reset') {
72
110
  process.exit(0);
73
111
  }
74
112
 
75
- const brandName = arg;
113
+ // ── Resolve brand name ──────────────────────────────────────────────────────
114
+
115
+ const brandName = positionalArgs[0];
116
+ if (!brandName) {
117
+ console.error('Brand name required. Run with --list to see available brands.');
118
+ process.exit(1);
119
+ }
120
+
76
121
  const brandDir = path.join(brandsDir, brandName);
77
122
 
78
123
  if (!fs.existsSync(brandDir)) {
@@ -84,85 +129,124 @@ if (!fs.existsSync(brandDir)) {
84
129
  process.exit(1);
85
130
  }
86
131
 
87
- console.log(`\n🎨 Applying "${brandName}" brand...\n`);
132
+ // ── --demo: Install the full demo app (with NEUTRAL theme) ──────────────────
88
133
 
89
- let installed = 0;
134
+ if (isDemo) {
135
+ const appDir = path.join(brandDir, 'app');
136
+ if (!fs.existsSync(appDir)) {
137
+ console.error(`Brand "${brandName}" does not have a demo app (no app/ directory).`);
138
+ process.exit(1);
139
+ }
90
140
 
91
- // 1. global.css src/styles/global.css (always overwrite)
92
- const brandCSS = path.join(brandDir, 'global.css');
93
- const targetCSS = path.join(cwd, 'src/styles/global.css');
94
- if (fs.existsSync(brandCSS)) {
95
- fs.mkdirSync(path.dirname(targetCSS), { recursive: true });
96
- fs.copyFileSync(brandCSS, targetCSS);
97
- console.log(' ✓ Brand theme applied (global.css)');
98
- installed++;
99
- }
141
+ console.log(`\n🚀 Installing "${brandName}" demo app...\n`);
142
+ let installed = 0;
143
+
144
+ // Copy the full app directory tree into src/
145
+ // Note: 'styles' is deliberately excluded — demo keeps the neutral theme
146
+ const appSubdirs = ['pages', 'hooks', 'api', 'config', 'features', 'components/workspace', 'components/alerts', 'components/layouts'];
147
+
148
+ for (const sub of appSubdirs) {
149
+ const src = path.join(appDir, sub);
150
+ const dst = path.join(cwd, 'src', sub);
151
+ if (fs.existsSync(src)) {
152
+ const count = copyDirectoryRecursive(src, dst);
153
+ if (count > 0) {
154
+ console.log(` ✓ Installed ${count} files in src/${sub}/`);
155
+ installed += count;
156
+ }
157
+ }
158
+ }
100
159
 
101
- // 2. Sample data files → src/data/
102
- const dataFiles = [
103
- { src: 'engine-sample-data.js', dst: 'src/data/engine-sample-data.js' },
104
- { src: 'engine-live-data.js', dst: 'src/data/engine-live-data.js' },
105
- { src: 'partner-hub-sample-data.js', dst: 'src/data/partner-hub-sample-data.js' },
106
- ];
107
- for (const { src, dst } of dataFiles) {
108
- const srcPath = path.join(brandDir, src);
109
- const dstPath = path.join(cwd, dst);
110
- if (fs.existsSync(srcPath)) {
111
- fs.mkdirSync(path.dirname(dstPath), { recursive: true });
112
- fs.copyFileSync(srcPath, dstPath);
113
- console.log(` ✓ Installed ${dst}`);
160
+ // Copy AgentforceConversationClient to src/components/
161
+ const agentClient = path.join(appDir, 'components/AgentforceConversationClient.tsx');
162
+ const agentClientInherit = path.join(appDir, 'components/__inherit_AgentforceConversationClient.tsx');
163
+ const componentsDir = path.join(cwd, 'src/components');
164
+ if (!fs.existsSync(componentsDir)) fs.mkdirSync(componentsDir, { recursive: true });
165
+
166
+ if (fs.existsSync(agentClient)) {
167
+ fs.copyFileSync(agentClient, path.join(componentsDir, 'AgentforceConversationClient.tsx'));
168
+ console.log(' Installed src/components/AgentforceConversationClient.tsx');
169
+ installed++;
170
+ }
171
+ if (fs.existsSync(agentClientInherit)) {
172
+ fs.copyFileSync(agentClientInherit, path.join(componentsDir, '__inherit_AgentforceConversationClient.tsx'));
114
173
  installed++;
115
174
  }
116
- }
117
175
 
118
- // 3. Hooks src/hooks/
119
- const hooks = [
120
- { src: 'useEngineLiveData.ts', dst: 'src/hooks/useEngineLiveData.ts' },
121
- { src: 'useEvaAgent.ts', dst: 'src/hooks/useEvaAgent.ts' },
122
- ];
123
- for (const { src, dst } of hooks) {
124
- const srcPath = path.join(brandDir, src);
125
- const dstPath = path.join(cwd, dst);
126
- if (fs.existsSync(srcPath)) {
127
- fs.mkdirSync(path.dirname(dstPath), { recursive: true });
128
- fs.copyFileSync(srcPath, dstPath);
129
- console.log(` ✓ Installed ${dst}`);
176
+ // Copy root-level app files (routes, appLayout, etc.)
177
+ const rootFiles = ['routes.tsx', 'appLayout.tsx', 'navigationMenu.tsx', 'router-utils.tsx'];
178
+ for (const file of rootFiles) {
179
+ const src = path.join(appDir, file);
180
+ const dst = path.join(cwd, 'src', file);
181
+ if (fs.existsSync(src)) {
182
+ fs.copyFileSync(src, dst);
183
+ console.log(` Installed src/${file}`);
184
+ installed++;
185
+ }
186
+ }
187
+
188
+ // Copy sample data files
189
+ const dataFiles = ['engine-sample-data.js', 'engine-live-data.js', 'partner-hub-sample-data.js'];
190
+ for (const file of dataFiles) {
191
+ const src = path.join(brandDir, file);
192
+ const dst = path.join(cwd, 'src/data', file);
193
+ if (fs.existsSync(src)) {
194
+ fs.mkdirSync(path.dirname(dst), { recursive: true });
195
+ fs.copyFileSync(src, dst);
196
+ console.log(` ✓ Installed src/data/${file}`);
197
+ installed++;
198
+ }
199
+ }
200
+
201
+ // Copy PRDs to project root
202
+ const prds = fs.readdirSync(brandDir).filter(f => f.endsWith('_PRD.md') || f.endsWith('-prd.md'));
203
+ for (const prdFile of prds) {
204
+ fs.copyFileSync(path.join(brandDir, prdFile), path.join(cwd, prdFile));
205
+ console.log(` ✓ Installed ${prdFile}`);
130
206
  installed++;
131
207
  }
132
- }
133
208
 
134
- // 4. Config files → src/config/
135
- const configs = [
136
- { src: 'agentApiConfig.ts', dst: 'src/config/agentApi.ts' },
137
- ];
138
- for (const { src, dst } of configs) {
139
- const srcPath = path.join(brandDir, src);
140
- const dstPath = path.join(cwd, dst);
141
- if (fs.existsSync(srcPath)) {
142
- fs.mkdirSync(path.dirname(dstPath), { recursive: true });
143
- fs.copyFileSync(srcPath, dstPath);
144
- console.log(` ✓ Installed ${dst}`);
209
+ // Copy schema
210
+ const schema = path.join(brandDir, 'schema.graphql');
211
+ if (fs.existsSync(schema)) {
212
+ fs.copyFileSync(schema, path.join(cwd, 'schema.graphql'));
213
+ console.log(' ✓ Installed schema.graphql');
145
214
  installed++;
146
215
  }
147
- }
148
216
 
149
- // 5. PRDs → project root
150
- const prds = fs.readdirSync(brandDir).filter(f => f.endsWith('_PRD.md') || f.endsWith('-prd.md'));
151
- for (const prdFile of prds) {
152
- fs.copyFileSync(path.join(brandDir, prdFile), path.join(cwd, prdFile));
153
- console.log(` ✓ Installed ${prdFile}`);
154
- installed++;
217
+ // Copy logo
218
+ const logo = path.join(brandDir, 'engine_logo.png');
219
+ if (fs.existsSync(logo)) {
220
+ const logoTarget = path.join(cwd, 'src/assets/images/engine_logo.png');
221
+ fs.mkdirSync(path.dirname(logoTarget), { recursive: true });
222
+ fs.copyFileSync(logo, logoTarget);
223
+ console.log(' ✓ Installed engine_logo.png');
224
+ installed++;
225
+ }
226
+
227
+ console.log(`\n✅ "${brandName}" demo app installed (${installed} files).`);
228
+ console.log(' The app uses the NEUTRAL color palette.');
229
+ console.log(` Run "npm run brand:${brandName}" to switch to ${brandName} colors.\n`);
230
+ process.exit(0);
155
231
  }
156
232
 
157
- // 6. Schema project root
158
- const schema = path.join(brandDir, 'schema.graphql');
159
- if (fs.existsSync(schema)) {
160
- fs.copyFileSync(schema, path.join(cwd, 'schema.graphql'));
161
- console.log(' ✓ Installed schema.graphql');
233
+ // ── Default: Apply brand colors only ────────────────────────────────────────
234
+
235
+ console.log(`\n🎨 Applying "${brandName}" brand colors...\n`);
236
+
237
+ let installed = 0;
238
+
239
+ // 1. global.css → src/styles/global.css (always overwrite)
240
+ const brandCSS = path.join(brandDir, 'global.css');
241
+ const targetCSS = path.join(cwd, 'src/styles/global.css');
242
+ if (fs.existsSync(brandCSS)) {
243
+ fs.mkdirSync(path.dirname(targetCSS), { recursive: true });
244
+ fs.copyFileSync(brandCSS, targetCSS);
245
+ console.log(' ✓ Brand theme applied (global.css)');
162
246
  installed++;
163
247
  }
164
248
 
165
- // 7. Logo → src/assets/images/
249
+ // 2. Logo → src/assets/images/
166
250
  const logo = path.join(brandDir, 'engine_logo.png');
167
251
  if (fs.existsSync(logo)) {
168
252
  const logoTarget = path.join(cwd, 'src/assets/images/engine_logo.png');
@@ -172,8 +256,8 @@ if (fs.existsSync(logo)) {
172
256
  installed++;
173
257
  }
174
258
 
175
- // 8. Write .brand marker so reset script knows which brand is active
259
+ // 3. Write .brand marker
176
260
  fs.writeFileSync(path.join(cwd, '.brand'), brandName + '\n', 'utf-8');
177
261
 
178
- console.log(`\n✅ "${brandName}" brand applied (${installed} files installed).`);
262
+ console.log(`\n✅ "${brandName}" brand colors applied (${installed} files).`);
179
263
  console.log(' Run "npm run brand:reset" to revert to neutral theme.\n');