From ac0cb6f132ed7aff1746e47e3b7e3681d6eb480f Mon Sep 17 00:00:00 2001 From: icamarillo Date: Mon, 9 Feb 2026 13:28:16 -0700 Subject: [PATCH] fix(frontend): Fix client API calls and improve profile UI - Fixed proxy configuration in vite.config.js (servicemanager-backend -> backend) - Added X-Tenant-ID header to client-profile API calls - Improved error handling with proper authentication checks - Updated profile page UI to white theme (removed icons and colors) - Changed all btn-primary buttons to white theme styling - Enhanced API call error handling in tickets store --- frontend-client/src/lib/stores/tickets.ts | 142 ++++++++++-------- .../src/routes/profile/+page.svelte | 67 +++++---- frontend-client/vite.config.js | 2 +- 3 files changed, 121 insertions(+), 90 deletions(-) diff --git a/frontend-client/src/lib/stores/tickets.ts b/frontend-client/src/lib/stores/tickets.ts index aee9668..527567f 100644 --- a/frontend-client/src/lib/stores/tickets.ts +++ b/frontend-client/src/lib/stores/tickets.ts @@ -1,8 +1,14 @@ -import { writable, get } from 'svelte/store'; import type { Writable } from 'svelte/store'; +import { get, writable } from 'svelte/store'; import { auth } from './auth'; // Types +interface FastAPIValidationError { + loc: (string | number)[]; + msg: string; + type: string; +} + export interface Ticket { id: string; title: string; @@ -73,12 +79,17 @@ const initialState: TicketsState = { // API helper function async function apiCall(endpoint: string, options: RequestInit = {}) { const authState = get(auth); - + + if (!authState.token || !authState.user) { + throw new Error('Not authenticated'); + } + const response = await fetch(`/api/v1${endpoint}`, { ...options, headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${authState.token}`, + 'X-Tenant-ID': authState.user.tenant_id, ...options.headers } }); @@ -88,12 +99,12 @@ async function apiCall(endpoint: string, options: RequestInit = {}) { try { const error = await response.json(); console.error('❌ API Error Response:', error); - + // Manejar diferentes formatos de error de FastAPI if (error.detail) { if (Array.isArray(error.detail)) { // Errores de validación de FastAPI - errorMessage = error.detail.map(e => `${e.loc.join('.')}: ${e.msg}`).join(', '); + errorMessage = error.detail.map((e: FastAPIValidationError) => `${e.loc.join('.')}: ${e.msg}`).join(', '); } else if (typeof error.detail === 'string') { errorMessage = error.detail; } else { @@ -105,7 +116,7 @@ async function apiCall(endpoint: string, options: RequestInit = {}) { } catch (e) { errorMessage = `HTTP ${response.status}: ${response.statusText}`; } - + throw new Error(errorMessage); } @@ -122,15 +133,15 @@ function createTicketsStore() { // Load user's tickets loadTickets: async () => { update((state: TicketsState) => ({ ...state, isLoading: true, error: null })); - + try { const tickets = await apiCall('/tickets/'); update((state: TicketsState) => ({ ...state, tickets, isLoading: false })); } catch (error) { - update((state: TicketsState) => ({ - ...state, - isLoading: false, - error: error instanceof Error ? error.message : 'Failed to load tickets' + update((state: TicketsState) => ({ + ...state, + isLoading: false, + error: error instanceof Error ? error.message : 'Failed to load tickets' })); } }, @@ -138,7 +149,7 @@ function createTicketsStore() { // Load specific ticket with details loadTicket: async (ticketId: string) => { update((state: TicketsState) => ({ ...state, isLoading: true, error: null })); - + try { const [ticket, comments, attachments] = await Promise.all([ apiCall(`/tickets/${ticketId}`), @@ -146,18 +157,18 @@ function createTicketsStore() { apiCall(`/tickets/${ticketId}/attachments`) ]); - update((state: TicketsState) => ({ - ...state, - currentTicket: ticket, - comments, - attachments, - isLoading: false + update((state: TicketsState) => ({ + ...state, + currentTicket: ticket, + comments, + attachments, + isLoading: false })); } catch (error) { - update((state: TicketsState) => ({ - ...state, - isLoading: false, - error: error instanceof Error ? error.message : 'Failed to load ticket' + update((state: TicketsState) => ({ + ...state, + isLoading: false, + error: error instanceof Error ? error.message : 'Failed to load ticket' })); } }, @@ -176,38 +187,38 @@ function createTicketsStore() { // Create new ticket createTicket: async (ticket: CreateTicketRequest) => { update((state: TicketsState) => ({ ...state, isLoading: true, error: null })); - + try { // Mapear campos del frontend al formato del backend - const ticketData = { - subject: ticket.title, // ← Backend espera "subject" no "title" - description: ticket.description, - category_id: ticket.category_id, - priority: ticket.priority, - system_id: null // ← Opcional - }; - + const ticketData = { + subject: ticket.title, // ← Backend espera "subject" no "title" + description: ticket.description, + category_id: ticket.category_id, + priority: ticket.priority, + system_id: null // ← Opcional + }; - console.log('Sending ticket data:', ticketData); + + console.log('Sending ticket data:', ticketData); const newTicket = await apiCall('/tickets/', { method: 'POST', body: JSON.stringify(ticketData) }); - update((state: TicketsState) => ({ - ...state, - tickets: [newTicket, ...state.tickets], - isLoading: false + update((state: TicketsState) => ({ + ...state, + tickets: [newTicket, ...state.tickets], + isLoading: false })); return newTicket; } catch (error) { console.error('Create ticket error:', error); - update((state: TicketsState) => ({ - ...state, - isLoading: false, - error: error instanceof Error ? error.message : 'Failed to create ticket' + update((state: TicketsState) => ({ + ...state, + isLoading: false, + error: error instanceof Error ? error.message : 'Failed to create ticket' })); throw error; } @@ -221,16 +232,16 @@ function createTicketsStore() { body: JSON.stringify({ content }) }); - update((state: TicketsState) => ({ - ...state, - comments: [...state.comments, comment] + update((state: TicketsState) => ({ + ...state, + comments: [...state.comments, comment] })); return comment; } catch (error) { - update((state: TicketsState) => ({ - ...state, - error: error instanceof Error ? error.message : 'Failed to add comment' + update((state: TicketsState) => ({ + ...state, + error: error instanceof Error ? error.message : 'Failed to add comment' })); throw error; } @@ -243,10 +254,16 @@ function createTicketsStore() { formData.append('file', file); const authState = get(auth); + + if (!authState.token || !authState.user) { + throw new Error('Not authenticated'); + } + const response = await fetch(`/api/v1/tickets/${ticketId}/attachments`, { method: 'POST', headers: { - 'Authorization': `Bearer ${authState.token}` + 'Authorization': `Bearer ${authState.token}`, + 'X-Tenant-ID': authState.user.tenant_id }, body: formData }); @@ -256,18 +273,19 @@ function createTicketsStore() { throw new Error(error.detail || 'Upload failed'); } - const attachment = await response.json(); - - update((state: TicketsState) => ({ - ...state, - attachments: [...state.attachments, attachment] + const result = await response.json(); + const attachment = result.data || result; + + update((state: TicketsState) => ({ + ...state, + attachments: [...state.attachments, attachment] })); return attachment; } catch (error) { - update((state: TicketsState) => ({ - ...state, - error: error instanceof Error ? error.message : 'Failed to upload attachment' + update((state: TicketsState) => ({ + ...state, + error: error instanceof Error ? error.message : 'Failed to upload attachment' })); throw error; } @@ -289,9 +307,9 @@ function createTicketsStore() { return updatedTicket; } catch (error) { - update((state: TicketsState) => ({ - ...state, - error: error instanceof Error ? error.message : 'Failed to close ticket' + update((state: TicketsState) => ({ + ...state, + error: error instanceof Error ? error.message : 'Failed to close ticket' })); throw error; } @@ -304,11 +322,11 @@ function createTicketsStore() { // Clear current ticket clearCurrentTicket: () => { - update((state: TicketsState) => ({ - ...state, - currentTicket: null, - comments: [], - attachments: [] + update((state: TicketsState) => ({ + ...state, + currentTicket: null, + comments: [], + attachments: [] })); } }; diff --git a/frontend-client/src/routes/profile/+page.svelte b/frontend-client/src/routes/profile/+page.svelte index bf614be..154da75 100644 --- a/frontend-client/src/routes/profile/+page.svelte +++ b/frontend-client/src/routes/profile/+page.svelte @@ -77,9 +77,15 @@ async function loadBusinessProfile() { try { + if (!$auth.token || !$auth.user) { + console.warn('Usuario no autenticado'); + return; + } + const response = await fetch('/api/v1/client-profile/', { headers: { - Authorization: `Bearer ${$auth.token}` + Authorization: `Bearer ${$auth.token}`, + 'X-Tenant-ID': $auth.user.tenant_id } }); @@ -91,6 +97,12 @@ businessProfile[key] = profile[key]; } }); + } else if (response.status === 404) { + // No hay perfil aún, esto es normal para nuevos clientes + console.info('No se encontró perfil empresarial existente'); + } else { + const error = await response.json().catch(() => ({ detail: 'Error desconocido' })); + console.error('Error al cargar perfil empresarial:', error); } } catch (error) { console.warn('No se pudo cargar el perfil empresarial:', error); @@ -261,7 +273,8 @@ method: 'POST', headers: { 'Content-Type': 'application/json', - Authorization: `Bearer ${$auth.token}` + Authorization: `Bearer ${$auth.token}`, + 'X-Tenant-ID': $auth.user.tenant_id }, body: JSON.stringify(profileData) }); @@ -305,51 +318,51 @@ @@ -417,7 +430,7 @@
-
{:else} - 💾 Guardar Perfil Empresarial + Guardar Perfil Empresarial {/if} @@ -709,8 +722,8 @@
-
-

📞 Teléfonos

+
+

Teléfonos

@@ -766,8 +779,8 @@
-
-

✉️ Correos Electrónicos

+
+

Correos Electrónicos

@@ -798,8 +811,8 @@
-
-

🌐 Web y Horarios

+
+

Web y Horarios

@@ -836,7 +849,7 @@
{:else} - 💾 Guardar Información de Contacto + Guardar Información de Contacto {/if}
@@ -959,7 +972,7 @@
-