@qoretechnologies/reqraft 0.0.1 → 0.2.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 (133) hide show
  1. package/__tests__/ mock.ts +44 -0
  2. package/__tests__/utils.ts +11 -0
  3. package/dist/__tests__/ mock.d.ts +23 -0
  4. package/dist/__tests__/ mock.d.ts.map +1 -0
  5. package/dist/__tests__/ mock.js +44 -0
  6. package/dist/__tests__/ mock.js.map +1 -0
  7. package/dist/__tests__/utils.d.ts +3 -0
  8. package/dist/__tests__/utils.d.ts.map +1 -0
  9. package/dist/__tests__/utils.js +60 -0
  10. package/dist/__tests__/utils.js.map +1 -0
  11. package/dist/mock/interfaceCategories.json +155 -0
  12. package/dist/mock/menu.d.ts +70 -0
  13. package/dist/mock/menu.d.ts.map +1 -0
  14. package/dist/mock/menu.js +301 -0
  15. package/dist/mock/menu.js.map +1 -0
  16. package/dist/src/components/form/fields/Field.d.ts +10 -0
  17. package/dist/src/components/form/fields/Field.d.ts.map +1 -0
  18. package/dist/src/components/form/fields/Field.js +44 -0
  19. package/dist/src/components/form/fields/Field.js.map +1 -0
  20. package/dist/{components/form → src/components/form/fields}/string/String.d.ts +3 -2
  21. package/dist/src/components/form/fields/string/String.d.ts.map +1 -0
  22. package/dist/src/components/form/fields/string/String.js.map +1 -0
  23. package/dist/src/components/form/fields/string/String.stories.d.ts.map +1 -0
  24. package/dist/src/components/form/fields/string/String.stories.js.map +1 -0
  25. package/dist/src/components/menu/Menu.d.ts +31 -0
  26. package/dist/src/components/menu/Menu.d.ts.map +1 -0
  27. package/dist/src/components/menu/Menu.js +130 -0
  28. package/dist/src/components/menu/Menu.js.map +1 -0
  29. package/dist/src/components/menu/Menu.stories.d.ts +12 -0
  30. package/dist/src/components/menu/Menu.stories.d.ts.map +1 -0
  31. package/dist/src/components/menu/Menu.stories.js +159 -0
  32. package/dist/src/components/menu/Menu.stories.js.map +1 -0
  33. package/dist/src/contexts/FetchContext.d.ts +10 -0
  34. package/dist/src/contexts/FetchContext.d.ts.map +1 -0
  35. package/dist/src/contexts/FetchContext.js +63 -0
  36. package/dist/src/contexts/FetchContext.js.map +1 -0
  37. package/dist/src/contexts/ReqraftContext.d.ts +9 -0
  38. package/dist/src/contexts/ReqraftContext.d.ts.map +1 -0
  39. package/dist/src/contexts/ReqraftContext.js +11 -0
  40. package/dist/src/contexts/ReqraftContext.js.map +1 -0
  41. package/dist/src/contexts/StorageContext.d.ts +11 -0
  42. package/dist/src/contexts/StorageContext.d.ts.map +1 -0
  43. package/dist/src/contexts/StorageContext.js +17 -0
  44. package/dist/src/contexts/StorageContext.js.map +1 -0
  45. package/dist/src/hooks/useFetch/useFetch.d.ts +21 -0
  46. package/dist/src/hooks/useFetch/useFetch.d.ts.map +1 -0
  47. package/dist/src/hooks/useFetch/useFetch.js +106 -0
  48. package/dist/src/hooks/useFetch/useFetch.js.map +1 -0
  49. package/dist/src/hooks/useFetch/useFetch.stories.d.ts +9 -0
  50. package/dist/src/hooks/useFetch/useFetch.stories.d.ts.map +1 -0
  51. package/dist/src/hooks/useFetch/useFetch.stories.js +177 -0
  52. package/dist/src/hooks/useFetch/useFetch.stories.js.map +1 -0
  53. package/dist/src/hooks/useReqraftProperty.d.ts +3 -0
  54. package/dist/src/hooks/useReqraftProperty.d.ts.map +1 -0
  55. package/dist/src/hooks/useReqraftProperty.js +16 -0
  56. package/dist/src/hooks/useReqraftProperty.js.map +1 -0
  57. package/dist/src/hooks/useStorage/useStorage.d.ts +8 -0
  58. package/dist/src/hooks/useStorage/useStorage.d.ts.map +1 -0
  59. package/dist/src/hooks/useStorage/useStorage.js +22 -0
  60. package/dist/src/hooks/useStorage/useStorage.js.map +1 -0
  61. package/dist/src/hooks/useStorage/useStorage.stories.d.ts +9 -0
  62. package/dist/src/hooks/useStorage/useStorage.stories.d.ts.map +1 -0
  63. package/dist/src/hooks/useStorage/useStorage.stories.js +162 -0
  64. package/dist/src/hooks/useStorage/useStorage.stories.js.map +1 -0
  65. package/dist/src/hooks/useValidation.d.ts +3 -0
  66. package/dist/src/hooks/useValidation.d.ts.map +1 -0
  67. package/dist/src/hooks/useValidation.js +11 -0
  68. package/dist/src/hooks/useValidation.js.map +1 -0
  69. package/dist/src/index.d.ts +6 -0
  70. package/dist/src/index.d.ts.map +1 -0
  71. package/dist/src/index.js +15 -0
  72. package/dist/src/index.js.map +1 -0
  73. package/dist/src/providers/FetchProvider.d.ts +6 -0
  74. package/dist/src/providers/FetchProvider.d.ts.map +1 -0
  75. package/dist/src/providers/FetchProvider.js +112 -0
  76. package/dist/src/providers/FetchProvider.js.map +1 -0
  77. package/dist/src/providers/ReqraftProvider.d.ts +10 -0
  78. package/dist/src/providers/ReqraftProvider.d.ts.map +1 -0
  79. package/dist/src/providers/ReqraftProvider.js +15 -0
  80. package/dist/src/providers/ReqraftProvider.js.map +1 -0
  81. package/dist/src/providers/StorageProvider.d.ts +6 -0
  82. package/dist/src/providers/StorageProvider.d.ts.map +1 -0
  83. package/dist/src/providers/StorageProvider.js +55 -0
  84. package/dist/src/providers/StorageProvider.js.map +1 -0
  85. package/dist/src/types/Form.d.ts +3 -0
  86. package/dist/src/types/Form.d.ts.map +1 -0
  87. package/dist/src/types/Form.js +3 -0
  88. package/dist/src/types/Form.js.map +1 -0
  89. package/dist/src/types.d.ts.map +1 -0
  90. package/dist/src/types.js.map +1 -0
  91. package/dist/src/utils/fetch.d.ts +22 -0
  92. package/dist/src/utils/fetch.d.ts.map +1 -0
  93. package/dist/src/utils/fetch.js +140 -0
  94. package/dist/src/utils/fetch.js.map +1 -0
  95. package/mock/interfaceCategories.json +155 -0
  96. package/mock/menu.ts +301 -0
  97. package/package.json +8 -3
  98. package/src/components/form/fields/Field.tsx +37 -0
  99. package/src/components/form/{string → fields/string}/String.stories.tsx +1 -1
  100. package/src/components/form/{string → fields/string}/String.tsx +3 -2
  101. package/src/components/menu/Menu.stories.tsx +73 -0
  102. package/src/components/menu/Menu.tsx +244 -0
  103. package/src/contexts/FetchContext.tsx +25 -0
  104. package/src/contexts/ReqraftContext.tsx +16 -0
  105. package/src/contexts/StorageContext.tsx +33 -0
  106. package/src/hooks/useFetch/useFetch.stories.tsx +123 -0
  107. package/src/hooks/useFetch/useFetch.tsx +71 -0
  108. package/src/hooks/useReqraftProperty.ts +16 -0
  109. package/src/hooks/useStorage/useStorage.stories.tsx +84 -0
  110. package/src/hooks/useStorage/useStorage.ts +30 -0
  111. package/src/hooks/useValidation.ts +9 -0
  112. package/src/index.tsx +12 -1
  113. package/src/providers/FetchProvider.tsx +62 -0
  114. package/src/providers/ReqraftProvider.tsx +33 -0
  115. package/src/providers/StorageProvider.tsx +80 -0
  116. package/src/types/Form.ts +57 -0
  117. package/src/utils/fetch.ts +121 -0
  118. package/tests.json +1 -1
  119. package/dist/components/form/string/String.d.ts.map +0 -1
  120. package/dist/components/form/string/String.js.map +0 -1
  121. package/dist/components/form/string/String.stories.d.ts.map +0 -1
  122. package/dist/components/form/string/String.stories.js.map +0 -1
  123. package/dist/index.d.ts +0 -2
  124. package/dist/index.d.ts.map +0 -1
  125. package/dist/index.js +0 -6
  126. package/dist/index.js.map +0 -1
  127. package/dist/types.d.ts.map +0 -1
  128. package/dist/types.js.map +0 -1
  129. /package/dist/{components/form → src/components/form/fields}/string/String.js +0 -0
  130. /package/dist/{components/form → src/components/form/fields}/string/String.stories.d.ts +0 -0
  131. /package/dist/{components/form → src/components/form/fields}/string/String.stories.js +0 -0
  132. /package/dist/{types.d.ts → src/types.d.ts} +0 -0
  133. /package/dist/{types.js → src/types.js} +0 -0
