From 3057f04a174935aed505dd3644a6a9cdc25859d7 Mon Sep 17 00:00:00 2001 From: ying2 Date: Tue, 12 May 2026 16:23:04 +0800 Subject: [PATCH 1/4] fix(wallet): read topup gateway flags from topupInfo instead of status (#4599) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit fix(wallet): read topup gateway flags from topupInfo instead of status Fixes #4632 — subscription purchase modal wrongly said online payment not enabled. From 7fe896d2f851c0e36cfcc2c80ae1a54eed38d6cc Mon Sep 17 00:00:00 2001 From: Micah-Zheng <102610064+Micah-Zheng@users.noreply.github.com> Date: Tue, 12 May 2026 16:23:14 +0800 Subject: [PATCH 2/4] fix: use getUserGroups for ratio display to respect GroupGroupRatio (#4772) fix(web/default): use getUserGroups for ratio display to respect GroupGroupRatio Unifies admin/user ratio display so API key list matches the actual billing ratio. --- .../keys/components/api-keys-columns.tsx | 32 ++----------------- 1 file changed, 2 insertions(+), 30 deletions(-) diff --git a/web/default/src/features/keys/components/api-keys-columns.tsx b/web/default/src/features/keys/components/api-keys-columns.tsx index cddbea22..2c6544c1 100644 --- a/web/default/src/features/keys/components/api-keys-columns.tsx +++ b/web/default/src/features/keys/components/api-keys-columns.tsx @@ -16,11 +16,9 @@ along with this program. If not, see . For commercial licensing, please contact support@quantumnous.com */ -import { useMemo } from 'react' import { useQuery } from '@tanstack/react-query' import { type ColumnDef } from '@tanstack/react-table' import { useTranslation } from 'react-i18next' -import { useAuthStore } from '@/stores/auth-store' import { getUserGroups } from '@/lib/api' import { formatQuota, formatTimestampToDate } from '@/lib/format' import { cn } from '@/lib/utils' @@ -34,7 +32,6 @@ import { import { DataTableColumnHeader } from '@/components/data-table' import { GroupBadge } from '@/components/group-badge' import { StatusBadge } from '@/components/status-badge' -import { getSystemOptions } from '@/features/system-settings/api' import { API_KEY_STATUSES } from '../constants' import { type ApiKey } from '../types' import { @@ -51,31 +48,9 @@ function getQuotaProgressColor(percentage: number): string { } function useGroupRatios(): Record { - const isAdmin = useAuthStore((s) => - Boolean(s.auth.user?.role && s.auth.user.role >= 10) - ) - - const { data: adminData } = useQuery({ - queryKey: ['system-options-group-ratio'], - queryFn: getSystemOptions, - enabled: isAdmin, - staleTime: 5 * 60 * 1000, - select: (res) => { - if (!res.success || !res.data) return {} - const option = res.data.find((o) => o.key === 'GroupRatio') - if (!option?.value) return {} - try { - return JSON.parse(option.value) as Record - } catch { - return {} - } - }, - }) - - const { data: userGroupsData } = useQuery({ + const { data } = useQuery({ queryKey: ['user-self-groups'], queryFn: getUserGroups, - enabled: !isAdmin, staleTime: 5 * 60 * 1000, select: (res) => { if (!res.success || !res.data) return {} @@ -89,10 +64,7 @@ function useGroupRatios(): Record { }, }) - return useMemo( - () => (isAdmin ? adminData : userGroupsData) ?? {}, - [isAdmin, adminData, userGroupsData] - ) + return data ?? {} } export function useApiKeysColumns(): ColumnDef[] { From 2b89989f6251f560759a80a216bd665e111b59a3 Mon Sep 17 00:00:00 2001 From: Li Duoyang Date: Tue, 12 May 2026 16:23:24 +0800 Subject: [PATCH 3/4] fix(default): support DropdownMenuItem onSelect (#4787) fix(ui): add onSelect compat wrapper for DropdownMenuItem Bridges Base UI DropdownMenu with Radix-style onSelect so existing consumers work without migration. --- .dockerignore | 4 ++ .../src/components/ui/dropdown-menu-events.ts | 25 ++++++++++ .../src/components/ui/dropdown-menu.test.tsx | 50 +++++++++++++++++++ .../src/components/ui/dropdown-menu.tsx | 17 ++++++- 4 files changed, 95 insertions(+), 1 deletion(-) create mode 100644 web/default/src/components/ui/dropdown-menu-events.ts create mode 100644 web/default/src/components/ui/dropdown-menu.test.tsx diff --git a/.dockerignore b/.dockerignore index 2cf7cad4..7a719362 100644 --- a/.dockerignore +++ b/.dockerignore @@ -8,4 +8,8 @@ docs .eslintcache .gocache /web/node_modules +/web/default/node_modules +/web/default/dist +/web/classic/node_modules +/web/classic/dist !THIRD-PARTY-LICENSES.md diff --git a/web/default/src/components/ui/dropdown-menu-events.ts b/web/default/src/components/ui/dropdown-menu-events.ts new file mode 100644 index 00000000..0f49d3f8 --- /dev/null +++ b/web/default/src/components/ui/dropdown-menu-events.ts @@ -0,0 +1,25 @@ +import type * as React from 'react' + +export type DropdownMenuItemSelectEvent = React.MouseEvent & { + preventBaseUIHandler?: () => void +} + +export type DropdownMenuItemSelectHandler = ( + event: DropdownMenuItemSelectEvent +) => void + +export function handleDropdownMenuItemSelect( + event: DropdownMenuItemSelectEvent, + onClick?: React.MouseEventHandler, + onSelect?: DropdownMenuItemSelectHandler +) { + onClick?.(event) + + if (!event.defaultPrevented) { + onSelect?.(event) + } + + if (event.defaultPrevented) { + event.preventBaseUIHandler?.() + } +} diff --git a/web/default/src/components/ui/dropdown-menu.test.tsx b/web/default/src/components/ui/dropdown-menu.test.tsx new file mode 100644 index 00000000..33917663 --- /dev/null +++ b/web/default/src/components/ui/dropdown-menu.test.tsx @@ -0,0 +1,50 @@ +import assert from 'node:assert/strict' +import { describe, test } from 'node:test' +import { handleDropdownMenuItemSelect } from './dropdown-menu-events' + +function createMenuEvent() { + let defaultPrevented = false + let baseUIHandlerPrevented = false + + return { + get defaultPrevented() { + return defaultPrevented + }, + preventDefault() { + defaultPrevented = true + }, + preventBaseUIHandler() { + baseUIHandlerPrevented = true + }, + get baseUIHandlerPrevented() { + return baseUIHandlerPrevented + }, + } as unknown as Parameters[0] & { + baseUIHandlerPrevented: boolean + } +} + +describe('DropdownMenuItem onSelect compatibility', () => { + test('calls the Radix-style onSelect handler on item click', () => { + const event = createMenuEvent() + let selected = false + + handleDropdownMenuItemSelect(event, undefined, () => { + selected = true + }) + + assert.equal(selected, true) + assert.equal(event.baseUIHandlerPrevented, false) + }) + + test('keeps the Base UI menu open when onSelect prevents default', () => { + const event = createMenuEvent() + + handleDropdownMenuItemSelect(event, undefined, (selectEvent) => { + selectEvent.preventDefault() + }) + + assert.equal(event.defaultPrevented, true) + assert.equal(event.baseUIHandlerPrevented, true) + }) +}) diff --git a/web/default/src/components/ui/dropdown-menu.tsx b/web/default/src/components/ui/dropdown-menu.tsx index e0da050c..f45ffb83 100644 --- a/web/default/src/components/ui/dropdown-menu.tsx +++ b/web/default/src/components/ui/dropdown-menu.tsx @@ -21,6 +21,10 @@ import { Menu as MenuPrimitive } from '@base-ui/react/menu' import { ArrowRight01Icon, Tick02Icon } from '@hugeicons/core-free-icons' import { HugeiconsIcon } from '@hugeicons/react' import { cn } from '@/lib/utils' +import { + handleDropdownMenuItemSelect, + type DropdownMenuItemSelectHandler, +} from './dropdown-menu-events' function DropdownMenu({ ...props }: MenuPrimitive.Root.Props) { return @@ -96,11 +100,21 @@ function DropdownMenuItem({ className, inset, variant = 'default', + onClick, + onSelect, ...props -}: MenuPrimitive.Item.Props & { +}: Omit & { inset?: boolean variant?: 'default' | 'destructive' + onSelect?: DropdownMenuItemSelectHandler }) { + const handleClick = React.useCallback( + (event: React.MouseEvent) => { + handleDropdownMenuItemSelect(event, onClick, onSelect) + }, + [onClick, onSelect] + ) + return ( ) From fde2cac9d311f57e92ed18aa541bbb19cdab3e37 Mon Sep 17 00:00:00 2001 From: skynono Date: Tue, 12 May 2026 16:23:33 +0800 Subject: [PATCH 4/4] fix(web/default): guard playground messages against legacy classic shape (#4650) fix(playground): handle legacy localStorage message shape Sanitizes old-format saved messages to prevent 500 on playground load. --- web/default/src/features/playground/lib/storage.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/web/default/src/features/playground/lib/storage.ts b/web/default/src/features/playground/lib/storage.ts index 0b225ab4..1ad23571 100644 --- a/web/default/src/features/playground/lib/storage.ts +++ b/web/default/src/features/playground/lib/storage.ts @@ -88,8 +88,12 @@ export function loadMessages(): Message[] | null { try { const saved = localStorage.getItem(STORAGE_KEYS.MESSAGES) if (saved) { - const parsed: Message[] = JSON.parse(saved) - const sanitized = sanitizeMessagesOnLoad(parsed) + const parsed: unknown = JSON.parse(saved) + if (!Array.isArray(parsed)) { + localStorage.removeItem(STORAGE_KEYS.MESSAGES) + return null + } + const sanitized = sanitizeMessagesOnLoad(parsed as Message[]) // Persist sanitized result to avoid re-sanitizing on subsequent loads if (sanitized !== parsed) { saveMessages(sanitized)