From af9d69cc78b7bb3ab6f13acac8e4cd1291de1510 Mon Sep 17 00:00:00 2001 From: streaper2 Date: Sun, 15 Feb 2026 23:15:56 +0100 Subject: [PATCH] commit before hard reset to change connection --- components/AppRouter.tsx | 7 +- components/layout/EditorShell.tsx | 226 +++++++++++++++--------------- hooks.ts | 34 +++-- services/api.ts | 226 ++++++++++++++++++++++++++---- types.ts | 4 + vite.config.ts | 40 ++++++ 6 files changed, 383 insertions(+), 154 deletions(-) diff --git a/components/AppRouter.tsx b/components/AppRouter.tsx index 285d53f..c4ba00f 100644 --- a/components/AppRouter.tsx +++ b/components/AppRouter.tsx @@ -118,8 +118,13 @@ const AppRouter: React.FC = (props) => { onChapterSelect={(id) => { setCurrentChapterId(id); props.onViewModeChange('write'); }} onUpdateProject={props.onUpdateProject} onAddChapter={async () => { + console.log("[AppRouter] onAddChapter triggered"); const id = await props.onAddChapter(); - if (id) setCurrentChapterId(id); + console.log("[AppRouter] onAddChapter result ID:", id); + if (id) { + setCurrentChapterId(id); + props.onViewModeChange('write'); + } }} onDeleteChapter={(id) => { if (project.chapters.length > 1) { diff --git a/components/layout/EditorShell.tsx b/components/layout/EditorShell.tsx index 53cfd1e..5eedfec 100644 --- a/components/layout/EditorShell.tsx +++ b/components/layout/EditorShell.tsx @@ -5,128 +5,128 @@ import AIPanel from '../AIPanel'; import { Book, FileText, Globe, GitGraph, Lightbulb, Settings, Menu, ChevronRight, ChevronLeft, Share2, HelpCircle, LogOut, LayoutDashboard, User, Plus, Trash2 } from 'lucide-react'; interface EditorShellProps { - project: BookProject; - user: UserProfile; - viewMode: ViewMode; - currentChapterId: string; - chatHistory: ChatMessage[]; - isGenerating: boolean; - onViewModeChange: (mode: ViewMode) => void; - onChapterSelect: (id: string) => void; - onUpdateProject: (updates: Partial) => void; - onAddChapter: () => void; - onDeleteChapter: (id: string) => void; - onLogout: () => void; - onSendMessage: (msg: string) => void; - onInsertText: (text: string) => void; - onOpenExport: () => void; - onOpenHelp: () => void; - children: React.ReactNode; + project: BookProject; + user: UserProfile; + viewMode: ViewMode; + currentChapterId: string; + chatHistory: ChatMessage[]; + isGenerating: boolean; + onViewModeChange: (mode: ViewMode) => void; + onChapterSelect: (id: string) => void; + onUpdateProject: (updates: Partial) => void; + onAddChapter: () => Promise; + onDeleteChapter: (id: string) => void; + onLogout: () => void; + onSendMessage: (msg: string) => void; + onInsertText: (text: string) => void; + onOpenExport: () => void; + onOpenHelp: () => void; + children: React.ReactNode; } const EditorShell: React.FC = (props) => { - const { project, user, viewMode, currentChapterId, children } = props; - const [isSidebarOpen, setIsSidebarOpen] = useState(true); - const [isAiPanelOpen, setIsAiPanelOpen] = useState(true); + const { project, user, viewMode, currentChapterId, children } = props; + const [isSidebarOpen, setIsSidebarOpen] = useState(true); + const [isAiPanelOpen, setIsAiPanelOpen] = useState(true); - const currentChapter = project.chapters.find(c => c.id === currentChapterId); + const currentChapter = project.chapters.find(c => c.id === currentChapterId); - return ( -
- - {/* SIDEBAR */} - + + {/* MAIN CONTENT */} +
+
+
+ + {viewMode === 'write' ? ( + props.onUpdateProject({ chapters: project.chapters.map(c => c.id === currentChapterId ? { ...c, title: e.target.value } : c) })} + className="font-serif font-bold text-lg bg-transparent border-b border-transparent focus:border-blue-500 focus:outline-none" + /> + ) : ( + {viewMode} + )} +
+
+ + + +
+
+ +
+ {children} +
+
+ + {/* AI PANEL */} +
+ {isAiPanelOpen && } +
- -
-
-
- Actions IA - {user.usage.aiActionsCurrent} / {user.usage.aiActionsLimit === 999999 ? '∞' : user.usage.aiActionsLimit} -
-
-
-
-
- - -
- - - {/* MAIN CONTENT */} -
-
-
- - {viewMode === 'write' ? ( - props.onUpdateProject({ chapters: project.chapters.map(c => c.id === currentChapterId ? {...c, title: e.target.value} : c) })} - className="font-serif font-bold text-lg bg-transparent border-b border-transparent focus:border-blue-500 focus:outline-none" - /> - ) : ( - {viewMode} - )} -
-
- - - -
-
- -
- {children} -
-
- - {/* AI PANEL */} -
- {isAiPanelOpen && } -
-
- ); + ); }; export default EditorShell; diff --git a/hooks.ts b/hooks.ts index 5a1af46..f88eaad 100644 --- a/hooks.ts +++ b/hooks.ts @@ -291,18 +291,32 @@ export function useProjects(user: UserProfile | null) { const addChapter = async () => { - if (!currentProjectId) return null; + console.log("[Hooks] addChapter called. Current Project ID:", currentProjectId); + if (!currentProjectId) { + console.error("[Hooks] addChapter failed: No currentProjectId"); + alert("Erreur: Impossible d'ajouter un chapitre car aucun projet n'est actif."); + return null; + } const projectId = parseInt(currentProjectId); - const result = await dataService.createItem('chapters', { - project_id: projectId, - title: "Nouveau Chapitre", - content: "

Contenu...

", - summary: "" - }); + console.log("[Hooks] Creating chapter for project:", projectId); - if (result.status === 'success') { - await fetchProjectDetails(currentProjectId); - return result.id.toString(); + try { + const result = await dataService.createItem('chapters', { + project_id: projectId, + title: "Nouveau Chapitre", + content: "

Contenu...

", + summary: "" + }); + console.log("[Hooks] createItem result:", result); + + if (result.status === 'success') { + await fetchProjectDetails(currentProjectId); + return result.id.toString(); + } else { + console.error("[Hooks] createItem failed status:", result); + } + } catch (e) { + console.error("[Hooks] createItem exception:", e); } return null; }; diff --git a/services/api.ts b/services/api.ts index b90f5a0..39768b3 100644 --- a/services/api.ts +++ b/services/api.ts @@ -2,27 +2,48 @@ import { BookProject, Chapter, Entity, Idea, WorkflowData, UserProfile } from '../types'; // --- CONFIGURATION --- -const AUTH_API_ROOT = 'https://app.nocodebackend.com/api/user-auth'; -const DATA_API_ROOT = 'https://app.nocodebackend.com/api/data'; +const AUTH_API_ROOT = '/api/user-auth'; +const DATA_API_ROOT = '/api/data'; const INSTANCE_ID = '54770_plumeia_db'; // --- HELPERS --- -const getHeaders = () => { - const token = localStorage.getItem('ncb_session_token'); +// --- HELPERS --- +const getHeaders = () => { + /*const token = localStorage.getItem('ncb_session_token'); + const sessionData = localStorage.getItem('ncb_session_data'); + console.log("[API] Token:", token); + console.log("[API] Session Data:", sessionData);*/ const headers: Record = { 'Content-Type': 'application/json', - 'X-Database-Instance': INSTANCE_ID + 'X-Database-Instance': INSTANCE_ID, + // 'credentials': 'include' }; - - if (token) { - headers['Authorization'] = `Bearer ${token}`; - } - + /* + if (token) { + // Fallback standard auth + //headers['Authorization'] = `Bearer ${token}`; + + // User-requested specific Cookie format + // Note: Browsers typically block manual "Cookie" header setting in fetch. + // This is implemented per user request, but relies on the environment allowing it + // or using credentials: 'include' for actual cookies. + let cookieString = `better-auth.session_token=${token}`; + if (sessionData) { + cookieString += `; better-auth.session_data=${sessionData}`; + } + headers['Cookie'] = cookieString; + + console.log("[API] Cookie:", cookieString); + } + + // Debug headers + console.log("[API] Generated Headers:", headers); + */ return headers; }; - +/* const handleAuthResponse = async (res: Response) => { const data = await res.json(); console.log("[API] Auth Response:", data); @@ -31,7 +52,7 @@ const handleAuthResponse = async (res: Response) => { throw new Error(data.message || 'Authentication failed'); } - // Token extraction strategy based on NCB patterns + // Token extraction strategy const token = data.user?.token || data.token || @@ -40,30 +61,74 @@ const handleAuthResponse = async (res: Response) => { data.accessToken || data._token; + // Extract session data if available (often needed for Better Auth) + const sessionData = data.session_data || data.session?.data || data.user?.session_data; + if (token) { console.log("[API] Token extracted and saved:", token); localStorage.setItem('ncb_session_token', token); + + if (sessionData) { + console.log("[API] Session Data extracted and saved"); + localStorage.setItem('ncb_session_data', sessionData); + } } else { console.warn("[API] No token found in successful auth response!"); } + return data; +};*/ + +const handleAuthResponse = async (res: Response) => { + const data = await res.json(); + console.log("[API] Raw Response Data:", data); + + if (!res.ok) { + throw new Error(data.message || 'Authentication failed'); + } + + // --- LOGIQUE DES SETTEURS --- + + // 1. Extraction du Token (selon la structure Better-Auth) + const token = data.session?.token || data.token; + + // 2. Extraction du Session Data + // On prend l'objet session complet et on le stringifie pour le stockage + const sessionData = data.session ? JSON.stringify(data.session) : null; + + if (token) { + localStorage.setItem('ncb_session_token', token); + console.log("[Auth] Token saved to LocalStorage"); + } + + if (sessionData) { + localStorage.setItem('ncb_session_data', sessionData); + console.log("[Auth] Session Data saved to LocalStorage"); + } + return data; }; // --- AUTH SERVICE --- export const authService = { - async getSession() { + /*async getSession() { const token = localStorage.getItem('ncb_session_token'); - if (!token) return null; + // Note: Even if we use cookies, we keep token logic as fallback or for UI state + // if (!token) return null; try { - const res = await fetch(`${AUTH_API_ROOT}/get-session?Instance=${INSTANCE_ID}`, { + const url = `${AUTH_API_ROOT}/get-session?Instance=${INSTANCE_ID}`; + console.log(`[API] GET session ${url}`); + + const res = await fetch(url, { method: 'GET', - headers: getHeaders() + //headers: getHeaders(), + credentials: 'include' // IMPORTANT: Send cookies }); if (!res.ok) { + console.log(`[API] getSession failed with status: ${res.status}`); if (res.status === 401) { localStorage.removeItem('ncb_session_token'); } @@ -71,26 +136,77 @@ export const authService = { } const data = await res.json(); + console.log("[API] getSession success:", data); return data.user || data; } catch (err) { console.error("[Auth] getSession error:", err); return null; } + },*/ + + async getSession() { + console.log("[getSession] démarrage"); + const token = localStorage.getItem('ncb_session_token'); + try { + const url = `${AUTH_API_ROOT}/get-session?Instance=${INSTANCE_ID}`; + const headers: Record = { + 'Content-Type': 'application/json', + 'X-Database-Instance': INSTANCE_ID + }; + + // Si on a un token mais pas encore la session complète, on l'envoie + if (token) { + headers['Cookie'] = `better-auth.session_token=${token}`; + // On peut aussi essayer le header Authorization au cas où + headers['Authorization'] = `Bearer ${token}`; + } + + const res = await fetch(url, { + method: 'GET', + headers: headers, + credentials: 'include' + }); + + if (res.ok) { + const data = await res.json(); + console.log("[getSession] getSession success:", data); + // --- LES SETTEURS ICI --- + if (data.session) { + localStorage.setItem('ncb_session_token', data.session.token); + localStorage.setItem('ncb_session_data', JSON.stringify(data.session)); + console.log("[getSession] Données récupérées depuis getSession"); + } + + return data.user || data; + } + return null; + } catch (err) { + console.error("[Auth] getSession error:", err); + return null; + } }, + async signIn(email: string, password: string) { try { // Force X-Database-Instance header as URL param is insufficient for some NCB versions - const res = await fetch(`${AUTH_API_ROOT}/sign-in/email?Instance=${INSTANCE_ID}`, { + const url = `${AUTH_API_ROOT}/sign-in/email?Instance=${INSTANCE_ID}`; + console.log(`[API] POST ${url}`, { email, password: '***' }); + + const res = await fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-Database-Instance': INSTANCE_ID }, - body: JSON.stringify({ email, password }) + body: JSON.stringify({ email, password }), + credentials: 'include' // IMPORTANT: Receive & Save cookies }); + const authData = await handleAuthResponse(res); + await this.getSession(); + console.log("sign in", res); - return await handleAuthResponse(res); + return await authData; } catch (err: any) { console.error("[Auth] signIn error:", err); return { error: err.message || 'Connection failed' }; @@ -99,26 +215,35 @@ export const authService = { async signUp(email: string, password: string, name: string) { try { - const res = await fetch(`${AUTH_API_ROOT}/sign-up/email?Instance=${INSTANCE_ID}`, { + const url = `${AUTH_API_ROOT}/sign-up/email?Instance=${INSTANCE_ID}`; + console.log(`[API] POST ${url}`, { email, name }); + + const res = await fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-Database-Instance': INSTANCE_ID }, - body: JSON.stringify({ email, password, name }) + body: JSON.stringify({ email, password, name }), + credentials: 'include' // IMPORTANT: Receive & Save cookies }); return await handleAuthResponse(res); } catch (err: any) { + console.error("[Auth] signUp error:", err); return { error: err.message || 'Registration failed' }; } }, async signOut() { try { - await fetch(`${AUTH_API_ROOT}/sign-out?Instance=${INSTANCE_ID}`, { + const url = `${AUTH_API_ROOT}/sign-out?Instance=${INSTANCE_ID}`; + console.log(`[API] POST ${url}`); + + await fetch(url, { method: 'POST', - headers: { 'Content-Type': 'application/json' } + headers: { 'Content-Type': 'application/json' }, + credentials: 'include' // IMPORTANT: Clear cookies }); } catch (err) { console.error("[Auth] signOut error:", err); @@ -137,8 +262,14 @@ export const dataService = { try { const encodedId = encodeURIComponent(userId); const url = `${DATA_API_ROOT}/read/profiles?Instance=${INSTANCE_ID}&user_id=${encodedId}`; - const res = await fetch(url, { headers: getHeaders() }); + console.log(`[API] GET ${url}`); + + const res = await fetch(url, { + headers: getHeaders(), + credentials: 'include' + }); const json = await res.json(); + console.log("[Data] getProfile result:", json); return json.data?.[0] || null; } catch (err) { console.error("[Data] getProfile error:", err); @@ -155,8 +286,14 @@ export const dataService = { try { const encodedId = encodeURIComponent(userId); const url = `${DATA_API_ROOT}/read/projects?Instance=${INSTANCE_ID}&user_id=${encodedId}`; - const res = await fetch(url, { headers: getHeaders() }); + console.log(`[API] GET ${url}`); + + const res = await fetch(url, { + headers: getHeaders(), + credentials: 'include' + }); const json = await res.json(); + console.log(`[Data] getProjects found ${json.data?.length || 0} items`); return json.data || []; } catch (err) { console.error("[Data] getProjects error:", err); @@ -175,8 +312,14 @@ export const dataService = { async getRelatedData(table: string, projectId: number) { try { const url = `${DATA_API_ROOT}/read/${table}?Instance=${INSTANCE_ID}&project_id=${projectId}`; - const res = await fetch(url, { headers: getHeaders() }); + console.log(`[API] GET ${url}`); + + const res = await fetch(url, { + headers: getHeaders(), + credentials: 'include' + }); const json = await res.json(); + console.log(`[Data] getRelatedData ${table} found ${json.data?.length || 0} items`); return json.data || []; } catch (err) { console.error(`[Data] getRelatedData ${table} error:`, err); @@ -188,6 +331,7 @@ export const dataService = { try { console.log(`[Data] Creating item in ${table}...`, data); const url = `${DATA_API_ROOT}/create/${table}?Instance=${INSTANCE_ID}`; + console.log(`[API] POST ${url}`, data); const res = await fetch(url, { method: 'POST', @@ -197,6 +341,8 @@ export const dataService = { }); const result = await res.json(); + console.log(`[Data] Create ${table} response:`, result); + if (!res.ok) { console.error(`[Data] Create ${table} failed:`, result); return { status: 'error', message: result.message || 'Creation failed' }; @@ -211,11 +357,21 @@ export const dataService = { async updateItem(table: string, id: number, data: any) { try { const url = `${DATA_API_ROOT}/update/${table}/${id}?Instance=${INSTANCE_ID}`; - await fetch(url, { + console.log(`[API] PUT ${url}`, data); + + const res = await fetch(url, { method: 'PUT', headers: getHeaders(), - body: JSON.stringify(data) + body: JSON.stringify(data), + credentials: 'include' }); + + if (!res.ok) { + const err = await res.json(); + console.error(`[Data] Update ${table} failed:`, err); + } else { + console.log(`[Data] Update ${table} success`); + } } catch (err) { console.error(`[Data] Update ${table} error:`, err); } @@ -224,10 +380,20 @@ export const dataService = { async deleteItem(table: string, id: number) { try { const url = `${DATA_API_ROOT}/delete/${table}/${id}?Instance=${INSTANCE_ID}`; - await fetch(url, { + console.log(`[API] DELETE ${url}`); + + const res = await fetch(url, { method: 'DELETE', - headers: getHeaders() + headers: getHeaders(), + credentials: 'include' }); + + if (!res.ok) { + const err = await res.json(); + console.error(`[Data] Delete ${table} failed:`, err); + } else { + console.log(`[Data] Delete ${table} success`); + } } catch (err) { console.error(`[Data] Delete ${table} error:`, err); } diff --git a/types.ts b/types.ts index 5742ba6..2ac6a27 100644 --- a/types.ts +++ b/types.ts @@ -107,6 +107,10 @@ export interface BookProject { author: string; lastModified: number; settings?: BookSettings; + // Direct fields sometimes used in creation/updates before settings normalization + genre?: string; + pov?: string; + tense?: string; chapters: Chapter[]; entities: Entity[]; workflow?: WorkflowData; diff --git a/vite.config.ts b/vite.config.ts index ee5fb8d..c60bf67 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -2,6 +2,45 @@ import path from 'path'; import { defineConfig, loadEnv } from 'vite'; import react from '@vitejs/plugin-react'; +export default defineConfig(({ mode }) => { + const env = loadEnv(mode, '.', ''); + return { + server: { + port: 3000, + host: '0.0.0.0', + // --- AJOUT DU PROXY ICI --- + proxy: { + '/api/user-auth': { + target: 'https://app.nocodebackend.com', + changeOrigin: true, + secure: false, + cookieDomainRewrite: "localhost", + }, + '/api/data': { + target: 'https://app.nocodebackend.com', + changeOrigin: true, + secure: false, + cookieDomainRewrite: "localhost", + } + } + }, + plugins: [react()], + define: { + 'process.env.API_KEY': JSON.stringify(env.GEMINI_API_KEY), + 'process.env.GEMINI_API_KEY': JSON.stringify(env.GEMINI_API_KEY) + }, + resolve: { + alias: { + '@': path.resolve(__dirname, '.'), + } + } + }; +}); + +/*mport path from 'path'; +import { defineConfig, loadEnv } from 'vite'; +import react from '@vitejs/plugin-react'; + export default defineConfig(({ mode }) => { const env = loadEnv(mode, '.', ''); return { @@ -21,3 +60,4 @@ export default defineConfig(({ mode }) => { } }; }); +*/ \ No newline at end of file