Add, remove, edit attendee reminders

docs/contribute-to-app-store
Joe Au-Yeung 2022-03-05 22:26:03 -05:00
parent 1816139295
commit ddf9323908
4 changed files with 137 additions and 93 deletions

View File

@ -1,4 +1,8 @@
import { EventTypeAttendeeReminder, EventTypeAttendeeReminderMethod } from "@prisma/client";
import {
EventTypeAttendeeReminder,
EventTypeAttendeeReminderMethod,
EventTypeAttendeeReminderTimeUnit,
} from "@prisma/client";
import React, { FC } from "react";
import { Controller, SubmitHandler, useForm, useWatch } from "react-hook-form";
import Select from "react-select";
@ -9,30 +13,38 @@ import Button from "@components/ui/Button";
interface OptionTypeBase {
label: string;
value: EventTypeAttendeeReminder;
value: EventTypeAttendeeReminderMethod | EventTypeAttendeeReminderTimeUnit;
}
interface Props {
onSubmit: SubmitHandler<IFormInput>;
onCancel: () => void;
selectedCustomInput?: EventTypeAttendeeReminder;
selectedAttendeeReminder?: EventTypeAttendeeReminder;
}
type IFormInput = EventTypeAttendeeReminder;
const AttendeeReminderTypeForm: FC<Props> = (props) => {
const { t } = useLocale();
const inputOptions: OptionTypeBase[] = [
const methodOptions: OptionTypeBase[] = [
{ value: EventTypeAttendeeReminderMethod.EMAIL, label: t("email") },
{ value: EventTypeAttendeeReminderMethod.SMS, label: t("SMS") },
];
const { selectedCustomInput } = props;
const defaultValues = selectedCustomInput || { type: inputOptions[0].value };
const timeUnitOptions: OptionTypeBase[] = [
{ value: EventTypeAttendeeReminderTimeUnit.MINUTES, label: t("minutes") },
{ value: EventTypeAttendeeReminderTimeUnit.HOURS, label: t("hours") },
{ value: EventTypeAttendeeReminderTimeUnit.DAYS, label: t("days") },
];
const { selectedAttendeeReminder } = props;
const defaultValues = selectedAttendeeReminder || { type: methodOptions[0].value };
const { register, control, handleSubmit } = useForm<IFormInput>({
defaultValues,
});
const selectedInputType = useWatch({ name: "type", control });
const selectedInputOption = inputOptions.find((e) => selectedInputType === e.value)!;
const selectedMethod = useWatch({ name: "method", control });
const selectedMethodOption = methodOptions.find((e) => selectedMethod === e.value)!;
const selectedTimeUnit = useWatch({ name: "timeUnit", control });
const selectedTimeUnitOption = timeUnitOptions.find((e) => selectedTimeUnit === e.value)!;
const onCancel = () => {
props.onCancel();
@ -42,82 +54,56 @@ const AttendeeReminderTypeForm: FC<Props> = (props) => {
<form onSubmit={handleSubmit(props.onSubmit)}>
<div className="mb-2">
<label htmlFor="type" className="block text-sm font-medium text-gray-700">
{t("input_type")}
{t("communication_method")}
</label>
<Controller
name="type"
name="method"
control={control}
render={({ field }) => (
<Select
id="type"
defaultValue={selectedInputOption}
options={inputOptions}
defaultValue={selectedMethodOption}
options={methodOptions}
isSearchable={false}
className="focus:border-primary-500 focus:ring-primary-500 mt-1 mb-2 block w-full min-w-0 flex-1 rounded-none rounded-r-md border-gray-300 sm:text-sm"
onChange={(option) => option && field.onChange(option.value)}
value={selectedInputOption}
value={selectedMethodOption}
onBlur={field.onBlur}
name={field.name}
/>
)}
/>
</div>
<div className="mb-2">
<label htmlFor="label" className="block text-sm font-medium text-gray-700">
{t("label")}
</label>
<div className="mt-1">
<input
type="text"
id="label"
required
className="focus:border-primary-500 focus:ring-primary-500 block w-full rounded-sm border-gray-300 shadow-sm sm:text-sm"
defaultValue={selectedCustomInput?.label}
{...register("label", { required: true })}
/>
</div>
</div>
{(selectedInputType === EventTypeCustomInputType.TEXT ||
selectedInputType === EventTypeCustomInputType.TEXTLONG) && (
<div className="mb-2">
<label htmlFor="placeholder" className="block text-sm font-medium text-gray-700">
{t("placeholder")}
</label>
<div className="mt-1">
<input
type="text"
id="placeholder"
className="focus:border-primary-500 focus:ring-primary-500 block w-full rounded-sm border-gray-300 shadow-sm sm:text-sm"
defaultValue={selectedCustomInput?.placeholder}
{...register("placeholder")}
/>
</div>
</div>
)}
<div className="flex h-5 items-center">
{/* TODO Align these vertically */}
<label htmlFor="type" className="block text-sm font-medium text-gray-700">
{t("when_to_send")}
</label>
<div className="middle mb-2 flex items-center justify-center text-sm">
<input
id="required"
type="checkbox"
className="text-primary-600 focus:ring-primary-500 h-4 w-4 rounded border-gray-300 ltr:mr-2 rtl:ml-2"
defaultChecked={selectedCustomInput?.required ?? true}
{...register("required")}
type="number"
className="focus:border-primary-500 focus:ring-primary-500 block w-12 rounded-sm border-gray-300 shadow-sm [appearance:textfield] ltr:mr-2 rtl:ml-2 sm:text-sm"
placeholder="30"
defaultValue={selectedAttendeeReminder?.time}
{...register("time", { required: true })}
/>
<Controller
name="timeUnit"
control={control}
render={({ field }) => (
<Select
id="timeUnit"
defaultValue={selectedTimeUnitOption}
options={timeUnitOptions}
isSearchable={false}
className="focus:border-primary-500 focus:ring-primary-500 mt-1 mb-2 block w-full min-w-0 flex-1 rounded-none rounded-r-md border-gray-300 sm:text-sm"
onChange={(option) => option && field.onChange(option.value)}
value={selectedTimeUnitOption}
onBlur={field.onBlur}
name={field.name}
/>
)}
/>
<label htmlFor="required" className="block text-sm font-medium text-gray-700">
{t("is_required")}
</label>
</div>
<input
type="hidden"
id="eventTypeId"
value={selectedCustomInput?.eventTypeId || -1}
{...register("eventTypeId", { valueAsNumber: true })}
/>
<input
type="hidden"
id="id"
value={selectedCustomInput?.id || -1}
{...register("id", { valueAsNumber: true })}
/>
<div className="mt-5 flex space-x-2 sm:mt-4">
<Button onClick={onCancel} type="button" color="secondary" className="ltr:mr-2">
{t("cancel")}

View File

@ -313,6 +313,12 @@ const EventTypePage = (props: inferSSRProps<typeof getServerSideProps>) => {
setCustomInputs([...customInputs]);
};
const removeReminder = (index: number) => {
formMethods.getValues("attendeeReminders").splice(index, 1);
attendeeReminders.splice(index, 1);
setAttendeeReminders([...attendeeReminders]);
};
const schedulingTypeOptions: {
value: SchedulingType;
label: string;
@ -1366,7 +1372,51 @@ const EventTypePage = (props: inferSSRProps<typeof getServerSideProps>) => {
render={() => (
<div className="w-full">
{/* TODO sort reminders based on time from event */}
<ul className="mt-1"></ul>
<ul className="mt-1">
{attendeeReminders.map(
(attendeeReminder: EventTypeAttendeeReminder, idx: number) => (
<li key={idx} className="bg-secondary-50 mb-2 border p-2">
<div className="flex justify-between">
<div className="w-0 flex-1">
<div className="truncate">
<span
className="text-sm ltr:ml-2 rtl:mr-2"
title={`${t("communication_method")}: ${
attendeeReminder.method
}`}>
{t("communication_method")}: {attendeeReminder.method}
</span>
</div>
{attendeeReminder.time && attendeeReminder.timeUnit && (
<div className="truncate">
<span
className="text-sm ltr:ml-2 rtl:mr-2"
title={`${t("when")}: ${attendeeReminder.time}`}>
{t("when")}: {attendeeReminder.time}{" "}
{attendeeReminder.timeUnit} {t("before_event")}
</span>
</div>
)}
</div>
<div className="flex">
<Button
onClick={() => {
setSelectedAttendeeReminder(attendeeReminder);
setSelectedAttendeeReminderModalOpen(true);
}}
color="minimal"
type="button">
{t("edit")}
</Button>
<button type="button" onClick={() => removeReminder(idx)}>
<XIcon className="h-6 w-6 border-l-2 pl-1 hover:text-red-500 " />
</button>
</div>
</div>
</li>
)
)}
</ul>
<Button
onClick={() => {
@ -1700,43 +1750,39 @@ const EventTypePage = (props: inferSSRProps<typeof getServerSideProps>) => {
</div>
<div className="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">
<h3 className="text-lg font-medium leading-6 text-gray-900" id="modal-title">
{t("add_new_custom_input_field")}
{t("add_new_attendee_reminder")}
</h3>
<div>
<p className="text-sm text-gray-400">
{t("this_input_will_shown_booking_this_event")}
</p>
<p className="text-sm text-gray-400">{t("attendee_reminder_description")}</p>
</div>
</div>
</div>
<AttendeeReminderTypeForm
selectedCustomInput={selectedAttendeeReminder}
selectedAttendeeReminder={selectedAttendeeReminder}
onSubmit={(values) => {
const customInput: EventTypeAttendeeReminder = {
const attendeeReminder: EventTypeAttendeeReminder = {
id: -1,
eventTypeId: -1,
label: values.label,
placeholder: values.placeholder,
required: values.required,
type: values.type,
method: values.method,
timeUnit: values.timeUnit,
time: values.time,
};
if (selectedCustomInput) {
selectedCustomInput.label = customInput.label;
selectedCustomInput.placeholder = customInput.placeholder;
selectedCustomInput.required = customInput.required;
selectedCustomInput.type = customInput.type;
if (selectedAttendeeReminder) {
selectedAttendeeReminder.method = attendeeReminder.method;
selectedAttendeeReminder.timeUnit = attendeeReminder.timeUnit;
selectedAttendeeReminder.time = attendeeReminder.time;
} else {
setCustomInputs(customInputs.concat(customInput));
setAttendeeReminders(attendeeReminders.concat(attendeeReminder));
formMethods.setValue(
"customInputs",
formMethods.getValues("customInputs").concat(customInput)
"attendeeReminders",
formMethods.getValues("attendeeReminders").concat(attendeeReminder)
);
}
setSelectedCustomInputModalOpen(false);
setSelectedAttendeeReminderModalOpen(false);
}}
onCancel={() => {
setSelectedCustomInputModalOpen(false);
setSelectedAttendeeReminderModalOpen(false);
}}
/>
</div>

View File

@ -673,5 +673,10 @@
"12_hour": "12 hour",
"24_hour": "24 hour",
"attendee_reminders": "Attendee Reminders",
"sms": "SMS"
"sms": "SMS",
"add_new_attendee_reminder": "Add new attendee reminder",
"attendee_reminder_description": "A reminder will be sent to attendees before the event",
"communication_method": "Reminder Method",
"when_to_send": "When to send the reminder",
"days": "Days"
}

View File

@ -309,12 +309,19 @@ enum EventTypeAttendeeReminderMethod {
SMS @map("sms")
}
enum EventTypeAttendeeReminderTimeUnit {
MINUTES @map("minutes")
HOURS @map("hours")
DAYS @map("days")
}
model EventTypeAttendeeReminder {
id Int @id @default(autoincrement())
eventTypeId Int
eventType EventType @relation(fields: [eventTypeId], references: [id])
method String
secondsBeforeEvent Int
id Int @id @default(autoincrement())
eventTypeId Int
eventType EventType @relation(fields: [eventTypeId], references: [id])
method EventTypeAttendeeReminderMethod
timeUnit EventTypeAttendeeReminderTimeUnit
time Int
}
model ResetPasswordRequest {