-
Notifications
You must be signed in to change notification settings - Fork 1
feat: 로그인 폼에 FormInput 컴포넌트 추가 및 상태 관리 개선 #21
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 2 commits
ee718c7
41b58c1
ffc4e90
7d25582
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,59 +1,53 @@ | ||||||||||||||||||||||||||||||||||||||||||
| "use client"; | ||||||||||||||||||||||||||||||||||||||||||
| import BubbleDiv from "@/app/_components/BubbleDiv"; | ||||||||||||||||||||||||||||||||||||||||||
| import Button from "@/components/ui/Button"; | ||||||||||||||||||||||||||||||||||||||||||
| import FormInput from "@/components/ui/FormInput"; | ||||||||||||||||||||||||||||||||||||||||||
| import { User } from "lucide-react"; | ||||||||||||||||||||||||||||||||||||||||||
| import React, { useActionState } from "react"; | ||||||||||||||||||||||||||||||||||||||||||
| import React, { useActionState, useState } 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: "", | ||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| const [email, setEmail] = useState(""); | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| return ( | ||||||||||||||||||||||||||||||||||||||||||
| <section className="mt-10 flex w-full flex-1 flex-col items-start gap-6"> | ||||||||||||||||||||||||||||||||||||||||||
| <form className="flex w-full flex-col gap-4" action={formAction}> | ||||||||||||||||||||||||||||||||||||||||||
| <div className="flex w-full flex-col gap-2"> | ||||||||||||||||||||||||||||||||||||||||||
| <label htmlFor="email" className="typo-14-500 text-gray-700"> | ||||||||||||||||||||||||||||||||||||||||||
| 아이디(이메일) | ||||||||||||||||||||||||||||||||||||||||||
| </label> | ||||||||||||||||||||||||||||||||||||||||||
| <input | ||||||||||||||||||||||||||||||||||||||||||
| <FormInput | ||||||||||||||||||||||||||||||||||||||||||
| id="email" | ||||||||||||||||||||||||||||||||||||||||||
| type="email" | ||||||||||||||||||||||||||||||||||||||||||
| name="email" | ||||||||||||||||||||||||||||||||||||||||||
| placeholder="이메일 입력" | ||||||||||||||||||||||||||||||||||||||||||
| required | ||||||||||||||||||||||||||||||||||||||||||
| autoComplete="email" | ||||||||||||||||||||||||||||||||||||||||||
| className={INPUT_CLASSNAME} | ||||||||||||||||||||||||||||||||||||||||||
| style={INPUT_STYLE} | ||||||||||||||||||||||||||||||||||||||||||
| value={email} | ||||||||||||||||||||||||||||||||||||||||||
| onChange={(e) => setEmail(e.target.value)} | ||||||||||||||||||||||||||||||||||||||||||
dasosann marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||||||||||||||||||||||||
| /> | ||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+26
to
35
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 서버 액션과
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| <div className="relative mb-6 flex w-full flex-col gap-2"> | ||||||||||||||||||||||||||||||||||||||||||
| <label htmlFor="password" className="typo-14-500 text-gray-700"> | ||||||||||||||||||||||||||||||||||||||||||
| 비밀번호 | ||||||||||||||||||||||||||||||||||||||||||
| </label> | ||||||||||||||||||||||||||||||||||||||||||
| <input | ||||||||||||||||||||||||||||||||||||||||||
| <FormInput | ||||||||||||||||||||||||||||||||||||||||||
| id="password" | ||||||||||||||||||||||||||||||||||||||||||
| type="password" | ||||||||||||||||||||||||||||||||||||||||||
| name="password" | ||||||||||||||||||||||||||||||||||||||||||
| placeholder="비밀번호 입력" | ||||||||||||||||||||||||||||||||||||||||||
| required | ||||||||||||||||||||||||||||||||||||||||||
| autoComplete="current-password" | ||||||||||||||||||||||||||||||||||||||||||
| className={INPUT_CLASSNAME} | ||||||||||||||||||||||||||||||||||||||||||
| style={INPUT_STYLE} | ||||||||||||||||||||||||||||||||||||||||||
| /> | ||||||||||||||||||||||||||||||||||||||||||
| {!state.success && ( | ||||||||||||||||||||||||||||||||||||||||||
| {!state.success && state.message && ( | ||||||||||||||||||||||||||||||||||||||||||
| <span className="typo-12-400 text-color-flame-700 absolute bottom-[-25px] left-0"> | ||||||||||||||||||||||||||||||||||||||||||
| * 이메일 혹은 비밀번호가 틀립니다 | ||||||||||||||||||||||||||||||||||||||||||
dasosann marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||||||||||||||||||||||||
| </span> | ||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -77,13 +71,13 @@ export const LoginForm = () => { | |||||||||||||||||||||||||||||||||||||||||
| <BubbleDiv w={162} h={26} typo="typo-12-600" top={3}> | ||||||||||||||||||||||||||||||||||||||||||
| 아직 계정이 없으신가요?! | ||||||||||||||||||||||||||||||||||||||||||
| </BubbleDiv> | ||||||||||||||||||||||||||||||||||||||||||
| <button | ||||||||||||||||||||||||||||||||||||||||||
| type="button" | ||||||||||||||||||||||||||||||||||||||||||
| <Link | ||||||||||||||||||||||||||||||||||||||||||
| href="/register" | ||||||||||||||||||||||||||||||||||||||||||
| className="typo-14-500 flex items-center gap-1 border-b-2 border-gray-500 text-gray-500" | ||||||||||||||||||||||||||||||||||||||||||
| > | ||||||||||||||||||||||||||||||||||||||||||
| <User /> | ||||||||||||||||||||||||||||||||||||||||||
| 이메일로 회원가입 | ||||||||||||||||||||||||||||||||||||||||||
| </button> | ||||||||||||||||||||||||||||||||||||||||||
| </Link> | ||||||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||||||
| </section> | ||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,50 @@ | ||
| import React from "react"; | ||
| import { cn } from "@/lib/utils"; | ||
|
|
||
| interface FormInputProps { | ||
| id: string; // input 요소의 고유 식별자 (label의 htmlFor와 연결) | ||
| type: string; // input 타입 (예: text, email, password 등) | ||
| name: string; // form 데이터 전송 시 key 역할 | ||
| placeholder: string; // 입력란에 표시되는 안내 텍스트 | ||
| autoComplete?: string; // 브라우저 자동완성 속성값 (예: email, username, current-password, new-password, tel, off 등) | ||
| required?: boolean; // 필수 입력 여부 | ||
| className?: string; // 추가 커스텀 클래스 | ||
| style?: React.CSSProperties; // 인라인 스타일 | ||
| value?: string; // input 값 (제어 컴포넌트로 사용 시) | ||
| onChange?: (e: React.ChangeEvent<HTMLInputElement>) => void; // 값 변경 핸들러 | ||
| } | ||
|
|
||
| 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] typo-16-500 placeholder:text-[#B3B3B3] text-color-gray-900 outline-none"; | ||
|
|
||
| const FormInput = ({ | ||
| id, | ||
| type, | ||
| name, | ||
| placeholder, | ||
| autoComplete, | ||
| required = false, | ||
| className = "", | ||
| style = {}, | ||
| ...rest | ||
| }: FormInputProps) => { | ||
| return ( | ||
| <input | ||
| id={id} | ||
| type={type} | ||
| name={name} | ||
| placeholder={placeholder} | ||
| autoComplete={autoComplete} | ||
| required={required} | ||
| className={cn(INPUT_CLASSNAME, className)} | ||
| style={{ ...INPUT_STYLE, ...style }} | ||
| {...rest} | ||
| /> | ||
|
Comment on lines
60
to
78
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The References
|
||
| ); | ||
| }; | ||
|
|
||
| export default FormInput; | ||
Uh oh!
There was an error while loading. Please reload this page.