From 67db9ff153d651a8861f417861e4c866ee7df752 Mon Sep 17 00:00:00 2001 From: Flatlogic Bot Date: Sat, 28 Feb 2026 19:54:09 +0000 Subject: [PATCH] Auto commit: 2026-02-28T19:54:09.195Z --- backend/src/routes/order_items.js | 6 +- backend/src/routes/orders.js | 6 +- backend/src/services/order_items.js | 7 +- backend/src/services/orders.js | 9 +- frontend/src/components/CartDrawer.tsx | 217 ++++++----- .../src/components/ElCalentico/Footer.tsx | 46 +++ .../src/components/ElCalentico/Header.tsx | 67 ++++ frontend/src/components/NavBarItem.tsx | 5 +- frontend/src/layouts/Authenticated.tsx | 5 +- frontend/src/menuAside.ts | 10 +- frontend/src/pages/checkout.tsx | 272 +++++++++++++ frontend/src/pages/dashboard.tsx | 16 +- frontend/src/pages/index.tsx | 365 +++++++++--------- frontend/src/pages/orders/dashboard.tsx | 164 ++++++++ frontend/src/stores/localCartSlice.ts | 13 +- 15 files changed, 904 insertions(+), 304 deletions(-) create mode 100644 frontend/src/components/ElCalentico/Footer.tsx create mode 100644 frontend/src/components/ElCalentico/Header.tsx create mode 100644 frontend/src/pages/checkout.tsx create mode 100644 frontend/src/pages/orders/dashboard.tsx diff --git a/backend/src/routes/order_items.js b/backend/src/routes/order_items.js index 8cd0b28..f5488ea 100644 --- a/backend/src/routes/order_items.js +++ b/backend/src/routes/order_items.js @@ -1,4 +1,3 @@ - const express = require('express'); const Order_itemsService = require('../services/order_items'); @@ -89,8 +88,7 @@ router.use(checkCrudPermissions('order_items')); router.post('/', wrapAsync(async (req, res) => { const referer = req.headers.referer || `${req.protocol}://${req.hostname}${req.originalUrl}`; const link = new URL(referer); - await Order_itemsService.create(req.body.data, req.currentUser, true, link.host); - const payload = true; + const payload = await Order_itemsService.create(req.body.data, req.currentUser, true, link.host); res.status(200).send(payload); })); @@ -438,4 +436,4 @@ router.get('/:id', wrapAsync(async (req, res) => { router.use('/', require('../helpers').commonErrorHandler); -module.exports = router; +module.exports = router; \ No newline at end of file diff --git a/backend/src/routes/orders.js b/backend/src/routes/orders.js index 372313b..7aec3f8 100644 --- a/backend/src/routes/orders.js +++ b/backend/src/routes/orders.js @@ -1,4 +1,3 @@ - const express = require('express'); const OrdersService = require('../services/orders'); @@ -97,8 +96,7 @@ router.use(checkCrudPermissions('orders')); router.post('/', wrapAsync(async (req, res) => { const referer = req.headers.referer || `${req.protocol}://${req.hostname}${req.originalUrl}`; const link = new URL(referer); - await OrdersService.create(req.body.data, req.currentUser, true, link.host); - const payload = true; + const payload = await OrdersService.create(req.body.data, req.currentUser, true, link.host); res.status(200).send(payload); })); @@ -446,4 +444,4 @@ router.get('/:id', wrapAsync(async (req, res) => { router.use('/', require('../helpers').commonErrorHandler); -module.exports = router; +module.exports = router; \ No newline at end of file diff --git a/backend/src/services/order_items.js b/backend/src/services/order_items.js index 9532731..f1310c0 100644 --- a/backend/src/services/order_items.js +++ b/backend/src/services/order_items.js @@ -15,7 +15,7 @@ module.exports = class Order_itemsService { static async create(data, currentUser) { const transaction = await db.sequelize.transaction(); try { - await Order_itemsDBApi.create( + const order_items = await Order_itemsDBApi.create( data, { currentUser, @@ -24,6 +24,7 @@ module.exports = class Order_itemsService { ); await transaction.commit(); + return order_items; } catch (error) { await transaction.rollback(); throw error; @@ -133,6 +134,4 @@ module.exports = class Order_itemsService { } -}; - - +}; \ No newline at end of file diff --git a/backend/src/services/orders.js b/backend/src/services/orders.js index 2cac58d..cf75ea4 100644 --- a/backend/src/services/orders.js +++ b/backend/src/services/orders.js @@ -15,7 +15,7 @@ module.exports = class OrdersService { static async create(data, currentUser) { const transaction = await db.sequelize.transaction(); try { - await OrdersDBApi.create( + const orders = await OrdersDBApi.create( data, { currentUser, @@ -24,6 +24,7 @@ module.exports = class OrdersService { ); await transaction.commit(); + return orders; } catch (error) { await transaction.rollback(); throw error; @@ -121,7 +122,7 @@ module.exports = class OrdersService { id, { currentUser, - transaction, + transaction, }, ); @@ -133,6 +134,4 @@ module.exports = class OrdersService { } -}; - - +}; \ No newline at end of file diff --git a/frontend/src/components/CartDrawer.tsx b/frontend/src/components/CartDrawer.tsx index 57b1a43..db14619 100644 --- a/frontend/src/components/CartDrawer.tsx +++ b/frontend/src/components/CartDrawer.tsx @@ -1,110 +1,153 @@ import React from 'react'; -import { mdiClose, mdiTrashCan, mdiMinus, mdiPlus } from '@mdi/js'; -import { useAppDispatch, useAppSelector } from '../stores/hooks'; -import { removeFromCart, updateQuantity, toggleCart } from '../stores/localCartSlice'; +import { mdiClose, mdiPlus, mdiMinus, mdiTrashCan, mdiCartOutline } from '@mdi/js'; import BaseButton from './BaseButton'; import BaseIcon from './BaseIcon'; +import { useAppDispatch, useAppSelector } from '../stores/hooks'; +import { removeFromCart, updateQuantity } from '../stores/localCartSlice'; +import { useRouter } from 'next/router'; -const CartDrawer = () => { +interface CartDrawerProps { + isOpen: boolean; + onClose: () => void; +} + +const CartDrawer: React.FC = ({ isOpen, onClose }) => { const dispatch = useAppDispatch(); - const { items, isOpen } = useAppSelector((state) => state.localCart); + const router = useRouter(); + const cartItems = useAppSelector((state) => state.localCart.items); - const totalPrice = items.reduce((sum, item) => sum + item.price * item.quantity, 0); + const subtotal = cartItems.reduce((acc, item) => acc + item.price * item.quantity, 0); if (!isOpen) return null; return ( -
+
{/* Backdrop */}
dispatch(toggleCart())} - >
- + className="absolute inset-0 bg-black bg-opacity-50 transition-opacity" + onClick={onClose} + /> + {/* Drawer */} -
-
-

Tu Carrito

- -
- -
- {items.length === 0 ? ( -
-

Tu carrito está vacío

- dispatch(toggleCart())} - /> +
+
+
+ {/* Header */} +
+

+ + Your Selection +

+
- ) : ( - items.map((item) => ( -
- {item.image && ( - {item.name} - )} -
-

{item.name}

-

${item.price.toFixed(2)}

-
- - {item.quantity} - + + {/* Content */} +
+ {cartItems.length === 0 ? ( +
+
+
+

Your cart is empty.

+
- -
- )) - )} -
+ ) : ( +
+ {cartItems.map((item) => ( +
+
+ {item.image ? ( + {item.name} + ) : ( + + )} +
+
+
+

{item.name}

+

${item.price.toFixed(2)}

+
+ +
+ {/* Quantity Controls */} +
+ + {item.quantity} + +
- {items.length > 0 && ( -
-
- Total: - ${totalPrice.toFixed(2)} + +
+
+
+ ))} +
+ )}
- { - alert('¡Próximamente! Estamos preparando el proceso de pago.'); - }} - /> -

- Envío gratuito en pedidos superiores a $50 -

+ + {/* Footer */} + {cartItems.length > 0 && ( +
+
+ Subtotal + ${subtotal.toFixed(2)} +
+

+ Shipping and taxes calculated at checkout. +

+
+ { + onClose(); + router.push('/checkout'); + }} + /> + +
+
+ )}
- )} +
-
); }; diff --git a/frontend/src/components/ElCalentico/Footer.tsx b/frontend/src/components/ElCalentico/Footer.tsx new file mode 100644 index 0000000..a2456b5 --- /dev/null +++ b/frontend/src/components/ElCalentico/Footer.tsx @@ -0,0 +1,46 @@ +import React from 'react'; +import Link from 'next/link'; +import { useAppSelector } from '../../stores/hooks'; + +const ElCalenticoFooter = () => { + const darkMode = useAppSelector((state) => state.style.darkMode); + + return ( +
+
+
+
+

El Calentico

+

+ Tu tienda virtual de confianza para alimentos, bebidas y productos del día a día. Llevamos la mejor calidad directamente a tu puerta. +

+
+
+

Enlaces Rápidos

+
    +
  • Productos
  • +
  • Categorías
  • +
  • Mi Cuenta
  • +
+
+
+

Legal

+
    +
  • Privacidad
  • +
  • Términos
  • +
+
+
+
+

© 2026 El Calentico. Todos los derechos reservados.

+
+ Términos de Uso + Política de Privacidad +
+
+
+
+ ); +}; + +export default ElCalenticoFooter; diff --git a/frontend/src/components/ElCalentico/Header.tsx b/frontend/src/components/ElCalentico/Header.tsx new file mode 100644 index 0000000..950c69f --- /dev/null +++ b/frontend/src/components/ElCalentico/Header.tsx @@ -0,0 +1,67 @@ +import React, { useState, useEffect } from 'react'; +import Link from 'next/link'; +import { mdiCartOutline } from '@mdi/js'; +import BaseIcon from '../BaseIcon'; +import { useAppSelector, useAppDispatch } from '../../stores/hooks'; +import { toggleCart } from '../../stores/localCartSlice'; + +const ElCalenticoHeader = () => { + const dispatch = useAppDispatch(); + const cartItems = useAppSelector((state) => state.localCart.items); + const darkMode = useAppSelector((state) => state.style.darkMode); + const [isScrolled, setIsScrolled] = useState(false); + + useEffect(() => { + const handleScroll = () => { + setIsScrolled(window.scrollY > 20); + }; + window.addEventListener('scroll', handleScroll); + return () => window.removeEventListener('scroll', handleScroll); + }, []); + + const cartCount = cartItems.reduce((acc, item) => acc + item.quantity, 0); + + return ( + + ); +}; + +export default ElCalenticoHeader; diff --git a/frontend/src/components/NavBarItem.tsx b/frontend/src/components/NavBarItem.tsx index 55559d2..762eec2 100644 --- a/frontend/src/components/NavBarItem.tsx +++ b/frontend/src/components/NavBarItem.tsx @@ -1,6 +1,5 @@ -import React, {useEffect, useRef} from 'react' +import React, {useEffect, useRef, useState} from 'react' import Link from 'next/link' -import { useState } from 'react' import { mdiChevronUp, mdiChevronDown } from '@mdi/js' import BaseDivider from './BaseDivider' import BaseIcon from './BaseIcon' @@ -129,4 +128,4 @@ export default function NavBarItem({ item }: Props) { } return
{NavBarItemComponentContents}
-} +} \ No newline at end of file diff --git a/frontend/src/layouts/Authenticated.tsx b/frontend/src/layouts/Authenticated.tsx index 1b9907d..26c3572 100644 --- a/frontend/src/layouts/Authenticated.tsx +++ b/frontend/src/layouts/Authenticated.tsx @@ -1,5 +1,4 @@ -import React, { ReactNode, useEffect } from 'react' -import { useState } from 'react' +import React, { ReactNode, useEffect, useState } from 'react' import jwt from 'jsonwebtoken'; import { mdiForwardburger, mdiBackburger, mdiMenu } from '@mdi/js' import menuAside from '../menuAside' @@ -126,4 +125,4 @@ export default function LayoutAuthenticated({
) -} +} \ No newline at end of file diff --git a/frontend/src/menuAside.ts b/frontend/src/menuAside.ts index ff9c865..e479046 100644 --- a/frontend/src/menuAside.ts +++ b/frontend/src/menuAside.ts @@ -7,6 +7,14 @@ const menuAside: MenuAsideItem[] = [ icon: icon.mdiViewDashboardOutline, label: 'Dashboard', }, + { + href: '/orders/dashboard', + label: 'Order Dashboard', + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + icon: icon.mdiMonitorDashboard ?? icon.mdiTable, + permissions: 'READ_ORDERS' + }, { href: '/users/users-list', @@ -160,4 +168,4 @@ const menuAside: MenuAsideItem[] = [ }, ] -export default menuAside +export default menuAside \ No newline at end of file diff --git a/frontend/src/pages/checkout.tsx b/frontend/src/pages/checkout.tsx new file mode 100644 index 0000000..3e5ff9e --- /dev/null +++ b/frontend/src/pages/checkout.tsx @@ -0,0 +1,272 @@ +import { mdiCartCheck, mdiChevronLeft, mdiTruckDelivery } from '@mdi/js'; +import Head from 'next/head'; +import React, { ReactElement, useState } from 'react'; +import CardBox from '../components/CardBox'; +import LayoutGuest from '../layouts/Guest'; +import SectionMain from '../components/SectionMain'; +import SectionTitleLineWithButton from '../components/SectionTitleLineWithButton'; +import { getPageTitle } from '../config'; +import { useAppDispatch, useAppSelector } from '../stores/hooks'; +import BaseButton from '../components/BaseButton'; +import FormField from '../components/FormField'; +import { clearCart } from '../stores/localCartSlice'; +import axios from 'axios'; +import { useRouter } from 'next/router'; +import ElCalenticoHeader from '../components/ElCalentico/Header'; +import ElCalenticoFooter from '../components/ElCalentico/Footer'; +import BaseIcon from '../components/BaseIcon'; + +const CheckoutPage = () => { + const dispatch = useAppDispatch(); + const router = useRouter(); + const cartItems = useAppSelector((state) => state.localCart.items); + + const [formData, setFormData] = useState({ + firstName: '', + lastName: '', + email: '', + phone: '', + address: '', + notes: '', + }); + + const [isSubmitting, setIsSubmitting] = useState(false); + const [orderSuccess, setOrderSuccess] = useState(false); + + const subtotal = cartItems.reduce((acc, item) => acc + item.price * item.quantity, 0); + + const handleInputChange = (e: React.ChangeEvent) => { + const { name, value } = e.target; + setFormData((prev) => ({ ...prev, [name]: value })); + }; + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + if (cartItems.length === 0) return; + + setIsSubmitting(true); + try { + // 1. Create Order + const orderData = { + data: { + order_number: `ORD-${Date.now()}`, + status: 'pending', + payment_status: 'unpaid', + subtotal_amount: subtotal, + total_amount: subtotal, + customer_note: `Guest: ${formData.firstName} ${formData.lastName}\nEmail: ${formData.email}\nPhone: ${formData.phone}\nAddress: ${formData.address}\nNotes: ${formData.notes}`, + placed_at: new Date().toISOString(), + }, + }; + + const orderResponse = await axios.post('/orders', orderData); + const orderId = orderResponse.data.id; + + // 2. Create Order Items + for (const item of cartItems) { + await axios.post('/order_items', { + data: { + orderId: orderId, + productId: item.id, + product_name_snapshot: item.name, + quantity: item.quantity, + unit_price_snapshot: item.price, + line_total_amount: item.price * item.quantity, + }, + }); + } + + // 3. Success + dispatch(clearCart()); + setOrderSuccess(true); + window.scrollTo(0, 0); + } catch (error) { + console.error('Checkout error:', error); + alert('There was an error processing your order. Please try again.'); + } finally { + setIsSubmitting(false); + } + }; + + if (orderSuccess) { + return ( +
+ + {getPageTitle('Order Success')} + + + +
+
+
+ +
+
+

¡Pedido Realizado con Éxito!

+

+ Gracias por tu pedido. Lo hemos recibido y empezaremos a preparar tus favoritos de El Calentico de inmediato. +

+ router.push('/')} + /> +
+
+ +
+ ); + } + + return ( +
+ + {getPageTitle('Checkout')} + + + + + router.push('/')} + small + /> + + +
+ {/* Form */} +
+ +
+ + + + + + +
+ +
+ + + + + + +
+ +
+ +