@happnest/enterprise
Per-client branding without code changes. The backend's system_branding table feeds SystemBrandingInjector, which writes CSS variables onto <html>. Every component reads them through Tailwind utilities.
Migration from @pamaconu/core is complete — 12 local primitives shipped, all 168 TSX files swept, 0 hardcoded Tailwind color literals remaining. 89 suites / 954 tests passing.
Migration status
Fully decoupled from @pamaconu/core visual components. All five steps complete.
Follow-ups also complete: HappnestBrandmark, PamaconuLogo, UserAvatarManager, MultiLanguageInput, SidePanelLayout, Timeline — all migrated to local components. CoreIcon remains (icon registry, functional only).
Architecture
Five files, one direction. The backend is the source of branding; CSS variables are the runtime contract.
bodyFont · displayFont
on document.documentElement
--theme-primary-50…900, etc.
(falls back when no branding)
(10 families)
selected font at runtime
Defaults — what ships before any client customises
From packages/enterprise/src/design-system/defaults.ts.
Colors
Fonts
Body and display defaults; clients can swap via the /branding admin page.
Token contract
Enterprise predates the unified contract; it still keeps the --theme-primary-50…900 scale and shadcn-style aliases (--background, --foreground, --ring, …).
Theme triplet
Primary scale · 50 → 900
Note for the unified contract: the core refactor explicitly does not delete this scale.
Enterprise + admin keep working against --primary-* while the new --accent / --bg contract lives alongside.
Per-client branding, live
One JSX, three "clients". SystemBrandingInjector writes a different --theme-primary for each — every component re-renders themed without code changes.
Bulk Excel Import
Enterprise users can create multiple experiences at once by uploading an Excel (.xlsx) file.
Feature details
- Parsing: Uses
xlsxto parse the file into JSON. Dates are properly handled withcellDates: trueand formatted as ISO. - Payload mapping: Maps row columns to the
ExperienceCreateInputschema payload. - Validation: Validates headers, parses dates, numbers, arrays, and flags any invalid rows in the UI before importing.
- Images: Parses comma-separated URLs in the
image_urlscolumn to an array of objects:[{ url }]. - UI: Handled by
ImportExperiencesModal.tsx. Shows progress bar and per-row success/error feedback.
Where things live
The enterprise design system already follows the rule "one canonical location per concern".
layout.tsx before component styles.ENTERPRISE_DESIGN_DEFAULTS · single source for "what runs when no client branding is configured".--font-sans + --font-brand and injects the Google Fonts <link>.The flow in one line
system_branding
→
useSystemBranding
→
SystemBrandingInjector
→
CSS vars on <html>
→
every component (via Tailwind utilities)
Local primitives
All live in src/components/ui/. Import from the barrel: import { EnterpriseButton } from "@/components/ui". No @pamaconu/core needed for visuals.
--primary / --primary-foreground.--card / --border.--primary + --primary-700. Replaces CustomSpinner + HappnestSpinner.--card..enterprise-input. Reads --border / --ring / --input..enterprise-select..enterprise-textarea. Resize-vertical, min 80 px..enterprise-alert--danger. Replaces core's ErrorAlert.--card / --border.How to extend
Adding a new design variable
Put it in tokens.css. Do not add :root declarations inside enterprise.css — that file is for component classes only.
Adding a customisable Google Font
Append it to CURATED_FONTS in fonts.ts. The admin /branding page picks it up automatically.
Adding a per-client override
No code change. Edit system_branding for that client. SystemBrandingInjector picks it up on the next page load.
Keeping in sync with core's refactor
Enterprise is deliberately out of scope for the core refactor. The legacy --primary-* scale stays. New --accent contract lives alongside without affecting enterprise.
Adding a new local primitive
Create src/components/ui/EnterpriseXxx.tsx, add the CSS class to styles/enterprise.css using @apply + token utilities, then export from components/ui/index.ts. No core imports.