@schandlergarcia/sf-web-components 2.2.0 → 2.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 (61) hide show
  1. package/CHANGELOG.md +15 -0
  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 +159 -76
  61. package/scripts/postinstall.mjs +24 -5
@@ -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.0",
3
+ "version": "2.3.0",
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,123 @@ 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
+ const appSubdirs = ['pages', 'hooks', 'api', 'config', 'features', 'components/workspace', 'components/alerts', 'components/layouts', 'styles'];
146
+
147
+ for (const sub of appSubdirs) {
148
+ const src = path.join(appDir, sub);
149
+ const dst = path.join(cwd, 'src', sub);
150
+ if (fs.existsSync(src)) {
151
+ const count = copyDirectoryRecursive(src, dst);
152
+ if (count > 0) {
153
+ console.log(` ✓ Installed ${count} files in src/${sub}/`);
154
+ installed += count;
155
+ }
156
+ }
157
+ }
100
158
 
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}`);
159
+ // Copy AgentforceConversationClient to src/components/
160
+ const agentClient = path.join(appDir, 'components/AgentforceConversationClient.tsx');
161
+ const agentClientInherit = path.join(appDir, 'components/__inherit_AgentforceConversationClient.tsx');
162
+ const componentsDir = path.join(cwd, 'src/components');
163
+ if (!fs.existsSync(componentsDir)) fs.mkdirSync(componentsDir, { recursive: true });
164
+
165
+ if (fs.existsSync(agentClient)) {
166
+ fs.copyFileSync(agentClient, path.join(componentsDir, 'AgentforceConversationClient.tsx'));
167
+ console.log(' Installed src/components/AgentforceConversationClient.tsx');
168
+ installed++;
169
+ }
170
+ if (fs.existsSync(agentClientInherit)) {
171
+ fs.copyFileSync(agentClientInherit, path.join(componentsDir, '__inherit_AgentforceConversationClient.tsx'));
114
172
  installed++;
115
173
  }
116
- }
117
174
 
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}`);
175
+ // Copy root-level app files (routes, appLayout, etc.)
176
+ const rootFiles = ['routes.tsx', 'appLayout.tsx', 'navigationMenu.tsx', 'router-utils.tsx'];
177
+ for (const file of rootFiles) {
178
+ const src = path.join(appDir, file);
179
+ const dst = path.join(cwd, 'src', file);
180
+ if (fs.existsSync(src)) {
181
+ fs.copyFileSync(src, dst);
182
+ console.log(` Installed src/${file}`);
183
+ installed++;
184
+ }
185
+ }
186
+
187
+ // Copy sample data files
188
+ const dataFiles = ['engine-sample-data.js', 'engine-live-data.js', 'partner-hub-sample-data.js'];
189
+ for (const file of dataFiles) {
190
+ const src = path.join(brandDir, file);
191
+ const dst = path.join(cwd, 'src/data', file);
192
+ if (fs.existsSync(src)) {
193
+ fs.mkdirSync(path.dirname(dst), { recursive: true });
194
+ fs.copyFileSync(src, dst);
195
+ console.log(` ✓ Installed src/data/${file}`);
196
+ installed++;
197
+ }
198
+ }
199
+
200
+ // Copy PRDs to project root
201
+ const prds = fs.readdirSync(brandDir).filter(f => f.endsWith('_PRD.md') || f.endsWith('-prd.md'));
202
+ for (const prdFile of prds) {
203
+ fs.copyFileSync(path.join(brandDir, prdFile), path.join(cwd, prdFile));
204
+ console.log(` ✓ Installed ${prdFile}`);
130
205
  installed++;
131
206
  }
132
- }
133
207
 
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}`);
208
+ // Copy schema
209
+ const schema = path.join(brandDir, 'schema.graphql');
210
+ if (fs.existsSync(schema)) {
211
+ fs.copyFileSync(schema, path.join(cwd, 'schema.graphql'));
212
+ console.log(' ✓ Installed schema.graphql');
145
213
  installed++;
146
214
  }
147
- }
148
215
 
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++;
216
+ // Copy logo
217
+ const logo = path.join(brandDir, 'engine_logo.png');
218
+ if (fs.existsSync(logo)) {
219
+ const logoTarget = path.join(cwd, 'src/assets/images/engine_logo.png');
220
+ fs.mkdirSync(path.dirname(logoTarget), { recursive: true });
221
+ fs.copyFileSync(logo, logoTarget);
222
+ console.log(' ✓ Installed engine_logo.png');
223
+ installed++;
224
+ }
225
+
226
+ console.log(`\n✅ "${brandName}" demo app installed (${installed} files).`);
227
+ console.log(' The app uses the NEUTRAL color palette.');
228
+ console.log(` Run "npm run brand:${brandName}" to switch to ${brandName} colors.\n`);
229
+ process.exit(0);
155
230
  }
156
231
 
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');
232
+ // ── Default: Apply brand colors only ────────────────────────────────────────
233
+
234
+ console.log(`\n🎨 Applying "${brandName}" brand colors...\n`);
235
+
236
+ let installed = 0;
237
+
238
+ // 1. global.css → src/styles/global.css (always overwrite)
239
+ const brandCSS = path.join(brandDir, 'global.css');
240
+ const targetCSS = path.join(cwd, 'src/styles/global.css');
241
+ if (fs.existsSync(brandCSS)) {
242
+ fs.mkdirSync(path.dirname(targetCSS), { recursive: true });
243
+ fs.copyFileSync(brandCSS, targetCSS);
244
+ console.log(' ✓ Brand theme applied (global.css)');
162
245
  installed++;
163
246
  }
164
247
 
165
- // 7. Logo → src/assets/images/
248
+ // 2. Logo → src/assets/images/
166
249
  const logo = path.join(brandDir, 'engine_logo.png');
167
250
  if (fs.existsSync(logo)) {
168
251
  const logoTarget = path.join(cwd, 'src/assets/images/engine_logo.png');
@@ -172,8 +255,8 @@ if (fs.existsSync(logo)) {
172
255
  installed++;
173
256
  }
174
257
 
175
- // 8. Write .brand marker so reset script knows which brand is active
258
+ // 3. Write .brand marker
176
259
  fs.writeFileSync(path.join(cwd, '.brand'), brandName + '\n', 'utf-8');
177
260
 
178
- console.log(`\n✅ "${brandName}" brand applied (${installed} files installed).`);
261
+ console.log(`\n✅ "${brandName}" brand colors applied (${installed} files).`);
179
262
  console.log(' Run "npm run brand:reset" to revert to neutral theme.\n');