mirror of
https://github.com/Significant-Gravitas/AutoGPT.git
synced 2026-01-21 04:57:58 -05:00
Compare commits
72 Commits
testing-cl
...
abhi/ci-ch
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
feac4b7579 | ||
|
|
90101604a7 | ||
|
|
20d5845c72 | ||
|
|
740a20fc87 | ||
|
|
cc16ac103a | ||
|
|
b1528c28db | ||
|
|
7c511ea0d3 | ||
|
|
a922f73f3e | ||
|
|
6425612e0b | ||
|
|
c6cc00f9a7 | ||
|
|
7449a378e5 | ||
|
|
4fa44df262 | ||
|
|
76db88730f | ||
|
|
8eedfade11 | ||
|
|
fe472e2dfb | ||
|
|
d93b6ecf59 | ||
|
|
1acc000593 | ||
|
|
fd345f2a3c | ||
|
|
249d61391e | ||
|
|
212c105acc | ||
|
|
5e7958ce60 | ||
|
|
ac9b0ad263 | ||
|
|
eadb3e88f9 | ||
|
|
5d198e4fad | ||
|
|
86cbd6edd1 | ||
|
|
781d8eb8b4 | ||
|
|
368df7bd3b | ||
|
|
5ba94958d9 | ||
|
|
2d5f541743 | ||
|
|
234ce73868 | ||
|
|
a7926e1249 | ||
|
|
bb7c8ba419 | ||
|
|
3c2fb6a3c5 | ||
|
|
b499b6a896 | ||
|
|
4d6ef9fcd1 | ||
|
|
cf015e80e3 | ||
|
|
dc16964a81 | ||
|
|
f4e0735d22 | ||
|
|
36578e29c3 | ||
|
|
a6fcb3abcd | ||
|
|
653a7f475a | ||
|
|
50b2fc30a5 | ||
|
|
c26b5dc611 | ||
|
|
43a508d9a0 | ||
|
|
94f0f5177a | ||
|
|
b3d4d81f76 | ||
|
|
141384856d | ||
|
|
35da036355 | ||
|
|
36bda1cd34 | ||
|
|
01557a6a68 | ||
|
|
073f9f4d58 | ||
|
|
6b44ca536c | ||
|
|
4ef5c5ca20 | ||
|
|
f60d44ecb9 | ||
|
|
c00d7a9f6d | ||
|
|
ee4ebc1956 | ||
|
|
bdbf8f2ffd | ||
|
|
0b224568e6 | ||
|
|
d76e2ac1f6 | ||
|
|
5679d6eb7f | ||
|
|
dfa29b9b22 | ||
|
|
fda57a423a | ||
|
|
dc7f482d0a | ||
|
|
4fd11676f2 | ||
|
|
335e4a695f | ||
|
|
34345dbd47 | ||
|
|
5e73bcc812 | ||
|
|
a7d4de66c2 | ||
|
|
3989bdd175 | ||
|
|
f341965e52 | ||
|
|
fcf6f3b393 | ||
|
|
52af83cf36 |
24
.github/workflows/platform-frontend-ci.yml
vendored
24
.github/workflows/platform-frontend-ci.yml
vendored
@@ -56,6 +56,30 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
yarn type-check
|
yarn type-check
|
||||||
|
|
||||||
|
design:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Set up Node.js
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: "21"
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: |
|
||||||
|
yarn install --frozen-lockfile
|
||||||
|
|
||||||
|
- name: Run Chromatic
|
||||||
|
uses: chromaui/action@latest
|
||||||
|
with:
|
||||||
|
# ⚠️ Make sure to configure a `CHROMATIC_PROJECT_TOKEN` repository secret
|
||||||
|
projectToken: ${{ secrets.CHROMATIC_PROJECT_TOKEN }}
|
||||||
|
workingDir: autogpt_platform/frontend
|
||||||
|
|
||||||
test:
|
test:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
|
|||||||
@@ -9,6 +9,10 @@ const config: StorybookConfig = {
|
|||||||
"@storybook/addon-essentials",
|
"@storybook/addon-essentials",
|
||||||
"@storybook/addon-interactions",
|
"@storybook/addon-interactions",
|
||||||
],
|
],
|
||||||
|
env: {
|
||||||
|
NEXT_PUBLIC_SUPABASE_URL: "https://your-project.supabase.co",
|
||||||
|
NEXT_PUBLIC_SUPABASE_ANON_KEY: "your-anon-key",
|
||||||
|
},
|
||||||
features: {
|
features: {
|
||||||
experimentalRSC: true,
|
experimentalRSC: true,
|
||||||
},
|
},
|
||||||
@@ -16,6 +20,16 @@ const config: StorybookConfig = {
|
|||||||
name: "@storybook/nextjs",
|
name: "@storybook/nextjs",
|
||||||
options: {},
|
options: {},
|
||||||
},
|
},
|
||||||
staticDirs: ["../public"],
|
staticDirs: [
|
||||||
|
"../public",
|
||||||
|
{
|
||||||
|
from: "../node_modules/geist/dist/fonts/geist-sans",
|
||||||
|
to: "/fonts/geist-sans",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
from: "../node_modules/geist/dist/fonts/geist-mono",
|
||||||
|
to: "/fonts/geist-mono",
|
||||||
|
},
|
||||||
|
],
|
||||||
};
|
};
|
||||||
export default config;
|
export default config;
|
||||||
|
|||||||
@@ -1,23 +0,0 @@
|
|||||||
import type { Preview } from "@storybook/react";
|
|
||||||
import { initialize, mswLoader } from "msw-storybook-addon";
|
|
||||||
import "../src/app/globals.css";
|
|
||||||
|
|
||||||
// Initialize MSW
|
|
||||||
initialize();
|
|
||||||
|
|
||||||
const preview: Preview = {
|
|
||||||
parameters: {
|
|
||||||
nextjs: {
|
|
||||||
appDirectory: true,
|
|
||||||
},
|
|
||||||
controls: {
|
|
||||||
matchers: {
|
|
||||||
color: /(background|color)$/i,
|
|
||||||
date: /Date$/i,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
loaders: [mswLoader],
|
|
||||||
};
|
|
||||||
|
|
||||||
export default preview;
|
|
||||||
61
autogpt_platform/frontend/.storybook/preview.tsx
Normal file
61
autogpt_platform/frontend/.storybook/preview.tsx
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
import React from "react";
|
||||||
|
import type { Preview } from "@storybook/react";
|
||||||
|
import { initialize, mswLoader } from "msw-storybook-addon";
|
||||||
|
import { Inter, Poppins } from "next/font/google";
|
||||||
|
import localFont from "next/font/local";
|
||||||
|
import "../src/app/globals.css";
|
||||||
|
import { Providers } from "../src/app/providers";
|
||||||
|
|
||||||
|
const poppins = Poppins({
|
||||||
|
subsets: ["latin"],
|
||||||
|
weight: ["400", "500", "600", "700"],
|
||||||
|
variable: "--font-poppins",
|
||||||
|
});
|
||||||
|
|
||||||
|
const inter = Inter({ subsets: ["latin"], variable: "--font-inter" });
|
||||||
|
|
||||||
|
const GeistSans = localFont({
|
||||||
|
src: "../fonts/geist-sans/Geist-Variable.woff2",
|
||||||
|
variable: "--font-geist-sans",
|
||||||
|
});
|
||||||
|
|
||||||
|
const GeistMono = localFont({
|
||||||
|
src: "../fonts/geist-mono/GeistMono-Variable.woff2",
|
||||||
|
variable: "--font-geist-mono",
|
||||||
|
});
|
||||||
|
|
||||||
|
// Initialize MSW
|
||||||
|
initialize();
|
||||||
|
|
||||||
|
const preview: Preview = {
|
||||||
|
parameters: {
|
||||||
|
nextjs: {
|
||||||
|
appDirectory: true,
|
||||||
|
},
|
||||||
|
controls: {
|
||||||
|
matchers: {
|
||||||
|
color: /(background|color)$/i,
|
||||||
|
date: /Date$/i,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
layout: "fullscreen",
|
||||||
|
},
|
||||||
|
decorators: [
|
||||||
|
(Story, context) => {
|
||||||
|
const mockOptions = context.parameters.mockBackend || {};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={`${poppins.variable} ${inter.variable} ${GeistMono.variable} ${GeistSans.variable}`}
|
||||||
|
>
|
||||||
|
<Providers mockClientProps={mockOptions}>
|
||||||
|
<Story />
|
||||||
|
</Providers>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
],
|
||||||
|
loaders: [mswLoader],
|
||||||
|
};
|
||||||
|
|
||||||
|
export default preview;
|
||||||
60
autogpt_platform/frontend/.storybook/stoybook_guide.md
Normal file
60
autogpt_platform/frontend/.storybook/stoybook_guide.md
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
# For Client-Side Components
|
||||||
|
|
||||||
|
When communicating with the server in client components, use the `useBackendAPI` hook. It automatically detects when running in a Storybook environment and switches to the mock client.
|
||||||
|
|
||||||
|
To provide custom mock data instead of the default values, add the `mockBackend` parameter in your stories:
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
export const MyStory = {
|
||||||
|
parameters: {
|
||||||
|
mockBackend: {
|
||||||
|
credits: 100,
|
||||||
|
isAuthenticated: true,
|
||||||
|
// Other custom mock data
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
# For Server-Side Components
|
||||||
|
|
||||||
|
The server-based Supabase client automatically switches between real requests and mock responses.
|
||||||
|
|
||||||
|
For server-side components, use the following pattern to select between backend client and mock client:
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
const api = process.env.STORYBOOK ? new MockClient() : new BackendAPI();
|
||||||
|
```
|
||||||
|
|
||||||
|
You need to override specific API request methods in your mock client implementation. If you don't override a method, you can use the default methods provided by `BackendAPI` in both server-side and client-side environments.
|
||||||
|
|
||||||
|
To use custom mock data in server components, pass it directly to the `MockClient` constructor:
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
const api = process.env.STORYBOOK
|
||||||
|
? new MockClient({ credits: 200, isAuthenticated: true })
|
||||||
|
: new BackendAPI();
|
||||||
|
```
|
||||||
|
|
||||||
|
> Note: For client components, always use the `mockBackend` parameter in stories instead.
|
||||||
|
|
||||||
|
# Using MSW
|
||||||
|
|
||||||
|
If you haven't overridden an API request method in your mock client, you can use Mock Service Worker (MSW) to intercept HTTP requests from both the browser and Node.js environments, then respond with custom mock data:
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
// In your story
|
||||||
|
export const WithMSW = {
|
||||||
|
parameters: {
|
||||||
|
msw: {
|
||||||
|
handlers: [
|
||||||
|
http.get("/api/user", () => {
|
||||||
|
return HttpResponse.json({ name: "John", role: "admin" });
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
Currently, it doesn't have support for client-side Supabase client and custom data for Supabase server-side client. You could use MSW for both cases.
|
||||||
BIN
autogpt_platform/frontend/public/agpt-logo.png
Normal file
BIN
autogpt_platform/frontend/public/agpt-logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.9 KiB |
37
autogpt_platform/frontend/public/agpt-logo.svg
Normal file
37
autogpt_platform/frontend/public/agpt-logo.svg
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
<svg width="90" height="40" viewBox="0 0 90 40" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g clip-path="url(#clip0_3_4463)">
|
||||||
|
<path d="M69.9139 28.8681V38.6414C69.9139 39.3617 69.3179 39.951 68.5928 39.951C67.8081 39.951 67.1592 39.4599 67.1592 38.6414V24.0584C67.1592 20.9644 69.6987 18.4531 72.8275 18.4531C75.9563 18.4531 78.4958 20.9644 78.4958 24.0584C78.4958 27.1525 75.9563 29.6637 72.8275 29.6637C71.7614 29.6637 70.7681 29.3723 69.9172 28.8681H69.9139ZM71.076 22.3231C72.0428 22.3231 72.8275 23.0991 72.8275 24.0552C72.8275 25.0112 72.0428 25.7872 71.076 25.7872C70.8972 25.7872 70.7217 25.761 70.5595 25.7119C71.0727 26.3994 71.8971 26.8447 72.8275 26.8447C74.3836 26.8447 75.6451 25.5973 75.6451 24.0584C75.6451 22.5196 74.3836 21.2721 72.8275 21.2721C71.8971 21.2721 71.0727 21.7174 70.5595 22.405C70.7217 22.3559 70.8972 22.3297 71.076 22.3297V22.3231Z" fill="url(#paint0_linear_3_4463)"/>
|
||||||
|
<path d="M62.8318 28.8675V35.144C62.8318 35.7137 62.5968 36.2343 62.216 36.6075C61.3817 37.4326 59.8355 37.4326 59.0011 36.6075C57.9052 35.5238 58.9448 33.6903 56.9848 31.752C55.0247 29.8137 51.7734 29.8694 49.8729 31.752C48.9955 32.6196 48.4492 33.8212 48.4492 35.144C48.4492 35.8643 49.0452 36.4536 49.7736 36.4536C50.5616 36.4536 51.2072 35.9625 51.2072 35.144C51.2072 34.5743 51.4423 34.057 51.823 33.6805C52.6574 32.8554 54.2036 32.8554 55.0379 33.6805C56.2332 34.8624 55.0247 36.5289 57.0543 38.536C58.9581 40.4186 62.2657 40.4186 64.1661 38.536C65.0435 37.6683 65.5898 36.4667 65.5898 35.144V24.0545C65.5898 20.9605 63.0504 18.4492 59.9215 18.4492C56.7927 18.4492 54.2533 20.9605 54.2533 24.0545C54.2533 27.1486 56.7927 29.6598 59.9215 29.6598C60.9877 29.6598 61.9842 29.3684 62.8318 28.8642V28.8675ZM59.9215 26.8441C58.9912 26.8441 58.1668 26.3988 57.6536 25.7112C57.8158 25.7603 57.9913 25.7865 58.1701 25.7865C59.1369 25.7865 59.9215 25.0106 59.9215 24.0545C59.9215 23.0985 59.1369 22.3225 58.1701 22.3225C57.9913 22.3225 57.8158 22.3487 57.6536 22.3978C58.1668 21.7103 58.9912 21.265 59.9215 21.265C61.4777 21.265 62.7391 22.5124 62.7391 24.0512C62.7391 25.5901 61.4777 26.8375 59.9215 26.8375V26.8441Z" fill="url(#paint1_linear_3_4463)"/>
|
||||||
|
<path d="M82.6281 12.959C82.6281 9.51134 81.2409 6.24048 78.7809 3.80453C76.3175 1.36858 73.0099 0 69.5235 0C66.0372 0 62.7296 1.37186 60.2662 3.80453C57.8029 6.24048 56.4189 9.51461 56.4189 12.959V13.5451C56.4189 14.2948 57.0348 14.9038 57.793 14.9038C58.5512 14.9038 59.167 14.2948 59.167 13.5451V12.959C59.167 10.2349 60.2662 7.64836 62.2097 5.72645C64.1532 3.80453 66.7689 2.71425 69.5235 2.71425C72.2782 2.71425 74.8905 3.80126 76.8374 5.72645C78.7809 7.64836 79.8834 10.2349 79.8834 12.959C79.8834 13.7088 80.4992 14.3178 81.2574 14.3178C82.0156 14.3178 82.6315 13.7088 82.6315 12.959H82.6281Z" fill="url(#paint2_linear_3_4463)"/>
|
||||||
|
<path d="M82.6289 17.061V18.7341H84.8406C85.5756 18.7341 86.1782 19.33 86.1782 20.0569C86.1782 20.7837 85.6485 21.4582 84.8406 21.4582H82.6289V35.1964C82.6289 35.7661 82.864 36.2834 83.2447 36.6599C84.0791 37.485 85.6253 37.485 86.4596 36.6599C86.8404 36.2834 87.0755 35.7661 87.0755 35.1964V34.738C87.0755 33.9228 87.7244 33.4284 88.5091 33.4284C89.2375 33.4284 89.8335 34.0177 89.8335 34.738V35.1964C89.8335 36.5192 89.2872 37.7208 88.4098 38.5884C86.506 40.471 83.2083 40.471 81.3046 38.5884C80.4272 37.7208 79.8809 36.5192 79.8809 35.1964V17.061C79.8809 16.272 80.5132 15.7383 81.288 15.7383C82.0628 15.7383 82.6256 16.3342 82.6256 17.061H82.6289Z" fill="url(#paint3_linear_3_4463)"/>
|
||||||
|
<path d="M76.2765 38.6377C76.2765 39.358 75.6806 39.9441 74.9522 39.9441C74.1675 39.9441 73.5186 39.453 73.5186 38.6377V34.2013C73.5186 33.4809 74.1145 32.8916 74.8429 32.8916C75.6276 32.8916 76.2765 33.3827 76.2765 34.2013V38.6377Z" fill="url(#paint4_linear_3_4463)"/>
|
||||||
|
<path d="M11.9004 22.2907V31.6252H9.04308V26.9399H2.8583V31.6252H0.000976562V22.2907C0.000976562 14.5998 11.9004 14.4983 11.9004 22.2907ZM44.8804 31.6252C49.1085 31.6252 52.5353 28.2365 52.5353 24.0554C52.5353 19.8744 49.1085 16.4857 44.8804 16.4857C40.6524 16.4857 37.2256 19.8744 37.2256 24.0554C37.2256 28.2365 40.6524 31.6252 44.8804 31.6252ZM44.8804 28.7309C42.2714 28.7309 40.1557 26.6387 40.1557 24.0587C40.1557 21.4787 42.2714 19.3865 44.8804 19.3865C47.4894 19.3865 49.6051 21.4787 49.6051 24.0587C49.6051 26.6387 47.4894 28.7309 44.8804 28.7309ZM37.7421 16.4857V19.2097H33.5836V31.6252H30.8289V19.2097H26.6704V16.4857H37.7421ZM25.2931 25.8202V16.4857H22.4357V25.8202C22.4357 30.0242 16.2476 29.9489 16.2476 25.8202V16.4857H13.3903V25.8202C13.3903 33.5111 25.2897 33.6126 25.2897 25.8202H25.2931ZM9.04308 24.2159V22.294C9.04308 18.09 2.85498 18.1653 2.85498 22.294V24.2159H9.04308Z" fill="#000030"/>
|
||||||
|
<path d="M88.4557 32.257C89.2365 32.257 89.8695 31.6311 89.8695 30.859C89.8695 30.0869 89.2365 29.4609 88.4557 29.4609C87.675 29.4609 87.042 30.0869 87.042 30.859C87.042 31.6311 87.675 32.257 88.4557 32.257Z" fill="#669CF6"/>
|
||||||
|
<path d="M49.7702 39.9475C50.551 39.9475 51.184 39.3215 51.184 38.5494C51.184 37.7773 50.551 37.1514 49.7702 37.1514C48.9894 37.1514 48.3564 37.7773 48.3564 38.5494C48.3564 39.3215 48.9894 39.9475 49.7702 39.9475Z" fill="#669CF6"/>
|
||||||
|
</g>
|
||||||
|
<defs>
|
||||||
|
<linearGradient id="paint0_linear_3_4463" x1="63.4383" y1="20.9589" x2="63.4383" y2="33.2932" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop stop-color="#000030"/>
|
||||||
|
<stop offset="1" stop-color="#9900FF"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="paint1_linear_3_4463" x1="48.0684" y1="20.947" x2="48.0684" y2="33.2951" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop stop-color="#000030"/>
|
||||||
|
<stop offset="1" stop-color="#4285F4"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="paint2_linear_3_4463" x1="70.1948" y1="6.17402" x2="48.7204" y2="-4.1259" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop stop-color="#4285F4"/>
|
||||||
|
<stop offset="1" stop-color="#9900FF"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="paint3_linear_3_4463" x1="75.134" y1="15.7136" x2="75.134" y2="34.5465" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop stop-color="#000030"/>
|
||||||
|
<stop offset="1" stop-color="#4285F4"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="paint4_linear_3_4463" x1="65.0808" y1="24.1914" x2="65.8035" y2="30.9774" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop stop-color="#4285F4"/>
|
||||||
|
<stop offset="1" stop-color="#9900FF"/>
|
||||||
|
</linearGradient>
|
||||||
|
<clipPath id="clip0_3_4463">
|
||||||
|
<rect width="89.8681" height="40" fill="white" transform="translate(0.000976562)"/>
|
||||||
|
</clipPath>
|
||||||
|
</defs>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 6.2 KiB |
BIN
autogpt_platform/frontend/public/testing_agent_image.jpg
Normal file
BIN
autogpt_platform/frontend/public/testing_agent_image.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 296 KiB |
BIN
autogpt_platform/frontend/public/testing_avatar.png
Normal file
BIN
autogpt_platform/frontend/public/testing_avatar.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.3 MiB |
@@ -7,14 +7,15 @@ export default function PlatformLayout({ children }: { children: ReactNode }) {
|
|||||||
<>
|
<>
|
||||||
<Navbar
|
<Navbar
|
||||||
links={[
|
links={[
|
||||||
|
{
|
||||||
|
name: "Home",
|
||||||
|
href: "/library",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "Marketplace",
|
name: "Marketplace",
|
||||||
href: "/marketplace",
|
href: "/marketplace",
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: "Library",
|
|
||||||
href: "/library",
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
name: "Build",
|
name: "Build",
|
||||||
href: "/build",
|
href: "/build",
|
||||||
@@ -62,7 +63,7 @@ export default function PlatformLayout({ children }: { children: ReactNode }) {
|
|||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
<main>{children}</main>
|
<main className="w-full">{children}</main>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -56,62 +56,49 @@ export default async function Page({
|
|||||||
|
|
||||||
const breadcrumbs = [
|
const breadcrumbs = [
|
||||||
{ name: "Marketplace", link: "/marketplace" },
|
{ name: "Marketplace", link: "/marketplace" },
|
||||||
{
|
|
||||||
name: agent.creator,
|
|
||||||
link: `/marketplace/creator/${encodeURIComponent(agent.creator)}`,
|
|
||||||
},
|
|
||||||
{ name: agent.agent_name, link: "#" },
|
{ name: agent.agent_name, link: "#" },
|
||||||
];
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mx-auto w-screen max-w-[1360px]">
|
<main className="mt-9 px-10">
|
||||||
<main className="mt-5 px-4">
|
<BreadCrumbs items={breadcrumbs} />
|
||||||
<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">
|
||||||
<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">
|
<AgentInfo
|
||||||
<div className="w-full md:w-auto md:shrink-0">
|
user={user}
|
||||||
<AgentInfo
|
name={agent.agent_name}
|
||||||
user={user}
|
creator={agent.creator}
|
||||||
name={agent.agent_name}
|
shortDescription={agent.sub_heading}
|
||||||
creator={agent.creator}
|
longDescription={agent.description}
|
||||||
shortDescription={agent.sub_heading}
|
rating={agent.rating}
|
||||||
longDescription={agent.description}
|
runs={agent.runs}
|
||||||
rating={agent.rating}
|
categories={agent.categories}
|
||||||
runs={agent.runs}
|
lastUpdated={agent.updated_at}
|
||||||
categories={agent.categories}
|
version={agent.versions[agent.versions.length - 1]}
|
||||||
lastUpdated={agent.updated_at}
|
storeListingVersionId={agent.store_listing_version_id}
|
||||||
version={agent.versions[agent.versions.length - 1]}
|
libraryAgent={libraryAgent}
|
||||||
storeListingVersionId={agent.store_listing_version_id}
|
|
||||||
libraryAgent={libraryAgent}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<AgentImages
|
|
||||||
images={
|
|
||||||
agent.agent_video
|
|
||||||
? [agent.agent_video, ...agent.agent_image]
|
|
||||||
: agent.agent_image
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<Separator className="mb-[25px] mt-[60px]" />
|
<AgentImages
|
||||||
<AgentsSection
|
images={
|
||||||
margin="32px"
|
agent.agent_video
|
||||||
agents={otherAgents.agents}
|
? [agent.agent_video, ...agent.agent_image]
|
||||||
sectionTitle={`Other agents by ${agent.creator}`}
|
: agent.agent_image
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
<Separator className="mb-[25px] mt-[60px]" />
|
</div>
|
||||||
<AgentsSection
|
<Separator className="mb-9 mt-7" />
|
||||||
margin="32px"
|
<AgentsSection
|
||||||
agents={similarAgents.agents}
|
agents={otherAgents.agents}
|
||||||
sectionTitle="Similar agents"
|
sectionTitle={`Other agents by ${agent.creator}`}
|
||||||
/>
|
/>
|
||||||
<Separator className="mb-[25px] mt-[60px]" />
|
<Separator className="mb-9 mt-11" />
|
||||||
<BecomeACreator
|
<AgentsSection
|
||||||
title="Become a Creator"
|
agents={similarAgents.agents}
|
||||||
description="Join our ever-growing community of hackers and tinkerers"
|
sectionTitle="Similar agents"
|
||||||
buttonText="Become a Creator"
|
/>
|
||||||
/>
|
<Separator className="mb-9 mt-11" />
|
||||||
</main>
|
<BecomeACreator title="Become a Creator" buttonText="Become a Creator" />
|
||||||
</div>
|
</main>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,50 +44,47 @@ export default async function Page({
|
|||||||
const creatorAgents = await api.getStoreAgents({ creator: params.creator });
|
const creatorAgents = await api.getStoreAgents({ creator: params.creator });
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mx-auto w-screen max-w-[1360px]">
|
<main className="mt-9 px-10">
|
||||||
<main className="mt-5 px-4">
|
<BreadCrumbs
|
||||||
<BreadCrumbs
|
items={[
|
||||||
items={[
|
{ name: "Store", link: "/marketplace" },
|
||||||
{ name: "Store", link: "/marketplace" },
|
{ name: creator.name, link: "#" },
|
||||||
{ 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="mt-4 flex flex-col items-start gap-4 sm:mt-6 sm:gap-6 md:mt-[3.2rem] md:flex-row md:gap-10">
|
||||||
<div className="w-full md:w-auto md:shrink-0">
|
<div>
|
||||||
<CreatorInfoCard
|
<CreatorInfoCard
|
||||||
username={creator.name}
|
username={creator.name}
|
||||||
handle={creator.username}
|
handle={creator.username}
|
||||||
avatarSrc={creator.avatar_url}
|
avatarSrc={creator.avatar_url}
|
||||||
categories={creator.top_categories}
|
categories={creator.top_categories}
|
||||||
averageRating={creator.agent_rating}
|
averageRating={creator.agent_rating}
|
||||||
totalRuns={creator.agent_runs}
|
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>
|
</div>
|
||||||
</main>
|
<div className="flex-1 space-y-7">
|
||||||
</div>
|
<div className="space-y-3">
|
||||||
|
<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>
|
||||||
|
|
||||||
|
<Separator className="mb-9 mt-12 bg-gray-200" />
|
||||||
|
<AgentsSection
|
||||||
|
className="mb-36"
|
||||||
|
agents={creatorAgents.agents}
|
||||||
|
hideAvatars={true}
|
||||||
|
sectionTitle={`Agents by ${creator.name}`}
|
||||||
|
/>
|
||||||
|
</main>
|
||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -149,27 +149,28 @@ export default async function Page({}: {}) {
|
|||||||
const { featuredAgents, topAgents, featuredCreators } = await getStoreData();
|
const { featuredAgents, topAgents, featuredCreators } = await getStoreData();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mx-auto w-screen max-w-[1360px]">
|
<main>
|
||||||
<main className="px-4">
|
<section className="px-4 md:px-10">
|
||||||
<HeroSection />
|
<HeroSection />
|
||||||
<FeaturedSection featuredAgents={featuredAgents.agents} />
|
</section>
|
||||||
{/* 100px margin because our featured sections button are placed 40px below the container */}
|
<FeaturedSection featuredAgents={featuredAgents.agents} />
|
||||||
<Separator className="mb-6 mt-24" />
|
<section className="px-4 md:px-10">
|
||||||
|
{/* Below Separator's mt is 44px as per design; I need to add extra to counter the absolute positioning of the arrows above */}
|
||||||
|
<Separator className="mb-9 mt-18" />
|
||||||
<AgentsSection
|
<AgentsSection
|
||||||
sectionTitle="Top Agents"
|
sectionTitle="Top Agents"
|
||||||
agents={topAgents.agents as Agent[]}
|
agents={topAgents.agents as Agent[]}
|
||||||
/>
|
/>
|
||||||
<Separator className="mb-[25px] mt-[60px]" />
|
<Separator className="mb-9 mt-11" />
|
||||||
<FeaturedCreators
|
<FeaturedCreators
|
||||||
featuredCreators={featuredCreators.creators as FeaturedCreator[]}
|
featuredCreators={featuredCreators.creators as FeaturedCreator[]}
|
||||||
/>
|
/>
|
||||||
<Separator className="mb-[25px] mt-[60px]" />
|
<Separator className="mb-9 mt-11" />
|
||||||
<BecomeACreator
|
<BecomeACreator
|
||||||
title="Become a Creator"
|
title="Become a Creator"
|
||||||
description="Join our ever-growing community of hackers and tinkerers"
|
buttonText="Upload your agent"
|
||||||
buttonText="Become a Creator"
|
|
||||||
/>
|
/>
|
||||||
</main>
|
</section>
|
||||||
</div>
|
</main>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -116,67 +116,67 @@ function SearchResults({
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-full">
|
<div className="px-10">
|
||||||
<div className="mx-auto min-h-screen max-w-[1440px] px-10 lg:min-w-[1440px]">
|
<div className="mt-9 flex items-center">
|
||||||
<div className="mt-8 flex items-center">
|
<div className="flex-1">
|
||||||
<div className="flex-1">
|
<h2 className="font-sans text-base font-medium leading-normal text-zinc-800 dark:text-neutral-200">
|
||||||
<h2 className="font-geist text-base font-medium leading-normal text-neutral-800 dark:text-neutral-200">
|
Results for:
|
||||||
Results for:
|
</h2>
|
||||||
</h2>
|
<h1 className="font-poppins text-2xl font-medium leading-[32px] text-zinc-800 dark:text-neutral-100">
|
||||||
<h1 className="font-poppins text-2xl font-semibold leading-[32px] text-neutral-800 dark:text-neutral-100">
|
{searchTerm}
|
||||||
{searchTerm}
|
</h1>
|
||||||
</h1>
|
|
||||||
</div>
|
|
||||||
<div className="flex-none">
|
|
||||||
<SearchBar width="w-[439px]" height="h-[60px]" />
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
<div className="flex-none">
|
||||||
|
<SearchBar className="w-[28rem]" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
<div className="mt-20 flex flex-col items-center justify-center">
|
<div className="mt-20 flex flex-col items-center justify-center">
|
||||||
<p className="text-neutral-500 dark:text-neutral-400">Loading...</p>
|
<p className="text-neutral-500 dark:text-neutral-400">Loading...</p>
|
||||||
|
</div>
|
||||||
|
) : totalCount > 0 ? (
|
||||||
|
<>
|
||||||
|
<div className="mt-9 flex items-center justify-between">
|
||||||
|
<SearchFilterChips
|
||||||
|
totalCount={totalCount}
|
||||||
|
agentsCount={agentsCount}
|
||||||
|
creatorsCount={creatorsCount}
|
||||||
|
onFilterChange={handleFilterChange}
|
||||||
|
/>
|
||||||
|
<SortDropdown onSort={handleSortChange} />
|
||||||
</div>
|
</div>
|
||||||
) : totalCount > 0 ? (
|
{/* Content section */}
|
||||||
<>
|
<div className="space-y-9 py-9">
|
||||||
<div className="mt-[36px] flex items-center justify-between">
|
{showAgents && agentsCount > 0 && (
|
||||||
<SearchFilterChips
|
<div>
|
||||||
totalCount={totalCount}
|
<AgentsSection agents={agents} sectionTitle="Agents" />
|
||||||
agentsCount={agentsCount}
|
</div>
|
||||||
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>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{showAgents && agentsCount > 0 && creatorsCount > 0 && (
|
{showAgents && agentsCount > 0 && creatorsCount > 0 && (
|
||||||
<Separator />
|
<Separator />
|
||||||
)}
|
)}
|
||||||
{showCreators && creatorsCount > 0 && (
|
{showCreators && creatorsCount > 0 && (
|
||||||
|
<div>
|
||||||
<FeaturedCreators
|
<FeaturedCreators
|
||||||
featuredCreators={creators}
|
featuredCreators={creators}
|
||||||
title="Creators"
|
title="Creators"
|
||||||
/>
|
/>
|
||||||
)}
|
</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>
|
||||||
)}
|
</>
|
||||||
</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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,9 +2,12 @@ import { APIKeysSection } from "@/components/agptui/composite/APIKeySection";
|
|||||||
|
|
||||||
const ApiKeysPage = () => {
|
const ApiKeysPage = () => {
|
||||||
return (
|
return (
|
||||||
<div className="w-full pr-4 pt-24 md:pt-0">
|
<main className="flex-1 space-y-7.5 pb-8">
|
||||||
|
<h1 className="font-poppins text-[1.75rem] font-medium leading-[2.5rem] text-zinc-500">
|
||||||
|
API key
|
||||||
|
</h1>
|
||||||
<APIKeysSection />
|
<APIKeysSection />
|
||||||
</div>
|
</main>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -104,8 +104,8 @@ export default function CreditsPage() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-full px-4 sm:px-8 md:min-w-[800px]">
|
<div className="flex-1 space-y-7.5 pb-8">
|
||||||
<h1 className="mb-6 text-[28px] font-normal text-neutral-900 dark:text-neutral-100 sm:mb-8 sm:text-[35px]">
|
<h1 className="font-poppins text-[1.75rem] font-medium leading-[2.5rem] text-zinc-500">
|
||||||
Billing
|
Billing
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import {
|
|||||||
} from "@/lib/autogpt-server-api/types";
|
} from "@/lib/autogpt-server-api/types";
|
||||||
import useSupabase from "@/hooks/useSupabase";
|
import useSupabase from "@/hooks/useSupabase";
|
||||||
import { useBackendAPI } from "@/lib/autogpt-server-api/context";
|
import { useBackendAPI } from "@/lib/autogpt-server-api/context";
|
||||||
|
import AutogptButton from "@/components/agptui/AutogptButton";
|
||||||
|
|
||||||
export default function Page({}: {}) {
|
export default function Page({}: {}) {
|
||||||
const { supabase } = useSupabase();
|
const { supabase } = useSupabase();
|
||||||
@@ -64,70 +65,71 @@ export default function Page({}: {}) {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<main className="flex-1 py-8">
|
<main className="flex-1 space-y-7.5 pb-8">
|
||||||
{/* Header Section */}
|
{/* Title */}
|
||||||
<div className="mb-8 flex flex-col gap-4 md:flex-row md:items-end md:justify-between">
|
<h1 className="font-poppins text-[1.75rem] font-medium leading-[2.5rem] text-zinc-500">
|
||||||
<div className="space-y-6">
|
Agent dashboard
|
||||||
<h1 className="text-4xl font-medium text-neutral-900 dark:text-neutral-100">
|
</h1>
|
||||||
Agent dashboard
|
|
||||||
</h1>
|
{/* Content */}
|
||||||
<div className="space-y-2">
|
<section className="space-y-8">
|
||||||
<h2 className="text-xl font-medium text-neutral-900 dark:text-neutral-100">
|
<div className="flex items-center justify-between">
|
||||||
|
<div>
|
||||||
|
<h2 className="font-poppins text-base font-medium text-zinc-800">
|
||||||
Submit a New Agent
|
Submit a New Agent
|
||||||
</h2>
|
</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
|
Select from the list of agents you currently have, or upload from
|
||||||
your local machine.
|
your local machine.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</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" />
|
<PublishAgentPopout
|
||||||
|
trigger={
|
||||||
{/* Agents Section */}
|
<AutogptButton onClick={onOpenPopout} variant="outline">
|
||||||
<div>
|
Add to Library
|
||||||
<h2 className="mb-4 text-xl font-bold text-neutral-900 dark:text-neutral-100">
|
</AutogptButton>
|
||||||
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}
|
openPopout={openPopout}
|
||||||
onDeleteSubmission={onDeleteSubmission}
|
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>
|
</main>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -149,83 +149,91 @@ export default function PrivatePage() {
|
|||||||
: [];
|
: [];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mx-auto max-w-3xl md:py-8">
|
<div className="space-y-6 pb-10">
|
||||||
<h2 className="mb-4 text-lg">Connections & Credentials</h2>
|
<h1 className="font-poppins text-[1.75rem] font-medium leading-[2.5rem] text-zinc-500">
|
||||||
<Table>
|
Profile
|
||||||
<TableHeader>
|
</h1>
|
||||||
<TableRow>
|
<div>
|
||||||
<TableHead>Provider</TableHead>
|
<h2 className="font-poppins text-base font-medium text-zinc-800">
|
||||||
<TableHead>Name</TableHead>
|
Connections & Credentials
|
||||||
<TableHead>Actions</TableHead>
|
</h2>
|
||||||
</TableRow>
|
<Table>
|
||||||
</TableHeader>
|
<TableHeader>
|
||||||
<TableBody>
|
<TableRow>
|
||||||
{allCredentials.map((cred) => (
|
<TableHead>Provider</TableHead>
|
||||||
<TableRow key={cred.id}>
|
<TableHead>Name</TableHead>
|
||||||
<TableCell>
|
<TableHead>Actions</TableHead>
|
||||||
<div className="flex items-center space-x-1.5">
|
|
||||||
<cred.ProviderIcon className="h-4 w-4" />
|
|
||||||
<strong>{cred.providerName}</strong>
|
|
||||||
</div>
|
|
||||||
</TableCell>
|
|
||||||
<TableCell>
|
|
||||||
<div className="flex h-full items-center space-x-1.5">
|
|
||||||
<cred.TypeIcon />
|
|
||||||
<span>{cred.title || cred.username}</span>
|
|
||||||
</div>
|
|
||||||
<small className="text-muted-foreground">
|
|
||||||
{
|
|
||||||
{
|
|
||||||
oauth2: "OAuth2 credentials",
|
|
||||||
api_key: "API key",
|
|
||||||
user_password: "Username & password",
|
|
||||||
}[cred.type]
|
|
||||||
}{" "}
|
|
||||||
- <code>{cred.id}</code>
|
|
||||||
</small>
|
|
||||||
</TableCell>
|
|
||||||
<TableCell className="w-0 whitespace-nowrap">
|
|
||||||
<Button
|
|
||||||
variant="destructive"
|
|
||||||
onClick={() => removeCredentials(cred.provider, cred.id)}
|
|
||||||
>
|
|
||||||
<Trash2Icon className="mr-1.5 size-4" /> Delete
|
|
||||||
</Button>
|
|
||||||
</TableCell>
|
|
||||||
</TableRow>
|
</TableRow>
|
||||||
))}
|
</TableHeader>
|
||||||
</TableBody>
|
<TableBody>
|
||||||
</Table>
|
{allCredentials.map((cred) => (
|
||||||
|
<TableRow key={cred.id}>
|
||||||
|
<TableCell>
|
||||||
|
<div className="flex items-center space-x-1.5">
|
||||||
|
<cred.ProviderIcon className="h-4 w-4" />
|
||||||
|
<strong>{cred.providerName}</strong>
|
||||||
|
</div>
|
||||||
|
</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
<div className="flex h-full items-center space-x-1.5">
|
||||||
|
<cred.TypeIcon />
|
||||||
|
<span>{cred.title || cred.username}</span>
|
||||||
|
</div>
|
||||||
|
<small className="text-muted-foreground">
|
||||||
|
{
|
||||||
|
{
|
||||||
|
oauth2: "OAuth2 credentials",
|
||||||
|
api_key: "API key",
|
||||||
|
user_password: "Username & password",
|
||||||
|
}[cred.type]
|
||||||
|
}{" "}
|
||||||
|
- <code>{cred.id}</code>
|
||||||
|
</small>
|
||||||
|
</TableCell>
|
||||||
|
<TableCell className="w-0 whitespace-nowrap">
|
||||||
|
<Button
|
||||||
|
variant="destructive"
|
||||||
|
onClick={() => removeCredentials(cred.provider, cred.id)}
|
||||||
|
>
|
||||||
|
<Trash2Icon className="mr-1.5 size-4" /> Delete
|
||||||
|
</Button>
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
))}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
|
||||||
<AlertDialog open={confirmationDialogState.open}>
|
<AlertDialog open={confirmationDialogState.open}>
|
||||||
<AlertDialogContent>
|
<AlertDialogContent>
|
||||||
<AlertDialogHeader>
|
<AlertDialogHeader>
|
||||||
<AlertDialogTitle>Are you sure?</AlertDialogTitle>
|
<AlertDialogTitle>Are you sure?</AlertDialogTitle>
|
||||||
<AlertDialogDescription>
|
<AlertDialogDescription>
|
||||||
{confirmationDialogState.open && confirmationDialogState.message}
|
{confirmationDialogState.open &&
|
||||||
</AlertDialogDescription>
|
confirmationDialogState.message}
|
||||||
</AlertDialogHeader>
|
</AlertDialogDescription>
|
||||||
<AlertDialogFooter>
|
</AlertDialogHeader>
|
||||||
<AlertDialogCancel
|
<AlertDialogFooter>
|
||||||
onClick={() =>
|
<AlertDialogCancel
|
||||||
confirmationDialogState.open &&
|
onClick={() =>
|
||||||
confirmationDialogState.onReject()
|
confirmationDialogState.open &&
|
||||||
}
|
confirmationDialogState.onReject()
|
||||||
>
|
}
|
||||||
Cancel
|
>
|
||||||
</AlertDialogCancel>
|
Cancel
|
||||||
<AlertDialogAction
|
</AlertDialogCancel>
|
||||||
variant="destructive"
|
<AlertDialogAction
|
||||||
onClick={() =>
|
variant="destructive"
|
||||||
confirmationDialogState.open &&
|
onClick={() =>
|
||||||
confirmationDialogState.onConfirm()
|
confirmationDialogState.open &&
|
||||||
}
|
confirmationDialogState.onConfirm()
|
||||||
>
|
}
|
||||||
Continue
|
>
|
||||||
</AlertDialogAction>
|
Continue
|
||||||
</AlertDialogFooter>
|
</AlertDialogAction>
|
||||||
</AlertDialogContent>
|
</AlertDialogFooter>
|
||||||
</AlertDialog>
|
</AlertDialogContent>
|
||||||
|
</AlertDialog>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,47 +14,49 @@ export default function Layout({ children }: { children: React.ReactNode }) {
|
|||||||
{
|
{
|
||||||
links: [
|
links: [
|
||||||
{
|
{
|
||||||
text: "Creator Dashboard",
|
text: "API Keys",
|
||||||
href: "/profile/dashboard",
|
href: "/profile/api_keys",
|
||||||
icon: <IconDashboardLayout className="h-6 w-6" />,
|
icon: <KeyIcon className="h-6 w-6 stroke-[1.25px]" />,
|
||||||
},
|
},
|
||||||
...(process.env.NEXT_PUBLIC_SHOW_BILLING_PAGE === "true"
|
...(process.env.NEXT_PUBLIC_SHOW_BILLING_PAGE === "true"
|
||||||
? [
|
? [
|
||||||
{
|
{
|
||||||
text: "Billing",
|
text: "Billing",
|
||||||
href: "/profile/credits",
|
href: "/profile/credits",
|
||||||
icon: <IconCoin className="h-6 w-6" />,
|
icon: <IconCoin className="h-6 w-6 stroke-[1.25px]" />,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
: []),
|
: []),
|
||||||
|
{
|
||||||
|
text: "Creator Dashboard",
|
||||||
|
href: "/profile/dashboard",
|
||||||
|
icon: <IconDashboardLayout className="h-6 w-6 stroke-[1.25px]" />,
|
||||||
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
text: "Integrations",
|
text: "Integrations",
|
||||||
href: "/profile/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" />,
|
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
text: "Profile",
|
text: "Profile",
|
||||||
href: "/profile",
|
href: "/profile",
|
||||||
icon: <IconProfile className="h-6 w-6" />,
|
icon: <IconProfile className="h-6 w-6 stroke-[1.25px]" />,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: "Settings",
|
text: "Settings",
|
||||||
href: "/profile/settings",
|
href: "/profile/settings",
|
||||||
icon: <IconSliders className="h-6 w-6" />,
|
icon: <IconSliders className="h-6 w-6 stroke-[1.25px]" />,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
return (
|
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 pt-8">
|
||||||
<Sidebar linkGroups={sidebarLinkGroups} />
|
<Sidebar linkGroups={sidebarLinkGroups} />
|
||||||
<div className="flex-1 pl-4">{children}</div>
|
<div className="flex-1">{children}</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,7 +30,10 @@ export default async function Page({}: {}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
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} />
|
<ProfileInfoForm profile={profile as CreatorDetails} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -19,13 +19,12 @@ export default async function SettingsPage() {
|
|||||||
const preferences = await getUserPreferences();
|
const preferences = await getUserPreferences();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="container max-w-2xl space-y-6 py-10">
|
<div className="space-y-6 pb-10">
|
||||||
<div>
|
{/* Title */}
|
||||||
<h3 className="text-lg font-medium">My account</h3>
|
<h1 className="font-poppins text-[1.75rem] font-medium leading-[2.5rem] text-zinc-500">
|
||||||
<p className="text-sm text-muted-foreground">
|
Settings
|
||||||
Manage your account settings and preferences.
|
</h1>
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<SettingsForm user={user} preferences={preferences} />
|
<SettingsForm user={user} preferences={preferences} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ export default async function RootLayout({
|
|||||||
>
|
>
|
||||||
<body
|
<body
|
||||||
className={cn(
|
className={cn(
|
||||||
"bg-neutral-50 antialiased transition-colors",
|
"bg-white antialiased transition-colors",
|
||||||
inter.className,
|
inter.className,
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -8,15 +8,30 @@ import { TooltipProvider } from "@/components/ui/tooltip";
|
|||||||
import CredentialsProvider from "@/components/integrations/credentials-provider";
|
import CredentialsProvider from "@/components/integrations/credentials-provider";
|
||||||
import { LaunchDarklyProvider } from "@/components/feature-flag/feature-flag-provider";
|
import { LaunchDarklyProvider } from "@/components/feature-flag/feature-flag-provider";
|
||||||
import OnboardingProvider from "@/components/onboarding/onboarding-provider";
|
import OnboardingProvider from "@/components/onboarding/onboarding-provider";
|
||||||
|
import { MockClientProps } from "@/lib/autogpt-server-api/mock_client";
|
||||||
|
import PageStructureContainer from "@/components/page-structure-container-provider";
|
||||||
|
|
||||||
export function Providers({ children, ...props }: ThemeProviderProps) {
|
export interface ProvidersProps extends ThemeProviderProps {
|
||||||
|
children: React.ReactNode;
|
||||||
|
useMockBackend?: boolean;
|
||||||
|
mockClientProps?: MockClientProps;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Providers({
|
||||||
|
children,
|
||||||
|
useMockBackend,
|
||||||
|
mockClientProps,
|
||||||
|
...props
|
||||||
|
}: ProvidersProps) {
|
||||||
return (
|
return (
|
||||||
<NextThemesProvider {...props}>
|
<NextThemesProvider {...props}>
|
||||||
<BackendAPIProvider>
|
<BackendAPIProvider mockClientProps={mockClientProps}>
|
||||||
<CredentialsProvider>
|
<CredentialsProvider>
|
||||||
<LaunchDarklyProvider>
|
<LaunchDarklyProvider>
|
||||||
<OnboardingProvider>
|
<OnboardingProvider>
|
||||||
<TooltipProvider>{children}</TooltipProvider>
|
<TooltipProvider>
|
||||||
|
<PageStructureContainer>{children}</PageStructureContainer>
|
||||||
|
</TooltipProvider>
|
||||||
</OnboardingProvider>
|
</OnboardingProvider>
|
||||||
</LaunchDarklyProvider>
|
</LaunchDarklyProvider>
|
||||||
</CredentialsProvider>
|
</CredentialsProvider>
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import * as React from "react";
|
|||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import { PlayIcon } from "@radix-ui/react-icons";
|
import { PlayIcon } from "@radix-ui/react-icons";
|
||||||
import { Button } from "./Button";
|
import { Button } from "./Button";
|
||||||
|
import AutogptButton from "./AutogptButton";
|
||||||
|
|
||||||
const isValidVideoFile = (url: string): boolean => {
|
const isValidVideoFile = (url: string): boolean => {
|
||||||
const videoExtensions = /\.(mp4|webm|ogg)$/i;
|
const videoExtensions = /\.(mp4|webm|ogg)$/i;
|
||||||
@@ -32,6 +33,8 @@ interface AgentImageItemProps {
|
|||||||
export const AgentImageItem: React.FC<AgentImageItemProps> = React.memo(
|
export const AgentImageItem: React.FC<AgentImageItemProps> = React.memo(
|
||||||
({ image, index, playingVideoIndex, handlePlay, handlePause }) => {
|
({ image, index, playingVideoIndex, handlePlay, handlePause }) => {
|
||||||
const videoRef = React.useRef<HTMLVideoElement>(null);
|
const videoRef = React.useRef<HTMLVideoElement>(null);
|
||||||
|
const [isVideoPlaying, setIsVideoPlaying] = React.useState(false);
|
||||||
|
const [thumbnail, setThumbnail] = React.useState<string>("");
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (
|
if (
|
||||||
@@ -43,11 +46,22 @@ export const AgentImageItem: React.FC<AgentImageItemProps> = React.memo(
|
|||||||
}
|
}
|
||||||
}, [playingVideoIndex, index]);
|
}, [playingVideoIndex, index]);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (videoRef.current && isValidVideoFile(image)) {
|
||||||
|
videoRef.current.currentTime = 0.1;
|
||||||
|
const canvas = document.createElement("canvas");
|
||||||
|
canvas.width = videoRef.current.videoWidth;
|
||||||
|
canvas.height = videoRef.current.videoHeight;
|
||||||
|
canvas.getContext("2d")?.drawImage(videoRef.current, 0, 0);
|
||||||
|
setThumbnail(canvas.toDataURL());
|
||||||
|
}
|
||||||
|
}, [image]);
|
||||||
|
|
||||||
const isVideoFile = isValidVideoFile(image);
|
const isVideoFile = isValidVideoFile(image);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<div className="h-[15rem] overflow-hidden rounded-[26px] bg-[#a8a8a8] dark:bg-neutral-700 sm:h-[20rem] sm:w-full md:h-[25rem] lg:h-[30rem]">
|
<div className="h-[15rem] overflow-hidden rounded-[1.5rem] bg-[#a8a8a8] dark:bg-neutral-700 sm:h-[20rem] sm:w-full md:h-[25rem] lg:h-[32rem]">
|
||||||
{isValidVideoUrl(image) ? (
|
{isValidVideoUrl(image) ? (
|
||||||
getYouTubeVideoId(image) ? (
|
getYouTubeVideoId(image) ? (
|
||||||
<iframe
|
<iframe
|
||||||
@@ -63,12 +77,18 @@ export const AgentImageItem: React.FC<AgentImageItemProps> = React.memo(
|
|||||||
<video
|
<video
|
||||||
ref={videoRef}
|
ref={videoRef}
|
||||||
className="absolute inset-0 h-full w-full object-cover"
|
className="absolute inset-0 h-full w-full object-cover"
|
||||||
controls
|
controls={isVideoPlaying}
|
||||||
preload="metadata"
|
preload="metadata"
|
||||||
poster={`${image}#t=0.1`}
|
poster={thumbnail || `${image}#t=0.1`}
|
||||||
style={{ objectPosition: "center 25%" }}
|
style={{ objectPosition: "center 25%" }}
|
||||||
onPlay={() => handlePlay(index)}
|
onPlay={() => {
|
||||||
onPause={() => handlePause(index)}
|
setIsVideoPlaying(true);
|
||||||
|
handlePlay(index);
|
||||||
|
}}
|
||||||
|
onPause={() => {
|
||||||
|
setIsVideoPlaying(false);
|
||||||
|
handlePause(index);
|
||||||
|
}}
|
||||||
autoPlay={false}
|
autoPlay={false}
|
||||||
title="Video"
|
title="Video"
|
||||||
>
|
>
|
||||||
@@ -83,27 +103,24 @@ export const AgentImageItem: React.FC<AgentImageItemProps> = React.memo(
|
|||||||
src={image}
|
src={image}
|
||||||
alt="Image"
|
alt="Image"
|
||||||
fill
|
fill
|
||||||
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
|
className="rounded-[1.5rem] object-cover"
|
||||||
className="rounded-xl object-cover"
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
{isVideoFile && playingVideoIndex !== index && (
|
{isVideoFile && playingVideoIndex !== index && (
|
||||||
<div className="absolute bottom-2 left-2 sm:bottom-3 sm:left-3 md:bottom-4 md:left-4 lg:bottom-[1.25rem] lg:left-[1.25rem]">
|
<div className="absolute bottom-2 left-2 sm:bottom-3 sm:left-3 md:bottom-4 md:left-4 lg:bottom-6 lg:left-6">
|
||||||
<Button
|
<AutogptButton
|
||||||
size="default"
|
icon
|
||||||
|
variant={"secondary"}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (videoRef.current) {
|
if (videoRef.current) {
|
||||||
videoRef.current.play();
|
videoRef.current.play();
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<span className="pr-1 font-neue text-sm font-medium leading-6 tracking-tight text-[#272727] dark:text-neutral-200 sm:pr-2 sm:text-base sm:leading-7 md:text-lg md:leading-8 lg:text-xl lg:leading-9">
|
Play
|
||||||
Play demo
|
</AutogptButton>
|
||||||
</span>
|
|
||||||
<PlayIcon className="h-5 w-5 text-black dark:text-neutral-200 sm:h-6 sm:w-6 md:h-7 md:w-7" />
|
|
||||||
</Button>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -2,15 +2,15 @@ import type { Meta, StoryObj } from "@storybook/react";
|
|||||||
import { AgentImages } from "./AgentImages";
|
import { AgentImages } from "./AgentImages";
|
||||||
|
|
||||||
const meta = {
|
const meta = {
|
||||||
title: "AGPT UI/Agent Images",
|
title: "Agpt UI/marketing/Agent Images",
|
||||||
component: AgentImages,
|
component: AgentImages,
|
||||||
parameters: {
|
decorators: [
|
||||||
layout: {
|
(Story) => (
|
||||||
center: true,
|
<div className="mx-auto flex h-full w-[80%] items-center justify-center p-4">
|
||||||
fullscreen: true,
|
<Story />
|
||||||
padding: 0,
|
</div>
|
||||||
},
|
),
|
||||||
},
|
],
|
||||||
tags: ["autodocs"],
|
tags: ["autodocs"],
|
||||||
argTypes: {
|
argTypes: {
|
||||||
images: { control: "object" },
|
images: { control: "object" },
|
||||||
@@ -25,34 +25,7 @@ export const Default: Story = {
|
|||||||
images: [
|
images: [
|
||||||
"https://framerusercontent.com/images/KCIpxr9f97EGJgpaoqnjKsrOPwI.jpg",
|
"https://framerusercontent.com/images/KCIpxr9f97EGJgpaoqnjKsrOPwI.jpg",
|
||||||
"https://youtu.be/KWonAsyKF3g?si=JMibxlN_6OVo6LhJ",
|
"https://youtu.be/KWonAsyKF3g?si=JMibxlN_6OVo6LhJ",
|
||||||
"https://storage.googleapis.com/agpt-dev-website-media/DJINeo.mp4",
|
|
||||||
],
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export const OnlyImages: Story = {
|
|
||||||
args: {
|
|
||||||
images: [
|
|
||||||
"https://framerusercontent.com/images/KCIpxr9f97EGJgpaoqnjKsrOPwI.jpg",
|
|
||||||
"https://upload.wikimedia.org/wikipedia/commons/c/c5/Big_buck_bunny_poster_big.jpg",
|
|
||||||
],
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export const WithVideos: Story = {
|
|
||||||
args: {
|
|
||||||
images: [
|
|
||||||
"https://storage.googleapis.com/agpt-dev-website-media/DJINeo.mp4",
|
|
||||||
"http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4",
|
"http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4",
|
||||||
"https://youtu.be/KWonAsyKF3g?si=JMibxlN_6OVo6LhJ",
|
|
||||||
],
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export const SingleItem: Story = {
|
|
||||||
args: {
|
|
||||||
images: [
|
|
||||||
"https://upload.wikimedia.org/wikipedia/commons/c/c5/Big_buck_bunny_poster_big.jpg",
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -26,19 +26,17 @@ export const AgentImages: React.FC<AgentImagesProps> = ({ images }) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-full overflow-y-auto bg-white px-2 dark:bg-transparent lg:w-[56.25rem]">
|
<div className="w-full space-y-4">
|
||||||
<div className="space-y-4 sm:space-y-6 md:space-y-[1.875rem]">
|
{images.map((image, index) => (
|
||||||
{images.map((image, index) => (
|
<AgentImageItem
|
||||||
<AgentImageItem
|
key={index}
|
||||||
key={index}
|
image={image}
|
||||||
image={image}
|
index={index}
|
||||||
index={index}
|
playingVideoIndex={playingVideoIndex}
|
||||||
playingVideoIndex={playingVideoIndex}
|
handlePlay={handlePlay}
|
||||||
handlePlay={handlePlay}
|
handlePause={handlePause}
|
||||||
handlePause={handlePause}
|
/>
|
||||||
/>
|
))}
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,12 +1,14 @@
|
|||||||
import type { Meta, StoryObj } from "@storybook/react";
|
import type { Meta, StoryObj } from "@storybook/react";
|
||||||
import { AgentInfo } from "./AgentInfo";
|
import { AgentInfo } from "./AgentInfo";
|
||||||
import { userEvent, within } from "@storybook/test";
|
|
||||||
|
|
||||||
const meta = {
|
const meta = {
|
||||||
title: "AGPT UI/Agent Info",
|
title: "Agpt UI/marketing/Agent Info",
|
||||||
component: AgentInfo,
|
component: AgentInfo,
|
||||||
parameters: {
|
parameters: {
|
||||||
layout: "centered",
|
layout: {
|
||||||
|
center: true,
|
||||||
|
padding: 0,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
tags: ["autodocs"],
|
tags: ["autodocs"],
|
||||||
argTypes: {
|
argTypes: {
|
||||||
@@ -19,7 +21,15 @@ const meta = {
|
|||||||
categories: { control: "object" },
|
categories: { control: "object" },
|
||||||
lastUpdated: { control: "text" },
|
lastUpdated: { control: "text" },
|
||||||
version: { control: "text" },
|
version: { control: "text" },
|
||||||
|
storeListingVersionId: { control: "text" },
|
||||||
},
|
},
|
||||||
|
decorators: [
|
||||||
|
(Story) => (
|
||||||
|
<div className="flex items-center justify-center p-4">
|
||||||
|
<Story />
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
],
|
||||||
} satisfies Meta<typeof AgentInfo>;
|
} satisfies Meta<typeof AgentInfo>;
|
||||||
|
|
||||||
export default meta;
|
export default meta;
|
||||||
@@ -30,10 +40,10 @@ export const Default: Story = {
|
|||||||
user: null,
|
user: null,
|
||||||
libraryAgent: null,
|
libraryAgent: null,
|
||||||
name: "AI Video Generator",
|
name: "AI Video Generator",
|
||||||
storeListingVersionId: "123",
|
storeListingVersionId: "123abc456def",
|
||||||
creator: "Toran Richards",
|
creator: "Toran Richards",
|
||||||
shortDescription:
|
shortDescription:
|
||||||
"Transform ideas into breathtaking images with this AI-powered Image Generator.",
|
"Transform ideas into breathtaking videos with this AI-powered Video Generator.",
|
||||||
longDescription: `Create Viral-Ready Content in Seconds! Transform trending topics into engaging videos with this cutting-edge AI Video Generator. Perfect for content creators, social media managers, and marketers looking to quickly produce high-quality content.
|
longDescription: `Create Viral-Ready Content in Seconds! Transform trending topics into engaging videos with this cutting-edge AI Video Generator. Perfect for content creators, social media managers, and marketers looking to quickly produce high-quality content.
|
||||||
|
|
||||||
Key features include:
|
Key features include:
|
||||||
@@ -51,87 +61,52 @@ Key features include:
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const LowRating: Story = {
|
export const LongContent: Story = {
|
||||||
args: {
|
args: {
|
||||||
...Default.args,
|
...Default.args,
|
||||||
name: "Data Analyzer",
|
name: "Super Advanced Ultra-Intelligent Universal Comprehensive AI-Powered Video Generator Pro Plus Premium Enterprise Edition With Extended Capabilities",
|
||||||
creator: "DataTech",
|
creator:
|
||||||
shortDescription:
|
"Global Artificial Intelligence Research and Development Consortium for Advanced Technology Implementation and Enterprise Solutions",
|
||||||
"Analyze complex datasets with machine learning algorithms",
|
longDescription: `Create Viral-Ready Content in Seconds! Transform trending topics into engaging videos with this cutting-edge AI Video Generator. Perfect for content creators, social media managers, and marketers looking to quickly produce high-quality content.
|
||||||
longDescription:
|
|
||||||
"A comprehensive data analysis tool that leverages machine learning to provide deep insights into your datasets. Currently in beta testing phase.",
|
Our advanced AI algorithms analyze current trends and viewer preferences to generate videos that are more likely to engage your target audience and achieve better conversion rates. The system adapts to your brand voice and style guidelines to maintain consistency across all your content.
|
||||||
rating: 2.7,
|
|
||||||
runs: 5000,
|
Key features include:
|
||||||
categories: ["Data Analysis", "Machine Learning"],
|
- Customizable video output with adjustable parameters for length, style, pacing, and transition effects
|
||||||
lastUpdated: "1 week ago",
|
- 15+ pre-made templates for different content types including explainer videos, product demonstrations, social media stories, and advertisements
|
||||||
version: "0.9.5",
|
- Auto scene detection that intelligently segments your content into engaging chapters
|
||||||
|
- Smart text-to-speech with over 50 natural-sounding voices in multiple languages and dialects
|
||||||
|
- Multiple export formats optimized for different platforms (YouTube, Instagram, TikTok, Facebook, Twitter)
|
||||||
|
- SEO-optimized suggestions for titles, descriptions, and tags to maximize discoverability
|
||||||
|
- Analytics dashboard to track performance metrics and audience engagement
|
||||||
|
- Collaborative workspace for team projects with permission management
|
||||||
|
- Regular updates with new features and improvements based on user feedback
|
||||||
|
|
||||||
|
The AI Video Generator integrates seamlessly with your existing workflow and content management systems. You can import assets from Adobe Creative Suite, Canva, and other popular design tools. Our cloud-based processing ensures fast rendering without taxing your local system resources.
|
||||||
|
|
||||||
|
With our enterprise plan, you'll get priority support, custom template development, and advanced branding options to ensure your videos stand out in today's crowded digital landscape.`,
|
||||||
|
|
||||||
|
categories: [
|
||||||
|
"Video",
|
||||||
|
"Content Creation",
|
||||||
|
"Social Media",
|
||||||
|
"Marketing",
|
||||||
|
"Artificial Intelligence",
|
||||||
|
"Machine Learning",
|
||||||
|
"Neural Networks",
|
||||||
|
"Deep Learning",
|
||||||
|
"Computer Vision",
|
||||||
|
"NLP",
|
||||||
|
"Automation",
|
||||||
|
"Productivity Tools",
|
||||||
|
],
|
||||||
|
runs: 1000000000,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const HighRuns: Story = {
|
export const NoCategories: Story = {
|
||||||
args: {
|
args: {
|
||||||
...Default.args,
|
...Default.args,
|
||||||
name: "Code Assistant",
|
categories: [],
|
||||||
creator: "DevAI",
|
|
||||||
shortDescription:
|
|
||||||
"Get AI-powered coding help for various programming languages",
|
|
||||||
longDescription:
|
|
||||||
"An advanced AI coding assistant that supports multiple programming languages and frameworks. Features include code completion, refactoring suggestions, and bug detection.",
|
|
||||||
rating: 4.8,
|
|
||||||
runs: 1000000,
|
|
||||||
categories: ["Programming", "AI", "Developer Tools"],
|
|
||||||
lastUpdated: "1 day ago",
|
|
||||||
version: "2.1.3",
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export const WithInteraction: Story = {
|
|
||||||
args: {
|
|
||||||
...Default.args,
|
|
||||||
name: "Task Planner",
|
|
||||||
creator: "Productivity AI",
|
|
||||||
shortDescription: "Plan and organize your tasks efficiently with AI",
|
|
||||||
longDescription:
|
|
||||||
"An intelligent task management system that helps you organize, prioritize, and complete your tasks more efficiently. Features smart scheduling and AI-powered suggestions.",
|
|
||||||
rating: 4.2,
|
|
||||||
runs: 50000,
|
|
||||||
categories: ["Productivity", "Task Management", "AI"],
|
|
||||||
lastUpdated: "3 days ago",
|
|
||||||
version: "1.5.2",
|
|
||||||
},
|
|
||||||
play: async ({ canvasElement }) => {
|
|
||||||
const canvas = within(canvasElement);
|
|
||||||
|
|
||||||
// Test run agent button
|
|
||||||
const runButton = canvas.getByText("Run agent");
|
|
||||||
await userEvent.hover(runButton);
|
|
||||||
await userEvent.click(runButton);
|
|
||||||
|
|
||||||
// Test rating interaction
|
|
||||||
const ratingStars = canvas.getAllByLabelText(/Star Icon/);
|
|
||||||
await userEvent.hover(ratingStars[3]);
|
|
||||||
await userEvent.click(ratingStars[3]);
|
|
||||||
|
|
||||||
// Test category interaction
|
|
||||||
const category = canvas.getByText("Productivity");
|
|
||||||
await userEvent.hover(category);
|
|
||||||
await userEvent.click(category);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export const LongDescription: Story = {
|
|
||||||
args: {
|
|
||||||
...Default.args,
|
|
||||||
name: "AI Writing Assistant",
|
|
||||||
creator: "WordCraft AI",
|
|
||||||
shortDescription:
|
|
||||||
"Enhance your writing with our advanced AI-powered assistant.",
|
|
||||||
longDescription:
|
|
||||||
"It offers real-time suggestions for grammar, style, and tone, helps with research and fact-checking, and can even generate content ideas based on your input.",
|
|
||||||
rating: 4.7,
|
|
||||||
runs: 75000,
|
|
||||||
categories: ["Writing", "AI", "Content Creation"],
|
|
||||||
lastUpdated: "5 days ago",
|
|
||||||
version: "3.0.1",
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ import Link from "next/link";
|
|||||||
import { useToast } from "@/components/ui/use-toast";
|
import { useToast } from "@/components/ui/use-toast";
|
||||||
|
|
||||||
import { useOnboarding } from "../onboarding/onboarding-provider";
|
import { useOnboarding } from "../onboarding/onboarding-provider";
|
||||||
|
import AutogptButton from "./AutogptButton";
|
||||||
|
import { Chip } from "./Chip";
|
||||||
import { User } from "@supabase/supabase-js";
|
import { User } from "@supabase/supabase-js";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { FC, useCallback, useMemo, useState } from "react";
|
import { FC, useCallback, useMemo, useState } from "react";
|
||||||
@@ -78,7 +80,7 @@ export const AgentInfo: FC<AgentInfoProps> = ({
|
|||||||
variant: "destructive",
|
variant: "destructive",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [toast, api, storeListingVersionId, completeStep, router]);
|
}, [toast, api, storeListingVersionId, completeStep, router, libraryAgent]);
|
||||||
|
|
||||||
const handleDownload = useCallback(async () => {
|
const handleDownload = useCallback(async () => {
|
||||||
const downloadAgent = async (): Promise<void> => {
|
const downloadAgent = async (): Promise<void> => {
|
||||||
@@ -125,113 +127,104 @@ export const AgentInfo: FC<AgentInfoProps> = ({
|
|||||||
}, [setDownloading, api, storeListingVersionId, toast]);
|
}, [setDownloading, api, storeListingVersionId, toast]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-full max-w-[396px] px-4 sm:px-6 lg:w-[396px] lg:px-0">
|
<div className="w-full max-w-[27rem] space-y-7">
|
||||||
{/* Title */}
|
{/* Top part */}
|
||||||
<div className="mb-3 w-full font-poppins text-2xl font-medium leading-normal text-neutral-900 dark:text-neutral-100 sm:text-3xl lg:mb-4 lg:text-[35px] lg:leading-10">
|
<div className="space-y-[3.25rem]">
|
||||||
{name}
|
{/* Agent name */}
|
||||||
</div>
|
<div>
|
||||||
|
<h2 className="font-poppins text-[1.75rem] font-medium leading-[2.5rem] text-zinc-800">
|
||||||
|
{name}
|
||||||
|
</h2>
|
||||||
|
|
||||||
{/* Creator */}
|
{/* Creator name */}
|
||||||
<div className="mb-3 flex w-full items-center gap-1.5 lg:mb-4">
|
<div className="mb-7 flex w-full items-center gap-1.5 font-sans">
|
||||||
<div className="font-sans text-base font-normal text-neutral-800 dark:text-neutral-200 sm:text-lg lg:text-xl">
|
<p className="text-base font-normal text-zinc-800">by</p>
|
||||||
by
|
<Link
|
||||||
</div>
|
href={`/marketplace/creator/${encodeURIComponent(creator)}`}
|
||||||
<Link
|
className="text-base font-medium text-zinc-800 hover:underline"
|
||||||
href={`/marketplace/creator/${encodeURIComponent(creator)}`}
|
>
|
||||||
className="font-sans text-base font-medium text-neutral-800 hover:underline dark:text-neutral-200 sm:text-lg lg:text-xl"
|
{creator}
|
||||||
>
|
</Link>
|
||||||
{creator}
|
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Short Description */}
|
|
||||||
<div className="mb-4 line-clamp-2 w-full font-sans text-base font-normal leading-normal text-neutral-600 dark:text-neutral-300 sm:text-lg lg:mb-6 lg:text-xl lg:leading-7">
|
|
||||||
{shortDescription}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Buttons */}
|
|
||||||
<div className="mb-4 flex w-full gap-3 lg:mb-[60px]">
|
|
||||||
{user && (
|
|
||||||
<button
|
|
||||||
className={cn(
|
|
||||||
"inline-flex min-w-24 items-center justify-center rounded-full bg-violet-600 px-4 py-3",
|
|
||||||
"transition-colors duration-200 hover:bg-violet-500 disabled:bg-zinc-400",
|
|
||||||
)}
|
|
||||||
onClick={libraryAction}
|
|
||||||
disabled={adding}
|
|
||||||
>
|
|
||||||
<span className="justify-start font-sans text-sm font-medium leading-snug text-primary-foreground">
|
|
||||||
{libraryAgent ? "See runs" : "Add to library"}
|
|
||||||
</span>
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
<button
|
|
||||||
className={cn(
|
|
||||||
"inline-flex min-w-24 items-center justify-center rounded-full bg-zinc-200 px-4 py-3",
|
|
||||||
"transition-colors duration-200 hover:bg-zinc-200/70 disabled:bg-zinc-200/40",
|
|
||||||
)}
|
|
||||||
onClick={handleDownload}
|
|
||||||
disabled={downloading}
|
|
||||||
>
|
|
||||||
<div className="justify-start text-center font-sans text-sm font-medium leading-snug text-zinc-800">
|
|
||||||
Download agent
|
|
||||||
</div>
|
</div>
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Rating and Runs */}
|
|
||||||
<div className="mb-4 flex w-full items-center justify-between lg:mb-[44px]">
|
|
||||||
<div className="flex items-center gap-1.5 sm:gap-2">
|
|
||||||
<span className="whitespace-nowrap font-sans text-base font-semibold text-neutral-800 dark:text-neutral-200 sm:text-lg">
|
|
||||||
{rating.toFixed(1)}
|
|
||||||
</span>
|
|
||||||
<div className="flex gap-0.5">{StarRatingIcons(rating)}</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="whitespace-nowrap font-sans text-base font-semibold text-neutral-800 dark:text-neutral-200 sm:text-lg">
|
|
||||||
{runs.toLocaleString()} runs
|
{/* Download and run button */}
|
||||||
|
{/* TODO - Add one more button */}
|
||||||
|
<div className="flex w-full items-center gap-3">
|
||||||
|
{user && (
|
||||||
|
<AutogptButton onClick={libraryAction} disabled={adding} icon>
|
||||||
|
{libraryAgent ? "Run agent" : "Add to library"}
|
||||||
|
</AutogptButton>
|
||||||
|
)}
|
||||||
|
<AutogptButton
|
||||||
|
variant={"secondary"}
|
||||||
|
onClick={handleDownload}
|
||||||
|
disabled={downloading}
|
||||||
|
>
|
||||||
|
Download agent
|
||||||
|
</AutogptButton>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Runs and ratings */}
|
||||||
|
<div className="flex w-full items-center gap-10">
|
||||||
|
<div className="flex items-center gap-1.5">
|
||||||
|
<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-base font-medium text-zinc-800">
|
||||||
|
{runs.toLocaleString()} runs
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Separator */}
|
{/* Separator */}
|
||||||
<Separator className="mb-4 lg:mb-[44px]" />
|
<Separator className="bg-neutral-300" />
|
||||||
|
|
||||||
{/* Description Section */}
|
{/* Bottom part */}
|
||||||
<div className="mb-4 w-full lg:mb-[36px]">
|
<div className="space-y-9">
|
||||||
<div className="mb-1.5 font-sans text-base font-medium leading-6 text-neutral-800 dark:text-neutral-200 sm:mb-2">
|
<div className="space-y-2.5">
|
||||||
Description
|
<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-zinc-600">
|
||||||
|
{longDescription}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="whitespace-pre-line font-sans text-base font-normal leading-6 text-neutral-600 dark:text-neutral-400">
|
|
||||||
{longDescription}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Categories */}
|
{/* Categories */}
|
||||||
<div className="mb-4 flex w-full flex-col gap-1.5 sm:gap-2 lg:mb-[36px]">
|
<div className="space-y-2.5">
|
||||||
<div className="decoration-skip-ink-none mb-1.5 font-sans text-base font-medium leading-6 text-neutral-800 dark:text-neutral-200 sm:mb-2">
|
<p className="font-sans text-base font-medium text-zinc-800">
|
||||||
Categories
|
Categories
|
||||||
|
</p>
|
||||||
|
<div className="flex flex-wrap gap-2.5">
|
||||||
|
{categories.map((category, index) => (
|
||||||
|
<Chip
|
||||||
|
key={index}
|
||||||
|
className="hover:border-zinc-400 hover:bg-white"
|
||||||
|
>
|
||||||
|
{category}
|
||||||
|
</Chip>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-wrap gap-1.5 sm:gap-2">
|
|
||||||
{categories.map((category, index) => (
|
|
||||||
<div
|
|
||||||
key={index}
|
|
||||||
className="decoration-skip-ink-none whitespace-nowrap rounded-full border border-neutral-600 bg-white px-2 py-0.5 font-sans text-base font-normal leading-6 text-neutral-800 underline-offset-[from-font] dark:border-neutral-700 dark:bg-neutral-800 dark:text-neutral-200 sm:px-[16px] sm:py-[10px]"
|
|
||||||
>
|
|
||||||
{category}
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Version History */}
|
{/* TODO : Rating Agent */}
|
||||||
<div className="flex w-full flex-col gap-0.5 sm:gap-1">
|
|
||||||
<div className="decoration-skip-ink-none mb-1.5 font-sans text-base font-medium leading-6 text-neutral-800 dark:text-neutral-200 sm:mb-2">
|
{/* Version History */}
|
||||||
Version history
|
<div className="space-y-2.5">
|
||||||
</div>
|
<p className="font-sans text-base font-medium text-zinc-800">
|
||||||
<div className="decoration-skip-ink-none font-sans text-base font-normal leading-6 text-neutral-600 underline-offset-[from-font] dark:text-neutral-400">
|
Version history
|
||||||
Last updated {lastUpdated}
|
</p>
|
||||||
</div>
|
<div className="space-y-1.5">
|
||||||
<div className="text-xs text-neutral-600 dark:text-neutral-400 sm:text-sm">
|
<p className="font-sans text-base font-normal text-zinc-600">
|
||||||
Version {version}
|
Last updated {lastUpdated}
|
||||||
|
</p>
|
||||||
|
<p className="font-sans text-base font-normal text-zinc-600">
|
||||||
|
Version {version}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,19 +1,28 @@
|
|||||||
import type { Meta, StoryObj } from "@storybook/react";
|
import type { Meta, StoryObj } from "@storybook/react";
|
||||||
import { AgentTable } from "./AgentTable";
|
import { AgentTable } from "./AgentTable";
|
||||||
import { AgentTableRowProps } from "./AgentTableRow";
|
import { within, expect, fn } from "@storybook/test";
|
||||||
import { userEvent, within, expect } from "@storybook/test";
|
|
||||||
import { StatusType } from "./Status";
|
import { StatusType } from "./Status";
|
||||||
|
|
||||||
const meta: Meta<typeof AgentTable> = {
|
const meta = {
|
||||||
title: "AGPT UI/Agent Table",
|
title: "Agpt UI/marketing/Agent Table",
|
||||||
component: AgentTable,
|
component: AgentTable,
|
||||||
|
parameters: {
|
||||||
|
layout: "fullscreen",
|
||||||
|
},
|
||||||
tags: ["autodocs"],
|
tags: ["autodocs"],
|
||||||
};
|
decorators: [
|
||||||
|
(Story) => (
|
||||||
|
<div className="container mx-auto p-4">
|
||||||
|
<Story />
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
],
|
||||||
|
} satisfies Meta<typeof AgentTable>;
|
||||||
|
|
||||||
export default meta;
|
export default meta;
|
||||||
type Story = StoryObj<typeof AgentTable>;
|
type Story = StoryObj<typeof meta>;
|
||||||
|
|
||||||
const sampleAgents: AgentTableRowProps[] = [
|
const sampleAgents = [
|
||||||
{
|
{
|
||||||
id: 43,
|
id: 43,
|
||||||
agentName: "Super Coder",
|
agentName: "Super Coder",
|
||||||
@@ -22,17 +31,13 @@ const sampleAgents: AgentTableRowProps[] = [
|
|||||||
"https://ddz4ak4pa3d19.cloudfront.net/cache/53/b2/53b2bc7d7900f0e1e60bf64ebf38032d.jpg",
|
"https://ddz4ak4pa3d19.cloudfront.net/cache/53/b2/53b2bc7d7900f0e1e60bf64ebf38032d.jpg",
|
||||||
],
|
],
|
||||||
dateSubmitted: "2023-05-15",
|
dateSubmitted: "2023-05-15",
|
||||||
status: "approved",
|
status: "approved" as StatusType,
|
||||||
runs: 1500,
|
runs: 1500,
|
||||||
rating: 4.8,
|
rating: 4.8,
|
||||||
agent_id: "43",
|
agent_id: "43",
|
||||||
agent_version: 1,
|
agent_version: 1,
|
||||||
sub_heading: "Super Coder",
|
sub_heading: "Super Coder",
|
||||||
date_submitted: "2023-05-15",
|
date_submitted: "2023-05-15",
|
||||||
onEditSubmission: () => console.log("Edit Super Coder"),
|
|
||||||
onDeleteSubmission: () => console.log("Delete Super Coder"),
|
|
||||||
selectedAgents: new Set(),
|
|
||||||
setSelectedAgents: () => {},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 44,
|
id: 44,
|
||||||
@@ -42,17 +47,13 @@ const sampleAgents: AgentTableRowProps[] = [
|
|||||||
"https://ddz4ak4pa3d19.cloudfront.net/cache/40/f7/40f7bc97c952f8df0f9c88d29defe8d4.jpg",
|
"https://ddz4ak4pa3d19.cloudfront.net/cache/40/f7/40f7bc97c952f8df0f9c88d29defe8d4.jpg",
|
||||||
],
|
],
|
||||||
dateSubmitted: "2023-05-10",
|
dateSubmitted: "2023-05-10",
|
||||||
status: "awaiting_review",
|
status: "awaiting_review" as StatusType,
|
||||||
runs: 1200,
|
runs: 1200,
|
||||||
rating: 4.5,
|
rating: 4.5,
|
||||||
agent_id: "44",
|
agent_id: "44",
|
||||||
agent_version: 1,
|
agent_version: 1,
|
||||||
sub_heading: "Data Analyzer",
|
sub_heading: "Data Analyzer",
|
||||||
date_submitted: "2023-05-10",
|
date_submitted: "2023-05-10",
|
||||||
onEditSubmission: () => console.log("Edit Data Analyzer"),
|
|
||||||
onDeleteSubmission: () => console.log("Delete Data Analyzer"),
|
|
||||||
selectedAgents: new Set(),
|
|
||||||
setSelectedAgents: () => {},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 45,
|
id: 45,
|
||||||
@@ -62,48 +63,95 @@ const sampleAgents: AgentTableRowProps[] = [
|
|||||||
"https://ddz4ak4pa3d19.cloudfront.net/cache/14/9e/149ebb9014aa8c0097e72ed89845af0e.jpg",
|
"https://ddz4ak4pa3d19.cloudfront.net/cache/14/9e/149ebb9014aa8c0097e72ed89845af0e.jpg",
|
||||||
],
|
],
|
||||||
dateSubmitted: "2023-05-05",
|
dateSubmitted: "2023-05-05",
|
||||||
status: "draft",
|
status: "draft" as StatusType,
|
||||||
runs: 800,
|
runs: 800,
|
||||||
rating: 4.2,
|
rating: 4.2,
|
||||||
agent_id: "45",
|
agent_id: "45",
|
||||||
agent_version: 1,
|
agent_version: 1,
|
||||||
sub_heading: "UI Designer",
|
sub_heading: "UI Designer",
|
||||||
date_submitted: "2023-05-05",
|
date_submitted: "2023-05-05",
|
||||||
onEditSubmission: () => console.log("Edit UI Designer"),
|
|
||||||
onDeleteSubmission: () => console.log("Delete UI Designer"),
|
|
||||||
selectedAgents: new Set(),
|
|
||||||
setSelectedAgents: () => {},
|
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
export const Default: Story = {
|
export const Default: Story = {
|
||||||
args: {
|
args: {
|
||||||
agents: sampleAgents,
|
agents: sampleAgents,
|
||||||
|
onEditSubmission: fn(() => {
|
||||||
|
console.log("Edit submission");
|
||||||
|
}),
|
||||||
|
onDeleteSubmission: fn(() => {
|
||||||
|
console.log(`Delete submission for agent `);
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const EmptyTable: Story = {
|
export const LongContent: Story = {
|
||||||
args: {
|
args: {
|
||||||
agents: [],
|
...Default.args,
|
||||||
|
agents: [
|
||||||
|
{
|
||||||
|
...sampleAgents[0],
|
||||||
|
agentName:
|
||||||
|
"Super Advanced Artificial Intelligence Code Generator and Optimizer with Machine Learning Capabilities",
|
||||||
|
description:
|
||||||
|
"This is an extremely advanced artificial intelligence code generator that can write clean, efficient, and optimized code in multiple programming languages. It utilizes state-of-the-art machine learning algorithms to understand requirements and generate appropriate solutions while following best practices and design patterns. The agent can handle complex programming tasks, debug existing code, and suggest improvements to enhance performance and readability.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
...sampleAgents[1],
|
||||||
|
agentName:
|
||||||
|
"Super Advanced Artificial Intelligence Code Generator and Optimizer with Machine Learning Capabilities",
|
||||||
|
description:
|
||||||
|
"A sophisticated data analysis tool capable of processing petabytes of structured and unstructured data to extract meaningful insights and patterns. This agent leverages advanced statistical methods, machine learning techniques, and data visualization capabilities to transform raw data into actionable business intelligence. It can handle time series analysis, predictive modeling, anomaly detection, and generate comprehensive reports with minimal human intervention.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
...sampleAgents[2],
|
||||||
|
agentName:
|
||||||
|
"Super Advanced Artificial Intelligence Code Generator and Optimizer with Machine Learning Capabilities",
|
||||||
|
description:
|
||||||
|
"This specialized UI/UX design assistant creates beautiful, accessible, and intuitive user interfaces for web and mobile applications. By combining principles of human-centered design with modern aesthetic sensibilities, the agent produces wireframes, mockups, and interactive prototypes that enhance user engagement and satisfaction. It follows design systems, ensures consistent branding, and optimizes layouts for various screen sizes while maintaining accessibility standards.",
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
// Tests
|
export const ManyAgents: Story = {
|
||||||
export const InteractionTest: Story = {
|
args: {
|
||||||
...Default,
|
...Default.args,
|
||||||
play: async ({ canvasElement }) => {
|
agents: Array(20)
|
||||||
const canvas = within(canvasElement);
|
.fill(null)
|
||||||
const editButtons = await canvas.findAllByText("Edit");
|
.map((_, index) => ({
|
||||||
await userEvent.click(editButtons[0]);
|
...sampleAgents[index % 3],
|
||||||
// You would typically assert something here, but console.log is used in the mocked function
|
id: 100 + index,
|
||||||
|
agent_id: `${100 + index}`,
|
||||||
|
agentName: `Test Agent ${index + 1}`,
|
||||||
|
})),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const EmptyTableTest: Story = {
|
export const EmptyTableTest: Story = {
|
||||||
...EmptyTable,
|
args: {
|
||||||
|
...Default.args,
|
||||||
|
agents: [],
|
||||||
|
},
|
||||||
play: async ({ canvasElement }) => {
|
play: async ({ canvasElement }) => {
|
||||||
const canvas = within(canvasElement);
|
const canvas = within(canvasElement);
|
||||||
const emptyMessage = canvas.getByText("No agents found");
|
const emptyMessages = canvas.getAllByText(
|
||||||
expect(emptyMessage).toBeTruthy();
|
"No agents available. Create your first agent to get started!",
|
||||||
|
);
|
||||||
|
await expect(emptyMessages.length).toBeGreaterThan(0);
|
||||||
|
await expect(emptyMessages[0]).toBeInTheDocument();
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const TestingInteractions: Story = {
|
||||||
|
args: {
|
||||||
|
...Default.args,
|
||||||
|
agents: sampleAgents,
|
||||||
|
},
|
||||||
|
play: async ({ canvasElement, args }) => {
|
||||||
|
const canvas = within(canvasElement);
|
||||||
|
|
||||||
|
const checkboxes = canvas.getAllByTestId("dropdown-button");
|
||||||
|
await expect(checkboxes.length).toBeGreaterThan(0);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -4,6 +4,15 @@ import * as React from "react";
|
|||||||
import { AgentTableRow, AgentTableRowProps } from "./AgentTableRow";
|
import { AgentTableRow, AgentTableRowProps } from "./AgentTableRow";
|
||||||
import { AgentTableCard } from "./AgentTableCard";
|
import { AgentTableCard } from "./AgentTableCard";
|
||||||
import { StoreSubmissionRequest } from "@/lib/autogpt-server-api/types";
|
import { StoreSubmissionRequest } from "@/lib/autogpt-server-api/types";
|
||||||
|
import {
|
||||||
|
Table,
|
||||||
|
TableBody,
|
||||||
|
TableCell,
|
||||||
|
TableHead,
|
||||||
|
TableHeader,
|
||||||
|
TableRow,
|
||||||
|
} from "@/components/ui/table";
|
||||||
|
import { Checkbox } from "@/components/ui/checkbox";
|
||||||
|
|
||||||
export interface AgentTableProps {
|
export interface AgentTableProps {
|
||||||
agents: Omit<
|
agents: Omit<
|
||||||
@@ -40,79 +49,91 @@ export const AgentTable: React.FC<AgentTableProps> = ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-full">
|
<div className="w-full border-t border-neutral-300">
|
||||||
{/* Table header - Hide on mobile */}
|
{/* Table for desktop view */}
|
||||||
<div className="hidden flex-col md:flex">
|
<div className="hidden md:block">
|
||||||
<div className="border-t border-neutral-300 dark:border-neutral-700" />
|
<Table>
|
||||||
<div className="flex items-center px-4 py-2">
|
<TableHeader>
|
||||||
<div className="flex items-center">
|
<TableRow>
|
||||||
<div className="flex min-w-[120px] items-center">
|
<TableHead>
|
||||||
<input
|
<Checkbox
|
||||||
type="checkbox"
|
id="selectAllAgents"
|
||||||
id="selectAllAgents"
|
aria-label="Select all agents"
|
||||||
aria-label="Select all agents"
|
checked={
|
||||||
className="mr-4 h-5 w-5 rounded border-2 border-neutral-400 dark:border-neutral-600"
|
selectedAgents.size === agents.length && agents.length > 0
|
||||||
checked={
|
}
|
||||||
selectedAgents.size === agents.length && agents.length > 0
|
onCheckedChange={(checked) => {
|
||||||
}
|
if (checked) {
|
||||||
onChange={handleSelectAll}
|
setSelectedAgents(
|
||||||
/>
|
new Set(agents.map((agent) => agent.agent_id)),
|
||||||
<label
|
);
|
||||||
htmlFor="selectAllAgents"
|
} else {
|
||||||
className="text-sm font-medium text-neutral-800 dark:text-neutral-200"
|
setSelectedAgents(new Set());
|
||||||
>
|
}
|
||||||
Select all
|
}}
|
||||||
</label>
|
/>
|
||||||
</div>
|
</TableHead>
|
||||||
</div>
|
<TableHead className="font-sans text-sm font-medium text-neutral-800">
|
||||||
<div className="ml-2 grid w-full grid-cols-[400px,150px,150px,100px,100px,50px] items-center">
|
Agent info
|
||||||
<div className="text-sm font-medium text-neutral-800 dark:text-neutral-200">
|
</TableHead>
|
||||||
Agent info
|
<TableHead className="font-sans text-sm font-medium text-neutral-800">
|
||||||
</div>
|
Date submitted
|
||||||
<div className="text-sm font-medium text-neutral-800 dark:text-neutral-200">
|
</TableHead>
|
||||||
Date submitted
|
<TableHead className="font-sans text-sm font-medium text-neutral-800">
|
||||||
</div>
|
Status
|
||||||
<div className="text-sm font-medium text-neutral-800 dark:text-neutral-200">
|
</TableHead>
|
||||||
Status
|
<TableHead className="font-sans text-sm font-medium text-neutral-800">
|
||||||
</div>
|
Runs
|
||||||
<div className="text-right text-sm font-medium text-neutral-800 dark:text-neutral-200">
|
</TableHead>
|
||||||
Runs
|
<TableHead className="text-right font-sans text-sm font-medium text-neutral-800">
|
||||||
</div>
|
Reviews
|
||||||
<div className="text-right text-sm font-medium text-neutral-800 dark:text-neutral-200">
|
</TableHead>
|
||||||
Reviews
|
<TableHead className="font-sans text-sm font-medium text-neutral-800"></TableHead>
|
||||||
</div>
|
</TableRow>
|
||||||
<div></div>
|
</TableHeader>
|
||||||
</div>
|
<TableBody>
|
||||||
</div>
|
{agents.length > 0 ? (
|
||||||
<div className="border-b border-neutral-300 dark:border-neutral-700" />
|
agents.map((agent) => (
|
||||||
|
<AgentTableRow
|
||||||
|
key={agent.id}
|
||||||
|
{...agent}
|
||||||
|
selectedAgents={selectedAgents}
|
||||||
|
setSelectedAgents={setSelectedAgents}
|
||||||
|
onEditSubmission={onEditSubmission}
|
||||||
|
onDeleteSubmission={onDeleteSubmission}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<TableRow>
|
||||||
|
<TableCell colSpan={7} className="py-4 text-center">
|
||||||
|
<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>
|
||||||
|
</TableRow>
|
||||||
|
)}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Table body */}
|
{/* Mobile view with cards */}
|
||||||
{agents.length > 0 ? (
|
<div className="md:hidden">
|
||||||
<div className="flex flex-col">
|
{agents.length > 0 ? (
|
||||||
{agents.map((agent, index) => (
|
<div className="flex flex-col">
|
||||||
<div key={agent.id} className="md:block">
|
{agents.map((agent) => (
|
||||||
<AgentTableRow
|
<AgentTableCard
|
||||||
|
key={agent.id}
|
||||||
{...agent}
|
{...agent}
|
||||||
selectedAgents={selectedAgents}
|
|
||||||
setSelectedAgents={setSelectedAgents}
|
|
||||||
onEditSubmission={onEditSubmission}
|
onEditSubmission={onEditSubmission}
|
||||||
onDeleteSubmission={onDeleteSubmission}
|
|
||||||
/>
|
/>
|
||||||
<div className="block md:hidden">
|
))}
|
||||||
<AgentTableCard
|
</div>
|
||||||
{...agent}
|
) : (
|
||||||
onEditSubmission={onEditSubmission}
|
<div className="py-4 text-center font-sans text-base text-neutral-600 dark:text-neutral-400">
|
||||||
/>
|
No agents available. Create your first agent to get started!
|
||||||
</div>
|
</div>
|
||||||
</div>
|
)}
|
||||||
))}
|
</div>
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<div className="py-4 text-center font-sans text-base text-neutral-600 dark:text-neutral-400">
|
|
||||||
No agents available. Create your first agent to get started!
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,65 +0,0 @@
|
|||||||
import type { Meta, StoryObj } from "@storybook/react";
|
|
||||||
import { AgentTableCard } from "./AgentTableCard";
|
|
||||||
import { userEvent, within, expect } from "@storybook/test";
|
|
||||||
import { type StatusType } from "./Status";
|
|
||||||
|
|
||||||
const meta: Meta<typeof AgentTableCard> = {
|
|
||||||
title: "AGPT UI/Agent Table Card",
|
|
||||||
component: AgentTableCard,
|
|
||||||
tags: ["autodocs"],
|
|
||||||
};
|
|
||||||
|
|
||||||
export default meta;
|
|
||||||
type Story = StoryObj<typeof AgentTableCard>;
|
|
||||||
|
|
||||||
export const Default: Story = {
|
|
||||||
args: {
|
|
||||||
agentName: "Super Coder",
|
|
||||||
description: "An AI agent that writes clean, efficient code",
|
|
||||||
imageSrc: [
|
|
||||||
"https://ddz4ak4pa3d19.cloudfront.net/cache/53/b2/53b2bc7d7900f0e1e60bf64ebf38032d.jpg",
|
|
||||||
],
|
|
||||||
dateSubmitted: "2023-05-15",
|
|
||||||
status: "ACTIVE" as StatusType,
|
|
||||||
runs: 1500,
|
|
||||||
rating: 4.8,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export const NoRating: Story = {
|
|
||||||
args: {
|
|
||||||
...Default.args,
|
|
||||||
rating: undefined,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export const NoRuns: Story = {
|
|
||||||
args: {
|
|
||||||
...Default.args,
|
|
||||||
runs: undefined,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export const InactiveAgent: Story = {
|
|
||||||
args: {
|
|
||||||
...Default.args,
|
|
||||||
status: "INACTIVE" as StatusType,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export const LongDescription: Story = {
|
|
||||||
args: {
|
|
||||||
...Default.args,
|
|
||||||
description:
|
|
||||||
"This is a very long description that should wrap to multiple lines. It contains detailed information about the agent and its capabilities.",
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export const InteractionTest: Story = {
|
|
||||||
...Default,
|
|
||||||
play: async ({ canvasElement }) => {
|
|
||||||
const canvas = within(canvasElement);
|
|
||||||
const moreButton = canvas.getByRole("button");
|
|
||||||
await userEvent.click(moreButton);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
@@ -50,7 +50,10 @@ export const AgentTableCard: React.FC<AgentTableCardProps> = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="border-b border-neutral-300 p-4 dark:border-neutral-700">
|
<div
|
||||||
|
className="border-b border-neutral-300 p-4 dark:border-neutral-700"
|
||||||
|
data-testid="agent-table-card"
|
||||||
|
>
|
||||||
<div className="flex gap-4">
|
<div className="flex gap-4">
|
||||||
<div className="relative h-[56px] w-[100px] overflow-hidden rounded-lg bg-[#d9d9d9] dark:bg-neutral-800">
|
<div className="relative h-[56px] w-[100px] overflow-hidden rounded-lg bg-[#d9d9d9] dark:bg-neutral-800">
|
||||||
<Image
|
<Image
|
||||||
@@ -61,10 +64,10 @@ export const AgentTableCard: React.FC<AgentTableCardProps> = ({
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
<h3 className="text-[15px] font-medium text-neutral-800 dark:text-neutral-200">
|
<h3 className="font-sans text-sm font-medium text-neutral-600">
|
||||||
{agentName}
|
{agentName}
|
||||||
</h3>
|
</h3>
|
||||||
<p className="line-clamp-2 text-sm text-neutral-600 dark:text-neutral-400">
|
<p className="font-sans text-sm font-normal text-neutral-600">
|
||||||
{description}
|
{description}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -76,16 +79,16 @@ export const AgentTableCard: React.FC<AgentTableCardProps> = ({
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="mt-4 flex flex-wrap gap-4">
|
<div className="mt-4 flex flex-wrap items-center gap-4">
|
||||||
<Status status={status} />
|
<Status status={status} />
|
||||||
<div className="text-sm text-neutral-600 dark:text-neutral-400">
|
<div className="font-sans text-sm font-normal text-neutral-600">
|
||||||
{dateSubmitted}
|
{dateSubmitted}
|
||||||
</div>
|
</div>
|
||||||
<div className="text-sm text-neutral-600 dark:text-neutral-400">
|
<div className="font-sans text-sm font-normal text-neutral-600">
|
||||||
{runs.toLocaleString()} runs
|
{runs.toLocaleString()} runs
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-1">
|
<div className="flex items-center gap-1">
|
||||||
<span className="text-sm font-medium text-neutral-800 dark:text-neutral-200">
|
<span className="font-sans text-sm font-normal text-neutral-600">
|
||||||
{rating.toFixed(1)}
|
{rating.toFixed(1)}
|
||||||
</span>
|
</span>
|
||||||
<IconStarFilled className="h-4 w-4 text-neutral-800 dark:text-neutral-200" />
|
<IconStarFilled className="h-4 w-4 text-neutral-800 dark:text-neutral-200" />
|
||||||
|
|||||||
@@ -4,9 +4,17 @@ import * as React from "react";
|
|||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import { IconStarFilled, IconMore, IconEdit } from "@/components/ui/icons";
|
import { IconStarFilled, IconMore, IconEdit } from "@/components/ui/icons";
|
||||||
import { Status, StatusType } from "./Status";
|
import { Status, StatusType } from "./Status";
|
||||||
import * as DropdownMenu from "@radix-ui/react-dropdown-menu";
|
|
||||||
import { TrashIcon } from "@radix-ui/react-icons";
|
import { TrashIcon } from "@radix-ui/react-icons";
|
||||||
import { StoreSubmissionRequest } from "@/lib/autogpt-server-api/types";
|
import { StoreSubmissionRequest } from "@/lib/autogpt-server-api/types";
|
||||||
|
import { TableCell, TableRow } from "../ui/table";
|
||||||
|
import { Checkbox } from "../ui/checkbox";
|
||||||
|
import {
|
||||||
|
DropdownMenu,
|
||||||
|
DropdownMenuContent,
|
||||||
|
DropdownMenuItem,
|
||||||
|
DropdownMenuSeparator,
|
||||||
|
DropdownMenuTrigger,
|
||||||
|
} from "../ui/dropdown-menu";
|
||||||
|
|
||||||
export interface AgentTableRowProps {
|
export interface AgentTableRowProps {
|
||||||
agent_id: string;
|
agent_id: string;
|
||||||
@@ -82,28 +90,22 @@ export const AgentTableRow: React.FC<AgentTableRowProps> = ({
|
|||||||
}, [agent_id, selectedAgents, setSelectedAgents]);
|
}, [agent_id, selectedAgents, setSelectedAgents]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="hidden items-center border-b border-neutral-300 px-4 py-4 hover:bg-neutral-50 dark:border-neutral-700 dark:hover:bg-neutral-800 md:flex">
|
<TableRow className="space-x-2.5 hover:bg-neutral-50 dark:hover:bg-neutral-800">
|
||||||
<div className="flex items-center">
|
<TableCell className="w-[40px]">
|
||||||
<div className="flex items-center">
|
<Checkbox
|
||||||
<input
|
id={checkboxId}
|
||||||
type="checkbox"
|
aria-label={`Select ${agentName}`}
|
||||||
id={checkboxId}
|
checked={selectedAgents.has(agent_id)}
|
||||||
aria-label={`Select ${agentName}`}
|
onCheckedChange={handleCheckboxChange}
|
||||||
className="mr-4 h-5 w-5 rounded border-2 border-neutral-400 dark:border-neutral-600"
|
/>
|
||||||
checked={selectedAgents.has(agent_id)}
|
<label htmlFor={checkboxId} className="sr-only">
|
||||||
onChange={handleCheckboxChange}
|
Select {agentName}
|
||||||
/>
|
</label>
|
||||||
{/* Single label instead of multiple */}
|
</TableCell>
|
||||||
<label htmlFor={checkboxId} className="sr-only">
|
|
||||||
Select {agentName}
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="grid w-full grid-cols-[minmax(400px,1fr),180px,140px,100px,100px,40px] items-center gap-4">
|
<TableCell className="max-w-md">
|
||||||
{/* Agent info column */}
|
|
||||||
<div className="flex items-center gap-4">
|
<div className="flex items-center gap-4">
|
||||||
<div className="relative h-[70px] w-[125px] overflow-hidden rounded-[10px] bg-[#d9d9d9] dark:bg-neutral-700">
|
<div className="relative aspect-video w-[125px] min-w-[125px] overflow-hidden rounded-xl bg-[#d9d9d9] dark:bg-neutral-700">
|
||||||
<Image
|
<Image
|
||||||
src={imageSrc?.[0] ?? "/nada.png"}
|
src={imageSrc?.[0] ?? "/nada.png"}
|
||||||
alt={agentName}
|
alt={agentName}
|
||||||
@@ -112,74 +114,66 @@ export const AgentTableRow: React.FC<AgentTableRowProps> = ({
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
<h3 className="text-[15px] font-medium text-neutral-800 dark:text-neutral-200">
|
<h3 className="line-clamp-2 font-sans text-sm font-medium text-neutral-800">
|
||||||
{agentName}
|
{agentName}
|
||||||
</h3>
|
</h3>
|
||||||
<p className="line-clamp-2 text-sm text-neutral-600 dark:text-neutral-400">
|
<p className="line-clamp-2 font-sans text-sm font-normal text-neutral-600">
|
||||||
{description}
|
{description}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</TableCell>
|
||||||
|
|
||||||
{/* Date column */}
|
<TableCell className="font-sans text-sm font-normal text-neutral-600">
|
||||||
<div className="pl-14 text-sm text-neutral-600 dark:text-neutral-400">
|
{dateSubmitted}
|
||||||
{dateSubmitted}
|
</TableCell>
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Status column */}
|
<TableCell>
|
||||||
<div>
|
<Status status={status} />
|
||||||
<Status status={status} />
|
</TableCell>
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Runs column */}
|
<TableCell className="text-right font-sans text-sm font-normal text-neutral-600">
|
||||||
<div className="text-right text-sm text-neutral-600 dark:text-neutral-400">
|
{runs?.toLocaleString() ?? "-"}
|
||||||
{runs?.toLocaleString() ?? "0"}
|
</TableCell>
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Reviews column */}
|
<TableCell className="text-right font-sans text-sm font-normal text-neutral-600">
|
||||||
<div className="text-right">
|
{rating ? (
|
||||||
{rating ? (
|
<div className="flex items-center justify-end gap-1">
|
||||||
<div className="flex items-center justify-end gap-1">
|
<span className="text-sm font-medium text-neutral-800 dark:text-neutral-200">
|
||||||
<span className="text-sm font-medium text-neutral-800 dark:text-neutral-200">
|
{rating.toFixed(1)}
|
||||||
{rating.toFixed(1)}
|
|
||||||
</span>
|
|
||||||
<IconStarFilled className="h-4 w-4 text-neutral-800 dark:text-neutral-200" />
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<span className="text-sm text-neutral-600 dark:text-neutral-400">
|
|
||||||
No reviews
|
|
||||||
</span>
|
</span>
|
||||||
)}
|
<IconStarFilled className="h-4 w-4 text-neutral-800 dark:text-neutral-200" />
|
||||||
</div>
|
</div>
|
||||||
|
) : (
|
||||||
|
<span className="text-sm text-neutral-600 dark:text-neutral-400">
|
||||||
|
No reviews
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</TableCell>
|
||||||
|
|
||||||
{/* Actions - Three dots menu */}
|
<TableCell className="text-right font-sans text-sm font-normal text-neutral-600">
|
||||||
<div className="flex justify-end">
|
<DropdownMenu>
|
||||||
<DropdownMenu.Root>
|
<DropdownMenuTrigger data-testid="dropdown-button">
|
||||||
<DropdownMenu.Trigger>
|
<IconMore className="h-5 w-5 text-neutral-800 dark:text-neutral-200" />
|
||||||
<button className="rounded-full p-1 hover:bg-neutral-100 dark:hover:bg-neutral-700">
|
</DropdownMenuTrigger>
|
||||||
<IconMore className="h-5 w-5 text-neutral-800 dark:text-neutral-200" />
|
<DropdownMenuContent className="z-10">
|
||||||
</button>
|
<DropdownMenuItem onClick={handleEdit}>
|
||||||
</DropdownMenu.Trigger>
|
<div className="flex items-center font-sans text-sm font-normal text-neutral-600">
|
||||||
<DropdownMenu.Content className="z-10 rounded-xl border bg-white p-1 shadow-md dark:bg-gray-800">
|
<IconEdit className="mr-2 h-5 w-5" />
|
||||||
<DropdownMenu.Item
|
<span>Edit</span>
|
||||||
onSelect={handleEdit}
|
</div>
|
||||||
className="flex cursor-pointer items-center rounded-md px-3 py-2 hover:bg-gray-100 dark:hover:bg-gray-700"
|
</DropdownMenuItem>
|
||||||
>
|
<DropdownMenuSeparator />
|
||||||
<IconEdit className="mr-2 h-5 w-5 dark:text-gray-100" />
|
<DropdownMenuItem
|
||||||
<span className="dark:text-gray-100">Edit</span>
|
onClick={handleDelete}
|
||||||
</DropdownMenu.Item>
|
className="font-sans text-sm font-normal text-red-500"
|
||||||
<DropdownMenu.Separator className="my-1 h-px bg-gray-300 dark:bg-gray-600" />
|
>
|
||||||
<DropdownMenu.Item
|
<TrashIcon className="mr-2 h-5 w-5 text-red-500" />
|
||||||
onSelect={handleDelete}
|
<span>Delete</span>
|
||||||
className="flex cursor-pointer items-center rounded-md px-3 py-2 text-red-500 hover:bg-gray-100 dark:hover:bg-gray-700"
|
</DropdownMenuItem>
|
||||||
>
|
</DropdownMenuContent>
|
||||||
<TrashIcon className="mr-2 h-5 w-5 text-red-500 dark:text-red-400" />
|
</DropdownMenu>
|
||||||
<span className="dark:text-red-400">Delete</span>
|
</TableCell>
|
||||||
</DropdownMenu.Item>
|
</TableRow>
|
||||||
</DropdownMenu.Content>
|
|
||||||
</DropdownMenu.Root>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,135 @@
|
|||||||
|
import type { Meta, StoryObj } from "@storybook/react";
|
||||||
|
import AutogptButton from "./AutogptButton";
|
||||||
|
|
||||||
|
const meta = {
|
||||||
|
title: "Agpt UI/general/AutogptButton",
|
||||||
|
component: AutogptButton,
|
||||||
|
parameters: {
|
||||||
|
layout: "centered",
|
||||||
|
},
|
||||||
|
tags: ["autodocs"],
|
||||||
|
} satisfies Meta<typeof AutogptButton>;
|
||||||
|
|
||||||
|
export default meta;
|
||||||
|
type Story = StoryObj<typeof meta>;
|
||||||
|
|
||||||
|
export const Primary: Story = {
|
||||||
|
args: {
|
||||||
|
children: "Primary Button",
|
||||||
|
variant: "default",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const PrimaryWithIcon: Story = {
|
||||||
|
args: {
|
||||||
|
icon: true,
|
||||||
|
children: "Primary Button",
|
||||||
|
variant: "default",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const PrimaryDisabled: Story = {
|
||||||
|
args: {
|
||||||
|
children: "Primary Button",
|
||||||
|
variant: "default",
|
||||||
|
isDisabled: true,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Secondary: Story = {
|
||||||
|
args: {
|
||||||
|
children: "Primary Button",
|
||||||
|
variant: "secondary",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const SecondaryWithIcon: Story = {
|
||||||
|
args: {
|
||||||
|
icon: true,
|
||||||
|
children: "Secondary Button",
|
||||||
|
variant: "secondary",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const SecondaryDisabled: Story = {
|
||||||
|
args: {
|
||||||
|
children: "Secondary Button",
|
||||||
|
variant: "secondary",
|
||||||
|
isDisabled: true,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Destructive: Story = {
|
||||||
|
args: {
|
||||||
|
children: "Destructive Button",
|
||||||
|
variant: "destructive",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const DestructiveWithIcon: Story = {
|
||||||
|
args: {
|
||||||
|
icon: true,
|
||||||
|
children: "Destructive Button",
|
||||||
|
variant: "destructive",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const DestructiveDisabled: Story = {
|
||||||
|
args: {
|
||||||
|
children: "Destructive Button",
|
||||||
|
variant: "destructive",
|
||||||
|
isDisabled: true,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Outline: Story = {
|
||||||
|
args: {
|
||||||
|
children: "Outline Button",
|
||||||
|
variant: "outline",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const OutlineWithIcon: Story = {
|
||||||
|
args: {
|
||||||
|
icon: true,
|
||||||
|
children: "Outline Button",
|
||||||
|
variant: "outline",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const OutlineDisabled: Story = {
|
||||||
|
args: {
|
||||||
|
children: "Outline Button",
|
||||||
|
variant: "outline",
|
||||||
|
isDisabled: true,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Ghost: Story = {
|
||||||
|
args: {
|
||||||
|
children: "Ghost Button",
|
||||||
|
variant: "ghost",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const GhostDisabled: Story = {
|
||||||
|
args: {
|
||||||
|
children: "Ghost Button",
|
||||||
|
variant: "ghost",
|
||||||
|
isDisabled: true,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Link: Story = {
|
||||||
|
args: {
|
||||||
|
children: "Link Button",
|
||||||
|
variant: "link",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Loading: Story = {
|
||||||
|
args: {
|
||||||
|
children: "Loading Button",
|
||||||
|
isLoading: true,
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
import { PlayIcon, Loader2Icon } from "lucide-react";
|
||||||
|
import { Button } from "../ui/button";
|
||||||
|
import { cva, type VariantProps } from "class-variance-authority";
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
|
const buttonVariants = cva("inline-flex items-center justify-center", {
|
||||||
|
variants: {
|
||||||
|
variant: {
|
||||||
|
default:
|
||||||
|
"bg-zinc-700 text-white hover:bg-zinc-800 disabled:bg-zinc-300 border-none",
|
||||||
|
secondary:
|
||||||
|
"bg-zinc-200 text-zinc-800 hover:bg-zinc-300 disabled:bg-zinc-50 disabled:text-zinc-300 border-none",
|
||||||
|
destructive:
|
||||||
|
"bg-red-500 text-white hover:bg-red-600 disabled:bg-zinc-300 disabled:text-white border-none",
|
||||||
|
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 disabled:bg-transparent border-none",
|
||||||
|
link: "bg-transparent text-zinc-800 hover:underline border-none hover:bg-transparent",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
defaultVariants: {
|
||||||
|
variant: "default",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
interface AutogptButtonProps extends VariantProps<typeof buttonVariants> {
|
||||||
|
icon?: boolean;
|
||||||
|
children: React.ReactNode;
|
||||||
|
isDisabled?: boolean;
|
||||||
|
isLoading?: boolean;
|
||||||
|
[key: string]: any; // Allow any additional props
|
||||||
|
}
|
||||||
|
|
||||||
|
const AutogptButton = ({
|
||||||
|
icon = false,
|
||||||
|
children,
|
||||||
|
variant,
|
||||||
|
isDisabled = false,
|
||||||
|
isLoading = false,
|
||||||
|
...props
|
||||||
|
}: AutogptButtonProps) => {
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
className={cn(
|
||||||
|
"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 || isLoading}
|
||||||
|
variant={variant}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{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>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AutogptButton;
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
import { Meta, StoryObj } from "@storybook/react";
|
||||||
|
import AutogptInput from "./AutogptInput";
|
||||||
|
|
||||||
|
const meta: Meta<typeof AutogptInput> = {
|
||||||
|
title: "Agpt UI/general/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");
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -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;
|
||||||
@@ -3,15 +3,18 @@ import { BecomeACreator } from "./BecomeACreator";
|
|||||||
import { userEvent, within } from "@storybook/test";
|
import { userEvent, within } from "@storybook/test";
|
||||||
|
|
||||||
const meta = {
|
const meta = {
|
||||||
title: "AGPT UI/Become A Creator",
|
title: "Agpt UI/marketing/Become A Creator",
|
||||||
component: BecomeACreator,
|
component: BecomeACreator,
|
||||||
parameters: {
|
decorators: [
|
||||||
layout: "centered",
|
(Story) => (
|
||||||
},
|
<div className="flex items-center justify-center p-4">
|
||||||
|
<Story />
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
],
|
||||||
tags: ["autodocs"],
|
tags: ["autodocs"],
|
||||||
argTypes: {
|
argTypes: {
|
||||||
title: { control: "text" },
|
title: { control: "text" },
|
||||||
description: { control: "text" },
|
|
||||||
buttonText: { control: "text" },
|
buttonText: { control: "text" },
|
||||||
onButtonClick: { action: "buttonClicked" },
|
onButtonClick: { action: "buttonClicked" },
|
||||||
},
|
},
|
||||||
@@ -22,31 +25,13 @@ type Story = StoryObj<typeof meta>;
|
|||||||
|
|
||||||
export const Default: Story = {
|
export const Default: Story = {
|
||||||
args: {
|
args: {
|
||||||
title: "Want to contribute?",
|
title: "Become a Creator",
|
||||||
description: "Join our ever-growing community of hackers and tinkerers",
|
buttonText: "Upload your agent",
|
||||||
buttonText: "Become a Creator",
|
|
||||||
onButtonClick: () => console.log("Button clicked"),
|
onButtonClick: () => console.log("Button clicked"),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const CustomText: Story = {
|
export const TestingInteractions: Story = {
|
||||||
args: {
|
|
||||||
title: "Become a Creator Today!",
|
|
||||||
description: "Share your ideas and build amazing AI agents with us",
|
|
||||||
buttonText: "Start Creating",
|
|
||||||
onButtonClick: () => console.log("Custom button clicked"),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export const LongDescription: Story = {
|
|
||||||
args: {
|
|
||||||
...Default.args,
|
|
||||||
description:
|
|
||||||
"Join our vibrant community of innovators, developers, and AI enthusiasts. Share your unique perspectives, collaborate on groundbreaking projects, and help shape the future of AI technology.",
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export const WithInteraction: Story = {
|
|
||||||
args: {
|
args: {
|
||||||
...Default.args,
|
...Default.args,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -2,16 +2,15 @@
|
|||||||
|
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { PublishAgentPopout } from "./composite/PublishAgentPopout";
|
import { PublishAgentPopout } from "./composite/PublishAgentPopout";
|
||||||
|
import AutogptButton from "./AutogptButton";
|
||||||
interface BecomeACreatorProps {
|
interface BecomeACreatorProps {
|
||||||
title?: string;
|
title?: string;
|
||||||
description?: string;
|
|
||||||
buttonText?: string;
|
buttonText?: string;
|
||||||
onButtonClick?: () => void;
|
onButtonClick?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const BecomeACreator: React.FC<BecomeACreatorProps> = ({
|
export const BecomeACreator: React.FC<BecomeACreatorProps> = ({
|
||||||
title = "Become a creator",
|
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",
|
buttonText = "Upload your agent",
|
||||||
onButtonClick,
|
onButtonClick,
|
||||||
}) => {
|
}) => {
|
||||||
@@ -20,37 +19,30 @@ export const BecomeACreator: React.FC<BecomeACreatorProps> = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="relative mx-auto h-auto min-h-[300px] w-full max-w-[1360px] md:min-h-[400px] lg:h-[459px]">
|
<div className="w-full space-y-18 sm:mb-36 md:mb-72">
|
||||||
{/* Title */}
|
{/* Title */}
|
||||||
<h2 className="mb-[77px] font-poppins text-[18px] font-semibold leading-[28px] text-neutral-800 dark:text-neutral-200">
|
<h2 className="font-poppins text-base font-medium text-zinc-500">
|
||||||
{title}
|
{title}
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
{/* Content Container */}
|
{/* Content Container */}
|
||||||
<div className="mx-auto w-full max-w-[900px] px-4 text-center md:px-6 lg:px-0">
|
<div className="flex flex-col items-center justify-center">
|
||||||
<h2 className="mb-6 text-center font-poppins text-[48px] font-semibold leading-[54px] tracking-[-0.012em] text-neutral-950 dark:text-neutral-50 md:mb-8 lg:mb-12">
|
<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
|
Build AI agents and share
|
||||||
<br />
|
<span className="text-violet-600"> your </span>
|
||||||
<span className="text-violet-600 dark:text-violet-400">
|
|
||||||
your
|
|
||||||
</span>{" "}
|
|
||||||
vision
|
vision
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
<p className="font-geist mx-auto mb-8 max-w-[90%] text-lg font-normal leading-relaxed text-neutral-700 dark:text-neutral-300 md:mb-10 md:text-xl md:leading-loose lg:mb-14 lg:text-2xl">
|
<p className="mb-12 text-center font-sans text-lg font-normal text-zinc-600">
|
||||||
{description}
|
Join a community where your AI creations can inspire, engage, <br />{" "}
|
||||||
|
and be downloaded by users around the world.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<PublishAgentPopout
|
<PublishAgentPopout
|
||||||
trigger={
|
trigger={
|
||||||
<button
|
<AutogptButton onClick={handleButtonClick}>
|
||||||
onClick={handleButtonClick}
|
{buttonText}
|
||||||
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"
|
</AutogptButton>
|
||||||
>
|
|
||||||
<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>
|
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { BreadCrumbs } from "./BreadCrumbs";
|
|||||||
import { userEvent, within } from "@storybook/test";
|
import { userEvent, within } from "@storybook/test";
|
||||||
|
|
||||||
const meta = {
|
const meta = {
|
||||||
title: "AGPT UI/BreadCrumbs",
|
title: "Agpt UI/marketing/BreadCrumbs",
|
||||||
component: BreadCrumbs,
|
component: BreadCrumbs,
|
||||||
parameters: {
|
parameters: {
|
||||||
layout: "centered",
|
layout: "centered",
|
||||||
@@ -48,7 +48,23 @@ export const LongPath: Story = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const WithInteraction: Story = {
|
export const LongNames: Story = {
|
||||||
|
args: {
|
||||||
|
items: [
|
||||||
|
{ name: "Home", link: "/" },
|
||||||
|
{
|
||||||
|
name: "AI-Powered Writing Assistants, AI-Powered Writing Assistants ",
|
||||||
|
link: "/ai-writing-assistants",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Advanced Grammar and Style Checker",
|
||||||
|
link: "/ai-writing-assistants/grammar-style-checker",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const TestingInteractions: Story = {
|
||||||
args: {
|
args: {
|
||||||
items: [
|
items: [
|
||||||
{ name: "Home", link: "/" },
|
{ name: "Home", link: "/" },
|
||||||
@@ -64,16 +80,3 @@ export const WithInteraction: Story = {
|
|||||||
await userEvent.click(homeLink);
|
await userEvent.click(homeLink);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const LongNames: Story = {
|
|
||||||
args: {
|
|
||||||
items: [
|
|
||||||
{ name: "Home", link: "/" },
|
|
||||||
{ name: "AI-Powered Writing Assistants", link: "/ai-writing-assistants" },
|
|
||||||
{
|
|
||||||
name: "Advanced Grammar and Style Checker",
|
|
||||||
link: "/ai-writing-assistants/grammar-style-checker",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -13,31 +13,23 @@ interface BreadCrumbsProps {
|
|||||||
|
|
||||||
export const BreadCrumbs: React.FC<BreadCrumbsProps> = ({ items }) => {
|
export const BreadCrumbs: React.FC<BreadCrumbsProps> = ({ items }) => {
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center gap-4">
|
<div className="flex h-auto flex-wrap items-center justify-start gap-2.5 bg-transparent">
|
||||||
{/*
|
{items.map((item, index) => (
|
||||||
Commented out for now, but keeping until we have approval to remove
|
<React.Fragment key={index}>
|
||||||
<button className="flex h-12 w-12 items-center justify-center rounded-full border border-neutral-200 transition-colors hover:bg-neutral-50 dark:border-neutral-700 dark:hover:bg-neutral-800">
|
<Link href={item.link}>
|
||||||
<IconLeftArrow className="h-5 w-5 text-neutral-900 dark:text-neutral-100" />
|
<span className="font-sans text-base font-medium text-zinc-800 transition-colors duration-200 hover:text-zinc-400 dark:text-neutral-100 dark:hover:text-gray-500">
|
||||||
</button>
|
{item.name.length > 50
|
||||||
<button className="flex h-12 w-12 items-center justify-center rounded-full border border-neutral-200 transition-colors hover:bg-neutral-50 dark:border-neutral-700 dark:hover:bg-neutral-800">
|
? `${item.name.slice(0, 50)}...`
|
||||||
<IconRightArrow className="h-5 w-5 text-neutral-900 dark:text-neutral-100" />
|
: item.name}
|
||||||
</button> */}
|
</span>
|
||||||
<div className="flex h-auto flex-wrap items-center justify-start gap-4 rounded-[5rem] dark:bg-transparent">
|
</Link>
|
||||||
{items.map((item, index) => (
|
{index < items.length - 1 && (
|
||||||
<React.Fragment key={index}>
|
<span className="font-sans text-base font-medium text-zinc-800 dark:text-zinc-100">
|
||||||
<Link href={item.link}>
|
/
|
||||||
<span className="rounded py-1 pr-2 font-neue text-xl font-medium leading-9 tracking-tight text-[#272727] transition-colors duration-200 hover:text-gray-400 dark:text-neutral-100 dark:hover:text-gray-500">
|
</span>
|
||||||
{item.name}
|
)}
|
||||||
</span>
|
</React.Fragment>
|
||||||
</Link>
|
))}
|
||||||
{index < items.length - 1 && (
|
|
||||||
<span className="text-center text-2xl font-normal text-black dark:text-neutral-100">
|
|
||||||
/
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</React.Fragment>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,9 +1,14 @@
|
|||||||
|
/**
|
||||||
|
* @deprecated This Button component will be deprecated in the future.
|
||||||
|
* Please use Shadcn Button or the custom AutogptButton component instead.
|
||||||
|
*/
|
||||||
|
|
||||||
import type { Meta, StoryObj } from "@storybook/react";
|
import type { Meta, StoryObj } from "@storybook/react";
|
||||||
import { Button } from "./Button";
|
import { Button } from "./Button";
|
||||||
import { userEvent, within, expect } from "@storybook/test";
|
import { userEvent, within, expect } from "@storybook/test";
|
||||||
|
|
||||||
const meta = {
|
const meta = {
|
||||||
title: "AGPT UI/Button",
|
title: "Agpt UI/Deprecated/general/Button",
|
||||||
component: Button,
|
component: Button,
|
||||||
parameters: {
|
parameters: {
|
||||||
layout: "centered",
|
layout: "centered",
|
||||||
@@ -85,7 +90,7 @@ export const Variants: Story = {
|
|||||||
play: async ({ canvasElement }) => {
|
play: async ({ canvasElement }) => {
|
||||||
const canvas = within(canvasElement);
|
const canvas = within(canvasElement);
|
||||||
const buttons = canvas.getAllByRole("button");
|
const buttons = canvas.getAllByRole("button");
|
||||||
await expect(buttons).toHaveLength(6);
|
await expect(buttons).toHaveLength(5);
|
||||||
for (const button of buttons) {
|
for (const button of buttons) {
|
||||||
await userEvent.hover(button);
|
await userEvent.hover(button);
|
||||||
await expect(button).toHaveAttribute(
|
await expect(button).toHaveAttribute(
|
||||||
@@ -115,24 +120,6 @@ export const Sizes: Story = {
|
|||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
play: async ({ canvasElement }) => {
|
|
||||||
const canvas = within(canvasElement);
|
|
||||||
const buttons = canvas.getAllByRole("button");
|
|
||||||
await expect(buttons).toHaveLength(5);
|
|
||||||
const sizeClasses = [
|
|
||||||
"h-8 px-3 py-1.5 text-xs",
|
|
||||||
"h-10 px-4 py-2 text-sm",
|
|
||||||
"h-12 px-5 py-2.5 text-lg",
|
|
||||||
"h-10 w-28",
|
|
||||||
"h-10 w-10",
|
|
||||||
];
|
|
||||||
for (let i = 0; i < buttons.length; i++) {
|
|
||||||
await expect(buttons[i]).toHaveAttribute(
|
|
||||||
"class",
|
|
||||||
expect.stringContaining(sizeClasses[i]),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Disabled: Story = {
|
export const Disabled: Story = {
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ const buttonVariants = cva(
|
|||||||
sm: "h-8 px-3 py-1.5 rounded-full text-xs",
|
sm: "h-8 px-3 py-1.5 rounded-full text-xs",
|
||||||
lg: "h-12 px-5 py-2.5 rounded-full text-lg",
|
lg: "h-12 px-5 py-2.5 rounded-full text-lg",
|
||||||
primary:
|
primary:
|
||||||
"h-10 w-28 rounded-full sm:h-12 sm:w-32 md:h-[4.375rem] md:w-[11rem] lg:h-[3.125rem] lg:w-[7rem]",
|
"h-10 w-28 rounded-full sm:h-12 sm:w-32 md:h-[4.375rem] md:w-[11rem] lg:h-[3.125rem] lg:w-[7rem] flex items-center justify-center text-xl",
|
||||||
icon: "h-10 w-10 justify-center",
|
icon: "h-10 w-10 justify-center",
|
||||||
card: "h-12 p-5 agpt-rounded-card justify-center text-lg",
|
card: "h-12 p-5 agpt-rounded-card justify-center text-lg",
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import type { Meta, StoryObj } from "@storybook/react";
|
||||||
|
import { Chip } from "./Chip";
|
||||||
|
|
||||||
|
const meta: Meta<typeof Chip> = {
|
||||||
|
component: Chip,
|
||||||
|
title: "Agpt UI/general/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",
|
||||||
|
},
|
||||||
|
};
|
||||||
22
autogpt_platform/frontend/src/components/agptui/Chip.tsx
Normal file
22
autogpt_platform/frontend/src/components/agptui/Chip.tsx
Normal 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 text-center font-sans text-sm font-normal text-zinc-600 shadow-none hover:border-zinc-500 hover:bg-zinc-100 md:text-base",
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</Badge>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -3,11 +3,15 @@ import { CreatorCard } from "./CreatorCard";
|
|||||||
import { userEvent, within } from "@storybook/test";
|
import { userEvent, within } from "@storybook/test";
|
||||||
|
|
||||||
const meta = {
|
const meta = {
|
||||||
title: "AGPT UI/Creator Card",
|
title: "Agpt UI/marketing/Creator Card",
|
||||||
component: CreatorCard,
|
component: CreatorCard,
|
||||||
parameters: {
|
decorators: [
|
||||||
layout: "centered",
|
(Story) => (
|
||||||
},
|
<div className="flex items-center justify-center p-4">
|
||||||
|
<Story />
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
],
|
||||||
tags: ["autodocs"],
|
tags: ["autodocs"],
|
||||||
argTypes: {
|
argTypes: {
|
||||||
creatorName: { control: "text" },
|
creatorName: { control: "text" },
|
||||||
@@ -15,63 +19,65 @@ const meta = {
|
|||||||
bio: { control: "text" },
|
bio: { control: "text" },
|
||||||
agentsUploaded: { control: "number" },
|
agentsUploaded: { control: "number" },
|
||||||
onClick: { action: "clicked" },
|
onClick: { action: "clicked" },
|
||||||
|
key: { control: "number" },
|
||||||
},
|
},
|
||||||
} satisfies Meta<typeof CreatorCard>;
|
} satisfies Meta<typeof CreatorCard>;
|
||||||
|
|
||||||
export default meta;
|
export default meta;
|
||||||
type Story = StoryObj<typeof meta>;
|
type Story = StoryObj<typeof meta>;
|
||||||
|
|
||||||
|
const defaultAvatarImage = "testing_avatar.png";
|
||||||
export const Default: Story = {
|
export const Default: Story = {
|
||||||
args: {
|
args: {
|
||||||
index: 0,
|
key: 0,
|
||||||
creatorName: "John Doe",
|
creatorName: "John Doe",
|
||||||
creatorImage:
|
creatorImage: defaultAvatarImage,
|
||||||
"https://framerusercontent.com/images/KCIpxr9f97EGJgpaoqnjKsrOPwI.jpg",
|
|
||||||
bio: "AI enthusiast and developer with a passion for creating innovative agents.",
|
bio: "AI enthusiast and developer with a passion for creating innovative agents.",
|
||||||
agentsUploaded: 15,
|
agentsUploaded: 15,
|
||||||
onClick: () => console.log("Default CreatorCard clicked"),
|
onClick: () => console.log("Default CreatorCard clicked"),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const NewCreator: Story = {
|
export const NoImage: Story = {
|
||||||
args: {
|
args: {
|
||||||
index: 1,
|
key: 0,
|
||||||
creatorName: "Jane Smith",
|
creatorName: "John Doe",
|
||||||
creatorImage:
|
creatorImage: "",
|
||||||
"https://framerusercontent.com/images/KCIpxr9f97EGJgpaoqnjKsrOPwI.jpg",
|
bio: "AI enthusiast and developer with a passion for creating innovative agents.",
|
||||||
bio: "Excited to start my journey in AI agent development!",
|
agentsUploaded: 15,
|
||||||
agentsUploaded: 1,
|
onClick: () => console.log("NoImage CreatorCard clicked"),
|
||||||
onClick: () => console.log("NewCreator CreatorCard clicked"),
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ExperiencedCreator: Story = {
|
export const LongContent: Story = {
|
||||||
args: {
|
args: {
|
||||||
index: 2,
|
key: 1,
|
||||||
creatorName: "Alex Johnson",
|
creatorName: "Alexandria Rodriguez-Fitzgerald Johnson III",
|
||||||
creatorImage:
|
creatorImage: defaultAvatarImage,
|
||||||
"https://framerusercontent.com/images/KCIpxr9f97EGJgpaoqnjKsrOPwI.jpg",
|
bio: "Excited to start my journey in AI agent development! I have a background in computer science and machine learning, with a special interest in creating agents that can assist with everyday tasks and solve complex problems efficiently.",
|
||||||
bio: "Veteran AI researcher with a focus on natural language processing and machine learning.",
|
agentsUploaded: 500000,
|
||||||
agentsUploaded: 50,
|
onClick: () => console.log("LongName CreatorCard clicked"),
|
||||||
onClick: () => console.log("ExperiencedCreator CreatorCard clicked"),
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const WithInteraction: Story = {
|
export const TestingInteractions: Story = {
|
||||||
args: {
|
args: {
|
||||||
index: 3,
|
key: 3,
|
||||||
creatorName: "Sam Brown",
|
creatorName: "Sam Brown",
|
||||||
creatorImage:
|
creatorImage: defaultAvatarImage,
|
||||||
"https://framerusercontent.com/images/KCIpxr9f97EGJgpaoqnjKsrOPwI.jpg",
|
|
||||||
bio: "Exploring the frontiers of AI and its applications in everyday life.",
|
bio: "Exploring the frontiers of AI and its applications in everyday life.",
|
||||||
agentsUploaded: 30,
|
agentsUploaded: 30,
|
||||||
onClick: () => console.log("WithInteraction CreatorCard clicked"),
|
onClick: () => console.log("WithInteraction CreatorCard clicked"),
|
||||||
},
|
},
|
||||||
play: async ({ canvasElement }) => {
|
play: async ({ canvasElement }) => {
|
||||||
const canvas = within(canvasElement);
|
const canvas = within(canvasElement);
|
||||||
const creatorCard = canvas.getByText("Sam Brown");
|
const creatorCard = canvas.getByTestId("creator-card");
|
||||||
|
|
||||||
|
// Test hover state
|
||||||
await userEvent.hover(creatorCard);
|
await userEvent.hover(creatorCard);
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
||||||
|
|
||||||
|
// Test click interaction
|
||||||
await userEvent.click(creatorCard);
|
await userEvent.click(creatorCard);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,12 +1,6 @@
|
|||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
|
import { Avatar, AvatarFallback, AvatarImage } from "../ui/avatar";
|
||||||
const BACKGROUND_COLORS = [
|
|
||||||
"bg-amber-100 dark:bg-amber-800", // #fef3c7 / #92400e
|
|
||||||
"bg-violet-100 dark:bg-violet-800", // #ede9fe / #5b21b6
|
|
||||||
"bg-green-100 dark:bg-green-800", // #dcfce7 / #065f46
|
|
||||||
"bg-blue-100 dark:bg-blue-800", // #dbeafe / #1e3a8a
|
|
||||||
];
|
|
||||||
|
|
||||||
interface CreatorCardProps {
|
interface CreatorCardProps {
|
||||||
creatorName: string;
|
creatorName: string;
|
||||||
@@ -14,7 +8,7 @@ interface CreatorCardProps {
|
|||||||
bio: string;
|
bio: string;
|
||||||
agentsUploaded: number;
|
agentsUploaded: number;
|
||||||
onClick: () => void;
|
onClick: () => void;
|
||||||
index: number;
|
key: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const CreatorCard: React.FC<CreatorCardProps> = ({
|
export const CreatorCard: React.FC<CreatorCardProps> = ({
|
||||||
@@ -23,43 +17,38 @@ export const CreatorCard: React.FC<CreatorCardProps> = ({
|
|||||||
bio,
|
bio,
|
||||||
agentsUploaded,
|
agentsUploaded,
|
||||||
onClick,
|
onClick,
|
||||||
index,
|
key,
|
||||||
}) => {
|
}) => {
|
||||||
const backgroundColor = BACKGROUND_COLORS[index % BACKGROUND_COLORS.length];
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={`h-[264px] w-full px-[18px] pb-5 pt-6 ${backgroundColor} inline-flex cursor-pointer flex-col items-start justify-start gap-3.5 rounded-[26px] transition-all duration-200 hover:brightness-95`}
|
key={key}
|
||||||
|
className={`aspect-square w-full space-y-4 rounded-3xl bg-amber-100 p-5 pt-6 hover:cursor-pointer hover:bg-amber-200 sm:w-80`}
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
data-testid="creator-card"
|
data-testid="creator-card"
|
||||||
>
|
>
|
||||||
<div className="relative h-[64px] w-[64px]">
|
<Avatar className="h-[84px] w-[84px]">
|
||||||
<div className="absolute inset-0 overflow-hidden rounded-full">
|
<AvatarImage
|
||||||
{creatorImage ? (
|
width={84}
|
||||||
<Image
|
height={84}
|
||||||
src={creatorImage}
|
src={creatorImage}
|
||||||
alt={creatorName}
|
alt={`${creatorName}`}
|
||||||
width={64}
|
/>
|
||||||
height={64}
|
<AvatarFallback size={84} className="h-[84px] w-[84px]">
|
||||||
className="h-full w-full object-cover"
|
{creatorName.charAt(0)}
|
||||||
priority
|
</AvatarFallback>
|
||||||
/>
|
</Avatar>
|
||||||
) : (
|
|
||||||
<div className="h-full w-full bg-neutral-300 dark:bg-neutral-600" />
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex flex-col gap-2">
|
<div className="flex h-36 flex-col gap-2">
|
||||||
<h3 className="font-poppins text-2xl font-semibold leading-tight text-neutral-900 dark:text-neutral-100">
|
<h3 className="line-clamp-1 font-poppins text-3xl font-medium text-zinc-800 dark:text-neutral-100">
|
||||||
{creatorName}
|
{creatorName}
|
||||||
</h3>
|
</h3>
|
||||||
<p className="font-geist text-sm font-normal leading-normal text-neutral-600 dark:text-neutral-400">
|
<p className="line-clamp-3 font-sans text-base font-normal text-zinc-600 dark:text-neutral-400">
|
||||||
{bio}
|
{bio}
|
||||||
</p>
|
</p>
|
||||||
<div className="font-geist text-lg font-semibold leading-7 text-neutral-800 dark:text-neutral-200">
|
</div>
|
||||||
{agentsUploaded} agents
|
|
||||||
</div>
|
<div className="font-sans text-sm font-medium text-zinc-800 dark:text-neutral-200">
|
||||||
|
{agentsUploaded} agents
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -2,11 +2,15 @@ import type { Meta, StoryObj } from "@storybook/react";
|
|||||||
import { CreatorInfoCard } from "./CreatorInfoCard";
|
import { CreatorInfoCard } from "./CreatorInfoCard";
|
||||||
|
|
||||||
const meta = {
|
const meta = {
|
||||||
title: "AGPT UI/Creator Info Card",
|
title: "Agpt UI/marketing/Creator Info Card",
|
||||||
component: CreatorInfoCard,
|
component: CreatorInfoCard,
|
||||||
parameters: {
|
decorators: [
|
||||||
layout: "centered",
|
(Story) => (
|
||||||
},
|
<div className="flex items-center justify-center p-4">
|
||||||
|
<Story />
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
],
|
||||||
tags: ["autodocs"],
|
tags: ["autodocs"],
|
||||||
argTypes: {
|
argTypes: {
|
||||||
username: { control: "text" },
|
username: { control: "text" },
|
||||||
@@ -32,24 +36,24 @@ export const Default: Story = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const NewCreator: Story = {
|
export const LongContent: Story = {
|
||||||
args: {
|
args: {
|
||||||
username: "AI Enthusiast",
|
username: "This Is An Extremel Long Username To Test",
|
||||||
handle: "ai_newbie",
|
handle: "this_is_an_extremely_long_there_what",
|
||||||
avatarSrc: "https://example.com/avatar2.jpg",
|
avatarSrc: "https://example.com/avatar2.jpg",
|
||||||
categories: ["AI", "Technology"],
|
categories: [
|
||||||
averageRating: 0,
|
"Artificial Intelligence",
|
||||||
totalRuns: 0,
|
"Machine Learning",
|
||||||
},
|
"Neural Networks",
|
||||||
};
|
"Deep Learning",
|
||||||
|
"Natural Language Processing",
|
||||||
export const ExperiencedCreator: Story = {
|
"Computer Vision",
|
||||||
args: {
|
"Robotics",
|
||||||
username: "Tech Master",
|
"Data Science",
|
||||||
handle: "techmaster",
|
"Cloud Computing",
|
||||||
avatarSrc: "https://example.com/avatar3.jpg",
|
"Internet of Things",
|
||||||
categories: ["AI", "Development", "Education"],
|
],
|
||||||
averageRating: 4.9,
|
averageRating: 4.8888888888,
|
||||||
totalRuns: 50000,
|
totalRuns: 1000000000,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
||||||
import { StarRatingIcons } from "@/components/ui/icons";
|
import { StarRatingIcons } from "@/components/ui/icons";
|
||||||
|
import { Separator } from "../ui/separator";
|
||||||
|
import { Chip } from "./Chip";
|
||||||
|
|
||||||
interface CreatorInfoCardProps {
|
interface CreatorInfoCardProps {
|
||||||
username: string;
|
username: string;
|
||||||
@@ -21,90 +23,86 @@ export const CreatorInfoCard: React.FC<CreatorInfoCardProps> = ({
|
|||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className="inline-flex h-auto min-h-[500px] w-full max-w-[440px] flex-col items-start justify-between rounded-[26px] bg-violet-100 p-4 dark:bg-violet-900 sm:h-[632px] sm:w-[440px] sm:p-6"
|
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-[27.5rem]"
|
||||||
role="article"
|
role="article"
|
||||||
aria-label={`Creator profile for ${username}`}
|
aria-label={`Creator profile for ${username}`}
|
||||||
>
|
>
|
||||||
<div className="flex w-full flex-col items-start justify-start gap-3.5 sm:h-[218px]">
|
{/* Avatar + Basic Info */}
|
||||||
<Avatar className="h-[100px] w-[100px] sm:h-[130px] sm:w-[130px]">
|
<div className="flex w-full flex-col items-start justify-start gap-3.5">
|
||||||
|
<Avatar className="h-[100px] w-[100px]">
|
||||||
<AvatarImage
|
<AvatarImage
|
||||||
width={130}
|
width={100}
|
||||||
height={130}
|
height={100}
|
||||||
src={avatarSrc}
|
src={avatarSrc}
|
||||||
alt={`${username}'s avatar`}
|
alt={`${username}'s avatar`}
|
||||||
/>
|
/>
|
||||||
<AvatarFallback
|
<AvatarFallback size={100} className="h-[100px] w-[100px]">
|
||||||
size={130}
|
|
||||||
className="h-[100px] w-[100px] sm:h-[130px] sm:w-[130px]"
|
|
||||||
>
|
|
||||||
{username.charAt(0)}
|
{username.charAt(0)}
|
||||||
</AvatarFallback>
|
</AvatarFallback>
|
||||||
</Avatar>
|
</Avatar>
|
||||||
<div className="flex w-full flex-col items-start justify-start gap-1.5">
|
<div className="flex w-full flex-col items-start justify-start gap-1.5">
|
||||||
<div className="w-full font-poppins text-[35px] font-medium leading-10 text-neutral-900 dark:text-neutral-100 sm:text-[35px] sm:leading-10">
|
<div className="w-full font-poppins text-[1.75rem] font-medium leading-[2.5rem] text-zinc-800 dark:text-zinc-100">
|
||||||
{username}
|
{username}
|
||||||
</div>
|
</div>
|
||||||
<div className="font-geist w-full text-lg font-normal leading-6 text-neutral-800 dark:text-neutral-200 sm:text-xl sm:leading-7">
|
<div className="w-full font-sans text-base font-normal text-zinc-800 dark:text-zinc-200">
|
||||||
@{handle}
|
@{handle}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="my-4 flex w-full flex-col items-start justify-start gap-6 sm:gap-[50px]">
|
<Separator className="bg-zinc-300" />
|
||||||
<div className="flex w-full flex-col items-start justify-start gap-3">
|
|
||||||
<div className="h-px w-full bg-neutral-700 dark:bg-neutral-300" />
|
<div className="flex flex-col items-start justify-start gap-4">
|
||||||
<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">
|
||||||
<div className="w-full font-neue text-base font-medium leading-normal text-neutral-800 dark:text-neutral-200">
|
Top categories
|
||||||
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 hover:border-zinc-400 hover: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-4">
|
||||||
|
<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 justify-center gap-2">
|
||||||
|
<div className="font-sans text-sm font-medium text-zinc-800 dark:text-zinc-200">
|
||||||
|
{averageRating.toFixed(1)}
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
className="flex flex-wrap items-center gap-2.5"
|
className="flex items-center gap-px"
|
||||||
role="list"
|
role="img"
|
||||||
aria-label="Categories"
|
aria-label={`Rating: ${averageRating} out of 5 stars`}
|
||||||
>
|
>
|
||||||
{categories.map((category, index) => (
|
{StarRatingIcons(averageRating)}
|
||||||
<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-neue text-base font-normal leading-normal text-neutral-800 dark:text-neutral-200">
|
|
||||||
{category}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{/* Number of runs */}
|
||||||
<div className="flex w-full flex-col items-start justify-start gap-3">
|
<div className="flex w-full flex-col items-start justify-start gap-4">
|
||||||
<div className="h-px w-full bg-neutral-700 dark:bg-neutral-300" />
|
<div className="w-full font-sans text-sm font-medium leading-normal text-zinc-800 dark:text-zinc-200 sm:text-base">
|
||||||
<div className="flex w-full flex-col items-start justify-between gap-4 sm:flex-row sm:gap-0">
|
Number of runs
|
||||||
<div className="flex w-full flex-col items-start justify-start gap-2.5 sm:w-[164px]">
|
</div>
|
||||||
<div className="w-full font-neue text-base font-medium leading-normal text-neutral-800 dark:text-neutral-200">
|
<div className="font-sans text-sm font-medium text-zinc-800 dark:text-zinc-200 sm:text-base">
|
||||||
Average rating
|
{new Intl.NumberFormat().format(totalRuns)} runs
|
||||||
</div>
|
|
||||||
<div className="inline-flex items-center gap-2">
|
|
||||||
<div className="font-geist text-[18px] font-semibold leading-[28px] text-neutral-800 dark:text-neutral-200">
|
|
||||||
{averageRating.toFixed(1)}
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
className="flex items-center gap-px"
|
|
||||||
role="img"
|
|
||||||
aria-label={`Rating: ${averageRating} out of 5 stars`}
|
|
||||||
>
|
|
||||||
{StarRatingIcons(averageRating)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="flex w-full flex-col items-start justify-start gap-2.5 sm:w-[164px]">
|
|
||||||
<div className="w-full font-neue text-base font-medium leading-normal text-neutral-800 dark:text-neutral-200">
|
|
||||||
Number of runs
|
|
||||||
</div>
|
|
||||||
<div className="font-geist text-[18px] font-semibold leading-[28px] text-neutral-800 dark:text-neutral-200">
|
|
||||||
{new Intl.NumberFormat().format(totalRuns)} runs
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
import type { Meta, StoryObj } from "@storybook/react";
|
||||||
|
import CreatorLink from "./CreatorLink";
|
||||||
|
|
||||||
|
const meta: Meta<typeof CreatorLink> = {
|
||||||
|
title: "Agpt UI/marketing/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",
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -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;
|
||||||
@@ -2,11 +2,15 @@ import type { Meta, StoryObj } from "@storybook/react";
|
|||||||
import { CreatorLinks } from "./CreatorLinks";
|
import { CreatorLinks } from "./CreatorLinks";
|
||||||
|
|
||||||
const meta = {
|
const meta = {
|
||||||
title: "AGPT UI/Creator Links",
|
title: "Agpt UI/marketing/Creator Links",
|
||||||
component: CreatorLinks,
|
component: CreatorLinks,
|
||||||
parameters: {
|
decorators: [
|
||||||
layout: "centered",
|
(Story) => (
|
||||||
},
|
<div className="flex h-screen w-full items-center justify-center p-4">
|
||||||
|
<Story />
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
],
|
||||||
tags: ["autodocs"],
|
tags: ["autodocs"],
|
||||||
argTypes: {
|
argTypes: {
|
||||||
links: {
|
links: {
|
||||||
@@ -31,22 +35,6 @@ export const Default: Story = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const WebsiteOnly: Story = {
|
|
||||||
args: {
|
|
||||||
links: ["https://example.com"],
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export const SocialLinks: Story = {
|
|
||||||
args: {
|
|
||||||
links: [
|
|
||||||
"https://linkedin.com/in/janedoe",
|
|
||||||
"https://github.com/janedoe",
|
|
||||||
"https://twitter.com/janedoe",
|
|
||||||
],
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export const NoLinks: Story = {
|
export const NoLinks: Story = {
|
||||||
args: {
|
args: {
|
||||||
links: [],
|
links: [],
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { getIconForSocial } from "@/components/ui/icons";
|
import CreatorLink from "./CreatorLink";
|
||||||
|
|
||||||
interface CreatorLinksProps {
|
interface CreatorLinksProps {
|
||||||
links: string[];
|
links: string[];
|
||||||
@@ -10,32 +10,16 @@ export const CreatorLinks: React.FC<CreatorLinksProps> = ({ links }) => {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const renderLinkButton = (url: string) => (
|
|
||||||
<a
|
|
||||||
href={url}
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
className="flex min-w-[200px] flex-1 items-center justify-between rounded-[34px] border border-neutral-600 px-5 py-3 dark:border-neutral-400"
|
|
||||||
>
|
|
||||||
<div className="font-neue text-base font-medium leading-normal text-neutral-800 dark:text-neutral-200">
|
|
||||||
{new URL(url).hostname.replace("www.", "")}
|
|
||||||
</div>
|
|
||||||
<div className="relative h-6 w-6">
|
|
||||||
{getIconForSocial(url, {
|
|
||||||
className: "h-6 w-6 text-neutral-800 dark:text-neutral-200",
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col items-start justify-start gap-4">
|
<div className="space-y-4">
|
||||||
<div className="font-neue text-base font-medium leading-normal text-neutral-800 dark:text-neutral-200">
|
<div className="font-sans text-base font-medium text-zinc-800">
|
||||||
Other links
|
Other links
|
||||||
</div>
|
</div>
|
||||||
<div className="flex w-full flex-wrap gap-3">
|
<div className="grid w-full grid-cols-1 gap-3 sm:grid-cols-2">
|
||||||
{links.map((link, index) => (
|
{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>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import {
|
|||||||
} from "@/components/ui/card";
|
} from "@/components/ui/card";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { StoreAgent } from "@/lib/autogpt-server-api";
|
import { StoreAgent } from "@/lib/autogpt-server-api";
|
||||||
|
import { Avatar, AvatarFallback, AvatarImage } from "../ui/avatar";
|
||||||
|
|
||||||
interface FeaturedStoreCardProps {
|
interface FeaturedStoreCardProps {
|
||||||
agent: StoreAgent;
|
agent: StoreAgent;
|
||||||
@@ -27,20 +28,21 @@ export const FeaturedAgentCard: React.FC<FeaturedStoreCardProps> = ({
|
|||||||
data-testid="featured-store-card"
|
data-testid="featured-store-card"
|
||||||
onMouseEnter={() => setIsHovered(true)}
|
onMouseEnter={() => setIsHovered(true)}
|
||||||
onMouseLeave={() => setIsHovered(false)}
|
onMouseLeave={() => setIsHovered(false)}
|
||||||
className={`flex h-full flex-col ${backgroundColor} rounded-[1.5rem] border-none`}
|
className={`flex h-[30rem] w-full min-w-[94vw] max-w-[27.5rem] flex-col hover:cursor-pointer md:w-[24rem] md:min-w-0 lg:w-[27.5rem] ${backgroundColor} rounded-[1.5rem] border-none px-5 pb-5 pt-6 transition-colors duration-200`}
|
||||||
>
|
>
|
||||||
<CardHeader>
|
<CardHeader className="mb-7 h-[9.5rem] space-y-3 p-0">
|
||||||
<CardTitle className="line-clamp-2 text-base sm:text-xl">
|
<CardTitle className="line-clamp-3 font-poppins text-3xl font-medium text-zinc-800">
|
||||||
{agent.agent_name}
|
{agent.agent_name}
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
<CardDescription className="text-sm">
|
<CardDescription className="line-clamp-1 font-sans text-base font-normal text-zinc-800">
|
||||||
By {agent.creator}
|
By {agent.creator}
|
||||||
</CardDescription>
|
</CardDescription>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="flex-1 p-4">
|
|
||||||
<div className="relative aspect-[4/3] w-full overflow-hidden rounded-xl">
|
<CardContent className="mb-4 flex flex-1 flex-col gap-4 p-0">
|
||||||
|
<div className="relative flex-1 overflow-hidden rounded-xl">
|
||||||
<Image
|
<Image
|
||||||
src={agent.agent_image || "/AUTOgpt_Logo_dark.png"}
|
src={agent.agent_image}
|
||||||
alt={`${agent.agent_name} preview`}
|
alt={`${agent.agent_name} preview`}
|
||||||
fill
|
fill
|
||||||
sizes="100%"
|
sizes="100%"
|
||||||
@@ -48,22 +50,41 @@ export const FeaturedAgentCard: React.FC<FeaturedStoreCardProps> = ({
|
|||||||
isHovered ? "opacity-0" : "opacity-100"
|
isHovered ? "opacity-0" : "opacity-100"
|
||||||
}`}
|
}`}
|
||||||
/>
|
/>
|
||||||
|
<Avatar
|
||||||
|
className={`absolute bottom-3 left-3 aspect-square h-[50px] w-[50px] rounded-full border border-zinc-200 transition-opacity duration-200 ${
|
||||||
|
isHovered ? "opacity-0" : "opacity-100"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<AvatarImage
|
||||||
|
width={50}
|
||||||
|
height={50}
|
||||||
|
src={agent.creator_avatar}
|
||||||
|
alt={`${agent.creator_avatar} avatar`}
|
||||||
|
/>
|
||||||
|
<AvatarFallback size={50}>
|
||||||
|
{agent.creator_avatar.charAt(0)}
|
||||||
|
</AvatarFallback>
|
||||||
|
</Avatar>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
className={`absolute inset-0 overflow-y-auto p-4 transition-opacity duration-200 ${
|
className={`absolute inset-0 overflow-hidden p-0 transition-opacity duration-200 ${
|
||||||
isHovered ? "opacity-100" : "opacity-0"
|
isHovered ? "opacity-100" : "opacity-0"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<CardDescription className="line-clamp-[6] text-xs sm:line-clamp-[8] sm:text-sm">
|
<CardDescription
|
||||||
|
data-testid="agent-description"
|
||||||
|
className="line-clamp-6 font-sans text-sm text-zinc-600"
|
||||||
|
>
|
||||||
{agent.description}
|
{agent.description}
|
||||||
</CardDescription>
|
</CardDescription>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
<CardFooter className="flex items-center justify-between">
|
<CardFooter className="flex min-h-7 flex-col items-start justify-between p-0 sm:flex-row sm:items-center">
|
||||||
<div className="font-semibold">
|
<div className="font-sans text-base font-medium text-zinc-800">
|
||||||
{agent.runs?.toLocaleString() ?? "0"} runs
|
{agent.runs?.toLocaleString() ?? "0"} runs
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-1.5">
|
<div className="flex items-center gap-1.5 font-sans text-base font-medium text-zinc-800">
|
||||||
<p>{agent.rating.toFixed(1) ?? "0.0"}</p>
|
<p>{agent.rating.toFixed(1) ?? "0.0"}</p>
|
||||||
{StarRatingIcons(agent.rating)}
|
{StarRatingIcons(agent.rating)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import type { Meta, StoryObj } from "@storybook/react";
|
import type { Meta, StoryObj } from "@storybook/react";
|
||||||
import { FeaturedAgentCard } from "./FeaturedAgentCard";
|
import { FeaturedAgentCard } from "./FeaturedAgentCard";
|
||||||
import { userEvent, within } from "@storybook/test";
|
import { userEvent, within, expect } from "@storybook/test";
|
||||||
|
|
||||||
const meta = {
|
const meta = {
|
||||||
title: "AGPT UI/Featured Store Card",
|
title: "Agpt UI/marketing/Featured Store Card",
|
||||||
component: FeaturedAgentCard,
|
component: FeaturedAgentCard,
|
||||||
parameters: {
|
parameters: {
|
||||||
layout: {
|
layout: {
|
||||||
@@ -11,6 +11,13 @@ const meta = {
|
|||||||
padding: 0,
|
padding: 0,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
decorators: [
|
||||||
|
(Story) => (
|
||||||
|
<div className="flex items-center justify-center p-4">
|
||||||
|
<Story />
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
],
|
||||||
tags: ["autodocs"],
|
tags: ["autodocs"],
|
||||||
argTypes: {
|
argTypes: {
|
||||||
agent: {
|
agent: {
|
||||||
@@ -32,50 +39,118 @@ const meta = {
|
|||||||
export default meta;
|
export default meta;
|
||||||
type Story = StoryObj<typeof meta>;
|
type Story = StoryObj<typeof meta>;
|
||||||
|
|
||||||
|
const BACKGROUND_COLORS = [
|
||||||
|
"bg-violet-100 hover:bg-violet-200 dark:bg-violet-800",
|
||||||
|
"bg-blue-100 hover:bg-blue-200 dark:bg-blue-800",
|
||||||
|
"bg-green-100 hover:bg-green-200 dark:bg-green-800",
|
||||||
|
];
|
||||||
|
|
||||||
export const Default: Story = {
|
export const Default: Story = {
|
||||||
args: {
|
args: {
|
||||||
agent: {
|
agent: {
|
||||||
|
slug: "ai-writing-assistant",
|
||||||
agent_name:
|
agent_name:
|
||||||
"Personalized Morning Coffee Newsletter example of three lines",
|
"Personalized Morning Coffee Newsletter example of three lines",
|
||||||
sub_heading:
|
sub_heading:
|
||||||
"Transform ideas into breathtaking images with this AI-powered Image Generator.",
|
"Transform ideas into breathtaking images with this AI-powered Image Generator.",
|
||||||
description:
|
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.",
|
"Transform ideas into breathtaking images with this AI-powered Image Generator.",
|
||||||
agent_image:
|
agent_image:
|
||||||
"https://framerusercontent.com/images/KCIpxr9f97EGJgpaoqnjKsrOPwI.jpg",
|
"https://framerusercontent.com/images/KCIpxr9f97EGJgpaoqnjKsrOPwI.jpg",
|
||||||
creator_avatar:
|
creator_avatar: "testing_avatar.png",
|
||||||
"https://framerusercontent.com/images/KCIpxr9f97EGJgpaoqnjKsrOPwI.jpg",
|
creator: "John Ababesh",
|
||||||
creator: "AI Solutions Inc.",
|
|
||||||
runs: 50000,
|
|
||||||
rating: 4.7,
|
|
||||||
slug: "",
|
|
||||||
},
|
|
||||||
backgroundColor: "bg-white",
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export const WithInteraction: Story = {
|
|
||||||
args: {
|
|
||||||
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,
|
runs: 200000,
|
||||||
rating: 4.6,
|
rating: 4.6,
|
||||||
},
|
},
|
||||||
backgroundColor: "bg-white",
|
backgroundColor: BACKGROUND_COLORS[0],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ExtraLarge: Story = {
|
||||||
|
args: {
|
||||||
|
agent: {
|
||||||
|
agent_name:
|
||||||
|
"Universal Language Translator Pro with Advanced Neural Network Technology and Cross-Cultural Communication Capabilities",
|
||||||
|
sub_heading:
|
||||||
|
"Breaking language barriers with cutting-edge AI translation technology that revolutionizes global communication for businesses and individuals across continents while preserving cultural nuances and contextual meanings",
|
||||||
|
description:
|
||||||
|
"Experience seamless communication across 150+ languages with our advanced neural translation engine. Perfect for international businesses, travelers, and language enthusiasts. Features real-time conversation translation, document processing, and cultural context adaptation to ensure your message is delivered exactly as intended in any language. Our proprietary machine learning algorithms continuously improve translation accuracy with each interaction, adapting to regional dialects and specialized terminology. The system includes voice recognition capabilities, image-to-text translation for signs and documents, and can operate offline in emergency situations where internet connectivity is limited. With dedicated mobile apps for iOS and Android plus browser extensions, you'll never encounter language barriers again, whether in business negotiations, academic research, or while exploring new destinations.",
|
||||||
|
agent_image: Default.args.agent.agent_image,
|
||||||
|
creator_avatar: Default.args.agent.creator_avatar,
|
||||||
|
creator:
|
||||||
|
"Global Linguistics Technologies International Corporation and Research Institute for Cross-Cultural Communication",
|
||||||
|
runs: 1000000000,
|
||||||
|
rating: 4.9,
|
||||||
|
slug: "universal-translator-pro-with-advanced-neural-networks-and-multilingual-support-for-global-enterprise-solutions-and-individual-travelers",
|
||||||
|
},
|
||||||
|
backgroundColor: BACKGROUND_COLORS[2],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const MinimalText: Story = {
|
||||||
|
args: {
|
||||||
|
agent: {
|
||||||
|
agent_name: "A",
|
||||||
|
sub_heading: "B",
|
||||||
|
description: "C",
|
||||||
|
agent_image: Default.args.agent.agent_image,
|
||||||
|
creator_avatar: Default.args.agent.creator_avatar,
|
||||||
|
creator: "D",
|
||||||
|
runs: 0,
|
||||||
|
rating: 0,
|
||||||
|
slug: Default.args.agent.slug,
|
||||||
|
},
|
||||||
|
backgroundColor: BACKGROUND_COLORS[0],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const MissingImage: Story = {
|
||||||
|
args: {
|
||||||
|
agent: {
|
||||||
|
...Default.args.agent,
|
||||||
|
agent_image: "",
|
||||||
|
},
|
||||||
|
backgroundColor: BACKGROUND_COLORS[1],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const MissingAvatar: Story = {
|
||||||
|
args: {
|
||||||
|
agent: {
|
||||||
|
...Default.args.agent,
|
||||||
|
creator_avatar: "",
|
||||||
|
},
|
||||||
|
backgroundColor: BACKGROUND_COLORS[2],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const TestingInteractions: Story = {
|
||||||
|
args: {
|
||||||
|
agent: {
|
||||||
|
...Default.args.agent,
|
||||||
|
runs: 200000,
|
||||||
|
rating: 4.6,
|
||||||
|
},
|
||||||
|
backgroundColor: BACKGROUND_COLORS[1],
|
||||||
},
|
},
|
||||||
play: async ({ canvasElement }) => {
|
play: async ({ canvasElement }) => {
|
||||||
const canvas = within(canvasElement);
|
const canvas = within(canvasElement);
|
||||||
const featuredCard = canvas.getByTestId("featured-store-card");
|
const card = canvas.getByTestId("featured-store-card");
|
||||||
await userEvent.hover(featuredCard);
|
|
||||||
await userEvent.click(featuredCard);
|
await expect(card).toBeInTheDocument();
|
||||||
|
|
||||||
|
await userEvent.hover(card);
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 300));
|
||||||
|
|
||||||
|
const description = canvas.getByTestId("agent-description");
|
||||||
|
await expect(description).toBeVisible();
|
||||||
|
|
||||||
|
const agentImage = canvas.getByAltText(
|
||||||
|
`${Default.args.agent.agent_name} preview`,
|
||||||
|
);
|
||||||
|
await expect(agentImage).toHaveStyle({ opacity: "0" });
|
||||||
|
|
||||||
|
await userEvent.unhover(card);
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 300));
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
import type { Meta, StoryObj } from "@storybook/react";
|
import type { Meta, StoryObj } from "@storybook/react";
|
||||||
import { FilterChips } from "./FilterChips";
|
import { FilterChips } from "./FilterChips";
|
||||||
import { userEvent, within, expect } from "@storybook/test";
|
|
||||||
|
|
||||||
const meta = {
|
const meta = {
|
||||||
title: "AGPT UI/Filter Chips",
|
title: "Agpt UI/marketing/Filter Chips",
|
||||||
component: FilterChips,
|
component: FilterChips,
|
||||||
parameters: {
|
parameters: {
|
||||||
layout: "centered",
|
layout: "centered",
|
||||||
@@ -23,8 +22,8 @@ const defaultBadges = [
|
|||||||
"Marketing",
|
"Marketing",
|
||||||
"Sales",
|
"Sales",
|
||||||
"Content creation",
|
"Content creation",
|
||||||
"Lorem ipsum",
|
"AI",
|
||||||
"Lorem ipsum",
|
"Data Science",
|
||||||
];
|
];
|
||||||
|
|
||||||
export const Default: Story = {
|
export const Default: Story = {
|
||||||
@@ -41,45 +40,6 @@ export const SingleSelect: Story = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const WithSelectedFilters: Story = {
|
|
||||||
args: {
|
|
||||||
badges: defaultBadges,
|
|
||||||
multiSelect: true,
|
|
||||||
},
|
|
||||||
play: async ({ canvasElement, args }) => {
|
|
||||||
const canvas = within(canvasElement);
|
|
||||||
const marketingChip = canvas.getByText("Marketing").parentElement;
|
|
||||||
const salesChip = canvas.getByText("Sales").parentElement;
|
|
||||||
if (!marketingChip || !salesChip) {
|
|
||||||
throw new Error("Marketing or Sales chip not found");
|
|
||||||
}
|
|
||||||
|
|
||||||
await userEvent.click(marketingChip);
|
|
||||||
await userEvent.click(salesChip);
|
|
||||||
|
|
||||||
await expect(marketingChip).toHaveClass("bg-neutral-100");
|
|
||||||
await expect(salesChip).toHaveClass("bg-neutral-100");
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export const WithFilterChangeCallback: Story = {
|
|
||||||
args: {
|
|
||||||
badges: defaultBadges,
|
|
||||||
multiSelect: true,
|
|
||||||
onFilterChange: (selectedFilters: string[]) => {
|
|
||||||
console.log("Selected filters:", selectedFilters);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
play: async ({ canvasElement, args }) => {
|
|
||||||
const canvas = within(canvasElement);
|
|
||||||
const salesChip = canvas.getByText("Sales");
|
|
||||||
const marketingChip = canvas.getByText("Marketing");
|
|
||||||
|
|
||||||
await userEvent.click(salesChip);
|
|
||||||
await userEvent.click(marketingChip);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export const EmptyBadges: Story = {
|
export const EmptyBadges: Story = {
|
||||||
args: {
|
args: {
|
||||||
badges: [],
|
badges: [],
|
||||||
@@ -91,33 +51,10 @@ export const LongBadgeNames: Story = {
|
|||||||
args: {
|
args: {
|
||||||
badges: [
|
badges: [
|
||||||
"Machine Learning",
|
"Machine Learning",
|
||||||
"Natural Language Processing",
|
"Natural Language Processing, Natural Language Processing, Natural Language Processing",
|
||||||
"Computer Vision",
|
"Computer Vision",
|
||||||
"Data Science",
|
"Data Science",
|
||||||
],
|
],
|
||||||
multiSelect: true,
|
multiSelect: true,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const SingleSelectBehavior: Story = {
|
|
||||||
args: {
|
|
||||||
badges: defaultBadges,
|
|
||||||
multiSelect: false,
|
|
||||||
},
|
|
||||||
play: async ({ canvasElement, args }) => {
|
|
||||||
const canvas = within(canvasElement);
|
|
||||||
const salesChip = canvas.getByText("Sales").parentElement;
|
|
||||||
const marketingChip = canvas.getByText("Marketing").parentElement;
|
|
||||||
|
|
||||||
if (!salesChip || !marketingChip) {
|
|
||||||
throw new Error("Sales or Marketing chip not found");
|
|
||||||
}
|
|
||||||
|
|
||||||
await userEvent.click(salesChip);
|
|
||||||
await expect(salesChip).toHaveClass("bg-neutral-100");
|
|
||||||
|
|
||||||
await userEvent.click(marketingChip);
|
|
||||||
await expect(marketingChip).toHaveClass("bg-neutral-100");
|
|
||||||
await expect(salesChip).not.toHaveClass("bg-neutral-100");
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { Badge } from "@/components/ui/badge";
|
import { Badge } from "@/components/ui/badge";
|
||||||
|
import { Chip } from "./Chip";
|
||||||
|
|
||||||
interface FilterChipsProps {
|
interface FilterChipsProps {
|
||||||
badges: string[];
|
badges: string[];
|
||||||
@@ -36,18 +37,15 @@ export const FilterChips: React.FC<FilterChipsProps> = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex h-auto min-h-8 flex-wrap items-center justify-center gap-3 lg:min-h-14 lg:justify-start lg:gap-5">
|
<div className="flex flex-wrap items-center justify-center gap-3">
|
||||||
{badges.map((badge) => (
|
{badges.map((badge) => (
|
||||||
<Badge
|
<div
|
||||||
|
data-testid="filter-chip"
|
||||||
key={badge}
|
key={badge}
|
||||||
variant={selectedFilters.includes(badge) ? "secondary" : "outline"}
|
|
||||||
className="mb-2 flex cursor-pointer items-center justify-center gap-2 rounded-full border border-black/50 px-3 py-1 dark:border-white/50 lg:mb-3 lg:gap-2.5 lg:px-6 lg:py-2"
|
|
||||||
onClick={() => handleBadgeClick(badge)}
|
onClick={() => handleBadgeClick(badge)}
|
||||||
>
|
>
|
||||||
<div className="font-neue text-sm font-light tracking-tight text-[#474747] dark:text-[#e0e0e0] lg:text-xl lg:font-medium lg:leading-9">
|
<Chip className="hover:cursor-pointer">{badge}</Chip>
|
||||||
{badge}
|
</div>
|
||||||
</div>
|
|
||||||
</Badge>
|
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,13 +1,14 @@
|
|||||||
import type { Meta, StoryObj } from "@storybook/react";
|
import type { Meta, StoryObj } from "@storybook/react";
|
||||||
import { MobileNavBar } from "./MobileNavBar";
|
import { MobileNavBar } from "./MobileNavBar";
|
||||||
import { userEvent, within } from "@storybook/test";
|
|
||||||
import { IconType } from "../ui/icons";
|
import { IconType } from "../ui/icons";
|
||||||
|
|
||||||
const meta = {
|
const meta = {
|
||||||
title: "AGPT UI/Mobile Nav Bar",
|
title: "Agpt UI/general/Mobile Nav Bar",
|
||||||
component: MobileNavBar,
|
component: MobileNavBar,
|
||||||
parameters: {
|
parameters: {
|
||||||
layout: "centered",
|
viewport: {
|
||||||
|
defaultViewport: "mobile2",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
tags: ["autodocs"],
|
tags: ["autodocs"],
|
||||||
argTypes: {
|
argTypes: {
|
||||||
@@ -87,18 +88,3 @@ export const LongUserName: Story = {
|
|||||||
menuItemGroups: defaultMenuItemGroups,
|
menuItemGroups: defaultMenuItemGroups,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const WithInteraction: Story = {
|
|
||||||
args: {
|
|
||||||
...Default.args,
|
|
||||||
},
|
|
||||||
play: async ({ canvasElement }) => {
|
|
||||||
const canvas = within(canvasElement);
|
|
||||||
const menuTrigger = canvas.getByRole("button");
|
|
||||||
|
|
||||||
await userEvent.click(menuTrigger);
|
|
||||||
|
|
||||||
// Wait for the popover to appear
|
|
||||||
await canvas.findByText("Edit profile");
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import {
|
|||||||
IconMarketplace,
|
IconMarketplace,
|
||||||
IconLibrary,
|
IconLibrary,
|
||||||
IconBuilder,
|
IconBuilder,
|
||||||
|
IconAutoGPTLogo,
|
||||||
} from "../ui/icons";
|
} from "../ui/icons";
|
||||||
import { AnimatePresence, motion } from "framer-motion";
|
import { AnimatePresence, motion } from "framer-motion";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
@@ -125,73 +126,76 @@ export const MobileNavBar: React.FC<MobileNavBarProps> = ({
|
|||||||
const activeLink = parts.length > 1 ? parts[1] : parts[0];
|
const activeLink = parts.length > 1 ? parts[1] : parts[0];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Popover open={isOpen} onOpenChange={setIsOpen}>
|
<div className="flex w-full items-center justify-between border-b bg-white/40 px-4 py-2 backdrop-blur-lg">
|
||||||
<PopoverTrigger asChild>
|
<IconAutoGPTLogo className="h-16 w-16" />
|
||||||
<Button
|
<Popover open={isOpen} onOpenChange={setIsOpen}>
|
||||||
aria-label="Open menu"
|
<PopoverTrigger asChild>
|
||||||
className="fixed right-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:hidden"
|
<Button
|
||||||
data-testid="mobile-nav-bar-trigger"
|
aria-label="Open menu"
|
||||||
>
|
className="z-50 flex h-14 items-center justify-center rounded-lg border-none bg-transparent shadow-none hover:bg-gray-200/50 dark:border-neutral-700 dark:bg-neutral-800 dark:hover:bg-gray-700/50 md:hidden"
|
||||||
{isOpen ? (
|
data-testid="mobile-nav-bar-trigger"
|
||||||
<IconChevronUp className="h-8 w-8 stroke-black dark:stroke-white" />
|
>
|
||||||
) : (
|
{isOpen ? (
|
||||||
<IconMenu className="h-8 w-8 stroke-black dark:stroke-white" />
|
<IconChevronUp className="h-5 w-5 stroke-black dark:stroke-white" />
|
||||||
)}
|
) : (
|
||||||
<span className="sr-only">Open menu</span>
|
<IconMenu className="h-6 w-6 stroke-black dark:stroke-white" />
|
||||||
</Button>
|
)}
|
||||||
</PopoverTrigger>
|
<span className="sr-only">Open menu</span>
|
||||||
<AnimatePresence>
|
</Button>
|
||||||
<PopoverPortal>
|
</PopoverTrigger>
|
||||||
<Overlay>
|
<AnimatePresence>
|
||||||
<PopoverContent asChild>
|
<PopoverPortal>
|
||||||
<motion.div
|
<Overlay>
|
||||||
initial={{ opacity: 0, y: -32 }}
|
<PopoverContent asChild>
|
||||||
animate={{ opacity: 1, y: 0 }}
|
<motion.div
|
||||||
exit={{ opacity: 0, y: -32, transition: { duration: 0.2 } }}
|
initial={{ opacity: 0, y: -32 }}
|
||||||
className="w-screen rounded-b-2xl bg-white dark:bg-neutral-900"
|
animate={{ opacity: 1, y: 0 }}
|
||||||
>
|
exit={{ opacity: 0, y: -32, transition: { duration: 0.2 } }}
|
||||||
<div className="mb-4 inline-flex w-full items-end justify-start gap-4">
|
className="w-screen rounded-b-2xl bg-white/40 backdrop-blur-xl dark:bg-neutral-900"
|
||||||
<Avatar className="h-14 w-14 border border-[#474747] dark:border-[#cfcfcf]">
|
>
|
||||||
<AvatarImage
|
<div className="mb-4 inline-flex w-full items-end justify-start gap-4">
|
||||||
src={avatarSrc}
|
<Avatar className="h-14 w-14 self-start dark:border-[#cfcfcf]">
|
||||||
alt={userName || "Unknown User"}
|
<AvatarImage
|
||||||
/>
|
src={avatarSrc}
|
||||||
<AvatarFallback>
|
alt={userName || "Unknown User"}
|
||||||
{userName?.charAt(0) || "U"}
|
/>
|
||||||
</AvatarFallback>
|
<AvatarFallback size={56}>
|
||||||
</Avatar>
|
{userName?.charAt(0) || "U"}
|
||||||
<div className="relative h-14 w-full">
|
</AvatarFallback>
|
||||||
<div className="absolute left-0 top-0 text-lg font-semibold leading-7 text-[#474747] dark:text-[#cfcfcf]">
|
</Avatar>
|
||||||
{userName || "Unknown User"}
|
<div className="relative w-full">
|
||||||
</div>
|
<div className="text-lg font-semibold leading-7 text-[#474747] dark:text-[#cfcfcf]">
|
||||||
<div className="absolute left-0 top-6 font-inter text-base font-normal leading-7 text-[#474747] dark:text-[#cfcfcf]">
|
{userName || "Unknown User"}
|
||||||
{userEmail || "No Email Set"}
|
</div>
|
||||||
|
<div className="top-6 font-inter text-base font-normal leading-7 text-[#474747] dark:text-[#cfcfcf]">
|
||||||
|
{userEmail || "No Email Set"}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<Separator className="mb-4 dark:bg-[#3a3a3a]" />
|
||||||
<Separator className="mb-4 dark:bg-[#3a3a3a]" />
|
{menuItemGroups.map((group, groupIndex) => (
|
||||||
{menuItemGroups.map((group, groupIndex) => (
|
<React.Fragment key={groupIndex}>
|
||||||
<React.Fragment key={groupIndex}>
|
{group.items.map((item, itemIndex) => (
|
||||||
{group.items.map((item, itemIndex) => (
|
<PopoutMenuItem
|
||||||
<PopoutMenuItem
|
key={itemIndex}
|
||||||
key={itemIndex}
|
icon={item.icon}
|
||||||
icon={item.icon}
|
isActive={item.href === activeLink}
|
||||||
isActive={item.href === activeLink}
|
text={item.text}
|
||||||
text={item.text}
|
onClick={item.onClick}
|
||||||
onClick={item.onClick}
|
href={item.href}
|
||||||
href={item.href}
|
/>
|
||||||
/>
|
))}
|
||||||
))}
|
{groupIndex < menuItemGroups.length - 1 && (
|
||||||
{groupIndex < menuItemGroups.length - 1 && (
|
<Separator className="my-4 dark:bg-[#3a3a3a]" />
|
||||||
<Separator className="my-4 dark:bg-[#3a3a3a]" />
|
)}
|
||||||
)}
|
</React.Fragment>
|
||||||
</React.Fragment>
|
))}
|
||||||
))}
|
</motion.div>
|
||||||
</motion.div>
|
</PopoverContent>
|
||||||
</PopoverContent>
|
</Overlay>
|
||||||
</Overlay>
|
</PopoverPortal>
|
||||||
</PopoverPortal>
|
</AnimatePresence>
|
||||||
</AnimatePresence>
|
</Popover>
|
||||||
</Popover>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -3,10 +3,7 @@ import { Navbar } from "./Navbar";
|
|||||||
import { userEvent, within } from "@storybook/test";
|
import { userEvent, within } from "@storybook/test";
|
||||||
import { IconType } from "../ui/icons";
|
import { IconType } from "../ui/icons";
|
||||||
import { ProfileDetails } from "@/lib/autogpt-server-api/types";
|
import { ProfileDetails } from "@/lib/autogpt-server-api/types";
|
||||||
// You can't import this here, jest is not available in storybook and will crash it
|
|
||||||
// import { jest } from "@jest/globals";
|
|
||||||
|
|
||||||
// Mock the API responses
|
|
||||||
const mockProfileData: ProfileDetails = {
|
const mockProfileData: ProfileDetails = {
|
||||||
name: "John Doe",
|
name: "John Doe",
|
||||||
username: "johndoe",
|
username: "johndoe",
|
||||||
@@ -19,22 +16,16 @@ const mockCreditData = {
|
|||||||
credits: 1500,
|
credits: 1500,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Mock the API module
|
|
||||||
// jest.mock("@/lib/autogpt-server-api", () => {
|
|
||||||
// return function () {
|
|
||||||
// return {
|
|
||||||
// getStoreProfile: () => Promise.resolve(mockProfileData),
|
|
||||||
// getUserCredit: () => Promise.resolve(mockCreditData),
|
|
||||||
// };
|
|
||||||
// };
|
|
||||||
// });
|
|
||||||
|
|
||||||
const meta = {
|
const meta = {
|
||||||
title: "AGPT UI/Navbar",
|
title: "Agpt UI/general/Navbar",
|
||||||
component: Navbar,
|
component: Navbar,
|
||||||
parameters: {
|
decorators: [
|
||||||
layout: "fullscreen",
|
(Story) => (
|
||||||
},
|
<div className="flex h-screen w-full justify-center">
|
||||||
|
<Story />
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
],
|
||||||
tags: ["autodocs"],
|
tags: ["autodocs"],
|
||||||
argTypes: {
|
argTypes: {
|
||||||
// isLoggedIn: { control: "boolean" },
|
// isLoggedIn: { control: "boolean" },
|
||||||
@@ -84,9 +75,19 @@ const defaultMenuItemGroups = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
const defaultLinks = [
|
const defaultLinks = [
|
||||||
{ name: "Marketplace", href: "/marketplace" },
|
{
|
||||||
{ name: "Library", href: "/library" },
|
name: "Home",
|
||||||
{ name: "Build", href: "/builder" },
|
href: "/library",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Marketplace",
|
||||||
|
href: "/marketplace",
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: "Build",
|
||||||
|
href: "/build",
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
export const Default: Story = {
|
export const Default: Story = {
|
||||||
@@ -99,65 +100,3 @@ export const Default: Story = {
|
|||||||
menuItemGroups: defaultMenuItemGroups,
|
menuItemGroups: defaultMenuItemGroups,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const WithActiveLink: Story = {
|
|
||||||
args: {
|
|
||||||
...Default.args,
|
|
||||||
// activeLink: "/library",
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export const LongUserName: Story = {
|
|
||||||
args: {
|
|
||||||
...Default.args,
|
|
||||||
// avatarSrc: "https://avatars.githubusercontent.com/u/987654321?v=4",
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export const NoAvatar: Story = {
|
|
||||||
args: {
|
|
||||||
...Default.args,
|
|
||||||
// avatarSrc: undefined,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export const WithInteraction: Story = {
|
|
||||||
args: {
|
|
||||||
...Default.args,
|
|
||||||
},
|
|
||||||
play: async ({ canvasElement }) => {
|
|
||||||
const canvas = within(canvasElement);
|
|
||||||
const profileTrigger = canvas.getByRole("button");
|
|
||||||
|
|
||||||
await userEvent.click(profileTrigger);
|
|
||||||
|
|
||||||
// Wait for the popover to appear
|
|
||||||
await canvas.findByText("Edit profile");
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export const NotLoggedIn: Story = {
|
|
||||||
args: {
|
|
||||||
...Default.args,
|
|
||||||
// isLoggedIn: false,
|
|
||||||
// avatarSrc: undefined,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export const WithCredits: Story = {
|
|
||||||
args: {
|
|
||||||
...Default.args,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export const WithLargeCredits: Story = {
|
|
||||||
args: {
|
|
||||||
...Default.args,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export const WithZeroCredits: Story = {
|
|
||||||
args: {
|
|
||||||
...Default.args,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -9,9 +9,9 @@ import { ProfileDetails } from "@/lib/autogpt-server-api/types";
|
|||||||
import { NavbarLink } from "./NavbarLink";
|
import { NavbarLink } from "./NavbarLink";
|
||||||
import getServerUser from "@/lib/supabase/getServerUser";
|
import getServerUser from "@/lib/supabase/getServerUser";
|
||||||
import BackendAPI from "@/lib/autogpt-server-api";
|
import BackendAPI from "@/lib/autogpt-server-api";
|
||||||
|
import MockClient from "@/lib/autogpt-server-api/mock_client";
|
||||||
// Disable theme toggle for now
|
import Image from "next/image";
|
||||||
// import { ThemeToggle } from "./ThemeToggle";
|
import AutogptButton from "./AutogptButton";
|
||||||
|
|
||||||
interface NavLink {
|
interface NavLink {
|
||||||
name: string;
|
name: string;
|
||||||
@@ -32,7 +32,7 @@ interface NavbarProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function getProfileData() {
|
async function getProfileData() {
|
||||||
const api = new BackendAPI();
|
const api = process.env.STORYBOOK ? new MockClient() : new BackendAPI();
|
||||||
const profile = await Promise.resolve(api.getStoreProfile());
|
const profile = await Promise.resolve(api.getStoreProfile());
|
||||||
|
|
||||||
return profile;
|
return profile;
|
||||||
@@ -48,19 +48,28 @@ export const Navbar = async ({ links, menuItemGroups }: NavbarProps) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<nav className="sticky top-0 z-40 mx-[16px] hidden h-16 items-center justify-between rounded-bl-2xl rounded-br-2xl border border-white/50 bg-white/5 py-3 pl-6 pr-3 backdrop-blur-[26px] dark:border-gray-700 dark:bg-gray-900 md:inline-flex">
|
<nav className="sticky top-0 z-40 hidden h-16 w-full border-b border-zinc-50 bg-neutral-50/20 px-4 backdrop-blur-[26px] md:flex md:items-center md:justify-center">
|
||||||
<div className="flex items-center gap-11">
|
{/* Nav Links */}
|
||||||
<div className="relative h-10 w-[88.87px]">
|
<div className="flex flex-1 items-center gap-5">
|
||||||
<IconAutoGPTLogo className="h-full w-full" />
|
|
||||||
</div>
|
|
||||||
{links.map((link) => (
|
{links.map((link) => (
|
||||||
<NavbarLink key={link.name} name={link.name} href={link.href} />
|
<NavbarLink key={link.name} name={link.name} href={link.href} />
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
{/* Profile section */}
|
|
||||||
<div className="flex items-center gap-4">
|
{/* Icon */}
|
||||||
|
<Link href="/" className="flex items-center">
|
||||||
|
<Image
|
||||||
|
src="/agpt-logo.svg"
|
||||||
|
alt="AutoGPT Logo"
|
||||||
|
width={90}
|
||||||
|
height={40}
|
||||||
|
/>
|
||||||
|
</Link>
|
||||||
|
|
||||||
|
{/* Popouts */}
|
||||||
|
<div className="flex flex-1 items-center justify-end gap-3">
|
||||||
{isLoggedIn ? (
|
{isLoggedIn ? (
|
||||||
<div className="flex items-center gap-4">
|
<>
|
||||||
{profile && <Wallet />}
|
{profile && <Wallet />}
|
||||||
<ProfilePopoutMenu
|
<ProfilePopoutMenu
|
||||||
menuItemGroups={menuItemGroups}
|
menuItemGroups={menuItemGroups}
|
||||||
@@ -68,25 +77,19 @@ export const Navbar = async ({ links, menuItemGroups }: NavbarProps) => {
|
|||||||
userEmail={profile?.name}
|
userEmail={profile?.name}
|
||||||
avatarSrc={profile?.avatar_url}
|
avatarSrc={profile?.avatar_url}
|
||||||
/>
|
/>
|
||||||
</div>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<Link href="/login">
|
<Link href="/login">
|
||||||
<Button
|
<AutogptButton variant={"default"}>Log In</AutogptButton>
|
||||||
size="sm"
|
|
||||||
className="flex items-center justify-end space-x-2"
|
|
||||||
>
|
|
||||||
<IconLogIn className="h-5 h-[48px] w-5" />
|
|
||||||
<span>Log In</span>
|
|
||||||
</Button>
|
|
||||||
</Link>
|
</Link>
|
||||||
)}
|
)}
|
||||||
{/* <ThemeToggle /> */}
|
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
{/* Mobile Navbar - Adjust positioning */}
|
{/* Mobile Navbar - Adjust positioning */}
|
||||||
<>
|
<>
|
||||||
{isLoggedIn ? (
|
{isLoggedIn ? (
|
||||||
<div className="fixed right-4 top-4 z-50">
|
<div className="sticky top-0 z-50 w-full md:hidden">
|
||||||
<MobileNavBar
|
<MobileNavBar
|
||||||
userName={profile?.username}
|
userName={profile?.username}
|
||||||
menuItemGroups={[
|
menuItemGroups={[
|
||||||
|
|||||||
@@ -1,60 +1,53 @@
|
|||||||
"use client";
|
"use client";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
|
import { usePathname } from "next/navigation";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
IconShoppingCart,
|
IconShoppingCart,
|
||||||
IconBoxes,
|
IconBoxes,
|
||||||
IconLibrary,
|
IconLibrary,
|
||||||
IconLaptop,
|
IconLaptop,
|
||||||
} from "@/components/ui/icons";
|
} from "@/components/ui/icons";
|
||||||
import { usePathname } from "next/navigation";
|
|
||||||
|
|
||||||
interface NavbarLinkProps {
|
interface NavbarLinkProps {
|
||||||
name: string;
|
name: string;
|
||||||
href: string;
|
href: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const icons = {
|
||||||
|
"/marketplace": IconShoppingCart,
|
||||||
|
"/build": IconBoxes,
|
||||||
|
"/library": IconLibrary,
|
||||||
|
"/home": IconLibrary,
|
||||||
|
};
|
||||||
|
|
||||||
export const NavbarLink = ({ name, href }: NavbarLinkProps) => {
|
export const NavbarLink = ({ name, href }: NavbarLinkProps) => {
|
||||||
const pathname = usePathname();
|
const pathname = usePathname();
|
||||||
const parts = pathname.split("/");
|
const isActive =
|
||||||
const activeLink = "/" + (parts.length > 2 ? parts[2] : parts[1]);
|
href === "/marketplace"
|
||||||
|
? pathname.includes("/marketplace")
|
||||||
|
: pathname === href;
|
||||||
|
|
||||||
|
const Icon = icons[href as keyof typeof icons];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Link
|
<Link href={href} data-testid={`navbar-link-${name.toLowerCase()}`}>
|
||||||
href={href}
|
|
||||||
data-testid={`navbar-link-${name.toLowerCase()}`}
|
|
||||||
className="font-poppins text-[20px] leading-[28px]"
|
|
||||||
>
|
|
||||||
<div
|
<div
|
||||||
className={`h-[48px] px-5 py-4 ${
|
className={`flex h-10 items-center justify-start gap-2 px-3 py-2 ${
|
||||||
activeLink === href
|
isActive ? "rounded-lg bg-zinc-800 dark:bg-neutral-200" : ""
|
||||||
? "rounded-2xl bg-neutral-800 dark:bg-neutral-200"
|
}`}
|
||||||
: ""
|
|
||||||
} flex items-center justify-start gap-3`}
|
|
||||||
>
|
>
|
||||||
{href === "/marketplace" && (
|
{Icon && (
|
||||||
<IconShoppingCart
|
<Icon
|
||||||
className={`h-6 w-6 ${activeLink === href ? "text-white dark:text-black" : ""}`}
|
className={`h-5 w-5 ${
|
||||||
/>
|
isActive ? "text-zinc-50 dark:text-black" : ""
|
||||||
)}
|
}`}
|
||||||
{href === "/build" && (
|
|
||||||
<IconBoxes
|
|
||||||
className={`h-6 w-6 ${activeLink === href ? "text-white dark:text-black" : ""}`}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{href === "/monitor" && (
|
|
||||||
<IconLaptop
|
|
||||||
className={`h-6 w-6 ${activeLink === href ? "text-white dark:text-black" : ""}`}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{href === "/library" && (
|
|
||||||
<IconLibrary
|
|
||||||
className={`h-6 w-6 ${activeLink === href ? "text-white dark:text-black" : ""}`}
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<div
|
<div
|
||||||
className={`hidden font-poppins text-[20px] font-medium leading-[28px] lg:block ${
|
className={`hidden font-poppins text-base font-medium lg:block ${
|
||||||
activeLink === href
|
isActive
|
||||||
? "text-neutral-50 dark:text-neutral-900"
|
? "text-zinc-50 dark:text-neutral-900"
|
||||||
: "text-neutral-900 dark:text-neutral-50"
|
: "text-neutral-900 dark:text-neutral-50"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -1,79 +0,0 @@
|
|||||||
import type { Meta, StoryObj } from "@storybook/react";
|
|
||||||
import { ProfileInfoForm } from "./ProfileInfoForm";
|
|
||||||
|
|
||||||
const meta: Meta<typeof ProfileInfoForm> = {
|
|
||||||
title: "AGPT UI/Profile/Profile Info Form",
|
|
||||||
component: ProfileInfoForm,
|
|
||||||
parameters: {
|
|
||||||
layout: "fullscreen",
|
|
||||||
},
|
|
||||||
tags: ["autodocs"],
|
|
||||||
argTypes: {
|
|
||||||
profile: {
|
|
||||||
control: "object",
|
|
||||||
description: "The profile details of the user",
|
|
||||||
displayName: {
|
|
||||||
control: "text",
|
|
||||||
description: "The display name of the user",
|
|
||||||
},
|
|
||||||
handle: {
|
|
||||||
control: "text",
|
|
||||||
description: "The user's handle/username",
|
|
||||||
},
|
|
||||||
bio: {
|
|
||||||
control: "text",
|
|
||||||
description: "User's biography text",
|
|
||||||
},
|
|
||||||
profileImage: {
|
|
||||||
control: "text",
|
|
||||||
description: "URL of the user's profile image",
|
|
||||||
},
|
|
||||||
links: {
|
|
||||||
control: "object",
|
|
||||||
description: "Array of social media links",
|
|
||||||
},
|
|
||||||
categories: {
|
|
||||||
control: "object",
|
|
||||||
description: "Array of selected categories",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export default meta;
|
|
||||||
type Story = StoryObj<typeof ProfileInfoForm>;
|
|
||||||
|
|
||||||
export const Empty: Story = {
|
|
||||||
args: {
|
|
||||||
profile: {
|
|
||||||
name: "",
|
|
||||||
username: "",
|
|
||||||
description: "",
|
|
||||||
avatar_url: "",
|
|
||||||
links: [],
|
|
||||||
top_categories: [],
|
|
||||||
agent_rating: 0,
|
|
||||||
agent_runs: 0,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export const Filled: Story = {
|
|
||||||
args: {
|
|
||||||
profile: {
|
|
||||||
name: "Olivia Grace",
|
|
||||||
username: "@ograce1421",
|
|
||||||
description:
|
|
||||||
"Our agents are designed to bring happiness and positive vibes to your daily routine. Each template helps you create and live more efficiently.",
|
|
||||||
avatar_url: "https://via.placeholder.com/130x130",
|
|
||||||
links: [
|
|
||||||
"www.websitelink.com",
|
|
||||||
"twitter.com/oliviagrace",
|
|
||||||
"github.com/ograce",
|
|
||||||
],
|
|
||||||
top_categories: ["Entertainment", "Blog", "Content creation"],
|
|
||||||
agent_rating: 4.5,
|
|
||||||
agent_runs: 100,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
@@ -1,22 +1,27 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { useState } from "react";
|
import { useState, useRef } from "react";
|
||||||
|
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
|
|
||||||
import { Button } from "./Button";
|
|
||||||
import { IconPersonFill } from "@/components/ui/icons";
|
import { IconPersonFill } from "@/components/ui/icons";
|
||||||
import { CreatorDetails, ProfileDetails } from "@/lib/autogpt-server-api/types";
|
import { CreatorDetails, ProfileDetails } from "@/lib/autogpt-server-api/types";
|
||||||
import { Separator } from "@/components/ui/separator";
|
import { Separator } from "@/components/ui/separator";
|
||||||
import useSupabase from "@/hooks/useSupabase";
|
import useSupabase from "@/hooks/useSupabase";
|
||||||
import { useBackendAPI } from "@/lib/autogpt-server-api/context";
|
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 }) => {
|
export const ProfileInfoForm = ({ profile }: { profile: CreatorDetails }) => {
|
||||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||||
const [profileData, setProfileData] = useState(profile);
|
const [profileData, setProfileData] = useState(profile);
|
||||||
const { supabase } = useSupabase();
|
const { supabase } = useSupabase();
|
||||||
const api = useBackendAPI();
|
const api = useBackendAPI();
|
||||||
|
const editPhotoRef = useRef<HTMLInputElement>(null);
|
||||||
|
|
||||||
const submitForm = async () => {
|
const submitForm = async () => {
|
||||||
try {
|
try {
|
||||||
@@ -98,171 +103,156 @@ export const ProfileInfoForm = ({ profile }: { profile: CreatorDetails }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-full min-w-[800px] px-4 sm:px-8">
|
<div className="mb-8 sm:mb-12">
|
||||||
<h1 className="font-circular mb-6 text-[28px] font-normal text-neutral-900 dark:text-neutral-100 sm:mb-8 sm:text-[35px]">
|
<div className="mb-8 flex flex-col items-center gap-4 sm:flex-row">
|
||||||
Profile
|
<div className="flex h-[6.25rem] w-[6.25rem] items-center justify-center rounded-full bg-[#DADADA]">
|
||||||
</h1>
|
{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">
|
<form className="space-y-10" onSubmit={submitForm}>
|
||||||
<div className="mb-8 flex flex-col items-center gap-4 sm:flex-row sm:items-start">
|
{/* Top section */}
|
||||||
<div className="relative h-[130px] w-[130px] rounded-full bg-[#d9d9d9] dark:bg-[#333333]">
|
<section className="max-w-3xl space-y-6">
|
||||||
{profileData.avatar_url ? (
|
<AutogptInput
|
||||||
<Image
|
label="Display name"
|
||||||
src={profileData.avatar_url}
|
type="text"
|
||||||
alt="Profile"
|
name="displayName"
|
||||||
fill
|
defaultValue={profileData.name}
|
||||||
className="rounded-full"
|
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) => {
|
||||||
<IconPersonFill className="absolute left-[30px] top-[24px] h-[77.80px] w-[70.63px] text-[#7e7e7e] dark:text-[#999999]" />
|
const newProfileData = {
|
||||||
)}
|
...profileData,
|
||||||
</div>
|
name: e.target.value,
|
||||||
<label className="font-circular mt-11 inline-flex h-[43px] items-center justify-center rounded-[22px] bg-[#15171A] px-6 py-2 text-sm font-normal text-white transition-colors hover:bg-[#2D2F34] dark:bg-white dark:text-[#15171A] dark:hover:bg-[#E5E5E5]">
|
};
|
||||||
<input
|
setProfileData(newProfileData);
|
||||||
type="file"
|
}}
|
||||||
accept="image/*"
|
/>
|
||||||
className="hidden"
|
|
||||||
onChange={async (e) => {
|
<AutogptInput
|
||||||
const file = e.target.files?.[0];
|
label="Handle"
|
||||||
if (file) {
|
type="text"
|
||||||
await handleImageUpload(file);
|
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-[#E2E8F0] bg-white py-2 pl-4 font-sans text-base font-normal text-zinc-800 shadow-none outline-none ring-0 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>
|
|
||||||
|
|
||||||
<form className="space-y-4 sm:space-y-6" onSubmit={submitForm}>
|
|
||||||
<div className="w-full">
|
|
||||||
<label className="font-circular mb-1.5 block text-base font-normal leading-tight text-neutral-700 dark:text-neutral-300">
|
|
||||||
Display name
|
|
||||||
</label>
|
|
||||||
<div className="rounded-[55px] border border-slate-200 px-4 py-2.5 dark:border-slate-700 dark:bg-slate-800">
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
name="displayName"
|
|
||||||
defaultValue={profileData.name}
|
|
||||||
placeholder="Enter your display name"
|
|
||||||
className="font-circular w-full border-none bg-transparent text-base font-normal text-neutral-900 placeholder:text-neutral-400 focus:outline-none dark:text-white dark:placeholder:text-neutral-500"
|
|
||||||
onChange={(e) => {
|
|
||||||
const newProfileData = {
|
|
||||||
...profileData,
|
|
||||||
name: e.target.value,
|
|
||||||
};
|
|
||||||
setProfileData(newProfileData);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
<div className="w-full">
|
<Separator className="bg-neutral-300" />
|
||||||
<label className="font-circular mb-1.5 block text-base font-normal leading-tight text-neutral-700 dark:text-neutral-300">
|
|
||||||
Handle
|
|
||||||
</label>
|
|
||||||
<div className="rounded-[55px] border border-slate-200 px-4 py-2.5 dark:border-slate-700 dark:bg-slate-800">
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
name="handle"
|
|
||||||
defaultValue={profileData.username}
|
|
||||||
placeholder="@username"
|
|
||||||
className="font-circular w-full border-none bg-transparent text-base font-normal text-neutral-900 placeholder:text-neutral-400 focus:outline-none dark:text-white dark:placeholder:text-neutral-500"
|
|
||||||
onChange={(e) => {
|
|
||||||
const newProfileData = {
|
|
||||||
...profileData,
|
|
||||||
username: e.target.value,
|
|
||||||
};
|
|
||||||
setProfileData(newProfileData);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="w-full">
|
{/* mid section */}
|
||||||
<label className="font-circular mb-1.5 block text-base font-normal leading-tight text-neutral-700 dark:text-neutral-300">
|
<section className="mb-8 max-w-3xl space-y-6">
|
||||||
Bio
|
<div>
|
||||||
</label>
|
<h2 className="font-poppins text-base font-medium text-neutral-900">
|
||||||
<div className="h-[220px] rounded-2xl border border-slate-200 py-2.5 pl-4 pr-4 dark:border-slate-700 dark:bg-slate-800">
|
|
||||||
<textarea
|
|
||||||
name="bio"
|
|
||||||
defaultValue={profileData.description}
|
|
||||||
placeholder="Tell us about yourself..."
|
|
||||||
className="font-circular h-full w-full resize-none border-none bg-transparent text-base font-normal text-neutral-900 placeholder:text-neutral-400 focus:outline-none dark:text-white dark:placeholder:text-neutral-500"
|
|
||||||
onChange={(e) => {
|
|
||||||
const newProfileData = {
|
|
||||||
...profileData,
|
|
||||||
description: e.target.value,
|
|
||||||
};
|
|
||||||
setProfileData(newProfileData);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<section className="mb-8">
|
|
||||||
<h2 className="font-circular mb-4 text-lg font-normal leading-7 text-neutral-700 dark:text-neutral-300">
|
|
||||||
Your links
|
Your links
|
||||||
</h2>
|
</h2>
|
||||||
<p className="font-circular mb-6 text-base font-normal leading-tight text-neutral-600 dark:text-neutral-400">
|
<p className="font-sans text-sm font-normal text-zinc-800">
|
||||||
You can display up to 5 links on your profile
|
You can display up to 5 links on your profile
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<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">
|
|
||||||
<label className="font-circular mb-1.5 block text-base font-normal leading-tight text-neutral-700 dark:text-neutral-300">
|
|
||||||
Link {linkNum}
|
|
||||||
</label>
|
|
||||||
<div className="rounded-[55px] border border-slate-200 px-4 py-2.5 dark:border-slate-700 dark:bg-slate-800">
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
name={`link${linkNum}`}
|
|
||||||
placeholder="https://"
|
|
||||||
defaultValue={link || ""}
|
|
||||||
className="font-circular w-full border-none bg-transparent text-base font-normal text-neutral-900 placeholder:text-neutral-400 focus:outline-none dark:text-white dark:placeholder:text-neutral-500"
|
|
||||||
onChange={(e) => {
|
|
||||||
const newLinks = [...profileData.links];
|
|
||||||
newLinks[linkNum - 1] = e.target.value;
|
|
||||||
const newProfileData = {
|
|
||||||
...profileData,
|
|
||||||
links: newLinks,
|
|
||||||
};
|
|
||||||
setProfileData(newProfileData);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<Separator />
|
|
||||||
|
|
||||||
<div className="flex h-[50px] items-center justify-end gap-3 py-8">
|
|
||||||
<Button
|
|
||||||
type="button"
|
|
||||||
variant="secondary"
|
|
||||||
className="font-circular h-[50px] rounded-[35px] bg-neutral-200 px-6 py-3 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="font-circular h-[50px] rounded-[35px] bg-neutral-800 px-6 py-3 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>
|
|
||||||
</div>
|
</div>
|
||||||
</form>
|
|
||||||
</div>
|
<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">
|
||||||
|
<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>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,99 +0,0 @@
|
|||||||
import type { Meta, StoryObj } from "@storybook/react";
|
|
||||||
import { ProfilePopoutMenu } from "./ProfilePopoutMenu";
|
|
||||||
import { userEvent, within } from "@storybook/test";
|
|
||||||
import { IconType } from "../ui/icons";
|
|
||||||
|
|
||||||
const meta = {
|
|
||||||
title: "AGPT UI/Profile Popout Menu",
|
|
||||||
component: ProfilePopoutMenu,
|
|
||||||
parameters: {
|
|
||||||
layout: "centered",
|
|
||||||
},
|
|
||||||
tags: ["autodocs"],
|
|
||||||
argTypes: {
|
|
||||||
userName: { control: "text" },
|
|
||||||
userEmail: { control: "text" },
|
|
||||||
avatarSrc: { control: "text" },
|
|
||||||
menuItemGroups: { control: "object" },
|
|
||||||
hideNavBarUsername: { control: "boolean" },
|
|
||||||
},
|
|
||||||
} satisfies Meta<typeof ProfilePopoutMenu>;
|
|
||||||
|
|
||||||
export default meta;
|
|
||||||
type Story = StoryObj<typeof meta>;
|
|
||||||
|
|
||||||
const defaultMenuItemGroups = [
|
|
||||||
{
|
|
||||||
// Creator actions group
|
|
||||||
items: [
|
|
||||||
{
|
|
||||||
icon: IconType.LayoutDashboard,
|
|
||||||
text: "Creator Dashboard",
|
|
||||||
href: "/dashboard",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: IconType.UploadCloud,
|
|
||||||
text: "Publish an agent",
|
|
||||||
href: "/publish",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
// Profile management group
|
|
||||||
items: [
|
|
||||||
{ icon: IconType.Edit, text: "Edit profile", href: "/profile/edit" },
|
|
||||||
{ icon: IconType.Settings, text: "Settings", href: "/settings" },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
// Logout group
|
|
||||||
items: [
|
|
||||||
{
|
|
||||||
icon: IconType.LogOut,
|
|
||||||
text: "Log out",
|
|
||||||
onClick: () => console.log("Logged out"),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
export const Default: Story = {
|
|
||||||
args: {
|
|
||||||
userName: "John Doe",
|
|
||||||
userEmail: "john.doe@example.com",
|
|
||||||
avatarSrc: "https://avatars.githubusercontent.com/u/123456789?v=4",
|
|
||||||
menuItemGroups: defaultMenuItemGroups,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export const NoAvatar: Story = {
|
|
||||||
args: {
|
|
||||||
userName: "Jane Smith",
|
|
||||||
userEmail: "jane.smith@example.com",
|
|
||||||
menuItemGroups: defaultMenuItemGroups,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export const LongUserName: Story = {
|
|
||||||
args: {
|
|
||||||
userName: "Alexander Bartholomew Christopherson III",
|
|
||||||
userEmail: "alexander@example.com",
|
|
||||||
avatarSrc: "https://avatars.githubusercontent.com/u/987654321?v=4",
|
|
||||||
menuItemGroups: defaultMenuItemGroups,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export const WithInteraction: Story = {
|
|
||||||
args: {
|
|
||||||
...Default.args,
|
|
||||||
},
|
|
||||||
play: async ({ canvasElement }) => {
|
|
||||||
const canvas = within(canvasElement);
|
|
||||||
const profileTrigger = canvas.getByText("John Doe");
|
|
||||||
|
|
||||||
await userEvent.click(profileTrigger);
|
|
||||||
|
|
||||||
// Wait for the popover to appear
|
|
||||||
await canvas.findByText("Edit profile");
|
|
||||||
},
|
|
||||||
};
|
|
||||||
@@ -20,6 +20,7 @@ import {
|
|||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { ProfilePopoutMenuLogoutButton } from "./ProfilePopoutMenuLogoutButton";
|
import { ProfilePopoutMenuLogoutButton } from "./ProfilePopoutMenuLogoutButton";
|
||||||
import { PublishAgentPopout } from "./composite/PublishAgentPopout";
|
import { PublishAgentPopout } from "./composite/PublishAgentPopout";
|
||||||
|
import AutogptButton from "./AutogptButton";
|
||||||
|
|
||||||
interface ProfilePopoutMenuProps {
|
interface ProfilePopoutMenuProps {
|
||||||
userName?: string;
|
userName?: string;
|
||||||
@@ -72,9 +73,9 @@ export const ProfilePopoutMenu: React.FC<ProfilePopoutMenuProps> = ({
|
|||||||
return (
|
return (
|
||||||
<Popover>
|
<Popover>
|
||||||
<PopoverTrigger asChild>
|
<PopoverTrigger asChild>
|
||||||
<button
|
<AutogptButton
|
||||||
type="button"
|
type="button"
|
||||||
className="flex cursor-pointer items-center space-x-3"
|
variant={"link"}
|
||||||
aria-label="Open profile menu"
|
aria-label="Open profile menu"
|
||||||
aria-controls={popupId}
|
aria-controls={popupId}
|
||||||
aria-haspopup="true"
|
aria-haspopup="true"
|
||||||
@@ -86,26 +87,26 @@ export const ProfilePopoutMenu: React.FC<ProfilePopoutMenuProps> = ({
|
|||||||
{userName?.charAt(0) || "U"}
|
{userName?.charAt(0) || "U"}
|
||||||
</AvatarFallback>
|
</AvatarFallback>
|
||||||
</Avatar>
|
</Avatar>
|
||||||
</button>
|
</AutogptButton>
|
||||||
</PopoverTrigger>
|
</PopoverTrigger>
|
||||||
|
|
||||||
<PopoverContent
|
<PopoverContent
|
||||||
id={popupId}
|
id={popupId}
|
||||||
className="flex h-[380px] w-[300px] flex-col items-start justify-start gap-4 rounded-[26px] bg-zinc-400/70 p-6 shadow backdrop-blur-2xl dark:bg-zinc-800/70"
|
className="mr-8 flex w-[300px] flex-col items-start justify-start gap-4 rounded-[26px] bg-zinc-400/70 p-6 shadow backdrop-blur-2xl dark:bg-zinc-800/70"
|
||||||
>
|
>
|
||||||
{/* Header with avatar and user info */}
|
{/* Header with avatar and user info */}
|
||||||
<div className="inline-flex items-center justify-start gap-4 self-stretch">
|
<div className="inline-flex items-center justify-start gap-4 self-stretch">
|
||||||
<Avatar className="h-[60px] w-[60px]">
|
<Avatar className="h-16 w-16">
|
||||||
<AvatarImage src={avatarSrc} alt="" aria-hidden="true" />
|
<AvatarImage src={avatarSrc} alt="" aria-hidden="true" />
|
||||||
<AvatarFallback aria-hidden="true">
|
<AvatarFallback aria-hidden="true">
|
||||||
{userName?.charAt(0) || "U"}
|
{userName?.charAt(0) || "U"}
|
||||||
</AvatarFallback>
|
</AvatarFallback>
|
||||||
</Avatar>
|
</Avatar>
|
||||||
<div className="relative h-[47px] w-[173px]">
|
<div>
|
||||||
<div className="absolute left-0 top-0 font-sans text-base font-semibold leading-7 text-white dark:text-neutral-200">
|
<div className="font-sans text-base font-semibold leading-7 text-white dark:text-neutral-200">
|
||||||
{userName}
|
{userName}
|
||||||
</div>
|
</div>
|
||||||
<div className="absolute left-0 top-[23px] font-sans text-base font-normal leading-normal text-white dark:text-neutral-400">
|
<div className="font-sans text-base font-normal leading-normal text-white dark:text-neutral-400">
|
||||||
{userEmail}
|
{userEmail}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -2,12 +2,16 @@ import type { Meta, StoryObj } from "@storybook/react";
|
|||||||
import { PublishAgentAwaitingReview } from "./PublishAgentAwaitingReview";
|
import { PublishAgentAwaitingReview } from "./PublishAgentAwaitingReview";
|
||||||
|
|
||||||
const meta: Meta<typeof PublishAgentAwaitingReview> = {
|
const meta: Meta<typeof PublishAgentAwaitingReview> = {
|
||||||
title: "AGPT UI/Publish Agent Awaiting Review",
|
title: "Agpt UI/marketing/Publish Agent Awaiting Review",
|
||||||
component: PublishAgentAwaitingReview,
|
component: PublishAgentAwaitingReview,
|
||||||
tags: ["autodocs"],
|
tags: ["autodocs"],
|
||||||
parameters: {
|
decorators: [
|
||||||
layout: "centered",
|
(Story) => (
|
||||||
},
|
<div className="backdrop-blur-4 flex h-screen items-center justify-center bg-black/40 md:p-4">
|
||||||
|
<Story />
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
export default meta;
|
export default meta;
|
||||||
|
|||||||
@@ -3,7 +3,8 @@
|
|||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { IconClose } from "../ui/icons";
|
import { IconClose } from "../ui/icons";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import { Button } from "../agptui/Button";
|
import { Button } from "./Button";
|
||||||
|
import { X } from "lucide-react";
|
||||||
|
|
||||||
interface PublishAgentAwaitingReviewProps {
|
interface PublishAgentAwaitingReviewProps {
|
||||||
agentName: string;
|
agentName: string;
|
||||||
@@ -28,37 +29,35 @@ export const PublishAgentAwaitingReview: React.FC<
|
|||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className="inline-flex min-h-screen w-full flex-col items-center justify-center rounded-none bg-white dark:bg-neutral-900 sm:h-auto sm:min-h-[824px] sm:rounded-3xl"
|
|
||||||
role="dialog"
|
role="dialog"
|
||||||
aria-labelledby="modal-title"
|
aria-labelledby="modal-title"
|
||||||
|
className="m-auto flex h-fit w-full max-w-[900px] flex-col rounded-3xl bg-white shadow-lg dark:bg-gray-800"
|
||||||
>
|
>
|
||||||
<div className="relative h-[180px] w-full rounded-none bg-white dark:bg-neutral-800 sm:h-[140px] sm:rounded-t-3xl">
|
{/* Top */}
|
||||||
<div className="absolute left-0 top-[40px] flex w-full flex-col items-center justify-start px-6 sm:top-[40px]">
|
<div className="relative items-center justify-center border-b border-slate-200 pb-4 pt-12 dark:border-slate-700 md:flex md:h-28">
|
||||||
<div
|
<div className="absolute right-4 top-4">
|
||||||
id="modal-title"
|
<Button
|
||||||
className="mb-4 text-center font-poppins text-xl font-semibold leading-relaxed text-neutral-900 dark:text-neutral-100 sm:mb-2 sm:text-2xl"
|
onClick={onClose}
|
||||||
|
className="flex h-8 w-8 items-center justify-center rounded-full bg-transparent p-0 transition-colors hover:bg-gray-200"
|
||||||
|
aria-label="Close"
|
||||||
>
|
>
|
||||||
|
<X className="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<div className="px-4 text-center">
|
||||||
|
<h3 className="font-poppins text-lg font-semibold text-neutral-900 md:text-2xl">
|
||||||
Agent is awaiting review
|
Agent is awaiting review
|
||||||
</div>
|
</h3>
|
||||||
<div className="max-w-[280px] text-center font-inter text-sm font-normal leading-relaxed text-slate-500 dark:text-slate-400 sm:max-w-none">
|
<p className="hidden font-sans text-sm font-normal text-neutral-600 sm:flex">
|
||||||
In the meantime you can check your progress on your Creator
|
In the meantime you can check your progress on your Creator
|
||||||
Dashboard page
|
Dashboard page
|
||||||
</div>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<button
|
|
||||||
onClick={onClose}
|
|
||||||
className="absolute right-4 top-4 flex h-[38px] w-[38px] items-center justify-center rounded-full bg-gray-100 transition-colors hover:bg-gray-200 dark:bg-neutral-700 dark:hover:bg-neutral-600"
|
|
||||||
aria-label="Close dialog"
|
|
||||||
>
|
|
||||||
<IconClose
|
|
||||||
size="default"
|
|
||||||
className="text-neutral-600 dark:text-neutral-300"
|
|
||||||
/>
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex flex-1 flex-col items-center gap-8 px-6 py-6 sm:gap-6">
|
<div className="h-[50vh] flex-grow space-y-5 overflow-y-auto p-4 md:h-[38rem] md:p-6">
|
||||||
<div className="mt-4 flex w-full flex-col items-center gap-6 sm:mt-0 sm:gap-4">
|
<div className="mt-4 flex w-full flex-col items-center gap-6 sm:mt-0 sm:gap-4">
|
||||||
|
{/* Heading */}
|
||||||
<div className="flex flex-col items-center gap-3 sm:gap-2">
|
<div className="flex flex-col items-center gap-3 sm:gap-2">
|
||||||
<div className="text-center font-sans text-lg font-semibold leading-7 text-neutral-800 dark:text-neutral-200">
|
<div className="text-center font-sans text-lg font-semibold leading-7 text-neutral-800 dark:text-neutral-200">
|
||||||
{agentName}
|
{agentName}
|
||||||
@@ -68,6 +67,7 @@ export const PublishAgentAwaitingReview: React.FC<
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Image */}
|
||||||
<div
|
<div
|
||||||
className="h-[280px] w-full rounded-xl bg-neutral-200 dark:bg-neutral-700 sm:h-[350px]"
|
className="h-[280px] w-full rounded-xl bg-neutral-200 dark:bg-neutral-700 sm:h-[350px]"
|
||||||
role="img"
|
role="img"
|
||||||
@@ -86,8 +86,9 @@ export const PublishAgentAwaitingReview: React.FC<
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Description */}
|
||||||
<div
|
<div
|
||||||
className="h-[150px] w-full overflow-y-auto font-sans text-base font-normal leading-normal text-neutral-600 dark:text-neutral-400 sm:h-[180px]"
|
className="w-full whitespace-pre-line font-sans text-base font-normal text-neutral-600 dark:text-neutral-400"
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
role="region"
|
role="region"
|
||||||
aria-label="Agent description"
|
aria-label="Agent description"
|
||||||
@@ -100,13 +101,13 @@ export const PublishAgentAwaitingReview: React.FC<
|
|||||||
<div className="flex w-full flex-col items-center justify-center gap-4 border-t border-slate-200 p-6 dark:border-slate-700 sm:flex-row">
|
<div className="flex w-full flex-col items-center justify-center gap-4 border-t border-slate-200 p-6 dark:border-slate-700 sm:flex-row">
|
||||||
<Button
|
<Button
|
||||||
onClick={onDone}
|
onClick={onDone}
|
||||||
className="h-12 w-full rounded-[59px] sm:flex-1"
|
className="flex h-12 w-full items-center justify-center rounded-[59px] sm:flex-1"
|
||||||
>
|
>
|
||||||
Done
|
Done
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
onClick={onViewProgress}
|
onClick={onViewProgress}
|
||||||
className="h-12 w-full rounded-[59px] bg-neutral-800 text-white hover:bg-neutral-900 dark:bg-neutral-700 dark:text-neutral-100 dark:hover:bg-neutral-600 sm:flex-1"
|
className="flex h-12 w-full items-center justify-center rounded-[59px] bg-neutral-800 text-white hover:bg-neutral-900 dark:bg-neutral-700 dark:text-neutral-100 dark:hover:bg-neutral-600 sm:flex-1"
|
||||||
>
|
>
|
||||||
View progress
|
View progress
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -1,8 +1,16 @@
|
|||||||
import type { Meta, StoryObj } from "@storybook/react";
|
import type { Meta, StoryObj } from "@storybook/react";
|
||||||
import { Agent, PublishAgentSelect } from "./PublishAgentSelect";
|
import { Agent, PublishAgentSelect } from "./PublishAgentSelect";
|
||||||
|
import { userEvent, within, expect } from "@storybook/test";
|
||||||
|
|
||||||
const meta: Meta<typeof PublishAgentSelect> = {
|
const meta: Meta<typeof PublishAgentSelect> = {
|
||||||
title: "AGPT UI/Publish Agent Select",
|
title: "Agpt UI/marketing/Publish Agent Select",
|
||||||
|
decorators: [
|
||||||
|
(Story) => (
|
||||||
|
<div className="backdrop-blur-4 flex h-screen items-center justify-center bg-black/40">
|
||||||
|
<Story />
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
],
|
||||||
component: PublishAgentSelect,
|
component: PublishAgentSelect,
|
||||||
tags: ["autodocs"],
|
tags: ["autodocs"],
|
||||||
};
|
};
|
||||||
@@ -22,56 +30,56 @@ const mockAgents: Agent[] = [
|
|||||||
name: "Content Writer",
|
name: "Content Writer",
|
||||||
lastEdited: "5 days ago",
|
lastEdited: "5 days ago",
|
||||||
imageSrc: "https://picsum.photos/seed/writer/300/200",
|
imageSrc: "https://picsum.photos/seed/writer/300/200",
|
||||||
id: "1",
|
id: "2",
|
||||||
version: 1,
|
version: 1,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Data Analyzer",
|
name: "Data Analyzer",
|
||||||
lastEdited: "1 week ago",
|
lastEdited: "1 week ago",
|
||||||
imageSrc: "https://picsum.photos/seed/data/300/200",
|
imageSrc: "https://picsum.photos/seed/data/300/200",
|
||||||
id: "1",
|
id: "3",
|
||||||
version: 1,
|
version: 1,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Image Recognition",
|
name: "Image Recognition",
|
||||||
lastEdited: "2 weeks ago",
|
lastEdited: "2 weeks ago",
|
||||||
imageSrc: "https://picsum.photos/seed/image/300/200",
|
imageSrc: "https://picsum.photos/seed/image/300/200",
|
||||||
id: "1",
|
id: "9",
|
||||||
version: 1,
|
version: 1,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Chatbot Assistant",
|
name: "Chatbot Assistant",
|
||||||
lastEdited: "3 weeks ago",
|
lastEdited: "3 weeks ago",
|
||||||
imageSrc: "https://picsum.photos/seed/chat/300/200",
|
imageSrc: "https://picsum.photos/seed/chat/300/200",
|
||||||
id: "1",
|
id: "4",
|
||||||
version: 1,
|
version: 1,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Code Generator",
|
name: "Code Generator",
|
||||||
lastEdited: "1 month ago",
|
lastEdited: "1 month ago",
|
||||||
imageSrc: "https://picsum.photos/seed/code/300/200",
|
imageSrc: "https://picsum.photos/seed/code/300/200",
|
||||||
id: "1",
|
id: "5",
|
||||||
version: 1,
|
version: 1,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "AI Translator",
|
name: "AI Translator",
|
||||||
lastEdited: "6 weeks ago",
|
lastEdited: "6 weeks ago",
|
||||||
imageSrc: "https://picsum.photos/seed/translate/300/200",
|
imageSrc: "https://picsum.photos/seed/translate/300/200",
|
||||||
id: "1",
|
id: "6",
|
||||||
version: 1,
|
version: 1,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Voice Assistant",
|
name: "Voice Assistant",
|
||||||
lastEdited: "2 months ago",
|
lastEdited: "2 months ago",
|
||||||
imageSrc: "https://picsum.photos/seed/voice/300/200",
|
imageSrc: "https://picsum.photos/seed/voice/300/200",
|
||||||
id: "1",
|
id: "7",
|
||||||
version: 1,
|
version: 1,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Data Visualizer",
|
name: "Data Visualizer",
|
||||||
lastEdited: "3 months ago",
|
lastEdited: "3 months ago",
|
||||||
imageSrc: "https://picsum.photos/seed/visualize/300/200",
|
imageSrc: "https://picsum.photos/seed/visualize/300/200",
|
||||||
id: "1",
|
id: "8",
|
||||||
version: 1,
|
version: 1,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
@@ -104,16 +112,20 @@ export const SingleAgent: Story = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const SixAgents: Story = {
|
export const TestingInteractions: Story = {
|
||||||
args: {
|
|
||||||
...defaultArgs,
|
|
||||||
agents: mockAgents.slice(0, 6),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export const NineAgents: Story = {
|
|
||||||
args: {
|
args: {
|
||||||
...defaultArgs,
|
...defaultArgs,
|
||||||
agents: mockAgents,
|
agents: mockAgents,
|
||||||
},
|
},
|
||||||
|
play: async ({ canvasElement }) => {
|
||||||
|
const canvas = within(canvasElement);
|
||||||
|
|
||||||
|
// Select an agent
|
||||||
|
const agentCard = canvas.getByText("SEO Optimizer");
|
||||||
|
await userEvent.click(agentCard);
|
||||||
|
|
||||||
|
// Click next button
|
||||||
|
const nextButton = canvas.getByText(/next/i);
|
||||||
|
await userEvent.click(nextButton);
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import { Button } from "../agptui/Button";
|
import { Button } from "../agptui/Button";
|
||||||
import { IconClose } from "../ui/icons";
|
import { X } from "lucide-react";
|
||||||
|
|
||||||
export interface Agent {
|
export interface Agent {
|
||||||
name: string;
|
name: string;
|
||||||
@@ -48,32 +48,30 @@ export const PublishAgentSelect: React.FC<PublishAgentSelectProps> = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mx-auto flex w-full max-w-[900px] flex-col rounded-3xl bg-white shadow-lg dark:bg-gray-800">
|
<div className="m-auto flex h-fit w-full max-w-[900px] flex-col rounded-3xl bg-white shadow-lg dark:bg-gray-800">
|
||||||
<div className="relative border-b border-slate-200 p-4 dark:border-slate-700 sm:p-6">
|
{/* Top */}
|
||||||
|
<div className="relative flex h-28 items-center justify-center border-b border-slate-200 dark:border-slate-700">
|
||||||
<div className="absolute right-4 top-4">
|
<div className="absolute right-4 top-4">
|
||||||
<button
|
<Button
|
||||||
onClick={onClose}
|
onClick={onClose}
|
||||||
className="flex h-8 w-8 items-center justify-center rounded-full bg-gray-100 transition-colors hover:bg-gray-200 dark:bg-gray-700 dark:hover:bg-gray-600"
|
className="flex h-8 w-8 items-center justify-center rounded-full bg-transparent p-0 transition-colors hover:bg-gray-200"
|
||||||
aria-label="Close"
|
aria-label="Close"
|
||||||
>
|
>
|
||||||
<IconClose
|
<X className="h-4 w-4" />
|
||||||
size="default"
|
</Button>
|
||||||
className="text-neutral-600 dark:text-neutral-400"
|
|
||||||
/>
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
<h3 className="font-poppins text-2xl font-semibold text-neutral-900 dark:text-neutral-100">
|
<h3 className="font-poppins text-2xl font-semibold text-neutral-900">
|
||||||
Publish Agent
|
Publish Agent
|
||||||
</h3>
|
</h3>
|
||||||
<p className="font-geist text-sm font-normal text-neutral-600 dark:text-neutral-400">
|
<p className="font-sans text-base font-normal text-neutral-600">
|
||||||
Select your project that you'd like to publish
|
Select your project that you'd like to publish
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{agents.length === 0 ? (
|
{agents.length === 0 ? (
|
||||||
<div className="inline-flex h-[370px] flex-col items-center justify-center gap-[29px] px-4 py-5 sm:px-6">
|
<div className="inline-flex h-96 flex-col items-center justify-center gap-[29px] px-4 py-5 sm:px-6">
|
||||||
<div className="w-full text-center font-sans text-lg font-normal leading-7 text-neutral-600 dark:text-neutral-400 sm:w-[573px] sm:text-xl">
|
<div className="w-full text-center font-sans text-lg font-normal leading-7 text-neutral-600 dark:text-neutral-400 sm:w-[573px] sm:text-xl">
|
||||||
Uh-oh.. It seems like you don't have any agents in your
|
Uh-oh.. It seems like you don't have any agents in your
|
||||||
library.
|
library.
|
||||||
@@ -90,10 +88,10 @@ export const PublishAgentSelect: React.FC<PublishAgentSelectProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<div className="flex-grow overflow-hidden p-4 sm:p-6">
|
<div className="flex-grow overflow-hidden">
|
||||||
<h3 className="sr-only">List of agents</h3>
|
<h3 className="sr-only">List of agents</h3>
|
||||||
<div
|
<div
|
||||||
className="h-[300px] overflow-y-auto pr-2 sm:h-[400px] md:h-[500px]"
|
className="h-72 overflow-y-auto px-6 py-6 sm:h-[400px] md:h-[500px]"
|
||||||
role="region"
|
role="region"
|
||||||
aria-labelledby="agentListHeading"
|
aria-labelledby="agentListHeading"
|
||||||
>
|
>
|
||||||
@@ -135,7 +133,7 @@ export const PublishAgentSelect: React.FC<PublishAgentSelectProps> = ({
|
|||||||
<p className="font-poppins text-base font-medium leading-normal text-neutral-800 dark:text-neutral-100 sm:text-base">
|
<p className="font-poppins text-base font-medium leading-normal text-neutral-800 dark:text-neutral-100 sm:text-base">
|
||||||
{agent.name}
|
{agent.name}
|
||||||
</p>
|
</p>
|
||||||
<small className="font-geist text-xs font-normal leading-[14px] text-neutral-500 dark:text-neutral-400 sm:text-sm">
|
<small className="font-sans text-xs font-normal leading-[14px] text-neutral-500 dark:text-neutral-400 sm:text-sm">
|
||||||
Edited {agent.lastEdited}
|
Edited {agent.lastEdited}
|
||||||
</small>
|
</small>
|
||||||
</div>
|
</div>
|
||||||
@@ -147,7 +145,11 @@ export const PublishAgentSelect: React.FC<PublishAgentSelectProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex justify-between gap-4 border-t border-slate-200 p-4 dark:border-slate-700 sm:p-6">
|
<div className="flex justify-between gap-4 border-t border-slate-200 p-4 dark:border-slate-700 sm:p-6">
|
||||||
<Button onClick={onCancel} size="lg" className="w-full sm:flex-1">
|
<Button
|
||||||
|
onClick={onCancel}
|
||||||
|
size="lg"
|
||||||
|
className="flex w-full items-center justify-center sm:flex-1"
|
||||||
|
>
|
||||||
Back
|
Back
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
@@ -158,7 +160,7 @@ export const PublishAgentSelect: React.FC<PublishAgentSelectProps> = ({
|
|||||||
}}
|
}}
|
||||||
disabled={!selectedAgentId || !selectedAgentVersion}
|
disabled={!selectedAgentId || !selectedAgentVersion}
|
||||||
size="lg"
|
size="lg"
|
||||||
className="w-full bg-neutral-800 text-white hover:bg-neutral-900 sm:flex-1"
|
className="flex w-full items-center justify-center bg-neutral-800 text-white hover:bg-neutral-900 sm:flex-1"
|
||||||
>
|
>
|
||||||
Next
|
Next
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -1,17 +1,23 @@
|
|||||||
import type { Meta, StoryObj } from "@storybook/react";
|
import type { Meta, StoryObj } from "@storybook/react";
|
||||||
import { PublishAgentInfo } from "./PublishAgentSelectInfo";
|
import { PublishAgentInfo } from "./PublishAgentSelectInfo";
|
||||||
|
import { expect, userEvent, within } from "@storybook/test";
|
||||||
|
|
||||||
const meta: Meta<typeof PublishAgentInfo> = {
|
const meta: Meta<typeof PublishAgentInfo> = {
|
||||||
title: "AGPT UI/Publish Agent Info",
|
title: "Agpt UI/marketing/Publish Agent Select Info",
|
||||||
component: PublishAgentInfo,
|
component: PublishAgentInfo,
|
||||||
tags: ["autodocs"],
|
tags: ["autodocs"],
|
||||||
decorators: [
|
decorators: [
|
||||||
(Story) => (
|
(Story) => (
|
||||||
<div style={{ maxWidth: "670px", margin: "0 auto" }}>
|
<div className="backdrop-blur-4 flex h-screen items-center justify-center bg-black/40">
|
||||||
<Story />
|
<Story />
|
||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
argTypes: {
|
||||||
|
onBack: { action: "back clicked" },
|
||||||
|
onSubmit: { action: "submit clicked" },
|
||||||
|
onClose: { action: "close clicked" },
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export default meta;
|
export default meta;
|
||||||
@@ -23,6 +29,13 @@ export const Default: Story = {
|
|||||||
onSubmit: () => console.log("Submit clicked"),
|
onSubmit: () => console.log("Submit clicked"),
|
||||||
onClose: () => console.log("Close clicked"),
|
onClose: () => console.log("Close clicked"),
|
||||||
},
|
},
|
||||||
|
play: async ({ canvasElement }) => {
|
||||||
|
const canvas = within(canvasElement);
|
||||||
|
const titleInput = canvas.getByLabelText(/title/i);
|
||||||
|
|
||||||
|
await userEvent.type(titleInput, "Test Agent");
|
||||||
|
await expect(titleInput).toHaveValue("Test Agent");
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Filled: Story = {
|
export const Filled: Story = {
|
||||||
@@ -35,7 +48,7 @@ export const Filled: Story = {
|
|||||||
subheader: "Boost your website's search engine rankings",
|
subheader: "Boost your website's search engine rankings",
|
||||||
thumbnailSrc: "https://picsum.photos/seed/seo/500/350",
|
thumbnailSrc: "https://picsum.photos/seed/seo/500/350",
|
||||||
youtubeLink: "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
|
youtubeLink: "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
|
||||||
category: "SEO",
|
category: "marketing",
|
||||||
description:
|
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.",
|
"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.",
|
||||||
},
|
},
|
||||||
@@ -52,7 +65,7 @@ export const ThreeImages: Story = {
|
|||||||
subheader: "Showcasing multiple images",
|
subheader: "Showcasing multiple images",
|
||||||
thumbnailSrc: "https://picsum.photos/seed/initial/500/350",
|
thumbnailSrc: "https://picsum.photos/seed/initial/500/350",
|
||||||
youtubeLink: "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
|
youtubeLink: "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
|
||||||
category: "SEO",
|
category: "marketing",
|
||||||
description:
|
description:
|
||||||
"This agent allows you to upload and manage multiple images.",
|
"This agent allows you to upload and manage multiple images.",
|
||||||
additionalImages: [
|
additionalImages: [
|
||||||
@@ -63,24 +76,22 @@ export const ThreeImages: Story = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const SixImages: Story = {
|
export const MaxImages: Story = {
|
||||||
args: {
|
args: {
|
||||||
...Default.args,
|
...Default.args,
|
||||||
initialData: {
|
initialData: {
|
||||||
agent_id: "1",
|
agent_id: "1",
|
||||||
slug: "super-seo-optimizer",
|
slug: "super-seo-optimizer",
|
||||||
title: "Gallery Agent",
|
title: "Gallery Agent",
|
||||||
subheader: "Showcasing a gallery of images",
|
subheader: "Showcasing maximum allowed images",
|
||||||
thumbnailSrc: "https://picsum.photos/seed/gallery1/500/350",
|
thumbnailSrc: "https://picsum.photos/seed/gallery1/500/350",
|
||||||
youtubeLink: "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
|
youtubeLink: "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
|
||||||
category: "SEO",
|
category: "marketing",
|
||||||
description: "This agent displays a gallery of six images.",
|
description: "This agent displays the maximum number of allowed images.",
|
||||||
additionalImages: [
|
additionalImages: [
|
||||||
"https://picsum.photos/seed/gallery2/500/350",
|
"https://picsum.photos/seed/gallery2/500/350",
|
||||||
"https://picsum.photos/seed/gallery3/500/350",
|
"https://picsum.photos/seed/gallery3/500/350",
|
||||||
"https://picsum.photos/seed/gallery4/500/350",
|
"https://picsum.photos/seed/gallery4/500/350",
|
||||||
"https://picsum.photos/seed/gallery5/500/350",
|
|
||||||
"https://picsum.photos/seed/gallery6/500/350",
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -6,6 +6,17 @@ import { Button } from "../agptui/Button";
|
|||||||
import { IconClose, IconPlus } from "../ui/icons";
|
import { IconClose, IconPlus } from "../ui/icons";
|
||||||
import BackendAPI from "@/lib/autogpt-server-api";
|
import BackendAPI from "@/lib/autogpt-server-api";
|
||||||
import { toast } from "../ui/use-toast";
|
import { toast } from "../ui/use-toast";
|
||||||
|
import { X } from "lucide-react";
|
||||||
|
import { Input } from "../ui/input";
|
||||||
|
import { Label } from "../ui/label";
|
||||||
|
import {
|
||||||
|
Select,
|
||||||
|
SelectContent,
|
||||||
|
SelectItem,
|
||||||
|
SelectTrigger,
|
||||||
|
SelectValue,
|
||||||
|
} from "../ui/select";
|
||||||
|
import { Textarea } from "../ui/textarea";
|
||||||
|
|
||||||
export interface PublishAgentInfoInitialData {
|
export interface PublishAgentInfoInitialData {
|
||||||
agent_id: string;
|
agent_id: string;
|
||||||
@@ -165,85 +176,88 @@ export const PublishAgentInfo: React.FC<PublishAgentInfoProps> = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mx-auto flex w-full flex-col rounded-3xl bg-white dark:bg-gray-800">
|
<div className="mx-auto flex h-fit w-full max-w-2xl flex-col rounded-3xl bg-white">
|
||||||
<div className="relative p-6">
|
{/* Top section */}
|
||||||
<div className="absolute right-4 top-2">
|
<div className="relative flex h-28 items-center justify-center border-b border-slate-200 dark:border-slate-700">
|
||||||
<button
|
{/* Cancel Button */}
|
||||||
|
<div className="absolute right-4 top-4">
|
||||||
|
<Button
|
||||||
onClick={onClose}
|
onClick={onClose}
|
||||||
className="flex h-[38px] w-[38px] items-center justify-center rounded-full bg-gray-100 transition-colors hover:bg-gray-200 dark:bg-gray-700 dark:hover:bg-gray-600"
|
className="flex h-8 w-8 items-center justify-center rounded-full bg-transparent p-0 transition-colors hover:bg-gray-200"
|
||||||
aria-label="Close"
|
aria-label="Close"
|
||||||
>
|
>
|
||||||
<IconClose
|
<X className="h-4 w-4" />
|
||||||
size="default"
|
</Button>
|
||||||
className="text-neutral-600 dark:text-neutral-300"
|
</div>
|
||||||
/>
|
{/* Content */}
|
||||||
</button>
|
<div className="text-center">
|
||||||
|
<h3 className="font-poppins text-2xl font-semibold text-neutral-900">
|
||||||
|
Publish Agent
|
||||||
|
</h3>
|
||||||
|
<p className="font-sans text-base font-normal text-neutral-600">
|
||||||
|
Write a bit of details about your agent{" "}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<h3 className="h3-poppins text-center text-2xl font-semibold leading-loose text-neutral-900 dark:text-neutral-100">
|
|
||||||
Publish Agent
|
|
||||||
</h3>
|
|
||||||
<p className="p text-center text-base font-normal leading-7 text-neutral-600 dark:text-neutral-400">
|
|
||||||
Write a bit of details about your agent
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex-grow space-y-5 overflow-y-auto p-6">
|
{/* Form fields */}
|
||||||
|
<div className="h-[50vh] flex-grow space-y-5 overflow-y-auto p-4 md:h-[38rem] md:p-6">
|
||||||
<div className="space-y-1.5">
|
<div className="space-y-1.5">
|
||||||
<label
|
<Label
|
||||||
htmlFor="title"
|
htmlFor="title"
|
||||||
className="text-sm font-medium leading-tight text-slate-950 dark:text-slate-300"
|
className="font-sans text-sm font-medium text-[#020617]"
|
||||||
>
|
>
|
||||||
Title
|
Title
|
||||||
</label>
|
</Label>
|
||||||
<input
|
<Input
|
||||||
id="title"
|
id="title"
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="Agent name"
|
placeholder="Agent name"
|
||||||
value={title}
|
value={title}
|
||||||
onChange={(e) => setTitle(e.target.value)}
|
onChange={(e) => setTitle(e.target.value)}
|
||||||
className="p-ui-medium w-full rounded-[55px] border border-slate-200 py-2.5 pl-4 pr-14 text-base font-normal leading-normal text-slate-500 dark:border-slate-700 dark:bg-gray-700 dark:text-slate-300"
|
className="h-11 rounded-full border border-[#E2E8F0] px-4 py-2.5 font-sans text-sm text-neutral-500 md:text-base"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-1.5">
|
<div className="space-y-1.5">
|
||||||
<label
|
<Label
|
||||||
htmlFor="subheader"
|
htmlFor="subheader"
|
||||||
className="text-sm font-medium leading-tight text-slate-950 dark:text-slate-300"
|
className="font-sans text-sm font-medium text-[#020617]"
|
||||||
>
|
>
|
||||||
Subheader
|
Subheader
|
||||||
</label>
|
</Label>
|
||||||
<input
|
<Input
|
||||||
id="subheader"
|
id="subheader"
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="A tagline for your agent"
|
placeholder="A tagline for your agent"
|
||||||
value={subheader}
|
value={subheader}
|
||||||
onChange={(e) => setSubheader(e.target.value)}
|
onChange={(e) => setSubheader(e.target.value)}
|
||||||
className="w-full rounded-[55px] border border-slate-200 py-2.5 pl-4 pr-14 font-sans text-base font-normal leading-normal text-slate-500 dark:border-slate-700 dark:bg-gray-700 dark:text-slate-300"
|
className="h-11 rounded-full border border-[#E2E8F0] px-4 py-2.5 font-sans text-sm text-neutral-500 md:text-base"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-1.5">
|
<div className="space-y-1.5">
|
||||||
<label
|
<Label
|
||||||
htmlFor="slug"
|
htmlFor="slug"
|
||||||
className="text-sm font-medium leading-tight text-slate-950 dark:text-slate-300"
|
className="font-sans text-sm font-medium text-[#020617]"
|
||||||
>
|
>
|
||||||
Slug
|
Slug
|
||||||
</label>
|
</Label>
|
||||||
<input
|
<Input
|
||||||
id="slug"
|
id="slug"
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="URL-friendly name for your agent"
|
placeholder="URL-friendly name for your agent"
|
||||||
value={slug}
|
value={slug}
|
||||||
onChange={(e) => setSlug(e.target.value)}
|
onChange={(e) => setSlug(e.target.value)}
|
||||||
className="w-full rounded-[55px] border border-slate-200 py-2.5 pl-4 pr-14 font-sans text-base font-normal leading-normal text-slate-500 dark:border-slate-700 dark:bg-gray-700 dark:text-slate-300"
|
className="h-11 rounded-full border border-[#E2E8F0] px-4 py-2.5 font-sans text-sm text-neutral-500 md:text-base"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-2.5">
|
<div className="space-y-2.5">
|
||||||
<label className="text-sm font-medium leading-tight text-slate-950 dark:text-slate-300">
|
<Label className="font-sans text-sm font-medium text-[#020617]">
|
||||||
Thumbnail images
|
Thumbnail images
|
||||||
</label>
|
</Label>
|
||||||
<div className="flex h-[350px] items-center justify-center overflow-hidden rounded-[20px] border border-neutral-300 p-2.5 dark:border-neutral-600">
|
<div className="flex h-[350px] items-center justify-center overflow-hidden rounded-[20px] border border-dashed border-neutral-300 p-2.5 dark:border-neutral-600">
|
||||||
{selectedImage !== null && selectedImage !== undefined ? (
|
{selectedImage !== null && selectedImage !== undefined ? (
|
||||||
<Image
|
<Image
|
||||||
src={selectedImage}
|
src={selectedImage}
|
||||||
@@ -268,10 +282,13 @@ export const PublishAgentInfo: React.FC<PublishAgentInfoProps> = ({
|
|||||||
<Button
|
<Button
|
||||||
onClick={handleAddImage}
|
onClick={handleAddImage}
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
className="flex h-[70px] w-[100px] flex-col items-center justify-center rounded-md bg-neutral-200 hover:bg-neutral-300 dark:bg-neutral-700 dark:hover:bg-neutral-600"
|
className="h-[70px] w-[100px] flex-col items-center justify-center rounded-md bg-neutral-200 hover:bg-neutral-300 dark:bg-neutral-700 dark:hover:bg-neutral-600"
|
||||||
>
|
>
|
||||||
<label htmlFor="image-upload" className="cursor-pointer">
|
<Label
|
||||||
<input
|
htmlFor="image-upload"
|
||||||
|
className="flex flex-col items-center justify-center font-sans text-sm font-medium text-[#020617]"
|
||||||
|
>
|
||||||
|
<Input
|
||||||
id="image-upload"
|
id="image-upload"
|
||||||
type="file"
|
type="file"
|
||||||
accept="image/*"
|
accept="image/*"
|
||||||
@@ -282,14 +299,14 @@ export const PublishAgentInfo: React.FC<PublishAgentInfoProps> = ({
|
|||||||
size="lg"
|
size="lg"
|
||||||
className="text-neutral-600 dark:text-neutral-300"
|
className="text-neutral-600 dark:text-neutral-300"
|
||||||
/>
|
/>
|
||||||
<span className="mt-1 font-sans text-xs font-normal text-neutral-600 dark:text-neutral-300">
|
<span className="mt-1 font-sans text-sm font-normal text-neutral-600 dark:text-neutral-300">
|
||||||
Add image
|
Add image
|
||||||
</span>
|
</span>
|
||||||
</label>
|
</Label>
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<div className="flex flex-wrap gap-2.5">
|
||||||
{images.map((src, index) => (
|
{images.map((src, index) => (
|
||||||
<div key={index} className="relative flex-shrink-0">
|
<div key={index} className="relative flex-shrink-0">
|
||||||
<Image
|
<Image
|
||||||
@@ -328,21 +345,21 @@ export const PublishAgentInfo: React.FC<PublishAgentInfoProps> = ({
|
|||||||
</span>
|
</span>
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
</>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-1.5">
|
<div className="space-y-1.5">
|
||||||
<label className="text-sm font-medium leading-tight text-slate-950 dark:text-slate-300">
|
<Label className="font-sans text-sm font-medium text-[#020617]">
|
||||||
AI image generator
|
AI image generator
|
||||||
</label>
|
</Label>
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex flex-col justify-between gap-2 md:flex-row md:items-center">
|
||||||
<p className="text-base font-normal leading-normal text-slate-700 dark:text-slate-400">
|
<p className="font-sans text-sm text-neutral-500 md:text-base">
|
||||||
You can use AI to generate a cover image for you
|
You can use AI to generate a cover image for you
|
||||||
</p>
|
</p>
|
||||||
<Button
|
<Button
|
||||||
className={`bg-neutral-800 text-white hover:bg-neutral-900 dark:bg-neutral-600 dark:hover:bg-neutral-500 ${
|
className={`w-fit bg-neutral-800 font-sans text-white hover:bg-neutral-900 dark:bg-neutral-600 dark:hover:bg-neutral-500 ${
|
||||||
images.length >= 5 ? "cursor-not-allowed opacity-50" : ""
|
images.length >= 5 ? "cursor-not-allowed opacity-50" : ""
|
||||||
}`}
|
}`}
|
||||||
onClick={handleGenerateImage}
|
onClick={handleGenerateImage}
|
||||||
@@ -358,79 +375,78 @@ export const PublishAgentInfo: React.FC<PublishAgentInfoProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-1.5">
|
<div className="space-y-1.5">
|
||||||
<label
|
<Label
|
||||||
htmlFor="youtube"
|
htmlFor="youtube"
|
||||||
className="text-sm font-medium leading-tight text-slate-950 dark:text-slate-300"
|
className="font-sans text-sm font-medium text-[#020617]"
|
||||||
>
|
>
|
||||||
YouTube video link
|
YouTube video link
|
||||||
</label>
|
</Label>
|
||||||
<input
|
<Input
|
||||||
id="youtube"
|
id="youtube"
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="Paste a video link here"
|
placeholder="Paste a video link here"
|
||||||
value={youtubeLink}
|
value={youtubeLink}
|
||||||
onChange={(e) => setYoutubeLink(e.target.value)}
|
onChange={(e) => setYoutubeLink(e.target.value)}
|
||||||
className="w-full rounded-[55px] border border-slate-200 py-2.5 pl-4 pr-14 font-sans text-base font-normal leading-normal text-slate-500 dark:border-slate-700 dark:bg-gray-700 dark:text-slate-300"
|
className="h-11 rounded-full border border-[#E2E8F0] px-4 py-2.5 font-sans text-sm text-neutral-500 md:text-base"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-1.5">
|
<div className="space-y-1.5">
|
||||||
<label
|
<Label
|
||||||
htmlFor="category"
|
htmlFor="category"
|
||||||
className="text-sm font-medium leading-tight text-slate-950 dark:text-slate-300"
|
className="font-sans text-sm font-medium text-[#020617]"
|
||||||
>
|
>
|
||||||
Category
|
Category
|
||||||
</label>
|
</Label>
|
||||||
<select
|
<Select value={category} onValueChange={setCategory}>
|
||||||
id="category"
|
<SelectTrigger className="h-11 rounded-full border border-[#E2E8F0] px-4 py-2.5 font-sans text-sm text-neutral-500 md:text-base">
|
||||||
value={category}
|
<SelectValue placeholder="Select a category for your agent" />
|
||||||
onChange={(e) => setCategory(e.target.value)}
|
</SelectTrigger>
|
||||||
className="w-full appearance-none rounded-[55px] border border-slate-200 py-2.5 pl-4 pr-5 font-sans text-base font-normal leading-normal text-slate-500 dark:border-slate-700 dark:bg-gray-700 dark:text-slate-300"
|
<SelectContent className="font-sans">
|
||||||
>
|
<SelectItem value="productivity">Productivity</SelectItem>
|
||||||
<option value="">Select a category for your agent</option>
|
<SelectItem value="writing">Writing & Content</SelectItem>
|
||||||
<option value="productivity">Productivity</option>
|
<SelectItem value="development">Development</SelectItem>
|
||||||
<option value="writing">Writing & Content</option>
|
<SelectItem value="data">Data & Analytics</SelectItem>
|
||||||
<option value="development">Development</option>
|
<SelectItem value="marketing">Marketing & SEO</SelectItem>
|
||||||
<option value="data">Data & Analytics</option>
|
<SelectItem value="research">Research & Learning</SelectItem>
|
||||||
<option value="marketing">Marketing & SEO</option>
|
<SelectItem value="creative">Creative & Design</SelectItem>
|
||||||
<option value="research">Research & Learning</option>
|
<SelectItem value="business">Business & Finance</SelectItem>
|
||||||
<option value="creative">Creative & Design</option>
|
<SelectItem value="personal">Personal Assistant</SelectItem>
|
||||||
<option value="business">Business & Finance</option>
|
<SelectItem value="other">Other</SelectItem>
|
||||||
<option value="personal">Personal Assistant</option>
|
</SelectContent>
|
||||||
<option value="other">Other</option>
|
</Select>
|
||||||
{/* Add more options here */}
|
|
||||||
</select>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-1.5">
|
<div className="space-y-1.5">
|
||||||
<label
|
<Label
|
||||||
htmlFor="description"
|
htmlFor="description"
|
||||||
className="text-sm font-medium leading-tight text-slate-950 dark:text-slate-300"
|
className="font-sans text-sm font-medium text-[#020617]"
|
||||||
>
|
>
|
||||||
Description
|
Description
|
||||||
</label>
|
</Label>
|
||||||
<textarea
|
<Textarea
|
||||||
id="description"
|
id="description"
|
||||||
placeholder="Describe your agent and what it does"
|
placeholder="Describe your agent and what it does"
|
||||||
value={description}
|
value={description}
|
||||||
onChange={(e) => setDescription(e.target.value)}
|
onChange={(e) => setDescription(e.target.value)}
|
||||||
className="h-[100px] w-full resize-none rounded-2xl border border-slate-200 bg-white py-2.5 pl-4 pr-14 font-sans text-base font-normal leading-normal text-slate-900 dark:border-slate-700 dark:bg-gray-700 dark:text-slate-300"
|
className="h-[100px] w-full resize-none rounded-2xl border border-[#E2E8F0] px-4 py-2.5 font-sans text-sm text-neutral-500 md:text-base"
|
||||||
></textarea>
|
></Textarea>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Bottom buttons */}
|
||||||
<div className="flex justify-between gap-4 border-t border-slate-200 p-6 dark:border-slate-700">
|
<div className="flex justify-between gap-4 border-t border-slate-200 p-6 dark:border-slate-700">
|
||||||
<Button
|
<Button
|
||||||
onClick={onBack}
|
onClick={onBack}
|
||||||
size="lg"
|
size="lg"
|
||||||
className="w-full dark:border-slate-700 dark:text-slate-300 sm:flex-1"
|
className="flex w-full items-center justify-center text-sm dark:border-slate-700 dark:text-slate-300 sm:flex-1 md:text-base"
|
||||||
>
|
>
|
||||||
Back
|
Back
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
onClick={handleSubmit}
|
onClick={handleSubmit}
|
||||||
size="lg"
|
size="lg"
|
||||||
className="w-full bg-neutral-800 text-white hover:bg-neutral-900 dark:bg-neutral-600 dark:hover:bg-neutral-500 sm:flex-1"
|
className="flex w-full items-center justify-center bg-neutral-800 text-sm text-white hover:bg-neutral-900 dark:bg-neutral-600 dark:hover:bg-neutral-500 sm:flex-1 md:text-base"
|
||||||
>
|
>
|
||||||
Submit for review
|
Submit for review
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -1,47 +0,0 @@
|
|||||||
import type { Meta, StoryObj } from "@storybook/react";
|
|
||||||
import { RatingCard } from "./RatingCard";
|
|
||||||
|
|
||||||
const meta = {
|
|
||||||
title: "AGPT UI/RatingCard",
|
|
||||||
component: RatingCard,
|
|
||||||
parameters: {
|
|
||||||
layout: "centered",
|
|
||||||
},
|
|
||||||
tags: ["autodocs"],
|
|
||||||
} satisfies Meta<typeof RatingCard>;
|
|
||||||
|
|
||||||
export default meta;
|
|
||||||
type Story = StoryObj<typeof meta>;
|
|
||||||
|
|
||||||
export const Default: Story = {
|
|
||||||
args: {
|
|
||||||
agentName: "Test Agent",
|
|
||||||
// onSubmit: (rating) => {
|
|
||||||
// console.log("Rating submitted:", rating);
|
|
||||||
// },
|
|
||||||
// onClose: () => {
|
|
||||||
// console.log("Rating card closed");
|
|
||||||
// },
|
|
||||||
storeListingVersionId: "1",
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export const LongAgentName: Story = {
|
|
||||||
args: {
|
|
||||||
agentName: "Very Long Agent Name That Might Need Special Handling",
|
|
||||||
// onSubmit: (rating) => {
|
|
||||||
// console.log("Rating submitted:", rating);
|
|
||||||
// },
|
|
||||||
// onClose: () => {
|
|
||||||
// console.log("Rating card closed");
|
|
||||||
// },
|
|
||||||
storeListingVersionId: "1",
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export const WithoutCallbacks: Story = {
|
|
||||||
args: {
|
|
||||||
agentName: "Test Agent",
|
|
||||||
storeListingVersionId: "1",
|
|
||||||
},
|
|
||||||
};
|
|
||||||
@@ -3,30 +3,13 @@ import { SearchBar } from "./SearchBar";
|
|||||||
import { userEvent, within, expect } from "@storybook/test";
|
import { userEvent, within, expect } from "@storybook/test";
|
||||||
|
|
||||||
const meta = {
|
const meta = {
|
||||||
title: "AGPT UI/Search Bar",
|
title: "Agpt UI/marketing/Search Bar",
|
||||||
component: SearchBar,
|
component: SearchBar,
|
||||||
parameters: {
|
|
||||||
layout: {
|
|
||||||
center: true,
|
|
||||||
padding: 0,
|
|
||||||
},
|
|
||||||
nextjs: {
|
|
||||||
appDirectory: true,
|
|
||||||
navigation: {
|
|
||||||
pathname: "/search",
|
|
||||||
query: {
|
|
||||||
searchTerm: "",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
tags: ["autodocs"],
|
tags: ["autodocs"],
|
||||||
argTypes: {
|
argTypes: {
|
||||||
placeholder: { control: "text" },
|
placeholder: { control: "text" },
|
||||||
backgroundColor: { control: "text" },
|
className: { control: "text" },
|
||||||
iconColor: { control: "text" },
|
|
||||||
textColor: { control: "text" },
|
|
||||||
placeholderColor: { control: "text" },
|
|
||||||
},
|
},
|
||||||
decorators: [
|
decorators: [
|
||||||
(Story) => (
|
(Story) => (
|
||||||
@@ -49,38 +32,25 @@ export const Default: Story = {
|
|||||||
export const CustomStyles: Story = {
|
export const CustomStyles: Story = {
|
||||||
args: {
|
args: {
|
||||||
placeholder: "Enter your search query",
|
placeholder: "Enter your search query",
|
||||||
backgroundColor: "bg-blue-100",
|
className: "bg-blue-100",
|
||||||
iconColor: "text-blue-500",
|
|
||||||
textColor: "text-blue-700",
|
|
||||||
placeholderColor: "text-blue-400",
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const WithInteraction: Story = {
|
export const TestingInteractions: Story = {
|
||||||
args: {
|
args: {
|
||||||
placeholder: "Type and press Enter",
|
placeholder: "Type and press Enter",
|
||||||
},
|
},
|
||||||
play: async ({ canvasElement }) => {
|
play: async ({ canvasElement }) => {
|
||||||
const canvas = within(canvasElement);
|
const canvas = within(canvasElement);
|
||||||
const input = canvas.getByPlaceholderText("Type and press Enter");
|
|
||||||
|
|
||||||
await userEvent.type(input, "test query");
|
// checking onChange in input
|
||||||
await userEvent.keyboard("{Enter}");
|
const Input = canvas.getByTestId("store-search-input");
|
||||||
|
await userEvent.type(Input, "test query", {
|
||||||
await expect(input).toHaveValue("test query");
|
delay: 100,
|
||||||
},
|
});
|
||||||
};
|
await userEvent.keyboard("{Enter}", {
|
||||||
|
delay: 100,
|
||||||
export const EmptySubmit: Story = {
|
});
|
||||||
args: {
|
await expect(Input).toHaveValue("test query");
|
||||||
placeholder: "Empty submit test",
|
|
||||||
},
|
|
||||||
play: async ({ canvasElement }) => {
|
|
||||||
const canvas = within(canvasElement);
|
|
||||||
const input = canvas.getByPlaceholderText("Empty submit test");
|
|
||||||
|
|
||||||
await userEvent.keyboard("{Enter}");
|
|
||||||
|
|
||||||
await expect(input).toHaveValue("");
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -4,26 +4,18 @@ import * as React from "react";
|
|||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
|
|
||||||
import { MagnifyingGlassIcon } from "@radix-ui/react-icons";
|
import { MagnifyingGlassIcon } from "@radix-ui/react-icons";
|
||||||
|
import { Input } from "../ui/input";
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
interface SearchBarProps {
|
interface SearchBarProps {
|
||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
backgroundColor?: string;
|
className?: string;
|
||||||
iconColor?: string;
|
|
||||||
textColor?: string;
|
|
||||||
placeholderColor?: string;
|
|
||||||
width?: string;
|
|
||||||
height?: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** SearchBar component for user input and search functionality. */
|
/** SearchBar component for user input and search functionality. */
|
||||||
export const SearchBar: React.FC<SearchBarProps> = ({
|
export const SearchBar: React.FC<SearchBarProps> = ({
|
||||||
placeholder = 'Search for tasks like "optimise SEO"',
|
placeholder = 'Search for tasks like "optimise SEO"',
|
||||||
backgroundColor = "bg-neutral-100 dark:bg-neutral-800",
|
className,
|
||||||
iconColor = "text-[#646464] dark:text-neutral-400",
|
|
||||||
textColor = "text-[#707070] dark:text-neutral-200",
|
|
||||||
placeholderColor = "text-[#707070] dark:text-neutral-400",
|
|
||||||
width = "w-9/10 lg:w-[56.25rem]",
|
|
||||||
height = "h-[60px]",
|
|
||||||
}) => {
|
}) => {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
@@ -31,7 +23,6 @@ export const SearchBar: React.FC<SearchBarProps> = ({
|
|||||||
|
|
||||||
const handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {
|
const handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
console.log(searchQuery);
|
|
||||||
|
|
||||||
if (searchQuery.trim()) {
|
if (searchQuery.trim()) {
|
||||||
// Encode the search term and navigate to the desired path
|
// Encode the search term and navigate to the desired path
|
||||||
@@ -44,15 +35,18 @@ export const SearchBar: React.FC<SearchBarProps> = ({
|
|||||||
<form
|
<form
|
||||||
onSubmit={handleSubmit}
|
onSubmit={handleSubmit}
|
||||||
data-testid="store-search-bar"
|
data-testid="store-search-bar"
|
||||||
className={`${width} ${height} px-4 pt-2 md:px-6 md:pt-1 ${backgroundColor} flex items-center justify-center gap-2 rounded-full md:gap-5`}
|
className={cn(
|
||||||
|
`flex h-14 w-full items-center justify-center gap-2 rounded-full bg-[#F3F3F3] px-6 py-2.5 md:h-18 md:gap-5`,
|
||||||
|
className,
|
||||||
|
)}
|
||||||
>
|
>
|
||||||
<MagnifyingGlassIcon className={`h-5 w-5 md:h-7 md:w-7 ${iconColor}`} />
|
<MagnifyingGlassIcon className={`h-5 w-5 text-[#020617] md:h-7 md:w-7`} />
|
||||||
<input
|
<Input
|
||||||
type="text"
|
type="text"
|
||||||
value={searchQuery}
|
value={searchQuery}
|
||||||
onChange={(e) => setSearchQuery(e.target.value)}
|
onChange={(e) => setSearchQuery(e.target.value)}
|
||||||
placeholder={placeholder}
|
placeholder={placeholder}
|
||||||
className={`flex-grow border-none bg-transparent ${textColor} font-sans text-lg font-normal leading-[2.25rem] tracking-tight md:text-xl placeholder:${placeholderColor} focus:outline-none`}
|
className={`m-0 flex-grow border-none bg-transparent p-0 font-sans text-base font-normal text-zinc-800 shadow-none placeholder:text-neutral-500 focus:shadow-none focus:outline-none focus:ring-0 md:text-xl`}
|
||||||
data-testid="store-search-input"
|
data-testid="store-search-input"
|
||||||
/>
|
/>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
import type { Meta, StoryObj } from "@storybook/react";
|
||||||
|
import { SearchFilterChips } from "./SearchFilterChips";
|
||||||
|
|
||||||
|
const meta = {
|
||||||
|
title: "Agpt UI/marketing/Search Filter Chips",
|
||||||
|
component: SearchFilterChips,
|
||||||
|
parameters: {
|
||||||
|
layout: "centered",
|
||||||
|
},
|
||||||
|
tags: ["autodocs"],
|
||||||
|
argTypes: {
|
||||||
|
onFilterChange: { action: "onFilterChange" },
|
||||||
|
},
|
||||||
|
} satisfies Meta<typeof SearchFilterChips>;
|
||||||
|
|
||||||
|
export default meta;
|
||||||
|
type Story = StoryObj<typeof meta>;
|
||||||
|
|
||||||
|
export const Default: Story = {};
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
|
import { Button } from "../ui/button";
|
||||||
|
import AutogptButton from "./AutogptButton";
|
||||||
|
|
||||||
interface FilterOption {
|
interface FilterOption {
|
||||||
label: string;
|
label: string;
|
||||||
@@ -38,26 +40,22 @@ export const SearchFilterChips: React.FC<SearchFilterChipsProps> = ({
|
|||||||
return (
|
return (
|
||||||
<div className="flex gap-2.5">
|
<div className="flex gap-2.5">
|
||||||
{filters.map((filter) => (
|
{filters.map((filter) => (
|
||||||
<button
|
<AutogptButton
|
||||||
key={filter.value}
|
key={filter.value}
|
||||||
|
variant={selected === filter.value ? "default" : "outline"}
|
||||||
onClick={() => handleFilterClick(filter.value)}
|
onClick={() => handleFilterClick(filter.value)}
|
||||||
className={`flex items-center gap-2.5 rounded-[34px] px-5 py-2 ${
|
|
||||||
selected === filter.value
|
|
||||||
? "bg-neutral-800 text-white dark:bg-neutral-100 dark:text-neutral-900"
|
|
||||||
: "border border-neutral-600 text-neutral-800 dark:border-neutral-400 dark:text-neutral-200"
|
|
||||||
}`}
|
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
className={`text-base ${selected === filter.value ? "font-medium" : ""}`}
|
className={`mr-2 font-sans text-base ${selected === filter.value ? "font-medium" : "font-normal"}`}
|
||||||
>
|
>
|
||||||
{filter.label}
|
{filter.label}
|
||||||
</span>
|
</span>
|
||||||
<span
|
<span
|
||||||
className={`text-base ${selected === filter.value ? "font-medium" : ""}`}
|
className={`font-sans text-base ${selected === filter.value ? "font-medium" : "font-normal"}`}
|
||||||
>
|
>
|
||||||
{filter.count}
|
{filter.count}
|
||||||
</span>
|
</span>
|
||||||
</button>
|
</AutogptButton>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import type { Meta, StoryObj } from "@storybook/react";
|
|||||||
import { Sidebar } from "./Sidebar";
|
import { Sidebar } from "./Sidebar";
|
||||||
|
|
||||||
const meta = {
|
const meta = {
|
||||||
title: "AGPT UI/Sidebar",
|
title: "Agpt UI/marketing/Sidebar",
|
||||||
component: Sidebar,
|
component: Sidebar,
|
||||||
parameters: {
|
parameters: {
|
||||||
layout: "centered",
|
layout: "centered",
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
|
"use client";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import Link from "next/link";
|
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 { IconDashboardLayout } from "../ui/icons";
|
||||||
|
import { usePathname } from "next/navigation";
|
||||||
|
|
||||||
export interface SidebarLink {
|
export interface SidebarLink {
|
||||||
text: string;
|
text: string;
|
||||||
@@ -28,54 +27,32 @@ const getDefaultIconForLink = () => {
|
|||||||
export const Sidebar: React.FC<SidebarProps> = ({ linkGroups }) => {
|
export const Sidebar: React.FC<SidebarProps> = ({ linkGroups }) => {
|
||||||
// Extract all links from linkGroups
|
// Extract all links from linkGroups
|
||||||
const allLinks = linkGroups.flatMap((group) => group.links);
|
const allLinks = linkGroups.flatMap((group) => group.links);
|
||||||
|
const pathname = usePathname();
|
||||||
|
|
||||||
// Function to render link items
|
// Function to render link items
|
||||||
const renderLinks = () => {
|
const renderLinks = () => {
|
||||||
return allLinks.map((link, index) => (
|
return allLinks.map((link, index) => {
|
||||||
<Link
|
const isActive = pathname === link.href;
|
||||||
key={`${link.href}-${index}`}
|
return (
|
||||||
href={link.href}
|
<Link
|
||||||
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"
|
key={`${link.href}-${index}`}
|
||||||
>
|
href={link.href}
|
||||||
{link.icon || getDefaultIconForLink()}
|
className={`inline-flex w-full items-center gap-2.5 rounded-xl p-3 ${
|
||||||
<div className="p-ui-medium text-base font-medium leading-normal">
|
isActive
|
||||||
{link.text}
|
? "bg-zinc-800 text-white dark:bg-neutral-700 dark:text-white"
|
||||||
</div>
|
: "text-neutral-800 hover:bg-zinc-800 hover:text-white dark:text-neutral-200 dark:hover:bg-neutral-700 dark:hover:text-white"
|
||||||
</Link>
|
}`}
|
||||||
));
|
>
|
||||||
|
{link.icon || getDefaultIconForLink()}
|
||||||
|
<p className="font-sans text-base font-medium">{link.text}</p>
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<div className="sticky top-24 flex h-[calc(100vh-7rem)] w-60 flex-col gap-6 rounded-[1rem] bg-zinc-200 p-3">
|
||||||
<Sheet>
|
{renderLinks()}
|
||||||
<SheetTrigger asChild>
|
</div>
|
||||||
<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>
|
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -34,22 +34,22 @@ export const SortDropdown: React.FC<{
|
|||||||
return (
|
return (
|
||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
<DropdownMenuTrigger className="flex items-center gap-1.5 focus:outline-none">
|
<DropdownMenuTrigger className="flex items-center gap-1.5 focus:outline-none">
|
||||||
<span className="font-geist text-base text-neutral-800 dark:text-neutral-200">
|
<span className="font-sans text-sm font-medium text-zinc-800 dark:text-neutral-200">
|
||||||
Sort by
|
Sort by
|
||||||
</span>
|
</span>
|
||||||
<span className="font-geist text-base text-neutral-800 dark:text-neutral-200">
|
<span className="font-sans text-sm font-medium text-zinc-800 dark:text-neutral-200">
|
||||||
{selected.label}
|
{selected.label}
|
||||||
</span>
|
</span>
|
||||||
<ChevronDownIcon className="h-4 w-4 text-neutral-800 dark:text-neutral-200" />
|
<ChevronDownIcon className="h-4 w-4 text-zinc-800 dark:text-neutral-200" />
|
||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
<DropdownMenuContent
|
<DropdownMenuContent
|
||||||
align="end"
|
align="end"
|
||||||
className="w-[200px] rounded-lg bg-white shadow-lg dark:bg-neutral-800"
|
className="w-52 rounded-lg bg-white shadow-lg dark:bg-neutral-800"
|
||||||
>
|
>
|
||||||
{sortOptions.map((option) => (
|
{sortOptions.map((option) => (
|
||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
key={option.value}
|
key={option.value}
|
||||||
className={`cursor-pointer px-4 py-2 text-base hover:bg-neutral-100 dark:hover:bg-neutral-700 ${
|
className={`cursor-pointer px-4 py-2 text-sm hover:bg-neutral-100 dark:hover:bg-neutral-700 ${
|
||||||
selected.value === option.value
|
selected.value === option.value
|
||||||
? "font-medium text-neutral-800 dark:text-neutral-200"
|
? "font-medium text-neutral-800 dark:text-neutral-200"
|
||||||
: "text-neutral-600 dark:text-neutral-400"
|
: "text-neutral-600 dark:text-neutral-400"
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import type { Meta, StoryObj } from "@storybook/react";
|
|||||||
import { Status, StatusType } from "./Status";
|
import { Status, StatusType } from "./Status";
|
||||||
|
|
||||||
const meta = {
|
const meta = {
|
||||||
title: "AGPT UI/Status",
|
title: "Agpt UI/general/Status",
|
||||||
component: Status,
|
component: Status,
|
||||||
parameters: {
|
parameters: {
|
||||||
layout: "centered",
|
layout: "centered",
|
||||||
@@ -42,17 +42,3 @@ export const Rejected: Story = {
|
|||||||
status: "rejected" as StatusType,
|
status: "rejected" as StatusType,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const AllStatuses: Story = {
|
|
||||||
args: {
|
|
||||||
status: "draft" as StatusType,
|
|
||||||
},
|
|
||||||
render: () => (
|
|
||||||
<div className="flex flex-col gap-4">
|
|
||||||
<Status status="draft" />
|
|
||||||
<Status status="awaiting_review" />
|
|
||||||
<Status status="approved" />
|
|
||||||
<Status status="rejected" />
|
|
||||||
</div>
|
|
||||||
),
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -12,46 +12,31 @@ const statusConfig: Record<
|
|||||||
bgColor: string;
|
bgColor: string;
|
||||||
dotColor: string;
|
dotColor: string;
|
||||||
text: string;
|
text: string;
|
||||||
darkBgColor: string;
|
|
||||||
darkDotColor: string;
|
|
||||||
}
|
}
|
||||||
> = {
|
> = {
|
||||||
draft: {
|
draft: {
|
||||||
bgColor: "bg-blue-50",
|
bgColor: "bg-blue-50",
|
||||||
dotColor: "bg-blue-500",
|
dotColor: "bg-blue-500",
|
||||||
text: "Draft",
|
text: "Draft",
|
||||||
darkBgColor: "dark:bg-blue-900",
|
|
||||||
darkDotColor: "dark:bg-blue-300",
|
|
||||||
},
|
},
|
||||||
awaiting_review: {
|
awaiting_review: {
|
||||||
bgColor: "bg-amber-50",
|
bgColor: "bg-amber-50",
|
||||||
dotColor: "bg-amber-500",
|
dotColor: "bg-amber-500",
|
||||||
text: "Awaiting review",
|
text: "Awaiting review",
|
||||||
darkBgColor: "dark:bg-amber-900",
|
|
||||||
darkDotColor: "dark:bg-amber-300",
|
|
||||||
},
|
},
|
||||||
approved: {
|
approved: {
|
||||||
bgColor: "bg-green-50",
|
bgColor: "bg-green-50",
|
||||||
dotColor: "bg-green-500",
|
dotColor: "bg-green-500",
|
||||||
text: "Approved",
|
text: "Approved",
|
||||||
darkBgColor: "dark:bg-green-900",
|
|
||||||
darkDotColor: "dark:bg-green-300",
|
|
||||||
},
|
},
|
||||||
rejected: {
|
rejected: {
|
||||||
bgColor: "bg-red-50",
|
bgColor: "bg-red-50",
|
||||||
dotColor: "bg-red-500",
|
dotColor: "bg-red-500",
|
||||||
text: "Rejected",
|
text: "Rejected",
|
||||||
darkBgColor: "dark:bg-red-900",
|
|
||||||
darkDotColor: "dark:bg-red-300",
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Status: React.FC<StatusProps> = ({ status }) => {
|
export const Status: React.FC<StatusProps> = ({ status }) => {
|
||||||
/**
|
|
||||||
* Status component displays a badge with a colored dot and text indicating the agent's status
|
|
||||||
* @param status - The current status of the agent
|
|
||||||
* Valid values: 'draft', 'awaiting_review', 'approved', 'rejected'
|
|
||||||
*/
|
|
||||||
if (!status) {
|
if (!status) {
|
||||||
return <Status status="awaiting_review" />;
|
return <Status status="awaiting_review" />;
|
||||||
} else if (!statusConfig[status]) {
|
} else if (!statusConfig[status]) {
|
||||||
@@ -62,12 +47,10 @@ export const Status: React.FC<StatusProps> = ({ status }) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={`px-2.5 py-1 ${config.bgColor} ${config.darkBgColor} flex items-center gap-1.5 rounded-[26px]`}
|
className={`px-2.5 py-1 ${config.bgColor} flex w-fit items-center gap-1.5 rounded-3xl`}
|
||||||
>
|
>
|
||||||
<div
|
<div className={`h-3 w-3 ${config.dotColor} rounded-full`} />
|
||||||
className={`h-3 w-3 ${config.dotColor} ${config.darkDotColor} rounded-full`}
|
<div className="font-sans text-sm font-normal text-neutral-600">
|
||||||
/>
|
|
||||||
<div className="font-sans text-sm font-normal leading-tight text-neutral-600 dark:text-neutral-300">
|
|
||||||
{config.text}
|
{config.text}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,17 +1,18 @@
|
|||||||
import type { Meta, StoryObj } from "@storybook/react";
|
import type { Meta, StoryObj } from "@storybook/react";
|
||||||
import { StoreCard } from "./StoreCard";
|
import { StoreCard } from "./StoreCard";
|
||||||
import { userEvent, within, expect } from "@storybook/test";
|
import { userEvent, within } from "@storybook/test";
|
||||||
|
|
||||||
const meta = {
|
const meta = {
|
||||||
title: "AGPT UI/StoreCard",
|
title: "Agpt UI/marketing/StoreCard",
|
||||||
component: StoreCard,
|
component: StoreCard,
|
||||||
parameters: {
|
|
||||||
layout: {
|
decorators: [
|
||||||
center: true,
|
(Story) => (
|
||||||
fullscreen: true,
|
<div className="flex items-center justify-center p-4">
|
||||||
padding: 0,
|
<Story />
|
||||||
},
|
</div>
|
||||||
},
|
),
|
||||||
|
],
|
||||||
tags: ["autodocs"],
|
tags: ["autodocs"],
|
||||||
argTypes: {
|
argTypes: {
|
||||||
agentName: { control: "text" },
|
agentName: { control: "text" },
|
||||||
@@ -21,6 +22,8 @@ const meta = {
|
|||||||
rating: { control: "number", min: 0, max: 5, step: 0.1 },
|
rating: { control: "number", min: 0, max: 5, step: 0.1 },
|
||||||
onClick: { action: "clicked" },
|
onClick: { action: "clicked" },
|
||||||
avatarSrc: { control: "text" },
|
avatarSrc: { control: "text" },
|
||||||
|
hideAvatar: { control: "boolean" },
|
||||||
|
creatorName: { control: "text" },
|
||||||
},
|
},
|
||||||
} satisfies Meta<typeof StoreCard>;
|
} satisfies Meta<typeof StoreCard>;
|
||||||
|
|
||||||
@@ -30,86 +33,64 @@ type Story = StoryObj<typeof meta>;
|
|||||||
export const Default: Story = {
|
export const Default: Story = {
|
||||||
args: {
|
args: {
|
||||||
agentName: "SEO Optimizer",
|
agentName: "SEO Optimizer",
|
||||||
agentImage:
|
agentImage: "/testing_agent_image.jpg",
|
||||||
"https://framerusercontent.com/images/KCIpxr9f97EGJgpaoqnjKsrOPwI.jpg",
|
description:
|
||||||
description: "Optimize your website's SEO with AI-powered suggestions",
|
"Optimize your website's SEO with AI-powered suggestions and best practices. Get detailed reports and actionable recommendations.",
|
||||||
runs: 10000,
|
runs: 10000,
|
||||||
rating: 4.5,
|
rating: 4.5,
|
||||||
onClick: () => console.log("Default StoreCard clicked"),
|
onClick: () => console.log("Default StoreCard clicked"),
|
||||||
avatarSrc: "https://github.com/shadcn.png",
|
avatarSrc: "/testing_avatar.png",
|
||||||
},
|
creatorName: "AI Solutions Inc.",
|
||||||
};
|
|
||||||
|
|
||||||
export const LowRating: Story = {
|
|
||||||
args: {
|
|
||||||
agentName: "Data Analyzer",
|
|
||||||
agentImage:
|
|
||||||
"https://upload.wikimedia.org/wikipedia/commons/c/c5/Big_buck_bunny_poster_big.jpg",
|
|
||||||
description: "Analyze complex datasets with machine learning algorithms",
|
|
||||||
runs: 5000,
|
|
||||||
rating: 2.7,
|
|
||||||
onClick: () => console.log("LowRating StoreCard clicked"),
|
|
||||||
avatarSrc: "https://example.com/avatar2.jpg",
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export const HighRuns: Story = {
|
|
||||||
args: {
|
|
||||||
agentName: "Code Assistant",
|
|
||||||
agentImage:
|
|
||||||
"https://framerusercontent.com/images/KCIpxr9f97EGJgpaoqnjKsrOPwI.jpg",
|
|
||||||
description: "Get AI-powered coding help for various programming languages",
|
|
||||||
runs: 1000000,
|
|
||||||
rating: 4.8,
|
|
||||||
onClick: () => console.log("HighRuns StoreCard clicked"),
|
|
||||||
avatarSrc: "https://example.com/avatar3.jpg",
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const WithInteraction: Story = {
|
export const WithInteraction: Story = {
|
||||||
args: {
|
args: {
|
||||||
agentName: "Task Planner",
|
agentName: "Task Planner",
|
||||||
agentImage:
|
agentImage: Default.args.agentImage,
|
||||||
"https://upload.wikimedia.org/wikipedia/commons/c/c5/Big_buck_bunny_poster_big.jpg",
|
description:
|
||||||
description: "Plan and organize your tasks efficiently with AI",
|
"Plan and organize your tasks efficiently with AI assistance. Set priorities, deadlines, and track progress.",
|
||||||
runs: 50000,
|
runs: 50000,
|
||||||
rating: 4.2,
|
rating: 4.2,
|
||||||
onClick: () => console.log("WithInteraction StoreCard clicked"),
|
onClick: () => console.log("WithInteraction StoreCard clicked"),
|
||||||
avatarSrc: "https://example.com/avatar4.jpg",
|
avatarSrc: Default.args.avatarSrc,
|
||||||
|
creatorName: "Productivity Plus",
|
||||||
},
|
},
|
||||||
play: async ({ canvasElement }) => {
|
play: async ({ canvasElement }) => {
|
||||||
const canvas = within(canvasElement);
|
const canvas = within(canvasElement);
|
||||||
const storeCard = canvas.getByText("Task Planner");
|
const storeCard = canvas.getByTestId("store-card");
|
||||||
|
|
||||||
await userEvent.hover(storeCard);
|
await userEvent.hover(storeCard);
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 300));
|
||||||
await userEvent.click(storeCard);
|
await userEvent.click(storeCard);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const LongDescription: Story = {
|
export const LongContent: Story = {
|
||||||
args: {
|
args: {
|
||||||
agentName: "AI Writing Assistant",
|
agentName:
|
||||||
agentImage:
|
"AI Writing Assistant that can help you to write some long content so you do not have to hire for writing some basic arrangement of letters",
|
||||||
"https://framerusercontent.com/images/KCIpxr9f97EGJgpaoqnjKsrOPwI.jpg",
|
agentImage: Default.args.agentImage,
|
||||||
description:
|
description:
|
||||||
"Enhance your writing with our advanced AI-powered assistant. It offers real-time suggestions for grammar, style, and tone, helps with research and fact-checking.",
|
"Enhance your writing with our advanced AI-powered assistant. It offers real-time suggestions for grammar, style, and tone, helps with research and fact-checking, and provides vocabulary enhancements for more engaging content. Perfect for content creators, marketers, and writers of all levels.",
|
||||||
runs: 75000,
|
runs: 75000,
|
||||||
rating: 4.7,
|
rating: 4.7,
|
||||||
onClick: () => console.log("LongDescription StoreCard clicked"),
|
onClick: () => console.log("LongContent StoreCard clicked"),
|
||||||
avatarSrc: "https://example.com/avatar5.jpg",
|
avatarSrc: Default.args.avatarSrc,
|
||||||
|
creatorName:
|
||||||
|
"The person who created the multiverst, including earth no. 631 and more..",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const HiddenAvatar: Story = {
|
export const SmallContent: Story = {
|
||||||
args: {
|
args: {
|
||||||
agentName: "Data Visualizer",
|
agentName: "Quick Notes",
|
||||||
agentImage:
|
agentImage: Default.args.agentImage,
|
||||||
"https://framerusercontent.com/images/KCIpxr9f97EGJgpaoqnjKsrOPwI.jpg",
|
description: "Simple note-taking assistant.",
|
||||||
description: "Create stunning visualizations from complex datasets",
|
runs: 3000,
|
||||||
runs: 60000,
|
rating: 4.0,
|
||||||
rating: 4.6,
|
onClick: () => console.log("SmallContent StoreCard clicked"),
|
||||||
onClick: () => console.log("HiddenAvatar StoreCard clicked"),
|
avatarSrc: Default.args.avatarSrc,
|
||||||
avatarSrc: "https://example.com/avatar6.jpg",
|
creatorName: "Note Systems",
|
||||||
hideAvatar: true,
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ export const StoreCard: React.FC<StoreCardProps> = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className="flex h-[27rem] w-full max-w-md cursor-pointer flex-col items-start rounded-3xl bg-white transition-all duration-300 hover:shadow-lg 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}
|
onClick={handleClick}
|
||||||
data-testid="store-card"
|
data-testid="store-card"
|
||||||
role="button"
|
role="button"
|
||||||
@@ -45,7 +45,7 @@ export const StoreCard: React.FC<StoreCardProps> = ({
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{/* First Section: Image with Avatar */}
|
{/* 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 && (
|
{agentImage && (
|
||||||
<Image
|
<Image
|
||||||
src={agentImage}
|
src={agentImage}
|
||||||
@@ -56,7 +56,7 @@ export const StoreCard: React.FC<StoreCardProps> = ({
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{!hideAvatar && (
|
{!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">
|
<Avatar className="h-16 w-16">
|
||||||
{avatarSrc && (
|
{avatarSrc && (
|
||||||
<AvatarImage
|
<AvatarImage
|
||||||
@@ -72,46 +72,40 @@ export const StoreCard: React.FC<StoreCardProps> = ({
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="mt-3 flex w-full flex-1 flex-col px-4">
|
<div className="flex w-full flex-1 flex-col">
|
||||||
{/* Second Section: Agent Name and Creator Name */}
|
{/* Second Section: Agent Name and Creator Name */}
|
||||||
<div className="flex w-full flex-col">
|
<div className="flex w-full flex-col px-1.5">
|
||||||
<h3 className="line-clamp-2 font-poppins text-2xl font-semibold text-[#272727] dark:text-neutral-100">
|
<h3 className="line-clamp-2 h-12 font-sans text-base font-medium text-zinc-800 dark:text-neutral-100">
|
||||||
{agentName}
|
{agentName}
|
||||||
</h3>
|
</h3>
|
||||||
{!hideAvatar && creatorName && (
|
{!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}
|
by {creatorName}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Third Section: Description */}
|
<div className="flex h-18 w-full flex-col px-1.5 pt-2">
|
||||||
<div className="mt-2.5 flex w-full flex-col">
|
<p className="line-clamp-3 font-sans text-sm font-normal text-zinc-500 dark:text-neutral-400">
|
||||||
<p className="line-clamp-3 font-sans text-base font-normal leading-normal text-neutral-600 dark:text-neutral-400">
|
|
||||||
{description}
|
{description}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex-grow" />
|
|
||||||
{/* Spacer to push stats to bottom */}
|
|
||||||
|
|
||||||
{/* Fourth Section: Stats Row - aligned to bottom */}
|
{/* Fourth Section: Stats Row - aligned to bottom */}
|
||||||
<div className="mt-5 w-full">
|
<div className="mt-2.5 flex items-center justify-between px-1.5 pt-2">
|
||||||
<div className="flex items-center justify-between">
|
<div className="font-sans text-sm font-medium text-zinc-800 dark:text-neutral-200">
|
||||||
<div className="font-sans text-lg font-semibold text-neutral-800 dark:text-neutral-200">
|
{runs.toLocaleString()} runs
|
||||||
{runs.toLocaleString()} runs
|
</div>
|
||||||
</div>
|
<div className="flex items-center gap-2">
|
||||||
<div className="flex items-center gap-2">
|
<span className="font-sans text-sm font-medium text-zinc-800 dark:text-neutral-200">
|
||||||
<span className="font-sans text-lg font-semibold text-neutral-800 dark:text-neutral-200">
|
{rating.toFixed(1)}
|
||||||
{rating.toFixed(1)}
|
</span>
|
||||||
</span>
|
<div
|
||||||
<div
|
className="inline-flex items-center"
|
||||||
className="inline-flex items-center"
|
role="img"
|
||||||
role="img"
|
aria-label={`Rating: ${rating.toFixed(1)} out of 5 stars`}
|
||||||
aria-label={`Rating: ${rating.toFixed(1)} out of 5 stars`}
|
>
|
||||||
>
|
{StarRatingIcons(rating)}
|
||||||
{StarRatingIcons(rating)}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,63 +1,65 @@
|
|||||||
import type { Meta, StoryObj } from "@storybook/react";
|
import type { Meta, StoryObj } from "@storybook/react";
|
||||||
import { Agent, AgentsSection } from "./AgentsSection";
|
import { Agent, AgentsSection } from "./AgentsSection";
|
||||||
import { userEvent, within, expect } from "@storybook/test";
|
import { userEvent, within } from "@storybook/test";
|
||||||
|
|
||||||
const meta = {
|
const meta = {
|
||||||
title: "AGPT UI/Composite/Agents Section",
|
title: "Agpt UI/marketing/Agents Section",
|
||||||
component: AgentsSection,
|
component: AgentsSection,
|
||||||
parameters: {
|
|
||||||
layout: {
|
decorators: [
|
||||||
center: true,
|
(Story) => (
|
||||||
fullscreen: true,
|
<div className="flex items-center justify-center py-4 md:p-4">
|
||||||
padding: 0,
|
<Story />
|
||||||
},
|
</div>
|
||||||
},
|
),
|
||||||
|
],
|
||||||
tags: ["autodocs"],
|
tags: ["autodocs"],
|
||||||
argTypes: {
|
argTypes: {
|
||||||
sectionTitle: { control: "text" },
|
sectionTitle: { control: "text" },
|
||||||
agents: { control: "object" },
|
agents: { control: "object" },
|
||||||
// onCardClick: { action: "clicked" },
|
hideAvatars: { control: "boolean" },
|
||||||
|
margin: { control: "text" },
|
||||||
|
className: { control: "text" },
|
||||||
},
|
},
|
||||||
} satisfies Meta<typeof AgentsSection>;
|
} satisfies Meta<typeof AgentsSection>;
|
||||||
|
|
||||||
export default meta;
|
export default meta;
|
||||||
type Story = StoryObj<typeof meta>;
|
type Story = StoryObj<typeof meta>;
|
||||||
|
|
||||||
|
const defaultAgentImage = "/testing_agent_image.jpg";
|
||||||
|
const defaultAvatarImage = "/testing_avatar.png";
|
||||||
const mockTopAgents = [
|
const mockTopAgents = [
|
||||||
{
|
{
|
||||||
agent_name: "SEO Optimizer Pro",
|
agent_name: "SEO Optimizer Pro",
|
||||||
agent_image:
|
agent_image: defaultAgentImage,
|
||||||
"https://framerusercontent.com/images/KCIpxr9f97EGJgpaoqnjKsrOPwI.jpg",
|
|
||||||
description:
|
description:
|
||||||
"Boost your website's search engine rankings with our advanced AI-powered SEO optimization tool.",
|
"Boost your website's search engine rankings with our advanced AI-powered SEO optimization tool.",
|
||||||
runs: 50000,
|
runs: 50000,
|
||||||
rating: 4.7,
|
rating: 4.7,
|
||||||
creator_avatar: "https://example.com/avatar1.jpg",
|
creator_avatar: defaultAvatarImage,
|
||||||
slug: "seo-optimizer-pro",
|
slug: "seo-optimizer-pro",
|
||||||
creator: "John Doe",
|
creator: "John Doe",
|
||||||
sub_heading: "SEO Expert",
|
sub_heading: "SEO Expert",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
agent_name: "Content Writer AI",
|
agent_name: "Content Writer AI",
|
||||||
agent_image:
|
agent_image: defaultAgentImage,
|
||||||
"https://upload.wikimedia.org/wikipedia/commons/c/c5/Big_buck_bunny_poster_big.jpg",
|
|
||||||
description:
|
description:
|
||||||
"Generate high-quality, engaging content for your blog, social media, or marketing campaigns.",
|
"Generate high-quality, engaging content for your blog, social media, or marketing campaigns.",
|
||||||
runs: 75000,
|
runs: 75000,
|
||||||
rating: 4.5,
|
rating: 4.5,
|
||||||
creator_avatar: "https://example.com/avatar2.jpg",
|
creator_avatar: defaultAvatarImage,
|
||||||
slug: "content-writer-ai",
|
slug: "content-writer-ai",
|
||||||
creator: "Jane Doe",
|
creator: "Jane Doe",
|
||||||
sub_heading: "Content Writer",
|
sub_heading: "Content Writer",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
agent_name: "Data Analyzer Lite",
|
agent_name: "Data Analyzer Lite",
|
||||||
agent_image:
|
agent_image: defaultAgentImage,
|
||||||
"https://framerusercontent.com/images/KCIpxr9f97EGJgpaoqnjKsrOPwI.jpg",
|
|
||||||
description: "A basic tool for analyzing small to medium-sized datasets.",
|
description: "A basic tool for analyzing small to medium-sized datasets.",
|
||||||
runs: 10000,
|
runs: 10000,
|
||||||
rating: 3.8,
|
rating: 3.8,
|
||||||
creator_avatar: "https://example.com/avatar3.jpg",
|
creator_avatar: defaultAvatarImage,
|
||||||
slug: "data-analyzer-lite",
|
slug: "data-analyzer-lite",
|
||||||
creator: "John Doe",
|
creator: "John Doe",
|
||||||
sub_heading: "Data Analyst",
|
sub_heading: "Data Analyst",
|
||||||
@@ -68,132 +70,115 @@ export const Default: Story = {
|
|||||||
args: {
|
args: {
|
||||||
sectionTitle: "Top Agents",
|
sectionTitle: "Top Agents",
|
||||||
agents: mockTopAgents,
|
agents: mockTopAgents,
|
||||||
// onCardClick: (agentName: string) => console.log(`Clicked on ${agentName}`),
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const SingleAgent: Story = {
|
export const SingleAgent: Story = {
|
||||||
args: {
|
args: {
|
||||||
sectionTitle: "Top Agents",
|
sectionTitle: "Featured Agent",
|
||||||
agents: [mockTopAgents[0]],
|
agents: [mockTopAgents[0]],
|
||||||
// onCardClick: (agentName: string) => console.log(`Clicked on ${agentName}`),
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const NoAgents: Story = {
|
export const NoAgents: Story = {
|
||||||
args: {
|
args: {
|
||||||
sectionTitle: "Top Agents",
|
sectionTitle: "Recommended Agents",
|
||||||
agents: [],
|
agents: [],
|
||||||
// onCardClick: (agentName: string) => console.log(`Clicked on ${agentName}`),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export const WithInteraction: Story = {
|
|
||||||
args: {
|
|
||||||
sectionTitle: "Top Agents",
|
|
||||||
agents: mockTopAgents,
|
|
||||||
// onCardClick: (agentName: string) => console.log(`Clicked on ${agentName}`),
|
|
||||||
},
|
|
||||||
play: async ({ canvasElement }) => {
|
|
||||||
const canvas = within(canvasElement);
|
|
||||||
const firstCard = canvas.getAllByRole("store-card")[0];
|
|
||||||
await userEvent.click(firstCard);
|
|
||||||
await expect(firstCard).toHaveAttribute("aria-pressed", "true");
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const MultiRowAgents: Story = {
|
export const MultiRowAgents: Story = {
|
||||||
args: {
|
args: {
|
||||||
sectionTitle: "Top Agents",
|
sectionTitle: "All Agents",
|
||||||
agents: [
|
agents: [
|
||||||
...mockTopAgents,
|
...mockTopAgents,
|
||||||
{
|
{
|
||||||
agent_name: "Image Recognition AI",
|
agent_name: "Image Recognition AI",
|
||||||
agent_image:
|
agent_image: defaultAgentImage,
|
||||||
"https://upload.wikimedia.org/wikipedia/commons/c/c5/Big_buck_bunny_poster_big.jpg",
|
|
||||||
description:
|
description:
|
||||||
"Accurately identify and classify objects in images using state-of-the-art machine learning algorithms.",
|
"Accurately identify and classify objects in images using state-of-the-art machine learning algorithms.",
|
||||||
runs: 60000,
|
runs: 60000,
|
||||||
rating: 4.6,
|
rating: 4.6,
|
||||||
creator_avatar: "https://example.com/avatar4.jpg",
|
creator_avatar: defaultAvatarImage,
|
||||||
slug: "image-recognition-ai",
|
slug: "image-recognition-ai",
|
||||||
creator: "John Doe",
|
creator: "Alex Smith",
|
||||||
sub_heading: "Image Recognition",
|
sub_heading: "Image Recognition",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
agent_name: "Natural Language Processor",
|
agent_name: "Natural Language Processor",
|
||||||
agent_image:
|
agent_image: defaultAgentImage,
|
||||||
"https://framerusercontent.com/images/KCIpxr9f97EGJgpaoqnjKsrOPwI.jpg",
|
|
||||||
description:
|
description:
|
||||||
"Analyze and understand human language with advanced NLP techniques.",
|
"Analyze and understand human language with advanced NLP techniques.",
|
||||||
runs: 80000,
|
runs: 80000,
|
||||||
rating: 4.8,
|
rating: 4.8,
|
||||||
creator_avatar: "https://example.com/avatar5.jpg",
|
creator_avatar: defaultAvatarImage,
|
||||||
slug: "natural-language-processor",
|
slug: "natural-language-processor",
|
||||||
creator: "John Doe",
|
creator: "Maria Garcia",
|
||||||
sub_heading: "Natural Language Processing",
|
sub_heading: "Natural Language Processing",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
agent_name: "Sentiment Analyzer",
|
agent_name: "Sentiment Analyzer",
|
||||||
agent_image:
|
agent_image: defaultAgentImage,
|
||||||
"https://upload.wikimedia.org/wikipedia/commons/c/c5/Big_buck_bunny_poster_big.jpg",
|
|
||||||
description:
|
description:
|
||||||
"Determine the emotional tone of text data for customer feedback analysis.",
|
"Determine the emotional tone of text data for customer feedback analysis.",
|
||||||
runs: 45000,
|
runs: 45000,
|
||||||
rating: 4.3,
|
rating: 4.3,
|
||||||
creator_avatar: "https://example.com/avatar6.jpg",
|
creator_avatar: defaultAvatarImage,
|
||||||
slug: "sentiment-analyzer",
|
slug: "sentiment-analyzer",
|
||||||
creator: "John Doe",
|
creator: "Robert Johnson",
|
||||||
sub_heading: "Sentiment Analysis",
|
sub_heading: "Sentiment Analysis",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
agent_name: "Chatbot Builder",
|
agent_name: "Chatbot Builder",
|
||||||
agent_image:
|
agent_image: defaultAgentImage,
|
||||||
"https://framerusercontent.com/images/KCIpxr9f97EGJgpaoqnjKsrOPwI.jpg",
|
|
||||||
description:
|
description:
|
||||||
"Create intelligent chatbots for customer service and engagement.",
|
"Create intelligent chatbots for customer service and engagement.",
|
||||||
runs: 55000,
|
runs: 55000,
|
||||||
rating: 4.4,
|
rating: 4.4,
|
||||||
creator_avatar: "https://example.com/avatar7.jpg",
|
creator_avatar: defaultAvatarImage,
|
||||||
slug: "chatbot-builder",
|
slug: "chatbot-builder",
|
||||||
creator: "John Doe",
|
creator: "Emma Wilson",
|
||||||
sub_heading: "Chatbot Developer",
|
sub_heading: "Chatbot Developer",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
agent_name: "Predictive Analytics Tool",
|
agent_name: "Predictive Analytics Tool",
|
||||||
agent_image:
|
agent_image: defaultAgentImage,
|
||||||
"https://upload.wikimedia.org/wikipedia/commons/c/c5/Big_buck_bunny_poster_big.jpg",
|
|
||||||
description:
|
description:
|
||||||
"Forecast future trends and outcomes based on historical data.",
|
"Forecast future trends and outcomes based on historical data.",
|
||||||
runs: 40000,
|
runs: 40000,
|
||||||
rating: 4.2,
|
rating: 4.2,
|
||||||
creator_avatar: "https://example.com/avatar8.jpg",
|
creator_avatar: defaultAvatarImage,
|
||||||
slug: "predictive-analytics-tool",
|
slug: "predictive-analytics-tool",
|
||||||
creator: "John Doe",
|
creator: "David Lee",
|
||||||
sub_heading: "Predictive Analytics",
|
sub_heading: "Predictive Analytics",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
agent_name: "Text-to-Speech Converter",
|
agent_name: "Text-to-Speech Converter",
|
||||||
agent_image:
|
agent_image: defaultAgentImage,
|
||||||
"https://framerusercontent.com/images/KCIpxr9f97EGJgpaoqnjKsrOPwI.jpg",
|
|
||||||
description:
|
description:
|
||||||
"Convert written text into natural-sounding speech in multiple languages.",
|
"Convert written text into natural-sounding speech in multiple languages.",
|
||||||
runs: 35000,
|
runs: 35000,
|
||||||
rating: 4.1,
|
rating: 4.1,
|
||||||
creator_avatar: "https://example.com/avatar9.jpg",
|
creator_avatar: defaultAvatarImage,
|
||||||
slug: "text-to-speech-converter",
|
slug: "text-to-speech-converter",
|
||||||
creator: "John Doe",
|
creator: "Sarah Brown",
|
||||||
sub_heading: "Text-to-Speech",
|
sub_heading: "Text-to-Speech",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
// onCardClick: (agentName: string) => console.log(`Clicked on ${agentName}`),
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const HiddenAvatars: Story = {
|
export const WithInteraction: Story = {
|
||||||
args: {
|
args: {
|
||||||
...Default.args,
|
sectionTitle: "Popular Agents",
|
||||||
hideAvatars: true,
|
agents: mockTopAgents,
|
||||||
sectionTitle: "Agents with Hidden Avatars",
|
},
|
||||||
|
play: async ({ canvasElement }) => {
|
||||||
|
const canvas = within(canvasElement);
|
||||||
|
// Using the proper test ID that's defined in the StoreCard component
|
||||||
|
const firstCard = canvas.getAllByTestId("store-card")[0];
|
||||||
|
await userEvent.hover(firstCard);
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 300));
|
||||||
|
await userEvent.click(firstCard);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -26,13 +26,14 @@ interface AgentsSectionProps {
|
|||||||
agents: Agent[];
|
agents: Agent[];
|
||||||
hideAvatars?: boolean;
|
hideAvatars?: boolean;
|
||||||
margin?: string;
|
margin?: string;
|
||||||
|
className?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const AgentsSection: React.FC<AgentsSectionProps> = ({
|
export const AgentsSection: React.FC<AgentsSectionProps> = ({
|
||||||
sectionTitle,
|
sectionTitle,
|
||||||
agents: allAgents,
|
agents: allAgents,
|
||||||
hideAvatars = false,
|
hideAvatars = false,
|
||||||
margin = "37px",
|
className,
|
||||||
}) => {
|
}) => {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
@@ -46,64 +47,32 @@ export const AgentsSection: React.FC<AgentsSectionProps> = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col items-center justify-center">
|
<div className={`w-full space-y-9 ${className}`}>
|
||||||
<div className="w-full max-w-[1360px]">
|
<h2 className="font-poppins text-base font-medium text-zinc-500">
|
||||||
<div
|
{sectionTitle ? sectionTitle : "Top agents"}
|
||||||
className={`mb-[${margin}] font-poppins text-lg font-semibold text-[#282828] dark:text-neutral-200`}
|
</h2>
|
||||||
>
|
{!displayedAgents || displayedAgents.length === 0 ? (
|
||||||
{sectionTitle}
|
<div className="font-poppins text-gray-500 dark:text-gray-400">
|
||||||
|
No agents found
|
||||||
</div>
|
</div>
|
||||||
{!displayedAgents || displayedAgents.length === 0 ? (
|
) : (
|
||||||
<div className="text-center text-gray-500 dark:text-gray-400">
|
<div className="grid grid-cols-1 place-items-center gap-5 md:grid-cols-2 lg:grid-cols-3 2xl:grid-cols-4">
|
||||||
No agents found
|
{displayedAgents.map((agent, index) => (
|
||||||
</div>
|
<StoreCard
|
||||||
) : (
|
key={index}
|
||||||
<>
|
agentName={agent.agent_name}
|
||||||
{/* Mobile Carousel View */}
|
agentImage={agent.agent_image}
|
||||||
<Carousel
|
description={agent.description}
|
||||||
className="md:hidden"
|
runs={agent.runs}
|
||||||
opts={{
|
rating={agent.rating}
|
||||||
loop: true,
|
avatarSrc={agent.creator_avatar}
|
||||||
}}
|
creatorName={agent.creator}
|
||||||
>
|
hideAvatar={hideAvatars}
|
||||||
<CarouselContent>
|
onClick={() => handleCardClick(agent.creator, agent.slug)}
|
||||||
{displayedAgents.map((agent, index) => (
|
/>
|
||||||
<CarouselItem key={index} className="min-w-64 max-w-71">
|
))}
|
||||||
<StoreCard
|
</div>
|
||||||
agentName={agent.agent_name}
|
)}
|
||||||
agentImage={agent.agent_image}
|
|
||||||
description={agent.description}
|
|
||||||
runs={agent.runs}
|
|
||||||
rating={agent.rating}
|
|
||||||
avatarSrc={agent.creator_avatar}
|
|
||||||
creatorName={agent.creator}
|
|
||||||
hideAvatar={hideAvatars}
|
|
||||||
onClick={() => handleCardClick(agent.creator, agent.slug)}
|
|
||||||
/>
|
|
||||||
</CarouselItem>
|
|
||||||
))}
|
|
||||||
</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">
|
|
||||||
{displayedAgents.map((agent, index) => (
|
|
||||||
<StoreCard
|
|
||||||
key={index}
|
|
||||||
agentName={agent.agent_name}
|
|
||||||
agentImage={agent.agent_image}
|
|
||||||
description={agent.description}
|
|
||||||
runs={agent.runs}
|
|
||||||
rating={agent.rating}
|
|
||||||
avatarSrc={agent.creator_avatar}
|
|
||||||
creatorName={agent.creator}
|
|
||||||
hideAvatar={hideAvatars}
|
|
||||||
onClick={() => handleCardClick(agent.creator, agent.slug)}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,17 +1,17 @@
|
|||||||
import type { Meta, StoryObj } from "@storybook/react";
|
import type { Meta, StoryObj } from "@storybook/react";
|
||||||
import { FeaturedCreators } from "./FeaturedCreators";
|
import { FeaturedCreators } from "./FeaturedCreators";
|
||||||
import { userEvent, within, expect } from "@storybook/test";
|
import { userEvent, within } from "@storybook/test";
|
||||||
|
|
||||||
const meta = {
|
const meta = {
|
||||||
title: "AGPT UI/Composite/Featured Creators",
|
title: "Agpt UI/marketing/Featured Creators",
|
||||||
component: FeaturedCreators,
|
component: FeaturedCreators,
|
||||||
parameters: {
|
decorators: [
|
||||||
layout: {
|
(Story) => (
|
||||||
center: true,
|
<div className="flex items-center justify-center p-4">
|
||||||
fullscreen: true,
|
<Story />
|
||||||
padding: 0,
|
</div>
|
||||||
},
|
),
|
||||||
},
|
],
|
||||||
tags: ["autodocs"],
|
tags: ["autodocs"],
|
||||||
argTypes: {
|
argTypes: {
|
||||||
featuredCreators: { control: "object" },
|
featuredCreators: { control: "object" },
|
||||||
@@ -98,7 +98,6 @@ export const ManyCreators: Story = {
|
|||||||
num_agents: 25,
|
num_agents: 25,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
// onCardClick: (creatorName) => console.log(`Clicked on ${creatorName}`),
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -109,13 +108,10 @@ export const WithInteraction: Story = {
|
|||||||
},
|
},
|
||||||
play: async ({ canvasElement }) => {
|
play: async ({ canvasElement }) => {
|
||||||
const canvas = within(canvasElement);
|
const canvas = within(canvasElement);
|
||||||
const creatorCards = canvas.getAllByRole("creator-card");
|
const creatorCards = canvas.getAllByTestId("creator-card");
|
||||||
const firstCreatorCard = creatorCards[0];
|
const firstCreatorCard = creatorCards[0];
|
||||||
|
|
||||||
await userEvent.hover(firstCreatorCard);
|
await userEvent.hover(firstCreatorCard);
|
||||||
await userEvent.click(firstCreatorCard);
|
await userEvent.click(firstCreatorCard);
|
||||||
|
|
||||||
// Check if the card has the expected hover and click effects
|
|
||||||
await expect(firstCreatorCard).toHaveClass("hover:shadow-lg");
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -31,25 +31,22 @@ export const FeaturedCreators: React.FC<FeaturedCreatorsProps> = ({
|
|||||||
const displayedCreators = featuredCreators.slice(0, 4);
|
const displayedCreators = featuredCreators.slice(0, 4);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex w-full flex-col items-center justify-center">
|
<div className="w-full space-y-9">
|
||||||
<div className="w-full max-w-[1360px]">
|
<h2 className="font-poppins text-base font-medium text-zinc-500 dark:text-zinc-200">
|
||||||
<h2 className="mb-9 font-poppins text-lg font-semibold text-neutral-800 dark:text-neutral-200">
|
{title}
|
||||||
{title}
|
</h2>
|
||||||
</h2>
|
|
||||||
|
|
||||||
<div className="grid grid-cols-1 gap-6 md:grid-cols-2 lg:grid-cols-4">
|
<div className="flex flex-wrap gap-5">
|
||||||
{displayedCreators.map((creator, index) => (
|
{displayedCreators.map((creator, index) => (
|
||||||
<CreatorCard
|
<CreatorCard
|
||||||
key={index}
|
key={index}
|
||||||
creatorName={creator.name || creator.username}
|
creatorName={creator.name || creator.username}
|
||||||
creatorImage={creator.avatar_url}
|
creatorImage={creator.avatar_url}
|
||||||
bio={creator.description}
|
bio={creator.description}
|
||||||
agentsUploaded={creator.num_agents}
|
agentsUploaded={creator.num_agents}
|
||||||
onClick={() => handleCardClick(creator.username)}
|
onClick={() => handleCardClick(creator.username)}
|
||||||
index={index}
|
/>
|
||||||
/>
|
))}
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,18 +1,18 @@
|
|||||||
import type { Meta, StoryObj } from "@storybook/react";
|
import type { Meta, StoryObj } from "@storybook/react";
|
||||||
import { FeaturedSection } from "./FeaturedSection";
|
import { FeaturedSection } from "./FeaturedSection";
|
||||||
import { userEvent, within } from "@storybook/test";
|
import { userEvent, within, expect } from "@storybook/test";
|
||||||
import { StoreAgent } from "@/lib/autogpt-server-api";
|
import { StoreAgent } from "@/lib/autogpt-server-api";
|
||||||
|
|
||||||
const meta = {
|
const meta = {
|
||||||
title: "AGPT UI/Composite/Featured Agents",
|
title: "Agpt UI/marketing/Featured Section",
|
||||||
component: FeaturedSection,
|
component: FeaturedSection,
|
||||||
parameters: {
|
decorators: [
|
||||||
layout: {
|
(Story) => (
|
||||||
center: true,
|
<div className="flex items-center justify-center py-4">
|
||||||
fullscreen: true,
|
<Story />
|
||||||
padding: 0,
|
</div>
|
||||||
},
|
),
|
||||||
},
|
],
|
||||||
tags: ["autodocs"],
|
tags: ["autodocs"],
|
||||||
argTypes: {
|
argTypes: {
|
||||||
featuredAgents: { control: "object" },
|
featuredAgents: { control: "object" },
|
||||||
@@ -32,10 +32,8 @@ const mockFeaturedAgents = [
|
|||||||
"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.",
|
"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.",
|
||||||
runs: 50000,
|
runs: 50000,
|
||||||
rating: 4.7,
|
rating: 4.7,
|
||||||
agent_image:
|
agent_image: "/testing_agent_image.jpg",
|
||||||
"https://framerusercontent.com/images/KCIpxr9f97EGJgpaoqnjKsrOPwI.jpg",
|
creator_avatar: "/testing_avatar.png",
|
||||||
creator_avatar:
|
|
||||||
"https://framerusercontent.com/images/KCIpxr9f97EGJgpaoqnjKsrOPwI.jpg",
|
|
||||||
slug: "personalized-morning-coffee-newsletter",
|
slug: "personalized-morning-coffee-newsletter",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -46,10 +44,8 @@ const mockFeaturedAgents = [
|
|||||||
"A lightweight data analysis tool for basic data processing needs.",
|
"A lightweight data analysis tool for basic data processing needs.",
|
||||||
runs: 10000,
|
runs: 10000,
|
||||||
rating: 2.8,
|
rating: 2.8,
|
||||||
agent_image:
|
agent_image: "/testing_agent_image.jpg",
|
||||||
"https://framerusercontent.com/images/KCIpxr9f97EGJgpaoqnjKsrOPwI.jpg",
|
creator_avatar: "/testing_avatar.png",
|
||||||
creator_avatar:
|
|
||||||
"https://framerusercontent.com/images/KCIpxr9f97EGJgpaoqnjKsrOPwI.jpg",
|
|
||||||
slug: "data-analyzer-lite",
|
slug: "data-analyzer-lite",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -60,10 +56,8 @@ const mockFeaturedAgents = [
|
|||||||
"An intelligent coding assistant that helps developers write better code faster.",
|
"An intelligent coding assistant that helps developers write better code faster.",
|
||||||
runs: 1000000,
|
runs: 1000000,
|
||||||
rating: 4.9,
|
rating: 4.9,
|
||||||
agent_image:
|
agent_image: "/testing_agent_image.jpg",
|
||||||
"https://framerusercontent.com/images/KCIpxr9f97EGJgpaoqnjKsrOPwI.jpg",
|
creator_avatar: "/testing_avatar.png",
|
||||||
creator_avatar:
|
|
||||||
"https://framerusercontent.com/images/KCIpxr9f97EGJgpaoqnjKsrOPwI.jpg",
|
|
||||||
slug: "codeassist-ai",
|
slug: "codeassist-ai",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -74,10 +68,8 @@ const mockFeaturedAgents = [
|
|||||||
"A comprehensive productivity suite that combines task management, note-taking, and project planning into one seamless interface. Features include smart task prioritization, automated scheduling, and AI-powered insights to help you work more efficiently.",
|
"A comprehensive productivity suite that combines task management, note-taking, and project planning into one seamless interface. Features include smart task prioritization, automated scheduling, and AI-powered insights to help you work more efficiently.",
|
||||||
runs: 75000,
|
runs: 75000,
|
||||||
rating: 4.5,
|
rating: 4.5,
|
||||||
agent_image:
|
agent_image: "/testing_agent_image.jpg",
|
||||||
"https://framerusercontent.com/images/KCIpxr9f97EGJgpaoqnjKsrOPwI.jpg",
|
creator_avatar: "/testing_avatar.png",
|
||||||
creator_avatar:
|
|
||||||
"https://framerusercontent.com/images/KCIpxr9f97EGJgpaoqnjKsrOPwI.jpg",
|
|
||||||
slug: "multitasker",
|
slug: "multitasker",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -87,10 +79,8 @@ const mockFeaturedAgents = [
|
|||||||
description: "Simple and efficient task automation tool.",
|
description: "Simple and efficient task automation tool.",
|
||||||
runs: 50000,
|
runs: 50000,
|
||||||
rating: 4.2,
|
rating: 4.2,
|
||||||
agent_image:
|
agent_image: "/testing_agent_image.jpg",
|
||||||
"https://framerusercontent.com/images/KCIpxr9f97EGJgpaoqnjKsrOPwI.jpg",
|
creator_avatar: "/testing_avatar.png",
|
||||||
creator_avatar:
|
|
||||||
"https://framerusercontent.com/images/KCIpxr9f97EGJgpaoqnjKsrOPwI.jpg",
|
|
||||||
slug: "quicktask",
|
slug: "quicktask",
|
||||||
},
|
},
|
||||||
] satisfies StoreAgent[];
|
] satisfies StoreAgent[];
|
||||||
@@ -98,36 +88,52 @@ const mockFeaturedAgents = [
|
|||||||
export const Default: Story = {
|
export const Default: Story = {
|
||||||
args: {
|
args: {
|
||||||
featuredAgents: mockFeaturedAgents,
|
featuredAgents: mockFeaturedAgents,
|
||||||
// onCardClick: (agentName: string) => console.log(`Clicked on ${agentName}`),
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const SingleAgent: Story = {
|
export const SingleAgent: Story = {
|
||||||
args: {
|
args: {
|
||||||
featuredAgents: [mockFeaturedAgents[0]],
|
featuredAgents: [mockFeaturedAgents[0]],
|
||||||
// onCardClick: (agentName: string) => console.log(`Clicked on ${agentName}`),
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const NoAgents: Story = {
|
export const NoAgents: Story = {
|
||||||
args: {
|
args: {
|
||||||
featuredAgents: [],
|
featuredAgents: [],
|
||||||
// onCardClick: (agentName: string) => console.log(`Clicked on ${agentName}`),
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const WithInteraction: Story = {
|
export const WithManyAgents: Story = {
|
||||||
|
args: {
|
||||||
|
featuredAgents: Array(20)
|
||||||
|
.fill(null)
|
||||||
|
.map((_, i) => ({
|
||||||
|
...mockFeaturedAgents[i % mockFeaturedAgents.length],
|
||||||
|
agent_name: `Agent ${i + 1}: ${mockFeaturedAgents[i % mockFeaturedAgents.length].agent_name}`,
|
||||||
|
slug: `agent-${i + 1}`,
|
||||||
|
})),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const WithCardInteraction: Story = {
|
||||||
args: {
|
args: {
|
||||||
featuredAgents: mockFeaturedAgents,
|
featuredAgents: mockFeaturedAgents,
|
||||||
// onCardClick: (agentName: string) => console.log(`Clicked on ${agentName}`),
|
|
||||||
},
|
},
|
||||||
play: async ({ canvasElement }) => {
|
play: async ({ canvasElement }) => {
|
||||||
const canvas = within(canvasElement);
|
const canvas = within(canvasElement);
|
||||||
const featuredCard = canvas.getByText(
|
|
||||||
"Personalized Morning Coffee Newsletter example of three lines",
|
|
||||||
);
|
|
||||||
|
|
||||||
await userEvent.hover(featuredCard);
|
// Find and interact with first card
|
||||||
await userEvent.click(featuredCard);
|
const cards = canvas.getAllByTestId("featured-store-card");
|
||||||
|
await expect(cards.length).toBeGreaterThan(0);
|
||||||
|
|
||||||
|
const firstCard = cards[0];
|
||||||
|
await userEvent.hover(firstCard);
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 300));
|
||||||
|
|
||||||
|
// Check that link is present and clickable
|
||||||
|
const cardLink = firstCard.closest("a");
|
||||||
|
await expect(cardLink).toBeInTheDocument();
|
||||||
|
|
||||||
|
await userEvent.click(firstCard);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -15,9 +15,9 @@ import { StoreAgent } from "@/lib/autogpt-server-api";
|
|||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
|
|
||||||
const BACKGROUND_COLORS = [
|
const BACKGROUND_COLORS = [
|
||||||
"bg-violet-200 dark:bg-violet-800", // #ddd6fe / #5b21b6
|
"bg-violet-100 hover:bg-violet-200 dark:bg-violet-800", // #ddd6fe / #5b21b6
|
||||||
"bg-blue-200 dark:bg-blue-800", // #bfdbfe / #1e3a8a
|
"bg-blue-100 hover:bg-blue-200 dark:bg-blue-800", // #bfdbfe / #1e3a8a
|
||||||
"bg-green-200 dark:bg-green-800", // #bbf7d0 / #065f46
|
"bg-green-100 hover:bg-green-200 dark:bg-green-800", // #bbf7d0 / #065f46
|
||||||
];
|
];
|
||||||
|
|
||||||
interface FeaturedSectionProps {
|
interface FeaturedSectionProps {
|
||||||
@@ -46,22 +46,22 @@ export const FeaturedSection: React.FC<FeaturedSectionProps> = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className="w-full">
|
<section className="w-full space-y-7">
|
||||||
<h2 className="mb-8 font-poppins text-2xl font-semibold leading-7 text-neutral-800 dark:text-neutral-200">
|
<h2 className="pl-4 font-poppins text-base font-medium text-zinc-500 md:pl-10">
|
||||||
Featured agents
|
Featured agents
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
<Carousel
|
<Carousel
|
||||||
opts={{
|
opts={{
|
||||||
align: "center",
|
align: "start",
|
||||||
containScroll: "trimSnaps",
|
containScroll: "trimSnaps",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<CarouselContent>
|
<CarouselContent className="p-0">
|
||||||
{featuredAgents.map((agent, index) => (
|
{featuredAgents.map((agent, index) => (
|
||||||
<CarouselItem
|
<CarouselItem
|
||||||
key={index}
|
key={index}
|
||||||
className="h-[480px] md:basis-1/2 lg:basis-1/3"
|
className={`flex w-screen flex-none items-center justify-center md:w-fit ${index === featuredAgents.length - 1 ? "md:mr-4" : ""} ${index === 0 ? "pl-8 md:pl-14" : ""}`}
|
||||||
>
|
>
|
||||||
<Link
|
<Link
|
||||||
href={`/marketplace/agent/${encodeURIComponent(agent.creator)}/${encodeURIComponent(agent.slug)}`}
|
href={`/marketplace/agent/${encodeURIComponent(agent.creator)}/${encodeURIComponent(agent.slug)}`}
|
||||||
@@ -75,10 +75,16 @@ export const FeaturedSection: React.FC<FeaturedSectionProps> = ({
|
|||||||
</CarouselItem>
|
</CarouselItem>
|
||||||
))}
|
))}
|
||||||
</CarouselContent>
|
</CarouselContent>
|
||||||
<div className="relative mt-4">
|
<div className="relative mt-4 px-4 md:px-10">
|
||||||
<CarouselIndicator />
|
<CarouselIndicator />
|
||||||
<CarouselPrevious afterClick={handlePrevSlide} />
|
<CarouselPrevious
|
||||||
<CarouselNext afterClick={handleNextSlide} />
|
afterClick={handlePrevSlide}
|
||||||
|
data-testid="Next slide Button"
|
||||||
|
/>
|
||||||
|
<CarouselNext
|
||||||
|
afterClick={handleNextSlide}
|
||||||
|
data-testid="Previous slide Button"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</Carousel>
|
</Carousel>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
@@ -3,15 +3,15 @@ import { HeroSection } from "./HeroSection";
|
|||||||
import { userEvent, within, expect } from "@storybook/test";
|
import { userEvent, within, expect } from "@storybook/test";
|
||||||
|
|
||||||
const meta = {
|
const meta = {
|
||||||
title: "AGPT UI/Composite/Hero Section",
|
title: "Agpt UI/marketing/Hero Section",
|
||||||
component: HeroSection,
|
component: HeroSection,
|
||||||
parameters: {
|
decorators: [
|
||||||
layout: {
|
(Story) => (
|
||||||
center: true,
|
<div className="flex items-center justify-center py-4 md:p-4">
|
||||||
fullscreen: true,
|
<Story />
|
||||||
padding: 0,
|
</div>
|
||||||
},
|
),
|
||||||
},
|
],
|
||||||
tags: ["autodocs"],
|
tags: ["autodocs"],
|
||||||
argTypes: {
|
argTypes: {
|
||||||
onSearch: { action: "searched" },
|
onSearch: { action: "searched" },
|
||||||
@@ -38,7 +38,7 @@ export const WithInteraction: Story = {
|
|||||||
},
|
},
|
||||||
play: async ({ canvasElement }) => {
|
play: async ({ canvasElement }) => {
|
||||||
const canvas = within(canvasElement);
|
const canvas = within(canvasElement);
|
||||||
const searchInput = canvas.getByRole("store-search-input");
|
const searchInput = canvas.getByRole("textbox");
|
||||||
|
|
||||||
await userEvent.type(searchInput, "test query");
|
await userEvent.type(searchInput, "test query");
|
||||||
await userEvent.keyboard("{Enter}");
|
await userEvent.keyboard("{Enter}");
|
||||||
@@ -47,8 +47,6 @@ export const WithInteraction: Story = {
|
|||||||
|
|
||||||
const filterChip = canvas.getByText("Marketing");
|
const filterChip = canvas.getByText("Marketing");
|
||||||
await userEvent.click(filterChip);
|
await userEvent.click(filterChip);
|
||||||
|
|
||||||
await expect(filterChip).toHaveClass("text-[#474747]");
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -60,7 +58,7 @@ export const EmptySearch: Story = {
|
|||||||
},
|
},
|
||||||
play: async ({ canvasElement }) => {
|
play: async ({ canvasElement }) => {
|
||||||
const canvas = within(canvasElement);
|
const canvas = within(canvasElement);
|
||||||
const searchInput = canvas.getByRole("store-search-input");
|
const searchInput = canvas.getByRole("textbox");
|
||||||
|
|
||||||
await userEvent.click(searchInput);
|
await userEvent.click(searchInput);
|
||||||
await userEvent.keyboard("{Enter}");
|
await userEvent.keyboard("{Enter}");
|
||||||
@@ -68,3 +66,20 @@ export const EmptySearch: Story = {
|
|||||||
await expect(searchInput).toHaveValue("");
|
await expect(searchInput).toHaveValue("");
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const FilterInteraction: Story = {
|
||||||
|
args: {
|
||||||
|
onSearch: (query: string) => console.log(`Searched: ${query}`),
|
||||||
|
onFilterChange: (selectedFilters: string[]) =>
|
||||||
|
console.log(`Filters changed: ${selectedFilters.join(", ")}`),
|
||||||
|
},
|
||||||
|
play: async ({ canvasElement }) => {
|
||||||
|
const canvas = within(canvasElement);
|
||||||
|
|
||||||
|
const filterChips = canvas.getAllByTestId("filter-chip");
|
||||||
|
|
||||||
|
for (const chip of filterChips) {
|
||||||
|
await userEvent.click(chip);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|||||||
@@ -21,46 +21,31 @@ export const HeroSection: React.FC = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mb-2 mt-8 flex flex-col items-center justify-center px-4 sm:mb-4 sm:mt-12 sm:px-6 md:mb-6 md:mt-16 lg:my-24 lg:px-8 xl:my-16">
|
<div className="mx-auto flex w-full flex-col items-center justify-center pb-20 pt-6 md:w-full md:pb-32 md:pt-36">
|
||||||
<div className="w-full max-w-3xl lg:max-w-4xl xl:max-w-5xl">
|
{/* Title */}
|
||||||
<div className="mb-4 text-center md:mb-8">
|
<h1 className="mb-4 text-center font-poppins text-3xl font-semibold leading-8 text-zinc-900 md:mb-8 md:text-[2.75rem] md:leading-[3.5rem]">
|
||||||
<h1 className="text-center">
|
<span>Explore AI agents built for </span>
|
||||||
<span className="font-poppins text-[48px] font-semibold leading-[54px] text-neutral-950 dark:text-neutral-50">
|
<span className="text-violet-600">you</span>
|
||||||
Explore AI agents built for{" "}
|
<br />
|
||||||
</span>
|
<span>by the </span>
|
||||||
<span className="font-poppins text-[48px] font-semibold leading-[54px] text-violet-600">
|
<span className="text-blue-500">community</span>
|
||||||
you
|
</h1>
|
||||||
</span>
|
|
||||||
<br />
|
{/* Description */}
|
||||||
<span className="font-poppins text-[48px] font-semibold leading-[54px] text-neutral-950 dark:text-neutral-50">
|
<h3 className="mb-6 text-center font-sans text-base text-zinc-600 md:mb-12 md:text-xl">
|
||||||
by the{" "}
|
Bringing you AI agents designed by thinkers from around the world
|
||||||
</span>
|
</h3>
|
||||||
<span className="font-poppins text-[48px] font-semibold leading-[54px] text-blue-500">
|
|
||||||
community
|
{/* Seach bar */}
|
||||||
</span>
|
<SearchBar className="max-w-4xl" />
|
||||||
</h1>
|
|
||||||
</div>
|
{/* Filter chips */}
|
||||||
<h3 className="mb:text-2xl mb-6 text-center font-sans text-xl font-normal leading-loose text-neutral-700 dark:text-neutral-300 md:mb-12">
|
<div className="mt-5">
|
||||||
Bringing you AI agents designed by thinkers from around the world
|
<FilterChips
|
||||||
</h3>
|
badges={["Marketing", "Content Creation", "SEO", "Automation", "Fun"]}
|
||||||
<div className="mb-4 flex justify-center sm:mb-5">
|
onFilterChange={onFilterChange}
|
||||||
<SearchBar height="h-[74px]" />
|
multiSelect={false}
|
||||||
</div>
|
/>
|
||||||
<div>
|
|
||||||
<div className="flex justify-center">
|
|
||||||
<FilterChips
|
|
||||||
badges={[
|
|
||||||
"Marketing",
|
|
||||||
"SEO",
|
|
||||||
"Content Creation",
|
|
||||||
"Automation",
|
|
||||||
"Fun",
|
|
||||||
]}
|
|
||||||
onFilterChange={onFilterChange}
|
|
||||||
multiSelect={false}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,52 +0,0 @@
|
|||||||
import type { Meta, StoryObj } from "@storybook/react";
|
|
||||||
import { PublishAgentPopout } from "@/components/agptui/composite/PublishAgentPopout";
|
|
||||||
import { userEvent, within, expect } from "@storybook/test";
|
|
||||||
|
|
||||||
const meta = {
|
|
||||||
title: "AGPT UI/Composite/Publish Agent Popout",
|
|
||||||
component: PublishAgentPopout,
|
|
||||||
parameters: {
|
|
||||||
layout: "centered",
|
|
||||||
},
|
|
||||||
tags: ["autodocs"],
|
|
||||||
argTypes: {
|
|
||||||
trigger: { control: "object" },
|
|
||||||
},
|
|
||||||
} satisfies Meta<typeof PublishAgentPopout>;
|
|
||||||
|
|
||||||
export default meta;
|
|
||||||
type Story = StoryObj<typeof meta>;
|
|
||||||
|
|
||||||
export const Default: Story = {
|
|
||||||
args: {},
|
|
||||||
};
|
|
||||||
|
|
||||||
export const WithCustomTrigger: Story = {
|
|
||||||
args: {
|
|
||||||
trigger: <button>Custom Publish Button</button>,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export const PublishFlow: Story = {
|
|
||||||
args: {},
|
|
||||||
play: async ({ canvasElement }) => {
|
|
||||||
const canvas = within(canvasElement);
|
|
||||||
|
|
||||||
// Open popout
|
|
||||||
const publishButton = canvas.getByText("Publish Agent");
|
|
||||||
await userEvent.click(publishButton);
|
|
||||||
|
|
||||||
// Select an agent (assuming one exists)
|
|
||||||
const agentCard = await canvas.findByRole("button", {
|
|
||||||
name: /select agent/i,
|
|
||||||
});
|
|
||||||
await userEvent.click(agentCard);
|
|
||||||
|
|
||||||
// Click next
|
|
||||||
const nextButton = canvas.getByText("Next");
|
|
||||||
await userEvent.click(nextButton);
|
|
||||||
|
|
||||||
// Fill out info form
|
|
||||||
// Note: Actual form interactions would need to be added based on PublishAgentInfo implementation
|
|
||||||
},
|
|
||||||
};
|
|
||||||
@@ -1,12 +1,7 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import {
|
import { Dialog, DialogContent, DialogTrigger } from "@/components/ui/dialog";
|
||||||
Popover,
|
|
||||||
PopoverTrigger,
|
|
||||||
PopoverContent,
|
|
||||||
PopoverAnchor,
|
|
||||||
} from "@/components/ui/popover";
|
|
||||||
import { PublishAgentSelect } from "../PublishAgentSelect";
|
import { PublishAgentSelect } from "../PublishAgentSelect";
|
||||||
import {
|
import {
|
||||||
PublishAgentInfo,
|
PublishAgentInfo,
|
||||||
@@ -21,6 +16,8 @@ import {
|
|||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
import { useBackendAPI } from "@/lib/autogpt-server-api/context";
|
import { useBackendAPI } from "@/lib/autogpt-server-api/context";
|
||||||
import { useToast } from "@/components/ui/use-toast";
|
import { useToast } from "@/components/ui/use-toast";
|
||||||
|
import AutogptButton from "../AutogptButton";
|
||||||
|
|
||||||
interface PublishAgentPopoutProps {
|
interface PublishAgentPopoutProps {
|
||||||
trigger?: React.ReactNode;
|
trigger?: React.ReactNode;
|
||||||
openPopout?: boolean;
|
openPopout?: boolean;
|
||||||
@@ -69,7 +66,6 @@ export const PublishAgentPopout: React.FC<PublishAgentPopoutProps> = ({
|
|||||||
>(null);
|
>(null);
|
||||||
const [open, setOpen] = React.useState(false);
|
const [open, setOpen] = React.useState(false);
|
||||||
|
|
||||||
const popupId = React.useId();
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const api = useBackendAPI();
|
const api = useBackendAPI();
|
||||||
|
|
||||||
@@ -209,98 +205,65 @@ export const PublishAgentPopout: React.FC<PublishAgentPopoutProps> = ({
|
|||||||
switch (step) {
|
switch (step) {
|
||||||
case "select":
|
case "select":
|
||||||
return (
|
return (
|
||||||
<div className="flex min-h-screen items-center justify-center">
|
<PublishAgentSelect
|
||||||
<div className="mx-auto flex w-full max-w-[900px] flex-col rounded-3xl bg-white shadow-lg dark:bg-gray-800">
|
agents={
|
||||||
<div className="h-full overflow-y-auto">
|
myAgents?.agents
|
||||||
<PublishAgentSelect
|
.map((agent) => ({
|
||||||
agents={
|
name: agent.agent_name,
|
||||||
myAgents?.agents
|
id: agent.agent_id,
|
||||||
.map((agent) => ({
|
version: agent.agent_version,
|
||||||
name: agent.agent_name,
|
lastEdited: agent.last_edited,
|
||||||
id: agent.agent_id,
|
imageSrc:
|
||||||
version: agent.agent_version,
|
agent.agent_image || "https://picsum.photos/300/200",
|
||||||
lastEdited: agent.last_edited,
|
}))
|
||||||
imageSrc:
|
.sort(
|
||||||
agent.agent_image || "https://picsum.photos/300/200",
|
(a, b) =>
|
||||||
}))
|
new Date(b.lastEdited).getTime() -
|
||||||
.sort(
|
new Date(a.lastEdited).getTime(),
|
||||||
(a, b) =>
|
) || []
|
||||||
new Date(b.lastEdited).getTime() -
|
}
|
||||||
new Date(a.lastEdited).getTime(),
|
onSelect={handleAgentSelect}
|
||||||
) || []
|
onCancel={handleClose}
|
||||||
}
|
onNext={handleNextFromSelect}
|
||||||
onSelect={handleAgentSelect}
|
onClose={handleClose}
|
||||||
onCancel={handleClose}
|
onOpenBuilder={() => router.push("/build")}
|
||||||
onNext={handleNextFromSelect}
|
/>
|
||||||
onClose={handleClose}
|
|
||||||
onOpenBuilder={() => router.push("/build")}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
case "info":
|
case "info":
|
||||||
return (
|
return (
|
||||||
<div className="flex min-h-screen items-center justify-center">
|
<PublishAgentInfo
|
||||||
<div className="mx-auto flex w-full max-w-[900px] flex-col rounded-3xl bg-white shadow-lg dark:bg-gray-800">
|
onBack={handleBack}
|
||||||
<div className="h-[700px] overflow-y-auto">
|
onSubmit={handleNextFromInfo}
|
||||||
<PublishAgentInfo
|
onClose={handleClose}
|
||||||
onBack={handleBack}
|
initialData={initialData}
|
||||||
onSubmit={handleNextFromInfo}
|
/>
|
||||||
onClose={handleClose}
|
|
||||||
initialData={initialData}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
case "review":
|
case "review":
|
||||||
return publishData ? (
|
return publishData ? (
|
||||||
<div className="flex justify-center">
|
<PublishAgentAwaitingReview
|
||||||
<div className="mx-auto flex w-full max-w-[900px] flex-col rounded-3xl bg-white shadow-lg dark:bg-gray-800">
|
agentName={publishData.name}
|
||||||
<div className="h-[600px] overflow-y-auto">
|
subheader={publishData.sub_heading}
|
||||||
<PublishAgentAwaitingReview
|
description={publishData.description}
|
||||||
agentName={publishData.name}
|
thumbnailSrc={publishData.image_urls[0]}
|
||||||
subheader={publishData.sub_heading}
|
onClose={handleClose}
|
||||||
description={publishData.description}
|
onDone={handleClose}
|
||||||
thumbnailSrc={publishData.image_urls[0]}
|
onViewProgress={() => {
|
||||||
onClose={handleClose}
|
router.push("/profile/dashboard");
|
||||||
onDone={handleClose}
|
handleClose();
|
||||||
onViewProgress={() => {
|
}}
|
||||||
router.push("/profile/dashboard");
|
/>
|
||||||
handleClose();
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
) : null;
|
) : null;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Popover
|
<Dialog open={open} onOpenChange={setOpen}>
|
||||||
open={open}
|
<DialogTrigger asChild>
|
||||||
onOpenChange={(isOpen) => {
|
|
||||||
if (isOpen !== open) {
|
|
||||||
setOpen(isOpen);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<PopoverTrigger asChild>
|
|
||||||
{trigger || <Button>Publish Agent</Button>}
|
{trigger || <Button>Publish Agent</Button>}
|
||||||
</PopoverTrigger>
|
</DialogTrigger>
|
||||||
<PopoverAnchor asChild>
|
<DialogContent className="h-screen w-screen max-w-full overflow-auto rounded-none border-none bg-black/40 backdrop-blur-[0.375rem]">
|
||||||
<div className="fixed left-0 top-0 hidden h-screen w-screen items-center justify-center"></div>
|
|
||||||
</PopoverAnchor>
|
|
||||||
|
|
||||||
<PopoverContent
|
|
||||||
id={popupId}
|
|
||||||
align="center"
|
|
||||||
className="z-50 h-screen w-screen bg-transparent"
|
|
||||||
>
|
|
||||||
{renderContent()}
|
{renderContent()}
|
||||||
</PopoverContent>
|
</DialogContent>
|
||||||
</Popover>
|
</Dialog>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
"use client";
|
||||||
|
import { usePathname } from "next/navigation";
|
||||||
|
|
||||||
|
interface ProfileColorContainerProps {
|
||||||
|
children: React.ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
const PageStructureContainer: React.FC<ProfileColorContainerProps> = ({
|
||||||
|
children,
|
||||||
|
}) => {
|
||||||
|
const pathname = usePathname();
|
||||||
|
|
||||||
|
const backgroundMap = {
|
||||||
|
"/profile": "bg-zinc-50",
|
||||||
|
"/library": "bg-gray-100",
|
||||||
|
};
|
||||||
|
|
||||||
|
const bgClass =
|
||||||
|
Object.entries(backgroundMap).find(([path]) =>
|
||||||
|
pathname.includes(path),
|
||||||
|
)?.[1] || "bg-white";
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={bgClass}>
|
||||||
|
<div className="mx-auto max-w-[1500px]">{children}</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default PageStructureContainer;
|
||||||
@@ -24,6 +24,8 @@ import {
|
|||||||
NotificationPreference,
|
NotificationPreference,
|
||||||
NotificationPreferenceDTO,
|
NotificationPreferenceDTO,
|
||||||
} from "@/lib/autogpt-server-api";
|
} from "@/lib/autogpt-server-api";
|
||||||
|
import AutogptInput from "@/components/agptui/AutogptInput";
|
||||||
|
import AutogptButton from "@/components/agptui/AutogptButton";
|
||||||
|
|
||||||
const formSchema = z
|
const formSchema = z
|
||||||
.object({
|
.object({
|
||||||
@@ -119,10 +121,10 @@ export default function SettingsForm({ user, preferences }: SettingsFormProps) {
|
|||||||
<Form {...form}>
|
<Form {...form}>
|
||||||
<form
|
<form
|
||||||
onSubmit={form.handleSubmit(onSubmit)}
|
onSubmit={form.handleSubmit(onSubmit)}
|
||||||
className="flex flex-col gap-8"
|
className="flex flex-col gap-10"
|
||||||
>
|
>
|
||||||
{/* Account Settings Section */}
|
{/* Account Settings Section */}
|
||||||
<div className="flex flex-col gap-4">
|
<div className="flex max-w-3xl flex-col gap-4">
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="email"
|
name="email"
|
||||||
@@ -130,7 +132,7 @@ export default function SettingsForm({ user, preferences }: SettingsFormProps) {
|
|||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>Email</FormLabel>
|
<FormLabel>Email</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input {...field} type="email" />
|
<AutogptInput {...field} type="email" />
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
@@ -144,7 +146,7 @@ export default function SettingsForm({ user, preferences }: SettingsFormProps) {
|
|||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>New Password</FormLabel>
|
<FormLabel>New Password</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input
|
<AutogptInput
|
||||||
{...field}
|
{...field}
|
||||||
type="password"
|
type="password"
|
||||||
placeholder="************"
|
placeholder="************"
|
||||||
@@ -162,7 +164,7 @@ export default function SettingsForm({ user, preferences }: SettingsFormProps) {
|
|||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>Confirm New Password</FormLabel>
|
<FormLabel>Confirm New Password</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input
|
<AutogptInput
|
||||||
{...field}
|
{...field}
|
||||||
type="password"
|
type="password"
|
||||||
placeholder="************"
|
placeholder="************"
|
||||||
@@ -174,11 +176,13 @@ export default function SettingsForm({ user, preferences }: SettingsFormProps) {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Separator />
|
<Separator className="bg-neutral-300" />
|
||||||
|
|
||||||
{/* Notifications Section */}
|
{/* Notifications Section */}
|
||||||
<div className="flex flex-col gap-6">
|
<div className="flex max-w-3xl flex-col gap-6">
|
||||||
<h3 className="text-lg font-medium">Notifications</h3>
|
<h3 className="font-poppins text-base font-medium text-neutral-900">
|
||||||
|
Notifications
|
||||||
|
</h3>
|
||||||
|
|
||||||
{/* Agent Notifications */}
|
{/* Agent Notifications */}
|
||||||
<div className="flex flex-col gap-4">
|
<div className="flex flex-col gap-4">
|
||||||
@@ -379,22 +383,25 @@ export default function SettingsForm({ user, preferences }: SettingsFormProps) {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<Separator className="bg-neutral-300" />
|
||||||
|
|
||||||
{/* Form Actions */}
|
{/* Form Actions */}
|
||||||
<div className="flex justify-end gap-4">
|
<div className="flex justify-end gap-4">
|
||||||
<Button
|
<AutogptButton
|
||||||
variant="outline"
|
variant="outline"
|
||||||
type="button"
|
type="button"
|
||||||
onClick={onCancel}
|
onClick={onCancel}
|
||||||
disabled={form.formState.isSubmitting}
|
disabled={form.formState.isSubmitting}
|
||||||
>
|
>
|
||||||
Cancel
|
Cancel
|
||||||
</Button>
|
</AutogptButton>
|
||||||
<Button
|
<AutogptButton
|
||||||
|
variant={"default"}
|
||||||
type="submit"
|
type="submit"
|
||||||
disabled={form.formState.isSubmitting || !form.formState.isDirty}
|
disabled={form.formState.isSubmitting || !form.formState.isDirty}
|
||||||
>
|
>
|
||||||
{form.formState.isSubmitting ? "Saving..." : "Save changes"}
|
{form.formState.isSubmitting ? "Saving..." : "Save changes"}
|
||||||
</Button>
|
</AutogptButton>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</Form>
|
</Form>
|
||||||
|
|||||||
@@ -26,10 +26,6 @@ const meta = {
|
|||||||
export default meta;
|
export default meta;
|
||||||
type Story = StoryObj<typeof meta>;
|
type Story = StoryObj<typeof meta>;
|
||||||
|
|
||||||
export const Default: Story = {
|
|
||||||
args: {},
|
|
||||||
};
|
|
||||||
|
|
||||||
export const SingleSelection: Story = {
|
export const SingleSelection: Story = {
|
||||||
args: {
|
args: {
|
||||||
mode: "single",
|
mode: "single",
|
||||||
@@ -56,15 +52,3 @@ export const RangeSelection: Story = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const HideOutsideDays: Story = {
|
|
||||||
args: {
|
|
||||||
showOutsideDays: false,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export const CustomClassName: Story = {
|
|
||||||
args: {
|
|
||||||
className: "border rounded-lg shadow-lg",
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -63,8 +63,7 @@ function Calendar({
|
|||||||
range_end: "range-end",
|
range_end: "range-end",
|
||||||
selected:
|
selected:
|
||||||
"bg-neutral-900 text-neutral-100 hover:bg-neutral-900 hover:text-neutral-50 focus:bg-neutral-700 focus:text-neutral-50 dark:bg-neutral-50 dark:text-neutral-900 dark:hover:bg-neutral-50 dark:hover:text-neutral-900 dark:focus:bg-neutral-50 dark:focus:text-neutral-900",
|
"bg-neutral-900 text-neutral-100 hover:bg-neutral-900 hover:text-neutral-50 focus:bg-neutral-700 focus:text-neutral-50 dark:bg-neutral-50 dark:text-neutral-900 dark:hover:bg-neutral-50 dark:hover:text-neutral-900 dark:focus:bg-neutral-50 dark:focus:text-neutral-900",
|
||||||
today:
|
today: "bg-neutral-100 dark:bg-neutral-800 dark:text-neutral-50",
|
||||||
"bg-neutral-100 text-neutral-900 dark:bg-neutral-800 dark:text-neutral-50",
|
|
||||||
outside:
|
outside:
|
||||||
"day-outside text-neutral-500 opacity-50 aria-selected:bg-neutral-100/50 aria-selected:text-neutral-500 aria-selected:opacity-30 dark:text-neutral-400 dark:aria-selected:bg-neutral-800/50 dark:aria-selected:text-neutral-400",
|
"day-outside text-neutral-500 opacity-50 aria-selected:bg-neutral-100/50 aria-selected:text-neutral-500 aria-selected:opacity-30 dark:text-neutral-400 dark:aria-selected:bg-neutral-800/50 dark:aria-selected:text-neutral-400",
|
||||||
disabled: "text-neutral-500 opacity-50 dark:text-neutral-400",
|
disabled: "text-neutral-500 opacity-50 dark:text-neutral-400",
|
||||||
|
|||||||
@@ -186,7 +186,7 @@ const CarouselItem = React.forwardRef<
|
|||||||
aria-roledescription="slide"
|
aria-roledescription="slide"
|
||||||
className={cn(
|
className={cn(
|
||||||
"min-w-0 shrink-0 grow-0 basis-full",
|
"min-w-0 shrink-0 grow-0 basis-full",
|
||||||
orientation === "horizontal" ? "pl-4" : "pt-4",
|
orientation === "horizontal" ? "pl-5" : "pt-5",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
@@ -211,9 +211,9 @@ const CarouselPrevious = React.forwardRef<
|
|||||||
variant={variant}
|
variant={variant}
|
||||||
size={size}
|
size={size}
|
||||||
className={cn(
|
className={cn(
|
||||||
"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"
|
orientation === "horizontal"
|
||||||
? "right-20 top-0"
|
? "right-18 top-0 md:right-24"
|
||||||
: "-top-12 left-1/2 -translate-x-1/2 rotate-90",
|
: "-top-12 left-1/2 -translate-x-1/2 rotate-90",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
@@ -226,7 +226,7 @@ const CarouselPrevious = React.forwardRef<
|
|||||||
}}
|
}}
|
||||||
{...props}
|
{...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>
|
<span className="sr-only">Previous slide</span>
|
||||||
</Button>
|
</Button>
|
||||||
);
|
);
|
||||||
@@ -257,9 +257,9 @@ const CarouselNext = React.forwardRef<
|
|||||||
variant={variant}
|
variant={variant}
|
||||||
size={size}
|
size={size}
|
||||||
className={cn(
|
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"
|
orientation === "horizontal"
|
||||||
? "right-4 top-0"
|
? "right-6 top-0 md:right-12"
|
||||||
: "-bottom-12 left-1/2 -translate-x-1/2 rotate-90",
|
: "-bottom-12 left-1/2 -translate-x-1/2 rotate-90",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
@@ -267,7 +267,7 @@ const CarouselNext = React.forwardRef<
|
|||||||
onClick={handleClick}
|
onClick={handleClick}
|
||||||
{...props}
|
{...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>
|
<span className="sr-only">Next slide</span>
|
||||||
</Button>
|
</Button>
|
||||||
);
|
);
|
||||||
@@ -302,7 +302,7 @@ const CarouselIndicator = React.forwardRef<
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn("relative top-7 flex h-3 items-center gap-2", className)}
|
className={cn("relative top-3.5 flex h-3 items-center gap-2", className)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
{scrollSnaps.map((_, index) => (
|
{scrollSnaps.map((_, index) => (
|
||||||
@@ -311,8 +311,8 @@ const CarouselIndicator = React.forwardRef<
|
|||||||
onClick={() => scrollTo(index)}
|
onClick={() => scrollTo(index)}
|
||||||
className={cn(
|
className={cn(
|
||||||
selectedIndex === index
|
selectedIndex === index
|
||||||
? "h-3 w-[52px] rounded-[39px] bg-neutral-800 transition-all duration-500 dark:bg-neutral-200"
|
? "h-3 w-[52px] rounded-[39px] bg-zinc-600 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-3 rounded-full bg-zinc-300 transition-all duration-500 dark:bg-neutral-600",
|
||||||
"cursor-pointer",
|
"cursor-pointer",
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ const TableRow = React.forwardRef<
|
|||||||
<tr
|
<tr
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn(
|
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,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
@@ -73,7 +73,7 @@ const TableHead = React.forwardRef<
|
|||||||
<th
|
<th
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn(
|
className={cn(
|
||||||
"h-10 px-2 text-left align-middle font-medium text-neutral-500 dark:text-neutral-400 [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]",
|
"h-10 pr-2.5 text-left align-middle font-medium text-neutral-500 dark:text-neutral-400 [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
@@ -88,7 +88,7 @@ const TableCell = React.forwardRef<
|
|||||||
<td
|
<td
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn(
|
className={cn(
|
||||||
"p-2 align-middle [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]",
|
"py-3 align-middle [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user