website refactor

This commit is contained in:
2026-01-14 11:38:05 +01:00
parent 0d89ad027e
commit 02073f19ef
3 changed files with 300 additions and 10 deletions

View File

@@ -0,0 +1,254 @@
# React Component Architecture (Concept)
This document defines the clean concept for React component architecture in `apps/website`.
## Core Principle
**Separation of concerns by responsibility, not just by file location.**
## The Four Layers
### 1. App Layer (`app/`)
**Purpose**: Entry points and data orchestration
**What lives here**:
- `page.tsx` - Server Components that fetch data
- `layout.tsx` - Root layouts
- `route.tsx` - API routes
- `*PageClient.tsx` - Client entry points that wire server data to client templates
**Rules**:
- `page.tsx` does ONLY data fetching and passes raw data to client components
- `*PageClient.tsx` manages client state and event handlers
- No UI rendering logic (except loading/error states)
**Example**:
```typescript
// app/teams/page.tsx
export default async function TeamsPage() {
const query = new TeamsPageQuery();
const result = await query.execute();
if (result.isErr()) {
return <ErrorTeams />;
}
return <TeamsPageClient teams={result.value.teams} />;
}
// app/teams/TeamsPageClient.tsx
'use client';
export function TeamsPageClient({ teams }: TeamsViewData) {
const [searchQuery, setSearchQuery] = useState('');
const router = useRouter();
const handleTeamClick = (teamId: string) => {
router.push(`/teams/${teamId}`);
};
return (
<TeamsTemplate
teams={teams}
searchQuery={searchQuery}
onSearchChange={setSearchQuery}
onTeamClick={handleTeamClick}
/>
);
}
```
### 2. Template Layer (`templates/`)
**Purpose**: Composition and layout of components
**What lives here**:
- Stateless component compositions
- Page-level layouts
- Component orchestration
**Rules**:
- Templates ARE stateless (no `useState`, `useEffect`)
- Templates CAN use `'use client'` for event handling and composition
- Templates receive ViewData (primitives) and event handlers
- Templates compose components and UI elements
- No business logic
- No data fetching
**Example**:
```typescript
// templates/TeamsTemplate.tsx
'use client';
export function TeamsTemplate({ teams, searchQuery, onSearchChange, onTeamClick }: TeamsTemplateProps) {
return (
<main>
<Header>
<SearchInput value={searchQuery} onChange={onSearchChange} />
</Header>
<TeamLeaderboardPreview
teams={teams}
onTeamClick={onTeamClick}
/>
<TeamGrid teams={teams} onTeamClick={onTeamClick} />
</main>
);
}
```
### 3. Component Layer (`components/`)
**Purpose**: Reusable app-specific components
**What lives here**:
- Components that understand app concepts (teams, races, leagues)
- Components that may contain state and business logic
- Components that orchestrate UI elements for app purposes
**Rules**:
- Can be stateful
- Can contain app-specific business logic
- Can use Next.js hooks (but not Next.js components like `Link`)
- Should be reusable within the app context
- Should NOT be generic UI primitives
**Example**:
```typescript
// components/teams/TeamLeaderboardPreview.tsx
'use client';
export function TeamLeaderboardPreview({ teams, onTeamClick }: Props) {
// App-specific logic: medal colors, ranking, etc.
const getMedalColor = (position: number) => { /* ... */ };
return (
<Card>
<CardHeader>Top Teams</CardHeader>
{teams.map((team, index) => (
<TeamRow
key={team.id}
team={team}
position={index}
onClick={() => onTeamClick(team.id)}
medalColor={getMedalColor(index)}
/>
))}
</Card>
);
}
```
### 4. UI Layer (`ui/`)
**Purpose**: Generic, reusable UI primitives
**What lives here**:
- Pure UI elements with no app knowledge
- Generic building blocks
- Framework-agnostic components
**Rules**:
- Stateless (no `useState`, `useEffect`)
- No app-specific logic
- No Next.js imports
- Only receive props and render
- Maximum reusability
**Example**:
```typescript
// ui/Button.tsx
export function Button({ children, variant, onClick }: ButtonProps) {
return (
<button
className={`btn btn-${variant}`}
onClick={onClick}
>
{children}
</button>
);
}
// ui/Card.tsx
export function Card({ children, className }: CardProps) {
return <div className={`card ${className}`}>{children}</div>;
}
```
## The Display Object Layer
**Purpose**: Reusable formatting and presentation logic
**What lives here**:
- `lib/display-objects/`
- Deterministic formatting functions
- Code-to-label mappings
- Value transformations for display
**Rules**:
- Class-based
- Immutable
- Deterministic
- No side effects
- No `Intl.*` or `toLocale*`
**Usage**:
```typescript
// lib/display-objects/RatingDisplay.ts
export class RatingDisplay {
static format(rating: number): string {
return rating.toFixed(0);
}
static getColor(rating: number): string {
if (rating >= 90) return 'text-green';
if (rating >= 70) return 'text-yellow';
return 'text-red';
}
}
// In ViewModel Builder
const viewModel = {
rating: RatingDisplay.format(dto.rating),
ratingColor: RatingDisplay.getColor(dto.rating)
};
```
## Dependency Flow
```
app/ (page.tsx)
↓ fetches data
app/ (*PageClient.tsx)
↓ manages state, creates handlers
templates/ (composition)
↓ uses components
components/ (app-specific)
↓ uses UI elements
ui/ (generic primitives)
```
## Decision Rules
### When to use *PageClient.tsx?
- When you need client-side state management
- When you need event handlers that use router/other hooks
- When server component needs to be split from client template
### When to use Template vs Component?
- **Template**: Page-level composition, orchestrates multiple components
- **Component**: Reusable app-specific element with logic
### When to use Component vs UI?
- **Component**: Understands app concepts (teams, races, leagues)
- **UI**: Generic building blocks (button, card, input)
### When to use Display Objects?
- When formatting is reusable across multiple ViewModels
- When mapping codes to labels
- When presentation logic needs to be deterministic
## Key Benefits
1. **Clear boundaries**: Each layer has a single responsibility
2. **Testability**: Pure functions at UI/Display layer
3. **Reusability**: UI elements can be used anywhere
4. **Maintainability**: Changes in one layer don't ripple
5. **Type safety**: Clear contracts between layers