diff --git a/app/components/sections/cms-voucher/select.tsx b/app/components/sections/cms-voucher/select.tsx new file mode 100644 index 0000000..3c40836 --- /dev/null +++ b/app/components/sections/cms-voucher/select.tsx @@ -0,0 +1,73 @@ +// app/components/sections/cms-voucher/select.tsx +import { twMerge } from "tailwind-merge"; + +export type SelectOption = { + value: string; + label: string; + disabled?: boolean; +}; + +export const Select = ({ + label, + id, + name, + placeholder, + options, + defaultValue = null, + disabled = false, + readonly = false, + errorMessage, +}: { + label: string; + id: string; + name: string; + options: SelectOption[]; + defaultValue?: string | null; + placeholder?: string; + disabled?: boolean; + readonly?: boolean; + errorMessage?: string; +}) => { + const isDisabled = disabled || readonly; + const normalizedDefaultValue = defaultValue ?? ""; + + return ( +
+ + + + + {errorMessage && ( +

{errorMessage}

+ )} +
+ ); +}; diff --git a/app/routes/cms/voucher-create.tsx b/app/routes/cms/voucher-create.tsx index 6b60643..424c43f 100644 --- a/app/routes/cms/voucher-create.tsx +++ b/app/routes/cms/voucher-create.tsx @@ -5,26 +5,69 @@ import { createVoucher } from "~/api/endpoint/.server/voucher"; import { clientErrorSchema } from "~/api/schema/shared"; import { Checkbox } from "~/components/sections/cms-voucher/checkbox"; import { Input } from "~/components/sections/cms-voucher/input"; +import { Select } from "~/components/sections/cms-voucher/select"; import type { Route } from "./+types/voucher-create"; +const PARTICIPANT_TYPES = [ + "Keynote Speaker", + "Speaker", + "Organizer", + "Volunteer", + "Sponsor", + "Community", + "Patron", +] as const; + +type ParticipantType = (typeof PARTICIPANT_TYPES)[number]; + export const action = async ({ request }: Route.ActionArgs) => { const formData = await request.formData(); const code = formData.get("code"); const value = formData.get("value"); const quota = formData.get("quota"); - const type = formData.get("type"); + const rawType = formData.get("type"); + const rawEmails = formData.get("email_whitelist"); const is_active = !!formData.get("is_active"); + + let type: ParticipantType | null = null; + + if (typeof rawType === "string" && rawType.trim() !== "") { + if ((PARTICIPANT_TYPES as readonly string[]).includes(rawType)) { + type = rawType as ParticipantType; + } else { + type = null; + } + } + + let email_whitelist: { emails: string[] } | null = null; + + if (typeof rawEmails === "string") { + const emails = rawEmails + .split(",") + .map((e) => e.trim()) + .filter((e) => e.length > 0); + + if (emails.length > 0) { + email_whitelist = { emails }; + } else { + email_whitelist = null; + } + } else { + email_whitelist = null; + } + const json = { code: typeof code === "string" ? code : "", value: value ? Number(value) : null, quota: quota ? Number(quota) : 0, - type: typeof type === "string" ? type : null, - email_whitelist: null, + type, + email_whitelist, is_active: is_active, }; console.log(json); const res = await createVoucher({ request, json }); + if (res.status === 422) { const json = await res.json(); console.error("Validation error:", json); @@ -111,11 +154,21 @@ export default function VoucherCreatePage( .join(", ") || undefined } /> - item.field === "type") @@ -123,6 +176,18 @@ export default function VoucherCreatePage( .join(", ") || undefined } /> + item.field === "email_whitelist") + .map((item) => item.message) + .join(", ") || undefined + } + />
{ const { id } = params; if (!id) { @@ -34,14 +47,45 @@ export const action = async ({ request }: Route.ActionArgs) => { const code = formData.get("code"); const value = formData.get("value"); const quota = formData.get("quota"); - const type = formData.get("type"); + const rawType = formData.get("type"); + const rawEmails = formData.get("email_whitelist"); const is_active = !!formData.get("is_active"); + + let type: ParticipantType | null = null; + + if (typeof rawType === "string" && rawType.trim() !== "") { + if ((PARTICIPANT_TYPES as readonly string[]).includes(rawType)) { + type = rawType as ParticipantType; + } else { + // Optional: if someone tampers with the form we could: + // - keep it null, or + // - throw, or + // - map to an error + type = null; + } + } + + let email_whitelist: { emails: string[] } | null = null; + + if (typeof rawEmails === "string") { + const emails = rawEmails + .split(",") + .map((e) => e.trim()) + .filter((e) => e.length > 0); + + if (emails.length > 0) { + email_whitelist = { emails }; + } else { + email_whitelist = null; + } + } + const json = { code: typeof code === "string" ? code : "", value: value ? Number(value) : null, quota: quota ? Number(quota) : 0, - type: typeof type === "string" ? type : null, - email_whitelist: null, + type, + email_whitelist, is_active: is_active, }; console.log(id); @@ -138,18 +182,40 @@ export default function VoucherCreatePage( } defaultValue={voucher.quota?.toString()} /> - item.field === "type") .map((item) => item.message) .join(", ") || undefined } - defaultValue={voucher.type || ""} + /> + item.field === "email_whitelist") + .map((item) => item.message) + .join(", ") || undefined + } />