From 76b72f64d87994aa958c7df81e0bb4e9f9ad3202 Mon Sep 17 00:00:00 2001 From: Malte Delfs Date: Fri, 18 Jun 2021 21:58:42 +0200 Subject: [PATCH 1/5] event type custom input WIP --- lib/EventTypeInput.ts | 14 ++ pages/api/availability/eventtype.ts | 23 ++- pages/availability/event/[type].tsx | 136 +++++++++++++++++- .../migration.sql | 13 ++ 4 files changed, 182 insertions(+), 4 deletions(-) create mode 100644 lib/EventTypeInput.ts create mode 100644 prisma/migrations/20210618140954_added_event_type_custom/migration.sql diff --git a/lib/EventTypeInput.ts b/lib/EventTypeInput.ts new file mode 100644 index 00000000..ac0799d3 --- /dev/null +++ b/lib/EventTypeInput.ts @@ -0,0 +1,14 @@ + +export enum EventTypeCustomInputType { + Text = 'text', + TextLong = 'textLong', + Number = 'number', +} + +export interface EventTypeCustomInput { + id?: number; + type: EventTypeCustomInputType; + label: string; + required: boolean; +} + diff --git a/pages/api/availability/eventtype.ts b/pages/api/availability/eventtype.ts index 8f03e3e9..885c117c 100644 --- a/pages/api/availability/eventtype.ts +++ b/pages/api/availability/eventtype.ts @@ -18,6 +18,27 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) length: parseInt(req.body.length), hidden: req.body.hidden, locations: req.body.locations, + customInputs: !req.body.customInputs + ? undefined + : { + createMany: { + data: req.body.customInputs.filter(input => !input.id).map(input => ({ + type: input.type, + label: input.label, + required: input.required + })) + }, + update: req.body.customInputs.filter(input => !!input.id).map(input => ({ + data: { + type: input.type, + label: input.label, + required: input.required + }, + where: { + id: input.id + } + })) + }, }; if (req.method == "POST") { @@ -50,4 +71,4 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) res.status(200).json({message: 'Event deleted successfully'}); } -} \ No newline at end of file +} diff --git a/pages/availability/event/[type].tsx b/pages/availability/event/[type].tsx index ed575f25..54ff28b3 100644 --- a/pages/availability/event/[type].tsx +++ b/pages/availability/event/[type].tsx @@ -13,14 +13,25 @@ import { XIcon, PhoneIcon, } from '@heroicons/react/outline'; +import {EventTypeCustomInput, EventTypeCustomInputType} from "../../../lib/EventTypeInput"; +import {PlusIcon} from "@heroicons/react/solid"; export default function EventType(props) { const router = useRouter(); + const inputOptions: OptionBase[] = [ + { value: EventTypeCustomInputType.Text, label: 'Text' }, + { value: EventTypeCustomInputType.TextLong, label: 'Multiline Text' }, + { value: EventTypeCustomInputType.Number, label: 'Number', }, + ] + const [ session, loading ] = useSession(); const [ showLocationModal, setShowLocationModal ] = useState(false); + const [ showAddCustomModal, setShowAddCustomModal ] = useState(false); const [ selectedLocation, setSelectedLocation ] = useState(undefined); + const [ selectedInputOption, setSelectedInputOption ] = useState(inputOptions[0]); const [ locations, setLocations ] = useState(props.eventType.locations || []); + const [customInputs, setCustomInputs] = useState(props.eventType.customInputs.sort((a, b) => a.id - b.id) || []); const titleRef = useRef(); const slugRef = useRef(); @@ -44,7 +55,7 @@ export default function EventType(props) { const response = await fetch('/api/availability/eventtype', { method: 'PATCH', - body: JSON.stringify({id: props.eventType.id, title: enteredTitle, slug: enteredSlug, description: enteredDescription, length: enteredLength, hidden: enteredIsHidden, locations }), + body: JSON.stringify({id: props.eventType.id, title: enteredTitle, slug: enteredSlug, description: enteredDescription, length: enteredLength, hidden: enteredIsHidden, locations, customInputs }), headers: { 'Content-Type': 'application/json' } @@ -83,6 +94,11 @@ export default function EventType(props) { setShowLocationModal(false); }; + const closeAddCustomModal = () => { + setSelectedInputOption(inputOptions[0]); + setShowAddCustomModal(false); + }; + const LocationOptions = () => { if (!selectedLocation) { return null; @@ -133,7 +149,22 @@ export default function EventType(props) { setLocations(locations.filter( (location) => location.type !== selectedLocation.type )); }; - return ( + const updateCustom = (e) => { + e.preventDefault(); + + const customInput: EventTypeCustomInput = { + label: e.target.label.value, + required: e.target.required.checked, + type: e.target.type.value + }; + + setCustomInputs(customInputs.concat(customInput)); + + console.log(customInput) + setShowAddCustomModal(false); + }; + + return (
{props.eventType.title} | Event Type | Calendso @@ -232,6 +263,44 @@ export default function EventType(props) {
+
+ +
    + {customInputs.map( (customInput) => ( +
  • +
    +
    +
    + Label: {customInput.label} +
    +
    + Type: {customInput.type} +
    +
    + {customInput.required ? "Required" : "Optional"} +
    +
    +
    + + +
    +
    +
  • + ))} +
  • + +
  • +
+
@@ -317,6 +386,66 @@ export default function EventType(props) {
} + {showAddCustomModal && +
+
+ +
+
+ }
); @@ -348,6 +477,7 @@ export async function getServerSideProps(context) { length: true, hidden: true, locations: true, + customInputs: true } }); @@ -357,4 +487,4 @@ export async function getServerSideProps(context) { eventType }, } -} \ No newline at end of file +} diff --git a/prisma/migrations/20210618140954_added_event_type_custom/migration.sql b/prisma/migrations/20210618140954_added_event_type_custom/migration.sql new file mode 100644 index 00000000..5d038f2b --- /dev/null +++ b/prisma/migrations/20210618140954_added_event_type_custom/migration.sql @@ -0,0 +1,13 @@ +-- CreateTable +CREATE TABLE "EventTypeCustomInput" ( + "id" SERIAL NOT NULL, + "eventTypeId" INTEGER NOT NULL, + "label" TEXT NOT NULL, + "type" TEXT NOT NULL, + "required" BOOLEAN NOT NULL, + + PRIMARY KEY ("id") +); + +-- AddForeignKey +ALTER TABLE "EventTypeCustomInput" ADD FOREIGN KEY ("eventTypeId") REFERENCES "EventType"("id") ON DELETE CASCADE ON UPDATE CASCADE; From 50d325d20af757b50cb921b9d18936558d400c71 Mon Sep 17 00:00:00 2001 From: Malte Delfs Date: Sat, 19 Jun 2021 20:55:40 +0200 Subject: [PATCH 2/5] added schema for EventTypeCustomInput --- prisma/schema.prisma | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 6fb9e1d9..6b33ce75 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -22,6 +22,7 @@ model EventType { userId Int? bookings Booking[] eventName String? + customInputs EventTypeCustomInput[] } model Credential { @@ -131,3 +132,13 @@ model SelectedCalendar { externalId String @@id([userId,integration,externalId]) } + +model EventTypeCustomInput { + id Int @id @default(autoincrement()) + eventTypeId Int + eventType EventType @relation(fields: [eventTypeId], references: [id]) + label String + type String + required Boolean +} + From 639341f701375678d76483b7324f1dde4e23fbab Mon Sep 17 00:00:00 2001 From: Malte Delfs Date: Sat, 19 Jun 2021 21:44:36 +0200 Subject: [PATCH 3/5] show custom inputs on booking screen --- lib/{EventTypeInput.ts => eventTypeInput.ts} | 1 + pages/[user]/book.tsx | 54 +++++++++++++++++++- pages/availability/event/[type].tsx | 3 +- 3 files changed, 55 insertions(+), 3 deletions(-) rename lib/{EventTypeInput.ts => eventTypeInput.ts} (92%) diff --git a/lib/EventTypeInput.ts b/lib/eventTypeInput.ts similarity index 92% rename from lib/EventTypeInput.ts rename to lib/eventTypeInput.ts index ac0799d3..f3d9f66e 100644 --- a/lib/EventTypeInput.ts +++ b/lib/eventTypeInput.ts @@ -3,6 +3,7 @@ export enum EventTypeCustomInputType { Text = 'text', TextLong = 'textLong', Number = 'number', + Bool = 'bool', } export interface EventTypeCustomInput { diff --git a/pages/[user]/book.tsx b/pages/[user]/book.tsx index fb9165e9..0030af8f 100644 --- a/pages/[user]/book.tsx +++ b/pages/[user]/book.tsx @@ -13,6 +13,7 @@ import PhoneInput from 'react-phone-number-input'; import {LocationType} from '../../lib/location'; import Avatar from '../../components/Avatar'; import Button from '../../components/ui/Button'; +import {EventTypeCustomInputType} from "../../lib/eventTypeInput"; dayjs.extend(utc); dayjs.extend(timezone); @@ -49,12 +50,31 @@ export default function Book(props) { const bookingHandler = event => { event.preventDefault(); + let notes = ""; + if (props.eventType.customInputs) { + notes = props.eventType.customInputs.map(input => { + const data = event.target["custom_" + input.id]; + if (!!data) { + if (input.type === EventTypeCustomInputType.Bool) { + return input.label + "\n" + (data.value ? "Yes" : "No") + } else { + return input.label + "\n" + data.value + } + } + }).join("\n\n") + } + if (!!notes && !!event.target.notes.value) { + notes += "Additional notes:\n" + event.target.notes.value; + } else { + notes += event.target.notes.value; + } + let payload = { start: dayjs(date).format(), end: dayjs(date).add(props.eventType.length, 'minute').format(), name: event.target.name.value, email: event.target.email.value, - notes: event.target.notes.value, + notes: notes, timeZone: preferredTimeZone, eventTypeId: props.eventType.id, rescheduleUid: rescheduleUid @@ -143,9 +163,38 @@ export default function Book(props) { {}} /> )} + {props.eventType.customInputs && props.eventType.customInputs.sort((a,b) => a.id - b.id).map(input => ( +
+ {input.type !== EventTypeCustomInputType.Bool && + } + {input.type === EventTypeCustomInputType.TextLong && + +