fix(wallet): honor configured quota units for reward transfers (#5808)
* fix(wallet): honor configured quota units for reward transfers * fix(i18n): localize quota preview descriptions * fix(settings): normalize invalid quota input
This commit is contained in:
+24
-7
@@ -34,6 +34,7 @@ import {
|
||||
} from '@/components/ui/form'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { Switch } from '@/components/ui/switch'
|
||||
import { formatQuota } from '@/lib/format'
|
||||
|
||||
import { FormDirtyIndicator } from '../components/form-dirty-indicator'
|
||||
import { FormNavigationGuard } from '../components/form-navigation-guard'
|
||||
@@ -64,6 +65,11 @@ const quotaSchema = z.object({
|
||||
})
|
||||
|
||||
type QuotaFormValues = z.infer<typeof quotaSchema>
|
||||
type QuotaInputValue = number | ''
|
||||
|
||||
function formatQuotaInputValue(value: QuotaInputValue): string {
|
||||
return formatQuota(value === '' ? 0 : value)
|
||||
}
|
||||
|
||||
type QuotaSettingsSectionProps = {
|
||||
defaultValues: QuotaFormValues
|
||||
@@ -77,11 +83,10 @@ export function QuotaSettingsSection({
|
||||
const { t } = useTranslation()
|
||||
const updateOption = useUpdateOption()
|
||||
const handleNumberChange =
|
||||
(onChange: (value: number | string) => void) =>
|
||||
(onChange: (value: QuotaInputValue) => void) =>
|
||||
(event: ChangeEvent<HTMLInputElement>) => {
|
||||
onChange(
|
||||
event.target.value === '' ? '' : event.currentTarget.valueAsNumber
|
||||
)
|
||||
const value = event.currentTarget.valueAsNumber
|
||||
onChange(Number.isNaN(value) ? '' : value)
|
||||
}
|
||||
|
||||
const { form, handleSubmit, isDirty, isSubmitting } =
|
||||
@@ -141,7 +146,12 @@ export function QuotaSettingsSection({
|
||||
/>
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
{t('Initial quota given to new users')}
|
||||
{t(
|
||||
'Initial quota given to new users ({{formattedQuota}})',
|
||||
{
|
||||
formattedQuota: formatQuotaInputValue(field.value),
|
||||
}
|
||||
)}
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
@@ -189,7 +199,12 @@ export function QuotaSettingsSection({
|
||||
/>
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
{t('Quota given to users who invite others')}
|
||||
{t(
|
||||
'Quota given to users who invite others ({{formattedQuota}})',
|
||||
{
|
||||
formattedQuota: formatQuotaInputValue(field.value),
|
||||
}
|
||||
)}
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
@@ -213,7 +228,9 @@ export function QuotaSettingsSection({
|
||||
/>
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
{t('Quota given to invited users')}
|
||||
{t('Quota given to invited users ({{formattedQuota}})', {
|
||||
formattedQuota: formatQuotaInputValue(field.value),
|
||||
})}
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
|
||||
@@ -24,9 +24,15 @@ import { Dialog } from '@/components/dialog'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { Label } from '@/components/ui/label'
|
||||
import { formatQuota } from '@/lib/format'
|
||||
|
||||
import { QUOTA_PER_DOLLAR } from '../../constants'
|
||||
import {
|
||||
formatQuota,
|
||||
parseQuotaFromDollars,
|
||||
quotaUnitsToDollars,
|
||||
} from '@/lib/format'
|
||||
import {
|
||||
DEFAULT_CURRENCY_CONFIG,
|
||||
useSystemConfigStore,
|
||||
} from '@/stores/system-config-store'
|
||||
|
||||
interface TransferDialogProps {
|
||||
open: boolean
|
||||
@@ -44,17 +50,34 @@ export function TransferDialog({
|
||||
transferring,
|
||||
}: TransferDialogProps) {
|
||||
const { t } = useTranslation()
|
||||
const [amount, setAmount] = useState(QUOTA_PER_DOLLAR)
|
||||
const currencyConfig = useSystemConfigStore(
|
||||
(state) => state.config.currency
|
||||
)
|
||||
const minimumQuota = Math.ceil(
|
||||
currencyConfig.quotaPerUnit > 0
|
||||
? currencyConfig.quotaPerUnit
|
||||
: DEFAULT_CURRENCY_CONFIG.quotaPerUnit
|
||||
)
|
||||
const minimumAmount = quotaUnitsToDollars(minimumQuota)
|
||||
const maximumAmount = quotaUnitsToDollars(availableQuota)
|
||||
const [amount, setAmount] = useState(minimumAmount)
|
||||
const transferQuota = parseQuotaFromDollars(amount)
|
||||
const canTransfer =
|
||||
Number.isFinite(amount) &&
|
||||
transferQuota >= minimumQuota &&
|
||||
transferQuota <= availableQuota
|
||||
|
||||
useEffect(() => {
|
||||
if (open) {
|
||||
// eslint-disable-next-line react-hooks/set-state-in-effect
|
||||
setAmount(QUOTA_PER_DOLLAR)
|
||||
setAmount(minimumAmount)
|
||||
}
|
||||
}, [open])
|
||||
}, [minimumAmount, open])
|
||||
|
||||
const handleConfirm = async () => {
|
||||
const success = await onConfirm(amount)
|
||||
if (!canTransfer) return
|
||||
|
||||
const success = await onConfirm(transferQuota)
|
||||
if (success) {
|
||||
onOpenChange(false)
|
||||
}
|
||||
@@ -80,7 +103,10 @@ export function TransferDialog({
|
||||
>
|
||||
{t('Cancel')}
|
||||
</Button>
|
||||
<Button onClick={handleConfirm} disabled={transferring}>
|
||||
<Button
|
||||
onClick={handleConfirm}
|
||||
disabled={transferring || !canTransfer}
|
||||
>
|
||||
{transferring && <Loader2 className='mr-2 h-4 w-4 animate-spin' />}
|
||||
{t('Transfer')}
|
||||
</Button>
|
||||
@@ -109,13 +135,13 @@ export function TransferDialog({
|
||||
type='number'
|
||||
value={amount}
|
||||
onChange={(e) => setAmount(Number(e.target.value))}
|
||||
min={QUOTA_PER_DOLLAR}
|
||||
max={availableQuota}
|
||||
step={QUOTA_PER_DOLLAR}
|
||||
min={minimumAmount}
|
||||
max={maximumAmount}
|
||||
step={minimumAmount}
|
||||
className='font-mono text-lg'
|
||||
/>
|
||||
<p className='text-muted-foreground text-xs'>
|
||||
{t('Minimum:')} {formatQuota(QUOTA_PER_DOLLAR)}
|
||||
{t('Minimum:')} {formatQuota(minimumQuota)}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -55,11 +55,6 @@ export const PAYMENT_ICON_COLORS = {
|
||||
[PAYMENT_TYPES.WAFFO_PANCAKE]: '#F97316',
|
||||
} as const
|
||||
|
||||
/**
|
||||
* Quota conversion rate: 500,000 units = $1
|
||||
*/
|
||||
export const QUOTA_PER_DOLLAR = 500000
|
||||
|
||||
/**
|
||||
* Default discount rate (no discount)
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user