From cdce90add965c3f66beec2120a8f87864a75be72 Mon Sep 17 00:00:00 2001 From: yeahsel Date: Mon, 2 Feb 2026 15:52:22 +0900 Subject: [PATCH] =?UTF-8?q?=EB=A7=88=EC=9D=B4=ED=8E=98=EC=9D=B4=EC=A7=80?= =?UTF-8?q?=20UI=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/routes/mypage/components/ConfirmModal.tsx | 76 ------ .../mypage/components/mypage/ConfirmModal.tsx | 122 +++++++++ .../mypage/components/mypage/GateModal.tsx | 60 +++++ .../mypage/components/mypage/MenuButton.tsx | 45 ++++ .../components/profileCard/CommonSection.tsx | 19 ++ .../profileCard/MatchingSection.tsx | 39 +++ .../components/profileCard/ProfileSection.tsx | 55 ++++ .../components/profileCard/SnsSection.tsx | 28 ++ .../components/profileCard/TraitCard.tsx | 36 +++ .../components/profileCard/TraitModal.tsx | 106 ++++++++ .../components/profileCard/TraitsSection.tsx | 37 +++ .../components/profileCard/traitData.tsx | 112 ++++++++ app/routes/mypage/mypage/MyPageHome.tsx | 112 +------- .../notifications-content.tsx | 0 .../{notifivation => notification}/route.tsx | 0 .../profileCard/profileCard-content.tsx | 248 +++++------------- 16 files changed, 728 insertions(+), 367 deletions(-) delete mode 100644 app/routes/mypage/components/ConfirmModal.tsx create mode 100644 app/routes/mypage/components/mypage/ConfirmModal.tsx create mode 100644 app/routes/mypage/components/mypage/GateModal.tsx create mode 100644 app/routes/mypage/components/mypage/MenuButton.tsx create mode 100644 app/routes/mypage/components/profileCard/CommonSection.tsx create mode 100644 app/routes/mypage/components/profileCard/MatchingSection.tsx create mode 100644 app/routes/mypage/components/profileCard/ProfileSection.tsx create mode 100644 app/routes/mypage/components/profileCard/SnsSection.tsx create mode 100644 app/routes/mypage/components/profileCard/TraitCard.tsx create mode 100644 app/routes/mypage/components/profileCard/TraitModal.tsx create mode 100644 app/routes/mypage/components/profileCard/TraitsSection.tsx create mode 100644 app/routes/mypage/components/profileCard/traitData.tsx rename app/routes/mypage/{notifivation => notification}/notifications-content.tsx (100%) rename app/routes/mypage/{notifivation => notification}/route.tsx (100%) diff --git a/app/routes/mypage/components/ConfirmModal.tsx b/app/routes/mypage/components/ConfirmModal.tsx deleted file mode 100644 index 7d17143..0000000 --- a/app/routes/mypage/components/ConfirmModal.tsx +++ /dev/null @@ -1,76 +0,0 @@ -export default function ConfirmModal({ - title, - desc, - primaryText, - onClose, - onPrimary, -}: { - title: string; - desc?: string; - primaryText: string; - onClose: () => void; - onPrimary: () => void; -}) { - return ( -
- {/* dim */} -
- - {/* modal */} -
- {/* content */} -
-
- {title} -
- - {desc ? ( -
- {desc} -
- ) : null} -
- - {/* buttons – 맨 아래 고정 */} -
-
- - - -
-
-
-
- ); -} diff --git a/app/routes/mypage/components/mypage/ConfirmModal.tsx b/app/routes/mypage/components/mypage/ConfirmModal.tsx new file mode 100644 index 0000000..ef3db64 --- /dev/null +++ b/app/routes/mypage/components/mypage/ConfirmModal.tsx @@ -0,0 +1,122 @@ +export default function ConfirmModal({ + title, + desc, + primaryText, + onClose, + onPrimary, + + showCloseIcon = false, + icon = null, + closeOnDim = false, + widthClassName = "w-[343px]", + heightClassName = "h-[214px]", + containerClassName = "", + dimClassName = "bg-[#17171833]", + titleClassName = "text-[20px] leading-[24px]", + descClassName = "text-[14px] leading-[20px] text-[#6B6B73]", +}: { + title: React.ReactNode; + desc?: React.ReactNode; + primaryText: string; + onClose: () => void; + onPrimary: () => void; + + showCloseIcon?: boolean; // 왼쪽 상단 X + icon?: React.ReactNode; // 상단 아이콘(느낌표) + closeOnDim?: boolean; // 딤 클릭 시 닫기 + widthClassName?: string; // 모달 너비 + heightClassName?: string; // 모달 높이 + containerClassName?: string; // 모달 박스 추가 클래스 + dimClassName?: string; // 딤 색 + titleClassName?: string; // 타이틀 스타일 + descClassName?: string; // 설명 스타일 +}) { + return ( +
+ {/* dim */} + {closeOnDim ? ( + + ) : null} + + {/* content */} +
+ {/* icon */} + {icon ?
{icon}
: null} + +
+ {title} +
+ + {desc ? ( +
+ {desc} +
+ ) : null} +
+ + {/* buttons – bottom */} +
+
+ + + +
+
+
+
+ ); +} \ No newline at end of file diff --git a/app/routes/mypage/components/mypage/GateModal.tsx b/app/routes/mypage/components/mypage/GateModal.tsx new file mode 100644 index 0000000..024fa52 --- /dev/null +++ b/app/routes/mypage/components/mypage/GateModal.tsx @@ -0,0 +1,60 @@ +export default function GateModal({ + onClose, + onGoTest, +}: { + onClose: () => void; + onGoTest: () => void; +}) { + return ( +
+ {/* dim */} +
+ + {/* modal */} +
+
+ +
+ +
+
+ ! +
+ +
+ 매칭 검사를 +
+ 먼저 진행해주세요 +
+ + +
+
+
+ ); +} \ No newline at end of file diff --git a/app/routes/mypage/components/mypage/MenuButton.tsx b/app/routes/mypage/components/mypage/MenuButton.tsx new file mode 100644 index 0000000..c07f8ac --- /dev/null +++ b/app/routes/mypage/components/mypage/MenuButton.tsx @@ -0,0 +1,45 @@ +export default function MenuButton({ + title, label, onClick, muted, py, +}: { + title?: string; + label: string; + onClick: () => void; + muted?: boolean; + py?: number; +}) { + return ( +
+ {title && ( +
+ {title} +
+ )} + + {/* label */} +
+ +
+
+ ); +} \ No newline at end of file diff --git a/app/routes/mypage/components/profileCard/CommonSection.tsx b/app/routes/mypage/components/profileCard/CommonSection.tsx new file mode 100644 index 0000000..52d12f9 --- /dev/null +++ b/app/routes/mypage/components/profileCard/CommonSection.tsx @@ -0,0 +1,19 @@ +export default function Section({ + title, + right, + children, +}: { + title: string; + right?: React.ReactNode; + children: React.ReactNode; +}) { + return ( +
+
+
{title}
+ {right} +
+ {children} +
+ ); +} \ No newline at end of file diff --git a/app/routes/mypage/components/profileCard/MatchingSection.tsx b/app/routes/mypage/components/profileCard/MatchingSection.tsx new file mode 100644 index 0000000..24570fe --- /dev/null +++ b/app/routes/mypage/components/profileCard/MatchingSection.tsx @@ -0,0 +1,39 @@ +import Section from "./CommonSection" + +interface MatchingSectionProps { + onOpenReMatch: () => void; +} + +export default function MatchingSection({ onOpenReMatch }: MatchingSectionProps ) { + return ( +
+ +
+ ); +} \ No newline at end of file diff --git a/app/routes/mypage/components/profileCard/ProfileSection.tsx b/app/routes/mypage/components/profileCard/ProfileSection.tsx new file mode 100644 index 0000000..4e8764d --- /dev/null +++ b/app/routes/mypage/components/profileCard/ProfileSection.tsx @@ -0,0 +1,55 @@ +export default function ProfileSection() { + + const profileImage = ""; + + return ( +
+ {/* profile summary */} +
+
+
+ {/* 프로필 이미지 */} + {profileImage ? ( + profile + ) : ( +
+ )} +
+ + +
+ +
+
비비
+
여성 22세
+
+ 관심분야: 뷰티, 패션 +
+
+
+
+ ); +} \ No newline at end of file diff --git a/app/routes/mypage/components/profileCard/SnsSection.tsx b/app/routes/mypage/components/profileCard/SnsSection.tsx new file mode 100644 index 0000000..22fa7ab --- /dev/null +++ b/app/routes/mypage/components/profileCard/SnsSection.tsx @@ -0,0 +1,28 @@ +import Section from "./CommonSection"; + +export default function SnsSection() { + return ( +
+
+
+ + + +
+
www.instagram.com/vivi
+ +
+
+ ) +} \ No newline at end of file diff --git a/app/routes/mypage/components/profileCard/TraitCard.tsx b/app/routes/mypage/components/profileCard/TraitCard.tsx new file mode 100644 index 0000000..4915fa8 --- /dev/null +++ b/app/routes/mypage/components/profileCard/TraitCard.tsx @@ -0,0 +1,36 @@ +type Trait = { + id: string; + badge: string; + icon: (className?: string) => React.ReactNode; + previewLines: { label: string; value: string }[]; +}; + +export default function TraitCard({ trait, onClick }: { trait: Trait; onClick: () => void }) { + return ( + + ); +} \ No newline at end of file diff --git a/app/routes/mypage/components/profileCard/TraitModal.tsx b/app/routes/mypage/components/profileCard/TraitModal.tsx new file mode 100644 index 0000000..acc2270 --- /dev/null +++ b/app/routes/mypage/components/profileCard/TraitModal.tsx @@ -0,0 +1,106 @@ +type Trait = { + badge: string; + icon: (className?: string) => React.ReactNode; + topSummary: { label: string; value: string }[]; + sections: { title: string; items: string[] }[]; +}; + +export default function TraitModal({ + trait, + onClose, +}: { + trait: Trait; + onClose: () => void; +}) { + const cols = trait.topSummary.length; // 3개 or 4개 + + return ( +
+
e.stopPropagation()} + > + {/* close */} + + + {/* body */} + +
+
+
+
+
{trait.icon("w-[46px] h-[47px]")}
+
+
+ {trait.badge} +
+
+
+ + {/* top summary bar */} +
+
+ {trait.topSummary.map((item, i) => ( +
+
+ {item.label} +
+
+ {item.value} +
+
+ ))} +
+
+ + {/* sections */} +
+ {trait.sections.map((section, i) => ( +
+
+ {section.title} +
+
+ {section.items.join(", ")} +
+
+ ))} +
+
+
+
+ ); +} diff --git a/app/routes/mypage/components/profileCard/TraitsSection.tsx b/app/routes/mypage/components/profileCard/TraitsSection.tsx new file mode 100644 index 0000000..c1e4981 --- /dev/null +++ b/app/routes/mypage/components/profileCard/TraitsSection.tsx @@ -0,0 +1,37 @@ +import { useState } from "react"; +import Section from "./CommonSection"; +import TraitCard from "./TraitCard"; +import TraitModal from "./TraitModal"; +import { TRAITS } from "./traitData"; +export default function TraitsSection() { + const [selectedTrait, setSelectedTrait] = useState(null); + + return ( + <> +
+ + + + + } + > +
+
+ {TRAITS.map((trait) => ( +
+ setSelectedTrait(trait)} /> +
+ ))} +
+
+
+ + {selectedTrait && ( + setSelectedTrait(null)} /> + )} + + ); +} \ No newline at end of file diff --git a/app/routes/mypage/components/profileCard/traitData.tsx b/app/routes/mypage/components/profileCard/traitData.tsx new file mode 100644 index 0000000..bca3030 --- /dev/null +++ b/app/routes/mypage/components/profileCard/traitData.tsx @@ -0,0 +1,112 @@ +import beautyIcon from "../../../../assets/beauty-icon.svg"; +import fashionIcon from "../../../../assets/fashion-icon.svg"; +import contentIcon from "../../../../assets/content-icon.svg"; + +type Trait = { + id: "beauty" | "fashion" | "content"; + badge: string; + icon: (className?: string) => React.ReactNode; + previewLines: { label: string; value: string }[]; + topSummary: { label: string; value: string }[]; + sections: { title: string; items: string[] }[]; +}; + +export const TRAITS: Trait[] = [ + { + id: "beauty", + badge: "뷰티 특성", + icon: (className = "") => ( + beauty + ), + previewLines: [ + { label: "피부 타입", value: "건성, 민감성" }, + { label: "피부 밝기", value: "17~21호" }, + { label: "메이크업 \n스타일", value: "내추럴, 글로우" }, + ], + topSummary: [ + { label: "피부타입", value: "건성, 민감성" }, + { label: "피부 밝기", value: "17~21호" }, + { label: "메이크업 스타일", value: "내추럴, 글로우" }, + ], + sections: [ + { + title: "관심 카테고리", + items: ["스킨케어", "메이크업", "향수", "바디", "헤어"], + }, + { + title: "관심 기능", + items: ["수분 / 보습", "진정", "미백", "안티에이징"], + }, + ], + }, + { + id: "fashion", + badge: "패션 특성", + icon: (className = "") => ( + fashion + ), + previewLines: [ + { label: "키", value: "165cm" }, + { label: "체형", value: "웨이브" }, + { label: "상의", value: "S" }, + { label: "하의", value: "33 inch" }, + ], + topSummary: [ + { label: "키/몸무게", value: "165cm" }, + { label: "체형", value: "웨이브" }, + { label: "상의 사이즈", value: "S" }, + { label: "하의 사이즈", value: "30" }, + ], + sections: [ + { + title: "관심 분야", + items: ["33 inch"], + }, + { + title: "관심 스타일", + items: ["캐주얼", "미니멀", "비즈니스 캐주얼"], + }, + { + title: "관심 브랜드", + items: ["디자이너 브랜드", "빈티지", "SPA"], + }, + ], + }, + { + id: "content", + badge: "향수 특성", + icon: (className = "") => ( + content + ), + previewLines: [ + { label: "성별", value: "우디, 머스크" }, + { label: "나이대", value: "미들" }, + { label: "평균 길이", value: "차분, 깨끗" }, + { label: "평균 조회수", value: "1~10만회" }, + ], + topSummary: [ + { label: "주 시청자 성별", value: "우디, 머스크" }, + { label: "주 시청자 나이대", value: "미들" }, + { label: "평균 영상 길이", value: "차분, 깨끗" }, + { label: "평균 조회수", value: "차분, 깨끗" }, + ], + sections: [ + { + title: "콘텐츠 형식", + items: ["시더우드", "머스크", "샌달우드"], + }, + { + title: "브랜드 톤", + items: ["데일리", "저녁", "특별한 날"], + }, + { + title: "희망 관여도", + items: ["데일리", "저녁", "특별한 날"], + }, + { + title: "희망 활용 범위", + items: ["데일리", "저녁", "특별한 날"], + }, + ], + }, +]; \ No newline at end of file diff --git a/app/routes/mypage/mypage/MyPageHome.tsx b/app/routes/mypage/mypage/MyPageHome.tsx index e096e16..1b11f0f 100644 --- a/app/routes/mypage/mypage/MyPageHome.tsx +++ b/app/routes/mypage/mypage/MyPageHome.tsx @@ -1,5 +1,7 @@ import { useState } from "react"; -import ConfirmModal from "../components/ConfirmModal"; +import ConfirmModal from "../components/mypage/ConfirmModal"; +import GateModal from "../components/mypage/GateModal"; +import MenuButton from "../components/mypage/MenuButton"; type Props = { // 서버/스토어에서 내려오는 값이라고 가정 @@ -186,114 +188,6 @@ export default function MyPageHome({ ); } -function MenuButton({ - title, label, onClick, muted, py, -}: { - title?: string; - label: string; - onClick: () => void; - muted?: boolean; - py?: number; -}) { - return ( -
- {title && ( -
- {title} -
- )} - - {/* label */} -
- -
-
- ); -} - - function Divider() { return
; -} - -function GateModal({ - onClose, - onGoTest, -}: { - onClose: () => void; - onGoTest: () => void; -}) { - return ( -
- {/* dim */} -
- - {/* modal */} -
-
- -
- -
-
- ! -
- -
- 매칭 검사를 -
- 먼저 진행해주세요 -
- - -
-
-
- ); } \ No newline at end of file diff --git a/app/routes/mypage/notifivation/notifications-content.tsx b/app/routes/mypage/notification/notifications-content.tsx similarity index 100% rename from app/routes/mypage/notifivation/notifications-content.tsx rename to app/routes/mypage/notification/notifications-content.tsx diff --git a/app/routes/mypage/notifivation/route.tsx b/app/routes/mypage/notification/route.tsx similarity index 100% rename from app/routes/mypage/notifivation/route.tsx rename to app/routes/mypage/notification/route.tsx diff --git a/app/routes/mypage/profileCard/profileCard-content.tsx b/app/routes/mypage/profileCard/profileCard-content.tsx index 6eb0611..83ec4c3 100644 --- a/app/routes/mypage/profileCard/profileCard-content.tsx +++ b/app/routes/mypage/profileCard/profileCard-content.tsx @@ -1,204 +1,88 @@ -import React from "react"; import NavigationHeader from "../../../components/common/NavigateHeader"; - -type Campaign = { - type: "보낸 제안" | "지원"; - title: string; - date: string; -}; +import ConfirmModal from "../components/mypage/ConfirmModal"; +import { useState } from "react"; +import { useNavigate } from "react-router"; +import { useHideHeader } from "../../../hooks/useHideHeader"; +import ProfileSection from "../components/profileCard/ProfileSection"; +import SnsSection from "../components/profileCard/SnsSection"; +import MatchingSection from "../components/profileCard/MatchingSection"; +import TraitsSection from "../components/profileCard/TraitsSection"; export default function ProfileCard() { - const campaigns: Campaign[] = [ - { type: "보낸 제안", title: "비플레인 - ‘글로우업’ 선크림 신제품 홍보…", date: "01/24/25 완료" }, - { type: "보낸 제안", title: "라운드랩 - ‘글로우업’ 크림 신제품 홍보…", date: "01/15/25 완료" }, - { type: "지원", title: "이즈토리 - 비타크림 신제품 체험단 모집", date: "12/15/24 완료" }, - ]; + + useHideHeader(true); + + const [openReMatchModal, setOpenReMatchModal] = useState(false); + const navigate = useNavigate(); + + const onOpenReMatch = () => { + setOpenReMatchModal(true); + }; + + const onCloseReMatch = () => { + setOpenReMatchModal(false); + }; + + const onConfirmReMatch = () => { + setOpenReMatchModal(false); + navigate("/matching/test/step1") + } return ( -
-
+
+
{/* header */} -
+
history.back()} />
-
- {/* profile summary */} -
-
- {/* 이미지 자리 */} -
-
-
-
-
+
+ + +
-
-
비비
-
여성 22세
-
- 관심분야: 뷰티, 패션 -
-
-
-
+
+ + +
+ + -
- - {/* content */} -
- {/* SNS */} -
-
-
- - - -
-
www.instagram.com/vivi
- -
-
- - {/* Matching */} -
-
-
-
- 비비 님은 -
-
- OO한 크리에이터 입니다. -
- OO한 브랜드와 잘 어울려요. +
+ + {openReMatchModal ? ( + + 기존 매칭 정보는 모두 삭제 + 되며 +
+ 새로운 검사 결과가 저장됩니다. + + } + primaryText="검사하기" + onClose={onCloseReMatch} + onPrimary={onConfirmReMatch} + showCloseIcon + closeOnDim + icon={ +
+
!
-
-
-
-
-
-
- - {/* Traits */} -
- › - - } - > -
- - -
-
+ ) : null} - {/* Campaigns */} -
- ˅ - - } - > -
- {campaigns.map((c, idx) => ( -
-
- {c.type} - {c.title} -
- {c.date} -
- ))} - - {/* pagination mock */} -
- - - - - - -
-
-
+
); -} - -function Section({ - title, - right, - children, -}: { - title: string; - right?: React.ReactNode; - children: React.ReactNode; -}) { - return ( -
-
-
{title}
- {right} -
- {children} -
- ); -} - -function TraitCard({ - badge, - icon, - lines, -}: { - badge: string; - icon: string; - lines: string[]; -}) { - return ( -
-
- {badge} -
- -
- {icon} -
- -
- {lines.map((t, i) => ( -
{t}
- ))} -
-
- ); } \ No newline at end of file