Repository: tcampb/react-calendly Branch: master Commit: b88c1db792d0 Files: 19 Total size: 36.8 KB Directory structure: gitextract_z_zg3mar/ ├── .editorconfig ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── package.json ├── rollup.config.js ├── src/ │ ├── calendly-widget.css │ ├── calendly.tsx │ ├── components/ │ │ ├── InlineWidget/ │ │ │ └── InlineWidget.tsx │ │ ├── LoadingSpinner/ │ │ │ └── LoadingSpinner.tsx │ │ ├── PopupButton/ │ │ │ └── PopupButton.tsx │ │ ├── PopupModal/ │ │ │ ├── Modal.tsx │ │ │ └── ModalContent.tsx │ │ ├── PopupWidget/ │ │ │ └── PopupWidget.tsx │ │ └── hooks/ │ │ └── useCalendlyEventListener.ts │ ├── helpers/ │ │ └── propHelpers.ts │ └── index.tsx └── tsconfig.json ================================================ FILE CONTENTS ================================================ ================================================ FILE: .editorconfig ================================================ root = true [*] charset = utf-8 indent_style = space indent_size = 2 end_of_line = lf insert_final_newline = true trim_trailing_whitespace = true ================================================ FILE: .gitignore ================================================ # See https://help.github.com/ignore-files/ for more about ignoring files. # dependencies node_modules # builds build .rpt2_cache dist # misc .DS_Store .env .env.local .env.development.local .env.test.local .env.production.local npm-debug.log* yarn-debug.log* yarn-error.log* typings example .idea .vscode ================================================ FILE: CHANGELOG.md ================================================ # Changelog ## 4.4.0 - Added `className` option to `InlineWidget` component. - Removed SMS prefill option since it is no longer supported by Calendly. https://github.com/tcampb/react-calendly/issues/194 https://github.com/tcampb/react-calendly/issues/186 ## 4.3.1 - Added `onPageHeightResize` option to `useCalendlyEventListener` hook. This function is called when the Calendly scheduling page's height changes. The event payload includes the new height in pixels. https://github.com/tcampb/react-calendly/issues/174 https://github.com/tcampb/react-calendly/issues/137 https://github.com/tcampb/react-calendly/issues/133 https://github.com/tcampb/react-calendly/issues/15 https://github.com/tcampb/react-calendly/issues/145 ## 4.3.0 - Added `smsReminderNumber` prefill option (https://github.com/tcampb/react-calendly/pull/171). ## 4.2.1 - Removes unused sourcemaps (https://github.com/tcampb/react-calendly/issues/169). ## 4.2.0 - All components now include an optional `LoadingSpinner` prop. This prop is a React component that will be rendered while the Calendly iframe is loading; the default Calendly loading spinner will be displayed if this property is not provided. ## 4.1.1 - `PopupWidget`, `PopupModal`, and `PopupButton` components will throw an error when opened without a `rootElement` prop (https://github.com/tcampb/react-calendly/issues/143). ## 4.1.0 - Added `salesforce_uuid` prefill option (https://github.com/tcampb/react-calendly/pull/128). ## 4.0.1 - Fixed issue that caused the `email` and `guests` prefill options to not be properly encoded (https://github.com/tcampb/react-calendly/issues/116). ## 4.0.0 - Replaced `CalendlyEventListener` component with `useCalendlyEventListener` hook (https://github.com/tcampb/react-calendly/issues/45). - Updated `react` & `react-dom` peer dependency versions; `react-calendly@4.0.0` now requires react and react-dom version >=16.8. ## 3.0.3 - Supports new React 18 types (https://github.com/tcampb/react-calendly/pull/111). - Allows closing modal with an overlay click (https://github.com/tcampb/react-calendly/pull/110). ## 3.0.2 - Adds React v18 support (https://github.com/tcampb/react-calendly/issues/106). ## 3.0.1 - Fixes uri encoding bug (https://github.com/tcampb/react-calendly/pull/102). ## 3.0.0 - Removes Calendly widget script dependency (https://assets.calendly.com/assets/external/widget.js). - Removes `openPopupWidget` and `closePopupWidget` functions (replaced by `PopupModal` component). - Adds `PopupModal` component. ## 2.2.3 - [#96] Added title attribute to the Calendly scheduling page iframe. ## 2.2.2 - [#88] Fixed bug that caused the loading spinner to remain on the page even after the Calendly widget had finished loading. ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) 2020 Tyler Campbell Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ # react-calendly [Calendly integration](https://help.calendly.com/hc/en-us/articles/223147027-Embed-options-overview) for React apps [![NPM](https://img.shields.io/npm/v/react-calendly.svg)](https://www.npmjs.com/package/react-calendly) [![JavaScript Style Guide](https://img.shields.io/badge/code_style-standard-brightgreen.svg)](https://standardjs.com) [![](https://raw.githubusercontent.com/storybooks/brand/master/badge/badge-storybook.svg)](https://tcampb.github.io/react-calendly) ---

