Skip to content

Commit

Permalink
set-up for kakao login
Browse files Browse the repository at this point in the history
  • Loading branch information
w00khyung committed Dec 11, 2023
1 parent 02f5cea commit 9796df8
Show file tree
Hide file tree
Showing 24 changed files with 1,069 additions and 727 deletions.
2 changes: 2 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
NEXT_PUBLIC_SERVER_URL=<NEXT_PUBLIC_SERVER_URL>
NEXT_PUBLIC_APP_ENV=<NEXT_PUBLIC_APP_ENV>
1 change: 1 addition & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,5 +66,6 @@ module.exports = {
],
'react/react-in-jsx-scope': 'off',
'@typescript-eslint/explicit-function-return-type': 'off',
'@typescript-eslint/non-nullable-type-assertion-style': 'off',
},
}
3 changes: 2 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,6 @@
"_utils": "Utils",
"(route)": "Routes"
},
"css.lint.unknownAtRules": "ignore"
"css.lint.unknownAtRules": "ignore",
"cSpell.words": ["KAKAO"]
}
12 changes: 9 additions & 3 deletions app/(route)/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,24 @@ import { Inter } from 'next/font/google'

import type { Metadata } from 'next'
import '@/app/_styles/globals.css'
import Providers from '@/app/_components/providers/QueryClientProvider'

const inter = Inter({ subsets: ['latin'] })

export const metadata: Metadata = {
title: 'Create Next App',
description: 'Generated by create next app',
title: '도달도달',
description: '도달도달',
}

export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang='en'>
<body className={inter.className}>{children}</body>
<head>
<link rel='icon' href='../favicon.png' sizes='any' />
</head>
<body className={inter.className}>
<Providers>{children}</Providers>
</body>
</html>
)
}
40 changes: 40 additions & 0 deletions app/(route)/oauth/callback/kakao/api/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { KAKAO } from '@/app/_constants/kakao'

interface KakaoRequestBody {
grant_type: string
client_id: string
redirect_uri: string
code: string
}

export async function GET(request: Request) {
const { searchParams } = new URL(request.url)
const code = searchParams.get('code')

if (code === null) {
return Response.json({
error: 'code is required',
})
}

const kakaoRequestBody: KakaoRequestBody = {
grant_type: 'authorization_code',
client_id: KAKAO.REST_API_KEY,
redirect_uri: KAKAO.REDIRECT_URI,
code,
}

const data = await fetch(`https://kauth.kakao.com/oauth/token`, {
method: 'POST',
headers: {
Authorization: `469f80f467f98201541aadbfa83e8456`,
'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8',
},

body: Object.keys(kakaoRequestBody)
.map((k) => encodeURIComponent(k) + '=' + encodeURI(kakaoRequestBody[k as keyof KakaoRequestBody]))
.join('&'),
}).then(async (res) => await res.json())

return Response.json(data)
}
45 changes: 45 additions & 0 deletions app/(route)/oauth/callback/kakao/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
'use client'

import { useSearchParams, useRouter } from 'next/navigation'

import { useEffect } from 'react'

import { useQuery } from '@tanstack/react-query'

import { ROUTE } from '@/app/_constants/route'
import { getTokenByAuthorizationCode } from '@/app/_service/auth'

import { QUERY_KEY } from './queries'

export default function KaKaoCallbackPage() {
const router = useRouter()

const searchParams = useSearchParams()
const code = searchParams.get('code')

const { isSuccess, data } = useQuery({
queryKey: QUERY_KEY.KAKAO_TOKEN(code as string),
queryFn: async () =>
await getTokenByAuthorizationCode({
code: code as string,
}),
enabled: code !== null,
})

useEffect(() => {
if (isSuccess) {
router.push(ROUTE.HOME)
}
}, [isSuccess])

if (!isSuccess) {
return null
}

return (
<div>
<span>{code}</span>
<span>{data.data.access_token}</span>
</div>
)
}
3 changes: 3 additions & 0 deletions app/(route)/oauth/callback/kakao/queries.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export const QUERY_KEY = {
KAKAO_TOKEN: (code: string) => ['kakaoToken', code],
}
14 changes: 13 additions & 1 deletion app/(route)/page.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,15 @@
import { KAKAO } from '../_constants/kakao'

export default function Home() {
return <main>Hello, World!</main>
return (
<main>
<span>Hello, World!</span>
<a
className='rounded-md bg-yellow-500 p-4 text-white'
href={`https://kauth.kakao.com/oauth/authorize?response_type=code&client_id=${KAKAO.REST_API_KEY}&redirect_uri=${KAKAO.REDIRECT_URI}`}
>
카카오 로그인
</a>
</main>
)
}
Empty file removed app/_components/.gitkeep
Empty file.
21 changes: 21 additions & 0 deletions app/_components/providers/QueryClientProvider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
'use client'

import { PropsWithChildren, useState } from 'react'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'

