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 { API_URL, ah } from '../lib/api';
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 h = ah(token, selectedOrgId);
31
- const [usageRes, pedagogyRes] = await Promise.all([
32
- fetch(`${API_URL}/v1/analytics/usage`, { headers: h }),
33
- fetch(`${API_URL}/v1/analytics/pedagogy`, { headers: h })
34
  ]);
35
 
36
- if (usageRes.ok) setUsage(await usageRes.json());
37
- if (pedagogyRes.ok) setPedagogy(await pedagogyRes.json());
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 { API_URL, ah } from '../lib/api';
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 res = await fetch(`${API_URL}/v1/organizations`, {
47
- headers: ah(token)
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
- const res = await fetch(`${API_URL}/v1/organizations`, {
69
- method: 'POST',
70
- headers: ah(token!),
71
- body: JSON.stringify({ name: newOrgName })
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
- // Send the received credentials to our backend
91
- const res = await fetch(`${API_URL}/v1/organizations/${orgId}/whatsapp-setup`, {
92
- method: 'POST',
93
- headers: ah(token!),
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 Meta : ${error.message}`);
106
  }
107
  };
108
 
@@ -296,18 +275,12 @@ export default function ClientsManagementView() {
296
  onClose={() => setSelectedOrgForPersonality(null)}
297
  onSave={async (config) => {
298
  try {
299
- const res = await fetch(`${API_URL}/v1/organizations/${selectedOrgForPersonality.id}/personality`, {
300
- method: 'PATCH',
301
- headers: ah(token!),
302
- body: JSON.stringify(config)
303
- });
304
- if (res.ok) {
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) => {