Replaces every em-dash and en-dash with regular ASCII hyphens across comments, JSX strings, and dev-facing logs. Mostly cosmetic but stops the inconsistent mix that crept in over the last few months (some files used em-dashes in comments, others didn't, some used both). Bundles two small dashboard-layout tweaks that touch a couple of already-modified files: - (dashboard)/layout.tsx main padding goes from p-6 to pt-3 px-6 pb-6 so page content sits closer to the topbar. - Sidebar now receives the ports list it needs for the footer port switcher. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
123 lines
5.3 KiB
TypeScript
123 lines
5.3 KiB
TypeScript
import { z } from 'zod';
|
|
|
|
import { baseListQuerySchema } from '@/lib/api/route-helpers';
|
|
import {
|
|
optionalCountryIsoSchema,
|
|
optionalIanaTimezoneSchema,
|
|
optionalPhoneE164Schema,
|
|
optionalSubdivisionIsoSchema,
|
|
} from '@/lib/validators/i18n';
|
|
|
|
// ─── Residential client ──────────────────────────────────────────────────────
|
|
|
|
export const createResidentialClientSchema = z.object({
|
|
fullName: z.string().min(1).max(200),
|
|
email: z
|
|
.string()
|
|
.email()
|
|
.optional()
|
|
.or(z.literal('').transform(() => undefined)),
|
|
phone: z.string().optional(),
|
|
/** E.164-normalized phone alongside the legacy free-text `phone`. */
|
|
phoneE164: optionalPhoneE164Schema.optional(),
|
|
/** ISO-3166-1 alpha-2 the phone was parsed against. */
|
|
phoneCountry: optionalCountryIsoSchema.optional(),
|
|
/** ISO-3166-1 alpha-2 nationality. */
|
|
nationalityIso: optionalCountryIsoSchema.optional(),
|
|
/** IANA timezone. */
|
|
timezone: optionalIanaTimezoneSchema.optional(),
|
|
placeOfResidence: z.string().optional(),
|
|
/** ISO-3166-1 alpha-2 country of residence. */
|
|
placeOfResidenceCountryIso: optionalCountryIsoSchema.optional(),
|
|
/** ISO 3166-2 subdivision code for place of residence. */
|
|
subdivisionIso: optionalSubdivisionIsoSchema.optional(),
|
|
preferredContactMethod: z.enum(['email', 'phone']).optional(),
|
|
status: z.enum(['prospect', 'active', 'inactive']).optional().default('prospect'),
|
|
source: z.enum(['website', 'manual', 'referral', 'broker']).optional(),
|
|
notes: z.string().optional(),
|
|
});
|
|
|
|
export const updateResidentialClientSchema = createResidentialClientSchema.partial();
|
|
|
|
export const listResidentialClientsSchema = baseListQuerySchema.extend({
|
|
status: z.enum(['prospect', 'active', 'inactive']).optional(),
|
|
source: z.enum(['website', 'manual', 'referral', 'broker']).optional(),
|
|
});
|
|
|
|
// ─── Residential interest ────────────────────────────────────────────────────
|
|
|
|
export const PIPELINE_STAGES = [
|
|
'new',
|
|
'contacted',
|
|
'viewing_scheduled',
|
|
'offer_made',
|
|
'offer_accepted',
|
|
'closed_won',
|
|
'closed_lost',
|
|
] as const;
|
|
|
|
export const createResidentialInterestSchema = z.object({
|
|
residentialClientId: z.string().min(1),
|
|
pipelineStage: z.enum(PIPELINE_STAGES).optional().default('new'),
|
|
source: z.enum(['website', 'manual', 'referral', 'broker']).optional(),
|
|
notes: z.string().optional(),
|
|
preferences: z.string().optional(),
|
|
assignedTo: z.string().optional(),
|
|
});
|
|
|
|
export const updateResidentialInterestSchema = createResidentialInterestSchema
|
|
.omit({ residentialClientId: true })
|
|
.partial();
|
|
|
|
export const listResidentialInterestsSchema = baseListQuerySchema.extend({
|
|
pipelineStage: z.enum(PIPELINE_STAGES).optional(),
|
|
assignedTo: z.string().optional(),
|
|
residentialClientId: z.string().optional(),
|
|
});
|
|
|
|
// ─── Public website inquiry ──────────────────────────────────────────────────
|
|
|
|
/**
|
|
* Shape posted by the public website's residential interest form. Coerces
|
|
* to internal create-shapes inside the public route.
|
|
*
|
|
* The legacy `phone` field stays free-text - older website builds may post
|
|
* raw international strings ('+44 7700 900123'). The route handler parses
|
|
* it server-side into `phoneE164` + `phoneCountry`. Newer website builds
|
|
* can post normalized values directly.
|
|
*/
|
|
export const publicResidentialInquirySchema = z.object({
|
|
firstName: z.string().min(1),
|
|
lastName: z.string().min(1),
|
|
email: z.string().email(),
|
|
phone: z.string().min(1),
|
|
/** Pre-normalized E.164 form, optional for backwards compat. */
|
|
phoneE164: optionalPhoneE164Schema.optional(),
|
|
/** ISO-3166-1 alpha-2 the phone was parsed against. */
|
|
phoneCountry: optionalCountryIsoSchema.optional(),
|
|
/** ISO-3166-1 alpha-2 nationality. */
|
|
nationalityIso: optionalCountryIsoSchema.optional(),
|
|
/** IANA timezone. */
|
|
timezone: optionalIanaTimezoneSchema.optional(),
|
|
placeOfResidence: z.string().optional(),
|
|
/** ISO-3166-1 alpha-2 country of residence. */
|
|
placeOfResidenceCountryIso: optionalCountryIsoSchema.optional(),
|
|
/** ISO 3166-2 subdivision code for place of residence. */
|
|
subdivisionIso: optionalSubdivisionIsoSchema.optional(),
|
|
preferredContactMethod: z.enum(['email', 'phone']).optional(),
|
|
notes: z.string().optional(),
|
|
preferences: z.string().optional(),
|
|
});
|
|
|
|
// ─── Types ────────────────────────────────────────────────────────────────────
|
|
|
|
export type CreateResidentialClientInput = z.infer<typeof createResidentialClientSchema>;
|
|
export type UpdateResidentialClientInput = z.infer<typeof updateResidentialClientSchema>;
|
|
export type ListResidentialClientsInput = z.infer<typeof listResidentialClientsSchema>;
|
|
|
|
export type CreateResidentialInterestInput = z.infer<typeof createResidentialInterestSchema>;
|
|
export type UpdateResidentialInterestInput = z.infer<typeof updateResidentialInterestSchema>;
|
|
export type ListResidentialInterestsInput = z.infer<typeof listResidentialInterestsSchema>;
|
|
|
|
export type PublicResidentialInquiryInput = z.infer<typeof publicResidentialInquirySchema>;
|