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:
+28
-32
@@ -21,13 +21,8 @@ import { ExternalLink, Copy, Music } from 'lucide-react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { toast } from 'sonner'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from '@/components/ui/dialog'
|
||||
import { ScrollArea } from '@/components/ui/scroll-area'
|
||||
import { Dialog } from '@/components/dialog'
|
||||
import { StatusBadge } from '@/components/status-badge'
|
||||
|
||||
export interface AudioClip {
|
||||
@@ -152,32 +147,33 @@ export function AudioPreviewDialog(props: AudioPreviewDialogProps) {
|
||||
const clips = Array.isArray(props.clips) ? props.clips : []
|
||||
|
||||
return (
|
||||
<Dialog open={props.open} onOpenChange={props.onOpenChange}>
|
||||
<DialogContent className='sm:max-w-lg'>
|
||||
<DialogHeader>
|
||||
<DialogTitle className='flex items-center gap-2'>
|
||||
<Music className='h-5 w-5' />
|
||||
{t('Audio Preview')}
|
||||
</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
{clips.length === 0 ? (
|
||||
<p className='text-muted-foreground py-4 text-center text-sm'>
|
||||
{t('None')}
|
||||
</p>
|
||||
) : (
|
||||
<ScrollArea className='max-h-[60vh]'>
|
||||
<div className='space-y-3 pr-2'>
|
||||
{clips.map((clip, idx) => (
|
||||
<AudioClipCard
|
||||
key={clip.clip_id || clip.id || idx}
|
||||
clip={clip}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</ScrollArea>
|
||||
)}
|
||||
</DialogContent>
|
||||
<Dialog
|
||||
open={props.open}
|
||||
onOpenChange={props.onOpenChange}
|
||||
title={
|
||||
<>
|
||||
<Music className='h-5 w-5' />
|
||||
{t('Audio Preview')}
|
||||
</>
|
||||
}
|
||||
contentClassName='sm:max-w-lg'
|
||||
titleClassName='flex items-center gap-2'
|
||||
contentHeight='auto'
|
||||
bodyClassName='space-y-4'
|
||||
>
|
||||
{clips.length === 0 ? (
|
||||
<p className='text-muted-foreground py-4 text-center text-sm'>
|
||||
{t('None')}
|
||||
</p>
|
||||
) : (
|
||||
<ScrollArea className='max-h-[60vh]'>
|
||||
<div className='space-y-3 pr-2'>
|
||||
{clips.map((clip, idx) => (
|
||||
<AudioClipCard key={clip.clip_id || clip.id || idx} clip={clip} />
|
||||
))}
|
||||
</div>
|
||||
</ScrollArea>
|
||||
)}
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
|
||||
+539
-547
File diff suppressed because it is too large
Load Diff
+35
-42
@@ -20,15 +20,9 @@ import { Copy, Check } from 'lucide-react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useCopyToClipboard } from '@/hooks/use-copy-to-clipboard'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from '@/components/ui/dialog'
|
||||
import { Label } from '@/components/ui/label'
|
||||
import { ScrollArea } from '@/components/ui/scroll-area'
|
||||
import { Dialog } from '@/components/dialog'
|
||||
|
||||
interface FailReasonDialogProps {
|
||||
failReason: string
|
||||
@@ -45,43 +39,42 @@ export function FailReasonDialog({
|
||||
const { copiedText, copyToClipboard } = useCopyToClipboard({ notify: false })
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<DialogContent className='sm:max-w-lg'>
|
||||
<DialogHeader>
|
||||
<DialogTitle>{t('Fail Reason Details')}</DialogTitle>
|
||||
<DialogDescription>
|
||||
{t('View the complete error message and details')}
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
<ScrollArea className='max-h-[500px] pr-4'>
|
||||
<div className='space-y-4 py-4'>
|
||||
<div className='space-y-2'>
|
||||
<Label className='text-sm font-semibold'>
|
||||
{t('Error Message')}
|
||||
</Label>
|
||||
<div className='bg-muted/50 relative rounded-md border border-red-200 p-3'>
|
||||
<Button
|
||||
variant='ghost'
|
||||
size='sm'
|
||||
className='absolute top-2 right-2 h-8 w-8 p-0'
|
||||
onClick={() => copyToClipboard(failReason)}
|
||||
title={t('Copy to clipboard')}
|
||||
>
|
||||
{copiedText === failReason ? (
|
||||
<Check className='size-4 text-green-600' />
|
||||
) : (
|
||||
<Copy className='size-4' />
|
||||
)}
|
||||
</Button>
|
||||
<p className='overflow-wrap-anywhere pr-10 text-sm leading-relaxed break-all whitespace-pre-wrap text-red-600'>
|
||||
{failReason || '-'}
|
||||
</p>
|
||||
</div>
|
||||
<Dialog
|
||||
open={open}
|
||||
onOpenChange={onOpenChange}
|
||||
title={t('Fail Reason Details')}
|
||||
description={t('View the complete error message and details')}
|
||||
contentClassName='sm:max-w-lg'
|
||||
contentHeight='auto'
|
||||
bodyClassName='space-y-4'
|
||||
>
|
||||
<ScrollArea className='max-h-[500px] pr-4'>
|
||||
<div className='space-y-4 py-4'>
|
||||
<div className='space-y-2'>
|
||||
<Label className='text-sm font-semibold'>
|
||||
{t('Error Message')}
|
||||
</Label>
|
||||
<div className='bg-muted/50 relative rounded-md border border-red-200 p-3'>
|
||||
<Button
|
||||
variant='ghost'
|
||||
size='sm'
|
||||
className='absolute top-2 right-2 h-8 w-8 p-0'
|
||||
onClick={() => copyToClipboard(failReason)}
|
||||
title={t('Copy to clipboard')}
|
||||
>
|
||||
{copiedText === failReason ? (
|
||||
<Check className='size-4 text-green-600' />
|
||||
) : (
|
||||
<Copy className='size-4' />
|
||||
)}
|
||||
</Button>
|
||||
<p className='overflow-wrap-anywhere pr-10 text-sm leading-relaxed break-all whitespace-pre-wrap text-red-600'>
|
||||
{failReason || '-'}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</ScrollArea>
|
||||
</DialogContent>
|
||||
</div>
|
||||
</ScrollArea>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -18,15 +18,9 @@ For commercial licensing, please contact support@quantumnous.com
|
||||
*/
|
||||
import { useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from '@/components/ui/dialog'
|
||||
import { ScrollArea } from '@/components/ui/scroll-area'
|
||||
import { Skeleton } from '@/components/ui/skeleton'
|
||||
import { Dialog } from '@/components/dialog'
|
||||
|
||||
interface ImageDialogProps {
|
||||
imageUrl: string
|
||||
@@ -65,56 +59,55 @@ export function ImageDialog({
|
||||
}
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={handleOpenChange}>
|
||||
<DialogContent className='sm:max-w-3xl'>
|
||||
<DialogHeader>
|
||||
<DialogTitle>{t('Image Preview')}</DialogTitle>
|
||||
<DialogDescription>
|
||||
{taskId
|
||||
? `${t('Task ID:')} ${taskId}`
|
||||
: t('View the generated image')}
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<Dialog
|
||||
open={open}
|
||||
onOpenChange={handleOpenChange}
|
||||
title={t('Image Preview')}
|
||||
description={
|
||||
taskId ? `${t('Task ID:')} ${taskId}` : t('View the generated image')
|
||||
}
|
||||
contentClassName='sm:max-w-3xl'
|
||||
contentHeight='auto'
|
||||
bodyClassName='space-y-4'
|
||||
>
|
||||
<ScrollArea className='max-h-[600px]'>
|
||||
<div className='py-4'>
|
||||
<div className='bg-muted/50 relative flex min-h-[300px] items-center justify-center rounded-lg border'>
|
||||
{/* Skeleton - show when loading or error */}
|
||||
{(isLoading || hasError) && (
|
||||
<Skeleton className='absolute inset-0 h-full w-full rounded-lg' />
|
||||
)}
|
||||
|
||||
<ScrollArea className='max-h-[600px]'>
|
||||
<div className='py-4'>
|
||||
<div className='bg-muted/50 relative flex min-h-[300px] items-center justify-center rounded-lg border'>
|
||||
{/* Skeleton - show when loading or error */}
|
||||
{(isLoading || hasError) && (
|
||||
<Skeleton className='absolute inset-0 h-full w-full rounded-lg' />
|
||||
)}
|
||||
{/* Actual Image */}
|
||||
<img
|
||||
src={imageUrl}
|
||||
alt={t('Generated image')}
|
||||
className={`max-h-[550px] w-full rounded-lg object-contain ${
|
||||
isLoading || hasError ? 'opacity-0' : 'opacity-100'
|
||||
}`}
|
||||
onLoad={handleImageLoad}
|
||||
onError={handleImageError}
|
||||
loading='lazy'
|
||||
/>
|
||||
|
||||
{/* Actual Image */}
|
||||
<img
|
||||
src={imageUrl}
|
||||
alt={t('Generated image')}
|
||||
className={`max-h-[550px] w-full rounded-lg object-contain ${
|
||||
isLoading || hasError ? 'opacity-0' : 'opacity-100'
|
||||
}`}
|
||||
onLoad={handleImageLoad}
|
||||
onError={handleImageError}
|
||||
loading='lazy'
|
||||
/>
|
||||
|
||||
{/* Error text overlay (shown on skeleton) */}
|
||||
{hasError && (
|
||||
<div className='absolute inset-0 flex items-center justify-center'>
|
||||
<p className='text-muted-foreground text-sm'>
|
||||
{t('Failed to load image')}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Image URL */}
|
||||
<div className='bg-muted mt-4 rounded-md p-3'>
|
||||
<p className='text-muted-foreground font-mono text-xs break-all'>
|
||||
{imageUrl}
|
||||
</p>
|
||||
</div>
|
||||
{/* Error text overlay (shown on skeleton) */}
|
||||
{hasError && (
|
||||
<div className='absolute inset-0 flex items-center justify-center'>
|
||||
<p className='text-muted-foreground text-sm'>
|
||||
{t('Failed to load image')}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</ScrollArea>
|
||||
</DialogContent>
|
||||
|
||||
{/* Image URL */}
|
||||
<div className='bg-muted mt-4 rounded-md p-3'>
|
||||
<p className='text-muted-foreground font-mono text-xs break-all'>
|
||||
{imageUrl}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</ScrollArea>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -20,15 +20,9 @@ import { Copy, Check } from 'lucide-react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useCopyToClipboard } from '@/hooks/use-copy-to-clipboard'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from '@/components/ui/dialog'
|
||||
import { Label } from '@/components/ui/label'
|
||||
import { ScrollArea } from '@/components/ui/scroll-area'
|
||||
import { Dialog } from '@/components/dialog'
|
||||
|
||||
interface PromptDialogProps {
|
||||
prompt: string
|
||||
@@ -47,69 +41,68 @@ export function PromptDialog({
|
||||
const { copiedText, copyToClipboard } = useCopyToClipboard({ notify: false })
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<DialogContent className='sm:max-w-lg'>
|
||||
<DialogHeader>
|
||||
<DialogTitle>{t('Prompt Details')}</DialogTitle>
|
||||
<DialogDescription>
|
||||
{t('View the complete prompt and its English translation')}
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<Dialog
|
||||
open={open}
|
||||
onOpenChange={onOpenChange}
|
||||
title={t('Prompt Details')}
|
||||
description={t('View the complete prompt and its English translation')}
|
||||
contentClassName='sm:max-w-lg'
|
||||
contentHeight='auto'
|
||||
bodyClassName='space-y-4'
|
||||
>
|
||||
<ScrollArea className='max-h-[500px] pr-4'>
|
||||
<div className='space-y-4 py-4'>
|
||||
{/* Original Prompt */}
|
||||
<div className='space-y-2'>
|
||||
<Label className='text-sm font-semibold'>{t('Prompt')}</Label>
|
||||
<div className='bg-muted/50 relative rounded-md border p-3'>
|
||||
<Button
|
||||
variant='ghost'
|
||||
size='sm'
|
||||
className='absolute top-2 right-2 h-8 w-8 p-0'
|
||||
onClick={() => copyToClipboard(prompt)}
|
||||
title={t('Copy to clipboard')}
|
||||
>
|
||||
{copiedText === prompt ? (
|
||||
<Check className='size-4 text-green-600' />
|
||||
) : (
|
||||
<Copy className='size-4' />
|
||||
)}
|
||||
</Button>
|
||||
<p className='pr-10 text-sm leading-relaxed break-words whitespace-pre-wrap'>
|
||||
{prompt || '-'}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ScrollArea className='max-h-[500px] pr-4'>
|
||||
<div className='space-y-4 py-4'>
|
||||
{/* Original Prompt */}
|
||||
{/* English Prompt */}
|
||||
{promptEn && (
|
||||
<div className='space-y-2'>
|
||||
<Label className='text-sm font-semibold'>{t('Prompt')}</Label>
|
||||
<Label className='text-sm font-semibold'>
|
||||
{t('Prompt (EN)')}
|
||||
</Label>
|
||||
<div className='bg-muted/50 relative rounded-md border p-3'>
|
||||
<Button
|
||||
variant='ghost'
|
||||
size='sm'
|
||||
className='absolute top-2 right-2 h-8 w-8 p-0'
|
||||
onClick={() => copyToClipboard(prompt)}
|
||||
onClick={() => copyToClipboard(promptEn)}
|
||||
title={t('Copy to clipboard')}
|
||||
>
|
||||
{copiedText === prompt ? (
|
||||
{copiedText === promptEn ? (
|
||||
<Check className='size-4 text-green-600' />
|
||||
) : (
|
||||
<Copy className='size-4' />
|
||||
)}
|
||||
</Button>
|
||||
<p className='pr-10 text-sm leading-relaxed break-words whitespace-pre-wrap'>
|
||||
{prompt || '-'}
|
||||
{promptEn}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* English Prompt */}
|
||||
{promptEn && (
|
||||
<div className='space-y-2'>
|
||||
<Label className='text-sm font-semibold'>
|
||||
{t('Prompt (EN)')}
|
||||
</Label>
|
||||
<div className='bg-muted/50 relative rounded-md border p-3'>
|
||||
<Button
|
||||
variant='ghost'
|
||||
size='sm'
|
||||
className='absolute top-2 right-2 h-8 w-8 p-0'
|
||||
onClick={() => copyToClipboard(promptEn)}
|
||||
title={t('Copy to clipboard')}
|
||||
>
|
||||
{copiedText === promptEn ? (
|
||||
<Check className='size-4 text-green-600' />
|
||||
) : (
|
||||
<Copy className='size-4' />
|
||||
)}
|
||||
</Button>
|
||||
<p className='pr-10 text-sm leading-relaxed break-words whitespace-pre-wrap'>
|
||||
{promptEn}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</ScrollArea>
|
||||
</DialogContent>
|
||||
)}
|
||||
</div>
|
||||
</ScrollArea>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
|
||||
+89
-96
@@ -21,14 +21,8 @@ import { Loader2 } from 'lucide-react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { toast } from 'sonner'
|
||||
import { formatQuota, formatCompactNumber } from '@/lib/format'
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from '@/components/ui/dialog'
|
||||
import { Label } from '@/components/ui/label'
|
||||
import { Dialog } from '@/components/dialog'
|
||||
import { getUserInfo } from '../../api'
|
||||
import type { UserInfo } from '../../types'
|
||||
|
||||
@@ -88,104 +82,103 @@ export function UserInfoDialog({
|
||||
)
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<DialogContent className='sm:max-w-lg'>
|
||||
<DialogHeader>
|
||||
<DialogTitle>{t('User Information')}</DialogTitle>
|
||||
<DialogDescription>
|
||||
{t(
|
||||
'View detailed information about this user including balance, usage statistics, and invitation details.'
|
||||
<Dialog
|
||||
open={open}
|
||||
onOpenChange={onOpenChange}
|
||||
title={t('User Information')}
|
||||
description={t(
|
||||
'View detailed information about this user including balance, usage statistics, and invitation details.'
|
||||
)}
|
||||
contentClassName='sm:max-w-lg'
|
||||
contentHeight='auto'
|
||||
bodyClassName='space-y-4'
|
||||
>
|
||||
{isLoading ? (
|
||||
<div className='flex items-center justify-center py-8'>
|
||||
<Loader2 className='text-muted-foreground size-6 animate-spin' />
|
||||
</div>
|
||||
) : userInfo ? (
|
||||
<div className='space-y-4 py-4'>
|
||||
{/* Basic Info */}
|
||||
<div className='grid grid-cols-2 gap-4'>
|
||||
<InfoItem label={t('Username')} value={userInfo.username} />
|
||||
{userInfo.display_name && (
|
||||
<InfoItem
|
||||
label={t('Display Name')}
|
||||
value={userInfo.display_name}
|
||||
/>
|
||||
)}
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
{isLoading ? (
|
||||
<div className='flex items-center justify-center py-8'>
|
||||
<Loader2 className='text-muted-foreground size-6 animate-spin' />
|
||||
</div>
|
||||
) : userInfo ? (
|
||||
<div className='space-y-4 py-4'>
|
||||
{/* Basic Info */}
|
||||
<div className='grid grid-cols-2 gap-4'>
|
||||
<InfoItem label={t('Username')} value={userInfo.username} />
|
||||
{userInfo.display_name && (
|
||||
<InfoItem
|
||||
label={t('Display Name')}
|
||||
value={userInfo.display_name}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Balance Info */}
|
||||
<div className='grid grid-cols-2 gap-4'>
|
||||
<InfoItem
|
||||
label={t('Balance')}
|
||||
value={formatQuota(userInfo.quota)}
|
||||
/>
|
||||
<InfoItem
|
||||
label={t('Used Quota')}
|
||||
value={formatQuota(userInfo.used_quota)}
|
||||
/>
|
||||
</div>
|
||||
{/* Balance Info */}
|
||||
<div className='grid grid-cols-2 gap-4'>
|
||||
<InfoItem
|
||||
label={t('Balance')}
|
||||
value={formatQuota(userInfo.quota)}
|
||||
/>
|
||||
<InfoItem
|
||||
label={t('Used Quota')}
|
||||
value={formatQuota(userInfo.used_quota)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Statistics */}
|
||||
<div className='grid grid-cols-2 gap-4'>
|
||||
<InfoItem
|
||||
label={t('Request Count')}
|
||||
value={formatCompactNumber(userInfo.request_count)}
|
||||
/>
|
||||
{userInfo.group && (
|
||||
<InfoItem label={t('User Group')} value={userInfo.group} />
|
||||
)}
|
||||
</div>
|
||||
{/* Statistics */}
|
||||
<div className='grid grid-cols-2 gap-4'>
|
||||
<InfoItem
|
||||
label={t('Request Count')}
|
||||
value={formatCompactNumber(userInfo.request_count)}
|
||||
/>
|
||||
{userInfo.group && (
|
||||
<InfoItem label={t('User Group')} value={userInfo.group} />
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Invitation Info */}
|
||||
{(userInfo.aff_code ||
|
||||
userInfo.aff_count !== undefined ||
|
||||
(userInfo.aff_quota !== undefined && userInfo.aff_quota > 0)) && (
|
||||
<>
|
||||
<div className='grid grid-cols-2 gap-4'>
|
||||
{userInfo.aff_code && (
|
||||
<InfoItem
|
||||
label={t('Invitation Code')}
|
||||
value={userInfo.aff_code}
|
||||
/>
|
||||
)}
|
||||
{userInfo.aff_count !== undefined && (
|
||||
<InfoItem
|
||||
label={t('Invited Users')}
|
||||
value={formatCompactNumber(userInfo.aff_count)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{userInfo.aff_quota !== undefined && userInfo.aff_quota > 0 && (
|
||||
{/* Invitation Info */}
|
||||
{(userInfo.aff_code ||
|
||||
userInfo.aff_count !== undefined ||
|
||||
(userInfo.aff_quota !== undefined && userInfo.aff_quota > 0)) && (
|
||||
<>
|
||||
<div className='grid grid-cols-2 gap-4'>
|
||||
{userInfo.aff_code && (
|
||||
<InfoItem
|
||||
label={t('Invitation Quota')}
|
||||
value={formatQuota(userInfo.aff_quota)}
|
||||
label={t('Invitation Code')}
|
||||
value={userInfo.aff_code}
|
||||
/>
|
||||
)}
|
||||
{userInfo.aff_count !== undefined && (
|
||||
<InfoItem
|
||||
label={t('Invited Users')}
|
||||
value={formatCompactNumber(userInfo.aff_count)}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* Remark */}
|
||||
{userInfo.remark && (
|
||||
<div className='space-y-1.5'>
|
||||
<Label className='text-muted-foreground text-xs'>
|
||||
{t('Remark')}
|
||||
</Label>
|
||||
<div className='text-sm leading-relaxed font-semibold break-words'>
|
||||
{userInfo.remark}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<div className='text-muted-foreground py-8 text-center text-sm'>
|
||||
{t('No user information available')}
|
||||
</div>
|
||||
)}
|
||||
</DialogContent>
|
||||
|
||||
{userInfo.aff_quota !== undefined && userInfo.aff_quota > 0 && (
|
||||
<InfoItem
|
||||
label={t('Invitation Quota')}
|
||||
value={formatQuota(userInfo.aff_quota)}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* Remark */}
|
||||
{userInfo.remark && (
|
||||
<div className='space-y-1.5'>
|
||||
<Label className='text-muted-foreground text-xs'>
|
||||
{t('Remark')}
|
||||
</Label>
|
||||
<div className='text-sm leading-relaxed font-semibold break-words'>
|
||||
{userInfo.remark}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<div className='text-muted-foreground py-8 text-center text-sm'>
|
||||
{t('No user information available')}
|
||||
</div>
|
||||
)}
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user