From febde06d86c71c0fa449c7bfba33691e1f594bb3 Mon Sep 17 00:00:00 2001
From: Gildas Garcia <1122076+djhi@users.noreply.github.com>
Date: Mon, 13 Apr 2026 10:31:57 +0200
Subject: [PATCH] chore: migrate smtp form to `zod`
---
.../interfaces/Auth/SmtpForm/SmtpForm.tsx | 140 ++++++++----------
1 file changed, 62 insertions(+), 78 deletions(-)
diff --git a/apps/studio/components/interfaces/Auth/SmtpForm/SmtpForm.tsx b/apps/studio/components/interfaces/Auth/SmtpForm/SmtpForm.tsx
index 9d5343a4301..eccf26ca978 100644
--- a/apps/studio/components/interfaces/Auth/SmtpForm/SmtpForm.tsx
+++ b/apps/studio/components/interfaces/Auth/SmtpForm/SmtpForm.tsx
@@ -1,8 +1,8 @@
-import { yupResolver } from '@hookform/resolvers/yup'
+import { zodResolver } from '@hookform/resolvers/zod'
import { PermissionAction } from '@supabase/shared-types/out/constants'
import { useParams } from 'common'
import { useEffect, useState } from 'react'
-import { useForm } from 'react-hook-form'
+import { SubmitHandler, useForm } from 'react-hook-form'
import { toast } from 'sonner'
import {
Button,
@@ -23,7 +23,7 @@ import {
import { Admonition, PageSection, PageSectionContent } from 'ui-patterns'
import { Input } from 'ui-patterns/DataInputs/Input'
import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout'
-import * as yup from 'yup'
+import * as z from 'zod'
import { urlRegex } from '../Auth.constants'
import { defaultDisabledSmtpFormValues } from './SmtpForm.constants'
@@ -35,16 +35,57 @@ import { useAuthConfigQuery } from '@/data/auth/auth-config-query'
import { useAuthConfigUpdateMutation } from '@/data/auth/auth-config-update-mutation'
import { useAsyncCheckPermissions } from '@/hooks/misc/useCheckPermissions'
-interface SmtpFormValues {
- SMTP_ADMIN_EMAIL?: string
- SMTP_SENDER_NAME?: string
- SMTP_HOST?: string
- SMTP_PORT?: number
- SMTP_MAX_FREQUENCY?: number
- SMTP_USER?: string
- SMTP_PASS?: string
- ENABLE_SMTP: boolean
-}
+const smtpEnabledSchema = z.object({
+ ENABLE_SMTP: z.literal(true),
+ SMTP_ADMIN_EMAIL: z
+ .string()
+ .trim()
+ .min(1, 'Sender email address is required')
+ .email('Must be a valid email'),
+ SMTP_SENDER_NAME: z.string().trim().min(1, 'Sender name is required'),
+ SMTP_HOST: z
+ .string()
+ .trim()
+ .min(1, 'Host URL is required')
+ .regex(urlRegex({ excludeSimpleDomains: false }), 'Must be a valid URL or IP address'),
+ SMTP_PORT: z.preprocess(
+ (val) => (val === '' || val == null ? undefined : val),
+ z.coerce
+ .number({
+ required_error: 'Port number is required',
+ invalid_type_error: 'Port number is required',
+ })
+ .min(1, 'Must be a valid port number more than 0')
+ .max(65535, 'Must be a valid port number no more than 65535')
+ ),
+ SMTP_MAX_FREQUENCY: z.preprocess(
+ (val) => (val === '' || val == null ? undefined : val),
+ z.coerce
+ .number({
+ required_error: 'Rate limit is required',
+ invalid_type_error: 'Rate limit is required',
+ })
+ .min(1, 'Must be more than 0')
+ .max(32767, 'Must not be more than 32,767 an hour')
+ ),
+ SMTP_USER: z.string().trim().min(1, 'SMTP Username is required'),
+ SMTP_PASS: z.string().trim().min(1, 'SMTP Password is required'),
+})
+
+const smtpDisabledSchema = z.object({
+ ENABLE_SMTP: z.literal(false),
+ SMTP_ADMIN_EMAIL: z.string().optional(),
+ SMTP_SENDER_NAME: z.string().optional(),
+ SMTP_HOST: z.string().optional(),
+ SMTP_PORT: z.number().optional(),
+ SMTP_MAX_FREQUENCY: z.number().optional(),
+ SMTP_USER: z.string().optional(),
+ SMTP_PASS: z.string().optional(),
+})
+
+const smtpSchema = z.discriminatedUnion('ENABLE_SMTP', [smtpEnabledSchema, smtpDisabledSchema])
+
+type SmtpFormValues = z.infer