Initial commit from remix

This commit is contained in:
gpt-engineer-app[bot]
2025-09-25 16:01:00 +00:00
commit 5ddc52658d
149 changed files with 32798 additions and 0 deletions

View File

@@ -0,0 +1,332 @@
import React, { createContext, useContext, useState, useEffect } from 'react';
import { apiClient } from '@/services/adminApi';
interface User {
id: string;
email: string;
name: string;
type: 'tourist' | 'business';
role?: 'tourist' | 'taxi' | 'guide' | 'restaurant' | 'hotel' | 'politur' | 'admin' | 'super_admin';
avatar: string;
location: { lat: number; lng: number };
preferences: { language: string };
wallet?: { balance: number; currency: string };
profile?: {
phone: string;
address: string;
joinedDate: string;
permissions: string[];
};
}
interface AuthContextType {
user: User | null;
isLoading: boolean;
login: (email: string, password: string) => Promise<void>;
register: (userData: any) => Promise<void>;
logout: () => void;
isAuthenticated: boolean;
}
const AuthContext = createContext<AuthContextType | undefined>(undefined);
export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
const [user, setUser] = useState<User | null>(null);
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
const init = async () => {
try {
const storedToken = localStorage.getItem('karibeo-token');
const storedUser = localStorage.getItem('karibeo-user');
if (storedUser) {
try {
const parsed = JSON.parse(storedUser);
const roleNum = parsed?.role_id ?? parsed?.roleId ?? parsed?.role?.id;
const roleStr = typeof parsed?.role === 'string' ? parsed.role : undefined;
const ROLE_MAP: Record<number, string> = { 1: 'super_admin', 2: 'admin', 3: 'taxi', 4: 'tourist', 5: 'guide', 6: 'hotel' };
const normalizedRole = (roleStr?.toLowerCase?.()) || (roleNum ? ROLE_MAP[Number(roleNum)] : undefined);
const normalizedUser = normalizedRole ? { ...parsed, role: normalizedRole } : parsed;
setUser(normalizedUser);
if (normalizedRole && normalizedRole !== parsed?.role) {
localStorage.setItem('karibeo-user', JSON.stringify(normalizedUser));
}
} catch {
setUser(JSON.parse(storedUser));
}
} else if (storedToken) {
// Avoid calling /auth/profile which returns 401 in this environment
// We'll rely on stored user until profile endpoint is accessible
console.warn('Token found but no cached user; skipping /auth/profile on init');
}
} catch (e) {
// Don't aggressively clear token; just reset user
setUser(null);
} finally {
setIsLoading(false);
}
};
init();
}, []);
const login = async (email: string, password: string) => {
setIsLoading(true);
try {
console.log('Login attempt with:', { email, password: '***' });
// Mock users for testing
const mockUsers = {
'superadmin@karibeo.com': {
id: '1',
email: 'superadmin@karibeo.com',
name: 'Super Admin',
role: 'super_admin' as const,
type: 'business' as const,
avatar: '/api/placeholder/40/40',
location: { lat: 18.4861, lng: -69.9312 },
preferences: { language: 'es' },
wallet: { balance: 0, currency: 'USD' },
profile: {
phone: '+1-809-555-0001',
address: 'Santo Domingo, República Dominicana',
joinedDate: '2023-01-01',
permissions: ['all']
}
},
'admin@karibeo.com': {
id: '2',
email: 'admin@karibeo.com',
name: 'Admin User',
role: 'admin' as const,
type: 'business' as const,
avatar: '/api/placeholder/40/40',
location: { lat: 18.4861, lng: -69.9312 },
preferences: { language: 'es' },
wallet: { balance: 0, currency: 'USD' },
profile: {
phone: '+1-809-555-0002',
address: 'Santiago, República Dominicana',
joinedDate: '2023-02-01',
permissions: ['user_management', 'content_management']
}
},
'user@karibeo.com': {
id: '3',
email: 'user@karibeo.com',
name: 'Regular User',
role: 'tourist' as const,
type: 'tourist' as const,
avatar: '/api/placeholder/40/40',
location: { lat: 18.4861, lng: -69.9312 },
preferences: { language: 'es' },
wallet: { balance: 150.50, currency: 'USD' },
profile: {
phone: '+1-809-555-0003',
address: 'Punta Cana, República Dominicana',
joinedDate: '2023-03-01',
permissions: ['booking', 'reviews']
}
}
};
// Check if it's a mock user
const mockUser = mockUsers[email as keyof typeof mockUsers];
if (mockUser && password === '123456') {
console.log('🎯 Mock login successful for:', email, 'with role:', mockUser.role);
const token = `mock-token-${Date.now()}`;
localStorage.setItem('karibeo-token', token);
localStorage.setItem('karibeo_token', token);
localStorage.setItem('karibeo-user', JSON.stringify(mockUser));
setUser(mockUser);
setIsLoading(false);
return;
}
const loginData = { email: email.trim(), password: password.trim() };
console.log('Sending login data (form first):', { email: loginData.email, password: '***' });
let loginRes: any;
// Try application/x-www-form-urlencoded first (some backends validate this path)
try {
loginRes = await apiClient.postForm('/auth/login', {
email: loginData.email,
password: loginData.password,
});
} catch (firstErr: any) {
console.warn('Form login failed, retrying as JSON:', firstErr?.message);
loginRes = await apiClient.post('/auth/login', loginData);
}
console.log('Login response:', loginRes);
const token = loginRes?.token || loginRes?.accessToken || loginRes?.access_token;
const refresh = loginRes?.refreshToken || loginRes?.refresh_token;
if (!token) {
throw new Error('Credenciales inválidas');
}
localStorage.setItem('karibeo-token', token);
localStorage.setItem('karibeo_token', token);
if (refresh) {
localStorage.setItem('karibeo-refresh', refresh);
localStorage.setItem('karibeo_refresh', refresh);
}
let userData = (loginRes && (loginRes.user || loginRes.profile || null)) as any;
// Try to enrich with /auth/profile (post-login, should be authorized)
try {
const profile = await apiClient.get('/auth/profile') as any;
const profileUser = (profile && (profile.user || profile)) as any;
if (profileUser) {
userData = { ...(userData || {}), ...profileUser };
}
} catch (e: any) {
console.warn('Skipping /auth/profile after login:', e?.message);
}
if (userData) {
// Enhanced role detection and normalization
let normalizedRole = '';
// Try role.name first (most reliable)
if (userData?.role?.name) {
const roleRaw = userData.role.name.toString().trim();
const roleName = roleRaw.toLowerCase();
console.log('🔍 Raw role.name from API:', userData.role.name, '-> normalized?', roleName);
if (roleName.includes('super') && roleName.includes('admin')) {
normalizedRole = 'super_admin';
} else if (roleName.includes('admin')) {
normalizedRole = 'admin';
} else if (roleName.includes('taxi')) {
normalizedRole = 'taxi';
} else if (roleName.includes('guide')) {
normalizedRole = 'guide';
} else if (roleName.includes('hotel')) {
normalizedRole = 'hotel';
} else if (roleName.includes('tourist')) {
normalizedRole = 'tourist';
}
}
// Fallback to roleId mapping
if (!normalizedRole && userData?.roleId) {
const ROLE_MAP: Record<number, string> = { 1: 'super_admin', 2: 'admin', 3: 'taxi', 4: 'tourist', 5: 'guide', 6: 'hotel', 7: 'restaurant', 8: 'business', 9: 'staff' };
normalizedRole = ROLE_MAP[Number(userData.roleId)] || '';
console.log('🔍 Using roleId fallback:', userData.roleId, '-> mapped to:', normalizedRole);
}
// Fallback to JWT token role
if (!normalizedRole) {
try {
const payload = JSON.parse(atob(String(token).split('.')[1]));
const tokenRole = payload?.role;
if (tokenRole) {
const tokenRoleNorm = tokenRole.toString().trim().toLowerCase();
console.log('🔍 JWT token role:', tokenRole, '-> normalized:', tokenRoleNorm);
if (tokenRoleNorm.includes('super') && tokenRoleNorm.includes('admin')) {
normalizedRole = 'super_admin';
} else if (tokenRoleNorm.includes('admin')) {
normalizedRole = 'admin';
}
}
} catch (e) {
console.warn('Failed to parse JWT token:', e);
}
}
const finalUser = { ...userData, role: normalizedRole };
console.log('🎯 Final user with normalized role:', { email: finalUser.email, role: finalUser.role, roleId: finalUser.roleId });
setUser(finalUser);
localStorage.setItem('karibeo-user', JSON.stringify(finalUser));
} else {
// If backend doesn't return user, skip extra call and set minimal user from token
setUser({
id: '',
email,
name: email.split('@')[0],
type: 'tourist',
avatar: '',
location: { lat: 0, lng: 0 },
preferences: { language: 'es' },
} as any);
}
} catch (error: any) {
console.error('Login error:', error);
localStorage.removeItem('karibeo-token');
localStorage.removeItem('karibeo-user');
setUser(null);
throw new Error(error?.message || 'No se pudo iniciar sesión');
} finally {
setIsLoading(false);
}
};
const register = async (userData: any) => {
setIsLoading(true);
try {
const res = await apiClient.post('/auth/register', userData) as any;
const token = res?.token || res?.accessToken || res?.access_token;
const refresh = res?.refreshToken || res?.refresh_token;
if (token) {
localStorage.setItem('karibeo-token', token);
localStorage.setItem('karibeo_token', token);
}
if (refresh) {
localStorage.setItem('karibeo-refresh', refresh);
localStorage.setItem('karibeo_refresh', refresh);
}
const profile = await apiClient.get('/auth/profile') as any;
const newUser = (profile && (profile.user || profile)) as any;
setUser(newUser);
localStorage.setItem('karibeo-user', JSON.stringify(newUser));
} catch (error: any) {
localStorage.removeItem('karibeo-token');
localStorage.removeItem('karibeo-user');
setUser(null);
throw new Error(error?.message || 'No se pudo registrar');
} finally {
setIsLoading(false);
}
};
const logout = () => {
setUser(null);
localStorage.removeItem('karibeo-user');
localStorage.removeItem('karibeo-token');
localStorage.removeItem('karibeo_token');
localStorage.removeItem('karibeo-refresh');
localStorage.removeItem('karibeo_refresh');
};
return (
<AuthContext.Provider value={{
user,
isLoading,
login,
register,
logout,
isAuthenticated: !!user
}}>
{children}
</AuthContext.Provider>
);
};
export const useAuth = () => {
const context = useContext(AuthContext);
if (context === undefined) {
// Soft fallback to avoid app crash; helps while provider is being hot-reloaded
console.warn('useAuth used outside AuthProvider. Using safe fallback.');
return {
user: null,
isLoading: false,
login: async () => { throw new Error('AuthProvider no inicializado'); },
register: async () => { throw new Error('AuthProvider no inicializado'); },
logout: () => {},
isAuthenticated: false,
} as AuthContextType;
}
return context;
};

