Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[SCSE-249] Port thank you page #99

Closed
wants to merge 8 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions apps/web/.env.example
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
WORDPRESS_API_URL=
NEXT_PUBLIC_MERCH_API_ORIGIN=
NEXT_PUBLIC_FRONTEND_URL='https://clubs.ntu.edu.sg/csec/'
12 changes: 10 additions & 2 deletions apps/web/features/merch/services/api.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Product } from "types/lib/merch";
import { Product } from 'types'

export class Api {
private API_ORIGIN: string;
Expand Down Expand Up @@ -63,7 +63,15 @@ export class Api {
throw new Error(e);
}
}

async getOrder(orderId: string) {
try {
const res = await this.get(`/orders/${orderId}`);
console.log("Order Summary response:", res);
return res;
} catch (e: any) {
throw new Error(e);
}
}
/*
async getOrder(userId: string, orderId: string) {
try {
Expand Down
10 changes: 7 additions & 3 deletions apps/web/next.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
const nextConfig = {
reactStrictMode: true,
swcMinify: true,
transpilePackages: ['ui', 'merch-helpers'],
images: {
remotePatterns: [
{
Expand All @@ -19,10 +20,13 @@ const nextConfig = {
hostname: "cdn.ntuscse.com",
pathname: "/merch/products/images/**",
},
{
protocol:"https",
hostname: "api.qrserver.com",
pathname: "/merch/order/**"
}
],
},
};

const withTM = require("next-transpile-modules")(["ui"]);

module.exports = withTM(nextConfig);
module.exports = nextConfig;
4 changes: 2 additions & 2 deletions apps/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@
"@tanstack/react-query": "^4.26.1",
"@tanstack/react-query-devtools": "^4.26.1",
"framer-motion": "^7.6.4",
"next": "13.0.0",
"next-transpile-modules": "^10.0.0",
"merch-helpers": "*",
"next": "13.4.0",
"react": "18.2.0",
"react-bootstrap": "^2.5.0",
"react-dom": "18.2.0",
Expand Down
199 changes: 199 additions & 0 deletions apps/web/pages/merch/order-summary/[slug].tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
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;

const [showThankYou, setShowThankYou] = useState<boolean>(false);
const [orderState, setOrderState] = useState<Order | null>(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),
{
onSuccess: (data: Order) => {
console.log(data);
setOrderState(data);
setTotal(
data.items.reduce((acc, item) => {
return Number(item.price) * item.quantity + acc;
}, 0)
);
setShowThankYou(true);
},
}
);

const renderThankYouMessage = () => (
<>
<Heading size="xl">THANK YOU</Heading>
<Text>Thank you for your purchase. We have received your order.</Text>
<Link href={routes.HOME}>
<Button borderRadius={0} size="sm">
CONTINUE SHOPPING
</Button>
</Link>
<Divider my={8} />
</>
);
const renderOrderSummary = () => (
<>
<Flex flexDirection="column" alignItems="center" rowGap={3}>
{showThankYou && renderThankYouMessage()}
</Flex>

<Flex
p={6}
borderWidth="1px"
borderRadius="lg"
overflow="hidden"
flexDir="column"
>
<div>
<Flex display= { { base: "flex", md: "none" } } justifyContent="space-between">
<Flex flexDir="column" w="100%">
<Badge
width="fit-content"
fontSize="sm"
mb={2}
color={getOrderStatusColor(orderState?.status ?? OrderStatus.PENDING_PAYMENT)}
>
{renderOrderStatus(orderState?.status ?? OrderStatus.PENDING_PAYMENT)}
</Badge>
<Heading size="md">Order Number</Heading>
<Heading size="lg">
{orderState?.id.split("-")[0]}
</Heading>
<Flex alignItems="center" mb={2}>
<Text fontSize="sm">{orderState?.id}</Text>
</Flex>
<Text fontSize="sm" color="grey">
Order date:{" "}
{orderState?.transaction_time
? new Date(`${orderState.transaction_time}`).toLocaleString(
"en-sg"
)
: ""}
</Text>
{/*<Text>Last update: {orderState?.lastUpdate}</Text>*/}
</Flex>
</Flex>
</div>
<div>
<Flex display= { { base: "none", md: "flex" } } justifyContent="space-between">
<Flex flexDir="column">
<Flex alignItems="center" gap={6}>
<Heading size="md">Order Number</Heading>
<Badge
width="fit-content"
fontSize="sm"
color={getOrderStatusColor(orderState?.status ?? OrderStatus.PENDING_PAYMENT)}
>
{renderOrderStatus(orderState?.status ?? OrderStatus.PENDING_PAYMENT)}
</Badge>
</Flex>
<Heading size="lg" mb={2}>
{orderState?.id.split("-")[0]}
</Heading>
<Flex alignItems="center">
<Text fontSize="sm">{orderState?.id}</Text>
</Flex>
</Flex>
<Flex flexDir="column" fontSize="sm" color="grey">
<Text>
Order date:{" "}
{orderState?.transaction_time
? new Date(`${orderState.transaction_time}`).toLocaleString(
"en-sg"
)
: ""}
</Text>
{/*<Text>Last update: {orderState?.lastUpdate}</Text>*/}
</Flex>
</Flex>
</div>
<Divider my={4} />
{/*{orderState?.items.map((item) => (*/}
{/* <OrderItem data={item} isMobile={isMobile} />*/}
{/*))}*/}

