Browse Source

fix(dashboard): Enforce numeric min/max in promotions & generated inputs

Michael Bromley 3 months ago
parent
commit
2d7f9271a9

+ 6 - 2
packages/dashboard/src/app/routes/_authenticated/_promotions/promotions_.$id.tsx

@@ -203,13 +203,17 @@ function PromotionDetailPage() {
                             control={form.control}
                             control={form.control}
                             name="perCustomerUsageLimit"
                             name="perCustomerUsageLimit"
                             label={<Trans>Per customer usage limit</Trans>}
                             label={<Trans>Per customer usage limit</Trans>}
-                            render={({ field }) => <NumberInput {...field} value={field.value ?? ''} />}
+                            render={({ field }) => (
+                                <NumberInput {...field} value={field.value ?? ''} min={0} max={1000} />
+                            )}
                         />
                         />
                         <FormFieldWrapper
                         <FormFieldWrapper
                             control={form.control}
                             control={form.control}
                             name="usageLimit"
                             name="usageLimit"
                             label={<Trans>Usage limit</Trans>}
                             label={<Trans>Usage limit</Trans>}
-                            render={({ field }) => <NumberInput {...field} value={field.value ?? ''} />}
+                            render={({ field }) => (
+                                <NumberInput {...field} value={field.value ?? ''} min={0} max={1000} />
+                            )}
                         />
                         />
                     </DetailFormGrid>
                     </DetailFormGrid>
                 </PageBlock>
                 </PageBlock>

+ 5 - 2
packages/dashboard/src/lib/components/data-input/affixed-input.tsx

@@ -13,7 +13,7 @@ export type AffixedInputProps = Omit<React.InputHTMLAttributes<HTMLInputElement>
 /**
 /**
  * @description
  * @description
  * A component for displaying an input with a prefix and/or a suffix.
  * A component for displaying an input with a prefix and/or a suffix.
- * 
+ *
  * @example
  * @example
  * ```tsx
  * ```tsx
  * <AffixedInput
  * <AffixedInput
@@ -24,7 +24,7 @@ export type AffixedInputProps = Omit<React.InputHTMLAttributes<HTMLInputElement>
  *     onChange={e => field.onChange(e.target.valueAsNumber)}
  *     onChange={e => field.onChange(e.target.valueAsNumber)}
  * />
  * />
  * ```
  * ```
- * 
+ *
  * @docsCategory form-components
  * @docsCategory form-components
  * @docsPage AffixedInput
  * @docsPage AffixedInput
  */
  */
@@ -65,6 +65,9 @@ export function AffixedInput({ prefix, suffix, className = '', ...props }: Reado
                 className={className}
                 className={className}
                 style={style}
                 style={style}
                 disabled={readOnly}
                 disabled={readOnly}
+                min={props.min}
+                max={props.max}
+                step={props.step}
             />
             />
             {suffix && (
             {suffix && (
                 <span ref={suffixRef} className="absolute right-3 text-muted-foreground whitespace-nowrap">
                 <span ref={suffixRef} className="absolute right-3 text-muted-foreground whitespace-nowrap">

+ 10 - 4
packages/dashboard/src/lib/components/data-input/number-input.tsx

@@ -4,6 +4,12 @@ import { Input } from '@/vdb/components/ui/input.js';
 import { DashboardFormComponentProps } from '@/vdb/framework/form-engine/form-engine-types.js';
 import { DashboardFormComponentProps } from '@/vdb/framework/form-engine/form-engine-types.js';
 import { isReadonlyField } from '@/vdb/framework/form-engine/utils.js';
 import { isReadonlyField } from '@/vdb/framework/form-engine/utils.js';
 
 
+export type NumberInputProps = DashboardFormComponentProps & {
+    min?: number;
+    max?: number;
+    step?: number;
+};
+
 /**
 /**
  * @description
  * @description
  * A component for displaying a numeric value.
  * A component for displaying a numeric value.
@@ -11,12 +17,12 @@ import { isReadonlyField } from '@/vdb/framework/form-engine/utils.js';
  * @docsCategory form-components
  * @docsCategory form-components
  * @docsPage NumberInput
  * @docsPage NumberInput
  */
  */
-export function NumberInput({ fieldDef, onChange, ...fieldProps }: Readonly<DashboardFormComponentProps>) {
+export function NumberInput({ fieldDef, onChange, ...fieldProps }: Readonly<NumberInputProps>) {
     const readOnly = fieldProps.disabled || isReadonlyField(fieldDef);
     const readOnly = fieldProps.disabled || isReadonlyField(fieldDef);
     const isFloat = fieldDef ? fieldDef.type === 'float' : false;
     const isFloat = fieldDef ? fieldDef.type === 'float' : false;
-    const min = fieldDef?.ui?.min;
-    const max = fieldDef?.ui?.max;
-    const step = fieldDef?.ui?.step || (isFloat ? 0.01 : 1);
+    const min = fieldProps.min ?? fieldDef?.ui?.min;
+    const max = fieldProps.max ?? fieldDef?.ui?.max;
+    const step = fieldProps.step ?? (fieldDef?.ui?.step || (isFloat ? 0.01 : 1));
     const prefix = fieldDef?.ui?.prefix;
     const prefix = fieldDef?.ui?.prefix;
     const suffix = fieldDef?.ui?.suffix;
     const suffix = fieldDef?.ui?.suffix;
     const shouldUseAffixedInput = prefix || suffix;
     const shouldUseAffixedInput = prefix || suffix;