correction des themes
This commit is contained in:
@@ -1,10 +1,49 @@
|
||||
@import "tailwindcss";
|
||||
|
||||
/* Custom Theme */
|
||||
@theme {
|
||||
--font-sans: 'Inter', sans-serif;
|
||||
--font-serif: 'Merriweather', serif;
|
||||
--color-paper: #fcfbf7;
|
||||
|
||||
/* Global Theme Colors */
|
||||
--color-theme-bg: var(--theme-bg);
|
||||
--color-theme-panel: var(--theme-panel);
|
||||
--color-theme-text: var(--theme-text);
|
||||
--color-theme-muted: var(--theme-muted);
|
||||
--color-theme-border: var(--theme-border);
|
||||
--color-theme-editor-bg: var(--theme-editor-bg);
|
||||
--color-theme-editor-text: var(--theme-editor-text);
|
||||
}
|
||||
|
||||
:root,
|
||||
.theme-light {
|
||||
--theme-bg: #eef2ff;
|
||||
--theme-panel: #ffffff;
|
||||
--theme-text: #0f172a;
|
||||
--theme-muted: #64748b;
|
||||
--theme-border: #e2e8f0;
|
||||
--theme-editor-bg: #ffffff;
|
||||
--theme-editor-text: #0f172a;
|
||||
}
|
||||
|
||||
.theme-dark {
|
||||
--theme-bg: #0f172a;
|
||||
--theme-panel: #1e293b;
|
||||
--theme-text: #f8fafc;
|
||||
--theme-muted: #94a3b8;
|
||||
--theme-border: #334155;
|
||||
--theme-editor-bg: #1e293b;
|
||||
--theme-editor-text: #e2e8f0;
|
||||
}
|
||||
|
||||
.theme-sepia {
|
||||
--theme-bg: #eaddc4;
|
||||
--theme-panel: #fbf8f1;
|
||||
--theme-text: #332616;
|
||||
--theme-muted: #735e44;
|
||||
--theme-border: #dfcdae;
|
||||
--theme-editor-bg: #fbf8f1;
|
||||
--theme-editor-text: #332616;
|
||||
}
|
||||
|
||||
/* Editor placeholder */
|
||||
@@ -19,13 +58,16 @@
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: #cbd5e1;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: #94a3b8;
|
||||
}
|
||||
@@ -37,7 +79,8 @@
|
||||
size: auto;
|
||||
}
|
||||
|
||||
html, body {
|
||||
html,
|
||||
body {
|
||||
height: auto !important;
|
||||
overflow: visible !important;
|
||||
margin: 0 !important;
|
||||
@@ -53,11 +96,23 @@
|
||||
position: relative !important;
|
||||
}
|
||||
|
||||
.no-print { display: none !important; }
|
||||
.print-only { display: block !important; }
|
||||
.no-print {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.break-before-page { page-break-before: always; break-before: page; }
|
||||
.break-after-page { page-break-after: always; break-after: page; }
|
||||
.print-only {
|
||||
display: block !important;
|
||||
}
|
||||
|
||||
.break-before-page {
|
||||
page-break-before: always;
|
||||
break-before: page;
|
||||
}
|
||||
|
||||
.break-after-page {
|
||||
page-break-after: always;
|
||||
break-after: page;
|
||||
}
|
||||
|
||||
p {
|
||||
text-align: justify;
|
||||
@@ -66,10 +121,16 @@
|
||||
color: black !important;
|
||||
}
|
||||
|
||||
h1, h2, h3, h4 {
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4 {
|
||||
color: black !important;
|
||||
page-break-after: avoid;
|
||||
}
|
||||
|
||||
a { text-decoration: none; color: black !important; }
|
||||
}
|
||||
a {
|
||||
text-decoration: none;
|
||||
color: black !important;
|
||||
}
|
||||
}
|
||||
@@ -26,7 +26,7 @@ export default function RootLayout({
|
||||
}) {
|
||||
return (
|
||||
<html lang="fr">
|
||||
<body className={`${inter.variable} ${merriweather.variable} font-sans bg-gray-100 text-slate-800 h-screen overflow-hidden antialiased`}>
|
||||
<body className={`${inter.variable} ${merriweather.variable} font-sans h-screen overflow-hidden antialiased bg-theme-bg text-theme-text transition-colors duration-300`}>
|
||||
<AuthProvider>
|
||||
{children}
|
||||
</AuthProvider>
|
||||
|
||||
@@ -32,17 +32,17 @@ const AIPanel: React.FC<AIPanelProps> = ({ chatHistory, onSendMessage, onInsertT
|
||||
const isLimitReached = usage ? usage.aiActionsCurrent >= usage.aiActionsLimit : false;
|
||||
|
||||
return (
|
||||
<div className="flex flex-col h-full bg-white border-l border-slate-200 shadow-xl w-80 lg:w-96">
|
||||
<div className="flex flex-col h-full bg-theme-panel border-l border-theme-border shadow-xl w-80 lg:w-96 transition-colors duration-300">
|
||||
{/* Header with Usage Counter */}
|
||||
<div className="p-4 bg-indigo-600 text-white flex items-center justify-between shadow-md">
|
||||
<div className="flex items-center gap-2">
|
||||
<Sparkles size={20} className="animate-pulse" />
|
||||
<h3 className="font-bold tracking-tight">Assistant IA</h3>
|
||||
<Sparkles size={20} className="animate-pulse" />
|
||||
<h3 className="font-bold tracking-tight">Assistant IA</h3>
|
||||
</div>
|
||||
{usage && (
|
||||
<div className="bg-indigo-900/50 px-2 py-1 rounded text-[10px] font-black flex items-center gap-1">
|
||||
<Zap size={10} fill="currentColor" /> {usage.aiActionsCurrent} / {usage.aiActionsLimit === 999999 ? '∞' : usage.aiActionsLimit}
|
||||
</div>
|
||||
<div className="bg-indigo-900/50 px-2 py-1 rounded text-[10px] font-black flex items-center gap-1">
|
||||
<Zap size={10} fill="currentColor" /> {usage.aiActionsCurrent} / {usage.aiActionsLimit === 999999 ? '∞' : usage.aiActionsLimit}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -53,33 +53,33 @@ const AIPanel: React.FC<AIPanelProps> = ({ chatHistory, onSendMessage, onInsertT
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex-1 overflow-y-auto p-4 space-y-4 bg-slate-50">
|
||||
<div className="flex-1 overflow-y-auto p-4 space-y-4 bg-theme-bg transition-colors duration-300">
|
||||
{chatHistory.length === 0 && (
|
||||
<div className="text-center text-slate-400 mt-10">
|
||||
<div className="text-center text-theme-muted mt-10">
|
||||
<Bot size={48} className="mx-auto mb-2 opacity-50" />
|
||||
<p className="text-sm">Bonjour ! Comment puis-je vous aider aujourd'hui ?</p>
|
||||
{isLimitReached && (
|
||||
<div className="mt-4 p-4 bg-red-50 border border-red-100 rounded-xl text-red-600 text-xs font-bold uppercase animate-pulse">
|
||||
Limite atteinte ! Améliorez votre plan.
|
||||
</div>
|
||||
<div className="mt-4 p-4 bg-red-50 border border-red-100 rounded-xl text-red-600 text-xs font-bold uppercase animate-pulse">
|
||||
Limite atteinte ! Améliorez votre plan.
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{chatHistory.map((msg) => (
|
||||
<div key={msg.id} className={`flex flex-col ${msg.role === 'user' ? 'items-end' : 'items-start'}`}>
|
||||
<div className={`max-w-[85%] rounded-2xl p-4 text-sm shadow-sm ${msg.role === 'user' ? 'bg-indigo-600 text-white rounded-br-none' : 'bg-white text-slate-700 border border-slate-100 rounded-bl-none'}`}>
|
||||
<div className={`max-w-[85%] rounded-2xl p-4 text-sm shadow-sm transition-colors duration-300 ${msg.role === 'user' ? 'bg-indigo-600 text-white rounded-br-none' : 'bg-theme-panel text-theme-text border border-theme-border rounded-bl-none'}`}>
|
||||
{msg.role === 'model' && msg.responseType === 'reflection' && (
|
||||
<div className="flex items-center gap-1.5 text-[10px] font-black text-amber-600 mb-1.5 uppercase tracking-wide"><BrainCircuit size={12} /> Réflexion</div>
|
||||
<div className="flex items-center gap-1.5 text-[10px] font-black text-amber-600 mb-1.5 uppercase tracking-wide"><BrainCircuit size={12} /> Réflexion</div>
|
||||
)}
|
||||
<div className="whitespace-pre-wrap leading-relaxed">{msg.text}</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
|
||||
|
||||
{isGenerating && (
|
||||
<div className="flex justify-start">
|
||||
<div className="bg-white p-3 rounded-2xl rounded-bl-none shadow-sm border border-slate-100 flex items-center gap-2 text-xs text-slate-500">
|
||||
<div className="bg-theme-panel p-3 rounded-2xl rounded-bl-none shadow-sm border border-theme-border flex items-center gap-2 text-xs text-theme-muted transition-colors duration-300">
|
||||
<RefreshCw size={14} className="animate-spin" /> L'IA travaille...
|
||||
</div>
|
||||
</div>
|
||||
@@ -87,17 +87,17 @@ const AIPanel: React.FC<AIPanelProps> = ({ chatHistory, onSendMessage, onInsertT
|
||||
<div ref={messagesEndRef} />
|
||||
</div>
|
||||
|
||||
<div className="p-4 bg-white border-t border-slate-200">
|
||||
<div className="p-4 bg-theme-panel border-t border-theme-border transition-colors duration-300">
|
||||
<form onSubmit={handleSubmit} className="relative">
|
||||
<input
|
||||
type="text"
|
||||
value={input}
|
||||
onChange={(e) => setInput(e.target.value)}
|
||||
placeholder={isLimitReached ? "Limite atteinte..." : "Votre message..."}
|
||||
className="w-full pl-4 pr-12 py-3 bg-slate-100 rounded-2xl text-sm focus:outline-none focus:ring-2 focus:ring-indigo-500 transition-shadow disabled:opacity-50"
|
||||
className="w-full pl-4 pr-12 py-3 bg-theme-bg text-theme-text border border-theme-border rounded-2xl text-sm focus:outline-none focus:border-indigo-500 transition-all disabled:opacity-50"
|
||||
disabled={isGenerating || isLimitReached}
|
||||
/>
|
||||
<button
|
||||
<button
|
||||
type="submit"
|
||||
disabled={!input.trim() || isGenerating || isLimitReached}
|
||||
className="absolute right-1.5 top-1.5 p-2 bg-indigo-600 text-white rounded-xl hover:bg-indigo-700 disabled:opacity-50 transition-colors shadow-md"
|
||||
|
||||
@@ -43,8 +43,8 @@ const BookSettingsComponent: React.FC<BookSettingsProps> = ({ project, onUpdate,
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="h-full bg-[#eef2ff] p-8 overflow-y-auto">
|
||||
<div className="max-w-4xl mx-auto bg-white rounded-xl shadow-lg border border-slate-200 overflow-hidden">
|
||||
<div className="h-full bg-theme-bg p-8 overflow-y-auto transition-colors duration-300">
|
||||
<div className="max-w-4xl mx-auto bg-theme-panel rounded-xl shadow-lg border border-theme-border overflow-hidden transition-colors duration-300">
|
||||
|
||||
<div className="bg-slate-900 text-white p-6 border-b border-slate-800 flex items-center gap-4">
|
||||
<div className="bg-blue-600 p-3 rounded-lg">
|
||||
@@ -58,53 +58,53 @@ const BookSettingsComponent: React.FC<BookSettingsProps> = ({ project, onUpdate,
|
||||
|
||||
<div className="p-8 space-y-8">
|
||||
<section className="space-y-4">
|
||||
<h3 className="text-lg font-bold text-slate-800 flex items-center gap-2 border-b border-slate-100 pb-2">
|
||||
<h3 className="text-lg font-bold text-theme-text flex items-center gap-2 border-b border-theme-border pb-2">
|
||||
<Book size={18} className="text-blue-600" /> Informations de Base
|
||||
</h3>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<div>
|
||||
<label className="block text-sm font-semibold text-slate-600 mb-1">Titre du Roman</label>
|
||||
<label className="block text-sm font-semibold text-theme-muted mb-1">Titre du Roman</label>
|
||||
<input
|
||||
type="text"
|
||||
value={project.title}
|
||||
onChange={(e) => onUpdate({ ...project, title: e.target.value })}
|
||||
className="w-full p-2.5 bg-[#eef2ff] border border-slate-300 rounded-lg focus:ring-2 focus:ring-blue-500 outline-none font-serif font-bold text-lg"
|
||||
className="w-full p-2.5 bg-theme-bg text-theme-text border border-theme-border rounded-lg focus:ring-2 focus:ring-blue-500 outline-none font-serif font-bold text-lg transition-colors duration-300"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-semibold text-slate-600 mb-1">Nom d'Auteur</label>
|
||||
<label className="block text-sm font-semibold text-theme-muted mb-1">Nom d'Auteur</label>
|
||||
<input
|
||||
type="text"
|
||||
value={project.author}
|
||||
onChange={(e) => onUpdate({ ...project, author: e.target.value })}
|
||||
className="w-full p-2.5 bg-[#eef2ff] border border-slate-300 rounded-lg focus:ring-2 focus:ring-blue-500 outline-none"
|
||||
className="w-full p-2.5 bg-theme-bg text-theme-text border border-theme-border rounded-lg focus:ring-2 focus:ring-blue-500 outline-none transition-colors duration-300"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-semibold text-slate-600 mb-1">Synopsis Global</label>
|
||||
<label className="block text-sm font-semibold text-theme-muted mb-1">Synopsis Global</label>
|
||||
<textarea
|
||||
value={settings.synopsis}
|
||||
onChange={(e) => handleChange('synopsis', e.target.value)}
|
||||
className="w-full p-3 border border-slate-300 rounded-lg focus:ring-2 focus:ring-blue-500 outline-none h-24 text-sm bg-[#eef2ff]"
|
||||
className="w-full p-3 bg-theme-bg text-theme-text border border-theme-border rounded-lg focus:ring-2 focus:ring-blue-500 outline-none h-24 text-sm transition-colors duration-300"
|
||||
placeholder="De quoi parle votre histoire dans les grandes lignes ?"
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="space-y-4">
|
||||
<h3 className="text-lg font-bold text-slate-800 flex items-center gap-2 border-b border-slate-100 pb-2">
|
||||
<h3 className="text-lg font-bold text-theme-text flex items-center gap-2 border-b border-theme-border pb-2">
|
||||
<Target size={18} className="text-red-500" /> Genre & Public
|
||||
</h3>
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
|
||||
<div>
|
||||
<label className="block text-sm font-semibold text-slate-600 mb-1">Genre Principal</label>
|
||||
<label className="block text-sm font-semibold text-theme-muted mb-1">Genre Principal</label>
|
||||
<input
|
||||
type="text"
|
||||
list="genre-suggestions"
|
||||
value={settings.genre}
|
||||
onChange={(e) => handleChange('genre', e.target.value)}
|
||||
className="w-full p-2.5 border border-slate-300 rounded-lg focus:ring-2 focus:ring-blue-500 outline-none bg-[#eef2ff]"
|
||||
className="w-full p-2.5 bg-theme-bg text-theme-text border border-theme-border rounded-lg focus:ring-2 focus:ring-blue-500 outline-none transition-colors duration-300"
|
||||
placeholder="Ex: Fantasy"
|
||||
/>
|
||||
<datalist id="genre-suggestions">
|
||||
@@ -112,35 +112,35 @@ const BookSettingsComponent: React.FC<BookSettingsProps> = ({ project, onUpdate,
|
||||
</datalist>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-semibold text-slate-600 mb-1">Sous-Genre</label>
|
||||
<label className="block text-sm font-semibold text-theme-muted mb-1">Sous-Genre</label>
|
||||
<input
|
||||
type="text"
|
||||
value={settings.subGenre || ''}
|
||||
onChange={(e) => handleChange('subGenre', e.target.value)}
|
||||
className="w-full p-2.5 border border-slate-300 rounded-lg focus:ring-2 focus:ring-blue-500 outline-none bg-[#eef2ff]"
|
||||
className="w-full p-2.5 bg-theme-bg text-theme-text border border-theme-border rounded-lg focus:ring-2 focus:ring-blue-500 outline-none transition-colors duration-300"
|
||||
placeholder="Ex: Dark Fantasy"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-semibold text-slate-600 mb-1">Public Cible</label>
|
||||
<label className="block text-sm font-semibold text-theme-muted mb-1">Public Cible</label>
|
||||
<input
|
||||
type="text"
|
||||
value={settings.targetAudience}
|
||||
onChange={(e) => handleChange('targetAudience', e.target.value)}
|
||||
className="w-full p-2.5 border border-slate-300 rounded-lg focus:ring-2 focus:ring-blue-500 outline-none bg-[#eef2ff]"
|
||||
className="w-full p-2.5 bg-theme-bg text-theme-text border border-theme-border rounded-lg focus:ring-2 focus:ring-blue-500 outline-none transition-colors duration-300"
|
||||
placeholder="Ex: Jeune Adulte, Adulte..."
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-semibold text-slate-600 mb-1">Thèmes Clés</label>
|
||||
<label className="block text-sm font-semibold text-theme-muted mb-1">Thèmes Clés</label>
|
||||
<div className="relative">
|
||||
<Hash size={14} className="absolute left-3 top-3 text-slate-400" />
|
||||
<Hash size={14} className="absolute left-3 top-3 text-theme-muted" />
|
||||
<input
|
||||
type="text"
|
||||
value={settings.themes}
|
||||
onChange={(e) => handleChange('themes', e.target.value)}
|
||||
className="w-full pl-9 p-2.5 border border-slate-300 rounded-lg focus:ring-2 focus:ring-blue-500 outline-none bg-[#eef2ff]"
|
||||
className="w-full pl-9 p-2.5 bg-theme-bg text-theme-text border border-theme-border rounded-lg focus:ring-2 focus:ring-blue-500 outline-none transition-colors duration-300"
|
||||
placeholder="Ex: Vengeance, Rédemption, Voyage initiatique..."
|
||||
/>
|
||||
</div>
|
||||
@@ -148,44 +148,44 @@ const BookSettingsComponent: React.FC<BookSettingsProps> = ({ project, onUpdate,
|
||||
</section>
|
||||
|
||||
<section className="space-y-4">
|
||||
<h3 className="text-lg font-bold text-slate-800 flex items-center gap-2 border-b border-slate-100 pb-2">
|
||||
<h3 className="text-lg font-bold text-theme-text flex items-center gap-2 border-b border-theme-border pb-2">
|
||||
<Feather size={18} className="text-purple-600" /> Narration & Style
|
||||
</h3>
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
|
||||
<div>
|
||||
<label className="block text-sm font-semibold text-slate-600 mb-1 flex items-center gap-1">
|
||||
<label className="block text-sm font-semibold text-theme-muted mb-1 flex items-center gap-1">
|
||||
<Users size={14} /> Point de Vue (POV)
|
||||
</label>
|
||||
<select
|
||||
value={settings.pov}
|
||||
onChange={(e) => handleChange('pov', e.target.value)}
|
||||
className="w-full p-2.5 bg-[#eef2ff] border border-slate-300 rounded-lg focus:ring-2 focus:ring-blue-500 outline-none"
|
||||
className="w-full p-2.5 bg-theme-bg text-theme-text border border-theme-border rounded-lg focus:ring-2 focus:ring-blue-500 outline-none transition-colors duration-300"
|
||||
>
|
||||
<option value="">Sélectionner...</option>
|
||||
{POV_OPTIONS.map(o => <option key={o} value={o}>{o}</option>)}
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-semibold text-slate-600 mb-1 flex items-center gap-1">
|
||||
<label className="block text-sm font-semibold text-theme-muted mb-1 flex items-center gap-1">
|
||||
<Clock size={14} /> Temps du récit
|
||||
</label>
|
||||
<select
|
||||
value={settings.tense}
|
||||
onChange={(e) => handleChange('tense', e.target.value)}
|
||||
className="w-full p-2.5 bg-[#eef2ff] border border-slate-300 rounded-lg focus:ring-2 focus:ring-blue-500 outline-none"
|
||||
className="w-full p-2.5 bg-theme-bg text-theme-text border border-theme-border rounded-lg focus:ring-2 focus:ring-blue-500 outline-none transition-colors duration-300"
|
||||
>
|
||||
<option value="">Sélectionner...</option>
|
||||
{TENSE_OPTIONS.map(o => <option key={o} value={o}>{o}</option>)}
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-semibold text-slate-600 mb-1">Ton Général</label>
|
||||
<label className="block text-sm font-semibold text-theme-muted mb-1">Ton Général</label>
|
||||
<input
|
||||
type="text"
|
||||
list="tone-suggestions"
|
||||
value={settings.tone}
|
||||
onChange={(e) => handleChange('tone', e.target.value)}
|
||||
className="w-full p-2.5 border border-slate-300 rounded-lg focus:ring-2 focus:ring-blue-500 outline-none bg-[#eef2ff]"
|
||||
className="w-full p-2.5 bg-theme-bg text-theme-text border border-theme-border rounded-lg focus:ring-2 focus:ring-blue-500 outline-none transition-colors duration-300"
|
||||
placeholder="Ex: Sombre, Ironique..."
|
||||
/>
|
||||
<datalist id="tone-suggestions">
|
||||
@@ -195,16 +195,16 @@ const BookSettingsComponent: React.FC<BookSettingsProps> = ({ project, onUpdate,
|
||||
</div>
|
||||
|
||||
<div className="mt-4">
|
||||
<label className="block text-sm font-semibold text-slate-600 mb-1">
|
||||
<label className="block text-sm font-semibold text-theme-muted mb-1">
|
||||
Guide de Style & Instructions IA (Prompt Système)
|
||||
</label>
|
||||
<p className="text-xs text-slate-400 mb-2">
|
||||
<p className="text-xs text-theme-muted mb-2">
|
||||
Ces instructions seront envoyées à l'IA à chaque génération. Décrivez ici le style d'écriture désiré (ex: "phrases courtes", "vocabulaire soutenu", "beaucoup de métaphores").
|
||||
</p>
|
||||
<textarea
|
||||
value={project.styleGuide || ''}
|
||||
onChange={(e) => handleStyleGuideChange(e.target.value)}
|
||||
className="w-full p-3 border border-indigo-100 bg-[#eef2ff] rounded-lg focus:ring-2 focus:ring-indigo-500 outline-none h-32 text-sm font-mono text-slate-700"
|
||||
className="w-full p-3 bg-theme-bg text-theme-text border border-theme-border rounded-lg focus:ring-2 focus:ring-indigo-500 outline-none h-32 text-sm font-mono transition-colors duration-300"
|
||||
placeholder="Ex: Utilise un style descriptif et sensoriel. Évite les adverbes. Le narrateur est cynique."
|
||||
/>
|
||||
</div>
|
||||
@@ -220,17 +220,17 @@ const BookSettingsComponent: React.FC<BookSettingsProps> = ({ project, onUpdate,
|
||||
Cette action est irréversible. Toutes les données associées à ce projet (chapitres, entités, idées) seront définitivement effacées.
|
||||
</p>
|
||||
{showDeleteConfirm ? (
|
||||
<div className="flex items-center gap-4 bg-white p-4 rounded border border-red-200">
|
||||
<span className="text-sm font-bold text-slate-700">Êtes-vous sûr ?</span>
|
||||
<div className="flex items-center gap-4 bg-theme-panel p-4 rounded border border-red-200">
|
||||
<span className="text-sm font-bold text-theme-text">Êtes-vous sûr ?</span>
|
||||
<button
|
||||
onClick={onDeleteProject}
|
||||
className="px-4 py-2 bg-red-600 text-white rounded hover:bg-red-700 text-sm font-bold"
|
||||
className="px-4 py-2 bg-red-600 text-white rounded hover:bg-red-700 text-sm font-bold opacity-90 transition-opacity"
|
||||
>
|
||||
Oui, supprimer définitivement
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setShowDeleteConfirm(false)}
|
||||
className="px-4 py-2 bg-slate-200 text-slate-700 rounded hover:bg-slate-300 text-sm"
|
||||
className="px-4 py-2 bg-theme-bg text-theme-text border border-theme-border rounded hover:opacity-80 text-sm transition-opacity"
|
||||
>
|
||||
Annuler
|
||||
</button>
|
||||
@@ -238,7 +238,7 @@ const BookSettingsComponent: React.FC<BookSettingsProps> = ({ project, onUpdate,
|
||||
) : (
|
||||
<button
|
||||
onClick={() => setShowDeleteConfirm(true)}
|
||||
className="px-4 py-2 bg-white border border-red-300 text-red-600 rounded hover:bg-red-50 text-sm font-bold"
|
||||
className="px-4 py-2 bg-theme-panel border border-red-300 text-red-600 rounded hover:bg-red-50 text-sm font-bold transition-colors duration-300"
|
||||
>
|
||||
Supprimer ce projet
|
||||
</button>
|
||||
|
||||
@@ -17,53 +17,53 @@ interface DashboardProps {
|
||||
|
||||
const Dashboard: React.FC<DashboardProps> = ({ user, projects, onSelect, onCreate, onLogout, onPricing, onProfile }) => {
|
||||
return (
|
||||
<div className="min-h-screen bg-[#eef2ff] p-8 font-sans">
|
||||
<div className="min-h-screen bg-theme-bg p-8 font-sans transition-colors duration-300">
|
||||
<div className="max-w-6xl mx-auto space-y-8">
|
||||
|
||||
{/* User Card */}
|
||||
<div className="flex flex-col md:flex-row justify-between items-center bg-white p-8 rounded-[2rem] shadow-sm border border-indigo-100 gap-6">
|
||||
<div className="flex flex-col md:flex-row justify-between items-center bg-theme-panel p-8 rounded-[2rem] shadow-sm border border-theme-border gap-6">
|
||||
<div className="flex items-center gap-6">
|
||||
<div className="relative">
|
||||
<img src={user.avatar} className="w-20 h-20 rounded-full border-4 border-slate-50 shadow-lg object-cover" alt="Avatar" />
|
||||
<div className="absolute -bottom-1 -right-1 bg-green-500 w-5 h-5 rounded-full border-4 border-white" />
|
||||
</div>
|
||||
<div>
|
||||
<h2 className="text-3xl font-black text-slate-900">Bonjour, {user.name} 👋</h2>
|
||||
<h2 className="text-3xl font-black text-theme-text">Bonjour, {user.name} 👋</h2>
|
||||
<div className="flex items-center gap-3 mt-1">
|
||||
<span className="px-3 py-1 rounded-full bg-indigo-100 text-indigo-700 text-[10px] uppercase font-black tracking-widest">{user.subscription.planDetails?.displayName || user.subscription.plan}</span>
|
||||
<span className="text-slate-400 text-xs font-medium">Membre depuis le 24 janv.</span>
|
||||
<span className="text-theme-muted text-xs font-medium">Membre depuis le 24 janv.</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-3">
|
||||
<button onClick={onProfile} className="bg-slate-50 text-slate-700 px-5 py-2.5 rounded-xl text-sm font-bold hover:bg-slate-100 transition-all flex items-center gap-2 border border-slate-200">
|
||||
<button onClick={onProfile} className="bg-theme-bg text-theme-text px-5 py-2.5 rounded-xl text-sm font-bold hover:opacity-80 transition-all flex items-center gap-2 border border-theme-border">
|
||||
<User size={18} /> Mon Profil
|
||||
</button>
|
||||
<button onClick={onLogout} className="p-3 text-slate-400 hover:text-red-500 rounded-full hover:bg-red-50 transition-colors"><LogOut size={20} /></button>
|
||||
<button onClick={onLogout} className="p-3 text-theme-muted hover:text-red-500 rounded-full hover:bg-red-500/10 transition-colors"><LogOut size={20} /></button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Stats Section */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
|
||||
<div className="bg-white p-6 rounded-3xl shadow-sm border border-indigo-50 flex items-center gap-4">
|
||||
<div className="bg-theme-panel p-6 rounded-3xl shadow-sm border border-theme-border flex items-center gap-4">
|
||||
<div className="bg-orange-100 p-3 rounded-2xl text-orange-600"><Flame size={24} /></div>
|
||||
<div>
|
||||
<p className="text-xs font-bold text-slate-400 uppercase tracking-wider">Série actuelle</p>
|
||||
<p className="text-2xl font-black text-slate-900">{user.stats.writingStreak} Jours</p>
|
||||
<p className="text-xs font-bold text-theme-muted uppercase tracking-wider">Série actuelle</p>
|
||||
<p className="text-2xl font-black text-theme-text">{user.stats.writingStreak} Jours</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="bg-white p-6 rounded-3xl shadow-sm border border-indigo-50 flex items-center gap-4">
|
||||
<div className="bg-theme-panel p-6 rounded-3xl shadow-sm border border-theme-border flex items-center gap-4">
|
||||
<div className="bg-blue-100 p-3 rounded-2xl text-blue-600"><Edit3 size={24} /></div>
|
||||
<div>
|
||||
<p className="text-xs font-bold text-slate-400 uppercase tracking-wider">Mots écrits</p>
|
||||
<p className="text-2xl font-black text-slate-900">{user.stats.totalWordsWritten.toLocaleString()}</p>
|
||||
<p className="text-xs font-bold text-theme-muted uppercase tracking-wider">Mots écrits</p>
|
||||
<p className="text-2xl font-black text-theme-text">{user.stats.totalWordsWritten.toLocaleString()}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="bg-white p-6 rounded-3xl shadow-sm border border-indigo-50 flex items-center gap-4">
|
||||
<div className="bg-theme-panel p-6 rounded-3xl shadow-sm border border-theme-border flex items-center gap-4">
|
||||
<div className="bg-indigo-100 p-3 rounded-2xl text-indigo-600"><Target size={24} /></div>
|
||||
<div>
|
||||
<p className="text-xs font-bold text-slate-400 uppercase tracking-wider">Objectif du jour</p>
|
||||
<p className="text-2xl font-black text-slate-900">{user.preferences.dailyWordGoal} Mots</p>
|
||||
<p className="text-xs font-bold text-theme-muted uppercase tracking-wider">Objectif du jour</p>
|
||||
<p className="text-2xl font-black text-theme-text">{user.preferences.dailyWordGoal} Mots</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -72,7 +72,7 @@ const Dashboard: React.FC<DashboardProps> = ({ user, projects, onSelect, onCreat
|
||||
{/* Project List */}
|
||||
<div className="lg:col-span-2 space-y-4">
|
||||
<div className="flex justify-between items-center mb-6">
|
||||
<h3 className="text-2xl font-black text-slate-900">Mes Romans</h3>
|
||||
<h3 className="text-2xl font-black text-theme-text">Mes Romans</h3>
|
||||
<button
|
||||
onClick={onCreate}
|
||||
className="flex items-center gap-2 bg-blue-600 text-white px-6 py-3 rounded-2xl font-bold hover:bg-blue-700 transition-all shadow-xl shadow-blue-200"
|
||||
@@ -86,23 +86,23 @@ const Dashboard: React.FC<DashboardProps> = ({ user, projects, onSelect, onCreat
|
||||
<div
|
||||
key={p.id}
|
||||
onClick={() => onSelect(p.id)}
|
||||
className="bg-white p-8 rounded-[2.5rem] border border-indigo-50 shadow-sm hover:shadow-2xl hover:scale-[1.02] transition-all cursor-pointer group flex flex-col justify-between h-64"
|
||||
className="bg-theme-panel p-8 rounded-[2.5rem] border border-theme-border shadow-sm hover:shadow-2xl hover:scale-[1.02] transition-all cursor-pointer group flex flex-col justify-between h-64"
|
||||
>
|
||||
<div>
|
||||
<div className="bg-blue-50 w-12 h-12 rounded-2xl flex items-center justify-center text-blue-600 mb-6 group-hover:bg-blue-600 group-hover:text-white transition-colors">
|
||||
<div className="bg-blue-500/10 w-12 h-12 rounded-2xl flex items-center justify-center text-blue-500 mb-6 group-hover:bg-blue-600 group-hover:text-white transition-colors">
|
||||
<Book size={24} />
|
||||
</div>
|
||||
<h4 className="font-black text-slate-900 text-xl truncate mb-1">{p.title}</h4>
|
||||
<p className="text-slate-400 text-sm">Dernière modification : {new Date(p.lastModified).toLocaleDateString()}</p>
|
||||
<h4 className="font-black text-theme-text text-xl truncate mb-1">{p.title}</h4>
|
||||
<p className="text-theme-muted text-sm">Dernière modification : {new Date(p.lastModified).toLocaleDateString()}</p>
|
||||
</div>
|
||||
<div className="flex justify-between items-center text-[10px] text-slate-400 font-black uppercase tracking-widest border-t border-slate-50 pt-4">
|
||||
<div className="flex justify-between items-center text-[10px] text-theme-muted font-black uppercase tracking-widest border-t border-theme-border pt-4 mt-auto">
|
||||
<span>{p.chapters.length} Chapitres</span>
|
||||
<ChevronRight size={20} className="group-hover:text-blue-600 transition-transform group-hover:translate-x-1 duration-300" />
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
{projects.length === 0 && (
|
||||
<div className="col-span-2 py-24 bg-white rounded-[3rem] border-2 border-dashed border-indigo-100 flex flex-col items-center justify-center text-indigo-300">
|
||||
<div className="col-span-2 py-24 bg-theme-panel rounded-[3rem] border-2 border-dashed border-theme-border flex flex-col items-center justify-center text-theme-muted">
|
||||
<Book size={64} className="mb-6 opacity-20" />
|
||||
<p className="font-bold text-lg">Prêt à commencer votre premier roman ?</p>
|
||||
<button onClick={onCreate} className="mt-4 text-blue-600 font-bold hover:underline">Créer un projet maintenant</button>
|
||||
|
||||
@@ -5,8 +5,8 @@ import { Idea, IdeaStatus, IdeaCategory } from '@/lib/types';
|
||||
import { Plus, X, GripVertical, CheckCircle, Circle, Clock, Lightbulb, Search, Trash2, Edit3, Save } from 'lucide-react';
|
||||
|
||||
interface IdeaBoardProps {
|
||||
ideas: Idea[];
|
||||
onUpdate: (ideas: Idea[]) => void;
|
||||
ideas: Idea[];
|
||||
onUpdate: (ideas: Idea[]) => void;
|
||||
}
|
||||
|
||||
const CATEGORIES: Record<IdeaCategory, { label: string, color: string, icon: any }> = {
|
||||
@@ -25,349 +25,347 @@ const STATUS_LABELS: Record<IdeaStatus, string> = {
|
||||
const MAX_DESCRIPTION_LENGTH = 500;
|
||||
|
||||
const IdeaBoard: React.FC<IdeaBoardProps> = ({ ideas, onUpdate }) => {
|
||||
const [newIdeaTitle, setNewIdeaTitle] = useState('');
|
||||
const [newIdeaCategory, setNewIdeaCategory] = useState<IdeaCategory>('plot');
|
||||
const [newIdeaTitle, setNewIdeaTitle] = useState('');
|
||||
const [newIdeaCategory, setNewIdeaCategory] = useState<IdeaCategory>('plot');
|
||||
|
||||
// Drag and Drop State
|
||||
const [draggedIdeaId, setDraggedIdeaId] = useState<string | null>(null);
|
||||
// Drag and Drop State
|
||||
const [draggedIdeaId, setDraggedIdeaId] = useState<string | null>(null);
|
||||
|
||||
// Modal State for Edit/Quick Add
|
||||
const [editingItem, setEditingItem] = useState<Partial<Idea> | null>(null);
|
||||
// Modal State for Edit/Quick Add
|
||||
const [editingItem, setEditingItem] = useState<Partial<Idea> | null>(null);
|
||||
|
||||
// --- ACTIONS ---
|
||||
// --- ACTIONS ---
|
||||
|
||||
const handleAddIdea = (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
if (!newIdeaTitle.trim()) return;
|
||||
const handleAddIdea = (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
if (!newIdeaTitle.trim()) return;
|
||||
|
||||
const newIdea: Idea = {
|
||||
id: `idea-${Date.now()}`,
|
||||
title: newIdeaTitle,
|
||||
description: '',
|
||||
category: newIdeaCategory,
|
||||
status: 'todo',
|
||||
createdAt: Date.now()
|
||||
const newIdea: Idea = {
|
||||
id: `idea-${Date.now()}`,
|
||||
title: newIdeaTitle,
|
||||
description: '',
|
||||
category: newIdeaCategory,
|
||||
status: 'todo',
|
||||
createdAt: Date.now()
|
||||
};
|
||||
|
||||
onUpdate([...ideas, newIdea]);
|
||||
setNewIdeaTitle('');
|
||||
};
|
||||
|
||||
onUpdate([...ideas, newIdea]);
|
||||
setNewIdeaTitle('');
|
||||
};
|
||||
const handleDelete = (id: string) => {
|
||||
if (confirm("Supprimer cette carte ?")) {
|
||||
onUpdate(ideas.filter(i => i.id !== id));
|
||||
if (editingItem?.id === id) setEditingItem(null);
|
||||
}
|
||||
};
|
||||
|
||||
const handleDelete = (id: string) => {
|
||||
if(confirm("Supprimer cette carte ?")) {
|
||||
onUpdate(ideas.filter(i => i.id !== id));
|
||||
if (editingItem?.id === id) setEditingItem(null);
|
||||
}
|
||||
};
|
||||
const handleSaveEdit = () => {
|
||||
if (!editingItem || !editingItem.title?.trim()) return;
|
||||
|
||||
const handleSaveEdit = () => {
|
||||
if (!editingItem || !editingItem.title?.trim()) return;
|
||||
|
||||
if (editingItem.id) {
|
||||
// Update existing
|
||||
onUpdate(ideas.map(i => i.id === editingItem.id ? { ...i, ...editingItem } as Idea : i));
|
||||
} else {
|
||||
// Create new from modal
|
||||
const newIdea: Idea = {
|
||||
id: `idea-${Date.now()}`,
|
||||
title: editingItem.title || '',
|
||||
description: editingItem.description || '',
|
||||
category: editingItem.category || 'plot',
|
||||
status: editingItem.status || 'todo',
|
||||
createdAt: Date.now()
|
||||
if (editingItem.id) {
|
||||
// Update existing
|
||||
onUpdate(ideas.map(i => i.id === editingItem.id ? { ...i, ...editingItem } as Idea : i));
|
||||
} else {
|
||||
// Create new from modal
|
||||
const newIdea: Idea = {
|
||||
id: `idea-${Date.now()}`,
|
||||
title: editingItem.title || '',
|
||||
description: editingItem.description || '',
|
||||
category: editingItem.category || 'plot',
|
||||
status: editingItem.status || 'todo',
|
||||
createdAt: Date.now()
|
||||
};
|
||||
onUpdate([...ideas, newIdea]);
|
||||
}
|
||||
setEditingItem(null);
|
||||
};
|
||||
}
|
||||
setEditingItem(null);
|
||||
};
|
||||
|
||||
const openQuickAdd = (status: IdeaStatus) => {
|
||||
setEditingItem({
|
||||
title: '',
|
||||
description: '',
|
||||
category: 'plot',
|
||||
status: status
|
||||
});
|
||||
};
|
||||
const openQuickAdd = (status: IdeaStatus) => {
|
||||
setEditingItem({
|
||||
title: '',
|
||||
description: '',
|
||||
category: 'plot',
|
||||
status: status
|
||||
});
|
||||
};
|
||||
|
||||
const openEdit = (idea: Idea) => {
|
||||
setEditingItem({ ...idea });
|
||||
};
|
||||
const openEdit = (idea: Idea) => {
|
||||
setEditingItem({ ...idea });
|
||||
};
|
||||
|
||||
// --- DRAG HANDLERS ---
|
||||
// --- DRAG HANDLERS ---
|
||||
|
||||
const handleDragStart = (e: React.DragEvent, id: string) => {
|
||||
setDraggedIdeaId(id);
|
||||
e.dataTransfer.effectAllowed = 'move';
|
||||
};
|
||||
const handleDragStart = (e: React.DragEvent, id: string) => {
|
||||
setDraggedIdeaId(id);
|
||||
e.dataTransfer.effectAllowed = 'move';
|
||||
};
|
||||
|
||||
const handleDrop = (e: React.DragEvent, status: IdeaStatus) => {
|
||||
e.preventDefault();
|
||||
if (draggedIdeaId) {
|
||||
const updatedIdeas = ideas.map(idea =>
|
||||
idea.id === draggedIdeaId ? { ...idea, status } : idea
|
||||
);
|
||||
onUpdate(updatedIdeas);
|
||||
setDraggedIdeaId(null);
|
||||
}
|
||||
};
|
||||
const handleDrop = (e: React.DragEvent, status: IdeaStatus) => {
|
||||
e.preventDefault();
|
||||
if (draggedIdeaId) {
|
||||
const updatedIdeas = ideas.map(idea =>
|
||||
idea.id === draggedIdeaId ? { ...idea, status } : idea
|
||||
);
|
||||
onUpdate(updatedIdeas);
|
||||
setDraggedIdeaId(null);
|
||||
}
|
||||
};
|
||||
|
||||
const handleDragOver = (e: React.DragEvent) => {
|
||||
e.preventDefault();
|
||||
e.dataTransfer.dropEffect = 'move';
|
||||
};
|
||||
const handleDragOver = (e: React.DragEvent) => {
|
||||
e.preventDefault();
|
||||
e.dataTransfer.dropEffect = 'move';
|
||||
};
|
||||
|
||||
// --- RENDERERS ---
|
||||
// --- RENDERERS ---
|
||||
|
||||
const Column = ({ title, status, icon: Icon }: { title: string, status: IdeaStatus, icon: any }) => {
|
||||
const columnIdeas = ideas.filter(i => i.status === status);
|
||||
const Column = ({ title, status, icon: Icon }: { title: string, status: IdeaStatus, icon: any }) => {
|
||||
const columnIdeas = ideas.filter(i => i.status === status);
|
||||
|
||||
return (
|
||||
<div
|
||||
className="flex-1 bg-theme-bg/50 rounded-xl border border-theme-border flex flex-col h-full overflow-hidden transition-colors hover:border-blue-300"
|
||||
onDragOver={handleDragOver}
|
||||
onDrop={(e) => handleDrop(e, status)}
|
||||
onDoubleClick={() => openQuickAdd(status)}
|
||||
title="Double-cliquez dans le vide pour ajouter une carte ici"
|
||||
>
|
||||
{/* Column Header */}
|
||||
<div className={`p-4 border-b border-theme-border flex justify-between items-center transition-colors duration-300 ${status === 'todo' ? 'bg-theme-bg' :
|
||||
status === 'progress' ? 'bg-indigo-500/10' :
|
||||
'bg-green-500/10'
|
||||
}`}>
|
||||
<div className="flex items-center gap-2 font-bold text-theme-text">
|
||||
<Icon size={18} />
|
||||
{title}
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<button
|
||||
onClick={(e) => { e.stopPropagation(); openQuickAdd(status); }}
|
||||
className="p-1 hover:bg-theme-panel rounded-full text-theme-muted hover:text-blue-600 transition-colors"
|
||||
>
|
||||
<Plus size={16} />
|
||||
</button>
|
||||
<span className="text-xs font-semibold bg-theme-panel px-2 py-1 rounded-full border border-theme-border text-theme-muted">
|
||||
{columnIdeas.length}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Column Body */}
|
||||
<div className="flex-1 overflow-y-auto p-3 space-y-3 custom-scrollbar">
|
||||
{columnIdeas.map(idea => {
|
||||
const truncatedDesc = idea.description.length > 300
|
||||
? idea.description.substring(0, 300) + '...'
|
||||
: idea.description;
|
||||
|
||||
return (
|
||||
<div
|
||||
key={idea.id}
|
||||
draggable
|
||||
onDragStart={(e) => handleDragStart(e, idea.id)}
|
||||
onDoubleClick={(e) => {
|
||||
e.stopPropagation(); // Prevent column double-click
|
||||
openEdit(idea);
|
||||
}}
|
||||
className="bg-theme-panel p-3 rounded-lg shadow-sm border border-theme-border cursor-grab active:cursor-grabbing hover:shadow-md hover:border-blue-300 transition-all group relative animate-in zoom-in-95 duration-200"
|
||||
>
|
||||
<div className="flex justify-between items-start mb-2">
|
||||
<span className={`text-[10px] uppercase font-bold px-2 py-0.5 rounded-full flex items-center gap-1 ${CATEGORIES[idea.category].color}`}>
|
||||
{CATEGORIES[idea.category].label}
|
||||
</span>
|
||||
<div className="flex gap-1 opacity-0 group-hover:opacity-100 transition-opacity">
|
||||
<button
|
||||
onClick={(e) => { e.stopPropagation(); openEdit(idea); }}
|
||||
className="text-slate-300 hover:text-blue-500"
|
||||
>
|
||||
<Edit3 size={14} />
|
||||
</button>
|
||||
<button
|
||||
onClick={(e) => { e.stopPropagation(); handleDelete(idea.id); }}
|
||||
className="text-slate-300 hover:text-red-500"
|
||||
>
|
||||
<Trash2 size={14} />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* CARD CONTENT */}
|
||||
<div className="mb-2">
|
||||
<h4 className="font-bold text-theme-text text-sm mb-1 leading-tight">{idea.title}</h4>
|
||||
{idea.description && (
|
||||
<p className="text-xs text-theme-muted line-clamp-3 leading-relaxed" title={idea.description.length > 300 ? "Description tronquée (voir détail)" : undefined}>
|
||||
{truncatedDesc}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between items-center text-xs text-theme-muted border-t border-theme-border pt-2 mt-2 transition-colors duration-300">
|
||||
<span className="flex items-center gap-1">
|
||||
<Clock size={10} /> {new Date(idea.createdAt).toLocaleDateString()}
|
||||
</span>
|
||||
<GripVertical size={14} className="opacity-20" />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
{columnIdeas.length === 0 && (
|
||||
<div className="h-full flex flex-col items-center justify-center text-slate-300 text-sm italic border-2 border-dashed border-indigo-200 rounded-lg m-1">
|
||||
<span className="mb-2">Vide</span>
|
||||
<span className="text-xs opacity-70">Double-cliquez pour ajouter</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
className="flex-1 bg-[#eef2ff] rounded-xl border border-indigo-100 flex flex-col h-full overflow-hidden transition-colors hover:border-blue-200"
|
||||
onDragOver={handleDragOver}
|
||||
onDrop={(e) => handleDrop(e, status)}
|
||||
onDoubleClick={() => openQuickAdd(status)}
|
||||
title="Double-cliquez dans le vide pour ajouter une carte ici"
|
||||
>
|
||||
{/* Column Header */}
|
||||
<div className={`p-4 border-b border-indigo-200 flex justify-between items-center ${
|
||||
status === 'todo' ? 'bg-[#eef2ff]' :
|
||||
status === 'progress' ? 'bg-indigo-50' :
|
||||
'bg-green-50'
|
||||
}`}>
|
||||
<div className="flex items-center gap-2 font-bold text-slate-700">
|
||||
<Icon size={18} />
|
||||
{title}
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<button
|
||||
onClick={(e) => { e.stopPropagation(); openQuickAdd(status); }}
|
||||
className="p-1 hover:bg-white rounded-full text-slate-400 hover:text-blue-600 transition-colors"
|
||||
>
|
||||
<Plus size={16} />
|
||||
</button>
|
||||
<span className="text-xs font-semibold bg-white px-2 py-1 rounded-full border border-indigo-100 text-slate-500">
|
||||
{columnIdeas.length}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col h-full bg-theme-bg p-6 gap-6 relative transition-colors duration-300 -m-8">
|
||||
|
||||
{/* Column Body */}
|
||||
<div className="flex-1 overflow-y-auto p-3 space-y-3 custom-scrollbar">
|
||||
{columnIdeas.map(idea => {
|
||||
const truncatedDesc = idea.description.length > 300
|
||||
? idea.description.substring(0, 300) + '...'
|
||||
: idea.description;
|
||||
{/* Header & Add Form (Top Bar) */}
|
||||
<div className="flex flex-col md:flex-row justify-between items-start md:items-center gap-4 bg-theme-panel p-4 rounded-xl border border-theme-border shadow-sm shrink-0 transition-colors duration-300">
|
||||
<div>
|
||||
<h2 className="text-2xl font-bold text-theme-text flex items-center gap-2">
|
||||
<Lightbulb className="text-yellow-500" /> Boîte à Idées
|
||||
</h2>
|
||||
<p className="text-theme-muted text-sm">Organisez vos tâches, idées de scènes et recherches.</p>
|
||||
</div>
|
||||
|
||||
return (
|
||||
<div
|
||||
key={idea.id}
|
||||
draggable
|
||||
onDragStart={(e) => handleDragStart(e, idea.id)}
|
||||
onDoubleClick={(e) => {
|
||||
e.stopPropagation(); // Prevent column double-click
|
||||
openEdit(idea);
|
||||
}}
|
||||
className="bg-white p-3 rounded-lg shadow-sm border border-slate-200 cursor-grab active:cursor-grabbing hover:shadow-md hover:border-blue-300 transition-all group relative animate-in zoom-in-95 duration-200"
|
||||
>
|
||||
<div className="flex justify-between items-start mb-2">
|
||||
<span className={`text-[10px] uppercase font-bold px-2 py-0.5 rounded-full flex items-center gap-1 ${CATEGORIES[idea.category].color}`}>
|
||||
{CATEGORIES[idea.category].label}
|
||||
</span>
|
||||
<div className="flex gap-1 opacity-0 group-hover:opacity-100 transition-opacity">
|
||||
<button
|
||||
onClick={(e) => { e.stopPropagation(); openEdit(idea); }}
|
||||
className="text-slate-300 hover:text-blue-500"
|
||||
>
|
||||
<Edit3 size={14} />
|
||||
<form onSubmit={handleAddIdea} className="flex-1 w-full md:w-auto max-w-2xl flex gap-2">
|
||||
<select
|
||||
value={newIdeaCategory}
|
||||
onChange={(e) => setNewIdeaCategory(e.target.value as IdeaCategory)}
|
||||
className="bg-theme-bg border border-theme-border text-theme-text text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 p-2.5 outline-none transition-colors duration-300"
|
||||
>
|
||||
{Object.entries(CATEGORIES).map(([key, val]) => (
|
||||
<option key={key} value={key}>{val.label}</option>
|
||||
))}
|
||||
</select>
|
||||
<input
|
||||
type="text"
|
||||
value={newIdeaTitle}
|
||||
onChange={(e) => setNewIdeaTitle(e.target.value)}
|
||||
placeholder="Titre de la nouvelle idée..."
|
||||
className="flex-1 bg-theme-bg border border-theme-border text-theme-text text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 p-2.5 outline-none font-medium transition-colors duration-300"
|
||||
/>
|
||||
<button
|
||||
type="submit"
|
||||
disabled={!newIdeaTitle.trim()}
|
||||
className="text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 disabled:opacity-50 transition-colors flex items-center gap-2"
|
||||
>
|
||||
<Plus size={18} />
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
{/* Kanban Board */}
|
||||
<div className="flex-1 grid grid-cols-1 md:grid-cols-3 gap-6 min-h-0">
|
||||
<Column title="Idées / À faire" status="todo" icon={Circle} />
|
||||
<Column title="En cours" status="progress" icon={Clock} />
|
||||
<Column title="Terminé" status="done" icon={CheckCircle} />
|
||||
</div>
|
||||
|
||||
{/* EDIT / QUICK ADD MODAL */}
|
||||
{editingItem && (
|
||||
<div className="absolute inset-0 z-50 flex items-center justify-center bg-black/40 backdrop-blur-sm p-4 animate-in fade-in duration-200">
|
||||
<div className="bg-theme-panel rounded-xl shadow-2xl w-full max-w-lg overflow-hidden flex flex-col max-h-[90%] transition-colors duration-300 border border-theme-border">
|
||||
<div className="bg-theme-bg border-b border-theme-border p-4 flex justify-between items-center">
|
||||
<h3 className="font-bold text-theme-text flex items-center gap-2">
|
||||
{editingItem.id ? <Edit3 size={18} /> : <Plus size={18} />}
|
||||
{editingItem.id ? 'Éditer la carte' : 'Ajouter une carte'}
|
||||
</h3>
|
||||
<button onClick={() => setEditingItem(null)} className="text-theme-muted hover:text-theme-text">
|
||||
<X size={20} />
|
||||
</button>
|
||||
<button
|
||||
onClick={(e) => { e.stopPropagation(); handleDelete(idea.id); }}
|
||||
className="text-slate-300 hover:text-red-500"
|
||||
</div>
|
||||
|
||||
<div className="p-6 space-y-4 overflow-y-auto">
|
||||
<div>
|
||||
<label className="block text-xs font-bold text-theme-muted uppercase mb-1">Titre</label>
|
||||
<input
|
||||
type="text"
|
||||
value={editingItem.title}
|
||||
onChange={(e) => setEditingItem({ ...editingItem, title: e.target.value })}
|
||||
className="w-full p-3 bg-theme-bg border border-theme-border rounded-lg focus:ring-2 focus:ring-blue-500 outline-none font-bold text-theme-text transition-colors duration-300"
|
||||
placeholder="Titre de la tâche ou de l'idée..."
|
||||
autoFocus
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-xs font-bold text-theme-muted uppercase mb-1">Description</label>
|
||||
<textarea
|
||||
value={editingItem.description}
|
||||
onChange={(e) => setEditingItem({ ...editingItem, description: e.target.value })}
|
||||
maxLength={MAX_DESCRIPTION_LENGTH}
|
||||
className="w-full p-3 bg-theme-bg border border-theme-border rounded-lg focus:ring-2 focus:ring-blue-500 outline-none min-h-[120px] text-sm text-theme-text leading-relaxed resize-none transition-colors duration-300"
|
||||
placeholder="Détails, notes, liens..."
|
||||
/>
|
||||
<div className={`text-right text-xs mt-1 transition-colors ${(editingItem.description?.length || 0) >= MAX_DESCRIPTION_LENGTH ? 'text-red-500 font-bold' :
|
||||
(editingItem.description?.length || 0) > MAX_DESCRIPTION_LENGTH * 0.9 ? 'text-orange-500' : 'text-slate-400'
|
||||
}`}>
|
||||
{editingItem.description?.length || 0} / {MAX_DESCRIPTION_LENGTH} caractères
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className="block text-xs font-bold text-theme-muted uppercase mb-1">Catégorie</label>
|
||||
<select
|
||||
value={editingItem.category}
|
||||
onChange={(e) => setEditingItem({ ...editingItem, category: e.target.value as IdeaCategory })}
|
||||
className="w-full p-2 bg-theme-bg border border-theme-border rounded-lg text-theme-text text-sm outline-none focus:border-blue-500 transition-colors duration-300"
|
||||
>
|
||||
{Object.entries(CATEGORIES).map(([key, val]) => (
|
||||
<option key={key} value={key}>{val.label}</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-xs font-bold text-theme-muted uppercase mb-1">Statut</label>
|
||||
<select
|
||||
value={editingItem.status}
|
||||
onChange={(e) => setEditingItem({ ...editingItem, status: e.target.value as IdeaStatus })}
|
||||
className="w-full p-2 bg-theme-bg border border-theme-border rounded-lg text-theme-text text-sm outline-none focus:border-blue-500 transition-colors duration-300"
|
||||
>
|
||||
{Object.entries(STATUS_LABELS).map(([key, val]) => (
|
||||
<option key={key} value={key}>{val}</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="p-4 border-t border-theme-border bg-theme-bg flex justify-end gap-2 shrink-0 transition-colors duration-300">
|
||||
{editingItem.id && (
|
||||
<button
|
||||
onClick={() => handleDelete(editingItem.id!)}
|
||||
className="mr-auto text-red-500 hover:text-red-700 text-sm font-medium px-3 py-2"
|
||||
>
|
||||
Supprimer
|
||||
</button>
|
||||
)}
|
||||
<button
|
||||
onClick={() => setEditingItem(null)}
|
||||
className="px-4 py-2 text-theme-text hover:bg-theme-panel border border-transparent rounded-lg text-sm font-medium transition-colors duration-300"
|
||||
>
|
||||
<Trash2 size={14} />
|
||||
Annuler
|
||||
</button>
|
||||
<button
|
||||
onClick={handleSaveEdit}
|
||||
disabled={!editingItem.title?.trim()}
|
||||
className="px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-lg text-sm font-medium shadow-sm disabled:opacity-50 flex items-center gap-2"
|
||||
>
|
||||
<Save size={16} /> Enregistrer
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* CARD CONTENT */}
|
||||
<div className="mb-2">
|
||||
<h4 className="font-bold text-slate-800 text-sm mb-1 leading-tight">{idea.title}</h4>
|
||||
{idea.description && (
|
||||
<p className="text-xs text-slate-500 line-clamp-3 leading-relaxed" title={idea.description.length > 300 ? "Description tronquée (voir détail)" : undefined}>
|
||||
{truncatedDesc}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between items-center text-xs text-slate-400 border-t border-slate-50 pt-2 mt-2">
|
||||
<span className="flex items-center gap-1">
|
||||
<Clock size={10} /> {new Date(idea.createdAt).toLocaleDateString()}
|
||||
</span>
|
||||
<GripVertical size={14} className="opacity-20" />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
{columnIdeas.length === 0 && (
|
||||
<div className="h-full flex flex-col items-center justify-center text-slate-300 text-sm italic border-2 border-dashed border-indigo-200 rounded-lg m-1">
|
||||
<span className="mb-2">Vide</span>
|
||||
<span className="text-xs opacity-70">Double-cliquez pour ajouter</span>
|
||||
</div>
|
||||
)}
|
||||
)}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex flex-col h-full bg-white p-6 gap-6 relative">
|
||||
|
||||
{/* Header & Add Form (Top Bar) */}
|
||||
<div className="flex flex-col md:flex-row justify-between items-start md:items-center gap-4 bg-white p-4 rounded-xl border border-slate-200 shadow-sm shrink-0">
|
||||
<div>
|
||||
<h2 className="text-2xl font-bold text-slate-800 flex items-center gap-2">
|
||||
<Lightbulb className="text-yellow-500" /> Boîte à Idées
|
||||
</h2>
|
||||
<p className="text-slate-500 text-sm">Organisez vos tâches, idées de scènes et recherches.</p>
|
||||
</div>
|
||||
|
||||
<form onSubmit={handleAddIdea} className="flex-1 w-full md:w-auto max-w-2xl flex gap-2">
|
||||
<select
|
||||
value={newIdeaCategory}
|
||||
onChange={(e) => setNewIdeaCategory(e.target.value as IdeaCategory)}
|
||||
className="bg-[#eef2ff] border border-indigo-200 text-slate-700 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 p-2.5 outline-none"
|
||||
>
|
||||
{Object.entries(CATEGORIES).map(([key, val]) => (
|
||||
<option key={key} value={key}>{val.label}</option>
|
||||
))}
|
||||
</select>
|
||||
<input
|
||||
type="text"
|
||||
value={newIdeaTitle}
|
||||
onChange={(e) => setNewIdeaTitle(e.target.value)}
|
||||
placeholder="Titre de la nouvelle idée..."
|
||||
className="flex-1 bg-[#eef2ff] border border-indigo-200 text-slate-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 p-2.5 outline-none font-medium"
|
||||
/>
|
||||
<button
|
||||
type="submit"
|
||||
disabled={!newIdeaTitle.trim()}
|
||||
className="text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 disabled:opacity-50 transition-colors flex items-center gap-2"
|
||||
>
|
||||
<Plus size={18} />
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
{/* Kanban Board */}
|
||||
<div className="flex-1 grid grid-cols-1 md:grid-cols-3 gap-6 min-h-0">
|
||||
<Column title="Idées / À faire" status="todo" icon={Circle} />
|
||||
<Column title="En cours" status="progress" icon={Clock} />
|
||||
<Column title="Terminé" status="done" icon={CheckCircle} />
|
||||
</div>
|
||||
|
||||
{/* EDIT / QUICK ADD MODAL */}
|
||||
{editingItem && (
|
||||
<div className="absolute inset-0 z-50 flex items-center justify-center bg-black/40 backdrop-blur-sm p-4 animate-in fade-in duration-200">
|
||||
<div className="bg-white rounded-xl shadow-2xl w-full max-w-lg overflow-hidden flex flex-col max-h-[90%]">
|
||||
<div className="bg-[#eef2ff] border-b border-indigo-100 p-4 flex justify-between items-center">
|
||||
<h3 className="font-bold text-slate-700 flex items-center gap-2">
|
||||
{editingItem.id ? <Edit3 size={18}/> : <Plus size={18}/>}
|
||||
{editingItem.id ? 'Éditer la carte' : 'Ajouter une carte'}
|
||||
</h3>
|
||||
<button onClick={() => setEditingItem(null)} className="text-slate-400 hover:text-slate-600">
|
||||
<X size={20} />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="p-6 space-y-4 overflow-y-auto">
|
||||
<div>
|
||||
<label className="block text-xs font-bold text-slate-500 uppercase mb-1">Titre</label>
|
||||
<input
|
||||
type="text"
|
||||
value={editingItem.title}
|
||||
onChange={(e) => setEditingItem({...editingItem, title: e.target.value})}
|
||||
className="w-full p-3 border border-slate-300 rounded-lg focus:ring-2 focus:ring-blue-500 outline-none font-bold text-slate-800"
|
||||
placeholder="Titre de la tâche ou de l'idée..."
|
||||
autoFocus
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-xs font-bold text-slate-500 uppercase mb-1">Description</label>
|
||||
<textarea
|
||||
value={editingItem.description}
|
||||
onChange={(e) => setEditingItem({...editingItem, description: e.target.value})}
|
||||
maxLength={MAX_DESCRIPTION_LENGTH}
|
||||
className="w-full p-3 border border-slate-300 rounded-lg focus:ring-2 focus:ring-blue-500 outline-none min-h-[120px] text-sm text-slate-600 leading-relaxed resize-none"
|
||||
placeholder="Détails, notes, liens..."
|
||||
/>
|
||||
<div className={`text-right text-xs mt-1 transition-colors ${
|
||||
(editingItem.description?.length || 0) >= MAX_DESCRIPTION_LENGTH ? 'text-red-500 font-bold' :
|
||||
(editingItem.description?.length || 0) > MAX_DESCRIPTION_LENGTH * 0.9 ? 'text-orange-500' : 'text-slate-400'
|
||||
}`}>
|
||||
{editingItem.description?.length || 0} / {MAX_DESCRIPTION_LENGTH} caractères
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className="block text-xs font-bold text-slate-500 uppercase mb-1">Catégorie</label>
|
||||
<select
|
||||
value={editingItem.category}
|
||||
onChange={(e) => setEditingItem({...editingItem, category: e.target.value as IdeaCategory})}
|
||||
className="w-full p-2 bg-white border border-slate-300 rounded-lg text-sm outline-none focus:border-blue-500"
|
||||
>
|
||||
{Object.entries(CATEGORIES).map(([key, val]) => (
|
||||
<option key={key} value={key}>{val.label}</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-xs font-bold text-slate-500 uppercase mb-1">Statut</label>
|
||||
<select
|
||||
value={editingItem.status}
|
||||
onChange={(e) => setEditingItem({...editingItem, status: e.target.value as IdeaStatus})}
|
||||
className="w-full p-2 bg-white border border-slate-300 rounded-lg text-sm outline-none focus:border-blue-500"
|
||||
>
|
||||
{Object.entries(STATUS_LABELS).map(([key, val]) => (
|
||||
<option key={key} value={key}>{val}</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="p-4 border-t border-slate-200 bg-[#eef2ff] flex justify-end gap-2 shrink-0">
|
||||
{editingItem.id && (
|
||||
<button
|
||||
onClick={() => handleDelete(editingItem.id!)}
|
||||
className="mr-auto text-red-500 hover:text-red-700 text-sm font-medium px-3 py-2"
|
||||
>
|
||||
Supprimer
|
||||
</button>
|
||||
)}
|
||||
<button
|
||||
onClick={() => setEditingItem(null)}
|
||||
className="px-4 py-2 text-slate-600 hover:bg-slate-200 rounded-lg text-sm font-medium"
|
||||
>
|
||||
Annuler
|
||||
</button>
|
||||
<button
|
||||
onClick={handleSaveEdit}
|
||||
disabled={!editingItem.title?.trim()}
|
||||
className="px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-lg text-sm font-medium shadow-sm disabled:opacity-50 flex items-center gap-2"
|
||||
>
|
||||
<Save size={16} /> Enregistrer
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default IdeaBoard;
|
||||
@@ -315,7 +315,7 @@ const RichTextEditor = forwardRef<RichTextEditorHandle, RichTextEditorProps>(({
|
||||
const hasSelection = savedRange.current && !savedRange.current.collapsed;
|
||||
|
||||
return (
|
||||
<div className="flex flex-col h-full bg-white rounded-lg shadow-sm border border-slate-200 overflow-hidden relative">
|
||||
<div className="flex flex-col h-full bg-theme-panel rounded-lg shadow-sm border border-theme-border overflow-hidden relative transition-colors duration-300">
|
||||
<style>{`
|
||||
.editor-content:empty::before {
|
||||
content: attr(data-placeholder);
|
||||
@@ -326,7 +326,7 @@ const RichTextEditor = forwardRef<RichTextEditorHandle, RichTextEditorProps>(({
|
||||
`}</style>
|
||||
|
||||
{/* Toolbar */}
|
||||
<div className="flex items-center gap-1 p-2 bg-slate-50 border-b border-slate-200 flex-wrap relative z-20 shadow-sm">
|
||||
<div className="flex items-center gap-1 p-2 bg-theme-bg border-b border-theme-border flex-wrap relative z-20 shadow-sm transition-colors duration-300">
|
||||
<ToolbarButton icon={Bold} cmd="bold" label="Gras" />
|
||||
<ToolbarButton icon={Italic} cmd="italic" label="Italique" />
|
||||
<ToolbarButton icon={Underline} cmd="underline" label="Souligné" />
|
||||
@@ -360,7 +360,7 @@ const RichTextEditor = forwardRef<RichTextEditorHandle, RichTextEditorProps>(({
|
||||
|
||||
{/* Main Container - Scrollable Area */}
|
||||
<div
|
||||
className="flex-1 overflow-y-auto relative bg-slate-100"
|
||||
className="flex-1 overflow-y-auto relative bg-theme-bg/50 transition-colors duration-300"
|
||||
ref={scrollContainerRef}
|
||||
>
|
||||
<div className="flex justify-center relative min-h-full py-8">
|
||||
@@ -370,7 +370,7 @@ const RichTextEditor = forwardRef<RichTextEditorHandle, RichTextEditorProps>(({
|
||||
ref={contentRef}
|
||||
contentEditable
|
||||
suppressContentEditableWarning
|
||||
className="bg-white shadow-sm w-[800px] min-h-[1000px] p-12 outline-none font-serif text-lg leading-relaxed text-slate-900 editor-content"
|
||||
className="bg-theme-editor-bg shadow-sm w-[800px] min-h-[1000px] p-12 outline-none font-serif text-lg leading-relaxed text-theme-editor-text editor-content transition-colors duration-300"
|
||||
onInput={handleInput}
|
||||
onBlur={() => { setIsFocused(false); saveSelection(); }}
|
||||
onFocus={() => setIsFocused(true)}
|
||||
@@ -422,8 +422,8 @@ const RichTextEditor = forwardRef<RichTextEditorHandle, RichTextEditorProps>(({
|
||||
<Layers size={14} className="text-indigo-500" />
|
||||
)}
|
||||
<span className={`text-[10px] font-bold px-1.5 py-0.5 rounded uppercase tracking-wide ${latest.type.includes('Correction') ? 'bg-green-100 text-green-700' :
|
||||
latest.type.includes('Insertion') ? 'bg-blue-100 text-blue-700' :
|
||||
'bg-purple-100 text-purple-700'
|
||||
latest.type.includes('Insertion') ? 'bg-blue-100 text-blue-700' :
|
||||
'bg-purple-100 text-purple-700'
|
||||
}`}>
|
||||
{latest.type}
|
||||
</span>
|
||||
|
||||
@@ -609,19 +609,19 @@ const StoryWorkflow: React.FC<StoryWorkflowProps> = ({ data, onUpdate, entities,
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="h-full flex flex-col overflow-hidden bg-[#eef2ff] relative">
|
||||
<div className="h-12 bg-white border-b border-indigo-100 flex items-center justify-between px-4 z-10 shadow-sm shrink-0">
|
||||
<div className="h-full flex flex-col overflow-hidden bg-theme-bg relative transition-colors duration-300">
|
||||
<div className="h-12 bg-theme-panel border-b border-theme-border flex items-center justify-between px-4 z-10 shadow-sm shrink-0 transition-colors duration-300">
|
||||
<div className="flex items-center gap-2">
|
||||
<button onClick={handleAddNodeCenter} className="flex items-center gap-1.5 px-3 py-1.5 bg-indigo-600 text-white rounded-lg hover:bg-indigo-700 text-xs font-bold transition-all shadow-md shadow-indigo-100">
|
||||
<Plus size={14} /> AJOUTER NŒUD
|
||||
</button>
|
||||
<div className="w-px h-6 bg-slate-100 mx-2" />
|
||||
<div className="text-[10px] uppercase font-bold text-slate-400 tracking-wider">
|
||||
<div className="w-px h-6 bg-theme-border mx-2" />
|
||||
<div className="text-[10px] uppercase font-bold text-theme-muted tracking-wider">
|
||||
{selectedNodeIds.size > 0 ? `${selectedNodeIds.size} SÉLECTIONNÉ(S)` : 'Double-cliquez sur le canvas pour créer'}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<button onClick={handleDeleteSelected} disabled={selectedNodeIds.size === 0} className="p-2 text-red-500 hover:bg-red-50 rounded-lg disabled:opacity-30 transition-colors" title="Supprimer">
|
||||
<button onClick={handleDeleteSelected} disabled={selectedNodeIds.size === 0} className="p-2 text-red-500 hover:bg-red-500/10 rounded-lg disabled:opacity-30 transition-colors" title="Supprimer">
|
||||
<Trash2 size={16} />
|
||||
</button>
|
||||
</div>
|
||||
@@ -629,13 +629,13 @@ const StoryWorkflow: React.FC<StoryWorkflowProps> = ({ data, onUpdate, entities,
|
||||
|
||||
<div
|
||||
ref={containerRef}
|
||||
className="flex-1 overflow-auto relative cursor-grab active:cursor-grabbing bg-[#eef2ff]"
|
||||
className="flex-1 overflow-auto relative cursor-grab active:cursor-grabbing bg-theme-bg canvas-grid transition-colors duration-300"
|
||||
onMouseDown={handleCanvasMouseDown}
|
||||
onMouseMove={handleMouseMove}
|
||||
onMouseUp={handleMouseUp}
|
||||
onDoubleClick={handleCanvasDoubleClick}
|
||||
style={{
|
||||
backgroundImage: 'radial-gradient(#d1d5db 1px, transparent 1px)',
|
||||
backgroundImage: 'radial-gradient(var(--theme-border) 1px, transparent 1px)',
|
||||
backgroundSize: '24px 24px'
|
||||
}}
|
||||
>
|
||||
@@ -649,7 +649,7 @@ const StoryWorkflow: React.FC<StoryWorkflowProps> = ({ data, onUpdate, entities,
|
||||
const endX = target.x + CARD_WIDTH / 2;
|
||||
const endY = target.y + CARD_HEIGHT / 2;
|
||||
return (
|
||||
<line key={conn.id} x1={startX} y1={startY} x2={endX} y2={endY} stroke="#cbd5e1" strokeWidth="2" markerEnd="url(#arrowhead)" />
|
||||
<line key={conn.id} x1={startX} y1={startY} x2={endX} y2={endY} stroke="var(--theme-border)" strokeWidth="2" markerEnd="url(#arrowhead)" />
|
||||
);
|
||||
})}
|
||||
{connectingNodeId && (
|
||||
@@ -662,7 +662,7 @@ const StoryWorkflow: React.FC<StoryWorkflowProps> = ({ data, onUpdate, entities,
|
||||
)}
|
||||
<defs>
|
||||
<marker id="arrowhead" markerWidth="10" markerHeight="7" refX="28" refY="3.5" orient="auto">
|
||||
<path d="M0,0 L0,7 L10,3.5 Z" fill="#cbd5e1" />
|
||||
<path d="M0,0 L0,7 L10,3.5 Z" fill="var(--theme-border)" />
|
||||
</marker>
|
||||
<marker id="arrowhead-blue" markerWidth="10" markerHeight="7" refX="10" refY="3.5" orient="auto">
|
||||
<path d="M0,0 L0,7 L10,3.5 Z" fill="#6366f1" />
|
||||
|
||||
@@ -86,15 +86,26 @@ const UserProfileSettings: React.FC<UserProfileSettingsProps> = ({ user, onUpdat
|
||||
alert("Profil mis à jour !");
|
||||
};
|
||||
|
||||
const isDark = formData.theme === 'dark';
|
||||
const isSepia = formData.theme === 'sepia';
|
||||
|
||||
const themeOuterClass = isDark ? 'bg-slate-900 text-white' : isSepia ? 'bg-[#eaddc4] text-[#433422]' : 'bg-slate-50 text-slate-900';
|
||||
const themeInnerClass = isDark ? 'bg-slate-800 border-slate-700' : isSepia ? 'bg-[#f4ecd8] border-[#dfcdae]' : 'bg-white border-slate-200';
|
||||
const themeTextHeading = isDark ? 'text-white' : isSepia ? 'text-[#332616]' : 'text-slate-900';
|
||||
const themeTextMuted = isDark ? 'text-slate-400' : isSepia ? 'text-[#735e44]' : 'text-slate-500';
|
||||
const themeInputBg = isDark ? 'bg-slate-900 border-slate-700 text-white' : isSepia ? 'bg-[#fbf8f1] border-[#eaddc4] text-[#433422]' : 'bg-slate-50 border-slate-200 text-slate-900';
|
||||
const themeTabActive = isDark ? 'bg-white text-slate-900 shadow-lg' : isSepia ? 'bg-[#5c4731] text-white shadow-lg' : 'bg-slate-900 text-white shadow-lg';
|
||||
const themeTabInactive = isDark ? 'text-slate-400 hover:bg-slate-800 hover:text-white' : isSepia ? 'text-[#735e44] hover:bg-[#eaddc4] hover:text-[#332616]' : 'text-slate-500 hover:bg-white hover:text-slate-900';
|
||||
|
||||
return (
|
||||
<div className="h-full bg-slate-50 overflow-y-auto p-8 font-sans">
|
||||
<div className={`h-screen overflow-y-auto p-8 font-sans ${themeOuterClass}`}>
|
||||
<div className="max-w-4xl mx-auto">
|
||||
<div className="flex justify-between items-center mb-10">
|
||||
<div>
|
||||
<h1 className="text-3xl font-black text-slate-900">Mon Compte</h1>
|
||||
<p className="text-slate-500">Gérez vos informations personnelles et préférences d'écriture.</p>
|
||||
<h1 className={`text-3xl font-black ${themeTextHeading}`}>Mon Compte</h1>
|
||||
<p className={themeTextMuted}>Gérez vos informations personnelles et préférences d'écriture.</p>
|
||||
</div>
|
||||
<button onClick={onBack} className="bg-white border border-slate-200 px-4 py-2 rounded-lg text-sm font-bold hover:bg-slate-50 transition-colors">Fermer</button>
|
||||
<button onClick={onBack} className={`${themeInnerClass} px-4 py-2 rounded-lg text-sm font-bold opacity-80 hover:opacity-100 transition-opacity`}>Fermer</button>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col md:flex-row gap-8">
|
||||
@@ -102,29 +113,29 @@ const UserProfileSettings: React.FC<UserProfileSettingsProps> = ({ user, onUpdat
|
||||
<div className="w-full md:w-64 space-y-1">
|
||||
<button
|
||||
onClick={() => setActiveTab('profile')}
|
||||
className={`w-full flex items-center gap-3 px-4 py-3 rounded-xl text-sm font-bold transition-all ${activeTab === 'profile' ? 'bg-slate-900 text-white shadow-lg' : 'text-slate-500 hover:bg-white hover:text-slate-900'}`}
|
||||
className={`w-full flex items-center gap-3 px-4 py-3 rounded-xl text-sm font-bold transition-all ${activeTab === 'profile' ? themeTabActive : themeTabInactive}`}
|
||||
>
|
||||
<User size={18} /> Profil Public
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setActiveTab('preferences')}
|
||||
className={`w-full flex items-center gap-3 px-4 py-3 rounded-xl text-sm font-bold transition-all ${activeTab === 'preferences' ? 'bg-slate-900 text-white shadow-lg' : 'text-slate-500 hover:bg-white hover:text-slate-900'}`}
|
||||
className={`w-full flex items-center gap-3 px-4 py-3 rounded-xl text-sm font-bold transition-all ${activeTab === 'preferences' ? themeTabActive : themeTabInactive}`}
|
||||
>
|
||||
<Layout size={18} /> Interface & Écriture
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setActiveTab('account')}
|
||||
className={`w-full flex items-center gap-3 px-4 py-3 rounded-xl text-sm font-bold transition-all ${activeTab === 'account' ? 'bg-slate-900 text-white shadow-lg' : 'text-slate-500 hover:bg-white hover:text-slate-900'}`}
|
||||
className={`w-full flex items-center gap-3 px-4 py-3 rounded-xl text-sm font-bold transition-all ${activeTab === 'account' ? themeTabActive : themeTabInactive}`}
|
||||
>
|
||||
<Shield size={18} /> Sécurité & Plan
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Main Content Pane */}
|
||||
<div className="flex-1 bg-white rounded-2xl shadow-sm border border-slate-200 p-8">
|
||||
<div className={`flex-1 rounded-2xl shadow-sm border p-8 ${themeInnerClass}`}>
|
||||
{activeTab === 'profile' && (
|
||||
<div className="space-y-8 animate-in fade-in slide-in-from-bottom-4 duration-300">
|
||||
<div className="flex items-center gap-6 pb-8 border-b border-slate-100">
|
||||
<div className={`flex items-center gap-6 pb-8 border-b ${isDark ? 'border-slate-700' : isSepia ? 'border-[#dfcdae]' : 'border-slate-100'}`}>
|
||||
<div className="relative group cursor-pointer" onClick={() => fileInputRef.current?.click()}>
|
||||
<input
|
||||
type="file"
|
||||
@@ -133,14 +144,14 @@ const UserProfileSettings: React.FC<UserProfileSettingsProps> = ({ user, onUpdat
|
||||
accept="image/*"
|
||||
className="hidden"
|
||||
/>
|
||||
<img src={formData.avatar || 'https://via.placeholder.com/150'} className="w-24 h-24 rounded-full object-cover border-4 border-slate-50 shadow-md" alt="Avatar" />
|
||||
<img src={formData.avatar || 'https://via.placeholder.com/150'} className={`w-24 h-24 rounded-full object-cover border-4 shadow-md ${isDark ? 'border-slate-800' : isSepia ? 'border-[#f4ecd8]' : 'border-slate-50'}`} alt="Avatar" />
|
||||
<div className="absolute inset-0 bg-black/40 text-white rounded-full opacity-0 group-hover:opacity-100 flex items-center justify-center transition-opacity" title="Changer d'avatar">
|
||||
<Camera size={20} />
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="font-bold text-slate-900 text-lg">{user.name}</h3>
|
||||
<p className="text-slate-400 text-sm">Membre depuis Janvier 2024</p>
|
||||
<h3 className={`font-bold text-lg ${themeTextHeading}`}>{user.name}</h3>
|
||||
<p className={`text-sm ${themeTextMuted}`}>Membre depuis {new Date(user.subscription.startDate).toLocaleDateString('fr-FR', { month: 'long', year: 'numeric' })}</p>
|
||||
<div className="mt-2 flex gap-4">
|
||||
<div className="flex items-center gap-1.5 text-xs font-bold text-orange-500">
|
||||
<Flame size={14} fill="currentColor" /> {user.stats.writingStreak} jours de streak
|
||||
@@ -151,21 +162,21 @@ const UserProfileSettings: React.FC<UserProfileSettingsProps> = ({ user, onUpdat
|
||||
|
||||
<div className="grid grid-cols-1 gap-6">
|
||||
<div className="space-y-1">
|
||||
<label className="text-xs font-black text-slate-400 uppercase tracking-widest">Nom affiché</label>
|
||||
<label className={`text-xs font-black uppercase tracking-widest ${themeTextMuted}`}>Nom affiché</label>
|
||||
<input
|
||||
type="text"
|
||||
value={formData.name}
|
||||
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
|
||||
className="w-full p-3 bg-slate-50 border border-slate-200 rounded-xl outline-none focus:ring-2 focus:ring-blue-500"
|
||||
className={`w-full p-3 rounded-xl outline-none focus:ring-2 focus:ring-blue-500 ${themeInputBg}`}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-1">
|
||||
<label className="text-xs font-black text-slate-400 uppercase tracking-widest">Bio / Citation inspirante</label>
|
||||
<label className={`text-xs font-black uppercase tracking-widest ${themeTextMuted}`}>Bio / Citation inspirante</label>
|
||||
<textarea
|
||||
value={formData.bio}
|
||||
onChange={(e) => setFormData({ ...formData, bio: e.target.value })}
|
||||
className="w-full p-3 bg-slate-50 border border-slate-200 rounded-xl outline-none focus:ring-2 focus:ring-blue-500 h-24 resize-none"
|
||||
className={`w-full p-3 rounded-xl outline-none focus:ring-2 focus:ring-blue-500 h-24 resize-none ${themeInputBg}`}
|
||||
placeholder="Partagez quelques mots sur votre style..."
|
||||
/>
|
||||
</div>
|
||||
@@ -177,7 +188,7 @@ const UserProfileSettings: React.FC<UserProfileSettingsProps> = ({ user, onUpdat
|
||||
<div className="space-y-8 animate-in fade-in slide-in-from-bottom-4 duration-300">
|
||||
<div className="grid grid-cols-1 gap-8">
|
||||
<div className="space-y-3">
|
||||
<label className="text-xs font-black text-slate-400 uppercase tracking-widest flex items-center gap-2">
|
||||
<label className={`text-xs font-black uppercase tracking-widest flex items-center gap-2 ${themeTextMuted}`}>
|
||||
<Target size={14} /> Objectif quotidien de mots
|
||||
</label>
|
||||
<div className="flex items-center gap-4">
|
||||
@@ -192,7 +203,7 @@ const UserProfileSettings: React.FC<UserProfileSettingsProps> = ({ user, onUpdat
|
||||
</div>
|
||||
|
||||
<div className="space-y-3">
|
||||
<label className="text-xs font-black text-slate-400 uppercase tracking-widest flex items-center gap-2">
|
||||
<label className={`text-xs font-black uppercase tracking-widest flex items-center gap-2 ${themeTextMuted}`}>
|
||||
Thème de l'éditeur
|
||||
</label>
|
||||
<div className="grid grid-cols-3 gap-3">
|
||||
@@ -200,10 +211,10 @@ const UserProfileSettings: React.FC<UserProfileSettingsProps> = ({ user, onUpdat
|
||||
<button
|
||||
key={t}
|
||||
onClick={() => setFormData({ ...formData, theme: t as any })}
|
||||
className={`p-4 rounded-xl border-2 transition-all flex flex-col items-center gap-2 ${formData.theme === t ? 'border-blue-500 bg-blue-50 text-blue-700' : 'border-slate-100 hover:border-slate-200 text-slate-500'}`}
|
||||
className={`p-4 rounded-xl border-2 transition-all flex flex-col items-center gap-2 ${formData.theme === t ? 'border-blue-500 bg-blue-50 text-blue-700' : isDark ? 'border-slate-700 hover:border-slate-600' : isSepia ? 'border-[#dfcdae] hover:border-[#cfbd9e]' : 'border-slate-100 hover:border-slate-200'}`}
|
||||
>
|
||||
<div className={`w-8 h-8 rounded-full border border-slate-200 ${t === 'light' ? 'bg-white' : t === 'sepia' ? 'bg-[#f4ecd8]' : 'bg-slate-900'}`} />
|
||||
<span className="text-[10px] font-bold uppercase">{t}</span>
|
||||
<span className={`text-[10px] font-bold uppercase ${formData.theme !== t ? themeTextMuted : ''}`}>{t}</span>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
@@ -217,18 +228,18 @@ const UserProfileSettings: React.FC<UserProfileSettingsProps> = ({ user, onUpdat
|
||||
<div className="p-4 bg-blue-50 border border-blue-100 rounded-xl flex justify-between items-center">
|
||||
<div>
|
||||
<h4 className="font-bold text-blue-900">Plan {(user.subscription.planDetails?.displayName || user.subscription.plan).toUpperCase()}</h4>
|
||||
<p className="text-xs text-blue-700">Prochaine facturation le 15 Mars 2024</p>
|
||||
<p className="text-xs text-blue-700">Abonnement actif</p>
|
||||
</div>
|
||||
<button className="bg-blue-600 text-white px-4 py-2 rounded-lg text-xs font-bold hover:bg-blue-700 shadow-md shadow-blue-200">Gérer</button>
|
||||
</div>
|
||||
|
||||
<div className="space-y-1">
|
||||
<label className="text-xs font-black text-slate-400 uppercase tracking-widest">Email du compte</label>
|
||||
<label className={`text-xs font-black uppercase tracking-widest ${themeTextMuted}`}>Email du compte</label>
|
||||
<input
|
||||
type="email"
|
||||
value={formData.email}
|
||||
onChange={(e) => setFormData({ ...formData, email: e.target.value })}
|
||||
className="w-full p-3 bg-slate-50 border border-slate-200 rounded-xl outline-none focus:ring-2 focus:ring-blue-500"
|
||||
className={`w-full p-3 rounded-xl outline-none focus:ring-2 focus:ring-blue-500 ${themeInputBg}`}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -238,10 +249,10 @@ const UserProfileSettings: React.FC<UserProfileSettingsProps> = ({ user, onUpdat
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="mt-12 pt-8 border-t border-slate-100 flex justify-end">
|
||||
<div className={`mt-12 pt-8 border-t flex justify-end ${isDark ? 'border-slate-700' : isSepia ? 'border-[#dfcdae]' : 'border-slate-100'}`}>
|
||||
<button
|
||||
onClick={handleSave}
|
||||
className="bg-slate-900 text-white px-8 py-3 rounded-xl font-bold flex items-center gap-2 hover:bg-blue-600 transition-all shadow-xl hover:shadow-blue-200"
|
||||
className={`px-8 py-3 rounded-xl font-bold flex items-center gap-2 transition-all shadow-xl hover:shadow-blue-200 ${isDark ? 'bg-white text-slate-900 hover:bg-blue-500 hover:text-white' : isSepia ? 'bg-[#5c4731] text-white hover:bg-blue-600' : 'bg-slate-900 text-white hover:bg-blue-600'}`}
|
||||
>
|
||||
<Save size={18} /> Sauvegarder les modifications
|
||||
</button>
|
||||
|
||||
@@ -204,22 +204,22 @@ const WorldBuilder: React.FC<WorldBuilderProps> = ({ entities, onCreate, onUpdat
|
||||
const attrs = tempEntity.attributes;
|
||||
|
||||
return (
|
||||
<div className="space-y-8 border-t border-slate-100 pt-6 mt-4">
|
||||
<div className="space-y-8 border-t border-theme-border pt-6 mt-4">
|
||||
|
||||
{/* SECTION 1: ROLE & ARCHETYPE */}
|
||||
<div className="bg-[#eef2ff] p-4 rounded-lg border border-indigo-100">
|
||||
<h3 className="text-sm font-bold text-slate-700 uppercase mb-4 flex items-center gap-2">
|
||||
<div className="bg-theme-bg p-4 rounded-lg border border-theme-border">
|
||||
<h3 className="text-sm font-bold text-theme-text uppercase mb-4 flex items-center gap-2">
|
||||
<User size={16} /> Identité Narrative
|
||||
</h3>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<div>
|
||||
<label className="block text-xs font-semibold text-slate-500 mb-2">Archétype</label>
|
||||
<label className="block text-xs font-semibold text-theme-muted mb-2">Archétype</label>
|
||||
<input
|
||||
type="text"
|
||||
list="archetype-suggestions"
|
||||
value={attrs.archetype}
|
||||
onChange={(e) => updateAttribute('archetype', e.target.value)}
|
||||
className="w-full p-2 bg-[#eef2ff] border border-slate-300 rounded text-sm outline-none focus:border-blue-500"
|
||||
className="w-full p-2 bg-theme-bg border border-theme-border rounded text-sm outline-none focus:border-blue-500"
|
||||
placeholder="Ex: Le Héros, Le Sage..."
|
||||
/>
|
||||
<datalist id="archetype-suggestions">
|
||||
@@ -227,7 +227,7 @@ const WorldBuilder: React.FC<WorldBuilderProps> = ({ entities, onCreate, onUpdat
|
||||
</datalist>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-xs font-semibold text-slate-500 mb-2">Rôle dans l'histoire</label>
|
||||
<label className="block text-xs font-semibold text-theme-muted mb-2">Rôle dans l'histoire</label>
|
||||
<div className="flex gap-2 flex-wrap">
|
||||
{[
|
||||
{ val: 'protagonist', label: 'Protagoniste' },
|
||||
@@ -235,7 +235,7 @@ const WorldBuilder: React.FC<WorldBuilderProps> = ({ entities, onCreate, onUpdat
|
||||
{ val: 'support', label: 'Secondaire' },
|
||||
{ val: 'extra', label: 'Figurant' }
|
||||
].map(opt => (
|
||||
<label key={opt.val} className={`cursor-pointer px-3 py-1.5 rounded text-xs border transition-colors ${attrs.role === opt.val ? 'bg-indigo-100 border-indigo-300 text-indigo-700 font-bold' : 'bg-[#eef2ff] border-slate-200 text-slate-600 hover:bg-slate-100'}`}>
|
||||
<label key={opt.val} className={`cursor-pointer px-3 py-1.5 rounded text-xs border transition-colors ${attrs.role === opt.val ? 'bg-indigo-100 border-indigo-300 text-indigo-700 font-bold' : 'bg-theme-bg border-theme-border text-theme-muted hover:bg-theme-border'}`}>
|
||||
<input
|
||||
type="radio"
|
||||
name="role"
|
||||
@@ -253,15 +253,15 @@ const WorldBuilder: React.FC<WorldBuilderProps> = ({ entities, onCreate, onUpdat
|
||||
</div>
|
||||
|
||||
{/* SECTION 2: PHYSIQUE */}
|
||||
<div className="bg-[#eef2ff] p-4 rounded-lg border border-indigo-100">
|
||||
<h3 className="text-sm font-bold text-slate-700 uppercase mb-4 flex items-center gap-2">
|
||||
<div className="bg-theme-bg p-4 rounded-lg border border-theme-border">
|
||||
<h3 className="text-sm font-bold text-theme-text uppercase mb-4 flex items-center gap-2">
|
||||
<Ruler size={16} /> Apparence Physique
|
||||
</h3>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<div className="space-y-6">
|
||||
<div>
|
||||
<div className="flex justify-between text-xs mb-1">
|
||||
<label className="font-semibold text-slate-600">Âge (ans)</label>
|
||||
<label className="font-semibold text-theme-muted">Âge (ans)</label>
|
||||
</div>
|
||||
<div className="flex items-center gap-3">
|
||||
<input
|
||||
@@ -274,14 +274,14 @@ const WorldBuilder: React.FC<WorldBuilderProps> = ({ entities, onCreate, onUpdat
|
||||
type="number"
|
||||
value={attrs.age}
|
||||
onChange={(e) => updateAttribute('age', parseInt(e.target.value))}
|
||||
className="w-20 p-1 text-right text-sm border border-slate-300 rounded font-mono text-indigo-700 bg-[#eef2ff] focus:border-indigo-500 outline-none"
|
||||
className="w-20 p-1 text-right text-sm border border-theme-border rounded font-mono text-indigo-700 bg-theme-bg focus:border-indigo-500 outline-none"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div className="flex justify-between text-xs mb-1">
|
||||
<label className="font-semibold text-slate-600">Taille (cm)</label>
|
||||
<label className="font-semibold text-theme-muted">Taille (cm)</label>
|
||||
</div>
|
||||
<div className="flex items-center gap-3">
|
||||
<input
|
||||
@@ -294,7 +294,7 @@ const WorldBuilder: React.FC<WorldBuilderProps> = ({ entities, onCreate, onUpdat
|
||||
type="number"
|
||||
value={attrs.height}
|
||||
onChange={(e) => updateAttribute('height', parseInt(e.target.value))}
|
||||
className="w-20 p-1 text-right text-sm border border-slate-300 rounded font-mono text-indigo-700 bg-[#eef2ff] focus:border-indigo-500 outline-none"
|
||||
className="w-20 p-1 text-right text-sm border border-theme-border rounded font-mono text-indigo-700 bg-theme-bg focus:border-indigo-500 outline-none"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -303,34 +303,34 @@ const WorldBuilder: React.FC<WorldBuilderProps> = ({ entities, onCreate, onUpdat
|
||||
<div className="space-y-4">
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className="block text-xs font-semibold text-slate-500 mb-1">Cheveux</label>
|
||||
<label className="block text-xs font-semibold text-theme-muted mb-1">Cheveux</label>
|
||||
<select
|
||||
value={attrs.hair}
|
||||
onChange={(e) => updateAttribute('hair', e.target.value)}
|
||||
className="w-full p-2 bg-[#eef2ff] border border-slate-300 rounded text-sm"
|
||||
className="w-full p-2 bg-theme-bg border border-theme-border rounded text-sm"
|
||||
>
|
||||
{HAIR_COLORS.map(c => <option key={c} value={c}>{c}</option>)}
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-xs font-semibold text-slate-500 mb-1">Yeux</label>
|
||||
<label className="block text-xs font-semibold text-theme-muted mb-1">Yeux</label>
|
||||
<select
|
||||
value={attrs.eyes}
|
||||
onChange={(e) => updateAttribute('eyes', e.target.value)}
|
||||
className="w-full p-2 bg-[#eef2ff] border border-slate-300 rounded text-sm"
|
||||
className="w-full p-2 bg-theme-bg border border-theme-border rounded text-sm"
|
||||
>
|
||||
{EYE_COLORS.map(c => <option key={c} value={c}>{c}</option>)}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-xs font-semibold text-slate-500 mb-1">Signe distinctif</label>
|
||||
<label className="block text-xs font-semibold text-theme-muted mb-1">Signe distinctif</label>
|
||||
<input
|
||||
type="text"
|
||||
value={attrs.physicalQuirk}
|
||||
onChange={(e) => updateAttribute('physicalQuirk', e.target.value)}
|
||||
placeholder="Cicatrice, tatouage..."
|
||||
className="w-full p-2 bg-[#eef2ff] border border-slate-300 rounded text-sm outline-none focus:border-indigo-400"
|
||||
className="w-full p-2 bg-theme-bg border border-theme-border rounded text-sm outline-none focus:border-indigo-400"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -338,15 +338,15 @@ const WorldBuilder: React.FC<WorldBuilderProps> = ({ entities, onCreate, onUpdat
|
||||
</div>
|
||||
|
||||
{/* SECTION 3: PSYCHOLOGIE */}
|
||||
<div className="bg-[#eef2ff] p-4 rounded-lg border border-indigo-100">
|
||||
<h3 className="text-sm font-bold text-slate-700 uppercase mb-4 flex items-center gap-2">
|
||||
<div className="bg-theme-bg p-4 rounded-lg border border-theme-border">
|
||||
<h3 className="text-sm font-bold text-theme-text uppercase mb-4 flex items-center gap-2">
|
||||
<Brain size={16} /> Psychologie & Comportement
|
||||
</h3>
|
||||
|
||||
<div className="space-y-6">
|
||||
<div className="space-y-4 px-2">
|
||||
<div className="relative pt-1">
|
||||
<div className="flex justify-between text-[10px] uppercase font-bold text-slate-500 mb-1">
|
||||
<div className="flex justify-between text-[10px] uppercase font-bold text-theme-muted mb-1">
|
||||
<span>Introverti</span>
|
||||
<span>Extraverti</span>
|
||||
</div>
|
||||
@@ -358,7 +358,7 @@ const WorldBuilder: React.FC<WorldBuilderProps> = ({ entities, onCreate, onUpdat
|
||||
/>
|
||||
</div>
|
||||
<div className="relative pt-1">
|
||||
<div className="flex justify-between text-[10px] uppercase font-bold text-slate-500 mb-1">
|
||||
<div className="flex justify-between text-[10px] uppercase font-bold text-theme-muted mb-1">
|
||||
<span>Émotionnel</span>
|
||||
<span>Rationnel</span>
|
||||
</div>
|
||||
@@ -370,7 +370,7 @@ const WorldBuilder: React.FC<WorldBuilderProps> = ({ entities, onCreate, onUpdat
|
||||
/>
|
||||
</div>
|
||||
<div className="relative pt-1">
|
||||
<div className="flex justify-between text-[10px] uppercase font-bold text-slate-500 mb-1">
|
||||
<div className="flex justify-between text-[10px] uppercase font-bold text-theme-muted mb-1">
|
||||
<span>Chaotique</span>
|
||||
<span>Loyal</span>
|
||||
</div>
|
||||
@@ -383,14 +383,14 @@ const WorldBuilder: React.FC<WorldBuilderProps> = ({ entities, onCreate, onUpdat
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="border-t border-slate-200 pt-4">
|
||||
<label className="block text-xs font-semibold text-slate-500 mb-1">Toc ou habitude comportementale</label>
|
||||
<div className="border-t border-theme-border pt-4">
|
||||
<label className="block text-xs font-semibold text-theme-muted mb-1">Toc ou habitude comportementale</label>
|
||||
<input
|
||||
type="text"
|
||||
value={attrs.behavioralQuirk}
|
||||
onChange={(e) => updateAttribute('behavioralQuirk', e.target.value)}
|
||||
placeholder="Joue avec sa bague, bégaie quand il ment..."
|
||||
className="w-full p-2 bg-[#eef2ff] border border-slate-300 rounded text-sm outline-none focus:border-indigo-400"
|
||||
className="w-full p-2 bg-theme-bg border border-theme-border rounded text-sm outline-none focus:border-indigo-400"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -404,8 +404,8 @@ const WorldBuilder: React.FC<WorldBuilderProps> = ({ entities, onCreate, onUpdat
|
||||
if (!currentTemplate || currentTemplate.fields.length === 0) return null;
|
||||
|
||||
return (
|
||||
<div className="bg-[#eef2ff] p-4 rounded-lg border border-indigo-100 mt-6">
|
||||
<h3 className="text-sm font-bold text-slate-700 uppercase mb-4 flex items-center gap-2">
|
||||
<div className="bg-theme-bg p-4 rounded-lg border border-theme-border mt-6">
|
||||
<h3 className="text-sm font-bold text-theme-text uppercase mb-4 flex items-center gap-2">
|
||||
<List size={16} /> Champs Personnalisés
|
||||
</h3>
|
||||
<div className="grid grid-cols-1 gap-4">
|
||||
@@ -414,20 +414,20 @@ const WorldBuilder: React.FC<WorldBuilderProps> = ({ entities, onCreate, onUpdat
|
||||
|
||||
return (
|
||||
<div key={field.id}>
|
||||
<label className="block text-xs font-semibold text-slate-500 mb-1">{field.label}</label>
|
||||
<label className="block text-xs font-semibold text-theme-muted mb-1">{field.label}</label>
|
||||
|
||||
{field.type === 'textarea' ? (
|
||||
<textarea
|
||||
value={value}
|
||||
onChange={(e) => updateCustomValue(field.id, e.target.value)}
|
||||
className="w-full p-2 bg-[#eef2ff] border border-slate-300 rounded text-sm outline-none focus:border-indigo-400"
|
||||
className="w-full p-2 bg-theme-bg border border-theme-border rounded text-sm outline-none focus:border-indigo-400"
|
||||
placeholder={field.placeholder}
|
||||
/>
|
||||
) : field.type === 'select' ? (
|
||||
<select
|
||||
value={value}
|
||||
onChange={(e) => updateCustomValue(field.id, e.target.value)}
|
||||
className="w-full p-2 bg-[#eef2ff] border border-slate-300 rounded text-sm outline-none focus:border-indigo-400"
|
||||
className="w-full p-2 bg-theme-bg border border-theme-border rounded text-sm outline-none focus:border-indigo-400"
|
||||
>
|
||||
<option value="">Sélectionner...</option>
|
||||
{field.options?.map(opt => (
|
||||
@@ -440,16 +440,16 @@ const WorldBuilder: React.FC<WorldBuilderProps> = ({ entities, onCreate, onUpdat
|
||||
type="checkbox"
|
||||
checked={!!value}
|
||||
onChange={(e) => updateCustomValue(field.id, e.target.checked)}
|
||||
className="w-4 h-4 text-indigo-600 rounded border-slate-300 focus:ring-indigo-500"
|
||||
className="w-4 h-4 text-indigo-600 rounded border-theme-border focus:ring-indigo-500"
|
||||
/>
|
||||
<span className="text-sm text-slate-700">Activé / Oui</span>
|
||||
<span className="text-sm text-theme-text">Activé / Oui</span>
|
||||
</label>
|
||||
) : (
|
||||
<input
|
||||
type={field.type === 'number' ? 'number' : 'text'}
|
||||
value={value}
|
||||
onChange={(e) => updateCustomValue(field.id, e.target.value)}
|
||||
className="w-full p-2 bg-[#eef2ff] border border-slate-300 rounded text-sm outline-none focus:border-indigo-400"
|
||||
className="w-full p-2 bg-theme-bg border border-theme-border rounded text-sm outline-none focus:border-indigo-400"
|
||||
placeholder={field.placeholder}
|
||||
/>
|
||||
)}
|
||||
@@ -465,29 +465,29 @@ const WorldBuilder: React.FC<WorldBuilderProps> = ({ entities, onCreate, onUpdat
|
||||
const template = templates.find(t => t.entityType === activeTemplateType) || { entityType: activeTemplateType, fields: [] };
|
||||
|
||||
return (
|
||||
<div className="flex-1 bg-white rounded-xl shadow-lg border border-slate-200 p-8 overflow-y-auto">
|
||||
<div className="flex-1 bg-theme-panel rounded-xl shadow-lg border border-theme-border p-8 overflow-y-auto">
|
||||
<div className="flex justify-between items-start mb-6">
|
||||
<div>
|
||||
<h2 className="text-2xl font-bold text-slate-800 flex items-center gap-2">
|
||||
<h2 className="text-2xl font-bold text-theme-text flex items-center gap-2">
|
||||
<Layout size={24} className="text-indigo-600" /> Éditeur de Modèles
|
||||
</h2>
|
||||
<p className="text-slate-500 text-sm mt-1">
|
||||
<p className="text-theme-muted text-sm mt-1">
|
||||
Configurez les champs personnalisés pour chaque type de fiche.
|
||||
</p>
|
||||
</div>
|
||||
<button onClick={() => setMode('entities')} className="p-2 text-slate-500 hover:bg-slate-100 rounded-full">
|
||||
<button onClick={() => setMode('entities')} className="p-2 text-theme-muted hover:bg-theme-border rounded-full">
|
||||
<X size={20} />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="flex gap-2 mb-8 border-b border-slate-200 pb-1">
|
||||
<div className="flex gap-2 mb-8 border-b border-theme-border pb-1">
|
||||
{Object.values(EntityType).map(type => (
|
||||
<button
|
||||
key={type}
|
||||
onClick={() => setActiveTemplateType(type)}
|
||||
className={`px-4 py-2 text-sm font-medium rounded-t-lg transition-colors ${activeTemplateType === type
|
||||
? 'bg-indigo-50 text-indigo-700 border-b-2 border-indigo-600'
|
||||
: 'text-slate-500 hover:text-slate-800 hover:bg-slate-50'
|
||||
? 'bg-indigo-500/10 text-indigo-700 border-b-2 border-indigo-600'
|
||||
: 'text-theme-muted hover:text-theme-text hover:bg-theme-panel/50'
|
||||
}`}
|
||||
>
|
||||
{type}
|
||||
@@ -497,23 +497,23 @@ const WorldBuilder: React.FC<WorldBuilderProps> = ({ entities, onCreate, onUpdat
|
||||
|
||||
<div className="space-y-4">
|
||||
{template.fields.map((field, idx) => (
|
||||
<div key={field.id} className="bg-[#eef2ff] border border-indigo-100 rounded-lg p-4 flex gap-4 items-start group">
|
||||
<div key={field.id} className="bg-theme-bg border border-theme-border rounded-lg p-4 flex gap-4 items-start group">
|
||||
<div className="flex-1 grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className="block text-xs font-semibold text-slate-500 mb-1">Nom du champ</label>
|
||||
<label className="block text-xs font-semibold text-theme-muted mb-1">Nom du champ</label>
|
||||
<input
|
||||
type="text"
|
||||
value={field.label}
|
||||
onChange={(e) => updateCustomField(activeTemplateType, field.id, { label: e.target.value })}
|
||||
className="w-full p-2 bg-[#eef2ff] border border-slate-300 rounded text-sm"
|
||||
className="w-full p-2 bg-theme-bg border border-theme-border rounded text-sm"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-xs font-semibold text-slate-500 mb-1">Type</label>
|
||||
<label className="block text-xs font-semibold text-theme-muted mb-1">Type</label>
|
||||
<select
|
||||
value={field.type}
|
||||
onChange={(e) => updateCustomField(activeTemplateType, field.id, { type: e.target.value as CustomFieldType })}
|
||||
className="w-full p-2 bg-[#eef2ff] border border-slate-300 rounded text-sm"
|
||||
className="w-full p-2 bg-theme-bg border border-theme-border rounded text-sm"
|
||||
>
|
||||
<option value="text">Texte court</option>
|
||||
<option value="textarea">Texte long</option>
|
||||
@@ -524,12 +524,12 @@ const WorldBuilder: React.FC<WorldBuilderProps> = ({ entities, onCreate, onUpdat
|
||||
</div>
|
||||
{field.type === 'select' && (
|
||||
<div className="col-span-2">
|
||||
<label className="block text-xs font-semibold text-slate-500 mb-1">Options (séparées par des virgules)</label>
|
||||
<label className="block text-xs font-semibold text-theme-muted mb-1">Options (séparées par des virgules)</label>
|
||||
<input
|
||||
type="text"
|
||||
value={field.options?.join(',') || ''}
|
||||
onChange={(e) => updateCustomField(activeTemplateType, field.id, { options: e.target.value.split(',').map(s => s.trim()) })}
|
||||
className="w-full p-2 bg-[#eef2ff] border border-slate-300 rounded text-sm"
|
||||
className="w-full p-2 bg-theme-bg border border-theme-border rounded text-sm"
|
||||
placeholder="Option A, Option B, Option C"
|
||||
/>
|
||||
</div>
|
||||
@@ -537,7 +537,7 @@ const WorldBuilder: React.FC<WorldBuilderProps> = ({ entities, onCreate, onUpdat
|
||||
</div>
|
||||
<button
|
||||
onClick={() => deleteCustomField(activeTemplateType, field.id)}
|
||||
className="p-2 text-slate-400 hover:text-red-600 hover:bg-red-50 rounded mt-5"
|
||||
className="p-2 text-theme-muted hover:text-red-600 hover:bg-red-50 rounded mt-5"
|
||||
>
|
||||
<Trash2 size={16} />
|
||||
</button>
|
||||
@@ -546,7 +546,7 @@ const WorldBuilder: React.FC<WorldBuilderProps> = ({ entities, onCreate, onUpdat
|
||||
|
||||
<button
|
||||
onClick={() => addCustomField(activeTemplateType)}
|
||||
className="w-full py-3 border-2 border-dashed border-slate-300 rounded-lg text-slate-500 hover:border-indigo-400 hover:text-indigo-600 hover:bg-indigo-50 transition-all flex items-center justify-center gap-2"
|
||||
className="w-full py-3 border-2 border-dashed border-theme-border rounded-lg text-theme-muted hover:border-indigo-400 hover:text-indigo-600 hover:bg-indigo-500/10 transition-all flex items-center justify-center gap-2"
|
||||
>
|
||||
<Plus size={20} /> Ajouter un champ
|
||||
</button>
|
||||
@@ -557,14 +557,14 @@ const WorldBuilder: React.FC<WorldBuilderProps> = ({ entities, onCreate, onUpdat
|
||||
|
||||
if (mode === 'templates') {
|
||||
return (
|
||||
<div className="flex h-full gap-6 p-6 bg-[#eef2ff]">
|
||||
<div className="flex h-full gap-6 p-6 bg-theme-bg">
|
||||
<div className="w-1/3 opacity-50 pointer-events-none filter blur-[1px]">
|
||||
<div className="bg-white rounded-lg p-6 shadow-sm border border-slate-200">
|
||||
<h3 className="font-bold text-slate-700 mb-4">Aperçu Fiches</h3>
|
||||
<div className="bg-theme-panel rounded-lg p-6 shadow-sm border border-theme-border">
|
||||
<h3 className="font-bold text-theme-text mb-4">Aperçu Fiches</h3>
|
||||
<div className="space-y-2">
|
||||
<div className="h-10 bg-indigo-50 rounded"></div>
|
||||
<div className="h-10 bg-indigo-50 rounded"></div>
|
||||
<div className="h-10 bg-indigo-50 rounded"></div>
|
||||
<div className="h-10 bg-indigo-500/10 rounded"></div>
|
||||
<div className="h-10 bg-indigo-500/10 rounded"></div>
|
||||
<div className="h-10 bg-indigo-500/10 rounded"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -574,10 +574,10 @@ const WorldBuilder: React.FC<WorldBuilderProps> = ({ entities, onCreate, onUpdat
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex h-full gap-6 p-6 bg-[#eef2ff]">
|
||||
<div className="flex h-full gap-6 p-6 bg-theme-bg">
|
||||
<div className="w-1/3 flex flex-col gap-4">
|
||||
<div className="flex justify-between items-center px-1">
|
||||
<h2 className="text-lg font-bold text-slate-700">Explorateur</h2>
|
||||
<h2 className="text-lg font-bold text-theme-text">Explorateur</h2>
|
||||
<button
|
||||
onClick={() => setMode('templates')}
|
||||
className="flex items-center gap-1.5 px-3 py-1.5 bg-indigo-100 text-indigo-700 hover:bg-indigo-200 rounded text-xs font-medium transition-colors"
|
||||
@@ -589,9 +589,9 @@ const WorldBuilder: React.FC<WorldBuilderProps> = ({ entities, onCreate, onUpdat
|
||||
|
||||
<div className="space-y-6 overflow-y-auto pr-2 pb-4 flex-1">
|
||||
{Object.values(EntityType).map(type => (
|
||||
<div key={type} className="bg-white rounded-lg shadow-sm border border-slate-200 overflow-hidden">
|
||||
<div className="bg-indigo-50 p-3 border-b border-indigo-100 flex justify-between items-center">
|
||||
<h3 className="font-semibold text-slate-700 flex items-center gap-2">
|
||||
<div key={type} className="bg-theme-panel rounded-lg shadow-sm border border-theme-border overflow-hidden">
|
||||
<div className="bg-indigo-500/10 p-3 border-b border-theme-border flex justify-between items-center">
|
||||
<h3 className="font-semibold text-theme-text flex items-center gap-2">
|
||||
<span>{ENTITY_ICONS[type]}</span> {type}s
|
||||
</h3>
|
||||
<button
|
||||
@@ -603,17 +603,17 @@ const WorldBuilder: React.FC<WorldBuilderProps> = ({ entities, onCreate, onUpdat
|
||||
</div>
|
||||
<div className="divide-y divide-slate-100">
|
||||
{filterByType(type).length === 0 && (
|
||||
<p className="p-4 text-sm text-slate-400 italic text-center">Aucun élément</p>
|
||||
<p className="p-4 text-sm text-theme-muted italic text-center">Aucun élément</p>
|
||||
)}
|
||||
{filterByType(type).map(entity => (
|
||||
<div
|
||||
key={entity.id}
|
||||
onClick={() => handleEdit(entity)}
|
||||
className={`p-3 cursor-pointer hover:bg-blue-50 transition-colors flex justify-between group ${editingId === entity.id ? 'bg-blue-50 border-l-4 border-blue-500' : ''}`}
|
||||
className={`p-3 cursor-pointer hover:bg-blue-500/10 transition-colors flex justify-between group ${editingId === entity.id ? 'bg-blue-500/10 border-l-4 border-blue-500' : ''}`}
|
||||
>
|
||||
<div>
|
||||
<div className="font-medium text-slate-800">{entity.name}</div>
|
||||
<div className="text-xs text-slate-500 truncate">{entity.description}</div>
|
||||
<div className="font-medium text-theme-text">{entity.name}</div>
|
||||
<div className="text-xs text-theme-muted truncate">{entity.description}</div>
|
||||
</div>
|
||||
<button
|
||||
onClick={(e) => { e.stopPropagation(); handleDelete(entity.id); }}
|
||||
@@ -629,7 +629,7 @@ const WorldBuilder: React.FC<WorldBuilderProps> = ({ entities, onCreate, onUpdat
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex-1 bg-white rounded-xl shadow-lg border border-slate-200 p-8 overflow-y-auto">
|
||||
<div className="flex-1 bg-theme-panel rounded-xl shadow-lg border border-theme-border p-8 overflow-y-auto">
|
||||
{editingId && tempEntity ? (
|
||||
<div className="space-y-6 animate-in fade-in duration-200">
|
||||
<div className="flex justify-between items-start">
|
||||
@@ -637,12 +637,12 @@ const WorldBuilder: React.FC<WorldBuilderProps> = ({ entities, onCreate, onUpdat
|
||||
<span className={`inline-block px-2 py-1 rounded text-xs font-bold uppercase tracking-wider ${ENTITY_COLORS[tempEntity.type]}`}>
|
||||
{tempEntity.type}
|
||||
</span>
|
||||
<h2 className="text-2xl font-bold text-slate-800">
|
||||
<h2 className="text-2xl font-bold text-theme-text">
|
||||
{tempEntity.type === EntityType.CHARACTER ? 'Fiche Personnage' : 'Édition de la fiche'}
|
||||
</h2>
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<button onClick={() => setEditingId(null)} className="p-2 text-slate-500 hover:bg-slate-100 rounded-full">
|
||||
<button onClick={() => setEditingId(null)} className="p-2 text-theme-muted hover:bg-theme-border rounded-full">
|
||||
<X size={20} />
|
||||
</button>
|
||||
</div>
|
||||
@@ -650,22 +650,22 @@ const WorldBuilder: React.FC<WorldBuilderProps> = ({ entities, onCreate, onUpdat
|
||||
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 mb-1">Nom</label>
|
||||
<label className="block text-sm font-medium text-theme-text mb-1">Nom</label>
|
||||
<input
|
||||
type="text"
|
||||
value={tempEntity.name}
|
||||
onChange={e => setTempEntity({ ...tempEntity, name: e.target.value })}
|
||||
className="w-full p-2 bg-[#eef2ff] border border-slate-300 rounded focus:ring-2 focus:ring-blue-500 outline-none font-serif text-lg"
|
||||
className="w-full p-2 bg-theme-bg border border-theme-border rounded focus:ring-2 focus:ring-blue-500 outline-none font-serif text-lg"
|
||||
placeholder="Ex: Gandalf le Gris"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 mb-1">Description Courte (pour l'IA)</label>
|
||||
<label className="block text-sm font-medium text-theme-text mb-1">Description Courte (pour l'IA)</label>
|
||||
<textarea
|
||||
value={tempEntity.description}
|
||||
onChange={e => setTempEntity({ ...tempEntity, description: e.target.value })}
|
||||
className="w-full p-2 bg-[#eef2ff] border border-slate-300 rounded focus:ring-2 focus:ring-blue-500 outline-none text-sm h-20"
|
||||
className="w-full p-2 bg-theme-bg border border-theme-border rounded focus:ring-2 focus:ring-blue-500 outline-none text-sm h-20"
|
||||
placeholder="Un magicien puissant qui guide la communauté..."
|
||||
/>
|
||||
</div>
|
||||
@@ -674,7 +674,7 @@ const WorldBuilder: React.FC<WorldBuilderProps> = ({ entities, onCreate, onUpdat
|
||||
|
||||
{renderCustomFieldsEditor()}
|
||||
|
||||
<div className="mt-6 border-t border-slate-100 pt-6">
|
||||
<div className="mt-6 border-t border-theme-border pt-6">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-indigo-700 mb-1 flex items-center gap-2">
|
||||
<Sparkles size={14} /> Contexte Narratif (Auto-généré)
|
||||
@@ -682,17 +682,17 @@ const WorldBuilder: React.FC<WorldBuilderProps> = ({ entities, onCreate, onUpdat
|
||||
<textarea
|
||||
value={tempEntity.storyContext || ''}
|
||||
onChange={e => setTempEntity({ ...tempEntity, storyContext: e.target.value })}
|
||||
className="w-full p-2 border border-indigo-200 bg-indigo-50 rounded focus:ring-2 focus:ring-blue-500 outline-none text-sm h-24 italic text-slate-600"
|
||||
className="w-full p-2 border border-indigo-200 bg-indigo-500/10 rounded focus:ring-2 focus:ring-blue-500 outline-none text-sm h-24 italic text-theme-muted"
|
||||
placeholder="Les événements vécus par ce personnage apparaîtront ici..."
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="mt-4">
|
||||
<label className="block text-sm font-medium text-slate-700 mb-1">Notes & Biographie Complète</label>
|
||||
<label className="block text-sm font-medium text-theme-text mb-1">Notes & Biographie Complète</label>
|
||||
<textarea
|
||||
value={tempEntity.details}
|
||||
onChange={e => setTempEntity({ ...tempEntity, details: e.target.value })}
|
||||
className="w-full p-2 bg-[#eef2ff] border border-slate-300 rounded focus:ring-2 focus:ring-blue-500 outline-none h-48 font-serif"
|
||||
className="w-full p-2 bg-theme-bg border border-theme-border rounded focus:ring-2 focus:ring-blue-500 outline-none h-48 font-serif"
|
||||
placeholder="Histoire détaillée, secrets, origines..."
|
||||
/>
|
||||
</div>
|
||||
@@ -710,7 +710,7 @@ const WorldBuilder: React.FC<WorldBuilderProps> = ({ entities, onCreate, onUpdat
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="h-full flex flex-col items-center justify-center text-slate-400">
|
||||
<div className="h-full flex flex-col items-center justify-center text-theme-muted">
|
||||
<div className="text-6xl mb-4 opacity-20">🌍</div>
|
||||
<p className="text-lg">Sélectionnez ou créez une fiche pour commencer.</p>
|
||||
<p className="text-sm">Ces informations aideront l'IA à rester cohérente.</p>
|
||||
|
||||
@@ -34,7 +34,7 @@ const EditorShell: React.FC<EditorShellProps> = (props) => {
|
||||
const currentChapter = project.chapters.find(c => c.id === currentChapterId);
|
||||
|
||||
return (
|
||||
<div className={`flex h-screen overflow-hidden no-print ${user.preferences.theme === 'dark' ? 'bg-slate-900 text-white' : user.preferences.theme === 'sepia' ? 'bg-[#f4ecd8]' : 'bg-[#eef2ff]'}`}>
|
||||
<div className="flex h-screen overflow-hidden no-print bg-theme-bg text-theme-text transition-colors duration-300">
|
||||
|
||||
{/* SIDEBAR */}
|
||||
<aside className={`${isSidebarOpen ? 'w-64' : 'w-0'} bg-slate-900 text-slate-300 flex-shrink-0 transition-all duration-300 overflow-hidden flex flex-col border-r border-slate-800`}>
|
||||
@@ -95,9 +95,9 @@ const EditorShell: React.FC<EditorShellProps> = (props) => {
|
||||
|
||||
{/* MAIN CONTENT */}
|
||||
<div className="flex-1 flex flex-col h-full overflow-hidden">
|
||||
<header className="h-14 bg-white border-b border-slate-200 flex items-center justify-between px-4 shadow-sm z-10 text-slate-800">
|
||||
<header className="h-14 bg-theme-panel border-b border-theme-border flex items-center justify-between px-4 shadow-sm z-10 text-theme-text transition-colors duration-300">
|
||||
<div className="flex items-center gap-4">
|
||||
<button onClick={() => setIsSidebarOpen(!isSidebarOpen)} className="text-slate-500 hover:text-slate-800"><Menu size={20} /></button>
|
||||
<button onClick={() => setIsSidebarOpen(!isSidebarOpen)} className="text-theme-muted hover:text-theme-text"><Menu size={20} /></button>
|
||||
{viewMode === 'write' ? (
|
||||
<input
|
||||
type="text"
|
||||
@@ -124,7 +124,7 @@ const EditorShell: React.FC<EditorShellProps> = (props) => {
|
||||
</div>
|
||||
|
||||
{/* AI PANEL */}
|
||||
<div className={`${isAiPanelOpen ? 'w-80 lg:w-96' : 'w-0'} transition-all duration-300 flex-shrink-0 h-full border-l border-slate-200 relative`}>
|
||||
<div className={`${isAiPanelOpen ? 'w-80 lg:w-96' : 'w-0'} transition-all duration-300 flex-shrink-0 h-full border-l border-theme-border relative bg-theme-panel`}>
|
||||
{isAiPanelOpen && <AIPanel chatHistory={props.chatHistory} onSendMessage={props.onSendMessage} onInsertText={props.onInsertText} selectedText="" isGenerating={props.isGenerating} usage={user.usage} />}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -3,12 +3,17 @@
|
||||
import React, { createContext, useContext } from 'react';
|
||||
import { SessionProvider } from 'next-auth/react';
|
||||
import { useAuth } from '@/hooks/useAuth';
|
||||
import { ThemeProvider } from './ThemeProvider';
|
||||
|
||||
const AuthContext = createContext<any>(null);
|
||||
|
||||
function AuthInner({ children }: { children: React.ReactNode }) {
|
||||
const auth = useAuth();
|
||||
return <AuthContext.Provider value={auth}>{children}</AuthContext.Provider>;
|
||||
return (
|
||||
<AuthContext.Provider value={auth}>
|
||||
<ThemeProvider>{children}</ThemeProvider>
|
||||
</AuthContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
|
||||
|
||||
20
src/providers/ThemeProvider.tsx
Normal file
20
src/providers/ThemeProvider.tsx
Normal file
@@ -0,0 +1,20 @@
|
||||
'use client';
|
||||
|
||||
import React, { useEffect } from 'react';
|
||||
import { useAuthContext } from './AuthProvider';
|
||||
|
||||
export function ThemeProvider({ children }: { children: React.ReactNode }) {
|
||||
const { user } = useAuthContext();
|
||||
|
||||
useEffect(() => {
|
||||
if (!user) return;
|
||||
|
||||
const theme = user.preferences?.theme || 'light';
|
||||
const root = document.documentElement;
|
||||
|
||||
root.classList.remove('theme-light', 'theme-dark', 'theme-sepia');
|
||||
root.classList.add(`theme-${theme}`);
|
||||
}, [user?.preferences?.theme]);
|
||||
|
||||
return <>{children}</>;
|
||||
}
|
||||
Reference in New Issue
Block a user