n8n anonyme
This commit is contained in:
42
app/components/MarkdownModal.tsx
Normal file
42
app/components/MarkdownModal.tsx
Normal file
@@ -0,0 +1,42 @@
|
||||
// components/MarkdownModal.tsx
|
||||
|
||||
import { X } from "lucide-react";
|
||||
|
||||
interface HtmlModalProps {
|
||||
content: string | null;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
export default function MarkdownModal({ content, onClose }: HtmlModalProps) {
|
||||
if (!content) return null;
|
||||
|
||||
return (
|
||||
<div
|
||||
className="fixed inset-0 bg-black bg-opacity-70 z-50 flex justify-center items-center p-4"
|
||||
onClick={onClose}
|
||||
>
|
||||
<div
|
||||
className="bg-[#061717] border-4 border-white shadow-[10px_10px_0_0_black] w-full max-w-3xl h-[80vh] flex flex-col relative"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<header className="flex items-center justify-between p-4 border-b-4 border-white">
|
||||
<h3 className="text-lg font-black text-white uppercase">
|
||||
Aperçu du Document
|
||||
</h3>
|
||||
<button
|
||||
onClick={onClose}
|
||||
className="p-2 bg-[#F7AB6E] text-white border-2 border-white shadow-[3px_3px_0_0_black] hover:shadow-[1px_1px_0_0_black] active:shadow-none active:translate-x-0.5 active:translate-y-0.5"
|
||||
>
|
||||
<X className="h-5 w-5" />
|
||||
</button>
|
||||
</header>
|
||||
|
||||
{/* Ce conteneur stylise le HTML propre qu'il reçoit */}
|
||||
<div
|
||||
className="preview-content p-6 overflow-y-auto w-full h-full"
|
||||
dangerouslySetInnerHTML={{ __html: content }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
193
app/globals.css
193
app/globals.css
@@ -2,121 +2,96 @@
|
||||
|
||||
@import "tailwindcss";
|
||||
|
||||
@theme inline {
|
||||
--color-background: var(--background);
|
||||
--color-foreground: var(--foreground);
|
||||
--font-sans: var(--font-geist-sans);
|
||||
--font-mono: var(--font-geist-mono);
|
||||
--color-sidebar-ring: var(--sidebar-ring);
|
||||
--color-sidebar-border: var(--sidebar-border);
|
||||
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
|
||||
--color-sidebar-accent: var(--sidebar-accent);
|
||||
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
|
||||
--color-sidebar-primary: var(--sidebar-primary);
|
||||
--color-sidebar-foreground: var(--sidebar-foreground);
|
||||
--color-sidebar: var(--sidebar);
|
||||
--color-chart-5: var(--chart-5);
|
||||
--color-chart-4: var(--chart-4);
|
||||
--color-chart-3: var(--chart-3);
|
||||
--color-chart-2: var(--chart-2);
|
||||
--color-chart-1: var(--chart-1);
|
||||
--color-ring: var(--ring);
|
||||
--color-input: var(--input);
|
||||
--color-border: var(--border);
|
||||
--color-destructive: var(--destructive);
|
||||
--color-accent-foreground: var(--accent-foreground);
|
||||
--color-accent: var(--accent);
|
||||
--color-muted-foreground: var(--muted-foreground);
|
||||
--color-muted: var(--muted);
|
||||
--color-secondary-foreground: var(--secondary-foreground);
|
||||
--color-secondary: var(--secondary);
|
||||
--color-primary-foreground: var(--primary-foreground);
|
||||
--color-primary: var(--primary);
|
||||
--color-popover-foreground: var(--popover-foreground);
|
||||
--color-popover: var(--popover);
|
||||
--color-card-foreground: var(--card-foreground);
|
||||
--color-card: var(--card);
|
||||
--radius-sm: calc(var(--radius) - 4px);
|
||||
--radius-md: calc(var(--radius) - 2px);
|
||||
--radius-lg: var(--radius);
|
||||
--radius-xl: calc(var(--radius) + 4px);
|
||||
/* app/globals.css */
|
||||
/* ======================================================= */
|
||||
/* == STYLES MANUELS POUR L'APERÇU DU DOCUMENT DANS LA MODALE == */
|
||||
/* ======================================================= */
|
||||
|
||||
/* Conteneur principal qui définit les styles par défaut */
|
||||
.preview-content {
|
||||
color: #d1d5db; /* Texte gris clair par défaut */
|
||||
font-size: 16px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: var(--font-sans);
|
||||
/* --- TITRES --- */
|
||||
.preview-content h1,
|
||||
.preview-content h2,
|
||||
.preview-content h3,
|
||||
.preview-content h4 {
|
||||
color: #f7ab6e; /* Couleur orange pour tous les titres */
|
||||
font-weight: 700; /* Gras */
|
||||
margin-top: 1.5em;
|
||||
margin-bottom: 0.8em;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
:root {
|
||||
--radius: 0.625rem;
|
||||
--background: oklch(1 0 0);
|
||||
--foreground: oklch(0.145 0 0);
|
||||
--card: oklch(1 0 0);
|
||||
--card-foreground: oklch(0.145 0 0);
|
||||
--popover: oklch(1 0 0);
|
||||
--popover-foreground: oklch(0.145 0 0);
|
||||
--primary: oklch(0.205 0 0);
|
||||
--muted: oklch(0.97 0 0);
|
||||
--muted-foreground: oklch(0.556 0 0);
|
||||
--accent: oklch(0.97 0 0);
|
||||
--accent-foreground: oklch(0.205 0 0);
|
||||
--destructive: oklch(0.577 0.245 27.325);
|
||||
--border: oklch(0.922 0 0);
|
||||
--input: oklch(0.922 0 0);
|
||||
--ring: oklch(0.708 0 0);
|
||||
--chart-1: oklch(0.646 0.222 41.116);
|
||||
--chart-2: oklch(0.6 0.118 184.704);
|
||||
--chart-3: oklch(0.398 0.07 227.392);
|
||||
--chart-4: oklch(0.828 0.189 84.429);
|
||||
--chart-5: oklch(0.769 0.188 70.08);
|
||||
--sidebar: oklch(0.985 0 0);
|
||||
--sidebar-foreground: oklch(0.145 0 0);
|
||||
--sidebar-primary: oklch(0.205 0 0);
|
||||
--sidebar-primary-foreground: oklch(0.985 0 0);
|
||||
--sidebar-accent: oklch(0.97 0 0);
|
||||
--sidebar-accent-foreground: oklch(0.205 0 0);
|
||||
--sidebar-border: oklch(0.922 0 0);
|
||||
--sidebar-ring: oklch(0.708 0 0);
|
||||
.preview-content h1 {
|
||||
font-size: 2.5rem; /* Très grand */
|
||||
}
|
||||
|
||||
.dark {
|
||||
--background: oklch(0.145 0 0);
|
||||
--foreground: oklch(0.985 0 0);
|
||||
--card: oklch(0.205 0 0);
|
||||
--card-foreground: oklch(0.985 0 0);
|
||||
--popover: oklch(0.205 0 0);
|
||||
--popover-foreground: oklch(0.985 0 0);
|
||||
--primary: oklch(0.922 0 0);
|
||||
--primary-foreground: oklch(0.205 0 0);
|
||||
--secondary: oklch(0.269 0 0);
|
||||
--secondary-foreground: oklch(0.985 0 0);
|
||||
--muted: oklch(0.269 0 0);
|
||||
--muted-foreground: oklch(0.708 0 0);
|
||||
--accent: oklch(0.269 0 0);
|
||||
--accent-foreground: oklch(0.985 0 0);
|
||||
--destructive: oklch(0.704 0.191 22.216);
|
||||
--border: oklch(1 0 0 / 10%);
|
||||
--input: oklch(1 0 0 / 15%);
|
||||
--ring: oklch(0.556 0 0);
|
||||
--chart-1: oklch(0.488 0.243 264.376);
|
||||
--chart-2: oklch(0.696 0.17 162.48);
|
||||
--chart-3: oklch(0.769 0.188 70.08);
|
||||
--chart-4: oklch(0.627 0.265 303.9);
|
||||
--chart-5: oklch(0.645 0.246 16.439);
|
||||
--sidebar: oklch(0.205 0 0);
|
||||
--sidebar-foreground: oklch(0.985 0 0);
|
||||
--sidebar-primary: oklch(0.488 0.243 264.376);
|
||||
--sidebar-primary-foreground: oklch(0.985 0 0);
|
||||
--sidebar-accent: oklch(0.269 0 0);
|
||||
--sidebar-accent-foreground: oklch(0.985 0 0);
|
||||
--sidebar-border: oklch(1 0 0 / 10%);
|
||||
--sidebar-ring: oklch(0.556 0 0);
|
||||
.preview-content h2 {
|
||||
font-size: 2rem; /* Grand */
|
||||
padding-bottom: 0.4em;
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.2); /* Ligne de séparation */
|
||||
}
|
||||
|
||||
@layer base {
|
||||
* {
|
||||
@apply border-border outline-ring/50;
|
||||
}
|
||||
body {
|
||||
@apply bg-background text-foreground;
|
||||
}
|
||||
.preview-content h3 {
|
||||
font-size: 1.5rem; /* Moyen */
|
||||
}
|
||||
|
||||
.preview-content h4 {
|
||||
font-size: 1.25rem; /* Petit */
|
||||
}
|
||||
|
||||
/* --- TEXTE --- */
|
||||
.preview-content p {
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
.preview-content strong {
|
||||
color: #ffffff; /* Texte en gras bien blanc pour ressortir */
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
/* --- LISTES À PUCES --- */
|
||||
.preview-content ul {
|
||||
list-style-type: disc; /* Activer les puces */
|
||||
margin-left: 1.5em; /* Indentation */
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
.preview-content li {
|
||||
margin-bottom: 0.5em;
|
||||
padding-left: 0.5em;
|
||||
}
|
||||
|
||||
/* Styliser la couleur de la puce elle-même ! */
|
||||
.preview-content li::marker {
|
||||
color: #f7ab6e;
|
||||
}
|
||||
|
||||
/* --- TABLEAUX --- */
|
||||
.preview-content table {
|
||||
width: 100%;
|
||||
margin-top: 1.5em;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
.preview-content th,
|
||||
.preview-content td {
|
||||
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||
padding: 0.75em 1em;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.preview-content th {
|
||||
background-color: rgba(
|
||||
255,
|
||||
255,
|
||||
255,
|
||||
0.05
|
||||
); /* Fond légèrement différent pour les en-têtes */
|
||||
font-weight: 700;
|
||||
color: #f7ab6e;
|
||||
}
|
||||
|
||||
58
app/page.tsx
58
app/page.tsx
@@ -8,14 +8,19 @@ import { Input } from "@/components/ui/input";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import {
|
||||
Upload, FileText, ShieldCheck, Download, Trash2, AlertCircle, X, Zap, Lock, Shield, Clock,
|
||||
User, AtSign, MapPin, Cake, Home as HomeIcon, Venus, Phone, Building, Fingerprint, CreditCard, Check
|
||||
User, AtSign, MapPin, Cake, Home as HomeIcon, Venus, Phone, Building, Fingerprint, CreditCard, Check,
|
||||
Eye // <-- Icône ajoutée
|
||||
} from "lucide-react";
|
||||
import MarkdownModal from "./components/MarkdownModal"; // <-- Composant de la modale ajouté
|
||||
|
||||
// Interfaces et Données
|
||||
// === Interfaces et Données ===
|
||||
|
||||
// L'interface est mise à jour pour inclure le contenu texte pour l'aperçu
|
||||
interface ProcessedFile {
|
||||
id: string; name: string; status: 'processing' | 'completed' | 'error';
|
||||
timestamp: Date; originalSize?: string; processedSize?: string;
|
||||
piiCount?: number; errorMessage?: string; processedBlob?: Blob;
|
||||
textContent?: string; // <-- Ajouté pour stocker le contenu du fichier
|
||||
}
|
||||
|
||||
interface AnonymizationOptions { [key: string]: boolean; }
|
||||
@@ -44,6 +49,8 @@ export default function Home() {
|
||||
const [anonymizationOptions, setAnonymizationOptions] = useState<AnonymizationOptions>(
|
||||
piiOptions.reduce((acc, option) => ({ ...acc, [option.id]: true }), {})
|
||||
);
|
||||
// Nouvel état pour gérer la modale
|
||||
const [modalContent, setModalContent] = useState<string | null>(null);
|
||||
|
||||
// === Fonctions utilitaires et Handlers ===
|
||||
const handleOptionChange = (id: string) => setAnonymizationOptions(prev => ({ ...prev, [id]: !prev[id] }));
|
||||
@@ -55,7 +62,27 @@ export default function Home() {
|
||||
const clearFile = () => { setFile(null); setError(null); };
|
||||
const clearHistory = () => setHistory([]);
|
||||
const removeFromHistory = (id: string) => setHistory(prev => prev.filter(item => item.id !== id));
|
||||
const handleDownload = (id: string) => { const fileToDownload = history.find(item => item.id === id); if (!fileToDownload?.processedBlob) return; const url = URL.createObjectURL(fileToDownload.processedBlob); const a = document.createElement('a'); a.href = url; a.download = `anonymized_${fileToDownload.name.split('.')[0] || 'file'}.txt`; a.click(); URL.revokeObjectURL(url); a.remove(); };
|
||||
|
||||
const handleDownload = (id: string) => {
|
||||
const fileToDownload = history.find(item => item.id === id);
|
||||
if (!fileToDownload?.processedBlob) return;
|
||||
const url = URL.createObjectURL(fileToDownload.processedBlob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = `anonymized_${fileToDownload.name.split('.')[0] || 'file'}.txt`;
|
||||
a.click();
|
||||
URL.revokeObjectURL(url);
|
||||
a.remove();
|
||||
};
|
||||
|
||||
// Nouvelle fonction pour gérer l'ouverture de l'aperçu
|
||||
const handlePreview = (id: string) => {
|
||||
const fileToPreview = history.find((item) => item.id === id);
|
||||
if (fileToPreview?.textContent) {
|
||||
setModalContent(fileToPreview.textContent);
|
||||
}
|
||||
};
|
||||
|
||||
const getStatusInfo = (item: ProcessedFile) => { switch (item.status) { case 'completed': return { icon: <ShieldCheck className="h-4 w-4 text-white" />, color: 'bg-[#061717]' }; case 'error': return { icon: <AlertCircle className="h-4 w-4 text-white" />, color: 'bg-[#F7AB6E]' }; default: return { icon: <div className="h-2 w-2 rounded-full bg-[#F7AB6E] animate-pulse" />, color: 'bg-[#061717]' }; } };
|
||||
|
||||
// === Fonction principale pour l'appel à n8n ===
|
||||
@@ -75,9 +102,7 @@ export default function Home() {
|
||||
|
||||
try {
|
||||
const formData = new FormData();
|
||||
// On attache le fichier binaire
|
||||
formData.append('file', file);
|
||||
// On attache les options cochées sous forme de chaîne JSON
|
||||
formData.append('options', JSON.stringify(anonymizationOptions));
|
||||
|
||||
setProgress(30);
|
||||
@@ -91,9 +116,13 @@ export default function Home() {
|
||||
|
||||
const processedBlob = await response.blob();
|
||||
const piiCount = parseInt(response.headers.get('X-Pii-Count') || '0', 10);
|
||||
|
||||
// On lit le contenu du blob en texte pour pouvoir l'afficher
|
||||
const textContent = await processedBlob.text();
|
||||
setProgress(90);
|
||||
|
||||
setHistory(prev => prev.map(item => item.id === fileId ? { ...item, status: 'completed', processedSize: formatFileSize(processedBlob.size), piiCount, processedBlob } : item));
|
||||
// On met à jour l'historique avec toutes les informations, y compris le contenu texte
|
||||
setHistory(prev => prev.map(item => item.id === fileId ? { ...item, status: 'completed', processedSize: formatFileSize(processedBlob.size), piiCount, processedBlob, textContent } : item));
|
||||
setProgress(100);
|
||||
|
||||
} catch (err) {
|
||||
@@ -127,7 +156,16 @@ export default function Home() {
|
||||
) : (
|
||||
history.map((item) => {
|
||||
const status = getStatusInfo(item);
|
||||
return (<div key={item.id} className="bg-[#061717] border-2 border-white shadow-[4px_4px_0_0_black] p-3 transition-all"><div className="flex items-start justify-between"><div className="flex items-center gap-3 min-w-0"><div className={`flex-shrink-0 w-7 h-7 border-2 border-white shadow-[2px_2px_0_0_black] flex items-center justify-center ${status.color}`}>{status.icon}</div><div className="min-w-0"><p className="text-sm font-black text-white uppercase truncate" title={item.name}>{item.name}</p>{item.status === 'completed' && (<div className="flex items-center gap-2 mt-1"><span className="text-xs font-bold text-white/70">{item.originalSize} → {item.processedSize}</span><Badge className="bg-[#F7AB6E] text-white border-2 border-white shadow-[2px_2px_0_0_black] font-black uppercase text-[10px] px-1.5 py-0">{item.piiCount} PII</Badge></div>)}{item.status === 'error' && <p className="text-xs font-bold text-[#F7AB6E] mt-1 uppercase">Erreur</p>}{item.status === 'processing' && <p className="text-xs font-bold text-[#F7AB6E] mt-1 uppercase">Traitement...</p>}</div></div><div className="flex items-center gap-1.5 ml-2">{item.status === 'completed' && (<Button onClick={() => handleDownload(item.id)} variant="ghost" size="icon" className="h-7 w-7 bg-[#061717] text-white border-2 border-white shadow-[2px_2px_0_0_black] hover:shadow-[1px_1px_0_0_black] active:shadow-none active:translate-x-0.5 active:translate-y-0.5"><Download className="h-3 w-3" /></Button>)}<Button onClick={() => removeFromHistory(item.id)} variant="ghost" size="icon" className="h-7 w-7 bg-[#F7AB6E] text-white border-2 border-white shadow-[2px_2px_0_0_black] hover:shadow-[1px_1px_0_0_black] active:shadow-none active:translate-x-0.5 active:translate-y-0.5"><X className="h-3 w-3" /></Button></div></div></div>);
|
||||
return (<div key={item.id} className="bg-[#061717] border-2 border-white shadow-[4px_4px_0_0_black] p-3 transition-all"><div className="flex items-start justify-between"><div className="flex items-center gap-3 min-w-0"><div className={`flex-shrink-0 w-7 h-7 border-2 border-white shadow-[2px_2px_0_0_black] flex items-center justify-center ${status.color}`}>{status.icon}</div><div className="min-w-0"><p className="text-sm font-black text-white uppercase truncate" title={item.name}>{item.name}</p>{item.status === 'completed' && (<div className="flex items-center gap-2 mt-1"><span className="text-xs font-bold text-white/70">{item.originalSize} → {item.processedSize}</span><Badge className="bg-[#F7AB6E] text-white border-2 border-white shadow-[2px_2px_0_0_black] font-black uppercase text-[10px] px-1.5 py-0">{item.piiCount} PII</Badge></div>)}{item.status === 'error' && <p className="text-xs font-bold text-[#F7AB6E] mt-1 uppercase">Erreur</p>}{item.status === 'processing' && <p className="text-xs font-bold text-[#F7AB6E] mt-1 uppercase">Traitement...</p>}</div></div><div className="flex items-center gap-1.5 ml-2">
|
||||
{/* Le nouveau bouton "Aperçu" est ajouté ici */}
|
||||
{item.status === 'completed' && item.textContent && (
|
||||
<Button onClick={() => handlePreview(item.id)} variant="ghost" size="icon" className="h-7 w-7 bg-[#061717] text-white border-2 border-white shadow-[2px_2px_0_0_black] hover:shadow-[1px_1px_0_0_black] active:shadow-none active:translate-x-0.5 active:translate-y-0.5">
|
||||
<Eye className="h-3 w-3" />
|
||||
</Button>
|
||||
)}
|
||||
{item.status === 'completed' && (<Button onClick={() => handleDownload(item.id)} variant="ghost" size="icon" className="h-7 w-7 bg-[#061717] text-white border-2 border-white shadow-[2px_2px_0_0_black] hover:shadow-[1px_1px_0_0_black] active:shadow-none active:translate-x-0.5 active:translate-y-0.5"><Download className="h-3 w-3" /></Button>)}
|
||||
<Button onClick={() => removeFromHistory(item.id)} variant="ghost" size="icon" className="h-7 w-7 bg-[#F7AB6E] text-white border-2 border-white shadow-[2px_2px_0_0_black] hover:shadow-[1px_1px_0_0_black] active:shadow-none active:translate-x-0.5 active:translate-y-0.5"><X className="h-3 w-3" /></Button>
|
||||
</div></div></div>);
|
||||
})
|
||||
)}
|
||||
</div>
|
||||
@@ -169,6 +207,12 @@ export default function Home() {
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
{/* Le composant de la modale est rendu ici, en dehors du flux principal */}
|
||||
<MarkdownModal
|
||||
content={modalContent}
|
||||
onClose={() => setModalContent(null)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
1532
package-lock.json
generated
1532
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -22,11 +22,14 @@
|
||||
"pdfjs-dist": "^5.3.31",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0",
|
||||
"react-markdown": "^10.1.0",
|
||||
"remark-gfm": "^4.0.1",
|
||||
"tailwind-merge": "^3.3.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/eslintrc": "^3",
|
||||
"@tailwindcss/postcss": "^4",
|
||||
"@tailwindcss/typography": "^0.5.16",
|
||||
"@types/node": "^20",
|
||||
"@types/pdfjs-dist": "^2.10.377",
|
||||
"@types/react": "^19",
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
module.exports = {
|
||||
content: [
|
||||
"./app/**/*.{js,ts,jsx,tsx,mdx}",
|
||||
"./components/**/*.{js,ts,jsx,tsx,mdx}",
|
||||
],
|
||||
theme: {
|
||||
extend: {},
|
||||
},
|
||||
plugins: [],
|
||||
};
|
||||
19
tailwind.config.ts
Normal file
19
tailwind.config.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
// tailwind.config.ts
|
||||
|
||||
import type { Config } from "tailwindcss";
|
||||
import typography from "@tailwindcss/typography";
|
||||
|
||||
const config: Config = {
|
||||
content: [
|
||||
"./app/**/*.{js,ts,jsx,tsx,mdx}",
|
||||
"./components/**/*.{js,ts,jsx,tsx,mdx}",
|
||||
],
|
||||
theme: {
|
||||
extend: {},
|
||||
},
|
||||
plugins: [
|
||||
typography, // C'est ici qu'on active le plugin pour la classe 'prose'
|
||||
],
|
||||
};
|
||||
|
||||
export default config;
|
||||
Reference in New Issue
Block a user