diff --git a/apps/customer/public/images/mock/store/store-1.jpg b/apps/customer/public/images/mock/store/store-1.jpg new file mode 100644 index 0000000..eb328f2 Binary files /dev/null and b/apps/customer/public/images/mock/store/store-1.jpg differ diff --git a/apps/customer/src/app/(tabs)/layout.tsx b/apps/customer/src/app/(tabs)/layout.tsx index bbb5da5..321895d 100644 --- a/apps/customer/src/app/(tabs)/layout.tsx +++ b/apps/customer/src/app/(tabs)/layout.tsx @@ -43,8 +43,8 @@ export default function TabsLayout({ children }: TabsLayoutProps) { }; return ( -
-
+
+
{children}
diff --git a/apps/customer/src/app/(tabs)/main/store/[id]/page.tsx b/apps/customer/src/app/(tabs)/main/store/[id]/page.tsx new file mode 100644 index 0000000..60ad0eb --- /dev/null +++ b/apps/customer/src/app/(tabs)/main/store/[id]/page.tsx @@ -0,0 +1,23 @@ +import { notFound } from "next/navigation"; +import StoreDetailContent from "../_components/StoreDetailContent"; +import { MOCK_MAIN_STORE_DETAIL_MAP } from "../_constants/mockStoreDetail"; + +interface StoreDetailPageProps { + params: Promise<{ + id: string; + }>; +} + +export default async function StoreDetailPage({ + params, +}: StoreDetailPageProps) { + const { id } = await params; + const storeId = Number(id); + const store = MOCK_MAIN_STORE_DETAIL_MAP[storeId]; + + if (!store) { + notFound(); + } + + return ; +} \ No newline at end of file diff --git a/apps/customer/src/app/(tabs)/main/store/_components/StoreDetailContent.tsx b/apps/customer/src/app/(tabs)/main/store/_components/StoreDetailContent.tsx new file mode 100644 index 0000000..8b0b118 --- /dev/null +++ b/apps/customer/src/app/(tabs)/main/store/_components/StoreDetailContent.tsx @@ -0,0 +1,179 @@ +"use client"; + +import { useMemo, useState } from "react"; +import { Icon } from "@compasser/design-system"; +import StoreMenuCard from "./StoreMenuCard"; +import type { DayKey, StoreDetailItem } from "../_types/store-detail"; + +interface StoreDetailContentProps { + store: StoreDetailItem; +} + +const DAY_LABEL: Record = { + mon: "월", + tue: "화", + wed: "수", + thu: "목", + fri: "금", + sat: "토", + sun: "일", +}; + +const DAY_ORDER: DayKey[] = ["sun", "mon", "tue", "wed", "thu", "fri", "sat"]; + +const getTodayDayKeyInKorea = (): DayKey => { + const weekday = new Intl.DateTimeFormat("en-US", { + weekday: "short", + timeZone: "Asia/Seoul", + }).format(new Date()); + + const map: Record = { + Mon: "mon", + Tue: "tue", + Wed: "wed", + Thu: "thu", + Fri: "fri", + Sat: "sat", + Sun: "sun", + }; + + return map[weekday]; +}; + +export default function StoreDetailContent({ + store, +}: StoreDetailContentProps) { + const [isHoursOpen, setIsHoursOpen] = useState(false); + + const todayKey = useMemo(() => getTodayDayKeyInKorea(), []); + const todayHours = store.businessHours[todayKey]; + + const currentBusinessText = `영업중 ${todayHours.open} ~ ${todayHours.close}`; + + return ( +
+
+ {store.storeName} +
+ +
+
+

{store.storeName}

+ +
+
+
+ +
+ +
+

{store.roadAddress}

+

{store.lotAddress}

+
+
+ +
+
+ +
+ +
+

{store.email}

+
+
+ +
+
+
+ +
+ +
+

{currentBusinessText}

+ + +
+
+ + {isHoursOpen && ( +
+ {DAY_ORDER.map((day) => { + const hour = store.businessHours[day]; + const isToday = day === todayKey; + + return ( +
+ + {DAY_LABEL[day]} + + + {hour.open} ~ {hour.close} + +
+ ); + })} +
+ )} +
+
+ +
+
+ +

메뉴

+
+ +
+ {store.menus.map((menu) => ( + + ))} +
+
+
+
+
+ ); +} \ No newline at end of file diff --git a/apps/customer/src/app/(tabs)/main/store/_components/StoreMenuCard.tsx b/apps/customer/src/app/(tabs)/main/store/_components/StoreMenuCard.tsx new file mode 100644 index 0000000..8c5469b --- /dev/null +++ b/apps/customer/src/app/(tabs)/main/store/_components/StoreMenuCard.tsx @@ -0,0 +1,40 @@ +"use client"; + +import { Card, Icon } from "@compasser/design-system"; +import type { StoreMenuItem } from "../_types/store-detail"; + +interface StoreMenuCardProps { + item: StoreMenuItem; +} + +const formatPrice = (price: number) => `${price.toLocaleString()}원`; + +export default function StoreMenuCard({ item }: StoreMenuCardProps) { + return ( + +
+
+ +
+ +
+

{item.name}

+ +

+ 잔여개수 {item.remainingCount}개 +

+ +

+ 픽업시간: {item.pickupStartTime} ~ {item.pickupEndTime} +

+ +
+ + {formatPrice(item.price)} + +
+
+
+
+ ); +} \ No newline at end of file diff --git a/apps/customer/src/app/(tabs)/main/store/_constants/mockStoreDetail.ts b/apps/customer/src/app/(tabs)/main/store/_constants/mockStoreDetail.ts new file mode 100644 index 0000000..45bd561 --- /dev/null +++ b/apps/customer/src/app/(tabs)/main/store/_constants/mockStoreDetail.ts @@ -0,0 +1,360 @@ +import type { DayKey, StoreBusinessHour, StoreDetailItem } from "../_types/store-detail"; + +const createWeeklyHours = ( + defaultOpen: string, + defaultClose: string, + overrides?: Partial>, +): Record => ({ + mon: { open: defaultOpen, close: defaultClose }, + tue: { open: defaultOpen, close: defaultClose }, + wed: { open: defaultOpen, close: defaultClose }, + thu: { open: defaultOpen, close: defaultClose }, + fri: { open: defaultOpen, close: defaultClose }, + sat: { open: defaultOpen, close: defaultClose }, + sun: { open: defaultOpen, close: defaultClose }, + ...overrides, +}); + +export const MOCK_MAIN_STORE_DETAIL_LIST: StoreDetailItem[] = [ + { + id: 1, + storeName: "가톨릭대 정문 브레드하우스", + roadAddress: "경기 부천시 지봉로 43", + lotAddress: "경기 부천시 역곡동 543-1", + email: "breadhouse@gmail.com", + thumbnailImageUrl: "/images/mock/store/store-1.jpg", + businessHours: createWeeklyHours("09:00", "18:00", { + sat: { open: "09:00", close: "17:00" }, + sun: { open: "10:00", close: "17:00" }, + }), + menus: [ + { + id: 101, + name: "딸기 생크림 박스", + remainingCount: 3, + pickupStartTime: "19:00", + pickupEndTime: "21:00", + originalPrice: 12000, + price: 5500, + imageUrl: "/images/mock/menu/menu-1-1.jpg", + }, + { + id: 102, + name: "소금빵 랜덤박스", + remainingCount: 2, + pickupStartTime: "19:30", + pickupEndTime: "21:00", + originalPrice: 11000, + price: 5000, + imageUrl: "/images/mock/menu/menu-1-2.jpg", + }, + { + id: 103, + name: "크루아상 4종 세트", + remainingCount: 1, + pickupStartTime: "18:30", + pickupEndTime: "20:00", + originalPrice: 15000, + price: 7000, + imageUrl: "/images/mock/menu/menu-1-3.jpg", + }, + ], + }, + { + id: 2, + storeName: "부천역 카페 오븐데이", + roadAddress: "경기 부천시 부일로 460", + lotAddress: "경기 부천시 심곡동 221-9", + email: "ovenday@gmail.com", + thumbnailImageUrl: "/images/mock/store/store-1.jpg", + businessHours: createWeeklyHours("10:00", "20:00", { + sun: { open: "11:00", close: "19:00" }, + }), + menus: [ + { + id: 201, + name: "수제 쿠키 랜덤박스", + remainingCount: 3, + pickupStartTime: "18:30", + pickupEndTime: "20:30", + originalPrice: 9000, + price: 4000, + imageUrl: "/images/mock/menu/menu-2-1.jpg", + }, + { + id: 202, + name: "스콘 디저트팩", + remainingCount: 2, + pickupStartTime: "19:00", + pickupEndTime: "20:30", + originalPrice: 10000, + price: 4500, + imageUrl: "/images/mock/menu/menu-2-2.jpg", + }, + ], + }, + { + id: 3, + storeName: "역곡 브런치살롱", + roadAddress: "경기 부천시 역곡로 8", + lotAddress: "경기 부천시 역곡동 112-4", + email: "brunchsalon@gmail.com", + thumbnailImageUrl: "/images/mock/store/store-1.jpg", + businessHours: createWeeklyHours("09:30", "19:30"), + menus: [ + { + id: 301, + name: "샌드위치 브런치팩", + remainingCount: 4, + pickupStartTime: "10:00", + pickupEndTime: "13:00", + originalPrice: 13500, + price: 6900, + imageUrl: "/images/mock/menu/menu-3-1.jpg", + }, + { + id: 302, + name: "샐러드 브런치 세트", + remainingCount: 2, + pickupStartTime: "11:00", + pickupEndTime: "13:30", + originalPrice: 14000, + price: 7500, + imageUrl: "/images/mock/menu/menu-3-2.jpg", + }, + ], + }, + { + id: 4, + storeName: "역곡역 앞 솔트베이크", + roadAddress: "경기 부천시 역곡로 3", + lotAddress: "경기 부천시 역곡동 98-7", + email: "saltbake@gmail.com", + thumbnailImageUrl: "/images/mock/store/store-1.jpg", + businessHours: createWeeklyHours("08:30", "19:30"), + menus: [ + { + id: 401, + name: "소금빵 4종 박스", + remainingCount: 3, + pickupStartTime: "17:00", + pickupEndTime: "20:00", + originalPrice: 13000, + price: 6000, + imageUrl: "/images/mock/menu/menu-4-1.jpg", + }, + { + id: 402, + name: "베이커리 랜덤팩", + remainingCount: 1, + pickupStartTime: "18:00", + pickupEndTime: "19:30", + originalPrice: 14000, + price: 6500, + imageUrl: "/images/mock/menu/menu-4-2.jpg", + }, + ], + }, + { + id: 5, + storeName: "가톨릭대 후문 모어커피", + roadAddress: "경기 부천시 지봉로 50", + lotAddress: "경기 부천시 역곡동 601-12", + email: "morecoffee@gmail.com", + thumbnailImageUrl: "/images/mock/store/store-1.jpg", + businessHours: createWeeklyHours("09:00", "22:00"), + menus: [ + { + id: 501, + name: "오늘의 디저트팩", + remainingCount: 5, + pickupStartTime: "20:00", + pickupEndTime: "22:00", + originalPrice: 9500, + price: 4500, + imageUrl: "/images/mock/menu/menu-5-1.jpg", + }, + { + id: 502, + name: "쿠키+음료 세트", + remainingCount: 2, + pickupStartTime: "19:30", + pickupEndTime: "21:30", + originalPrice: 11000, + price: 5200, + imageUrl: "/images/mock/menu/menu-5-2.jpg", + }, + ], + }, + { + id: 6, + storeName: "부천 디저트랩", + roadAddress: "경기 부천시 부일로 448", + lotAddress: "경기 부천시 심곡동 203-15", + email: "dessertlab@gmail.com", + thumbnailImageUrl: "/images/mock/store/store-1.jpg", + businessHours: createWeeklyHours("12:00", "21:00"), + menus: [ + { + id: 601, + name: "마카롱 혼합 박스", + remainingCount: 2, + pickupStartTime: "18:00", + pickupEndTime: "21:00", + originalPrice: 15000, + price: 7000, + imageUrl: "/images/mock/menu/menu-6-1.jpg", + }, + { + id: 602, + name: "케이크 조각 세트", + remainingCount: 1, + pickupStartTime: "19:00", + pickupEndTime: "20:30", + originalPrice: 16000, + price: 7500, + imageUrl: "/images/mock/menu/menu-6-2.jpg", + }, + ], + }, + { + id: 7, + storeName: "역곡 한끼식당", + roadAddress: "경기 부천시 역곡로 22", + lotAddress: "경기 부천시 역곡동 231-6", + email: "hankki@gmail.com", + thumbnailImageUrl: "/images/mock/store/store-1.jpg", + businessHours: createWeeklyHours("10:30", "20:30"), + menus: [ + { + id: 701, + name: "한식 도시락 세트", + remainingCount: 4, + pickupStartTime: "13:00", + pickupEndTime: "15:00", + originalPrice: 12000, + price: 6500, + imageUrl: "/images/mock/menu/menu-7-1.jpg", + }, + { + id: 702, + name: "반찬 랜덤팩", + remainingCount: 2, + pickupStartTime: "18:30", + pickupEndTime: "20:00", + originalPrice: 10000, + price: 5000, + imageUrl: "/images/mock/menu/menu-7-2.jpg", + }, + ], + }, + { + id: 8, + storeName: "가톨릭대 베이글스팟", + roadAddress: "경기 부천시 지봉로 59", + lotAddress: "경기 부천시 역곡동 614-8", + email: "bagelspot@gmail.com", + thumbnailImageUrl: "/images/mock/store/store-1.jpg", + businessHours: createWeeklyHours("08:00", "17:00", { + sun: { open: "09:00", close: "16:00" }, + }), + menus: [ + { + id: 801, + name: "베이글 랜덤팩", + remainingCount: 3, + pickupStartTime: "16:00", + pickupEndTime: "19:00", + originalPrice: 11000, + price: 5200, + imageUrl: "/images/mock/menu/menu-8-1.jpg", + }, + { + id: 802, + name: "크림치즈 베이글 세트", + remainingCount: 2, + pickupStartTime: "15:30", + pickupEndTime: "17:30", + originalPrice: 12500, + price: 5800, + imageUrl: "/images/mock/menu/menu-8-2.jpg", + }, + ], + }, + { + id: 9, + storeName: "부천 로컬키친", + roadAddress: "경기 부천시 부일로 472", + lotAddress: "경기 부천시 심곡동 227-2", + email: "localkitchen@gmail.com", + thumbnailImageUrl: "/images/mock/store/store-1.jpg", + businessHours: createWeeklyHours("11:00", "21:00"), + menus: [ + { + id: 901, + name: "파스타 투고 박스", + remainingCount: 2, + pickupStartTime: "11:30", + pickupEndTime: "14:00", + originalPrice: 15000, + price: 7900, + imageUrl: "/images/mock/menu/menu-9-1.jpg", + }, + { + id: 902, + name: "리조또 데일리팩", + remainingCount: 3, + pickupStartTime: "12:00", + pickupEndTime: "14:30", + originalPrice: 14500, + price: 7600, + imageUrl: "/images/mock/menu/menu-9-2.jpg", + }, + ], + }, + { + id: 10, + storeName: "역곡 스윗테이블", + roadAddress: "경기 부천시 역곡로 15", + lotAddress: "경기 부천시 역곡동 142-10", + email: "sweettable@gmail.com", + thumbnailImageUrl: "/images/mock/store/store-1.jpg", + businessHours: createWeeklyHours("10:00", "22:00"), + menus: [ + { + id: 1001, + name: "조각케이크 랜덤팩", + remainingCount: 3, + pickupStartTime: "19:30", + pickupEndTime: "21:30", + originalPrice: 13000, + price: 6000, + imageUrl: "/images/mock/menu/menu-10-1.jpg", + }, + { + id: 1002, + name: "디저트 박스 Level.2", + remainingCount: 1, + pickupStartTime: "20:00", + pickupEndTime: "21:30", + originalPrice: 16000, + price: 8000, + imageUrl: "/images/mock/menu/menu-10-2.jpg", + }, + { + id: 1003, + name: "쿠키&케이크 세트", + remainingCount: 2, + pickupStartTime: "19:00", + pickupEndTime: "22:00", + originalPrice: 15000, + price: 7200, + imageUrl: "/images/mock/menu/menu-10-3.jpg", + }, + ], + }, +]; + +export const MOCK_MAIN_STORE_DETAIL_MAP = Object.fromEntries( + MOCK_MAIN_STORE_DETAIL_LIST.map((store) => [store.id, store]), +) as Record; \ No newline at end of file diff --git a/apps/customer/src/app/(tabs)/main/store/_types/store-detail.ts b/apps/customer/src/app/(tabs)/main/store/_types/store-detail.ts new file mode 100644 index 0000000..cddc6bb --- /dev/null +++ b/apps/customer/src/app/(tabs)/main/store/_types/store-detail.ts @@ -0,0 +1,35 @@ +export type DayKey = + | "mon" + | "tue" + | "wed" + | "thu" + | "fri" + | "sat" + | "sun"; + +export interface StoreBusinessHour { + open: string; + close: string; +} + +export interface StoreMenuItem { + id: number; + name: string; + remainingCount: number; + pickupStartTime: string; + pickupEndTime: string; + price: number; + originalPrice?: number; + imageUrl: string; +} + +export interface StoreDetailItem { + id: number; + storeName: string; + roadAddress: string; + lotAddress: string; + email: string; + thumbnailImageUrl: string; + businessHours: Record; + menus: StoreMenuItem[]; +} \ No newline at end of file diff --git a/packages/design-system/src/icons/generated/iconNames.ts b/packages/design-system/src/icons/generated/iconNames.ts index b82fdd5..eeada31 100644 --- a/packages/design-system/src/icons/generated/iconNames.ts +++ b/packages/design-system/src/icons/generated/iconNames.ts @@ -16,6 +16,7 @@ export const iconNames = [ "Mail", "MapIcon", "MapPin", + "Menu", "My", "NextButton", "Notice", @@ -25,6 +26,8 @@ export const iconNames = [ "RadioActive", "RadioDeactive", "Stamp", - "StoreIcon" + "StoreIcon", + "ToggleDown", + "ToggleUp" ] as const; export type IconName = typeof iconNames[number]; diff --git a/packages/design-system/src/icons/generated/spriteSymbols.ts b/packages/design-system/src/icons/generated/spriteSymbols.ts index 571eaaa..7964c06 100644 --- a/packages/design-system/src/icons/generated/spriteSymbols.ts +++ b/packages/design-system/src/icons/generated/spriteSymbols.ts @@ -1,2 +1,2 @@ // 이 파일은 자동 생성 파일입니다. (직접 수정 금지) -export const spriteSymbols = ""; +export const spriteSymbols = ""; diff --git a/packages/design-system/src/icons/source/Menu.svg b/packages/design-system/src/icons/source/Menu.svg new file mode 100644 index 0000000..c345ea0 --- /dev/null +++ b/packages/design-system/src/icons/source/Menu.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/design-system/src/icons/source/ToggleDown.svg b/packages/design-system/src/icons/source/ToggleDown.svg new file mode 100644 index 0000000..9bc0f0d --- /dev/null +++ b/packages/design-system/src/icons/source/ToggleDown.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/design-system/src/icons/source/ToggleUp.svg b/packages/design-system/src/icons/source/ToggleUp.svg new file mode 100644 index 0000000..de2847d --- /dev/null +++ b/packages/design-system/src/icons/source/ToggleUp.svg @@ -0,0 +1 @@ + \ No newline at end of file