CognxSafeTrack commited on
Commit ·
cfeac02
1
Parent(s): 06cb7f3
fix: resolve 401/400 errors by centralizing API client and injecting organizationId in backend
Browse files
apps/admin/src/lib/api.ts
CHANGED
|
@@ -8,3 +8,49 @@ export const ah = (token: string, orgId?: string | null) => {
|
|
| 8 |
if (orgId) headers['x-organization-id'] = orgId;
|
| 9 |
return headers;
|
| 10 |
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 8 |
if (orgId) headers['x-organization-id'] = orgId;
|
| 9 |
return headers;
|
| 10 |
};
|
| 11 |
+
|
| 12 |
+
export const api = {
|
| 13 |
+
async request(path: string, options: any = {}, token: string | null = null, orgId: string | null = null) {
|
| 14 |
+
const url = path.startsWith('http') ? path : `${API_URL}${path}`;
|
| 15 |
+
const headers = {
|
| 16 |
+
...ah(token || '', orgId),
|
| 17 |
+
...(options.headers || {})
|
| 18 |
+
};
|
| 19 |
+
|
| 20 |
+
const res = await fetch(url, { ...options, headers });
|
| 21 |
+
|
| 22 |
+
if (res.status === 401) {
|
| 23 |
+
// Global 401 handling: Clear session and redirect to login
|
| 24 |
+
sessionStorage.clear();
|
| 25 |
+
window.location.href = '/login';
|
| 26 |
+
throw new Error('Unauthorized');
|
| 27 |
+
}
|
| 28 |
+
|
| 29 |
+
if (!res.ok) {
|
| 30 |
+
const error = await res.json().catch(() => ({ error: 'Unknown error' }));
|
| 31 |
+
throw new Error(error.error || `Request failed with status ${res.status}`);
|
| 32 |
+
}
|
| 33 |
+
|
| 34 |
+
return res.json();
|
| 35 |
+
},
|
| 36 |
+
|
| 37 |
+
get(path: string, token: string | null, orgId: string | null = null) {
|
| 38 |
+
return this.request(path, { method: 'GET' }, token, orgId);
|
| 39 |
+
},
|
| 40 |
+
|
| 41 |
+
post(path: string, body: any, token: string | null, orgId: string | null = null) {
|
| 42 |
+
return this.request(path, { method: 'POST', body: JSON.stringify(body) }, token, orgId);
|
| 43 |
+
},
|
| 44 |
+
|
| 45 |
+
put(path: string, body: any, token: string | null, orgId: string | null = null) {
|
| 46 |
+
return this.request(path, { method: 'PUT', body: JSON.stringify(body) }, token, orgId);
|
| 47 |
+
},
|
| 48 |
+
|
| 49 |
+
patch(path: string, body: any, token: string | null, orgId: string | null = null) {
|
| 50 |
+
return this.request(path, { method: 'PATCH', body: JSON.stringify(body) }, token, orgId);
|
| 51 |
+
},
|
| 52 |
+
|
| 53 |
+
delete(path: string, token: string | null, orgId: string | null = null) {
|
| 54 |
+
return this.request(path, { method: 'DELETE' }, token, orgId);
|
| 55 |
+
}
|
| 56 |
+
};
|
apps/admin/src/pages/AnalyticsPage.tsx
CHANGED
|
@@ -10,7 +10,7 @@ import {
|
|
| 10 |
} from 'lucide-react';
|
| 11 |
import { useAuth } from '../lib/auth';
|
| 12 |
import { useTenant } from '../lib/tenant';
|
| 13 |
-
import {
|
| 14 |
|
| 15 |
const COLORS = ['#6366f1', '#10b981', '#f59e0b', '#ef4444'];
|
| 16 |
|
|
@@ -27,14 +27,13 @@ export default function AnalyticsPage() {
|
|
| 27 |
const fetchData = async () => {
|
| 28 |
setLoading(true);
|
| 29 |
try {
|
| 30 |
-
const
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
fetch(`${API_URL}/v1/analytics/pedagogy`, { headers: h })
|
| 34 |
]);
|
| 35 |
|
| 36 |
-
|
| 37 |
-
|
| 38 |
} catch (err) {
|
| 39 |
console.error('Analytics fetch error:', err);
|
| 40 |
} finally {
|
|
|
|
| 10 |
} from 'lucide-react';
|
| 11 |
import { useAuth } from '../lib/auth';
|
| 12 |
import { useTenant } from '../lib/tenant';
|
| 13 |
+
import { api } from '../lib/api';
|
| 14 |
|
| 15 |
const COLORS = ['#6366f1', '#10b981', '#f59e0b', '#ef4444'];
|
| 16 |
|
|
|
|
| 27 |
const fetchData = async () => {
|
| 28 |
setLoading(true);
|
| 29 |
try {
|
| 30 |
+
const [usageData, pedagogyData] = await Promise.all([
|
| 31 |
+
api.get('/v1/analytics/usage', token, selectedOrgId),
|
| 32 |
+
api.get('/v1/analytics/pedagogy', token, selectedOrgId)
|
|
|
|
| 33 |
]);
|
| 34 |
|
| 35 |
+
setUsage(usageData);
|
| 36 |
+
setPedagogy(pedagogyData);
|
| 37 |
} catch (err) {
|
| 38 |
console.error('Analytics fetch error:', err);
|
| 39 |
} finally {
|
apps/admin/src/pages/ClientsManagementView.tsx
CHANGED
|
@@ -1,6 +1,6 @@
|
|
| 1 |
import { useState, useEffect } from 'react';
|
| 2 |
import { Building2, Plus, MessageSquare, ShieldCheck, Activity, Loader2, X } from 'lucide-react';
|
| 3 |
-
import {
|
| 4 |
import { useAuth } from '../lib/auth';
|
| 5 |
import { launchEmbeddedSignup } from '../lib/meta-signup';
|
| 6 |
import MetaVerificationGuide from '../components/MetaVerificationGuide';
|
|
@@ -43,13 +43,8 @@ export default function ClientsManagementView() {
|
|
| 43 |
const fetchClients = async () => {
|
| 44 |
if (!token) return;
|
| 45 |
try {
|
| 46 |
-
const
|
| 47 |
-
|
| 48 |
-
});
|
| 49 |
-
if (res.ok) {
|
| 50 |
-
const data = await res.json();
|
| 51 |
-
setClients(data);
|
| 52 |
-
}
|
| 53 |
} catch (error) {
|
| 54 |
console.error("Failed to fetch organizations:", error);
|
| 55 |
} finally {
|
|
@@ -65,16 +60,10 @@ export default function ClientsManagementView() {
|
|
| 65 |
e.preventDefault();
|
| 66 |
setIsCreating(true);
|
| 67 |
try {
|
| 68 |
-
|
| 69 |
-
|
| 70 |
-
|
| 71 |
-
|
| 72 |
-
});
|
| 73 |
-
if (res.ok) {
|
| 74 |
-
await fetchClients();
|
| 75 |
-
setIsModalOpen(false);
|
| 76 |
-
setNewOrgName('');
|
| 77 |
-
}
|
| 78 |
} catch (error) {
|
| 79 |
console.error("Failed to create organization:", error);
|
| 80 |
} finally {
|
|
@@ -87,22 +76,12 @@ export default function ClientsManagementView() {
|
|
| 87 |
try {
|
| 88 |
const signupData = await launchEmbeddedSignup();
|
| 89 |
|
| 90 |
-
//
|
| 91 |
-
|
| 92 |
-
|
| 93 |
-
|
| 94 |
-
body: JSON.stringify(signupData)
|
| 95 |
-
});
|
| 96 |
-
|
| 97 |
-
if (res.ok) {
|
| 98 |
-
alert("WhatsApp connecté avec succès !");
|
| 99 |
-
fetchClients(); // Refresh list
|
| 100 |
-
} else {
|
| 101 |
-
const err = await res.json();
|
| 102 |
-
alert(`Erreur lors de l'enregistrement : ${err.error}`);
|
| 103 |
-
}
|
| 104 |
} catch (error: any) {
|
| 105 |
-
alert(`Erreur
|
| 106 |
}
|
| 107 |
};
|
| 108 |
|
|
@@ -296,18 +275,12 @@ export default function ClientsManagementView() {
|
|
| 296 |
onClose={() => setSelectedOrgForPersonality(null)}
|
| 297 |
onSave={async (config) => {
|
| 298 |
try {
|
| 299 |
-
|
| 300 |
-
|
| 301 |
-
|
| 302 |
-
|
| 303 |
-
|
| 304 |
-
|
| 305 |
-
alert("Personnalité mise à jour !");
|
| 306 |
-
fetchClients();
|
| 307 |
-
setSelectedOrgForPersonality(null);
|
| 308 |
-
}
|
| 309 |
-
} catch (err) {
|
| 310 |
-
console.error(err);
|
| 311 |
}
|
| 312 |
}}
|
| 313 |
/>
|
|
|
|
| 1 |
import { useState, useEffect } from 'react';
|
| 2 |
import { Building2, Plus, MessageSquare, ShieldCheck, Activity, Loader2, X } from 'lucide-react';
|
| 3 |
+
import { api } from '../lib/api';
|
| 4 |
import { useAuth } from '../lib/auth';
|
| 5 |
import { launchEmbeddedSignup } from '../lib/meta-signup';
|
| 6 |
import MetaVerificationGuide from '../components/MetaVerificationGuide';
|
|
|
|
| 43 |
const fetchClients = async () => {
|
| 44 |
if (!token) return;
|
| 45 |
try {
|
| 46 |
+
const data = await api.get('/v1/organizations', token);
|
| 47 |
+
setClients(data);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 48 |
} catch (error) {
|
| 49 |
console.error("Failed to fetch organizations:", error);
|
| 50 |
} finally {
|
|
|
|
| 60 |
e.preventDefault();
|
| 61 |
setIsCreating(true);
|
| 62 |
try {
|
| 63 |
+
await api.post('/v1/organizations', { name: newOrgName }, token!);
|
| 64 |
+
await fetchClients();
|
| 65 |
+
setIsModalOpen(false);
|
| 66 |
+
setNewOrgName('');
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 67 |
} catch (error) {
|
| 68 |
console.error("Failed to create organization:", error);
|
| 69 |
} finally {
|
|
|
|
| 76 |
try {
|
| 77 |
const signupData = await launchEmbeddedSignup();
|
| 78 |
|
| 79 |
+
await api.post(`/v1/organizations/${orgId}/whatsapp-setup`, signupData, token!);
|
| 80 |
+
|
| 81 |
+
alert("WhatsApp connecté avec succès !");
|
| 82 |
+
fetchClients(); // Refresh list
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 83 |
} catch (error: any) {
|
| 84 |
+
alert(`Erreur : ${error.message}`);
|
| 85 |
}
|
| 86 |
};
|
| 87 |
|
|
|
|
| 275 |
onClose={() => setSelectedOrgForPersonality(null)}
|
| 276 |
onSave={async (config) => {
|
| 277 |
try {
|
| 278 |
+
await api.patch(`/v1/organizations/${selectedOrgForPersonality.id}/personality`, config, token!);
|
| 279 |
+
alert("Personnalité mise à jour !");
|
| 280 |
+
fetchClients();
|
| 281 |
+
setSelectedOrgForPersonality(null);
|
| 282 |
+
} catch (err: any) {
|
| 283 |
+
alert(`Erreur : ${err.message}`);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 284 |
}
|
| 285 |
}}
|
| 286 |
/>
|
apps/api/src/index.ts
CHANGED
|
@@ -87,6 +87,9 @@ const registerRoutes = async () => {
|
|
| 87 |
request.headers['x-organization-id'] = user.organizationId;
|
| 88 |
}
|
| 89 |
}
|
|
|
|
|
|
|
|
|
|
| 90 |
});
|
| 91 |
|
| 92 |
scope.addHook('preHandler', (request, _reply, done) => {
|
|
|
|
| 87 |
request.headers['x-organization-id'] = user.organizationId;
|
| 88 |
}
|
| 89 |
}
|
| 90 |
+
|
| 91 |
+
// Centralized property for routes to use
|
| 92 |
+
(request as any).organizationId = request.headers['x-organization-id'];
|
| 93 |
});
|
| 94 |
|
| 95 |
scope.addHook('preHandler', (request, _reply, done) => {
|