{"version":3,"file":"calendly-CCrxBmU2.js","sources":["../../src/composables/calendly.ts"],"sourcesContent":["import User from \"@/types/user\"\nimport { Ref, ref } from \"vue\"\nimport { useAppFlasher } from \"@xy-planning-network/trees\"\nimport logger from \"./logger\"\nimport { initLinkTag, initScriptTag } from \"./browser\"\n\nconst calendlyStyle = \"https://assets.calendly.com/assets/external/widget.css\"\nconst calendlyScript = \"https://assets.calendly.com/assets/external/widget.js\"\n\n// used to ensure only one listener is ever attached to the window\n// for \"message\" events duplicate listeners can be added if the calendly pop\n// up is opened and closed multiple times by a user.\nlet hasCalendlyMessageListener = false\n\n/**\n * Calendly does not make types avaialable for window.Calendly\n * This is a partial interface that we currently expect to be available\n * scoped to only the methods and properties what we expect to use.\n * The actual methods and properties available on window.Calendly may change\n * as the Calendly API is updated.\n */\ninterface Calendly {\n closePopupWidget(): void\n initPopupWidget(options: CalendlyOptions): void\n}\n\n/**\n * CalendlyOptions are the currently expected options that can be\n * passed to Calendly's widget initialization methods. These options\n * may change as the Calendly API changes.\n */\ninterface CalendlyOptions {\n url: string\n prefill: {\n email: string\n name: string\n }\n utm?: {\n utmSource: string\n utmMedium: string\n }\n}\n\n/**\n * CalendlyMsgEventScheduled is the currently expected data property\n * value of the MessageEvent emitted from the Calendly iframe.\n * This interface may change as the Calendly API is updated.\n */\ninterface CalendlyMsgEventScheduled {\n event: \"calendly.event_scheduled\"\n payload: {\n event: {\n uri: string\n }\n invitee: {\n uri: string\n }\n }\n}\n\n/**\n * initCalendlyAssets injects the necessary Calendly hosted\n * stylesheet and javascript dependencies. Ensuring that it\n * only occurs once. The returning Calendly object is a copy\n * of the instance that Calendly's script on the global window.\n * @return Promise\n */\nconst initCalendlyAssets: () => Promise = (() => {\n let once: Promise | undefined\n\n const init = async (): Promise => {\n const script = await initScriptTag(calendlyScript).then(() => {\n const calendly = (window as any).Calendly as Calendly\n return Promise.resolve({ ...calendly })\n })\n\n const style = await initLinkTag(calendlyStyle).then(() => {\n return Promise.resolve()\n })\n\n return Promise.all([script, style]).then(([calendly]) => {\n return calendly\n })\n }\n\n return async () => {\n if (!once) {\n once = init()\n }\n\n return once\n }\n})()\n\nexport interface UseCalendly {\n /**\n * closePopup removes message event listeners and triggers the Calendly\n * interal closePopup method. Flashing a generic error if a failure occurs.\n * @returns void\n */\n closePopup(): void\n /**\n * isLoaded tracks the state of Calendly external assets being loaded and ready.\n * Because assets are loaded once this ref may be returned with an immediate truthy value.\n */\n isLoaded: Ref\n /**\n * loadingError tracks the error state of loading any Calendly external assets.\n * Because assets are loaded only once this ref may be returned with an immediate truthy value.\n */\n loadingError: Ref\n /**\n * openPopup adds a message event listener before triggering the internal Calendly\n * openPopup method. Flashing a generic error if a failure occurs.\n * @returns void\n */\n openPopup(): void\n /**\n *\n * @param callback function when the calendly.event_scheduled message is received\n * from the Calendly iframe. It passes a single argument\n */\n onScheduled(callback: (url: string) => void): void\n}\n\nexport interface UseCalendlyConfig {\n /**\n * url is the Calendly Booking URL to display the overlay for\n */\n url: string\n /**\n * user will prefill the name and email fields that exist on all Calendly popup forms\n */\n user?: User\n /**\n * utmSource will apply a Google Analytics utmSource value when GA integration exists on the account\n * documentation on this feature is minimal\n */\n utmSource?: string\n /**\n * utmMedium will apply a Google Analytics utmMedium value when GA integration exists on the account\n * documentation on this feature is minimal\n */\n utmMedium?: string\n}\n\n/**\n * useCalendly is a composable function that initializes\n * the necessary dependencies for working with Calendly's\n * popup widget along with useful callbacks and ref's\n * for composing Calendly popup widget functionality\n * @param config UseCalendlyConfig\n * @param onReady optional callback once Calendly assets are loaded\n * @param onError optional callback if one or more Calendly assets fails to load\n * @returns UseCalendly\n */\nexport const useCalendly = (\n config: UseCalendlyConfig,\n onReady?: () => void,\n onError?: (e: unknown) => void\n): UseCalendly => {\n config = {\n user: null,\n utmSource: \"XYPN\",\n utmMedium: \"\",\n ...config,\n }\n\n const opts: CalendlyOptions = {\n url: config.url,\n prefill: config.user\n ? {\n email: config.user.email,\n name: config.user.fullName,\n }\n : undefined,\n utm: {\n utmSource: config.utmSource,\n utmMedium: config.utmMedium,\n },\n }\n\n const isLoaded = ref(false)\n const loadingError = ref(false)\n\n let calendly: Calendly | undefined\n\n let onScheduledCb: (event: MessageEvent) => void | undefined\n\n const onScheduled = (cb: (url: string) => void) => {\n onScheduledCb = (event: MessageEvent) => {\n if (isEventScheduled(event)) {\n cb(event.data.payload.event.uri)\n }\n }\n }\n\n const isEventScheduled = (\n msg: MessageEvent\n ): msg is MessageEvent => {\n return (\n msg.origin === \"https://calendly.com\" &&\n msg.data?.event === \"calendly.event_scheduled\"\n )\n }\n\n // Existing use cases are all likely to benefit from the convenience of baked in error handling\n // and a genericError flash to the user if this call fails. This flash is a convenience for\n // existing use cases, but should not be held on to if future use cases want to avoid it.\n // In the future it can be refactored to return a Promise where the consumer\n // can handle errors explicitly.\n const openPopup = (): void => {\n try {\n calendly.initPopupWidget(opts)\n if (!hasCalendlyMessageListener) {\n window.addEventListener(\"message\", onScheduledCb)\n hasCalendlyMessageListener = true\n }\n } catch (err) {\n useAppFlasher.genericError()\n logger.report(err)\n }\n }\n\n // The Calendly api is the top reason that this method could throw an\n // unexpected error, but even in that case the user will have the option to close the\n // existing window, so in nearly all cases, we can likely avoid additional UI state (i.e. Flashes)\n // to let the user know something went wrong.\n const closePopup = (): void => {\n try {\n calendly.closePopupWidget()\n window.removeEventListener(\"message\", onScheduledCb)\n hasCalendlyMessageListener = false\n } catch (err) {\n logger.report(err)\n }\n }\n\n initCalendlyAssets()\n .then((c) => {\n calendly = c\n isLoaded.value = true\n onReady ? onReady() : undefined\n })\n .catch((err: unknown) => {\n logger.report(err, { config })\n loadingError.value = true\n onError ? onError(err) : undefined\n })\n\n return {\n closePopup,\n isLoaded,\n loadingError,\n onScheduled,\n openPopup,\n }\n}\n"],"names":["calendlyStyle","calendlyScript","hasCalendlyMessageListener","initCalendlyAssets","once","init","script","initScriptTag","calendly","style","initLinkTag","useCalendly","config","onReady","onError","opts","isLoaded","ref","loadingError","onScheduledCb","onScheduled","cb","event","isEventScheduled","msg","_a","openPopup","err","useAppFlasher","logger","closePopup","c"],"mappings":"4IAMA,MAAMA,EAAgB,yDAChBC,EAAiB,wDAKvB,IAAIC,EAA6B,GAuDjC,MAAMC,GAAqD,IAAA,CACrD,IAAAC,EAEJ,MAAMC,EAAO,SAA+B,CAC1C,MAAMC,EAAS,MAAMC,EAAcN,CAAc,EAAE,KAAK,IAAM,CAC5D,MAAMO,EAAY,OAAe,SACjC,OAAO,QAAQ,QAAQ,CAAE,GAAGA,EAAU,CAAA,CACvC,EAEKC,EAAQ,MAAMC,EAAYV,CAAa,EAAE,KAAK,IAC3C,QAAQ,QAAQ,CACxB,EAEM,OAAA,QAAQ,IAAI,CAACM,EAAQG,CAAK,CAAC,EAAE,KAAK,CAAC,CAACD,CAAQ,IAC1CA,CACR,CACH,EAEA,MAAO,WACAJ,IACHA,EAAOC,EAAK,GAGPD,EAEX,GAAG,EAgEUO,EAAc,CACzBC,EACAC,EACAC,IACgB,CACPF,EAAA,CACP,KAAM,KACN,UAAW,OACX,UAAW,GACX,GAAGA,CACL,EAEA,MAAMG,EAAwB,CAC5B,IAAKH,EAAO,IACZ,QAASA,EAAO,KACZ,CACE,MAAOA,EAAO,KAAK,MACnB,KAAMA,EAAO,KAAK,QAAA,EAEpB,OACJ,IAAK,CACH,UAAWA,EAAO,UAClB,UAAWA,EAAO,SAAA,CAEtB,EAEMI,EAAWC,EAAI,EAAK,EACpBC,EAAeD,EAAI,EAAK,EAE1B,IAAAT,EAEAW,EAEE,MAAAC,EAAeC,GAA8B,CACjDF,EAAiBG,GAAwB,CACnCC,EAAiBD,CAAK,GACxBD,EAAGC,EAAM,KAAK,QAAQ,MAAM,GAAG,CAEnC,CACF,EAEMC,EACJC,GACmD,OACnD,OACEA,EAAI,SAAW,0BACfC,EAAAD,EAAI,OAAJ,YAAAC,EAAU,SAAU,0BAExB,EAOMC,EAAY,IAAY,CACxB,GAAA,CACFlB,EAAS,gBAAgBO,CAAI,EACxBb,IACI,OAAA,iBAAiB,UAAWiB,CAAa,EACnBjB,EAAA,UAExByB,EAAK,CACZC,EAAc,aAAa,EAC3BC,EAAO,OAAOF,CAAG,CAAA,CAErB,EAMMG,EAAa,IAAY,CACzB,GAAA,CACFtB,EAAS,iBAAiB,EACnB,OAAA,oBAAoB,UAAWW,CAAa,EACtBjB,EAAA,SACtByB,EAAK,CACZE,EAAO,OAAOF,CAAG,CAAA,CAErB,EAEmB,OAAAxB,EAAA,EAChB,KAAM4B,GAAM,CACAvB,EAAAuB,EACXf,EAAS,MAAQ,EACK,CACvB,EACA,MAAOW,GAAiB,CACvBE,EAAO,OAAOF,EAAK,CAAE,OAAAf,CAAA,CAAQ,EAC7BM,EAAa,MAAQ,EACI,CAC1B,EAEI,CACL,WAAAY,EACA,SAAAd,EACA,aAAAE,EACA,YAAAE,EACA,UAAAM,CACF,CACF"}