mirror of
https://github.com/Significant-Gravitas/AutoGPT.git
synced 2026-04-08 03:00:28 -04:00
fix(market): Market featured agent card (#9463)
### Background Resolves: #9313 The marketplace featured agent's section has a bug where if you hover over a featured agent's card we are getting an incorrect background color applied to the description. ### Changes 🏗️ 1. Refactored `FeaturedStoreCard` to `FeaturedAgentCard`: - Condensed props and leverage StoreAgent type from api - Removed onClick handler from props as this is not json serializable and is not inline with NextJS best practices - Used built in Card Components from ShadCN to minimize custom styling. - Optimize images with implementation of the Image component from NextJS 2. Enhanced `FeaturedCardSection` components: - Removing extensive prop passing and leverage the agent itself with the StoreAgent type. - Implemented Link from NextJS to better handler routing and remove the `useRouter` implementation - Removed unnecessary handleCardClick method. ### Checklist 📋 #### For code changes: - [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: Test Plan <details> <summary></summary> - [ ] Goto the landing page of the application or /marketplace - [ ] Scroll to the featured agents section - [ ] Move mouse over each of the cards and observe the image disappearing and text being shown - [ ] Observe the background color of the text that replaced the image matches that of the card </details>
This commit is contained in:
@@ -1,9 +1,6 @@
|
||||
import * as React from "react";
|
||||
import { HeroSection } from "@/components/agptui/composite/HeroSection";
|
||||
import {
|
||||
FeaturedSection,
|
||||
FeaturedAgent,
|
||||
} from "@/components/agptui/composite/FeaturedSection";
|
||||
import { FeaturedSection } from "@/components/agptui/composite/FeaturedSection";
|
||||
import {
|
||||
AgentsSection,
|
||||
Agent,
|
||||
@@ -155,9 +152,7 @@ export default async function Page({}: {}) {
|
||||
<div className="mx-auto w-screen max-w-[1360px]">
|
||||
<main className="px-4">
|
||||
<HeroSection />
|
||||
<FeaturedSection
|
||||
featuredAgents={featuredAgents.agents as FeaturedAgent[]}
|
||||
/>
|
||||
<FeaturedSection featuredAgents={featuredAgents.agents} />
|
||||
<Separator />
|
||||
<AgentsSection
|
||||
sectionTitle="Top Agents"
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
import Image from "next/image";
|
||||
import { StarRatingIcons } from "@/components/ui/icons";
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardFooter,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "@/components/ui/card";
|
||||
import { useState } from "react";
|
||||
import { StoreAgent } from "@/lib/autogpt-server-api";
|
||||
|
||||
interface FeaturedStoreCardProps {
|
||||
agent: StoreAgent;
|
||||
backgroundColor: string;
|
||||
}
|
||||
|
||||
export const FeaturedAgentCard: React.FC<FeaturedStoreCardProps> = ({
|
||||
agent,
|
||||
backgroundColor,
|
||||
}) => {
|
||||
const [isHovered, setIsHovered] = useState(false);
|
||||
|
||||
return (
|
||||
<Card
|
||||
data-testid="featured-store-card"
|
||||
onMouseEnter={() => setIsHovered(true)}
|
||||
onMouseLeave={() => setIsHovered(false)}
|
||||
className={backgroundColor}
|
||||
>
|
||||
<CardHeader>
|
||||
<CardTitle>{agent.agent_name}</CardTitle>
|
||||
<CardDescription>{agent.description}</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="relative h-[397px] w-full overflow-hidden rounded-xl">
|
||||
<div
|
||||
className={`transition-opacity duration-200 ${isHovered ? "opacity-0" : "opacity-100"}`}
|
||||
>
|
||||
<Image
|
||||
src={agent.agent_image || "/AUTOgpt_Logo_dark.png"}
|
||||
alt={`${agent.agent_name} preview`}
|
||||
fill
|
||||
sizes="100%"
|
||||
className="rounded-xl object-cover"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className={`absolute inset-0 overflow-y-auto transition-opacity duration-200 ${
|
||||
isHovered ? "opacity-100" : "opacity-0"
|
||||
} rounded-xl dark:bg-neutral-700`}
|
||||
>
|
||||
<p className="text-base text-neutral-800 dark:text-neutral-200">
|
||||
{agent.description}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
<CardFooter className="flex items-center justify-between">
|
||||
<div className="font-semibold">
|
||||
{agent.runs?.toLocaleString() ?? "0"} runs
|
||||
</div>
|
||||
<div className="flex items-center gap-1.5">
|
||||
<p>{agent.rating.toFixed(1) ?? "0.0"}</p>
|
||||
<div
|
||||
className="inline-flex items-center justify-start gap-px"
|
||||
role="img"
|
||||
aria-label={`Rating: ${agent.rating.toFixed(1)} out of 5 stars`}
|
||||
>
|
||||
{StarRatingIcons(agent.rating)}
|
||||
</div>
|
||||
</div>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
@@ -1,10 +1,10 @@
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
import { FeaturedStoreCard } from "./FeaturedStoreCard";
|
||||
import { FeaturedAgentCard } from "./FeaturedAgentCard";
|
||||
import { userEvent, within } from "@storybook/test";
|
||||
|
||||
const meta = {
|
||||
title: "AGPT UI/Featured Store Card",
|
||||
component: FeaturedStoreCard,
|
||||
component: FeaturedAgentCard,
|
||||
parameters: {
|
||||
layout: {
|
||||
center: true,
|
||||
@@ -13,123 +13,63 @@ const meta = {
|
||||
},
|
||||
tags: ["autodocs"],
|
||||
argTypes: {
|
||||
agentName: { control: "text" },
|
||||
subHeading: { control: "text" },
|
||||
agentImage: { control: "text" },
|
||||
creatorImage: { control: "text" },
|
||||
creatorName: { control: "text" },
|
||||
description: { control: "text" },
|
||||
runs: { control: "number" },
|
||||
rating: { control: "number", min: 0, max: 5, step: 0.1 },
|
||||
onClick: { action: "clicked" },
|
||||
agent: {
|
||||
agent_name: { control: "text" },
|
||||
sub_heading: { control: "text" },
|
||||
agent_image: { control: "text" },
|
||||
creator_avatar: { control: "text" },
|
||||
creator: { control: "text" },
|
||||
runs: { control: "number" },
|
||||
rating: { control: "number", min: 0, max: 5, step: 0.1 },
|
||||
slug: { control: "text" },
|
||||
},
|
||||
backgroundColor: {
|
||||
control: "color",
|
||||
},
|
||||
},
|
||||
} satisfies Meta<typeof FeaturedStoreCard>;
|
||||
} satisfies Meta<typeof FeaturedAgentCard>;
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const Default: Story = {
|
||||
args: {
|
||||
agentName: "Personalized Morning Coffee Newsletter example of three lines",
|
||||
subHeading:
|
||||
"Transform ideas into breathtaking images with this AI-powered Image Generator.",
|
||||
description:
|
||||
"Elevate your web content with this powerful AI Webpage Copy Improver. Designed for marketers, SEO specialists, and web developers, this tool analyses and enhances website copy for maximum impact. Using advanced language models, it optimizes text for better clarity, SEO performance, and increased conversion rates.",
|
||||
agentImage:
|
||||
"https://framerusercontent.com/images/KCIpxr9f97EGJgpaoqnjKsrOPwI.jpg",
|
||||
creatorImage:
|
||||
"https://framerusercontent.com/images/KCIpxr9f97EGJgpaoqnjKsrOPwI.jpg",
|
||||
creatorName: "AI Solutions Inc.",
|
||||
runs: 50000,
|
||||
rating: 4.7,
|
||||
onClick: () => console.log("Card clicked"),
|
||||
backgroundColor: "bg-white",
|
||||
},
|
||||
};
|
||||
|
||||
export const LowRating: Story = {
|
||||
args: {
|
||||
agentName: "Data Analyzer Lite",
|
||||
subHeading: "Basic data analysis tool",
|
||||
description:
|
||||
"A lightweight data analysis tool for basic data processing needs.",
|
||||
agentImage:
|
||||
"https://framerusercontent.com/images/KCIpxr9f97EGJgpaoqnjKsrOPwI.jpg",
|
||||
creatorImage:
|
||||
"https://framerusercontent.com/images/KCIpxr9f97EGJgpaoqnjKsrOPwI.jpg",
|
||||
creatorName: "DataTech",
|
||||
runs: 10000,
|
||||
rating: 2.8,
|
||||
onClick: () => console.log("Card clicked"),
|
||||
backgroundColor: "bg-white",
|
||||
},
|
||||
};
|
||||
|
||||
export const HighRuns: Story = {
|
||||
args: {
|
||||
agentName: "CodeAssist AI",
|
||||
subHeading: "Your AI coding companion",
|
||||
description:
|
||||
"An intelligent coding assistant that helps developers write better code faster.",
|
||||
agentImage:
|
||||
"https://framerusercontent.com/images/KCIpxr9f97EGJgpaoqnjKsrOPwI.jpg",
|
||||
creatorImage:
|
||||
"https://framerusercontent.com/images/KCIpxr9f97EGJgpaoqnjKsrOPwI.jpg",
|
||||
creatorName: "DevTools Co.",
|
||||
runs: 1000000,
|
||||
rating: 4.9,
|
||||
onClick: () => console.log("Card clicked"),
|
||||
backgroundColor: "bg-white",
|
||||
},
|
||||
};
|
||||
|
||||
export const NoCreatorImage: Story = {
|
||||
args: {
|
||||
agentName: "MultiTasker",
|
||||
subHeading: "All-in-one productivity suite",
|
||||
description:
|
||||
"A comprehensive productivity suite that combines task management, note-taking, and project planning into one seamless interface.",
|
||||
agentImage:
|
||||
"https://framerusercontent.com/images/KCIpxr9f97EGJgpaoqnjKsrOPwI.jpg",
|
||||
creatorName: "Productivity Plus",
|
||||
runs: 75000,
|
||||
rating: 4.5,
|
||||
onClick: () => console.log("Card clicked"),
|
||||
backgroundColor: "bg-white",
|
||||
},
|
||||
};
|
||||
|
||||
export const ShortDescription: Story = {
|
||||
args: {
|
||||
agentName: "QuickTask",
|
||||
subHeading: "Fast task automation",
|
||||
description: "Simple and efficient task automation tool.",
|
||||
agentImage:
|
||||
"https://framerusercontent.com/images/KCIpxr9f97EGJgpaoqnjKsrOPwI.jpg",
|
||||
creatorImage:
|
||||
"https://framerusercontent.com/images/KCIpxr9f97EGJgpaoqnjKsrOPwI.jpg",
|
||||
creatorName: "EfficientWorks",
|
||||
runs: 50000,
|
||||
rating: 4.2,
|
||||
onClick: () => console.log("Card clicked"),
|
||||
agent: {
|
||||
agent_name:
|
||||
"Personalized Morning Coffee Newsletter example of three lines",
|
||||
sub_heading:
|
||||
"Transform ideas into breathtaking images with this AI-powered Image Generator.",
|
||||
description:
|
||||
"Elevate your web content with this powerful AI Webpage Copy Improver. Designed for marketers, SEO specialists, and web developers, this tool analyses and enhances website copy for maximum impact. Using advanced language models, it optimizes text for better clarity, SEO performance, and increased conversion rates.",
|
||||
agent_image:
|
||||
"https://framerusercontent.com/images/KCIpxr9f97EGJgpaoqnjKsrOPwI.jpg",
|
||||
creator_avatar:
|
||||
"https://framerusercontent.com/images/KCIpxr9f97EGJgpaoqnjKsrOPwI.jpg",
|
||||
creator: "AI Solutions Inc.",
|
||||
runs: 50000,
|
||||
rating: 4.7,
|
||||
slug: "",
|
||||
},
|
||||
backgroundColor: "bg-white",
|
||||
},
|
||||
};
|
||||
|
||||
export const WithInteraction: Story = {
|
||||
args: {
|
||||
agentName: "AI Writing Assistant",
|
||||
subHeading: "Enhance your writing",
|
||||
description:
|
||||
"An AI-powered writing assistant that helps improve your writing style and clarity.",
|
||||
agentImage:
|
||||
"https://framerusercontent.com/images/KCIpxr9f97EGJgpaoqnjKsrOPwI.jpg",
|
||||
creatorImage:
|
||||
"https://framerusercontent.com/images/KCIpxr9f97EGJgpaoqnjKsrOPwI.jpg",
|
||||
creatorName: "WordCraft AI",
|
||||
runs: 200000,
|
||||
rating: 4.6,
|
||||
onClick: () => console.log("Card clicked"),
|
||||
agent: {
|
||||
slug: "",
|
||||
agent_name: "AI Writing Assistant",
|
||||
sub_heading: "Enhance your writing",
|
||||
description:
|
||||
"An AI-powered writing assistant that helps improve your writing style and clarity.",
|
||||
agent_image:
|
||||
"https://framerusercontent.com/images/KCIpxr9f97EGJgpaoqnjKsrOPwI.jpg",
|
||||
creator_avatar:
|
||||
"https://framerusercontent.com/images/KCIpxr9f97EGJgpaoqnjKsrOPwI.jpg",
|
||||
creator: "WordCraft AI",
|
||||
runs: 200000,
|
||||
rating: 4.6,
|
||||
},
|
||||
backgroundColor: "bg-white",
|
||||
},
|
||||
play: async ({ canvasElement }) => {
|
||||
|
||||
@@ -1,96 +0,0 @@
|
||||
import * as React from "react";
|
||||
import Image from "next/image";
|
||||
import { StarRatingIcons } from "@/components/ui/icons";
|
||||
|
||||
interface FeaturedStoreCardProps {
|
||||
agentName: string;
|
||||
subHeading: string;
|
||||
agentImage: string;
|
||||
creatorImage?: string;
|
||||
creatorName: string;
|
||||
description: string; // Added description prop
|
||||
runs: number;
|
||||
rating: number;
|
||||
onClick: () => void;
|
||||
backgroundColor: string;
|
||||
}
|
||||
|
||||
export const FeaturedStoreCard: React.FC<FeaturedStoreCardProps> = ({
|
||||
agentName,
|
||||
subHeading,
|
||||
agentImage,
|
||||
creatorImage,
|
||||
creatorName,
|
||||
description,
|
||||
runs,
|
||||
rating,
|
||||
onClick,
|
||||
backgroundColor,
|
||||
}) => {
|
||||
return (
|
||||
<div
|
||||
className={`group h-[755px] w-[440px] px-[22px] pb-5 pt-[30px] ${backgroundColor} inline-flex flex-col items-start justify-start gap-7 rounded-[26px] transition-all duration-200 hover:brightness-95 dark:bg-neutral-800`}
|
||||
onClick={onClick}
|
||||
data-testid="featured-store-card"
|
||||
>
|
||||
<div className="flex h-[188px] flex-col items-start justify-start gap-3 self-stretch">
|
||||
<h2 className="font-poppins self-stretch text-[35px] font-medium leading-10 text-neutral-900 dark:text-neutral-100">
|
||||
{agentName}
|
||||
</h2>
|
||||
<div className="font-lead self-stretch text-xl font-normal leading-7 text-neutral-800 dark:text-neutral-200">
|
||||
{subHeading}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex h-[489px] flex-col items-start justify-start gap-[18px] self-stretch">
|
||||
<div className="font-lead self-stretch text-xl font-normal leading-7 text-neutral-800 dark:text-neutral-200">
|
||||
by {creatorName}
|
||||
</div>
|
||||
|
||||
<div className="relative h-[397px] self-stretch">
|
||||
<Image
|
||||
src={agentImage}
|
||||
alt={`${agentName} preview`}
|
||||
layout="fill"
|
||||
objectFit="cover"
|
||||
className="rounded-xl transition-opacity duration-200 group-hover:opacity-0"
|
||||
/>
|
||||
<div className="absolute inset-0 overflow-y-auto rounded-xl bg-white p-4 opacity-0 transition-opacity duration-200 group-hover:opacity-100 dark:bg-neutral-700">
|
||||
<div className="font-geist text-base font-normal leading-normal text-neutral-800 dark:text-neutral-200">
|
||||
{description}
|
||||
</div>
|
||||
</div>
|
||||
{creatorImage && (
|
||||
<div className="absolute left-[8.74px] top-[313px] h-[74px] w-[74px] overflow-hidden rounded-full transition-opacity duration-200 group-hover:opacity-0">
|
||||
<Image
|
||||
src={creatorImage}
|
||||
alt={`${creatorName} image`}
|
||||
layout="fill"
|
||||
className="object-cover"
|
||||
priority
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="inline-flex items-center justify-between self-stretch">
|
||||
<div className="font-large-geist text-lg font-semibold leading-7 text-neutral-800 dark:text-neutral-200">
|
||||
{runs.toLocaleString()} runs
|
||||
</div>
|
||||
<div className="flex items-center justify-start gap-[5px]">
|
||||
<div className="font-large-geist text-lg font-semibold leading-7 text-neutral-800 dark:text-neutral-200">
|
||||
{rating.toFixed(1)}
|
||||
</div>
|
||||
<div
|
||||
className="inline-flex items-center justify-start gap-px"
|
||||
role="img"
|
||||
aria-label={`Rating: ${rating.toFixed(1)} out of 5 stars`}
|
||||
>
|
||||
{StarRatingIcons(rating)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
import { FeaturedAgent, FeaturedSection } from "./FeaturedSection";
|
||||
import { userEvent, within, expect } from "@storybook/test";
|
||||
import { FeaturedSection } from "./FeaturedSection";
|
||||
import { userEvent, within } from "@storybook/test";
|
||||
import { StoreAgent } from "@/lib/autogpt-server-api";
|
||||
|
||||
const meta = {
|
||||
title: "AGPT UI/Composite/Featured Agents",
|
||||
@@ -15,7 +16,6 @@ const meta = {
|
||||
tags: ["autodocs"],
|
||||
argTypes: {
|
||||
featuredAgents: { control: "object" },
|
||||
// onCardClick: { action: "clicked" },
|
||||
},
|
||||
} satisfies Meta<typeof FeaturedSection>;
|
||||
|
||||
@@ -93,7 +93,7 @@ const mockFeaturedAgents = [
|
||||
"https://framerusercontent.com/images/KCIpxr9f97EGJgpaoqnjKsrOPwI.jpg",
|
||||
slug: "quicktask",
|
||||
},
|
||||
] satisfies FeaturedAgent[];
|
||||
] satisfies StoreAgent[];
|
||||
|
||||
export const Default: Story = {
|
||||
args: {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import * as React from "react";
|
||||
import { FeaturedStoreCard } from "@/components/agptui/FeaturedStoreCard";
|
||||
import { FeaturedAgentCard } from "@/components/agptui/FeaturedAgentCard";
|
||||
import {
|
||||
Carousel,
|
||||
CarouselContent,
|
||||
@@ -11,7 +11,8 @@ import {
|
||||
CarouselIndicator,
|
||||
} from "@/components/ui/carousel";
|
||||
import { useCallback, useState } from "react";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { StoreAgent } from "@/lib/autogpt-server-api";
|
||||
import Link from "next/link";
|
||||
|
||||
const BACKGROUND_COLORS = [
|
||||
"bg-violet-200 dark:bg-violet-800", // #ddd6fe / #5b21b6
|
||||
@@ -19,33 +20,14 @@ const BACKGROUND_COLORS = [
|
||||
"bg-green-200 dark:bg-green-800", // #bbf7d0 / #065f46
|
||||
];
|
||||
|
||||
export interface FeaturedAgent {
|
||||
slug: string;
|
||||
agent_name: string;
|
||||
agent_image: string;
|
||||
creator: string;
|
||||
creator_avatar: string;
|
||||
sub_heading: string;
|
||||
description: string;
|
||||
runs: number;
|
||||
rating: number;
|
||||
}
|
||||
|
||||
interface FeaturedSectionProps {
|
||||
featuredAgents: FeaturedAgent[];
|
||||
featuredAgents: StoreAgent[];
|
||||
}
|
||||
|
||||
export const FeaturedSection: React.FC<FeaturedSectionProps> = ({
|
||||
featuredAgents,
|
||||
}) => {
|
||||
const [currentSlide, setCurrentSlide] = useState(0);
|
||||
const router = useRouter();
|
||||
|
||||
const handleCardClick = (creator: string, slug: string) => {
|
||||
router.push(
|
||||
`/marketplace/agent/${encodeURIComponent(creator)}/${encodeURIComponent(slug)}`,
|
||||
);
|
||||
};
|
||||
|
||||
const handlePrevSlide = useCallback(() => {
|
||||
setCurrentSlide((prev) =>
|
||||
@@ -84,17 +66,14 @@ export const FeaturedSection: React.FC<FeaturedSectionProps> = ({
|
||||
key={index}
|
||||
className="max-w-[460px] flex-[0_0_auto]"
|
||||
>
|
||||
<FeaturedStoreCard
|
||||
agentName={agent.agent_name}
|
||||
subHeading={agent.sub_heading}
|
||||
agentImage={agent.agent_image}
|
||||
creatorName={agent.creator}
|
||||
description={agent.description}
|
||||
runs={agent.runs}
|
||||
rating={agent.rating}
|
||||
backgroundColor={getBackgroundColor(index)}
|
||||
onClick={() => handleCardClick(agent.creator, agent.slug)}
|
||||
/>
|
||||
<Link
|
||||
href={`/marketplace/agent/${encodeURIComponent(agent.creator)}/${encodeURIComponent(agent.slug)}`}
|
||||
>
|
||||
<FeaturedAgentCard
|
||||
agent={agent}
|
||||
backgroundColor={getBackgroundColor(index)}
|
||||
/>
|
||||
</Link>
|
||||
</CarouselItem>
|
||||
))}
|
||||
</CarouselContent>
|
||||
|
||||
Reference in New Issue
Block a user