118 lines
3.5 KiB
TypeScript
118 lines
3.5 KiB
TypeScript
import { z } from "zod/v4";
|
|
|
|
const CAMPAIGN_NAME_MIN = 3;
|
|
const CAMPAIGN_NAME_MAX = 120;
|
|
export const CAMPAIGN_COUNTRY_CODE = "DE";
|
|
export const CAMPAIGN_COUNTRY_NAME = "Deutschland";
|
|
const MIN_RADIUS_KM = 1;
|
|
const MAX_RADIUS_KM = 5000;
|
|
const MIN_LEADS_PER_RUN = 1;
|
|
const MAX_LEADS_PER_RUN = 9999;
|
|
const MIN_AUDITS_PER_RUN = 1;
|
|
const MAX_AUDITS_PER_RUN = 9999;
|
|
|
|
export const CAMPAIGN_CATEGORY_MODES = ["preset", "custom"] as const;
|
|
export type CampaignCategoryMode = (typeof CAMPAIGN_CATEGORY_MODES)[number];
|
|
|
|
export const CAMPAIGN_RECURRENCES = [
|
|
"manual",
|
|
"daily",
|
|
"weekly",
|
|
"monthly",
|
|
] as const;
|
|
export type CampaignRecurrence = (typeof CAMPAIGN_RECURRENCES)[number];
|
|
|
|
export const CAMPAIGN_STATUSES = ["active", "paused"] as const;
|
|
export type CampaignStatus = (typeof CAMPAIGN_STATUSES)[number];
|
|
|
|
export const ORTHODOX_POSTAL_CODE_MESSAGE =
|
|
"Bitte eine gültige deutsche PLZ (Postleitzahl) mit genau 5 Ziffern angeben.";
|
|
|
|
const postalCodeSchema = z
|
|
.string()
|
|
.regex(/^\d{5}$/, ORTHODOX_POSTAL_CODE_MESSAGE);
|
|
|
|
const nonEmptyString = (label: string) =>
|
|
z
|
|
.string()
|
|
.trim()
|
|
.min(1, `${label} ist erforderlich.`);
|
|
|
|
const positiveBoundedInt = (label: string, min: number, max: number) =>
|
|
z
|
|
.number({ message: `${label} muss eine Zahl sein.` })
|
|
.finite(`${label} muss eine Zahl sein.`)
|
|
.min(min, `${label} muss mindestens ${min} sein.`)
|
|
.int(`${label} muss eine ganze Zahl sein.`)
|
|
.max(max, `${label} muss maximal ${max} sein.`);
|
|
|
|
export const campaignFormSchema = z
|
|
.object({
|
|
name: nonEmptyString("Name").min(CAMPAIGN_NAME_MIN).max(CAMPAIGN_NAME_MAX),
|
|
categoryMode: z.union(
|
|
CAMPAIGN_CATEGORY_MODES.map((value) => z.literal(value)),
|
|
{
|
|
error: "Bitte zwischen vorgegebener Kategorie oder eigener Kategorie wählen.",
|
|
},
|
|
),
|
|
category: nonEmptyString("Kategorie"),
|
|
customSearchTerm: z.string().optional(),
|
|
postalCode: postalCodeSchema,
|
|
radiusKm: positiveBoundedInt("Radius", MIN_RADIUS_KM, MAX_RADIUS_KM),
|
|
maxNewLeadsPerRun: positiveBoundedInt(
|
|
"Max. neue Leads",
|
|
MIN_LEADS_PER_RUN,
|
|
MAX_LEADS_PER_RUN,
|
|
),
|
|
maxAuditsPerRun: positiveBoundedInt(
|
|
"Max. Audits",
|
|
MIN_AUDITS_PER_RUN,
|
|
MAX_AUDITS_PER_RUN,
|
|
),
|
|
recurrence: z.union(
|
|
CAMPAIGN_RECURRENCES.map((value) => z.literal(value)),
|
|
{
|
|
error:
|
|
"Bitte eine gültige Häufigkeit wählen: manuell, täglich, wöchentlich oder monatlich.",
|
|
},
|
|
),
|
|
status: z.union(
|
|
CAMPAIGN_STATUSES.map((value) => z.literal(value)),
|
|
{ error: "Status ist ungültig." },
|
|
),
|
|
})
|
|
.superRefine((values, ctx) => {
|
|
const needsCustomSearchTerm =
|
|
values.categoryMode === "custom" || values.category === "Anderes";
|
|
|
|
if (needsCustomSearchTerm && !values.customSearchTerm?.trim()) {
|
|
ctx.addIssue({
|
|
code: "custom",
|
|
path: ["customSearchTerm"],
|
|
message:
|
|
"Für Kategorie 'Anderes' ist ein eigener Suchbegriff erforderlich.",
|
|
});
|
|
}
|
|
});
|
|
|
|
export const campaignFormDefaults = {
|
|
name: "",
|
|
status: "active" as CampaignStatus,
|
|
categoryMode: "preset" as CampaignCategoryMode,
|
|
category: "Anwalt",
|
|
customSearchTerm: "",
|
|
recurrence: "daily" as CampaignRecurrence,
|
|
radiusKm: 10,
|
|
maxNewLeadsPerRun: 5,
|
|
maxAuditsPerRun: 5,
|
|
postalCode: "10115",
|
|
};
|
|
|
|
export function mapCampaignFormToPayload(values: Record<string, unknown>) {
|
|
return {
|
|
...values,
|
|
countryCode: CAMPAIGN_COUNTRY_CODE as "DE",
|
|
country: CAMPAIGN_COUNTRY_NAME as "Deutschland",
|
|
};
|
|
}
|