From 789a26396723e3cbfdb6df71972034048535c898 Mon Sep 17 00:00:00 2001 From: diegosouzapw Date: Sat, 16 May 2026 15:53:37 -0300 Subject: [PATCH] feat(dashboard): provider summary card, free test btn, sidebar order, i18n fix - sidebarVisibility: move endpoints before api-manager - en.json: add freeTierProviders/Label/Desc + providerSummaryAll to providers namespace - page.tsx: apply showConfiguredOnly filter to free tier section (was hardcoded false) - page.tsx: replace search bar with summary Card containing: search (25%) + configured-only toggle + test-all button / dot legend / stats row stats show Total / Free / OAuth / API Key configured/total counts - page.tsx: add batch test button to Free Tier Providers section header - page.tsx: remove duplicate configured-only toggle from OAuth section header - page.tsx: import Card component --- .../(dashboard)/dashboard/providers/page.tsx | 188 ++++++++++++------ src/i18n/messages/en.json | 6 +- src/shared/constants/sidebarVisibility.ts | 2 +- 3 files changed, 137 insertions(+), 59 deletions(-) diff --git a/src/app/(dashboard)/dashboard/providers/page.tsx b/src/app/(dashboard)/dashboard/providers/page.tsx index 6185f4412a..ad77339f55 100644 --- a/src/app/(dashboard)/dashboard/providers/page.tsx +++ b/src/app/(dashboard)/dashboard/providers/page.tsx @@ -1,7 +1,7 @@ "use client"; import { useState, useEffect, useCallback } from "react"; -import { CardSkeleton, Badge, Button, Input, Toggle } from "@/shared/components"; +import { Card, CardSkeleton, Badge, Button, Input, Toggle } from "@/shared/components"; import { FREE_PROVIDERS, OAUTH_PROVIDERS, @@ -566,10 +566,23 @@ export default function ProvidersPage() { ); const freeSectionEntries = filterConfiguredProviderEntries( freeSectionEntriesAll, - false, + showConfiguredOnly, searchQuery ); + const oauthOnlyEntriesAll = oauthProviderEntriesAll.filter((e) => e.toggleAuthType === "oauth"); + const summaryStats = { + all: { + configured: + oauthProviderEntriesAll.filter((e) => Number(e.stats?.total || 0) > 0).length + + apiKeyProviderEntriesAll.filter((e) => Number(e.stats?.total || 0) > 0).length, + total: oauthProviderEntriesAll.length + apiKeyProviderEntriesAll.length, + }, + free: countConfigured(freeSectionEntriesAll), + oauth: countConfigured(oauthOnlyEntriesAll), + apikey: countConfigured(apiKeyProviderEntriesAll), + }; + if (loading) { return (
@@ -581,51 +594,102 @@ export default function ProvidersPage() { return (
- {/* Search Bar + Dot Legend */} -
-
- - search - - setSearchQuery(e.target.value)} - placeholder={t("searchProviders")} - aria-label={t("searchProviders")} - className="pl-10 pr-10" - /> - {searchQuery && ( + {/* Provider Summary Card */} + +
+ {/* Row 1: Search + Controls */} +
+
+ + search + + setSearchQuery(e.target.value)} + placeholder={t("searchProviders")} + aria-label={t("searchProviders")} + className="pl-10 pr-10" + /> + {searchQuery && ( + + )} +
+
+ - )} +
+ + {/* Row 2: Legend */} +
+ {( + [ + ["bg-green-500", tc("free")], + ["bg-blue-500", t("oauthLabel")], + ["bg-amber-500", t("apiKeyLabel")], + ["bg-orange-500", t("compatibleLabel")], + ["bg-purple-500", t("webCookieProviders")], + ["bg-teal-500", t("searchProvidersHeading")], + ["bg-rose-500", t("audioProvidersHeading")], + ["bg-emerald-500", t("localProviders")], + ["bg-indigo-500", t("upstreamProxyProviders")], + ["bg-violet-500", t("cloudAgentProviders")], + ] as [string, string][] + ).map(([color, label]) => ( + + + {label} + + ))} +
+ + {/* Divider + Stats */} +
+ {( + [ + [null, t("providerSummaryAll"), summaryStats.all], + ["bg-green-500", tc("free"), summaryStats.free], + ["bg-blue-500", t("oauthLabel"), summaryStats.oauth], + ["bg-amber-500", t("apiKeyLabel"), summaryStats.apikey], + ] as [string | null, string, { configured: number; total: number }][] + ).map(([color, label, stat]) => ( + + {color && } + {label} + + {stat.configured} + /{stat.total} + + + ))} +
-
- {( - [ - ["bg-green-500", tc("free")], - ["bg-blue-500", t("oauthLabel")], - ["bg-amber-500", t("apiKeyLabel")], - ["bg-orange-500", t("compatibleLabel")], - ["bg-purple-500", t("webCookieProviders")], - ["bg-teal-500", t("searchProvidersHeading")], - ["bg-rose-500", t("audioProvidersHeading")], - ["bg-emerald-500", t("localProviders")], - ["bg-indigo-500", t("upstreamProxyProviders")], - ["bg-violet-500", t("cloudAgentProviders")], - ] as [string, string][] - ).map(([color, label]) => ( - - - {label} - - ))} -
-
+
{/* API Key Compatible Providers — dynamic (OpenAI/Anthropic compatible) */}
@@ -732,13 +796,30 @@ export default function ProvidersPage() { {/* Free Tier Providers */} {freeSectionEntries.length > 0 && (
-
-

- {t("freeTierProviders")} - - -

-

{t("freeTierProvidersDesc")}

+
+
+

+ {t("freeTierProviders")} + + +

+

{t("freeTierProvidersDesc")}

+
+
{freeSectionEntries.map( @@ -766,13 +847,6 @@ export default function ProvidersPage() {
-