From ffe7c5cfb250caba4366d8e060dd059df56149d6 Mon Sep 17 00:00:00 2001 From: Ulises Jeremias Date: Thu, 4 Jul 2024 12:38:31 -0300 Subject: [PATCH] Zendesk: Vanilla and React integration (#111) * feat: Update dependencies to latest versions Co-authored-by: Matias Pompilio Co-authored-by: Ulises Jeremias * feat: Update dependencies to latest versions * feat: Add zendesk export to react-thirdparty package * feat: Add Zendesk integration to react-thirdparty package * feat: Add Zendesk integration to react-thirdparty package * feat: Refactor Zendesk integration in react-thirdparty package This commit refactors the Zendesk integration in the react-thirdparty package. It updates the `ZendeskContextProps` interface in `Context.tsx` to use the `ZendeskWidget` type instead of `any`. It also updates the `ZendeskProvider` component in `Provider.tsx` to use the `ZendeskWidget` type for the `zendeskClient` state and the `executeZendesk` function. Co-authored-by: Matias Pompilio Co-authored-by: Ulises Jeremias * refactor: Update Zendesk integration in react-thirdparty package * refactor: Update Zendesk integration in react-thirdparty package * chore: Update third-party scripts to include source URLs Co-authored-by: Matias Pompilio Co-authored-by: Ulises Jeremias --------- Co-authored-by: Matias Pompilio --- .changeset/curvy-bikes-peel.md | 8 + .../react-thirdparty/Zendesk.stories.mdx | 120 ++++++++++++ packages/react-thirdparty/index.tsx | 1 + .../react-thirdparty/recaptcha/Context.tsx | 5 +- .../recaptcha/GoogleReCaptcha.tsx | 4 +- .../react-thirdparty/recaptcha/Provider.tsx | 10 +- packages/react-thirdparty/recaptcha/index.ts | 1 - .../recaptcha/useGoogleReCaptcha.ts | 2 +- .../recaptcha/useGoogleReCaptchaContext.ts | 5 - packages/react-thirdparty/zendesk/Context.tsx | 14 ++ .../react-thirdparty/zendesk/Provider.tsx | 53 ++++++ packages/react-thirdparty/zendesk/README.md | 82 +++++++++ packages/react-thirdparty/zendesk/Zendesk.tsx | 29 +++ packages/react-thirdparty/zendesk/index.ts | 4 + .../react-thirdparty/zendesk/useZendesk.ts | 7 + .../react-thirdparty/zendesk/zendesk.d.ts | 172 ++++++++++++++++++ packages/thirdparty/index.ts | 1 + packages/thirdparty/recaptcha/scripts.ts | 19 +- packages/thirdparty/zendesk/index.ts | 1 + packages/thirdparty/zendesk/scripts.ts | 69 +++++++ packages/tsconfig/react-library.json | 2 +- 21 files changed, 586 insertions(+), 23 deletions(-) create mode 100644 .changeset/curvy-bikes-peel.md create mode 100644 apps/playground/src/stories/react-thirdparty/Zendesk.stories.mdx delete mode 100644 packages/react-thirdparty/recaptcha/useGoogleReCaptchaContext.ts create mode 100644 packages/react-thirdparty/zendesk/Context.tsx create mode 100644 packages/react-thirdparty/zendesk/Provider.tsx create mode 100644 packages/react-thirdparty/zendesk/README.md create mode 100644 packages/react-thirdparty/zendesk/Zendesk.tsx create mode 100644 packages/react-thirdparty/zendesk/index.ts create mode 100644 packages/react-thirdparty/zendesk/useZendesk.ts create mode 100644 packages/react-thirdparty/zendesk/zendesk.d.ts create mode 100644 packages/thirdparty/zendesk/index.ts create mode 100644 packages/thirdparty/zendesk/scripts.ts diff --git a/.changeset/curvy-bikes-peel.md b/.changeset/curvy-bikes-peel.md new file mode 100644 index 0000000..6b09ba5 --- /dev/null +++ b/.changeset/curvy-bikes-peel.md @@ -0,0 +1,8 @@ +--- +"@nanlabs/react-thirdparty": minor +"@nanlabs/thirdparty": minor +"@nanlabs/playground": patch +"tsconfig": patch +--- + +Added Zendesk integration diff --git a/apps/playground/src/stories/react-thirdparty/Zendesk.stories.mdx b/apps/playground/src/stories/react-thirdparty/Zendesk.stories.mdx new file mode 100644 index 0000000..38a3bd9 --- /dev/null +++ b/apps/playground/src/stories/react-thirdparty/Zendesk.stories.mdx @@ -0,0 +1,120 @@ +import { Meta } from "@storybook/addon-docs"; + + + +

React Zendesk Integration

+
+ +Integrating Zendesk + +
+ +## Usage + +### Provide Zendesk Key + +To use this integration, you need to create a Zendesk key for your domain. + +### Components + +#### ZendeskProvider + +`ZendeskProvider` is a React component that wraps your application and loads the Zendesk script. It provides context to other components and hooks that need to interact with Zendesk. + +```jsx +import { ZendeskProvider } from "@nanlabs/react-thirdparty"; + +const App = () => ( + console.log("Zendesk script loaded!")} + > + + +); + +export default App; +``` + +#### Zendesk Component + +`Zendesk` is a React component that can be used in your app to load the Zendesk script. It utilizes the context provided by `ZendeskProvider`. + +```jsx +import React from "react"; +import { Zendesk } from "@nanlabs/react-thirdparty"; + +const YourComponent = () => ( +
+ +
+); + +export default YourComponent; +``` + +### React Hook: useZendesk + +If you prefer a React Hook approach, you can choose to use the custom hook `useZendesk`. + +It's very simple to use the hook: + +```tsx +import React from "react"; +import { useZendesk } from "@nanlabs/react-thirdparty"; + +const YourZendeskComponent = () => { + const { executeZendesk } = useZendesk(); + + return ( +
+ +
+ ); +}; + +export default YourZendeskComponent; +``` + +## More Examples! + +### Zendesk Component Example + +```jsx +import React from "react"; +import { ZendeskProvider, Zendesk } from "@nanlabs/react-thirdparty"; + +export const ZendeskComponentExample = () => ( + + + +); +``` + +### useZendesk Hook Example + +```jsx +import React from "react"; +import { ZendeskProvider, useZendesk } from "@nanlabs/react-thirdparty"; + +export const UseZendeskHookExample = () => { + const { executeZendesk } = useZendesk(); + + return ( +
+ +
+ ); +}; + +export const UseZendeskHookExampleWrapped = () => ( + + + +); +``` diff --git a/packages/react-thirdparty/index.tsx b/packages/react-thirdparty/index.tsx index a69c535..c3a1253 100644 --- a/packages/react-thirdparty/index.tsx +++ b/packages/react-thirdparty/index.tsx @@ -1 +1,2 @@ export * from "./recaptcha"; +export * from "./zendesk"; diff --git a/packages/react-thirdparty/recaptcha/Context.tsx b/packages/react-thirdparty/recaptcha/Context.tsx index 3513994..e7bfecc 100644 --- a/packages/react-thirdparty/recaptcha/Context.tsx +++ b/packages/react-thirdparty/recaptcha/Context.tsx @@ -1,4 +1,4 @@ -import { createContext } from "react"; +import { createContext, useContext } from "react"; import { ReCaptchaExecuteOptions } from "./Provider"; export interface GoogleReCaptchaConsumerProps { @@ -12,3 +12,6 @@ const GoogleReCaptchaContext = createContext({ const { Consumer: GoogleReCaptchaConsumer } = GoogleReCaptchaContext; export { GoogleReCaptchaConsumer, GoogleReCaptchaContext }; + +export const useGoogleReCaptchaContext = () => + useContext(GoogleReCaptchaContext); diff --git a/packages/react-thirdparty/recaptcha/GoogleReCaptcha.tsx b/packages/react-thirdparty/recaptcha/GoogleReCaptcha.tsx index a492b97..28df58f 100644 --- a/packages/react-thirdparty/recaptcha/GoogleReCaptcha.tsx +++ b/packages/react-thirdparty/recaptcha/GoogleReCaptcha.tsx @@ -1,4 +1,4 @@ -import { FC } from "react"; +import React, { FC } from "react"; import { GoogleReCaptchaConfig, useGoogleReCaptcha, @@ -6,5 +6,5 @@ import { export const GoogleReCaptcha: FC = (config) => { useGoogleReCaptcha(config); - return null; + return
; }; diff --git a/packages/react-thirdparty/recaptcha/Provider.tsx b/packages/react-thirdparty/recaptcha/Provider.tsx index 4accd14..41e11f1 100644 --- a/packages/react-thirdparty/recaptcha/Provider.tsx +++ b/packages/react-thirdparty/recaptcha/Provider.tsx @@ -1,6 +1,10 @@ import React, { FC, useCallback, useEffect, useState } from "react"; import { GoogleReCaptchaContext } from "./Context"; -import { injectGoogleReCaptchaScript, removeScript } from "@nanlabs/thirdparty"; +import { + GoogleReCaptchaSrc, + injectGoogleReCaptchaScript, + removeGoogleReCaptchaScript, +} from "@nanlabs/thirdparty"; const SCRIPT_ID = "google-recaptcha-v3"; @@ -38,7 +42,7 @@ export const GoogleReCaptchaProvider: FC = ({ }) => { const [grecaptcha, setGrecaptcha] = useState(null); - const googleRecaptchaSrc = () => { + const googleRecaptchaSrc = (): GoogleReCaptchaSrc => { const hostName = useRecaptchaNet ? "recaptcha.net" : "google.com"; return `https://www.${hostName}/recaptcha/api.js`; @@ -83,7 +87,7 @@ export const GoogleReCaptchaProvider: FC = ({ return () => { // remove badge and script - removeScript(SCRIPT_ID); + removeGoogleReCaptchaScript(SCRIPT_ID); }; }, [reCaptchaKey]); diff --git a/packages/react-thirdparty/recaptcha/index.ts b/packages/react-thirdparty/recaptcha/index.ts index 9586bef..38290b4 100644 --- a/packages/react-thirdparty/recaptcha/index.ts +++ b/packages/react-thirdparty/recaptcha/index.ts @@ -1,5 +1,4 @@ export * from "./useGoogleReCaptcha"; -export * from "./useGoogleReCaptchaContext"; export * from "./GoogleReCaptcha"; export * from "./Context"; export * from "./Provider"; diff --git a/packages/react-thirdparty/recaptcha/useGoogleReCaptcha.ts b/packages/react-thirdparty/recaptcha/useGoogleReCaptcha.ts index 7bf2b67..83d80ff 100644 --- a/packages/react-thirdparty/recaptcha/useGoogleReCaptcha.ts +++ b/packages/react-thirdparty/recaptcha/useGoogleReCaptcha.ts @@ -1,5 +1,5 @@ import { useCallback, useEffect } from "react"; -import { useGoogleReCaptchaContext } from "./useGoogleReCaptchaContext"; +import { useGoogleReCaptchaContext } from "./Context"; export interface GoogleReCaptchaConfig { onVerify: (token: string) => void | Promise; diff --git a/packages/react-thirdparty/recaptcha/useGoogleReCaptchaContext.ts b/packages/react-thirdparty/recaptcha/useGoogleReCaptchaContext.ts deleted file mode 100644 index 4e21ed0..0000000 --- a/packages/react-thirdparty/recaptcha/useGoogleReCaptchaContext.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { useContext } from "react"; -import { GoogleReCaptchaContext } from "./Context"; - -export const useGoogleReCaptchaContext = () => - useContext(GoogleReCaptchaContext); diff --git a/packages/react-thirdparty/zendesk/Context.tsx b/packages/react-thirdparty/zendesk/Context.tsx new file mode 100644 index 0000000..aa474b7 --- /dev/null +++ b/packages/react-thirdparty/zendesk/Context.tsx @@ -0,0 +1,14 @@ +import { createContext, useContext } from "react"; + +export interface ZendeskContextProps { + executeZendesk?: ZendeskMessagingWidget; +} + +export const ZendeskContext = createContext({}); + +/** + * Custom hook to use the Zendesk context. + * @throws If used outside of a ZendeskProvider. + * @returns The Zendesk context value. + */ +export const useZendeskContext = () => useContext(ZendeskContext); diff --git a/packages/react-thirdparty/zendesk/Provider.tsx b/packages/react-thirdparty/zendesk/Provider.tsx new file mode 100644 index 0000000..250c2eb --- /dev/null +++ b/packages/react-thirdparty/zendesk/Provider.tsx @@ -0,0 +1,53 @@ +import React, { useEffect, ReactNode, useState } from "react"; +import { ZendeskContext } from "./Context"; +import { + injectZendeskScript, + removeZendeskScript, + ZendeskScriptProps, +} from "@nanlabs/thirdparty"; + +/** + * ZendeskProvider component to wrap around your application and provide Zendesk context. + * @param props - The props for the provider. + * @returns The provider component. + */ +export const ZendeskProvider: React.FC< + ZendeskScriptProps & { children: ReactNode } +> = ({ zendeskKey, scriptId, appendTo = "body", handleOnLoad, children }) => { + const [zendeskClient, setZendeskClient] = useState< + ZendeskMessagingWidget | undefined + >(undefined); + + const onLoad = () => { + if (!window?.zE) { + console.warn("Zendesk script is not available"); + return; + } + setZendeskClient(window.zE); + if (handleOnLoad) { + handleOnLoad(); + } + }; + + useEffect(() => { + injectZendeskScript({ + zendeskKey, + scriptId, + appendTo, + handleOnLoad: onLoad, + }); + + return () => { + removeZendeskScript(scriptId); + setZendeskClient(undefined); + }; + }, [zendeskKey, scriptId, appendTo, handleOnLoad]); + + const zendeskContextValue = { executeZendesk: zendeskClient }; + + return ( + + {children} + + ); +}; diff --git a/packages/react-thirdparty/zendesk/README.md b/packages/react-thirdparty/zendesk/README.md new file mode 100644 index 0000000..c88f15d --- /dev/null +++ b/packages/react-thirdparty/zendesk/README.md @@ -0,0 +1,82 @@ +

