import { z } from 'zod'; export interface UserData { id?: string; firstname: string; lastname: string; email: string; phoneNumber?: string; description?: string; companyName?: string; companyOverview?: string; companyWebsite?: string; companyLocation?: string; offeredServices?: string; areasServed?: string[]; hasProfile?: boolean; hasCompanyLogo?: boolean; licensedIn?: string[]; gender?: 'male' | 'female'; customerType?: 'buyer' | 'seller' | 'professional'; customerSubType?: 'broker' | 'cpa' | 'attorney' | 'titleCompany' | 'surveyor' | 'appraiser'; created?: Date; updated?: Date; } export type SortByOptions = 'priceAsc' | 'priceDesc' | 'creationDateFirst' | 'creationDateLast' | 'nameAsc' | 'nameDesc' | 'srAsc' | 'srDesc' | 'cfAsc' | 'cfDesc'; export type SortByTypes = 'professional' | 'listing' | 'business' | 'commercial'; export type Gender = 'male' | 'female'; export type CustomerType = 'buyer' | 'seller' | 'professional'; export type CustomerSubType = 'broker' | 'cpa' | 'attorney' | 'titleCompany' | 'surveyor' | 'appraiser'; export type ListingsCategory = 'commercialProperty' | 'business'; export const GenderEnum = z.enum(['male', 'female']); export const CustomerTypeEnum = z.enum(['buyer', 'seller', 'professional']); export const SubscriptionTypeEnum = z.enum(['free', 'professional', 'broker']); export const CustomerSubTypeEnum = z.enum(['broker', 'cpa', 'attorney', 'titleCompany', 'surveyor', 'appraiser']); export const ListingsCategoryEnum = z.enum(['commercialProperty', 'business']); export const ZodEventTypeEnum = z.enum(['view', 'print', 'email', 'facebook', 'x', 'linkedin', 'contact', 'favorite', 'emailus', 'pricing']); export type EventTypeEnum = z.infer; const PropertyTypeEnum = z.enum(['retail', 'land', 'industrial', 'office', 'mixedUse', 'multifamily', 'uncategorized']); const TypeEnum = z.enum([ 'automotive', 'industrialServices', 'foodAndRestaurant', 'realEstate', 'retail', 'oilfield', 'service', 'advertising', 'agriculture', 'franchise', 'professional', 'manufacturing', 'uncategorized', ]); const USStates = z.enum([ 'AL', 'AK', 'AZ', 'AR', 'CA', 'CO', 'CT', 'DC', 'DE', 'FL', 'GA', 'HI', 'ID', 'IL', 'IN', 'IA', 'KS', 'KY', 'LA', 'ME', 'MD', 'MA', 'MI', 'MN', 'MS', 'MO', 'MT', 'NE', 'NV', 'NH', 'NJ', 'NM', 'NY', 'NC', 'ND', 'OH', 'OK', 'OR', 'PA', 'RI', 'SC', 'SD', 'TN', 'TX', 'UT', 'VT', 'VA', 'WA', 'WV', 'WI', 'WY', ]); export const AreasServedSchema = z.object({ county: z.string().optional().nullable(), state: z .string() .nullable() .refine(val => val !== null && val !== '', { message: 'State is required', }), }); export const LicensedInSchema = z.object({ state: z .string() .nullable() .refine(val => val !== null && val !== '', { message: 'State is required', }), registerNo: z.string().nonempty('License number is required'), }); export const GeoSchema = z .object({ name: z.string().optional().nullable(), state: z.string().refine(val => USStates.safeParse(val).success, { message: 'Invalid state. Must be a valid 2-letter US state code.', }), latitude: z.number().refine( value => { return value >= -90 && value <= 90; }, { message: 'Latitude muss zwischen -90 und 90 liegen', }, ), longitude: z.number().refine( value => { return value >= -180 && value <= 180; }, { message: 'Longitude muss zwischen -180 und 180 liegen', }, ), county: z.string().optional().nullable(), housenumber: z.string().optional().nullable(), street: z.string().optional().nullable(), zipCode: z.number().optional().nullable(), }) .superRefine((data, ctx) => { if (!data.state) { ctx.addIssue({ code: z.ZodIssueCode.custom, message: 'You need to select at least a state', path: ['name'], }); } }); const phoneRegex = /^(\+1|1)?[-.\s]?\(?[2-9]\d{2}\)?[-.\s]?\d{3}[-.\s]?\d{4}$/; export const UserSchema = z .object({ id: z.string().uuid().optional().nullable(), firstname: z.string().min(3, { message: 'First name must contain at least 2 characters' }), lastname: z.string().min(3, { message: 'Last name must contain at least 2 characters' }), email: z.string().email({ message: 'Invalid email address' }), phoneNumber: z.string().optional().nullable(), description: z.string().optional().nullable(), companyName: z.string().optional().nullable(), companyOverview: z.string().optional().nullable(), companyWebsite: z.string().url({ message: 'Invalid URL format' }).optional().nullable(), location: GeoSchema.optional().nullable(), offeredServices: z.string().optional().nullable(), areasServed: z.array(AreasServedSchema).optional().nullable(), hasProfile: z.boolean().optional().nullable(), hasCompanyLogo: z.boolean().optional().nullable(), licensedIn: z.array(LicensedInSchema).optional().nullable(), gender: GenderEnum.optional().nullable(), customerType: CustomerTypeEnum, customerSubType: CustomerSubTypeEnum.optional().nullable(), created: z.date().optional().nullable(), updated: z.date().optional().nullable(), subscriptionId: z.string().optional().nullable(), subscriptionPlan: SubscriptionTypeEnum.optional().nullable(), showInDirectory: z.boolean(), }) .superRefine((data, ctx) => { if (data.customerType === 'professional') { if (!data.customerSubType) { ctx.addIssue({ code: z.ZodIssueCode.custom, message: 'Customer subtype is required for professional customers', path: ['customerSubType'], }); } if (!data.companyName || data.companyName.length < 6) { ctx.addIssue({ code: z.ZodIssueCode.custom, message: 'Company Name must contain at least 6 characters for professional customers', path: ['companyName'], }); } if (!data.phoneNumber || !phoneRegex.test(data.phoneNumber)) { ctx.addIssue({ code: z.ZodIssueCode.custom, message: 'Phone number is required and must be in US format (XXX) XXX-XXXX for professional customers', path: ['phoneNumber'], }); } if (!data.companyOverview || data.companyOverview.length < 10) { ctx.addIssue({ code: z.ZodIssueCode.custom, message: 'Company overview must contain at least 10 characters for professional customers', path: ['companyOverview'], }); } if (!data.description || data.description.length < 10) { ctx.addIssue({ code: z.ZodIssueCode.custom, message: 'Description must contain at least 10 characters for professional customers', path: ['description'], }); } if (!data.offeredServices || data.offeredServices.length < 10) { ctx.addIssue({ code: z.ZodIssueCode.custom, message: 'Offered services must contain at least 10 characters for professional customers', path: ['offeredServices'], }); } if (!data.location) { ctx.addIssue({ code: z.ZodIssueCode.custom, message: 'Company location is required for professional customers', path: ['location'], }); } if (!data.areasServed || data.areasServed.length < 1) { ctx.addIssue({ code: z.ZodIssueCode.custom, message: 'At least one area served is required for professional customers', path: ['areasServed'], }); } } }); export type AreasServed = z.infer; export type LicensedIn = z.infer; export type User = z.infer; export const BusinessListingSchema = z .object({ id: z.string().uuid().optional().nullable(), email: z.string().email(), type: z.string().refine(val => TypeEnum.safeParse(val).success, { message: 'Invalid type. Must be one of: ' + TypeEnum.options.join(', '), }), title: z.string().min(10), description: z.string().min(10), location: GeoSchema, price: z.number().positive().optional().nullable(), favoritesForUser: z.array(z.string()), draft: z.boolean(), listingsCategory: ListingsCategoryEnum, realEstateIncluded: z.boolean().optional().nullable(), leasedLocation: z.boolean().optional().nullable(), franchiseResale: z.boolean().optional().nullable(), salesRevenue: z.number().positive().nullable(), cashFlow: z.number().optional().nullable(), ffe: z.number().optional().nullable(), inventory: z.number().optional().nullable(), supportAndTraining: z.string().min(5).optional().nullable(), employees: z.number().int().positive().max(100000).optional().nullable(), established: z.number().int().min(1).max(250).optional().nullable(), internalListingNumber: z.number().int().positive().optional().nullable(), reasonForSale: z.string().min(5).optional().nullable(), brokerLicencing: z.string().optional().nullable(), internals: z.string().min(5).optional().nullable(), imageName: z.string().optional().nullable(), created: z.date(), updated: z.date(), }) .superRefine((data, ctx) => { if (data.price && data.price > 1000000000) { ctx.addIssue({ code: z.ZodIssueCode.custom, message: 'Price must less than or equal $1,000,000,000', path: ['price'], }); } if (data.salesRevenue && data.salesRevenue > 100000000) { ctx.addIssue({ code: z.ZodIssueCode.custom, message: 'SalesRevenue must less than or equal $100,000,000', path: ['salesRevenue'], }); } if (data.cashFlow && data.cashFlow > 100000000) { ctx.addIssue({ code: z.ZodIssueCode.custom, message: 'CashFlow must less than or equal $100,000,000', path: ['cashFlow'], }); } }); export type BusinessListing = z.infer; export const CommercialPropertyListingSchema = z .object({ id: z.string().uuid().optional().nullable(), serialId: z.number().int().positive().optional().nullable(), email: z.string().email(), type: z.string().refine(val => PropertyTypeEnum.safeParse(val).success, { message: 'Invalid type. Must be one of: ' + PropertyTypeEnum.options.join(', '), }), title: z.string().min(10), description: z.string().min(10), location: GeoSchema, price: z.number().positive().optional().nullable(), favoritesForUser: z.array(z.string()), listingsCategory: ListingsCategoryEnum, internalListingNumber: z.number().int().positive().optional().nullable(), draft: z.boolean(), imageOrder: z.array(z.string()), imagePath: z.string().nullable().optional(), created: z.date(), updated: z.date(), }) .superRefine((data, ctx) => { if (data.price && data.price > 1000000000) { ctx.addIssue({ code: z.ZodIssueCode.custom, message: 'Price must less than or equal $1,000,000,000', path: ['price'], }); } }); export type CommercialPropertyListing = z.infer; export const SenderSchema = z.object({ name: z.string().min(6, { message: 'Name must be at least 6 characters long' }), email: z.string().email({ message: 'Invalid email address' }), phoneNumber: z.string().regex(/^(\+1|1)?[-.\s]?\(?[2-9]\d{2}\)?[-.\s]?\d{3}[-.\s]?\d{4}$/, { message: 'Invalid US phone number format', }), state: z.string().refine(val => USStates.safeParse(val).success, { message: 'Invalid state. Must be a valid 2-letter US state code.', }), comments: z.string().min(10, { message: 'Comments must be at least 10 characters long' }), }); export type Sender = z.infer; export const ShareByEMailSchema = z.object({ yourName: z.string().min(6, { message: 'Name must be at least 6 characters long' }), recipientEmail: z.string().email({ message: 'Invalid email address' }), yourEmail: z.string().email({ message: 'Invalid email address' }), listingTitle: z.string().optional().nullable(), url: z.string().url({ message: 'Invalid URL format' }).optional().nullable(), id: z.string().optional().nullable(), type: ListingsCategoryEnum, }); export type ShareByEMail = z.infer; export const ListingEventSchema = z.object({ id: z.string().uuid(), // UUID für das Event listingId: z.string().uuid().optional().nullable(), // UUID für das Listing email: z.string().email().optional().nullable(), // EMail des den Benutzer, optional, wenn kein Benutzer eingeloggt ist eventType: ZodEventTypeEnum, // Die Event-Typen eventTimestamp: z.string().datetime().or(z.date()), // Der Zeitstempel des Events, kann ein String im ISO-Format oder ein Date-Objekt sein userIp: z.string().max(45).optional().nullable(), // IP-Adresse des Benutzers, optional userAgent: z.string().max(255).optional().nullable(), // User-Agent des Benutzers, optional locationCountry: z.string().max(100).optional().nullable(), // Land, optional locationCity: z.string().max(100).optional().nullable(), // Stadt, optional locationLat: z.string().max(20).optional().nullable(), // Latitude, als String locationLng: z.string().max(20).optional().nullable(), // Longitude, als String referrer: z.string().max(255).optional().nullable(), // Referrer URL, optional additionalData: z.record(z.any()).optional().nullable(), // JSON für zusätzliche Daten, z.B. soziale Medien, optional }); export type ListingEvent = z.infer;