diff --git a/webapp/src/components/BottomNavigation.tsx b/webapp/src/components/BottomNavigation.tsx index e84827d7..3bccf2f1 100644 --- a/webapp/src/components/BottomNavigation.tsx +++ b/webapp/src/components/BottomNavigation.tsx @@ -34,7 +34,7 @@ const BottomNavigation = () => { const navigationItems = [ { - label: "Mes réductions", + label: "Mon portefeuille", icon: ReductionOutlineIcon, activeIcon: ReductionIcon, href: "/dashboard/wallet", diff --git a/webapp/src/components/wallet/Filter.tsx b/webapp/src/components/wallet/Filter.tsx new file mode 100644 index 00000000..93c819a4 --- /dev/null +++ b/webapp/src/components/wallet/Filter.tsx @@ -0,0 +1,55 @@ +import { Center, Box, Text } from "@chakra-ui/react"; +import { Dispatch, SetStateAction, ReactNode } from "react"; + +type WalletFilterProps = { + label: string; + slug: string; + filterSelected: string; + setFilterSelected: Dispatch>; + icon: ReactNode; + onClick?: () => void; + disabled?: boolean; +}; + +const WalletFilter = ({ + label, + icon, + filterSelected, + slug, + setFilterSelected, + disabled, + onClick, +}: WalletFilterProps) => { + const selected = filterSelected === slug; + + return ( +
!disabled && setFilterSelected(slug)} + > + + {icon} + + + {label} + +
+ ); +}; + +export default WalletFilter; diff --git a/webapp/src/components/wallet/HelpSection.tsx b/webapp/src/components/wallet/HelpSection.tsx new file mode 100644 index 00000000..e4b1affb --- /dev/null +++ b/webapp/src/components/wallet/HelpSection.tsx @@ -0,0 +1,42 @@ +import { Flex, Icon, Text } from "@chakra-ui/react"; +import { ReactNode } from "react"; +import { HiMiniTag } from "react-icons/hi2"; + +type HelpSectionProps = { + title: string; + icon: ReactNode; + iconName: string; + description: string; +}; + +const HelpSection = ({ + title, + icon, + iconName, + description, +}: HelpSectionProps) => { + return ( + + {title} + + {icon} + {iconName} + + + {description} + + + ); +}; + +export default HelpSection; diff --git a/webapp/src/components/wrappers/WalletWrapper.tsx b/webapp/src/components/wrappers/WalletWrapper.tsx index f63b43d2..c3eaf285 100644 --- a/webapp/src/components/wrappers/WalletWrapper.tsx +++ b/webapp/src/components/wrappers/WalletWrapper.tsx @@ -1,48 +1,219 @@ -import { Box, Button, Flex, Heading } from "@chakra-ui/react"; -import { ReactNode } from "react"; +import { + Box, + Center, + Divider, + Flex, + Heading, + Icon, + Modal, + ModalBody, + ModalContent, + Text, + useDisclosure, + Link, +} from "@chakra-ui/react"; +import { Dispatch, ReactNode, SetStateAction } from "react"; import { PassIcon } from "../icons/pass"; import { useRouter } from "next/router"; +import Image from "../ui/Image"; +import { + HiChevronRight, + HiMiniInformationCircle, + HiMiniTag, + HiMiniTicket, + HiReceiptRefund, + HiRectangleGroup, + HiXMark, +} from "react-icons/hi2"; +import { BarcodeIcon } from "../icons/barcode"; +import { api } from "~/utils/api"; +import BackButton from "../ui/BackButton"; +import WalletFilter from "../wallet/Filter"; +import HelpSection from "../wallet/HelpSection"; +import NextLink from "next/link"; + +const helpItems = [ + { + title: "Quand ce sont des codes", + icon: , + iconName: "Code", + description: + "Le montant que vous économisez grâce aux codes de réduction ne peut as toujours être calculé et ajouté à votre somme d’économies. Par exemple si le code vous offre -10% sur votre commande, l’application carte “jeune engagé” ne peut savoir combien vous avez dépensé dans votre commande et donc combien représentent les 10% d’économies finales.", + }, + { + title: "Quand ce sont des bons d’achat", + icon: , + iconName: "Bons d’achat", + description: + "Pour les bons d’achat calculons les économies que vous réalisez directement au moment où vous achetez votre bon d’achat. Par exemple si vous achetez un bon d’une valeur de 10€ avec 10% de réduction, l’application carte “jeune engagé” vous affiche 1€ d’économie réalisée.", + }, + { + title: "Quand ce sont des billets", + icon: , + iconName: "Billets", + description: + "Pour les billets nous calculons directement les économies que vous faite grâce à la différence entre le prix public qui est affiché sur l’offre et le prix réel avec la réduction qui vous est proposé sur la carte “jeune engagé”.", + }, +]; type WalletWrapperProps = { children: ReactNode; + filterSelected: string; + setFilterSelected: Dispatch>; }; -const WalletWrapper = ({ children }: WalletWrapperProps) => { +const WalletWrapper = ({ + children, + filterSelected, + setFilterSelected, +}: WalletWrapperProps) => { const router = useRouter(); + const { + isOpen: isOpenInfoModal, + onOpen: onOpenInfoModal, + onClose: onCloseInfoModal, + } = useDisclosure(); + + const { data: resultTotalAmount } = + api.saving.getTotalAmountByCurrentUser.useQuery(); + const { data: userTotalAmount } = resultTotalAmount || { data: 0 }; return ( - - - - - Mes Réductions - - - {children} - + <> + +
+ + + } + filterSelected={filterSelected} + setFilterSelected={setFilterSelected} + onClick={() => router.push("/dashboard/account")} + /> + + Logo + + Vous avez économisé + + + {userTotalAmount}€ + +
+ + + } + filterSelected={filterSelected} + setFilterSelected={setFilterSelected} + /> + } + filterSelected={filterSelected} + setFilterSelected={setFilterSelected} + /> + } + filterSelected={filterSelected} + setFilterSelected={setFilterSelected} + /> + + } + filterSelected={filterSelected} + setFilterSelected={setFilterSelected} + disabled + /> + + + {children} + +
-
+ + + + + + + + Comment sont calculées mes économies ? + + + Vos économies sont calculées à partir des offres que vous avez + utilisées. + + + + + + + Historique des réductions + + + + + + {helpItems.map(({ title, icon, iconName, description }, index) => ( + <> + + {index !== helpItems.length - 1 && ( + + )} + + ))} + + + + ); }; diff --git a/webapp/src/pages/dashboard/account/history.tsx b/webapp/src/pages/dashboard/account/history.tsx index c226231d..2e0912ad 100644 --- a/webapp/src/pages/dashboard/account/history.tsx +++ b/webapp/src/pages/dashboard/account/history.tsx @@ -43,9 +43,12 @@ export default function AccountHistory() { api.order.getList.useQuery({ status: "delivered", }); + const { data: resultTotalAmount } = + api.saving.getTotalAmountByCurrentUser.useQuery(); const { data: userSavings } = resultUserSavings || {}; const { data: userOrders } = resultUserOrders || {}; + const { data: userTotalAmount } = resultTotalAmount || { data: 0 }; if ( !user || @@ -68,155 +71,186 @@ export default function AccountHistory() { ); return ( - - { - router.back(); - }} - borderRadius="2.25xl" - size="md" - icon={} - /> - - Historique de mes réductions - - {history.length > 0 ? ( - - {history.map((userHistoryItem, index) => { - const currentDate = new Date(); - const currentCouponUsedAt = new Date( - ("usedAt" in userHistoryItem - ? userHistoryItem.usedAt + + + { + router.back(); + }} + borderRadius="2.25xl" + size="md" + icon={} + /> + + Historique de mes économies + +
+ + Vous avez économisé + + + {userTotalAmount}€ + + + vous avez la carte “jeune engagé” depuis le{" "} + {new Date(user.createdAt).toLocaleDateString("fr-FR", { + day: "2-digit", + month: "2-digit", + year: "numeric", + })} + +
+
+ + + {history.length > 0 ? ( + + {history.map((userHistoryItem, index) => { + const currentDate = new Date(); + const currentCouponUsedAt = new Date( + ("usedAt" in userHistoryItem ? userHistoryItem.usedAt - : userHistoryItem.assignUserAt - : userHistoryItem.createdAt) as string - ); - const previousCoupon = history[index - 1]; - const previousCouponUsedAt = previousCoupon - ? new Date( - ("usedAt" in previousCoupon - ? previousCoupon.usedAt + ? userHistoryItem.usedAt + : userHistoryItem.assignUserAt + : userHistoryItem.createdAt) as string + ); + console.log("currentCouponUsedAt", currentCouponUsedAt); + const previousCoupon = history[index - 1]; + const previousCouponUsedAt = previousCoupon + ? new Date( + ("usedAt" in previousCoupon ? previousCoupon.usedAt - : previousCoupon.assignUserAt - : previousCoupon.createdAt) as string - ) - : new Date(); + ? previousCoupon.usedAt + : previousCoupon.assignUserAt + : previousCoupon.createdAt) as string + ) + : new Date(); - const currentMonth = currentCouponUsedAt.toLocaleString("fr-FR", { - month: "long", - }); + const currentMonth = currentCouponUsedAt.toLocaleString("fr-FR", { + month: "long", + }); - const darkenPartnerColor = new TinyColor( - userHistoryItem.offer.partner.color - ) - .darken(10) - .toHexString(); + const darkenPartnerColor = new TinyColor( + userHistoryItem.offer.partner.color + ) + .darken(10) + .toHexString(); - const formatedCurrentMonth = - index === 0 && - currentDate.getMonth() === currentCouponUsedAt.getMonth() && - currentDate.getFullYear() === currentCouponUsedAt.getFullYear() - ? "Ce mois-ci" - : `${ - currentMonth.charAt(0).toUpperCase() + currentMonth.slice(1) - } ${ - currentDate.getFullYear() !== - currentCouponUsedAt.getFullYear() - ? currentCouponUsedAt.getFullYear() - : "" - }`; + const formatedCurrentMonth = + index === 0 && + currentDate.getMonth() === currentCouponUsedAt.getMonth() && + currentDate.getFullYear() === currentCouponUsedAt.getFullYear() + ? "Ce mois-ci" + : `${ + currentMonth.charAt(0).toUpperCase() + + currentMonth.slice(1) + } ${ + currentDate.getFullYear() !== + currentCouponUsedAt.getFullYear() + ? currentCouponUsedAt.getFullYear() + : "" + }`; - return ( - <> - {currentCouponUsedAt.getMonth() !== - previousCouponUsedAt.getMonth() && ( - - {formatedCurrentMonth} - - )} - - - + {currentCouponUsedAt.getMonth() !== + previousCouponUsedAt.getMonth() && ( + - {userHistoryItem.offer.partner.icon.alt - - - - {userHistoryItem.offer.partner.name} - - - {userHistoryItem.offer.title} - - - {"used" in userHistoryItem && !userHistoryItem.used ? ( - - - - Fin{" "} - {currentCouponUsedAt.toLocaleDateString("fr-FR", { - day: "2-digit", - month: "2-digit", - })} - - - ) : ( - - - Déjà utilisée - - )} + {formatedCurrentMonth} + + )} + + + + {userHistoryItem.offer.partner.icon.alt + + + {userHistoryItem.offer.partner.name} + + + {userHistoryItem.offer.title} + + + {"used" in userHistoryItem && + !userHistoryItem.used ? ( + + + + Fin{" "} + {currentCouponUsedAt.toLocaleDateString( + "fr-FR", + { + day: "2-digit", + month: "2-digit", + } + )} + + + ) : ( + + + Déjà utilisée + + )} + + - - - - ); - })} - - ) : ( - - )} + + + ); + })} + + ) : ( + + )} +
); } diff --git a/webapp/src/pages/dashboard/wallet/index.tsx b/webapp/src/pages/dashboard/wallet/index.tsx index 2eeeea60..b3eac703 100644 --- a/webapp/src/pages/dashboard/wallet/index.tsx +++ b/webapp/src/pages/dashboard/wallet/index.tsx @@ -21,10 +21,13 @@ import Image from "next/image"; import { CouponIncluded } from "~/server/api/routers/coupon"; import { OrderIncluded } from "~/server/api/routers/order"; import OrderCard from "~/components/cards/OrderCard"; +import { useMemo, useState } from "react"; export default function Wallet() { const router = useRouter(); + const [filterSelected, setFilterSelected] = useState("all"); + const { data: resultOffers, isLoading: isLoadingOffers } = api.offer.getListOfAvailables.useQuery({ page: 1, @@ -40,24 +43,38 @@ export default function Wallet() { const { data: currentUserOrders } = resultUserOrders || { data: [] }; const { data: offers } = resultOffers || { data: [] }; + const walletOffers = useMemo(() => { + let filteredOffers: (CouponIncluded | OrderIncluded)[] = []; + if (filterSelected === "all") { + filteredOffers = [...currentUserCoupons, ...currentUserOrders]; + } else if (filterSelected === "coupons") { + filteredOffers = currentUserCoupons; + } else if (filterSelected === "orders") { + filteredOffers = currentUserOrders; + } + return filteredOffers.sort( + (a, b) => + new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime() + ); + }, [filterSelected, currentUserCoupons, currentUserOrders]); + if (isLoadingUserCoupons || isLoadingOffers || isLoadingUserOrders) return ( - +
); - const walletOffers: (CouponIncluded | OrderIncluded)[] = [ - ...currentUserCoupons, - ...currentUserOrders, - ].sort( - (a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime() - ); - return ( - + {walletOffers && walletOffers.length > 0 ? ( <> @@ -69,7 +86,7 @@ export default function Wallet() { pt={index !== 0 ? 3 : 0} zIndex={1} borderTopWidth={index !== 0 ? 1 : 0} - px={8} + px={7} > {"code" in walletOffer ? ( ))} - + ) : ( - + )} - +
{ - const { userId } = input; - const userSavings = await ctx.payload.find({ - collection: "savings", - depth: 0, - where: { - "coupon.user": { - equals: userId, - }, + getTotalAmountByCurrentUser: userProtectedProcedure.query(async ({ ctx }) => { + const userSavings = await ctx.payload.find({ + collection: "savings", + depth: 0, + where: { + "coupon.user": { + equals: ctx.session.id, }, - }); + }, + }); - const totalAmount = userSavings.docs.reduce( - (total, saving) => - total + (typeof saving.amount === "number" ? saving.amount : 0), - 0 - ); + const totalAmount = userSavings.docs.reduce( + (total, saving) => + total + (typeof saving.amount === "number" ? saving.amount : 0), + 0 + ); - return { - data: Math.round(totalAmount * 100) / 100, - }; - }), + return { + data: Math.round(totalAmount * 100) / 100, + }; + }), }); diff --git a/webapp/src/utils/chakra-theme.ts b/webapp/src/utils/chakra-theme.ts index 340c0120..34d61bba 100644 --- a/webapp/src/utils/chakra-theme.ts +++ b/webapp/src/utils/chakra-theme.ts @@ -243,6 +243,7 @@ export const theme = extendTheme({ "installation-banner-icon": "0px 16px 14px -9px #1698FC70, 0px 1px 40px 6px #A8CEEC", icon: "0px 4px 13px 0px rgba(32, 32, 44, 0.20)", + "wallet-header": "0px -4px 13px 0px rgba(32, 32, 44, 0.20)", }, radii: { "2lg": "0.625rem",