Skip to content
Toggle navigation
P
Projects
G
Groups
S
Snippets
Help
phsl
/
new-api
This project
Loading...
Sign in
Toggle navigation
Go to a project
Project
Repository
Issues
0
Merge Requests
0
Pipelines
Wiki
Snippets
Members
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Commit
eb863116
authored
Jun 16, 2026
by
t0ng7u
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
style(ui): align wallet and profile interactions
parent
b36888ba
Hide whitespace changes
Inline
Side-by-side
Showing
39 changed files
with
338 additions
and
311 deletions
+338
-311
web/default/src/components/data-table/core/data-table-header.tsx
+5
-1
web/default/src/components/data-table/layout/data-table-page.tsx
+2
-1
web/default/src/components/data-table/layout/mobile-card-list.tsx
+5
-2
web/default/src/components/data-table/static/static-data-table-classnames.ts
+2
-1
web/default/src/components/provider-badge.tsx
+2
-8
web/default/src/components/ui/table.tsx
+1
-1
web/default/src/components/ui/titled-card.tsx
+6
-1
web/default/src/features/channels/components/channels-columns.tsx
+11
-4
web/default/src/features/dashboard/components/overview/overview-dashboard.tsx
+4
-2
web/default/src/features/models/components/data-table-row-actions.tsx
+66
-66
web/default/src/features/pricing/components/dynamic-pricing-breakdown.tsx
+3
-5
web/default/src/features/pricing/components/model-card.tsx
+1
-3
web/default/src/features/profile/components/checkin-calendar-card.tsx
+8
-9
web/default/src/features/profile/components/language-preferences-card.tsx
+1
-0
web/default/src/features/profile/components/passkey-card.tsx
+3
-3
web/default/src/features/profile/components/profile-header.tsx
+19
-12
web/default/src/features/profile/components/profile-security-card.tsx
+4
-5
web/default/src/features/profile/components/profile-settings-card.tsx
+2
-1
web/default/src/features/profile/components/sidebar-modules-card.tsx
+2
-2
web/default/src/features/profile/components/tabs/account-bindings-tab.tsx
+1
-1
web/default/src/features/profile/components/tabs/notification-tab.tsx
+38
-29
web/default/src/features/profile/components/two-fa-card.tsx
+2
-2
web/default/src/features/redemption-codes/components/data-table-row-actions.tsx
+56
-56
web/default/src/features/subscriptions/components/data-table-row-actions.tsx
+36
-36
web/default/src/features/system-settings/content/uptime-kuma-section.tsx
+1
-2
web/default/src/features/system-settings/general/channel-affinity/index.tsx
+3
-1
web/default/src/features/system-settings/integrations/amount-discount-visual-editor.tsx
+1
-3
web/default/src/features/system-settings/integrations/payment-methods-visual-editor.tsx
+1
-3
web/default/src/features/system-settings/models/conflict-confirm-dialog.tsx
+7
-9
web/default/src/features/system-settings/models/tool-price-settings.tsx
+22
-22
web/default/src/features/usage-logs/components/dialogs/details-dialog.tsx
+3
-1
web/default/src/features/wallet/components/affiliate-rewards-card.tsx
+2
-2
web/default/src/features/wallet/components/creem-products-section.tsx
+3
-2
web/default/src/features/wallet/components/dialogs/billing-history-dialog.tsx
+1
-1
web/default/src/features/wallet/components/dialogs/creem-confirm-dialog.tsx
+1
-1
web/default/src/features/wallet/components/recharge-form-card.tsx
+3
-2
web/default/src/features/wallet/components/subscription-plans-card.tsx
+4
-5
web/default/src/styles/index.css
+3
-3
web/default/src/tanstack-table.d.ts
+3
-3
No files found.
web/default/src/components/data-table/core/data-table-header.tsx
View file @
eb863116
...
@@ -16,7 +16,11 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
...
@@ -16,7 +16,11 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com
For commercial licensing, please contact support@quantumnous.com
*/
*/
import
{
flexRender
,
type
Header
,
type
Table
as
TanstackTable
}
from
'@tanstack/react-table'
import
{
flexRender
,
type
Header
,
type
Table
as
TanstackTable
,
}
from
'@tanstack/react-table'
import
{
TableHead
,
TableHeader
,
TableRow
}
from
'@/components/ui/table'
import
{
TableHead
,
TableHeader
,
TableRow
}
from
'@/components/ui/table'
import
{
DataTableColumnHeader
}
from
'./column-header'
import
{
DataTableColumnHeader
}
from
'./column-header'
import
type
{
DataTableColumnClassName
}
from
'./types'
import
type
{
DataTableColumnClassName
}
from
'./types'
...
...
web/default/src/components/data-table/layout/data-table-page.tsx
View file @
eb863116
...
@@ -344,7 +344,8 @@ function renderDesktop<TData>(
...
@@ -344,7 +344,8 @@ function renderDesktop<TData>(
splitHeader=
{
fixedHeight
}
splitHeader=
{
fixedHeight
}
tableContainerClassName=
{
fixedHeight
?
'h-full min-h-0'
:
undefined
}
tableContainerClassName=
{
fixedHeight
?
'h-full min-h-0'
:
undefined
}
tableHeaderClassName=
{
cn
(
tableHeaderClassName=
{
cn
(
fixedHeight
&&
'[background-color:color-mix(in_oklch,var(--muted)_30%,var(--background))]'
,
fixedHeight
&&
'[background-color:color-mix(in_oklch,var(--muted)_30%,var(--background))]'
,
props
.
tableHeaderClassName
props
.
tableHeaderClassName
)
}
)
}
getColumnClassName=
{
props
.
getColumnClassName
}
getColumnClassName=
{
props
.
getColumnClassName
}
...
...
web/default/src/components/data-table/layout/mobile-card-list.tsx
View file @
eb863116
...
@@ -25,7 +25,6 @@ import {
...
@@ -25,7 +25,6 @@ import {
}
from
'@tanstack/react-table'
}
from
'@tanstack/react-table'
import
{
Database
}
from
'lucide-react'
import
{
Database
}
from
'lucide-react'
import
{
useTranslation
}
from
'react-i18next'
import
{
useTranslation
}
from
'react-i18next'
import
{
StatusBadgeTypeContext
}
from
'@/components/status-badge'
import
{
cn
}
from
'@/lib/utils'
import
{
cn
}
from
'@/lib/utils'
import
{
import
{
Empty
,
Empty
,
...
@@ -35,6 +34,7 @@ import {
...
@@ -35,6 +34,7 @@ import {
EmptyTitle
,
EmptyTitle
,
}
from
'@/components/ui/empty'
}
from
'@/components/ui/empty'
import
{
Skeleton
}
from
'@/components/ui/skeleton'
import
{
Skeleton
}
from
'@/components/ui/skeleton'
import
{
StatusBadgeTypeContext
}
from
'@/components/status-badge'
interface
MobileCardListProps
<
TData
>
{
interface
MobileCardListProps
<
TData
>
{
table
:
Table
<
TData
>
table
:
Table
<
TData
>
...
@@ -211,7 +211,10 @@ function FallbackRow<TData>({ row }: { row: Row<TData> }) {
...
@@ -211,7 +211,10 @@ function FallbackRow<TData>({ row }: { row: Row<TData> }) {
if
(
!
label
)
{
if
(
!
label
)
{
return
(
return
(
<
div
key=
{
cell
.
id
}
className=
'flex justify-end overflow-hidden [&_[data-slot=provider-badge]]:ml-0 [&_[data-slot=status-badge]]:ml-0'
>
<
div
key=
{
cell
.
id
}
className=
'flex justify-end overflow-hidden [&_[data-slot=provider-badge]]:ml-0 [&_[data-slot=status-badge]]:ml-0'
>
<
StatusBadgeTypeContext
.
Provider
value=
'text'
>
<
StatusBadgeTypeContext
.
Provider
value=
'text'
>
{
renderCellContent
(
cell
)
}
{
renderCellContent
(
cell
)
}
</
StatusBadgeTypeContext
.
Provider
>
</
StatusBadgeTypeContext
.
Provider
>
...
...
web/default/src/components/data-table/static/static-data-table-classnames.ts
View file @
eb863116
...
@@ -22,7 +22,8 @@ export const staticDataTableClassNames = {
...
@@ -22,7 +22,8 @@ export const staticDataTableClassNames = {
embeddedContainer
:
'rounded-none border-0'
,
embeddedContainer
:
'rounded-none border-0'
,
compactTable
:
'text-sm'
,
compactTable
:
'text-sm'
,
compactHeaderRow
:
'hover:bg-transparent'
,
compactHeaderRow
:
'hover:bg-transparent'
,
mutedHeaderRow
:
'[background-color:color-mix(in_oklch,var(--muted)_30%,var(--background))] hover:[background-color:color-mix(in_oklch,var(--muted)_30%,var(--background))]'
,
mutedHeaderRow
:
'[background-color:color-mix(in_oklch,var(--muted)_30%,var(--background))] hover:[background-color:color-mix(in_oklch,var(--muted)_30%,var(--background))]'
,
compactHeaderCell
:
compactHeaderCell
:
'text-muted-foreground py-2 text-[10px] font-medium tracking-wider uppercase'
,
'text-muted-foreground py-2 text-[10px] font-medium tracking-wider uppercase'
,
compactHeaderCellRight
:
compactHeaderCellRight
:
...
...
web/default/src/components/provider-badge.tsx
View file @
eb863116
...
@@ -38,20 +38,14 @@ export function ProviderBadge({
...
@@ -38,20 +38,14 @@ export function ProviderBadge({
return
(
return
(
<
div
<
div
data
-
slot=
'provider-badge'
data
-
slot=
'provider-badge'
className=
{
cn
(
className=
{
cn
(
'flex max-w-full min-w-0 items-center gap-1.5'
,
className
)
}
'flex min-w-0 max-w-full items-center gap-1.5'
,
className
)
}
>
>
{
icon
&&
<
span
className=
'flex shrink-0 items-center'
>
{
icon
}
</
span
>
}
{
icon
&&
<
span
className=
'flex shrink-0 items-center'
>
{
icon
}
</
span
>
}
<
StatusBadge
<
StatusBadge
label=
{
label
}
label=
{
label
}
autoColor=
{
label
}
autoColor=
{
label
}
size=
'sm'
size=
'sm'
className=
{
cn
(
className=
{
cn
(
'min-w-0 shrink overflow-hidden'
,
!
icon
&&
'pl-0'
)
}
'min-w-0 shrink overflow-hidden'
,
!
icon
&&
'pl-0'
)
}
{
...
badgeProps
}
{
...
badgeProps
}
/>
/>
</
div
>
</
div
>
...
...
web/default/src/components/ui/table.tsx
View file @
eb863116
...
@@ -77,7 +77,7 @@ function TableRow({ className, ...props }: React.ComponentProps<'tr'>) {
...
@@ -77,7 +77,7 @@ function TableRow({ className, ...props }: React.ComponentProps<'tr'>) {
<
tr
<
tr
data
-
slot=
'table-row'
data
-
slot=
'table-row'
className=
{
cn
(
className=
{
cn
(
'group
hover:[background-color:color-mix(in_oklch,var(--muted)_50%,var(--background))] has-aria-expanded:[background-color:color-mix(in_oklch,var(--muted)_50%,var(--background))] data-[state=selected]:bg-muted border-b transition-colors
'
,
'group
data-[state=selected]:bg-muted border-b transition-colors hover:[background-color:color-mix(in_oklch,var(--muted)_50%,var(--background))] has-aria-expanded:[background-color:color-mix(in_oklch,var(--muted)_50%,var(--background))]
'
,
className
className
)
}
)
}
{
...
props
}
{
...
props
}
...
...
web/default/src/components/ui/titled-card.tsx
View file @
eb863116
...
@@ -32,6 +32,7 @@ type TitledCardProps = {
...
@@ -32,6 +32,7 @@ type TitledCardProps = {
icon
?:
ReactNode
icon
?:
ReactNode
action
?:
ReactNode
action
?:
ReactNode
children
?:
ReactNode
children
?:
ReactNode
disableHoverEffect
?:
boolean
className
?:
string
className
?:
string
headerClassName
?:
string
headerClassName
?:
string
contentClassName
?:
string
contentClassName
?:
string
...
@@ -46,6 +47,7 @@ export function TitledCard({
...
@@ -46,6 +47,7 @@ export function TitledCard({
icon
,
icon
,
action
,
action
,
children
,
children
,
disableHoverEffect
,
className
,
className
,
headerClassName
,
headerClassName
,
contentClassName
,
contentClassName
,
...
@@ -54,7 +56,10 @@ export function TitledCard({
...
@@ -54,7 +56,10 @@ export function TitledCard({
descriptionClassName
,
descriptionClassName
,
}:
TitledCardProps
)
{
}:
TitledCardProps
)
{
return
(
return
(
<
Card
className=
{
cn
(
'gap-0 overflow-hidden py-0'
,
className
)
}
>
<
Card
data
-
card
-
hover=
{
disableHoverEffect
?
'false'
:
undefined
}
className=
{
cn
(
'gap-0 overflow-hidden py-0'
,
className
)
}
>
<
CardHeader
<
CardHeader
className=
{
cn
(
'border-b p-3 !pb-3 sm:p-5 sm:!pb-5'
,
headerClassName
)
}
className=
{
cn
(
'border-b p-3 !pb-3 sm:p-5 sm:!pb-5'
,
headerClassName
)
}
>
>
...
...
web/default/src/features/channels/components/channels-columns.tsx
View file @
eb863116
...
@@ -622,7 +622,7 @@ export function useChannelsColumns(): ColumnDef<Channel>[] {
...
@@ -622,7 +622,7 @@ export function useChannelsColumns(): ColumnDef<Channel>[] {
:
undefined
:
undefined
return
(
return
(
<
div
className=
'flex m
in-w-0 max-w-full
items-center gap-2 overflow-hidden'
>
<
div
className=
'flex m
ax-w-full min-w-0
items-center gap-2 overflow-hidden'
>
{
isMultiKey
&&
(
{
isMultiKey
&&
(
<
TooltipProvider
delay=
{
100
}
>
<
TooltipProvider
delay=
{
100
}
>
<
Tooltip
>
<
Tooltip
>
...
@@ -641,7 +641,7 @@ export function useChannelsColumns(): ColumnDef<Channel>[] {
...
@@ -641,7 +641,7 @@ export function useChannelsColumns(): ColumnDef<Channel>[] {
<
Tooltip
>
<
Tooltip
>
<
TooltipTrigger
<
TooltipTrigger
render=
{
render=
{
<
div
className=
'm
in-w-0 max-w-full
overflow-hidden'
/>
<
div
className=
'm
ax-w-full min-w-0
overflow-hidden'
/>
}
}
>
>
<
ProviderBadge
<
ProviderBadge
...
@@ -649,7 +649,7 @@ export function useChannelsColumns(): ColumnDef<Channel>[] {
...
@@ -649,7 +649,7 @@ export function useChannelsColumns(): ColumnDef<Channel>[] {
label=
{
typeName
}
label=
{
typeName
}
copyable=
{
false
}
copyable=
{
false
}
showDot=
{
false
}
showDot=
{
false
}
className=
'm
in-w-0 max-w-full
overflow-hidden'
className=
'm
ax-w-full min-w-0
overflow-hidden'
/>
/>
</
TooltipTrigger
>
</
TooltipTrigger
>
<
TooltipContent
side=
'top'
>
{
typeName
}
</
TooltipContent
>
<
TooltipContent
side=
'top'
>
{
typeName
}
</
TooltipContent
>
...
@@ -895,7 +895,14 @@ export function useChannelsColumns(): ColumnDef<Channel>[] {
...
@@ -895,7 +895,14 @@ export function useChannelsColumns(): ColumnDef<Channel>[] {
if
(
!
tag
)
if
(
!
tag
)
return
<
span
className=
'text-muted-foreground text-xs'
>
-
</
span
>
return
<
span
className=
'text-muted-foreground text-xs'
>
-
</
span
>
return
<
StatusBadge
label=
{
tag
}
autoColor=
{
tag
}
size=
'sm'
className=
'-ml-1.5'
/>
return
(
<
StatusBadge
label=
{
tag
}
autoColor=
{
tag
}
size=
'sm'
className=
'-ml-1.5'
/>
)
},
},
size
:
120
,
size
:
120
,
enableSorting
:
false
,
enableSorting
:
false
,
...
...
web/default/src/features/dashboard/components/overview/overview-dashboard.tsx
View file @
eb863116
...
@@ -190,7 +190,7 @@ function SetupGuideBackdrop(props: { compact?: boolean }) {
...
@@ -190,7 +190,7 @@ function SetupGuideBackdrop(props: { compact?: boolean }) {
/>
/>
<
div
<
div
className=
{
cn
(
className=
{
cn
(
'text-foreground/5
pointer-events-none absolute inset-y-0 right-0 hidden overflow-hidden font-mono sm:block dark:text-foreground/8
'
,
'text-foreground/5
dark:text-foreground/8 pointer-events-none absolute inset-y-0 right-0 hidden overflow-hidden font-mono sm:block
'
,
props
.
compact
?
'w-1/2 opacity-45'
:
'w-[58%] opacity-75'
props
.
compact
?
'w-1/2 opacity-45'
:
'w-[58%] opacity-75'
)
}
)
}
aria
-
hidden=
'true'
aria
-
hidden=
'true'
...
@@ -589,7 +589,9 @@ export function OverviewDashboard() {
...
@@ -589,7 +589,9 @@ export function OverviewDashboard() {
model
,
model
,
keyName
,
keyName
,
keyId
:
preferredKey
?.
id
,
keyId
:
preferredKey
?.
id
,
displayKey
:
preferredKey
?
formatDisplayKey
(
`sk-
${
preferredKey
.
key
}
`
)
:
'sk-...'
,
displayKey
:
preferredKey
?
formatDisplayKey
(
`sk-
${
preferredKey
.
key
}
`
)
:
'sk-...'
,
ready
,
ready
,
}
}
},
[
apiInfoItems
,
modelsQuery
.
data
,
preferredKey
,
t
])
},
[
apiInfoItems
,
modelsQuery
.
data
,
preferredKey
,
t
])
...
...
web/default/src/features/models/components/data-table-row-actions.tsx
View file @
eb863116
...
@@ -64,77 +64,77 @@ export function DataTableRowActions({ row }: DataTableRowActionsProps) {
...
@@ -64,77 +64,77 @@ export function DataTableRowActions({ row }: DataTableRowActionsProps) {
return
(
return
(
<
div
className=
'-ml-2'
>
<
div
className=
'-ml-2'
>
<
DropdownMenu
>
<
DropdownMenu
>
<
DropdownMenuTrigger
<
DropdownMenuTrigger
render=
{
render=
{
<
Button
<
Button
variant=
'ghost'
variant=
'ghost'
className=
'data-popup-open:bg-muted flex h-8 w-8 p-0'
className=
'data-popup-open:bg-muted flex h-8 w-8 p-0'
/>
/>
}
}
>
>
<
MoreHorizontal
className=
'h-4 w-4'
/>
<
MoreHorizontal
className=
'h-4 w-4'
/>
<
span
className=
'sr-only'
>
{
t
(
'Open menu'
)
}
</
span
>
<
span
className=
'sr-only'
>
{
t
(
'Open menu'
)
}
</
span
>
</
DropdownMenuTrigger
>
</
DropdownMenuTrigger
>
<
DropdownMenuContent
align=
'end'
className=
'w-48'
>
<
DropdownMenuContent
align=
'end'
className=
'w-48'
>
{
/* Edit */
}
{
/* Edit */
}
<
DropdownMenuItem
onClick=
{
handleEdit
}
>
<
DropdownMenuItem
onClick=
{
handleEdit
}
>
{
t
(
'Edit'
)
}
{
t
(
'Edit'
)
}
<
DropdownMenuShortcut
>
<
DropdownMenuShortcut
>
<
Pencil
size=
{
16
}
/>
<
Pencil
size=
{
16
}
/>
</
DropdownMenuShortcut
>
</
DropdownMenuShortcut
>
</
DropdownMenuItem
>
</
DropdownMenuItem
>
<
DropdownMenuSeparator
/>
<
DropdownMenuSeparator
/>
{
/* Enable/Disable */
}
{
/* Enable/Disable */
}
<
DropdownMenuItem
onClick=
{
handleToggleStatus
}
>
<
DropdownMenuItem
onClick=
{
handleToggleStatus
}
>
{
isEnabled
?
(
{
isEnabled
?
(
<>
<>
{
t
(
'Disable'
)
}
{
t
(
'Disable'
)
}
<
DropdownMenuShortcut
>
<
DropdownMenuShortcut
>
<
PowerOff
size=
{
16
}
/>
<
PowerOff
size=
{
16
}
/>
</
DropdownMenuShortcut
>
</
DropdownMenuShortcut
>
</>
</>
)
:
(
)
:
(
<>
<>
{
t
(
'Enable'
)
}
{
t
(
'Enable'
)
}
<
DropdownMenuShortcut
>
<
DropdownMenuShortcut
>
<
Power
size=
{
16
}
/>
<
Power
size=
{
16
}
/>
</
DropdownMenuShortcut
>
</
DropdownMenuShortcut
>
</>
</>
)
}
)
}
</
DropdownMenuItem
>
</
DropdownMenuItem
>
<
DropdownMenuSeparator
/>
<
DropdownMenuSeparator
/>
{
/* Delete */
}
{
/* Delete */
}
<
DropdownMenuItem
<
DropdownMenuItem
onSelect=
{
(
e
)
=>
{
onSelect=
{
(
e
)
=>
{
e
.
preventDefault
()
e
.
preventDefault
()
setDeleteConfirmOpen
(
true
)
setDeleteConfirmOpen
(
true
)
}
}
}
}
className=
'text-destructive focus:text-destructive'
className=
'text-destructive focus:text-destructive'
>
>
{
t
(
'Delete'
)
}
{
t
(
'Delete'
)
}
<
DropdownMenuShortcut
>
<
DropdownMenuShortcut
>
<
Trash2
size=
{
16
}
/>
<
Trash2
size=
{
16
}
/>
</
DropdownMenuShortcut
>
</
DropdownMenuShortcut
>
</
DropdownMenuItem
>
</
DropdownMenuItem
>
</
DropdownMenuContent
>
</
DropdownMenuContent
>
<
ConfirmDialog
<
ConfirmDialog
open=
{
deleteConfirmOpen
}
open=
{
deleteConfirmOpen
}
onOpenChange=
{
setDeleteConfirmOpen
}
onOpenChange=
{
setDeleteConfirmOpen
}
title=
{
t
(
'Delete Model'
)
}
title=
{
t
(
'Delete Model'
)
}
desc=
{
`Are you sure you want to delete "${model.model_name}"? This action cannot be undone.`
}
desc=
{
`Are you sure you want to delete "${model.model_name}"? This action cannot be undone.`
}
confirmText=
'Delete'
confirmText=
'Delete'
destructive
destructive
handleConfirm=
{
()
=>
{
handleConfirm=
{
()
=>
{
handleDeleteModel
(
model
.
id
,
queryClient
)
handleDeleteModel
(
model
.
id
,
queryClient
)
setDeleteConfirmOpen
(
false
)
setDeleteConfirmOpen
(
false
)
}
}
}
}
/>
/>
</
DropdownMenu
>
</
DropdownMenu
>
</
div
>
</
div
>
)
)
}
}
web/default/src/features/pricing/components/dynamic-pricing-breakdown.tsx
View file @
eb863116
...
@@ -322,13 +322,11 @@ export function DynamicPricingBreakdown({
...
@@ -322,13 +322,11 @@ export function DynamicPricingBreakdown({
className
:
'text-muted-foreground py-2 font-medium'
,
className
:
'text-muted-foreground py-2 font-medium'
,
cellClassName
:
'py-2.5 align-top'
,
cellClassName
:
'py-2.5 align-top'
,
cell
:
(
tier
)
=>
{
cell
:
(
tier
)
=>
{
const
condSummary
=
formatConditionSummary
(
const
condSummary
=
formatConditionSummary
(
tier
.
conditions
,
t
)
tier
.
conditions
,
t
)
const
isMatched
=
const
isMatched
=
normalizedMatchedTierLabel
!==
''
&&
normalizedMatchedTierLabel
!==
''
&&
normalizeTierLabel
(
tier
.
label
)
===
normalizedMatchedTierLabel
normalizeTierLabel
(
tier
.
label
)
===
normalizedMatchedTierLabel
return
(
return
(
<>
<>
<
div
className=
'flex flex-wrap items-center gap-1.5'
>
<
div
className=
'flex flex-wrap items-center gap-1.5'
>
...
...
web/default/src/features/pricing/components/model-card.tsx
View file @
eb863116
...
@@ -57,9 +57,7 @@ export const ModelCard = memo(function ModelCard(props: ModelCardProps) {
...
@@ -57,9 +57,7 @@ export const ModelCard = memo(function ModelCard(props: ModelCardProps) {
const
groups
=
props
.
model
.
enable_groups
||
[]
const
groups
=
props
.
model
.
enable_groups
||
[]
const
endpoints
=
props
.
model
.
supported_endpoint_types
||
[]
const
endpoints
=
props
.
model
.
supported_endpoint_types
||
[]
const
modelIconKey
=
props
.
model
.
icon
||
props
.
model
.
vendor_icon
const
modelIconKey
=
props
.
model
.
icon
||
props
.
model
.
vendor_icon
const
modelIcon
=
modelIconKey
const
modelIcon
=
modelIconKey
?
getLobeIcon
(
modelIconKey
,
28
)
:
null
?
getLobeIcon
(
modelIconKey
,
28
)
:
null
const
initial
=
props
.
model
.
model_name
?.
charAt
(
0
).
toUpperCase
()
||
'?'
const
initial
=
props
.
model
.
model_name
?.
charAt
(
0
).
toUpperCase
()
||
'?'
const
isDynamicPricing
=
const
isDynamicPricing
=
props
.
model
.
billing_mode
===
'tiered_expr'
&&
props
.
model
.
billing_mode
===
'tiered_expr'
&&
...
...
web/default/src/features/profile/components/checkin-calendar-card.tsx
View file @
eb863116
...
@@ -32,6 +32,7 @@ import { formatQuotaWithCurrency } from '@/lib/currency'
...
@@ -32,6 +32,7 @@ import { formatQuotaWithCurrency } from '@/lib/currency'
import
dayjs
from
'@/lib/dayjs'
import
dayjs
from
'@/lib/dayjs'
import
{
cn
}
from
'@/lib/utils'
import
{
cn
}
from
'@/lib/utils'
import
{
Button
}
from
'@/components/ui/button'
import
{
Button
}
from
'@/components/ui/button'
import
{
Card
}
from
'@/components/ui/card'
import
{
Skeleton
}
from
'@/components/ui/skeleton'
import
{
Skeleton
}
from
'@/components/ui/skeleton'
import
{
import
{
Tooltip
,
Tooltip
,
...
@@ -221,7 +222,7 @@ export function CheckinCalendarCard({
...
@@ -221,7 +222,7 @@ export function CheckinCalendarCard({
if
(
isLoading
)
{
if
(
isLoading
)
{
return
(
return
(
<
div
className=
'bg-card overflow-hidden rounded-2xl border
'
>
<
Card
data
-
card
-
hover=
'false'
className=
'gap-0 overflow-hidden py-0
'
>
<
div
className=
'p-6'
>
<
div
className=
'p-6'
>
<
div
className=
'flex items-start justify-between gap-4'
>
<
div
className=
'flex items-start justify-between gap-4'
>
<
div
className=
'flex items-center gap-3'
>
<
div
className=
'flex items-center gap-3'
>
...
@@ -234,7 +235,7 @@ export function CheckinCalendarCard({
...
@@ -234,7 +235,7 @@ export function CheckinCalendarCard({
<
Skeleton
className=
'h-9 w-28 rounded-md'
/>
<
Skeleton
className=
'h-9 w-28 rounded-md'
/>
</
div
>
</
div
>
</
div
>
</
div
>
</
div
>
</
Card
>
)
)
}
}
...
@@ -270,14 +271,13 @@ export function CheckinCalendarCard({
...
@@ -270,14 +271,13 @@ export function CheckinCalendarCard({
</
div
>
</
div
>
</
Dialog
>
</
Dialog
>
<
div
className=
'bg-card overflow-hidden rounded-2xl border
'
>
<
Card
data
-
card
-
hover=
'false'
className=
'gap-0 overflow-hidden py-0
'
>
{
/* Header */
}
{
/* Header */
}
<
div
className=
'border-b p-4 sm:p-6'
>
<
div
className=
'border-b p-4 sm:p-6'
>
<
div
className=
'flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between sm:gap-4'
>
<
div
className=
'flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between sm:gap-4'
>
<
B
utton
<
b
utton
type=
'button'
type=
'button'
variant=
'ghost'
className=
'flex min-w-0 flex-1 items-start gap-3 rounded-lg text-left whitespace-normal outline-none'
className=
'flex h-auto min-w-0 flex-1 items-start gap-3 p-0 text-left whitespace-normal hover:bg-transparent'
onClick=
{
()
=>
setCollapsed
((
v
)
=>
!
v
)
}
onClick=
{
()
=>
setCollapsed
((
v
)
=>
!
v
)
}
>
>
<
div
className=
'bg-primary/10 text-primary flex h-10 w-10 shrink-0 items-center justify-center rounded-xl sm:h-11 sm:w-11'
>
<
div
className=
'bg-primary/10 text-primary flex h-10 w-10 shrink-0 items-center justify-center rounded-xl sm:h-11 sm:w-11'
>
...
@@ -311,7 +311,7 @@ export function CheckinCalendarCard({
...
@@ -311,7 +311,7 @@ export function CheckinCalendarCard({
:
t
(
'Check in daily to receive random quota rewards'
)
}
:
t
(
'Check in daily to receive random quota rewards'
)
}
</
p
>
</
p
>
</
div
>
</
div
>
</
B
utton
>
</
b
utton
>
<
Button
<
Button
onClick=
{
()
=>
doCheckin
()
}
onClick=
{
()
=>
doCheckin
()
}
disabled=
{
checkinLoading
||
checkedToday
}
disabled=
{
checkinLoading
||
checkedToday
}
...
@@ -423,7 +423,6 @@ export function CheckinCalendarCard({
...
@@ -423,7 +423,6 @@ export function CheckinCalendarCard({
'relative flex h-9 w-full flex-col items-center justify-center rounded-lg px-0 text-xs font-medium sm:h-10 sm:text-sm'
,
'relative flex h-9 w-full flex-col items-center justify-center rounded-lg px-0 text-xs font-medium sm:h-10 sm:text-sm'
,
!
dayObj
.
isCurrentMonth
&&
!
dayObj
.
isCurrentMonth
&&
'text-muted-foreground/40 cursor-default'
,
'text-muted-foreground/40 cursor-default'
,
isToday
&&
'hover:bg-primary/90'
,
!
isToday
&&
isCheckedIn
&&
'font-semibold'
!
isToday
&&
isCheckedIn
&&
'font-semibold'
)
}
)
}
>
>
...
@@ -476,7 +475,7 @@ export function CheckinCalendarCard({
...
@@ -476,7 +475,7 @@ export function CheckinCalendarCard({
</
div
>
</
div
>
</>
</>
)
:
null
}
)
:
null
}
</
div
>
</
Card
>
</
TooltipProvider
>
</
TooltipProvider
>
)
)
}
}
web/default/src/features/profile/components/language-preferences-card.tsx
View file @
eb863116
...
@@ -105,6 +105,7 @@ export function LanguagePreferencesCard(props: LanguagePreferencesCardProps) {
...
@@ -105,6 +105,7 @@ export function LanguagePreferencesCard(props: LanguagePreferencesCardProps) {
title=
{
t
(
'Language Preferences'
)
}
title=
{
t
(
'Language Preferences'
)
}
description=
{
t
(
'Set the language used across the interface'
)
}
description=
{
t
(
'Set the language used across the interface'
)
}
icon=
{
<
Languages
className=
'h-4 w-4'
/>
}
icon=
{
<
Languages
className=
'h-4 w-4'
/>
}
disableHoverEffect
>
>
<
div
className=
'flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between sm:gap-4'
>
<
div
className=
'flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between sm:gap-4'
>
<
div
className=
'space-y-1'
>
<
div
className=
'space-y-1'
>
...
...
web/default/src/features/profile/components/passkey-card.tsx
View file @
eb863116
...
@@ -187,7 +187,7 @@ export function PasskeyCard({ loading: pageLoading }: PasskeyCardProps) {
...
@@ -187,7 +187,7 @@ export function PasskeyCard({ loading: pageLoading }: PasskeyCardProps) {
if
(
pageLoading
||
loading
)
{
if
(
pageLoading
||
loading
)
{
return
(
return
(
<
Card
className=
'gap-0 overflow-hidden py-0'
>
<
Card
data
-
card
-
hover=
'false'
className=
'gap-0 overflow-hidden py-0'
>
<
CardHeader
className=
'p-3 sm:p-5'
>
<
CardHeader
className=
'p-3 sm:p-5'
>
<
Skeleton
className=
'h-6 w-48'
/>
<
Skeleton
className=
'h-6 w-48'
/>
<
Skeleton
className=
'mt-2 h-4 w-64'
/>
<
Skeleton
className=
'mt-2 h-4 w-64'
/>
...
@@ -208,7 +208,7 @@ export function PasskeyCard({ loading: pageLoading }: PasskeyCardProps) {
...
@@ -208,7 +208,7 @@ export function PasskeyCard({ loading: pageLoading }: PasskeyCardProps) {
return
(
return
(
<>
<>
<
Card
className=
'gap-0 overflow-hidden py-0'
>
<
Card
data
-
card
-
hover=
'false'
className=
'gap-0 overflow-hidden py-0'
>
<
CardHeader
className=
'p-3 sm:p-5'
>
<
CardHeader
className=
'p-3 sm:p-5'
>
<
CardTitle
className=
'text-lg tracking-tight sm:text-xl'
>
<
CardTitle
className=
'text-lg tracking-tight sm:text-xl'
>
{
t
(
'Passkey Login'
)
}
{
t
(
'Passkey Login'
)
}
...
@@ -310,7 +310,7 @@ export function PasskeyCard({ loading: pageLoading }: PasskeyCardProps) {
...
@@ -310,7 +310,7 @@ export function PasskeyCard({ loading: pageLoading }: PasskeyCardProps) {
{
t
(
'Cancel'
)
}
{
t
(
'Cancel'
)
}
</
AlertDialogCancel
>
</
AlertDialogCancel
>
<
AlertDialogAction
<
AlertDialogAction
className=
'bg-destructive text-destructive-foreground
hover:bg-destructive/90
'
className=
'bg-destructive text-destructive-foreground'
disabled=
{
removing
}
disabled=
{
removing
}
onClick=
{
(
event
)
=>
{
onClick=
{
(
event
)
=>
{
event
.
preventDefault
()
event
.
preventDefault
()
...
...
web/default/src/features/profile/components/profile-header.tsx
View file @
eb863116
...
@@ -18,12 +18,14 @@ For commercial licensing, please contact support@quantumnous.com
...
@@ -18,12 +18,14 @@ For commercial licensing, please contact support@quantumnous.com
*/
*/
import
{
Activity
,
BarChart3
,
WalletCards
}
from
'lucide-react'
import
{
Activity
,
BarChart3
,
WalletCards
}
from
'lucide-react'
import
{
useTranslation
}
from
'react-i18next'
import
{
useTranslation
}
from
'react-i18next'
import
{
getUserAvatarFallback
,
getUserAvatarStyle
}
from
'@/lib/avatar'
import
{
formatCompactNumber
,
formatQuota
}
from
'@/lib/format'
import
{
formatCompactNumber
,
formatQuota
}
from
'@/lib/format'
import
{
getRoleLabel
}
from
'@/lib/roles'
import
{
getRoleLabel
}
from
'@/lib/roles'
import
{
Avatar
,
AvatarFallback
}
from
'@/components/ui/avatar'
import
{
Avatar
,
AvatarFallback
}
from
'@/components/ui/avatar'
import
{
Card
,
CardContent
}
from
'@/components/ui/card'
import
{
Skeleton
}
from
'@/components/ui/skeleton'
import
{
Skeleton
}
from
'@/components/ui/skeleton'
import
{
StatusBadge
}
from
'@/components/status-badge'
import
{
StatusBadge
}
from
'@/components/status-badge'
import
{
get
UserInitials
,
get
DisplayName
}
from
'../lib'
import
{
getDisplayName
}
from
'../lib'
import
type
{
UserProfile
}
from
'../types'
import
type
{
UserProfile
}
from
'../types'
// ============================================================================
// ============================================================================
...
@@ -40,8 +42,8 @@ export function ProfileHeader({ profile, loading }: ProfileHeaderProps) {
...
@@ -40,8 +42,8 @@ export function ProfileHeader({ profile, loading }: ProfileHeaderProps) {
if
(
loading
)
{
if
(
loading
)
{
return
(
return
(
<
div
className=
'bg-card overflow-hidden rounded-lg border
'
>
<
Card
data
-
card
-
hover=
'false'
className=
'gap-0 overflow-hidden py-0
'
>
<
div
className=
'p-4 sm:p-5'
>
<
CardContent
className=
'p-4 sm:p-5'
>
<
div
className=
'flex flex-col items-center gap-4 text-center sm:flex-row sm:text-left'
>
<
div
className=
'flex flex-col items-center gap-4 text-center sm:flex-row sm:text-left'
>
<
Skeleton
className=
'h-16 w-16 rounded-2xl'
/>
<
Skeleton
className=
'h-16 w-16 rounded-2xl'
/>
<
div
className=
'space-y-3'
>
<
div
className=
'space-y-3'
>
...
@@ -56,7 +58,7 @@ export function ProfileHeader({ profile, loading }: ProfileHeaderProps) {
...
@@ -56,7 +58,7 @@ export function ProfileHeader({ profile, loading }: ProfileHeaderProps) {
</
div
>
</
div
>
</
div
>
</
div
>
</
div
>
</
div
>
</
div
>
</
CardContent
>
<
div
className=
'border-t'
>
<
div
className=
'border-t'
>
<
div
className=
'divide-border/60 grid grid-cols-1 divide-y sm:grid-cols-3 sm:divide-x sm:divide-y-0'
>
<
div
className=
'divide-border/60 grid grid-cols-1 divide-y sm:grid-cols-3 sm:divide-x sm:divide-y-0'
>
{
Array
.
from
({
length
:
3
}).
map
((
_
,
i
)
=>
(
{
Array
.
from
({
length
:
3
}).
map
((
_
,
i
)
=>
(
...
@@ -68,14 +70,16 @@ export function ProfileHeader({ profile, loading }: ProfileHeaderProps) {
...
@@ -68,14 +70,16 @@ export function ProfileHeader({ profile, loading }: ProfileHeaderProps) {
))
}
))
}
</
div
>
</
div
>
</
div
>
</
div
>
</
div
>
</
Card
>
)
)
}
}
if
(
!
profile
)
return
null
if
(
!
profile
)
return
null
const
displayName
=
getDisplayName
(
profile
)
const
displayName
=
getDisplayName
(
profile
)
const
initials
=
getUserInitials
(
profile
)
const
avatarName
=
profile
.
username
||
displayName
const
avatarFallback
=
getUserAvatarFallback
(
avatarName
)
const
avatarFallbackStyle
=
getUserAvatarStyle
(
avatarName
)
const
roleLabel
=
getRoleLabel
(
profile
.
role
)
const
roleLabel
=
getRoleLabel
(
profile
.
role
)
const
stats
=
[
const
stats
=
[
{
{
...
@@ -99,12 +103,15 @@ export function ProfileHeader({ profile, loading }: ProfileHeaderProps) {
...
@@ -99,12 +103,15 @@ export function ProfileHeader({ profile, loading }: ProfileHeaderProps) {
]
]
return
(
return
(
<
div
className=
'bg-card overflow-hidden rounded-lg border
'
>
<
Card
data
-
card
-
hover=
'false'
className=
'gap-0 overflow-hidden py-0
'
>
<
div
className=
'p-3 sm:p-5'
>
<
CardContent
className=
'p-3 sm:p-5'
>
<
div
className=
'flex items-center gap-3 text-left sm:gap-4'
>
<
div
className=
'flex items-center gap-3 text-left sm:gap-4'
>
<
Avatar
className=
'ring-background h-12 w-12 rounded-xl text-sm ring-2 sm:h-16 sm:w-16 sm:rounded-2xl sm:text-lg sm:ring-4'
>
<
Avatar
className=
'ring-background h-12 w-12 rounded-xl text-sm ring-2 sm:h-16 sm:w-16 sm:rounded-2xl sm:text-lg sm:ring-4'
>
<
AvatarFallback
className=
'bg-primary/10 text-primary rounded-xl sm:rounded-2xl'
>
<
AvatarFallback
{
initials
}
className=
'rounded-xl font-semibold text-white sm:rounded-2xl'
style=
{
avatarFallbackStyle
}
>
{
avatarFallback
}
</
AvatarFallback
>
</
AvatarFallback
>
</
Avatar
>
</
Avatar
>
...
@@ -142,7 +149,7 @@ export function ProfileHeader({ profile, loading }: ProfileHeaderProps) {
...
@@ -142,7 +149,7 @@ export function ProfileHeader({ profile, loading }: ProfileHeaderProps) {
</
div
>
</
div
>
</
div
>
</
div
>
</
div
>
</
div
>
</
div
>
</
CardContent
>
<
div
className=
'border-t'
>
<
div
className=
'border-t'
>
<
div
className=
'divide-border/60 grid grid-cols-3 divide-x'
>
<
div
className=
'divide-border/60 grid grid-cols-3 divide-x'
>
{
stats
.
map
((
item
)
=>
(
{
stats
.
map
((
item
)
=>
(
...
@@ -164,6 +171,6 @@ export function ProfileHeader({ profile, loading }: ProfileHeaderProps) {
...
@@ -164,6 +171,6 @@ export function ProfileHeader({ profile, loading }: ProfileHeaderProps) {
))
}
))
}
</
div
>
</
div
>
</
div
>
</
div
>
</
div
>
</
Card
>
)
)
}
}
web/default/src/features/profile/components/profile-security-card.tsx
View file @
eb863116
...
@@ -47,7 +47,7 @@ export function ProfileSecurityCard({
...
@@ -47,7 +47,7 @@ export function ProfileSecurityCard({
if
(
loading
)
{
if
(
loading
)
{
return
(
return
(
<
Card
className=
'gap-0 overflow-hidden py-0'
>
<
Card
data
-
card
-
hover=
'false'
className=
'gap-0 overflow-hidden py-0'
>
<
CardHeader
className=
'border-b p-3 !pb-3 sm:p-5 sm:!pb-5'
>
<
CardHeader
className=
'border-b p-3 !pb-3 sm:p-5 sm:!pb-5'
>
<
Skeleton
className=
'h-6 w-32'
/>
<
Skeleton
className=
'h-6 w-32'
/>
<
Skeleton
className=
'mt-2 h-4 w-48'
/>
<
Skeleton
className=
'mt-2 h-4 w-48'
/>
...
@@ -93,6 +93,7 @@ export function ProfileSecurityCard({
...
@@ -93,6 +93,7 @@ export function ProfileSecurityCard({
title=
{
t
(
'Security'
)
}
title=
{
t
(
'Security'
)
}
description=
{
t
(
'Manage your security settings and account access'
)
}
description=
{
t
(
'Manage your security settings and account access'
)
}
icon=
{
<
Shield
className=
'h-4 w-4'
/>
}
icon=
{
<
Shield
className=
'h-4 w-4'
/>
}
disableHoverEffect
>
>
<
div
className=
'grid grid-cols-1 gap-2.5 sm:gap-3 md:grid-cols-3'
>
<
div
className=
'grid grid-cols-1 gap-2.5 sm:gap-3 md:grid-cols-3'
>
{
securityActions
.
map
((
item
)
=>
(
{
securityActions
.
map
((
item
)
=>
(
...
@@ -100,10 +101,8 @@ export function ProfileSecurityCard({
...
@@ -100,10 +101,8 @@ export function ProfileSecurityCard({
key=
{
item
.
title
}
key=
{
item
.
title
}
type=
'button'
type=
'button'
onClick=
{
item
.
action
}
onClick=
{
item
.
action
}
className=
{
`hover:bg-muted/50 flex items-center gap-3 rounded-lg border p-3 text-left transition-colors md:flex-col md:gap-2 md:p-4 md:text-center ${
className=
{
`flex items-center gap-3 rounded-lg border p-3 text-left md:flex-col md:gap-2 md:p-4 md:text-center ${
item.variant === 'destructive'
item.variant === 'destructive' ? 'border-destructive/30' : ''
? 'border-destructive/30 hover:border-destructive/50 hover:bg-destructive/5'
: ''
}`
}
}`
}
>
>
<
div
<
div
...
...
web/default/src/features/profile/components/profile-settings-card.tsx
View file @
eb863116
...
@@ -47,7 +47,7 @@ export function ProfileSettingsCard({
...
@@ -47,7 +47,7 @@ export function ProfileSettingsCard({
if
(
loading
)
{
if
(
loading
)
{
return
(
return
(
<
Card
className=
'gap-0 overflow-hidden py-0'
>
<
Card
data
-
card
-
hover=
'false'
className=
'gap-0 overflow-hidden py-0'
>
<
CardHeader
className=
'border-b p-3 !pb-3 sm:p-5 sm:!pb-5'
>
<
CardHeader
className=
'border-b p-3 !pb-3 sm:p-5 sm:!pb-5'
>
<
Skeleton
className=
'h-6 w-32'
/>
<
Skeleton
className=
'h-6 w-32'
/>
<
Skeleton
className=
'mt-2 h-4 w-48'
/>
<
Skeleton
className=
'mt-2 h-4 w-48'
/>
...
@@ -67,6 +67,7 @@ export function ProfileSettingsCard({
...
@@ -67,6 +67,7 @@ export function ProfileSettingsCard({
title=
{
t
(
'Settings'
)
}
title=
{
t
(
'Settings'
)
}
description=
{
t
(
'Configure your account preferences and integrations'
)
}
description=
{
t
(
'Configure your account preferences and integrations'
)
}
icon=
{
<
Settings
className=
'h-4 w-4'
/>
}
icon=
{
<
Settings
className=
'h-4 w-4'
/>
}
disableHoverEffect
>
>
<
Tabs
value=
{
activeTab
}
onValueChange=
{
setActiveTab
}
>
<
Tabs
value=
{
activeTab
}
onValueChange=
{
setActiveTab
}
>
<
TabsList
className=
'grid w-full grid-cols-2 items-stretch gap-1 rounded-xl p-1 group-data-horizontal/tabs:h-10'
>
<
TabsList
className=
'grid w-full grid-cols-2 items-stretch gap-1 rounded-xl p-1 group-data-horizontal/tabs:h-10'
>
...
...
web/default/src/features/profile/components/sidebar-modules-card.tsx
View file @
eb863116
...
@@ -200,7 +200,7 @@ export function SidebarModulesCard() {
...
@@ -200,7 +200,7 @@ export function SidebarModulesCard() {
}
}
return
(
return
(
<
Card
className=
'gap-0 overflow-hidden py-0'
>
<
Card
data
-
card
-
hover=
'false'
className=
'gap-0 overflow-hidden py-0'
>
<
CardHeader
className=
'border-b p-3 !pb-3 sm:p-5 sm:!pb-5'
>
<
CardHeader
className=
'border-b p-3 !pb-3 sm:p-5 sm:!pb-5'
>
<
div
className=
'flex items-center gap-3'
>
<
div
className=
'flex items-center gap-3'
>
<
div
className=
'bg-muted flex h-8 w-8 shrink-0 items-center justify-center rounded-lg sm:h-9 sm:w-9'
>
<
div
className=
'bg-muted flex h-8 w-8 shrink-0 items-center justify-center rounded-lg sm:h-9 sm:w-9'
>
...
@@ -240,7 +240,7 @@ export function SidebarModulesCard() {
...
@@ -240,7 +240,7 @@ export function SidebarModulesCard() {
{
section
.
modules
.
map
((
mod
)
=>
(
{
section
.
modules
.
map
((
mod
)
=>
(
<
div
<
div
key=
{
mod
.
key
}
key=
{
mod
.
key
}
className=
{
`flex min-h-16 items-center justify-between rounded-lg border p-3
transition-opacity
${
className=
{
`flex min-h-16 items-center justify-between rounded-lg border p-3 ${
sectionEnabled ? '' : 'opacity-50'
sectionEnabled ? '' : 'opacity-50'
}`
}
}`
}
>
>
...
...
web/default/src/features/profile/components/tabs/account-bindings-tab.tsx
View file @
eb863116
...
@@ -350,7 +350,7 @@ export function AccountBindingsTab({
...
@@ -350,7 +350,7 @@ export function AccountBindingsTab({
<
Button
<
Button
variant=
'ghost'
variant=
'ghost'
size=
'sm'
size=
'sm'
className=
'text-destructive h
over:text-destructive h
-7 shrink-0 px-2.5 text-xs'
className=
'text-destructive h-7 shrink-0 px-2.5 text-xs'
onClick=
{
()
=>
setUnbindTarget
(
binding
)
}
onClick=
{
()
=>
setUnbindTarget
(
binding
)
}
>
>
<
Unlink
className=
'mr-1 h-3 w-3'
/>
<
Unlink
className=
'mr-1 h-3 w-3'
/>
...
...
web/default/src/features/profile/components/tabs/notification-tab.tsx
View file @
eb863116
...
@@ -24,8 +24,8 @@ import { ROLE } from '@/lib/roles'
...
@@ -24,8 +24,8 @@ import { ROLE } from '@/lib/roles'
import
{
Button
}
from
'@/components/ui/button'
import
{
Button
}
from
'@/components/ui/button'
import
{
Input
}
from
'@/components/ui/input'
import
{
Input
}
from
'@/components/ui/input'
import
{
Label
}
from
'@/components/ui/label'
import
{
Label
}
from
'@/components/ui/label'
import
{
RadioGroup
,
RadioGroupItem
}
from
'@/components/ui/radio-group'
import
{
Switch
}
from
'@/components/ui/switch'
import
{
Switch
}
from
'@/components/ui/switch'
import
{
ToggleGroup
,
ToggleGroupItem
}
from
'@/components/ui/toggle-group'
import
{
PasswordInput
}
from
'@/components/password-input'
import
{
PasswordInput
}
from
'@/components/password-input'
import
{
updateUserSettings
}
from
'../../api'
import
{
updateUserSettings
}
from
'../../api'
import
{
import
{
...
@@ -35,13 +35,24 @@ import {
...
@@ -35,13 +35,24 @@ import {
import
{
parseUserSettings
}
from
'../../lib'
import
{
parseUserSettings
}
from
'../../lib'
import
type
{
UserProfile
,
UserSettings
,
NotifyType
}
from
'../../types'
import
type
{
UserProfile
,
UserSettings
,
NotifyType
}
from
'../../types'
const
NOTIFICATION_ICONS
:
Record
<
string
,
typeof
Mail
>
=
{
const
NOTIFICATION_ICONS
:
Record
<
NotifyType
,
typeof
Mail
>
=
{
email
:
Mail
,
email
:
Mail
,
webhook
:
Webhook
,
webhook
:
Webhook
,
bark
:
Bell
,
bark
:
Bell
,
gotify
:
Server
,
gotify
:
Server
,
}
}
const
NOTIFICATION_VALUES
=
new
Set
<
NotifyType
>
(
NOTIFICATION_METHODS
.
map
((
method
)
=>
method
.
value
)
)
function
normalizeNotifyType
(
value
:
unknown
):
NotifyType
{
return
typeof
value
===
'string'
&&
NOTIFICATION_VALUES
.
has
(
value
as
NotifyType
)
?
(
value
as
NotifyType
)
:
'email'
}
// ============================================================================
// ============================================================================
// Settings Tab Component
// Settings Tab Component
// ============================================================================
// ============================================================================
...
@@ -82,7 +93,7 @@ export function NotificationTab({ profile, onUpdate }: NotificationTabProps) {
...
@@ -82,7 +93,7 @@ export function NotificationTab({ profile, onUpdate }: NotificationTabProps) {
if
(
profile
?.
setting
)
{
if
(
profile
?.
setting
)
{
const
parsed
=
parseUserSettings
(
profile
.
setting
)
const
parsed
=
parseUserSettings
(
profile
.
setting
)
setSettings
({
setSettings
({
notify_type
:
parsed
.
notify_type
||
'email'
,
notify_type
:
normalizeNotifyType
(
parsed
.
notify_type
)
,
quota_warning_threshold
:
quota_warning_threshold
:
parsed
.
quota_warning_threshold
??
DEFAULT_QUOTA_WARNING_THRESHOLD
,
parsed
.
quota_warning_threshold
??
DEFAULT_QUOTA_WARNING_THRESHOLD
,
notification_email
:
parsed
.
notification_email
??
''
,
notification_email
:
parsed
.
notification_email
??
''
,
...
@@ -119,44 +130,42 @@ export function NotificationTab({ profile, onUpdate }: NotificationTabProps) {
...
@@ -119,44 +130,42 @@ export function NotificationTab({ profile, onUpdate }: NotificationTabProps) {
}
}
}
}
const notifyType = normalizeNotifyType(settings.notify_type)
return (
return (
<
div
className=
'space-y-4 sm:space-y-6'
>
<
div
className=
'space-y-4 sm:space-y-6'
>
{
/* Notification Type */
}
{
/* Notification Type */
}
<
div
className=
'space-y-2.5'
>
<
div
className=
'space-y-2.5'
>
<
Label
>
{
t
(
'Notification Method'
)
}
</
Label
>
<
Label
>
{
t
(
'Notification Method'
)
}
</
Label
>
<
RadioGroup
<
ToggleGroup
value=
{
settings
.
notify_type
}
value=
{
[
notifyType
]
}
onValueChange=
{
(
value
)
=>
onValueChange=
{
(
value
)
=>
{
updateField
(
'notify_type'
,
value
as
NotifyType
)
const
nextValue
=
value
.
find
((
item
)
=>
item
!==
notifyType
)
}
if
(
nextValue
)
className=
'grid grid-cols-4 gap-1.5 sm:gap-3'
updateField
(
'notify_type'
,
normalizeNotifyType
(
nextValue
))
}
}
aria
-
label=
{
t
(
'Notification Method'
)
}
variant=
'outline'
size=
'lg'
spacing=
{
2
}
className=
'grid w-full grid-cols-2 gap-2 sm:grid-cols-4 sm:gap-3'
>
>
{
NOTIFICATION_METHODS
.
map
((
method
)
=>
{
{
NOTIFICATION_METHODS
.
map
((
method
)
=>
{
const
Icon
=
NOTIFICATION_ICONS
[
method
.
value
]
const
Icon
=
NOTIFICATION_ICONS
[
method
.
value
]
const
isSelected
=
settings
.
notify_type
===
method
.
value
return
(
return
(
<
Label
<
ToggleGroupItem
key=
{
method
.
value
}
key=
{
method
.
value
}
htmlFor=
{
method
.
value
}
value=
{
method
.
value
}
className=
{
`flex min-h-16 cursor-pointer flex-col items-center justify-center gap-1.5 rounded-lg border p-2 text-center transition-colors sm:min-h-20 sm:gap-2 sm:border-2 sm:p-3 ${
className=
'h-auto min-h-14 w-full flex-col gap-1.5 px-3 py-3 sm:min-h-16'
isSelected
? 'border-primary bg-primary/5 text-primary'
: 'border-muted hover:border-muted-foreground/25 hover:bg-muted/50'
}`
}
>
>
<
RadioGroupItem
value=
{
method
.
value
}
id=
{
method
.
value
}
className=
'sr-only'
/>
<
Icon
className=
'h-4 w-4 sm:h-5 sm:w-5'
/>
<
Icon
className=
'h-4 w-4 sm:h-5 sm:w-5'
/>
<
span
className=
'max-w-full truncate text-xs font-medium sm:text-sm'
>
<
span
className=
'max-w-full truncate text-xs font-medium sm:text-sm'
>
{
t
(
method
.
label
)
}
{
t
(
method
.
label
)
}
</
span
>
</
span
>
</
Label
>
</
ToggleGroupItem
>
)
)
})
}
})
}
</
Radio
Group
>
</
Toggle
Group
>
</
div
>
</
div
>
{
/* Warning Threshold */
}
{
/* Warning Threshold */
}
...
@@ -178,7 +187,7 @@ export function NotificationTab({ profile, onUpdate }: NotificationTabProps) {
...
@@ -178,7 +187,7 @@ export function NotificationTab({ profile, onUpdate }: NotificationTabProps) {
</
div
>
</
div
>
{
/* Email Settings */
}
{
/* Email Settings */
}
{
settings
.
notify_t
ype
===
'email'
&&
(
{
notifyT
ype
===
'email'
&&
(
<
div
className=
'space-y-1.5'
>
<
div
className=
'space-y-1.5'
>
<
Label
htmlFor=
'notifyEmail'
>
{
t
(
'Notification Email'
)
}
</
Label
>
<
Label
htmlFor=
'notifyEmail'
>
{
t
(
'Notification Email'
)
}
</
Label
>
<
Input
<
Input
...
@@ -193,7 +202,7 @@ export function NotificationTab({ profile, onUpdate }: NotificationTabProps) {
...
@@ -193,7 +202,7 @@ export function NotificationTab({ profile, onUpdate }: NotificationTabProps) {
)
}
)
}
{
/* Webhook Settings */
}
{
/* Webhook Settings */
}
{
settings
.
notify_t
ype
===
'webhook'
&&
(
{
notifyT
ype
===
'webhook'
&&
(
<>
<>
<
div
className=
'space-y-1.5'
>
<
div
className=
'space-y-1.5'
>
<
Label
htmlFor=
'webhookUrl'
>
{
t
(
'Webhook URL'
)
}
</
Label
>
<
Label
htmlFor=
'webhookUrl'
>
{
t
(
'Webhook URL'
)
}
</
Label
>
...
@@ -219,7 +228,7 @@ export function NotificationTab({ profile, onUpdate }: NotificationTabProps) {
...
@@ -219,7 +228,7 @@ export function NotificationTab({ profile, onUpdate }: NotificationTabProps) {
)
}
)
}
{
/* Bark Settings */
}
{
/* Bark Settings */
}
{
settings
.
notify_t
ype
===
'bark'
&&
(
{
notifyT
ype
===
'bark'
&&
(
<
div
className=
'space-y-1.5'
>
<
div
className=
'space-y-1.5'
>
<
Label
htmlFor=
'barkUrl'
>
{
t
(
'Bark Push URL'
)
}
</
Label
>
<
Label
htmlFor=
'barkUrl'
>
{
t
(
'Bark Push URL'
)
}
</
Label
>
<
Input
<
Input
...
@@ -237,7 +246,7 @@ export function NotificationTab({ profile, onUpdate }: NotificationTabProps) {
...
@@ -237,7 +246,7 @@ export function NotificationTab({ profile, onUpdate }: NotificationTabProps) {
)
}
)
}
{
/* Gotify Settings */
}
{
/* Gotify Settings */
}
{
settings
.
notify_t
ype
===
'gotify'
&&
(
{
notifyT
ype
===
'gotify'
&&
(
<>
<>
<
div
className=
'space-y-1.5'
>
<
div
className=
'space-y-1.5'
>
<
Label
htmlFor=
'gotifyUrl'
>
{
t
(
'Gotify Server URL'
)
}
</
Label
>
<
Label
htmlFor=
'gotifyUrl'
>
{
t
(
'Gotify Server URL'
)
}
</
Label
>
...
@@ -300,7 +309,7 @@ export function NotificationTab({ profile, onUpdate }: NotificationTabProps) {
...
@@ -300,7 +309,7 @@ export function NotificationTab({ profile, onUpdate }: NotificationTabProps) {
href=
'https://gotify.net/'
href=
'https://gotify.net/'
target=
'_blank'
target=
'_blank'
rel=
'noopener noreferrer'
rel=
'noopener noreferrer'
className=
'text-primary
hover:underline
'
className=
'text-primary
underline underline-offset-4
'
>
>
{
t
(
'Gotify Documentation'
)
}
{
t
(
'Gotify Documentation'
)
}
</
a
>
</
a
>
...
...
web/default/src/features/profile/components/two-fa-card.tsx
View file @
eb863116
...
@@ -51,7 +51,7 @@ export function TwoFACard({ loading: pageLoading }: TwoFACardProps) {
...
@@ -51,7 +51,7 @@ export function TwoFACard({ loading: pageLoading }: TwoFACardProps) {
if
(
pageLoading
||
loading
)
{
if
(
pageLoading
||
loading
)
{
return
(
return
(
<
Card
className=
'gap-0 overflow-hidden py-0'
>
<
Card
data
-
card
-
hover=
'false'
className=
'gap-0 overflow-hidden py-0'
>
<
CardHeader
className=
'p-3 sm:p-5'
>
<
CardHeader
className=
'p-3 sm:p-5'
>
<
Skeleton
className=
'h-6 w-48'
/>
<
Skeleton
className=
'h-6 w-48'
/>
<
Skeleton
className=
'mt-2 h-4 w-64'
/>
<
Skeleton
className=
'mt-2 h-4 w-64'
/>
...
@@ -65,7 +65,7 @@ export function TwoFACard({ loading: pageLoading }: TwoFACardProps) {
...
@@ -65,7 +65,7 @@ export function TwoFACard({ loading: pageLoading }: TwoFACardProps) {
return
(
return
(
<>
<>
<
Card
className=
'gap-0 overflow-hidden py-0'
>
<
Card
data
-
card
-
hover=
'false'
className=
'gap-0 overflow-hidden py-0'
>
<
CardHeader
className=
'p-3 sm:p-5'
>
<
CardHeader
className=
'p-3 sm:p-5'
>
<
CardTitle
className=
'text-lg tracking-tight sm:text-xl'
>
<
CardTitle
className=
'text-lg tracking-tight sm:text-xl'
>
{
t
(
'Two-Factor Authentication'
)
}
{
t
(
'Two-Factor Authentication'
)
}
...
...
web/default/src/features/redemption-codes/components/data-table-row-actions.tsx
View file @
eb863116
...
@@ -79,64 +79,64 @@ export function DataTableRowActions<TData>({
...
@@ -79,64 +79,64 @@ export function DataTableRowActions<TData>({
return
(
return
(
<
div
className=
'-ml-2'
>
<
div
className=
'-ml-2'
>
<
DropdownMenu
modal=
{
false
}
>
<
DropdownMenu
modal=
{
false
}
>
<
DropdownMenuTrigger
<
DropdownMenuTrigger
render=
{
render=
{
<
Button
<
Button
variant=
'ghost'
variant=
'ghost'
className=
'data-popup-open:bg-muted flex h-8 w-8 p-0'
className=
'data-popup-open:bg-muted flex h-8 w-8 p-0'
/>
/>
}
}
>
<
DotsHorizontalIcon
className=
'h-4 w-4'
/>
<
span
className=
'sr-only'
>
{
t
(
'Open menu'
)
}
</
span
>
</
DropdownMenuTrigger
>
<
DropdownMenuContent
align=
'end'
className=
'w-[160px]'
>
<
DropdownMenuItem
onClick=
{
()
=>
{
setCurrentRow
(
redemption
)
setOpen
(
'update'
)
}
}
disabled=
{
!
canEdit
}
>
>
{
t
(
'Edit'
)
}
<
DotsHorizontalIcon
className=
'h-4 w-4'
/>
<
DropdownMenuShortcut
>
<
span
className=
'sr-only'
>
{
t
(
'Open menu'
)
}
</
span
>
<
Edit
size=
{
16
}
/>
</
DropdownMenuTrigger
>
</
DropdownMenuShortcut
>
<
DropdownMenuContent
align=
'end'
className=
'w-[160px]'
>
</
DropdownMenuItem
>
<
DropdownMenuItem
{
canToggle
&&
(
onClick=
{
()
=>
{
<
DropdownMenuItem
onClick=
{
handleToggleStatus
}
>
setCurrentRow
(
redemption
)
{
isEnabled
?
(
setOpen
(
'update'
)
<>
}
}
{
t
(
'Disable'
)
}
disabled=
{
!
canEdit
}
<
DropdownMenuShortcut
>
>
<
PowerOff
size=
{
16
}
/>
{
t
(
'Edit'
)
}
</
DropdownMenuShortcut
>
<
DropdownMenuShortcut
>
</>
<
Edit
size=
{
16
}
/>
)
:
(
</
DropdownMenuShortcut
>
<>
{
t
(
'Enable'
)
}
<
DropdownMenuShortcut
>
<
Power
size=
{
16
}
/>
</
DropdownMenuShortcut
>
</>
)
}
</
DropdownMenuItem
>
</
DropdownMenuItem
>
)
}
{
canToggle
&&
(
<
DropdownMenuSeparator
/>
<
DropdownMenuItem
onClick=
{
handleToggleStatus
}
>
<
DropdownMenuItem
{
isEnabled
?
(
onClick=
{
()
=>
{
<>
setCurrentRow
(
redemption
)
{
t
(
'Disable'
)
}
setOpen
(
'delete'
)
<
DropdownMenuShortcut
>
}
}
<
PowerOff
size=
{
16
}
/>
className=
'text-destructive focus:text-destructive'
</
DropdownMenuShortcut
>
>
</>
{
t
(
'Delete'
)
}
)
:
(
<
DropdownMenuShortcut
>
<>
<
Trash2
size=
{
16
}
/>
{
t
(
'Enable'
)
}
</
DropdownMenuShortcut
>
<
DropdownMenuShortcut
>
</
DropdownMenuItem
>
<
Power
size=
{
16
}
/>
</
DropdownMenuContent
>
</
DropdownMenuShortcut
>
</
DropdownMenu
>
</>
)
}
</
DropdownMenuItem
>
)
}
<
DropdownMenuSeparator
/>
<
DropdownMenuItem
onClick=
{
()
=>
{
setCurrentRow
(
redemption
)
setOpen
(
'delete'
)
}
}
className=
'text-destructive focus:text-destructive'
>
{
t
(
'Delete'
)
}
<
DropdownMenuShortcut
>
<
Trash2
size=
{
16
}
/>
</
DropdownMenuShortcut
>
</
DropdownMenuItem
>
</
DropdownMenuContent
>
</
DropdownMenu
>
</
div
>
</
div
>
)
)
}
}
web/default/src/features/subscriptions/components/data-table-row-actions.tsx
View file @
eb863116
...
@@ -40,43 +40,43 @@ export function DataTableRowActions({ row }: DataTableRowActionsProps) {
...
@@ -40,43 +40,43 @@ export function DataTableRowActions({ row }: DataTableRowActionsProps) {
return
(
return
(
<
div
className=
'-ml-2'
>
<
div
className=
'-ml-2'
>
<
DropdownMenu
>
<
DropdownMenu
>
<
DropdownMenuTrigger
<
DropdownMenuTrigger
render=
{
<
Button
variant=
'ghost'
className=
'h-8 w-8 p-0'
/>
}
render=
{
<
Button
variant=
'ghost'
className=
'h-8 w-8 p-0'
/>
}
>
<
MoreHorizontal
className=
'h-4 w-4'
/>
</
DropdownMenuTrigger
>
<
DropdownMenuContent
align=
'end'
>
<
DropdownMenuItem
disabled=
{
!
complianceConfirmed
}
onClick=
{
()
=>
{
setCurrentRow
(
row
.
original
)
setOpen
(
'update'
)
}
}
>
>
<
Pencil
className=
'mr-2 h-4 w-4'
/>
<
MoreHorizontal
className=
'h-4 w-4'
/>
{
t
(
'Edit'
)
}
</
DropdownMenuTrigger
>
</
DropdownMenuItem
>
<
DropdownMenuContent
align=
'end'
>
<
DropdownMenuItem
<
DropdownMenuItem
disabled=
{
!
complianceConfirmed
}
disabled=
{
!
complianceConfirmed
}
onClick=
{
()
=>
{
onClick=
{
()
=>
{
setCurrentRow
(
row
.
original
)
setCurrentRow
(
row
.
original
)
setOpen
(
'toggle-status'
)
setOpen
(
'update'
)
}
}
}
}
>
>
{
row
.
original
.
plan
.
enabled
?
(
<
Pencil
className=
'mr-2 h-4 w-4'
/>
<>
{
t
(
'Edit'
)
}
<
PowerOff
className=
'mr-2 h-4 w-4'
/>
</
DropdownMenuItem
>
{
t
(
'Disable'
)
}
<
DropdownMenuItem
</>
disabled=
{
!
complianceConfirmed
}
)
:
(
onClick=
{
()
=>
{
<>
setCurrentRow
(
row
.
original
)
<
Power
className=
'mr-2 h-4 w-4'
/>
setOpen
(
'toggle-status'
)
{
t
(
'Enable'
)
}
}
}
</>
>
)
}
{
row
.
original
.
plan
.
enabled
?
(
</
DropdownMenuItem
>
<>
</
DropdownMenuContent
>
<
PowerOff
className=
'mr-2 h-4 w-4'
/>
</
DropdownMenu
>
{
t
(
'Disable'
)
}
</>
)
:
(
<>
<
Power
className=
'mr-2 h-4 w-4'
/>
{
t
(
'Enable'
)
}
</>
)
}
</
DropdownMenuItem
>
</
DropdownMenuContent
>
</
DropdownMenu
>
</
div
>
</
div
>
)
)
}
}
web/default/src/features/system-settings/content/uptime-kuma-section.tsx
View file @
eb863116
...
@@ -307,8 +307,7 @@ export function UptimeKumaSection({ enabled, data }: UptimeKumaSectionProps) {
...
@@ -307,8 +307,7 @@ export function UptimeKumaSection({ enabled, data }: UptimeKumaSectionProps) {
{
{
id
:
'url'
,
id
:
'url'
,
header
:
t
(
'Uptime Kuma URL'
),
header
:
t
(
'Uptime Kuma URL'
),
cellClassName
:
cellClassName
:
'text-primary max-w-xs truncate font-mono text-sm'
,
'text-primary max-w-xs truncate font-mono text-sm'
,
cell
:
(
group
)
=>
group
.
url
,
cell
:
(
group
)
=>
group
.
url
,
},
},
{
{
...
...
web/default/src/features/system-settings/general/channel-affinity/index.tsx
View file @
eb863116
...
@@ -554,7 +554,9 @@ export function ChannelAffinitySection(props: Props) {
...
@@ -554,7 +554,9 @@ export function ChannelAffinitySection(props: Props) {
{
{
id
:
'model-regex'
,
id
:
'model-regex'
,
header
:
t
(
'Model Regex'
),
header
:
t
(
'Model Regex'
),
cell
:
(
rule
)
=>
<
RuleBadgeList
items=
{
rule
.
model_regex
||
[]
}
/>,
cell
:
(
rule
)
=>
(
<
RuleBadgeList
items=
{
rule
.
model_regex
||
[]
}
/>
),
},
},
{
{
id
:
'key-sources'
,
id
:
'key-sources'
,
...
...
web/default/src/features/system-settings/integrations/amount-discount-visual-editor.tsx
View file @
eb863116
...
@@ -149,9 +149,7 @@ export function AmountDiscountVisualEditor({
...
@@ -149,9 +149,7 @@ export function AmountDiscountVisualEditor({
id
:
'amount'
,
id
:
'amount'
,
header
:
t
(
'Recharge Amount'
),
header
:
t
(
'Recharge Amount'
),
cell
:
(
discount
)
=>
(
cell
:
(
discount
)
=>
(
<
span
className=
'font-mono text-sm'
>
<
span
className=
'font-mono text-sm'
>
$
{
discount
.
amount
}
</
span
>
$
{
discount
.
amount
}
</
span
>
),
),
},
},
{
{
...
...
web/default/src/features/system-settings/integrations/payment-methods-visual-editor.tsx
View file @
eb863116
...
@@ -26,9 +26,7 @@ import {
...
@@ -26,9 +26,7 @@ import {
PopoverContent
,
PopoverContent
,
PopoverTrigger
,
PopoverTrigger
,
}
from
'@/components/ui/popover'
}
from
'@/components/ui/popover'
import
{
import
{
StaticDataTable
}
from
'@/components/data-table'
StaticDataTable
,
}
from
'@/components/data-table'
import
{
safeJsonParseWithValidation
}
from
'../utils/json-parser'
import
{
safeJsonParseWithValidation
}
from
'../utils/json-parser'
import
{
isArray
}
from
'../utils/json-validators'
import
{
isArray
}
from
'../utils/json-validators'
import
{
import
{
...
...
web/default/src/features/system-settings/models/conflict-confirm-dialog.tsx
View file @
eb863116
...
@@ -27,9 +27,7 @@ import {
...
@@ -27,9 +27,7 @@ import {
AlertDialogHeader
,
AlertDialogHeader
,
AlertDialogTitle
,
AlertDialogTitle
,
}
from
'@/components/ui/alert-dialog'
}
from
'@/components/ui/alert-dialog'
import
{
import
{
StaticDataTable
}
from
'@/components/data-table'
StaticDataTable
,
}
from
'@/components/data-table'
export
type
ConflictItem
=
{
export
type
ConflictItem
=
{
channel
:
string
channel
:
string
...
@@ -86,18 +84,18 @@ export function ConflictConfirmDialog({
...
@@ -86,18 +84,18 @@ export function ConflictConfirmDialog({
id
:
'current'
,
id
:
'current'
,
header
:
t
(
'Current Billing'
),
header
:
t
(
'Current Billing'
),
cell
:
(
conflict
)
=>
(
cell
:
(
conflict
)
=>
(
<
pre
className=
'text-sm whitespace-pre-wrap'
>
<
pre
className=
'text-sm whitespace-pre-wrap'
>
{
conflict
.
current
}
{
conflict
.
current
}
</
pre
>
</
pre
>
),
),
},
},
{
{
id
:
'new'
,
id
:
'new'
,
header
:
t
(
'Change To'
),
header
:
t
(
'Change To'
),
cell
:
(
conflict
)
=>
(
cell
:
(
conflict
)
=>
(
<
pre
className=
'text-sm whitespace-pre-wrap'
>
<
pre
className=
'text-sm whitespace-pre-wrap'
>
{
conflict
.
newVal
}
{
conflict
.
newVal
}
</
pre
>
</
pre
>
),
),
},
},
]
}
]
}
...
...
web/default/src/features/system-settings/models/tool-price-settings.tsx
View file @
eb863116
...
@@ -263,11 +263,11 @@ export const ToolPriceSettings = memo(function ToolPriceSettings({
...
@@ -263,11 +263,11 @@ export const ToolPriceSettings = memo(function ToolPriceSettings({
id
:
'tool'
,
id
:
'tool'
,
header
:
t
(
'Tool identifier'
),
header
:
t
(
'Tool identifier'
),
cell
:
(
row
)
=>
(
cell
:
(
row
)
=>
(
<
Input
<
Input
value=
{
row
.
key
}
value=
{
row
.
key
}
placeholder=
'web_search_preview:gpt-4o*'
placeholder=
'web_search_preview:gpt-4o*'
onChange=
{
(
e
)
=>
updateRow
(
row
.
id
,
'key'
,
e
.
target
.
value
)
}
onChange=
{
(
e
)
=>
updateRow
(
row
.
id
,
'key'
,
e
.
target
.
value
)
}
/>
/>
),
),
},
},
{
{
...
@@ -275,15 +275,15 @@ export const ToolPriceSettings = memo(function ToolPriceSettings({
...
@@ -275,15 +275,15 @@ export const ToolPriceSettings = memo(function ToolPriceSettings({
header
:
t
(
'Price ($/1K calls)'
),
header
:
t
(
'Price ($/1K calls)'
),
className
:
'w-[200px]'
,
className
:
'w-[200px]'
,
cell
:
(
row
)
=>
(
cell
:
(
row
)
=>
(
<
Input
<
Input
type=
'number'
type=
'number'
min=
{
0
}
min=
{
0
}
step=
{
0.5
}
step=
{
0.5
}
value=
{
row
.
price
}
value=
{
row
.
price
}
onChange=
{
(
e
)
=>
onChange=
{
(
e
)
=>
updateRow
(
row
.
id
,
'price'
,
Number
(
e
.
target
.
value
)
||
0
)
updateRow
(
row
.
id
,
'price'
,
Number
(
e
.
target
.
value
)
||
0
)
}
}
/>
/>
),
),
},
},
{
{
...
@@ -292,14 +292,14 @@ export const ToolPriceSettings = memo(function ToolPriceSettings({
...
@@ -292,14 +292,14 @@ export const ToolPriceSettings = memo(function ToolPriceSettings({
className
:
'w-[80px] text-right'
,
className
:
'w-[80px] text-right'
,
cellClassName
:
'text-right'
,
cellClassName
:
'text-right'
,
cell
:
(
row
)
=>
(
cell
:
(
row
)
=>
(
<
Button
<
Button
variant=
'ghost'
variant=
'ghost'
size=
'icon'
size=
'icon'
onClick=
{
()
=>
removeRow
(
row
.
id
)
}
onClick=
{
()
=>
removeRow
(
row
.
id
)
}
aria
-
label=
{
t
(
'Delete'
)
}
aria
-
label=
{
t
(
'Delete'
)
}
>
>
<
Trash2
className=
'text-destructive h-4 w-4'
/>
<
Trash2
className=
'text-destructive h-4 w-4'
/>
</
Button
>
</
Button
>
),
),
},
},
]
}
]
}
...
...
web/default/src/features/usage-logs/components/dialogs/details-dialog.tsx
View file @
eb863116
...
@@ -487,7 +487,9 @@ export function DetailsDialog(props: DetailsDialogProps) {
...
@@ -487,7 +487,9 @@ export function DetailsDialog(props: DetailsDialogProps) {
// Channel update records which fields changed (stable field tokens); render
// Channel update records which fields changed (stable field tokens); render
// them with their localized labels for admins.
// them with their localized labels for admins.
const
changedFieldTokens
=
const
changedFieldTokens
=
isManage
&&
props
.
isAdmin
&&
Array
.
isArray
(
other
?.
op
?.
params
?.
changed_fields
)
isManage
&&
props
.
isAdmin
&&
Array
.
isArray
(
other
?.
op
?.
params
?.
changed_fields
)
?
(
other
.
op
.
params
.
changed_fields
as
string
[])
?
(
other
.
op
.
params
.
changed_fields
as
string
[])
:
[]
:
[]
const
changedFieldsText
=
changedFieldTokens
const
changedFieldsText
=
changedFieldTokens
...
...
web/default/src/features/wallet/components/affiliate-rewards-card.tsx
View file @
eb863116
...
@@ -44,7 +44,7 @@ export function AffiliateRewardsCard({
...
@@ -44,7 +44,7 @@ export function AffiliateRewardsCard({
const
{
t
}
=
useTranslation
()
const
{
t
}
=
useTranslation
()
if
(
loading
)
{
if
(
loading
)
{
return
(
return
(
<
Card
className=
'bg-muted/20 py-0'
>
<
Card
data
-
card
-
hover=
'false'
className=
'bg-muted/20 py-0'
>
<
CardContent
className=
'grid gap-4 p-3 sm:p-4 lg:grid-cols-[minmax(220px,1fr)_minmax(220px,0.72fr)_minmax(320px,1.15fr)] lg:items-center'
>
<
CardContent
className=
'grid gap-4 p-3 sm:p-4 lg:grid-cols-[minmax(220px,1fr)_minmax(220px,0.72fr)_minmax(320px,1.15fr)] lg:items-center'
>
<
div
>
<
div
>
<
Skeleton
className=
'h-5 w-32'
/>
<
Skeleton
className=
'h-5 w-32'
/>
...
@@ -60,7 +60,7 @@ export function AffiliateRewardsCard({
...
@@ -60,7 +60,7 @@ export function AffiliateRewardsCard({
const
hasRewards
=
(
user
?.
aff_quota
??
0
)
>
0
const
hasRewards
=
(
user
?.
aff_quota
??
0
)
>
0
return
(
return
(
<
Card
className=
'bg-muted/20 py-0'
>
<
Card
data
-
card
-
hover=
'false'
className=
'bg-muted/20 py-0'
>
<
CardContent
className=
'grid gap-3 p-3 sm:gap-4 sm:p-4 lg:grid-cols-[minmax(200px,1fr)_minmax(180px,0.65fr)_minmax(280px,1fr)] lg:items-center'
>
<
CardContent
className=
'grid gap-3 p-3 sm:gap-4 sm:p-4 lg:grid-cols-[minmax(200px,1fr)_minmax(180px,0.65fr)_minmax(280px,1fr)] lg:items-center'
>
<
div
className=
'flex min-w-0 items-center gap-2.5'
>
<
div
className=
'flex min-w-0 items-center gap-2.5'
>
<
div
className=
'bg-background flex size-8 shrink-0 items-center justify-center rounded-lg border'
>
<
div
className=
'bg-background flex size-8 shrink-0 items-center justify-center rounded-lg border'
>
...
...
web/default/src/features/wallet/components/creem-products-section.tsx
View file @
eb863116
...
@@ -55,7 +55,8 @@ export function CreemProductsSection({
...
@@ -55,7 +55,8 @@ export function CreemProductsSection({
{
products
.
map
((
product
)
=>
(
{
products
.
map
((
product
)
=>
(
<
Card
<
Card
key=
{
product
.
productId
}
key=
{
product
.
productId
}
className=
'hover:border-foreground/50 cursor-pointer transition-all hover:shadow-md'
data
-
card
-
hover=
'false'
className=
'cursor-pointer'
onClick=
{
()
=>
onProductSelect
(
product
)
}
onClick=
{
()
=>
onProductSelect
(
product
)
}
>
>
<
CardContent
className=
'p-3 text-center sm:p-4'
>
<
CardContent
className=
'p-3 text-center sm:p-4'
>
...
@@ -63,7 +64,7 @@ export function CreemProductsSection({
...
@@ -63,7 +64,7 @@ export function CreemProductsSection({
<
div
className=
'text-muted-foreground mb-2 text-sm'
>
<
div
className=
'text-muted-foreground mb-2 text-sm'
>
{
t
(
'Quota'
)
}
:
{
formatNumber
(
product
.
quota
)
}
{
t
(
'Quota'
)
}
:
{
formatNumber
(
product
.
quota
)
}
</
div
>
</
div
>
<
div
className=
'text-
lg font-semibold text-indigo-600
'
>
<
div
className=
'text-
primary text-lg font-semibold
'
>
{
formatCreemPrice
(
product
.
price
,
product
.
currency
)
}
{
formatCreemPrice
(
product
.
price
,
product
.
currency
)
}
</
div
>
</
div
>
</
CardContent
>
</
CardContent
>
...
...
web/default/src/features/wallet/components/dialogs/billing-history-dialog.tsx
View file @
eb863116
...
@@ -183,7 +183,7 @@ export function BillingHistoryDialog({
...
@@ -183,7 +183,7 @@ export function BillingHistoryDialog({
return
(
return
(
<
div
<
div
key=
{
record
.
id
}
key=
{
record
.
id
}
className=
'
hover:bg-muted/50 rounded-lg border p-3 transition-colors
sm:p-4'
className=
'
rounded-lg border p-3
sm:p-4'
>
>
{
/* Header Row */
}
{
/* Header Row */
}
<
div
className=
'flex items-start justify-between gap-2'
>
<
div
className=
'flex items-start justify-between gap-2'
>
...
...
web/default/src/features/wallet/components/dialogs/creem-confirm-dialog.tsx
View file @
eb863116
...
@@ -76,7 +76,7 @@ export function CreemConfirmDialog({
...
@@ -76,7 +76,7 @@ export function CreemConfirmDialog({
</
div
>
</
div
>
<
div
className=
'flex items-center justify-between'
>
<
div
className=
'flex items-center justify-between'
>
<
span
className=
'text-muted-foreground'
>
{
t
(
'Price'
)
}
</
span
>
<
span
className=
'text-muted-foreground'
>
{
t
(
'Price'
)
}
</
span
>
<
span
className=
'
font-medium text-indigo-600
'
>
<
span
className=
'
text-primary font-medium
'
>
{
formatCreemPrice
(
product
.
price
,
product
.
currency
)
}
{
formatCreemPrice
(
product
.
price
,
product
.
currency
)
}
</
span
>
</
span
>
</
div
>
</
div
>
...
...
web/default/src/features/wallet/components/recharge-form-card.tsx
View file @
eb863116
...
@@ -139,7 +139,7 @@ export function RechargeFormCard({
...
@@ -139,7 +139,7 @@ export function RechargeFormCard({
if
(
loading
)
{
if
(
loading
)
{
return
(
return
(
<
Card
className=
'gap-0 overflow-hidden py-0'
>
<
Card
data
-
card
-
hover=
'false'
className=
'gap-0 overflow-hidden py-0'
>
<
CardHeader
className=
'border-b p-3 !pb-3 sm:p-5 sm:!pb-5'
>
<
CardHeader
className=
'border-b p-3 !pb-3 sm:p-5 sm:!pb-5'
>
<
Skeleton
className=
'h-6 w-32'
/>
<
Skeleton
className=
'h-6 w-32'
/>
<
Skeleton
className=
'mt-2 h-4 w-48'
/>
<
Skeleton
className=
'mt-2 h-4 w-48'
/>
...
@@ -191,6 +191,7 @@ export function RechargeFormCard({
...
@@ -191,6 +191,7 @@ export function RechargeFormCard({
title=
{
t
(
'Add Funds'
)
}
title=
{
t
(
'Add Funds'
)
}
description=
{
t
(
'Choose an amount and payment method'
)
}
description=
{
t
(
'Choose an amount and payment method'
)
}
icon=
{
<
WalletCards
className=
'h-4 w-4'
/>
}
icon=
{
<
WalletCards
className=
'h-4 w-4'
/>
}
disableHoverEffect
action=
{
action=
{
onOpenBilling
?
(
onOpenBilling
?
(
<
Button
<
Button
...
@@ -238,7 +239,7 @@ export function RechargeFormCard({
...
@@ -238,7 +239,7 @@ export function RechargeFormCard({
key=
{
index
}
key=
{
index
}
variant=
'outline'
variant=
'outline'
className=
{
cn
(
className=
{
cn
(
'
hover:border-foreground
flex min-h-16 flex-col items-start rounded-lg px-3 py-2.5 text-left whitespace-normal sm:min-h-[72px] sm:p-4'
,
'flex min-h-16 flex-col items-start rounded-lg px-3 py-2.5 text-left whitespace-normal sm:min-h-[72px] sm:p-4'
,
selectedPreset
===
preset
.
value
selectedPreset
===
preset
.
value
?
'border-foreground bg-foreground/5 dark:border-foreground dark:bg-foreground/10'
?
'border-foreground bg-foreground/5 dark:border-foreground dark:bg-foreground/10'
:
'border-muted'
:
'border-muted'
...
...
web/default/src/features/wallet/components/subscription-plans-card.tsx
View file @
eb863116
...
@@ -235,7 +235,7 @@ export function SubscriptionPlansCard({
...
@@ -235,7 +235,7 @@ export function SubscriptionPlansCard({
if
(
loading
)
{
if
(
loading
)
{
return
(
return
(
<
Card
className=
'gap-0 overflow-hidden py-0'
>
<
Card
data
-
card
-
hover=
'false'
className=
'gap-0 overflow-hidden py-0'
>
<
CardHeader
className=
'border-b p-3 !pb-3 sm:p-5 sm:!pb-5'
>
<
CardHeader
className=
'border-b p-3 !pb-3 sm:p-5 sm:!pb-5'
>
<
Skeleton
className=
'h-6 w-32'
/>
<
Skeleton
className=
'h-6 w-32'
/>
</
CardHeader
>
</
CardHeader
>
...
@@ -261,6 +261,7 @@ export function SubscriptionPlansCard({
...
@@ -261,6 +261,7 @@ export function SubscriptionPlansCard({
title=
{
t
(
'Subscription Plans'
)
}
title=
{
t
(
'Subscription Plans'
)
}
description=
{
t
(
'Subscribe to a plan for model access'
)
}
description=
{
t
(
'Subscribe to a plan for model access'
)
}
icon=
{
<
Crown
className=
'h-4 w-4'
/>
}
icon=
{
<
Crown
className=
'h-4 w-4'
/>
}
disableHoverEffect
contentClassName=
'space-y-4 sm:space-y-5'
contentClassName=
'space-y-4 sm:space-y-5'
>
>
{
/* My subscriptions & billing preference */
}
{
/* My subscriptions & billing preference */
}
...
@@ -539,10 +540,8 @@ export function SubscriptionPlansCard({
...
@@ -539,10 +540,8 @@ export function SubscriptionPlansCard({
return
(
return
(
<
Card
<
Card
key=
{
plan
.
id
}
key=
{
plan
.
id
}
className=
{
cn
(
data
-
card
-
hover=
'false'
'transition-shadow hover:shadow-md'
,
className=
{
cn
(
isPopular
&&
'border-primary/70 shadow-sm'
)
}
isPopular
&&
'border-primary/70 shadow-sm'
)
}
>
>
<
CardContent
className=
'flex h-full flex-col p-3.5 sm:p-4'
>
<
CardContent
className=
'flex h-full flex-col p-3.5 sm:p-4'
>
<
div
className=
'mb-2 flex items-start justify-between gap-3'
>
<
div
className=
'mb-2 flex items-start justify-between gap-3'
>
...
...
web/default/src/styles/index.css
View file @
eb863116
...
@@ -477,19 +477,19 @@ For commercial licensing, please contact support@quantumnous.com
...
@@ -477,19 +477,19 @@ For commercial licensing, please contact support@quantumnous.com
/* Micro-interactions — Vercel-style subtle hover/active feedback */
/* Micro-interactions — Vercel-style subtle hover/active feedback */
@media
(
prefers-reduced-motion
:
no-preference
)
{
@media
(
prefers-reduced-motion
:
no-preference
)
{
[
data-slot
=
'card'
]
{
[
data-slot
=
'card'
]
:not
([
data-card-hover
=
'false'
])
{
transition
:
transition
:
transform
150ms
ease
,
transform
150ms
ease
,
box-shadow
150ms
ease
;
box-shadow
150ms
ease
;
}
}
@media
(
min-width
:
641px
)
{
@media
(
min-width
:
641px
)
{
[
data-slot
=
'card'
]
:hover
{
[
data-slot
=
'card'
]
:
not
([
data-card-hover
=
'false'
])
:
hover
{
transform
:
translateY
(
-1px
);
transform
:
translateY
(
-1px
);
box-shadow
:
0
4px
12px
rgb
(
0
0
0
/
0.06
);
box-shadow
:
0
4px
12px
rgb
(
0
0
0
/
0.06
);
}
}
.dark
[
data-slot
=
'card'
]
:hover
{
.dark
[
data-slot
=
'card'
]
:
not
([
data-card-hover
=
'false'
])
:
hover
{
box-shadow
:
0
4px
12px
rgb
(
0
0
0
/
0.3
);
box-shadow
:
0
4px
12px
rgb
(
0
0
0
/
0.3
);
}
}
}
}
...
...
web/default/src/tanstack-table.d.ts
View file @
eb863116
...
@@ -25,8 +25,8 @@ declare module '@tanstack/react-table' {
...
@@ -25,8 +25,8 @@ declare module '@tanstack/react-table' {
className
?:
string
className
?:
string
pinned
?:
'left'
|
'right'
pinned
?:
'left'
|
'right'
// Mobile card list layout hints (used by MobileCardList)
// Mobile card list layout hints (used by MobileCardList)
mobileTitle
?:
boolean
// card title area (left, larger text)
mobileTitle
?:
boolean
// card title area (left, larger text)
mobileBadge
?:
boolean
// status badge alongside title (right)
mobileBadge
?:
boolean
// status badge alongside title (right)
mobileHidden
?:
boolean
// hide this column on mobile entirely
mobileHidden
?:
boolean
// hide this column on mobile entirely
}
}
}
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment