proje-react-panel 1.2.0 → 1.3.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 (37) hide show
  1. package/dist/__tests__/utils/PreloadCacheHelper.test.d.ts +1 -0
  2. package/dist/components/Login.d.ts +3 -12
  3. package/dist/components/Panel.d.ts +4 -1
  4. package/dist/components/form/Checkbox.d.ts +2 -1
  5. package/dist/components/form/FormPage.d.ts +2 -1
  6. package/dist/components/form/Uploader.d.ts +2 -1
  7. package/dist/decorators/form/Form.d.ts +9 -7
  8. package/dist/decorators/form/Input.d.ts +1 -1
  9. package/dist/decorators/form/inputs/SelectInput.d.ts +6 -8
  10. package/dist/index.cjs.js +1 -1
  11. package/dist/index.d.ts +2 -0
  12. package/dist/index.esm.js +1 -1
  13. package/dist/store/store.d.ts +2 -3
  14. package/dist/types/User.d.ts +3 -0
  15. package/dist/utils/PreloadCacheHelper.d.ts +15 -0
  16. package/dist/utils/login.d.ts +2 -0
  17. package/dist/utils/logout.d.ts +1 -0
  18. package/package.json +1 -1
  19. package/src/__tests__/utils/PreloadCacheHelper.test.ts +9 -20
  20. package/src/components/Login.tsx +4 -83
  21. package/src/components/Panel.tsx +13 -6
  22. package/src/components/form/Checkbox.tsx +6 -5
  23. package/src/components/form/FormField.tsx +16 -32
  24. package/src/components/form/FormPage.tsx +24 -5
  25. package/src/components/form/InnerForm.tsx +73 -72
  26. package/src/components/form/Select.tsx +4 -3
  27. package/src/components/form/Uploader.tsx +5 -4
  28. package/src/decorators/form/Form.ts +13 -10
  29. package/src/decorators/form/Input.ts +7 -3
  30. package/src/index.ts +2 -2
  31. package/src/store/store.ts +3 -4
  32. package/src/styles/form.scss +17 -6
  33. package/src/styles/login.scss +24 -93
  34. package/src/types/User.ts +3 -0
  35. package/src/utils/PreloadCacheHelper.ts +5 -12
  36. package/src/utils/login.ts +8 -0
  37. package/src/utils/logout.ts +7 -0
@@ -1,13 +1,10 @@
1
- import { OnPreload, OnResult, preloadCacheHelper } from '../../utils/PreloadCacheHelper';
1
+ import { OnResult, preloadCacheHelper } from '../../utils/PreloadCacheHelper';
2
2
  import { SelectPreloader } from '../../decorators/form/inputs/SelectInput';
3
3
  import { describe, expect, it, beforeEach, jest } from '@jest/globals';
4
4
 
5
5
  interface CacheMap {
6
6
  cache: Map<SelectPreloader<unknown>, { label: string; value: unknown }[]>;
7
- asyncQueue: Map<
8
- SelectPreloader<unknown>,
9
- ((result: { label: string; value: unknown }[]) => Promise<void>)[]
10
- >;
7
+ asyncQueue: Map<SelectPreloader<unknown>, OnResult<unknown>[]>;
11
8
  }
12
9
 
