fix all components of marketplace and profile according to new design

This commit is contained in:
Abhimanyu Yadav
2025-05-01 18:05:25 +05:30
parent 5ba94958d9
commit 368df7bd3b
42 changed files with 794 additions and 658 deletions

View File

@@ -9,7 +9,7 @@ export default function PlatformLayout({ children }: { children: ReactNode }) {
links={[
{
name: "Home",
href: "/home",
href: "/library",
},
{
name: "Marketplace",
@@ -63,7 +63,7 @@ export default function PlatformLayout({ children }: { children: ReactNode }) {
},
]}
/>
<main>{children}</main>
<main className="w-full pt-8">{children}</main>
</>
);
}

View File

@@ -54,52 +54,46 @@ export default async function Page({
];
return (
<div className="mx-auto w-screen max-w-[1360px]">
<main className="mt-5 px-4">
<BreadCrumbs items={breadcrumbs} />
<main className="mt-9 px-10">
<BreadCrumbs items={breadcrumbs} />
<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">
<AgentInfo
name={agent.agent_name}
creator={agent.creator}
shortDescription={agent.sub_heading}
longDescription={agent.description}
rating={agent.rating}
runs={agent.runs}
categories={agent.categories}
lastUpdated={agent.updated_at}
version={agent.versions[agent.versions.length - 1]}
storeListingVersionId={agent.store_listing_version_id}
/>
</div>
<AgentImages
images={
agent.agent_video
? [agent.agent_video, ...agent.agent_image]
: agent.agent_image
}
<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">
<AgentInfo
name={agent.agent_name}
creator={agent.creator}
shortDescription={agent.sub_heading}
longDescription={agent.description}
rating={agent.rating}
runs={agent.runs}
categories={agent.categories}
lastUpdated={agent.updated_at}
version={agent.versions[agent.versions.length - 1]}
storeListingVersionId={agent.store_listing_version_id}
/>
</div>
<Separator className="mb-[25px] mt-[60px]" />
<AgentsSection
margin="32px"
agents={otherAgents.agents}
sectionTitle={`Other agents by ${agent.creator}`}
<AgentImages
images={
agent.agent_video
? [agent.agent_video, ...agent.agent_image]
: agent.agent_image
}
/>
<Separator className="mb-[25px] mt-[60px]" />
<AgentsSection
margin="32px"
agents={similarAgents.agents}
sectionTitle="Similar agents"
/>
<Separator className="mb-[25px] mt-[60px]" />
<BecomeACreator
title="Become a Creator"
description="Join our ever-growing community of hackers and tinkerers"
buttonText="Become a Creator"
/>
</main>
</div>
</div>
<Separator className="mb-[25px] mt-[60px]" />
<AgentsSection
margin="32px"
agents={otherAgents.agents}
sectionTitle={`Other agents by ${agent.creator}`}
/>
<Separator className="mb-[25px] mt-[60px]" />
<AgentsSection
margin="32px"
agents={similarAgents.agents}
sectionTitle="Similar agents"
/>
<Separator className="mb-[25px] mt-[60px]" />
<BecomeACreator title="Become a Creator" buttonText="Become a Creator" />
</main>
);
}

View File

