Skip to content

Commit 9f747e3

Browse files
committed
[TOOL-4959] Dashboard: Asset page UI improvements
1 parent c863047 commit 9f747e3

File tree

12 files changed

+306
-237
lines changed

12 files changed

+306
-237
lines changed

apps/dashboard/src/@/components/blocks/charts/area-chart.tsx

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ type ThirdwebAreaChartProps<TConfig extends ChartConfig> = {
2929
title: string;
3030
description?: string;
3131
titleClassName?: string;
32+
headerClassName?: string;
3233
};
3334
customHeader?: React.ReactNode;
3435
// chart config
@@ -62,7 +63,7 @@ export function ThirdwebAreaChart<TConfig extends ChartConfig>(
6263
return (
6364
<Card className={props.className}>
6465
{props.header && (
65-
<CardHeader>
66+
<CardHeader className={props.header.headerClassName}>
6667
<CardTitle className={cn("mb-2", props.header.titleClassName)}>
6768
{props.header.title}
6869
</CardTitle>
@@ -85,7 +86,11 @@ export function ThirdwebAreaChart<TConfig extends ChartConfig>(
8586
{props.emptyChartState}
8687
</EmptyChartState>
8788
) : (
88-
<AreaChart accessibilityLayer data={props.data}>
89+
<AreaChart
90+
accessibilityLayer
91+
data={props.data}
92+
margin={{ right: 0, left: 0, bottom: 10, top: 10 }}
93+
>
8994
<CartesianGrid vertical={false} />
9095
{props.yAxis && <YAxis axisLine={false} tickLine={false} />}
9196
<XAxis

apps/dashboard/src/@/components/ui/background-patterns.tsx

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { useId } from "react";
12
import { cn } from "@/lib/utils";
23

34
export function DotsBackgroundPattern(props: { className?: string }) {
@@ -16,3 +17,70 @@ export function DotsBackgroundPattern(props: { className?: string }) {
1617
/>
1718
);
1819
}
20+
21+
interface GridPatternProps extends React.SVGProps<SVGSVGElement> {
22+
width?: number;
23+
height?: number;
24+
x?: number;
25+
y?: number;
26+
squares?: Array<[x: number, y: number]>;
27+
strokeDasharray?: string;
28+
className?: string;
29+
[key: string]: unknown;
30+
}
31+
32+
export function GridPattern({
33+
width = 40,
34+
height = 40,
35+
x = -1,
36+
y = -1,
37+
strokeDasharray = "0",
38+
squares,
39+
className,
40+
...props
41+
}: GridPatternProps) {
42+
const id = useId();
43+
44+
return (
45+
<svg
46+
aria-hidden="true"
47+
className={cn(
48+
"pointer-events-none absolute inset-0 h-full w-full fill-current stroke-current",
49+
className,
50+
)}
51+
{...props}
52+
>
53+
<defs>
54+
<pattern
55+
id={id}
56+
width={width}
57+
height={height}
58+
patternUnits="userSpaceOnUse"
59+
x={x}
60+
y={y}
61+
>
62+
<path
63+
d={`M.5 ${height}V.5H${width}`}
64+
fill="none"
65+
strokeDasharray={strokeDasharray}
66+
/>
67+
</pattern>
68+
</defs>
69+
<rect width="100%" height="100%" strokeWidth={0} fill={`url(#${id})`} />
70+
{squares && (
71+
<svg x={x} y={y} className="overflow-visible" role="presentation">
72+
{squares.map(([x, y]) => (
73+
<rect
74+
strokeWidth="0"
75+
key={`${x}-${y}`}
76+
width={width - 1}
77+
height={height - 1}
78+
x={x * width + 1}
79+
y={y * height + 1}
80+
/>
81+
))}
82+
</svg>
83+
)}
84+
</svg>
85+
);
86+
}

apps/dashboard/src/@/components/ui/card.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ const Card = React.forwardRef<
88
>(({ className, ...props }, ref) => (
99
<div
1010
className={cn(
11-
"rounded-lg border border-border bg-card text-card-foreground shadow-sm",
11+
"rounded-lg border border-border bg-card text-card-foreground",
1212
className,
1313
)}
1414
ref={ref}

apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/_components/PageHeader.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { PublicPageConnectButton } from "./PublicPageConnectButton";
66

77
export function PageHeader(props: { containerClassName?: string }) {
88
return (
9-
<div className="border-b border-dashed">
9+
<div className="border-b bg-card">
1010
<header
1111
className={cn(
1212
"container flex max-w-8xl justify-between py-3",

apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/erc20/_components/ContractCreatorBadge.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,13 @@ export function ContractCreatorBadge(props: {
66
clientContract: ThirdwebContract;
77
}) {
88
return (
9-
<div className="flex items-center gap-1.5 bg-card rounded-full px-2.5 py-1.5 border hover:bg-accent">
9+
<div className="flex items-center gap-2 bg-card rounded-full px-2.5 py-1.5 border hover:bg-accent">
1010
<span className="text-xs text-foreground">By</span>
1111
<WalletAddress
1212
address={props.contractCreator}
13-
className="py-0 text-xs h-auto !no-underline"
13+
className="py-0 text-xs h-auto !no-underline gap-1.5"
1414
client={props.clientContract.client}
15-
iconClassName="size-3.5 hidden"
15+
iconClassName="size-3.5"
1616
/>
1717
</div>
1818
);

apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/erc20/_components/ContractHeader.tsx

Lines changed: 49 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ export function ContractHeaderUI(props: {
4444
socialUrls: object;
4545
imageClassName?: string;
4646
contractCreator: string | null;
47+
className?: string;
4748
}) {
4849
const socialUrls = useMemo(() => {
4950
const socialUrlsValue: { name: string; href: string }[] = [];
@@ -64,41 +65,45 @@ export function ContractHeaderUI(props: {
6465
?.replace("Mainnet", "")
6566
.trim();
6667

67-
const explorersToShow = getExplorersToShow(props.chainMetadata);
68+
const validBlockExplorer = getExplorerToShow(props.chainMetadata);
6869

6970
return (
70-
<div className="flex flex-col items-start gap-4 border-b border-dashed py-8 lg:flex-row lg:items-center">
71-
{props.image && (
72-
<Img
73-
className={cn(
74-
"size-20 shrink-0 rounded-full border bg-muted",
75-
props.imageClassName,
76-
)}
77-
fallback={
78-
<div className="flex items-center justify-center font-bold text-3xl text-muted-foreground/80">
79-
{props.name[0]}
80-
</div>
81-
}
82-
src={
83-
props.image
84-
? resolveSchemeWithErrorHandler({
85-
client: props.clientContract.client,
86-
uri: props.image,
87-
})
88-
: ""
89-
}
90-
/>
71+
<div
72+
className={cn(
73+
"flex flex-col lg:flex-row lg:items-center gap-5 py-6 relative",
74+
props.className,
9175
)}
76+
>
77+
<div className="flex">
78+
<div className="border p-1 rounded-full bg-card">
79+
<Img
80+
className={cn("size-28 shrink-0 rounded-full bg-muted")}
81+
fallback={
82+
<div className="flex items-center justify-center font-bold text-5xl text-muted-foreground/50 capitalize">
83+
{props.name[0]}
84+
</div>
85+
}
86+
src={
87+
props.image
88+
? resolveSchemeWithErrorHandler({
89+
client: props.clientContract.client,
90+
uri: props.image,
91+
})
92+
: ""
93+
}
94+
/>
95+
</div>
96+
</div>
9297

93-
<div className="flex flex-col gap-3">
98+
<div className="flex flex-col gap-2.5 flex-1">
9499
{/* top row */}
95100
<div className="flex flex-col gap-3">
96101
<div className="flex flex-col flex-wrap gap-3 lg:flex-row lg:items-center">
97-
<h1 className="line-clamp-2 font-bold text-2xl tracking-tight lg:text-3xl">
102+
<h1 className="font-semibold text-3xl tracking-tighter lg:text-5xl">
98103
{props.name}
99104
</h1>
100105

101-
<div className="flex flex-wrap gap-2">
106+
<div className="flex flex-wrap gap-2 ">
102107
<Link
103108
className="flex w-fit shrink-0 items-center gap-2 rounded-3xl border border-border bg-card px-2.5 py-1.5 hover:bg-accent"
104109
href={`/${props.chainMetadata.slug}`}
@@ -148,12 +153,14 @@ export function ContractHeaderUI(props: {
148153
</div>
149154

150155
{/* bottom row */}
151-
<div className="flex flex-row flex-wrap items-center gap-2">
156+
<div className="flex flex-col items-end sm:flex-row flex-wrap sm:items-center gap-2 absolute top-6 right-0 sm:static w-1/2 sm:w-full">
152157
{props.contractCreator && props.contractCreator !== ZERO_ADDRESS && (
153-
<ContractCreatorBadge
154-
clientContract={props.clientContract}
155-
contractCreator={props.contractCreator}
156-
/>
158+
<div>
159+
<ContractCreatorBadge
160+
clientContract={props.clientContract}
161+
contractCreator={props.contractCreator}
162+
/>
163+
</div>
157164
)}
158165

159166
<ToolTipLabel
@@ -180,13 +187,13 @@ export function ContractHeaderUI(props: {
180187
</Button>
181188
</ToolTipLabel>
182189

183-
{explorersToShow?.map((validBlockExplorer) => (
190+
{validBlockExplorer && (
184191
<BadgeLink
185192
href={`${validBlockExplorer.url.endsWith("/") ? validBlockExplorer.url : `${validBlockExplorer.url}/`}address/${props.clientContract.address}`}
186193
key={validBlockExplorer.url}
187194
name={validBlockExplorer.name}
188195
/>
189-
))}
196+
)}
190197

191198
{/* TODO - render social links here */}
192199
</div>
@@ -204,19 +211,22 @@ function isValidUrl(url: string) {
204211
}
205212
}
206213

207-
function getExplorersToShow(chainMetadata: ChainMetadata) {
208-
const validBlockExplorers = chainMetadata.explorers
209-
?.filter((e) => e.standard === "EIP3091")
210-
?.slice(0, 2);
214+
function getExplorerToShow(chainMetadata: ChainMetadata) {
215+
const validBlockExplorers = chainMetadata.explorers?.filter(
216+
(e) => e.standard === "EIP3091",
217+
);
211218

212-
return validBlockExplorers?.slice(0, 1);
219+
return validBlockExplorers?.[0];
213220
}
214221

215-
function BadgeLink(props: { name: string; href: string }) {
222+
function BadgeLink(props: { name: string; href: string; className?: string }) {
216223
return (
217224
<Button
218225
asChild
219-
className="!h-auto gap-2 rounded-full bg-card px-3 py-1.5 text-xs capitalize"
226+
className={cn(
227+
"!h-auto gap-2 rounded-full bg-card px-3 py-1.5 text-xs capitalize",
228+
props.className,
229+
)}
220230
variant="outline"
221231
>
222232
<Link href={props.href} rel="noopener noreferrer" target="_blank">

apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/erc20/_components/PriceChart.tsx

Lines changed: 20 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import { Button } from "@/components/ui/button";
99
import { SkeletonContainer } from "@/components/ui/skeleton";
1010
import { ToolTipLabel } from "@/components/ui/tooltip";
1111
import { cn } from "@/lib/utils";
12-
import { useTokenPriceData } from "../_hooks/useTokenPriceData";
12+
import type { TokenPriceData } from "../_hooks/useTokenPriceData";
1313

1414
function PriceChartUI(props: {
1515
isPending: boolean;
@@ -29,7 +29,7 @@ function PriceChartUI(props: {
2929
<ThirdwebAreaChart
3030
cardContentClassName="p-0"
3131
chartClassName="aspect-[1.5] lg:aspect-[3]"
32-
className="border-none bg-background p-0"
32+
className="border-none bg-transparent p-0"
3333
config={{
3434
price: {
3535
color: "hsl(var(--chart-1))",
@@ -49,7 +49,7 @@ function PriceChartUI(props: {
4949

5050
const tokenPriceUSDFormatter = new Intl.NumberFormat("en-US", {
5151
currency: "USD",
52-
maximumFractionDigits: 10,
52+
maximumFractionDigits: 4,
5353
minimumFractionDigits: 0,
5454
notation: "compact",
5555
roundingMode: "halfEven",
@@ -92,20 +92,14 @@ function getTooltipLabelFormatter(includeTimeOfDay: boolean) {
9292
export function TokenStats(params: {
9393
chainId: number;
9494
contractAddress: string;
95+
tokenPriceData: TokenPriceData;
9596
}) {
96-
const tokenPriceQuery = useTokenPriceData(params);
9797
const [interval, setInterval] = useState<Interval>("max");
9898

99-
const tokenPriceData = tokenPriceQuery.data;
100-
10199
const filteredHistoricalPrices = useMemo(() => {
102100
const currentDate = new Date();
103101

104-
if (tokenPriceData?.type === "no-data") {
105-
return [];
106-
}
107-
108-
return tokenPriceData?.data?.historical_prices.filter((item) => {
102+
return params.tokenPriceData.historical_prices.filter((item) => {
109103
const date = new Date(item.date);
110104
const maxDiff =
111105
interval === "24h"
@@ -120,32 +114,17 @@ export function TokenStats(params: {
120114

121115
return differenceInCalendarDays(currentDate, date) <= maxDiff;
122116
});
123-
}, [tokenPriceData, interval]);
124-
125-
const priceUsd =
126-
tokenPriceData?.type === "no-data" || tokenPriceQuery.isError
127-
? "N/A"
128-
: tokenPriceData?.data?.price_usd;
117+
}, [params.tokenPriceData, interval]);
129118

130-
const percentChange24h =
131-
tokenPriceData?.type === "no-data" || tokenPriceQuery.isError
132-
? "N/A"
133-
: tokenPriceData?.data?.percent_change_24h;
134-
135-
const marketCap =
136-
tokenPriceData?.type === "no-data" || tokenPriceQuery.isError
137-
? "N/A"
138-
: tokenPriceData?.data?.market_cap_usd;
139-
140-
const holders =
141-
tokenPriceData?.type === "no-data" || tokenPriceQuery.isError
142-
? "N/A"
143-
: tokenPriceData?.data?.holders;
119+
const priceUsd = params.tokenPriceData.price_usd;
120+
const percentChange24h = params.tokenPriceData.percent_change_24h;
121+
const marketCap = params.tokenPriceData.market_cap_usd;
122+
const holders = params.tokenPriceData.holders;
144123

145124
return (
146-
<div>
125+
<div className="bg-card rounded-lg border">
147126
{/* price and change */}
148-
<div className="flex flex-col gap-4 lg:flex-row lg:items-center lg:justify-between">
127+
<div className="flex flex-col gap-4 lg:flex-row lg:items-center lg:justify-between px-6 pb-4 pt-6">
149128
<div>
150129
<p className="mb-1 text-muted-foreground text-sm">Current Price</p>
151130
<div className="flex items-center gap-2">
@@ -209,14 +188,15 @@ export function TokenStats(params: {
209188
<IntervalSelector interval={interval} setInterval={setInterval} />
210189
</div>
211190

212-
<div className="h-4" />
213-
<PriceChartUI
214-
data={filteredHistoricalPrices || []}
215-
isPending={tokenPriceQuery.isPending}
216-
showTimeOfDay={interval === "24h"}
217-
/>
191+
<div className="px-0">
192+
<PriceChartUI
193+
data={filteredHistoricalPrices || []}
194+
isPending={false}
195+
showTimeOfDay={interval === "24h"}
196+
/>
197+
</div>
218198

219-
<div className="mt-8 flex gap-6 border-y border-dashed py-4 [&>*:not(:first-child)]:border-l [&>*:not(:first-child)]:border-dashed [&>*:not(:first-child)]:pl-5 [&>*]:grow">
199+
<div className="px-6 py-4 mt-6 border-t flex gap-6 [&>*:not(:first-child)]:border-l [&>*:not(:first-child)]:pl-5 [&>*]:grow">
220200
<TokenStat
221201
label="Market Cap"
222202
skeletonData={"1000000000"}

0 commit comments

Comments
 (0)