diff --git a/client/package-lock.json b/client/package-lock.json index b467ca8..ebc67f6 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -29,6 +29,7 @@ "clsx": "^2.1.1", "cmdk": "^1.0.0", "dotenv": "^16.4.5", + "framer-motion": "^12.5.0", "js-cookie": "^3.0.5", "lucide-react": "^0.376.0", "mini-svg-data-uri": "^1.4.4", @@ -7712,6 +7713,33 @@ "url": "https://github.com/sponsors/rawify" } }, + "node_modules/framer-motion": { + "version": "12.5.0", + "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.5.0.tgz", + "integrity": "sha512-buPlioFbH9/W7rDzYh1C09AuZHAk2D1xTA1BlounJ2Rb9aRg84OXexP0GLd+R83v0khURdMX7b5MKnGTaSg5iA==", + "license": "MIT", + "dependencies": { + "motion-dom": "^12.5.0", + "motion-utils": "^12.5.0", + "tslib": "^2.4.0" + }, + "peerDependencies": { + "@emotion/is-prop-valid": "*", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/is-prop-valid": { + "optional": true + }, + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } + } + }, "node_modules/fs-extra": { "version": "9.1.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", @@ -8830,6 +8858,21 @@ "node": ">=16 || 14 >=14.17" } }, + "node_modules/motion-dom": { + "version": "12.5.0", + "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.5.0.tgz", + "integrity": "sha512-uH2PETDh7m+Hjd1UQQ56yHqwn83SAwNjimNPE/kC+Kds0t4Yh7+29rfo5wezVFpPOv57U4IuWved5d1x0kNhbQ==", + "license": "MIT", + "dependencies": { + "motion-utils": "^12.5.0" + } + }, + "node_modules/motion-utils": { + "version": "12.5.0", + "resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-12.5.0.tgz", + "integrity": "sha512-+hFFzvimn0sBMP9iPxBa9OtRX35ZQ3py0UHnb8U29VD+d8lQ8zH3dTygJWqK7av2v6yhg7scj9iZuvTS0f4+SA==", + "license": "MIT" + }, "node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", diff --git a/client/package.json b/client/package.json index bbccb0a..dc15291 100644 --- a/client/package.json +++ b/client/package.json @@ -31,6 +31,7 @@ "clsx": "^2.1.1", "cmdk": "^1.0.0", "dotenv": "^16.4.5", + "framer-motion": "^12.5.0", "js-cookie": "^3.0.5", "lucide-react": "^0.376.0", "mini-svg-data-uri": "^1.4.4", diff --git a/client/src/components/ui/spinner.tsx b/client/src/components/ui/spinner.tsx new file mode 100644 index 0000000..6080df1 --- /dev/null +++ b/client/src/components/ui/spinner.tsx @@ -0,0 +1,17 @@ +import React from 'react' + +interface SpinnerProps { + className?: string +} + +export const Spinner: React.FC = ({ className = 'h-64' }) => { + return ( +
+
+
+
+
+
+
+ ) +} diff --git a/client/src/pages/Landing.tsx b/client/src/pages/Landing.tsx index 956f403..519429f 100644 --- a/client/src/pages/Landing.tsx +++ b/client/src/pages/Landing.tsx @@ -1,6 +1,17 @@ import { useEffect, useState } from 'react' import { Link, useNavigate } from 'react-router-dom' -import { Github, FileCode } from 'lucide-react' +import { + Github, + FileCode, + Search, + Database, + Shield, + ChevronRight, + Clock, + Hash, + HardDrive, + Activity, +} from 'lucide-react' import { getProviders, getProofSets, @@ -152,304 +163,341 @@ export const Landing = () => { if (loading) { return ( -
-
Loading...
+
+
+
+
+
+
) } return ( -
-
-
-

PDP Explorer

-
-
- setSearchQuery(e.target.value)} - /> - - - {/* Search results dropdown */} - {searchResults.length > 0 && ( -
- {searchResults.map((result, index) => ( - -
-

- {result.type === 'provider' ? 'Provider: ' : 'ProofSet: '} - {result.id} -

-

- {result.type === 'provider' - ? `${result.active_sets} active sets` - : `${formatDataSize(result.data_size)}`} -

-
- - ))} -
- )} -
-
- - {/* Network Wide Metrics */} -
-

Network Wide Metrics

-
- - - - - - - - -
-
+
+ {/* Hero Section with Search */} +
+
+
+

+ PDP Explorer +

+

+ Explore and verify Provable Data Possession on the network +

+
- {/* Providers Section */} -
-
-

Providers

- - View All - -
-
- {providers.length === 0 ? ( -
- No providers found +
+
+ + setSearchQuery(e.target.value)} + />
- ) : ( - - - - - - - - - - - - - - {providers.map((provider, index) => ( - - - - - - - - - - ))} - -
#ProviderProofSet#Data SizeJoined DateLast SeenFault #
- {(providerPage - 1) * ITEMS_PER_PAGE + index + 1} - - - {provider.providerId} - - {provider.activeProofSets} - {formatDataSize(provider.totalDataSize)} - - {formatDate(provider.firstSeen, false)} - - {formatDate(provider.lastSeen, false)} - - {provider.totalFaultedPeriods} -
- )} -
- {totalProviders > ITEMS_PER_PAGE && ( - - )} -
- {/* ProofSets Section */} -
-
-

ProofSets

- - View All - -
-
- {proofSets.length === 0 ? ( -
- No proof sets found -
- ) : ( - - - - - - - - - - - - - {proofSets.map((proofSet, index) => ( - - - - - - - - + {searchResults.length > 0 && ( +
+ {searchResults.map((result, index) => ( + +
+ {result.type === 'provider' ? ( + + ) : ( + + )} +
+
+
+ + {result.type === 'provider' ? 'Provider' : 'ProofSet'} + + + {result.id} + +
+

+ {result.type === 'provider' + ? `${result.active_sets} active sets` + : `${formatDataSize(result.data_size)}`} +

+
+ + ))} -
-
#Proof Set IDStatusRoot ## of proofsCreated At
- {(proofSetPage - 1) * ITEMS_PER_PAGE + index + 1} - - - {proofSet.setId} - - - {proofSet.isActive ? 'Active' : 'Inactive'} - {proofSet.totalRoots} - {proofSet.totalProvedRoots.toLocaleString()} - - {formatDate(proofSet.createdAt, false)} -
- )} +
+ )} +
- {totalProofSets > ITEMS_PER_PAGE && ( - - )}
-
-
-
-

Want to Learn More?

-

- Explore our codebase and smart contracts -

+
+
+

+ Network Overview +

+
+ } + change={null} + period="24h" + /> + } + change={null} + period="24h" + /> + } + change={null} + period="24h" + /> + } + change={null} + period="24h" + />
+
-
- + - + - -
- -

