-
Notifications
You must be signed in to change notification settings - Fork 1
feat: 매칭결과 디자인 #56
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
feat: 매칭결과 디자인 #56
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,169 @@ | ||
| import { ADVANTAGES } from "@/lib/constants/advantages"; | ||
| import { HOBBIES } from "@/lib/constants/hobbies"; | ||
| import Image from "next/image"; | ||
| import React from "react"; | ||
|
|
||
| const MatchingResult = () => { | ||
| // 백엔드에서 내려오는 텍스트와 상수를 매칭하여 이모지를 포함한 전체 문자열을 반환하는 헬퍼 함수 | ||
| const allHobbies = Object.values(HOBBIES).flat(); | ||
| const allAdvantages = Object.values(ADVANTAGES).flat(); | ||
|
|
||
| const findWithEmoji = (list: readonly string[] | string[], text: string) => { | ||
| return list.find((item) => item.includes(text)) || text; | ||
| }; | ||
|
|
||
| // 임시 데이터 (상수에 존재하는 값들로 구성) | ||
| const data = { | ||
| nickname: "겨울이오길", | ||
| major: "정보통신전자공학부", | ||
| age: "21", | ||
| mbti: "ENTP", | ||
| contactFrequency: "보통", | ||
| hobbies: ["축구", "영화감상", "캠핑", "코딩", "게임"], | ||
| strengths: ["다정다감", "유머러스", "계획적"], | ||
| song: "한로로 - 사랑하게 될 거야", | ||
| intro: "친하게 지내요@!🙃", | ||
| instagram: "@winterizcoming_", | ||
| }; | ||
|
Comment on lines
+16
to
+27
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
|
|
||
| return ( | ||
| <div className="mt-6 flex w-full flex-col gap-6 rounded-[24px] border border-white/30 bg-white/50 p-6 shadow-[0px_0px_8px_rgba(0,0,0,0.08)] backdrop-blur-[50px]"> | ||
| <div className="flex w-full flex-col gap-4"> | ||
| {/* Header Section (Frame 2612385) */} | ||
| <div className="flex w-full flex-row items-center gap-4"> | ||
| {/* Profile Image (Container + Image (Profile)) */} | ||
| <div className="flex h-12 w-12 shrink-0 items-center justify-center rounded-full border-2 border-white bg-white/0 p-[2px] shadow-[0px_1px_3px_rgba(0,0,0,0.1),0px_1px_2px_-1px_rgba(0,0,0,0.1)]"> | ||
| <div className="relative h-11 w-11 overflow-hidden rounded-full"> | ||
| <Image | ||
| src="/animal/cat_female 1.png" | ||
| alt="Profile" | ||
| fill | ||
| className="object-cover" | ||
| /> | ||
| </div> | ||
| </div> | ||
| {/* Nickname & Label (Frame 2612933) */} | ||
| <div className="flex flex-col items-start gap-1"> | ||
| <span className="typo-12-600 flex items-center text-[#777777]"> | ||
| 내가 뽑은 사람 | ||
| </span> | ||
| <span className="typo-16-600 flex items-center text-black"> | ||
| {data.nickname} | ||
| </span> | ||
| </div> | ||
| </div> | ||
|
|
||
| {/* Major Section */} | ||
| <div className="flex w-full flex-col items-start gap-1"> | ||
| <span className="typo-12-600 flex items-center text-[#777777]"> | ||
| 전공 | ||
| </span> | ||
| <span className="typo-16-700 flex items-center text-black"> | ||
| {data.major} | ||
| </span> | ||
| </div> | ||
|
|
||
| {/* Stats Section (Frame 22) */} | ||
| <div className="flex w-full flex-row items-start gap-2"> | ||
| {/* Age (Std_Num) */} | ||
| <div className="flex flex-1 flex-col items-center gap-1"> | ||
| <span className="typo-12-600 flex w-full items-center text-[#777777]"> | ||
| 나이 | ||
| </span> | ||
| <span className="typo-16-700 flex w-full items-center text-black"> | ||
| {data.age} | ||
| </span> | ||
| </div> | ||
| {/* MBTI (Major) */} | ||
| <div className="flex flex-1 flex-col items-start gap-1"> | ||
| <span className="typo-12-600 flex w-full items-center text-[#777777]"> | ||
| MBTI | ||
| </span> | ||
| <span className="typo-16-700 flex w-full items-center text-black"> | ||
| {data.mbti} | ||
| </span> | ||
| </div> | ||
| {/* Contact (Std_Num) */} | ||
| <div className="flex flex-1 flex-col items-start gap-1"> | ||
| <span className="typo-12-600 flex items-center text-[#777777]"> | ||
| 연락빈도 | ||
| </span> | ||
| <span className="typo-16-700 flex w-full items-center text-black"> | ||
| {data.contactFrequency} | ||
| </span> | ||
| </div> | ||
| </div> | ||
| </div> | ||
|
|
||
| {/* Hobbies Section */} | ||
| <div className="flex w-full flex-col items-start gap-1"> | ||
| <span className="typo-12-600 flex items-center text-[#777777]"> | ||
| 취미 | ||
| </span> | ||
| <div className="flex w-full flex-row flex-wrap items-start gap-1 py-1"> | ||
| {data.hobbies.map((hobby, index) => ( | ||
| <div | ||
| key={index} | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
| className="flex h-8 items-center justify-center gap-[10px] rounded-full border border-[#DFDFDF] bg-[#B3B3B3]/10 px-3 py-2 backdrop-blur-[50px]" | ||
| > | ||
| <span className="typo-14-500 text-black"> | ||
| {findWithEmoji(allHobbies, hobby)} | ||
| </span> | ||
| </div> | ||
| ))} | ||
| </div> | ||
| </div> | ||
|
|
||
| {/* Strengths Section */} | ||
| <div className="flex w-full flex-col items-start gap-1"> | ||
| <span className="typo-12-600 flex items-center text-[#777777]"> | ||
| 장점 | ||
| </span> | ||
| <div className="flex w-full flex-row flex-wrap items-start gap-1 py-1"> | ||
| {data.strengths.map((strength, index) => ( | ||
| <div | ||
| key={index} | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
| className="flex h-8 items-center justify-center gap-[10px] rounded-full border border-[#DFDFDF] bg-[#B3B3B3]/10 px-3 py-2 backdrop-blur-[50px]" | ||
| > | ||
| <span className="typo-14-500 text-black"> | ||
| {findWithEmoji(allAdvantages, strength)} | ||
| </span> | ||
| </div> | ||
| ))} | ||
| </div> | ||
| </div> | ||
|
|
||
| {/* Song Section */} | ||
| <div className="flex w-full flex-col items-start gap-1"> | ||
| <span className="typo-12-600 flex items-center text-[#777777]"> | ||
| 좋아하는 노래 | ||
| </span> | ||
| <span className="typo-16-700 flex items-center text-black"> | ||
| {data.song} | ||
| </span> | ||
| </div> | ||
|
|
||
| {/* Intro Section */} | ||
| <div className="flex w-full flex-col items-start gap-1"> | ||
| <span className="typo-12-600 flex items-center text-[#777777]"> | ||
| 나를 소개하는 한마디 | ||
| </span> | ||
| <span className="typo-16-700 flex items-center text-black"> | ||
| {data.intro} | ||
| </span> | ||
| </div> | ||
|
|
||
| {/* SNS Section (Contacts) */} | ||
| <div className="flex w-full flex-col items-start gap-1 py-2"> | ||
| <span className="typo-12-600 flex w-full items-center justify-center text-center text-[#777777]"> | ||
| </span> | ||
| <span className="typo-16-700 flex w-full items-center justify-center text-center text-[#FF4D61]"> | ||
| {data.instagram} | ||
| </span> | ||
| </div> | ||
| </div> | ||
| ); | ||
| }; | ||
|
|
||
| export default MatchingResult; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,131 @@ | ||
| import Image from "next/image"; | ||
| import React, { useState, useEffect, useRef } from "react"; | ||
|
|
||
| const ResultFooter = () => { | ||
| const [timeLeft, setTimeLeft] = useState(3); | ||
| const [isHolding, setIsHolding] = useState(false); | ||
| const [isTriggered, setIsTriggered] = useState(false); | ||
| const timerRef = useRef<NodeJS.Timeout | null>(null); | ||
|
|
||
| const handleHoldStart = (e: React.MouseEvent | React.TouchEvent) => { | ||
| // 롱프레스 시 브라우저 기본 컨텍스트 메뉴 등이 뜨지 않도록 방지 (모바일 대응) | ||
| if ("button" in e && e.button !== 0) return; // 마우스 왼쪽 클릭만 허용 | ||
|
|
||
| // 모바일에서 touchstart 후에 mousedown이 또 발생하는 것 방지 | ||
| if (e.type === "touchstart") { | ||
| // e.preventDefault(); // 필요 시 추가 (단, 스크롤 방해 가능성 있음) | ||
| } | ||
|
|
||
| if (isHolding) return; // 이미 누르는 중이면 무시 | ||
|
|
||
| setIsHolding(true); | ||
| setTimeLeft(3); | ||
| setIsTriggered(false); | ||
| }; | ||
|
|
||
| const handleHoldEnd = () => { | ||
| setIsHolding(false); | ||
| if (timerRef.current) { | ||
| clearInterval(timerRef.current); | ||
| timerRef.current = null; | ||
| } | ||
| // 성공적으로 트리거된 경우가 아니라면 시간 초기화 | ||
| if (!isTriggered) { | ||
| setTimeLeft(3); | ||
| } | ||
| }; | ||
|
|
||
| useEffect(() => { | ||
| if (isHolding) { | ||
| timerRef.current = setInterval(() => { | ||
| setTimeLeft((prev) => { | ||
| if (prev <= 1) { | ||
| setIsTriggered((prevTriggered) => { | ||
| if (prevTriggered) return prevTriggered; | ||
| alert("한 번 더 뽑기 로직 실행!"); // 로직 실행 위치 | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
| return true; | ||
| }); | ||
| setIsHolding(false); | ||
| return 0; | ||
| } | ||
| return prev - 1; | ||
| }); | ||
| }, 1000); | ||
| } else { | ||
| if (timerRef.current) { | ||
| clearInterval(timerRef.current); | ||
| timerRef.current = null; | ||
| } | ||
| } | ||
|
|
||
| return () => { | ||
| if (timerRef.current) clearInterval(timerRef.current); | ||
| }; | ||
| }, [isHolding]); | ||
|
|
||
| return ( | ||
| <div className="mt-6 flex w-full flex-col gap-3"> | ||
| {/* Top Row: Retry & Mail Buttons */} | ||
| <div className="flex w-full flex-row gap-[10px]"> | ||
| {/* Retry Button */} | ||
| <button className="flex flex-1 flex-col items-center justify-center rounded-[15px] bg-white px-[22px] py-4 shadow-[0px_4px_16px_rgba(0,0,0,0.12)] backdrop-blur-[50px]"> | ||
| <span className="typo-18-700 text-[#4E4E4E]">다시 뽑기</span> | ||
| </button> | ||
|
|
||
| {/* Mail Button */} | ||
| <button className="bg-milky-pink flex flex-1 flex-col items-center justify-center rounded-[15px] px-[22px] py-4 text-white shadow-[0px_4px_16px_rgba(0,0,0,0.12)] backdrop-blur-[50px]"> | ||
| <span className="typo-18-700 text-white">쪽지 보내기</span> | ||
| </button> | ||
| </div> | ||
|
|
||
| {/* Bottom Row: One More Button with Long Press */} | ||
| <button | ||
| onMouseDown={handleHoldStart} | ||
| onMouseUp={handleHoldEnd} | ||
| onMouseLeave={handleHoldEnd} | ||
| onTouchStart={handleHoldStart} | ||
| onTouchEnd={handleHoldEnd} | ||
| className="flex w-full flex-row items-center justify-center gap-2 rounded-[15px] bg-linear-to-r from-[#FF4D61] to-[#FF775E] px-[22px] py-4 text-white shadow-[0px_4px_16px_rgba(0,0,0,0.12)] backdrop-blur-[50px] transition-all select-none active:scale-[0.98]" | ||
|
Comment on lines
+82
to
+88
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
| > | ||
| {!isHolding && !isTriggered && ( | ||
| <div | ||
| className="flex h-6 flex-row items-center gap-[10px] rounded-[36px] px-2 py-1 shadow-[0px_4px_16px_rgba(0,0,0,0.12)] backdrop-blur-[50px]" | ||
| style={{ | ||
| background: | ||
| "radial-gradient(100% 99.65% at 0% -4.11%, #FFFFFF 0%, rgba(255, 255, 255, 0.85) 100%)", | ||
| }} | ||
| > | ||
| <div className="flex flex-row items-center gap-1"> | ||
| <Image | ||
| src="/main/coin.png" | ||
| alt="coin" | ||
| width={16} | ||
| height={16} | ||
| className="scale-x-[-1]" | ||
| /> | ||
| <span className="typo-12-700 text-black">1</span> | ||
| </div> | ||
| <div className="flex flex-row items-center gap-1"> | ||
| <Image | ||
| src="/main/elec-bulb.png" | ||
| alt="bulb" | ||
| width={16} | ||
| height={16} | ||
| /> | ||
| <span className="typo-12-700 text-black">1</span> | ||
| </div> | ||
| </div> | ||
| )} | ||
| <span className="typo-18-700"> | ||
| {isHolding | ||
| ? `${timeLeft}초간 길게 누르세요 ...` | ||
| : isTriggered | ||
| ? "처리 중..." | ||
| : "같은 조건으로 한번 더 뽑기"} | ||
| </span> | ||
| </button> | ||
| </div> | ||
| ); | ||
| }; | ||
|
|
||
| export default ResultFooter; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,48 @@ | ||
| "use client"; | ||
| import React, { useState, useEffect } from "react"; | ||
| import { BackButton } from "@/components/ui/BackButton"; | ||
| import Image from "next/image"; | ||
| import WaitingFrame from "./WaitingFrame"; | ||
| import MatchingResult from "./MatchingResult"; | ||
| import ResultFooter from "./ResultFooter"; | ||
|
|
||
| const ScreenMatchingResult = () => { | ||
| const [isWaiting, setIsWaiting] = useState(true); | ||
|
|
||
| useEffect(() => { | ||
| const timer = setTimeout(() => { | ||
| setIsWaiting(false); | ||
| }, 3000); | ||
|
Comment on lines
+13
to
+15
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
|
|
||
| return () => clearTimeout(timer); | ||
| }, []); | ||
|
|
||
| return ( | ||
| <main className="relative flex min-h-screen flex-col items-center px-4 py-2 pb-10"> | ||
| <BackButton | ||
| text={ | ||
| <div className="flex items-center gap-1.5"> | ||
| <Image | ||
| src="/logo/comatching-logo.svg" | ||
| alt="Comatching" | ||
| width={96} | ||
| height={16} | ||
| priority | ||
| /> | ||
| <span className="typo-18-700 text-color-text-black">매칭 결과</span> | ||
| </div> | ||
| } | ||
| /> | ||
| {isWaiting ? ( | ||
| <WaitingFrame /> | ||
| ) : ( | ||
| <> | ||
| <MatchingResult /> | ||
| <ResultFooter /> | ||
| </> | ||
| )} | ||
| </main> | ||
| ); | ||
| }; | ||
|
|
||
| export default ScreenMatchingResult; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,26 @@ | ||
| import React from "react"; | ||
|
|
||
| const WaitingFrame = () => { | ||
| return ( | ||
| <div | ||
| className="mt-6 flex h-[200px] w-full flex-col items-center gap-[10px] rounded-[24px] border border-white/30 bg-white/50 p-6 shadow-[0px_0px_8px_rgba(0,0,0,0.08)] backdrop-blur-[50px]" | ||
| style={{ boxSizing: "border-box" }} | ||
| > | ||
| <div className="flex w-full flex-col self-stretch"> | ||
| <p | ||
| className="animate-shimmer typo-16-500 bg-linear-to-r from-[#666666] via-[#B3B3B3] to-[#666666] bg-[length:200%_auto] bg-clip-text text-start leading-[19px] text-transparent" | ||
| style={{ | ||
| backgroundImage: | ||
| "linear-gradient(91.24deg, #666666 9.51%, #B3B3B3 35.68%, #666666 76.49%)", | ||
| }} | ||
| > | ||
| 코매칭 AI가 입력하신 결과를 바탕으로 | ||
| <br /> | ||
| 비슷한 매칭 상대를 찾고 있어요.. | ||
| </p> | ||
| </div> | ||
| </div> | ||
| ); | ||
| }; | ||
|
|
||
| export default WaitingFrame; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
allHobbies, allAdvantages 변수와 findWithEmoji 함수는 컴포넌트의 상태나 props에 의존하지 않는 순수 로직입니다. 프로젝트 규칙에 따라 이를 공통 유틸리티 파일로 추출하여 재사용성을 높이고 관심사를 분리하는 것을 권장합니다.
References