diff --git a/apps/nowait-admin/src/pages/AdminBooth/components/MenuSection.tsx b/apps/nowait-admin/src/pages/AdminBooth/components/MenuSection.tsx index 1dffa50d..45de6e35 100644 --- a/apps/nowait-admin/src/pages/AdminBooth/components/MenuSection.tsx +++ b/apps/nowait-admin/src/pages/AdminBooth/components/MenuSection.tsx @@ -12,9 +12,28 @@ import MenuRemoveModal from "./Modal/MenuRemoveModal"; import { useDeleteMenu } from "../../../hooks/booth/menu/useDeleteMenu"; import { useToggleMenuSoldOut } from "../../../hooks/booth/menu/useToggleMenuSoldOut"; import { useUpdateMenuSort } from "../../../hooks/booth/menu/useUpadateMenuSort"; -import { useVerticalLockStyle } from "../../../utils/useVerticalLockStyle"; import { SwipeableRow } from "./Swipe/SwipeableRow"; +function lockVertical( + style?: React.CSSProperties +): React.CSSProperties | undefined { + if (!style || !style.transform) return style; + const t = String(style.transform); + const m2d = t.match(/translate\((-?\d+\.?\d*)px,\s*(-?\d+\.?\d*)px\)/); + if (m2d) { + const [, , y] = m2d; + return { ...style, transform: `translate(0px, ${y}px)` }; + } + const m3d = t.match( + /translate3d\((-?\d+\.?\d*)px,\s*(-?\d+\.?\d*)px,\s*(-?\d+\.?\d*)px\)/ + ); + if (m3d) { + const [, , y, z] = m3d; + return { ...style, transform: `translate3d(0px, ${y}px, ${z}px)` }; + } + return style; +} + // 세 자리마다 , 붙여서 가격표시 const formatNumber = (num: number) => { if (!num) return ""; @@ -132,6 +151,7 @@ const MenuSection = ({ isTablet }: { isTablet: boolean }) => { adminDisplayName: string; description: string; price: string; + image?: File | string; }) => { const payload = { menuId: updated.id, @@ -153,6 +173,25 @@ const MenuSection = ({ isTablet }: { isTablet: boolean }) => { console.log("메뉴 수정에 실패했습니다."); }, }); + + if (updated.image && updated.image instanceof File) { + uploadMenuImage( + { menuId: updated.id, image: updated.image }, + { + onSuccess: (imgData) => { + const url = imgData.url; + setMenus((prev) => + prev.map((m) => + m.id === updated.id ? { ...m, imageUrl: url } : m + ) + ); + }, + onError: () => { + console.log("이미지 업로드 실패"); + }, + } + ); + } }; const handleDeleteMenu = () => { @@ -201,12 +240,12 @@ const MenuSection = ({ isTablet }: { isTablet: boolean }) => { const reordered = Array.from(menus); const [removed] = reordered.splice(result.source.index, 1); reordered.splice(result.destination.index, 0, removed); - const next = reordered.map((m, i) => ({ ...m, sortOrder: i })); // 서버가 1-base면 i+1 + const next = reordered.map((m, i) => ({ ...m, sortOrder: i })); setMenus(next); const body = next.map(({ id, sortOrder }) => ({ menuId: id, - sortOrder, // 1-base면 sortOrder: sortOrder + 1 + sortOrder, })); updateMenuSort(body, { onSuccess: (res) => { @@ -287,17 +326,22 @@ const MenuSection = ({ isTablet }: { isTablet: boolean }) => { isDragDisabled={!editMode} > {(provided) => { - const lockedStyle = useVerticalLockStyle( + const lockedStyle = lockVertical( provided.draggableProps.style ); - const rowContent = ( + return editMode ? ( + // ✅ 편집 모드: SwipeableRow 사용 안 함 (충돌 차단)
+ {/* rowContent 대신, 핸들 아이콘에만 dragHandleProps를 붙여줘야 해 */}
!editMode && openEditModal(menu)} >
@@ -317,37 +361,55 @@ const MenuSection = ({ isTablet }: { isTablet: boolean }) => {
-
- {editMode ? ( - 순서 변경 - ) : ( - toggleSoldOut(idx)} - /> - )} -
+ {/* ✨ 여기만 드래그 핸들! */} + 순서 변경
- ); - return ( + ) : ( + // ✅ 보기 모드: SwipeableRow로 스와이프 삭제 { setSelectedMenu(menu); setIsRemoveModalOpen(true); }} contentProps={{ - ...provided.draggableProps, + ...provided.draggableProps, // isDragDisabled=true라 드래그는 안됨 style: lockedStyle, + className: + "flex justify-between items-center py-4 w-full", }} > - {rowContent} +
openEditModal(menu)} + > +
+ placeholder +
+
+ + {menu.name} + + + {formatNumber(menu.price)}원 + +
+
+ + toggleSoldOut(idx)} + />
); }} diff --git a/apps/nowait-admin/src/pages/AdminBooth/components/Modal/menuModal.tsx b/apps/nowait-admin/src/pages/AdminBooth/components/Modal/menuModal.tsx index 4866f47f..01b69f64 100644 --- a/apps/nowait-admin/src/pages/AdminBooth/components/Modal/menuModal.tsx +++ b/apps/nowait-admin/src/pages/AdminBooth/components/Modal/menuModal.tsx @@ -40,13 +40,19 @@ const PriceInput: React.FC = ({ price, setPrice }) => { setPrice(rawValue); }; + const displayValue = isFocused + ? price + : price + ? formatNumber(parseInt(price)) + : ""; + return (
setIsFocused(true)} onBlur={() => setIsFocused(false)} diff --git a/apps/nowait-user/src/assets/icon/arrow-right.svg b/apps/nowait-user/src/assets/icon/arrow-right.svg index d262d24a..61898d4d 100644 --- a/apps/nowait-user/src/assets/icon/arrow-right.svg +++ b/apps/nowait-user/src/assets/icon/arrow-right.svg @@ -1,3 +1,3 @@ - + diff --git a/apps/nowait-user/src/assets/icon/check.svg b/apps/nowait-user/src/assets/icon/check.svg new file mode 100644 index 00000000..10dc9c0f --- /dev/null +++ b/apps/nowait-user/src/assets/icon/check.svg @@ -0,0 +1,3 @@ + + + diff --git a/apps/nowait-user/src/assets/icon/signup.svg b/apps/nowait-user/src/assets/icon/signup.svg new file mode 100644 index 00000000..d2e549aa --- /dev/null +++ b/apps/nowait-user/src/assets/icon/signup.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/apps/nowait-user/src/assets/icon/x-circle.svg b/apps/nowait-user/src/assets/icon/x-circle.svg new file mode 100644 index 00000000..2077f08a --- /dev/null +++ b/apps/nowait-user/src/assets/icon/x-circle.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/apps/nowait-user/src/pages/login/components/AnimatedModal.tsx b/apps/nowait-user/src/pages/login/components/AnimatedModal.tsx new file mode 100644 index 00000000..dbf83991 --- /dev/null +++ b/apps/nowait-user/src/pages/login/components/AnimatedModal.tsx @@ -0,0 +1,43 @@ +import { motion, AnimatePresence } from "framer-motion"; +import { type ReactNode } from "react"; + +interface AnimatedModalProps { + isOpen: boolean; + onClose: () => void; + children: ReactNode; +} + +const AnimatedModal = ({ isOpen, onClose, children }: AnimatedModalProps) => { + return ( + + {isOpen && ( + + e.stopPropagation()} + > + {children} + + + )} + + ); +}; + +export default AnimatedModal; diff --git a/apps/nowait-user/src/pages/login/components/Checkbox.tsx b/apps/nowait-user/src/pages/login/components/Checkbox.tsx new file mode 100644 index 00000000..1381b58c --- /dev/null +++ b/apps/nowait-user/src/pages/login/components/Checkbox.tsx @@ -0,0 +1,44 @@ +import { useState } from "react"; +import Check from "../../../assets/icon/check.svg?react"; + +interface CheckboxProps { + checked?: boolean; + onChange?: (checked: boolean) => void; + className?: string; +} + +const Checkbox = ({ + checked: controlledChecked, + onChange, + className = "", +}: CheckboxProps) => { + const [internalChecked, setInternalChecked] = useState(false); + + // controlled 또는 uncontrolled 모드 지원 + const isControlled = controlledChecked !== undefined; + const isChecked = isControlled ? controlledChecked : internalChecked; + + const handleClick = () => { + const newChecked = !isChecked; + + if (isControlled) { + onChange?.(newChecked); + } else { + setInternalChecked(newChecked); + onChange?.(newChecked); + } + }; + + return ( +
+ {isChecked && } +
+ ); +}; + +export default Checkbox; diff --git a/apps/nowait-user/src/pages/login/components/PhoneNumberInput.tsx b/apps/nowait-user/src/pages/login/components/PhoneNumberInput.tsx new file mode 100644 index 00000000..8cbe88f6 --- /dev/null +++ b/apps/nowait-user/src/pages/login/components/PhoneNumberInput.tsx @@ -0,0 +1,81 @@ +import { useState } from "react"; +import XCircle from "../../../assets/icon/x-circle.svg?react"; + +interface PhoneNumberInputProps { + value?: string; + onChange?: (value: string) => void; + placeholder?: string; + className?: string; +} + +const PhoneNumberInput = ({ + value: controlledValue, + onChange, + placeholder = "전화번호 입력", + className = "text-title-20-semibold text-black-90 leading-[144%] tracking-[-0.01em] placeholder:text-black-50 outline-none focus:outline-none", +}: PhoneNumberInputProps) => { + const [internalValue, setInternalValue] = useState(""); + + // controlled 또는 uncontrolled 모드 지원 + const isControlled = controlledValue !== undefined; + const currentValue = isControlled ? controlledValue : internalValue; + + const formatPhoneNumber = (value: string) => { + // 숫자만 추출 + const numbers = value.replace(/[^\d]/g, ""); + + // 길이에 따라 포맷팅 + if (numbers.length <= 3) { + return numbers; + } else if (numbers.length <= 7) { + return `${numbers.slice(0, 3)}-${numbers.slice(3)}`; + } else { + return `${numbers.slice(0, 3)}-${numbers.slice(3, 7)}-${numbers.slice( + 7, + 11 + )}`; + } + }; + + const handlePhoneNumberChange = (e: React.ChangeEvent) => { + const formatted = formatPhoneNumber(e.target.value); + + if (isControlled) { + onChange?.(formatted); + } else { + setInternalValue(formatted); + onChange?.(formatted); + } + }; + + const handleClearPhoneNumber = () => { + if (isControlled) { + onChange?.(""); + } else { + setInternalValue(""); + onChange?.(""); + } + }; + + return ( +
+ +
+ +
+
+ ); +}; + +export default PhoneNumberInput; diff --git a/apps/nowait-user/src/pages/login/components/Term.tsx b/apps/nowait-user/src/pages/login/components/Term.tsx new file mode 100644 index 00000000..0d53fbde --- /dev/null +++ b/apps/nowait-user/src/pages/login/components/Term.tsx @@ -0,0 +1,185 @@ +// 서비스 이용약관 +const ServiceTermsContent = () => { + return ( +
+
+
+ [필수] 노웨잇 서비스 이용약관 동의
제 1조 (목적) +
+
+ 본 약관은 노웨잇(이하 “회사”)이 제공하는 서비스의 이용 조건 및 절차, + 회사와 회원 간의 권리·의무 및 책임 사항을 규정함을 목적으로 합니다. +
+
+ +
+
+ 제 2조 (회원가입 및 계정관리) +
+
+
1.
+
+ 회원은 회사가 정한 절차에 따라 소셜 로그인 또는 별도의 가입 절차를 + 통해 계정을 생성합니다. +
+
+
+
2.
+
+ 회원은 계정 및 비밀번호 관리 책임이 있으며, 이를 제3자에게 양도, + 대여, 공유할 수 없습니다. +
+
+
+ +
+
+ 제 3조 (서비스의 변경 및 중단) +
+
+
1.
+
+ 회사는 회원에게 예약, 알림, 대기 관리 등의 서비스를 제공합니다. +
+
+
+
2.
+
+ 회원은 관련 법령을 준수해야 하며, 타인의 정보를 도용하거나 허위 + 정보를 제공해서는 안 됩니다. +
+
+
+ +
+
+ 제 4조 (서비스의 변경 및 중단) +
+
+ 회사는 서비스의 일부 또는 전부를 변경·중단할 수 있으며, 이 경우 사전에 + 회원에게 고지합니다. +
+
+ +
+
+ 제 5조 (이용 제한 및 계약 해지) +
+
+ 회사는 서비스의 일부 또는 전부를 변경·중단할 수 있으며, 이 경우 사전에 + 회원에게 고지합니다. +
+
+ +
+
제 6조 (면책 조항)
+
+ 회사는 천재지변, 네트워크 장애, 불가항력적 사유로 인한 서비스 중단에 + 대하여 책임을 지지 않습니다. +
+
+
+ ); +}; + +const PrivacyTermsContent = () => { + return ( +
+
+
+ [필수] 개인정보 수집 및 이용 동의 +
+
+ 회사는 「개인정보 보호법」에 따라 회원님의 개인정보를 수집·이용하고 + 있습니다. +
+ 서비스 이용을 위해 아래 내용을 확인하시고 동의해 주시기 바랍니다. +
+
+ +
+
수집항목
+
+
+
이메일(소셜 로그인 연동), 비밀번호, 휴대전화 번호
+
+
+ +
+
수집 및 이용목적
+
+
+
회원 식별 및 계정 관리
+
+
+
+
본인 확인, 계정 복구, 서비스 알림 발송
+
+
+ +
+
보유 및 이용기간
+
+
+
회원 탈퇴 시까지
+
+
+ +
+
+ ※ 법령에 따라 보관이 필요한 경우, 관련 법령에서 정한 기간 동안 + 보관합니다. +
+
+ ※ 서비스 이용기록, 접속 로그, 쿠키, 접속 IP 정보, 단말기정보(서비스 + 버전, OS, OS 버전, 디바이스 모델명) 등 자동으로 수집될 수 있습니다. +
+
+ ※ 필수항목에 대한 수집 및 이용에 동의하지 않을 권리는 있으나, 미동의 + 시 회원가입이 불가합니다. +
+
+
+ ); +}; + +const MarketingTermsContent = () => { + return ( +
+
+
+ [선택] 마케팅 정보 수신 동의 +
+
+ 회사는 회원에게 이벤트, 할인 혜택, 신규 서비스 안내 등 유용한 정보를 + 제공하기 위하여 휴대전화 번호 또는 이메일을 통해 광고성 정보를 발송할 + 수 있습니다. +
+
+
1. 수신 항목:
+
휴대전화(SMS, 카카오 알림톡 등), 이메일
+
+
+
2. 수신 목적:
+
이벤트, 프로모션, 서비스 안내
+
+
+
3. 보유 및 이용 기간:
+
동의 철회 시 또는 회원 탈퇴 시까지
+
+
+ +
+
+ ※ 동의를 거부하셔도 서비스 이용에는 제한이 없습니다. +
+
+ ※ 법령에 따라 보관이 필요한 경우, 관련 법령에서 정한 기간 동안 + 보관합니다. +
+
+
+ ); +}; + +export { ServiceTermsContent, PrivacyTermsContent, MarketingTermsContent }; diff --git a/apps/nowait-user/src/pages/login/components/TermsContent.tsx b/apps/nowait-user/src/pages/login/components/TermsContent.tsx new file mode 100644 index 00000000..8cf3f0bf --- /dev/null +++ b/apps/nowait-user/src/pages/login/components/TermsContent.tsx @@ -0,0 +1,24 @@ +import { + ServiceTermsContent, + MarketingTermsContent, + PrivacyTermsContent, +} from "./Term"; + +interface TermsContentProps { + termType: "service" | "privacy" | "marketing"; +} + +const TermsContent = ({ termType }: TermsContentProps) => { + switch (termType) { + case "service": + return ; + case "privacy": + return ; + case "marketing": + return ; + default: + return ; + } +}; + +export default TermsContent; diff --git a/apps/nowait-user/src/pages/login/components/TermsItem.tsx b/apps/nowait-user/src/pages/login/components/TermsItem.tsx new file mode 100644 index 00000000..4559b080 --- /dev/null +++ b/apps/nowait-user/src/pages/login/components/TermsItem.tsx @@ -0,0 +1,42 @@ +import Checkbox from "./Checkbox"; +import ArrowRight from "../../../assets/icon/arrow-right.svg?react"; + +interface TermsItemProps { + title: string; + isRequired: boolean; + checked: boolean; + onChange: (checked: boolean) => void; + onOpenViewModal: (termType: "service" | "privacy" | "marketing") => void; + termType: "service" | "privacy" | "marketing"; +} + +const TermsItem = ({ + title, + isRequired, + checked, + onChange, + onOpenViewModal, + termType, +}: TermsItemProps) => { + return ( +
onOpenViewModal(termType)} + > +
+
e.stopPropagation()}> + +
+
+ ({isRequired ? "필수" : "선택"}) {title} +
+
+ +
+ +
+
+ ); +}; + +export default TermsItem; diff --git a/apps/nowait-user/src/pages/login/components/TermsModal.tsx b/apps/nowait-user/src/pages/login/components/TermsModal.tsx new file mode 100644 index 00000000..fd51f25d --- /dev/null +++ b/apps/nowait-user/src/pages/login/components/TermsModal.tsx @@ -0,0 +1,137 @@ +import { useState } from "react"; +import Checkbox from "./Checkbox"; +import TermsItem from "./TermsItem"; +import { useNavigate } from "react-router-dom"; + +interface TermsModalProps { + onOpenViewModal: (termType: "service" | "privacy" | "marketing") => void; +} + +const TermsModal = ({ onOpenViewModal }: TermsModalProps) => { + // onClose는 나중에 모달 닫기 기능에서 사용될 예정 (현재는 사용하지 않음) + const [isAllAgreed, setIsAllAgreed] = useState(false); + const [isServiceAgreed, setIsServiceAgreed] = useState(false); + const [isPrivacyAgreed, setIsPrivacyAgreed] = useState(false); + const [isMarketingAgreed, setIsMarketingAgreed] = useState(false); + + const navigate = useNavigate(); + + // 전체 동의 체크박스 핸들러 + const handleAllAgreedChange = (checked: boolean) => { + setIsAllAgreed(checked); + setIsServiceAgreed(checked); + setIsPrivacyAgreed(checked); + setIsMarketingAgreed(checked); + }; + + // 개별 체크박스 변경 시 전체 동의 상태 업데이트 + const updateAllAgreedState = () => { + const allChecked = isServiceAgreed && isPrivacyAgreed && isMarketingAgreed; + setIsAllAgreed(allChecked); + }; + + // 개별 체크박스 핸들러들 + const handleServiceAgreedChange = (checked: boolean) => { + setIsServiceAgreed(checked); + if (!checked) { + setIsAllAgreed(false); + } else { + updateAllAgreedState(); + } + }; + + const handlePrivacyAgreedChange = (checked: boolean) => { + setIsPrivacyAgreed(checked); + if (!checked) { + setIsAllAgreed(false); + } else { + updateAllAgreedState(); + } + }; + + const handleMarketingAgreedChange = (checked: boolean) => { + setIsMarketingAgreed(checked); + if (!checked) { + setIsAllAgreed(false); + } else { + updateAllAgreedState(); + } + }; + + const signupHandler = () => { + navigate("/onboarding/success"); + }; + + // 필수 약관이 모두 체크되었는지 확인 + const isRequiredTermsAgreed = isServiceAgreed && isPrivacyAgreed; + return ( +
+
+
+
+
+ +
+
+ 서비스 이용약관에 동의해주세요 +
+ +
+
+
+ +
+ 약관 전체동의 +
+
+
+ + + + +
+
+
+ +
+ +
+
+ ); +}; + +export default TermsModal; diff --git a/apps/nowait-user/src/pages/login/components/ViewModal.tsx b/apps/nowait-user/src/pages/login/components/ViewModal.tsx new file mode 100644 index 00000000..5343a770 --- /dev/null +++ b/apps/nowait-user/src/pages/login/components/ViewModal.tsx @@ -0,0 +1,35 @@ +import ArrowLeft from "../../../assets/icon/arrow_back.svg?react"; + +interface ViewModalProps { + isOpen: boolean; + onClose: () => void; + title: string; + children: React.ReactNode; +} + +const ViewModal = ({ isOpen, onClose, title, children }: ViewModalProps) => { + if (!isOpen) return null; + + return ( +
+
+
+
+ +
+
+
+ {title} +
+
+
+ + {children} +
+ ); +}; + +export default ViewModal; diff --git a/apps/nowait-user/src/pages/login/onboarding/OnboardingPage.tsx b/apps/nowait-user/src/pages/login/onboarding/OnboardingPage.tsx new file mode 100644 index 00000000..8a75d7e6 --- /dev/null +++ b/apps/nowait-user/src/pages/login/onboarding/OnboardingPage.tsx @@ -0,0 +1,117 @@ +// 전화번호 입력 페이지 입니다. +import { useState } from "react"; +import PhoneNumberInput from "../components/PhoneNumberInput"; +import TermsModal from "../components/TermsModal"; +import AnimatedModal from "../components/AnimatedModal"; +import ViewModal from "../components/ViewModal"; +import TermsContent from "../components/TermsContent"; + +const OnboardingPage = () => { + const [phoneNumber, setPhoneNumber] = useState(""); + const [isModalOpen, setIsModalOpen] = useState(false); + const [showError, setShowError] = useState(false); + const [isViewModalOpen, setIsViewModalOpen] = useState(false); + const [selectedTermType, setSelectedTermType] = useState< + "service" | "privacy" | "marketing" + >("service"); + + // 전화번호가 11자리 모두 입력되었는지 확인 (하이픈 제외) + const isPhoneNumberComplete = phoneNumber.replace(/[^\d]/g, "").length === 11; + + // 전화번호가 010으로 시작하는지 확인 + const isValidPhoneNumber = phoneNumber + .replace(/[^\d]/g, "") + .startsWith("010"); + + const handleNextClick = () => { + if (isPhoneNumberComplete && isValidPhoneNumber) { + setIsModalOpen(true); + setShowError(false); + } else if (isPhoneNumberComplete && !isValidPhoneNumber) { + setShowError(true); + } + }; + + const handleCloseModal = () => { + setIsModalOpen(false); + }; + + const handleOpenViewModal = ( + termType: "service" | "privacy" | "marketing" + ) => { + setSelectedTermType(termType); + setIsViewModalOpen(true); + }; + + const handleCloseViewModal = () => { + setIsViewModalOpen(false); + }; + + const handlePhoneNumberChange = (value: string) => { + setPhoneNumber(value); + // 전화번호가 변경되면 에러 메시지 숨기기 + if (showError) { + setShowError(false); + } + }; + + return ( +
+
+
+ 전화번호를 알려주세요
+ 호출 시에 필요해요 +
+ + + + {/* 에러 메시지 */} + {showError && ( +
+ 유효하지 않은 번호입니다. +
+ )} +
+ +
+ +
+ + {/* TermsModal 오버레이 */} + + + + + {/* ViewModal 오버레이 */} + + + +
+ ); +}; + +export default OnboardingPage; diff --git a/apps/nowait-user/src/pages/login/onboarding/OnboardingSuccessPage.tsx b/apps/nowait-user/src/pages/login/onboarding/OnboardingSuccessPage.tsx new file mode 100644 index 00000000..208e944f --- /dev/null +++ b/apps/nowait-user/src/pages/login/onboarding/OnboardingSuccessPage.tsx @@ -0,0 +1,23 @@ +import SuccessImage from "../../../assets/icon/signup.svg?react"; + +const OnboardingSuccessPage = () => { + return ( +
+
+
+ +
+
+
+ 가입을 축하드려요 +
+
+ 노웨잇과 함께 축제를 즐겨봐요! +
+
+
+
+ ); +}; + +export default OnboardingSuccessPage; diff --git a/apps/nowait-user/src/routes/Router.tsx b/apps/nowait-user/src/routes/Router.tsx index 43d0d76d..47087409 100644 --- a/apps/nowait-user/src/routes/Router.tsx +++ b/apps/nowait-user/src/routes/Router.tsx @@ -21,6 +21,8 @@ import StoreNoticePage from "../pages/waiting/storeNotice/StoreNoticePage"; import WaitingSummaryPage from "../pages/waiting/WaitingSummary/WaitingSummaryPage"; import MapManagePage from "../pages/waiting/boothMap/MapManagePage"; import NotFound from "../pages/NotFound/NotFound"; +import OnboardingPage from "../pages/login/onboarding/OnboardingPage"; +import OnboardingSuccessPage from "../pages/login/onboarding/OnboardingSuccessPage"; // AuthGuard로 래핑하는 헬퍼 함수 const withAuth = (Component: React.ComponentType) => ( @@ -42,6 +44,10 @@ const Router = () => { } /> } /> + } /> + + } /> + {/* 보호된 라우트 - 인증 필요 (구체적인 경로 먼저) */} { path="/:storeId/orderDetails" element={withTransition(OrderDetailsPage)} /> - } /> + } /> ); diff --git a/packages/tailwind-config/shared-styles.css b/packages/tailwind-config/shared-styles.css index 10704422..aaeedc8d 100644 --- a/packages/tailwind-config/shared-styles.css +++ b/packages/tailwind-config/shared-styles.css @@ -277,7 +277,7 @@ pre { letter-spacing: 0em; font-weight: 400; } - + .text-14-semibold { font-size: 14px; line-height: 136%; @@ -408,6 +408,23 @@ pre { font-weight: 500; } + /* 이용약관 폰트 */ + .term-text-13-semibold { + font-size: 13px; + line-height: 160%; + letter-spacing: 0%; + font-weight: 600; + color: var(--black-70); + } + + .term-text-13-regular { + font-size: 13px; + line-height: 160%; + letter-spacing: 0%; + font-weight: 400; + color: var(--black-70); + } + /* Work Sans 폰트 유틸리티 */ .font-work-sans { font-family: "Work Sans", sans-serif;