fix(default): improve billing settings forms
This commit is contained in:
@@ -1,8 +1,14 @@
|
||||
import { memo, useCallback, useState } from 'react'
|
||||
import { type UseFormReturn } from 'react-hook-form'
|
||||
import { Code2, Eye } from 'lucide-react'
|
||||
import { Code2, Eye, HelpCircle } from 'lucide-react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import {
|
||||
Accordion,
|
||||
AccordionContent,
|
||||
AccordionItem,
|
||||
AccordionTrigger,
|
||||
} from '@/components/ui/accordion'
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
@@ -14,6 +20,13 @@ import {
|
||||
} from '@/components/ui/form'
|
||||
import { Switch } from '@/components/ui/switch'
|
||||
import { Textarea } from '@/components/ui/textarea'
|
||||
import {
|
||||
Sheet,
|
||||
SheetContent,
|
||||
SheetDescription,
|
||||
SheetHeader,
|
||||
SheetTitle,
|
||||
} from '@/components/ui/sheet'
|
||||
import { GroupRatioVisualEditor } from './group-ratio-visual-editor'
|
||||
import { GroupSpecialUsableRulesEditor } from './group-special-usable-editor'
|
||||
|
||||
@@ -40,6 +53,7 @@ export const GroupRatioForm = memo(function GroupRatioForm({
|
||||
}: GroupRatioFormProps) {
|
||||
const { t } = useTranslation()
|
||||
const [editMode, setEditMode] = useState<'visual' | 'json'>('visual')
|
||||
const [guideOpen, setGuideOpen] = useState(false)
|
||||
|
||||
const handleFieldChange = useCallback(
|
||||
(field: keyof GroupFormValues, value: string) => {
|
||||
@@ -57,7 +71,15 @@ export const GroupRatioForm = memo(function GroupRatioForm({
|
||||
|
||||
return (
|
||||
<div className='space-y-6'>
|
||||
<div className='flex justify-end'>
|
||||
<div className='flex flex-wrap justify-end gap-2'>
|
||||
<Button
|
||||
variant='outline'
|
||||
size='sm'
|
||||
onClick={() => setGuideOpen(true)}
|
||||
>
|
||||
<HelpCircle className='mr-2 h-4 w-4' />
|
||||
{t('Usage guide')}
|
||||
</Button>
|
||||
<Button variant='outline' size='sm' onClick={toggleEditMode}>
|
||||
{editMode === 'visual' ? (
|
||||
<>
|
||||
@@ -73,6 +95,8 @@ export const GroupRatioForm = memo(function GroupRatioForm({
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<GroupPricingGuide open={guideOpen} onOpenChange={setGuideOpen} />
|
||||
|
||||
<Form {...form}>
|
||||
{editMode === 'visual' ? (
|
||||
<div className='space-y-6'>
|
||||
@@ -276,3 +300,165 @@ export const GroupRatioForm = memo(function GroupRatioForm({
|
||||
</div>
|
||||
)
|
||||
})
|
||||
|
||||
type GroupPricingGuideProps = {
|
||||
open: boolean
|
||||
onOpenChange: (open: boolean) => void
|
||||
}
|
||||
|
||||
function GuideCodeBlock({ children }: { children: string }) {
|
||||
return (
|
||||
<pre className='bg-muted/60 overflow-x-auto rounded-lg border px-3 py-2 text-xs leading-6 whitespace-pre-wrap'>
|
||||
{children}
|
||||
</pre>
|
||||
)
|
||||
}
|
||||
|
||||
function GroupPricingGuide({ open, onOpenChange }: GroupPricingGuideProps) {
|
||||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<Sheet open={open} onOpenChange={onOpenChange}>
|
||||
<SheetContent side='right' className='w-full gap-0 p-0 sm:max-w-2xl'>
|
||||
<SheetHeader className='border-b p-4'>
|
||||
<SheetTitle>{t('Group pricing usage guide')}</SheetTitle>
|
||||
<SheetDescription>
|
||||
{t(
|
||||
'Understand how user groups, token groups, ratios, and special rules work together.'
|
||||
)}
|
||||
</SheetDescription>
|
||||
</SheetHeader>
|
||||
|
||||
<div className='space-y-5 overflow-y-auto p-4'>
|
||||
<section className='space-y-2'>
|
||||
<h3 className='text-sm font-semibold'>{t('Core concepts')}</h3>
|
||||
<div className='text-muted-foreground space-y-2 text-sm leading-6'>
|
||||
<p>
|
||||
<span className='text-foreground font-medium'>
|
||||
{t('User group')}
|
||||
</span>
|
||||
{': '}
|
||||
{t(
|
||||
'Assigned by administrators and used to represent a user level, such as default or vip.'
|
||||
)}
|
||||
</p>
|
||||
<p>
|
||||
<span className='text-foreground font-medium'>
|
||||
{t('Token group')}
|
||||
</span>
|
||||
{': '}
|
||||
{t(
|
||||
'Selected when creating a token and used as the default billing group for API calls.'
|
||||
)}
|
||||
</p>
|
||||
<p>
|
||||
<span className='text-foreground font-medium'>
|
||||
{t('Ratio')}
|
||||
</span>
|
||||
{': '}
|
||||
{t(
|
||||
'A billing multiplier. Lower ratios mean lower API call costs.'
|
||||
)}
|
||||
</p>
|
||||
<p>
|
||||
<span className='text-foreground font-medium'>
|
||||
{t('User selectable')}
|
||||
</span>
|
||||
{': '}
|
||||
{t(
|
||||
'When enabled, users can pick this group when creating tokens.'
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<Accordion className='rounded-lg border px-3'>
|
||||
<AccordionItem value='groups'>
|
||||
<AccordionTrigger>{t('Pricing group example')}</AccordionTrigger>
|
||||
<AccordionContent className='space-y-3'>
|
||||
<p className='text-muted-foreground text-sm leading-6'>
|
||||
{t(
|
||||
'Use the pricing group table to manage the ratio and whether the group appears in the token creation dropdown.'
|
||||
)}
|
||||
</p>
|
||||
<GuideCodeBlock>
|
||||
{`${t('Group name')} ${t('Ratio')} ${t('User selectable')} ${t('Description')}
|
||||
standard 1.0 ${t('Yes')} ${t('Standard price')}
|
||||
premium 0.5 ${t('Yes')} ${t('Premium plan, half price')}
|
||||
vip 0.5 ${t('No')} ${t('Assigned by administrator only')}`}
|
||||
</GuideCodeBlock>
|
||||
<p className='text-muted-foreground text-sm leading-6'>
|
||||
{t(
|
||||
'Users only see groups marked as user selectable. Non-selectable groups can still be assigned by administrators.'
|
||||
)}
|
||||
</p>
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
|
||||
<AccordionItem value='auto'>
|
||||
<AccordionTrigger>{t('Auto group behavior')}</AccordionTrigger>
|
||||
<AccordionContent className='space-y-3'>
|
||||
<p className='text-muted-foreground text-sm leading-6'>
|
||||
{t(
|
||||
'When a token uses the auto group, the system tries groups from top to bottom until it finds an available group.'
|
||||
)}
|
||||
</p>
|
||||
<GuideCodeBlock>{`["default", "vip"]`}</GuideCodeBlock>
|
||||
<p className='text-muted-foreground text-sm leading-6'>
|
||||
{t(
|
||||
'If default auto group is enabled, newly created tokens start with auto instead of an empty group.'
|
||||
)}
|
||||
</p>
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
|
||||
<AccordionItem value='special-ratio'>
|
||||
<AccordionTrigger>{t('Special ratio rules')}</AccordionTrigger>
|
||||
<AccordionContent className='space-y-3'>
|
||||
<p className='text-muted-foreground text-sm leading-6'>
|
||||
{t(
|
||||
'Special ratios override the token group ratio for specific user group and token group combinations.'
|
||||
)}
|
||||
</p>
|
||||
<GuideCodeBlock>{`{
|
||||
"vip": {
|
||||
"standard": 0.8,
|
||||
"premium": 0.3
|
||||
}
|
||||
}`}</GuideCodeBlock>
|
||||
<p className='text-muted-foreground text-sm leading-6'>
|
||||
{t(
|
||||
'Only configured combinations are overridden. All other calls keep the token group base ratio.'
|
||||
)}
|
||||
</p>
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
|
||||
<AccordionItem value='usable'>
|
||||
<AccordionTrigger>{t('Special usable group rules')}</AccordionTrigger>
|
||||
<AccordionContent className='space-y-3'>
|
||||
<p className='text-muted-foreground text-sm leading-6'>
|
||||
{t(
|
||||
'Special usable group rules can add, remove, or append selectable token groups for a specific user group.'
|
||||
)}
|
||||
</p>
|
||||
<GuideCodeBlock>{`{
|
||||
"vip": {
|
||||
"+:premium": "${t('Premium plan, half price')}",
|
||||
"-:default": "remove",
|
||||
"special": "${t('Special group')}"
|
||||
}
|
||||
}`}</GuideCodeBlock>
|
||||
<p className='text-muted-foreground text-sm leading-6'>
|
||||
{t(
|
||||
'Use +: to add a group, -: to remove a default selectable group, or no prefix to append a group.'
|
||||
)}
|
||||
</p>
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
</Accordion>
|
||||
</div>
|
||||
</SheetContent>
|
||||
</Sheet>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user