Files
property-management-network/lib/validations/index.ts
T
Leon SerfatyandClaude Opus 4.8 857b9a7811 Initial import: property management SaaS + security hardening + admin dashboard
Property Management Network — Next.js 16 (App Router), Better Auth,
Drizzle ORM over PostgreSQL, Stripe, OpenAI, Resend.

Includes:
- Security hardening: access-control/IDOR fixes, TLS-by-default DB layer,
  constant-time cron auth, strict security headers, atomic AI quota gating,
  HTML/email output encoding, demo-backdoor disabled in production.
- Superadmin dashboard at /admin (overview/MRR, server-paginated users with
  ban/impersonate/plan/delete, billing, platform activity + admin audit log,
  AI usage, system health) via the Better Auth admin plugin.
- Seed/migration utility scripts under scripts/.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 20:36:07 -04:00

121 lines
4.9 KiB
TypeScript

import { z } from "zod"
export const propertySchema = z.object({
name: z.string().min(1, "Property name is required"),
address_line1: z.string().min(1, "Address is required"),
address_line2: z.string().optional(),
city: z.string().min(1, "City is required"),
state: z.string().optional(),
postal_code: z.string().optional(),
country: z.string().default("US"),
property_type: z.enum(["residential", "commercial", "mixed"]).default("residential"),
total_units: z.number().int().min(1).default(1),
notes: z.string().optional(),
image_url: z.string().url().nullable().optional(),
})
export const unitSchema = z.object({
property_id: z.string().uuid(),
unit_number: z.string().min(1, "Unit number is required"),
bedrooms: z.number().int().min(0).default(1),
bathrooms: z.number().min(0).default(1),
sq_ft: z.number().int().positive().optional(),
rent_amount: z.number().positive("Rent amount must be positive"),
status: z.enum(["vacant", "occupied", "maintenance", "unavailable"]).default("vacant"),
notes: z.string().optional(),
})
export const tenantSchema = z.object({
property_id: z.string().uuid(),
unit_id: z.string().uuid().optional(),
first_name: z.string().min(1, "First name is required"),
last_name: z.string().min(1, "Last name is required"),
email: z.string().email("Invalid email").optional().or(z.literal("")),
phone: z.string().optional(),
emergency_contact_name: z.string().optional(),
emergency_contact_phone: z.string().optional(),
move_in_date: z.string().optional(),
notes: z.string().optional(),
})
export const rentPaymentSchema = z.object({
tenant_id: z.string().uuid(),
property_id: z.string().uuid(),
unit_id: z.string().uuid().optional(),
amount: z.number().positive("Amount must be positive"),
due_date: z.string().min(1, "Due date is required"),
paid_date: z.string().optional(),
status: z.enum(["pending", "paid", "overdue", "partial", "waived"]).default("pending"),
payment_method: z.string().optional(),
notes: z.string().optional(),
})
export const maintenanceSchema = z.object({
property_id: z.string().uuid(),
unit_id: z.string().uuid().optional(),
tenant_id: z.string().uuid().optional(),
title: z.string().min(1, "Title is required"),
description: z.string().min(1, "Description is required"),
category: z.enum(["plumbing", "electrical", "hvac", "appliance", "structural", "pest", "general"]).default("general"),
priority: z.enum(["low", "medium", "high", "emergency"]).default("medium"),
assigned_to: z.string().optional(),
estimated_cost: z.number().positive().optional(),
})
export const leaseSchema = z.object({
tenant_id: z.string().uuid(),
property_id: z.string().uuid(),
unit_id: z.string().uuid().optional(),
lease_start: z.string().min(1, "Lease start date is required"),
lease_end: z.string().min(1, "Lease end date is required"),
rent_amount: z.number().positive("Rent amount must be positive"),
security_deposit: z.number().positive().optional(),
lease_type: z.enum(["fixed", "month_to_month"]).default("fixed"),
auto_renew: z.boolean().default(false),
notes: z.string().optional(),
})
export const expenseSchema = z.object({
property_id: z.string().uuid(),
unit_id: z.string().uuid().optional(),
category: z.enum(["repairs", "utilities", "insurance", "mortgage", "taxes", "management", "supplies", "other"]),
description: z.string().min(1, "Description is required"),
amount: z.number().positive("Amount must be positive"),
expense_date: z.string().min(1, "Date is required"),
vendor: z.string().optional(),
is_recurring: z.boolean().default(false),
recurrence: z.enum(["monthly", "quarterly", "yearly"]).optional(),
notes: z.string().optional(),
})
export type PropertyFormValues = z.infer<typeof propertySchema>
export type UnitFormValues = z.infer<typeof unitSchema>
export type TenantFormValues = z.infer<typeof tenantSchema>
export type RentPaymentFormValues = z.infer<typeof rentPaymentSchema>
export type MaintenanceFormValues = z.infer<typeof maintenanceSchema>
export type LeaseFormValues = z.infer<typeof leaseSchema>
export const vendorSchema = z.object({
name: z.string().min(1, "Vendor name is required"),
trade: z.string().optional(),
phone: z.string().optional(),
email: z.string().email("Invalid email").optional().or(z.literal("")),
notes: z.string().optional(),
property_id: z.string().uuid().optional().or(z.literal("")),
})
export const inspectionSchema = z.object({
property_id: z.string().uuid("Property is required"),
unit_id: z.string().uuid().optional().or(z.literal("")),
type: z.enum(["move_in", "move_out", "routine"]),
date: z.string().min(1, "Date is required"),
notes: z.string().optional(),
})
export const paymentLinkSchema = z.object({
payment_id: z.string().uuid("Valid payment ID is required"),
})
export type ExpenseFormValues = z.infer<typeof expenseSchema>
export type VendorFormValues = z.infer<typeof vendorSchema>
export type InspectionFormValues = z.infer<typeof inspectionSchema>