Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
5f7dc51
feat: MyFeedPage 로딩 시 Skeleton UI 적용 => what
ljh130334 Feb 19, 2026
167e793
feat: 내 피드/다른 피드 스켈레톤 UI 추가 및 프로필 버그 수정
ljh130334 Feb 19, 2026
707b560
refactor: 스켈레톤 폴더 구조 개선 및 재사용 컴포넌트 추출 => what
ljh130334 Feb 20, 2026
1c33e62
feat: FeedDetailPage 스켈레톤 UI 적용 => what
ljh130334 Feb 20, 2026
5a29f72
feat: SavePage 탭별 스켈레톤 UI 적용 => what
ljh130334 Feb 20, 2026
c6d54f8
feat: Mypage 프로필 영역 스켈레톤 UI 적용 => what
ljh130334 Feb 20, 2026
70d7776
feat: TodayWords 메시지 목록 스켈레톤 UI 적용 => what
ljh130334 Feb 20, 2026
75e8a03
feat: GroupDetail 선택적 스켈레톤 UI 적용 => what
ljh130334 Feb 20, 2026
d806ae5
feat: ParticipatedGroupDetail 스켈레톤 UI 적용 => what
ljh130334 Feb 20, 2026
2a8b21e
feat: Memory 페이지 스켈레톤 UI 적용 => what
ljh130334 Feb 22, 2026
be4687a
feat: Memory/SearchBook 페이지 스켈레톤 UI 적용 => what
ljh130334 Feb 22, 2026
f88bfc2
feat: Memory/Search/SearchBook 스켈레톤 UI 적용 및 로딩 최적화 => what
ljh130334 Feb 22, 2026
21da851
feat: 검색 결과, 최근 검색어, 인기도서 스켈레톤 UI 추가
ljh130334 Feb 22, 2026
6123bfa
feat: GroupSearch 로딩 시 Skeleton UI 적용 => what
ljh130334 Feb 22, 2026
c60a1fa
feat: GroupMembers 로딩 시 Skeleton UI 적용 => what
ljh130334 Feb 22, 2026
a9fc219
feat: FollowerListPage 로딩 시 Skeleton UI 적용 => what
ljh130334 Feb 22, 2026
3df023d
fix: Skeleton.Text style prop 타입 오류 수정
ljh130334 Feb 24, 2026
62f38b2
fix: MyFeedPage 프로필 API 불일치 수정 => what
ljh130334 Feb 24, 2026
39e30f5
fix: ParticipatedGroupDetail 에러 화면에 뒤로가기 버튼 추가 => what
ljh130334 Feb 24, 2026
cc23912
fix: Memory 로딩 중 탭/필터 클릭으로 인한 Race Condition 방지 => what
ljh130334 Feb 24, 2026
9fad3e5
fix: 미사용 MenuItemSkeleton 제거 및 프로필 이미지 alt 속성 추가 => what
ljh130334 Feb 24, 2026
3e1e159
refactor: GroupDetail BottomButton 상태 로직 가독성 개선
ljh130334 Feb 24, 2026
4883092
refactor: JSX 삼항 조건(A) → early return 패턴(B) 으로 전환 => what
ljh130334 Feb 24, 2026
2b4ab5e
fix: GroupDetail 에러 상태에 뒤로가기 버튼 누락 수정 => what
ljh130334 Feb 24, 2026
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
3 changes: 2 additions & 1 deletion src/components/feed/MyFeed.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import type { FeedListProps } from '../../types/post';
import TotalBar from './TotalBar';
import { getMyProfile } from '@/api/feeds/getMyProfile';
import type { MyProfileData } from '@/types/profile';
import { OtherFeedSkeleton } from '@/shared/ui/Skeleton';
import { Container, EmptyState } from './MyFeed.styled';

const MyFeed = ({ showHeader, posts = [], isLast = false }: FeedListProps) => {
Expand All @@ -30,7 +31,7 @@ const MyFeed = ({ showHeader, posts = [], isLast = false }: FeedListProps) => {
}, []);

if (loading || !profileData) {
return <></>;
return <OtherFeedSkeleton showFollowButton={false} paddingTop={136} />;
}

