diff --git a/webapp/src/components/cards/OfferCard.tsx b/webapp/src/components/cards/OfferCard.tsx index f9b6e236..26c82002 100644 --- a/webapp/src/components/cards/OfferCard.tsx +++ b/webapp/src/components/cards/OfferCard.tsx @@ -8,62 +8,96 @@ import { OfferKindBadge } from "../OfferKindBadge"; type OfferCardProps = { offer: OfferIncluded; displayExpiryDate?: boolean; + userOffline?: boolean; }; -const OfferCard = ({ offer, displayExpiryDate = false }: OfferCardProps) => { +const OfferCard = ({ + offer, + displayExpiryDate = false, + userOffline = false, +}: OfferCardProps) => { return ( - - - {offer.partner.icon.alt + {!userOffline && ( + + + {offer.partner.icon.alt + - + )} - + {offer.partner.name} - + {offer.title} - - {displayExpiryDate && ( - - - Expire le : {new Date(offer.validityTo).toLocaleDateString()} - - + {userOffline && offer.coupons && offer.coupons[0] && ( + + {offer.coupons[0].code} + )} + + + {displayExpiryDate && ( + + + Expire le : {new Date(offer.validityTo).toLocaleDateString()} + + + )} + diff --git a/webapp/src/middleware.ts b/webapp/src/middleware.ts index dca53e4e..88ddf2ff 100644 --- a/webapp/src/middleware.ts +++ b/webapp/src/middleware.ts @@ -3,6 +3,8 @@ import type { NextRequest } from "next/server"; // This function can be marked `async` if using `await` inside export function middleware(request: NextRequest) { + if (request.nextUrl.pathname === "/_offline") return NextResponse.next(); + if ( !request.cookies.get(process.env.NEXT_PUBLIC_JWT_NAME ?? "cje-jwt") && request.nextUrl.pathname.startsWith("/dashboard") diff --git a/webapp/src/pages/_offline.tsx b/webapp/src/pages/_offline.tsx new file mode 100644 index 00000000..9d8fe58d --- /dev/null +++ b/webapp/src/pages/_offline.tsx @@ -0,0 +1,37 @@ +import { Flex, Heading, Text } from "@chakra-ui/react"; +import { useEffect, useState } from "react"; +import OfferCard from "~/components/cards/OfferCard"; +import { OfferIncluded } from "~/server/api/routers/offer"; + +const OfflinePage = () => { + const [userOffers, setUserOffers] = useState([]); + + useEffect(() => { + const storedOffers = localStorage.getItem("cje-user-offers"); + if (storedOffers) { + setUserOffers(JSON.parse(storedOffers)); + } + }, []); + + return ( + + + Pas de réseau... + + + {userOffers.map((userOffer) => { + return ( + + ); + })} + + + ); +}; + +export default OfflinePage; diff --git a/webapp/src/pages/dashboard/offer/online/[id].tsx b/webapp/src/pages/dashboard/offer/online/[id].tsx index f85819fa..a99f076d 100644 --- a/webapp/src/pages/dashboard/offer/online/[id].tsx +++ b/webapp/src/pages/dashboard/offer/online/[id].tsx @@ -16,12 +16,14 @@ import { useRouter } from "next/router"; import { useState } from "react"; import { FiBook, FiCopy, FiLink } from "react-icons/fi"; import { IoCloseCircleOutline } from "react-icons/io5"; +import { useLocalStorage } from "usehooks-ts"; import LoadingLoader from "~/components/LoadingLoader"; import ToastComponent from "~/components/ToastComponent"; import { CouponIcon } from "~/components/icons/coupon"; import OfferActivationModal from "~/components/modals/OfferActivationModal"; import CouponWrapper from "~/components/wrappers/CouponWrapper"; import OfferWrapper from "~/components/wrappers/OfferWrapper"; +import { OfferIncluded } from "~/server/api/routers/offer"; import { couponAnimation } from "~/utils/animations"; import { api } from "~/utils/api"; @@ -29,6 +31,11 @@ export default function Dashboard() { const router = useRouter(); const { id } = router.query; + const [userOffers, setUserOffers] = useLocalStorage( + "cje-user-offers", + [] + ); + const { data: resultOffer, isLoading: isLoadingOffer } = api.offer.getById.useQuery( { @@ -58,7 +65,17 @@ export default function Dashboard() { isLoading, isSuccess, } = api.coupon.assignToUser.useMutation({ - onSuccess: () => refetchCoupon(), + onSuccess: (response) => { + if (offer) + setUserOffers([ + ...userOffers, + { + ...offer, + coupons: [response.data], + }, + ]); + refetchCoupon(); + }, }); const toast = useToast(); diff --git a/webapp/src/pages/dashboard/wallet/index.tsx b/webapp/src/pages/dashboard/wallet/index.tsx index 85c47d3e..d8e2dc37 100644 --- a/webapp/src/pages/dashboard/wallet/index.tsx +++ b/webapp/src/pages/dashboard/wallet/index.tsx @@ -7,14 +7,16 @@ import { TabPanel, Text, } from "@chakra-ui/react"; +import { useRouter } from "next/router"; +import { useEffect, useState } from "react"; +import { PiSmileySadFill } from "react-icons/pi"; +import { useLocalStorage } from "usehooks-ts"; import LoadingLoader from "~/components/LoadingLoader"; import OfferCard from "~/components/cards/OfferCard"; import WalletWrapper from "~/components/wrappers/WalletWrapper"; -import { api } from "~/utils/api"; -import { PiSmileySadFill } from "react-icons/pi"; -import { useRouter } from "next/router"; -import { useEffect, useState } from "react"; import { Offer } from "~/payload/payload-types"; +import { OfferIncluded } from "~/server/api/routers/offer"; +import { api } from "~/utils/api"; const WalletNoData = ({ kind }: { kind: Offer["kind"] }) => { return ( @@ -38,6 +40,10 @@ export default function Wallet() { offerKind?: Offer["kind"]; }; + const [userOffers, setUserOffers] = useLocalStorage( + "cje-user-offers", + [] + ); const [tabIndex, setTabIndex] = useState(2); useEffect(() => { @@ -81,6 +87,10 @@ export default function Wallet() { (offer) => offer.kind === "code" ); + useEffect(() => { + if (currentUserOffers) setUserOffers(currentUserOffers); + }, [currentUserOffers]); + if (isLoadingUserOffers) { return ( diff --git a/webapp/src/server/api/routers/offer.ts b/webapp/src/server/api/routers/offer.ts index 1d3e45dc..59e2a099 100644 --- a/webapp/src/server/api/routers/offer.ts +++ b/webapp/src/server/api/routers/offer.ts @@ -1,12 +1,20 @@ +import { PaginatedDocs } from "payload/database"; import { Where, WhereField } from "payload/types"; import { z } from "zod"; -import { Category, Offer, Media, Partner } from "~/payload/payload-types"; +import { + Category, + Offer, + Media, + Partner, + Coupon, +} from "~/payload/payload-types"; import { createTRPCRouter, protectedProcedure } from "~/server/api/trpc"; import { ZGetListParams } from "~/server/types"; export interface OfferIncluded extends Offer { partner: Partner & { icon: Media }; category: Category & { icon: Media }; + coupons?: Coupon[]; } export const offerRouter = createTRPCRouter({ @@ -34,17 +42,19 @@ export const offerRouter = createTRPCRouter({ }; } - const offers = await ctx.payload.find({ + const offers = (await ctx.payload.find({ collection: "offers", limit: perPage, page: page, where: where as Where, sort, - }); + })) as PaginatedDocs; const couponCountOfOffers = await ctx.payload.find({ collection: "coupons", depth: 0, + limit: 10000, + page: 1, where: { offer: { in: offers.docs.map((offer) => offer.id), @@ -52,32 +62,33 @@ export const offerRouter = createTRPCRouter({ }, }); - const offersFiltered = offers.docs.filter((offer) => { - const couponFiltered = couponCountOfOffers.docs.filter( - (coupon) => coupon.offer === offer.id - ); - - let couponCount = 0; + const offersFiltered = offers.docs + .map((offer) => { + const couponFiltered = couponCountOfOffers.docs.filter( + (coupon) => coupon.offer === offer.id + ); - if (isCurrentUser) { - couponCount = couponFiltered.filter( - (coupon) => coupon.user === ctx.session.id && coupon.used === false - ).length; - } else { - couponCount = couponFiltered.filter( - (coupon) => - (coupon.user === undefined || - coupon.user === null || - coupon.user === ctx.session.id) && - coupon.used === false - ).length; - } + if (isCurrentUser) { + offer.coupons = couponFiltered.filter( + (coupon) => + coupon.user === ctx.session.id && coupon.used === false + ); + } else { + offer.coupons = couponFiltered.filter( + (coupon) => + (coupon.user === undefined || + coupon.user === null || + coupon.user === ctx.session.id) && + coupon.used === false + ); + } - if (couponCount > 0) return offer; - }); + return offer; + }) + .filter((offer) => !offer.coupons || offer.coupons.length > 0); return { - data: offersFiltered as OfferIncluded[], + data: offersFiltered, metadata: { page, count: offers.docs.length }, }; }),