Skip to content

Commit e75f2e0

Browse files
authored
[#161] ✨ 로그인 페이지 개발 (#175)
* [#161] 🌱 Initialize Login page creation * [#161] ✨ Implement login page * [#45] ♻️ labelTextClass to use twMergeEx * [#43] ♻️ Set fullWidth as the default prop value for components * [#98] ♻️ Add single checkbox support to FormCheckbox and connect error prop to FormPassword * [#56] ✨ Add BASE_URL to .env configuration * [#42] 💄 Add default size to Logo component
1 parent 430dd1e commit e75f2e0

File tree

7 files changed

+146
-72
lines changed

7 files changed

+146
-72
lines changed

.env

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1-
# NEXT_PUBLIC_API_BASE_URL=http://localhost:3000
1+
NEXT_PUBLIC_API_BASE_URL=http://localhost:3000
2+
NEXT_PUBLIC_BACKEND_BASE_URL=http://43.202.50.174:8080

src/app/(pages)/login/page.tsx

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
'use client'
2+
3+
import { useEffect } from 'react'
4+
import { useForm } from 'react-hook-form'
5+
6+
import { SignInRequest } from '@/types/api/Auth.types'
7+
8+
import { Button, Link } from '@/components/common/button'
9+
import { Label } from '@/components/common/label'
10+
import { Logo } from '@/components/common/logo'
11+
import { Form } from '@/components/shared/form'
12+
13+
import { useSignInMutation } from '@/queries/auth'
14+
15+
/**
16+
* TODO
17+
* 1. 유효성 검사 부분 zod로 작업 및 관심사 분리 (name 말고 다른 prop으로)
18+
* 2. 로그인, 회원가입 버튼 사이 또는 부분 구분선 컴포넌트 추가되면 지정
19+
* 3. 비밀번호 찾기 페이지 디자인 요청 및 구현
20+
*/
21+
22+
interface SignInForm extends SignInRequest {
23+
rememberEmail?: boolean
24+
}
25+
26+
export default function Login(): JSX.Element {
27+
const methods = useForm<SignInForm>({
28+
mode: 'onBlur',
29+
defaultValues: {
30+
email: '',
31+
password: '',
32+
rememberEmail: false,
33+
},
34+
})
35+
36+
const {
37+
formState: { isValid },
38+
setValue,
39+
} = methods
40+
41+
const { mutate: signIn } = useSignInMutation()
42+
43+
const onSubmit = (data: SignInForm) => {
44+
if (data.rememberEmail) {
45+
localStorage.setItem('rememberedEmail', data.email)
46+
} else {
47+
localStorage.removeItem('rememberedEmail')
48+
}
49+
signIn({
50+
email: data.email,
51+
password: data.password,
52+
})
53+
}
54+
55+
useEffect(() => {
56+
const rememberedEmail = localStorage.getItem('rememberedEmail')
57+
if (rememberedEmail) {
58+
setValue('email', rememberedEmail)
59+
setValue('rememberEmail', true)
60+
}
61+
}, [setValue])
62+
63+
return (
64+
<div className='m-auto flex h-auto w-420 flex-col pt-238'>
65+
<div className='m-auto mb-36'>
66+
<Logo />
67+
</div>
68+
<Form methods={methods} onSubmit={methods.handleSubmit(onSubmit)}>
69+
<Label labelText='이메일' className='mb-20'>
70+
<Form.Text name='email' placeholder='이메일을 입력해주세요' />
71+
</Label>
72+
<Label labelText='비밀번호' className='mb-20'>
73+
<Form.Password
74+
name='password'
75+
placeholder='비밀번호를 입력해주세요'
76+
/>
77+
</Label>
78+
<div className='flex h-24 items-center justify-between'>
79+
<Form.Checkbox
80+
variant='checkbox'
81+
name='rememberEmail'
82+
label='이메일 기억하기'
83+
className='ml-4 h-20 text-body3 font-medium text-gray-600'
84+
/>
85+
<Link
86+
variant='text'
87+
href='/find-password'
88+
className='p-0 text-body3 font-medium text-gray-600'
89+
>
90+
비밀번호 찾기
91+
</Link>
92+
</div>
93+
<Button disabled={!isValid} type='submit' fullWidth className='my-20'>
94+
로그인
95+
</Button>
96+
<span className='flex flex-col items-center text-body3 text-gray-600'>
97+
또는
98+
</span>
99+
<Link variant='outlined' href='/sign-up' fullWidth className='mt-20'>
100+
회원가입
101+
</Link>
102+
</Form>
103+
</div>
104+
)
105+
}

src/app/(pages)/sign-in/page.tsx

Lines changed: 0 additions & 48 deletions
This file was deleted.

src/components/common/input/CheckboxInput.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { twMerge } from 'tailwind-merge'
44

55
import { handleKeyDown } from '@/utils/handleKeyDown'
66
import { toggleCheckbox } from '@/utils/toggleCheckbox'
7+
import { twMergeEx } from '@/lib/twMerge'
78

89
export interface CheckboxInputProps
910
extends React.InputHTMLAttributes<HTMLInputElement> {
@@ -59,8 +60,7 @@ export const CheckboxInput = ({
5960
'focus:outline-none focus:ring-1 focus:ring-primary-normal',
6061
disabled && 'cursor-not-allowed opacity-50'
6162
)
62-
const labelTextClass = twMerge('ml-10 h-22', className)
63-
63+
const labelTextClass = twMergeEx('ml-10 h-22', className)
6464
return (
6565
<label className={labelClass}>
6666
<input

src/components/common/input/TextInput.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ export const TextInput = forwardRef<HTMLInputElement, TextInputProps>(
2323
startAdornment,
2424
endAdornment,
2525
className = '',
26-
fullWidth = false,
26+
fullWidth = true,
2727
...props
2828
},
2929
ref

src/components/common/logo/Logo.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,9 @@ type Variant = 'default' | 'primary' | 'secondary'
77
type LogoProps = { variant?: Variant }
88

99
const logoByVariant: Record<Variant, React.ReactElement> = {
10-
default: <LogoBlack />,
11-
primary: <LogoWhite />,
12-
secondary: <LogoGray />,
10+
default: <LogoBlack width='200px' height='40px' />,
11+
primary: <LogoWhite width='200px' height='40px' />,
12+
secondary: <LogoGray width='200px' height='40px' />,
1313
}
1414

1515
export const Logo = ({ variant = 'default' }: LogoProps): JSX.Element => {

src/components/shared/form/Form.tsx

Lines changed: 33 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -73,11 +73,11 @@ const FormText = ({
7373
</StatusMessage>
7474
)}
7575
{/* TODO: 이미 가입된 이메일 확인하는 로직 추가하면서 수정 필요 */}
76-
{isSuccess && (
76+
{/* {isSuccess && (
7777
<StatusMessage hasError={false}>
7878
가입 가능한 이메일입니다.
7979
</StatusMessage>
80-
)}
80+
)} */}
8181
</>
8282
)
8383
}
@@ -94,14 +94,19 @@ const FormPassword = ({
9494
formState: { errors },
9595
getValues,
9696
} = useFormContext()
97+
9798
const registerOptions =
9899
name === 'passwordConfirmation'
99100
? PASSWORD_CONFIRM_RULES(getValues('password'))
100101
: VALIDATION_RULES[name]
101102

102103
return (
103104
<>
104-
<PasswordInput {...register(name, registerOptions)} {...props} />
105+
<PasswordInput
106+
{...register(name, registerOptions)}
107+
{...props}
108+
error={Boolean(errors[name])}
109+
/>
105110
{errors[name]?.message && (
106111
<StatusMessage hasError={Boolean(errors[name])}>
107112
{errors[name].message as string}
@@ -136,11 +141,13 @@ const FormCheckbox = ({
136141
name,
137142
rules,
138143
options,
144+
label,
139145
...props
140146
}: {
141147
name: string
142148
rules?: Record<string, unknown>
143-
options: { label: string; value: string }[]
149+
options?: { label: string; value: string }[]
150+
label?: string
144151
} & CheckboxInputProps): JSX.Element => {
145152
const { control } = useFormContext()
146153
return (
@@ -150,22 +157,31 @@ const FormCheckbox = ({
150157
rules={rules}
151158
render={({ field }) => (
152159
<>
153-
{options.map(option => (
160+
{options ? (
161+
options.map(option => (
162+
<CheckboxInput
163+
key={option.value}
164+
{...props}
165+
value={option.value}
166+
label={option.label}
167+
checked={(field.value || []).includes(option.value)}
168+
onChange={e => {
169+
const currentValue = field.value || []
170+
const newValue = e.target.checked
171+
? [...currentValue, option.value]
172+
: currentValue.filter((v: string) => v !== option.value)
173+
field.onChange(newValue)
174+
}}
175+
/>
176+
))
177+
) : (
154178
<CheckboxInput
155-
key={option.value}
156179
{...props}
157-
value={option.value}
158-
label={option.label}
159-
checked={(field.value || []).includes(option.value)}
160-
onChange={e => {
161-
const currentValue = field.value || []
162-
const newValue = e.target.checked
163-
? [...currentValue, option.value]
164-
: currentValue.filter((v: string) => v !== option.value)
165-
field.onChange(newValue)
166-
}}
180+
label={label}
181+
checked={field.value || false}
182+
onChange={e => field.onChange(e.target.checked)}
167183
/>
168-
))}
184+
)}
169185
</>
170186
)}
171187
/>

0 commit comments

Comments
 (0)