From f75e0e09fbf5bdc9611076df16830b2decf4395b Mon Sep 17 00:00:00 2001 From: Johan Date: Sun, 14 Jan 2024 10:03:49 +0100 Subject: [PATCH 1/9] first mapping --- src/app.tsx | 10 ++-- src/components/form/inputs.tsx | 4 +- src/examples/form/ui.tsx | 26 +++++----- src/lib/form/form-wrapper.tsx | 79 ++++++++++--------------------- src/lib/form/generator/ui.tsx | 2 +- src/lib/form/generator2/index.tsx | 18 +++---- src/lib/form/generator2/type.ts | 9 ++-- src/lib/form/type.ts | 14 ++++-- src/lib/form/utils.ts | 2 +- 9 files changed, 74 insertions(+), 90 deletions(-) diff --git a/src/app.tsx b/src/app.tsx index f1b44ad..3ce0731 100644 --- a/src/app.tsx +++ b/src/app.tsx @@ -57,17 +57,19 @@ export default () => ( } /> } /> } /> - } /> + } /> } /> } /> } /> } /> } /> + {/* + } /> } /> - } /> - } /> - } /> + //} /> + } /> } />*/} + } /> } /> } /> diff --git a/src/components/form/inputs.tsx b/src/components/form/inputs.tsx index ae2b636..c1df546 100644 --- a/src/components/form/inputs.tsx +++ b/src/components/form/inputs.tsx @@ -3,7 +3,7 @@ import React from "react"; import * as T from "../../lib/form/type"; export const getClassName = ( - errors?: string[], + errors?: string, mainClass = "border p-2 rounded" ): string => { const isInvalid: boolean = !!errors; @@ -32,7 +32,7 @@ export const InputWrapper = ({ id="validationServer03Feedback" className="text-red-500 text-xs italic mt-1" > - {errors[0]} + {errors} )} diff --git a/src/examples/form/ui.tsx b/src/examples/form/ui.tsx index 8ec472a..2506a4c 100644 --- a/src/examples/form/ui.tsx +++ b/src/examples/form/ui.tsx @@ -1,32 +1,32 @@ -import React from 'react'; -import * as T from '../../lib/form/type'; -import * as Inputs from '../../components/form/inputs'; -import { FormDataShape } from './type'; +import React from "react"; +import * as T from "../../lib/form/type"; +import * as Inputs from "../../components/form/inputs"; +import { FormDataShape } from "./type"; const FormUI = ({ form, setForm, loading, - errors + errors, }: T.FormUIProps) => { return ( <> - + setForm({ ...form, firstName })} + onChange={(firstName) => setForm({ ...form, firstName })} disabled={loading} - placeholder={'First Name'} - errors={errors['firstName']} + placeholder={"First Name"} + errors={errors["firstName"]} /> - + setForm({ ...form, lastName })} + onChange={(lastName) => setForm({ ...form, lastName })} disabled={loading} - placeholder={'Last Name'} - errors={errors['lastName']} + placeholder={"Last Name"} + errors={errors["lastName"]} /> diff --git a/src/lib/form/form-wrapper.tsx b/src/lib/form/form-wrapper.tsx index 7b55806..6ad6fb6 100644 --- a/src/lib/form/form-wrapper.tsx +++ b/src/lib/form/form-wrapper.tsx @@ -1,84 +1,55 @@ // generic wrapper for forms, see args below for details -import React from 'react'; +import React from "react"; -import * as T from './type'; -import * as Validation from '@nexys/validation'; -import { isA } from './utils'; +import * as T from "./type"; +import * as Validation from "@nexys/validation"; + +import { FormWrapper as F2 } from "./generator2"; +import { FormUIProps } from "./generator2/type"; export interface FormWrapperProps { data?: { dataIn: Partial; - options?: { - [k in keyof A]?: { id: number | string; name: string }[]; - }; + options?: T.FormOptionSets; }; onSuccess?: (data: A, out?: Out) => void; onErrors?: (err: any, data: A) => { errors?: T.FormErrorsGeneric }; } +interface FormWrapperOptions { + resetAfterSubmit: boolean; +} + /** * @type FormShape: shape of the form * @param FormUI : UI for the form, must respect FormUIProps * @param shape : validation shape * @param asyncCall [optional]: async call, typically backend * @param onSuccess [optional]: after call to the backend, action + * note this is the old version of the formwrapper that maps to the new one */ const FormWrapper = ( - FormUI: (props: T.FormUIProps) => JSX.Element, + FormUIInput: (props: FormUIProps) => JSX.Element, shape: Validation.Type.Shape, asyncCall?: (data: FormShape) => Promise, - { resetAfterSubmit = true }: { resetAfterSubmit?: boolean } = {} + { resetAfterSubmit = true }: Partial = {} ) => ({ data = { options: {}, dataIn: {} }, onSuccess, - onErrors + onErrors, }: FormWrapperProps): JSX.Element => { - type FormErrors = T.FormErrorsGeneric; - const [form, setForm] = React.useState>(data.dataIn); - const [errors, setErrors] = React.useState({}); - const [loading, setLoading] = React.useState(false); - - const handleSubmit = async (e: React.FormEvent) => { - e.preventDefault(); - - const validation = Validation.Main.checkObject(form, shape) as FormErrors; - - setErrors(validation); - - if (isA(form, validation)) { - // here call the backend - setLoading(true); - try { - const out = asyncCall && (await asyncCall(form)); - setLoading(false); - resetAfterSubmit && setForm({}); // this lines comes before onSuccess, else it creates an error/warning - onSuccess && onSuccess(form, out); - } catch (err) { - if (onErrors) { - const { errors } = onErrors(err, form); - - if (errors) { - setErrors(errors); - setLoading(false); - } - } - } - } - }; - - return ( -
- - - ); + const clientValidationFunction = (form: any) => + Validation.Main.checkObject(form, shape) as any; + + return F2({ + onSuccess, + onErrors, + clientValidationFunction, + FormUI: FormUIInput, + asyncCall, + }); }; export default FormWrapper; diff --git a/src/lib/form/generator/ui.tsx b/src/lib/form/generator/ui.tsx index bd4f3be..21bef6b 100644 --- a/src/lib/form/generator/ui.tsx +++ b/src/lib/form/generator/ui.tsx @@ -32,7 +32,7 @@ const FormUIGenerator = disabled={loading} placeholder={item.placeholder} errors={errors[item.name]} - options={options[item.name]} + options={options && options[item.name]} /> ); diff --git a/src/lib/form/generator2/index.tsx b/src/lib/form/generator2/index.tsx index 74529f2..67e22c1 100644 --- a/src/lib/form/generator2/index.tsx +++ b/src/lib/form/generator2/index.tsx @@ -2,10 +2,12 @@ import React from "react"; import * as T from "./type"; import { isNotPartial } from "./utils"; +import { FormErrorsGeneric } from "../type"; interface FormWrapperProps { - onSuccess: (formData: A, asyncCallResponse?: B) => void; - clientValidationFunction?:
(_a: Partial) => T.FormErrors; + onSuccess?: (formData: A, asyncCallResponse?: B) => void; + onErrors?: (err: any, data: A) => { errors?: FormErrorsGeneric }; + clientValidationFunction?: (form: Partial) => T.FormErrors; asyncCall?: (formData: A) => Promise; FormUI: (props: T.FormUIProps) => JSX.Element; errors?: T.FormErrors; @@ -48,7 +50,7 @@ export const FormWrapper = ({ setLoading(true); asyncCall(formData) .then((response) => { - onSuccess(formData, response); + onSuccess && onSuccess(formData, response); }) .catch((x) => setErrors(x)) .finally(() => setLoading(false)); @@ -56,14 +58,14 @@ export const FormWrapper = ({ return; } - onSuccess(formData); + onSuccess && onSuccess(formData); }; return ( @@ -97,9 +99,9 @@ export const generateFormUI = - props.setFormData({ ...props.formData, [name]: value }) + props.setForm({ ...props.form, [name]: value }) } /> diff --git a/src/lib/form/generator2/type.ts b/src/lib/form/generator2/type.ts index a5962fe..9b6b00c 100644 --- a/src/lib/form/generator2/type.ts +++ b/src/lib/form/generator2/type.ts @@ -1,3 +1,5 @@ +import { FormErrorsGeneric } from "../type"; + export type FormErrors = { [k in keyof A]?: string; }; @@ -5,9 +7,10 @@ export type FormErrors = { export interface FormUIProps { loading: boolean; onSubmit: (e: React.FormEvent) => void; - errors: FormErrors; - formData: Partial; - setFormData: (x: Partial) => void; + errors: FormErrorsGeneric; + form: Partial; + setForm: (x: Partial) => void; + options?: { [k in keyof A]?: { id: number | string; name: string }[] }; children?: JSX.Element; } diff --git a/src/lib/form/type.ts b/src/lib/form/type.ts index 2bb5a90..ba1d5bc 100644 --- a/src/lib/form/type.ts +++ b/src/lib/form/type.ts @@ -1,20 +1,26 @@ import { Render } from "../view"; // compare with https://github.com/nexys-system/react-bootstrap-components/blob/master/src/components/headless/form/type.ts -export type FormErrorsGeneric = { [k in keyof A]?: string[] }; +export type FormErrorsGeneric = { [k in keyof A]?: string }; + +export type FormOptionSets = { + [k in keyof A]?: { id: number | string; name: string }[]; +}; export interface FormUIProps { loading: boolean; form: Partial; errors: FormErrorsGeneric; - options: { [k in keyof FormShape]?: { id: number | string; name: string }[] }; + options?: { + [k in keyof FormShape]?: { id: number | string; name: string }[]; + }; setForm: (f: Partial) => void; } export interface InputProps { value?: A; onChange: (a?: A) => void; - errors?: string[]; + errors?: string; disabled?: boolean; placeholder?: string; options?: { id: A; name: string }[]; @@ -23,7 +29,7 @@ export interface InputProps { export interface InputWrapperProps { label?: string; children: JSX.Element; - errors?: string[]; + errors?: string; info?: string; } diff --git a/src/lib/form/utils.ts b/src/lib/form/utils.ts index 75720c5..8a960e7 100644 --- a/src/lib/form/utils.ts +++ b/src/lib/form/utils.ts @@ -10,7 +10,7 @@ export const enumToOptions = (keys: { export const isA = ( a: Partial, - formErrors: { [k in keyof A]?: string[] } + formErrors: { [k in keyof A]?: string } ): a is A => Object.keys(formErrors).length === 0; const getType = (uiType: FormUIType) => { From 8b7646482e01cb987c6e0a2cbdcf68520743dc52 Mon Sep 17 00:00:00 2001 From: Johan Date: Sun, 14 Jan 2024 10:35:38 +0100 Subject: [PATCH 2/9] added options --- src/components/form/inputs.tsx | 33 ++++++---- src/examples/form/generated.tsx | 27 ++++++++ src/examples/form/index.tsx | 35 +---------- src/examples/form/ui.tsx | 4 +- src/examples/simple-list/list-form/form.tsx | 30 ++++----- src/examples/simple-list/list-form/index.tsx | 22 +++---- .../superadmin/user/authentication/body.tsx | 2 +- .../superadmin/user/authentication/form.tsx | 6 +- src/examples/toggle/index.tsx | 2 +- src/lib/form/form-wrapper.tsx | 10 +-- src/lib/form/generator/ui.tsx | 16 ++--- src/lib/form/type.ts | 62 ++++++++++++++++++- src/lib/toggle/index.tsx | 1 - 13 files changed, 147 insertions(+), 103 deletions(-) diff --git a/src/components/form/inputs.tsx b/src/components/form/inputs.tsx index c1df546..e11502a 100644 --- a/src/components/form/inputs.tsx +++ b/src/components/form/inputs.tsx @@ -22,17 +22,17 @@ export const getClassName = ( export const InputWrapper = ({ label, children, - errors, + error, }: T.InputWrapperProps) => (
{children} - {errors && ( + {error && (
- {errors} + {error}
)}
@@ -77,13 +77,13 @@ export const Textarea = ({ /> ); -export const Select =
({ +export const Select = ({ onChange, options, value, errors, disabled, -}: T.InputProps) => ( +}: T.InputProps) => ( setForm({ ...form, [item.name]: val })} disabled={loading} placeholder={item.placeholder} errors={errors[item.name]} - options={options && options[item.name]} + options={item.options && item.options} /> ); diff --git a/src/lib/form/type.ts b/src/lib/form/type.ts index ba1d5bc..3326ec1 100644 --- a/src/lib/form/type.ts +++ b/src/lib/form/type.ts @@ -17,20 +17,47 @@ export interface FormUIProps { setForm: (f: Partial) => void; } -export interface InputProps { +export interface FormUIProp2s { + loading: boolean; + form: Partial; + errors: FormErrorsGeneric; + options?: FormOptionSets; + setForm: (x: Partial) => void; + onSubmit: (e: React.FormEvent) => void; + children?: JSX.Element; +} + +export interface InputProps { value?: A; onChange: (a?: A) => void; errors?: string; disabled?: boolean; placeholder?: string; - options?: { id: A; name: string }[]; + options?: OptionUnit[]; +} + +export interface OptionUnit { + id: Id; + name: string; +} + +export interface InputOptionProps + extends InputProps { + options: OptionUnit[]; } export interface InputWrapperProps { label?: string; + error?: string; + info?: string; children: JSX.Element; - errors?: string; +} + +export interface WrapperProps { + label?: string; + error?: string; info?: string; + children: JSX.Element; } export enum FormUIType { @@ -58,8 +85,37 @@ export interface FormDef extends StructureUnitCore { optional: boolean; disabled?: boolean; placeholder?: string; + options?: OptionUnit[]; } export interface FormViewDef extends FormDef { render?: Render; } + +export type FormErrors = { + [k in keyof A]?: string; +}; + +export interface SubmitButtonProps { + disabled?: boolean; + loading?: boolean; +} + +export interface BackButtonProps { + onClick: () => void; +} + +export enum UXType { + text = 1, + number = 2, + checkbox = 3, +} + +export interface FormDefUnit { + uxType: UXType; + label: string; + description?: string; + name: keyof A; + placeholder?: string; + info?: string; +} diff --git a/src/lib/toggle/index.tsx b/src/lib/toggle/index.tsx index a684624..aa581ce 100644 --- a/src/lib/toggle/index.tsx +++ b/src/lib/toggle/index.tsx @@ -37,7 +37,6 @@ const ToggleHeadless = return (
{ setData(d); setIsForm(false); From 4a8f7dedb6f270b7c6815245df32d0a1b3539504 Mon Sep 17 00:00:00 2001 From: Johan Date: Sun, 14 Jan 2024 10:51:17 +0100 Subject: [PATCH 3/9] migration finished --- src/examples/form/ui.tsx | 5 +- src/examples/simple-list/list-add/form.tsx | 27 ++++---- src/examples/simple-list/list-form/form.tsx | 5 +- .../superadmin/user/authentication/form.tsx | 5 +- src/lib/form/form-wrapper.tsx | 3 +- src/lib/form/generator/ui.tsx | 12 +++- .../form/generator2/components/buttons.tsx | 2 +- src/lib/form/generator2/components/index.tsx | 10 +-- src/lib/form/generator2/components/inputs.tsx | 8 +-- src/lib/form/generator2/index.tsx | 4 +- src/lib/form/generator2/type.ts | 62 ------------------- src/lib/form/generator2/utils.ts | 6 -- src/lib/form/type.ts | 9 +-- src/lib/form/utils.ts | 10 +-- 14 files changed, 52 insertions(+), 116 deletions(-) delete mode 100644 src/lib/form/generator2/type.ts delete mode 100644 src/lib/form/generator2/utils.ts diff --git a/src/examples/form/ui.tsx b/src/examples/form/ui.tsx index 73d445a..d1616bc 100644 --- a/src/examples/form/ui.tsx +++ b/src/examples/form/ui.tsx @@ -8,9 +8,10 @@ const FormUI = ({ setForm, loading, errors, + onSubmit, }: T.FormUIProps) => { return ( - <> + Send - + ); }; diff --git a/src/examples/simple-list/list-add/form.tsx b/src/examples/simple-list/list-add/form.tsx index d5252e9..dcb1b69 100644 --- a/src/examples/simple-list/list-add/form.tsx +++ b/src/examples/simple-list/list-add/form.tsx @@ -1,9 +1,9 @@ -import React from 'react'; -import { FormUIProps } from '../../../lib/form/type'; +import React from "react"; +import { FormUIProps } from "../../../lib/form/type"; -import * as Inputs from '../../../components/form/inputs'; -import FormWrapper from '../../../lib/form/form-wrapper'; -import Icon from '../../../components/icon'; +import * as Inputs from "../../../components/form/inputs"; +import FormWrapper from "../../../lib/form/form-wrapper"; +import Icon from "../../../components/icon"; export interface FormDataShape { name: string; @@ -13,20 +13,21 @@ const FormUI = ({ form, setForm, loading, - errors + onSubmit, + errors, }: FormUIProps) => { return ( - <> +
setForm({ ...form, name })} + onChange={(name) => setForm({ ...form, name })} disabled={loading} - placeholder={'Name'} - errors={errors['name']} + placeholder={"Name"} + errors={errors["name"]} />
- +
); }; @@ -44,7 +45,7 @@ const apiCall = async (a: FormDataShape) => Promise.resolve(Math.random() * 100); const Form = FormWrapper(FormUI, shape, apiCall, { - resetAfterSubmit: true + resetAfterSubmit: true, }); export default Form; diff --git a/src/examples/simple-list/list-form/form.tsx b/src/examples/simple-list/list-form/form.tsx index 05d7af7..1c64861 100644 --- a/src/examples/simple-list/list-form/form.tsx +++ b/src/examples/simple-list/list-form/form.tsx @@ -15,9 +15,10 @@ const FormUI = ({ setForm, loading, errors, + onSubmit, }: FormUIProps) => { return ( - <> +
Add - + ); }; diff --git a/src/examples/superadmin/user/authentication/form.tsx b/src/examples/superadmin/user/authentication/form.tsx index 2b523a6..93eb372 100644 --- a/src/examples/superadmin/user/authentication/form.tsx +++ b/src/examples/superadmin/user/authentication/form.tsx @@ -15,9 +15,10 @@ const FormUI = ({ setForm, loading, errors, + onSubmit, }: FormUIProps) => { return ( - <> +
Add - + ); }; diff --git a/src/lib/form/form-wrapper.tsx b/src/lib/form/form-wrapper.tsx index 789d12f..09dc25f 100644 --- a/src/lib/form/form-wrapper.tsx +++ b/src/lib/form/form-wrapper.tsx @@ -5,7 +5,6 @@ import * as T from "./type"; import * as Validation from "@nexys/validation"; import { FormWrapper as F2 } from "./generator2"; -import { FormUIProps } from "./generator2/type"; export interface FormWrapperProps { onSuccess?: (data: A, out?: Out) => void; @@ -26,7 +25,7 @@ interface FormWrapperOptions { */ const FormWrapper = ( - FormUIInput: (props: FormUIProps) => JSX.Element, + FormUIInput: (props: T.FormUIProps) => JSX.Element, shape: Validation.Type.Shape, asyncCall?: (data: FormShape) => Promise, { resetAfterSubmit = true }: Partial = {} diff --git a/src/lib/form/generator/ui.tsx b/src/lib/form/generator/ui.tsx index 5d6e374..db54bc7 100644 --- a/src/lib/form/generator/ui.tsx +++ b/src/lib/form/generator/ui.tsx @@ -12,9 +12,15 @@ export interface FormUIGeneratorProps { const FormUIGenerator = ({ InputGeneric, InputWrapper, Button }: FormUIGeneratorProps) => (def: T.FormDef
[]) => - ({ form, setForm, loading, errors }: T.FormUIProps): JSX.Element => { + ({ + form, + setForm, + loading, + errors, + onSubmit, + }: T.FormUIProps): JSX.Element => { return ( - <> +
{def.map((item, i) => { const Input = InputGeneric(item.uiType); @@ -35,7 +41,7 @@ const FormUIGenerator = })}
From 590b83de2e30799f044591cd386cb6e4925769a7 Mon Sep 17 00:00:00 2001 From: Johan Date: Mon, 15 Jan 2024 19:53:58 +0100 Subject: [PATCH 6/9] moved everything --- src/app.tsx | 2 +- src/components/buttons/with-action.tsx | 58 ++++++- src/examples/form/free-ui.tsx | 143 ++++++++++++++++ src/examples/form/generated-form.tsx | 78 +++++++++ src/examples/form/index.tsx | 27 ++- src/examples/form/ui-generate.tsx | 162 ++++++++++++++++++ .../form/generator2/components/buttons.tsx | 58 ------- src/lib/form/generator2/components/index.tsx | 112 ------------ src/lib/form/generator2/components/inputs.tsx | 131 -------------- .../form/generator2/components/spinner.tsx | 13 -- src/lib/form/generator2/components/type.ts | 59 ------- src/lib/form/generator2/components/utils.ts | 6 - src/lib/form/generator2/index.tsx | 4 +- src/lib/form/type.ts | 8 +- src/links.ts | 6 +- 15 files changed, 472 insertions(+), 395 deletions(-) create mode 100644 src/examples/form/free-ui.tsx create mode 100644 src/examples/form/generated-form.tsx create mode 100644 src/examples/form/ui-generate.tsx delete mode 100644 src/lib/form/generator2/components/buttons.tsx delete mode 100644 src/lib/form/generator2/components/index.tsx delete mode 100644 src/lib/form/generator2/components/inputs.tsx delete mode 100644 src/lib/form/generator2/components/spinner.tsx delete mode 100644 src/lib/form/generator2/components/type.ts delete mode 100644 src/lib/form/generator2/components/utils.ts diff --git a/src/app.tsx b/src/app.tsx index 3ce0731..7755728 100644 --- a/src/app.tsx +++ b/src/app.tsx @@ -50,7 +50,7 @@ export default () => ( } /> } /> } /> - } /> + } /> } /> } /> } /> diff --git a/src/components/buttons/with-action.tsx b/src/components/buttons/with-action.tsx index f6b6904..bfd4a04 100644 --- a/src/components/buttons/with-action.tsx +++ b/src/components/buttons/with-action.tsx @@ -1,6 +1,7 @@ import React from "react"; - +import Spinner from "../spinner"; import BtnWithActionGeneric, { BtnProps } from "../../lib/buttons/with-action"; +import { SubmitButtonProps } from "../../lib/form/type"; const Btn = ({ children, disabled, onClick }: BtnProps) => { return ( @@ -19,3 +20,58 @@ const Btn = ({ children, disabled, onClick }: BtnProps) => { const BtnWithAction = BtnWithActionGeneric(Btn); export default BtnWithAction; + +const getButtonClassName = (context?: "primary") => { + const classBase = "m-2 px-4 py-2 border rounded-md text-sm font-medium "; + + if (context === "primary") { + return ( + classBase + + "border-transparent rounded-md text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 disabled:bg-blue-300" + ); + } + return classBase + "hover:bg-gray-100 px-4 py-2"; +}; + +export const Button = ({ + onClick, + context, + disabled, + type, + children, +}: { + type?: "submit"; + disabled?: boolean; + context?: "primary"; + onClick?: () => void; + children: string | JSX.Element; +}) => { + const className = getButtonClassName(context); + + return ( + + ); +}; + +export const SubmitButton = ({ disabled, loading }: SubmitButtonProps) => { + if (loading) { + return ; + } + + return ( + + ); +}; + +export const BackButton = ({ onClick }: { onClick: () => void }) => ( + +); diff --git a/src/examples/form/free-ui.tsx b/src/examples/form/free-ui.tsx new file mode 100644 index 0000000..3f59516 --- /dev/null +++ b/src/examples/form/free-ui.tsx @@ -0,0 +1,143 @@ +import React from "react"; + +import * as T from "../../lib/form/type"; + +import { + Input, + Checkbox as InputCheckbox, + SelectObject as InputOptions, + Select as InputOptionsScalar, + InputWrapper as Wrapper, +} from "../../components/form/inputs"; +import { FormWrapper } from "../../lib/form/generator2"; +import { SubmitButton } from "../../components/buttons/with-action"; + +enum UIStyle { + card = 1, + list = 2, +} + +interface FormData { + name: string; + isUuid: boolean; + country: { id: number; name: string }; + lang: { id: string; name: string }; + uiStyle: UIStyle; +} + +const countries: T.OptionUnit[] = [ + { id: 1, name: "Switzerland" }, + { id: 2, name: "Australia" }, +]; + +const langs: T.OptionUnit[] = [ + { id: "en", name: "English" }, + { id: "fr", name: "French" }, +]; + +const uiStyles: T.OptionUnit[] = Object.keys(UIStyle) + .filter((x) => !isNaN(x as any)) + .map((id) => ({ + id: id as any as UIStyle, + name: UIStyle[id as any as UIStyle], + })); + +const FormUI = ({ + onSubmit, + errors, + form, + setForm, + loading, +}: T.FormUIProps) => { + console.log(errors); + return ( +
+ + setForm({ ...form, name })} + /> + + + setForm({ ...form, isUuid })} + /> + + + + options={countries} + value={form["country"]} + onChange={(country) => setForm({ ...form, country })} + /> + + + + options={langs} + value={form["lang"]} + onChange={(lang) => setForm({ ...form, lang })} + /> + + + + + options={uiStyles} + value={form["uiStyle"]} + onChange={(uiStyle) => setForm({ ...form, uiStyle })} + /> + + + + + ); +}; + +const clientValidationFunction = ( + v: Partial +): T.FormErrors => { + console.log(v); + + const e: T.FormErrors = {}; + + if (!("name" in v) || v["name"] === "") { + e.name = "wth dude"; + } + + if (!("isUuid" in v) || v.isUuid === false) { + e.isUuid = "wth dude"; + } + + return e; +}; + +const asyncCall = () => + new Promise((r, f) => { + setTimeout(() => { + console.log("sdf"); + f({ country: "sdf" }); + }, 2000); + }); + +const FreeUI = () => { + return ( + + ); +}; +export default FreeUI; diff --git a/src/examples/form/generated-form.tsx b/src/examples/form/generated-form.tsx new file mode 100644 index 0000000..ff01ab4 --- /dev/null +++ b/src/examples/form/generated-form.tsx @@ -0,0 +1,78 @@ +import React from "react"; + +import * as T from "../../lib/form/type"; + +import { FormWrapper, generateFormUI } from "../../lib/form/generator2/index"; +import { + InputGeneric, + InputWrapper as Wrapper, +} from "../../components/form/inputs"; +import { SubmitButton } from "../../components/buttons/with-action"; + +enum UIStyle { + card = 1, + list = 2, +} + +interface FormData { + name: string; + isUuid: boolean; + country: { id: number; name: string }; + lang: { id: string; name: string }; + uiStyle: UIStyle; +} + +const clientValidationFunction = ( + v: Partial +): T.FormErrors => { + console.log(v); + + const e: T.FormErrors = {}; + + if (!("name" in v) || v["name"] === "") { + e.name = "wth dude"; + } + + if (!("isUuid" in v) || v.isUuid === false) { + e.isUuid = "wth dude"; + } + + return e; +}; + +const asyncCall = () => + new Promise((r, f) => { + setTimeout(() => { + console.log("sdf"); + f({ country: "sdf" }); + }, 2000); + }); + +const formDef: T.FormDefUnit[] = [ + { + name: "name", + label: "Name", + uxType: T.FormUIType.Text, + placeholder: "add something here", + }, + { + name: "isUuid", + label: "Is Uuid", + uxType: T.FormUIType.Switch, + placeholder: "add something here", + }, +]; + +const GeneratedForm = () => { + return ( + <> + + + ); +}; +export default GeneratedForm; diff --git a/src/examples/form/index.tsx b/src/examples/form/index.tsx index 8a63e12..d471190 100644 --- a/src/examples/form/index.tsx +++ b/src/examples/form/index.tsx @@ -7,12 +7,16 @@ import FormUI from "./ui"; import FormGenerated from "./generated"; import { apiCall, onSuccess, cartoonCharacters, shape } from "./utils"; +import FreeUI from "./free-ui"; +import GeneratedForm from "./generated-form"; +import FormDefs from "./ui-generate"; const Form = FormWrapper(FormUI, shape, apiCall); export default () => ( <> -

Form

+

Form

+

Form Simple

Form demo.  @@ -24,10 +28,27 @@ export default () => (

({ errors })} /> -
+
-

Form Generated

+

Form Generated

({ errors })} /> +
+ +

Form Free UI

+ + + +
+ +

Generated Form

+ + + +
+ +

UI for Form Generation

+ + ); diff --git a/src/examples/form/ui-generate.tsx b/src/examples/form/ui-generate.tsx new file mode 100644 index 0000000..24970a3 --- /dev/null +++ b/src/examples/form/ui-generate.tsx @@ -0,0 +1,162 @@ +import React from "react"; + +import * as T from "../../lib/form/type"; + +import { FormWrapper, generateFormUI } from "../../lib/form/generator2"; +import { InputGeneric, InputWrapper } from "../../components/form/inputs"; + +import { SubmitButton, BackButton } from "../../components/buttons/with-action"; + +const formDef2: T.FormDefUnit[] = [ + { + name: "name", + label: "Name", + uxType: T.FormUIType.Text, + placeholder: "Add name", + info: "Name of the attribute", + }, + { + name: "label", + label: "Label", + uxType: T.FormUIType.Text, + placeholder: "Add label", + info: "Label for the attribute", + }, + { + name: "placeholder", + label: "Placeholder", + uxType: T.FormUIType.Text, + placeholder: "placeholder", + }, + { + name: "info", + label: "Info", + uxType: T.FormUIType.Text, + placeholder: "info", + }, + { + name: "uxType", + label: "UX Type", + uxType: T.FormUIType.Switch, + }, +]; + +const FormDefs = () => { + const [formDefs, setFormDefs] = React.useState< + T.FormDefUnit<{ [k: string]: string }>[] + >([]); + const [addForm, setAddForm] = React.useState(false); + + const handleAdd = (a: T.FormDefUnit<{ [k: string]: string }>) => { + setFormDefs([...formDefs, a]); + setAddForm(false); + }; + + const handleRemove = (idx: number) => { + const f = formDefs.filter((_x, i) => i !== idx); + setFormDefs(f); + }; + + return ( +
+
+ {addForm && ( + <> + >(formDef2)} + onSuccess={handleAdd} + > + setAddForm(false)} /> + + + )} + + {!addForm && ( + <> + + + {formDefs.length === 0 && ( +

Click add to start buikding your form

+ )} + {formDefs.length > 0 && ( + <> + + + + + + + + + + + + {formDefs.map((formDef, idx) => ( + + + + + + + + ))} + +
NameLabelPlaceholderInfo
+ {formDef.name} + + {formDef.label} + + {formDef.placeholder} + + {formDef.info} + + +
+ + )} + + )} +
+
+

Generated Form

{" "} + {formDefs.length > 0 && ( + <> + +
+
{JSON.stringify(formDefs, null, 2)}
+ + + )} +
+
+ ); +}; + +export default FormDefs; diff --git a/src/lib/form/generator2/components/buttons.tsx b/src/lib/form/generator2/components/buttons.tsx deleted file mode 100644 index 732238a..0000000 --- a/src/lib/form/generator2/components/buttons.tsx +++ /dev/null @@ -1,58 +0,0 @@ -import React from "react"; -import { SubmitButtonProps } from "../../type"; -import Spinner from "./spinner"; - -const getButtonClassName = (context?: "primary") => { - const classBase = "m-2 px-4 py-2 border rounded-md text-sm font-medium "; - - if (context === "primary") { - return ( - classBase + - "border-transparent rounded-md text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 disabled:bg-blue-300" - ); - } - return classBase + "hover:bg-gray-100 px-4 py-2"; -}; - -export const Button = ({ - onClick, - context, - disabled, - type, - children, -}: { - type?: "submit"; - disabled?: boolean; - context?: "primary"; - onClick?: () => void; - children: string | JSX.Element; -}) => { - const className = getButtonClassName(context); - - return ( - - ); -}; - -export const SubmitButton = ({ disabled, loading }: SubmitButtonProps) => { - if (loading) { - return ; - } - - return ( - - ); -}; - -export const BackButton = ({ onClick }: { onClick: () => void }) => ( - -); diff --git a/src/lib/form/generator2/components/index.tsx b/src/lib/form/generator2/components/index.tsx deleted file mode 100644 index 6931b40..0000000 --- a/src/lib/form/generator2/components/index.tsx +++ /dev/null @@ -1,112 +0,0 @@ -import React from "react"; - -import * as T from "../../type"; -import { isNotPartial } from "./utils"; - -interface FormWrapperProps { - onSuccess: (formData: A, asyncCallResponse?: B) => void; - clientValidationFunction?:
(_a: Partial) => T.FormErrors; - asyncCall?: (formData: A) => Promise; - FormUI: (props: T.FormUIProps) => JSX.Element; - errors?: T.FormErrors; - children?: JSX.Element; -} - -export const FormWrapper = ({ - onSuccess, - clientValidationFunction, - FormUI, - errors: externalErrors, - asyncCall, - children, -}: FormWrapperProps) => { - const [formData, setFormData] = React.useState>({}); - const [errors, setErrors] = React.useState>( - externalErrors || {} - ); - const [loading, setLoading] = React.useState(false); - - const handleSubmit = async (e: React.FormEvent) => { - e.preventDefault(); - - // client validation - const validation = clientValidationFunction - ? clientValidationFunction(formData) - : {}; - - if (!isNotPartial(formData, validation)) { - // errors found - setErrors(validation); - return; - } - - // reset errors - setErrors({}); - // and pass value up - - if (asyncCall) { - setLoading(true); - asyncCall(formData) - .then((response) => { - onSuccess(formData, response); - }) - .catch((x) => setErrors(x)) - .finally(() => setLoading(false)); - - return; - } - - onSuccess(formData); - }; - - return ( - - {children} - - ); -}; - -// - -export const generateFormUI = - ( - InputGeneric: (uxType: T.UXType) => (a: T.InputProps) => JSX.Element, - Wrapper: (a: T.WrapperProps) => JSX.Element, - SubmitButton: (a: T.SubmitButtonProps) => JSX.Element - ) => - (formDef: T.FormDefUnit[]) => - (props: T.FormUIProps): JSX.Element => - ( - - {formDef.map(({ name, uxType, label, placeholder, info }, key) => { - const Input = InputGeneric(uxType); - - return ( - - - props.setForm({ ...props.form, [name]: value }) - } - /> - - ); - })} - - - {props.children} - - ); diff --git a/src/lib/form/generator2/components/inputs.tsx b/src/lib/form/generator2/components/inputs.tsx deleted file mode 100644 index db64170..0000000 --- a/src/lib/form/generator2/components/inputs.tsx +++ /dev/null @@ -1,131 +0,0 @@ -import React from "react"; -import { - InputOptionProps, - InputProps, - OptionUnit, - UXType, - WrapperProps, -} from "../../type"; - -export const Wrapper = ({ label, info, error, children }: WrapperProps) => ( -
-

{label}

- {info} - {children} - {error} -
-); - -export const Input = ({ - value, - onChange, - disabled, - placeholder, -}: InputProps) => ( - onChange(e.target.value)} - className="bg-white border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 disabled:bg-gray-100 disabled:cursor-not-allowed" - /> -); - -export const InputNumber = ({ - value, - onChange, - disabled, -}: InputProps) => ( - { - const v = e.target.value; - - const n = Number(v); - - if (isNaN(n)) { - return; - } - - onChange(n); - }} - /> -); - -export const InputCheckbox = ({ - value, - onChange, - disabled, -}: InputProps) => ( - onChange(e.target.checked)} - /> -); - -export const InputOptions = ({ - value, - onChange, - options, -}: InputOptionProps, any>) => { - const handleChange = (e: React.ChangeEvent) => { - const v = e.target.value; - - const optionSelected = options.find( - ({ id }) => (id as unknown as number).toString() === v - ); - if (optionSelected) { - onChange(optionSelected); - } - - console.log(optionSelected); - }; - - return ( - - ); -}; - -export const InputOptionsScalar = ({ - value, - onChange, - options, -}: InputOptionProps) => { - const f = options.find((x) => x.id === value); - return ( - onChange(v?.id)} - /> - ); -}; - -export const InputGeneric = ( - uxType: UXType -): ((props: InputProps) => JSX.Element) => { - switch (uxType) { - case UXType.checkbox: - return InputCheckbox; - case UXType.number: - return InputNumber; - default: - return Input; - } -}; diff --git a/src/lib/form/generator2/components/spinner.tsx b/src/lib/form/generator2/components/spinner.tsx deleted file mode 100644 index dbcb99e..0000000 --- a/src/lib/form/generator2/components/spinner.tsx +++ /dev/null @@ -1,13 +0,0 @@ -// src/components/Spinner.tsx - -import React from "react"; - -const Spinner: React.FC = () => { - return ( -
-
-
- ); -}; - -export default Spinner; diff --git a/src/lib/form/generator2/components/type.ts b/src/lib/form/generator2/components/type.ts deleted file mode 100644 index a5962fe..0000000 --- a/src/lib/form/generator2/components/type.ts +++ /dev/null @@ -1,59 +0,0 @@ -export type FormErrors
= { - [k in keyof A]?: string; -}; - -export interface FormUIProps { - loading: boolean; - onSubmit: (e: React.FormEvent) => void; - errors: FormErrors; - formData: Partial; - setFormData: (x: Partial) => void; - children?: JSX.Element; -} - -export interface WrapperProps { - label?: string; - info?: string; - error?: string; - children: JSX.Element; -} - -export interface OptionUnit { - id: Id; - name: string; -} - -export interface InputProps { - onChange: (s: T) => void; - value?: T; - placeholder?: string; - disabled?: boolean; -} - -export interface InputOptionProps extends InputProps { - options: OptionUnit[]; -} - -export interface SubmitButtonProps { - disabled?: boolean; - loading?: boolean; -} - -export interface BackButtonProps { - onClick: () => void; -} - -export enum UXType { - text = 1, - number = 2, - checkbox = 3, -} - -export interface FormDefUnit { - uxType: UXType; - label: string; - description?: string; - name: keyof A; - placeholder?: string; - info?: string; -} diff --git a/src/lib/form/generator2/components/utils.ts b/src/lib/form/generator2/components/utils.ts deleted file mode 100644 index e3521ef..0000000 --- a/src/lib/form/generator2/components/utils.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { FormErrors } from "./type"; - -export const isNotPartial = ( - formData: Partial, - formErrors: FormErrors -): formData is A => Object.keys(formErrors).length === 0; diff --git a/src/lib/form/generator2/index.tsx b/src/lib/form/generator2/index.tsx index 548e047..f3a360b 100644 --- a/src/lib/form/generator2/index.tsx +++ b/src/lib/form/generator2/index.tsx @@ -78,7 +78,9 @@ export const FormWrapper = ({ export const generateFormUI = ( - InputGeneric: (uxType: T.UXType) => (a: T.InputProps) => JSX.Element, + InputGeneric: ( + uxType: T.FormUIType + ) => (a: T.InputProps) => JSX.Element, Wrapper: (a: T.WrapperProps) => JSX.Element, SubmitButton: (a: T.SubmitButtonProps) => JSX.Element ) => diff --git a/src/lib/form/type.ts b/src/lib/form/type.ts index c53d322..87ab480 100644 --- a/src/lib/form/type.ts +++ b/src/lib/form/type.ts @@ -100,14 +100,8 @@ export interface BackButtonProps { onClick: () => void; } -export enum UXType { - text = 1, - number = 2, - checkbox = 3, -} - export interface FormDefUnit { - uxType: UXType; + uxType: FormUIType; label: string; description?: string; name: keyof A; diff --git a/src/links.ts b/src/links.ts index 1813a17..16125bf 100644 --- a/src/links.ts +++ b/src/links.ts @@ -11,16 +11,16 @@ export const links: { [menu: string]: { name: string; link: string } } = { buttons: { name: "Buttons", link: "/buttons" }, badge: { name: "Badge", link: "/badge" }, statusChange: { name: "Status Change", link: "/status-change" }, - //download: { name: "Download", link: "/download" }, simpleList: { name: "Simple list", link: "/simple-list" }, toggle: { name: "Toggle", link: "/toggle" }, tabs: { name: "Tabs", link: "/tabs" }, fileUpload: { name: "File Upload", link: "/file-upload" }, detail: { name: "Detail", link: "/detail" }, - //auth: { name: "Auth", link: "/auth" }, + // download: { name: "Download", link: "/download" }, + // auth: { name: "Auth", link: "/auth" }, // superadmin: { name: "Superadmin", link: "/superadmin" }, // crudBrowser: { name: "Crud Browser", link: "/crud-browser" }, - //dateRange: { name: "Date Range", link: "/date-range" }, + // dateRange: { name: "Date Range", link: "/date-range" }, }; export const menus: { name: string; link: string }[] = Object.values(links); From 3aad8006577c7e0178e9b4a1ea6cd18162def507 Mon Sep 17 00:00:00 2001 From: Johan Date: Mon, 15 Jan 2024 20:00:41 +0100 Subject: [PATCH 7/9] harmonize props --- src/examples/form/generated-form.tsx | 12 +++++++++--- src/examples/form/ui-generate.tsx | 18 +++++++++--------- src/lib/form/generator2/index.tsx | 6 +++--- src/lib/form/type.ts | 14 ++++---------- 4 files changed, 25 insertions(+), 25 deletions(-) diff --git a/src/examples/form/generated-form.tsx b/src/examples/form/generated-form.tsx index ff01ab4..8ff855c 100644 --- a/src/examples/form/generated-form.tsx +++ b/src/examples/form/generated-form.tsx @@ -48,19 +48,25 @@ const asyncCall = () => }, 2000); }); -const formDef: T.FormDefUnit[] = [ +const formDef: T.FormDef[] = [ { name: "name", label: "Name", - uxType: T.FormUIType.Text, + uiType: T.FormUIType.Text, placeholder: "add something here", }, { name: "isUuid", label: "Is Uuid", - uxType: T.FormUIType.Switch, + uiType: T.FormUIType.Switch, placeholder: "add something here", }, + { + name: "country", + label: "Country", + uiType: T.FormUIType.SelectObject, + placeholder: "lal", + }, ]; const GeneratedForm = () => { diff --git a/src/examples/form/ui-generate.tsx b/src/examples/form/ui-generate.tsx index 24970a3..4228730 100644 --- a/src/examples/form/ui-generate.tsx +++ b/src/examples/form/ui-generate.tsx @@ -7,47 +7,47 @@ import { InputGeneric, InputWrapper } from "../../components/form/inputs"; import { SubmitButton, BackButton } from "../../components/buttons/with-action"; -const formDef2: T.FormDefUnit[] = [ +const formDef: T.FormDef[] = [ { name: "name", label: "Name", - uxType: T.FormUIType.Text, + uiType: T.FormUIType.Text, placeholder: "Add name", info: "Name of the attribute", }, { name: "label", label: "Label", - uxType: T.FormUIType.Text, + uiType: T.FormUIType.Text, placeholder: "Add label", info: "Label for the attribute", }, { name: "placeholder", label: "Placeholder", - uxType: T.FormUIType.Text, + uiType: T.FormUIType.Text, placeholder: "placeholder", }, { name: "info", label: "Info", - uxType: T.FormUIType.Text, + uiType: T.FormUIType.Text, placeholder: "info", }, { name: "uxType", label: "UX Type", - uxType: T.FormUIType.Switch, + uiType: T.FormUIType.Switch, }, ]; const FormDefs = () => { const [formDefs, setFormDefs] = React.useState< - T.FormDefUnit<{ [k: string]: string }>[] + T.FormDef<{ [k: string]: string }>[] >([]); const [addForm, setAddForm] = React.useState(false); - const handleAdd = (a: T.FormDefUnit<{ [k: string]: string }>) => { + const handleAdd = (a: T.FormDef<{ [k: string]: string }>) => { setFormDefs([...formDefs, a]); setAddForm(false); }; @@ -67,7 +67,7 @@ const FormDefs = () => { InputGeneric, InputWrapper, SubmitButton - )>(formDef2)} + )>(formDef)} onSuccess={handleAdd} > setAddForm(false)} /> diff --git a/src/lib/form/generator2/index.tsx b/src/lib/form/generator2/index.tsx index f3a360b..9bff6da 100644 --- a/src/lib/form/generator2/index.tsx +++ b/src/lib/form/generator2/index.tsx @@ -84,12 +84,12 @@ export const generateFormUI = Wrapper: (a: T.WrapperProps) => JSX.Element, SubmitButton: (a: T.SubmitButtonProps) => JSX.Element ) => - (formDef: T.FormDefUnit[]) => + (formDef: T.FormDef[]) => (props: T.FormUIProps): JSX.Element => (
- {formDef.map(({ name, uxType, label, placeholder, info }, key) => { - const Input = InputGeneric(uxType); + {formDef.map(({ name, uiType, label, placeholder, info }, key) => { + const Input = InputGeneric(uiType); return ( { export interface FormDef extends StructureUnitCore { uiType: FormUIType; - optional: boolean; + optional?: boolean; disabled?: boolean; placeholder?: string; + description?: string; + + info?: string; options?: OptionUnit[]; } @@ -99,12 +102,3 @@ export interface SubmitButtonProps { export interface BackButtonProps { onClick: () => void; } - -export interface FormDefUnit { - uxType: FormUIType; - label: string; - description?: string; - name: keyof A; - placeholder?: string; - info?: string; -} From f1a9d9c795b899846d218f1dbe2d7931cfa0855d Mon Sep 17 00:00:00 2001 From: Johan Date: Mon, 15 Jan 2024 20:19:01 +0100 Subject: [PATCH 8/9] merge form and form legacy --- src/app.tsx | 2 +- src/components/toggle.tsx | 5 +- src/examples/form/free-ui.tsx | 3 +- src/examples/form/generated-form.tsx | 14 ++- src/examples/form/index.tsx | 4 +- src/examples/form/ui-generate.tsx | 16 +-- src/examples/simple-list/list-add/form.tsx | 5 +- src/examples/simple-list/list-form/form.tsx | 5 +- src/examples/superadmin/data.ts | 54 -------- src/examples/superadmin/index.tsx | 3 - .../superadmin/instance/detail/detail.tsx | 15 --- .../superadmin/instance/detail/index.tsx | 38 ------ src/examples/superadmin/instance/index.tsx | 12 -- src/examples/superadmin/instance/list.tsx | 36 ------ src/examples/superadmin/instance/type.ts | 4 - src/examples/superadmin/permissions/index.tsx | 29 ----- .../superadmin/user/authentication/body.tsx | 82 ------------- .../superadmin/user/authentication/form.tsx | 74 ----------- .../superadmin/user/authentication/index.tsx | 30 ----- .../user/authentication/list-item.tsx | 21 ---- .../superadmin/user/authentication/list.tsx | 58 --------- .../superadmin/user/authentication/type.ts | 20 --- .../superadmin/user/detail/access-token.tsx | 33 ----- .../superadmin/user/detail/detail.tsx | 21 ---- src/examples/superadmin/user/detail/index.tsx | 48 -------- .../superadmin/user/detail/permissions.tsx | 29 ----- .../superadmin/user/detail/status.tsx | 23 ---- src/examples/superadmin/user/index.tsx | 15 --- src/examples/superadmin/user/list.tsx | 53 -------- src/examples/superadmin/user/type.ts | 11 -- src/lib/form/form-wrapper.tsx | 75 +++++++++-- src/lib/form/generator/form.tsx | 4 +- src/lib/form/generator/ui.tsx | 13 +- src/lib/form/generator2/index.tsx | 116 ------------------ src/lib/form/type.ts | 13 ++ src/lib/toggle/index.tsx | 5 +- 36 files changed, 122 insertions(+), 867 deletions(-) delete mode 100644 src/examples/superadmin/data.ts delete mode 100644 src/examples/superadmin/index.tsx delete mode 100644 src/examples/superadmin/instance/detail/detail.tsx delete mode 100644 src/examples/superadmin/instance/detail/index.tsx delete mode 100644 src/examples/superadmin/instance/index.tsx delete mode 100644 src/examples/superadmin/instance/list.tsx delete mode 100644 src/examples/superadmin/instance/type.ts delete mode 100644 src/examples/superadmin/permissions/index.tsx delete mode 100644 src/examples/superadmin/user/authentication/body.tsx delete mode 100644 src/examples/superadmin/user/authentication/form.tsx delete mode 100644 src/examples/superadmin/user/authentication/index.tsx delete mode 100644 src/examples/superadmin/user/authentication/list-item.tsx delete mode 100644 src/examples/superadmin/user/authentication/list.tsx delete mode 100644 src/examples/superadmin/user/authentication/type.ts delete mode 100644 src/examples/superadmin/user/detail/access-token.tsx delete mode 100644 src/examples/superadmin/user/detail/detail.tsx delete mode 100644 src/examples/superadmin/user/detail/index.tsx delete mode 100644 src/examples/superadmin/user/detail/permissions.tsx delete mode 100644 src/examples/superadmin/user/detail/status.tsx delete mode 100644 src/examples/superadmin/user/index.tsx delete mode 100644 src/examples/superadmin/user/list.tsx delete mode 100644 src/examples/superadmin/user/type.ts delete mode 100644 src/lib/form/generator2/index.tsx diff --git a/src/app.tsx b/src/app.tsx index 7755728..12517ee 100644 --- a/src/app.tsx +++ b/src/app.tsx @@ -23,7 +23,7 @@ import Detail from "./examples/detail"; import Badge from "./examples/badge"; import Auth from "./examples/auth"; import DateRange from "./examples/date-range"; -import Superadmin from "./examples/superadmin"; + import CrudBrowser from "./examples/crud-browser"; import FormBuilder from "./builder/form"; import TableBuilder from "./builder/table"; diff --git a/src/components/toggle.tsx b/src/components/toggle.tsx index 2a86ad0..7ea8eeb 100644 --- a/src/components/toggle.tsx +++ b/src/components/toggle.tsx @@ -1,8 +1,7 @@ -import { FormWrapperProps } from "../lib/form/form-wrapper"; import { ViewStructureUnit } from "../lib/view"; import ViewGeneric, { toViewStructure } from "../lib/view"; import ToggleHeadless, { LayoutProps } from "../lib/toggle"; -import { FormViewDef } from "../lib/form/type"; +import { FormViewDef, FormWrapperOnActionProps } from "../lib/form/type"; import PreForm from "./form/generator"; import { Row } from "./view"; @@ -34,7 +33,7 @@ const LayoutForm = ({ setIsForm, children }: LayoutProps) => ( const PreToggle = ( structure: ViewStructureUnit[], - Form: (p: FormWrapperProps) => JSX.Element + Form: (p: FormWrapperOnActionProps) => JSX.Element ) => ToggleHeadless(structure, Form, View, LayoutView, LayoutForm); export const ToggleFromDef = ( diff --git a/src/examples/form/free-ui.tsx b/src/examples/form/free-ui.tsx index 3f59516..6b7f730 100644 --- a/src/examples/form/free-ui.tsx +++ b/src/examples/form/free-ui.tsx @@ -9,8 +9,9 @@ import { Select as InputOptionsScalar, InputWrapper as Wrapper, } from "../../components/form/inputs"; -import { FormWrapper } from "../../lib/form/generator2"; + import { SubmitButton } from "../../components/buttons/with-action"; +import { FormWrapper } from "../../lib/form/form-wrapper"; enum UIStyle { card = 1, diff --git a/src/examples/form/generated-form.tsx b/src/examples/form/generated-form.tsx index 8ff855c..3128dc7 100644 --- a/src/examples/form/generated-form.tsx +++ b/src/examples/form/generated-form.tsx @@ -2,12 +2,10 @@ import React from "react"; import * as T from "../../lib/form/type"; -import { FormWrapper, generateFormUI } from "../../lib/form/generator2/index"; -import { - InputGeneric, - InputWrapper as Wrapper, -} from "../../components/form/inputs"; +import { FormWrapper } from "../../lib/form/form-wrapper"; +import { InputGeneric, InputWrapper } from "../../components/form/inputs"; import { SubmitButton } from "../../components/buttons/with-action"; +import FormUIGenerator from "../../lib/form/generator/ui"; enum UIStyle { card = 1, @@ -73,7 +71,11 @@ const GeneratedForm = () => { return ( <> (FormUI, shape, apiCall); +const Form = FormWrapperLegacy(FormUI, shape, apiCall); export default () => ( <> diff --git a/src/examples/form/ui-generate.tsx b/src/examples/form/ui-generate.tsx index 4228730..449b802 100644 --- a/src/examples/form/ui-generate.tsx +++ b/src/examples/form/ui-generate.tsx @@ -2,10 +2,12 @@ import React from "react"; import * as T from "../../lib/form/type"; -import { FormWrapper, generateFormUI } from "../../lib/form/generator2"; import { InputGeneric, InputWrapper } from "../../components/form/inputs"; import { SubmitButton, BackButton } from "../../components/buttons/with-action"; +import { FormWrapper } from "../../lib/form/form-wrapper"; +import FormGenerator from "../../lib/form/generator/form"; +import FormUIGenerator from "../../lib/form/generator/ui"; const formDef: T.FormDef[] = [ { @@ -63,11 +65,11 @@ const FormDefs = () => { {addForm && ( <> >(formDef)} + Button: SubmitButton, + })>(formDef)} onSuccess={handleAdd} > setAddForm(false)} /> @@ -136,11 +138,11 @@ const FormDefs = () => { {formDefs.length > 0 && ( <>
diff --git a/src/examples/simple-list/list-add/form.tsx b/src/examples/simple-list/list-add/form.tsx index dcb1b69..4340673 100644 --- a/src/examples/simple-list/list-add/form.tsx +++ b/src/examples/simple-list/list-add/form.tsx @@ -2,8 +2,9 @@ import React from "react"; import { FormUIProps } from "../../../lib/form/type"; import * as Inputs from "../../../components/form/inputs"; -import FormWrapper from "../../../lib/form/form-wrapper"; + import Icon from "../../../components/icon"; +import { FormWrapperLegacy } from "../../../lib/form/form-wrapper"; export interface FormDataShape { name: string; @@ -44,7 +45,7 @@ const shape = { name: {} }; const apiCall = async (a: FormDataShape) => Promise.resolve(Math.random() * 100); -const Form = FormWrapper(FormUI, shape, apiCall, { +const Form = FormWrapperLegacy(FormUI, shape, apiCall, { resetAfterSubmit: true, }); diff --git a/src/examples/simple-list/list-form/form.tsx b/src/examples/simple-list/list-form/form.tsx index 1c64861..e3b77e6 100644 --- a/src/examples/simple-list/list-form/form.tsx +++ b/src/examples/simple-list/list-form/form.tsx @@ -2,8 +2,9 @@ import React from "react"; import { FormUIProps } from "../../../lib/form/type"; import * as Inputs from "../../../components/form/inputs"; -import FormWrapper from "../../../lib/form/form-wrapper"; + import Icon from "../../../components/icon"; +import { FormWrapperLegacy } from "../../../lib/form/form-wrapper"; export interface FormDataShape { name: string; @@ -56,6 +57,6 @@ const apiCall = async (a: FormDataShape) => { return Promise.resolve(Math.random() * 100); }; -const Form = FormWrapper(FormUI, shape, apiCall); +const Form = FormWrapperLegacy(FormUI, shape, apiCall); export default Form; diff --git a/src/examples/superadmin/data.ts b/src/examples/superadmin/data.ts deleted file mode 100644 index 9b74783..0000000 --- a/src/examples/superadmin/data.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { delay } from "../../lib/utils"; -import { Instance } from "./instance/type"; -import { User } from "./user/type"; - -export const getPermissions = (uuid: string) => { - const dataIn = [ - { id: 1, assigned: false, name: "App" }, - { id: 2, assigned: true, name: "Admin" }, - { id: 2, assigned: true, name: "Superadmin" }, - ]; - - return Promise.resolve(dataIn); -}; - -export const updatePermission = async (id: number) => { - await delay(1000); - return { uuid: id }; -}; - -export const InstanceData = { - getDetail: (uuid: string): Promise => - Promise.resolve({ uuid, name: "my instance " + uuid }), - getList: () => { - const d: Instance[] = [ - { uuid: "u1", name: "Instance #1" }, - { uuid: "u2", name: "Instance #2" }, - ]; - return Promise.resolve(d); - }, - - update: (data: Instance) => { - return Promise.resolve(); - }, -}; - -export const UserData = { - getList: () => { - const d: User[] = [ - { uuid: "u1", firstName: "John", lastName: "Doe" }, - { uuid: "u2", firstName: "Jane", lastName: "Doe" }, - ]; - return Promise.resolve(d); - }, - getDetail: (uuid: string) => { - return Promise.resolve({ - uuid, - firstName: "my fn " + uuid, - lastName: "my ln ", - }); - }, - update: (data: User) => { - return Promise.resolve({ uuid: "i2" }); - }, -}; diff --git a/src/examples/superadmin/index.tsx b/src/examples/superadmin/index.tsx deleted file mode 100644 index 28d5076..0000000 --- a/src/examples/superadmin/index.tsx +++ /dev/null @@ -1,3 +0,0 @@ -import Instance from "./instance"; - -export default Instance; diff --git a/src/examples/superadmin/instance/detail/detail.tsx b/src/examples/superadmin/instance/detail/detail.tsx deleted file mode 100644 index acf9f55..0000000 --- a/src/examples/superadmin/instance/detail/detail.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import { ToggleFromDef } from "../../../../components/toggle"; -import { FormUIType, FormViewDef } from "../../../../lib/form/type"; -import { InstanceData } from "../../data"; -import { Instance } from "../type"; - -const def: FormViewDef[] = [ - { - label: "Name", - name: "name", - uiType: FormUIType.Text, - optional: false, - }, -]; - -export default ToggleFromDef(def, InstanceData.update); diff --git a/src/examples/superadmin/instance/detail/index.tsx b/src/examples/superadmin/instance/detail/index.tsx deleted file mode 100644 index 250b9c7..0000000 --- a/src/examples/superadmin/instance/detail/index.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import { useParams } from "react-router-dom"; - -import Layout from "../../../../components/layout"; -import LoadDataAsync from "../../../../components/load-data-async"; -import links from "../../../../links"; -import { Instance } from "../type"; -import Permissions from "../../permissions"; -import User from "../../user"; - -import Detail from "./detail"; -import { InstanceData } from "../../data"; - -export default () => { - const { uuid } = useParams<{ uuid?: string }>(); - - if (typeof uuid !== "string") { - throw Error("expected a uuid in the url"); - } - - const L = Layout({ - cards: { - Instance: [{ Component: ({ data }) => }], - User: [ - { Component: ({ data }) => , width: 12 }, - ], - Permissions: [ - { Component: ({ data: { uuid } }) => }, - ], - }, - title: "Instance" + uuid, - backRedirect: links.superadmin.link, - pathPrefix: links.superadmin.link + `/${uuid}/detail`, - }); - - return ( - InstanceData.getDetail(uuid)} /> - ); -}; diff --git a/src/examples/superadmin/instance/index.tsx b/src/examples/superadmin/instance/index.tsx deleted file mode 100644 index 4d46075..0000000 --- a/src/examples/superadmin/instance/index.tsx +++ /dev/null @@ -1,12 +0,0 @@ -import { Route, Routes } from "react-router-dom"; -import List from "./list"; -import Detail from "./detail"; - -export default () => { - return ( - - } /> - } /> - - ); -}; diff --git a/src/examples/superadmin/instance/list.tsx b/src/examples/superadmin/instance/list.tsx deleted file mode 100644 index c19d48b..0000000 --- a/src/examples/superadmin/instance/list.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import { Link } from "react-router-dom"; -import LoadDataAsync from "../../../components/load-data-async"; -import List from "../../../components/table"; -import { Definition } from "../../../lib/types"; -import links from "../../../links"; -import { InstanceData } from "../data"; -import { Instance } from "./type"; - -const def: Definition = [ - { name: "name", label: "Country name" }, - { - name: "uuid", - label: "", - render: (x) => ( - Edit - ), - }, -]; - -const InstanceList = ({ data }: { data: Instance[] }): JSX.Element => ( - -); - -const Layout = ({ children }: { children: JSX.Element }) => ( - <> -

Instances

- - {children} - -); - -export default () => ( - - - -); diff --git a/src/examples/superadmin/instance/type.ts b/src/examples/superadmin/instance/type.ts deleted file mode 100644 index afb493b..0000000 --- a/src/examples/superadmin/instance/type.ts +++ /dev/null @@ -1,4 +0,0 @@ -export interface Instance { - uuid: string; - name: string; -} diff --git a/src/examples/superadmin/permissions/index.tsx b/src/examples/superadmin/permissions/index.tsx deleted file mode 100644 index f7d281e..0000000 --- a/src/examples/superadmin/permissions/index.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import React from "react"; - -import ListAssign from "../../../components/list-assign"; -import LoadDataAsync from "../../../components/load-data-async"; -import { Item } from "../../../lib/list-assign"; -import { getPermissions, updatePermission } from "../data"; - -const ListAssign2 = ({ data: dataIn }: { data: Item[] }) => { - const [data, setData] = React.useState(dataIn); - - const onUpdate = async (id: number) => { - await updatePermission(id); - const d = data.map((d) => { - if (d.id === id) { - return { ...d, assigned: !d.assigned }; - } - - return d; - }); - - setData([...d]); - }; - - return data={data} updatePromise={onUpdate} />; -}; - -export default ({ uuid }: { uuid: string }) => ( - getPermissions(uuid)} /> -); diff --git a/src/examples/superadmin/user/authentication/body.tsx b/src/examples/superadmin/user/authentication/body.tsx deleted file mode 100644 index 3288f4c..0000000 --- a/src/examples/superadmin/user/authentication/body.tsx +++ /dev/null @@ -1,82 +0,0 @@ -import React from "react"; -import Icon from "../../../../components/icon"; - -import Form from "./form"; -import List from "./list"; -import { FormDataShape, UserAuthentication } from "./type"; - -const Body = ({ data: dataIn }: { data: UserAuthentication[] }) => { - const [data, setData] = React.useState(dataIn); - - const [isInsert, setIsInsert] = React.useState(false); - const [edit, setEdit] = React.useState< - { data: FormDataShape; uuid: string } | undefined - >(undefined); - - const handleRemove = (uuid: string) => { - if (confirm("Are you sure you would like to delete that entry?")) { - setData(data.filter((x) => x.uuid !== uuid)); - } - }; - - const handleSuccess = (d: FormDataShape, uuid?: string) => { - if (!uuid) { - throw Error("can't be edited"); - } - const item: UserAuthentication = { ...d, uuid }; - setData([...data, item]); - setIsInsert(false); - }; - - const handleEdit = (d: UserAuthentication) => { - const { uuid, ...data } = d; - // const data: FormDataShape = { name: d.title, description: d.subtitle }; - setEdit({ data, uuid }); - }; - - const handleSuccessEdit = (d: FormDataShape) => { - if (!edit) { - throw Error("can't be edited"); - } - const { uuid } = edit; - if (!uuid) { - throw Error("can't be edited"); - } - - const newData = data.map((x) => { - if (x.uuid === uuid) { - return { ...d, uuid }; - } - - return x; - }); - setData([...newData]); - setEdit(undefined); - }; - - if (edit) { - // note: when connecting to an api the update call should be different to the insert call, maybe needs two forms - return ; - } - - if (isInsert) { - return ; - } - - return ( - <> -

Authentication

- -
- - - ); -}; - -export default Body; diff --git a/src/examples/superadmin/user/authentication/form.tsx b/src/examples/superadmin/user/authentication/form.tsx deleted file mode 100644 index 93eb372..0000000 --- a/src/examples/superadmin/user/authentication/form.tsx +++ /dev/null @@ -1,74 +0,0 @@ -import { FormUIProps } from "../../../../lib/form/type"; - -import * as Inputs from "../../../../components/form/inputs"; -import FormWrapper from "../../../../lib/form/form-wrapper"; -import Icon from "../../../../components/icon"; -import { FormDataShape, shape, UserAuthenticationType } from "./type"; - -const userAuthentiactionOptions: { - id: UserAuthenticationType; - name: string; -}[] = [{ id: UserAuthenticationType.password, name: "password" }]; - -const FormUI = ({ - form, - setForm, - loading, - errors, - onSubmit, -}: FormUIProps) => { - return ( - - - setForm({ ...form, value })} - disabled={loading} - placeholder={"Value"} - errors={errors["value"]} - /> - - - - setForm({ ...form, type })} - disabled={loading} - placeholder={"Type"} - errors={errors["type"]} - /> - - - - setForm({ ...form, isEnabled })} - disabled={loading} - placeholder={"Is Enabled"} - errors={errors["isEnabled"]} - /> - - - - ); -}; - -const addUserAuthentication = async (a: FormDataShape): Promise => { - console.log("calling API"); - return Promise.resolve(String(Math.random() * 100)); -}; - -const Form = FormWrapper( - FormUI, - shape, - addUserAuthentication -); - -export default Form; diff --git a/src/examples/superadmin/user/authentication/index.tsx b/src/examples/superadmin/user/authentication/index.tsx deleted file mode 100644 index 6c34782..0000000 --- a/src/examples/superadmin/user/authentication/index.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import { UserAuthentication, UserAuthenticationType } from "./type"; -import LoadDataAsync from "../../../../components/load-data-async"; - -import Body from "./body"; - -const getUserAuthentications = (uuid: string) => { - const data: UserAuthentication[] = [ - { - uuid: "u1", - value: "myemail@gmail.com", - type: UserAuthenticationType.password, - isEnabled: true, - }, - { - uuid: "u2", - value: "myghusername", - type: UserAuthenticationType.password, - isEnabled: false, - }, - ]; - - return Promise.resolve(data); -}; - -export default ({ uuid }: { uuid: string }) => ( - getUserAuthentications(uuid)} - Component={Body} - /> -); diff --git a/src/examples/superadmin/user/authentication/list-item.tsx b/src/examples/superadmin/user/authentication/list-item.tsx deleted file mode 100644 index 410555d..0000000 --- a/src/examples/superadmin/user/authentication/list-item.tsx +++ /dev/null @@ -1,21 +0,0 @@ -const ListItem = ({ - title, - children, - right, -}: { - title: string; - children?: JSX.Element; - right?: JSX.Element; -}) => { - return ( -
  • -
    -
    {title}
    - {children} -
    - {right} -
  • - ); -}; - -export default ListItem; diff --git a/src/examples/superadmin/user/authentication/list.tsx b/src/examples/superadmin/user/authentication/list.tsx deleted file mode 100644 index edd5304..0000000 --- a/src/examples/superadmin/user/authentication/list.tsx +++ /dev/null @@ -1,58 +0,0 @@ -import Icon from "../../../../components/icon"; -import { yesOrNo } from "../../../../lib/utils"; -import ListItem from "./list-item"; -import { UserAuthentication, UserAuthenticationType } from "./type"; - -const List = ({ - data, - onRemove, - onEdit, -}: { - data: UserAuthentication[]; - onRemove: (uuid: string) => void; - onEdit: (d: UserAuthentication) => void; -}) => { - if (data.length === 0) { - return ( -

    - Nothing was found -

    - ); - } - - return ( -
      - {data.map((d, i) => ( - - onEdit(d)} - style={{ cursor: "pointer" }} - className="badge bg-warning rounded-pill" - > - - -   - onRemove(d.uuid)} - style={{ cursor: "pointer" }} - className="badge bg-danger rounded-pill" - > - - - - } - > - <> - {UserAuthenticationType[d.type]} {yesOrNo(d.isEnabled)} - - - ))} -
    - ); -}; - -export default List; diff --git a/src/examples/superadmin/user/authentication/type.ts b/src/examples/superadmin/user/authentication/type.ts deleted file mode 100644 index b10a25a..0000000 --- a/src/examples/superadmin/user/authentication/type.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { Shape } from "@nexys/validation/dist/type"; - -export enum UserAuthenticationType { - password = 1, -} - -export interface UserAuthentication { - uuid: string; - value: string; - type: UserAuthenticationType; - isEnabled: boolean; -} - -export const shape: Shape = { - value: {}, - type: { type: "number" }, - isEnabled: { type: "boolean" }, -}; - -export type FormDataShape = Omit; diff --git a/src/examples/superadmin/user/detail/access-token.tsx b/src/examples/superadmin/user/detail/access-token.tsx deleted file mode 100644 index f3fbcd8..0000000 --- a/src/examples/superadmin/user/detail/access-token.tsx +++ /dev/null @@ -1,33 +0,0 @@ -import LoadDataAsync from "../../../../components/load-data-async"; -import List from "../../../../components/table"; -import { Definition } from "../../../../lib/types"; -import { AccessToken } from "../type"; - -const getAccessTokens = (user: { uuid: string }) => { - const d: AccessToken[] = [ - { uuid: "u1", token: "t1", date: "2021-23-23" }, - { uuid: "u2", token: "t2", date: "2021-23-23" }, - ]; - return Promise.resolve(d); -}; - -const def: Definition = [ - { name: "token", label: "Token" }, - { name: "date", label: "Date" }, - { - name: "uuid", - label: "", - render: (x) => , - }, -]; - -const TokenList = ({ data }: { data: AccessToken[] }): JSX.Element => ( - -); - -export default ({ user }: { user: { uuid: string } }) => ( - } - getData={() => getAccessTokens(user)} - /> -); diff --git a/src/examples/superadmin/user/detail/detail.tsx b/src/examples/superadmin/user/detail/detail.tsx deleted file mode 100644 index 928cd93..0000000 --- a/src/examples/superadmin/user/detail/detail.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import { ToggleFromDef } from "../../../../components/toggle"; -import { FormUIType, FormViewDef } from "../../../../lib/form/type"; -import { UserData } from "../../data"; -import { User } from "../type"; - -const def: FormViewDef[] = [ - { - label: "First Name", - name: "firstName", - uiType: FormUIType.Text, - optional: false, - }, - { - label: "Last Name", - name: "lastName", - uiType: FormUIType.Text, - optional: false, - }, -]; - -export default ToggleFromDef(def, UserData.update); diff --git a/src/examples/superadmin/user/detail/index.tsx b/src/examples/superadmin/user/detail/index.tsx deleted file mode 100644 index a85004c..0000000 --- a/src/examples/superadmin/user/detail/index.tsx +++ /dev/null @@ -1,48 +0,0 @@ -import { useParams } from "react-router-dom"; - -import Layout from "../../../../components/layout"; -import LoadDataAsync from "../../../../components/load-data-async"; -import links from "../../../../links"; -import { User } from "../type"; - -import Detail from "./detail"; -import StatusChange from "./status"; -import Permissions from "./permissions"; -import AccessToken from "./access-token"; -import Authentication from "../authentication"; -import { UserData } from "../../data"; - -export default ({ instance }: { instance: { uuid: string } }) => { - const { uuid } = useParams<{ uuid?: string }>(); - - if (typeof uuid !== "string") { - throw Error("expected a uuid in the url"); - } - - const baseUrl = links.superadmin.link + `/${instance.uuid}/detail/user`; - - const L = Layout({ - cards: { - User: [ - { Component: ({ data }) => }, - { - Component: ({ data }) => ( - - ), - }, - { Component: ({ data }) => }, - ], - Permissions: [ - { Component: ({ data }) => }, - { Component: ({ data }) => }, - ], - }, - title: "User" + uuid, - backRedirect: baseUrl, - pathPrefix: baseUrl + `/${uuid}/detail`, - }); - - return ( - UserData.getDetail(uuid)} /> - ); -}; diff --git a/src/examples/superadmin/user/detail/permissions.tsx b/src/examples/superadmin/user/detail/permissions.tsx deleted file mode 100644 index 28e9477..0000000 --- a/src/examples/superadmin/user/detail/permissions.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import React from "react"; - -import ListAssign from "../../../../components/list-assign"; -import LoadDataAsync from "../../../../components/load-data-async"; -import { Item } from "../../../../lib/list-assign"; -import { getPermissions, updatePermission } from "../../data"; - -const ListAssign2 = ({ data: dataIn }: { data: Item[] }) => { - const [data, setData] = React.useState(dataIn); - - const onUpdate = async (id: number) => { - await updatePermission(id); - const d = data.map((d) => { - if (d.id === id) { - return { ...d, assigned: !d.assigned }; - } - - return d; - }); - - setData([...d]); - }; - - return data={data} updatePromise={onUpdate} />; -}; - -export default ({ uuid }: { uuid: string }) => ( - getPermissions(uuid)} /> -); diff --git a/src/examples/superadmin/user/detail/status.tsx b/src/examples/superadmin/user/detail/status.tsx deleted file mode 100644 index 2c47b62..0000000 --- a/src/examples/superadmin/user/detail/status.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import StatusChange from "../../../../components/status-change"; - -const status = [ - { id: 1, label: "active" }, - { id: 2, label: "pending" }, - { id: 3, label: "inactive" }, -]; - -const statusChange = (uuid: string, statusId: number) => { - return Promise.resolve(); -}; - -export default ({ uuid, statusId }: { uuid: string; statusId: number }) => { - return ( - <> - statusChange(uuid, s)} - /> - - ); -}; diff --git a/src/examples/superadmin/user/index.tsx b/src/examples/superadmin/user/index.tsx deleted file mode 100644 index 85e6cb8..0000000 --- a/src/examples/superadmin/user/index.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import { Route, Routes } from "react-router-dom"; -import List from "./list"; -import Detail from "./detail"; - -export default ({ instance }: { instance: { uuid: string } }) => { - return ( - - } /> - } - /> - - ); -}; diff --git a/src/examples/superadmin/user/list.tsx b/src/examples/superadmin/user/list.tsx deleted file mode 100644 index 4d0a287..0000000 --- a/src/examples/superadmin/user/list.tsx +++ /dev/null @@ -1,53 +0,0 @@ -import { Link } from "react-router-dom"; -import LoadDataAsync from "../../../components/load-data-async"; -import List from "../../../components/table"; -import { Definition } from "../../../lib/types"; -import links from "../../../links"; -import { UserData } from "../data"; -import { User } from "./type"; - -const def = (instance: { uuid: string }): Definition => [ - { name: "firstName", label: "First Name" }, - { name: "lastName", label: "last Name" }, - { - name: "uuid", - label: "", - render: (x) => ( - - Edit - - ), - }, -]; - -const UserList = ({ - data, - instance, -}: { - data: User[]; - instance: { uuid: string }; -}): JSX.Element => ( - -); - -const Layout = ({ children }: { children: JSX.Element }) => ( - <> -

    Users

    - - {children} - -); - -export default ({ instance }: { instance: { uuid: string } }) => ( - - } - getData={UserData.getList} - /> - -); diff --git a/src/examples/superadmin/user/type.ts b/src/examples/superadmin/user/type.ts deleted file mode 100644 index f475c0f..0000000 --- a/src/examples/superadmin/user/type.ts +++ /dev/null @@ -1,11 +0,0 @@ -export interface User { - uuid: string; - firstName: string; - lastName: string; -} - -export interface AccessToken { - uuid: string; - token: string; - date: string; -} diff --git a/src/lib/form/form-wrapper.tsx b/src/lib/form/form-wrapper.tsx index 09dc25f..30e9b4e 100644 --- a/src/lib/form/form-wrapper.tsx +++ b/src/lib/form/form-wrapper.tsx @@ -3,13 +3,67 @@ import React from "react"; import * as T from "./type"; import * as Validation from "@nexys/validation"; +import { isNotPartial } from "./utils"; -import { FormWrapper as F2 } from "./generator2"; +export const FormWrapper = ({ + onSuccess, + clientValidationFunction, + FormUI, + errors: externalErrors, + asyncCall, + children, +}: T.FormWrapperProps) => { + const [formData, setFormData] = React.useState>({}); + const [errors, setErrors] = React.useState>( + externalErrors || {} + ); + const [loading, setLoading] = React.useState(false); -export interface FormWrapperProps { - onSuccess?: (data: A, out?: Out) => void; - onErrors?: (err: any, data: A) => { errors?: T.FormErrorsGeneric
    }; -} + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + + // client validation + const validation = clientValidationFunction + ? clientValidationFunction(formData) + : {}; + + if (!isNotPartial(formData, validation)) { + // errors found + setErrors(validation); + return; + } + + // reset errors + setErrors({}); + // and pass value up + + if (asyncCall) { + setLoading(true); + asyncCall(formData) + .then((response) => { + onSuccess && onSuccess(formData, response); + }) + .catch((x) => setErrors(x)) + .finally(() => setLoading(false)); + + return; + } + + onSuccess && onSuccess(formData); + }; + + return ( + + {children} + + ); +}; interface FormWrapperOptions { resetAfterSubmit: boolean; @@ -23,18 +77,21 @@ interface FormWrapperOptions { * @param onSuccess [optional]: after call to the backend, action * note this is the old version of the formwrapper that maps to the new one */ -const FormWrapper = +export const FormWrapperLegacy = ( FormUIInput: (props: T.FormUIProps) => JSX.Element, shape: Validation.Type.Shape, asyncCall?: (data: FormShape) => Promise, { resetAfterSubmit = true }: Partial = {} ) => - ({ onSuccess, onErrors }: FormWrapperProps): JSX.Element => { + ({ + onSuccess, + onErrors, + }: T.FormWrapperOnActionProps): JSX.Element => { const clientValidationFunction = (form: any) => Validation.Main.checkObject(form, shape) as any; - return F2({ + return FormWrapper({ onSuccess, onErrors, clientValidationFunction, @@ -42,5 +99,3 @@ const FormWrapper = asyncCall, }); }; - -export default FormWrapper; diff --git a/src/lib/form/generator/form.tsx b/src/lib/form/generator/form.tsx index 97227e5..cdcc7eb 100644 --- a/src/lib/form/generator/form.tsx +++ b/src/lib/form/generator/form.tsx @@ -1,8 +1,8 @@ import * as T from "../type"; import { defToShape } from "../utils"; +import { FormWrapperLegacy } from "../form-wrapper"; import FormUIGenerator, { FormUIGeneratorProps } from "./ui"; -import FormWrapper from "../form-wrapper"; const FormGenerator = (p: FormUIGeneratorProps) => @@ -14,7 +14,7 @@ const FormGenerator = const shape = defToShape(def); const ui = FormUIGenerator(p)(def); - return FormWrapper(ui, shape, asyncCall, options); + return FormWrapperLegacy(ui, shape, asyncCall, options); }; export default FormGenerator; diff --git a/src/lib/form/generator/ui.tsx b/src/lib/form/generator/ui.tsx index db54bc7..7300f9f 100644 --- a/src/lib/form/generator/ui.tsx +++ b/src/lib/form/generator/ui.tsx @@ -6,7 +6,7 @@ export interface FormUIGeneratorProps { InputGeneric: ( uiType: T.FormUIType ) => (p: T.InputProps) => JSX.Element; // here cast to any to avoid types issue, if coded properly this should not cause any issues - Button: () => JSX.Element; + Button: (a: T.SubmitButtonProps) => JSX.Element; } const FormUIGenerator = @@ -18,6 +18,7 @@ const FormUIGenerator = loading, errors, onSubmit, + children, }: T.FormUIProps): JSX.Element => { return (
    @@ -27,7 +28,12 @@ const FormUIGenerator = const value = form[item.name]; return ( - + setForm({ ...form, [item.name]: val })} @@ -40,7 +46,8 @@ const FormUIGenerator = ); })} -