From 668276aad0d2958d3b468d0db791479e1dca6b3c Mon Sep 17 00:00:00 2001 From: Mvjtb <1001.25854794.ucla@gmail.com> Date: Tue, 20 May 2025 19:31:30 -0400 Subject: [PATCH 01/12] Add OfferCard component with props and styles --- src/components/OfferCardProps.tsx | 158 ++++++++++++++++++++++++++++++ 1 file changed, 158 insertions(+) create mode 100644 src/components/OfferCardProps.tsx diff --git a/src/components/OfferCardProps.tsx b/src/components/OfferCardProps.tsx new file mode 100644 index 0000000..2e1f831 --- /dev/null +++ b/src/components/OfferCardProps.tsx @@ -0,0 +1,158 @@ +// src/components/OfferCard.tsx + +import React from 'react'; +import { View, StyleSheet, Image, TouchableOpacity } from 'react-native'; +import { Colors, FontSizes } from '../styles/theme'; +import PoppinsText from './PoppinsText'; +import CardButton from './CardButton'; +import { truncateString } from '../utils/commons'; +import { useCart } from '../hooks/useCart'; +import { useRouter } from 'expo-router'; + +export interface OfferCardProps { + id: string; + presentationId: string; + productId: string; + imageUrl: string; + name: string; + category: string; + originalPrice: number; + discount: number; + finalPrice: number; + getQuantity?: (qty: number) => void; +} + +const OfferCard: React.FC = ({ + id, + presentationId, + productId, + imageUrl, + name, + category, + originalPrice, + discount, + finalPrice, + getQuantity, +}) => { + const { getItemQuantity, updateCartQuantity } = useCart(); + const router = useRouter(); + + return ( + + router.push(`/products/${productId}/presentation/${presentationId}`) + } + > + + + {category} + + + + + + { + getQuantity?.(qty); + updateCartQuantity(id, qty); + }} + initialValue={getItemQuantity(id)} + /> + + + + + + {truncateString(name, 25)} + + + + + ${originalPrice.toFixed(2)} + + + {discount.toFixed(0)}% + + + + ${finalPrice.toFixed(2)} + + + + + ); +}; + +const styles = StyleSheet.create({ + card: { + margin: 10, + backgroundColor: Colors.menuWhite, + borderWidth: 1, + borderColor: Colors.gray_100, + borderRadius: 15, + padding: 10, + maxWidth: 140, + minWidth: 140, + minHeight: 340, + }, + tagContainer: { + flexDirection: 'row-reverse', + marginBottom: 8, + }, + tag: { + backgroundColor: Colors.semanticInfo, + borderRadius: 50, + paddingHorizontal: 9, + color: Colors.textWhite, + fontSize: FontSizes.c3.size, + }, + imageContainer: { + position: 'relative', + alignItems: 'center', + marginBottom: 12, + }, + image: { + width: 114, + height: 118, + borderRadius: 14, + }, + cardButtonContainer: { + position: 'absolute', + bottom: -15, + right: 8, + zIndex: 2, + }, + description: { + marginHorizontal: 2, + }, + name: { + fontSize: FontSizes.s2.size, + marginBottom: 6, + minHeight: 50, + }, + priceContainer: { + flexDirection: 'row', + alignItems: 'center', + marginBottom: 4, + }, + originalPrice: { + fontSize: FontSizes.b1.size, + color: Colors.disableText, + textDecorationLine: 'line-through', + marginRight: 6, + }, + discountBadge: { + fontSize: FontSizes.c1.size, + backgroundColor: Colors.semanticInfo, + borderRadius: 4, + paddingHorizontal: 4, + color: Colors.textWhite, + }, + finalPrice: { + fontSize: FontSizes.s1.size, + fontWeight: 'bold', + color: Colors.gray_500, + }, +}); + +export default OfferCard; From 67db2f2544825f9b2f7fa7c1d8169b4bbfa065a7 Mon Sep 17 00:00:00 2001 From: Mvjtb <1001.25854794.ucla@gmail.com> Date: Tue, 20 May 2025 20:18:38 -0400 Subject: [PATCH 02/12] Refactor OfferCard component and enhance OffersScreen with loading state and promo filtering --- src/components/OfferCardProps.tsx | 75 ++++++++++++--------- src/screens/tab/OffersScreen.tsx | 105 ++++++++++++++++++++++++++++-- 2 files changed, 145 insertions(+), 35 deletions(-) diff --git a/src/components/OfferCardProps.tsx b/src/components/OfferCardProps.tsx index 2e1f831..8d5d0f5 100644 --- a/src/components/OfferCardProps.tsx +++ b/src/components/OfferCardProps.tsx @@ -44,28 +44,27 @@ const OfferCard: React.FC = ({ } > - - {category} - - - - - - { - getQuantity?.(qty); - updateCartQuantity(id, qty); - }} - initialValue={getItemQuantity(id)} - /> + + + {category} + + + + + { + getQuantity?.(qty); + updateCartQuantity(id, qty); + }} + initialValue={getItemQuantity(id)} + /> + - {truncateString(name, 25)} - ${originalPrice.toFixed(2)} @@ -91,13 +90,21 @@ const styles = StyleSheet.create({ borderColor: Colors.gray_100, borderRadius: 15, padding: 10, + paddingBottom: 5, + marginBottom: 16, maxWidth: 140, minWidth: 140, - minHeight: 340, + minHeight: 315, + maxHeight: 315, }, - tagContainer: { + imageTagWrapper: { + width: '100%', + height: 135, + marginBottom: 30, + }, + tagRow: { + width: '100%', flexDirection: 'row-reverse', - marginBottom: 8, }, tag: { backgroundColor: Colors.semanticInfo, @@ -105,11 +112,14 @@ const styles = StyleSheet.create({ paddingHorizontal: 9, color: Colors.textWhite, fontSize: FontSizes.c3.size, + maxWidth: '100%', }, imageContainer: { - position: 'relative', - alignItems: 'center', - marginBottom: 12, + minHeight: 118, + maxHeight: 118, + maxWidth: 114, + borderRadius: 15, + marginVertical: 8, }, image: { width: 114, @@ -117,29 +127,32 @@ const styles = StyleSheet.create({ borderRadius: 14, }, cardButtonContainer: { - position: 'absolute', - bottom: -15, - right: 8, - zIndex: 2, + position: 'relative', + top: -30, + left: 58, + maxWidth: '65%', + alignItems: 'flex-end', + zIndex: 999, }, description: { marginHorizontal: 2, + justifyContent: 'center', }, name: { fontSize: FontSizes.s2.size, marginBottom: 6, - minHeight: 50, + minHeight: 70, + maxHeight: 70, }, priceContainer: { flexDirection: 'row', alignItems: 'center', - marginBottom: 4, }, originalPrice: { fontSize: FontSizes.b1.size, color: Colors.disableText, textDecorationLine: 'line-through', - marginRight: 6, + marginRight: 14, }, discountBadge: { fontSize: FontSizes.c1.size, @@ -150,8 +163,8 @@ const styles = StyleSheet.create({ }, finalPrice: { fontSize: FontSizes.s1.size, - fontWeight: 'bold', - color: Colors.gray_500, + fontWeight: 'medium', + color: Colors.textMain, }, }); diff --git a/src/screens/tab/OffersScreen.tsx b/src/screens/tab/OffersScreen.tsx index 9ca0574..0be6b3c 100644 --- a/src/screens/tab/OffersScreen.tsx +++ b/src/screens/tab/OffersScreen.tsx @@ -1,18 +1,115 @@ -import React from 'react'; -import { View, StyleSheet } from 'react-native'; +// src/screens/tab/OffersScreen.tsx +import React, { useState, useEffect } from 'react'; +import { + View, + FlatList, + StyleSheet, + ActivityIndicator, + ListRenderItemInfo, +} from 'react-native'; import PoppinsText from '../../components/PoppinsText'; -import { Colors } from '../../styles/theme'; +import { Colors, FontSizes } from '../../styles/theme'; +import OfferCard from '../../components/OfferCardProps'; +import { ProductService } from '../../services/products'; +import { ProductPresentation } from '../../types/api.d'; export default function OffersScreen() { + const [offers, setOffers] = useState([]); + const [loading, setLoading] = useState(false); + + const loadOffers = async () => { + setLoading(true); + const res = await ProductService.getProducts(1, 50); + if (res.success) { + const promoItems = res.data.results.filter((item) => + // puede venir en item.promo o dentro de item.presentation.promo + Boolean( + // @ts-expect-error - promo puede estar en item o en item.presentation + item.promo?.discount ?? + // @ts-expect-error - promo puede estar en item o en item.presentation + item.presentation.promo?.discount, + ), + ); + setOffers(promoItems); + } else { + console.error('Error al cargar productos:', res.error); + } + setLoading(false); + }; + + useEffect(() => { + loadOffers(); + }, []); + + if (loading) { + return ( + + + + ); + } + + const renderOffer = ({ item }: ListRenderItemInfo) => { + // @ts-expect-error: asumimos que existe promo en alguna de estas rutas + const promo = item.promo ?? item.presentation.promo; + const discount = promo?.discount ?? 0; + const finalPrice = item.price * (1 - discount / 100); + + return ( + {}} + /> + ); + }; + return ( - Pantalla Ofertas + + Ofertas especiales + + item.id} + numColumns={2} + contentContainerStyle={styles.list} + renderItem={renderOffer} + columnWrapperStyle={styles.columnWrapper} + onRefresh={loadOffers} + refreshing={loading} + /> ); } const styles = StyleSheet.create({ container: { + flex: 1, + backgroundColor: Colors.bgColor, + paddingLeft: 5, + }, + title: { + fontSize: FontSizes.s1.size, + color: Colors.textMain, + paddingHorizontal: 15, + }, + list: { + paddingHorizontal: 10, + paddingBottom: 20, + }, + columnWrapper: { + justifyContent: 'space-between', + paddingHorizontal: 10, + }, + loaderContainer: { flex: 1, justifyContent: 'center', alignItems: 'center', From b14d0b5608fc319be8ee20e722e2e5d960f35e88 Mon Sep 17 00:00:00 2001 From: Mvjtb <1001.25854794.ucla@gmail.com> Date: Wed, 21 May 2025 10:49:06 -0400 Subject: [PATCH 03/12] Fix PaymentInformationService to return response directly instead of wrapping it in an array --- src/services/paymentInformation.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/services/paymentInformation.ts b/src/services/paymentInformation.ts index bc627ae..850c59d 100644 --- a/src/services/paymentInformation.ts +++ b/src/services/paymentInformation.ts @@ -17,7 +17,7 @@ export const PaymentInformationService = { const response = await api.paymentInformation.findAll(paymentMethod); // La respuesta ahora es directamente un array de PaymentInfoResponse - return [response]; + return response; } catch (error) { console.error('Error en PaymentInformationService.findAll:', error); throw new Error(extractErrorMessage(error)); From f3900086c17d616d9c1ee6c5d9d8bcebc4e25c8d Mon Sep 17 00:00:00 2001 From: Mvjtb <1001.25854794.ucla@gmail.com> Date: Wed, 21 May 2025 10:54:42 -0400 Subject: [PATCH 04/12] Refactor PaymentInformationService to return response directly without wrapping in an array --- src/services/paymentInformation.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/services/paymentInformation.ts b/src/services/paymentInformation.ts index 850c59d..4ed9e2a 100644 --- a/src/services/paymentInformation.ts +++ b/src/services/paymentInformation.ts @@ -13,11 +13,11 @@ export const PaymentInformationService = { throw new Error('Token de autenticación no encontrado'); } - // Llamar al método findAll con el argumento directamente + // Call the API method const response = await api.paymentInformation.findAll(paymentMethod); - // La respuesta ahora es directamente un array de PaymentInfoResponse - return response; + // Ensure the response is an array + return Array.isArray(response) ? response : [response]; } catch (error) { console.error('Error en PaymentInformationService.findAll:', error); throw new Error(extractErrorMessage(error)); From 008db41373c280337b1c0cf240edba9aedafb950 Mon Sep 17 00:00:00 2001 From: Mvjtb <1001.25854794.ucla@gmail.com> Date: Wed, 21 May 2025 16:06:18 -0400 Subject: [PATCH 05/12] Refactor product and offer handling to utilize promo discounts and update component structure --- src/components/Card.tsx | 26 ++--- src/components/OfferCardProps.tsx | 171 ------------------------------ src/screens/tab/HomeScreen.tsx | 134 +++++++++++++---------- src/screens/tab/OffersScreen.tsx | 95 +++++++++++++---- src/types/Product.ts | 4 +- 5 files changed, 165 insertions(+), 265 deletions(-) delete mode 100644 src/components/OfferCardProps.tsx diff --git a/src/components/Card.tsx b/src/components/Card.tsx index 4c6a046..07fc9a6 100644 --- a/src/components/Card.tsx +++ b/src/components/Card.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { View, StyleSheet, Image, TouchableOpacity } from 'react-native'; +import { View, StyleSheet, Image, TouchableOpacity, Text } from 'react-native'; import { Colors, FontSizes } from '../styles/theme'; import PoppinsText from './PoppinsText'; import CardButton from './CardButton'; @@ -15,13 +15,17 @@ const ProductCard: React.FC = ({ imageUrl, name, category, - //originalPrice, - //discount, + originalPrice, + discount, finalPrice, getQuantity, }) => { const { getItemQuantity, updateCartQuantity } = useCart(); const router = useRouter(); + const computedFinalPrice = discount + ? (finalPrice * (100 - discount)) / 100 + : finalPrice; + return ( @@ -66,22 +70,20 @@ const ProductCard: React.FC = ({ {truncateString(name)} - {/** {discount && ( - ${originalPrice} + ${originalPrice} + + + {discount}% - {discount}% // Comentado - )}*/} + )} - ${finalPrice} + ${computedFinalPrice} diff --git a/src/components/OfferCardProps.tsx b/src/components/OfferCardProps.tsx deleted file mode 100644 index 8d5d0f5..0000000 --- a/src/components/OfferCardProps.tsx +++ /dev/null @@ -1,171 +0,0 @@ -// src/components/OfferCard.tsx - -import React from 'react'; -import { View, StyleSheet, Image, TouchableOpacity } from 'react-native'; -import { Colors, FontSizes } from '../styles/theme'; -import PoppinsText from './PoppinsText'; -import CardButton from './CardButton'; -import { truncateString } from '../utils/commons'; -import { useCart } from '../hooks/useCart'; -import { useRouter } from 'expo-router'; - -export interface OfferCardProps { - id: string; - presentationId: string; - productId: string; - imageUrl: string; - name: string; - category: string; - originalPrice: number; - discount: number; - finalPrice: number; - getQuantity?: (qty: number) => void; -} - -const OfferCard: React.FC = ({ - id, - presentationId, - productId, - imageUrl, - name, - category, - originalPrice, - discount, - finalPrice, - getQuantity, -}) => { - const { getItemQuantity, updateCartQuantity } = useCart(); - const router = useRouter(); - - return ( - - router.push(`/products/${productId}/presentation/${presentationId}`) - } - > - - - - {category} - - - - - { - getQuantity?.(qty); - updateCartQuantity(id, qty); - }} - initialValue={getItemQuantity(id)} - /> - - - - - - {truncateString(name, 25)} - - - - ${originalPrice.toFixed(2)} - - - {discount.toFixed(0)}% - - - - ${finalPrice.toFixed(2)} - - - - - ); -}; - -const styles = StyleSheet.create({ - card: { - margin: 10, - backgroundColor: Colors.menuWhite, - borderWidth: 1, - borderColor: Colors.gray_100, - borderRadius: 15, - padding: 10, - paddingBottom: 5, - marginBottom: 16, - maxWidth: 140, - minWidth: 140, - minHeight: 315, - maxHeight: 315, - }, - imageTagWrapper: { - width: '100%', - height: 135, - marginBottom: 30, - }, - tagRow: { - width: '100%', - flexDirection: 'row-reverse', - }, - tag: { - backgroundColor: Colors.semanticInfo, - borderRadius: 50, - paddingHorizontal: 9, - color: Colors.textWhite, - fontSize: FontSizes.c3.size, - maxWidth: '100%', - }, - imageContainer: { - minHeight: 118, - maxHeight: 118, - maxWidth: 114, - borderRadius: 15, - marginVertical: 8, - }, - image: { - width: 114, - height: 118, - borderRadius: 14, - }, - cardButtonContainer: { - position: 'relative', - top: -30, - left: 58, - maxWidth: '65%', - alignItems: 'flex-end', - zIndex: 999, - }, - description: { - marginHorizontal: 2, - justifyContent: 'center', - }, - name: { - fontSize: FontSizes.s2.size, - marginBottom: 6, - minHeight: 70, - maxHeight: 70, - }, - priceContainer: { - flexDirection: 'row', - alignItems: 'center', - }, - originalPrice: { - fontSize: FontSizes.b1.size, - color: Colors.disableText, - textDecorationLine: 'line-through', - marginRight: 14, - }, - discountBadge: { - fontSize: FontSizes.c1.size, - backgroundColor: Colors.semanticInfo, - borderRadius: 4, - paddingHorizontal: 4, - color: Colors.textWhite, - }, - finalPrice: { - fontSize: FontSizes.s1.size, - fontWeight: 'medium', - color: Colors.textMain, - }, -}); - -export default OfferCard; diff --git a/src/screens/tab/HomeScreen.tsx b/src/screens/tab/HomeScreen.tsx index ad91bf1..395e00b 100644 --- a/src/screens/tab/HomeScreen.tsx +++ b/src/screens/tab/HomeScreen.tsx @@ -8,6 +8,7 @@ import { Colors, FontSizes } from '../../styles/theme'; import Carousel from '../../components/Carousel'; import { ProductService } from '../../services/products'; import { Product } from '../../types/Product'; +import type { Promo } from '@pharmatech/sdk'; import EmailVerificationModal from './EmailVerificationModal'; import { decodeJWT } from '../../helper/jwtHelper'; @@ -63,7 +64,76 @@ export default function HomeScreen() { const productsData = await ProductService.getProducts(1, 20); if (productsData.success) { const pd = productsData.data.results; - const carouselProducts = pd.map((p) => ({ + const carouselProducts = pd.map((p) => { + // Usa el descuento real si hay promo, si no, no lo agregues + // @ts-expect-error: promo puede estar en p o en p.presentation + const promo: Promo | undefined = p.promo ?? p.presentation.promo; + const discount: Promo['discount'] | undefined = promo?.discount + ? Math.round(promo.discount * 100) / 100 + : undefined; + return { + id: p.id, + presentationId: p.presentation.id, + productId: p.product.id, + imageUrl: p.product.images[0].url, + name: + p.product.name + + ' ' + + p.presentation.name + + ' ' + + p.presentation.quantity + + ' ' + + p.presentation.measurementUnit, + category: p.product.categories[0].name, + originalPrice: p.price, + ...(discount !== undefined ? { discount } : {}), + finalPrice: p.price, + quantity: getItemQuantity(p.id), + getQuantity: (quantity: number) => { + addToCart({ + id: p.id, + name: + p.product.name + + ' ' + + p.presentation.name + + ' ' + + p.presentation.quantity + + ' ' + + p.presentation.measurementUnit, + price: p.price, + quantity, + image: + p.product.images?.[0]?.url || + 'https://via.placeholder.com/150', + }); + updateCartQuantity(p.id, quantity); + }, + }; + }); + + setProducts(carouselProducts); + } else { + console.log(productsData.error); + } + } catch (error) { + console.error('Error fetching products:', error); + } finally { + setLoading(false); + } + }; + + const obtainRecommendedProducts = async () => { + setLoadingRecommendations(true); + try { + const recommendations = await ProductService.getRecommendations(); + const carouselRecommendations = recommendations.results.map((p) => { + // Usa el descuento real si hay promo, si no, no lo agregues + // @ts-expect-error: promo puede estar en p o en p.presentation + const promo: Promo | undefined = p.promo ?? p.presentation.promo; + const discount: Promo['discount'] | undefined = promo?.discount + ? Math.round(promo.discount * 100) / 100 + : undefined; + return { id: p.id, presentationId: p.presentation.id, productId: p.product.id, @@ -76,9 +146,9 @@ export default function HomeScreen() { p.presentation.quantity + ' ' + p.presentation.measurementUnit, - category: p.product.categories[0].name, - //originalPrice: p.price, - //discount: 10, + category: p.product.categories[0]?.name || 'Sin categoría', + originalPrice: p.price, + ...(discount !== undefined ? { discount } : {}), finalPrice: p.price, quantity: getItemQuantity(p.id), getQuantity: (quantity: number) => { @@ -99,60 +169,8 @@ export default function HomeScreen() { }); updateCartQuantity(p.id, quantity); }, - })); - - setProducts(carouselProducts); - } else { - console.log(productsData.error); - } - } catch (error) { - console.error('Error fetching products:', error); - } finally { - setLoading(false); - } - }; - - const obtainRecommendedProducts = async () => { - setLoadingRecommendations(true); - try { - const recommendations = await ProductService.getRecommendations(); - const carouselRecommendations = recommendations.results.map((p) => ({ - id: p.id, - presentationId: p.presentation.id, - productId: p.product.id, - imageUrl: p.product.images[0].url, - name: - p.product.name + - ' ' + - p.presentation.name + - ' ' + - p.presentation.quantity + - ' ' + - p.presentation.measurementUnit, - category: p.product.categories[0]?.name || 'Sin categoría', - //originalPrice: p.price, - //discount: 10, - finalPrice: p.price, - quantity: getItemQuantity(p.id), - getQuantity: (quantity: number) => { - addToCart({ - id: p.id, - name: - p.product.name + - ' ' + - p.presentation.name + - ' ' + - p.presentation.quantity + - ' ' + - p.presentation.measurementUnit, - price: p.price, - quantity, - image: - p.product.images?.[0]?.url || 'https://via.placeholder.com/150', - }); - updateCartQuantity(p.id, quantity); - }, - })); + }; + }); setRecommendedProducts(carouselRecommendations); } catch (error) { diff --git a/src/screens/tab/OffersScreen.tsx b/src/screens/tab/OffersScreen.tsx index 0be6b3c..ea647a3 100644 --- a/src/screens/tab/OffersScreen.tsx +++ b/src/screens/tab/OffersScreen.tsx @@ -9,28 +9,69 @@ import { } from 'react-native'; import PoppinsText from '../../components/PoppinsText'; import { Colors, FontSizes } from '../../styles/theme'; -import OfferCard from '../../components/OfferCardProps'; +import Card from '../../components/Card'; import { ProductService } from '../../services/products'; -import { ProductPresentation } from '../../types/api.d'; +import type { Promo } from '@pharmatech/sdk'; export default function OffersScreen() { - const [offers, setOffers] = useState([]); + const [offers, setOffers] = useState< + Array<{ + id: string; + presentationId: string; + productId: string; + imageUrl: string; + name: string; + category: string; + originalPrice: number; + discount: number; + finalPrice: number; + quantity: number; + getQuantity: (quantity: number) => void; + }> + >([]); const [loading, setLoading] = useState(false); const loadOffers = async () => { setLoading(true); const res = await ProductService.getProducts(1, 50); if (res.success) { + // Solo productos con promo const promoItems = res.data.results.filter((item) => - // puede venir en item.promo o dentro de item.presentation.promo Boolean( - // @ts-expect-error - promo puede estar en item o en item.presentation item.promo?.discount ?? // @ts-expect-error - promo puede estar en item o en item.presentation item.presentation.promo?.discount, ), ); - setOffers(promoItems); + // Mapear la info igual que en HomeScreen + const mappedPromoItems = promoItems.map((p) => { + // @ts-expect-error: promo puede estar en p o en p.presentation + const promo: Promo | undefined = p.promo ?? p.presentation.promo; + const discount: Promo['discount'] = promo?.discount ?? 0; + // No calcular el finalPrice aquí + return { + id: p.id, + presentationId: p.presentation.id, + productId: p.product.id, + imageUrl: + p.product.images?.[0]?.url || 'https://via.placeholder.com/150', + name: + p.product.name + + ' ' + + p.presentation.name + + ' ' + + p.presentation.quantity + + ' ' + + p.presentation.measurementUnit, + category: p.product.categories?.[0]?.name || 'Sin categoría', + originalPrice: p.price, + discount, + finalPrice: p.price, + quantity: 0, + getQuantity: () => 0, + }; + }); + setOffers(mappedPromoItems); } else { console.error('Error al cargar productos:', res.error); } @@ -49,24 +90,34 @@ export default function OffersScreen() { ); } - const renderOffer = ({ item }: ListRenderItemInfo) => { - // @ts-expect-error: asumimos que existe promo en alguna de estas rutas - const promo = item.promo ?? item.presentation.promo; - const discount = promo?.discount ?? 0; - const finalPrice = item.price * (1 - discount / 100); - + const renderOffer = ({ + item, + }: ListRenderItemInfo<{ + id: string; + presentationId: string; + productId: string; + imageUrl: string; + name: string; + category: string; + originalPrice: number; + discount: number; + finalPrice: number; + quantity: number; + getQuantity: (quantity: number) => void; + }>) => { return ( - {}} + presentationId={item.presentationId} + productId={item.productId} + imageUrl={item.imageUrl} + name={item.name} + category={item.category} + originalPrice={item.originalPrice} + discount={item.discount} + finalPrice={item.finalPrice} + quantity={item.quantity} + getQuantity={item.getQuantity} /> ); }; diff --git a/src/types/Product.ts b/src/types/Product.ts index b037903..a759249 100644 --- a/src/types/Product.ts +++ b/src/types/Product.ts @@ -5,8 +5,8 @@ export type Product = { imageUrl: string; name: string; category: string; - //originalPrice: number; - //discount: number; + originalPrice?: number; + discount?: number; finalPrice: number; quantity: number; getQuantity: (quantity: number) => void; From c832eb597727c286d41b1b26d1b9ca6e6c1a206a Mon Sep 17 00:00:00 2001 From: Mvjtb <1001.25854794.ucla@gmail.com> Date: Wed, 21 May 2025 16:36:56 -0400 Subject: [PATCH 06/12] Refactor --- src/components/Card.tsx | 10 ++++------ src/screens/tab/OffersScreen.tsx | 18 +++++++++++------- 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/src/components/Card.tsx b/src/components/Card.tsx index 07fc9a6..6f85d63 100644 --- a/src/components/Card.tsx +++ b/src/components/Card.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { View, StyleSheet, Image, TouchableOpacity, Text } from 'react-native'; +import { View, StyleSheet, Image, TouchableOpacity } from 'react-native'; import { Colors, FontSizes } from '../styles/theme'; import PoppinsText from './PoppinsText'; import CardButton from './CardButton'; @@ -73,17 +73,15 @@ const ProductCard: React.FC = ({ {discount && ( - ${originalPrice} - - - {discount}% + ${originalPrice} + {discount}% )} - ${computedFinalPrice} + ${computedFinalPrice} diff --git a/src/screens/tab/OffersScreen.tsx b/src/screens/tab/OffersScreen.tsx index ea647a3..0a66b0f 100644 --- a/src/screens/tab/OffersScreen.tsx +++ b/src/screens/tab/OffersScreen.tsx @@ -14,6 +14,12 @@ import { ProductService } from '../../services/products'; import type { Promo } from '@pharmatech/sdk'; export default function OffersScreen() { + type PresentationWithPromo = typeof ProductService.getProducts extends ( + ...args: unknown[] + ) => Promise<{ data: { results: Array } }> + ? { promo?: Promo } + : { promo?: Promo }; + const [offers, setOffers] = useState< Array<{ id: string; @@ -35,20 +41,18 @@ export default function OffersScreen() { setLoading(true); const res = await ProductService.getProducts(1, 50); if (res.success) { - // Solo productos con promo const promoItems = res.data.results.filter((item) => Boolean( item.promo?.discount ?? - // @ts-expect-error - promo puede estar en item o en item.presentation - item.presentation.promo?.discount, + (item.presentation as PresentationWithPromo)?.promo?.discount, ), ); - // Mapear la info igual que en HomeScreen + const mappedPromoItems = promoItems.map((p) => { - // @ts-expect-error: promo puede estar en p o en p.presentation - const promo: Promo | undefined = p.promo ?? p.presentation.promo; + const promo: Promo | undefined = + p.promo ?? (p.presentation as PresentationWithPromo)?.promo; const discount: Promo['discount'] = promo?.discount ?? 0; - // No calcular el finalPrice aquí + return { id: p.id, presentationId: p.presentation.id, From 5cba7713fd1162c75f8c50fb09ce16c8468c1644 Mon Sep 17 00:00:00 2001 From: Mvjtb <1001.25854794.ucla@gmail.com> Date: Wed, 21 May 2025 16:42:26 -0400 Subject: [PATCH 07/12] Add a type guard hasPromo to safely check for the promo --- src/screens/tab/OffersScreen.tsx | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/src/screens/tab/OffersScreen.tsx b/src/screens/tab/OffersScreen.tsx index 0a66b0f..0bd9b5f 100644 --- a/src/screens/tab/OffersScreen.tsx +++ b/src/screens/tab/OffersScreen.tsx @@ -14,12 +14,6 @@ import { ProductService } from '../../services/products'; import type { Promo } from '@pharmatech/sdk'; export default function OffersScreen() { - type PresentationWithPromo = typeof ProductService.getProducts extends ( - ...args: unknown[] - ) => Promise<{ data: { results: Array } }> - ? { promo?: Promo } - : { promo?: Promo }; - const [offers, setOffers] = useState< Array<{ id: string; @@ -37,20 +31,22 @@ export default function OffersScreen() { >([]); const [loading, setLoading] = useState(false); + function hasPromo(item: { + [key: string]: unknown; + }): item is { promo?: Promo } { + return 'promo' in item && typeof item.promo !== 'undefined'; + } + const loadOffers = async () => { setLoading(true); const res = await ProductService.getProducts(1, 50); if (res.success) { - const promoItems = res.data.results.filter((item) => - Boolean( - item.promo?.discount ?? - (item.presentation as PresentationWithPromo)?.promo?.discount, - ), + const promoItems = res.data.results.filter( + (item) => hasPromo(item) && Boolean(item.promo?.discount), ); const mappedPromoItems = promoItems.map((p) => { - const promo: Promo | undefined = - p.promo ?? (p.presentation as PresentationWithPromo)?.promo; + const promo: Promo | undefined = hasPromo(p) ? p.promo : undefined; const discount: Promo['discount'] = promo?.discount ?? 0; return { From 7a8385cd9f1843f0174024643a898c69b04fadea Mon Sep 17 00:00:00 2001 From: Mvjtb <1001.25854794.ucla@gmail.com> Date: Fri, 23 May 2025 12:47:24 -0400 Subject: [PATCH 08/12] Enhance cart functionality to support discounts --- src/components/Card.tsx | 12 ++++++-- src/components/OrderSummary.tsx | 46 +++++++++++++++++----------- src/redux/slices/cartSlice.ts | 2 ++ src/screens/CartListScreen.tsx | 53 +++++++++++++++++++-------------- 4 files changed, 71 insertions(+), 42 deletions(-) diff --git a/src/components/Card.tsx b/src/components/Card.tsx index 6f85d63..d44247c 100644 --- a/src/components/Card.tsx +++ b/src/components/Card.tsx @@ -20,7 +20,7 @@ const ProductCard: React.FC = ({ finalPrice, getQuantity, }) => { - const { getItemQuantity, updateCartQuantity } = useCart(); + const { getItemQuantity, addToCart } = useCart(); const router = useRouter(); const computedFinalPrice = discount ? (finalPrice * (100 - discount)) / 100 @@ -61,7 +61,15 @@ const ProductCard: React.FC = ({ { if (getQuantity) getQuantity(quantity); - updateCartQuantity(id, quantity); + // Asegura que price siempre sea number + addToCart({ + id, + name, + price: originalPrice ?? 0, + quantity, + image: imageUrl, + discount: discount ?? 0, + }); }} initialValue={getItemQuantity(id)} /> diff --git a/src/components/OrderSummary.tsx b/src/components/OrderSummary.tsx index 978f236..45e56f4 100644 --- a/src/components/OrderSummary.tsx +++ b/src/components/OrderSummary.tsx @@ -14,26 +14,29 @@ const OrderSummary = () => { (sum, item) => sum + item.price * item.quantity, 0, ); - // const totalDiscount = cartItems.reduce( - // (sum, item) => sum + item.price * (item.quantity * 0.1), // Comentado - // 0, - // ); + const totalDiscount = cartItems.reduce( + (sum, item) => + sum + item.price * item.quantity * ((item.discount ?? 0) / 100), + 0, + ); + const total = subtotal - totalDiscount; const renderItem = ({ item }: { item: CartItem }) => { - // const discount = 10; // Comentado - // const discountedPrice = item.price * (1 - discount / 100); // Comentado - // const totalDiscountedPrice = discountedPrice * item.quantity; // Comentado + const discount = item.discount ?? 0; + const discountedPrice = item.price * (1 - discount / 100); + const totalDiscountedPrice = discountedPrice * item.quantity; const totalOriginalPrice = item.price * item.quantity; return ( - {/** - - - -10% - - */} + {discount > 0 && ( + + + -{discount}% + + + )} { {item.name} - ${totalOriginalPrice.toFixed(2)} + ${totalDiscountedPrice.toFixed(2)} - {/** ${totalOriginalPrice.toFixed(2)} - */} + - (${item.price} c/u) + (${discountedPrice.toFixed(2)} c/u) Cantidad: {item.quantity} @@ -107,7 +109,15 @@ const OrderSummary = () => { Descuentos - -$0.00 + + -${totalDiscount.toFixed(2)} + + + + Total + + ${total.toFixed(2)} + diff --git a/src/redux/slices/cartSlice.ts b/src/redux/slices/cartSlice.ts index 26183bc..9505d0d 100644 --- a/src/redux/slices/cartSlice.ts +++ b/src/redux/slices/cartSlice.ts @@ -6,6 +6,7 @@ export type CartItem = { price: number; quantity: number; image: string; + discount?: number; }; type CartState = { @@ -40,6 +41,7 @@ const cartSlice = createSlice({ ); if (existingItem) { existingItem.quantity = action.payload.quantity; + existingItem.discount = action.payload.discount; } else { state.items.push(action.payload); } diff --git a/src/screens/CartListScreen.tsx b/src/screens/CartListScreen.tsx index c2151aa..4ae6477 100644 --- a/src/screens/CartListScreen.tsx +++ b/src/screens/CartListScreen.tsx @@ -22,28 +22,33 @@ const CartListScreen = () => { const subtotal = cartItems.reduce( (sum, item) => sum + item.price * item.quantity, 0, - ); // Total price sum - // const totalDiscount = cartItems.reduce( - // (sum, item) => sum + item.price * (item.quantity * 0.1), // Comentado - // 0, - // ); // Discount sum - const total = subtotal; // Subtotal without discount + ); + // Total price sum + const totalDiscount = cartItems.reduce( + (sum, item) => + sum + item.price * item.quantity * ((item.discount ?? 0) / 100), + 0, + ); // Discount sum + const total = subtotal - totalDiscount; // Subtotal with discount const renderItem = ({ item }: { item: CartItem }) => { - // const discount = 10; // Comentado - // const discountedPrice = item.price * (1 - discount / 100); // Comentado - // const totalDiscountedPrice = discountedPrice * item.quantity; // Comentado - const totalOriginalPrice = item.price * item.quantity; // Original total price + // Usar el descuento del item, si existe, si no 0 + const discount = item.discount ?? 0; + const discountedPrice = item.price * (1 - discount / 100); + const totalDiscountedPrice = discountedPrice * item.quantity; + const totalOriginalPrice = item.price * item.quantity; return ( {/* Discount badge */} - {/* - - -{discount}% - - */} + {discount > 0 && ( + + + -{discount}% + + + )} { {item.name} - - ${totalOriginalPrice.toFixed(2)} - {/* Original total price */} - + + + ${totalDiscountedPrice.toFixed(2)} + + + ${totalOriginalPrice.toFixed(2)} + + - (${item.price} c/u) + (${discountedPrice.toFixed(2)} c/u) { ${subtotal.toFixed(2)} - {/* + Descuentos -${totalDiscount.toFixed(2)} - */} + Total From 3df3a68405f70848202d6da6d49b630bedc02038 Mon Sep 17 00:00:00 2001 From: miglesiasDev <1001.28391672.ucla@gmail.com> Date: Mon, 26 May 2025 20:57:41 -0400 Subject: [PATCH 09/12] Fixing the way discounts worked in cartlist coming from productDetails --- package-lock.json | 8 +-- package.json | 6 +-- src/components/CardButton.tsx | 7 ++- src/hooks/useCart.ts | 20 +++++++- src/redux/slices/cartSlice.ts | 25 ++++++--- src/screens/CartListScreen.tsx | 17 +++++-- src/screens/ProductDetailScreen.tsx | 79 ++++++++++++++++++++++++----- src/screens/tab/HomeScreen.tsx | 33 +----------- src/screens/tab/OffersScreen.tsx | 7 ++- 9 files changed, 132 insertions(+), 70 deletions(-) diff --git a/package-lock.json b/package-lock.json index 95c5fb0..ac7f9d9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,7 @@ "version": "0.2.0", "dependencies": { "@expo-google-fonts/poppins": "^0.2.3", - "@pharmatech/sdk": "^0.4.11", + "@pharmatech/sdk": "^0.4.19", "@ptomasroos/react-native-multi-slider": "^2.2.2", "@react-native-community/slider": "^4.5.6", "@reduxjs/toolkit": "^2.6.1", @@ -3592,9 +3592,9 @@ } }, "node_modules/@pharmatech/sdk": { - "version": "0.4.11", - "resolved": "https://registry.npmjs.org/@pharmatech/sdk/-/sdk-0.4.11.tgz", - "integrity": "sha512-iJChZa3uTsQrz6kLZJfnwrBTZOe2o6azPlwk/ByiMxA5V7UlPfVp8sDz4EIppYbBl2+uM4sLMGnQmiWatKEECA==", + "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" diff --git a/package.json b/package.json index 62c765a..bd58b0d 100644 --- a/package.json +++ b/package.json @@ -19,13 +19,14 @@ }, "dependencies": { "@expo-google-fonts/poppins": "^0.2.3", - "@pharmatech/sdk": "^0.4.11", + "@pharmatech/sdk": "^0.4.19", "@ptomasroos/react-native-multi-slider": "^2.2.2", "@react-native-community/slider": "^4.5.6", "@reduxjs/toolkit": "^2.6.1", "date-fns": "^4.1.0", "expo": "~52.0.37", "expo-constants": "~17.0.7", + "expo-dev-client": "~5.0.20", "expo-font": "~13.0.4", "expo-image-picker": "~16.0.6", "expo-linking": "~7.0.5", @@ -51,8 +52,7 @@ "react-native-svg": "^15.11.2", "react-redux": "^9.2.0", "socket.io-client": "^4.8.1", - "tailwindcss": "^3.4.17", - "expo-dev-client": "~5.0.20" + "tailwindcss": "^3.4.17" }, "devDependencies": { "@babel/core": "^7.25.2", diff --git a/src/components/CardButton.tsx b/src/components/CardButton.tsx index db632b2..ea62541 100644 --- a/src/components/CardButton.tsx +++ b/src/components/CardButton.tsx @@ -19,15 +19,16 @@ const CardButton: React.FC = ({ }) => { const [count, setCount] = useState(initialValue); const [showCounter, setShowCounter] = useState(false); + const [hasInteracted, setHasInteracted] = useState(false); const toggleCounter = () => setShowCounter(count > 0); useEffect(toggleCounter, [count]); useEffect(() => { - if (getValue) { + if (hasInteracted && getValue) { getValue(count); } - if (syncQuantity) { + if (hasInteracted && syncQuantity) { syncQuantity(count); } }, [count]); @@ -37,11 +38,13 @@ const CardButton: React.FC = ({ }, [initialValue]); const incrementCount = () => { + setHasInteracted(true); setCount((prev) => prev + 1); }; const decrementCount = () => { if (count > 0) { + setHasInteracted(true); setCount((prev) => prev - 1); } }; diff --git a/src/hooks/useCart.ts b/src/hooks/useCart.ts index 05eae89..6fd2a91 100644 --- a/src/hooks/useCart.ts +++ b/src/hooks/useCart.ts @@ -15,6 +15,8 @@ export const useCart = () => { const userId = useSelector((state: RootState) => state.cart.userId); const addToCart = (item: CartItem) => { + if (item.quantity <= 0) return; // <-- NO agregar si cantidad es 0 + console.log('[addToCart] Item añadido al carrito:', item); // <-- LOG dispatch(addItem(item)); }; @@ -22,8 +24,22 @@ export const useCart = () => { dispatch(removeItem(id)); }; - const updateCartQuantity = (id: string, quantity: number) => { - dispatch(updateQuantity({ id, quantity })); + const updateCartQuantity = ( + id: string, + quantity: number, + discount?: number, + price?: number, + ) => { + // Busca el item actual en el carrito + const item = cartItems.find((item) => item.id === id); + dispatch( + updateQuantity({ + id, + quantity, + discount: typeof discount === 'number' ? discount : item?.discount, + price: typeof price === 'number' ? price : item?.price, + }), + ); }; const getItemQuantity = (id: string) => { diff --git a/src/redux/slices/cartSlice.ts b/src/redux/slices/cartSlice.ts index 9505d0d..017b1d9 100644 --- a/src/redux/slices/cartSlice.ts +++ b/src/redux/slices/cartSlice.ts @@ -36,14 +36,16 @@ const cartSlice = createSlice({ } }, addItem: (state, action: PayloadAction) => { - const existingItem = state.items.find( + const existing = state.items.find( (item) => item.id === action.payload.id, ); - if (existingItem) { - existingItem.quantity = action.payload.quantity; - existingItem.discount = action.payload.discount; + if (existing) { + // Si ya existe, actualiza cantidad y descuento correctamente + existing.quantity = action.payload.quantity; + existing.price = action.payload.price; + existing.discount = action.payload.discount; // <-- ¡Asegúrate de actualizar el descuento! } else { - state.items.push(action.payload); + state.items.push({ ...action.payload }); } state.total = calculateTotal(state.items); }, @@ -53,11 +55,22 @@ const cartSlice = createSlice({ }, updateQuantity: ( state, - action: PayloadAction<{ id: string; quantity: number }>, + action: PayloadAction<{ + id: string; + quantity: number; + discount?: number; + price?: number; + }>, ) => { const item = state.items.find((item) => item.id === action.payload.id); if (item) { item.quantity = action.payload.quantity; + if (typeof action.payload.discount === 'number') { + item.discount = action.payload.discount; + } + if (typeof action.payload.price === 'number') { + item.price = action.payload.price; + } } state.total = calculateTotal(state.items); }, diff --git a/src/screens/CartListScreen.tsx b/src/screens/CartListScreen.tsx index 4ae6477..6937e9e 100644 --- a/src/screens/CartListScreen.tsx +++ b/src/screens/CartListScreen.tsx @@ -32,6 +32,7 @@ const CartListScreen = () => { const total = subtotal - totalDiscount; // Subtotal with discount const renderItem = ({ item }: { item: CartItem }) => { + console.log('[CartListScreen] Renderizando item:', item); // <-- LOG // Usar el descuento del item, si existe, si no 0 const discount = item.discount ?? 0; const discountedPrice = item.price * (1 - discount / 100); @@ -62,9 +63,11 @@ const CartListScreen = () => { ${totalDiscountedPrice.toFixed(2)} - - ${totalOriginalPrice.toFixed(2)} - + {discount > 0 && ( + + ${totalOriginalPrice.toFixed(2)} + + )} @@ -72,9 +75,13 @@ const CartListScreen = () => { updateCartQuantity(item.id, quantity)} + getValue={(quantity) => + updateCartQuantity(item.id, quantity, item.discount ?? 0) + } initialValue={item.quantity > 0 ? item.quantity : 0} - syncQuantity={(quantity) => updateCartQuantity(item.id, quantity)} + syncQuantity={(quantity) => + updateCartQuantity(item.id, quantity, item.discount ?? 0) + } /> removeFromCart(item.id)} diff --git a/src/screens/ProductDetailScreen.tsx b/src/screens/ProductDetailScreen.tsx index 2a9eaf2..ad23d49 100644 --- a/src/screens/ProductDetailScreen.tsx +++ b/src/screens/ProductDetailScreen.tsx @@ -291,7 +291,21 @@ const ProductDetailScreen: React.FC = () => { ); }; - console.log('Current inventory state:', inventory); // Log para inspeccionar el estado de inventory + const getProductDiscount = () => { + // Busca promo en product.promo + if (product?.promo && typeof product.promo.discount === 'number') { + return product.promo.discount; + } + return 0; + }; + + const getOriginalPrice = () => product?.price ?? 0; + const getDiscount = () => getProductDiscount(); + const getFinalPrice = () => { + const original = getOriginalPrice(); + const discount = getDiscount(); + return discount > 0 ? (original * (100 - discount)) / 100 : original; + }; return ( @@ -383,10 +397,24 @@ const ProductDetailScreen: React.FC = () => { {/* Información del producto */} - $ {product?.price} - {/* {discount && ( // Comentado */} - {/* -{discount}% */} - {/* )} */} + {product ? ( + getDiscount() > 0 ? ( + <> + + ${getFinalPrice()} + + + -{getDiscount()}% + + + ) : ( + + ${getOriginalPrice()} + + ) + ) : ( + + )} { { - if (product?.id) updateCartQuantity(product.id, quantity); + if (product?.id && quantity > 0) { + const promo = product.promo; + const discount = + typeof promo?.discount === 'number' ? promo.discount : 0; + addToCart({ + id: product.id, + name: + product.product.name + + ' ' + + product.presentation.name + + ' ' + + product.presentation.quantity + + ' ' + + product.presentation.measurementUnit, + price: product.price, + quantity, + image: images?.[0]?.url || 'https://via.placeholder.com/150', + discount, + }); + } }} initialValue={getQuantity()} /> @@ -636,6 +683,20 @@ const styles = StyleSheet.create({ color: Colors.primary, marginRight: 10, }, + originalPrice: { + fontSize: FontSizes.b1.size, + color: Colors.disableText, + textDecorationLine: 'line-through', + marginRight: 14, + }, + discountBadge: { + fontSize: FontSizes.c1.size, + backgroundColor: Colors.semanticInfo, + borderRadius: 5, + padding: 4, + color: Colors.textWhite, + marginRight: 10, + }, productName: { textAlign: 'center', fontSize: FontSizes.h5.size, @@ -698,12 +759,6 @@ const styles = StyleSheet.create({ alignItems: 'flex-end', zIndex: 999, }, - discount: { - fontSize: FontSizes.b4.size, - backgroundColor: Colors.semanticInfo, - borderRadius: 5, - padding: 4, - }, }); export default ProductDetailScreen; diff --git a/src/screens/tab/HomeScreen.tsx b/src/screens/tab/HomeScreen.tsx index 395e00b..5465fa3 100644 --- a/src/screens/tab/HomeScreen.tsx +++ b/src/screens/tab/HomeScreen.tsx @@ -21,7 +21,7 @@ export default function HomeScreen() { const router = useRouter(); const { showEmailVerification: showEmailVerificationParam } = useLocalSearchParams(); - const { cartItems, addToCart, updateCartQuantity, setCartUserId } = useCart(); + const { cartItems, updateCartQuantity, setCartUserId } = useCart(); const getItemQuantity = (productId: string) => { const cartItem = cartItems.find((item) => item.id === productId.toString()); @@ -90,22 +90,6 @@ export default function HomeScreen() { finalPrice: p.price, quantity: getItemQuantity(p.id), getQuantity: (quantity: number) => { - addToCart({ - id: p.id, - name: - p.product.name + - ' ' + - p.presentation.name + - ' ' + - p.presentation.quantity + - ' ' + - p.presentation.measurementUnit, - price: p.price, - quantity, - image: - p.product.images?.[0]?.url || - 'https://via.placeholder.com/150', - }); updateCartQuantity(p.id, quantity); }, }; @@ -152,21 +136,6 @@ export default function HomeScreen() { finalPrice: p.price, quantity: getItemQuantity(p.id), getQuantity: (quantity: number) => { - addToCart({ - id: p.id, - name: - p.product.name + - ' ' + - p.presentation.name + - ' ' + - p.presentation.quantity + - ' ' + - p.presentation.measurementUnit, - price: p.price, - quantity, - image: - p.product.images?.[0]?.url || 'https://via.placeholder.com/150', - }); updateCartQuantity(p.id, quantity); }, }; diff --git a/src/screens/tab/OffersScreen.tsx b/src/screens/tab/OffersScreen.tsx index 0bd9b5f..f94ce9b 100644 --- a/src/screens/tab/OffersScreen.tsx +++ b/src/screens/tab/OffersScreen.tsx @@ -1,4 +1,3 @@ -// src/screens/tab/OffersScreen.tsx import React, { useState, useEffect } from 'react'; import { View, @@ -145,7 +144,7 @@ const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: Colors.bgColor, - paddingLeft: 5, + paddingHorizontal: 5, }, title: { fontSize: FontSizes.s1.size, @@ -153,12 +152,12 @@ const styles = StyleSheet.create({ paddingHorizontal: 15, }, list: { - paddingHorizontal: 10, paddingBottom: 20, + justifyContent: 'center', + alignItems: 'center', }, columnWrapper: { justifyContent: 'space-between', - paddingHorizontal: 10, }, loaderContainer: { flex: 1, From bccfa052ff528314b1b3463a74933bfb779edd4c Mon Sep 17 00:00:00 2001 From: miglesiasDev <1001.28391672.ucla@gmail.com> Date: Tue, 27 May 2025 17:18:03 -0400 Subject: [PATCH 10/12] Fixing prices to work with cents --- src/components/Card.tsx | 5 +++-- src/screens/CartListScreen.tsx | 13 +++++++------ src/screens/OrdersScreen.tsx | 3 ++- src/screens/ProductDetailScreen.tsx | 5 +++-- src/utils/formatPrice.ts | 8 ++++++++ 5 files changed, 23 insertions(+), 11 deletions(-) create mode 100644 src/utils/formatPrice.ts diff --git a/src/components/Card.tsx b/src/components/Card.tsx index d44247c..8e492cb 100644 --- a/src/components/Card.tsx +++ b/src/components/Card.tsx @@ -7,6 +7,7 @@ import { truncateString } from '../utils/commons'; import { useCart } from '../hooks/useCart'; import { Product } from '../types/Product'; import { useRouter } from 'expo-router'; +import { formatPrice } from '../utils/formatPrice'; const ProductCard: React.FC = ({ id, @@ -81,7 +82,7 @@ const ProductCard: React.FC = ({ {discount && ( - ${originalPrice} + ${formatPrice(originalPrice ?? 0)} {discount}% @@ -89,7 +90,7 @@ const ProductCard: React.FC = ({ - ${computedFinalPrice} + ${formatPrice(computedFinalPrice)} diff --git a/src/screens/CartListScreen.tsx b/src/screens/CartListScreen.tsx index 6937e9e..89beb9f 100644 --- a/src/screens/CartListScreen.tsx +++ b/src/screens/CartListScreen.tsx @@ -14,6 +14,7 @@ import type { CartItem } from '../redux/slices/cartSlice'; import Button from '../components/Button'; import { TrashIcon } from 'react-native-heroicons/outline'; import { useRouter } from 'expo-router'; +import { formatPrice } from '../utils/formatPrice'; const CartListScreen = () => { const router = useRouter(); @@ -61,17 +62,17 @@ const CartListScreen = () => { {item.name} - ${totalDiscountedPrice.toFixed(2)} + ${formatPrice(totalDiscountedPrice)} {discount > 0 && ( - ${totalOriginalPrice.toFixed(2)} + ${formatPrice(totalOriginalPrice)} )} - (${discountedPrice.toFixed(2)} c/u) + (${formatPrice(discountedPrice)} c/u) { Subtotal - ${subtotal.toFixed(2)} + ${formatPrice(subtotal)} Descuentos - -${totalDiscount.toFixed(2)} + -${formatPrice(totalDiscount)} Total - ${total.toFixed(2)} + ${formatPrice(total)}