Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[김미소]week15 #484

Merged
merged 5 commits into from
May 29, 2024
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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,6 @@ yarn-error.log*
# typescript
*.tsbuildinfo
next-env.d.ts

# page
page_bak.tsx
39 changes: 39 additions & 0 deletions api/folder.api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { instance } from '@/lib/axios';

const FOLDERS = '/users/1/folders';
const LINKS = '/users/1/links';

// folder name
export async function getFolderProps() {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Props라는 워딩이 의도하는 바를 모르겠습니다 !

Suggested change
export async function getFolderProps() {
export async function getFolders() {

PropsProperties의 줄임말인데요 ! 폴더의 속성들을 불러오는 것으로 보이지는 않아서..
"폴더들을 불러온다" 라는 함수로 getFolders가 어떤지 제안드리고 싶어요 😊

try {
const res = await instance.get(FOLDERS);
Copy link
Collaborator

Choose a reason for hiding this comment

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

반환 타입을 지정해주는게 어떨까요?

Suggested change
const res = await instance.get(FOLDERS);
const res = await instance.get<GetFoldersResponse>(FOLDERS);

API 도큐먼트를 보고 반환 타입을 지정해주는게 어떨가 싶어서 제안드립니다 =)

const { data } = res.data;
return data;
Comment on lines +9 to +11
Copy link
Collaborator

Choose a reason for hiding this comment

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

다음과 같이 구조분해 할당을 사용할 수 있습니다 =)

Suggested change
const res = await instance.get(FOLDERS);
const { data } = res.data;
return data;
const { data } = await instance.get(FOLDERS);
return data;

} catch (error) {
console.log('ERROR IN SERVER FETCHING DATA: ', error);
Copy link
Collaborator

Choose a reason for hiding this comment

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

console은 생각보다 다양한 메서드가 있어요.

Suggested change
console.log('ERROR IN SERVER FETCHING DATA: ', error);
console.error('ERROR IN SERVER FETCHING DATA: ', error);

console.log 말고도 console.error, console.dir, console.countconsole은 개발자들이 모르는 많은 메써드들이 있어요 !

이번은 실수일 수도 있겠지만 디버깅 할 때 유용하므로 이번 기회에 console에 대해서 mdn 문서를 한 번 보시는 것도 도움이 될 것 같아요. =)
mdn console

return;
}
Comment on lines +12 to +15
Copy link
Collaborator

Choose a reason for hiding this comment

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

이렇게 되면 해당 함수의 반환 타입은 | undefined가 될 것 같아요 =)

적절한 에러 처리가 되지 않았다면, 어떤 에러인지 명시하는게 어떨까요?

Copy link
Collaborator

Choose a reason for hiding this comment

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

(이어서) API 통신부에서 에러가 났을 때 어떻게 대처하면 될까요?

제가 예상하는 문제는 로그인을 하는데 "이미 등록된 아이디임"과 "아이디에 특수문자 들어가면 안됨" 이라는 에러 UI를 출력한다고 생각해봅시다..!

현재 error를 그냥 undefined로 반환해주고 있기에 해당 함수를 사용하는 컴포넌트에서는 분기처리하기 힘들거예요 !

그렇다면 어떻게 할까요?

방법은 다양합니다만, 지금 바로 해볼 수 있는 방법은 throw를 해보는거예요:

Suggested change
} catch (error) {
console.log('ERROR IN SERVER FETCHING DATA: ', error);
return;
}
} catch (error) {
console.error(`ERROR IN SERVER FETCHING DATA: ${error}`); // 통신부에서 처리할 로직 및 로깅
throw(error);
}

위처럼 throw를 해준다면 서버에서 보내준 에러의 메시지를 사용자에게 toast든, 모달이든, 알러트든 보여줄 수 있겠죠?

다음과 같이요 !!:

  // Component
  useEffect(() => {
    try {
      getFolders();
  // 이어서..
    } catch (err) {
      alert(err.message) 
    }
  }, [])

}
export async function getFolderDetailProps(id: string) {
try {
const res = await instance.get(`${FOLDERS}/${id}`);
const { data } = res.data;
return data;
} catch (error) {
console.log('ERROR IN SERVER FETCHING DATA: ', error);
return;
}
}
Comment on lines +17 to +26
Copy link
Collaborator

Choose a reason for hiding this comment

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

그리고.. 각 엔드포인트 별로 함수를 만들어두신 전략 매우 훌륭합니다 👍

이렇게 하면 컴포넌트든, 페이지든, 훅이든 아주 간편하게 API를 호출할 수 있겠네요 :)
공통적인 처리를 핸들링하거나 독립적인 엔드포인트 분기 처리할 때 유용하겠습니다 !


// folder content list
export async function getLinkProps(id?: string) {
try {
let endPoint = id ? `${LINKS}?folderId=${id}` : LINKS;
const res = await instance.get(endPoint);
const { data } = res.data;
return data;
} catch (error) {
console.log('ERROR IN SERVER FETCHING DATA: ', error);
return;
}
}
39 changes: 39 additions & 0 deletions app/(auth)/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
'use client';
import Loading from '@/components/loading/Loading';
import { ACCESS_TOKEN_KEY } from '@/lib/axios';
import { JoinBody, JoinWrap } from '@/styles/loginStyle';
import { useRouter } from 'next/navigation';
import { useEffect, useState } from 'react';

