diff --git a/src/bookmarks/ui/Detail/SkeletonBookmarkDetail.tsx b/src/bookmarks/ui/Detail/SkeletonBookmarkDetail.tsx
new file mode 100644
index 00000000..39a153cc
--- /dev/null
+++ b/src/bookmarks/ui/Detail/SkeletonBookmarkDetail.tsx
@@ -0,0 +1,143 @@
+/* eslint-disable no-irregular-whitespace */
+import getRem from '@/utils/getRem';
+import Button from '@/common-ui/Button';
+import styled from '@emotion/styled';
+import { theme } from '@/styles/theme';
+
+import { skeletonAnimation1 } from '@/common-ui/utils/skeletonAnimations';
+import { ReactNode } from 'react';
+import Icon from '@/common-ui/assets/Icon';
+
+const SkeletonBookmarkDetail = () => {
+ return (
+ <>
+
+
+
+
+
+
+
+
+
+
+
+
+ }
+ content={}
+ />
+ }
+ content={}
+ />
+
+
+ >
+ );
+};
+
+export default SkeletonBookmarkDetail;
+
+const BookMarkInfo = ({
+ icon,
+ content,
+}: {
+ icon: ReactNode;
+ content: ReactNode;
+}) => {
+ return (
+
+ {icon}
+
+
+ {content}
+
+
+ );
+};
+
+const Container = styled.article`
+ padding: ${getRem(0, 20)};
+`;
+
+const BookMarkImage = styled.div`
+ width: 100%;
+ height: ${getRem(247)};
+ border-radius: ${getRem(0, 0, 32, 32)};
+ background-color: ${theme.colors.grey800};
+ ${skeletonAnimation1};
+`;
+
+const CategoryAndIconsWrapper = styled.div`
+ display: flex;
+ justify-content: space-between;
+ margin-top: ${getRem(15)};
+`;
+
+const CategoryButtonWrapper = styled.div`
+ width: ${getRem(154)};
+`;
+
+const CategoryButton = styled(Button)`
+ color: ${theme.colors.black};
+`;
+
+const IconWrapper = styled.div`
+ display: flex;
+`;
+
+const LikeAndMessageIconWrapper = styled.div`
+ display: flex;
+ align-items: center;
+ column-gap: ${getRem(12)};
+ width: 30%;
+ height: 1.7rem;
+ border-radius: ${getRem(5)};
+ align-self: center;
+ ${skeletonAnimation1};
+ background-color: ${theme.colors.grey800};
+`;
+
+const BookMarkInfoWrapper = styled.div`
+ padding: ${getRem(20)} 0px;
+`;
+const InfoRow = styled.div`
+ display: flex;
+ align-items: center;
+ column-gap: ${getRem(10)};
+ width: 100%;
+ overflow: hidden;
+`;
+
+const InfoTextWrapper = styled.div`
+ width: 100%;
+ display: flex;
+ height: 2rem;
+ align-items: center;
+ column-gap: 2rem;
+`;
+
+const SkeletonTitleText = styled.div`
+ width: 100%;
+ height: 1.8rem;
+ background-color: ${theme.colors.grey800};
+ margin-top: ${getRem(28)};
+ border-radius: ${getRem(5)};
+ ${skeletonAnimation1};
+`;
+
+const SkeletonSubText = styled.div`
+ width: 20%;
+ background-color: ${theme.colors.grey800};
+ ${skeletonAnimation1};
+ border-radius: ${getRem(5)};
+`;
+
+const SkeletonText = styled.div`
+ width: 50%;
+ height: 1.1rem;
+ background-color: ${theme.colors.grey800};
+ ${skeletonAnimation1};
+ border-radius: ${getRem(5)};
+`;
diff --git a/src/bookmarks/ui/SkeletonBookmarkUserInfo.tsx b/src/bookmarks/ui/SkeletonBookmarkUserInfo.tsx
new file mode 100644
index 00000000..9cab0561
--- /dev/null
+++ b/src/bookmarks/ui/SkeletonBookmarkUserInfo.tsx
@@ -0,0 +1,73 @@
+import SkeletonText from '@/common-ui/skeleton/SkeletonText';
+import { skeletonAnimation1 } from '@/common-ui/utils/skeletonAnimations';
+import { theme } from '@/styles/theme';
+import getRem from '@/utils/getRem';
+import styled from '@emotion/styled';
+
+interface SkeletonBookmarkUserInfoProps {
+ isFriendPage?: {
+ isFollowing: boolean;
+ friendId: number;
+ memberId: number;
+ isBlocked: boolean;
+ };
+}
+
+const SkeletonBookmarkUserInfo = ({
+ isFriendPage,
+}: SkeletonBookmarkUserInfoProps) => {
+ return (
+
+
+
+ {!!isFriendPage && (
+
+ )}
+ {!isFriendPage && }
+
+ {!!isFriendPage && (
+ <>
+
+ >
+ )}
+
+ );
+};
+
+export default SkeletonBookmarkUserInfo;
+
+const StyleWrapper = styled.div`
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+`;
+
+const UserBox = styled.div`
+ display: flex;
+ width: 4rem;
+ height: 4rem;
+ border-radius: 50%;
+ background-color: ${theme.colors.grey800};
+ justify-content: center;
+ align-items: center;
+ flex-shrink: 0;
+ ${skeletonAnimation1};
+`;
+
+const TextWrapper = styled.div`
+ display: flex;
+ align-items: center;
+ column-gap: 0.5rem;
+ width: 100%;
+`;
+
+const StyledButton = styled.div`
+ width: ${getRem(70)};
+ height: 1.4rem;
+ font-size: ${getRem(14)};
+ padding: ${getRem(4, 15)};
+ border-radius: ${getRem(5)};
+ background-color: ${theme.colors.grey800};
+ font-weight: bold;
+ ${skeletonAnimation1}
+`;
diff --git a/src/comment/api/Comment.ts b/src/comment/api/Comment.ts
index c4f6cdfe..f1ee44a5 100644
--- a/src/comment/api/Comment.ts
+++ b/src/comment/api/Comment.ts
@@ -45,6 +45,7 @@ export const useGETCommentListQuery = ({ userId }: GETCommentListRequest) => {
refetchOnWindowFocus: false,
retry: 0,
enabled: !!userId,
+ suspense: true,
});
};
diff --git a/src/comment/ui/bookmark/SkeletonCommentList.tsx b/src/comment/ui/bookmark/SkeletonCommentList.tsx
new file mode 100644
index 00000000..696f22e6
--- /dev/null
+++ b/src/comment/ui/bookmark/SkeletonCommentList.tsx
@@ -0,0 +1,78 @@
+/* eslint-disable @typescript-eslint/no-empty-function */
+import styled from '@emotion/styled';
+import { theme } from '@/styles/theme';
+import Icon from '@/common-ui/assets/Icon';
+import getRem from '@/utils/getRem';
+import SkeletonText from '@/common-ui/skeleton/SkeletonText';
+import { skeletonAnimation2 } from '@/common-ui/utils/skeletonAnimations';
+
+const SkeletonCommentList = () => {
+ return (
+
+ {[...Array(3)].map((_, index) => (
+
+ ))}
+
+ );
+};
+
+const CommentListWrapper = styled.div`
+ padding: 0 ${getRem(20)};
+`;
+
+const SkeletonCommentItem = () => {
+ return (
+
+
+
+
+
+
+ {/*
+ */}
+
+
+
+
+
+
+
+ );
+};
+
+export default SkeletonCommentList;
+
+const Container = styled.div`
+ display: grid;
+ flex-direction: column;
+ row-gap: 0.8rem;
+ padding: ${getRem(15, 20)};
+ border-radius: ${getRem(7)};
+ background-color: ${theme.colors.grey700};
+ ${skeletonAnimation2}
+ margin-bottom: 1rem;
+ :nth-last-of-type(1) {
+ margin-bottom: 5rem;
+ }
+`;
+
+const CommentHeader = styled.div`
+ display: flex;
+ justify-content: flex-end;
+ align-items: center;
+ height: 1.5rem;
+`;
+
+const NicknameTextAndIconWrapper = styled.div`
+ display: flex;
+ align-items: center;
+ column-gap: ${getRem(8)};
+ margin-right: auto; // 추가
+ width: 100%;
+`;
+
+const IconAndTextWrapper = styled.div`
+ display: flex;
+ align-items: center;
+ column-gap: ${getRem(8)};
+`;
diff --git a/src/comment/ui/comment-list/CommentItem.tsx b/src/comment/ui/comment-list/CommentItem.tsx
index f979fe44..efe79283 100644
--- a/src/comment/ui/comment-list/CommentItem.tsx
+++ b/src/comment/ui/comment-list/CommentItem.tsx
@@ -46,10 +46,10 @@ const CommentItem = ({
+ {}
{nickName}
- {}
{content}
diff --git a/src/comment/ui/comment-list/CommentList.tsx b/src/comment/ui/comment-list/CommentList.tsx
new file mode 100644
index 00000000..1882bacd
--- /dev/null
+++ b/src/comment/ui/comment-list/CommentList.tsx
@@ -0,0 +1,40 @@
+import { useGETCommentListQuery } from '@/comment/api/Comment';
+import BlankItem from '@/common-ui/BlankItem';
+import useAuthStore from '@/store/auth';
+import CommentItem from './CommentItem';
+import styled from '@emotion/styled';
+import getRem from '@/utils/getRem';
+
+const CommentList = () => {
+ const { memberId } = useAuthStore();
+ const { data: commentList } = useGETCommentListQuery({
+ userId: memberId,
+ });
+ return (
+
+ {!commentList?.length && }
+ {!!commentList &&
+ commentList.map((comment) => (
+
+ ))}
+
+ );
+};
+
+export default CommentList;
+
+const CommentListWrapper = styled.div`
+ > * + * {
+ margin-top: ${getRem(10)};
+ margin-bottom: ${getRem(10)};
+ }
+`;
diff --git a/src/comment/ui/comment-list/SkeletonCommentList.tsx b/src/comment/ui/comment-list/SkeletonCommentList.tsx
new file mode 100644
index 00000000..35e35fad
--- /dev/null
+++ b/src/comment/ui/comment-list/SkeletonCommentList.tsx
@@ -0,0 +1,78 @@
+import styled from '@emotion/styled';
+import getRem from '@/utils/getRem';
+import { theme } from '@/styles/theme';
+import SkeletonText from '@/common-ui/skeleton/SkeletonText';
+
+const SkeletonCommentList = () => {
+ return (
+
+ {Array.from({ length: 5 }).map((_, index) => (
+
+ ))}
+
+ );
+};
+
+export default SkeletonCommentList;
+
+const CommentListWrapper = styled.div`
+ > * + * {
+ margin-top: ${getRem(10)};
+ margin-bottom: ${getRem(10)};
+ }
+`;
+const CommentItem = () => {
+ return (
+ <>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ >
+ );
+};
+
+const Container = styled.div`
+ display: grid;
+ row-gap: ${getRem(10)};
+ width: 100%;
+ padding: ${getRem(15, 20)};
+ border-radius: ${getRem(7)};
+ background-color: ${theme.colors.grey850};
+`;
+
+const CommentHeader = styled.div`
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+`;
+
+const IconAndTitleWrapper = styled.div`
+ display: flex;
+ align-items: center;
+ column-gap: ${getRem(8)};
+ width: 100%;
+`;
+
+const IconAndNickNameWrapper = styled.div`
+ display: flex;
+ align-items: center;
+ column-gap: ${getRem(8)};
+`;
+
+const IconAndTimeAndCategoryWrapper = styled.div`
+ display: flex;
+ align-items: center;
+ column-gap: ${getRem(8)};
+`;
diff --git a/src/common-ui/skeleton/SkeletonText.tsx b/src/common-ui/skeleton/SkeletonText.tsx
new file mode 100644
index 00000000..d1491b01
--- /dev/null
+++ b/src/common-ui/skeleton/SkeletonText.tsx
@@ -0,0 +1,36 @@
+/* eslint-disable no-irregular-whitespace */
+import { theme } from '@/styles/theme';
+import { css } from '@emotion/react';
+import {
+ skeletonAnimation1,
+ skeletonAnimation2,
+} from '../utils/skeletonAnimations';
+
+interface SkeletonTextProps {
+ width?: number;
+ height?: number;
+ animationType?: 'normal' | 'reverse';
+}
+
+const SkeletonText = ({
+ width = 100,
+ height = 1,
+ animationType = 'normal',
+}: SkeletonTextProps) => {
+ return (
+
+
+
+ );
+};
+
+export default SkeletonText;
diff --git a/src/friend/api/friends.tsx b/src/friend/api/friends.tsx
index 98fcb924..3eba2c2a 100644
--- a/src/friend/api/friends.tsx
+++ b/src/friend/api/friends.tsx
@@ -72,6 +72,7 @@ export const useGETFollowingListQuery = (
}
return undefined;
},
+ suspense: true,
},
);
};
@@ -139,6 +140,7 @@ export const useGETFollowerListQuery = (
}
return undefined;
},
+ suspense: true,
},
);
};
diff --git a/src/friend/ui/FriendSkeletonItem.tsx b/src/friend/ui/FriendSkeletonItem.tsx
index 31e0b8a5..82d67f8d 100644
--- a/src/friend/ui/FriendSkeletonItem.tsx
+++ b/src/friend/ui/FriendSkeletonItem.tsx
@@ -1,29 +1,100 @@
import styled from '@emotion/styled';
import getRem from '@/utils/getRem';
-import {
- skeletonBackgroundStyle,
- skeletonContentStyle,
-} from '@/common-ui/utils/skeletonStyles';
+import { FriendType } from '@/store/friend';
+import Button from '@/common-ui/Button';
+import { css } from '@emotion/react';
+import { theme } from '@/styles/theme';
+import SkeletonText from '@/common-ui/skeleton/SkeletonText';
+import { skeletonAnimation1 } from '@/common-ui/utils/skeletonAnimations';
const FriendSkeletonItem = () => {
return (
-
-
-
+ <>
+
+
+ {Array.from({ length: 5 }).map((_, index) => (
+
+
+
+
+ ))}
+
+ >
);
};
export default FriendSkeletonItem;
+const Wrapper = styled.div`
+ padding: ${getRem(10)};
+ > * + * {
+ margin-top: ${getRem(10)};
+ }
+`;
+
const Container = styled.div`
- height: ${getRem(45)};
- padding: ${getRem(12, 20)};
- width: 100%;
- ${skeletonBackgroundStyle};
- margin-bottom: ${getRem(10)};
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ height: ${getRem(57)};
+ column-gap: ${getRem(10)};
+`;
+
+const SkeletonIcon = styled.div`
+ width: ${getRem(25)};
+ height: ${getRem(25)};
+ border-radius: 50%;
+ background-color: ${theme.colors.grey800};
+ ${skeletonAnimation1};
+`;
+
+interface FriendTypeSelectProps {
+ value: FriendType;
+
+ followerTotalCount: number;
+ followingTotalCount: number;
+}
+
+const FriendTypeSelect = ({
+ value,
+
+ followerTotalCount,
+ followingTotalCount,
+}: FriendTypeSelectProps) => {
+ return (
+
+
+ 팔로워 {followerTotalCount}
+
+
+ 팔로잉 {followingTotalCount}
+
+
+ );
+};
+
+const ButtonWrapper = styled.div`
+ display: flex;
`;
-const TitleItem = styled.div`
- height: ${getRem(16)};
- ${skeletonContentStyle};
+const FriendTypeButton = styled(Button)<{ active: boolean }>`
+ border-radius: 0;
+ ${(p) =>
+ p.active &&
+ css`
+ color: ${theme.colors.lightPrimary};
+ border-bottom: 1px solid ${theme.colors.lightPrimary};
+ `}
`;
diff --git a/src/pages/BookMarkDetailPage.tsx b/src/pages/BookMarkDetailPage.tsx
index c6fe2c11..a4cc3998 100644
--- a/src/pages/BookMarkDetailPage.tsx
+++ b/src/pages/BookMarkDetailPage.tsx
@@ -9,6 +9,9 @@ import TriggerBottomSheet from '@/common-ui/BottomSheet/TriggerBottomSheet';
import IconButton from '@/common/ui/IconButton';
import BSConfirmation from '@/common/ui/BSConfirmation';
import useHandleBookmarkDetailMore from '@/bookmarks/service/hooks/detail/useHandleBookmarkDetailMore';
+import SkeletonBookmarkDetail from '@/bookmarks/ui/Detail/SkeletonBookmarkDetail';
+import SkeletonWrapper from '@/common-ui/SkeletonWrapper';
+import SkeletonCommentList from '@/comment/ui/bookmark/SkeletonCommentList';
const BookMarkDetailPage = () => {
const {
@@ -56,7 +59,13 @@ const BookMarkDetailPage = () => {
/>
{/** 북마크 정보 영역 */}
-
+
+
+
+ }
+ >
{
/>
{/** 댓글 리스트 영역 */}
-
+
+
+
+ }
+ >
diff --git a/src/pages/CommentPage.tsx b/src/pages/CommentPage.tsx
index 5684e08e..eb76d4ce 100644
--- a/src/pages/CommentPage.tsx
+++ b/src/pages/CommentPage.tsx
@@ -1,38 +1,25 @@
import Header from '@/common-ui/Header/Header';
import styled from '@emotion/styled';
-import CommentItem from '@/comment/ui/comment-list/CommentItem';
import getRem from '@/utils/getRem';
-import { useGETCommentListQuery } from '@/comment/api/Comment';
-import useAuthStore from '@/store/auth';
-import BlankItem from '@/common-ui/BlankItem';
+import { Suspense } from 'react';
+import CommentList from '@/comment/ui/comment-list/CommentList';
+import SkeletonWrapper from '@/common-ui/SkeletonWrapper';
+import SkeletonCommentList from '@/comment/ui/comment-list/SkeletonCommentList';
const CommentPage = () => {
- const { memberId } = useAuthStore();
- const { data: commentList } = useGETCommentListQuery({
- userId: memberId,
- });
-
return (
<>
-
- {!commentList?.length && }
- {commentList &&
- commentList.length > 0 &&
- commentList.map((comment) => (
-
- ))}
-
+
+
+
+ }
+ >
+
+
>
);
@@ -43,10 +30,3 @@ export default CommentPage;
const Body = styled.div`
padding: ${getRem(10, 20)};
`;
-
-const CommentListWrapper = styled.div`
- > * + * {
- margin-top: ${getRem(10)};
- margin-bottom: ${getRem(10)};
- }
-`;
diff --git a/src/pages/FriendBookmarkPage.tsx b/src/pages/FriendBookmarkPage.tsx
index 8ec2a96d..9db9e15c 100644
--- a/src/pages/FriendBookmarkPage.tsx
+++ b/src/pages/FriendBookmarkPage.tsx
@@ -24,6 +24,7 @@ import SkeletonWrapper from '@/common-ui/SkeletonWrapper';
import BookmarkSkeletonItem from '@/bookmarks/ui/Main/BookmarkSkeletonItem';
import useFriendStore from '@/store/friend';
import useBookmarkStore from '@/store/bookmark';
+import SkeletonBookmarkUserInfo from '@/bookmarks/ui/SkeletonBookmarkUserInfo';
const FriendBookmarkPage = () => {
// FIRST RENDER
@@ -39,10 +40,11 @@ const FriendBookmarkPage = () => {
// SERVER
// 1. 친구 프로필 조회
- const { data: profileInfo } = useGETFriendProfileQuery({
- loginId: memberId,
- memberId: Number(friendId),
- });
+ const { data: profileInfo, isLoading: profileLoading } =
+ useGETFriendProfileQuery({
+ loginId: memberId,
+ memberId: Number(friendId),
+ });
// USER INTERACTION
// 뒤로가기
@@ -97,16 +99,29 @@ const FriendBookmarkPage = () => {
}
/>
-
+ {profileLoading ? (
+
+
+
+ ) : (
+
+ )}
{
const router = useNavigate();
@@ -21,9 +22,11 @@ const FriendPage = () => {
}
/>
(
-
- ))}
+ fallback={
+
+
+
+ }
>
diff --git a/src/styles/theme.ts b/src/styles/theme.ts
index 3332274d..4fa7a0e0 100644
--- a/src/styles/theme.ts
+++ b/src/styles/theme.ts
@@ -12,6 +12,7 @@ export const theme = {
grey600: '#757575',
grey700: '#616161',
grey800: '#424242',
+ grey850: '#303030',
grey900: '#212121',
darkBlack: '#161514',
darkGrey: '#2A2A2A',