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
2 changes: 2 additions & 0 deletions .env
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,5 @@ NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID=your-measurement-id
NEXT_PUBLIC_FIREBASE_VAPID_KEY=your-vapid-key

# 로컬 개발 환경 설정은 .env.local 파일에 작성하세요


19 changes: 19 additions & 0 deletions app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -124,3 +124,22 @@
@apply bg-background text-foreground;
}
}

/* ============================
Shared bg-pink-conic utility
============================ */

.bg-pink-conic {
background:
conic-gradient(
from -36.07deg at 108.5px -49.1px,
#e83abc -66.6deg,
rgba(255, 119, 94, 0.1) 0.04deg,
rgba(255, 77, 97, 0.6) 169.2deg,
#e83abc 192.6deg,
rgba(255, 98, 95, 0.344627) 208.31deg,
#e83abc 293.4deg,
rgba(255, 119, 94, 0.1) 360.04deg
),
rgba(255, 255, 255, 0.2);
}
35 changes: 35 additions & 0 deletions app/hobby-select/_components/HobbyButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import React from "react";
import { Plus } from "lucide-react";
import { cn } from "@/lib/utils";

interface HobbyButtonProps {
children: React.ReactNode;
onClick?: () => void;
selected?: boolean;
plus?: boolean;
}

const HobbyButton = ({
children,
onClick,
selected,
plus,
}: HobbyButtonProps) => {
return (
<button
type="button"
className={cn(
"typo-14-500 flex items-center gap-1 rounded-full border px-3 py-[7.5px] whitespace-nowrap text-black transition-all duration-200 ease-in-out",
selected && !plus
? "border-[#FF4D61] bg-[#FFEBED]"
: "border-[#DFDFDF] bg-[#B3B3B3]/15",
)}
onClick={onClick}
>
{plus && <Plus size={13} color="#000" strokeWidth={2} />}
{children}
</button>
);
};

export default HobbyButton;
37 changes: 37 additions & 0 deletions app/hobby-select/_components/HobbySearchInput.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import React, { FormEvent, useRef } from "react";
import { Search } from "lucide-react";

interface HobbySearchInputProps {
onSearch: (keyword: string) => void;
}

const HobbySearchInput = ({ onSearch }: HobbySearchInputProps) => {
const inputRef = useRef<HTMLInputElement>(null);

const handleSubmit = (e: FormEvent) => {
e.preventDefault();
if (inputRef.current) {
onSearch(inputRef.current.value);
inputRef.current.blur();
}
};

return (
<form
onSubmit={handleSubmit}
className="flex h-9 w-full items-center rounded-[12px] border border-[#C2C2C2] bg-[#B3B3B31A]"
>
<input
ref={inputRef}
type="text"
placeholder="관심사 혹은 키워드를 입력하세요."
className="placeholder:typo-12-500 w-full bg-transparent py-2 pl-4 placeholder:text-gray-400 focus:outline-none"
/>
<button type="submit" className="mr-[17.25px]">
<Search size={16} color="#b3b3b3" />
</button>
</form>
);
};

export default HobbySearchInput;
143 changes: 143 additions & 0 deletions app/hobby-select/_components/ScreenHobbySelect.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
"use client";

import React, { useState, useMemo } from "react";
import { Plus } from "lucide-react";

Check warning on line 4 in app/hobby-select/_components/ScreenHobbySelect.tsx

View workflow job for this annotation

GitHub Actions / lint

'Plus' is defined but never used
import HobbyButton from "./HobbyButton";
import { useRouter } from "next/navigation";
import { BackButton } from "@/components/ui/BackButton";

Check warning on line 7 in app/hobby-select/_components/ScreenHobbySelect.tsx

View workflow job for this annotation

GitHub Actions / lint

'BackButton' is defined but never used
import Button from "@/components/ui/Button";
import { useProfile } from "@/providers/profile-provider";
import ProgressStepBar from "@/components/ui/ProgressStepBar";
import Blur from "@/components/common/Blur";