{orderState? <OrderItem isMobile={isMobile} orderData={orderState}/>: <Text>Order Not Found</Text>}


<Flex alignItems="end" flexDirection="row" gap={1} mt={4}>
<Flex flexDir="column" flex={1} textAlign="end" fontWeight={500}>
<Text>Item Subtotal:</Text>
<Text>Voucher Discount:</Text>
<Text>Total:</Text>
</Flex>
<Flex flexDir="column" textAlign="end">
<Text fontSize="md"> {displayPrice(total)}</Text>
<Text fontSize="md">
{/*{displayPrice(*/}
{/* (orderState?.billing?.subtotal ?? 0) -*/}
{/* (orderState?.billing?.total ?? 0)*/}
{/*)}*/}
0
</Text>
<Text fontSize="md">{displayPrice(total)}</Text>
</Flex>
</Flex>
</Flex>

<Flex
mt={6}
alignItems="center"
py={3}
borderRadius="lg"
borderWidth="1px"
flexDirection="column"
rowGap={4}
>
{/* TODO: QR Code generator based on Param. */}
<Image
src={
orderState
? `https://api.qrserver.com/v1/create-qr-code/?size=150x150&data=${ process.env.NEXT_PUBLIC_FRONTEND_URL ?? "localhost:3001"}/merch/orderSummary/${orderState?.id}`
: ""
}
alt="QRCode"
width={150}
height={150}
sizes="(max-width: 768px)"
/>
<Text fontWeight="bold">
Please screenshot this QR code and show it at SCSE Lounge to collect your order.
Alternatively, show the email receipt you have received.
</Text>
<Text>
For any assistance, please contact our email address:
merch@ntuscse.com
</Text>
</Flex>
</>
);
const renderSummaryPage = () => {
if (isLoading) return <LoadingScreen text="Fetching order detail" />;
//rmb to change this v
if (orderState === undefined || orderState === null){return <LoadingScreen text="Order Does Not Exist" />;}
return renderOrderSummary();
};
return <Page>{renderSummaryPage()}</Page>;
}
export default OrderSummary
3 changes: 3 additions & 0 deletions packages/merch-helpers/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# merch-helpers

todo...
16 changes: 16 additions & 0 deletions packages/merch-helpers/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"name": "merch-helpers",
"version": "0.0.1",
"main": "src/index.ts",
"license": "Apache-2.0",
"scripts": {
"lint": "TIMING=1 eslint \"**/*.ts*\"",
"lint:fix": "TIMING=1 eslint --fix \"**/*.ts*\""
},
"devDependencies": {
"eslint": "^7.32.0",
"eslint-config-custom": "*",
"tsconfig": "*",
"typescript": "^4.5.2"
}
}
2 changes: 2 additions & 0 deletions packages/merch-helpers/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from "./lib/orderstatus";
export * from "./lib/price";
26 changes: 26 additions & 0 deletions packages/merch-helpers/src/lib/orderstatus.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { OrderStatus } from 'types'
export const renderOrderStatus = (status: OrderStatus) => {
switch (status) {
case OrderStatus.ORDER_COMPLETED:
return "Order Collected";
case OrderStatus.PAYMENT_COMPLETED:
return "Processing";
case OrderStatus.PENDING_PAYMENT:
return "Order Received";
default:
return "Item Delayed";
}
};

export const getOrderStatusColor = (status: OrderStatus) => {
switch (status) {
case OrderStatus.ORDER_COMPLETED:
return "green.500";
case OrderStatus.PAYMENT_COMPLETED:
return "primary.400";
case OrderStatus.PENDING_PAYMENT:
return "primary.600";
default:
return "red.500";
}
};
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Cart, PricedCart, Product, Promotion } from "types";
import { Cart, PricedCart, Product, Promotion, PromoType } from "types";

export const calculatePricing = (
products: Product[],
Expand Down Expand Up @@ -33,10 +33,10 @@ export const calculatePricing = (
continue;
}
switch (discount.promoType) {
case FIXED_VALUE:
case PromoType.FIXED_VALUE:
itemPrice -= discount.promoValue;
break;
case PERCENTAGE:
case PromoType.PERCENTAGE:
itemPrice *= 1 - discount.promoValue;
itemPrice = Math.floor(itemPrice);
break;
Expand Down
File renamed without changes.
2 changes: 1 addition & 1 deletion packages/types/lib/merch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ export interface PricedCart {
}[];
}

enum PromoType {
export enum PromoType {
PERCENTAGE = "PERCENTAGE",
FIXED_VALUE = "FIXED_VALUE",
}
Expand Down
Loading