-
Notifications
You must be signed in to change notification settings - Fork 41
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
JWT와 web storage로 로그인 기능을 구현합니다. (#144)
* feat: `WebStorage` 클래스 구현 local, session storage 관리를 위한 클래스 구현 * feat: authInterceptor 구현 storage에 토큰이 있다면 자동으로 토큰을 넣는 interceptor 구현 * feat: `useGetUser` 구현 로그인 유/무, 현재 로그인 유저의 정보를 가져오는 커스텀 훅 구현 * feat: login/logout 구현
- Loading branch information
Showing
8 changed files
with
209 additions
and
12 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
import { | ||
getGetCurrentUserQueryKey, | ||
useGetCurrentUser, | ||
} from '@/shared/api/realworld/endpoints/user-and-authentication/user-and-authentication'; | ||
|
||
const useGetUser = () => { | ||
const { data: user, isError: isLoginError } = useGetCurrentUser({ | ||
query: { | ||
staleTime: Infinity, | ||
queryKey: getGetUserQueryKey(), | ||
select: data => { | ||
return data.user; | ||
}, | ||
}, | ||
}); | ||
const isLogin = Boolean(user && !isLoginError); | ||
|
||
if (isLogin) { | ||
return { | ||
isLogin, | ||
user: user!, | ||
}; | ||
} | ||
|
||
return { | ||
isLogin, | ||
user: null, | ||
}; | ||
}; | ||
|
||
export default useGetUser; | ||
|
||
export const getGetUserQueryKey = () => { | ||
return [...getGetCurrentUserQueryKey(), 'useGetUser']; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,6 @@ | ||
import LoginForm from './login-form/login-form'; | ||
import LogoutButton from './logout-button/logout-button'; | ||
import RegisterForm from './register-form/register-form'; | ||
import UserFollowToggleButton from './user-follow-toggle-button/user-follow-toggle-button'; | ||
|
||
export { UserFollowToggleButton, LoginForm, RegisterForm }; | ||
export { UserFollowToggleButton, LoginForm, RegisterForm, LogoutButton }; |
22 changes: 21 additions & 1 deletion
22
apps/realworld/src/features/user/ui/login-form/login-form.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
51 changes: 51 additions & 0 deletions
51
apps/realworld/src/features/user/ui/logout-button/logout-button.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
'use client'; | ||
import { getGetUserQueryKey } from '@/entities/user/api/useGetUser'; | ||
import { PathBuilder } from '@/shared/utils/routes'; | ||
import webStorage, { StorageKey } from '@/shared/utils/webStorage'; | ||
import { Button, ButtonProps } from '@packages/ui'; | ||
import { useQueryClient } from '@tanstack/react-query'; | ||
import { useRouter } from 'next/navigation'; | ||
import React, { ReactNode } from 'react'; | ||
|
||
interface LogoutButtonProps extends Partial<ButtonProps> { | ||
children: ReactNode; | ||
} | ||
|
||
const LogoutButton = ({ | ||
children, | ||
onClick, | ||
color = 'error', | ||
size = 'm', | ||
variant = 'outlined', | ||
...rest | ||
}: LogoutButtonProps) => { | ||
const queryClient = useQueryClient(); | ||
const { push } = useRouter(); | ||
|
||
const logout = async () => { | ||
webStorage.removeItem(StorageKey.userToken); | ||
await queryClient.invalidateQueries(getGetUserQueryKey()); | ||
}; | ||
const movePath = () => { | ||
const homePath = PathBuilder.buildHome().getPath(); | ||
push(homePath); | ||
}; | ||
|
||
return ( | ||
<Button | ||
{...rest} | ||
variant={variant} | ||
color={color} | ||
size={size} | ||
onClick={async e => { | ||
await logout(); | ||
movePath(); | ||
onClick?.(e); | ||
}} | ||
> | ||
{children} | ||
</Button> | ||
); | ||
}; | ||
|
||
export default LogoutButton; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
12 changes: 12 additions & 0 deletions
12
apps/realworld/src/shared/api/realworld/axios/requestInterceptors/authInterceptor.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
import webStorage, { StorageKey } from '@/shared/utils/webStorage'; | ||
import { InternalAxiosRequestConfig } from 'axios'; | ||
|
||
export const authInterceptor: (value: InternalAxiosRequestConfig) => InternalAxiosRequestConfig = config => { | ||
const token = webStorage.getItem(StorageKey.userToken); | ||
|
||
if (token) { | ||
config.headers.Authorization = `Token ${token}`; | ||
} | ||
|
||
return config; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
import { match } from 'ts-pattern'; | ||
|
||
export enum StorageKey { | ||
userToken = 'u', | ||
} | ||
|
||
StorageKey.userToken === 'u'; | ||
|
||
class WebStorage { | ||
private localStorage; | ||
constructor() { | ||
if (typeof window !== 'undefined') { | ||
this.localStorage = window.localStorage; | ||
} | ||
} | ||
|
||
setItem(key: StorageKey, value: any) { | ||
if (!this.localStorage) { | ||
return; | ||
} | ||
this.localStorage.setItem(key, JSON.stringify(value)); | ||
} | ||
|
||
getItem(key: StorageKey.userToken): string | null; | ||
getItem(key: StorageKey) { | ||
if (!this.localStorage) { | ||
return null; | ||
} | ||
const stringifyItem = this.localStorage.getItem(key); | ||
|
||
return match({ stringifyItem, key }) | ||
.with({ stringifyItem: null }, () => null) | ||
.with({ key: StorageKey.userToken }, () => JSON.parse(stringifyItem!)) | ||
.exhaustive(); | ||
} | ||
removeItem(key: StorageKey) { | ||
if (!this.localStorage) { | ||
return; | ||
} | ||
this.localStorage.removeItem(key); | ||
} | ||
} | ||
|
||
const webStorage = new WebStorage(); | ||
|
||
export default webStorage; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters