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
16 changes: 16 additions & 0 deletions src/assets/signup/guide1.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
16 changes: 16 additions & 0 deletions src/assets/signup/guide2.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
16 changes: 16 additions & 0 deletions src/assets/signup/guide3.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
16 changes: 16 additions & 0 deletions src/assets/signup/guide4.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
16 changes: 16 additions & 0 deletions src/assets/signup/guide5.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
16 changes: 16 additions & 0 deletions src/assets/signup/guide6.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion src/components/common/Modal/PopupContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ const Wrapper = styled.div`
min-width: 320px;
max-width: 767px;
margin: 0 auto;
background-color: rgba(18, 18, 18, 0.1);
background-color: rgba(18, 18, 18, 0.3);
backdrop-filter: blur(2.5px);
z-index: 1000;
`;
Expand Down
2 changes: 1 addition & 1 deletion src/components/common/Modal/ReplyModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ const ModalContainer = styled.div`
padding: 20px 12px;
border-radius: 16px;
border: 1px solid ${colors.grey[200]};
background-color: ${colors.black.main};
background-color: rgba(18, 18, 18, 0.3);
z-index: 1001;
`;

Expand Down
50 changes: 25 additions & 25 deletions src/components/feed/BookInfoCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,31 @@ import { useNavigate } from 'react-router-dom';
import styled from '@emotion/styled';
import rightArrow from '../../assets/common/rightArrow.svg';

interface BookInfoCardProps {
bookTitle: string;
bookAuthor: string;
isbn: string;
}

const BookInfoCard = ({ bookTitle, bookAuthor, isbn }: BookInfoCardProps) => {
const navigate = useNavigate();

const handleClick = () => {
navigate(`/search/book/${isbn}`);
};

return (
<BookContainer onClick={handleClick}>
<div className="left">{bookTitle}</div>
<div className="right">
<div className="name">{bookAuthor}</div>
<div className="author">저</div>
<img src={rightArrow} />
</div>
</BookContainer>
);
};

const BookContainer = styled.div`
display: flex;
height: 44px;
Expand Down Expand Up @@ -48,29 +73,4 @@ const BookContainer = styled.div`
}
`;

interface BookInfoCardProps {
bookTitle: string;
bookAuthor: string;
isbn: string;
}

const BookInfoCard = ({ bookTitle, bookAuthor, isbn }: BookInfoCardProps) => {
const navigate = useNavigate();

const handleClick = () => {
navigate(`/book/${isbn}`);
};

return (
<BookContainer onClick={handleClick}>
<div className="left">{bookTitle}</div>
<div className="right">
<div className="name">{bookAuthor}</div>
<div className="author">저</div>
<img src={rightArrow} />
</div>
</BookContainer>
);
};

export default BookInfoCard;
13 changes: 13 additions & 0 deletions src/components/feed/Profile.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { useState, useEffect } from 'react';
import styled from '@emotion/styled';
import MyFollower from './MyFollower';
import { postFollow } from '@/api/users/postFollow';
import { usePopupStore } from '@/stores/usePopupStore';

export interface ProfileProps {
showFollowButton?: boolean;
Expand All @@ -27,6 +28,7 @@ const Profile = ({
userId,
}: ProfileProps) => {
const [followed, setFollowed] = useState(isFollowing);
const { openPopup } = usePopupStore();

useEffect(() => {
setFollowed(isFollowing);
Expand All @@ -50,6 +52,17 @@ const Profile = ({
// API 응답으로 팔로우 상태 업데이트
setFollowed(response.data.isFollowing);
console.log(`${nickname} - ${response.data.isFollowing ? '띱 완료' : '띱 취소'}`);

// Snackbar 표시
const message = response.data.isFollowing
? `${nickname}님을 띱 했어요.`
: `${nickname}님을 띱 취소했어요.`;

openPopup('snackbar', {
message,
variant: 'top',
onClose: () => {}
});
} catch (error) {
console.error('팔로우/언팔로우 실패:', error);
// 에러 발생 시 상태 변경하지 않음
Expand Down
13 changes: 13 additions & 0 deletions src/components/feed/UserProfileItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import rightArrow from '../../assets/feed/rightArrow.svg';
import type { UserProfileItemProps } from '@/types/user';
import { colors, typography } from '@/styles/global/global';
import { postFollow } from '@/api/users/postFollow';
import { usePopupStore } from '@/stores/usePopupStore';

const UserProfileItem = ({
profileImgUrl,
Expand All @@ -19,6 +20,7 @@ const UserProfileItem = ({
}: UserProfileItemProps) => {
const navigate = useNavigate();
const [followed, setFollowed] = useState(isFollowing);
const { openPopup } = usePopupStore();

const handleProfileClick = () => {
navigate(`/otherfeed/${userId}`);
Expand All @@ -32,6 +34,17 @@ const UserProfileItem = ({
// API 응답으로 팔로우 상태 업데이트
setFollowed(response.data.isFollowing);
console.log(`${nickname} - ${response.data.isFollowing ? '띱 완료' : '띱 취소'}`);

// Snackbar 표시
const message = response.data.isFollowing
? `${nickname}님을 띱 했어요.`
: `${nickname}님을 띱 취소했어요.`;

openPopup('snackbar', {
message,
variant: 'top',
onClose: () => {}
});
} catch (error) {
console.error('팔로우/언팔로우 실패:', error);
}
Expand Down
31 changes: 28 additions & 3 deletions src/components/search/BookSearchResult.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,20 @@ import { useNavigate } from 'react-router-dom';
interface BookSearchResultProps {
type: 'searching' | 'searched';
searchedBookList: SearchedBook[];
hasMore?: boolean;
isLoading?: boolean;
lastBookElementCallback?: (node: HTMLDivElement | null) => void;
}

export function BookSearchResult({ type, searchedBookList }: BookSearchResultProps) {
export function BookSearchResult({
type,
searchedBookList,
hasMore = false,
isLoading = false,
lastBookElementCallback,
}: BookSearchResultProps) {
const navigate = useNavigate();

const isEmptySearchedBookList = () => {
if (searchedBookList.length === 0) return true;
else return false;
Expand All @@ -18,6 +28,7 @@ export function BookSearchResult({ type, searchedBookList }: BookSearchResultPro
const handleApplyBook = () => {
navigate('/search/applybook');
};

return (
<Wrapper>
<List>
Expand All @@ -30,8 +41,16 @@ export function BookSearchResult({ type, searchedBookList }: BookSearchResultPro
<RequestButton onClick={handleApplyBook}>책 신청하기</RequestButton>
</EmptyWrapper>
) : (
searchedBookList.map(book => (
<BookItem key={book.id} onClick={() => navigate(`/search/book/${book.isbn}`)}>
searchedBookList.map((book, index) => (
<BookItem
key={book.id}
onClick={() => navigate(`/search/book/${book.isbn}`)}
ref={
index === searchedBookList.length - 1 && lastBookElementCallback
? lastBookElementCallback
: undefined
}
>
Comment on lines +44 to +53
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

key 충돌 가능성: 페이지 append 시 동일 id 재사용 → key를 isbn으로 전환 권장

convertToSearchedBooks가 각 페이지 내 index 기반 id를 부여하고 있습니다. 무한 스크롤에서 페이지 단위로 결과를 append하면 id가 중복되어 React key 충돌이 발생할 수 있어 리스트 업데이트/관찰자(ref) 부착이 오작동할 수 있습니다. 안정적인 고유키인 isbn을 key로 사용해 주세요.

아래처럼 변경을 제안드립니다:

-            <BookItem
-              key={book.id}
+            <BookItem
+              key={book.isbn}
               onClick={() => navigate(`/search/book/${book.isbn}`)}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
searchedBookList.map((book, index) => (
<BookItem
key={book.id}
onClick={() => navigate(`/search/book/${book.isbn}`)}
ref={
index === searchedBookList.length - 1 && lastBookElementCallback
? lastBookElementCallback
: undefined
}
>
searchedBookList.map((book, index) => (
<BookItem
key={book.isbn}
onClick={() => navigate(`/search/book/${book.isbn}`)}
ref={
index === searchedBookList.length - 1 && lastBookElementCallback
? lastBookElementCallback
: undefined
}
>
🤖 Prompt for AI Agents
In src/components/search/BookSearchResult.tsx around lines 44-53, the current
key uses book.id (which convertToSearchedBooks assigns per-page/index and can
duplicate when appending pages); change the key prop to use the stable unique
identifier book.isbn instead (i.e. key={book.isbn}) so React sees unique items
across appended pages; if some items may lack isbn, fallback to a deterministic
unique string that includes page/index (e.g. `${book.isbn ??
`${book.id}-${index}-${page}`}`) to avoid collisions; keep the ref and onClick
logic unchanged.

<Cover src={book.coverUrl} alt={`${book.title} 커버`} />
<BookInfo>
<Title>{book.title}</Title>
Expand All @@ -42,6 +61,12 @@ export function BookSearchResult({ type, searchedBookList }: BookSearchResultPro
</BookItem>
))
)}

{/* 로딩 상태 표시 */}
{isLoading && searchedBookList.length > 0 && <></>}

{/* 더 이상 데이터가 없음을 표시 */}
{!hasMore && searchedBookList.length > 0 && <></>}
</List>
</Wrapper>
);
Expand Down
Loading