diff --git a/bookduck/public/assets/items/hairband.glb b/bookduck/public/assets/items/hairband.glb new file mode 100644 index 0000000..91013f1 Binary files /dev/null and b/bookduck/public/assets/items/hairband.glb differ diff --git a/bookduck/public/assets/items/safetyhat.glb b/bookduck/public/assets/items/safetyhat.glb new file mode 100644 index 0000000..da33c0e Binary files /dev/null and b/bookduck/public/assets/items/safetyhat.glb differ diff --git a/bookduck/public/assets/items/spanner.glb b/bookduck/public/assets/items/spanner.glb new file mode 100644 index 0000000..9ea5bc6 Binary files /dev/null and b/bookduck/public/assets/items/spanner.glb differ diff --git a/bookduck/src/App.jsx b/bookduck/src/App.jsx index 9a1ef66..30a94d4 100644 --- a/bookduck/src/App.jsx +++ b/bookduck/src/App.jsx @@ -17,6 +17,7 @@ import BookInfoPage from "./pages/BookInfoPage/BookInfoPage"; import UserCommentPage from "./pages/BookInfoPage/UserCommentPage"; import BookInfoAddedPage from "./pages/BookInfoPage/BoonInfoAddedPage"; import StatisticsPage from "./pages/StatisticsPage/StatisticsPage"; +import CharacterExportPage from "./pages/StatisticsPage/CharacterExportPage"; import CardDecorationPage from "./pages/RecordingPage/CardDecorationPage"; import LibraryPage from "./pages/LibraryPage/LibraryPage"; import EnterBookCasePage from "./pages/LibraryPage/EnterBookCasePage"; @@ -49,7 +50,10 @@ function App() { } /> } /> } /> - + } + /> } /> } /> } /> @@ -57,7 +61,7 @@ function App() { } /> } /> } /> - } /> + } /> } /> } /> } /> diff --git a/bookduck/src/api/bookinfo.js b/bookduck/src/api/bookinfo.js new file mode 100644 index 0000000..d22e665 --- /dev/null +++ b/bookduck/src/api/bookinfo.js @@ -0,0 +1,130 @@ +import { apiAuth } from "./api"; +import { get, post, patch, put, del } from "./example"; + +// 한줄평,별점 목록 조회 +export const getBookInfo = async ({ bookinfoId }) => { + try { + const res = await get(`/bookinfo/${bookinfoId}`); + console.log("책정보 조회 성공: ", res); + return res; + } catch (error) { + console.error("책정보 조회 실패: ", error); + throw error; + } +}; + +// 한줄평,별점 목록 조회 +export const getOneLineRatingsInfo = async ({ + bookinfoId, + orderBy, + page, + size, +}) => { + try { + // Query Parameter 생성 (값이 존재하는 경우에만 추가) + const queryParams = new URLSearchParams(); + if (orderBy) queryParams.append("orderBy", orderBy); + if (page) queryParams.append("page", page); + if (size) queryParams.append("size", size); + + // 최종 URL + const url = `/bookinfo/${bookinfoId}/onelineratings${ + queryParams.toString() ? `?${queryParams}` : "" + }`; + + const res = await get(url); + console.log("한줄평 목록 조회 성공: ", res); + return res; + } catch (error) { + console.error("한줄평 목록 조회 실패: ", error); + throw error; + } +}; +// 별점 등록 및 수정 +export const enrollRating = async (userbookId, rating) => { + const url = `books/${userbookId}/rating`; + const data = { rating }; + try { + const res = await patch(url, data); + console.log("별점 등록 성공: ", res); + return res?.data; + } catch (error) { + console.error("별점 등록 실패:", error); + throw error; + } +}; + +//별점 삭제 +export const deleteRating = async (userbookId) => { + const url = `books/${userbookId}/rating`; + try { + const res = await del(url); + console.log("별점 삭제 성공: ", res); + } catch (error) { + console.error("별점 삭제 실패: ", error); + throw error; + } +}; + +//한줄평 생성 +export const enrollOneLine = async (userBookId, oneLineContent) => { + const url = `onelines`; + const data = { oneLineContent, userBookId }; + try { + const res = await post(url, data); + console.log("한줄평 등록 성공: ", res); + return res?.data; + } catch (error) { + console.error("한줄평 등록 실패:", error); + throw error; + } +}; +// 한줄평 수정 +export const editOneLine = async (onelineId, oneLineContent) => { + const url = `onelines/${onelineId}`; + const data = { oneLineContent }; + try { + const res = await put(url, data); + console.log("한줄평 수정 성공: ", res); + return res?.data; + } catch (error) { + console.error("한줄평 수정 실패:", error); + throw error; + } +}; +//한줄평 삭제 +export const deleteOneLine = async (onelineId) => { + const url = `onelines/${onelineId}`; + try { + const res = await del(url); + console.log("한줄평 삭제 성공: ", res); + } catch (error) { + console.error("한줄평 삭제 실패: ", error); + throw error; + } +}; + +//한줄평 좋아요 +export const enrollLike = async (onelineId) => { + const url = `onelines/${onelineId}/like`; + try { + const res = await post(url); + console.log("한줄평 좋아요 성공: ", res); + return res; + } catch (error) { + console.error("한줄평 좋아요 실패:", error); + throw error; + } +}; + +//한줄평 좋아요 삭제 +export const deleteLike = async (onelineId) => { + const url = `onelines/${onelineId}/like`; + try { + const res = await del(url); + console.log("한줄평 좋아요 삭제 성공: ", res); + } catch (error) { + console.error("한줄평 좋아요 삭제 실패: ", error); + throw error; + } +}; diff --git a/bookduck/src/api/character.js b/bookduck/src/api/character.js new file mode 100644 index 0000000..8bf3f26 --- /dev/null +++ b/bookduck/src/api/character.js @@ -0,0 +1,38 @@ +import { apiAuth } from "./api"; +import { get, post, patch, put, del } from "./example"; + +// 유저 정보 조회 - 닉네임, 기록 수 +export const getUserInfo = async (userId) => { + try { + const res = await get(`/users/${userId}`); + console.log("유저 정보 조회 성공: ", res); + return res; + } catch (error) { + console.error("유저 정보 조회 실패: ", error); + throw error; + } +}; + +// 유저 레벨 정보 조회 +export const getUserLevelInfo = async (userId) => { + try { + const res = await get(`/users/${userId}/growth`); + console.log("유저 레벨 정보 조회 성공: ", res); + return res; + } catch (error) { + console.error("유저 레벨 정보 조회 실패: ", error); + throw error; + } +}; + +// 캐릭터 아이템 조회 +export const getItemLists = async () => { + try { + const res = await get(`/useritems`); + console.log("아이템 리스트 조회 성공: ", res); + return res; + } catch (error) { + console.error("아이템 리스트 조회 실패: ", error); + throw error; + } +}; diff --git a/bookduck/src/api/statistics.js b/bookduck/src/api/statistics.js new file mode 100644 index 0000000..f6c2c4c --- /dev/null +++ b/bookduck/src/api/statistics.js @@ -0,0 +1,26 @@ +import { apiAuth } from "./api"; +import { get, post, patch, put, del } from "./example"; + +// 유저 정보 조회 - 닉네임, 기록 수 +export const getUserInfo = async (userId) => { + try { + const res = await get(`/users/${userId}`); + console.log("유저 정보 조회 성공: ", res); + return res; + } catch (error) { + console.error("유저 정보 조회 실패: ", error); + throw error; + } +}; + +// 유저 독서 리포트 조회 - 통계 +export const getUserStatisticsInfo = async (userId) => { + try { + const res = await get(`/users/${userId}/statistics`); + console.log("유저 통계 정보 조회 성공: ", res); + return res; + } catch (error) { + console.error("유저 통계 정보 조회 실패: ", error); + throw error; + } +}; diff --git a/bookduck/src/assets/bookinfoPage/heart-no.svg b/bookduck/src/assets/bookinfoPage/heart-no.svg new file mode 100644 index 0000000..4fb9f5a --- /dev/null +++ b/bookduck/src/assets/bookinfoPage/heart-no.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/bookduck/src/assets/bookinfoPage/heart-yes.svg b/bookduck/src/assets/bookinfoPage/heart-yes.svg new file mode 100644 index 0000000..32bacf2 --- /dev/null +++ b/bookduck/src/assets/bookinfoPage/heart-yes.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/bookduck/src/assets/bookinfoPage/star-half.svg b/bookduck/src/assets/bookinfoPage/star-half.svg new file mode 100644 index 0000000..849ef7a --- /dev/null +++ b/bookduck/src/assets/bookinfoPage/star-half.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/bookduck/src/assets/bookinfoPage/star-small-half.svg b/bookduck/src/assets/bookinfoPage/star-small-half.svg new file mode 100644 index 0000000..b2044b0 --- /dev/null +++ b/bookduck/src/assets/bookinfoPage/star-small-half.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/bookduck/src/assets/bookinfoPage/star-small-yes.svg b/bookduck/src/assets/bookinfoPage/star-small-yes.svg new file mode 100644 index 0000000..3d213b9 --- /dev/null +++ b/bookduck/src/assets/bookinfoPage/star-small-yes.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/bookduck/src/assets/characterPage/bookduck.svg b/bookduck/src/assets/characterPage/bookduck.svg new file mode 100644 index 0000000..726e7f6 --- /dev/null +++ b/bookduck/src/assets/characterPage/bookduck.svg @@ -0,0 +1,3 @@ + + + diff --git a/bookduck/src/assets/statisticsPage/rectangle1.svg b/bookduck/src/assets/statisticsPage/rectangle1.svg index a8864db..641a250 100644 --- a/bookduck/src/assets/statisticsPage/rectangle1.svg +++ b/bookduck/src/assets/statisticsPage/rectangle1.svg @@ -1,8 +1,5 @@ - - - - - - + + + diff --git a/bookduck/src/assets/statisticsPage/rectangle2.svg b/bookduck/src/assets/statisticsPage/rectangle2.svg index 91e32b8..e890096 100644 --- a/bookduck/src/assets/statisticsPage/rectangle2.svg +++ b/bookduck/src/assets/statisticsPage/rectangle2.svg @@ -1,8 +1,3 @@ - - - - - - - + + diff --git a/bookduck/src/assets/statisticsPage/rectangle3.svg b/bookduck/src/assets/statisticsPage/rectangle3.svg index af117c5..87166c6 100644 --- a/bookduck/src/assets/statisticsPage/rectangle3.svg +++ b/bookduck/src/assets/statisticsPage/rectangle3.svg @@ -1,8 +1,3 @@ - - - - - - + diff --git a/bookduck/src/assets/statisticsPage/rectangle4.svg b/bookduck/src/assets/statisticsPage/rectangle4.svg index 0408189..8744a92 100644 --- a/bookduck/src/assets/statisticsPage/rectangle4.svg +++ b/bookduck/src/assets/statisticsPage/rectangle4.svg @@ -1,8 +1,3 @@ - - - - - - - + + diff --git a/bookduck/src/assets/statisticsPage/rectangle5.svg b/bookduck/src/assets/statisticsPage/rectangle5.svg index baa4764..1749559 100644 --- a/bookduck/src/assets/statisticsPage/rectangle5.svg +++ b/bookduck/src/assets/statisticsPage/rectangle5.svg @@ -1,8 +1,3 @@ - - - - - - - + + diff --git a/bookduck/src/assets/statisticsPage/rectangle6.svg b/bookduck/src/assets/statisticsPage/rectangle6.svg new file mode 100644 index 0000000..0d6f82b --- /dev/null +++ b/bookduck/src/assets/statisticsPage/rectangle6.svg @@ -0,0 +1,3 @@ + + + diff --git a/bookduck/src/assets/statisticsPage/rectangle7.svg b/bookduck/src/assets/statisticsPage/rectangle7.svg new file mode 100644 index 0000000..df79b36 --- /dev/null +++ b/bookduck/src/assets/statisticsPage/rectangle7.svg @@ -0,0 +1,3 @@ + + + diff --git a/bookduck/src/assets/statisticsPage/rectangle8.svg b/bookduck/src/assets/statisticsPage/rectangle8.svg new file mode 100644 index 0000000..de94132 --- /dev/null +++ b/bookduck/src/assets/statisticsPage/rectangle8.svg @@ -0,0 +1,3 @@ + + + diff --git a/bookduck/src/components/BookInfoPage/BookInfo.jsx b/bookduck/src/components/BookInfoPage/BookInfo.jsx index d4f4909..c0dc61c 100644 --- a/bookduck/src/components/BookInfoPage/BookInfo.jsx +++ b/bookduck/src/components/BookInfoPage/BookInfo.jsx @@ -1,46 +1,87 @@ +import { useState } from "react"; import cover from "../../assets/bookinfoPage/cover.svg"; import down from "../../assets/common/down-arrow.svg"; +import BottomSheetModal from "../common/BottomSheetModal"; +import ListBottomSheet from "../common/ListBottomSheet"; -const BookInfo = ({ isMe = "default" }) => { +const BookInfo = ({ isMe = "default", bookData }) => { + const statusArr = ["읽고 싶어요", "읽고 있어요", "다 읽었어요", "중단했어요"]; + const [currentState, setCurrentState] = useState("읽고 싶어요"); + const [bottomSheetShow, setBottomSheetShow] = useState(false); + const [visible, setVisible] = useState(false); + const [isCancel, setCancel] = useState(true); + + const handleStatusClick = () => { + setBottomSheetShow(true); + }; + + const handleStatusChange = (status) => { + setCurrentState(status); + }; + + const handlePutCancel = () => {}; + const bookBasicData = bookData?.bookInfoBasicDto; // 기본으로 등록되어 있는 책: default 내가 직접 등록한 책: me 타유저가 직접 등록한 책: other return (
Cover -
+
-
책 제목
-
지은이
-
-
출판사
-
-
2024
+
+ {bookBasicData?.title}
+
{bookBasicData?.author}
- {isMe === "other" ? ( -
- 유저닉네임님이 등록한 책 -
- ) : ( -
- {isMe === "default" ? ( -
-
기본평점
-
4.3
-
- ) : ( -
내가 등록한 책
- )} +
+ {isMe === "other" ? ( +
+ 유저닉네임님이 등록한 책 +
+ ) : ( +
+ {isMe === "default" ? ( +
+
기본평점
+
{bookData?.ratingAverage}
+
+ ) : ( +
내가 등록한 책
+ )} -
- 읽고 싶어요 - +
+ {currentState} + +
+ {bottomSheetShow && ( + + {" "} +
+ +
{" "} +
+ )}
-
- )} + )} +
); diff --git a/bookduck/src/components/BookInfoPage/BookPlot.jsx b/bookduck/src/components/BookInfoPage/BookPlot.jsx index 659749c..c03c047 100644 --- a/bookduck/src/components/BookInfoPage/BookPlot.jsx +++ b/bookduck/src/components/BookInfoPage/BookPlot.jsx @@ -1,11 +1,12 @@ import { useState, useRef, useEffect } from "react"; -const BookPlot = () => { +const BookPlot = ({ bookData }) => { const [isExpanded, setIsExpanded] = useState(false); const [showMoreBtn, setShowMoreBtn] = useState(false); const contentRef = useRef(null); - const text = `책 줄거리 소개글이 들어갈 자리입니다들.책 소개글이 들어갈 자리입니다들. 소개글이 들어갈 자리입니다들. 소개글이 들어갈 자리입니다들. 소개글이 들어갈 자리입니다들.줄거리 소개글이 들어갈 자리입니다.책 줄거리 소개글이 들어갈 자리입니다. 책 줄거리 및 소개글이 들어갈 자리입니다.`; + // const text = `책 줄거리 소개글이 들어갈 자리입니다들.책 소개글이 들어갈 자리입니다들. 소개글이 들어갈 자리입니다들. 소개글이 들어갈 자리입니다들. 소개글이 들어갈 자리입니다들.줄거리 소개글이 들어갈 자리입니다.책 줄거리 소개글이 들어갈 자리입니다. 책 줄거리 및 소개글이 들어갈 자리입니다.`; + const text = bookData?.description; useEffect(() => { if (contentRef.current) { setShowMoreBtn( @@ -22,21 +23,32 @@ const BookPlot = () => {
기본정보
-
-
- 카테고리 -
시/소설
+
+
+
+ 카테고리 +
{bookData?.koreanGenreName}
+
+
+ 출판사 +
{bookData?.publisher}
+
-
- 페이지 -
133pg
+
+
+ 페이지 +
{bookData?.pageCount}p
+
+
+ 출간일 +
{bookData?.publishedDate}
+
{ isExpanded ? "text-blue-400" : "absolute bottom-0 right-0 flex justify-end text-gray-400 bg-white-gradiation" - }`} > {isExpanded ? "접기" : "더보기"} diff --git a/bookduck/src/components/BookInfoPage/BottomSheetModal2.jsx b/bookduck/src/components/BookInfoPage/BottomSheetModal2.jsx index 6af2917..2db0352 100644 --- a/bookduck/src/components/BookInfoPage/BottomSheetModal2.jsx +++ b/bookduck/src/components/BookInfoPage/BottomSheetModal2.jsx @@ -34,6 +34,7 @@ const BottomSheetModal2 = ({ setBottomSheetShow, visible, setVisible, + handleEdit, handleDelete, deleteOnly = false, children, @@ -74,7 +75,10 @@ const BottomSheetModal2 = ({
{!deleteOnly && ( <> -
+
수정하기
diff --git a/bookduck/src/components/BookInfoPage/InfoView.jsx b/bookduck/src/components/BookInfoPage/InfoView.jsx index 0414f92..384419c 100644 --- a/bookduck/src/components/BookInfoPage/InfoView.jsx +++ b/bookduck/src/components/BookInfoPage/InfoView.jsx @@ -1,34 +1,53 @@ import { useNavigate } from "react-router-dom"; import right from "../../assets/common/right.svg"; +import cover from "../../assets/bookinfoPage/cover.svg"; import BookPlot from "./BookPlot"; import Divider1 from "../common/Divider1"; import Divider2 from "../common/Divider2"; import UserComment from "./UserComment"; -const InfoView = () => { +const InfoView = ({ bookData, ratingData }) => { + const images = [cover, cover, cover, cover, cover, cover, cover]; + const ratingList = ratingData?.oneLineRatingList || []; const navigate = useNavigate(); const handleCommentClick = () => { - navigate("/info/book/comment"); + navigate("/info/book/comment", { state: { ratingList } }); }; return ( -
- +
+
- 다른 사용자들의 한줄평 + 한줄평 ({ratingList?.length})
- - - - - - + {ratingList.map((oneLine, index) => ( +
+ +
+ +
+
+ ))} +
+ +
+ 이 책을 읽은 사용자들이 읽은 다른 책 +
+ {images.map((cover, index) => ( + {`book-cover-${index}`} + ))} +
); diff --git a/bookduck/src/components/BookInfoPage/MyComment.jsx b/bookduck/src/components/BookInfoPage/MyComment.jsx index 59007f8..ef32956 100644 --- a/bookduck/src/components/BookInfoPage/MyComment.jsx +++ b/bookduck/src/components/BookInfoPage/MyComment.jsx @@ -1,23 +1,43 @@ -import { useState } from "react"; +import { useState, useEffect } from "react"; import starNo from "../../assets/bookinfoPage/star-no.svg"; import starYes from "../../assets/bookinfoPage/star-yes.svg"; +import starHalf from "../../assets/bookinfoPage/star-half.svg"; import leftMark from "../../assets/bookinfoPage/left-quotmark.svg"; import rightMark from "../../assets/bookinfoPage/right-quotmark.svg"; import menu from "../../assets/bookinfoPage/menu-vertical.svg"; import BottomSheetModal from "../../components/common/BottomSheetModal"; import ButtonComponent from "../common/ButtonComponent"; import BottomSheetModal2 from "./BottomSheetModal2"; +import { + enrollRating, + deleteRating, + enrollOneLine, + editOneLine, + deleteOneLine, +} from "../../api/bookinfo"; -const MyComment = () => { +const MyComment = ({ bookData }) => { + const bookInfo = bookData?.bookInfoBasicDto; const [bottomSheetShow, setBottomSheetShow] = useState(false); const [bottomSheet2Show, setBottomSheet2Show] = useState(false); + const [editSheetShow, setEditSheetShow] = useState(false); const [visible, setVisible] = useState(false); const [visible2, setVisible2] = useState(false); + const [editVisible, setEditVisible] = useState(false); - const [inputValue, setInputValue] = useState(""); - const [text, setText] = useState(false); + const [inputValue, setInputValue] = useState(bookData?.myOneLine || ""); + const [text, setText] = useState(bookData?.myOneLine || ""); const [rating, setRating] = useState(0); - + // bookData.myRating 값이 바뀔 때 rating 상태를 업데이트 + useEffect(() => { + if (bookInfo?.myRating) { + setRating(bookInfo.myRating); + } + if (bookData?.myOneLine) { + setText(bookData.myOneLine); + setInputValue(bookData.myOneLine); + } + }, [bookData, bookInfo]); const handleChange = (event) => { const { value } = event.target; if (value.length <= 25) { @@ -30,80 +50,171 @@ const MyComment = () => { setBottomSheetShow(false); }, 200); }; - const handleCompleteClick = () => { + const handleEditCancleClick = () => { + setEditVisible(false); + setTimeout(() => { + setEditSheetShow(false); + }, 200); + }; + //수정하기 버튼 누르면 수정 바텀시트 올라오도록 함 + const handleEdit = () => { + // setEditVisible(true) + setBottomSheet2Show(false); + setVisible2(false); + setEditSheetShow(true); + }; + //한줄평 등록 + const handleCompleteClick = async (userbookId, oneLineContent) => { setText(inputValue); setVisible(false); setTimeout(() => { setBottomSheetShow(false); }, 200); + try { + await enrollOneLine(userbookId, oneLineContent); + window.location.reload(); + } catch (error) { + console.error(error); + } + }; + //한줄평 수정 + const handleEditClick = async (onelineId, oneLineContent) => { + setText(inputValue); + setEditVisible(false); + setTimeout(() => { + setEditSheetShow(false); + }, 200); + try { + await editOneLine(onelineId, oneLineContent); + } catch (error) { + console.error(error); + } + }; + // 한줄평 삭제 + const handleDeleteClick = async (onelineId) => { + setText(""); + setInputValue(""); + setVisible2(false); + setTimeout(() => { + setBottomSheet2Show(false); + }, 200); + try { + await deleteOneLine(onelineId); + window.location.reload(); + } catch (error) { + console.error(error); + } }; - const handleStarClick = (index) => { - if (rating === index + 1) { + const handleStarClick = async (index, event) => { + //클릭된 위치 + const { offsetX, target } = event.nativeEvent; + const starWidth = target.offsetWidth; + let newRating = offsetX < starWidth / 2 ? index + 0.5 : index + 1; + // 최소 별점 1점 적용 + if (newRating < 1) { + newRating = 1; + } + //클릭 범위 절반 이하인지 구분하기 + if (rating === newRating) { setRating(0); + try { + await deleteRating(bookInfo.userbookId); + } catch (error) { + console.error(error); + } } else { - setRating(index + 1); + setRating(newRating); + try { + await enrollRating(bookInfo.userbookId, newRating); + } catch (error) { + console.error(error); + } } }; + return (
- {text ? ( + {/* //책이 서재에 담긴 경우 */} + {bookInfo?.userbookId ? (
-
-
- {Array.from({ length: 5 }, (_, index) => ( + {/* 한줄평이 존재하는 경우 */} + {text ? ( +
+
+
+ {Array.from({ length: 5 }, (_, index) => { + let starSrc = starNo; + if (rating > index) { + starSrc = rating >= index + 1 ? starYes : starHalf; + } + return ( + handleStarClick(index, event)} + alt="star" + /> + ); + })} +
+
+ + {text} + +
handleStarClick(index)} - alt="star" + className="absolute top-5 right-5 cursor-pointer" + src={menu} + onClick={() => setBottomSheet2Show(true)} /> - ))} +
-
- - {text} - + ) : ( +
+
+
+ {Array.from({ length: 5 }, (_, index) => { + let starSrc = starNo; + if (rating > index) { + starSrc = rating >= index + 1 ? starYes : starHalf; + } + return ( + handleStarClick(index, event)} + alt="star" + /> + ); + })} +
+ setBottomSheetShow(true)} + > + 책에 대한 나의 한줄 평을 작성해주세요! + +
- setBottomSheet2Show(true)} - /> -
+ )}
) : ( -
-
-
- {Array.from({ length: 5 }, (_, index) => ( - handleStarClick(index)} - alt="star" - /> - ))} -
- setBottomSheetShow(true)} - > - 책에 대한 나의 한줄 평을 작성해주세요! - -
+
+ 책을 서재에 담아 +
+ 별점과 한줄평을 입력해보세요
)} - -
+
한줄평 작성 { handleCompleteClick(bookInfo.userbookId, inputValue)} />
@@ -132,7 +243,39 @@ const MyComment = () => { setBottomSheetShow={setBottomSheet2Show} visible={visible2} setVisible={setVisible2} + handleEdit={handleEdit} + handleDelete={() => handleDeleteClick(bookData?.oneLineId)} > + +
+
+ 한줄평 수정 + + 취소 + +
+ + handleEditClick(bookData?.oneLineId, inputValue)} + /> +
+
); }; diff --git a/bookduck/src/components/BookInfoPage/UserComment.jsx b/bookduck/src/components/BookInfoPage/UserComment.jsx index ea911ff..74cf2e7 100644 --- a/bookduck/src/components/BookInfoPage/UserComment.jsx +++ b/bookduck/src/components/BookInfoPage/UserComment.jsx @@ -1,18 +1,72 @@ +import { useState } from "react"; import starNo from "../../assets/bookinfoPage/star-small-no.svg"; -const UserComment = () => { +import starYes from "../../assets/bookinfoPage/star-small-yes.svg"; +import starHalf from "../../assets/bookinfoPage/star-small-half.svg"; +import heartNo from "../../assets/bookinfoPage/heart-no.svg"; +import heartYes from "../../assets/bookinfoPage/heart-yes.svg"; +import { enrollLike, deleteLike } from "../../api/bookinfo"; +import { getUserId } from "../../api/oauth"; + +const UserComment = ({ data }) => { + //날짜 포맷 + const formattedDate = data?.createdTime.split("T")[0].replace(/-/g, "."); + const [isLiked, setIsLiked] = useState(false); + const [likeCount, setLikeCount] = useState(data?.oneLineLikeCount || 0); + const userId = getUserId(); + console.log(userId); + const handleLikeClick = async (onelineId) => { + try { + if (isLiked) { + //좋아요 취소 + setIsLiked(false); + setLikeCount(likeCount - 1); + await deleteLike(onelineId); + } else { + //좋아요 등록 + setIsLiked(true); + setLikeCount(likeCount + 1); + await enrollLike(onelineId); + } + } catch (error) { + console.error("좋아요 처리 실패:", error); + } + }; + return ( -
-
+
+
- - - - - + {Array.from({ length: 5 }, (_, index) => { + let starSrc = starNo; + if (data?.rating > index) { + starSrc = data?.rating >= index + 1 ? starYes : starHalf; + } + return star; + })} +
+ {data?.oneLineContent} +
+
+
+ {userId === data.userId ? ( + 나의 한줄평 + ) : ( + + {data?.userNickname} + + )} + {formattedDate} +
+
+ handleLikeClick(data.oneLineId)} + /> + + {likeCount} +
- 유저 닉네임
- 타유저가 작성한 한줄평이 들어갈 자리입니다.
); }; diff --git a/bookduck/src/components/CharacterPage/LevelModal.jsx b/bookduck/src/components/CharacterPage/LevelModal.jsx new file mode 100644 index 0000000..4629d78 --- /dev/null +++ b/bookduck/src/components/CharacterPage/LevelModal.jsx @@ -0,0 +1,30 @@ +const LevelModal = ({ onClick }) => { + return ( +
+
+
+
+ 레벨업을 위한 팁! +
+
+ 1. 독서기록 작성하기 + 2. 별점과 한줄평 꼭 남기기 + 3. 완독 후 ‘다 읽었어요’로 저장하기 +
+
+ 💡 레벨업 팁을 참고하여 경험치를 쌓아보세요! +
+
+
+ +
+
+
+ ); +}; +export default LevelModal; diff --git a/bookduck/src/components/CharacterPage/UserDuck.jsx b/bookduck/src/components/CharacterPage/UserDuck.jsx index 9d2a7a1..e527281 100644 --- a/bookduck/src/components/CharacterPage/UserDuck.jsx +++ b/bookduck/src/components/CharacterPage/UserDuck.jsx @@ -4,12 +4,20 @@ import { useLoader } from "@react-three/fiber"; import { OrbitControls } from "@react-three/drei"; import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader"; +// 기본 오리캐릭터 불러오기 const DuckModel = () => { const gltf = useLoader(GLTFLoader, "/assets/characterPage/duck.glb"); - return ; }; +// 아이템 목록 불러오기 +const HairBand = () => { + const gltf = useLoader(GLTFLoader, "/assets/items/hairband.glb"); + return ( + + ); +}; + const UserDuck = () => { return (
@@ -17,6 +25,7 @@ const UserDuck = () => { + {/* */} { + return ( +
+ +
+ 여덟글자잖아요오님은 +
+ 문학에 진심인 문학덕! +
+
+ +
+
+ #슬픈 #아름다운 #귀여운 +
+
+ ); +}; +export default ExportCharacter; diff --git a/bookduck/src/components/StatisticsPage/MonthlyReading.jsx b/bookduck/src/components/StatisticsPage/MonthlyReading.jsx index 2e467bf..c0e2ac6 100644 --- a/bookduck/src/components/StatisticsPage/MonthlyReading.jsx +++ b/bookduck/src/components/StatisticsPage/MonthlyReading.jsx @@ -1,11 +1,12 @@ import StatisticsHeader from "./StatisticsHeader"; import BookRecord from "./BookRecord"; -const MonthlyReading = () => { +const MonthlyReading = ({ userData }) => { return (
- {/* 하반기도 만들어야함 */} - +
@@ -14,14 +15,25 @@ const MonthlyReading = () => {
-
- 1월 - 2월 - 3월 - 4월 - 5월 - 6월 -
+ {userData.isFirstHalf ? ( +
+ 1월 + 2월 + 3월 + 4월 + 5월 + 6월 +
+ ) : ( +
+ 7월 + 8월 + 9월 + 10월 + 11월 + 12월 +
+ )}
); diff --git a/bookduck/src/components/StatisticsPage/MyKeyword.jsx b/bookduck/src/components/StatisticsPage/MyKeyword.jsx index abdfbaf..82e7792 100644 --- a/bookduck/src/components/StatisticsPage/MyKeyword.jsx +++ b/bookduck/src/components/StatisticsPage/MyKeyword.jsx @@ -3,41 +3,62 @@ import rectangle2 from "../../assets/statisticsPage/rectangle2.svg"; import rectangle3 from "../../assets/statisticsPage/rectangle3.svg"; import rectangle4 from "../../assets/statisticsPage/rectangle4.svg"; import rectangle5 from "../../assets/statisticsPage/rectangle5.svg"; +import rectangle6 from "../../assets/statisticsPage/rectangle6.svg"; +import rectangle7 from "../../assets/statisticsPage/rectangle7.svg"; +import rectangle8 from "../../assets/statisticsPage/rectangle8.svg"; const MyKeyword = () => { return (
내 기록 키워드예요 -
+
슬픈
- 아름다운 + 아름
- 귀여운 + 귀여운운
+
+ 아름다운 +
+
+ 아름다운 +
+
+ 아름다운 +
); diff --git a/bookduck/src/components/StatisticsPage/PreferredAuthor.jsx b/bookduck/src/components/StatisticsPage/PreferredAuthor.jsx index 1917431..29954a5 100644 --- a/bookduck/src/components/StatisticsPage/PreferredAuthor.jsx +++ b/bookduck/src/components/StatisticsPage/PreferredAuthor.jsx @@ -1,25 +1,40 @@ -import book_cover from "../../assets/common/book-cover.svg"; +// import book_cover from "../../assets/common/book-cover.svg"; -const PreferredAuthor = () => { - const images = [book_cover, book_cover, book_cover]; +const PreferredAuthor = ({ author, imgPath }) => { + // const images = [book_cover, book_cover, book_cover]; + const images = imgPath; + console.log(images); return (
-
- 선호하는 작가예요 - - 어스트 허밍웨이 - -
-
- {images.map((cover, index) => ( - {`book-cover-${index}`} - ))} -
+ {/* 정보가 없으면 정보 부족 문구 띄움 */} + {author && images ? ( + <> +
+ 선호하는 작가예요 + + {author} + +
+
+ {images.map((cover, index) => ( + {`book-cover-${index}`} + ))} +
+ + ) : ( +
+ 선호하는 작가예요 +
+ 북덕이 분석하기에 +
정보가 부족해요 +
+
+ )}
); }; diff --git a/bookduck/src/components/StatisticsPage/PreferredGenre.jsx b/bookduck/src/components/StatisticsPage/PreferredGenre.jsx index a346a81..eb91c18 100644 --- a/bookduck/src/components/StatisticsPage/PreferredGenre.jsx +++ b/bookduck/src/components/StatisticsPage/PreferredGenre.jsx @@ -1,32 +1,46 @@ import StatisticsHeader from "./StatisticsHeader"; - -const PreferredGenre = () => { - const categories = [ - { id: 1, name: "로맨스", count: "7권" }, - { id: 2, name: "경제, 경영", count: "5권" }, - { id: 3, name: "소설", count: "3권" }, - ]; +const PreferredGenre = ({ userData }) => { + // const categories = [ + // { id: 1, name: "로맨스", count: "7권" }, + // { id: 2, name: "경제, 경영", count: "5권" }, + // { id: 3, name: "소설", count: "3권" }, + // ]; + const categories = userData || []; return (
-
-
- 선호하는 장르 TOP3를 - 알려드릴게요 + + {categories.length > 0 ? ( +
+
+ 선호하는 장르 TOP3를 + 알려드릴게요 +
+
+ {categories.map((category, index) => ( +
+ {index + 1} + {category.genreName} + {category.bookCount} +
+ ))} +
-
- {categories.map((category, index) => ( -
- {category.id} - {category.name} - {category.count} -
- ))} + ) : ( +
+
+ 선호하는 장르 TOP3를 + 알려드릴게요 +
+
+ 북덕이 분석하기에 +
정보가 부족해요 +
-
+ )}
); }; diff --git a/bookduck/src/components/StatisticsPage/UserCard.jsx b/bookduck/src/components/StatisticsPage/UserCard.jsx index 0651d7d..067ac43 100644 --- a/bookduck/src/components/StatisticsPage/UserCard.jsx +++ b/bookduck/src/components/StatisticsPage/UserCard.jsx @@ -1,11 +1,11 @@ import right from "../../assets/statisticsPage/right.svg"; import UserDuck from "../CharacterPage/UserDuck"; -const UserCard = () => { +const UserCard = ({ nickname }) => { return (
- 여덟글자잖아요오님은 + {nickname}님은
문학에 진심인 문학덕!
diff --git a/bookduck/src/components/common/Divider2.jsx b/bookduck/src/components/common/Divider2.jsx index b995df8..abefd7b 100644 --- a/bookduck/src/components/common/Divider2.jsx +++ b/bookduck/src/components/common/Divider2.jsx @@ -1,4 +1,4 @@ const Divider2 = () => { - return
; + return
; }; export default Divider2; diff --git a/bookduck/src/pages/BookInfoPage/BookInfoPage.jsx b/bookduck/src/pages/BookInfoPage/BookInfoPage.jsx index c06f6c5..6403ecb 100644 --- a/bookduck/src/pages/BookInfoPage/BookInfoPage.jsx +++ b/bookduck/src/pages/BookInfoPage/BookInfoPage.jsx @@ -1,4 +1,5 @@ -import { useState } from "react"; +import { useState, useEffect } from "react"; +import { useParams } from "react-router-dom"; import Header3 from "../../components/common/Header3"; import BookInfo from "../../components/BookInfoPage/BookInfo"; import TabBarComponent from "../../components/common/TabBarComponent"; @@ -6,16 +7,41 @@ import InfoView from "../../components/BookInfoPage/InfoView"; import ArchiveView from "../../components/BookInfoPage/ArchiveView"; import FloatingRecordButton from "../../components/common/FloatingRecordButton"; import MyComment from "../../components/BookInfoPage/MyComment"; +import { getBookInfo, getOneLineRatingsInfo } from "../../api/bookinfo"; + const BookInfoPage = () => { + const { bookinfoId } = useParams(); const [activeTab, setActiveTab] = useState("책 정보"); + const [RatingListData, setRatingListData] = useState(null); + const [bookData, setBookData] = useState(null); + const [loading, setLoading] = useState(true); + + useEffect(() => { + const fetchData = async () => { + try { + const res = await getBookInfo({ bookinfoId }); + const res2 = await getOneLineRatingsInfo({ bookinfoId }); + console.log("조회성공: ", res); + setBookData(res); + setRatingListData(res2); + console.log("조회2 성공: ", res2); + } catch (err) { + console.error("오류 발생: ", err); + } finally { + setLoading(false); + } + }; + fetchData(); + }, []); + return (
- - + +
{ size="" />
- {activeTab === "책 정보" && } + {activeTab === "책 정보" && ( + + )} {activeTab === "기록" && }
diff --git a/bookduck/src/pages/BookInfoPage/UserCommentPage.jsx b/bookduck/src/pages/BookInfoPage/UserCommentPage.jsx index 67efb02..2809805 100644 --- a/bookduck/src/pages/BookInfoPage/UserCommentPage.jsx +++ b/bookduck/src/pages/BookInfoPage/UserCommentPage.jsx @@ -1,22 +1,22 @@ +import { useLocation } from "react-router-dom"; import Header3 from "../../components/common/Header3"; import UserComment from "../../components/BookInfoPage/UserComment"; import Divider2 from "../../components/common/Divider2"; const UserCommentPage = () => { + const { state } = useLocation(); + const ratingList = state?.ratingList || []; return (
- +
- - - - - - - - - - + {ratingList.length > 0 && + ratingList.map((oneLine, index) => ( +
+ + +
+ ))}
diff --git a/bookduck/src/pages/CharacterPage/CharacterPage.jsx b/bookduck/src/pages/CharacterPage/CharacterPage.jsx index 654d5c4..84254b7 100644 --- a/bookduck/src/pages/CharacterPage/CharacterPage.jsx +++ b/bookduck/src/pages/CharacterPage/CharacterPage.jsx @@ -1,11 +1,14 @@ +import { useState, useEffect } from "react"; import { useNavigate } from "react-router-dom"; import polygon from "../../assets/characterPage/polygon.svg"; -import bookIcon from "../../assets/characterPage/book.svg"; -import reviewIcon from "../../assets/characterPage/review.svg"; import right from "../../assets/characterPage/right.svg"; +import help from "../../assets/characterPage/help-circle.svg"; import UserDuck from "../../components/CharacterPage/UserDuck"; import CharacterHeader from "../../components/CharacterPage/CharacterHeader"; import BottomNavbar from "../../components/common/BottomNavbar"; +import LevelModal from "../../components/CharacterPage/LevelModal"; +import { getUserId } from "../../api/oauth"; +import { getUserInfo, getUserLevelInfo } from "../../api/character"; const CharacterPage = () => { const navigate = useNavigate(); @@ -13,71 +16,92 @@ const CharacterPage = () => { const handleBadgeClick = () => { navigate("/myBadge"); }; + const [isModalOpen, setIsModalOpen] = useState(false); + + const toggleModal = () => { + setIsModalOpen(!isModalOpen); + }; + + const [userData, setUserData] = useState(null); + const [userInfo, setUserInfo] = useState(null); + const [loading, setLoading] = useState(true); + + useEffect(() => { + const fetchData = async () => { + try { + const userId = getUserId(); + const res = await getUserLevelInfo(userId); + const res2 = await getUserInfo(userId); + console.log("조회성공: ", res); + setUserData(res); + setUserInfo(res2); + } catch (err) { + console.error("오류 발생: ", err); + } finally { + setLoading(false); + } + }; + fetchData(); + }, []); + if (loading) { + return
; + } + const expProgress = + userData?.expInCurrentLevel === 0 + ? 0 + : Math.round( + (userData?.expInCurrentLevel / userData?.expToNextLevel) * 100 + ); return (
-
+
캐릭터 말풍선
-
+
{/* 유저 정보 */} -
-
-
레벨1
-
유저 닉네임
+
+
+
+
+ 레벨{userData?.level} +
+
{userInfo?.nickname}
+
+
+ 레벨업 + +
+ {isModalOpen && }
-
-
-
10%
-
-
- {/* 레벨업미션 컴포넌트 분리 고려중..*/} -
-
레벨업 미션
-
- {/* 레벨업 미션 detail */} -
-
-
- book Icon -
-
- 다 읽었어요 10권 달성하기 -
-
- 3/10 -
-
-
-
- review Icon -
-
- 독서기록 10개 작성하기 -
-
- 1/10 +
+
{expProgress}%
+ {/* 나의 배지 */}
나의 배지
- {/* 네비바 들어갈 자리 */}
); diff --git a/bookduck/src/pages/StatisticsPage/CharacterExportPage.jsx b/bookduck/src/pages/StatisticsPage/CharacterExportPage.jsx new file mode 100644 index 0000000..ffa5390 --- /dev/null +++ b/bookduck/src/pages/StatisticsPage/CharacterExportPage.jsx @@ -0,0 +1,17 @@ +import Header3 from "../../components/common/Header3"; +import ExportCharacter from "../../components/StatisticsPage/ExportCharacter"; + +const CharacterExportPage = () => { + return ( +
+ +
+ +
+ 이미지 저장 +
+
+
+ ); +}; +export default CharacterExportPage; diff --git a/bookduck/src/pages/StatisticsPage/StatisticsPage.jsx b/bookduck/src/pages/StatisticsPage/StatisticsPage.jsx index e096150..e86a8e3 100644 --- a/bookduck/src/pages/StatisticsPage/StatisticsPage.jsx +++ b/bookduck/src/pages/StatisticsPage/StatisticsPage.jsx @@ -1,3 +1,4 @@ +import { useEffect, useState } from "react"; import Header3 from "../../components/common/Header3"; import RecordCalender from "../../components/StatisticsPage/RecordCalender"; import Divider2 from "../../components/common/Divider2"; @@ -7,31 +8,67 @@ import PreferredAuthor from "../../components/StatisticsPage/PreferredAuthor"; import PreferredGenre from "../../components/StatisticsPage/PreferredGenre"; import MonthlyReading from "../../components/StatisticsPage/MonthlyReading"; import UserCard from "../../components/StatisticsPage/UserCard"; +import { getUserStatisticsInfo } from "../../api/statistics"; +import { getUserId } from "../../api/oauth"; const StatisticsPage = () => { + const [userData, setUserData] = useState(null); + const [loading, setLoading] = useState(true); + + useEffect(() => { + const fetchData = async () => { + try { + const userId = getUserId(); + // const userId = 1; + + const res = await getUserStatisticsInfo(userId); + console.log("조회성공: ", res); + setUserData(res); + } catch (err) { + console.error("오류 발생: ", err); + } finally { + setLoading(false); + } + }; + fetchData(); + }, []); + if (loading) { + return
; + } return (
- +
- 이번달 기록은 총 16개! + {userData?.isFirstHalf ? "상반기" : "하반기"} 기록은 총{" "} + + {userData.excerptCount + userData.bookRecordCount} + + 개!
- - - + + +
- - + + {/* 같은 작가의 작품을 최소 2권 이상을 읽어야 ‘선호하는 작가' 카드가 보임 */} - +
- 북덕은 언제나 여덟글자잖아요오님의 + 북덕은 언제나 {userData?.nickname}님의
독서를 응원합니다!
diff --git a/bookduck/tailwind.config.js b/bookduck/tailwind.config.js index ca49887..671cd59 100644 --- a/bookduck/tailwind.config.js +++ b/bookduck/tailwind.config.js @@ -63,7 +63,7 @@ export default { }, fontSize: { t1: ["1.5rem", { lineHeight: "2.25rem" }], //24px - t2: ["1.25rem", { lineHeight: "1.75rem" }], //20px /26px + t2: ["1.25rem", { lineHeight: "1.75rem" }], //20px /28px st: ["1.125rem", { lineHeight: "1.5rem" }], //18px / 24px b1: ["1rem", { lineHeight: "1.5rem" }], //16px / 24px b2: ["0.875rem", { lineHeight: "1.5rem" }], //14px /24px