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 1 commit
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
10 changes: 9 additions & 1 deletion apps/web/features/merch/services/api.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,15 @@ export class Api {
throw new Error(e);
}
}

async getOrder(userId: string, 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
207 changes: 207 additions & 0 deletions apps/web/pages/merch/orderSummary/[slug].tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
import React, { useState } from "react";
import Image from "next/image";
import { useRouter } from "next/router";
import {
Flex,
Heading,
Text,
Divider,
Button,
Badge,
useBreakpointValue
} from "@chakra-ui/react";
import { useQuery } from "@tanstack/react-query";
import {
EmptyProductView,
Page,
} from "ui/components/merch";
import { Order, OrderStatus } from "types/lib/merch";
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 "../../../../../packages/merch/lib/orderstatus";
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("jacob" ,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 className="md:hidden">
<Flex 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}Z`).toLocaleString(
"en-sg"
)
: ""}
</Text>
{/*<Text>Last update: {orderState?.lastUpdate}</Text>*/}
</Flex>
</Flex>
</div>
<div className="hidden md:flex">
<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}Z`).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=https://dev.merch.ntuscse.com/order-summary/${orderState?.id}`
: ""
}
alt="QRCode"
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 <EmptyProductView />;}
return renderOrderSummary();
};
return <Page>{renderSummaryPage()}</Page>;
}
export default OrderSummary
27 changes: 27 additions & 0 deletions packages/merch/lib/orderstatus.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { OrderStatus } from "types";
kingsmil marked this conversation as resolved.
Show resolved Hide resolved

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";
}
};
73 changes: 73 additions & 0 deletions packages/ui/components/merch/OrderItem.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import React from "react";
import { Flex, Grid, GridItem, Image, Text, Divider, Box } from "@chakra-ui/react";
import { displayPrice } from "web/features/merch/functions/currency";
import { Order } from "types";

export type OrderItemProps = {
isMobile: boolean;
orderData: Order;
};

const OrderItem: React.FC<OrderItemProps> = (props: OrderItemProps) => {
const { isMobile, orderData } = props;

const flexItemConfig = {
alignItems: "center",
h: isMobile ? "auto" : 100,
justifyContent: isMobile ? "start" : "center",
};
{orderData.items.map((data) => {
return (
<>
<Flex my="4" justifyContent="center">
<Box boxShadow="sm" borderRadius={5} maxW={isMobile ? 150 : 100}>
<Image w="100%" borderRadius={5} src={data?.image} fallbackSrc="https://via.placeholder.com/100" />
</Box>
<Grid flex={1} templateColumns={!isMobile ? "3fr repeat(3, 1fr)" : "1fr"} rowGap={2}>
<GridItem pl="4">
<Flex h={isMobile ? "auto" : 100} flexDir="column" fontWeight="600" fontSize={isMobile ? "sm" : "md"}>
<Text color="primary.600">{data?.name}</Text>
<Flex color="grey">
Size:
<Text ml={1} textTransform="uppercase">
{data.size}
</Text>
</Flex>
<Flex color="grey">
Color:
<Text ml={1} textTransform="uppercase">
{data.color}
</Text>
</Flex>
</Flex>
</GridItem>
<GridItem pl="4">
<Flex {...flexItemConfig}>
<Text fontSize={isMobile ? "sm" : "md"} fontWeight={500}>
{isMobile && "Unit Price:"} {displayPrice(Number(data?.price) ?? 0)}
</Text>
</Flex>
</GridItem>
<GridItem pl="4">
<Flex {...flexItemConfig}>
<Text fontSize={isMobile ? "sm" : "md"} fontWeight={500}>
{isMobile && "Quantity:"} x{data?.quantity ?? 0}
</Text>
</Flex>
</GridItem>
<GridItem pl="4">
<Flex {...flexItemConfig}>
<Text fontSize={isMobile ? "sm" : "md"} fontWeight={500}>
{isMobile && "Subtotal:"} {displayPrice(Number(data.price) * data.quantity)}
</Text>
</Flex>
</GridItem>
</Grid>
</Flex>
<Divider />
</>
);});
return null;
}}

export default OrderItem;
24 changes: 24 additions & 0 deletions packages/ui/components/merch/skeleton/LoadingScreen.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import React from "react";
import { Flex, Spinner, Text } from "@chakra-ui/react";

export type LoadingScreenType = {
minH?: string;
text: string;
};
const LoadingScreen: React.FC<LoadingScreenType> = (props) => {
const { minH = "50vh", text = "" } = props;
return (
<Flex
minH={minH}
alignItems="center"
justifyContent="center"
flexDirection="column"
gap={8}
>
<Spinner thickness="4px" speed="0.65s" size="xl" />
<Text>{text}</Text>
</Flex>
);
};

export default LoadingScreen;