Initial commit: Port Nimara CRM (Layers 0-4)
Full CRM rebuild with Next.js 15, TypeScript, Tailwind, Drizzle ORM, PostgreSQL, Redis, BullMQ, MinIO, and Socket.io. Includes 461 source files covering clients, berths, interests/pipeline, documents/EOI, expenses/invoices, email, notifications, dashboard, admin, and client portal. CI/CD via Gitea Actions with Docker builds. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
65
src/lib/validators/clients.ts
Normal file
65
src/lib/validators/clients.ts
Normal file
@@ -0,0 +1,65 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import { baseListQuerySchema } from '@/lib/api/route-helpers';
|
||||
|
||||
// ─── Contact sub-schema ──────────────────────────────────────────────────────
|
||||
|
||||
export const contactSchema = z.object({
|
||||
channel: z.enum(['email', 'phone', 'whatsapp', 'other']),
|
||||
value: z.string().min(1),
|
||||
label: z.string().optional(),
|
||||
isPrimary: z.boolean().optional().default(false),
|
||||
notes: z.string().optional(),
|
||||
});
|
||||
|
||||
// ─── Create ──────────────────────────────────────────────────────────────────
|
||||
|
||||
export const createClientSchema = z.object({
|
||||
fullName: z.string().min(1).max(200),
|
||||
contacts: z.array(contactSchema).min(1, 'At least one contact is required'),
|
||||
companyName: z.string().optional(),
|
||||
nationality: z.string().optional(),
|
||||
isProxy: z.boolean().optional().default(false),
|
||||
proxyType: z.string().optional(),
|
||||
actualOwnerName: z.string().optional(),
|
||||
yachtName: z.string().optional(),
|
||||
yachtLengthFt: z.string().optional(),
|
||||
yachtWidthFt: z.string().optional(),
|
||||
yachtDraftFt: z.string().optional(),
|
||||
yachtLengthM: z.string().optional(),
|
||||
yachtWidthM: z.string().optional(),
|
||||
yachtDraftM: z.string().optional(),
|
||||
berthSizeDesired: z.string().optional(),
|
||||
preferredContactMethod: z.enum(['email', 'phone', 'whatsapp']).optional(),
|
||||
preferredLanguage: z.string().optional(),
|
||||
timezone: z.string().optional(),
|
||||
source: z.enum(['website', 'manual', 'referral', 'broker']).optional(),
|
||||
sourceDetails: z.string().optional(),
|
||||
tagIds: z.array(z.string()).optional().default([]),
|
||||
});
|
||||
|
||||
// ─── Update ──────────────────────────────────────────────────────────────────
|
||||
|
||||
export const updateClientSchema = createClientSchema.omit({ contacts: true, tagIds: true }).partial();
|
||||
|
||||
// ─── List ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
export const listClientsSchema = baseListQuerySchema.extend({
|
||||
source: z.enum(['website', 'manual', 'referral', 'broker']).optional(),
|
||||
nationality: z.string().optional(),
|
||||
isProxy: z
|
||||
.string()
|
||||
.transform((v) => v === 'true')
|
||||
.optional(),
|
||||
tagIds: z
|
||||
.string()
|
||||
.transform((v) => v.split(',').filter(Boolean))
|
||||
.optional(),
|
||||
});
|
||||
|
||||
// ─── Types ────────────────────────────────────────────────────────────────────
|
||||
|
||||
export type ContactInput = z.infer<typeof contactSchema>;
|
||||
export type CreateClientInput = z.infer<typeof createClientSchema>;
|
||||
export type UpdateClientInput = z.infer<typeof updateClientSchema>;
|
||||
export type ListClientsInput = z.infer<typeof listClientsSchema>;
|
||||
Reference in New Issue
Block a user