Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/components/common/LoadingSpinner.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ const LoadingSpinner = ({
const Container = styled.div<{ fullHeight: boolean }>`
display: flex;
flex-direction: column;
width: 100%;
max-width: 767px;
min-width: 320px;
margin: 0 auto;
justify-content: center;
align-items: center;
${({ fullHeight }) =>
Expand Down
9 changes: 6 additions & 3 deletions src/components/common/Post/PostBody.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ const Container = styled.div`
flex-direction: column;
width: 100%;
gap: 16px;
cursor: pointer;
`;

const PostContent = styled.div<{ hasImage: boolean }>`
Expand Down Expand Up @@ -82,9 +83,11 @@ const PostBody = ({
}, [contentBody]);

return (
<Container>
<BookInfoCard bookTitle={bookTitle} bookAuthor={bookAuthor} isbn={isbn} />
<PostContent hasImage={hasImage} onClick={() => handlePostClick(feedId)}>
<Container onClick={() => handlePostClick(feedId)}>
<div onClick={(e) => e.stopPropagation()}>
<BookInfoCard bookTitle={bookTitle} bookAuthor={bookAuthor} isbn={isbn} />
</div>
<PostContent hasImage={hasImage}>
<div className="content" ref={contentRef}>
{contentBody}
</div>
Expand Down
12 changes: 10 additions & 2 deletions src/components/common/Post/PostFooter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ interface PostFooterProps {
isLiked?: boolean;
isPublic?: boolean;
isDetail?: boolean;
onSaveToggle?: (feedId: number, newSaveState: boolean) => void;
}

const PostFooter = ({
Expand All @@ -63,6 +64,7 @@ const PostFooter = ({
isLiked = false,
isPublic = true,
isDetail = false,
onSaveToggle,
}: PostFooterProps) => {
const [liked, setLiked] = useState(isLiked);
const [likeCount, setLikeCount] = useState<number>(initialLikeCount);
Expand Down Expand Up @@ -90,9 +92,15 @@ const PostFooter = ({
const response = await postSaveFeed(feedId, !saved);

if (response.isSuccess) {
const newSaveState = response.data?.isSaved ?? !saved;
// 성공 시 상태 업데이트
setSaved(response.data?.isSaved ?? !saved);
console.log('저장 상태 변경 성공:', response.data?.isSaved);
setSaved(newSaveState);
console.log('저장 상태 변경 성공:', newSaveState);

// 부모 컴포넌트에 알림
if (onSaveToggle) {
onSaveToggle(feedId, newSaveState);
}
} else {
console.error('저장 상태 변경 실패:', response.message);
}
Expand Down
4 changes: 2 additions & 2 deletions src/components/feed/FeedPost.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,13 @@ const BorderBottom = styled.div`
background: #1c1c1c;
`;

const FeedPost = ({ showHeader, isLast, isMyFeed, ...postData }: FeedPostProps) => {
const FeedPost = ({ showHeader, isLast, isMyFeed, onSaveToggle, ...postData }: FeedPostProps) => {
return (
<>
<Container>
{showHeader && <PostHeader {...postData} />}
<PostBody {...postData} />
<PostFooter isMyFeed={!!isMyFeed} {...postData} />
<PostFooter isMyFeed={!!isMyFeed} onSaveToggle={onSaveToggle} {...postData} />
</Container>
{!isLast && <BorderBottom />}
</>
Expand Down
9 changes: 7 additions & 2 deletions src/components/feed/UserProfileItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,18 @@ const UserProfileItem = ({
userId,
isLast,
type,
isMyself,
}: UserProfileItemProps) => {
const navigate = useNavigate();
const [followed, setFollowed] = useState(isFollowing);
const { openPopup } = usePopupStore();

const handleProfileClick = () => {
navigate(`/otherfeed/${userId}`);
if (isMyself) {
navigate(`/myfeed/${userId}`);
} else {
navigate(`/otherfeed/${userId}`);
}
};

const toggleFollow = async (e: React.MouseEvent) => {
Expand Down Expand Up @@ -62,7 +67,7 @@ const UserProfileItem = ({
</div>
</div>
</div>
{type === 'followlist' && (
{type === 'followlist' && !isMyself && (
<div className="followbutton" onClick={toggleFollow}>
{followed ? '띱 취소' : '띱 하기'}
</div>
Expand Down
9 changes: 6 additions & 3 deletions src/components/group/Modal.styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,14 @@ import styled from '@emotion/styled';
export const Overlay = styled.div<{ $whiteBg?: boolean }>`
display: flex;
justify-content: center;
align-items: flex-start;
align-items: center;
position: fixed;
top: 0;
left: 0;
width: 100vw;
left: 50%;
transform: translateX(-50%);
width: 100%;
max-width: 767px;
min-width: 320px;
height: 100vh;
Comment on lines +7 to 15
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Overlay가 전체 가로폭을 덮지 않아 배경 클릭/스크롤이 ‘옆으로’ 새는 문제가 발생할 수 있습니다.

현재 Overlay에 max/min-width와 가운데 정렬이 적용되어, 큰 화면에서 Overlay가 뷰포트 전체를 덮지 않습니다. 이 경우:

  • 좌우 여백 영역이 Overlay 밖으로 남아, 배경 클릭이 통과되거나 스크롤 락이 완전하지 않을 수 있습니다.
  • 일반적으로는 Overlay가 뷰포트 전체를 덮고, 안쪽 Modal만 max-width로 센터링하는 패턴이 안전합니다.

Overlay는 100vw/100vh로 전체를 덮고, Modal에만 max-width를 두는 형태로 되돌리는 것을 권장합니다.

다음 변경으로 문제를 해소할 수 있습니다.

   align-items: center;
   position: fixed;
   top: 0;
-  left: 50%;
-  transform: translateX(-50%);
-  width: 100%;
-  max-width: 767px;
-  min-width: 320px;
+  left: 0;
+  width: 100vw;
   height: 100vh;

참고:

  • 이미 Modal 자체에 width: 100%; max-width: 767px;가 있어, Overlay를 전체화면으로 유지해도 콘텐츠는 동일하게 가운데 정렬됩니다(Overlay의 flex 정렬에 의해 수평·수직 센터링).
🤖 Prompt for AI Agents
In src/components/group/Modal.styles.ts around lines 7 to 15, the Overlay
currently uses fixed positioning with left: 50%, translateX(-50%) and
width/max-width/min-width which prevents it from covering the full viewport;
change the Overlay to span the full viewport (e.g., width: 100vw; height: 100vh;
left: 0; top: 0) and remove the centering width constraints from the Overlay,
keeping max-width/width only on the inner Modal so the Overlay covers the entire
screen while the Modal remains centered and constrained.

background: ${({ $whiteBg }) => ($whiteBg ? 'white' : colors.black.main)};
z-index: 110;
Expand Down
5 changes: 4 additions & 1 deletion src/pages/feed/FollowerListPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@ const FollowerListPage = () => {
type={type as UserProfileType}
isFollowing={user.isFollowing}
isLast={index === userList.length - 1}
isMyself={user.isMyself}
/>
))}
</UserProfileList>
Expand All @@ -156,7 +157,9 @@ const Wrapper = styled.div`
const TotalBar = styled.div`
position: fixed;
top: 0;
width: 727px;
width: 94.8%;
max-width: 727px;
min-width: 320px;
padding: 76px 0px 4px 0px;
border-bottom: 1px solid var(--color-darkgrey-dark);
background-color: var(--color-black-main);
Expand Down
47 changes: 39 additions & 8 deletions src/pages/mypage/SavePage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { colors, typography } from '@/styles/global/global';
import { getSavedBooksInMy, type SavedBookInMy } from '@/api/books/getSavedBooksInMy';
import { getSavedFeedsInMy, type SavedFeedInMy } from '@/api/feeds/getSavedFeedsInMy';
import { postSaveBook } from '@/api/books/postSaveBook';

import LoadingSpinner from '@/components/common/LoadingSpinner';

const tabs = ['피드', '책'];
Expand Down Expand Up @@ -38,7 +39,17 @@ const SavePage = () => {
navigate('/mypage');
};

// 저장된 피드 로드
// 저장된 책 목록 로드 함수
const loadSavedBooks = useCallback(async () => {
try {
const response = await getSavedBooksInMy();
setSavedBooks(response.data.bookList);
} catch (error) {
console.error('저장된 책 목록 로드 실패:', error);
}
}, []);

// 저장된 피드 목록 로드 함수
const loadSavedFeeds = useCallback(async (cursor: string | null = null) => {
try {
setFeedLoading(true);
Expand All @@ -61,7 +72,7 @@ const SavePage = () => {
}
}, []);

// 페이지 진입 시 모든 데이터 로드
// 페이지 진입 시 모든 데이터 로드 (한 번만 실행)
useEffect(() => {
const loadAllData = async () => {
try {
Expand All @@ -88,7 +99,7 @@ const SavePage = () => {
};

loadAllData();
}, []); // 한 번만 실행
}, []); // 빈 의존성 배열로 변경

// Intersection Observer 설정 (피드)
useEffect(() => {
Expand Down Expand Up @@ -122,17 +133,35 @@ const SavePage = () => {
const newSaveState = !currentBook.isSaved;
await postSaveBook(isbn, newSaveState);

// 로컬 상태 업데이트
setSavedBooks(prev =>
prev.map(book => (book.isbn === isbn ? { ...book, isSaved: newSaveState } : book)),
);
// 저장 취소인 경우 저장된 책 목록을 다시 불러옴
if (!newSaveState) {
await loadSavedBooks();
} else {
// 저장인 경우 로컬 상태만 업데이트
setSavedBooks(prev =>
prev.map(book => (book.isbn === isbn ? { ...book, isSaved: newSaveState } : book)),
);
}

console.log('저장 토글:', isbn, newSaveState);
} catch (error) {
console.error('저장 토글 실패:', error);
}
};

// 피드 저장 토글 처리
const handleFeedSaveToggle = async (feedId: number, newSaveState: boolean) => {
try {
if (!newSaveState) {
// 저장 취소인 경우 리스트에서 제거
setSavedFeeds(prev => prev.filter(feed => feed.feedId !== feedId));
console.log('피드 저장 취소 완료:', feedId);
}
} catch (error) {
console.error('피드 저장 상태 변경 실패:', error);
}
};

return (
<Wrapper>
<TitleHeader
Expand All @@ -153,6 +182,7 @@ const SavePage = () => {
showHeader={true}
isMyFeed={false}
isLast={index === savedFeeds.length - 1}
onSaveToggle={handleFeedSaveToggle}
{...feed}
/>
))}
Expand Down Expand Up @@ -258,8 +288,9 @@ const EmptyState = styled.div`
const BookList = styled.div`
display: flex;
flex-direction: column;
width: 100%;
min-width: 320px;
max-width: 540px;
max-width: 767px;
padding-top: 32px;
margin: 0 auto;
width: 100%;
Expand Down
2 changes: 1 addition & 1 deletion src/pages/searchBook/SearchBook.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,7 @@ const SearchBook = () => {

if (isLoading || error || !bookDetail) {
if (isLoading) {
return <LoadingSpinner fullHeight={true} />;
return <LoadingSpinner fullHeight={true} size="large" message="책 정보 불러오는 중..." />;
}
return (
<Wrapper>
Expand Down
1 change: 1 addition & 0 deletions src/types/follow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@ export interface FollowData {
aliasColor?: string;
followerCount?: number;
isFollowing?: boolean;
isMyself?: boolean;
}
1 change: 1 addition & 0 deletions src/types/post.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ export interface FeedPostProps extends PostData {
isMyFeed?: boolean;
isTotalFeed?: boolean;
isLast?: boolean;
onSaveToggle?: (feedId: number, newSaveState: boolean) => void;
}

export type PostBodyProps = Pick<
Expand Down
1 change: 1 addition & 0 deletions src/types/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,5 @@ export interface UserProfileItemProps {
userId: number;
isLast?: boolean;
type?: UserProfileType;
isMyself?: boolean;
}