diff --git a/package.json b/package.json index 22f84f4..9fb54fa 100644 --- a/package.json +++ b/package.json @@ -24,10 +24,12 @@ "react-dom": "18.2.0", "react-redux": "^8.0.5", "react-slick": "^0.29.0", + "redux-persist": "^6.0.0", "slick-carousel": "^1.8.1", "typescript": "5.0.2" }, "devDependencies": { + "@types/react-redux": "^7.1.25", "autoprefixer": "^10.4.14", "postcss": "^8.4.21", "tailwindcss": "^3.2.7" diff --git a/src/Redux/Features/UserSlice.ts b/src/Redux/Features/UserSlice.ts new file mode 100644 index 0000000..3dc635f --- /dev/null +++ b/src/Redux/Features/UserSlice.ts @@ -0,0 +1,28 @@ +import { createSlice } from "@reduxjs/toolkit"; + +interface UserState { + email: string; + token: string; +} + +const initialState: UserState = { + email: "", + token: "", +}; + +export const userSlice = createSlice({ + name: "userSlicer", + initialState, + reducers: { + __saveEmail: function (state, { type, payload }) { + state.email = payload.email; + }, + __saveToken: function (state, { type, payload }) { + state.token = payload.token; + }, + }, +}); + +export const { __saveEmail, __saveToken } = userSlice.actions; + +export default userSlice.reducer; diff --git a/src/Redux/Features/exampleSlice.ts b/src/Redux/Features/exampleSlice.ts deleted file mode 100644 index f3c219e..0000000 --- a/src/Redux/Features/exampleSlice.ts +++ /dev/null @@ -1,24 +0,0 @@ -'use client'; - -import { createSlice } from '@reduxjs/toolkit'; - -export interface ExampleState { - value: number -} - -const initialState: ExampleState = { - value: 0 -} - -export const exampleSlice = createSlice({ - name: 'example', - initialState, - reducers: { - increment: (state) => { state.value += 1 }, - - } -}) - -export const { increment } = exampleSlice.actions; - -export default exampleSlice.reducer; \ No newline at end of file diff --git a/src/Redux/Features/index.ts b/src/Redux/Features/index.ts new file mode 100644 index 0000000..b72d6ee --- /dev/null +++ b/src/Redux/Features/index.ts @@ -0,0 +1,6 @@ +import { combineReducers } from "@reduxjs/toolkit"; +import UserSlice from "./UserSlice"; + +export const rootReducer = combineReducers({ + user: UserSlice, +}); diff --git a/src/Redux/store.ts b/src/Redux/store.ts index da36acf..f594387 100644 --- a/src/Redux/store.ts +++ b/src/Redux/store.ts @@ -1,11 +1,28 @@ -'use client'; +import { configureStore } from "@reduxjs/toolkit"; +import { useDispatch, useSelector } from "react-redux"; +import { persistReducer } from "redux-persist"; +import { rootReducer } from "./Features"; +import storage from "redux-persist/lib/storage"; -import { configureStore } from '@reduxjs/toolkit'; -import exampleSlice from './Features/exampleSlice'; +const persistConfig = { + key: "root", + storage, + whitelist: ["user"], +}; -export const store = configureStore({reducer:{ - example: exampleSlice -}}) +const persistedReducer = persistReducer(persistConfig, rootReducer); +const store = configureStore({ + reducer: persistedReducer, + middleware: (getDefaultMiddleware) => + getDefaultMiddleware({ + serializableCheck: false, + immutableCheck: false, + }), +}); export type RootState = ReturnType; -export type AppDispatch = typeof store.dispatch; \ No newline at end of file +export type AppDispatch = typeof store.dispatch; +export const useAppDispatch = () => useDispatch(); +export const useAppSelector = (selector: (state: RootState) => T) => + useSelector(selector); +export default store; diff --git a/src/api/endpoints/UserAPIs.ts b/src/api/endpoints/UserAPIs.ts index 9830a49..75e40bc 100644 --- a/src/api/endpoints/UserAPIs.ts +++ b/src/api/endpoints/UserAPIs.ts @@ -1,4 +1,9 @@ -import { DupCheckDTO, UserLoginDTO, UserSignUpDTO } from "@/data/DTO/UserDTO"; +import { + DupCheckDTO, + UserDeleteDTO, + UserLoginDTO, + UserSignUpDTO, +} from "@/data/DTO/UserDTO"; import api from "../index"; export const UserAPIs = { @@ -8,4 +13,6 @@ export const UserAPIs = { api.post("/member/checkemail", payload), postNickNameDupCheck: (payload: DupCheckDTO) => api.post("/member/checknickname", payload), + deleteUser: (payload: UserDeleteDTO) => + api.delete(`/profile/${payload.memberId}`), }; diff --git a/src/api/index.ts b/src/api/index.ts index 64615c8..ae322e5 100644 --- a/src/api/index.ts +++ b/src/api/index.ts @@ -1,3 +1,4 @@ +import { useAppSelector } from "@/Redux/store"; import axios from "axios"; const api = axios.create({ @@ -7,6 +8,8 @@ const api = axios.create({ // request Interceptor api.interceptors.request.use( (data) => { + const { token } = useAppSelector((state) => state.user); + data.headers!.Authorization = "Bearer " + token; return data; }, () => {} diff --git a/src/app/Providers.tsx b/src/app/Providers.tsx index 3902a40..7b76cdf 100644 --- a/src/app/Providers.tsx +++ b/src/app/Providers.tsx @@ -1,10 +1,10 @@ -"use client"; - import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import { ReactQueryDevtools } from "@tanstack/react-query-devtools"; import { Provider } from "react-redux"; import React from "react"; -import { store } from "@/Redux/store"; +import store from "@/Redux/store"; +import { persistStore } from "redux-persist"; +import { PersistGate } from "redux-persist/integration/react"; const queryClient = new QueryClient({ defaultOptions: { @@ -15,11 +15,14 @@ const queryClient = new QueryClient({ }, }); +const persistor = persistStore(store); + const Providers = ({ children }: { children: React.ReactNode }) => { return ( - {children} - + + {children} + ); diff --git a/src/app/layout.tsx b/src/app/layout.tsx index e4fc971..6d1e03d 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -5,6 +5,7 @@ import "./globals.css"; import Providers from "./Providers"; import BottomNav from "@/components/BottomNav"; import { usePathname } from "next/navigation"; +import { MAX_WIDTH_SIZE } from "@/data/Enum"; export default function RootLayout({ children, @@ -17,7 +18,9 @@ export default function RootLayout({
-
+
{children} {pathname !== "/" && }
diff --git a/src/app/main/page.tsx b/src/app/main/page.tsx index 8dd7cff..c5809fe 100644 --- a/src/app/main/page.tsx +++ b/src/app/main/page.tsx @@ -1,11 +1,15 @@ +"use client"; import Image from "next/image"; import Logo_H from "@/assets/logo_horizontal.svg"; import BottomNav from "@/components/BottomNav"; import PostInfoCard from "@/components/Common/PostInfoCard"; import ImageCarousel from "@/components/Main/ImageCarousel"; import TodaysHotItemCarousel from "@/components/Main/TodaysHotItemCarousel"; +import { useAppSelector } from "@/Redux/store"; +import { useSelector } from "react-redux"; const MainPage = () => { + const { token, email } = useAppSelector((state) => state.user); return (
{/* TODO: 컴포넌트 분리 */} diff --git a/src/app/mypage/page.tsx b/src/app/mypage/page.tsx index 3b6fe6e..b74cafc 100644 --- a/src/app/mypage/page.tsx +++ b/src/app/mypage/page.tsx @@ -2,8 +2,15 @@ import Image from "next/image"; import Logo_H_W from "@/assets/logo_horizontal_white.svg"; import Arrow_Right from "@/assets/arrow_right.svg"; +import useDeleteUser from "@/hooks/query/userDelete"; const Page = () => { + const deleteUserAPI = useDeleteUser(); + + const deleteUserHandler = () => { + deleteUserAPI.mutate({ memberId: "kjunho.dev@gmail.com" }); + }; + return (
{/* Header */} @@ -32,7 +39,10 @@ const Page = () => { />
-
+
회원탈퇴
+ + + + + + + + diff --git a/src/components/Auth/LoginComponent.tsx b/src/components/Auth/LoginComponent.tsx index ed4e4ca..2b7d5ee 100644 --- a/src/components/Auth/LoginComponent.tsx +++ b/src/components/Auth/LoginComponent.tsx @@ -6,6 +6,8 @@ import Str from "@/data/string.json"; import usePostLogin from "@/hooks/query/userPostLogin"; import { ENUM } from "@/data/Enum"; import { useRouter } from "next/navigation"; +import { useAppDispatch } from "@/Redux/store"; +import { __saveEmail } from "@/Redux/Features/UserSlice"; interface LoginComponentProps { toggleHandler: () => void; @@ -21,8 +23,10 @@ const LoginComponent = ({ const [isButtonDisabled, setIsButtonDisabled] = useState(true); const loginAPI = usePostLogin(); const router = useRouter(); + const dispatch = useAppDispatch(); const onLoginHandler = () => { + dispatch(__saveEmail({ email: loginForm.id })); loginAPI.mutate({ email: loginForm.id, password: loginForm.pw, @@ -35,6 +39,7 @@ const LoginComponent = ({ }, [loginForm, setLoginForm] ); + const onChangePw = useCallback( (e: React.ChangeEvent) => { setLoginForm({ ...loginForm, pw: e.target.value }); @@ -52,7 +57,6 @@ const LoginComponent = ({ useEffect(() => { if (loginAPI.data?.data.statusCode === ENUM.STATUS_200) { - alert("로그인 성공"); router.push("/main"); } }, [loginAPI, router]); diff --git a/src/components/Auth/RegisterComponent.tsx b/src/components/Auth/RegisterComponent.tsx index 804fe4f..29c902b 100644 --- a/src/components/Auth/RegisterComponent.tsx +++ b/src/components/Auth/RegisterComponent.tsx @@ -1,4 +1,4 @@ -import React, { use, useCallback, useEffect, useState } from "react"; +import React, { useEffect, useState } from "react"; import Image from "next/image"; import Back from "@/assets/backButton_red.svg"; import { RegisterFormType } from "./AuthTypes"; @@ -13,6 +13,8 @@ import usePostEmailDupCheck from "@/hooks/query/userPostEmailCheck copy"; import usePostImageUpload from "@/hooks/query/commonPostImageUpload"; import usePostSignUp from "@/hooks/query/userPostSignUp"; import { useRouter } from "next/navigation"; +import Modal from "../Common/Modal"; +import Thanks from "@/assets/thanks.svg"; interface RegisterComponentProps { toggleHandler: () => void; @@ -41,14 +43,16 @@ const RegisterComponent = ({ const [previewImage, setPreviewImage] = useState( "https://cdn-icons-png.flaticon.com/512/338/338864.png" ); - const router = useRouter(); + const nameDupCheckAPI = usePostNameDupCheck(); const emailDupCheckAPI = usePostEmailDupCheck(); const imageAPI = usePostImageUpload(); const signupAPI = usePostSignUp(); + const [regiSuccessModal, setRegiSuccessModal] = useState(false); + const onNameDupCheckHandler = () => { - nameDupCheckAPI.mutate({ nickName }); + nameDupCheckAPI.mutate({ nickname: nickName }); }; const onEmailDupCheckHandler = () => { emailDupCheckAPI.mutate({ email: id }); @@ -101,12 +105,24 @@ const RegisterComponent = ({ useEffect(() => { if (signupAPI.isSuccess) { - alert("회원가입 성공"); - setToggle(!toggle); + setRegiSuccessModal(true); } }, [signupAPI.isSuccess, toggle, setToggle]); return ( <> + {regiSuccessModal && ( + + )}
{/* 뒤로가기 */}
{ + router.push(link); + }; return ( -
+
-
+
navigationHandler("/posts")} + >

카테고리

-
+
navigationHandler("/main")} + >

-
+
navigationHandler("/toktok")} + >

톡톡

-
+
navigationHandler("/mypage")} + > >; + modalSetter: React.Dispatch>; +} + +const Modal = (props: IProps) => { + const router = useRouter(); + const onNavigationHandler = () => { + props.modalSetter((v) => !v); + if (props.btnNav) { + router.push(props.btnNav); + } + if (props.btnSetter) { + props.btnSetter((v) => !v); + } + }; + return ( +
+
+

{props.title}

+ tok tok logo +
+

{props.sub}

+

{props.sub2}

+
+
+

{props.sub3}

+

{props.sub4}

+
+ {props.btnTxt && ( + + )} +
+
+ ); +}; + +export default Modal; diff --git a/src/data/DTO/UserDTO.ts b/src/data/DTO/UserDTO.ts index 82bbcdd..dbcd432 100644 --- a/src/data/DTO/UserDTO.ts +++ b/src/data/DTO/UserDTO.ts @@ -9,6 +9,10 @@ export interface UserSignUpDTO extends UserLoginDTO { } export interface DupCheckDTO { - nickName?: string; + nickname?: string; email?: string; } + +export interface UserDeleteDTO { + memberId: string; +} diff --git a/src/data/Enum.ts b/src/data/Enum.ts index afdf4dd..f2e906f 100644 --- a/src/data/Enum.ts +++ b/src/data/Enum.ts @@ -2,3 +2,5 @@ export enum ENUM { STATUS_200 = 200, STATUS_400 = 400, } + +export const MAX_WIDTH_SIZE = "420px"; diff --git a/src/hooks/query/userDelete.ts b/src/hooks/query/userDelete.ts new file mode 100644 index 0000000..0ac4f0f --- /dev/null +++ b/src/hooks/query/userDelete.ts @@ -0,0 +1,17 @@ +import { UserAPIs } from "@/api/endpoints/UserAPIs"; +import { UserDeleteDTO } from "@/data/DTO/UserDTO"; +import { useMutation } from "@tanstack/react-query"; + +const __delete = async (payload: UserDeleteDTO) => { + const del = await UserAPIs.deleteUser(payload); + return del; +}; + +const useDeleteUser = () => { + return useMutation(__delete, { + onSuccess: (data) => {}, + onError: (error) => {}, + }); +}; + +export default useDeleteUser; diff --git a/src/hooks/query/userPostLogin.ts b/src/hooks/query/userPostLogin.ts index 23a57aa..c49be6c 100644 --- a/src/hooks/query/userPostLogin.ts +++ b/src/hooks/query/userPostLogin.ts @@ -1,3 +1,5 @@ +import { __saveToken } from "@/Redux/Features/UserSlice"; +import { useAppDispatch } from "@/Redux/store"; import { UserAPIs } from "@/api/endpoints/UserAPIs"; import { UserLoginDTO } from "@/data/DTO/UserDTO"; import { useMutation } from "@tanstack/react-query"; @@ -8,8 +10,11 @@ const __login = async (payload: UserLoginDTO) => { }; const usePostLogin = () => { + const dispatch = useAppDispatch(); return useMutation(__login, { - onSuccess: (data) => {}, + onSuccess: (data) => { + dispatch(__saveToken({ token: data?.headers.authorization })); + }, onError: (error) => {}, }); }; diff --git a/yarn.lock b/yarn.lock index 4780308..ce8f3e6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,7 +2,14 @@ # yarn lockfile v1 -"@babel/runtime@^7.12.1", "@babel/runtime@^7.20.7", "@babel/runtime@^7.9.2": +"@babel/runtime@^7.12.1": + version "7.21.5" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.21.5.tgz#8492dddda9644ae3bda3b45eabe87382caee7200" + integrity sha512-8jI69toZqqcsnqGGqwGS4Qb1VwLOEp4hz+CXPywcvjs60u3B4Pom/U/7rm4W8tMOYEB+E9wgD0mW1l3r8qlI9Q== + dependencies: + regenerator-runtime "^0.13.11" + +"@babel/runtime@^7.20.7", "@babel/runtime@^7.9.2": version "7.21.0" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.21.0.tgz#5b55c9d394e5fcf304909a8b00c07dc217b56673" integrity sha512-xwII0//EObnq89Ji5AKYQaRYiW/nZ3llSv29d49IuxPhKbtJoLP+9QUUZ4nVragQVtaVGeZrpB+ZtG/Pdy/POw== @@ -221,7 +228,7 @@ "@tanstack/query-core" "4.27.0" use-sync-external-store "^1.2.0" -"@types/hoist-non-react-statics@^3.3.1": +"@types/hoist-non-react-statics@^3.3.0", "@types/hoist-non-react-statics@^3.3.1": version "3.3.1" resolved "https://registry.yarnpkg.com/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz#1124aafe5118cb591977aeb1ceaaed1070eb039f" integrity sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA== @@ -251,6 +258,16 @@ dependencies: "@types/react" "*" +"@types/react-redux@^7.1.25": + version "7.1.25" + resolved "https://registry.yarnpkg.com/@types/react-redux/-/react-redux-7.1.25.tgz#de841631205b24f9dfb4967dd4a7901e048f9a88" + integrity sha512-bAGh4e+w5D8dajd6InASVIyCo4pZLJ66oLb80F9OBLO1gKESbZcRCJpTT6uLXX+HAB57zw1WTdwJdAsewuTweg== + dependencies: + "@types/hoist-non-react-statics" "^3.3.0" + "@types/react" "*" + hoist-non-react-statics "^3.3.0" + redux "^4.0.0" + "@types/react-slick@^0.23.10": version "0.23.10" resolved "https://registry.yarnpkg.com/@types/react-slick/-/react-slick-0.23.10.tgz#56126e6e4e95cdce7771535b2811c2c1931a7caa" @@ -2121,12 +2138,17 @@ readdirp@~3.6.0: dependencies: picomatch "^2.2.1" +redux-persist@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/redux-persist/-/redux-persist-6.0.0.tgz#b4d2972f9859597c130d40d4b146fecdab51b3a8" + integrity sha512-71LLMbUq2r02ng2We9S215LtPu3fY0KgaGE0k8WRgl6RkqxtGfl7HUozz1Dftwsb0D/5mZ8dwAaPbtnzfvbEwQ== + redux-thunk@^2.4.2: version "2.4.2" resolved "https://registry.yarnpkg.com/redux-thunk/-/redux-thunk-2.4.2.tgz#b9d05d11994b99f7a91ea223e8b04cf0afa5ef3b" integrity sha512-+P3TjtnP0k/FEjcBL5FZpoovtvrTNT/UXd4/sluaSyrURlSlhLSzEdfsTBW7WsKB6yPvgd7q/iZPICFjW4o57Q== -redux@^4.2.0: +redux@^4.0.0, redux@^4.2.0: version "4.2.1" resolved "https://registry.yarnpkg.com/redux/-/redux-4.2.1.tgz#c08f4306826c49b5e9dc901dee0452ea8fce6197" integrity sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==