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
5 changes: 3 additions & 2 deletions app/extra-info/detail/_components/ScreenExtraInfoDetail.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,14 @@ import FormInput from "@/components/ui/FormInput";
import ProgressStepBar from "@/components/ui/ProgressStepBar";
import AdvantageDrawer from "./AdvantageDrawer";
import { cn, removeEmoji } from "@/lib/utils";
import { useProfile } from "@/providers/profile-provider";
import { useProfileStore } from "@/stores/profile-store";
import { SocialType } from "@/lib/types/profile";
import { useRouter } from "next/navigation";

const ScreenExtraInfoDetail = () => {
const router = useRouter();
const { profile, updateProfile } = useProfile();
const profile = useProfileStore((state) => state.profile);
const updateProfile = useProfileStore((state) => state.updateProfile);

const [contactType, setContactType] = useState<"instagram" | "kakao" | null>(
(profile.socialType?.toLowerCase() as "instagram" | "kakao") || null,
Expand Down
10 changes: 10 additions & 0 deletions app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,16 @@
);
}

.bg-pink-gradient {
background:
linear-gradient(
288.98deg,
rgba(255, 98, 95, 0.051694) 19.94%,
rgba(232, 58, 188, 0.15) 105.37%
),
rgba(255, 255, 255, 0.2);
}

