@evoke-platform/ui-components 1.9.1-testing.0 → 1.10.0-testing.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.
Files changed (39) hide show
  1. package/dist/published/components/custom/FormV2/FormRenderer.d.ts +20 -6
  2. package/dist/published/components/custom/FormV2/FormRenderer.js +142 -128
  3. package/dist/published/components/custom/FormV2/FormRendererContainer.d.ts +21 -2
  4. package/dist/published/components/custom/FormV2/FormRendererContainer.js +44 -46
  5. package/dist/published/components/custom/FormV2/components/Body.d.ts +18 -0
  6. package/dist/published/components/custom/FormV2/components/Body.js +27 -0
  7. package/dist/published/components/custom/FormV2/components/Footer.d.ts +15 -0
  8. package/dist/published/components/custom/FormV2/components/Footer.js +77 -0
  9. package/dist/published/components/custom/FormV2/components/FormContext.d.ts +2 -2
  10. package/dist/published/components/custom/FormV2/components/FormContext.js +1 -1
  11. package/dist/published/components/custom/FormV2/components/FormFieldTypes/CollectionFiles/ActionDialog.d.ts +1 -1
  12. package/dist/published/components/custom/FormV2/components/FormFieldTypes/CollectionFiles/ActionDialog.js +64 -22
  13. package/dist/published/components/custom/FormV2/components/FormFieldTypes/CollectionFiles/RepeatableField.js +18 -16
  14. package/dist/published/components/custom/FormV2/components/FormFieldTypes/DocumentFiles/Document.js +7 -7
  15. package/dist/published/components/custom/FormV2/components/FormFieldTypes/relatedObjectFiles/InstanceLookup.d.ts +0 -1
  16. package/dist/published/components/custom/FormV2/components/FormFieldTypes/relatedObjectFiles/InstanceLookup.js +10 -8
  17. package/dist/published/components/custom/FormV2/components/FormFieldTypes/relatedObjectFiles/ObjectPropertyInput.js +38 -49
  18. package/dist/published/components/custom/FormV2/components/FormFieldTypes/relatedObjectFiles/RelatedObjectInstance.d.ts +2 -1
  19. package/dist/published/components/custom/FormV2/components/FormFieldTypes/relatedObjectFiles/RelatedObjectInstance.js +95 -51
  20. package/dist/published/components/custom/FormV2/components/Header.d.ts +29 -0
  21. package/dist/published/components/custom/FormV2/components/Header.js +63 -0
  22. package/dist/published/components/custom/FormV2/components/RecursiveEntryRenderer.js +3 -3
  23. package/dist/published/components/custom/FormV2/components/ValidationFiles/ValidationErrors.d.ts +9 -0
  24. package/dist/published/components/custom/FormV2/components/ValidationFiles/{ValidationErrorDisplay.js → ValidationErrors.js} +11 -8
  25. package/dist/published/components/custom/FormV2/components/types.d.ts +1 -7
  26. package/dist/published/components/custom/FormV2/index.d.ts +3 -0
  27. package/dist/published/components/custom/FormV2/tests/FormRenderer.test.js +6 -5
  28. package/dist/published/components/custom/FormV2/tests/FormRendererContainer.test.js +4 -3
  29. package/dist/published/components/custom/index.d.ts +1 -1
  30. package/dist/published/components/custom/index.js +1 -1
  31. package/dist/published/index.d.ts +1 -1
  32. package/dist/published/stories/FormRenderer.stories.d.ts +24 -16
  33. package/dist/published/stories/FormRenderer.stories.js +2 -10
  34. package/dist/published/stories/FormRendererContainer.stories.d.ts +40 -10
  35. package/dist/published/theme/hooks.d.ts +12 -3
  36. package/package.json +2 -2
  37. package/dist/published/components/custom/FormV2/components/ActionButtons.d.ts +0 -17
  38. package/dist/published/components/custom/FormV2/components/ActionButtons.js +0 -111
  39. package/dist/published/components/custom/FormV2/components/ValidationFiles/ValidationErrorDisplay.d.ts +0 -11
@@ -1,23 +1,37 @@
1
1
  import { EvokeForm, ObjectInstance } from '@evoke-platform/context';
2
2
  import React, { ComponentType } from 'react';
3
- import { FieldErrors, FieldValues } from 'react-hook-form';
3
+ import { FieldValues, SubmitErrorHandler } from 'react-hook-form';
4
+ import { BodyProps } from './components/Body';
5
+ import { FooterProps } from './components/Footer';
6
+ import { HeaderProps } from './components/Header';
4
7
  import { BaseProps, Document, SimpleEditorProps } from './components/types';
