Skip to content

Commit 8b13b9a

Browse files
committed
Migrate profile page to tailwind and Move to app router
1 parent 535e1df commit 8b13b9a

File tree

18 files changed

+671
-865
lines changed

18 files changed

+671
-865
lines changed
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
import { Spinner } from "@/components/ui/Spinner/Spinner";
2+
import { fetchPublishedContracts } from "components/contract-components/fetchPublishedContracts";
3+
import { PublisherSocials } from "components/contract-components/publisher/PublisherSocials";
4+
import { EditProfile } from "components/contract-components/publisher/edit-profile";
5+
import { PublisherAvatar } from "components/contract-components/publisher/masked-avatar";
6+
import { DeployedContracts } from "components/contract-components/tables/deployed-contracts";
7+
import type { ProfileMetadata } from "constants/schemas";
8+
import { Suspense } from "react";
9+
import { shortenIfAddress } from "utils/usedapp-external";
10+
import { getSortedDeployedContracts } from "../../../account/contracts/_components/getSortedDeployedContracts";
11+
import { PublishedContracts } from "./components/published-contracts";
12+
13+
export function ProfileUI(props: {
14+
profileAddress: string;
15+
ensName: string | undefined;
16+
publisherProfile: ProfileMetadata;
17+
showEditProfile: boolean;
18+
}) {
19+
const { profileAddress, ensName, publisherProfile, showEditProfile } = props;
20+
21+
const displayName = shortenIfAddress(ensName || profileAddress).replace(
22+
"deployer.thirdweb.eth",
23+
"thirdweb.eth",
24+
);
25+
26+
return (
27+
<div className="container pt-8 pb-20">
28+
{/* Header */}
29+
<div className="flex w-full flex-col items-center justify-between gap-4 border-border border-b pb-6 md:flex-row">
30+
<div className="flex w-full items-center gap-4">
31+
<PublisherAvatar address={profileAddress} className="size-20" />
32+
<div>
33+
<h1 className="font-semibold text-4xl tracking-tight">
34+
{displayName}
35+
</h1>
36+
37+
{publisherProfile.bio && (
38+
<p className="line-clamp-2 text-muted-foreground">
39+
{publisherProfile.bio}
40+
</p>
41+
)}
42+
43+
<div className="-translate-x-2 mt-1">
44+
<PublisherSocials publisherProfile={publisherProfile} />
45+
</div>
46+
</div>
47+
</div>
48+
49+
{showEditProfile && (
50+
<div className="shrink-0">
51+
<EditProfile publisherProfile={publisherProfile} />
52+
</div>
53+
)}
54+
</div>
55+
56+
<div className="h-8" />
57+
58+
<div>
59+
<h2 className="font-semibold text-2xl tracking-tight">
60+
Published contracts
61+
</h2>
62+
63+
<div className="h-4" />
64+
<Suspense fallback={<LoadingSection />}>
65+
<AsyncPublishedContracts publisherAddress={profileAddress} />
66+
</Suspense>
67+
</div>
68+
69+
<div className="h-12" />
70+
71+
<div>
72+
<h2 className="font-semibold text-2xl tracking-tight">
73+
Deployed contracts
74+
</h2>
75+
76+
<p className="text-muted-foreground">
77+
List of contracts deployed across all Mainnets
78+
</p>
79+
80+
<div className="h-4" />
81+
<Suspense fallback={<LoadingSection />}>
82+
<AsyncDeployedContracts profileAddress={profileAddress} />
83+
</Suspense>
84+
</div>
85+
</div>
86+
);
87+
}
88+
89+
async function AsyncDeployedContracts(props: {
90+
profileAddress: string;
91+
}) {
92+
const contracts = await getSortedDeployedContracts({
93+
address: props.profileAddress,
94+
onlyMainnet: true,
95+
});
96+
97+
return <DeployedContracts contractList={contracts} limit={50} />;
98+
}
99+
100+
async function AsyncPublishedContracts(props: {
101+
publisherAddress: string;
102+
}) {
103+
const publishedContracts = await fetchPublishedContracts(
104+
props.publisherAddress,
105+
);
106+
107+
if (publishedContracts.length === 0) {
108+
return (
109+
<div className="flex min-h-[300px] items-center justify-center rounded-lg border border-border">
110+
No published contracts found
111+
</div>
112+
);
113+
}
114+
115+
return <PublishedContracts publishedContracts={publishedContracts} />;
116+
}
117+
118+
function LoadingSection() {
119+
return (
120+
<div className="flex min-h-[450px] items-center justify-center rounded-lg border border-border">
121+
<Spinner className="size-10" />
122+
</div>
123+
);
124+
}
Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
import { Img } from "@/components/blocks/Img";
2+
import { Button } from "@/components/ui/button";
3+
import {
4+
Table,
5+
TableBody,
6+
TableCell,
7+
TableContainer,
8+
TableHead,
9+
TableHeader,
10+
TableRow,
11+
} from "@/components/ui/table";
12+
import { ToolTipLabel } from "@/components/ui/tooltip";
13+
import { TrackedLinkTW } from "@/components/ui/tracked-link";
14+
import { replaceDeployerAddress } from "components/explore/publisher";
15+
import { useTrack } from "hooks/analytics/useTrack";
16+
import { replaceIpfsUrl } from "lib/sdk";
17+
import { ShieldCheckIcon } from "lucide-react";
18+
import Link from "next/link";
19+
import { useMemo } from "react";
20+
import { type Column, type Row, useTable } from "react-table";
21+
import type { PublishedContractDetails } from "../../../../../components/contract-components/hooks";
22+
23+
interface PublishedContractTableProps {
24+
contractDetails: ContractDataInput[];
25+
footer?: React.ReactNode;
26+
}
27+
28+
type ContractDataInput = PublishedContractDetails;
29+
type ContractDataRow = ContractDataInput["metadata"] & {
30+
id: string;
31+
};
32+
33+
function convertContractDataToRowData(
34+
input: ContractDataInput,
35+
): ContractDataRow {
36+
return {
37+
id: input.contractId,
38+
...input.metadata,
39+
};
40+
}
41+
42+
export function PublishedContractTable(props: PublishedContractTableProps) {
43+
const { contractDetails, footer } = props;
44+
const trackEvent = useTrack();
45+
const rows = useMemo(
46+
() => contractDetails.map(convertContractDataToRowData),
47+
[contractDetails],
48+
);
49+
50+
const tableColumns: Column<ContractDataRow>[] = useMemo(() => {
51+
const cols: Column<ContractDataRow>[] = [
52+
{
53+
Header: "Logo",
54+
accessor: (row) => row.logo,
55+
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
56+
Cell: (cell: any) => (
57+
<Img
58+
alt=""
59+
src={cell.value ? replaceIpfsUrl(cell.value) : ""}
60+
fallback={
61+
<div className="size-8 rounded-full border border-border bg-muted" />
62+
}
63+
className="size-8"
64+
/>
65+
),
66+
},
67+
{
68+
Header: "Name",
69+
accessor: (row) => row.name,
70+
// biome-ignore lint/suspicious/noExplicitAny: FIXME
71+
Cell: (cell: any) => {
72+
return (
73+
<Link
74+
href={replaceDeployerAddress(
75+
`/${cell.row.original.publisher}/${cell.row.original.id}`,
76+
)}
77+
className="whitespace-nowrap text-foreground before:absolute before:inset-0"
78+
>
79+
{cell.value}
80+
</Link>
81+
);
82+
},
83+
},
84+
{
85+
Header: "Description",
86+
accessor: (row) => row.description,
87+
// biome-ignore lint/suspicious/noExplicitAny: FIXME
88+
Cell: (cell: any) => (
89+
<span className="line-clamp-2 text-muted-foreground">
90+
{cell.value}
91+
</span>
92+
),
93+
},
94+
{
95+
Header: "Version",
96+
accessor: (row) => row.version,
97+
// biome-ignore lint/suspicious/noExplicitAny: FIXME
98+
Cell: (cell: any) => (
99+
<span className="text-muted-foreground">{cell.value}</span>
100+
),
101+
},
102+
{
103+
id: "audit-badge",
104+
accessor: (row) => ({ audit: row.audit }),
105+
// biome-ignore lint/suspicious/noExplicitAny: FIXME
106+
Cell: (cell: any) => (
107+
<span className="flex items-center gap-2">
108+
{cell.value.audit ? (
109+
<ToolTipLabel label="View Contract Audit">
110+
<Button asChild variant="ghost" className="h-auto w-auto p-2">
111+
<TrackedLinkTW
112+
href={replaceIpfsUrl(cell.value.audit)}
113+
category="deploy"
114+
label="audited"
115+
aria-label="View Contract Audit"
116+
target="_blank"
117+
onClick={(e) => {
118+
e.stopPropagation();
119+
trackEvent({
120+
category: "visit-audit",
121+
action: "click",
122+
label: cell.value.audit,
123+
});
124+
}}
125+
>
126+
<ShieldCheckIcon className="size-5 text-success-text" />
127+
</TrackedLinkTW>
128+
</Button>
129+
</ToolTipLabel>
130+
) : null}
131+
</span>
132+
),
133+
},
134+
];
135+
136+
return cols;
137+
}, [trackEvent]);
138+
139+
const tableInstance = useTable({
140+
columns: tableColumns,
141+
data: rows,
142+
});
143+
144+
return (
145+
<TableContainer>
146+
<Table {...tableInstance.getTableProps()}>
147+
<TableHeader>
148+
{tableInstance.headerGroups.map((headerGroup) => {
149+
const { key, ...rowProps } = headerGroup.getHeaderGroupProps();
150+
return (
151+
<TableRow {...rowProps} key={key}>
152+
{headerGroup.headers.map((column, columnIndex) => (
153+
<TableHead
154+
{...column.getHeaderProps()}
155+
// biome-ignore lint/suspicious/noArrayIndexKey: FIXME
156+
key={columnIndex}
157+
>
158+
<span className="text-muted-foreground">
159+
{column.render("Header")}
160+
</span>
161+
</TableHead>
162+
))}
163+
</TableRow>
164+
);
165+
})}
166+
</TableHeader>
167+
168+
<TableBody {...tableInstance.getTableBodyProps()} className="relative">
169+
{tableInstance.rows.map((row) => {
170+
tableInstance.prepareRow(row);
171+
return <ContractTableRow row={row} key={row.getRowProps().key} />;
172+
})}
173+
</TableBody>
174+
</Table>
175+
{footer}
176+
</TableContainer>
177+
);
178+
}
179+
180+
function ContractTableRow(props: {
181+
row: Row<ContractDataRow>;
182+
}) {
183+
const { row } = props;
184+
const { key, ...rowProps } = row.getRowProps();
185+
return (
186+
<>
187+
<TableRow
188+
className="relative cursor-pointer hover:bg-muted/50"
189+
{...rowProps}
190+
key={key}
191+
>
192+
{row.cells.map((cell) => (
193+
<TableCell {...cell.getCellProps()} key={cell.getCellProps().key}>
194+
{cell.render("Cell")}
195+
</TableCell>
196+
))}
197+
</TableRow>
198+
</>
199+
);
200+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
"use client";
2+
3+
import { useState } from "react";
4+
import type { fetchPublishedContracts } from "../../../../../components/contract-components/fetchPublishedContracts";
5+
import { ShowMoreButton } from "../../../../../components/contract-components/tables/show-more-button";
6+
import { PublishedContractTable } from "./PublishedContractTable";
7+
8+
interface PublishedContractsProps {
9+
limit?: number;
10+
publishedContracts: Awaited<ReturnType<typeof fetchPublishedContracts>>;
11+
}
12+
13+
export const PublishedContracts: React.FC<PublishedContractsProps> = ({
14+
limit = 10,
15+
publishedContracts,
16+
}) => {
17+
const [showMoreLimit, setShowMoreLimit] = useState(10);
18+
const slicedData = publishedContracts.slice(0, showMoreLimit);
19+
20+
return (
21+
<PublishedContractTable
22+
contractDetails={slicedData}
23+
footer={
24+
publishedContracts.length > slicedData.length ? (
25+
<ShowMoreButton
26+
limit={limit}
27+
showMoreLimit={showMoreLimit}
28+
setShowMoreLimit={setShowMoreLimit}
29+
/>
30+
) : undefined
31+
}
32+
/>
33+
);
34+
};

0 commit comments

Comments
 (0)