Files
supabase/apps/studio/components/interfaces/Storage/BucketDataProtectionFields.schema.ts
T
Claude 7b1dd94c79 use zod schema validation for object-versioning fields, not manual setError
- Move min/max/required validation for version_expiry_days and
  max_noncurrent_versions into a shared superRefine (BucketDataProtectionFields.schema.ts),
  called from each modal's own superRefine, instead of imperative
  validateVersioningFields() + form.setError() calls in onSubmit. Errors
  now surface the same way as the rest of the form (FormItemLayout/FormMessage),
  matching Studio's established form-error convention.
- Switch the fields to the '' empty-sentinel + z.coerce.number() union
  pattern (per the react-hook-form skill) instead of z.string().optional(),
  keeping inputs fully controlled without special-casing undefined.
- Build each modal's schema dynamically via useMemo(() => schema(planLimits), ...)
  since the versioning bounds depend on the async-loaded org plan.
- Replace destructured useFormContext().watch() with useWatch({ control, name })
  in BucketDataProtectionFields, avoiding the ratcheted no-use-watch lint warning.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TiUEvmC84bRsteqsWXHY2p
2026-08-05 15:55:16 +00:00

71 lines
2.3 KiB
TypeScript

import { z } from 'zod'
import type { VersioningPlanLimits } from './StorageProtection.constants'
/**
* Shared zod fields for the object-versioning controls in the create/edit
* bucket modals. `''` is the in-form empty sentinel (per the react-hook-form
* convention) so the number inputs stay fully deletable/controlled; it's
* coerced to a real number on parse.
*/
const versioningNumberField = z.union([z.literal(''), z.coerce.number().int()])
export const bucketProtectionFormFields = {
enable_versioning: z.boolean().default(false),
version_expiry_days: versioningNumberField.default(''),
max_noncurrent_versions: versioningNumberField.default(''),
}
export interface BucketProtectionFormValues {
enable_versioning: boolean
version_expiry_days: '' | number
max_noncurrent_versions: '' | number
}
/**
* Validates the versioning fields against the org's plan limits. Call this
* from the parent modal's `.superRefine` so errors surface through the usual
* `FormItemLayout`/`FormMessage` rendering.
*/
export const superRefineBucketProtection = (
data: BucketProtectionFormValues,
ctx: z.RefinementCtx,
planLimits: VersioningPlanLimits | null
) => {
if (!data.enable_versioning || !planLimits) return
if (data.version_expiry_days === '') {
ctx.addIssue({
path: ['version_expiry_days'],
code: z.ZodIssueCode.custom,
message: 'Please provide a retention period',
})
} else if (
data.version_expiry_days < planLimits.minRetentionDays ||
data.version_expiry_days > planLimits.maxRetentionDays
) {
ctx.addIssue({
path: ['version_expiry_days'],
code: z.ZodIssueCode.custom,
message: `Must be between ${planLimits.minRetentionDays} and ${planLimits.maxRetentionDays} days on your plan`,
})
}
if (data.max_noncurrent_versions === '') {
ctx.addIssue({
path: ['max_noncurrent_versions'],
code: z.ZodIssueCode.custom,
message: 'Please provide a value',
})
} else if (
data.max_noncurrent_versions < planLimits.minVersions ||
data.max_noncurrent_versions > planLimits.maxVersions
) {
ctx.addIssue({
path: ['max_noncurrent_versions'],
code: z.ZodIssueCode.custom,
message: `Must be between ${planLimits.minVersions} and ${planLimits.maxVersions} versions on your plan`,
})
}
}