export default function Providers({ children }: PropsWithChildren) {
const [queryClient] = useState(
() =>
new QueryClient({
defaultOptions: {
queries: {
// With SSR, we usually want to set some default staleTime
// above 0 to avoid refetching immediately on the client
staleTime: 60 * 1000,
},
},
})
)

return <QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
}
Empty file removed app/_constants/.gitkeep
Empty file.
16 changes: 16 additions & 0 deletions app/_constants/env.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
export const SERVER_URL = process.env.NEXT_PUBLIC_SERVER_URL

export type AppEnv = 'production' | 'development'

const getAppEnv = (): AppEnv => (process.env.NEXT_PUBLIC_APP_ENV as Exclude<AppEnv, 'development'>) || 'development'

export const getApiEndpoint = () => {
switch (getAppEnv()) {
case 'production':
return SERVER_URL
case 'development':
return SERVER_URL
default:
return SERVER_URL
}
}
5 changes: 5 additions & 0 deletions app/_constants/kakao.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export const KAKAO = {
// REST_API_KEY: '8cb6bb4a47d00e99bb5a038f9b6467fe',
REST_API_KEY: 'df0059498e461a8b0c76d9b4d537dbaa',
REDIRECT_URI: 'http://localhost:3000/oauth/callback/kakao',
}
10 changes: 10 additions & 0 deletions app/_constants/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
export const ROUTE = {
HOME: '/',
LOGIN: '/login',
SIGNUP: '/signup',
OAUTH: {
KAKAO: {
CALLBACK: '/oauth/callback/kakao',
},
},
}
Empty file removed app/_service/.gitkeep
Empty file.
11 changes: 11 additions & 0 deletions app/_service/auth/auth.types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export interface GetTokenFromKakaoParams {
code: string
}

export interface GetTokenFromKakaoResponse {
access_token: string
expires_in: number
refresh_token: string
refresh_token_expires_in: number
token_type: string
}
6 changes: 6 additions & 0 deletions app/_service/auth/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import axios from 'axios'
import { GetTokenFromKakaoParams, GetTokenFromKakaoResponse } from './auth.types'

export const getTokenByAuthorizationCode = ({ code }: GetTokenFromKakaoParams) => {
return axios.get<GetTokenFromKakaoResponse>(`/oauth/callback/kakao/api?code=${code}`)
}
33 changes: 33 additions & 0 deletions app/_service/core/api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import axios, { type AxiosRequestConfig } from 'axios'

import { getApiEndpoint } from './api.utils'
import type { ServerResponse } from './api.types'

const createApi = () => {
const _api = axios.create({
baseURL: getApiEndpoint(),
withCredentials: true,
headers: {
'Content-Type': 'application/json',
},
})

return _api
}

export const axiosInstance = createApi()

const api = {
get: <T>(url: string, config?: AxiosRequestConfig) =>
axiosInstance.get<ServerResponse<T>>(url, config).then((res) => res.data),
post: <T>(url: string, payload?: object, config?: AxiosRequestConfig) =>
axiosInstance.post<ServerResponse<T>>(url, payload, config).then((res) => res.data),
put: <T>(url: string, payload?: object, config?: AxiosRequestConfig) =>
axiosInstance.put<ServerResponse<T>>(url, payload, config).then((res) => res.data),
patch: <T>(url: string, payload?: object, config?: AxiosRequestConfig) =>
axiosInstance.patch<ServerResponse<T>>(url, payload, config).then((res) => res.data),
delete: <T>(url: string, config?: AxiosRequestConfig) =>
axiosInstance.delete<ServerResponse<T>>(url, config).then((res) => res.data),
}

export default api
11 changes: 11 additions & 0 deletions app/_service/core/api.types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export interface ServerResponse<T> {
statusCode: number
responseMessage: string
data: T
}

export interface ServerError {
responseMessage: string
data: boolean
statusCode: number
}
16 changes: 16 additions & 0 deletions app/_service/core/api.utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
export const SERVER_URL = process.env.NEXT_PUBLIC_SERVER_URL as string

export type AppEnv = 'production' | 'development'

const getAppEnv = (): AppEnv => (process.env.NEXT_PUBLIC_APP_ENV as Exclude<AppEnv, 'development'>) || 'development'

export const getApiEndpoint = () => {
switch (getAppEnv()) {
case 'production':
return SERVER_URL
case 'development':
return SERVER_URL
default:
return SERVER_URL
}
}
1 change: 1 addition & 0 deletions app/api/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export async function GET(request: Request) {}
Binary file modified app/favicon.ico
Binary file not shown.
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,13 @@
},
"lint-staged": {
"*.{js,jsx,ts,tsx}": [
"prettier --write",
"eslint --fix"
"prettier --write"
]
},
"dependencies": {
"clsx": "^2.0.0",
"@tanstack/react-query": "^5.13.4",
"axios": "^1.6.2",
"next": "14.0.4",
"react": "^18",
"react-dom": "^18",
Expand Down
Loading

0 comments on commit 9796df8

Please sign in to comment.