feat: add channel sensitive info toggle
This commit is contained in:
+27
-15
@@ -18,7 +18,7 @@ For commercial licensing, please contact support@quantumnous.com
|
|||||||
*/
|
*/
|
||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
import { useState, type ReactNode } from 'react'
|
import { useState, type ReactNode } from 'react'
|
||||||
import { type Table } from '@tanstack/react-table'
|
import type { Table } from '@tanstack/react-table'
|
||||||
import { useDebounce } from '@/hooks'
|
import { useDebounce } from '@/hooks'
|
||||||
import { ChevronDown, Loader2, X as Cross2Icon } from 'lucide-react'
|
import { ChevronDown, Loader2, X as Cross2Icon } from 'lucide-react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
@@ -76,6 +76,11 @@ export type DataTableToolbarProps<TData> = {
|
|||||||
* search input and filter chips.
|
* search input and filter chips.
|
||||||
*/
|
*/
|
||||||
additionalSearch?: ReactNode
|
additionalSearch?: ReactNode
|
||||||
|
/**
|
||||||
|
* Extra controls displayed immediately after the filter chips, before the
|
||||||
|
* right-aligned action cluster.
|
||||||
|
*/
|
||||||
|
afterFilters?: ReactNode
|
||||||
/**
|
/**
|
||||||
* Whether non-table filters (e.g. `additionalSearch` or `expandable`
|
* Whether non-table filters (e.g. `additionalSearch` or `expandable`
|
||||||
* inputs) are currently active. Controls Reset button visibility
|
* inputs) are currently active. Controls Reset button visibility
|
||||||
@@ -282,20 +287,25 @@ export function DataTableToolbar<TData>(props: DataTableToolbarProps<TData>) {
|
|||||||
// Reset: outline text-only for form mode (always visible, disabled when
|
// Reset: outline text-only for form mode (always visible, disabled when
|
||||||
// nothing to reset); ghost text + X for filter-as-you-type mode (only
|
// nothing to reset); ghost text + X for filter-as-you-type mode (only
|
||||||
// visible when active filters exist).
|
// visible when active filters exist).
|
||||||
const resetButton = hasSearch ? (
|
let resetButton: ReactNode = null
|
||||||
<Button variant='outline' onClick={handleReset} disabled={!isFiltered}>
|
if (hasSearch) {
|
||||||
{t('Reset')}
|
resetButton = (
|
||||||
</Button>
|
<Button variant='outline' onClick={handleReset} disabled={!isFiltered}>
|
||||||
) : isFiltered ? (
|
{t('Reset')}
|
||||||
<Button
|
</Button>
|
||||||
variant='ghost'
|
)
|
||||||
onClick={handleReset}
|
} else if (isFiltered) {
|
||||||
className='text-muted-foreground hover:text-foreground gap-1 px-2'
|
resetButton = (
|
||||||
>
|
<Button
|
||||||
{t('Reset')}
|
variant='ghost'
|
||||||
<Cross2Icon />
|
onClick={handleReset}
|
||||||
</Button>
|
className='text-muted-foreground hover:text-foreground gap-1 px-2'
|
||||||
) : null
|
>
|
||||||
|
{t('Reset')}
|
||||||
|
<Cross2Icon />
|
||||||
|
</Button>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
const searchButton = hasSearch ? (
|
const searchButton = hasSearch ? (
|
||||||
<Button onClick={props.onSearch} disabled={props.searchLoading}>
|
<Button onClick={props.onSearch} disabled={props.searchLoading}>
|
||||||
@@ -341,6 +351,7 @@ export function DataTableToolbar<TData>(props: DataTableToolbarProps<TData>) {
|
|||||||
{props.customSearch !== undefined ? props.customSearch : searchInput}
|
{props.customSearch !== undefined ? props.customSearch : searchInput}
|
||||||
{props.additionalSearch}
|
{props.additionalSearch}
|
||||||
{filterChips}
|
{filterChips}
|
||||||
|
{props.afterFilters}
|
||||||
<div className='ms-auto flex shrink-0 items-center gap-1.5 sm:gap-2'>
|
<div className='ms-auto flex shrink-0 items-center gap-1.5 sm:gap-2'>
|
||||||
{expandToggle}
|
{expandToggle}
|
||||||
</div>
|
</div>
|
||||||
@@ -376,6 +387,7 @@ export function DataTableToolbar<TData>(props: DataTableToolbarProps<TData>) {
|
|||||||
{props.customSearch !== undefined ? props.customSearch : searchInput}
|
{props.customSearch !== undefined ? props.customSearch : searchInput}
|
||||||
{props.additionalSearch}
|
{props.additionalSearch}
|
||||||
{filterChips}
|
{filterChips}
|
||||||
|
{props.afterFilters}
|
||||||
{expanded && hasExpandable && props.expandable}
|
{expanded && hasExpandable && props.expandable}
|
||||||
|
|
||||||
<div className='ms-auto flex shrink-0 items-center gap-1.5 sm:gap-2'>
|
<div className='ms-auto flex shrink-0 items-center gap-1.5 sm:gap-2'>
|
||||||
|
|||||||
@@ -24,6 +24,9 @@ import { CHANNEL_STATUS } from '../constants'
|
|||||||
import { isTagAggregateRow, parseGroupsList } from '../lib'
|
import { isTagAggregateRow, parseGroupsList } from '../lib'
|
||||||
import type { Channel } from '../types'
|
import type { Channel } from '../types'
|
||||||
import { ChannelRowActionsLayoutContext } from './channel-row-actions-context'
|
import { ChannelRowActionsLayoutContext } from './channel-row-actions-context'
|
||||||
|
import { useChannels } from './channels-provider'
|
||||||
|
|
||||||
|
const SENSITIVE_MASK = '••••'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Bespoke channel card for the card view. Reuses every column's existing cell
|
* Bespoke channel card for the card view. Reuses every column's existing cell
|
||||||
@@ -35,6 +38,7 @@ import { ChannelRowActionsLayoutContext } from './channel-row-actions-context'
|
|||||||
*/
|
*/
|
||||||
export function ChannelCard({ row }: { row: Row<Channel> }) {
|
export function ChannelCard({ row }: { row: Row<Channel> }) {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
const { sensitiveVisible } = useChannels()
|
||||||
const isTagRow = isTagAggregateRow(row.original)
|
const isTagRow = isTagAggregateRow(row.original)
|
||||||
const cells = row.getAllCells()
|
const cells = row.getAllCells()
|
||||||
|
|
||||||
@@ -99,7 +103,11 @@ export function ChannelCard({ row }: { row: Row<Channel> }) {
|
|||||||
{/* Left column */}
|
{/* Left column */}
|
||||||
<div className='flex min-w-0 flex-1 flex-col gap-3 overflow-hidden'>
|
<div className='flex min-w-0 flex-1 flex-col gap-3 overflow-hidden'>
|
||||||
<div className='min-w-0 text-sm'>
|
<div className='min-w-0 text-sm'>
|
||||||
{!isTagRow && <div className={labelClass}>#{row.original.id}</div>}
|
{!isTagRow && (
|
||||||
|
<div className={labelClass}>
|
||||||
|
#{sensitiveVisible ? row.original.id : SENSITIVE_MASK}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
{nameCell}
|
{nameCell}
|
||||||
</div>
|
</div>
|
||||||
<div className='min-w-0'>
|
<div className='min-w-0'>
|
||||||
@@ -138,7 +146,12 @@ export function ChannelCard({ row }: { row: Row<Channel> }) {
|
|||||||
{groups.length > 0 ? (
|
{groups.length > 0 ? (
|
||||||
<div className='-ml-1.5 flex flex-wrap gap-1'>
|
<div className='-ml-1.5 flex flex-wrap gap-1'>
|
||||||
{groups.map((g) => (
|
{groups.map((g) => (
|
||||||
<GroupBadge key={g} group={g} size='sm' />
|
<GroupBadge
|
||||||
|
key={g}
|
||||||
|
group={g}
|
||||||
|
label={sensitiveVisible ? undefined : SENSITIVE_MASK}
|
||||||
|
size='sm'
|
||||||
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
@@ -285,6 +285,7 @@ function WeightCell({ channel }: { channel: Channel }) {
|
|||||||
* notation (e.g. "$28万"); the precise value stays available in the tooltip.
|
* notation (e.g. "$28万"); the precise value stays available in the tooltip.
|
||||||
*/
|
*/
|
||||||
const MAX_INLINE_BALANCE_CHARS = 8
|
const MAX_INLINE_BALANCE_CHARS = 8
|
||||||
|
const SENSITIVE_MASK = '••••'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Balance cell component with click to update
|
* Balance cell component with click to update
|
||||||
@@ -292,6 +293,7 @@ const MAX_INLINE_BALANCE_CHARS = 8
|
|||||||
function BalanceCell({ channel }: { channel: Channel }) {
|
function BalanceCell({ channel }: { channel: Channel }) {
|
||||||
const { t, i18n } = useTranslation()
|
const { t, i18n } = useTranslation()
|
||||||
const queryClient = useQueryClient()
|
const queryClient = useQueryClient()
|
||||||
|
const { sensitiveVisible } = useChannels()
|
||||||
const isTagRow = isTagAggregateRow(channel)
|
const isTagRow = isTagAggregateRow(channel)
|
||||||
const balance = channel.balance || 0
|
const balance = channel.balance || 0
|
||||||
const usedQuota = channel.used_quota || 0
|
const usedQuota = channel.used_quota || 0
|
||||||
@@ -320,6 +322,8 @@ function BalanceCell({ channel }: { channel: Channel }) {
|
|||||||
: remainingFull
|
: remainingFull
|
||||||
const usedLabel = `${t('Used:')} ${usedFull}`
|
const usedLabel = `${t('Used:')} ${usedFull}`
|
||||||
const remainingLabel = `${t('Remaining:')} ${remainingFull}`
|
const remainingLabel = `${t('Remaining:')} ${remainingFull}`
|
||||||
|
const maskedUsedLabel = `${t('Used:')} ${SENSITIVE_MASK}`
|
||||||
|
const maskedRemainingLabel = `${t('Remaining:')} ${SENSITIVE_MASK}`
|
||||||
|
|
||||||
// Tag row: only show cumulative used quota
|
// Tag row: only show cumulative used quota
|
||||||
if (isTagRow) {
|
if (isTagRow) {
|
||||||
@@ -329,7 +333,11 @@ function BalanceCell({ channel }: { channel: Channel }) {
|
|||||||
<TooltipTrigger
|
<TooltipTrigger
|
||||||
render={
|
render={
|
||||||
<StatusBadge
|
<StatusBadge
|
||||||
label={`${t('Used:')} ${usedDisplay}`}
|
label={
|
||||||
|
sensitiveVisible
|
||||||
|
? `${t('Used:')} ${usedDisplay}`
|
||||||
|
: maskedUsedLabel
|
||||||
|
}
|
||||||
variant='neutral'
|
variant='neutral'
|
||||||
size='sm'
|
size='sm'
|
||||||
copyable={false}
|
copyable={false}
|
||||||
@@ -339,7 +347,7 @@ function BalanceCell({ channel }: { channel: Channel }) {
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<TooltipContent>
|
<TooltipContent>
|
||||||
<p>{usedLabel}</p>
|
<p>{sensitiveVisible ? usedLabel : maskedUsedLabel}</p>
|
||||||
</TooltipContent>
|
</TooltipContent>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</TooltipProvider>
|
</TooltipProvider>
|
||||||
@@ -376,12 +384,20 @@ function BalanceCell({ channel }: { channel: Channel }) {
|
|||||||
await handleUpdateChannelBalance(channel.id, queryClient)
|
await handleUpdateChannelBalance(channel.id, queryClient)
|
||||||
setIsUpdating(false)
|
setIsUpdating(false)
|
||||||
}
|
}
|
||||||
let remainingBadgeLabel = remainingDisplay
|
let remainingBadgeLabel = sensitiveVisible
|
||||||
if (isUpdating) {
|
? remainingDisplay
|
||||||
|
: SENSITIVE_MASK
|
||||||
|
if (sensitiveVisible && isUpdating) {
|
||||||
remainingBadgeLabel = t('Updating...')
|
remainingBadgeLabel = t('Updating...')
|
||||||
} else if (channel.type === 57) {
|
} else if (sensitiveVisible && channel.type === 57) {
|
||||||
remainingBadgeLabel = t('Account Info')
|
remainingBadgeLabel = t('Account Info')
|
||||||
}
|
}
|
||||||
|
let remainingTooltipLabel = remainingLabel
|
||||||
|
if (!sensitiveVisible) {
|
||||||
|
remainingTooltipLabel = maskedRemainingLabel
|
||||||
|
} else if (channel.type === 57) {
|
||||||
|
remainingTooltipLabel = t('Click to view Codex usage')
|
||||||
|
}
|
||||||
let remainingBadgeVariant: StatusBadgeProps['variant'] = variant
|
let remainingBadgeVariant: StatusBadgeProps['variant'] = variant
|
||||||
if (channel.type === 57) {
|
if (channel.type === 57) {
|
||||||
remainingBadgeVariant = 'info'
|
remainingBadgeVariant = 'info'
|
||||||
@@ -396,7 +412,7 @@ function BalanceCell({ channel }: { channel: Channel }) {
|
|||||||
<TooltipTrigger
|
<TooltipTrigger
|
||||||
render={
|
render={
|
||||||
<StatusBadge
|
<StatusBadge
|
||||||
label={usedDisplay}
|
label={sensitiveVisible ? usedDisplay : SENSITIVE_MASK}
|
||||||
variant='neutral'
|
variant='neutral'
|
||||||
size='sm'
|
size='sm'
|
||||||
copyable={false}
|
copyable={false}
|
||||||
@@ -406,7 +422,7 @@ function BalanceCell({ channel }: { channel: Channel }) {
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<TooltipContent>
|
<TooltipContent>
|
||||||
<p>{usedLabel}</p>
|
<p>{sensitiveVisible ? usedLabel : maskedUsedLabel}</p>
|
||||||
</TooltipContent>
|
</TooltipContent>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
@@ -424,11 +440,7 @@ function BalanceCell({ channel }: { channel: Channel }) {
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<TooltipContent>
|
<TooltipContent>
|
||||||
<p>
|
<p>{remainingTooltipLabel}</p>
|
||||||
{channel.type === 57
|
|
||||||
? t('Click to view Codex usage')
|
|
||||||
: remainingLabel}
|
|
||||||
</p>
|
|
||||||
{channel.type !== 57 && <p>{t('Click to update balance')}</p>}
|
{channel.type !== 57 && <p>{t('Click to update balance')}</p>}
|
||||||
</TooltipContent>
|
</TooltipContent>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
@@ -439,6 +451,8 @@ function BalanceCell({ channel }: { channel: Channel }) {
|
|||||||
onOpenChange={setCodexUsageOpen}
|
onOpenChange={setCodexUsageOpen}
|
||||||
channelName={channel.name}
|
channelName={channel.name}
|
||||||
channelId={channel.id}
|
channelId={channel.id}
|
||||||
|
channelDisplayName={sensitiveVisible ? undefined : SENSITIVE_MASK}
|
||||||
|
channelDisplayId={sensitiveVisible ? undefined : SENSITIVE_MASK}
|
||||||
response={codexUsageResponse}
|
response={codexUsageResponse}
|
||||||
onRefresh={async () => {
|
onRefresh={async () => {
|
||||||
if (isUpdating) {
|
if (isUpdating) {
|
||||||
@@ -472,6 +486,7 @@ function BalanceCell({ channel }: { channel: Channel }) {
|
|||||||
*/
|
*/
|
||||||
export function useChannelsColumns(): ColumnDef<Channel>[] {
|
export function useChannelsColumns(): ColumnDef<Channel>[] {
|
||||||
const { t, i18n } = useTranslation()
|
const { t, i18n } = useTranslation()
|
||||||
|
const { sensitiveVisible } = useChannels()
|
||||||
const locale = i18n.resolvedLanguage || i18n.language
|
const locale = i18n.resolvedLanguage || i18n.language
|
||||||
return [
|
return [
|
||||||
// Checkbox column
|
// Checkbox column
|
||||||
@@ -513,7 +528,7 @@ export function useChannelsColumns(): ColumnDef<Channel>[] {
|
|||||||
meta: { mobileHidden: true },
|
meta: { mobileHidden: true },
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
const id = row.getValue('id') as number
|
const id = row.getValue('id') as number
|
||||||
return <TableId value={id} />
|
return <TableId value={sensitiveVisible ? id : SENSITIVE_MASK} />
|
||||||
},
|
},
|
||||||
size: 80,
|
size: 80,
|
||||||
},
|
},
|
||||||
@@ -570,7 +585,7 @@ export function useChannelsColumns(): ColumnDef<Channel>[] {
|
|||||||
<div className='flex flex-col gap-1'>
|
<div className='flex flex-col gap-1'>
|
||||||
<div className='flex items-center gap-1.5'>
|
<div className='flex items-center gap-1.5'>
|
||||||
<TruncatedText
|
<TruncatedText
|
||||||
text={name}
|
text={sensitiveVisible ? name : SENSITIVE_MASK}
|
||||||
className='font-medium'
|
className='font-medium'
|
||||||
maxWidth='max-w-[180px]'
|
maxWidth='max-w-[180px]'
|
||||||
/>
|
/>
|
||||||
@@ -929,7 +944,12 @@ export function useChannelsColumns(): ColumnDef<Channel>[] {
|
|||||||
return (
|
return (
|
||||||
<BadgeListCell
|
<BadgeListCell
|
||||||
items={groupArray.map((g) => (
|
items={groupArray.map((g) => (
|
||||||
<GroupBadge key={g} group={g} size='sm' />
|
<GroupBadge
|
||||||
|
key={g}
|
||||||
|
group={g}
|
||||||
|
label={sensitiveVisible ? undefined : SENSITIVE_MASK}
|
||||||
|
size='sm'
|
||||||
|
/>
|
||||||
))}
|
))}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -53,6 +53,8 @@ type ChannelsContextType = {
|
|||||||
setEnableTagMode: (enabled: boolean) => void
|
setEnableTagMode: (enabled: boolean) => void
|
||||||
idSort: boolean
|
idSort: boolean
|
||||||
setIdSort: (enabled: boolean) => void
|
setIdSort: (enabled: boolean) => void
|
||||||
|
sensitiveVisible: boolean
|
||||||
|
setSensitiveVisible: (visible: boolean) => void
|
||||||
upstream: UpstreamUpdateState
|
upstream: UpstreamUpdateState
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -78,6 +80,7 @@ export function ChannelsProvider({ children }: { children: React.ReactNode }) {
|
|||||||
const [idSort, setIdSort] = useState(() => {
|
const [idSort, setIdSort] = useState(() => {
|
||||||
return localStorage.getItem('channels-id-sort') === 'true'
|
return localStorage.getItem('channels-id-sort') === 'true'
|
||||||
})
|
})
|
||||||
|
const [sensitiveVisible, setSensitiveVisible] = useState(true)
|
||||||
|
|
||||||
const queryClient = useQueryClient()
|
const queryClient = useQueryClient()
|
||||||
const refreshChannels = useCallback(async () => {
|
const refreshChannels = useCallback(async () => {
|
||||||
@@ -98,6 +101,8 @@ export function ChannelsProvider({ children }: { children: React.ReactNode }) {
|
|||||||
setEnableTagMode,
|
setEnableTagMode,
|
||||||
idSort,
|
idSort,
|
||||||
setIdSort,
|
setIdSort,
|
||||||
|
sensitiveVisible,
|
||||||
|
setSensitiveVisible,
|
||||||
upstream,
|
upstream,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|||||||
+47
-14
@@ -19,16 +19,19 @@ For commercial licensing, please contact support@quantumnous.com
|
|||||||
import { useState, useMemo } from 'react'
|
import { useState, useMemo } from 'react'
|
||||||
import { useQuery } from '@tanstack/react-query'
|
import { useQuery } from '@tanstack/react-query'
|
||||||
import { getRouteApi } from '@tanstack/react-router'
|
import { getRouteApi } from '@tanstack/react-router'
|
||||||
import {
|
import type { OnChangeFn, SortingState, Row } from '@tanstack/react-table'
|
||||||
type OnChangeFn,
|
import { Eye, EyeOff } from 'lucide-react'
|
||||||
type SortingState,
|
|
||||||
type Row,
|
|
||||||
} from '@tanstack/react-table'
|
|
||||||
import { useMediaQuery } from '@/hooks'
|
import { useMediaQuery } from '@/hooks'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { getLobeIcon } from '@/lib/lobe-icon'
|
import { getLobeIcon } from '@/lib/lobe-icon'
|
||||||
import { useTableUrlState } from '@/hooks/use-table-url-state'
|
import { useTableUrlState } from '@/hooks/use-table-url-state'
|
||||||
|
import { Button } from '@/components/ui/button'
|
||||||
import { Input } from '@/components/ui/input'
|
import { Input } from '@/components/ui/input'
|
||||||
|
import {
|
||||||
|
Tooltip,
|
||||||
|
TooltipContent,
|
||||||
|
TooltipTrigger,
|
||||||
|
} from '@/components/ui/tooltip'
|
||||||
import {
|
import {
|
||||||
DISABLED_ROW_DESKTOP,
|
DISABLED_ROW_DESKTOP,
|
||||||
DISABLED_ROW_MOBILE,
|
DISABLED_ROW_MOBILE,
|
||||||
@@ -77,7 +80,12 @@ function isDisabledChannelRow(channel: Channel) {
|
|||||||
|
|
||||||
export function ChannelsTable() {
|
export function ChannelsTable() {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const { enableTagMode, idSort } = useChannels()
|
const {
|
||||||
|
enableTagMode,
|
||||||
|
idSort,
|
||||||
|
sensitiveVisible,
|
||||||
|
setSensitiveVisible,
|
||||||
|
} = useChannels()
|
||||||
const isMobile = useMediaQuery('(max-width: 640px)')
|
const isMobile = useMediaQuery('(max-width: 640px)')
|
||||||
|
|
||||||
// Table state
|
// Table state
|
||||||
@@ -343,7 +351,10 @@ export function ChannelsTable() {
|
|||||||
|
|
||||||
const groupFilterOptions = [
|
const groupFilterOptions = [
|
||||||
{ label: t('All Groups'), value: 'all' },
|
{ label: t('All Groups'), value: 'all' },
|
||||||
...groupOptions,
|
...groupOptions.map((option) => ({
|
||||||
|
...option,
|
||||||
|
label: sensitiveVisible ? option.label : '••••',
|
||||||
|
})),
|
||||||
]
|
]
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -398,14 +409,36 @@ export function ChannelsTable() {
|
|||||||
singleSelect: true,
|
singleSelect: true,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
afterFilters: (
|
||||||
|
<Tooltip>
|
||||||
|
<TooltipTrigger
|
||||||
|
render={
|
||||||
|
<Button
|
||||||
|
variant='ghost'
|
||||||
|
size='icon'
|
||||||
|
onClick={() => setSensitiveVisible(!sensitiveVisible)}
|
||||||
|
aria-label={sensitiveVisible ? t('Hide') : t('Show')}
|
||||||
|
className='text-muted-foreground hover:text-foreground size-8'
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{sensitiveVisible ? <Eye /> : <EyeOff />}
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent>
|
||||||
|
{sensitiveVisible ? t('Hide') : t('Show')}
|
||||||
|
</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
getRowClassName={(row, { isMobile }) => {
|
||||||
|
if (!isDisabledChannelRow(row.original)) {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
if (isMobile) {
|
||||||
|
return DISABLED_ROW_MOBILE
|
||||||
|
}
|
||||||
|
return DISABLED_ROW_DESKTOP
|
||||||
}}
|
}}
|
||||||
getRowClassName={(row, { isMobile }) =>
|
|
||||||
isDisabledChannelRow(row.original)
|
|
||||||
? isMobile
|
|
||||||
? DISABLED_ROW_MOBILE
|
|
||||||
: DISABLED_ROW_DESKTOP
|
|
||||||
: undefined
|
|
||||||
}
|
|
||||||
bulkActions={<DataTableBulkActions table={table} />}
|
bulkActions={<DataTableBulkActions table={table} />}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
|||||||
+12
-3
@@ -139,6 +139,8 @@ type CodexUsageDialogProps = {
|
|||||||
onOpenChange: (open: boolean) => void
|
onOpenChange: (open: boolean) => void
|
||||||
channelName?: string
|
channelName?: string
|
||||||
channelId?: number
|
channelId?: number
|
||||||
|
channelDisplayName?: string
|
||||||
|
channelDisplayId?: string
|
||||||
response: CodexUsageDialogData | null
|
response: CodexUsageDialogData | null
|
||||||
onRefresh?: () => void | Promise<void>
|
onRefresh?: () => void | Promise<void>
|
||||||
isRefreshing?: boolean
|
isRefreshing?: boolean
|
||||||
@@ -885,6 +887,8 @@ export function CodexUsageDialog({
|
|||||||
onOpenChange,
|
onOpenChange,
|
||||||
channelName,
|
channelName,
|
||||||
channelId,
|
channelId,
|
||||||
|
channelDisplayName,
|
||||||
|
channelDisplayId,
|
||||||
response,
|
response,
|
||||||
onRefresh,
|
onRefresh,
|
||||||
isRefreshing,
|
isRefreshing,
|
||||||
@@ -931,9 +935,14 @@ export function CodexUsageDialog({
|
|||||||
? String(resetCredits)
|
? String(resetCredits)
|
||||||
: '-'
|
: '-'
|
||||||
const canResetCodexUsage = Number(resetCredits) > 0
|
const canResetCodexUsage = Number(resetCredits) > 0
|
||||||
const channelLabel = `${channelName || '-'}${
|
const channelLabelName = channelDisplayName ?? channelName ?? '-'
|
||||||
channelId ? ` (#${channelId})` : ''
|
let channelLabelId = ''
|
||||||
}`
|
if (channelDisplayId != null) {
|
||||||
|
channelLabelId = ` (#${channelDisplayId})`
|
||||||
|
} else if (channelId) {
|
||||||
|
channelLabelId = ` (#${channelId})`
|
||||||
|
}
|
||||||
|
const channelLabel = `${channelLabelName}${channelLabelId}`
|
||||||
const { fiveHourWindow, weeklyWindow } = resolveRateLimitWindows(payload)
|
const { fiveHourWindow, weeklyWindow } = resolveRateLimitWindows(payload)
|
||||||
|
|
||||||
const errorMessage =
|
const errorMessage =
|
||||||
|
|||||||
Reference in New Issue
Block a user