From f9825448259b7d4931fda8e487f7f0772b89396a Mon Sep 17 00:00:00 2001 From: CaIon Date: Wed, 29 Apr 2026 11:40:05 +0800 Subject: [PATCH] feat(ui): refine default frontend layouts --- .../src/components/layout/lib/url-utils.ts | 8 +- web/default/src/components/layout/types.ts | 2 + web/default/src/features/dashboard/index.tsx | 39 +- web/default/src/features/models/index.tsx | 80 ++- .../profile/components/passkey-card.tsx | 26 +- .../profile/components/profile-header.tsx | 130 +++-- .../components/profile-security-card.tsx | 41 +- .../components/profile-settings-card.tsx | 51 +- .../components/sidebar-modules-card.tsx | 41 +- .../components/tabs/account-bindings-tab.tsx | 14 +- .../components/tabs/notification-tab.tsx | 11 +- .../profile/components/two-fa-card.tsx | 29 +- web/default/src/features/profile/index.tsx | 24 +- web/default/src/features/usage-logs/index.tsx | 100 +++- .../components/affiliate-rewards-card.tsx | 62 ++- .../wallet/components/recharge-form-card.tsx | 76 +-- .../components/subscription-plans-card.tsx | 502 +++++++++--------- .../wallet/components/wallet-stats-card.tsx | 92 ++-- web/default/src/features/wallet/index.tsx | 92 ++-- web/default/src/hooks/use-sidebar-config.ts | 7 +- web/default/src/hooks/use-sidebar-data.ts | 26 +- web/default/src/i18n/locales/en.json | 1 + web/default/src/i18n/locales/fr.json | 1 + web/default/src/i18n/locales/ja.json | 1 + web/default/src/i18n/locales/ru.json | 1 + web/default/src/i18n/locales/vi.json | 1 + web/default/src/i18n/locales/zh.json | 1 + web/default/src/styles/theme.css | 54 +- 28 files changed, 926 insertions(+), 587 deletions(-) diff --git a/web/default/src/components/layout/lib/url-utils.ts b/web/default/src/components/layout/lib/url-utils.ts index b043d8f8..d2a9bb9c 100644 --- a/web/default/src/components/layout/lib/url-utils.ts +++ b/web/default/src/components/layout/lib/url-utils.ts @@ -40,11 +40,16 @@ export function checkIsActive( item: NavItem, mainNav = false ): boolean { + const hrefWithoutQuery = href.split('?')[0] + + if (item.activeUrls?.some((url) => urlToString(url) === hrefWithoutQuery)) { + return true + } + // For collapsible items (NavCollapsible), check sub-items first if ('items' in item && item.items) { const collapsibleItem = item as NavCollapsible const items = collapsibleItem.items - const hrefWithoutQuery = href.split('?')[0] // Check if any sub-item matches if ( @@ -76,7 +81,6 @@ export function checkIsActive( // Exact match if (href === itemUrl) return true - const hrefWithoutQuery = href.split('?')[0] const itemUrlWithoutQuery = itemUrl.split('?')[0] const itemUrlHasQuery = itemUrl.includes('?') diff --git a/web/default/src/components/layout/types.ts b/web/default/src/components/layout/types.ts index f5b3d676..fa76ffec 100644 --- a/web/default/src/components/layout/types.ts +++ b/web/default/src/components/layout/types.ts @@ -18,6 +18,8 @@ type BaseNavItem = { title: string badge?: string icon?: React.ElementType + activeUrls?: (LinkProps['to'] | (string & {}))[] + configUrls?: (LinkProps['to'] | (string & {}))[] } /** diff --git a/web/default/src/features/dashboard/index.tsx b/web/default/src/features/dashboard/index.tsx index 0c1dc6ea..ee6ed59f 100644 --- a/web/default/src/features/dashboard/index.tsx +++ b/web/default/src/features/dashboard/index.tsx @@ -1,7 +1,10 @@ -import { useState, useCallback, lazy, Suspense } from 'react' -import { getRouteApi } from '@tanstack/react-router' +import { useState, useCallback, useMemo, lazy, Suspense } from 'react' +import { getRouteApi, useNavigate } from '@tanstack/react-router' import { useTranslation } from 'react-i18next' +import { useAuthStore } from '@/stores/auth-store' +import { ROLE } from '@/lib/roles' import { Skeleton } from '@/components/ui/skeleton' +import { Tabs, TabsList, TabsTrigger } from '@/components/ui/tabs' import { SectionPageLayout } from '@/components/layout' import { CardStaggerContainer, @@ -18,6 +21,7 @@ import { DEFAULT_TIME_GRANULARITY } from './constants' import { type DashboardSectionId, DASHBOARD_DEFAULT_SECTION, + DASHBOARD_SECTION_IDS, } from './section-registry' import { type DashboardFilters, type QuotaDataItem } from './types' @@ -97,7 +101,9 @@ const SECTION_META: Record< export function Dashboard() { const { t } = useTranslation() + const navigate = useNavigate() const params = route.useParams() + const userRole = useAuthStore((state) => state.auth.user?.role) const activeSection = (params.section ?? DASHBOARD_DEFAULT_SECTION) as DashboardSectionId @@ -122,6 +128,24 @@ export function Dashboard() { ) const meta = SECTION_META[activeSection] ?? SECTION_META.overview + const isAdmin = Boolean(userRole && userRole >= ROLE.ADMIN) + const visibleSections = useMemo( + () => + DASHBOARD_SECTION_IDS.filter( + (section) => section !== 'overview' && (section !== 'users' || isAdmin) + ), + [isAdmin] + ) + const handleSectionChange = useCallback( + (section: string) => { + void navigate({ + to: '/dashboard/$section', + params: { section: section as DashboardSectionId }, + }) + }, + [navigate] + ) + const showSectionTabs = activeSection !== 'overview' && visibleSections.length > 1 return ( @@ -139,6 +163,17 @@ export function Dashboard() { )}
+ {showSectionTabs && ( + + + {visibleSections.map((section) => ( + + {t(SECTION_META[section].titleKey)} + + ))} + + + )} {activeSection === 'overview' && ( <> diff --git a/web/default/src/features/models/index.tsx b/web/default/src/features/models/index.tsx index cd1ece34..df8fb005 100644 --- a/web/default/src/features/models/index.tsx +++ b/web/default/src/features/models/index.tsx @@ -1,9 +1,10 @@ -import { useEffect, useState } from 'react' +import { useCallback, useEffect, useState } from 'react' import { useQueryClient } from '@tanstack/react-query' -import { getRouteApi } from '@tanstack/react-router' +import { getRouteApi, useNavigate } from '@tanstack/react-router' import { Plus } from 'lucide-react' import { useTranslation } from 'react-i18next' import { Button } from '@/components/ui/button' +import { Tabs, TabsList, TabsTrigger } from '@/components/ui/tabs' import { SectionPageLayout } from '@/components/layout' import { listDeployments } from './api' import { DeploymentAccessGuard } from './components/deployment-access-guard' @@ -18,12 +19,28 @@ import { deploymentsQueryKeys } from './lib' import { type ModelsSectionId, MODELS_DEFAULT_SECTION, + MODELS_SECTION_IDS, } from './section-registry' const route = getRouteApi('/_authenticated/models/$section') +const SECTION_META: Record< + ModelsSectionId, + { titleKey: string; descriptionKey: string } +> = { + metadata: { + titleKey: 'Metadata', + descriptionKey: 'Manage model metadata and configuration', + }, + deployments: { + titleKey: 'Deployments', + descriptionKey: 'Manage model deployments', + }, +} + function ModelsContent() { const { t } = useTranslation() + const navigate = useNavigate() const queryClient = useQueryClient() const { tabCategory, setTabCategory } = useModels() const params = route.useParams() @@ -75,16 +92,26 @@ function ModelsContent() { } }, [activeSection, isIoNetEnabled, loadingPhase, queryClient]) + const handleSectionChange = useCallback( + (section: string) => { + void navigate({ + to: '/models/$section', + params: { section: section as ModelsSectionId }, + }) + }, + [navigate] + ) + + const meta = SECTION_META[activeSection] ?? SECTION_META.metadata + return ( <> - {activeSection === 'metadata' ? t('Metadata') : t('Deployments')} + {t(meta.titleKey)} - {activeSection === 'metadata' - ? t('Manage model metadata and configuration') - : t('Manage model deployments')} + {t(meta.descriptionKey)} {activeSection === 'metadata' ? ( @@ -97,21 +124,32 @@ function ModelsContent() { )} - {activeSection === 'metadata' ? ( - - ) : ( - - - - )} +
+ + + {MODELS_SECTION_IDS.map((section) => ( + + {t(SECTION_META[section].titleKey)} + + ))} + + + {activeSection === 'metadata' ? ( + + ) : ( + + + + )} +
diff --git a/web/default/src/features/profile/components/passkey-card.tsx b/web/default/src/features/profile/components/passkey-card.tsx index 50521654..f43cf54a 100644 --- a/web/default/src/features/profile/components/passkey-card.tsx +++ b/web/default/src/features/profile/components/passkey-card.tsx @@ -15,7 +15,13 @@ import { AlertDialogTrigger, } from '@/components/ui/alert-dialog' import { Button } from '@/components/ui/button' -import { Card, CardContent, CardHeader } from '@/components/ui/card' +import { + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle, +} from '@/components/ui/card' import { Skeleton } from '@/components/ui/skeleton' import { StatusBadge } from '@/components/status-badge' import { usePasskeyManagement } from '@/features/auth/passkey' @@ -163,7 +169,7 @@ export function PasskeyCard({ loading: pageLoading }: PasskeyCardProps) { if (pageLoading || loading) { return ( - + @@ -185,18 +191,18 @@ export function PasskeyCard({ loading: pageLoading }: PasskeyCardProps) { return ( <> - + -

+ {t('Passkey Login')} -

-

+ + {t('Use Passkey to sign in without entering your password.')} -

+
-
+
@@ -239,7 +245,7 @@ export function PasskeyCard({ loading: pageLoading }: PasskeyCardProps) { {!enabled ? ( diff --git a/web/default/src/features/profile/components/tabs/account-bindings-tab.tsx b/web/default/src/features/profile/components/tabs/account-bindings-tab.tsx index ef1ece23..fd42b4c6 100644 --- a/web/default/src/features/profile/components/tabs/account-bindings-tab.tsx +++ b/web/default/src/features/profile/components/tabs/account-bindings-tab.tsx @@ -249,9 +249,9 @@ export function AccountBindingsTab({ {bindings.map((binding) => (
-
+
@@ -274,7 +274,7 @@ export function AccountBindingsTab({ )} @@ -105,7 +114,7 @@ export function TwoFACard({ loading: pageLoading }: TwoFACardProps) { {/* Actions Section - Only show when enabled */} {status.enabled && ( -
+
)}
- + {/* Online Topup Section */} {hasAnyTopup ? (
@@ -202,7 +213,7 @@ export function RechargeFormCard({ -
+
{presetAmounts.map((preset, index) => { const discount = preset.discount || @@ -224,7 +235,7 @@ export function RechargeFormCard({ key={index} variant='outline' className={cn( - 'hover:border-foreground h-auto rounded-lg p-4 text-left whitespace-normal', + 'hover:border-foreground flex h-auto flex-col items-start rounded-lg p-4 text-left whitespace-normal', selectedPreset === preset.value ? 'border-foreground bg-foreground/5' : 'border-muted' @@ -264,7 +275,7 @@ export function RechargeFormCard({ > {t('Custom Amount')} -
+
handleAmountChange(e.target.value)} min={minTopup} placeholder={`Minimum ${minTopup}`} - className='pr-32 text-lg' + className='text-lg' /> -
+
{t('Amount to pay:')} @@ -294,7 +305,7 @@ export function RechargeFormCard({ {t('Payment Method')} {hasStandardPaymentMethods ? ( -
+
{topupInfo?.pay_methods?.map((method) => { const minTopup = method.min_topup || 0 const disabled = minTopup > topupAmount @@ -305,7 +316,7 @@ export function RechargeFormCard({ variant='outline' onClick={() => onPaymentMethodSelect(method)} disabled={disabled || !!paymentLoading} - className='gap-2 rounded-lg' + className='justify-start gap-2 rounded-lg' > {paymentLoading === method.type ? ( @@ -355,7 +366,7 @@ export function RechargeFormCard({ -
+
{waffoPayMethods?.map((method, index) => { const loadingKey = `waffo-${index}` const waffoMin = waffoMinTopup || 0 @@ -367,7 +378,7 @@ export function RechargeFormCard({ variant='outline' onClick={() => onWaffoMethodSelect(method, index)} disabled={belowMin || !!paymentLoading} - className='gap-2 rounded-lg' + className='justify-start gap-2 rounded-lg' > {paymentLoading === loadingKey ? ( @@ -434,7 +445,7 @@ export function RechargeFormCard({ )} {/* Redemption Code Section */} -
+
-
+
- diff --git a/web/default/src/features/wallet/components/subscription-plans-card.tsx b/web/default/src/features/wallet/components/subscription-plans-card.tsx index 56dd4c4c..fe59fe06 100644 --- a/web/default/src/features/wallet/components/subscription-plans-card.tsx +++ b/web/default/src/features/wallet/components/subscription-plans-card.tsx @@ -6,7 +6,13 @@ import { formatQuota } from '@/lib/format' import { cn } from '@/lib/utils' import { useStatus } from '@/hooks/use-status' import { Button } from '@/components/ui/button' -import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card' +import { + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle, +} from '@/components/ui/card' import { Progress } from '@/components/ui/progress' import { Select, @@ -185,11 +191,11 @@ export function SubscriptionPlansCard(props: SubscriptionPlansCardProps) { if (loading) { return ( - - + + - +
{Array.from({ length: 3 }).map((_, i) => ( @@ -207,237 +213,242 @@ export function SubscriptionPlansCard(props: SubscriptionPlansCardProps) { return ( <> - - - - - {t('Subscription Plans')} - + + +
+
+ +
+
+ + {t('Subscription Plans')} + + + {t('Purchase a plan to enjoy model benefits')} + +
+
- + {/* My subscriptions & billing preference */} - - -
-
- - {t('My Subscriptions')} - - -
- {hasAny && ( - <> - -
- {allSubscriptions.map((sub) => { - const subscription = sub.subscription - const totalAmount = Number( - subscription?.amount_total || 0 - ) - const usedAmount = Number(subscription?.amount_used || 0) - const remainAmount = - totalAmount > 0 - ? Math.max(0, totalAmount - usedAmount) - : 0 - const planTitle = - planTitleMap.get(subscription?.plan_id) || '' - const remainDays = getRemainingDays(sub) - const usagePercent = getUsagePercent(sub) - const now = Date.now() / 1000 - const isExpired = (subscription?.end_time || 0) < now - const isCancelled = subscription?.status === 'cancelled' - const isActive = - subscription?.status === 'active' && !isExpired + {disablePref && isSubPref && ( +

+ {t( + 'Preference saved as {{pref}}, but no active subscription. Wallet will be used automatically.', + { + pref: + billingPreference === 'subscription_only' + ? t('Subscription Only') + : t('Subscription First'), + } + )} +

+ )} - return ( -
-
-
- - {planTitle - ? `${planTitle} · ${t('Subscription')} #${subscription?.id}` - : `${t('Subscription')} #${subscription?.id}`} - - {isActive ? ( - - ) : isCancelled ? ( - - ) : ( - - )} -
- {isActive && ( - - {t('{{count}} days remaining', { - count: remainDays, - })} - - )} -
-
- {isActive - ? t('Until') - : isCancelled - ? t('Cancelled at') - : t('Expired at')}{' '} - {new Date( - (subscription?.end_time || 0) * 1000 - ).toLocaleString()} -
- {isActive && - (subscription?.next_reset_time ?? 0) > 0 && ( -
- {t('Next reset')}:{' '} - {new Date( - subscription!.next_reset_time! * 1000 - ).toLocaleString()} -
- )} -
- {t('Total Quota')}:{' '} - {totalAmount > 0 ? ( - - - - {formatQuota(usedAmount)}/ - {formatQuota(totalAmount)} ·{' '} - {t('Remaining')} {formatQuota(remainAmount)} - - - - {t('Raw Quota')}: {usedAmount}/{totalAmount} ·{' '} - {t('Remaining')} {remainAmount} - - + {hasAny && ( + <> + +
+ {allSubscriptions.map((sub) => { + const subscription = sub.subscription + const totalAmount = Number(subscription?.amount_total || 0) + const usedAmount = Number(subscription?.amount_used || 0) + const remainAmount = + totalAmount > 0 + ? Math.max(0, totalAmount - usedAmount) + : 0 + const planTitle = + planTitleMap.get(subscription?.plan_id) || '' + const remainDays = getRemainingDays(sub) + const usagePercent = getUsagePercent(sub) + const now = Date.now() / 1000 + const isExpired = (subscription?.end_time || 0) < now + const isCancelled = subscription?.status === 'cancelled' + const isActive = + subscription?.status === 'active' && !isExpired + + return ( +
+
+
+ + {planTitle + ? `${planTitle} · ${t('Subscription')} #${subscription?.id}` + : `${t('Subscription')} #${subscription?.id}`} + + {isActive ? ( + + ) : isCancelled ? ( + ) : ( - t('Unlimited') - )} - {totalAmount > 0 && ( - - {t('Used')} {usagePercent}% - + )}
- {totalAmount > 0 && isActive && ( - + {isActive && ( + + {t('{{count}} days remaining', { + count: remainDays, + })} + )}
- ) - })} -
- - )} +
+ {isActive + ? t('Until') + : isCancelled + ? t('Cancelled at') + : t('Expired at')}{' '} + {new Date( + (subscription?.end_time || 0) * 1000 + ).toLocaleString()} +
+ {isActive && + (subscription?.next_reset_time ?? 0) > 0 && ( +
+ {t('Next reset')}:{' '} + {new Date( + subscription!.next_reset_time! * 1000 + ).toLocaleString()} +
+ )} +
+ {t('Total Quota')}:{' '} + {totalAmount > 0 ? ( + + + + {formatQuota(usedAmount)}/ + {formatQuota(totalAmount)} · {t('Remaining')}{' '} + {formatQuota(remainAmount)} + + + + {t('Raw Quota')}: {usedAmount}/{totalAmount} ·{' '} + {t('Remaining')} {remainAmount} + + + ) : ( + t('Unlimited') + )} + {totalAmount > 0 && ( + + {t('Used')} {usagePercent}% + + )} +
+ {totalAmount > 0 && isActive && ( + + )} +
+ ) + })} +
+ + )} - {!hasAny && ( -

- {t('Purchase a plan to enjoy model benefits')} -

- )} - - + {!hasAny && ( +

+ {t('Purchase a plan to enjoy model benefits')} +

+ )} +
{/* Available plans grid */} {plans.length > 0 ? ( @@ -469,27 +480,32 @@ export function SubscriptionPlansCard(props: SubscriptionPlansCardProps) { return ( - {isPopular && ( -
- - - {t('Recommended')} - -
+ className={cn( + 'transition-shadow hover:shadow-md', + isPopular && 'border-primary/70 shadow-sm' )} - -
-

- {plan.title || t('Subscription Plans')} -

- {plan.subtitle && ( -

- {plan.subtitle} -

+ > + +
+
+

+ {plan.title || t('Subscription Plans')} +

+ {plan.subtitle && ( +

+ {plan.subtitle} +

+ )} +
+ {isPopular && ( + + + {t('Recommended')} + )}
diff --git a/web/default/src/features/wallet/components/wallet-stats-card.tsx b/web/default/src/features/wallet/components/wallet-stats-card.tsx index 67ff9a39..a859253c 100644 --- a/web/default/src/features/wallet/components/wallet-stats-card.tsx +++ b/web/default/src/features/wallet/components/wallet-stats-card.tsx @@ -1,5 +1,7 @@ +import { Activity, BarChart3, WalletCards } from 'lucide-react' import { useTranslation } from 'react-i18next' import { formatQuota } from '@/lib/format' +import { cn } from '@/lib/utils' import { Card, CardContent } from '@/components/ui/card' import { Skeleton } from '@/components/ui/skeleton' import type { UserWalletData } from '../types' @@ -13,13 +15,21 @@ export function WalletStatsCard(props: WalletStatsCardProps) { const { t } = useTranslation() if (props.loading) { return ( - - -
+ + +
{Array.from({ length: 3 }).map((_, i) => ( -
- - +
0 && 'border-t sm:border-t-0 sm:border-l' + )} + > +
+ + +
))}
@@ -28,39 +38,47 @@ export function WalletStatsCard(props: WalletStatsCardProps) { ) } + const stats = [ + { + label: t('Current Balance'), + value: formatQuota(props.user?.quota ?? 0), + icon: WalletCards, + }, + { + label: t('Total Usage'), + value: formatQuota(props.user?.used_quota ?? 0), + icon: BarChart3, + }, + { + label: t('API Requests'), + value: (props.user?.request_count ?? 0).toLocaleString(), + icon: Activity, + }, + ] + return ( - - -
- {/* Current Balance */} -
-
- {t('Current Balance')} + + +
+ {stats.map((item, index) => ( +
0 && 'border-t sm:border-t-0 sm:border-l' + )} + > +
+
+ + {item.label} +
+
+ {item.value} +
+
-
- {formatQuota(props.user?.quota ?? 0)} -
-
- - {/* Total Usage */} -
-
- {t('Total Usage')} -
-
- {formatQuota(props.user?.used_quota ?? 0)} -
-
- - {/* Request Count */} -
-
- {t('API Requests')} -
-
- {(props.user?.request_count ?? 0).toLocaleString()} -
-
+ ))}
diff --git a/web/default/src/features/wallet/index.tsx b/web/default/src/features/wallet/index.tsx index ad09d012..b467e85c 100644 --- a/web/default/src/features/wallet/index.tsx +++ b/web/default/src/features/wallet/index.tsx @@ -239,54 +239,56 @@ export function Wallet(props: WalletProps) { {t('Manage your balance and payment methods')} -
- {/* Left Column - Stats & Recharge */} -
- - setBillingDialogOpen(true)} - creemProducts={topupInfo?.creem_products} - enableCreemTopup={topupInfo?.enable_creem_topup} - onCreemProductSelect={handleCreemProductSelect} - enableWaffoTopup={topupInfo?.enable_waffo_topup} - waffoPayMethods={topupInfo?.waffo_pay_methods} - waffoMinTopup={topupInfo?.waffo_min_topup} - onWaffoMethodSelect={handleWaffoMethodSelect} - enableWaffoPancakeTopup={topupInfo?.enable_waffo_pancake_topup} - /> -
+
+ - {/* Right Column - Affiliate & Subscriptions */} -
- setTransferDialogOpen(true)} - loading={affiliateLoading} - /> + + +
+
+ setBillingDialogOpen(true)} + creemProducts={topupInfo?.creem_products} + enableCreemTopup={topupInfo?.enable_creem_topup} + onCreemProductSelect={handleCreemProductSelect} + enableWaffoTopup={topupInfo?.enable_waffo_topup} + waffoPayMethods={topupInfo?.waffo_pay_methods} + waffoMinTopup={topupInfo?.waffo_min_topup} + onWaffoMethodSelect={handleWaffoMethodSelect} + enableWaffoPancakeTopup={ + topupInfo?.enable_waffo_pancake_topup + } + /> +
+ +
+ setTransferDialogOpen(true)} + loading={affiliateLoading} + /> +
- - {/* Subscription Plans */} - diff --git a/web/default/src/hooks/use-sidebar-config.ts b/web/default/src/hooks/use-sidebar-config.ts index 7b5f90a9..05b8a11f 100644 --- a/web/default/src/hooks/use-sidebar-config.ts +++ b/web/default/src/hooks/use-sidebar-config.ts @@ -55,7 +55,9 @@ const URL_TO_CONFIG_MAP: Record = { '/dashboard': { section: 'console', module: 'detail' }, '/dashboard/overview': { section: 'console', module: 'detail' }, '/dashboard/models': { section: 'console', module: 'detail' }, + '/dashboard/users': { section: 'console', module: 'detail' }, '/keys': { section: 'console', module: 'token' }, + '/usage-logs': { section: 'console', module: 'log' }, '/usage-logs/common': { section: 'console', module: 'log' }, '/usage-logs/drawing': { section: 'console', module: 'midjourney' }, '/usage-logs/task': { section: 'console', module: 'task' }, @@ -173,7 +175,10 @@ function isNavItemVisible( // Handle direct link type if ('url' in item && item.url) { - return isModuleEnabled(item.url as string, adminConfig, userConfig) + const configUrls = item.configUrls ?? [item.url] + return configUrls.some((url) => + isModuleEnabled(url as string, adminConfig, userConfig) + ) } // Handle collapsible type (with sub-items) diff --git a/web/default/src/hooks/use-sidebar-data.ts b/web/default/src/hooks/use-sidebar-data.ts index cac64e00..04249535 100644 --- a/web/default/src/hooks/use-sidebar-data.ts +++ b/web/default/src/hooks/use-sidebar-data.ts @@ -1,5 +1,6 @@ import { LayoutDashboard, + Activity, Key, FileText, Wallet, @@ -12,19 +13,14 @@ import { FlaskConical, MessageSquare, CreditCard, + ListTodo, } from 'lucide-react' import { useTranslation } from 'react-i18next' -import { useAuthStore } from '@/stores/auth-store' import { WORKSPACE_IDS } from '@/components/layout/lib/workspace-registry' import { type SidebarData } from '@/components/layout/types' -import { getDashboardSectionNavItems } from '@/features/dashboard/section-registry' -import { getModelsSectionNavItems } from '@/features/models/section-registry' -import { getUsageLogsSectionNavItems } from '@/features/usage-logs/section-registry' export function useSidebarData(): SidebarData { const { t } = useTranslation() - const user = useAuthStore((s) => s.auth.user) - const isAdmin = Boolean(user?.role && user.role >= 10) return { workspaces: [ @@ -56,10 +52,15 @@ export function useSidebarData(): SidebarData { id: 'general', title: t('General'), items: [ + { + title: t('Overview'), + url: '/dashboard/overview', + icon: Activity, + }, { title: t('Dashboard'), + url: '/dashboard/models', icon: LayoutDashboard, - items: getDashboardSectionNavItems(t, { isAdmin }), }, { title: t('API Keys'), @@ -68,8 +69,15 @@ export function useSidebarData(): SidebarData { }, { title: t('Usage Logs'), + url: '/usage-logs/common', icon: FileText, - items: getUsageLogsSectionNavItems(t), + }, + { + title: t('Task Logs'), + url: '/usage-logs/task', + activeUrls: ['/usage-logs/drawing'], + configUrls: ['/usage-logs/drawing', '/usage-logs/task'], + icon: ListTodo, }, { title: t('Wallet'), @@ -94,8 +102,8 @@ export function useSidebarData(): SidebarData { }, { title: t('Models'), + url: '/models/metadata', icon: Box, - items: getModelsSectionNavItems(t), }, { title: t('Users'), diff --git a/web/default/src/i18n/locales/en.json b/web/default/src/i18n/locales/en.json index 9b002da9..8cfe1cf6 100644 --- a/web/default/src/i18n/locales/en.json +++ b/web/default/src/i18n/locales/en.json @@ -2301,6 +2301,7 @@ "Or continue with": "Or continue with", "Or enter this key manually:": "Or enter this key manually:", "Order completed successfully": "Order completed successfully", + "Order History": "Order History", "Order Payment Method": "Order Payment Method", "org-...": "org-...", "Original Model": "Original Model", diff --git a/web/default/src/i18n/locales/fr.json b/web/default/src/i18n/locales/fr.json index 7635d6ad..c5ed76e8 100644 --- a/web/default/src/i18n/locales/fr.json +++ b/web/default/src/i18n/locales/fr.json @@ -2301,6 +2301,7 @@ "Or continue with": "Ou continuer avec", "Or enter this key manually:": "Ou entrez cette clé manuellement :", "Order completed successfully": "Commande terminée avec succès", + "Order History": "Historique des commandes", "Order Payment Method": "Moyen de paiement (commande)", "org-...": "org-...", "Original Model": "Modèle Original", diff --git a/web/default/src/i18n/locales/ja.json b/web/default/src/i18n/locales/ja.json index 5707ea33..f4328c6b 100644 --- a/web/default/src/i18n/locales/ja.json +++ b/web/default/src/i18n/locales/ja.json @@ -2301,6 +2301,7 @@ "Or continue with": "または、以下で続行", "Or enter this key manually:": "または、このキーを手動で入力してください:", "Order completed successfully": "注文が正常に完了しました", + "Order History": "注文履歴", "Order Payment Method": "注文の支払い方法", "org-...": "org-...", "Original Model": "オリジナルモデル", diff --git a/web/default/src/i18n/locales/ru.json b/web/default/src/i18n/locales/ru.json index 5320093a..5d21bd40 100644 --- a/web/default/src/i18n/locales/ru.json +++ b/web/default/src/i18n/locales/ru.json @@ -2301,6 +2301,7 @@ "Or continue with": "Или продолжить с", "Or enter this key manually:": "Или введите этот ключ вручную:", "Order completed successfully": "Заказ успешно завершен", + "Order History": "История заказов", "Order Payment Method": "Способ оплаты (заказа)", "org-...": "орг-...", "Original Model": "Оригинальная модель", diff --git a/web/default/src/i18n/locales/vi.json b/web/default/src/i18n/locales/vi.json index 33007e5f..116a10c9 100644 --- a/web/default/src/i18n/locales/vi.json +++ b/web/default/src/i18n/locales/vi.json @@ -2301,6 +2301,7 @@ "Or continue with": "Hoặc tiếp tục với", "Or enter this key manually:": "Hoặc nhập khóa này thủ công:", "Order completed successfully": "Đơn hàng đã hoàn thành thành công", + "Order History": "Lịch sử đơn hàng", "Order Payment Method": "Phương thức thanh toán đơn hàng", "org-...": "org-...", "Original Model": "Nguyên mẫu", diff --git a/web/default/src/i18n/locales/zh.json b/web/default/src/i18n/locales/zh.json index 3b02f377..dc438320 100644 --- a/web/default/src/i18n/locales/zh.json +++ b/web/default/src/i18n/locales/zh.json @@ -2301,6 +2301,7 @@ "Or continue with": "或继续使用", "Or enter this key manually:": "或手动输入此密钥:", "Order completed successfully": "订单已成功完成", + "Order History": "订单历史", "Order Payment Method": "订单支付方式", "org-...": "org-...", "Original Model": "原始模型", diff --git a/web/default/src/styles/theme.css b/web/default/src/styles/theme.css index 11711203..98ee941b 100644 --- a/web/default/src/styles/theme.css +++ b/web/default/src/styles/theme.css @@ -38,39 +38,39 @@ } .dark { - --background: oklch(0.165 0.012 258); - --foreground: oklch(0.92 0.008 247.858); - --card: oklch(0.205 0.012 258); - --card-foreground: oklch(0.92 0.008 247.858); - --popover: oklch(0.225 0.014 258); - --popover-foreground: oklch(0.92 0.008 247.858); - --primary: oklch(0.87 0.018 255.508); - --primary-foreground: oklch(0.235 0.042 265.755); - --secondary: oklch(0.255 0.012 258); - --secondary-foreground: oklch(0.92 0.008 247.858); - --muted: oklch(0.245 0.012 258); - --muted-foreground: oklch(0.68 0.014 257.417); - --accent: oklch(0.265 0.014 258); - --accent-foreground: oklch(0.92 0.008 247.858); + --background: oklch(0.245 0.018 265); + --foreground: oklch(0.88 0.014 252); + --card: oklch(0.275 0.017 265); + --card-foreground: oklch(0.88 0.014 252); + --popover: oklch(0.3 0.018 265); + --popover-foreground: oklch(0.88 0.014 252); + --primary: oklch(0.68 0.12 236); + --primary-foreground: oklch(0.985 0.004 247.858); + --secondary: oklch(0.32 0.016 265); + --secondary-foreground: oklch(0.88 0.014 252); + --muted: oklch(0.305 0.016 265); + --muted-foreground: oklch(0.72 0.018 252); + --accent: oklch(0.34 0.024 255); + --accent-foreground: oklch(0.9 0.012 252); --destructive: oklch(0.704 0.191 22.216); - --border: oklch(0.31 0.014 258); - --input: oklch(0.34 0.014 258); - --ring: oklch(0.58 0.025 256.788); + --border: oklch(0.38 0.018 265); + --input: oklch(0.405 0.018 265); + --ring: oklch(0.62 0.09 236); --chart-1: oklch(0.488 0.243 264.376); --chart-2: oklch(0.696 0.17 162.48); --chart-3: oklch(0.769 0.188 70.08); --chart-4: oklch(0.627 0.265 303.9); --chart-5: oklch(0.645 0.246 16.439); - --sidebar: oklch(0.185 0.012 258); - --sidebar-foreground: oklch(0.92 0.008 247.858); - --sidebar-primary: oklch(0.75 0.14 233); - --sidebar-primary-foreground: oklch(0.29 0.06 243); - --sidebar-accent: oklch(0.255 0.012 258); - --sidebar-accent-foreground: oklch(0.92 0.008 247.858); - --sidebar-border: oklch(0.3 0.014 258); - --sidebar-ring: oklch(0.52 0.02 256.788); - --skeleton-base: oklch(0.245 0.01 258); - --skeleton-highlight: oklch(0.32 0.014 258); + --sidebar: oklch(0.255 0.017 265); + --sidebar-foreground: oklch(0.86 0.014 252); + --sidebar-primary: var(--primary); + --sidebar-primary-foreground: var(--primary-foreground); + --sidebar-accent: oklch(0.325 0.018 265); + --sidebar-accent-foreground: oklch(0.9 0.012 252); + --sidebar-border: oklch(0.37 0.018 265); + --sidebar-ring: var(--ring); + --skeleton-base: oklch(0.31 0.014 265); + --skeleton-highlight: oklch(0.39 0.018 265); } @theme inline {