Reusable component library with tenant-aware theming for B2C applications
Reusable React component library with centralized tenant themes and tenant-aware theming for B2C applications.
- Overview
- Project Architecture
- Features
- Installation
- Quick Start
- Centralized Themes
- Components
- General Components
- Interactive Components
- Checkout Components
- Form Components
- Hooks & Utilities
- Asset Management
- Integration Guide
- Multi-Tenant Architecture
- Checkout Flow Integration
- Recommendations Feature
- Header Integration
- Routing Guide
- Development
- Storybook
- Build Pipeline
- Publishing
- Technical Details
- Troubleshooting
- Changelog
- Contributing
---
The D2C Component Library provides reusable, tenant-aware UI components with centralized theme management. All tenant themes are defined in one place, ensuring consistency across all applications.
We've built a multi-tenant B2C insurance platform that enables rapid deployment of white-label insurance applications for different partners.
| Project | Purpose |
| ------------------------- | --------------------------------------------------------------------------- |
| d2c-component-library | Shared component library with centralized themes and reusable UI components |
| b2c-web-demo | Multi-tenant web application consuming the component library |
- šØ Centralized Themes - All tenant themes (Igloo, CIMB, AmmetLife) in one library
- š§© Tenant-Aware Components - Automatically adapt to tenant branding
- š¦ ES2015 Compatible - Works with older webpack configurations
- š§ Unminified Output - Better debugging and tree-shaking
- š Full TypeScript Support - Complete type definitions
- ā” Tree-Shakeable - Import only what you need
| Tenant | Port | Description |
| ------------- | ---- | ----------------------- |
| Igloo | 8000 | Default insurance brand |
| AmmetLife | 8001 | Life insurance partner |
| CIMB | 8002 | Banking partner |
---
```
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
ā d2c-component-library ā
ā āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
ā ā Centralized Themes āā
ā ā āāāāāāāāāāāāāāāāāāāāāāāāā āāāāāāāāāāāāāāāāāāāāāāāāā āā
ā ā ā iglooTheme ā ā ammetlifeTheme ā āā
ā ā āāāāāāāāāāāāāāāāāāāāāāāāā āāāāāāāāāāāāāāāāāāāāāāāāā āā
ā āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
ā āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
ā ā Shared Components āā
ā ā Button ā Card ā Banner ā Header ā CheckoutProgress ā ... āā
ā āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
ā āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
ā ā Theme Utilities āā
ā ā TenantThemeProvider ā useTenantTheme ā getTenantTheme āā
ā āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
ā
ā¼
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
ā b2c-web-demo ā
ā āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
ā ā Tenant Configurations āā
ā ā āāāāāāāāāāāāāāāāāāāāāāāāā āāāāāāāāāāāāāāāāāāāāāāāāā āā
ā ā ā igloo.ts ā ā ammetlife.ts ā āā
ā ā ā (uses iglooTheme) ā ā (uses ammetlifeTheme) ā āā
ā ā āāāāāāāāāāāāāāāāāāāāāāāāā āāāāāāāāāāāāāāāāāāāāāāāāā āā
ā āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
ā āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
ā ā Single Codebase, Multiple Tenants āā
ā ā yarn start-igloo ā http://localhost:8000 āā
ā ā yarn start-ammetlife ā http://localhost:8001 āā
ā āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
``
Frontend Framework: React 17 + TypeScript
Build System: UmiJS (React framework) for b2c-web-demo
UI Components: MUI v5 + Custom Components
Styling: Emotion (CSS-in-JS) + Tailwind CSS
State Management: Recoil + React Context
Component Library: Rollup (ES2015 output)
Documentation: Storybook
Package Manager: Yarn
---
- ā
Single source of truth for all tenant themes
- ā
Type-safe theme access with TypeScript
- ā
Dynamic theme loading with getTenantTheme()
- ā
~585 lines of code removed from consuming apps
- ā
Easy to maintain - update once, reflects everywhere
| Tenant | Theme Export | Description |
| ------------- | ---------------- | ----------------------- |
| Igloo | iglooTheme | Default insurance brand |cimbTheme
| CIMB | | Banking partner theme |ammetlifeTheme
| AmmetLife | | Life insurance partner |
| Category | Components |
| --------------- | ----------------------------------------------------------------------------------------- |
| General | Button, Card, Banner, Header, NewHeader, Footer |
| Interactive | RecommendationsDrawer, ProductSelectionDrawer, QuestionSection, OptionButton, ToggleGroup |
| Checkout | CheckoutProgress, ProductCard, CheckoutHeader, CheckoutFormButton, HealthQuestionGroup |
| Forms | PersonalInformationForm, ContactDetailsForm, HealthInformationForm |
- useTenantTheme() - Access tenant theme and IDuseTenantId()
- - Get current tenant IDuseIsTenant()
- - Check tenant matchuseTenantLogo()
- - Get tenant logouseTenantAsset()
- - Get tenant-specific asset
- getTenantTheme() - Get theme by tenant IDisValidTenantId()
- - Validate tenant IDgetThemeColor()
- - Extract colors from themecreateThemeCSSVariables()
- - Generate CSS variables
#### For Development Teams
| Benefit | Impact |
| -------------------------- | -------------------------------------------------------- |
| Single Source of Truth | Update themes once, reflected everywhere |
| ~585 Lines Removed | From consuming apps (no more duplicated themes) |
| Type Safety | Full TypeScript support with interfaces |
| Faster Development | Reuse tested components instead of building from scratch |
| Better DX | Hot reload, Storybook for component development |
#### For Business
| Benefit | Impact |
| ----------------------------- | -------------------------------------------- |
| Faster Partner Onboarding | Days instead of weeks to launch a new tenant |
| Consistent UX | Same quality experience across all brands |
| Reduced Costs | One team maintains one codebase |
| Scalability | Easy to add new tenants and features |
---
Perfect for active development when working on the library.
`bash1. Build the library
cd /path/to/d2c-component-library
yarn install
yarn build
Workflow:
`bash
Make changes to library
cd d2c-component-library
... edit files ...
yarn buildUpdate consuming app
cd ../b2c-web-demo
yarn install # Copies updated build
`$3
For production deployments and CI/CD.
Install:
`bash
yarn add igloo-d2c-components
or
npm install igloo-d2c-components
`Configure authentication (if using private registry):
`bash
For npm
export NPM_AUTH_TOKEN="your-npm-token"For GitLab Package Registry
export GITLAB_NPM_TOKEN="glpat-your-token"
`$3
`bash
Check installed version
yarn lib:checkGet package information
yarn lib:infoUpgrade to latest version (from registry)
yarn lib:upgradeInstall local development version
yarn lib:install-localInstall registry version
yarn lib:install-registryVerify registry configuration
yarn lib:verify-registry
`---
š Quick Start
$3
`tsx
import {
TenantThemeProvider,
iglooTheme,
cimbTheme,
ammetlifeTheme
} from 'igloo-d2c-components'function App() {
return (
)
}
`$3
`tsx
import { TenantThemeProvider, getTenantTheme } from 'igloo-d2c-components'function App({ tenantId }) {
const theme = getTenantTheme(tenantId) // 'igloo', 'cimb', or 'ammetlife'
return (
)
}
`$3
`tsx
import { Button, Card, Banner } from 'igloo-d2c-components'function MyPage() {
return (
{/ Tenant-themed button /}
{/ Tenant-themed card /}
title="My Card"
content="Card content"
tenantAccent
/>
{/ Tenant-themed banner /}
title="Welcome"
description="Get started today"
gradient
/>
)
}
`---
šØ Centralized Themes
$3
Before (ā Old Way):
`typescript
// In each consuming app - duplicated code
const iglooTheme = {
palette: {
primary: { main: '#5656F6', dark: '#1300A9', ... },
// ... 60+ lines per tenant
}
}
`After (ā
New Way):
`typescript
// Import from library - single source of truth
import { iglooTheme } from 'igloo-d2c-components'
`$3
`typescript
import {
// Individual themes
iglooTheme, // Igloo brand theme
cimbTheme, // CIMB bank theme
ammetlifeTheme, // AmmetLife insurance theme // Theme registry
tenantThemes, // { igloo: iglooTheme, cimb: cimbTheme, ... }
// Utility functions
getTenantTheme, // (tenantId: TenantId) => TenantThemeConfig
isValidTenantId, // (id: string) => boolean
getAvailableTenants, // () => TenantId[]
} from 'igloo-d2c-components'
`$3
Each theme includes:
`typescript
interface TenantThemeConfig {
palette: {
// Core palettes
primary: { main, dark, light, bright, plain, border }
secondary: { dim, dark, main, bright, mediumBright, light, lighter }
tertiary: { dim, dark, main, light, bright }
natural: { dim, dark, main, light, bright, granite } // Product-specific colors
motor: { main, light, bright }
car: { main, light, darkAI }
travel: { main, light }
health: { main, light? }
life: { main, light }
pet: { main }
// CIMB-specific (optional)
paCimb?: { main, light, bright, buttonBg }
}
typography: {
fontFamily: string
}
logo: string
logoDark?: string
logoAlt?: string
logoWhite?: string
favicon: string
assetBaseUrl?: string
}
`$3
In Tenant Configuration:
`typescript
// config/tenants/igloo.ts
import { iglooTheme } from 'igloo-d2c-components'const iglooConfig: TenantConfig = {
id: 'igloo',
theme: iglooTheme, // ⨠That's it!
// ... other config
}
`In Components:
`typescript
import { useTenantTheme } from 'igloo-d2c-components'function MyComponent() {
const { theme, tenantId } = useTenantTheme()
return (
backgroundColor: theme.palette.primary.main,
color: theme.palette.primary.bright
}}>
Current tenant: {tenantId}
---
š Components
$3
#### Button
Tenant-aware button component based on MUI Button.
`tsx
import { Button } from 'igloo-d2c-components'// Tenant-colored button
// Standard MUI button
`Props:
-
tenantColored?: boolean - Use tenant primary color
- variant?: 'text' | 'outlined' | 'contained' - Button variant
- All MUI ButtonProps#### Card
Tenant-aware card component based on MUI Card.
`tsx
import { Card } from 'igloo-d2c-components' title="Card Title"
content="Card content goes here"
actions={}
tenantAccent
/>
`Props:
-
title?: React.ReactNode - Card title
- content?: React.ReactNode - Card content
- actions?: React.ReactNode - Card actions
- tenantAccent?: boolean - Add tenant-colored top border
- All MUI CardProps#### Banner
Promotional banner with tenant theming.
`tsx
import { Banner } from 'igloo-d2c-components' title="Special Offer"
description="Limited time only"
action={}
gradient
/>
`#### NewHeader
Simplified header component with mobile drawer navigation.
`tsx
import { NewHeader } from 'igloo-d2c-components' logo="/path/to/logo.svg"
navigationLinks={[
{
key: 'car',
label: 'Car Insurance',
onClick: () => navigate('/car')
}
]}
additionalMenuSections={[
{
title: 'Account',
items: [
{ key: 'login', label: 'Log in', onClick: handleLogin }
]
}
]}
onLogoClick={() => navigate('/')}
/>
`Props:
`typescript
interface NewHeaderProps {
logo: string // Logo image source
navigationLinks?: NewHeaderNavigationLink[]
additionalMenuSections?: {
title?: string
items: NewHeaderNavigationLink[]
}[]
isMobile?: boolean
onLogoClick?: () => void
onMenuOpen?: () => void
onMenuClose?: () => void
customDrawerContent?: React.ReactNode
formatMessage?: (descriptor: { id: string }) => string
}
`$3
#### RecommendationsDrawer
Mobile drawer for collecting user preferences and showing personalized recommendations.
`tsx
import {
RecommendationsDrawer,
QuestionSection,
OptionButton,
ToggleGroup
} from 'igloo-d2c-components'function MyComponent() {
const [open, setOpen] = React.useState(false)
const [selectedOption, setSelectedOption] = React.useState('')
const options = [
{ value: 'option1', label: 'Option 1', icon: 'šÆ' },
{ value: 'option2', label: 'Option 2', icon: 'āØ' },
]
return (
<>
open={open}
onClose={() => setOpen(false)}
title="Personalize Your Plan"
subtitle="Answer a few questions"
primaryButtonText="Next"
onPrimaryAction={handleSubmit}
>
question="What's your preference?"
options={options}
selectedValue={selectedOption}
onSelect={setSelectedOption}
renderOption={(option, isSelected) => (
key={option.value}
value={option.value}
label={option.label}
icon={option.icon}
selected={isSelected}
onClick={setSelectedOption}
/>
)}
/>
>
)
}
`RecommendationsDrawer Props:
`typescript
interface RecommendationsDrawerProps {
open: boolean
onClose: () => void
children: React.ReactNode
headerIcon?: string
title?: string
subtitle?: string
showBackButton?: boolean
onBack?: () => void
primaryButtonText?: string
onPrimaryAction?: () => void
primaryButtonDisabled?: boolean
secondaryButtonText?: string
onSecondaryAction?: () => void
showFooter?: boolean
customFooter?: React.ReactNode
formatMessage?: (descriptor: { id: string }) => string
}
`#### ProductSelectionDrawer
Mobile-first bottom drawer for displaying insurance products in a grid.
`tsx
import { ProductSelectionDrawer, Product } from 'igloo-d2c-components'const products: Product[] = [
{
id: 'life-byond',
name: 'LIFE BYOND',
type: 'Term Plan',
logo: '/path/to/logo.png',
},
{
id: 'health-cvr',
name: 'HEALTH CVR',
type: 'CI Plan',
logo: '/path/to/logo.png',
},
]
open={open}
onClose={() => setOpen(false)}
products={products}
onProductSelect={(productId) => {
console.log('Selected:', productId)
setOpen(false)
}}
title="Select your product"
subtitle="Pick the product that suits your protection goals"
viewPlansButtonText="View plans"
/>
`#### ToggleGroup
Segmented control for binary/multiple choice toggles.
`tsx
import { ToggleGroup } from 'igloo-d2c-components' options={[
{ value: 'domestic', label: 'Domestic', icon: '/icon1.svg' },
{ value: 'international', label: 'International', icon: '/icon2.svg' }
]}
value={travelType}
onChange={setTravelType}
/>
`$3
#### CheckoutProgress
Progress bar with step indicator for multi-step checkout flows.
`tsx
import { CheckoutProgress } from 'igloo-d2c-components' currentStep={0}
totalSteps={3}
onBack={() => handleBack()}
showBackButton={true}
/>
`Props:
| Prop | Type | Default | Description |
| ---------------- | ------------ | -------- | ------------------------------------ |
|
currentStep | number | Required | Current step (0-indexed) |
| totalSteps | number | Required | Total number of steps |
| onBack | () => void | - | Callback when back button is clicked |
| showBackButton | boolean | true | Whether to show the back button |#### ProductCard
Product information card for checkout flows.
`tsx
import { ProductCard } from 'igloo-d2c-components' productName="LIFE BYOND"
planName="Plan A"
price="25"
currency="RM"
period="/month"
logoUrl="path/to/logo.png"
showIndicator={true}
/>
`#### CheckoutHeader
Complete header component combining progress, product card, and section information.
`tsx
import { CheckoutHeader } from 'igloo-d2c-components' progress={{
currentStep: 0,
totalSteps: 3,
onBack: () => handleBack(),
}}
product={{
productName: 'LIFE BYOND',
planName: 'Plan A',
price: '25',
currency: 'RM',
period: '/month',
}}
sectionTitle="Personal information"
sectionDescription="Let's get to know you better..."
sticky={true}
/>
`#### CheckoutFormButton
Fixed or floating button for form submission.
`tsx
import { CheckoutFormButton } from 'igloo-d2c-components' text="Next"
disabled={!isValid}
onClick={handleSubmit}
fixed={true}
type="button"
loading={isSubmitting}
/>
`Tenant Theme Support: The button automatically uses tenant-specific colors:
- AmmetLife: Blue (#317ABC)
- CIMB: Red (#D71920)
- Igloo: Black (#13131B)
#### HealthQuestionGroup
Health questions with Yes/No button options.
`tsx
import { HealthQuestionGroup } from 'igloo-d2c-components' question="Have you ever had any ongoing or past health conditions?"
questionNumber={1}
value={healthConditions}
onChange={(value) => setHealthConditions(value)}
error={errors.healthConditions}
labels={{ yes: 'Yes', no: 'No' }}
/>
`$3
The library includes three reusable checkout form components designed to be form library agnostic.
#### PersonalInformationForm
Collects personal details, occupation, and banking information.
`tsx
import { PersonalInformationForm } from 'igloo-d2c-components' renderField={renderField}
fields={{
full_name: {
name: 'full_name',
label: 'Full name',
value: formik.values.full_name,
error: formik.errors.full_name,
touched: formik.touched.full_name,
helperText: 'Full name as per your ID card',
onChange: formik.handleChange,
onBlur: formik.handleBlur,
},
nric: { / ... / },
date_of_birth: { / ... / },
gender: { / ... / },
// ... other fields
}}
consents={{
bank_account_confirmation: {
checked: formik.values.bank_account_confirmation,
onChange: (checked) =>
formik.setFieldValue('bank_account_confirmation', checked),
error: formik.errors.bank_account_confirmation,
},
marketing_consent: {
checked: formik.values.marketing_consent,
onChange: (checked) =>
formik.setFieldValue('marketing_consent', checked),
},
}}
onSubmit={formik.handleSubmit}
/>
`#### ContactDetailsForm
Collects contact and address information.
`tsx
import { ContactDetailsForm } from 'igloo-d2c-components' renderField={renderField}
fields={{
phone_number: { / ... / },
email_address: { / ... / },
residential_address: { / ... / },
postal_code: { / ... / },
city: { / ... / },
state: { / ... / },
}}
mailingAddressSame={{
checked: formik.values.mailing_same_as_residential,
onChange: (checked) =>
formik.setFieldValue('mailing_same_as_residential', checked),
}}
onSubmit={formik.handleSubmit}
/>
`#### HealthInformationForm
Collects health measurements and questions with gender-specific support.
`tsx
import { HealthInformationForm } from 'igloo-d2c-components' renderField={renderField}
measurementFields={{
weight: {
name: 'weight',
label: 'Weight (kg)',
type: 'number',
value: formik.values.weight,
inputProps: { min: 20, max: 300 },
onChange: formik.handleChange,
},
height: { / ... / },
}}
healthQuestions={[
{
question: 'Have you ever had any ongoing or past health conditions?',
questionNumber: 1,
name: 'health_conditions',
value: formik.values.health_conditions,
onChange: (value) => formik.setFieldValue('health_conditions', value),
},
]}
onSubmit={formik.handleSubmit}
/>
`---
šØ Hooks & Utilities
$3
#### useTenantTheme()
Access tenant theme configuration and ID.
`tsx
import { useTenantTheme } from 'igloo-d2c-components'function MyComponent() {
const { theme, tenantId } = useTenantTheme()
const primaryColor = theme.palette.primary.main
return
...
}
`#### useTenantId()
Get current tenant ID.
`tsx
import { useTenantId } from 'igloo-d2c-components'function MyComponent() {
const tenantId = useTenantId() // 'igloo' | 'cimb' | 'ammetlife'
return
Current tenant: {tenantId}
}
`#### useIsTenant()
Check if current tenant matches a specific ID.
`tsx
import { useIsTenant } from 'igloo-d2c-components'function MyComponent() {
const isCIMB = useIsTenant('cimb')
if (isCIMB) {
return
}
return
}
`#### useTenantLogo()
Get tenant logo URL.
`tsx
import { useTenantLogo } from 'igloo-d2c-components'function MyComponent() {
const logo = useTenantLogo() // Default logo
const darkLogo = useTenantLogo('dark') // Dark variant
return 
}
`$3
#### getTenantTheme()
Get theme configuration by tenant ID.
`tsx
import { getTenantTheme } from 'igloo-d2c-components'const theme = getTenantTheme('igloo')
console.log(theme.palette.primary.main) // '#5656F6'
`#### isValidTenantId()
Type guard to check if a string is a valid tenant ID.
`tsx
import { isValidTenantId } from 'igloo-d2c-components'if (isValidTenantId(userInput)) {
const theme = getTenantTheme(userInput) // Type-safe!
}
`#### getAvailableTenants()
Get list of all available tenant IDs.
`tsx
import { getAvailableTenants } from 'igloo-d2c-components'const tenants = getAvailableTenants()
// ['igloo', 'cimb', 'ammetlife']
`#### getThemeColor()
Extract color from theme using dot notation.
`tsx
import { getThemeColor } from 'igloo-d2c-components'const color = getThemeColor(theme, 'primary.main', '#000')
// Returns theme.palette.primary.main or '#000' if not found
`---
š¦ Asset Management
$3
`
d2c-component-library/src/assets/
āāā icons/ # Common UI icons
ā āāā alert.svg
ā āāā arrow-down.svg
ā āāā close.svg
ā āāā facebook.svg
ā āāā instagram.svg
ā āāā youtube.svg
ā āāā index.ts # Icon path utilities
āāā tenants/ # Tenant-specific assets
ā āāā igloo/logo.svg
ā āāā cimb/logo.svg
ā āāā ammetlife/logo.svg
āāā index.ts # Main asset exports
`$3
`
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
ā ASSET MANAGEMENT ARCHITECTURE ā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā¤
ā ā
ā d2c-component-library/ ā
ā āāā src/assets/ ā
ā āāā icons/ ā Common UI icons ā
ā āāā tenants/ ā Tenant logos ā
ā āāā igloo/logo.svg ā
ā āāā cimb/logo.svg ā
ā āāā ammetlife/logo.svg ā
ā ā
ā BUILD ā¬ļø ā
ā dist/assets/ ā Published with library ā
ā ā
ā INSTALL ā¬ļø ā
ā node_modules/igloo-d2c-components/dist/assets/ ā
ā ā
ā COPY (yarn copy-assets) ā¬ļø ā
ā public/assets/ ā Served by UMI ā
ā ā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
`$3
`typescript
// Get Tenant Logo
import { useTenantLogo } from 'igloo-d2c-components'function MyComponent() {
const logo = useTenantLogo() // Default logo
const darkLogo = useTenantLogo('dark') // Dark variant
return 
}
// Get Icon Paths
import { ICON_PATHS, getIconPath } from 'igloo-d2c-components'

})
`$3
| Asset Type | Location | Reason |
| --------------------- | ------------------- | --------------------------- |
| Generic UI Icons | Library | Reusable across projects |
| Tenant Logos | Library (via theme) | Centralized management |
| Insurer Logos | CDN | Shared, updated by partners |
| Marketing Banners | Application | Frequent changes |
| Documents (PDFs) | CDN | Large, rarely change |
$3
Important: Assets need to be copied to the
public/ folder for UMI to serve them:`bash
In consuming application
yarn copy-assets # Copies library assets to public/assets/
`The theme paths reference
/assets/tenants/{tenant}/logo.svg which maps to:- Theme config:
logo: '/assets/tenants/igloo/logo.svg'
- File location: public/assets/tenants/igloo/logo.svg
- Browser URL: http://localhost:8001/assets/tenants/igloo/logo.svg---
š Integration Guide
$3
#### Tenant Configuration Structure
`
config/tenants/
āāā index.ts # Tenant registry and utility functions
āāā types.ts # TypeScript interfaces
āāā igloo.ts # Igloo tenant configuration
āāā ammetlife.ts # AmmetLife tenant configuration
āāā cimb.ts # CIMB tenant configuration
`#### Tenant Configuration Example
`typescript
// config/tenants/igloo.ts
import { iglooTheme } from 'igloo-d2c-components'
import { TenantConfig } from './types'const iglooConfig: TenantConfig = {
id: 'igloo',
name: 'igloo',
displayName: 'Igloo',
domain: 'igloo.co.id',
theme: iglooTheme, // ā Centralized theme from component library
features: {
showPAMenu: true,
enableAIChatbot: true,
showWorkshopEntryPoint: false,
enableFreeAddons: false,
},
routes: {
enabled: ['car', 'motorbike', 'health', 'life', 'travel', 'pet'],
disabled: [],
},
branding: {
companyName: 'Igloo',
supportEmail: 'hello@iglooinsure.com',
supportPhone: '+62 21 5022 0888',
},
integrations: {
gtmCode: 'GTM-5RHHNVQ',
gaCode: 'G-QQV6F41YPJ',
},
}
export default iglooConfig
`#### AmmetLife Custom Homepage
AmMetLife uses a custom homepage that displays the life insurance landing page:
`typescript
// config/tenants/ammetlife.ts
customRoutes: [
{
path: '/',
exact: true,
component: '@/pages/life-landing-page/index.tsx',
},
]
`$3
#### Complete 3-Step Checkout
The library provides components for a complete checkout flow:
1. Personal Information - 11 fields + 2 consent checkboxes
2. Contact Details - 6 address fields + mailing checkbox
3. Health Information - 2 measurements + health questions (gender-specific)
#### Session Storage Requirements
`typescript
// Required before navigating to checkout
session.setItem('plan', {
name: 'Plan A',
premiumInfo: { currency: 'RM', monthlyAmount: '25' }
});session.setItem('product', {
name: 'LIFE BYOND',
logoUrl: 'url-to-logo'
});
`#### Navigation Flow
`
PDP (/en/ammetlife-pdp)
ā Click "Buy now"
Checkout Step 1/3 (Personal Info) ā Back ā PDP
ā Click "Next"
Checkout Step 2/3 (Contact Details) ā Back ā Step 1
ā Click "Next"
Checkout Step 3/3 (Health Information) ā Back ā Step 2
ā Click "Submit"
Payment (/en/payment-options)
`#### Back Button Logic
`typescript
const handleBack = () => {
if (currentStep === 0) {
// On first step, redirect to PDP
window.location.href = /${currentLocale}/ammetlife-pdp;
} else {
// On other steps, go to previous step
setCurrentStep(currentStep - 1);
window.scrollTo({ top: 0, behavior: 'smooth' });
}
};
`#### Validation Rules
Personal Information:
- Full name: minimum 2 characters
- NRIC: numbers and dashes only
- DOB: 30 days to 80 years old
- Bank account: numbers only
- Bank confirmation: must be checked
Contact Details:
- Phone: 9-14 digits
- Email: valid format
- Address: minimum 10 characters
- Postal code: exactly 5 digits
Health Information:
- Weight: 20-300 kg
- Height: 50-250 cm
- All questions: must select Yes or No
$3
#### User Flow
`
1. User sees "See my recommendations" button
ā User clicks
2. Mobile drawer slides up from bottom (95vh height)
ā
3. Drawer shows:
- Toggle: Domestic/International
- 5 Questions with selectable options
- Next button
ā User answers questions
4. User clicks "Next" button
ā
5. Data collected, drawer closes, recommendations shown
`#### Implementation
`typescript
import { TravelRecommendations } from '@/components/recommendations';
import { RecommendationsButton } from '@/components/recommendations/recommendations-button';export default function MyPage() {
const [showRecommendations, setShowRecommendations] = React.useState(false);
return (
onClick={() => setShowRecommendations(true)}
/> open={showRecommendations}
onClose={() => setShowRecommendations(false)}
onSubmit={(data) => {
console.log('User selected:', data);
}}
/>
);
}
`#### Data Structure
`typescript
interface TravelRecommendationsData {
travelType: 'domestic' | 'international';
selectedPlan: string;
monthlyIncome: string;
monthlyExpenses: string;
currentCoverage: string;
employerCoverage: string;
}
`$3
#### NewHeader Implementation
The NewHeader component provides a simplified header with mobile drawer navigation.
Features:
1. Product Navigation - Dynamically loaded from navLinks
2. User Authentication - Login/Logout flow
3. Additional Links - Partnership, About Us, Blog
4. Language Selector - EN/ID with cookie persistence
5. Analytics - Google Analytics integration
Performance Improvement:
| Metric | Before | After |
| -------------- | --------- | --------- |
| Component size | 860 lines | 200 lines |
| Bundle size | ~45KB | ~15KB |
| Props count | 30+ | 9 |
$3
#### Route Pattern
`
/:locale/life-checkout
`#### Navigation with Locale
`typescript
// ā
CORRECT - With locale
const { currentLocale } = local.getItem('globalState');
window.location.href = /${currentLocale}/life-checkout;// ā WRONG - Without locale
window.location.href = '/life-checkout';
`---
š§ Development
$3
- Node.js:
>=16.20.0 <=18.x
- Yarn: ^1.22.0 (recommended) or npmCheck your version:
`bash
node --version # Should be 16.20.0 - 18.x
yarn --version # Should be 1.22.x
`$3
`bash
Clone the repository
git clone https://gitlab.iglooinsure.com/axinan/fe/d2c-component-library.git
cd d2c-component-libraryInstall dependencies
yarn install
`$3
`bash
Production build
yarn buildDevelopment mode (watch)
yarn devClean build artifacts
yarn cleanClean and rebuild
yarn clean && yarn build
`$3
`bash
Lint code
yarn lintType check
yarn type-check
`$3
The library outputs unminified code targeting ES2015 for maximum compatibility:
`
dist/
āāā cjs/
ā āāā index.js # CommonJS bundle (unminified, ES2015)
ā āāā index.js.map # Source map
āāā esm/
ā āāā index.js # ES Module bundle (unminified, ES2015)
ā āāā index.js.map # Source map
āāā types/
ā āāā index.d.ts # TypeScript definitions
āāā assets/ # Copied assets
`$3
`
d2c-component-library/
āāā src/
ā āāā components/ # Component implementations
ā ā āāā Button/
ā ā āāā Card/
ā ā āāā Banner/
ā ā āāā NewHeader/
ā ā āāā RecommendationsDrawer/
ā ā āāā ProductSelectionDrawer/
ā ā āāā CheckoutHeader/
ā ā āāā PersonalInformationForm/
ā ā āāā ...
ā āāā context/
ā ā āāā TenantThemeContext.tsx
ā āāā themes/
ā ā āāā index.ts # ā Centralized theme definitions
ā āāā types/
ā ā āāā tenant.ts # TypeScript types
ā āāā utils/
ā ā āāā theme.ts # Theme utilities
ā ā āāā assets.ts # Asset utilities
ā āāā assets/ # UI icons and tenant logos
ā āāā index.ts # Main exports
āāā dist/ # Build output (generated)
āāā examples/
ā āāā usage-example.tsx
āāā .storybook/ # Storybook configuration
āāā rollup.config.cjs # Rollup build config
āāā tsconfig.json # TypeScript config
āāā package.json
āāā README.md # This file
`---
š Storybook
The library includes Storybook for component documentation and testing.
$3
`bash
Start Storybook dev server
yarn storybook
Opens at http://localhost:6006
Build static Storybook
yarn build-storybook
Output in storybook-static/
`$3
NewHeader Stories (12 scenarios):
1. Default - Basic header with Igloo branding
2. Authenticated User - Header with logged-in user menu
3. Mobile View - Responsive mobile layout
4. CIMB Tenant - CIMB branding
5. AmMetLife Tenant - AmMetLife branding
6. Minimal Header - Bare-bones implementation
7. Custom Drawer Content - Advanced customization
8. Many Menu Items - Stress test with scrolling
9. Without Logo - Edge case testing
10. With Internationalization - i18n integration
11. All Tenants Comparison - Side-by-side view
12. Responsive Demo - Interactive responsive testing
Other Component Stories:
- RecommendationsDrawer - Complete questionnaire flow
- ProductSelectionDrawer - Product grid selection
- CheckoutHeader - Progress and product display
- All checkout components
$3
| Key | Action |
| --- | ------------------- |
|
F | Toggle fullscreen |
| S | Toggle sidebar |
| A | Toggle addons panel |
| T | Toggle toolbar |
| D | Toggle dark mode |
| / | Search stories |---
š§ Build Pipeline
$3
1. Module parse error: Webpack couldn't parse igloo-d2c-components ESM format
2. Source map error: Terser plugin couldn't process source maps
$3
#### 1. Library: Disabled Source Maps
`javascript
// rollup.config.cjs
output: [
{
file: packageJson.main,
format: 'cjs',
sourcemap: false, // ā
Disabled
exports: 'named',
banner: '"use client"',
},
]
`#### 2. Consumer App: Webpack Configuration
`typescript
// .umirc.ts
chainWebpack: (memo, { type, webpack }) => {
// Prefer CommonJS over ESM
memo.resolve.mainFields.clear().add('main').add('module').add('browser'); // Include igloo-d2c-components in babel transpilation
memo.module
.rule('js')
.include.add(/node_modules[\\/]igloo-d2c-components/)
.end();
}
`$3
Option A: Publish to npm (Recommended)
`bash
cd d2c-component-library
npm publish --access public
`Option B: Use Git URL
`json
{
"igloo-d2c-components": "git+https://gitlab.com/axinan/fe/d2c-component-library.git#main"
}
`Option C: Multi-Repo CI/CD
`yaml
before_script:
- cd ..
- git clone https://gitlab.com/axinan/fe/d2c-component-library.git
- cd d2c-component-library && yarn install && yarn build
- cd ../b2c-web-demo && yarn install
`---
š¦ Publishing
$3
1. Ensure clean working directory:
`bash
git status # Should be clean
`2. Update version in package.json:
`json
{
"version": "1.0.7"
}
`3. Build the library:
`bash
yarn build
`$3
Set up NPM token:
`bash
export NPM_AUTH_TOKEN="npm_your_actual_token"
`Publish:
`bash
Automated script with safety checks
./publish-to-npm.shOr manual
yarn build
npm publish --access public
`$3
Semantic Versioning:
- Major (1.0.0 ā 2.0.0): Breaking changes
- Minor (1.0.0 ā 1.1.0): New features, backwards compatible
- Patch (1.0.0 ā 1.0.1): Bug fixes
Update version:
`bash
npm version patch # 1.0.6 ā 1.0.7
npm version minor # 1.0.6 ā 1.1.0
npm version major # 1.0.6 ā 2.0.0
`---
š¬ Technical Details
$3
Rollup Configuration (
rollup.config.cjs):`javascript
{
input: 'src/index.ts',
output: [
{
file: 'dist/cjs/index.js',
format: 'cjs',
sourcemap: true,
exports: 'named',
banner: '"use client"',
},
{
file: 'dist/esm/index.js',
format: 'esm',
sourcemap: true,
exports: 'named',
banner: '"use client"',
},
],
}
`$3
Target: ES2015 for maximum compatibility
Module: ESNext for tree-shaking
Strict: Enabled for type safety
$3
`json
{
"peerDependencies": {
"@emotion/react": "^11.11.4",
"@emotion/styled": "^11.11.5",
"@mui/icons-material": "^5.15.20",
"@mui/material": "^5.15.20",
"@mui/styles": "^5.15.20",
"react": "^17.0.0",
"react-dom": "^17.0.0"
}
}
`---
š Troubleshooting
$3
Error:
`
Module parse failed: Unexpected token
`Solution:
`bash
cd d2c-component-library
yarn clean && yarn build
cd ../b2c-web-demo
rm -rf node_modules/igloo-d2c-components
yarn install
`$3
Error:
`
Theme not found for tenant: xxx
`Solution: Use valid tenant IDs:
'igloo', 'cimb', or 'ammetlife'.$3
Error:
`
Module '"igloo-d2c-components"' has no exported member 'iglooTheme'
`Solution:
1. Rebuild the library:
yarn build
2. Reinstall in consuming app: yarn install
3. Restart TypeScript server in your IDE$3
Error: Build fails with memory issues
Solution:
`bash
export NODE_OPTIONS="--max-old-space-size=4096"
yarn build
`$3
Logos not displaying:
1. Run
yarn copy-assets in consuming app
2. Verify assets in public/assets/tenants/
3. Check theme path: /assets/tenants/igloo/logo.svg$3
Issue: 404 on /life-checkout
Use locale prefix:
/en/life-checkout, not /life-checkout---
š Changelog
$3
#### Added
- CheckoutProgress - Progress bar component with step indicator
- ProductCard - Product information card for checkout flows
- CheckoutHeader - Complete checkout header combining progress and product card
- CheckoutFormButton - Fixed/floating button for form submission
- HealthQuestionGroup - Health question component with Yes/No options
- ProductSelectionDrawer - Mobile-first bottom drawer for product selection
- PersonalInformationForm - Personal details form component
- ContactDetailsForm - Contact and address form component
- HealthInformationForm - Health measurements and questions form
- Comprehensive Storybook documentation
- Integration guides for multi-tenant architecture
$3
#### Changed
- Package name configured for public NPM registry
- Updated all documentation
$3
#### Added
- Initial release of D2C Component Library
-
TenantThemeProvider - Context provider for tenant theming
- Hooks: useTenantTheme(), useTenantId(), useIsTenant()
- Components: Button, Card, Banner
- Utility functions for theme management
- Full TypeScript support
- ESM and CommonJS builds---
š¤ Contributing
$3
1. Fork the repository
2. Create a feature branch:
`bash
git checkout -b feature/my-new-feature
`3. Make your changes
4. Test thoroughly:
`bash
yarn lint
yarn type-check
yarn build
`5. Test in consuming app:
`bash
cd ../b2c-web-demo
yarn install
yarn start-igloo-dev
`6. Commit your changes:
`bash
git commit -m "feat: add new feature"
`7. Push and create a Pull Request
$3
Follow Conventional Commits:
`
():
`Types:
-
feat: New feature
- fix: Bug fix
- docs: Documentation changes
- style: Code style changes (formatting)
- refactor: Code refactoring
- test: Test additions/changes
- chore: Maintenance tasks$3
1. Add theme to
src/themes/index.ts:
`typescript
export const newTenantTheme: TenantThemeConfig = {
palette: { / ... / },
typography: { / ... / },
logo: '/assets/tenants/newtenant/logo.svg',
favicon: 'https://...',
} export const tenantThemes: Record = {
igloo: iglooTheme,
cimb: cimbTheme,
ammetlife: ammetlifeTheme,
newtenant: newTenantTheme, // Add here
}
`2. Update TenantId type in
src/types/tenant.ts:
`typescript
export type TenantId = 'igloo' | 'cimb' | 'ammetlife' | 'newtenant'
`3. Add logo asset to
src/assets/tenants/newtenant/4. Build and test:
`bash
yarn build
`$3
1. Create component folder in
src/components/
2. Include: ComponentName.tsx, styled.tsx, index.ts
3. Add Storybook stories
4. Export from src/index.ts
5. Update documentation---
š License
MIT
---
š„ Team
Frontend Engineering Team - Axinan/Igloo
---
š Links
- Repository:
- NPM Package:
- Consuming App:
---
š Quick Reference
$3
`bash
Local development
yarn add igloo-d2c-components@file:../d2c-component-libraryProduction
yarn add igloo-d2c-components@latest
`$3
`typescript
import { iglooTheme, cimbTheme, ammetlifeTheme, getTenantTheme } from 'igloo-d2c-components'
`$3
`typescript
import { Button, Card, Banner, TenantThemeProvider, NewHeader, CheckoutHeader } from 'igloo-d2c-components'
`$3
`typescript
import { useTenantTheme, useTenantId, useIsTenant, useTenantLogo } from 'igloo-d2c-components'
`$3
`bash
Build
yarn buildPublish to NPM
./publish-to-npm.sh
`$3
`bash
yarn start-igloo # Port 8000
yarn start-ammetlife # Port 8001
yarn start-cimb # Port 8002
``---
Version: 1.0.11+
Last Updated: December 2025
Node.js: >=16.20.0 <=18.x
Target: ES2015
Output: Unminified