diff --git a/public/svg/ic_refresh.svg b/public/svg/ic_refresh.svg new file mode 100644 index 0000000..6aeba75 --- /dev/null +++ b/public/svg/ic_refresh.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/App.tsx b/src/App.tsx index 64dc0c6..83b7cbb 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -4,19 +4,29 @@ import { homeRoutes, orderInfoRoutes, adminRoutes, + authRoutes, + orderCheckRoutes, } from "@routes"; import GlobalStyle from "@styles/global"; import theme from "@styles/theme"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import { createBrowserRouter, RouterProvider } from "react-router-dom"; +import PrivateRoute from "./routes/PrivateRoute/PrivateRoute"; const allRoutes = [ ...homeRoutes, ...orderInfoRoutes, ...experienceOrderInfoRoutes, - ...adminRoutes, + ...authRoutes, + ...orderCheckRoutes, ]; -const router = createBrowserRouter([...allRoutes]); + +const protectedRoutes = adminRoutes.map((route) => ({ + ...route, + element: , +})); + +const router = createBrowserRouter([...allRoutes, ...protectedRoutes]); function App() { const queryClient = new QueryClient(); diff --git a/src/apis/api.ts b/src/apis/api.ts index ff30650..97c3b39 100644 --- a/src/apis/api.ts +++ b/src/apis/api.ts @@ -4,6 +4,13 @@ export const instance = axios.create({ baseURL: import.meta.env.VITE_APP_BASE_URL, }); +export const adminInstance = axios.create({ + baseURL: import.meta.env.VITE_APP_BASE_URL, + headers: { + Authorization: `Bearer ${localStorage.getItem("accessToken")}`, + }, +}); + export function get(...args: Parameters) { return instance.get(...args); } diff --git a/src/apis/domains/admin/usePostAdminLogin.ts b/src/apis/domains/admin/usePostAdminLogin.ts new file mode 100644 index 0000000..fe55a72 --- /dev/null +++ b/src/apis/domains/admin/usePostAdminLogin.ts @@ -0,0 +1,38 @@ +import { post } from "@apis/api"; +import { useMutation } from "@tanstack/react-query"; +import { ErrorResponse, CodeResponseType } from "@types"; +import { useNavigate } from "react-router-dom"; + +interface LoginDataType { + username: string; + password: string; +} + +const postAdminLogin = async ( + loginData: LoginDataType +): Promise => { + try { + const response = await post( + `/api/v1/admin/authenticate`, + loginData + ); + return response.data; + } catch (error) { + const errorResponse = error as ErrorResponse; + const errorData = errorResponse.response.data; + throw errorData; + } +}; + +export const usePostAdminLogin = () => { + const navigate = useNavigate(); + return useMutation({ + mutationFn: (loginData: LoginDataType) => postAdminLogin(loginData), + onSuccess: (data) => { + if (data.code === "success") { + localStorage.setItem("accessToken", data.code); + navigate("/admin"); + } + }, + }); +}; diff --git a/src/apis/domains/orderCheck/useFetchOrderInfoWithOrderNumber.ts b/src/apis/domains/orderCheck/useFetchOrderInfoWithOrderNumber.ts new file mode 100644 index 0000000..b3eb09f --- /dev/null +++ b/src/apis/domains/orderCheck/useFetchOrderInfoWithOrderNumber.ts @@ -0,0 +1,25 @@ +import { get } from "@apis/api"; +import { QUERY_KEY } from "@apis/queryKeys/queryKeys"; +import { useQuery } from "@tanstack/react-query"; +import { ApiResponseType, OrderInfoData } from "@types"; + +const getOrderInfo = async ( + orderNumber: number +): Promise => { + try { + const response = await get>( + `api/v1/order/${orderNumber}` + ); + return response.data.data; + } catch { + return null; + } +}; + +export const useFetchOrderInfoWithOrderNumber = (orderNumber: number) => { + return useQuery({ + queryKey: [QUERY_KEY.ORDER_INFO_WITH_ORDER_NUMBER], + queryFn: () => getOrderInfo(orderNumber), + enabled: false, + }); +}; diff --git a/src/apis/domains/orderCheck/useFetchRecentOrderNumber.ts b/src/apis/domains/orderCheck/useFetchRecentOrderNumber.ts new file mode 100644 index 0000000..12aa1d8 --- /dev/null +++ b/src/apis/domains/orderCheck/useFetchRecentOrderNumber.ts @@ -0,0 +1,22 @@ +import { get } from "@apis/api"; +import { QUERY_KEY } from "@apis/queryKeys/queryKeys"; +import { useQuery } from "@tanstack/react-query"; +import { ApiResponseType, RecentOrderType } from "@types"; + +const getRecentOrderNumber = async (): Promise => { + try { + const response = await get>( + "api/v1/order/recent" + ); + return response.data.data; + } catch { + return null; + } +}; + +export const useFetchRecentOrderNumber = () => { + return useQuery({ + queryKey: [QUERY_KEY.RECENT_ORDER_NUMBER], + queryFn: () => getRecentOrderNumber(), + }); +}; diff --git a/src/apis/domains/orderCheck/usePatchPayCancel.ts b/src/apis/domains/orderCheck/usePatchPayCancel.ts new file mode 100644 index 0000000..f04e9e6 --- /dev/null +++ b/src/apis/domains/orderCheck/usePatchPayCancel.ts @@ -0,0 +1,22 @@ +import { patch } from "@apis/api"; +import { useMutation } from "@tanstack/react-query"; +import { MutateResponseType } from "@types"; + +const patchPayCancel = async ( + orderNumber: number +): Promise => { + try { + const response = await patch( + `api/v1/order/cancel/${orderNumber}` + ); + return response.data; + } catch { + return null; + } +}; + +export const usePatchPayCancel = () => { + return useMutation({ + mutationFn: (orderNumber: number) => patchPayCancel(orderNumber), + }); +}; diff --git a/src/apis/domains/orderCheck/usePatchPayComplete.ts b/src/apis/domains/orderCheck/usePatchPayComplete.ts new file mode 100644 index 0000000..518dfd8 --- /dev/null +++ b/src/apis/domains/orderCheck/usePatchPayComplete.ts @@ -0,0 +1,23 @@ +import { patch } from "@apis/api"; +import { useMutation } from "@tanstack/react-query"; +import { MutateResponseType } from "@types"; + +const patchPayComplete = async ( + orderNumber: number +): Promise => { + try { + const response = await patch( + `api/v1/order/pay/${orderNumber}` + ); + return response.data; + } catch { + return null; + } +}; + +export const usePatchPayComplete = () => { + return useMutation({ + mutationFn: (orderNumber: number) => patchPayComplete(orderNumber), + onSuccess: () => {}, + }); +}; diff --git a/src/apis/queryKeys/queryKeys.ts b/src/apis/queryKeys/queryKeys.ts index 8546ff8..87fb3a2 100644 --- a/src/apis/queryKeys/queryKeys.ts +++ b/src/apis/queryKeys/queryKeys.ts @@ -6,4 +6,6 @@ export const QUERY_KEY = { PRODUCT_LIST: "productList", PRODUCT_LIST_ALL: "productListAll", SAILED_PRODUCT: "sailedProduct", + ORDER_INFO_WITH_ORDER_NUMBER: "orderInfoWithOrderNumber", + RECENT_ORDER_NUMBER: "recentOrderNumber", } as const; diff --git a/src/assets/svg/IcRefresh.tsx b/src/assets/svg/IcRefresh.tsx new file mode 100644 index 0000000..ebf161e --- /dev/null +++ b/src/assets/svg/IcRefresh.tsx @@ -0,0 +1,15 @@ +import type { SVGProps } from "react"; +const SvgIcRefresh = (props: SVGProps) => ( + + + +); +export default SvgIcRefresh; diff --git a/src/assets/svg/index.ts b/src/assets/svg/index.ts index f3ffe3e..d6e0054 100644 --- a/src/assets/svg/index.ts +++ b/src/assets/svg/index.ts @@ -12,3 +12,4 @@ export { default as IcFix } from "./IcFix"; export { default as IcMainCharacter } from "./IcMainCharacter"; export { default as IcMinus } from "./IcMinus"; export { default as IcPlus } from "./IcPlus"; +export { default as IcRefresh } from "./IcRefresh"; diff --git a/src/components/common/steps/CheckInfo/CheckInfo.tsx b/src/components/common/steps/CheckInfo/CheckInfo.tsx index 34e76d8..266b63f 100644 --- a/src/components/common/steps/CheckInfo/CheckInfo.tsx +++ b/src/components/common/steps/CheckInfo/CheckInfo.tsx @@ -1,5 +1,5 @@ import { Button, Header, ProgressBar } from "@components"; -import { ErrorType, StepProps } from "@types"; +import { StepProps } from "@types"; import { buttonSectionStyle, layoutStyle } from "@pages/orderInfo/styles"; import { checkSpanText, @@ -28,6 +28,7 @@ const CheckInfo = ({ onNext }: StepProps) => { handleAddReceiver, setOrderNumberState, handleDeleteClick, + resetOrderPostData, } = useOrderPostDataChange(); const { mutateAsync } = usePostOrder(); const receivers = orderPostDataState.recipientInfo; @@ -53,9 +54,13 @@ const CheckInfo = ({ onNext }: StepProps) => { .then((data) => { setOrderNumberState(data); onNext(); + resetOrderPostData(); }) - .catch((error: ErrorType) => { - alert(error.message); + .catch(() => { + alert( + `필수 입력칸을 작성하지 않으셨습니다. \n혹은 이미 주문을 완료하지 않으셨나요?` + ); + navigate(`/${category}`); }); }; diff --git a/src/constants/routePath.ts b/src/constants/routePath.ts index 8e5251a..4a2014d 100644 --- a/src/constants/routePath.ts +++ b/src/constants/routePath.ts @@ -22,10 +22,20 @@ const adminPages = { ADMIN_TAB: "/admin/:tab", }; +const authPages = { + ADMIN_LOGIN: "/admin/login", +}; + +const orderCheckPages = { + ORDER_CHECK: "/order-check", +}; + export default { ...productHomePages, ...experienceHomePages, ...orderInfoPages, ...experienceProductOrderInfoPages, ...adminPages, + ...authPages, + ...orderCheckPages, }; diff --git a/src/hooks/useOrderPostDataChange.ts b/src/hooks/useOrderPostDataChange.ts index a853cd5..1d69e4a 100644 --- a/src/hooks/useOrderPostDataChange.ts +++ b/src/hooks/useOrderPostDataChange.ts @@ -108,6 +108,16 @@ export const useOrderPostDataChange = () => { } }; + const resetOrderPostData = () => { + setOrderPostDataState({ + senderName: "", + senderPhone: "", + isPersonalInfoConsent: false, + isMarketingConsent: false, + recipientInfo: [], + }); + }; + return { orderPostDataState, currentRecipientIndex, @@ -119,5 +129,6 @@ export const useOrderPostDataChange = () => { orderNumberState, setOrderNumberState, handleDeleteClick, + resetOrderPostData, }; }; diff --git a/src/pages/Admin/components/OrderTable/OrderTable.style.ts b/src/pages/Admin/components/OrderTable/OrderTable.style.ts index 9f6dd26..964915c 100644 --- a/src/pages/Admin/components/OrderTable/OrderTable.style.ts +++ b/src/pages/Admin/components/OrderTable/OrderTable.style.ts @@ -64,14 +64,10 @@ export const tableStyle = (theme: Theme) => css` } /* 상품명 */ - /* th:nth-of-type(4), + th:nth-of-type(4), td:nth-of-type(4) { - } */ - - /* 주소 */ - /* th:nth-of-type(9), - td:nth-of-type(9) { - } */ + min-width: 20rem; + } `; export const checkboxStyle = css` diff --git a/src/pages/Admin/components/OrderTable/OrderTable.tsx b/src/pages/Admin/components/OrderTable/OrderTable.tsx index ff346fb..ddcaa19 100644 --- a/src/pages/Admin/components/OrderTable/OrderTable.tsx +++ b/src/pages/Admin/components/OrderTable/OrderTable.tsx @@ -159,7 +159,11 @@ const OrderTable = ({ orders }: OrderTableProps) => { {order.orderReceivedDate} {order.orderNumber} - {...order.productList} + + {order.productList.map((product) => { + return
{product}
; + })} + {order.senderName} {order.senderPhone} {order.recipientName} diff --git a/src/pages/Admin/page/AdminLogin/AdminLogin.style.ts b/src/pages/Admin/page/AdminLogin/AdminLogin.style.ts new file mode 100644 index 0000000..6ee29b4 --- /dev/null +++ b/src/pages/Admin/page/AdminLogin/AdminLogin.style.ts @@ -0,0 +1,27 @@ +import { Theme } from "@emotion/react"; +import { css } from "@emotion/react"; +import { flexGenerator } from "@styles/generator"; + +export const loginLayout = css` + width: 100%; + height: 100dvh; + ${flexGenerator()}; + padding: 0 3rem; +`; + +export const formStyle = css` + width: 100%; + ${flexGenerator("column", "flex-start", "flex-start")} +`; + +export const inputWrapper = css` + width: 100%; + ${flexGenerator("column", "flex-start", "flex-start")} + gap: 0.8rem; + + margin-bottom: 2rem; +`; + +export const labelStyle = (theme: Theme) => css` + ${theme.font["head06-b-16"]}; +`; diff --git a/src/pages/Admin/page/AdminLogin/AdminLogin.tsx b/src/pages/Admin/page/AdminLogin/AdminLogin.tsx new file mode 100644 index 0000000..19486c0 --- /dev/null +++ b/src/pages/Admin/page/AdminLogin/AdminLogin.tsx @@ -0,0 +1,63 @@ +import { Button, Input } from "@components"; +import { useState } from "react"; +import { + formStyle, + inputWrapper, + labelStyle, + loginLayout, +} from "./AdminLogin.style"; +import { usePostAdminLogin } from "@apis/domains/admin/usePostAdminLogin"; + +const AdminLogin = () => { + const [adminUsername, setAdminUsername] = useState(""); + const [adminPw, setAdminPw] = useState(""); + + const { mutate } = usePostAdminLogin(); + + const handleUsernameChange = (e: React.ChangeEvent) => { + setAdminUsername(e.target.value); + }; + + const handlePwChange = (e: React.ChangeEvent) => { + setAdminPw(e.target.value); + }; + + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault(); + mutate({ username: adminUsername, password: adminPw }); + }; + + return ( +
+
+
+ + +
+
+ + +
+ +
+
+ ); +}; + +export default AdminLogin; diff --git a/src/pages/Admin/page/AdminPage/AdminPage.tsx b/src/pages/Admin/page/AdminPage/AdminPage.tsx index e775e6c..0408bdf 100644 --- a/src/pages/Admin/page/AdminPage/AdminPage.tsx +++ b/src/pages/Admin/page/AdminPage/AdminPage.tsx @@ -7,7 +7,7 @@ import { tapLayoutStyle, } from "./AdminPage.style"; import { useNavigate, useParams } from "react-router-dom"; -import { OrderCheck, ProductCheck, DeliveryCheck } from "./"; +import { OrderCheck, ProductCheck, DeliveryCheck } from ".."; const Admin = () => { const { tab = "order" } = useParams<{ tab: string }>(); diff --git a/src/pages/Admin/page/AdminPage/index.ts b/src/pages/Admin/page/AdminPage/index.ts deleted file mode 100644 index 5860163..0000000 --- a/src/pages/Admin/page/AdminPage/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -import OrderCheck from "./OrderCheck/OrderCheck"; -import ProductCheck from "./ProductCheck/ProductCheck"; -import DeliveryCheck from "./DeliveryCheck/DeliveryCheck"; - -export { OrderCheck, ProductCheck, DeliveryCheck }; diff --git a/src/pages/Admin/page/index.ts b/src/pages/Admin/page/index.ts new file mode 100644 index 0000000..8712911 --- /dev/null +++ b/src/pages/Admin/page/index.ts @@ -0,0 +1,6 @@ +import OrderCheck from "./AdminPage/OrderCheck/OrderCheck"; +import ProductCheck from "./AdminPage/ProductCheck/ProductCheck"; +import DeliveryCheck from "./AdminPage/DeliveryCheck/DeliveryCheck"; +import AdminLogin from "./AdminLogin/AdminLogin"; + +export { OrderCheck, ProductCheck, DeliveryCheck, AdminLogin }; diff --git a/src/pages/orderCheck/components/DialButton/DialButton.style.ts b/src/pages/orderCheck/components/DialButton/DialButton.style.ts new file mode 100644 index 0000000..369750a --- /dev/null +++ b/src/pages/orderCheck/components/DialButton/DialButton.style.ts @@ -0,0 +1,19 @@ +import { Theme, css } from "@emotion/react"; +import { flexGenerator } from "@styles/generator"; + +export const buttonStyle = (index: number) => (theme: Theme) => + css` + ${flexGenerator()}; + padding: 1rem; + width: 15rem; + height: 12.8rem; + border: 1px solid ${theme.color.lightgray3}; + background-color: ${index === 9 || index === 11 + ? theme.color.lightgray2 + : theme.color.white}; + `; + +export const buttonSpan = (theme: Theme) => css` + color: ${theme.color.black}; + ${theme.font["dialNumber-56"]} +`; diff --git a/src/pages/orderCheck/components/DialButton/DialButton.tsx b/src/pages/orderCheck/components/DialButton/DialButton.tsx new file mode 100644 index 0000000..7b088bf --- /dev/null +++ b/src/pages/orderCheck/components/DialButton/DialButton.tsx @@ -0,0 +1,18 @@ +import { ButtonHTMLAttributes } from "react"; +import { buttonSpan, buttonStyle } from "./DialButton.style"; + +export interface DialButtonProps + extends ButtonHTMLAttributes { + onClick: () => void; + index: number; +} + +const DialButton = ({ onClick, index, children }: DialButtonProps) => { + return ( + + ); +}; + +export default DialButton; diff --git a/src/pages/orderCheck/components/OrderInfoSection/OrderInfoSection.style.ts b/src/pages/orderCheck/components/OrderInfoSection/OrderInfoSection.style.ts new file mode 100644 index 0000000..50a92fe --- /dev/null +++ b/src/pages/orderCheck/components/OrderInfoSection/OrderInfoSection.style.ts @@ -0,0 +1,32 @@ +import { css, Theme } from "@emotion/react"; +import { flexGenerator } from "@styles/generator"; + +export const section3Container = (theme: Theme) => css` + ${flexGenerator("column", "start", "start")}; + width: 38rem; + min-height: 100%; + background-color: ${theme.color.white}; +`; +export const section3InfoWrapper = css` + ${flexGenerator("column", "start", "start")}; + gap: 2rem; +`; + +export const section3Div = css` + ${flexGenerator("column", "start", "start")}; + gap: 0.5rem; +`; +export const graySpan = (theme: Theme) => css` + color: ${theme.color.lightgray4}; + ${theme.font["head01-b-24"]} +`; +export const blackSpan = (theme: Theme) => css` + color: ${theme.color.black}; + ${theme.font["orderCheck-36"]} +`; + +export const buttonWrapper = css` + ${flexGenerator("row", "space-between", "center")}; + width: 100%; + margin-top: auto; +`; diff --git a/src/pages/orderCheck/components/OrderInfoSection/OrderInfoSection.tsx b/src/pages/orderCheck/components/OrderInfoSection/OrderInfoSection.tsx new file mode 100644 index 0000000..e2d5f40 --- /dev/null +++ b/src/pages/orderCheck/components/OrderInfoSection/OrderInfoSection.tsx @@ -0,0 +1,65 @@ +import { orderInfoAtom, previousOrderNumberAtom } from "@stores"; +import PayButton from "../PayButton/PayButton"; +import { + blackSpan, + buttonWrapper, + graySpan, + section3Container, + section3Div, + section3InfoWrapper, +} from "./OrderInfoSection.style"; +import { useAtom } from "jotai"; +import { usePatchPayComplete } from "@apis/domains/orderCheck/usePatchPayComplete"; +import { usePatchPayCancel } from "@apis/domains/orderCheck/usePatchPayCancel"; + +const OrderInfoSection = () => { + const [previousOrderNumber] = useAtom(previousOrderNumberAtom); + const [orderInfo] = useAtom(orderInfoAtom); + + const { mutate: mutatePayComplete } = usePatchPayComplete(); + const { mutate: mutatePayCancel } = usePatchPayCancel(); + + const handlePayCancel = () => { + mutatePayCancel(Number(previousOrderNumber)); + }; + const handlePayComplete = () => { + mutatePayComplete(Number(previousOrderNumber)); + }; + return ( +
+
+
+ 주문번호 + {previousOrderNumber} +
+
+ 이름 + {orderInfo?.senderName} +
+
+ 상품 + {orderInfo?.orderList.map((order, i) => ( + {`${order.productName} ${order.productCount}개`} + ))} +
+
+ 총 금액 + {orderInfo?.totalPrice}원 +
+
+
+ + 결제취소 + + + 결제완료 + +
+
+ ); +}; + +export default OrderInfoSection; diff --git a/src/pages/orderCheck/components/OrderNumberSearchSection/OrderNumberSearchSection.style.ts b/src/pages/orderCheck/components/OrderNumberSearchSection/OrderNumberSearchSection.style.ts new file mode 100644 index 0000000..410c060 --- /dev/null +++ b/src/pages/orderCheck/components/OrderNumberSearchSection/OrderNumberSearchSection.style.ts @@ -0,0 +1,41 @@ +import { css, Theme } from "@emotion/react"; +import { flexGenerator } from "@styles/generator"; + +export const section1Container = (theme: Theme) => css` + ${flexGenerator("column", "start", "start")}; + padding: 2rem; + width: 27rem; + height: 100%; + gap: 1.5rem; + border: 1px solid ${theme.color.lightgray3}; + background-color: ${theme.color.white}; + border-radius: 10px; + overflow: scroll; +`; + +export const section2Container = css` + ${flexGenerator("column")}; + width: 45rem; + height: 100%; +`; + +export const orderNumberStyle = (theme: Theme) => css` + ${flexGenerator()} + width: 100%; + height: 12.6rem; + padding: 2rem 0; + border: 1px solid ${theme.color.lightgray3}; + background-color: ${theme.color.white}; +`; + +export const orderNumberSpan = (theme: Theme) => css` + color: ${theme.color.black}; + ${theme.font["dialNumber-72"]} +`; + +export const dialButtonWrapper = css` + width: 100%; + display: grid; + grid-template-columns: repeat(3, 1fr); + grid-template-rows: repeat(4, 1fr); +`; diff --git a/src/pages/orderCheck/components/OrderNumberSearchSection/OrderNumberSearchSection.tsx b/src/pages/orderCheck/components/OrderNumberSearchSection/OrderNumberSearchSection.tsx new file mode 100644 index 0000000..dbbe213 --- /dev/null +++ b/src/pages/orderCheck/components/OrderNumberSearchSection/OrderNumberSearchSection.tsx @@ -0,0 +1,103 @@ +import DialButton from "../DialButton/DialButton"; +import { + dialButtonWrapper, + orderNumberSpan, + orderNumberStyle, + section1Container, + section2Container, +} from "./OrderNumberSearchSection.style"; +import { useFetchOrderInfoWithOrderNumber } from "@apis/domains/orderCheck/useFetchOrderInfoWithOrderNumber"; +import { useAtom } from "jotai"; +import { + orderInfoAtom, + orderNumberAtom, + previousOrderNumberAtom, +} from "@stores"; +import { RecentOrderType } from "@types"; +import RecentOrderCard from "../RecentOrderCard/RecentOrderCard"; +import { useEffect, useState } from "react"; + +interface OrderTrackingSectionProps { + recentOrderList: RecentOrderType[] | null | undefined; +} + +const OrderNumberSearchSection = ({ + recentOrderList, +}: OrderTrackingSectionProps) => { + const [orderNumber, setOrderNumber] = useAtom(orderNumberAtom); + const [, setOrderInfo] = useAtom(orderInfoAtom); + const [, setPreviousOrderNumber] = useAtom(previousOrderNumberAtom); + const [triggerSearch, setTriggerSearch] = useState(false); + const { refetch } = useFetchOrderInfoWithOrderNumber(Number(orderNumber)); + + useEffect(() => { + if (triggerSearch) { + handleSearch(); + setTriggerSearch(false); + } + }, [orderNumber, triggerSearch]); + + const handleButtonClick = (value: string) => { + if (orderNumber.length < 4) { + setOrderNumber((prev) => prev + value); + } + }; + + const handleDelete = () => { + setOrderNumber((prev) => prev.slice(0, -1)); + }; + + const handleSearch = async () => { + const result = await refetch(); + if (result.data === null) { + alert("주문번호에 대한 주문내역이 존재하지 않습니다."); + } + setOrderInfo(result?.data ?? null); + setPreviousOrderNumber(orderNumber); + setOrderNumber(""); + }; + + const handleRecentOrderClick = (orderNumber: string) => { + setOrderNumber(orderNumber); + setTriggerSearch(true); + }; + + return ( + <> +
+ {recentOrderList?.map((order, i) => ( + handleRecentOrderClick(order.orderNumber.toString())} + /> + ))} +
+
+
+ {orderNumber} +
+
+ {["1", "2", "3", "4", "5", "6", "7", "8", "9", "<-", "0", "조회"].map( + (item, i) => ( + { + if (item === "<-") handleDelete(); + else if (item === "조회") handleSearch(); + else handleButtonClick(item); + }} + index={i} + > + {item} + + ) + )} +
+
+ + ); +}; + +export default OrderNumberSearchSection; diff --git a/src/pages/orderCheck/components/PayButton/PayButton.style.ts b/src/pages/orderCheck/components/PayButton/PayButton.style.ts new file mode 100644 index 0000000..dbe3cae --- /dev/null +++ b/src/pages/orderCheck/components/PayButton/PayButton.style.ts @@ -0,0 +1,23 @@ +import { css, Theme } from "@emotion/react"; +import { flexGenerator } from "@styles/generator"; + +export const buttonStyle = (theme: Theme) => css` + ${flexGenerator()}; + width: 18rem; + height: 8.8rem; + padding: 1rem; + border: 1px solid ${theme.color.black}; + border-radius: 20px; + ${theme.font["orderCheck-36"]} +`; + +export const buttonVariant = { + fill: (theme: Theme) => css` + color: ${theme.color.white}; + background-color: ${theme.color.orange}; + `, + stroke: (theme: Theme) => css` + color: ${theme.color.black}; + background-color: ${theme.color.white}; + `, +}; diff --git a/src/pages/orderCheck/components/PayButton/PayButton.tsx b/src/pages/orderCheck/components/PayButton/PayButton.tsx new file mode 100644 index 0000000..285b283 --- /dev/null +++ b/src/pages/orderCheck/components/PayButton/PayButton.tsx @@ -0,0 +1,18 @@ +import { ButtonHTMLAttributes } from "react"; +import { buttonStyle, buttonVariant } from "./PayButton.style"; + +export interface PayButtonProps + extends ButtonHTMLAttributes { + variant: "fill" | "stroke"; + onClick: () => void; +} + +const PayButton = ({ variant, onClick, children }: PayButtonProps) => { + return ( + + ); +}; + +export default PayButton; diff --git a/src/pages/orderCheck/components/RecentOrderCard/RecentOrderCard.style.ts b/src/pages/orderCheck/components/RecentOrderCard/RecentOrderCard.style.ts new file mode 100644 index 0000000..a9b8d83 --- /dev/null +++ b/src/pages/orderCheck/components/RecentOrderCard/RecentOrderCard.style.ts @@ -0,0 +1,21 @@ +import { css, Theme } from "@emotion/react"; +import { flexGenerator } from "@styles/generator"; + +export const cardWrapper = (theme: Theme) => css` + ${flexGenerator("row", "space-between", "center")}; + gap: 1rem; + width: 100%; + padding: 1.5rem 1.2rem; + border-radius: 10px; + background-color: ${theme.color.lightorange}; +`; + +export const numberStyle = (theme: Theme) => css` + color: ${theme.color.orange}; + ${theme.font["orderCheck-32"]} +`; + +export const nameStyle = (theme: Theme) => css` + color: ${theme.color.black}; + ${theme.font["orderCheck-32"]} +`; diff --git a/src/pages/orderCheck/components/RecentOrderCard/RecentOrderCard.tsx b/src/pages/orderCheck/components/RecentOrderCard/RecentOrderCard.tsx new file mode 100644 index 0000000..82820bd --- /dev/null +++ b/src/pages/orderCheck/components/RecentOrderCard/RecentOrderCard.tsx @@ -0,0 +1,22 @@ +import { cardWrapper, nameStyle, numberStyle } from "./RecentOrderCard.style"; + +interface RecentOrderCardProps { + orderNumber: number; + senderName: string; + onClick: () => void; +} + +const RecentOrderCard = ({ + orderNumber, + senderName, + onClick, +}: RecentOrderCardProps) => { + return ( +
+ {orderNumber}번 + {senderName} +
+ ); +}; + +export default RecentOrderCard; diff --git a/src/pages/orderCheck/components/index.ts b/src/pages/orderCheck/components/index.ts new file mode 100644 index 0000000..42ca083 --- /dev/null +++ b/src/pages/orderCheck/components/index.ts @@ -0,0 +1,13 @@ +import DialButton from "./DialButton/DialButton"; +import PayButton from "./PayButton/PayButton"; +import RecentOrderCard from "./RecentOrderCard/RecentOrderCard"; +import OrderNumberSearchSection from "./OrderNumberSearchSection/OrderNumberSearchSection"; +import OrderInfoSection from "./OrderInfoSection/OrderInfoSection"; + +export { + DialButton, + PayButton, + RecentOrderCard, + OrderNumberSearchSection, + OrderInfoSection, +}; diff --git a/src/pages/orderCheck/page/OrderCheckPage.style.ts b/src/pages/orderCheck/page/OrderCheckPage.style.ts new file mode 100644 index 0000000..558d71d --- /dev/null +++ b/src/pages/orderCheck/page/OrderCheckPage.style.ts @@ -0,0 +1,31 @@ +import { css, Theme } from "@emotion/react"; +import { flexGenerator } from "@styles/generator"; + +export const orderCheckLayout = (theme: Theme) => css` + ${flexGenerator()}; + position: absolute; + top: 0; + left: 0; + padding: 9.3rem 6.1rem 6.7rem; + gap: 3rem; + width: 100vw; + height: 100%; + background-color: ${theme.color.white}; +`; + +export const refreshButton = (theme: Theme) => css` + position: absolute; + top: 50px; + right: 50px; + ${flexGenerator()}; + width: 8rem; + height: 8rem; + border-radius: 40px; + background-color: ${theme.color.lightorange}; + cursor: pointer; +`; + +export const iconStyle = css` + width: 3.2rem; + height: 3.2rem; +`; diff --git a/src/pages/orderCheck/page/OrderCheckPage.tsx b/src/pages/orderCheck/page/OrderCheckPage.tsx new file mode 100644 index 0000000..2bd5d72 --- /dev/null +++ b/src/pages/orderCheck/page/OrderCheckPage.tsx @@ -0,0 +1,33 @@ +import { + iconStyle, + orderCheckLayout, + refreshButton, +} from "./OrderCheckPage.style"; +import { + // OrderTrackingSection, + OrderNumberSearchSection, + OrderInfoSection, +} from "../components"; +import { IcRefresh } from "@svg"; +import { useFetchRecentOrderNumber } from "@apis/domains/orderCheck/useFetchRecentOrderNumber"; + +const OrderCheckPage = () => { + const { data: recentOrderList, refetch } = useFetchRecentOrderNumber(); + + const handleRefresh = () => { + refetch(); + }; + + return ( +
+ {/* */} + + +
+ +
+
+ ); +}; + +export default OrderCheckPage; diff --git a/src/routes/PrivateRoute/PrivateRoute.tsx b/src/routes/PrivateRoute/PrivateRoute.tsx new file mode 100644 index 0000000..86b5038 --- /dev/null +++ b/src/routes/PrivateRoute/PrivateRoute.tsx @@ -0,0 +1,14 @@ +import { ReactNode } from "react"; +import { Navigate } from "react-router-dom"; + +import { isLoggedIn } from "@utils"; + +interface PrivateRouteProps { + element: ReactNode; +} + +const PrivateRoute = ({ element }: PrivateRouteProps) => { + return isLoggedIn() ? element : ; +}; + +export default PrivateRoute; diff --git a/src/routes/adminRoutes.tsx b/src/routes/adminRoutes.tsx index 8e6db02..87e9719 100644 --- a/src/routes/adminRoutes.tsx +++ b/src/routes/adminRoutes.tsx @@ -1,4 +1,5 @@ import { routePath } from "@constants"; +import { AdminLogin } from "@pages/Admin/page"; import { Admin } from "@pages/index"; import { RouteType } from "@types"; import { Navigate } from "react-router-dom"; @@ -8,6 +9,10 @@ const adminRoutes: RouteType[] = [ path: routePath.ADMIN, element: , }, + { + path: routePath.ADMIN_LOGIN, + element: , + }, { path: routePath.ADMIN_TAB, element: , diff --git a/src/routes/authRoutes.tsx b/src/routes/authRoutes.tsx new file mode 100644 index 0000000..92e9bee --- /dev/null +++ b/src/routes/authRoutes.tsx @@ -0,0 +1,12 @@ +import { routePath } from "@constants"; +import { AdminLogin } from "@pages/Admin/page"; +import { RouteType } from "@types"; + +const authRoutes: RouteType[] = [ + { + path: routePath.ADMIN_LOGIN, + element: , + }, +]; + +export default authRoutes; diff --git a/src/routes/index.ts b/src/routes/index.ts index 874ac1a..e0120ea 100644 --- a/src/routes/index.ts +++ b/src/routes/index.ts @@ -2,5 +2,14 @@ import homeRoutes from "./homeRoutes"; import orderInfoRoutes from "./orderInfoRoutes"; import experienceOrderInfoRoutes from "./experienceOrderInfoRoutes"; import adminRoutes from "./adminRoutes"; +import authRoutes from "./authRoutes"; +import orderCheckRoutes from "./orderCheckRoutes"; -export { homeRoutes, orderInfoRoutes, experienceOrderInfoRoutes, adminRoutes}; +export { + homeRoutes, + orderInfoRoutes, + experienceOrderInfoRoutes, + adminRoutes, + authRoutes, + orderCheckRoutes, +}; diff --git a/src/routes/orderCheckRoutes.tsx b/src/routes/orderCheckRoutes.tsx new file mode 100644 index 0000000..a06aa70 --- /dev/null +++ b/src/routes/orderCheckRoutes.tsx @@ -0,0 +1,12 @@ +import { routePath } from "@constants"; +import OrderCheckPage from "@pages/orderCheck/page/OrderCheckPage"; +import { RouteType } from "@types"; + +const orderCheckRoutes: RouteType[] = [ + { + path: routePath.ORDER_CHECK, + element: , + }, +]; + +export default orderCheckRoutes; diff --git a/src/stores/index.ts b/src/stores/index.ts index 63be496..b57e9ad 100644 --- a/src/stores/index.ts +++ b/src/stores/index.ts @@ -3,6 +3,9 @@ import { currentRecipient } from "./currentRecipientIndex"; import { productListAtom } from "./productList"; import { categoryAtom } from "./category"; import { orderNumber } from "./orderNumber"; +import { orderNumberAtom } from "./orderInfo"; +import { previousOrderNumberAtom } from "./orderInfo"; +import { orderInfoAtom } from "./orderInfo"; export { orderPostAtom, @@ -10,4 +13,7 @@ export { productListAtom, categoryAtom, orderNumber, + orderNumberAtom, + previousOrderNumberAtom, + orderInfoAtom, }; diff --git a/src/stores/orderInfo.ts b/src/stores/orderInfo.ts new file mode 100644 index 0000000..091338c --- /dev/null +++ b/src/stores/orderInfo.ts @@ -0,0 +1,6 @@ +import { OrderInfoData } from "@types"; +import { atom } from "jotai"; + +export const orderNumberAtom = atom(""); +export const previousOrderNumberAtom = atom(""); +export const orderInfoAtom = atom(null); diff --git a/src/styles/global.ts b/src/styles/global.ts index b8a8abe..15561e0 100644 --- a/src/styles/global.ts +++ b/src/styles/global.ts @@ -1,6 +1,6 @@ -import { css } from '@emotion/react'; +import { css } from "@emotion/react"; -import Reset from './reset'; +import Reset from "./reset"; const GlobalStyle = css` ${Reset} @@ -11,6 +11,10 @@ const GlobalStyle = css` :root { --min-width: 375px; --max-width: 430px; + --tablet-min-width: 768px; + --tablet-max-width: 1366px; + --tablet-ratio-width: 1280px; + --tablet-ratio-height: 800px; } html, diff --git a/src/styles/theme.ts b/src/styles/theme.ts index b943515..068da0d 100644 --- a/src/styles/theme.ts +++ b/src/styles/theme.ts @@ -14,6 +14,8 @@ const theme = { red: "#ff2c2c", lightgray1: "#DFE2E7", lightgray2: "#D9D9D9", + lightgray3: "#C4C4C4", + lightgray4: "#B6B6B6", midgray1: "#9FA4AE", midgray2: "#F1F1F1", midgray3: "#6B6F77", @@ -22,6 +24,7 @@ const theme = { background2: "#F5F5F5", orange: "#EC6732", + lightorange: "#FFEDE7", green: "#3CA178", }, font: { @@ -115,6 +118,31 @@ const theme = { font-weight: 400; line-height: 140%; `, + "dialNumber-56": css` + ${PretendardFont} + font-size: 5.6rem; + font-weight: 600; + line-height: normal; + `, + "dialNumber-72": css` + ${PretendardFont} + font-size: 7.2rem; + font-weight: 700; + line-height: normal; + letter-spacing: 10px; + `, + "orderCheck-36": css` + ${PretendardFont} + font-size: 3.6rem; + font-weight: 700; + line-height: normal; + `, + "orderCheck-32": css` + ${PretendardFont} + font-size: 3.2rem; + font-weight: 700; + line-height: normal; + `, }, }; diff --git a/src/types/commonType.ts b/src/types/commonType.ts index 961975c..58250f3 100644 --- a/src/types/commonType.ts +++ b/src/types/commonType.ts @@ -32,3 +32,7 @@ export interface OrderNumberType { code: string; data: number; } + +export interface CodeResponseType { + code: string; +} diff --git a/src/types/index.ts b/src/types/index.ts index 83ac750..27989bd 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -3,3 +3,5 @@ export * from "./nextStep"; export * from "./commonType"; export * from "./productType"; export * from "./orderType"; +export * from "./orderInfoWithOrderNumber"; +export * from "./recentOrderType"; diff --git a/src/types/orderInfoWithOrderNumber.ts b/src/types/orderInfoWithOrderNumber.ts new file mode 100644 index 0000000..22caaee --- /dev/null +++ b/src/types/orderInfoWithOrderNumber.ts @@ -0,0 +1,12 @@ +export interface OrderInfo { + productName: string; + productCount: number; + orderState: string; + price: number; +} + +export interface OrderInfoData { + senderName: string; + orderList: OrderInfo[]; + totalPrice: number; +} diff --git a/src/types/recentOrderType.ts b/src/types/recentOrderType.ts new file mode 100644 index 0000000..8acfda9 --- /dev/null +++ b/src/types/recentOrderType.ts @@ -0,0 +1,4 @@ +export interface RecentOrderType { + orderNumber: number; + senderName: string; +} diff --git a/src/utils/auth.ts b/src/utils/auth.ts new file mode 100644 index 0000000..84c959b --- /dev/null +++ b/src/utils/auth.ts @@ -0,0 +1,7 @@ +export const getToken = () => { + return localStorage.getItem("accessToken"); +}; + +export const isLoggedIn = () => { + return getToken() ? true : false; +}; diff --git a/src/utils/index.ts b/src/utils/index.ts index 8642f44..5fdf69c 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -1,4 +1,3 @@ -import { formatPhoneNumber } from "./phoneNumber"; -import { getTwoDaysLaterDate } from "./getDate"; - -export { formatPhoneNumber, getTwoDaysLaterDate }; +export * from "./phoneNumber"; +export * from "./getDate"; +export * from "./auth";