Skip to content

Commit 65be57d

Browse files
authored
feat: 좋아요 페이지 api 연결 (#221)
* feat: 좋아요 페이지, 좋아한 작품 api 연결 * feat: 좋아요 페이지 페이지네이션 구현 * feat: 작품 둘러보기 페이지 네이션 구현 * fix: 이미지 비율 수정 * fix: 작품 관리 상세 accessToken 오류 수정
1 parent 2fa46cd commit 65be57d

File tree

19 files changed

+1064
-213
lines changed

19 files changed

+1064
-213
lines changed

src/App.jsx

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ import SignInV2 from "./pages/auth/SignInV2";
1515
import FindBar from "./pages/auth/FindBar";
1616

1717
import PostGallery from "./pages/work/postList/PostGallery";
18+
import PostGalleryShort from "./pages/work/postList/PostGalleryShort";
19+
import PostGalleryLong from "./pages/work/postList/PostGalleryLong";
1820
import Detail from "./pages/work/Detail";
1921
import PostWork from "./pages/work/PostWork";
2022
import Purchase from "./pages/payment/Purchase";
@@ -34,7 +36,9 @@ import ScriptManageDetail from "./pages/myPage/PostManage/PostManageDetail";
3436

3537
import AskedPerformManage from "./pages/myPage/AskedPerformManage";
3638
import AccountInfoChange from "./pages/myPage/AccountInfoChange";
37-
import LikedWorks from "./pages/myPage/LikedWorksPage";
39+
import LikedWorks from "./pages/myPage/Liked/LikedWorks";
40+
import LikedLong from "./pages/myPage/Liked/LikedLong";
41+
import LikedShort from "./pages/myPage/Liked/LikedShort";
3842
import Loading from "./pages/Loading";
3943
import NotFound from "./pages/NotFound";
4044

@@ -77,6 +81,8 @@ function App() {
7781

7882
{/* <Route path="list" element={<List />} /> */}
7983
<Route path="list" element={<PostGallery />} />
84+
<Route path="list/long" element={<PostGalleryLong />} />
85+
<Route path="list/short" element={<PostGalleryShort />} />
8086
<Route path="list/detail/:id" element={<Detail />} />
8187

8288
<Route
@@ -102,6 +108,14 @@ function App() {
102108
path="mypage/liked"
103109
element={<ProtectedRoute element={<LikedWorks />} />}
104110
/>
111+
<Route
112+
path="mypage/liked/long"
113+
element={<ProtectedRoute element={<LikedLong />} />}
114+
/>
115+
<Route
116+
path="mypage/liked/short"
117+
element={<ProtectedRoute element={<LikedShort />} />}
118+
/>
105119
<Route
106120
path="mypage/purchased"
107121
element={<ProtectedRoute element={<PurchasedScript />} />}

src/api/user/postListApi.ts

Lines changed: 48 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,15 +45,13 @@ export const fetchExploreScripts = async (
4545
withCredentials: true, // 쿠키 인증 시 필요
4646
});
4747

48-
4948
const { longPlay, shortPlay } = response.data;
5049
console.log(longPlay);
5150
console.log(shortPlay);
5251
return {
5352
longPlay: Array.isArray(longPlay) ? longPlay : [],
5453
shortPlay: Array.isArray(shortPlay) ? shortPlay : [],
5554
};
56-
5755
} catch (error) {
5856
console.error("Error fetchExploreScripts:", error);
5957
throw new Error(`작품 둘러보기 API 호출 실패: ${(error as Error).message}`);
@@ -85,3 +83,51 @@ export const toggleLikeScript = async (
8583
throw new Error(`좋아요 토글 실패: ${(error as Error).message}`);
8684
}
8785
};
86+
87+
// 좋아한 장편 작품 목록 조회
88+
export const getLongWorks = async (
89+
page: number = 0,
90+
accessToken?: string
91+
): Promise<ScriptItem[]> => {
92+
try {
93+
const headers: Record<string, string> = {};
94+
95+
if (accessToken) {
96+
headers["Authorization"] = `Bearer ${accessToken}`;
97+
}
98+
99+
const response = await api.get<ScriptItem[]>(`/scripts/long`, {
100+
params: { page },
101+
headers,
102+
});
103+
console.log(response);
104+
return response.data;
105+
} catch (error) {
106+
console.error("Error fetching long works:", error);
107+
throw new Error(` 장편 작품 API 호출 실패: ${(error as Error).message}`);
108+
}
109+
};
110+
111+
// 좋아한 장편 작품 목록 조회
112+
export const getShortWorks = async (
113+
page: number = 0,
114+
accessToken?: string
115+
): Promise<ScriptItem[]> => {
116+
try {
117+
const headers: Record<string, string> = {};
118+
119+
if (accessToken) {
120+
headers["Authorization"] = `Bearer ${accessToken}`;
121+
}
122+
123+
const response = await api.get<ScriptItem[]>(`/scripts/short`, {
124+
params: { page },
125+
headers,
126+
});
127+
128+
return response.data;
129+
} catch (error) {
130+
console.error("Error fetchin short works:", error);
131+
throw new Error(`단편 작품 API 호출 실패: ${(error as Error).message}`);
132+
}
133+
};

src/api/user/profile/likeApi.ts

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
import axios from "axios";
2+
import Cookies from "js-cookie";
3+
4+
import { SERVER_URL } from "@/constants/ServerURL";
5+
import { ScriptItem, ExploreScriptsResponse } from "@/api/user/postListApi";
6+
const api = axios.create({
7+
baseURL: SERVER_URL, // Use environment variables
8+
timeout: 10000,
9+
});
10+
11+
// 좋아한 작품
12+
export const fetchLikedPost = async (
13+
accessToken?: string
14+
): Promise<ExploreScriptsResponse> => {
15+
try {
16+
const headers: Record<string, string> = {};
17+
18+
if (accessToken) {
19+
headers["Authorization"] = `Bearer ${accessToken}`;
20+
}
21+
22+
const response = await api.get<ExploreScriptsResponse>("/profile/like", {
23+
headers,
24+
withCredentials: true, // 쿠키 인증 시 필요
25+
});
26+
27+
const { longPlay, shortPlay } = response.data;
28+
console.log(longPlay);
29+
console.log(shortPlay);
30+
return {
31+
longPlay: Array.isArray(longPlay) ? longPlay : [],
32+
shortPlay: Array.isArray(shortPlay) ? shortPlay : [],
33+
};
34+
} catch (error) {
35+
console.error("Error fetchLikedPost:", error);
36+
throw new Error(`좋아한 작품 API 호출 실패: ${(error as Error).message}`);
37+
}
38+
};
39+
40+
// 좋아한 장편 작품 목록 조회
41+
export const getLikedLongWorks = async (
42+
page: number = 0,
43+
accessToken?: string
44+
): Promise<ScriptItem[]> => {
45+
try {
46+
const headers: Record<string, string> = {};
47+
48+
if (accessToken) {
49+
headers["Authorization"] = `Bearer ${accessToken}`;
50+
}
51+
52+
const response = await api.get<ScriptItem[]>(`/profile/like/long`, {
53+
params: { page },
54+
headers,
55+
});
56+
57+
return response.data;
58+
} catch (error) {
59+
console.error("Error fetching liked long works:", error);
60+
throw new Error(
61+
`좋아한 장편 작품 API 호출 실패: ${(error as Error).message}`
62+
);
63+
}
64+
};
65+
66+
// 좋아한 장편 작품 목록 조회
67+
export const getLikedShortWorks = async (
68+
page: number = 0,
69+
accessToken?: string
70+
): Promise<ScriptItem[]> => {
71+
try {
72+
const headers: Record<string, string> = {};
73+
74+
if (accessToken) {
75+
headers["Authorization"] = `Bearer ${accessToken}`;
76+
}
77+
78+
const response = await api.get<ScriptItem[]>(`/profile/like/short`, {
79+
params: { page },
80+
headers,
81+
});
82+
console.log(response);
83+
return response.data;
84+
85+
} catch (error) {
86+
console.error("Error fetching liked short works:", error);
87+
throw new Error(
88+
`좋아한 단편 작품 API 호출 실패: ${(error as Error).message}`
89+
);
90+
}
91+
};

src/components/myPage/MyPageMenu.jsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,9 @@ const MyPageMenu = ({ nickname, currentPage, isFooterVisible }) => {
4040
? "select-menu-btn selected liked"
4141
: "select-menu-btn"
4242
}
43-
// onClick={() => {
44-
// navigate("/mypage/liked");
45-
// }}
43+
onClick={() => {
44+
navigate("/mypage/liked");
45+
}}
4646
>
4747
<p className="p-medium-regular">좋아한 작품</p>
4848
<img src={likedMenuImg} alt="likedMenu"></img>

src/components/post/OnePostCard.tsx

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,18 +28,21 @@ export const OnePostCard = ({ posts, viewType, onToggleLike }: Props) => {
2828
};
2929

3030
return (
31-
3231
<div
3332
key={posts.id}
3433
className="flex flex-col items-center max-w-[197px] min-w-[197px] cursor-pointer "
3534
onClick={handleCardClick}
3635
>
3736
{/* 이미지 */}
38-
<div className="flex relative rounded-[20px] mb-[7px] ">
37+
<div
38+
className={`flex relative rounded-[20px] bg-white mb-[7px] ${
39+
posts.imagePath !== "" ? "border border-[var(--grey3)]" : ""
40+
}`}
41+
>
3942
<img
4043
src={posts.imagePath === "" ? defaultImg : posts.imagePath}
4144
alt={posts.title}
42-
className="object-cover w-full shrink-0 rounded-[20px]"
45+
className="object-contain w-[197px] h-[197px] shrink-0 rounded-[20px]"
4346
/>
4447
<div className="absolute top-[80%] right-[5%]">
4548
<button onClick={handleLikeClick}>
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
// src/components/post/PostHeaderControl.tsx
2+
import React from "react";
3+
import StageTab from "./StageTab";
4+
import ViewToggleButton from "./ViewToggleButton";
5+
import StoryLengthTeb from "@/components/post/StoryLengthTabs";
6+
import SortDropdown from "@/components/post/SortDropdown";
7+
8+
interface PostHeaderControlProps {
9+
activeCategory: string;
10+
setActiveCategory: (value: string) => void;
11+
activeStoryLength: string;
12+
setActiveStoryLength: (value: string) => void;
13+
viewType: "grid" | "card";
14+
setViewType: (value: "grid" | "card") => void;
15+
page: string;
16+
isSorted?: boolean;
17+
sortType?: string;
18+
setSortType?: (value: string) => void;
19+
}
20+
21+
const PostHeaderControl: React.FC<PostHeaderControlProps> = ({
22+
activeCategory,
23+
setActiveCategory,
24+
activeStoryLength,
25+
setActiveStoryLength,
26+
viewType,
27+
setViewType,
28+
page,
29+
isSorted,
30+
sortType,
31+
setSortType,
32+
}) => {
33+
return (
34+
<>
35+
<StageTab
36+
activeCategory={activeCategory}
37+
setActiveCategory={setActiveCategory}
38+
/>
39+
<span className="w-full h-[1px] block bg-[#E2E2E2]" />
40+
41+
<div className="flex items-center justify-between w-full mb-[35px]">
42+
<StoryLengthTeb
43+
activeStoryLength={activeStoryLength}
44+
setActiveStoryLength={setActiveStoryLength}
45+
page={page}
46+
/>
47+
<div className="flex items-center flex-row gap-[10px] h-full ">
48+
{isSorted ? (
49+
<SortDropdown selected={sortType} onChange={setSortType} />
50+
) : (
51+
<></>
52+
)}
53+
54+
<ViewToggleButton viewType={viewType} setViewType={setViewType} />
55+
</div>
56+
</div>
57+
</>
58+
);
59+
};
60+
61+
export default PostHeaderControl;

src/components/post/SortDropdown.tsx

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,14 @@ import { useState, useRef, useEffect } from "react";
22
import downDropIcon from "@/assets/image/post/ic_arrow_down.svg";
33
const SORT_OPTIONS = ["조회수순", "좋아요순", "최신순"];
44

5-
const SortDropdown = ({
6-
selected,
7-
onChange,
8-
}: {
9-
selected: string;
10-
onChange: (val: string) => void;
5+
interface SortDropdownProps {
6+
selected?: string; // optional
7+
onChange?: (val: string) => void; // optional
8+
}
9+
10+
const SortDropdown: React.FC<SortDropdownProps> = ({
11+
selected = "조회수",
12+
onChange = () => {},
1113
}) => {
1214
const [isOpen, setIsOpen] = useState(false);
1315
const dropdownRef = useRef<HTMLDivElement>(null);

src/components/post/StageTab.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import React from "react";
2+
import { useNavigate } from "react-router-dom";
23

34
interface Props {
45
activeCategory: string;
@@ -9,6 +10,7 @@ const stages = ["포도밭", "포도알", "포도송이", "와인"];
910
const availableStages = ["포도밭"];
1011

1112
const StageTab = ({ activeCategory, setActiveCategory }: Props) => {
13+
const navigate = useNavigate();
1214
return (
1315
<>
1416
<ul

src/components/post/StoryLengthTabs.tsx

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,57 @@
1-
import React from "react";
1+
import React, { useEffect } from "react";
2+
import { useNavigate } from "react-router-dom";
3+
import { useLocation } from "react-router-dom";
24

35
interface Props {
46
activeStoryLength: string;
57
setActiveStoryLength: (value: string) => void;
8+
page: string; // 이동할 페이지
69
}
710

811
const storyLength = ["전체", "장편", "단편"];
912

10-
const StoryLengthTeb = ({ activeStoryLength, setActiveStoryLength }: Props) => {
13+
const pathMap: Record<string, string> = {
14+
전체: "",
15+
장편: "/long",
16+
단편: "/short",
17+
};
18+
19+
const StoryLengthTeb = ({
20+
activeStoryLength,
21+
setActiveStoryLength,
22+
page,
23+
}: Props) => {
24+
const navigate = useNavigate();
25+
const location = useLocation();
26+
27+
useEffect(() => {
28+
if (location.pathname.includes("/long")) {
29+
setActiveStoryLength("장편");
30+
} else if (location.pathname.includes("/short")) {
31+
setActiveStoryLength("단편");
32+
} else {
33+
setActiveStoryLength("전체");
34+
}
35+
}, [location.pathname, setActiveStoryLength]);
36+
1137
return (
1238
<ul className="flex list-none " style={{ padding: 0, margin: 0 }}>
1339
{storyLength.map((length) => {
1440
const isActive = activeStoryLength === length;
1541
return (
1642
<li
1743
key={length}
44+
onClick={() => {
45+
if (!isActive) {
46+
setActiveStoryLength(length);
47+
navigate(`${page}${pathMap[length]}`); // 원하는 경로로 수정하세요
48+
}
49+
}}
1850
className={`cursor-pointer z-10 px-[15px] py-[10px] text-[20px] leading-[28px] font-medium tracking-[-0.4px] font-['Pretendard'] ${
1951
isActive
2052
? "border-b-2 border-[#6A39C0] rounded-[1px] "
2153
: "text-black"
2254
}`}
23-
onClick={() => setActiveStoryLength(length)}
2455
>
2556
{length}
2657
</li>

0 commit comments

Comments
 (0)