Whoiam est un portfolio personnel qui transforme l'expérience classique d'un CV en une interface desktop interactive.
Inspiré des environnements macOS et Linux, le projet présente mes compétences, projets et expériences à travers un système de fenêtres draggables avec gestion complète de l'état via NgRx.
L'objectif : démontrer une maîtrise technique solide tout en offrant une expérience utilisateur originale et engageante.
|
|
|
|
| Technologie | Version | Usage |
|---|---|---|
| Angular | 20.0.0 |
Framework principal, mode zoneless |
| TypeScript | 5.8.2 |
Strict mode, explicit return types |
| RxJS | 7.8.0 |
Programmation réactive |
| SCSS | - | Styling avec variables et mixins |
// NgRx Store pour la gestion centralisée des fenêtres
@ngrx/
store // Store global
@ngrx/
effects // Side-effects asynchrones
@ngrx/
entity // Normalisation des entités
@ngrx/
store - devtools // Debugging en dev- Angular CDK : Drag & Drop natif
- Angular Signals : Réactivité fine-grained
- ngx-translate : Traductions dynamiques avec custom loaders
| Outil | Configuration | Purpose |
|---|---|---|
| ESLint | TypeScript strict + Angular best practices | Linting avec règles de sécurité |
| Prettier | Single quotes, 120 chars, tab 2 | Formatage automatique |
| Husky | Pre-commit hooks | Lint + format sur fichiers staged |
| Jasmine/Karma | ChromeHeadless, coverage | Tests unitaires |
- Docker : Multi-stage builds (dev/prod avec Nginx)
- GitHub Actions : CI/CD (lint, test, build, release)
- Cloudflare : CDN et protection DDoS
- Nginx : Reverse proxy avec gzip, security headers
src/app/
├── common/ # Éléments partagés
│ ├── components/ # Composants réutilisables
│ │ ├── bubble/ # Bulles d'information
│ │ ├── card/ # Cartes de contenu
│ │ ├── content-window/ # Fenêtre de contenu générique
│ │ ├── listing-window/ # Fenêtre de liste
│ │ ├── placeholder-text/ # Texte de placeholder
│ │ ├── resize-handle/ # Poignée de redimensionnement
│ │ ├── spinner/ # Indicateur de chargement
│ │ └── window-header/ # En-tête de fenêtre
│ ├── constants/ # Constantes globales (paths, langs, styles)
│ ├── directives/ # window-actions directive
│ └── models/ # Classes abstraites de base
│
├── features/ # Features organisées par domaine
│ ├── app-bar/ # Dock applicatif (bottom bar)
│ ├── contact/ # Informations de contact
│ ├── experiences/ # Expériences professionnelles
│ │ ├── components/details/ # Détails d'une expérience
│ │ ├── models/ # Types Experience, RawExperience
│ │ └── services/ # ExperiencesService
│ ├── header-bar/ # Barre supérieure (menu, horloge, langues)
│ ├── home/ # Page d'accueil/introduction
│ └── projects/ # Portfolio de projets
│ ├── models/ # Types Project, Category, Status
│ └── services/ # ProjectsService
│
├── services/ # Services globaux
│ ├── contact/ # Gestion des contacts
│ ├── data/ # Chargement des données JSON
│ ├── drag-n-drop/ # Service de drag & drop
│ ├── format/ # Formatage des données
│ ├── i18n/ # Service d'internationalisation
│ ├── navigation/ # Navigation entre fenêtres
│ ├── translate-loader/ # Custom loaders pour SSR
│ └── window-manager/ # Façade du store NgRx
│
└── store/ # NgRx Store
└── window-manager/ # Feature store des fenêtres
├── actions/ # Actions (open, close, minimize, etc.)
├── constants/ # Valeurs par défaut, breakpoints
├── effects/ # Side-effects asynchrones
├── models/ # Types du state
├── reducers/ # Reducers avec NgRx Entity
└── selectors/ # Selectors mémoïsés
- Feature-based architecture : Chaque feature est autonome avec ses composants, services et modèles
- Separation of concerns : Logique métier séparée de la présentation
- NgRx Store : État global uniquement pour le window manager
- Standalone components : Tous les composants sont standalone (no NgModules)
L'application n'utilise pas le Router Angular traditionnel. La navigation se fait entièrement via le store NgRx :
// Navigation par ouverture de fenêtres plutôt que par routes URL
windowManagerService.openWindow('projects');
windowManagerService.closeWindow('home');Avantage : Crée une expérience utilisateur type desktop natif où plusieurs fenêtres coexistent simultanément.
Angular 20 en mode zoneless pour des performances optimales :
// app.config.ts
provideZonelessChangeDetection()- Signals pour l'état local réactif
computed()pour les dérivations automatiques- OnPush change detection partout
- Détection de changements explicite et prédictible
State = EntityState < WindowState > {
ids: ['home', 'projects', 'experiences', 'contact'],
entities: {
home: {id, status, position, size, zIndex, isActive},
// ...
}
}Bénéfices :
- Accès O(1) aux entités par ID
- Reducers standardisés
- Selectors mémoïsés pour éviter les recalculs
Problématique : Les paths diffèrent entre browser et SSR.
Solution : Custom loaders qui détectent l'environnement :
// Browser : /assets/i18n/fr.json
// SSR : dist/Whoiam/browser/assets/i18n/fr.jsonHelper getTranslatedField() pour les données multilingues stockées en JSON.
npm install| Commande | Description |
|---|---|
npm start |
Dev server sur http://localhost:4200 |
npm run build |
Build production optimisé |
npm test |
Tests unitaires en mode watch |
npm run test:coverage |
Tests avec rapport de couverture |
npm run lint |
Vérification ESLint |
npm run lint:fix |
Auto-fix des erreurs ESLint |
npm run format |
Formatage Prettier |
npm run format:check |
Vérification Prettier |
Développement
npm run start:docker:dev
# Accessible sur http://localhost:4200
# Hot-reload activéProduction
npm run start:docker:prod
# Build optimisé avec Nginx
# Accessible sur http://localhost:80- ChromeHeadless pour CI/CD
- Code coverage avec seuils configurables
- Tests unitaires pour tous les composants et services
- Tests des actions, reducers et selectors NgRx
# Pre-commit : lint + format automatique
git commit -m "feat: nouvelle fonctionnalité"
# ✓ Lint staged files
# ✓ Format staged files
# ✓ Commit créé- Lint : ESLint avec règles strictes
- Test : Jasmine/Karma avec coverage upload vers Codecov
- Build : Vérification de la compilation production
- Release : Création automatique de release + Docker image
{
"strict": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"explicit-function-return-type": "error"
}- Complexité : Max 20 (cyclomatique)
- Longueur : Max 120 caractères, 400 lignes par fichier
- Sécurité : no-eval, no-implied-eval, eqeqeq
- Angular : prefer-on-push, prefer-signals, prefer-standalone
| Type | Warning | Error |
|---|---|---|
| Initial bundle | 500 kB | 1 MB |
| Component styles | 4 kB | 8 kB |
URL : https://benjaminbats.fr
Infrastructure :
- Serveur : VPS avec Docker
- Reverse proxy : Nginx avec gzip compression
- CDN : Cloudflare pour cache et protection
- SSL : Certificat géré par Cloudflare
Les images Docker sont automatiquement publiées sur GitHub Container Registry lors des releases
Ce projet est un portfolio personnel destiné à mettre en valeur mes compétences techniques.
Je n'accepte pas de pull requests pour préserver l'authenticité du code.
Cependant, vous êtes bienvenue à :
- ✅ Créer des issues pour signaler des bugs
- ✅ Proposer des suggestions d'amélioration
- ✅ Poser des questions sur l'architecture ou les choix techniques
Projet personnel - Tous droits réservés
Le code source est disponible à des fins de consultation et d'apprentissage uniquement.