diff --git a/src/app/(header-nav)/hakgwan/page.tsx b/src/app/(header-nav)/hakgwan/page.tsx
index 84ecae1..b8d2e8b 100644
--- a/src/app/(header-nav)/hakgwan/page.tsx
+++ b/src/app/(header-nav)/hakgwan/page.tsx
@@ -1,7 +1,8 @@
import AdBannerCarousel from "@/features/banner/components/AdBannerCarousel";
import styles from "./page.module.css";
-import { getHakgwanMenus, HakgwanMenuData } from "@/features/menu/services/hakgwanMenuService";
+import { getHakgwanMenus } from "@/features/menu/services/hakgwanMenuService.server";
import MenuPageContainer from "@/features/menu/components/MenuPageContainer";
+import { HakgwanMenuData } from "@/features/menu/types/hakgwanType";
export default async function HakgwanMainPage() {
diff --git a/src/app/(header-nav)/my/page.tsx b/src/app/(header-nav)/my/page.tsx
index 8de7145..0b862f9 100644
--- a/src/app/(header-nav)/my/page.tsx
+++ b/src/app/(header-nav)/my/page.tsx
@@ -1,7 +1,7 @@
"use client" // TODO: 추후 로그아웃 정상 구현 후 SSR 변경
import { useRouter } from "next/navigation";
-import { logout } from "@/features/auth/api/logoutApi";
+import { logout } from "@/features/auth/api/logoutApi.client";
export default function MyMainPage() {
const router = useRouter();
diff --git a/src/app/(header-only)/cart/page.tsx b/src/app/(header-only)/cart/page.tsx
index 297ca8c..29ed71e 100644
--- a/src/app/(header-only)/cart/page.tsx
+++ b/src/app/(header-only)/cart/page.tsx
@@ -1,19 +1,11 @@
import EmptyCartView from "@/features/cart/components/view/EmptyCartView";
-import CartMenuCard from "@/features/cart/components/card/CartMenuCard";
+import CartView from "@/features/cart/components/view/CartView";
+import { getCartInfo } from "@/features/cart/services/cartService.server";
-export default function CartPage() {
- return (
- // <>
- //
- //
- // >
-
- );
+export default async function CartPage() {
+
+ const cartInfo = await getCartInfo();
+ const empty = cartInfo.totalQuantity === 0;
+
+ return empty ? :
}
\ No newline at end of file
diff --git a/src/app/(header-only)/order/page.tsx b/src/app/(header-only)/order/page.tsx
new file mode 100644
index 0000000..735424a
--- /dev/null
+++ b/src/app/(header-only)/order/page.tsx
@@ -0,0 +1,22 @@
+"use client";
+
+import { useRouter } from "next/navigation";
+
+export default function OrderPage() {
+ const router = useRouter();
+
+ const onClick = () => {
+ router.replace("/");
+ }
+ return (
+
+ 주문 페이지 입니다
+
+
+ );
+};
\ No newline at end of file
diff --git a/src/features/auth/api/loginApi.ts b/src/features/auth/api/loginApi.client.ts
similarity index 95%
rename from src/features/auth/api/loginApi.ts
rename to src/features/auth/api/loginApi.client.ts
index 39257ea..6886bc0 100644
--- a/src/features/auth/api/loginApi.ts
+++ b/src/features/auth/api/loginApi.client.ts
@@ -1,3 +1,4 @@
+"use client";
import { apiClient } from "@/shared/lib/api/apiClient";
import { LoginRequest, LoginResponse } from "@/features/auth/types/loginTypes";
diff --git a/src/features/auth/api/logoutApi.ts b/src/features/auth/api/logoutApi.client.ts
similarity index 91%
rename from src/features/auth/api/logoutApi.ts
rename to src/features/auth/api/logoutApi.client.ts
index 254b866..b538e6b 100644
--- a/src/features/auth/api/logoutApi.ts
+++ b/src/features/auth/api/logoutApi.client.ts
@@ -1,3 +1,4 @@
+"use client";
import { apiClient } from "@/shared/lib/api/apiClient";
export async function logout(): Promise {
diff --git a/src/features/auth/components/form/LoginForm.tsx b/src/features/auth/components/form/LoginForm.tsx
index 6947138..a204c8e 100644
--- a/src/features/auth/components/form/LoginForm.tsx
+++ b/src/features/auth/components/form/LoginForm.tsx
@@ -11,7 +11,7 @@ import { useRouter } from "next/navigation";
import { CustomError } from "@/shared/lib/errors/customError";
import { ERROR_MESSAGE } from "@/shared/lib/errors/errorCodes";
import { LoginRequest } from "@/features/auth/types/loginTypes";
-import { login } from "@/features/auth/api/loginApi";
+import { login } from "@/features/auth/api/loginApi.client";
export default function LoginForm() {
const router = useRouter();
diff --git a/src/features/cart/api/cartApi.ts b/src/features/cart/api/cartApi.client.ts
similarity index 97%
rename from src/features/cart/api/cartApi.ts
rename to src/features/cart/api/cartApi.client.ts
index e1693e4..717bc5b 100644
--- a/src/features/cart/api/cartApi.ts
+++ b/src/features/cart/api/cartApi.client.ts
@@ -1,3 +1,4 @@
+"use client";
import { CartApiResponse, CartRequest } from "@/features/cart/types/cartType";
import { apiClient } from "@/shared/lib/api/apiClient";
diff --git a/src/features/cart/api/cartApi.server.ts b/src/features/cart/api/cartApi.server.ts
new file mode 100644
index 0000000..b73e327
--- /dev/null
+++ b/src/features/cart/api/cartApi.server.ts
@@ -0,0 +1,11 @@
+import "server-only";
+import { CartApiResponse } from "@/features/cart/types/cartType";
+import { apiServer } from "@/shared/lib/api/apiServer";
+
+/**
+ * 장바구니 조회 (SSR)
+ * GET /api/cart
+ */
+export async function getCart(): Promise {
+ return await apiServer.get("/api/cart");
+}
\ No newline at end of file
diff --git a/src/features/cart/components/bar/PaymentBar.module.css b/src/features/cart/components/bar/PaymentBar.module.css
new file mode 100644
index 0000000..34a71b9
--- /dev/null
+++ b/src/features/cart/components/bar/PaymentBar.module.css
@@ -0,0 +1,43 @@
+.container {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+
+ border-radius: 1.25rem 1.5rem 0 0;
+ background-color: var(--color-white);
+
+ box-shadow: 0 2px 8px 0 rgba(0, 0, 0, 0.20);
+
+ width: 100%;
+ height: var(--payment-bar-height);
+
+ padding-inline: 1.5rem;
+ padding-top: 1rem;
+ padding-bottom: 1.25rem;
+}
+
+.button {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ width: 100%;
+
+ border-radius: 1rem;
+ background-color: var(--color-main);
+
+ padding-block: 1.25rem;
+}
+
+.label {
+ color: var(--color-white);
+ text-align: center;
+
+ font-size: var(--text-base);
+ font-weight: var(--font-bold);
+ line-height: var(--line-height-single);
+ letter-spacing: var(--letter-spacing);
+}
+
+@theme {
+ --payment-bar-height: 5.75rem;
+}
\ No newline at end of file
diff --git a/src/features/cart/components/bar/PaymentBar.tsx b/src/features/cart/components/bar/PaymentBar.tsx
new file mode 100644
index 0000000..357915a
--- /dev/null
+++ b/src/features/cart/components/bar/PaymentBar.tsx
@@ -0,0 +1,26 @@
+import styles from "./PaymentBar.module.css";
+import { formatNumberWithComma } from "@/shared/utils/number/utils";
+
+interface PaymentBarProps {
+ totalPrice: number;
+ onClick: () => void;
+}
+
+export default function PaymentBar({
+ totalPrice,
+ onClick
+}: PaymentBarProps) {
+
+ const formattedPrice = formatNumberWithComma(totalPrice);
+
+ return (
+
+
+
+ )
+}
\ No newline at end of file
diff --git a/src/features/cart/components/card/CartMenuCard.module.css b/src/features/cart/components/card/CartMenuCard.module.css
index 10e7196..592f010 100644
--- a/src/features/cart/components/card/CartMenuCard.module.css
+++ b/src/features/cart/components/card/CartMenuCard.module.css
@@ -3,8 +3,6 @@
position: relative;
gap: 1rem;
- border: 1px solid black;
-
width: 100%;
background-color: var(--color-white);
diff --git a/src/features/cart/components/list/CartMenuList.module.css b/src/features/cart/components/list/CartMenuList.module.css
new file mode 100644
index 0000000..85e0dd0
--- /dev/null
+++ b/src/features/cart/components/list/CartMenuList.module.css
@@ -0,0 +1,20 @@
+.container {
+ display: flex;
+ flex-direction: column;
+ width: 100%;
+
+ border-radius: 1rem;
+ border: 1px solid var(--color-gray-100);
+ background-color: var(--color-white);
+
+ padding-block: 1.25rem;
+}
+
+.cardWrapper {
+ padding-inline: 1.25rem;
+}
+
+.separatorWrapper {
+ padding-inline: 1rem;
+ padding-block: 1rem;
+}
\ No newline at end of file
diff --git a/src/features/cart/components/list/CartMenuList.tsx b/src/features/cart/components/list/CartMenuList.tsx
new file mode 100644
index 0000000..1c025a9
--- /dev/null
+++ b/src/features/cart/components/list/CartMenuList.tsx
@@ -0,0 +1,36 @@
+import { CartInfo } from "@/features/cart/types/cartType";
+import CartMenuCard from "@/features/cart/components/card/CartMenuCard";
+import SeparationBar from "@/shared/components/bar/SeparationBar";
+import React from "react";
+import styles from "./CartMenuList.module.css";
+
+interface CartMenuListProps {
+ cartInfo: CartInfo
+}
+
+export default function CartMenuList({
+ cartInfo,
+}: CartMenuListProps) {
+ return (
+
+ {cartInfo.cartItems.map((item, index) => (
+
+
+
+
+ {index < cartInfo.cartItems.length - 1 && (
+
+
+
+ )}
+
+ ))}
+
+ );
+}
\ No newline at end of file
diff --git a/src/features/cart/components/view/CartView.module.css b/src/features/cart/components/view/CartView.module.css
new file mode 100644
index 0000000..ec95b7b
--- /dev/null
+++ b/src/features/cart/components/view/CartView.module.css
@@ -0,0 +1,20 @@
+.container {
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ width: 100%;
+
+ padding-inline: 1.25rem;
+ padding-top: 0.75rem;
+ padding-bottom: var(--payment-bar-height);
+}
+
+.paymentBarWrapper {
+ position: fixed;
+ bottom: 0;
+ left: 50%;
+ transform: translateX(-50%);
+
+ width: 100%;
+ max-width: var(--container-3xl);
+}
\ No newline at end of file
diff --git a/src/features/cart/components/view/CartView.tsx b/src/features/cart/components/view/CartView.tsx
new file mode 100644
index 0000000..1ea585e
--- /dev/null
+++ b/src/features/cart/components/view/CartView.tsx
@@ -0,0 +1,40 @@
+"use client";
+
+import CartMenuList from "@/features/cart/components/list/CartMenuList";
+import styles from "./CartView.module.css";
+import PaymentBar from "@/features/cart/components/bar/PaymentBar";
+import { useRouter } from "next/navigation";
+import { useCart } from "@/features/cart/hooks/useCart";
+import EmptyCartView from "@/features/cart/components/view/EmptyCartView";
+
+export default function CartView() {
+
+ const router = useRouter();
+ const { cartInfo } = useCart();
+
+ const handleClick = () => {
+ router.replace("/order");
+ }
+
+ if (!cartInfo) {
+ return null;
+ }
+
+ // 장바구니가 비어있으면 EmptyCartView 표시
+ if (cartInfo.totalQuantity === 0) {
+ return ;
+ }
+
+ return (
+
+ );
+}
\ No newline at end of file
diff --git a/src/features/cart/hooks/useCart.ts b/src/features/cart/hooks/useCart.ts
index 6d31f7d..e12255f 100644
--- a/src/features/cart/hooks/useCart.ts
+++ b/src/features/cart/hooks/useCart.ts
@@ -2,8 +2,8 @@
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { CartInfo, CartRequest } from "@/features/cart/types/cartType";
-import { getCartInfo, upsertCartInfo } from "@/features/cart/services/cartService";
-import { useCallback, useRef } from "react";
+import { useCallback, useMemo, useRef } from "react";
+import { getCartInfo, upsertCartInfo } from "@/features/cart/services/cartService.client";
interface UseCartReturn {
cartInfo: CartInfo | undefined,
@@ -37,6 +37,18 @@ export function useCart(): UseCartReturn {
retry: 1,
});
+ // menuId 기준으로 순서 정렬
+ const sortedCartInfo = useMemo(() => {
+ if (!cartInfo) {
+ return undefined;
+ }
+
+ return {
+ ...cartInfo,
+ cartItems: [...cartInfo.cartItems].sort((a, b) => a.menuId - b.menuId)
+ };
+ }, [cartInfo]);
+
const upsertCartMutation = useMutation({
mutationFn: (request: CartRequest) => upsertCartInfo(request),
onSuccess: (data: CartInfo) => {
@@ -51,9 +63,9 @@ export function useCart(): UseCartReturn {
* 특정 메뉴의 현재 수량 조회
*/
const getMenuQuantity = useCallback((menuId: number): number => {
- const cartItem = cartInfo?.cartItems.find((item) => item.menuId === menuId);
+ const cartItem = sortedCartInfo?.cartItems.find((item) => item.menuId === menuId);
return cartItem?.quantity ?? 0;
- }, [cartInfo]);
+ }, [sortedCartInfo]);
/**
* 메뉴 1개 추가 (기존 수량 + 1)
@@ -151,7 +163,7 @@ export function useCart(): UseCartReturn {
}, [getMenuQuantity, upsertCartMutation]);
return {
- cartInfo,
+ cartInfo: sortedCartInfo,
isLoading,
error,
getMenuQuantity,
diff --git a/src/features/cart/services/cartService.ts b/src/features/cart/services/cartService.client.ts
similarity index 95%
rename from src/features/cart/services/cartService.ts
rename to src/features/cart/services/cartService.client.ts
index c97258e..45bf09b 100644
--- a/src/features/cart/services/cartService.ts
+++ b/src/features/cart/services/cartService.client.ts
@@ -1,6 +1,7 @@
+"use client";
import { CartApiResponse, CartInfo, CartRequest } from "@/features/cart/types/cartType";
-import { getCart, upsertCart } from "@/features/cart/api/cartApi";
import { toCartInfo } from "@/features/cart/utils/cartMapper";
+import { getCart, upsertCart } from "@/features/cart/api/cartApi.client";
export async function getCartInfo(): Promise {
const cartApiResponse: CartApiResponse = await getCart();
diff --git a/src/features/cart/services/cartService.server.ts b/src/features/cart/services/cartService.server.ts
new file mode 100644
index 0000000..9ef00c0
--- /dev/null
+++ b/src/features/cart/services/cartService.server.ts
@@ -0,0 +1,9 @@
+import "server-only";
+import { CartApiResponse, CartInfo } from "@/features/cart/types/cartType";
+import { getCart } from "@/features/cart/api/cartApi.server";
+import { toCartInfo } from "@/features/cart/utils/cartMapper";
+
+export async function getCartInfo():Promise {
+ const cartApiResponse: CartApiResponse = await getCart();
+ return toCartInfo(cartApiResponse);
+}
\ No newline at end of file
diff --git a/src/features/menu/api/cafeteriaApi.ts b/src/features/menu/api/cafeteriaApi.server.ts
similarity index 92%
rename from src/features/menu/api/cafeteriaApi.ts
rename to src/features/menu/api/cafeteriaApi.server.ts
index effe435..9f1149b 100644
--- a/src/features/menu/api/cafeteriaApi.ts
+++ b/src/features/menu/api/cafeteriaApi.server.ts
@@ -1,3 +1,4 @@
+import "server-only";
import { apiServer } from "@/shared/lib/api/apiServer";
import { CafeteriaApiResponse } from "@/features/menu/types/cafeteriaType";
diff --git a/src/features/menu/api/categoryApi.ts b/src/features/menu/api/categoryApi.server.ts
similarity index 93%
rename from src/features/menu/api/categoryApi.ts
rename to src/features/menu/api/categoryApi.server.ts
index 60cd861..6f8d6c7 100644
--- a/src/features/menu/api/categoryApi.ts
+++ b/src/features/menu/api/categoryApi.server.ts
@@ -1,3 +1,4 @@
+import "server-only";
import { apiServer } from "@/shared/lib/api/apiServer";
import { CategoryApiResponse } from "@/features/menu/types/categoryType";
diff --git a/src/features/menu/api/menuApi.ts b/src/features/menu/api/menuApi.server.ts
similarity index 93%
rename from src/features/menu/api/menuApi.ts
rename to src/features/menu/api/menuApi.server.ts
index 96a8827..d6d9cb7 100644
--- a/src/features/menu/api/menuApi.ts
+++ b/src/features/menu/api/menuApi.server.ts
@@ -1,3 +1,4 @@
+import "server-only";
import { apiServer } from "@/shared/lib/api/apiServer";
import { MenuApiResponse } from "@/features/menu/types/menuType";
diff --git a/src/features/menu/components/MenuPageContainer.tsx b/src/features/menu/components/MenuPageContainer.tsx
index 17fda47..fb82539 100644
--- a/src/features/menu/components/MenuPageContainer.tsx
+++ b/src/features/menu/components/MenuPageContainer.tsx
@@ -1,6 +1,5 @@
"use client";
-import { HakgwanMenuData } from "@/features/menu/services/hakgwanMenuService";
import CategoryBar from "@/features/menu/components/category/CategoryBar";
import MenuList from "@/features/menu/components/list/MenuList";
import { useMemo, useState } from "react";
@@ -10,6 +9,7 @@ import { useCart } from "@/features/cart/hooks/useCart";
import OrderSummaryBar from "@/features/menu/components/bar/OrderSummaryBar";
import CartToast from "@/features/menu/components/toast/CartToast";
import { useToast } from "@/shared/hooks/useToast";
+import { HakgwanMenuData } from "@/features/menu/types/hakgwanType";
interface MenuPageContainerProps {
hakgwanMenuData: HakgwanMenuData;
diff --git a/src/features/menu/components/bar/OrderSummaryBar.tsx b/src/features/menu/components/bar/OrderSummaryBar.tsx
index f7c24f3..d28f7e9 100644
--- a/src/features/menu/components/bar/OrderSummaryBar.tsx
+++ b/src/features/menu/components/bar/OrderSummaryBar.tsx
@@ -19,7 +19,7 @@ export default function OrderSummaryBar({
const router = useRouter();
const handleCartButtonClick: MouseEventHandler = () => {
- router.replace("/cart");
+ router.push("/cart");
}
return (
diff --git a/src/features/menu/services/hakgwanMenuService.ts b/src/features/menu/services/hakgwanMenuService.server.ts
similarity index 92%
rename from src/features/menu/services/hakgwanMenuService.ts
rename to src/features/menu/services/hakgwanMenuService.server.ts
index 7118a04..1827057 100644
--- a/src/features/menu/services/hakgwanMenuService.ts
+++ b/src/features/menu/services/hakgwanMenuService.server.ts
@@ -1,16 +1,12 @@
-import { getAllCafeteria } from "@/features/menu/api/cafeteriaApi";
-import { getAllMenuByCafeteriaId } from "@/features/menu/api/menuApi";
-import { getCategoriesByCafeteriaId } from "@/features/menu/api/categoryApi";
+import { getAllCafeteria } from "@/features/menu/api/cafeteriaApi.server";
+import { getAllMenuByCafeteriaId } from "@/features/menu/api/menuApi.server";
+import { getCategoriesByCafeteriaId } from "@/features/menu/api/categoryApi.server";
import { CategoryApiResponse, CategoryItem } from "@/features/menu/types/categoryType";
import { MenuApiResponse, MenuItem } from "@/features/menu/types/menuType";
import { CafeteriaApiResponse } from "@/features/menu/types/cafeteriaType";
import { toCategoryItem } from "@/features/menu/utils/categoryMapper";
import { toMenuItem } from "@/features/menu/utils/menuMapper";
-
-export interface HakgwanMenuData {
- categoryItems: CategoryItem[];
- menuItems: MenuItem[];
-}
+import { HakgwanMenuData } from "@/features/menu/types/hakgwanType";
export async function getHakgwanMenus(): Promise {
const cafeterias: CafeteriaApiResponse[] = await getAllCafeteria();
diff --git a/src/features/menu/types/hakgwanType.ts b/src/features/menu/types/hakgwanType.ts
new file mode 100644
index 0000000..b62b334
--- /dev/null
+++ b/src/features/menu/types/hakgwanType.ts
@@ -0,0 +1,7 @@
+import { MenuItem } from "@/features/menu/types/menuType";
+import { CategoryItem } from "@/features/menu/types/categoryType";
+
+export interface HakgwanMenuData {
+ categoryItems: CategoryItem[];
+ menuItems: MenuItem[];
+}
\ No newline at end of file