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;
@@ -74,11 +80,16 @@ const initialState: TicketsState = {
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
}
});
@@ -93,7 +104,7 @@ async function apiCall(endpoint: string, options: RequestInit = {}) {
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 {
@@ -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,7 +273,8 @@ function createTicketsStore() {
throw new Error(error.detail || 'Upload failed');
}
const attachment = await response.json();
const result = await response.json();
const attachment = result.data || result;
update((state: TicketsState) => ({
...state,

View File

@@ -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 @@
<button
class="border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300 whitespace-nowrap py-2 px-1 border-b-2 font-medium text-sm transition-colors {activeTab ===
'personal'
? 'border-primary-500 text-primary-600'
? 'border-white text-white'
: ''}"
on:click={() => (activeTab = 'personal')}
>
👤 Personal
Personal
</button>
<button
class="border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300 whitespace-nowrap py-2 px-1 border-b-2 font-medium text-sm transition-colors {activeTab ===
'general'
? 'border-primary-500 text-primary-600'
? 'border-white text-white'
: ''}"
on:click={() => (activeTab = 'general')}
>
🏢 General
General
</button>
<button
class="border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300 whitespace-nowrap py-2 px-1 border-b-2 font-medium text-sm transition-colors {activeTab ===
'contact'
? 'border-primary-500 text-primary-600'
? 'border-white text-white'
: ''}"
on:click={() => (activeTab = 'contact')}
>
📞 Contacto
Contacto
</button>
<button
class="border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300 whitespace-nowrap py-2 px-1 border-b-2 font-medium text-sm transition-colors {activeTab ===
'security'
? 'border-primary-500 text-primary-600'
? 'border-white text-white'
: ''}"
on:click={() => (activeTab = 'security')}
>
🔐 Seguridad
Seguridad
</button>
<button
class="border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300 whitespace-nowrap py-2 px-1 border-b-2 font-medium text-sm transition-colors {activeTab ===
'account'
? 'border-primary-500 text-primary-600'
? 'border-white text-white'
: ''}"
on:click={() => (activeTab = 'account')}
>
Cuenta
Cuenta
</button>
</nav>
</div>
@@ -417,7 +430,7 @@
</div>
<div class="flex justify-end">
<button type="submit" class="btn-primary px-6 py-2" disabled={isUpdatingProfile}>
<button type="submit" class="bg-white border border-gray-300 text-gray-700 hover:bg-gray-50 px-6 py-2 rounded-md font-medium transition-colors" disabled={isUpdatingProfile}>
{#if isUpdatingProfile}
<div class="flex items-center space-x-2">
<div class="spinner w-4 h-4" />
@@ -517,7 +530,7 @@
</div>
<!-- Ubicación -->
<div class="bg-blue-50 p-4 rounded-lg">
<div class="bg-white p-4 rounded-lg border border-gray-200">
<h3 class="font-medium text-gray-900 mb-4">Ubicación</h3>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
<div>
@@ -602,7 +615,7 @@
</div>
<!-- Representantes -->
<div class="bg-purple-50 p-4 rounded-lg">
<div class="bg-white p-4 rounded-lg border border-gray-200">
<h3 class="font-medium text-gray-900 mb-4">Representantes</h3>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<div>
@@ -628,7 +641,7 @@
</div>
<!-- Configuración -->
<div class="bg-green-50 p-4 rounded-lg">
<div class="bg-white p-4 rounded-lg border border-gray-200">
<h3 class="font-medium text-gray-900 mb-4">Configuración</h3>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<div>
@@ -680,7 +693,7 @@
<div class="flex justify-end pt-6">
<button
type="submit"
class="btn-primary px-8 py-2"
class="bg-white border border-gray-300 text-gray-700 hover:bg-gray-50 px-8 py-2 rounded-md font-medium transition-colors"
disabled={isSavingBusinessProfile}
>
{#if isSavingBusinessProfile}
@@ -689,7 +702,7 @@
<span>Guardando...</span>
</div>
{:else}
💾 Guardar Perfil Empresarial
Guardar Perfil Empresarial
{/if}
</button>
</div>
@@ -709,8 +722,8 @@
<div class="card-content">
<form on:submit|preventDefault={handleBusinessProfileSave} class="space-y-6">
<!-- Teléfonos -->
<div class="bg-blue-50 p-4 rounded-lg">
<h3 class="font-medium text-gray-900 mb-4">📞 Teléfonos</h3>
<div class="bg-white p-4 rounded-lg border border-gray-200">
<h3 class="font-medium text-gray-900 mb-4">Teléfonos</h3>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<div>
<label class="form-label">Teléfono Principal</label>
@@ -766,8 +779,8 @@
</div>
<!-- Emails -->
<div class="bg-green-50 p-4 rounded-lg">
<h3 class="font-medium text-gray-900 mb-4">✉️ Correos Electrónicos</h3>
<div class="bg-white p-4 rounded-lg border border-gray-200">
<h3 class="font-medium text-gray-900 mb-4">Correos Electrónicos</h3>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<div>
<label class="form-label">Email Principal</label>
@@ -798,8 +811,8 @@
</div>
<!-- Web y Horarios -->
<div class="bg-purple-50 p-4 rounded-lg">
<h3 class="font-medium text-gray-900 mb-4">🌐 Web y Horarios</h3>
<div class="bg-white p-4 rounded-lg border border-gray-200">
<h3 class="font-medium text-gray-900 mb-4">Web y Horarios</h3>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<div>
<label class="form-label">Página Web</label>
@@ -836,7 +849,7 @@
<div class="flex justify-end pt-6">
<button
type="submit"
class="btn-primary px-8 py-2"
class="bg-white border border-gray-300 text-gray-700 hover:bg-gray-50 px-8 py-2 rounded-md font-medium transition-colors"
disabled={isSavingBusinessProfile}
>
{#if isSavingBusinessProfile}
@@ -845,7 +858,7 @@
<span>Guardando...</span>
</div>
{:else}
💾 Guardar Información de Contacto
Guardar Información de Contacto
{/if}
</button>
</div>
@@ -959,7 +972,7 @@
</div>
<div class="flex justify-end">
<button type="submit" class="btn-primary px-6 py-2" disabled={isChangingPassword}>
<button type="submit" class="bg-white border border-gray-300 text-gray-700 hover:bg-gray-50 px-6 py-2 rounded-md font-medium transition-colors" disabled={isChangingPassword}>
{#if isChangingPassword}
<div class="flex items-center space-x-2">
<div class="spinner w-4 h-4" />

View File

@@ -8,7 +8,7 @@ export default defineConfig({
host: '0.0.0.0',
proxy: {
'/api': {
target: 'http://servicemanager-backend:8000',
target: 'http://backend:8000',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, '')
}