@@ -44,50 +44,48 @@ export default async function Page({
const creatorAgents = await api.getStoreAgents({ creator: 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: "#" },
]}
/>
<main className="px-10">
<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="font-geist text-underline-position-from-font text-decoration-skip-none text-left text-base font-medium leading-6">
About
</p>
<div
className="font-poppins 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 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>
<CreatorInfoCard
username={creator.name}
handle={creator.username}
avatarSrc={creator.avatar_url}
categories={creator.top_categories}
averageRating={creator.agent_rating}
totalRuns={creator.agent_runs}
/>
</div>
</main>
</div>
<div className="flex-1 space-y-7">
<div>
<p className="font-sans text-base font-medium text-zinc-800">
About
</p>
<h1 className="font-poppins text-4xl font-normal leading-[3.25rem] text-zinc-800">
{creator.description}
</h1>
</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>
);
} catch (error) {
return (

View File

@@ -149,11 +149,13 @@ export default async function Page({}: {}) {
const { featuredAgents, topAgents, featuredCreators } = await getStoreData();
return (
<div className="mx-auto w-screen max-w-[1360px] overflow-x-hidden">
<main>
<main>
<section className="px-10">
<HeroSection />
<FeaturedSection featuredAgents={featuredAgents.agents} />
{/* 100px margin because our featured sections button are placed 40px below the container */}
</section>
<FeaturedSection featuredAgents={featuredAgents.agents} />
{/* 100px margin because our featured sections button are placed 40px below the container */}
<section className="px-10">
<Separator className="mb-6 mt-24" />
<AgentsSection
sectionTitle="Top Agents"
@@ -166,10 +168,9 @@ export default async function Page({}: {}) {
<Separator className="mb-[25px] mt-[60px]" />
<BecomeACreator
title="Become a Creator"
description="Join our ever-growing community of hackers and tinkerers"
buttonText="Become a Creator"
buttonText="Upload your agent"
/>
</main>
</div>
</section>
</main>
);
}

View File

@@ -116,67 +116,67 @@ function SearchResults({
};
return (
<div className="w-full">
<div className="mx-auto min-h-screen max-w-[1440px] px-10 lg:min-w-[1440px]">
<div className="mt-8 flex items-center">
<div className="flex-1">
<h2 className="font-geist text-base font-medium leading-normal text-neutral-800 dark:text-neutral-200">
Results for:
</h2>
<h1 className="font-poppins text-2xl font-semibold leading-[32px] text-neutral-800 dark:text-neutral-100">
{searchTerm}
</h1>
</div>
<div className="flex-none">
<SearchBar width="w-[439px]" height="h-[60px]" />
</div>
<div className="px-10">
<div className="mt-8 flex items-center">
<div className="flex-1">
<h2 className="font-geist text-base font-medium leading-normal text-neutral-800 dark:text-neutral-200">
Results for:
</h2>
<h1 className="font-poppins text-2xl font-semibold leading-[32px] text-neutral-800 dark:text-neutral-100">
{searchTerm}
</h1>
</div>
<div className="flex-none">
<SearchBar width="w-[439px]" height="h-[60px]" />
</div>
</div>
{isLoading ? (
<div className="mt-20 flex flex-col items-center justify-center">
<p className="text-neutral-500 dark:text-neutral-400">Loading...</p>
{isLoading ? (
<div className="mt-20 flex flex-col items-center justify-center">
<p className="text-neutral-500 dark:text-neutral-400">Loading...</p>
</div>
) : totalCount > 0 ? (
<>
<div className="mt-[36px] flex items-center justify-between">
<SearchFilterChips
totalCount={totalCount}
agentsCount={agentsCount}
creatorsCount={creatorsCount}
onFilterChange={handleFilterChange}
/>
<SortDropdown onSort={handleSortChange} />
</div>
) : totalCount > 0 ? (
<>
<div className="mt-[36px] flex items-center justify-between">
<SearchFilterChips
totalCount={totalCount}
agentsCount={agentsCount}
creatorsCount={creatorsCount}
onFilterChange={handleFilterChange}
/>
<SortDropdown onSort={handleSortChange} />
</div>
{/* Content section */}
<div className="min-h-[500px] max-w-[1440px]">
{showAgents && agentsCount > 0 && (
<div className="mt-[36px]">
<AgentsSection agents={agents} sectionTitle="Agents" />
</div>
)}
{/* Content section */}
<div className="">
{showAgents && agentsCount > 0 && (
<div className="mt-[36px]">
<AgentsSection agents={agents} sectionTitle="Agents" />
</div>
)}
{showAgents && agentsCount > 0 && creatorsCount > 0 && (
<Separator />
)}
{showCreators && creatorsCount > 0 && (
{showAgents && agentsCount > 0 && creatorsCount > 0 && (
<Separator />
)}
{showCreators && creatorsCount > 0 && (
<div className="mt-[36px]">
<FeaturedCreators
featuredCreators={creators}
title="Creators"
/>
)}
</div>
</>
) : (
<div className="mt-20 flex flex-col items-center justify-center">
<h3 className="mb-2 text-xl font-medium text-neutral-600 dark:text-neutral-300">
No results found
</h3>
<p className="text-neutral-500 dark:text-neutral-400">
Try adjusting your search terms or filters
</p>
</div>
)}
</div>
)}
</div>
</>
) : (
<div className="mt-20 flex flex-col items-center justify-center">
<h3 className="mb-2 text-xl font-medium text-neutral-600 dark:text-neutral-300">
No results found
</h3>
<p className="text-neutral-500 dark:text-neutral-400">
Try adjusting your search terms or filters
</p>
</div>
)}
</div>
);
}

View File

@@ -13,6 +13,7 @@ import {
} from "@/lib/autogpt-server-api/types";
import useSupabase from "@/hooks/useSupabase";
import { useBackendAPI } from "@/lib/autogpt-server-api/context";
import AutogptButton from "@/components/agptui/AutogptButton";
export default function Page({}: {}) {
const { supabase } = useSupabase();
@@ -64,70 +65,71 @@ export default function Page({}: {}) {
}, []);
return (
<main className="flex-1 py-8">
{/* Header Section */}
<div className="mb-8 flex flex-col gap-4 md:flex-row md:items-end md:justify-between">
<div className="space-y-6">
<h1 className="text-4xl font-medium text-neutral-900 dark:text-neutral-100">
Agent dashboard
</h1>
<div className="space-y-2">
<h2 className="text-xl font-medium text-neutral-900 dark:text-neutral-100">
<main className="flex-1 space-y-7.5 pb-8">
{/* Title */}
<h1 className="font-poppins text-[1.75rem] font-medium leading-[2.5rem] text-zinc-800">
Agent dashboard
</h1>
{/* Content */}
<section className="space-y-8">
<div className="flex items-center justify-between">
<div>
<h2 className="font-poppins text-base font-medium text-zinc-800">
Submit a New Agent
</h2>
<p className="text-sm text-[#707070] dark:text-neutral-400">
<p className="font-sans text-base font-normal text-zinc-600">
Select from the list of agents you currently have, or upload from
your local machine.
</p>
</div>
</div>
<PublishAgentPopout
trigger={
<Button
onClick={onOpenPopout}
className="h-9 rounded-full bg-black px-4 text-sm font-medium text-white hover:bg-neutral-700 dark:hover:bg-neutral-600"
>
Submit agent
</Button>
}
openPopout={openPopout}
inputStep={popoutStep}
submissionData={submissionData}
/>
</div>
<Separator className="mb-8" />
{/* Agents Section */}
<div>
<h2 className="mb-4 text-xl font-bold text-neutral-900 dark:text-neutral-100">
Your uploaded agents
</h2>
{submissions && (
<AgentTable
agents={
submissions?.submissions.map((submission, index) => ({
id: index,
agent_id: submission.agent_id,
agent_version: submission.agent_version,
sub_heading: submission.sub_heading,
date_submitted: submission.date_submitted,
agentName: submission.name,
description: submission.description,
imageSrc: submission.image_urls || [""],
dateSubmitted: new Date(
submission.date_submitted,
).toLocaleDateString(),
status: submission.status.toLowerCase() as StatusType,
runs: submission.runs,
rating: submission.rating,
})) || []
<PublishAgentPopout
trigger={
<AutogptButton onClick={onOpenPopout} variant="outline">
Add to Library
</AutogptButton>
}
onEditSubmission={onEditSubmission}
onDeleteSubmission={onDeleteSubmission}
openPopout={openPopout}
inputStep={popoutStep}
submissionData={submissionData}
/>
)}
</div>
</div>
<Separator className="bg-neutral-300" />
<div className="space-y-3">
<h2 className="font-poppins text-base font-medium text-zinc-800">
Your uploaded agents
</h2>
{submissions && (
<AgentTable
agents={
submissions?.submissions.map((submission, index) => ({
id: index,
agent_id: submission.agent_id,
agent_version: submission.agent_version,
sub_heading: submission.sub_heading,
date_submitted: submission.date_submitted,
agentName: submission.name,
description: submission.description,
imageSrc: submission.image_urls || [""],
dateSubmitted: new Date(
submission.date_submitted,
).toLocaleDateString(),
status: submission.status.toLowerCase() as StatusType,
runs: submission.runs,
rating: submission.rating,
})) || []
}
onEditSubmission={onEditSubmission}
onDeleteSubmission={onDeleteSubmission}
/>
)}
</div>
</section>
</main>
);
}

View File

@@ -149,7 +149,7 @@ export default function PrivatePage() {
: [];
return (
<div className="mx-auto max-w-3xl md:py-8">
<div className="mx-auto max-w-3xl md:pb-8">
<h2 className="mb-4 text-lg">Connections & Credentials</h2>
<Table>
<TableHeader>

View File

@@ -16,45 +16,45 @@ export default function Layout({ children }: { children: React.ReactNode }) {
{
text: "Creator Dashboard",
href: "/profile/dashboard",
icon: <IconDashboardLayout className="h-6 w-6" />,
icon: <IconDashboardLayout className="h-6 w-6 stroke-[1.25px]" />,
},
...(process.env.NEXT_PUBLIC_SHOW_BILLING_PAGE === "true"
? [
{
text: "Billing",
href: "/profile/credits",
icon: <IconCoin className="h-6 w-6" />,
icon: <IconCoin className="h-6 w-6 stroke-[1.25px]" />,
},
]
: []),
{
text: "Integrations",
href: "/profile/integrations",
icon: <IconIntegrations className="h-6 w-6" />,
icon: <IconIntegrations className="h-6 w-6 stroke-[1.25px]" />,
},
{
text: "API Keys",
href: "/profile/api_keys",
icon: <KeyIcon className="h-6 w-6" />,
icon: <KeyIcon className="h-6 w-6 stroke-[1.25px]" />,
},
{
text: "Profile",
href: "/profile",
icon: <IconProfile className="h-6 w-6" />,
icon: <IconProfile className="h-6 w-6 stroke-[1.25px]" />,
},
{
text: "Settings",
href: "/profile/settings",
icon: <IconSliders className="h-6 w-6" />,
icon: <IconSliders className="h-6 w-6 stroke-[1.25px]" />,
},
],
},
];
return (
<div className="flex min-h-screen w-screen max-w-[1360px] flex-col lg:flex-row">
<div className="flex flex-row gap-14 px-4 pr-10">
<Sidebar linkGroups={sidebarLinkGroups} />
<div className="flex-1 pl-4">{children}</div>
<div className="flex-1">{children}</div>
</div>
);
}

View File

@@ -30,7 +30,10 @@ export default async function Page({}: {}) {
}
return (
<div className="flex flex-col items-center justify-center px-4">
<div className="space-y-6 pb-10">
<h1 className="font-poppins text-[1.75rem] font-medium leading-[2.5rem] text-zinc-500">
Profile
</h1>
<ProfileInfoForm profile={profile as CreatorDetails} />
</div>
);

View File

@@ -19,13 +19,12 @@ export default async function SettingsPage() {
const preferences = await getUserPreferences();
return (
<div className="container max-w-2xl space-y-6 py-10">
<div>
<h3 className="text-lg font-medium">My account</h3>
<p className="text-sm text-muted-foreground">
Manage your account settings and preferences.
</p>
</div>
<div className="space-y-6 pb-10">
{/* Title */}
<h1 className="font-poppins text-[1.75rem] font-medium leading-[2.5rem] text-zinc-500">
Settings
</h1>
<SettingsForm user={user} preferences={preferences} />
</div>
);

View File

@@ -38,7 +38,7 @@ export default async function RootLayout({
>
<body
className={cn(
"bg-neutral-50 antialiased transition-colors",
"mx-auto w-screen max-w-[1500px] overflow-x-hidden bg-white antialiased transition-colors",
inter.className,
)}
>

View File

@@ -11,6 +11,8 @@ import { useToast } from "@/components/ui/use-toast";
import useSupabase from "@/hooks/useSupabase";
import { DownloadIcon, LoaderIcon } from "lucide-react";
import { useOnboarding } from "../onboarding/onboarding-provider";
import AutogptButton from "./AutogptButton";
import { Chip } from "./Chip";
interface AgentInfoProps {
name: string;
creator: string;
@@ -97,37 +99,33 @@ export const AgentInfo: React.FC<AgentInfoProps> = ({
};
return (
<div className="w-full max-w-[25rem] space-y-11">
<div className="w-full max-w-[27rem] space-y-7">
{/* Top part */}
<div>
<h2 className="mb-3 font-poppins text-4xl font-medium text-neutral-900">
{name}
</h2>
<div className="mb-7 flex w-full items-center gap-1.5 font-sans">
<p className="text-xl font-normal text-neutral-800">by</p>
<Link
href={`/marketplace/creator/${encodeURIComponent(creator)}`}
className="text-xl font-medium text-neutral-800 hover:underline"
>
{creator}
</Link>
<div className="space-y-14">
{/* Agent name */}
<div>
<h2 className="font-poppins text-[1.75rem] font-medium leading-[2.5rem] text-zinc-800">
{name}
</h2>
{/* Creator name */}
<div className="mb-7 flex w-full items-center gap-1.5 font-sans">
<p className="text-base font-normal text-zinc-800">by</p>
<Link
href={`/marketplace/creator/${encodeURIComponent(creator)}`}
className="text-base font-medium text-zinc-800 hover:underline"
>
{creator}
</Link>
</div>
</div>
<p className="mb-7 line-clamp-2 font-sans text-xl font-normal text-neutral-600">
{shortDescription}
</p>
<div className="mb-14 w-full">
{/* Download and run button */}
<div className="w-full">
{user ? (
<button
onClick={handleAddToLibrary}
className="inline-flex w-full items-center justify-center gap-2 rounded-[38px] bg-violet-600 px-4 py-3 transition-colors hover:bg-violet-700 sm:w-auto sm:gap-2.5 sm:px-5 sm:py-3.5 lg:px-6 lg:py-4"
>
<IconPlay className="h-5 w-5 text-white sm:h-5 sm:w-5 lg:h-6 lg:w-6" />
<span className="font-poppins text-base font-medium text-neutral-50 sm:text-lg">
Add To Library
</span>
</button>
<AutogptButton variant={"secondary"} onClick={handleAddToLibrary}>
Add To Library
</AutogptButton>
) : (
<button
onClick={handleDownloadToLibrary}
@@ -149,47 +147,42 @@ export const AgentInfo: React.FC<AgentInfoProps> = ({
</button>
)}
</div>
<div className="flex w-full items-center justify-between">
{/* Runs and ratings */}
<div className="flex w-full items-center gap-10">
<div className="flex items-center gap-1.5 sm:gap-2">
<span className="font-sans text-lg font-semibold text-neutral-800">
<span className="font-sans text-base font-medium text-zinc-800">
{rating.toFixed(1)}
</span>
<div className="flex gap-0.5">{StarRatingIcons(rating)}</div>
</div>
<div className="font-sans text-lg font-semibold text-neutral-800">
<div className="font-sans text-base font-medium text-zinc-800">
{runs.toLocaleString()} runs
</div>
</div>
</div>
{/* Separator */}
<Separator className="mb-4 lg:mb-[44px]" />
<Separator className="bg-neutral-300" />
{/* Bottom part */}
<div className="space-y-9">
<div className="space-y-2.5">
<p className="font-sans text-base font-medium text-neutral-800">
<p className="font-sans text-base font-medium text-zinc-800">
Description
</p>
<p className="whitespace-pre-line font-sans text-base font-normal text-neutral-600">
<p className="whitespace-pre-line font-sans text-base font-normal text-zinc-600">
{longDescription}
</p>
</div>
{/* Categories */}
<div className="space-y-2.5">
<p className="font-sans text-base font-medium text-neutral-800">
<p className="font-sans text-base font-medium text-zinc-800">
Categories
</p>
<div className="flex flex-wrap gap-2.5">
{categories.map((category, index) => (
<p
key={index}
className="rounded-full border border-neutral-600 bg-white px-5 py-3 font-sans text-base font-normal text-neutral-800"
>
{category}
</p>
<Chip key={index}>{category}</Chip>
))}
</div>
</div>
@@ -197,16 +190,18 @@ export const AgentInfo: React.FC<AgentInfoProps> = ({
{/* TODO : Rating Agent */}
{/* Version History */}
<div className="flex flex-col gap-2.5">
<p className="font-base font-sans font-medium text-neutral-800">
<div className="space-y-2.5">
<p className="font-sans text-base font-medium text-zinc-800">
Version history
</p>
<p className="font-sans text-base font-normal text-neutral-600">
Last updated {lastUpdated}
</p>
<p className="font-sans text-base font-normal text-neutral-600">
Version {version}
</p>
<div className="space-y-1.5">
<p className="font-sans text-base font-normal text-zinc-600">
Last updated {lastUpdated}
</p>
<p className="font-sans text-base font-normal text-zinc-600">
Version {version}
</p>
</div>
</div>
</div>
</div>

View File

@@ -49,7 +49,7 @@ export const AgentTable: React.FC<AgentTableProps> = ({
);
return (
<div className="w-full">
<div className="w-full border-t border-neutral-300">
{/* Table for desktop view */}
<div className="hidden md:block">
<Table>
@@ -106,7 +106,7 @@ export const AgentTable: React.FC<AgentTableProps> = ({
) : (
<TableRow>
<TableCell colSpan={7} className="py-4 text-center">
<span className="font-sans text-base text-neutral-600 dark:text-neutral-400">
<span className="font-sans text-base text-zinc-600 dark:text-neutral-400">
No agents available. Create your first agent to get started!
</span>
</TableCell>

View File

@@ -126,3 +126,10 @@ export const Link: Story = {
variant: "link",
},
};
export const Loading: Story = {
args: {
children: "Loading Button",
isLoading: true,
},
};

View File

@@ -1,4 +1,4 @@
import { PlayIcon } from "lucide-react";
import { PlayIcon, Loader2Icon } from "lucide-react";
import { Button } from "../ui/button";
import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "@/lib/utils";
@@ -15,7 +15,7 @@ const buttonVariants = cva("inline-flex items-center justify-center", {
outline:
"bg-white text-zinc-800 hover:bg-zinc-100 border border-zinc-700 disabled:bg-white disabled:border-zinc-300 disabled:text-zinc-300",
ghost:
"bg-transparent text-zinc-800 hover:bg-zinc-100 disabled:text-zinc-300 border-none",
"bg-transparent text-zinc-800 hover:bg-zinc-100 disabled:text-zinc-300 disabled:bg-transparent border-none",
link: "bg-transparent text-zinc-800 hover:underline border-none hover:bg-transparent",
},
},
@@ -28,6 +28,8 @@ interface AutogptButtonProps extends VariantProps<typeof buttonVariants> {
icon?: boolean;
children: React.ReactNode;
isDisabled?: boolean;
isLoading?: boolean;
[key: string]: any; // Allow any additional props
}
const AutogptButton = ({
@@ -35,6 +37,8 @@ const AutogptButton = ({
children,
variant,
isDisabled = false,
isLoading = false,
...props
}: AutogptButtonProps) => {
return (
<Button
@@ -42,10 +46,17 @@ const AutogptButton = ({
"h-12 space-x-1.5 rounded-[3rem] px-4 py-3 shadow-none",
buttonVariants({ variant }),
isDisabled && "bg-red-500",
isLoading && "bg-[#3F3F4680] text-white",
)}
disabled={isDisabled}
disabled={isDisabled || isLoading}
variant={variant}
{...props}
>
{icon && <PlayIcon className="h-5 w-5" />}
{isLoading ? (
<Loader2Icon className="h-4 w-4 animate-spin" />
) : (
icon && <PlayIcon className="h-5 w-5" />
)}
<p className="font-sans text-sm font-medium">{children}</p>
</Button>
);

View File

@@ -0,0 +1,39 @@
import { Meta, StoryObj } from "@storybook/react";
import AutogptInput from "./AutogptInput";
const meta: Meta<typeof AutogptInput> = {
title: "new/AutogptInput",
component: AutogptInput,
decorators: [
(Story) => (
<div className="flex items-center justify-center bg-[#E1E1E1] p-4">
<Story />
</div>
),
],
tags: ["autodocs"],
};
export default meta;
type Story = StoryObj<typeof AutogptInput>;
export const Default: Story = {
args: {
label: "Email",
placeholder: "Type something...",
onChange: () => {
console.log("It's working");
},
},
};
export const IsDisabled: Story = {
args: {
label: "Email",
placeholder: "Type something...",
isDisabled: true,
onChange: () => {
console.log("It's working");
},
},
};

View File

@@ -0,0 +1,37 @@
import React from "react";
import { Input } from "../ui/input";
import { Label } from "../ui/label";
import { cn } from "@/lib/utils";
interface AutogptInputProps
extends React.InputHTMLAttributes<HTMLInputElement> {
className?: string;
label?: string;
isDisabled?: boolean;
}
const AutogptInput: React.FC<AutogptInputProps> = ({
label,
isDisabled,
...props
}) => {
return (
<div className={cn("flex flex-col gap-2", isDisabled && "opacity-50")}>
{label && (
<Label className="font-sans text-sm font-medium leading-[1.4rem]">
{label}
</Label>
)}
<Input
{...props}
disabled={isDisabled}
className={cn(
"m-0 h-10 w-full rounded-3xl border border-zinc-300 bg-white py-2 pl-4 font-sans text-base font-normal text-zinc-800 shadow-none outline-none placeholder:text-zinc-400 focus:border-2 focus:border-[#CBD5E1] focus:shadow-none focus:ring-0",
props.className,
)}
/>
</div>
);
};
export default AutogptInput;

View File

@@ -2,16 +2,15 @@
import * as React from "react";
import { PublishAgentPopout } from "./composite/PublishAgentPopout";
import AutogptButton from "./AutogptButton";
interface BecomeACreatorProps {
title?: string;
description?: string;
buttonText?: string;
onButtonClick?: () => void;
}
export const BecomeACreator: React.FC<BecomeACreatorProps> = ({
title = "Become a creator",
description = "Join a community where your AI creations can inspire, engage, and be downloaded by users around the world.",
buttonText = "Upload your agent",
onButtonClick,
}) => {
@@ -22,32 +21,28 @@ export const BecomeACreator: React.FC<BecomeACreatorProps> = ({
return (
<div className="mb-18 w-full sm:mb-36 md:mb-72">
{/* Title */}
<h2 className="mb-18 font-poppins text-lg font-semibold text-neutral-800">
<h2 className="mb-18 text-base font-medium text-zinc-500 dark:text-zinc-200">
{title}
</h2>
{/* Content Container */}
<div className="flex flex-col items-center justify-center">
<h2 className="mb-9 text-center font-poppins text-3xl font-semibold leading-[3rem] text-neutral-950 md:text-5xl">
<h2 className="mb-9 text-center font-poppins text-3xl font-semibold leading-[3.5rem] text-neutral-950 md:text-[2.75rem]">
Build AI agents and share
<br />
your vision
<span className="text-violet-600"> your </span>
vision
</h2>
<p className="mb-12 text-center font-sans text-lg font-normal text-neutral-700 md:text-2xl">
{description}
<p className="mb-12 text-center font-sans text-lg font-normal text-zinc-600">
Join a community where your AI creations can inspire, engage, <br />{" "}
and be downloaded by users around the world.
</p>
<PublishAgentPopout
trigger={
<button
onClick={handleButtonClick}
className="inline-flex h-[48px] cursor-pointer items-center justify-center rounded-[38px] bg-neutral-800 px-8 py-3 transition-colors hover:bg-neutral-700 dark:bg-neutral-700 dark:hover:bg-neutral-600 md:h-[56px] md:px-10 md:py-4 lg:h-[68px] lg:px-12 lg:py-5"
>
<span className="whitespace-nowrap font-poppins text-base font-medium leading-normal text-neutral-50 md:text-lg md:leading-relaxed lg:text-xl lg:leading-7">
{buttonText}
</span>
</button>
<AutogptButton onClick={handleButtonClick}>
{buttonText}
</AutogptButton>
}
/>
</div>

View File

@@ -0,0 +1,25 @@
import type { Meta, StoryObj } from "@storybook/react";
import { Chip } from "./Chip";
const meta: Meta<typeof Chip> = {
component: Chip,
title: "new/BasicBadge",
argTypes: {
children: {
control: "text",
description: "The content of the badge",
},
},
parameters: {
layout: "centered",
},
};
export default meta;
type Story = StoryObj<typeof Chip>;
export const Default: Story = {
args: {
children: "Marketing",
},
};

View File

@@ -0,0 +1,22 @@
import React from "react";
import { Badge } from "@/components/ui/badge";
import { cn } from "@/lib/utils";
export function Chip({
children,
className,
}: {
children?: React.ReactNode;
className?: string;
}) {
return (
<Badge
className={cn(
"rounded-[30px] border border-zinc-400 bg-white px-3.5 py-2 font-sans text-base font-normal text-zinc-600 shadow-none hover:border-zinc-500 hover:bg-zinc-100",
className,
)}
>
{children}
</Badge>
);
}

View File

@@ -3,7 +3,7 @@ import { CreatorCard } from "./CreatorCard";
import { userEvent, within, expect } from "@storybook/test";
const meta = {
title: "AGPT UI/Creator Card",
title: "new/Creator Card",
component: CreatorCard,
decorators: [
(Story) => (

View File

@@ -29,7 +29,7 @@ export const CreatorCard: React.FC<CreatorCardProps> = ({
return (
<div
className={`h-64 w-full px-5 pb-5 pt-6 md:w-80 ${backgroundColor} inline-flex cursor-pointer flex-col items-start justify-start gap-3.5 rounded-[1.5rem] transition-all duration-200 hover:brightness-95`}
className={`aspect-square w-80 space-y-4 rounded-3xl bg-amber-100 p-5 pt-6 hover:cursor-pointer hover:bg-amber-200`}
onClick={onClick}
data-testid="creator-card"
>
@@ -37,25 +37,26 @@ export const CreatorCard: React.FC<CreatorCardProps> = ({
<Image
src={creatorImage}
alt={creatorName}
width={80}
height={80}
className="h-20 w-20 rounded-full"
width={84}
height={84}
className="rounded-full"
priority
/>
) : (
<div className="h-20 w-20 rounded-full bg-neutral-300 dark:bg-neutral-600" />
)}
<div className="flex flex-col gap-2">
<h3 className="line-clamp-1 font-poppins text-2xl font-semibold text-neutral-800 dark:text-neutral-100">
<div className="flex h-36 flex-col gap-2">
<h3 className="line-clamp-1 font-poppins text-3xl font-medium text-zinc-800 dark:text-neutral-100">
{creatorName}
</h3>
<p className="line-clamp-2 font-sans text-base font-normal text-neutral-800 dark:text-neutral-400">
<p className="line-clamp-3 font-sans text-base font-normal text-zinc-600 dark:text-neutral-400">
{bio}
</p>
<div className="font-sans text-lg font-semibold text-neutral-800 dark:text-neutral-200">
{agentsUploaded} agents
</div>
</div>
<div className="font-sans text-sm font-medium text-zinc-800 dark:text-neutral-200">
{agentsUploaded} agents
</div>
</div>
);

View File

@@ -2,7 +2,7 @@ import type { Meta, StoryObj } from "@storybook/react";
import { CreatorInfoCard } from "./CreatorInfoCard";
const meta = {
title: "AGPT UI/Creator Info Card",
title: "new/Creator Info Card",
component: CreatorInfoCard,
decorators: [
(Story) => (

View File

@@ -2,6 +2,7 @@ import * as React from "react";
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
import { StarRatingIcons } from "@/components/ui/icons";
import { Separator } from "../ui/separator";
import { Chip } from "./Chip";
interface CreatorInfoCardProps {
username: string;
@@ -22,95 +23,84 @@ export const CreatorInfoCard: React.FC<CreatorInfoCardProps> = ({
}) => {
return (
<div
className="inline-flex h-auto min-h-[32rem] w-full max-w-[440px] flex-col items-start justify-between overflow-hidden rounded-[26px] bg-violet-100 p-4 dark:bg-violet-900 sm:min-h-[40rem] sm:w-[440px] sm:p-6"
className="h-auto w-full max-w-md space-y-6 overflow-hidden rounded-[26px] bg-violet-100 px-5 pb-7 pt-6 dark:bg-violet-900 sm:w-[27rem]"
role="article"
aria-label={`Creator profile for ${username}`}
>
{/* Avatar + Basic Info */}
<div className="flex w-full flex-col items-start justify-start gap-3.5">
<Avatar className="h-[100px] w-[100px] sm:h-[130px] sm:w-[130px]">
<Avatar className="h-[100px] w-[100px]">
<AvatarImage
width={130}
height={130}
width={100}
height={100}
src={avatarSrc}
alt={`${username}'s avatar`}
/>
<AvatarFallback
size={130}
className="h-[100px] w-[100px] sm:h-[130px] sm:w-[130px]"
>
<AvatarFallback size={100} className="h-[100px] w-[100px]">
{username.charAt(0)}
</AvatarFallback>
</Avatar>
<div className="flex w-full flex-col items-start justify-start gap-1.5">
<div className="w-full font-poppins text-2xl font-medium text-neutral-900 dark:text-neutral-100 sm:text-4xl">
<div className="w-full font-poppins text-3xl font-medium text-zinc-800 dark:text-zinc-100">
{username}
</div>
<div className="w-full font-sans text-lg font-normal text-neutral-800 dark:text-neutral-200 sm:text-xl">
<div className="w-full font-sans text-base font-normal text-zinc-800 dark:text-zinc-200">
@{handle}
</div>
</div>
</div>
<div className="my-4 flex w-full flex-col items-start justify-start gap-6 sm:gap-12">
{/* Categories */}
<div className="flex w-full flex-col items-start justify-start gap-3">
<Separator className="bg-neutral-700" />
<div className="flex flex-col items-start justify-start gap-2.5">
<div className="w-full font-sans text-sm font-medium text-neutral-800 dark:text-neutral-200 sm:text-base">
Top categories
<Separator className="bg-zinc-300" />
<div className="flex flex-col items-start justify-start gap-2.5">
<div className="w-full font-sans text-sm font-medium text-zinc-800 dark:text-zinc-200 sm:text-base">
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"
role="listitem"
>
<Chip className="bg-transparent">{category}</Chip>
</div>
))}
</div>
</div>
<Separator className="bg-zinc-300" />
<div className="flex w-full flex-col items-center justify-between gap-4 sm:flex-row sm:gap-0">
{/* Average Rating */}
<div className="flex w-full flex-col items-start justify-start gap-2.5">
<div className="w-full font-sans text-sm font-medium leading-normal text-zinc-800 dark:text-zinc-200 sm:text-base">
Average rating
</div>
<div className="inline-flex items-center gap-2">
<div className="font-sans text-sm font-medium text-zinc-800 dark:text-zinc-200">
{averageRating.toFixed(1)}
</div>
<div
className="flex flex-wrap items-center gap-2.5"
role="list"
aria-label="Categories"
className="flex items-center gap-px"
role="img"
aria-label={`Rating: ${averageRating} out of 5 stars`}
>
{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="font-sans text-sm font-normal text-neutral-800 dark:text-neutral-200 sm:text-base">
{category}
</div>
</div>
))}
{StarRatingIcons(averageRating)}
</div>
</div>
</div>
{/* Ratings */}
<div className="flex w-full flex-col items-start justify-start gap-3">
<Separator className="bg-neutral-700" />
<div className="flex w-full flex-col items-center justify-between gap-4 sm:flex-row sm:gap-0">
{/* Average Rating */}
<div className="flex w-full flex-col items-start justify-start gap-2.5">
<div className="w-full font-sans text-sm font-medium leading-normal text-neutral-800 dark:text-neutral-200 sm:text-base">
Average rating
</div>
<div className="inline-flex items-center gap-2">
<div className="font-sans text-base font-semibold text-neutral-800 dark:text-neutral-200 sm:text-lg">
{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>
{/* Number of runs */}
<div className="flex w-full flex-col items-start justify-start gap-2.5">
<div className="w-full font-sans text-sm font-medium leading-normal text-neutral-800 dark:text-neutral-200 sm:text-base">
Number of runs
</div>
<div className="font-sans text-sm font-semibold text-neutral-800 dark:text-neutral-200 sm:text-lg">
{new Intl.NumberFormat().format(totalRuns)} runs
</div>
</div>
{/* Number of runs */}
<div className="flex w-full flex-col items-start justify-start gap-2.5">
<div className="w-full font-sans text-sm font-medium leading-normal text-zinc-800 dark:text-zinc-200 sm:text-base">
Number of runs
</div>
<div className="font-sans text-sm font-medium text-zinc-800 dark:text-zinc-200 sm:text-base">
{new Intl.NumberFormat().format(totalRuns)} runs
</div>
</div>
</div>

View File

@@ -0,0 +1,29 @@
import type { Meta, StoryObj } from "@storybook/react";
import CreatorLink from "./CreatorLink";
const meta: Meta<typeof CreatorLink> = {
title: "new/CreatorLink",
component: CreatorLink,
tags: ["autodocs"],
decorators: [
(Story) => (
<div className="flex items-center justify-center p-4">
<Story />
</div>
),
],
argTypes: {
href: { control: "text" },
children: { control: "text" },
},
};
export default meta;
type Story = StoryObj<typeof CreatorLink>;
export const Default: Story = {
args: {
href: "https://linkedin.com",
children: "View Creator Profile",
},
};

View File

@@ -0,0 +1,36 @@
import { FC } from "react";
import Link from "next/link";
import { cn } from "@/lib/utils";
import { ExternalLink } from "lucide-react";
interface CreatorLinkProps {
href: string;
children: React.ReactNode;
className?: string;
key?: number;
}
const CreatorLink: FC<CreatorLinkProps> = ({
href,
children,
className,
key,
}) => {
return (
<Link
key={key}
href={href}
className={cn(
"flex h-12 w-full min-w-80 max-w-md items-center justify-between rounded-[34px] border border-neutral-600 bg-transparent px-5 py-3",
className,
)}
>
<p className="font-sans text-base font-medium text-neutral-800">
{children}
</p>
<ExternalLink className="h-5 w-5 stroke-[1.5px]" />
</Link>
);
};
export default CreatorLink;

View File

@@ -1,6 +1,5 @@
import * as React from "react";
import { getIconForSocial } from "@/components/ui/icons";
import Link from "next/link";
import CreatorLink from "./CreatorLink";
interface CreatorLinksProps {
links: string[];
@@ -11,32 +10,16 @@ export const CreatorLinks: React.FC<CreatorLinksProps> = ({ links }) => {
return null;
}
const renderLinkButton = (url: string) => (
<Link
href={url}
target="_blank"
rel="noopener noreferrer"
className="flex items-center justify-between rounded-[34px] border border-neutral-600 px-5 py-3 dark:border-neutral-400"
>
<div className="font-sans text-base font-medium 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>
</Link>
);
return (
<div className="flex w-full flex-col items-start justify-start gap-4">
<div className="font-sans text-base font-medium text-neutral-800 dark:text-neutral-200">
<div className="space-y-4">
<div className="font-sans text-base font-medium text-zinc-800">
Other links
</div>
<div className="grid w-full grid-cols-1 gap-3 sm:grid-cols-2">
{links.map((link, index) => (
<React.Fragment key={index}>{renderLinkButton(link)}</React.Fragment>
<CreatorLink href={link} key={index}>
{new URL(link).hostname.replace("www.", "").replace(".com", "")}
</CreatorLink>
))}
</div>
</div>

View File

@@ -33,7 +33,7 @@ export const FeaturedAgentCard: React.FC<FeaturedStoreCardProps> = ({
<CardTitle className="line-clamp-3 font-poppins text-3xl font-medium text-zinc-800">
{agent.agent_name}
</CardTitle>
<CardDescription className="line-clamp-1 font-sans text-lg font-normal text-zinc-800">
<CardDescription className="line-clamp-1 font-sans text-base font-normal text-zinc-800">
By {agent.creator}
</CardDescription>
</CardHeader>
@@ -65,7 +65,7 @@ export const FeaturedAgentCard: React.FC<FeaturedStoreCardProps> = ({
>
<CardDescription
data-testid="agent-description"
className="line-clamp-6 font-sans text-sm text-neutral-800"
className="line-clamp-6 font-sans text-sm text-zinc-600"
>
{agent.description}
</CardDescription>

View File

@@ -2,6 +2,7 @@
import * as React from "react";
import { Badge } from "@/components/ui/badge";
import { Chip } from "./Chip";
interface FilterChipsProps {
badges: string[];
@@ -38,17 +39,13 @@ export const FilterChips: React.FC<FilterChipsProps> = ({
return (
<div className="flex flex-wrap items-center justify-center gap-3">
{badges.map((badge) => (
<Badge
<div
data-testid="filter-chip"
key={badge}
variant={selectedFilters.includes(badge) ? "secondary" : "outline"}
className="rounded-[2rem] border border-neutral-600 px-5 py-3 hover:cursor-pointer hover:bg-neutral-200"
onClick={() => handleBadgeClick(badge)}
>
<p className="font-sans text-base font-normal text-neutral-800 md:text-xl">
{badge}
</p>
</Badge>
<Chip className="hover:cursor-pointer">{badge}</Chip>
</div>
))}
</div>
);

View File

@@ -17,7 +17,7 @@ interface NavbarLinkProps {
const icons = {
"/marketplace": IconShoppingCart,
"/build": IconBoxes,
"/monitor": IconLaptop,
"/library": IconLibrary,
"/home": IconLibrary,
};

View File

@@ -1,7 +1,7 @@
"use client";
import * as React from "react";
import { useState } from "react";
import { useState, useRef } from "react";
import Image from "next/image";
@@ -14,12 +14,15 @@ import { useBackendAPI } from "@/lib/autogpt-server-api/context";
import { Input } from "../ui/input";
import { Label } from "../ui/label";
import { Textarea } from "../ui/textarea";
import AutogptButton from "./AutogptButton";
import AutogptInput from "./AutogptInput";
export const ProfileInfoForm = ({ profile }: { profile: CreatorDetails }) => {
const [isSubmitting, setIsSubmitting] = useState(false);
const [profileData, setProfileData] = useState(profile);
const { supabase } = useSupabase();
const api = useBackendAPI();
const editPhotoRef = useRef<HTMLInputElement>(null);
const submitForm = async () => {
try {
@@ -101,168 +104,156 @@ export const ProfileInfoForm = ({ profile }: { profile: CreatorDetails }) => {
};
return (
<div className="md:min-w-md w-full px-4 sm:px-8">
<h1 className="mb-6 font-poppins text-4xl font-medium text-neutral-900">
Profile
</h1>
<div className="mb-8 sm:mb-12">
<div className="mb-8 flex flex-col items-center gap-4 sm:flex-row">
<div className="flex h-[6.25rem] w-[6.25rem] items-center justify-center rounded-full bg-[#DADADA]">
{profileData.avatar_url ? (
<Image
src={profileData.avatar_url}
alt="Profile"
fill
className="rounded-full"
/>
) : (
<IconPersonFill className="h-10 w-10 text-[#7e7e7e]" />
)}
</div>
<div>
<Input
type="file"
accept="image/*"
className="hidden"
ref={editPhotoRef}
onChange={async (e) => {
const file = e.target.files?.[0];
if (file) {
await handleImageUpload(file);
}
}}
/>
<AutogptButton onClick={() => editPhotoRef.current?.click()}>
Edit photo
</AutogptButton>
</div>
</div>
<div className="mb-8 sm:mb-12">
<div className="mb-8 flex flex-col items-center gap-4 sm:flex-row sm:items-start">
<div className="relative h-[130px] w-[130px] rounded-full bg-[#d9d9d9] dark:bg-[#333333]">
{profileData.avatar_url ? (
<Image
src={profileData.avatar_url}
alt="Profile"
fill
className="rounded-full"
/>
) : (
<IconPersonFill className="absolute left-[30px] top-[24px] h-[77.80px] w-[70.63px] text-[#7e7e7e] dark:text-[#999999]" />
)}
</div>
<Label className="mt-11 inline-flex h-[43px] items-center justify-center rounded-[22px] bg-[#15171A] px-6 py-2 font-sans text-sm font-normal text-white transition-colors hover:cursor-pointer hover:bg-[#2D2F34] dark:bg-white dark:text-[#15171A] dark:hover:bg-[#E5E5E5]">
<Input
type="file"
accept="image/*"
className="hidden"
onChange={async (e) => {
const file = e.target.files?.[0];
if (file) {
await handleImageUpload(file);
}
<form className="space-y-10" onSubmit={submitForm}>
{/* Top section */}
<section className="max-w-3xl space-y-6">
<AutogptInput
label="Display name"
type="text"
name="displayName"
defaultValue={profileData.name}
placeholder="Enter your display name"
className="h-11 w-full rounded-full border border-[#E2E8F0] px-4 py-2.5 font-inter text-base font-normal text-[#7e7e7e] outline-none"
onChange={(e) => {
const newProfileData = {
...profileData,
name: e.target.value,
};
setProfileData(newProfileData);
}}
/>
<AutogptInput
label="Handle"
type="text"
name="handle"
defaultValue={profileData.username}
placeholder="@username"
className="h-11 w-full rounded-full border border-[#E2E8F0] px-4 py-2.5 font-inter text-base font-normal text-[#7e7e7e] outline-none"
onChange={(e) => {
const newProfileData = {
...profileData,
username: e.target.value,
};
setProfileData(newProfileData);
}}
/>
<div className="w-full space-y-1.5">
<Label className="font-sans text-sm font-medium leading-[1.4rem]">
Bio
</Label>
<Textarea
name="bio"
defaultValue={profileData.description}
placeholder="Tell us about yourself..."
className="m-0 h-10 min-h-56 w-full resize-none rounded-3xl border border-zinc-300 bg-white py-2 pl-4 font-sans text-base font-normal text-zinc-800 shadow-none outline-none placeholder:text-zinc-400 focus:border-2 focus:border-[#CBD5E1] focus:shadow-none focus:ring-0"
onChange={(e) => {
const newProfileData = {
...profileData,
description: e.target.value,
};
setProfileData(newProfileData);
}}
/>
Edit photo
</Label>
</div>
</div>
</section>
<form className="space-y-8" onSubmit={submitForm}>
{/* Top section */}
<section className="space-y-6">
<div className="w-full space-y-1.5">
<Label className="block font-sans text-base font-medium text-[#020617]">
Display name
</Label>
<Input
type="text"
name="displayName"
defaultValue={profileData.name}
placeholder="Enter your display name"
className="h-11 w-full rounded-full border border-[#E2E8F0] px-4 py-2.5 font-inter text-base font-normal text-[#7e7e7e] outline-none"
onChange={(e) => {
const newProfileData = {
...profileData,
name: e.target.value,
};
setProfileData(newProfileData);
}}
/>
</div>
<Separator className="bg-neutral-300" />
<div className="w-full space-y-1.5">
<Label className="block font-sans text-base font-medium text-[#020617]">
Handle
</Label>
<Input
type="text"
name="handle"
defaultValue={profileData.username}
placeholder="@username"
className="h-11 w-full rounded-full border border-[#E2E8F0] px-4 py-2.5 font-inter text-base font-normal text-[#7e7e7e] outline-none"
onChange={(e) => {
const newProfileData = {
...profileData,
username: e.target.value,
};
setProfileData(newProfileData);
}}
/>
</div>
<div className="w-full space-y-1.5">
<Label className="block font-sans text-base font-medium text-[#020617]">
Bio
</Label>
<Textarea
name="bio"
defaultValue={profileData.description}
placeholder="Tell us about yourself..."
className="min-h-56 w-full resize-none rounded-[1rem] border border-[#E2E8F0] px-4 py-2.5 font-inter text-base font-normal text-[#7e7e7e] outline-none"
onChange={(e) => {
const newProfileData = {
...profileData,
description: e.target.value,
};
setProfileData(newProfileData);
}}
/>
</div>
</section>
<Separator className="bg-neutral-300" />
{/* mid section */}
<section className="mb-8 space-y-6">
<h2 className="font-poppins text-lg font-semibold text-neutral-500">
{/* mid section */}
<section className="mb-8 max-w-3xl space-y-6">
<div>
<h2 className="font-poppins text-base font-medium text-neutral-900">
Your links
</h2>
<p className="font-sans text-base font-medium text-[#020617]">
<p className="font-sans text-sm font-normal text-zinc-800">
You can display up to 5 links on your profile
</p>
</div>
<div className="space-y-4 sm:space-y-6">
{[1, 2, 3, 4, 5].map((linkNum) => {
const link = profileData.links[linkNum - 1];
return (
<div key={linkNum} className="w-full space-y-1.5">
<Label className="block font-sans text-base font-medium text-[#020617]">
Link {linkNum}
</Label>
<Input
type="text"
name={`link${linkNum}`}
placeholder="https://"
defaultValue={link || ""}
className="h-11 w-full rounded-full border border-[#E2E8F0] px-4 py-2.5 font-inter text-base font-normal text-[#7e7e7e] outline-none"
onChange={(e) => {
const newLinks = [...profileData.links];
newLinks[linkNum - 1] = e.target.value;
const newProfileData = {
...profileData,
links: newLinks,
};
setProfileData(newProfileData);
}}
/>
</div>
);
})}
</div>
</section>
<div className="space-y-4 sm:space-y-6">
{[1, 2, 3, 4, 5].map((linkNum) => {
const link = profileData.links[linkNum - 1];
return (
<AutogptInput
key={linkNum}
label={`Link ${linkNum}`}
type="text"
name={`link${linkNum}`}
placeholder="https://"
defaultValue={link || ""}
className="h-11 w-full rounded-full border border-[#E2E8F0] px-4 py-2.5 font-inter text-base font-normal text-[#7e7e7e] outline-none"
onChange={(e) => {
const newLinks = [...profileData.links];
newLinks[linkNum - 1] = e.target.value;
const newProfileData = {
...profileData,
links: newLinks,
};
setProfileData(newProfileData);
}}
/>
);
})}
</div>
</section>
{/* buttons */}
<section className="flex h-[50px] items-center justify-end gap-3 py-8">
<Button
type="button"
variant="secondary"
className="h-[50px] rounded-[35px] bg-neutral-200 px-6 py-3 font-sans text-base font-medium text-neutral-800 transition-colors hover:bg-neutral-300 dark:border-neutral-700 dark:bg-neutral-700 dark:text-neutral-200 dark:hover:border-neutral-600 dark:hover:bg-neutral-600"
onClick={() => {
setProfileData(profile);
}}
>
Cancel
</Button>
<Button
type="submit"
disabled={isSubmitting}
className="h-[50px] rounded-[35px] bg-neutral-800 px-6 py-3 font-sans text-base font-medium text-white transition-colors hover:bg-neutral-900 dark:bg-neutral-200 dark:text-neutral-900 dark:hover:bg-neutral-100"
onClick={submitForm}
>
{isSubmitting ? "Saving..." : "Save changes"}
</Button>
</section>
</form>
</div>
{/* buttons */}
<section className="flex h-[50px] items-center justify-end gap-3 py-8">
<AutogptButton
type="button"
variant="secondary"
className="h-[50px] rounded-[35px] bg-neutral-200 px-6 py-3 font-sans text-base font-medium text-neutral-800 transition-colors hover:bg-neutral-300 dark:border-neutral-700 dark:bg-neutral-700 dark:text-neutral-200 dark:hover:border-neutral-600 dark:hover:bg-neutral-600"
onClick={() => {
setProfileData(profile);
}}
>
Cancel
</AutogptButton>
<AutogptButton
type="submit"
disabled={isSubmitting}
className="h-[50px] rounded-[35px] bg-neutral-800 px-6 py-3 font-sans text-base font-medium text-white transition-colors hover:bg-neutral-900 dark:bg-neutral-200 dark:text-neutral-900 dark:hover:bg-neutral-100"
onClick={submitForm}
>
{isSubmitting ? "Saving..." : "Save changes"}
</AutogptButton>
</section>
</form>
</div>
);
};

View File

@@ -1,9 +1,8 @@
"use client";
import * as React from "react";
import Link from "next/link";
import { Button } from "./Button";
import { Sheet, SheetContent, SheetTrigger } from "@/components/ui/sheet";
import { Menu } from "lucide-react";
import { IconDashboardLayout } from "../ui/icons";
import { usePathname } from "next/navigation";
export interface SidebarLink {
text: string;
@@ -28,54 +27,32 @@ const getDefaultIconForLink = () => {
export const Sidebar: React.FC<SidebarProps> = ({ linkGroups }) => {
// Extract all links from linkGroups
const allLinks = linkGroups.flatMap((group) => group.links);
const pathname = usePathname();
// Function to render link items
const renderLinks = () => {
return allLinks.map((link, index) => (
<Link
key={`${link.href}-${index}`}
href={link.href}
className="inline-flex w-full items-center gap-2.5 rounded-xl px-3 py-3 text-neutral-800 hover:bg-neutral-800 hover:text-white dark:text-neutral-200 dark:hover:bg-neutral-700 dark:hover:text-white"
>
{link.icon || getDefaultIconForLink()}
<div className="p-ui-medium text-base font-medium leading-normal">
{link.text}
</div>
</Link>
));
return allLinks.map((link, index) => {
const isActive = pathname === link.href;
return (
<Link
key={`${link.href}-${index}`}
href={link.href}
className={`inline-flex w-full items-center gap-2.5 rounded-xl px-3 py-3 ${
isActive
? "bg-neutral-800 text-white dark:bg-neutral-700 dark:text-white"
: "text-neutral-800 hover:bg-neutral-800 hover:text-white dark:text-neutral-200 dark:hover:bg-neutral-700 dark:hover:text-white"
}`}
>
{link.icon || getDefaultIconForLink()}
<p className="font-sans text-base font-medium">{link.text}</p>
</Link>
);
});
};
return (
<>
<Sheet>
<SheetTrigger asChild>
<Button
aria-label="Open sidebar menu"
className="fixed left-4 top-4 z-50 flex h-14 w-14 items-center justify-center rounded-lg border border-neutral-500 bg-neutral-200 hover:bg-gray-200/50 dark:border-neutral-700 dark:bg-neutral-800 dark:hover:bg-gray-700/50 md:block lg:hidden"
>
<Menu className="h-8 w-8 stroke-black dark:stroke-white" />
<span className="sr-only">Open sidebar menu</span>
</Button>
</SheetTrigger>
<SheetContent
side="left"
className="z-50 w-[280px] border-none p-0 dark:bg-neutral-900 sm:w-[280px]"
>
<div className="h-full w-full rounded-2xl bg-zinc-200 dark:bg-zinc-800">
<div className="inline-flex h-[264px] flex-col items-start justify-start gap-6 p-3">
{renderLinks()}
</div>
</div>
</SheetContent>
</Sheet>
<div className="relative hidden h-[912px] w-[234px] border-none lg:block">
<div className="h-full w-full rounded-2xl bg-zinc-200 dark:bg-zinc-800">
<div className="inline-flex h-[264px] flex-col items-start justify-start gap-6 p-3">
{renderLinks()}
</div>
</div>
</div>
</>
<div className="sticky top-24 flex h-[calc(100vh-7rem)] w-60 flex-col gap-6 rounded-[1rem] bg-zinc-200 p-3">
{renderLinks()}
</div>
);
};

View File

@@ -3,12 +3,12 @@ import { StoreCard } from "./StoreCard";
import { userEvent, within, expect } from "@storybook/test";
const meta = {
title: "AGPT UI/StoreCard",
title: "new/StoreCard",
component: StoreCard,
decorators: [
(Story) => (
<div className="flex items-center justify-center">
<div className="flex items-center justify-center p-4">
<Story />
</div>
),

View File

@@ -32,7 +32,7 @@ export const StoreCard: React.FC<StoreCardProps> = ({
return (
<div
className="flex h-[27rem] w-full max-w-md cursor-pointer flex-col items-start rounded-3xl transition-all duration-300 dark:bg-transparent dark:hover:shadow-gray-700"
className="w-full min-w-80 max-w-md space-y-2 rounded-3xl bg-white p-2 pb-3 hover:bg-gray-50"
onClick={handleClick}
data-testid="store-card"
role="button"
@@ -45,7 +45,7 @@ export const StoreCard: React.FC<StoreCardProps> = ({
}}
>
{/* First Section: Image with Avatar */}
<div className="relative aspect-[2/1.2] w-full overflow-hidden rounded-3xl md:aspect-[2.17/1]">
<div className="relative aspect-[2/1.2] w-full overflow-hidden rounded-3xl md:aspect-[1.78/1]">
{agentImage && (
<Image
src={agentImage}
@@ -56,7 +56,7 @@ export const StoreCard: React.FC<StoreCardProps> = ({
/>
)}
{!hideAvatar && (
<div className="absolute bottom-4 left-4">
<div className="absolute bottom-4 left-4 rounded-full border border-zinc-200">
<Avatar className="h-16 w-16">
{avatarSrc && (
<AvatarImage
@@ -72,46 +72,40 @@ export const StoreCard: React.FC<StoreCardProps> = ({
)}
</div>
<div className="mt-3 flex w-full flex-1 flex-col">
<div className="flex w-full flex-1 flex-col">
{/* Second Section: Agent Name and Creator Name */}
<div className="flex w-full flex-col">
<h3 className="line-clamp-2 font-poppins text-2xl font-semibold text-[#272727] dark:text-neutral-100">
<div className="flex w-full flex-col px-1.5">
<h3 className="line-clamp-2 h-12 font-sans text-base font-medium text-zinc-800 dark:text-neutral-100">
{agentName}
</h3>
{!hideAvatar && creatorName && (
<p className="mt-3 truncate font-sans text-xl font-normal text-neutral-600 dark:text-neutral-400">
<p className="truncate font-sans text-sm font-normal text-zinc-600 dark:text-neutral-400">
by {creatorName}
</p>
)}
</div>
{/* Third Section: Description */}
<div className="mt-2.5 flex w-full flex-col">
<p className="line-clamp-3 font-sans text-base font-normal text-neutral-600 dark:text-neutral-400">
<div className="flex h-18 w-full flex-col px-1.5 pt-2">
<p className="line-clamp-3 font-sans text-sm font-normal text-zinc-500 dark:text-neutral-400">
{description}
</p>
</div>
<div className="flex-grow" />
{/* Spacer to push stats to bottom */}
{/* Fourth Section: Stats Row - aligned to bottom */}
<div className="mt-5 w-full">
<div className="flex items-center justify-between">
<div className="font-sans text-lg font-semibold text-neutral-800 dark:text-neutral-200">
{runs.toLocaleString()} runs
</div>
<div className="flex items-center gap-2">
<span className="font-sans text-lg font-semibold text-neutral-800 dark:text-neutral-200">
{rating.toFixed(1)}
</span>
<div
className="inline-flex items-center"
role="img"
aria-label={`Rating: ${rating.toFixed(1)} out of 5 stars`}
>
{StarRatingIcons(rating)}
</div>
<div className="mt-2.5 flex items-center justify-between px-1.5 pt-2">
<div className="font-sans text-sm font-medium text-zinc-800 dark:text-neutral-200">
{runs.toLocaleString()} runs
</div>
<div className="flex items-center gap-2">
<span className="font-sans text-sm font-medium text-zinc-800 dark:text-neutral-200">
{rating.toFixed(1)}
</span>
<div
className="inline-flex items-center"
role="img"
aria-label={`Rating: ${rating.toFixed(1)} out of 5 stars`}
>
{StarRatingIcons(rating)}
</div>
</div>
</div>

View File

@@ -3,7 +3,7 @@ import { Agent, AgentsSection } from "./AgentsSection";
import { userEvent, within, expect } from "@storybook/test";
const meta = {
title: "AGPT UI/Composite/Agents Section",
title: "new/Composite/Agents Section",
component: AgentsSection,
decorators: [

View File

@@ -33,6 +33,7 @@ export const AgentsSection: React.FC<AgentsSectionProps> = ({
sectionTitle,
agents: allAgents,
hideAvatars = false,
className,
}) => {
const router = useRouter();
@@ -46,10 +47,12 @@ export const AgentsSection: React.FC<AgentsSectionProps> = ({
};
return (
<div className="flex w-full flex-col items-center justify-center">
<div
className={`flex w-full flex-col items-center justify-center ${className}`}
>
<div className="w-full">
<div
className={`mb-9 pl-4 font-poppins text-lg font-semibold text-neutral-800 dark:text-neutral-200 md:pl-0`}
className={`mb-9 pl-4 text-base font-medium text-zinc-500 dark:text-zinc-200 md:pl-0`}
>
{sectionTitle}
</div>
@@ -85,7 +88,7 @@ export const AgentsSection: React.FC<AgentsSectionProps> = ({
</CarouselContent>
</Carousel>
<div className="hidden grid-cols-1 place-items-center gap-6 md:grid md:grid-cols-2 lg:grid-cols-3 2xl:grid-cols-4">
<div className="hidden grid-cols-1 place-items-center gap-5 md:grid md:grid-cols-2 lg:grid-cols-3 2xl:grid-cols-4">
{displayedAgents.map((agent, index) => (
<StoreCard
key={index}

View File

@@ -33,7 +33,7 @@ export const FeaturedCreators: React.FC<FeaturedCreatorsProps> = ({
return (
<div className="flex w-full flex-col items-center justify-center">
<div className="w-full">
<h2 className="mb-9 font-poppins text-lg font-semibold text-neutral-800 dark:text-neutral-200">
<h2 className="mb-9 text-base font-medium text-zinc-500 dark:text-zinc-200">
{title}
</h2>

View File

@@ -47,7 +47,7 @@ export const FeaturedSection: React.FC<FeaturedSectionProps> = ({
return (
<section className="w-full space-y-8">
<h2 className="font-poppins text-lg font-semibold text-neutral-800">
<h2 className="pl-10 font-poppins text-base font-medium text-zinc-500">
Featured agents
</h2>
@@ -61,7 +61,7 @@ export const FeaturedSection: React.FC<FeaturedSectionProps> = ({
{featuredAgents.map((agent, index) => (
<CarouselItem
key={index}
className={`w-fit flex-none ${index === featuredAgents.length - 1 ? "mr-4" : ""}`}
className={`w-fit flex-none ${index === featuredAgents.length - 1 ? "mr-4" : ""} ${index === 0 ? "pl-14" : ""}`}
>
<Link
href={`/marketplace/agent/${encodeURIComponent(agent.creator)}/${encodeURIComponent(agent.slug)}`}
@@ -75,7 +75,7 @@ export const FeaturedSection: React.FC<FeaturedSectionProps> = ({
</CarouselItem>
))}
</CarouselContent>
<div className="relative mt-4">
<div className="relative mt-4 px-10">
<CarouselIndicator />
<CarouselPrevious
afterClick={handlePrevSlide}

View File

@@ -23,7 +23,7 @@ export const HeroSection: React.FC = () => {
return (
<div className="mx-auto flex w-[90%] flex-col items-center justify-center pb-12 pt-16 md:w-full md:pb-28 md:pt-32">
{/* Title */}
<h1 className="mb-4 text-center font-poppins text-2xl font-semibold leading-8 text-neutral-950 md:mb-9 md:text-5xl md:leading-[3rem]">
<h1 className="mb-4 text-center font-poppins text-2xl font-semibold leading-8 text-zinc-900 md:mb-9 md:text-[2.75rem] md:leading-[3.5rem]">
<span>Explore AI agents built for </span>
<span className="text-violet-600">you</span>
<br />
@@ -32,7 +32,7 @@ export const HeroSection: React.FC = () => {
</h1>
{/* Description */}
<h3 className="mb-6 text-center font-sans text-lg text-neutral-700 md:mb-12 md:text-2xl">
<h3 className="mb-6 text-center font-sans text-lg text-zinc-600 md:mb-12 md:text-xl">
Bringing you AI agents designed by thinkers from around the world
</h3>

View File

@@ -24,6 +24,8 @@ import {
NotificationPreference,
NotificationPreferenceDTO,
} from "@/lib/autogpt-server-api";
import AutogptInput from "@/components/agptui/AutogptInput";
import AutogptButton from "@/components/agptui/AutogptButton";
const formSchema = z
.object({
@@ -119,10 +121,10 @@ export default function SettingsForm({ user, preferences }: SettingsFormProps) {
<Form {...form}>
<form
onSubmit={form.handleSubmit(onSubmit)}
className="flex flex-col gap-8"
className="flex flex-col gap-10"
>
{/* Account Settings Section */}
<div className="flex flex-col gap-4">
<div className="flex max-w-3xl flex-col gap-4">
<FormField
control={form.control}
name="email"
@@ -130,7 +132,7 @@ export default function SettingsForm({ user, preferences }: SettingsFormProps) {
<FormItem>
<FormLabel>Email</FormLabel>
<FormControl>
<Input {...field} type="email" />
<AutogptInput {...field} type="email" />
</FormControl>
<FormMessage />
</FormItem>
@@ -144,7 +146,7 @@ export default function SettingsForm({ user, preferences }: SettingsFormProps) {
<FormItem>
<FormLabel>New Password</FormLabel>
<FormControl>
<Input
<AutogptInput
{...field}
type="password"
placeholder="************"
@@ -162,7 +164,7 @@ export default function SettingsForm({ user, preferences }: SettingsFormProps) {
<FormItem>
<FormLabel>Confirm New Password</FormLabel>
<FormControl>
<Input
<AutogptInput
{...field}
type="password"
placeholder="************"
@@ -174,11 +176,13 @@ export default function SettingsForm({ user, preferences }: SettingsFormProps) {
/>
</div>
<Separator />
<Separator className="bg-neutral-300" />
{/* Notifications Section */}
<div className="flex flex-col gap-6">
<h3 className="text-lg font-medium">Notifications</h3>
<div className="flex max-w-3xl flex-col gap-6">
<h3 className="font-poppins text-base font-medium text-neutral-900">
Notifications
</h3>
{/* Agent Notifications */}
<div className="flex flex-col gap-4">
@@ -379,22 +383,25 @@ export default function SettingsForm({ user, preferences }: SettingsFormProps) {
</div>
</div>
<Separator className="bg-neutral-300" />
{/* Form Actions */}
<div className="flex justify-end gap-4">
<Button
<AutogptButton
variant="outline"
type="button"
onClick={onCancel}
disabled={form.formState.isSubmitting}
>
Cancel
</Button>
<Button
</AutogptButton>
<AutogptButton
variant={"default"}
type="submit"
disabled={form.formState.isSubmitting || !form.formState.isDirty}
>
{form.formState.isSubmitting ? "Saving..." : "Save changes"}
</Button>
</AutogptButton>
</div>
</form>
</Form>

View File

@@ -211,9 +211,9 @@ const CarouselPrevious = React.forwardRef<
variant={variant}
size={size}
className={cn(
"pointer absolute h-[52px] w-[52px] rounded-full",
"pointer absolute h-10 w-10 rounded-full border border-zinc-700 bg-white text-zinc-800 hover:bg-zinc-800 hover:text-white",
orientation === "horizontal"
? "right-20 top-0"
? "right-24 top-0"
: "-top-12 left-1/2 -translate-x-1/2 rotate-90",
className,
)}
@@ -226,7 +226,7 @@ const CarouselPrevious = React.forwardRef<
}}
{...props}
>
<ChevronLeft className="h-8 w-8" strokeWidth={1.25} />
<ChevronLeft className="h-5 w-5" strokeWidth={1.25} />
<span className="sr-only">Previous slide</span>
</Button>
);
@@ -257,9 +257,9 @@ const CarouselNext = React.forwardRef<
variant={variant}
size={size}
className={cn(
"absolute h-[52px] w-[52px] rounded-full",
"order absolute h-10 w-10 rounded-full border-zinc-700 bg-white text-zinc-800 hover:bg-zinc-800 hover:text-white",
orientation === "horizontal"
? "right-4 top-0"
? "right-12 top-0"
: "-bottom-12 left-1/2 -translate-x-1/2 rotate-90",
className,
)}
@@ -267,7 +267,7 @@ const CarouselNext = React.forwardRef<
onClick={handleClick}
{...props}
>
<ChevronRight className="h-8 w-8" strokeWidth={1.25} />
<ChevronRight className="h-5 w-5" strokeWidth={1.25} />
<span className="sr-only">Next slide</span>
</Button>
);
@@ -311,8 +311,8 @@ const CarouselIndicator = React.forwardRef<
onClick={() => scrollTo(index)}
className={cn(
selectedIndex === index
? "h-3 w-[52px] rounded-[39px] bg-neutral-800 transition-all duration-500 dark:bg-neutral-200"
: "h-3 w-3 rounded-full bg-neutral-300 transition-all duration-500 dark:bg-neutral-600",
? "h-3 w-[52px] rounded-[39px] bg-zinc-600 transition-all duration-500 dark:bg-neutral-200"
: "h-3 w-3 rounded-full bg-zinc-300 transition-all duration-500 dark:bg-neutral-600",
"cursor-pointer",
)}
/>

View File

@@ -58,7 +58,7 @@ const TableRow = React.forwardRef<
<tr
ref={ref}
className={cn(
"border-b transition-colors hover:bg-neutral-100/50 data-[state=selected]:bg-neutral-100 dark:hover:bg-neutral-800/50 dark:data-[state=selected]:bg-neutral-800",
"border-b border-neutral-300 transition-colors hover:bg-neutral-100/50 data-[state=selected]:bg-neutral-100 dark:hover:bg-neutral-800/50 dark:data-[state=selected]:bg-neutral-800",
className,
)}
{...props}