Adds application design (awaiting approval)

This commit is contained in:
2026-06-17 20:15:54 +02:00
parent c7154e288f
commit 9e49489f7e
12 changed files with 1171 additions and 22 deletions
@@ -0,0 +1,192 @@
# Component Methods — CMS Frontend
> **Note**: Detailed business rules and logic are defined per-unit in the Functional Design stage (CONSTRUCTION phase). This document captures method signatures, inputs, outputs, and high-level purpose only.
---
## Backend — AuthController (updated)
```csharp
// POST /api/v1/auth/login
// Body: LoginRequest { Email, Password }
// Sets: Set-Cookie: refreshToken=<token>; HttpOnly; Secure; SameSite=Strict; Path=/api/v1/auth
// Returns: { accessToken: string, expiresAt: datetime, user: { id, email, name, role, isActive } }
Task<IActionResult> Login(LoginRequest request)
// POST /api/v1/auth/refresh
// Reads: Cookie: refreshToken
// Sets: Set-Cookie: refreshToken=<newToken>; HttpOnly; Secure; SameSite=Strict; Path=/api/v1/auth
// Returns: { accessToken: string, expiresAt: datetime, user: { id, email, name, role, isActive } }
Task<IActionResult> Refresh() // no body parameter — reads from cookie
// POST /api/v1/auth/revoke
// Requires: Authorization: Bearer <accessToken>
// Reads: Cookie: refreshToken
// Clears: Set-Cookie: refreshToken=; Expires=epoch; Path=/api/v1/auth
// Returns: 204 No Content
Task<IActionResult> Revoke() // no body parameter — reads from cookie
```
## Backend — ServiceCollectionExtensions (updated)
```csharp
// Registers CORS policy "FrontendPolicy" using Cors:AllowedOrigins from configuration
// Called in AddCoreInfrastructure()
static void AddCorsFrontendPolicy(this IServiceCollection services, IConfiguration configuration)
// Adds app.UseCors("FrontendPolicy") to the pipeline
// Called in Program.cs after UseExceptionHandler, before UseAuthentication
static void UseFrontendCors(this WebApplication app)
```
---
## Frontend — ApiClient (`frontend/src/api/client.ts`)
```typescript
// Base configured fetch wrapper; sets Authorization header and credentials: 'include'
// Retries once on 401 after calling authContext.refresh()
function createApiClient(getAccessToken: () => string | null, refresh: () => Promise<void>): ApiClient
// Typed GET request
async function get<T>(path: string, options?: RequestInit): Promise<T>
// Typed POST request
async function post<T>(path: string, body: unknown, options?: RequestInit): Promise<T>
```
## Frontend — AuthContext (`frontend/src/auth/AuthContext.tsx`)
```typescript
interface AuthContextValue {
isAuthenticated: boolean
isLoading: boolean // true during initial session restore
user: User | null // { id, email, name, role, isActive }
accessToken: string | null // in-memory only, never in localStorage
login(email: string, password: string): Promise<void>
logout(): Promise<void>
refresh(): Promise<void> // called by ApiClient on 401; restores token from cookie
}
// Hook
function useAuth(): AuthContextValue
```
## Frontend — Route Guards
```typescript
// ProtectedRoute: renders children if authenticated, redirects to /login otherwise
// Used as a TanStack Router beforeLoad or as a wrapper component
function ProtectedRoute({ children }: { children: ReactNode }): JSX.Element
// RoleGuard: renders children if user.role is in allowedRoles, redirects to /403 otherwise
function RoleGuard({ allowedRoles, children }: { allowedRoles: string[], children: ReactNode }): JSX.Element
// InitGuard: checks GET /setup/status on app load; redirects to /setup if not initialized
function InitGuard({ children }: { children: ReactNode }): JSX.Element
```
## Frontend — TanStack Query hooks (`frontend/src/api/`)
```typescript
// Fetch current availability status; staleTime: 30s
function useAvailabilityStatus(): UseQueryResult<AvailabilityStatusResponse>
// Fetch user list; staleTime: 60s
function useUsers(): UseQueryResult<User[]>
// Invite a user; invalidates useUsers on success
function useInviteUser(): UseMutationResult<InviteResponse, Error, InviteUserRequest>
// Complete invitation setup (public, no auth required)
function useCompleteSetup(): UseMutationResult<void, Error, CompleteSetupRequest>
// Validate invitation token (public)
function useValidateInvitation(token: string): UseQueryResult<InvitationValidationResponse>
```
## Frontend — Page component signatures
```typescript
// All page components are default exports used as TanStack Router route components
export default function LoginPage(): JSX.Element
export default function SetupPage(): JSX.Element
export default function InviteCompletePage(): JSX.Element // reads token from search params
export default function DashboardPage(): JSX.Element
export default function UsersPage(): JSX.Element
export default function ProfilePage(): JSX.Element
export default function SettingsPage(): JSX.Element
export default function CmsPage(): JSX.Element
export default function NotFoundPage(): JSX.Element
export default function AccessDeniedPage(): JSX.Element
```
## Frontend — Shared components
```typescript
// Availability badge with colour-coding
function AvailabilityStatusBadge({ status, message }: {
status: 'Available' | 'Maintenance' | 'Unavailable'
message?: string
}): JSX.Element
// Two-step invite dialog
function InviteUserDialog({ open, onClose }: {
open: boolean
onClose: () => void
}): JSX.Element
// Error boundary — class component wrapping children
class ErrorBoundary extends React.Component<{
fallback?: ReactNode
children: ReactNode
}>
```
---
## TypeScript Type Definitions (`frontend/src/api/types.ts`)
```typescript
interface User {
id: string
email: string
name: string
role: 'Owner' | 'Admin' | 'User'
isActive: boolean
}
interface AuthResponse {
accessToken: string
expiresAt: string // ISO datetime
user: User
}
interface AvailabilityStatusResponse {
status: 'Available' | 'Maintenance' | 'Unavailable'
checkedAt: string // ISO datetime
message?: string
}
interface InviteUserRequest {
email: string
role: 'Admin' | 'User'
}
interface InviteResponse {
inviteLink: string
}
interface CompleteSetupRequest {
token: string
displayName: string
password: string
}
interface InvitationValidationResponse {
valid: boolean
email: string
role: string
}
```