From 8fff2d9cef27589a54eeca8626ccfd2c5bdd1a00 Mon Sep 17 00:00:00 2001 From: dyllon Date: Mon, 9 Oct 2023 17:17:27 +0800 Subject: [PATCH] remove merch routes for launch --- apps/web/pages/merch/cart/index.tsx | 379 ------------------- apps/web/pages/merch/checkout/index.tsx | 190 ---------- apps/web/pages/merch/index.tsx | 92 ----- apps/web/pages/merch/orders/[slug].tsx | 199 ---------- apps/web/pages/merch/product/[slug].tsx | 474 ------------------------ 5 files changed, 1334 deletions(-) delete mode 100644 apps/web/pages/merch/cart/index.tsx delete mode 100644 apps/web/pages/merch/checkout/index.tsx delete mode 100644 apps/web/pages/merch/index.tsx delete mode 100644 apps/web/pages/merch/orders/[slug].tsx delete mode 100644 apps/web/pages/merch/product/[slug].tsx diff --git a/apps/web/pages/merch/cart/index.tsx b/apps/web/pages/merch/cart/index.tsx deleted file mode 100644 index 45e2fd33..00000000 --- a/apps/web/pages/merch/cart/index.tsx +++ /dev/null @@ -1,379 +0,0 @@ -/* eslint-disable */ -/* eslint-disable @typescript-eslint/no-misused-promises */ - - -import React, { useRef, useState, FC, useEffect } from "react"; -import Link from "next/link"; -import { - Button, - Flex, - Heading, - useBreakpointValue, - Divider, - useDisclosure, - Grid, - GridItem, - Text, - Input, - Spinner, -} from "@chakra-ui/react"; -import { useQuery } from "@tanstack/react-query"; -import Joi from "joi"; -import { - CartAction, - CartActionType, - useCartStore, -} from "features/merch/context/cart"; -import { - CartCard, - CartEmptyView, - CartHeader, - CartItemCard, - CartRemoveModal, - LoadingScreen, - Page, -} from "ui/components/merch"; -import { api } from "features/merch/services/api"; -import { routes, QueryKeys } from "features/merch/constants"; -import { displayPrice } from "features/merch/functions"; -import { calculatePricing } from "merch-helpers"; -import { useRouter } from "next/router"; - -type ValidationType = { - error: boolean; - isLoading: boolean; -}; - -const Cart: FC = () => { - // Context hook. - const cartContext = useCartStore(); - const { state: cartState, dispatch: cartDispatch } = cartContext; - - const router = useRouter(); - const [reroute, setReroute] = useState(false); - - // Email input for billing. - const [validation, setValidation] = useState({ - isLoading: false, - error: false, - }); - - // Calculation of pricing - const [isCartLoading, setIsCartLoading] = useState(true); - const { data: products, isLoading: isProductsQueryLoading } = useQuery( - [QueryKeys.PRODUCTS], - () => api.getProducts(), - { - onSuccess: () => { - setIsCartLoading(false); - }, - } - ); - - // Voucher section - // const [voucherInput, setVoucherInput] = useState(""); - // const [voucherError, setVoucherError] = useState(false); - - const pricedCart = products - ? calculatePricing(products, cartState.cart, undefined) - : null; - - const emailValidator = Joi.string() - .email({ tlds: { allow: false } }) - .required() - .label("Email"); - - // Removal Modal cartStates - const { isOpen, onOpen, onClose } = useDisclosure(); - const toBeRemoved = useRef({ productId: "", size: "", color: "" }); - - // Check if break point hit. - const isMobile: boolean = - useBreakpointValue({ base: true, md: false }) || false; - - // Apply voucher - TODO - // const { mutate: applyVoucher, isLoading: voucherLoading } = useMutation( - // () => api.postQuotation(cartState.cart, voucherInput), - // { - // onMutate: () => { - // setPriceLoading(true); - // }, - // onSuccess: (data: PricedCart) => { - // setPriceInfo(data.total); - // if (data.price.discount > 0) { - // // Voucher is valid - // cartDispatch({ type: CartActionType.VALID_VOUCHER, payload: voucherInput }); - // setVoucherError(false); - // setVoucherInput(""); - // } else { - // setVoucherError(true); - // } - // }, - // onSettled: () => { - // setPriceLoading(false); - // }, - // } - // ); - - // const handleRemoveVoucher = () => { - // setVoucherInput(""); - // cartDispatch({ type: CartActionType.REMOVE_VOUCHER, payload: null }); - // applyVoucher(); - // }; - - // Update Cart Item by Size & Id (To be changed next time: BE) - const removeItem = (productId: string, size: string, color: string) => { - cartDispatch({ - type: CartActionType.REMOVE_ITEM, - payload: { id: productId, size: size, color: color }, - }); - onClose(); - }; - - // Set modal's ref value to size & productId pair. - const handleRemoveItem = (productId: string, size: string, color: string) => { - onOpen(); - toBeRemoved.current.size = size; - toBeRemoved.current.color = color; - toBeRemoved.current.productId = productId; - }; - - // Update Cart Item by Size & Id (To be changed next time: BE) - const onQuantityChange = ( - productId: string, - size: string, - color: string, - qty: number - ) => { - const action: CartAction = { - type: CartActionType.UPDATE_QUANTITY, - payload: { id: productId, size: size, color: color, quantity: qty }, - }; - cartDispatch(action); - }; - - const handleToCheckout = async () => { - setValidation({ isLoading: true, error: false }); - try { - await emailValidator.validateAsync(cartState.billingEmail); - cartDispatch({ - type: CartActionType.UPDATE_BILLING_EMAIL, - payload: cartState.billingEmail, - }); - setReroute(true); - } catch (error: any) { - setValidation({ isLoading: false, error: true }); - } - }; - - const CartHeading = ( - - Your Cart - - ); - - const PriceInfoSection = ( - - {!pricedCart ? ( - - - Calculating your cart price - - ) : ( - <> - - - Item(s) subtotal - {displayPrice(pricedCart.subtotal)} - - - Voucher Discount - {displayPrice(pricedCart.discount)} - - - - Total - {displayPrice(pricedCart.total)} - - - - - { - cartDispatch({ - type: CartActionType.UPDATE_NAME, - payload: event.target.value, - }); - }} - variant="outline" - /> - - { - cartDispatch({ - type: CartActionType.UPDATE_BILLING_EMAIL, - payload: event.target.value, - }); - }} - variant="outline" - /> - - {validation.error && "*Invalid email format"} - - - - - - - - - - )} - - ); - /* TODO - const VoucherSection = ( - - - - ) => { - const target = e.target as HTMLInputElement; - setVoucherInput(target.value); - }} - /> - - - - {!cartState.voucher ? ( - Apply your voucher code! - ) : ( - - {voucherError && Invalid voucher} - {cartState.voucher && priceInfo.discount > 0 && ( - - Applied Voucher - - - )} - - )} - - - - ); -*/ - const renderCartView = () => ( - - - {!isMobile && } - {cartState.cart.items.map((item, index) => ( - <> - product.id === item.id)} - isLoading={isProductsQueryLoading} - isMobile={isMobile} - onRemove={handleRemoveItem} - onQuantityChange={onQuantityChange} - /> - {index !== cartState.cart.items.length - 1 && } - - ))} - - - {/* {VoucherSection} TODO*/} - {PriceInfoSection} - - - An email will be sent to you closer to the collection date. Our - collection venue is at 50 Nanyang Ave, #32 Block N4 #02a, Singapore - 639798. - - - - - removeItem( - toBeRemoved.current.productId, - toBeRemoved.current.size, - toBeRemoved.current.color - ) - } - /> - - ); - - const renderCartContent = () => { - if (isCartLoading) { - return ; - } - if (cartState.cart.items.length === 0) { - return ; - } - return renderCartView(); - }; - - useEffect(() => { - if (reroute) { - void router.push(routes.CHECKOUT); - } - }, [reroute]); - - return ( - - {CartHeading} - {renderCartContent()} - - ); -}; - -export default Cart; diff --git a/apps/web/pages/merch/checkout/index.tsx b/apps/web/pages/merch/checkout/index.tsx deleted file mode 100644 index 07f44240..00000000 --- a/apps/web/pages/merch/checkout/index.tsx +++ /dev/null @@ -1,190 +0,0 @@ -/* eslint-disable */ - -import { CartEmptyView, Page } from "ui/components/merch"; -import { useCartStore } from "@/features/merch/context/cart"; -import { useEffect, useState } from "react"; -import { useCheckoutStore } from "@/features/merch/context/checkout"; -import { - Box, - Divider, - Flex, - Grid, - GridItem, - Heading, - Text, - Image, - Badge, -} from "@chakra-ui/react"; -import { useMutation, useQuery } from "@tanstack/react-query"; -import { QueryKeys, routes } from "@/features/merch/constants"; -import { api } from "@/features/merch/services/api"; -import CheckoutSkeleton from "@/features/merch/components/checkout/Skeleton"; -import Link from "next/link"; -import { displayPrice } from "@/features/merch/functions"; -import { useRouter } from "next/router"; -import StripeForm from "@/features/merch/components/checkout/StripeForm"; -import { CheckoutResponse } from "types"; - -const CheckoutPage = () => { - const [isLoading, setIsLoading] = useState(true); - const { state: cartState } = useCartStore(); - const { setState: setCheckoutState } = useCheckoutStore(); - - // Fetch and check if cart item is valid. - const { mutate: initCheckout } = useMutation( - () => - api.postCheckoutCart( - cartState.cart, - cartState.billingEmail, - cartState.voucher - ), - { - retry: false, - onMutate: () => { - setIsLoading(true); - }, - onSuccess: (data: CheckoutResponse) => { - setCheckoutState(data); - }, - onSettled: () => { - setIsLoading(false); - }, - } - ); - - const router = useRouter(); - - useEffect(() => { - if (!cartState.billingEmail) { - void router.push(routes.CART); - return; - } - initCheckout(); - }, []); - - return ( - - - Checkout - - {isLoading ? ( - - ) : cartState.cart.items.length === 0 ? ( - - ) : ( - - )} - - ); -}; - -const CheckoutView = () => { - const { state: checkoutState } = useCheckoutStore(); - return ( - - - {OrderSummary()} - - - {checkoutState?.payment?.clientSecret && ( - - )} - - - ); -}; - -const OrderSummary = () => { - const { state: cartState } = useCartStore(); - const { state: checkoutState } = useCheckoutStore(); - - const noOfItems = cartState.cart.items.length; - - const { data: products } = useQuery( - [QueryKeys.PRODUCTS], - () => api.getProducts(), - {} - ); - - return ( - - - Order Summary - - {`${noOfItems} item(s) Edit`} - - - {`Name: ${cartState.name}`} - {`Billing email: ${cartState.billingEmail}`} - {cartState.cart.items?.map((item) => { - const product = products?.find(({ id }) => id === item.id); - const subtotal = (product?.price ?? -1) * item.quantity; - return ( - - {product?.name} - - - - {product?.name} - - {displayPrice(subtotal)} - - - {`Color: ${item.color}`} - - - - {`Qty x${item.quantity}`} - - {item.size} - - - {displayPrice(product?.price ?? 0)} each - - - - ); - })} - - - - - {/* Subtotal: */} - {/* Discount: */} - Grand total: - - - {/* {displayPrice(checkoutState?.price?.subtotal ?? 0)} */} - {/* {displayPrice(checkoutState?.price?.discount ?? 0)} */} - - {displayPrice(checkoutState?.price?.grandTotal ?? 0)} - - - - - ); -}; - -export default CheckoutPage; diff --git a/apps/web/pages/merch/index.tsx b/apps/web/pages/merch/index.tsx deleted file mode 100644 index c6cdfcb6..00000000 --- a/apps/web/pages/merch/index.tsx +++ /dev/null @@ -1,92 +0,0 @@ -import React, { useState } from "react"; -import { Flex, Divider, Select, Heading, Grid } from "@chakra-ui/react"; -import { useQuery } from "@tanstack/react-query"; -import { Card, MerchListSkeleton, Page } from "ui/components/merch"; -import { QueryKeys } from "features/merch/constants"; -import { api } from "features/merch/services/api"; -import { Product } from "types"; -import { isOutOfStock } from "features/merch/functions"; - -const MerchandiseList = () => { - const [selectedCategory, setSelectedCategory] = useState(""); - - const { data: products, isLoading } = useQuery( - [QueryKeys.PRODUCTS], - () => api.getProducts(), - {} - ); - - const categories = products?.map((product: Product) => product?.category); - const uniqueCategories = categories - ?.filter((c, idx) => categories.indexOf(c) === idx) - .filter(Boolean); - - const handleCategoryChange = ( - event: React.ChangeEvent - ) => { - setSelectedCategory(event.target.value); - }; - - return ( - - - - New Drop - - - - - {isLoading ? ( - - ) : ( - - {products - ?.filter((product: Product) => { - if (!product?.is_available) return false; - if (selectedCategory === "") return true; - return product?.category === selectedCategory; - }) - ?.map((item: Product, idx: number) => ( - - ))} - - )} - - ); -}; - -export default MerchandiseList; diff --git a/apps/web/pages/merch/orders/[slug].tsx b/apps/web/pages/merch/orders/[slug].tsx deleted file mode 100644 index 5f9e8a52..00000000 --- a/apps/web/pages/merch/orders/[slug].tsx +++ /dev/null @@ -1,199 +0,0 @@ -import React, { useState } from "react"; -import { useRouter } from "next/router"; -import { Image, Badge, Button, Divider, Flex, Heading, Text, useBreakpointValue } from "@chakra-ui/react"; -import { useQuery } from "@tanstack/react-query"; -import { Page } from "ui/components/merch"; -import { Order, OrderStatus } from "types"; -import { api } from "features/merch/services/api"; -import { routes } from "features/merch/constants/routes"; -import { QueryKeys } from "features/merch/constants/queryKeys"; -import { displayPrice } from "features/merch/functions/currency"; -import Link from "next/link" -import LoadingScreen from "ui/components/merch/skeleton/LoadingScreen"; -import { getOrderStatusColor, renderOrderStatus } from "merch-helpers"; -import OrderItem from "ui/components/merch/OrderItem"; -const OrderSummary: React.FC = () => { -// Check if break point hit. KIV - const isMobile: boolean = useBreakpointValue({ base: true, md: false }) || false; - const router = useRouter(); - const orderSlug = router.query.slug as string | undefined; - - const [showThankYou, setShowThankYou] = useState(false); - const [orderState, setOrderState] = useState(null); - // TODO: Fetch subtotal and total from server. - const [total, setTotal] = useState(0); - // Fetch and check if cart item is valid. Number(item.price) set to convert string to num - const { isLoading } = useQuery( - [QueryKeys.ORDER, orderSlug], - () => api.getOrder(orderSlug ?? ""), - { - enabled: !!orderSlug, - onSuccess: (data: Order) => { - setOrderState(data); - setTotal( - data.items.reduce((acc, item) => { - return item.price * item.quantity + acc; - }, 0) - ); - setShowThankYou(true); - }, - } - ); - - const renderThankYouMessage = () => ( - <> - THANK YOU - Thank you for your purchase. We have received your order. - - - - - - ); - const renderOrderSummary = () => ( - <> - - {showThankYou && renderThankYouMessage()} - - - -
- - - - {renderOrderStatus(orderState?.status ?? OrderStatus.PENDING_PAYMENT)} - - Order Number - - {orderState?.id.split("-")[0]} - - - {orderState?.id} - - - Order date:{" "} - {orderState?.transaction_time - ? new Date(`${orderState.transaction_time}`).toLocaleString( - "en-sg" - ) - : ""} - - {/*Last update: {orderState?.lastUpdate}*/} - - -
-
- - - - Order Number - - {renderOrderStatus(orderState?.status ?? OrderStatus.PENDING_PAYMENT)} - - - - {orderState?.id.split("-")[0]} - - - {orderState?.id} - - - - - Order date:{" "} - {orderState?.transaction_time - ? new Date(`${orderState.transaction_time}`).toLocaleString( - "en-sg" - ) - : ""} - - {/*Last update: {orderState?.lastUpdate}*/} - - -
- - {/*{orderState?.items.map((item) => (*/} - {/* */} - {/*))}*/} - - {orderState? : Order Not Found} - - - - - Item Subtotal: - Voucher Discount: - Total: - - - {displayPrice(total)} - - {/*{displayPrice( TODO*/} - {/* (orderState?.billing?.subtotal ?? 0) -*/} - {/* (orderState?.billing?.total ?? 0)*/} - {/*)}*/} - 0 - - {displayPrice(total)} - - -
- - - {/* TODO: QR Code generator based on Param. */} - QRCode - - Please screenshot this QR code and show it at SCSE Lounge to collect your order. - Alternatively, show the email receipt you have received. - - - For any assistance, please contact our email address: - merch@ntuscse.com - - - - ); - const renderSummaryPage = () => { - if (isLoading) return ; - //rmb to change this v - if (orderState === undefined || orderState === null){return ;} - return renderOrderSummary(); - }; - return {renderSummaryPage()}; -} -export default OrderSummary diff --git a/apps/web/pages/merch/product/[slug].tsx b/apps/web/pages/merch/product/[slug].tsx deleted file mode 100644 index 4085d159..00000000 --- a/apps/web/pages/merch/product/[slug].tsx +++ /dev/null @@ -1,474 +0,0 @@ -/* eslint-disable @typescript-eslint/no-misused-promises */ - -import React, { useState } from "react"; -import { useRouter } from "next/router"; -import { - Badge, - Button, - Center, - Divider, - Flex, - Grid, - GridItem, - Heading, - Input, - Text, - useDisclosure, -} from "@chakra-ui/react"; -import { - EmptyProductView, - MerchCarousel, - MerchDetailSkeleton, - Page, - SizeChartDialog, - SizeOption, -} from "ui/components/merch"; -import { Product } from "types"; -import { - CartAction, - CartActionType, - useCartStore, -} from "features/merch/context/cart"; -import { QueryKeys, routes } from "features/merch/constants"; -import { - displayPrice, - displayQtyInCart, - displayStock, getDefaultColor, getDefaultSize, - getQtyInCart, - getQtyInStock, - isColorAvailable, - isOutOfStock, - isSizeAvailable, -} from "features/merch/functions"; -import { GetStaticPaths, GetStaticProps, InferGetStaticPropsType } from "next"; -import { useQuery } from "@tanstack/react-query"; -import { api } from "@/features/merch/services/api"; - -interface GroupTitleProps { - children: React.ReactNode; -} - -const GroupTitle = ({ children }: GroupTitleProps) => ( - - {children} - -); - -const MerchDetail = (_props: InferGetStaticPropsType) => { - // Context hook. - const { state: cartState, dispatch: cartDispatch } = useCartStore(); - const router = useRouter(); - const id = (router.query.slug ?? "") as string; - - const [quantity, setQuantity] = useState(1); - const [isDisabled, setIsDisabled] = useState(false); - const [selectedSize, setSelectedSize] = useState(null); - const [selectedColor, setSelectedColor] = useState(null); - const [maxQuantity, setMaxQuantity] = useState(1); - - const { isOpen, onOpen, onClose } = useDisclosure(); - - const { data: product, isLoading } = useQuery( - [QueryKeys.PRODUCT, id], - () => api.getProduct(id), - { - onSuccess: (data: Product) => { - setIsDisabled(!(data?.is_available === true)); - setSelectedSize(getDefaultSize(data)); - setSelectedColor(getDefaultColor(data)); - }, - } - ); - - //* In/decrement quantity - const handleQtyChangeCounter = (isAdd = true) => { - const value = isAdd ? 1 : -1; - if (!isAdd && quantity === 1) return; - if (isAdd && quantity >= maxQuantity) return; - setQuantity(quantity + value); - }; - - //* Manual input quantity. - const handleQtyChangeInput = (e: React.FormEvent): void => { - const target = e.target as HTMLInputElement; - if (Number.isNaN(parseInt(target.value, 10))) { - setQuantity(1); - return; - } - const value = parseInt(target.value, 10); - if (value <= 0) { - setQuantity(1); - } else if (value > maxQuantity) { - setQuantity(maxQuantity); - } else { - setQuantity(value); - } - }; - - const updateMaxQuantity = (color: string, size: string) => { - if (product) { - const stockQty = getQtyInStock(product, color, size); - const cartQty = getQtyInCart( - cartState.cart.items, - product.id, - color, - size - ); - const max = stockQty > cartQty ? stockQty - cartQty : 0; - setMaxQuantity(max); - } - }; - - const handleAddToCart = () => { - if (!selectedColor || !selectedSize) { - return; - } - setIsDisabled(true); - const payload: CartAction = { - type: CartActionType.ADD_ITEM, - payload: { - id, - quantity, - color: selectedColor, - size: selectedSize, - }, - }; - cartDispatch(payload); - setMaxQuantity(maxQuantity - quantity); - setQuantity(1); - setIsDisabled(false); - }; - - const handleBuyNow = async () => { - handleAddToCart(); - await router.push(routes.CART); - }; - - const ProductNameSection = ( - - - {product?.name} - {!product?.is_available && ( - - unavailable - - )} - {product && isOutOfStock(product) && ( - - out of stock - - )} - - - {displayPrice(product?.price ?? 0)} - - - ); - - const renderSizeSection = ( - - - Sizes - {product?.size_chart && ( - - )} - - - {product?.sizes?.map((size, idx) => { - return ( - { - setQuantity(1); - if (size !== selectedSize) { - setSelectedSize(size); - if (selectedColor) { - updateMaxQuantity(selectedColor, size); - } - } else { - setSelectedSize(null); - } - }} - disabled={ - isDisabled || - (product - ? !isSizeAvailable(product, size) // size is not available for all colors - : false) || - (product && selectedColor - ? getQtyInStock(product, selectedColor, size) === 0 // size is not available for selected color - : false) - } - > - - {size} - - - ); - })} - - - ); - - const renderColorSection = ( - - - Colors - - - {product?.colors?.map((color, idx) => { - return ( - { - setQuantity(1); - if (color !== selectedColor) { - setSelectedColor(color); - if (selectedSize) { - updateMaxQuantity(color, selectedSize); - } - } else { - setSelectedColor(null); - } - }} - width="auto" - px={4} - disabled={ - isDisabled || - (product - ? !isColorAvailable(product, color) // color is not available for all sizes - : false) || - (product && selectedSize - ? getQtyInStock(product, color, selectedSize) === 0 // color is not available for selected size - : false) - } - > - - {color} - - - ); - })} - - - ); - - const renderQuantitySection = ( - - Quantity - - handleQtyChangeCounter(false)} - > - - - - - = maxQuantity - } - active={false.toString()} - onClick={() => handleQtyChangeCounter(true)} - > - + - -
- - {product && selectedColor && selectedSize && product.is_available - ? displayStock(product, selectedColor, selectedSize) - : ""} - -
-
- - - {product && selectedColor && selectedSize - ? displayQtyInCart( - cartState.cart.items, - product.id, - selectedColor, - selectedSize - ) - : ""} - - - {product && selectedColor && selectedSize && maxQuantity === 0 - ? "You have reached the maximum purchase quantity." - : ""} - - -
- ); - - const purchaseButtons = ( - - - - - ); - - const renderMerchDetails = () => { - return ( - - - - - - {ProductNameSection} - - {renderSizeSection} - {renderColorSection} - {renderQuantitySection} - - {purchaseButtons} - - {/* {renderDescription} */} - - - - ); - }; - - const renderMerchPage = () => { - if (isLoading) return ; - if (product === undefined || product === null) return ; - return renderMerchDetails(); - }; - - return {renderMerchPage()}; -}; - -export default MerchDetail; - -export const getStaticProps: GetStaticProps<{ - slug: string; - product: Product | undefined; -}> = async ({ params }) => { - console.log("generating static props for /merch/product/[slug]"); - console.log("params", JSON.stringify(params)); - - // TODO: replace this with trpc/react-query call - if (!process.env.NEXT_PUBLIC_MERCH_API_ORIGIN) { - throw new Error("NEXT_PUBLIC_MERCH_API_ORIGIN is not defined"); - } - const res = await fetch( - `${ - process.env.NEXT_PUBLIC_MERCH_API_ORIGIN - }/trpc/getProduct?batch=1&input=${encodeURIComponent( - JSON.stringify({ "0": { id: params?.slug } }) - )}` - ); - - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access - const product = (await res.json())[0].result.data as Product; - - return { - props: { - slug: params?.slug as string, - product: product, - }, - }; -}; - -// eslint-disable-next-line @typescript-eslint/require-await -export const getStaticPaths: GetStaticPaths = async () => { - console.log("generating static paths for /merch/product/[slug]"); - - // TODO: replace this with trpc/react-query call - if (!process.env.NEXT_PUBLIC_MERCH_API_ORIGIN) { - throw new Error("NEXT_PUBLIC_MERCH_API_ORIGIN is not defined"); - } - const res = await fetch( - `${process.env.NEXT_PUBLIC_MERCH_API_ORIGIN}/trpc/getProducts` - ); - - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access - const products = (await res.json()).result.data.products as Product[]; - - return { - paths: products.map((product) => ({ - params: { - slug: product.id, - }, - })), - // https://nextjs.org/docs/pages/api-reference/functions/get-static-paths#fallback-blocking - fallback: "blocking", - }; -};