function getCookie(name: string) {
const cookieString = document.cookie;
const cookies = cookieString.split('; ');
for (const cookie of cookies) {
const [cookieName] = cookie.split('=');
if (cookieName === name) {
return true;
}
}
return false;
}

export default function Template({ children }: { children: React.ReactNode }) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

NextJS에는 실제로 template이라는 키워드가 있어서 헷갈릴 수 있을 것 같아요 !

Suggested change
export default function Template({ children }: { children: React.ReactNode }) {
export default function AuthLayout({ children }: { children: React.ReactNode }) {

auth에서 사용하는 layout이므로 위와 같은 네이밍은 어떠세요 ? ☺️

Template

const router = useRouter();
const [isLoading, setIsLoading] = useState(true);

useEffect(() => {
if (getCookie(ACCESS_TOKEN_KEY)) {
router.push('/folder');
return;
}
setIsLoading(false);
}, [isLoading]);

if (isLoading) return <Loading />;

return (
<JoinWrap className='no-header--container signup__wrap'>
<JoinBody>{children}</JoinBody>
</JoinWrap>
);
}
122 changes: 122 additions & 0 deletions app/(auth)/signin/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
'use client';
import AuthLink from '@/components/Auth/AuthLink';
import AuthLogo from '@/components/Auth/AuthLogo';
import AuthRegister from '@/components/Auth/AuthRegister';
import Button from '@/components/common/atoms/Button';
import { ErrorText, FormRowBox, FormWrap } from '@/components/join/formStyle';
import { loginForm } from '@/components/join/interface';
import { AuthContext } from '@/lib/auto.provider';
import { instance } from '@/lib/axios';
import { Relative } from '@/styles/commonStyle';
import Image from 'next/image';
import { useRouter } from 'next/navigation';
import { useContext, useState } from 'react';
import { useForm } from 'react-hook-form';

export default function SignIn() {
const router = useRouter();
const { handleLogin } = useContext(AuthContext);
const [IsVisibility, setIsVisibility] = useState(false);

const {
register,
handleSubmit,
formState: { errors },
setError,
} = useForm<loginForm>({ mode: 'onBlur' });

const handleLoginCheck = async (email: loginForm['email'], password: loginForm['password']) => {
try {
const res = await instance.post('/sign-in', { email, password });
Copy link
Collaborator

Choose a reason for hiding this comment

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

API 함수 잘 만들어두셨는데 여기도 API 함수 따로 선언하셔서 불러와도 될 것 같아요 !

const { data } = res;
if (data) {
handleLogin(data.data.accessToken);
router.push('/folder');
}
} catch {
setError('email', { message: '이메일을 확인해 주세요.' });
setError('password', { message: '비밀번호를 확인해 주세요.' });
return;
}
};

const handleValid = (data: loginForm) => {
const { email, password } = data;
handleLoginCheck(email, password);
};

return (
<>
<AuthLogo />
<AuthLink
desc={'회원이 아니신가요?'}
href={`/signup`}
btnText={'회원 가입하기'}
/>
<FormWrap>
<form onSubmit={handleSubmit(handleValid)}>
<FormRowBox className='input__id'>
<label
htmlFor='input__id-element'
className='input__id-label'>
이메일
</label>
<input
{...register('email', {
required: '이메일을 입력해 주세요.',
pattern: {
value: /^[A-Za-z0-9_\.\-]+@[A-Za-z0-9\-]+\.[A-za-z0-9\-]+/,
message: '올바른 이메일 주소가 아닙니다',
},
})}
type='email'
name='email'
id='input__id-element'
className={errors.email ? 'error' : ''}
/>
<ErrorText className='error__text'>{errors.email?.message}</ErrorText>
</FormRowBox>
<FormRowBox className='input__password'>
<label
htmlFor='input__password-element'
className='input__password-label'>
비밀번호
</label>
<Relative>
<input
{...register('password', {
required: '비밀번호를 입력해 주세요',
pattern: {
value: /^(?=.*[a-zA-Z])(?=.*\d)[a-zA-Z\d]{8,}$/,
message: '비밀번호는 영문, 숫자 조합 8자 이상 입력해 주세요.',
},
})}
type={IsVisibility ? 'text' : 'password'}
name='password'
id='input__password-element'
className={errors.password ? 'error' : ''}
/>
<Button
btnClass={'button--input-password'}
onclick={() => setIsVisibility((prev) => !prev)}>
<Image
src={`/assets/icon/icon-eye-${IsVisibility ? 'on' : 'off'}.svg`}
alt='비밀번호 보기'
width={16}
height={16}
/>
</Button>
</Relative>
<ErrorText className='error__text'>{errors.password?.message}</ErrorText>
</FormRowBox>
<Button
type='submit'
btnClass={`button--gradient large btn_login`}>
로그인
</Button>
</form>
</FormWrap>
<AuthRegister title='소셜 로그인' />
</>
);
}
Loading
Loading