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

Feat/kakao login #134

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
Open
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
7 changes: 6 additions & 1 deletion packages/santa_close_frame/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,16 @@
"react": "^18.1.0",
"react-dom": "^18.1.0",
"recoil": "^0.7.2",
"@karrotframe/navigator": "^0.24.2"
"@karrotframe/navigator": "^0.24.2",
"urql": "^2.2.3",
"graphql": "^16.5.0",
"graphql-tag": "^2.12.6",
"react-router-dom": "^6.3.0"
},
"devDependencies": {
"@types/react": "^18.0.8",
"@types/react-dom": "^18.0.3",
"@types/kakao-js-sdk": "^1.39.1",
"@babel/core": "^7.17.10",
"@babel/preset-react": "^7.16.7",
"@babel/preset-env": "^7.17.10",
Expand Down
38 changes: 22 additions & 16 deletions packages/santa_close_frame/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,26 +1,21 @@
import React, {ChangeEvent} from 'react'
import {Navigator, Screen} from '@karrotframe/navigator'
import {RecoilRoot, useRecoilState} from 'recoil'
import {sampleState} from 'map_app/atoms'
import {UrqlProvider} from 'santa_close_common'
import {BrowserRouter, Routes, Route} from 'react-router-dom'
import Login from './pages/Login'
import {ProtectedRoute} from './components'
import {useAccessToken} from './hooks'

const MapApp = React.lazy(() => import('map_app/MapApp'))

const Page1 = () => {
return <h1>Here is PAGE1</h1>
}

const Page2 = () => {
return <h1>Here is PAGE2</h1>
}

const MapAppContainer = () => {
const [state, setState] = useRecoilState<string>(sampleState)

const handleStateChange = (e: ChangeEvent<HTMLInputElement>) => {
const handleStateChange = (event: ChangeEvent<HTMLInputElement>) => {
const {
target: {value},
} = e
} = event

setState(value)
}
Expand All @@ -43,14 +38,25 @@ const MapAppContainer = () => {
}

const App = () => {
const {isAvailable} = useAccessToken()

return (
<UrqlProvider>
<RecoilRoot>
<Navigator onClose={console.log}>
<Screen component={MapAppContainer} path="/" />
<Screen component={Page1} path="/page1" />
<Screen component={Page2} path="/page2" />
</Navigator>
<BrowserRouter>
<Routes>
<Route path="/" element={<MapAppContainer />} />
<Route path="/login" element={<Login />} />
<Route
path="/protectePath"
element={
<ProtectedRoute isAllowed={isAvailable} redirectTo="/login">
EUNYUSEO marked this conversation as resolved.
Show resolved Hide resolved
<div>Protected Page</div>
</ProtectedRoute>
}
/>
</Routes>
</BrowserRouter>
</RecoilRoot>
</UrqlProvider>
)
Expand Down
20 changes: 20 additions & 0 deletions packages/santa_close_frame/src/components/ProtectedRoute.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import {FC, ReactElement} from 'react'
import {Navigate} from 'react-router-dom'

interface ProtectedRouteProps {
isAllowed: boolean
redirectTo: string
children: ReactElement
}

export const ProtectedRoute: FC<ProtectedRouteProps> = ({
isAllowed,
redirectTo,
children,
}) => {
if (!isAllowed) {
return <Navigate replace to={redirectTo} />
}

return children
}
Comment on lines +10 to +20
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
export const ProtectedRoute: FC<ProtectedRouteProps> = ({
isAllowed,
redirectTo,
children,
}) => {
if (!isAllowed) {
return <Navigate replace to={redirectTo} />
}
return children
}
export const ProtectedRoute: FC<ProtectedRouteProps> = ({
isAllowed,
redirectTo,
element,
path
}) => {
return (
<Route
path={path}
element={isAllowed ? element : <Navigate replace to={redirectTo} />}
/>
}

이런 구현은 어떨까요??
사용하는 곳에서 아래처럼 쓸 수 있을 것 같아요!

 <Routes>
  <Route path="/" element={<MapAppContainer />} />
  <Route path="/login" element={<Login />} />
  <ProtectedRoute
    path="/protectePath"
    isAllowed={isAvailable} 
    redirectTo="/login"
    element={
        <div>Protected Page</div>
    }
  />
</Routes>

1 change: 1 addition & 0 deletions packages/santa_close_frame/src/components/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './ProtectedRoute'
3 changes: 3 additions & 0 deletions packages/santa_close_frame/src/hooks/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export * from './useKakaoInit'
export * from './useLocalStorage'
export * from './useAccessToken'
26 changes: 26 additions & 0 deletions packages/santa_close_frame/src/hooks/useAccessToken.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import {useLocalStorage} from './useLocalStorage'

const validateExpiryDate = (expiredAt: string) => {
const now = new Date().getTime()
const expiredAtTime = new Date(expiredAt).getTime()

return now < expiredAtTime
}

export const useAccessToken = (key = 'accessToken') => {
const [{accessToken, expiredAt}] = useLocalStorage(key, {
accessToken: '',
expiredAt: '',
})

if (!accessToken || !expiredAt) {
return {isAvailable: false, accessToken: {accessToken, expiredAt}}
}

const isAvailableToken = validateExpiryDate(expiredAt)
if (isAvailableToken) {
return {isAvailable: false, accessToken: {accessToken, expiredAt}}
}

return {isAvailable: true, accessToken: {accessToken, expiredAt}}
}
65 changes: 65 additions & 0 deletions packages/santa_close_frame/src/hooks/useKakaoInit.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import {useState, useEffect} from 'react'

type ScriptLoadType = 'async' | 'defer'

const createKakaoScript = (loadType?: ScriptLoadType) => {
const script = document.createElement('script')
script.id = 'kakao-sdk'
script.src = 'https://developers.kakao.com/sdk/js/kakao.js'

if (loadType) {
script[loadType] = true
}

return script
}

const appendScriptIntoHeaders = (script: HTMLScriptElement) => {
document.head.appendChild(script)
}

export const useKakaoInit = ({
appKey,
loadType,
}: {
appKey: string
loadType?: ScriptLoadType
}) => {
const [initResult, setInitResult] = useState({
isLoaded: false,
isError: false,
})

useEffect(() => {
const kakaoSdkScript = document.getElementById('kakao-sdk')
if (kakaoSdkScript) return

const initKakaoScript = async () => {
const scriptInitResult = new Promise<{
isLoaded: boolean
isError: boolean
}>((resolve, reject) => {
const script = createKakaoScript(loadType)
appendScriptIntoHeaders(script)

script.addEventListener('load', () => {
resolve({isLoaded: true, isError: false})
})

script.addEventListener('error', () => {
// eslint-disable-next-line prefer-promise-reject-errors
reject({isLoaded: false, isError: true})
})
})

const result = await scriptInitResult
setInitResult(result)

Kakao.init(appKey)
}

initKakaoScript()
}, [appKey, loadType])

return initResult
}
28 changes: 28 additions & 0 deletions packages/santa_close_frame/src/hooks/useLocalStorage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import {useState} from 'react'

const isClient = typeof window !== 'undefined'

export const useLocalStorage = <T>(key: string, initialValue: T) => {
const [storedValue, setStoredValue] = useState<T>(() => {
if (!isClient) return initialValue

try {
const item = window.localStorage.getItem(key)
return item ? JSON.parse(item) : initialValue
} catch (error) {
console.error(error)
return initialValue
}
})

const setValue = (value: T | ((value: T) => T)) => {
const valueToStore = value instanceof Function ? value(storedValue) : value
setStoredValue(valueToStore)

if (isClient) {
window.localStorage.setItem(key, JSON.stringify(valueToStore))
}
}

return [storedValue, setValue] as const
}
68 changes: 68 additions & 0 deletions packages/santa_close_frame/src/pages/Login.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import {useNavigate} from 'react-router-dom'
import {urqlClient} from 'santa_close_common'
import {gql} from 'urql'
import {useKakaoInit, useLocalStorage} from '../hooks'

const SignInDocumnet = gql`
mutation SignIn($input: SignInAppInput!) {
signIn(input: $input) {
accessToken
expiredAt
}
}
`

const Login = () => {
const navigate = useNavigate()
const {isLoaded} = useKakaoInit({
appKey: '1a01c6b9fba660692d7388f0f7c5baaf',
loadType: 'defer',
})

// eslint-disable-next-line @typescript-eslint/naming-convention, @typescript-eslint/no-unused-vars
const [_, setAccessToken] = useLocalStorage('accessToken', {
accessToken: '',
expiredAt: '',
})

const handleKaKaoLoginClick = () => {
Kakao.Auth.login({
success: ({access_token}) => {
;(async () => {
const {
data: {
signIn: {accessToken, expiredAt},
},
} = await urqlClient
.mutation(SignInDocumnet, {
input: {
code: access_token,
type: 'KAKAO',
},
})
.toPromise()
Comment on lines +32 to +43
Copy link
Member

Choose a reason for hiding this comment

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

useMutation훅 사용하지 않고 이렇게 처리하신 이유가 있을까요??? 👀👀👀


const result = {accessToken, expiredAt}
setAccessToken(result)
})()

navigate('/', {replace: true})
},
fail: (error) => {
console.error('kakao login failed', error)
},
})
}

if (!isLoaded) return null

return (
<div>
<button type="button" onClick={handleKaKaoLoginClick}>
login
</button>
</div>
)
}

export default Login
Loading