@payabli/components-react 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,15 @@
1
+ import { type ComponentProps, type ComponentRef } from './createComponent.js';
2
+ export type PayInProps = ComponentProps<'payin'>;
3
+ export type PayInRef = ComponentRef;
4
+ export declare const PayIn: import("react").ForwardRefExoticComponent<import("./createComponent.js").ComponentEventProps<"payin"> & {
5
+ appearance?: import("@payabli/component-contracts").AppearanceOptions;
6
+ options?: {
7
+ showSubmitButton?: boolean | undefined;
8
+ } | undefined;
9
+ prefill?: Record<string, string>;
10
+ locale?: string;
11
+ timeout?: number;
12
+ className?: string;
13
+ style?: import("react").CSSProperties;
14
+ id?: string;
15
+ } & import("react").RefAttributes<ComponentRef>>;
package/dist/PayIn.js ADDED
@@ -0,0 +1,3 @@
1
+ 'use client';
2
+ import { createComponent, } from './createComponent.js';
3
+ export const PayIn = createComponent('payin');
@@ -0,0 +1,22 @@
1
+ import type { LoadOptions, Session, SessionEventPayload } from '@payabli/components-web';
2
+ import type { ReactNode } from 'react';
3
+ export interface PayabliProviderProps {
4
+ /**
5
+ * Session from `POST /api/v2/{slug}/Session/init`, created on your server.
6
+ * When the field values change, the provider calls `updateSession()` and all
7
+ * mounted components re-bootstrap.
8
+ */
9
+ session: Session;
10
+ /** Forwarded to `loadPayabli()` on first mount. Later changes have no effect. */
11
+ loadOptions?: LoadOptions;
12
+ /**
13
+ * Fires when the session reaches its server-side ceiling, or when auto-refresh
14
+ * fails. Create a new session on your server and pass it through the `session` prop.
15
+ */
16
+ onSessionExpired?: (payload: SessionEventPayload) => void;
17
+ onSessionError?: (payload: SessionEventPayload) => void;
18
+ /** Fires when the hosted script fails to load. Also available as `usePayabli().error`. */
19
+ onLoadError?: (error: Error) => void;
20
+ children?: ReactNode;
21
+ }
22
+ export declare function PayabliProvider({ session, loadOptions, onSessionExpired, onSessionError, onLoadError, children, }: PayabliProviderProps): import("react").JSX.Element;
@@ -0,0 +1,67 @@
1
+ 'use client';
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ import { loadPayabli } from '@payabli/components-web';
4
+ import { useEffect, useMemo, useRef, useState } from 'react';
5
+ import { PayabliContext } from './context.js';
6
+ function isSameSession(a, b) {
7
+ return (a !== null &&
8
+ a.sessionToken === b.sessionToken &&
9
+ a.renderToken === b.renderToken &&
10
+ a.entryName === b.entryName &&
11
+ a.environment === b.environment &&
12
+ a.expiresAt === b.expiresAt);
13
+ }
14
+ export function PayabliProvider({ session, loadOptions, onSessionExpired, onSessionError, onLoadError, children, }) {
15
+ const [payabli, setPayabli] = useState(null);
16
+ const [error, setError] = useState(null);
17
+ const callbacksRef = useRef({ onSessionExpired, onSessionError, onLoadError });
18
+ useEffect(() => {
19
+ callbacksRef.current = { onSessionExpired, onSessionError, onLoadError };
20
+ });
21
+ const sessionRef = useRef(session);
22
+ const appliedSessionRef = useRef(null);
23
+ const loadOptionsRef = useRef(loadOptions);
24
+ useEffect(() => {
25
+ let cancelled = false;
26
+ let instance = null;
27
+ loadPayabli(loadOptionsRef.current).then((Payabli) => {
28
+ if (cancelled)
29
+ return;
30
+ instance = Payabli({ session: sessionRef.current });
31
+ appliedSessionRef.current = sessionRef.current;
32
+ instance.on('sessionExpired', (payload) => {
33
+ var _a, _b;
34
+ (_b = (_a = callbacksRef.current).onSessionExpired) === null || _b === void 0 ? void 0 : _b.call(_a, payload);
35
+ });
36
+ instance.on('sessionError', (payload) => {
37
+ var _a, _b;
38
+ (_b = (_a = callbacksRef.current).onSessionError) === null || _b === void 0 ? void 0 : _b.call(_a, payload);
39
+ });
40
+ setPayabli(instance);
41
+ }, (cause) => {
42
+ var _a, _b;
43
+ if (cancelled)
44
+ return;
45
+ const loadError = cause instanceof Error ? cause : new Error(String(cause));
46
+ setError(loadError);
47
+ (_b = (_a = callbacksRef.current).onLoadError) === null || _b === void 0 ? void 0 : _b.call(_a, loadError);
48
+ });
49
+ return () => {
50
+ cancelled = true;
51
+ instance === null || instance === void 0 ? void 0 : instance.destroy();
52
+ instance = null;
53
+ appliedSessionRef.current = null;
54
+ setPayabli(null);
55
+ };
56
+ }, []);
57
+ useEffect(() => {
58
+ sessionRef.current = session;
59
+ if (!payabli || isSameSession(appliedSessionRef.current, session)) {
60
+ return;
61
+ }
62
+ appliedSessionRef.current = session;
63
+ payabli.updateSession(session);
64
+ }, [payabli, session]);
65
+ const value = useMemo(() => ({ payabli, error }), [payabli, error]);
66
+ return _jsx(PayabliContext.Provider, { value: value, children: children });
67
+ }
@@ -0,0 +1,9 @@
1
+ import type { PayabliInstance } from '@payabli/components-web';
2
+ export interface PayabliContextValue {
3
+ /** The live instance, or null while the hosted script loads. */
4
+ payabli: PayabliInstance | null;
5
+ /** Set when the hosted script fails to load. */
6
+ error: Error | null;
7
+ }
8
+ export declare const PayabliContext: import("react").Context<PayabliContextValue | null>;
9
+ export declare function usePayabli(): PayabliContextValue;
@@ -0,0 +1,10 @@
1
+ 'use client';
2
+ import { createContext, useContext } from 'react';
3
+ export const PayabliContext = createContext(null);
4
+ export function usePayabli() {
5
+ const value = useContext(PayabliContext);
6
+ if (!value) {
7
+ throw new Error('usePayabli must be called inside <PayabliProvider>.');
8
+ }
9
+ return value;
10
+ }
@@ -0,0 +1,27 @@
1
+ import type { ComponentEventKey, ComponentEventMap, ComponentType } from '@payabli/component-contracts';
2
+ import type { AppearanceOptions, ComponentOptions } from '@payabli/components-web';
3
+ import type { CSSProperties } from 'react';
4
+ type EventName<T extends ComponentType> = Extract<ComponentEventKey<T>, string>;
5
+ type EventPropName<K extends string> = `on${Capitalize<K>}`;
6
+ export type ComponentEventProps<T extends ComponentType> = {
7
+ [K in EventName<T> as EventPropName<K>]?: (payload: ComponentEventMap<T>[K]) => void;
8
+ };
9
+ export type ComponentProps<T extends ComponentType> = ComponentEventProps<T> & {
10
+ appearance?: AppearanceOptions;
11
+ options?: ComponentOptions<T>;
12
+ prefill?: Record<string, string>;
13
+ locale?: string;
14
+ timeout?: number;
15
+ /** Applied to the container element the component mounts into. */
16
+ className?: string;
17
+ style?: CSSProperties;
18
+ id?: string;
19
+ };
20
+ export interface ComponentRef {
21
+ /** Submits the form. Resolves with the submit result, rejects with the component error. */
22
+ submit: () => Promise<Record<string, unknown>>;
23
+ focus: (field?: string) => void;
24
+ blur: () => void;
25
+ }
26
+ export declare function createComponent<T extends ComponentType>(type: T): import("react").ForwardRefExoticComponent<import("react").PropsWithoutRef<ComponentProps<T>> & import("react").RefAttributes<ComponentRef>>;
27
+ export {};
@@ -0,0 +1,107 @@
1
+ 'use client';
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ import { BASE_COMPONENT_EVENT_NAMES, getComponentDescriptor, } from '@payabli/component-contracts';
4
+ import { forwardRef, useEffect, useImperativeHandle, useRef } from 'react';
5
+ import { usePayabli } from './context.js';
6
+ export function createComponent(type) {
7
+ const descriptor = getComponentDescriptor(type);
8
+ if (!descriptor) {
9
+ throw new Error(`Component type "${type}" is not registered.`);
10
+ }
11
+ const eventNames = [
12
+ ...new Set([
13
+ ...BASE_COMPONENT_EVENT_NAMES,
14
+ ...Object.values(descriptor.eventSchemas).map((schema) => schema.event),
15
+ ]),
16
+ ];
17
+ const RegisteredComponent = forwardRef(function RegisteredComponent(rawProps, ref) {
18
+ // React removes a possible `ref` prop from generic props. Component event
19
+ // keys never include `ref`, so restore the registry-derived type here.
20
+ const props = rawProps;
21
+ const { payabli } = usePayabli();
22
+ const containerRef = useRef(null);
23
+ const componentRef = useRef(null);
24
+ const appliedOptionsRef = useRef(null);
25
+ const propsRef = useRef(props);
26
+ useEffect(() => {
27
+ propsRef.current = props;
28
+ });
29
+ useEffect(() => {
30
+ if (!payabli || !containerRef.current) {
31
+ return;
32
+ }
33
+ const createOptions = toCreateOptions(propsRef.current);
34
+ const component = payabli.create(type, createOptions);
35
+ for (const event of eventNames) {
36
+ forwardEvent(component, event, propsRef);
37
+ }
38
+ component.mount(containerRef.current);
39
+ componentRef.current = component;
40
+ appliedOptionsRef.current = JSON.stringify(createOptions);
41
+ return () => {
42
+ componentRef.current = null;
43
+ appliedOptionsRef.current = null;
44
+ component.destroy();
45
+ };
46
+ }, [payabli]);
47
+ const { appearance, options, prefill, locale, timeout } = props;
48
+ useEffect(() => {
49
+ const component = componentRef.current;
50
+ if (!component) {
51
+ return;
52
+ }
53
+ const next = {
54
+ appearance,
55
+ options,
56
+ prefill,
57
+ locale,
58
+ timeout,
59
+ };
60
+ const serialized = JSON.stringify(next);
61
+ if (serialized === appliedOptionsRef.current) {
62
+ return;
63
+ }
64
+ appliedOptionsRef.current = serialized;
65
+ component.update(next);
66
+ }, [appearance, options, prefill, locale, timeout]);
67
+ useImperativeHandle(ref, () => ({
68
+ submit: () => {
69
+ const component = componentRef.current;
70
+ if (!component) {
71
+ return Promise.reject(new Error(`${descriptor.displayName} is not ready. Wait for onReady before you call submit().`));
72
+ }
73
+ return component.submit();
74
+ },
75
+ focus: (field) => {
76
+ var _a;
77
+ (_a = componentRef.current) === null || _a === void 0 ? void 0 : _a.focus(field);
78
+ },
79
+ blur: () => {
80
+ var _a;
81
+ (_a = componentRef.current) === null || _a === void 0 ? void 0 : _a.blur();
82
+ },
83
+ }), []);
84
+ return (_jsx("div", { ref: containerRef, className: props.className, style: props.style, id: props.id, "data-payabli-component": type }));
85
+ });
86
+ RegisteredComponent.displayName = descriptor.displayName;
87
+ return RegisteredComponent;
88
+ }
89
+ function toCreateOptions(props) {
90
+ return {
91
+ appearance: props.appearance,
92
+ options: props.options,
93
+ prefill: props.prefill,
94
+ locale: props.locale,
95
+ timeout: props.timeout,
96
+ };
97
+ }
98
+ function forwardEvent(component, event, propsRef) {
99
+ component.on(event, (payload) => {
100
+ const values = propsRef.current;
101
+ const handler = values[toEventPropName(event)];
102
+ handler === null || handler === void 0 ? void 0 : handler(payload);
103
+ });
104
+ }
105
+ function toEventPropName(event) {
106
+ return `on${event.charAt(0).toUpperCase()}${event.slice(1)}`;
107
+ }
@@ -0,0 +1,10 @@
1
+ export { createComponent } from './createComponent.js';
2
+ export type { ComponentEventProps, ComponentProps, ComponentRef, } from './createComponent.js';
3
+ export { PayabliProvider } from './PayabliProvider.js';
4
+ export type { PayabliProviderProps } from './PayabliProvider.js';
5
+ export { PayIn } from './PayIn.js';
6
+ export type { PayInProps, PayInRef } from './PayIn.js';
7
+ export { usePayabli } from './context.js';
8
+ export type { PayabliContextValue } from './context.js';
9
+ export { ErrorCodes, loadPayabli } from '@payabli/components-web';
10
+ export type { AppearanceOptions, CardBrand, PayInEvents, ComponentEventKey, ComponentEventMap, ComponentHandle, ComponentOptions, ComponentType, CreateOptions, LoadOptions, PayInOptions, PayabliConfig, PaymentMethodName, PayabliInstance, Session, SessionEventKey, SessionEventMap, SessionEventPayload, } from '@payabli/components-web';
package/dist/index.js ADDED
@@ -0,0 +1,5 @@
1
+ export { createComponent } from './createComponent.js';
2
+ export { PayabliProvider } from './PayabliProvider.js';
3
+ export { PayIn } from './PayIn.js';
4
+ export { usePayabli } from './context.js';
5
+ export { ErrorCodes, loadPayabli } from '@payabli/components-web';
package/package.json ADDED
@@ -0,0 +1,52 @@
1
+ {
2
+ "name": "@payabli/components-react",
3
+ "version": "0.1.0",
4
+ "description": "React bindings for the Payabli embedded components SDK.",
5
+ "private": false,
6
+ "license": "UNLICENSED",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/payabli/components-web.git",
10
+ "directory": "packages/react"
11
+ },
12
+ "type": "module",
13
+ "sideEffects": false,
14
+ "main": "dist/index.js",
15
+ "module": "dist/index.js",
16
+ "types": "dist/index.d.ts",
17
+ "exports": {
18
+ ".": {
19
+ "types": "./dist/index.d.ts",
20
+ "import": "./dist/index.js",
21
+ "default": "./dist/index.js"
22
+ }
23
+ },
24
+ "files": [
25
+ "dist",
26
+ "!dist/__tests__"
27
+ ],
28
+ "publishConfig": {
29
+ "access": "public"
30
+ },
31
+ "dependencies": {
32
+ "@payabli/component-contracts": "0.1.0",
33
+ "@payabli/components-web": "0.1.0"
34
+ },
35
+ "peerDependencies": {
36
+ "react": "^18.0.0 || ^19.0.0"
37
+ },
38
+ "devDependencies": {
39
+ "@testing-library/dom": "^10.4.0",
40
+ "@testing-library/react": "^16.1.0",
41
+ "@types/react": "^19.0.0",
42
+ "@types/react-dom": "^19.0.0",
43
+ "react": "^19.0.0",
44
+ "react-dom": "^19.0.0"
45
+ },
46
+ "scripts": {
47
+ "build": "tsc -p tsconfig.build.json",
48
+ "lint": "eslint src",
49
+ "typecheck": "tsc -p tsconfig.json --noEmit",
50
+ "test": "vitest run"
51
+ }
52
+ }