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
+ }
/>