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 (
-
-
-
+ {!userOffline && (
+
+
+
+
-
+ )}
-
+
{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 },
};
}),