diff --git a/app/globals.css b/app/globals.css
index 2506853..da94dde 100644
--- a/app/globals.css
+++ b/app/globals.css
@@ -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 {
diff --git a/app/matching-result/_components/MatchingResult.tsx b/app/matching-result/_components/MatchingResult.tsx
new file mode 100644
index 0000000..64925bf
--- /dev/null
+++ b/app/matching-result/_components/MatchingResult.tsx
@@ -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_",
+ };
+
+ return (
+
+
+ {/* Header Section (Frame 2612385) */}
+
+ {/* Profile Image (Container + Image (Profile)) */}
+
+ {/* Nickname & Label (Frame 2612933) */}
+
+
+ 내가 뽑은 사람
+
+
+ {data.nickname}
+
+
+
+
+ {/* Major Section */}
+
+
+ 전공
+
+
+ {data.major}
+
+
+
+ {/* Stats Section (Frame 22) */}
+
+ {/* Age (Std_Num) */}
+
+
+ 나이
+
+
+ {data.age}
+
+
+ {/* MBTI (Major) */}
+
+
+ MBTI
+
+
+ {data.mbti}
+
+
+ {/* Contact (Std_Num) */}
+
+
+ 연락빈도
+
+
+ {data.contactFrequency}
+
+
+
+
+
+ {/* Hobbies Section */}
+
+
+ 취미
+
+
+ {data.hobbies.map((hobby, index) => (
+
+
+ {findWithEmoji(allHobbies, hobby)}
+
+
+ ))}
+
+
+
+ {/* Strengths Section */}
+
+
+ 장점
+
+
+ {data.strengths.map((strength, index) => (
+
+
+ {findWithEmoji(allAdvantages, strength)}
+
+
+ ))}
+
+
+
+ {/* Song Section */}
+
+
+ 좋아하는 노래
+
+
+ {data.song}
+
+
+
+ {/* Intro Section */}
+
+
+ 나를 소개하는 한마디
+
+
+ {data.intro}
+
+
+
+ {/* SNS Section (Contacts) */}
+
+
+ Instagram
+
+
+ {data.instagram}
+
+
+
+ );
+};
+
+export default MatchingResult;
diff --git a/app/matching-result/_components/ResultFooter.tsx b/app/matching-result/_components/ResultFooter.tsx
new file mode 100644
index 0000000..1534c57
--- /dev/null
+++ b/app/matching-result/_components/ResultFooter.tsx
@@ -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(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("한 번 더 뽑기 로직 실행!"); // 로직 실행 위치
+ 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 (
+
+ {/* Top Row: Retry & Mail Buttons */}
+
+ {/* Retry Button */}
+
+
+ {/* Mail Button */}
+
+
+
+ {/* Bottom Row: One More Button with Long Press */}
+
+
+ );
+};
+
+export default ResultFooter;
diff --git a/app/matching-result/_components/ScreenMatchingResult.tsx b/app/matching-result/_components/ScreenMatchingResult.tsx
new file mode 100644
index 0000000..7424437
--- /dev/null
+++ b/app/matching-result/_components/ScreenMatchingResult.tsx
@@ -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);
+
+ return () => clearTimeout(timer);
+ }, []);
+
+ return (
+
+
+
+ 매칭 결과
+
+ }
+ />
+ {isWaiting ? (
+
+ ) : (
+ <>
+
+
+ >
+ )}
+
+ );
+};
+
+export default ScreenMatchingResult;
diff --git a/app/matching-result/_components/WaitingFrame.tsx b/app/matching-result/_components/WaitingFrame.tsx
new file mode 100644
index 0000000..2d707fb
--- /dev/null
+++ b/app/matching-result/_components/WaitingFrame.tsx
@@ -0,0 +1,26 @@
+import React from "react";
+
+const WaitingFrame = () => {
+ return (
+
+
+
+ 코매칭 AI가 입력하신 결과를 바탕으로
+
+ 비슷한 매칭 상대를 찾고 있어요..
+
+
+
+ );
+};
+
+export default WaitingFrame;
diff --git a/app/matching-result/page.tsx b/app/matching-result/page.tsx
new file mode 100644
index 0000000..4099a8b
--- /dev/null
+++ b/app/matching-result/page.tsx
@@ -0,0 +1,5 @@
+import ScreenMatchingResult from "./_components/ScreenMatchingResult";
+
+export default function MatchingResultPage() {
+ return ;
+}
diff --git a/components/ui/BackButton.tsx b/components/ui/BackButton.tsx
index 3ff5360..eabee02 100644
--- a/components/ui/BackButton.tsx
+++ b/components/ui/BackButton.tsx
@@ -7,7 +7,7 @@ import { cn } from "@/lib/utils";
type BackButtonProps = {
className?: string;
onClick?: () => void;
- text?: string;
+ text?: React.ReactNode;
};
export const BackButton = ({
@@ -33,7 +33,7 @@ export const BackButton = ({
>
- {text}
+ {text}
);
}