From 9b420236e319c5752340523147a23475f011ffa2 Mon Sep 17 00:00:00 2001 From: developerayo Date: Fri, 20 Sep 2024 14:24:55 +0100 Subject: [PATCH 1/6] handle pagination/blockfeed height --- app/api/latestBlock/route.ts | 2 +- app/page.tsx | 119 ++++++++++++++++++++++++++++++++--- components/blockFeed.tsx | 2 + 3 files changed, 115 insertions(+), 8 deletions(-) diff --git a/app/api/latestBlock/route.ts b/app/api/latestBlock/route.ts index ee994cb..e007409 100644 --- a/app/api/latestBlock/route.ts +++ b/app/api/latestBlock/route.ts @@ -25,4 +25,4 @@ export async function POST(request: Request) { console.error('Error fetching block data:', error); return NextResponse.json({ error: 'Failed to fetch block data' }, { status: 500 }); } -} \ No newline at end of file +} diff --git a/app/page.tsx b/app/page.tsx index 32de37e..52948d5 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -2,6 +2,13 @@ import React, { useState, useEffect, useCallback, useRef } from 'react'; import { ChevronLeft, ChevronRight, Box } from 'lucide-react'; import { Button } from "@/components/ui/button"; +import { + Pagination, + PaginationContent, + PaginationEllipsis, + PaginationItem, + PaginationLink, +} from "@/components/ui/pagination" import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import BlockTable from '@/components/blockFeed'; import { getLatestBlock, getBlockByHeight, Block } from '@/utils/api'; @@ -81,6 +88,42 @@ export default function Home() { } }, [currentPage, latestBlock, fetchBlocks]); + const handlePages = useCallback(() => { + const pageNumbers = []; + const maxVisiblePages = 5; + const totalPages = latestBlock ? Math.ceil(latestBlock.height / PAGE_SIZE) : 1; + + if (totalPages <= maxVisiblePages) { + for (let i = 1; i <= totalPages; i++) { + pageNumbers.push(i); + } + } else { + if (currentPage <= 3) { + for (let i = 1; i <= 4; i++) { + pageNumbers.push(i); + } + pageNumbers.push('ellipsis'); + pageNumbers.push(totalPages); + } else if (currentPage >= totalPages - 2) { + pageNumbers.push(1); + pageNumbers.push('ellipsis'); + for (let i = totalPages - 3; i <= totalPages; i++) { + pageNumbers.push(i); + } + } else { + pageNumbers.push(1); + pageNumbers.push('ellipsis'); + pageNumbers.push(currentPage - 1); + pageNumbers.push(currentPage); + pageNumbers.push(currentPage + 1); + pageNumbers.push('ellipsis'); + pageNumbers.push(totalPages); + } + } + + return pageNumbers; + }, [currentPage, latestBlock]); + const handlePrevPage = useCallback(() => { if (currentPage > 1) { setCurrentPage(prev => prev - 1); @@ -88,7 +131,8 @@ export default function Home() { }, [currentPage]); const handleNextPage = useCallback(() => { - if (latestBlock && (currentPage * PAGE_SIZE) < latestBlock.height) { + const totalPages = latestBlock ? Math.ceil(latestBlock.height / PAGE_SIZE) : 1; + if (currentPage < totalPages) { setCurrentPage(prev => prev + 1); } }, [currentPage, latestBlock]); @@ -109,8 +153,6 @@ export default function Home() { setTimeout(() => setCopiedField(null), 2000); }; - const totalPages = latestBlock ? Math.ceil(latestBlock.height / PAGE_SIZE) : 1; - if (error) return
Error: {error}
; return ( @@ -139,21 +181,84 @@ export default function Home() { onClick={handlePrevPage} disabled={currentPage === 1} aria-label="Previous page" + className="hidden sm:inline-flex" > Previous - - Page {currentPage} of {totalPages} - + +
+ + + {handlePages().map((page, index) => ( + + {page === "ellipsis" ? ( + + ) : ( + setCurrentPage(page as number)} + isActive={currentPage === page} + > + {page} + + )} + + ))} + + +
+
+ + + {handlePages().map((page, index) => ( + + {page === "ellipsis" ? ( + + ) : ( + setCurrentPage(page as number)} + isActive={currentPage === page} + > + {page} + + )} + + ))} + + +
+ diff --git a/components/blockFeed.tsx b/components/blockFeed.tsx index 76f965a..e4fe7e7 100644 --- a/components/blockFeed.tsx +++ b/components/blockFeed.tsx @@ -56,6 +56,7 @@ const BlockTable: React.FC = ({ } return ( +
@@ -195,6 +196,7 @@ const BlockTable: React.FC = ({
+
); }; From 4e516756621cd92e5f6905b726540a4cda649573 Mon Sep 17 00:00:00 2001 From: developerayo Date: Fri, 20 Sep 2024 19:08:45 +0100 Subject: [PATCH 2/6] feat: find transaction by `hash` --- .gitignore | 2 + app/page.tsx | 4 + components/ui/SearchTxnHash.tsx | 168 ++++++++++++++++++++++++++++++++ components/ui/input.tsx | 20 ++++ package.json | 2 +- utils/api.ts | 19 +++- 6 files changed, 213 insertions(+), 2 deletions(-) create mode 100644 components/ui/SearchTxnHash.tsx create mode 100644 components/ui/input.tsx diff --git a/.gitignore b/.gitignore index fd3dbb5..768fc6c 100644 --- a/.gitignore +++ b/.gitignore @@ -27,6 +27,8 @@ yarn-error.log* # local env files .env*.local +.env +.env.example # vercel .vercel diff --git a/app/page.tsx b/app/page.tsx index 52948d5..bbd78ed 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -11,6 +11,7 @@ import { } from "@/components/ui/pagination" import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import BlockTable from '@/components/blockFeed'; +import SearchTransactionHash from '@/components/ui/SearchTxnHash'; import { getLatestBlock, getBlockByHeight, Block } from '@/utils/api'; const PAGE_SIZE = 25; @@ -164,6 +165,9 @@ export default function Home() { HyperSDK Explorer + + +
diff --git a/components/ui/SearchTxnHash.tsx b/components/ui/SearchTxnHash.tsx new file mode 100644 index 0000000..e3784db --- /dev/null +++ b/components/ui/SearchTxnHash.tsx @@ -0,0 +1,168 @@ +import React, { useState, useEffect } from 'react'; +import { Search, X, Clock, CheckCircle, ChevronUp, ChevronDown, XCircle, Cpu, Coins } from 'lucide-react'; +import { Input } from "@/components/ui/input"; +import { Button } from "@/components/ui/button"; +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; +import { getTx, Transaction } from '@/utils/api'; +import { motion, AnimatePresence } from 'framer-motion'; + +const SearchTransactionHash: React.FC = () => { + const [searchHash, setsearchHash] = useState(''); + const [transaction, setTransaction] = useState(null); + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(null); + const [isMinimized, setIsMinimized] = useState(false); + + useEffect(() => { + if (transaction) { + setIsMinimized(false); + } + }, [transaction]); + + const handleSearch = async () => { + setIsLoading(true); + setError(null); + setTransaction(null); + try { + const result = await getTx(searchHash); + if (result) { + setTransaction(result); + } else { + setError('Your search did not match any records.'); + } + } catch (err) { + // console.log('Error fetching transaction:', err); + } finally { + setIsLoading(false); + } + }; + + const handleClose = () => { + setTransaction(null); + setError(null); + }; + + const toggleMinimize = () => { + setIsMinimized(!isMinimized); + }; + + return ( +
+
+ ) => + setsearchHash(e.target.value) + } + /> + +
+ {error &&

{error}

} + + {transaction && ( + + + + Transaction Details +
+ + +
+
+ + {!isMinimized && ( + + +
+
+

+ + Fee +

+

+ {transaction.fee} +

+
+
+

+ {transaction.success ? ( + + ) : ( + + )} + Success +

+

+ {transaction.success ? "Yes" : "No"} +

+
+
+

+ + Timestamp +

+

+ {new Date(transaction.timestamp).toLocaleString()} +

+
+
+

+ + Units +

+

+ [{transaction.units.join(", ")}] +

+
+
+
+
+ )} +
+
+
+ )} +
+
+ ); +}; + +export default SearchTransactionHash; \ No newline at end of file diff --git a/components/ui/input.tsx b/components/ui/input.tsx new file mode 100644 index 0000000..6e9103f --- /dev/null +++ b/components/ui/input.tsx @@ -0,0 +1,20 @@ +import * as React from "react" + +export interface InputProps + extends React.InputHTMLAttributes {} + +const Input = React.forwardRef( + ({ className, type, ...props }, ref) => { + return ( + + ) + } +) +Input.displayName = "Input" + +export { Input } \ No newline at end of file diff --git a/package.json b/package.json index 9f9ae2e..2a96098 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,7 @@ "axios": "^1.7.7", "class-variance-authority": "^0.7.0", "clsx": "^2.1.1", - "framer-motion": "^11.5.4", + "framer-motion": "^11.5.5", "lucide-react": "^0.441.0", "next": "14.2.11", "react": "^18", diff --git a/utils/api.ts b/utils/api.ts index 3417c10..ed36ef9 100644 --- a/utils/api.ts +++ b/utils/api.ts @@ -39,6 +39,8 @@ export const getLatestBlock = async (): Promise => { method: 'indexer.getLatestBlock', params: {}, }); + // Log for now + console.log('Block res:', JSON.stringify(response.data, null, 2)); return response.data.result.block; }; @@ -60,4 +62,19 @@ export const getBlock = async (blockID: string): Promise => { params: { blockID }, }); return response.data.result.block; -}; \ No newline at end of file +}; + +export const getTx = async (txID: string): Promise => { + try { + const response = await api.post('/latestBlock', { + jsonrpc: '2.0', + id: 1, + method: 'indexer.getTx', + params: { txID }, + }); + return response.data.result; + } catch (error) { + console.error('Error fetching transaction:', error); + throw error; + } +}; From ad54cba5e12c5746d690e3a7d59d2d612c21d512 Mon Sep 17 00:00:00 2001 From: developerayo Date: Fri, 20 Sep 2024 19:35:45 +0100 Subject: [PATCH 3/6] type error --- components/ui/SearchTxnHash.tsx | 13 ++++++++++--- utils/api.ts | 3 +++ 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/components/ui/SearchTxnHash.tsx b/components/ui/SearchTxnHash.tsx index e3784db..251ea0d 100644 --- a/components/ui/SearchTxnHash.tsx +++ b/components/ui/SearchTxnHash.tsx @@ -16,6 +16,7 @@ const SearchTransactionHash: React.FC = () => { useEffect(() => { if (transaction) { setIsMinimized(false); + console.log('Txn:', transaction); } }, [transaction]); @@ -112,7 +113,7 @@ const SearchTransactionHash: React.FC = () => { Fee

- {transaction.fee} + {transaction.fee ?? "N/A"}

@@ -140,7 +141,11 @@ const SearchTransactionHash: React.FC = () => { Timestamp

- {new Date(transaction.timestamp).toLocaleString()} + {transaction.timestamp + ? new Date( + Number(transaction.timestamp) + ).toLocaleString() + : "N/A"}

@@ -149,7 +154,9 @@ const SearchTransactionHash: React.FC = () => { Units

- [{transaction.units.join(", ")}] + {transaction.units + ? `[${transaction.units.join(", ")}]` + : "N/A"}

diff --git a/utils/api.ts b/utils/api.ts index ed36ef9..52433d8 100644 --- a/utils/api.ts +++ b/utils/api.ts @@ -22,6 +22,9 @@ export interface Transaction { signer: string[]; signature: string[]; }; + success?: boolean; + fee?: number; + units?: number[]; } export interface Block { From ea0e0c0ec10b837bc815772ea94fee633fccabe3 Mon Sep 17 00:00:00 2001 From: developerayo Date: Fri, 20 Sep 2024 19:45:53 +0100 Subject: [PATCH 4/6] enter --- components/ui/SearchTxnHash.tsx | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/components/ui/SearchTxnHash.tsx b/components/ui/SearchTxnHash.tsx index 251ea0d..9c13587 100644 --- a/components/ui/SearchTxnHash.tsx +++ b/components/ui/SearchTxnHash.tsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect } from 'react'; +import React, { useState, useEffect, KeyboardEvent } from 'react'; import { Search, X, Clock, CheckCircle, ChevronUp, ChevronDown, XCircle, Cpu, Coins } from 'lucide-react'; import { Input } from "@/components/ui/input"; import { Button } from "@/components/ui/button"; @@ -21,6 +21,7 @@ const SearchTransactionHash: React.FC = () => { }, [transaction]); const handleSearch = async () => { + if (!searchHash.trim()) return; setIsLoading(true); setError(null); setTransaction(null); @@ -32,12 +33,18 @@ const SearchTransactionHash: React.FC = () => { setError('Your search did not match any records.'); } } catch (err) { - // console.log('Error fetching transaction:', err); + setError('Error'); } finally { setIsLoading(false); } }; + const handleEnter = (e: KeyboardEvent) => { + if (e.key === 'Enter') { + handleSearch(); + } + }; + const handleClose = () => { setTransaction(null); setError(null); @@ -52,11 +59,12 @@ const SearchTransactionHash: React.FC = () => {
) => setsearchHash(e.target.value) } + onKeyDown={handleEnter} />