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

[TASK-30] feat: 로그인/회원가입 페이지 구현 #11

Merged
merged 22 commits into from
Dec 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
e5ec060
setting : 외부 패키지 추가 ( axios, react-hook-form )
SangWoo9734 Dec 20, 2024
8c8d1d4
style : 페이지 콘텐츠 영역 크기 설정
SangWoo9734 Dec 20, 2024
061b13c
style : default value가 있는 property를 옵셔널로 관리
SangWoo9734 Dec 20, 2024
3c668b7
feat : API route 관련 상수 추가
SangWoo9734 Dec 20, 2024
ee39927
feat : Kakao SDK 활용을 위한 Provider 추가
SangWoo9734 Dec 20, 2024
c28cde9
feat : 회원가입 페이지 구현
SangWoo9734 Dec 20, 2024
370320a
feat : 일반 회원가입 API 로직 추가
SangWoo9734 Dec 20, 2024
097b907
feat : 이메일 검증 API 로직 추가
SangWoo9734 Dec 20, 2024
c307199
feat : 닉네임 검증 API 로직 추가
SangWoo9734 Dec 20, 2024
9005a42
feat: Social Login 영역 컴포넌트 추가
SangWoo9734 Dec 20, 2024
f86bc2c
feat: Social Login 시 사용되는 redirect 페이지 추가
SangWoo9734 Dec 20, 2024
0a2dc8a
feat: Social Login 서버 API 로직 추가
SangWoo9734 Dec 20, 2024
dfbfcff
feat: 로그인 페이지 추가
SangWoo9734 Dec 20, 2024
8f44f34
feat: 로그인 API 로직 추가
SangWoo9734 Dec 20, 2024
0772ac6
feat: 로그인/회원가입 Layout 추가
SangWoo9734 Dec 20, 2024
106a1d3
feat: MSW Mocking API 추가
SangWoo9734 Dec 20, 2024
358b655
feat: 인증 및 API 응답 관련 타입 추가
SangWoo9734 Dec 20, 2024
a83bee1
feat: auth 관련 query에 enable 옵션 추가
SangWoo9734 Dec 20, 2024
8d0b4f3
feat: react-hook-form 사용을 위한 EmailInput 컴포넌트 개선
SangWoo9734 Dec 20, 2024
c59ceb7
feat: react-hook-form 사용을 위한 NicknameInput 컴포넌트 개선
SangWoo9734 Dec 20, 2024
43b9078
feat: react-hook-form 사용을 위한 PasswordInput 컴포넌트 개선
SangWoo9734 Dec 20, 2024
ee0e66e
Merge branch 'dev' into feature/auth
SangWoo9734 Dec 25, 2024
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
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,13 @@
"dependencies": {
"@nextui-org/react": "^2.6.8",
"@tanstack/react-query": "^5.62.3",
"axios": "^1.7.9",
"framer-motion": "^11.14.4",
"next": "15.0.4",
"next-themes": "^0.4.4",
"react": "19.0.0",
"react-dom": "19.0.0",
"react-hook-form": "^7.54.1",
"zustand": "^5.0.2"
},
"devDependencies": {
Expand Down
2,041 changes: 1,074 additions & 967 deletions pnpm-lock.yaml

Large diffs are not rendered by default.

9 changes: 9 additions & 0 deletions public/images/google.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
9 changes: 9 additions & 0 deletions public/images/kakako.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Empty file removed src/apis/.gitkeep
Empty file.
26 changes: 26 additions & 0 deletions src/apis/user/login.ts
Copy link
Member

Choose a reason for hiding this comment

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

로그인 관련 파일과 코드는 파일명과 함수명을 'login' 으로 작성하고, 회원가입은 'sign up' 으로 나누신 이유가 따로 있을까요?

로그인은 sign in, 회원가입은 sign up으로 이름 일관성을 맞추는 게 좋을 것 같아서요 :)

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

