diff --git a/app/_components/BubbleDiv.tsx b/app/_components/BubbleDiv.tsx index 38f1d87..856af87 100644 --- a/app/_components/BubbleDiv.tsx +++ b/app/_components/BubbleDiv.tsx @@ -1,14 +1,33 @@ import Image from "next/image"; import React from "react"; -const BubbleDiv = () => { +const BubbleDiv = ({ + children, + w = 226, + h = 41.77, + typo = "typo-16-600", + top = 1, +}: { + children?: React.ReactNode; + w?: number; + h?: number; + typo?: string; + top?: number; +}) => { return ( -
- - 현재 775명 - 참여중이에요! +
+ + {children || ( + <> + 현재 775명 + 참여중이에요! + + )} - bubble + 말풍선
); }; diff --git a/app/_components/LoginActionSection.tsx b/app/_components/LoginActionSection.tsx index d18909e..27b061b 100644 --- a/app/_components/LoginActionSection.tsx +++ b/app/_components/LoginActionSection.tsx @@ -1,6 +1,5 @@ "use client"; import BubbleDiv from "@/app/_components/BubbleDiv"; -import Button from "@/components/ui/Button"; import { KakaoLoginButton, GoogleLoginButton } from "./SocialButtonList"; import { Drawer, @@ -8,6 +7,7 @@ import { DrawerTitle, DrawerTrigger, } from "@/components/ui/drawer"; +import Link from "next/link"; export default function ScreenLoginActionSection() { const handleKakaoLogin = () => { @@ -65,12 +65,12 @@ export default function ScreenLoginActionSection() {
혹은 - +
diff --git a/app/login/_components/LocalLoginIntro.tsx b/app/login/_components/LocalLoginIntro.tsx new file mode 100644 index 0000000..6e93978 --- /dev/null +++ b/app/login/_components/LocalLoginIntro.tsx @@ -0,0 +1,17 @@ +import React from "react"; + +const LocalLoginIntro = () => { + return ( +
+ + 이메일로 +
코매칭 시작하기 +
+ + 로그인하거나 새로운 계정을 만들어보세요. + +
+ ); +}; + +export default LocalLoginIntro; diff --git a/app/login/_components/LoginForm.tsx b/app/login/_components/LoginForm.tsx new file mode 100644 index 0000000..2d97e91 --- /dev/null +++ b/app/login/_components/LoginForm.tsx @@ -0,0 +1,90 @@ +"use client"; +import BubbleDiv from "@/app/_components/BubbleDiv"; +import Button from "@/components/ui/Button"; +import { User } from "lucide-react"; +import React, { useActionState } from "react"; +import Link from "next/link"; +import { loginAction } from "@/lib/actions/loginAction"; + +const INPUT_STYLE = { + background: + "linear-gradient(180deg, rgba(248, 248, 248, 0.03) 0%, rgba(248, 248, 248, 0.24) 100%)", +}; +const INPUT_CLASSNAME = + "all:unset box-border w-full border-b border-gray-300 px-2 py-[14.5px] leading-[19px] placeholder:text-[#B3B3B3]"; + +export const LoginForm = () => { + // React 19: useActionState로 폼 상태 및 팬딩 처리 관리 + const [state, formAction, isPending] = useActionState(loginAction, { + success: false, + message: "", + }); + + return ( +
+
+
+ + +
+ +
+ + + {!state.success && ( + + * 이메일 혹은 비밀번호가 틀립니다 + + )} +
+ +
+
+ + 이메일 찾기 + + | + + 비밀번호 변경 + +
+ +
+ + 아직 계정이 없으신가요?! + + +
+
+ ); +}; diff --git a/app/login/_components/ScreenLocalLoginPage.tsx b/app/login/_components/ScreenLocalLoginPage.tsx new file mode 100644 index 0000000..f5d5a9f --- /dev/null +++ b/app/login/_components/ScreenLocalLoginPage.tsx @@ -0,0 +1,16 @@ +import React from "react"; +import { BackButton } from "@/components/ui/BackButton"; +import LocalLoginIntro from "./LocalLoginIntro"; +import { LoginForm } from "./LoginForm"; + +const ScreenLocalLoginPage = () => { + return ( +
+ + + +
+ ); +}; + +export default ScreenLocalLoginPage; diff --git a/app/login/page.tsx b/app/login/page.tsx new file mode 100644 index 0000000..5821d39 --- /dev/null +++ b/app/login/page.tsx @@ -0,0 +1,14 @@ +import React from "react"; +import { Metadata } from "next"; +import ScreenLocalLoginPage from "./_components/ScreenLocalLoginPage"; + +export const metadata: Metadata = { + title: "로그인 | COMAtching", + description: "COMAtching 로그인 페이지", +}; + +const LoginPage = () => { + return ; +}; + +export default LoginPage; diff --git a/components/ui/BackButton.tsx b/components/ui/BackButton.tsx new file mode 100644 index 0000000..6ab4b30 --- /dev/null +++ b/components/ui/BackButton.tsx @@ -0,0 +1,36 @@ +"use client"; +import React from "react"; +import { useRouter } from "next/navigation"; +import { ChevronLeft } from "lucide-react"; +import { cn } from "@/lib/utils"; + +type BackButtonProps = { + variant?: "absolute" | "static"; + className?: string; + onClick?: () => void; +}; + +export const BackButton = ({ + variant = "static", + className = "", + onClick, +}: BackButtonProps) => { + const router = useRouter(); + const positionClass = variant === "absolute" ? "absolute left-4 top-2" : ""; + const handleClick = onClick ?? (() => router.back()); + + return ( + + ); +}; diff --git a/components/ui/Button.tsx b/components/ui/Button.tsx index a0f4dda..a741f05 100644 --- a/components/ui/Button.tsx +++ b/components/ui/Button.tsx @@ -48,7 +48,7 @@ export default function Button({ disabled={disabled} className={cn( // 기본 스타일 (기본 높이 h-12, 너비 w-full, 폰트 등 복구) - "typo-20-600 text-button-primary-text-default flex h-12 w-full items-center justify-center rounded-[12px] transition-colors duration-100", + "typo-20-600 text-button-primary-text-default bg-button-primary flex h-12 w-full items-center justify-center rounded-[16px] transition-colors duration-100", fixed && "fixed z-50 mx-auto", disabled ? "cursor-not-allowed" : "cursor-pointer", // 1. 사용자 className 먼저 적용 (여기서 h-10 등을 넣으면 위 h-12가 덮어씌워짐) diff --git a/lib/actions/loginAction.ts b/lib/actions/loginAction.ts new file mode 100644 index 0000000..5260d69 --- /dev/null +++ b/lib/actions/loginAction.ts @@ -0,0 +1,31 @@ +"use server"; + +type LoginState = { + success: boolean; + message: string; +}; + +export async function loginAction( + prevState: LoginState, + formData: FormData, +): Promise { + const email = formData.get("email"); + const password = formData.get("password"); + + // Mock delay + await new Promise((resolve) => setTimeout(resolve, 1000)); + + // TODO: 실제 백엔드 API 호출로 교체 필요 + // const response = await fetch("https://api.comatching.com/login", { + // method: "POST", + // body: JSON.stringify({ email, password }), + // headers: { "Content-Type": "application/json" }, + // }); + + // Mock logic + if (email === "test@test.com" && password === "1234") { + return { success: true, message: "" }; + } + + return { success: false, message: "이메일 혹은 비밀번호가 틀립니다" }; +}