[feat] front: 인증(auth) 피처 추가 — 로그인 폼/훅/임시 mutation
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
245c287a9d
commit
09643332f2
60
front/src/features/auth/components/LoginForm.tsx
Normal file
60
front/src/features/auth/components/LoginForm.tsx
Normal 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
|
||||||
55
front/src/features/auth/hooks/useLogin.ts
Normal file
55
front/src/features/auth/hooks/useLogin.ts
Normal 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,
|
||||||
|
}
|
||||||
|
}
|
||||||
20
front/src/features/auth/hooks/useLoginMutation.ts
Normal file
20
front/src/features/auth/hooks/useLoginMutation.ts
Normal 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 연동
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
1
front/src/features/auth/index.ts
Normal file
1
front/src/features/auth/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export { LoginForm } from './components/LoginForm'
|
||||||
Loading…
Reference in New Issue
Block a user