mirror of
https://github.com/Significant-Gravitas/AutoGPT.git
synced 2026-04-08 03:00:28 -04:00
refactor(frontend): Revamp creator page data fetching and structure (#10737)
### Changes 🏗️ - Updated the creator page to utilize React Query for data fetching, improving performance and reliability. - Removed legacy API calls and integrated prefetching for creator details and agents. - Introduced a new MainCreatorPage component for better separation of concerns. - Added a hydration boundary for managing server state. ### Checklist 📋 ### Checklist 📋 - [x] I have clearly listed my changes in the PR description - [x] I have made a test plan - [x] I have tested my changes according to the test plan: - [x] All marketplace E2E tests are working. - [x] I’ve tested all the links and checked if everything renders perfectly on the marketplace page.
This commit is contained in:
@@ -0,0 +1,115 @@
|
||||
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
||||
import { StarRatingIcons } from "@/components/ui/icons";
|
||||
|
||||
interface CreatorInfoCardProps {
|
||||
username: string;
|
||||
handle: string;
|
||||
avatarSrc: string;
|
||||
categories: string[];
|
||||
averageRating: number;
|
||||
totalRuns: number;
|
||||
}
|
||||
|
||||
export const CreatorInfoCard = ({
|
||||
username,
|
||||
handle,
|
||||
avatarSrc,
|
||||
categories,
|
||||
averageRating,
|
||||
totalRuns,
|
||||
}: CreatorInfoCardProps) => {
|
||||
return (
|
||||
<div
|
||||
className="inline-flex h-auto min-h-[500px] w-full max-w-[440px] flex-col items-start justify-between rounded-[26px] bg-violet-100 p-4 dark:bg-violet-900 sm:h-[632px] sm:w-[440px] sm:p-6"
|
||||
role="article"
|
||||
aria-label={`Creator profile for ${username}`}
|
||||
>
|
||||
<div className="flex w-full flex-col items-start justify-start gap-3.5 sm:h-[218px]">
|
||||
<Avatar className="h-[100px] w-[100px] sm:h-[130px] sm:w-[130px]">
|
||||
<AvatarImage
|
||||
width={130}
|
||||
height={130}
|
||||
src={avatarSrc}
|
||||
alt={`${username}'s avatar`}
|
||||
/>
|
||||
<AvatarFallback
|
||||
size={130}
|
||||
className="h-[100px] w-[100px] sm:h-[130px] sm:w-[130px]"
|
||||
>
|
||||
{username.charAt(0)}
|
||||
</AvatarFallback>
|
||||
</Avatar>
|
||||
<div className="flex w-full flex-col items-start justify-start gap-1.5">
|
||||
<div
|
||||
data-testid="creator-title"
|
||||
className="w-full font-poppins text-[35px] font-medium leading-10 text-neutral-900 dark:text-neutral-100 sm:text-[35px] sm:leading-10"
|
||||
>
|
||||
{username}
|
||||
</div>
|
||||
<div className="w-full text-lg font-normal leading-6 text-neutral-800 dark:text-neutral-200 sm:text-xl sm:leading-7">
|
||||
@{handle}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="my-4 flex w-full flex-col items-start justify-start gap-6 sm:gap-[50px]">
|
||||
<div className="flex w-full flex-col items-start justify-start gap-3">
|
||||
<div className="h-px w-full bg-neutral-700 dark:bg-neutral-300" />
|
||||
<div className="flex flex-col items-start justify-start gap-2.5">
|
||||
<div className="w-full text-base font-medium leading-normal text-neutral-800 dark:text-neutral-200">
|
||||
Top categories
|
||||
</div>
|
||||
<div
|
||||
className="flex flex-wrap items-center gap-2.5"
|
||||
role="list"
|
||||
aria-label="Categories"
|
||||
>
|
||||
{categories.map((category, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className="flex items-center justify-center gap-2.5 rounded-[34px] border border-neutral-600 px-4 py-3 dark:border-neutral-400"
|
||||
role="listitem"
|
||||
>
|
||||
<div className="text-base font-normal leading-normal text-neutral-800 dark:text-neutral-200">
|
||||
{category}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex w-full flex-col items-start justify-start gap-3">
|
||||
<div className="h-px w-full bg-neutral-700 dark:bg-neutral-300" />
|
||||
<div className="flex w-full flex-col items-start justify-between gap-4 sm:flex-row sm:gap-0">
|
||||
<div className="flex w-full flex-col items-start justify-start gap-2.5 sm:w-[164px]">
|
||||
<div className="w-full text-base font-medium leading-normal text-neutral-800 dark:text-neutral-200">
|
||||
Average rating
|
||||
</div>
|
||||
<div className="inline-flex items-center gap-2">
|
||||
<div className="text-[18px] font-semibold leading-[28px] text-neutral-800 dark:text-neutral-200">
|
||||
{averageRating.toFixed(1)}
|
||||
</div>
|
||||
<div
|
||||
className="flex items-center gap-px"
|
||||
role="img"
|
||||
aria-label={`Rating: ${averageRating} out of 5 stars`}
|
||||
>
|
||||
{StarRatingIcons(averageRating)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex w-full flex-col items-start justify-start gap-2.5 sm:w-[164px]">
|
||||
<div className="w-full text-base font-medium leading-normal text-neutral-800 dark:text-neutral-200">
|
||||
Number of runs
|
||||
</div>
|
||||
<div className="text-[18px] font-semibold leading-[28px] text-neutral-800 dark:text-neutral-200">
|
||||
{new Intl.NumberFormat().format(totalRuns)} runs
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,43 @@
|
||||
import { getIconForSocial } from "@/components/ui/icons";
|
||||
import { Fragment } from "react";
|
||||
|
||||
interface CreatorLinksProps {
|
||||
links: string[];
|
||||
}
|
||||
|
||||
export const CreatorLinks = ({ links }: CreatorLinksProps) => {
|
||||
if (!links || links.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const renderLinkButton = (url: string) => (
|
||||
<a
|
||||
href={url}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="flex min-w-[200px] flex-1 items-center justify-between rounded-[34px] border border-neutral-600 px-5 py-3 dark:border-neutral-400"
|
||||
>
|
||||
<div className="text-base font-medium leading-normal text-neutral-800 dark:text-neutral-200">
|
||||
{new URL(url).hostname.replace("www.", "")}
|
||||
</div>
|
||||
<div className="relative h-6 w-6">
|
||||
{getIconForSocial(url, {
|
||||
className: "h-6 w-6 text-neutral-800 dark:text-neutral-200",
|
||||
})}
|
||||
</div>
|
||||
</a>
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="flex flex-col items-start justify-start gap-4">
|
||||
<div className="text-base font-medium leading-normal text-neutral-800 dark:text-neutral-200">
|
||||
Other links
|
||||
</div>
|
||||
<div className="flex w-full flex-wrap gap-3">
|
||||
{links.map((link, index) => (
|
||||
<Fragment key={index}>{renderLinkButton(link)}</Fragment>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,40 @@
|
||||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
|
||||
export const CreatorPageLoading = () => {
|
||||
return (
|
||||
<div className="mx-auto w-screen max-w-[1360px]">
|
||||
<main className="mt-5 px-4">
|
||||
<Skeleton className="mb-4 h-6 w-40" />
|
||||
|
||||
<div className="mt-4 flex flex-col items-start gap-4 sm:mt-6 sm:gap-6 md:mt-8 md:flex-row md:gap-8">
|
||||
<div className="w-full md:w-auto md:shrink-0">
|
||||
<Skeleton className="h-80 w-80 rounded-xl" />
|
||||
<div className="mt-4 space-y-2">
|
||||
<Skeleton className="h-6 w-80" />
|
||||
<Skeleton className="h-4 w-80" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex min-w-0 flex-1 flex-col gap-4">
|
||||
<Skeleton className="h-6 w-24" />
|
||||
<Skeleton className="h-8 w-full max-w-xl" />
|
||||
<Skeleton className="h-4 w-1/2" />
|
||||
<div className="flex gap-2">
|
||||
<Skeleton className="h-8 w-8 rounded-full" />
|
||||
<Skeleton className="h-8 w-8 rounded-full" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-8">
|
||||
<Skeleton className="mb-6 h-px w-full" />
|
||||
<div className="grid grid-cols-1 gap-6 sm:grid-cols-2 md:grid-cols-3">
|
||||
{Array.from({ length: 3 }).map((_, i) => (
|
||||
<Skeleton key={i} className="h-32 w-full rounded-lg" />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,90 @@
|
||||
"use client";
|
||||
|
||||
import { Separator } from "@/components/ui/separator";
|
||||
import { AgentsSection } from "../AgentsSection/AgentsSection";
|
||||
import { MarketplaceCreatorPageParams } from "../../creator/[creator]/page";
|
||||
import { Breadcrumbs } from "@/components/molecules/Breadcrumbs/Breadcrumbs";
|
||||
import { CreatorInfoCard } from "../CreatorInfoCard/CreatorInfoCard";
|
||||
import { CreatorLinks } from "../CreatorLinks/CreatorLinks";
|
||||
import { useMainCreatorPage } from "./useMainCreatorPage";
|
||||
import { ErrorCard } from "@/components/molecules/ErrorCard/ErrorCard";
|
||||
import { CreatorPageLoading } from "../CreatorPageLoading";
|
||||
|
||||
interface MainCreatorPageProps {
|
||||
params: MarketplaceCreatorPageParams;
|
||||
}
|
||||
|
||||
export const MainCreatorPage = ({ params }: MainCreatorPageProps) => {
|
||||
const { creatorAgents, creator, isLoading, hasError } = useMainCreatorPage({
|
||||
params,
|
||||
});
|
||||
|
||||
if (isLoading) return <CreatorPageLoading />;
|
||||
|
||||
if (hasError) {
|
||||
return (
|
||||
<div className="mx-auto w-screen max-w-[1360px]">
|
||||
<div className="flex min-h-[60vh] items-center justify-center">
|
||||
<ErrorCard
|
||||
isSuccess={false}
|
||||
responseError={{ message: "Failed to load creator data" }}
|
||||
context="creator page"
|
||||
onRetry={() => window.location.reload()}
|
||||
className="w-full max-w-md"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (creator)
|
||||
return (
|
||||
<div className="mx-auto w-screen max-w-[1360px]">
|
||||
<main className="mt-5 px-4">
|
||||
<Breadcrumbs
|
||||
items={[
|
||||
{ name: "Store", link: "/marketplace" },
|
||||
{ name: creator.name, link: "#" },
|
||||
]}
|
||||
/>
|
||||
|
||||
<div className="mt-4 flex flex-col items-start gap-4 sm:mt-6 sm:gap-6 md:mt-8 md:flex-row md:gap-8">
|
||||
<div className="w-full md:w-auto md:shrink-0">
|
||||
<CreatorInfoCard
|
||||
username={creator.name}
|
||||
handle={creator.username}
|
||||
avatarSrc={creator.avatar_url}
|
||||
categories={creator.top_categories}
|
||||
averageRating={creator.agent_rating}
|
||||
totalRuns={creator.agent_runs}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex min-w-0 flex-1 flex-col gap-4 sm:gap-6 md:gap-8">
|
||||
<p className="text-underline-position-from-font text-decoration-skip-none text-left font-poppins text-base font-medium leading-6">
|
||||
About
|
||||
</p>
|
||||
<div
|
||||
className="text-[48px] font-normal leading-[59px] text-neutral-900 dark:text-zinc-50"
|
||||
style={{ whiteSpace: "pre-line" }}
|
||||
data-testid="creator-description"
|
||||
>
|
||||
{creator.description}
|
||||
</div>
|
||||
|
||||
<CreatorLinks links={creator.links} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-8 sm:mt-12 md:mt-16 lg:pb-[58px]">
|
||||
<Separator className="mb-6 bg-gray-200" />
|
||||
{creatorAgents && (
|
||||
<AgentsSection
|
||||
agents={creatorAgents.agents}
|
||||
hideAvatars={true}
|
||||
sectionTitle={`Agents by ${creator.name}`}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,50 @@
|
||||
import {
|
||||
useGetV2GetCreatorDetails,
|
||||
useGetV2ListStoreAgents,
|
||||
} from "@/app/api/__generated__/endpoints/store/store";
|
||||
import { StoreAgentsResponse } from "@/app/api/__generated__/models/storeAgentsResponse";
|
||||
import { MarketplaceCreatorPageParams } from "../../creator/[creator]/page";
|
||||
import { CreatorDetails } from "@/app/api/__generated__/models/creatorDetails";
|
||||
|
||||
interface useMainCreatorPageProps {
|
||||
params: MarketplaceCreatorPageParams;
|
||||
}
|
||||
|
||||
export const useMainCreatorPage = ({ params }: useMainCreatorPageProps) => {
|
||||
const {
|
||||
data: creatorAgents,
|
||||
isLoading: isCreatorAgentsLoading,
|
||||
isError: isCreatorAgentsError,
|
||||
} = useGetV2ListStoreAgents(
|
||||
{ creator: params.creator },
|
||||
{
|
||||
query: {
|
||||
select: (x) => {
|
||||
return x.data as StoreAgentsResponse;
|
||||
},
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
const {
|
||||
data: creator,
|
||||
isLoading: isCreatorDetailsLoading,
|
||||
isError: isCreatorDetailsError,
|
||||
} = useGetV2GetCreatorDetails(params.creator, {
|
||||
query: {
|
||||
select: (x) => {
|
||||
return x.data as CreatorDetails;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const isLoading = isCreatorAgentsLoading || isCreatorDetailsLoading;
|
||||
const hasError = isCreatorAgentsError || isCreatorDetailsError;
|
||||
|
||||
return {
|
||||
creatorAgents,
|
||||
creator,
|
||||
isLoading,
|
||||
hasError,
|
||||
};
|
||||
};
|
||||
@@ -6,52 +6,29 @@ import { HeroSection } from "../HeroSection/HeroSection";
|
||||
import { AgentsSection } from "../AgentsSection/AgentsSection";
|
||||
import { useMainMarketplacePage } from "./useMainMarketplacePage";
|
||||
import { FeaturedCreators } from "../FeaturedCreators/FeaturedCreators";
|
||||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
import { MainMarketplacePageLoading } from "../MainMarketplacePageLoading";
|
||||
import { ErrorCard } from "@/components/molecules/ErrorCard/ErrorCard";
|
||||
|
||||
export const MainMarkeplacePage = () => {
|
||||
const { featuredAgents, topAgents, featuredCreators, isLoading, hasError } =
|
||||
useMainMarketplacePage();
|
||||
|
||||
// FRONTEND-TODO : Add better Loading Skeletons
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className="mx-auto w-screen max-w-[1360px]">
|
||||
<main className="px-4">
|
||||
<div className="flex flex-col gap-2 pt-16">
|
||||
<div className="flex flex-col items-center justify-center gap-8">
|
||||
<Skeleton className="h-16 w-[60%]" />
|
||||
<Skeleton className="h-12 w-[40%]" />
|
||||
</div>
|
||||
<div className="flex flex-col items-center justify-center gap-8 pt-8">
|
||||
<Skeleton className="h-8 w-[60%]" />
|
||||
</div>
|
||||
<div className="mx-auto flex w-[80%] flex-wrap items-center justify-center gap-8 pt-24">
|
||||
<Skeleton className="h-[12rem] w-[12rem]" />
|
||||
<Skeleton className="h-[12rem] w-[12rem]" />
|
||||
<Skeleton className="h-[12rem] w-[12rem]" />
|
||||
<Skeleton className="h-[12rem] w-[12rem]" />
|
||||
<Skeleton className="h-[12rem] w-[12rem]" />
|
||||
<Skeleton className="h-[12rem] w-[12rem]" />
|
||||
<Skeleton className="h-[12rem] w-[12rem]" />
|
||||
<Skeleton className="h-[12rem] w-[12rem]" />
|
||||
<Skeleton className="h-[12rem] w-[12rem]" />
|
||||
<Skeleton className="h-[12rem] w-[12rem]" />
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
return <MainMarketplacePageLoading />;
|
||||
}
|
||||
|
||||
// FRONTEND-TODO : Add better Error UI
|
||||
if (hasError) {
|
||||
return (
|
||||
<div className="mx-auto w-screen max-w-[1360px]">
|
||||
<main className="px-4">
|
||||
<div className="flex min-h-[400px] items-center justify-center">
|
||||
<div className="text-lg text-red-500">
|
||||
Error loading marketplace data. Please try again later.
|
||||
</div>
|
||||
<ErrorCard
|
||||
isSuccess={false}
|
||||
responseError={{ message: "Failed to load marketplace data" }}
|
||||
context="marketplace page"
|
||||
onRetry={() => window.location.reload()}
|
||||
className="w-full max-w-md"
|
||||
/>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
|
||||
export const MainMarketplacePageLoading = () => {
|
||||
return (
|
||||
<div className="mx-auto w-screen max-w-[1360px]">
|
||||
<main className="px-4">
|
||||
<div className="flex flex-col gap-2 pt-16">
|
||||
<div className="flex flex-col items-center justify-center gap-8">
|
||||
<Skeleton className="h-16 w-[60%]" />
|
||||
<Skeleton className="h-12 w-[40%]" />
|
||||
</div>
|
||||
<div className="flex flex-col items-center justify-center gap-8 pt-8">
|
||||
<Skeleton className="h-8 w-[60%]" />
|
||||
</div>
|
||||
<div className="mx-auto flex w-[80%] flex-wrap items-center justify-center gap-8 pt-24">
|
||||
<Skeleton className="h-[12rem] w-[12rem]" />
|
||||
<Skeleton className="h-[12rem] w-[12rem]" />
|
||||
<Skeleton className="h-[12rem] w-[12rem]" />
|
||||
<Skeleton className="h-[12rem] w-[12rem]" />
|
||||
<Skeleton className="h-[12rem] w-[12rem]" />
|
||||
<Skeleton className="h-[12rem] w-[12rem]" />
|
||||
<Skeleton className="h-[12rem] w-[12rem]" />
|
||||
<Skeleton className="h-[12rem] w-[12rem]" />
|
||||
<Skeleton className="h-[12rem] w-[12rem]" />
|
||||
<Skeleton className="h-[12rem] w-[12rem]" />
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -1,103 +1,55 @@
|
||||
import BackendAPI from "@/lib/autogpt-server-api";
|
||||
import { AgentsSection } from "@/components/agptui/composite/AgentsSection";
|
||||
import { Breadcrumbs } from "@/components/molecules/Breadcrumbs/Breadcrumbs";
|
||||
import { getQueryClient } from "@/lib/react-query/queryClient";
|
||||
import {
|
||||
getV2GetCreatorDetails,
|
||||
prefetchGetV2GetCreatorDetailsQuery,
|
||||
prefetchGetV2ListStoreAgentsQuery,
|
||||
} from "@/app/api/__generated__/endpoints/store/store";
|
||||
import { dehydrate, HydrationBoundary } from "@tanstack/react-query";
|
||||
import { MainCreatorPage } from "../../components/MainCreatorPage/MainCreatorPage";
|
||||
import { Metadata } from "next";
|
||||
import { CreatorInfoCard } from "@/components/agptui/CreatorInfoCard";
|
||||
import { CreatorLinks } from "@/components/agptui/CreatorLinks";
|
||||
import { Separator } from "@/components/ui/separator";
|
||||
import { CreatorDetails } from "@/app/api/__generated__/models/creatorDetails";
|
||||
|
||||
// Force dynamic rendering to avoid static generation issues with cookies
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
type MarketplaceCreatorPageParams = { creator: string };
|
||||
export interface MarketplaceCreatorPageParams {
|
||||
creator: string;
|
||||
}
|
||||
|
||||
export async function generateMetadata({
|
||||
params: _params,
|
||||
}: {
|
||||
params: Promise<MarketplaceCreatorPageParams>;
|
||||
}): Promise<Metadata> {
|
||||
const api = new BackendAPI();
|
||||
const params = await _params;
|
||||
const creator = await api.getStoreCreator(params.creator.toLowerCase());
|
||||
const { data: creator } = await getV2GetCreatorDetails(
|
||||
params.creator.toLowerCase(),
|
||||
);
|
||||
|
||||
return {
|
||||
title: `${creator.name} - AutoGPT Store`,
|
||||
description: creator.description,
|
||||
title: `${(creator as CreatorDetails).name} - AutoGPT Store`,
|
||||
description: (creator as CreatorDetails).description,
|
||||
};
|
||||
}
|
||||
|
||||
// export async function generateStaticParams() {
|
||||
// const api = new BackendAPI();
|
||||
// const creators = await api.getStoreCreators({ featured: true });
|
||||
// return creators.creators.map((creator) => ({
|
||||
// creator: creator.username,
|
||||
// }));
|
||||
// }
|
||||
|
||||
export default async function Page({
|
||||
params: _params,
|
||||
}: {
|
||||
params: Promise<MarketplaceCreatorPageParams>;
|
||||
}) {
|
||||
const api = new BackendAPI();
|
||||
const queryClient = getQueryClient();
|
||||
|
||||
const params = await _params;
|
||||
|
||||
try {
|
||||
const creator = await api.getStoreCreator(params.creator);
|
||||
const creatorAgents = await api.getStoreAgents({ creator: params.creator });
|
||||
await Promise.all([
|
||||
prefetchGetV2ListStoreAgentsQuery(queryClient, {
|
||||
creator: params.creator,
|
||||
}),
|
||||
prefetchGetV2GetCreatorDetailsQuery(queryClient, params.creator),
|
||||
]);
|
||||
|
||||
return (
|
||||
<div className="mx-auto w-screen max-w-[1360px]">
|
||||
<main className="mt-5 px-4">
|
||||
<Breadcrumbs
|
||||
items={[
|
||||
{ name: "Store", link: "/marketplace" },
|
||||
{ name: creator.name, link: "#" },
|
||||
]}
|
||||
/>
|
||||
|
||||
<div className="mt-4 flex flex-col items-start gap-4 sm:mt-6 sm:gap-6 md:mt-8 md:flex-row md:gap-8">
|
||||
<div className="w-full md:w-auto md:shrink-0">
|
||||
<CreatorInfoCard
|
||||
username={creator.name}
|
||||
handle={creator.username}
|
||||
avatarSrc={creator.avatar_url}
|
||||
categories={creator.top_categories}
|
||||
averageRating={creator.agent_rating}
|
||||
totalRuns={creator.agent_runs}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex min-w-0 flex-1 flex-col gap-4 sm:gap-6 md:gap-8">
|
||||
<p className="text-underline-position-from-font text-decoration-skip-none text-left font-poppins text-base font-medium leading-6">
|
||||
About
|
||||
</p>
|
||||
<div
|
||||
data-testid="creator-description"
|
||||
className="text-[48px] font-normal leading-[59px] text-neutral-900 dark:text-zinc-50"
|
||||
style={{ whiteSpace: "pre-line" }}
|
||||
>
|
||||
{creator.description}
|
||||
</div>
|
||||
|
||||
<CreatorLinks links={creator.links} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-8 sm:mt-12 md:mt-16 lg:pb-[58px]">
|
||||
<Separator className="mb-6 bg-gray-200" />
|
||||
<AgentsSection
|
||||
agents={creatorAgents.agents}
|
||||
hideAvatars={true}
|
||||
sectionTitle={`Agents by ${creator.name}`}
|
||||
/>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
} catch {
|
||||
return (
|
||||
<div className="flex h-screen w-full items-center justify-center">
|
||||
<div className="text-2xl text-neutral-900">Creator not found</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<HydrationBoundary state={dehydrate(queryClient)}>
|
||||
<MainCreatorPage params={params} />
|
||||
</HydrationBoundary>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user