Admin / back-office · Migration complete — May 2026

@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.

Shipped

Migration status

Fully decoupled from @pamaconu/core visual components. All five steps complete.

Step What Status
1 Standalone Tailwind config (no presets:[coreConfig]) Done
2 Remove @pamaconu/core/styles/globals.css from layout.tsx Done
3 Local primitives — Spinner, Card, Button, Badge, Modal, SidePanel, EmptyState, Input, Select, TextArea, ErrorAlert, PageActionBar Done
4 Replace all visual @pamaconu/core imports in source files Done
5 Color token sweep — replace hardcoded Tailwind literals with --primary/--foreground/--border/… equivalents across all 168 TSX files Done

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.

backend: system_branding
primaryColor · secondaryColor
bodyFont · displayFont
useSystemBranding()
React hook · fetches per request
SystemBrandingInjector
writes --primary, --font-sans, …
on document.documentElement
↓ files involved ↓
design-system/tokens.css
every --theme-primary,
--theme-primary-50…900, etc.
design-system/defaults.ts
ENTERPRISE_DESIGN_DEFAULTS
(falls back when no branding)
design-system/fonts.ts
curated Google Fonts catalog
(10 families)
design-system/apply-fonts.ts
injects <link> for the
selected font at runtime

Defaults — what ships before any client customises

From packages/enterprise/src/design-system/defaults.ts.

Colors

primary
#1E3A8A
secondary
#64748B
accent / warning / info
#3B82F6

Fonts

Body and display defaults; clients can swap via the /branding admin page.

Inter — body / UI font
Space Grotesk — display

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

--theme-primary
222 64% 33%
--theme-primary-hover
222 64% 28%
--theme-primary-foreground
0 0% 100%

Primary scale · 50 → 900

--theme-primary-50
--theme-primary-200
--theme-primary-400
--theme-primary-600
--theme-primary-700
--theme-primary-800
--theme-primary-900
--theme-accent

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.

default · #1E3A8A
Dashboard
12 hotels · 4 active alerts
hotel-red · #C71D1D
Dashboard
12 hotels · 4 active alerts
hotel-green · #2F855A
Dashboard
12 hotels · 4 active alerts

Bulk Excel Import

Enterprise users can create multiple experiences at once by uploading an Excel (.xlsx) file.

Feature details

  • Parsing: Uses xlsx to parse the file into JSON. Dates are properly handled with cellDates: true and formatted as ISO.
  • Payload mapping: Maps row columns to the ExperienceCreateInput schema 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_urls column 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".

packages/enterprise/src/design-system/tokens.css
Every CSS variable. Imported by layout.tsx before component styles.
design-system/defaults.ts
ENTERPRISE_DESIGN_DEFAULTS · single source for "what runs when no client branding is configured".
design-system/fonts.ts
Curated Google Fonts catalog (10 families). Add new fonts here.
design-system/apply-fonts.ts
Runtime helper that sets --font-sans + --font-brand and injects the Google Fonts <link>.

The flow in one line

backend 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.

EnterpriseButton
Variants: primary · secondary · outline · ghost · danger · link. Reads --primary / --primary-foreground.
EnterpriseCard
+CardContent, CardHeader, CardTitle, CardSubtitle. Uses --card / --border.
EnterpriseSpinner
Sizes: xs–xl. SVG arc animation using --primary + --primary-700. Replaces CustomSpinner + HappnestSpinner.
EnterpriseBadge
Variants: default · success · warning · error · info. Token-based bg + text.
EnterpriseModal
Headless modal with backdrop. Title + children + footer slots. Reads --card.
EnterpriseSidePanel
Right-slide panel. isOpen · onClose · title · footer. Replaces core's SidePanel.
EnterpriseEmptyState
Icon + title + description + optional action button.
EnterpriseInput
label · error · fullWidth. CSS class .enterprise-input. Reads --border / --ring / --input.
EnterpriseSelect
label · error · options[] or children. CSS class .enterprise-select.
EnterpriseTextArea
error flag. CSS class .enterprise-textarea. Resize-vertical, min 80 px.
EnterpriseErrorAlert
message + className. Uses .enterprise-alert--danger. Replaces core's ErrorAlert.
EnterprisePageActionBar
title · breadcrumbs[] · actions · leftExtra. Replaces core's PageActionBar. Uses --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.