PDP Repository

-
-

- Check out our core PDP implementation + {/* Resources Section */} +

+
+ - -
- -

PDP Explorer

-
-

- Contribute to this explorer application -

-
+
+ } + href={`${explorerUrl}/address/${contractAddresses.PDPVerifier}`} + /> + } + href={`${explorerUrl}/address/${contractAddresses.SimplePDPService}`} + /> + } + href="https://github.com/FilOzone/pdp" + /> + } + href="https://github.com/FilOzone/pdp-explorer" + /> +
@@ -457,17 +505,76 @@ export const Landing = () => { ) } -export const MetricCard = ({ +// Enhanced MetricCard component +const MetricCard = ({ title, value, + icon, + change, + period, }: { title: string value: React.ReactNode + icon: React.ReactNode + change: string | null + period: string }) => { + const isPositive = change?.startsWith('+') return ( -
-

{title}

-

{value}

+
+
+
+ {icon} + {title} +
+
+
+
{value}
+ {change && ( +
+ + {change} + + in last {period} +
+ )} +
) } + +// Enhanced ResourceCard component +const ResourceCard = ({ + title, + description, + icon, + href, +}: { + title: string + description: string + icon: React.ReactNode + href: string +}) => { + return ( + +
+
+ {icon} +
+

+ {title} +

