page wrapper

This commit is contained in:
2026-01-07 12:40:52 +01:00
parent e589c30bf8
commit 0db80fa98d
128 changed files with 7386 additions and 8096 deletions

View File

@@ -1,280 +0,0 @@
# Dependency Injection Migration Summary
## ✅ Completed Work
### 1. Core Infrastructure (100% Complete)
- **InversifyJS** installed and configured with reflect-metadata
- **ContainerProvider** integrated into root layout
- **Token registry** using Symbol.for for cross-module consistency
- **useInject()** hook for type-safe dependency injection
- **Module system** following NestJS patterns
### 2. Module Architecture (100% Complete)
All domain modules created with proper bindings:
```typescript
// API Module
- AnalyticsApi
- AuthApi
- DashboardApi
- DriverApi
- LeagueApi
- MediaApi
- PolicyApi
- RaceApi
- SponsorApi
- TeamApi
// Core Module
- Logger
- ErrorReporter
- Config
// Domain Modules
- AnalyticsModule
- DashboardModule
- DriverModule
- LandingModule
- LeagueModule
- PolicyModule
- RaceModule
- SponsorModule
- TeamModule
```
### 3. React-Query Integration (100% Complete)
Created 20+ hooks following SCREAMING_SNAKE_CASE pattern:
**Dashboard:**
- `useDashboardOverview()`
**Driver:**
- `useCurrentDriver()`
- `useDriverLeaderboard()`
**League:**
- `useAllLeagues()`
- `useLeagueAdminStatus()`
- `useLeagueDetail()`
- `useLeagueDetailWithSponsors()`
- `useLeagueMemberships()`
- `useLeagueRosterAdmin()`
- `useLeagueSchedule()`
- `useLeagueSettings()`
- `useLeagueStewardingData()`
- `useLeagueWallet()`
- `useProtestDetail()`
**Penalty:**
- `useRacePenalties()`
**Protest:**
- `useLeagueProtests()`
**Race:**
- `useCancelRace()`
- `useCompleteRace()`
- `useRaceDetail()`
- `useRaceResultsDetail()`
- `useRacesPageData()`
- `useRaceStewardingData()`
- `useRaceWithSOF()`
- `useRegisterForRace()`
- `useReopenRace()`
- `useWithdrawFromRace()`
**Sponsor:**
- `useAvailableLeagues()`
**Team:**
- `useAllTeams()`
- `useTeamDetails()`
- `useTeamMembers()`
**Shared:**
- `useCapability()`
- `useEffectiveDriverId()`
### 4. Pages Migrated to DI + React-Query (100% Complete)
-`apps/website/app/dashboard/page.tsx` - Uses `useDashboardOverview()`
-`apps/website/app/profile/page.tsx` - Uses `useDriverProfile()`
-`apps/website/app/sponsor/leagues/page.tsx` - Uses `useAvailableLeagues()`
### 5. Components Migrated from useServices() to useInject() (16+ files)
-`CapabilityGate.tsx` - Uses `useCapability()`
-`StateContainer.tsx` - Uses `useInject()` for Logger
-`ErrorDisplay.tsx` - Uses `useInject()` for Logger
-`LoadingWrapper.tsx` - Uses `useInject()` for Logger
-`LoadingState.tsx` - Uses `useInject()` for Logger
-`DriversInteractive.tsx` - Uses `useDriverLeaderboard()`
-`LeagueRosterAdmin.tsx` - Uses `useLeagueRosterAdmin()` + mutations
-`LeagueSettings.tsx` - Uses `useLeagueSettings()` + mutation
-`LeagueSchedule.tsx` - Uses `useLeagueSchedule()` + mutations
-`RaceDetail.tsx` - Uses `useRaceDetail()` + mutations
-`RaceResultsDetail.tsx` - Uses `useRaceResultsDetail()`
-`RaceStewarding.tsx` - Uses `useRaceStewardingData()` + mutations
-`TeamDetails.tsx` - Uses `useTeamDetails()` + mutation
-`TeamMembers.tsx` - Uses `useTeamMembers()` + mutation
-`TeamRoster.tsx` - Uses `useTeamMembers()`
-`TeamStandings.tsx` - Uses `useInject()` for leagueService
### 6. DRY Error Handling (100% Complete)
Created `enhanceQueryResult()` utility that:
- Converts React-Query errors to `ApiError` for StateContainer compatibility
- Provides `retry()` function for refetching
- Eliminates repetitive error handling code
### 7. Testing Infrastructure (100% Complete)
- `createTestContainer()` utility for unit tests
- Mock service providers
- Test module configurations
### 8. Documentation (100% Complete)
- `README.md` - Comprehensive DI guide
- `MIGRATION_SUMMARY.md` - This file
## 🔄 Current State
### Files Still Using useServices() (22 files)
#### Sponsor Pages (3 files)
1. `apps/website/app/sponsor/billing/page.tsx` - Line 263
2. `apps/website/app/sponsor/campaigns/page.tsx` - Line 367
3. `apps/website/app/sponsor/leagues/[id]/page.tsx` - Line 42
#### Race Components (2 files)
4. `apps/website/components/races/FileProtestModal.tsx` - Line 42
5. `apps/website/app/races/RacesStatic.tsx` - Line 7
#### Team Components (5 files)
6. `apps/website/components/teams/TeamStandings.tsx` - Line 13
7. `apps/website/components/teams/TeamAdmin.tsx` - Line 19
8. `apps/website/components/teams/CreateTeamForm.tsx` - Line 17
9. `apps/website/components/teams/TeamRoster.tsx` - Line 28
10. `apps/website/components/teams/JoinTeamButton.tsx` - Line 32
#### League Components (6 files)
11. `apps/website/components/leagues/QuickPenaltyModal.tsx` - Line 47
12. `apps/website/components/leagues/ScheduleRaceForm.tsx` - Line 38
13. `apps/website/components/leagues/CreateLeagueForm.tsx` - Line 54
14. `apps/website/components/leagues/LeagueSponsorshipsSection.tsx` - Line 32
15. `apps/website/components/leagues/LeagueActivityFeed.tsx` - Line 35
16. `apps/website/components/leagues/JoinLeagueButton.tsx` - Line 22
#### Driver Components (3 files)
17. `apps/website/components/drivers/DriverProfile.tsx` - Line 28
18. `apps/website/components/drivers/CreateDriverForm.tsx` - Line 19
19. `apps/website/components/profile/UserPill.tsx` - Line 139
#### Sponsor Components (1 file)
20. `apps/website/components/sponsors/SponsorInsightsCard.tsx` - Line 159
#### Auth & Onboarding (2 files)
21. `apps/website/lib/auth/AuthContext.tsx` - Line 34
22. `apps/website/components/onboarding/OnboardingWizard.tsx` - Line 166
## 📋 Migration Pattern
### Before (Old Pattern)
```typescript
import { useServices } from '@/lib/services/ServiceProvider';
function MyComponent() {
const { someService } = useServices();
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
someService.getData()
.then(setData)
.catch(setError)
.finally(() => setLoading(false));
}, [someService]);
if (loading) return <Loading />;
if (error) return <Error error={error} />;
return <div>{data}</div>;
}
```
### After (New Pattern)
```typescript
// 1. Create hook in hooks/domain/
'use client';
import { useQuery } from '@tanstack/react-query';
import { useInject } from '@/lib/di/hooks/useInject';
import { SOME_SERVICE_TOKEN } from '@/lib/di/tokens';
import { enhanceQueryResult } from '@/lib/di/hooks/useReactQueryWithApiError';
export function useSomeData() {
const someService = useInject(SOME_SERVICE_TOKEN);
const queryResult = useQuery({
queryKey: ['some-data'],
queryFn: () => someService.getData(),
staleTime: 1000 * 60 * 5,
});
return enhanceQueryResult(queryResult);
}
// 2. Use in component
import { useSomeData } from '@/hooks/domain/useSomeData';
function MyComponent() {
const { data, isLoading, isError, error } = useSomeData();
if (isLoading) return <Loading />;
if (isError) return <Error error={error} />;
return <div>{data}</div>;
}
```
## 🎯 Next Steps
### Option 1: Continue Migration (Recommended)
Migrate the remaining 22 files systematically:
1. **Create hooks for each service usage** in `apps/website/hooks/` subdirectories
2. **Update components** to use new hooks
3. **Test each migration** thoroughly
### Option 2: Stop Here
The core infrastructure is complete and working. The remaining files can be migrated gradually as needed.
## 🏆 Key Benefits Achieved
1. **Clean Architecture**: Follows NestJS patterns, familiar to backend team
2. **Type Safety**: Full TypeScript support with proper inference
3. **Testability**: Easy to mock dependencies in tests
4. **Maintainability**: Centralized dependency management
5. **DRY Principle**: Reusable hooks with consistent error handling
6. **Performance**: React-Query caching + DI container optimization
## 📚 Key Files Reference
### Infrastructure
- `apps/website/lib/di/container.ts` - Main container
- `apps/website/lib/di/tokens.ts` - Token registry
- `apps/website/lib/di/hooks/useInject.ts` - Injection hook
- `apps/website/lib/di/providers/ContainerProvider.tsx` - React provider
### Modules
- `apps/website/lib/di/modules/*.module.ts` - Domain modules
### Hooks
- `apps/website/hooks/*/*.ts` - 20+ React-Query hooks
### Pages
- `apps/website/app/dashboard/page.tsx` - Migrated
- `apps/website/app/profile/page.tsx` - Migrated
- `apps/website/app/sponsor/leagues/page.tsx` - Migrated
### Documentation
- `apps/website/lib/di/README.md` - Usage guide
- `apps/website/lib/di/MIGRATION_SUMMARY.md` - This summary
---
**Status**: ✅ Core infrastructure complete and production-ready. Remaining migration is optional and can be done incrementally.

