Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 64 additions & 0 deletions apps/dashboard/src/app/(dashboard)/dashboard/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { Changelog, type ChangelogItem } from "components/dashboard/Changelog";
import { HomeProductCard } from "components/dashboard/HomeProductCard";
import { OnboardingSteps } from "components/onboarding/Steps";
import { PRODUCTS } from "components/product-pages/common/nav/data";

const TRACKING_CATEGORY = "dashboard";

export default async function Page() {
const changelog = await getChangelog();

return (
<div className="container flex flex-col justify-between gap-16 pt-8 pb-16 xl:flex-row">
<div className="grow">
<h1 className="mb-6 font-semibold text-2xl tracking-tight lg:mb-8 lg:text-3xl">
Get started quickly
</h1>
<div className="flex w-full flex-col gap-10">
<OnboardingSteps />
<div className="flex w-full flex-col gap-12">
{["connect", "contracts", "infrastructure"].map((section) => {
const products = PRODUCTS.filter(
(p) => p.section === section && !!p.dashboardLink,
);

return (
<div key={section}>
<h3 className="mb-2.5 font-medium text-muted-foreground text-xl capitalize tracking-tight">
{section === "infrastructure" ? "Engine" : section}
</h3>
<div className="grid grid-cols-1 gap-4 lg:grid-cols-3">
{products.map((product) => (
<HomeProductCard
key={product.name}
product={product}
TRACKING_CATEGORY={TRACKING_CATEGORY}
/>
))}
</div>
</div>
);
})}
</div>
</div>
</div>
<div className="shrink-0 lg:w-[320px]">
<h2 className="mb-4 font-semibold text-lg tracking-tight">
Latest changes
</h2>
<Changelog changelog={changelog} />
</div>
</div>
);
}

async function getChangelog() {
const res = await fetch(
"https://thirdweb.ghost.io/ghost/api/content/posts/?key=49c62b5137df1c17ab6b9e46e3&fields=title,url,published_at&filter=tag:changelog&visibility:public&limit=5",
);
const json = await res.json();
return json.posts as ChangelogItem[];
}

// revalidate every 5 minutes
export const revalidate = 300;
8 changes: 4 additions & 4 deletions apps/dashboard/src/components/dashboard/Changelog.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Skeleton } from "@/components/ui/skeleton";
import { formatDistance } from "date-fns/formatDistance";
import { ArrowRightIcon } from "lucide-react";
import { Link } from "tw-components";
import Link from "next/link";
import { ClientOnly } from "../ClientOnly/ClientOnly";

export interface ChangelogItem {
Expand All @@ -23,15 +23,15 @@ export const Changelog: React.FC<ChangelogProps> = ({ changelog }) => {

<div className="flex flex-col">
<Link
isExternal
target="_blank"
href={`${item.url}?utm_source=thirdweb&utm_campaign=changelog`}
role="group"
className="!text-muted-foreground hover:!text-foreground hover:!no-underline line-clamp-2 text-sm"
>
{item.title}
</Link>
<div className="mt-1 text-muted-foreground text-xs opacity-70">
<ClientOnly ssr={<Skeleton className="h-2" />}>
<ClientOnly ssr={<Skeleton className="h-2 w-28" />}>
{formatDistance(new Date(item.published_at), Date.now(), {
addSuffix: true,
})}
Expand All @@ -42,7 +42,7 @@ export const Changelog: React.FC<ChangelogProps> = ({ changelog }) => {
))}
<Link
href="https://blog.thirdweb.com/changelog?utm_source=thirdweb&utm_campaign=changelog"
isExternal
target="_blank"
className="!text-foreground flex items-center gap-2 pl-7 text-sm"
>
View More <ArrowRightIcon className="size-4" />
Expand Down
74 changes: 33 additions & 41 deletions apps/dashboard/src/components/dashboard/HomeProductCard.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { Flex, LinkBox, LinkOverlay } from "@chakra-ui/react";
import { ChakraNextImage } from "components/Image";
"use client";
import type { SectionItemProps } from "components/product-pages/common/nav/types";
import { useTrack } from "hooks/analytics/useTrack";
import { Card, Text } from "tw-components";
import Image from "next/image";
import Link from "next/link";

interface HomeProductCardProps {
product: SectionItemProps;
Expand All @@ -17,43 +17,35 @@ export const HomeProductCard: React.FC<HomeProductCardProps> = ({
}) => {
const trackEvent = useTrack();
return (
<LinkBox
onClick={() => {
trackEvent({
category: TRACKING_CATEGORY,
action: "click",
label: "select-product",
product: product.name,
});
}}
>
<Card
p={4}
overflow="hidden"
className="bg-muted/50 hover:bg-muted"
h="full"
minHeight={{ base: "full", md: 28 }}
>
<Flex flexDir="column">
<Flex gap={2} alignItems="center">
{product.icon && (
<ChakraNextImage alt="" boxSize={6} src={product.icon} />
)}
<LinkOverlay
href={isFromLandingPage ? product.link : product.dashboardLink}
>
<Text size="label.md" m={0} color="bgBlack">
{isFromLandingPage
? product.name
: product?.dashboardName || product.name}
</Text>
</LinkOverlay>
</Flex>
<Text mt={3} color="faded">
{product.description}
</Text>
</Flex>
</Card>
</LinkBox>
<div className="relative flex h-full items-center gap-3.5 overflow-hidden rounded-lg border border-border bg-muted/50 p-4 hover:bg-muted md:min-h-24">
{product.icon && (
<div className="shrink-0 rounded-full border border-border p-2">
<Image alt="" className="size-5" src={product.icon} />
</div>
)}
<div>
<Link
href={
(isFromLandingPage ? product.link : product.dashboardLink) || ""
}
className="font-semibold tracking-tight before:absolute before:inset-0"
onClick={() => {
trackEvent({
category: TRACKING_CATEGORY,
action: "click",
label: "select-product",
product: product.name,
});
}}
>
{isFromLandingPage
? product.name
: product?.dashboardName || product.name}
</Link>
<p className="mt-0.5 text-muted-foreground text-sm">
{product.description}
</p>
</div>
</div>
);
};
Loading
Loading