@woodsportal/hubspot-kit 1.0.4 → 1.0.26
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/CHANGELOG.md +9 -0
- package/README.md +4 -2
- package/dist/{chunk-JDGIAYHX.js → chunk-GTTIJVAR.js} +43 -3
- package/dist/chunk-GTTIJVAR.js.map +1 -0
- package/dist/cli.js +1400 -338
- package/dist/cli.js.map +1 -1
- package/dist/exec-U2OJATR3.js +12 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.js +8 -1
- package/dist/index.js.map +1 -1
- package/examples/greenfield/module-hybrid-cdn/README.md +40 -3
- package/examples/greenfield/module-hybrid-cdn/public/fields.json +44 -1
- package/examples/greenfield/module-hybrid-cdn/src/app.tsx +8 -0
- package/examples/greenfield/module-hybrid-cdn/src/assets/css/main.dev.css +6 -1
- package/examples/greenfield/module-hybrid-cdn/src/cdn/app-entry.ts +32 -0
- package/examples/greenfield/module-hybrid-cdn/src/cdn/vendor-entry.ts +28 -0
- package/examples/greenfield/module-hybrid-cdn/src/components/layout/section-card.tsx +20 -0
- package/examples/greenfield/module-hybrid-cdn/src/components/layout/starter-nav.tsx +28 -0
- package/examples/greenfield/module-hybrid-cdn/src/components/layout/starter-shell.tsx +18 -0
- package/examples/greenfield/module-hybrid-cdn/src/config/router.ts +4 -0
- package/examples/greenfield/module-hybrid-cdn/src/config/routes.ts +5 -0
- package/examples/greenfield/module-hybrid-cdn/src/defaultData.ts +2 -7
- package/examples/greenfield/module-hybrid-cdn/src/features/starter/components/starter-guide-page.tsx +93 -0
- package/examples/greenfield/module-hybrid-cdn/src/features/starter/components/starter-home-page.tsx +21 -0
- package/examples/greenfield/module-hybrid-cdn/src/features/starter/components/starter-home.tsx +52 -0
- package/examples/greenfield/module-hybrid-cdn/src/features/starter/components/starter-single-page.tsx +17 -0
- package/examples/greenfield/module-hybrid-cdn/src/features/starter/content/woodscli-guide-content.ts +86 -0
- package/examples/greenfield/module-hybrid-cdn/src/features/starter/hooks/use-starter-preview.ts +47 -0
- package/examples/greenfield/module-hybrid-cdn/src/features/starter/index.ts +6 -0
- package/examples/greenfield/module-hybrid-cdn/src/hubspot/bootstrap.ts +10 -0
- package/examples/greenfield/module-hybrid-cdn/src/hubspot/default-data.ts +7 -0
- package/examples/greenfield/module-hybrid-cdn/src/integrations/tanstack-router/router.tsx +25 -0
- package/examples/greenfield/module-hybrid-cdn/src/main.tsx +4 -3
- package/examples/greenfield/module-hybrid-cdn/src/portal/mount-dev-preview.tsx +8 -0
- package/examples/greenfield/module-hybrid-cdn/src/portal/mount-portal.tsx +12 -0
- package/examples/greenfield/module-hybrid-cdn/src/routeTree.gen.ts +77 -0
- package/examples/greenfield/module-hybrid-cdn/src/routes/__root.tsx +15 -0
- package/examples/greenfield/module-hybrid-cdn/src/routes/guide.tsx +7 -0
- package/examples/greenfield/module-hybrid-cdn/src/routes/index.tsx +7 -0
- package/examples/greenfield/module-hybrid-cdn/tsconfig.json +20 -0
- package/examples/greenfield/module-monolith/public/fields.json +44 -1
- package/examples/greenfield/module-monolith/src/app.tsx +8 -0
- package/examples/greenfield/module-monolith/src/assets/css/main.dev.css +8 -0
- package/examples/greenfield/module-monolith/src/components/layout/section-card.tsx +20 -0
- package/examples/greenfield/module-monolith/src/components/layout/starter-nav.tsx +28 -0
- package/examples/greenfield/module-monolith/src/components/layout/starter-shell.tsx +18 -0
- package/examples/greenfield/module-monolith/src/config/router.ts +4 -0
- package/examples/greenfield/module-monolith/src/config/routes.ts +5 -0
- package/examples/greenfield/module-monolith/src/defaultData.ts +2 -7
- package/examples/greenfield/module-monolith/src/features/starter/components/starter-guide-page.tsx +93 -0
- package/examples/greenfield/module-monolith/src/features/starter/components/starter-home-page.tsx +21 -0
- package/examples/greenfield/module-monolith/src/features/starter/components/starter-home.tsx +52 -0
- package/examples/greenfield/module-monolith/src/features/starter/components/starter-single-page.tsx +17 -0
- package/examples/greenfield/module-monolith/src/features/starter/content/woodscli-guide-content.ts +86 -0
- package/examples/greenfield/module-monolith/src/features/starter/hooks/use-starter-preview.ts +47 -0
- package/examples/greenfield/module-monolith/src/features/starter/index.ts +6 -0
- package/examples/greenfield/module-monolith/src/hubspot/default-data.ts +7 -0
- package/examples/greenfield/module-monolith/src/hubspot/types.ts +17 -0
- package/examples/greenfield/module-monolith/src/integrations/tanstack-router/router.tsx +25 -0
- package/examples/greenfield/module-monolith/src/main.tsx +5 -0
- package/examples/greenfield/module-monolith/src/portal/mount-dev-preview.tsx +8 -0
- package/examples/greenfield/module-monolith/src/portal/mount-portal.tsx +12 -0
- package/examples/greenfield/module-monolith/src/routeTree.gen.ts +77 -0
- package/examples/greenfield/module-monolith/src/routes/__root.tsx +15 -0
- package/examples/greenfield/module-monolith/src/routes/guide.tsx +7 -0
- package/examples/greenfield/module-monolith/src/routes/index.tsx +7 -0
- package/examples/greenfield/module-monolith/tsconfig.json +20 -0
- package/package.json +4 -1
- package/scripts/check-hubspot-bundle-size.mjs +5 -2
- package/scripts/ensure-wphs-workspace.mjs +2 -1
- package/scripts/hubspot-module-postbuild.mjs +1 -1
- package/scripts/read-woodsportal-config.mjs +4 -1
- package/scripts/run-hubspot-module-postbuild.mjs +9 -4
- package/scripts/vite/bootstrap-shim-resolver.js +47 -0
- package/scripts/vite/ensure-dev-kit-shims.js +7 -1
- package/scripts/vite/react-dev-plugin-options.js +2 -1
- package/scripts/watch-hubspot.mjs +107 -17
- package/scripts/wphs-paths.mjs +1 -0
- package/scripts/write-audit-artifacts.mjs +2 -1
- package/src/dev/bootstrap-greenfield.tsx +59 -0
- package/src/dev/bootstrap.tsx +46 -0
- package/src/dev-module-config/__tests__/fixtures/fields.json +3014 -0
- package/src/dev-module-config/__tests__/fixtures/golden-module.fixture.ts +102 -0
- package/src/dev-module-config/__tests__/module-config-storage.test.ts +40 -0
- package/src/dev-module-config/__tests__/module-config-transform.test.ts +55 -0
- package/src/dev-module-config/__tests__/normalize-repeater-items.test.ts +46 -0
- package/src/dev-module-config/dev-module-display-name.ts +5 -0
- package/src/dev-module-config/runtime/module-config-bridge.ts +27 -0
- package/src/dev-module-config/runtime/module-config-runtime.ts +185 -0
- package/src/dev-module-config/schema/build-default-item.ts +7 -0
- package/src/dev-module-config/schema/deep-merge.ts +27 -0
- package/src/dev-module-config/schema/extract-defaults.ts +62 -0
- package/src/dev-module-config/schema/normalize-choices.ts +18 -0
- package/src/dev-module-config/schema/normalize-repeater-items.ts +31 -0
- package/src/dev-module-config/schema/parse-fields-json.ts +2 -0
- package/src/dev-module-config/schema/types.ts +75 -0
- package/src/dev-module-config/schema/validate-module-config.ts +81 -0
- package/src/dev-module-config/schema/visibility.ts +130 -0
- package/src/dev-module-config/storage/config-json.ts +46 -0
- package/src/dev-module-config/storage/workspace-store.ts +139 -0
- package/src/dev-module-config/transform/build-hubspot-data.ts +409 -0
- package/src/dev-module-config/ui/FieldRenderer.tsx +353 -0
- package/src/dev-module-config/ui/ModuleConfigAnimatedPresence.tsx +72 -0
- package/src/dev-module-config/ui/ModuleConfigCollapsible.tsx +43 -0
- package/src/dev-module-config/ui/ModuleConfigDockSideIcon.tsx +27 -0
- package/src/dev-module-config/ui/ModuleConfigOverlay.tsx +134 -0
- package/src/dev-module-config/ui/ModuleConfigPersistenceStatus.tsx +89 -0
- package/src/dev-module-config/ui/ModuleConfigShell.tsx +355 -0
- package/src/dev-module-config/ui/ModuleConfigThemeToggle.tsx +32 -0
- package/src/dev-module-config/ui/ModuleConfigUiContext.tsx +65 -0
- package/src/dev-module-config/ui/editors/DeveloperOptionsEditor.tsx +61 -0
- package/src/dev-module-config/ui/editors/MenuOptionsEditor.tsx +103 -0
- package/src/dev-module-config/ui/hubspot/HsColorField.tsx +106 -0
- package/src/dev-module-config/ui/hubspot/HsColorPickerPopover.tsx +210 -0
- package/src/dev-module-config/ui/hubspot/HsFieldLabel.tsx +24 -0
- package/src/dev-module-config/ui/hubspot/HsFieldWidgets.tsx +248 -0
- package/src/dev-module-config/ui/hubspot/HsImageField.tsx +112 -0
- package/src/dev-module-config/ui/hubspot/HsOccurrenceGroupEditor.tsx +243 -0
- package/src/dev-module-config/ui/hubspot/HsRepeaterList.tsx +178 -0
- package/src/dev-module-config/ui/hubspot/HsSectionHeader.tsx +20 -0
- package/src/dev-module-config/ui/hubspot/HsToggle.tsx +39 -0
- package/src/dev-module-config/ui/hubspot/HsToggleRow.tsx +30 -0
- package/src/dev-module-config/ui/hubspot/hs-color-palette.ts +11 -0
- package/src/dev-module-config/ui/hubspot/module-config-typography.ts +26 -0
- package/src/dev-module-config/ui/module-config-motion.css +134 -0
- package/src/dev-module-config/ui/module-config-motion.ts +28 -0
- package/src/dev-module-config/ui/module-config-overlay-side.ts +29 -0
- package/src/dev-module-config/ui/module-config-surfaces.ts +27 -0
- package/src/dev-module-config/ui/module-config-theme.css +489 -0
- package/src/dev-module-config/ui/module-config-toolbar-theme.ts +35 -0
- package/src/dev-module-config/ui/navigation/ModuleConfigNavContext.tsx +194 -0
- package/src/dev-module-config/ui/use-dev-module-config-live-revision.ts +22 -0
- package/src/dev-module-config/ui/use-hubspot-live-exports.ts +27 -0
- package/src/dev-module-config/ui/use-module-config.ts +124 -0
- package/src/routing/build-api-routes.ts +37 -0
- package/src/routing/route-format.ts +19 -0
- package/templates/module-hybrid-cdn/README.md +53 -3
- package/templates/module-hybrid-cdn/index.html +12 -1
- package/templates/module-hybrid-cdn/package.json +6 -2
- package/templates/module-hybrid-cdn/public/fields.json +44 -1
- package/templates/module-hybrid-cdn/src/app.tsx +8 -0
- package/templates/module-hybrid-cdn/src/assets/css/main.dev.css +6 -1
- package/templates/module-hybrid-cdn/src/cdn/app-entry.ts +32 -0
- package/templates/module-hybrid-cdn/src/cdn/vendor-entry.ts +28 -0
- package/templates/module-hybrid-cdn/src/components/layout/section-card.tsx +20 -0
- package/templates/module-hybrid-cdn/src/components/layout/starter-nav.tsx +28 -0
- package/templates/module-hybrid-cdn/src/components/layout/starter-shell.tsx +18 -0
- package/templates/module-hybrid-cdn/src/config/router.ts +4 -0
- package/templates/module-hybrid-cdn/src/config/routes.ts +5 -0
- package/templates/module-hybrid-cdn/src/defaultData.ts +2 -7
- package/templates/module-hybrid-cdn/src/features/starter/components/starter-guide-page.tsx +93 -0
- package/templates/module-hybrid-cdn/src/features/starter/components/starter-home-page.tsx +21 -0
- package/templates/module-hybrid-cdn/src/features/starter/components/starter-home.tsx +52 -0
- package/templates/module-hybrid-cdn/src/features/starter/components/starter-single-page.tsx +17 -0
- package/templates/module-hybrid-cdn/src/features/starter/content/woodscli-guide-content.ts +86 -0
- package/templates/module-hybrid-cdn/src/features/starter/hooks/starter-preview-values.ts +42 -0
- package/templates/module-hybrid-cdn/src/features/starter/hooks/use-starter-preview.dev.ts +17 -0
- package/templates/module-hybrid-cdn/src/features/starter/hooks/use-starter-preview.ts +30 -0
- package/templates/module-hybrid-cdn/src/features/starter/index.ts +6 -0
- package/templates/module-hybrid-cdn/src/hubspot/bootstrap.ts +10 -0
- package/templates/module-hybrid-cdn/src/hubspot/default-data.ts +7 -0
- package/templates/module-hybrid-cdn/src/hubspot/module-entry.tsx +9 -0
- package/templates/module-hybrid-cdn/src/integrations/tanstack-router/router.tsx +25 -0
- package/templates/module-hybrid-cdn/src/main.tsx +4 -3
- package/templates/module-hybrid-cdn/src/portal/mount-dev-preview.tsx +8 -0
- package/templates/module-hybrid-cdn/src/portal/mount-portal.tsx +12 -0
- package/templates/module-hybrid-cdn/src/routeTree.gen.ts +77 -0
- package/templates/module-hybrid-cdn/src/routes/__root.tsx +15 -0
- package/templates/module-hybrid-cdn/src/routes/guide.tsx +7 -0
- package/templates/module-hybrid-cdn/src/routes/index.tsx +7 -0
- package/templates/module-hybrid-cdn/tsconfig.json +20 -0
- package/templates/module-hybrid-cdn/vite.config.monolith.mjs +8 -0
- package/templates/module-monolith/README.md +59 -0
- package/templates/module-monolith/index.html +12 -0
- package/templates/module-monolith/package.json +10 -3
- package/templates/module-monolith/public/fields.json +44 -1
- package/templates/module-monolith/public/module.html +1 -0
- package/templates/module-monolith/src/app.tsx +8 -0
- package/templates/module-monolith/src/assets/css/main.dev.css +6 -1
- package/templates/module-monolith/src/components/layout/section-card.tsx +20 -0
- package/templates/module-monolith/src/components/layout/starter-nav.tsx +28 -0
- package/templates/module-monolith/src/components/layout/starter-shell.tsx +18 -0
- package/templates/module-monolith/src/config/router.ts +4 -0
- package/templates/module-monolith/src/config/routes.ts +5 -0
- package/templates/module-monolith/src/defaultData.ts +2 -7
- package/templates/module-monolith/src/features/starter/components/starter-guide-page.tsx +93 -0
- package/templates/module-monolith/src/features/starter/components/starter-home-page.tsx +21 -0
- package/templates/module-monolith/src/features/starter/components/starter-home.tsx +52 -0
- package/templates/module-monolith/src/features/starter/components/starter-single-page.tsx +17 -0
- package/templates/module-monolith/src/features/starter/content/woodscli-guide-content.ts +86 -0
- package/templates/module-monolith/src/features/starter/hooks/starter-preview-values.ts +42 -0
- package/templates/module-monolith/src/features/starter/hooks/use-starter-preview.dev.ts +17 -0
- package/templates/module-monolith/src/features/starter/hooks/use-starter-preview.ts +30 -0
- package/templates/module-monolith/src/features/starter/index.ts +6 -0
- package/templates/module-monolith/src/hubspot/default-data.ts +7 -0
- package/templates/module-monolith/src/hubspot/module-entry.tsx +9 -0
- package/templates/module-monolith/src/integrations/tanstack-router/router.tsx +25 -0
- package/templates/module-monolith/src/main.tsx +4 -3
- package/templates/module-monolith/src/portal/mount-dev-preview.tsx +8 -0
- package/templates/module-monolith/src/portal/mount-portal.tsx +12 -0
- package/templates/module-monolith/src/routeTree.gen.ts +77 -0
- package/templates/module-monolith/src/routes/__root.tsx +15 -0
- package/templates/module-monolith/src/routes/guide.tsx +7 -0
- package/templates/module-monolith/src/routes/index.tsx +7 -0
- package/templates/module-monolith/tsconfig.json +20 -0
- package/templates/module-monolith/vite.config.monolith.mjs +8 -0
- package/templates/module-monolith/woodscli.json +1 -0
- package/templates/shared/.env.dev.example +5 -0
- package/templates/shared/.env.prod.example +5 -0
- package/templates/shared/.env.stg.example +3 -0
- package/templates/shared/portal.cdn.json.example +8 -0
- package/templates/theme-starter/package.json +5 -2
- package/vite/vite.config.dev.mjs +9 -1
- package/vite/vite.config.monolith.mjs +22 -17
- package/dist/chunk-JDGIAYHX.js.map +0 -1
- package/dist/exec-K5C6LZP4.js +0 -10
- /package/dist/{exec-K5C6LZP4.js.map → exec-U2OJATR3.js.map} +0 -0
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
export type ModuleConfigToolbarTheme = 'light' | 'dark'
|
|
2
|
+
|
|
3
|
+
const STORAGE_KEY = 'woodsportal:module-config-toolbar-theme'
|
|
4
|
+
|
|
5
|
+
export function detectPortalTheme(): ModuleConfigToolbarTheme {
|
|
6
|
+
if (typeof document === 'undefined') return 'light'
|
|
7
|
+
return document.documentElement.classList.contains('dark') ? 'dark' : 'light'
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function loadModuleConfigToolbarTheme(): ModuleConfigToolbarTheme {
|
|
11
|
+
if (typeof window === 'undefined') return 'light'
|
|
12
|
+
try {
|
|
13
|
+
const stored = window.localStorage.getItem(STORAGE_KEY)
|
|
14
|
+
if (stored === 'dark' || stored === 'light') return stored
|
|
15
|
+
} catch {
|
|
16
|
+
// ignore
|
|
17
|
+
}
|
|
18
|
+
return detectPortalTheme()
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function saveModuleConfigToolbarTheme(theme: ModuleConfigToolbarTheme): void {
|
|
22
|
+
try {
|
|
23
|
+
window.localStorage.setItem(STORAGE_KEY, theme)
|
|
24
|
+
} catch {
|
|
25
|
+
// ignore
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function toggleModuleConfigToolbarTheme(
|
|
30
|
+
theme: ModuleConfigToolbarTheme,
|
|
31
|
+
): ModuleConfigToolbarTheme {
|
|
32
|
+
const next: ModuleConfigToolbarTheme = theme === 'dark' ? 'light' : 'dark'
|
|
33
|
+
saveModuleConfigToolbarTheme(next)
|
|
34
|
+
return next
|
|
35
|
+
}
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createContext,
|
|
3
|
+
useCallback,
|
|
4
|
+
useContext,
|
|
5
|
+
useMemo,
|
|
6
|
+
useState,
|
|
7
|
+
type ReactNode,
|
|
8
|
+
} from 'react'
|
|
9
|
+
|
|
10
|
+
import { devModuleDisplayName } from '../../dev-module-display-name'
|
|
11
|
+
|
|
12
|
+
export type BreadcrumbSegment = {
|
|
13
|
+
id: string
|
|
14
|
+
label: string
|
|
15
|
+
onClick?: () => void
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
type ModuleConfigNavContextValue = {
|
|
19
|
+
segments: Array<BreadcrumbSegment>
|
|
20
|
+
drillDownPanel: ReactNode | null
|
|
21
|
+
pushSegment: (segment: Omit<BreadcrumbSegment, 'onClick'> & { onPop?: () => void }) => void
|
|
22
|
+
popTo: (id: string) => void
|
|
23
|
+
resetNav: () => void
|
|
24
|
+
registerPopHandler: (id: string, handler: () => void) => void
|
|
25
|
+
/** @deprecated use registerDrillDownPanel */
|
|
26
|
+
setDrillDownPanel: (panel: ReactNode | null) => void
|
|
27
|
+
registerDrillDownPanel: (id: string, panel: ReactNode | null) => void
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const ROOT_SEGMENT: BreadcrumbSegment = { id: 'contents', label: 'Contents' }
|
|
31
|
+
|
|
32
|
+
function moduleTitleSegment(stackLength: number, popTo: (id: string) => void): BreadcrumbSegment {
|
|
33
|
+
return {
|
|
34
|
+
id: 'module',
|
|
35
|
+
label: devModuleDisplayName(),
|
|
36
|
+
onClick: stackLength > 1 ? () => popTo('module') : undefined,
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const ModuleConfigNavContext = createContext<ModuleConfigNavContextValue | null>(null)
|
|
41
|
+
|
|
42
|
+
export function ModuleConfigNavProvider({ children }: { children: ReactNode }) {
|
|
43
|
+
const [stack, setStack] = useState<Array<BreadcrumbSegment>>([ROOT_SEGMENT])
|
|
44
|
+
const [popHandlers, setPopHandlers] = useState<Record<string, () => void>>({})
|
|
45
|
+
const [drillDownPanels, setDrillDownPanels] = useState<Record<string, ReactNode>>({})
|
|
46
|
+
const [drillDownOrder, setDrillDownOrder] = useState<string[]>([])
|
|
47
|
+
|
|
48
|
+
const registerDrillDownPanel = useCallback((id: string, panel: ReactNode | null) => {
|
|
49
|
+
if (panel === null) {
|
|
50
|
+
setDrillDownPanels((prev) => {
|
|
51
|
+
if (!(id in prev)) return prev
|
|
52
|
+
const next = { ...prev }
|
|
53
|
+
delete next[id]
|
|
54
|
+
return next
|
|
55
|
+
})
|
|
56
|
+
setDrillDownOrder((order) => order.filter((entry) => entry !== id))
|
|
57
|
+
return
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
setDrillDownPanels((prev) => ({ ...prev, [id]: panel }))
|
|
61
|
+
setDrillDownOrder((order) => (order.includes(id) ? order : [...order, id]))
|
|
62
|
+
}, [])
|
|
63
|
+
|
|
64
|
+
const clearAllDrillDownPanels = useCallback(() => {
|
|
65
|
+
setDrillDownPanels({})
|
|
66
|
+
setDrillDownOrder([])
|
|
67
|
+
}, [])
|
|
68
|
+
|
|
69
|
+
const drillDownPanel = useMemo(() => {
|
|
70
|
+
const topId = drillDownOrder[drillDownOrder.length - 1]
|
|
71
|
+
return topId ? (drillDownPanels[topId] ?? null) : null
|
|
72
|
+
}, [drillDownOrder, drillDownPanels])
|
|
73
|
+
|
|
74
|
+
const setDrillDownPanel = useCallback(
|
|
75
|
+
(panel: ReactNode | null) => {
|
|
76
|
+
if (panel === null) {
|
|
77
|
+
clearAllDrillDownPanels()
|
|
78
|
+
return
|
|
79
|
+
}
|
|
80
|
+
registerDrillDownPanel('__legacy__', panel)
|
|
81
|
+
},
|
|
82
|
+
[clearAllDrillDownPanels, registerDrillDownPanel],
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
const registerPopHandler = useCallback((id: string, handler: () => void) => {
|
|
86
|
+
setPopHandlers((prev) => ({ ...prev, [id]: handler }))
|
|
87
|
+
}, [])
|
|
88
|
+
|
|
89
|
+
const popTo = useCallback(
|
|
90
|
+
(id: string) => {
|
|
91
|
+
if (id === 'module' || id === 'contents') {
|
|
92
|
+
for (const segment of stack.slice(1)) {
|
|
93
|
+
popHandlers[segment.id]?.()
|
|
94
|
+
}
|
|
95
|
+
setStack([ROOT_SEGMENT])
|
|
96
|
+
clearAllDrillDownPanels()
|
|
97
|
+
return
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
setStack((prev) => {
|
|
101
|
+
const index = prev.findIndex((segment) => segment.id === id)
|
|
102
|
+
if (index < 0) return prev
|
|
103
|
+
|
|
104
|
+
const removed = prev.slice(index + 1)
|
|
105
|
+
for (const segment of removed) {
|
|
106
|
+
popHandlers[segment.id]?.()
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return prev.slice(0, index + 1)
|
|
110
|
+
})
|
|
111
|
+
},
|
|
112
|
+
[clearAllDrillDownPanels, popHandlers, stack],
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
const pushSegment = useCallback(
|
|
116
|
+
(segment: Omit<BreadcrumbSegment, 'onClick'> & { onPop?: () => void }) => {
|
|
117
|
+
if (segment.onPop) {
|
|
118
|
+
setPopHandlers((prev) => ({ ...prev, [segment.id]: segment.onPop! }))
|
|
119
|
+
}
|
|
120
|
+
setStack((prev) => {
|
|
121
|
+
const existing = prev.findIndex((item) => item.id === segment.id)
|
|
122
|
+
if (existing >= 0) {
|
|
123
|
+
return [...prev.slice(0, existing + 1), { id: segment.id, label: segment.label }]
|
|
124
|
+
}
|
|
125
|
+
return [...prev, { id: segment.id, label: segment.label }]
|
|
126
|
+
})
|
|
127
|
+
},
|
|
128
|
+
[],
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
const resetNav = useCallback(() => {
|
|
132
|
+
setStack([ROOT_SEGMENT])
|
|
133
|
+
clearAllDrillDownPanels()
|
|
134
|
+
}, [clearAllDrillDownPanels])
|
|
135
|
+
|
|
136
|
+
const segments = useMemo(() => {
|
|
137
|
+
const moduleTitle = moduleTitleSegment(stack.length, popTo)
|
|
138
|
+
|
|
139
|
+
if (stack.length <= 1) {
|
|
140
|
+
return [ROOT_SEGMENT, moduleTitle]
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const middle = stack.slice(1).map((segment) => ({
|
|
144
|
+
...segment,
|
|
145
|
+
onClick: () => popTo(segment.id),
|
|
146
|
+
}))
|
|
147
|
+
|
|
148
|
+
if (middle.length > 2) {
|
|
149
|
+
const collapsed: Array<BreadcrumbSegment> = [
|
|
150
|
+
middle[0]!,
|
|
151
|
+
{ id: 'ellipsis', label: '…' },
|
|
152
|
+
middle[middle.length - 1]!,
|
|
153
|
+
]
|
|
154
|
+
return [ROOT_SEGMENT, moduleTitle, ...collapsed]
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return [ROOT_SEGMENT, moduleTitle, ...middle]
|
|
158
|
+
}, [popTo, stack])
|
|
159
|
+
|
|
160
|
+
const value = useMemo(
|
|
161
|
+
() => ({
|
|
162
|
+
segments,
|
|
163
|
+
drillDownPanel,
|
|
164
|
+
pushSegment,
|
|
165
|
+
popTo,
|
|
166
|
+
resetNav,
|
|
167
|
+
registerPopHandler,
|
|
168
|
+
setDrillDownPanel,
|
|
169
|
+
registerDrillDownPanel,
|
|
170
|
+
}),
|
|
171
|
+
[
|
|
172
|
+
segments,
|
|
173
|
+
drillDownPanel,
|
|
174
|
+
pushSegment,
|
|
175
|
+
popTo,
|
|
176
|
+
resetNav,
|
|
177
|
+
registerPopHandler,
|
|
178
|
+
setDrillDownPanel,
|
|
179
|
+
registerDrillDownPanel,
|
|
180
|
+
],
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
return (
|
|
184
|
+
<ModuleConfigNavContext.Provider value={value}>{children}</ModuleConfigNavContext.Provider>
|
|
185
|
+
)
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
export function useModuleConfigNav() {
|
|
189
|
+
const ctx = useContext(ModuleConfigNavContext)
|
|
190
|
+
if (!ctx) {
|
|
191
|
+
throw new Error('useModuleConfigNav must be used within ModuleConfigNavProvider')
|
|
192
|
+
}
|
|
193
|
+
return ctx
|
|
194
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { useEffect, useState } from 'react'
|
|
2
|
+
import { MODULE_CONFIG_CHANGED } from '../schema/types'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* DEV-only: bump when module config is applied so the portal can re-read hubSpotData exports.
|
|
6
|
+
*/
|
|
7
|
+
export function useDevModuleConfigLiveRevision(): number {
|
|
8
|
+
const [revision, setRevision] = useState(0)
|
|
9
|
+
|
|
10
|
+
useEffect(() => {
|
|
11
|
+
if (!import.meta.env.DEV) return
|
|
12
|
+
|
|
13
|
+
const onApplied = () => {
|
|
14
|
+
setRevision((value) => value + 1)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
window.addEventListener(MODULE_CONFIG_CHANGED, onApplied)
|
|
18
|
+
return () => window.removeEventListener(MODULE_CONFIG_CHANGED, onApplied)
|
|
19
|
+
}, [])
|
|
20
|
+
|
|
21
|
+
return revision
|
|
22
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { useMemo } from 'react'
|
|
2
|
+
import { getDevHubSpotLiveExports, type DevHubSpotLiveExports } from '@/data/dev-hubspot-sync'
|
|
3
|
+
import { refreshHubSpotUserDetails } from '@/data/hubSpotData'
|
|
4
|
+
import { useDevModuleConfigLiveRevision } from './use-dev-module-config-live-revision'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* DEV-only: re-read hubSpotData exports after module config Apply.
|
|
8
|
+
* Production callers receive revision 0 and should keep using module-level imports.
|
|
9
|
+
*/
|
|
10
|
+
export function useHubSpotLiveExports(): {
|
|
11
|
+
revision: number
|
|
12
|
+
live: DevHubSpotLiveExports | null
|
|
13
|
+
} {
|
|
14
|
+
const revision = useDevModuleConfigLiveRevision()
|
|
15
|
+
|
|
16
|
+
return useMemo(() => {
|
|
17
|
+
if (!import.meta.env.DEV) {
|
|
18
|
+
return { revision: 0, live: null }
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
refreshHubSpotUserDetails()
|
|
22
|
+
return {
|
|
23
|
+
revision,
|
|
24
|
+
live: getDevHubSpotLiveExports(),
|
|
25
|
+
}
|
|
26
|
+
}, [revision])
|
|
27
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { useCallback, useEffect, useState } from 'react'
|
|
2
|
+
import {
|
|
3
|
+
getModuleConfigState,
|
|
4
|
+
getModuleFieldValues,
|
|
5
|
+
initModuleConfigRuntime,
|
|
6
|
+
updateModuleFieldValues,
|
|
7
|
+
} from '../runtime/module-config-runtime'
|
|
8
|
+
import { validateModuleConfig, type ValidationError } from '../schema/validate-module-config'
|
|
9
|
+
import { loadWorkspaceMeta } from '../storage/workspace-store'
|
|
10
|
+
import { MODULE_CONFIG_CHANGED } from '../schema/types'
|
|
11
|
+
import type { ModuleConfigState } from '../runtime/module-config-runtime'
|
|
12
|
+
import type { ModuleFieldValues } from '../schema/types'
|
|
13
|
+
|
|
14
|
+
export function useModuleConfig() {
|
|
15
|
+
const [state, setState] = useState<ModuleConfigState | null>(() => getModuleConfigState())
|
|
16
|
+
const [draftValues, setDraftValues] = useState<ModuleFieldValues>(() => getModuleFieldValues())
|
|
17
|
+
const [appliedValues, setAppliedValues] = useState<ModuleFieldValues>(() => getModuleFieldValues())
|
|
18
|
+
const [validationErrors, setValidationErrors] = useState<Array<ValidationError>>([])
|
|
19
|
+
const [persistenceSource, setPersistenceSource] = useState<'local' | 'example' | 'empty'>('empty')
|
|
20
|
+
const [isApplying, setIsApplying] = useState(false)
|
|
21
|
+
|
|
22
|
+
const isDirty =
|
|
23
|
+
JSON.stringify(draftValues) !== JSON.stringify(appliedValues)
|
|
24
|
+
|
|
25
|
+
useEffect(() => {
|
|
26
|
+
void loadWorkspaceMeta().then((source) => setPersistenceSource(source ?? 'empty'))
|
|
27
|
+
}, [appliedValues])
|
|
28
|
+
|
|
29
|
+
useEffect(() => {
|
|
30
|
+
const onChange = (event: Event) => {
|
|
31
|
+
const detail = (event as CustomEvent<ModuleConfigState>).detail
|
|
32
|
+
setState(detail)
|
|
33
|
+
setDraftValues(detail.moduleValues)
|
|
34
|
+
setAppliedValues(detail.moduleValues)
|
|
35
|
+
setValidationErrors([])
|
|
36
|
+
}
|
|
37
|
+
window.addEventListener(MODULE_CONFIG_CHANGED, onChange)
|
|
38
|
+
return () => window.removeEventListener(MODULE_CONFIG_CHANGED, onChange)
|
|
39
|
+
}, [])
|
|
40
|
+
|
|
41
|
+
const setFieldValue = useCallback((path: string, value: unknown) => {
|
|
42
|
+
setDraftValues((current) => {
|
|
43
|
+
const next = structuredClone(current)
|
|
44
|
+
const parts = path.split('.')
|
|
45
|
+
let cursor: Record<string, unknown> = next
|
|
46
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
47
|
+
const key = parts[i]!
|
|
48
|
+
if (cursor[key] === undefined || cursor[key] === null) cursor[key] = {}
|
|
49
|
+
cursor = cursor[key] as Record<string, unknown>
|
|
50
|
+
}
|
|
51
|
+
cursor[parts[parts.length - 1]!] = value
|
|
52
|
+
return next
|
|
53
|
+
})
|
|
54
|
+
setValidationErrors((errors) => errors.filter((error) => !error.path.startsWith(path)))
|
|
55
|
+
}, [])
|
|
56
|
+
|
|
57
|
+
const applyChanges = useCallback(async (): Promise<boolean> => {
|
|
58
|
+
const currentState = getModuleConfigState()
|
|
59
|
+
if (!currentState) return false
|
|
60
|
+
|
|
61
|
+
const errors = validateModuleConfig(currentState.schema, draftValues)
|
|
62
|
+
if (errors.length > 0) {
|
|
63
|
+
setValidationErrors(errors)
|
|
64
|
+
return false
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
setIsApplying(true)
|
|
68
|
+
try {
|
|
69
|
+
const updated = await updateModuleFieldValues(draftValues)
|
|
70
|
+
setState(updated)
|
|
71
|
+
setDraftValues(updated.moduleValues)
|
|
72
|
+
setAppliedValues(updated.moduleValues)
|
|
73
|
+
setValidationErrors([])
|
|
74
|
+
const source = await loadWorkspaceMeta()
|
|
75
|
+
setPersistenceSource(source ?? 'local')
|
|
76
|
+
return true
|
|
77
|
+
} catch (error) {
|
|
78
|
+
console.error('[module-config] Apply failed', error)
|
|
79
|
+
setValidationErrors([
|
|
80
|
+
{
|
|
81
|
+
path: '_apply',
|
|
82
|
+
label: 'Apply',
|
|
83
|
+
message:
|
|
84
|
+
error instanceof Error
|
|
85
|
+
? error.message
|
|
86
|
+
: 'Failed to save config. Restart yarn local if the dev server was started before the module-config plugin was added.',
|
|
87
|
+
},
|
|
88
|
+
])
|
|
89
|
+
return false
|
|
90
|
+
} finally {
|
|
91
|
+
setIsApplying(false)
|
|
92
|
+
}
|
|
93
|
+
}, [draftValues])
|
|
94
|
+
|
|
95
|
+
const discardDraft = useCallback(() => {
|
|
96
|
+
setDraftValues(appliedValues)
|
|
97
|
+
setValidationErrors([])
|
|
98
|
+
}, [appliedValues])
|
|
99
|
+
|
|
100
|
+
const confirmDiscardIfDirty = useCallback((): boolean => {
|
|
101
|
+
if (!isDirty) return true
|
|
102
|
+
return window.confirm('You have unsaved changes. Discard them?')
|
|
103
|
+
}, [isDirty])
|
|
104
|
+
|
|
105
|
+
return {
|
|
106
|
+
state,
|
|
107
|
+
moduleValues: draftValues,
|
|
108
|
+
appliedValues,
|
|
109
|
+
isDirty,
|
|
110
|
+
isApplying,
|
|
111
|
+
validationErrors,
|
|
112
|
+
persistenceSource,
|
|
113
|
+
setFieldValue,
|
|
114
|
+
applyChanges,
|
|
115
|
+
discardDraft,
|
|
116
|
+
confirmDiscardIfDirty,
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export async function ensureModuleConfigReady() {
|
|
121
|
+
if (!getModuleConfigState()) {
|
|
122
|
+
await initModuleConfigRuntime()
|
|
123
|
+
}
|
|
124
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { formatCustomObjectLabel, formatPath } from './route-format'
|
|
2
|
+
|
|
3
|
+
export const makeLink = (menuItem: Record<string, unknown>) => {
|
|
4
|
+
let link = menuItem?.hubspotObjectTypeId as string | undefined
|
|
5
|
+
|
|
6
|
+
if (menuItem?.companyAsMediator) {
|
|
7
|
+
link = `${link}-c`
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
if (menuItem?.specPipeLine && menuItem?.pipeLineId) {
|
|
11
|
+
link = `${link}-${menuItem?.pipeLineId}`
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
if (!link) {
|
|
15
|
+
return `/${formatPath((menuItem.tabName || menuItem.label) as string)}`
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
return `/${link}`
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function buildApiRoutes(sidebarMenuOptions: Array<Record<string, unknown>>) {
|
|
22
|
+
return sidebarMenuOptions.map((menuItem) => ({
|
|
23
|
+
hubspotObjectTypeId: `${menuItem.hubspotObjectTypeId ?? ''}`,
|
|
24
|
+
path: makeLink(menuItem),
|
|
25
|
+
title: formatCustomObjectLabel((menuItem.tabName || menuItem.label) as string),
|
|
26
|
+
icon: menuItem.icon,
|
|
27
|
+
isRequiredAuth: true,
|
|
28
|
+
isHeader: true,
|
|
29
|
+
companyAsMediator: menuItem.companyAsMediator,
|
|
30
|
+
pipeLineId: menuItem.pipeLineId,
|
|
31
|
+
specPipeLine: menuItem.specPipeLine,
|
|
32
|
+
objectDescription: menuItem.objectDescription,
|
|
33
|
+
objectUserProperties: menuItem.objectUserProperties,
|
|
34
|
+
homeCardsView: menuItem.homeCardsView,
|
|
35
|
+
objectUserPropertiesView: menuItem.objectUserPropertiesView,
|
|
36
|
+
}))
|
|
37
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/** Route path / label helpers — runtime-safe, no consumer path aliases. */
|
|
2
|
+
|
|
3
|
+
export const formatPath = (key: unknown) => {
|
|
4
|
+
if (!key || typeof key !== 'string') return ''
|
|
5
|
+
|
|
6
|
+
return key
|
|
7
|
+
.replace(/\s+/g, '-')
|
|
8
|
+
.replace(/#/g, '%23')
|
|
9
|
+
.replace(/\b\w/g, (l: string) => l.toLowerCase())
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function formatCustomObjectLabel(key = '') {
|
|
13
|
+
if (!key || typeof key !== 'string') return ''
|
|
14
|
+
|
|
15
|
+
return key
|
|
16
|
+
.replace(/-/g, ' ')
|
|
17
|
+
.replace(/%23/g, '#')
|
|
18
|
+
.replace(/\b[a-z]/g, (l: string) => l.toUpperCase())
|
|
19
|
+
}
|
|
@@ -1,9 +1,59 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Getting started
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
This project was scaffolded with **WoodsCLI** (`@woodsportal/hubspot-kit`).
|
|
4
|
+
|
|
5
|
+
## Daily dev
|
|
4
6
|
|
|
5
7
|
```bash
|
|
6
|
-
|
|
8
|
+
yarn install
|
|
7
9
|
wp doctor
|
|
8
10
|
wp dev
|
|
9
11
|
```
|
|
12
|
+
|
|
13
|
+
Open the local URL, then **Ctrl+Shift+Z** (Mac **⌃⇧Z**) to edit module fields. Click **Apply changes** to refresh the starter preview.
|
|
14
|
+
|
|
15
|
+
## Routing (`wp init`)
|
|
16
|
+
|
|
17
|
+
Default is **hash history** — same as `woodsportal-client-frontend` (`#/guide` URLs work in HubSpot CMS).
|
|
18
|
+
|
|
19
|
+
| `wp init --router` | Behavior |
|
|
20
|
+
|--------------------|----------|
|
|
21
|
+
| `hash` (default) | `createHashHistory()` — production-safe in HubSpot embeds |
|
|
22
|
+
| `browser` | Clean `/guide` URLs for local dev only |
|
|
23
|
+
| `none` | Single-page starter, no TanStack Router |
|
|
24
|
+
|
|
25
|
+
Recorded in `woodscli.json` → `scaffold.router`.
|
|
26
|
+
|
|
27
|
+
## `src/` layout
|
|
28
|
+
|
|
29
|
+
| Path | Purpose |
|
|
30
|
+
|------|---------|
|
|
31
|
+
| `main.tsx` | Dev entry — loads styles and `mountDevPreview()` |
|
|
32
|
+
| `app.tsx` | `RouterProvider` shell (client-frontend pattern) |
|
|
33
|
+
| `config/router.ts` | Hash vs browser history |
|
|
34
|
+
| `portal/` | Dev vs production mount (`mount-dev-preview`, `mount-portal`) |
|
|
35
|
+
| `routes/` | TanStack file routes — keep thin; import from `features/` |
|
|
36
|
+
| `features/starter/` | Demo home + developer guide (replace with your app) |
|
|
37
|
+
| `components/` | Shared layout and UI |
|
|
38
|
+
| `integrations/` | Router and third-party wiring |
|
|
39
|
+
| `hubspot/` | HubSpot types and dev fixtures |
|
|
40
|
+
| `config/` | Route constants and app config |
|
|
41
|
+
| `assets/css/` | Tailwind entry for local dev |
|
|
42
|
+
|
|
43
|
+
Kit runtime expects `src/defaultData.ts` (re-exports `hubspot/default-data.ts`).
|
|
44
|
+
|
|
45
|
+
## Docs (in the kit package)
|
|
46
|
+
|
|
47
|
+
After `npm install`, see:
|
|
48
|
+
|
|
49
|
+
- `node_modules/@woodsportal/hubspot-kit/docs/CLI-REFERENCE.md`
|
|
50
|
+
- `node_modules/@woodsportal/hubspot-kit/docs/MODULES.md`
|
|
51
|
+
- `node_modules/@woodsportal/hubspot-kit/docs/HUBSPOT-CLI-SETUP.md`
|
|
52
|
+
|
|
53
|
+
## HubSpot upload
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
cp hubspot.config.yml.example hubspot.config.yml
|
|
57
|
+
# add portal + personal access key
|
|
58
|
+
wp upload
|
|
59
|
+
```
|
|
@@ -1 +1,12 @@
|
|
|
1
|
-
<!doctype html
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<title>WoodsCLI dev</title>
|
|
7
|
+
</head>
|
|
8
|
+
<body>
|
|
9
|
+
<div id="app"></div>
|
|
10
|
+
<script type="module" src="/src/main.tsx"></script>
|
|
11
|
+
</body>
|
|
12
|
+
</html>
|
|
@@ -9,11 +9,15 @@
|
|
|
9
9
|
"deploy:prod": "wp deploy --tier prod"
|
|
10
10
|
},
|
|
11
11
|
"devDependencies": {
|
|
12
|
-
"@woodsportal/hubspot-kit": "^1.0.
|
|
12
|
+
"@woodsportal/hubspot-kit": "^1.0.11",
|
|
13
|
+
"@dnd-kit/core": "^6.3.1",
|
|
14
|
+
"@dnd-kit/sortable": "^10.0.0",
|
|
15
|
+
"@dnd-kit/utilities": "^3.2.2",
|
|
13
16
|
"@tailwindcss/vite": "^4.0.6",
|
|
17
|
+
"@tanstack/react-router": "^1.121.2",
|
|
14
18
|
"@tanstack/router-plugin": "^1.121.2",
|
|
15
19
|
"@vitejs/plugin-react": "^4.3.4",
|
|
16
|
-
"babel-plugin-react-compiler": "^1.0.
|
|
20
|
+
"babel-plugin-react-compiler": "^1.0.11",
|
|
17
21
|
"react": "^19.0.0",
|
|
18
22
|
"react-dom": "^19.0.0",
|
|
19
23
|
"tailwindcss": "^4.0.6",
|
|
@@ -1 +1,44 @@
|
|
|
1
|
-
[
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"name": "title",
|
|
4
|
+
"label": "Portal title",
|
|
5
|
+
"type": "text",
|
|
6
|
+
"tab": "CONTENT",
|
|
7
|
+
"default": "My HubSpot Portal"
|
|
8
|
+
},
|
|
9
|
+
{
|
|
10
|
+
"name": "tagline",
|
|
11
|
+
"label": "Tagline",
|
|
12
|
+
"type": "text",
|
|
13
|
+
"tab": "CONTENT",
|
|
14
|
+
"help_text": "Subtitle shown under the hero title in the local dev preview.",
|
|
15
|
+
"default": "Customize this preview with Ctrl+Shift+Z, then replace StarterPage with your portal app."
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
"name": "show_feature_cards",
|
|
19
|
+
"label": "Show feature cards",
|
|
20
|
+
"type": "boolean",
|
|
21
|
+
"display": "toggle",
|
|
22
|
+
"tab": "CONTENT",
|
|
23
|
+
"default": true
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
"name": "accent_color",
|
|
27
|
+
"label": "Accent color",
|
|
28
|
+
"type": "color",
|
|
29
|
+
"tab": "STYLE",
|
|
30
|
+
"default": { "color": "#0091ae", "opacity": 100 }
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
"name": "hero_style",
|
|
34
|
+
"label": "Hero style",
|
|
35
|
+
"type": "choice",
|
|
36
|
+
"tab": "STYLE",
|
|
37
|
+
"choices": [
|
|
38
|
+
["gradient", "Gradient"],
|
|
39
|
+
["minimal", "Minimal"],
|
|
40
|
+
["bold", "Bold"]
|
|
41
|
+
],
|
|
42
|
+
"default": "gradient"
|
|
43
|
+
}
|
|
44
|
+
]
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { RouterProvider } from '@tanstack/react-router'
|
|
2
|
+
|
|
3
|
+
import { router } from '@/integrations/tanstack-router/router'
|
|
4
|
+
|
|
5
|
+
/** Root app shell — same pattern as woodsportal-client-frontend `App`. */
|
|
6
|
+
export function App() {
|
|
7
|
+
return <RouterProvider router={router} />
|
|
8
|
+
}
|
|
@@ -1,3 +1,8 @@
|
|
|
1
1
|
@import 'tailwindcss';
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
/* Kit dev-module-config — paths relative to this file (src/assets/css/) */
|
|
4
|
+
@source '../../../.wphs/.kit-module-config/**/*.{ts,tsx}';
|
|
5
|
+
@source '../../../.wphs/.kit-bootstrap.tsx';
|
|
6
|
+
@source '../../../.wphs/.kit-bootstrap-greenfield.tsx';
|
|
7
|
+
@source '../../../src/**/*.{ts,tsx}';
|
|
8
|
+
@source '../../../node_modules/@woodsportal/hubspot-kit/src/dev-module-config/**/*.{ts,tsx}';
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CDN application chunk — mounts after vendor.js (HubSpot require_js order).
|
|
3
|
+
*/
|
|
4
|
+
import { mountPortal } from '@/portal/mount-portal'
|
|
5
|
+
|
|
6
|
+
declare const __PORTAL_BUILD_ID__: string | undefined
|
|
7
|
+
|
|
8
|
+
declare global {
|
|
9
|
+
interface Window {
|
|
10
|
+
WoodsPortal?: {
|
|
11
|
+
mount: typeof mountPortal
|
|
12
|
+
version: string
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const buildId =
|
|
18
|
+
typeof __PORTAL_BUILD_ID__ !== 'undefined' ? String(__PORTAL_BUILD_ID__) : 'dev'
|
|
19
|
+
|
|
20
|
+
window.WoodsPortal = {
|
|
21
|
+
mount: mountPortal,
|
|
22
|
+
version: buildId,
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async function autoMount() {
|
|
26
|
+
if (!document.getElementById('app')) {
|
|
27
|
+
return
|
|
28
|
+
}
|
|
29
|
+
await mountPortal()
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
void autoMount()
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CDN vendor chunk — React + ReactDOM on `window` for the app IIFE externals.
|
|
3
|
+
*/
|
|
4
|
+
import React from 'react'
|
|
5
|
+
import * as ReactDOM from 'react-dom'
|
|
6
|
+
import * as ReactDOMClient from 'react-dom/client'
|
|
7
|
+
import * as jsxRuntime from 'react/jsx-runtime'
|
|
8
|
+
import * as jsxDevRuntime from 'react/jsx-dev-runtime'
|
|
9
|
+
|
|
10
|
+
declare global {
|
|
11
|
+
interface Window {
|
|
12
|
+
WoodsPortalVendor?: {
|
|
13
|
+
React: typeof React
|
|
14
|
+
ReactDOM: typeof ReactDOM
|
|
15
|
+
ReactDOMClient: typeof ReactDOMClient
|
|
16
|
+
jsxRuntime: typeof jsxRuntime
|
|
17
|
+
jsxDevRuntime: typeof jsxDevRuntime
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
window.WoodsPortalVendor = {
|
|
23
|
+
React,
|
|
24
|
+
ReactDOM,
|
|
25
|
+
ReactDOMClient,
|
|
26
|
+
jsxRuntime,
|
|
27
|
+
jsxDevRuntime,
|
|
28
|
+
}
|