npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

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

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 ID
  • useTenantId() - Get current tenant ID
  • useIsTenant() - Check tenant match
  • useTenantLogo() - Get tenant logo
  • useTenantAsset() - Get tenant-specific asset

Utilities

  • getTenantTheme() - Get theme by tenant ID
  • isValidTenantId() - Validate tenant ID
  • getThemeColor() - Extract colors from theme
  • createThemeCSSVariables() - 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 install

Workflow:

# Make changes to library
cd d2c-component-library
# ... edit files ...
yarn build

# Update consuming app
cd ../b2c-web-demo
yarn install  # Copies updated build

Option 2: NPM Registry (Production)

For production deployments and CI/CD.

Install:

yarn add igloo-d2c-components
# or
npm install igloo-d2c-components

Configure 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 color
  • variant?: '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 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.

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 exports

Asset 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 configuration

Tenant 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 iglooConfig

AmmetLife 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:

  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

// 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 shown

Implementation

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:

  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 |

Routing Guide

Route Pattern

/:locale/life-checkout

Navigation 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.x

Setup

# Clone the repository
git clone https://gitlab.iglooinsure.com/axinan/fe/d2c-component-library.git
cd d2c-component-library

# Install dependencies
yarn install

Build Commands

# Production build
yarn build

# Development mode (watch)
yarn dev

# Clean build artifacts
yarn clean

# Clean and rebuild
yarn clean && yarn build

Code Quality

# Lint code
yarn lint

# Type check
yarn type-check

Build 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 assets

Project 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):

  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

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

  1. Module parse error: Webpack couldn't parse igloo-d2c-components ESM format
  2. 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 public

Option 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

  1. Ensure clean working directory:

    git status  # Should be clean
  2. Update version in package.json:

    {
      "version": "1.0.7"
    }
  3. 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 public

Version 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 token

Solution:

cd d2c-component-library
yarn clean && yarn build
cd ../b2c-web-demo
rm -rf node_modules/igloo-d2c-components
yarn install

Theme Not Found Error

Error:

Theme not found for tenant: xxx

Solution: Use valid tenant IDs: 'igloo', 'cimb', or 'ammetlife'.

TypeScript Errors with Imports

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

Build Failures

Error: Build fails with memory issues

Solution:

export NODE_OPTIONS="--max-old-space-size=4096"
yarn build

Asset Issues

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

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

  1. Fork the repository

  2. Create a feature branch:

    git checkout -b feature/my-new-feature
  3. Make your changes

  4. Test thoroughly:

    yarn lint
    yarn type-check
    yarn build
  5. Test in consuming app:

    cd ../b2c-web-demo
    yarn install
    yarn start-igloo-dev
  6. Commit your changes:

    git commit -m "feat: add new feature"
  7. Push and create a Pull Request

Commit Message Format

Follow Conventional Commits:

<type>(<scope>): <description>

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

Adding a New Tenant

  1. 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
    }
  2. Update TenantId type in src/types/tenant.ts:

    export type TenantId = 'igloo' | 'cimb' | 'ammetlife' | 'newtenant'
  3. Add logo asset to src/assets/tenants/newtenant/

  4. Build and test:

    yarn build

Adding New Components

  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


📝 Quick Reference

Installation

# Local development
yarn add igloo-d2c-components@file:../d2c-component-library

# Production
yarn add igloo-d2c-components@latest

Import 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.sh

Test All Tenants (in b2c-web-demo)

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