Enhance PaidUntilBadge and UserManageDialog to display billing date
CI / Backend (build + test) (push) Successful in 1m18s
CI / Frontend (lint + typecheck + build) (push) Successful in 31s

- Updated the PaidUntilBadge component to accept a new `showDate` prop, allowing it to display the billing date alongside the remaining days.
- Modified UserManageDialog and admin user management components to utilize the updated PaidUntilBadge, improving context for billing information.
- Added internationalization support for the new date format in both English and Russian.
This commit is contained in:
Leonid Pershin
2026-07-19 05:57:13 +03:00
parent 1c13695556
commit 6a2d2d2318
4 changed files with 27 additions and 7 deletions
@@ -155,7 +155,7 @@ export function UserManageDialog({ user, open, onOpenChange }: { user: UserSumma
{user.billingEnabled && ( {user.billingEnabled && (
<div className="flex flex-col gap-1.5"> <div className="flex flex-col gap-1.5">
<Label>{t('admin.users.billingLabel')}</Label> <Label>{t('admin.users.billingLabel')}</Label>
<PaidUntilBadge paidUntil={user.billingPaidUntil} /> <PaidUntilBadge paidUntil={user.billingPaidUntil} showDate />
<div className="mt-1 flex gap-2"> <div className="mt-1 flex gap-2">
<Input <Input
type="number" type="number"
@@ -10,31 +10,49 @@ const YELLOW_THRESHOLD_DAYS = 7
* *
* `pendingReview` — есть заявка на оплату, ожидающая решения админа (`PaymentRequestStatus.AwaitingConfirmation`): * `pendingReview` — есть заявка на оплату, ожидающая решения админа (`PaymentRequestStatus.AwaitingConfirmation`):
* конфиги не гасятся, пока админ не решит (см. BillingService), поэтому вместо тревожного "Истекло" * конфиги не гасятся, пока админ не решит (см. BillingService), поэтому вместо тревожного "Истекло"
* показываем нейтральный статус — пользователю незачем переживать за то, что от него не зависит. */ * показываем нейтральный статус — пользователю незачем переживать за то, что от него не зависит.
*
* `showDate` — добавляет саму дату к остатку («до 15.08.2026 (99 дн.)») — для админских экранов
* (карточка пользователя, список пользователей), где голого «99 дн.» недостаточно контекста.
* Компактные пользовательские места (дашборд) оставляют только остаток. */
export function PaidUntilBadge({ export function PaidUntilBadge({
paidUntil, paidUntil,
pendingReview, pendingReview,
showDate,
}: { }: {
paidUntil: string | null paidUntil: string | null
pendingReview?: boolean pendingReview?: boolean
showDate?: boolean
}) { }) {
const { t } = useTranslation() const { t } = useTranslation()
if (pendingReview) if (pendingReview)
return ( return (
<Badge variant="warning" title={t('billing.pendingReviewHint')}> <Badge variant="warning" className="self-start" title={t('billing.pendingReviewHint')}>
{t('billing.pendingReviewShort')} {t('billing.pendingReviewShort')}
</Badge> </Badge>
) )
if (!paidUntil) return <Badge variant="destructive">{t('billing.neverPaidShort')}</Badge> if (!paidUntil)
return (
<Badge variant="destructive" className="self-start">
{t('billing.neverPaidShort')}
</Badge>
)
const days = getDaysRemaining(paidUntil) const days = getDaysRemaining(paidUntil)
const variant = days <= RED_THRESHOLD_DAYS ? 'destructive' : days <= YELLOW_THRESHOLD_DAYS ? 'warning' : 'success' const variant = days <= RED_THRESHOLD_DAYS ? 'destructive' : days <= YELLOW_THRESHOLD_DAYS ? 'warning' : 'success'
const remaining = formatRemaining(paidUntil, t)
const label = showDate
? t('billing.paidUntilWithRemaining', {
date: new Date(paidUntil).toLocaleDateString(),
remaining,
})
: remaining
return ( return (
<Badge variant={variant} title={new Date(paidUntil).toLocaleString()}> <Badge variant={variant} className="self-start" title={new Date(paidUntil).toLocaleString()}>
{formatRemaining(paidUntil, t)} {label}
</Badge> </Badge>
) )
} }
+1 -1
View File
@@ -77,7 +77,7 @@ function AdminUsersPage() {
</td> </td>
<td className="py-2"> <td className="py-2">
{user.billingEnabled ? ( {user.billingEnabled ? (
<PaidUntilBadge paidUntil={user.billingPaidUntil} /> <PaidUntilBadge paidUntil={user.billingPaidUntil} showDate />
) : ( ) : (
<span className="text-muted-foreground"></span> <span className="text-muted-foreground"></span>
)} )}
+2
View File
@@ -116,6 +116,7 @@ const resources = {
minutesRemaining: '{{count}} мин', minutesRemaining: '{{count}} мин',
pendingReviewShort: 'На проверке', pendingReviewShort: 'На проверке',
pendingReviewHint: 'Оплата отправлена на проверку администратору — доступ не будет приостановлен, пока заявка не решена.', pendingReviewHint: 'Оплата отправлена на проверку администратору — доступ не будет приостановлен, пока заявка не решена.',
paidUntilWithRemaining: 'до {{date}} ({{remaining}})',
newRequest: 'Оформить оплату', newRequest: 'Оформить оплату',
period: 'Период', period: 'Период',
periods: { periods: {
@@ -642,6 +643,7 @@ const resources = {
minutesRemaining: '{{count}}m', minutesRemaining: '{{count}}m',
pendingReviewShort: 'Under review', pendingReviewShort: 'Under review',
pendingReviewHint: 'Payment sent for admin review — access will not be suspended until the request is decided.', pendingReviewHint: 'Payment sent for admin review — access will not be suspended until the request is decided.',
paidUntilWithRemaining: 'until {{date}} ({{remaining}})',
newRequest: 'Set up payment', newRequest: 'Set up payment',
period: 'Period', period: 'Period',
periods: { periods: {