diff --git a/apps/web/app/error.tsx b/apps/web/app/error.tsx new file mode 100644 index 00000000..1bb52150 --- /dev/null +++ b/apps/web/app/error.tsx @@ -0,0 +1,38 @@ +"use client"; + +import { useEffect } from "react"; + +export default function GlobalError({ + error, + reset, +}: { + error: Error & { digest?: string }; + reset: () => void; +}) { + useEffect(() => { + console.error(error); + }, [error]); + + return ( +
+

+ Something broke +

+

+ We hit an unexpected error +

+

+ The page failed to render. Try again without losing your current session. +

+
+ +
+
+ ); +} diff --git a/apps/web/app/globals.css b/apps/web/app/globals.css index 3ddab396..5071f295 100644 --- a/apps/web/app/globals.css +++ b/apps/web/app/globals.css @@ -29,3 +29,12 @@ body { color: var(--foreground); font-family: var(--font-geist-sans), sans-serif; } + +@keyframes shimmer { + 0% { + background-position: 200% 0; + } + 100% { + background-position: -200% 0; + } +} diff --git a/apps/web/app/jobs/[id]/fund/page.tsx b/apps/web/app/jobs/[id]/fund/page.tsx index a31c09f2..8b0645b4 100644 --- a/apps/web/app/jobs/[id]/fund/page.tsx +++ b/apps/web/app/jobs/[id]/fund/page.tsx @@ -7,6 +7,7 @@ import { SiteShell } from "@/components/site-shell"; import { api, type Job } from "@/lib/api"; import { depositEscrow } from "@/lib/contracts"; import { formatUsdc } from "@/lib/format"; +import { TransactionPendingNotification } from "@/components/wallet/transaction-pending-notification"; const PLATFORM_FEE_BPS = 200; @@ -145,6 +146,18 @@ export default function EscrowFundingPage() { title="Fund Escrow" description="Review the full contract value, platform fee, and milestone count before authorising the blockchain transfer." > +
+ +
+
diff --git a/apps/web/app/jobs/[id]/page.tsx b/apps/web/app/jobs/[id]/page.tsx index 21316d33..378cda89 100644 --- a/apps/web/app/jobs/[id]/page.tsx +++ b/apps/web/app/jobs/[id]/page.tsx @@ -14,6 +14,7 @@ import { } from "lucide-react"; import { SiteShell } from "@/components/site-shell"; import { Stars } from "@/components/stars"; +import { JobDetailsSkeleton } from "@/components/ui/skeleton"; import { useLiveJobWorkspace } from "@/hooks/use-live-job-workspace"; import { api } from "@/lib/api"; import { releaseFunds, openDispute } from "@/lib/contracts"; @@ -171,7 +172,7 @@ export default function JobDetailsPage() { title="Loading workspace" description="Fetching counterparties, milestones, deliverables, and dispute state." > -
+ ); } diff --git a/apps/web/app/jobs/page.tsx b/apps/web/app/jobs/page.tsx index 61c7cf1c..6881e0b5 100644 --- a/apps/web/app/jobs/page.tsx +++ b/apps/web/app/jobs/page.tsx @@ -4,6 +4,7 @@ import Link from "next/link"; import { ArrowUpRight, Clock3, Search, SlidersHorizontal } from "lucide-react"; import { SiteShell } from "@/components/site-shell"; import { Stars } from "@/components/stars"; +import { JobCardSkeleton } from "@/components/ui/skeleton"; import { useJobBoard } from "@/hooks/use-job-board"; import { formatDate, formatUsdc, shortenAddress } from "@/lib/format"; @@ -83,13 +84,11 @@ export default function JobsPage() {
{loading ? ( -
+
{Array.from({ length: 6 }, (_, index) => ( -
+ ))} + Loading open jobs
) : (
diff --git a/apps/web/components/ui/skeleton.tsx b/apps/web/components/ui/skeleton.tsx new file mode 100644 index 00000000..5b0a778c --- /dev/null +++ b/apps/web/components/ui/skeleton.tsx @@ -0,0 +1,94 @@ +import { cn } from "@/lib/utils"; + +interface SkeletonProps { + className?: string; +} + +export function Skeleton({ className }: SkeletonProps) { + return ( +