Check warning on line 11 in app/hobby-select/_components/ScreenHobbySelect.tsx

View workflow job for this annotation

GitHub Actions / lint

'Blur' is defined but never used

import { HOBBIES, type HobbyCategory } from "@/lib/constants/hobbies";
import HobbySearchInput from "./HobbySearchInput";
import { createChoseongRegex } from "@/lib/utils/hangul";

const ALL_HOBBIES = Object.values(HOBBIES).flat();

const ScreenHobbySelect = () => {
const router = useRouter();
const { profile, updateProfile } = useProfile();
const [selected, setSelected] = useState<string[]>(profile.hobbies || []);
const [searchKeyword, setSearchKeyword] = useState("");

const toggleHobby = (hobby: string) => {
// alert 중복 방지: selected.length 기준으로 체크
const isAlreadySelected = selected.includes(hobby);
if (!isAlreadySelected && selected.length >= 10) {
console.log("ALERT: 10개 초과 시도", selected);
alert("최대 10개까지 선택할 수 있어요.");
return;
}
setSelected((prev) => {
const isAlreadySelectedInner = prev.includes(hobby);
if (isAlreadySelectedInner) {
// 선택 해제는 무조건 허용
return prev.filter((h) => h !== hobby);
}
return [...prev, hobby];
});
};

const handleComplete = () => {
// Context 업데이트
updateProfile({
...profile,
hobbies: selected,
});

// 다음 페이지로 이동 (회원가입/추가 정보 입력 등)
router.push("/extra-info");
};

const filteredHobbies = useMemo(() => {
if (!searchKeyword.trim()) return null; // No search term

// Normalize and create regex for chosung matching
const searchRegex = createChoseongRegex(searchKeyword.trim());
return ALL_HOBBIES.filter((hobby) => searchRegex.test(hobby));
}, [searchKeyword]);

return (
<main className="relative flex min-h-svh flex-col overflow-x-hidden px-4 pb-[120px]">
<ProgressStepBar currentStep={2} totalSteps={3} />
<div className="my-6 flex flex-col gap-2 text-center">
<h1 className="typo-20-700 text-[#373737]">관심사를 알려주세요.</h1>
<p className="typo-14-500 leading-[1.6] text-[#858585]">
요즘 관심있는 것들을 3개 이상 선택해주세요.
<br />
최대 10개까지 선택할 수 있어요.
</p>
</div>
<HobbySearchInput onSearch={setSearchKeyword} />

<div className="mt-6 flex flex-col gap-8">
{filteredHobbies ? (
<div className="flex flex-col">
<h2 className="typo-16-600 mb-3 text-black">검색 결과</h2>
<div className="flex flex-wrap gap-3">
{filteredHobbies.length > 0 ? (
filteredHobbies.map((hobby) => {
const isSelected = selected.includes(hobby);
return (
<HobbyButton
key={hobby}
onClick={() => toggleHobby(hobby)}
selected={isSelected}
>
{hobby}
</HobbyButton>
);
})
) : (
<p className="typo-14-500 text-[#858585]">
검색된 관심사가 없습니다.
</p>
)}
</div>
</div>
) : (
(Object.keys(HOBBIES) as HobbyCategory[]).map((category) => (
<div key={category} className="flex flex-col">
<h2 className="typo-16-600 mb-3 text-black">{category}</h2>
<div className="flex flex-wrap gap-3">
{HOBBIES[category].map((hobby) => {
const isSelected = selected.includes(hobby);
return (
<HobbyButton
key={hobby}
onClick={() => toggleHobby(hobby)}
selected={isSelected}
>
{hobby}
</HobbyButton>
);
})}
</div>
</div>
))
)}
</div>
<div className="typo-14-600 mt-8 flex flex-col items-start gap-3">
<h2>내가 좋아하는 관심사가 없나요?</h2>
<HobbyButton plus>내 관심사 추가하기</HobbyButton>
</div>

<Button
type="button"
fixed
bottom={24}
sideGap={16}
safeArea
disabled={selected.length < 3}
onClick={handleComplete}
className="bg-button-primary text-button-primary-text-default"
>
다음으로
</Button>
</main>
);
};