+
+

{description}

+
+ ) +} diff --git a/client/src/pages/ProofSetDetails.tsx b/client/src/pages/ProofSetDetails.tsx index ab04f2f..a521952 100644 --- a/client/src/pages/ProofSetDetails.tsx +++ b/client/src/pages/ProofSetDetails.tsx @@ -25,11 +25,32 @@ import { CollapsibleContent, CollapsibleTrigger, } from '@/components/ui/collapsible' -import { ChevronDown, ChevronUp } from 'lucide-react' +import { + ChevronDown, + ChevronUp, + Database, + User, + HardDrive, + Activity, + Hash, + FileText, + Calendar, + BarChart2, +} from 'lucide-react' import { trackedEvents, trackedMethods, explorerUrl } from '@/utility/constants' import JsonDisplay from '@/components/json-viewer' import ProofHeatMap from '@/components/proof-heatmap' import { formatDate, formatDataSize } from '@/utility/helper' +import { Spinner } from '@/components/ui/spinner' + +interface EventLogData { + value?: string + [key: string]: string | number | boolean | null | undefined | object +} + +type ExtendedEventLog = EventLog & { + data: EventLogData +} export const ProofSetDetails = () => { const { proofSetId } = useParams() @@ -41,7 +62,7 @@ export const ProofSetDetails = () => { const [totalEventLogs, setTotalEventLogs] = useState(0) const [totalRoots, setTotalRoots] = useState(0) const [transactions, setTransactions] = useState([]) - const [eventLogs, setEventLogs] = useState([]) + const [eventLogs, setEventLogs] = useState([]) const [roots, setRoots] = useState([]) const [heatmapRoots, setHeatmapRoots] = useState([]) const [isHeatmapExpanded, setIsHeatmapExpanded] = useState(false) @@ -109,7 +130,7 @@ export const ProofSetDetails = () => { } fetchData() - }, [proofSetId]) + }, [proofSetId, currentPage, activeTab]) useEffect(() => { if (!proofSetId) return @@ -132,7 +153,7 @@ export const ProofSetDetails = () => { } if (activeTab === 'eventLogs') fetchDataEventLogs() - }, [eventFilter, currentPage, activeTab]) + }, [eventFilter, currentPage, activeTab, proofSetId]) useEffect(() => { const fetchDataTxs = async () => { @@ -153,7 +174,7 @@ export const ProofSetDetails = () => { } if (activeTab === 'transactions') fetchDataTxs() - }, [methodFilter, currentPage, activeTab]) + }, [methodFilter, currentPage, activeTab, proofSetId]) useEffect(() => { const fetchDataRoots = async () => { @@ -173,7 +194,7 @@ export const ProofSetDetails = () => { } fetchDataRoots() - }, [currentRootsPage]) + }, [currentRootsPage, proofSetId]) useEffect(() => { if (!isHeatmapExpanded || !proofSetId) return @@ -199,7 +220,9 @@ export const ProofSetDetails = () => { fetchAllRoots() }, [isHeatmapExpanded, proofSetId, totalRoots]) - if (loading || !proofSet) return
Loading...
+ if (loading || !proofSet) { + return + } const formatTokenAmount = (attoFil: string) => { if (!attoFil || attoFil === '0') return '0 FIL' @@ -249,7 +272,7 @@ export const ProofSetDetails = () => { setCurrentPage(page)} />
) @@ -270,350 +293,88 @@ export const ProofSetDetails = () => { } return ( -
-
- - ← Back to Proof Sets - -

Proof Set Details: {proofSetId}

-
-
-
-

Overview

-
-
- Owner: - - {proofSet.owner} - -
-
- Listener Address: - {proofSet.listenerAddr} -
-
- Status: - {proofSet.isActive ? 'Active' : 'Inactive'} -
-
- Total Roots: - {proofSet.totalRoots || 0} +
+
+
+
+
+ +

+ Proof Set Details +

-
- Total Proofs Submitted: - {proofSet.totalProvedRoots || 0} -
-
- Data Size: - {formatDataSize(proofSet.totalDataSize)} +

ID: {proofSet.setId}

+
+ + {proofSet.isActive ? 'Active' : 'Inactive'} + +
+ +
+
+
+ + Owner
-
- Total Fee Paid: - {formatTokenAmount(proofSet.totalFeePaid)} + + {proofSet.owner} + +
+ +
+
+ + Data Size
-
- Faulted Periods: - {proofSet.totalFaultedPeriods} +
+ {formatDataSize(proofSet.totalDataSize)}
-
- Last Proven: - - {proofSet.lastProvenEpoch - ? proofSet.lastProvenEpoch.toLocaleString() - : 'Never'} - +
+ +
+
+ + Roots Status
-
- Next Challenge: - - {proofSet.nextChallengeEpoch - ? proofSet.nextChallengeEpoch.toLocaleString() - : 'N/A'} - +
+ {proofSet.totalProvedRoots} / {proofSet.totalRoots} Proved
-
- Created At: - {formatDate(proofSet.createdAt)} +
+ +
+
+ + Last Activity
-
- Updated At: - {formatDate(proofSet.updatedAt)} +
+ {formatDate(proofSet.lastProvenEpoch.toString())}
-
-
-

Proof Set Roots

-
-
- - - - - - - - - - - - - - - {roots.length > 0 ? ( - roots.map((root) => ( - - - - - - - - - - - )) - ) : ( - - - - )} - -
Root IdCidRaw SizeRemovedTotal ProofsTotal Fault PeriodsLastProvenEpochLast Faulted Epoch
- {root.rootId} - {root.cid} - {formatDataSize(root.size?.toString())} - - - {root.removed ? 'true' : 'false'} - - {root.totalProofsSubmitted}{root.totalPeriodsFaulted} - {root.lastProvenEpoch - ? root.lastProvenEpoch.toLocaleString() - : 'Never'} - - {root.lastFaultedEpoch - ? root.lastFaultedEpoch.toLocaleString() - : 'Never'} -
- No roots found. -
- {renderRootsPagination(totalRoots)} -
-
-
- { - setActiveTab(value) - setCurrentPage(1) - }} - > - - Transactions - Event Logs - - - -
- -
- -
- - - - - - - - - - - - - {filteredTransactions.length > 0 ? ( - filteredTransactions.map((tx) => ( - - - - - - - - - )) - ) : ( - - - - )} - -
HashMethodStatusValueHeightTime
- - {tx.messageId && ( - - )} - {tx.method} - - {tx.status ? 'Success' : 'Failed'} - - {formatTokenAmount(tx.value)}{tx.height}{formatDate(tx.createdAt)}
- No transactions found. -
-
- {renderPagination(totalTransactions)} -
- - -
- -
- -
- - - - - - - - - - - - {filteredEventLogs.length > 0 ? ( - filteredEventLogs.map((log) => ( - - - - - - - - )) - ) : ( - - - - )} - -
Event NameTransaction HashHeightTimeData
{log.eventName} - - {log.transactionHash} - - {log.blockNumber} - {new Date(log.createdAt).toLocaleString()} - -
-
-
- {(() => { - try { - const jsonData = - typeof log.data === 'string' - ? JSON.parse(log.data) - : log.data - - return - } catch (e) { - return ( - - Invalid JSON data - - ) - } - })()} -
-
-
-
- No events found. -
-
- {renderPagination(totalEventLogs)} -
-
-
- - +
-

- Last 7 Days Proving Heat Map - - ({heatmapRoots.length} of {totalRoots} roots) - -

+
+ +

+ Proof Activity Heatmap +

+
-
-
-
-
- Not challenged -
-
-
- Successful proof -
-
-
- Faulted proof -
-
- {isLoadingAllRoots ? ( -
-
-
- ) : ( - - )} -
- {!isLoadingAllRoots && heatmapRoots.length < totalRoots && ( -
- Showing all {totalRoots} roots from the last 7 days -
- )} +
+ {isLoadingAllRoots ? ( + + ) : ( + + )} +
+ +
+ +
+ + + + Transactions + + + + Event Logs + + + + Roots + + +
+ + +
+ +
+
+ + + + + + + + + + + {filteredTransactions.map((tx) => ( + + + + + + + ))} + +
+ Transaction Hash + + Method + + Status + + Timestamp +
+ + {tx.hash.slice(0, 10)}...{tx.hash.slice(-8)} + + {tx.method} + + {Number(tx.status) === 1 ? 'Success' : 'Failed'} + + + {formatDate(tx.createdAt.toString())} +
+ {filteredTransactions.length === 0 && ( +
+

No transactions found

+
+ )} +
+ {renderPagination(totalTransactions)} +
+ + +
+ +
+
+ {filteredEventLogs.map((log) => ( +
+
+ + {log.eventName} + + + View Transaction + +
+
+ {log.data.value && ( +
+ Amount: {formatTokenAmount(log.data.value)} +
+ )} + +
+
+ ))} + {filteredEventLogs.length === 0 && ( +
+

No event logs found

+
+ )} +
+ {renderPagination(totalEventLogs)} +
+ + +
+ {roots.map((root) => ( +
+
+
+
+ Root ID +
+
{root.rootId}
+
+
+
+ Status +
+ 0 + ? 'bg-green-100 text-green-800' + : 'bg-gray-100 text-gray-800' + }`} + > + {root.totalProofsSubmitted > 0 ? 'Proved' : 'Unproved'} + +
+
+
+ Data Size +
+
+ {formatDataSize(root.size?.toString())} +
+
+
+
+ Last Update +
+
+ {formatDate(root.createdAt)} +
+
+
+
+ ))} + {roots.length === 0 && ( +
+

No roots found

+
+ )} +
+ {renderRootsPagination(totalRoots)} +
+
+
) } diff --git a/client/src/pages/ProofSets.tsx b/client/src/pages/ProofSets.tsx index e606598..c240b7c 100644 --- a/client/src/pages/ProofSets.tsx +++ b/client/src/pages/ProofSets.tsx @@ -2,7 +2,16 @@ import { useEffect, useState } from 'react' import { Link } from 'react-router-dom' import { getProofSets, ProofSet } from '@/api/apiService' import { Input } from '@/components/ui/input' -import { Search } from 'lucide-react' +import { + Search, + Database, + CheckCircle, + Clock, + HardDrive, + User, + Shield, + Calendar, +} from 'lucide-react' import { Pagination } from '@/components/ui/pagination' import { useDebounce } from '@/hooks/useDebounce' import { formatDataSize } from '@/utility/helper' @@ -49,63 +58,112 @@ export const ProofSets = () => { if (loading) { return (
-
Loading...
+
+
+
+
+
) } return ( -
-
-

Proof Sets

-
- - setSearchQuery(e.target.value)} - className="pl-8" - /> +
+
+
+
+

Proof Sets

+

+ Browse and search through all proof sets +

+
+
+ + setSearchQuery(e.target.value)} + className="pl-10 h-10 bg-white border-gray-200 hover:border-gray-300 focus:border-blue-500 transition-colors" + /> +
-
-
- +
- - - - - - - - - + + + + + + + + + - + {proofSets.map((proofSet) => ( - - + - - - - + - - - ))}
Proof Set IDOwnerStatusTotal RootsProved RootsData SizeLast Proof EpochNext Challenge
+
+ + Proof Set ID +
+
+
+ + Owner +
+
+
+ + Status +
+
+ Total Roots + +
+ + Proved Roots +
+
+
+ + Data Size +
+
+
+ + Last Proof +
+
+
+ + Next Challenge +
+
+
{proofSet.setId} + {proofSet.owner} + { {proofSet.isActive ? 'Active' : 'Inactive'} {proofSet.totalRoots} + + {proofSet.totalRoots} + {proofSet.totalProvedRoots || 0} + {formatDataSize(proofSet.totalDataSize)} - - {proofSet.lastProvenEpoch - ? proofSet.lastProvenEpoch.toLocaleString() - : 'Never'} - + + {proofSet.lastProvenEpoch + ? proofSet.lastProvenEpoch.toLocaleString() + : 'Never'} - - {proofSet.nextChallengeEpoch - ? proofSet.nextChallengeEpoch.toLocaleString() - : 'Never'} - + + {proofSet.nextChallengeEpoch + ? proofSet.nextChallengeEpoch.toLocaleString() + : 'Never'}
+ {proofSets.length === 0 && !loading && ( +
+

No proof sets found

+
+ )} {totalProofSets > ITEMS_PER_PAGE && ( - +
+ +
)}
diff --git a/client/src/pages/ProviderDetails.tsx b/client/src/pages/ProviderDetails.tsx index a7718ad..5c6ed3b 100644 --- a/client/src/pages/ProviderDetails.tsx +++ b/client/src/pages/ProviderDetails.tsx @@ -9,6 +9,7 @@ import { } from '@/api/apiService' import { Pagination } from '@/components/ui/pagination' import { LineChart, Line, CartesianGrid, XAxis, YAxis } from 'recharts' +import { Spinner } from '@/components/ui/spinner' import { ChartContainer, ChartTooltip, @@ -81,7 +82,9 @@ export const ProviderDetails = () => { fetchData() }, [providerId, activityType, currentPage]) - if (loading || !provider) return
Loading...
+ if (loading || !provider) { + return + } return (
diff --git a/client/src/pages/Providers.tsx b/client/src/pages/Providers.tsx index 08bdf46..6dafe04 100644 --- a/client/src/pages/Providers.tsx +++ b/client/src/pages/Providers.tsx @@ -2,7 +2,7 @@ import { useEffect, useState } from 'react' import { Link } from 'react-router-dom' import { getProviders, Provider } from '@/api/apiService' import { Input } from '@/components/ui/input' -import { Search } from 'lucide-react' +import { Search, HardDrive, Database, AlertTriangle, Clock } from 'lucide-react' import { Pagination } from '@/components/ui/pagination' import { useDebounce } from '@/hooks/useDebounce' import { formatDate, formatDataSize } from '@/utility/helper' @@ -47,58 +47,97 @@ export const Providers = () => { if (loading) { return (
-
Loading...
+
+
+
+
+
) } return ( -
-
-

Storage Providers

-
- - setSearchQuery(e.target.value)} - className="pl-8" - /> +
+
+
+
+

+ Storage Providers +

+

+ Browse and search through all storage providers +

+
+
+ + setSearchQuery(e.target.value)} + className="pl-10 h-10 bg-white border-gray-200 hover:border-gray-300 focus:border-blue-500 transition-colors" + /> +
-
-
- +
- - - - - - - - - + + + + + + + + + - + {providers.map((provider) => ( - - - - - - + + - - + + ))}
Provider IDStatusData SizeProof SetsTotal RootsFaultsFirst SeenLast Seen
+ Provider ID + + Status + +
+ + Data Size +
+
+
+ + Proof Sets +
+
+ Total Roots + +
+ + Faults +
+
+
+ + First Seen +
+
+ Last Seen +
+ {provider.providerId} + 0 ? 'bg-green-100 text-green-800' : 'bg-gray-100 text-gray-800' @@ -107,14 +146,18 @@ export const Providers = () => { {provider.activeProofSets > 0 ? 'Active' : 'Inactive'} + {formatDataSize(provider.totalDataSize)} {provider.proofSetIds.length}{provider.numRoots} + + {provider.proofSetIds.length} + + {provider.numRoots} + 0 ? 'bg-red-100 text-red-800' : 'bg-green-100 text-green-800' @@ -123,19 +166,30 @@ export const Providers = () => { {provider.totalFaultedPeriods} {formatDate(provider.firstSeen)}{formatDate(provider.lastSeen)} + {formatDate(provider.firstSeen)} + + {formatDate(provider.lastSeen)} +
+ {providers.length === 0 && !loading && ( +
+

No providers found

+
+ )} {totalProviders > ITEMS_PER_PAGE && ( - +
+ +
)}