Skip to content

Commit 2da8651

Browse files
Reskin UI (#141)
1 parent 06946a4 commit 2da8651

39 files changed

+2116
-681
lines changed

block-explorer/app/page.tsx

Lines changed: 84 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,19 @@
1-
import Container from '@/components/container';
2-
import LatestBlocks from '@/components/homepage/latest-blocks';
3-
import LatestExtrinsics from '@/components/homepage/latest-extrinsics';
4-
import LatestTransactions from '@/components/homepage/latest-transactions';
1+
import { LatestBlocks } from '@/components/homepage/latest-blocks';
2+
import { LatestExtrinsics } from '@/components/homepage/latest-extrinsics';
3+
import { LatestTransactions } from '@/components/homepage/latest-transactions';
4+
import { Search } from '@/components/homepage/search.tsx';
55
import TargetTimeCountdown from '@/components/target-time-countdown';
6-
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
76
import { ApiCommand, request } from '@/lib/api';
87
import { formatNumber, handleRequestResult } from '@/lib/utils';
9-
import { ArrowLeftRight, Clock, Pencil, Wallet } from 'lucide-react';
8+
import { RiArrowLeftRightLine, RiArrowRightUpLine, RiPencilLine, RiTimerLine, RiWalletLine } from '@remixicon/react';
109

1110
export const dynamic = 'force-dynamic';
1211

1312
const getData = async () => {
1413
const [blocksResponse, transactionsResponse, extrinsicsResponse, chainSummaryResponse] = await Promise.all([
1514
request(ApiCommand.getBlocks, { page: 1, limit: 10 }),
1615
request(ApiCommand.getTransactions, { page: 1, limit: 5 }),
17-
request(ApiCommand.getExtrinsics, { page: 1, limit: 5 }),
16+
request(ApiCommand.getExtrinsics, { page: 1, limit: 10 }),
1817
request(ApiCommand.getChainSummary),
1918
]);
2019

@@ -29,63 +28,89 @@ const getData = async () => {
2928
export default async function IndexPage() {
3029
const { latestBlocks, latestExtrinsics, latestTransactions, chainSummary } = await getData();
3130

31+
const stats = [
32+
{ title: 'Target Block Time', value: '4s', icon: RiTimerLine },
33+
{
34+
title: 'Signed Extrinsics',
35+
value: Number(chainSummary?.signedExtrinsics || 0),
36+
icon: RiPencilLine,
37+
},
38+
{
39+
title: 'Total Transactions',
40+
value: Number(chainSummary?.evmTransactions || 0),
41+
icon: RiArrowLeftRightLine,
42+
},
43+
{
44+
title: 'Wallet Addresses',
45+
value: Number(chainSummary?.addresses || 0),
46+
icon: RiWalletLine,
47+
},
48+
];
49+
3250
return (
33-
<Container>
34-
<div className="flex flex-col gap-8">
35-
<section className="grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-4">
36-
{[
37-
{ title: 'Target Block Time', value: '4s', icon: Clock },
38-
{
39-
title: 'Signed Extrinsics',
40-
value: Number(chainSummary?.signedExtrinsics || 0),
41-
icon: Pencil,
42-
},
43-
{
44-
title: 'Total Transactions',
45-
value: Number(chainSummary?.evmTransactions || 0),
46-
icon: ArrowLeftRight,
47-
},
48-
{
49-
title: 'Wallet Addresses',
50-
value: Number(chainSummary?.addresses || 0),
51-
icon: Wallet,
52-
},
53-
].map((stat, _) => {
54-
const Icon = stat.icon;
55-
return (
56-
<div key={_}>
57-
<Card>
58-
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-4">
59-
<Icon className="size-5 text-muted-foreground" />
60-
<CardTitle className="text-xs uppercase text-muted-foreground">{stat.title}</CardTitle>
61-
</CardHeader>
62-
<CardContent>
63-
<div className="text-2xl font-bold">
64-
{stat?.title === 'Target Block Time' ? (
65-
<TargetTimeCountdown />
66-
) : (
67-
formatNumber(typeof stat.value === 'string' ? Number(stat.value) : stat.value)
68-
)}
69-
</div>
70-
</CardContent>
71-
</Card>
51+
<div style={{ background: `url('/home-page-background.png') top / 100% no-repeat` }}>
52+
<div className="container grid grid-cols-2 gap-4 px-4 pb-6 pt-8 md:gap-6 md:px-6 md:pt-10 lg:px-8 xl:px-14 xl:pb-14 xl:pt-8">
53+
<section className="col-span-2 flex flex-col gap-6 xl:flex-row xl:items-center xl:gap-0 xl:py-4">
54+
<div className="flex flex-1 flex-col gap-4">
55+
<h1 className="text-[32px]/[44px] font-bold">The Root Network Explorer</h1>
56+
<div className="w-full xl:max-w-[650px]">
57+
<Search />
58+
</div>
59+
</div>
60+
<div
61+
className="link-box rounded-[16px] p-px"
62+
style={{ background: 'linear-gradient(90deg, #8F9AE9 0%, #E0BC95 50%, #F78F50 100%)' }}
63+
>
64+
<div className="flex flex-col gap-1 rounded-[15px] bg-white p-6 pt-5 hover:bg-api-portal-banner dark:bg-black xl:max-w-[330px]">
65+
<div className="flex items-center justify-between">
66+
<a
67+
href="https://build.rootscan.io/"
68+
target="_blank"
69+
className="link-overlay text-[18px]/[28px] font-semibold"
70+
>
71+
API Portal
72+
</a>
73+
<RiArrowRightUpLine className="size-6" />
7274
</div>
73-
);
74-
})}
75+
<p className="text-xs font-normal text-foreground/60">
76+
Kickstart your development on The Root Network with Rootscan’s{' '}
77+
<strong className="text-foreground">API</strong> and <strong className="text-foreground">RPC</strong>{' '}
78+
services
79+
</p>
80+
</div>
81+
</div>
7582
</section>
7683

77-
<section className="grid grid-cols-12 gap-6">
78-
<div className="col-span-full">
79-
<LatestBlocks latestBlocks={latestBlocks} />
80-
</div>
81-
<div className="col-span-full lg:col-span-8">
82-
<LatestTransactions latestTransactions={latestTransactions} />
83-
</div>
84-
<div className="col-span-full lg:col-span-4">
85-
<LatestExtrinsics latestExtrinsics={latestExtrinsics} />
86-
</div>
84+
<section className="col-span-2 grid grid-cols-1 gap-px overflow-hidden rounded-[16px] bg-secondary md:grid-cols-2 xl:grid-cols-4">
85+
{stats.map(({ icon: Icon, ...stat }, i) => (
86+
<div key={i} className="flex flex-col gap-2 bg-white p-4 dark:bg-black md:gap-3 md:p-6">
87+
<div className="flex items-center gap-2 text-muted-foreground">
88+
<Icon className="size-5" />
89+
<span className="text-xs font-bold uppercase">{stat.title}</span>
90+
</div>
91+
<div className="text-2xl font-semibold">
92+
{stat?.title === 'Target Block Time' ? (
93+
<TargetTimeCountdown />
94+
) : (
95+
formatNumber(typeof stat.value === 'string' ? Number(stat.value) : stat.value)
96+
)}
97+
</div>
98+
</div>
99+
))}
87100
</section>
101+
102+
<div className="col-span-2 xl:col-span-1">
103+
<LatestBlocks latestBlocks={latestBlocks} />
104+
</div>
105+
106+
<div className="col-span-2 xl:col-span-1">
107+
<LatestExtrinsics latestExtrinsics={latestExtrinsics} />
108+
</div>
109+
110+
<div className="col-span-2">
111+
<LatestTransactions latestTransactions={latestTransactions} />
112+
</div>
88113
</div>
89-
</Container>
114+
</div>
90115
);
91116
}

block-explorer/components/address-display.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { getAddressName, knownAddressNames } from '@/lib/constants/knownAddresses';
22
import { cn } from '@/lib/utils';
3-
import { FileText } from 'lucide-react';
3+
import { RiFileList3Line } from '@remixicon/react';
44
import Link from 'next/link';
55
import { Address, getAddress, isAddress } from 'viem';
66

@@ -43,10 +43,10 @@ export default function AddressDisplay({
4343
const isFuturepass = address?.toLowerCase()?.startsWith('0xffffffff');
4444

4545
return (
46-
<div className={cn(['flex items-center gap-2', className ? className : ''])}>
46+
<div className={cn(['flex font-semibold items-center gap-1.5 text-sm', className ? className : ''])}>
4747
{isContract ? (
4848
<Tooltip text="EVM Contract" asChild>
49-
<FileText className="size-4 text-muted-foreground" />
49+
<RiFileList3Line className="size-4 text-muted-foreground" />
5050
</Tooltip>
5151
) : null}
5252
{isFuturepass ? (

block-explorer/components/container.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import * as React from 'react';
2+
13
import { cn } from '@/lib/utils';
24

35
export default function Container({ children, className }: { children: React.ReactNode; className?: string }) {
@@ -7,3 +9,7 @@ export default function Container({ children, className }: { children: React.Rea
79
</div>
810
);
911
}
12+
13+
export function ContainerV2({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {
14+
return <div className={cn('container px-4 md:px-6 xl:px-14 lg:px-8', className)} {...props} />;
15+
}

block-explorer/components/copy-button.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import * as React from 'react';
44

55
import { cn } from '@/lib/utils';
6-
import { Check, Copy } from 'lucide-react';
6+
import { RiCheckLine, RiFileCopyLine } from '@remixicon/react';
77

88
const copyToClipboardWithMeta = async (value: string) => {
99
navigator.clipboard.writeText(value);
@@ -21,16 +21,16 @@ export const CopyButton = ({ value, className, ...props }: { value: string; clas
2121
return (
2222
<div
2323
className={cn(
24-
'text-muted-foreground relative z-10 ml-1 inline-flex cursor-pointer items-center justify-center rounded-md border-neutral-200 text-sm font-medium transition-all focus:outline-none',
24+
'text-muted-foreground relative z-10 inline-flex cursor-pointer items-center justify-center rounded-md border-neutral-200 text-sm font-medium transition-all focus:outline-none',
2525
className,
2626
)}
2727
{...props}
2828
>
2929
{hasCopied ? (
30-
<Check className="size-4" />
30+
<RiCheckLine className="size-3.5" />
3131
) : (
32-
<Copy
33-
className="size-4"
32+
<RiFileCopyLine
33+
className="size-3.5"
3434
onClick={() => {
3535
copyToClipboardWithMeta(value);
3636
setHasCopied(true);
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
'use client';
2+
3+
import { ReactNode, useState } from 'react';
4+
5+
import { Button } from '@/components/ui/button.tsx';
6+
import { CardContent } from '@/components/ui/v2/card.tsx';
7+
import { cn } from '@/lib/utils.ts';
8+
import { RiArrowDownSLine } from '@remixicon/react';
9+
10+
type DataItemCardProps = {
11+
iconSrc: string;
12+
children: ReactNode;
13+
summary: Array<[string, string | number | ReactNode]>;
14+
};
15+
export const DataItemCard = (props: DataItemCardProps) => {
16+
const { iconSrc, summary, children } = props;
17+
18+
const [openSummary, setOpenSummary] = useState(false);
19+
20+
return (
21+
<CardContent className="relative flex flex-col gap-3 pl-6 lg:flex-row lg:items-center lg:gap-6">
22+
<Button
23+
variant="secondary"
24+
size="sm"
25+
className={cn('lg:hidden absolute h-6 right-[16px] top-[18px] p-1', openSummary && 'rotate-180')}
26+
onClick={() => setOpenSummary((prev) => !prev)}
27+
>
28+
<RiArrowDownSLine className="size-4" />
29+
</Button>
30+
<div className="flex flex-1 items-center gap-4">
31+
<div className="shrink-0 rounded-[12px] bg-[#F5F5F5] p-3 dark:bg-[#1C1C1C]">
32+
<img src={iconSrc} alt="" className="size-10" />
33+
</div>
34+
<div className="flex flex-col">{children}</div>
35+
</div>
36+
<div
37+
className={cn(
38+
'hidden min-w-[200px] flex-col gap-1 rounded-[12px] border border-[#F1F1F1] p-3 dark:border-[#1C1C1C] lg:flex',
39+
openSummary && 'flex',
40+
)}
41+
>
42+
{summary.map(([label, value], i) => (
43+
<div key={i} className="flex items-center justify-between gap-4">
44+
<p className="text-xs text-[#737373]">{label}</p>
45+
{typeof value === 'string' || typeof value === 'number' ? (
46+
<p className="text-xs font-semibold">{value}</p>
47+
) : (
48+
value
49+
)}
50+
</div>
51+
))}
52+
</div>
53+
</CardContent>
54+
);
55+
};

0 commit comments

Comments
 (0)