From 9b2a13b223279b039bba112377cbb5b75e27b5d3 Mon Sep 17 00:00:00 2001 From: Andres Alvarez Date: Sun, 25 May 2025 11:30:29 -0400 Subject: [PATCH 01/13] Fix context --- package-lock.json | 62 ++++++------------ package.json | 1 - src/app/register/RegisterForm.tsx | 1 - src/context/AuthContext.tsx | 19 +++--- src/context/CartContext.tsx | 101 +++++------------------------- 5 files changed, 47 insertions(+), 137 deletions(-) diff --git a/package-lock.json b/package-lock.json index fc8fbde..c7f30e7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,7 +12,6 @@ "@heroicons/react": "^2.2.0", "@microsoft/fetch-event-source": "^2.0.1", "@next/third-parties": "^15.3.0", - "@pharmatech/sdk": "^0.4.19", "@radix-ui/react-slider": "^1.2.4", "@react-google-maps/api": "^2.20.6", "@types/google.maps": "^3.58.1", @@ -1927,15 +1926,6 @@ "node": ">=12.4.0" } }, - "node_modules/@pharmatech/sdk": { - "version": "0.4.19", - "resolved": "https://registry.npmjs.org/@pharmatech/sdk/-/sdk-0.4.19.tgz", - "integrity": "sha512-ZiYsoiVRtDjxs5eDqtUAZrEsUWMEZ9+q5XpSOSHt+gTcZLBzntIOO21ZyAm6ONXyRO6EORsOy2vlnnOv87Cs0g==", - "license": "MIT", - "dependencies": { - "axios": "^1.8.1" - } - }, "node_modules/@pkgjs/parseargs": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", @@ -3548,6 +3538,7 @@ "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "dev": true, "license": "MIT" }, "node_modules/available-typed-arrays": { @@ -3576,16 +3567,6 @@ "node": ">=4" } }, - "node_modules/axios": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.8.1.tgz", - "integrity": "sha512-NN+fvwH/kV01dYUQ3PTOZns4LWtWhOFCAhQ/pHb88WQ1hNe5V/dvFwc4VJcDL11LT9xSX0QtsR8sWUuyOuOq7g==", - "dependencies": { - "follow-redirects": "^1.15.6", - "form-data": "^4.0.0", - "proxy-from-env": "^1.1.0" - } - }, "node_modules/axobject-query": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", @@ -3714,6 +3695,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -3998,6 +3980,7 @@ "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, "license": "MIT", "dependencies": { "delayed-stream": "~1.0.0" @@ -4248,6 +4231,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "dev": true, "license": "MIT", "engines": { "node": ">=0.4.0" @@ -4309,6 +4293,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dev": true, "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.1", @@ -4508,6 +4493,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -4517,6 +4503,7 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -4561,6 +4548,7 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0" @@ -4573,6 +4561,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -5290,25 +5279,6 @@ "dev": true, "license": "ISC" }, - "node_modules/follow-redirects": { - "version": "1.15.9", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", - "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/RubenVerborgh" - } - ], - "engines": { - "node": ">=4.0" - }, - "peerDependenciesMeta": { - "debug": { - "optional": true - } - } - }, "node_modules/for-each": { "version": "0.3.5", "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", @@ -5345,6 +5315,7 @@ "version": "4.0.2", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz", "integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==", + "dev": true, "license": "MIT", "dependencies": { "asynckit": "^0.4.0", @@ -5437,6 +5408,7 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "dev": true, "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.2", @@ -5461,6 +5433,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dev": true, "license": "MIT", "dependencies": { "dunder-proto": "^1.0.1", @@ -5611,6 +5584,7 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -5689,6 +5663,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -5701,6 +5676,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dev": true, "license": "MIT", "dependencies": { "has-symbols": "^1.0.3" @@ -6883,6 +6859,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -6921,6 +6898,7 @@ "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.6" @@ -6930,6 +6908,7 @@ "version": "2.1.35", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, "license": "MIT", "dependencies": { "mime-db": "1.52.0" @@ -10293,11 +10272,6 @@ "react-is": "^16.13.1" } }, - "node_modules/proxy-from-env": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" - }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", diff --git a/package.json b/package.json index ed6debf..c48ceef 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,6 @@ "@heroicons/react": "^2.2.0", "@microsoft/fetch-event-source": "^2.0.1", "@next/third-parties": "^15.3.0", - "@pharmatech/sdk": "^0.4.19", "@radix-ui/react-slider": "^1.2.4", "@react-google-maps/api": "^2.20.6", "@types/google.maps": "^3.58.1", diff --git a/src/app/register/RegisterForm.tsx b/src/app/register/RegisterForm.tsx index b1b360c..42978af 100644 --- a/src/app/register/RegisterForm.tsx +++ b/src/app/register/RegisterForm.tsx @@ -141,7 +141,6 @@ export default function RegisterForm() { }); login(loginResponse.accessToken, false); - window.location.reload(); router.push('/'); } catch (err) { console.error('Error creating account:', err); diff --git a/src/context/AuthContext.tsx b/src/context/AuthContext.tsx index e791c1c..2fd05e1 100644 --- a/src/context/AuthContext.tsx +++ b/src/context/AuthContext.tsx @@ -48,6 +48,13 @@ const decodeToken = (rawToken: string): JwtPayload | null => { } }; +const getToken = (): string | null => { + const storedToken = + localStorage.getItem('pharmatechToken') || + sessionStorage.getItem('pharmatechToken'); + return storedToken; +}; + export const AuthProvider = ({ children }: { children: ReactNode }) => { const [token, setToken] = useState(null); const [user, setUser] = useState(null); @@ -56,9 +63,7 @@ export const AuthProvider = ({ children }: { children: ReactNode }) => { const router = useRouter(); useEffect(() => { - const storedToken = - localStorage.getItem('pharmatechToken') || - sessionStorage.getItem('pharmatechToken'); + const storedToken = getToken(); if (storedToken) { setToken(storedToken); @@ -70,7 +75,7 @@ export const AuthProvider = ({ children }: { children: ReactNode }) => { useEffect(() => { const syncLogout = () => { - const storedToken = localStorage.getItem('pharmatechToken'); + const storedToken = getToken(); setToken(storedToken); if (storedToken) { @@ -86,9 +91,9 @@ export const AuthProvider = ({ children }: { children: ReactNode }) => { }, []); const login = (newToken: string, remember: boolean) => { - localStorage.setItem('pharmatechToken', newToken); - if (!remember) { - sessionStorage.setItem('pharmatechToken', newToken); + sessionStorage.setItem('pharmatechToken', newToken); + if (remember) { + localStorage.setItem('pharmatechToken', newToken); } setToken(newToken); diff --git a/src/context/CartContext.tsx b/src/context/CartContext.tsx index a493b8f..c108892 100644 --- a/src/context/CartContext.tsx +++ b/src/context/CartContext.tsx @@ -6,8 +6,8 @@ import React, { useEffect, ReactNode, useRef, + useCallback, } from 'react'; -import { useAuth } from '@/context/AuthContext'; import { toast } from 'react-toastify'; export interface CartItem { @@ -34,19 +34,6 @@ const CartContext = createContext(undefined); export const CartProvider = ({ children }: { children: ReactNode }) => { const [cartItems, setCartItems] = useState([]); const alertShownRef = useRef(false); - const { token, user } = useAuth(); - console.log('Token in Cart Provider:', token); - console.log('User in Cart Provider:', user); - - const showStockAlert = () => { - if (!alertShownRef.current) { - alertShownRef.current = true; - toast.error('No hay suficiente stock para este producto.'); - setTimeout(() => { - alertShownRef.current = false; - }, 1000); - } - }; useEffect(() => { const storedCart = localStorage.getItem('cartItems'); @@ -63,68 +50,17 @@ export const CartProvider = ({ children }: { children: ReactNode }) => { localStorage.setItem('cartItems', JSON.stringify(cartItems)); }, [cartItems]); - // Al detectar cambios en el token: - // - Si existe y además tenemos el usuario decodificado, se fusiona el carrito local con el del usuario (si no se ha fusionado ya). - // - Si no hay token, se mantiene el carrito del usuario anónimo. - useEffect(() => { - if (token && user) { - const userId = user.sub; - // Verificar si ya se fusionó el carrito para este usuario usando un flag en localStorage - const mergedUser = localStorage.getItem('mergedUser'); - if (mergedUser === userId) { - // Ya se realizó la fusión para este usuario - return; - } - - // Aquí se debería obtener el carrito del usuario desde la API. - // Descomenta y ajusta el siguiente bloque cuando el endpoint esté listo: - /* - api.cart.getUserCart(token) - .then((serverCart: CartItem[]) => { - const localCart: CartItem[] = JSON.parse(localStorage.getItem('cartItems') || '[]'); - const mergedCart = mergeCarts(serverCart, localCart); - setCartItems(mergedCart); - localStorage.setItem('cartItems', JSON.stringify(mergedCart)); - // Guardar el userId para evitar fusiones repetidas - localStorage.setItem('mergedUser', userId); - }) - .catch((error) => { - console.error('Error al obtener el carrito del usuario:', error); - }); - */ - - // Simulación: Se fusiona el carrito local con un carrito de servidor vacío - const localCart: CartItem[] = JSON.parse( - localStorage.getItem('cartItems') || '[]', - ); - const mergedCart = mergeCarts([], localCart); - setCartItems(mergedCart); - localStorage.setItem('cartItems', JSON.stringify(mergedCart)); - localStorage.setItem('mergedUser', userId); + const showStockAlert = useCallback(() => { + if (!alertShownRef.current) { + alertShownRef.current = true; + toast.error('No hay suficiente stock para este producto.'); + setTimeout(() => { + alertShownRef.current = false; + }, 1000); } - // Si no hay token, se mantiene el carrito guardado sin limpiarlo. - }, [token, user]); - - // Función para fusionar dos carritos sumando las cantidades en caso de que se repita el mismo ítem - const mergeCarts = ( - serverCart: CartItem[], - localCart: CartItem[], - ): CartItem[] => { - const merged = [...serverCart]; - localCart.forEach((localItem) => { - const index = merged.findIndex((item) => item.id === localItem.id); - if (index !== -1) { - // Si el ítem ya existe, se suman las cantidades - merged[index].quantity += localItem.quantity; - } else { - merged.push(localItem); - } - }); - return merged; - }; + }, []); - // Función para agregar un ítem al carrito - const addItem = (item: CartItem) => { + const addItem = useCallback((item: CartItem) => { setCartItems((prev) => { const exists = prev.find((p) => p.id === item.id); if (exists) { @@ -147,10 +83,9 @@ export const CartProvider = ({ children }: { children: ReactNode }) => { } return [...prev, item]; }); - }; + }, []); - // Función para actualizar la cantidad de un ítem del carrito - const updateItemQuantity = (id: string, quantity: number) => { + const updateItemQuantity = useCallback((id: string, quantity: number) => { setCartItems((prev) => prev.map((item) => { if (item.id === id) { @@ -169,19 +104,17 @@ export const CartProvider = ({ children }: { children: ReactNode }) => { return item; }), ); - }; + }, []); - // Función para eliminar un ítem del carrito - const removeItem = (id: string) => { + const removeItem = useCallback((id: string) => { setCartItems((prev) => prev.filter((item) => item.id !== id)); - }; + }, []); - // Función para vaciar el carrito (por ejemplo, al hacer logout) - const clearCart = () => { + const clearCart = useCallback(() => { setCartItems([]); localStorage.removeItem('cartItems'); localStorage.removeItem('mergedUser'); - }; + }, []); return ( Date: Sun, 25 May 2025 11:56:22 -0400 Subject: [PATCH 02/13] Standarize icons and bubbles in navbar --- src/components/Avatar.tsx | 4 ++- src/components/Navbar.tsx | 71 ++++++++++++++++++++----------------- src/context/CartContext.tsx | 16 ++++++++- 3 files changed, 56 insertions(+), 35 deletions(-) diff --git a/src/components/Avatar.tsx b/src/components/Avatar.tsx index f1ca606..aa18fb1 100644 --- a/src/components/Avatar.tsx +++ b/src/components/Avatar.tsx @@ -5,6 +5,7 @@ import Image from 'next/image'; import { useRouter } from 'next/navigation'; import { useAuth } from '@/context/AuthContext'; import { Colors } from '@/styles/styles'; +import { useCart } from '@/context/CartContext'; export type AvatarProps = { name: string; @@ -33,6 +34,7 @@ export default function Avatar({ const dropdownRef = useRef(null); const router = useRouter(); const { logout, token } = useAuth(); + const { clearCart } = useCart(); const initials = name .split(' ') @@ -50,8 +52,8 @@ export default function Avatar({ const handleLogoutClick = () => { logout(); + clearCart(); setDropdownOpen(false); - router.push('/'); }; useEffect(() => { diff --git a/src/components/Navbar.tsx b/src/components/Navbar.tsx index e9a864d..8169edd 100644 --- a/src/components/Navbar.tsx +++ b/src/components/Navbar.tsx @@ -34,7 +34,7 @@ type NavBarProps = { export default function NavBar({ onCartClick }: NavBarProps) { const router = useRouter(); const [categories, setCategories] = useState([]); - const { cartItems } = useCart(); + const { itemsCount } = useCart(); const { token, user, isLoading } = useAuth(); const { notifications, @@ -43,8 +43,6 @@ export default function NavBar({ onCartClick }: NavBarProps) { toggleNotifications, panelRef, } = useNotifications(token ?? undefined); - - const totalCount = cartItems.reduce((acc, item) => acc + item.quantity, 0); const [isLoggedIn, setIsLoggedIn] = useState(false); const [userData, setUserData] = useState(null); const [isCartOpen, setIsCartOpen] = useState(false); @@ -131,28 +129,31 @@ export default function NavBar({ onCartClick }: NavBarProps) { onClick={() => setIsCartOpen(true)} > - - {totalCount} - + {itemsCount > 0 && ( + + {itemsCount} + + )} - - {isLoggedIn && userData ? ( - router.push('/user')} - /> + <> + + router.push('/user')} + /> + ) : showLogin ? ( @@ -244,10 +128,8 @@ export default function SearchPage() { @@ -267,9 +149,7 @@ export default function SearchPage() {

Resultados para:{' '} - {selectedCategoryNames.length > 0 - ? selectedCategoryNames.join(', ') - : query || 'Todos los productos'} + {query ? query : 'Todos los productos'}

diff --git a/src/components/Navbar.tsx b/src/components/Navbar.tsx index 8169edd..c24c4a3 100644 --- a/src/components/Navbar.tsx +++ b/src/components/Navbar.tsx @@ -13,7 +13,6 @@ import Button from '@/components/Button'; import { useCart } from '@/context/CartContext'; import { useAuth } from '@/context/AuthContext'; import { api } from '@/lib/sdkConfig'; -import { CategoryResponse, Pagination } from '@pharmatech/sdk'; import CartOverlay from './Cart/CartOverlay'; import NotificationBell from '@/components/User/NotificationBell'; import { useNotifications } from '@/lib/utils/helpers/useNotificationList'; @@ -33,7 +32,7 @@ type NavBarProps = { export default function NavBar({ onCartClick }: NavBarProps) { const router = useRouter(); - const [categories, setCategories] = useState([]); + const { itemsCount } = useCart(); const { token, user, isLoading } = useAuth(); const { @@ -49,17 +48,6 @@ export default function NavBar({ onCartClick }: NavBarProps) { const [showLogin, setShowLogin] = useState(false); - useEffect(() => { - api.category - .findAll({ page: 1, limit: 20 }) - .then((resp: Pagination) => { - if (resp?.results) setCategories(resp.results); - }) - .catch((err) => { - console.error('Error al cargar categorías:', err); - }); - }, []); - useEffect(() => { if (token && user?.sub) { setIsLoggedIn(true); @@ -85,10 +73,6 @@ export default function NavBar({ onCartClick }: NavBarProps) { return () => clearTimeout(timeout); }, []); - const handleSearch = (query: string, category: string) => { - console.log('Buscando:', query, 'en', category); - }; - const handleLoginClick = () => { router.push('/login'); }; @@ -112,8 +96,6 @@ export default function NavBar({ onCartClick }: NavBarProps) { /> void; width?: string; height?: string; borderRadius?: string; @@ -24,9 +23,13 @@ type SearchBarProps = { disableDropdown?: boolean; }; +const defaultCategory: CategoryResponse = { + name: 'Categorías', + id: '', + description: '', +}; + export default function SearchBar({ - categories, - onSearch, width = '100%', height = '2.5rem', borderRadius = '0.375rem', @@ -38,20 +41,26 @@ export default function SearchBar({ disableDropdown = false, }: SearchBarProps) { const router = useRouter(); - const [selectedCategory, setSelectedCategory] = useState({ - name: 'Categorías', - id: '1', - description: '', - }); + const [categories, setCategories] = useState([]); + const [selectedCategory, setSelectedCategory] = + useState(defaultCategory); const [isOpen, setIsOpen] = useState(false); const [searchTerm, setSearchTerm] = useState(''); + useEffect(() => { + api.category + .findAll({ page: 1, limit: 20 }) + .then((resp: Pagination) => { + if (resp?.results) setCategories([defaultCategory, ...resp.results]); + }) + .catch((err) => { + console.error('Error al cargar categorías:', err); + }); + }, []); + const handleSearch = () => { const term = searchTerm.trim(); if (!term) return; - - onSearch?.(term, selectedCategory.name); - const q = encodeURIComponent(term); const categoryId = encodeURIComponent(selectedCategory.id.trim()); router.push(`/search?query=${q}&categoryId=${categoryId}`); diff --git a/src/components/SidebarFilter.tsx b/src/components/SidebarFilter.tsx index d4a0e01..4575535 100644 --- a/src/components/SidebarFilter.tsx +++ b/src/components/SidebarFilter.tsx @@ -1,7 +1,7 @@ 'use client'; -import React, { useState, useEffect, useRef } from 'react'; -import { useRouter, usePathname, useSearchParams } from 'next/navigation'; +import React, { useState, useEffect, useMemo } from 'react'; +import { useRouter } from 'next/navigation'; import { api } from '@/lib/sdkConfig'; import CheckButton from '@/components/CheckButton'; import { @@ -12,17 +12,18 @@ import { } from '@radix-ui/react-slider'; export interface Filters { - category: string[]; - brand: string[]; - presentation: string[]; - activeIngredient: string[]; + categories: string[]; + brands: string[]; + presentations: string[]; + activeIngredients: string[]; + query?: string; + priceMin?: number; + priceMax?: number; } interface SidebarFilterProps { initialFilters: Filters; - initialPriceRange?: [number, number]; - initialCurrentPriceRange?: [number, number]; - onApplyFilters: (filters: Filters, priceRange: [number, number]) => void; + setFilters: (filters: Filters) => void; onClearFilters: () => void; } @@ -33,53 +34,17 @@ interface Option { export default function SidebarFilter({ initialFilters, - initialPriceRange = [0, 0], - initialCurrentPriceRange = [0, 0], - onApplyFilters, onClearFilters, + setFilters, }: SidebarFilterProps) { + const priceRange = useMemo<[number, number]>(() => [0, 10000], []); const router = useRouter(); - const pathname = usePathname() || '/'; - const searchParams = useSearchParams(); const [localFilters, setLocalFilters] = useState(initialFilters); - const [localPrice, setLocalPrice] = useState<[number, number]>( - initialCurrentPriceRange, - ); const [categoriesList, setCategoriesList] = useState([]); const [brandsList, setBrandsList] = useState([]); const [presentationsList, setPresentationsList] = useState([]); - const initialSyncRef = useRef(true); - - useEffect(() => { - if (initialSyncRef.current) { - const params = searchParams; - const rawCategory = - params?.get('category') || params?.get('categoryId') || ''; - const category = rawCategory ? rawCategory.split(',') : []; - const brand = params?.get('brand')?.split(',') || []; - const presentation = params?.get('presentation')?.split(',') || []; - const activeIngredient = - params?.get('activeIngredient')?.split(',') || []; - const priceMin = parseFloat( - params?.get('priceMin') || String(initialPriceRange[0]), - ); - const priceMax = parseFloat( - params?.get('priceMax') || String(initialPriceRange[1]), - ); - - setLocalFilters({ category, brand, presentation, activeIngredient }); - setLocalPrice([priceMin, priceMax]); - // Trigger parent to reload results on refresh - onApplyFilters({ category, brand, presentation, activeIngredient }, [ - priceMin, - priceMax, - ]); - initialSyncRef.current = false; - } - }, [searchParams, initialPriceRange, onApplyFilters]); - const fetchOptions = async ( serviceFn: (opts: { page: number; limit: number }) => Promise<{ results: Array<{ @@ -130,46 +95,39 @@ export default function SidebarFilter({ }, []); const handleApply = () => { - const params = new URLSearchParams( - Array.from(searchParams?.entries() || []), - ); - - // Preserve initial category if none selected locally - const rawCat = params.get('category') || params.get('categoryId') || ''; - const initialCats = rawCat ? rawCat.split(',') : []; - const selectedCategories = - localFilters.category.length > 0 ? localFilters.category : initialCats; + const params = new URLSearchParams(); - if (localFilters.category.length) - params.set('categoryId', localFilters.category.join(',')); + if (localFilters.categories.length > 0) + // remove empty string from categories if length is greater than 1 + params.set( + 'categoryId', + localFilters.categories.filter((c) => c !== '').join(','), + ); else params.delete('categoryId'); - if (localFilters.brand.length > 0) - params.set('brand', localFilters.brand.join(',')); + if (localFilters.brands.length > 0) + params.set('brand', localFilters.brands.join(',')); else params.delete('brand'); - if (localFilters.presentation.length > 0) - params.set('presentation', localFilters.presentation.join(',')); + if (localFilters.presentations.length > 0) + params.set('presentation', localFilters.presentations.join(',')); else params.delete('presentation'); - if (localFilters.activeIngredient.length > 0) - params.set('activeIngredient', localFilters.activeIngredient.join(',')); + if (localFilters.activeIngredients.length > 0) + params.set('activeIngredient', localFilters.activeIngredients.join(',')); else params.delete('activeIngredient'); - params.set('priceMin', String(localPrice[0])); - params.set('priceMax', String(localPrice[1])); - - router.push(`${pathname}?${params.toString()}`); - onApplyFilters( - { - ...localFilters, - category: selectedCategories, - }, - localPrice, - ); + params.set('priceMin', String(localFilters.priceMin)); + params.set('priceMax', String(localFilters.priceMax)); + params.set('query', localFilters.query || ''); + setFilters(localFilters); + router.push(`/search?${params.toString()}`); }; - const toggleSelection = (key: keyof Filters, id: string) => { + const toggleSelection = ( + key: 'categories' | 'brands' | 'presentations', + id: string, + ) => { setLocalFilters((prev) => { const arr = prev[key]; const updated = arr.includes(id) @@ -179,16 +137,25 @@ export default function SidebarFilter({ }); }; + const setLocalPrice = (value: [number, number]) => { + setLocalFilters((prev) => ({ + ...prev, + priceMin: value[0], + priceMax: value[1], + })); + }; + const handleClear = () => { setLocalFilters({ - category: [], - brand: [], - presentation: [], - activeIngredient: [], + categories: [], + brands: [], + presentations: [], + activeIngredients: [], + query: '', + priceMin: priceRange[0], + priceMax: priceRange[1], }); - setLocalPrice(initialPriceRange); onClearFilters(); - router.push(pathname); }; const scrollClass = @@ -213,8 +180,8 @@ export default function SidebarFilter({
toggleSelection('category', opt.id)} + checked={localFilters.categories.includes(opt.id)} + onChange={() => toggleSelection('categories', opt.id)} />
))} @@ -228,8 +195,8 @@ export default function SidebarFilter({
toggleSelection('brand', opt.id)} + checked={localFilters.brands.includes(opt.id)} + onChange={() => toggleSelection('brands', opt.id)} />
))} @@ -243,8 +210,8 @@ export default function SidebarFilter({
toggleSelection('presentation', opt.id)} + checked={localFilters.presentations.includes(opt.id)} + onChange={() => toggleSelection('presentations', opt.id)} />
))} @@ -255,9 +222,9 @@ export default function SidebarFilter({

Precio

setLocalPrice([value[0], value[1]])} > @@ -267,8 +234,8 @@ export default function SidebarFilter({
- Bs {localPrice[0]} - Bs {localPrice[1]} + Bs {priceRange[0] / 100} + Bs {priceRange[1] / 100}
diff --git a/src/components/SuggestionProduct.tsx b/src/components/SuggestionProduct.tsx index 742a300..43922b0 100644 --- a/src/components/SuggestionProduct.tsx +++ b/src/components/SuggestionProduct.tsx @@ -42,8 +42,7 @@ export default function SearchSuggestions({ page: 1, limit: 10, ...(query.trim() && { q: query.trim() }), - ...(category.id && - category.id !== '1' && { categoryId: [category.id] }), + ...(category.id && { categoryId: [category.id] }), }; const data = await api.product.getProducts(params); setProducts(data.results); @@ -83,7 +82,9 @@ export default function SearchSuggestions({

Categoría

-

{category.name}

+

+ {category.name == 'Categorías' ? 'Todas' : category.name} +

From 44b4a7b3c4b9c46a134e439a5034accba1b4bf79 Mon Sep 17 00:00:00 2001 From: Andres Alvarez Date: Sun, 25 May 2025 19:08:31 -0400 Subject: [PATCH 06/13] Fix loader in product presentation detail --- .../presentation/[presentationId]/page.tsx | 32 ++++++++++++++----- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/src/app/(shop)/product/[productId]/presentation/[presentationId]/page.tsx b/src/app/(shop)/product/[productId]/presentation/[presentationId]/page.tsx index 8814aa9..ee91f5f 100644 --- a/src/app/(shop)/product/[productId]/presentation/[presentationId]/page.tsx +++ b/src/app/(shop)/product/[productId]/presentation/[presentationId]/page.tsx @@ -34,15 +34,16 @@ export default function ProductDetailPage() { const presentationId = String(params?.presentationId || ''); const [presentation, setPresentation] = - useState(null); + useState(); const [genericProduct, setGenericProduct] = - useState(null); + useState(); const [slides, setSlides] = useState([]); const [products, setProducts] = useState([]); const [presentationList, setPresentationList] = useState< ProductPresentationResponse[] >([]); - const [loading, setLoading] = useState(true); + const [presentationIsLoading, setPresentationIsLoading] = useState(true); + const [productIsLoading, setProductIsLoading] = useState(true); // 1) Load presentation detail useEffect(() => { @@ -50,8 +51,11 @@ export default function ProductDetailPage() { api.productPresentation .getByPresentationId(productId, presentationId) .then((data) => setPresentation(data)) - .catch((err) => console.error(err)) - .finally(() => setLoading(false)); + .catch((err) => { + console.error(err); + setProductIsLoading(false); + }) + .finally(() => setPresentationIsLoading(false)); }, [productId, presentationId]); // 2) Load generic product info & variants @@ -87,7 +91,10 @@ export default function ProductDetailPage() { { id: 1, imageUrl: '/images/product-detail.jpg' }, { id: 2, imageUrl: '/images/product-detail-2.jpg' }, ]), - ); + ) + .finally(() => { + setProductIsLoading(false); + }); }, [genericProduct]); // 4) Fetch related products @@ -105,8 +112,14 @@ export default function ProductDetailPage() { .then((res) => setProducts(res.results)) .catch((err) => console.error(err)); }, [genericProduct]); - if (loading) return ; - if (!presentation || !genericProduct) return ; + + if (presentationIsLoading || productIsLoading) { + return ; + } + + if (!presentation || !genericProduct) { + return ; + } // Breadcrumb con acción de "volver" si es búsqueda personalizada const breadcrumbItems = [ @@ -153,6 +166,9 @@ export default function ProductDetailPage() {

{presentation.presentation.description}

+

+ Existencia: {presentation.stock || 0} +

${presentation.price.toFixed(2)} From 0858a8913ff4c47fc439bb3ad56625af21a79fe6 Mon Sep 17 00:00:00 2001 From: Andres Alvarez Date: Sun, 25 May 2025 19:51:49 -0400 Subject: [PATCH 07/13] Add missing component for order states --- src/app/(shop)/order/[id]/page.tsx | 14 ++++++++--- src/app/(shop)/user/address/page.tsx | 2 +- src/app/(shop)/user/layout.tsx | 2 +- src/components/Avatar.tsx | 9 ++++++- src/components/Order/PaymentProcess.tsx | 24 +++---------------- src/components/User/Order/UserOrderDetail.tsx | 9 ++++--- 6 files changed, 30 insertions(+), 30 deletions(-) diff --git a/src/app/(shop)/order/[id]/page.tsx b/src/app/(shop)/order/[id]/page.tsx index 57bfad7..09632f6 100644 --- a/src/app/(shop)/order/[id]/page.tsx +++ b/src/app/(shop)/order/[id]/page.tsx @@ -139,19 +139,27 @@ export default function OrderInProgress() { case OrderStatus.REQUESTED: return ; case OrderStatus.APPROVED: - return ; + if ( + [PaymentMethod.BANK_TRANSFER, PaymentMethod.MOBILE_PAYMENT].includes( + order.paymentMethod, + ) + ) { + return ; + } else { + return ; + } case OrderStatus.IN_PROGRESS: return order.type === OrderType.PICKUP ? ( ) : ( ); + case OrderStatus.READY_FOR_PICKUP: + return ; case OrderStatus.CANCELED: return ; case OrderStatus.COMPLETED: return ; - default: - return

Error
; } }; diff --git a/src/app/(shop)/user/address/page.tsx b/src/app/(shop)/user/address/page.tsx index a557c97..1e267c0 100644 --- a/src/app/(shop)/user/address/page.tsx +++ b/src/app/(shop)/user/address/page.tsx @@ -61,7 +61,7 @@ export default function AddressPage() { } }; - if (!user || loading) return; //; + if (!user || loading) return; return ( <> diff --git a/src/app/(shop)/user/layout.tsx b/src/app/(shop)/user/layout.tsx index 61c2a8d..299251c 100644 --- a/src/app/(shop)/user/layout.tsx +++ b/src/app/(shop)/user/layout.tsx @@ -68,7 +68,7 @@ export default function UserProfileLayout({ })(); }, [user?.sub, token]); - if (!user?.sub || !userData) return; //; + if (!user?.sub || !userData) return; const sidebarUser: SidebarUser = { name: `${userData.firstName} ${userData.lastName}`, diff --git a/src/components/Avatar.tsx b/src/components/Avatar.tsx index aa18fb1..3509614 100644 --- a/src/components/Avatar.tsx +++ b/src/components/Avatar.tsx @@ -56,6 +56,13 @@ export default function Avatar({ setDropdownOpen(false); }; + const handleSafeProfileClick = () => { + if (onProfileClick) { + setDropdownOpen(false); + onProfileClick(); + } + }; + useEffect(() => { const handleClickOutside = (event: MouseEvent) => { if ( @@ -116,7 +123,7 @@ export default function Avatar({ {onProfileClick && (
  • Ir a mi perfil
  • diff --git a/src/components/Order/PaymentProcess.tsx b/src/components/Order/PaymentProcess.tsx index 4b186ae..c5b3163 100644 --- a/src/components/Order/PaymentProcess.tsx +++ b/src/components/Order/PaymentProcess.tsx @@ -27,31 +27,13 @@ type Props = { couponDiscount: number; }; -const PaymentProcess: React.FC = ({ order, couponDiscount }) => { +const PaymentProcess: React.FC = ({ order }) => { const { token } = useAuth(); const totalProducts = order.details.reduce( (acc, item) => acc + item.quantity, 0, ); - const subtotal = order.details.reduce( - (acc, item) => acc + item.productPresentation.price * item.quantity, - 0, - ); - const itemDiscount = order.details.reduce( - (acc, item) => - acc + - (item.productPresentation.promo?.discount - ? item.productPresentation.price * - item.quantity * - (item.productPresentation.promo.discount / 100) - : 0), - 0, - ); - const tax = (subtotal - itemDiscount - couponDiscount) * 0.16; - const totalAmount = - subtotal - itemDiscount - couponDiscount + (tax > 0 ? tax : 0); - const [banks, setBanks] = useState([]); const [selectedBank, setSelectedBank] = useState(''); const [paymentConfirmation, setPaymentConfirmationData] = @@ -108,7 +90,7 @@ const PaymentProcess: React.FC = ({ order, couponDiscount }) => { [paymentConfirmation, order.id, token], ); - const isBank = false; //paymentMethod === 'bank'; + const isBank = order.paymentMethod === PaymentMethod.BANK_TRANSFER; const description = isBank ? 'Debes hacer el pago del monto exacto, la orden se creará cuando se confirme el pago' @@ -186,7 +168,7 @@ const PaymentProcess: React.FC = ({ order, couponDiscount }) => {

    Monto

    - Bs.{totalAmount.toFixed(2)} + Bs.{order.totalPrice.toFixed(2)}
    diff --git a/src/components/User/Order/UserOrderDetail.tsx b/src/components/User/Order/UserOrderDetail.tsx index f00b4ab..d14512b 100644 --- a/src/components/User/Order/UserOrderDetail.tsx +++ b/src/components/User/Order/UserOrderDetail.tsx @@ -47,7 +47,7 @@ export default function UserOrderDetail({ }, 0); return ( -
    +
    {/* Header */}

    @@ -170,8 +170,11 @@ export default function UserOrderDetail({

    {product.name}

    -

    - {product.description} +

    + {product.description?.substring(0, 50)} + {product.description && product.description.length > 50 && ( + ... + )}

    Date: Sun, 25 May 2025 19:53:04 -0400 Subject: [PATCH 08/13] Update dependencies --- package-lock.json | 65 ++++++++++++++++++++++++++++++++++------------- package.json | 1 + 2 files changed, 48 insertions(+), 18 deletions(-) diff --git a/package-lock.json b/package-lock.json index c7f30e7..59d4245 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,6 +12,7 @@ "@heroicons/react": "^2.2.0", "@microsoft/fetch-event-source": "^2.0.1", "@next/third-parties": "^15.3.0", + "@pharmatech/sdk": "^0.4.19", "@radix-ui/react-slider": "^1.2.4", "@react-google-maps/api": "^2.20.6", "@types/google.maps": "^3.58.1", @@ -1926,6 +1927,15 @@ "node": ">=12.4.0" } }, + "node_modules/@pharmatech/sdk": { + "version": "0.4.19", + "resolved": "https://registry.npmjs.org/@pharmatech/sdk/-/sdk-0.4.19.tgz", + "integrity": "sha512-ZiYsoiVRtDjxs5eDqtUAZrEsUWMEZ9+q5XpSOSHt+gTcZLBzntIOO21ZyAm6ONXyRO6EORsOy2vlnnOv87Cs0g==", + "license": "MIT", + "dependencies": { + "axios": "^1.8.1" + } + }, "node_modules/@pkgjs/parseargs": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", @@ -3538,7 +3548,6 @@ "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", - "dev": true, "license": "MIT" }, "node_modules/available-typed-arrays": { @@ -3567,6 +3576,17 @@ "node": ">=4" } }, + "node_modules/axios": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.9.0.tgz", + "integrity": "sha512-re4CqKTJaURpzbLHtIi6XpDv20/CnpXOtjRY5/CU32L8gU8ek9UIivcfvSWvmKEngmVbrUtPpdDwWDWL7DNHvg==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, "node_modules/axobject-query": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", @@ -3695,7 +3715,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", - "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -3980,7 +3999,6 @@ "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dev": true, "license": "MIT", "dependencies": { "delayed-stream": "~1.0.0" @@ -4231,7 +4249,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.4.0" @@ -4293,7 +4310,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", - "dev": true, "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.1", @@ -4493,7 +4509,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -4503,7 +4518,6 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -4548,7 +4562,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", - "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0" @@ -4561,7 +4574,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", - "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -5279,6 +5291,26 @@ "dev": true, "license": "ISC" }, + "node_modules/follow-redirects": { + "version": "1.15.9", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", + "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, "node_modules/for-each": { "version": "0.3.5", "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", @@ -5315,7 +5347,6 @@ "version": "4.0.2", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz", "integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==", - "dev": true, "license": "MIT", "dependencies": { "asynckit": "^0.4.0", @@ -5408,7 +5439,6 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", - "dev": true, "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.2", @@ -5433,7 +5463,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", - "dev": true, "license": "MIT", "dependencies": { "dunder-proto": "^1.0.1", @@ -5584,7 +5613,6 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -5663,7 +5691,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -5676,7 +5703,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", - "dev": true, "license": "MIT", "dependencies": { "has-symbols": "^1.0.3" @@ -6859,7 +6885,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -6898,7 +6923,6 @@ "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.6" @@ -6908,7 +6932,6 @@ "version": "2.1.35", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "dev": true, "license": "MIT", "dependencies": { "mime-db": "1.52.0" @@ -10272,6 +10295,12 @@ "react-is": "^16.13.1" } }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "license": "MIT" + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", diff --git a/package.json b/package.json index c48ceef..ed6debf 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "@heroicons/react": "^2.2.0", "@microsoft/fetch-event-source": "^2.0.1", "@next/third-parties": "^15.3.0", + "@pharmatech/sdk": "^0.4.19", "@radix-ui/react-slider": "^1.2.4", "@react-google-maps/api": "^2.20.6", "@types/google.maps": "^3.58.1", From ae42df49aa1309b2edd0bd52594b229d11171ace Mon Sep 17 00:00:00 2001 From: Andres Alvarez Date: Sun, 25 May 2025 20:08:59 -0400 Subject: [PATCH 09/13] Install version 0.4.20 of sdk --- package-lock.json | 8 ++++---- package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index 59d4245..804e55c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,7 +12,7 @@ "@heroicons/react": "^2.2.0", "@microsoft/fetch-event-source": "^2.0.1", "@next/third-parties": "^15.3.0", - "@pharmatech/sdk": "^0.4.19", + "@pharmatech/sdk": "^0.4.20", "@radix-ui/react-slider": "^1.2.4", "@react-google-maps/api": "^2.20.6", "@types/google.maps": "^3.58.1", @@ -1928,9 +1928,9 @@ } }, "node_modules/@pharmatech/sdk": { - "version": "0.4.19", - "resolved": "https://registry.npmjs.org/@pharmatech/sdk/-/sdk-0.4.19.tgz", - "integrity": "sha512-ZiYsoiVRtDjxs5eDqtUAZrEsUWMEZ9+q5XpSOSHt+gTcZLBzntIOO21ZyAm6ONXyRO6EORsOy2vlnnOv87Cs0g==", + "version": "0.4.20", + "resolved": "https://registry.npmjs.org/@pharmatech/sdk/-/sdk-0.4.20.tgz", + "integrity": "sha512-unhWNGoPDVmZ4z877xjH5DHA6E8MYuFe6smo6JDmxFX/AbdXa3O8hTZzSAG1mc5PUmfVTa6mKeSxJpEe05fnSw==", "license": "MIT", "dependencies": { "axios": "^1.8.1" diff --git a/package.json b/package.json index ed6debf..fed1d04 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,7 @@ "@heroicons/react": "^2.2.0", "@microsoft/fetch-event-source": "^2.0.1", "@next/third-parties": "^15.3.0", - "@pharmatech/sdk": "^0.4.19", + "@pharmatech/sdk": "^0.4.20", "@radix-ui/react-slider": "^1.2.4", "@react-google-maps/api": "^2.20.6", "@types/google.maps": "^3.58.1", From 3d3619b78284626f6cf8d96bb196e68520bf2e8c Mon Sep 17 00:00:00 2001 From: Andres Alvarez Date: Sun, 25 May 2025 20:36:57 -0400 Subject: [PATCH 10/13] Format price to decimals --- .../presentation/[presentationId]/page.tsx | 3 +- src/components/Cart/CartItem.tsx | 23 +++---------- src/components/Cart/CartSummary.tsx | 7 ++-- src/components/Order/OrderSummary.tsx | 17 +++++----- src/components/Order/PaymentProcess.tsx | 3 +- src/components/Order/ProductOrderSummary.tsx | 15 ++++---- src/components/Product/ProductCard.tsx | 7 ++-- src/components/User/Order/UserOrderDetail.tsx | 34 +++++++------------ src/components/User/Order/UserOrdertable.tsx | 5 ++- src/lib/utils/helpers/priceFormatter.ts | 5 +++ 10 files changed, 54 insertions(+), 65 deletions(-) create mode 100644 src/lib/utils/helpers/priceFormatter.ts diff --git a/src/app/(shop)/product/[productId]/presentation/[presentationId]/page.tsx b/src/app/(shop)/product/[productId]/presentation/[presentationId]/page.tsx index ee91f5f..6ab0bd6 100644 --- a/src/app/(shop)/product/[productId]/presentation/[presentationId]/page.tsx +++ b/src/app/(shop)/product/[productId]/presentation/[presentationId]/page.tsx @@ -20,6 +20,7 @@ import { } from '@pharmatech/sdk'; import Loading from '@/app/loading'; import ProductNotFound from '@/components/Product/NotFound'; +import { formatPrice } from '@/lib/utils/helpers/priceFormatter'; export default function ProductDetailPage() { const router = useRouter(); @@ -171,7 +172,7 @@ export default function ProductDetailPage() {

    - ${presentation.price.toFixed(2)} + ${formatPrice(presentation.price)}

    = ({

    {item.name}

    -

    - (${Number.isInteger(item.price) ? item.price : item.price.toFixed(2)}{' '} - c/u) -

    +

    (${formatPrice(item.price)} c/u)

    = ({ {discount > 0 ? ( <> - $ - {Number.isInteger(discountedTotal) - ? discountedTotal - : discountedTotal.toFixed(2)} + ${formatPrice(discountedTotal)} - $ - {Number.isInteger(originalTotal) - ? originalTotal - : originalTotal.toFixed(2)} + ${formatPrice(originalTotal)} ) : ( - - $ - {Number.isInteger(originalTotal) - ? originalTotal - : originalTotal.toFixed(2)} - + ${formatPrice(originalTotal)} )}
    @@ -29,7 +30,7 @@ const CartSummary: React.FC = ({ Descuento - - ${Number.isInteger(discount) ? discount : discount.toFixed(2)} + - ${formatPrice(discount)}
    @@ -37,7 +38,7 @@ const CartSummary: React.FC = ({ Total - ${Number.isInteger(total) ? total : total.toFixed(2)} + ${formatPrice(total)}
    diff --git a/src/components/Order/ProductOrderSummary.tsx b/src/components/Order/ProductOrderSummary.tsx index bc65dc5..b250c77 100644 --- a/src/components/Order/ProductOrderSummary.tsx +++ b/src/components/Order/ProductOrderSummary.tsx @@ -3,6 +3,7 @@ import React, { useMemo } from 'react'; import Image from 'next/image'; import { OrderDetailedResponse } from '@pharmatech/sdk'; +import { formatPrice } from '@/lib/utils/helpers/priceFormatter'; interface ProductOrderSummaryProps { order: OrderDetailedResponse; @@ -64,22 +65,22 @@ const ProductOrderSummary: React.FC = ({ order }) => { {detail.productPresentation.product.name}

    - ${discountedUnit.toFixed(2)} x {qty} + ${formatPrice(discountedUnit)} x {qty}

    {promo > 0 ? (

    - ${lineSubtotal.toFixed(2)} + ${formatPrice(lineSubtotal)}

    - ${(price * qty).toFixed(2)} + ${formatPrice(price * qty)}

    ) : (

    - ${lineSubtotal.toFixed(2)} + ${formatPrice(lineSubtotal)}

    )}
    @@ -91,17 +92,17 @@ const ProductOrderSummary: React.FC = ({ order }) => {
    Subtotal - ${subtotal.toFixed(2)} + ${formatPrice(subtotal)}
    {itemDiscount > 0 && (
    Descuento - -${itemDiscount.toFixed(2)} + -${formatPrice(itemDiscount)}
    )}
    Total - ${total.toFixed(2)} + ${formatPrice(total)}
    diff --git a/src/components/Product/ProductCard.tsx b/src/components/Product/ProductCard.tsx index 7b8ab79..68dbb68 100644 --- a/src/components/Product/ProductCard.tsx +++ b/src/components/Product/ProductCard.tsx @@ -5,6 +5,7 @@ import Link from 'next/link'; import CardButton from '../CardButton'; import Badge from '../Badge'; import { ProductPresentation } from '@pharmatech/sdk'; +import { formatPrice } from '@/lib/utils/helpers/priceFormatter'; type Props = { product: ProductPresentation; @@ -73,7 +74,7 @@ export default function ProductCard({ product }: Props) { <>
    - ${price.toFixed(2)} + ${formatPrice(price)}
    - ${finalPrice.toFixed(2)} + ${formatPrice(finalPrice)} ) : ( - ${price.toFixed(2)} + ${formatPrice(price)} )}
    diff --git a/src/components/User/Order/UserOrderDetail.tsx b/src/components/User/Order/UserOrderDetail.tsx index d14512b..f19521f 100644 --- a/src/components/User/Order/UserOrderDetail.tsx +++ b/src/components/User/Order/UserOrderDetail.tsx @@ -9,6 +9,7 @@ import { OrderDetailProductPresentationResponse, } from '@pharmatech/sdk'; import Button from '@/components/Button'; +import { formatPrice } from '@/lib/utils/helpers/priceFormatter'; interface OrderDetailProps { orderNumber: string; @@ -76,12 +77,7 @@ export default function UserOrderDetail({ const quantity = item.quantity; const promo = presentation.promo; - const isPromoActive = - promo && - new Date(promo.startAt) <= now && - now < new Date(promo.expiredAt); - - const discountedPrice = isPromoActive + const discountedPrice = promo ? basePrice * (1 - promo.discount / 100) : basePrice; @@ -119,13 +115,11 @@ export default function UserOrderDetail({
    - {isPromoActive && ( - - ${(basePrice * quantity).toFixed(2)} - - )} + + ${formatPrice(basePrice * quantity)} + - ${(discountedPrice * quantity).toFixed(2)} + ${formatPrice(discountedPrice * quantity)}
    @@ -191,12 +185,10 @@ export default function UserOrderDetail({
    {quantity}
    - {isPromoActive && ( - - ${(basePrice * quantity).toFixed(2)} - - )} - ${(discountedPrice * quantity).toFixed(2)} + + ${formatPrice(basePrice * quantity)} + + ${formatPrice(discountedPrice * quantity)}
    @@ -215,19 +207,19 @@ export default function UserOrderDetail({ ({products.length} productos)
    -
    ${subtotal.toFixed(2)}
    +
    ${formatPrice(subtotal)}

    Descuentos
    -
    -${totalDiscount.toFixed(2)}
    +
    -${formatPrice(totalDiscount)}
    TOTAL
    -
    ${total.toFixed(2)}
    +
    ${formatPrice(total)}
    diff --git a/src/components/User/Order/UserOrdertable.tsx b/src/components/User/Order/UserOrdertable.tsx index 2f0c3a7..5ebd3ee 100644 --- a/src/components/User/Order/UserOrdertable.tsx +++ b/src/components/User/Order/UserOrdertable.tsx @@ -5,6 +5,7 @@ import type { BadgeProps } from '@/components/Badge'; import Button from '@/components/Button'; import Pagination from '@/components/Pagination'; import { OrderResponse, OrderStatus } from '@pharmatech/sdk'; +import { formatPrice } from '@/lib/utils/helpers/priceFormatter'; interface OrderTableProps { orders: OrderResponse[]; @@ -35,8 +36,6 @@ const getBadgeProps = ( } }; -const formatPrice = (price: number) => `$${price.toFixed(2)}`; - export default function OrderTable({ orders, onViewDetails }: OrderTableProps) { const [currentPage, setCurrentPage] = useState(1); const itemsPerPage = 4; @@ -57,7 +56,7 @@ export default function OrderTable({ orders, onViewDetails }: OrderTableProps) {
    #{order.id.slice(0, 8)}
    -
    {formatPrice(order.totalPrice)}
    +
    ${formatPrice(order.totalPrice)}
    {new Date(order.createdAt).toLocaleDateString('es-Es')} diff --git a/src/lib/utils/helpers/priceFormatter.ts b/src/lib/utils/helpers/priceFormatter.ts new file mode 100644 index 0000000..0040a86 --- /dev/null +++ b/src/lib/utils/helpers/priceFormatter.ts @@ -0,0 +1,5 @@ +export function formatPrice(price: number): string { + if (isNaN(price) || price < 0) return '0.00'; + + return (price / 100).toFixed(2); +} From 67e51ae8ee0e6af8c07cb5934217ded8515c858f Mon Sep 17 00:00:00 2001 From: Andres Alvarez Date: Sun, 25 May 2025 20:40:26 -0400 Subject: [PATCH 11/13] Standarize currency to USD instead of VES --- src/components/Order/PaymentProcess.tsx | 2 +- src/components/SidebarFilter.tsx | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/Order/PaymentProcess.tsx b/src/components/Order/PaymentProcess.tsx index a3ba0c3..bc54e71 100644 --- a/src/components/Order/PaymentProcess.tsx +++ b/src/components/Order/PaymentProcess.tsx @@ -169,7 +169,7 @@ const PaymentProcess: React.FC = ({ order }) => {

    Monto

    - Bs.{formatPrice(order.totalPrice)} + ${formatPrice(order.totalPrice)}
    diff --git a/src/components/SidebarFilter.tsx b/src/components/SidebarFilter.tsx index 4575535..0e99248 100644 --- a/src/components/SidebarFilter.tsx +++ b/src/components/SidebarFilter.tsx @@ -234,8 +234,8 @@ export default function SidebarFilter({
    - Bs {priceRange[0] / 100} - Bs {priceRange[1] / 100} + ${priceRange[0] / 100} + ${priceRange[1] / 100}
    From 01e329b812617e454ed1dfb176f0c57dc83d94ba Mon Sep 17 00:00:00 2001 From: Andres Alvarez Date: Mon, 26 May 2025 17:13:27 -0400 Subject: [PATCH 12/13] Refactor to use server components --- src/app/(shop)/layout.tsx | 5 +- src/app/(shop)/page.tsx | 153 ++-------------- .../presentation/[presentationId]/page.tsx | 172 +++++------------- src/app/(shop)/product/error.tsx | 45 +++++ src/components/Home/Categories.tsx | 26 +++ src/components/Home/EmailConfirmation.tsx | 60 ++++++ src/components/Home/ProductsOffer.tsx | 16 ++ src/components/Home/ProductsRecommended.tsx | 52 ++++++ .../Product/PresentationDropdown.tsx | 26 +++ .../Product/ProductCarouselSkelete.tsx | 25 +++ 10 files changed, 315 insertions(+), 265 deletions(-) create mode 100644 src/app/(shop)/product/error.tsx create mode 100644 src/components/Home/Categories.tsx create mode 100644 src/components/Home/EmailConfirmation.tsx create mode 100644 src/components/Home/ProductsOffer.tsx create mode 100644 src/components/Home/ProductsRecommended.tsx create mode 100644 src/components/Product/PresentationDropdown.tsx create mode 100644 src/components/Product/ProductCarouselSkelete.tsx diff --git a/src/app/(shop)/layout.tsx b/src/app/(shop)/layout.tsx index 353a291..887c460 100644 --- a/src/app/(shop)/layout.tsx +++ b/src/app/(shop)/layout.tsx @@ -1,9 +1,10 @@ 'use client'; -import { ReactNode } from 'react'; +import { ReactNode, Suspense } from 'react'; import NavBar from '@/components/Navbar'; import Footer from '@/components/Footer'; import { useAuth } from '@/context/AuthContext'; +import Loading from '../loading'; type ShopLayoutProps = { children: ReactNode; @@ -21,7 +22,7 @@ export default function ShopLayout({ children }: ShopLayoutProps) {
    - {children} + }>{children}