mirror of
https://github.com/Significant-Gravitas/AutoGPT.git
synced 2026-04-08 03:00:28 -04:00
feat(platform): Add Publish agent flow section (#8462)
* Publish agents select page * updates to new design * made agent selection be dynamic based on screen size * add new line for no agents message * add accessibility * add Publish Agent Info screen * add Publish Agent Awaiting Review page * Fixes for smaller screen sizes * update to use agptui/Button for buttons * move svgs to components/ui/icons.tsx
This commit is contained in:
@@ -0,0 +1,26 @@
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
import { PublishAgentAwaitingReview } from "./PublishAgentAwaitingReview";
|
||||
|
||||
const meta: Meta<typeof PublishAgentAwaitingReview> = {
|
||||
title: "AGPT UI/Publish Agent Awaiting Review",
|
||||
component: PublishAgentAwaitingReview,
|
||||
tags: ["autodocs"],
|
||||
parameters: {
|
||||
layout: "centered",
|
||||
},
|
||||
};
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof PublishAgentAwaitingReview>;
|
||||
|
||||
export const Filled: Story = {
|
||||
args: {
|
||||
agentName: "AI Video Generator",
|
||||
subheader: "Create Viral-Ready Content in Seconds",
|
||||
description: "AI Shortform Video Generator: Create Viral-Ready Content in Seconds Transform trending topics into engaging shortform videos with this cutting-edge AI Video Generator. Perfect for content creators, social media managers, and marketers looking to capitalize on the latest news and viral trends. Simply input your desired video count and source website, and watch as the AI scours the internet for the hottest stories, crafting them into attention-grabbing scripts optimized for platforms like TikTok, Instagram Reels, and YouTube Shorts. Key features include: - Customizable video count (1-5 per generation) - Flexible source selection for trending topics - AI-driven script writing following best practices for shortform content - Hooks that capture attention in the first 3 seconds - Dual narrative storytelling for maximum engagement - SEO-optimized content to boost discoverability - Integration with video generation tools for seamless production From hook to conclusion, each script is meticulously crafted to maintain viewer interest, incorporating proven techniques like 'but so' storytelling, visual metaphors, and strategically placed calls-to-action. The AI Shortform Video Generator streamlines your content creation process, allowing you to stay ahead of trends and consistently produce viral-worthy videos that resonate with your audience.",
|
||||
thumbnailSrc: "https://picsum.photos/seed/video/500/350",
|
||||
onClose: () => console.log("Close clicked"),
|
||||
onDone: () => console.log("Done clicked"),
|
||||
onViewProgress: () => console.log("View progress clicked"),
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,108 @@
|
||||
import * as React from "react";
|
||||
import { IconClose } from "../ui/icons";
|
||||
import Image from "next/image";
|
||||
import { Button } from "../agptui/Button";
|
||||
|
||||
interface PublishAgentAwaitingReviewProps {
|
||||
agentName: string;
|
||||
subheader: string;
|
||||
description: string;
|
||||
thumbnailSrc?: string;
|
||||
onClose: () => void;
|
||||
onDone: () => void;
|
||||
onViewProgress: () => void;
|
||||
}
|
||||
|
||||
export const PublishAgentAwaitingReview: React.FC<PublishAgentAwaitingReviewProps> = ({
|
||||
agentName,
|
||||
subheader,
|
||||
description,
|
||||
thumbnailSrc,
|
||||
onClose,
|
||||
onDone,
|
||||
onViewProgress,
|
||||
}) => {
|
||||
return (
|
||||
<div
|
||||
className="inline-flex min-h-screen sm:h-auto sm:min-h-[824px] w-full sm:max-w-[670px] flex-col rounded-none sm:rounded-3xl border border-slate-200 bg-white shadow"
|
||||
role="dialog"
|
||||
aria-labelledby="modal-title"
|
||||
>
|
||||
<div className="w-full relative h-[180px] sm:h-[140px] rounded-none sm:rounded-t-3xl border-b border-slate-200">
|
||||
<div className="w-full absolute left-0 top-[40px] sm:top-[40px] flex flex-col items-center justify-start px-6">
|
||||
<div
|
||||
id="modal-title"
|
||||
className="text-neutral-900 text-xl sm:text-2xl font-semibold font-['Poppins'] leading-relaxed mb-4 sm:mb-2 text-center"
|
||||
>
|
||||
Agent is awaiting review
|
||||
</div>
|
||||
<div className="text-center text-slate-500 text-sm font-normal font-['Inter'] leading-relaxed max-w-[280px] sm:max-w-none">
|
||||
In the meantime you can check your progress on your Creator Dashboard page
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
onClick={onClose}
|
||||
className="absolute right-4 top-4 w-[38px] h-[38px] rounded-full bg-gray-100 flex items-center justify-center hover:bg-gray-200 transition-colors"
|
||||
aria-label="Close dialog"
|
||||
>
|
||||
<IconClose size="default" className="text-neutral-600" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-1 flex-col items-center px-6 py-6 gap-8 sm:gap-6">
|
||||
<div className="flex w-full flex-col items-center gap-6 sm:gap-4 mt-4 sm:mt-0">
|
||||
<div className="flex flex-col items-center gap-3 sm:gap-2">
|
||||
<div className="font-['Geist'] text-lg font-semibold leading-7 text-neutral-800 text-center">
|
||||
{agentName}
|
||||
</div>
|
||||
<div className="font-['Geist'] text-base font-normal leading-normal text-neutral-600 text-center max-w-[280px] sm:max-w-none">
|
||||
{subheader}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
className="w-full h-[280px] sm:h-[350px] bg-neutral-200 rounded-xl"
|
||||
role="img"
|
||||
aria-label={thumbnailSrc ? "Agent thumbnail" : "Thumbnail placeholder"}
|
||||
>
|
||||
{thumbnailSrc && (
|
||||
<Image
|
||||
src={thumbnailSrc}
|
||||
alt="Agent thumbnail"
|
||||
width={500}
|
||||
height={350}
|
||||
className="h-full w-full rounded-xl object-cover"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div
|
||||
className="w-full h-[150px] sm:h-[180px] overflow-y-auto font-['Geist'] text-base font-normal leading-normal text-neutral-600"
|
||||
tabIndex={0}
|
||||
role="region"
|
||||
aria-label="Agent description"
|
||||
>
|
||||
{description}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="w-full p-6 flex flex-col sm:flex-row items-center justify-center gap-4 border-t border-slate-200">
|
||||
<Button
|
||||
onClick={onDone}
|
||||
variant="outline"
|
||||
className="w-full sm:flex-1 h-12 rounded-[59px]"
|
||||
>
|
||||
Done
|
||||
</Button>
|
||||
<Button
|
||||
onClick={onViewProgress}
|
||||
variant="default"
|
||||
className="w-full sm:flex-1 h-12 rounded-[59px] bg-neutral-800 hover:bg-neutral-900 text-white"
|
||||
>
|
||||
View progress
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,65 @@
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
import { PublishAgentSelect } from "./PublishAgentSelect";
|
||||
|
||||
const meta: Meta<typeof PublishAgentSelect> = {
|
||||
title: "AGPT UI/Publish Agent Select",
|
||||
component: PublishAgentSelect,
|
||||
tags: ["autodocs"],
|
||||
};
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof PublishAgentSelect>;
|
||||
|
||||
const mockAgents = [
|
||||
{ name: "SEO Optimizer", lastEdited: "2 days ago", imageSrc: "https://picsum.photos/seed/seo/300/200" },
|
||||
{ name: "Content Writer", lastEdited: "5 days ago", imageSrc: "https://picsum.photos/seed/writer/300/200" },
|
||||
{ name: "Data Analyzer", lastEdited: "1 week ago", imageSrc: "https://picsum.photos/seed/data/300/200" },
|
||||
{ name: "Image Recognition", lastEdited: "2 weeks ago", imageSrc: "https://picsum.photos/seed/image/300/200" },
|
||||
{ name: "Chatbot Assistant", lastEdited: "3 weeks ago", imageSrc: "https://picsum.photos/seed/chat/300/200" },
|
||||
{ name: "Code Generator", lastEdited: "1 month ago", imageSrc: "https://picsum.photos/seed/code/300/200" },
|
||||
{ name: "AI Translator", lastEdited: "6 weeks ago", imageSrc: "https://picsum.photos/seed/translate/300/200" },
|
||||
{ name: "Voice Assistant", lastEdited: "2 months ago", imageSrc: "https://picsum.photos/seed/voice/300/200" },
|
||||
{ name: "Data Visualizer", lastEdited: "3 months ago", imageSrc: "https://picsum.photos/seed/visualize/300/200" },
|
||||
];
|
||||
|
||||
const defaultArgs = {
|
||||
onSelect: (agentName: string) => console.log(`Selected: ${agentName}`),
|
||||
onCancel: () => console.log("Cancelled"),
|
||||
onNext: () => console.log("Next clicked"),
|
||||
onOpenBuilder: () => console.log("Open builder clicked"),
|
||||
};
|
||||
|
||||
export const Default: Story = {
|
||||
args: {
|
||||
...defaultArgs,
|
||||
agents: mockAgents,
|
||||
},
|
||||
};
|
||||
|
||||
export const NoAgents: Story = {
|
||||
args: {
|
||||
...defaultArgs,
|
||||
agents: [],
|
||||
},
|
||||
};
|
||||
|
||||
export const SingleAgent: Story = {
|
||||
args: {
|
||||
...defaultArgs,
|
||||
agents: [mockAgents[0]],
|
||||
},
|
||||
};
|
||||
|
||||
export const SixAgents: Story = {
|
||||
args: {
|
||||
...defaultArgs,
|
||||
agents: mockAgents.slice(0, 6),
|
||||
},
|
||||
};
|
||||
|
||||
export const NineAgents: Story = {
|
||||
args: {
|
||||
...defaultArgs,
|
||||
agents: mockAgents,
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,141 @@
|
||||
import * as React from "react";
|
||||
import Image from "next/image";
|
||||
import { Button } from "../agptui/Button";
|
||||
import { IconClose } from "../ui/icons";
|
||||
|
||||
interface Agent {
|
||||
name: string;
|
||||
lastEdited: string;
|
||||
imageSrc: string;
|
||||
}
|
||||
|
||||
interface PublishAgentSelectProps {
|
||||
agents: Agent[];
|
||||
onSelect: (agentName: string) => void;
|
||||
onCancel: () => void;
|
||||
onNext: () => void;
|
||||
onClose: () => void;
|
||||
onOpenBuilder: () => void;
|
||||
}
|
||||
|
||||
export const PublishAgentSelect: React.FC<PublishAgentSelectProps> = ({
|
||||
agents,
|
||||
onSelect,
|
||||
onCancel,
|
||||
onNext,
|
||||
onClose,
|
||||
onOpenBuilder,
|
||||
}) => {
|
||||
const [selectedAgent, setSelectedAgent] = React.useState<string | null>(null);
|
||||
|
||||
const handleAgentClick = (agentName: string) => {
|
||||
setSelectedAgent(agentName);
|
||||
onSelect(agentName);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="w-full max-w-[900px] bg-white rounded-3xl shadow-lg flex flex-col mx-auto">
|
||||
<div className="p-4 sm:p-6 border-b border-slate-200 relative">
|
||||
<div className="absolute top-4 right-4">
|
||||
<button
|
||||
onClick={onClose}
|
||||
className="w-8 h-8 rounded-full bg-gray-100 flex items-center justify-center hover:bg-gray-200 transition-colors"
|
||||
aria-label="Close"
|
||||
>
|
||||
<IconClose size="default" className="text-neutral-600" />
|
||||
</button>
|
||||
</div>
|
||||
<h2 className="text-neutral-900 text-xl sm:text-2xl font-semibold font-['Poppins'] leading-loose text-center mb-2">Publish Agent</h2>
|
||||
<p className="text-neutral-600 text-sm sm:text-base font-normal font-['Geist'] leading-7 text-center">Select your project that you'd like to publish</p>
|
||||
</div>
|
||||
|
||||
{agents.length === 0 ? (
|
||||
<div className="h-[370px] px-4 sm:px-6 py-5 flex-col justify-center items-center gap-[29px] inline-flex">
|
||||
<div className="w-full sm:w-[573px] text-center text-neutral-600 text-lg sm:text-xl font-normal font-['Geist'] leading-7">
|
||||
Uh-oh.. It seems like you don't have any agents in your library.
|
||||
<br />
|
||||
We'd suggest you to create an agent in our builder first
|
||||
</div>
|
||||
<Button
|
||||
onClick={onOpenBuilder}
|
||||
variant="default"
|
||||
size="lg"
|
||||
className="text-white bg-neutral-800 hover:bg-neutral-900"
|
||||
>
|
||||
Open builder
|
||||
</Button>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<div className="flex-grow p-4 sm:p-6 overflow-hidden">
|
||||
<h3 className="sr-only">List of agents</h3>
|
||||
<div
|
||||
className="h-[300px] sm:h-[400px] md:h-[500px] overflow-y-auto pr-2"
|
||||
role="region"
|
||||
aria-labelledby="agentListHeading"
|
||||
>
|
||||
<div id="agentListHeading" className="sr-only">Scrollable list of agents</div>
|
||||
<div className="p-2">
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
{agents.map((agent) => (
|
||||
<div
|
||||
key={agent.name}
|
||||
className={`rounded-2xl overflow-hidden cursor-pointer transition-all ${
|
||||
selectedAgent === agent.name
|
||||
? "ring-4 ring-violet-600 shadow-lg"
|
||||
: "hover:shadow-md"
|
||||
}`}
|
||||
onClick={() => handleAgentClick(agent.name)}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Enter' || e.key === ' ') {
|
||||
e.preventDefault();
|
||||
handleAgentClick(agent.name);
|
||||
}
|
||||
}}
|
||||
tabIndex={0}
|
||||
role="button"
|
||||
aria-pressed={selectedAgent === agent.name}
|
||||
>
|
||||
<div className="relative h-32 sm:h-40 bg-gray-100">
|
||||
<Image
|
||||
src={agent.imageSrc}
|
||||
alt={agent.name}
|
||||
layout="fill"
|
||||
objectFit="cover"
|
||||
/>
|
||||
</div>
|
||||
<div className="p-3">
|
||||
<h3 className="text-neutral-800 text-sm sm:text-base font-medium font-['Geist'] leading-normal">{agent.name}</h3>
|
||||
<p className="text-neutral-500 text-xs sm:text-sm font-normal font-['Geist'] leading-[14px]">Edited {agent.lastEdited}</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="p-4 sm:p-6 border-t border-slate-200 flex justify-between gap-4">
|
||||
<Button
|
||||
onClick={onCancel}
|
||||
variant="outline"
|
||||
size="default"
|
||||
className="w-full sm:flex-1"
|
||||
>
|
||||
Back
|
||||
</Button>
|
||||
<Button
|
||||
onClick={onNext}
|
||||
disabled={!selectedAgent}
|
||||
variant="default"
|
||||
size="default"
|
||||
className="w-full sm:flex-1 text-white bg-neutral-800 hover:bg-neutral-900"
|
||||
>
|
||||
Next
|
||||
</Button>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,73 @@
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
import { PublishAgentInfo } from "./PublishAgentSelectInfo";
|
||||
|
||||
const meta: Meta<typeof PublishAgentInfo> = {
|
||||
title: "AGPT UI/Publish Agent Info",
|
||||
component: PublishAgentInfo,
|
||||
tags: ["autodocs"],
|
||||
decorators: [(Story) => <div style={{ maxWidth: "670px", margin: "0 auto" }}><Story /></div>],
|
||||
};
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof PublishAgentInfo>;
|
||||
|
||||
export const Default: Story = {
|
||||
args: {
|
||||
onBack: () => console.log("Back clicked"),
|
||||
onSubmit: () => console.log("Submit clicked"),
|
||||
onClose: () => console.log("Close clicked"),
|
||||
},
|
||||
};
|
||||
|
||||
export const Filled: Story = {
|
||||
args: {
|
||||
...Default.args,
|
||||
initialData: {
|
||||
title: "Super SEO Optimizer",
|
||||
subheader: "Boost your website's search engine rankings",
|
||||
thumbnailSrc: "https://picsum.photos/seed/seo/500/350",
|
||||
youtubeLink: "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
|
||||
category: "SEO",
|
||||
description: "This AI agent specializes in analyzing websites and providing actionable recommendations to improve search engine optimization. It can perform keyword research, analyze backlinks, and suggest content improvements.",
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const ThreeImages: Story = {
|
||||
args: {
|
||||
...Default.args,
|
||||
initialData: {
|
||||
title: "Multi-Image Agent",
|
||||
subheader: "Showcasing multiple images",
|
||||
thumbnailSrc: "https://picsum.photos/seed/initial/500/350",
|
||||
youtubeLink: "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
|
||||
category: "SEO",
|
||||
description: "This agent allows you to upload and manage multiple images.",
|
||||
additionalImages: [
|
||||
"https://picsum.photos/seed/second/500/350",
|
||||
"https://picsum.photos/seed/third/500/350",
|
||||
],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const SixImages: Story = {
|
||||
args: {
|
||||
...Default.args,
|
||||
initialData: {
|
||||
title: "Gallery Agent",
|
||||
subheader: "Showcasing a gallery of images",
|
||||
thumbnailSrc: "https://picsum.photos/seed/gallery1/500/350",
|
||||
youtubeLink: "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
|
||||
category: "SEO",
|
||||
description: "This agent displays a gallery of six images.",
|
||||
additionalImages: [
|
||||
"https://picsum.photos/seed/gallery2/500/350",
|
||||
"https://picsum.photos/seed/gallery3/500/350",
|
||||
"https://picsum.photos/seed/gallery4/500/350",
|
||||
"https://picsum.photos/seed/gallery5/500/350",
|
||||
"https://picsum.photos/seed/gallery6/500/350",
|
||||
],
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,218 @@
|
||||
import * as React from "react";
|
||||
import Image from "next/image";
|
||||
import { Button } from "../agptui/Button";
|
||||
import { IconClose, IconPlus } from "../ui/icons";
|
||||
|
||||
interface PublishAgentInfoProps {
|
||||
onBack: () => void;
|
||||
onSubmit: () => void;
|
||||
onClose: () => void;
|
||||
initialData?: {
|
||||
title: string;
|
||||
subheader: string;
|
||||
thumbnailSrc: string;
|
||||
youtubeLink: string;
|
||||
category: string;
|
||||
description: string;
|
||||
additionalImages?: string[];
|
||||
};
|
||||
}
|
||||
|
||||
export const PublishAgentInfo: React.FC<PublishAgentInfoProps> = ({
|
||||
onBack,
|
||||
onSubmit,
|
||||
onClose,
|
||||
initialData,
|
||||
}) => {
|
||||
const [images, setImages] = React.useState<string[]>(
|
||||
initialData?.additionalImages
|
||||
? [initialData.thumbnailSrc, ...initialData.additionalImages]
|
||||
: initialData?.thumbnailSrc
|
||||
? [initialData.thumbnailSrc]
|
||||
: []
|
||||
);
|
||||
const [selectedImage, setSelectedImage] = React.useState<string | null>(initialData?.thumbnailSrc || null);
|
||||
const thumbnailsContainerRef = React.useRef<HTMLDivElement | null>(null);
|
||||
|
||||
const handleRemoveImage = (indexToRemove: number) => {
|
||||
console.log(`Remove image at index: ${indexToRemove}`);
|
||||
// Placeholder function for removing an image
|
||||
};
|
||||
|
||||
const handleAddImage = () => {
|
||||
console.log("Add image button clicked");
|
||||
// Placeholder function for adding an image
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="w-full max-w-[670px] bg-white rounded-3xl shadow-lg border border-slate-200 flex flex-col mx-auto">
|
||||
<div className="p-6 border-b border-slate-200 relative">
|
||||
<div className="absolute top-2 right-4">
|
||||
<button
|
||||
onClick={onClose}
|
||||
className="w-[38px] h-[38px] rounded-full bg-gray-100 flex items-center justify-center hover:bg-gray-200 transition-colors"
|
||||
aria-label="Close"
|
||||
>
|
||||
<IconClose size="default" className="text-neutral-600" />
|
||||
</button>
|
||||
</div>
|
||||
<h2 className="text-neutral-900 text-2xl font-semibold font-['Poppins'] leading-loose text-center">Publish Agent</h2>
|
||||
<p className="text-neutral-600 text-base font-normal font-['Geist'] leading-7 text-center">Write a bit of details about your agent</p>
|
||||
</div>
|
||||
|
||||
<div className="flex-grow p-6 space-y-5 overflow-y-auto">
|
||||
<div className="space-y-1.5">
|
||||
<label htmlFor="title" className="text-slate-950 text-sm font-medium font-['Geist'] leading-tight">Title</label>
|
||||
<input
|
||||
id="title"
|
||||
type="text"
|
||||
placeholder="Agent name"
|
||||
defaultValue={initialData?.title}
|
||||
className="w-full pl-4 pr-14 py-2.5 rounded-[55px] border border-slate-200 text-slate-500 text-base font-normal font-['Geist'] leading-normal"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-1.5">
|
||||
<label htmlFor="subheader" className="text-slate-950 text-sm font-medium font-['Geist'] leading-tight">Subheader</label>
|
||||
<input
|
||||
id="subheader"
|
||||
type="text"
|
||||
placeholder="A tagline for your agent"
|
||||
defaultValue={initialData?.subheader}
|
||||
className="w-full pl-4 pr-14 py-2.5 rounded-[55px] border border-slate-200 text-slate-500 text-base font-normal font-['Geist'] leading-normal"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2.5">
|
||||
<label className="text-slate-950 text-sm font-medium font-['Geist'] leading-tight">Thumbnail images</label>
|
||||
<div className="h-[350px] p-2.5 rounded-[20px] border border-neutral-300 flex items-center justify-center overflow-hidden">
|
||||
{selectedImage ? (
|
||||
<Image
|
||||
src={selectedImage}
|
||||
alt="Selected Thumbnail"
|
||||
width={500}
|
||||
height={350}
|
||||
objectFit="cover"
|
||||
className="rounded-md"
|
||||
/>
|
||||
) : (
|
||||
<p className="text-neutral-600 text-sm font-normal font-['Geist']">No images yet</p>
|
||||
)}
|
||||
</div>
|
||||
<div ref={thumbnailsContainerRef} className="flex items-center space-x-2 overflow-x-auto">
|
||||
{images.length === 0 ? (
|
||||
<div className="w-full flex justify-center">
|
||||
<Button
|
||||
onClick={handleAddImage}
|
||||
variant="ghost"
|
||||
className="w-[100px] h-[70px] bg-neutral-200 rounded-md flex flex-col items-center justify-center hover:bg-neutral-300"
|
||||
>
|
||||
<IconPlus size="lg" className="text-neutral-600" />
|
||||
<span className="text-neutral-600 text-xs font-normal font-['Geist'] mt-1">Add image</span>
|
||||
</Button>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
{images.map((src, index) => (
|
||||
<div key={index} className="relative flex-shrink-0">
|
||||
<Image
|
||||
src={src}
|
||||
alt={`Thumbnail ${index + 1}`}
|
||||
width={100}
|
||||
height={70}
|
||||
objectFit="cover"
|
||||
className="rounded-md cursor-pointer"
|
||||
onClick={() => setSelectedImage(src)}
|
||||
/>
|
||||
<button
|
||||
onClick={() => handleRemoveImage(index)}
|
||||
className="absolute top-1 right-1 w-5 h-5 bg-white bg-opacity-70 rounded-full flex items-center justify-center hover:bg-opacity-100 transition-opacity"
|
||||
aria-label="Remove image"
|
||||
>
|
||||
<IconClose size="sm" className="text-neutral-600" />
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
<Button
|
||||
onClick={handleAddImage}
|
||||
variant="ghost"
|
||||
className="w-[100px] h-[70px] bg-neutral-200 rounded-md flex flex-col items-center justify-center hover:bg-neutral-300"
|
||||
>
|
||||
<IconPlus size="lg" className="text-neutral-600" />
|
||||
<span className="text-neutral-600 text-xs font-normal font-['Geist'] mt-1">Add image</span>
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-1.5">
|
||||
<label className="text-slate-950 text-sm font-medium font-['Geist'] leading-tight">AI image generator</label>
|
||||
<div className="flex items-center justify-between">
|
||||
<p className="text-slate-700 text-base font-normal font-['Geist'] leading-normal">You can use AI to generate a cover image for you</p>
|
||||
<Button
|
||||
variant="default"
|
||||
size="sm"
|
||||
className="text-white bg-neutral-800 hover:bg-neutral-900"
|
||||
>
|
||||
Generate
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-1.5">
|
||||
<label htmlFor="youtube" className="text-slate-950 text-sm font-medium font-['Geist'] leading-tight">YouTube video link</label>
|
||||
<input
|
||||
id="youtube"
|
||||
type="text"
|
||||
placeholder="Paste a video link here"
|
||||
defaultValue={initialData?.youtubeLink}
|
||||
className="w-full pl-4 pr-14 py-2.5 rounded-[55px] border border-slate-200 text-slate-500 text-base font-normal font-['Geist'] leading-normal"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-1.5">
|
||||
<label htmlFor="category" className="text-slate-950 text-sm font-medium font-['Geist'] leading-tight">Category</label>
|
||||
<select
|
||||
id="category"
|
||||
defaultValue={initialData?.category}
|
||||
className="w-full pl-4 pr-5 py-2.5 rounded-[55px] border border-slate-200 text-slate-500 text-base font-normal font-['Geist'] leading-normal appearance-none"
|
||||
>
|
||||
<option value="">Select a category for your agent</option>
|
||||
<option value="SEO">SEO</option>
|
||||
{/* Add more options here */}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div className="space-y-1.5">
|
||||
<label htmlFor="description" className="text-slate-950 text-sm font-medium font-['Geist'] leading-tight">Description</label>
|
||||
<textarea
|
||||
id="description"
|
||||
placeholder="Describe your agent and what it does"
|
||||
defaultValue={initialData?.description}
|
||||
className="w-full h-[100px] pl-4 pr-14 py-2.5 rounded-2xl border border-slate-200 text-slate-900 text-base font-normal font-['Geist'] leading-normal resize-none bg-white"
|
||||
></textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="p-6 border-t border-slate-200 flex justify-between gap-4">
|
||||
<Button
|
||||
onClick={onBack}
|
||||
variant="outline"
|
||||
size="default"
|
||||
className="w-full sm:flex-1"
|
||||
>
|
||||
Back
|
||||
</Button>
|
||||
<Button
|
||||
onClick={onSubmit}
|
||||
variant="default"
|
||||
size="default"
|
||||
className="w-full sm:flex-1 text-white bg-neutral-800 hover:bg-neutral-900"
|
||||
>
|
||||
Submit for review
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -1213,6 +1213,69 @@ export const IconTiktok = createIcon((props) => (
|
||||
</svg>
|
||||
));
|
||||
|
||||
/**
|
||||
* Close (X) icon component.
|
||||
*
|
||||
* @component IconClose
|
||||
* @param {IconProps} props - The props object containing additional attributes and event handlers for the icon.
|
||||
* @returns {JSX.Element} - The close icon.
|
||||
*
|
||||
* @example
|
||||
* // Default usage
|
||||
* <IconClose />
|
||||
*
|
||||
* @example
|
||||
* // With custom color and size
|
||||
* <IconClose className="text-primary" size="lg" />
|
||||
*/
|
||||
export const IconClose = createIcon((props) => (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 14 14"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
aria-label="Close Icon"
|
||||
{...props}
|
||||
>
|
||||
<path d="M1 1L13 13M1 13L13 1" />
|
||||
</svg>
|
||||
));
|
||||
|
||||
/**
|
||||
* Plus icon component.
|
||||
*
|
||||
* @component IconPlus
|
||||
* @param {IconProps} props - The props object containing additional attributes and event handlers for the icon.
|
||||
* @returns {JSX.Element} - The plus icon.
|
||||
*
|
||||
* @example
|
||||
* // Default usage
|
||||
* <IconPlus />
|
||||
*
|
||||
* @example
|
||||
* // With custom color and size
|
||||
* <IconPlus className="text-primary" size="lg" />
|
||||
*/
|
||||
export const IconPlus = createIcon((props) => (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 28 28"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
aria-label="Plus Icon"
|
||||
{...props}
|
||||
>
|
||||
<path d="M14 5.83334V22.1667" />
|
||||
<path d="M5.83331 14H22.1666" />
|
||||
</svg>
|
||||
));
|
||||
|
||||
/**
|
||||
* Globe icon component.
|
||||
*
|
||||
|
||||
Reference in New Issue
Block a user