From 95583b2e8a6ceefae279254a317fcfe7579fd42d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9E=AC=EA=B5=AD?= <97966640+jk6722@users.noreply.github.com> Date: Mon, 20 Nov 2023 16:07:33 +0900 Subject: [PATCH] Feat/#42/api login (#49) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: 후기 페이지 서버에서 주어지는 데이터에 맞게 props 변경 * feat: recoil, recoil-persist 패키지 다운로드 * feat: 후기 페이지 API 연결 * feat: 후기 페이지 API 연결 * feat: axios interceptor를 사용해서 API 요청 전 토큰 재발급 * feat: 리코일 사용을 위해 설정 * feat: 헤더에 로그인 버튼 추가 * feat: 로그인을 위한 함수 구현 * feat: 로그인 성공시 사용자의 정보를 recoil에 저장 * feat: 후기 페이지 API 연결 --- package-lock.json | 52 +++++++++- package.json | 6 +- src/apis/board/index.ts | 22 +++++ src/apis/index.ts | 78 ++++++++++++++- src/components/Header/Header.tsx | 73 ++++++++++---- src/main.tsx | 5 +- src/pages/board/Board.tsx | 128 +++++-------------------- src/pages/board/PostingList.tsx | 11 +-- src/pages/board/components/Posting.tsx | 21 ++-- src/pages/login/Login.tsx | 27 ++---- src/pages/login/functions/index.ts | 81 ++++++++++++++-- src/pages/user/MyPosting/index.tsx | 5 +- src/recoil/LoginAtom.ts | 21 ++++ src/types/index.ts | 16 +++- 14 files changed, 369 insertions(+), 177 deletions(-) create mode 100644 src/apis/board/index.ts create mode 100644 src/recoil/LoginAtom.ts diff --git a/package-lock.json b/package-lock.json index 4c94806..ca2f208 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,7 @@ "name": "frontend", "version": "0.0.0", "dependencies": { + "js-cookie": "^3.0.5", "moment": "^2.29.4", "quill-image-resize-module-react": "^3.0.0", "react": "^18.2.0", @@ -16,9 +17,12 @@ "react-icons": "^4.11.0", "react-query": "^3.39.3", "react-quill": "^2.0.0", - "react-router-dom": "^6.16.0" + "react-router-dom": "^6.16.0", + "recoil": "^0.7.7", + "recoil-persist": "^5.1.0" }, "devDependencies": { + "@types/js-cookie": "^3.0.6", "@types/node": "^20.8.9", "@types/react": "^18.2.14", "@types/react-dom": "^18.2.6", @@ -2970,6 +2974,12 @@ "integrity": "sha512-myfUej5naTBWnqOCc/MdVOLVjXUXtIA+NpDrDBKJtLLg2shUjBu3cZmB/85RyitKc55+lUUyl7oRfLOvkr2hsw==", "dev": true }, + "node_modules/@types/js-cookie": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/js-cookie/-/js-cookie-3.0.6.tgz", + "integrity": "sha512-wkw9yd1kEXOPnvEeEV1Go1MmxtBJL0RR79aOTAApecWFVu7w0NNXNqhcWgvw2YgZDYadliXkl14pa3WXw5jlCQ==", + "dev": true + }, "node_modules/@types/json-schema": { "version": "7.0.13", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.13.tgz", @@ -5232,6 +5242,11 @@ "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", "dev": true }, + "node_modules/hamt_plus": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/hamt_plus/-/hamt_plus-1.0.2.tgz", + "integrity": "sha512-t2JXKaehnMb9paaYA7J0BX8QQAY8lwfQ9Gjf4pg/mk4krt+cmwmU652HOoWonf+7+EQV97ARPMhhVgU1ra2GhA==" + }, "node_modules/has": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", @@ -5824,6 +5839,14 @@ "set-function-name": "^2.0.1" } }, + "node_modules/js-cookie": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.5.tgz", + "integrity": "sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==", + "engines": { + "node": ">=14" + } + }, "node_modules/js-sha3": { "version": "0.8.0", "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.8.0.tgz", @@ -6849,6 +6872,33 @@ "node": ">=8.10.0" } }, + "node_modules/recoil": { + "version": "0.7.7", + "resolved": "https://registry.npmjs.org/recoil/-/recoil-0.7.7.tgz", + "integrity": "sha512-8Og5KPQW9LwC577Vc7Ug2P0vQshkv1y3zG3tSSkWMqkWSwHmE+by06L8JtnGocjW6gcCvfwB3YtrJG6/tWivNQ==", + "dependencies": { + "hamt_plus": "1.0.2" + }, + "peerDependencies": { + "react": ">=16.13.1" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + }, + "react-native": { + "optional": true + } + } + }, + "node_modules/recoil-persist": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/recoil-persist/-/recoil-persist-5.1.0.tgz", + "integrity": "sha512-sew4k3uBVJjRWKCSFuBw07Y1p1pBOb0UxLJPxn4G2bX/9xNj+r2xlqYy/BRfyofR/ANfqBU04MIvulppU4ZC0w==", + "peerDependencies": { + "recoil": "^0.7.2" + } + }, "node_modules/reflect.getprototypeof": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.4.tgz", diff --git a/package.json b/package.json index bf5e1e6..a9c1af6 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ "preview": "vite preview" }, "dependencies": { + "js-cookie": "^3.0.5", "moment": "^2.29.4", "quill-image-resize-module-react": "^3.0.0", "react": "^18.2.0", @@ -18,9 +19,12 @@ "react-icons": "^4.11.0", "react-query": "^3.39.3", "react-quill": "^2.0.0", - "react-router-dom": "^6.16.0" + "react-router-dom": "^6.16.0", + "recoil": "^0.7.7", + "recoil-persist": "^5.1.0" }, "devDependencies": { + "@types/js-cookie": "^3.0.6", "@types/node": "^20.8.9", "@types/react": "^18.2.14", "@types/react-dom": "^18.2.6", diff --git a/src/apis/board/index.ts b/src/apis/board/index.ts new file mode 100644 index 0000000..b4507fe --- /dev/null +++ b/src/apis/board/index.ts @@ -0,0 +1,22 @@ +import Axios from '..'; + +export const fetchBoardData = + (boardType: string, filter: string, page: number, size: number) => () => { + if (boardType === 'review') { + return Axios.get('/reviews/category', { + params: { + category: filter === '전체' ? null : filter, + page: page - 1, + size, + }, + }); + } else if (boardType === 'archive') { + return Axios.get('/archives/category', { + params: { + category: filter, + page: page - 1, + size, + }, + }); + } + }; diff --git a/src/apis/index.ts b/src/apis/index.ts index 06a274e..931fd89 100644 --- a/src/apis/index.ts +++ b/src/apis/index.ts @@ -1,7 +1,81 @@ -import axios from 'axios'; +import axios, { AxiosResponse } from 'axios'; +import Cookies from 'js-cookie'; +import { onLoginSuccess } from '@/pages/login/functions'; const Axios = axios.create(); -Axios.defaults.baseURL = 'http://52.78.13.36'; // 서버 URL +const ServerURL = 'https://www.tvmaker.shop'; // 서버 URL +Axios.defaults.baseURL = ServerURL; Axios.defaults.withCredentials = true; +// 요청 전 access 토큰 만료되었는지 확인 +Axios.interceptors.request.use( + async config => { + const expireToken = localStorage.getItem('expireToken'); + const expireTime = expireToken + ? new Date(expireToken as string) + : undefined; + const currentTime = new Date().getTime(); + const refreshToken = Cookies.get('refreshToken'); + + if (expireTime && refreshToken) { + // 이전에 로그인한 적이 있음 + if (currentTime > expireTime.getTime()) { + // 만료되었으면 + const res = await axios.get(`${ServerURL}/auth/refresh`, { + params: { + refreshToken, + }, + }); + const { accessToken: newAccessToken, refreshToken: newRefreshToken } = + res?.data?.result; + + // header에 accessToken 세팅 + Axios.defaults.headers.common[ + 'Authorization' + ] = `Bearer ${newAccessToken}`; + config.headers.Authorization = `Bearer ${newAccessToken}`; + + // 갱신된 만료 시간 localStorage에 저장 + const newExpireTime = new Date(currentTime + 1 * 60 * 60 * 1000); // 유효시간 1시간 + localStorage.setItem('expireToken', newExpireTime.toString()); + + Cookies.remove('refreshToken'); + Cookies.set('refreshToken', newRefreshToken, { expires: 1 }); + } + } + return config; + }, + error => Promise.reject(error), +); + +// 응답 전 accessToken이 만료되어 발생한 에러라면 갈아끼우고 다시 요청 +Axios.interceptors.response.use( + (response: AxiosResponse) => { + return response; + }, + async error => { + if (error.response?.status === 401) { + // accessToken이 없어서 에러가 발생한 상황 + const refreshToken = Cookies.get('refreshToken'); + if (refreshToken) { + // refreshToken이 있다면, 이미 로그인된 사용자이므로 + const res = await axios.get(`${ServerURL}/auth/refresh`, { + params: { refreshToken }, + }); + // 기존에 있던 refreshToken은 지우고 + Cookies.remove('refreshToken'); + // 로그인 성공 로직 실행 + onLoginSuccess(res, null); + return Axios(error.config); + } else { + // refreshToken이 없다는 건 로그인을 해야 한다는 것 + if (window.confirm('로그인이 필요한 서비스입니다.')) { + window.location.href = '/login'; + } + } + } + return error; + }, +); + export default Axios; diff --git a/src/components/Header/Header.tsx b/src/components/Header/Header.tsx index 7d6df2b..b121ea7 100644 --- a/src/components/Header/Header.tsx +++ b/src/components/Header/Header.tsx @@ -1,14 +1,44 @@ -import { Link } from 'react-router-dom'; +import { useState, useEffect } from 'react'; +import { Link, useNavigate } from 'react-router-dom'; import styled from 'styled-components'; +import { useRecoilState } from 'recoil'; import { HeaderData, UserDropdown } from '@/constants/Header'; import { B1, H3 } from '@/style/fonts/StyledFonts'; import UserIcon from '@/assets/icons/user-icon.svg'; +import RoundedButton from '../Button/RoundedButton'; +import { UserAtom } from '@/recoil/LoginAtom'; const Header = () => { + const [isLogined, setIsLogined] = useState(false); + const [userInfo, setUserInfo] = useRecoilState(UserAtom); + + const navigate = useNavigate(); + const handleLogout = () => { // logout + // 저장하고 있던 사용자 정보 초기화 + console.log(userInfo); + setUserInfo({ + id: -1, + email: '', + phoneNumber: '', + name: '', + nickName: '', + }); + setTimeout(() => { + setIsLogined(false); + }, 1000); + }; + + const handleToLogin = () => { + navigate('/login'); }; + + useEffect(() => { + if (userInfo?.id != -1) setIsLogined(true); + }, [userInfo]); + return ( @@ -32,22 +62,33 @@ const Header = () => { {/* 헤더 우측 (유저 아이콘) */} - - user - - {UserDropdown.map(({ title, link }, index) => ( -
  • - - {title} - + {isLogined ? ( + + user + + {UserDropdown.map(({ title, link }, index) => ( +
  • + + {title} + +
  • + ))} + +
  • + {'로그아웃'}
  • - ))} - -
  • - {'로그아웃'} -
  • -
    -
    + + + ) : ( + 로그인} + /> + )}
    diff --git a/src/main.tsx b/src/main.tsx index 6dc190b..d045360 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -2,13 +2,16 @@ import React from 'react'; import ReactDOM from 'react-dom/client'; import App from './App.tsx'; import { QueryClient, QueryClientProvider } from 'react-query'; +import { RecoilRoot } from 'recoil'; const queryClient = new QueryClient(); ReactDOM.createRoot(document.getElementById('root')!).render( - + + + , ); diff --git a/src/pages/board/Board.tsx b/src/pages/board/Board.tsx index 225024c..f2f14d6 100644 --- a/src/pages/board/Board.tsx +++ b/src/pages/board/Board.tsx @@ -3,10 +3,12 @@ import styled from 'styled-components'; import Introduction from './Introduction'; import Filter from './Filter'; import PostingList from './PostingList'; -import { PostingType } from '@/types'; import PageBar from '@/components/PageBar/PageBar'; import { useLocation } from 'react-router-dom'; import SearchBar from '@/components/SearchBar/SearchBar'; +import { useQuery } from 'react-query'; +import { fetchBoardData } from '@/apis/board'; +import Loading from '@/components/Loading/Loading'; interface BoardProps { title: string; @@ -21,115 +23,26 @@ const filterList: string[] = [ '여행 공모전 후기', ]; -export const tempData: PostingType[] = [ - // 임시 데이터 (API 연결되면 삭제 예정) - { - boardName: '이벤트', - title: - '취준 고민 1개만 남겨도 커피 드려요! 삼성/SKblablablablablablablablablablablabla', - id: 1, - nickName: '닉네임', - registerDate: '2023.10.13', - type: '여행 지원사업 후기', - }, - { - boardName: '이벤트', - title: - '취준 고민 1개만 남겨도 커피 드려요! 삼성/SKblablablablablablablablablablablabla', - id: 2, - nickName: '닉네임', - registerDate: '2023.10.13', - type: '여행 지원사업 후기', - }, - { - boardName: '이벤트', - title: - '취준 고민 1개만 남겨도 커피 드려요! 삼성/SKblablablablablablablablablablablabla', - id: 3, - nickName: '닉네임', - registerDate: '2023.10.13', - type: '여행 지원사업 후기', - }, - { - boardName: '이벤트', - title: - '취준 고민 1개만 남겨도 커피 드려요! 삼성/SKblablablablablablablablablablablabla', - id: 4, - nickName: '닉네임', - registerDate: '2023.10.13', - type: '여행 대외활동 후기', - }, - { - boardName: '이벤트', - title: - '취준 고민 1개만 남겨도 커피 드려요! 삼성/SKblablablablablablablablablablablabla', - id: 5, - nickName: '닉네임', - registerDate: '2023.10.13', - type: '여행 지원사업 후기', - }, - { - boardName: '이벤트', - title: - '취준 고민 1개만 남겨도 커피 드려요! 삼성/SKblablablablablablablablablablablabla', - id: 6, - nickName: '닉네임', - registerDate: '2023.10.13', - type: '여행 대외활동 후기', - }, - { - boardName: '이벤트', - title: - '취준 고민 1개만 남겨도 커피 드려요! 삼성/SKblablablablablablablablablablablabla', - id: 7, - nickName: '닉네임', - registerDate: '2023.10.13', - type: '여행 지원사업 후기', - }, - { - boardName: '이벤트', - title: - '취준 고민 1개만 남겨도 커피 드려요! 삼성/SKblablablablablablablablablablablabla', - id: 8, - nickName: '닉네임', - registerDate: '2023.10.13', - type: '여행 대외활동 후기', - }, - { - boardName: '이벤트', - title: - '취준 고민 1개만 남겨도 커피 드려요! 삼성/SKblablablablablablablablablablablabla', - id: 9, - nickName: '닉네임', - registerDate: '2023.10.13', - type: '여행 공모전 후기', - }, - { - boardName: '이벤트', - title: '취준 고민 1개만 남겨도 커피 드려요!', - id: 10, - nickName: '닉네임', - registerDate: '2023.10.13', - type: '여행 공모전 후기', - }, - { - boardName: '이벤트', - title: - '취준 고민 1개만 남겨도 커피 드려요! 삼성/SKblablablablablablablablablablablabla', - id: 11, - nickName: '닉네임', - registerDate: '2023.10.13', - type: '여행 공모전 후기', - }, -]; - const Board: React.FC = ({ title, description, imageSrc }) => { const [filter, setFilter] = useState('전체'); // 전체가 디폴트 const [page, setPage] = useState(1); const [searchInput, setSearchInput] = useState(''); + const boardType: string = window.location.pathname.includes('review') + ? 'review' + : 'archive'; + const location = useLocation(); + const { isLoading, data } = useQuery( + [`${boardType}`, filter, page], + fetchBoardData(boardType, filter, page, 10), + { + cacheTime: 500005, + staleTime: 500000, + }, + ); + useEffect(() => { if (location?.state?.filter) { setFilter(location.state.filter); @@ -140,6 +53,11 @@ const Board: React.FC = ({ title, description, imageSrc }) => { window.scrollTo({ top: 0, behavior: 'smooth' }); }, [filter]); + if (isLoading) return ; + + const postingData = data?.data?.result?.reviews; + const maxPage = data?.data?.result?.totalSize; + return ( = ({ title, description, imageSrc }) => { handleSubmit={() => {}} /> - - + + ); }; diff --git a/src/pages/board/PostingList.tsx b/src/pages/board/PostingList.tsx index d92801f..2b00a68 100644 --- a/src/pages/board/PostingList.tsx +++ b/src/pages/board/PostingList.tsx @@ -5,11 +5,10 @@ import { PostingType } from '@/types'; import { useNavigate } from 'react-router-dom'; interface Props { - filter: string; postingList: PostingType[]; } -const PostingList: React.FC = ({ filter, postingList }) => { +const PostingList: React.FC = ({ postingList }) => { const navigate = useNavigate(); return ( @@ -29,13 +28,7 @@ const PostingList: React.FC = ({ filter, postingList }) => { - {postingList - .filter(data => { - if (filter === '전체' || data.type === filter) return data; - }) - .map(data => ( - - ))} + {postingList?.map(data => )} = ({ - boardName, + category, title, id, - nickName, - registerDate, + writer, + createdDate, }) => { const navigate = useNavigate(); @@ -16,18 +18,25 @@ const Posting: React.FC = ({ navigate(`${id}`); }; + const truncDate = useCallback(() => { + const newDate = new Date(createdDate); + return `${newDate.getFullYear()}-${ + newDate.getMonth() + 1 + }-${newDate.getDate()}`; + }, [createdDate]); + return ( - {boardName} + {category} {title} - {nickName} + {writer} - {registerDate} + {truncDate()} ); }; diff --git a/src/pages/login/Login.tsx b/src/pages/login/Login.tsx index da87996..89b2b7d 100644 --- a/src/pages/login/Login.tsx +++ b/src/pages/login/Login.tsx @@ -1,12 +1,13 @@ import { useState } from 'react'; import styled from 'styled-components'; import { useNavigate } from 'react-router-dom'; +import { useSetRecoilState } from 'recoil'; -import Axios from '@/apis'; import { B2Bold, B3, H1 } from '@/style/fonts/StyledFonts'; import SocialLogin from './SocialLogin'; import LoginNavLink from './LoginNavLink'; -import { onLoginSuccess } from './functions'; +import { userLogin } from './functions'; +import { UserAtom } from '@/recoil/LoginAtom'; interface UserInputType { id: string; @@ -19,25 +20,13 @@ const Login = () => { password: '', }); const [isError, setIsError] = useState(false); + const setUserInfo = useSetRecoilState(UserAtom); + const navigate = useNavigate(); - const handleLogin = async () => { - try { - const res = await Axios.post('/auth/login', null, { - params: { loginId: userInput.id, loginPassword: userInput.password }, - }); - onLoginSuccess(res); - navigate('/'); - } catch (e) { - // 비밀번호 불일치시 메시지 - setIsError(true); - setTimeout(() => { - setIsError(false); - }, 4000); - console.error(e); - } finally { - setUserInput(prev => ({ ...prev, password: '' })); - } + const handleLogin = () => { + // 로그인 함수 호출 + userLogin(userInput, setUserInput, setIsError, navigate, setUserInfo); }; const handleKeyDown = (e: React.KeyboardEvent) => { diff --git a/src/pages/login/functions/index.ts b/src/pages/login/functions/index.ts index e2dc1d5..c398133 100644 --- a/src/pages/login/functions/index.ts +++ b/src/pages/login/functions/index.ts @@ -1,14 +1,75 @@ -import Axios from '@/apis'; import { AxiosResponse } from 'axios'; +import { NavigateFunction } from 'react-router-dom'; +import Cookies from 'js-cookie'; + +import Axios from '@/apis'; +import { SetterOrUpdater } from 'recoil'; +import { UserInfoType } from '@/types'; -export const onLoginSuccess = (res: AxiosResponse) => { - const { accessToken, refreshToken } = res?.data?.result; - refreshToken; - Axios.defaults.headers.common['Authorization'] = `Bearer ${accessToken}`; +export const userLogin = async ( + userInput: { id: string; password: string }, + setUserInput: React.Dispatch< + React.SetStateAction<{ id: string; password: string }> + >, + setIsError: React.Dispatch>, + navigate: NavigateFunction, + setUserInfo: SetterOrUpdater, +) => { + try { + const res = await Axios.post('/auth/login', null, { + params: { loginId: userInput.id, loginPassword: userInput.password }, + }); + onLoginSuccess(res, setUserInfo); + setUserInput({ id: '', password: '' }); + navigate('/'); + } catch (e) { + // 비밀번호 불일치시 메시지 + setIsError(true); + setTimeout(() => { + setIsError(false); + }, 4000); + console.error(e); + } finally { + setUserInput(prev => ({ ...prev, password: '' })); + } }; -// export const onSilentRefresh = async () => { -// try { -// const res = await Axios.post(TOKEN) -// } -// } +export const onLoginSuccess = ( + res: AxiosResponse, + setUserInfo: SetterOrUpdater | null, +) => { + if (res) { + const { + accessToken, + refreshToken, + id, + nickName, + email, + name, + phoneNumber, + } = res?.data?.result; + + const currentTime = new Date(); + + // header에 accessToken 세팅 + Axios.defaults.headers.common['Authorization'] = `Bearer ${accessToken}`; + + // refreshToken 쿠키에 저장 + Cookies.set('refreshToken', refreshToken, { expires: 1 }); + + // localStroage에 토큰 만료시간 저장 + const expireTime = new Date(currentTime.getTime() + 1 * 60 * 60 * 1000); + localStorage.setItem('expireToken', expireTime.toString()); + + // 최초 로그인시 recoil에 유저 정보 저장 + if (setUserInfo) { + setUserInfo({ + id, + nickName, + email, + name, + phoneNumber, + }); + } + } +}; diff --git a/src/pages/user/MyPosting/index.tsx b/src/pages/user/MyPosting/index.tsx index 89760ac..03516df 100644 --- a/src/pages/user/MyPosting/index.tsx +++ b/src/pages/user/MyPosting/index.tsx @@ -1,6 +1,5 @@ import styled from 'styled-components'; -import PostingList from '../../board/PostingList'; -import { tempData } from '../../board/Board'; +// import PostingList from '../../board/PostingList'; import { useState } from 'react'; import PageBar from '@/components/PageBar/PageBar'; @@ -10,7 +9,7 @@ const MyPosting = () => { return ( {'내가 쓴 글'} - + {/* */} ); diff --git a/src/recoil/LoginAtom.ts b/src/recoil/LoginAtom.ts new file mode 100644 index 0000000..52dc2e2 --- /dev/null +++ b/src/recoil/LoginAtom.ts @@ -0,0 +1,21 @@ +import { UserInfoType } from '@/types'; +import { atom } from 'recoil'; +import { recoilPersist } from 'recoil-persist'; + +const { persistAtom } = recoilPersist({ + key: 'persist-atom-key', + storage: localStorage, +}); + +// 사용자 정보를 저장하는 Atom +export const UserAtom = atom({ + key: 'UserAtom', + default: { + id: -1, + nickName: '', + email: '', + name: '', + phoneNumber: '', + }, + effects_UNSTABLE: [persistAtom], +}); diff --git a/src/types/index.ts b/src/types/index.ts index 93e9fe6..a1c104e 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -9,12 +9,11 @@ export interface ProgramMainInfoType { } export interface PostingType { - boardName: string; + category: string; title: string; id: number; - nickName: string; - registerDate: string; - type: string; + writer: string; + createdDate: string; } export interface ProgramDetailInfoType extends ProgramMainInfoType { @@ -132,3 +131,12 @@ export interface TipDataType { title: string; content: string[]; } + +// 사용자 정보 관련 타입 +export interface UserInfoType { + id: number; + nickName: string; + email: string; + name: string; + phoneNumber: string; +}