처음에 signin, signup으로 진행했었는데, 백엔드 API 쪽이 login / sign up을 활용하고 있는 걸 확인하고 통일하기 위해 그렇게 네이밍을 변경했었습니다. front 쪽은 sign in / sing up으로 통일하겠습니다.

Copy link
Member

Choose a reason for hiding this comment

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

아하 그러셨군요
백엔드와도 네이밍 컨벤션을 동일하게 해야 하는 부분이 있네요. sign in 통일 확인했습니다!

Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { USER } from '@/constants/API';
import { useMutation } from '@tanstack/react-query';
import { useRouter } from 'next/navigation';
Copy link
Member

Choose a reason for hiding this comment

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

import 를 한 줄씩 띄우는 게 좋을 것 같은데 어떻게 생각하실까요?

해당 파일에선 같은 import 분류가 없으니 각 import 를 한 줄씩 띄워야 할 것 같아요!
지금은 import 가 3줄뿐이라 띄우기 애매하다고 생각드실 것 같은데, import 라인이 '많다/적다'의 기준을 나눠서 띄우는 것보단 모든 파일에 동일하게 적용하는 게 코드의 일관성 측면에서 좋을 것 같다는 생각이 들어요.

상우님은 어떻게 생각하실까요? 🤔


import defaultClient from '..';

const postLogin = async (formData: LoginFormType) => {
const { data } = await defaultClient.post<ResponseRootType<DefaultAuthType>>(
USER.login,
formData,
);

return data;
};

const usePostLogin = () => {
const router = useRouter();

return useMutation({
mutationKey: [USER.login],
mutationFn: (formData: LoginFormType) => postLogin(formData),
onSuccess: () => router.push('/'),
});
};

export default usePostLogin;
25 changes: 25 additions & 0 deletions src/apis/user/signUp.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { USER } from '@/constants/API';
import { useMutation } from '@tanstack/react-query';
import { useRouter } from 'next/navigation';
import defaultClient from '..';

const postSignUp = async (formData: SignUpFormType) => {
const { data } = await defaultClient.post<ResponseRootType<DefaultAuthType>>(
USER.signup,
formData,
);

return data;
};

const usePostSignUp = () => {
const router = useRouter();

return useMutation({
mutationKey: [USER.signup],
mutationFn: (formData: SignUpFormType) => postSignUp(formData),
onSuccess: () => router.push('/'),
});
};

export default usePostSignUp;
40 changes: 40 additions & 0 deletions src/apis/user/socialLogIn.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { USER } from '@/constants/API';
import { useQuery } from '@tanstack/react-query';
import defaultClient from '..';

const getSocialLogin = async (queryUrl: string) => {
const { data } =
await defaultClient.get<ResponseRootType<SocialLoginAuthType>>(queryUrl);

return data;
};
Copy link
Member

Choose a reason for hiding this comment

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

현재 getSocialLogin 함수 내에 에러 처리가 없는 상태인데, 소셜 로그인 데이터를 못 가져왔을 경우의 에러 처리를 추가해야 하지 않을까요?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

백엔드 구현이 완료된 이후에 작성되어있는 버전의 api 문서로 구체적인 에러케이스를 처리하는 것이 낫지 않을까 하는 생각에 우선 성궁 케이스에 대해서만 고려해두었습니다..! 에러 핸들링까지 추가해두겠습니다!

Copy link
Member

Choose a reason for hiding this comment

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

아아 그렇군요. 반영 감사해요! 이와 관련해서 백엔드 측과 얘기를 해봐야겠네요


