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:
@@ -1,8 +1,14 @@
|
|||||||
import { writable, get } from 'svelte/store';
|
|
||||||
import type { Writable } from 'svelte/store';
|
import type { Writable } from 'svelte/store';
|
||||||
|
import { get, writable } from 'svelte/store';
|
||||||
import { auth } from './auth';
|
import { auth } from './auth';
|
||||||
|
|
||||||
// Types
|
// Types
|
||||||
|
interface FastAPIValidationError {
|
||||||
|
loc: (string | number)[];
|
||||||
|
msg: string;
|
||||||
|
type: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface Ticket {
|
export interface Ticket {
|
||||||
id: string;
|
id: string;
|
||||||
title: string;
|
title: string;
|
||||||
@@ -73,12 +79,17 @@ const initialState: TicketsState = {
|
|||||||
// API helper function
|
// API helper function
|
||||||
async function apiCall(endpoint: string, options: RequestInit = {}) {
|
async function apiCall(endpoint: string, options: RequestInit = {}) {
|
||||||
const authState = get(auth);
|
const authState = get(auth);
|
||||||
|
|
||||||
|
if (!authState.token || !authState.user) {
|
||||||
|
throw new Error('Not authenticated');
|
||||||
|
}
|
||||||
|
|
||||||
const response = await fetch(`/api/v1${endpoint}`, {
|
const response = await fetch(`/api/v1${endpoint}`, {
|
||||||
...options,
|
...options,
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
'Authorization': `Bearer ${authState.token}`,
|
'Authorization': `Bearer ${authState.token}`,
|
||||||
|
'X-Tenant-ID': authState.user.tenant_id,
|
||||||
...options.headers
|
...options.headers
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -88,12 +99,12 @@ async function apiCall(endpoint: string, options: RequestInit = {}) {
|
|||||||
try {
|
try {
|
||||||
const error = await response.json();
|
const error = await response.json();
|
||||||
console.error('❌ API Error Response:', error);
|
console.error('❌ API Error Response:', error);
|
||||||
|
|
||||||
// Manejar diferentes formatos de error de FastAPI
|
// Manejar diferentes formatos de error de FastAPI
|
||||||
if (error.detail) {
|
if (error.detail) {
|
||||||
if (Array.isArray(error.detail)) {
|
if (Array.isArray(error.detail)) {
|
||||||
// Errores de validación de FastAPI
|
// 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') {
|
} else if (typeof error.detail === 'string') {
|
||||||
errorMessage = error.detail;
|
errorMessage = error.detail;
|
||||||
} else {
|
} else {
|
||||||
@@ -105,7 +116,7 @@ async function apiCall(endpoint: string, options: RequestInit = {}) {
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
errorMessage = `HTTP ${response.status}: ${response.statusText}`;
|
errorMessage = `HTTP ${response.status}: ${response.statusText}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new Error(errorMessage);
|
throw new Error(errorMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -122,15 +133,15 @@ function createTicketsStore() {
|
|||||||
// Load user's tickets
|
// Load user's tickets
|
||||||
loadTickets: async () => {
|
loadTickets: async () => {
|
||||||
update((state: TicketsState) => ({ ...state, isLoading: true, error: null }));
|
update((state: TicketsState) => ({ ...state, isLoading: true, error: null }));
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const tickets = await apiCall('/tickets/');
|
const tickets = await apiCall('/tickets/');
|
||||||
update((state: TicketsState) => ({ ...state, tickets, isLoading: false }));
|
update((state: TicketsState) => ({ ...state, tickets, isLoading: false }));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
update((state: TicketsState) => ({
|
update((state: TicketsState) => ({
|
||||||
...state,
|
...state,
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
error: error instanceof Error ? error.message : 'Failed to load tickets'
|
error: error instanceof Error ? error.message : 'Failed to load tickets'
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -138,7 +149,7 @@ function createTicketsStore() {
|
|||||||
// Load specific ticket with details
|
// Load specific ticket with details
|
||||||
loadTicket: async (ticketId: string) => {
|
loadTicket: async (ticketId: string) => {
|
||||||
update((state: TicketsState) => ({ ...state, isLoading: true, error: null }));
|
update((state: TicketsState) => ({ ...state, isLoading: true, error: null }));
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const [ticket, comments, attachments] = await Promise.all([
|
const [ticket, comments, attachments] = await Promise.all([
|
||||||
apiCall(`/tickets/${ticketId}`),
|
apiCall(`/tickets/${ticketId}`),
|
||||||
@@ -146,18 +157,18 @@ function createTicketsStore() {
|
|||||||
apiCall(`/tickets/${ticketId}/attachments`)
|
apiCall(`/tickets/${ticketId}/attachments`)
|
||||||
]);
|
]);
|
||||||
|
|
||||||
update((state: TicketsState) => ({
|
update((state: TicketsState) => ({
|
||||||
...state,
|
...state,
|
||||||
currentTicket: ticket,
|
currentTicket: ticket,
|
||||||
comments,
|
comments,
|
||||||
attachments,
|
attachments,
|
||||||
isLoading: false
|
isLoading: false
|
||||||
}));
|
}));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
update((state: TicketsState) => ({
|
update((state: TicketsState) => ({
|
||||||
...state,
|
...state,
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
error: error instanceof Error ? error.message : 'Failed to load ticket'
|
error: error instanceof Error ? error.message : 'Failed to load ticket'
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -176,38 +187,38 @@ function createTicketsStore() {
|
|||||||
// Create new ticket
|
// Create new ticket
|
||||||
createTicket: async (ticket: CreateTicketRequest) => {
|
createTicket: async (ticket: CreateTicketRequest) => {
|
||||||
update((state: TicketsState) => ({ ...state, isLoading: true, error: null }));
|
update((state: TicketsState) => ({ ...state, isLoading: true, error: null }));
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Mapear campos del frontend al formato del backend
|
// Mapear campos del frontend al formato del backend
|
||||||
const ticketData = {
|
const ticketData = {
|
||||||
subject: ticket.title, // ← Backend espera "subject" no "title"
|
subject: ticket.title, // ← Backend espera "subject" no "title"
|
||||||
description: ticket.description,
|
description: ticket.description,
|
||||||
category_id: ticket.category_id,
|
category_id: ticket.category_id,
|
||||||
priority: ticket.priority,
|
priority: ticket.priority,
|
||||||
system_id: null // ← Opcional
|
system_id: null // ← Opcional
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
console.log('Sending ticket data:', ticketData);
|
|
||||||
|
console.log('Sending ticket data:', ticketData);
|
||||||
|
|
||||||
const newTicket = await apiCall('/tickets/', {
|
const newTicket = await apiCall('/tickets/', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: JSON.stringify(ticketData)
|
body: JSON.stringify(ticketData)
|
||||||
});
|
});
|
||||||
|
|
||||||
update((state: TicketsState) => ({
|
update((state: TicketsState) => ({
|
||||||
...state,
|
...state,
|
||||||
tickets: [newTicket, ...state.tickets],
|
tickets: [newTicket, ...state.tickets],
|
||||||
isLoading: false
|
isLoading: false
|
||||||
}));
|
}));
|
||||||
|
|
||||||
return newTicket;
|
return newTicket;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Create ticket error:', error);
|
console.error('Create ticket error:', error);
|
||||||
update((state: TicketsState) => ({
|
update((state: TicketsState) => ({
|
||||||
...state,
|
...state,
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
error: error instanceof Error ? error.message : 'Failed to create ticket'
|
error: error instanceof Error ? error.message : 'Failed to create ticket'
|
||||||
}));
|
}));
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
@@ -221,16 +232,16 @@ function createTicketsStore() {
|
|||||||
body: JSON.stringify({ content })
|
body: JSON.stringify({ content })
|
||||||
});
|
});
|
||||||
|
|
||||||
update((state: TicketsState) => ({
|
update((state: TicketsState) => ({
|
||||||
...state,
|
...state,
|
||||||
comments: [...state.comments, comment]
|
comments: [...state.comments, comment]
|
||||||
}));
|
}));
|
||||||
|
|
||||||
return comment;
|
return comment;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
update((state: TicketsState) => ({
|
update((state: TicketsState) => ({
|
||||||
...state,
|
...state,
|
||||||
error: error instanceof Error ? error.message : 'Failed to add comment'
|
error: error instanceof Error ? error.message : 'Failed to add comment'
|
||||||
}));
|
}));
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
@@ -243,10 +254,16 @@ function createTicketsStore() {
|
|||||||
formData.append('file', file);
|
formData.append('file', file);
|
||||||
|
|
||||||
const authState = get(auth);
|
const authState = get(auth);
|
||||||
|
|
||||||
|
if (!authState.token || !authState.user) {
|
||||||
|
throw new Error('Not authenticated');
|
||||||
|
}
|
||||||
|
|
||||||
const response = await fetch(`/api/v1/tickets/${ticketId}/attachments`, {
|
const response = await fetch(`/api/v1/tickets/${ticketId}/attachments`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Authorization': `Bearer ${authState.token}`
|
'Authorization': `Bearer ${authState.token}`,
|
||||||
|
'X-Tenant-ID': authState.user.tenant_id
|
||||||
},
|
},
|
||||||
body: formData
|
body: formData
|
||||||
});
|
});
|
||||||
@@ -256,18 +273,19 @@ function createTicketsStore() {
|
|||||||
throw new Error(error.detail || 'Upload failed');
|
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,
|
update((state: TicketsState) => ({
|
||||||
attachments: [...state.attachments, attachment]
|
...state,
|
||||||
|
attachments: [...state.attachments, attachment]
|
||||||
}));
|
}));
|
||||||
|
|
||||||
return attachment;
|
return attachment;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
update((state: TicketsState) => ({
|
update((state: TicketsState) => ({
|
||||||
...state,
|
...state,
|
||||||
error: error instanceof Error ? error.message : 'Failed to upload attachment'
|
error: error instanceof Error ? error.message : 'Failed to upload attachment'
|
||||||
}));
|
}));
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
@@ -289,9 +307,9 @@ function createTicketsStore() {
|
|||||||
|
|
||||||
return updatedTicket;
|
return updatedTicket;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
update((state: TicketsState) => ({
|
update((state: TicketsState) => ({
|
||||||
...state,
|
...state,
|
||||||
error: error instanceof Error ? error.message : 'Failed to close ticket'
|
error: error instanceof Error ? error.message : 'Failed to close ticket'
|
||||||
}));
|
}));
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
@@ -304,11 +322,11 @@ function createTicketsStore() {
|
|||||||
|
|
||||||
// Clear current ticket
|
// Clear current ticket
|
||||||
clearCurrentTicket: () => {
|
clearCurrentTicket: () => {
|
||||||
update((state: TicketsState) => ({
|
update((state: TicketsState) => ({
|
||||||
...state,
|
...state,
|
||||||
currentTicket: null,
|
currentTicket: null,
|
||||||
comments: [],
|
comments: [],
|
||||||
attachments: []
|
attachments: []
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -77,9 +77,15 @@
|
|||||||
|
|
||||||
async function loadBusinessProfile() {
|
async function loadBusinessProfile() {
|
||||||
try {
|
try {
|
||||||
|
if (!$auth.token || !$auth.user) {
|
||||||
|
console.warn('Usuario no autenticado');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const response = await fetch('/api/v1/client-profile/', {
|
const response = await fetch('/api/v1/client-profile/', {
|
||||||
headers: {
|
headers: {
|
||||||
Authorization: `Bearer ${$auth.token}`
|
Authorization: `Bearer ${$auth.token}`,
|
||||||
|
'X-Tenant-ID': $auth.user.tenant_id
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -91,6 +97,12 @@
|
|||||||
businessProfile[key] = profile[key];
|
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) {
|
} catch (error) {
|
||||||
console.warn('No se pudo cargar el perfil empresarial:', error);
|
console.warn('No se pudo cargar el perfil empresarial:', error);
|
||||||
@@ -261,7 +273,8 @@
|
|||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
Authorization: `Bearer ${$auth.token}`
|
Authorization: `Bearer ${$auth.token}`,
|
||||||
|
'X-Tenant-ID': $auth.user.tenant_id
|
||||||
},
|
},
|
||||||
body: JSON.stringify(profileData)
|
body: JSON.stringify(profileData)
|
||||||
});
|
});
|
||||||
@@ -305,51 +318,51 @@
|
|||||||
<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 ===
|
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'
|
'personal'
|
||||||
? 'border-primary-500 text-primary-600'
|
? 'border-white text-white'
|
||||||
: ''}"
|
: ''}"
|
||||||
on:click={() => (activeTab = 'personal')}
|
on:click={() => (activeTab = 'personal')}
|
||||||
>
|
>
|
||||||
👤 Personal
|
Personal
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<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 ===
|
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'
|
'general'
|
||||||
? 'border-primary-500 text-primary-600'
|
? 'border-white text-white'
|
||||||
: ''}"
|
: ''}"
|
||||||
on:click={() => (activeTab = 'general')}
|
on:click={() => (activeTab = 'general')}
|
||||||
>
|
>
|
||||||
🏢 General
|
General
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<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 ===
|
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'
|
'contact'
|
||||||
? 'border-primary-500 text-primary-600'
|
? 'border-white text-white'
|
||||||
: ''}"
|
: ''}"
|
||||||
on:click={() => (activeTab = 'contact')}
|
on:click={() => (activeTab = 'contact')}
|
||||||
>
|
>
|
||||||
📞 Contacto
|
Contacto
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<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 ===
|
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'
|
'security'
|
||||||
? 'border-primary-500 text-primary-600'
|
? 'border-white text-white'
|
||||||
: ''}"
|
: ''}"
|
||||||
on:click={() => (activeTab = 'security')}
|
on:click={() => (activeTab = 'security')}
|
||||||
>
|
>
|
||||||
🔐 Seguridad
|
Seguridad
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<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 ===
|
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'
|
'account'
|
||||||
? 'border-primary-500 text-primary-600'
|
? 'border-white text-white'
|
||||||
: ''}"
|
: ''}"
|
||||||
on:click={() => (activeTab = 'account')}
|
on:click={() => (activeTab = 'account')}
|
||||||
>
|
>
|
||||||
ℹ️ Cuenta
|
Cuenta
|
||||||
</button>
|
</button>
|
||||||
</nav>
|
</nav>
|
||||||
</div>
|
</div>
|
||||||
@@ -417,7 +430,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex justify-end">
|
<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}
|
{#if isUpdatingProfile}
|
||||||
<div class="flex items-center space-x-2">
|
<div class="flex items-center space-x-2">
|
||||||
<div class="spinner w-4 h-4" />
|
<div class="spinner w-4 h-4" />
|
||||||
@@ -517,7 +530,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Ubicación -->
|
<!-- 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>
|
<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 class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||||
<div>
|
<div>
|
||||||
@@ -602,7 +615,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Representantes -->
|
<!-- 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>
|
<h3 class="font-medium text-gray-900 mb-4">Representantes</h3>
|
||||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||||
<div>
|
<div>
|
||||||
@@ -628,7 +641,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Configuración -->
|
<!-- 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>
|
<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 class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||||
<div>
|
<div>
|
||||||
@@ -680,7 +693,7 @@
|
|||||||
<div class="flex justify-end pt-6">
|
<div class="flex justify-end pt-6">
|
||||||
<button
|
<button
|
||||||
type="submit"
|
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}
|
disabled={isSavingBusinessProfile}
|
||||||
>
|
>
|
||||||
{#if isSavingBusinessProfile}
|
{#if isSavingBusinessProfile}
|
||||||
@@ -689,7 +702,7 @@
|
|||||||
<span>Guardando...</span>
|
<span>Guardando...</span>
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
💾 Guardar Perfil Empresarial
|
Guardar Perfil Empresarial
|
||||||
{/if}
|
{/if}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -709,8 +722,8 @@
|
|||||||
<div class="card-content">
|
<div class="card-content">
|
||||||
<form on:submit|preventDefault={handleBusinessProfileSave} class="space-y-6">
|
<form on:submit|preventDefault={handleBusinessProfileSave} class="space-y-6">
|
||||||
<!-- Teléfonos -->
|
<!-- Teléfonos -->
|
||||||
<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">📞 Teléfonos</h3>
|
<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 class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||||
<div>
|
<div>
|
||||||
<label class="form-label">Teléfono Principal</label>
|
<label class="form-label">Teléfono Principal</label>
|
||||||
@@ -766,8 +779,8 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Emails -->
|
<!-- Emails -->
|
||||||
<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">✉️ Correos Electrónicos</h3>
|
<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 class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||||
<div>
|
<div>
|
||||||
<label class="form-label">Email Principal</label>
|
<label class="form-label">Email Principal</label>
|
||||||
@@ -798,8 +811,8 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Web y Horarios -->
|
<!-- Web y Horarios -->
|
||||||
<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">🌐 Web y Horarios</h3>
|
<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 class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||||
<div>
|
<div>
|
||||||
<label class="form-label">Página Web</label>
|
<label class="form-label">Página Web</label>
|
||||||
@@ -836,7 +849,7 @@
|
|||||||
<div class="flex justify-end pt-6">
|
<div class="flex justify-end pt-6">
|
||||||
<button
|
<button
|
||||||
type="submit"
|
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}
|
disabled={isSavingBusinessProfile}
|
||||||
>
|
>
|
||||||
{#if isSavingBusinessProfile}
|
{#if isSavingBusinessProfile}
|
||||||
@@ -845,7 +858,7 @@
|
|||||||
<span>Guardando...</span>
|
<span>Guardando...</span>
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
💾 Guardar Información de Contacto
|
Guardar Información de Contacto
|
||||||
{/if}
|
{/if}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -959,7 +972,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex justify-end">
|
<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}
|
{#if isChangingPassword}
|
||||||
<div class="flex items-center space-x-2">
|
<div class="flex items-center space-x-2">
|
||||||
<div class="spinner w-4 h-4" />
|
<div class="spinner w-4 h-4" />
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ export default defineConfig({
|
|||||||
host: '0.0.0.0',
|
host: '0.0.0.0',
|
||||||
proxy: {
|
proxy: {
|
||||||
'/api': {
|
'/api': {
|
||||||
target: 'http://servicemanager-backend:8000',
|
target: 'http://backend:8000',
|
||||||
changeOrigin: true,
|
changeOrigin: true,
|
||||||
rewrite: (path) => path.replace(/^\/api/, '')
|
rewrite: (path) => path.replace(/^\/api/, '')
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user