Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,17 @@
--radius-2xl: calc(var(--radius) + 8px);
--radius-3xl: calc(var(--radius) + 12px);
--radius-4xl: calc(var(--radius) + 16px);

--animate-shimmer: shimmer 1s linear infinite;

@keyframes shimmer {
from {
background-position: 0% 0%;
}
to {
background-position: 200% 0%;
}
}
}

:root {
Expand Down
164 changes: 164 additions & 0 deletions app/matching-result/_components/MatchingResult.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
import {
ALL_ADVANTAGES,
ALL_HOBBIES,
findWithEmoji,
} from "@/lib/utils/matching";
import Image from "next/image";
import React from "react";

const MatchingResult = () => {
// 임시 데이터 (상수에 존재하는 값들로 구성)
const data = {
nickname: "겨울이오길",
major: "정보통신전자공학부",
age: "21",
mbti: "ENTP",
contactFrequency: "보통",
hobbies: ["축구", "영화감상", "캠핑", "코딩", "게임"],
strengths: ["다정다감", "유머러스", "계획적"],
song: "한로로 - 사랑하게 될 거야",
intro: "친하게 지내요@!🙃",
instagram: "@winterizcoming_",
};
Comment thread
dasosann marked this conversation as resolved.
Comment thread
dasosann marked this conversation as resolved.

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]">

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Repository Style Guide 85번에 따라, 단순히 스타일을 위한 div 대신 의미론적인 article 태그를 사용하여 매칭 결과라는 독립적인 콘텐츠 단위를 명시하는 것을 권장합니다. (닫는 태그인 160번 라인도 함께 수정이 필요합니다.)

References
  1. Semantic Markup: 단순히 스타일을 위해
    를 남용하지 말고, main, section, article 등 의미에 맞는 태그를 사용했는지 엄격히 확인하세요. (link)

<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"
Comment thread
dasosann marked this conversation as resolved.
alt="Profile"
Comment thread
dasosann marked this conversation as resolved.
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) => (
<div
key={hobby}
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(ALL_HOBBIES, 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) => (
<div
key={strength}
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(ALL_ADVANTAGES, 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]">
Instagram
</span>
<span className="typo-16-700 flex w-full items-center justify-center text-center text-[#FF4D61]">
{data.instagram}
</span>
</div>
</div>
Comment thread
dasosann marked this conversation as resolved.
);
};

export default MatchingResult;
132 changes: 132 additions & 0 deletions app/matching-result/_components/ResultFooter.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
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<ReturnType<typeof setInterval> | 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("한 번 더 뽑기 로직 실행!"); // 로직 실행 위치
Comment thread
dasosann marked this conversation as resolved.
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}
aria-label="같은 조건으로 한 번 더 뽑기. 3초간 길게 누르면 실행됩니다."
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 thread
dasosann marked this conversation as resolved.
>
{!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;
48 changes: 48 additions & 0 deletions app/matching-result/_components/ScreenMatchingResult.tsx
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 thread
dasosann marked this conversation as resolved.

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;
23 changes: 23 additions & 0 deletions app/matching-result/_components/WaitingFrame.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
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]">
<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;
5 changes: 5 additions & 0 deletions app/matching-result/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import ScreenMatchingResult from "./_components/ScreenMatchingResult";

export default function MatchingResultPage() {
return <ScreenMatchingResult />;
}
Loading
Loading