From 33cb319d96195d786f1f6119c62db054114b3b7a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?junh=5Feee=28=EC=9D=B4=EC=A4=80=ED=9D=AC=29?= Date: Mon, 29 Jan 2024 14:32:10 +0900 Subject: [PATCH] =?UTF-8?q?redux-toolkit=20=EC=B4=88=EA=B8=B0=EC=85=8B?= =?UTF-8?q?=ED=8C=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 13 +++- src/app/test/drone.tsx | 31 +++++++++ src/app/test/page.tsx | 12 ++++ src/redux/features/basis/drone/droneSlice.ts | 25 +++++++ src/redux/features/basis/drone/droneState.ts | 71 ++++++++++++++++++++ src/redux/features/basis/drone/droneThunk.ts | 32 +++++++++ src/redux/hooks.ts | 7 ++ src/redux/provider.tsx | 10 +++ src/redux/rootReducer.ts | 11 +++ src/redux/store.ts | 9 +++ src/utils/axiosInterceptor.ts | 27 ++++++++ src/utils/cookie.ts | 20 ++++++ src/utils/jwtToken.ts | 51 ++++++++++++++ 13 files changed, 316 insertions(+), 3 deletions(-) create mode 100644 src/app/test/drone.tsx create mode 100644 src/app/test/page.tsx create mode 100644 src/redux/features/basis/drone/droneSlice.ts create mode 100644 src/redux/features/basis/drone/droneState.ts create mode 100644 src/redux/features/basis/drone/droneThunk.ts create mode 100644 src/redux/hooks.ts create mode 100644 src/redux/provider.tsx create mode 100644 src/redux/rootReducer.ts create mode 100644 src/redux/store.ts create mode 100644 src/utils/axiosInterceptor.ts create mode 100644 src/utils/cookie.ts create mode 100644 src/utils/jwtToken.ts diff --git a/package.json b/package.json index 2d21512..22d21ef 100644 --- a/package.json +++ b/package.json @@ -9,16 +9,23 @@ "lint": "next lint" }, "dependencies": { + "@reduxjs/toolkit": "^1.9.7", + "axios": "^1.6.0", + "js-cookie": "^3.0.0", + "jwt-decode": "^3.1.2", "next": "13.5.6", + "qs": "^6.11.2", "react": "18.2.0", - "react-dom": "18.2.0" + "react-dom": "18.2.0", + "react-redux": "^8.1.3" }, "devDependencies": { - "typescript": "^5", "@types/node": "^20", + "@types/qs": "^6.9.11", "@types/react": "^18", "@types/react-dom": "^18", "eslint": "^8", - "eslint-config-next": "13.5.6" + "eslint-config-next": "13.5.6", + "typescript": "^5" } } diff --git a/src/app/test/drone.tsx b/src/app/test/drone.tsx new file mode 100644 index 0000000..dba4796 --- /dev/null +++ b/src/app/test/drone.tsx @@ -0,0 +1,31 @@ +import { getDroneList } from '@/redux/features/basis/drone/droneThunk'; +import { useAppSelector } from '@/redux/hooks'; +import { AppDispatch } from '@/redux/store'; +import { useEffect } from 'react'; +import { useDispatch } from 'react-redux'; + +export default function Drone() { + const dispatch = useDispatch(); + const droneList = useAppSelector(state => state.drone.list); + + useEffect(() => { + console.log(droneList, '---list'); + }, [droneList]); + + const handler = () => { + dispatch( + getDroneList({ + groupId: 'C807F9', + page: 1 + }) + ); + }; + return ( +
+ main +
+ Redux-toolkit +
+
+ ); +} diff --git a/src/app/test/page.tsx b/src/app/test/page.tsx new file mode 100644 index 0000000..3b4f3dd --- /dev/null +++ b/src/app/test/page.tsx @@ -0,0 +1,12 @@ +'use Client'; + +import Drone from './drone'; + +export default function Home() { + return ( + <> +
main
+ {/* */} + + ); +} diff --git a/src/redux/features/basis/drone/droneSlice.ts b/src/redux/features/basis/drone/droneSlice.ts new file mode 100644 index 0000000..c714e9b --- /dev/null +++ b/src/redux/features/basis/drone/droneSlice.ts @@ -0,0 +1,25 @@ +import { createSlice } from '@reduxjs/toolkit'; +import { InitDroneState } from './droneState'; +import { getDroneList, getIdntfList } from './droneThunk'; + +const droneSlice = createSlice({ + name: 'droneSlice', + initialState: InitDroneState, + reducers: {}, + extraReducers: builder => { + builder.addCase(getDroneList.fulfilled, (state, action) => { + const { data, count } = action.payload; + state.list = data.items; + state.total = data.total; + state.count = count; + }); + builder.addCase(getIdntfList.fulfilled, (state, action) => { + const { data, count } = action.payload; + state.listIdntf = data; + state.idntfCount = count; + state.isRefreshIdntf = false; + }); + } +}); + +export const droneReducer = droneSlice.reducer; diff --git a/src/redux/features/basis/drone/droneState.ts b/src/redux/features/basis/drone/droneState.ts new file mode 100644 index 0000000..dfabb26 --- /dev/null +++ b/src/redux/features/basis/drone/droneState.ts @@ -0,0 +1,71 @@ +export const InitDroneState = { + list: undefined, + count: 0, + detail: undefined, + searchParams: '', + selectData: undefined, + listIdntf: undefined, + idntfCount: 0, + isRefreshIdntf: false, + page: 1, + total: 0 +}; + +export interface DroneState { + list: DroneData[] | undefined; + count: number | 0; + detail: DroneData | undefined; + searchParams: string | ''; + selectData: SelectData | undefined; + listIdntf: IdntfData[] | undefined; + idntfCount: number | 0; + isRefreshIdntf: boolean | false; + page: number | 1; + total: number | 0; +} + +export interface SelectData { + groupId: string; + groupNm: string; +} + +export interface DroneData { + groupId: string; + arcrftSno: number; + arcrftHght: number; + arcrftLngth: number; + arcrftModelNm: string; + arcrftTypeCd: string; + arcrftWdth: number; + arcrftWght: number; + cameraYn: string; + createDt: Date; + createUserId: string; + insrncYn: string; + prdctCmpnNm: string; + prdctDate: Date; + prdctNum: string; + takeoffWght: number; + updateDt: Date; + updateUserId: string; + imageUrl: string; + wghtTypeCd: string; + idntfNum: string; + newIdntfNum: string; + idntfTypeCd: string; + ownerSno: number; + ownerNm: string; + hpno: string; + telno: string; + useYn: string; + file: FileList; +} + +export interface IdntfData { + idntfNum: string; + newIdntfNum: string; + isSave: boolean; + updateDt: Date; + createDt: Date; + cstmrSno: number; +} diff --git a/src/redux/features/basis/drone/droneThunk.ts b/src/redux/features/basis/drone/droneThunk.ts new file mode 100644 index 0000000..2085c61 --- /dev/null +++ b/src/redux/features/basis/drone/droneThunk.ts @@ -0,0 +1,32 @@ +import axios from '../../../../utils/axiosInterceptor'; +import qs from 'qs'; + +import { createAsyncThunk } from '@reduxjs/toolkit'; + +export const getDroneList = createAsyncThunk( + 'basis/getDroneList', + async (data: any) => { + try { + const queryString = qs.stringify(data, { + addQueryPrefix: true, + arrayFormat: 'repeat' + }); + const response = await axios.get(`api/bas/dron/list${queryString}`); + return response.data; + } catch (error) { + console.error(error); + } + } +); + +export const getIdntfList = createAsyncThunk( + 'basis/getIdntfList', + async (id: number) => { + try { + const response = await axios.get(`api/bas/dron/idntf/list/${id}`); + return response.data; + } catch (error) { + console.error(error); + } + } +); diff --git a/src/redux/hooks.ts b/src/redux/hooks.ts new file mode 100644 index 0000000..399e939 --- /dev/null +++ b/src/redux/hooks.ts @@ -0,0 +1,7 @@ +import { AppDispatch, store } from './store'; +import { useDispatch, useSelector, TypedUseSelectorHook } from 'react-redux'; + +type RootState = ReturnType; + +export const useAppDispatch = () => useDispatch(); +export const useAppSelector: TypedUseSelectorHook = useSelector; diff --git a/src/redux/provider.tsx b/src/redux/provider.tsx new file mode 100644 index 0000000..dd1abc6 --- /dev/null +++ b/src/redux/provider.tsx @@ -0,0 +1,10 @@ +'use client'; +import { store } from './store'; +import { Provider } from 'react-redux'; + +type Props = { + children: React.ReactNode; +}; +export default function ReduxProvider({ children }: Props) { + return {children}; +} diff --git a/src/redux/rootReducer.ts b/src/redux/rootReducer.ts new file mode 100644 index 0000000..d6affba --- /dev/null +++ b/src/redux/rootReducer.ts @@ -0,0 +1,11 @@ +import { combineReducers } from '@reduxjs/toolkit'; +import { droneReducer } from './features/basis/drone/droneSlice'; + +const rootReducer = (state: any, action: any) => { + const combineReducer = combineReducers({ + drone: droneReducer + }); + + return combineReducer(state, action); +}; +export default rootReducer; diff --git a/src/redux/store.ts b/src/redux/store.ts new file mode 100644 index 0000000..0f0001d --- /dev/null +++ b/src/redux/store.ts @@ -0,0 +1,9 @@ +import { configureStore } from '@reduxjs/toolkit'; +import rootReducer from './rootReducer'; + +export const store = configureStore({ + reducer: rootReducer +}); + +export type RootState = ReturnType; +export type AppDispatch = typeof store.dispatch; diff --git a/src/utils/axiosInterceptor.ts b/src/utils/axiosInterceptor.ts new file mode 100644 index 0000000..df5133a --- /dev/null +++ b/src/utils/axiosInterceptor.ts @@ -0,0 +1,27 @@ +import axios, { AxiosInstance } from 'axios'; +import { getAccessToken } from './jwtToken'; +import { store } from '@/redux/store'; + +const axiosInstance: AxiosInstance = axios.create({ + baseURL: 'http://121.190.193.50:6081/' +}); + +axiosInstance.interceptors.request.use( + async config => { + config.headers['Authorization'] = + 'palnet eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJwYWxuZXQiLCJjc3RtclNubyI6MiwiZXhwIjoxNzA4MzA2Nzg4LCJ1c2VySWQiOiJwYWxuZXQiLCJpYXQiOjE3MDY0OTIzODh9.X5S5oF5ZWcTK6FwjpVAfRwffuwLeCHUssn1OEJbsSFJDzi-Vp_DdnfgIvWAM1ry97VmNfFsT9bpX8XhE4dRnWg'; + // let accessToken = await getAccessToken(); + + // config.headers['Authorization'] = accessToken; + // // alert(JSON.stringify(config.headers)); + // // store.dispatch({ + // // // type: GLOBAL_LOADING + // // }); + return config; + }, + error => { + return Promise.reject(error); + } +); + +export default axiosInstance; diff --git a/src/utils/cookie.ts b/src/utils/cookie.ts new file mode 100644 index 0000000..9d45a0b --- /dev/null +++ b/src/utils/cookie.ts @@ -0,0 +1,20 @@ +import Cookies, { CookieAttributes, CookiesStatic } from 'js-cookie'; + +const CookieStorageBuilder = (cookies: CookiesStatic) => ({ + setCookie: (key: string, value: string, options?: CookieAttributes) => + cookies.set(key, value, { + path: '/', + secure: process.env.NODE_ENV === 'production' ? false : false, + sameSite: 'Lax', + ...options + }), + getCookie: (key: string) => cookies.get(key), + removeCookie: (key: string) => cookies.remove(key) +}); + +export const cookieStorage = CookieStorageBuilder(Cookies); + +const COOKIE_BASE_NAME = 'palnet'; + +export const COOKIE_ACCESS_TOKEN = `${COOKIE_BASE_NAME}_accs`; +export const COOKIE_REFRESH_TOKEN = `${COOKIE_BASE_NAME}_rfrs`; diff --git a/src/utils/jwtToken.ts b/src/utils/jwtToken.ts new file mode 100644 index 0000000..faecdba --- /dev/null +++ b/src/utils/jwtToken.ts @@ -0,0 +1,51 @@ +import decode from 'jwt-decode'; +import { + cookieStorage, + COOKIE_ACCESS_TOKEN, + COOKIE_REFRESH_TOKEN +} from './cookie'; + +const JWT_FLEFIX = 'palnet '; +export const checkTokenExpired = (token?: string) => { + return true; + // const decodedToken = decode(token as string); + + // if (parseInt(decodedToken.exp) < dayjs().unix()) { + // return true; + // } + + // return false; +}; + +export const getAccessToken = async () => { + let token = cookieStorage.getCookie(COOKIE_ACCESS_TOKEN); + + if (!token || token == undefined || token == 'undefined') { + return ''; + } + + // if (checkTokenExpired(token)) { + // //freshtoken 으로 재발급 진행. + // const refreshToken = getRefreshToken(); + // //const isRefresh = store.getState()?.authState?.isRefresh; + + // if (refreshToken) { + // // if (!store.getState()?.authState?.isRefresh) { + // const decodeToken = decode(token as string); + // const cstmrSno: number = decodeToken.cstmrSno; + + // const { data, errorCode } = await refreshTokenAPI(cstmrSno, refreshToken); + // if (errorCode === '-101') { + // cookieStorage.setCookie(COOKIE_REFRESH_TOKEN, ''); + // } + // //REFHRESTH 토큰 넣기 + // cookieStorage.setCookie(COOKIE_ACCESS_TOKEN, data.accessToken); + // cookieStorage.setCookie(COOKIE_REFRESH_TOKEN, data.refreshToken); + // token = data.refreshToken; + + // // } + // } + // } + + return JWT_FLEFIX + token; +};