mirror of
https://github.com/Significant-Gravitas/AutoGPT.git
synced 2026-04-08 03:00:28 -04:00
integrated navbar
This commit is contained in:
@@ -131,6 +131,40 @@ async def get_store_agent_details(
|
||||
) from e
|
||||
|
||||
|
||||
async def get_user_profile(
|
||||
user_id: str,
|
||||
) -> backend.server.v2.store.model.ProfileDetails:
|
||||
logger.debug(f"Getting user profile for {user_id}")
|
||||
|
||||
try:
|
||||
profile = await prisma.models.Profile.prisma().find_unique(
|
||||
where={"userId": user_id} # type: ignore
|
||||
)
|
||||
|
||||
if not profile:
|
||||
logger.warning(f"Profile not found for user {user_id}")
|
||||
raise backend.server.v2.store.exceptions.ProfileNotFoundError(
|
||||
f"Profile not found for user {user_id}"
|
||||
)
|
||||
|
||||
return backend.server.v2.store.model.ProfileDetails(
|
||||
name=profile.name,
|
||||
username=profile.username,
|
||||
description=profile.description,
|
||||
links=profile.links,
|
||||
avatar_url=profile.avatarUrl,
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting user profile: {str(e)}")
|
||||
return backend.server.v2.store.model.ProfileDetails(
|
||||
name="No Profile Data",
|
||||
username="No Profile Data",
|
||||
description="No Profile Data",
|
||||
links=[],
|
||||
avatar_url="",
|
||||
)
|
||||
|
||||
|
||||
async def get_store_creators(
|
||||
featured: bool = False,
|
||||
search_query: str | None = None,
|
||||
|
||||
@@ -228,3 +228,55 @@ async def test_update_profile(mocker):
|
||||
# Verify mocks called correctly
|
||||
mock_profile_db.return_value.find_first.assert_called_once()
|
||||
mock_profile_db.return_value.update.assert_called_once()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_user_profile(mocker):
|
||||
# Mock data
|
||||
mock_profile = prisma.models.Profile(
|
||||
id="profile-id",
|
||||
name="Test User",
|
||||
username="testuser",
|
||||
description="Test description",
|
||||
links=["link1", "link2"],
|
||||
avatarUrl="avatar.jpg",
|
||||
createdAt=datetime.now(),
|
||||
updatedAt=datetime.now(),
|
||||
)
|
||||
|
||||
# Mock prisma calls
|
||||
mock_profile_db = mocker.patch("prisma.models.Profile.prisma")
|
||||
mock_profile_db.return_value.find_unique = mocker.AsyncMock(
|
||||
return_value=mock_profile
|
||||
)
|
||||
|
||||
# Call function
|
||||
result = await db.get_user_profile("user-id")
|
||||
|
||||
# Verify results
|
||||
assert result.name == "Test User"
|
||||
assert result.username == "testuser"
|
||||
assert result.description == "Test description"
|
||||
assert result.links == ["link1", "link2"]
|
||||
assert result.avatar_url == "avatar.jpg"
|
||||
|
||||
# Verify mock called correctly
|
||||
mock_profile_db.return_value.find_unique.assert_called_once_with(
|
||||
where={"userId": "user-id"}
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_user_profile_not_found(mocker):
|
||||
# Mock prisma calls to return None
|
||||
mock_profile_db = mocker.patch("prisma.models.Profile.prisma")
|
||||
mock_profile_db.return_value.find_unique = mocker.AsyncMock(return_value=None)
|
||||
|
||||
# Verify exception raised
|
||||
with pytest.raises(backend.server.v2.store.exceptions.ProfileNotFoundError):
|
||||
await db.get_user_profile("user-id")
|
||||
|
||||
# Verify mock called correctly
|
||||
mock_profile_db.return_value.find_unique.assert_called_once_with(
|
||||
where={"userId": "user-id"}
|
||||
)
|
||||
|
||||
@@ -62,3 +62,9 @@ class DatabaseError(StoreError):
|
||||
"""Raised when there is an error interacting with the database"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class ProfileNotFoundError(StoreError):
|
||||
"""Raised when a profile is not found"""
|
||||
|
||||
pass
|
||||
|
||||
@@ -102,3 +102,11 @@ class StoreSubmissionRequest(pydantic.BaseModel):
|
||||
image_urls: list[str] = []
|
||||
description: str = ""
|
||||
categories: list[str] = []
|
||||
|
||||
|
||||
class ProfileDetails(pydantic.BaseModel):
|
||||
name: str
|
||||
username: str
|
||||
description: str
|
||||
links: list[str]
|
||||
avatar_url: str | None = None
|
||||
|
||||
@@ -14,8 +14,31 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
router = fastapi.APIRouter()
|
||||
|
||||
|
||||
##############################################
|
||||
############### Agent Endpoints #############
|
||||
############### Profile Endpoints ############
|
||||
##############################################
|
||||
|
||||
|
||||
@router.get("/profile", tags=["store", "private"])
|
||||
async def get_profile(
|
||||
user_id: typing.Annotated[
|
||||
str, fastapi.Depends(autogpt_libs.auth.depends.get_user_id)
|
||||
]
|
||||
) -> backend.server.v2.store.model.ProfileDetails:
|
||||
"""
|
||||
Get the profile details for the authenticated user.
|
||||
"""
|
||||
try:
|
||||
profile = await backend.server.v2.store.db.get_user_profile(user_id)
|
||||
return profile
|
||||
except Exception:
|
||||
logger.exception("Exception occurred whilst getting user profile")
|
||||
raise
|
||||
|
||||
|
||||
##############################################
|
||||
############### Agent Endpoints ##############
|
||||
##############################################
|
||||
|
||||
|
||||
|
||||
@@ -1,12 +1,19 @@
|
||||
import AutoGPTServerAPI from "@/lib/autogpt-server-api";
|
||||
import { CreatorDetails as Creator, StoreAgent } from "@/lib/autogpt-server-api";
|
||||
import {
|
||||
CreatorDetails as Creator,
|
||||
StoreAgent,
|
||||
} from "@/lib/autogpt-server-api";
|
||||
import { AgentsSection } from "@/components/agptui/composite/AgentsSection";
|
||||
import { BreadCrumbs } from "@/components/agptui/BreadCrumbs";
|
||||
import { Metadata } from "next";
|
||||
import { CreatorInfoCard } from "@/components/agptui/CreatorInfoCard";
|
||||
import { CreatorLinks } from "@/components/agptui/CreatorLinks";
|
||||
|
||||
export async function generateMetadata({ params }: { params: { creator: string } }): Promise<Metadata> {
|
||||
export async function generateMetadata({
|
||||
params,
|
||||
}: {
|
||||
params: { creator: string };
|
||||
}): Promise<Metadata> {
|
||||
const api = new AutoGPTServerAPI();
|
||||
const creator = await api.getStoreCreator(params.creator);
|
||||
|
||||
@@ -21,7 +28,7 @@ export async function generateStaticParams() {
|
||||
const creators = await api.getStoreCreators({ featured: true });
|
||||
return creators.creators.map((creator) => ({
|
||||
creator: creator.username,
|
||||
lang: 'en'
|
||||
lang: "en",
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -31,18 +38,22 @@ export default async function Page({
|
||||
params: { lang: string; creator: string };
|
||||
}) {
|
||||
const api = new AutoGPTServerAPI();
|
||||
|
||||
|
||||
try {
|
||||
const creator = await api.getStoreCreator(params.creator);
|
||||
const creatorAgents = await api.getStoreAgents({ creator: params.creator });
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="w-full px-4 sm:px-6 md:px-10 py-4 sm:py-6 md:py-8">
|
||||
<div className="w-full px-4 py-4 sm:px-6 sm:py-6 md:px-10 md:py-8">
|
||||
<BreadCrumbs
|
||||
items={[
|
||||
{ name: "Store", link: "/store" },
|
||||
{ name: creator.name, link: "#" },
|
||||
]}
|
||||
/>
|
||||
|
||||
<BreadCrumbs items={[{ name: "Store", link: "/store" }, { name: creator.name, link: "#" }]}/>
|
||||
|
||||
<div className="mt-4 sm:mt-6 md:mt-8 flex flex-col md:flex-row items-start gap-4 sm:gap-6 md:gap-8">
|
||||
<div className="mt-4 flex flex-col items-start gap-4 sm:mt-6 sm:gap-6 md:mt-8 md:flex-row md:gap-8">
|
||||
<div className="w-full md:w-auto md:shrink-0">
|
||||
<CreatorInfoCard
|
||||
username={creator.name}
|
||||
@@ -54,7 +65,7 @@ export default async function Page({
|
||||
/>
|
||||
</div>
|
||||
<div className="flex min-w-0 flex-1 flex-col gap-4 sm:gap-6 md:gap-8">
|
||||
<div className="font-neue text-2xl sm:text-3xl md:text-[35px] font-normal leading-normal md:leading-[45px] text-neutral-900">
|
||||
<div className="font-neue text-2xl font-normal leading-normal text-neutral-900 sm:text-3xl md:text-[35px] md:leading-[45px]">
|
||||
{creator.description}
|
||||
</div>
|
||||
<CreatorLinks links={creator.links} />
|
||||
@@ -68,17 +79,16 @@ export default async function Page({
|
||||
sectionTitle={`Agents by ${creator.name}`}
|
||||
/>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
} catch (error) {
|
||||
return (
|
||||
<div className="w-full h-screen flex items-center justify-center">
|
||||
<div className="flex h-screen w-full items-center justify-center">
|
||||
<div className="font-neue text-2xl text-neutral-900">
|
||||
Creator not found
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,14 +2,14 @@ import React from "react";
|
||||
import type { Metadata } from "next";
|
||||
import { Inter } from "next/font/google";
|
||||
import { Providers } from "@/app/providers";
|
||||
import { NavBar } from "@/components/NavBar";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { Navbar } from "@/components/agptui/Navbar";
|
||||
|
||||
import "./globals.css";
|
||||
import TallyPopupSimple from "@/components/TallyPopup";
|
||||
import { GoogleAnalytics } from "@next/third-parties/google";
|
||||
import { Toaster } from "@/components/ui/toaster";
|
||||
|
||||
import { IconType } from "@/components/ui/icons";
|
||||
import { createServerClient } from "@/lib/supabase/server";
|
||||
|
||||
// Import Fonts
|
||||
@@ -45,8 +45,68 @@ export default async function RootLayout({
|
||||
// enableSystem
|
||||
disableTransitionOnChange
|
||||
>
|
||||
<div className="flex min-h-screen flex-col">
|
||||
<NavBar />
|
||||
<div className="flex min-h-screen flex-col items-center justify-center">
|
||||
<Navbar
|
||||
user={user}
|
||||
isLoggedIn={!!user}
|
||||
activeLink={"/store"}
|
||||
links={[
|
||||
{
|
||||
name: "Agent Store",
|
||||
href: "/store",
|
||||
},
|
||||
{
|
||||
name: "Library",
|
||||
href: "/library",
|
||||
},
|
||||
{
|
||||
name: "Build",
|
||||
href: "/build",
|
||||
},
|
||||
]}
|
||||
menuItemGroups={[
|
||||
{
|
||||
items: [
|
||||
{
|
||||
icon: IconType.Edit,
|
||||
text: "Edit profile",
|
||||
href: "/profile/edit",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
items: [
|
||||
{
|
||||
icon: IconType.LayoutDashboard,
|
||||
text: "Creator Dashboard",
|
||||
href: "/dashboard",
|
||||
},
|
||||
{
|
||||
icon: IconType.UploadCloud,
|
||||
text: "Publish an agent",
|
||||
href: "/publish",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
items: [
|
||||
{
|
||||
icon: IconType.Settings,
|
||||
text: "Settings",
|
||||
href: "/settings",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
items: [
|
||||
{
|
||||
icon: IconType.LogOut,
|
||||
text: "Log out",
|
||||
},
|
||||
],
|
||||
},
|
||||
]}
|
||||
/>
|
||||
<main className="flex-1 p-4">{children}</main>
|
||||
<TallyPopupSimple />
|
||||
</div>
|
||||
|
||||
@@ -38,4 +38,4 @@ export const BreadCrumbs: React.FC<BreadCrumbsProps> = ({ items }) => {
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
};
|
||||
|
||||
@@ -52,4 +52,4 @@ export const ExperiencedCreator: Story = {
|
||||
averageRating: 4.9,
|
||||
totalRuns: 50000,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
@@ -20,7 +20,7 @@ export const CreatorInfoCard: React.FC<CreatorInfoCardProps> = ({
|
||||
totalRuns,
|
||||
}) => {
|
||||
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 sm:h-[632px] sm:w-[440px] sm:p-6"
|
||||
role="article"
|
||||
aria-label={`Creator profile for ${username}`}
|
||||
@@ -33,23 +33,23 @@ export const CreatorInfoCard: React.FC<CreatorInfoCardProps> = ({
|
||||
</AvatarFallback>
|
||||
</Avatar>
|
||||
<div className="flex w-full flex-col items-start justify-start gap-1.5">
|
||||
<div className="w-full font-poppins text-2xl sm:text-[35px] font-medium leading-8 sm:leading-10 text-neutral-900">
|
||||
<div className="font-poppins w-full text-2xl font-medium leading-8 text-neutral-900 sm:text-[35px] sm:leading-10">
|
||||
{username}
|
||||
</div>
|
||||
<div className="w-full font-neue text-lg sm:text-xl font-normal leading-6 sm:leading-7 text-neutral-800">
|
||||
<div className="w-full font-neue text-lg font-normal leading-6 text-neutral-800 sm:text-xl sm:leading-7">
|
||||
@{handle}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex w-full flex-col items-start justify-start gap-6 sm:gap-[50px] my-4">
|
||||
<div className="my-4 flex w-full flex-col items-start justify-start gap-6 sm:gap-[50px]">
|
||||
<div className="flex w-full flex-col items-start justify-start gap-3">
|
||||
<div className="h-px w-full bg-neutral-700" />
|
||||
<div className="flex flex-col items-start justify-start gap-2.5">
|
||||
<div className="w-full font-neue text-base font-medium leading-normal text-neutral-800">
|
||||
Top categories
|
||||
</div>
|
||||
<div
|
||||
<div
|
||||
className="flex flex-wrap items-center gap-2.5"
|
||||
role="list"
|
||||
aria-label="Categories"
|
||||
@@ -71,8 +71,8 @@ export const CreatorInfoCard: React.FC<CreatorInfoCardProps> = ({
|
||||
|
||||
<div className="flex w-full flex-col items-start justify-start gap-3">
|
||||
<div className="h-px w-full bg-neutral-700" />
|
||||
<div className="flex w-full flex-col sm:flex-row justify-between items-start gap-4 sm:gap-0">
|
||||
<div className="w-full sm:w-[164px] flex flex-col items-start justify-start gap-2.5">
|
||||
<div className="flex w-full flex-col items-start justify-between gap-4 sm:flex-row sm:gap-0">
|
||||
<div className="flex w-full flex-col items-start justify-start gap-2.5 sm:w-[164px]">
|
||||
<div className="w-full font-neue text-base font-medium leading-normal text-neutral-800">
|
||||
Average rating
|
||||
</div>
|
||||
@@ -80,16 +80,16 @@ export const CreatorInfoCard: React.FC<CreatorInfoCardProps> = ({
|
||||
<div className="font-neue text-lg font-semibold leading-7 text-neutral-800">
|
||||
{averageRating.toFixed(1)}
|
||||
</div>
|
||||
<div
|
||||
className="flex items-center gap-px"
|
||||
role="img"
|
||||
<div
|
||||
className="flex items-center gap-px"
|
||||
role="img"
|
||||
aria-label={`Rating: ${averageRating} out of 5 stars`}
|
||||
>
|
||||
{StarRatingIcons(averageRating)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="w-full sm:w-[164px] flex flex-col items-start justify-start gap-2.5">
|
||||
<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">
|
||||
Number of runs
|
||||
</div>
|
||||
@@ -102,4 +102,4 @@ export const CreatorInfoCard: React.FC<CreatorInfoCardProps> = ({
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
};
|
||||
|
||||
@@ -25,10 +25,7 @@ export const Default: Story = {
|
||||
website: "https://example.com",
|
||||
linkedin: "https://linkedin.com/in/johndoe",
|
||||
github: "https://github.com/johndoe",
|
||||
other: [
|
||||
"https://twitter.com/johndoe",
|
||||
"https://medium.com/@johndoe",
|
||||
],
|
||||
other: ["https://twitter.com/johndoe", "https://medium.com/@johndoe"],
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -71,4 +68,4 @@ export const MultipleOtherLinks: Story = {
|
||||
],
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
@@ -33,11 +33,9 @@ export const CreatorLinks: React.FC<CreatorLinksProps> = ({ links }) => {
|
||||
</div>
|
||||
<div className="flex w-full flex-wrap gap-3">
|
||||
{links.map((link, index) => (
|
||||
<React.Fragment key={index}>
|
||||
{renderLinkButton(link)}
|
||||
</React.Fragment>
|
||||
<React.Fragment key={index}>{renderLinkButton(link)}</React.Fragment>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,16 +1,27 @@
|
||||
"use client";
|
||||
|
||||
import { IconRefresh } from "@/components/ui/icons";
|
||||
import AutoGPTServerAPI from "@/lib/autogpt-server-api";
|
||||
import { useState } from "react";
|
||||
|
||||
interface CreditsCardProps {
|
||||
credits: number;
|
||||
onRefresh?: () => void;
|
||||
}
|
||||
|
||||
const CreditsCard = ({ credits, onRefresh }: CreditsCardProps) => {
|
||||
const CreditsCard = ({ credits }: CreditsCardProps) => {
|
||||
const [currentCredits, setCurrentCredits] = useState(credits);
|
||||
const api = new AutoGPTServerAPI();
|
||||
|
||||
const onRefresh = async () => {
|
||||
const { credits } = await api.getUserCredit();
|
||||
setCurrentCredits(credits);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="inline-flex h-[60px] items-center gap-2.5 rounded-2xl bg-neutral-200 p-4">
|
||||
<div className="flex items-center gap-0.5">
|
||||
<span className="font-['Geist'] text-base font-semibold leading-7 text-neutral-900">
|
||||
{credits.toLocaleString()}
|
||||
{currentCredits.toLocaleString()}
|
||||
</span>
|
||||
<span className="font-['Geist'] text-base font-normal leading-7 text-neutral-900">
|
||||
credits
|
||||
|
||||
@@ -1,7 +1,32 @@
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
import { Navbar } from "./Navbar";
|
||||
import Navbar from "./Navbar";
|
||||
import { userEvent, within } from "@storybook/test";
|
||||
import { IconType } from "../ui/icons";
|
||||
import { ProfileDetails } from "@/lib/autogpt-server-api/types";
|
||||
import { jest } from "@jest/globals";
|
||||
|
||||
// Mock the API responses
|
||||
const mockProfileData: ProfileDetails = {
|
||||
name: "John Doe",
|
||||
username: "johndoe",
|
||||
description: "",
|
||||
links: [],
|
||||
avatar_url: "https://avatars.githubusercontent.com/u/123456789?v=4",
|
||||
};
|
||||
|
||||
const mockCreditData = {
|
||||
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 = {
|
||||
title: "AGPT UI/Navbar",
|
||||
@@ -12,12 +37,11 @@ const meta = {
|
||||
tags: ["autodocs"],
|
||||
argTypes: {
|
||||
isLoggedIn: { control: "boolean" },
|
||||
userName: { control: "text" },
|
||||
avatarSrc: { control: "text" },
|
||||
links: { control: "object" },
|
||||
activeLink: { control: "text" },
|
||||
avatarSrc: { control: "text" },
|
||||
userEmail: { control: "text" },
|
||||
menuItemGroups: { control: "object" },
|
||||
params: { control: { type: "object", defaultValue: { lang: "en" } } },
|
||||
},
|
||||
} satisfies Meta<typeof Navbar>;
|
||||
|
||||
@@ -66,15 +90,12 @@ const defaultLinks = [
|
||||
|
||||
export const Default: Story = {
|
||||
args: {
|
||||
params: { lang: "en" },
|
||||
isLoggedIn: true,
|
||||
userName: "John Doe",
|
||||
links: defaultLinks,
|
||||
activeLink: "/marketplace",
|
||||
avatarSrc: "https://avatars.githubusercontent.com/u/123456789?v=4",
|
||||
userEmail: "john.doe@example.com",
|
||||
avatarSrc: mockProfileData.avatar_url,
|
||||
menuItemGroups: defaultMenuItemGroups,
|
||||
credits: 1500,
|
||||
onRefreshCredits: () => console.log("Refreshing credits"),
|
||||
},
|
||||
};
|
||||
|
||||
@@ -88,8 +109,6 @@ export const WithActiveLink: Story = {
|
||||
export const LongUserName: Story = {
|
||||
args: {
|
||||
...Default.args,
|
||||
userName: "Alexander Bartholomew Christopherson III",
|
||||
userEmail: "alexander@example.com",
|
||||
avatarSrc: "https://avatars.githubusercontent.com/u/987654321?v=4",
|
||||
},
|
||||
};
|
||||
@@ -120,30 +139,24 @@ export const NotLoggedIn: Story = {
|
||||
args: {
|
||||
...Default.args,
|
||||
isLoggedIn: false,
|
||||
userName: undefined,
|
||||
userEmail: undefined,
|
||||
avatarSrc: undefined,
|
||||
credits: undefined,
|
||||
},
|
||||
};
|
||||
|
||||
export const WithCredits: Story = {
|
||||
args: {
|
||||
...Default.args,
|
||||
credits: 9999,
|
||||
},
|
||||
};
|
||||
|
||||
export const WithLargeCredits: Story = {
|
||||
args: {
|
||||
...Default.args,
|
||||
credits: 1000000,
|
||||
},
|
||||
};
|
||||
|
||||
export const WithZeroCredits: Story = {
|
||||
args: {
|
||||
...Default.args,
|
||||
credits: 0,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -5,6 +5,9 @@ import { IconType, IconLogIn } from "@/components/ui/icons";
|
||||
import { MobileNavBar } from "./MobileNavBar";
|
||||
import { Button } from "./Button";
|
||||
import CreditsCard from "./CreditsCard";
|
||||
import { ProfileDetails } from "@/lib/autogpt-server-api/types";
|
||||
import { User } from "@supabase/supabase-js";
|
||||
import AutoGPTServerAPIServerSide from "@/lib/autogpt-server-api/clientServer";
|
||||
|
||||
interface NavLink {
|
||||
name: string;
|
||||
@@ -12,14 +15,10 @@ interface NavLink {
|
||||
}
|
||||
|
||||
interface NavbarProps {
|
||||
user: User | null;
|
||||
isLoggedIn: boolean;
|
||||
userName?: string;
|
||||
links: NavLink[];
|
||||
activeLink: string;
|
||||
avatarSrc?: string;
|
||||
userEmail?: string;
|
||||
credits?: number;
|
||||
onRefreshCredits?: () => void;
|
||||
menuItemGroups: {
|
||||
groupName?: string;
|
||||
items: {
|
||||
@@ -31,49 +30,37 @@ interface NavbarProps {
|
||||
}[];
|
||||
}
|
||||
|
||||
{
|
||||
/* <div className="w-[1408px] h-20 pl-6 pr-3 py-3 bg-white/5 rounded-bl-2xl rounded-br-2xl border border-white/50 backdrop-blur-[26px] justify-between items-center inline-flex">
|
||||
<div className="justify-start items-center gap-11 flex">
|
||||
<div className="w-[88.87px] h-10 relative" />
|
||||
<div className="justify-start items-center gap-6 flex">
|
||||
<div className="px-5 py-4 bg-neutral-800 rounded-2xl justify-start items-center gap-3 flex">
|
||||
<div className="w-6 h-6 relative" />
|
||||
<div className="text-neutral-50 text-xl font-medium font-['Poppins'] leading-7">Marketplace</div>
|
||||
</div>
|
||||
<div className="px-5 justify-start items-center gap-3 flex">
|
||||
<div className="w-6 h-6 relative" />
|
||||
<div className="text-neutral-900 text-xl font-medium font-['Poppins'] leading-7">Monitor</div>
|
||||
</div>
|
||||
<div className="px-5 py-4 rounded-2xl justify-start items-center gap-3 flex">
|
||||
<div className="w-6 h-6 relative" />
|
||||
<div className="text-neutral-900 text-xl font-medium font-['Poppins'] leading-7">Build</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="justify-start items-center gap-4 flex">
|
||||
<div className="p-4 bg-neutral-200 rounded-2xl justify-start items-center gap-2.5 flex">
|
||||
<div className="justify-start items-center gap-0.5 flex">
|
||||
<div className="text-neutral-900 text-base font-semibold font-['Geist'] leading-7">1500</div>
|
||||
<div className="text-neutral-900 text-base font-normal font-['Geist'] leading-7">credits</div>
|
||||
</div>
|
||||
<div className="w-6 h-6 relative" />
|
||||
</div>
|
||||
<img className="w-[60px] h-[60px] rounded-full" src="https://via.placeholder.com/60x60" />
|
||||
</div>
|
||||
</div> */
|
||||
}
|
||||
async function getProfileData(user: User | null) {
|
||||
console.log(user);
|
||||
const api = new AutoGPTServerAPIServerSide();
|
||||
const [profile, credits] = await Promise.all([
|
||||
api.getStoreProfile(),
|
||||
api.getUserCredit(),
|
||||
]);
|
||||
|
||||
export const Navbar: React.FC<NavbarProps> = ({
|
||||
return {
|
||||
profile,
|
||||
credits,
|
||||
};
|
||||
}
|
||||
export const Navbar = async ({
|
||||
user,
|
||||
isLoggedIn,
|
||||
userName,
|
||||
links,
|
||||
activeLink,
|
||||
avatarSrc,
|
||||
userEmail,
|
||||
credits = 0,
|
||||
onRefreshCredits,
|
||||
menuItemGroups,
|
||||
}) => {
|
||||
}: NavbarProps) => {
|
||||
let profile: ProfileDetails | null = null;
|
||||
let credits: { credits: number } = { credits: 0 };
|
||||
if (isLoggedIn) {
|
||||
console.log("Fetching profile data");
|
||||
console.log(user);
|
||||
const { profile: t_profile, credits: t_credits } =
|
||||
await getProfileData(user);
|
||||
profile = t_profile;
|
||||
credits = t_credits;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<nav className="sticky top-0 hidden h-20 w-[1408px] 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] md:inline-flex">
|
||||
@@ -96,12 +83,12 @@ export const Navbar: React.FC<NavbarProps> = ({
|
||||
{/* Profile section */}
|
||||
{isLoggedIn ? (
|
||||
<div className="flex items-center gap-4">
|
||||
<CreditsCard credits={credits} onRefresh={onRefreshCredits} />
|
||||
{profile && <CreditsCard credits={credits.credits} />}
|
||||
<ProfilePopoutMenu
|
||||
menuItemGroups={menuItemGroups}
|
||||
userName={userName}
|
||||
userEmail={userEmail}
|
||||
avatarSrc={avatarSrc}
|
||||
userName={profile?.username}
|
||||
userEmail={profile?.name}
|
||||
avatarSrc={profile?.avatar_url}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
@@ -120,31 +107,32 @@ export const Navbar: React.FC<NavbarProps> = ({
|
||||
{/* Mobile Navbar - Adjust positioning */}
|
||||
<>
|
||||
{isLoggedIn ? (
|
||||
<MobileNavBar
|
||||
userName={userName}
|
||||
activeLink={activeLink}
|
||||
menuItemGroups={[
|
||||
{
|
||||
groupName: "Navigation",
|
||||
items: links.map((link) => ({
|
||||
icon:
|
||||
link.name === "Marketplace"
|
||||
? IconType.Marketplace
|
||||
: link.name === "Library"
|
||||
? IconType.Library
|
||||
: link.name === "Build"
|
||||
? IconType.Builder
|
||||
: IconType.LayoutDashboard,
|
||||
text: link.name,
|
||||
href: link.href,
|
||||
})),
|
||||
},
|
||||
...menuItemGroups,
|
||||
]}
|
||||
userEmail={userEmail}
|
||||
avatarSrc={avatarSrc}
|
||||
className="fixed right-4 top-4 z-50"
|
||||
/>
|
||||
<div className="fixed right-4 top-4 z-50">
|
||||
<MobileNavBar
|
||||
userName={profile?.username}
|
||||
activeLink={activeLink}
|
||||
menuItemGroups={[
|
||||
{
|
||||
groupName: "Navigation",
|
||||
items: links.map((link) => ({
|
||||
icon:
|
||||
link.name === "Agent Store"
|
||||
? IconType.Marketplace
|
||||
: link.name === "Library"
|
||||
? IconType.Library
|
||||
: link.name === "Build"
|
||||
? IconType.Builder
|
||||
: IconType.LayoutDashboard,
|
||||
text: link.name,
|
||||
href: link.href,
|
||||
})),
|
||||
},
|
||||
...menuItemGroups,
|
||||
]}
|
||||
userEmail={profile?.name}
|
||||
avatarSrc={profile?.avatar_url}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<Link
|
||||
href="/login"
|
||||
|
||||
@@ -138,48 +138,46 @@ export const ProfilePopoutMenu: React.FC<ProfilePopoutMenuProps> = ({
|
||||
</Avatar>
|
||||
</button>
|
||||
</PopoverTrigger>
|
||||
|
||||
|
||||
<PopoverContent
|
||||
id={popupId}
|
||||
className="w-[300px] h-[380px] p-6 bg-zinc-400/70 rounded-[26px] shadow backdrop-blur-2xl flex flex-col justify-start items-start gap-4"
|
||||
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"
|
||||
>
|
||||
{/* Header with avatar and user info */}
|
||||
<div className="self-stretch inline-flex justify-start items-center gap-4">
|
||||
<div className="inline-flex items-center justify-start gap-4 self-stretch">
|
||||
<Avatar className="h-[60px] w-[60px]">
|
||||
<AvatarImage src={avatarSrc} alt="" aria-hidden="true" />
|
||||
<AvatarFallback aria-hidden="true">
|
||||
{userName?.charAt(0) || "U"}
|
||||
</AvatarFallback>
|
||||
</Avatar>
|
||||
<div className="w-[173px] h-[47px] relative">
|
||||
<div className="left-0 top-0 absolute text-white text-base font-semibold font-['Geist'] leading-7">
|
||||
<div className="relative h-[47px] w-[173px]">
|
||||
<div className="absolute left-0 top-0 font-['Geist'] text-base font-semibold leading-7 text-white">
|
||||
{userName}
|
||||
</div>
|
||||
<div className="left-0 top-[23px] absolute text-white text-base font-normal font-['Geist'] leading-normal">
|
||||
<div className="absolute left-0 top-[23px] font-['Geist'] text-base font-normal leading-normal text-white">
|
||||
{userEmail}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Menu items */}
|
||||
<div className="w-full rounded-[23px] flex flex-col justify-start items-start gap-1.5">
|
||||
<div className="flex w-full flex-col items-start justify-start gap-1.5 rounded-[23px]">
|
||||
{menuItemGroups.map((group, groupIndex) => (
|
||||
<div
|
||||
key={groupIndex}
|
||||
className="w-full p-3.5 bg-white rounded-[18px] flex flex-col justify-start items-start gap-5"
|
||||
className="flex w-full flex-col items-start justify-start gap-5 rounded-[18px] bg-white p-3.5"
|
||||
>
|
||||
{group.items.map((item, itemIndex) => (
|
||||
<div
|
||||
key={itemIndex}
|
||||
className="w-full inline-flex justify-start items-center gap-2.5"
|
||||
className="inline-flex w-full items-center justify-start gap-2.5"
|
||||
onClick={item.onClick}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
>
|
||||
<div className="w-6 h-6 relative">
|
||||
{getIcon(item.icon)}
|
||||
</div>
|
||||
<div className="text-neutral-800 text-base font-medium font-['Geist'] leading-normal">
|
||||
<div className="relative h-6 w-6">{getIcon(item.icon)}</div>
|
||||
<div className="font-['Geist'] text-base font-medium leading-normal text-neutral-800">
|
||||
{item.text}
|
||||
</div>
|
||||
</div>
|
||||
@@ -210,4 +208,3 @@ const getIcon = (icon: IconType) => {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -82,18 +82,17 @@ const mockCreatorInfo = {
|
||||
website: "https://ailabs.com",
|
||||
github: "https://github.com/ailabs",
|
||||
linkedin: "https://linkedin.com/company/ailabs",
|
||||
other: [
|
||||
"https://twitter.com/ailabs",
|
||||
"https://medium.com/@ailabs",
|
||||
],
|
||||
other: ["https://twitter.com/ailabs", "https://medium.com/@ailabs"],
|
||||
},
|
||||
};
|
||||
|
||||
const mockCreatorAgents = [
|
||||
{
|
||||
agentName: "Super SEO Optimizer",
|
||||
agentImage: "https://ddz4ak4pa3d19.cloudfront.net/cache/cc/11/cc1172271dcf723a34f488a3344e82b2.jpg",
|
||||
description: "Boost your website's search engine rankings with our advanced AI-powered SEO optimization tool.",
|
||||
agentImage:
|
||||
"https://ddz4ak4pa3d19.cloudfront.net/cache/cc/11/cc1172271dcf723a34f488a3344e82b2.jpg",
|
||||
description:
|
||||
"Boost your website's search engine rankings with our advanced AI-powered SEO optimization tool.",
|
||||
runs: 100000,
|
||||
rating: 4.9,
|
||||
avatarSrc: "https://example.com/avatar1.jpg",
|
||||
@@ -208,7 +207,8 @@ export const LongDescription: Story = {
|
||||
...Default.args,
|
||||
creatorInfo: {
|
||||
...mockCreatorInfo,
|
||||
description: "We are a team of passionate developers and researchers dedicated to pushing the boundaries of artificial intelligence. Our focus spans across multiple domains including natural language processing, computer vision, and reinforcement learning. With years of experience in both academia and industry, we strive to create AI agents that are not only powerful but also ethical and user-friendly. Our mission is to make AI accessible to everyone while maintaining the highest standards of quality and reliability.",
|
||||
description:
|
||||
"We are a team of passionate developers and researchers dedicated to pushing the boundaries of artificial intelligence. Our focus spans across multiple domains including natural language processing, computer vision, and reinforcement learning. With years of experience in both academia and industry, we strive to create AI agents that are not only powerful but also ethical and user-friendly. Our mission is to make AI accessible to everyone while maintaining the highest standards of quality and reliability.",
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -75,10 +75,10 @@ export const CreatorPage: React.FC<CreatorPageProps> = ({
|
||||
activeLink={activeLink}
|
||||
menuItemGroups={menuItemGroups}
|
||||
/>
|
||||
<main className="w-full px-4 sm:px-6 md:px-10 py-4 sm:py-6 md:py-8">
|
||||
<main className="w-full px-4 py-4 sm:px-6 sm:py-6 md:px-10 md:py-8">
|
||||
<BreadCrumbs items={breadcrumbs} />
|
||||
|
||||
<div className="mt-4 sm:mt-6 md:mt-8 flex flex-col md:flex-row items-start gap-4 sm:gap-6 md:gap-8">
|
||||
|
||||
<div className="mt-4 flex flex-col items-start gap-4 sm:mt-6 sm:gap-6 md:mt-8 md:flex-row md:gap-8">
|
||||
<div className="w-full md:w-auto md:shrink-0">
|
||||
<CreatorInfoCard
|
||||
username={creatorInfo.name}
|
||||
@@ -90,7 +90,7 @@ export const CreatorPage: React.FC<CreatorPageProps> = ({
|
||||
/>
|
||||
</div>
|
||||
<div className="flex min-w-0 flex-1 flex-col gap-4 sm:gap-6 md:gap-8">
|
||||
<div className="font-neue text-2xl sm:text-3xl md:text-[35px] font-normal leading-normal md:leading-[45px] text-neutral-900">
|
||||
<div className="font-neue text-2xl font-normal leading-normal text-neutral-900 sm:text-3xl md:text-[35px] md:leading-[45px]">
|
||||
{creatorInfo.description}
|
||||
</div>
|
||||
<CreatorLinks links={creatorInfo.otherLinks} />
|
||||
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
ExecutionMeta,
|
||||
NodeExecutionResult,
|
||||
OAuth2Credentials,
|
||||
ProfileDetails,
|
||||
User,
|
||||
StoreAgentsResponse,
|
||||
StoreAgentDetails,
|
||||
@@ -58,7 +59,11 @@ export default class BaseAutoGPTServerAPI {
|
||||
}
|
||||
|
||||
getUserCredit(): Promise<{ credits: number }> {
|
||||
return this._get(`/credits`);
|
||||
try {
|
||||
return this._get(`/credits`);
|
||||
} catch (error) {
|
||||
return Promise.resolve({ credits: 0 });
|
||||
}
|
||||
}
|
||||
|
||||
getBlocks(): Promise<Block[]> {
|
||||
@@ -245,6 +250,15 @@ export default class BaseAutoGPTServerAPI {
|
||||
/////////// V2 STORE API /////////////////
|
||||
/////////////////////////////////////////
|
||||
|
||||
getStoreProfile(): Promise<ProfileDetails | null> {
|
||||
try {
|
||||
return this._get("/store/profile");
|
||||
} catch (error) {
|
||||
console.error("Error fetching store profile:", error);
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
}
|
||||
|
||||
getStoreAgents(params?: {
|
||||
featured?: boolean;
|
||||
creator?: string;
|
||||
@@ -320,7 +334,9 @@ export default class BaseAutoGPTServerAPI {
|
||||
|
||||
const token =
|
||||
(await this.supabaseClient?.auth.getSession())?.data.session
|
||||
?.access_token || "";
|
||||
?.access_token || "no-token-found";
|
||||
|
||||
console.log("Token for request type: ", method, path, token);
|
||||
|
||||
let url = this.baseUrl + path;
|
||||
if (method === "GET" && payload) {
|
||||
|
||||
@@ -419,3 +419,11 @@ export type StoreSubmissionRequest = {
|
||||
description: string;
|
||||
categories: string[];
|
||||
};
|
||||
|
||||
export type ProfileDetails = {
|
||||
name: string;
|
||||
username: string;
|
||||
description: string;
|
||||
links: string[];
|
||||
avatar_url: string;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user