Skip to content

Commit bbbcec9

Browse files
C0mberryrogaldh
andauthored
fix: UI improvements (#886)
## Description - fixing header active tab displaying - adding footer - removing ToS from header - adding disclaimer for account inputs - not allowing copying of the account data for accounts with 0 size - putting CU Profiling under the logs section - logic to copy/download the account data ## Type of change - [x] Bug fix - [x] New feature ## Screenshots <img width="1156" height="373" alt="Screenshot 2026-03-18 at 21 03 50" src="https://github.com/user-attachments/assets/97c51d8d-7f46-4fe3-a0a4-3bdc96e36e34" /> <img width="954" height="833" alt="Screenshot 2026-03-20 at 16 14 27" src="https://github.com/user-attachments/assets/39d6d717-b0fd-4f77-ac1c-8b1e62f6549b" /> <img width="964" height="748" alt="Screenshot 2026-03-20 at 17 13 35" src="https://github.com/user-attachments/assets/c8856b30-0e20-43ab-92a6-d6d88044230c" /> ## Related Issues [HOO-356](https://linear.app/solana-fndn/issue/HOO-356/various-ui-improvements) ## Checklist - [x] My code follows the project's style guidelines - [x] All tests pass locally and in CI - [x] I have run `build:info` script to update build information - [x] CI/CD checks pass - [x] I have included screenshots for protocol screens (if applicable) --------- Co-authored-by: Sergo <rogaldh@radsh.red>
1 parent a60f28d commit bbbcec9

File tree

10 files changed

+131
-54
lines changed

10 files changed

+131
-54
lines changed

app/components/Footer.tsx

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
'use client';
2+
3+
import { useClusterPath } from '@utils/url';
4+
import Link from 'next/link';
5+
6+
export function Footer() {
7+
const tosPath = useClusterPath({ pathname: '/tos' });
8+
const cookiesPath = useClusterPath({ pathname: '/cookies' });
9+
10+
return (
11+
<footer className="e-border-0 e-border-t e-border-solid e-border-neutral-800">
12+
<div className="container">
13+
<div className="e-flex e-flex-col e-gap-3 e-py-3 md:e-flex-row md:e-items-center md:e-justify-between md:e-py-5">
14+
<span className="e-text-xs e-font-medium e-uppercase e-tracking-[0.72px] e-text-heavy-metal-400">
15+
Solana explorer <span>(Beta)</span>
16+
</span>
17+
<nav className="e-flex e-items-center e-gap-5 e-text-xs e-tracking-[-0.24px] e-text-accent-700">
18+
<Link className="e-transition-colors hover:e-text-accent-500" href={tosPath}>
19+
Terms of Services
20+
</Link>
21+
<Link className="e-transition-colors hover:e-text-accent-500" href={cookiesPath}>
22+
Cookies policy
23+
</Link>
24+
</nav>
25+
</div>
26+
</div>
27+
</footer>
28+
);
29+
}

app/components/Navbar.tsx

Lines changed: 8 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,14 @@ export function Navbar({ children }: INavbarProps) {
1818
const [navOpened, navHandlers] = useDisclosure(false);
1919
const homePath = useClusterPath({ pathname: '/' });
2020
const featureGatesPath = useClusterPath({ pathname: '/feature-gates' });
21-
const tosPath = useClusterPath({ pathname: '/tos' });
2221
const supplyPath = useClusterPath({ pathname: '/supply' });
2322
const programsPath = useClusterPath({ pathname: '/verified-programs' });
2423
const inspectorPath = useClusterPath({ pathname: '/tx/inspector' });
2524
const selectedLayoutSegment = useSelectedLayoutSegment();
2625
const selectedLayoutSegments = useSelectedLayoutSegments();
26+
27+
const linkClass = (active: boolean) => `nav-link${active ? ' active' : ''}`;
28+
2729
return (
2830
<nav className="navbar navbar-expand-lg navbar-light">
2931
<div className="container px-4">
@@ -46,48 +48,35 @@ export function Navbar({ children }: INavbarProps) {
4648
<ul className="navbar-nav me-auto">
4749
<li className="nav-item">
4850
<Link
49-
className={`nav-link${selectedLayoutSegment === 'feature-gates' ? ' active' : ''}`}
51+
className={linkClass(selectedLayoutSegment === 'feature-gates')}
5052
href={featureGatesPath}
5153
>
5254
Feature Gates
5355
</Link>
5456
</li>
5557
<li className="nav-item">
56-
<Link
57-
className={`nav-link${selectedLayoutSegment === 'supply' ? ' active' : ''}`}
58-
href={supplyPath}
59-
>
58+
<Link className={linkClass(selectedLayoutSegment === 'supply')} href={supplyPath}>
6059
Supply
6160
</Link>
6261
</li>
6362
<li className="nav-item">
6463
<Link
65-
className={`nav-link${selectedLayoutSegment === 'programs' ? ' active' : ''}`}
64+
className={linkClass(selectedLayoutSegment === 'verified-programs')}
6665
href={programsPath}
6766
>
6867
Programs
6968
</Link>
7069
</li>
7170
<li className="nav-item">
7271
<Link
73-
className={`nav-link${
72+
className={linkClass(
7473
selectedLayoutSegments[0] === 'tx' && selectedLayoutSegments[1] === '(inspector)'
75-
? ' active'
76-
: ''
77-
}`}
74+
)}
7875
href={inspectorPath}
7976
>
8077
Inspector
8178
</Link>
8279
</li>
83-
<li className="nav-item">
84-
<Link
85-
className={`nav-link${selectedLayoutSegment === 'tos' ? ' active' : ''}`}
86-
href={tosPath}
87-
>
88-
ToS
89-
</Link>
90-
</li>
9180
<li className="nav-item align-items-center justify-content-center pt-2">
9281
<a
9382
aria-label="GitHub Repository"

app/components/common/Copyable.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { CheckCircle, Copy, Loader, XCircle } from 'react-feather';
55

66
import { type CopyState, useCopyToClipboard } from '@/app/shared/lib/useCopyToClipboard';
77

8-
export function Copyable({ text, children }: { text: string | null; children: ReactNode }) {
8+
export function Copyable({ text, children }: { text: string | null; children?: ReactNode }) {
99
const [clipboardState, copy] = useCopyToClipboard(1000);
1010
const [loading, setLoading] = useState(false);
1111

app/components/common/Downloadable.tsx

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,10 +56,12 @@ export function DownloadableDropdown({
5656
data,
5757
encodings = ['hex', 'base58', 'base64'],
5858
filename,
59+
iconButton = false,
5960
}: {
6061
filename: string;
6162
data: ByteArray | null;
6263
encodings?: EncodingFormat[];
64+
iconButton?: boolean;
6365
}) {
6466
const dropdownRef = createRef<HTMLButtonElement>();
6567
const dropdownOptions = useMemo(
@@ -74,9 +76,16 @@ export function DownloadableDropdown({
7476
return (
7577
<Dropdown dropdownRef={dropdownRef} options={dropdownOptions}>
7678
<div className="dropdown e-overflow-visible">
77-
<Button variant="outline" size="sm" ref={dropdownRef} data-bs-toggle="dropdown" type="button">
79+
<Button
80+
variant="outline"
81+
size="sm"
82+
className={iconButton ? 'e-border-none e-px-2' : ''}
83+
ref={dropdownRef}
84+
data-bs-toggle="dropdown"
85+
type="button"
86+
>
7887
<Download size={12} />
79-
Download
88+
{!iconButton && 'Download'}
8089
</Button>
8190
<div className="dropdown-menu-end dropdown-menu e-z-10">
8291
<div className="d-flex e-flex-col">

app/components/common/HexData.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ export function HexData({
5858
<div className={cn('d-none d-lg-flex align-items-center justify-content-end', className)}>
5959
<Content />
6060
</div>
61-
<div className="d-flex d-lg-none align-items-center">
61+
<div className={cn('d-flex d-lg-none align-items-center', className)}>
6262
<Content />
6363
</div>
6464
</>
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import type { Meta, StoryObj } from '@storybook/react';
2+
import { expect, within } from 'storybook/test';
3+
4+
import { nextjsParameters, withCluster } from '../../../.storybook/decorators';
5+
import { Footer } from '../Footer';
6+
7+
const meta = {
8+
component: Footer,
9+
decorators: [withCluster],
10+
parameters: nextjsParameters,
11+
tags: ['autodocs', 'test'],
12+
title: 'Components/Layout/Footer',
13+
} satisfies Meta<typeof Footer>;
14+
15+
export default meta;
16+
type Story = StoryObj<typeof meta>;
17+
18+
export const Default: Story = {
19+
play: async ({ canvasElement }) => {
20+
const canvas = within(canvasElement);
21+
await expect(canvas.getByText('(Beta)')).toBeInTheDocument();
22+
await expect(canvas.getByRole('link', { name: 'Terms of Services' })).toBeInTheDocument();
23+
await expect(canvas.getByRole('link', { name: 'Cookies policy' })).toBeInTheDocument();
24+
},
25+
};

app/components/transaction/AccountsCard.tsx

Lines changed: 39 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { Address } from '@components/common/Address';
22
import { BalanceDelta } from '@components/common/BalanceDelta';
3-
import { Copyable } from '@components/common/Copyable';
3+
import { DownloadableDropdown } from '@components/common/Downloadable';
44
import { ErrorCard } from '@components/common/ErrorCard';
55
import { HexData } from '@components/common/HexData';
66
import { SolBalance } from '@components/common/SolBalance';
@@ -13,7 +13,7 @@ import { BigNumber } from 'bignumber.js';
1313
import React, { useMemo, useState } from 'react';
1414
import { Code } from 'react-feather';
1515

16-
import { ByteArray, toHex } from '@/app/shared/lib/bytes';
16+
import { ByteArray } from '@/app/shared/lib/bytes';
1717

1818
export function AccountsCard({ signature }: SignatureProps) {
1919
const details = useTransactionDetails(signature);
@@ -50,8 +50,6 @@ export function AccountsCard({ signature }: SignatureProps) {
5050
const key = pubkey.toBase58();
5151
const delta = new BigNumber(post).minus(new BigNumber(pre));
5252
const accountInfo = accounts.get(key);
53-
const hexData = accountInfo ? toHex(accountInfo.data) : null;
54-
5553
return (
5654
<tr key={key}>
5755
<td>{index + 1}</td>
@@ -68,11 +66,21 @@ export function AccountsCard({ signature }: SignatureProps) {
6866
{loading ? (
6967
<span className="text-muted">Loading...</span>
7068
) : accountInfo ? (
71-
<Copyable text={hexData}>
72-
<span>{accountInfo.size.toLocaleString('en-US')}</span>
73-
</Copyable>
69+
accountInfo.size > 0 ? (
70+
<div className="e-flex e-items-center">
71+
<DownloadableDropdown
72+
data={accountInfo.data}
73+
encodings={['hex', 'base64']}
74+
filename={key}
75+
iconButton
76+
/>
77+
<span>{accountInfo.size.toLocaleString('en-US')}</span>
78+
</div>
79+
) : (
80+
<span className="e-ml-7">{accountInfo.size.toLocaleString('en-US')}</span>
81+
)
7482
) : (
75-
<span className="text-muted">-</span>
83+
<span className="text-muted e-ml-7">-</span>
7684
)}
7785
</td>
7886
<td>
@@ -131,14 +139,18 @@ export function AccountsCard({ signature }: SignatureProps) {
131139
{totalAccountSize > 0 && (
132140
<tfoot>
133141
<tr>
134-
<td colSpan={3} />
142+
<td colSpan={3} className="align-bottom">
143+
<p className="text-muted e-m-0 e-text-right e-text-[0.625rem]">
144+
reflects current account state
145+
</p>
146+
</td>
135147
<td className="align-bottom">
136148
<p className="text-muted e-m-0 e-text-[0.625rem] e-uppercase">
137149
Total Account Size:
138150
</p>
139151
</td>
140152
<td>
141-
<span className="text-white">
153+
<span className="text-white e-ml-7">
142154
{totalAccountSize.toLocaleString('en-US')}
143155
</span>
144156
</td>
@@ -221,21 +233,30 @@ function DataRow({
221233
</div>
222234
</div>
223235

224-
<button
225-
className={`btn btn-sm d-flex ${isDataVisible ? 'btn-black active' : 'btn-white'}`}
226-
onClick={() => setIsDataVisible(!isDataVisible)}
227-
>
228-
{isDataVisible ? 'Hide Data' : 'Show Data'}
229-
</button>
236+
<div className="d-flex align-items-center gap-2">
237+
{!!data?.length && (
238+
<DownloadableDropdown
239+
data={data}
240+
encodings={['hex', 'base64']}
241+
filename={account.pubkey.toBase58()}
242+
/>
243+
)}
244+
<button
245+
className={`btn btn-sm d-flex ${isDataVisible ? 'btn-black active' : 'btn-white'}`}
246+
onClick={() => setIsDataVisible(!isDataVisible)}
247+
>
248+
{isDataVisible ? 'Hide Data' : 'Show Data'}
249+
</button>
250+
</div>
230251
</div>
231252

232253
{isDataVisible && (
233-
<div className="e-items-end e-text-end">
254+
<div className="md:e-items-end xl:e-text-end">
234255
{displayData && displayData.length > 0 ? (
235256
<>
236257
<HexData raw={displayData} copyableRaw={data} className="!e-items-baseline" />
237258
{isTruncated && (
238-
<span className="text-muted e-mt-1 e-text-xs">
259+
<span className="text-muted e-ml-5 e-mt-1 e-text-xs xl:e-ml-0">
239260
Showing first {MAX_DISPLAY_BYTES.toLocaleString()} of{' '}
240261
{data.length.toLocaleString()} bytes
241262
</span>

app/layout.tsx

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import './styles.css';
33

44
import { ClusterModal } from '@components/ClusterModal';
55
import { ClusterStatusButton } from '@components/ClusterStatusButton';
6+
import { Footer } from '@components/Footer';
67
import { MessageBanner } from '@components/MessageBanner';
78
import { Navbar } from '@components/Navbar';
89
import { ClusterProvider } from '@providers/cluster';
@@ -61,18 +62,21 @@ export default function RootLayout({ analytics, children }: { analytics: React.R
6162
<VisibilityProvider>
6263
<TokenInfoBatchProvider>
6364
<ClusterModal />
64-
<div className="main-content pb-4">
65-
<Navbar>
66-
<SearchBar />
67-
</Navbar>
68-
<MessageBanner />
69-
<div className="container my-3 d-xl-none">
70-
<SearchBar />
65+
<div className="e-flex e-min-h-screen e-flex-col">
66+
<div className="main-content pb-4 e-flex-1">
67+
<Navbar>
68+
<SearchBar />
69+
</Navbar>
70+
<MessageBanner />
71+
<div className="container my-3 d-xl-none">
72+
<SearchBar />
73+
</div>
74+
<div className="container my-3 d-lg-none">
75+
<ClusterStatusButton />
76+
</div>
77+
{children}
7178
</div>
72-
<div className="container my-3 d-lg-none">
73-
<ClusterStatusButton />
74-
</div>
75-
{children}
79+
<Footer />
7680
</div>
7781
<Toaster position="bottom-center" toastOptions={{ duration: 5_000 }} />
7882
</TokenInfoBatchProvider>

app/tx/[signature]/page-client.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -428,13 +428,13 @@ function DetailsSection({ signature }: SignatureProps) {
428428

429429
return (
430430
<>
431-
<CUProfilingSection signature={signature} />
432431
<Suspense fallback={<LoadingCard message="Loading accounts" />}>
433432
<AccountsCard signature={signature} />
434433
</Suspense>
435434
<TokenBalancesCard signature={signature} />
436435
<InstructionsSection signature={signature} />
437436
<ProgramLogSection signature={signature} />
437+
<CUProfilingSection signature={signature} />
438438
</>
439439
);
440440
}

bench/BUILD.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,4 +55,4 @@
5555
| Dynamic | `/tx/[signature]` | 45.5 kB | 1.44 MB |
5656
| Dynamic | `/tx/[signature]/inspect` | 622 B | 1.21 MB |
5757
| Static | `/tx/inspector` | 629 B | 1.21 MB |
58-
| Static | `/verified-programs` | 6.34 kB | 173 kB |
58+
| Static | `/verified-programs` | 6.34 kB | 173 kB |

0 commit comments

Comments
 (0)