commit before hard reset to change connection

This commit is contained in:
2026-02-15 23:15:56 +01:00
parent be5bd2b2bf
commit af9d69cc78
6 changed files with 383 additions and 154 deletions

View File

@@ -118,8 +118,13 @@ const AppRouter: React.FC<AppRouterProps> = (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) {

View File

@@ -14,7 +14,7 @@ interface EditorShellProps {
onViewModeChange: (mode: ViewMode) => void;
onChapterSelect: (id: string) => void;
onUpdateProject: (updates: Partial<BookProject>) => void;
onAddChapter: () => void;
onAddChapter: () => Promise<void>;
onDeleteChapter: (id: string) => void;
onLogout: () => void;
onSendMessage: (msg: string) => void;
@@ -69,7 +69,7 @@ const EditorShell: React.FC<EditorShellProps> = (props) => {
))}
<div className="mt-6 px-4 py-2 text-xs font-semibold text-slate-500 uppercase">Outils & Bible</div>
<button onClick={() => props.onViewModeChange('write')} className={`w-full text-left px-4 py-2 text-sm flex items-center gap-2 ${viewMode === 'write' ? 'bg-blue-900 text-white' : 'hover:bg-slate-800'}`}><FileText size={16} /> Éditeur</button>
<button onClick={() => props.onViewModeChange('write')} className={`w-full text-left px-4 py-2 text-sm flex items-center gap-2 ${viewMode === 'write' ? 'bg-blue-900 text-white' : 'hover:bg-slate-800'}`}><FileText size={16} /> Retour à la rédaction</button>
<button onClick={() => props.onViewModeChange('world_building')} className={`w-full text-left px-4 py-2 text-sm flex items-center gap-2 ${viewMode === 'world_building' ? 'bg-indigo-900 text-white' : 'hover:bg-slate-800'}`}><Globe size={16} /> Bible du Monde</button>
<button onClick={() => props.onViewModeChange('workflow')} className={`w-full text-left px-4 py-2 text-sm flex items-center gap-2 ${viewMode === 'workflow' ? 'bg-indigo-900 text-white' : 'hover:bg-slate-800'}`}><GitGraph size={16} /> Workflow</button>
<button onClick={() => props.onViewModeChange('ideas')} className={`w-full text-left px-4 py-2 text-sm flex items-center gap-2 ${viewMode === 'ideas' ? 'bg-indigo-900 text-white' : 'hover:bg-slate-800'}`}><Lightbulb size={16} /> Boîte à Idées</button>

View File

@@ -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);
console.log("[Hooks] Creating chapter for project:", projectId);
try {
const result = await dataService.createItem('chapters', {
project_id: projectId,
title: "Nouveau Chapitre",
content: "<p>Contenu...</p>",
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;
};

View File

@@ -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<string, string> = {
'Content-Type': 'application/json',
'X-Database-Instance': INSTANCE_ID
'X-Database-Instance': INSTANCE_ID,
// 'credentials': 'include'
};
/*
if (token) {
headers['Authorization'] = `Bearer ${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<string, string> = {
'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);
}

View File

@@ -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;

View File

@@ -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 }) => {
}
};
});
*/