mirror of
https://github.com/QuantumNous/new-api.git
synced 2026-09-07 01:56:53 +00:00
perf(web): improve dialog sizing and footer layout
- migrate frontend dialogs to the shared footer API so actions stay separated from scrollable body content. - tune dialog dimensions for model analytics, prefill groups, billing history, channel model sync, and related workflows. - update channel terminology and dialog action translations across supported locales.
This commit is contained in:
+170
-175
@@ -25,12 +25,6 @@ import { formatQuota } from '@/lib/format'
|
||||
import { useSystemConfig } from '@/hooks/use-system-config'
|
||||
import { Alert, AlertDescription } from '@/components/ui/alert'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from '@/components/ui/dialog'
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
@@ -40,6 +34,7 @@ import {
|
||||
SelectValue,
|
||||
} from '@/components/ui/select'
|
||||
import { Separator } from '@/components/ui/separator'
|
||||
import { Dialog } from '@/components/dialog'
|
||||
import { GroupBadge } from '@/components/group-badge'
|
||||
import {
|
||||
paySubscriptionStripe,
|
||||
@@ -259,189 +254,189 @@ export function SubscriptionPurchaseDialog(props: Props) {
|
||||
}
|
||||
|
||||
return (
|
||||
<Dialog open={props.open} onOpenChange={props.onOpenChange}>
|
||||
<DialogContent className='max-sm:w-[calc(100vw-1.5rem)] sm:max-w-md'>
|
||||
<DialogHeader>
|
||||
<DialogTitle className='flex items-center gap-2'>
|
||||
<Crown className='h-5 w-5' />
|
||||
{t('Purchase Subscription')}
|
||||
</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<div className='space-y-3 sm:space-y-4'>
|
||||
<div className='bg-muted/50 space-y-2.5 rounded-lg border p-3 sm:space-y-3 sm:p-4'>
|
||||
<Dialog
|
||||
open={props.open}
|
||||
onOpenChange={props.onOpenChange}
|
||||
title={
|
||||
<>
|
||||
<Crown className='h-5 w-5' />
|
||||
{t('Purchase Subscription')}
|
||||
</>
|
||||
}
|
||||
contentClassName='max-sm:w-[calc(100vw-1.5rem)] sm:max-w-md'
|
||||
titleClassName='flex items-center gap-2'
|
||||
contentHeight='auto'
|
||||
bodyClassName='space-y-4'
|
||||
>
|
||||
<div className='space-y-3 sm:space-y-4'>
|
||||
<div className='bg-muted/50 space-y-2.5 rounded-lg border p-3 sm:space-y-3 sm:p-4'>
|
||||
<div className='flex justify-between'>
|
||||
<span className='text-muted-foreground text-sm'>
|
||||
{t('Plan Name')}
|
||||
</span>
|
||||
<span className='max-w-[200px] truncate text-sm font-medium'>
|
||||
{plan.title}
|
||||
</span>
|
||||
</div>
|
||||
<div className='flex items-center justify-between'>
|
||||
<span className='text-muted-foreground text-sm'>
|
||||
{t('Validity Period')}
|
||||
</span>
|
||||
<span className='flex items-center gap-1 text-sm'>
|
||||
<CalendarClock className='h-3.5 w-3.5' />
|
||||
{formatDuration(plan, t)}
|
||||
</span>
|
||||
</div>
|
||||
{formatResetPeriod(plan, t) !== t('No Reset') && (
|
||||
<div className='flex justify-between'>
|
||||
<span className='text-muted-foreground text-sm'>
|
||||
{t('Plan Name')}
|
||||
</span>
|
||||
<span className='max-w-[200px] truncate text-sm font-medium'>
|
||||
{plan.title}
|
||||
{t('Reset Period')}
|
||||
</span>
|
||||
<span className='text-sm'>{formatResetPeriod(plan, t)}</span>
|
||||
</div>
|
||||
<div className='flex items-center justify-between'>
|
||||
<span className='text-muted-foreground text-sm'>
|
||||
{t('Validity Period')}
|
||||
</span>
|
||||
<span className='flex items-center gap-1 text-sm'>
|
||||
<CalendarClock className='h-3.5 w-3.5' />
|
||||
{formatDuration(plan, t)}
|
||||
</span>
|
||||
</div>
|
||||
{formatResetPeriod(plan, t) !== t('No Reset') && (
|
||||
<div className='flex justify-between'>
|
||||
<span className='text-muted-foreground text-sm'>
|
||||
{t('Reset Period')}
|
||||
</span>
|
||||
<span className='text-sm'>{formatResetPeriod(plan, t)}</span>
|
||||
</div>
|
||||
)}
|
||||
<div className='flex items-center justify-between'>
|
||||
<span className='text-muted-foreground text-sm'>
|
||||
{t('Received amount')}
|
||||
</span>
|
||||
<span className='flex items-center gap-1 text-sm'>
|
||||
<Package className='h-3.5 w-3.5' />
|
||||
{totalAmount > 0 ? formatQuota(totalAmount) : t('Unlimited')}
|
||||
</span>
|
||||
</div>
|
||||
{plan.upgrade_group && (
|
||||
<div className='flex items-center justify-between'>
|
||||
<span className='text-muted-foreground text-sm'>
|
||||
{t('Upgrade Group')}
|
||||
</span>
|
||||
<GroupBadge group={plan.upgrade_group} />
|
||||
</div>
|
||||
)}
|
||||
<Separator />
|
||||
<div className='flex items-center justify-between'>
|
||||
<span className='text-sm font-medium'>{t('Amount Due')}</span>
|
||||
<span className='text-primary text-lg font-bold'>${price}</span>
|
||||
</div>
|
||||
)}
|
||||
<div className='flex items-center justify-between'>
|
||||
<span className='text-muted-foreground text-sm'>
|
||||
{t('Received amount')}
|
||||
</span>
|
||||
<span className='flex items-center gap-1 text-sm'>
|
||||
<Package className='h-3.5 w-3.5' />
|
||||
{totalAmount > 0 ? formatQuota(totalAmount) : t('Unlimited')}
|
||||
</span>
|
||||
</div>
|
||||
{plan.upgrade_group && (
|
||||
<div className='flex items-center justify-between'>
|
||||
<span className='text-muted-foreground text-sm'>
|
||||
{t('Upgrade Group')}
|
||||
</span>
|
||||
<GroupBadge group={plan.upgrade_group} />
|
||||
</div>
|
||||
)}
|
||||
<Separator />
|
||||
<div className='flex items-center justify-between'>
|
||||
<span className='text-sm font-medium'>{t('Amount Due')}</span>
|
||||
<span className='text-primary text-lg font-bold'>${price}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{limitReached && (
|
||||
{limitReached && (
|
||||
<Alert variant='destructive'>
|
||||
<AlertDescription>
|
||||
{t('Purchase limit reached')} ({props.purchaseCount}/
|
||||
{props.purchaseLimit})
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
<div className='flex flex-col gap-2 rounded-md border p-3'>
|
||||
<div className='flex items-center justify-between gap-2 text-xs'>
|
||||
<span className='text-muted-foreground'>{t('Required')}</span>
|
||||
<span>{formatQuota(balanceCost)}</span>
|
||||
</div>
|
||||
<div className='flex items-center justify-between gap-2 text-xs'>
|
||||
<span className='text-muted-foreground'>{t('Available')}</span>
|
||||
<span>{formatQuota(userQuota)}</span>
|
||||
</div>
|
||||
{!allowBalancePay ? (
|
||||
<Alert variant='destructive'>
|
||||
<AlertDescription>
|
||||
{t('Purchase limit reached')} ({props.purchaseCount}/
|
||||
{props.purchaseLimit})
|
||||
{t('This plan does not allow balance redemption')}
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
<div className='flex flex-col gap-2 rounded-md border p-3'>
|
||||
<div className='flex items-center justify-between gap-2 text-xs'>
|
||||
<span className='text-muted-foreground'>{t('Required')}</span>
|
||||
<span>{formatQuota(balanceCost)}</span>
|
||||
</div>
|
||||
<div className='flex items-center justify-between gap-2 text-xs'>
|
||||
<span className='text-muted-foreground'>{t('Available')}</span>
|
||||
<span>{formatQuota(userQuota)}</span>
|
||||
</div>
|
||||
{!allowBalancePay ? (
|
||||
) : (
|
||||
insufficientBalance && (
|
||||
<Alert variant='destructive'>
|
||||
<AlertDescription>
|
||||
{t('This plan does not allow balance redemption')}
|
||||
</AlertDescription>
|
||||
<AlertDescription>{t('Insufficient balance')}</AlertDescription>
|
||||
</Alert>
|
||||
) : (
|
||||
insufficientBalance && (
|
||||
<Alert variant='destructive'>
|
||||
<AlertDescription>
|
||||
{t('Insufficient balance')}
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
)
|
||||
)}
|
||||
<Button
|
||||
variant='outline'
|
||||
onClick={handlePayBalance}
|
||||
disabled={
|
||||
paying || limitReached || !allowBalancePay || insufficientBalance
|
||||
}
|
||||
>
|
||||
{t('Pay with Balance')}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{hasAnyPayment && (
|
||||
<div className='space-y-3'>
|
||||
<p className='text-muted-foreground text-xs'>
|
||||
{t('Select payment method')}
|
||||
</p>
|
||||
{(hasStripe || hasCreem || hasWaffoPancake) && (
|
||||
<div className='grid grid-cols-2 gap-2 sm:flex'>
|
||||
{hasStripe && (
|
||||
<Button
|
||||
variant='outline'
|
||||
className='flex-1'
|
||||
onClick={handlePayStripe}
|
||||
disabled={paying || limitReached}
|
||||
>
|
||||
Stripe
|
||||
</Button>
|
||||
)}
|
||||
{hasCreem && (
|
||||
<Button
|
||||
variant='outline'
|
||||
className='flex-1'
|
||||
onClick={handlePayCreem}
|
||||
disabled={paying || limitReached}
|
||||
>
|
||||
Creem
|
||||
</Button>
|
||||
)}
|
||||
{hasWaffoPancake && (
|
||||
<Button
|
||||
variant='outline'
|
||||
className='flex-1'
|
||||
onClick={handlePayWaffoPancake}
|
||||
disabled={paying || limitReached}
|
||||
>
|
||||
Waffo Pancake
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{hasEpay && (
|
||||
<div className='grid grid-cols-[minmax(0,1fr)_auto] gap-2'>
|
||||
<Select
|
||||
items={[
|
||||
...(props.epayMethods || []).map((m) => ({
|
||||
value: m.type,
|
||||
label: m.name || m.type,
|
||||
})),
|
||||
]}
|
||||
value={selectedEpayMethod}
|
||||
onValueChange={(v) =>
|
||||
v !== null && setSelectedEpayMethod(v)
|
||||
}
|
||||
disabled={limitReached}
|
||||
>
|
||||
<SelectTrigger className='flex-1'>
|
||||
<SelectValue>{selectedEpayMethodLabel}</SelectValue>
|
||||
</SelectTrigger>
|
||||
<SelectContent alignItemWithTrigger={false}>
|
||||
<SelectGroup>
|
||||
{(props.epayMethods || []).map((m) => (
|
||||
<SelectItem key={m.type} value={m.type}>
|
||||
{m.name || m.type}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectGroup>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<Button
|
||||
onClick={handlePayEpay}
|
||||
disabled={paying || !selectedEpayMethod || limitReached}
|
||||
>
|
||||
{t('Pay')}
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
)}
|
||||
<Button
|
||||
variant='outline'
|
||||
onClick={handlePayBalance}
|
||||
disabled={
|
||||
paying || limitReached || !allowBalancePay || insufficientBalance
|
||||
}
|
||||
>
|
||||
{t('Pay with Balance')}
|
||||
</Button>
|
||||
</div>
|
||||
</DialogContent>
|
||||
|
||||
{hasAnyPayment && (
|
||||
<div className='space-y-3'>
|
||||
<p className='text-muted-foreground text-xs'>
|
||||
{t('Select payment method')}
|
||||
</p>
|
||||
{(hasStripe || hasCreem || hasWaffoPancake) && (
|
||||
<div className='grid grid-cols-2 gap-2 sm:flex'>
|
||||
{hasStripe && (
|
||||
<Button
|
||||
variant='outline'
|
||||
className='flex-1'
|
||||
onClick={handlePayStripe}
|
||||
disabled={paying || limitReached}
|
||||
>
|
||||
Stripe
|
||||
</Button>
|
||||
)}
|
||||
{hasCreem && (
|
||||
<Button
|
||||
variant='outline'
|
||||
className='flex-1'
|
||||
onClick={handlePayCreem}
|
||||
disabled={paying || limitReached}
|
||||
>
|
||||
Creem
|
||||
</Button>
|
||||
)}
|
||||
{hasWaffoPancake && (
|
||||
<Button
|
||||
variant='outline'
|
||||
className='flex-1'
|
||||
onClick={handlePayWaffoPancake}
|
||||
disabled={paying || limitReached}
|
||||
>
|
||||
Waffo Pancake
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{hasEpay && (
|
||||
<div className='grid grid-cols-[minmax(0,1fr)_auto] gap-2'>
|
||||
<Select
|
||||
items={[
|
||||
...(props.epayMethods || []).map((m) => ({
|
||||
value: m.type,
|
||||
label: m.name || m.type,
|
||||
})),
|
||||
]}
|
||||
value={selectedEpayMethod}
|
||||
onValueChange={(v) => v !== null && setSelectedEpayMethod(v)}
|
||||
disabled={limitReached}
|
||||
>
|
||||
<SelectTrigger className='flex-1'>
|
||||
<SelectValue>{selectedEpayMethodLabel}</SelectValue>
|
||||
</SelectTrigger>
|
||||
<SelectContent alignItemWithTrigger={false}>
|
||||
<SelectGroup>
|
||||
{(props.epayMethods || []).map((m) => (
|
||||
<SelectItem key={m.type} value={m.type}>
|
||||
{m.name || m.type}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectGroup>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<Button
|
||||
onClick={handlePayEpay}
|
||||
disabled={paying || !selectedEpayMethod || limitReached}
|
||||
>
|
||||
{t('Pay')}
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user