Validate booking contacts: required FIO and email, RF +7 phone mask, and email domain.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Alexey Novikov
2026-09-17 01:27:38 +03:00
co-authored by Cursor
parent a6a748281f
commit 2ab7c29576
17 changed files with 248 additions and 28 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@moniki/cms",
"version": "1.0.8",
"version": "1.0.9",
"private": true,
"type": "module",
"scripts": {
@@ -1,6 +1,7 @@
import configPromise from '@payload-config'
import { getPayload } from 'payload'
import { NextResponse } from 'next/server'
import { isValidEmailAddress, isValidFullName, isValidRuPhone, normalizeRuPhone } from '@/lib/contact-fields'
function asText(value: unknown) {
return typeof value === "string" ? value.trim() : ""
@@ -17,14 +18,14 @@ function asId(value: unknown) {
export async function POST(req: Request) {
const body = (await req.json().catch(() => ({}))) as Record<string, unknown>
const name = asText(body.name)
const name = asText(body.name).replace(/\s+/g, " ")
const email = asText(body.email)
const phone = asText(body.phone)
const phone = normalizeRuPhone(asText(body.phone))
const branch = asId(body.branch)
const service = asId(body.service)
const doctor = asId(body.doctor)
if (!name || !email || !phone || branch == null || (service == null && doctor == null)) {
if (!isValidFullName(name) || !isValidEmailAddress(email) || !isValidRuPhone(phone) || branch == null || (service == null && doctor == null)) {
return NextResponse.json({ error: "invalid" }, { status: 400 })
}
+39 -3
View File
@@ -1,5 +1,6 @@
import type { CollectionConfig } from 'payload'
import { ADMIN_GROUP } from '../admin/groups'
import { isValidEmailAddress, isValidFullName, isValidRuPhone, normalizeRuPhone } from '../lib/contact-fields'
export const Inquiries: CollectionConfig = {
slug: 'inquiries',
@@ -16,10 +17,45 @@ export const Inquiries: CollectionConfig = {
update: ({ req: { user } }) => user?.role === 'admin',
delete: ({ req: { user } }) => user?.role === 'admin',
},
hooks: {
beforeValidate: [
({ data }) => {
if (!data) return data
if (typeof data.name === 'string') data.name = data.name.trim().replace(/\s+/g, ' ')
if (typeof data.email === 'string') data.email = data.email.trim()
if (typeof data.phone === 'string') {
const phone = normalizeRuPhone(data.phone)
if (phone) data.phone = phone
}
return data
},
],
},
fields: [
{ name: 'name', type: 'text', required: true, label: 'ФИО' },
{ name: 'email', type: 'email', required: true, label: 'E-mail' },
{ name: 'phone', type: 'text', required: true, label: 'Телефон' },
{
name: 'name',
type: 'text',
required: true,
label: 'ФИО',
validate: (value: string | null | undefined) =>
isValidFullName(String(value ?? '')) ? true : 'Укажите фамилию и имя буквами',
},
{
name: 'email',
type: 'email',
required: true,
label: 'E-mail',
validate: (value: string | null | undefined) =>
isValidEmailAddress(String(value ?? '')) ? true : 'Укажите e-mail в формате name@domain.ru',
},
{
name: 'phone',
type: 'text',
required: true,
label: 'Телефон',
validate: (value: string | null | undefined) =>
isValidRuPhone(String(value ?? '')) ? true : 'Укажите телефон в формате +7 и 10 цифр',
},
{ name: 'service', type: 'relationship', relationTo: 'services', label: 'Услуга' },
{ name: 'doctor', type: 'relationship', relationTo: 'doctors', label: 'Врач' },
{ name: 'branch', type: 'relationship', relationTo: 'branches', required: true, label: 'Филиал' },
+25
View File
@@ -0,0 +1,25 @@
const EMAIL_RE = /^[^\s@]+@[^\s@]+\.[A-Za-z0-9-]{2,}$/
export function isValidFullName(value: string) {
const name = value.trim().replace(/\s+/g, " ")
if (name.length < 3) return false
const parts = name.split(" ").filter(Boolean)
if (parts.length < 2) return false
return /^[\p{L}][\p{L}\s'-]*$/u.test(name)
}
export function isValidEmailAddress(value: string) {
return EMAIL_RE.test(value.trim())
}
export function normalizeRuPhone(value: string) {
let digits = value.replace(/\D/g, "")
if (digits.startsWith("8")) digits = `7${digits.slice(1)}`
if (!digits.startsWith("7")) digits = `7${digits}`
digits = digits.slice(0, 11)
return digits.length === 11 ? `+${digits}` : ""
}
export function isValidRuPhone(value: string) {
return /^\+7\d{10}$/.test(normalizeRuPhone(value))
}