feat: implement complete React PWA client with LiveKit integration

Client React complet avec intégration LiveKit et interface PTT professionnelle :

Infrastructure :
- Configuration Vite avec plugin PWA (Service Worker auto-généré)
- Proxy API vers serveur backend
- Build optimisé et PWA manifest

Composants UI :
- App.jsx : écran connexion + interface principale PTT
- PTTButton : bouton push-to-talk avec gestion touch/mouse events
- UserList : liste participants temps réel avec indicateurs
- AudioIndicator : VU-mètre avec visualisation niveau audio

Fonctionnalités WebRTC :
- Hook useLiveKit : connexion room, publish/subscribe, events
- Gestion micro avec mute/unmute (mode PTT)
- Auto-play audio participants distants
- Analyseur audio pour VU-mètre
- Feedback haptique (vibrations)

Design :
- Mode sombre par défaut
- Responsive mobile-first
- Animations fluides et accessibles
- Support paysage mobile

Phase 1.4 complétée : Client PWA opérationnel

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2026-05-21 14:48:18 +02:00
parent 5e74f0dcdf
commit 0640a9f0b6
15 changed files with 1568 additions and 32 deletions
+44
View File
@@ -0,0 +1,44 @@
import './AudioIndicator.css';
/**
* VU-mètre simple pour visualiser le niveau audio
*/
export default function AudioIndicator({ level, isTalking }) {
// Normaliser niveau 0-100
const normalizedLevel = Math.min(100, Math.max(0, level));
return (
<div className="audio-indicator-container">
<div className="audio-indicator-label">
<span>{isTalking ? 'Votre micro' : 'Audio entrant'}</span>
<span className="audio-level-value">{Math.round(normalizedLevel)}%</span>
</div>
<div className="audio-indicator-bar">
<div
className={`audio-indicator-fill ${isTalking ? 'talking' : ''}`}
style={{ width: `${normalizedLevel}%` }}
/>
</div>
{/* Bars VU-mètre style */}
<div className="audio-bars">
{[...Array(20)].map((_, i) => {
const threshold = (i + 1) * 5;
const isActive = normalizedLevel >= threshold;
const isWarning = i >= 15; // > 75%
const isDanger = i >= 18; // > 90%
return (
<div
key={i}
className={`audio-bar ${isActive ? 'active' : ''} ${
isActive && isDanger ? 'danger' : isActive && isWarning ? 'warning' : ''
}`}
/>
);
})}
</div>
</div>
);
}