Skip to content

Commit d5eb851

Browse files
committed
Dashboard: Update Project header style
1 parent 34239ea commit d5eb851

File tree

24 files changed

+216
-217
lines changed

24 files changed

+216
-217
lines changed

apps/dashboard/src/@/components/blocks/project-page/header/link-group.tsx

Lines changed: 30 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,8 @@ import { Button } from "@workspace/ui/components/button";
44
import {
55
BookTextIcon,
66
BoxIcon,
7-
EllipsisVerticalIcon,
8-
NetworkIcon,
9-
SettingsIcon,
7+
ChevronDownIcon,
8+
CodeIcon,
109
WebhookIcon,
1110
} from "lucide-react";
1211
import Link from "next/link";
@@ -17,33 +16,28 @@ import {
1716
DropdownMenuItem,
1817
DropdownMenuTrigger,
1918
} from "@/components/ui/dropdown-menu";
20-
import { ToolTipLabel } from "@/components/ui/tooltip";
21-
import { useIsMobile } from "@/hooks/use-mobile";
2219

23-
type LinkType = "api" | "docs" | "playground" | "webhooks" | "settings";
20+
type LinkType = "api" | "docs" | "playground" | "webhooks";
2421

2522
const linkTypeToLabel: Record<LinkType, string> = {
2623
api: "API Reference",
2724
docs: "Documentation",
2825
playground: "Playground",
2926
webhooks: "Webhooks",
30-
settings: "Settings",
3127
};
3228

3329
const linkTypeToOrder: Record<LinkType, number> = {
3430
docs: 0,
3531
playground: 1,
3632
api: 3,
3733
webhooks: 4,
38-
settings: 5,
3934
};
4035

4136
const linkTypeToIcon: Record<LinkType, React.FC<{ className?: string }>> = {
42-
api: NetworkIcon,
37+
api: CodeIcon,
4338
docs: BookTextIcon,
4439
playground: BoxIcon,
4540
webhooks: WebhookIcon,
46-
settings: SettingsIcon,
4741
};
4842

