From a8dbd37c5aef8fee05ede712da2d5b74ae155985 Mon Sep 17 00:00:00 2001 From: Urtzi Alfaro Date: Mon, 4 Aug 2025 08:42:35 +0200 Subject: [PATCH] Fix new Frontend 7 --- frontend/src/api/client/interceptors.ts | 16 +++--- frontend/src/api/types/tenant.ts | 2 + .../src/pages/onboarding/OnboardingPage.tsx | 43 ++++++++++------ services/data/app/api/sales.py | 50 ++++++++++++++----- 4 files changed, 76 insertions(+), 35 deletions(-) diff --git a/frontend/src/api/client/interceptors.ts b/frontend/src/api/client/interceptors.ts index e9a6b234..064a5043 100644 --- a/frontend/src/api/client/interceptors.ts +++ b/frontend/src/api/client/interceptors.ts @@ -12,12 +12,12 @@ import { ApiErrorHandler } from '../utils'; * Authentication Interceptor * Automatically adds authentication headers to requests */ -export class AuthInterceptor { +class AuthInterceptor { static setup() { apiClient.addRequestInterceptor({ onRequest: async (config: RequestConfig) => { const token = localStorage.getItem('auth_token'); - + if (token) { config.headers = { ...config.headers, @@ -58,7 +58,7 @@ export class AuthInterceptor { * Logging Interceptor * Logs API requests and responses for debugging */ -export class LoggingInterceptor { +class LoggingInterceptor { static setup() { apiClient.addRequestInterceptor({ onRequest: async (config: RequestConfig) => { @@ -120,7 +120,7 @@ export class LoggingInterceptor { * Tenant Context Interceptor * Automatically adds tenant context to tenant-scoped requests */ -export class TenantInterceptor { +class TenantInterceptor { private static currentTenantId: string | null = null; static setCurrentTenant(tenantId: string | null) { @@ -160,7 +160,7 @@ export class TenantInterceptor { * Error Recovery Interceptor * Handles automatic token refresh and retry logic */ -export class ErrorRecoveryInterceptor { +class ErrorRecoveryInterceptor { private static isRefreshing = false; private static failedQueue: Array<{ resolve: (token: string) => void; @@ -261,7 +261,7 @@ export class ErrorRecoveryInterceptor { * Performance Monitoring Interceptor * Tracks API performance metrics */ -export class PerformanceInterceptor { +class PerformanceInterceptor { private static metrics: Array<{ url: string; method: string; @@ -351,7 +351,9 @@ export class PerformanceInterceptor { export const setupInterceptors = () => { AuthInterceptor.setup(); - if (process.env.NODE_ENV === 'development') { + const isDevelopment = import.meta.env.DEV; + + if (isDevelopment) { LoggingInterceptor.setup(); PerformanceInterceptor.setup(); } diff --git a/frontend/src/api/types/tenant.ts b/frontend/src/api/types/tenant.ts index afb26ad1..16340ee8 100644 --- a/frontend/src/api/types/tenant.ts +++ b/frontend/src/api/types/tenant.ts @@ -61,6 +61,8 @@ export interface TenantSubscription { export interface TenantCreate { name: string; + postal_code: string; + phone: string; description?: string; settings?: Partial; location?: TenantLocation; diff --git a/frontend/src/pages/onboarding/OnboardingPage.tsx b/frontend/src/pages/onboarding/OnboardingPage.tsx index ca286b39..fb97021a 100644 --- a/frontend/src/pages/onboarding/OnboardingPage.tsx +++ b/frontend/src/pages/onboarding/OnboardingPage.tsx @@ -2,9 +2,13 @@ import React, { useState } from 'react'; import { ChevronLeft, ChevronRight, Upload, MapPin, Store, Factory, Check } from 'lucide-react'; import toast from 'react-hot-toast'; -import { useTenant, useTraining, useData, useAuth } from '../../api/hooks'; -import { TenantCreate } from '../../api/types'; -import { UserResponse } from '../../api/types'; +import { + useTenant, + useTraining, + useData, + useAuth, + TenantCreate +} from '../../api'; interface OnboardingPageProps { user: any; @@ -40,8 +44,7 @@ const OnboardingPage: React.FC = ({ user, onComplete }) => const { createTenant, isLoading: tenantLoading } = useTenant(); const { startTrainingJob } = useTraining(); - const { uploadSalesHistory, validateSalesData, uploadProgress } = useData(); - const [ setUploadProgress ] = useState(0); + const { uploadSalesHistory, validateSalesData } = useData(); const steps = [ { id: 1, title: 'Datos de Panadería', icon: Store }, @@ -99,11 +102,20 @@ const OnboardingPage: React.FC = ({ user, onComplete }) => const tenantData: TenantCreate = { name: bakeryData.name, address: bakeryData.address, - business_type: bakeryData.businessType, + business_type:"bakery", + postal_code: "28010", + phone: "+34655334455", coordinates: bakeryData.coordinates, products: bakeryData.products, 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 tenantId = newTenant.id; @@ -147,10 +159,7 @@ const OnboardingPage: React.FC = ({ user, onComplete }) => const uploadResult = await uploadSalesHistory(tenantId, bakeryData.csvFile, { source: 'onboarding_upload', - validate_only: false, - onProgress: (progress) => { - setUploadProgress(progress.percentage); - } + validate_only: false }); toast.dismiss('csv-upload'); @@ -188,9 +197,6 @@ const OnboardingPage: React.FC = ({ user, onComplete }) => throw new Error(`Upload failed: ${uploadResult.errors?.join(', ') || 'Unknown error'}`); } - // Reset progress when done - setUploadProgress(0); - } catch (uploadError) { // Handle validation or upload error gracefully console.error('CSV validation/upload error:', uploadError); @@ -210,11 +216,16 @@ const OnboardingPage: React.FC = ({ user, onComplete }) => } catch (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 { setIsLoading(false); - setUploadProgress(0); // Reset progress in case of error } }; diff --git a/services/data/app/api/sales.py b/services/data/app/api/sales.py index c57c4c64..99d20354 100644 --- a/services/data/app/api/sales.py +++ b/services/data/app/api/sales.py @@ -247,26 +247,52 @@ async def import_sales_data( @router.post("/tenants/{tenant_id}/sales/import/validate", response_model=SalesValidationResult) async def validate_import_data( - import_data: SalesDataImport, 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) ): - """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: - 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, - user_id=current_user["user_id"]) + data_length=len(file_content), + format=file_format) - # Set tenant context from URL path - import_data.tenant_id = tenant_id + # ✅ STEP 3: Use existing validation service + 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", - is_valid=validation.get("is_valid", False), - tenant_id=tenant_id) - - return validation + return validation_result except Exception as e: logger.error("Failed to validate import data",