di usage in website
This commit is contained in:
512
plans/DI_PLAN.md
Normal file
512
plans/DI_PLAN.md
Normal file
@@ -0,0 +1,512 @@
|
||||
# Dependency Injection Plan for GridPilot Website
|
||||
|
||||
## Overview
|
||||
|
||||
Implement proper dependency injection in your website using InversifyJS, following the same patterns as your NestJS API. This replaces the current manual `ServiceFactory` approach with a professional DI container system.
|
||||
|
||||
## Why InversifyJS?
|
||||
|
||||
- **NestJS-like**: Same decorators and patterns you already know
|
||||
- **TypeScript-first**: Excellent type safety
|
||||
- **React-friendly**: Works seamlessly with React Context
|
||||
- **Production-ready**: Battle-tested, well-maintained
|
||||
|
||||
## Current Problem
|
||||
|
||||
```typescript
|
||||
// Current: Manual dependency management
|
||||
// apps/website/lib/services/ServiceProvider.tsx
|
||||
const services = useMemo(() => {
|
||||
const serviceFactory = new ServiceFactory(getWebsiteApiBaseUrl());
|
||||
return {
|
||||
leagueService: serviceFactory.createLeagueService(),
|
||||
driverService: serviceFactory.createDriverService(),
|
||||
// ... 25+ services manually created
|
||||
};
|
||||
}, []);
|
||||
```
|
||||
|
||||
**Issues:**
|
||||
- No formal DI container
|
||||
- Manual dependency wiring
|
||||
- Hard to test/mock
|
||||
- No lifecycle management
|
||||
- Inconsistent with API
|
||||
|
||||
## Solution Architecture
|
||||
|
||||
### 1. Install Dependencies
|
||||
|
||||
```bash
|
||||
npm install inversify reflect-metadata
|
||||
npm install --save-dev @types/inversify
|
||||
```
|
||||
|
||||
### 2. Configure TypeScript
|
||||
|
||||
```json
|
||||
// apps/website/tsconfig.json
|
||||
{
|
||||
"compilerOptions": {
|
||||
"experimentalDecorators": true,
|
||||
"emitDecoratorMetadata": true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Core Structure
|
||||
|
||||
```
|
||||
apps/website/lib/di/
|
||||
├── index.ts # Main exports
|
||||
├── container.ts # Container factory
|
||||
├── tokens.ts # Symbol tokens
|
||||
├── providers/
|
||||
│ └── ContainerProvider.tsx
|
||||
├── hooks/
|
||||
│ └── useInject.ts
|
||||
└── modules/
|
||||
├── core.module.ts
|
||||
├── api.module.ts
|
||||
├── league.module.ts
|
||||
└── ... (one per domain)
|
||||
```
|
||||
|
||||
## Implementation
|
||||
|
||||
### Step 1: Token Registry
|
||||
|
||||
**File**: `apps/website/lib/di/tokens.ts`
|
||||
|
||||
```typescript
|
||||
// Centralized token registry
|
||||
export const LOGGER_TOKEN = Symbol.for('Core.Logger');
|
||||
export const ERROR_REPORTER_TOKEN = Symbol.for('Core.ErrorReporter');
|
||||
|
||||
export const LEAGUE_SERVICE_TOKEN = Symbol.for('Service.League');
|
||||
export const DRIVER_SERVICE_TOKEN = Symbol.for('Service.Driver');
|
||||
export const TEAM_SERVICE_TOKEN = Symbol.for('Service.Team');
|
||||
export const RACE_SERVICE_TOKEN = Symbol.for('Service.Race');
|
||||
// ... all service tokens
|
||||
```
|
||||
|
||||
### Step 2: Container Factory
|
||||
|
||||
**File**: `apps/website/lib/di/container.ts`
|
||||
|
||||
```typescript
|
||||
import { Container } from 'inversify';
|
||||
import { CoreModule } from './modules/core.module';
|
||||
import { ApiModule } from './modules/api.module';
|
||||
import { LeagueModule } from './modules/league.module';
|
||||
// ... other modules
|
||||
|
||||
export function createContainer(): Container {
|
||||
const container = new Container({ defaultScope: 'Singleton' });
|
||||
|
||||
container.load(
|
||||
CoreModule,
|
||||
ApiModule,
|
||||
LeagueModule,
|
||||
// ... all modules
|
||||
);
|
||||
|
||||
return container;
|
||||
}
|
||||
```
|
||||
|
||||
### Step 3: React Integration
|
||||
|
||||
**File**: `apps/website/lib/di/providers/ContainerProvider.tsx`
|
||||
|
||||
```typescript
|
||||
'use client';
|
||||
|
||||
import { createContext, ReactNode, useContext, useMemo } from 'react';
|
||||
import { Container } from 'inversify';
|
||||
import { createContainer } from '../container';
|
||||
|
||||
const ContainerContext = createContext<Container | null>(null);
|
||||
|
||||
export function ContainerProvider({ children }: { children: ReactNode }) {
|
||||
const container = useMemo(() => createContainer(), []);
|
||||
|
||||
return (
|
||||
<ContainerContext.Provider value={container}>
|
||||
{children}
|
||||
</ContainerContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
export function useContainer(): Container {
|
||||
const container = useContext(ContainerContext);
|
||||
if (!container) throw new Error('Missing ContainerProvider');
|
||||
return container;
|
||||
}
|
||||
```
|
||||
|
||||
### Step 4: Injection Hook
|
||||
|
||||
**File**: `apps/website/lib/di/hooks/useInject.ts`
|
||||
|
||||
```typescript
|
||||
'use client';
|
||||
|
||||
import { useContext, useMemo } from 'react';
|
||||
import { ContainerContext } from '../providers/ContainerProvider';
|
||||
|
||||
export function useInject<T>(token: symbol): T {
|
||||
const container = useContext(ContainerContext);
|
||||
if (!container) throw new Error('Missing ContainerProvider');
|
||||
|
||||
return useMemo(() => container.get<T>(token), [container, token]);
|
||||
}
|
||||
```
|
||||
|
||||
### Step 5: Module Examples
|
||||
|
||||
**File**: `apps/website/lib/di/modules/league.module.ts`
|
||||
|
||||
```typescript
|
||||
import { ContainerModule } from 'inversify';
|
||||
import { LeagueService } from '../../services/leagues/LeagueService';
|
||||
import {
|
||||
LEAGUE_SERVICE_TOKEN,
|
||||
LEAGUE_API_CLIENT_TOKEN,
|
||||
DRIVER_API_CLIENT_TOKEN,
|
||||
SPONSOR_API_CLIENT_TOKEN,
|
||||
RACE_API_CLIENT_TOKEN
|
||||
} from '../tokens';
|
||||
|
||||
export const LeagueModule = new ContainerModule((bind) => {
|
||||
bind<LeagueService>(LEAGUE_SERVICE_TOKEN)
|
||||
.toDynamicValue((context) => {
|
||||
const leagueApiClient = context.container.get(LEAGUE_API_CLIENT_TOKEN);
|
||||
const driverApiClient = context.container.get(DRIVER_API_CLIENT_TOKEN);
|
||||
const sponsorApiClient = context.container.get(SPONSOR_API_CLIENT_TOKEN);
|
||||
const raceApiClient = context.container.get(RACE_API_CLIENT_TOKEN);
|
||||
|
||||
return new LeagueService(
|
||||
leagueApiClient,
|
||||
driverApiClient,
|
||||
sponsorApiClient,
|
||||
raceApiClient
|
||||
);
|
||||
})
|
||||
.inSingletonScope();
|
||||
});
|
||||
```
|
||||
|
||||
**File**: `apps/website/lib/di/modules/api.module.ts`
|
||||
|
||||
```typescript
|
||||
import { ContainerModule } from 'inversify';
|
||||
import { LeaguesApiClient } from '../../api/leagues/LeaguesApiClient';
|
||||
import { DriversApiClient } from '../../api/drivers/DriversApiClient';
|
||||
// ... other API clients
|
||||
import {
|
||||
LEAGUE_API_CLIENT_TOKEN,
|
||||
DRIVER_API_CLIENT_TOKEN,
|
||||
LOGGER_TOKEN,
|
||||
ERROR_REPORTER_TOKEN,
|
||||
CONFIG_TOKEN
|
||||
} from '../tokens';
|
||||
|
||||
export const ApiModule = new ContainerModule((bind) => {
|
||||
const createApiClient = (ClientClass: any, context: any) => {
|
||||
const baseUrl = context.container.get(CONFIG_TOKEN);
|
||||
const errorReporter = context.container.get(ERROR_REPORTER_TOKEN);
|
||||
const logger = context.container.get(LOGGER_TOKEN);
|
||||
return new ClientClass(baseUrl, errorReporter, logger);
|
||||
};
|
||||
|
||||
bind(LEAGUE_API_CLIENT_TOKEN)
|
||||
.toDynamicValue(ctx => createApiClient(LeaguesApiClient, ctx))
|
||||
.inSingletonScope();
|
||||
|
||||
bind(DRIVER_API_CLIENT_TOKEN)
|
||||
.toDynamicValue(ctx => createApiClient(DriversApiClient, ctx))
|
||||
.inSingletonScope();
|
||||
|
||||
// ... other API clients
|
||||
});
|
||||
```
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### In React-Query Hooks (Your Current Pattern)
|
||||
|
||||
**Before**:
|
||||
```typescript
|
||||
// apps/website/hooks/useLeagueService.ts
|
||||
export function useAllLeagues() {
|
||||
const { leagueService } = useServices(); // ❌ Manual service retrieval
|
||||
|
||||
return useQuery({
|
||||
queryKey: ['allLeagues'],
|
||||
queryFn: () => leagueService.getAllLeagues(),
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
**After**:
|
||||
```typescript
|
||||
// apps/website/hooks/useLeagueService.ts
|
||||
import { useInject } from '@/lib/di/hooks/useInject';
|
||||
import { LEAGUE_SERVICE_TOKEN } from '@/lib/di/tokens';
|
||||
|
||||
export function useAllLeagues() {
|
||||
const leagueService = useInject(LEAGUE_SERVICE_TOKEN); // ✅ Clean DI
|
||||
|
||||
return useQuery({
|
||||
queryKey: ['allLeagues'],
|
||||
queryFn: () => leagueService.getAllLeagues(),
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
### In React-Query Mutations
|
||||
|
||||
```typescript
|
||||
export function useCreateLeague() {
|
||||
const leagueService = useInject(LEAGUE_SERVICE_TOKEN);
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: (input: CreateLeagueInputDTO) => leagueService.createLeague(input),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ['allLeagues'] });
|
||||
},
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
### In Components
|
||||
|
||||
```typescript
|
||||
'use client';
|
||||
|
||||
import { useInject } from '@/lib/di/hooks/useInject';
|
||||
import { LEAGUE_SERVICE_TOKEN } from '@/lib/di/tokens';
|
||||
|
||||
export function CreateLeagueForm() {
|
||||
const leagueService = useInject(LEAGUE_SERVICE_TOKEN);
|
||||
|
||||
// Use leagueService directly
|
||||
}
|
||||
```
|
||||
|
||||
### In Server Components
|
||||
|
||||
```typescript
|
||||
import { createContainer } from '@/lib/di/container';
|
||||
import { LEAGUE_SERVICE_TOKEN } from '@/lib/di/tokens';
|
||||
|
||||
export default async function LeaguePage({ params }) {
|
||||
const container = createContainer();
|
||||
const leagueService = container.get(LEAGUE_SERVICE_TOKEN);
|
||||
|
||||
const league = await leagueService.getLeague(params.id);
|
||||
return <ClientComponent league={league} />;
|
||||
}
|
||||
```
|
||||
|
||||
### In Tests
|
||||
|
||||
```typescript
|
||||
import { createTestContainer } from '@/lib/di/container';
|
||||
import { ContainerProvider } from '@/lib/di/providers/ContainerProvider';
|
||||
|
||||
test('useAllLeagues works with DI', () => {
|
||||
const mockLeagueService = {
|
||||
getAllLeagues: jest.fn().mockResolvedValue([{ id: '1', name: 'Test League' }])
|
||||
};
|
||||
|
||||
const overrides = new Map([
|
||||
[LEAGUE_SERVICE_TOKEN, mockLeagueService]
|
||||
]);
|
||||
|
||||
const container = createTestContainer(overrides);
|
||||
|
||||
const { result } = renderHook(() => useAllLeagues(), {
|
||||
wrapper: ({ children }) => (
|
||||
<ContainerProvider container={container}>
|
||||
<QueryClientProvider client={new QueryClient()}>
|
||||
{children}
|
||||
</QueryClientProvider>
|
||||
</ContainerProvider>
|
||||
)
|
||||
});
|
||||
|
||||
// Test works exactly the same
|
||||
});
|
||||
```
|
||||
|
||||
### Complete React-Query Hook Migration Example
|
||||
|
||||
```typescript
|
||||
// apps/website/hooks/useLeagueService.ts
|
||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { useInject } from '@/lib/di/hooks/useInject';
|
||||
import { LEAGUE_SERVICE_TOKEN } from '@/lib/di/tokens';
|
||||
import { CreateLeagueInputDTO } from '@/lib/types/generated/CreateLeagueInputDTO';
|
||||
|
||||
export function useAllLeagues() {
|
||||
const leagueService = useInject(LEAGUE_SERVICE_TOKEN);
|
||||
|
||||
return useQuery({
|
||||
queryKey: ['allLeagues'],
|
||||
queryFn: () => leagueService.getAllLeagues(),
|
||||
});
|
||||
}
|
||||
|
||||
export function useLeagueStandings(leagueId: string, currentUserId: string) {
|
||||
const leagueService = useInject(LEAGUE_SERVICE_TOKEN);
|
||||
|
||||
return useQuery({
|
||||
queryKey: ['leagueStandings', leagueId, currentUserId],
|
||||
queryFn: () => leagueService.getLeagueStandings(leagueId, currentUserId),
|
||||
enabled: !!leagueId && !!currentUserId,
|
||||
});
|
||||
}
|
||||
|
||||
export function useCreateLeague() {
|
||||
const leagueService = useInject(LEAGUE_SERVICE_TOKEN);
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: (input: CreateLeagueInputDTO) => leagueService.createLeague(input),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ['allLeagues'] });
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useRemoveLeagueMember() {
|
||||
const leagueService = useInject(LEAGUE_SERVICE_TOKEN);
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: ({ leagueId, performerDriverId, targetDriverId }: {
|
||||
leagueId: string;
|
||||
performerDriverId: string;
|
||||
targetDriverId: string;
|
||||
}) => leagueService.removeMember(leagueId, performerDriverId, targetDriverId),
|
||||
onSuccess: (data, variables) => {
|
||||
queryClient.invalidateQueries({ queryKey: ['leagueMemberships', variables.leagueId] });
|
||||
queryClient.invalidateQueries({ queryKey: ['leagueStandings', variables.leagueId] });
|
||||
},
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
### Multiple Services in One Hook
|
||||
|
||||
```typescript
|
||||
import { useInjectMany } from '@/lib/di/hooks/useInjectMany';
|
||||
import { LEAGUE_SERVICE_TOKEN, RACE_SERVICE_TOKEN } from '@/lib/di/tokens';
|
||||
|
||||
export function useLeagueAndRaceData(leagueId: string) {
|
||||
const [leagueService, raceService] = useInjectMany([
|
||||
LEAGUE_SERVICE_TOKEN,
|
||||
RACE_SERVICE_TOKEN
|
||||
]);
|
||||
|
||||
const leagueQuery = useQuery({
|
||||
queryKey: ['league', leagueId],
|
||||
queryFn: () => leagueService.getLeague(leagueId),
|
||||
});
|
||||
|
||||
const racesQuery = useQuery({
|
||||
queryKey: ['races', leagueId],
|
||||
queryFn: () => raceService.getRacesByLeague(leagueId),
|
||||
});
|
||||
|
||||
return { leagueQuery, racesQuery };
|
||||
}
|
||||
```
|
||||
|
||||
## Migration Strategy
|
||||
|
||||
### Phase 1: Setup (Week 1)
|
||||
1. Install dependencies
|
||||
2. Configure TypeScript
|
||||
3. Create core infrastructure
|
||||
4. Create API module
|
||||
|
||||
### Phase 2: Domain Modules (Week 2-3)
|
||||
1. Create module for each domain (2-3 per day)
|
||||
2. Register all services
|
||||
3. Test each module
|
||||
|
||||
### Phase 3: React-Query Hooks Migration (Week 4)
|
||||
1. Update all hooks to use `useInject()` instead of `useServices()`
|
||||
2. Migrate one domain at a time:
|
||||
- `useLeagueService.ts` → `useInject(LEAGUE_SERVICE_TOKEN)`
|
||||
- `useRaceService.ts` → `useInject(RACE_SERVICE_TOKEN)`
|
||||
- `useDriverService.ts` → `useInject(DRIVER_SERVICE_TOKEN)`
|
||||
- etc.
|
||||
3. Test each hook after migration
|
||||
|
||||
### Phase 4: Integration & Cleanup (Week 5)
|
||||
1. Update root layout with ContainerProvider
|
||||
2. Remove old ServiceProvider
|
||||
3. Remove ServiceFactory
|
||||
4. Final verification
|
||||
|
||||
### React-Query Hook Migration Pattern
|
||||
|
||||
Each hook file gets a simple 2-line change:
|
||||
|
||||
```typescript
|
||||
// Before
|
||||
import { useServices } from '@/lib/services/ServiceProvider';
|
||||
const { leagueService } = useServices();
|
||||
|
||||
// After
|
||||
import { useInject } from '@/lib/di/hooks/useInject';
|
||||
import { LEAGUE_SERVICE_TOKEN } from '@/lib/di/tokens';
|
||||
const leagueService = useInject(LEAGUE_SERVICE_TOKEN);
|
||||
```
|
||||
|
||||
The rest of the hook (queries, mutations, etc.) stays exactly the same.
|
||||
|
||||
## Benefits
|
||||
|
||||
✅ **Testability**: Easy mocking via container overrides
|
||||
✅ **Maintainability**: Clear dependency graphs
|
||||
✅ **Type Safety**: Compile-time validation
|
||||
✅ **Consistency**: Same patterns as API
|
||||
✅ **Performance**: Singleton scope by default
|
||||
|
||||
## Files to Create
|
||||
|
||||
1. `apps/website/lib/di/index.ts`
|
||||
2. `apps/website/lib/di/container.ts`
|
||||
3. `apps/website/lib/di/tokens.ts`
|
||||
4. `apps/website/lib/di/providers/ContainerProvider.tsx`
|
||||
5. `apps/website/lib/di/hooks/useInject.ts`
|
||||
6. `apps/website/lib/di/modules/core.module.ts`
|
||||
7. `apps/website/lib/di/modules/api.module.ts`
|
||||
8. `apps/website/lib/di/modules/league.module.ts`
|
||||
9. `apps/website/lib/di/modules/driver.module.ts`
|
||||
10. `apps/website/lib/di/modules/team.module.ts`
|
||||
11. `apps/website/lib/di/modules/race.module.ts`
|
||||
12. `apps/website/lib/di/modules/media.module.ts`
|
||||
13. `apps/website/lib/di/modules/payment.module.ts`
|
||||
14. `apps/website/lib/di/modules/analytics.module.ts`
|
||||
15. `apps/website/lib/di/modules/auth.module.ts`
|
||||
16. `apps/website/lib/di/modules/dashboard.module.ts`
|
||||
17. `apps/website/lib/di/modules/policy.module.ts`
|
||||
18. `apps/website/lib/di/modules/protest.module.ts`
|
||||
19. `apps/website/lib/di/modules/penalty.module.ts`
|
||||
20. `apps/website/lib/di/modules/onboarding.module.ts`
|
||||
21. `apps/website/lib/di/modules/landing.module.ts`
|
||||
|
||||
## Next Steps
|
||||
|
||||
1. **Approve this plan**
|
||||
2. **Start Phase 1**: Install dependencies and create core infrastructure
|
||||
3. **Proceed module by module**: No backward compatibility needed - clean migration
|
||||
|
||||
**Total effort**: 4-5 weeks for clean implementation
|
||||
114
plans/state-management-consolidation-plan.md
Normal file
114
plans/state-management-consolidation-plan.md
Normal file
@@ -0,0 +1,114 @@
|
||||
# State Management Consolidation Plan
|
||||
|
||||
## Problem Analysis
|
||||
|
||||
### Current Mess
|
||||
The codebase has **multiple competing state management solutions** causing confusion and redundancy:
|
||||
|
||||
#### 1. `/apps/website/components/shared/state/` (The "New" Solution)
|
||||
- **StateContainer.tsx** - Combined wrapper for all states (loading, error, empty, success)
|
||||
- **LoadingWrapper.tsx** - 5 variants: spinner, skeleton, full-screen, inline, card
|
||||
- **ErrorDisplay.tsx** - 4 variants: full-screen, inline, card, toast
|
||||
- **EmptyState.tsx** - 3 variants + pre-configured states + illustrations
|
||||
- **❌ Missing types file**: `../types/state.types` (deleted but still imported)
|
||||
|
||||
#### 2. `/apps/website/components/shared/` (Legacy Simple Components)
|
||||
- **EmptyState.tsx** - Simple empty state (39 lines)
|
||||
- **LoadingState.tsx** - Simple loading spinner (15 lines)
|
||||
|
||||
#### 3. `/apps/website/components/errors/` (Alternative Error Solutions)
|
||||
- **ErrorDisplay.tsx** - API error focused (different API than shared/state)
|
||||
- **EnhancedErrorBoundary.tsx** - React error boundary
|
||||
- **ApiErrorBoundary.tsx** - API error boundary
|
||||
- **EnhancedFormError.tsx** - Form error handling
|
||||
|
||||
#### 4. Domain-Specific Solutions
|
||||
- **FeedEmptyState.tsx** - Feed-specific empty state
|
||||
- **EmptyState.tsx** in leagues - Different design system
|
||||
|
||||
### Core Issues
|
||||
1. **Missing types file** causing import errors in all state components
|
||||
2. **Multiple similar components** with different APIs
|
||||
3. **Inconsistent naming patterns**
|
||||
4. **Redundant functionality**
|
||||
5. **No single source of truth**
|
||||
6. **Mixed concerns** between generic and domain-specific
|
||||
|
||||
## Solution Strategy
|
||||
|
||||
### Phase 1: Create Unified Types
|
||||
Create `apps/website/components/shared/state/types.ts` with all type definitions:
|
||||
- EmptyState types
|
||||
- LoadingState types
|
||||
- ErrorDisplay types
|
||||
- StateContainer types
|
||||
- Convenience prop types
|
||||
|
||||
### Phase 2: Consolidate Components
|
||||
Keep only the comprehensive solution in `/apps/website/components/shared/state/`:
|
||||
- Update imports to use new types file
|
||||
- Ensure all components work with unified types
|
||||
- Remove any redundant internal type definitions
|
||||
|
||||
### Phase 3: Remove Redundant Files
|
||||
Delete:
|
||||
- `/apps/website/components/shared/EmptyState.tsx` (legacy)
|
||||
- `/apps/website/components/shared/LoadingState.tsx` (legacy)
|
||||
- Keep domain-specific ones if they serve unique purposes
|
||||
|
||||
### Phase 4: Update All Imports
|
||||
Find and update all imports across the codebase to use the consolidated solution.
|
||||
|
||||
## Detailed Implementation
|
||||
|
||||
### Step 1: Create Types File
|
||||
**File**: `apps/website/components/shared/state/types.ts`
|
||||
|
||||
```typescript
|
||||
// All type definitions for state management
|
||||
// EmptyState, LoadingWrapper, ErrorDisplay, StateContainer props
|
||||
// Plus convenience types
|
||||
```
|
||||
|
||||
### Step 2: Update State Components
|
||||
**Files to update**:
|
||||
- `EmptyState.tsx` - Import from `./types.ts`
|
||||
- `LoadingWrapper.tsx` - Import from `./types.ts`
|
||||
- `ErrorDisplay.tsx` - Import from `./types.ts`
|
||||
- `StateContainer.tsx` - Import from `./types.ts`
|
||||
|
||||
### Step 3: Remove Legacy Files
|
||||
**Delete**:
|
||||
- `apps/website/components/shared/EmptyState.tsx`
|
||||
- `apps/website/components/shared/LoadingState.tsx`
|
||||
|
||||
### Step 4: Update External Imports
|
||||
**Search for**: `from '@/components/shared/EmptyState'` or `from '@/components/shared/LoadingState'`
|
||||
**Replace with**: `from '@/components/shared/state/EmptyState'` or `from '@/components/shared/state/LoadingWrapper'`
|
||||
|
||||
## Files to Modify
|
||||
|
||||
### Create:
|
||||
1. `apps/website/components/shared/state/types.ts`
|
||||
|
||||
### Update:
|
||||
1. `apps/website/components/shared/state/EmptyState.tsx`
|
||||
2. `apps/website/components/shared/state/LoadingWrapper.tsx`
|
||||
3. `apps/website/components/shared/state/ErrorDisplay.tsx`
|
||||
4. `apps/website/components/shared/state/StateContainer.tsx`
|
||||
|
||||
### Delete:
|
||||
1. `apps/website/components/shared/EmptyState.tsx`
|
||||
2. `apps/website/components/shared/LoadingState.tsx`
|
||||
|
||||
### Update Imports in:
|
||||
- Multiple app pages and templates
|
||||
- Components that use the old paths
|
||||
|
||||
## Expected Benefits
|
||||
1. ✅ Single source of truth for state types
|
||||
2. ✅ Consistent API across all state components
|
||||
3. ✅ No import errors
|
||||
4. ✅ Reduced file count and complexity
|
||||
5. ✅ Better maintainability
|
||||
6. ✅ Clear separation between generic and domain-specific solutions
|
||||
Reference in New Issue
Block a user