const useGetSocialLogin = ({
provider,
params,
}: {
provider: string;
params: string;
}) => {
const queryUrl = `${USER.socialLogin}/${provider}?${params}`;
Copy link
Member

Choose a reason for hiding this comment

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

아래 코드와의 한 줄을 띄우고, queryUrlsocialLoginUrl 처럼 URL의 구체적인 목적을 나타내는 이름으로 변경하는 건 어떨까요?

const { data, isLoading } = useQuery({
queryKey: [`${USER.socialLogin}/${provider}`],
queryFn: () => getSocialLogin(queryUrl),
});

const hasData = !!data;
Copy link
Member

Choose a reason for hiding this comment

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

데이터의 유무를 파악하기 위한 변수다 보니 '어떤 데이터의 유무를 파악하기 위함'을 내포하는 이름으로 명확히 작명하면 더 좋을 것 같아요! 예를 들면 isSocialLoginDataAvailable 같은? 🤔

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

이부분에서는 다른 의견이 있습니다! 말씀해주신 피드백에 대해서 고민을 해봤을 때 현재 hasData의 용도가 단순히 데이터의 유무에 초점이 맞춰져 있고, 데이터의 종류에는 관계가 없다보니 어떤 데이터에 대한 정보가 네이밍에 포함되지 않아도 될 것 같다고 생각합니다!

만약 data 내부에 특정 값이 있는지 없는지를 확인한다면 말씀해주신 구체적인 네이밍이 더 효과적이라 생각합니다!

Copy link
Member

Choose a reason for hiding this comment

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

아아 data 자체가 어떤 데이터인지 (e.g. SocialLoginData)가 명시돼있지 않기 때문에 가독성 측면에서 제안드린 거였어요!

변수명이 명확하지 않으면 코드를 읽는 내내 파일명에 의존해서 상기하며 읽어야 되더라구요. 물론 지금은 한 파일 내에 코드가 많지 않기도 하고, 작은 프로젝트다 보니 크게 유의미하지 않을지 모르지만 유지보수나 프로젝트 디벨롭 측면을 염두에 두고 제안드렸어요! 상우님이 편하신 대로 해도 됩니다 ㅎㅎ


const authorizedRes: SocialLoginAuthType = hasData
Copy link
Member

Choose a reason for hiding this comment

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

Response 는 안 줄이는 게 좋지 않을까요??
Result의 약자로 자주 사용하는 분들도 있고 해서 철자 그대로 작성하는 게 혼란 방지에 좋을 것 같아요 :)

? data.value
: {
isRegistered: false,
token: {
accessToken: '',
refreshToken: '',
},
};

return { hasData, authorizedRes, isLoading };
};

export default useGetSocialLogin;
39 changes: 39 additions & 0 deletions src/apis/user/validateEmail.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { USER } from '@/constants/API';
import { useQuery } from '@tanstack/react-query';
import defaultClient from '..';

const getValidateEmail = async (email: string): Promise<ValidateResponse> => {
const { data } = await defaultClient.get<ResponseRootType<null>>(
USER.validateEmail,
{
params: {
email,
},
},
);

const { code, message } = data;

return {
isValid: code === 204,
message,
};
};

const useGetValidateEmail = (email: string, mode: string) => {
const { data, isLoading } = useQuery({
queryKey: [USER.validateEmail, email],
queryFn: () => getValidateEmail(email),
enabled: email.trim() !== '' && mode === 'sign-up',
});

const hasData = !!data;

const validInfo = hasData
? { isValid: data.isValid, message: data.message }
: { isValid: false, message: '' };

return { isLoading, ...validInfo };
};

export default useGetValidateEmail;
39 changes: 39 additions & 0 deletions src/apis/user/validateNickname.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { USER } from '@/constants/API';
import { useQuery } from '@tanstack/react-query';
import defaultClient from '..';

const getValidateNickname = async (
nickname: string,
): Promise<ValidateResponse> => {
const { data } = await defaultClient.get<ResponseRootType<null>>(
USER.validateNickname,
{
params: {
nickname,
},
},
);

return {
isValid: data.code === 204,
message: data.message,
};
};

