TaxasGE Frontend Dev Skill
Overview
Ce skill complète le DEV_AGENT avec des patterns spécifiques frontend Next.js/React. Il ne remplace PAS le workflow de développement mais fournit des références techniques et templates pour l'implémentation frontend.
Principe fondamental : Guide technique, pas workflow (le workflow est dans DEV_AGENT.md et DEV_WORKFLOW.md).
When to Use This Skill
Claude invoquera automatiquement ce skill quand :
- DEV_AGENT implémente tâche frontend
- Besoin patterns Next.js/React
- Questions shadcn/ui composants
- Référence technique frontend
Ne PAS utiliser pour :
- Workflow développement (voir DEV_WORKFLOW.md)
- Validation (voir Go/No-Go Validator)
- Orchestration (voir Orchestrator)
Core Responsibilities
1. Architecture Frontend TaxasGE
Stack :
Next.js 14 (App Router)
↓
React Server Components + Client Components
↓
shadcn/ui + Tailwind CSS
↓
Zod (Validation) + React Hook Form
↓
API Client (fetch/axios)
Structure :
packages/web/src/
├── app/
│ ├── (dashboard)/ ← Pages authentifiées
│ │ ├── declarations/
│ │ ├── profile/
│ │ └── layout.tsx
│ └── (auth)/ ← Pages auth (login, register)
│ ├── login/
│ └── register/
│
├── components/
│ ├── ui/ ← shadcn/ui (Button, Input, etc.)
│ ├── declarations/ ← Composants métier
│ └── layout/ ← Layout (Header, Sidebar)
│
├── lib/
│ ├── api/ ← API clients
│ ├── validations/ ← Schémas Zod
│ ├── utils.ts ← Utilitaires
│ └── constants.ts ← Constantes
│
└── hooks/
├── useDeclarations.ts ← Hooks métier
└── useAuth.ts
2. Référence Documentation Frontend
Source unique de vérité : .github/docs-internal/Documentations/Frontend/
Structure :
.github/docs-internal/Documentations/Frontend/
├── ARCHITECTURE.md ← Architecture Next.js
├── COMPONENTS.md ← Composants shadcn/ui
├── ROUTING.md ← Routing App Router
├── STATE_MANAGEMENT.md ← State (Zustand/Context)
├── FORMS.md ← Formulaires (react-hook-form + Zod)
└── STYLING.md ← Tailwind CSS + Themes
Important : Toujours référencer cette documentation, ne PAS dupliquer.
3. Templates Code
Templates disponibles :
templates/page_template.tsx- Page Next.jstemplates/component_template.tsx- Composant Reacttemplates/api_client_template.ts- Client APItemplates/form_template.tsx- Formulaire react-hook-form
Patterns Frontend
Pattern 1 : Page Next.js (App Router)
Template : templates/page_template.tsx
Structure :
import { Suspense } from 'react'
import { DeclarationList } from '@/components/declarations/declaration-list'
import { DeclarationListSkeleton } from '@/components/declarations/declaration-list-skeleton'
/**
* Page liste déclarations
*
* @source .github/docs-internal/Documentations/Frontend/ROUTING.md
* @route /declarations
*/
export default function DeclarationsPage() {
return (
<div className="container mx-auto py-8">
<h1 className="text-3xl font-bold mb-6">Mes Déclarations</h1>
<Suspense fallback={<DeclarationListSkeleton />}>
<DeclarationList />
</Suspense>
</div>
)
}
Points clés :
- Suspense : Loading states automatiques
- Skeleton : Composant loading dédié
- Tailwind : Classes utilitaires uniquement
- Docstring : Source + Route
- Export default : Convention Next.js
Pattern 2 : Composant Client
Template : templates/component_template.tsx
Structure :
'use client'
import { useState } from 'react'
import { Button } from '@/components/ui/button'
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
import { useDeclarations } from '@/hooks/useDeclarations'
/**
* Liste déclarations avec actions
*
* @source .github/docs-internal/Documentations/Frontend/COMPONENTS.md
*/
export function DeclarationList() {
const { declarations, isLoading, error, deleteDeclaration } = useDeclarations()
const [deletingId, setDeletingId] = useState<string | null>(null)
const handleDelete = async (id: string) => {
setDeletingId(id)
try {
await deleteDeclaration(id)
} finally {
setDeletingId(null)
}
}
if (isLoading) return <DeclarationListSkeleton />
if (error) return <ErrorMessage error={error} />
if (!declarations.length) return <EmptyState />
return (
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
{declarations.map((declaration) => (
<Card key={declaration.id}>
<CardHeader>
<CardTitle>{declaration.title}</CardTitle>
</CardHeader>
<CardContent>
<p className="text-sm text-muted-foreground">
{declaration.description}
</p>
<div className="mt-4 flex gap-2">
<Button variant="outline" size="sm">
Voir
</Button>
<Button
variant="destructive"
size="sm"
disabled={deletingId === declaration.id}
=> handleDelete(declaration.id)}
>
{deletingId === declaration.id ? 'Suppression...' : 'Supprimer'}
</Button>
</div>
</CardContent>
</Card>
))}
</div>
)
}
Points clés :
- 'use client' : Directive obligatoire pour interactivité
- shadcn/ui : Composants UI (Button, Card)
- Custom hook :
useDeclarations()pour logique - Loading states : Gestion isLoading + disabled
- Responsive :
md:grid-cols-2 lg:grid-cols-3
Pattern 3 : Formulaire (react-hook-form + Zod)
Template : templates/form_template.tsx
Structure :
'use client'
import { zodResolver } from '@hookform/resolvers/zod'
import { useForm } from 'react-hook-form'
import * as z from 'zod'
import { Button } from '@/components/ui/button'
import {
Form,
FormControl,
FormDescription,
FormField,
FormItem,
FormLabel,
FormMessage,
} from '@/components/ui/form'
import { Input } from '@/components/ui/input'
import { Textarea } from '@/components/ui/textarea'
/**
* Schéma validation déclaration
*
* @source .github/docs-internal/Documentations/Frontend/FORMS.md
*/
const declarationSchema = z.object({
title: z.string()
.min(3, 'Titre minimum 3 caractères')
.max(100, 'Titre maximum 100 caractères'),
description: z.string()
.min(10, 'Description minimum 10 caractères')
.max(500, 'Description maximum 500 caractères'),
amount: z.number()
.positive('Montant doit être positif')
.max(1000000, 'Montant maximum 1 000 000'),
})
type DeclarationFormData = z.infer<typeof declarationSchema>
interface DeclarationFormProps {
onSubmit: (data: DeclarationFormData) => Promise<void>
defaultValues?: Partial<DeclarationFormData>
}
/**
* Formulaire création/édition déclaration
*
* @source .github/docs-internal/Documentations/Frontend/FORMS.md
*/
export function DeclarationForm({ onSubmit, defaultValues }: DeclarationFormProps) {
const form = useForm<DeclarationFormData>({
resolver: zodResolver(declarationSchema),
defaultValues: {
title: '',
description: '',
amount: 0,
...defaultValues,
},
})
const handleSubmit = async (data: DeclarationFormData) => {
try {
await onSubmit(data)
form.reset()
} catch (error) {
form.setError('root', {
message: error instanceof Error ? error.message : 'Erreur inconnue',
})
}
}
return (
<Form {...form}>
<form className="space-y-6">
<FormField
control={form.control}
name="title"
render={({ field }) => (
<FormItem>
<FormLabel>Titre</FormLabel>
<FormControl>
<Input placeholder="Titre de la déclaration" {...field} />
</FormControl>
<FormDescription>
Titre court et descriptif
</FormDescription>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="description"
render={({ field }) => (
<FormItem>
<FormLabel>Description</FormLabel>
<FormControl>
<Textarea
placeholder="Description détaillée"
rows={4}
{...field}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="amount"
render={({ field }) => (
<FormItem>
<FormLabel>Montant (FCFA)</FormLabel>
<FormControl>
<Input
type="number"
placeholder="0"
{...field}
=> field.onChange(parseFloat(e.target.value))}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
{form.formState.errors.root && (
<div className="rounded-md bg-destructive/10 p-4 text-sm text-destructive">
{form.formState.errors.root.message}
</div>
)}
<Button
type="submit"
disabled={form.formState.isSubmitting}
className="w-full"
>
{form.formState.isSubmitting ? 'Envoi...' : 'Créer déclaration'}
</Button>
</form>
</Form>
)
}
Points clés :
- Zod schema : Validation côté client
- react-hook-form : Gestion formulaire
- shadcn/ui Form : Composants formulaire
- Error handling :
form.setError('root') - Type-safety :
z.infer<typeof schema>
Pattern 4 : API Client
Template : templates/api_client_template.ts
Structure :
/**
* Client API Déclarations
*
* @source .github/docs-internal/Documentations/Frontend/*.md
*/
const API_BASE_URL = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:8000/api/v1'
export interface Declaration {
id: string
title: string
description: string
amount: number
status: 'draft' | 'submitted' | 'approved' | 'rejected'
created_at: string
updated_at: string
}
export interface CreateDeclarationDTO {
title: string
description: string
amount: number
}
export interface UpdateDeclarationDTO extends Partial<CreateDeclarationDTO> {}
/**
* Récupérer toutes déclarations
*/
export async function getDeclarations(): Promise<Declaration[]> {
const response = await fetch(`${API_BASE_URL}/declarations/`, {
headers: {
'Authorization': `Bearer ${getToken()}`,
},
})
if (!response.ok) {
throw new Error('Erreur récupération déclarations')
}
return response.json()
}
/**
* Créer déclaration
*/
export async function createDeclaration(
data: CreateDeclarationDTO
): Promise<Declaration> {
const response = await fetch(`${API_BASE_URL}/declarations/`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${getToken()}`,
},
body: JSON.stringify(data),
})
if (!response.ok) {
const error = await response.json()
throw new Error(error.detail || 'Erreur création déclaration')
}
return response.json()
}
/**
* Mettre à jour déclaration
*/
export async function updateDeclaration(
id: string,
data: UpdateDeclarationDTO
): Promise<Declaration> {
const response = await fetch(`${API_BASE_URL}/declarations/${id}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${getToken()}`,
},
body: JSON.stringify(data),
})
if (!response.ok) {
throw new Error('Erreur mise à jour déclaration')
}
return response.json()
}
/**
* Supprimer déclaration
*/
export async function deleteDeclaration(id: string): Promise<void> {
const response = await fetch(`${API_BASE_URL}/declarations/${id}`, {
method: 'DELETE',
headers: {
'Authorization': `Bearer ${getToken()}`,
},
})
if (!response.ok) {
throw new Error('Erreur suppression déclaration')
}
}
/**
* Récupérer token JWT depuis localStorage
*/
function getToken(): string | null {
if (typeof window === 'undefined') return null
return localStorage.getItem('access_token')
}
Points clés :
- Types TypeScript : Interfaces pour types retour
- Environment variables :
NEXT_PUBLIC_API_URL - Error handling : Throw errors explicites
- Authorization : Header Bearer token
- SSR-safe : Check
typeof window
Pattern 5 : Custom Hook
Structure :
'use client'
import { useState, useEffect } from 'react'
import * as api from '@/lib/api/declarations'
import type { Declaration, CreateDeclarationDTO } from '@/lib/api/declarations'
/**
* Hook déclarations
*
* @source .github/docs-internal/Documentations/Frontend/STATE_MANAGEMENT.md
*/
export function useDeclarations() {
const [declarations, setDeclarations] = useState<Declaration[]>([])
const [isLoading, setIsLoading] = useState(true)
const [error, setError] = useState<Error | null>(null)
// Charger déclarations
useEffect(() => {
const loadDeclarations = async () => {
try {
setIsLoading(true)
const data = await api.getDeclarations()
setDeclarations(data)
setError(null)
} catch (err) {
setError(err instanceof Error ? err : new Error('Erreur inconnue'))
} finally {
setIsLoading(false)
}
}
loadDeclarations()
}, [])
// Créer déclaration
const createDeclaration = async (data: CreateDeclarationDTO) => {
const newDeclaration = await api.createDeclaration(data)
setDeclarations((prev) => [newDeclaration, ...prev])
return newDeclaration
}
// Supprimer déclaration
const deleteDeclaration = async (id: string) => {
await api.deleteDeclaration(id)
setDeclarations((prev) => prev.filter((d) => d.id !== id))
}
return {
declarations,
isLoading,
error,
createDeclaration,
deleteDeclaration,
}
}
Points clés :
- State local :
useStatepour données - Side effects :
useEffectpour chargement - Mutations : Fonctions async retournées
- Optimistic updates : Mise à jour state avant API
Checklist Implémentation Frontend
Avant de considérer tâche frontend terminée :
Code
- Pages : Structure App Router respectée
- Composants : shadcn/ui utilisés
- Formulaires : react-hook-form + Zod
- API Client : Type-safe avec interfaces
- Hooks : Custom hooks pour logique réutilisable
Styling
- Tailwind : Classes utilitaires uniquement
- Responsive : Mobile/Tablet/Desktop
- Dark mode : Support thème sombre
- Accessibilité : ARIA labels, keyboard navigation
Tests
- Tests unitaires : Jest (composants) >75%
- Tests E2E : Playwright (flows) >80%
- Tests passent : 100%
Qualité
- ESLint : 0 erreurs
- TypeScript : 0 erreurs (strict mode)
- Build :
npm run buildréussi
Performance
- Lighthouse : Score >90
- Bundle size : Optimisé
- Images : Next.js Image optimisé
Integration avec DEV_AGENT
Workflow Complet
1. DEV_AGENT reçoit tâche frontend (ex: TASK-P2-015)
↓
2. DEV_AGENT lit DEV_WORKFLOW.md
↓
3. DEV_AGENT invoque Frontend Dev Skill (ce skill)
↓
4. Frontend Dev Skill fournit :
- Patterns Next.js
- Templates composants
- Références documentation
↓
5. DEV_AGENT implémente selon patterns
↓
6. DEV_AGENT génère rapport
↓
7. Go/No-Go Validator valide
Ce skill ne fait PAS :
- ❌ Workflow développement
- ❌ Git operations
- ❌ Génération rapports
- ❌ Validation
Ce skill fait :
- ✅ Fournit patterns techniques
- ✅ Référence documentation
- ✅ Templates code
- ✅ Best practices frontend
References
Agents & Workflows
.claude/.agent/Tasks/DEV_AGENT.md.claude/.agent/SOP/DEV_WORKFLOW.md.claude/.agent/SOP/CODE_STANDARDS.md.claude/.agent/SOP/TEST_WORKFLOW.md
Documentation Frontend
.github/docs-internal/Documentations/Frontend/ARCHITECTURE.md→ Architecture Next.js App Router.github/docs-internal/Documentations/Frontend/COMPONENTS.md→ Composants shadcn/ui disponibles.github/docs-internal/Documentations/Frontend/ROUTING.md→ Patterns routing Next.js.github/docs-internal/Documentations/Frontend/STYLING.md→ Design system et charte graphique.github/docs-internal/Documentations/Frontend/FORMS.md→ Patterns formulaires.github/docs-internal/Documentations/Frontend/STATE_MANAGEMENT.md→ Gestion état
Templates
templates/page_template.tsxtemplates/component_template.tsxtemplates/api_client_template.tstemplates/form_template.tsx
Success Criteria
Une implémentation frontend est réussie si :
- ✅ Pages Next.js App Router
- ✅ shadcn/ui composants
- ✅ Formulaires react-hook-form + Zod
- ✅ Tests coverage >75%
- ✅ Lighthouse >90
- ✅ 0 erreurs ESLint/TypeScript
- ✅ Build réussi
Skill created by: TaxasGE Frontend Team
Date: 2025-10-31
Version: 2.0.0
Status: ✅ READY FOR USE