import { useState, useMemo, memo } from 'react' import { Pencil, Plus, Trash2, GripVertical, ChevronDown } from 'lucide-react' import { useTranslation } from 'react-i18next' import { Button } from '@/components/ui/button' import { Card, CardContent, CardDescription, CardHeader, CardTitle, } from '@/components/ui/card' import { Collapsible, CollapsibleContent, CollapsibleTrigger, } from '@/components/ui/collapsible' import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from '@/components/ui/dialog' import { Input } from '@/components/ui/input' import { Label } from '@/components/ui/label' import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from '@/components/ui/table' import { safeJsonParse } from '../utils/json-parser' type GroupRatioVisualEditorProps = { groupRatio: string topupGroupRatio: string userUsableGroups: string groupGroupRatio: string autoGroups: string onChange: (field: string, value: string) => void } type SimpleGroup = { name: string value: string } type UsableGroup = { name: string description: string } type GroupOverride = { targetGroup: string ratio: number } export const GroupRatioVisualEditor = memo(function GroupRatioVisualEditor({ groupRatio, topupGroupRatio, userUsableGroups, groupGroupRatio, autoGroups, onChange, }: GroupRatioVisualEditorProps) { const { t } = useTranslation() const [simpleDialogOpen, setSimpleDialogOpen] = useState(false) const [simpleDialogType, setSimpleDialogType] = useState< 'groupRatio' | 'topupGroupRatio' | null >(null) const [simpleEditData, setSimpleEditData] = useState(null) const [usableDialogOpen, setUsableDialogOpen] = useState(false) const [usableEditData, setUsableEditData] = useState(null) const [autoGroupDialogOpen, setAutoGroupDialogOpen] = useState(false) const [autoGroupInput, setAutoGroupInput] = useState('') const [groupOverrideDialogOpen, setGroupOverrideDialogOpen] = useState(false) const [groupOverrideUserGroup, setGroupOverrideUserGroup] = useState< string | null >(null) const [groupOverrideEditData, setGroupOverrideEditData] = useState(null) const [userGroupDialogOpen, setUserGroupDialogOpen] = useState(false) const [userGroupInput, setUserGroupInput] = useState('') // Parse group ratios const groupRatioList = useMemo(() => { const map = safeJsonParse>(groupRatio, { fallback: {}, context: 'group ratios', }) return Object.entries(map).map(([name, value]) => ({ name, value: String(value), })) }, [groupRatio]) // Parse topup group ratios const topupRatioList = useMemo(() => { const map = safeJsonParse>(topupGroupRatio, { fallback: {}, context: 'topup group ratios', }) return Object.entries(map).map(([name, value]) => ({ name, value: String(value), })) }, [topupGroupRatio]) // Parse usable groups const usableGroupsList = useMemo(() => { const map = safeJsonParse>(userUsableGroups, { fallback: {}, context: 'user usable groups', }) return Object.entries(map).map(([name, description]) => ({ name, description: String(description), })) }, [userUsableGroups]) // Parse auto groups const autoGroupsList = useMemo(() => { return safeJsonParse(autoGroups, { fallback: [], context: 'auto groups', }) }, [autoGroups]) // Parse group-group ratios const groupGroupRatioList = useMemo(() => { const map = safeJsonParse>>( groupGroupRatio, { fallback: {}, context: 'group-group ratios', } ) return Object.entries(map).map(([userGroup, overrides]) => ({ userGroup, overrides: Object.entries(overrides).map(([targetGroup, ratio]) => ({ targetGroup, ratio, })), })) }, [groupGroupRatio]) // Simple group handlers (for groupRatio and topupGroupRatio) const handleSimpleAdd = (type: 'groupRatio' | 'topupGroupRatio') => { setSimpleDialogType(type) setSimpleEditData(null) setSimpleDialogOpen(true) } const handleSimpleEdit = ( type: 'groupRatio' | 'topupGroupRatio', group: SimpleGroup ) => { setSimpleDialogType(type) setSimpleEditData(group) setSimpleDialogOpen(true) } const handleSimpleSave = (name: string, value: string) => { if (!simpleDialogType) return const fieldName = simpleDialogType === 'groupRatio' ? groupRatio : topupGroupRatio const map = safeJsonParse>(fieldName, { fallback: {}, silent: true, }) if (simpleEditData && simpleEditData.name !== name) { delete map[simpleEditData.name] } map[name] = parseFloat(value) const field = simpleDialogType === 'groupRatio' ? 'GroupRatio' : 'TopupGroupRatio' onChange(field, JSON.stringify(map, null, 2)) setSimpleDialogOpen(false) } const handleSimpleDelete = ( type: 'groupRatio' | 'topupGroupRatio', name: string ) => { const fieldName = type === 'groupRatio' ? groupRatio : topupGroupRatio const map = safeJsonParse>(fieldName, { fallback: {}, silent: true, }) delete map[name] const field = type === 'groupRatio' ? 'GroupRatio' : 'TopupGroupRatio' onChange(field, JSON.stringify(map, null, 2)) } // Usable groups handlers const handleUsableAdd = () => { setUsableEditData(null) setUsableDialogOpen(true) } const handleUsableEdit = (group: UsableGroup) => { setUsableEditData(group) setUsableDialogOpen(true) } const handleUsableSave = (name: string, description: string) => { const map = safeJsonParse>(userUsableGroups, { fallback: {}, silent: true, }) if (usableEditData && usableEditData.name !== name) { delete map[usableEditData.name] } map[name] = description onChange('UserUsableGroups', JSON.stringify(map, null, 2)) setUsableDialogOpen(false) } const handleUsableDelete = (name: string) => { const map = safeJsonParse>(userUsableGroups, { fallback: {}, silent: true, }) delete map[name] onChange('UserUsableGroups', JSON.stringify(map, null, 2)) } // Auto groups handlers const handleAutoGroupAdd = () => { setAutoGroupInput('') setAutoGroupDialogOpen(true) } const handleAutoGroupSave = () => { if (!autoGroupInput.trim()) return const list = [...autoGroupsList, autoGroupInput.trim()] onChange('AutoGroups', JSON.stringify(list, null, 2)) setAutoGroupDialogOpen(false) } const handleAutoGroupDelete = (index: number) => { const list = autoGroupsList.filter((_, i) => i !== index) onChange('AutoGroups', JSON.stringify(list, null, 2)) } const handleAutoGroupMove = (index: number, direction: 'up' | 'down') => { const list = [...autoGroupsList] const newIndex = direction === 'up' ? index - 1 : index + 1 if (newIndex < 0 || newIndex >= list.length) return ;[list[index], list[newIndex]] = [list[newIndex], list[index]] onChange('AutoGroups', JSON.stringify(list, null, 2)) } // Group-group ratio handlers const handleUserGroupAdd = () => { setUserGroupInput('') setUserGroupDialogOpen(true) } const handleUserGroupSave = () => { if (!userGroupInput.trim()) return const map = safeJsonParse>>( groupGroupRatio, { fallback: {}, silent: true, } ) if (!map[userGroupInput.trim()]) { map[userGroupInput.trim()] = {} } onChange('GroupGroupRatio', JSON.stringify(map, null, 2)) setUserGroupDialogOpen(false) } const handleUserGroupDelete = (userGroup: string) => { const map = safeJsonParse>>( groupGroupRatio, { fallback: {}, silent: true, } ) delete map[userGroup] onChange('GroupGroupRatio', JSON.stringify(map, null, 2)) } const handleOverrideAdd = (userGroup: string) => { setGroupOverrideUserGroup(userGroup) setGroupOverrideEditData(null) setGroupOverrideDialogOpen(true) } const handleOverrideEdit = (userGroup: string, override: GroupOverride) => { setGroupOverrideUserGroup(userGroup) setGroupOverrideEditData(override) setGroupOverrideDialogOpen(true) } const handleOverrideSave = ( targetGroup: string, ratio: number, oldTargetGroup?: string ) => { if (!groupOverrideUserGroup) return const map = safeJsonParse>>( groupGroupRatio, { fallback: {}, silent: true, } ) if (!map[groupOverrideUserGroup]) { map[groupOverrideUserGroup] = {} } if (oldTargetGroup && oldTargetGroup !== targetGroup) { delete map[groupOverrideUserGroup][oldTargetGroup] } map[groupOverrideUserGroup][targetGroup] = ratio onChange('GroupGroupRatio', JSON.stringify(map, null, 2)) setGroupOverrideDialogOpen(false) } const handleOverrideDelete = (userGroup: string, targetGroup: string) => { const map = safeJsonParse>>( groupGroupRatio, { fallback: {}, silent: true, } ) if (map[userGroup]) { delete map[userGroup][targetGroup] if (Object.keys(map[userGroup]).length === 0) { delete map[userGroup] } } onChange('GroupGroupRatio', JSON.stringify(map, null, 2)) } return (
{/* Group Ratios */} {t('Group ratios')} {t('Base multipliers applied when users select specific groups.')}
{groupRatioList.length > 0 && (
{t('Group name')} {t('Ratio')} {t('Actions')} {groupRatioList.map((group) => ( {group.name} {group.value}
))}
)}
{/* Topup Group Ratios */} {t('Top-up group ratios')} {t('Multipliers for recharge pricing based on user groups.')}
{topupRatioList.length > 0 && (
{t('Group name')} {t('Multiplier')} {t('Actions')} {topupRatioList.map((group) => ( {group.name} {group.value}
))}
)}
{/* Inter-group ratio overrides */} {t('Inter-group ratio overrides')} {t( 'Custom multipliers when specific user groups use specific token groups. Example: VIP users get 0.9x rate when using "edit_this" group tokens.' )}
{groupGroupRatioList.length > 0 && (
{groupGroupRatioList.map((userGroupData) => (
} > {userGroupData.userGroup} {t('{{count}} override', { count: userGroupData.overrides.length, })}
{userGroupData.overrides.length > 0 && (
{t('Target group')} {t('Ratio')} {t('Actions')} {userGroupData.overrides.map((override) => ( {override.targetGroup} {override.ratio}
))}
)}
))}
)}
{/* Usable Groups */} {t('Selectable groups')} {t('Groups that users can select when creating API keys.')}
{usableGroupsList.length > 0 && (
{t('Group name')} {t('Description')} {t('Actions')} {usableGroupsList.map((group) => ( {group.name} {group.description}
))}
)}
{/* Auto Groups */} {t('Auto assignment order')} {t( 'Priority order for automatic group assignment. New tokens rotate through this list.' )}
{autoGroupsList.length > 0 && (
{autoGroupsList.map((group, index) => (
{group}
))}
)}
{/* Simple Group Dialog */} {/* Usable Group Dialog */} {/* Auto Group Dialog */} {t('Add auto group')} {t('Add a group identifier to the auto assignment list.')}
setAutoGroupInput(e.target.value)} placeholder={t('default')} />
{/* User Group Dialog */} {t('Add user group')} {t('Create a new user group to configure ratio overrides for.')}
setUserGroupInput(e.target.value)} placeholder={t('vip')} />
{/* Group Override Dialog */}
) }) // Simple Group Dialog Component type SimpleGroupDialogProps = { open: boolean onOpenChange: (open: boolean) => void onSave: (name: string, value: string) => void editData: SimpleGroup | null type: 'groupRatio' | 'topupGroupRatio' | null } function SimpleGroupDialog({ open, onOpenChange, onSave, editData, type, }: SimpleGroupDialogProps) { const { t } = useTranslation() const [name, setName] = useState('') const [value, setValue] = useState('') const title = type === 'groupRatio' ? t('group ratio') : t('top-up ratio') const handleSave = () => { if (!name.trim() || !value.trim()) return onSave(name.trim(), value.trim()) setName('') setValue('') } return ( { onOpenChange(open) if (open && editData) { setName(editData.name) setValue(editData.value) } else { setName('') setValue('') } }} > {editData ? t('Edit {{title}}', { title }) : t('Add {{title}}', { title })} {t('Configure the ratio for this group.')}
setName(e.target.value)} placeholder={t('default')} disabled={!!editData} />
{ const val = e.target.value if (val === '' || !isNaN(parseFloat(val))) { setValue(val) } }} placeholder='1.0' />
) } // Usable Group Dialog Component type UsableGroupDialogProps = { open: boolean onOpenChange: (open: boolean) => void onSave: (name: string, description: string) => void editData: UsableGroup | null } function UsableGroupDialog({ open, onOpenChange, onSave, editData, }: UsableGroupDialogProps) { const { t } = useTranslation() const [name, setName] = useState('') const [description, setDescription] = useState('') const handleSave = () => { if (!name.trim() || !description.trim()) return onSave(name.trim(), description.trim()) setName('') setDescription('') } return ( { onOpenChange(open) if (open && editData) { setName(editData.name) setDescription(editData.description) } else { setName('') setDescription('') } }} > {editData ? t('Edit selectable group') : t('Add selectable group')} {t( 'Configure a group that users can select when creating API keys.' )}
setName(e.target.value)} placeholder={t('vip')} disabled={!!editData} />
setDescription(e.target.value)} placeholder={t('VIP users with premium access')} />
) } // Group Override Dialog Component type GroupOverrideDialogProps = { open: boolean onOpenChange: (open: boolean) => void onSave: (targetGroup: string, ratio: number, oldTargetGroup?: string) => void editData: GroupOverride | null userGroup: string | null } function GroupOverrideDialog({ open, onOpenChange, onSave, editData, userGroup, }: GroupOverrideDialogProps) { const { t } = useTranslation() const [targetGroup, setTargetGroup] = useState('') const [ratio, setRatio] = useState('') const handleSave = () => { if (!targetGroup.trim() || !ratio.trim()) return const parsedRatio = parseFloat(ratio) if (isNaN(parsedRatio)) return onSave(targetGroup.trim(), parsedRatio, editData?.targetGroup) setTargetGroup('') setRatio('') } return ( { onOpenChange(open) if (open && editData) { setTargetGroup(editData.targetGroup) setRatio(String(editData.ratio)) } else { setTargetGroup('') setRatio('') } }} > {editData ? t('Edit ratio override') : t('Add ratio override')} {userGroup ? t( 'Configure a custom ratio for "{{userGroup}}" users when using a specific token group.', { userGroup } ) : t( 'Configure a custom ratio for when users use a specific token group.' )}
setTargetGroup(e.target.value)} placeholder={t('edit_this')} disabled={!!editData} />

{t('The token group that will have a custom ratio')}

{ const val = e.target.value if (val === '' || !isNaN(parseFloat(val))) { setRatio(val) } }} placeholder='0.9' />

{t('Multiplier applied when {{userGroup}} uses {{targetGroup}}', { userGroup: userGroup || t('this user group'), targetGroup: targetGroup || t('this token group'), })}

) }