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
23 changes: 16 additions & 7 deletions app/firebase-messaging-sw.js/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,22 @@ const messaging = firebase.messaging();
messaging.onBackgroundMessage((payload) => {
console.log("[Service Worker] 백그라운드 메시지 수신:", payload);

const notificationTitle = payload.notification?.title || "새 알림";
const notificationOptions = {
body: payload.notification?.body || "",
icon: payload.notification?.icon || "/logo/logo.svg",
};

self.registration.showNotification(notificationTitle, notificationOptions);
// 1. 만약 payload.notification 필드가 존재한다면, FCM SDK가 백그라운드에서 자동으로 알림을 보여줍니다.
// 이 상황에서 수동으로 showNotification을 또 부르면 알림이 2개 뜨게 되므로 수동 팝업은 패스합니다!
if (payload.notification) {
console.log("[Service Worker] notification 필드 존재로 인한 자동 알림 완료. 수동 노출 생략.");
return;
}

// 2. 오직 payload.notification이 없고 payload.data만 있는 'Data-only Message' 형태일 때만 수동으로 띄웁니다.
if (payload.data) {
const notificationTitle = payload.data.title || "새 알림";
const notificationOptions = {
body: payload.data.body || payload.data.message || "",
icon: payload.data.icon || "/logo/logo.svg",
};
self.registration.showNotification(notificationTitle, notificationOptions);
}
});
`;

Expand Down
2 changes: 2 additions & 0 deletions app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { QueryProvider } from "@/providers/query-provider";
// import { getInitialMaintenanceStatus } from "@/lib/status";
import FcmInitializer from "@/components/common/FcmInitializer";
import ChatSocketInitializer from "@/components/common/ChatSocketInitializer";
import ToastContainer from "@/components/common/ToastContainer";

const pretendard = localFont({
src: "./fonts/PretendardVariable.woff2",
Expand Down Expand Up @@ -72,6 +73,7 @@ export default async function RootLayout({
<Blur />
<FcmInitializer />
<ChatSocketInitializer />
<ToastContainer />
{children}
</div>
{/* </ServiceStatusProvider> */}
Expand Down
7 changes: 5 additions & 2 deletions app/login/_components/LoginForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,12 @@ export const LoginForm = () => {
</Button>
</form>
<div className="typo-14-500 text-color-text-caption2 flex w-full justify-center">
<Link href="/find-email" className="cursor-pointer">
<span
onClick={() => alert("미지원 서비스입니다.")}
className="cursor-pointer"
>
이메일 찾기
</Link>
</span>
Comment thread
dasosann marked this conversation as resolved.
<span className="mx-4">|</span>
<Link href="/reset" className="cursor-pointer">
비밀번호 변경
Expand Down
4 changes: 3 additions & 1 deletion app/matching-list/_components/YesMatchingList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,13 +44,15 @@ const YesMatchingList = ({
// 검색 필터
if (searchQuery.trim()) {
const query = searchQuery.trim().toLowerCase();
const cleanQuery = query.replace("살", "").trim();
result = result.filter((item) => {
const p = item.partner;
const rawAge = p.age || (p.birthDate ? getAge(p.birthDate) : null);
return (
p.nickname.toLowerCase().includes(query) ||
p.mbti.toLowerCase().includes(query) ||
p.major.toLowerCase().includes(query) ||
(p.birthDate && String(getAge(p.birthDate)).includes(query))
(rawAge !== null && String(rawAge).includes(cleanQuery))
);
});
}
Expand Down
2 changes: 1 addition & 1 deletion app/matching-result/_components/ScreenMatchingResult.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ const ScreenMatchingResult = () => {
return (
<main className="relative flex min-h-screen flex-col items-center px-4 py-2 pb-10">
<BackButton
onClick={() => router.push("/main")}
onClick={() => router.push("/matching")}
text={
<div className="flex items-center gap-1.5">
<Image
Expand Down
27 changes: 25 additions & 2 deletions app/matching/_components/ImportantOptionDrawer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,25 @@ function TypingText({
return <>{displayedText}</>;
}

const formatMBTISelection = (mbti: string): string => {
const mbtiMap: Record<string, string> = {
E: "외향형",
I: "내향형",
S: "감각형",
N: "직관형",
T: "사고형",
F: "감정형",
J: "판단형",
P: "인식형",
};
const chars = mbti.split("").filter(Boolean);
const labels = chars.map((c) => mbtiMap[c] || c);
if (labels.length === 2) {
return `${labels[0]}, 그리고 ${labels[1]}`;
}
return labels.join(", ");
};
Comment thread
dasosann marked this conversation as resolved.

export default function ImportantOptionDrawer({
trigger,
onSelect,
Expand Down Expand Up @@ -224,7 +243,7 @@ export default function ImportantOptionDrawer({
</span>
</div>
) : (
<div className="relative flex w-full flex-col items-end gap-1 pt-3">
<div className="relative mx-auto flex w-[calc(100%-12px)] flex-col items-end gap-1 pt-3">
{typingStep >= 1 && (
<div className="bg-button-primary flex items-center justify-center rounded-t-[16px] rounded-br-[8px] rounded-bl-[16px] px-3 py-[12px] shadow-[0px_4px_16px_rgba(0,0,0,0.12)]">
<span className="typo-14-500 text-right text-white">
Expand All @@ -241,7 +260,11 @@ export default function ImportantOptionDrawer({
<span className="typo-18-600 w-full text-right text-white">
<TypingText
key={selectedOption}
text={`${selectedItem?.label}는 ${selections[selectedOption]} 이면 좋겠어!`}
text={
selectedOption === "MBTI"
? `MBTI는 ${formatMBTISelection(selections.MBTI)} 이면 좋겠어!`
: `${selectedItem?.label}는 ${selections[selectedOption]} 이면 좋겠어!`
}
/>
</span>
</div>
Expand Down
22 changes: 16 additions & 6 deletions app/matching/_components/MatchingAgeOption.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -61,9 +61,14 @@ export default function MatchingAgeOption({
onConfirm={handleConfirm}
trigger={
<button className="flex flex-col gap-1 text-left outline-none">
<h2 className="typo-20-700 text-color-text-black">
나이 구간 선택하기
</h2>
<div className="flex items-end gap-1">
<h2 className="typo-20-700 text-color-text-black">
나이 구간 선택하기
</h2>
<span className="typo-10-600 text-color-gray-400 mb-[3px] leading-[12px]">
선택
</span>
</div>
<p className="typo-14-500 text-color-text-caption3">
선택됨: {displayText}
</p>
Expand Down Expand Up @@ -113,9 +118,14 @@ export default function MatchingAgeOption({
trigger={
<button className="border-color-gray-100 flex w-full items-center justify-between border-b pb-5 text-left outline-none">
<div className="flex flex-col gap-1">
<h2 className="typo-20-700 text-color-text-black">
나이 구간 선택하기
</h2>
<div className="flex items-end gap-1">
<h2 className="typo-20-700 text-color-text-black">
나이 구간 선택하기
</h2>
<span className="typo-10-600 text-color-gray-400 mb-[3px] leading-[12px]">
선택
</span>
</div>
<p className="typo-14-500 text-color-text-caption3">
원하는 나이 범위를 설정해 보세요!
</p>
Expand Down
16 changes: 9 additions & 7 deletions app/matching/_components/ScreenMatching.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import { BackButton } from "@/components/ui/BackButton";
import Image from "next/image";
import React, { useState } from "react";
import { useRouter } from "next/navigation";
import MatchingAgeOption from "./MatchingAgeOption";
import MatchingAgeSection from "./MatchingAgeSection";
import MatchingHobbySection from "./MatchingHobbySection";
Expand All @@ -27,7 +28,7 @@
스포츠: "SPORTS",
문화: "CULTURE",
음악: "MUSIC",
여행: "TRAVEL",
여행: "LEISURE",
자기계발: "DAILY",
게임: "GAME",
};
Expand All @@ -43,6 +44,7 @@
import { useMyProfile } from "@/hooks/useProfile";

const ScreenMatching = () => {
const router = useRouter();
const { data: itemData, isLoading: isItemsLoading } = useItems();
const { data: myProfile } = useMyProfile();
const { mutate: match, isPending } = useMatching();
Expand All @@ -60,7 +62,7 @@
const [resetKey, setResetKey] = useState(0);

// 현재 사용자 나이 계산 (한국식 나이: 현재연도 - 태어난연도 + 1)
const userAge = React.useMemo(() => {

Check warning on line 65 in app/matching/_components/ScreenMatching.tsx

View workflow job for this annotation

GitHub Actions / lint

'userAge' is assigned a value but never used
if (!myProfile?.data.birthDate) return 0;
try {
const birthYear = new Date(myProfile.data.birthDate).getFullYear();
Expand Down Expand Up @@ -162,17 +164,17 @@
if (isAgeRangeActive) {
finalMinAge = minAge ?? null;
finalMaxAge = maxAge ?? null;
} else if (ageInfo.min !== null && ageInfo.max !== null) {
finalMinAge = userAge + ageInfo.min;
finalMaxAge = userAge + ageInfo.max;
} else {
// 연상/연하/동갑 선택 시에는 직접 지정하는 나이 구간(min/max)을 null로 전달하여
// 백엔드가 ageOption("OLDER"/"YOUNGER"/"EQUAL")만 보고 올바르게 연산하도록 유도합니다.
finalMinAge = null;
finalMaxAge = null;
}

const payload: MatchingRequest = {
// 이제 오프셋이 아닌 실제 나이를 보냅니다. (필드명은 규격상 Offset 유지)
ageOption: isAgeRangeActive ? null : ageInfo.option || null,
minAgeOffset: finalMinAge,
maxAgeOffset: finalMaxAge,

mbtiOption: selectedMBTI || undefined,
hobbyOption: selectedHobbyCategory
? hobbyMapping[selectedHobbyCategory]
Expand All @@ -193,7 +195,7 @@

return (
<main className="relative flex min-h-screen flex-col items-center px-4 py-2 pb-30">
<BackButton text="매칭하기" />
<BackButton text="매칭하기" onClick={() => router.push("/main")} />
<MyCoinSection className="my-6" />

<div className="flex w-full flex-col gap-4">
Expand Down
45 changes: 31 additions & 14 deletions app/mypage/_components/ScreenMyPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,12 @@
import Button from "@/components/ui/Button";
import ProfileButton from "@/app/profile-builder/_components/ProfileButton";
import ProfileImageSelection from "@/app/profile-image/_components/ProfileImageSelection";
import { ChevronRight } from "lucide-react";

Check warning on line 9 in app/mypage/_components/ScreenMyPage.tsx

View workflow job for this annotation

GitHub Actions / lint

'ChevronRight' is defined but never used
import { cn } from "@/lib/utils";
import { cn, removeEmoji } from "@/lib/utils";

Check warning on line 10 in app/mypage/_components/ScreenMyPage.tsx

View workflow job for this annotation

GitHub Actions / lint

'cn' is defined but never used
import {
getDefaultProfilesByGender,
DEFAULT_PROFILE_ASSETS,
getAutoSwitchProfileIdByGender,

Check warning on line 14 in app/mypage/_components/ScreenMyPage.tsx

View workflow job for this annotation

GitHub Actions / lint

'getAutoSwitchProfileIdByGender' is defined but never used
} from "@/lib/constants/defaultProfiles";
import type {
Gender,
Expand Down Expand Up @@ -452,7 +453,26 @@
socialAccountId: formattedSocialId || null,
major: major.trim() || undefined,
birthDate: editableBirthYear ? `${editableBirthYear}-01-01` : undefined,
hobbies: hobbies,
hobbies: hobbies.map((h) => {
const categoryMap: Record<string, string> = {
스포츠: "SPORTS",
문화: "CULTURE",
음악: "MUSIC",
여행: "LEISURE",
자기계발: "DAILY",
게임: "GAME",
SPORTS: "SPORTS",
CULTURE: "CULTURE",
MUSIC: "MUSIC",
LEISURE: "LEISURE",
DAILY: "DAILY",
GAME: "GAME",
};
return {
category: categoryMap[h.category] || "DAILY",
name: removeEmoji(h.name),
};
}),
Comment thread
dasosann marked this conversation as resolved.
tags: tags.filter(Boolean).map((t) => ({ tag: t })),
profileImageKey: finalImageUrl || "default",
isMatchable: true,
Expand All @@ -472,7 +492,6 @@
}
};

/* ───── 프로필 이미지 핸들러 ───── */
const handleSelectProfileType = (type: "default" | "custom") => {
setSelectedType(type);
if (type === "default") {
Expand All @@ -488,9 +507,13 @@
const currentHobbies = JSON.stringify(hobbies);
const currentSocialId =
socialType === "INSTAGRAM"
? socialAccountId
? socialAccountId.trim()
? socialAccountId.trim().startsWith("@")
? socialAccountId.trim()
: `@${socialAccountId.trim()}`
: ""
: socialType === "KAKAO"
? kakaoId
? kakaoId.trim()
: "";

return checkProfileChanged({
Expand Down Expand Up @@ -556,7 +579,7 @@
<ProfileImageSelection
selected={selectedType}
onSelect={handleSelectProfileType}
gender={profile?.gender}
gender={genderMap[gender]}
selectedProfile={profileImageUrl}
onProfileSelect={(id) => {
setProfileImageUrl(id);
Expand Down Expand Up @@ -735,16 +758,10 @@
<div className="box-border border-b border-[#E5E5E5] py-4">
<label className="typo-16-600 mb-2 block text-black">성별</label>
<div className="flex gap-1.5">
<ProfileButton
selected={gender === "여자"}
onClick={() => setGender("여자")}
>
<ProfileButton selected={gender === "여자"} disabled={true}>
여자
</ProfileButton>
<ProfileButton
selected={gender === "남자"}
onClick={() => setGender("남자")}
>
<ProfileButton selected={gender === "남자"} disabled={true}>
남자
</ProfileButton>
</div>
Expand Down
4 changes: 4 additions & 0 deletions app/profile-builder/_components/ProfileButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,14 @@ interface ProfileButtonProps {
children: React.ReactNode;
selected?: boolean;
onClick?: () => void;
disabled?: boolean;
}

export default function ProfileButton({
children,
selected = false,
onClick,
disabled = false,
}: ProfileButtonProps) {
return (
<button
Expand All @@ -22,8 +24,10 @@ export default function ProfileButton({
selected
? "bg-pink-gradient border-color-pink-700 text-color-pink-700 border"
: "bg-color-gray-0-a30 text-color-gray-300",
disabled && "pointer-events-none cursor-not-allowed opacity-50",
)}
onClick={onClick}
disabled={disabled}
>
{children}
</button>
Expand Down
6 changes: 5 additions & 1 deletion app/profile-image/_components/TermsDrawer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -148,8 +148,12 @@ const TermsDrawer = ({ children }: TermsDrawerProps) => {

if (result.success) {
setIsOpen(false);
clearProfile();
// 페이지 이동이 완료되기 전에 프로필 데이터가 비워져서 '여성 강아지(female_dog)' 기본 이미지로 화면이 깜빡이는 현상을 방지하기 위해,
// 먼저 이동하고 약간의 딜레이(500ms) 후에 프로필 저장소를 클리어합니다.
router.push("/main");
setTimeout(() => {
clearProfile();
}, 500);
} else {
alert(result.message);
}
Expand Down
3 changes: 2 additions & 1 deletion app/register/_components/ScreenRegister.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,8 @@ export const ScreenRegister = () => {
{
onSuccess: (data) => {
if (data.status === 200) {
// TODO: 회원가입 완료 후 이동 (예: router.push("/login"))
alert("가입이 완료되었습니다.");
router.push("/login");
} else {
alert("회원가입에 실패했습니다. 다시 시도해주세요.");
}
Expand Down
2 changes: 1 addition & 1 deletion app/register/_components/VerificationStep.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ export const VerificationStep = ({
</div>

<div className="mt-auto flex w-full flex-col gap-4">
<div className="bg-color-gray-50-a80 w-full rounded-[16px] border-[#b3b3b34d] p-4 leading-0">
<div className="bg-color-gray-50-a80 w-full rounded-[16px] border-[#b3b3b34d] p-4 leading-normal">
<span className="typo-12-500 text-color-text-caption3">
인증번호가 오지 않았나요?
<br /> 이전 단계에서 이메일을 재확인하거나, 스팸함을 확인해
Expand Down
4 changes: 4 additions & 0 deletions app/reset/password/new/_components/ScreenNewPasswordPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,10 @@ const ScreenNewPasswordPage = () => {
},

onError: (error) => {
// '존재하지 않는 유저' 에러가 이미 표시된 상태라면, 중복 클릭 시 다른 에러(인증번호 오류 등)로 덮어쓰지 않고 유지합니다.
if (errorMessage.includes("존재하지 않는")) {
return;
}
setErrorMessage(
error.message ||
"비밀번호 변경에 실패했습니다. 다시 시도해 주세요.",
Expand Down
Loading
Loading