Skip to content

Commit

Permalink
Merge pull request #67 from dnd-side-project/feature/66-kakao-login-api
Browse files Browse the repository at this point in the history
Feature : kakao login api 구현
  • Loading branch information
seondal committed Nov 7, 2023
2 parents ce939ed + 238f345 commit 794dcc5
Show file tree
Hide file tree
Showing 11 changed files with 188 additions and 8 deletions.
Binary file added public/images/kakao-login.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions src/apis/apis.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
PoseFeedResponse,
PosePickResponse,
PoseTalkResponse,
RegisterResponse,
} from '.';
import publicApi from './config/publicApi';

Expand Down Expand Up @@ -31,3 +32,6 @@ export const getPoseFeed = async (
});

export const getFilterTag = () => publicApi.get<FilterTagsResponse>('/pose/tags');

export const getRegister = (code: string) =>
publicApi.get<RegisterResponse>(`/users/login/oauth/kakao?code=${code}`);
30 changes: 30 additions & 0 deletions src/apis/config/privateApi.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import axios, { AxiosError, InternalAxiosRequestConfig } from 'axios';

import { BASE_API_URL } from '@/constants';
import { getCookie } from '@/utils/cookieController';

import type { CustomInstance } from './type';

const privateApi: CustomInstance = axios.create({
baseURL: `${BASE_API_URL}/api`,
withCredentials: true,
});

privateApi.interceptors.request.use(
async (config: InternalAxiosRequestConfig) => {
try {
const accessToken = getCookie('accessToken');
config.headers.Authorization = `Bearer ${accessToken}`;
return config;
} catch (error) {
return Promise.reject(error);
}
},
(error: AxiosError) => {
Promise.reject(error);
}
);

privateApi.interceptors.response.use((response) => response.data);

export default privateApi;
5 changes: 5 additions & 0 deletions src/apis/queries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,13 @@ import {
PoseFeedResponse,
PosePickResponse,
PoseTalkResponse,
RegisterResponse,
getFilterTag,
getPoseDetail,
getPoseFeed,
getPosePick,
getPoseTalk,
getRegister,
} from '.';
import { FilterState } from '@/hooks/useFilterState';

Expand Down Expand Up @@ -50,3 +52,6 @@ export const usePoseFeedQuery = (

export const useFilterTagQuery = (options?: UseQueryOptions<FilterTagsResponse>) =>
useSuspenseQuery<FilterTagsResponse>(['filterTag'], getFilterTag, { ...options });

export const useRegisterQuery = (code: string) =>
useSuspenseQuery<RegisterResponse>(['register'], () => getRegister(code), {});
14 changes: 14 additions & 0 deletions src/apis/type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,3 +78,17 @@ export interface PoseTalkResponse {
wordId: number;
};
}

export interface Token {
accessToken: string;
refreshToken: string;
grantType: string;
expiresIn: number;
}

