Skip to content

Commit

Permalink
Merge pull request #957 from openedx/mkeating/ENT-6592/0
Browse files Browse the repository at this point in the history
feat: New LMS Config Workflow Skeleton + Typescript
  • Loading branch information
marlonkeating authored Mar 9, 2023
2 parents 3573caf + 16b1c75 commit 7eb4eec
Show file tree
Hide file tree
Showing 31 changed files with 3,351 additions and 6,097 deletions.
6,582 changes: 1,249 additions & 5,333 deletions package-lock.json

Large diffs are not rendered by default.

5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@
},
"devDependencies": {
"@edx/browserslist-config": "1.0.0",
"@edx/frontend-build": "^12.3.0",
"@edx/frontend-build": "^12.5.0",
"@faker-js/faker": "^7.6.0",
"@testing-library/dom": "7.31.2",
"@testing-library/jest-dom": "5.11.9",
Expand All @@ -106,6 +106,7 @@
"postcss": "8.1.0",
"react-dev-utils": "11.0.4",
"react-test-renderer": "16.13.1",
"resize-observer-polyfill": "1.5.1"
"resize-observer-polyfill": "1.5.1",
"ts-jest": "^26.5.0"
}
}
4 changes: 2 additions & 2 deletions src/components/RequestCodesPage/RequestCodesForm.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import RenderField from '../RenderField';
import StatusAlert from '../StatusAlert';

import {
isRequired, isValidEmail, isValidNumber, maxLength512,
isRequired, isValidEmail, isNotValidNumberString, maxLength512,
} from '../../utils';

class RequestCodesForm extends React.Component {
Expand Down Expand Up @@ -103,7 +103,7 @@ class RequestCodesForm extends React.Component {
type="number"
component={RenderField}
label="Number of Codes"
validate={[isValidNumber]}
validate={[isNotValidNumberString]}
data-hj-suppress
/>
<Field
Expand Down
54 changes: 54 additions & 0 deletions src/components/forms/FormContext.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import React, {
createContext,
useContext,
Context,
ReactNode,
Dispatch,
} from "react";
import type { FormActionArguments } from "./data/actions";
import type { FormWorkflowStep } from "./FormWorkflow";

export type FormFields = { [name: string]: any };
export type FormValidatorResult = boolean | string;
export type FormValidator = (formFields: FormFields) => FormValidatorResult;
export type FormFieldValidation = {
formFieldId: string;
validator: FormValidator;
};

export type FormContext = {
dispatch?: Dispatch<FormActionArguments>;
formFields?: FormFields;
isEdited?: boolean;
hasErrors?: boolean;
errorMap?: { [name: string]: string[] };
stateMap?: { [name: string]: any };
currentStep?: FormWorkflowStep<any>;
};

export const FormContextObject: Context<FormContext> = createContext({});

export function useFormContext(): FormContext {
return useContext(FormContextObject);
}

type FormContextProps = {
children: ReactNode;
formContext: FormContext;
dispatch?: Dispatch<FormActionArguments>;
};

// Context wrapper for all form components
const FormContextProvider = ({
children,
dispatch,
formContext,
}: FormContextProps) => {
return (
<FormContextObject.Provider value={{ ...formContext, dispatch }}>
{children}
</FormContextObject.Provider>
);
};

export default FormContextProvider;
45 changes: 45 additions & 0 deletions src/components/forms/FormContextWrapper.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import React, { useReducer } from "react";
// @ts-ignore
import FormContextProvider from "./FormContext.tsx";
import type { FormFields } from "./FormContext";
// @ts-ignore
import FormWorkflow from "./FormWorkflow.tsx";
import type { FormWorkflowProps } from "./FormWorkflow";
// @ts-ignore
import {FormReducer, initializeForm } from "./data/reducer.ts";
import type { FormActionArguments } from "./data/actions";

// Context wrapper for multi-step form container
function FormContextWrapper<FormData>({
formWorkflowConfig,
onClickOut,
onSubmit,
formData,
}: FormWorkflowProps<FormData>) {
const [formFieldsState, dispatch] = useReducer<
FormReducer,
FormActionArguments
>(
FormReducer,
initializeForm(
{},
{
formFields: formData as FormFields,
currentStep: formWorkflowConfig.getCurrentStep(),
}
),
initializeForm
);
return (
<FormContextProvider
dispatch={dispatch}
formContext={formFieldsState || {}}
>
<FormWorkflow
{...{ formWorkflowConfig, onClickOut, onSubmit, dispatch }}
/>
</FormContextProvider>
);
}

export default FormContextWrapper;
41 changes: 41 additions & 0 deletions src/components/forms/FormWaitModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import React from "react";
import { AlertModal, Spinner } from "@edx/paragon";
// @ts-ignore
import { useFormContext } from "./FormContext.tsx";
import type { FormContext } from "./FormContext";

type FormWaitModal = {
// FormContext state that when truthy, shows the modal
triggerState: string;
onClose: () => void;
header: string;
text: string;
};

// Modal shown when waiting for a background operation to complete in forms
const FormWaitModal = ({
triggerState,
onClose,
header,
text,
}: FormWaitModal) => {
const { stateMap }: FormContext = useFormContext();

const isOpen = stateMap && stateMap[triggerState];

return (
<AlertModal title={header} isOpen={isOpen} onClose={onClose} hasCloseButton>
<div className="d-flex justify-content-center">
<Spinner
screenReaderText={text}
animation="border"
className="mr-2"
variant="primary"
/>
</div>
<p>{text}</p>
</AlertModal>
);
};

export default FormWaitModal;
Loading

0 comments on commit 7eb4eec

Please sign in to comment.