View File

@@ -0,0 +1,111 @@
import React, { createContext, useContext, useState, useEffect } from 'react';
interface CartItem {
id: string;
title: string;
price: number;
image: string;
category: string;
location: string;
quantity: number;
selectedDate?: string;
guests?: number;
}
interface CartContextType {
items: CartItem[];
addToCart: (item: Omit<CartItem, 'quantity'>, quantity?: number) => void;
removeFromCart: (id: string) => void;
updateQuantity: (id: string, quantity: number) => void;
clearCart: () => void;
getTotalItems: () => number;
getTotalPrice: () => number;
}
const CartContext = createContext<CartContextType | undefined>(undefined);
export const CartProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
const [items, setItems] = useState<CartItem[]>([]);
// Load cart from localStorage on component mount
useEffect(() => {
const savedCart = localStorage.getItem('karibeo_cart');
if (savedCart) {
try {
setItems(JSON.parse(savedCart));
} catch (error) {
console.error('Error loading cart from localStorage:', error);
}
}
}, []);
// Save cart to localStorage whenever items change
useEffect(() => {
localStorage.setItem('karibeo_cart', JSON.stringify(items));
}, [items]);
const addToCart = (item: Omit<CartItem, 'quantity'>, quantity = 1) => {
setItems(prevItems => {
const existingItem = prevItems.find(cartItem => cartItem.id === item.id);
if (existingItem) {
return prevItems.map(cartItem =>
cartItem.id === item.id
? { ...cartItem, quantity: cartItem.quantity + quantity }
: cartItem
);
}
return [...prevItems, { ...item, quantity }];
});
};
const removeFromCart = (id: string) => {
setItems(prevItems => prevItems.filter(item => item.id !== id));
};
const updateQuantity = (id: string, quantity: number) => {
if (quantity <= 0) {
removeFromCart(id);
return;
}
setItems(prevItems =>
prevItems.map(item =>
item.id === id ? { ...item, quantity } : item
)
);
};
const clearCart = () => {
setItems([]);
localStorage.removeItem('karibeo_cart');
};
const getTotalItems = () => {
return items.reduce((total, item) => total + item.quantity, 0);
};
const getTotalPrice = () => {
return items.reduce((total, item) => total + (item.price * item.quantity), 0);
};
return (
<CartContext.Provider value={{
items,
addToCart,
removeFromCart,
updateQuantity,
clearCart,
getTotalItems,
getTotalPrice
}}>
{children}
</CartContext.Provider>
);
};
export const useCart = () => {
const context = useContext(CartContext);
if (context === undefined) {
throw new Error('useCart must be used within a CartProvider');
}
return context;
};

View File

@@ -0,0 +1,48 @@
import React, { createContext, useContext, useState, useEffect } from 'react';
import { translations } from '@/i18n/translations';
type Language = 'es' | 'en' | 'fr';
type TranslationKey = keyof typeof translations.es;
interface LanguageContextType {
language: Language;
setLanguage: (lang: Language) => void;
t: (key: TranslationKey) => string;
}
const LanguageContext = createContext<LanguageContextType | undefined>(undefined);
export const LanguageProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
const [language, setLanguageState] = useState<Language>(() => {
const saved = localStorage.getItem('karibeo-language');
return (saved as Language) || 'es';
});
const setLanguage = (lang: Language) => {
setLanguageState(lang);
localStorage.setItem('karibeo-language', lang);
};
const t = (key: TranslationKey): string => {
return translations[language][key] || translations.es[key] || key;
};
useEffect(() => {
// Update document language
document.documentElement.lang = language;
}, [language]);
return (
<LanguageContext.Provider value={{ language, setLanguage, t }}>
{children}
</LanguageContext.Provider>
);
};
export const useLanguage = () => {
const context = useContext(LanguageContext);
if (context === undefined) {
throw new Error('useLanguage must be used within a LanguageProvider');
}
return context;
};