Are you looking to fully customize your React booking page?
Ship a React booking page you control - with https://components.calforce.pro and react-calendly-scheduler

--- react-calendly ## Installation Depending on the package manager you are using for your project, use `npm install` or `yarn add` to include react-calendly in your react app. ```bash npm install --save react-calendly ``` ```bash yarn add react-calendly ``` ## Documentation - [Basic Usage](#basic-usage) - [Advanced Usage](#advanced-usage) - [Frequently Asked Questions](#faq) ### Basic Usage Ensure that React has been included into your page or component. Then, you can import any of the following components from the "react-calendly" package: - [InlineWidget](#inlinewidget) - [PopupWidget](#popupwidget) - [PopupButton](#popupbutton) - [useCalendlyEventListener](#usecalendlyeventlistener) #### InlineWidget ```jsx import React from "react"; import { InlineWidget } from "react-calendly"; const App = () => { return (
); }; export default App; ``` #### PopupWidget ```jsx import React from "react"; import { PopupWidget } from "react-calendly"; const App = () => { return (
); }; export default App; ``` #### PopupButton ```jsx import React from "react"; import { PopupButton } from "react-calendly"; const App = () => { return (
); }; export default App; ``` #### useCalendlyEventListener ```jsx import React from "react"; import { useCalendlyEventListener, InlineWidget } from "react-calendly"; const App = () => { useCalendlyEventListener({ onProfilePageViewed: () => console.log("onProfilePageViewed"), onDateAndTimeSelected: () => console.log("onDateAndTimeSelected"), onEventTypeViewed: () => console.log("onEventTypeViewed"), onEventScheduled: (e) => console.log(e.data.payload), onPageHeightResize: (e) => console.log(e.data.payload.height), }); return (
); }; export default App; ``` ### Advanced Usage You can also take advantage of using optional props on the component(s) such as including a defined height, color customization options (available on Pro plan only), utm parameters, pre-filling custom questions, etc. Here are the optional props you can use with the inline embed: #### Inline Embed Height ```jsx styles={{ height: '1000px' }} ``` #### Page Settings ```jsx pageSettings={{ backgroundColor: 'ffffff', hideEventTypeDetails: false, hideLandingPageDetails: false, primaryColor: '00a2ff', textColor: '4d5055' }} ``` #### Prefill Values ```jsx prefill={{ email: 'test@test.com', firstName: 'Jon', lastName: 'Snow', name: 'Jon Snow', guests: [ 'janedoe@example.com', 'johndoe@example.com' ], customAnswers: { a1: 'a1', a2: 'a2', a3: 'a3', a4: 'a4', a5: 'a5', a6: 'a6', a7: 'a7', a8: 'a8', a9: 'a9', a10: 'a10' }, date: new Date(Date.now() + 86400000) }} ``` #### UTM Parameters ```jsx utm={{ utmCampaign: 'Spring Sale 2019', utmContent: 'Shoe and Shirts', utmMedium: 'Ad', utmSource: 'Facebook', utmTerm: 'Spring' }} ``` ## FAQ #### Why are my page settings not working? For the page settings to work, you'll need to pass in a `url` prop that is associated with a Calendly account on the [Pro plan](https://calendly.com/pages/pricing). #### How do I create a custom button that triggers a pop-up scheduler? ```tsx import { PopupModal } from "react-calendly"; class CustomButtonExample extends React.Component { constructor(props) { super(props); this.state = { isOpen: false, }; } render() { return (
this.setState({ isOpen: false })} open={this.state.isOpen} /* * react-calendly uses React's Portal feature (https://reactjs.org/docs/portals.html) to render the popup modal. As a result, you'll need to * specify the rootElement property to ensure that the modal is inserted into the correct domNode. */ rootElement={document.getElementById("root")} />
); } } ``` #### How can I access the event details when an event is scheduled? The [useCalendlyEventListener](https://tcampb.github.io/react-calendly/?path=/story/components--usecalendlyeventlistener) `onEventScheduled` prop receives an event with the following data structure: ```javascript { event: "calendly.event_scheduled", payload: { event: { uri: "https://calendly.com/api/v2/scheduled_events/AAAAAAAAAAAAAA" }, invitee: { uri: "https://calendly.com/api/v2/scheduled_events/AAAAAAAAAAAAAA/invitees/AAAAAAAAAAAAAA" } } } ``` If you are using [Calendly's v2 api](https://developer.calendly.com/docs/api-docs/docs/A-API-Getting-Started.md) you can reference the event/invitee URIs included in the event payload to retrieve additional information about the event and/or invitee record. - [Scheduled Event Schema](https://developer.calendly.com/api-docs/e2f95ebd44914-get-event) - [Invitee Schema](https://developer.calendly.com/api-docs/8305c0ccfac70-get-event-invitee) #### Can I use react-calendly with Nextjs? Yes, see https://github.com/tcampb/react-calendly/issues/105 for additional details. ## Additional Resources [Embed options overview](https://help.calendly.com/hc/en-us/articles/223147027-Embed-options-overview) [Advanced embed options](https://help.calendly.com/hc/en-us/articles/360020052833-Advanced-embed-options) [Common embed questions](https://help.calendly.com/hc/en-us/articles/360019861794-Common-embed-questions) ## License MIT © [tcampb](https://github.com/tcampb) ================================================ FILE: package.json ================================================ { "name": "react-calendly", "version": "4.4.0", "description": "Calendly integration for React apps", "author": "tcampb", "license": "MIT", "repository": "tcampb/react-calendly", "main": "dist/index.js", "typings": "typings/index.d.ts", "module": "dist/index.es.js", "jsnext:main": "dist/index.es.js", "engines": { "node": ">=8", "npm": ">=5" }, "keywords": [ "react", "calendly", "component" ], "scripts": { "build": "rollup -c && tsc --emitDeclarationOnly", "start": "rollup -c -w", "prepare": "npm run build" }, "peerDependencies": { "react": ">=16.8.0", "react-dom": ">=16.8.0" }, "devDependencies": { "@rollup/plugin-typescript": "^11.1.4", "@types/react": "^16.8.0", "@types/react-dom": "^16.8.0", "prettier": "2.0.2", "react": "^16.8.0", "react-dom": "^16.8.0", "rollup": "^3.29.4", "typescript": "^4.0.5", "@rollup/plugin-commonjs": "^25.0.4", "@rollup/plugin-node-resolve": "^15.2.1", "rollup-plugin-peer-deps-external": "^2.2.4", "rollup-plugin-postcss": "^4.0.2", "tslib": "^2.6.2" }, "files": [ "dist", "typings" ] } ================================================ FILE: rollup.config.js ================================================ const typescript = require("@rollup/plugin-typescript"); const commonjs = require('@rollup/plugin-commonjs') const pkg = require("./package.json") const postcss = require('rollup-plugin-postcss'); const external = require('rollup-plugin-peer-deps-external'); module.exports = { input: "src/index.tsx", output: [ { file: pkg.main, format: "cjs", exports: "named" }, { file: pkg.module, format: "es", exports: "named" }, ], plugins: [external(), postcss(), typescript(), commonjs()], }; ================================================ FILE: src/calendly-widget.css ================================================ /* code is extracted from Calendly's embed stylesheet: https://assets.calendly.com/assets/external/widget.css */ .calendly-inline-widget, .calendly-inline-widget *, .calendly-badge-widget, .calendly-badge-widget *, .calendly-overlay, .calendly-overlay * { font-size: 16px; line-height: 1.2em; } .calendly-inline-widget { min-width: 320px; height: 630px; } .calendly-inline-widget iframe, .calendly-badge-widget iframe, .calendly-overlay iframe { display: inline; width: 100%; height: 100%; } .calendly-popup-content { position: relative; } .calendly-popup-content.calendly-mobile { -webkit-overflow-scrolling: touch; overflow-y: auto; } .calendly-overlay { position: fixed; top: 0; left: 0; right: 0; bottom: 0; overflow: hidden; z-index: 9999; background-color: #a5a5a5; background-color: rgba(31, 31, 31, 0.4); } .calendly-overlay .calendly-close-overlay { position: absolute; top: 0; left: 0; right: 0; bottom: 0; } .calendly-overlay .calendly-popup { box-sizing: border-box; position: absolute; top: 50%; left: 50%; -webkit-transform: translateY(-50%) translateX(-50%); transform: translateY(-50%) translateX(-50%); width: 80%; min-width: 900px; max-width: 1000px; height: 90%; max-height: 680px; } @media (max-width: 975px) { .calendly-overlay .calendly-popup { position: fixed; top: 50px; left: 0; right: 0; bottom: 0; -webkit-transform: none; transform: none; width: 100%; height: auto; min-width: 0; max-height: none; } } .calendly-overlay .calendly-popup .calendly-popup-content { height: 100%; } .calendly-overlay .calendly-popup-close { position: absolute; top: 25px; right: 25px; color: #fff; width: 19px; height: 19px; cursor: pointer; background: url(https://assets.calendly.com/assets/external/close-icon.svg) no-repeat; background-size: contain; } @media (max-width: 975px) { .calendly-overlay .calendly-popup-close { top: 15px; right: 15px; } } .calendly-badge-widget { position: fixed; right: 20px; bottom: 15px; z-index: 9998; } .calendly-badge-widget .calendly-badge-content { display: table-cell; width: auto; height: 45px; padding: 0 30px; border-radius: 25px; box-shadow: rgba(0, 0, 0, 0.25) 0 2px 5px; font-family: sans-serif; text-align: center; vertical-align: middle; font-weight: bold; font-size: 14px; color: #fff; cursor: pointer; } .calendly-badge-widget .calendly-badge-content.calendly-white { color: #666a73; } .calendly-badge-widget .calendly-badge-content span { display: block; font-size: 12px; } .calendly-spinner { position: absolute; top: 50%; left: 0; right: 0; -webkit-transform: translateY(-50%); transform: translateY(-50%); text-align: center; z-index: -1; } .calendly-spinner > div { display: inline-block; width: 18px; height: 18px; background-color: #e1e1e1; border-radius: 50%; vertical-align: middle; -webkit-animation: calendly-bouncedelay 1.4s infinite ease-in-out; animation: calendly-bouncedelay 1.4s infinite ease-in-out; -webkit-animation-fill-mode: both; animation-fill-mode: both; } .calendly-spinner .calendly-bounce1 { -webkit-animation-delay: -0.32s; animation-delay: -0.32s; } .calendly-spinner .calendly-bounce2 { -webkit-animation-delay: -0.16s; animation-delay: -0.16s; } @-webkit-keyframes calendly-bouncedelay { 0%, 80%, 100% { -webkit-transform: scale(0); transform: scale(0); } 40% { -webkit-transform: scale(1); transform: scale(1); } } @keyframes calendly-bouncedelay { 0%, 80%, 100% { -webkit-transform: scale(0); transform: scale(0); } 40% { -webkit-transform: scale(1); transform: scale(1); } } ================================================ FILE: src/calendly.tsx ================================================ import { sanitizePageSettingsProps } from "./helpers/propHelpers"; type Optional = { [P in keyof T]?: T[P]; }; export type Prefill = Optional<{ name: string; email: string; firstName: string; lastName: string; location: string; guests: string[]; customAnswers: Optional<{ a1: string; a2: string; a3: string; a4: string; a5: string; a6: string; a7: string; a8: string; a9: string; a10: string; }>; date: Date; }>; export enum CalendlyEvent { PROFILE_PAGE_VIEWED = "calendly.profile_page_viewed", EVENT_TYPE_VIEWED = "calendly.event_type_viewed", DATE_AND_TIME_SELECTED = "calendly.date_and_time_selected", EVENT_SCHEDULED = "calendly.event_scheduled", PAGE_HEIGHT = "calendly.page_height", } export type Utm = Optional<{ utmCampaign: string; utmSource: string; utmMedium: string; utmContent: string; utmTerm: string; salesforce_uuid: string; }>; /** * @description The default title is Calendly Scheduling Page * @see {@link https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/title} */ export type IframeTitle = string; /** * @description LoadingSpinner is a React component that will be displayed while the Calendly iframe is loading. If no component is provided, the default Calendly loading spinner will be used. */ export type LoadingSpinner = React.FunctionComponent; export type PageSettings = Optional<{ /** * @description Use this setting to hide your profile picture, name, event duration, location, and description when Calendly is embedded. This will help reduce duplicate information that you may already have on your web page. * @see {@link https://help.calendly.com/hc/en-us/articles/360020052833-Advanced-embed-options#2} for further information. */ hideLandingPageDetails: boolean; /** * @description Use this setting to hide your profile picture, name, event duration, location, and description when Calendly is embedded. This will help reduce duplicate information that you may already have on your web page. * @see {@link https://help.calendly.com/hc/en-us/articles/360020052833-Advanced-embed-options#2} for further information. */ hideEventTypeDetails: boolean; /** * @description This setting is only available for Calendly users on the Pro plan. Use this setting to change your Calendly scheduling page's background color. * @example 00a2ff * @see {@link https://help.calendly.com/hc/en-us/articles/223147027-Embed-options-overview#3} for further information. */ backgroundColor: string; /** * @description This setting is only available for Calendly users on the Pro plan. Use this setting to change your Calendly scheduling page's text color. * @example ffffff * @see {@link https://help.calendly.com/hc/en-us/articles/223147027-Embed-options-overview#3} for further information. */ textColor: string; /** * @description This setting is only available for Calendly users on the Pro plan. Use this setting to change your Calendly scheduling page's primary color. * @example 4d5055 * @see {@link https://help.calendly.com/hc/en-us/articles/223147027-Embed-options-overview#3} for further information. */ primaryColor: string; /** * @description The General Data Protection Regulation governs data protection in the EU and EEA. Certain Calendly integrations require access to cookies with user information. If you do not embed the GDPR banner, users in those areas will not have the ability to give their consent in order to access integrations such as Google Analytics, Facebook Pixel, PayPal, and Stripe. * @see {@link https://help.calendly.com/hc/en-us/articles/360007385493-Cookie-FAQs} for further information. */ hideGdprBanner: boolean; }>; export const formatCalendlyUrl = ({ url, prefill = {}, pageSettings = {}, utm = {}, embedType, }: { url: string; prefill?: Prefill; pageSettings?: PageSettings; utm?: Utm; embedType?: "Inline" | "PopupWidget" | "PopupButton"; }) => { const sanitizedPageSettings = sanitizePageSettingsProps(pageSettings); const { backgroundColor, hideEventTypeDetails, hideLandingPageDetails, primaryColor, textColor, hideGdprBanner, } = sanitizedPageSettings; const { customAnswers, date, email, firstName, guests, lastName, location, name, } = prefill; const { utmCampaign, utmContent, utmMedium, utmSource, utmTerm, salesforce_uuid, } = utm; const queryStringIndex = url.indexOf("?"); const hasQueryString = queryStringIndex > -1; const queryString = url.slice(queryStringIndex + 1); const baseUrl = hasQueryString ? url.slice(0, queryStringIndex) : url; const updatedQueryString = [ hasQueryString ? queryString : null, backgroundColor ? `background_color=${backgroundColor}` : null, hideEventTypeDetails ? `hide_event_type_details=1` : null, hideLandingPageDetails ? `hide_landing_page_details=1` : null, primaryColor ? `primary_color=${primaryColor}` : null, textColor ? `text_color=${textColor}` : null, hideGdprBanner ? `hide_gdpr_banner=1` : null, name ? `name=${encodeURIComponent(name)}` : null, location ? `location=${encodeURIComponent(location)}` : null, firstName ? `first_name=${encodeURIComponent(firstName)}` : null, lastName ? `last_name=${encodeURIComponent(lastName)}` : null, guests ? `guests=${guests.map(encodeURIComponent).join(",")}` : null, email ? `email=${encodeURIComponent(email)}` : null, date && date instanceof Date ? `date=${formatDate(date)}` : null, utmCampaign ? `utm_campaign=${encodeURIComponent(utmCampaign)}` : null, utmContent ? `utm_content=${encodeURIComponent(utmContent)}` : null, utmMedium ? `utm_medium=${encodeURIComponent(utmMedium)}` : null, utmSource ? `utm_source=${encodeURIComponent(utmSource)}` : null, utmTerm ? `utm_term=${encodeURIComponent(utmTerm)}` : null, salesforce_uuid ? `salesforce_uuid=${encodeURIComponent(salesforce_uuid)}` : null, embedType ? `embed_type=${embedType}` : null, /* * https://github.com/tcampb/react-calendly/pull/31 * embed_domain must be defined to receive messages from the Calendly iframe. */ `embed_domain=1`, ] .concat(customAnswers ? formatCustomAnswers(customAnswers) : []) .filter((item) => item !== null) .join("&"); return `${baseUrl}?${updatedQueryString}`; }; const formatDate = (d: Date) => { const month = d.getMonth() + 1; const day = d.getDate(); const year = d.getFullYear(); return [ year, month < 10 ? `0${month}` : month, day < 10 ? `0${day}` : day, ].join("-"); }; const CUSTOM_ANSWER_PATTERN = /^a\d{1,2}$/; const formatCustomAnswers = (customAnswers: object) => { const customAnswersFiltered = Object.keys(customAnswers).filter((key) => key.match(CUSTOM_ANSWER_PATTERN) ); if (!customAnswersFiltered.length) return []; return customAnswersFiltered.map( (key) => `${key}=${encodeURIComponent(customAnswers[key])}` ); }; ================================================ FILE: src/components/InlineWidget/InlineWidget.tsx ================================================ import * as React from "react"; import "../../calendly-widget.css"; import { PageSettings, Prefill, Utm, IframeTitle, formatCalendlyUrl, LoadingSpinner, } from "../../calendly"; import CalendlyLoadingSpinner from "../LoadingSpinner/LoadingSpinner"; export interface Props { url: string; prefill?: Prefill; utm?: Utm; styles?: React.CSSProperties | undefined; pageSettings?: PageSettings; iframeTitle?: IframeTitle; LoadingSpinner?: LoadingSpinner; className?: string; } const defaultClassName = "calendly-inline-widget"; class InlineWidget extends React.Component { constructor(props: Props) { super(props); this.state = { isLoading: true, }; this.onLoad = this.onLoad.bind(this); } private onLoad() { this.setState({ isLoading: false, }); } render() { const src = formatCalendlyUrl({ url: this.props.url, pageSettings: this.props.pageSettings, prefill: this.props.prefill, utm: this.props.utm, embedType: "Inline", }); const LoadingSpinner = this.props.LoadingSpinner || CalendlyLoadingSpinner; return (
{this.state.isLoading && }
); } } export default InlineWidget; ================================================ FILE: src/components/LoadingSpinner/LoadingSpinner.tsx ================================================ import * as React from "react"; import "../../calendly-widget.css"; class LoadingSpinner extends React.Component { render() { return (
); } } export default LoadingSpinner; ================================================ FILE: src/components/PopupButton/PopupButton.tsx ================================================ import * as React from "react"; import "../../calendly-widget.css"; import { PageSettings, Prefill, Utm, IframeTitle, LoadingSpinner } from "../../calendly"; import Modal from "../PopupModal/Modal"; export interface Props { url: string; text: string; rootElement: HTMLElement; prefill?: Prefill; utm?: Utm; pageSettings?: PageSettings; styles?: React.CSSProperties | undefined; className?: string; iframeTitle?: IframeTitle; LoadingSpinner?: LoadingSpinner; } class PopupButton extends React.Component { constructor(props: Props) { super(props); this.state = { isOpen: false, }; this.onClick = this.onClick.bind(this); this.onClose = this.onClose.bind(this); } onClick(e: React.SyntheticEvent) { e.preventDefault(); this.setState({ isOpen: true, }); } onClose(e: React.SyntheticEvent) { e.stopPropagation(); this.setState({ isOpen: false, }); } render() { return ( <> ); } } export default PopupButton; ================================================ FILE: src/components/PopupModal/Modal.tsx ================================================ import * as React from "react"; import * as ReactDom from "react-dom"; import { LoadingSpinner } from "../../calendly"; import ModalContent, { Props as ModalContentProps } from "./ModalContent"; interface Props extends ModalContentProps { onModalClose: (e: React.MouseEvent) => void; open: boolean; rootElement: HTMLElement; LoadingSpinner?: LoadingSpinner; } export default (props: Props) => { if (!props.open) return null; if (!props.rootElement) { throw new Error('[react-calendly]: PopupModal rootElement property cannot be undefined') } return ReactDom.createPortal(
, props.rootElement ); }; ================================================ FILE: src/components/PopupModal/ModalContent.tsx ================================================ import * as React from "react"; import "../../calendly-widget.css"; import { PageSettings, Prefill, Utm, IframeTitle, formatCalendlyUrl, LoadingSpinner, } from "../../calendly"; import CalendlyLoadingSpinner from "../LoadingSpinner/LoadingSpinner"; export interface Props { url: string; prefill?: Prefill; utm?: Utm; pageSettings?: PageSettings; iframeTitle?: IframeTitle; LoadingSpinner?: LoadingSpinner; } class ModalContent extends React.Component { constructor(props: Props) { super(props); this.state = { isLoading: true, }; this.onLoad = this.onLoad.bind(this); } private onLoad() { this.setState({ isLoading: false, }); } render() { const src = formatCalendlyUrl({ url: this.props.url, pageSettings: this.props.pageSettings, prefill: this.props.prefill, utm: this.props.utm, embedType: "Inline", }); const LoadingSpinner = this.props.LoadingSpinner || CalendlyLoadingSpinner; return ( <> {this.state.isLoading && } ); } } export default ModalContent; ================================================ FILE: src/components/PopupWidget/PopupWidget.tsx ================================================ import * as React from "react"; import "../../calendly-widget.css"; import { PageSettings, Prefill, Utm, IframeTitle, LoadingSpinner } from "../../calendly"; import Modal from "../PopupModal/Modal"; export interface Props { url: string; text: string; rootElement: HTMLElement; color?: string; textColor?: string; branding?: boolean; prefill?: Prefill; utm?: Utm; pageSettings?: PageSettings; iframeTitle?: IframeTitle; LoadingSpinner?: LoadingSpinner; } class PopupWidget extends React.Component { constructor(props: Props) { super(props); this.state = { isOpen: false, }; this.onClick = this.onClick.bind(this); this.onClose = this.onClose.bind(this); } onClick() { this.setState({ isOpen: true, }); } onClose(e: React.SyntheticEvent) { e.stopPropagation(); this.setState({ isOpen: false, }); } render() { return (
{this.props.text || "Schedule time with me"} {this.props.branding && powered by Calendly}
); } } export default PopupWidget; ================================================ FILE: src/components/hooks/useCalendlyEventListener.ts ================================================ import * as React from "react"; import { CalendlyEvent } from "../../calendly"; export type DateAndTimeSelectedEvent = MessageEvent<{ event: CalendlyEvent.DATE_AND_TIME_SELECTED; payload: {}; }>; export type EventScheduledEvent = MessageEvent<{ event: CalendlyEvent.EVENT_SCHEDULED; payload: { event: { /** * @description Canonical reference (unique identifier) to the event that was scheduled. * @example https://calendly.com/api/v2/scheduled_events/AAAAAAAAAAAAAA * @see {@link https://developer.calendly.com/docs/api-docs/reference/calendly-api/openapi.yaml/paths/~1scheduled_events~1%7Buuid%7D/get} for further information. */ uri: string; }; invitee: { /** * @description Canonical reference (unique identifier) for the invitee who scheduled the event. * @example https://calendly.com/api/v2/scheduled_events/AAAAAAAAAAAAAA/invitees/AAAAAAAAAAAAAA * @see {@link https://developer.calendly.com/docs/api-docs/reference/calendly-api/openapi.yaml/paths/~1scheduled_events~1%7Bevent_uuid%7D~1invitees~1%7Binvitee_uuid%7D/get} for further information. */ uri: string; }; }; }>; export type EventTypeViewedEvent = MessageEvent<{ event: CalendlyEvent.EVENT_TYPE_VIEWED; payload: {}; }>; export type ProfilePageViewedEvent = MessageEvent<{ event: CalendlyEvent.PROFILE_PAGE_VIEWED; payload: {}; }>; export type PageHeightResizeEvent = MessageEvent<{ event: CalendlyEvent.PAGE_HEIGHT; payload: { /** * @description The height of the Calendly scheduling page in pixels. * @example 1200px */ height: string; }; }>; export type CalendlyEventHandlers = { onDateAndTimeSelected?: (e: DateAndTimeSelectedEvent) => any; onEventScheduled?: (e: EventScheduledEvent) => any; onEventTypeViewed?: (e: EventTypeViewedEvent) => any; onProfilePageViewed?: (e: ProfilePageViewedEvent) => any; onPageHeightResize?: (e: PageHeightResizeEvent) => any; }; const EVENT_NAME = "message"; export default function useCalendlyEventListener( eventHandlers: CalendlyEventHandlers ) { const { onDateAndTimeSelected, onEventScheduled, onEventTypeViewed, onProfilePageViewed, onPageHeightResize } = eventHandlers || {}; React.useEffect(() => { const onMessage = (e: MessageEvent) => { const eventName = e.data.event; if (eventName === CalendlyEvent.DATE_AND_TIME_SELECTED) { onDateAndTimeSelected && onDateAndTimeSelected(e); } else if (eventName === CalendlyEvent.EVENT_SCHEDULED) { onEventScheduled && onEventScheduled(e); } else if (eventName === CalendlyEvent.EVENT_TYPE_VIEWED) { onEventTypeViewed && onEventTypeViewed(e); } else if (eventName === CalendlyEvent.PROFILE_PAGE_VIEWED) { onProfilePageViewed && onProfilePageViewed(e); } else if (eventName === CalendlyEvent.PAGE_HEIGHT) { onPageHeightResize && onPageHeightResize(e); } }; window.addEventListener(EVENT_NAME, onMessage); return function cleanup() { window.removeEventListener(EVENT_NAME, onMessage); }; }, [eventHandlers]); } ================================================ FILE: src/helpers/propHelpers.ts ================================================ import { PageSettings } from "../calendly"; function sanitizeColorString(str: string): string { if (str.charAt(0) === "#") { return str.slice(1); } return str; } export function sanitizePageSettingsProps( props: PageSettings ) { if (props?.primaryColor) { props.primaryColor = sanitizeColorString(props.primaryColor); } if (props?.textColor) { props.textColor = sanitizeColorString(props.textColor); } if (props?.backgroundColor) { props.backgroundColor = sanitizeColorString(props.backgroundColor); } return props; } ================================================ FILE: src/index.tsx ================================================ import InlineWidget from "./components/InlineWidget/InlineWidget"; import PopupButton from "./components/PopupButton/PopupButton"; import PopupWidget from "./components/PopupWidget/PopupWidget"; import PopupModal from "./components/PopupModal/Modal"; import useCalendlyEventListener from "./components/hooks/useCalendlyEventListener"; import type { DateAndTimeSelectedEvent, EventScheduledEvent, EventTypeViewedEvent, ProfilePageViewedEvent, } from "./components/hooks/useCalendlyEventListener"; export { InlineWidget }; export { PopupButton }; export { PopupWidget }; export { PopupModal }; export { useCalendlyEventListener }; export { DateAndTimeSelectedEvent, EventScheduledEvent, EventTypeViewedEvent, ProfilePageViewedEvent, }; ================================================ FILE: tsconfig.json ================================================ { "compilerOptions": { "outDir": "build", "module": "esnext", "target": "es5", "lib": ["es6", "dom", "es2016", "es2017"], "sourceMap": true, "allowJs": false, "jsx": "react", "declaration": true, "declarationDir": "typings", "moduleResolution": "node", "forceConsistentCasingInFileNames": true, "noImplicitReturns": true, "noImplicitThis": true, "noImplicitAny": true, "strictNullChecks": true, "suppressImplicitAnyIndexErrors": true, "noUnusedLocals": true, "noUnusedParameters": true }, "include": ["src"], "exclude": [ "node_modules", "build", "dist", "rollup.config.js", "**/*.stories.tsx" ] }