Skip to content
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
15192a0
feat(DEVING-25): chip 컴포넌트 개발
dbswl701 Feb 21, 2025
54440cb
feat(DEVING-25): 회원가입 닉네임 중복 검사
dbswl701 Feb 21, 2025
92772d8
feat(DEVING-25): 회원가입 이메일 중복 검사
dbswl701 Feb 21, 2025
380ab9e
feat(DEVING-25): 비밀번호 확인 로직 추가
dbswl701 Feb 21, 2025
7f6bfc9
feat(DEVING-25): 회원가입API 연동 전 동작 구현
dbswl701 Feb 21, 2025
b95dd8d
feat(DEVING-25): 회원가입 API 연동
dbswl701 Feb 21, 2025
c5f7eb2
refactor(DEVING-25): 회원가입 로직과 ui 분리 리펙토링
dbswl701 Feb 21, 2025
94809a7
feat(DEVING-25): 입력창 포커스 1초 뒤 유효성 검사
dbswl701 Feb 23, 2025
6778d13
refactor(DEVING-25): 유효성 검사 파일 분리
dbswl701 Feb 23, 2025
9e7d588
feat(DEVING-20): toast 구현
dbswl701 Feb 26, 2025
801b035
Merge pull request #30 from MoimService/feat/component/toast/DEVING-20
dbswl701 Feb 27, 2025
787c2a8
Merge pull request #32 from MoimService/dev
lee1nna Feb 27, 2025
442530f
Merge branch 'dev' of https://github.com/MoimService/Moim-FE into fea…
dbswl701 Feb 28, 2025
c4d9a92
Merge branch 'feat/markup/DEVING-10' of https://github.com/MoimServic…
dbswl701 Feb 28, 2025
e4e3ac0
refactor(DEVING-25): 로그인 리렌더링 감소 위한 리펙토링
dbswl701 Feb 28, 2025
c1fdeb6
refactor(DEVING-25): 회원가입 리렌더링 감소 위한 리펙토링
dbswl701 Feb 28, 2025
0dcbd56
refactor(DEVING-25): 포지션 선택 공통 컴포넌트로 분리
dbswl701 Feb 28, 2025
303fb79
refactor(DEVING-25): 타입 재사용 및 메타데이터 추가
dbswl701 Feb 28, 2025
66d5593
refactor(DEVING-25): gnb 요소 객체로 관리
dbswl701 Feb 28, 2025
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
36 changes: 16 additions & 20 deletions src/app/login/components/LoginForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,19 @@

import { Button } from '@/components/ui/Button';
import { Input } from '@/components/ui/Input';
import { useLoginForm } from '@/hooks/useLoginForm';
import useLoginForm from '@/hooks/useLoginForm';
import {
loginEmailValidation,
loginPasswordValidation,
} from '@/util/validation';
import Link from 'next/link';
import { useRouter } from 'next/navigation';

