[feat] front: 인증(auth) 피처 추가 — 로그인 폼/훅/임시 mutation

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
민헌 2026-06-16 14:33:23 +09:00
parent 245c287a9d
commit 09643332f2
4 changed files with 136 additions and 0 deletions

View File

@ -0,0 +1,60 @@
import { Button, Input } from '@/components'
import { useLogin } from '@/features/auth/hooks/useLogin'
const inputClassName = 'h-12 rounded-md px-4 text-base'
export function LoginForm() {
const {
id,
password,
errorMessage,
isLoading,
onIdChange,
onPasswordChange,
onLogin,
} = useLogin()
return (
<form
onSubmit={(e) => {
e.preventDefault()
onLogin()
}}
className="flex w-full flex-col justify-center gap-6"
>
<div className="flex flex-col gap-3">
<Input
type="text"
aria-label="아이디"
placeholder="아이디를 입력해주세요."
value={id}
onChange={(e) => onIdChange(e.target.value)}
autoComplete="username"
className={inputClassName}
/>
<Input
type="password"
aria-label="비밀번호"
placeholder="비밀번호를 입력해주세요."
value={password}
onChange={(e) => onPasswordChange(e.target.value)}
autoComplete="current-password"
className={inputClassName}
/>
<p
className="text-sm tracking-[-0.28px] text-destructive"
role="alert"
aria-live="polite"
>
{errorMessage || ' '}
</p>
</div>
<Button type="submit" size="lg" className="w-full" disabled={isLoading}>
{isLoading ? '로그인 중…' : '로그인'}
</Button>
</form>
)
}
export default LoginForm

View File

@ -0,0 +1,55 @@
import { useState } from 'react'
import { useNavigate } from 'react-router'
import { useLoginMutation } from '@/features/auth/hooks/useLoginMutation'
export function useLogin() {
const navigate = useNavigate()
const loginMutation = useLoginMutation()
const [id, setId] = useState('')
const [password, setPassword] = useState('')
const [errorMessage, setErrorMessage] = useState('')
const onIdChange = (value: string) => {
setId(value)
if (errorMessage) setErrorMessage('')
}
const onPasswordChange = (value: string) => {
setPassword(value)
if (errorMessage) setErrorMessage('')
}
const onLogin = () => {
if (!id || !password) {
setErrorMessage('아이디와 비밀번호를 입력해주세요.')
return
}
loginMutation.mutate(
{ id, password },
{
onSuccess: () => {
setErrorMessage('')
navigate('/list')
},
onError: (error) => {
setErrorMessage(
error instanceof Error
? error.message
: '아이디 또는 비밀번호가 올바르지 않습니다.',
)
},
},
)
}
return {
id,
password,
errorMessage,
isLoading: loginMutation.isPending,
onIdChange,
onPasswordChange,
onLogin,
}
}

View File

@ -0,0 +1,20 @@
import { useMutation } from '@tanstack/react-query'
export interface LoginParams {
id: string
password: string
}
/**
* 로그인 mutation (임시 stub).
*
* TODO: mutationFn 을 실제 백엔드 호출로 교체.
* 현재는 API 미연동 상태라 검증만 통과하면 성공 처리한다.
*/
export function useLoginMutation() {
return useMutation<void, Error, LoginParams>({
mutationFn: async () => {
// TODO: 실제 로그인 API 연동
},
})
}

View File

@ -0,0 +1 @@
export { LoginForm } from './components/LoginForm'