mirror of
https://github.com/chillpadclub/bedolaga-cabinet.git
synced 2026-07-28 09:33:46 +00:00
fix: standardize admin form inputs, validation, and sync with backend constraints
- Unified all inputs to use .input CSS class, consistent label styles, and btn-primary/btn-secondary buttons - Fixed number inputs to use number | '' pattern allowing field clearing without default replacement - Added field-level validation with inline error borders and messages - Synced frontend constraints with backend Pydantic schemas (maxLength, min/max values) - Fixed toggle switch dimensions across all admin forms
This commit is contained in:
@@ -5,6 +5,7 @@ import { useTranslation } from 'react-i18next';
|
||||
import { adminPaymentMethodsApi } from '../api/adminPaymentMethods';
|
||||
import type { PromoGroupSimple } from '../types';
|
||||
import { usePlatform } from '../platform/hooks/usePlatform';
|
||||
import { createNumberInputHandler, toNumber } from '../utils/inputHelpers';
|
||||
|
||||
const BackIcon = () => (
|
||||
<svg
|
||||
@@ -89,8 +90,8 @@ export default function AdminPaymentMethodEdit() {
|
||||
const [isEnabled, setIsEnabled] = useState(false);
|
||||
const [customName, setCustomName] = useState('');
|
||||
const [subOptions, setSubOptions] = useState<Record<string, boolean>>({});
|
||||
const [minAmount, setMinAmount] = useState('');
|
||||
const [maxAmount, setMaxAmount] = useState('');
|
||||
const [minAmount, setMinAmount] = useState<number | ''>('');
|
||||
const [maxAmount, setMaxAmount] = useState<number | ''>('');
|
||||
const [userTypeFilter, setUserTypeFilter] = useState<'all' | 'telegram' | 'email'>('all');
|
||||
const [firstTopupFilter, setFirstTopupFilter] = useState<'any' | 'yes' | 'no'>('any');
|
||||
const [promoGroupFilterMode, setPromoGroupFilterMode] = useState<'all' | 'selected'>('all');
|
||||
@@ -102,8 +103,8 @@ export default function AdminPaymentMethodEdit() {
|
||||
setIsEnabled(config.is_enabled);
|
||||
setCustomName(config.display_name || '');
|
||||
setSubOptions(config.sub_options || {});
|
||||
setMinAmount(config.min_amount_kopeks?.toString() || '');
|
||||
setMaxAmount(config.max_amount_kopeks?.toString() || '');
|
||||
setMinAmount(config.min_amount_kopeks ?? '');
|
||||
setMaxAmount(config.max_amount_kopeks ?? '');
|
||||
setUserTypeFilter(config.user_type_filter);
|
||||
setFirstTopupFilter(config.first_topup_filter);
|
||||
setPromoGroupFilterMode(config.promo_group_filter_mode);
|
||||
@@ -144,13 +145,13 @@ export default function AdminPaymentMethodEdit() {
|
||||
}
|
||||
|
||||
// Amounts
|
||||
if (minAmount.trim()) {
|
||||
data.min_amount_kopeks = parseInt(minAmount, 10) || null;
|
||||
if (minAmount !== '') {
|
||||
data.min_amount_kopeks = toNumber(minAmount) || null;
|
||||
} else {
|
||||
data.reset_min_amount = true;
|
||||
}
|
||||
if (maxAmount.trim()) {
|
||||
data.max_amount_kopeks = parseInt(maxAmount, 10) || null;
|
||||
if (maxAmount !== '') {
|
||||
data.max_amount_kopeks = toNumber(maxAmount) || null;
|
||||
} else {
|
||||
data.reset_max_amount = true;
|
||||
}
|
||||
@@ -236,13 +237,13 @@ export default function AdminPaymentMethodEdit() {
|
||||
</div>
|
||||
<button
|
||||
onClick={() => setIsEnabled(!isEnabled)}
|
||||
className={`relative h-7 w-12 rounded-full transition-colors ${
|
||||
className={`relative h-6 w-11 rounded-full transition-colors ${
|
||||
isEnabled ? 'bg-accent-500' : 'bg-dark-600'
|
||||
}`}
|
||||
>
|
||||
<span
|
||||
className={`absolute top-0.5 h-6 w-6 rounded-full bg-white shadow transition-transform ${
|
||||
isEnabled ? 'left-[calc(100%-1.625rem)]' : 'left-0.5'
|
||||
className={`absolute top-1 h-4 w-4 rounded-full bg-white transition-transform ${
|
||||
isEnabled ? 'left-6' : 'left-1'
|
||||
}`}
|
||||
/>
|
||||
</button>
|
||||
@@ -250,7 +251,7 @@ export default function AdminPaymentMethodEdit() {
|
||||
|
||||
{/* Display name */}
|
||||
<div>
|
||||
<label className="mb-2 block text-sm font-medium text-dark-200">
|
||||
<label className="mb-2 block text-sm font-medium text-dark-300">
|
||||
{t('admin.paymentMethods.displayName')}
|
||||
</label>
|
||||
<input
|
||||
@@ -268,7 +269,7 @@ export default function AdminPaymentMethodEdit() {
|
||||
{/* Sub-options */}
|
||||
{config.available_sub_options && config.available_sub_options.length > 0 && (
|
||||
<div>
|
||||
<label className="mb-2 block text-sm font-medium text-dark-200">
|
||||
<label className="mb-2 block text-sm font-medium text-dark-300">
|
||||
{t('admin.paymentMethods.subOptions')}
|
||||
</label>
|
||||
<div className="space-y-2">
|
||||
@@ -302,25 +303,25 @@ export default function AdminPaymentMethodEdit() {
|
||||
{/* Min/Max amounts */}
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
<div>
|
||||
<label className="mb-2 block text-sm font-medium text-dark-200">
|
||||
<label className="mb-2 block text-sm font-medium text-dark-300">
|
||||
{t('admin.paymentMethods.minAmount')}
|
||||
</label>
|
||||
<input
|
||||
type="number"
|
||||
value={minAmount}
|
||||
onChange={(e) => setMinAmount(e.target.value)}
|
||||
onChange={createNumberInputHandler(setMinAmount, 0)}
|
||||
placeholder={config.default_min_amount_kopeks.toString()}
|
||||
className="input"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="mb-2 block text-sm font-medium text-dark-200">
|
||||
<label className="mb-2 block text-sm font-medium text-dark-300">
|
||||
{t('admin.paymentMethods.maxAmount')}
|
||||
</label>
|
||||
<input
|
||||
type="number"
|
||||
value={maxAmount}
|
||||
onChange={(e) => setMaxAmount(e.target.value)}
|
||||
onChange={createNumberInputHandler(setMaxAmount, 0)}
|
||||
placeholder={config.default_max_amount_kopeks.toString()}
|
||||
className="input"
|
||||
/>
|
||||
|
||||
Reference in New Issue
Block a user