From cdc69225c061d94938c276c4521263448d1d4523 Mon Sep 17 00:00:00 2001 From: Fishbakh-N <54896745+Fishbakh-N@users.noreply.github.com> Date: Fri, 12 Jul 2024 20:37:28 +0300 Subject: [PATCH] Form only mode (#407) * feat: Add getScriptParameters helper function * feat: Add shouldEnableFormOnlyMode helper function * feat: Add form-only donation flow --- .../donate-button-v4/src/formOnlyMode.tsx | 130 ++++++++++++++++++ .../src/helpers/getScriptParameters.ts | 15 ++ .../src/helpers/shouldEnableAutoPlay.ts | 18 +-- .../src/helpers/shouldEnableFormOnlyMode.ts | 13 ++ packages/donate-button-v4/src/index.tsx | 7 +- .../src/loaders/FormLoader.tsx | 41 ++++++ 6 files changed, 210 insertions(+), 14 deletions(-) create mode 100644 packages/donate-button-v4/src/formOnlyMode.tsx create mode 100644 packages/donate-button-v4/src/helpers/getScriptParameters.ts create mode 100644 packages/donate-button-v4/src/helpers/shouldEnableFormOnlyMode.ts create mode 100644 packages/donate-button-v4/src/loaders/FormLoader.tsx diff --git a/packages/donate-button-v4/src/formOnlyMode.tsx b/packages/donate-button-v4/src/formOnlyMode.tsx new file mode 100644 index 00000000..689ad82b --- /dev/null +++ b/packages/donate-button-v4/src/formOnlyMode.tsx @@ -0,0 +1,130 @@ +import {render} from 'preact'; +import EmbedButton from 'src/components/embed-button'; +import {CreateButtonInSelectorProps} from 'src/components/embed-button/types'; +import {CreateWidgetInSelectorProps} from 'src/components/widget/types'; +import {WidgetConfig} from 'src/components/widget/types/WidgetConfig'; +import {loadFonts} from 'src/loadFonts'; +import {FormLoader} from 'src/loaders/FormLoader'; +import {WidgetLoader} from 'src/loaders/Widgetloader'; +import resetcss from 'src/resetCss'; + +interface GlobalExport { + createButton: (options: CreateButtonInSelectorProps) => void; + createWidget: (options: CreateWidgetInSelectorProps) => void; + setOptions: (options: Partial) => void; + showWidget: () => void; +} + +declare const window: Window & { + everyDotOrgDonateButton?: GlobalExport; +}; + +export default function formOnlyMode() { + const DEFAULT_HASH_OPEN_WIDGET = 'donate'; + + const baseOptions: Partial = {}; + const options = { + show: false, + openAt: DEFAULT_HASH_OPEN_WIDGET + }; + const instanceOptions: Partial = {}; + + const getNode = (element?: Element, selector?: string) => + element ? element : selector ? document.querySelector(selector) : null; + + loadFonts(); + + /** + * Helper function to debug donate button issues + */ + function log(...messages: unknown[]): void { + console.info('Every.org Donate Button:', ...messages); + } + + let formContainer: HTMLElement; + let formMountPoint: HTMLElement; + + const mountWidget = () => { + const shadowWidgetWrapper = document.createElement('div'); + shadowWidgetWrapper.id = 'shadow-wrapper'; + formContainer.append(shadowWidgetWrapper); + + formMountPoint = document.createElement('div'); + shadowWidgetWrapper.attachShadow({mode: 'open'}).append(formMountPoint); + + const everyStyles: HTMLStyleElement | null = + document.querySelector('#every-styles'); + + if (everyStyles) { + const rules = Object.values(everyStyles.sheet?.cssRules ?? {}) + .map((rule) => rule.cssText) + .join('\n'); + + const everyShadowStyles = document.createElement('style'); + everyShadowStyles.id = 'every-shadow-styles'; + everyShadowStyles.innerHTML = resetcss + rules; + + formMountPoint.append(everyShadowStyles); + } + }; + + const renderWidget = () => { + if (!formMountPoint) { + mountWidget(); + } + + const finalOptions: Partial = { + ...options, + ...baseOptions, + ...instanceOptions + }; + + render(, formMountPoint); + }; + + function setOptions(newOptions: Partial) { + Object.assign(baseOptions, newOptions); + renderWidget(); + } + + const createWidgetInSelector = ({ + element, + selector, + ...options + }: CreateWidgetInSelectorProps) => { + if (!element && !selector) { + log('createWidget():', 'must provide element or selector'); + } + + const node = getNode(element, selector); + if (!node) { + log('createWidget():', 'element or selector not found'); + return; + } + + if (!options.nonprofitSlug) { + log('createWidget():', 'must provide nonprofitSlug'); + return; + } + + formContainer = node as HTMLElement; + + Object.assign(baseOptions, options); + renderWidget(); + }; + + window.everyDotOrgDonateButton = { + createButton: () => { + log( + 'createButton function is not available in formOnlyMode. Use createWidget instead.' + ); + }, + showWidget: () => { + log( + 'showWidget function is not available in formOnlyMode. Use createWidget instead.' + ); + }, + setOptions, + createWidget: createWidgetInSelector + }; +} diff --git a/packages/donate-button-v4/src/helpers/getScriptParameters.ts b/packages/donate-button-v4/src/helpers/getScriptParameters.ts new file mode 100644 index 00000000..b3cdbbcf --- /dev/null +++ b/packages/donate-button-v4/src/helpers/getScriptParameters.ts @@ -0,0 +1,15 @@ +export default function getScriptParameters() { + const parametersString = document.currentScript + ?.getAttribute('src') + ?.split('?')[1]; + const parametersArray = parametersString + ?.split('&') + .map((parameterString) => { + const [key, value] = parameterString.split('='); + return [key, value]; + }); + const parametersObject = + parametersArray && Object.fromEntries(parametersArray); + + return parametersObject; +} diff --git a/packages/donate-button-v4/src/helpers/shouldEnableAutoPlay.ts b/packages/donate-button-v4/src/helpers/shouldEnableAutoPlay.ts index 66c0de6e..1d7d31b9 100644 --- a/packages/donate-button-v4/src/helpers/shouldEnableAutoPlay.ts +++ b/packages/donate-button-v4/src/helpers/shouldEnableAutoPlay.ts @@ -1,20 +1,12 @@ +import getScriptParameters from 'src/helpers/getScriptParameters'; + const AUTO_PLAY_PARAM = 'explicit'; export function shouldEnableAutoPlay() { - const parametersString = document.currentScript - ?.getAttribute('src') - ?.split('?')[1]; - const parametersArray = parametersString - ?.split('&') - .map((parameterString) => { - const [key, value] = parameterString.split('='); - return [key, value]; - }); - const parametersObject = - parametersArray && Object.fromEntries(parametersArray); + const parameters = getScriptParameters(); - if (parametersObject) { - return parametersObject[AUTO_PLAY_PARAM] !== '1'; + if (parameters) { + return parameters[AUTO_PLAY_PARAM] !== '1'; } return true; diff --git a/packages/donate-button-v4/src/helpers/shouldEnableFormOnlyMode.ts b/packages/donate-button-v4/src/helpers/shouldEnableFormOnlyMode.ts new file mode 100644 index 00000000..27a07aec --- /dev/null +++ b/packages/donate-button-v4/src/helpers/shouldEnableFormOnlyMode.ts @@ -0,0 +1,13 @@ +import getScriptParameters from 'src/helpers/getScriptParameters'; + +const FORM_ONLY_PARAM = 'formOnly'; + +export function shouldEnableFormOnlyMode() { + const parameters = getScriptParameters(); + + if (parameters) { + return parameters[FORM_ONLY_PARAM] === '1'; + } + + return false; +} diff --git a/packages/donate-button-v4/src/index.tsx b/packages/donate-button-v4/src/index.tsx index 5a325a8d..93b45023 100644 --- a/packages/donate-button-v4/src/index.tsx +++ b/packages/donate-button-v4/src/index.tsx @@ -1,10 +1,15 @@ import autoPlayMode from 'src/autoPlayMode'; +import formOnlyMode from 'src/formOnlyMode'; import {shouldEnableAutoPlay} from 'src/helpers/shouldEnableAutoPlay'; +import {shouldEnableFormOnlyMode} from 'src/helpers/shouldEnableFormOnlyMode'; import manualMode from 'src/manualMode'; const autoPlay = shouldEnableAutoPlay(); +const formOnly = shouldEnableFormOnlyMode(); -if (autoPlay) { +if (formOnly) { + formOnlyMode(); +} else if (autoPlay) { autoPlayMode(); } else { manualMode(); diff --git a/packages/donate-button-v4/src/loaders/FormLoader.tsx b/packages/donate-button-v4/src/loaders/FormLoader.tsx new file mode 100644 index 00000000..d8bf9b97 --- /dev/null +++ b/packages/donate-button-v4/src/loaders/FormLoader.tsx @@ -0,0 +1,41 @@ +import {PaymentProcess} from 'src/components/widget/components/PaymentProcess'; +import {ContextProvider} from 'src/components/widget/context'; +import {useConfigContext} from 'src/components/widget/hooks/useConfigContext'; +import {useFundraiser} from 'src/components/widget/hooks/useFundraiser'; +import {useNonprofit} from 'src/components/widget/hooks/useNonprofit'; +import {LoadingIcon} from 'src/components/widget/icons/LoadingIcon'; +import {FundraiserFetching} from 'src/components/widget/types/Fundraiser'; +import {NonprofitFetching} from 'src/components/widget/types/Nonprofit'; +import {WidgetConfig} from 'src/components/widget/types/WidgetConfig'; + +interface FormLoaderProps { + options: Partial; +} + +export const FormLoader = ({options = {}}: FormLoaderProps) => { + return ( + { + // do nothing + }} + > +
+ + ); +}; + +const Form = () => { + const {fundraiserSlug} = useConfigContext(); + const findraiser = useFundraiser(); + const nonprofit = useNonprofit(); + + if ( + nonprofit === NonprofitFetching || + (fundraiserSlug && findraiser === FundraiserFetching) + ) { + return ; + } + + return ; +};