@@ -0,0 +1,37 @@
1
+ import { TFormFieldType, TFormFieldValueType } from '../../../types/Form';
2
+ import { FormStringField } from './string/String';
3
+
4
+ export interface IFormFieldProps<T extends TFormFieldType = TFormFieldType> {
5
+ type?: T;
6
+ value: TFormFieldValueType<T>;
7
+ onChange: (value: TFormFieldValueType<T>, event?: unknown) => void;
8
+
9
+ validateSelf?: boolean;
10
+ onValidateChange?: (isValid: boolean) => void;
11
+ }
12
+
13
+ export const FormField = <T extends TFormFieldType>({
14
+ type,
15
+ onChange,
16
+ ...rest
17
+ }: IFormFieldProps<T>) => {
18
+ const handleChange = (value: TFormFieldValueType<T>, event?: unknown) => {
19
+ onChange(value, event);
20
+ };
21
+
22
+ const renderField = (type: T) => {
23
+ switch (type) {
24
+ case 'string':
25
+ return (
26
+ <FormStringField
27
+ {...rest}
28
+ onChange={(value: string) => handleChange(value as TFormFieldValueType<T>)}
29
+ />
30
+ );
31
+ default:
32
+ return null;
33
+ }
34
+ };
35
+
36
+ return renderField(type);
37
+ };
@@ -1,5 +1,5 @@
1
1
  import { StoryObj } from '@storybook/react';