View File

@@ -1,177 +0,0 @@
# Dependency Injection System
This directory contains the new dependency injection system for the GridPilot website, built with InversifyJS.
## Overview
The DI system provides:
- **Centralized dependency management** - All services are registered in modules
- **Type-safe injection** - Compile-time validation of dependencies
- **Easy testing** - Simple mocking via container overrides
- **React integration** - Hooks for component-level injection
- **NestJS-like patterns** - Familiar structure for API developers
## Architecture
```
lib/di/
├── index.ts # Main exports
├── container.ts # Container factory & lifecycle
├── tokens.ts # Symbol-based tokens
├── providers/ # React Context integration
│ └── ContainerProvider.tsx
├── hooks/ # Injection hooks
│ └── useInject.ts
└── modules/ # Domain modules
├── core.module.ts # Logger, error reporter, config
├── api.module.ts # API clients
├── league.module.ts # League services
├── driver.module.ts # Driver services
└── team.module.ts # Team services
```
## Usage
### 1. Setup Root Provider
```tsx
// app/layout.tsx
import { ContainerProvider } from '@/lib/di/providers/ContainerProvider';
export default function RootLayout({ children }) {
return (
<html>
<body>
<ContainerProvider>
{children}
</ContainerProvider>
</body>
</html>
);
}
```
### 2. Inject Services in Hooks
```typescript
// hooks/useLeagueService.ts
import { useInject } from '@/lib/di/hooks/useInject';
import { LEAGUE_SERVICE_TOKEN } from '@/lib/di/tokens';
import { useQuery } from '@tanstack/react-query';
export function useAllLeagues() {
const leagueService = useInject(LEAGUE_SERVICE_TOKEN);
return useQuery({
queryKey: ['allLeagues'],
queryFn: () => leagueService.getAllLeagues(),
});
}
```
### 3. Inject in Components
```typescript
// components/MyComponent.tsx
'use client';
import { useInject } from '@/lib/di/hooks/useInject';
import { LEAGUE_SERVICE_TOKEN } from '@/lib/di/tokens';
export function MyComponent() {
const leagueService = useInject(LEAGUE_SERVICE_TOKEN);
// Use leagueService...
}
```
### 4. Server Components
```typescript
// app/leagues/[id]/page.tsx
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} />;
}
```
### 5. Testing
```typescript
import { createTestContainer } from '@/lib/di/container';
import { ContainerProvider } from '@/lib/di/providers/ContainerProvider';
import { LEAGUE_SERVICE_TOKEN } from '@/lib/di/tokens';
test('component works', () => {
const mockService = { getData: jest.fn() };
const overrides = new Map([
[LEAGUE_SERVICE_TOKEN, mockService]
]);
const container = createTestContainer(overrides);
render(
<ContainerProvider container={container}>
<MyComponent />
</ContainerProvider>
);
});
```
## Token Naming Convention
```typescript
// Format: DOMAIN_SERVICE_TYPE_TOKEN
export const LEAGUE_SERVICE_TOKEN = Symbol.for('Service.League');
export const LEAGUE_API_CLIENT_TOKEN = Symbol.for('Api.LeagueClient');
export const LOGGER_TOKEN = Symbol.for('Core.Logger');
```
## Module Pattern
```typescript
import { ContainerModule } from 'inversify';
import { Service } from './Service';
import { SERVICE_TOKEN } from '../tokens';
export const DomainModule = new ContainerModule((options) => {
const bind = options.bind;
bind<SERVICE_TOKEN>(SERVICE_TOKEN)
.to(Service)
.inSingletonScope();
});
```
## Migration from Old System
### Before
```typescript
const { leagueService } = useServices();
```
### After
```typescript
const leagueService = useInject(LEAGUE_SERVICE_TOKEN);
```
## Benefits
**Testability** - Easy mocking via container overrides
**Maintainability** - Clear dependency graphs
**Type Safety** - Compile-time validation
**Consistency** - Same patterns as NestJS API
**Performance** - Singleton scope by default
## Next Steps
1. Complete module implementations for all domains
2. Migrate all React-Query hooks to use `useInject()`
3. Update tests to use test containers
4. Remove old `ServiceProvider` and `ServiceFactory`