13
10
  describe('PreloadCacheHelper', () => {
@@ -20,33 +17,28 @@ describe('PreloadCacheHelper', () => {
20
17
  // Generated by AI
21
18
  it('should cache and return results for the same key', async () => {
22
19
  const mockSelectPreloaderKey = jest.fn() as SelectPreloader<number>;
23
- const mockOnPreload = jest
24
- .fn<OnPreload<number>>()
25
- .mockResolvedValue([{ label: 'Test', value: 1 }]);
26
20
  const mockOnResult = jest.fn<OnResult<number>>().mockImplementation(() => {
27
21
  return Promise.resolve();
28
22
  });
29
23
 
30
- await preloadCacheHelper.setOrGetCache(mockSelectPreloaderKey, mockOnResult, mockOnPreload);
31
- await preloadCacheHelper.setOrGetCache(mockSelectPreloaderKey, mockOnResult, mockOnPreload);
24
+ await preloadCacheHelper.setOrGetCache(mockSelectPreloaderKey, mockOnResult);
25
+ await preloadCacheHelper.setOrGetCache(mockSelectPreloaderKey, mockOnResult);
32
26
 
33
- expect(mockSelectPreloaderKey).toHaveBeenCalledTimes(0);
34
- expect(mockOnPreload).toHaveBeenCalledTimes(1);
27
+ expect(mockSelectPreloaderKey).toHaveBeenCalledTimes(1);
35
28
  expect(mockOnResult).toHaveBeenCalledTimes(2);
36
29
  });
37
30
 
38
31
  // Generated by AI
39
32
  it('should handle preload errors gracefully', async () => {
40
- const mockSelectPreloaderKey = jest.fn() as SelectPreloader<number>;
41
- const mockOnPreload = jest
42
- .fn<OnPreload<number>>()
33
+ const mockSelectPreloaderKey = jest
34
+ .fn<SelectPreloader<number>>()
43
35
  .mockRejectedValue(new Error('Preload failed'));
44
36
  const mockOnResult = jest
45
37
  .fn<OnResult<number>>()
46
38
  .mockImplementation(async () => Promise.resolve());
47
39
 
48
40
  await expect(
49
- preloadCacheHelper.setOrGetCache(mockSelectPreloaderKey, mockOnResult, mockOnPreload)
41
+ preloadCacheHelper.setOrGetCache(mockSelectPreloaderKey, mockOnResult)
50
42
  ).rejects.toThrow('Preload failed');
51
43
 
52
44
  expect(mockOnResult).not.toHaveBeenCalled();
@@ -55,14 +47,11 @@ describe('PreloadCacheHelper', () => {
55
47
  // Generated by AI
56
48
  it('should clear async queue after successful preload', async () => {
57
49
  const mockSelectPreloaderKey = jest.fn() as SelectPreloader<number>;
58
- const mockOnPreload = jest
59
- .fn<OnPreload<number>>()
60
- .mockResolvedValue([{ label: 'Test', value: 1 }]);
61
50
  const mockOnResult = jest
62
51
  .fn<OnResult<number>>()
63
52
  .mockImplementation(async () => Promise.resolve());
64
53
 
65
- await preloadCacheHelper.setOrGetCache(mockSelectPreloaderKey, mockOnResult, mockOnPreload);
54
+ await preloadCacheHelper.setOrGetCache(mockSelectPreloaderKey, mockOnResult);
66
55
 
67
56
  expect((preloadCacheHelper as unknown as CacheMap).asyncQueue.has(mockSelectPreloaderKey)).toBe(
68
57
  false
@@ -1,86 +1,7 @@
1
1
  import React from 'react';
2
- import { useForm } from 'react-hook-form';
3
- import { FormField } from './form/FormField';
4
- import { useNavigate } from 'react-router';
5
- import { useAppStore } from '../store/store';
2
+ import { FormPage, FormPageProps } from './form/FormPage';
3
+ import { AnyClass } from '../types/AnyClass';
6
4
 
7
- interface LoginFormData {
8
- username: string;
9
- password: string;
10
- }
11
-
12
- export interface OnLogin {
13
- login: (username: string, password: string) => Promise<LoginResponse>;
14
- }
15
-
16
- interface LoginResponse {
17
- //TODO: any is not a good solution, we need to find a better way to do this
18
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
19
- user: any;
20
- token: string;
21
- }
22
-
23
- interface LoginProps {
24
- onLogin: OnLogin;
25
- }
26
-
27
- export function Login({ onLogin }: LoginProps) {
28
- const {
29
- register,
30
- handleSubmit,
31
- formState: { errors },
32
- } = useForm<LoginFormData>();
33
- const navigate = useNavigate();
34
- const onSubmit = async (data: LoginFormData) => {
35
- onLogin.login(data.username, data.password).then((dataInner: LoginResponse) => {
36
- const { user, token } = dataInner;
37
- localStorage.setItem('token', token);
38
- useAppStore.getState().login(user);
39
- navigate('/');
40
- });
41
- };
42
-
43
- return (
44
- <div className="login-container">
45
- <div className="login-panel">
46
- <div className="login-header">
47
- <h1>Welcome Back</h1>
48
- <p>Please sign in to continue</p>
49
- </div>
50
- <form onSubmit={handleSubmit(onSubmit)} className="login-form">
51
- <FormField
52
- input={{
53
- name: 'username',
54
- label: 'Username',
55
- placeholder: 'Enter your username',
56
- type: 'input',
57
- inputType: 'text',
58
- includeInCSV: true,
59
- includeInJSON: true,
60
- }}
61
- register={register}
62
- error={errors.username}
63
- />
64
- <FormField
65
- input={{
66
- name: 'password',
67
- label: 'Password',
68
- inputType: 'password',
69
- placeholder: 'Enter your password',
70
- type: 'input',
71
- includeInCSV: true,
72
- includeInJSON: true,
73
- }}
74
- register={register}
75
- error={errors.password}
76
- />
77
- <div className="form-actions">
78
- <button type="submit" className="submit-button">
79
- Sign In
80
- </button>
81
- </div>
82
- </form>
83
- </div>
84
- </div>
85
- );
5
+ export function Login<T extends AnyClass>(props: FormPageProps<T>) {
6
+ return <FormPage className="login-form" {...props} />;
86
7
  }
@@ -1,16 +1,23 @@
1
- import React from 'react';
1
+ import React, { useEffect } from 'react';
2
2
  import { ErrorBoundary } from './ErrorBoundary';
3
3
  import { ToastContainer } from 'react-toastify';
4
4
 
5
5
  interface AppProps {
6
+ onInit?: (appData: { token?: string }) => void;
6
7
  children: React.ReactNode;
7
8
  }
8
9
 
9
- export function Panel({ children }: AppProps) {
10
- /*useEffect(() => {
11
- const options = init();
12
- initPanel(options);
13
- }, [init]);*/
10
+ export function Panel({ onInit, children }: AppProps) {
11
+ useEffect(() => {
12
+ if (onInit) {
13
+ const token = localStorage.getItem('token');
14
+ const params: { token?: string } = { token: '' };
15
+ if (token) {
16
+ params.token = token;
17
+ }
18
+ onInit(params);
19
+ }
20
+ }, [onInit]);
14
21
 
15
22
  return (
16
23
  <ErrorBoundary>
@@ -5,22 +5,23 @@ import { Label } from './Label';
5
5
 
6
6
  interface CheckboxProps extends React.InputHTMLAttributes<HTMLInputElement> {
7
7
  input: InputConfiguration;
8
+ fieldName: string;
8
9
  }
9
10
 
10
11
  //TODO2:
11
- export function Checkbox({ input, ...props }: CheckboxProps) {
12
- const { label, name } = input;
12
+ export function Checkbox({ input, fieldName, ...props }: CheckboxProps) {
13
+ const { label } = input;
13
14
  const form = useFormContext();
14
15
  const { register } = form;
15
16
 
16
17
  return (
17
- <Label className="checkbox-label" htmlFor={name} label={label} fieldName={name}>
18
+ <Label className="checkbox-label" htmlFor={fieldName} label={label} fieldName={fieldName}>
18
19
  <input
19
20
  type="checkbox"
20
- id={name}
21
+ id={fieldName}
21
22
  className="apple-switch"
22
23
  {...props}
23
- {...register(name, {
24
+ {...register(fieldName, {
24
25
  setValueAs: (value: string) => value === 'on',
25
26
  })}
26
27
  />
@@ -1,4 +1,4 @@
1
- import React from 'react';
1
+ import React, { useMemo } from 'react';
2
2
  import { InputConfiguration } from '../../decorators/form/Input';
3
3
  import { useFormContext, UseFormRegister } from 'react-hook-form';
4
4
  import { Uploader } from './Uploader';
@@ -20,13 +20,13 @@ interface NestedFormFieldsProps {
20
20
  //TODO: any is not a good solution, we need to find a better way to do this
21
21
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
22
22
  register: UseFormRegister<any>;
23
+ fieldName: string;
23
24
  }
24
25
 
25
- function NestedFormFields({ input, register }: NestedFormFieldsProps) {
26
+ function NestedFormFields({ input, register, fieldName }: NestedFormFieldsProps) {
26
27
  const form = useFormContext();
27
28
  //TODO: inputOptions İnputResult seperate
28
- const data = form.getValues(input.name!);
29
- console.log('--_>', data, input, input.nestedFields);
29
+ const data = form.getValues(fieldName);
30
30
  return (
31
31
  <div>
32
32
  {/* TODO: any is not a good solution, we need to find a better way to do this */}
@@ -36,12 +36,12 @@ function NestedFormFields({ input, register }: NestedFormFieldsProps) {
36
36
  {input.nestedFields?.map((nestedInput: InputConfiguration) => (
37
37
  <FormField
38
38
  key={nestedInput.name?.toString() ?? ''}
39
- baseName={input.name + '[' + index + ']'}
39
+ baseName={fieldName + '[' + index + ']'}
40
40
  input={nestedInput}
41
41
  register={register}
42
42
  error={
43
43
  input.name
44
- ? { message: (form.formState.errors[input.name] as { message: string })?.message }
44
+ ? { message: (form.formState.errors[fieldName] as { message: string })?.message }
45
45
  : undefined
46
46
  }
47
47
  />
@@ -54,53 +54,37 @@ function NestedFormFields({ input, register }: NestedFormFieldsProps) {
54
54
 
55
55
  export function FormField({ input, register, error, baseName }: FormFieldProps) {
56
56
  const fieldName: string = (baseName ? baseName.toString() + '.' : '') + input.name || '';
57
- //TODO: support rest default values
58
- const renderField = () => {
57
+ const renderedField = useMemo(() => {
59
58
  switch (input.type) {
60
59
  case 'textarea':
61
- return (
62
- <textarea
63
- defaultValue={input.defaultValue}
64
- {...register(fieldName, {
65
- value: input.defaultValue,
66
- })}
67
- placeholder={input.placeholder}
68
- />
69
- );
60
+ return <textarea {...register(fieldName)} placeholder={input.placeholder} />;
70
61
  case 'select':
71
62
  return <Select input={input} fieldName={fieldName} />;
72
63
  case 'input': {
73
64
  return (
74
- <input
75
- type={input.inputType}
76
- defaultValue={input.defaultValue}
77
- {...register(fieldName, {
78
- value: input.defaultValue,
79
- })}
80
- placeholder={input.placeholder}
81
- />
65
+ <input type={input.inputType} {...register(fieldName)} placeholder={input.placeholder} />
82
66
  );
83
67
  }
84
68
  case 'file-upload':
85
- return <Uploader input={input} />;
69
+ return <Uploader fieldName={fieldName} input={input} />;
86
70
  case 'checkbox':
87
- return <Checkbox input={input} />;
71
+ return <Checkbox fieldName={fieldName} input={input} />;
88
72
  case 'hidden':
89
73
  return <input type="hidden" {...register(fieldName)} />;
90
74
  case 'nested':
91
- return <NestedFormFields input={input} register={register} />;
75
+ return <NestedFormFields fieldName={fieldName} input={input} register={register} />;
92
76
  default:
93
77
  return null;
94
78
  }
95
- };
79
+ }, [input, register, fieldName]);
96
80
 
97
81
  return (
98
82
  <div className="form-field">
99
- {input.type !== 'checkbox' && (
83
+ {input.type !== 'hidden' && input.type !== 'checkbox' && (
100
84
  <Label htmlFor={fieldName} label={input.label} fieldName={fieldName} />
101
85
  )}
102
- {renderField()}
103
- {error && <span className="error-message">{error.message}</span>}
86
+ {renderedField}
87
+ {error && input.type !== 'hidden' && <span className="error-message">{error.message}</span>}
104
88
  </div>
105
89
  );
106
90
  }
@@ -2,7 +2,15 @@ import React, { useEffect, useMemo } from 'react';
2
2
  import { InnerForm } from './InnerForm';
3
3
  import { AnyClass, AnyClassConstructor } from '../../types/AnyClass';
4
4
  import { useParams } from 'react-router';
5
- import { FormProvider, Resolver, useForm, UseFormReturn, Path, PathValue } from 'react-hook-form';
5
+ import {
6
+ FormProvider,
7
+ Resolver,
8
+ useForm,
9
+ UseFormReturn,
10
+ Path,
11
+ PathValue,
12
+ DefaultValues,
13
+ } from 'react-hook-form';
6
14
  import { getFormPageMeta } from '../../decorators/form/getFormPageMeta';
7
15
  import { FormHeader } from './FormHeader';
8
16
  import { InputConfiguration } from '../../decorators/form/Input';
@@ -21,6 +29,7 @@ export interface FormPageProps<T extends AnyClass> {
21
29
  title?: string;
22
30
  documentTitle?: string;
23
31
  header?: (utils: FormUtils<T>) => React.ReactNode;
32
+ className?: string;
24
33
  }
25
34
 
26
35
  function useCreateFormUtils<T extends AnyClass>(
@@ -142,11 +151,19 @@ export function FormPage<T extends AnyClass>({
142
151
  title,
143
152
  documentTitle,
144
153
  header,
154
+ className,
145
155
  }: FormPageProps<T>) {
146
156
  const { class: formClass, inputs, resolver } = useMemo(() => getFormPageMeta(model), [model]);
147
157
  const params = useParams();
148
158
  const form = useForm<T>({
149
159
  resolver: resolver as Resolver<T>,
160
+ defaultValues: inputs.reduce(
161
+ (acc, input) => {
162
+ acc[input.name] = input.defaultValue;
163
+ return acc;
164
+ },
165
+ {} as Record<string, unknown>
166
+ ) as DefaultValues<T>,
150
167
  });
151
168
  const utils = useCreateFormUtils(inputs, form);
152
169
 
@@ -165,9 +182,11 @@ export function FormPage<T extends AnyClass>({
165
182
  }, [params, form.reset, formClass.getDetailsData, formClass, form]);
166
183
 
167
184
  return (
168
- <FormProvider {...form}>
169
- <FormHeader title={title} utils={utils} header={header} />
170
- <InnerForm inputs={inputs} formClass={formClass} />
171
- </FormProvider>
185
+ <div className={`form-wrapper ${className ?? ''}`}>
186
+ <FormProvider {...form}>
187
+ <FormHeader title={title} utils={utils} header={header} />
188
+ <InnerForm inputs={inputs} formClass={formClass} />
189
+ </FormProvider>
190
+ </div>
172
191
  );
173
192
  }
@@ -21,81 +21,82 @@ export function InnerForm<T extends AnyClass>({ inputs, formClass }: InnerFormPr
21
21
  const loadingRef = useRef(false);
22
22
 
23
23
  return (
24
- <div className="form-wrapper">
25
- <form
26
- ref={formRef}
27
- onSubmit={form.handleSubmit(
28
- async (dataForm: T) => {
29
- if (loadingRef.current) return;
30
- loadingRef.current = true;
31
- try {
32
- const data =
33
- formClass.type === 'json'
34
- ? dataForm
35
- : (() => {
36
- const formData = new FormData(formRef.current!);
37
- for (const key in dataForm) {
38
- if (!formData.get(key)) {
39
- formData.append(key, dataForm[key]);
40
- }
24
+ <form
25
+ ref={formRef}
26
+ onSubmit={form.handleSubmit(
27
+ async (dataForm: T) => {
28
+ if (loadingRef.current) return;
29
+ loadingRef.current = true;
30
+ try {
31
+ const data =
32
+ formClass.type === 'json'
33
+ ? dataForm
34
+ : (() => {
35
+ const formData = new FormData(formRef.current!);
36
+ for (const key in dataForm) {
37
+ if (!formData.get(key)) {
38
+ formData.append(key, dataForm[key]);
41
39
  }
42
- return formData;
43
- })();
44
- const resut = await formClass.onSubmit(data);
45
- form.reset(resut);
46
- setErrorMessage(null);
47
- toast.success('Form submitted successfully');
48
- //TODO: https path or relative path
49
- if (formClass.redirectSuccessUrl) {
50
- navigate(formClass.redirectSuccessUrl);
51
- }
52
- } catch (error: unknown) {
53
- const errorResponse = error as { response?: { data?: { message?: string } } };
54
- const message =
55
- errorResponse?.response?.data?.message ||
56
- (error instanceof Error ? error.message : 'An error occurred');
57
- toast.error('Something went wrong');
58
- setErrorMessage(message);
59
- console.error(error);
60
- } finally {
61
- loadingRef.current = false;
40
+ }
41
+ return formData;
42
+ })();
43
+ const resut = await formClass.onSubmit(data);
44
+ form.reset(resut);
45
+ setErrorMessage(null);
46
+ toast.success('Form submitted successfully');
47
+ //TODO: https path or relative path
48
+ if (formClass.redirectSuccessUrl) {
49
+ navigate(formClass.redirectSuccessUrl);
50
+ }
51
+ if (formClass.onSubmitSuccess) {
52
+ formClass.onSubmitSuccess(resut);
62
53
  }
63
- },
64
- (errors, event) => {
65
- //TOOD: put error if useer choose global error
66
- console.log('error creating creation', errors, event);
54
+ } catch (error: unknown) {
55
+ const errorResponse = error as { response?: { data?: { message?: string } } };
56
+ const message =
57
+ errorResponse?.response?.data?.message ||
58
+ (error instanceof Error ? error.message : 'An error occurred');
59
+ toast.error('Something went wrong');
60
+ setErrorMessage(message);
61
+ console.error(error);
62
+ } finally {
63
+ loadingRef.current = false;
67
64
  }
65
+ },
66
+ (errors, event) => {
67
+ //TOOD: put error if useer choose global error
68
+ console.error('error creating creation', errors, event);
69
+ }
70
+ )}
71
+ >
72
+ <div>
73
+ {errorMessage && (
74
+ <div className="error-message" style={{ color: 'red', marginBottom: '1rem' }}>
75
+ {errorMessage}
76
+ </div>
68
77
  )}
69
- >
70
- <div>
71
- {errorMessage && (
72
- <div className="error-message" style={{ color: 'red', marginBottom: '1rem' }}>
73
- {errorMessage}
74
- </div>
75
- )}
76
- {inputs?.map((input: InputConfiguration) => (
77
- <FormField
78
- key={input.name || ''}
79
- input={input}
80
- register={form.register}
81
- error={
82
- input.name
83
- ? {
84
- message: (
85
- form.formState.errors[input.name as keyof T] as {
86
- message: string;
87
- }
88
- )?.message,
89
- }
90
- : undefined
91
- }
92
- />
93
- ))}
94
- <button type="submit" className="submit-button">
95
- Submit
96
- </button>
97
- </div>
98
- </form>
99
- </div>
78
+ {inputs?.map((input: InputConfiguration) => (
79
+ <FormField
80
+ key={input.name || ''}
81
+ input={input}
82
+ register={form.register}
83
+ error={
84
+ input.name
85
+ ? {
86
+ message: (
87
+ form.formState.errors[input.name as keyof T] as {
88
+ message: string;
89
+ }
90
+ )?.message,
91
+ }
92
+ : undefined
93
+ }
94
+ />
95
+ ))}
96
+ <button type="submit" className="submit-button">
97
+ Submit
98
+ </button>
99
+ </div>
100
+ </form>
100
101
  );
101
102
  }
@@ -4,6 +4,7 @@ import { useFormContext, Controller } from 'react-hook-form';
4
4
  import { SelectInputConfiguration } from '../../decorators/form/inputs/SelectInput';
5
5
  import ReactSelect from 'react-select';
6
6
  import { darkSelectStyles } from './SelectStyles';
7
+ import { preloadCacheHelper } from '../../utils/PreloadCacheHelper';
7
8
 
8
9
  interface SelectProps {
9
10
  input: InputConfiguration;
@@ -23,9 +24,9 @@ export function Select<TValue>({ input, fieldName }: SelectProps) {
23
24
  const styles = useMemo(() => darkSelectStyles<TValue>(), []);
24
25
  useEffect(() => {
25
26
  if (inputSelect.onSelectPreloader) {
26
- const
27
- inputSelect.onSelectPreloader().then(option => {
28
- setOptions(option);
27
+ const onSelectPreloader = inputSelect.onSelectPreloader;
28
+ preloadCacheHelper.setOrGetCache(onSelectPreloader, (options: OptionType<TValue>[]) => {
29
+ setOptions(options);
29
30
  });
30
31
  }
31
32
  }, [inputSelect, inputSelect.onSelectPreloader]);
@@ -5,17 +5,18 @@ import { InputConfiguration } from '../../decorators/form/Input';
5
5
  interface UploaderProps {
6
6
  input: InputConfiguration;
7
7
  maxLength?: number;
8
+ fieldName: string;
8
9
  }
9
10
 
10
- export function Uploader({ input, maxLength = 1 }: UploaderProps) {
11
+ export function Uploader({ input, maxLength = 1, fieldName }: UploaderProps) {
11
12
  const form = useFormContext();
12
13
  const [files, setFiles] = useState<File[]>([]);
13
- const id = input.name!;
14
+ const id = fieldName;
14
15
 
15
16
  useEffect(() => {
16
17
  // Update form value whenever files change
17
- form.setValue(input.name + '_files', files.length > 0);
18
- }, [files, form, input.name]);
18
+ form.setValue(fieldName + '_files', files.length > 0);
19
+ }, [files, form, fieldName]);
19
20
 
20
21
  const onChange = (e: React.ChangeEvent<HTMLInputElement>) => {
21
22
  if (maxLength > 1) {
@@ -3,23 +3,25 @@ import { AnyClass, AnyClassConstructor } from '../../types/AnyClass';
3
3
  import { GetDetailsDataFN } from '../details/Details';
4
4
 
5
5
  const DETAILS_METADATA_KEY = 'DetailsMetaData';
6
- export type OnSubmitFN<T> = (data: T | FormData) => Promise<T>;
6
+ export type OnSubmitFN<T, L = T> = (data: T | FormData) => Promise<L>;
7
7
 
8
- interface FormOptions<T extends AnyClass> {
9
- onSubmit: OnSubmitFN<T>;
8
+ interface FormOptions<T extends AnyClass, L = T> {
9
+ onSubmit: OnSubmitFN<T, L>;
10
+ onSubmitSuccess?: (data: L) => void;
10
11
  getDetailsData?: GetDetailsDataFN<T>;
11
12
  type?: 'json' | 'formData';
12
13
  redirectSuccessUrl?: string;
13
14
  }
14
15
 
15
- export interface FormConfiguration<T extends AnyClass> {
16
- onSubmit: OnSubmitFN<T>;
16
+ export interface FormConfiguration<T extends AnyClass, L = T> {
17
+ onSubmit: OnSubmitFN<T, L>;
18
+ onSubmitSuccess?: (data: L) => void;
17
19
  getDetailsData?: GetDetailsDataFN<T>;
18
20
  type: 'json' | 'formData';
19
21
  redirectSuccessUrl?: string;
20
22
  }
21
23
 
22
- export function Form<T extends AnyClass>(options?: FormOptions<T>): ClassDecorator {
24
+ export function Form<T extends AnyClass, L = T>(options?: FormOptions<T, L>): ClassDecorator {
23
25
  return (target: object) => {
24
26
  if (options) {
25
27
  Reflect.defineMetadata(DETAILS_METADATA_KEY, options, target);
@@ -27,18 +29,19 @@ export function Form<T extends AnyClass>(options?: FormOptions<T>): ClassDecorat
27
29
  };
28
30
  }
29
31
 
30
- export function getFormConfiguration<T extends AnyClass, K extends AnyClassConstructor<T>>(
32
+ export function getFormConfiguration<T extends AnyClass, K extends AnyClassConstructor<T>, L = T>(
31
33
  entityClass: K
32
- ): FormConfiguration<T> {
33
- const formOptions: FormOptions<T> = Reflect.getMetadata(
34
+ ): FormConfiguration<T, L> {
35
+ const formOptions: FormOptions<T, L> = Reflect.getMetadata(
34
36
  DETAILS_METADATA_KEY,
35
37
  entityClass as object
36
38
  );
37
39
  if (!formOptions) {
38
40
  throw new Error('Form decerator should be used on class');
39
41
  }
40
- const formConfiguration: FormConfiguration<T> = {
42
+ const formConfiguration: FormConfiguration<T, L> = {
41
43
  onSubmit: formOptions.onSubmit,
44
+ onSubmitSuccess: formOptions.onSubmitSuccess,
42
45
  getDetailsData: formOptions.getDetailsData,
43
46
  type: formOptions.type ?? 'json',
44
47
  redirectSuccessUrl: formOptions.redirectSuccessUrl,