const LoginForm = () => {
const router = useRouter();
const { register, handleSubmit, onSubmit, errors, setFocusedField } =
useLoginForm();

return (
<form
onSubmit={handleSubmit(onSubmit)}
Expand All @@ -23,13 +30,7 @@ const LoginForm = () => {
id="email"
className="mb-[20px] mt-[8px]"
placeholder="이메일을 입력해주세요."
{...register('email', {
required: '이메일을 입력해주세요.',
pattern: {
value: /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/,
message: '올바른 이메일 형식이 아닙니다.',
},
})}
{...register('email', loginEmailValidation)}
errorMessage={errors.email?.message}
onFocus={() => setFocusedField('email')}
/>
Expand All @@ -41,17 +42,7 @@ const LoginForm = () => {
type="password"
className="mb-[20px] mt-[8px]"
placeholder="비밀번호를 입력해주세요."
{...register('password', {
required: '비밀번호를 입력해주세요.',
minLength: {
value: 6,
message: '비밀번호는 최소 6자 이상이어야 합니다.',
},
pattern: {
value: /^(?=.*[A-Za-z])(?=.*\d)[A-Za-z\d]{6,}$/,
message: '비밀번호는 영문과 숫자를 포함해야 합니다.',
},
})}
{...register('password', loginPasswordValidation)}
errorMessage={errors.password?.message}
onFocus={() => setFocusedField('password')}
/>
Expand All @@ -68,7 +59,12 @@ const LoginForm = () => {
<Button type="submit" className="w-full">
로그인
</Button>
<Button type="button" className="w-full" variant={'outline'}>
<Button
type="button"
className="w-full"
variant={'outline'}
onClick={() => router.push('/signup')}
>
회원가입
</Button>
</div>
Expand Down
40 changes: 40 additions & 0 deletions src/app/preview/chip/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
'use client';

import Chip from '@/components/ui/Chip';
import React, { useState } from 'react';

export default function ChipPreview() {
const [position, setPosition] = useState('');

return (
<div className="flex flex-col gap-8 bg-gray-50 p-8 pb-32">
<Chip>All</Chip>
<Chip isActive>All</Chip>
<div className="w-[544px] p-[40px]">
<div className="flex w-full">
<Chip
className={`flex-1 hover:cursor-pointer`}
isActive={position === 'Frontend'}
onClick={() => setPosition('Frontend')}
>
프론트엔드
</Chip>
<Chip
className={`flex-1 hover:cursor-pointer`}
isActive={position === 'Backend'}
onClick={() => setPosition('Backend')}
>
백엔드
</Chip>
<Chip
className={`flex-1 hover:cursor-pointer`}
isActive={position === 'Designer'}
onClick={() => setPosition('Designer')}
>
디자이너
</Chip>
</div>
</div>
Comment on lines +13 to +37
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

ChipContainer 컴포넌트 재사용이 필요합니다.

이미 구현된 ChipContainer 컴포넌트를 재사용하여 코드 중복을 제거하는 것이 좋습니다.

-      <div className="w-[544px] p-[40px]">
-        <div className="flex w-full">
-          <Chip
-            className={`flex-1 hover:cursor-pointer`}
-            isActive={position === 'Frontend'}
-            onClick={() => setPosition('Frontend')}
-          >
-            프론트엔드
-          </Chip>
-          <Chip
-            className={`flex-1 hover:cursor-pointer`}
-            isActive={position === 'Backend'}
-            onClick={() => setPosition('Backend')}
-          >
-            백엔드
-          </Chip>
-          <Chip
-            className={`flex-1 hover:cursor-pointer`}
-            isActive={position === 'Designer'}
-            onClick={() => setPosition('Designer')}
-          >
-            디자이너
-          </Chip>
-        </div>
+      <div className="w-[544px] p-[40px]">
+        <ChipContainer position={position} setPosition={setPosition} />
       </div>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<div className="w-[544px] p-[40px]">
<div className="flex w-full">
<Chip
className={`flex-1 hover:cursor-pointer`}
isActive={position === 'Frontend'}
onClick={() => setPosition('Frontend')}
>
프론트엔드
</Chip>
<Chip
className={`flex-1 hover:cursor-pointer`}
isActive={position === 'Backend'}
onClick={() => setPosition('Backend')}
>
백엔드
</Chip>
<Chip
className={`flex-1 hover:cursor-pointer`}
isActive={position === 'Designer'}
onClick={() => setPosition('Designer')}
>
디자이너
</Chip>
</div>
</div>
<div className="w-[544px] p-[40px]">
<ChipContainer position={position} setPosition={setPosition} />
</div>

</div>
);
}
35 changes: 35 additions & 0 deletions src/app/signup/components/ChipContainer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import Chip from '@/components/ui/Chip';

export const ChipContainer = ({
position,
setPosition,
}: {
position: string;
setPosition: (value: string) => void;
}) => {
return (
<div className="flex w-full gap-[8px]">
<Chip
className={`flex-1 hover:cursor-pointer`}
isActive={position === 'Frontend'}
onClick={() => setPosition('Frontend')}
>
프론트엔드
</Chip>
<Chip
className={`flex-1 hover:cursor-pointer`}
isActive={position === 'Backend'}
onClick={() => setPosition('Backend')}
>
백엔드
</Chip>
<Chip
className={`flex-1 hover:cursor-pointer`}
isActive={position === 'Designer'}
onClick={() => setPosition('Designer')}
>
디자이너
</Chip>
Copy link
Contributor

Choose a reason for hiding this comment

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

요 Chip은 마이페이지 수정에서도 사용될 컴포넌트여서 추후 분리 요청가능할까용

</div>
);
};
164 changes: 164 additions & 0 deletions src/app/signup/components/SignupForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
'use client';

import { Button } from '@/components/ui/Button';
import { Input } from '@/components/ui/Input';
import useSignUpForm from '@/hooks/useSignupForm';
import {
emailValidation,
nameValidation,
passwordCheckValidation,
passwordValidation,
positionValidation,
} from '@/util/validation';
import Link from 'next/link';

import { ChipContainer } from './ChipContainer';

const SignupForm = () => {
const {
handleSubmit,
onSubmit,
register,
errors,
isNameCheck,
handleNameCheck,
isEmailCheck,
handleEmailCheck,
watch,
handleClickPosition,
dirtyFields,
setFocusedField,
} = useSignUpForm();
return (
<form
onSubmit={handleSubmit(onSubmit)}
className="flex w-[544px] flex-col rounded-[16px] bg-BG_2 p-[40px]"
>
Comment on lines +32 to +35
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

폼 제출 시 피드백이 필요합니다.

사용자에게 폼 제출 상태와 결과를 알려주어야 합니다.

     <form
       onSubmit={handleSubmit(onSubmit)}
-      className="flex w-[544px] flex-col rounded-[16px] bg-BG_2 p-[40px]"
+      className="flex w-[544px] flex-col rounded-[16px] bg-BG_2 p-[40px] relative"
     >
+      {isSubmitting && (
+        <div className="absolute inset-0 bg-black/50 flex items-center justify-center rounded-[16px]">
+          <p className="text-white">가입 처리중...</p>
+        </div>
+      )}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<form
onSubmit={handleSubmit(onSubmit)}
className="flex w-[544px] flex-col rounded-[16px] bg-BG_2 p-[40px]"
>
<form
onSubmit={handleSubmit(onSubmit)}
className="flex w-[544px] flex-col rounded-[16px] bg-BG_2 p-[40px] relative"
>
{isSubmitting && (
<div className="absolute inset-0 bg-black/50 flex items-center justify-center rounded-[16px]">
<p className="text-white">가입 처리중...</p>
</div>
)}

<div>
<h2 className="typo-head2 mb-[40px] text-center text-white">
회원가입
</h2>
<div className="flex flex-col gap-[24px]">
<div className="flex flex-col gap-[8px]">
<label htmlFor="name" className="typo-head3 text-Cgray700">
닉네임
</label>
<div className="flex flex-row gap-[8px]">
<Input
id="name"
className="h-full"
placeholder="닉네임을 입력해주세요."
{...register('name', nameValidation)}
errorMessage={errors.name?.message}
state={isNameCheck ? 'success' : 'default'}
onFocus={() => setFocusedField('name')}
/>
<Button
disabled={isNameCheck}
variant={'outline'}
size={'sm'}
className="h-[50px]"
type="button"
onClick={handleNameCheck}
>
중복확인
</Button>
</div>
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

중복 확인 버튼의 로딩 상태 처리가 필요합니다.

사용자 경험 향상을 위해 중복 확인 중인 상태를 표시해야 합니다.

             <Button
               disabled={isNameCheck}
               variant={'outline'}
               size={'sm'}
               className="h-[50px]"
               type="button"
+              isLoading={isCheckingName}
               onClick={handleNameCheck}
             >
-              중복확인
+              {isCheckingName ? '확인중...' : '중복확인'}
             </Button>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<Button
disabled={isNameCheck}
variant={'outline'}
size={'sm'}
className="h-[50px]"
type="button"
onClick={handleNameCheck}
>
중복확인
</Button>
</div>
<Button
disabled={isNameCheck}
variant={'outline'}
size={'sm'}
className="h-[50px]"
type="button"
isLoading={isCheckingName}
onClick={handleNameCheck}
>
{isCheckingName ? '확인중...' : '중복확인'}
</Button>
</div>

</div>

<div className="flex flex-col gap-[8px]">
<label htmlFor="id" className="typo-head3 text-Cgray700">
이메일
</label>
<div className="flex flex-row gap-[8px]">
<Input
id="email"
className=" h-full"
placeholder="이메일을 입력해주세요."
{...register('email', emailValidation)}
state={isEmailCheck ? 'success' : 'default'}
errorMessage={errors.email?.message}
onFocus={() => setFocusedField('email')}
/>
<Button
disabled={isEmailCheck}
variant={'outline'}
size={'sm'}
className="h-[50px]"
onClick={handleEmailCheck}
type="button"
>
중복확인
</Button>
</div>
</div>

<div className="flex flex-col gap-[8px]">
<label htmlFor="id" className="typo-head3 text-Cgray700">
포지션
</label>
<ChipContainer
position={watch('position')}
setPosition={handleClickPosition}
/>
{errors.position?.message && (
<p className="typo-caption1 mt-[10px] px-[10px] text-warning">
{errors.position?.message}
</p>
)}
<input
type="hidden"
{...register('position', positionValidation)}
/>
</div>

<div className="flex flex-col gap-[8px]">
<label htmlFor="password" className="typo-head3 text-Cgray700">
비밀번호
</label>
<Input
id="password"
type="password"
placeholder="비밀번호를 입력해주세요."
{...register('password', passwordValidation)}
state={dirtyFields.password ? 'success' : 'default'}
errorMessage={errors.password?.message}
onFocus={() => setFocusedField('password')}
/>
</div>

<div className="flex flex-col gap-[8px]">
<label htmlFor="pw" className="typo-head3 text-Cgray700">
비밀번호 확인
</label>
<Input
id="passwordCheck"
type="password"
placeholder="비밀번호를 입력해주세요."
{...register(
'passwordCheck',
passwordCheckValidation(watch('password')),
)}
state={dirtyFields.passwordCheck ? 'success' : 'default'}
errorMessage={errors.passwordCheck?.message}
onFocus={() => setFocusedField('passwordCheck')}
/>
</div>
</div>
</div>
<div className="mb-[20px] mt-[48px] flex flex-col">
<Button type="submit" className="w-full">
회원가입
</Button>
</div>
Comment on lines +85 to +89
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

폼 유효성 검사 상태 표시 필요

현재 제출 버튼은 폼의 유효성 검사 상태와 관계없이 활성화되어 있습니다. 모든 필드가 유효하지 않을 때는 버튼을 비활성화하거나 시각적으로 표시하여 사용자에게 명확한 피드백을 제공해야 합니다.

       <div className="mb-[20px] mt-[48px] flex flex-col">
-        <Button type="submit" className="w-full">
+        <Button 
+          type="submit" 
+          className="w-full"
+          disabled={!isValid || isSubmitting || !isNameCheck || !isEmailCheck}
+        >
           회원가입
         </Button>
       </div>

이렇게 수정하면 폼이 유효하지 않거나 제출 중일 때, 또는 닉네임과 이메일 중복 확인이 완료되지 않았을 때 버튼이 비활성화되어 사용자에게 더 명확한 지침을 제공할 수 있습니다.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<div className="mb-[20px] mt-[48px] flex flex-col">
<Button type="submit" className="w-full">
회원가입
</Button>
</div>
<div className="mb-[20px] mt-[48px] flex flex-col">
<Button
type="submit"
className="w-full"
disabled={!isValid || isSubmitting || !isNameCheck || !isEmailCheck}
>
회원가입
</Button>
</div>

<div className="flex justify-between">
<p className="text-Cgray700">이미 회원이신가요?</p>
<Link href="/login" className="text-main underline">
로그인
</Link>
</div>
</form>
);
};

export default SignupForm;
Loading
Loading