@schandlergarcia/sf-web-components 1.0.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 (195) hide show
  1. package/.a4drules/skills/building-data-visualization/SKILL.md +72 -0
  2. package/.a4drules/skills/building-data-visualization/implementation/bar-line-chart.md +316 -0
  3. package/.a4drules/skills/building-data-visualization/implementation/dashboard-layout.md +189 -0
  4. package/.a4drules/skills/building-data-visualization/implementation/donut-chart.md +181 -0
  5. package/.a4drules/skills/building-data-visualization/implementation/stat-card.md +150 -0
  6. package/.a4drules/skills/building-react-components/SKILL.md +96 -0
  7. package/.a4drules/skills/building-react-components/implementation/component.md +78 -0
  8. package/.a4drules/skills/building-react-components/implementation/header-footer.md +132 -0
  9. package/.a4drules/skills/building-react-components/implementation/page.md +93 -0
  10. package/.a4drules/skills/configuring-csp-trusted-sites/SKILL.md +90 -0
  11. package/.a4drules/skills/configuring-csp-trusted-sites/implementation/metadata-format.md +281 -0
  12. package/.a4drules/skills/configuring-webapp-metadata/SKILL.md +158 -0
  13. package/.a4drules/skills/creating-webapp/SKILL.md +140 -0
  14. package/.a4drules/skills/deploying-to-salesforce/SKILL.md +226 -0
  15. package/.a4drules/skills/implementing-file-upload/SKILL.md +396 -0
  16. package/.a4drules/skills/installing-webapp-features/SKILL.md +210 -0
  17. package/.a4drules/skills/managing-agentforce-conversation-client/SKILL.md +186 -0
  18. package/.a4drules/skills/managing-agentforce-conversation-client/references/constraints.md +134 -0
  19. package/.a4drules/skills/managing-agentforce-conversation-client/references/examples.md +132 -0
  20. package/.a4drules/skills/managing-agentforce-conversation-client/references/style-tokens.md +101 -0
  21. package/.a4drules/skills/managing-agentforce-conversation-client/references/troubleshooting.md +57 -0
  22. package/.a4drules/skills/using-salesforce-data/SKILL.md +363 -0
  23. package/.a4drules/skills/using-salesforce-data/graphql-search.sh +139 -0
  24. package/.a4drules/webapp-data.md +353 -0
  25. package/.a4drules/webapp-ui.md +16 -0
  26. package/README.md +124 -0
  27. package/dist/components/library/cards/ActionList.js +27 -0
  28. package/dist/components/library/cards/ActionList.js.map +1 -0
  29. package/dist/components/library/cards/ActivityCard.js +40 -0
  30. package/dist/components/library/cards/ActivityCard.js.map +1 -0
  31. package/dist/components/library/cards/BaseCard.js +89 -0
  32. package/dist/components/library/cards/BaseCard.js.map +1 -0
  33. package/dist/components/library/cards/CalloutCard.js +28 -0
  34. package/dist/components/library/cards/CalloutCard.js.map +1 -0
  35. package/dist/components/library/cards/ChartCard.js +79 -0
  36. package/dist/components/library/cards/ChartCard.js.map +1 -0
  37. package/dist/components/library/cards/FeedPanel.js +38 -0
  38. package/dist/components/library/cards/FeedPanel.js.map +1 -0
  39. package/dist/components/library/cards/ListCard.js +112 -0
  40. package/dist/components/library/cards/ListCard.js.map +1 -0
  41. package/dist/components/library/cards/MetricCard.js +86 -0
  42. package/dist/components/library/cards/MetricCard.js.map +1 -0
  43. package/dist/components/library/cards/MetricsStrip.js +60 -0
  44. package/dist/components/library/cards/MetricsStrip.js.map +1 -0
  45. package/dist/components/library/cards/SectionCard.js +59 -0
  46. package/dist/components/library/cards/SectionCard.js.map +1 -0
  47. package/dist/components/library/cards/StatusCard.js +137 -0
  48. package/dist/components/library/cards/StatusCard.js.map +1 -0
  49. package/dist/components/library/cards/TableCard.js +244 -0
  50. package/dist/components/library/cards/TableCard.js.map +1 -0
  51. package/dist/components/library/cards/WidgetCard.js +60 -0
  52. package/dist/components/library/cards/WidgetCard.js.map +1 -0
  53. package/dist/components/library/charts/D3Chart.js +74 -0
  54. package/dist/components/library/charts/D3Chart.js.map +1 -0
  55. package/dist/components/library/charts/D3ChartTemplates.js +44 -0
  56. package/dist/components/library/charts/D3ChartTemplates.js.map +1 -0
  57. package/dist/components/library/charts/GeoMap.js +229 -0
  58. package/dist/components/library/charts/GeoMap.js.map +1 -0
  59. package/dist/components/library/chat/ChatBar.js +194 -0
  60. package/dist/components/library/chat/ChatBar.js.map +1 -0
  61. package/dist/components/library/chat/ChatInput.js +67 -0
  62. package/dist/components/library/chat/ChatInput.js.map +1 -0
  63. package/dist/components/library/chat/ChatMessage.js +112 -0
  64. package/dist/components/library/chat/ChatMessage.js.map +1 -0
  65. package/dist/components/library/chat/ChatMessageList.js +50 -0
  66. package/dist/components/library/chat/ChatMessageList.js.map +1 -0
  67. package/dist/components/library/chat/ChatPanel.js +77 -0
  68. package/dist/components/library/chat/ChatPanel.js.map +1 -0
  69. package/dist/components/library/chat/ChatSuggestions.js +22 -0
  70. package/dist/components/library/chat/ChatSuggestions.js.map +1 -0
  71. package/dist/components/library/chat/ChatToolCall.js +87 -0
  72. package/dist/components/library/chat/ChatToolCall.js.map +1 -0
  73. package/dist/components/library/chat/ChatTypingIndicator.js +20 -0
  74. package/dist/components/library/chat/ChatTypingIndicator.js.map +1 -0
  75. package/dist/components/library/chat/ChatWelcome.js +24 -0
  76. package/dist/components/library/chat/ChatWelcome.js.map +1 -0
  77. package/dist/components/library/chat/useChatState.js +77 -0
  78. package/dist/components/library/chat/useChatState.js.map +1 -0
  79. package/dist/components/library/data/DataModeProvider.js +47 -0
  80. package/dist/components/library/data/DataModeProvider.js.map +1 -0
  81. package/dist/components/library/data/DataModeToggle.js +28 -0
  82. package/dist/components/library/data/DataModeToggle.js.map +1 -0
  83. package/dist/components/library/data/filterUtils.js +71 -0
  84. package/dist/components/library/data/filterUtils.js.map +1 -0
  85. package/dist/components/library/data/useDataSource.js +13 -0
  86. package/dist/components/library/data/useDataSource.js.map +1 -0
  87. package/dist/components/library/data/usePageFilters.js +46 -0
  88. package/dist/components/library/data/usePageFilters.js.map +1 -0
  89. package/dist/components/library/filters/FilterBar.js +89 -0
  90. package/dist/components/library/filters/FilterBar.js.map +1 -0
  91. package/dist/components/library/filters/SearchFilter.js +44 -0
  92. package/dist/components/library/filters/SearchFilter.js.map +1 -0
  93. package/dist/components/library/filters/SelectFilter.js +44 -0
  94. package/dist/components/library/filters/SelectFilter.js.map +1 -0
  95. package/dist/components/library/filters/ToggleFilter.js +48 -0
  96. package/dist/components/library/filters/ToggleFilter.js.map +1 -0
  97. package/dist/components/library/forms/FormField.js +256 -0
  98. package/dist/components/library/forms/FormField.js.map +1 -0
  99. package/dist/components/library/forms/FormModal.js +161 -0
  100. package/dist/components/library/forms/FormModal.js.map +1 -0
  101. package/dist/components/library/forms/FormRenderer.js +32 -0
  102. package/dist/components/library/forms/FormRenderer.js.map +1 -0
  103. package/dist/components/library/forms/FormSection.js +49 -0
  104. package/dist/components/library/forms/FormSection.js.map +1 -0
  105. package/dist/components/library/forms/useFormState.js +85 -0
  106. package/dist/components/library/forms/useFormState.js.map +1 -0
  107. package/dist/components/library/heroui/Accordion.js +12 -0
  108. package/dist/components/library/heroui/Accordion.js.map +1 -0
  109. package/dist/components/library/heroui/Alert.js +12 -0
  110. package/dist/components/library/heroui/Alert.js.map +1 -0
  111. package/dist/components/library/heroui/Badge.js +12 -0
  112. package/dist/components/library/heroui/Badge.js.map +1 -0
  113. package/dist/components/library/heroui/Breadcrumbs.js +12 -0
  114. package/dist/components/library/heroui/Breadcrumbs.js.map +1 -0
  115. package/dist/components/library/heroui/Button.js +18 -0
  116. package/dist/components/library/heroui/Button.js.map +1 -0
  117. package/dist/components/library/heroui/Card.js +12 -0
  118. package/dist/components/library/heroui/Card.js.map +1 -0
  119. package/dist/components/library/heroui/Drawer.js +12 -0
  120. package/dist/components/library/heroui/Drawer.js.map +1 -0
  121. package/dist/components/library/heroui/Dropdown.js +12 -0
  122. package/dist/components/library/heroui/Dropdown.js.map +1 -0
  123. package/dist/components/library/heroui/Input.js +10 -0
  124. package/dist/components/library/heroui/Input.js.map +1 -0
  125. package/dist/components/library/heroui/Kbd.js +12 -0
  126. package/dist/components/library/heroui/Kbd.js.map +1 -0
  127. package/dist/components/library/heroui/Meter.js +12 -0
  128. package/dist/components/library/heroui/Meter.js.map +1 -0
  129. package/dist/components/library/heroui/Modal.js +12 -0
  130. package/dist/components/library/heroui/Modal.js.map +1 -0
  131. package/dist/components/library/heroui/Pagination.js +12 -0
  132. package/dist/components/library/heroui/Pagination.js.map +1 -0
  133. package/dist/components/library/heroui/ProgressBar.js +12 -0
  134. package/dist/components/library/heroui/ProgressBar.js.map +1 -0
  135. package/dist/components/library/heroui/ProgressCircle.js +12 -0
  136. package/dist/components/library/heroui/ProgressCircle.js.map +1 -0
  137. package/dist/components/library/heroui/ScrollShadow.js +12 -0
  138. package/dist/components/library/heroui/ScrollShadow.js.map +1 -0
  139. package/dist/components/library/heroui/Select.js +12 -0
  140. package/dist/components/library/heroui/Select.js.map +1 -0
  141. package/dist/components/library/heroui/Separator.js +12 -0
  142. package/dist/components/library/heroui/Separator.js.map +1 -0
  143. package/dist/components/library/heroui/Skeleton.js +12 -0
  144. package/dist/components/library/heroui/Skeleton.js.map +1 -0
  145. package/dist/components/library/heroui/Tabs.js +12 -0
  146. package/dist/components/library/heroui/Tabs.js.map +1 -0
  147. package/dist/components/library/heroui/Toast.js +13 -0
  148. package/dist/components/library/heroui/Toast.js.map +1 -0
  149. package/dist/components/library/heroui/Toggle.js +12 -0
  150. package/dist/components/library/heroui/Toggle.js.map +1 -0
  151. package/dist/components/library/heroui/Tooltip.js +12 -0
  152. package/dist/components/library/heroui/Tooltip.js.map +1 -0
  153. package/dist/components/library/layout/PageContainer.js +9 -0
  154. package/dist/components/library/layout/PageContainer.js.map +1 -0
  155. package/dist/components/library/skeletons/CardSkeleton.js +29 -0
  156. package/dist/components/library/skeletons/CardSkeleton.js.map +1 -0
  157. package/dist/components/library/theme/AppThemeProvider.js +55 -0
  158. package/dist/components/library/theme/AppThemeProvider.js.map +1 -0
  159. package/dist/components/library/theme/tokens.js +55 -0
  160. package/dist/components/library/theme/tokens.js.map +1 -0
  161. package/dist/components/library/ui/Avatar.js +33 -0
  162. package/dist/components/library/ui/Avatar.js.map +1 -0
  163. package/dist/components/library/ui/Button.js +50 -0
  164. package/dist/components/library/ui/Button.js.map +1 -0
  165. package/dist/components/library/ui/Card.js +21 -0
  166. package/dist/components/library/ui/Card.js.map +1 -0
  167. package/dist/components/library/ui/Chip.js +31 -0
  168. package/dist/components/library/ui/Chip.js.map +1 -0
  169. package/dist/components/library/ui/Container.js +42 -0
  170. package/dist/components/library/ui/Container.js.map +1 -0
  171. package/dist/components/library/ui/EmptyState.js +27 -0
  172. package/dist/components/library/ui/EmptyState.js.map +1 -0
  173. package/dist/components/library/ui/Input.js +22 -0
  174. package/dist/components/library/ui/Input.js.map +1 -0
  175. package/dist/components/library/ui/Spinner.js +64 -0
  176. package/dist/components/library/ui/Spinner.js.map +1 -0
  177. package/dist/components/library/ui/Text.js +43 -0
  178. package/dist/components/library/ui/Text.js.map +1 -0
  179. package/dist/components/library/ui/Toggle.js +47 -0
  180. package/dist/components/library/ui/Toggle.js.map +1 -0
  181. package/dist/components/workspace/ComponentRegistry.js +231 -0
  182. package/dist/components/workspace/ComponentRegistry.js.map +1 -0
  183. package/dist/index.js +185 -0
  184. package/dist/index.js.map +1 -0
  185. package/dist/lib/utils.js +9 -0
  186. package/dist/lib/utils.js.map +1 -0
  187. package/dist/node_modules/clsx/dist/clsx.js +17 -0
  188. package/dist/node_modules/clsx/dist/clsx.js.map +1 -0
  189. package/dist/node_modules/tailwind-merge/dist/bundle-mjs.js +2925 -0
  190. package/dist/node_modules/tailwind-merge/dist/bundle-mjs.js.map +1 -0
  191. package/package.json +81 -0
  192. package/scripts/get-graphql-schema.mjs +68 -0
  193. package/scripts/reset-command-center.sh +358 -0
  194. package/scripts/rewrite-e2e-assets.mjs +23 -0
  195. package/scripts/validate-dashboard.sh +290 -0
