Fix new Frontend 7
This commit is contained in:
@@ -12,12 +12,12 @@ 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) => {
|
||||||
const token = localStorage.getItem('auth_token');
|
const token = localStorage.getItem('auth_token');
|
||||||
|
|
||||||
if (token) {
|
if (token) {
|
||||||
config.headers = {
|
config.headers = {
|
||||||
...config.headers,
|
...config.headers,
|
||||||
@@ -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();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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,11 +102,20 @@ 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
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
Reference in New Issue
Block a user