From 526495db962a6e37ad1af57425171d2fa29632e6 Mon Sep 17 00:00:00 2001 From: VPS 4 Builder Date: Wed, 13 May 2026 10:04:49 +0200 Subject: [PATCH] fix(frontend): Resolve login redirection and inject JWT token on requests --- frontend/App.tsx | 3 +- frontend/services/backend.ts | 158 ++++++++++++++++------------------- 2 files changed, 73 insertions(+), 88 deletions(-) diff --git a/frontend/App.tsx b/frontend/App.tsx index 725da90..803dc08 100644 --- a/frontend/App.tsx +++ b/frontend/App.tsx @@ -170,7 +170,8 @@ const App: React.FC = () => { // Keep URL in sync when view changes programmatically via sidebar const handleNavigate = (view: ViewKey) => { - if (isViewAllowed(view, currentRole)) { + const freshRole = HybridBackend.getCurrentRole(); + if (isViewAllowed(view, freshRole)) { setCurrentView(view); navigate(view); setSidebarOpen(false); // Close sidebar on mobile after navigation diff --git a/frontend/services/backend.ts b/frontend/services/backend.ts index 80259ae..8ad0c3b 100644 --- a/frontend/services/backend.ts +++ b/frontend/services/backend.ts @@ -22,12 +22,13 @@ const enforceUppercase = (data: T): T => { const API_URL = import.meta.env.VITE_API_URL || '/api'; // Returns headers always including the JWT token stored after login -const getAuthHeaders = (): HeadersInit => { +const apiFetch = async (url: string, options: RequestInit = {}) => { const token = localStorage.getItem('SCOREODONTO_AUTH_TOKEN'); - return { - 'Content-Type': 'application/json', + const headers = { + ...options.headers, ...(token ? { 'Authorization': `Bearer ${token}` } : {}) }; + return fetch(url, { ...options, headers }); }; export const HybridBackend = { @@ -45,7 +46,7 @@ export const HybridBackend = { const cleanPass = pass.trim(); try { - const res = await fetch(`${API_URL}/login`, { + const res = await apiFetch(`${API_URL}/login`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ email: cleanEmail, password: cleanPass }) @@ -144,7 +145,7 @@ export const HybridBackend = { }, getDashboardStats: async () => { - const res = await fetch(`${API_URL}/dashboard/stats`); + const res = await apiFetch(`${API_URL}/dashboard/stats`); return await res.json(); }, @@ -206,7 +207,7 @@ export const HybridBackend = { }, getPacientes: async (termo: string = ''): Promise => { - const res = await fetch(`${API_URL}/pacientes`); + const res = await apiFetch(`${API_URL}/pacientes`); const data = await res.json(); if (!termo) return data; const t = termo.toUpperCase(); @@ -223,7 +224,7 @@ export const HybridBackend = { savePaciente: async (paciente: Partial) => { const data = enforceUppercase(paciente); const body = { ...data, id: Math.random().toString() }; - const res = await fetch(`${API_URL}/pacientes`, { + const res = await apiFetch(`${API_URL}/pacientes`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) @@ -233,7 +234,7 @@ export const HybridBackend = { updatePaciente: async (paciente: Paciente) => { const data = enforceUppercase(paciente); - const res = await fetch(`${API_URL}/pacientes/${paciente.id}`, { + const res = await apiFetch(`${API_URL}/pacientes/${paciente.id}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data) @@ -243,18 +244,18 @@ export const HybridBackend = { getDentistas: async (clinicaId?: string): Promise => { const url = clinicaId ? `${API_URL}/dentistas?clinicaId=${clinicaId}` : `${API_URL}/dentistas`; - const res = await fetch(url); + const res = await apiFetch(url); return await res.json(); }, getPlanos: async (): Promise => { - const res = await fetch(`${API_URL}/planos`); + const res = await apiFetch(`${API_URL}/planos`); return await res.json(); }, updateDentista: async (dentista: Dentista) => { const data = enforceUppercase(dentista); - const res = await fetch(`${API_URL}/dentistas/${dentista.id}`, { + const res = await apiFetch(`${API_URL}/dentistas/${dentista.id}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data) @@ -266,7 +267,7 @@ export const HybridBackend = { const activeW = HybridBackend.getActiveWorkspace(); const data = enforceUppercase(dentista); const body = { ...data, id: Math.random().toString(), clinica_id: activeW?.id }; - const res = await fetch(`${API_URL}/dentistas`, { + const res = await apiFetch(`${API_URL}/dentistas`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) @@ -275,7 +276,7 @@ export const HybridBackend = { }, reorderDentistas: async (orders: { id: string; ordem: number }[]) => { - const res = await fetch(`${API_URL}/dentistas/reorder`, { + const res = await apiFetch(`${API_URL}/dentistas/reorder`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ orders }) @@ -284,16 +285,16 @@ export const HybridBackend = { }, deleteDentista: async (id: string) => { - await fetch(`${API_URL}/dentistas/${id}`, { method: 'DELETE' }); + await apiFetch(`${API_URL}/dentistas/${id}`, { method: 'DELETE' }); }, getHorariosByDentista: async (dentistaId: string, clinicaId: string): Promise => { - const res = await fetch(`${API_URL}/dentistas/${dentistaId}/horarios?clinicaId=${clinicaId}`); + const res = await apiFetch(`${API_URL}/dentistas/${dentistaId}/horarios?clinicaId=${clinicaId}`); return await res.json(); }, saveHorario: async (horario: Partial) => { - const res = await fetch(`${API_URL}/dentistas/horarios`, { + const res = await apiFetch(`${API_URL}/dentistas/horarios`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(horario) @@ -302,17 +303,17 @@ export const HybridBackend = { }, deleteHorario: async (id: string) => { - await fetch(`${API_URL}/dentistas/horarios/${id}`, { method: 'DELETE' }); + await apiFetch(`${API_URL}/dentistas/horarios/${id}`, { method: 'DELETE' }); }, getProductivityReport: async (clinicaId: string, start?: string, end?: string) => { - const res = await fetch(`${API_URL}/reports/productivity?clinicaId=${clinicaId}&start=${start || ''}&end=${end || ''}`); + const res = await apiFetch(`${API_URL}/reports/productivity?clinicaId=${clinicaId}&start=${start || ''}&end=${end || ''}`); return await res.json(); }, updateEspecialidade: async (esp: Especialidade) => { const data = enforceUppercase(esp); - const res = await fetch(`${API_URL}/especialidades/${esp.id}`, { + const res = await apiFetch(`${API_URL}/especialidades/${esp.id}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data) @@ -323,7 +324,7 @@ export const HybridBackend = { saveEspecialidade: async (esp: Partial) => { const data = enforceUppercase(esp); const body = { ...data, id: Math.random().toString() }; - const res = await fetch(`${API_URL}/especialidades`, { + const res = await apiFetch(`${API_URL}/especialidades`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) @@ -332,7 +333,7 @@ export const HybridBackend = { }, reorderEspecialidades: async (orders: { id: string; ordem: number }[]) => { - const res = await fetch(`${API_URL}/especialidades/reorder`, { + const res = await apiFetch(`${API_URL}/especialidades/reorder`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ orders }) @@ -341,12 +342,12 @@ export const HybridBackend = { }, deleteEspecialidade: async (id: string) => { - await fetch(`${API_URL}/especialidades/${id}`, { method: 'DELETE' }); + await apiFetch(`${API_URL}/especialidades/${id}`, { method: 'DELETE' }); }, updateProcedimento: async (proc: Procedimento) => { const data = enforceUppercase(proc); - const res = await fetch(`${API_URL}/procedimentos/${proc.id}`, { + const res = await apiFetch(`${API_URL}/procedimentos/${proc.id}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data) @@ -357,7 +358,7 @@ export const HybridBackend = { saveProcedimento: async (proc: Partial) => { const data = enforceUppercase(proc); const body = { ...data, id: Math.random().toString() }; - const res = await fetch(`${API_URL}/procedimentos`, { + const res = await apiFetch(`${API_URL}/procedimentos`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) @@ -366,7 +367,7 @@ export const HybridBackend = { }, reorderProcedimentos: async (orders: { id: string; ordem: number }[]) => { - const res = await fetch(`${API_URL}/procedimentos/reorder`, { + const res = await apiFetch(`${API_URL}/procedimentos/reorder`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ orders }) @@ -375,12 +376,12 @@ export const HybridBackend = { }, deleteProcedimento: async (id: string) => { - await fetch(`${API_URL}/procedimentos/${id}`, { method: 'DELETE' }); + await apiFetch(`${API_URL}/procedimentos/${id}`, { method: 'DELETE' }); }, updatePlano: async (plano: Plano) => { const data = enforceUppercase(plano); - const res = await fetch(`${API_URL}/planos/${plano.id}`, { + const res = await apiFetch(`${API_URL}/planos/${plano.id}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data) @@ -391,7 +392,7 @@ export const HybridBackend = { savePlano: async (plano: Partial) => { const data = enforceUppercase(plano); const body = { ...data, id: Math.random().toString() }; - const res = await fetch(`${API_URL}/planos`, { + const res = await apiFetch(`${API_URL}/planos`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) @@ -400,57 +401,55 @@ export const HybridBackend = { }, deletePlano: async (id: string) => { - await fetch(`${API_URL}/planos/${id}`, { method: 'DELETE' }); + await apiFetch(`${API_URL}/planos/${id}`, { method: 'DELETE' }); }, getEspecialidades: async (): Promise => { - const res = await fetch(`${API_URL}/especialidades`); + const res = await apiFetch(`${API_URL}/especialidades`); return await res.json(); }, getProcedimentos: async (): Promise => { - const res = await fetch(`${API_URL}/procedimentos`); + const res = await apiFetch(`${API_URL}/procedimentos`); return await res.json(); }, getFinanceiro: async (): Promise => { - const res = await fetch(`${API_URL}/financeiro`); + const res = await apiFetch(`${API_URL}/financeiro`); return await res.json(); }, saveFinanceiro: async (item: Partial) => { const data = enforceUppercase(item); - const res = await fetch(`${API_URL}/financeiro`, { + const res = await apiFetch(`${API_URL}/financeiro`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(data), - }); + body: JSON.stringify(data)}); return await res.json(); }, updateFinanceiro: async (item: FinanceiroItem) => { const data = enforceUppercase(item); - const res = await fetch(`${API_URL}/financeiro/${item.id}`, { + const res = await apiFetch(`${API_URL}/financeiro/${item.id}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(data), - }); + body: JSON.stringify(data)}); return await res.json(); }, deleteFinanceiro: async (id: string) => { - await fetch(`${API_URL}/financeiro/${id}`, { method: 'DELETE' }); + await apiFetch(`${API_URL}/financeiro/${id}`, { method: 'DELETE' }); }, getAgendamentos: async (): Promise => { - const res = await fetch(`${API_URL}/agendamentos`, { headers: getAuthHeaders() }); + const res = await apiFetch(`${API_URL}/agendamentos`, { }); return await res.json(); }, salvarAgendamento: async (ag: Partial) => { - const res = await fetch(`${API_URL}/agendamentos`, { + const res = await apiFetch(`${API_URL}/agendamentos`, { method: 'POST', - headers: getAuthHeaders(), + , body: JSON.stringify(ag) }); if (!res.ok) { @@ -461,9 +460,9 @@ export const HybridBackend = { }, atualizarAgendamento: async (id: string, data: Partial) => { - const res = await fetch(`${API_URL}/agendamentos/${id}`, { + const res = await apiFetch(`${API_URL}/agendamentos/${id}`, { method: 'PUT', - headers: getAuthHeaders(), + , body: JSON.stringify(data) }); if (!res.ok) { @@ -474,7 +473,7 @@ export const HybridBackend = { }, excluirAgendamento: async (id: string) => { - await fetch(`${API_URL}/agendamentos/${id}`, { method: 'DELETE', headers: getAuthHeaders() }); + await apiFetch(`${API_URL}/agendamentos/${id}`, { method: 'DELETE'}); }, getOrtoTreatments: async (): Promise => { @@ -492,15 +491,13 @@ export const HybridBackend = { medicacao: 'NENHUMA', alergias: 'NENHUMA', respiracaoBucal: false, - habitos: 'BRUXISMO LEVE NOTURNO', - }, + habitos: 'BRUXISMO LEVE NOTURNO'}, diagnostico: { classeAngle: 'II', perfil: 'Convexo', apinhamento: 'Moderado', mordida: 'Profunda', - linhaMedia: '1MM PARA ESQUERDA', - }, + linhaMedia: '1MM PARA ESQUERDA'}, dataInicio: '2024-08-10', tipoAparelho: 'AUTOLIGADO METÁLICO', extracoes: 'NÃO', @@ -528,8 +525,7 @@ export const HybridBackend = { proximoRetorno: '2024-09-10', fio: '0.12', higiene: 8, - cooperacao: 'Boa', - }, + cooperacao: 'Boa'}, { id: 'ev2', data: '2024-09-10', @@ -538,8 +534,7 @@ export const HybridBackend = { proximoRetorno: '2024-10-10', fio: '0.14', higiene: 7, - cooperacao: 'Boa', - }, + cooperacao: 'Boa'}, { id: 'ev3', data: '2024-10-10', @@ -549,8 +544,7 @@ export const HybridBackend = { fio: '0.16', elastico: 'CLASSE II', higiene: 6, - cooperacao: 'Média', - }, + cooperacao: 'Média'}, { id: 'ev4', data: '2025-11-10', @@ -562,8 +556,7 @@ export const HybridBackend = { procedimentos: ['Profilaxia'], higiene: 5, cooperacao: 'Média', - observacoes: 'HIGIENE ABAIXO DO IDEAL. ORIENTAÇÕES REFORÇADAS.', - }, + observacoes: 'HIGIENE ABAIXO DO IDEAL. ORIENTAÇÕES REFORÇADAS.'}, { id: 'ev5', data: '2026-01-14', @@ -573,8 +566,7 @@ export const HybridBackend = { fio: '0.16', elastico: 'CLASSE II', higiene: 6, - cooperacao: 'Média', - }, + cooperacao: 'Média'}, ], flags: [ { tipo: 'warning', mensagem: 'HIGIENE ABAIXO DO IDEAL' }, @@ -583,8 +575,7 @@ export const HybridBackend = { fotos: [ { url: '', tipo: 'inicio', data: '2024-08-10' }, { url: '', tipo: 'atual', data: '2026-01-14' }, - ], - }, + ]}, { id: 't2', pacienteId: '2', @@ -601,15 +592,13 @@ export const HybridBackend = { periodonto: 'RECESSÃO GENGIVAL LEVE EM 31-41', implantes: 'NENHUM', recessoes: '31 E 41 - CLASSE I MILLER', - mobilidade: 'NENHUMA', - }, + mobilidade: 'NENHUMA'}, diagnostico: { classeAngle: 'III', perfil: 'Côncavo', apinhamento: 'Severo', mordida: 'Cruzada', - linhaMedia: '3MM PARA DIREITA', - }, + linhaMedia: '3MM PARA DIREITA'}, dataInicio: '2024-03-05', tipoAparelho: 'AUTOLIGADO CERÂMICO', extracoes: '1º PRÉ-MOLARES SUPERIORES (14 E 24)', @@ -637,8 +626,7 @@ export const HybridBackend = { proximoRetorno: '2024-04-05', fio: '0.14', higiene: 9, - cooperacao: 'Boa', - }, + cooperacao: 'Boa'}, { id: 'ev7', data: '2025-06-05', @@ -648,8 +636,7 @@ export const HybridBackend = { fio: '0.19x0.25', procedimentos: ['IPR'], higiene: 8, - cooperacao: 'Boa', - }, + cooperacao: 'Boa'}, { id: 'ev8', data: '2026-02-05', @@ -659,8 +646,7 @@ export const HybridBackend = { fio: '0.19x0.25', elastico: 'CLASSE III', higiene: 9, - cooperacao: 'Boa', - }, + cooperacao: 'Boa'}, ], flags: [ { tipo: 'danger', mensagem: 'COMPLEXIDADE ALTA - APINHAMENTO SEVERO' }, @@ -669,8 +655,7 @@ export const HybridBackend = { fotos: [ { url: '', tipo: 'inicio', data: '2024-03-05' }, { url: '', tipo: 'atual', data: '2026-02-05' }, - ], - }, + ]}, ]; }, @@ -686,8 +671,7 @@ export const HybridBackend = { procedimentos: evolucao.procedimentos, higiene: evolucao.higiene, cooperacao: evolucao.cooperacao, - observacoes: evolucao.observacoes, - }; + observacoes: evolucao.observacoes}; console.log(`[Mock] Evolução salva no tratamento ${tratamentoId}:`, newEv); return newEv; }, @@ -703,7 +687,7 @@ export const HybridBackend = { salvarAgendamento: async (agendamento: Partial) => { const data = enforceUppercase(agendamento); const body = { id: Math.random().toString(), ...data }; - const res = await fetch(`${API_URL}/agendamentos`, { + const res = await apiFetch(`${API_URL}/agendamentos`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) @@ -713,7 +697,7 @@ export const HybridBackend = { atualizarAgendamento: async (id: string, agendamento: Partial) => { const data = enforceUppercase(agendamento); - const res = await fetch(`${API_URL}/agendamentos/${id}`, { + const res = await apiFetch(`${API_URL}/agendamentos/${id}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data) @@ -722,7 +706,7 @@ export const HybridBackend = { }, excluirAgendamento: async (id: string) => { - await fetch(`${API_URL}/agendamentos/${id}`, { method: 'DELETE' }); + await apiFetch(`${API_URL}/agendamentos/${id}`, { method: 'DELETE' }); }, // --- LEAD & NOTIFICATION METHODS --- @@ -738,7 +722,7 @@ export const HybridBackend = { dataCadastro: new Date().toISOString(), status: 'Novo' }; - await fetch(`${API_URL}/leads`, { + await apiFetch(`${API_URL}/leads`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(newLead) @@ -747,13 +731,13 @@ export const HybridBackend = { }, getLeads: async (): Promise => { - const res = await fetch(`${API_URL}/leads`); + const res = await apiFetch(`${API_URL}/leads`); return await res.json(); }, getNotifications: async (): Promise => { try { - const res = await fetch(`${API_URL}/notificacoes`); + const res = await apiFetch(`${API_URL}/notificacoes`); return await res.json(); } catch { return []; @@ -762,7 +746,7 @@ export const HybridBackend = { markNotificationRead: async (id: string) => { try { - await fetch(`${API_URL}/notificacoes/${id}/read`, { method: 'PUT' }); + await apiFetch(`${API_URL}/notificacoes/${id}/read`, { method: 'PUT' }); } catch (err) { console.error("Erro ao marcar como lida:", err); } @@ -770,7 +754,7 @@ export const HybridBackend = { markAllNotificationsRead: async () => { try { - await fetch(`${API_URL}/notificacoes/read-all`, { method: 'POST' }); + await apiFetch(`${API_URL}/notificacoes/read-all`, { method: 'POST' }); } catch (err) { console.error("Erro ao marcar todas como lidas:", err); } @@ -778,19 +762,19 @@ export const HybridBackend = { deleteNotification: async (id: string) => { try { - await fetch(`${API_URL}/notificacoes/${id}`, { method: 'DELETE' }); + await apiFetch(`${API_URL}/notificacoes/${id}`, { method: 'DELETE' }); } catch (err) { console.error("Erro ao excluir notificação:", err); } }, getSettings: async (): Promise => { - const res = await fetch(`${API_URL}/settings`); + const res = await apiFetch(`${API_URL}/settings`); return await res.json(); }, saveSettings: async (settings: Settings) => { - const res = await fetch(`${API_URL}/settings`, { + const res = await apiFetch(`${API_URL}/settings`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(settings) @@ -800,7 +784,7 @@ export const HybridBackend = { getGoogleEvents: async (): Promise => { try { - const res = await fetch(`${API_URL}/google/events`); + const res = await apiFetch(`${API_URL}/google/events`); return await res.json(); } catch { return [];