8
+ import ValidationErrors from './components/ValidationFiles/ValidationErrors';
5
9
  export type FormRendererProps = BaseProps & {
6
10
  richTextEditor?: ComponentType<SimpleEditorProps>;
7
- hideButtons?: boolean;
8
11
  value?: FieldValues;
9
12
  onSubmit?: (data: FieldValues) => void;
13
+ onDiscardChanges?: () => void;
14
+ onSubmitError?: SubmitErrorHandler<FieldValues>;
10
15
  fieldHeight?: 'small' | 'medium';
11
- stickyFooter?: boolean;
12
- onCancel?: () => void;
13
16
  form: EvokeForm;
17
+ title?: string | React.ReactNode;
14
18
  instance?: ObjectInstance | Document;
15
19
  onChange: (id: string, value: unknown) => void;
16
- onValidationChange?: (errors: FieldErrors) => void;
17
20
  associatedObject?: {
18
21
  instanceId?: string;
19
22
  propertyId?: string;
20
23
  };
24
+ renderHeader?: (props: HeaderProps) => React.ReactNode;
25
+ renderBody?: (props: BodyProps) => React.ReactNode;
26
+ renderFooter?: (props: FooterProps) => React.ReactNode;
27
+ };
28
+ export declare const FormRenderer: React.FC<FormRendererProps> & {
29
+ Header: React.FC<HeaderProps>;
30
+ Body: React.FC<BodyProps>;
31
+ Footer: React.FC<FooterProps>;
32
+ FooterActions: (props: import("./components/Footer").FooterActionsProps) => React.JSX.Element;
33
+ Title: React.FC<import("./components/Header").TitleProps>;
34
+ AccordionActions: React.FC<import("./components/Header").AccordionActionsProps>;
35
+ ValidationErrors: typeof ValidationErrors;
21
36
  };
22
- declare function FormRenderer(props: FormRendererProps): React.JSX.Element;
23
37
  export default FormRenderer;
@@ -1,37 +1,37 @@
1
1
  import { useObject } from '@evoke-platform/context';
2
- import { isEmpty, isEqual } from 'lodash';
2
+ import { isEmpty, isEqual, omit } from 'lodash';
3
3
  import React, { useEffect, useMemo, useState } from 'react';
4
4
  import { useForm } from 'react-hook-form';
5
5
  import { useWidgetSize } from '../../../theme';
6
- import { Button, Skeleton, Typography } from '../../core';
7
6
  import { Box } from '../../layout';
8
- import ActionButtons from './components/ActionButtons';
7
+ import { Body } from './components/Body';
8
+ import { Footer, FooterActions } from './components/Footer';
9
9
  import { FormContext } from './components/FormContext';
10
- import { RecursiveEntryRenderer } from './components/RecursiveEntryRenderer';
11
- import { assignIdsToSectionsAndRichText, convertDocToParameters, convertPropertiesToParams } from './components/utils';
10
+ import Header, { AccordionActions, Title } from './components/Header';
11
+ import { assignIdsToSectionsAndRichText, convertDocToParameters, convertPropertiesToParams, entryIsVisible, getEntryId, getNestedParameterIds, isAddressProperty, } from './components/utils';
12
12
  import { handleValidation } from './components/ValidationFiles/Validation';
