690 files changed
+12045
-33943
No files matched your search
@@ -0,0 +1,109 @@
|
||||
import { z } from "zod";
|
||||
|
||||
/**
|
||||
* Single source of truth for the installation wizard's shape — shared
|
||||
* between the client wizard component and the final Server Action (see
|
||||
* actions/install.ts). Deliberately one flat schema, not one per wizard
|
||||
* step: the wizard itself is client-side-only state (see
|
||||
* install-wizard.tsx's own header for why) and nothing is validated or
|
||||
* written server-side until the single final submission, so there is only
|
||||
* ever one real validation boundary to define.
|
||||
*/
|
||||
|
||||
export const installAdministratorSchema = z
|
||||
.object({
|
||||
firstName: z.string().trim().min(1, "First name is required"),
|
||||
lastName: z.string().trim().min(1, "Last name is required"),
|
||||
email: z.email("Enter a valid email address"),
|
||||
phone: z.string().trim().optional().or(z.literal("")),
|
||||
password: z.string().min(8, "Password must be at least 8 characters"),
|
||||
confirmPassword: z.string(),
|
||||
})
|
||||
.refine((data) => data.password === data.confirmPassword, {
|
||||
message: "Passwords don't match",
|
||||
path: ["confirmPassword"],
|
||||
});
|
||||
|
||||
export const installBusinessSchema = z.object({
|
||||
name: z.string().trim().min(1, "Business name is required"),
|
||||
shortName: z.string().trim().max(40).optional().or(z.literal("")),
|
||||
description: z.string().trim().optional().or(z.literal("")),
|
||||
email: z.email("Enter a valid email address").optional().or(z.literal("")),
|
||||
phone: z.string().trim().optional().or(z.literal("")),
|
||||
website: z.string().trim().optional().or(z.literal("")),
|
||||
});
|
||||
|
||||
export const installLocationSchema = z.object({
|
||||
addressLine1: z.string().trim().min(1, "Address line 1 is required"),
|
||||
addressLine2: z.string().trim().optional().or(z.literal("")),
|
||||
city: z.string().trim().min(1, "Town or city is required"),
|
||||
region: z.string().trim().optional().or(z.literal("")),
|
||||
postalCode: z.string().trim().min(1, "Postcode is required"),
|
||||
country: z.string().trim().min(1, "Country is required"),
|
||||
timezone: z.string().trim().min(1, "Timezone is required"),
|
||||
currency: z.string().trim().length(3, "Currency should be a 3-letter code, e.g. GBP"),
|
||||
locale: z.string().trim().min(1, "Locale is required"),
|
||||
});
|
||||
|
||||
const weekdaySchema = z.enum(["MONDAY", "TUESDAY", "WEDNESDAY", "THURSDAY", "FRIDAY", "SATURDAY", "SUNDAY"]);
|
||||
|
||||
export const installHoursDaySchema = z.object({
|
||||
dayOfWeek: weekdaySchema,
|
||||
isClosed: z.boolean(),
|
||||
opensAtMinutes: z.number().int().min(0).max(1439).optional(),
|
||||
closesAtMinutes: z.number().int().min(0).max(1439).optional(),
|
||||
});
|
||||
|
||||
export const installBookingRulesSchema = z.object({
|
||||
allowGuestBooking: z.boolean(),
|
||||
minNoticeMinutes: z.number().int().min(0),
|
||||
maxAdvanceDays: z.number().int().min(1),
|
||||
cancellationWindowHours: z.number().int().min(0),
|
||||
rescheduleWindowHours: z.number().int().min(0),
|
||||
slotIntervalMinutes: z.number().int().min(5).max(120),
|
||||
bufferBeforeMinutes: z.number().int().min(0),
|
||||
bufferAfterMinutes: z.number().int().min(0),
|
||||
maxAppointmentsPerDay: z.number().int().min(1).nullable(),
|
||||
maxAppointmentsPerStaffPerDay: z.number().int().min(1).nullable(),
|
||||
customerCanChooseStaff: z.boolean(),
|
||||
requireApproval: z.boolean(),
|
||||
});
|
||||
|
||||
export const installBrandingSchema = z.object({
|
||||
logoUrl: z.string().trim().optional().or(z.literal("")),
|
||||
coverImageUrl: z.string().trim().optional().or(z.literal("")),
|
||||
themeColor: z.string().trim().optional().or(z.literal("")),
|
||||
backgroundColor: z.string().trim().optional().or(z.literal("")),
|
||||
});
|
||||
|
||||
const decimalPrice = z.string().regex(/^\d+(\.\d{1,2})?$/, "Enter a price like 22.50");
|
||||
|
||||
export const installStarterServiceSchema = z.object({
|
||||
categoryName: z.string().trim().min(1),
|
||||
name: z.string().trim().min(1),
|
||||
description: z.string().trim().optional().or(z.literal("")),
|
||||
price: decimalPrice,
|
||||
durationMinutes: z.number().int().min(5).max(480),
|
||||
active: z.boolean(),
|
||||
allowOnlineBooking: z.boolean(),
|
||||
});
|
||||
|
||||
export const installSchema = z.object({
|
||||
installationToken: z.string().min(1, "Installation token is required"),
|
||||
administrator: installAdministratorSchema,
|
||||
business: installBusinessSchema,
|
||||
location: installLocationSchema,
|
||||
hours: z.array(installHoursDaySchema).length(7),
|
||||
bookingRules: installBookingRulesSchema,
|
||||
branding: installBrandingSchema,
|
||||
starterServices: z.array(installStarterServiceSchema).max(20),
|
||||
});
|
||||
|
||||
export type InstallAdministratorInput = z.infer<typeof installAdministratorSchema>;
|
||||
export type InstallBusinessInput = z.infer<typeof installBusinessSchema>;
|
||||
export type InstallLocationInput = z.infer<typeof installLocationSchema>;
|
||||
export type InstallHoursDayInput = z.infer<typeof installHoursDaySchema>;
|
||||
export type InstallBookingRulesInput = z.infer<typeof installBookingRulesSchema>;
|
||||
export type InstallBrandingInput = z.infer<typeof installBrandingSchema>;
|
||||
export type InstallStarterServiceInput = z.infer<typeof installStarterServiceSchema>;
|
||||
export type InstallInput = z.infer<typeof installSchema>;
|
||||
Reference in new issue
Block a user