DeveloperBilling
Care Packages Implementation Guide
This document provides a comprehensive reference for implementing the Care Packages feature in the Growth module of the Kulcare platform.
Overview
The Care Packages feature allows clinics to create and manage bundled service packages that patients can subscribe to. These packages include multiple consultations and services at a discounted rate with a specific duration.
Architecture Integration
Module Federation Context
- Host App: Shell application (port 3000)
- Remote App: Growth module (port 3003)
- Route:
/growth/packages - Base Path Handling: Uses
getBasePath()for federated mode
Shared Dependencies
@kulcare/ui-kit: Reusable UI components@kulcare/api-client: Centralized API client@kulcare/auth: Authentication and authorization@tanstack/react-query: Server state management
API Specification
1. Get Clinics for User
GET /users/{user_id}/clinics
Response:
{
success: boolean
message: string
data: {
clinics: Clinic[]
}
}2. Get Care Packages
GET /clinics/{clinic_id}/care_packages?page=1&perpage=20&c_grp=true
Response:
{
success: boolean
message: string
data: {
care_packages: CarePackage[]
total_pages: number
total_records: number
}
}Type Definitions
Core Types
// types/carePackages.ts
export interface Clinic {
id: number
name: string
locality: string
unique_id: string
speciality: {
id: number
name: string
}
role: 'owner' | 'consultant' | 'staff'
profile_name: string
settings_viewed: boolean
telemedicine_setup: boolean
use_kulcare_appts: boolean
book_appt_link: string
}
export interface CarePackageService {
id: number
clinic_service_id: number
doctor_id: number
benefits_count: number
name: string
price: number
}
export interface CarePackage {
id: number
clinic_id: number
name: string
price: number
duration_frequency: number
duration_unit: 'days' | 'weeks' | 'months'
doctor_id: number
expire_by: string
subscribers_count: number
short_link: string
clinic_name: string
doctor_details: {
full_name: string
}
care_package_services: CarePackageService[]
}
export interface GetPackagesParams {
page?: number
perpage?: number
search?: string
clinic_id?: number
c_grp?: boolean
}
export interface PackagesResponse {
success: boolean
message: string
data: {
care_packages: CarePackage[]
total_pages: number
total_records: number
}
}File Structure
apps/growth/src/
├── services/
│ └── carePackagesApi.ts # API service layer
├── types/
│ └── carePackages.ts # TypeScript definitions
├── pages/
│ └── packages/
│ ├── index.ts # Export barrel
│ ├── PackagesPage.tsx # Main container
│ ├── components/
│ │ ├── PackagesTable.tsx # Table component
│ │ ├── PackageActions.tsx # Action buttons
│ │ ├── AddPackageButton.tsx # Create new
│ │ └── PackageFilters.tsx # Search & filters
│ └── hooks/
│ ├── usePackages.ts # Data fetching
│ └── useClinics.ts # Clinic data
└── layouts/
└── GrowthLayout.tsx # Update navigationImplementation Details
API Service Layer
// services/carePackagesApi.ts
import { apiClient } from "@kulcare/api-client"
import type {
GetPackagesParams,
PackagesResponse,
CarePackage,
Clinic
} from "../types/carePackages"
export const carePackagesApi = {
async getClinics(userId: string): Promise<Clinic[]> {
if (import.meta.env.VITE_USE_REAL_API === 'true') {
const response = await apiClient.get<{
success: boolean
data: { clinics: Clinic[] }
}>(`/users/${userId}/clinics`)
return response.data.data.clinics
}
// Mock data for development
return mockClinics
},
async getPackages(
clinicId: string,
params?: GetPackagesParams
): Promise<PackagesResponse['data']> {
if (import.meta.env.VITE_USE_REAL_API === 'true') {
const response = await apiClient.get<PackagesResponse>(
`/clinics/${clinicId}/care_packages`,
{
params: {
page: params?.page || 1,
perpage: params?.perpage || 20,
c_grp: true,
...params
}
}
)
return response.data.data
}
// Mock implementation
return getMockPackages(params)
},
async createPackage(clinicId: string, data: CreatePackageData) {
// Implementation
},
async updatePackage(packageId: string, data: UpdatePackageData) {
// Implementation
},
async deletePackage(packageId: string) {
// Implementation
}
}React Query Hooks
// hooks/usePackages.ts
import { useQuery } from "@tanstack/react-query"
import { carePackagesApi } from "../services/carePackagesApi"
import { useAuth } from "@kulcare/auth"
export function usePackages(params?: GetPackagesParams) {
const { selectedClinic } = useAuth()
return useQuery({
queryKey: ['packages', selectedClinic?.id, params],
queryFn: () => carePackagesApi.getPackages(
selectedClinic!.id.toString(),
params
),
enabled: !!selectedClinic,
staleTime: 5 * 60 * 1000, // 5 minutes
})
}
export function useClinics() {
const { user } = useAuth()
return useQuery({
queryKey: ['clinics', user?.id],
queryFn: () => carePackagesApi.getClinics(user!.id.toString()),
enabled: !!user,
staleTime: 30 * 60 * 1000, // 30 minutes
})
}Main Component Structure
// PackagesPage.tsx
import { useState } from "react"
import { usePackages } from "./hooks/usePackages"
import { PackagesTable } from "./components/PackagesTable"
import { PackageFilters } from "./components/PackageFilters"
import { AddPackageButton } from "./components/AddPackageButton"
import { GrowthPageWrapper } from "@/components/GrowthPageWrapper"
export function PackagesPage() {
const [searchTerm, setSearchTerm] = useState("")
const [selectedClinic, setSelectedClinic] = useState<string>()
const [currentPage, setCurrentPage] = useState(1)
const { data, isLoading, error } = usePackages({
page: currentPage,
perpage: 20,
search: searchTerm,
clinic_id: selectedClinic ? parseInt(selectedClinic) : undefined
})
return (
<GrowthPageWrapper>
<div className="space-y-4">
<div className="flex justify-between items-center">
<h1 className="text-2xl font-semibold">Packages</h1>
<AddPackageButton />
</div>
<PackageFilters
searchTerm={searchTerm}
onSearchChange={setSearchTerm}
selectedClinic={selectedClinic}
onClinicChange={setSelectedClinic}
/>
<PackagesTable
packages={data?.care_packages || []}
isLoading={isLoading}
error={error}
pagination={{
currentPage,
totalPages: data?.total_pages || 1,
totalRecords: data?.total_records || 0,
onPageChange: setCurrentPage
}}
/>
</div>
</GrowthPageWrapper>
)
}Table Component
// components/PackagesTable.tsx
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow
} from "@kulcare/ui-kit"
import { formatCurrency, formatDate } from "@/lib/utils"
import { PackageActions } from "./PackageActions"
import type { CarePackage } from "../types/carePackages"
interface PackagesTableProps {
packages: CarePackage[]
isLoading: boolean
error: Error | null
pagination: {
currentPage: number
totalPages: number
totalRecords: number
onPageChange: (page: number) => void
}
}
export function PackagesTable({
packages,
isLoading,
error,
pagination
}: PackagesTableProps) {
if (isLoading) {
return <div>Loading...</div>
}
if (error) {
return <div>Error: {error.message}</div>
}
if (packages.length === 0) {
return <EmptyState />
}
return (
<>
<Table>
<TableHeader>
<TableRow>
<TableHead>Package name</TableHead>
<TableHead>Price</TableHead>
<TableHead>Duration</TableHead>
<TableHead>Benefits</TableHead>
<TableHead>Clinic name</TableHead>
<TableHead>Subs</TableHead>
<TableHead>Expires by</TableHead>
<TableHead>Action</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{packages.map((pkg) => (
<TableRow key={pkg.id}>
<TableCell>{pkg.name}</TableCell>
<TableCell>{formatCurrency(pkg.price)}</TableCell>
<TableCell>
{pkg.duration_frequency} {pkg.duration_unit}
</TableCell>
<TableCell>
{pkg.care_package_services.map(s => (
<div key={s.id}>
{s.benefits_count} {s.name}
</div>
))}
</TableCell>
<TableCell>{pkg.clinic_name}</TableCell>
<TableCell>{pkg.subscribers_count}</TableCell>
<TableCell>{formatDate(pkg.expire_by)}</TableCell>
<TableCell>
<PackageActions package={pkg} />
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
<Pagination {...pagination} />
</>
)
}Routing Configuration
Update Growth App Routes
// App.tsx
import { PackagesPage } from "./pages/packages"
function GrowthRoutes() {
return (
<Routes>
{/* ... existing routes ... */}
<Route path="packages" element={<PackagesPage />} />
</Routes>
)
}Update Navigation Menu
// layouts/GrowthLayout.tsx
const menuItems = [
// ... existing items ...
{
key: '/packages',
icon: <Package />,
label: 'Packages',
path: '/packages'
}
]UI Components from UI Kit
Reusable Components
- Table: Already supports density modes
- Input: For search functionality
- Select: For clinic filter
- Button: For actions and create
- Badge: For counts and status
- EmptyState: When no packages
- LoadingState: During data fetch
- Pagination: For table navigation
- Dialog/Modal: For create/edit forms
- Tooltip: For additional info
State Management
Server State (React Query)
- Package list with pagination
- Clinic list for filters
- Optimistic updates for actions
- Cache invalidation on mutations
Local State
- Search term (with debounce)
- Selected clinic filter
- Current page
- Table density preference
- Modal open states
Mock Data Structure
const mockPackages: CarePackage[] = [
{
id: 1,
clinic_id: 204745,
name: "Premium Health Package",
price: 5000,
duration_frequency: 3,
duration_unit: "months",
doctor_id: 87009,
expire_by: "2025-12-31",
subscribers_count: 15,
short_link: "http://ps.kulcare.com?s=ABC123",
clinic_name: "Care for you",
doctor_details: {
full_name: "Dr. Madhav Singh"
},
care_package_services: [
{
id: 1,
clinic_service_id: 5500,
doctor_id: 87009,
benefits_count: 4,
name: "General Consultation",
price: 1000
},
{
id: 2,
clinic_service_id: 5501,
doctor_id: 87009,
benefits_count: 2,
name: "Specialist Consultation",
price: 1500
}
]
}
// ... more mock data
]Error Handling
API Errors
- Network failures
- Authentication errors
- Validation errors
- Server errors
UI Error States
- Empty states with CTAs
- Error boundaries
- Toast notifications
- Inline error messages
Performance Considerations
Optimization Strategies
- Debounced search (300ms)
- Pagination (20 items default)
- React Query caching
- Lazy loading for modals
- Virtual scrolling for large lists
Bundle Size
- Code splitting by route
- Dynamic imports for forms
- Tree shaking unused UI components
Testing Checklist
Functionality
- List packages with pagination
- Search packages by name
- Filter by clinic
- Sort columns
- Create new package
- Edit existing package
- Delete package
- View package details
Integration
- Works in standalone mode
- Works in federated mode
- Correct base path handling
- API integration
- Mock data toggle
User Experience
- Loading states
- Error handling
- Empty states
- Responsive design
- Keyboard navigation
- Screen reader support
Security Considerations
Authorization
- Clinic-based access control
- Role-based permissions
- Subscription status checks
Data Security
- No sensitive data in URLs
- Proper error messages
- Input validation
- XSS prevention
Future Enhancements
Phase 2 Features
- Bulk operations
- Export to CSV/PDF
- Advanced filters
- Package templates
- Analytics dashboard
- Email notifications
- Package duplication
- Version history
Technical Improvements
- Offline support
- Real-time updates
- Advanced caching
- Performance metrics
- A/B testing
Development Workflow
Getting Started
# Start development servers
pnpm dev:all
# Navigate to Growth module
http://localhost:3003/packages (standalone)
http://localhost:3000/growth/packages (federated)Environment Variables
VITE_USE_REAL_API=false # Use mock data
VITE_API_URL=https://api-stg.kulcare.comGit Workflow
# Create feature branch
git checkout -b feature/care-packages
# Commit changes
git add .
git commit -m "feat(growth): implement care packages listing"
# Push and create PR
git push origin feature/care-packagesReferences
- Module Federation Architecture
- API Client Documentation
- UI Kit Components
- Growth App Documentation
- Original API Specification
Last updated: 2025-07-30