const useGetValidateNickName = (nickName: string) => {
const { data, isLoading } = useQuery({
queryKey: [USER.validateNickname, nickName],
queryFn: () => getValidateNickname(nickName),
enabled: nickName.trim() !== '',
});

const hasData = !!data;

const validInfo = hasData
? { isValid: data.isValid, message: data.message }
: { isValid: false, message: '' };

return { isLoading, ...validInfo };
Copy link
Member

Choose a reason for hiding this comment

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

nickNamenickname 으로 변경 부탁드려용
그리고 validInfo 도 '정보' 보다는 '결과'가 좀 더 네이밍에 걸맞지 않을까 싶은데, 어떻게 생각하실까요?

};

export default useGetValidateNickName;
17 changes: 17 additions & 0 deletions src/app/auth/(authWithLayout)/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import Image from 'next/image';
import { ReactNode } from 'react';

import logo from '@/../public/images/momentiaLogoSymbol.png';

interface LayoutProps {
readonly children: ReactNode;
}
Copy link
Member

Choose a reason for hiding this comment

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

children 하나만 Prop으로 사용되고 있는데, 따로 선언하신 이유가 있을까요?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

다른 컴포넌트와 형식을 통일시키는 의도로 따로 뺐었습니다! 다현님께서는 보통 몇 개 이상 props가 있으면 분리하시나요?

Copy link
Member

@dahyeo-n dahyeo-n Dec 23, 2024

Choose a reason for hiding this comment

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

그러셨군요
저는 Omit 처럼 1개의 prop에 2개 이상의 타입을 사용하거나 3개 이상이면 따로 빼는 편이에요! 상우님의 다른 코드에서는 2개일 때도 따로 빼지 않으신 걸 발견해서 다른 의도가 있어서 빼신 건지 궁금했어요 ㅎㅎ

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

아 그랬었군요;; 2개 이상인 경우 Props 타입을 별도로 정의 해서 사용하겠습니다!

Copy link
Member

Choose a reason for hiding this comment

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

땀 ㅋㅋㅋㅋㅋ 그럼 2개 이상이 2개 포함인 걸까요? 저도 타입 정의할 때 맞추려구요

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

2개부터 분리하는걸로 하시죠!

Copy link
Member

Choose a reason for hiding this comment

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

헉 그럼 지금 prop 타입 2개 이상인데 분리 안 한 게 많아서 지금부터 분리 적용하되, 이전에 한 건 추후에 분리해도 될까요?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

이거는 추후에 분리하시죠..!

Copy link
Member

Choose a reason for hiding this comment

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

ㅋㅋㅋㅋㅋㅋㅋ 넵넵


export default function layout({ children }: LayoutProps) {
return (
<div className='m-auto h-full w-[420px] flex flex-col justify-center items-center gap-[25px]'>
<Image src={logo} alt='모멘티아 로고' width={45} priority />
{children}
</div>
);
}
64 changes: 64 additions & 0 deletions src/app/auth/(authWithLayout)/login/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
'use client';

import usePostLogin from '@/apis/user/login';
import SquareButtonL from '@/components/Button/SquareButtonL';
import EmailInput from '@/components/Input/EmailInput';
import PasswordInput from '@/components/Input/PasswordInput';
import SocialLoginSection from '@/components/SocialLoginSection';

import Link from 'next/link';

import { FormProvider, useForm } from 'react-hook-form';

