From 3de8d8de1891ad827da46a79083ef36514d70b45 Mon Sep 17 00:00:00 2001 From: thebeyondr <19380973+thebeyondr@users.noreply.github.com> Date: Wed, 17 Sep 2025 11:52:06 -0500 Subject: [PATCH 01/15] fix(ui): format API key creation date in onboarding step Updated the date format for the API key creation timestamp in the onboarding component to a more user-friendly format, enhancing readability. --- apps/ui/src/components/onboarding/api-key-step.tsx | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/apps/ui/src/components/onboarding/api-key-step.tsx b/apps/ui/src/components/onboarding/api-key-step.tsx index d1e9531e3..d76a8ad1e 100644 --- a/apps/ui/src/components/onboarding/api-key-step.tsx +++ b/apps/ui/src/components/onboarding/api-key-step.tsx @@ -259,8 +259,13 @@ export function ApiKeyStep() { {key.status} - - {key.createdAt} + + {new Intl.DateTimeFormat(undefined, { + month: "short", + day: "numeric", + hour: "2-digit", + minute: "2-digit", + }).format(new Date(key.createdAt))} ))} From b0f163747fbfac4b2b3c5913e81d443bd8548725 Mon Sep 17 00:00:00 2001 From: thebeyondr <19380973+thebeyondr@users.noreply.github.com> Date: Wed, 17 Sep 2025 11:52:46 -0500 Subject: [PATCH 02/15] chore(docs): update README for development setup instructions Revised the development setup section to clarify the steps for starting required services, building packages, and setting up the database. Added notes for WSL2 users regarding Docker integration. --- README.md | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 1fb1a3c53..4244f7a01 100644 --- a/README.md +++ b/README.md @@ -43,13 +43,34 @@ curl -X POST https://api.llmgateway.io/v1/chat/completions \ pnpm install ``` -2. Start development servers: +2. Start required services (PostgreSQL and Redis): + + ```bash + docker compose up -d + ``` + + **Note for WSL2 users**: Ensure Docker Desktop is running with WSL integration enabled. + +3. Build all packages: + + ```bash + pnpm build + ``` + +4. Set up the database: + + ```bash + pnpm --filter db push + pnpm --filter db seed + ``` + +5. Start development servers: ```bash pnpm dev ``` -3. Build for production: +6. Build for production: ```bash pnpm build ``` From ce81cdb5b9ea702d8c53566afcf152c0cfa62f7e Mon Sep 17 00:00:00 2001 From: thebeyondr <19380973+thebeyondr@users.noreply.github.com> Date: Wed, 17 Sep 2025 12:42:11 -0500 Subject: [PATCH 03/15] feat(ui): improve API keys interface with cleaner mobile layout - Replace status column with inline badges for better space usage - Remove redundant "Keys" card wrapper on mobile for cleaner design - Fix mobile button placement to be full-width and below header - Remove redundant copy and simplify card descriptions - Use consistent badge styling across desktop and mobile views --- .../src/components/api-keys/api-keys-list.tsx | 76 ++++++++++++------- 1 file changed, 48 insertions(+), 28 deletions(-) diff --git a/apps/ui/src/components/api-keys/api-keys-list.tsx b/apps/ui/src/components/api-keys/api-keys-list.tsx index 021eb5789..63b1c9c25 100644 --- a/apps/ui/src/components/api-keys/api-keys-list.tsx +++ b/apps/ui/src/components/api-keys/api-keys-list.tsx @@ -50,11 +50,12 @@ import { } from "@/lib/components/table"; import { toast } from "@/lib/components/use-toast"; import { useApi } from "@/lib/fetch-client"; +import { cn } from "@/lib/utils"; import { CreateApiKeyDialog } from "./create-api-key-dialog"; import { IamRulesDialog } from "./iam-rules-dialog"; -import type { Project, ApiKey } from "@/lib/types"; +import type { ApiKey, Project } from "@/lib/types"; interface ApiKeysListProps { selectedProject: Project | null; @@ -273,7 +274,6 @@ export function ApiKeysList({ Name API Key Created - Status Usage Usage Limit IAM Rules @@ -282,26 +282,40 @@ export function ApiKeysList({ {keys!.map((key) => ( - - {key.description} + + +
+ + {key.description} + + + {key.status} + +
+
{key.maskedToken}
- {key.createdAt} - - {key.status} - + {Intl.DateTimeFormat(undefined, { + month: "short", + day: "numeric", + year: "numeric", + hour: "2-digit", + minute: "2-digit", + }).format(new Date(key.createdAt))} ${Number(key.usage).toFixed(2)} @@ -475,25 +489,31 @@ export function ApiKeysList({ {/* Mobile Cards */}
{keys!.map((key) => ( -
+
-

{key.description}

-
+
+

{key.description}

{key.status} +
+
- {key.createdAt} + {Intl.DateTimeFormat(undefined, { + month: "short", + day: "numeric", + year: "numeric", + hour: "2-digit", + minute: "2-digit", + }).format(new Date(key.createdAt))}
From 2f2b43774625afb8b561164bbb1619024b2b775c Mon Sep 17 00:00:00 2001 From: thebeyondr <19380973+thebeyondr@users.noreply.github.com> Date: Wed, 17 Sep 2025 12:42:35 -0500 Subject: [PATCH 04/15] feat(ui): enhance API keys layout for better mobile experience - Updated layout to use flexbox for improved responsiveness - Separated desktop and mobile views for API keys display - Simplified card structure and descriptions for clarity - Ensured full-width button on mobile for better usability --- .../components/api-keys/api-keys-client.tsx | 59 ++++++++++++------- 1 file changed, 37 insertions(+), 22 deletions(-) diff --git a/apps/ui/src/components/api-keys/api-keys-client.tsx b/apps/ui/src/components/api-keys/api-keys-client.tsx index 2ef085715..3640cb9b8 100644 --- a/apps/ui/src/components/api-keys/api-keys-client.tsx +++ b/apps/ui/src/components/api-keys/api-keys-client.tsx @@ -60,8 +60,8 @@ export function ApiKeysClient({ initialData }: { initialData: ApiKey[] }) { return (
-
-
+
+

API Keys

@@ -70,7 +70,7 @@ export function ApiKeysClient({ initialData }: { initialData: ApiKey[] }) {

{selectedProject && ( - @@ -78,25 +78,40 @@ export function ApiKeysClient({ initialData }: { initialData: ApiKey[] }) { )}
- - - Your API Keys - - API keys allow you to authenticate with the LLM Gateway API. - {!selectedProject && ( - - Loading project information... - - )} - - - - - - + {/* Desktop Card */} +
+ + + Keys + + {!selectedProject && ( + + Loading project information... + + )} + + + + + + +
+ + {/* Mobile - Direct rendering */} +
+ {!selectedProject && ( +
+ Loading project information... +
+ )} + +
From 3b72bfca66cd0f690db9d0f32007531e45a0145f Mon Sep 17 00:00:00 2001 From: thebeyondr <19380973+thebeyondr@users.noreply.github.com> Date: Wed, 17 Sep 2025 12:49:36 -0500 Subject: [PATCH 05/15] chore(docs): update CLAUDE.md for clarity and structure - Revised guidance to specify AI agents instead of Claude Code - Added new commands for pushing and seeding the database - Corrected "LLMGateway" to "LLM Gateway" for consistency - Introduced folder structure section for better organization - Added license information and details on enterprise features --- CLAUDE.md | 34 ++++++++++++++++++++++++++++++++-- 1 file changed, 32 insertions(+), 2 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index de8c087d1..d10c7a7e4 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,6 +1,6 @@ # CLAUDE.md -This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. +This file provides guidance to AI agents when working with code in this repository. ## Development Commands @@ -59,11 +59,13 @@ E2E tests are organized for optimal performance: NOTE: these commands can only be run in the root directory of the repository, not in individual app directories. +- `pnpm --filter db push` - Push database schema +- `pnpm --filter db seed` - Seed database with initial data - `pnpm run setup` – Reset db, sync schema, seed data (use this for development) ## Architecture Overview -**LLMGateway** is a monorepo containing a full-stack LLM API gateway with multiple services: +**LLM Gateway** is a monorepo containing a full-stack LLM API gateway with multiple services: ### Core Services @@ -140,6 +142,16 @@ NOTE: these commands can only be run in the root directory of the repository, no - PostgreSQL: localhost:5432 - Redis: localhost:6379 +## Folder Structure + +- `apps/ui`: Next.js frontend +- `apps/api`: Hono backend +- `apps/gateway`: API gateway for routing LLM requests +- `apps/docs`: Documentation site +- `packages/db`: Drizzle ORM schema and migrations +- `packages/models`: Model and provider definitions +- `packages/shared`: Shared types and utilities + ## Key Features ### LLM Gateway @@ -165,3 +177,21 @@ NOTE: these commands can only be run in the root directory of the repository, no - API keys and provider configurations - Usage tracking and billing records - Analytics and performance metrics + +## License + +LLM Gateway is available under a dual license: + +- **Open Source**: Core functionality is licensed under AGPLv3 - see the [LICENSE](LICENSE) file for details. +- **Enterprise**: Commercial features in the `ee/` directory require an Enterprise license - see [ee/LICENSE](ee/LICENSE) for details. + +### Enterprise features include: + +- Advanced billing and subscription management +- Extended data retention (90 days vs 3 days) +- Provider API key management (Pro plan) +- Team and organization management +- Priority support +- And more to be defined + +For enterprise licensing, please contact us at contact@llmgateway.io From 9524b2aaad152d806d422084442ae5e9ad00cdc8 Mon Sep 17 00:00:00 2001 From: thebeyondr <19380973+thebeyondr@users.noreply.github.com> Date: Wed, 17 Sep 2025 13:11:50 -0500 Subject: [PATCH 06/15] fix(ui): update badge styling for API keys list - Changed badge variant from "outline" to "secondary" for better visual distinction - Updated active status badge styles for improved readability and consistency across the UI --- apps/ui/src/components/api-keys/api-keys-list.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/ui/src/components/api-keys/api-keys-list.tsx b/apps/ui/src/components/api-keys/api-keys-list.tsx index 63b1c9c25..916f98af6 100644 --- a/apps/ui/src/components/api-keys/api-keys-list.tsx +++ b/apps/ui/src/components/api-keys/api-keys-list.tsx @@ -292,11 +292,11 @@ export function ApiKeysList({ {key.description} {key.status} @@ -499,7 +499,7 @@ export function ApiKeysList({ className={cn( "text-xs uppercase text-gray-500 border-gray-500/50 dark:text-gray-300 dark:border-gray-300/50", key.status === "active" && - "text-green-500 border-green-500/50 dark:text-green-500 dark:border-green-500/50", + "bg-green-600/10 text-green-600 border-green-600/50 dark:text-green-500 dark:border-green-500/50", )} > {key.status} From ea0a5b4b7328dc5d8cdb5cc08acd0d6da567fd22 Mon Sep 17 00:00:00 2001 From: thebeyondr <19380973+thebeyondr@users.noreply.github.com> Date: Wed, 17 Sep 2025 13:17:23 -0500 Subject: [PATCH 07/15] feat(ui): introduce StatusBadge component for API and provider keys - Added StatusBadge component to standardize status display across API keys and provider keys lists - Replaced existing badge implementations with StatusBadge for improved consistency and styling --- .../src/components/api-keys/api-keys-list.tsx | 25 ++----------- .../provider-keys/provider-keys-list.tsx | 15 +++----- apps/ui/src/components/ui/status-badge.tsx | 36 +++++++++++++++++++ 3 files changed, 44 insertions(+), 32 deletions(-) create mode 100644 apps/ui/src/components/ui/status-badge.tsx diff --git a/apps/ui/src/components/api-keys/api-keys-list.tsx b/apps/ui/src/components/api-keys/api-keys-list.tsx index 916f98af6..804453974 100644 --- a/apps/ui/src/components/api-keys/api-keys-list.tsx +++ b/apps/ui/src/components/api-keys/api-keys-list.tsx @@ -7,6 +7,7 @@ import { Shield, } from "lucide-react"; +import { StatusBadge } from "@/components/ui/status-badge"; import { AlertDialog, AlertDialogAction, @@ -18,7 +19,6 @@ import { AlertDialogTitle, AlertDialogTrigger, } from "@/lib/components/alert-dialog"; -import { Badge } from "@/lib/components/badge"; import { Button } from "@/lib/components/button"; import { Dialog, @@ -50,7 +50,6 @@ import { } from "@/lib/components/table"; import { toast } from "@/lib/components/use-toast"; import { useApi } from "@/lib/fetch-client"; -import { cn } from "@/lib/utils"; import { CreateApiKeyDialog } from "./create-api-key-dialog"; import { IamRulesDialog } from "./iam-rules-dialog"; @@ -291,16 +290,7 @@ export function ApiKeysList({ {key.description} - - {key.status} - +
@@ -494,16 +484,7 @@ export function ApiKeysList({

{key.description}

- - {key.status} - +
diff --git a/apps/ui/src/components/provider-keys/provider-keys-list.tsx b/apps/ui/src/components/provider-keys/provider-keys-list.tsx index 90757ecb3..a5862e981 100644 --- a/apps/ui/src/components/provider-keys/provider-keys-list.tsx +++ b/apps/ui/src/components/provider-keys/provider-keys-list.tsx @@ -4,6 +4,7 @@ import { useQueryClient } from "@tanstack/react-query"; import { KeyIcon, MoreHorizontal } from "lucide-react"; import { ProviderIcons } from "@/components/ui/providers-icons"; +import { StatusBadge } from "@/components/ui/status-badge"; import { AlertDialog, AlertDialogAction, @@ -187,16 +188,10 @@ export function ProviderKeysList({
{hasKey && (
- - {existingKey.status} - + {existingKey.maskedToken} diff --git a/apps/ui/src/components/ui/status-badge.tsx b/apps/ui/src/components/ui/status-badge.tsx new file mode 100644 index 000000000..a95086ef2 --- /dev/null +++ b/apps/ui/src/components/ui/status-badge.tsx @@ -0,0 +1,36 @@ +import { Badge } from "@/lib/components/badge"; +import { cn } from "@/lib/utils"; + +interface StatusBadgeProps { + status: "active" | "inactive" | "deleted" | null; + variant?: "simple" | "detailed"; +} + +export function StatusBadge({ + status, + variant = "detailed", +}: StatusBadgeProps) { + if (variant === "simple") { + return ( + + {status} + + ); + } + + return ( + + {status} + + ); +} From b09bd7471c490466dfc59e631c478b9a0d658d75 Mon Sep 17 00:00:00 2001 From: thebeyondr <19380973+thebeyondr@users.noreply.github.com> Date: Thu, 18 Sep 2025 10:36:07 -0500 Subject: [PATCH 08/15] chore(docs): refine development setup instructions - Updated the development setup section to combine dependency installation and environment setup into a single command. - Clarified the steps for starting development servers and building for production. - Removed redundant steps for starting services and setting up the database, streamlining the process for users. --- README.md | 27 +++++---------------------- 1 file changed, 5 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index 4244f7a01..668cb877b 100644 --- a/README.md +++ b/README.md @@ -37,40 +37,23 @@ curl -X POST https://api.llmgateway.io/v1/chat/completions \ ## Development Setup -1. Install dependencies: +1. Install dependencies and set up the development environment: ```bash - pnpm install + pnpm i && pnpm run setup ``` -2. Start required services (PostgreSQL and Redis): - - ```bash - docker compose up -d - ``` + This will install all dependencies, start Docker services, sync the database schema, and seed initial data. **Note for WSL2 users**: Ensure Docker Desktop is running with WSL integration enabled. -3. Build all packages: - - ```bash - pnpm build - ``` - -4. Set up the database: - - ```bash - pnpm --filter db push - pnpm --filter db seed - ``` - -5. Start development servers: +2. Start development servers: ```bash pnpm dev ``` -6. Build for production: +3. Build for production: ```bash pnpm build ``` From 994aacd07a56b5a07979f6b1cb34ed5cc6fc11a9 Mon Sep 17 00:00:00 2001 From: thebeyondr <19380973+thebeyondr@users.noreply.github.com> Date: Thu, 18 Sep 2025 13:40:45 -0500 Subject: [PATCH 09/15] feat(ui): add status filter and bulk activation for API keys - Implemented a status filter for API keys, allowing users to view all, active, or inactive keys. - Added bulk activation functionality for inactive keys, enhancing user management capabilities. - Introduced Tabs and Tooltip components for improved UI interaction and information display. - Updated the API keys list to reflect the new filtering and activation features. --- .../src/components/api-keys/api-keys-list.tsx | 210 ++++++++++++++++-- 1 file changed, 190 insertions(+), 20 deletions(-) diff --git a/apps/ui/src/components/api-keys/api-keys-list.tsx b/apps/ui/src/components/api-keys/api-keys-list.tsx index 804453974..ddafb9d0b 100644 --- a/apps/ui/src/components/api-keys/api-keys-list.tsx +++ b/apps/ui/src/components/api-keys/api-keys-list.tsx @@ -5,7 +5,9 @@ import { MoreHorizontal, PlusIcon, Shield, + Zap, } from "lucide-react"; +import { useState } from "react"; import { StatusBadge } from "@/components/ui/status-badge"; import { @@ -19,6 +21,7 @@ import { AlertDialogTitle, AlertDialogTrigger, } from "@/lib/components/alert-dialog"; +import { Badge } from "@/lib/components/badge"; import { Button } from "@/lib/components/button"; import { Dialog, @@ -48,6 +51,12 @@ import { TableHeader, TableRow, } from "@/lib/components/table"; +import { Tabs, TabsList, TabsTrigger } from "@/lib/components/tabs"; +import { + Tooltip, + TooltipContent, + TooltipTrigger, +} from "@/lib/components/tooltip"; import { toast } from "@/lib/components/use-toast"; import { useApi } from "@/lib/fetch-client"; @@ -61,12 +70,15 @@ interface ApiKeysListProps { initialData: ApiKey[]; } +type StatusFilter = "all" | "active" | "inactive"; + export function ApiKeysList({ selectedProject, initialData, }: ApiKeysListProps) { const queryClient = useQueryClient(); const api = useApi(); + const [statusFilter, setStatusFilter] = useState("active"); // All hooks must be called before any conditional returns const { data, isLoading, error } = api.useQuery( @@ -120,7 +132,21 @@ export function ApiKeysList({ ); } - const keys = data?.apiKeys.filter((key) => key.status !== "deleted"); + const allKeys = data?.apiKeys.filter((key) => key.status !== "deleted") || []; + const activeKeys = allKeys.filter((key) => key.status === "active"); + const inactiveKeys = allKeys.filter((key) => key.status === "inactive"); + + const filteredKeys = (() => { + switch (statusFilter) { + case "active": + return activeKeys; + case "inactive": + return inactiveKeys; + case "all": + default: + return allKeys; + } + })(); // Handle loading state if (isLoading) { @@ -243,7 +269,37 @@ export function ApiKeysList({ ); }; - if (keys!.length === 0) { + const bulkActivateInactive = () => { + inactiveKeys.forEach((key) => { + toggleKeyStatus( + { + params: { + path: { id: key.id }, + }, + body: { + status: "active", + }, + }, + { + onSuccess: () => { + const queryKey = api.queryOptions("get", "/keys/api", { + params: { + query: { projectId: selectedProject!.id }, + }, + }).queryKey; + queryClient.invalidateQueries({ queryKey }); + }, + }, + ); + }); + + toast({ + title: "Activating Keys", + description: `${inactiveKeys.length} key${inactiveKeys.length !== 1 ? "s" : ""} are being activated.`, + }); + }; + + if (allKeys.length === 0) { return (
@@ -265,6 +321,105 @@ export function ApiKeysList({ return ( <> + {/* Status Filter Tabs */} +
+ setStatusFilter(value as StatusFilter)} + > + + + All{" "} + + {allKeys.length} + + + {activeKeys.length > 0 && ( + + Active{" "} + + {activeKeys.length} + + + )} + {inactiveKeys.length > 0 && ( + + Inactive{" "} + + {inactiveKeys.length} + + + )} + + +
+ + {/* Inactive Keys Summary Bar */} + {statusFilter === "active" && inactiveKeys.length > 0 && ( +
+
+
+
+ 💤 {inactiveKeys.length} inactive key + {inactiveKeys.length !== 1 ? "s" : ""} +
+
+
+ + +
+
+
+ )} + + {/* Bulk Actions Bar for Inactive Tab */} + {statusFilter === "inactive" && inactiveKeys.length > 0 && ( +
+
+
+
+ 💤 {inactiveKeys.length} inactive key + {inactiveKeys.length !== 1 ? "s" : ""} selected +
+
+
+ +
+
+
+ )} + {/* Desktop Table */}
@@ -272,6 +427,7 @@ export function ApiKeysList({ Name API Key + Status Created Usage Usage Limit @@ -280,18 +436,13 @@ export function ApiKeysList({ - {keys!.map((key) => ( + {filteredKeys.map((key) => ( -
- - {key.description} - - -
+ {key.description}
@@ -299,13 +450,31 @@ export function ApiKeysList({
- {Intl.DateTimeFormat(undefined, { - month: "short", - day: "numeric", - year: "numeric", - hour: "2-digit", - minute: "2-digit", - }).format(new Date(key.createdAt))} + + + + + + + {Intl.DateTimeFormat(undefined, { + month: "short", + day: "numeric", + year: "numeric", + }).format(new Date(key.createdAt))} + + + +

+ {Intl.DateTimeFormat(undefined, { + month: "short", + day: "numeric", + year: "numeric", + hour: "2-digit", + minute: "2-digit", + }).format(new Date(key.createdAt))} +

+
+
${Number(key.usage).toFixed(2)} @@ -434,7 +603,8 @@ export function ApiKeysList({ toggleStatus(key.id, key.status)} > - {key.status === "active" ? "Disable" : "Enable"} Key + {key.status === "active" ? "Deactivate" : "Activate"}{" "} + Key @@ -478,7 +648,7 @@ export function ApiKeysList({ {/* Mobile Cards */}
- {keys!.map((key) => ( + {filteredKeys.map((key) => (
@@ -517,7 +687,7 @@ export function ApiKeysList({ toggleStatus(key.id, key.status)} > - {key.status === "active" ? "Disable" : "Enable"} Key + {key.status === "active" ? "Deactivate" : "Activate"} Key From d90837a44548ba303b780bca1a4221da9dc2d478 Mon Sep 17 00:00:00 2001 From: thebeyondr <19380973+thebeyondr@users.noreply.github.com> Date: Thu, 18 Sep 2025 13:41:22 -0500 Subject: [PATCH 10/15] feat(ui): update API keys client component for improved UX - Replaced the Plus icon with Orbit for a fresh look in the Create API Key button. - Enhanced the description text to clarify the purpose of API keys. - Adjusted the Card component layout for better spacing and visual appeal. --- .../src/components/api-keys/api-keys-client.tsx | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/apps/ui/src/components/api-keys/api-keys-client.tsx b/apps/ui/src/components/api-keys/api-keys-client.tsx index 3640cb9b8..313380723 100644 --- a/apps/ui/src/components/api-keys/api-keys-client.tsx +++ b/apps/ui/src/components/api-keys/api-keys-client.tsx @@ -1,6 +1,6 @@ "use client"; -import { Plus } from "lucide-react"; +import { Orbit } from "lucide-react"; import { usePathname } from "next/navigation"; import { useMemo } from "react"; @@ -12,7 +12,6 @@ import { CardContent, CardDescription, CardHeader, - CardTitle, } from "@/lib/components/card"; import { useApi } from "@/lib/fetch-client"; import { extractOrgAndProjectFromPath } from "@/lib/navigation-utils"; @@ -65,13 +64,16 @@ export function ApiKeysClient({ initialData }: { initialData: ApiKey[] }) {

API Keys

- Manage your API keys for accessing LLM Gateway + Create and manage API keys to authenticate requests to LLM Gateway

{selectedProject && ( - @@ -80,9 +82,8 @@ export function ApiKeysClient({ initialData }: { initialData: ApiKey[] }) {
{/* Desktop Card */}
- + - Keys {!selectedProject && ( From e43ad7373dc4e29b351f249e818ab5415715af53 Mon Sep 17 00:00:00 2001 From: thebeyondr <19380973+thebeyondr@users.noreply.github.com> Date: Thu, 18 Sep 2025 13:47:00 -0500 Subject: [PATCH 11/15] feat(ui): refine API key onboarding component for better UX - Simplified header and description text for clarity. - Enhanced error handling for project loading status. - Improved layout for displaying existing API keys with mobile responsiveness. - Added Tooltip for displaying creation dates in a user-friendly format. - Updated card structure for newly created API keys to enhance visibility and organization. --- .../components/onboarding/api-key-step.tsx | 357 ++++++++++-------- 1 file changed, 206 insertions(+), 151 deletions(-) diff --git a/apps/ui/src/components/onboarding/api-key-step.tsx b/apps/ui/src/components/onboarding/api-key-step.tsx index d76a8ad1e..edfa8b819 100644 --- a/apps/ui/src/components/onboarding/api-key-step.tsx +++ b/apps/ui/src/components/onboarding/api-key-step.tsx @@ -1,20 +1,14 @@ import { zodResolver } from "@hookform/resolvers/zod"; -import { Copy, Key, CheckCircle, Plus } from "lucide-react"; +import { Copy, CheckCircle, Plus } from "lucide-react"; import * as React from "react"; import { useState } from "react"; import { useForm } from "react-hook-form"; import { z } from "zod"; +import { StatusBadge } from "@/components/ui/status-badge"; import { useDefaultProject } from "@/hooks/useDefaultProject"; -import { Badge } from "@/lib/components/badge"; import { Button } from "@/lib/components/button"; -import { - Card, - CardContent, - CardDescription, - CardHeader, - CardTitle, -} from "@/lib/components/card"; +import { Card, CardContent } from "@/lib/components/card"; import { Form, FormControl, @@ -33,6 +27,12 @@ import { TableHeader, TableRow, } from "@/lib/components/table"; +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from "@/lib/components/tooltip"; import { toast } from "@/lib/components/use-toast"; import { useApi } from "@/lib/fetch-client"; @@ -154,46 +154,35 @@ export function ApiKeyStep() {

- {hasExistingKeys ? "Your API Keys" : "Create Your API Key"} + {hasExistingKeys ? "API Keys" : "Create API Key"}

{hasExistingKeys - ? "Great! You already have API keys set up. You can use these to make requests to the LLM Gateway." - : "You'll need an API key to make requests to the LLM Gateway."} + ? "Use these keys to authenticate requests to the LLM Gateway." + : "Create an API key to authenticate requests to the LLM Gateway."}

+ {isError || !defaultProject ? ( +

+ Unable to load project. Please try again. +

+ ) : ( +

+ Project: {defaultProject.name} +

+ )}
- - - - - API Key{hasExistingKeys ? "s" : ""} - - - {hasExistingKeys - ? "Your existing API keys are listed below. You can create additional keys if needed." - : "Create an API key to authenticate your requests to the LLM Gateway."} - {isError || !defaultProject ? ( - - Unable to load project. Please try again. - - ) : ( - - Project: {defaultProject.name} - - )} - - - - {/* Show newly created API key */} - {apiKey && ( -
+
+ {/* Show newly created API key */} + {apiKey && ( + +
- New API Key Created! + API Key Created
-
+

{apiKey}

-
-

- Important -

+
+

Important

- This API key will only be shown once. Make sure to copy it - now and store it securely. You can always create new API - keys later. + This key is only shown once. Copy it now and store it + securely.

-
- )} + + + )} - {/* Show existing API keys */} - {hasExistingKeys && !showCreateForm && ( -
-
-
- - - Name - API Key - Status - Created - - - - {existingKeys.map((key) => ( - - - {key.description} - - - - {key.maskedToken} - - - - - {key.status} - - - - {new Intl.DateTimeFormat(undefined, { - month: "short", - day: "numeric", - hour: "2-digit", - minute: "2-digit", - }).format(new Date(key.createdAt))} - - - ))} - -
-
+ {/* Show existing API keys */} + {hasExistingKeys && !showCreateForm && ( + + +
+ {/* Desktop Table */} +
+
+ + + + Name + Key + Status + Created + + + + {existingKeys.map((key) => ( + + + + {key.description} + + + + + {key.maskedToken} + + + + + + + + + + + {Intl.DateTimeFormat(undefined, { + month: "short", + day: "numeric", + year: "numeric", + }).format(new Date(key.createdAt))} + + + +

+ {Intl.DateTimeFormat(undefined, { + month: "short", + day: "numeric", + year: "numeric", + hour: "2-digit", + minute: "2-digit", + }).format(new Date(key.createdAt))} +

+
+
+
+
+
+ ))} +
+
+
+
-
- -
-
- )} + {/* Mobile Cards */} +
+ {existingKeys.map((key) => ( +
+
+
+
+

+ {key.description} +

+ +
+
+ + {Intl.DateTimeFormat(undefined, { + month: "short", + day: "numeric", + year: "numeric", + hour: "2-digit", + minute: "2-digit", + }).format(new Date(key.createdAt))} + +
+
+
+
+
+ API Key +
+
+ {key.maskedToken} +
+
+
+ ))} +
- {/* Show create form (either when no existing keys or when requested) */} - {(!hasExistingKeys || showCreateForm) && !apiKey && ( -
- {showCreateForm && ( -
-

Create New API Key

+
- )} -
- - ( - - API Key Name - - - - - - )} - /> - +
+ )} + + - {isLoading ? "Creating..." : "Create API Key"} - - - -
- )} -
-
+ ( + + Name + + + + + + )} + /> + + + +
+ + + )} +
); From 344f531ca77758b8da57fcd3b8ab50dc57c5bb15 Mon Sep 17 00:00:00 2001 From: thebeyondr <19380973+thebeyondr@users.noreply.github.com> Date: Thu, 18 Sep 2025 13:47:44 -0500 Subject: [PATCH 12/15] feat(ui): enhance StatusBadge component with dynamic status icons - Added dynamic icons for different status states (active, inactive, default) using lucide-react. - Improved status text formatting for better readability. - Updated badge styling for a more consistent and visually appealing layout. --- apps/ui/src/components/ui/status-badge.tsx | 45 +++++++++++++++++++--- 1 file changed, 39 insertions(+), 6 deletions(-) diff --git a/apps/ui/src/components/ui/status-badge.tsx b/apps/ui/src/components/ui/status-badge.tsx index a95086ef2..cda4c566d 100644 --- a/apps/ui/src/components/ui/status-badge.tsx +++ b/apps/ui/src/components/ui/status-badge.tsx @@ -1,3 +1,5 @@ +import { CircleSlash, Clock, PlayCircle } from "lucide-react"; + import { Badge } from "@/lib/components/badge"; import { cn } from "@/lib/utils"; @@ -10,13 +12,44 @@ export function StatusBadge({ status, variant = "detailed", }: StatusBadgeProps) { + const statusText = status && status.charAt(0).toUpperCase() + status.slice(1); + + const getStatusConfig = () => { + switch (status) { + case "active": + return { + icon: PlayCircle, + className: + "bg-green-600/10 text-green-600 border-green-600/50 dark:text-green-500 dark:border-green-500/50", + iconClassName: "text-green-600 dark:text-green-500", + }; + case "inactive": + return { + icon: CircleSlash, + className: + "bg-gray-100 text-gray-600 border-gray-200 dark:bg-gray-800 dark:text-gray-400 dark:border-gray-700", + iconClassName: "text-gray-600 dark:text-gray-400", + }; + default: + return { + icon: Clock, + className: + "bg-gray-100 text-gray-600 border-gray-200 dark:bg-gray-800 dark:text-gray-400 dark:border-gray-700", + iconClassName: "text-gray-600 dark:text-gray-400", + }; + } + }; + + const config = getStatusConfig(); + const Icon = config.icon; + if (variant === "simple") { return ( - {status} + {statusText} ); } @@ -25,12 +58,12 @@ export function StatusBadge({ - {status} + + {statusText} ); } From bfe0bc97ccd51f5d186eca45144ec8da596e6ae2 Mon Sep 17 00:00:00 2001 From: thebeyondr <19380973+thebeyondr@users.noreply.github.com> Date: Thu, 18 Sep 2025 13:50:16 -0500 Subject: [PATCH 13/15] style(ui): enhance tooltip styling in API key components - Updated tooltip span in ApiKeysList and ApiKeyStep components to include a dotted underline and hover effect for improved user interaction and visual clarity. --- apps/ui/src/components/api-keys/api-keys-list.tsx | 2 +- apps/ui/src/components/onboarding/api-key-step.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/ui/src/components/api-keys/api-keys-list.tsx b/apps/ui/src/components/api-keys/api-keys-list.tsx index ddafb9d0b..79f2b8b0d 100644 --- a/apps/ui/src/components/api-keys/api-keys-list.tsx +++ b/apps/ui/src/components/api-keys/api-keys-list.tsx @@ -455,7 +455,7 @@ export function ApiKeysList({ - + {Intl.DateTimeFormat(undefined, { month: "short", day: "numeric", diff --git a/apps/ui/src/components/onboarding/api-key-step.tsx b/apps/ui/src/components/onboarding/api-key-step.tsx index edfa8b819..2ef7fc3d3 100644 --- a/apps/ui/src/components/onboarding/api-key-step.tsx +++ b/apps/ui/src/components/onboarding/api-key-step.tsx @@ -249,7 +249,7 @@ export function ApiKeyStep() { - + {Intl.DateTimeFormat(undefined, { month: "short", day: "numeric", From 02d3452b963d7340911d41528464a433612361dd Mon Sep 17 00:00:00 2001 From: thebeyondr <19380973+thebeyondr@users.noreply.github.com> Date: Thu, 18 Sep 2025 14:13:32 -0500 Subject: [PATCH 14/15] feat(ui): improve API keys list component functionality - Added auto-switching between active and inactive tabs based on key availability. - Enhanced loading state handling and ensured active tab displays results after activation. --- .../src/components/api-keys/api-keys-list.tsx | 52 +++++++++++++------ 1 file changed, 37 insertions(+), 15 deletions(-) diff --git a/apps/ui/src/components/api-keys/api-keys-list.tsx b/apps/ui/src/components/api-keys/api-keys-list.tsx index 79f2b8b0d..d4ca11d55 100644 --- a/apps/ui/src/components/api-keys/api-keys-list.tsx +++ b/apps/ui/src/components/api-keys/api-keys-list.tsx @@ -7,7 +7,7 @@ import { Shield, Zap, } from "lucide-react"; -import { useState } from "react"; +import { useEffect, useState } from "react"; import { StatusBadge } from "@/components/ui/status-badge"; import { @@ -118,20 +118,6 @@ export function ApiKeysList({ "/keys/api/limit/{id}", ); - // Show message if no project is selected - if (!selectedProject) { - return ( -
-
- -
-

- Please select a project to view API keys. -

-
- ); - } - const allKeys = data?.apiKeys.filter((key) => key.status !== "deleted") || []; const activeKeys = allKeys.filter((key) => key.status === "active"); const inactiveKeys = allKeys.filter((key) => key.status === "inactive"); @@ -148,6 +134,39 @@ export function ApiKeysList({ } })(); + // Auto-switch to a tab with content if current tab becomes empty + useEffect(() => { + if (filteredKeys.length === 0 && allKeys.length > 0) { + if (statusFilter === "active" && inactiveKeys.length > 0) { + setStatusFilter("inactive"); + } else if (statusFilter === "inactive" && activeKeys.length > 0) { + setStatusFilter("active"); + } else if (statusFilter !== "all") { + setStatusFilter("all"); + } + } + }, [ + filteredKeys.length, + allKeys.length, + activeKeys.length, + inactiveKeys.length, + statusFilter, + ]); + + // Show message if no project is selected + if (!selectedProject) { + return ( +
+
+ +
+

+ Please select a project to view API keys. +

+
+ ); + } + // Handle loading state if (isLoading) { return ( @@ -293,6 +312,9 @@ export function ApiKeysList({ ); }); + // Switch to active tab to show the results + setStatusFilter("active"); + toast({ title: "Activating Keys", description: `${inactiveKeys.length} key${inactiveKeys.length !== 1 ? "s" : ""} are being activated.`, From d07784290a645be09fd3ade445e9e7ebdb7e87ac Mon Sep 17 00:00:00 2001 From: thebeyondr <19380973+thebeyondr@users.noreply.github.com> Date: Thu, 18 Sep 2025 14:28:04 -0500 Subject: [PATCH 15/15] feat(ui): enhance Stepper component with responsive design - Implemented a mobile layout featuring a compact circular progress indicator. - Improved accessibility and visual clarity with updated button and text styles. - Ensured step titles and optional indicators are displayed correctly across devices. --- apps/ui/src/lib/components/stepper.tsx | 126 +++++++++++++++++-------- 1 file changed, 87 insertions(+), 39 deletions(-) diff --git a/apps/ui/src/lib/components/stepper.tsx b/apps/ui/src/lib/components/stepper.tsx index e2318ea05..3c86b108d 100644 --- a/apps/ui/src/lib/components/stepper.tsx +++ b/apps/ui/src/lib/components/stepper.tsx @@ -35,50 +35,98 @@ export function Stepper({ return (
- -
- {steps.map((step, index) => { - const isActive = activeStep === index; - const isCompleted = activeStep > index; - const isClickable = isCompleted || index === activeStep + 1; + {/* Desktop stepper - show full horizontal layout */} +
+ +
+ {steps.map((step, index) => { + const isActive = activeStep === index; + const isCompleted = activeStep > index; + const isClickable = isCompleted || index === activeStep + 1; - return ( -
- - {step.title} - {step.optional && ( - - (Optional) + + + {step.title} - )} -
- ); - })} + {step.optional && ( + + (Optional) + + )} +
+ ); + })} +
+
+ + {/* Mobile stepper - compact circular progress with current/next step */} +
+
+ + + + + + {activeStep + 1} of {steps.length} + +
+
+

+ {currentStep?.title} + {currentStep?.optional && ( + (Optional) + )} +

+ {activeStep < steps.length - 1 && ( +

+ Next: {steps[activeStep + 1]?.title} +

+ )} +