return (
Expand Down
16 changes: 11 additions & 5 deletions src/components/search/MostSearchedBooks.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ import {
EmptyMessage,
MainText,
SubText,
LoadingMessage,
} from './MostSearchedBooks.styled';
import { MostSearchedBooksSkeleton } from '@/shared/ui/Skeleton';

export default function MostSearchedBooks() {
const [books, setBooks] = useState<MostSearchedBook[]>([]);
Expand All @@ -27,7 +27,11 @@ export default function MostSearchedBooks() {
const fetchMostSearchedBooks = async () => {
try {
setIsLoading(true);
const response = await getMostSearchedBooks();
const minLoadingTime = new Promise(resolve => setTimeout(resolve, 500));
const [response] = await Promise.all([
getMostSearchedBooks(),
minLoadingTime,
]);

if (response.isSuccess) {
setBooks(response.data.bookList);
Expand Down Expand Up @@ -55,15 +59,17 @@ export default function MostSearchedBooks() {
const day = String(now.getDate()).padStart(2, '0');
return `${month}.${day}. 기준`;
};
if (isLoading) {
return <MostSearchedBooksSkeleton />;
}

return (
<Container>
<Header>
<Title>가장 많이 검색된 책</Title>
<DateText>{getCurrentDate()}</DateText>
</Header>
{isLoading ? (
<LoadingMessage>로딩 중...</LoadingMessage>
) : error ? (
{error ? (
<EmptyMessage>
<MainText>데이터를 불러올 수 없어요.</MainText>
<SubText>{error}</SubText>
Expand Down
18 changes: 11 additions & 7 deletions src/pages/feed/Feed.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import TabBar from '../../components/feed/TabBar';
import MyFeed from '../../components/feed/MyFeed';
import TotalFeed from '../../components/feed/TotalFeed';
import MainHeader from '@/components/common/MainHeader';
import FeedPostSkeleton from '@/shared/ui/Skeleton/FeedPostSkeleton';
import { FeedPostSkeleton, OtherFeedSkeleton } from '@/shared/ui/Skeleton';
import writefab from '../../assets/common/writefab.svg';
import { useNavigate, useLocation } from 'react-router-dom';
import { getTotalFeeds } from '@/api/feeds/getTotalFeed';
Expand Down Expand Up @@ -143,7 +143,7 @@ const Feed = () => {
setTabLoading(true);

try {
const minLoadingTime = new Promise(resolve => setTimeout(resolve, 1000));
const minLoadingTime = new Promise(resolve => setTimeout(resolve, 500));

if (activeTab === '피드') {
await Promise.all([loadTotalFeeds(), minLoadingTime]);
Expand All @@ -168,11 +168,15 @@ const Feed = () => {
/>
<TabBar tabs={tabs} activeTab={activeTab} onTabClick={setActiveTab} />
{initialLoading || tabLoading ? (
<SkeletonWrapper>
{Array.from({ length: 3 }).map((_, index) => (
<FeedPostSkeleton key={index} />
))}
</SkeletonWrapper>
activeTab === '내 피드' ? (
<OtherFeedSkeleton showFollowButton={false} paddingTop={136} />
) : (
<SkeletonWrapper>
{Array.from({ length: 3 }).map((_, index) => (
<FeedPostSkeleton key={index} />
))}
</SkeletonWrapper>
)
) : (
<>
{activeTab === '피드' ? (
Expand Down
12 changes: 12 additions & 0 deletions src/pages/feed/FeedDetailPage.styled.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,15 @@ export const Wrapper = styled.div`
margin: 0 auto;
background-color: #121212;
`;

export const SkeletonWrapper = styled.div`
width: 100%;
padding-bottom: 80px;
`;

export const CommentSkeletonItem = styled.div`
display: flex;
gap: 12px;
padding: 16px 20px;
align-items: flex-start;
`;
26 changes: 24 additions & 2 deletions src/pages/feed/FeedDetailPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ import { useReplyActions } from '@/hooks/useReplyActions';
import { getFeedDetail, type FeedDetailData } from '@/api/feeds/getFeedDetail';
import { getComments, type CommentData } from '@/api/comments/getComments';
import { deleteFeedPost } from '@/api/feeds/deleteFeedPost';
import { Wrapper } from './FeedDetailPage.styled';
import Skeleton, { FeedPostSkeleton } from '@/shared/ui/Skeleton';
import { Wrapper, SkeletonWrapper, CommentSkeletonItem } from './FeedDetailPage.styled';
import { useReplyStore } from '@/stores/replyStore';

const FeedDetailPage = () => {
Expand Down Expand Up @@ -65,9 +66,11 @@ const FeedDetailPage = () => {
try {
setLoading(true);

const minLoadingTime = new Promise(resolve => setTimeout(resolve, 500));
const [feedResponse, commentsResponse] = await Promise.all([
getFeedDetail(Number(feedId)),
getComments(Number(feedId), { postType: 'FEED' }),
minLoadingTime,
]);

setFeedData(feedResponse.data);
Expand Down Expand Up @@ -170,7 +173,26 @@ const FeedDetailPage = () => {
};

if (loading) {
return <></>;
return (
<Wrapper>
<TitleHeader
leftIcon={<img src={leftArrow} alt="뒤로가기" />}
onLeftClick={handleBackClick}
/>
<SkeletonWrapper>
<FeedPostSkeleton />
{Array.from({ length: 5 }).map((_, index) => (
<CommentSkeletonItem key={index}>
<Skeleton.Circle width={36} />
<div style={{ flex: 1 }}>
<Skeleton.Text width={80} height={14} />
<Skeleton.Text lines={2} height={14} gap={6} />
</div>
</CommentSkeletonItem>
))}
</SkeletonWrapper>
</Wrapper>
);
}

if (error) {
Expand Down
21 changes: 18 additions & 3 deletions src/pages/feed/FollowerListPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { getFollowingList } from '@/api/users/getFollowingList';
import type { FollowData } from '@/types/follow';
import LoadingSpinner from '@/components/common/LoadingSpinner';
import { TotalBar, UserProfileList, Wrapper } from './FollowerListPage.syled';
import { UserProfileItemSkeleton } from '@/shared/ui/Skeleton';

const FollowerListPage = () => {
const navigate = useNavigate();
Expand Down Expand Up @@ -36,14 +37,24 @@ const FollowerListPage = () => {
setError(null);
let response;

const minLoadingTime = !cursor ? new Promise(resolve => setTimeout(resolve, 500)) : null;

if (type === 'followerlist') {
if (!userId) {
setError('사용자 ID가 없습니다.');
return;
}
response = await getFollowerList(userId, { size: 10, cursor: cursor || null });
const [data] = await Promise.all([
getFollowerList(userId, { size: 10, cursor: cursor || null }),
minLoadingTime,
]);
response = data;
} else {
response = await getFollowingList({ size: 10, cursor: cursor || null });
const [data] = await Promise.all([
getFollowingList({ size: 10, cursor: cursor || null }),
minLoadingTime,
]);
response = data;
}

let userData: FollowData[] = [];
Expand Down Expand Up @@ -121,7 +132,11 @@ const FollowerListPage = () => {
<TitleHeader leftIcon={<img src={leftArrow} />} onLeftClick={handleBackClick} title={title} />
<TotalBar>전체 {totalCount}</TotalBar>
{loading && userList.length === 0 ? (
<LoadingSpinner size="medium" fullHeight={true} />
<UserProfileList>
{Array.from({ length: 5 }).map((_, i) => (
<UserProfileItemSkeleton key={i} type={type as UserProfileType} />
))}
</UserProfileList>
) : (
<UserProfileList>
{userList.map((user, index) => (
Expand Down
22 changes: 19 additions & 3 deletions src/pages/feed/MyFeedPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import OtherFeed from '@/components/feed/OtherFeed';
import { getOtherFeed, type OtherFeedItem } from '@/api/feeds/getOtherFeed';
import { getOtherProfile } from '@/api/users/getOtherProfile';
import type { OtherProfileData } from '@/types/profile';
import { OtherFeedSkeleton } from '@/shared/ui/Skeleton';
import { Container } from './MyFeedPage.styled';

const MyFeedPage = () => {
Expand All @@ -33,10 +34,16 @@ const MyFeedPage = () => {
try {
setLoading(true);

const [feedResponse, profileResponse] = await Promise.all([
const minLoadingTime = new Promise(resolve => setTimeout(resolve, 500));
const [feedResponse, profileResponse] = (await Promise.all([
getOtherFeed(Number(userId)),
getOtherProfile(Number(userId)),
]);
minLoadingTime,
])) as [
Awaited<ReturnType<typeof getOtherFeed>>,
Awaited<ReturnType<typeof getOtherProfile>>,
void,
];

setFeedData(feedResponse.data.feedList);
setProfileData(profileResponse.data);
Expand All @@ -53,7 +60,16 @@ const MyFeedPage = () => {
}, [userId]);

if (loading) {
return <></>;
return (
<Container>
<TitleHeader
leftIcon={<img src={leftArrow} alt="뒤로가기" />}
onLeftClick={handleBackClick}
/>
<OtherFeedSkeleton showFollowButton={false} />
<NavBar src={writefab} path="/post/create" />
</Container>
);
}

if (error) {
Expand Down
22 changes: 19 additions & 3 deletions src/pages/feed/OtherFeedPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import OtherFeed from '@/components/feed/OtherFeed';
import { getOtherFeed, type OtherFeedItem } from '@/api/feeds/getOtherFeed';
import { getOtherProfile } from '@/api/users/getOtherProfile';
import type { OtherProfileData } from '@/types/profile';
import { OtherFeedSkeleton } from '@/shared/ui/Skeleton';
import { Container } from './MyFeedPage.styled';

const OtherFeedPage = () => {
Expand All @@ -33,10 +34,16 @@ const OtherFeedPage = () => {
try {
setLoading(true);

const [feedResponse, profileResponse] = await Promise.all([
const minLoadingTime = new Promise(resolve => setTimeout(resolve, 500));
const [feedResponse, profileResponse] = (await Promise.all([
getOtherFeed(Number(userId)),
getOtherProfile(Number(userId)),
]);
minLoadingTime,
])) as [
Awaited<ReturnType<typeof getOtherFeed>>,
Awaited<ReturnType<typeof getOtherProfile>>,
void,
];

setFeedData(feedResponse.data.feedList);
setProfileData(profileResponse.data);
Expand All @@ -53,7 +60,16 @@ const OtherFeedPage = () => {
}, [userId]);

if (loading) {
return <></>;
return (
<Container>
<TitleHeader
leftIcon={<img src={leftArrow} alt="뒤로가기" />}
onLeftClick={handleBackClick}
/>
<OtherFeedSkeleton />
<NavBar src={writefab} path="/post/create" />
</Container>
);
}

if (error) {
Expand Down
Loading