4943
function orderLinks(links: ActionLink[]) {
@@ -60,52 +54,43 @@ export type ActionLink = {
6054
};
6155

6256
export function LinkGroup(props: { links: ActionLink[] }) {
63-
const isMobile = useIsMobile();
64-
const maxLinks = isMobile ? 1 : 2;
6557
const orderedLinks = useMemo(() => orderLinks(props.links), [props.links]);
6658

67-
// case where we just render directly
68-
if (props.links.length <= maxLinks) {
59+
if (orderedLinks.length === 1 && orderedLinks[0]) {
60+
const link = orderedLinks[0];
61+
const Icon = linkTypeToIcon[link.type];
6962
return (
70-
<div className="flex flex-row items-center gap-2">
71-
{orderedLinks.map((link) => {
72-
const isExternal = link.href.startsWith("http");
73-
const Icon = linkTypeToIcon[link.type];
74-
return (
75-
<ToolTipLabel key={link.type} label={linkTypeToLabel[link.type]}>
76-
<Button
77-
asChild
78-
size="icon"
79-
variant="secondary"
80-
className="rounded-full border"
81-
>
82-
<Link
83-
href={link.href}
84-
target={isExternal ? "_blank" : undefined}
85-
rel={isExternal ? "noopener noreferrer" : undefined}
86-
className="flex flex-row items-center gap-2"
87-
>
88-
<Icon className="size-4 text-foreground" />
89-
</Link>
90-
</Button>
91-
</ToolTipLabel>
92-
);
93-
})}
94-
</div>
63+
<Link
64+
href={link.href}
65+
target={link.href.startsWith("http") ? "_blank" : undefined}
66+
>
67+
<Button
68+
variant="outline"
69+
size="sm"
70+
className="rounded-full border gap-2 bg-card"
71+
>
72+
<Icon className="size-3.5 text-muted-foreground" />
73+
{linkTypeToLabel[link.type]}
74+
</Button>
75+
</Link>
9576
);
9677
}
9778

98-
// case where we render a dropdown
9979
return (
10080
<DropdownMenu>
10181
<DropdownMenuTrigger asChild>
102-
<Button size="icon" variant="secondary" className="rounded-full border">
103-
<EllipsisVerticalIcon className="size-4 text-foreground" />
82+
<Button
83+
variant="outline"
84+
size="sm"
85+
className="rounded-full border gap-2 bg-card [&[data-state=open]>svg]:rotate-180"
86+
>
87+
Resources
88+
<ChevronDownIcon className="size-4 transition-transform duration-200 text-muted-foreground" />
10489
</Button>
10590
</DropdownMenuTrigger>
10691
<DropdownMenuContent
107-
align="center"
108-
className="gap-1 flex flex-col md:w-48 rounded-lg"
92+
align="end"
93+
className="gap-1 flex flex-col w-48 rounded-xl"
10994
sideOffset={10}
11095
>
11196
{orderedLinks.map((link) => {
@@ -115,7 +100,7 @@ export function LinkGroup(props: { links: ActionLink[] }) {
115100
<DropdownMenuItem
116101
key={link.type}
117102
asChild
118-
className="flex flex-row items-center gap-2 cursor-pointer py-2"
103+
className="flex flex-row items-center gap-2 cursor-pointer py-1.5"
119104
>
120105
<Link
121106
href={link.href}

apps/dashboard/src/@/components/blocks/project-page/project-page-header.tsx

Lines changed: 74 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
import { Button } from "@workspace/ui/components/button";
2-
import { ArrowUpRightIcon } from "lucide-react";
2+
import { ArrowUpRightIcon, Settings2Icon } from "lucide-react";
33
import Link from "next/link";
44
import type { ThirdwebClient } from "thirdweb";
55
import { cn } from "@/lib/utils";
6-
import { ProjectAvatar } from "../avatar/project-avatar";
76
import { type ActionLink, LinkGroup } from "./header/link-group";
87

98
type Action =
@@ -58,63 +57,100 @@ export type ProjectPageHeaderProps = {
5857
title: string;
5958
description?: React.ReactNode;
6059
imageUrl?: string | null;
60+
icon: React.FC<{ className?: string }>;
61+
isProjectIcon?: boolean;
62+
iconContainerClassName?: string;
6163
actions: {
6264
primary: Action;
6365
secondary?: Action;
6466
} | null;
6567

6668
links?: ActionLink[];
69+
settings?: {
70+
href: string;
71+
};
6772

6873
// TODO: add task card component
6974
task?: never;
7075
};
7176

7277
export function ProjectPageHeader(props: ProjectPageHeaderProps) {
7378
return (
74-
<header className="flex flex-col gap-4 container max-w-7xl py-6">
75-
{/* main row */}
76-
<div className="flex flex-row items-center justify-between">
77-
{/* left */}
78-
<div className="flex flex-col gap-4">
79-
{/* image */}
80-
{props.imageUrl !== undefined && (
81-
<ProjectAvatar
82-
className="size-12"
83-
client={props.client}
84-
src={props.imageUrl ?? undefined}
85-
/>
79+
<header className="container max-w-7xl py-6 relative">
80+
{/* top row */}
81+
<div className="flex justify-between items-start mb-4">
82+
{/* left - icon */}
83+
<div className="flex">
84+
{props.isProjectIcon ? (
85+
<props.icon />
86+
) : (
87+
<div className="border rounded-full p-2.5 bg-card">
88+
<props.icon className="size-5 text-muted-foreground" />
89+
</div>
8690
)}
87-
{/* title */}
88-
<div className="flex flex-col gap-1 max-w-3xl">
89-
<h2 className="text-3xl font-semibold tracking-tight line-clamp-1">
90-
{props.title}
91-
</h2>
92-
<p className="text-sm text-muted-foreground line-clamp-3 md:line-clamp-2">
93-
{props.description}
94-
</p>
95-
</div>
9691
</div>
9792

98-
{/* right */}
99-
{/* TODO: add "current task" card component */}
100-
</div>
93+
{/* right - buttons */}
94+
<div className="flex items-center gap-3">
95+
<div className="flex items-center gap-3">
96+
{props.links && props.links.length > 0 && (
97+
<LinkGroup links={props.links} />
98+
)}
10199

102-
{/* actions row */}
103-
{props.actions && (
104-
<div className="flex flex-row items-center justify-between">
105-
{/* left actions */}
106-
<div className="flex flex-row items-center gap-3">
107-
{props.actions.primary && <Action action={props.actions.primary} />}
108-
{props.actions.secondary && (
109-
<Action action={props.actions.secondary} variant="secondary" />
100+
{props.settings && (
101+
<Link href={props.settings.href}>
102+
<Button
103+
variant="outline"
104+
size="sm"
105+
className="rounded-full gap-2 bg-card"
106+
>
107+
<Settings2Icon className="size-4 text-muted-foreground" />
108+
Settings
109+
</Button>
110+
</Link>
110111
)}
111112
</div>
112-
{/* right actions */}
113-
{props.links && props.links.length > 0 && (
114-
<LinkGroup links={props.links} />
113+
114+
{/* hide on mobile */}
115+
{props.actions && (
116+
<div className="hidden lg:flex items-center gap-3">
117+
{props.actions.secondary && (
118+
<Action action={props.actions.secondary} variant="secondary" />
119+
)}
120+
121+
{props.actions.primary && (
122+
<Action action={props.actions.primary} />
123+
)}
124+
</div>
115125
)}
116126
</div>
117-
)}
127+
</div>
128+
129+
<div className="space-y-4">
130+
{/* mid row */}
131+
<div className="space-y-1 max-w-3xl">
132+
<h2 className="text-3xl font-semibold tracking-tight">
133+
{props.title}
134+
</h2>
135+
{/* description */}
136+
<p className="text-sm text-muted-foreground line-clamp-3 md:line-clamp-2">
137+
{props.description}
138+
</p>
139+
</div>
140+
141+
{/* bottom row - hidden on desktop */}
142+
{props.actions && (
143+
<div className="flex items-center gap-3 lg:hidden">
144+
{props.actions?.primary && (
145+
<Action action={props.actions.primary} />
146+
)}
147+
148+
{props.actions?.secondary && (
149+
<Action action={props.actions.secondary} variant="secondary" />
150+
)}
151+
</div>
152+
)}
153+
</div>
118154
</header>
119155
);
120156
}

apps/dashboard/src/@/components/contracts/import-contract/modal.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
"use client";
22

33
import { zodResolver } from "@hookform/resolvers/zod";
4-
import { ExternalLinkIcon, ImportIcon } from "lucide-react";
4+
import { ArrowDownToLineIcon, ExternalLinkIcon } from "lucide-react";
55
import Link from "next/link";
66
import { useForm } from "react-hook-form";
77
import { toast } from "sonner";
@@ -234,7 +234,7 @@ function ImportForm(props: {
234234
{addContractToProject.isPending ? (
235235
<Spinner className="size-4" />
236236
) : (
237-
<ImportIcon className="size-4" />
237+
<ArrowDownToLineIcon className="size-4" />
238238
)}
239239

240240
{addContractToProject.isPending ? "Importing" : "Import"}

apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/account-abstraction/page.tsx

Lines changed: 5 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
} from "@/components/analytics/date-range-selector";
1212
import { ProjectPage } from "@/components/blocks/project-page/project-page";
1313
import { getClientThirdwebClient } from "@/constants/thirdweb-client.client";
14+
import { SmartAccountIcon } from "@/icons/SmartAccountIcon";
1415
import { getAbsoluteUrl } from "@/utils/vercel";
1516
import { AccountAbstractionSummary } from "./AccountAbstractionAnalytics/AccountAbstractionSummary";
1617
import { SmartWalletsBillingAlert } from "./Alerts";
@@ -93,22 +94,14 @@ export default async function Page(props: {
9394
return (
9495
<ProjectPage
9596
header={{
97+
icon: SmartAccountIcon,
9698
client,
9799
title: "Account Abstraction",
98100
description:
99101
"Integrate EIP-7702 and EIP-4337 compliant smart accounts for gasless sponsorships and more.",
100-
101-
actions: {
102-
primary: {
103-
label: "Documentation",
104-
href: "https://portal.thirdweb.com/transactions/sponsor",
105-
external: true,
106-
},
107-
secondary: {
108-
label: "Playground",
109-
href: "https://playground.thirdweb.com/account-abstraction/eip-7702",
110-
external: true,
111-
},
102+
actions: null,
103+
settings: {
104+
href: `/team/${params.team_slug}/${params.project_slug}/settings/account-abstraction`,
112105
},
113106
links: [
114107
{
@@ -119,10 +112,6 @@ export default async function Page(props: {
119112
type: "playground",
120113
href: "https://playground.thirdweb.com/account-abstraction/eip-7702",
121114
},
122-
{
123-
type: "settings",
124-
href: `/team/${params.team_slug}/${params.project_slug}/settings/account-abstraction`,
125-
},
126115
],
127116
}}
128117
>

apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/ai/page.tsx

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import type { DurationId } from "@/components/analytics/date-range-selector";
66
import { ResponsiveTimeFilters } from "@/components/analytics/responsive-time-filters";
77
import { ProjectPage } from "@/components/blocks/project-page/project-page";
88
import { getClientThirdwebClient } from "@/constants/thirdweb-client.client";
9+
import { NebulaIcon } from "@/icons/NebulaIcon";
910
import { getFiltersFromSearchParams } from "@/lib/time";
1011
import { loginRedirect } from "@/utils/redirects";
1112
import { AiAnalytics } from "./analytics/chart";
@@ -57,21 +58,11 @@ export default async function Page(props: {
5758
<ResponsiveSearchParamsProvider value={searchParams}>
5859
<ProjectPage
5960
header={{
61+
icon: NebulaIcon,
6062
client,
6163
title: "AI",
6264
description: "Interact with any EVM chain with natural language",
63-
actions: {
64-
primary: {
65-
label: "Documentation",
66-
href: "https://portal.thirdweb.com/ai/chat",
67-
external: true,
68-
},
69-
secondary: {
70-
label: "API Reference",
71-
href: "https://api.thirdweb.com/reference#tag/ai/post/ai/chat",
72-
external: true,
73-
},
74-
},
65+
actions: null,
7566
links: [
7667
{
7768
href: "https://portal.thirdweb.com/ai/chat",

apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/components/ProjectSidebarLayout.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import {
77
HomeIcon,
88
LockIcon,
99
RssIcon,
10-
SettingsIcon,
10+
Settings2Icon,
1111
WebhookIcon,
1212
} from "lucide-react";
1313
import { FullWidthSidebarLayout } from "@/components/blocks/full-width-sidebar-layout";
@@ -138,7 +138,7 @@ export function ProjectSidebarLayout(props: {
138138
},
139139
{
140140
href: `${props.layoutPath}/settings`,
141-
icon: SettingsIcon,
141+
icon: Settings2Icon,
142142
label: "Project Settings",
143143
},
144144
{

0 commit comments

Comments
 (0)