From 524d6572b5bc21d90b64bed3212fceee6d8de84c Mon Sep 17 00:00:00 2001 From: sujikim Date: Thu, 20 Jul 2023 15:54:13 +0900 Subject: [PATCH 01/22] =?UTF-8?q?chore:=20=EB=B6=88=ED=95=84=EC=9A=94?= =?UTF-8?q?=ED=95=9C=20index.css=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/index.css | 13 ------------- src/index.tsx | 1 - 2 files changed, 14 deletions(-) delete mode 100644 src/index.css diff --git a/src/index.css b/src/index.css deleted file mode 100644 index ec2585e8..00000000 --- a/src/index.css +++ /dev/null @@ -1,13 +0,0 @@ -body { - margin: 0; - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', - 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', - sans-serif; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; -} - -code { - font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', - monospace; -} diff --git a/src/index.tsx b/src/index.tsx index 7628acc5..594e509a 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -4,7 +4,6 @@ import { RecoilRoot } from "recoil"; import * as Sentry from "@sentry/react"; import { BrowserTracing } from "@sentry/tracing"; import App from "./App"; -import "./index.css"; Sentry.init({ dsn: import.meta.env.REACT_APP_SENTRY, From 1b5c241875fb9830ed42d24dd313658d2ad7b111 Mon Sep 17 00:00:00 2001 From: scarf Date: Wed, 20 Sep 2023 14:19:05 +0900 Subject: [PATCH 02/22] =?UTF-8?q?feat:=20=EB=8F=84=EC=84=9C=20=EC=83=81?= =?UTF-8?q?=ED=83=9C=20=EC=A0=95=EB=B3=B4=EB=A5=BC=20=EC=B2=AD=EA=B5=AC?= =?UTF-8?q?=EA=B8=B0=ED=98=B8=20=EC=88=9C=EC=9C=BC=EB=A1=9C=20=EC=A0=95?= =?UTF-8?q?=EB=A0=AC=20(#549)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/component/book/BookDetail.tsx | 26 +++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/src/component/book/BookDetail.tsx b/src/component/book/BookDetail.tsx index aa5f0502..fb38a243 100644 --- a/src/component/book/BookDetail.tsx +++ b/src/component/book/BookDetail.tsx @@ -9,6 +9,20 @@ import Image from "~/component/utils/Image"; import Like from "~/component/book/like/Like"; import TagWrapper from "~/component/book/tag/TagWrapper"; import "~/asset/css/BookDetail.css"; +import { Book } from "~/type"; + +const callsignToNumbers = (callSign: string) => + callSign + .replace(/[^0-9\.]/g, "") + .split(".") + .map(Number); + +const compareCallsign = (a: Book, b: Book) => { + const xs = callsignToNumbers(a.callSign); + const ys = callsignToNumbers(b.callSign); + + return xs.reduce((sum, x, i) => sum + (x - ys[i]), 0); +}; const BookDetail = () => { const id = useParams().id || ""; @@ -104,9 +118,15 @@ const BookDetail = () => {
- {bookDetailInfo.books?.map((book, index) => ( - - ))} + {bookDetailInfo.books + ?.toSorted((a, b) => compareCallsign(a, b)) + .map((book, index) => ( + + ))}
From 16739434e35d581b74837bf6c0ac653fa295f7f9 Mon Sep 17 00:00:00 2001 From: sujikim Date: Mon, 23 Oct 2023 21:15:39 +0900 Subject: [PATCH 03/22] =?UTF-8?q?refactor:=20search/CategoryFilter=20?= =?UTF-8?q?=EA=B0=80=EB=8F=85=EC=84=B1=20=EA=B0=9C=EC=84=A0=20(#535)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. 변수명 변경 "userCate" 같은 모호한 표현 대신 selectedCategory 등으로 명시 2. DOM 접근방식 개선 querySelector -> useRef 3. 복잡한 for문의 합계 대신 array filter 및 getBoundingClientRect를 활용 --- src/component/search/Category.tsx | 34 +++ src/component/search/CategoryFilter.tsx | 280 +++++++----------------- src/component/search/Search.tsx | 6 +- 3 files changed, 118 insertions(+), 202 deletions(-) create mode 100644 src/component/search/Category.tsx diff --git a/src/component/search/Category.tsx b/src/component/search/Category.tsx new file mode 100644 index 00000000..2b8040cd --- /dev/null +++ b/src/component/search/Category.tsx @@ -0,0 +1,34 @@ +import { useSearchParams } from "react-router-dom"; + +type CategoryProps = { + isSelected: boolean; + name: string; + count: number; +}; + +const Category = ({ isSelected, name, count }: CategoryProps) => { + const [searchParams, setSearchParams] = useSearchParams(); + + const changeFilter = () => { + searchParams.set("category", name); + setSearchParams(searchParams); + }; + + return ( + + ); +}; + +export default Category; diff --git a/src/component/search/CategoryFilter.tsx b/src/component/search/CategoryFilter.tsx index 620d21e5..419edf5d 100644 --- a/src/component/search/CategoryFilter.tsx +++ b/src/component/search/CategoryFilter.tsx @@ -1,226 +1,110 @@ -import { useEffect, useState } from "react"; -import { useNavigate } from "react-router-dom"; +import { useEffect, useRef, useState } from "react"; import Image from "../utils/Image"; +import Category from "./Category"; import ArrLeftGray from "../../asset/img/arrow_left_gray.svg"; import ArrLeftBlack from "../../asset/img/arrow_left_black.svg"; import ArrRightGray from "../../asset/img/arrow_right_gray.svg"; import ArrRightBlack from "../../asset/img/arrow_right_black.svg"; import "../../asset/css/CategoryFilter.css"; -const MARGIN_OF_CATEGORY_BUTTON = 36; -const EPSILON = 1; - -type PreCategoryProps = { - startOfScroll: boolean; +type Props = { + selectedCategory: number; + categoryList: { name: string; count: number }[]; }; -const PreCategory = ({ startOfScroll }: PreCategoryProps) => { - const scrollToPre = () => { - const categories = document.querySelector(".categories"); - if (categories) { - const categoriesScrollX = categories.scrollLeft; - - const categoryButton = document.getElementsByClassName("category-button"); - const categoryButtonWidth = Array.from(categoryButton).map( - items => items.clientWidth + MARGIN_OF_CATEGORY_BUTTON, - ); - - let sumOfCategory = 0; - // eslint-disable-next-line no-plusplus - for (let index = 0; index < categoryButtonWidth.length; index++) { - if ( - sumOfCategory + categoryButtonWidth[index] + EPSILON >= - categoriesScrollX - ) { - break; - } - sumOfCategory += categoryButtonWidth[index]; - } - categories.scrollTo({ left: sumOfCategory, top: 0, behavior: "smooth" }); - } - }; - - return ( - - ); -}; - -type NextCategoryProps = { - endOfScroll: boolean; -}; - -const NextCategory = ({ endOfScroll }: NextCategoryProps) => { - const scrollToNext = () => { - const categories = document.querySelector(".categories"); - if (categories) { - const categoriesScrollX = categories.scrollLeft; - const categoriesOffsetWidth = categories.offsetWidth; - const categoriesScrollWidth = categories.scrollWidth; - const endOfScrollWidth = categoriesScrollWidth - categoriesOffsetWidth; - - const categoryButton = document.getElementsByClassName("category-button"); - const categoryButtonWidth = Array.from(categoryButton).map( - items => items.clientWidth + MARGIN_OF_CATEGORY_BUTTON, - ); - - let sumOfCategory = 0; - // eslint-disable-next-line no-plusplus - for (let index = 0; index < categoryButtonWidth.length; index++) { - sumOfCategory += categoryButtonWidth[index]; - if (sumOfCategory - EPSILON > categoriesScrollX) { - break; - } - } - categories.scrollTo({ - left: - sumOfCategory > endOfScrollWidth ? endOfScrollWidth : sumOfCategory, - top: 0, - behavior: "smooth", +const CategoryFilter = ({ selectedCategory, categoryList }: Props) => { + const categoriesRef = useRef(null); + // categoriesRef의 스크롤이 처음이거나 끝인지 판단하는 state + const [scrollPosition, setScrollPosition] = useState({ + isScrollAtStart: true, + isScrollAtEnd: true, + }); + + const updateScrollPosition = () => { + const target = categoriesRef.current; + if (target) { + const endOfScroll = target.scrollWidth - target.offsetWidth; + setScrollPosition({ + isScrollAtStart: target.scrollLeft === 0, + isScrollAtEnd: target.scrollLeft === endOfScroll, }); } }; - return ( - - ); -}; - -type CategoryProps = { - userWord: string; - userSort: string; - userCate: number; - categoryIndex: number; - categoryName: string; - categoryNum: number; -}; - -const Category = ({ - userWord, - userSort, - userCate, - categoryIndex, - categoryName, - categoryNum, -}: CategoryProps) => { - const navigate = useNavigate(); - - const changeFilter = () => { - navigate( - `?search=${userWord}&page=${1}&category=${categoryName}&sort=${userSort}`, - ); - }; - - return ( - - ); -}; - -type CategoryFilterProps = { - userWord: string; - userSort: string; - userCate: number; - entireCate: { name: string; count: number }[]; -}; - -const CategoryFilter = ({ - userWord, - userSort, - userCate, - entireCate, -}: CategoryFilterProps) => { - const [startOfScroll, setStartOfScroll] = useState(true); - const [endOfScroll, setEndOfScroll] = useState(true); - - const setScrollState = () => { - const categories = document.querySelector(".categories"); - if (categories) { - const categoriesScrollX = categories.scrollLeft; - const categoriesOffsetWidth = categories.offsetWidth; - const categoriesScrollWidth = categories.scrollWidth; - const endOfScrollWidth = categoriesScrollWidth - categoriesOffsetWidth; - - if (categoriesScrollX === 0) { - setStartOfScroll(true); - } else { - setStartOfScroll(false); - } - - if (categoriesScrollX === endOfScrollWidth) { - setEndOfScroll(true); - } else { - setEndOfScroll(false); - } - } - }; - useEffect(() => { - setScrollState(); - window.addEventListener("resize", setScrollState); + updateScrollPosition(); + // 윈도우 창 크기가 변할 때마다 updateScrollPosition 함수를 실행 + window.addEventListener("resize", updateScrollPosition); return () => { - window.removeEventListener("resize", setScrollState); + window.removeEventListener("resize", updateScrollPosition); }; - }, [setScrollState]); + }, [categoriesRef]); + + const scrollTo = (direction: "prev" | "next") => { + if (categoriesRef.current) { + const childElements = Array.from(categoriesRef.current.children); + + // categories의 왼쪽 경계값 기준 + const edge = categoriesRef.current.getBoundingClientRect().left; + const target = + direction === "prev" + ? childElements + // 경계보다 작은 left값을 가진 마지막 요소 + .filter(e => Math.ceil(e.getBoundingClientRect().left) < edge) + .slice(-1)[0] + : childElements + // 경계보다 큰 left값을 가진 첫번째 요소 + .filter(e => Math.floor(e.getBoundingClientRect().left) > edge) + .slice()[0]; + + // 해당 요소로 가로 스크롤 부드럽게 이동 + target?.scrollIntoView({ + inline: "start", + block: "nearest", + behavior: "smooth", + }); + } + }; return (
- -
- {entireCate.map((items, index) => ( + +
+ {categoryList.map((items, index) => ( ))}
- +
diff --git a/src/component/search/Search.tsx b/src/component/search/Search.tsx index a2dd06d5..c7da788f 100644 --- a/src/component/search/Search.tsx +++ b/src/component/search/Search.tsx @@ -68,10 +68,8 @@ const Search = () => { />
From dc2e98572c4ee211f983fe1194321895a1be8320 Mon Sep 17 00:00:00 2001 From: scarf Date: Mon, 23 Oct 2023 21:27:52 +0900 Subject: [PATCH 04/22] =?UTF-8?q?refactor:=20recoil=20localstorage=20effec?= =?UTF-8?q?t=20=EC=A0=81=EC=9A=A9=20(#492)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: recoil `localStorageEffect` * refactor: `userState` -> `userAtom` 및 localStorageEffect 적용 * refactor: useGetAuthMe에 userAtom 적용 * refactor: useGetReviewInfo에 userAtom 적용 * refactor: PostReview에 userAtom 적용 * refactor: Tag에 userAtom 적용 * refactor: ELibraryHeader에 적용 * refactor: Logout에 localStorageEffect 적용 * refactor: MyRent에 userIdAtom 적용 * refactor: editEmail에 userAtom 적용 * refactor: Mypage에 userIdAtom 적용 * refactor: MyReservation에 userIdAtom 적용 * refactor: HandleReview userAtom 적용 * refactor: HeaderDefault에 isUserAuthedAtom 적용 * refactor: HeaderDefaultLNB에 userAtom 적용 * refactor: HeaderModal에 userAtom 적용 * refactor: usePermission에 userAtom 적용 * feat: App 인증 로직에 recoil localStorageEffect 적용 * refactor: LimitedRoute에 userAtom 적용 --- src/{App.jsx => App.tsx} | 32 +++++++------- src/LimitedRoute.tsx | 12 ++--- src/api/auth/useGetAuthMe.ts | 14 +++--- ...seGetReviewInfo.js => useGetReviewInfo.ts} | 8 ++-- src/atom/userAtom.ts | 44 +++++++++++++++++++ src/atom/userState.ts | 16 ------- src/component/book/review/PostReview.tsx | 13 +++--- src/component/book/tag/Tag.tsx | 25 +++++------ ...{ELibraryHeader.jsx => ELibraryHeader.tsx} | 21 ++++----- src/component/login/Logout.tsx | 19 +++----- src/component/mypage/EditEmailOrPassword.tsx | 25 +++++------ src/component/mypage/MyRentInfo/MyRent.tsx | 5 ++- src/component/mypage/MyReservation.tsx | 11 ++--- src/component/mypage/Mypage.tsx | 5 ++- src/component/utils/HandleReview.tsx | 20 ++++----- src/component/utils/HeaderDefault.tsx | 8 ++-- src/component/utils/HeaderDefaultLNB.tsx | 4 +- src/component/utils/HeaderModal.tsx | 12 ++--- src/hook/usePermission.ts | 4 +- src/type/User.ts | 6 +-- src/util/localStorageEffect.ts | 21 +++++++++ 21 files changed, 180 insertions(+), 145 deletions(-) rename src/{App.jsx => App.tsx} (83%) rename src/api/reviews/{useGetReviewInfo.js => useGetReviewInfo.ts} (73%) create mode 100644 src/atom/userAtom.ts delete mode 100644 src/atom/userState.ts rename src/component/eLibraryIn42Box/{ELibraryHeader.jsx => ELibraryHeader.tsx} (79%) create mode 100644 src/util/localStorageEffect.ts diff --git a/src/App.jsx b/src/App.tsx similarity index 83% rename from src/App.jsx rename to src/App.tsx index a7371532..a4c57429 100644 --- a/src/App.jsx +++ b/src/App.tsx @@ -1,6 +1,6 @@ import { useEffect } from "react"; import { BrowserRouter, Route, Routes } from "react-router-dom"; -import { useSetRecoilState } from "recoil"; +import { useRecoilState, useRecoilValue, useResetRecoilState, useSetRecoilState } from "recoil"; import { install } from "ga-gtag"; import BookDetail from "./component/book/BookDetail"; import Footer from "./component/utils/Footer"; @@ -18,31 +18,29 @@ import History from "./component/history/History"; import ReservedLoan from "./component/reservedloan/ReservedLoan"; import ReturnBook from "./component/return/ReturnBook"; import UserManagement from "./component/userManagement/UserManagement"; -import AddBook from "./component/addbook/AddBook"; -import MyPageRoutes from "./component/mypage/MyPageRoutes"; -import userState from "./atom/userState"; -import Mypage from "./component/mypage/Mypage"; -import EditEmailOrPassword from "./component/mypage/EditEmailOrPassword"; -import LimitedRoute from "./LimitedRoute"; -import { isExpiredDate } from "./util/date"; -import BookManagement from "./component/bookManagement/BookManagement"; -import ReviewManagement from "./component/reviewManagement/ReviewManagement"; import BookStock from "./component/bookStock/BookStock"; import ELibraryIn42Box from "./component/eLibraryIn42Box/EventPage"; import SuperTagManagement from "./component/superTag/SuperTagManagement"; import SubTagManagement from "./component/subTag/SubTagManagement"; import Portals from "./component/utils/Portals"; import "./asset/css/reset.css"; +import LimitedRoute from "./LimitedRoute" +import { isUserExpiredAtom, userAtom } from "./atom/userAtom" +import AddBook from "./component/addbook/AddBook" +import BookManagement from "./component/bookManagement/BookManagement" +import EditEmailOrPassword from "./component/mypage/EditEmailOrPassword" +import MyPageRoutes from "./component/mypage/MyPageRoutes" +import Mypage from "./component/mypage/Mypage" +import ReviewManagement from "./component/reviewManagement/ReviewManagement" function App() { - const setUser = useSetRecoilState(userState); - useEffect(() => { - install(import.meta.env.REACT_APP_GA_ID); - const localUser = JSON.parse(window.localStorage.getItem("user")); + const isUserExpired = useRecoilValue(isUserExpiredAtom); + const resetUser = useResetRecoilState(userAtom); - if (localUser?.isLogin) { - if (!isExpiredDate(localUser?.expire)) setUser(localUser); - else window.localStorage.removeItem("user"); + useEffect(() => install(import.meta.env.REACT_APP_GA_ID), []); + useEffect(() => { + if (isUserExpired) { + resetUser(); } }, []); diff --git a/src/LimitedRoute.tsx b/src/LimitedRoute.tsx index fad0b973..39d12565 100644 --- a/src/LimitedRoute.tsx +++ b/src/LimitedRoute.tsx @@ -1,5 +1,7 @@ import { Navigate, Outlet } from "react-router-dom"; import NotFound from "./component/utils/NotFound"; +import { useRecoilValue } from "recoil"; +import { userAtom } from "./atom/userAtom"; type Props = { isLoginOnly?: boolean; @@ -7,14 +9,8 @@ type Props = { isLogoutOnly?: boolean; }; -const LimitedRoute = ({ - isLoginOnly, - isAdminOnly, - isLogoutOnly, -}: Props) => { - // 로그인 정보를 확인 - // recoil 전역상태는 새로고침시 초기화되기 때문에 로컬스토리지 참고 - const user = JSON.parse(window.localStorage.getItem("user")); +const LimitedRoute = ({ isLoginOnly, isAdminOnly, isLogoutOnly }: Props) => { + const user = useRecoilValue(userAtom); if (isAdminOnly && !user?.isAdmin) { return ; diff --git a/src/api/auth/useGetAuthMe.ts b/src/api/auth/useGetAuthMe.ts index e1fa931f..6ebf9ab1 100644 --- a/src/api/auth/useGetAuthMe.ts +++ b/src/api/auth/useGetAuthMe.ts @@ -1,16 +1,17 @@ import { useSetRecoilState } from "recoil"; -import { useApi } from "../../hook/useApi"; -import { addHourDateObject } from "../../util/date"; -import userState from "../../atom/userState"; -import getErrorMessage from "../../constant/error"; +import { useApi } from "~/hook/useApi"; +import { addHourDateObject } from "~/util/date"; +import { userAtom } from "~/atom/userAtom"; +import getErrorMessage from "~/constant/error"; +import { UserState } from "~/type"; export const useGetAuthMe = () => { const { request } = useApi("get", "auth/me"); - const setUser = useSetRecoilState(userState); + const setUser = useSetRecoilState(userAtom); const onSuccess = (response: any) => { const { data } = response; - const newUser = { + const newUser: UserState = { isLogin: true, id: data.id, userName: data.intra, @@ -19,7 +20,6 @@ export const useGetAuthMe = () => { expire: addHourDateObject(new Date(), 8).toISOString(), }; setUser(newUser); - window.localStorage.setItem("user", JSON.stringify(newUser)); }; const onError = (error: any) => { diff --git a/src/api/reviews/useGetReviewInfo.js b/src/api/reviews/useGetReviewInfo.ts similarity index 73% rename from src/api/reviews/useGetReviewInfo.js rename to src/api/reviews/useGetReviewInfo.ts index 672b8b40..70517833 100644 --- a/src/api/reviews/useGetReviewInfo.js +++ b/src/api/reviews/useGetReviewInfo.ts @@ -1,17 +1,19 @@ import { useEffect, useState } from "react"; -import { useApi } from "../../hook/useApi"; +import { useApi } from "~/hook/useApi"; +import { userIdAtom } from "~/atom/userAtom"; +import { useRecoilValue } from "recoil"; export const useGetReviewInfo = () => { const [page, setPage] = useState(1); const [lastPage, setLastPage] = useState(null); const [reviewList, setReviewList] = useState([]); - const userId = JSON.parse(window.localStorage.getItem("user")).id; + const userId = useRecoilValue(userIdAtom); const { request } = useApi("get", `reviews`, { userId, page: page - 1, }); - const refineResponse = response => { + const refineResponse = (response: any) => { const info = response.data.items; const { totalPages } = response.data.meta; setReviewList(info); diff --git a/src/atom/userAtom.ts b/src/atom/userAtom.ts new file mode 100644 index 00000000..4cc1117a --- /dev/null +++ b/src/atom/userAtom.ts @@ -0,0 +1,44 @@ +import { atom, selector } from "recoil"; +import type { UserState } from "../type"; +import { localStorageEffect } from "../util/localStorageEffect"; +import { isExpiredDate } from "~/util/date"; + +const defaultUserState: UserState = { + isLogin: false, + id: 0, + userName: undefined, + email: undefined, + isAdmin: false, + expire: undefined, +}; + +// TODO: UserState | null 타입 사용 +export const userAtom = atom({ + key: "user", + default: defaultUserState, + effects: [localStorageEffect("user")], +}); + +export const userIdAtom = selector({ + key: "userId", + get: ({ get }) => get(userAtom).id, +}); + +export const isUserExpiredAtom = selector({ + key: "isUserExpired", + get: ({ get }) => { + const user = get(userAtom); + + return isExpiredDate(user.expire ?? "1970-01-01T00:00:00.000Z"); + }, +}); + +export const isUserAuthedAtom = selector({ + key: "isUserAuthed", + get: ({ get }) => { + const user = get(userAtom); + const isExpired = get(isUserExpiredAtom); + + return !isExpired && user.isLogin; + }, +}); diff --git a/src/atom/userState.ts b/src/atom/userState.ts deleted file mode 100644 index b73c638b..00000000 --- a/src/atom/userState.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { atom } from "recoil"; -import type { UserState } from "../type"; - -const userState = atom({ - key: "userState", - default: { - isLogin: false, - id: 0, - userName: "", - email: "", - isAdmin: false, - expire: "", - }, -}); - -export default userState; diff --git a/src/component/book/review/PostReview.tsx b/src/component/book/review/PostReview.tsx index 64822d7a..fa832771 100644 --- a/src/component/book/review/PostReview.tsx +++ b/src/component/book/review/PostReview.tsx @@ -1,10 +1,11 @@ import { FormEventHandler } from "react"; -import "../../../asset/css/Review.css"; -import Button from "../../utils/Button"; -import { useNewDialog } from "../../../hook/useNewDialog"; import { useRecoilValue } from "recoil"; -import userState from "../../../atom/userState"; -import { usePostReview } from "../../../api/reviews/usePostReview"; + +import "~/asset/css/Review.css"; +import { useNewDialog } from "~/hook/useNewDialog"; +import { userAtom } from "~/atom/userAtom"; +import { usePostReview } from "~/api/reviews/usePostReview"; +import Button from "~/component/utils/Button" type Props = { bookInfoId: number; @@ -17,7 +18,7 @@ const PostReview = ({ bookInfoId, resetTab }: Props) => { resetTab, }); - const user = useRecoilValue(userState); + const user = useRecoilValue(userAtom); const hasPermissionToPostReview = user && user.userName !== user.email; // 인증된 유저는 이메일과 다른 닉네임을 가짐 const isValidLength = content.length >= 10 && content.length <= 420; const { addDialogWithTitleAndMessage, addConfirmDialog } = useNewDialog(); diff --git a/src/component/book/tag/Tag.tsx b/src/component/book/tag/Tag.tsx index 9f5d6459..40dab343 100644 --- a/src/component/book/tag/Tag.tsx +++ b/src/component/book/tag/Tag.tsx @@ -1,16 +1,15 @@ -import { MouseEventHandler } from "react"; -import { useState } from "react"; -import { TagType } from "../../../type/TagType"; +import { useState, MouseEventHandler } from "react"; import { useNavigate } from "react-router-dom"; -import { useRecoilValue } from "recoil"; -import Tooltip from "../../utils/Tooltip"; -import userState from "../../../atom/userState"; -import "../../../asset/css/Tags.css"; -import { useApi } from "../../../hook/useApi"; import { AxiosResponse } from "axios"; +import { TagType } from "~/type/TagType"; +import { useRecoilValue } from "recoil"; +import Tooltip from "~/component/utils/Tooltip"; +import { userAtom } from "~/atom/userAtom"; +import { useApi } from "~/hook/useApi"; -import minusicon from "../../../asset/img/tag_minus_white.svg"; -import trashicon from "../../../asset/img/trash_white.svg"; +import minusicon from "~/asset/img/tag_minus_white.svg"; +import trashicon from "~/asset/img/trash_white.svg"; +import "~/asset/css/Tags.css"; type TagProps = TagType & { tagData: TagType[]; @@ -29,7 +28,7 @@ const Tag = ({ setTagData, }: TagProps) => { const navigate = useNavigate(); - const currentLogin = useRecoilValue(userState); + const { userName } = useRecoilValue(userAtom); const [clickDeleteTag, setClickDeleteTag] = useState(false); const [icon, setIcon] = useState(minusicon); const { request } = useApi("delete", `/tags/sub/${id}`); @@ -56,11 +55,11 @@ const Tag = ({ const isType = () => { if (type === "super") return "super"; - else if (login === currentLogin.userName) return "my-sub"; + else if (login === userName) return "my-sub"; return "sub"; }; - const isMysub = login === currentLogin.userName; + const isMysub = login === userName; const onClickImage: MouseEventHandler = e => { e.stopPropagation(); diff --git a/src/component/eLibraryIn42Box/ELibraryHeader.jsx b/src/component/eLibraryIn42Box/ELibraryHeader.tsx similarity index 79% rename from src/component/eLibraryIn42Box/ELibraryHeader.jsx rename to src/component/eLibraryIn42Box/ELibraryHeader.tsx index 62194347..7bc0893b 100644 --- a/src/component/eLibraryIn42Box/ELibraryHeader.jsx +++ b/src/component/eLibraryIn42Box/ELibraryHeader.tsx @@ -1,21 +1,18 @@ import { useState } from "react"; import { useRecoilValue } from "recoil"; import { Link } from "react-router-dom"; -import userState from "../../atom/userState"; -import Logo from "../../asset/img/jiphyeonjeon_logo_without_text.svg"; +import { userAtom } from "~/atom/userAtom"; +import Logo from "~/asset/img/jiphyeonjeon_logo_without_text.svg"; -const ELibraryHeader = ({ setModalOpened }) => { - const { isLogin } = useRecoilValue(userState); +type Props = { + setModalOpened: (value: boolean) => void; +}; + +const ELibraryHeader = ({ setModalOpened }: Props) => { + const { isLogin } = useRecoilValue(userAtom); const [isFixed, setFixed] = useState(false); - const stickyHeader = () => { - if (window.pageYOffset > 140) { - setFixed(true); - } else { - setFixed(false); - } - }; - window.onscroll = stickyHeader; + window.onscroll = () => setFixed(window.scrollY > 140); return ( <> diff --git a/src/component/login/Logout.tsx b/src/component/login/Logout.tsx index 0c3b606f..78422952 100644 --- a/src/component/login/Logout.tsx +++ b/src/component/login/Logout.tsx @@ -1,25 +1,16 @@ import { useEffect } from "react"; import { Navigate } from "react-router-dom"; import { useResetRecoilState } from "recoil"; -import { usePostAuthLogout } from "../../api/auth/usePostAuthLogout"; -import userState from "../../atom/userState"; +import { usePostAuthLogout } from "~/api/auth/usePostAuthLogout"; +import { userAtom } from "~/atom/userAtom"; const Logout = () => { - const resetState = useResetRecoilState(userState); + const resetUser = useResetRecoilState(userAtom); const requestLogout = usePostAuthLogout(); - useEffect(() => { - requestLogout(() => { - resetState(); - window.localStorage.removeItem("user"); - }); - }, []); + useEffect(() => requestLogout(resetUser), []); - return ( - <> - - - ); + return ; }; export default Logout; diff --git a/src/component/mypage/EditEmailOrPassword.tsx b/src/component/mypage/EditEmailOrPassword.tsx index b12b90ce..f04c0f49 100644 --- a/src/component/mypage/EditEmailOrPassword.tsx +++ b/src/component/mypage/EditEmailOrPassword.tsx @@ -1,11 +1,13 @@ -import { useState, useMemo, ChangeEventHandler, FormEventHandler } from "react"; +import { useState, ChangeEventHandler, FormEventHandler } from "react"; +import { useRecoilValue } from "recoil"; import { useNavigate, useParams } from "react-router-dom"; -import { usePatchUsersMyupdate } from "../../api/users/usePatchUsersMyupdate"; -import { useNewDialog } from "../../hook/useNewDialog"; -import Image from "../utils/Image"; -import { registerRule } from "../../constant/validate"; -import arrowLeft from "../../asset/img/arrow_left_black.svg"; -import "../../asset/css/EditEmailOrPassword.css"; +import { usePatchUsersMyupdate } from "~/api/users/usePatchUsersMyupdate"; +import { useNewDialog } from "~/hook/useNewDialog"; +import Image from "~/component/utils/Image"; +import { registerRule } from "~/constant/validate"; +import arrowLeft from "~/asset/img/arrow_left_black.svg"; +import { userAtom } from "~/atom/userAtom" +import "~/asset/css/EditEmailOrPassword.css"; function EditEmailOrPassword() { const { mode } = useParams(); @@ -17,10 +19,7 @@ function EditEmailOrPassword() { check: "", }); - const userInfo = useMemo( - () => JSON.parse(window.localStorage.getItem("user") || "{}"), - [], - ); + const { email } = useRecoilValue(userAtom); const onChangeInput: ChangeEventHandler = e => { const { value } = e.currentTarget; @@ -60,7 +59,7 @@ function EditEmailOrPassword() {
- {`${userInfo ? userInfo.email : "-"}님의, `} + {`${email ?? "-"}님의, `} {`${modeStringKorean} 변경 페이지입니다`}
{mode === "email" ? ( @@ -68,7 +67,7 @@ function EditEmailOrPassword() {
현재 이메일 - {userInfo ? userInfo.email : "-"} + {email ?? "-"}
diff --git a/src/component/mypage/MyRentInfo/MyRent.tsx b/src/component/mypage/MyRentInfo/MyRent.tsx index 09c43651..0cce2589 100644 --- a/src/component/mypage/MyRentInfo/MyRent.tsx +++ b/src/component/mypage/MyRentInfo/MyRent.tsx @@ -3,10 +3,11 @@ import RentHistory from "./RentHistory"; import RentedOrReservedBooks from "./RentedOrReservedBooks"; import InquireBoxTitle from "~/component/utils/InquireBoxTitle"; import Book from "~/asset/img/admin_icon.svg"; +import { useRecoilValue } from "recoil" +import { userIdAtom } from "~/atom/userAtom" const MyRent = () => { - const user = window.localStorage.getItem("user"); - const userId = user && JSON.parse(user).id; + const userId = useRecoilValue(userIdAtom); const { userInfo } = useGetUsersSearchId({ userId }); return ( diff --git a/src/component/mypage/MyReservation.tsx b/src/component/mypage/MyReservation.tsx index ed52eec3..31d488f0 100644 --- a/src/component/mypage/MyReservation.tsx +++ b/src/component/mypage/MyReservation.tsx @@ -1,11 +1,12 @@ -import { useGetUsersSearchId } from "../../api/users/useGetUsersSearchId"; +import { useGetUsersSearchId } from "~/api/users/useGetUsersSearchId"; import RentedOrReservedBooks from "./MyRentInfo/RentedOrReservedBooks"; -import InquireBoxTitle from "../utils/InquireBoxTitle"; -import Reserve from "../../asset/img/list-check-solid.svg"; +import InquireBoxTitle from "~/component/utils/InquireBoxTitle"; +import Reserve from "~/asset/img/list-check-solid.svg"; +import { useRecoilValue } from "recoil" +import { userIdAtom } from "~/atom/userAtom" const MyReservation = () => { - const user = window.localStorage.getItem("user"); - const userId = user && JSON.parse(user).id; + const userId = useRecoilValue(userIdAtom); const { userInfo } = useGetUsersSearchId({ userId }); return ( diff --git a/src/component/mypage/Mypage.tsx b/src/component/mypage/Mypage.tsx index 7a1fcb8f..ee778265 100644 --- a/src/component/mypage/Mypage.tsx +++ b/src/component/mypage/Mypage.tsx @@ -13,6 +13,8 @@ import InquireBoxTitle from "../utils/InquireBoxTitle"; import getErrorMessage from "../../constant/error"; import Login from "../../asset/img/login_icon_white.svg"; import "../../asset/css/Mypage.css"; +import { useRecoilValue } from "recoil"; +import { userIdAtom } from "~/atom/userAtom" const Mypage = () => { const { currentTab, changeTab } = useTabFocus(0, myPageTabList); @@ -23,8 +25,7 @@ const Mypage = () => { myReservation: , myReview: , }; - const user = window.localStorage.getItem("user"); - const userId = user && JSON.parse(user).id; + const userId = useRecoilValue(userIdAtom); const { userInfo } = useGetUsersSearchId({ userId }); const [deviceMode, setDeviceMode] = useState("desktop"); diff --git a/src/component/utils/HandleReview.tsx b/src/component/utils/HandleReview.tsx index 211305b8..2ac47cc4 100644 --- a/src/component/utils/HandleReview.tsx +++ b/src/component/utils/HandleReview.tsx @@ -1,14 +1,14 @@ import { useState } from "react"; -import { dateFormat } from "../../util/date"; +import { dateFormat } from "~/util/date"; import Image from "./Image"; -import UserEdit from "../../asset/img/edit.svg"; -import DeleteButton from "../../asset/img/x_button.svg"; -import "../../asset/css/Review.css"; -import { Review } from "../../type"; -import { useNewDialog } from "../../hook/useNewDialog"; -import userState from "../../atom/userState"; +import UserEdit from "~/asset/img/edit.svg"; +import DeleteButton from "~/asset/img/x_button.svg"; +import "~/asset/css/Review.css"; +import { Review } from "~/type"; +import { useNewDialog } from "~/hook/useNewDialog"; +import { userAtom } from "~/atom/userAtom"; import { useRecoilValue } from "recoil"; -import { usePutReviewsReviewsId } from "../../api/reviews/usePutReviewsReviewsId"; +import { usePutReviewsReviewsId } from "~/api/reviews/usePutReviewsReviewsId"; import { Link } from "react-router-dom"; type Props = { @@ -19,8 +19,8 @@ type Props = { const HandleReview = ({ type, review, deleteReview }: Props) => { const [isEditMode, setEditMode] = useState(false); - const user = useRecoilValue(userState); - const hasPermissionToEdit = user && user.userName === review.nickname; + const { userName } = useRecoilValue(userAtom); + const hasPermissionToEdit = userName === review.nickname; const startEditMode = () => setEditMode(true); const finishEditMode = () => setEditMode(false); diff --git a/src/component/utils/HeaderDefault.tsx b/src/component/utils/HeaderDefault.tsx index b26950d0..b19ddb31 100644 --- a/src/component/utils/HeaderDefault.tsx +++ b/src/component/utils/HeaderDefault.tsx @@ -1,17 +1,17 @@ import { useRecoilValue } from "recoil"; import { Link } from "react-router-dom"; import { basicGnbMenu } from "~/constant/headerMenu"; -import userState from "~/atom/userState"; import Image from "./Image"; import HeaderDefaultLNB from "./HeaderDefaultLNB"; import Logo from "~/asset/img/jiphyeonjeon_logo.svg"; import "~/asset/css/HeaderDefault.css"; import SearchRanking from "./SearchRanking"; +import { isUserAuthedAtom } from "~/atom/userAtom"; const HeaderDefault = () => { - const user = useRecoilValue(userState); + const isAuthed = useRecoilValue(isUserAuthedAtom); - const gnbMenu = user.isLogin + const gnbMenu = isAuthed ? basicGnbMenu.slice(0, basicGnbMenu.length - 1) // basicGnbMenu의 마지막 요소는 "로그인", 이미 로그인 상태면 제외 : basicGnbMenu; @@ -35,7 +35,7 @@ const HeaderDefault = () => { {menu.text} ))} - {user.isLogin && } + {isAuthed && } diff --git a/src/component/utils/HeaderDefaultLNB.tsx b/src/component/utils/HeaderDefaultLNB.tsx index bf90ee6b..c1f2caf3 100644 --- a/src/component/utils/HeaderDefaultLNB.tsx +++ b/src/component/utils/HeaderDefaultLNB.tsx @@ -2,17 +2,17 @@ import { useEffect, useState } from "react"; import { useRecoilValue } from "recoil"; import { Link, useLocation } from "react-router-dom"; import { adminLnbMenu, loginLnbMenu } from "~/constant/headerMenu"; -import userState from "~/atom/userState"; import Image from "./Image"; import User from "~/asset/img/Uniconlabs.png"; import ToggleUser from "~/asset/img/UniconlabsFill.png"; import ToggleDownArrow from "~/asset/img/caret-down_DaveGandy.png"; import DownArrow from "~/asset/img/drop-down_Freepik.png"; import "~/asset/css/HeaderDefaultLNB.css"; +import { userAtom } from "~/atom/userAtom" const HeaderDefaultLNB = () => { const [isLNBOpened, setIsLNBOpened] = useState(false); - const user = useRecoilValue(userState); + const user = useRecoilValue(userAtom); const location = useLocation(); useEffect(() => { diff --git a/src/component/utils/HeaderModal.tsx b/src/component/utils/HeaderModal.tsx index 12c5e95e..534e54d4 100644 --- a/src/component/utils/HeaderModal.tsx +++ b/src/component/utils/HeaderModal.tsx @@ -1,22 +1,22 @@ import { useRecoilValue } from "recoil"; import { Link } from "react-router-dom"; -import userState from "../../atom/userState"; +import { userAtom } from "~/atom/userAtom"; import Image from "./Image"; import { basicGnbMenu, adminLnbMenu, loginLnbMenu, -} from "../../constant/headerMenu"; -import CloseButton from "../../asset/img/x_button_grey.svg"; -import User from "../../asset/img/Freepik_user.png"; -import "../../asset/css/HeaderModal.css"; +} from "~/constant/headerMenu"; +import CloseButton from "~/asset/img/x_button_grey.svg"; +import User from "~/asset/img/Freepik_user.png"; +import "~/asset/css/HeaderModal.css"; type Props = { setHeaderModal(...args: unknown[]): unknown; }; const HeaderModal = ({ setHeaderModal }: Props) => { - const user = useRecoilValue(userState); + const user = useRecoilValue(userAtom); const closeHeaderModal = () => { setHeaderModal(false); }; diff --git a/src/hook/usePermission.ts b/src/hook/usePermission.ts index 04f4b5b2..ac06e35f 100644 --- a/src/hook/usePermission.ts +++ b/src/hook/usePermission.ts @@ -1,9 +1,9 @@ import { useRecoilValue } from "recoil"; +import { userAtom } from "~/atom/userAtom" import { useNewDialog } from "~/hook/useNewDialog"; -import userState from "~/atom/userState"; export const usePermission = () => { - const user = useRecoilValue(userState); + const user = useRecoilValue(userAtom); const isLoggined = user !== null; const isAdmin = user?.isAdmin; diff --git a/src/type/User.ts b/src/type/User.ts index 36a7e551..5bb42d3c 100644 --- a/src/type/User.ts +++ b/src/type/User.ts @@ -19,8 +19,8 @@ export type User = { export type UserState = { isLogin: boolean; id: number; - userName: string; - email: string; + userName: string | undefined; + email: string | undefined; isAdmin: boolean; - expire: string; // ISO String + expire: string | undefined; // ISO String }; diff --git a/src/util/localStorageEffect.ts b/src/util/localStorageEffect.ts new file mode 100644 index 00000000..1fc4dd2d --- /dev/null +++ b/src/util/localStorageEffect.ts @@ -0,0 +1,21 @@ +import { AtomEffect } from "recoil"; + +/** + * recoil atom 상태를 localStorage에 연동합니다. + * + * {@link https://recoiljs.org/docs/guides/atom-effects/#local-storage-persistence | recoil 공식 문서 } + */ +export const localStorageEffect = + (key: string): AtomEffect => + ({ setSelf, onSet }) => { + const savedValue = localStorage.getItem(key); + if (savedValue != null) { + setSelf(JSON.parse(savedValue)); + } + + onSet((newValue, _, isReset) => { + isReset + ? localStorage.removeItem(key) + : localStorage.setItem(key, JSON.stringify(newValue)); + }); + }; From f13d7f554de6ef2bb7707b22a582d96d8c4605ff Mon Sep 17 00:00:00 2001 From: yeonseong Date: Sun, 26 Nov 2023 14:58:57 +0900 Subject: [PATCH 05/22] build: add animated lib --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index 62c8352c..79ff65d6 100644 --- a/package.json +++ b/package.json @@ -4,6 +4,7 @@ "private": true, "dependencies": { "@ladle/react": "^2.13.0", + "@react-spring/web": "^9.7.3", "@sentry/react": "^7.53.0", "@sentry/tracing": "^7.53.0", "@testing-library/jest-dom": "^5.16.5", From 2efc2885c2733fd825b317f9279e673a350dab4b Mon Sep 17 00:00:00 2001 From: yeonseong Date: Sun, 26 Nov 2023 15:43:37 +0900 Subject: [PATCH 06/22] chore: add paw.png --- public/paw.png | Bin 0 -> 4088 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 public/paw.png diff --git a/public/paw.png b/public/paw.png new file mode 100644 index 0000000000000000000000000000000000000000..09d6d90fb025b1cb314fac336488e0fce2f399b9 GIT binary patch literal 4088 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 q;>!Vs?MS&q{ka#V{TBj~)BX=E%1 Date: Sun, 26 Nov 2023 15:43:51 +0900 Subject: [PATCH 07/22] wip: error cat not yet implemented --- src/asset/css/NotFound.css | 10 +++++++ src/component/utils/NotFound.jsx | 51 ++++++++++++++++++++++++++++++-- 2 files changed, 58 insertions(+), 3 deletions(-) diff --git a/src/asset/css/NotFound.css b/src/asset/css/NotFound.css index bab8d65d..dbfd7c85 100644 --- a/src/asset/css/NotFound.css +++ b/src/asset/css/NotFound.css @@ -6,3 +6,13 @@ background-color: #2d2d2d; padding: 25rem 0 10rem 0; } + +#error_cat { + width: 100vh; + height: 100vh; + position: absolute; + z-index: 1; + object-fit: cover; + object-position: center; + background: red; +} \ No newline at end of file diff --git a/src/component/utils/NotFound.jsx b/src/component/utils/NotFound.jsx index 195c97c8..292063b3 100644 --- a/src/component/utils/NotFound.jsx +++ b/src/component/utils/NotFound.jsx @@ -1,13 +1,58 @@ +import { useSpring, animated } from '@react-spring/web'; import "../../asset/css/NotFound.css"; - const NotFound = () => { + const [springs, api] = useSpring(() => ({ + from: { opacity: 0, transform: 'translate3d(0px, 0px, 0px)' }, + config: { duration: 500 } // 애니메이션 지속 시간 설정 + })); + + const froms = [ + { x: 0, y: 0 }, + { x: window.innerWidth, y: 0 }, + { x: 0, y: window.innerHeight }, + { x: window.innerWidth, y: window.innerHeight }, + ]; + + const getRandomFromIndex = (from, to) => { + const min = Math.ceil(from); + const max = Math.floor(to); + return Math.floor(Math.random() * (max - min)) + min; + } + + const handleClick = (event) => { + api.start({ + to: { + opacity: 1, // 불투명도를 1로 설정 + transform: `translate3d(${event.clientX}px, ${event.clientY}px, 0px)` // 위치 업데이트 + }, + // 애니메이션 시작시 불투명도를 0으로 설정 및 위치 설정 froms 배열에서 랜덤으로 from 선택되도록 수정 + from: { + opacity: 0, + transform: `translate3d(${froms[getRandomFromIndex(0, froms.length)].x}px, ${froms[getRandomFromIndex(0, froms.length)].y}px, 0px)` + }, + reset: true // 매 클릭마다 애니메이션 리셋 + }); + }; + return ( -
+ <> +
+ +
404
Not Found
-
+ ); }; From 147bb186d22547df56d4ec4346a9441f5dc40a1b Mon Sep 17 00:00:00 2001 From: yeonseong Date: Mon, 27 Nov 2023 16:54:33 +0900 Subject: [PATCH 08/22] feat: cat paw in NotFound component --- src/component/utils/NotFound.jsx | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/src/component/utils/NotFound.jsx b/src/component/utils/NotFound.jsx index 292063b3..085801c8 100644 --- a/src/component/utils/NotFound.jsx +++ b/src/component/utils/NotFound.jsx @@ -37,13 +37,9 @@ const NotFound = () => { return ( <>
- From f8b1ba16c3e7ceb623885191a197c6a32f98ef2d Mon Sep 17 00:00:00 2001 From: yeonseong Date: Mon, 27 Nov 2023 16:54:43 +0900 Subject: [PATCH 09/22] feat: error_cat css --- src/asset/css/NotFound.css | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/asset/css/NotFound.css b/src/asset/css/NotFound.css index dbfd7c85..3bd5a5eb 100644 --- a/src/asset/css/NotFound.css +++ b/src/asset/css/NotFound.css @@ -8,11 +8,21 @@ } #error_cat { - width: 100vh; + width: 100vw; height: 100vh; position: absolute; z-index: 1; object-fit: cover; object-position: center; - background: red; +} + +#error_cat__paw { + width: 12vw; + height: 12vw; + position: absolute; + z-index: 2; + object-fit: cover; + object-position: center; + background: url("paw.png") no-repeat; + background-size: 100% 100%; } \ No newline at end of file From b16e5f0a493379c58a8cddf6bcab6b1ec3f5cdd1 Mon Sep 17 00:00:00 2001 From: sujikim Date: Mon, 27 Nov 2023 17:16:21 +0900 Subject: [PATCH 10/22] =?UTF-8?q?fix:=20=EA=B2=80=EC=83=89=EC=B0=BD=20?= =?UTF-8?q?=EC=9E=85=EB=A0=A5=EC=8B=9C=20=EA=B8=B0=EB=B3=B8=20=EC=83=88?= =?UTF-8?q?=EB=A1=9C=EA=B3=A0=EC=B9=A8=20=ED=95=B4=EC=A0=9C=20(#584)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/component/utils/SearchBar.tsx | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/component/utils/SearchBar.tsx b/src/component/utils/SearchBar.tsx index b792fbec..faf7688a 100644 --- a/src/component/utils/SearchBar.tsx +++ b/src/component/utils/SearchBar.tsx @@ -11,11 +11,19 @@ export type Props = ComponentProps<"form"> & { const SearchBar = ({ width = "banner", className = "", + onSubmit, children, ...rest }: Props) => { return ( - + { + e.preventDefault(); + onSubmit && onSubmit(e); + }} + > {children} ); From 6b3b9794b9c77434721abdafbccb4aa02a359a2d Mon Sep 17 00:00:00 2001 From: chan Date: Mon, 27 Nov 2023 17:35:19 +0900 Subject: [PATCH 11/22] fix: change png -> svg --- pnpm-lock.yaml | 51 +++++++++++++++++++++++++++++++++++++ public/paw.png | Bin 4088 -> 0 bytes src/asset/css/NotFound.css | 2 +- src/asset/img/paw.svg | 16 ++++++++++++ 4 files changed, 68 insertions(+), 1 deletion(-) delete mode 100644 public/paw.png create mode 100644 src/asset/img/paw.svg diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0c55ed7e..c137ace3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,6 +8,9 @@ dependencies: '@ladle/react': specifier: ^2.13.0 version: 2.13.0(react-dom@18.2.0)(react@18.2.0) + '@react-spring/web': + specifier: ^9.7.3 + version: 9.7.3(react-dom@18.2.0)(react@18.2.0) '@sentry/react': specifier: ^7.53.0 version: 7.53.0(react@18.2.0) @@ -765,6 +768,54 @@ packages: '@nodelib/fs.scandir': 2.1.5 fastq: 1.15.0 + /@react-spring/animated@9.7.3(react@18.2.0): + resolution: {integrity: sha512-5CWeNJt9pNgyvuSzQH+uy2pvTg8Y4/OisoscZIR8/ZNLIOI+CatFBhGZpDGTF/OzdNFsAoGk3wiUYTwoJ0YIvw==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + dependencies: + '@react-spring/shared': 9.7.3(react@18.2.0) + '@react-spring/types': 9.7.3 + react: 18.2.0 + dev: false + + /@react-spring/core@9.7.3(react@18.2.0): + resolution: {integrity: sha512-IqFdPVf3ZOC1Cx7+M0cXf4odNLxDC+n7IN3MDcVCTIOSBfqEcBebSv+vlY5AhM0zw05PDbjKrNmBpzv/AqpjnQ==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + dependencies: + '@react-spring/animated': 9.7.3(react@18.2.0) + '@react-spring/shared': 9.7.3(react@18.2.0) + '@react-spring/types': 9.7.3 + react: 18.2.0 + dev: false + + /@react-spring/shared@9.7.3(react@18.2.0): + resolution: {integrity: sha512-NEopD+9S5xYyQ0pGtioacLhL2luflh6HACSSDUZOwLHoxA5eku1UPuqcJqjwSD6luKjjLfiLOspxo43FUHKKSA==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + dependencies: + '@react-spring/types': 9.7.3 + react: 18.2.0 + dev: false + + /@react-spring/types@9.7.3: + resolution: {integrity: sha512-Kpx/fQ/ZFX31OtlqVEFfgaD1ACzul4NksrvIgYfIFq9JpDHFwQkMVZ10tbo0FU/grje4rcL4EIrjekl3kYwgWw==} + dev: false + + /@react-spring/web@9.7.3(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-BXt6BpS9aJL/QdVqEIX9YoUy8CE6TJrU0mNCqSoxdXlIeNcEBWOfIyE6B14ENNsyQKS3wOWkiJfco0tCr/9tUg==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 + dependencies: + '@react-spring/animated': 9.7.3(react@18.2.0) + '@react-spring/core': 9.7.3(react@18.2.0) + '@react-spring/shared': 9.7.3(react@18.2.0) + '@react-spring/types': 9.7.3 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: false + /@remix-run/router@1.6.2: resolution: {integrity: sha512-LzqpSrMK/3JBAVBI9u3NWtOhWNw5AMQfrUFYB0+bDHTSw17z++WJLsPsxAuK+oSddsxk4d7F/JcdDPM1M5YAhA==} engines: {node: '>=14'} diff --git a/public/paw.png b/public/paw.png deleted file mode 100644 index 09d6d90fb025b1cb314fac336488e0fce2f399b9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4088 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 q;>!Vs?MS&q{ka#V{TBj~)BX=E%1 + + + + + + + + + + + + + + + \ No newline at end of file From 971e2a3e114d6714a264056e7977f51b72761c5e Mon Sep 17 00:00:00 2001 From: gilee Date: Wed, 10 Jan 2024 14:29:19 +0900 Subject: [PATCH 12/22] [fix] add env REACT_APP_SENTRY --- .github/workflows/deploy-main.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/deploy-main.yml b/.github/workflows/deploy-main.yml index e4bda03c..73573ff8 100644 --- a/.github/workflows/deploy-main.yml +++ b/.github/workflows/deploy-main.yml @@ -28,6 +28,7 @@ jobs: echo "REACT_APP_E_BOOK_LIBRARY=$REACT_APP_E_BOOK_LIBRARY" >> .env echo "REACT_APP_SUGGESTION=$REACT_APP_SUGGESTION" >> .env echo "REACT_APP_GA_ID=$REACT_APP_GA_ID" >> .env + echo "REACT_APP_SENTRY=$REACT_APP_SENTRY" >> .env echo "PORT=$PORT" >> .env env: REACT_APP_API: ${{ secrets.REACT_APP_API }} From 1ec68a2371705ed60df1d9d37cf1dfb532d63417 Mon Sep 17 00:00:00 2001 From: gilee Date: Wed, 10 Jan 2024 14:39:11 +0900 Subject: [PATCH 13/22] [fix] delete cloudfront config --- .github/workflows/deploy-main.yml | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/.github/workflows/deploy-main.yml b/.github/workflows/deploy-main.yml index 73573ff8..79ad28a5 100644 --- a/.github/workflows/deploy-main.yml +++ b/.github/workflows/deploy-main.yml @@ -54,12 +54,4 @@ jobs: AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} run: | - aws s3 sync ./build s3://42library.kr --region ap-northeast-2 - - - name: invalidate CDN cache - env: - AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} - AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} - AWS_CDN_DISTRIBUTION_ID: ${{ secrets.AWS_CDN_DISTRIBUTION_ID }} - run: | - aws cloudfront create-invalidation --distribution-id ${{ secrets.AWS_CDN_DISTRIBUTION_ID }} --paths "/*" --region ap-northeast-2 + aws s3 sync ./build s3://42library.kr --region ap-northeast-2 --delete From 41f1bc0f32eadd73ea77f33e3032112a99c87bfa Mon Sep 17 00:00:00 2001 From: scarf Date: Fri, 12 Jan 2024 10:37:53 +0900 Subject: [PATCH 14/22] =?UTF-8?q?build:=20base=20=EC=B6=94=EA=B0=80=20(#60?= =?UTF-8?q?3)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- vite.config.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/vite.config.ts b/vite.config.ts index 101cd41a..deea2e9e 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -31,5 +31,7 @@ export default defineConfig(({ mode }) => { /** @see https://vitejs.dev/config/build-options.html#build-outdir */ build: { outDir: "build" }, + + base: "./", }; }); From 55413e10c2b892c33e16834dff801e5f83dc2723 Mon Sep 17 00:00:00 2001 From: sujikim Date: Wed, 29 Nov 2023 13:54:48 +0900 Subject: [PATCH 15/22] =?UTF-8?q?fix:=20=EB=8F=84=EC=84=9C=EB=93=B1?= =?UTF-8?q?=EB=A1=9D=20=EC=B9=B4=ED=85=8C=EA=B3=A0=EB=A6=AC=20=EC=A0=9C?= =?UTF-8?q?=EB=8C=80=EB=A1=9C=20=EC=A0=81=EC=9A=A9=EB=90=98=EC=A7=80=20?= =?UTF-8?q?=EC=95=8A=EB=8D=98=20=EB=AC=B8=EC=A0=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/books/useGetBooksCreate.ts | 51 ++++++------------- src/api/books/usePostBooksCreate.ts | 48 ++++++----------- src/component/addbook/AddBook.tsx | 10 ++-- .../addbook/AddBookDisplayBasicBookInfo.tsx | 9 ++-- ...AddBookRegisterBookWithUsersExtraInput.tsx | 43 ++++++++-------- src/type/BookInfo.ts | 1 - 6 files changed, 61 insertions(+), 101 deletions(-) diff --git a/src/api/books/useGetBooksCreate.ts b/src/api/books/useGetBooksCreate.ts index efef74e3..c17513f5 100644 --- a/src/api/books/useGetBooksCreate.ts +++ b/src/api/books/useGetBooksCreate.ts @@ -1,38 +1,16 @@ -import { useState, useEffect } from "react"; -import { useApi } from "../../hook/useApi"; -import { compareExpect } from "../../util/typeCheck"; -import getErrorMessage from "../../constant/error"; -import { Book } from "../../type"; +import { useState } from "react"; +import { useApi } from "~/hook/useApi"; +import getErrorMessage from "~/constant/error"; +import { type BookInfo } from "~/type"; -export const useGetBooksCreate = (defalutBook: Book) => { - const [isbnQuery, setIsbnQuery] = useState(""); +export const useGetBooksCreate = (defalutBook: Omit) => { const [bookInfo, setBookInfo] = useState(defalutBook); const [errorMessage, setErrorMessage] = useState(""); - const { request } = useApi("get", "books/create", { - isbnQuery, - }); - - const expectedItem = [ - { key: "title", type: "string", isNullable: false }, - { key: "author", type: "string", isNullable: false }, - { key: "publisher", type: "string", isNullable: false }, - { key: "pubdate", type: "string", isNullable: false }, - { key: "category", type: "string", isNullable: false }, - { key: "image", type: "string", isNullable: true }, - ]; + const { requestWithUrl } = useApi(); const refineResponse = (response: any) => { - const books = compareExpect( - "books/create", - [response.data.bookInfo], - expectedItem, - ); - setBookInfo({ - ...books[0], - isbn: isbnQuery, - koreanDemicalClassification: books[0].category, - }); + setBookInfo(response.data.bookInfo); setErrorMessage(""); }; @@ -46,11 +24,14 @@ export const useGetBooksCreate = (defalutBook: Book) => { setBookInfo(defalutBook); }; - useEffect(() => { - if (isbnQuery && isbnQuery.length) { - request(refineResponse, displayError); - } - }, [isbnQuery]); + const fetchData = (isbnQuery: string) => { + if (!isbnQuery) return; + requestWithUrl("get", "books/create", { + data: { isbnQuery }, + onSuccess: refineResponse, + onError: displayError, + }); + }; - return { bookInfo, errorMessage, fetchData: setIsbnQuery, setBookInfo }; + return { bookInfo, errorMessage, fetchData, setBookInfo }; }; diff --git a/src/api/books/usePostBooksCreate.ts b/src/api/books/usePostBooksCreate.ts index 38e5cc84..c9b73313 100644 --- a/src/api/books/usePostBooksCreate.ts +++ b/src/api/books/usePostBooksCreate.ts @@ -1,29 +1,17 @@ -import { useState, useEffect } from "react"; -import { useApi } from "../../hook/useApi"; -import getErrorMessage from "../../constant/error"; -import { compareExpect } from "../../util/typeCheck"; -import { BookInfo } from "../../type"; +import { useState } from "react"; +import { useNavigate } from "react-router-dom"; +import { useApi } from "~/hook/useApi"; +import getErrorMessage from "~/constant/error"; +import { type BookInfo } from "~/type"; export const usePostBooksCreate = () => { - const [newBookInfo, setNewBookInfo] = useState(null); const [message, setMessage] = useState(""); - - const { request } = useApi("post", "books/create", newBookInfo); - - const expectedItem = [ - { key: "title", type: "string", isNullable: false }, - { key: "isbn", type: "string", isNullable: true }, - { key: "author", type: "string", isNullable: false }, - { key: "publisher", type: "string", isNullable: false }, - { key: "pubdate", type: "string", isNullable: false }, - { key: "categoryId", type: "string", isNullable: false }, - { key: "image", type: "string", isNullable: true }, - { key: "donator", type: "string", isNullable: true }, - ]; + const navigate = useNavigate(); + const { requestWithUrl } = useApi(); const displaySuccess = () => { setMessage("등록되었습니다!"); - window.location.reload(); + navigate(0); }; const displayError = (error: any) => { @@ -32,19 +20,13 @@ export const usePostBooksCreate = () => { setMessage(`실패했습니다. ${errorMessage} `); }; - const registerBook = (newBook: BookInfo) => { - const [book] = compareExpect("books/create", [newBook], expectedItem); - setNewBookInfo(book); + const registerBook = (newBook: Omit) => { + requestWithUrl("post", "books/create", { + data: newBook, + onSuccess: displaySuccess, + onError: displayError, + }); }; - useEffect(() => { - if (newBookInfo) { - request(displaySuccess, displayError); - } - }, [newBookInfo]); - - return { - message, - registerBook, - }; + return { message, registerBook }; }; diff --git a/src/component/addbook/AddBook.tsx b/src/component/addbook/AddBook.tsx index 1e6c15c1..b03fb141 100644 --- a/src/component/addbook/AddBook.tsx +++ b/src/component/addbook/AddBook.tsx @@ -1,5 +1,5 @@ import { useState } from "react"; -import { useGetBooksCreate } from "../../api/books/useGetBooksCreate"; +import { useGetBooksCreate } from "~/api/books/useGetBooksCreate"; import RegisterBookWithUsersExtraInput from "./AddBookRegisterBookWithUsersExtraInput"; import DisplayBasicBookInfo from "./AddBookDisplayBasicBookInfo"; @@ -9,9 +9,9 @@ import Banner from "../utils/Banner"; import BarcodeReader from "../utils/BarcodeReader"; import InquireBoxTitle from "../utils/InquireBoxTitle"; -import { bookManagementTabList } from "../../constant/tablist"; -import Book from "../../asset/img/admin_icon.svg"; -import "../../asset/css/AddBook.css"; +import { bookManagementTabList } from "~/constant/tablist"; +import Book from "~/asset/img/admin_icon.svg"; +import "~/asset/css/AddBook.css"; const AddBook = () => { const [isUsingBarcodeReader, setUsingBarcodeReader] = useState(true); @@ -22,7 +22,7 @@ const AddBook = () => { author: "", publisher: "", pubdate: "", - koreanDemicalClassification: "", + category: "", }; const { bookInfo, errorMessage, fetchData, setBookInfo } = diff --git a/src/component/addbook/AddBookDisplayBasicBookInfo.tsx b/src/component/addbook/AddBookDisplayBasicBookInfo.tsx index 50582964..dd1a8c69 100644 --- a/src/component/addbook/AddBookDisplayBasicBookInfo.tsx +++ b/src/component/addbook/AddBookDisplayBasicBookInfo.tsx @@ -1,5 +1,5 @@ import { ChangeEventHandler, useState } from "react"; -import { BookInfo } from "../../type"; +import { BookInfo } from "~/type"; const labelText: { [key: string]: string; @@ -10,8 +10,8 @@ const labelText: { }; type Props = { - bookInfo: BookInfo & { [key: string]: string }; - setBookInfo: (bookinfo: BookInfo) => void; + bookInfo: Omit; + setBookInfo: (bookinfo: Omit) => void; }; const DisplayBasicBookInfo = ({ bookInfo, setBookInfo }: Props) => { @@ -48,6 +48,7 @@ const DisplayBasicBookInfo = ({ bookInfo, setBookInfo }: Props) => { /> {Object.keys(labelText).map(key => { + const bookInfoKey = key as keyof Omit; return (