diff --git a/src/app/(auth)/login/page.tsx b/src/app/(auth)/login/page.tsx index 092a607..d35451f 100644 --- a/src/app/(auth)/login/page.tsx +++ b/src/app/(auth)/login/page.tsx @@ -1,8 +1,7 @@ import { AuthHeader } from "@/components/auth/AuthHeader"; import { AuthMain } from "@/components/auth/AuthMain"; +import { SubTitle, Title } from "@/components/auth/AuthTitle"; import { LoginForm } from "@/components/auth/login/LoginForm"; -import { LoginPageTitle } from "@/components/auth/login/LoginPageTitle"; -import { LoginSubtitle } from "@/components/auth/login/LoginSubtitle"; import { makePageMetadata } from "@/seo/metadata"; export const metadata = { @@ -18,8 +17,8 @@ export default function LoginPage() { return ( <> - - + 로그인 + 오늘도 만나서 반가워요! diff --git a/src/app/(auth)/signup/email/page.tsx b/src/app/(auth)/signup/email/page.tsx index 8c79b24..3c13f44 100644 --- a/src/app/(auth)/signup/email/page.tsx +++ b/src/app/(auth)/signup/email/page.tsx @@ -1,14 +1,38 @@ +import { AuthHeader } from "@/components/auth/AuthHeader"; +import { AuthMain } from "@/components/auth/AuthMain"; +import { SubTitle, Title } from "@/components/auth/AuthTitle"; +import { SignupForm } from "@/components/auth/signup/SignupForm"; import { makePageMetadata } from "@/seo/metadata"; export const metadata = { ...makePageMetadata({ title: "회원가입 — 이메일 입력", - description: "PlanMate 회원가입 2단계: 이메일 입력 페이지", + description: "MyPlanMate 회원가입 2단계: 이메일 입력 페이지", canonical: "/signup/email", }), robots: { index: false, follow: false }, }; export default function SignupEmailPage() { - return
회원가입 - 이메일 입력 페이지
; + return ( + <> + + 이메일 + 계정으로 사용할 이메일을 알려주세요. + + + + + + + ); } diff --git a/src/app/(auth)/signup/name/page.tsx b/src/app/(auth)/signup/name/page.tsx index bc30608..b74a13f 100644 --- a/src/app/(auth)/signup/name/page.tsx +++ b/src/app/(auth)/signup/name/page.tsx @@ -1,14 +1,36 @@ +import { AuthHeader } from "@/components/auth/AuthHeader"; +import { AuthMain } from "@/components/auth/AuthMain"; +import { SubTitle, Title } from "@/components/auth/AuthTitle"; +import { SignupForm } from "@/components/auth/signup/SignupForm"; import { makePageMetadata } from "@/seo/metadata"; export const metadata = { ...makePageMetadata({ title: "회원가입 — 이름 입력", - description: "PlanMate 회원가입 1단계: 이름 입력 페이지", + description: "MyPlanMate 회원가입 1단계: 이름 입력 페이지", canonical: "/signup/name", }), robots: { index: false, follow: false }, }; export default function SignupNamePage() { - return
회원가입 - 이름 입력 페이지
; + return ( + <> + + 이름 + 이름을 알려주세요. + + + + + + + ); } diff --git a/src/app/(auth)/signup/page.tsx b/src/app/(auth)/signup/page.tsx index 575ca43..229f6bf 100644 --- a/src/app/(auth)/signup/page.tsx +++ b/src/app/(auth)/signup/page.tsx @@ -1,6 +1,7 @@ import { AuthHeader } from "@/components/auth/AuthHeader"; -import { SignupPageTitle } from "@/components/auth/signup/SignupPageTitle"; -import { SignupSubtitle } from "@/components/auth/signup/SignupSubTitle"; +import { AuthMain } from "@/components/auth/AuthMain"; +import { SubTitle, Title } from "@/components/auth/AuthTitle"; +import { SignupGroupButton } from "@/components/auth/signup/SignupGroupButton"; import { makePageMetadata } from "@/seo/metadata"; export const metadata = { @@ -16,9 +17,13 @@ export default function SignupPage() { return ( <> - - + 회원가입 + 원하시는 가입 방식을 선택해주세요. + + + + ); } diff --git a/src/app/(auth)/signup/password/page.tsx b/src/app/(auth)/signup/password/page.tsx index 52bba8b..683491e 100644 --- a/src/app/(auth)/signup/password/page.tsx +++ b/src/app/(auth)/signup/password/page.tsx @@ -1,3 +1,7 @@ +import { AuthHeader } from "@/components/auth/AuthHeader"; +import { AuthMain } from "@/components/auth/AuthMain"; +import { SubTitle, Title } from "@/components/auth/AuthTitle"; +import { SignupForm } from "@/components/auth/signup/SignupForm"; import { makePageMetadata } from "@/seo/metadata"; export const metadata = { @@ -10,5 +14,25 @@ export const metadata = { }; export default function SignupPasswordPage() { - return
회원가입 - 비밀번호 설정 페이지
; + return ( + <> + + 비밀번호 + 8자 이상 / 특수문자 포함 + + + + + + + ); } diff --git a/src/app/(auth)/signup/terms/page.tsx b/src/app/(auth)/signup/terms/page.tsx index 421e7df..0034491 100644 --- a/src/app/(auth)/signup/terms/page.tsx +++ b/src/app/(auth)/signup/terms/page.tsx @@ -1,3 +1,7 @@ +import { AuthHeader } from "@/components/auth/AuthHeader"; +import { AuthMain } from "@/components/auth/AuthMain"; +import { SubTitle, Title } from "@/components/auth/AuthTitle"; +import { SignupForm } from "@/components/auth/signup/SignupForm"; import { makePageMetadata } from "@/seo/metadata"; export const metadata = { @@ -10,5 +14,25 @@ export const metadata = { }; export default function SignupTermsPage() { - return
회원가입 - 약관 동의 페이지
; + return ( + <> + + 약관동의 + 서비스 이용을 위한 필수 약관입니다. + + + + + + + ); } diff --git a/src/components/auth/AuthTitle.tsx b/src/components/auth/AuthTitle.tsx new file mode 100644 index 0000000..04d9b4f --- /dev/null +++ b/src/components/auth/AuthTitle.tsx @@ -0,0 +1,13 @@ +import type { AuthCommonProps } from "@/types/auth"; + +export function Title({ children }: AuthCommonProps) { + return ( +

+ {children} +

+ ); +} + +export function SubTitle({ children }: AuthCommonProps) { + return

{children}

; +} diff --git a/src/components/auth/login/LoginPageTitle.tsx b/src/components/auth/login/LoginPageTitle.tsx deleted file mode 100644 index 67c3393..0000000 --- a/src/components/auth/login/LoginPageTitle.tsx +++ /dev/null @@ -1,7 +0,0 @@ -export function LoginPageTitle() { - return ( -

- 로그인 -

- ); -} diff --git a/src/components/auth/login/LoginSubtitle.tsx b/src/components/auth/login/LoginSubtitle.tsx deleted file mode 100644 index 4ef609e..0000000 --- a/src/components/auth/login/LoginSubtitle.tsx +++ /dev/null @@ -1,3 +0,0 @@ -export function LoginSubtitle() { - return

오늘도 만나서 반가워요!

; -} diff --git a/src/components/auth/signup/SignupForm.tsx b/src/components/auth/signup/SignupForm.tsx new file mode 100644 index 0000000..6585e50 --- /dev/null +++ b/src/components/auth/signup/SignupForm.tsx @@ -0,0 +1,76 @@ +"use client"; + +import { cn } from "@/lib/utils"; +import { Button } from "@/shared/button"; +import { Input } from "@/shared/input"; +import { SignupFormProps } from "@/types/auth"; +import Link from "next/link"; +import { SignupStepIndicator } from "./SignupStepIndicator"; + +export function SignupForm({ + className, + fieldId, + fieldName, + label, + type = "text", + placeholder, + autoComplete, + nextHref, + prevHref, +}: SignupFormProps) { + return ( +
+ + {/* 단일 필드 */} +
+ + +
+ + {/* 다음 / 이전 버튼 (임시: Link로 라우팅) */} +
+ + + + + {prevHref && ( + + + + )} +
+ + {/* 하단 로그인 링크 */} +

+ 이미 회원이신가요?{" "} + + 로그인하기 + +

+ + ); +} diff --git a/src/components/auth/signup/SignupGroupButton.tsx b/src/components/auth/signup/SignupGroupButton.tsx index bdb4b1b..02fffd5 100644 --- a/src/components/auth/signup/SignupGroupButton.tsx +++ b/src/components/auth/signup/SignupGroupButton.tsx @@ -1,45 +1,84 @@ "use client"; -import { SIGNUP_BTNS } from "@/lib/constants"; +import { useFadeSlideInOnMount } from "@/hooks/useMotionPresets"; import { cn } from "@/lib/utils"; import { Button } from "@/shared/button"; import { Icon } from "@/shared/Icon"; import Image from "next/image"; +import Link from "next/link"; import * as React from "react"; -interface Props { +interface SignupGroupButtonProps { className?: string; } -export const SignupGroupButton = React.forwardRef(({ className }, ref) => { - return ( -
- {SIGNUP_BTNS.map(({ key, bg, label, icon }) => ( +export const SignupGroupButton = React.forwardRef( + ({ className }, ref) => { + const fadeClass = useFadeSlideInOnMount("up"); + + return ( +
+ {/* 1) 일반 회원가입 → 이메일 회원가입 폼 페이지로 이동 */} + + + + + {/* 2) Google로 가입 */} + + {/* 3) 카카오로 가입 */} + - ))} -
- ); -}); +
+ ); + }, +); + SignupGroupButton.displayName = "SignupGroupButton"; diff --git a/src/components/auth/signup/SignupPageTitle.tsx b/src/components/auth/signup/SignupPageTitle.tsx deleted file mode 100644 index 6e84dc0..0000000 --- a/src/components/auth/signup/SignupPageTitle.tsx +++ /dev/null @@ -1,7 +0,0 @@ -export function SignupPageTitle() { - return ( -

- 회원가입 -

- ); -} diff --git a/src/components/auth/signup/SignupStepIndicator.tsx b/src/components/auth/signup/SignupStepIndicator.tsx new file mode 100644 index 0000000..b232a11 --- /dev/null +++ b/src/components/auth/signup/SignupStepIndicator.tsx @@ -0,0 +1,51 @@ +"use client"; + +import { cn } from "@/lib/utils"; +import type { SignupStepIndicatorProps } from "@/types/auth"; + +export function SignupStepIndicator({ + currentStep, + totalSteps = 4, + className, +}: SignupStepIndicatorProps) { + const steps = Array.from({ length: totalSteps }, (_, index) => index + 1); + + return ( +
+ {steps.map((step, index) => { + const isActive = step <= currentStep; + const isLast = index === steps.length - 1; + const isCompletedConnector = step < currentStep; + + return ( +
+ {/* ● 동그라미 */} +
+ {step} +
+ + {/* ― 선 (마지막 스텝 뒤에는 없음) */} + {!isLast && ( +
+ )} +
+ ); + })} +
+ ); +} diff --git a/src/components/auth/signup/SignupSubTitle.tsx b/src/components/auth/signup/SignupSubTitle.tsx deleted file mode 100644 index 2263fef..0000000 --- a/src/components/auth/signup/SignupSubTitle.tsx +++ /dev/null @@ -1,3 +0,0 @@ -export function SignupSubtitle() { - return

원하시는 가입 방식을 선택해주세요.

; -} diff --git a/src/lib/constants.ts b/src/lib/constants.ts index bb6689d..15e7bba 100644 --- a/src/lib/constants.ts +++ b/src/lib/constants.ts @@ -27,35 +27,6 @@ export const OG_DEFAULT_IMAGE = "/og/og-default.png"; /** 언어 / 지역 설정 */ export const LOCALE = "ko_KR"; -/* ------------------------------------------------- - 🪪 회원가입 버튼 목록 (Signup Buttons) - - SignupGroupButton에서 사용 - - bg: cva variant key와 1:1 매핑 - ------------------------------------------------- */ - -export const SIGNUP_BTNS = [ - { - key: "email", - bg: "basic" as const, - label: "일반 회원가입", - icon: { kind: "lucide" as const, name: "user-plus2" as const, size: 28 }, // 28px ≒ w-7 h-7 - }, - { - key: "google", - bg: "google" as const, - label: "Google로 가입", - icon: { kind: "image" as const, src: "/icons/google.svg", width: 36, height: 36 }, - }, - { - key: "kakao", - bg: "kakao" as const, - label: "카카오로 가입", - icon: { kind: "image" as const, src: "/icons/kakaotalk.svg", width: 36, height: 36 }, - }, -] as const; - -export type SignupButtonKey = (typeof SIGNUP_BTNS)[number]["key"]; - /* ------------------------------------------------- 🧩 Layout - Footer - LandingFooter에서 사용 diff --git a/src/lib/variants/button.auth.ts b/src/lib/variants/button.auth.ts index 03a3450..8a2f734 100644 --- a/src/lib/variants/button.auth.ts +++ b/src/lib/variants/button.auth.ts @@ -36,6 +36,7 @@ export const loginButtonVariants = cva( "border-0", "hover:bg-[var(--color-yellow-200)]", ].join(" "), + white: ["bg-withe", "text-black", "hover:bg-[var(--color-gray-100)]"], }, }, defaultVariants: { diff --git a/src/stores/authForm.store.ts b/src/stores/authForm.store.ts index b0783c6..008d989 100644 --- a/src/stores/authForm.store.ts +++ b/src/stores/authForm.store.ts @@ -1,4 +1,4 @@ -import type { AuthFormState } from "@/types/authForm"; +import type { AuthFormState } from "@/types/auth"; import { create } from "zustand"; export const useAuthFormStore = create((set, get) => ({ diff --git a/src/types/auth.ts b/src/types/auth.ts index 8afa326..4d561c2 100644 --- a/src/types/auth.ts +++ b/src/types/auth.ts @@ -3,3 +3,50 @@ import type { ReactNode } from "react"; export interface AuthCommonProps { children: ReactNode; } + +export interface AuthFormState { + email: string; + password: string; + emailError: boolean; + passwordError: boolean; + isPasswordVisible: boolean; + + setEmail: (value: string) => void; + setPassword: (value: string) => void; + + clearEmailError: () => void; + clearPasswordError: () => void; + + togglePasswordVisible: () => void; + + validateLogin: () => boolean; +} + +export interface SignupFormProps { + className?: string; + /** 인풋 id / name */ + fieldId: string; + fieldName: string; + /** 라벨 텍스트 (예: 이름, 이메일) */ + label: string; + /** 인풋 타입 */ + type?: "text" | "email" | "password"; + /** placeholder */ + placeholder?: string; + /** autoComplete 힌트 */ + autoComplete?: string; + + /** 임시: 다음 스텝으로 이동할 링크 */ + nextHref: string; + + /** 임시: 이전 스텝으로 이동할 링크 (있을 때만 버튼 노출) */ + prevHref?: string; +} + +export interface SignupStepIndicatorProps { + /** 1부터 시작하는 현재 스텝 번호 */ + currentStep: number; + /** 전체 스텝 수 (기본 4) */ + totalSteps?: number; + className?: string; +} diff --git a/src/types/authForm.ts b/src/types/authForm.ts deleted file mode 100644 index 6593a65..0000000 --- a/src/types/authForm.ts +++ /dev/null @@ -1,17 +0,0 @@ -export interface AuthFormState { - email: string; - password: string; - emailError: boolean; - passwordError: boolean; - isPasswordVisible: boolean; - - setEmail: (value: string) => void; - setPassword: (value: string) => void; - - clearEmailError: () => void; - clearPasswordError: () => void; - - togglePasswordVisible: () => void; - - validateLogin: () => boolean; -} diff --git a/src/types/button.ts b/src/types/button.ts index 28d7a9c..cbf4974 100644 --- a/src/types/button.ts +++ b/src/types/button.ts @@ -7,6 +7,7 @@ export type Size = "sm" | "md"; export type Radius = "sm" | "md" | "lg" | "xl" | "2xl"; export type AuthColor = "black" | "white"; export type SignupBg = "basic" | "google" | "kakao"; +export type BasicSignupBg = "basic" | "google" | "kakao" | "white"; /** 공통 기본 타입 */ export interface BaseButtonProps extends ButtonHTMLAttributes { @@ -35,7 +36,7 @@ export type FeatureProps = BaseButtonProps & { /** auth 전용 */ export type AuthProps = BaseButtonProps & { preset: "auth"; - bg?: SignupBg; + bg?: BasicSignupBg; // 금지 size?: never; disabled?: never;