From 454311dd522a498b875001b95a87b0fd0caf7ee0 Mon Sep 17 00:00:00 2001 From: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com> Date: Thu, 24 Nov 2022 17:23:29 +0530 Subject: [PATCH] Edit location dialog new select component (#5647) --- .../components/dialog/EditLocationDialog.tsx | 52 ++++++++++++++--- .../eventtype/EditLocationDialog.tsx | 42 ++++++++++++-- .../components/eventtype/EventSetupTab.tsx | 32 ++++++++--- apps/web/public/static/locales/en/common.json | 3 + apps/web/styles/globals.css | 6 +- packages/app-store/locations.ts | 6 ++ packages/app-store/utils.ts | 56 +++++++++++++++++++ packages/trpc/server/routers/viewer.tsx | 5 +- packages/ui/form/fields.tsx | 2 + 9 files changed, 175 insertions(+), 29 deletions(-) diff --git a/apps/web/components/dialog/EditLocationDialog.tsx b/apps/web/components/dialog/EditLocationDialog.tsx index 6e90496796..8c04866436 100644 --- a/apps/web/components/dialog/EditLocationDialog.tsx +++ b/apps/web/components/dialog/EditLocationDialog.tsx @@ -3,6 +3,7 @@ import { zodResolver } from "@hookform/resolvers/zod"; import { isValidPhoneNumber } from "libphonenumber-js"; import { useEffect } from "react"; import { Controller, useForm, useWatch } from "react-hook-form"; +import { components } from "react-select"; import { z } from "zod"; import { @@ -13,6 +14,7 @@ import { LocationObject, LocationType, } from "@calcom/app-store/locations"; +import { classNames } from "@calcom/lib"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import { RouterOutputs, trpc } from "@calcom/trpc/react"; import { Button, Dialog, DialogContent, Form, Icon, PhoneInput } from "@calcom/ui"; @@ -38,6 +40,7 @@ interface ISetLocationDialog { setShowLocationModal: React.Dispatch>; isOpenDialog: boolean; setSelectedLocation?: (param: OptionTypeBase | undefined) => void; + setEditingLocationType?: (param: string) => void; } const LocationInput = (props: { @@ -75,6 +78,7 @@ export const EditLocationDialog = (props: ISetLocationDialog) => { isOpenDialog, defaultValues, setSelectedLocation, + setEditingLocationType, } = props; const { t } = useLocale(); const locationsQuery = trpc.viewer.locationOptions.useQuery(); @@ -278,23 +282,52 @@ export const EditLocationDialog = (props: ISetLocationDialog) => { query={locationsQuery} success={({ data: locationOptions }) => { if (!locationOptions.length) return null; + if (booking) { + locationOptions.forEach((location) => { + if (location.label === "phone") { + location.options.filter((l) => l.value !== "phone"); + } else if (location.label === "in person") { + location.options.filter((l) => l.value !== "attendeeInPerson"); + } + }); + } return ( ( - maxMenuHeight={150} name="location" defaultValue={selection} - options={ - booking - ? locationOptions.filter((location) => location.value !== "phone") - : locationOptions - } + options={locationOptions} + components={{ + Option: (props) => ( + +
+ {props.data.icon && ( + cover + )} + + {props.data.label} + +
+
+ ), + }} + formatOptionLabel={(e) => ( +
+ {e.icon && app-icon} + {e.label} +
+ )} + formatGroupLabel={(e) =>

{e.label}

} isSearchable={false} onChange={(val) => { if (val) { diff --git a/apps/web/components/eventtype/EventSetupTab.tsx b/apps/web/components/eventtype/EventSetupTab.tsx index 1d08e60bdd..c5420098a8 100644 --- a/apps/web/components/eventtype/EventSetupTab.tsx +++ b/apps/web/components/eventtype/EventSetupTab.tsx @@ -13,7 +13,7 @@ import { Button, Icon, Label, Select, Skeleton, TextField } from "@calcom/ui"; import { slugify } from "@lib/slugify"; -import { EditLocationDialog } from "@components/eventtype/EditLocationDialog"; +import { EditLocationDialog } from "@components/dialog/EditLocationDialog"; type OptionTypeBase = { label: string; @@ -28,6 +28,7 @@ export const EventSetupTab = ( const formMethods = useFormContext(); const { eventType, locationOptions, team } = props; const [showLocationModal, setShowLocationModal] = useState(false); + const [editingLocationType, setEditingLocationType] = useState(""); const [selectedLocation, setSelectedLocation] = useState(undefined); const openLocationModal = (type: EventLocationType["type"]) => { @@ -43,14 +44,23 @@ export const EventSetupTab = ( ); }; - const addLocation = (newLocationType: EventLocationType["type"], details = {}) => { - const existingIdx = formMethods.getValues("locations").findIndex((loc) => newLocationType === loc.type); + const saveLocation = (newLocationType: EventLocationType["type"], details = {}) => { + const locationType = editingLocationType !== "" ? editingLocationType : newLocationType; + const existingIdx = formMethods.getValues("locations").findIndex((loc) => locationType === loc.type); if (existingIdx !== -1) { const copy = formMethods.getValues("locations"); - copy[existingIdx] = { - ...formMethods.getValues("locations")[existingIdx], - ...details, - }; + if (editingLocationType !== "") { + copy[existingIdx] = { + ...details, + type: newLocationType, + }; + } else { + copy[existingIdx] = { + ...formMethods.getValues("locations")[existingIdx], + ...details, + }; + } + formMethods.setValue("locations", copy); } else { formMethods.setValue( @@ -58,6 +68,8 @@ export const EventSetupTab = ( formMethods.getValues("locations").concat({ type: newLocationType, ...details }) ); } + + setEditingLocationType(""); setShowLocationModal(false); }; @@ -115,7 +127,7 @@ export const EventSetupTab = ( if (eventLocationType.organizerInputType) { openLocationModal(newLocationType); } else { - addLocation(newLocationType); + saveLocation(newLocationType); } } }} @@ -150,6 +162,7 @@ export const EventSetupTab = ( locationFormMethods.unregister("locationLink"); locationFormMethods.unregister("locationAddress"); locationFormMethods.unregister("locationPhoneNumber"); + setEditingLocationType(location.type); openLocationModal(location.type); }} aria-label={t("edit")} @@ -234,12 +247,13 @@ export const EventSetupTab = ( ); diff --git a/apps/web/public/static/locales/en/common.json b/apps/web/public/static/locales/en/common.json index 38b57c54d9..c46bcf44b0 100644 --- a/apps/web/public/static/locales/en/common.json +++ b/apps/web/public/static/locales/en/common.json @@ -464,11 +464,14 @@ "booking_confirmation": "Confirm your {{eventTypeTitle}} with {{profileName}}", "booking_reschedule_confirmation": "Reschedule your {{eventTypeTitle}} with {{profileName}}", "in_person_meeting": "In-person meeting", + "attendeeInPerson":"In Person (Attendee Address)", + "inPerson":"In Person (Organizer Address)", "link_meeting": "Link meeting", "phone_call": "Attendee Phone Number", "your_number": "Your phone number", "phone_number": "Phone Number", "attendee_phone_number": "Attendee Phone Number", + "organizer_phone_number" :"Organizer Phone Number", "host_phone_number": "Your Phone Number", "enter_phone_number": "Enter phone number", "reschedule": "Reschedule", diff --git a/apps/web/styles/globals.css b/apps/web/styles/globals.css index 72fd642bbd..772b4abfdd 100644 --- a/apps/web/styles/globals.css +++ b/apps/web/styles/globals.css @@ -149,6 +149,9 @@ select:focus { --tw-ring-color: var(--brand-color); border-color: var(--brand-color); } +/* https://stackoverflow.com/a/69308190 */ +input[type='text']:focus { box-shadow: none; } + button[role="switch"][data-state="checked"] { @apply bg-gray-900; @@ -496,5 +499,4 @@ hr { .h-screen { height: -webkit-fill-available; } -} - +} \ No newline at end of file diff --git a/packages/app-store/locations.ts b/packages/app-store/locations.ts index d17747d9c3..9c0b21cccf 100644 --- a/packages/app-store/locations.ts +++ b/packages/app-store/locations.ts @@ -12,6 +12,7 @@ export type DefaultEventLocationType = { type: DefaultEventLocationTypeEnum; label: string; messageForOrganizer: string; + category: string; iconUrl: string; @@ -71,6 +72,7 @@ export const defaultLocations: DefaultEventLocationType[] = [ attendeeInputPlaceholder: `Enter Address`, defaultValueVariable: "attendeeAddress", iconUrl: "/map-pin.svg", + category: "in person", }, { default: true, @@ -82,6 +84,7 @@ export const defaultLocations: DefaultEventLocationType[] = [ variable: "locationAddress", defaultValueVariable: "address", iconUrl: "/map-pin.svg", + category: "in person", }, { default: true, @@ -92,6 +95,7 @@ export const defaultLocations: DefaultEventLocationType[] = [ messageForOrganizer: "Provide a Meeting Link", defaultValueVariable: "link", iconUrl: "/link.svg", + category: "other", }, { default: true, @@ -106,6 +110,7 @@ export const defaultLocations: DefaultEventLocationType[] = [ // This isn't inputType phone because organizer doesn't need to provide it. // inputType: "phone" iconUrl: "/phone.svg", + category: "phone", }, { default: true, @@ -116,6 +121,7 @@ export const defaultLocations: DefaultEventLocationType[] = [ variable: "locationPhoneNumber", defaultValueVariable: "hostPhoneNumber", iconUrl: "/phone.svg", + category: "phone", }, ]; diff --git a/packages/app-store/utils.ts b/packages/app-store/utils.ts index e82a378f9b..4eb0f6f98a 100644 --- a/packages/app-store/utils.ts +++ b/packages/app-store/utils.ts @@ -72,6 +72,62 @@ export function getLocationOptions(integrations: ReturnType, t: return translateLocations(locations, t); } +export function getLocationGroupedOptions(integrations: ReturnType, t: TFunction) { + const apps: Record = {}; + integrations.forEach((app) => { + if (app.locationOption) { + const category = app.category; + const option = { ...app.locationOption, icon: app.imageSrc }; + if (apps[category]) { + apps[category] = [...apps[category], option]; + } else { + apps[category] = [option]; + } + } + }); + + defaultLocations.forEach((l) => { + const category = l.category; + if (apps[category]) { + apps[category] = [ + ...apps[category], + { + label: l.label, + value: l.type, + icon: l.iconUrl, + }, + ]; + } else { + apps[category] = [ + { + label: l.label, + value: l.type, + icon: l.iconUrl, + }, + ]; + } + }); + const locations = []; + + // Translating labels and pushing into array + for (const category in apps) { + const tmp = { label: category, options: apps[category] }; + if (tmp.label === "in person") { + tmp.options.map((l) => ({ ...l, label: t(l.value) })); + } else { + tmp.options.map((l) => ({ + ...l, + label: t(l.label.toLowerCase().split(" ").join("_")), + })); + } + + tmp.label = t(tmp.label); + + locations.push(tmp); + } + return locations; +} + /** * This should get all available apps to the user based on his saved * credentials, this should also get globally available apps. diff --git a/packages/trpc/server/routers/viewer.tsx b/packages/trpc/server/routers/viewer.tsx index 7ea4b2156e..eadd1b8d23 100644 --- a/packages/trpc/server/routers/viewer.tsx +++ b/packages/trpc/server/routers/viewer.tsx @@ -8,7 +8,7 @@ import ethRouter from "@calcom/app-store/rainbow/trpc/router"; import { deleteStripeCustomer } from "@calcom/app-store/stripepayment/lib/customer"; import { getCustomerAndCheckoutSession } from "@calcom/app-store/stripepayment/lib/getCustomerAndCheckoutSession"; import stripe, { closePayments } from "@calcom/app-store/stripepayment/lib/server"; -import getApps, { getLocationOptions } from "@calcom/app-store/utils"; +import getApps, { getLocationGroupedOptions } from "@calcom/app-store/utils"; import { cancelScheduledJobs } from "@calcom/app-store/zapier/lib/nodeScheduler"; import { getCalendarCredentials, getConnectedCalendars } from "@calcom/core/CalendarManager"; import { DailyLocationType } from "@calcom/core/location"; @@ -769,12 +769,11 @@ const loggedInViewerRouter = router({ appId: true, }, }); - const integrations = getApps(credentials); const t = await getTranslation(ctx.user.locale ?? "en", "common"); - const locationOptions = getLocationOptions(integrations, t); + const locationOptions = getLocationGroupedOptions(integrations, t); return locationOptions; }), diff --git a/packages/ui/form/fields.tsx b/packages/ui/form/fields.tsx index 54ff577f5e..9e34181ba0 100644 --- a/packages/ui/form/fields.tsx +++ b/packages/ui/form/fields.tsx @@ -206,6 +206,8 @@ const PlainForm = (props: FormProps, ref: Ref { event.preventDefault(); + event.stopPropagation(); + form .handleSubmit(handleSubmit)(event) .catch((err) => {