Fix new Frontend 7

This commit is contained in:
Urtzi Alfaro
2025-08-04 08:42:35 +02:00
parent 0e2c6dd54e
commit a8dbd37c5a
4 changed files with 76 additions and 35 deletions

View File

@@ -12,7 +12,7 @@ import { ApiErrorHandler } from '../utils';
* Authentication Interceptor * Authentication Interceptor
* Automatically adds authentication headers to requests * Automatically adds authentication headers to requests
*/ */
export class AuthInterceptor { class AuthInterceptor {
static setup() { static setup() {
apiClient.addRequestInterceptor({ apiClient.addRequestInterceptor({
onRequest: async (config: RequestConfig) => { onRequest: async (config: RequestConfig) => {
@@ -58,7 +58,7 @@ export class AuthInterceptor {
* Logging Interceptor * Logging Interceptor
* Logs API requests and responses for debugging * Logs API requests and responses for debugging
*/ */
export class LoggingInterceptor { class LoggingInterceptor {
static setup() { static setup() {
apiClient.addRequestInterceptor({ apiClient.addRequestInterceptor({
onRequest: async (config: RequestConfig) => { onRequest: async (config: RequestConfig) => {
@@ -120,7 +120,7 @@ export class LoggingInterceptor {
* Tenant Context Interceptor * Tenant Context Interceptor
* Automatically adds tenant context to tenant-scoped requests * Automatically adds tenant context to tenant-scoped requests
*/ */
export class TenantInterceptor { class TenantInterceptor {
private static currentTenantId: string | null = null; private static currentTenantId: string | null = null;
static setCurrentTenant(tenantId: string | null) { static setCurrentTenant(tenantId: string | null) {
@@ -160,7 +160,7 @@ export class TenantInterceptor {
* Error Recovery Interceptor * Error Recovery Interceptor
* Handles automatic token refresh and retry logic * Handles automatic token refresh and retry logic
*/ */
export class ErrorRecoveryInterceptor { class ErrorRecoveryInterceptor {
private static isRefreshing = false; private static isRefreshing = false;
private static failedQueue: Array<{ private static failedQueue: Array<{
resolve: (token: string) => void; resolve: (token: string) => void;
@@ -261,7 +261,7 @@ export class ErrorRecoveryInterceptor {
* Performance Monitoring Interceptor * Performance Monitoring Interceptor
* Tracks API performance metrics * Tracks API performance metrics
*/ */
export class PerformanceInterceptor { class PerformanceInterceptor {
private static metrics: Array<{ private static metrics: Array<{
url: string; url: string;
method: string; method: string;
@@ -351,7 +351,9 @@ export class PerformanceInterceptor {
export const setupInterceptors = () => { export const setupInterceptors = () => {
AuthInterceptor.setup(); AuthInterceptor.setup();
if (process.env.NODE_ENV === 'development') { const isDevelopment = import.meta.env.DEV;
if (isDevelopment) {
LoggingInterceptor.setup(); LoggingInterceptor.setup();
PerformanceInterceptor.setup(); PerformanceInterceptor.setup();
} }

View File

@@ -61,6 +61,8 @@ export interface TenantSubscription {
export interface TenantCreate { export interface TenantCreate {
name: string; name: string;
postal_code: string;
phone: string;
description?: string; description?: string;
settings?: Partial<TenantSettings>; settings?: Partial<TenantSettings>;
location?: TenantLocation; location?: TenantLocation;

View File

@@ -2,9 +2,13 @@ import React, { useState } from 'react';
import { ChevronLeft, ChevronRight, Upload, MapPin, Store, Factory, Check } from 'lucide-react'; import { ChevronLeft, ChevronRight, Upload, MapPin, Store, Factory, Check } from 'lucide-react';
import toast from 'react-hot-toast'; import toast from 'react-hot-toast';
import { useTenant, useTraining, useData, useAuth } from '../../api/hooks'; import {
import { TenantCreate } from '../../api/types'; useTenant,
import { UserResponse } from '../../api/types'; useTraining,
useData,
useAuth,
TenantCreate
} from '../../api';
interface OnboardingPageProps { interface OnboardingPageProps {
user: any; user: any;
@@ -40,8 +44,7 @@ const OnboardingPage: React.FC<OnboardingPageProps> = ({ user, onComplete }) =>
const { createTenant, isLoading: tenantLoading } = useTenant(); const { createTenant, isLoading: tenantLoading } = useTenant();
const { startTrainingJob } = useTraining(); const { startTrainingJob } = useTraining();
const { uploadSalesHistory, validateSalesData, uploadProgress } = useData(); const { uploadSalesHistory, validateSalesData } = useData();
const [ setUploadProgress ] = useState<number>(0);
const steps = [ const steps = [
{ id: 1, title: 'Datos de Panadería', icon: Store }, { id: 1, title: 'Datos de Panadería', icon: Store },
@@ -99,12 +102,21 @@ const OnboardingPage: React.FC<OnboardingPageProps> = ({ user, onComplete }) =>
const tenantData: TenantCreate = { const tenantData: TenantCreate = {
name: bakeryData.name, name: bakeryData.name,
address: bakeryData.address, address: bakeryData.address,
business_type: bakeryData.businessType, business_type:"bakery",
postal_code: "28010",
phone: "+34655334455",
coordinates: bakeryData.coordinates, coordinates: bakeryData.coordinates,
products: bakeryData.products, products: bakeryData.products,
has_historical_data: bakeryData.hasHistoricalData, has_historical_data: bakeryData.hasHistoricalData,
}; };
const token = localStorage.getItem('auth_token');
if (!token) {
toast.error('Sesión expirada. Por favor, inicia sesión nuevamente.');
// Redirect to login or handle authentication error
return;
}
const newTenant = await createTenant(tenantData); const newTenant = await createTenant(tenantData);
const tenantId = newTenant.id; const tenantId = newTenant.id;
@@ -147,10 +159,7 @@ const OnboardingPage: React.FC<OnboardingPageProps> = ({ user, onComplete }) =>
const uploadResult = await uploadSalesHistory(tenantId, bakeryData.csvFile, { const uploadResult = await uploadSalesHistory(tenantId, bakeryData.csvFile, {
source: 'onboarding_upload', source: 'onboarding_upload',
validate_only: false, validate_only: false
onProgress: (progress) => {
setUploadProgress(progress.percentage);
}
}); });
toast.dismiss('csv-upload'); toast.dismiss('csv-upload');
@@ -188,9 +197,6 @@ const OnboardingPage: React.FC<OnboardingPageProps> = ({ user, onComplete }) =>
throw new Error(`Upload failed: ${uploadResult.errors?.join(', ') || 'Unknown error'}`); throw new Error(`Upload failed: ${uploadResult.errors?.join(', ') || 'Unknown error'}`);
} }
// Reset progress when done
setUploadProgress(0);
} catch (uploadError) { } catch (uploadError) {
// Handle validation or upload error gracefully // Handle validation or upload error gracefully
console.error('CSV validation/upload error:', uploadError); console.error('CSV validation/upload error:', uploadError);
@@ -210,11 +216,16 @@ const OnboardingPage: React.FC<OnboardingPageProps> = ({ user, onComplete }) =>
} catch (error) { } catch (error) {
console.error('Onboarding completion error:', error); console.error('Onboarding completion error:', error);
const errorMessage = error instanceof Error ? error.message : 'Error al completar la configuración';
toast.error(errorMessage); // 🔧 ADD: Better error handling for auth issues
if (error instanceof Error && error.message.includes('401')) {
toast.error('Sesión expirada. Por favor, inicia sesión nuevamente.');
} else {
toast.error('Error en la configuración. Inténtalo de nuevo.');
}
} finally { } finally {
setIsLoading(false); setIsLoading(false);
setUploadProgress(0); // Reset progress in case of error
} }
}; };

View File

@@ -247,26 +247,52 @@ async def import_sales_data(
@router.post("/tenants/{tenant_id}/sales/import/validate", response_model=SalesValidationResult) @router.post("/tenants/{tenant_id}/sales/import/validate", response_model=SalesValidationResult)
async def validate_import_data( async def validate_import_data(
import_data: SalesDataImport,
tenant_id: UUID = Path(..., description="Tenant ID"), tenant_id: UUID = Path(..., description="Tenant ID"),
file: UploadFile = File(..., description="File to validate"),
file_format: str = Form(default="csv", description="File format: csv, json, excel"),
validate_only: bool = Form(default=True, description="Only validate, don't import"),
source: str = Form(default="onboarding_upload", description="Source of the upload"),
current_user: Dict[str, Any] = Depends(get_current_user_dep) current_user: Dict[str, Any] = Depends(get_current_user_dep)
): ):
"""Validate import data - Gateway already verified tenant access""" """
✅ FIXED: Validate import data using FormData (same as import endpoint)
Now both validation and import endpoints use the same FormData approach
"""
try: try:
logger.debug("Validating import data", logger.info("Validating import data",
tenant_id=tenant_id,
format=file_format,
filename=file.filename,
user_id=current_user["user_id"])
# ✅ STEP 1: Read file content (same as import endpoint)
content = await file.read()
file_content = content.decode('utf-8')
# ✅ STEP 2: Create validation data structure
# This matches the SalesDataImport schema but gets data from FormData
validation_data = {
"tenant_id": str(tenant_id), # From URL path
"data": file_content, # From uploaded file
"data_format": file_format, # From form field
"source": source, # From form field
"validate_only": validate_only # From form field
}
logger.debug("Validation data prepared",
tenant_id=tenant_id, tenant_id=tenant_id,
user_id=current_user["user_id"]) data_length=len(file_content),
format=file_format)
# Set tenant context from URL path # ✅ STEP 3: Use existing validation service
import_data.tenant_id = tenant_id validation_result = await DataImportService.validate_import_data(validation_data)
validation = await DataImportService.validate_import_data(import_data.model_dump()) logger.info("Validation completed",
is_valid=validation_result.get("is_valid", False),
total_records=validation_result.get("total_records", 0),
tenant_id=tenant_id)
logger.debug("Validation completed", return validation_result
is_valid=validation.get("is_valid", False),
tenant_id=tenant_id)
return validation
except Exception as e: except Exception as e:
logger.error("Failed to validate import data", logger.error("Failed to validate import data",