- Updated address retrieval in user service to replace billing info with a dedicated address method. - Adjusted API endpoints to use `PATCH /api/me/address` for address updates instead of billing updates. - Enhanced documentation to reflect changes in address management processes and API usage. - Removed deprecated types and services related to billing address handling, streamlining the codebase.
10 KiB
10 KiB
Design Document
Overview
This design document outlines a comprehensive refactoring strategy to transform the current customer portal codebase into a modern, maintainable, and scalable architecture. The refactoring will eliminate redundancies, establish consistent patterns, and improve developer experience while maintaining existing functionality.
Architecture
High-Level Structure
The refactored architecture will follow a feature-driven, layered approach:
apps/portal/src/
├── app/ # Next.js App Router pages
├── components/ # Shared UI components (Design System)
│ ├── ui/ # Base UI components (atoms)
│ ├── layout/ # Layout components (organisms)
│ └── common/ # Shared business components (molecules)
├── features/ # Feature-specific modules
│ ├── auth/ # Authentication feature
│ ├── dashboard/ # Dashboard feature
│ ├── billing/ # Billing feature
│ ├── subscriptions/ # Subscriptions feature
│ ├── catalog/ # Product catalog feature
│ └── support/ # Support feature
├── lib/ # Core utilities and services
│ ├── api/ # API client and services
│ ├── auth/ # Authentication logic
│ ├── hooks/ # Shared React hooks
│ ├── stores/ # State management
│ ├── types/ # Shared TypeScript types
│ └── utils/ # Utility functions
├── providers/ # React context providers
└── styles/ # Global styles and design tokens
Design Principles
- Feature-First Organization: Group related functionality together
- Atomic Design: Build UI components in a hierarchical manner
- Separation of Concerns: Separate business logic from presentation
- Single Responsibility: Each module has one clear purpose
- Dependency Inversion: Depend on abstractions, not concretions
Components and Interfaces
Design System Architecture
Atomic Design Structure
// Base UI Components (Atoms)
components/ui/
├── Button/
│ ├── Button.tsx
│ ├── Button.stories.tsx
│ ├── Button.test.tsx
│ └── index.ts
├── Input/
├── Badge/
└── ...
// Composite Components (Molecules)
components/common/
├── DataTable/
├── SearchBar/
├── StatusIndicator/
└── ...
// Layout Components (Organisms)
components/layout/
├── DashboardLayout/
├── AuthLayout/
├── PageLayout/
└── ...
Component Interface Standards
// Base component props interface
interface BaseComponentProps {
className?: string;
children?: React.ReactNode;
testId?: string;
}
// Variant-based component pattern
interface ButtonProps extends BaseComponentProps {
variant?: 'primary' | 'secondary' | 'outline' | 'ghost';
size?: 'sm' | 'md' | 'lg';
disabled?: boolean;
loading?: boolean;
onClick?: () => void;
}
Feature Module Structure
Each feature will follow a consistent internal structure:
features/[feature-name]/
├── components/ # Feature-specific components
│ ├── [Component]/
│ │ ├── Component.tsx
│ │ ├── Component.test.tsx
│ │ └── index.ts
│ └── index.ts
├── hooks/ # Feature-specific hooks
│ ├── use[Feature].ts
│ └── index.ts
├── services/ # Feature business logic
│ ├── [feature].service.ts
│ └── index.ts
├── types/ # Feature-specific types
│ ├── [feature].types.ts
│ └── index.ts
├── utils/ # Feature utilities
└── index.ts # Feature public API
API Service Layer
// Centralized API service structure
lib/api/
├── client.ts # Base API client
├── types.ts # API response types
├── services/
│ ├── auth.service.ts
│ ├── billing.service.ts
│ ├── subscriptions.service.ts
│ └── index.ts
└── index.ts
// Service interface pattern
interface ApiService<T> {
getAll(params?: QueryParams): Promise<T[]>;
getById(id: string): Promise<T>;
create(data: CreateT): Promise<T>;
update(id: string, data: UpdateT): Promise<T>;
delete(id: string): Promise<void>;
}
Data Models
Centralized Type Definitions
// lib/types/index.ts - Centralized type exports
export * from './api.types';
export * from './auth.types';
export * from './billing.types';
export * from './subscription.types';
export * from './common.types';
// Common base types
interface BaseEntity {
id: string;
createdAt: string;
updatedAt: string;
}
interface PaginatedResponse<T> {
data: T[];
pagination: {
page: number;
limit: number;
total: number;
totalPages: number;
};
}
// Feature-specific type extensions
interface Subscription extends BaseEntity {
userId: string;
planId: string;
status: SubscriptionStatus;
// ... other properties
}
State Management Pattern
// Zustand store pattern for each feature
interface FeatureStore {
// State
data: FeatureData[];
loading: boolean;
error: string | null;
// Actions
fetchData: () => Promise<void>;
updateItem: (id: string, data: Partial<FeatureData>) => Promise<void>;
reset: () => void;
}
// React Query integration for server state
const useFeatureQuery = (params?: QueryParams) => {
return useQuery({
queryKey: ['feature', params],
queryFn: () => featureService.getAll(params),
staleTime: 5 * 60 * 1000, // 5 minutes
});
};
Error Handling
Centralized Error Management
// lib/errors/index.ts
export class AppError extends Error {
constructor(
message: string,
public code: string,
public statusCode?: number
) {
super(message);
this.name = 'AppError';
}
}
// Error boundary component
export function ErrorBoundary({ children }: { children: React.ReactNode }) {
// Implementation with error logging and user-friendly fallbacks
}
// API error handling
export function handleApiError(error: unknown): AppError {
if (error instanceof ApiError) {
return new AppError(error.message, error.code, error.status);
}
return new AppError('An unexpected error occurred', 'UNKNOWN_ERROR');
}
Error Display Strategy
// Consistent error display components
interface ErrorStateProps {
error: AppError;
onRetry?: () => void;
variant?: 'page' | 'inline' | 'toast';
}
export function ErrorState({ error, onRetry, variant = 'page' }: ErrorStateProps) {
// Render appropriate error UI based on variant
}
Testing Strategy
Testing Architecture
// Test utilities and setup
__tests__/
├── setup.ts # Test environment setup
├── utils/
│ ├── render.tsx # Custom render with providers
│ ├── mocks/ # API and service mocks
│ └── factories/ # Test data factories
└── fixtures/ # Test data fixtures
// Component testing pattern
describe('Button Component', () => {
it('renders with correct variant styles', () => {
render(<Button variant="primary">Click me</Button>);
expect(screen.getByRole('button')).toHaveClass('bg-blue-600');
});
it('handles click events', async () => {
const handleClick = jest.fn();
render(<Button onClick={handleClick}>Click me</Button>);
await user.click(screen.getByRole('button'));
expect(handleClick).toHaveBeenCalledTimes(1);
});
});
Integration Testing
// Feature integration tests
describe('Dashboard Feature', () => {
beforeEach(() => {
// Setup mocks and test data
mockApiService.dashboard.getSummary.mockResolvedValue(mockDashboardData);
});
it('displays user dashboard with correct data', async () => {
render(<DashboardPage />);
await waitFor(() => {
expect(screen.getByText('Active Subscriptions')).toBeInTheDocument();
expect(screen.getByText('3')).toBeInTheDocument();
});
});
});
Performance Optimization
Code Splitting Strategy
// Feature-based code splitting
const DashboardPage = lazy(() => import('@/features/dashboard/pages/DashboardPage'));
const BillingPage = lazy(() => import('@/features/billing/pages/BillingPage'));
// Component-level splitting for heavy components
const DataVisualization = lazy(() => import('@/components/common/DataVisualization'));
Bundle Optimization
// Tree-shakeable exports
// Instead of: export * from './components';
// Use specific exports:
export { Button } from './Button';
export { Input } from './Input';
export type { ButtonProps, InputProps } from './types';
// Dynamic imports for heavy dependencies
const heavyLibrary = await import('heavy-library');
Caching Strategy
// React Query configuration
const queryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: 5 * 60 * 1000, // 5 minutes
gcTime: 10 * 60 * 1000, // 10 minutes
retry: (failureCount, error) => {
if (error.status === 404) return false;
return failureCount < 3;
},
},
},
});
Migration Strategy
Phase 1: Foundation (Weeks 1-2)
- Set up new folder structure
- Create base UI components (Button, Input, etc.)
- Establish design tokens and styling system
- Set up centralized API client
Phase 2: Core Features (Weeks 3-4)
- Refactor authentication module
- Migrate dashboard feature
- Consolidate layout components
- Implement error handling system
Phase 3: Business Features (Weeks 5-6)
- Migrate billing feature
- Refactor subscriptions feature
- Consolidate catalog functionality
- Implement testing framework
Phase 4: Optimization (Weeks 7-8)
- Performance optimization
- Bundle analysis and splitting
- Documentation and cleanup
- Final testing and validation
Risk Mitigation
Backward Compatibility
- Maintain existing API contracts during migration
- Use feature flags for gradual rollout
- Keep old components until migration is complete
Testing Coverage
- Maintain existing functionality through comprehensive testing
- Implement visual regression testing
- Set up automated testing pipeline
Performance Monitoring
- Implement bundle size monitoring
- Set up performance metrics tracking
- Monitor Core Web Vitals during migration