export interface RegisterResponse {
id: number;
nickname: string;
email: string;
token: Token;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
'use client';

import { useRouter } from 'next/navigation';

import { useRegisterQuery } from '@/apis';
import { Loading } from '@/components/Loading';
import { setCookie } from '@/utils/cookieController';

interface LoginSectionProps {
code: string;
}
export default function LoginSection({ code }: LoginSectionProps) {
const router = useRouter();
const { data } = useRegisterQuery(code);
console.log(data);
const { token, email, nickname } = data;
const { accessToken, refreshToken, expiresIn } = token;
setCookie('accessToken', accessToken);
setCookie('refreshToken', refreshToken);
router.replace('/menu');

return <Loading />;
}
25 changes: 25 additions & 0 deletions src/app/(Sub)/api/users/login/oauth/kakao/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { QueryAsyncBoundary } from '@suspensive/react-query';

import LoginSection from './components/LoginSection';
import { getRegister } from '@/apis';
import { RejectedFallback } from '@/components/ErrorBoundary';
import { Loading } from '@/components/Loading';
import { HydrationProvider } from '@/components/Provider/HydrationProvider';

interface PageProps {
searchParams: {
code: string;
};
}

export default function Page({ searchParams }: PageProps) {
const { code } = searchParams;

return (
<QueryAsyncBoundary rejectedFallback={RejectedFallback} pendingFallback={<Loading />}>
<HydrationProvider queryKey={['register']} queryFn={() => getRegister(code)}>
<LoginSection code={code} />
</HydrationProvider>
</QueryAsyncBoundary>
);
}
38 changes: 38 additions & 0 deletions src/app/(Sub)/menu/components/LoginModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import Image from 'next/image';

import { Modal } from '@/components/Modal';
import { Spacing } from '@/components/Spacing';

interface LoginModalProps {
onClose: () => void;
}
export default function LoginModal({ onClose }: LoginModalProps) {
const link = `https://kauth.kakao.com/oauth/authorize?client_id=${process.env.NEXT_PUBLIC_KAKAO_SERVER_KEY}&redirect_uri=${process.env.NEXT_PUBLIC_SITE_URL}/api/users/login/oauth/kakao&response_type=code`;

const handleLogin = () => {
window.location.href = link;
};

return (
<Modal onCloseOutside={onClose}>
<Spacing size={32} />
<h4>간편 로그인</h4>
<Spacing size={8} />
<p>
로그인해야 북마크를 쓸 수 있어요.
<br />
카카오로 3초만에 가입할 수 있어요!
</p>
<Spacing size={16} />
<Image
src="/images/kakao-login.png"
width={268}
height={54}
alt="kakao-login"
className="cursor-pointer"
onClick={handleLogin}
/>
<Spacing size={32} />
</Modal>
);
}
13 changes: 6 additions & 7 deletions src/app/(Sub)/menu/components/LoginSection.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
'use client';

import LoginModal from './LoginModal';
import { Icon } from '@/components/Icon';
import { PreparingModal } from '@/components/Modal';
import { useOverlay } from '@/components/Overlay/useOverlay';
import { Spacing } from '@/components/Spacing';

Expand All @@ -14,17 +14,16 @@ function DefaultProfile() {
}

export default function LoginSection() {
const { open } = useOverlay();
const { open, exit } = useOverlay();

return (
<section className="py-12">
<div
className="bg-violet flex items-center rounded-16 bg-main-violet-bright px-20 py-24"
onClick={() => open(({ exit }) => <PreparingModal onClose={exit} />)}
>
<div className="bg-violet flex items-center rounded-16 bg-main-violet-bright px-20 py-24">
<DefaultProfile />
<Spacing size={16} direction="horizontal" />
<div id="subtitle-1">로그인하기</div>
<div id="subtitle-1" onClick={() => open(() => <LoginModal onClose={exit} />)}>
로그인하기
</div>
</div>
</section>
);
Expand Down
1 change: 0 additions & 1 deletion src/app/(Sub)/menu/components/MakerSection.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
'use client';
import Image from 'next/image';

import BottomFixedDiv from '@/components/BottomFixedDiv';
import { Icon } from '@/components/Icon';
Expand Down
43 changes: 43 additions & 0 deletions src/utils/cookieController.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
const isServer = typeof window === 'undefined';

export const setCookie = async (key: string, value: string, options?: { expires?: Date }) => {
// 서버 측 쿠키
if (isServer) {
const { cookies } = await import('next/headers');
cookies().set(key, value, {
expires: options?.expires,
});
return;
}

// 클라이언트 측 쿠키
document.cookie = `${key}=${value}; path=/; ${
options?.expires ? `expires=${options.expires.toUTCString()}` : ''
}`;
};

export const getCookie = async (key: string) => {
// 서버측 쿠키
if (isServer) {
const { cookies } = await import('next/headers');
return cookies().get(key);
}

// 클라이언트 측 쿠키
const value = `; ${document.cookie}`;
const parts = value.split(`; ${key}=`);

return parts.pop()?.split(';').shift();
};

export const removeCookie = async (key: string) => {
// 서버측 쿠키
if (isServer) {
const { cookies } = await import('next/headers');
cookies().set(key, '');
return;
}

// 클라이언트 측 쿠키
document.cookie = `${key}=; expires=Thu, 01 Jan 1999 00:00:10 GMT;`;
};

0 comments on commit 794dcc5

Please sign in to comment.