Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
]
},
"dependencies": {
"@tanstack/react-form": "^1.27.0",
"@tanstack/react-query": "^5.90.3",
"@tanstack/react-query-devtools": "^5.90.2",
"axios": "^1.13.2",
Expand All @@ -50,7 +51,8 @@
"react": "19.2.0",
"react-dom": "19.2.0",
"swiper": "^12.0.3",
"tailwind-merge": "^3.3.1"
"tailwind-merge": "^3.3.1",
"zod": "^4.1.13"
},
"devDependencies": {
"@commitlint/cli": "^20.1.0",
Expand Down
93 changes: 86 additions & 7 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions src/components/pages/login/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { LoginForm } from './login-form';
111 changes: 111 additions & 0 deletions src/components/pages/login/login-form/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
'use client';

import { useForm } from '@tanstack/react-form';

import { FormInput } from '@/components/shared';
import { Button } from '@/components/ui';
import { loginSchema } from '@/lib/schema/auth';

const getHintMessage = (errors: unknown[], isTouched: boolean, submissionAttempts: number) => {
const firstError = errors[0] as { message?: string } | undefined;
const showError = isTouched || submissionAttempts > 0;

return showError ? firstError?.message : undefined;
};

export const LoginForm = () => {
const form = useForm({
defaultValues: {
email: '',
password: '',
},
validators: {
onSubmit: loginSchema,
onChange: loginSchema,
},
onSubmit: async ({ value }) => {
// API 호출
alert('login:' + value.email);
},
});

return (
<form
className='flex-col-center w-full gap-8'
onSubmit={(e) => {
e.preventDefault();
void form.handleSubmit();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

오 void라는게 있는지 몰랐네요 좋은 것 같습니다

}}
>
<div className='flex-col-center w-full gap-4'>
<form.Field name='email'>
{(field) => {
const {
meta: { errors, isTouched },
} = field.state;
const hintMessage = getHintMessage(errors, isTouched, form.state.submissionAttempts);
Comment on lines +43 to +46
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getHintMessage가 에러메시지를 처리하는 유틸함수니까, 더 간단하게 이렇게 처리할 수 있을 것 같아요

Suggested change
const {
meta: { errors, isTouched },
} = field.state;
const hintMessage = getHintMessage(errors, isTouched, form.state.submissionAttempts);
const hintMessage = getHintMessage(field);

그러면 아래처럼 isTouched랑 errors를 전부 field에서 뽑을 수 있어서 JSX 리턴문을 좀 더 간소화 시킬 수 있게 돼요!

const getHintMessage = (field: AnyFieldAPI) => {
  const errors = field.state.meta.errors
  ...
};


return (
<FormInput
hintMessage={hintMessage}
inputProps={{
type: 'email',
autoComplete: 'email',
placeholder: '이메일을 입력해주세요',
value: field.state.value,
onChange: (e) => field.handleChange(e.target.value),
}}
labelName='이메일'
/>
);
}}
</form.Field>

<form.Field name='password'>
{(field) => {
const {
meta: { errors, isTouched },
} = field.state;
const hintMessage = getHintMessage(errors, isTouched, form.state.submissionAttempts);

return (
<FormInput
hintMessage={hintMessage}
inputProps={{
type: 'password',
placeholder: '비밀번호를 입력해주세요',
value: field.state.value,
onChange: (e) => field.handleChange(e.target.value),
}}
labelName='비밀번호'
/>
);
}}
</form.Field>
</div>

<form.Subscribe
selector={(state) => ({
canSubmit: state.canSubmit,
isSubmitting: state.isSubmitting,
})}
>
{({ canSubmit, isSubmitting }) => {
const disabled = !canSubmit || isSubmitting;

return (
<Button
className='border-none'
disabled={disabled}
size='md'
type='submit'
variant='primary'
>
로그인하기
</Button>
);
}}
</form.Subscribe>
</form>
);
};
1 change: 1 addition & 0 deletions src/components/pages/signup/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { SignupForm } from './signup-form';
Loading