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
3 changes: 2 additions & 1 deletion src/app/estimate/received-requests/page.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import MoverAuthGate from "@/components/auth/MoverAuthGate";
import ReceivedRequestsPage from "@/components/estimate/ReceivedRequestsPage";
import { ReceivedRequestsPageSkeleton } from "@/components/estimate/ReceivedRequestsSkeleton";

export default function Page() {
return (
<MoverAuthGate loadingMessage="받은 요청을 불러오는 중입니다.">
<MoverAuthGate loadingFallback={<ReceivedRequestsPageSkeleton />}>
<div className="bg-background-default text-text-primary min-h-screen">
<ReceivedRequestsPage />
</div>
Expand Down
3 changes: 2 additions & 1 deletion src/app/estimate/rejected/page.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { Metadata } from "next";

import MoverAuthGate from "@/components/auth/MoverAuthGate";
import MoverEstimateListPageSkeleton from "@/components/estimate/MoverEstimateListSkeleton";
import RejectedRequestsPage from "@/components/estimate/RejectedRequestsPage";

export const metadata: Metadata = {
Expand All @@ -10,7 +11,7 @@ export const metadata: Metadata = {

export default function RejectedRequestsRoute() {
return (
<MoverAuthGate loadingMessage="반려 요청을 불러오는 중입니다.">
<MoverAuthGate loadingFallback={<MoverEstimateListPageSkeleton />}>
<RejectedRequestsPage />
</MoverAuthGate>
);
Expand Down
3 changes: 2 additions & 1 deletion src/app/estimate/sent/page.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { Metadata } from "next";

import MoverAuthGate from "@/components/auth/MoverAuthGate";
import MoverEstimateListPageSkeleton from "@/components/estimate/MoverEstimateListSkeleton";
import SentEstimatesPage from "@/components/estimate/sent/SentEstimatesPage";

export const metadata: Metadata = {
Expand All @@ -10,7 +11,7 @@ export const metadata: Metadata = {

export default function SentEstimatesRoute() {
return (
<MoverAuthGate loadingMessage="보낸 견적을 불러오는 중입니다.">
<MoverAuthGate loadingFallback={<MoverEstimateListPageSkeleton />}>
<SentEstimatesPage />
</MoverAuthGate>
);
Expand Down
62 changes: 62 additions & 0 deletions src/components/estimate/MoverEstimateListSkeleton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { Skeleton } from "@/components/common/Skeleton/Skeleton";
import MoverEstimateTabs from "@/components/estimate/MoverEstimateTabs";

function EstimateCardSkeleton() {
return (
<div className="border-border-subtle bg-background-default shadow-estimate-card rounded-20 flex min-h-[333px] w-full flex-col gap-24 border-[0.5px] px-20 py-24 md:min-h-[322px] md:gap-32 md:px-40 md:py-32 xl:min-h-[324px]">
<div className="flex flex-1 flex-col gap-16 md:gap-24">
<div className="flex min-h-32 items-center justify-between gap-12">
<div className="flex gap-8">
<Skeleton className="h-32 w-72 rounded-full" />
<Skeleton className="h-32 w-96 rounded-full" />
</div>
<Skeleton className="h-24 w-72" />
</div>

<div className="flex flex-col gap-12">
<Skeleton className="h-28 w-160" />
<div className="bg-border-subtle h-px" />
</div>

<div className="flex flex-col gap-12 md:flex-row md:justify-between md:gap-20">
<div className="flex items-end gap-12">
<Skeleton className="h-48 w-72" />
<Skeleton className="mb-8 h-1 w-20" />
<Skeleton className="h-48 w-72" />
</div>
<Skeleton className="h-48 w-136" />
</div>
</div>

<div className="border-border-default flex h-47 items-end justify-between border-t md:h-52">
<Skeleton className="h-20 w-72" />
<Skeleton className="h-32 w-120" />
</div>
</div>
);
}

export function MoverEstimateCardGridSkeleton() {
return (
<div
className="mx-auto grid w-full min-w-0 grid-cols-1 gap-20 md:max-w-[600px] md:gap-32 xl:max-w-[1200px] xl:grid-cols-2 xl:gap-24"
role="status"
aria-label="견적 목록을 불러오는 중입니다."
aria-busy="true"
>
Comment thread
coderabbitai[bot] marked this conversation as resolved.
<EstimateCardSkeleton />
<EstimateCardSkeleton />
</div>
);
}

export default function MoverEstimateListPageSkeleton() {
return (
<>
<MoverEstimateTabs />
<main className="bg-background-subtle min-h-[calc(100vh-108px)] px-24 pt-24 pb-40 md:min-h-[calc(100vh-142px)] md:px-72 md:pt-32 xl:min-h-[calc(100vh-168px)] xl:px-0 xl:pt-[59px] xl:pb-[107px]">
<MoverEstimateCardGridSkeleton />
</main>
</>
);
}
16 changes: 9 additions & 7 deletions src/components/estimate/ReceivedRequestsPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { PageHeader } from "@/components/common/PageHeader";
import Search from "@/components/common/Search/Search";
import Select from "@/components/common/Select/Select";
import { Text } from "@/components/common/Text";
import { Skeleton } from "@/components/common/Skeleton/Skeleton";
import Toast from "@/components/common/Toast/Toast";
import {
useMoverEstimateRequests,
Expand All @@ -21,6 +22,7 @@ import type { MoveType } from "@/types/move";
import type { MoverEstimateRequest, RequestSort } from "@/types/moverEstimateRequest";

import ReceivedRequestCard from "./ReceivedRequestCard";
import ReceivedRequestsSkeleton from "./ReceivedRequestsSkeleton";
import RejectEstimateModal from "./RejectEstimateModal";
import SendEstimateModal, { type SendEstimateInput } from "./SendEstimateModal";

Expand Down Expand Up @@ -142,13 +144,17 @@ export default function ReceivedRequestsPage() {
</section>

<section className="flex flex-col gap-12 xl:gap-24">
{!query.isPending && (
{query.isPending ? (
<Skeleton className="hidden h-26 w-72 xl:block" />
) : (
<Text as="p" variant="2lg-semibold" className="text-text-secondary hidden xl:block">
전체 {totalCount}건
</Text>
)}
<div className="flex min-h-40 flex-wrap items-center justify-between gap-12 px-10 xl:px-0">
{!query.isPending && (
{query.isPending ? (
<Skeleton className="h-20 w-64 xl:hidden" />
) : (
<Text as="p" variant="md-semibold" className="text-text-secondary xl:hidden">
전체 {totalCount}건
</Text>
Expand Down Expand Up @@ -188,11 +194,7 @@ export default function ReceivedRequestsPage() {
</div>
</div>

{query.isPending && (
<Text as="p" variant="lg-regular" className="text-text-subtle py-80 text-center">
받은 요청을 불러오는 중이에요.
</Text>
)}
{query.isPending ? <ReceivedRequestsSkeleton /> : null}
{query.isError && (
<Text as="p" variant="lg-regular" className="text-text-error py-80 text-center">
받은 요청을 불러오지 못했어요.
Expand Down
87 changes: 87 additions & 0 deletions src/components/estimate/ReceivedRequestsSkeleton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import { Skeleton } from "@/components/common/Skeleton/Skeleton";
import { PageHeader } from "@/components/common/PageHeader";
import { ESTIMATE_REQUEST_DETAIL_CARD_CLASSNAME } from "@/components/estimate/EstimateRequestSummaryContent";

function ReceivedRequestCardSkeleton() {
return (
<div className={ESTIMATE_REQUEST_DETAIL_CARD_CLASSNAME}>
<div className="flex flex-col gap-16 md:gap-24">
<div className="flex min-h-32 items-center justify-between gap-12">
<div className="flex gap-8">
<Skeleton className="h-32 w-72 rounded-full" />
<Skeleton className="h-32 w-96 rounded-full" />
</div>
<Skeleton className="h-20 w-56" />
</div>

<div className="flex flex-col gap-12">
<Skeleton className="h-28 w-160" />
<div className="bg-border-subtle h-px" />
</div>

<div className="flex flex-col gap-12 xl:flex-row xl:justify-between xl:gap-20">
<div className="flex items-end gap-12">
<Skeleton className="h-48 w-72" />
<Skeleton className="mb-8 h-1 w-20" />
<Skeleton className="h-48 w-72" />
</div>
<Skeleton className="h-48 w-136" />
</div>
</div>

<div className="flex flex-col gap-12 sm:grid sm:grid-cols-2">
<Skeleton className="rounded-16 h-64 w-full" />
<Skeleton className="rounded-16 h-64 w-full" />
</div>
</div>
);
}

export default function ReceivedRequestsSkeleton() {
return (
<div
className="grid w-full grid-cols-1 gap-24 md:max-w-[588px] xl:max-w-none xl:grid-cols-2"
role="status"
aria-label="받은 요청을 불러오는 중입니다."
aria-busy="true"
>
<ReceivedRequestCardSkeleton />
<ReceivedRequestCardSkeleton />
</div>
);
}

export function ReceivedRequestsPageSkeleton() {
return (
<div className="bg-background-default text-text-primary min-h-screen">
<PageHeader title="받은 요청" />

<main className="mx-auto flex max-w-[1200px] flex-col gap-0 px-24 pb-80 md:px-[72px] xl:gap-40 xl:px-0">
<section className="flex flex-col gap-24">
<Skeleton className="rounded-16 mx-10 h-64 w-[calc(100%_-_20px)] xl:mx-0 xl:w-full" />

<div className="hidden flex-wrap gap-12 xl:flex">
<Skeleton className="h-32 w-80 rounded-full" />
<Skeleton className="h-32 w-80 rounded-full" />
<Skeleton className="h-32 w-80 rounded-full" />
</div>
</section>

<section className="flex flex-col gap-12 xl:gap-24">
<Skeleton className="hidden h-26 w-72 xl:block" />

<div className="flex min-h-40 items-center justify-between gap-12 px-10 xl:px-0">
<Skeleton className="h-20 w-64 xl:hidden" />
<div className="hidden items-center gap-12 xl:flex">
<Skeleton className="h-24 w-112" />
<Skeleton className="h-24 w-112" />
</div>
<Skeleton className="rounded-8 h-40 w-112" />
</div>

<ReceivedRequestsSkeleton />
</section>
</main>
</div>
);
}
3 changes: 2 additions & 1 deletion src/components/estimate/RejectedRequestsPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { useEffect, useRef } from "react";

import { Text } from "@/components/common/Text";
import MoverEstimateTabs from "@/components/estimate/MoverEstimateTabs";
import { MoverEstimateCardGridSkeleton } from "@/components/estimate/MoverEstimateListSkeleton";
import RejectedRequestCard from "@/components/estimate/RejectedRequestCard";
import EstimatesQueryStatus from "@/components/estimate/EstimatesQueryStatus";
import { useRejectedEstimateRequests } from "@/hooks/useMoverEstimateRequests";
Expand Down Expand Up @@ -37,7 +38,7 @@ export default function RejectedRequestsPage() {
<main className="bg-background-subtle min-h-[calc(100vh-108px)] px-24 pt-24 pb-40 md:min-h-[calc(100vh-142px)] md:px-72 md:pt-32 xl:min-h-[calc(100vh-168px)] xl:px-0 xl:pt-[59px] xl:pb-[107px]">
<h1 className="sr-only">반려 요청</h1>

{query.isPending ? <EstimatesQueryStatus message="반려 요청을 불러오는 중이에요." /> : null}
{query.isPending ? <MoverEstimateCardGridSkeleton /> : null}

{query.isError ? (
<EstimatesQueryStatus
Expand Down
25 changes: 11 additions & 14 deletions src/components/estimate/sent/SentEstimateCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import DesignatedChip from "@/components/estimate/DesignatedChip";
import FrameIcon from "@/icons/frame.svg";
import { APP_ROUTES } from "@/lib/constants/appRoutes";
import { formatKoreanDateTime } from "@/lib/utils/date";
import { cn } from "@/lib/utils/cn";
import { markInternalDetailNavigationOnClick } from "@/lib/utils/detailNavigation";
import type { MoveType } from "@/types/move";

Expand Down Expand Up @@ -40,7 +41,12 @@ export default function SentEstimateCard({ estimate }: SentEstimateCardProps) {
const detailHref = APP_ROUTES.MOVER_ESTIMATES.SENT_DETAIL(estimate.id);

return (
<article className="border-border-subtle bg-background-default shadow-estimate-card rounded-20 relative flex min-h-[333px] w-full flex-col gap-24 overflow-hidden border-[0.5px] px-20 py-24 md:min-h-[322px] md:gap-32 md:px-40 md:py-32 xl:min-h-[324px]">
<article
className={cn(
"group border-border-subtle bg-background-default shadow-estimate-card rounded-20 hover:border-border-brand hover:shadow-notification relative flex min-h-[333px] w-full flex-col gap-24 overflow-hidden border-[0.5px] px-20 py-24 transition-[transform,box-shadow,border-color] duration-200 ease-out hover:-translate-y-2 md:min-h-[322px] md:gap-32 md:px-40 md:py-32 xl:min-h-[324px]",
isCompleted && "select-none",
)}
>
<Link
href={detailHref}
aria-label={`${estimate.customerName} 고객님 견적 상세보기`}
Expand Down Expand Up @@ -127,19 +133,10 @@ export default function SentEstimateCard({ estimate }: SentEstimateCardProps) {
</div>

{isCompleted ? (
<div className="bg-overlay-card-disabled border-border-dimmed rounded-20 pointer-events-none absolute inset-[-0.5px] z-20 flex items-center justify-center border">
<div className="flex w-[200px] flex-col items-center gap-20">
<Text variant="2lg-semibold" className="text-text-inverse">
이사 완료된 견적이에요
</Text>
<Link
href={detailHref}
className="bg-background-brand-muted border-border-brand text-text-brand shadow-cta rounded-12 pointer-events-auto flex h-54 w-full items-center justify-center border"
onClick={(event) => markInternalDetailNavigationOnClick(event, detailHref)}
>
<Text variant="lg-semibold">견적 상세보기</Text>
</Link>
</div>
<div className="bg-overlay-card-disabled border-border-dimmed rounded-20 group-hover:border-border-brand pointer-events-none absolute inset-[-0.5px] z-20 flex items-center justify-center border transition-colors duration-200">
<Text variant="2lg-semibold" className="text-text-inverse">
이사 완료된 견적이에요
</Text>
</div>
) : null}
</article>
Expand Down
3 changes: 2 additions & 1 deletion src/components/estimate/sent/SentEstimatesPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import { useEffect, useRef } from "react";

import MoverEstimateTabs from "@/components/estimate/MoverEstimateTabs";
import { MoverEstimateCardGridSkeleton } from "@/components/estimate/MoverEstimateListSkeleton";
import EstimatesQueryStatus from "@/components/estimate/EstimatesQueryStatus";
import SentEstimateCard from "@/components/estimate/sent/SentEstimateCard";
import { useSentEstimates } from "@/hooks/useSentEstimates";
Expand Down Expand Up @@ -34,7 +35,7 @@ export default function SentEstimatesPage() {
<main className="bg-background-subtle min-h-[calc(100vh-108px)] px-24 pt-24 pb-[77px] md:min-h-[calc(100vh-142px)] md:px-72 md:pt-32 md:pb-40 xl:min-h-[calc(100vh-168px)] xl:px-0 xl:pt-[59px] xl:pb-[107px]">
<h1 className="sr-only">보낸 견적 조회</h1>

{query.isPending ? <EstimatesQueryStatus message="보낸 견적을 불러오는 중이에요." /> : null}
{query.isPending ? <MoverEstimateCardGridSkeleton /> : null}

{query.isError ? (
<EstimatesQueryStatus
Expand Down
13 changes: 13 additions & 0 deletions src/lib/loading/getMoverProtectedLoadingFallback.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,26 @@
import type { ReactNode } from "react";

import ProfileFormSkeleton from "@/components/profile/ProfileFormSkeleton";
import { ReceivedRequestsPageSkeleton } from "@/components/estimate/ReceivedRequestsSkeleton";
import MoverEstimateListPageSkeleton from "@/components/estimate/MoverEstimateListSkeleton";
import { APP_ROUTES } from "@/lib/constants/appRoutes";

/** RoleGuard auth 대기 중 기사 protected layout용 로딩 UI
*
* pathname: 현재 페이지 경로
*/
export const getMoverProtectedLoadingFallback = (pathname: string): ReactNode => {
if (pathname === APP_ROUTES.MOVER_ESTIMATES.RECEIVED_REQUESTS) {
return <ReceivedRequestsPageSkeleton />;
}

if (
pathname === APP_ROUTES.MOVER_ESTIMATES.SENT ||
pathname === APP_ROUTES.MOVER_ESTIMATES.REJECTED
) {
return <MoverEstimateListPageSkeleton />;
}

if (pathname === APP_ROUTES.MOVER_PROFILE) {
return (
<ProfileFormSkeleton
Expand Down