Horus is a website from osiris backend app.
README
Frontend e-commerce construido en React + TypeScript para el proyecto Osiris. Consume una REST API Spring Boot 3 con autenticación JWT.
| Tecnología | Uso | |---|---| | React 19 + TypeScript | UI y tipado | | Vite | Bundler y dev server | | React Router v6 | Navegación y guards de rutas | | TanStack Query | Fetching, caché e invalidación | | Axios | Cliente HTTP con interceptor JWT | | Zustand | Estado global (auth + carrito) | | Tailwind CSS v4 | Estilos con diseño glassmorphism |
npm install
Copiá el archivo de ejemplo y configurá la URL del backend:
cp .env.example .env
VITE_API_URL=http://localhost:8080
Cambiá el valor por la IP y puerto donde corre tu backend. Reiniciá el dev server después de cualquier cambio en este archivo.
npm run dev
La app queda disponible en http://localhost:5173.
npm run build
| Ruta | Descripción |
|---|---|
| / | Catálogo de productos con búsqueda y filtro por categoría |
| /products/:id | Detalle del producto |
| /login | Iniciar sesión |
| /register | Crear cuenta |
| /forgot-password | Solicitar reset de contraseña |
| /reset-password?token=... | Nueva contraseña con token del email |
| /verify-email?token=... | Confirmación de email |
| Ruta | Descripción |
|---|---|
| /cart | Carrito con edición de cantidades y checkout |
| /orders | Historial de órdenes |
| /orders/:id | Detalle de orden (cancelable si está en PENDING) |
| /profile | Editar nombre y contraseña |
role: ADMIN)| Ruta | Descripción |
|---|---|
| /admin/products | CRUD de productos con subida de imágenes |
| /admin/categories | CRUD de categorías |
| /admin/users | Listado de usuarios y cambio de rol |
| /admin/orders | Todas las órdenes con cambio de estado |
src/
├── api/ # Módulos por dominio (auth, products, cart, orders, admin)
│ └── client.ts # Instancia Axios con interceptor de refresh token
├── components/
│ ├── guards/ # AuthGuard, AdminGuard, GuestGuard
│ ├── AdminLayout.tsx
│ ├── Layout.tsx
│ └── Navbar.tsx
├── hooks/
│ └── useBackendStatus.ts # Indicador de conexión al backend
├── lib/
│ ├── error.ts # Extrae mensajes de error de respuestas API
│ └── format.ts # Formateo de precios, fechas y estados de orden
├── pages/
│ ├── admin/
│ └── ...
├── router/ # Definición de rutas con guards aplicados
├── store/
│ ├── authStore.ts # Zustand: sesión de usuario
│ └── cartStore.ts # Zustand: carrito con contador
└── types/ # Tipos TypeScript de todos los contratos del API
accessToken y refreshToken en localStorage.accessToken en cada request.401, el interceptor intenta renovar el token con /auth/refresh. Si la renovación falla, limpia los tokens y redirige a /login./auth/* están excluidas del interceptor para que los errores de credenciales lleguen correctamente a la UI./login si no hay sesión activa./ si el usuario no tiene role: ADMIN./ si ya hay sesión activa (evita que un usuario logueado vea /login)./login.imageUrl.Para que todas las funcionalidades del frontend operen correctamente, el backend necesita los siguientes ajustes:
El preflight OPTIONS devuelve 403 si CORS no está configurado en la cadena de seguridad. Agregá esto en tu SecurityFilterChain:
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.cors(cors -> cors.configurationSource(corsConfigurationSource()))
// ... resto de la configuración
return http.build();
}
@Bean
CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration config = new CorsConfiguration();
config.setAllowedOrigins(List.of(
"http://localhost:5173",
"http://localhost:3000"
// Agregá la IP/origen desde donde servis el frontend
));
config.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"));
config.setAllowedHeaders(List.of("*"));
config.setAllowCredentials(true);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", config);
return source;
}
El catálogo se puede navegar sin login, por lo que estos endpoints deben ser accesibles sin token:
.requestMatchers(HttpMethod.GET, "/products", "/products/**", "/categories", "/categories/**").permitAll()
imageUrl como TEXTLas imágenes se guardan como base64, lo que puede superar los 255 caracteres de un VARCHAR. Cambiá el tipo de columna:
ALTER TABLE products MODIFY COLUMN image_url TEXT;
Ejecutá este SQL directamente en la base de datos:
UPDATE users SET role = 'ADMIN' WHERE email = 'tu@email.com';
Luego el usuario debe hacer logout y login para que el frontend tome el rol actualizado.
Con Docker Compose:
docker exec -it <nombre-contenedor-mysql> mysql -u root -p
USE <nombre-base-de-datos>;
UPDATE users SET role = 'ADMIN' WHERE email = 'tu@email.com';