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
This commit is contained in:
2026-02-09 13:28:16 -07:00
parent d4ff32dac7
commit ac0cb6f132
3 changed files with 121 additions and 90 deletions

View File

@@ -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: []
}));
}
};