@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.
- package/.a4drules/skills/building-data-visualization/SKILL.md +72 -0
- package/.a4drules/skills/building-data-visualization/implementation/bar-line-chart.md +316 -0
- package/.a4drules/skills/building-data-visualization/implementation/dashboard-layout.md +189 -0
- package/.a4drules/skills/building-data-visualization/implementation/donut-chart.md +181 -0
- package/.a4drules/skills/building-data-visualization/implementation/stat-card.md +150 -0
- package/.a4drules/skills/building-react-components/SKILL.md +96 -0
- package/.a4drules/skills/building-react-components/implementation/component.md +78 -0
- package/.a4drules/skills/building-react-components/implementation/header-footer.md +132 -0
- package/.a4drules/skills/building-react-components/implementation/page.md +93 -0
- package/.a4drules/skills/configuring-csp-trusted-sites/SKILL.md +90 -0
- package/.a4drules/skills/configuring-csp-trusted-sites/implementation/metadata-format.md +281 -0
- package/.a4drules/skills/configuring-webapp-metadata/SKILL.md +158 -0
- package/.a4drules/skills/creating-webapp/SKILL.md +140 -0
- package/.a4drules/skills/deploying-to-salesforce/SKILL.md +226 -0
- package/.a4drules/skills/implementing-file-upload/SKILL.md +396 -0
- package/.a4drules/skills/installing-webapp-features/SKILL.md +210 -0
- package/.a4drules/skills/managing-agentforce-conversation-client/SKILL.md +186 -0
- package/.a4drules/skills/managing-agentforce-conversation-client/references/constraints.md +134 -0
- package/.a4drules/skills/managing-agentforce-conversation-client/references/examples.md +132 -0
- package/.a4drules/skills/managing-agentforce-conversation-client/references/style-tokens.md +101 -0
- package/.a4drules/skills/managing-agentforce-conversation-client/references/troubleshooting.md +57 -0
- package/.a4drules/skills/using-salesforce-data/SKILL.md +363 -0
- package/.a4drules/skills/using-salesforce-data/graphql-search.sh +139 -0
- package/.a4drules/webapp-data.md +353 -0
- package/.a4drules/webapp-ui.md +16 -0
- package/README.md +124 -0
- package/dist/components/library/cards/ActionList.js +27 -0
- package/dist/components/library/cards/ActionList.js.map +1 -0
- package/dist/components/library/cards/ActivityCard.js +40 -0
- package/dist/components/library/cards/ActivityCard.js.map +1 -0
- package/dist/components/library/cards/BaseCard.js +89 -0
- package/dist/components/library/cards/BaseCard.js.map +1 -0
- package/dist/components/library/cards/CalloutCard.js +28 -0
- package/dist/components/library/cards/CalloutCard.js.map +1 -0
- package/dist/components/library/cards/ChartCard.js +79 -0
- package/dist/components/library/cards/ChartCard.js.map +1 -0
- package/dist/components/library/cards/FeedPanel.js +38 -0
- package/dist/components/library/cards/FeedPanel.js.map +1 -0
- package/dist/components/library/cards/ListCard.js +112 -0
- package/dist/components/library/cards/ListCard.js.map +1 -0
- package/dist/components/library/cards/MetricCard.js +86 -0
- package/dist/components/library/cards/MetricCard.js.map +1 -0
- package/dist/components/library/cards/MetricsStrip.js +60 -0
- package/dist/components/library/cards/MetricsStrip.js.map +1 -0
- package/dist/components/library/cards/SectionCard.js +59 -0
- package/dist/components/library/cards/SectionCard.js.map +1 -0
- package/dist/components/library/cards/StatusCard.js +137 -0
- package/dist/components/library/cards/StatusCard.js.map +1 -0
- package/dist/components/library/cards/TableCard.js +244 -0
- package/dist/components/library/cards/TableCard.js.map +1 -0
- package/dist/components/library/cards/WidgetCard.js +60 -0
- package/dist/components/library/cards/WidgetCard.js.map +1 -0
- package/dist/components/library/charts/D3Chart.js +74 -0
- package/dist/components/library/charts/D3Chart.js.map +1 -0
- package/dist/components/library/charts/D3ChartTemplates.js +44 -0
- package/dist/components/library/charts/D3ChartTemplates.js.map +1 -0
- package/dist/components/library/charts/GeoMap.js +229 -0
- package/dist/components/library/charts/GeoMap.js.map +1 -0
- package/dist/components/library/chat/ChatBar.js +194 -0
- package/dist/components/library/chat/ChatBar.js.map +1 -0
- package/dist/components/library/chat/ChatInput.js +67 -0
- package/dist/components/library/chat/ChatInput.js.map +1 -0
- package/dist/components/library/chat/ChatMessage.js +112 -0
- package/dist/components/library/chat/ChatMessage.js.map +1 -0
- package/dist/components/library/chat/ChatMessageList.js +50 -0
- package/dist/components/library/chat/ChatMessageList.js.map +1 -0
- package/dist/components/library/chat/ChatPanel.js +77 -0
- package/dist/components/library/chat/ChatPanel.js.map +1 -0
- package/dist/components/library/chat/ChatSuggestions.js +22 -0
- package/dist/components/library/chat/ChatSuggestions.js.map +1 -0
- package/dist/components/library/chat/ChatToolCall.js +87 -0
- package/dist/components/library/chat/ChatToolCall.js.map +1 -0
- package/dist/components/library/chat/ChatTypingIndicator.js +20 -0
- package/dist/components/library/chat/ChatTypingIndicator.js.map +1 -0
- package/dist/components/library/chat/ChatWelcome.js +24 -0
- package/dist/components/library/chat/ChatWelcome.js.map +1 -0
- package/dist/components/library/chat/useChatState.js +77 -0
- package/dist/components/library/chat/useChatState.js.map +1 -0
- package/dist/components/library/data/DataModeProvider.js +47 -0
- package/dist/components/library/data/DataModeProvider.js.map +1 -0
- package/dist/components/library/data/DataModeToggle.js +28 -0
- package/dist/components/library/data/DataModeToggle.js.map +1 -0
- package/dist/components/library/data/filterUtils.js +71 -0
- package/dist/components/library/data/filterUtils.js.map +1 -0
- package/dist/components/library/data/useDataSource.js +13 -0
- package/dist/components/library/data/useDataSource.js.map +1 -0
- package/dist/components/library/data/usePageFilters.js +46 -0
- package/dist/components/library/data/usePageFilters.js.map +1 -0
- package/dist/components/library/filters/FilterBar.js +89 -0
- package/dist/components/library/filters/FilterBar.js.map +1 -0
- package/dist/components/library/filters/SearchFilter.js +44 -0
- package/dist/components/library/filters/SearchFilter.js.map +1 -0
- package/dist/components/library/filters/SelectFilter.js +44 -0
- package/dist/components/library/filters/SelectFilter.js.map +1 -0
- package/dist/components/library/filters/ToggleFilter.js +48 -0
- package/dist/components/library/filters/ToggleFilter.js.map +1 -0
- package/dist/components/library/forms/FormField.js +256 -0
- package/dist/components/library/forms/FormField.js.map +1 -0
- package/dist/components/library/forms/FormModal.js +161 -0
- package/dist/components/library/forms/FormModal.js.map +1 -0
- package/dist/components/library/forms/FormRenderer.js +32 -0
- package/dist/components/library/forms/FormRenderer.js.map +1 -0
- package/dist/components/library/forms/FormSection.js +49 -0
- package/dist/components/library/forms/FormSection.js.map +1 -0
- package/dist/components/library/forms/useFormState.js +85 -0
- package/dist/components/library/forms/useFormState.js.map +1 -0
- package/dist/components/library/heroui/Accordion.js +12 -0
- package/dist/components/library/heroui/Accordion.js.map +1 -0
- package/dist/components/library/heroui/Alert.js +12 -0
- package/dist/components/library/heroui/Alert.js.map +1 -0
- package/dist/components/library/heroui/Badge.js +12 -0
- package/dist/components/library/heroui/Badge.js.map +1 -0
- package/dist/components/library/heroui/Breadcrumbs.js +12 -0
- package/dist/components/library/heroui/Breadcrumbs.js.map +1 -0
- package/dist/components/library/heroui/Button.js +18 -0
- package/dist/components/library/heroui/Button.js.map +1 -0
- package/dist/components/library/heroui/Card.js +12 -0
- package/dist/components/library/heroui/Card.js.map +1 -0
- package/dist/components/library/heroui/Drawer.js +12 -0
- package/dist/components/library/heroui/Drawer.js.map +1 -0
- package/dist/components/library/heroui/Dropdown.js +12 -0
- package/dist/components/library/heroui/Dropdown.js.map +1 -0
- package/dist/components/library/heroui/Input.js +10 -0
- package/dist/components/library/heroui/Input.js.map +1 -0
- package/dist/components/library/heroui/Kbd.js +12 -0
- package/dist/components/library/heroui/Kbd.js.map +1 -0
- package/dist/components/library/heroui/Meter.js +12 -0
- package/dist/components/library/heroui/Meter.js.map +1 -0
- package/dist/components/library/heroui/Modal.js +12 -0
- package/dist/components/library/heroui/Modal.js.map +1 -0
- package/dist/components/library/heroui/Pagination.js +12 -0
- package/dist/components/library/heroui/Pagination.js.map +1 -0
- package/dist/components/library/heroui/ProgressBar.js +12 -0
- package/dist/components/library/heroui/ProgressBar.js.map +1 -0
- package/dist/components/library/heroui/ProgressCircle.js +12 -0
- package/dist/components/library/heroui/ProgressCircle.js.map +1 -0
- package/dist/components/library/heroui/ScrollShadow.js +12 -0
- package/dist/components/library/heroui/ScrollShadow.js.map +1 -0
- package/dist/components/library/heroui/Select.js +12 -0
- package/dist/components/library/heroui/Select.js.map +1 -0
- package/dist/components/library/heroui/Separator.js +12 -0
- package/dist/components/library/heroui/Separator.js.map +1 -0
- package/dist/components/library/heroui/Skeleton.js +12 -0
- package/dist/components/library/heroui/Skeleton.js.map +1 -0
- package/dist/components/library/heroui/Tabs.js +12 -0
- package/dist/components/library/heroui/Tabs.js.map +1 -0
- package/dist/components/library/heroui/Toast.js +13 -0
- package/dist/components/library/heroui/Toast.js.map +1 -0
- package/dist/components/library/heroui/Toggle.js +12 -0
- package/dist/components/library/heroui/Toggle.js.map +1 -0
- package/dist/components/library/heroui/Tooltip.js +12 -0
- package/dist/components/library/heroui/Tooltip.js.map +1 -0
- package/dist/components/library/layout/PageContainer.js +9 -0
- package/dist/components/library/layout/PageContainer.js.map +1 -0
- package/dist/components/library/skeletons/CardSkeleton.js +29 -0
- package/dist/components/library/skeletons/CardSkeleton.js.map +1 -0
- package/dist/components/library/theme/AppThemeProvider.js +55 -0
- package/dist/components/library/theme/AppThemeProvider.js.map +1 -0
- package/dist/components/library/theme/tokens.js +55 -0
- package/dist/components/library/theme/tokens.js.map +1 -0
- package/dist/components/library/ui/Avatar.js +33 -0
- package/dist/components/library/ui/Avatar.js.map +1 -0
- package/dist/components/library/ui/Button.js +50 -0
- package/dist/components/library/ui/Button.js.map +1 -0
- package/dist/components/library/ui/Card.js +21 -0
- package/dist/components/library/ui/Card.js.map +1 -0
- package/dist/components/library/ui/Chip.js +31 -0
- package/dist/components/library/ui/Chip.js.map +1 -0
- package/dist/components/library/ui/Container.js +42 -0
- package/dist/components/library/ui/Container.js.map +1 -0
- package/dist/components/library/ui/EmptyState.js +27 -0
- package/dist/components/library/ui/EmptyState.js.map +1 -0
- package/dist/components/library/ui/Input.js +22 -0
- package/dist/components/library/ui/Input.js.map +1 -0
- package/dist/components/library/ui/Spinner.js +64 -0
- package/dist/components/library/ui/Spinner.js.map +1 -0
- package/dist/components/library/ui/Text.js +43 -0
- package/dist/components/library/ui/Text.js.map +1 -0
- package/dist/components/library/ui/Toggle.js +47 -0
- package/dist/components/library/ui/Toggle.js.map +1 -0
- package/dist/components/workspace/ComponentRegistry.js +231 -0
- package/dist/components/workspace/ComponentRegistry.js.map +1 -0
- package/dist/index.js +185 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/utils.js +9 -0
- package/dist/lib/utils.js.map +1 -0
- package/dist/node_modules/clsx/dist/clsx.js +17 -0
- package/dist/node_modules/clsx/dist/clsx.js.map +1 -0
- package/dist/node_modules/tailwind-merge/dist/bundle-mjs.js +2925 -0
- package/dist/node_modules/tailwind-merge/dist/bundle-mjs.js.map +1 -0
- package/package.json +81 -0
- package/scripts/get-graphql-schema.mjs +68 -0
- package/scripts/reset-command-center.sh +358 -0
- package/scripts/rewrite-e2e-assets.mjs +23 -0
- 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
|