igloo-d2c-components
v1.0.33
Published
Reusable component library with tenant-aware theming for B2C applications
Readme
D2C Component Library
Reusable React component library with centralized tenant themes and tenant-aware theming for B2C applications.
📋 Table of Contents
- Overview
- Project Architecture
- Features
- Installation
- Quick Start
- Centralized Themes
- Components
- Hooks & Utilities
- Asset Management
- Integration Guide
- Development
- Storybook
- Build Pipeline
- Publishing
- Technical Details
- Troubleshooting
- Changelog
- Contributing
🎯 Overview
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.
What Are These Projects?
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 |
Key Highlights
- 🎨 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
Supported Tenants
| Tenant | Port | Description | | ------------- | ---- | ----------------------- | | Igloo | 8000 | Default insurance brand | | AmmetLife | 8001 | Life insurance partner | | CIMB | 8002 | Banking partner |
🏗️ Project Architecture
The Solution Architecture
┌─────────────────────────────────────────────────────────────────┐
│ 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 ││
│ └─────────────────────────────────────────────────────────────┘│
└─────────────────────────────────────────────────────────────────┘Technical Stack
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✨ Features
Centralized Theme System
- ✅ 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
Available Themes
| Tenant | Theme Export | Description |
| ------------- | ---------------- | ----------------------- |
| Igloo | iglooTheme | Default insurance brand |
| CIMB | cimbTheme | Banking partner theme |
| AmmetLife | ammetlifeTheme | Life insurance partner |
Component Categories
| 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 |
Hooks
useTenantTheme()- Access tenant theme and IDuseTenantId()- Get current tenant IDuseIsTenant()- Check tenant matchuseTenantLogo()- Get tenant logouseTenantAsset()- Get tenant-specific asset
Utilities
getTenantTheme()- Get theme by tenant IDisValidTenantId()- Validate tenant IDgetThemeColor()- Extract colors from themecreateThemeCSSVariables()- Generate CSS variables
Key Benefits
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 |
📦 Installation
Option 1: Local Development (Recommended for Development)
Perfect for active development when working on the library.
# 1. Build the library
cd /path/to/d2c-component-library
yarn install
yarn build
# 2. In consuming app (b2c-web-demo)
cd /path/to/b2c-web-demo
# Add to package.json:
{
"dependencies": {
"igloo-d2c-components": "file:../d2c-component-library"
}
}
# Install
yarn installWorkflow:
# Make changes to library
cd d2c-component-library
# ... edit files ...
yarn build
# Update consuming app
cd ../b2c-web-demo
yarn install # Copies updated buildOption 2: NPM Registry (Production)
For production deployments and CI/CD.
Install:
yarn add igloo-d2c-components
# or
npm install igloo-d2c-componentsConfigure authentication (if using private registry):
# For npm
export NPM_AUTH_TOKEN="your-npm-token"
# For GitLab Package Registry
export GITLAB_NPM_TOKEN="glpat-your-token"Component Library Management Scripts (in b2c-web-demo)
# Check installed version
yarn lib:check
# Get package information
yarn lib:info
# Upgrade to latest version (from registry)
yarn lib:upgrade
# Install local development version
yarn lib:install-local
# Install registry version
yarn lib:install-registry
# Verify registry configuration
yarn lib:verify-registry🚀 Quick Start
1. Import Pre-built Themes (Recommended)
import {
TenantThemeProvider,
iglooTheme,
cimbTheme,
ammetlifeTheme
} from 'igloo-d2c-components'
function App() {
return (
<TenantThemeProvider tenantId="igloo" theme={iglooTheme}>
<YourApp />
</TenantThemeProvider>
)
}2. Dynamic Theme Loading
import { TenantThemeProvider, getTenantTheme } from 'igloo-d2c-components'
function App({ tenantId }) {
const theme = getTenantTheme(tenantId) // 'igloo', 'cimb', or 'ammetlife'
return (
<TenantThemeProvider tenantId={tenantId} theme={theme}>
<YourApp />
</TenantThemeProvider>
)
}3. Use Components
import { Button, Card, Banner } from 'igloo-d2c-components'
function MyPage() {
return (
<div>
{/* Tenant-themed button */}
<Button tenantColored variant="contained">
Click Me
</Button>
{/* Tenant-themed card */}
<Card
title="My Card"
content="Card content"
tenantAccent
/>
{/* Tenant-themed banner */}
<Banner
title="Welcome"
description="Get started today"
gradient
/>
</div>
)
}🎨 Centralized Themes
Why Centralized Themes?
Before (❌ Old Way):
// In each consuming app - duplicated code
const iglooTheme = {
palette: {
primary: { main: '#5656F6', dark: '#1300A9', ... },
// ... 60+ lines per tenant
}
}After (✅ New Way):
// Import from library - single source of truth
import { iglooTheme } from 'igloo-d2c-components'Available Theme Exports
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'Theme Structure
Each theme includes:
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
}Using Themes
In Tenant Configuration:
// config/tenants/igloo.ts
import { iglooTheme } from 'igloo-d2c-components'
const iglooConfig: TenantConfig = {
id: 'igloo',
theme: iglooTheme, // ✨ That's it!
// ... other config
}In Components:
import { useTenantTheme } from 'igloo-d2c-components'
function MyComponent() {
const { theme, tenantId } = useTenantTheme()
return (
<div style={{
backgroundColor: theme.palette.primary.main,
color: theme.palette.primary.bright
}}>
Current tenant: {tenantId}
</div>
)
}📚 Components
General Components
Button
Tenant-aware button component based on MUI Button.
import { Button } from 'igloo-d2c-components'
// Tenant-colored button
<Button tenantColored variant="contained">
Tenant Colored Button
</Button>
// Standard MUI button
<Button color="primary" variant="outlined">
Default Button
</Button>Props:
tenantColored?: boolean- Use tenant primary colorvariant?: 'text' | 'outlined' | 'contained'- Button variant- All MUI ButtonProps
Card
Tenant-aware card component based on MUI Card.
import { Card } from 'igloo-d2c-components'
<Card
title="Card Title"
content="Card content goes here"
actions={<Button>Action</Button>}
tenantAccent
/>Props:
title?: React.ReactNode- Card titlecontent?: React.ReactNode- Card contentactions?: React.ReactNode- Card actionstenantAccent?: boolean- Add tenant-colored top border- All MUI CardProps
Banner
Promotional banner with tenant theming.
import { Banner } from 'igloo-d2c-components'
<Banner
title="Special Offer"
description="Limited time only"
action={<Button>Learn More</Button>}
gradient
/>NewHeader
Simplified header component with mobile drawer navigation.
import { NewHeader } from 'igloo-d2c-components'
<NewHeader
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:
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
}Interactive Components
RecommendationsDrawer
Mobile drawer for collecting user preferences and showing personalized recommendations.
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 (
<>
<Button onClick={() => setOpen(true)}>
See my recommendations
</Button>
<RecommendationsDrawer
open={open}
onClose={() => setOpen(false)}
title="Personalize Your Plan"
subtitle="Answer a few questions"
primaryButtonText="Next"
onPrimaryAction={handleSubmit}
>
<QuestionSection
question="What's your preference?"
options={options}
selectedValue={selectedOption}
onSelect={setSelectedOption}
renderOption={(option, isSelected) => (
<OptionButton
key={option.value}
value={option.value}
label={option.label}
icon={option.icon}
selected={isSelected}
onClick={setSelectedOption}
/>
)}
/>
</RecommendationsDrawer>
</>
)
}RecommendationsDrawer Props:
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.
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',
},
]
<ProductSelectionDrawer
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.
import { ToggleGroup } from 'igloo-d2c-components'
<ToggleGroup
options={[
{ value: 'domestic', label: 'Domestic', icon: '/icon1.svg' },
{ value: 'international', label: 'International', icon: '/icon2.svg' }
]}
value={travelType}
onChange={setTravelType}
/>Checkout Components
CheckoutProgress
Progress bar with step indicator for multi-step checkout flows.
import { CheckoutProgress } from 'igloo-d2c-components'
<CheckoutProgress
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.
import { ProductCard } from 'igloo-d2c-components'
<ProductCard
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.
import { CheckoutHeader } from 'igloo-d2c-components'
<CheckoutHeader
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.
import { CheckoutFormButton } from 'igloo-d2c-components'
<CheckoutFormButton
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.
import { HealthQuestionGroup } from 'igloo-d2c-components'
<HealthQuestionGroup
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' }}
/>Form Components
The library includes three reusable checkout form components designed to be form library agnostic.
PersonalInformationForm
Collects personal details, occupation, and banking information.
import { PersonalInformationForm } from 'igloo-d2c-components'
<PersonalInformationForm
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.
import { ContactDetailsForm } from 'igloo-d2c-components'
<ContactDetailsForm
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.
import { HealthInformationForm } from 'igloo-d2c-components'
<HealthInformationForm
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
Hooks
useTenantTheme()
Access tenant theme configuration and ID.
import { useTenantTheme } from 'igloo-d2c-components'
function MyComponent() {
const { theme, tenantId } = useTenantTheme()
const primaryColor = theme.palette.primary.main
return <div style={{ color: primaryColor }}>...</div>
}useTenantId()
Get current tenant ID.
import { useTenantId } from 'igloo-d2c-components'
function MyComponent() {
const tenantId = useTenantId() // 'igloo' | 'cimb' | 'ammetlife'
return <div>Current tenant: {tenantId}</div>
}useIsTenant()
Check if current tenant matches a specific ID.
import { useIsTenant } from 'igloo-d2c-components'
function MyComponent() {
const isCIMB = useIsTenant('cimb')
if (isCIMB) {
return <CIMBSpecificFeature />
}
return <DefaultFeature />
}useTenantLogo()
Get tenant logo URL.
import { useTenantLogo } from 'igloo-d2c-components'
function MyComponent() {
const logo = useTenantLogo() // Default logo
const darkLogo = useTenantLogo('dark') // Dark variant
return <img src={logo} alt="Logo" />
}Utility Functions
getTenantTheme()
Get theme configuration by tenant ID.
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.
import { isValidTenantId } from 'igloo-d2c-components'
if (isValidTenantId(userInput)) {
const theme = getTenantTheme(userInput) // Type-safe!
}getAvailableTenants()
Get list of all available tenant IDs.
import { getAvailableTenants } from 'igloo-d2c-components'
const tenants = getAvailableTenants()
// ['igloo', 'cimb', 'ammetlife']getThemeColor()
Extract color from theme using dot notation.
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
Asset Organization
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 exportsAsset Flow Architecture
┌─────────────────────────────────────────────────────┐
│ 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 │
│ │
└─────────────────────────────────────────────────────┘Using Assets
// Get Tenant Logo
import { useTenantLogo } from 'igloo-d2c-components'
function MyComponent() {
const logo = useTenantLogo() // Default logo
const darkLogo = useTenantLogo('dark') // Dark variant
return <img src={logo} alt="Logo" />
}
// Get Icon Paths
import { ICON_PATHS, getIconPath } from 'igloo-d2c-components'
<img src={ICON_PATHS.facebook} alt="Facebook" />
<img src={getIconPath('instagram')} alt="Instagram" />Asset Distribution Strategy
| 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 |
Logo Rendering in Applications
Important: Assets need to be copied to the public/ folder for UMI to serve them:
# 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
Multi-Tenant Architecture
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 configurationTenant Configuration Example
// 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: '[email protected]',
supportPhone: '+62 21 5022 0888',
},
integrations: {
gtmCode: 'GTM-5RHHNVQ',
gaCode: 'G-QQV6F41YPJ',
},
}
export default iglooConfigAmmetLife Custom Homepage
AmMetLife uses a custom homepage that displays the life insurance landing page:
// config/tenants/ammetlife.ts
customRoutes: [
{
path: '/',
exact: true,
component: '@/pages/life-landing-page/index.tsx',
},
]Checkout Flow Integration
Complete 3-Step Checkout
The library provides components for a complete checkout flow:
- Personal Information - 11 fields + 2 consent checkboxes
- Contact Details - 6 address fields + mailing checkbox
- Health Information - 2 measurements + health questions (gender-specific)
Session Storage Requirements
// 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
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
Recommendations Feature
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 shownImplementation
import { TravelRecommendations } from '@/components/recommendations';
import { RecommendationsButton } from '@/components/recommendations/recommendations-button';
export default function MyPage() {
const [showRecommendations, setShowRecommendations] = React.useState(false);
return (
<div>
<RecommendationsButton
onClick={() => setShowRecommendations(true)}
/>
<TravelRecommendations
open={showRecommendations}
onClose={() => setShowRecommendations(false)}
onSubmit={(data) => {
console.log('User selected:', data);
}}
/>
</div>
);
}Data Structure
interface TravelRecommendationsData {
travelType: 'domestic' | 'international';
selectedPlan: string;
monthlyIncome: string;
monthlyExpenses: string;
currentCoverage: string;
employerCoverage: string;
}Header Integration
NewHeader Implementation
The NewHeader component provides a simplified header with mobile drawer navigation.
Features:
- Product Navigation - Dynamically loaded from navLinks
- User Authentication - Login/Logout flow
- Additional Links - Partnership, About Us, Blog
- Language Selector - EN/ID with cookie persistence
- Analytics - Google Analytics integration
Performance Improvement:
| Metric | Before | After | | -------------- | --------- | --------- | | Component size | 860 lines | 200 lines | | Bundle size | ~45KB | ~15KB | | Props count | 30+ | 9 |
Routing Guide
Route Pattern
/:locale/life-checkoutNavigation with Locale
// ✅ CORRECT - With locale
const { currentLocale } = local.getItem('globalState');
window.location.href = `/${currentLocale}/life-checkout`;
// ❌ WRONG - Without locale
window.location.href = '/life-checkout';🔧 Development
Prerequisites
- Node.js:
>=16.20.0 <=18.x - Yarn:
^1.22.0(recommended) or npm
Check your version:
node --version # Should be 16.20.0 - 18.x
yarn --version # Should be 1.22.xSetup
# Clone the repository
git clone https://gitlab.iglooinsure.com/axinan/fe/d2c-component-library.git
cd d2c-component-library
# Install dependencies
yarn installBuild Commands
# Production build
yarn build
# Development mode (watch)
yarn dev
# Clean build artifacts
yarn clean
# Clean and rebuild
yarn clean && yarn buildCode Quality
# Lint code
yarn lint
# Type check
yarn type-checkBuild Output
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 assetsProject Structure
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.
Running Storybook
# Start Storybook dev server
yarn storybook
# Opens at http://localhost:6006
# Build static Storybook
yarn build-storybook
# Output in storybook-static/Available Stories
NewHeader Stories (12 scenarios):
- Default - Basic header with Igloo branding
- Authenticated User - Header with logged-in user menu
- Mobile View - Responsive mobile layout
- CIMB Tenant - CIMB branding
- AmMetLife Tenant - AmMetLife branding
- Minimal Header - Bare-bones implementation
- Custom Drawer Content - Advanced customization
- Many Menu Items - Stress test with scrolling
- Without Logo - Edge case testing
- With Internationalization - i18n integration
- All Tenants Comparison - Side-by-side view
- Responsive Demo - Interactive responsive testing
Other Component Stories:
- RecommendationsDrawer - Complete questionnaire flow
- ProductSelectionDrawer - Product grid selection
- CheckoutHeader - Progress and product display
- All checkout components
Keyboard Shortcuts
| Key | Action |
| --- | ------------------- |
| F | Toggle fullscreen |
| S | Toggle sidebar |
| A | Toggle addons panel |
| T | Toggle toolbar |
| D | Toggle dark mode |
| / | Search stories |
🔧 Build Pipeline
Problems Solved
- Module parse error: Webpack couldn't parse igloo-d2c-components ESM format
- Source map error: Terser plugin couldn't process source maps
Solutions Applied
1. Library: Disabled Source Maps
// rollup.config.cjs
output: [
{
file: packageJson.main,
format: 'cjs',
sourcemap: false, // ✅ Disabled
exports: 'named',
banner: '"use client"',
},
]2. Consumer App: Webpack Configuration
// .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();
}CI/CD Pipeline Options
Option A: Publish to npm (Recommended)
cd d2c-component-library
npm publish --access publicOption B: Use Git URL
{
"igloo-d2c-components": "git+https://gitlab.com/axinan/fe/d2c-component-library.git#main"
}Option C: Multi-Repo CI/CD
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
Prerequisites
Ensure clean working directory:
git status # Should be cleanUpdate version in package.json:
{ "version": "1.0.7" }Build the library:
yarn build
Publishing to NPM
Set up NPM token:
export NPM_AUTH_TOKEN="npm_your_actual_token"Publish:
# Automated script with safety checks
./publish-to-npm.sh
# Or manual
yarn build
npm publish --access publicVersion Management
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:
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
Build Configuration
Rollup Configuration (rollup.config.cjs):
{
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"',
},
],
}TypeScript Configuration
Target: ES2015 for maximum compatibility Module: ESNext for tree-shaking Strict: Enabled for type safety
Peer Dependencies
{
"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
Webpack Module Parse Error
Error:
Module parse failed: Unexpected tokenSolution:
cd d2c-component-library
yarn clean && yarn build
cd ../b2c-web-demo
rm -rf node_modules/igloo-d2c-components
yarn installTheme Not Found Error
Error:
Theme not found for tenant: xxxSolution: Use valid tenant IDs: 'igloo', 'cimb', or 'ammetlife'.
TypeScript Errors with Imports
Error:
Module '"igloo-d2c-components"' has no exported member 'iglooTheme'Solution:
- Rebuild the library:
yarn build - Reinstall in consuming app:
yarn install - Restart TypeScript server in your IDE
Build Failures
Error: Build fails with memory issues
Solution:
export NODE_OPTIONS="--max-old-space-size=4096"
yarn buildAsset Issues
Logos not displaying:
- Run
yarn copy-assetsin consuming app - Verify assets in
public/assets/tenants/ - Check theme path:
/assets/tenants/igloo/logo.svg
Routing Issues
Issue: 404 on /life-checkout
Use locale prefix: /en/life-checkout, not /life-checkout
📝 Changelog
[Unreleased]
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
[1.0.1] - 2025-11-11
Changed
- Package name configured for public NPM registry
- Updated all documentation
[1.0.0] - 2025-10-31
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
Getting Started
Fork the repository
Create a feature branch:
git checkout -b feature/my-new-featureMake your changes
Test thoroughly:
yarn lint yarn type-check yarn buildTest in consuming app:
cd ../b2c-web-demo yarn install yarn start-igloo-devCommit your changes:
git commit -m "feat: add new feature"Push and create a Pull Request
Commit Message Format
Follow Conventional Commits:
<type>(<scope>): <description>Types:
feat: New featurefix: Bug fixdocs: Documentation changesstyle: Code style changes (formatting)refactor: Code refactoringtest: Test additions/changeschore: Maintenance tasks
Adding a New Tenant
Add theme to
src/themes/index.ts:export const newTenantTheme: TenantThemeConfig = { palette: { /* ... */ }, typography: { /* ... */ }, logo: '/assets/tenants/newtenant/logo.svg', favicon: 'https://...', } export const tenantThemes: Record<TenantId, TenantThemeConfig> = { igloo: iglooTheme, cimb: cimbTheme, ammetlife: ammetlifeTheme, newtenant: newTenantTheme, // Add here }Update TenantId type in
src/types/tenant.ts:export type TenantId = 'igloo' | 'cimb' | 'ammetlife' | 'newtenant'Add logo asset to
src/assets/tenants/newtenant/Build and test:
yarn build
Adding New Components
- Create component folder in
src/components/ - Include:
ComponentName.tsx,styled.tsx,index.ts - Add Storybook stories
- Export from
src/index.ts - Update documentation
📄 License
MIT
👥 Team
Frontend Engineering Team - Axinan/Igloo
🔗 Links
- Repository: https://gitlab.iglooinsure.com/axinan/fe/d2c-component-library
- NPM Package: https://www.npmjs.com/package/igloo-d2c-components
- Consuming App: https://gitlab.iglooinsure.com/axinan/fe/b2c-web-demo
📝 Quick Reference
Installation
# Local development
yarn add igloo-d2c-components@file:../d2c-component-library
# Production
yarn add igloo-d2c-components@latestImport Themes
import { iglooTheme, cimbTheme, ammetlifeTheme, getTenantTheme } from 'igloo-d2c-components'Import Components
import { Button, Card, Banner, TenantThemeProvider, NewHeader, CheckoutHeader } from 'igloo-d2c-components'Import Hooks
import { useTenantTheme, useTenantId, useIsTenant, useTenantLogo } from 'igloo-d2c-components'Build & Publish
# Build
yarn build
# Publish to NPM
./publish-to-npm.shTest All Tenants (in b2c-web-demo)
yarn start-igloo # Port 8000
yarn start-ammetlife # Port 8001
yarn start-cimb # Port 8002Version: 1.0.11+ Last Updated: December 2025 Node.js: >=16.20.0 <=18.x Target: ES2015 Output: Unminified