/* 스크롤바 숨기기 유틸리티 */
.scrollbar-hide {
-ms-overflow-style: none; /* IE and Edge */
Expand Down
5 changes: 3 additions & 2 deletions app/hobby-select/_components/ScreenHobbySelect.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import React, { useState, useMemo } from "react";
import HobbyButton from "./HobbyButton";
import { useRouter } from "next/navigation";
import Button from "@/components/ui/Button";
import { useProfile } from "@/providers/profile-provider";
import { useProfileStore } from "@/stores/profile-store";
import ProgressStepBar from "@/components/ui/ProgressStepBar";

import { HOBBIES, type HobbyCategory } from "@/lib/constants/hobbies";
Expand All @@ -17,7 +17,8 @@ const ALL_HOBBIES = Object.values(HOBBIES).flat() as string[];

const ScreenHobbySelect = () => {
const router = useRouter();
const { profile, updateProfile } = useProfile();
const profile = useProfileStore((state) => state.profile);
const updateProfile = useProfileStore((state) => state.updateProfile);

// 취미 이름으로 카테고리를 찾는 헬퍼 함수
const findCategoryByHobbyName = (name: string): HobbyCategory => {
Expand Down
13 changes: 5 additions & 8 deletions app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import localFont from "next/font/local";
import "./globals.css";
import Blur from "@/components/common/Blur";
import { QueryProvider } from "@/providers/query-provider";
import { ProfileProvider } from "@/providers/profile-provider";
// import { ServiceStatusProvider } from "@/providers/service-status-provider";
// import { getInitialMaintenanceStatus } from "@/lib/status";
import FcmInitializer from "@/components/common/FcmInitializer";
Expand Down Expand Up @@ -68,13 +67,11 @@ export default async function RootLayout({
{/* <ServiceStatusProvider
initialMaintenanceMode={initialMaintenanceMode}
> */}
<ProfileProvider>
<div className="bg-background-app-base relative min-h-dvh w-full overflow-x-hidden text-black md:max-w-[430px] md:shadow-lg">
<Blur />
<FcmInitializer />
{children}
</div>
</ProfileProvider>
<div className="bg-background-app-base relative min-h-dvh w-full overflow-x-hidden text-black md:max-w-[430px] md:shadow-lg">
<Blur />
<FcmInitializer />
{children}
</div>
{/* </ServiceStatusProvider> */}
</QueryProvider>
</body>
Expand Down
131 changes: 48 additions & 83 deletions app/profile-builder/_components/ScreenProfileBuilder.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
"use client";

import React, { useState, useEffect } from "react";
import React, { useState, useEffect, useRef } from "react";
import { useRouter } from "next/navigation";
import Button from "@/components/ui/Button";
import { useProfile } from "@/providers/profile-provider";
import { useProfileStore } from "@/stores/profile-store";
import { majorCategories, universities } from "@/lib/constants/majors";
import {
getDepartmentOptions,
Expand Down Expand Up @@ -57,12 +57,25 @@ const mbtiSet = new Set<MBTI>([
const isValidMBTI = (mbti?: string): mbti is MBTI =>
mbtiSet.has((mbti || "").toUpperCase() as MBTI);

const PROFILE_STORAGE_KEY = "onboarding-profile-data";
const LEGACY_PROFILE_STORAGE_KEY = "profileBuilder";
const mapProfileToInitialValues = (profile: Partial<ProfileData>) => ({
birthYear: profile.birthDate ? profile.birthDate.split("-")[0] : "",
university: profile.university || "",
department: profile.department || "",
major: profile.major || "",
gender:
Object.keys(genderMap).find((k) => genderMap[k] === profile.gender) || "",
mbti: profile.mbti || "",
frequency:
Object.keys(contactFrequencyMap).find(
(k) => contactFrequencyMap[k] === profile.contactFrequency,
) || "",
});

export const ScreenProfileBuilder = () => {
const router = useRouter();
const { profile, updateProfile, isReady } = useProfile();
const profile = useProfileStore((state) => state.profile);
const updateProfile = useProfileStore((state) => state.updateProfile);
const isReady = useProfileStore((state) => state.isReady);

const [currentStep, setCurrentStep] = useState(1);
const [selectedBirthYear, setSelectedBirthYear] = useState("");
Expand All @@ -72,72 +85,12 @@ export const ScreenProfileBuilder = () => {
const [selectedGender, setSelectedGender] = useState("");
const [selectedMBTI, setSelectedMBTI] = useState("");
const [selectedFrequency, setSelectedFrequency] = useState("");
const [hasSelectedGender, setHasSelectedGender] = useState(false);
const [hasSelectedMBTI, setHasSelectedMBTI] = useState(false);
const [hasSelectedFrequency, setHasSelectedFrequency] = useState(false);

const getInitialValues = () => {
try {
const savedProfile = localStorage.getItem(PROFILE_STORAGE_KEY);
if (savedProfile) {
const parsed = JSON.parse(savedProfile) as Partial<ProfileData>;
return {
birthYear: parsed.birthDate ? parsed.birthDate.split("-")[0] : "",
university: parsed.university || "",
department: parsed.department || "",
major: parsed.major || "",
gender:
Object.keys(genderMap).find(
(k) => genderMap[k] === parsed.gender,
) || "",
mbti: parsed.mbti || "",
frequency:
Object.keys(contactFrequencyMap).find(
(k) => contactFrequencyMap[k] === parsed.contactFrequency,
) || "",
};
}

const legacySaved = localStorage.getItem(LEGACY_PROFILE_STORAGE_KEY);
if (legacySaved) return JSON.parse(legacySaved);
} catch {
// ignore
}

if (profile && Object.keys(profile).length > 0) {
return {
birthYear: profile.birthDate ? profile.birthDate.split("-")[0] : "",
university: profile.university || "",
department: profile.department || "",
major: profile.major || "",
gender:
Object.keys(genderMap).find((k) => genderMap[k] === profile.gender) ||
"",
mbti: profile.mbti || "",
frequency:
Object.keys(contactFrequencyMap).find(
(k) => contactFrequencyMap[k] === profile.contactFrequency,
) || "",
};
}

return {};
};
const hasInitialized = useRef(false);

useEffect(() => {
if (!isReady) return;

const initialValues = getInitialValues();
const allFilled = Boolean(
initialValues.birthYear &&
initialValues.university &&
initialValues.department &&
initialValues.major &&
initialValues.gender &&
initialValues.mbti &&
initialValues.frequency,
);
if (!isReady || hasInitialized.current) return;

const initialValues = mapProfileToInitialValues(profile);
const timeoutId = setTimeout(() => {
if (initialValues.birthYear)
setSelectedBirthYear(initialValues.birthYear);
Expand All @@ -148,22 +101,32 @@ export const ScreenProfileBuilder = () => {
if (initialValues.major) setSelectedMajor(initialValues.major);
if (initialValues.gender) {
setSelectedGender(initialValues.gender);
setHasSelectedGender(true);
}
if (initialValues.mbti) {
setSelectedMBTI(initialValues.mbti);
setHasSelectedMBTI(true);
}
if (initialValues.frequency) {
setSelectedFrequency(initialValues.frequency);
setHasSelectedFrequency(true);
}

if (allFilled) setCurrentStep(4);
// 모든 정보가 이미 있다면 Step 4까지 모두 펼쳐줌
if (
initialValues.birthYear &&
initialValues.university &&
initialValues.department &&
initialValues.major &&
initialValues.gender &&
initialValues.mbti &&
initialValues.frequency
) {
setCurrentStep(4);
}

hasInitialized.current = true;
}, 0);

return () => clearTimeout(timeoutId);
}, [isReady]);
}, [isReady, profile]);

const yearOptions = getYearOptions();
const universityOptions = getUniversityOptions(universities);
Expand Down Expand Up @@ -202,17 +165,14 @@ export const ScreenProfileBuilder = () => {

const handleGenderSelect = (value: string) => {
setSelectedGender(value);
setHasSelectedGender(true);
};

const handleMBTISelect = (value: string) => {
setSelectedMBTI(value);
setHasSelectedMBTI(true);
};

const handleFrequencySelect = (value: string) => {
setSelectedFrequency(value);
setHasSelectedFrequency(true);
};

const isStepValid = (() => {
Expand All @@ -225,11 +185,11 @@ export const ScreenProfileBuilder = () => {
selectedMajor
);
case 2:
return !!selectedGender && hasSelectedGender;
return !!selectedGender;
case 3:
return isValidMBTI(selectedMBTI) && hasSelectedMBTI;
return isValidMBTI(selectedMBTI);
case 4:
return !!selectedFrequency && hasSelectedFrequency;
return !!selectedFrequency;
default:
return false;
}
Expand Down Expand Up @@ -280,13 +240,19 @@ export const ScreenProfileBuilder = () => {
selectedUniversity={selectedUniversity}
selectedDepartment={selectedDepartment}
selectedMajor={selectedMajor}
onBirthYearChange={setSelectedBirthYear}
onUniversityChange={setSelectedUniversity}
onBirthYearChange={(value) => {
setSelectedBirthYear(value);
}}
onUniversityChange={(value) => {
setSelectedUniversity(value);
}}
onDepartmentChange={(value) => {
setSelectedDepartment(value);
setSelectedMajor("");
}}
onMajorChange={setSelectedMajor}
onMajorChange={(value) => {
setSelectedMajor(value);
}}
/>
</div>

Expand All @@ -298,7 +264,6 @@ export const ScreenProfileBuilder = () => {
safeArea
disabled={!isStepValid}
onClick={currentStep === 4 ? handleComplete : handleNext}
className="bg-button-primary text-button-primary-text-default"
>
{currentStep === 4 ? "완료" : "다음으로"}
</Button>
Expand Down
5 changes: 2 additions & 3 deletions app/profile-builder/_components/Step3MBTI.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,8 @@ export default function Step3MBTI({
(category === "tf" ? value : tf) +
(category === "jp" ? value : jp);

if (newMBTI.length === 4) {
onMBTISelect(newMBTI);
}
// Always sync partial/complete selection so parent step validity updates immediately.
onMBTISelect(newMBTI);
};

return (
Expand Down
64 changes: 64 additions & 0 deletions app/profile-image/_components/AgreeBottomSheet.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
"use client";
import React from "react";
import {
Drawer,
DrawerContent,
DrawerDescription,
DrawerTitle,
} from "@/components/ui/drawer";
import { X } from "lucide-react";

interface AgreeBottomSheetProps {
isOpen: boolean;
onClose: () => void;
title: React.ReactNode;
description?: React.ReactNode;
children: React.ReactNode;
footer?: React.ReactNode;
}

const AgreeBottomSheet = ({
isOpen,
onClose,
title,
description,
children,
footer,
}: AgreeBottomSheetProps) => {
return (
<Drawer open={isOpen} onOpenChange={(open) => !open && onClose()}>
<DrawerContent
showHandle={false}
className="z-[100] mx-auto max-h-none w-full rounded-t-[24px] border-0 bg-white px-4 pt-6 pb-8 md:max-w-[430px]"
>
<div className="mb-6 flex flex-row items-start justify-between">
<div className="flex flex-col gap-1 text-left">
<DrawerTitle className="typo-20-700 text-black">
{title}
</DrawerTitle>
{description && (
<DrawerDescription className="typo-14-500 !leading-[1.4] text-[#858585]">
{description}
</DrawerDescription>
)}
</div>
<button
onClick={onClose}
className="shrink-0 rounded-full p-1 transition-colors hover:bg-gray-100"
aria-label="닫기"
>
<X className="h-5 w-5 text-gray-400" />
</button>
</div>

<div className="scrollbar-hide max-h-[60vh] overflow-y-auto">
{children}
</div>

{footer && <div className="mt-6 w-full">{footer}</div>}
</DrawerContent>
</Drawer>
);
};

export default AgreeBottomSheet;
Loading
Loading