Skip to content

Commit

Permalink
Provide Collect form instance (#64)
Browse files Browse the repository at this point in the history
* provide form instance for developer

* remove manager hash

* fix examples

* up version

* revert env file

* remove console.log
  • Loading branch information
flor-master authored Nov 28, 2024
1 parent 2e6aaf1 commit eba319a
Show file tree
Hide file tree
Showing 8 changed files with 167 additions and 14 deletions.
3 changes: 2 additions & 1 deletion .prettierrc
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,6 @@
"bracketSpacing": true,
"jsxBracketSameLine": false,
"arrowParens": "always",
"trailingComma": "none"
"trailingComma": "none",
"printWidth": 120
}
3 changes: 3 additions & 0 deletions example/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { Routes, Route, Outlet, Link } from 'react-router-dom';
import Base from './features/Basic';
import CustomPayload from './features/CustomPayload';
import Tokenization from './features/Tokenization';
import SubmitHandling from './features/SubmitHandling';

export default function App() {
return (
Expand All @@ -12,6 +13,7 @@ export default function App() {
<Route index element={<Base />} />
<Route path='/custom-payload' element={<CustomPayload />} />
<Route path='/tokenization-api' element={<Tokenization />} />
<Route path='/submit-handling' element={<SubmitHandling />} />
<Route path='*' element={<NoMatch />} />
</Route>
</Routes>
Expand All @@ -26,6 +28,7 @@ function Layout() {
<Link to='/'>Basic</Link>
<Link to='/custom-payload'>Custom Payload</Link>
<Link to='/tokenization-api'>Tokenization API</Link>
<Link to='/submit-handling'>Submit Handling</Link>
</nav>
<hr />
<main className='container'>
Expand Down
124 changes: 124 additions & 0 deletions example/src/features/SubmitHandling.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
import {
VGSCollectForm,
VGSCollectFormState,
VGSCollectHttpStatusCode,
VGSCollectVaultEnvironment,
useVGSCollectResponse,
useVGSCollectState,
useVGSCollectFormInstance,
IVGSCollectForm
} from 'collect-js-react';
import React, { useEffect, useState } from 'react';

import { loadVGSCollect } from '@vgs/collect-js';

const { TextField } = VGSCollectForm;

const { REACT_APP_VAULT_ID, REACT_APP_ENVIRONMENT, REACT_APP_COLLECT_VERSION } = process.env;

const SubmitHandling = (e: any) => {
const [isVGSCollectScriptLoaded, setCollectScriptLoaded] = useState(false);
const [formInstance, setFormInstance] = useState<IVGSCollectForm | null>(null);
const [isFormSubmitting, setFormSubmitting] = useState(false);
const VGSCollectFieldStyles = {
'&::placeholder': {
color: '#686868'
},
padding: '.5rem 1rem',
boxSizing: 'border-box'
};

const [state] = useVGSCollectState();
const [response] = useVGSCollectResponse();
const [form] = useVGSCollectFormInstance();

useEffect(() => {
/**
* Track form state
*/
}, [state]);

useEffect(() => {
/**
* Track response from the VGS Collect form
*/
}, [response]);

useEffect(() => {
setFormInstance(form);
}, [form]);

useEffect(() => {
/**
* Loading VGS Collect script from and attaching it to the <head>
*/
loadVGSCollect({
vaultId: REACT_APP_VAULT_ID as string,
environment: REACT_APP_ENVIRONMENT as VGSCollectVaultEnvironment,
version: REACT_APP_COLLECT_VERSION as string
}).then(() => {
setCollectScriptLoaded(true);
});
}, []);

const onErrorCallback = (errors: VGSCollectFormState) => {
/**
* Receive information about Erorrs (client-side validation, or rejection in async headers function)
*/
};

const onUpdateCallback = (state: VGSCollectFormState) => {
/**
* Listen to the VGS Collect form state
*/
};

const customHandling = (event: any) => {
event.preventDefault();
if (!isFormSubmitting && formInstance) {
setFormSubmitting(true);
formInstance.submit(
'/post',
{},
(status, data) => {
console.log('Response:', status, data);
setFormSubmitting(false);
},
(errors) => {
console.log(errors);
setFormSubmitting(false);
}
);
}
};

return (
<>
{isVGSCollectScriptLoaded && (
<div className='left'>
<h2>Custom Submit Handling</h2>
{/**
* VGS Collect form wrapper element. Abstraction over the VGSCollect.create()
* https://www.verygoodsecurity.com/docs/api/collect/#api-vgscollectcreate
*/}
<VGSCollectForm
vaultId={REACT_APP_VAULT_ID as string}
environment={REACT_APP_ENVIRONMENT as VGSCollectVaultEnvironment}
onCustomSubmit={customHandling}
onUpdateCallback={onUpdateCallback}
onErrorCallback={onErrorCallback}
>
{/**
* VGS Collect text field component:
* https://www.verygoodsecurity.com/docs/api/collect/#api-formfield
*/}
<TextField name='textField' validations={['required']} css={VGSCollectFieldStyles} />
<button type='submit'>Submit</button>
</VGSCollectForm>
</div>
)}
</>
);
};

export default SubmitHandling;
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@vgs/collect-js-react",
"version": "1.4.1",
"version": "1.5.1",
"description": "VGS Collect.js React wrapper",
"author": "vgs",
"license": "MIT",
Expand Down
4 changes: 2 additions & 2 deletions src/Fields.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,6 @@ function RenderField(props: any) {
onDelete
};
const [ formState ] = useVGSCollectFormState();

const eventsToListen = Object.keys(events).filter(e => events[e] !== undefined);

useEffect(() => {
Expand Down Expand Up @@ -229,5 +228,6 @@ export {
TextareaField,
NumberField,
FileField,
DateField
DateField,
getFormInstance
};
17 changes: 8 additions & 9 deletions src/Form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
TextareaField,
ZipCodeField
} from './Fields';
import { DispatchStateContext, DispatchSubmitContext } from './provider';
import { DispatchStateContext, DispatchSubmitContext, DispatchFormInstanceContext } from './provider';
import { FormStateProvider, DispatchFormContext } from './formStateProvider';
import {
ICollectFormProps,
Expand All @@ -35,10 +35,11 @@ function CollectForm(props: ICollectFormProps) {
routeId,
submitParameters,
tokenizationAPI = false,
onCustomSubmit,
onUpdateCallback,
onSubmitCallback,
onErrorCallback,
children
children,
} = props;

if (!vaultId) {
Expand All @@ -48,6 +49,7 @@ function CollectForm(props: ICollectFormProps) {
const dispatchFormStateUpdate = useContext(DispatchStateContext);
const dispatchResponseUpdate = useContext(DispatchSubmitContext);
const dispatchFormСontext = useContext(DispatchFormContext);
const dispatchFormInstanceContextUpdate = useContext(DispatchFormInstanceContext);

const isProviderExists =
typeof dispatchFormStateUpdate === 'function' &&
Expand Down Expand Up @@ -81,6 +83,7 @@ function CollectForm(props: ICollectFormProps) {
}

setFormInstance(form);
dispatchFormInstanceContextUpdate(getFormInstance());
}

return () => {
Expand All @@ -98,14 +101,12 @@ function CollectForm(props: ICollectFormProps) {
}, []);

const submitHandler = (e: React.SyntheticEvent) => {
e.preventDefault();

e.preventDefault();
const form: IVGSCollectForm = getFormInstance();

if (!form) {
throw new Error('@vgs/collect-js-react: VGS Collect form not found.');
}

if (tokenizationAPI) {
form.tokenize(
(status: HttpStatusCode | null, resp: any) => {
Expand Down Expand Up @@ -142,10 +143,8 @@ function CollectForm(props: ICollectFormProps) {
};

return (
<form
onSubmit={(event) => {
submitHandler(event);
}}
<form
onSubmit={(event) => (onCustomSubmit || submitHandler)(event)}
>
{children}
</form>
Expand Down
27 changes: 26 additions & 1 deletion src/provider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,17 @@ import { VGSCollectFormState } from './types/Form';

type GlobalStateContext = VGSCollectFormState | undefined;
type GlobalSubmitContext = { status: HttpStatusCode, data: any } | undefined;
type GlobalFormInstanceContext = any;

export const initialState = undefined;

export const GlobalSubmitContext = createContext<GlobalSubmitContext>(initialState);
export const DispatchSubmitContext = createContext({} as Dispatch<any>);
export const GlobalStateContext = createContext<GlobalStateContext>(initialState);
export const DispatchStateContext = createContext({} as Dispatch<any>);
export const GlobalFormInstanceContext = createContext<GlobalFormInstanceContext>(false);
export const DispatchFormInstanceContext = createContext({} as Dispatch<any>);


export const GlobalStateProvider = ({ children }: any) => {
const [state, dispatch] = useReducer(
Expand All @@ -26,6 +30,13 @@ export const GlobalStateProvider = ({ children }: any) => {
initialState
);

const [formInstance, dispatchFormInstance] = useReducer(
(_form: any, formInstance: any) => {
return formInstance ? formInstance : null
},
null
);

const memoState = useMemo(
() => (state),
[state]
Expand All @@ -36,13 +47,22 @@ export const GlobalStateProvider = ({ children }: any) => {
[response]
);

const memoFormInstance = useMemo(
() => (formInstance),
[formInstance]
);

return (
<div>
<GlobalStateContext.Provider value={memoState}>
<DispatchStateContext.Provider value={dispatch}>
<GlobalSubmitContext.Provider value={memoResponse}>
<DispatchSubmitContext.Provider value={dispatchSubmit}>
{children}
<GlobalFormInstanceContext.Provider value={memoFormInstance}>
<DispatchFormInstanceContext.Provider value={dispatchFormInstance}>
{children}
</DispatchFormInstanceContext.Provider>
</GlobalFormInstanceContext.Provider>
</DispatchSubmitContext.Provider>
</GlobalSubmitContext.Provider>
</DispatchStateContext.Provider>
Expand All @@ -59,6 +79,11 @@ export const useVGSCollectResponse = () => [
useContext(GlobalSubmitContext)
];

export const useVGSCollectFormInstance = () => [
useContext(GlobalFormInstanceContext)
];

export const VGSCollectProvider = ({ children }: any) => {
return <GlobalStateProvider>{children}</GlobalStateProvider>;
};

1 change: 1 addition & 0 deletions src/types/Form.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ interface ICollectFormProps {
routeId?: string;
tokenizationAPI?: boolean;
children?: JSX.Element[] | JSX.Element;
onCustomSubmit?: (event: React.SyntheticEvent) => void;
onUpdateCallback?: (state: VGSCollectFormState | null) => void;
onSubmitCallback?: (status: any, resp: any) => void;
onErrorCallback?: (errors: any) => void;
Expand Down

0 comments on commit eba319a

Please sign in to comment.