diff --git a/src/app/globals.css b/src/app/globals.css index 078e671d..b44e0a6e 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -133,38 +133,3 @@ div.swiper-scrollbar-drag { div.swiper-scrollbar { background: var(--color-gray-300); } - -/* 스켈레톤 애니메이션 */ - -@-webkit-keyframes skeleton-gradient { - 0% { - background-color: rgba(165, 165, 165, 0.1); - } - - 50% { - background-color: rgba(165, 165, 165, 0.3); - } - - 100% { - background-color: rgba(165, 165, 165, 0.1); - } -} - -@keyframes skeleton-gradient { - 0% { - background-color: rgba(165, 165, 165, 0.1); - } - - 50% { - background-color: rgba(165, 165, 165, 0.3); - } - - 100% { - background-color: rgba(165, 165, 165, 0.1); - } -} - -.skeleton-gradient { - -webkit-animation: skeleton-gradient 1.8s infinite ease-in-out; - animation: skeleton-gradient 1.8s infinite ease-in-out; -} diff --git a/src/app/myprofile/_components/my-profile/my-profile.tsx b/src/app/myprofile/_components/my-profile/my-profile.tsx index 3cafe086..b8893482 100644 --- a/src/app/myprofile/_components/my-profile/my-profile.tsx +++ b/src/app/myprofile/_components/my-profile/my-profile.tsx @@ -13,8 +13,12 @@ import { useInfiniteScroll } from "@/hooks/use-infinite-scroll"; import getUserReview from "@/api/my-profile/get-user-review"; import getUserWines from "@/api/user/get-user-wines"; import { EmptyState } from "@/components"; -import Loader from "@/components/loader/loader"; -import CardSkeleton from "@/components/card/card-skeleton"; + +const WINE_ITEM_CONTAINER = cn( + "grid w-full gap-y-[16px] pt-[24px]", + "tablet:grid-cols-2 tablet:gap-x-[16px] tablet:gap-y-[32px]", + "pc:grid-cols-3 pc:gap-x-[15px] pc:gap-y-[40px] pc:pt-[40px]" +); interface MyProfileProps { userInfo: User; @@ -61,80 +65,80 @@ const MyProfile = ({ userInfo }: MyProfileProps) => { if (!userInfo) return redirect("/login"); return ( -
-
- -
- {tab === "review" && ( - <> - {reviewIsLoading ? ( - - ) : userReview?.length === 0 || reviewIsError ? ( - - ) : ( - (userReview as ReviewItemType[])?.map((review) => ( - - )) - )} -
- - )} + <> +
+
+ +
+ {tab === "review" && ( + <> + {reviewIsLoading && ( + <> + {Array.from({ length: 4 }).map((_, i) => ( + + ))} + + )} + {userReview?.length === 0 || reviewIsError ? ( + + ) : ( + (userReview as ReviewItemType[])?.map((review) => ( + + )) + )} + {isFetchingNextPage && ( +
+ {Array.from({ length: 4 }).map((_, i) => ( + + ))} +
+ )} + + )} - {tab === "registered" && ( - <> - {wineIsLoading ? ( - - ) : userWinesTotalCount === 0 || wineIsError ? ( - - ) : ( - <> -
+ {tab === "registered" && ( + <> + {wineIsLoading && ( +
+ {Array.from({ length: 6 }).map((_, i) => ( + + ))} +
+ )} + {userWinesTotalCount === 0 || wineIsError ? ( + + ) : ( +
{(userWines as WineType[])?.map((wine) => ( ))}
-
- {wineIsLoading === false && isFetchingNextPage && ( -
- {Array.from({ length: 6 }).map((_, i) => ( - - ))} -
- )} - - )} - - )} + )} + {isFetchingNextPage && ( +
+ {Array.from({ length: 6 }).map((_, i) => ( + + ))} +
+ )} + + )} - {tab === "account" && } -
-
-
+ {tab === "account" && } +
+
+
+ ); }; diff --git a/src/app/myprofile/_components/review-item/review-item-skeleton.tsx b/src/app/myprofile/_components/review-item/review-item-skeleton.tsx new file mode 100644 index 00000000..fb2eb1be --- /dev/null +++ b/src/app/myprofile/_components/review-item/review-item-skeleton.tsx @@ -0,0 +1,65 @@ +import { cn } from "@/lib/utils"; +import Skeleton from "react-loading-skeleton"; +import "react-loading-skeleton/dist/skeleton.css"; +import Image from "next/image"; + +const ReviewItemSkeleton = () => { + return ( +
+
+
+
+ {/*별점 컴포넌트 */} + + {/* 와인 정보 */} +
+
+ +
+
+ + +
+
+
+
+ {/* FlavorIconList 컴포넌트 */} + +
+ {/* 리뷰 내용 */} +
+ + +
+
+ + {/* WineTaste 컴포넌트 */} +
+ {Array.from({ length: 4 }).map((_, i) => ( + + ))} +
+
+
+ ); +}; + +export default ReviewItemSkeleton; diff --git a/src/app/myprofile/_components/review-item/review-item.tsx b/src/app/myprofile/_components/review-item/review-item.tsx index 63cd8d11..4f4c9981 100644 --- a/src/app/myprofile/_components/review-item/review-item.tsx +++ b/src/app/myprofile/_components/review-item/review-item.tsx @@ -7,17 +7,31 @@ import { buildTasteData } from "@/components/wine-taste"; import { ReviewItemType } from "../../_types/review-type"; import { ReviewInfo, ReviewRating } from "."; import FlavorIconList from "@/components/flavor-icon-list/flavor-icon-list"; +import ReviewItemSkeleton from "./review-item-skeleton"; -const ReviewItem = ({ review }: { review: ReviewItemType }) => { +const ReviewItem = ({ + review, + skeleton = false, +}: { + review?: ReviewItemType; + skeleton?: boolean; +}) => { const [optionMenu, setOptionMenu] = useState(false); + if (skeleton) { + return ; + } + + if (!review) { + return null; + } + const tastes = buildTasteData({ lightBold: review.lightBold, smoothTannic: review.smoothTannic, drySweet: review.drySweet, softAcidic: review.softAcidic, }); - return (
{ )} >
-
+
{ }; if (skeleton) { - return ; + return ; } - if (!wine) return null; + if (!wine) { + return null; + } return ( <> diff --git a/src/components/card/card-skeleton.tsx b/src/components/card/card-skeleton.tsx index 13d4a537..16192cc3 100644 --- a/src/components/card/card-skeleton.tsx +++ b/src/components/card/card-skeleton.tsx @@ -3,13 +3,9 @@ import "react-loading-skeleton/dist/skeleton.css"; interface CardSkeletonProps { showReview?: boolean; - hasActionMenu?: boolean; } -const CardSkeleton = ({ - showReview = false, - hasActionMenu = false, -}: CardSkeletonProps) => { +const CardSkeleton = ({ showReview = false }: CardSkeletonProps) => { return (
{/* 이미지 영역 */} @@ -29,12 +25,6 @@ const CardSkeleton = ({ )} - {/* 액션메뉴 (우측 상단) */} - {hasActionMenu && ( -
- -
- )}
); diff --git a/src/components/utils/floating-actions.tsx b/src/components/floating-actions/floating-actions.tsx similarity index 100% rename from src/components/utils/floating-actions.tsx rename to src/components/floating-actions/floating-actions.tsx diff --git a/src/components/index.ts b/src/components/index.ts index 2156d14a..fc52566b 100644 --- a/src/components/index.ts +++ b/src/components/index.ts @@ -23,6 +23,6 @@ export { default as Card } from "./card/card"; export { default as LikeButton } from "./button/like-button"; export { default as Carousel } from "./carousel/carousel"; export { default as EmptyState } from "./empty-state/empty-state"; -export { default as FloatingActions } from "./utils/floating-actions"; +export { default as FloatingActions } from "./floating-actions/floating-actions"; export { default as ChatButton } from "./button/chat-button"; export { default as ChatBot } from "./chat-bot/chat-bot";