Add event reminder modal & section

docs/contribute-to-app-store
Joe Au-Yeung 2022-03-05 15:55:10 -05:00
parent 74e2e8ba58
commit 1816139295
3 changed files with 246 additions and 2 deletions

View File

@ -0,0 +1,131 @@
import { EventTypeAttendeeReminder, EventTypeAttendeeReminderMethod } from "@prisma/client";
import React, { FC } from "react";
import { Controller, SubmitHandler, useForm, useWatch } from "react-hook-form";
import Select from "react-select";
import { useLocale } from "@lib/hooks/useLocale";
import Button from "@components/ui/Button";
interface OptionTypeBase {
label: string;
value: EventTypeAttendeeReminder;
}
interface Props {
onSubmit: SubmitHandler<IFormInput>;
onCancel: () => void;
selectedCustomInput?: EventTypeAttendeeReminder;
}
type IFormInput = EventTypeAttendeeReminder;
const AttendeeReminderTypeForm: FC<Props> = (props) => {
const { t } = useLocale();
const inputOptions: OptionTypeBase[] = [
{ value: EventTypeAttendeeReminderMethod.EMAIL, label: t("email") },
{ value: EventTypeAttendeeReminderMethod.SMS, label: t("SMS") },
];
const { selectedCustomInput } = props;
const defaultValues = selectedCustomInput || { type: inputOptions[0].value };
const { register, control, handleSubmit } = useForm<IFormInput>({
defaultValues,
});
const selectedInputType = useWatch({ name: "type", control });
const selectedInputOption = inputOptions.find((e) => selectedInputType === e.value)!;
const onCancel = () => {
props.onCancel();
};
return (
<form onSubmit={handleSubmit(props.onSubmit)}>
<div className="mb-2">
<label htmlFor="type" className="block text-sm font-medium text-gray-700">
{t("input_type")}
</label>
<Controller
name="type"
control={control}
render={({ field }) => (
<Select
id="type"
defaultValue={selectedInputOption}
options={inputOptions}
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}
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">
<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")}
/>
<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")}
</Button>
<Button type="submit">{t("save")}</Button>
</div>
</form>
);
};
export default AttendeeReminderTypeForm;

View File

@ -13,7 +13,14 @@ import {
UsersIcon,
} from "@heroicons/react/solid";
import { MembershipRole } from "@prisma/client";
import { Availability, EventTypeCustomInput, PeriodType, Prisma, SchedulingType } from "@prisma/client";
import {
Availability,
EventTypeCustomInput,
EventTypeAttendeeReminder,
PeriodType,
Prisma,
SchedulingType,
} from "@prisma/client";
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@radix-ui/react-collapsible";
import * as RadioGroup from "@radix-ui/react-radio-group";
import dayjs from "dayjs";
@ -48,6 +55,7 @@ import Loader from "@components/Loader";
import Shell from "@components/Shell";
import ConfirmationDialogContent from "@components/dialog/ConfirmationDialogContent";
import { Form } from "@components/form/fields";
import AttendeeReminderTypeForm from "@components/pages/eventtypes/AttendeeReminderTypeForm";
import CustomInputTypeForm from "@components/pages/eventtypes/CustomInputTypeForm";
import Button from "@components/ui/Button";
import InfoBadge from "@components/ui/InfoBadge";
@ -173,6 +181,13 @@ const EventTypePage = (props: inferSSRProps<typeof getServerSideProps>) => {
const [customInputs, setCustomInputs] = useState<EventTypeCustomInput[]>(
eventType.customInputs.sort((a, b) => a.id - b.id) || []
);
// prettier-ignore
const [selectedAttendeeReminder, setSelectedAttendeeReminder] = useState<EventTypeAttendeeReminder | undefined>(undefined);
const [selectedAttendeeReminderModalOpen, setSelectedAttendeeReminderModalOpen] = useState(false);
const [attendeeReminders, setAttendeeReminders] = useState<EventTypeAttendeeReminder[]>(
eventType.attendeeReminders.sort((a, b) => a.id - b.id) || []
);
const [tokensList, setTokensList] = useState<Array<Token>>([]);
const periodType =
@ -371,6 +386,7 @@ const EventTypePage = (props: inferSSRProps<typeof getServerSideProps>) => {
integration: string;
externalId: string;
};
attendeeReminders: EventTypeAttendeeReminder[];
}>({
defaultValues: {
locations: eventType.locations || [],
@ -1334,6 +1350,40 @@ const EventTypePage = (props: inferSSRProps<typeof getServerSideProps>) => {
</div>
</div>
<hr className="border-neutral-200" />
<div className="block sm:flex">
<div className="min-w-48 mb-4 sm:mb-0">
<label
htmlFor="attendeeReminders"
className="flex text-sm font-medium text-neutral-700">
{t("attendee_reminders")}
</label>
</div>
<div className="w-full">
<Controller
name="attendeeReminders"
control={formMethods.control}
render={() => (
<div className="w-full">
{/* TODO sort reminders based on time from event */}
<ul className="mt-1"></ul>
<Button
onClick={() => {
setSelectedAttendeeReminder(undefined);
setSelectedAttendeeReminderModalOpen(true);
}}
color="secondary"
type="button"
StartIcon={PlusIcon}>
{t("add_input")}
</Button>
</div>
)}
/>
</div>
</div>
{hasPaymentIntegration && (
<>
<hr className="border-neutral-200" />
@ -1634,6 +1684,66 @@ const EventTypePage = (props: inferSSRProps<typeof getServerSideProps>) => {
</Dialog>
)}
/>
<Controller
name="attendeeReminders"
control={formMethods.control}
defaultValue={eventType.attendeeReminders.sort((a, b) => a.id - b.id) || []}
render={() => (
<Dialog
open={selectedAttendeeReminderModalOpen}
onOpenChange={setSelectedAttendeeReminderModalOpen}>
<DialogContent asChild>
<div className="inline-block transform rounded-sm bg-white px-4 pt-5 pb-4 text-left align-bottom shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-lg sm:p-6 sm:align-middle">
<div className="mb-4 sm:flex sm:items-start">
<div className="bg-secondary-100 mx-auto flex h-12 w-12 flex-shrink-0 items-center justify-center rounded-full sm:mx-0 sm:h-10 sm:w-10">
<PlusIcon className="text-primary-600 h-6 w-6" />
</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")}
</h3>
<div>
<p className="text-sm text-gray-400">
{t("this_input_will_shown_booking_this_event")}
</p>
</div>
</div>
</div>
<AttendeeReminderTypeForm
selectedCustomInput={selectedAttendeeReminder}
onSubmit={(values) => {
const customInput: EventTypeAttendeeReminder = {
id: -1,
eventTypeId: -1,
label: values.label,
placeholder: values.placeholder,
required: values.required,
type: values.type,
};
if (selectedCustomInput) {
selectedCustomInput.label = customInput.label;
selectedCustomInput.placeholder = customInput.placeholder;
selectedCustomInput.required = customInput.required;
selectedCustomInput.type = customInput.type;
} else {
setCustomInputs(customInputs.concat(customInput));
formMethods.setValue(
"customInputs",
formMethods.getValues("customInputs").concat(customInput)
);
}
setSelectedCustomInputModalOpen(false);
}}
onCancel={() => {
setSelectedCustomInputModalOpen(false);
}}
/>
</div>
</DialogContent>
</Dialog>
)}
/>
{isAdmin && (
<WebhookListContainer
title={t("team_webhooks")}
@ -1748,6 +1858,7 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
price: true,
currency: true,
destinationCalendar: true,
attendeeReminders: true,
},
});

View File

@ -671,5 +671,7 @@
"example_name": "John Doe",
"time_format": "Time format",
"12_hour": "12 hour",
"24_hour": "24 hour"
"24_hour": "24 hour",
"attendee_reminders": "Attendee Reminders",
"sms": "SMS"
}