@@ -0,0 +1,358 @@
1
+ #!/usr/bin/env bash
2
+ #
3
+ # reset-command-center.sh
4
+ #
5
+ # Resets the app to the baseline state:
6
+ # - Home (/) renders the Command Center (blank dashboard)
7
+ # - Search (/search) renders the global search input
8
+ # - Nav bar visible on Home and Search pages only
9
+ # - Salesforce SDK stubs are in place for local dev
10
+ #
11
+ # Usage:
12
+ # npm run reset:command-center
13
+ # # or directly:
14
+ # bash scripts/reset-command-center.sh
15
+ #
16
+ set -euo pipefail
17
+
18
+ ROOT="$(cd "$(dirname "$0")/.." && pwd)"
19
+ cd "$ROOT"
20
+
21
+ echo ""
22
+ echo "╔════════════════════════════════════════════════╗"
23
+ echo "║ Reset App → Baseline State ║"
24
+ echo "╚════════════════════════════════════════════════╝"
25
+ echo ""
26
+
27
+ # ── 1. Remove custom dashboard pages (keep library & workspace) ─────────────
28
+
29
+ echo "→ Removing custom dashboards…"
30
+
31
+ removed=0
32
+ for f in src/components/pages/*Dashboard*.jsx src/components/pages/*Dashboard*.tsx; do
33
+ if [ -f "$f" ]; then
34
+ rm "$f"
35
+ echo " ✓ Removed $(basename "$f")"
36
+ removed=$((removed + 1))
37
+ fi
38
+ done
39
+
40
+ if [ "$removed" -eq 0 ]; then
41
+ echo " (no custom dashboards found)"
42
+ fi
43
+ echo ""
44
+
45
+ # ── 2. Write the blank starter dashboard ────────────────────────────────────
46
+
47
+ BLANK="src/components/pages/BlankDashboard.jsx"
48
+ echo "→ Creating ${BLANK}..."
49
+
50
+ cat > "$BLANK" << 'DASHBOARD_EOF'
51
+ import React from "react";
52
+ import { RocketLaunchIcon } from "@heroicons/react/24/outline";
53
+ import { EmptyState } from "@/components/library";
54
+
55
+ export default function BlankDashboard() {
56
+ return (
57
+ <div className="flex min-h-screen items-center justify-center bg-slate-50 dark:bg-slate-950 transition-colors">
58
+ <EmptyState
59
+ size="lg"
60
+ icon={<RocketLaunchIcon className="h-14 w-14" />}
61
+ heading="Bespoke App Template"
62
+ body="Component library loaded and ready to go."
63
+ />
64
+ </div>
65
+ );
66
+ }
67
+ DASHBOARD_EOF
68
+
69
+ echo " ✓ Created blank starter dashboard"
70
+ echo ""
71
+
72
+ # ── 3. Update CommandCenter.tsx to use BlankDashboard ────────────────────────
73
+ # Preserves theme initialMode if previously customized.
74
+
75
+ WRAPPER="src/components/pages/CommandCenter.tsx"
76
+ echo "→ Updating ${WRAPPER}..."
77
+
78
+ # Extract existing initialMode from AppThemeProvider (default: "light")
79
+ THEME_MODE="light"
80
+ if [ -f "$WRAPPER" ]; then
81
+ EXISTING_MODE=$(grep -oP 'AppThemeProvider\s+initialMode="\K[^"]+' "$WRAPPER" 2>/dev/null || true)
82
+ if [ -n "$EXISTING_MODE" ]; then
83
+ THEME_MODE="$EXISTING_MODE"
84
+ echo " ℹ Preserving theme mode: ${THEME_MODE}"
85
+ fi
86
+ fi
87
+
88
+ cat > "$WRAPPER" << WRAPPER_EOF
89
+ import AppThemeProvider from "@/components/library/theme/AppThemeProvider";
90
+ import DataModeProvider from "@/components/library/data/DataModeProvider";
91
+ import { Toast } from "@heroui/react";
92
+ import BlankDashboard from "./BlankDashboard";
93
+
94
+ export default function CommandCenter() {
95
+ return (
96
+ <div className="heroui-scope">
97
+ <AppThemeProvider initialMode="${THEME_MODE}">
98
+ <DataModeProvider initialMode="sample">
99
+ <BlankDashboard />
100
+ <Toast.Provider placement="bottom end" />
101
+ </DataModeProvider>
102
+ </AppThemeProvider>
103
+ </div>
104
+ );
105
+ }
106
+ WRAPPER_EOF
107
+
108
+ echo " ✓ Wired up BlankDashboard"
109
+ echo ""
110
+
111
+ # ── 4. Reset Home.tsx to render CommandCenter ────────────────────────────────
112
+
113
+ HOME="src/pages/Home.tsx"
114
+ echo "→ Updating ${HOME}..."
115
+
116
+ cat > "$HOME" << 'HOME_EOF'
117
+ import CommandCenter from "../components/pages/CommandCenter";
118
+
119
+ export default function Home() {
120
+ return <CommandCenter />;
121
+ }
122
+ HOME_EOF
123
+
124
+ echo " ✓ Home renders CommandCenter"
125
+ echo ""
126
+
127
+ # ── 5. Ensure Search.tsx page exists ─────────────────────────────────────────
128
+
129
+ SEARCH="src/pages/Search.tsx"
130
+ echo "→ Updating ${SEARCH}..."
131
+
132
+ cat > "$SEARCH" << 'SEARCH_EOF'
133
+ import { GlobalSearchInput } from "../features/global-search/components/search/GlobalSearchInput";
134
+
135
+ export default function Search() {
136
+ return (
137
+ <div className="flex min-h-[80vh] items-center justify-center px-4 sm:px-6 lg:px-8">
138
+ <div className="w-full max-w-4xl">
139
+ <div className="text-center mb-8">
140
+ <h1 className="text-4xl font-bold text-slate-900 dark:text-slate-50 mb-4">Search</h1>
141
+ <p className="text-lg text-slate-600 dark:text-slate-300">Find records across your organization.</p>
142
+ </div>
143
+ <GlobalSearchInput />
144
+ </div>
145
+ </div>
146
+ );
147
+ }
148
+ SEARCH_EOF
149
+
150
+ echo " ✓ Search page ready"
151
+ echo ""
152
+
153
+ # ── 6. Reset routes.tsx ──────────────────────────────────────────────────────
154
+
155
+ ROUTES="src/routes.tsx"
156
+ echo "→ Updating ${ROUTES}..."
157
+
158
+ cat > "$ROUTES" << 'ROUTES_EOF'
159
+ import type { RouteObject } from 'react-router';
160
+ import { lazy, Suspense } from "react";
161
+
162
+ const AppLayout = lazy(() => import('./appLayout'));
163
+ const Home = lazy(() => import('./pages/Home'));
164
+ const Search = lazy(() => import('./pages/Search'));
165
+ const NotFound = lazy(() => import('./pages/NotFound'));
166
+ const TestAccPage = lazy(() => import("./pages/TestAccPage"));
167
+ const GlobalSearch = lazy(() => import("./features/global-search/pages/GlobalSearch"));
168
+ const DetailPage = lazy(() => import("./features/global-search/pages/DetailPage"));
169
+
170
+ function SuspenseWrap({ children }: { children: React.ReactNode }) {
171
+ return <Suspense fallback={<div className="flex min-h-screen items-center justify-center text-sm text-muted-foreground">Loading...</div>}>{children}</Suspense>;
172
+ }
173
+
174
+ export const routes: RouteObject[] = [
175
+ {
176
+ path: "/",
177
+ element: (
178
+ <SuspenseWrap>
179
+ <AppLayout />
180
+ </SuspenseWrap>
181
+ ),
182
+ children: [
183
+ {
184
+ index: true,
185
+ element: <SuspenseWrap><Home /></SuspenseWrap>,
186
+ handle: { showInNavigation: true, showNavBar: true, label: 'Home' }
187
+ },
188
+ {
189
+ path: "search",
190
+ element: <SuspenseWrap><Search /></SuspenseWrap>,
191
+ handle: { showInNavigation: true, showNavBar: true, label: 'Search' }
192
+ },
193
+ {
194
+ path: "test-acc",
195
+ element: <SuspenseWrap><TestAccPage /></SuspenseWrap>,
196
+ handle: { showInNavigation: true, label: "Test ACC" }
197
+ },
198
+ {
199
+ path: "global-search/:query",
200
+ element: (
201
+ <SuspenseWrap>
202
+ <GlobalSearch />
203
+ </SuspenseWrap>
204
+ ),
205
+ handle: { showInNavigation: false }
206
+ },
207
+ {
208
+ path: "object/:objectApiName/:recordId",
209
+ element: (
210
+ <SuspenseWrap>
211
+ <DetailPage />
212
+ </SuspenseWrap>
213
+ ),
214
+ handle: { showInNavigation: false }
215
+ },
216
+ {
217
+ path: '*',
218
+ element: <SuspenseWrap><NotFound /></SuspenseWrap>
219
+ },
220
+ ]
221
+ }
222
+ ];
223
+ ROUTES_EOF
224
+
225
+ echo " ✓ Routes updated (Home at /, Search at /search, nav on both)"
226
+ echo ""
227
+
228
+ # ── 7. Reset appLayout.tsx (nav on Home/Search only) ─────────────────────────
229
+
230
+ LAYOUT="src/appLayout.tsx"
231
+ echo "→ Updating ${LAYOUT}..."
232
+
233
+ cat > "$LAYOUT" << 'LAYOUT_EOF'
234
+ import { AgentforceConversationClient } from "./components/AgentforceConversationClient";
235
+ import { Outlet, Link, useLocation, useMatches } from "react-router";
236
+ import { getAllRoutes } from "./router-utils";
237
+ import { useState } from "react";
238
+
239
+ export default function AppLayout() {
240
+ const [isOpen, setIsOpen] = useState(false);
241
+ const location = useLocation();
242
+ const matches = useMatches();
243
+
244
+ const showNavBar = matches.some(
245
+ (m) => (m.handle as Record<string, unknown>)?.showNavBar === true,
246
+ );
247
+
248
+ const isActive = (path: string) => location.pathname === path;
249
+
250
+ const toggleMenu = () => setIsOpen(!isOpen);
251
+
252
+ const navigationRoutes: { path: string; label: string }[] = getAllRoutes()
253
+ .filter(
254
+ (route) =>
255
+ route.handle?.showInNavigation === true &&
256
+ route.fullPath !== undefined &&
257
+ route.handle?.label !== undefined,
258
+ )
259
+ .map(
260
+ (route) =>
261
+ ({
262
+ path: route.fullPath,
263
+ label: route.handle?.label,
264
+ }) as { path: string; label: string },
265
+ );
266
+
267
+ return (
268
+ <>
269
+ {showNavBar && (
270
+ <nav className="bg-white border-b border-gray-200">
271
+ <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
272
+ <div className="flex justify-between items-center h-16">
273
+ <Link to="/" className="text-xl font-semibold text-gray-900">
274
+ React App
275
+ </Link>
276
+ <button
277
+ onClick={toggleMenu}
278
+ className="p-2 rounded-md text-gray-700 hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-blue-500"
279
+ aria-label="Toggle menu"
280
+ >
281
+ <div className="w-6 h-6 flex flex-col justify-center space-y-1.5">
282
+ <span
283
+ className={`block h-0.5 w-6 bg-current transition-all ${
284
+ isOpen ? "rotate-45 translate-y-2" : ""
285
+ }`}
286
+ />
287
+ <span
288
+ className={`block h-0.5 w-6 bg-current transition-all ${isOpen ? "opacity-0" : ""}`}
289
+ />
290
+ <span
291
+ className={`block h-0.5 w-6 bg-current transition-all ${
292
+ isOpen ? "-rotate-45 -translate-y-2" : ""
293
+ }`}
294
+ />
295
+ </div>
296
+ </button>
297
+ </div>
298
+ {isOpen && (
299
+ <div className="pb-4">
300
+ <div className="flex flex-col space-y-2">
301
+ {navigationRoutes.map((item) => (
302
+ <Link
303
+ key={item.path}
304
+ to={item.path}
305
+ onClick={() => setIsOpen(false)}
306
+ className={`px-3 py-2 rounded-md text-sm font-medium transition-colors ${
307
+ isActive(item.path)
308
+ ? "bg-blue-100 text-blue-700"
309
+ : "text-gray-700 hover:bg-gray-100"
310
+ }`}
311
+ >
312
+ {item.label}
313
+ </Link>
314
+ ))}
315
+ </div>
316
+ </div>
317
+ )}
318
+ </div>
319
+ </nav>
320
+ )}
321
+ <Outlet />
322
+ <AgentforceConversationClient />
323
+ </>
324
+ );
325
+ }
326
+ LAYOUT_EOF
327
+
328
+ echo " ✓ Layout updated (nav bar on Home/Search pages)"
329
+ echo ""
330
+
331
+ # ── 8. Clear Vite cache ─────────────────────────────────────────────────────
332
+
333
+ echo "→ Cleaning caches…"
334
+ rm -rf node_modules/.vite 2>/dev/null && echo " ✓ Cleared Vite cache" || true
335
+ echo ""
336
+
337
+ # ── Done ─────────────────────────────────────────────────────────────────────
338
+
339
+ echo "╔════════════════════════════════════════════════╗"
340
+ echo "║ ✓ Reset complete! ║"
341
+ echo "║ ║"
342
+ echo "║ Everything preserved: ║"
343
+ echo "║ • Theme (global.css + initialMode) ║"
344
+ echo "║ • Component library (cards, charts, etc.) ║"
345
+ echo "║ • HeroUI wrappers & theme providers ║"
346
+ echo "║ • Salesforce SDK stubs for local dev ║"
347
+ echo "║ • All styles & dependencies ║"
348
+ echo "║ ║"
349
+ echo "║ Layout: ║"
350
+ echo "║ / → Home (CommandCenter dashboard) ║"
351
+ echo "║ /search → Search (global search input) ║"
352
+ echo "║ Nav bar on Home + Search pages only ║"
353
+ echo "║ ║"
354
+ echo "║ Start building: ║"
355
+ echo "║ Edit src/components/pages/BlankDashboard.jsx║"
356
+ echo "║ npm run dev ║"
357
+ echo "╚════════════════════════════════════════════════╝"
358
+ echo ""
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Prepares dist/ for e2e: root-relative asset paths + SPA fallback for serve.
3
+ */
4
+ import { readFileSync, writeFileSync } from 'node:fs';
5
+ import { join, dirname } from 'node:path';
6
+ import { fileURLToPath } from 'node:url';
7
+
8
+ const __dirname = dirname(fileURLToPath(import.meta.url));
9
+ const distDir = join(__dirname, '..', 'dist');
10
+
11
+ // Rewrite index.html so asset paths are root-relative (/assets/...)
12
+ const indexPath = join(distDir, 'index.html');
13
+ let html = readFileSync(indexPath, 'utf8');
14
+ html = html.replace(/(src|href)="[^"]*\/assets\//g, '$1="/assets/');
15
+ writeFileSync(indexPath, html);
16
+
17
+ // SPA fallback so /about, /non-existent-route etc. serve index.html
18
+ writeFileSync(
19
+ join(distDir, 'serve.json'),
20
+ JSON.stringify({
21
+ rewrites: [{ source: '**', destination: '/index.html' }],
22
+ })
23
+ );
@@ -0,0 +1,290 @@
1
+ #!/usr/bin/env bash
2
+ #
3
+ # validate-dashboard.sh
4
+ #
5
+ # Checks command center dashboard pages for common rule violations.
6
+ # Run after building a dashboard to catch issues before review.
7
+ #
8
+ # Usage:
9
+ # bash scripts/validate-dashboard.sh
10
+ #
11
+ set -euo pipefail
12
+
13
+ ROOT="$(cd "$(dirname "$0")/.." && pwd)"
14
+ cd "$ROOT"
15
+
16
+ PAGES_DIR="src/components/pages"
17
+ TRAVEL_DIR="src/components/travel"
18
+ ERRORS=0
19
+ WARNINGS=0
20
+
21
+ red() { echo -e "\033[0;31m✗ $1\033[0m"; }
22
+ yellow() { echo -e "\033[0;33m⚠ $1\033[0m"; }
23
+ green() { echo -e "\033[0;32m✓ $1\033[0m"; }
24
+
25
+ echo ""
26
+ echo "╔════════════════════════════════════════════════╗"
27
+ echo "║ Dashboard Rule Validator ║"
28
+ echo "╚════════════════════════════════════════════════╝"
29
+ echo ""
30
+
31
+ # ── 1. Check CommandCenter.tsx imports a real dashboard ──────────────────────
32
+
33
+ echo "── Wiring ──"
34
+
35
+ WRAPPER="$PAGES_DIR/CommandCenter.tsx"
36
+ if grep -q 'BlankDashboard' "$WRAPPER"; then
37
+ red "CommandCenter.tsx still imports BlankDashboard — no dashboard is wired up"
38
+ ERRORS=$((ERRORS + 1))
39
+ else
40
+ green "CommandCenter.tsx imports a custom dashboard"
41
+ fi
42
+ echo ""
43
+
44
+ # ── 2. Find all dashboard files to scan ──────────────────────────────────────
45
+
46
+ # Collect all .jsx/.tsx in pages/ and travel/ dirs (excluding BlankDashboard, HelloWorld, CommandCenter)
47
+ SCAN_FILES=()
48
+ for dir in "$PAGES_DIR" "$TRAVEL_DIR" "src/pages"; do
49
+ if [ -d "$dir" ]; then
50
+ while IFS= read -r f; do
51
+ base=$(basename "$f")
52
+ case "$base" in
53
+ BlankDashboard.*|HelloWorld.*|CommandCenter.*|Home.*|NotFound.*|Search.*|TestAccPage.*) continue ;;
54
+ *) SCAN_FILES+=("$f") ;;
55
+ esac
56
+ done < <(find "$dir" -maxdepth 1 \( -name "*.jsx" -o -name "*.tsx" \) | sort)
57
+ fi
58
+ done
59
+
60
+ if [ ${#SCAN_FILES[@]} -eq 0 ]; then
61
+ yellow "No dashboard files found to scan"
62
+ echo ""
63
+ exit 0
64
+ fi
65
+
66
+ echo "── Scanning ${#SCAN_FILES[@]} files ──"
67
+ printf " %s\n" "${SCAN_FILES[@]}"
68
+ echo ""
69
+
70
+ # ── 3. Check for hand-rolled HTML cards ──────────────────────────────────────
71
+
72
+ echo "── Hand-rolled HTML (should use library components) ──"
73
+
74
+ for f in "${SCAN_FILES[@]}"; do
75
+ base=$(basename "$f")
76
+
77
+ # Pattern: bg-white ... border ... rounded ... shadow — classic hand-rolled card
78
+ count=$(grep -cE 'bg-white.*border.*rounded|bg-white.*rounded.*shadow|rounded-\[10px\].*shadow' "$f" 2>/dev/null || true)
79
+ if [ "$count" -gt 0 ]; then
80
+ red "$base: $count hand-rolled card(s) (bg-white border rounded shadow — use MetricCard/ListCard/WidgetCard/etc.)"
81
+ ERRORS=$((ERRORS + count))
82
+ fi
83
+
84
+ # Pattern: raw <table> element
85
+ count=$(grep -cE '<table\b' "$f" 2>/dev/null || true)
86
+ if [ "$count" -gt 0 ]; then
87
+ red "$base: $count raw <table> element(s) — use TableCard from library"
88
+ ERRORS=$((ERRORS + count))
89
+ fi
90
+
91
+ # Pattern: raw <svg> element (hand-rolled charts)
92
+ count=$(grep -cE '<svg\b' "$f" 2>/dev/null || true)
93
+ if [ "$count" -gt 0 ]; then
94
+ red "$base: $count raw <svg> element(s) — use ChartCard + D3Chart or GeoMap"
95
+ ERRORS=$((ERRORS + count))
96
+ fi
97
+ done
98
+ echo ""
99
+
100
+ # ── 4. Check for navigation inside dashboard ─────────────────────────────────
101
+
102
+ echo "── Dashboard navigation (should not exist) ──"
103
+
104
+ for f in "${SCAN_FILES[@]}"; do
105
+ base=$(basename "$f")
106
+
107
+ count=$(grep -cE '<nav\b' "$f" 2>/dev/null || true)
108
+ if [ "$count" -gt 0 ]; then
109
+ red "$base: $count <nav> element(s) inside dashboard — navigation is handled by appLayout.tsx"
110
+ ERRORS=$((ERRORS + count))
111
+ fi
112
+
113
+ # Pattern: sticky tab bar acting as navigation
114
+ if grep -qE 'sticky.*top-0|activeTab|setActiveTab' "$f" 2>/dev/null; then
115
+ if grep -qE "tab.*\.map|tabs\.map|Tab.*onClick" "$f" 2>/dev/null; then
116
+ red "$base: multi-tab layout detected — build a single scrollable page, not tab-swapped content"
117
+ ERRORS=$((ERRORS + 1))
118
+ fi
119
+ fi
120
+ done
121
+ echo ""
122
+
123
+ # ── 4b. Check for wrong D3Chart API usage ────────────────────────────────────
124
+
125
+ echo "── D3Chart API (must use renderChart, not template) ──"
126
+
127
+ for f in "${SCAN_FILES[@]}"; do
128
+ base=$(basename "$f")
129
+
130
+ count=$(grep -cE 'template=\{D3ChartTemplates' "$f" 2>/dev/null || true)
131
+ if [ "$count" -gt 0 ]; then
132
+ red "$base: $count use(s) of template={D3ChartTemplates...} — use renderChart={D3ChartTemplates.lineChart} instead"
133
+ ERRORS=$((ERRORS + count))
134
+ fi
135
+
136
+ count=$(grep -cE 'D3ChartTemplates\.(LINE|BAR|DONUT|AREA|PIE)' "$f" 2>/dev/null || true)
137
+ if [ "$count" -gt 0 ]; then
138
+ red "$base: $count use(s) of non-existent template (LINE/BAR/DONUT/AREA) — only .lineChart and .groupedBarChart exist"
139
+ ERRORS=$((ERRORS + count))
140
+ fi
141
+ done
142
+ echo ""
143
+
144
+ # ── 5. Check for forbidden colors ────────────────────────────────────────────
145
+
146
+ echo "── Forbidden colors (use slate scale instead) ──"
147
+
148
+ for f in "${SCAN_FILES[@]}"; do
149
+ base=$(basename "$f")
150
+
151
+ for pattern in 'text-black' 'text-white' 'bg-black'; do
152
+ count=$(grep -cF "$pattern" "$f" 2>/dev/null || true)
153
+ if [ "$count" -gt 0 ]; then
154
+ red "$base: $count use(s) of '$pattern' — use slate scale (text-slate-900, text-slate-50, bg-slate-900)"
155
+ ERRORS=$((ERRORS + count))
156
+ fi
157
+ done
158
+ done
159
+ echo ""
160
+
161
+ # ── 6. Check for inline styles ───────────────────────────────────────────────
162
+
163
+ echo "── Inline styles ──"
164
+
165
+ for f in "${SCAN_FILES[@]}"; do
166
+ base=$(basename "$f")
167
+
168
+ count=$(grep -cE 'style=\{\{' "$f" 2>/dev/null || true)
169
+ if [ "$count" -gt 0 ]; then
170
+ red "$base: $count inline style={{}} — use Tailwind classes or global.css"
171
+ ERRORS=$((ERRORS + count))
172
+ fi
173
+
174
+ count=$(grep -cE '<style>' "$f" 2>/dev/null || true)
175
+ if [ "$count" -gt 0 ]; then
176
+ red "$base: $count <style> tag(s) — use Tailwind classes or global.css"
177
+ ERRORS=$((ERRORS + count))
178
+ fi
179
+ done
180
+ echo ""
181
+
182
+ # ── 7. Check for missing useDataSource ────────────────────────────────────────
183
+
184
+ echo "── Data mode (should use useDataSource) ──"
185
+
186
+ for f in "${SCAN_FILES[@]}"; do
187
+ base=$(basename "$f")
188
+
189
+ if ! grep -qE 'useDataSource|useDataMode' "$f" 2>/dev/null; then
190
+ yellow "$base: no useDataSource/useDataMode — data may not support sample/live switching"
191
+ WARNINGS=$((WARNINGS + 1))
192
+ fi
193
+ done
194
+ echo ""
195
+
196
+ # ── 8. Check for more than 4 metrics in one grid row ─────────────────────────
197
+
198
+ echo "── Grid layout (max 4 per row) ──"
199
+
200
+ for f in "${SCAN_FILES[@]}"; do
201
+ base=$(basename "$f")
202
+
203
+ count=$(grep -cE 'grid-cols-[5-9]|grid-cols-1[0-9]' "$f" 2>/dev/null || true)
204
+ if [ "$count" -gt 0 ]; then
205
+ red "$base: $count grid(s) with 5+ columns — max 4 MetricCards per row"
206
+ ERRORS=$((ERRORS + count))
207
+ fi
208
+ done
209
+ echo ""
210
+
211
+ # ── 9. Check for fixed position without createPortal ─────────────────────────
212
+
213
+ echo "── Portals (fixed position needs createPortal) ──"
214
+
215
+ for f in "${SCAN_FILES[@]}"; do
216
+ base=$(basename "$f")
217
+
218
+ if grep -qE '\bfixed\b' "$f" 2>/dev/null; then
219
+ if ! grep -qE 'createPortal' "$f" 2>/dev/null; then
220
+ red "$base: uses 'fixed' positioning without createPortal"
221
+ ERRORS=$((ERRORS + 1))
222
+ fi
223
+ fi
224
+ done
225
+ echo ""
226
+
227
+ # ── 10. Check file extensions ─────────────────────────────────────────────────
228
+
229
+ echo "── File extensions (dashboard pages should be .jsx) ──"
230
+
231
+ for f in "${SCAN_FILES[@]}"; do
232
+ base=$(basename "$f")
233
+
234
+ if [[ "$f" == *.tsx ]] && [[ "$f" == *components/pages* || "$f" == *components/travel* ]]; then
235
+ yellow "$base: dashboard component uses .tsx — convention is .jsx"
236
+ WARNINGS=$((WARNINGS + 1))
237
+ fi
238
+ done
239
+ echo ""
240
+
241
+ # ── 11. Check for shadcn/Lucide imports in dashboard ─────────────────────────
242
+
243
+ echo "── Wrong imports (no shadcn/Lucide in command center) ──"
244
+
245
+ for f in "${SCAN_FILES[@]}"; do
246
+ base=$(basename "$f")
247
+
248
+ if grep -qE "from ['\"].*components/ui/" "$f" 2>/dev/null; then
249
+ red "$base: imports from shadcn (components/ui/) — use library components only"
250
+ ERRORS=$((ERRORS + 1))
251
+ fi
252
+
253
+ if grep -qE "from ['\"]lucide-react['\"]" "$f" 2>/dev/null; then
254
+ red "$base: imports Lucide icons — use Heroicons in command center"
255
+ ERRORS=$((ERRORS + 1))
256
+ fi
257
+ done
258
+ echo ""
259
+
260
+ # ── 12. Check for space-y-6 wrapper ──────────────────────────────────────────
261
+
262
+ echo "── Section spacing ──"
263
+
264
+ for f in "${SCAN_FILES[@]}"; do
265
+ base=$(basename "$f")
266
+
267
+ if ! grep -qE 'space-y-6' "$f" 2>/dev/null; then
268
+ yellow "$base: no space-y-6 wrapper found — use <div className=\"space-y-6\"> for section spacing"
269
+ WARNINGS=$((WARNINGS + 1))
270
+ fi
271
+ done
272
+ echo ""
273
+
274
+ # ── Summary ───────────────────────────────────────────────────────────────────
275
+
276
+ echo "╔════════════════════════════════════════════════╗"
277
+ if [ "$ERRORS" -eq 0 ] && [ "$WARNINGS" -eq 0 ]; then
278
+ echo "║ ✓ All checks passed! ║"
279
+ elif [ "$ERRORS" -eq 0 ]; then
280
+ echo "║ ⚠ $WARNINGS warning(s), 0 errors ║"
281
+ else
282
+ printf "║ ✗ %-3d error(s), %-3d warning(s) ║\n" "$ERRORS" "$WARNINGS"
283
+ fi
284
+ echo "╚════════════════════════════════════════════════╝"
285
+ echo ""
286
+
287
+ if [ "$ERRORS" -gt 0 ]; then
288
+ echo "Fix all errors before the dashboard is considered complete."
289
+ exit 1
290
+ fi