export default ScreenHobbySelect;
5 changes: 5 additions & 0 deletions app/hobby-select/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import ScreenHobbySelect from "./_components/ScreenHobbySelect";

export default function HobbySelectPage() {
return <ScreenHobbySelect />;
}
25 changes: 14 additions & 11 deletions app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@ import localFont from "next/font/local";
import "./globals.css";
import Blur from "@/components/common/Blur";
import { QueryProvider } from "@/providers/query-provider";
import { ServiceStatusProvider } from "@/providers/service-status-provider";
import { getInitialMaintenanceStatus } from "@/lib/status";
import { ProfileProvider } from "@/providers/profile-provider";
// import { ServiceStatusProvider } from "@/providers/service-status-provider";
// import { getInitialMaintenanceStatus } from "@/lib/status";

const pretendard = localFont({
src: "./fonts/PretendardVariable.woff2",
Expand Down Expand Up @@ -54,22 +55,24 @@ export default async function RootLayout({
}: Readonly<{
children: React.ReactNode;
}>) {
const initialMaintenanceMode = await getInitialMaintenanceStatus();
// const initialMaintenanceMode = await getInitialMaintenanceStatus();

return (
<html lang="ko" className={pretendard.variable}>
<body
className={`${pretendard.className} flex justify-center bg-white antialiased`}
>
<QueryProvider>
{/* <ServiceStatusProvider */}
{/* initialMaintenanceMode={initialMaintenanceMode} */}
{/* > */}
<div className="bg-background-app-base relative isolate min-h-dvh w-full overflow-x-hidden text-black md:max-w-[430px] md:shadow-lg">
<Blur />
{children}
</div>
{/* </ServiceStatusProvider> */}
<ProfileProvider>
{/* <ServiceStatusProvider */}
{/* initialMaintenanceMode={initialMaintenanceMode} */}
{/* > */}
<div className="bg-background-app-base relative isolate min-h-dvh w-full overflow-x-hidden text-black md:max-w-[430px] md:shadow-lg">
<Blur />
{children}
</div>
{/* </ServiceStatusProvider> */}
</ProfileProvider>
</QueryProvider>
</body>
</html>
Expand Down
7 changes: 2 additions & 5 deletions app/login/page.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import React from "react";
import { Metadata } from "next";
import ScreenLocalLoginPage from "./_components/ScreenLocalLoginPage";

Expand All @@ -7,8 +6,6 @@ export const metadata: Metadata = {
description: "COMAtching 로그인 페이지",
};

const LoginPage = () => {
export default function LoginPage() {
return <ScreenLocalLoginPage />;
};

export default LoginPage;
}
7 changes: 2 additions & 5 deletions app/onboarding/page.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
import React from "react";
import ScreenOnBoarding from "./_components/ScreenOnBoarding";

const page = () => {
export default function OnboardingPage() {
return <ScreenOnBoarding />;
};

export default page;
}
2 changes: 1 addition & 1 deletion app/page.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import ScreenLoginPage from "@/app/_components/ScreenLoginPage";

export default function Home() {
export default function HomePage() {
return <ScreenLoginPage />;
}
31 changes: 31 additions & 0 deletions app/profile-builder/_components/ProfileButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
"use client";

import React from "react";
import { cn } from "@/lib/utils";

interface ProfileButtonProps {
children: React.ReactNode;
selected?: boolean;
onClick?: () => void;
}

export default function ProfileButton({
children,
selected = false,
onClick,
}: ProfileButtonProps) {
return (
<button
type="button"
className={cn(
"typo-20-700 flex h-12 flex-1 items-center justify-center rounded-full transition-colors",
selected
? "bg-pink-conic border border-pink-700 text-pink-700"
: "bg-[#FFFFFF4D] text-gray-300",
)}
onClick={onClick}
>
{children}
</button>
);
}
Loading
Loading