Fix new Frontend 4
This commit is contained in:
15
frontend/.env.development
Normal file
15
frontend/.env.development
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
# API Configuration
|
||||||
|
VITE_API_URL=http://localhost:8000/api/v1
|
||||||
|
VITE_API_TIMEOUT=30000
|
||||||
|
VITE_API_RETRIES=3
|
||||||
|
VITE_API_RETRY_DELAY=1000
|
||||||
|
VITE_API_LOGGING=true
|
||||||
|
VITE_API_CACHING=true
|
||||||
|
VITE_API_CACHE_TIMEOUT=300000
|
||||||
|
|
||||||
|
# Feature Flags
|
||||||
|
VITE_ENABLE_WEBSOCKETS=false
|
||||||
|
VITE_ENABLE_OFFLINE=false
|
||||||
|
VITE_ENABLE_OPTIMISTIC_UPDATES=true
|
||||||
|
VITE_ENABLE_DEDUPLICATION=true
|
||||||
|
VITE_ENABLE_METRICS=false
|
||||||
@@ -25,17 +25,18 @@ export interface ServiceEndpoints {
|
|||||||
|
|
||||||
// Environment-based configuration
|
// Environment-based configuration
|
||||||
const getEnvironmentConfig = (): ApiConfig => {
|
const getEnvironmentConfig = (): ApiConfig => {
|
||||||
const isProduction = process.env.NODE_ENV === 'production';
|
// Use import.meta.env instead of process.env for Vite
|
||||||
const isDevelopment = process.env.NODE_ENV === 'development';
|
const isDevelopment = import.meta.env.DEV;
|
||||||
|
const isProduction = import.meta.env.PROD;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
baseURL: process.env.NEXT_PUBLIC_API_URL || 'http://localhost:8000/api/v1',
|
baseURL: import.meta.env.VITE_API_URL || 'http://localhost:8000/api/v1',
|
||||||
timeout: parseInt(process.env.NEXT_PUBLIC_API_TIMEOUT || '30000'),
|
timeout: parseInt(import.meta.env.VITE_API_TIMEOUT || '30000'),
|
||||||
retries: parseInt(process.env.NEXT_PUBLIC_API_RETRIES || '3'),
|
retries: parseInt(import.meta.env.VITE_API_RETRIES || '3'),
|
||||||
retryDelay: parseInt(process.env.NEXT_PUBLIC_API_RETRY_DELAY || '1000'),
|
retryDelay: parseInt(import.meta.env.VITE_API_RETRY_DELAY || '1000'),
|
||||||
enableLogging: isDevelopment || process.env.NEXT_PUBLIC_API_LOGGING === 'true',
|
enableLogging: isDevelopment || import.meta.env.VITE_API_LOGGING === 'true',
|
||||||
enableCaching: process.env.NEXT_PUBLIC_API_CACHING !== 'false',
|
enableCaching: import.meta.env.VITE_API_CACHING !== 'false',
|
||||||
cacheTimeout: parseInt(process.env.NEXT_PUBLIC_API_CACHE_TIMEOUT || '300000'), // 5 minutes
|
cacheTimeout: parseInt(import.meta.env.VITE_API_CACHE_TIMEOUT || '300000'), // 5 minutes
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -124,7 +125,6 @@ export const ApiVersion = {
|
|||||||
CURRENT: 'v1',
|
CURRENT: 'v1',
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
// Feature flags for API behavior
|
|
||||||
export interface FeatureFlags {
|
export interface FeatureFlags {
|
||||||
enableWebSockets: boolean;
|
enableWebSockets: boolean;
|
||||||
enableOfflineMode: boolean;
|
enableOfflineMode: boolean;
|
||||||
@@ -134,9 +134,9 @@ export interface FeatureFlags {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const featureFlags: FeatureFlags = {
|
export const featureFlags: FeatureFlags = {
|
||||||
enableWebSockets: process.env.NEXT_PUBLIC_ENABLE_WEBSOCKETS === 'true',
|
enableWebSockets: import.meta.env.VITE_ENABLE_WEBSOCKETS === 'true',
|
||||||
enableOfflineMode: process.env.NEXT_PUBLIC_ENABLE_OFFLINE === 'true',
|
enableOfflineMode: import.meta.env.VITE_ENABLE_OFFLINE === 'true',
|
||||||
enableOptimisticUpdates: process.env.NEXT_PUBLIC_ENABLE_OPTIMISTIC_UPDATES !== 'false',
|
enableOptimisticUpdates: import.meta.env.VITE_ENABLE_OPTIMISTIC_UPDATES !== 'false',
|
||||||
enableRequestDeduplication: process.env.NEXT_PUBLIC_ENABLE_DEDUPLICATION !== 'false',
|
enableRequestDeduplication: import.meta.env.VITE_ENABLE_DEDUPLICATION !== 'false',
|
||||||
enableMetrics: process.env.NEXT_PUBLIC_ENABLE_METRICS === 'true',
|
enableMetrics: import.meta.env.VITE_ENABLE_METRICS === 'true',
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -4,15 +4,10 @@
|
|||||||
* Central entry point for all API functionality
|
* Central entry point for all API functionality
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// Setup interceptors on import
|
// Export main API client first
|
||||||
import { setupInterceptors } from './client/interceptors';
|
|
||||||
setupInterceptors();
|
|
||||||
|
|
||||||
// Export main API client and services
|
|
||||||
export { apiClient } from './client';
|
export { apiClient } from './client';
|
||||||
export { api } from './services';
|
|
||||||
|
|
||||||
// Export all services individually
|
// Export all services
|
||||||
export {
|
export {
|
||||||
authService,
|
authService,
|
||||||
tenantService,
|
tenantService,
|
||||||
@@ -20,7 +15,7 @@ export {
|
|||||||
trainingService,
|
trainingService,
|
||||||
forecastingService,
|
forecastingService,
|
||||||
notificationService,
|
notificationService,
|
||||||
healthService,
|
api
|
||||||
} from './services';
|
} from './services';
|
||||||
|
|
||||||
// Export all hooks
|
// Export all hooks
|
||||||
@@ -35,37 +30,12 @@ export {
|
|||||||
useApiHooks,
|
useApiHooks,
|
||||||
} from './hooks';
|
} from './hooks';
|
||||||
|
|
||||||
// Export WebSocket functionality
|
|
||||||
export {
|
|
||||||
WebSocketManager,
|
|
||||||
useWebSocket,
|
|
||||||
useTrainingWebSocket,
|
|
||||||
useForecastWebSocket,
|
|
||||||
} from './websocket';
|
|
||||||
|
|
||||||
// Export utilities
|
|
||||||
export {
|
|
||||||
ApiErrorHandler,
|
|
||||||
ResponseProcessor,
|
|
||||||
RequestValidator,
|
|
||||||
DataTransformer,
|
|
||||||
} from './utils';
|
|
||||||
|
|
||||||
// Export types
|
// Export types
|
||||||
export * from './types';
|
export * from './types';
|
||||||
|
|
||||||
// Export interceptors for manual control
|
|
||||||
export {
|
|
||||||
AuthInterceptor,
|
|
||||||
LoggingInterceptor,
|
|
||||||
TenantInterceptor,
|
|
||||||
ErrorRecoveryInterceptor,
|
|
||||||
PerformanceInterceptor,
|
|
||||||
setupInterceptors,
|
|
||||||
} from './client/interceptors';
|
|
||||||
|
|
||||||
// Export configuration
|
// Export configuration
|
||||||
export { apiConfig, serviceEndpoints, featureFlags } from './client/config';
|
export { apiConfig, serviceEndpoints, featureFlags } from './client/config';
|
||||||
|
|
||||||
// Default export for convenience
|
// Setup interceptors on import (move to end to avoid circular deps)
|
||||||
export default api;
|
import { setupInterceptors } from './client/interceptors';
|
||||||
|
setupInterceptors();
|
||||||
@@ -4,13 +4,24 @@
|
|||||||
* Central export point for all API services
|
* Central export point for all API services
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// Import all services
|
// Import and export individual services
|
||||||
export { AuthService, authService } from './auth.service';
|
import { AuthService } from './auth.service';
|
||||||
export { TenantService, tenantService } from './tenant.service';
|
import { TenantService } from './tenant.service';
|
||||||
export { DataService, dataService } from './data.service';
|
import { DataService } from './data.service';
|
||||||
export { TrainingService, trainingService } from './training.service';
|
import { TrainingService } from './training.service';
|
||||||
export { ForecastingService, forecastingService } from './forecasting.service';
|
import { ForecastingService } from './forecasting.service';
|
||||||
export { NotificationService, notificationService } from './notification.service';
|
import { NotificationService } from './notification.service';
|
||||||
|
|
||||||
|
// Create service instances
|
||||||
|
export const authService = new AuthService();
|
||||||
|
export const tenantService = new TenantService();
|
||||||
|
export const dataService = new DataService();
|
||||||
|
export const trainingService = new TrainingService();
|
||||||
|
export const forecastingService = new ForecastingService();
|
||||||
|
export const notificationService = new NotificationService();
|
||||||
|
|
||||||
|
// Export the classes as well
|
||||||
|
export { AuthService, TenantService, DataService, TrainingService, ForecastingService, NotificationService };
|
||||||
|
|
||||||
// Import base client
|
// Import base client
|
||||||
export { apiClient } from '../client';
|
export { apiClient } from '../client';
|
||||||
@@ -26,7 +37,6 @@ export const api = {
|
|||||||
training: trainingService,
|
training: trainingService,
|
||||||
forecasting: forecastingService,
|
forecasting: forecastingService,
|
||||||
notification: notificationService,
|
notification: notificationService,
|
||||||
client: apiClient,
|
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
// Service status checking
|
// Service status checking
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
import { Eye, EyeOff, Loader2, Check } from 'lucide-react';
|
import { Eye, EyeOff, Loader2, Check } from 'lucide-react';
|
||||||
import toast from 'react-hot-toast';
|
import toast from 'react-hot-toast';
|
||||||
|
|
||||||
|
import { useAuth } from '../../api/hooks/useAuth';
|
||||||
|
import type { RegisterRequest } from '../../api/types';
|
||||||
|
|
||||||
interface RegisterPageProps {
|
interface RegisterPageProps {
|
||||||
onLogin: (user: any, token: string) => void;
|
onLogin: (user: any, token: string) => void;
|
||||||
onNavigateToLogin: () => void;
|
onNavigateToLogin: () => void;
|
||||||
@@ -15,6 +19,8 @@ interface RegisterForm {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const RegisterPage: React.FC<RegisterPageProps> = ({ onLogin, onNavigateToLogin }) => {
|
const RegisterPage: React.FC<RegisterPageProps> = ({ onLogin, onNavigateToLogin }) => {
|
||||||
|
const { register, isLoading, error } = useAuth();
|
||||||
|
|
||||||
const [formData, setFormData] = useState<RegisterForm>({
|
const [formData, setFormData] = useState<RegisterForm>({
|
||||||
fullName: '',
|
fullName: '',
|
||||||
email: '',
|
email: '',
|
||||||
@@ -25,7 +31,6 @@ const RegisterPage: React.FC<RegisterPageProps> = ({ onLogin, onNavigateToLogin
|
|||||||
|
|
||||||
const [showPassword, setShowPassword] = useState(false);
|
const [showPassword, setShowPassword] = useState(false);
|
||||||
const [showConfirmPassword, setShowConfirmPassword] = useState(false);
|
const [showConfirmPassword, setShowConfirmPassword] = useState(false);
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
|
||||||
const [errors, setErrors] = useState<Partial<RegisterForm>>({});
|
const [errors, setErrors] = useState<Partial<RegisterForm>>({});
|
||||||
|
|
||||||
const validateForm = (): boolean => {
|
const validateForm = (): boolean => {
|
||||||
@@ -47,13 +52,9 @@ const RegisterPage: React.FC<RegisterPageProps> = ({ onLogin, onNavigateToLogin
|
|||||||
newErrors.password = 'La contraseña es obligatoria';
|
newErrors.password = 'La contraseña es obligatoria';
|
||||||
} else if (formData.password.length < 8) {
|
} else if (formData.password.length < 8) {
|
||||||
newErrors.password = 'La contraseña debe tener al menos 8 caracteres';
|
newErrors.password = 'La contraseña debe tener al menos 8 caracteres';
|
||||||
} else if (!/(?=.*[a-z])(?=.*[A-Z])(?=.*\d)/.test(formData.password)) {
|
|
||||||
newErrors.password = 'La contraseña debe incluir mayúsculas, minúsculas y números';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!formData.confirmPassword) {
|
if (formData.password !== formData.confirmPassword) {
|
||||||
newErrors.confirmPassword = 'Confirma tu contraseña';
|
|
||||||
} else if (formData.password !== formData.confirmPassword) {
|
|
||||||
newErrors.confirmPassword = 'Las contraseñas no coinciden';
|
newErrors.confirmPassword = 'Las contraseñas no coinciden';
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -70,54 +71,30 @@ const RegisterPage: React.FC<RegisterPageProps> = ({ onLogin, onNavigateToLogin
|
|||||||
|
|
||||||
if (!validateForm()) return;
|
if (!validateForm()) return;
|
||||||
|
|
||||||
setIsLoading(true);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch('/api/v1/auth/register', {
|
const registerData: RegisterRequest = {
|
||||||
method: 'POST',
|
email: formData.email,
|
||||||
headers: {
|
password: formData.password,
|
||||||
'Content-Type': 'application/json',
|
full_name: formData.fullName,
|
||||||
},
|
role: 'user' // Default role
|
||||||
body: JSON.stringify({
|
};
|
||||||
full_name: formData.fullName,
|
|
||||||
email: formData.email,
|
|
||||||
password: formData.password,
|
|
||||||
role: 'admin' // Default role for bakery owners
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
|
|
||||||
const data = await response.json();
|
await register(registerData);
|
||||||
|
|
||||||
if (!response.ok) {
|
toast.success('¡Registro exitoso! Bienvenido a PanIA');
|
||||||
throw new Error(data.message || 'Error al crear la cuenta');
|
|
||||||
|
// The useAuth hook handles auto-login after registration
|
||||||
|
// Get the user data from localStorage since useAuth auto-logs in
|
||||||
|
const userData = localStorage.getItem('user_data');
|
||||||
|
const token = localStorage.getItem('auth_token');
|
||||||
|
|
||||||
|
if (userData && token) {
|
||||||
|
onLogin(JSON.parse(userData), token);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Auto-login after successful registration
|
} catch (error) {
|
||||||
const loginResponse = await fetch('/api/v1/auth/login', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
email: formData.email,
|
|
||||||
password: formData.password,
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
|
|
||||||
const loginData = await loginResponse.json();
|
|
||||||
|
|
||||||
if (!loginResponse.ok) {
|
|
||||||
throw new Error('Cuenta creada, pero error al iniciar sesión');
|
|
||||||
}
|
|
||||||
|
|
||||||
toast.success('¡Cuenta creada exitosamente! Bienvenido a PanIA');
|
|
||||||
onLogin(loginData.user, loginData.access_token);
|
|
||||||
|
|
||||||
} catch (error: any) {
|
|
||||||
console.error('Registration error:', error);
|
console.error('Registration error:', error);
|
||||||
toast.error(error.message || 'Error al crear la cuenta');
|
toast.error(error instanceof Error ? error.message : 'Error en el registro');
|
||||||
} finally {
|
|
||||||
setIsLoading(false);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -372,7 +349,7 @@ const RegisterPage: React.FC<RegisterPageProps> = ({ onLogin, onNavigateToLogin
|
|||||||
<p className="mt-1 text-sm text-red-600">{errors.acceptTerms}</p>
|
<p className="mt-1 text-sm text-red-600">{errors.acceptTerms}</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Submit Button */}
|
{/* Submit Button */}
|
||||||
<div>
|
<div>
|
||||||
<button
|
<button
|
||||||
|
|||||||
20
frontend/vite-env.d.ts
vendored
Normal file
20
frontend/vite-env.d.ts
vendored
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
/// <reference types="vite/client" />
|
||||||
|
|
||||||
|
interface ImportMetaEnv {
|
||||||
|
readonly VITE_API_URL: string
|
||||||
|
readonly VITE_API_TIMEOUT: string
|
||||||
|
readonly VITE_API_RETRIES: string
|
||||||
|
readonly VITE_API_RETRY_DELAY: string
|
||||||
|
readonly VITE_API_LOGGING: string
|
||||||
|
readonly VITE_API_CACHING: string
|
||||||
|
readonly VITE_API_CACHE_TIMEOUT: string
|
||||||
|
readonly VITE_ENABLE_WEBSOCKETS: string
|
||||||
|
readonly VITE_ENABLE_OFFLINE: string
|
||||||
|
readonly VITE_ENABLE_OPTIMISTIC_UPDATES: string
|
||||||
|
readonly VITE_ENABLE_DEDUPLICATION: string
|
||||||
|
readonly VITE_ENABLE_METRICS: string
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ImportMeta {
|
||||||
|
readonly env: ImportMetaEnv
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user