mirror of
https://github.com/Significant-Gravitas/AutoGPT.git
synced 2026-02-09 22:35:54 -05:00
feat(frontend): new navbar design (#10341)
## Changes 🏗️ <img width="900" height="327" alt="Screenshot 2025-07-10 at 20 12 38" src="https://github.com/user-attachments/assets/044f00ed-7e05-46b7-a821-ce1cb0ee9298" /> <br /><br /> Navbar updated to look pretty from the new designs: - the logo is now centred instead of on the left - menu items have been updated to a smaller font-size and less radius - icons have been updated I also generated the API files ( _sorry for the noise_ ). I had to do some border-radius and button updates on the atoms/tokens for it to look good. ## Checklist 📋 ## For code changes: - [x] I have clearly listed my changes in the PR description - [x] I have made a test plan - [x] I have tested my changes according to the test plan: - [x] Login/logout - [x] The new navbar looks good across screens ## For configuration changes No config changes
This commit is contained in:
@@ -1,12 +1,13 @@
|
||||
"use client";
|
||||
|
||||
import { useSearchParams } from "next/navigation";
|
||||
import { GraphID } from "@/lib/autogpt-server-api/types";
|
||||
import FlowEditor from "@/components/Flow";
|
||||
import { useOnboarding } from "@/components/onboarding/onboarding-provider";
|
||||
import { useEffect } from "react";
|
||||
import LoadingBox from "@/components/ui/loading";
|
||||
import { GraphID } from "@/lib/autogpt-server-api/types";
|
||||
import { useSearchParams } from "next/navigation";
|
||||
import { Suspense, useEffect } from "react";
|
||||
|
||||
export default function BuilderPage() {
|
||||
function BuilderContent() {
|
||||
const query = useSearchParams();
|
||||
const { completeStep } = useOnboarding();
|
||||
|
||||
@@ -15,12 +16,20 @@ export default function BuilderPage() {
|
||||
}, [completeStep]);
|
||||
|
||||
const _graphVersion = query.get("flowVersion");
|
||||
const graphVersion = _graphVersion ? parseInt(_graphVersion) : undefined
|
||||
const graphVersion = _graphVersion ? parseInt(_graphVersion) : undefined;
|
||||
return (
|
||||
<FlowEditor
|
||||
className="flow-container"
|
||||
flowID={query.get("flowID") as GraphID | null ?? undefined}
|
||||
flowID={(query.get("flowID") as GraphID | null) ?? undefined}
|
||||
flowVersion={graphVersion}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default function BuilderPage() {
|
||||
return (
|
||||
<Suspense fallback={<LoadingBox className="h-[80vh]" />}>
|
||||
<BuilderContent />
|
||||
</Suspense>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,67 +1,10 @@
|
||||
import { Navbar } from "@/components/layout/Navbar/Navbar";
|
||||
import { ReactNode } from "react";
|
||||
import { Navbar } from "@/components/agptui/Navbar";
|
||||
import { IconType } from "@/components/ui/icons";
|
||||
|
||||
export default function PlatformLayout({ children }: { children: ReactNode }) {
|
||||
return (
|
||||
<>
|
||||
<Navbar
|
||||
links={[
|
||||
{
|
||||
name: "Marketplace",
|
||||
href: "/marketplace",
|
||||
},
|
||||
{
|
||||
name: "Library",
|
||||
href: "/library",
|
||||
},
|
||||
{
|
||||
name: "Build",
|
||||
href: "/build",
|
||||
},
|
||||
]}
|
||||
menuItemGroups={[
|
||||
{
|
||||
items: [
|
||||
{
|
||||
icon: IconType.Edit,
|
||||
text: "Edit profile",
|
||||
href: "/profile",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
items: [
|
||||
{
|
||||
icon: IconType.LayoutDashboard,
|
||||
text: "Creator Dashboard",
|
||||
href: "/profile/dashboard",
|
||||
},
|
||||
{
|
||||
icon: IconType.UploadCloud,
|
||||
text: "Publish an agent",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
items: [
|
||||
{
|
||||
icon: IconType.Settings,
|
||||
text: "Settings",
|
||||
href: "/profile/settings",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
items: [
|
||||
{
|
||||
icon: IconType.LogOut,
|
||||
text: "Log out",
|
||||
},
|
||||
],
|
||||
},
|
||||
]}
|
||||
/>
|
||||
<Navbar />
|
||||
<main>{children}</main>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
"use client";
|
||||
import Link from "next/link";
|
||||
|
||||
import { Alert, AlertDescription } from "@/components/ui/alert";
|
||||
import {
|
||||
ArrowBottomRightIcon,
|
||||
QuestionMarkCircledIcon,
|
||||
} from "@radix-ui/react-icons";
|
||||
import { Alert, AlertDescription } from "@/components/ui/alert";
|
||||
|
||||
import { LibraryPageStateProvider } from "./components/state-provider";
|
||||
import LibraryActionHeader from "./components/LibraryActionHeader/LibraryActionHeader";
|
||||
import LibraryAgentList from "./components/LibraryAgentList/LibraryAgentList";
|
||||
import { LibraryPageStateProvider } from "./components/state-provider";
|
||||
|
||||
/**
|
||||
* LibraryPage Component
|
||||
@@ -17,7 +17,7 @@ import LibraryAgentList from "./components/LibraryAgentList/LibraryAgentList";
|
||||
*/
|
||||
export default function LibraryPage() {
|
||||
return (
|
||||
<main className="container min-h-screen space-y-4 pb-20 sm:px-8 md:px-12">
|
||||
<main className="pt-160 container min-h-screen space-y-4 pb-20 pt-16 sm:px-8 md:px-12">
|
||||
<LibraryPageStateProvider>
|
||||
<LibraryActionHeader />
|
||||
<LibraryAgentList />
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -6,12 +6,12 @@
|
||||
* OpenAPI spec version: 0.1
|
||||
*/
|
||||
import type { CredentialsMetaInputTitle } from "./credentialsMetaInputTitle";
|
||||
import type { ProviderName } from "./providerName";
|
||||
import type { CredentialsMetaInputType } from "./credentialsMetaInputType";
|
||||
|
||||
export interface CredentialsMetaInput {
|
||||
id: string;
|
||||
title?: CredentialsMetaInputTitle;
|
||||
/** Provider name for integrations. Can be any string value, including custom provider names. */
|
||||
provider: string;
|
||||
provider: ProviderName;
|
||||
type: CredentialsMetaInputType;
|
||||
}
|
||||
|
||||
@@ -5,12 +5,12 @@
|
||||
* This server is used to execute agents that are created by the AutoGPT system.
|
||||
* OpenAPI spec version: 0.1
|
||||
*/
|
||||
import type { ProviderName } from "./providerName";
|
||||
import type { LibraryAgentTriggerInfoConfigSchema } from "./libraryAgentTriggerInfoConfigSchema";
|
||||
import type { LibraryAgentTriggerInfoCredentialsInputName } from "./libraryAgentTriggerInfoCredentialsInputName";
|
||||
|
||||
export interface LibraryAgentTriggerInfo {
|
||||
/** Provider name for integrations. Can be any string value, including custom provider names. */
|
||||
provider: string;
|
||||
provider: ProviderName;
|
||||
/** Input schema for the trigger block */
|
||||
config_schema: LibraryAgentTriggerInfoConfigSchema;
|
||||
credentials_input_name: LibraryAgentTriggerInfoCredentialsInputName;
|
||||
|
||||
53
autogpt_platform/frontend/src/app/api/__generated__/models/providerName.ts
generated
Normal file
53
autogpt_platform/frontend/src/app/api/__generated__/models/providerName.ts
generated
Normal file
@@ -0,0 +1,53 @@
|
||||
/**
|
||||
* Generated by orval v7.10.0 🍺
|
||||
* Do not edit manually.
|
||||
* AutoGPT Agent Server
|
||||
* This server is used to execute agents that are created by the AutoGPT system.
|
||||
* OpenAPI spec version: 0.1
|
||||
*/
|
||||
|
||||
export type ProviderName = (typeof ProviderName)[keyof typeof ProviderName];
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-redeclare
|
||||
export const ProviderName = {
|
||||
aiml_api: "aiml_api",
|
||||
anthropic: "anthropic",
|
||||
apollo: "apollo",
|
||||
compass: "compass",
|
||||
discord: "discord",
|
||||
d_id: "d_id",
|
||||
e2b: "e2b",
|
||||
exa: "exa",
|
||||
fal: "fal",
|
||||
generic_webhook: "generic_webhook",
|
||||
github: "github",
|
||||
google: "google",
|
||||
google_maps: "google_maps",
|
||||
groq: "groq",
|
||||
http: "http",
|
||||
hubspot: "hubspot",
|
||||
ideogram: "ideogram",
|
||||
jina: "jina",
|
||||
linear: "linear",
|
||||
llama_api: "llama_api",
|
||||
medium: "medium",
|
||||
mem0: "mem0",
|
||||
notion: "notion",
|
||||
nvidia: "nvidia",
|
||||
ollama: "ollama",
|
||||
openai: "openai",
|
||||
openweathermap: "openweathermap",
|
||||
open_router: "open_router",
|
||||
pinecone: "pinecone",
|
||||
reddit: "reddit",
|
||||
replicate: "replicate",
|
||||
revid: "revid",
|
||||
screenshotone: "screenshotone",
|
||||
slant3d: "slant3d",
|
||||
smartlead: "smartlead",
|
||||
smtp: "smtp",
|
||||
twitter: "twitter",
|
||||
todoist: "todoist",
|
||||
unreal_speech: "unreal_speech",
|
||||
zerobounce: "zerobounce",
|
||||
} as const;
|
||||
@@ -5,13 +5,13 @@
|
||||
* This server is used to execute agents that are created by the AutoGPT system.
|
||||
* OpenAPI spec version: 0.1
|
||||
*/
|
||||
import type { ProviderName } from "./providerName";
|
||||
import type { WebhookConfig } from "./webhookConfig";
|
||||
|
||||
export interface Webhook {
|
||||
id?: string;
|
||||
user_id: string;
|
||||
/** Provider name for integrations. Can be any string value, including custom provider names. */
|
||||
provider: string;
|
||||
provider: ProviderName;
|
||||
credentials_id: string;
|
||||
webhook_type: string;
|
||||
resource: string;
|
||||
|
||||
@@ -18,9 +18,8 @@
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"title": "The provider to initiate an OAuth flow for",
|
||||
"description": "Provider name for integrations. Can be any string value, including custom provider names."
|
||||
"$ref": "#/components/schemas/ProviderName",
|
||||
"title": "The provider to initiate an OAuth flow for"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -65,9 +64,8 @@
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"title": "The target provider for this OAuth exchange",
|
||||
"description": "Provider name for integrations. Can be any string value, including custom provider names."
|
||||
"$ref": "#/components/schemas/ProviderName",
|
||||
"title": "The target provider for this OAuth exchange"
|
||||
}
|
||||
}
|
||||
],
|
||||
@@ -135,9 +133,8 @@
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"title": "The provider to list credentials for",
|
||||
"description": "Provider name for integrations. Can be any string value, including custom provider names."
|
||||
"$ref": "#/components/schemas/ProviderName",
|
||||
"title": "The provider to list credentials for"
|
||||
}
|
||||
}
|
||||
],
|
||||
@@ -176,9 +173,8 @@
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"title": "The provider to create credentials for",
|
||||
"description": "Provider name for integrations. Can be any string value, including custom provider names."
|
||||
"$ref": "#/components/schemas/ProviderName",
|
||||
"title": "The provider to create credentials for"
|
||||
}
|
||||
}
|
||||
],
|
||||
@@ -257,9 +253,8 @@
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"title": "The provider to retrieve credentials for",
|
||||
"description": "Provider name for integrations. Can be any string value, including custom provider names."
|
||||
"$ref": "#/components/schemas/ProviderName",
|
||||
"title": "The provider to retrieve credentials for"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -320,9 +315,8 @@
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"title": "The provider to delete credentials for",
|
||||
"description": "Provider name for integrations. Can be any string value, including custom provider names."
|
||||
"$ref": "#/components/schemas/ProviderName",
|
||||
"title": "The provider to delete credentials for"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -386,9 +380,8 @@
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"title": "Provider where the webhook was registered",
|
||||
"description": "Provider name for integrations. Can be any string value, including custom provider names."
|
||||
"$ref": "#/components/schemas/ProviderName",
|
||||
"title": "Provider where the webhook was registered"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -443,86 +436,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/integrations/providers": {
|
||||
"get": {
|
||||
"tags": ["v1", "integrations"],
|
||||
"summary": "List Providers",
|
||||
"description": "Get a list of all available provider names.\n\nReturns both statically defined providers (from ProviderName enum)\nand dynamically registered providers (from SDK decorators).\n\nNote: The complete list of provider names is also available as a constant\nin the generated TypeScript client via PROVIDER_NAMES.",
|
||||
"operationId": "getV1ListProviders",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Successful Response",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"items": { "type": "string" },
|
||||
"type": "array",
|
||||
"title": "Response Getv1Listproviders"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/integrations/providers/names": {
|
||||
"get": {
|
||||
"tags": ["v1", "integrations"],
|
||||
"summary": "Get Provider Names",
|
||||
"description": "Get all provider names in a structured format.\n\nThis endpoint is specifically designed to expose the provider names\nin the OpenAPI schema so that code generators like Orval can create\nappropriate TypeScript constants.",
|
||||
"operationId": "getV1GetProviderNames",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Successful Response",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ProviderNamesResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/integrations/providers/constants": {
|
||||
"get": {
|
||||
"tags": ["v1", "integrations"],
|
||||
"summary": "Get Provider Constants",
|
||||
"description": "Get provider names as constants.\n\nThis endpoint returns a model with provider names as constants,\nspecifically designed for OpenAPI code generation tools to create\nTypeScript constants.",
|
||||
"operationId": "getV1GetProviderConstants",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Successful Response",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": { "$ref": "#/components/schemas/ProviderConstants" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/integrations/providers/enum-example": {
|
||||
"get": {
|
||||
"tags": ["v1", "integrations"],
|
||||
"summary": "Get Provider Enum Example",
|
||||
"description": "Example endpoint that uses the CompleteProviderNames enum.\n\nThis endpoint exists to ensure that the CompleteProviderNames enum is included\nin the OpenAPI schema, which will cause Orval to generate it as a\nTypeScript enum/constant.",
|
||||
"operationId": "getV1GetProviderEnumExample",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Successful Response",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ProviderEnumResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/analytics/log_raw_metric": {
|
||||
"post": {
|
||||
"tags": ["v1", "analytics"],
|
||||
@@ -3373,6 +3286,48 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/library/agents/by-graph/{graph_id}": {
|
||||
"get": {
|
||||
"tags": ["v2", "library", "private"],
|
||||
"summary": "Get Library Agent By Graph Id",
|
||||
"operationId": "getV2GetLibraryAgentByGraphId",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "graph_id",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"schema": { "type": "string", "title": "Graph Id" }
|
||||
},
|
||||
{
|
||||
"name": "version",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"schema": {
|
||||
"anyOf": [{ "type": "integer" }, { "type": "null" }],
|
||||
"title": "Version"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Successful Response",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": { "$ref": "#/components/schemas/LibraryAgent" }
|
||||
}
|
||||
}
|
||||
},
|
||||
"422": {
|
||||
"description": "Validation Error",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": { "$ref": "#/components/schemas/HTTPValidationError" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/library/agents/marketplace/{store_listing_version_id}": {
|
||||
"get": {
|
||||
"tags": ["v2", "library", "private", "store, library"],
|
||||
@@ -4146,11 +4101,7 @@
|
||||
"anyOf": [{ "type": "string" }, { "type": "null" }],
|
||||
"title": "Title"
|
||||
},
|
||||
"provider": {
|
||||
"type": "string",
|
||||
"title": "Provider",
|
||||
"description": "Provider name for integrations. Can be any string value, including custom provider names."
|
||||
},
|
||||
"provider": { "$ref": "#/components/schemas/ProviderName" },
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": ["api_key", "oauth2", "user_password", "host_scoped"],
|
||||
@@ -4160,8 +4111,8 @@
|
||||
"type": "object",
|
||||
"required": ["id", "provider", "type"],
|
||||
"title": "CredentialsMetaInput",
|
||||
"credentials_provider": ["string"],
|
||||
"credentials_types": ["api_key", "oauth2", "user_password"]
|
||||
"credentials_provider": [],
|
||||
"credentials_types": []
|
||||
},
|
||||
"CredentialsMetaResponse": {
|
||||
"properties": {
|
||||
@@ -4869,11 +4820,7 @@
|
||||
},
|
||||
"LibraryAgentTriggerInfo": {
|
||||
"properties": {
|
||||
"provider": {
|
||||
"type": "string",
|
||||
"title": "Provider",
|
||||
"description": "Provider name for integrations. Can be any string value, including custom provider names."
|
||||
},
|
||||
"provider": { "$ref": "#/components/schemas/ProviderName" },
|
||||
"config_schema": {
|
||||
"additionalProperties": true,
|
||||
"type": "object",
|
||||
@@ -5288,6 +5235,7 @@
|
||||
"AGENT_INPUT",
|
||||
"CONGRATS",
|
||||
"GET_RESULTS",
|
||||
"RUN_AGENTS",
|
||||
"MARKETPLACE_VISIT",
|
||||
"MARKETPLACE_ADD_AGENT",
|
||||
"MARKETPLACE_RUN_AGENT",
|
||||
@@ -5675,44 +5623,51 @@
|
||||
"required": ["name", "username", "description", "links"],
|
||||
"title": "ProfileDetails"
|
||||
},
|
||||
"ProviderConstants": {
|
||||
"properties": {
|
||||
"PROVIDER_NAMES": {
|
||||
"additionalProperties": { "type": "string" },
|
||||
"type": "object",
|
||||
"title": "Provider Names",
|
||||
"description": "All available provider names as a constant mapping"
|
||||
}
|
||||
},
|
||||
"type": "object",
|
||||
"title": "ProviderConstants",
|
||||
"description": "Model that exposes all provider names as a constant in the OpenAPI schema.\nThis is designed to be converted by Orval into a TypeScript constant."
|
||||
},
|
||||
"ProviderEnumResponse": {
|
||||
"properties": {
|
||||
"provider": {
|
||||
"type": "string",
|
||||
"title": "Provider",
|
||||
"description": "A provider name from the complete list of providers"
|
||||
}
|
||||
},
|
||||
"type": "object",
|
||||
"required": ["provider"],
|
||||
"title": "ProviderEnumResponse",
|
||||
"description": "Response containing a provider from the enum."
|
||||
},
|
||||
"ProviderNamesResponse": {
|
||||
"properties": {
|
||||
"providers": {
|
||||
"items": { "type": "string" },
|
||||
"type": "array",
|
||||
"title": "Providers",
|
||||
"description": "List of all available provider names"
|
||||
}
|
||||
},
|
||||
"type": "object",
|
||||
"title": "ProviderNamesResponse",
|
||||
"description": "Response containing list of all provider names."
|
||||
"ProviderName": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"aiml_api",
|
||||
"anthropic",
|
||||
"apollo",
|
||||
"compass",
|
||||
"discord",
|
||||
"d_id",
|
||||
"e2b",
|
||||
"exa",
|
||||
"fal",
|
||||
"generic_webhook",
|
||||
"github",
|
||||
"google",
|
||||
"google_maps",
|
||||
"groq",
|
||||
"http",
|
||||
"hubspot",
|
||||
"ideogram",
|
||||
"jina",
|
||||
"linear",
|
||||
"llama_api",
|
||||
"medium",
|
||||
"mem0",
|
||||
"notion",
|
||||
"nvidia",
|
||||
"ollama",
|
||||
"openai",
|
||||
"openweathermap",
|
||||
"open_router",
|
||||
"pinecone",
|
||||
"reddit",
|
||||
"replicate",
|
||||
"revid",
|
||||
"screenshotone",
|
||||
"slant3d",
|
||||
"smartlead",
|
||||
"smtp",
|
||||
"twitter",
|
||||
"todoist",
|
||||
"unreal_speech",
|
||||
"zerobounce"
|
||||
],
|
||||
"title": "ProviderName"
|
||||
},
|
||||
"RefundRequest": {
|
||||
"properties": {
|
||||
@@ -6486,11 +6441,7 @@
|
||||
"properties": {
|
||||
"id": { "type": "string", "title": "Id" },
|
||||
"user_id": { "type": "string", "title": "User Id" },
|
||||
"provider": {
|
||||
"type": "string",
|
||||
"title": "Provider",
|
||||
"description": "Provider name for integrations. Can be any string value, including custom provider names."
|
||||
},
|
||||
"provider": { "$ref": "#/components/schemas/ProviderName" },
|
||||
"credentials_id": { "type": "string", "title": "Credentials Id" },
|
||||
"webhook_type": { "type": "string", "title": "Webhook Type" },
|
||||
"resource": { "type": "string", "title": "Resource" },
|
||||
|
||||
@@ -1,131 +0,0 @@
|
||||
import * as React from "react";
|
||||
import Link from "next/link";
|
||||
import { ProfilePopoutMenu } from "./ProfilePopoutMenu";
|
||||
import { IconType, IconLogIn, IconAutoGPTLogo } from "@/components/ui/icons";
|
||||
import { MobileNavBar } from "./MobileNavBar";
|
||||
import { Button } from "./Button";
|
||||
import Wallet from "./Wallet";
|
||||
import { ProfileDetails } from "@/lib/autogpt-server-api/types";
|
||||
import { NavbarLink } from "./NavbarLink";
|
||||
|
||||
import BackendAPI from "@/lib/autogpt-server-api";
|
||||
import { getServerUser } from "@/lib/supabase/server/getServerUser";
|
||||
|
||||
// Disable theme toggle for now
|
||||
// import { ThemeToggle } from "./ThemeToggle";
|
||||
|
||||
interface NavLink {
|
||||
name: string;
|
||||
href: string;
|
||||
}
|
||||
|
||||
interface NavbarProps {
|
||||
links: NavLink[];
|
||||
menuItemGroups: {
|
||||
groupName?: string;
|
||||
items: {
|
||||
icon: IconType;
|
||||
text: string;
|
||||
href?: string;
|
||||
onClick?: () => void;
|
||||
}[];
|
||||
}[];
|
||||
}
|
||||
|
||||
async function getProfileData() {
|
||||
const api = new BackendAPI();
|
||||
const profile = await Promise.resolve(api.getStoreProfile());
|
||||
|
||||
return profile;
|
||||
}
|
||||
|
||||
export const Navbar = async ({ links, menuItemGroups }: NavbarProps) => {
|
||||
const { user } = await getServerUser();
|
||||
const isLoggedIn = user !== null;
|
||||
let profile: ProfileDetails | null = null;
|
||||
if (isLoggedIn) {
|
||||
profile = await getProfileData();
|
||||
}
|
||||
|
||||
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">
|
||||
<div className="flex items-center gap-11">
|
||||
<div className="relative h-10 w-[88.87px]">
|
||||
<IconAutoGPTLogo className="h-full w-full" />
|
||||
</div>
|
||||
{links.map((link) => (
|
||||
<NavbarLink key={link.name} name={link.name} href={link.href} />
|
||||
))}
|
||||
</div>
|
||||
{/* Profile section */}
|
||||
<div className="flex items-center gap-4">
|
||||
{isLoggedIn ? (
|
||||
<div className="flex items-center gap-4">
|
||||
{profile && <Wallet />}
|
||||
<ProfilePopoutMenu
|
||||
menuItemGroups={menuItemGroups}
|
||||
userName={profile?.username}
|
||||
userEmail={profile?.name}
|
||||
avatarSrc={profile?.avatar_url}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<Link href="/login">
|
||||
<Button
|
||||
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>
|
||||
)}
|
||||
{/* <ThemeToggle /> */}
|
||||
</div>
|
||||
</nav>
|
||||
{/* Mobile Navbar - Adjust positioning */}
|
||||
<>
|
||||
{isLoggedIn ? (
|
||||
<div className="fixed right-4 top-4 z-50">
|
||||
<MobileNavBar
|
||||
userName={profile?.username}
|
||||
menuItemGroups={[
|
||||
{
|
||||
groupName: "Navigation",
|
||||
items: links.map((link) => ({
|
||||
icon:
|
||||
link.name === "Marketplace"
|
||||
? IconType.Marketplace
|
||||
: link.name === "Library"
|
||||
? IconType.Library
|
||||
: link.name === "Build"
|
||||
? IconType.Builder
|
||||
: link.name === "Monitor"
|
||||
? IconType.Library
|
||||
: IconType.LayoutDashboard,
|
||||
text: link.name,
|
||||
href: link.href,
|
||||
})),
|
||||
},
|
||||
...menuItemGroups,
|
||||
]}
|
||||
userEmail={profile?.name}
|
||||
avatarSrc={profile?.avatar_url}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<Link
|
||||
href="/login"
|
||||
className="fixed right-4 top-4 z-50 mt-4 inline-flex h-8 items-center justify-end rounded-lg pr-4 md:hidden"
|
||||
>
|
||||
<Button size="sm" className="flex items-center space-x-2">
|
||||
<IconLogIn className="h-5 w-5" />
|
||||
<span>Log In</span>
|
||||
</Button>
|
||||
</Link>
|
||||
)}
|
||||
</>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -1,66 +0,0 @@
|
||||
"use client";
|
||||
import Link from "next/link";
|
||||
import {
|
||||
IconShoppingCart,
|
||||
IconBoxes,
|
||||
IconLibrary,
|
||||
IconLaptop,
|
||||
} from "@/components/ui/icons";
|
||||
import { usePathname } from "next/navigation";
|
||||
|
||||
interface NavbarLinkProps {
|
||||
name: string;
|
||||
href: string;
|
||||
}
|
||||
|
||||
export const NavbarLink = ({ name, href }: NavbarLinkProps) => {
|
||||
const pathname = usePathname();
|
||||
const parts = pathname.split("/");
|
||||
const activeLink = "/" + (parts.length > 2 ? parts[2] : parts[1]);
|
||||
|
||||
return (
|
||||
<Link
|
||||
href={href}
|
||||
data-testid={`navbar-link-${name.toLowerCase()}`}
|
||||
className="font-poppins text-[20px] leading-[28px]"
|
||||
>
|
||||
<div
|
||||
className={`h-[48px] px-5 py-4 ${
|
||||
activeLink === href
|
||||
? "rounded-2xl bg-neutral-800 dark:bg-neutral-200"
|
||||
: ""
|
||||
} flex items-center justify-start gap-3`}
|
||||
>
|
||||
{href === "/marketplace" && (
|
||||
<IconShoppingCart
|
||||
className={`h-6 w-6 ${activeLink === href ? "text-white 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
|
||||
className={`hidden font-poppins text-[20px] font-medium leading-[28px] lg:block ${
|
||||
activeLink === href
|
||||
? "text-neutral-50 dark:text-neutral-900"
|
||||
: "text-neutral-900 dark:text-neutral-50"
|
||||
}`}
|
||||
>
|
||||
{name}
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
);
|
||||
};
|
||||
@@ -109,7 +109,7 @@ export default function Wallet() {
|
||||
<button
|
||||
ref={walletRef}
|
||||
className={cn(
|
||||
"relative flex items-center gap-1 rounded-md bg-zinc-200 px-3 py-2 text-sm transition-colors duration-200 hover:bg-zinc-300",
|
||||
"relative flex items-center gap-1 rounded-md bg-zinc-50 px-3 py-2 text-sm",
|
||||
)}
|
||||
onClick={onWalletOpen}
|
||||
>
|
||||
|
||||
@@ -56,7 +56,7 @@ const meta: Meta<typeof Button> = {
|
||||
};
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof meta>;
|
||||
type Story = StoryObj<typeof Button>;
|
||||
|
||||
// Basic variants
|
||||
export const Primary: Story = {
|
||||
|
||||
@@ -1,82 +1,78 @@
|
||||
import { cn } from "@/lib/utils";
|
||||
import { CircleNotchIcon } from "@phosphor-icons/react/dist/ssr";
|
||||
import { cva, type VariantProps } from "class-variance-authority";
|
||||
import Link, { type LinkProps } from "next/link";
|
||||
import React from "react";
|
||||
import { ButtonProps, extendedButtonVariants } from "./helpers";
|
||||
|
||||
// Extended button variants based on our design system
|
||||
const extendedButtonVariants = cva(
|
||||
"inline-flex items-center justify-center whitespace-nowrap font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-neutral-950 disabled:pointer-events-none disabled:opacity-50 font-['Geist'] leading-snug border",
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
primary:
|
||||
"bg-zinc-800 border-zinc-800 text-white hover:bg-zinc-900 hover:border-zinc-900 rounded-full disabled:text-white disabled:bg-zinc-200 disabled:border-zinc-200 disabled:opacity-1",
|
||||
secondary:
|
||||
"bg-zinc-100 border-zinc-100 text-black hover:bg-zinc-300 hover:border-zinc-300 rounded-full disabled:text-zinc-300 disabled:bg-zinc-50 disabled:border-zinc-50 disabled:opacity-1",
|
||||
destructive:
|
||||
"bg-red-500 border-red-500 text-white hover:bg-red-600 hover:border-red-600 rounded-full disabled:text-white disabled:bg-zinc-200 disabled:border-zinc-200 disabled:opacity-1",
|
||||
outline:
|
||||
"bg-transparent border-zinc-700 text-black hover:bg-zinc-100 hover:border-zinc-700 rounded-full disabled:border-zinc-200 disabled:text-zinc-200 disabled:opacity-1",
|
||||
ghost:
|
||||
"bg-transparent border-transparent text-black hover:bg-zinc-50 hover:border-zinc-50 rounded-full disabled:text-zinc-200 disabled:opacity-1",
|
||||
icon: "bg-white text-black border border-zinc-600 hover:bg-zinc-100 rounded-[96px] disabled:opacity-1",
|
||||
},
|
||||
size: {
|
||||
small: "px-3 py-2 text-sm gap-1.5 h-[2.25rem]",
|
||||
large: "min-w-20 px-4 py-3 text-sm gap-2",
|
||||
icon: "p-3",
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: "primary",
|
||||
size: "large",
|
||||
},
|
||||
},
|
||||
);
|
||||
export function Button(props: ButtonProps) {
|
||||
const {
|
||||
className,
|
||||
variant,
|
||||
size,
|
||||
loading = false,
|
||||
leftIcon,
|
||||
rightIcon,
|
||||
children,
|
||||
as = "button",
|
||||
...restProps
|
||||
} = props;
|
||||
|
||||
export interface ButtonProps
|
||||
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
|
||||
VariantProps<typeof extendedButtonVariants> {
|
||||
loading?: boolean;
|
||||
leftIcon?: React.ReactNode;
|
||||
rightIcon?: React.ReactNode;
|
||||
asChild?: boolean;
|
||||
}
|
||||
|
||||
function Button({
|
||||
className,
|
||||
variant,
|
||||
size,
|
||||
loading = false,
|
||||
leftIcon,
|
||||
rightIcon,
|
||||
children,
|
||||
disabled,
|
||||
...props
|
||||
}: ButtonProps) {
|
||||
const disabled = "disabled" in props ? props.disabled : false;
|
||||
const isDisabled = disabled;
|
||||
|
||||
const buttonContent = (
|
||||
<>
|
||||
{loading && (
|
||||
<CircleNotchIcon className="h-4 w-4 animate-spin" weight="bold" />
|
||||
)}
|
||||
{!loading && leftIcon}
|
||||
{children}
|
||||
{!loading && rightIcon}
|
||||
</>
|
||||
);
|
||||
|
||||
if (loading) {
|
||||
return variant === "ghost" ? (
|
||||
<button
|
||||
const loadingClassName =
|
||||
variant === "ghost"
|
||||
? cn(
|
||||
extendedButtonVariants({ variant, size, className }),
|
||||
"pointer-events-none",
|
||||
)
|
||||
: cn(
|
||||
extendedButtonVariants({ variant: "primary", size, className }),
|
||||
"pointer-events-none border-zinc-500 bg-zinc-500 text-white",
|
||||
);
|
||||
|
||||
return as === "NextLink" ? (
|
||||
<Link
|
||||
{...(restProps as LinkProps)}
|
||||
className={loadingClassName}
|
||||
aria-disabled="true"
|
||||
>
|
||||
<CircleNotchIcon className="h-4 w-4 animate-spin" weight="bold" />
|
||||
{children}
|
||||
</Link>
|
||||
) : (
|
||||
<button className={loadingClassName} disabled>
|
||||
<CircleNotchIcon className="h-4 w-4 animate-spin" weight="bold" />
|
||||
{children}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
if (as === "NextLink") {
|
||||
return (
|
||||
<Link
|
||||
{...(restProps as LinkProps)}
|
||||
className={cn(
|
||||
extendedButtonVariants({ variant, size, className }),
|
||||
"pointer-events-none",
|
||||
loading && "pointer-events-none",
|
||||
isDisabled && "pointer-events-none opacity-50",
|
||||
)}
|
||||
aria-disabled={isDisabled}
|
||||
>
|
||||
<CircleNotchIcon className="h-4 w-4 animate-spin" weight="bold" />
|
||||
{children}
|
||||
</button>
|
||||
) : (
|
||||
<button
|
||||
className={cn(
|
||||
extendedButtonVariants({ variant: "primary", size, className }),
|
||||
"pointer-events-none border-zinc-500 bg-zinc-500 text-white",
|
||||
)}
|
||||
>
|
||||
<CircleNotchIcon className="h-4 w-4 animate-spin" weight="bold" />
|
||||
{children}
|
||||
</button>
|
||||
{buttonContent}
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -87,18 +83,9 @@ function Button({
|
||||
loading && "pointer-events-none",
|
||||
)}
|
||||
disabled={isDisabled}
|
||||
{...props}
|
||||
{...(restProps as React.ButtonHTMLAttributes<HTMLButtonElement>)}
|
||||
>
|
||||
{loading && (
|
||||
<CircleNotchIcon className="h-4 w-4 animate-spin" weight="bold" />
|
||||
)}
|
||||
{!loading && leftIcon}
|
||||
{children}
|
||||
{!loading && rightIcon}
|
||||
{buttonContent}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
Button.displayName = "Button";
|
||||
|
||||
export { Button, extendedButtonVariants };
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
import { cva, VariantProps } from "class-variance-authority";
|
||||
import { LinkProps } from "next/link";
|
||||
|
||||
// Extended button variants based on our design system
|
||||
export const extendedButtonVariants = cva(
|
||||
"inline-flex items-center justify-center whitespace-nowrap font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-neutral-950 disabled:pointer-events-none disabled:opacity-50 font-sans leading-snug border",
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
primary:
|
||||
"bg-zinc-800 border-zinc-800 text-white hover:bg-zinc-900 hover:border-zinc-900 rounded-full disabled:text-white disabled:bg-zinc-200 disabled:border-zinc-200 disabled:opacity-1",
|
||||
secondary:
|
||||
"bg-zinc-100 border-zinc-100 text-black hover:bg-zinc-300 hover:border-zinc-300 rounded-full disabled:text-zinc-300 disabled:bg-zinc-50 disabled:border-zinc-50 disabled:opacity-1",
|
||||
destructive:
|
||||
"bg-red-500 border-red-500 text-white hover:bg-red-600 hover:border-red-600 rounded-full disabled:text-white disabled:bg-zinc-200 disabled:border-zinc-200 disabled:opacity-1",
|
||||
outline:
|
||||
"bg-transparent border-zinc-700 text-black hover:bg-zinc-100 hover:border-zinc-700 rounded-full disabled:border-zinc-200 disabled:text-zinc-200 disabled:opacity-1",
|
||||
ghost:
|
||||
"bg-transparent border-transparent text-black hover:bg-zinc-50 hover:border-zinc-50 rounded-full disabled:text-zinc-200 disabled:opacity-1",
|
||||
icon: "bg-white text-black border border-zinc-600 hover:bg-zinc-100 rounded-[96px] disabled:opacity-1",
|
||||
},
|
||||
size: {
|
||||
small: "px-3 py-2 text-sm gap-1.5 h-[2.25rem]",
|
||||
large: "min-w-20 px-4 py-3 text-sm gap-2",
|
||||
icon: "p-3",
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: "primary",
|
||||
size: "large",
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
type BaseButtonProps = {
|
||||
loading?: boolean;
|
||||
leftIcon?: React.ReactNode;
|
||||
rightIcon?: React.ReactNode;
|
||||
asChild?: boolean;
|
||||
} & VariantProps<typeof extendedButtonVariants>;
|
||||
|
||||
type ButtonAsButton = BaseButtonProps &
|
||||
React.ButtonHTMLAttributes<HTMLButtonElement> & {
|
||||
as?: "button";
|
||||
href?: never;
|
||||
};
|
||||
|
||||
type ButtonAsLink = BaseButtonProps &
|
||||
Omit<React.AnchorHTMLAttributes<HTMLAnchorElement>, keyof LinkProps> &
|
||||
LinkProps & {
|
||||
as: "NextLink";
|
||||
disabled?: boolean;
|
||||
};
|
||||
|
||||
export type ButtonProps = ButtonAsButton | ButtonAsLink;
|
||||
@@ -0,0 +1,84 @@
|
||||
import { IconAutoGPTLogo, IconType } from "@/components/ui/icons";
|
||||
import Wallet from "../../agptui/Wallet";
|
||||
import { AccountMenu } from "./components/AccountMenu/AccountMenu";
|
||||
import { LoginButton } from "./components/LoginButton";
|
||||
import { MobileNavBar } from "./components/MobileNavbar/MobileNavBar";
|
||||
import { NavbarLink } from "./components/NavbarLink";
|
||||
import { accountMenuItems, loggedInLinks, loggedOutLinks } from "./helpers";
|
||||
import { getNavbarAccountData } from "./data";
|
||||
|
||||
export async function Navbar() {
|
||||
const { profile, isLoggedIn } = await getNavbarAccountData();
|
||||
|
||||
return (
|
||||
<>
|
||||
<nav className="sticky top-0 z-40 hidden h-16 items-center rounded-bl-2xl rounded-br-2xl border border-white/50 bg-[#f3f4f6]/20 p-3 backdrop-blur-[26px] md:inline-flex">
|
||||
{/* Left section */}
|
||||
<div className="flex flex-1 items-center gap-6">
|
||||
{isLoggedIn
|
||||
? loggedInLinks.map((link) => (
|
||||
<NavbarLink key={link.name} name={link.name} href={link.href} />
|
||||
))
|
||||
: loggedOutLinks.map((link) => (
|
||||
<NavbarLink key={link.name} name={link.name} href={link.href} />
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Centered logo */}
|
||||
<div className="absolute left-1/2 top-1/2 h-10 w-[88.87px] -translate-x-1/2 -translate-y-1/2">
|
||||
<IconAutoGPTLogo className="h-full w-full" />
|
||||
</div>
|
||||
|
||||
{/* Right section */}
|
||||
<div className="flex flex-1 items-center justify-end gap-4">
|
||||
{isLoggedIn ? (
|
||||
<div className="flex items-center gap-4">
|
||||
{profile && <Wallet />}
|
||||
<AccountMenu
|
||||
userName={profile?.username}
|
||||
userEmail={profile?.name}
|
||||
avatarSrc={profile?.avatar_url ?? ""}
|
||||
menuItemGroups={accountMenuItems}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<LoginButton />
|
||||
)}
|
||||
{/* <ThemeToggle /> */}
|
||||
</div>
|
||||
</nav>
|
||||
{/* Mobile Navbar - Adjust positioning */}
|
||||
<>
|
||||
{isLoggedIn ? (
|
||||
<div className="fixed right-4 top-4 z-50">
|
||||
<MobileNavBar
|
||||
userName={profile?.username}
|
||||
menuItemGroups={[
|
||||
{
|
||||
groupName: "Navigation",
|
||||
items: loggedInLinks.map((link) => ({
|
||||
icon:
|
||||
link.name === "Marketplace"
|
||||
? IconType.Marketplace
|
||||
: link.name === "Library"
|
||||
? IconType.Library
|
||||
: link.name === "Build"
|
||||
? IconType.Builder
|
||||
: link.name === "Monitor"
|
||||
? IconType.Library
|
||||
: IconType.LayoutDashboard,
|
||||
text: link.name,
|
||||
href: link.href,
|
||||
})),
|
||||
},
|
||||
...accountMenuItems,
|
||||
]}
|
||||
userEmail={profile?.name}
|
||||
avatarSrc={profile?.avatar_url ?? ""}
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
</>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -1,74 +1,31 @@
|
||||
import * as React from "react";
|
||||
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
||||
import {
|
||||
Popover,
|
||||
PopoverTrigger,
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
} from "@/components/ui/popover";
|
||||
import {
|
||||
IconType,
|
||||
IconEdit,
|
||||
IconLayoutDashboard,
|
||||
IconUploadCloud,
|
||||
IconSettings,
|
||||
IconLogOut,
|
||||
IconRefresh,
|
||||
IconMarketplace,
|
||||
IconLibrary,
|
||||
IconBuilder,
|
||||
} from "../ui/icons";
|
||||
import Link from "next/link";
|
||||
import { ProfilePopoutMenuLogoutButton } from "./ProfilePopoutMenuLogoutButton";
|
||||
import { PublishAgentPopout } from "./composite/PublishAgentPopout";
|
||||
import * as React from "react";
|
||||
import { PublishAgentPopout } from "../../../../agptui/composite/PublishAgentPopout";
|
||||
import { getAccountMenuOptionIcon, MenuItemGroup } from "../../helpers";
|
||||
import { AccountLogoutOption } from "./components/AccountLogoutOption";
|
||||
|
||||
interface ProfilePopoutMenuProps {
|
||||
interface Props {
|
||||
userName?: string;
|
||||
userEmail?: string;
|
||||
avatarSrc?: string;
|
||||
hideNavBarUsername?: boolean;
|
||||
menuItemGroups: {
|
||||
groupName?: string;
|
||||
items: {
|
||||
icon: IconType;
|
||||
text: string;
|
||||
href?: string;
|
||||
onClick?: () => void;
|
||||
}[];
|
||||
}[];
|
||||
menuItemGroups: MenuItemGroup[];
|
||||
}
|
||||
|
||||
export function ProfilePopoutMenu({
|
||||
export function AccountMenu({
|
||||
userName,
|
||||
userEmail,
|
||||
avatarSrc,
|
||||
menuItemGroups,
|
||||
}: ProfilePopoutMenuProps) {
|
||||
}: Props) {
|
||||
const popupId = React.useId();
|
||||
|
||||
const getIcon = (icon: IconType) => {
|
||||
const iconClass = "w-6 h-6";
|
||||
switch (icon) {
|
||||
case IconType.LayoutDashboard:
|
||||
return <IconLayoutDashboard className={iconClass} />;
|
||||
case IconType.UploadCloud:
|
||||
return <IconUploadCloud className={iconClass} />;
|
||||
case IconType.Edit:
|
||||
return <IconEdit className={iconClass} />;
|
||||
case IconType.Settings:
|
||||
return <IconSettings className={iconClass} />;
|
||||
case IconType.LogOut:
|
||||
return <IconLogOut className={iconClass} />;
|
||||
case IconType.Marketplace:
|
||||
return <IconMarketplace className={iconClass} />;
|
||||
case IconType.Library:
|
||||
return <IconLibrary className={iconClass} />;
|
||||
case IconType.Builder:
|
||||
return <IconBuilder className={iconClass} />;
|
||||
default:
|
||||
return <IconRefresh className={iconClass} />;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
@@ -127,7 +84,7 @@ export function ProfilePopoutMenu({
|
||||
className="inline-flex w-full items-center justify-start gap-2.5"
|
||||
>
|
||||
<div className="relative h-6 w-6">
|
||||
{getIcon(item.icon)}
|
||||
{getAccountMenuOptionIcon(item.icon)}
|
||||
</div>
|
||||
<div className="font-sans text-base font-medium leading-normal text-neutral-800 dark:text-neutral-200">
|
||||
{item.text}
|
||||
@@ -135,7 +92,7 @@ export function ProfilePopoutMenu({
|
||||
</Link>
|
||||
);
|
||||
} else if (item.text === "Log out") {
|
||||
return <ProfilePopoutMenuLogoutButton key={itemIndex} />;
|
||||
return <AccountLogoutOption key={itemIndex} />;
|
||||
} else if (item.text === "Publish an agent") {
|
||||
return (
|
||||
<PublishAgentPopout
|
||||
@@ -143,7 +100,7 @@ export function ProfilePopoutMenu({
|
||||
trigger={
|
||||
<div className="inline-flex w-full items-center justify-start gap-2.5">
|
||||
<div className="relative h-6 w-6">
|
||||
{getIcon(item.icon)}
|
||||
{getAccountMenuOptionIcon(item.icon)}
|
||||
</div>
|
||||
<div className="font-sans text-base font-medium leading-normal text-neutral-800 dark:text-neutral-200">
|
||||
{item.text}
|
||||
@@ -165,7 +122,7 @@ export function ProfilePopoutMenu({
|
||||
tabIndex={0}
|
||||
>
|
||||
<div className="relative h-6 w-6">
|
||||
{getIcon(item.icon)}
|
||||
{getAccountMenuOptionIcon(item.icon)}
|
||||
</div>
|
||||
<div className="font-sans text-base font-medium leading-normal text-neutral-800 dark:text-neutral-200">
|
||||
{item.text}
|
||||
@@ -1,14 +1,14 @@
|
||||
"use client";
|
||||
import { IconLogOut } from "@/components/ui/icons";
|
||||
import { LoadingSpinner } from "@/components/ui/loading";
|
||||
import { useSupabase } from "@/lib/supabase/hooks/useSupabase";
|
||||
import { cn } from "@/lib/utils";
|
||||
import * as Sentry from "@sentry/nextjs";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useTransition } from "react";
|
||||
import { LoadingSpinner } from "../ui/loading";
|
||||
import { toast } from "../molecules/Toast/use-toast";
|
||||
import { toast } from "@/components/molecules/Toast/use-toast";
|
||||
|
||||
export function ProfilePopoutMenuLogoutButton() {
|
||||
export function AccountLogoutOption() {
|
||||
const router = useRouter();
|
||||
const [isPending, startTransition] = useTransition();
|
||||
const supabase = useSupabase();
|
||||
@@ -0,0 +1,29 @@
|
||||
"use client";
|
||||
|
||||
import { Button } from "@/components/atoms/Button/Button";
|
||||
import { SignInIcon } from "@phosphor-icons/react/dist/ssr";
|
||||
import { usePathname, useRouter } from "next/navigation";
|
||||
|
||||
export function LoginButton() {
|
||||
const router = useRouter();
|
||||
const pathname = usePathname();
|
||||
const isLoginPage = pathname.includes("/login");
|
||||
|
||||
if (isLoginPage) return null;
|
||||
|
||||
function handleLogin() {
|
||||
router.push("/login");
|
||||
}
|
||||
|
||||
return (
|
||||
<Button
|
||||
onClick={handleLogin}
|
||||
size="small"
|
||||
className="flex items-center justify-end space-x-2"
|
||||
leftIcon={<SignInIcon className="h-5 w-5" />}
|
||||
variant="secondary"
|
||||
>
|
||||
Log In
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
@@ -1,45 +1,26 @@
|
||||
"use client";
|
||||
|
||||
import * as React from "react";
|
||||
import Link from "next/link";
|
||||
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Popover,
|
||||
PopoverTrigger,
|
||||
PopoverContent,
|
||||
PopoverPortal,
|
||||
PopoverTrigger,
|
||||
} from "@/components/ui/popover";
|
||||
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
||||
import { Separator } from "@/components/ui/separator";
|
||||
import {
|
||||
IconType,
|
||||
IconMenu,
|
||||
IconChevronUp,
|
||||
IconEdit,
|
||||
IconLayoutDashboard,
|
||||
IconUploadCloud,
|
||||
IconSettings,
|
||||
IconLogOut,
|
||||
IconMarketplace,
|
||||
IconLibrary,
|
||||
IconBuilder,
|
||||
} from "../ui/icons";
|
||||
import { AnimatePresence, motion } from "framer-motion";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { usePathname } from "next/navigation";
|
||||
import * as React from "react";
|
||||
import { IconChevronUp, IconMenu } from "../../../../ui/icons";
|
||||
import { MenuItemGroup } from "../../helpers";
|
||||
import { MobileNavbarMenuItem } from "./components/MobileNavbarMenuItem";
|
||||
|
||||
interface MobileNavBarProps {
|
||||
userName?: string;
|
||||
userEmail?: string;
|
||||
avatarSrc?: string;
|
||||
menuItemGroups: {
|
||||
groupName?: string;
|
||||
items: {
|
||||
icon: IconType;
|
||||
text: string;
|
||||
href?: string;
|
||||
onClick?: () => void;
|
||||
}[];
|
||||
}[];
|
||||
menuItemGroups: MenuItemGroup[];
|
||||
}
|
||||
|
||||
const Overlay = React.forwardRef<HTMLDivElement, { children: React.ReactNode }>(
|
||||
@@ -49,76 +30,15 @@ const Overlay = React.forwardRef<HTMLDivElement, { children: React.ReactNode }>(
|
||||
</div>
|
||||
),
|
||||
);
|
||||
|
||||
Overlay.displayName = "Overlay";
|
||||
|
||||
const PopoutMenuItem: React.FC<{
|
||||
icon: IconType;
|
||||
isActive: boolean;
|
||||
text: React.ReactNode;
|
||||
href?: string;
|
||||
onClick?: () => void;
|
||||
}> = ({ icon, isActive, text, href, onClick }) => {
|
||||
const getIcon = (iconType: IconType) => {
|
||||
const iconClass = "w-6 h-6 relative";
|
||||
switch (iconType) {
|
||||
case IconType.Marketplace:
|
||||
return <IconMarketplace className={iconClass} />;
|
||||
case IconType.Library:
|
||||
return <IconLibrary className={iconClass} />;
|
||||
case IconType.Builder:
|
||||
return <IconBuilder className={iconClass} />;
|
||||
case IconType.Edit:
|
||||
return <IconEdit className={iconClass} />;
|
||||
case IconType.LayoutDashboard:
|
||||
return <IconLayoutDashboard className={iconClass} />;
|
||||
case IconType.UploadCloud:
|
||||
return <IconUploadCloud className={iconClass} />;
|
||||
case IconType.Settings:
|
||||
return <IconSettings className={iconClass} />;
|
||||
case IconType.LogOut:
|
||||
return <IconLogOut className={iconClass} />;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
const content = (
|
||||
<div className="inline-flex w-full items-center justify-start gap-4 hover:rounded hover:bg-[#e0e0e0] dark:hover:bg-[#3a3a3a]">
|
||||
{getIcon(icon)}
|
||||
<div className="relative">
|
||||
<div
|
||||
className={`font-sans text-base font-normal leading-7 text-[#474747] dark:text-[#cfcfcf] ${isActive ? "font-semibold text-[#272727] dark:text-[#ffffff]" : "text-[#474747] dark:text-[#cfcfcf]"}`}
|
||||
>
|
||||
{text}
|
||||
</div>
|
||||
{isActive && (
|
||||
<div className="absolute bottom-[-4px] left-0 h-[2px] w-full bg-[#272727] dark:bg-[#ffffff]"></div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
if (onClick)
|
||||
return (
|
||||
<div className="w-full" onClick={onClick}>
|
||||
{content}
|
||||
</div>
|
||||
);
|
||||
if (href)
|
||||
return (
|
||||
<Link href={href} className="w-full">
|
||||
{content}
|
||||
</Link>
|
||||
);
|
||||
return content;
|
||||
};
|
||||
|
||||
export const MobileNavBar: React.FC<MobileNavBarProps> = ({
|
||||
export function MobileNavBar({
|
||||
userName,
|
||||
userEmail,
|
||||
avatarSrc,
|
||||
menuItemGroups,
|
||||
}) => {
|
||||
}: MobileNavBarProps) {
|
||||
const [isOpen, setIsOpen] = React.useState(false);
|
||||
const pathname = usePathname();
|
||||
const parts = pathname.split("/");
|
||||
@@ -173,7 +93,7 @@ export const MobileNavBar: React.FC<MobileNavBarProps> = ({
|
||||
{menuItemGroups.map((group, groupIndex) => (
|
||||
<React.Fragment key={groupIndex}>
|
||||
{group.items.map((item, itemIndex) => (
|
||||
<PopoutMenuItem
|
||||
<MobileNavbarMenuItem
|
||||
key={itemIndex}
|
||||
icon={item.icon}
|
||||
isActive={item.href === activeLink}
|
||||
@@ -194,4 +114,4 @@ export const MobileNavBar: React.FC<MobileNavBarProps> = ({
|
||||
</AnimatePresence>
|
||||
</Popover>
|
||||
);
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
import { IconType } from "@/components/ui/icons";
|
||||
import { cn } from "@/lib/utils";
|
||||
import Link from "next/link";
|
||||
import { getAccountMenuOptionIcon } from "../../../helpers";
|
||||
|
||||
interface Props {
|
||||
icon: IconType;
|
||||
isActive: boolean;
|
||||
text: string;
|
||||
href?: string;
|
||||
onClick?: () => void;
|
||||
}
|
||||
|
||||
export function MobileNavbarMenuItem({
|
||||
icon,
|
||||
isActive,
|
||||
text,
|
||||
href,
|
||||
onClick,
|
||||
}: Props) {
|
||||
const content = (
|
||||
<div className="inline-flex w-full items-center justify-start gap-4 hover:rounded hover:bg-[#e0e0e0] dark:hover:bg-[#3a3a3a]">
|
||||
{getAccountMenuOptionIcon(icon)}
|
||||
<div className="relative">
|
||||
<div
|
||||
className={cn(
|
||||
"font-sans text-base font-normal leading-7",
|
||||
isActive
|
||||
? "font-semibold text-[#272727] dark:text-[#ffffff]"
|
||||
: "text-[#474747] dark:text-[#cfcfcf]",
|
||||
)}
|
||||
>
|
||||
{text}
|
||||
</div>
|
||||
{isActive && (
|
||||
<div className="absolute bottom-[-4px] left-0 h-[2px] w-full bg-[#272727] dark:bg-[#ffffff]"></div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
if (onClick)
|
||||
return (
|
||||
<div className="w-full" onClick={onClick}>
|
||||
{content}
|
||||
</div>
|
||||
);
|
||||
if (href)
|
||||
return (
|
||||
<Link href={href} className="w-full">
|
||||
{content}
|
||||
</Link>
|
||||
);
|
||||
return content;
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
"use client";
|
||||
|
||||
import { IconLaptop } from "@/components/ui/icons";
|
||||
import { cn } from "@/lib/utils";
|
||||
import {
|
||||
CubeIcon,
|
||||
HouseIcon,
|
||||
StorefrontIcon,
|
||||
} from "@phosphor-icons/react/dist/ssr";
|
||||
import Link from "next/link";
|
||||
import { usePathname } from "next/navigation";
|
||||
import { Text } from "../../../atoms/Text/Text";
|
||||
|
||||
interface Props {
|
||||
name: string;
|
||||
href: string;
|
||||
}
|
||||
|
||||
export function NavbarLink({ name, href }: Props) {
|
||||
const pathname = usePathname();
|
||||
const isActive = pathname.includes(href);
|
||||
|
||||
return (
|
||||
<Link
|
||||
href={href}
|
||||
data-testid={`navbar-link-${name.toLowerCase()}`}
|
||||
className="font-poppins text-[20px] leading-[28px]"
|
||||
>
|
||||
<div
|
||||
className={cn(
|
||||
"flex items-center justify-start gap-1 p-2",
|
||||
isActive &&
|
||||
"rounded-small bg-neutral-800 p-2 transition-all duration-300 dark:bg-neutral-200",
|
||||
)}
|
||||
>
|
||||
{href === "/marketplace" && (
|
||||
<StorefrontIcon
|
||||
className={cn("h-6 w-6", isActive && "text-white dark:text-black")}
|
||||
/>
|
||||
)}
|
||||
{href === "/build" && (
|
||||
<CubeIcon
|
||||
className={cn("h-6 w-6", isActive && "text-white dark:text-black")}
|
||||
/>
|
||||
)}
|
||||
{href === "/monitor" && (
|
||||
<IconLaptop
|
||||
className={cn("h-6 w-6", isActive && "text-white dark:text-black")}
|
||||
/>
|
||||
)}
|
||||
{href === "/library" && (
|
||||
<HouseIcon
|
||||
className={cn("h-6 w-6", isActive && "text-white dark:text-black")}
|
||||
/>
|
||||
)}
|
||||
<Text
|
||||
variant="body-medium"
|
||||
className={cn(
|
||||
"hidden lg:block",
|
||||
isActive ? "!text-white" : "!text-black",
|
||||
)}
|
||||
>
|
||||
{name}
|
||||
</Text>
|
||||
</div>
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import { IconAutoGPTLogo } from "@/components/ui/icons";
|
||||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
|
||||
export function NavbarLoading() {
|
||||
return (
|
||||
<nav className="sticky top-0 z-40 hidden h-16 items-center rounded-bl-2xl rounded-br-2xl border border-white/50 bg-white/5 p-3 backdrop-blur-[26px] md:inline-flex">
|
||||
<div className="flex flex-1 items-center gap-6">
|
||||
<Skeleton className="h-4 w-20 bg-white/20" />
|
||||
<Skeleton className="h-4 w-16 bg-white/20" />
|
||||
<Skeleton className="h-4 w-12 bg-white/20" />
|
||||
</div>
|
||||
<div className="absolute left-1/2 top-1/2 h-10 w-[88.87px] -translate-x-1/2 -translate-y-1/2">
|
||||
<IconAutoGPTLogo className="h-full w-full" />
|
||||
</div>
|
||||
<div className="flex flex-1 items-center justify-end gap-4">
|
||||
<Skeleton className="h-8 w-8 rounded-full bg-white/20" />
|
||||
<Skeleton className="h-8 w-8 rounded-full bg-white/20" />
|
||||
</div>
|
||||
</nav>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
import { getV2GetUserProfile } from "@/app/api/__generated__/endpoints/store/store";
|
||||
import { getServerUser } from "@/lib/supabase/server/getServerUser";
|
||||
|
||||
export async function getNavbarAccountData() {
|
||||
const { user } = await getServerUser();
|
||||
const isLoggedIn = Boolean(user);
|
||||
|
||||
if (!isLoggedIn) {
|
||||
return {
|
||||
profile: null,
|
||||
isLoggedIn,
|
||||
};
|
||||
}
|
||||
|
||||
let profile = null;
|
||||
|
||||
try {
|
||||
const profileResponse = await getV2GetUserProfile();
|
||||
profile = profileResponse.data || null;
|
||||
} catch (error) {
|
||||
console.error("Error fetching profile:", error);
|
||||
profile = null;
|
||||
}
|
||||
|
||||
return {
|
||||
profile,
|
||||
isLoggedIn,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
import {
|
||||
IconBuilder,
|
||||
IconEdit,
|
||||
IconLayoutDashboard,
|
||||
IconLibrary,
|
||||
IconLogOut,
|
||||
IconMarketplace,
|
||||
IconRefresh,
|
||||
IconSettings,
|
||||
IconType,
|
||||
IconUploadCloud,
|
||||
} from "@/components/ui/icons";
|
||||
|
||||
type Link = {
|
||||
name: string;
|
||||
href: string;
|
||||
};
|
||||
|
||||
export const loggedInLinks: Link[] = [
|
||||
{
|
||||
name: "Marketplace",
|
||||
href: "/marketplace",
|
||||
},
|
||||
{
|
||||
name: "Library",
|
||||
href: "/library",
|
||||
},
|
||||
{
|
||||
name: "Build",
|
||||
href: "/build",
|
||||
},
|
||||
];
|
||||
|
||||
export const loggedOutLinks: Link[] = [
|
||||
{
|
||||
name: "Marketplace",
|
||||
href: "/marketplace",
|
||||
},
|
||||
];
|
||||
|
||||
export type MenuItemGroup = {
|
||||
groupName?: string;
|
||||
items: {
|
||||
icon: IconType;
|
||||
text: string;
|
||||
href?: string;
|
||||
onClick?: () => void;
|
||||
}[];
|
||||
};
|
||||
|
||||
export const accountMenuItems: MenuItemGroup[] = [
|
||||
{
|
||||
items: [
|
||||
{
|
||||
icon: IconType.Edit,
|
||||
text: "Edit profile",
|
||||
href: "/profile",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
items: [
|
||||
{
|
||||
icon: IconType.LayoutDashboard,
|
||||
text: "Creator Dashboard",
|
||||
href: "/profile/dashboard",
|
||||
},
|
||||
{
|
||||
icon: IconType.UploadCloud,
|
||||
text: "Publish an agent",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
items: [
|
||||
{
|
||||
icon: IconType.Settings,
|
||||
text: "Settings",
|
||||
href: "/profile/settings",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
items: [
|
||||
{
|
||||
icon: IconType.LogOut,
|
||||
text: "Log out",
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
export function getAccountMenuOptionIcon(icon: IconType) {
|
||||
const iconClass = "w-6 h-6";
|
||||
switch (icon) {
|
||||
case IconType.LayoutDashboard:
|
||||
return <IconLayoutDashboard className={iconClass} />;
|
||||
case IconType.UploadCloud:
|
||||
return <IconUploadCloud className={iconClass} />;
|
||||
case IconType.Edit:
|
||||
return <IconEdit className={iconClass} />;
|
||||
case IconType.Settings:
|
||||
return <IconSettings className={iconClass} />;
|
||||
case IconType.LogOut:
|
||||
return <IconLogOut className={iconClass} />;
|
||||
case IconType.Marketplace:
|
||||
return <IconMarketplace className={iconClass} />;
|
||||
case IconType.Library:
|
||||
return <IconLibrary className={iconClass} />;
|
||||
case IconType.Builder:
|
||||
return <IconBuilder className={iconClass} />;
|
||||
default:
|
||||
return <IconRefresh className={iconClass} />;
|
||||
}
|
||||
}
|
||||
@@ -71,6 +71,30 @@ export const colors = {
|
||||
800: "#0c5a29",
|
||||
900: "#09441f",
|
||||
},
|
||||
purple: {
|
||||
50: "#f1ebfe",
|
||||
100: "#d5c0fc",
|
||||
200: "#c0a1fa",
|
||||
300: "#a476f8",
|
||||
400: "#925cf7",
|
||||
500: "#7733f5",
|
||||
600: "#6c2edf",
|
||||
700: "#5424ae",
|
||||
800: "#411c87",
|
||||
900: "#321567",
|
||||
},
|
||||
pink: {
|
||||
50: "#fdedf5",
|
||||
100: "#f9c6df",
|
||||
200: "#f6abd0",
|
||||
300: "#f284bb",
|
||||
400: "#f06dad",
|
||||
500: "#ec4899",
|
||||
600: "#d7428b",
|
||||
700: "#a8336d",
|
||||
800: "#822854",
|
||||
900: "#631e40",
|
||||
},
|
||||
|
||||
// Special semantic colors
|
||||
white: "#fefefe",
|
||||
|
||||
@@ -14,54 +14,54 @@ const meta: Meta = {
|
||||
export default meta;
|
||||
|
||||
// Border radius scale data based on Figma design tokens
|
||||
// Custom naming convention: xs, s, m, l, xl, 2xl, full
|
||||
// Custom naming convention: xsmall, small, medium, large, xlarge, 2xlarge, full
|
||||
const borderRadiusScale = [
|
||||
{
|
||||
name: "xs",
|
||||
name: "xsmall",
|
||||
value: "0.25rem",
|
||||
rem: "0.25rem",
|
||||
px: "4px",
|
||||
class: "rounded-xs",
|
||||
class: "rounded-xsmall",
|
||||
description: "Extra small - for subtle rounding",
|
||||
},
|
||||
{
|
||||
name: "s",
|
||||
name: "small",
|
||||
value: "0.5rem",
|
||||
rem: "0.5rem",
|
||||
px: "8px",
|
||||
class: "rounded-s",
|
||||
class: "rounded-small",
|
||||
description: "Small - for cards and containers",
|
||||
},
|
||||
{
|
||||
name: "m",
|
||||
name: "medium",
|
||||
value: "0.75rem",
|
||||
rem: "0.75rem",
|
||||
px: "12px",
|
||||
class: "rounded-m",
|
||||
class: "rounded-medium",
|
||||
description: "Medium - for buttons and inputs",
|
||||
},
|
||||
{
|
||||
name: "l",
|
||||
name: "large",
|
||||
value: "1rem",
|
||||
rem: "1rem",
|
||||
px: "16px",
|
||||
class: "rounded-l",
|
||||
class: "rounded-large",
|
||||
description: "Large - for panels and modals",
|
||||
},
|
||||
{
|
||||
name: "xl",
|
||||
name: "xlarge",
|
||||
value: "1.25rem",
|
||||
rem: "1.25rem",
|
||||
px: "20px",
|
||||
class: "rounded-xl",
|
||||
class: "rounded-xlarge",
|
||||
description: "Extra large - for hero sections",
|
||||
},
|
||||
{
|
||||
name: "2xl",
|
||||
name: "2xlarge",
|
||||
value: "1.5rem",
|
||||
rem: "1.5rem",
|
||||
px: "24px",
|
||||
class: "rounded-2xl",
|
||||
class: "rounded-2xlarge",
|
||||
description: "2X large - for major containers",
|
||||
},
|
||||
{
|
||||
@@ -84,10 +84,11 @@ export function AllVariants() {
|
||||
Border Radius
|
||||
</Text>
|
||||
<Text variant="large" className="text-zinc-600">
|
||||
Our border radius system uses a simplified naming convention (xs, s,
|
||||
m, l, xl, 2xl, full) based on our Figma design tokens. This creates
|
||||
visual hierarchy and maintains design consistency across all
|
||||
components.
|
||||
Our border radius system uses a descriptive naming convention
|
||||
(xsmall, small, medium, large, xlarge, 2xlarge, full) based on our
|
||||
Figma design tokens. This creates visual hierarchy and maintains
|
||||
design consistency across all components while avoiding conflicts
|
||||
with Tailwind's built-in classes.
|
||||
</Text>
|
||||
</div>
|
||||
|
||||
@@ -137,9 +138,10 @@ export function AllVariants() {
|
||||
</div>
|
||||
<Text variant="body" className="mb-4 text-zinc-600">
|
||||
We use a custom border radius system based on our Figma design
|
||||
tokens, with simplified naming (xs, s, m, l, xl, 2xl, full) that
|
||||
provides consistent radius values optimized for our design
|
||||
system.
|
||||
tokens, with descriptive naming (xsmall, small, medium, large,
|
||||
xlarge, 2xlarge, full) that provides consistent radius values
|
||||
optimized for our design system while avoiding conflicts with
|
||||
Tailwind's built-in classes.
|
||||
</Text>
|
||||
</div>
|
||||
</div>
|
||||
@@ -188,7 +190,8 @@ export function AllVariants() {
|
||||
<Text variant="body" className="mb-6 text-zinc-600">
|
||||
All border radius values from our Figma design tokens. Each value
|
||||
can be applied to all corners or specific corners/sides using our
|
||||
simplified naming convention.
|
||||
descriptive naming convention (xsmall, small, medium, large, xlarge,
|
||||
2xlarge, full).
|
||||
</Text>
|
||||
</div>
|
||||
|
||||
@@ -232,30 +235,30 @@ export function AllVariants() {
|
||||
|
||||
<StoryCode
|
||||
code={`// Border radius examples - Design System Tokens
|
||||
<div className="rounded-xs">Extra small rounding (4px)</div>
|
||||
<div className="rounded-s">Small rounding (8px)</div>
|
||||
<div className="rounded-m">Medium rounding (12px)</div>
|
||||
<div className="rounded-l">Large rounding (16px)</div>
|
||||
<div className="rounded-xl">Extra large rounding (20px)</div>
|
||||
<div className="rounded-2xl">2X large rounding (24px)</div>
|
||||
<div className="rounded-xsmall">Extra small rounding (4px)</div>
|
||||
<div className="rounded-small">Small rounding (8px)</div>
|
||||
<div className="rounded-medium">Medium rounding (12px)</div>
|
||||
<div className="rounded-large">Large rounding (16px)</div>
|
||||
<div className="rounded-xlarge">Extra large rounding (20px)</div>
|
||||
<div className="rounded-2xlarge">2X large rounding (24px)</div>
|
||||
<div className="rounded-full">Pill buttons (circular)</div>
|
||||
|
||||
// Directional rounding (works with all sizes)
|
||||
<div className="rounded-t-m">Top corners only</div>
|
||||
<div className="rounded-r-m">Right corners only</div>
|
||||
<div className="rounded-b-m">Bottom corners only</div>
|
||||
<div className="rounded-l-m">Left corners only</div>
|
||||
<div className="rounded-t-medium">Top corners only</div>
|
||||
<div className="rounded-r-medium">Right corners only</div>
|
||||
<div className="rounded-b-medium">Bottom corners only</div>
|
||||
<div className="rounded-l-medium">Left corners only</div>
|
||||
|
||||
// Individual corners
|
||||
<div className="rounded-tl-m">Top-left corner</div>
|
||||
<div className="rounded-tr-m">Top-right corner</div>
|
||||
<div className="rounded-bl-m">Bottom-left corner</div>
|
||||
<div className="rounded-br-m">Bottom-right corner</div>
|
||||
<div className="rounded-tl-medium">Top-left corner</div>
|
||||
<div className="rounded-tr-medium">Top-right corner</div>
|
||||
<div className="rounded-bl-medium">Bottom-left corner</div>
|
||||
<div className="rounded-br-medium">Bottom-right corner</div>
|
||||
|
||||
// Usage recommendations
|
||||
<button className="rounded-full">Pill Button</button>
|
||||
<div className="rounded-m">Card Container</div>
|
||||
<input className="rounded-s">Input Field</input>`}
|
||||
<div className="rounded-medium">Card Container</div>
|
||||
<input className="rounded-small">Input Field</input>`}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -36,6 +36,8 @@ const colorCategories = Object.entries(colors)
|
||||
orange: "Warnings, notifications, and secondary call-to-actions",
|
||||
yellow: "Highlights, cautions, and attention-grabbing elements",
|
||||
green: "Success states, confirmations, and positive actions",
|
||||
purple: "Brand accents, premium features, and creative elements",
|
||||
pink: "Highlights, special promotions, and playful interactions",
|
||||
};
|
||||
|
||||
return {
|
||||
@@ -312,6 +314,8 @@ export function AllVariants() {
|
||||
<div className="bg-green-50 border-green-200 text-green-800">Success</div>
|
||||
<div className="bg-red-50 border-red-200 text-red-800">Error</div>
|
||||
<div className="bg-yellow-50 border-yellow-200 text-yellow-800">Warning</div>
|
||||
<div className="bg-purple-50 border-purple-200 text-purple-800">Premium</div>
|
||||
<div className="bg-pink-50 border-pink-200 text-pink-800">Special</div>
|
||||
|
||||
// ❌ INCORRECT - Don't use these
|
||||
<div className="bg-blue-500 text-purple-600">❌ Not approved</div>
|
||||
|
||||
@@ -11,12 +11,12 @@ const ScrollArea = React.forwardRef<
|
||||
>(({ className, children, ...props }, ref) => (
|
||||
<ScrollAreaPrimitive.Root
|
||||
ref={ref}
|
||||
className={cn("relative overflow-hidden", className)}
|
||||
className={cn("relative", className)}
|
||||
{...props}
|
||||
>
|
||||
<ScrollAreaPrimitive.Viewport
|
||||
className="h-full w-full rounded-[inherit]"
|
||||
style={{ overflow: "scroll" }}
|
||||
style={{ overflowX: "hidden", overflowY: "scroll" }}
|
||||
>
|
||||
{children}
|
||||
</ScrollAreaPrimitive.Viewport>
|
||||
|
||||
@@ -166,6 +166,8 @@ export async function serverLogout(options: ServerLogoutOptions = {}) {
|
||||
scope: options.globalLogout ? "global" : "local",
|
||||
});
|
||||
|
||||
revalidatePath("/");
|
||||
|
||||
if (error) {
|
||||
console.error("Error logging out:", error);
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ export function useSupabase() {
|
||||
const [isUserLoading, setIsUserLoading] = useState(true);
|
||||
const lastValidationRef = useRef<number>(0);
|
||||
const isValidatingRef = useRef(false);
|
||||
const isLoggedIn = Boolean(user);
|
||||
|
||||
const supabase = useMemo(() => {
|
||||
try {
|
||||
@@ -50,7 +51,9 @@ export function useSupabase() {
|
||||
await serverLogout(options);
|
||||
} catch (error) {
|
||||
console.error("Error logging out:", error);
|
||||
router.push("/login");
|
||||
} finally {
|
||||
setUser(null);
|
||||
router.refresh();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -162,9 +165,9 @@ export function useSupabase() {
|
||||
}, []);
|
||||
|
||||
return {
|
||||
supabase, // Available for non-auth operations like real-time subscriptions
|
||||
user,
|
||||
isLoggedIn: !isUserLoading ? !!user : null,
|
||||
supabase, // Available for non-auth operations like real-time subscriptions
|
||||
isLoggedIn,
|
||||
isUserLoading,
|
||||
logOut,
|
||||
validateSession: validateSessionServer,
|
||||
|
||||
Reference in New Issue
Block a user