UNPKG

@progress/sitefinity-nextjs-sdk

Version:

Provides OOB widgets developed using the Next.js framework, which includes an abstraction layer for Sitefinity communication. Additionally, it offers an expanded API, typings, and tools for further development and integration.

228 lines (227 loc) 11.1 kB
'use client'; import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime"; import React from 'react'; import { StylingConfig } from '../styling/styling-config'; import { VisibilityStyle } from '../styling/visibility-style'; import { FormRulesExecutor } from './rules/form-rules-executor'; import { classNames } from '../../editor/utils/classNames'; import { FormContext } from './form-context'; const generateHiddenFields = (fields) => { return fields.reduce((obj, item) => Object.assign(obj, { [item]: true }), {}); }; export function FormClient(props) { const { children, viewProps, className, formDataAttributes } = props; const fromElementRef = React.useRef(null); const formRef = React.useRef(null); const formRules = React.useRef(null); const [disabledSubmitButton, setDisabledSubmitButton] = React.useState(false); const [showLoading, setShowLoading] = React.useState(false); const [showFields, setShowFields] = React.useState(true); const [errorMessage, setErrorMessage] = React.useState(); const [successMessage, setSuccessMessage] = React.useState(); const [formSubmitted, setFormSubmitted] = React.useState(false); const splitHiddenFields = viewProps.hiddenFields?.split(',') || []; const [hiddenInputs, setHiddenInputs] = React.useState(generateHiddenFields(splitHiddenFields)); const [skippedInputs, setSkippedInputs] = React.useState({}); const [validatedInputs, setValidatedInputs] = React.useState({}); const sfFormValueChanged = () => { formRules.current.process(); }; const dispatchValidity = (inputKey, valid) => { setValidatedInputs(vI => { const newValidatedInputs = { ...vI, [inputKey]: valid }; return newValidatedInputs; }); }; const updateFields = React.useCallback((args) => { if (args.show) { setHiddenInputs(hI => { const newHiddenFields = { ...hI }; delete newHiddenFields[args.show]; return newHiddenFields; }); } if (args.hide) { setHiddenInputs(hI => { const newHiddenFields = { ...hI }; newHiddenFields[args.hide] = true; return newHiddenFields; }); } if (args.skip) { setSkippedInputs(sI => { const newSkippedFields = { ...sI }; delete newSkippedFields[args.skip]; return newSkippedFields; }); } if (args.unSkip) { setSkippedInputs(sI => { const newSkippedFields = { ...sI }; newSkippedFields[args.unSkip] = true; return newSkippedFields; }); } }, []); const formCreateRef = React.useCallback((node) => { if (node !== null) { formRef.current = node; const fr = new FormRulesExecutor(node, updateFields); formRules.current = fr; fr.process(); } }, [updateFields]); function showSuccessMessage(message) { setShowLoading(false); setShowFields(false); setSuccessMessage(message); } function showErrorMessage(message) { setErrorMessage(message); setShowLoading(false); setShowFields(false); } function triggerLoading() { setErrorMessage(''); setShowLoading(true); setSuccessMessage(''); setShowFields(false); } function showSubmittedForm() { setDisabledSubmitButton(true); setShowLoading(false); setSuccessMessage(''); setShowFields(true); } const handleResponse = (redirectUrl, successMessageVal, openInNewWindow) => { if (redirectUrl) { if (openInNewWindow) { showSubmittedForm(); window.open(redirectUrl, '_blank'); } else { document.location.replace(redirectUrl); } } else { showSuccessMessage(successMessageVal || viewProps.successMessage || ''); } }; const validFormSubmit = () => { const form = fromElementRef.current; if (viewProps.skipDataSubmission) { handleResponse(viewProps.redirectUrl); return false; } const genericFormError = 'Form could not be submitted'; const headerName = 'X-SF-ANTIFORGERY-REQUEST'; const headers = {}; headers[headerName] = ''; fetch('/sitefinity/anticsrf', { headers: headers }).then(function (csrfResponse) { function sendSubmitRequest(headerValue) { if (headerValue) { headers[headerName] = headerValue; } const formData = new FormData(form); formData.set('sf_antiforgery', headerValue || ''); const hiddenInputsTrueKeys = Object.entries(hiddenInputs).filter(([key, value]) => value === true).map(([key, value]) => key); formData.set('sf_FormHiddenFields', hiddenInputsTrueKeys.join(',')); fetch(viewProps.submitUrl, { method: 'POST', body: formData }).then(function (formSubmitResponse) { formSubmitResponse.json().then(function (jsonFormSubmitResponse) { // Successfull request statuses if (formSubmitResponse.status >= 200 && formSubmitResponse.status < 300) { if (jsonFormSubmitResponse.success) { if (viewProps.customSubmitAction.toString().toLowerCase() === 'true') { handleResponse(viewProps.redirectUrl); } else { handleResponse(jsonFormSubmitResponse.redirectUrl, jsonFormSubmitResponse.message, jsonFormSubmitResponse.openInNewWindow); } } else { showErrorMessage(jsonFormSubmitResponse.error); } // Client and Server error request statuses } else if (formSubmitResponse.status >= 400 && formSubmitResponse.status < 600) { showErrorMessage(jsonFormSubmitResponse.error); } }, function (error) { if (formSubmitResponse.status === 413) { showErrorMessage('Request was too large'); } else { showErrorMessage(genericFormError); } }); }, function (error) { const serializedError = JSON.stringify(error); if (serializedError === '{}') { showErrorMessage(genericFormError); } else { showErrorMessage(serializedError); } }); } if (csrfResponse.status === 200) { if (csrfResponse.headers.get('content-type') === 'application/json') { csrfResponse.json().then(function (jsonCsrfResponse) { sendSubmitRequest(jsonCsrfResponse.Value); }); } else { sendSubmitRequest(); } } else if (csrfResponse.status === 204 || csrfResponse.status === 404) { sendSubmitRequest(); } else { showErrorMessage('Failed to submit form due to lack of csrf token'); } }); }; const handleSubmit = function (e) { e?.preventDefault(); setFormSubmitted(true); }; const allInputsAreValid = React.useMemo(() => { return Object.entries(validatedInputs) .filter(([inputKey, inputValue]) => !hiddenInputs[inputKey]) .map(([inputKey, inputValue]) => { return !skippedInputs[inputKey] && inputValue; }).every((i) => i); }, [validatedInputs, hiddenInputs, skippedInputs]); React.useEffect(() => { if (formSubmitted && allInputsAreValid) { validFormSubmit(); } if (formSubmitted) { setTimeout(() => { setFormSubmitted(false); }, 10); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [formSubmitted, allInputsAreValid]); return (_jsxs("form", { ref: fromElementRef, action: viewProps.submitUrl, onSubmit: handleSubmit, method: "post", ...formDataAttributes, noValidate: true, children: [_jsx("div", { "data-sf-role": "success-message", className: classNames('valid-feedback', successMessage ? [StylingConfig.VisibilityClasses[VisibilityStyle.Visible]] : [StylingConfig.VisibilityClasses[VisibilityStyle.Hidden]]), role: "alert", "aria-live": "assertive", children: successMessage || viewProps.successMessage }), _jsx("div", { "data-sf-role": "error-message", className: classNames('invalid-feedback', errorMessage ? [StylingConfig.VisibilityClasses[VisibilityStyle.Visible]] : [StylingConfig.VisibilityClasses[VisibilityStyle.Hidden]]), role: "alert", "aria-live": "assertive", children: errorMessage }), _jsx("div", { "data-sf-role": "loading", className: classNames(showLoading ? [StylingConfig.VisibilityClasses[VisibilityStyle.Visible]] : [StylingConfig.VisibilityClasses[VisibilityStyle.Hidden]]), children: _jsx("div", { className: "d-flex justify-content-center", children: _jsx("div", { className: "spinner-border", role: "status", children: _jsx("span", { className: "visually-hidden", children: "Loading..." }) }) }) }), _jsx(FormContext.Provider, { value: { formViewProps: viewProps, sfFormValueChanged, hiddenInputs, skippedInputs, formSubmitted, disabledSubmitButton, dispatchValidity }, children: _jsx("div", { ref: formCreateRef, className: classNames(className, showFields ? [StylingConfig.VisibilityClasses[VisibilityStyle.Visible]] : [StylingConfig.VisibilityClasses[VisibilityStyle.Hidden]]), "data-sf-role": "form-container", "data-sf-invalid": viewProps.invalidClass, "data-sf-visibility-inline-visible": viewProps.visibilityClasses[VisibilityStyle.InlineVisible], "data-sf-visibility-hidden": viewProps.visibilityClasses[VisibilityStyle.Hidden], "data-sf-visibility-visible": viewProps.visibilityClasses[VisibilityStyle.Visible], children: children }) })] })); }