2
- import { StoryMeta } from '../../../types';
2
+ import { StoryMeta } from '../../../../types';
3
3
  import { FormStringField } from './String';
4
4
 
5
5
  const meta = {
@@ -3,15 +3,16 @@ import { IReqoreControlGroupProps } from '@qoretechnologies/reqore/dist/componen
3
3
  import { IReqoreInputProps } from '@qoretechnologies/reqore/dist/components/Input';
4
4
  import { IReqoreTagProps } from '@qoretechnologies/reqore/dist/components/Tag';
5
5
  import { ChangeEvent, useCallback } from 'react';
6
+ import { TFormFieldValueType } from '../../../../types/Form';
6
7
 
7
8
  export interface IStringFormFieldProps extends Omit<IReqoreInputProps, 'onChange' | 'value'> {
8
9
  sensitive?: boolean;
9
- value?: string;
10
+ value?: TFormFieldValueType<'string'>;
10
11
  label?: IReqoreTagProps['label'];
11
12
  labelPosition?: 'top' | 'left' | 'right' | 'bottom';
12
13
  labelProps?: IReqoreTagProps;
13
14
  wrapperProps?: IReqoreControlGroupProps;
14
- onChange?: (value?: string, event?: ChangeEvent<HTMLInputElement>) => void;
15
+ onChange?: (value?: TFormFieldValueType<'string'>, event?: ChangeEvent<HTMLInputElement>) => void;
15
16
  }
16
17
 
17
18
  export const FormStringField = ({
@@ -0,0 +1,73 @@
1
+ import { StoryObj } from '@storybook/react';
2
+ import { expect, fireEvent, fn, waitFor } from '@storybook/test';
3
+ import { storiesStorageMock, storiesStorageMockEmpty } from '../../../__tests__/ mock';
4
+ import { testsWaitForText } from '../../../__tests__/utils';
5
+ import menu from '../../../mock/menu';
6
+ import { StoryMeta } from '../../types';
7
+ import { ReqraftMenu, TReqraftMenu } from './Menu';
8
+
9
+ const typedMenu = menu as TReqraftMenu;
10
+
11
+ const meta = {
12
+ component: ReqraftMenu,
13
+ title: 'Components/Menu',
14
+ render: (props) => <ReqraftMenu {...props} />,
15
+ parameters: {
16
+ mockData: [...storiesStorageMockEmpty],
17
+ },
18
+ } as StoryMeta<typeof ReqraftMenu>;
19
+
20
+ export default meta;
21
+ export type Story = StoryObj<typeof meta>;
22
+
23
+ export const Basic: Story = {
24
+ args: {
25
+ menu: typedMenu,
26
+ },
27
+ play: async () => {
28
+ await testsWaitForText('Developer Portal');
29
+ },
30
+ };
31
+ export const ActivePath: Story = {
32
+ args: {
33
+ path: '/Interfaces/mapper',
34
+ menu: typedMenu,
35
+ },
36
+ play: async () => {
37
+ await testsWaitForText('Developer Portal');
38
+ },
39
+ };
40
+
41
+ export const WithDefaultQuery: Story = {
42
+ args: {
43
+ menu: typedMenu,
44
+ defaultQuery: 'mapper',
45
+ },
46
+ play: async () => {
47
+ await testsWaitForText('Developer Portal');
48
+ await expect(document.querySelector('.reqore-input')).toHaveValue('mapper');
49
+ await expect(document.querySelectorAll('.reqore-menu-item')).toHaveLength(2);
50
+ },
51
+ };
52
+
53
+ export const Filtered: Story = {
54
+ args: {
55
+ menu: typedMenu,
56
+ onQueryChange: fn(),
57
+ },
58
+ play: async () => {
59
+ await testsWaitForText('Developer Portal');
60
+ await fireEvent.change(document.querySelector('.reqore-input'), { target: { value: 'step' } });
61
+
62
+ await waitFor(() => expect(document.querySelectorAll('.reqore-menu-item')).toHaveLength(2), {
63
+ timeout: 1000,
64
+ });
65
+ },
66
+ };
67
+
68
+ export const WidthFromStorage: Story = {
69
+ ...ActivePath,
70
+ parameters: {
71
+ mockData: [...storiesStorageMock],
72
+ },
73
+ };
@@ -0,0 +1,244 @@
1
+ import {
2
+ ReqoreButton,
3
+ ReqoreControlGroup,
4
+ ReqoreInput,
5
+ ReqoreMenuDivider,
6
+ ReqoreMenuItem,
7
+ ReqoreMenuSection,
8
+ } from '@qoretechnologies/reqore';
9
+ import ReqoreMenu, { IReqoreMenuProps } from '@qoretechnologies/reqore/dist/components/Menu';
10
+ import { IReqoreMenuDividerProps } from '@qoretechnologies/reqore/dist/components/Menu/divider';
11
+ import { IReqoreMenuItemProps } from '@qoretechnologies/reqore/dist/components/Menu/item';
12
+ import { map, reduce, size } from 'lodash';
13
+ import { useEffect, useMemo, useState } from 'react';
14
+ import { useReqraftStorage } from '../../hooks/useStorage/useStorage';
15
+
16
+ export interface IReqraftMenuItem extends IReqoreMenuItemProps {
17
+ submenu?: TReqraftMenuItem[];
18
+ activePaths?: string[];
19
+ to?: string;
20
+ href?: string;
21
+ }
22
+
23
+ export type TReqraftMenuItem = IReqraftMenuItem | ({ divider: true } & IReqoreMenuDividerProps);
24
+ export type TReqraftMenu = TReqraftMenuItem[];
25
+
26
+ export interface IReqraftMenuProps extends Partial<Omit<IReqoreMenuProps, 'resizable'>> {
27
+ hidden?: boolean;
28
+ onHideClick?: () => void;
29
+
30
+ defaultQuery?: string;
31
+ onQueryChange?: (query: string) => void;
32
+
33
+ menu: TReqraftMenu;
34
+ inputFocusShortcut?: string;
35
+ path?: string;
36
+
37
+ resizable?: boolean;
38
+ onResizeChange?: (width: number) => void;
39
+ defaultWidth?: number;
40
+ }
41
+
42
+ export const ReqraftMenuItem = ({
43
+ path,
44
+ isCollapsed,
45
+ ...props
46
+ }: TReqraftMenuItem & { path?: string; isCollapsed?: boolean }) => {
47
+ if ('divider' in props) {
48
+ return <ReqoreMenuDivider />;
49
+ }
50
+
51
+ const isActive = useMemo(
52
+ () =>
53
+ props.activePaths?.some(
54
+ (activePath) => activePath === path || path?.startsWith(`${activePath}/`)
55
+ ),
56
+ [path]
57
+ );
58
+
59
+ if (props.submenu) {
60
+ const { submenu, ...menuData } = props;
61
+
62
+ return (
63
+ <ReqoreMenuSection
64
+ label={menuData.label}
65
+ icon={menuData.icon}
66
+ isCollapsed={isCollapsed && !isActive}
67
+ verticalPadding='tiny'
68
+ {...menuData}
69
+ >
70
+ {map(submenu, (submenuData, submenuId) => (
71
+ <ReqraftMenuItem key={submenuId} {...submenuData} path={path} />
72
+ ))}
73
+ </ReqoreMenuSection>
74
+ );
75
+ }
76
+
77
+ return (
78
+ <ReqoreMenuItem
79
+ customTheme={{ main: '#050505' }}
80
+ effect={
81
+ isActive
82
+ ? {
83
+ gradient: {
84
+ colors: {
85
+ 0: 'info:darken:5:0.4',
86
+ 40: '#181818',
87
+ 100: '#181818',
88
+ },
89
+ },
90
+ }
91
+ : undefined
92
+ }
93
+ leftIconColor={isActive ? 'info:lighten:10' : undefined}
94
+ verticalPadding='tiny'
95
+ {...props}
96
+ />
97
+ );
98
+ };
99
+
100
+ export const ReqraftMenu = ({
101
+ defaultQuery,
102
+ defaultWidth = 250,
103
+ inputFocusShortcut = '/',
104
+ hidden,
105
+ menu,
106
+ onQueryChange,
107
+ onResizeChange,
108
+ onHideClick,
109
+ resizable,
110
+ path,
111
+ ...rest
112
+ }: IReqraftMenuProps) => {
113
+ const [query, setQuery] = useState<string>(defaultQuery);
114
+
115
+ const [isSidebarOpen, update] = useReqraftStorage<boolean>('sidebar-open', true, false);
116
+ const [sidebarSize, updateSidebarSize] = useReqraftStorage<number>(
117
+ 'sidebar-size',
118
+ defaultWidth,
119
+ false
120
+ );
121
+
122
+ useEffect(() => {
123
+ if (defaultQuery) {
124
+ setQuery(defaultQuery);
125
+ }
126
+ }, [defaultQuery]);
127
+
128
+ const handleQueryChange = (newQuery: string) => {
129
+ setQuery(newQuery);
130
+ onQueryChange?.(newQuery);
131
+ };
132
+
133
+ const handleWidthChange = (newWidth: number) => {
134
+ updateSidebarSize(newWidth);
135
+ onResizeChange?.(newWidth);
136
+ };
137
+
138
+ const filteredMenu: TReqraftMenu = useMemo<TReqraftMenu>(() => {
139
+ if (!query) {
140
+ return menu;
141
+ }
142
+
143
+ const filterItems = (items: TReqraftMenu): TReqraftMenu => {
144
+ return reduce(
145
+ items,
146
+ (acc, item) => {
147
+ if ('divider' in item) {
148
+ acc.push(item);
149
+ return acc;
150
+ }
151
+
152
+ if (item.submenu) {
153
+ const submenu = filterItems(item.submenu);
154
+ const hasChildMatch = size(submenu);
155
+
156
+ if (hasChildMatch) {
157
+ acc.push({
158
+ ...item,
159
+ submenu,
160
+ });
161
+
162
+ return acc;
163
+ }
164
+ }
165
+
166
+ if (item.label.toString().toLowerCase().includes(query.toLowerCase())) {
167
+ acc.push(item);
168
+ }
169
+
170
+ return acc;
171
+ },
172
+ []
173
+ );
174
+ };
175
+
176
+ return filterItems(menu);
177
+ }, [menu, query]);
178
+
179
+ if (hidden) {
180
+ return null;
181
+ }
182
+
183
+ return (
184
+ <ReqoreMenu
185
+ width='250px'
186
+ minimal
187
+ position='left'
188
+ resizable={{
189
+ enable: { right: resizable, left: false },
190
+ minWidth: '250px',
191
+ maxWidth: '350px',
192
+ onResizeStop: (_e, _direction, _ref, d) => {
193
+ handleWidthChange(sidebarSize + d.width);
194
+ },
195
+ size: {
196
+ width: `${sidebarSize}px`,
197
+ height: '100%',
198
+ },
199
+ }}
200
+ rounded={false}
201
+ customTheme={{ main: '#181818' }}
202
+ {...rest}
203
+ >
204
+ <ReqoreControlGroup>
205
+ <ReqoreInput
206
+ icon='Search2Line'
207
+ minimal={false}
208
+ flat={false}
209
+ placeholder={`Filter menu "${inputFocusShortcut}"`}
210
+ intent={query ? 'info' : 'muted'}
211
+ leftIconProps={{ size: 'small' }}
212
+ iconColor={query ? 'info' : 'muted'}
213
+ pill
214
+ value={query}
215
+ onClearClick={() => handleQueryChange('')}
216
+ onChange={(e: any) => handleQueryChange(e.target.value)}
217
+ focusRules={{
218
+ shortcut: inputFocusShortcut,
219
+ type: 'keypress',
220
+ clearOnFocus: true,
221
+ doNotInsertShortcut: true,
222
+ }}
223
+ />
224
+ <ReqoreButton
225
+ icon='SideBarLine'
226
+ fixed
227
+ minimal={false}
228
+ onClick={() => {
229
+ update(!isSidebarOpen);
230
+ onHideClick?.();
231
+ }}
232
+ />
233
+ </ReqoreControlGroup>
234
+ {map(filteredMenu, (menuData, menuId) => (
235
+ <ReqraftMenuItem
236
+ key={menuId}
237
+ {...menuData}
238
+ path={path}
239
+ isCollapsed={!query && !!(menuData as IReqraftMenuItem).submenu}
240
+ />
241
+ ))}
242
+ </ReqoreMenu>
243
+ );
244
+ };
@@ -0,0 +1,25 @@
1
+ import { createContext } from 'use-context-selector';
2
+ import { IReqraftFetchResponse, IReqraftQueryConfig } from '../utils/fetch';
3
+
4
+ export type TReqraftContextQueryConfig = Omit<IReqraftQueryConfig, 'method'>;
5
+ export interface IReqraftFetchContext {
6
+ get: <T>(config?: TReqraftContextQueryConfig) => Promise<IReqraftFetchResponse<T>>;
7
+ post: <T>(config?: TReqraftContextQueryConfig) => Promise<IReqraftFetchResponse<T>>;
8
+ put: <T>(config?: TReqraftContextQueryConfig) => Promise<IReqraftFetchResponse<T>>;
9
+ del: <T>(config?: TReqraftContextQueryConfig) => Promise<IReqraftFetchResponse<T>>;
10
+ }
11
+
12
+ export const FetchContext = createContext<IReqraftFetchContext>({
13
+ get: async () => {
14
+ throw new Error('FetchContext not implemented');
15
+ },
16
+ post: async () => {
17
+ throw new Error('FetchContext not implemented');
18
+ },
19
+ put: async () => {
20
+ throw new Error('FetchContext not implemented');
21
+ },
22
+ del: async () => {
23
+ throw new Error('FetchContext not implemented');
24
+ },
25
+ });
@@ -0,0 +1,16 @@
1
+ import { createContext } from 'use-context-selector';
2
+ import { IReqraftFetchConfig } from '../utils/fetch';
3
+
4
+ export interface IReqraftContext {
5
+ appName: string;
6
+ instance?: string;
7
+ instanceToken: string;
8
+ instanceUnauthorizedRedirect?: IReqraftFetchConfig['unauthorizedRedirect'];
9
+ }
10
+
11
+ export const ReqraftContext = createContext<IReqraftContext>({
12
+ appName: '',
13
+ instance: '',
14
+ instanceToken: '',
15
+ instanceUnauthorizedRedirect: undefined,
16
+ });
@@ -0,0 +1,33 @@
1
+ import { Get } from 'type-fest';
2
+ import { createContext } from 'use-context-selector';
3
+ import { TReqraftStorageValue } from '../hooks/useStorage/useStorage';
4
+
5
+ export type TReqraftStorage = Record<string, any>;
6
+
7
+ export interface IReqraftStorageContext {
8
+ storage?: Record<string, any>;
9
+ getStorage?: <T extends TReqraftStorageValue>(
10
+ path: string,
11
+ defaultValue?: T,
12
+ includeAppPrefix?: boolean
13
+ ) => Get<TReqraftStorage, string>;
14
+ updateStorage?: <T extends TReqraftStorageValue>(
15
+ path: string,
16
+ value: T,
17
+ includeAppPrefix?: boolean
18
+ ) => void;
19
+ removeStorageValue: (path: string, includeAppPrefix?: boolean) => void;
20
+ }
21
+
22
+ export const ReqraftStorageContext = createContext<IReqraftStorageContext>({
23
+ storage: {},
24
+ getStorage: () => {
25
+ throw new Error('Storage not implemented');
26
+ },
27
+ updateStorage: () => {
28
+ throw new Error('Storage not implemented');
29
+ },
30
+ removeStorageValue: () => {
31
+ throw new Error('Storage not implemented');
32
+ },
33
+ });
@@ -0,0 +1,123 @@
1
+ import { ReqoreSpinner, ReqoreTree } from '@qoretechnologies/reqore';
2
+ import { StoryObj } from '@storybook/react';
3
+ import { useEffectOnce } from 'react-use';
4
+ import { testsWaitForText } from '../../../__tests__/utils';
5
+ import { StoryMeta } from '../../types';
6
+ import { useFetch } from './useFetch';
7
+
8
+ const meta = {
9
+ title: 'Hooks/useFetch',
10
+ render: (args) => {
11
+ const { data = {}, load, loading } = useFetch<any>({ url: 'public/info', method: args.method });
12
+
13
+ useEffectOnce(() => {
14
+ load();
15
+ });
16
+
17
+ return loading ? (
18
+ <ReqoreSpinner />
19
+ ) : (
20
+ <ReqoreTree data={data} bottomActions={[{ label: 'Refetch', onClick: load }]} />
21
+ );
22
+ },
23
+ } as StoryMeta<any>;
24
+
25
+ export default meta;
26
+ export type Story = StoryObj<typeof meta>;
27
+
28
+ export const get: Story = {
29
+ args: {
30
+ method: 'GET',
31
+ },
32
+ parameters: {
33
+ mockData: [
34
+ {
35
+ // An array of mock objects which will add in every story
36
+ url: 'https://hq.qoretechnologies.com:8092/api/latest/public/info',
37
+ method: 'GET',
38
+ status: 200,
39
+ delay: 200,
40
+ response: {
41
+ 'instance-key': 'rippy-dev1',
42
+ 'omq-version': '6.1.0_prod',
43
+ 'omq-build': 'd115c45fbed95c03327aea880be455137e9778ba',
44
+ 'qore-version': '2.0.0',
45
+ 'omq-schema': 'omq@omq',
46
+ edition: 'Enterprise',
47
+ tz_region: 'Europe/Prague',
48
+ tz_utc_offset: 3600,
49
+ noauth: false,
50
+ },
51
+ },
52
+ ],
53
+ },
54
+ play: async () => {
55
+ await testsWaitForText('"rippy-dev1"');
56
+ },
57
+ };
58
+
59
+ export const put: Story = {
60
+ args: {
61
+ method: 'PUT',
62
+ },
63
+ parameters: {
64
+ mockData: [
65
+ {
66
+ // An array of mock objects which will add in every story
67
+ url: 'https://hq.qoretechnologies.com:8092/api/latest/public/info',
68
+ method: 'PUT',
69
+ status: 200,
70
+ response: {
71
+ status: 'Successfuly updated',
72
+ },
73
+ },
74
+ ],
75
+ },
76
+ play: async () => {
77
+ await testsWaitForText('"Successfuly updated"');
78
+ },
79
+ };
80
+
81
+ export const post: Story = {
82
+ args: {
83
+ method: 'POST',
84
+ },
85
+ parameters: {
86
+ mockData: [
87
+ {
88
+ // An array of mock objects which will add in every story
89
+ url: 'https://hq.qoretechnologies.com:8092/api/latest/public/info',
90
+ method: 'POST',
91
+ status: 200,
92
+ response: {
93
+ status: 'Successfuly created',
94
+ },
95
+ },
96
+ ],
97
+ },
98
+ play: async () => {
99
+ await testsWaitForText('"Successfuly created"');
100
+ },
101
+ };
102
+
103
+ export const del: Story = {
104
+ args: {
105
+ method: 'DELETE',
106
+ },
107
+ parameters: {
108
+ mockData: [
109
+ {
110
+ // An array of mock objects which will add in every story
111
+ url: 'https://hq.qoretechnologies.com:8092/api/latest/public/info',
112
+ method: 'DELETE',
113
+ status: 200,
114
+ response: {
115
+ status: 'Successfuly deleted',
116
+ },
117
+ },
118
+ ],
119
+ },
120
+ play: async () => {
121
+ await testsWaitForText('"Successfuly deleted"');
122
+ },
123
+ };
@@ -0,0 +1,71 @@
1
+ import { useState } from 'react';
2
+ import { useEffectOnce } from 'react-use';
3
+ import { useContextSelector } from 'use-context-selector';
4
+ import { FetchContext } from '../../contexts/FetchContext';
5
+ import { IReqraftQueryConfig } from '../../utils/fetch';
6
+
7
+ export interface IReqraftUseFetch<T> {
8
+ data: T | undefined;
9
+ loading: boolean;
10
+ load: () => Promise<T>;
11
+ error: Error | undefined;
12
+ }
13
+
14
+ export interface IReqraftUseFetchOptions<T> extends IReqraftQueryConfig {
15
+ defaultData?: T;
16
+ loadOnMount?: boolean;
17
+ }
18
+
19
+ export function useFetch<T>({
20
+ url,
21
+ method = 'GET',
22
+ body,
23
+ cache,
24
+ defaultData,
25
+ loadOnMount,
26
+ }: IReqraftUseFetchOptions<T>) {
27
+ const query = useContextSelector(FetchContext, (context) => {
28
+ switch (method) {
29
+ case 'GET':
30
+ return context.get;
31
+ case 'POST':
32
+ return context.post;
33
+ case 'PUT':
34
+ return context.put;
35
+ case 'DELETE':
36
+ return context.del;
37
+ default:
38
+ throw new Error('Invalid method');
39
+ }
40
+ });
41
+
42
+ const [loading, setLoading] = useState(loadOnMount);
43
+ const [data, setData] = useState<T | undefined>(defaultData);
44
+ const [error, setError] = useState<Error | undefined>();
45
+
46
+ async function load({
47
+ body: customBody,
48
+ mergeBodies,
49
+ }: { body?: Record<string | number, any>; mergeBodies?: boolean } = {}) {
50
+ setLoading(true);
51
+
52
+ const _body = mergeBodies ? { ...body, ...customBody } : customBody || body;
53
+ const response = await query<T>({ url, body: _body, cache });
54
+
55
+ setLoading(false);
56
+
57
+ if (response.ok) {
58
+ setData(response.data);
59
+ } else {
60
+ setError(response.error);
61
+ }
62
+ }
63
+
64
+ useEffectOnce(() => {
65
+ if (loadOnMount) {
66
+ load();
67
+ }
68
+ });
69
+
70
+ return { data, loading, load, error };
71
+ }
@@ -0,0 +1,16 @@
1
+ import { useContextSelector } from 'use-context-selector';
2
+ import { IReqraftContext, ReqraftContext } from '../contexts/ReqraftContext';
3
+
4
+ export const useReqraftProperty = <T extends keyof IReqraftContext>(
5
+ property: T
6
+ ): IReqraftContext[T] => {
7
+ const contextProperty = useContextSelector(ReqraftContext, (value) => {
8
+ if (!(property in value)) {
9
+ throw new Error(`Reqraft context property ${property} not found`);
10
+ }
11
+
12
+ return value[property];
13
+ });
14
+
15
+ return contextProperty;
16
+ };