Skip to content

Commit

Permalink
develop to main (#573)
Browse files Browse the repository at this point in the history
* fix: 메인페이지 인기 도서 순위 오류 해결 (#521)

* fix: 메인페이지 책 타입 Book이 아나리 BookInfo

* fix: 메인페이지 인기도서 rank 속성 추가

* docs: 📝 README에 이름 추가 (#524)

* fix: 도서 수정 categoryId를 숫자로 변경 (#525)

* refactor: 쓰이지 않는 비표준 css 제거 (#545)

* refactor: 검색 바 아이콘 img로 변경 (#546)

* refactor: 검색 바 아이콘 img로 변경

* fix: `alt=""` 속성 추가

장식(아이콘)이라는 점을 명시적으로 표시

* feat: 메인페이지 스크롤을 내려주세요 애니메이션 추가 (#539)

png 이미지 대신 css로 마우스형태 구현, 동작 추가

* fix: Image 컴포넌트 에러 처리 방식 변경 (#538)

* build: 절대경로 import 추가, `MyRentInfo`에 절대경로 적용 (#542)

* build: vite-tsconfig-paths 추가

* build: 절대경로 import 추가

* refactor: `MyRentInfo`에 절대경로 적용

---------

Co-authored-by: nocontribute <>

* refactor: 대출제한일 계산 로직 통합 (#536)

* fix: 불필요한 타입체크 코드 삭제
* fix: addDay 날짜 오류 해결
* feat: string 형태의 날짜 비교 함수 compareDate 추가
* refactor: 대출제한일 계산 로직 변경 및 통합

* fix: 좋아요 취소시 오류 해결 및 리팩토링 (#527)

* fix: 불필요한 검증 코드 삭제
* refactor: 재사용되지 않는 ShowLike 컴포넌트 통합
* feat: 유저 권한을 확인하는 usePermission 훅 추가
* refactor: 로컬스토리지 대신 usePermission 훅으로 권한 명시
* refactor: Like JSX 가독성 개선
* refactor: 모호한 변수명 변경 initBookInfoId => bookInfoId
* refactor: useGetLike 훅 안팎에 중복된 상태 제거
* refactor: PostLike, DeleteLike 네트워크 성공시에만 뷰가 변경되도록 수정
* refactor: import 절대경로로 변경

* feat: 반응형을 위한 useBreakPoint 훅 추가

* fix: 현재 윈도우 너비에 따라 알맞는 헤더 하나만 표시하도록

* refactor: 기본 헤더에서 LNB 분리

* fix: headerMenu js 파일 ts로 변경 및 절대경로 적용

* refactor: 기본 헤더 불필요한 html 태그 정리 및 css 단순화

* refactor: 모바일 헤더 불필요한 html 태그 정리 및 css 단순화

* refactor: 검색창 컴포넌트 개선 및 드롭다운 UI 추가 (#551)

* refactor: SearchBar 용도에 따라 분리 및 합성 방식으로 변경

용도에 따라 BookSearchBar, ManagementSearchBar로 활용하도록 변경
Compound 패턴으로 SearchBar 확장성 개선

* feat: 검색창 드롭다운 UI 추가

검색창 및 드롭다운 Blur 이벤트 추가

* fix: 검색창 드롭다운 배경 투명도 조절

* fix: 검색 결과 변경시 검색창 blur 처리

* feat: 검색창 드롭다운 ESC 키로 끌 수 있도록

* fix: 검색창 드롭다운 padding 설정 삭제

* feat: 최근 검색어 기능 구현 (#553)

* feat: 최근 검색어 기능 추가

* fix: 최근검색어 X 버튼 css 수정

* feat: carousel UI 컴포넌트 추가 및 메인페이지에 적용 (#560)

* refactor: interval 설정 부분 훅으로 추출

* refactor: useBound 훅에서 event 마다 선택할 수 있도록 props 설정

* feat: useResponsiveWidth 훅 추가

pc, tablet, mobile 환경에 맞는 사이즈 확인용

* refactor: 메인 신규도서 책크기 useResponsiveWidth 훅 적용

* feat: Carousel 컴포넌트 추가

무한 회전하는 슬라이딩 UI, 조합하여 사용할 수 있도록

* refactor: 메인 신규도서 Carousel 형식으로 변경 및 불필요한 태그 depth 제거

* fix: Carousel 사이즈 대신 container 안에 count로 지정할 수 있도록

size나 count 둘 중 하나를 props로 받아서 displayCount를 결정

* fix: Carousel flex 설정으로 너비 보장 안되는 문제 해결

itemSize로 지정한 크기가 조정되지 않도록 flex-grow, flex-shrink 설정 추가

* fix: Carousel 자동 애니메이션 props isAutoAnimated로 제어하도록

- mount 시 useInterval 자동 실행 막음
- props isAutoAnimated True 일때만 carousel 자동으로 돌아가도록
- isAutoAnimated와 isSmoothAnimated 변수 구분

* fix: onPrev 제대로 넘어가지 않던 문제 수정

index가 0이 아닌 1부터 시작하도록 조정

* fix: Carousel 세로형 itemCount = 1일 때 동작오류 수정

translateX와 translateY 설정 및 items이 적을때만 복사 추가

* fix: Carousel 항상 items 뒤에 복사해서 붙이도록 수정

* fix: Carousel list 키 관련 타입오류 수정

* fix: 모바일 화면 메인 신규리스트 동작 오류 수정

* fix: Carousel 가로형 itemCount = 1일 때 UI오류 수정, flexBasis 속성이 필요함

* refactor: 컨테이너와 ContextProvider 분리 및 컴포넌트 파일 독립

- 버튼이나 페이지네이션이 컨테이너 밖에 위치할 수 있도록 Provider와 분리

* fix: 메인 신규도서 컨테이너 컴포넌트 추가

* fix: useApi 임시 활용방법 추가 (#564)

url data도 호출 시점에 전달할 수 있도록

* feat: 검색결과 미리보기 기능 구현 (#555)

* feat: 검색결과 미리보기 UI 구현

* feat: 검색결과 미리보기에서 검색어와 일치할때 색상 강조 추가

* fix: EmphasisInString default props 설정

* fix: EmphasisInString 강조 단어로 시작할때 제대로 적용되지 않는 문제 수정

index = 0 일 때도 적용되도록

* fix: EmphasisInString 대소문자 구분하지 않도록

* fix: EmphasisInString 원래 문자열의 대소문자 형식 변형되던 문제 수정

wholeString 그대로 보일 수 있도록 수정

* fix: autocomplete api 수정 내용 mockdata 반영

* fix: 검색결과 미리보기 키보드 제어 삭제 및 hover 방식으로 변경

* fix: 검색결과 미리보기 레이아웃 변경

* refactor: 검색결과 미리보기 컴포넌트 구조 단순화

* refactor: Pagination 컴파운드 패턴 적용한 Paginations 방식으로 변경

* fix: 검색결과 미리보기 Pagination 방식 변경

* feat: 검색결과 미리보기에 전체 검색결과 카운트 보여주도록

* fix: 페이지네이션 disabled 인 이동버튼 투명도 적용

* fix: autocomplete api 수정사항 반영

* fix: 검색결과 프리뷰 mock 대신 실제 api 연결

* feat: 검색결과 미리보기 로딩 추가

* fix: 검색결과 미리보기 클릭시 상세로 이동되지 않던 오류 수정

* feat: 메인 추천 도서 기능 구현 (#565)

* feat: 추천 도서 기본 UI 구현

* feat: 추천도서 도서 클릭시 해당 도서 상세로 이동

* feat: 메인 추천도서 양 옆 버튼 추가

* fix: 메인 추천도서 모바일 반응형 개선

* feat: 메인 서클별 추천도서 api 연결

* refactor: 메인 신작도서 페이지네이션 추출

* feat: 메인 추천도서에도 원형 페이지네이션 적용

* fix: 메인 서클별 추천도서 반응형 css 수정

* fix: Carousel 길이가 바뀌었을 때 슬라이드 초기화

* feat: 인기검색어 구현 (#566)

* feat: 인기검색어 구현

* chore: 오타 수정

* fix: 검색개선 UI 피드백 반영 (#568)

* fix: 인기검색어 UI 피드백 반영, 검색어 클릭시 검색결과로 이동

* fix: 검색결과 UI 피드백 반영, 긴 검색어는 줄바꿈 하도록

* fix: 메인 신작도서 모바일 화면 튀어나온 버튼 위치 조정

* fix: 메인 추천도서 UI 피드백 반영, 로딩 추가 및 리스트 하나일때 자동 애니메이션 해제

* fix: 인기검색어 모바일일때 fixed 해제

* fix: 검색결과 미리보기 UI 피드백 반영, 검색결과 없을때 ui 개선

* fix: 검색 개선 UI 피드백 2차 (#570)

* fix: 검색결과 미리보기 로딩 중 검색결과없습니다 문구 안보이도록

* fix: 검색결과 더보기 클릭시 미리보기 닫히도록

* fix: 검색창 드롭다운 제대로 focus 안되던 문제 수정

* fix: 검색어 변경시 결과 미리보기 page가 갱신되지 않던 문제 수정

* fix: 검색결과 미리보기 전체 검색결과 더보기를 검색결과 없을때 보이지 않도록

* fix: 공백만 검색했을 때 최근검색어에 추가하지 않도록

* fix: 검색 api요청시 기본 옵션 sort title 설정

* fix: 도서 수정 카테고리 제대로 적용되지 않던 오류 해결

* fix: 추천도서 과격한 애니메이션 수정, 캐러셀 초반에 트랜지션 끌 수 있도록

* fix: 검색결과 미리보기 totalCount 없을때 0 나오던 문제 수정

* fix: 최근 검색어 최대 10개로 제한 (#572)

---------

Co-authored-by: mink97 <[email protected]>
Co-authored-by: scarf <[email protected]>
  • Loading branch information
3 people authored Sep 6, 2023
1 parent 54d0698 commit f920d84
Show file tree
Hide file tree
Showing 109 changed files with 2,951 additions and 1,379 deletions.
38 changes: 26 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
## 42서울의 도서관, 집현전

<p align='center'>
<img width='70%' src='https://user-images.githubusercontent.com/79993356/180756135-0f622d3c-e5aa-4ac8-8d50-fa768aeaf5f9.png'>
</p>
Expand All @@ -19,24 +20,32 @@

## 🏠 [HOME PAGE](https://42library.kr/)

**1,000명 이상**이 사용하는 42 SEOUL 공식 도서관 웹사이트입니다.
**1,000명 이상**이 사용하는 42 SEOUL 공식 도서관 웹사이트입니다.

사용자와 사서님들에게 피드백을 매주 받는 것은 물론, 사이트에 대한 건의 사항을 **42 Seoul 재단**에서 직접 받기도 합니다.
사용자와 사서님들에게 피드백을 매주 받는 것은 물론, 사이트에 대한 건의 사항을 **42 Seoul 재단**에서 직접 받기도 합니다.

매주 유지보수 팀이 도서관에 상주하여 해당 건의 사항을 해결합니다.

## 📌 집현전 서비스 소개
### 👩‍👩‍👧‍👦 도서검색

### 👩‍👩‍👧‍👦 도서검색

> - 네이버에서 제공하는 모든 도서를 검색 가능합니다.
> - 도서명, ISBN, 저자명으로 검색합니다.
### 📈 도서 대출과 반납, 등록 가능

> - 도서의 **QR코드** 및 바코드를 읽어 간단하게 책을 대출해드릴 수 있습니다.
> - 같은 방법으로, 집현전에 새로운 도서를 등록하여 DB에 저장 가능합니다.
### 📆 간편한 마이페이지 제공

> - 자신의 연체 날짜를 확인하며, 대출하거나 예약한 책의 내역을 봅니다.
> - 이메일과 비밀번호를 변경할 수 있습니다.
> - 연체 날짜가 잘못된 경우, 사서가 이를 수정해줄 수 있습니다.
### 📩 42 OAuth 2.0 활용

> - 이메일과 비밀번호로 회원가입은 물론, [**42의 OAuth API**](https://api.intra.42.fr/apidoc/guides/specification)를 통한 로그인 인증이 가능합니다.
## 📌 시연 영상
Expand All @@ -56,6 +65,7 @@

**1. MySQL 다운로드 후 워크벤치에 DB 스키마 설계 후 실행**
**2. 백엔드**

- 백엔드 환경 변수 설정
- backend폴더 바로 안에 .env 파일 생성
- .env 예시
Expand Down Expand Up @@ -84,17 +94,20 @@
```
**3. 프론트엔드**
- 프론트엔드 환경 변수 설정
- 폴더에 .env 파일 생성
- .env 예시
```
REACT_APP_API=...
REACT_APP_WISH=...
REACT_APP_E_BOOK_LIBRARY=...
PORT=...
REACT_APP_SUGGESTION=...
```
- 폴더에 .env 파일 생성
- .env 예시
```
REACT_APP_API=...
REACT_APP_WISH=...
REACT_APP_E_BOOK_LIBRARY=...
PORT=...
REACT_APP_SUGGESTION=...
```
- 실행
```jsx
pnpm install
pnpm dev
Expand All @@ -118,3 +131,4 @@
- [Jaekim](https://github.com/jae-hwan-kim)
- [Chanheki](https://github.com/chanhihi)
- [Youkim](https://github.com/scarf005)
- [Mingkang](https://github.com/mink97)
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
"react-router-dom": "^6.11.2",
"recoil": "^0.7.7",
"vite": "^4.3.8",
"vite-tsconfig-paths": "^4.2.0",
"web-vitals": "^3.3.1"
},
"scripts": {
Expand Down
33 changes: 32 additions & 1 deletion pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 0 additions & 2 deletions src/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import BookDetail from "./component/book/BookDetail";
import Footer from "./component/utils/Footer";
import NotFound from "./component/utils/NotFound";
import Header from "./component/utils/Header";
import MobileHeader from "./component/utils/MobileHeader";
import Information from "./component/information/Information";
import Main from "./component/main/Main";
import Search from "./component/search/Search";
Expand Down Expand Up @@ -52,7 +51,6 @@ function App() {
<div id="portal" />
<Portals />
<Header />
<MobileHeader />
<Routes>
<Route path="/" element={<Main />} />
<Route path="/41" element={<ELibraryIn42Box />} />
Expand Down
4 changes: 2 additions & 2 deletions src/api/books/useGetBooksInfoNew.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { useState, useEffect } from "react";
import { useApi } from "../../hook/useApi";
import { compareExpect } from "../../util/typeCheck";
import { Book } from "../../type";
import { BookInfo } from "../../type";

export const useGetBooksInfoNew = () => {
const [bookList, setBookList] = useState<Book[]>([]);
const [bookList, setBookList] = useState<BookInfo[]>([]);

const { request } = useApi("get", "books/info/", {
sort: "new",
Expand Down
10 changes: 7 additions & 3 deletions src/api/books/useGetBooksInfoPopular.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { useState, useEffect } from "react";
import { useApi } from "../../hook/useApi";
import { compareExpect } from "../../util/typeCheck";
import { Book } from "../../type";
import { BookInfo } from "../../type";

export const useGetBooksInfoPopular = () => {
const [docs, setDocs] = useState<Book[]>([]);
const [docs, setDocs] = useState<(BookInfo & { rank: number })[]>([]);

const { request } = useApi("get", "books/info", {
sort: "popular",
Expand All @@ -27,7 +27,11 @@ export const useGetBooksInfoPopular = () => {
response.data.items,
expectedItem,
);
setDocs(books);
const booksWithRank = books.map((book, index) => ({
...book,
rank: index + 1,
}));
setDocs(booksWithRank);
};

useEffect(() => request(refineResponse), []);
Expand Down
2 changes: 1 addition & 1 deletion src/api/books/useGetBooksInfoSearchUrl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export const useGetBooksInfoSearchUrl = () => {
query,
page: page ? page - 1 : 0,
limit: 20,
sort,
sort: sort ?? "title",
category,
});

Expand Down
5 changes: 4 additions & 1 deletion src/api/books/usePatchBooksUpdate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,10 @@ type Props = {
export const usePatchBooksUpdate = ({ bookTitle, closeModal }: Props) => {
const [change, setChange] = useState<Partial<Book>>();

const { request } = useApi("patch", "books/update", change);
const { request } = useApi("patch", "books/update", {
...change,
categoryId: change?.categoryId ? change.categoryId + 1 : undefined, // DB에는 1부터 저장되어 있으므로 +1
});

const { addDialogWithTitleAndMessage } = useNewDialog();

Expand Down
43 changes: 43 additions & 0 deletions src/api/cursus/useGetCursusRecommendBooks.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { useEffect, useState } from "react";
import { useApi } from "~/hook/useApi";
import type { BookInfoRecommend } from "~/type";

export const useGetCursusRecommendBooks = () => {
const [isLoading, setIsLoading] = useState(false);
const [selectedOption, setSelectedOption] = useState<string>();
const [recommend, setRecommend] = useState<Recommend>({
books: [],
options: ["사용자 지정"],
});
const { requestWithUrl } = useApi();
useEffect(() => {
setIsLoading(true);
const saveRecommend = (response: any) => {
const { items, meta } = response.data;
meta.sort();
setRecommend({
books: items,
options: [
"사용자 지정",
...meta.filter((item: string) => item !== "사용자 지정"),
],
});
setIsLoading(false);
};
requestWithUrl("get", "/cursus/recommend/books", {
data: {
project: selectedOption
? selectedOption.split("| ")[1] ?? undefined
: undefined,
},
onSuccess: saveRecommend,
});
}, [selectedOption]);

return { ...recommend, setSelectedOption, isLoading };
};

type Recommend = {
books: BookInfoRecommend[];
options: string[];
};
3 changes: 3 additions & 0 deletions src/api/like/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export * from "./useDeleteLike";
export * from "./useGetLike";
export * from "./usePostLike";
33 changes: 12 additions & 21 deletions src/api/like/useDeleteLike.ts
Original file line number Diff line number Diff line change
@@ -1,31 +1,22 @@
import { useEffect, useState } from "react";
import { compareExpect } from "../../util/typeCheck";
import { useApi } from "../../hook/useApi";
import { Dispatch, SetStateAction, useEffect, useState } from "react";
import { useApi } from "~/hook/useApi";

export const useDeleteLike = (initBookInfoId?: number) => {
const [bookInfoId, setBookInfoId] = useState(initBookInfoId);
const { request } = useApi("delete", `books/info/${bookInfoId}/like`);
const [likeData, setLikeData] = useState({});
type Props = {
setLike: Dispatch<SetStateAction<{ isLiked: boolean; likeNum: number }>>;
};

const expectedItem = [
{ key: "bookInfoId", type: "number", isNullable: false },
{ key: "isLiked", type: "bool", isNullable: false },
{ key: "likeNum", type: "number", isNullable: false },
];
export const useDeleteLike = ({ setLike }: Props) => {
const [bookInfoId, setBookInfoId] = useState<number>();
const { request } = useApi("delete", `books/info/${bookInfoId}/like`);

const refineResponse = (response: any) => {
const [refinelikeData] = compareExpect(
`books/info/${initBookInfoId}/like`,
[response.data],
expectedItem,
);
setLikeData(refinelikeData);
const onSuccess = () => {
setLike(prev => ({ isLiked: false, likeNum: prev.likeNum - 1 }));
};

useEffect(() => {
if (bookInfoId) request(refineResponse);
if (bookInfoId) request(onSuccess);
setBookInfoId(undefined);
}, [bookInfoId]);

return { setBookInfoId, likeData };
return { setBookInfoId };
};
40 changes: 11 additions & 29 deletions src/api/like/useGetLike.ts
Original file line number Diff line number Diff line change
@@ -1,42 +1,24 @@
import { useEffect, useState } from "react";
import getErrorMessage from "../../constant/error";
import { useApi } from "../../hook/useApi";
import { compareExpect } from "../../util/typeCheck";
import { useApi } from "~/hook/useApi";

type Props = {
initBookInfoId: number;
setCurrentLike: (isLiked: boolean) => void;
setCurrentLikeNum?: (likeNum: number) => void;
bookInfoId: number;
};

export const useGetLike = ({
initBookInfoId,
setCurrentLike,
setCurrentLikeNum,
}: Props) => {
const { request } = useApi("get", `books/info/${initBookInfoId}/like`);
const [likeData, setLikeData] = useState({});

const expectedItem = [
{ key: "bookInfoId", type: "number", isNullable: false },
{ key: "isLiked", type: "bool", isNullable: false },
{ key: "likeNum", type: "number", isNullable: false },
];
export const useGetLike = ({ bookInfoId }: Props) => {
const { request } = useApi("get", `books/info/${bookInfoId}/like`);
const [like, setLike] = useState({
isLiked: false,
likeNum: 0,
});

const refineResponse = (response: any) => {
const [refinelikeData] = compareExpect(
`books/info/${initBookInfoId}/like`,
[response.data],
expectedItem,
);
setLikeData(refinelikeData);
setCurrentLike(refinelikeData.isLiked);
setCurrentLikeNum && setCurrentLikeNum(refinelikeData.likeNum);
setLike(response.data);
};

useEffect(() => {
if (initBookInfoId) request(refineResponse);
if (bookInfoId) request(refineResponse);
}, []);

return { likeData };
return { like, setLike };
};
Loading

0 comments on commit f920d84

Please sign in to comment.