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
20 changes: 12 additions & 8 deletions apps/web/src/apis/mentor/getMentorList.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { useInfiniteQuery, useQueryClient } from "@tanstack/react-query";
import type { AxiosError } from "axios";
import { useCallback } from "react";
import { type MentorCardDetail, type MentorListResponse, MentorQueryKeys, mentorApi } from "./api";

interface UseGetMentorListRequest {
Expand All @@ -24,14 +25,17 @@ const useGetMentorList = ({ region = "" }: UseGetMentorListRequest = {}) => {
export const usePrefetchMentorList = () => {
const queryClient = useQueryClient();

const prefetchMentorList = (region: string) => {
queryClient.prefetchInfiniteQuery({
queryKey: [MentorQueryKeys.mentorList, region],
queryFn: ({ pageParam = 0 }) => mentorApi.getMentorList(region, pageParam as number),
initialPageParam: 0,
staleTime: 1000 * 60 * 5,
});
};
const prefetchMentorList = useCallback(
(region: string) => {
queryClient.prefetchInfiniteQuery({
queryKey: [MentorQueryKeys.mentorList, region],
queryFn: ({ pageParam = 0 }) => mentorApi.getMentorList(region, pageParam as number),
initialPageParam: 0,
staleTime: 1000 * 60 * 5,
});
},
[queryClient],
);

return { prefetchMentorList };
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,19 @@ import TabSelector from "@/components/ui/TabSelector";
import { IconDirectionRight } from "@/public/svgs/mentor";
import { VerifyStatus } from "@/types/mentee";
import { MenteeTab } from "@/types/mentor";
import { MentorChatCardsSkeleton } from "../../../MentorPageSkeleton";

const MenteePageTabs = () => {
// api
const { data: mentoList = [] } = useGetChatRooms();
const { data: menteeWaitingMentoringList = [] } = useGetMenteeMentoringList(VerifyStatus.PENDING);
const { data: mentoList = [], isPending: isMentoListPending } = useGetChatRooms();
const { data: menteeWaitingMentoringList = [], isPending: isWaitingListPending } = useGetMenteeMentoringList(
VerifyStatus.PENDING,
);

// state
const [selectedTab, setSelectedTab] = useState<MenteeTab>(MenteeTab.MY_MENTOR);
const tabs = [MenteeTab.MY_MENTOR, MenteeTab.MY_APPLIED];
const isCurrentTabPending = selectedTab === MenteeTab.MY_MENTOR ? isMentoListPending : isWaitingListPending;

// 현재 탭에 따라 보여줄 데이터의 길이
const currentDataLength = selectedTab === MenteeTab.MY_MENTOR ? mentoList.length : menteeWaitingMentoringList.length;
Expand Down Expand Up @@ -51,13 +55,16 @@ const MenteePageTabs = () => {
)}
</div>

{currentDataLength === 0 && (
{isCurrentTabPending ? (
<MentorChatCardsSkeleton />
) : currentDataLength === 0 ? (
<EmptyMentorChatCards
message={selectedTab === MenteeTab.MY_MENTOR ? "진행중인 멘토링이 없어요!" : "대기중인 멘토링이 없어요!"}
/>
)}
) : null}

{selectedTab === MenteeTab.MY_MENTOR &&
{!isCurrentTabPending &&
selectedTab === MenteeTab.MY_MENTOR &&
mentoList.slice(0, 2).map((mentor) => (
<Link href={`mentor/chat/${mentor.id}`} key={mentor.id}>
<MentorChatCard
Expand All @@ -68,7 +75,8 @@ const MenteePageTabs = () => {
/>
</Link>
))}
{selectedTab === MenteeTab.MY_APPLIED &&
{!isCurrentTabPending &&
selectedTab === MenteeTab.MY_APPLIED &&
menteeWaitingMentoringList
.slice(0, 2)
.map((mentor) => (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import EmptySdwBCards from "@/components/ui/EmptySdwBCards";
import FloatingUpBtn from "@/components/ui/FloatingUpBtn";
import { FilterTab } from "@/types/mentor";
import useInfinityScroll from "@/utils/useInfinityScroll";
import { MentorCardListSkeleton } from "../../../MentorPageSkeleton";
import usePrefetchMentorFindTab from "./_hooks/usePrefetchMentorFindTab";
import useSelectedTab from "./_hooks/useSelectedTab";

Expand All @@ -17,6 +18,7 @@ const MentorFindSection = () => {
data: mentorList = [],
fetchNextPage,
hasNextPage,
isPending,
} = useGetMentorList({
region: selectedTab !== FilterTab.ALL ? selectedTab : "",
});
Expand Down Expand Up @@ -44,12 +46,14 @@ const MentorFindSection = () => {

{/* 멘토 리스트 */}
<div ref={listRef} className="space-y-4 pb-28">
{mentorList.length === 0 ? (
{isPending ? (
<MentorCardListSkeleton />
) : mentorList.length === 0 ? (
<EmptySdwBCards message="멘토가 없습니다. 필터를 변경해보세요." />
) : (
mentorList.map((mentor) => (
mentorList.map((mentor, index) => (
<MentorCard
observeRef={mentorList.length === mentorList.indexOf(mentor) + 1 ? lastElementRef : undefined}
observeRef={index === mentorList.length - 1 ? lastElementRef : undefined}
key={mentor.id}
mentor={mentor}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,17 @@ import { useGetMentoringList } from "@/apis/mentor";
import MentorExpandChatCard from "@/components/mentor/MentorExpandChatCard";
import EmptySdwBCards from "@/components/ui/EmptySdwBCards";
import useInfinityScroll from "@/utils/useInfinityScroll";
import { MentorApplicantListSkeleton } from "../../../../../MentorPageSkeleton";

const ApplicantListSection = () => {
const { data: mentoringApplicantList = [], fetchNextPage, hasNextPage } = useGetMentoringList({ size: 6 });
const { data: mentoringApplicantList = [], fetchNextPage, hasNextPage, isPending } = useGetMentoringList({ size: 6 });
const { lastElementRef } = useInfinityScroll({ fetchNextPage, hasNextPage });

return (
<>
{mentoringApplicantList.length === 0 ? (
{isPending ? (
<MentorApplicantListSkeleton />
) : mentoringApplicantList.length === 0 ? (
<EmptySdwBCards message={"나와 매칭된 멘토입니다"} />
) : (
mentoringApplicantList.map((mentor, index) => (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,21 @@

import { useGetMentorMyProfile } from "@/apis/mentor";
import MentorCard from "@/components/mentor/MentorCard";
import { MentorCardListSkeleton } from "../../../../../MentorPageSkeleton";

const MyMentorSection = () => {
const { data: myMentorProfile } = useGetMentorMyProfile();
const { data: myMentorProfile, isPending } = useGetMentorMyProfile();

if (isPending) {
return (
<>
<h2 className="text-gray-900 typo-sb-5 mt-5">나의 멘토 페이지</h2>
<div className="mt-[14px]">
<MentorCardListSkeleton count={1} />
</div>
</>
);
}

if (!myMentorProfile) {
return <div className="text-gray-500">멘토 프로필을 불러오는 중...</div>;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import TabSelector from "@/components/ui/TabSelector";
import { IconDirectionRight } from "@/public/svgs/mentor";

import { MentorTab } from "@/types/mentor";
import { MentorChatCardsSkeleton } from "../../../MentorPageSkeleton";
import ApplicantListSection from "./_ui/ApplicantListSection";
import MyMentorSection from "./_ui/MyMentorSection";

Expand All @@ -17,7 +18,7 @@ const MentorPage = () => {
const isMyMenteeTab = selectedTab === MentorTab.MY_MENTEE;
const tabs = [MentorTab.MY_MENTEE, MentorTab.APPLY_LIST];

const { data: myMenteeList = [] } = useGetChatRooms();
const { data: myMenteeList = [], isPending: isMyMenteeListPending } = useGetChatRooms();

return (
<>
Expand Down Expand Up @@ -46,7 +47,9 @@ const MentorPage = () => {
{isMyMenteeTab ? (
<>
{/* 나의 멘티 */}
{myMenteeList.length === 0 ? (
{isMyMenteeListPending ? (
<MentorChatCardsSkeleton count={3} />
) : myMenteeList.length === 0 ? (
<EmptySdwBCards message={"나와 매칭된 멘토입니다"} />
) : (
myMenteeList.slice(0, 3).map((mentee) => {
Expand Down
3 changes: 2 additions & 1 deletion apps/web/src/app/mentor/_ui/MentorClient/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { useGetMyInfo } from "@/apis/MyPage";
import CloudSpinnerPage from "@/components/ui/CloudSpinnerPage";
import useAuthStore from "@/lib/zustand/useAuthStore";
import { UserRole } from "@/types/mentor";
import MentorPageSkeleton from "../MentorPageSkeleton";
import MenteePage from "./_ui/MenteePage";
import MentorPage from "./_ui/MentorPage";

Expand All @@ -27,7 +28,7 @@ const MentorClient = () => {
}, [isAuthResolving, isUnauthorized, isError, role, router]);

if (isAuthResolving) {
return <CloudSpinnerPage />;
return <MentorPageSkeleton />;
}

if (isUnauthorized || (!isError && !role)) {
Expand Down
82 changes: 82 additions & 0 deletions apps/web/src/app/mentor/_ui/MentorPageSkeleton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
const MentorTabSkeleton = () => (
<div className="mb-5 flex gap-2" aria-hidden="true">
<div className="h-9 flex-1 animate-pulse rounded-full bg-k-50" />
<div className="h-9 flex-1 animate-pulse rounded-full bg-k-50" />
</div>
);

export const MentorChatCardsSkeleton = ({ count = 2 }: { count?: number }) => (
<div className="space-y-2" aria-hidden="true">
{Array.from({ length: count }).map((_, index) => (
<div key={index} className="flex h-16 w-full items-center gap-3 rounded-lg bg-k-0 px-3 py-4 shadow-sdwB">
<div className="h-10 w-10 shrink-0 animate-pulse rounded-full bg-k-50" />
<div className="min-w-0 flex-1 animate-pulse">
<div className="mb-2 h-4 w-24 rounded bg-k-50" />
<div className="h-3 w-2/3 rounded bg-k-50" />
</div>
</div>
))}
</div>
);

export const MentorCardListSkeleton = ({ count = 3 }: { count?: number }) => (
<div className="space-y-4 pb-28" aria-hidden="true">
{Array.from({ length: count }).map((_, index) => (
<div key={index} className="rounded-lg bg-white px-4 pt-4 pb-3 shadow-sdwB">
<div className="flex items-start gap-3">
<div className="h-12 w-12 shrink-0 animate-pulse rounded-full bg-k-50" />
<div className="min-w-0 flex-1 animate-pulse">
<div className="mb-2 h-4 w-16 rounded bg-k-50" />
<div className="mb-2 h-5 w-28 rounded bg-k-50" />
<div className="h-4 w-40 rounded bg-k-50" />
</div>
</div>
<div className="mt-4 border-t border-t-k-50 pt-2">
<div className="mx-auto h-5 w-7 animate-pulse rounded bg-k-50" />
</div>
</div>
))}
</div>
);

export const MentorApplicantListSkeleton = ({ count = 4 }: { count?: number }) => (
<div aria-hidden="true">
{Array.from({ length: count }).map((_, index) => (
<div key={index} className="flex w-full items-start gap-3 border-b border-k-50 p-4">
<div className="h-10 w-10 shrink-0 animate-pulse rounded-full bg-k-50" />
<div className="flex-1 animate-pulse">
<div className="mb-2 h-4 w-3/4 rounded bg-k-50" />
<div className="h-3 w-1/2 rounded bg-k-50" />
</div>
<div className="h-5 w-5 animate-pulse rounded bg-k-50" />
</div>
))}
</div>
);

const MentorFindSectionSkeleton = () => (
<section>
<div className="mb-3 h-6 w-24 animate-pulse rounded bg-k-50" />
<div className="mb-3 flex gap-2 overflow-hidden">
{Array.from({ length: 4 }).map((_, index) => (
<div key={index} className="h-8 w-16 shrink-0 animate-pulse rounded-2xl bg-k-50" />
))}
</div>
<MentorCardListSkeleton />
</section>
);

const MentorPageSkeleton = () => (
<div role="status" aria-label="멘토 페이지 정보를 불러오는 중입니다">
<MentorTabSkeleton />
<div className="mb-3 mt-5 flex items-center justify-between">
<div className="h-6 w-36 animate-pulse rounded bg-k-50" />
<div className="h-5 w-14 animate-pulse rounded bg-k-50" />
</div>
<MentorChatCardsSkeleton />
<div className="my-8 h-1.5 w-full bg-k-50" />
<MentorFindSectionSkeleton />
</div>
);

export default MentorPageSkeleton;
9 changes: 9 additions & 0 deletions apps/web/src/app/mentor/loading.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import MentorPageSkeleton from "./_ui/MentorPageSkeleton";

const MentorLoading = () => (
<div className="w-full px-5">
<MentorPageSkeleton />
</div>
);

export default MentorLoading;
Loading