export default function LogInPage() {
const { mutate } = usePostLogin();

const methods = useForm<LoginFormType>({
defaultValues: {
email: '',
password: '',
},
});

const onValid = (data: LoginFormType) => {
console.log(data);
mutate(data);
};

const allFieldsFilled = Object.values(methods.watch()).every(
(value) => value !== '',
);

return (
<div className='flex flex-col justify-center items-center gap-[60px]'>
<h4>로그인</h4>

<FormProvider {...methods}>
<form
name='login-form'
onSubmit={methods.handleSubmit(onValid)}
className='w-full flex flex-col gap-[60px]'
>
<div className='flex flex-col gap-[30px]'>
<EmailInput mode={'sign-in'} />
<PasswordInput mode={'sign-in'} />
</div>
<SquareButtonL
type='submit'
textSize={''}
backgroundColor={allFieldsFilled ? 'bg-main' : 'bg-gray-800'}
>
<p>로그인</p>
</SquareButtonL>
</form>
</FormProvider>
<div className='w-full flex flex-col gap-[30px] justify-center text-center'>
<SocialLoginSection />
<div className='flex gap-2.5 justify-center items-center mt-[13px]'>
<p className='text-gray-600'>아직 회원이 아니신가요?</p>
<Link href='/auth/signup'>회원가입하기</Link>
</div>
</div>
</div>
);
}
59 changes: 59 additions & 0 deletions src/app/auth/(authWithLayout)/signup/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
'use client';

import usePostSignUp from '@/apis/user/signUp';
Copy link
Member

Choose a reason for hiding this comment

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

@/apis@/components 간의 import 띄어쓰기가 필요할 듯해요 :)

import SquareButtonL from '@/components/Button/SquareButtonL';
import EmailInput from '@/components/Input/EmailInput';
import NicknameInput from '@/components/Input/NicknameInput';
import PasswordInput from '@/components/Input/PasswordInput';

import Link from 'next/link';
import { FormProvider, useForm } from 'react-hook-form';

export default function SignUpPage() {
const { mutate } = usePostSignUp();
const methods = useForm<SignUpFormType>({
defaultValues: {
email: '',
password: '',
nickname: '',
},
mode: 'onChange',
});

const onValidForm = (data: SignUpFormType) => {
mutate(data);
};

const { isValid } = methods.formState;
Copy link
Member

Choose a reason for hiding this comment

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

methods, isValid, mutate, data 의 변수명을 의도를 명확히 표현하는 이름으로 변경하는 건 어떨까요?

물론 해당 파일 내에서 methods, isValid, mutate, data 모두 1개씩이긴 하지만 명확한 변수명을 가져가는 습관을 갖는 게 추후 다시 코드를 읽고, 수정할 때 들 시간 절감에 좋을 것 같습니다 :)


return (
<div className='flex flex-col justify-center items-center gap-[60px]'>
<h4>회원가입</h4>
<FormProvider {...methods}>
<form
onSubmit={methods.handleSubmit(onValidForm)}
className='w-full flex flex-col gap-[60px]'
>
<div className='flex flex-col gap-[30px]'>
<EmailInput mode={'sign-up'} />
<PasswordInput mode={'sign-up'} />
<NicknameInput />
</div>
<SquareButtonL
type='submit'
textSize={''}
Copy link
Member

Choose a reason for hiding this comment

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

타이포그래피가 들어가면 되는 prop인데 빈칸으로 남겨두신 이유가 있을까요?
엇 혹시 해당 prop의 네이밍이 직관적이지 않았을까요??

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

해당 부분은 default 사이즈와 같은 크기인 것 같아서 비워두었습니다! default 옵션으로 body1을 설정해두고 그 외의 경우만 옵션을 추가하도록 수정하겠습니다.

Copy link
Member

Choose a reason for hiding this comment

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

아 그렇군요. 네 알겠습니당

backgroundColor={isValid ? 'bg-main' : 'bg-gray-800'}
disabled={!isValid}
>
<p>회원가입</p>
</SquareButtonL>
</form>
</FormProvider>

<div className='flex gap-2.5 justify-center items-center mt-[13px]'>
<p className='text-gray-600'>이미 가입된 계정이 있으신가요?</p>
<Link href='/auth/login'>로그인하러가기</Link>
</div>
</div>
);
}
27 changes: 27 additions & 0 deletions src/app/auth/redirect/[provider]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
'use client';

import useGetSocialLogin from '@/apis/user/socialLogIn';

import { useRouter, useSearchParams } from 'next/navigation';
import { useEffect } from 'react';

