From d5ac9879bbbe99f005465a44958755f70424a9b4 Mon Sep 17 00:00:00 2001 From: Sadrinho27 Date: Mon, 29 Sep 2025 23:34:38 +0200 Subject: [PATCH] retrieve the docv code source instead of skeleton --- README.md | 371 ---------- app/dashboard/layout.tsx | 16 +- app/dashboard/page.tsx | 10 +- app/login/page.tsx | 351 +++++++--- components/4nk/AuthModal.tsx | 395 ++++++++--- components/4nk/Iframe.tsx | 42 +- components/modal/Modal.css | 163 ----- components/modal/Modal.tsx | 38 - lib/4nk/EventBus.ts | 52 +- lib/4nk/IframeReference.ts | 18 +- lib/4nk/Loader.tsx | 23 - lib/4nk/MessageBus.ts | 1232 ++++++++++++++------------------- lib/4nk/MockService.ts | 363 ++++++++++ lib/4nk/UserStore.ts | 62 +- lib/4nk/models/Data.ts | 15 - lib/4nk/models/FolderData.ts | 137 ---- lib/4nk/models/ProfileData.ts | 122 ---- lib/4nk/models/Roles.ts | 11 - package-lock.json | 36 +- package.json | 6 +- 20 files changed, 1587 insertions(+), 1876 deletions(-) delete mode 100644 components/modal/Modal.css delete mode 100644 components/modal/Modal.tsx delete mode 100644 lib/4nk/Loader.tsx create mode 100644 lib/4nk/MockService.ts delete mode 100644 lib/4nk/models/Data.ts delete mode 100644 lib/4nk/models/FolderData.ts delete mode 100644 lib/4nk/models/ProfileData.ts delete mode 100644 lib/4nk/models/Roles.ts diff --git a/README.md b/README.md index c5cc353..8b13789 100644 --- a/README.md +++ b/README.md @@ -1,372 +1 @@ -# 🛡️ DocV - GED Souveraine et Sécurisée -> **Une approche révolutionnaire de la gestion documentaire avec sécurité, souveraineté et conformité garanties.** - -[![Version](https://img.shields.io/badge/version-0.1.0-blue.svg)](VERSION) -[![Next.js](https://img.shields.io/badge/Next.js-15.2.4-black.svg)](https://nextjs.org/) -[![TypeScript](https://img.shields.io/badge/TypeScript-5.0-blue.svg)](https://www.typescriptlang.org/) -[![Tailwind CSS](https://img.shields.io/badge/Tailwind%20CSS-4.1.9-38B2AC.svg)](https://tailwindcss.com/) -[![License](https://img.shields.io/badge/license-Private-red.svg)](#license) - -## 📋 Table des Matières - -- [🎯 Vue d'ensemble](#-vue-densemble) -- [✨ Fonctionnalités](#-fonctionnalités) -- [🚀 Installation Rapide](#-installation-rapide) -- [⚙️ Configuration](#️-configuration) -- [🔧 Commandes de Développement](#-commandes-de-développement) -- [📚 Documentation](#-documentation) -- [🏗️ Architecture](#️-architecture) -- [🔒 Sécurité](#-sécurité) -- [🤝 Contribution](#-contribution) -- [📞 Support](#-support) - -## 🎯 Vue d'ensemble - -**DocV** est une plateforme de gestion documentaire (GED) révolutionnaire qui combine : - -- **🔐 Authentification cryptographique** sans mots de passe -- **🤖 IA embarquée** pour l'OCR et la classification -- **🌐 Architecture souveraine** sans dépendance cloud -- **⚡ Interface conversationnelle** pour le suivi des dossiers -- **🔗 Ancrage blockchain** pour la traçabilité - -### 🎯 Cas d'Usage Principaux - -- **Entreprises** : Gestion documentaire sécurisée -- **Notaires** : Échanges documentaires via lecoffre.io -- **Secteur public** : Conformité et souveraineté des données -- **Éditeurs** : Intégration marque blanche - -## ✨ Fonctionnalités - -### 🔑 Authentification Ultra-Simplifiée -- ✅ Aucun mot de passe requis -- ✅ Aucun OTP ou code SMS -- ✅ Aucune application mobile -- ✅ Identité auto-générée et auto-portée - -### 🤖 Intelligence Artificielle Locale -- ✅ OCR automatique des documents -- ✅ Classification intelligente -- ✅ Extraction de données -- ✅ Interface conversationnelle -- ✅ Traitements 100% locaux - -### 🛡️ Sécurité de Bout en Bout -- ✅ Chiffrement natif -- ✅ Aucune interface admin exposée -- ✅ Aucun serveur d'identité -- ✅ Aucune dépendance cloud -- ✅ Conformité RGPD, ISO 27001, SecNumCloud - -### 🌐 Architecture Souveraine -- ✅ Déploiement local -- ✅ Migration automatisée -- ✅ Compatible bases existantes -- ✅ APIs souveraines -- ✅ Accompagnement personnalisé - -## 🚀 Installation Rapide - -### 📋 Prérequis - -- **Node.js** : Version 18.0+ (recommandé 20.x) -- **npm** ou **pnpm** : Gestionnaire de paquets -- **Git** : Pour le clonage du repository - -### 1️⃣ Cloner le Repository - -```bash -# Cloner le projet -git clone -cd docv - -# Installer les dépendances -npm install -# ou -pnpm install -``` - -### 2️⃣ Configuration d'Environnement - -```bash -# Créer le fichier d'environnement -cp .env.example .env.local - -# Éditer les variables d'environnement -nano .env.local -``` - -**Variables essentielles :** -```env -# Configuration de base -NEXT_PUBLIC_APP_NAME=DocV -NEXT_PUBLIC_APP_VERSION=0.1.0 - -# Base de données (si applicable) -DATABASE_URL=your_database_url - -# Authentification -NEXTAUTH_SECRET=your_secret_key -NEXTAUTH_URL=http://localhost:3000 - -# Services externes (optionnels) -EMAIL_SERVICE_API_KEY=your_email_api_key -``` - -### 3️⃣ Démarrage en Mode Développement - -```bash -# Démarrer le serveur de développement -npm run dev -# ou -pnpm dev - -# L'application sera disponible sur http://localhost:3000 -``` - -## ⚙️ Configuration - -### 🎨 Configuration de l'Interface - -Le projet utilise **Tailwind CSS** avec des composants **Radix UI** pour une interface moderne et accessible. - -```bash -# Fichier de configuration Tailwind -tailwind.config.js - -# Composants UI personnalisés -components/ui/ -``` - -### 🔧 Configuration TypeScript - -```bash -# Configuration TypeScript -tsconfig.json - -# Types personnalisés -types/ -``` - -### 📱 Configuration Responsive - -L'interface s'adapte automatiquement aux différentes tailles d'écran : -- 📱 Mobile (< 768px) -- 📟 Tablet (768px - 1024px) -- 💻 Desktop (> 1024px) - -## 🔧 Commandes de Développement - -### 🚀 Commandes Principales - -```bash -# Développement -npm run dev # Serveur de développement (port 3000) -npm run build # Build de production -npm run start # Serveur de production -npm run lint # Vérification du code - -# Tests (si configurés) -npm run test # Tests unitaires -npm run test:watch # Tests en mode watch -npm run test:coverage # Tests avec couverture - -# Maintenance -npm run clean # Nettoyer les fichiers temporaires -npm run type-check # Vérification TypeScript -``` - -### 🛠️ Commandes de Maintenance - -```bash -# Mise à jour des dépendances -npm update # Mise à jour des paquets -npm audit # Audit de sécurité -npm audit fix # Correction automatique - -# Gestion des dépendances -npm install # Installer un paquet -npm uninstall # Désinstaller un paquet -npm list # Lister les paquets installés -``` - -### 📦 Commandes de Build - -```bash -# Build de production -npm run build - -# Analyse du bundle -npm run analyze # (si configuré) - -# Build statique -npm run export # (si configuré) -``` - -### 🔍 Commandes de Debug - -```bash -# Logs détaillés -DEBUG=* npm run dev - -# Profiling -npm run dev -- --profile - -# Inspection du bundle -npm run build -- --debug -``` - -## 📚 Documentation - -### 📖 Guides Disponibles - -- **[Installation](docs/INSTALLATION.md)** - Guide d'installation complet -- **[Configuration](docs/CONFIGURATION.md)** - Configuration avancée -- **[Architecture](docs/ARCHITECTURE.md)** - Architecture technique -- **[API](docs/API.md)** - Documentation des APIs -- **[Sécurité](docs/SECURITY_AUDIT.md)** - Audit de sécurité -- **[Utilisation](docs/USAGE.md)** - Guide d'utilisation - -### 🔗 Ressources Externes - -- [Next.js Documentation](https://nextjs.org/docs) -- [Tailwind CSS](https://tailwindcss.com/docs) -- [Radix UI](https://www.radix-ui.com/docs) -- [TypeScript Handbook](https://www.typescriptlang.org/docs/) - -## 🏗️ Architecture - -### 📁 Structure du Projet - -``` -docv/ -├── app/ # Pages et routes Next.js 13+ -│ ├── dashboard/ # Interface utilisateur -│ ├── login/ # Authentification -│ ├── formation/ # Module formation -│ └── contact/ # Contact -├── components/ # Composants réutilisables -│ ├── ui/ # Composants UI de base -│ └── 4nk/ # Composants spécifiques 4NK -├── lib/ # Utilitaires et logique métier -│ ├── 4nk/ # Modules 4NK -│ └── utils.ts # Fonctions utilitaires -├── public/ # Assets statiques -├── styles/ # Styles globaux -└── docs/ # Documentation -``` - -### 🔧 Technologies Utilisées - -| Technologie | Version | Description | -|-------------|---------|-------------| -| **Next.js** | 15.2.4 | Framework React full-stack | -| **React** | 19.1.1 | Bibliothèque UI | -| **TypeScript** | 5.0+ | Typage statique | -| **Tailwind CSS** | 4.1.9 | Framework CSS | -| **Radix UI** | Latest | Composants accessibles | -| **Lucide React** | 0.454.0 | Icônes | -| **Zod** | 3.25.67 | Validation de schémas | - -### 🌐 Architecture de Sécurité - -```mermaid -graph TB - A[Client] --> B[Next.js App] - B --> C[Authentification Cryptographique] - C --> D[Base de Données Locale] - D --> E[Chiffrement Bout en Bout] - E --> F[Ancrage Blockchain] - - G[IA Locale] --> H[Traitement OCR] - H --> I[Classification] - I --> J[Extraction de Données] -``` - -## 🔒 Sécurité - -### 🛡️ Mesures de Sécurité Implémentées - -- ✅ **Authentification sans mot de passe** - Clés cryptographiques locales -- ✅ **Chiffrement bout en bout** - Données protégées en transit et au repos -- ✅ **Aucune interface admin** - Pas d'accès privilégié exposé -- ✅ **Conformité réglementaire** - RGPD, ISO 27001, SecNumCloud -- ✅ **Audit de sécurité** - Voir [SECURITY_AUDIT.md](docs/SECURITY_AUDIT.md) - -### 🔐 Bonnes Pratiques - -1. **Variables d'environnement** - Jamais de secrets en dur -2. **Validation des données** - Schémas Zod pour toutes les entrées -3. **HTTPS obligatoire** - En production uniquement -4. **Audit régulier** - `npm audit` avant chaque déploiement - -## 🤝 Contribution - -### 🚀 Comment Contribuer - -1. **Fork** le repository -2. **Créer** une branche feature (`git checkout -b feature/amazing-feature`) -3. **Commit** vos changements (`git commit -m 'Add amazing feature'`) -4. **Push** vers la branche (`git push origin feature/amazing-feature`) -5. **Ouvrir** une Pull Request - -### 📝 Standards de Code - -- **TypeScript** strict activé -- **ESLint** pour la qualité du code -- **Prettier** pour le formatage -- **Conventional Commits** pour les messages - -### 🧪 Tests - -```bash -# Avant de contribuer, assurez-vous que : -npm run lint # ✅ Pas d'erreurs ESLint -npm run type-check # ✅ Pas d'erreurs TypeScript -npm run build # ✅ Build réussi -``` - -## 📞 Support - -### 🆘 Obtenir de l'Aide - -- **📧 Email** : contact@docv.fr -- **📚 Documentation** : [docs/](docs/) -- **🐛 Issues** : [GitHub Issues](https://github.com/your-org/docv/issues) -- **💬 Discussions** : [GitHub Discussions](https://github.com/your-org/docv/discussions) - -### 🏢 Entreprise - -**4NK** - Pionnier du Web 5.0 -- 🏢 Solutions de souveraineté -- 🔒 Sécurité de bout en bout -- 🌐 Architecture distribuée - -### 📋 Checklist de Support - -Avant de demander de l'aide, vérifiez : - -- [ ] Version de Node.js compatible (18.0+) -- [ ] Dépendances installées (`npm install`) -- [ ] Variables d'environnement configurées -- [ ] Logs d'erreur consultés -- [ ] Documentation parcourue - ---- - -## 📄 Licence - -Ce projet est propriétaire et confidentiel. Tous droits réservés à **4NK**. - ---- - -
- -**🛡️ DocV - Sécurisez votre entreprise avec la GED simple et souveraine** - -[![4NK](https://img.shields.io/badge/By-4NK-blue.svg)](https://4nkweb.com) -[![Contact](https://img.shields.io/badge/Contact-contact@docv.fr-green.svg)](mailto:contact@docv.fr) - -
diff --git a/app/dashboard/layout.tsx b/app/dashboard/layout.tsx index 559e8a5..d38129d 100644 --- a/app/dashboard/layout.tsx +++ b/app/dashboard/layout.tsx @@ -24,9 +24,9 @@ import { ChevronRight, Home, } from "lucide-react" -import AuthModal from "@/components/4nk/AuthModal" -import MessageBus from "@/lib/4nk/MessageBus" -import UserStore from "@/lib/4nk/UserStore" +import { AuthModal } from "@/components/4nk/AuthModal" +import { MessageBus } from "@/lib/4nk/MessageBus" +import { UserStore } from "@/lib/4nk/UserStore" // DebugInfo supprimé export default function DashboardLayout({ children }: { children: React.ReactNode }) { @@ -40,7 +40,7 @@ export default function DashboardLayout({ children }: { children: React.ReactNod const router = useRouter() const pathname = usePathname() - const iframeUrl = process.env.NEXT_PUBLIC_4NK_IFRAME_URL || "https://dev3.4nkweb.com" + const iframeUrl = process.env.NEXT_PUBLIC_4NK_IFRAME_URL || "https://dev.4nk.io" const navigation = [ { name: "Tableau de bord", href: "/dashboard", icon: LayoutDashboard }, @@ -61,10 +61,10 @@ export default function DashboardLayout({ children }: { children: React.ReactNod if (accessToken) { // Vérifier si on est en mode mock - // const mockMode = messageBus.isInMockMode() - // setIsMockMode(mockMode) + const mockMode = messageBus.isInMockMode() + setIsMockMode(mockMode) - if (true) { + if (mockMode) { console.log("🎭 Dashboard en mode mock") setIsAuthenticated(true) setUserInfo({ @@ -117,7 +117,7 @@ export default function DashboardLayout({ children }: { children: React.ReactNod const messageBus = MessageBus.getInstance(iframeUrl) userStore.disconnect() - // messageBus.disableMockMode() + messageBus.disableMockMode() // Afficher un message de confirmation avec options setShowLogoutConfirm(true) diff --git a/app/dashboard/page.tsx b/app/dashboard/page.tsx index 6ce6fd0..bc6c578 100644 --- a/app/dashboard/page.tsx +++ b/app/dashboard/page.tsx @@ -26,7 +26,7 @@ import { HardDrive, X, } from "lucide-react" -import MessageBus from "@/lib/4nk/MessageBus" +import { MessageBus } from "@/lib/4nk/MessageBus" import Link from "next/link" export default function DashboardPage() { @@ -54,13 +54,13 @@ export default function DashboardPage() { const [notifications, setNotifications] = useState([]) useEffect(() => { - const iframeUrl = process.env.NEXT_PUBLIC_4NK_IFRAME_URL || "https://dev3.4nkweb.com" + const iframeUrl = process.env.NEXT_PUBLIC_4NK_IFRAME_URL || "https://dev.4nk.io" const messageBus = MessageBus.getInstance(iframeUrl) - // const mockMode = messageBus.isInMockMode() - // setIsMockMode(mockMode) + const mockMode = messageBus.isInMockMode() + setIsMockMode(mockMode) // Simuler le chargement des données - if (true) { + if (mockMode) { setStats({ totalDocuments: 1247, totalFolders: 89, diff --git a/app/login/page.tsx b/app/login/page.tsx index 14267c6..19c9f8b 100644 --- a/app/login/page.tsx +++ b/app/login/page.tsx @@ -6,78 +6,134 @@ import { useState } from "react" import { useRouter } from "next/navigation" import Link from "next/link" import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card" +import { Input } from "@/components/ui/input" +import { Label } from "@/components/ui/label" import { Button } from "@/components/ui/button" import { Badge } from "@/components/ui/badge" import { Shield, + Building2, + TestTube, ArrowLeft, Home, + Key, CheckCircle, + AlertTriangle, + Eye, + EyeOff, } from "lucide-react" -import AuthModal from "@/components/4nk/AuthModal" -import MessageBus from "@/lib/4nk/MessageBus" -import UserStore from "@/lib/4nk/UserStore" +import { AuthModal } from "@/components/4nk/AuthModal" +import { MessageBus } from "@/lib/4nk/MessageBus" +import { MockService } from "@/lib/4nk/MockService" +import { UserStore } from "@/lib/4nk/UserStore" export default function LoginPage() { + const [companyId, setCompanyId] = useState("") const [isAuthModalOpen, setIsAuthModalOpen] = useState(false) const [isLoading, setIsLoading] = useState(false) - const [isConnected, setIsConnected] = useState(false) - const [error, setError] = useState(null) + const [showPairingSection, setShowPairingSection] = useState(false) + const [pairingWords, setPairingWords] = useState(["", "", "", ""]) + const [pairingError, setPairingError] = useState("") + const [pairingSuccess, setPairingSuccess] = useState(false) const router = useRouter() + const [showPairingInput, setShowPairingInput] = useState(false) - const iframeUrl = process.env.NEXT_PUBLIC_4NK_IFRAME_URL || "https://dev3.4nkweb.com" + const iframeUrl = process.env.NEXT_PUBLIC_4NK_IFRAME_URL || "https://dev.4nk.io" - // Vérifier l'état de connexion au chargement - useState(() => { - const userStore = UserStore.getInstance() - setIsConnected(userStore.isConnected()) - }) + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault() - const handleLogin = () => { - setIsAuthModalOpen(true) - setError(null) - } + if (!companyId.trim()) { + return + } + + setIsLoading(true) - const handleAuthSuccess = async () => { - setIsAuthModalOpen(false) - setIsConnected(true) - try { - // Récupérer l'ID d'appairage après connexion - const messageBus = MessageBus.getInstance(iframeUrl) - await messageBus.isReady() - const pairingId = await messageBus.getUserPairingId() - - console.log("✅ Authentification 4NK réussie, ID d'appairage:", pairingId) - - // Redirection vers le dashboard - router.push("/dashboard") - } catch (err) { - console.error("Erreur lors de la récupération de l'ID d'appairage:", err) - // Redirection quand même vers le dashboard - router.push("/dashboard") + // Si l'identifiant est "1234", activer le mode mock directement + if (companyId === "1234") { + console.log("🎭 Activation du mode mock avec l'identifiant:", companyId) + + const messageBus = MessageBus.getInstance(iframeUrl) + const mockService = MockService.getInstance() + const userStore = UserStore.getInstance() + + // Activer le mode mock + messageBus.enableMockMode() + + // Authentification mock + const authResult = await mockService.mockAuthentication(companyId) + if (!authResult) { + throw new Error("Échec de l'authentification de démonstration") + } + + // Simuler la récupération des tokens + const tokens = await mockService.mockRequestLink() + userStore.connect(tokens.accessToken, tokens.refreshToken) + + // Simuler la récupération de l'ID d'appairage + const pairingId = await mockService.mockGetUserPairingId() + userStore.pair(pairingId) + + console.log("✅ Mode mock activé avec succès") + + // Redirection directe vers le dashboard + router.push("/dashboard") + } else { + // Mode normal - ouvrir la modal d'authentification 4NK + setIsAuthModalOpen(true) + } + } catch (error) { + console.error("Erreur lors de l'activation du mode mock:", error) + // En cas d'erreur, ouvrir quand même la modal d'authentification + setIsAuthModalOpen(true) + } finally { + setIsLoading(false) } } - const handleAuthError = (errorMessage: string) => { - setError(errorMessage) + const handleAuthSuccess = () => { setIsAuthModalOpen(false) + router.push("/dashboard") } - // Si déjà connecté, rediriger vers le dashboard - if (isConnected) { - router.push("/dashboard") - return ( -
- - - -

Déjà connecté

-

Redirection vers le dashboard...

-
-
-
- ) + const handlePairingSubmit = async (e: React.FormEvent) => { + e.preventDefault() + setPairingError("") + + // Vérifier que tous les mots sont remplis + if (pairingWords.some((word) => !word.trim())) { + setPairingError("Veuillez saisir les 4 mots de pairing") + return + } + + // Simuler la vérification des mots de pairing + const validWords = ["alpha", "bravo", "charlie", "delta"] + const isValid = pairingWords.every((word, index) => word.toLowerCase().trim() === validWords[index]) + + if (isValid) { + setPairingSuccess(true) + setTimeout(() => { + // Simuler l'ajout de l'appareil et la connexion + const userStore = UserStore.getInstance() + const mockService = MockService.getInstance() + + // Simuler des tokens pour le pairing + userStore.connect("paired_access_token", "paired_refresh_token") + userStore.pair("paired_device_id") + + router.push("/dashboard") + }, 2000) + } else { + setPairingError("Mots de pairing incorrects. Vérifiez les mots saisis sur votre autre appareil.") + } + } + + const handlePairingWordChange = (index: number, value: string) => { + const newWords = [...pairingWords] + newWords[index] = value + setPairingWords(newWords) + setPairingError("") } return ( @@ -103,57 +159,160 @@ export default function LoginPage() {

Gestion électronique de documents sécurisée

- {/* Carte de connexion 4NK */} - - - - - Connexion sécurisée 4NK - - - Authentification cryptographique sans mot de passe - - - - {/* Description de la connexion 4NK */} -
-

🔐 Authentification 4NK

-
    -
  • • Aucun mot de passe requis
  • -
  • • Identité cryptographique sécurisée
  • -
  • • Chiffrement bout en bout
  • -
  • • Protection par blockchain
  • -
-
+ {/* Navigation entre connexion et pairing */} +
+ + +
- {/* Affichage des erreurs */} - {error && ( -
-

Erreur de connexion :

-

{error}

-
- )} + {!showPairingSection ? ( + /* Carte de connexion */ + + + + + Identification d'entreprise + + Connectez-vous avec votre identifiant unique sécurisé par 4NK + + +
+
+ + setCompanyId(e.target.value)} + required + className="w-full" + /> +
- {/* Bouton de connexion */} - + {/* Info mode démonstration */} +
+
+ +
+

Mode démonstration

+

+ Utilisez l'identifiant "1234" pour accéder directement aux écrans de + démonstration avec des données simulées. +

+
+
+
- {/* Informations sur l'iframe */} -
-

- URL d'authentification :
- {iframeUrl} -

-
- - + +
+
+
+ ) : ( + /* Carte de pairing */ + + + + + Pairing d'appareil + + Ajoutez cet appareil à votre compte existant + + + {!pairingSuccess ? ( +
+
+
+ + +
+

Saisissez les 4 mots affichés sur votre autre appareil

+
+ {pairingWords.map((word, index) => ( + handlePairingWordChange(index, e.target.value)} + className="text-center font-mono select-none" + style={{ userSelect: "none", WebkitUserSelect: "none" }} + onContextMenu={(e) => e.preventDefault()} + onCopy={(e) => e.preventDefault()} + onCut={(e) => e.preventDefault()} + onPaste={(e) => e.preventDefault()} + autoComplete="off" + spellCheck={false} + required + /> + ))} +
+
+ + {pairingError && ( +
+
+ +

{pairingError}

+
+
+ )} + +
+
+

Instructions :

+
    +
  1. 1. Ouvrez DocV sur votre appareil principal
  2. +
  3. 2. Allez dans Paramètres → Sécurité
  4. +
  5. 3. Cliquez sur "Ajouter un appareil"
  6. +
  7. 4. Saisissez les 4 mots affichés ici
  8. +
+
+
+ + +
+ ) : ( +
+ +

Pairing réussi !

+

Cet appareil a été ajouté à votre compte avec succès.

+
Redirection vers le dashboard...
+
+ )} +
+
+ )} {/* Badges de sécurité */}
diff --git a/components/4nk/AuthModal.tsx b/components/4nk/AuthModal.tsx index 6d923ca..57ee619 100644 --- a/components/4nk/AuthModal.tsx +++ b/components/4nk/AuthModal.tsx @@ -1,101 +1,330 @@ -import { useState, useEffect, memo } from 'react'; -import Iframe from './Iframe'; -import MessageBus from '@/lib/4nk/MessageBus'; -import Loader from '@/lib/4nk/Loader'; -import Modal from '../modal/Modal'; +"use client" + +import { useState, useEffect, memo } from "react" +import { Button } from "@/components/ui/button" +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card" +import { Shield, CheckCircle, Loader2, AlertCircle, RefreshCw } from "lucide-react" +import { Iframe } from "./Iframe" +import { MessageBus } from "@/lib/4nk/MessageBus" +import { IframeReference } from "@/lib/4nk/IframeReference" interface AuthModalProps { - isOpen: boolean; - onConnect: () => void; - onClose: () => void; - iframeUrl: string; + isOpen: boolean + onConnect: () => void + onClose: () => void + iframeUrl: string } -function AuthModal({ isOpen, onConnect, onClose, iframeUrl }: AuthModalProps) { - const [isIframeReady, setIsIframeReady] = useState(false); - const [showIframe, setShowIframe] = useState(false); - const [authSuccess, setAuthSuccess] = useState(false); +export const AuthModal = memo(function AuthModal({ isOpen, onConnect, onClose, iframeUrl }: AuthModalProps) { + const [isIframeReady, setIsIframeReady] = useState(false) + const [showIframe, setShowIframe] = useState(false) + const [authSuccess, setAuthSuccess] = useState(false) + const [isLoading, setIsLoading] = useState(false) + const [error, setError] = useState(null) + const [loadingStep, setLoadingStep] = useState("") + const [retryCount, setRetryCount] = useState(0) + const [iframeLoaded, setIframeLoaded] = useState(false) - useEffect(() => { - MessageBus.getInstance(iframeUrl).isReady().then(() => { - setIsIframeReady(true); - }); - }, [iframeUrl]); + const maxRetries = 3 useEffect(() => { if (!isOpen) { - setShowIframe(false); - setIsIframeReady(false); - setAuthSuccess(false); + // Reset des états à la fermeture + setIsIframeReady(false) + setShowIframe(false) + setAuthSuccess(false) + setIsLoading(false) + setError(null) + setLoadingStep("") + setRetryCount(0) + setIframeLoaded(false) + return } - }, [isOpen]); - useEffect(() => { - if (isIframeReady && !showIframe) { - setShowIframe(true); + initAuth() + }, [isOpen, iframeUrl, retryCount]) - MessageBus.getInstance(iframeUrl).requestLink().then(() => { - setAuthSuccess(true); + const initAuth = async () => { + try { + setIsLoading(true) + setError(null) + setLoadingStep("Initialisation...") - setTimeout(() => onConnect(), 500); - }).catch((_error: string) => { - setShowIframe(false); - setIsIframeReady(false); - setAuthSuccess(false); + console.log("🔗 Initialisation authentification 4NK avec:", iframeUrl) + console.log("🔄 Tentative:", retryCount + 1, "/", maxRetries + 1) - onClose(); - }); + // Étape 1: Attendre que l'iframe soit disponible dans le DOM + setLoadingStep("Chargement de l'iframe...") + let attempts = 0 + const maxAttempts = 40 // 20 secondes + + while (attempts < maxAttempts) { + const iframe = IframeReference.getIframe() + if (iframe && iframe.contentWindow) { + console.log("✅ Iframe disponible après", attempts * 500, "ms") + break + } + await new Promise((resolve) => setTimeout(resolve, 500)) + attempts++ + } + + if (attempts >= maxAttempts) { + throw new Error("Iframe 4NK non disponible dans le DOM après 20 secondes") + } + + // Étape 2: Attendre que l'iframe soit complètement chargée + setLoadingStep("Attente du chargement complet...") + await waitForIframeLoad() + + setShowIframe(true) + + // Étape 3: Attendre le message LISTENING de l'iframe + setLoadingStep("Attente du signal LISTENING...") + const messageBus = MessageBus.getInstance(iframeUrl) + + console.log("⏳ Attente du message LISTENING de l'iframe...") + await messageBus.isReady() + console.log("✅ Iframe prête et en écoute") + + setIsIframeReady(true) + + // Étape 4: Demander l'authentification (REQUEST_LINK) + setLoadingStep("Demande d'authentification...") + console.log("🔐 Envoi REQUEST_LINK...") + await messageBus.requestLink() + console.log("✅ LINK_ACCEPTED reçu, tokens stockés") + + // Étape 5: Récupérer l'ID d'appairage + setLoadingStep("Récupération de l'identité...") + console.log("🆔 Récupération de l'ID d'appairage...") + await messageBus.getUserPairingId() + console.log("✅ ID d'appairage récupéré") + + setAuthSuccess(true) + + // Délai avant de déclencher onConnect + setTimeout(() => { + onConnect() + }, 500) + } catch (err) { + console.error("❌ Authentication error:", err) + const errorMessage = err instanceof Error ? err.message : "Erreur d'authentification" + + // Messages d'erreur plus spécifiques selon le protocole 4NK + if (errorMessage.includes("LINK_ACCEPTED")) { + setError("Erreur d'authentification : réponse inattendue du serveur 4NK") + } else if (errorMessage.includes("Tokens manquants")) { + setError("Erreur : les tokens d'authentification n'ont pas été reçus") + } else if (errorMessage.includes("LISTENING")) { + setError("L'iframe 4NK n'est pas en écoute. Vérifiez l'URL de l'iframe.") + } else if (errorMessage.includes("Timeout")) { + setError("Timeout : L'iframe 4NK ne répond pas. Vérifiez votre connexion.") + } else if (errorMessage.includes("origin")) { + setError("Erreur de configuration : les domaines ne correspondent pas") + } else { + setError(errorMessage) + } + + setIsIframeReady(false) + setShowIframe(false) + } finally { + setIsLoading(false) + setLoadingStep("") } - }, [isIframeReady, showIframe]); + } + + const waitForIframeLoad = (): Promise => { + return new Promise((resolve, reject) => { + const iframe = IframeReference.getIframe() + if (!iframe) { + reject(new Error("Iframe not found")) + return + } + + // Si l'iframe est déjà chargée + if (iframeLoaded) { + resolve() + return + } + + const timeout = setTimeout(() => { + cleanup() + reject(new Error("Timeout: L'iframe n'a pas fini de se charger")) + }, 30000) // 30 secondes pour le chargement + + const cleanup = () => { + clearTimeout(timeout) + iframe.removeEventListener("load", onLoad) + iframe.removeEventListener("error", onError) + } + + const onLoad = () => { + console.log("✅ Iframe loaded successfully") + setIframeLoaded(true) + cleanup() + // Attendre un peu plus pour que le contenu soit prêt + setTimeout(resolve, 2000) + } + + const onError = () => { + console.error("❌ Iframe failed to load") + cleanup() + reject(new Error("Erreur de chargement de l'iframe")) + } + + iframe.addEventListener("load", onLoad) + iframe.addEventListener("error", onError) + + // Si l'iframe semble déjà chargée + if (iframe.contentDocument?.readyState === "complete") { + onLoad() + } + }) + } + + const handleRetry = () => { + if (retryCount < maxRetries) { + setRetryCount((prev) => prev + 1) + setError(null) + } else { + setError("Nombre maximum de tentatives atteint. Veuillez vérifier votre connexion et réessayer plus tard.") + } + } + + const handleForceRetry = () => { + setRetryCount(0) + setError(null) + setIframeLoaded(false) + // Forcer le rechargement de l'iframe + const iframe = IframeReference.getIframe() + if (iframe) { + iframe.src = iframe.src + } + } + + if (!isOpen) return null return ( - - {!isIframeReady && ( -
- -
Chargement de l'authentification...
-
- )} - {authSuccess ? ( -
-
- Authentification réussie ! +
+ + +
+
-
- ) : ( -
-