Skip to content

Commit 987457b

Browse files
authored
Merge pull request #36 from happbob/feat/OODD-52
Feat: 즐겨찾기 탭 api 연결 추가
2 parents 6a321a7 + 544749a commit 987457b

10 files changed

Lines changed: 197 additions & 40 deletions

File tree

src/pages/Home/Favorites/Feed/index.tsx

Lines changed: 68 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React from 'react';
1+
import React, { useState } from 'react';
22
import { StyledText } from '../../../../components/Text/StyledText';
33
import theme from '../../../../styles/theme';
44
import { Btn, CheckedBtn, FeedImgBox, FeedProfileImgWrapper, FeedTop, FeedWrapper, Info, Reaction } from './styles';
@@ -8,24 +8,62 @@ import checkedStarBtn from '../../../../assets/Home/clicked_star.svg';
88
import heartBtn from '../../../../assets/Home/button_heart.svg';
99
import checkedHeartBtn from '../../../../assets/Home/clicked_heart.svg';
1010
import { useNavigate } from 'react-router-dom';
11+
import request, { BaseResponse } from '../../../../apis/core';
1112

1213
interface Props {
1314
feed: FeedProps;
1415
}
1516

1617
const Feed: React.FC<Props> = ({ feed }) => {
18+
const [hasLiked, setHasLiked] = useState(feed.hasLiked);
19+
const [hasInterested, setHasInterested] = useState(feed.hasInterested);
1720
const nav = useNavigate();
21+
22+
const handleProfileClick = () => {
23+
nav(`/users/${feed.userId}`);
24+
};
25+
1826
const handleFeedClick = () => {
1927
nav(`/post/${feed.postId}`); // 게시물 ID를 포함한 경로로 이동
2028
};
2129

30+
const handleLikeClick = async () => {
31+
try {
32+
const response = await request.post<BaseResponse>(`/user-relationships`, {
33+
targetId: feed.userId,
34+
});
35+
if (response.isSuccess) {
36+
setHasLiked((prev) => !prev);
37+
} else {
38+
console.error('Failed to like the post');
39+
}
40+
} catch (error) {
41+
console.error('Error liking the post:', error);
42+
}
43+
};
44+
45+
const handleInterestedClick = async () => {
46+
try {
47+
const response = await request.patch<BaseResponse>(`/user-interests`, {
48+
friendId: feed.userId, // feed.userId를 friendId로 보냄
49+
});
50+
if (response.isSuccess) {
51+
setHasInterested((prev) => !prev);
52+
} else {
53+
console.error('Failed to update interest status');
54+
}
55+
} catch (error) {
56+
console.error('Error updating interested status:', error);
57+
}
58+
};
59+
2260
return (
23-
<FeedWrapper onClick={handleFeedClick}>
61+
<FeedWrapper>
2462
<FeedImgBox>
25-
<img src={feed.feedImgUrl} />
63+
<img src={feed.feedImgUrl} onClick={handleFeedClick} />
2664
<FeedTop>
2765
<Info>
28-
<FeedProfileImgWrapper>
66+
<FeedProfileImgWrapper onClick={handleProfileClick}>
2967
<img src={feed.profileUrl} alt="profile" />
3068
</FeedProfileImgWrapper>
3169
<StyledText $textTheme={{ style: 'body1-medium', lineHeight: 1.2 }} color={theme.colors.white}>
@@ -34,21 +72,41 @@ const Feed: React.FC<Props> = ({ feed }) => {
3472
</Info>
3573
</FeedTop>
3674
<Reaction>
37-
{feed.hasLiked ? (
38-
<CheckedBtn>
75+
{hasLiked ? (
76+
<CheckedBtn
77+
onClick={(e) => {
78+
e.stopPropagation();
79+
handleLikeClick();
80+
}}
81+
>
3982
<img src={checkedHeartBtn} />
4083
</CheckedBtn>
4184
) : (
42-
<Btn>
85+
<Btn
86+
onClick={(e) => {
87+
e.stopPropagation();
88+
handleLikeClick();
89+
}}
90+
>
4391
<img src={heartBtn} />
4492
</Btn>
4593
)}
46-
{feed.hasInterested ? (
47-
<CheckedBtn>
94+
{hasInterested ? (
95+
<CheckedBtn
96+
onClick={(e) => {
97+
e.stopPropagation();
98+
handleInterestedClick();
99+
}}
100+
>
48101
<img src={checkedStarBtn} />
49102
</CheckedBtn>
50103
) : (
51-
<Btn>
104+
<Btn
105+
onClick={(e) => {
106+
e.stopPropagation();
107+
handleInterestedClick();
108+
}}
109+
>
52110
<img src={starBtn} />
53111
</Btn>
54112
)}

src/pages/Home/Favorites/Feed/styles.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ export const Info = styled.div`
3535
display: flex;
3636
align-items: center;
3737
gap: 0.5rem;
38+
padding-right: 0.75rem;
3839
`;
3940

4041
export const FeedProfileImgWrapper = styled.div`

src/pages/Home/Favorites/User/index.tsx

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
import React from 'react';
2-
import { StyledText } from '../../../../components/Text/StyledText';
32
import theme from '../../../../styles/theme';
43
import { UserProps } from '../dto';
5-
import { UserImgBorder, UserImgWrapper, UserWrapper } from './styles';
4+
import { UserImgBorder, UserImgWrapper, UserWrapper, StyledTextClipped } from './styles';
65

76
interface Props {
87
user: UserProps;
@@ -18,9 +17,9 @@ const User: React.FC<Props> = ({ user, isSelected, onClick }) => {
1817
<img src={user.userImgUrl} alt="user" />
1918
</UserImgWrapper>
2019
</UserImgBorder>
21-
<StyledText $textTheme={{ style: 'body5-light', lineHeight: 1 }} color={theme.colors.black}>
20+
<StyledTextClipped $textTheme={{ style: 'body5-light', lineHeight: 1 }} color={theme.colors.black}>
2221
{user.userName}
23-
</StyledText>
22+
</StyledTextClipped>
2423
</UserWrapper>
2524
);
2625
};

src/pages/Home/Favorites/User/styles.tsx

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,3 +39,13 @@ export const UserImgWrapper = styled.div`
3939
object-fit: cover;
4040
}
4141
`;
42+
43+
import { StyledText } from '../../../../components/Text/StyledText';
44+
export const StyledTextClipped = styled(StyledText)`
45+
overflow: hidden;
46+
white-space: nowrap;
47+
text-overflow: ellipsis;
48+
text-align: center;
49+
width: 5rem;
50+
display: inline-block; /* 텍스트 클리핑을 적용하기 위해 inline-block으로 설정 */
51+
`;

src/pages/Home/Favorites/dto.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
export interface FavoritesProps {
2+
onTabSelect?: () => void;
3+
}
4+
15
export interface UserProps {
26
userId: number;
37
userImgUrl: string;
@@ -6,6 +10,7 @@ export interface UserProps {
610

711
export interface FeedProps {
812
postId: number;
13+
userId: number;
914
profileUrl: string;
1015
userName: string;
1116
feedImgUrl: string;

src/pages/Home/Favorites/index.tsx

Lines changed: 74 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,69 @@
11
import React, { useState, useEffect } from 'react';
22
import { StyledText } from '../../../components/Text/StyledText';
33
import theme from '../../../styles/theme';
4-
import { FavoritesContainer, FavoritesMent, FeedContainer, UserContainer, UserRow } from './styles';
4+
import {
5+
FavoritesContainer,
6+
FavoritesMent,
7+
FeedContainer,
8+
UserContainer,
9+
UserRow,
10+
NoFavoriteContainer,
11+
} from './styles';
512
import Feed from './Feed';
6-
import { FeedProps, UserProps, UserInterestsResponse, UserPostsResponse } from './dto';
13+
import { FavoritesProps, FeedProps, UserProps, UserInterestsResponse, UserPostsResponse } from './dto';
714
import User from './User';
815
import Loading from '../../../components/Loading';
9-
import request from '../../../apis/core';
16+
import request, { BaseResponse } from '../../../apis/core';
1017

11-
const Favorites: React.FC = () => {
12-
const [selectedUser, setSelectedUser] = useState<number | null>(null); // 초기값을 null로 설정
18+
const Favorites: React.FC<FavoritesProps> = () => {
19+
const [selectedUser, setSelectedUser] = useState<number | null>(null);
1320
const [users, setUsers] = useState<UserProps[]>([]);
1421
const [feeds, setFeeds] = useState<FeedProps[]>([]);
1522
const [isUserLoading, setIsUserLoading] = useState(false);
1623
const [isFeedLoading, setIsFeedLoading] = useState(false);
1724

18-
// 관심 친구 목록을 서버에서 가져오는 함수
19-
const fetchUserInterests = async () => {
25+
// 즐겨찾기 친구 목록을 서버에서 가져오는 함수
26+
const fetchFavoriteUsers = async () => {
2027
setIsUserLoading(true);
2128
try {
22-
const response: UserInterestsResponse = await request.get('/user-interests');
23-
if (response.isSuccess) {
24-
const userData = response.result.map((user: any) => ({
29+
// 매칭 요청한 친구 목록 요청
30+
const requestedResponse: BaseResponse = await request.get('/user-relationships/requested');
31+
let requestedUserData: UserProps[] = [];
32+
33+
if (requestedResponse.isSuccess) {
34+
requestedUserData = requestedResponse.result.map((relationship: any) => {
35+
const target = relationship.target;
36+
return {
37+
userId: target.id,
38+
userImgUrl: target.profilePictureUrl,
39+
userName: target.nickname || target.name,
40+
};
41+
});
42+
} else {
43+
console.error('Failed to fetch requested users');
44+
}
45+
46+
// 관심 친구 목록 요청
47+
const interestsResponse: UserInterestsResponse = await request.get('/user-interests');
48+
let interestedUserData: UserProps[] = [];
49+
50+
if (interestsResponse.isSuccess) {
51+
interestedUserData = interestsResponse.result.map((user: any) => ({
2552
userId: user.friendId,
2653
userImgUrl: user.profilePictureUrl,
27-
userName: user.nickname,
54+
userName: user.nickname || user.name,
2855
}));
29-
setUsers(userData);
3056
} else {
31-
console.error('Failed to fetch user interests');
57+
console.error('Failed to fetch interested users');
3258
}
59+
60+
// 두 데이터를 합쳐서 중복된 userId를 제거하고 users로 설정
61+
const combinedData = [...requestedUserData, ...interestedUserData];
62+
const uniqueUsers = Array.from(new Map(combinedData.map((user) => [user.userId, user])).values());
63+
64+
setUsers(uniqueUsers);
3365
} catch (error) {
34-
console.error('Error fetching user interests:', error);
66+
console.error('Error fetching users:', error);
3567
} finally {
3668
setIsUserLoading(false);
3769
}
@@ -45,6 +77,7 @@ const Favorites: React.FC = () => {
4577
if (response.isSuccess) {
4678
const feedData = response.result.posts.map((post: any) => ({
4779
postId: post.postId, // postId 추가
80+
userId: userId,
4881
profileUrl: users.find((user) => user.userId === userId)?.userImgUrl || '',
4982
userName: users.find((user) => user.userId === userId)?.userName || '',
5083
feedImgUrl: post.firstPhoto,
@@ -62,11 +95,18 @@ const Favorites: React.FC = () => {
6295
}
6396
};
6497

65-
// 관심 친구 목록을 가져오는 effect
98+
// 컴포넌트가 마운트되거나 탭이 선택될 때마다 fetchFavoriteUsers 호출
6699
useEffect(() => {
67-
fetchUserInterests();
100+
fetchFavoriteUsers();
68101
}, []);
69102

103+
// users가 업데이트될 때, 첫 번째 사용자를 selectedUser로 설정
104+
useEffect(() => {
105+
if (users.length > 0) {
106+
setSelectedUser(users[0].userId);
107+
}
108+
}, [users]);
109+
70110
// 특정 친구를 클릭했을 때 해당 유저의 게시물을 가져오는 effect
71111
useEffect(() => {
72112
if (selectedUser !== null) {
@@ -89,7 +129,7 @@ const Favorites: React.FC = () => {
89129
<UserContainer>
90130
{isUserLoading ? (
91131
<Loading />
92-
) : (
132+
) : users.length !== 0 ? (
93133
<UserRow>
94134
{users.map((user) => (
95135
<User
@@ -100,11 +140,25 @@ const Favorites: React.FC = () => {
100140
/>
101141
))}
102142
</UserRow>
143+
) : (
144+
<NoFavoriteContainer>
145+
<StyledText $textTheme={{ style: 'heading2-light', lineHeight: 2 }} color={theme.colors.black}>
146+
즐겨찾기 친구가 없습니다
147+
</StyledText>
148+
<StyledText $textTheme={{ style: 'heading2-light', lineHeight: 2 }} color={theme.colors.black}>
149+
친구 신청 또는
150+
</StyledText>
151+
<StyledText $textTheme={{ style: 'heading2-light', lineHeight: 2 }} color={theme.colors.black}>
152+
관심 친구 등록을 해보세요 !
153+
</StyledText>
154+
</NoFavoriteContainer>
103155
)}
104156
</UserContainer>
105-
<FeedContainer>
106-
{isFeedLoading ? <Loading /> : feeds.map((feed, index) => <Feed key={index} feed={feed} />)}
107-
</FeedContainer>
157+
{!isUserLoading && (
158+
<FeedContainer>
159+
{isFeedLoading ? <Loading /> : feeds.map((feed, index) => <Feed key={index} feed={feed} />)}
160+
</FeedContainer>
161+
)}
108162
</FavoritesContainer>
109163
);
110164
};

src/pages/Home/Favorites/styles.tsx

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,15 @@ export const UserContainer = styled.div`
2222
display: flex;
2323
flex-direction: column;
2424
width: 100%;
25+
height: 6.1875rem;
2526
gap: 0.5rem;
2627
margin-left: 1.25rem;
2728
padding-right: 1.25rem;
2829
`;
2930

3031
export const UserRow = styled.div`
3132
display: flex;
33+
width: 100%;
3234
gap: 0.5rem;
3335
overflow-x: auto;
3436
white-space: nowrap;
@@ -51,3 +53,13 @@ export const FeedContainer = styled.div`
5153
grid-template-columns: repeat(2, 1fr);
5254
grid-gap: 0.25rem;
5355
`;
56+
57+
//
58+
export const NoFavoriteContainer = styled.div`
59+
width: 100%;
60+
height: 20rem;
61+
display: flex;
62+
flex-direction: column;
63+
align-items: center;
64+
justify-content: center;
65+
`;

0 commit comments

Comments
 (0)