interface ProviderPageProps {
provider: string;
}
Copy link
Member

Choose a reason for hiding this comment

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

ProviderPageProps 도 하나인데 따로 선언해주신 이유가 있을까요?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

이것도 동일하게 다른 컴포넌트와 형식을 통일시키는 의도로 따로 뺐었습니다!


function ProviderPage({ provider }: ProviderPageProps) {
const router = useRouter();
const searchParams = useSearchParams();
const { hasData, isLoading } = useGetSocialLogin({
provider,
params: searchParams.toString(),
});

useEffect(() => {
if (hasData) router.replace('/');
}, [hasData, isLoading]);
Copy link
Member

Choose a reason for hiding this comment

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

hasData 값의 변경에 이미 의존하고 있는데 isLoading 도 의존성 배열에 넣으신 이유가 있을까요?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

hasData만 의존성 배열에 있을때 리다이렉트가 잘 안되던 현상이 있어서 isLoading을 함께 넣었던 거였는데 빼고 테스트 해보니 문제가 없군요;; 제가 착각한 부분이었던 것 같습니다!


return <></>;
Copy link
Member

Choose a reason for hiding this comment

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

빈 JSX보다는, 로딩 상태를 사용자에게 명확히 표시하기 위해 로딩 JSX를 추가하는 건 어떨까요?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

빈 페이지 보다 로딩요소가 보이는 게 좋을 것 같다는 점은 찬성입니다. 이부분은 다른 리뷰까지 확인 후에 예림님께 요청드려서 Loading UI를 추가하겠습니다.

Copy link
Member

Choose a reason for hiding this comment

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

네 감사해요!

}

export default ProviderPage;
Copy link
Member

Choose a reason for hiding this comment

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

ProviderPage 는 이름만으론 어떤 역할을 하는지 명확히 알 수가 없다는 생각이 듭니다.
소셜 로그인의 리다이렉션을 처리하는 페이지명에 걸맞게 해당 컴포넌트 이름을 바꿔보는 건 어떨까요?

2 changes: 2 additions & 0 deletions src/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { ReactNode } from 'react';

import '../styles/globals.css';

import KakaoProvider from './providers/KakaoProvider';
import MSWProvider from './providers/MSWProvider';
import ReactQueryProvider from './providers/ReactQueryProvider';

Expand All @@ -28,6 +29,7 @@ const RootLayout = ({
<AppShell>{children}</AppShell>
</ReactQueryProvider>
</MSWProvider>
<KakaoProvider />
</body>
</html>
);
Expand Down
24 changes: 24 additions & 0 deletions src/app/providers/KakaoProvider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
'use client';

import Script from 'next/script';

declare global {
interface Window {
Kakao: any;
Copy link
Member

Choose a reason for hiding this comment

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

Kakao정확한 타입 명시 부탁드려요!

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Kakao 인스턴스에 대한 타입 정의가 카카오 측에서 제공되고 있지 않은 걸로 확인됩니다..! npm에 임의로 타입 정의를 제공하는 라이브러리가 있긴 한데, 이걸 활용하거나, 제가 필요한 함수만 타입 정의를 하거나 해야할 것 같습니다..! 이 부분은 조금더 확인해보고 타입을 활용할 수 있도록 수정하겠습니다.

Copy link
Member

Choose a reason for hiding this comment

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

네 알겠습니다 :)

}
}

export default function KakaoProvider() {
const initKakao = () => {
window.Kakao.init(process.env.NEXT_PUBLIC_KAKAO_API_KEY);
};

return (
<Script
src='https://t1.kakaocdn.net/kakao_js_sdk/2.7.4/kakao.min.js'
integrity='sha384-DKYJZ8NLiK8MN4/C5P2dtSmLQ4KwPaoqAfyA/DfmEc1VDxu4yyC7wy6K1Hs90nka'
crossOrigin='anonymous'
onLoad={initKakao}
/>
);
}
Loading
Loading