React Zendesk Integration

+
+ +Integrating Zendesk into your React application + +
+ +## Usage + +### Provide API Key + +To use these integrations, you need to create an API key for your domain. You can get one from [here](https://www.zendesk.com/). + +### Components + +### Zendesk + +To use the Zendesk integration, you need to include the `ZendeskProvider` in your application and use the `Zendesk` component or the `useZendesk` hook. + +#### ZendeskProvider + +`ZendeskProvider` is a React component that wraps your application and loads the Zendesk script. It provides context to other components and hooks that need to interact with Zendesk. + +```jsx +import { ZendeskProvider } from "@nanlabs/react-thirdparty"; + +const App = () => ( + console.log("Zendesk script loaded!")} + > + + +); + +export default App; +``` + +#### Zendesk Component + +`Zendesk` is a React component that can be used in your app to load the Zendesk script. It utilizes the context provided by `ZendeskProvider`. + +```jsx +import React, { useEffect } from "react"; +import { Zendesk } from "@nanlabs/react-thirdparty"; + +const YourComponent = () => ( +
+ +
+); + +export default YourComponent; +``` + +### React Hook: useZendesk + +If you prefer a React Hook approach, you can choose to use the custom hook `useZendesk`. + +It's very simple to use the hook: + +```tsx +import React from "react"; +import { useZendesk } from "@nanlabs/react-thirdparty"; + +const YourZendeskComponent = () => { + const { executeZendesk } = useZendesk(); + + return ( +
+ +
+ ); +}; + +export default YourZendeskComponent; +``` + +By following these steps, you can integrate Zendesk into your project using either a React hook or a component, providing flexibility depending on your needs. diff --git a/packages/react-thirdparty/zendesk/Zendesk.tsx b/packages/react-thirdparty/zendesk/Zendesk.tsx new file mode 100644 index 0000000..2670828 --- /dev/null +++ b/packages/react-thirdparty/zendesk/Zendesk.tsx @@ -0,0 +1,29 @@ +import React, { FC, useEffect } from "react"; +import { useZendesk } from "./useZendesk"; + +/** + * Zendesk component to load the Zendesk script. + * @returns The Zendesk container element. + */ +const Zendesk: React.FC = () => { + const { executeZendesk } = useZendesk(); + + useEffect(() => { + if (!executeZendesk) { + console.warn("Zendesk client is not available"); + return; + } + + // Example usage of executeZendesk when the component mounts + executeZendesk("messenger", "open"); + + return () => { + // Example cleanup if needed + executeZendesk("messenger", "close"); + }; + }, [executeZendesk]); + + return
; +}; + +export default Zendesk; diff --git a/packages/react-thirdparty/zendesk/index.ts b/packages/react-thirdparty/zendesk/index.ts new file mode 100644 index 0000000..5869a05 --- /dev/null +++ b/packages/react-thirdparty/zendesk/index.ts @@ -0,0 +1,4 @@ +export { useZendeskContext } from "./Context"; +export { ZendeskProvider } from "./Provider"; +export { useZendesk } from "./useZendesk"; +export { default as Zendesk } from "./Zendesk"; diff --git a/packages/react-thirdparty/zendesk/useZendesk.ts b/packages/react-thirdparty/zendesk/useZendesk.ts new file mode 100644 index 0000000..f2b6cc1 --- /dev/null +++ b/packages/react-thirdparty/zendesk/useZendesk.ts @@ -0,0 +1,7 @@ +import { useZendeskContext } from "./Context"; + +/** + * Custom hook to access Zendesk client through context. + * @returns The Zendesk context value. + */ +export const useZendesk = () => useZendeskContext(); diff --git a/packages/react-thirdparty/zendesk/zendesk.d.ts b/packages/react-thirdparty/zendesk/zendesk.d.ts new file mode 100644 index 0000000..6bd2e98 --- /dev/null +++ b/packages/react-thirdparty/zendesk/zendesk.d.ts @@ -0,0 +1,172 @@ +/** + * Zendesk messaging Web Widget SDK + * https://developer.zendesk.com/api-reference/widget-messaging/introduction/ + */ +interface ZendeskMessagingWidget { + /** + * If your application has a login flow, or if a user needs to access the same conversation from multiple devices, + * you can use the `loginUser` API. + * + * You can associate users with your own user directory by issuing a `JWT` credential during the login flow. + * For information on creating signing keys, see + * [Authenticating end users in messaging](https://support.zendesk.com/hc/en-us/articles/4411666638746). + * For information on creating JWT tokens, see + * [Enabling authenticated visitors for messaging with Zendesk SDKs](https://developer.zendesk.com/documentation/zendesk-web-widget-sdks/sdks/web/enabling_auth_visitors). + * + * ## Expiring JWTs + * If you want to generate credentials that expire after a certain amount of time, using JWTs is a good way to + * accomplish this. + * + * The `exp` (expiration time) property of a JWT payload is honored by the messaging Web Widget. + * A request made with a JWT which has an `exp` that is in the past is rejected. + * + * Keep in mind that using JWTs with `exp` means you need to handle regeneration of JWTs in the function that you + * provide when calling the `loginUser` API. + */ + ( + type: "messenger", + command: "loginUser", + callback: (fn: (newJwtForUser: string) => void) => void + ): void; + + /** + * Your app may have a logout function that brings users back to a login screen. In this case, revert the messaging + * Web Widget to a pre-login state by calling the `logoutUser` API. + * + * After a user is logged out, all conversation tags and conversation fields data is cleared. + * Use the Conversation Fields and Tags API again to apply conversation fields and tags data to a new conversation. + */ + (type: "messenger", command: "logoutUser"): void; + + /** + * Displays the widget on the host page in the state it was in before it was hidden. + * The widget is displayed by default on page load. + * You don't need to call `show` to display the widget unless you use `hide`. + */ + (type: "messenger", command: "show"): void; + + /** + * Hides all parts of the widget from the page. You can invoke it before or after page load. + */ + (type: "messenger", command: "hide"): void; + + /** + * Opens the messaging Web Widget. + */ + (type: "messenger", command: "open"): void; + + /** + * Closes the messaging Web Widget. + */ + (type: "messenger", command: "close"): void; + + /** + * Executes a callback when the messaging Web Widget opens. + */ + (type: "messenger:on", event: "open", callback: () => void): void; + + /** + * Executes a callback when the messaging Web Widget closes. + */ + (type: "messenger:on", event: "close", callback: () => void): void; + + /** + * Executes a callback when the number of unread messages changes. + */ + ( + type: "messenger:on", + event: "unreadMessages", + callback: (unreadMessageCount: number) => void + ): void; + + /** + * Sets the locale of the messaging Web Widget. + * It overrides the messaging Web Widget's default behavior of matching the same language an + * end user has set in their web browser. + * + * The command takes a locale string as an argument. + * For a list of supported locales and associated codes, use the following Zendesk public REST API endpoint: + * https://support.zendesk.com/api/v2/locales/public.json. + * + * **Note:** This code should be placed immediately after the messaging Web Widget code snippet. + */ + (type: "messenger:set", setting: "locale", newLocale: string): void; + + /** + * Sets the CSS property z-index on all the iframes for the messaging Web Widget. + * + * When two elements overlap, the z-index values of the elements determine which one covers the other. + * An element with a greater z-index value covers an element with a smaller one. + * + * By default, all iframes in the messaging Web Widget have a z-index value of `999999`. + */ + (type: "messenger:set", setting: "zIndex", newZIndex: number): void; + + /** + * The messaging Web Widget uses a mixture of cookies as well as local and session storage in order to function. + * + * If the end user has opted out of cookies, you can use the command below to let the messaging + * Web Widget know that it is unable to use any of these storage options. + * + * Currently, disabling cookies will result in the messaging Web Widget being hidden from the end user + * and all values in local and session storage being deleted. + */ + (type: "messenger:set", setting: "cookies", isEnabled: boolean): void; + + /** + * Allows values for conversation fields to be set in the client to add contextual data about the conversation. + * To learn more about Messaging Metadata, see + * [Introduction to Messaging Metadata](https://support.zendesk.com/hc/en-us/articles/5868905484442). + * + * Conversation fields must first be created as custom ticket fields and configured to allow their values to be + * set by end users in Admin Center. To use conversation fields, see + * [Using Messaging Metadata with the Zendesk Web Widget and SDKs](https://support.zendesk.com/hc/en-us/articles/5658339908378). + * + * **Note**: Conversation fields aren't immediately associated with a conversation when the API is called. + * It'll only be applied when end users start a conversation or send a message in an existing conversation + * from the page it's called from. + * + * [System ticket fields](https://support.zendesk.com/hc/en-us/articles/4408886739098), such as the Priority field, + * are not supported. + * + * Conversation fields are cleared when the + * [authentication API](https://developer.zendesk.com/api-reference/widget-messaging/web/authentication/#logout) + * to sign-out is called. The `conversationFields` API needs to be called again to apply field metadata to a fresh + * conversation. + */ + ( + type: "messenger:set", + setting: "conversationFields", + conversationFields: { + id: string; + value: string | number | boolean; + }[] + ): void; + + /** + * Allows custom conversation tags to be set in the client to add contextual data about the conversation. + * To learn more about Messaging Metadata, see + * [Introduction to Messaging Metadata](https://support.zendesk.com/hc/en-us/articles/5868905484442). + * + * Conversation tags do not need any prerequisite steps before the API can be used. To use conversation tags, see + * [Using Messaging Metadata with the Zendesk Web Widget and SDKs](https://support.zendesk.com/hc/en-us/articles/5658339908378). + * + * **Note:** Conversation tags aren't immediately associated with a conversation when the API is called. + * It'll only be applied when end users start a conversation or send a message in an existing conversation + * from the page it's called from. + * + * Conversation tags are cleared when the + * [authentication API](https://developer.zendesk.com/api-reference/widget-messaging/web/authentication/#logout) + * to sign-out is called. The `conversationTags` API needs to be called again to apply tag metadata to a fresh + * conversation. + */ + ( + type: "messenger:set", + setting: "conversationTags", + conversationTags: string[] + ): void; +} + +interface Window { + zE?: ZendeskMessagingWidget; +} diff --git a/packages/thirdparty/index.ts b/packages/thirdparty/index.ts index a69c535..c3a1253 100644 --- a/packages/thirdparty/index.ts +++ b/packages/thirdparty/index.ts @@ -1 +1,2 @@ export * from "./recaptcha"; +export * from "./zendesk"; diff --git a/packages/thirdparty/recaptcha/scripts.ts b/packages/thirdparty/recaptcha/scripts.ts index 07b4814..bebe952 100644 --- a/packages/thirdparty/recaptcha/scripts.ts +++ b/packages/thirdparty/recaptcha/scripts.ts @@ -1,5 +1,9 @@ +export type GoogleReCaptchaSrc = + | "https://www.google.com/recaptcha/api.js" + | "https://www.recaptcha.net/recaptcha/api.js"; + export interface GoogleReCaptchaScriptProps { - src?: string; + src?: GoogleReCaptchaSrc; reCaptchaKey: string; scriptId: string; language?: string; @@ -60,6 +64,7 @@ export const injectGoogleReCaptchaScript = ({ } const js = generateGoogleReCaptchaScript({ + src, scriptId, handleOnLoad, appendTo, @@ -74,19 +79,15 @@ export const injectGoogleReCaptchaScript = ({ }; /** - * removeScript removes the Google ReCaptcha script tag from the DOM. + * removeGoogleReCaptchaScript removes the Google ReCaptcha script tag from the DOM. * @param {string} scriptId - The id of the script tag. */ -export const removeScript = (scriptId: string) => { +export const removeGoogleReCaptchaScript = (scriptId: string) => { // remove badge const nodeBadge = document.querySelector(".grecaptcha-badge"); - if (nodeBadge && nodeBadge.parentNode) { - document.body.removeChild(nodeBadge.parentNode); - } + nodeBadge?.parentElement?.remove(); // remove script const script = document.querySelector(`#${scriptId}`); - if (script) { - script.remove(); - } + script?.remove(); }; diff --git a/packages/thirdparty/zendesk/index.ts b/packages/thirdparty/zendesk/index.ts new file mode 100644 index 0000000..2b01a26 --- /dev/null +++ b/packages/thirdparty/zendesk/index.ts @@ -0,0 +1 @@ +export * from "./scripts"; diff --git a/packages/thirdparty/zendesk/scripts.ts b/packages/thirdparty/zendesk/scripts.ts new file mode 100644 index 0000000..1efc50e --- /dev/null +++ b/packages/thirdparty/zendesk/scripts.ts @@ -0,0 +1,69 @@ +export type ZendeskSrc = "https://static.zdassets.com/ekr/snippet.js"; + +export interface ZendeskScriptProps { + src?: ZendeskSrc; + zendeskKey: string; + scriptId: string; + appendTo?: "head" | "body"; + handleOnLoad?: null | (() => void); +} + +/** + * generateZendeskScript generates a script tag for the Zendesk API. + * @param {ZendeskScriptProps} props - The props for the script tag. + */ +export const generateZendeskScript = ({ + src = "https://static.zdassets.com/ekr/snippet.js", + zendeskKey, + scriptId, + handleOnLoad = null, +}: ZendeskScriptProps) => { + const js = document.createElement("script"); + js.id = scriptId; + js.src = `${src}?key=${zendeskKey}`; + js.async = true; + js.onload = handleOnLoad; + + return js; +}; + +/** + * injectZendeskScript injects a script tag for the Zendesk API. + * @param {ZendeskScriptProps} props - The props for the script tag. + */ +export const injectZendeskScript = ({ + src = "https://static.zdassets.com/ekr/snippet.js", + scriptId, + handleOnLoad = null, + appendTo = "body", + ...rest +}: ZendeskScriptProps) => { + // Check if script is already injected + const existingScript = document.getElementById(scriptId); + if (existingScript) { + handleOnLoad?.(); + return; + } + + const js = generateZendeskScript({ + src, + scriptId, + handleOnLoad, + ...rest, + }); + const elementToInjectScript = + appendTo === "body" + ? document.body + : document.getElementsByTagName("head")[0]; + + elementToInjectScript.appendChild(js); +}; + +/** + * removeZendeskScript removes the Zendesk script tag from the DOM. + * @param {string} scriptId - The id of the script tag. + */ +export const removeZendeskScript = (scriptId: string) => { + const script = document.querySelector(`#${scriptId}`); + script?.remove(); +}; diff --git a/packages/tsconfig/react-library.json b/packages/tsconfig/react-library.json index a40a422..2da031e 100644 --- a/packages/tsconfig/react-library.json +++ b/packages/tsconfig/react-library.json @@ -3,7 +3,7 @@ "display": "React Library", "extends": "./base.json", "compilerOptions": { - "jsx": "react-jsx", + "jsx": "react", "lib": ["dom", "dom.iterable", "ES2015"], "module": "ESNext", "target": "es6"