From 6c863170815c37b9a9ae519caa09ab2a21ebf23a Mon Sep 17 00:00:00 2001
From: Hariom Balhara
Date: Fri, 26 Aug 2022 06:18:50 +0530
Subject: [PATCH] AppStore CLI: Making video app creation a breeze with major
cleanup of locations code throughout (#3825)
* Fix breadcrumb colors
* HorizontalTabs
* Team List Item WIP
* Horizontal Tabs
* Cards
* Remove team list item WIP
* Login Page
* Add welcome back i118n
* EventType page work
* Update EventType Icons
* WIP Availability
* Horizontal Tab Work
* Add build command for in root
* Update build DIr/command
* Add Edit Button + change buttons to v2
* Availablitiy page
* Fix IPAD
* Make mobile look a little nicer
* WIP bookingshell
* Remove list items from breaking build
* Add Embed ModalBox for routing forms
* Mian bulk of Booking Page.
* Few updates to components
* Fix chormatic feedback
* Add duplicate form support
* Fix duplication logic
* Change to feathericons everywhere and other fixes
* Dont allow routes for fallback route
* Fix banner
* Fix Empty Screen
* Text area + embded window fixes
* Semi fix avatar
* Fix all TS issues
* Fix tests
* Troubleshoot container + Active on count
* Support routing using query params
* Improve mobile
* NITS
* Fix padding on input
* Support multiselect in router endpoint
* Fix the issue where app goes in embed mode after viewing embed once
* Fix icons
* Add router url tests
* Add Responses download and form toggling tests
* Add required validation test
* Change Icons everywhere
* App typeform app
* Improvements in cli
* Starting to move event types settings to tabs
* Begin migration to single page form
* Single page tabs
* Limits Page
* Advanced tab
* Add RHF to dependancies
* Add typeform how-to-use page
* Add typeform how-to-use page and screenshots
* Most of advanced tab
* Solved RHF mismtach
* Build fixes
* RHF conditionals fixes
* Improved legibility
* Fix TS error
* Add missing image
* Update CliApp.tsx
* Major refactor/organisation into optional V2 UI
* Portal EditLocationModal
* Fix dialoug form
* Update imports
* Auto Animate + custom inputs WIP
* Custom Inputs
* WIP Apps
* Fixing stories imports
* Stripe app
* Remove duplicate dialog
* Remove duplicate dialog
* Major locations cleanup, 10s of bug fixes and app-store improvements
* Fix missing pieces
* More fixes
* Fix embed URL
* Fix app toggles + number of active apps
* Fix container padding on disabledBorder prop
* Removes strict
* more fixes
* EventType Team page WIP
* Fix embed
* NIT
* Add Darkmode gray color
* V2 Shell WIP
* Fix headings on shell V2
* Fix mobile layout with V2 shell
* V2 create event type button
* Checked Team Select
* Hidden to happen on save - not on toggle
* Team Attendee Select animation
* Fix scheduling type and remove multi select label
* Fix overflow on teams url
* Revert console
* Revert api
* Fix Embed TS errors
* Fix TS errors
* Fix Eslint errors
* Fix TS errors for UI
* Fix ESLINT error
* Fix TS errors
* Add missing import
* Fix CLI
* Add a default placeholder
* Remove hardcoded daily:integrations
* Fix message for payment page
* Revert api and console to main
* Update README
* Fix TS errors
* Fix Lint warnings
* Fix Tests
* Fix conflict issues
* Fix conflict issues
Co-authored-by: sean-brydon <55134778+sean-brydon@users.noreply.github.com>
Co-authored-by: Peer Richelsen
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
Co-authored-by: zomars
---
.../booking/AvailableEventLocations.tsx | 32 ++
.../components/booking/BookingListItem.tsx | 28 +-
.../booking/pages/AvailabilityPage.tsx | 111 +---
.../components/booking/pages/BookingPage.tsx | 139 +++--
.../components/dialog/EditLocationDialog.tsx | 477 +++++-------------
.../v2/eventtype/EditLocationDialog.tsx | 462 +++++------------
.../components/v2/eventtype/EventSetupTab.tsx | 408 +++------------
apps/web/lib/linkValueToString.ts | 41 --
apps/web/pages/[user]/[type].tsx | 9 +-
apps/web/pages/[user]/book.tsx | 8 +-
apps/web/pages/api/book/event.ts | 13 +-
apps/web/pages/api/cancel.ts | 3 +-
apps/web/pages/apps/[slug]/index.tsx | 2 +-
apps/web/pages/d/[link]/[slug].tsx | 4 +-
apps/web/pages/d/[link]/book.tsx | 6 +-
apps/web/pages/event-types/[type].tsx | 390 ++------------
apps/web/pages/success.tsx | 34 +-
apps/web/pages/team/[slug]/[type].tsx | 6 +-
apps/web/pages/team/[slug]/book.tsx | 7 +-
.../web/pages/v2/event-types/[type]/index.tsx | 4 +-
apps/web/public/globe.svg | 1 +
apps/web/public/map-pin.svg | 1 +
apps/web/public/phone.svg | 1 +
apps/web/public/static/locales/en/common.json | 2 +-
packages/app-store-cli/readme.md | 5 +-
packages/app-store-cli/src/CliApp.tsx | 179 ++++---
packages/app-store-cli/src/app-store.ts | 1 +
packages/app-store/_appRegistry.ts | 9 +-
packages/app-store/_baseApp/_metadata.ts | 10 +-
packages/app-store/_example/_metadata.ts | 4 +-
packages/app-store/applecalendar/_metadata.ts | 4 +-
packages/app-store/around/_metadata.ts | 22 +-
.../app-store/caldavcalendar/_metadata.ts | 4 +-
packages/app-store/dailyvideo/_metadata.ts | 14 +-
.../app-store/ee/routing_forms/_metadata.ts | 4 +-
.../exchange2013calendar/_metadata.ts | 4 +-
.../exchange2016calendar/_metadata.ts | 4 +-
packages/app-store/giphy/_metadata.ts | 4 +-
.../app-store/googlecalendar/_metadata.ts | 5 +-
packages/app-store/googlevideo/_metadata.ts | 14 +-
.../hubspotothercalendar/_metadata.ts | 4 +-
packages/app-store/huddle01video/_metadata.ts | 14 +-
packages/app-store/jitsivideo/_metadata.ts | 14 +-
packages/app-store/larkcalendar/_metadata.ts | 4 +-
packages/app-store/locations.ts | 356 +++++++++++--
packages/app-store/metamask/_metadata.ts | 4 +-
.../app-store/office365calendar/_metadata.ts | 4 +-
.../app-store/office365video/_metadata.ts | 14 +-
packages/app-store/ping/_metadata.ts | 16 +-
packages/app-store/riverside/_metadata.ts | 16 +-
.../app-store/slackmessaging/_metadata.ts | 4 +-
.../slackmessaging/lib/actions/createEvent.ts | 3 +-
packages/app-store/stripepayment/_metadata.ts | 4 +-
packages/app-store/tandemvideo/_metadata.ts | 14 +-
packages/app-store/typeform/_metadata.ts | 10 +-
packages/app-store/utils.ts | 63 +--
packages/app-store/vital/_metadata.ts | 4 +-
packages/app-store/whereby/_metadata.ts | 16 +-
.../app-store/wipemycalother/_metadata.ts | 4 +-
packages/app-store/zapier/_metadata.ts | 4 +-
.../app-store/zapier/pages/setup/index.tsx | 4 +-
packages/app-store/zoomvideo/_metadata.ts | 16 +-
packages/core/EventManager.ts | 49 +-
{apps/web/lib => packages/core}/event.ts | 33 +-
.../emails/src/components/LocationInfo.tsx | 108 ++--
.../ee/payments/components/PaymentPage.tsx | 4 +-
.../lib/reminders/templates/customTemplate.ts | 34 +-
packages/lib/defaultEvents.ts | 3 +-
packages/lib/server/defaultResponder.ts | 1 +
packages/prisma/seed-app-store.config.json | 26 +-
packages/prisma/seed.ts | 9 +-
packages/prisma/zod-utils.ts | 4 +-
packages/trpc/server/routers/viewer.tsx | 3 +-
.../trpc/server/routers/viewer/bookings.tsx | 4 +-
.../trpc/server/routers/viewer/eventTypes.tsx | 3 +-
packages/types/App.d.ts | 50 +-
packages/ui/v2/core/Card.tsx | 1 -
yarn.lock | 4 +-
78 files changed, 1275 insertions(+), 2129 deletions(-)
create mode 100644 apps/web/components/booking/AvailableEventLocations.tsx
delete mode 100644 apps/web/lib/linkValueToString.ts
create mode 100644 apps/web/public/globe.svg
create mode 100644 apps/web/public/map-pin.svg
create mode 100644 apps/web/public/phone.svg
rename {apps/web/lib => packages/core}/event.ts (57%)
diff --git a/apps/web/components/booking/AvailableEventLocations.tsx b/apps/web/components/booking/AvailableEventLocations.tsx
new file mode 100644
index 0000000000..c46da9f75b
--- /dev/null
+++ b/apps/web/components/booking/AvailableEventLocations.tsx
@@ -0,0 +1,32 @@
+import { getEventLocationType, locationKeyToString } from "@calcom/app-store/locations";
+import { useLocale } from "@calcom/lib/hooks/useLocale";
+
+import { Props } from "./pages/AvailabilityPage";
+
+export function AvailableEventLocations({ locations }: { locations: Props["eventType"]["locations"] }) {
+ return (
+
+
+
+ {locations.map((location) => {
+ const eventLocationType = getEventLocationType(location.type);
+ if (!eventLocationType) {
+ // It's possible that the location app got uninstalled
+ return null;
+ }
+ return (
+
+
+ {locationKeyToString(location)}
+
+ );
+ })}
+
+
+
+ );
+}
diff --git a/apps/web/components/booking/BookingListItem.tsx b/apps/web/components/booking/BookingListItem.tsx
index 3bbc90570a..705eb91746 100644
--- a/apps/web/components/booking/BookingListItem.tsx
+++ b/apps/web/components/booking/BookingListItem.tsx
@@ -3,6 +3,7 @@ import { useRouter } from "next/router";
import { useState } from "react";
import { useMutation } from "react-query";
+import { EventLocationType, getEventLocationType } from "@calcom/app-store/locations";
import dayjs from "@calcom/dayjs";
import classNames from "@calcom/lib/classNames";
import { useLocale } from "@calcom/lib/hooks/useLocale";
@@ -17,8 +18,6 @@ import { TextArea } from "@calcom/ui/form/fields";
import { HttpError } from "@lib/core/http/error";
import useMeQuery from "@lib/hooks/useMeQuery";
-import { linkValueToString } from "@lib/linkValueToString";
-import { LocationType } from "@lib/location";
import { extractRecurringDates } from "@lib/parseDate";
import { EditLocationDialog } from "@components/dialog/EditLocationDialog";
@@ -182,17 +181,10 @@ function BookingListItem(booking: BookingItemProps) {
},
});
- const saveLocation = (newLocationType: LocationType, details: { [key: string]: string }) => {
+ const saveLocation = (newLocationType: EventLocationType["type"], details: { [key: string]: string }) => {
let newLocation = newLocationType as string;
- if (
- newLocationType === LocationType.InPerson ||
- newLocationType === LocationType.Link ||
- newLocationType === LocationType.UserPhone ||
- newLocationType === LocationType.Riverside ||
- newLocationType === LocationType.Around ||
- newLocationType === LocationType.Whereby ||
- newLocationType === LocationType.Ping
- ) {
+ const eventLocationType = getEventLocationType(newLocationType);
+ if (eventLocationType?.organizerInputType) {
newLocation = details[Object.keys(details)[0]];
}
setLocationMutation.mutate({ bookingId: booking.id, newLocation });
@@ -215,17 +207,7 @@ function BookingListItem(booking: BookingItemProps) {
}
}
- let location = booking.location || "";
-
- if (location.includes("integration")) {
- if (booking.status === BookingStatus.CANCELLED || booking.status === BookingStatus.REJECTED) {
- location = t("web_conference");
- } else if (isConfirmed) {
- location = linkValueToString(booking.location, t);
- } else {
- location = t("web_conferencing_details_to_follow");
- }
- }
+ const location = booking.location || "";
const onClick = () => {
router.push({
diff --git a/apps/web/components/booking/pages/AvailabilityPage.tsx b/apps/web/components/booking/pages/AvailabilityPage.tsx
index e213d5eedd..e876e118cf 100644
--- a/apps/web/components/booking/pages/AvailabilityPage.tsx
+++ b/apps/web/components/booking/pages/AvailabilityPage.tsx
@@ -8,7 +8,6 @@ import { useEffect, useMemo, useState } from "react";
import { FormattedNumber, IntlProvider } from "react-intl";
import { z } from "zod";
-import { AppStoreLocationType, LocationObject, LocationType } from "@calcom/app-store/locations";
import dayjs, { Dayjs } from "@calcom/dayjs";
import {
useEmbedNonStylesConfig,
@@ -46,41 +45,9 @@ import PoweredByCal from "@components/ui/PoweredByCal";
import type { AvailabilityPageProps } from "../../../pages/[user]/[type]";
import type { DynamicAvailabilityPageProps } from "../../../pages/d/[link]/[slug]";
import type { AvailabilityTeamPageProps } from "../../../pages/team/[slug]/[type]";
+import { AvailableEventLocations } from "../AvailableEventLocations";
-type Props = AvailabilityTeamPageProps | AvailabilityPageProps | DynamicAvailabilityPageProps;
-
-export const locationKeyToString = (location: LocationObject, t: TFunction) => {
- switch (location.type) {
- case LocationType.InPerson:
- return location.address || "In Person"; // If disabled address won't exist on the object
- case LocationType.Link:
- case LocationType.Ping:
- case LocationType.Riverside:
- case LocationType.Around:
- case LocationType.Whereby:
- return location.link || "Link"; // If disabled link won't exist on the object
- case LocationType.Phone:
- return t("your_number");
- case LocationType.UserPhone:
- return t("phone_call");
- case LocationType.GoogleMeet:
- return "Google Meet";
- case LocationType.Zoom:
- return "Zoom";
- case LocationType.Daily:
- return "Cal Video";
- case LocationType.Jitsi:
- return "Jitsi";
- case LocationType.Huddle01:
- return "Huddle Video";
- case LocationType.Tandem:
- return "Tandem";
- case LocationType.Teams:
- return "Microsoft Teams";
- default:
- return null;
- }
-};
+export type Props = AvailabilityTeamPageProps | AvailabilityPageProps | DynamicAvailabilityPageProps;
const GoBackToPreviousPage = ({ t }: { t: TFunction }) => {
const router = useRouter();
@@ -457,40 +424,9 @@ const AvailabilityPage = ({ profile, eventType }: Props) => {
{t("requires_confirmation")}
)}
- {eventType.locations.length === 1 && (
-
- {Object.values(AppStoreLocationType).includes(
- eventType.locations[0].type as unknown as AppStoreLocationType
- ) ? (
-
- ) : (
-
- )}
-
- {locationKeyToString(eventType.locations[0], t)}
-
- )}
- {eventType.locations.length > 1 && (
-
-
-
-
-
- {eventType.locations.map((el, i, arr) => {
- return (
-
- {locationKeyToString(el, t)}{" "}
- {arr.length - 1 !== i && (
- {t("or_lowercase")}
- )}
-
- );
- })}
-
-
- )}
-
-
+
+
+
{eventType.length} {t("minutes")}
{eventType.price > 0 && (
@@ -590,40 +526,9 @@ const AvailabilityPage = ({ profile, eventType }: Props) => {
{t("requires_confirmation")}
)}
- {eventType.locations.length === 1 && (
-
- {Object.values(AppStoreLocationType).includes(
- eventType.locations[0].type as unknown as AppStoreLocationType
- ) ? (
-
- ) : (
-
- )}
-
- {locationKeyToString(eventType.locations[0], t)}
-
- )}
- {eventType.locations.length > 1 && (
-
-
-
-
-
- {eventType.locations.map((el, i, arr) => {
- return (
-
- {locationKeyToString(el, t)}{" "}
- {arr.length - 1 !== i && (
- {t("or_lowercase")}
- )}
-
- );
- })}
-
-
- )}
-
-
+
+
+
{eventType.length} {t("minutes")}
{!rescheduleUid && eventType.recurringEvent && (
diff --git a/apps/web/components/booking/pages/BookingPage.tsx b/apps/web/components/booking/pages/BookingPage.tsx
index bff0cdb89d..f90a5f34d1 100644
--- a/apps/web/components/booking/pages/BookingPage.tsx
+++ b/apps/web/components/booking/pages/BookingPage.tsx
@@ -13,7 +13,14 @@ import { useMutation } from "react-query";
import { v4 as uuidv4 } from "uuid";
import { z } from "zod";
+import {
+ locationKeyToString,
+ getEventLocationValue,
+ getEventLocationType,
+ EventLocationType,
+} from "@calcom/app-store/locations";
import { createPaymentLink } from "@calcom/app-store/stripepayment/lib/client";
+import { LocationObject, LocationType } from "@calcom/core/location";
import dayjs from "@calcom/dayjs";
import {
useEmbedNonStylesConfig,
@@ -37,7 +44,6 @@ import { EmailInput, Form } from "@calcom/ui/form/fields";
import { asStringOrNull } from "@lib/asStringOrNull";
import { timeZone } from "@lib/clock";
import { ensureArray } from "@lib/ensureArray";
-import { LocationObject, LocationType } from "@lib/location";
import createBooking from "@lib/mutations/bookings/create-booking";
import createRecurringBooking from "@lib/mutations/bookings/create-recurring-booking";
import { parseDate, parseRecurringDates } from "@lib/parseDate";
@@ -58,15 +64,13 @@ declare global {
};
}
-type BookingPageProps = (BookPageProps | TeamBookingPageProps | HashLinkPageProps) & {
- locationLabels: Record;
-};
+type BookingPageProps = BookPageProps | TeamBookingPageProps | HashLinkPageProps;
type BookingFormValues = {
name: string;
email: string;
notes?: string;
- locationType?: LocationType;
+ locationType?: EventLocationType["type"];
guests?: string[];
phone?: string;
hostPhoneNumber?: string; // Maybe come up with a better way to name this to distingish between two types of phone numbers
@@ -83,7 +87,6 @@ const BookingPage = ({
profile,
isDynamicGroupBooking,
recurringEventCount,
- locationLabels,
hasHashedBookingLink,
hashedLink,
}: BookingPageProps) => {
@@ -132,16 +135,6 @@ const BookingPage = ({
);
}
- const location = (function humanReadableLocation(location) {
- if (!location) {
- return;
- }
- if (location.includes("integration")) {
- return t("web_conferencing_details_to_follow");
- }
- return location;
- })(responseData.location);
-
return router.push({
pathname: "/success",
query: {
@@ -152,7 +145,7 @@ const BookingPage = ({
reschedule: !!rescheduleUid,
name: attendees[0].name,
email: attendees[0].email,
- location,
+ location: responseData.location,
eventName: profile.eventName || "",
bookingId: id,
isSuccessBookingPage: true,
@@ -213,7 +206,6 @@ const BookingPage = ({
}
}, [router.query.guest]);
- const locationInfo = (type: LocationType) => locations.find((location) => location.type === type);
const loggedInIsOwner = eventType?.users[0]?.id === session?.user?.id;
const guestListEmails = !isDynamicGroupBooking
? booking?.attendees.slice(1).map((attendee) => attendee.email)
@@ -296,12 +288,12 @@ const BookingPage = ({
resolver: zodResolver(bookingFormSchema), // Since this isn't set to strict we only validate the fields in the schema
});
- const selectedLocation = useWatch({
+ const selectedLocationType = useWatch({
control: bookingForm.control,
name: "locationType",
- defaultValue: ((): LocationType | undefined => {
+ defaultValue: ((): EventLocationType["type"] | undefined => {
if (router.query.location) {
- return router.query.location as LocationType;
+ return router.query.location as EventLocationType["type"];
}
if (locations.length === 1) {
return locations[0]?.type;
@@ -309,40 +301,13 @@ const BookingPage = ({
})(),
});
- const getLocationValue = (
- booking: Pick
- ) => {
- const { locationType } = booking;
- switch (locationType) {
- case LocationType.Phone: {
- return booking.phone || "";
- }
- case LocationType.InPerson: {
- return locationInfo(locationType)?.address || "";
- }
- case LocationType.Link: {
- return locationInfo(locationType)?.link || "";
- }
- case LocationType.UserPhone: {
- return locationInfo(locationType)?.hostPhoneNumber || "";
- }
- case LocationType.Around: {
- return locationInfo(locationType)?.link || "";
- }
- case LocationType.Riverside: {
- return locationInfo(locationType)?.link || "";
- }
- case LocationType.Whereby: {
- return locationInfo(locationType)?.link || "";
- }
- case LocationType.Ping: {
- return locationInfo(locationType)?.link || "";
- }
- // Catches all other location types, such as Google Meet, Zoom etc.
- default:
- return selectedLocation || "";
- }
- };
+ const selectedLocation = getEventLocationType(selectedLocationType);
+ const AttendeeInput =
+ selectedLocation?.attendeeInputType === "text"
+ ? "input"
+ : selectedLocation?.attendeeInputType === "phone"
+ ? PhoneInput
+ : null;
// Calculate the booking date(s)
let recurringStrings: string[] = [],
@@ -403,9 +368,10 @@ const BookingPage = ({
language: i18n.language,
rescheduleUid,
user: router.query.user,
- location: getLocationValue(
- booking.locationType ? booking : { ...booking, locationType: selectedLocation }
- ),
+ location: getEventLocationValue(locations, {
+ type: booking.locationType ? booking.locationType : selectedLocationType || "",
+ phone: booking.phone,
+ }),
metadata,
customInputs: Object.keys(booking.customInputs || {}).map((inputId) => ({
label: eventType.customInputs.find((input) => input.id === parseInt(inputId))?.label || "",
@@ -414,7 +380,7 @@ const BookingPage = ({
hasHashedBookingLink,
hashedLink,
smsReminderNumber:
- selectedLocation === LocationType.Phone ? booking.phone : booking.smsReminderNumber,
+ selectedLocationType === LocationType.Phone ? booking.phone : booking.smsReminderNumber,
}));
recurringMutation.mutate(recurringBookings);
} else {
@@ -430,9 +396,10 @@ const BookingPage = ({
rescheduleUid,
bookingUid: router.query.bookingUid as string,
user: router.query.user,
- location: getLocationValue(
- booking.locationType ? booking : { ...booking, locationType: selectedLocation }
- ),
+ location: getEventLocationValue(locations, {
+ type: (booking.locationType ? booking.locationType : selectedLocationType) || "",
+ phone: booking.phone,
+ }),
metadata,
customInputs: Object.keys(booking.customInputs || {}).map((inputId) => ({
label: eventType.customInputs.find((input) => input.id === parseInt(inputId))?.label || "",
@@ -441,13 +408,14 @@ const BookingPage = ({
hasHashedBookingLink,
hashedLink,
smsReminderNumber:
- selectedLocation === LocationType.Phone ? booking.phone : booking.smsReminderNumber,
+ selectedLocationType === LocationType.Phone ? booking.phone : booking.smsReminderNumber,
});
}
};
// Should be disabled when rescheduleUid is present and data was found in defaultUserValues name/email fields.
const disableInput = !!rescheduleUid && !!defaultUserValues.email && !!defaultUserValues.name;
+ const disableLocations = !!rescheduleUid;
const disabledExceptForOwner = disableInput && !loggedInIsOwner;
const inputClassName =
"dark:placeholder:text-darkgray-600 focus:border-brand dark:border-darkgray-300 dark:text-darkgray-900 block w-full rounded-md border-gray-300 text-sm focus:ring-black disabled:bg-gray-200 disabled:hover:cursor-not-allowed dark:bg-transparent dark:selection:bg-green-500 disabled:dark:text-gray-500";
@@ -656,21 +624,34 @@ const BookingPage = ({
{t("location")}
- {locations.map((location, i) => (
-
- ))}
+ {locations.map((location, i) => {
+ const locationString = locationKeyToString(location);
+ // TODO: Right now selectedLocationType isn't send by getSSP. Once that's available defaultChecked should work and show the location in the original booking
+ const defaultChecked = rescheduleUid ? selectedLocationType === location.type : i === 0;
+ if (typeof locationString !== "string") {
+ // It's possible that location app got uninstalled
+ return null;
+ }
+ return (
+
+ );
+ })}
)}
- {selectedLocation === LocationType.Phone && (
+ {/* TODO: Change name and id ="phone" to something generic */}
+ {AttendeeInput && (