Skip to content

Commit

Permalink
Form only mode (#407)
Browse files Browse the repository at this point in the history
* feat: Add getScriptParameters helper function
* feat: Add shouldEnableFormOnlyMode helper function
* feat: Add form-only donation flow
  • Loading branch information
Fishbakh-N authored Jul 12, 2024
1 parent 9dabc4a commit cdc6922
Show file tree
Hide file tree
Showing 6 changed files with 210 additions and 14 deletions.
130 changes: 130 additions & 0 deletions packages/donate-button-v4/src/formOnlyMode.tsx
Original file line number Diff line number Diff line change
@@ -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<WidgetConfig>) => void;
showWidget: () => void;
}

declare const window: Window & {
everyDotOrgDonateButton?: GlobalExport;
};

export default function formOnlyMode() {
const DEFAULT_HASH_OPEN_WIDGET = 'donate';

const baseOptions: Partial<WidgetConfig> = {};
const options = {
show: false,
openAt: DEFAULT_HASH_OPEN_WIDGET
};
const instanceOptions: Partial<WidgetConfig> = {};

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<WidgetConfig> = {
...options,
...baseOptions,
...instanceOptions
};

render(<FormLoader options={finalOptions} />, formMountPoint);
};

function setOptions(newOptions: Partial<WidgetConfig>) {
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
};
}
15 changes: 15 additions & 0 deletions packages/donate-button-v4/src/helpers/getScriptParameters.ts
Original file line number Diff line number Diff line change
@@ -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;
}
18 changes: 5 additions & 13 deletions packages/donate-button-v4/src/helpers/shouldEnableAutoPlay.ts
Original file line number Diff line number Diff line change
@@ -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;
Expand Down
13 changes: 13 additions & 0 deletions packages/donate-button-v4/src/helpers/shouldEnableFormOnlyMode.ts
Original file line number Diff line number Diff line change
@@ -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;
}
7 changes: 6 additions & 1 deletion packages/donate-button-v4/src/index.tsx
Original file line number Diff line number Diff line change
@@ -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();
Expand Down
41 changes: 41 additions & 0 deletions packages/donate-button-v4/src/loaders/FormLoader.tsx
Original file line number Diff line number Diff line change
@@ -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<WidgetConfig>;
}

export const FormLoader = ({options = {}}: FormLoaderProps) => {
return (
<ContextProvider
options={options}
hide={() => {
// do nothing
}}
>
<Form />
</ContextProvider>
);
};

const Form = () => {
const {fundraiserSlug} = useConfigContext();
const findraiser = useFundraiser();
const nonprofit = useNonprofit();

if (
nonprofit === NonprofitFetching ||
(fundraiserSlug && findraiser === FundraiserFetching)
) {
return <LoadingIcon size={24} />;
}

return <PaymentProcess />;
};

0 comments on commit cdc6922

Please sign in to comment.