13
- import ValidationErrorDisplay from './components/ValidationFiles/ValidationErrorDisplay';
14
- function FormRenderer(props) {
15
- const { onSubmit, value, fieldHeight, richTextEditor, hideButtons, stickyFooter, onCancel, form, instance, onChange, onValidationChange, associatedObject, } = props;
13
+ import ValidationErrors from './components/ValidationFiles/ValidationErrors';
14
+ const FormRendererInternal = (props) => {
15
+ const { onSubmit, onDiscardChanges, onSubmitError, value, fieldHeight, richTextEditor, form, instance, onChange, associatedObject, renderHeader, renderBody, renderFooter, } = props;
16
16
  const { entries, name: title, objectId, actionId, display } = form;
17
17
  const { register, unregister, setValue, reset, handleSubmit, formState: { errors, isSubmitted }, getValues, } = useForm({
18
18
  defaultValues: value,
19
19
  });
20
20
  const hasSections = entries.some((entry) => entry.type === 'sections');
21
- const isModal = !!document.querySelector('.MuiDialog-container');
22
- const { ref: containerRef, breakpoints, isBelow, width, } = useWidgetSize({
21
+ const { ref: containerRef, isBelow, width, } = useWidgetSize({
23
22
  scroll: false,
24
23
  defaultWidth: 1200,
25
24
  });
26
- const { isXs, isSm } = breakpoints;
27
25
  const isSmallerThanMd = isBelow('md');
28
- const objectStore = useObject(objectId);
29
26
  const [expandedSections, setExpandedSections] = useState([]);
30
27
  const [fetchedOptions, setFetchedOptions] = useState({});
31
28
  const [expandAll, setExpandAll] = useState();
32
29
  const [action, setAction] = useState();
33
30
  const [object, setObject] = useState();
34
31
  const [triggerFieldReset, setTriggerFieldReset] = useState(false);
32
+ const [isInitializing, setIsInitializing] = useState(true);
33
+ const [parameters, setParameters] = useState();
34
+ const objectStore = useObject(objectId);
35
35
  const updateFetchedOptions = (newData) => {
36
36
  setFetchedOptions((prev) => ({
37
37
  ...prev,
@@ -44,18 +44,6 @@ function FormRenderer(props) {
44
44
  function handleCollapseAll() {
45
45
  setExpandAll(false);
46
46
  }
47
- const parameters = useMemo(() => {
48
- if (form.id === 'documentForm') {
49
- return convertDocToParameters(instance);
50
- }
51
- else if (action?.parameters) {
52
- return action.parameters;
53
- }
54
- else if (object) {
55
- // if forms actionId is synced with object properties
56
- return convertPropertiesToParams(object);
57
- }
58
- }, [form.id, action?.parameters, object, instance]);
59
47
  const updatedEntries = useMemo(() => {
60
48
  return assignIdsToSectionsAndRichText(entries, object, parameters);
61
49
  }, [entries, object, parameters]);
@@ -63,14 +51,25 @@ function FormRenderer(props) {
63
51
  (async () => {
64
52
  try {
65
53
  const object = await objectStore.get({ sanitized: true });
54
+ const action = object?.actions?.find((a) => a.id === actionId);
66
55
  setObject(object);
67
- if (actionId) {
68
- const action = object?.actions?.find((a) => a.id === actionId);
69
- setAction(action);
56
+ setAction(action);
57
+ if (form.id === 'documentForm') {
58
+ setParameters(convertDocToParameters(instance));
59
+ }
60
+ else if (action?.parameters) {
61
+ setParameters(action.parameters);
62
+ }
63
+ else if (object) {
64
+ // if forms actionId is synced with object properties
65
+ setParameters(convertPropertiesToParams(object));
70
66
  }
71
67
  }
72
68
  catch (error) {
73
- console.error('Failed to fetch object or action:', error);
69
+ console.error('Failed to fetch object, action or parameters:', error);
70
+ }
71
+ finally {
72
+ setIsInitializing(false);
74
73
  }
75
74
  })();
76
75
  }, [objectStore, actionId]);
@@ -87,14 +86,9 @@ function FormRenderer(props) {
87
86
  }
88
87
  }
89
88
  }, [value]);
90
- useEffect(() => {
91
- if (onValidationChange) {
92
- onValidationChange(errors);
93
- }
94
- }, [errors, onValidationChange]);
95
89
  const handleReset = () => {
96
- if (onCancel) {
97
- onCancel();
90
+ if (onDiscardChanges) {
91
+ onDiscardChanges();
98
92
  }
99
93
  else {
100
94
  reset(instance); // clears react-hook-form state back to default values
@@ -104,99 +98,119 @@ function FormRenderer(props) {
104
98
  useEffect(() => {
105
99
  handleValidation(entries, register, getValues(), action?.parameters, instance);
106
100
  }, [action?.parameters, instance, entries, register, getValues]);
107
- if (parameters && (!actionId || action)) {
108
- return (React.createElement(Box, { ref: containerRef },
109
- ((isSubmitted && !isEmpty(errors)) || (isSmallerThanMd && hasSections) || title) && (React.createElement(Box, { sx: {
110
- paddingX: isSmallerThanMd ? 2 : 3,
111
- paddingTop: '0px',
112
- } },
113
- React.createElement(Box, { sx: {
114
- display: 'flex',
115
- justifyContent: 'space-between',
116
- alignItems: 'center',
117
- flexWrap: 'wrap',
118
- paddingY: isSm || isXs ? 2 : 3,
119
- } },
120
- title && (React.createElement(Typography, { sx: {
121
- fontSize: '20px',
122
- lineHeight: '30px',
123
- fontWeight: 700,
124
- flexGrow: '1',
125
- } }, title)),
126
- isSmallerThanMd && hasSections && (React.createElement(Box, { sx: {
127
- color: '#212B36',
128
- display: 'flex',
129
- alignItems: 'center',
130
- maxHeight: '22px',
131
- } },
132
- React.createElement(Button, { variant: "text", size: "small", disableRipple: true, disabled: expandedSections.every((section) => section.expanded === true), sx: {
133
- color: '#212B36',
134
- borderRight: '1px solid #e5e8eb',
135
- borderRadius: '0px',
136
- '&:hover': {
137
- backgroundColor: 'transparent',
138
- },
139
- fontWeight: 400,
140
- fontSize: '14px',
141
- }, onClick: handleExpandAll }, "Expand all"),
142
- React.createElement(Button, { variant: "text", size: "small", disableRipple: true, disabled: expandedSections.every((section) => section.expanded === false), sx: {
143
- color: '#212B36',
144
- '&:hover': {
145
- backgroundColor: 'transparent',
146
- },
147
- fontWeight: 400,
148
- fontSize: '14px',
149
- }, onClick: handleCollapseAll }, "Collapse all")))),
150
- React.createElement(ValidationErrorDisplay, { formId: form.id, title: title, errors: errors, showSubmitError: isSubmitted, isSmallerThanMd: isSmallerThanMd }))),
151
- React.createElement(FormContext.Provider, { value: {
152
- fetchedOptions,
153
- setFetchedOptions: updateFetchedOptions,
154
- getValues,
155
- stickyFooter,
156
- object,
101
+ const unregisterHiddenFields = (entriesToCheck) => {
102
+ entriesToCheck.forEach((entry) => {
103
+ if (entry.type === 'sections' || entry.type === 'columns') {
104
+ const subEntries = entry.type === 'sections' ? entry.sections : entry.columns;
105
+ subEntries.forEach((subEntry) => {
106
+ if (subEntry.entries) {
107
+ unregisterHiddenFields(subEntry.entries);
108
+ }
109
+ });
110
+ }
111
+ if (!entryIsVisible(entry, getValues(), instance)) {
112
+ if (entry.type === 'sections' || entry.type === 'columns') {
113
+ const fieldsToUnregister = getNestedParameterIds(entry);
114
+ fieldsToUnregister.forEach(processFieldUnregister);
115
+ }
116
+ else {
117
+ const fieldId = getEntryId(entry);
118
+ if (fieldId)
119
+ processFieldUnregister(fieldId);
120
+ }
121
+ }
122
+ });
123
+ };
124
+ const processFieldUnregister = (fieldId) => {
125
+ if (isAddressProperty(fieldId)) {
126
+ // Unregister entire addressObject to clear hidden field errors, then restore existing values since unregistering address.line1 etc is not working
127
+ const [addressObject, addressField] = fieldId.split('.');
128
+ let addressValues = getValues(addressObject);
129
+ addressValues = omit(addressValues, addressField);
130
+ unregister(addressObject);
131
+ setValue(addressObject, addressValues);
132
+ }
133
+ else {
134
+ unregister(fieldId);
135
+ }
136
+ };
137
+ async function unregisterHiddenFieldsAndSubmit() {
138
+ unregisterHiddenFields(entries ?? []);
139
+ await handleSubmit((data) => onSubmit && onSubmit(action?.type === 'delete' ? {} : data), (errors) => onSubmitError?.(errors))();
140
+ }
141
+ const headerProps = {
142
+ title,
143
+ onExpandAll: handleExpandAll,
144
+ onCollapseAll: handleCollapseAll,
145
+ expandedSections,
146
+ errors,
147
+ hasAccordions: hasSections && isSmallerThanMd,
148
+ shouldShowValidationErrors: isSubmitted,
149
+ form,
150
+ action,
151
+ };
152
+ const footerProps = {
153
+ onSubmit: unregisterHiddenFieldsAndSubmit,
154
+ onDiscardChanges: handleReset,
155
+ action,
156
+ discardChangesButtonLabel: 'Discard Changes',
157
+ submitButtonLabel: display?.submitLabel ?? 'Submit',
158
+ };
159
+ return (React.createElement(Box, { ref: containerRef },
160
+ React.createElement(FormContext.Provider, { value: {
161
+ fetchedOptions,
162
+ setFetchedOptions: updateFetchedOptions,
163
+ getValues,
164
+ object,
165
+ errors,
166
+ instance,
167
+ richTextEditor,
168
+ expandedSections,
169
+ setExpandedSections,
170
+ expandAll,
171
+ setExpandAll,
172
+ parameters,
173
+ fieldHeight,
174
+ handleChange: onChange,
175
+ triggerFieldReset,
176
+ showSubmitError: isSubmitted,
177
+ associatedObject,
178
+ form,
179
+ width,
180
+ } },
181
+ React.createElement(React.Fragment, null,
182
+ ((isSubmitted && !isEmpty(errors)) || (isSmallerThanMd && hasSections) || title || renderHeader) &&
183
+ (renderHeader ? renderHeader({ ...headerProps }) : React.createElement(Header, { ...headerProps })),
184
+ renderBody ? (renderBody({
185
+ isInitializing,
186
+ entries: updatedEntries,
157
187
  errors,
158
- instance,
159
- richTextEditor,
188
+ shouldShowValidationErrors: isSubmitted,
189
+ onExpandAll: handleExpandAll,
190
+ onCollapseAll: handleCollapseAll,
160
191
  expandedSections,
161
- setExpandedSections,
162
- expandAll,
163
- setExpandAll,
164
- parameters,
165
- fieldHeight,
166
- handleChange: onChange,
167
- triggerFieldReset,
168
- showSubmitError: isSubmitted,
169
- associatedObject,
170
- width,
171
- } },
172
- React.createElement(Box, { sx: {
173
- paddingX: isSm || isXs ? 2 : 3,
174
- // when rendering the default delete action, we don't want a border
175
- borderTop: !form.id || isModal ? undefined : '1px solid #e9ecef',
176
- } },
177
- updatedEntries.map((entry, index) => (React.createElement(RecursiveEntryRenderer, { key: index, entry: entry, isDocument: !!(form.id === 'documentForm') }))),
178
- !hideButtons && (actionId || form.id === 'documentForm') && onSubmit && (React.createElement(Box, { sx: {
179
- ...(stickyFooter === false ? { position: 'static' } : { position: 'sticky' }),
180
- bottom: isModal || isSmallerThanMd ? 0 : 24,
181
- zIndex: 1000,
182
- borderTop: action?.type !== 'delete' ? '1px solid #f4f6f8' : 'none',
183
- backgroundColor: '#fff',
184
- padding: isSmallerThanMd ? '16px' : '20px',
185
- display: 'flex',
186
- justifyContent: isXs ? 'center' : 'flex-end',
187
- alignItems: 'center',
188
- marginX: isSmallerThanMd ? -2 : -3,
189
- borderRadius: '0px 0px 6px 6px',
190
- } },
191
- React.createElement(ActionButtons, { onSubmit: onSubmit, handleSubmit: handleSubmit, isModal: isModal, actionType: action?.type, submitButtonLabel: display?.submitLabel, onReset: handleReset, unregister: unregister, entries: entries, setValue: setValue, formId: form.id })))))));
192
- }
193
- else {
194
- return (React.createElement(Box, { p: 2 },
195
- React.createElement(Skeleton, null),
196
- React.createElement(Skeleton, null),
197
- React.createElement(Skeleton, null),
198
- React.createElement(Skeleton, null),
199
- React.createElement(Skeleton, null)));
200
- }
201
- }
192
+ hasAccordions: hasSections && isSmallerThanMd,
193
+ })) : (React.createElement(Body, { ...{
194
+ isInitializing,
195
+ entries: updatedEntries,
196
+ errors,
197
+ shouldShowValidationErrors: isSubmitted,
198
+ onExpandAll: handleExpandAll,
199
+ onCollapseAll: handleCollapseAll,
200
+ expandedSections,
201
+ hasAccordions: hasSections && isSmallerThanMd,
202
+ } })),
203
+ (actionId || form.id === 'documentForm') &&
204
+ onSubmit &&
205
+ (renderFooter ? renderFooter(footerProps) : React.createElement(Footer, { ...footerProps }))))));
206
+ };
207
+ export const FormRenderer = Object.assign(FormRendererInternal, {
208
+ Header,
209
+ Body,
210
+ Footer,
211
+ FooterActions,
212
+ Title,
213
+ AccordionActions,
214
+ ValidationErrors,
215
+ });
202
216
  export default FormRenderer;
@@ -1,5 +1,18 @@
1
+ import { EvokeForm, ObjectInstance } from '@evoke-platform/context';
2
+ import { SxProps } from '@mui/material';
3
+ import { AxiosError } from 'axios';
1
4
  import React, { ComponentType } from 'react';
2
5
  import { BaseProps, SimpleEditorProps } from './components/types';
6
+ import { FormRendererProps } from './FormRenderer';
7
+ export type FORM_RENDERER_CONTAINER_ERRORS = 'Configured action ID does not match form action ID' | 'A default action form could not be found' | 'Action could not be found';
8
+ export type FormRendererContainerError = AxiosError | number | FORM_RENDERER_CONTAINER_ERRORS | unknown;
9
+ export type FormRendererState = {
10
+ status: 'loading' | 'error' | 'ready';
11
+ error?: FormRendererContainerError;
12
+ form?: EvokeForm;
13
+ instance?: ObjectInstance;
14
+ defaultContainer: React.ReactNode;
15
+ };
3
16
  export type FormRendererContainerProps = BaseProps & {
4
17
  formId?: string;
5
18
  instanceId?: string;
@@ -15,12 +28,18 @@ export type FormRendererContainerProps = BaseProps & {
15
28
  hideButtons?: boolean;
16
29
  objectId: string;
17
30
  richTextEditor?: ComponentType<SimpleEditorProps>;
18
- onClose?: () => void;
19
- onSubmit?: (submission: Record<string, unknown>) => Promise<void>;
31
+ onSubmit?: (submission: Record<string, unknown>, defaultSubmitHandler: (submission: Record<string, unknown>) => Promise<void>) => Promise<void>;
32
+ onDiscardChanges?: FormRendererProps['onDiscardChanges'];
33
+ onSubmitError?: FormRendererProps['onSubmitError'];
20
34
  associatedObject?: {
21
35
  instanceId?: string;
22
36
  propertyId?: string;
23
37
  };
38
+ renderContainer?: (state: FormRendererState) => React.ReactNode;
39
+ renderHeader?: FormRendererProps['renderHeader'];
40
+ renderBody?: FormRendererProps['renderBody'];
41
+ renderFooter?: FormRendererProps['renderFooter'];
42
+ sx?: SxProps;
24
43
  };
25
44
  declare function FormRendererContainer(props: FormRendererContainerProps): React.JSX.Element;
26
45
  export default FormRendererContainer;
@@ -9,7 +9,7 @@ import { evalDefaultVals, processValueUpdate } from './components/DefaultValues'
9
9
  import { convertDocToEntries, deleteDocuments, encodePageSlug, formatDataToDoc, formatSubmission, getEntryId, getPrefixedUrl, getUnnestedEntries, isAddressProperty, isEmptyWithDefault, plainTextToRtf, } from './components/utils';
10
10
  import FormRenderer from './FormRenderer';
11
11
  function FormRendererContainer(props) {
12
- const { instanceId, pageNavigation, documentId, dataType, display, formId, stickyFooter, objectId, actionId, richTextEditor, onClose, onSubmit, associatedObject, hideButtons, } = props;
12
+ const { instanceId, pageNavigation, documentId, dataType, display, formId, objectId, actionId, richTextEditor, onSubmit, onDiscardChanges: onDiscardChangesOverride, associatedObject, renderContainer, onSubmitError, sx, renderHeader, renderBody, renderFooter, } = props;
13
13
  const apiServices = useApiServices();
14
14
  const navigateTo = useNavigate();
15
15
  const { id: appId, defaultPages } = useApp();
@@ -33,7 +33,7 @@ function FormRendererContainer(props) {
33
33
  const onError = (err) => {
34
34
  const code = axios.isAxiosError(err) ? err.response?.status : undefined;
35
35
  setSnackbarError({ ...snackbarError, isError: true });
36
- setError(code ?? true);
36
+ setError(code ?? err);
37
37
  };
38
38
  useEffect(() => {
39
39
  (async () => {
@@ -61,7 +61,7 @@ function FormRendererContainer(props) {
61
61
  setAction(action);
62
62
  }
63
63
  else {
64
- setError(true);
64
+ setError('Action could not be found');
65
65
  }
66
66
  }
67
67
  }
@@ -100,11 +100,11 @@ function FormRendererContainer(props) {
100
100
  .get(getPrefixedUrl(`/forms/${formId || action?.defaultFormId}`))
101
101
  .then((evokeForm) => {
102
102
  if (evokeForm?.actionId === actionId) {
103
- const form = onClose ? { ...evokeForm, name: '' } : evokeForm;
103
+ const form = evokeForm;
104
104
  setForm(form);
105
105
  }
106
106
  else {
107
- setError(true);
107
+ setError('Configured action ID does not match form action ID');
108
108
  }
109
109
  })
110
110
  .catch((error) => {
@@ -125,7 +125,7 @@ function FormRendererContainer(props) {
125
125
  })
126
126
  .then((matchingForms) => {
127
127
  if (matchingForms.length === 1) {
128
- const form = onClose ? { ...matchingForms[0], name: '' } : matchingForms[0];
128
+ const form = matchingForms[0];
129
129
  setForm(form);
130
130
  // use this default form if no delete form is found
131
131
  }
@@ -147,7 +147,7 @@ function FormRendererContainer(props) {
147
147
  });
148
148
  }
149
149
  else if (instance || action.type === 'create') {
150
- setError(true);
150
+ setError('Default action form could not be found');
151
151
  }
152
152
  })
153
153
  .catch((error) => {
@@ -231,25 +231,16 @@ function FormRendererContainer(props) {
231
231
  }
232
232
  try {
233
233
  if (dataType === 'documents' && !!document) {
234
- try {
235
- await apiServices.patch(getPrefixedUrl(`/objects/${form.objectId}/instances/${instanceId}/documents/${documentId}`), pick(submission, ['metadata']).metadata ?? submission);
236
- setDocument((prev) => ({
237
- ...prev,
238
- metadata: submission.metadata,
239
- }));
240
- setSnackbarError({
241
- showAlert: true,
242
- message: 'Your changes have been submitted',
243
- isError: false,
244
- });
245
- }
246
- catch (error) {
247
- setSnackbarError({
248
- isError: true,
249
- showAlert: true,
250
- message: error.response?.data?.error?.message ?? 'An error occurred',
251
- });
252
- }
234
+ await apiServices.patch(getPrefixedUrl(`/objects/${form.objectId}/instances/${instanceId}/documents/${documentId}`), pick(submission, ['metadata']).metadata ?? submission);
235
+ setDocument((prev) => ({
236
+ ...prev,
237
+ metadata: submission.metadata,
238
+ }));
239
+ setSnackbarError({
240
+ showAlert: true,
241
+ message: 'Your changes have been submitted',
242
+ isError: false,
243
+ });
253
244
  }
254
245
  else if (action?.type === 'create') {
255
246
  const response = await apiServices.post(getPrefixedUrl(`/objects/${form.objectId}/instances/actions`), {
@@ -285,6 +276,7 @@ function FormRendererContainer(props) {
285
276
  showAlert: true,
286
277
  message: error.response?.data?.error?.message ?? 'An error occurred',
287
278
  });
279
+ throw error; // Throw error so caller knows submission failed
288
280
  }
289
281
  };
290
282
  const getDefaultValues = async (entries, instanceData) => {
@@ -414,8 +406,11 @@ function FormRendererContainer(props) {
414
406
  });
415
407
  }
416
408
  }
417
- function onCancel() {
418
- (async () => {
409
+ const isLoading = (instanceId && !formData && !document) || !form || !sanitizedObject;
410
+ const status = error ? 'error' : isLoading ? 'loading' : 'ready';
411
+ const onDiscardChanges = onDiscardChangesOverride
412
+ ? onDiscardChangesOverride
413
+ : async () => {
419
414
  if (document) {
420
415
  const defaultValues = await getDefaultValues(convertDocToEntries(document), document);
421
416
  setFormData(defaultValues);
@@ -424,29 +419,32 @@ function FormRendererContainer(props) {
424
419
  const defaultValues = await getDefaultValues(form.entries, instance || {});
425
420
  setFormData(defaultValues);
426
421
  }
427
- })();
428
- }
429
- const isLoading = (instanceId && !formData && !document) || !form || !sanitizedObject;
430
- return !error ? (React.createElement(Box, { sx: {
422
+ };
423
+ const defaultContainer = !error ? (React.createElement(Box, { sx: {
431
424
  backgroundColor: '#ffffff',
432
425
  borderRadius: '6px',
433
426
  padding: '0px',
434
- border: !isLoading && !onClose ? '1px solid #dbe0e4' : undefined,
435
- } },
436
- !isLoading ? (React.createElement(FormRenderer, { onSubmit: onSubmit ?? saveHandler, hideButtons: hideButtons ?? (document && !hasDocumentUpdateAccess), richTextEditor: richTextEditor, fieldHeight: display?.fieldHeight ?? 'medium', value: formData, stickyFooter: stickyFooter, form: form, instance: dataType !== 'documents' ? instance : document, onChange: onChange, onCancel: onClose ?? onCancel, associatedObject: associatedObject })) : (React.createElement(Box, { sx: { padding: '20px' } },
437
- React.createElement(Box, { display: 'flex', width: '100%', justifyContent: 'space-between' },
438
- React.createElement(Skeleton, { width: '78%', sx: { borderRadius: '8px', height: '40px' } }),
439
- React.createElement(Skeleton, { width: '20%', sx: { borderRadius: '8px', height: '40px' } })),
440
- React.createElement(Box, { display: 'flex', width: '100%', justifyContent: 'space-between' },
441
- React.createElement(Skeleton, { width: '32%', sx: { borderRadius: '8px', height: '40px' } }),
442
- React.createElement(Skeleton, { width: '33%', sx: { borderRadius: '8px', height: '40px' } }),
443
- React.createElement(Skeleton, { width: '32%', sx: { borderRadius: '8px', height: '40px' } })),
444
- React.createElement(Box, { display: 'flex', width: '100%', justifyContent: 'space-between' },
445
- React.createElement(Skeleton, { width: '49%', sx: { borderRadius: '8px', height: '40px' } }),
446
- React.createElement(Skeleton, { width: '49%', sx: { borderRadius: '8px', height: '40px' } })))),
427
+ border: !isLoading ? '1px solid #dbe0e4' : undefined,
428
+ ...sx,
429
+ } }, !isLoading ? (React.createElement(React.Fragment, null, form && sanitizedObject && (React.createElement(FormRenderer, { onSubmit: onSubmit ? (data) => onSubmit(data, saveHandler) : saveHandler, onSubmitError: onSubmitError, onDiscardChanges: onDiscardChanges, richTextEditor: richTextEditor, fieldHeight: display?.fieldHeight ?? 'medium', value: formData, form: form, instance: dataType !== 'documents' ? instance : document, onChange: onChange, associatedObject: associatedObject, renderHeader: renderHeader, renderBody: renderBody, renderFooter: document && !hasDocumentUpdateAccess ? () => React.createElement(React.Fragment, null) : renderFooter })))) : (React.createElement(Box, { sx: { padding: '20px' } },
430
+ React.createElement(Box, { display: 'flex', width: '100%', justifyContent: 'space-between' },
431
+ React.createElement(Skeleton, { width: '78%', sx: { borderRadius: '8px', height: '40px' } }),
432
+ React.createElement(Skeleton, { width: '20%', sx: { borderRadius: '8px', height: '40px' } })),
433
+ React.createElement(Box, { display: 'flex', width: '100%', justifyContent: 'space-between' },
434
+ React.createElement(Skeleton, { width: '32%', sx: { borderRadius: '8px', height: '40px' } }),
435
+ React.createElement(Skeleton, { width: '33%', sx: { borderRadius: '8px', height: '40px' } }),
436
+ React.createElement(Skeleton, { width: '32%', sx: { borderRadius: '8px', height: '40px' } })),
437
+ React.createElement(Box, { display: 'flex', width: '100%', justifyContent: 'space-between' },
438
+ React.createElement(Skeleton, { width: '49%', sx: { borderRadius: '8px', height: '40px' } }),
439
+ React.createElement(Skeleton, { width: '49%', sx: { borderRadius: '8px', height: '40px' } })))))) : (React.createElement(ErrorComponent, { colspan: props.colspan, code: error === 403 ? 'AccessDenied' : error === 404 ? 'NotFound' : 'Misconfigured',
440
+ // box shadow looks wrong when rendering a form in a modal, and there's no need for it
441
+ // in a standalone form v2 widget.
442
+ styles: { boxShadow: 'none' } }));
443
+ return (React.createElement(React.Fragment, null,
444
+ renderContainer ? React.createElement(React.Fragment, null, renderContainer({ status, error, defaultContainer })) : defaultContainer,
447
445
  React.createElement(Snackbar, { open: snackbarError.showAlert, handleClose: () => setSnackbarError({
448
446
  isError: snackbarError.isError,
449
447
  showAlert: false,
450
- }), message: snackbarError.message, error: snackbarError.isError }))) : (React.createElement(ErrorComponent, { colspan: props.colspan, code: error === 403 ? 'AccessDenied' : error === 404 ? 'NotFound' : 'Misconfigured' }));
448
+ }), message: snackbarError.message, error: snackbarError.isError })));
451
449
  }
452
450
  export default FormRendererContainer;
@@ -0,0 +1,18 @@
1
+ import { FormEntry } from '@evoke-platform/context';
2
+ import { SxProps } from '@mui/material';
3
+ import React from 'react';
4
+ import { FieldErrors } from 'react-hook-form';
5
+ import { ExpandedSection } from './types';
6
+ export type BodyProps = {
7
+ entries: FormEntry[];
8
+ isInitializing: boolean;
9
+ errors?: FieldErrors;
10
+ shouldShowValidationErrors: boolean;
11
+ hasAccordions: boolean;
12
+ expandedSections?: ExpandedSection[];
13
+ onExpandAll?: () => void;
14
+ onCollapseAll?: () => void;
15
+ sx?: SxProps;
16
+ };
17
+ export declare const Body: React.FC<BodyProps>;
18
+ export default Body;
@@ -0,0 +1,27 @@
1
+ import React, { useContext } from 'react';
2
+ import { FormContext } from '../..';
3
+ import useWidgetSize from '../../../../theme/hooks';
4
+ import { Skeleton } from '../../../core';
5
+ import Box from '../../../layout/Box/Box';
6
+ import { RecursiveEntryRenderer } from './RecursiveEntryRenderer';
7
+ export const Body = (props) => {
8
+ const { entries, isInitializing, sx } = props;
9
+ const { width } = useContext(FormContext);
10
+ const { breakpoints } = useWidgetSize({
11
+ scroll: false,
12
+ defaultWidth: width,
13
+ });
14
+ const { isXs, isSm } = breakpoints;
15
+ return (React.createElement(React.Fragment, null, isInitializing ? (React.createElement(Box, { sx: { padding: '20px', ...sx } },
16
+ React.createElement(Box, { display: 'flex', width: '100%', justifyContent: 'space-between' },
17
+ React.createElement(Skeleton, { width: '78%', sx: { borderRadius: '8px', height: '40px' } }),
18
+ React.createElement(Skeleton, { width: '20%', sx: { borderRadius: '8px', height: '40px' } })),
19
+ React.createElement(Box, { display: 'flex', width: '100%', justifyContent: 'space-between' },
20
+ React.createElement(Skeleton, { width: '32%', sx: { borderRadius: '8px', height: '40px' } }),
21
+ React.createElement(Skeleton, { width: '33%', sx: { borderRadius: '8px', height: '40px' } }),
22
+ React.createElement(Skeleton, { width: '32%', sx: { borderRadius: '8px', height: '40px' } })),
23
+ React.createElement(Box, { display: 'flex', width: '100%', justifyContent: 'space-between' },
24
+ React.createElement(Skeleton, { width: '49%', sx: { borderRadius: '8px', height: '40px' } }),
25
+ React.createElement(Skeleton, { width: '49%', sx: { borderRadius: '8px', height: '40px' } })))) : (React.createElement(Box, { sx: { paddingX: isSm || isXs ? 2 : 3, ...sx } }, entries.map((entry, index) => (React.createElement(RecursiveEntryRenderer, { key: index, entry: entry })))))));
26
+ };
27
+ export default Body;