Skip to content
Open
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
12 changes: 11 additions & 1 deletion src/app/commitments/[id]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use client';

import React from 'react';
import React, { useState, useEffect } from 'react';
import Link from 'next/link';
import { notFound } from 'next/navigation';
import CommitmentHealthMetrics from '@/components/dashboard/CommitmentHealthMetrics';
Expand Down Expand Up @@ -119,6 +119,15 @@ export default function CommitmentDetailPage({
}: {
params: { id: string };
}) {
const [isLoading, setIsLoading] = useState(true)

useEffect(() => {
const timer = setTimeout(() => {
setIsLoading(false)
}, 1000)
return () => clearTimeout(timer)
}, [])

const commitment = getCommitmentById(params.id)
if (!commitment) notFound()

Expand Down Expand Up @@ -189,6 +198,7 @@ export default function CommitmentDetailPage({
feeGenerationData={MOCK_FEE_GENERATION_DATA}
thresholdPercent={0.5}
volatilityPercent={35}
isLoading={isLoading}
/>

<RecentAttestationsPanel
Expand Down
46 changes: 34 additions & 12 deletions src/app/commitments/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import MyCommitmentsGrid from '@/components/MyCommitmentsGrid'
import CommitmentEarlyExitModal from '@/components/CommitmentEarlyExitModal/CommitmentEarlyExitModal'
import { Commitment, CommitmentStats } from '@/types/commitment'
import { listCommitments } from '@/lib/backend/mocks/contracts'
import { MyCommitmentCardSkeleton, MyCommitmentsStatsSkeleton } from '@/components/MyCommitmentsSkeletons'

const mockCommitments: Commitment[] = [
{
Expand Down Expand Up @@ -140,6 +141,15 @@ export default function MyCommitments() {
const [earlyExitCommitmentId, setEarlyExitCommitmentId] = useState<string | null>(null)
const [hasAcknowledged, setHasAcknowledged] = useState(false)
const [commitmentsList, setCommitmentsList] = useState<Commitment[]>(mockCommitments)
const [isLoading, setIsLoading] = useState(true)

useEffect(() => {
// Initial data load simulation
const timer = setTimeout(() => {
setIsLoading(false)
}, 800)
return () => clearTimeout(timer)
}, [])

useEffect(() => {
if (process.env.NEXT_PUBLIC_USE_MOCKS === 'true') {
Expand Down Expand Up @@ -201,12 +211,16 @@ export default function MyCommitments() {
/>

<div className="w-full flex-1 px-22 py-8 max-[1024px]:px-8 max-[640px]:px-4">
<MyCommitmentsStats
totalActive={mockStats.totalActive}
totalCommittedValue={mockStats.totalCommittedValue}
averageComplianceScore={`${mockStats.avgComplianceScore}%`}
totalFeesGenerated={mockStats.totalFeesGenerated}
/>
{isLoading ? (
<MyCommitmentsStatsSkeleton />
) : (
<MyCommitmentsStats
totalActive={mockStats.totalActive}
totalCommittedValue={mockStats.totalCommittedValue}
averageComplianceScore={`${mockStats.avgComplianceScore}%`}
totalFeesGenerated={mockStats.totalFeesGenerated}
/>
)}

<MyCommitmentsFilters
searchQuery={searchQuery}
Expand All @@ -219,12 +233,20 @@ export default function MyCommitments() {
onSortByChange={setSortBy}
/>

<MyCommitmentsGrid
commitments={filteredCommitments}
onDetails={(id) => router.push(`/commitments/${id}`)}
onAttestations={(id) => console.log('Attestations for', id)}
onEarlyExit={openEarlyExitModal}
/>
{isLoading ? (
<div className="mt-8 grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{[1, 2, 3].map((i) => (
<MyCommitmentCardSkeleton key={`loading-${i}`} />
))}
</div>
) : (
<MyCommitmentsGrid
commitments={filteredCommitments}
onDetails={(id) => router.push(`/commitments/${id}`)}
onAttestations={(id) => console.log('Attestations for', id)}
onEarlyExit={openEarlyExitModal}
/>
)}
</div>

{commitmentForEarlyExit && earlyExitSummary && (
Expand Down
30 changes: 29 additions & 1 deletion src/app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -147,8 +147,36 @@ button {
background-color: #51A2FF;
}

/* Firefox */
/* ... (existing custom-scrollbar styles) */
.custom-scrollbar {
scrollbar-width: thin;
scrollbar-color: #4A6B8A #0A0A0A;
}

/* Unified Loading Pattern: Shimmer */
@keyframes shimmer {
0% {
background-position: -200% 0;
}
100% {
background-position: 200% 0;
}
}

.animate-shimmer {
background: linear-gradient(
90deg,
rgba(255, 255, 255, 0.03) 25%,
rgba(255, 255, 255, 0.08) 50%,
rgba(255, 255, 255, 0.03) 75%
);
background-size: 200% 100%;
animation: shimmer 2s infinite linear;
}

@media (prefers-reduced-motion: reduce) {
.animate-shimmer {
animation: none;
background: rgba(255, 255, 255, 0.05);
}
}
32 changes: 30 additions & 2 deletions src/app/marketplace/page.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
'use client'

import Link from 'next/link'
import { useMemo, useState } from 'react'
import { useMemo, useState, useEffect } from 'react'
import { MarketplaceHeader } from '@/components/MarketplaceHeader/MarketplaceHeader'
import { MarketplaceGrid } from '@/components/MarketplaceGrid'
import { MarketplaceResultsLayout } from '@/components/MarketplaceResultsLayout'
import MarketplaceFilters from '@/components/MarketplaceFilter/MarketplaceFilters'
import { MarketplaceCardSkeleton, MarketplaceRowSkeleton } from '@/components/MarketplaceSkeletons'

// Interfaces matching the components
interface Filters {
Expand Down Expand Up @@ -335,6 +336,7 @@ export default function Marketplace() {
const [viewMode, setViewMode] = useState<'grid' | 'list'>('grid')
const [currentPage, setCurrentPage] = useState(1)
const [showMobileFilters, setShowMobileFilters] = useState(false)
const [isLoading, setIsLoading] = useState(true)
const [filters, setFilters] = useState<Filters>({
sortBy: 'price',
commitmentType: ['balanced'],
Expand All @@ -344,6 +346,14 @@ export default function Marketplace() {
maxLoss: 100,
})

useEffect(() => {
// Initial data load simulation
const timer = setTimeout(() => {
setIsLoading(false)
}, 1200)
return () => clearTimeout(timer)
}, [])

// ... rest of the logic
const itemsPerPage = 9

Expand Down Expand Up @@ -451,7 +461,25 @@ export default function Marketplace() {
totalPages={totalPages}
onPageChange={handlePageChange}
>
{viewMode === 'grid' ? (
{isLoading ? (
viewMode === 'grid' ? (
<section className="mt-6">
<ul className="list-none p-0 m-0 grid grid-cols-3 gap-6 max-[1024px]:grid-cols-2 max-[720px]:grid-cols-1">
{[1, 2, 3, 4, 5, 6].map((idx) => (
<li key={`skel-${idx}`}>
<MarketplaceCardSkeleton />
</li>
))}
</ul>
</section>
) : (
<div className="flex flex-col gap-4 mt-6">
{[1, 2, 3, 4, 5].map((idx) => (
<MarketplaceRowSkeleton key={`skel-row-${idx}`} />
))}
</div>
)
) : viewMode === 'grid' ? (
<MarketplaceGrid items={pagedListings} />
) : (
<MarketplaceListView items={pagedListings} />
Expand Down
38 changes: 38 additions & 0 deletions src/components/ChartSkeleton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
'use client';

import React from 'react';
import { Skeleton } from './ui/Skeleton';

export function ChartSkeleton() {
return (
<div className="w-full h-full min-h-[350px] bg-[#111] rounded-xl p-4 sm:p-6 border border-[#222] animate-shimmer overflow-hidden">
<div className="flex justify-between items-center mb-8">
<Skeleton className="w-32 h-6" />
<div className="flex gap-4">
<Skeleton className="w-20 h-4 rounded-full" />
<Skeleton className="w-20 h-4 rounded-full" />
</div>
</div>

{/* Simulation of a chart axis and lines */}
<div className="relative h-[200px] mt-10">
{/* Y Axis simulation */}
<div className="absolute left-0 top-0 bottom-0 w-px bg-white/5" />
{/* X Axis simulation */}
<div className="absolute left-0 right-0 bottom-0 h-px bg-white/5" />

{/* Shimmering chart lines (simplified) */}
<div className="absolute inset-0 flex items-end justify-between px-4">
{[40, 70, 50, 90, 60, 80, 45, 75].map((h, i) => (
<Skeleton key={i} className={`w-[2px] rounded-full`} style={{ height: `${h}%` }} />
))}
</div>
</div>

<div className="mt-8 pt-4 border-t border-[#222]">
<Skeleton className="w-full h-4 mb-2" />
<Skeleton className="w-3/4 h-4" />
</div>
</div>
);
}
69 changes: 69 additions & 0 deletions src/components/MarketplaceSkeletons.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
'use client';

import React from 'react';
import { Skeleton } from './ui/Skeleton';

export function MarketplaceCardSkeleton() {
return (
<div className="relative flex flex-col h-full rounded-[14px] p-[18px] bg-[#0A0A0AE5] border border-[rgba(255,255,255,0.08)] overflow-hidden">
{/* Header */}
<header className="flex items-center justify-between gap-3.5 mb-3.5">
<Skeleton className="w-[52px] h-[52px] rounded-[14px]" />
<div className="flex flex-col items-end gap-2">
<Skeleton className="w-20 h-8 rounded-full" />
<Skeleton className="w-16 h-8 rounded-[10px]" />
</div>
</header>

{/* Body */}
<div className="flex-1 pt-[10px] px-[2px] pb-0">
<Skeleton className="w-24 h-4 mb-4" />
<div className="space-y-4">
{[1, 2, 3, 4, 5].map((i) => (
<div key={i} className="flex justify-between items-center">
<Skeleton className="w-20 h-4" />
<Skeleton className="w-24 h-4" />
</div>
))}
</div>
</div>

{/* Footer */}
<footer className="mt-6 pt-4 border-t border-white/10 grid gap-3.5">
<Skeleton className="w-full h-[68px] rounded-[14px]" />
<div className="grid grid-cols-2 gap-3">
<Skeleton className="h-11 rounded-[14px]" />
<Skeleton className="h-11 rounded-[14px]" />
</div>
</footer>
</div>
);
}

export function MarketplaceRowSkeleton() {
return (
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-6 rounded-2xl border border-white/10 bg-[#0A0A0A]/90 p-6 sm:p-5">
<div className="flex items-center gap-4 min-w-[150px]">
<Skeleton className="h-12 w-12 sm:h-10 sm:w-10 rounded-lg" />
<div className="space-y-2">
<Skeleton className="w-16 h-4 rounded-full" />
<Skeleton className="w-20 h-4" />
</div>
</div>

<div className="grid grid-cols-2 sm:flex sm:items-center gap-6 sm:gap-4 flex-1">
{[1, 2, 3, 4, 5].map((i) => (
<div key={i} className="flex flex-col gap-2 min-w-[80px]">
<Skeleton className="w-12 h-3" />
<Skeleton className="w-16 h-6" />
</div>
))}
</div>

<div className="flex items-center gap-3 pt-4 sm:pt-0 border-t border-white/5 sm:border-0">
<Skeleton className="w-24 h-11 rounded-xl" />
<Skeleton className="w-24 h-11 rounded-xl" />
</div>
</div>
);
}
68 changes: 68 additions & 0 deletions src/components/MyCommitmentsSkeletons.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
'use client';

import React from 'react';
import { Skeleton } from './ui/Skeleton';

export function MyCommitmentCardSkeleton() {
return (
<div className="relative flex flex-col gap-5 rounded-[16px] border border-white/10 bg-[rgba(13,13,13,0.8)] p-6 overflow-hidden">
<div className="flex items-center justify-between">
<Skeleton className="w-24 h-7 rounded-full" />
<Skeleton className="w-20 h-7 rounded-full" />
</div>

<Skeleton className="w-32 h-4" />

<div className="flex flex-col gap-2">
<Skeleton className="w-48 h-10" />
<Skeleton className="w-40 h-5" />
</div>

<div className="space-y-4">
{[1, 2].map((i) => (
<div key={i} className="space-y-2">
<Skeleton className="w-full h-3" />
<Skeleton className="w-full h-1.5 rounded-full" />
</div>
))}
</div>

<div className="grid grid-cols-2 gap-3">
<Skeleton className="h-14 rounded-[10px]" />
<Skeleton className="h-14 rounded-[10px]" />
</div>

<div className="flex justify-between">
<div className="space-y-2">
<Skeleton className="w-16 h-3" />
<Skeleton className="w-20 h-4" />
</div>
<div className="space-y-2 items-end flex flex-col">
<Skeleton className="w-16 h-3" />
<Skeleton className="w-20 h-4" />
</div>
</div>

<div className="grid grid-cols-2 gap-3 mt-2">
<Skeleton className="h-10 rounded-[8px]" />
<Skeleton className="h-10 rounded-[8px]" />
</div>
</div>
);
}

export function MyCommitmentsStatsSkeleton() {
return (
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4 mb-8">
{[1, 2, 3, 4].map((i) => (
<div key={i} className="rounded-2xl border border-white/10 bg-white/5 p-5 flex items-center gap-4">
<Skeleton className="w-10 h-10 rounded-xl" />
<div className="space-y-2">
<Skeleton className="w-16 h-6" />
<Skeleton className="w-32 h-3" />
</div>
</div>
))}
</div>
);
}
Loading