@centreon/ui 23.10.39 → 23.10.40
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.
- package/package.json +2 -1
- package/src/ThemeProvider/index.tsx +0 -24
- package/src/ThemeProvider/palettes.ts +36 -6
- package/src/api/useMutationQuery/index.ts +16 -6
- package/src/components/Form/AccessRights/AccessRights.resource.ts +45 -0
- package/src/components/Form/AccessRights/AccessRightsForm.stories.tsx +59 -0
- package/src/components/Form/AccessRights/AccessRightsForm.styles.ts +21 -0
- package/src/components/Form/AccessRights/AccessRightsForm.tsx +67 -0
- package/src/components/Form/AccessRights/AccessRightsFormActions.tsx +80 -0
- package/src/components/Form/AccessRights/Input/AddAction.tsx +31 -0
- package/src/components/Form/AccessRights/Input/ContactAccessRightInput.stories.tsx +54 -0
- package/src/components/Form/AccessRights/Input/ContactAccessRightInput.tsx +72 -0
- package/src/components/Form/AccessRights/Input/ContactAccessRightsInput.styles.ts +22 -0
- package/src/components/Form/AccessRights/Input/ContactInputField.tsx +105 -0
- package/src/components/Form/AccessRights/Input/RoleInputField.tsx +29 -0
- package/src/components/Form/AccessRights/List/ContactAccessRightsList.stories.tsx +97 -0
- package/src/components/Form/AccessRights/List/ContactAccessRightsList.styles.ts +71 -0
- package/src/components/Form/AccessRights/List/ContactAccessRightsList.tsx +51 -0
- package/src/components/Form/AccessRights/List/ContactAccessRightsListItem.stories.tsx +116 -0
- package/src/components/Form/AccessRights/List/ContactAccessRightsListItem.tsx +118 -0
- package/src/components/Form/AccessRights/List/ContactAccessRightsListItemSkeleton.tsx +26 -0
- package/src/components/Form/AccessRights/List/ContactAccessRightsListSkeleton.tsx +28 -0
- package/src/components/Form/AccessRights/Stats/AccessRightsStats.styles.ts +18 -0
- package/src/components/Form/AccessRights/Stats/AccessRightsStats.tsx +41 -0
- package/src/components/Form/AccessRights/__fixtures__/contactAccessRight.mock.ts +52 -0
- package/src/components/Form/AccessRights/common/GroupLabel.styles.ts +18 -0
- package/src/components/Form/AccessRights/common/GroupLabel.tsx +15 -0
- package/src/components/Form/AccessRights/common/Input.styles.ts +48 -0
- package/src/components/Form/AccessRights/common/RoleInputSelect.styles.ts +11 -0
- package/src/components/Form/AccessRights/common/RoleInputSelect.tsx +57 -0
- package/src/components/Form/AccessRights/index.ts +3 -0
- package/src/components/Form/AccessRights/useAccessRightsForm.test.tsx +531 -0
- package/src/components/Form/AccessRights/useAccessRightsForm.tsx +282 -0
- package/src/components/Form/AccessRights/useAccessRightsForm.utils.ts +41 -0
- package/src/components/Form/Dashboard/DashboardForm.styles.ts +1 -7
- package/src/components/Form/Dashboard/DashboardForm.tsx +9 -12
- package/src/components/Form/Form.resource.ts +1 -0
- package/src/components/Form/Form.styles.ts +17 -0
- package/src/components/Form/{Dashboard/DashboardFormActions.tsx → FormActions.tsx} +17 -16
- package/src/components/Form/index.ts +1 -0
- package/src/components/Header/PageHeader/PageHeader.stories.tsx +9 -0
- package/src/components/List/Item/Avatar.tsx +21 -0
- package/src/components/List/Item/AvatarSkeleton.tsx +17 -0
- package/src/components/List/Item/ListItem.stories.tsx +41 -0
- package/src/components/List/Item/ListItem.styles.ts +57 -0
- package/src/components/List/Item/ListItem.tsx +32 -0
- package/src/components/List/Item/Text.tsx +25 -0
- package/src/components/List/Item/TextSkeleton.tsx +22 -0
- package/src/components/List/Item/index.ts +14 -0
- package/src/components/List/List.stories.tsx +70 -0
- package/src/components/List/List.styles.ts +8 -0
- package/src/components/List/List.tsx +19 -0
- package/src/components/List/index.ts +6 -0
- package/src/components/Modal/ModalActions.tsx +4 -1
- package/src/components/index.ts +1 -0
- package/src/utils/index.ts +1 -0
- package/src/utils/useInfiniteScrollListing.ts +106 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@centreon/ui",
|
|
3
|
-
"version": "23.10.
|
|
3
|
+
"version": "23.10.40",
|
|
4
4
|
"description": "Centreon UI Components",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"eslint": "eslint ./src --ext .js,.jsx,.ts,.tsx --max-warnings 0",
|
|
@@ -48,6 +48,7 @@
|
|
|
48
48
|
"devDependencies": {
|
|
49
49
|
"@cypress/react": "^7.0.2",
|
|
50
50
|
"@cypress/webpack-dev-server": "^3.2.3",
|
|
51
|
+
"@faker-js/faker": "^8.0.2",
|
|
51
52
|
"@mdx-js/react": "^2.1.4",
|
|
52
53
|
"@simonsmith/cypress-image-snapshot": "^6.1.0",
|
|
53
54
|
"@storybook/addon-a11y": "^7.0.9",
|
|
@@ -148,30 +148,6 @@ export const getTheme = (mode: ThemeMode): ThemeOptions => ({
|
|
|
148
148
|
},
|
|
149
149
|
MuiCssBaseline: {
|
|
150
150
|
styleOverrides: (theme) => `
|
|
151
|
-
::-webkit-scrollbar {
|
|
152
|
-
height: ${theme.spacing(1)};
|
|
153
|
-
width: ${theme.spacing(1)};
|
|
154
|
-
background-color: ${theme.palette.background.default};
|
|
155
|
-
}
|
|
156
|
-
::-webkit-scrollbar-thumb {
|
|
157
|
-
background-color: ${
|
|
158
|
-
equals(mode, 'dark')
|
|
159
|
-
? theme.palette.divider
|
|
160
|
-
: theme.palette.text.disabled
|
|
161
|
-
};
|
|
162
|
-
border-radius: ${theme.spacing(0.5)};
|
|
163
|
-
}
|
|
164
|
-
::-webkit-scrollbar-thumb:hover {
|
|
165
|
-
background-color: ${theme.palette.primary.main};
|
|
166
|
-
}
|
|
167
|
-
* {
|
|
168
|
-
scrollbar-color: ${
|
|
169
|
-
equals(mode, 'dark')
|
|
170
|
-
? theme.palette.divider
|
|
171
|
-
: theme.palette.text.disabled
|
|
172
|
-
} ${theme.palette.background.default};
|
|
173
|
-
scrollbar-width: thin;
|
|
174
|
-
}
|
|
175
151
|
html {
|
|
176
152
|
margin: 0;
|
|
177
153
|
padding: 0;
|
|
@@ -18,6 +18,7 @@ declare module '@mui/material/styles/createPalette' {
|
|
|
18
18
|
declare module '@mui/material/styles' {
|
|
19
19
|
interface Palette {
|
|
20
20
|
background: TypeBackground;
|
|
21
|
+
chip: TypeChip;
|
|
21
22
|
header: TypeHeader;
|
|
22
23
|
layout: TypeLayout;
|
|
23
24
|
menu: TypeMenu;
|
|
@@ -36,6 +37,7 @@ declare module '@mui/material/styles' {
|
|
|
36
37
|
warning: string;
|
|
37
38
|
}
|
|
38
39
|
interface PaletteOptions {
|
|
40
|
+
chip: TypeChip;
|
|
39
41
|
header: TypeHeader;
|
|
40
42
|
layout: TypeLayout;
|
|
41
43
|
menu: TypeMenu;
|
|
@@ -113,6 +115,16 @@ declare module '@mui/material/styles' {
|
|
|
113
115
|
};
|
|
114
116
|
};
|
|
115
117
|
}
|
|
118
|
+
|
|
119
|
+
interface TypeChip {
|
|
120
|
+
color: {
|
|
121
|
+
error: string;
|
|
122
|
+
info: string;
|
|
123
|
+
neutral: string;
|
|
124
|
+
success: string;
|
|
125
|
+
warning: string;
|
|
126
|
+
};
|
|
127
|
+
}
|
|
116
128
|
}
|
|
117
129
|
|
|
118
130
|
declare module '@mui/material/Button' {
|
|
@@ -152,6 +164,15 @@ export const lightPalette: PaletteOptions = {
|
|
|
152
164
|
paper: '#FFFFFF',
|
|
153
165
|
tooltip: '#434E5B'
|
|
154
166
|
},
|
|
167
|
+
chip: {
|
|
168
|
+
color: {
|
|
169
|
+
error: '#FF6666',
|
|
170
|
+
info: '#1588D1',
|
|
171
|
+
neutral: BaseTokens.colorGrey300,
|
|
172
|
+
success: '#88B922',
|
|
173
|
+
warning: '#FD9B27'
|
|
174
|
+
}
|
|
175
|
+
},
|
|
155
176
|
divider: '#E3E3E3',
|
|
156
177
|
error: {
|
|
157
178
|
contrastText: '#000',
|
|
@@ -193,7 +214,7 @@ export const lightPalette: PaletteOptions = {
|
|
|
193
214
|
background: {
|
|
194
215
|
active: BaseTokens.colorBlue50,
|
|
195
216
|
default: 'transparent',
|
|
196
|
-
hover: BaseTokens.
|
|
217
|
+
hover: BaseTokens.colorGrey100
|
|
197
218
|
},
|
|
198
219
|
color: {
|
|
199
220
|
active: BaseTokens.colorBlue400,
|
|
@@ -208,7 +229,7 @@ export const lightPalette: PaletteOptions = {
|
|
|
208
229
|
background: {
|
|
209
230
|
active: BaseTokens.colorBlue50,
|
|
210
231
|
default: 'transparent',
|
|
211
|
-
hover: BaseTokens.
|
|
232
|
+
hover: BaseTokens.colorGrey100
|
|
212
233
|
},
|
|
213
234
|
color: {
|
|
214
235
|
active: BaseTokens.colorBlue400,
|
|
@@ -282,6 +303,15 @@ export const darkPalette: PaletteOptions = {
|
|
|
282
303
|
paper: '#212121',
|
|
283
304
|
tooltip: '#AAB4C0'
|
|
284
305
|
},
|
|
306
|
+
chip: {
|
|
307
|
+
color: {
|
|
308
|
+
error: '#D60101',
|
|
309
|
+
info: '#1CA9F4',
|
|
310
|
+
neutral: BaseTokens.colorGrey700,
|
|
311
|
+
success: '#5F8118',
|
|
312
|
+
warning: '#C55400'
|
|
313
|
+
}
|
|
314
|
+
},
|
|
285
315
|
divider: '#666666',
|
|
286
316
|
error: {
|
|
287
317
|
contrastText: '#fff',
|
|
@@ -321,9 +351,9 @@ export const darkPalette: PaletteOptions = {
|
|
|
321
351
|
background: BaseTokens.colorGrey950,
|
|
322
352
|
button: {
|
|
323
353
|
background: {
|
|
324
|
-
active: BaseTokens.
|
|
354
|
+
active: BaseTokens.colorBlue900,
|
|
325
355
|
default: 'transparent',
|
|
326
|
-
hover: BaseTokens.
|
|
356
|
+
hover: BaseTokens.colorGrey900
|
|
327
357
|
},
|
|
328
358
|
color: {
|
|
329
359
|
active: BaseTokens.colorBlue600,
|
|
@@ -336,9 +366,9 @@ export const darkPalette: PaletteOptions = {
|
|
|
336
366
|
},
|
|
337
367
|
item: {
|
|
338
368
|
background: {
|
|
339
|
-
active: BaseTokens.
|
|
369
|
+
active: BaseTokens.colorBlue900,
|
|
340
370
|
default: 'transparent',
|
|
341
|
-
hover: BaseTokens.
|
|
371
|
+
hover: BaseTokens.colorGrey900
|
|
342
372
|
},
|
|
343
373
|
color: {
|
|
344
374
|
active: BaseTokens.colorBlue400,
|
|
@@ -26,7 +26,7 @@ export type UseMutationQueryProps<T> = {
|
|
|
26
26
|
decoder?: JsonDecoder.Decoder<T>;
|
|
27
27
|
defaultFailureMessage?: string;
|
|
28
28
|
fetchHeaders?: HeadersInit;
|
|
29
|
-
getEndpoint: (
|
|
29
|
+
getEndpoint: (_meta?: Record<string, unknown> | unknown) => string;
|
|
30
30
|
httpCodesBypassErrorSnackbar?: Array<number>;
|
|
31
31
|
method: Method;
|
|
32
32
|
} & Omit<UseMutationOptions<T>, 'mutationFn'>;
|
|
@@ -45,17 +45,22 @@ const useMutationQuery = <T extends object>({
|
|
|
45
45
|
defaultFailureMessage,
|
|
46
46
|
fetchHeaders,
|
|
47
47
|
httpCodesBypassErrorSnackbar = [],
|
|
48
|
-
method
|
|
48
|
+
method,
|
|
49
|
+
onMutate,
|
|
50
|
+
onError
|
|
49
51
|
}: UseMutationQueryProps<T>): UseMutationQueryState<T> => {
|
|
50
52
|
const { showErrorMessage } = useSnackbar();
|
|
51
53
|
|
|
52
54
|
const queryData = useMutation<T | ResponseError>(
|
|
53
|
-
|
|
54
|
-
|
|
55
|
+
// @ts-expect-error useMutation / useMutationQuery is not typed correctly
|
|
56
|
+
(_payload: Record<string, unknown> | null): Promise<T | ResponseError> => {
|
|
57
|
+
const { _meta, ...payload } = _payload || {};
|
|
58
|
+
|
|
59
|
+
return customFetch<T>({
|
|
55
60
|
catchError,
|
|
56
61
|
decoder,
|
|
57
62
|
defaultFailureMessage,
|
|
58
|
-
endpoint: getEndpoint(
|
|
63
|
+
endpoint: getEndpoint(_meta),
|
|
59
64
|
headers: new Headers({
|
|
60
65
|
'Content-Type': 'application/x-www-form-urlencoded',
|
|
61
66
|
...fetchHeaders
|
|
@@ -63,7 +68,12 @@ const useMutationQuery = <T extends object>({
|
|
|
63
68
|
isMutation: true,
|
|
64
69
|
method,
|
|
65
70
|
payload
|
|
66
|
-
})
|
|
71
|
+
});
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
onError,
|
|
75
|
+
onMutate
|
|
76
|
+
}
|
|
67
77
|
);
|
|
68
78
|
|
|
69
79
|
const manageError = (): void => {
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
export type ContactAccessRightState =
|
|
2
|
+
| 'added'
|
|
3
|
+
| 'updated'
|
|
4
|
+
| 'removed'
|
|
5
|
+
| 'unchanged';
|
|
6
|
+
|
|
7
|
+
export type ContactAccessRightStateResource = {
|
|
8
|
+
contactAccessRight: ContactAccessRightResource;
|
|
9
|
+
state: ContactAccessRightState;
|
|
10
|
+
stateHistory: Array<ContactAccessRightState>;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export type ContactAccessRightResource = {
|
|
14
|
+
contact: ContactResource | ContactGroupResource | null;
|
|
15
|
+
role: RoleResource['role'];
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export type ContactResource = {
|
|
19
|
+
email?: string;
|
|
20
|
+
id?: number | string;
|
|
21
|
+
name: string;
|
|
22
|
+
type: 'contact';
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export const isContactResource = (
|
|
26
|
+
resource: ContactResource | ContactGroupResource | null
|
|
27
|
+
): resource is ContactResource => {
|
|
28
|
+
return resource?.type === 'contact';
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export type ContactGroupResource = {
|
|
32
|
+
id?: number | string;
|
|
33
|
+
name: string;
|
|
34
|
+
type: 'contact_group';
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export const isContactGroupResource = (
|
|
38
|
+
resource: ContactResource | ContactGroupResource | null
|
|
39
|
+
): resource is ContactResource => {
|
|
40
|
+
return resource?.type === 'contact_group';
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export type RoleResource = {
|
|
44
|
+
role: 'viewer' | 'editor' | string;
|
|
45
|
+
};
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { Meta, StoryObj } from '@storybook/react';
|
|
2
|
+
|
|
3
|
+
import { AccessRightsForm } from './AccessRightsForm';
|
|
4
|
+
import { Default as DefaultContactAccessRightsListStory } from './List/ContactAccessRightsList.stories';
|
|
5
|
+
import { Default as DefaultContactAccessRightInputStory } from './Input/ContactAccessRightInput.stories';
|
|
6
|
+
import { ContactAccessRightsListProps } from './List/ContactAccessRightsList';
|
|
7
|
+
import { ContactAccessRightInputProps } from './Input/ContactAccessRightInput';
|
|
8
|
+
import {
|
|
9
|
+
contactAccessRightsMock,
|
|
10
|
+
contactsAndGroupsMock
|
|
11
|
+
} from './__fixtures__/contactAccessRight.mock';
|
|
12
|
+
|
|
13
|
+
const meta: Meta<typeof AccessRightsForm> = {
|
|
14
|
+
component: AccessRightsForm
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export default meta;
|
|
18
|
+
type Story = StoryObj<typeof AccessRightsForm>;
|
|
19
|
+
|
|
20
|
+
export const Default: Story = {
|
|
21
|
+
args: {
|
|
22
|
+
initialValues: contactAccessRightsMock(12),
|
|
23
|
+
labels: {
|
|
24
|
+
actions: {
|
|
25
|
+
cancel: 'Cancel',
|
|
26
|
+
copyLink: 'Copy link',
|
|
27
|
+
copyLinkMessages: {
|
|
28
|
+
error: 'Unable to copy link',
|
|
29
|
+
success: 'Link copied'
|
|
30
|
+
},
|
|
31
|
+
submit: 'Update'
|
|
32
|
+
},
|
|
33
|
+
input: {
|
|
34
|
+
...DefaultContactAccessRightInputStory.args?.labels
|
|
35
|
+
} as ContactAccessRightInputProps['labels'],
|
|
36
|
+
list: {
|
|
37
|
+
...DefaultContactAccessRightsListStory.args?.labels
|
|
38
|
+
} as ContactAccessRightsListProps['labels'],
|
|
39
|
+
stats: {
|
|
40
|
+
added: 'added',
|
|
41
|
+
removed: 'removed',
|
|
42
|
+
updated: 'updated'
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
options: {
|
|
46
|
+
contacts: contactsAndGroupsMock(25),
|
|
47
|
+
roles: [{ role: 'viewer' }, { role: 'editor' }]
|
|
48
|
+
},
|
|
49
|
+
resourceLink: 'https://app.centreon.com/resource/1'
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
export const AsEmptyState: Story = {
|
|
54
|
+
args: {
|
|
55
|
+
labels: Default.args?.labels,
|
|
56
|
+
options: Default.args?.options,
|
|
57
|
+
resourceLink: Default.args?.resourceLink
|
|
58
|
+
}
|
|
59
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { makeStyles } from 'tss-react/mui';
|
|
2
|
+
|
|
3
|
+
const useStyles = makeStyles()((theme) => ({
|
|
4
|
+
accessRightsForm: {
|
|
5
|
+
display: 'flex',
|
|
6
|
+
flexDirection: 'column',
|
|
7
|
+
gap: theme.spacing(6),
|
|
8
|
+
|
|
9
|
+
maxWidth: '520px',
|
|
10
|
+
paddingTop: theme.spacing(3),
|
|
11
|
+
|
|
12
|
+
width: '100%'
|
|
13
|
+
},
|
|
14
|
+
accessRightsFormList: {
|
|
15
|
+
display: 'flex',
|
|
16
|
+
flexDirection: 'column',
|
|
17
|
+
gap: theme.spacing(1)
|
|
18
|
+
}
|
|
19
|
+
}));
|
|
20
|
+
|
|
21
|
+
export { useStyles };
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { ReactElement } from 'react';
|
|
2
|
+
|
|
3
|
+
import { useStyles } from './AccessRightsForm.styles';
|
|
4
|
+
import {
|
|
5
|
+
AccessRightsFormProvider,
|
|
6
|
+
AccessRightsFormProviderProps
|
|
7
|
+
} from './useAccessRightsForm';
|
|
8
|
+
import {
|
|
9
|
+
ContactAccessRightInput,
|
|
10
|
+
ContactAccessRightInputProps
|
|
11
|
+
} from './Input/ContactAccessRightInput';
|
|
12
|
+
import {
|
|
13
|
+
ContactAccessRightsList,
|
|
14
|
+
ContactAccessRightsListProps
|
|
15
|
+
} from './List/ContactAccessRightsList';
|
|
16
|
+
import {
|
|
17
|
+
AccessRightsFormActions,
|
|
18
|
+
AccessRightsFormActionsProps
|
|
19
|
+
} from './AccessRightsFormActions';
|
|
20
|
+
import {
|
|
21
|
+
AccessRightsStats,
|
|
22
|
+
AccessRightsStatsProps
|
|
23
|
+
} from './Stats/AccessRightsStats';
|
|
24
|
+
|
|
25
|
+
export type AccessRightsFormProps = {
|
|
26
|
+
labels: AccessRightsFormLabels;
|
|
27
|
+
onCancel?: AccessRightsFormActionsProps['onCancel'];
|
|
28
|
+
resourceLink: string;
|
|
29
|
+
} & Pick<
|
|
30
|
+
AccessRightsFormProviderProps,
|
|
31
|
+
'initialValues' | 'onSubmit' | 'options' | 'loadingStatus'
|
|
32
|
+
>;
|
|
33
|
+
|
|
34
|
+
export type AccessRightsFormLabels = {
|
|
35
|
+
actions: AccessRightsFormActionsProps['labels'];
|
|
36
|
+
input: ContactAccessRightInputProps['labels'];
|
|
37
|
+
list: ContactAccessRightsListProps['labels'];
|
|
38
|
+
stats: AccessRightsStatsProps['labels'];
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const AccessRightsForm = ({
|
|
42
|
+
labels,
|
|
43
|
+
onCancel,
|
|
44
|
+
resourceLink,
|
|
45
|
+
...providerProps
|
|
46
|
+
}: AccessRightsFormProps): ReactElement => {
|
|
47
|
+
const { classes } = useStyles();
|
|
48
|
+
|
|
49
|
+
return (
|
|
50
|
+
<AccessRightsFormProvider {...providerProps}>
|
|
51
|
+
<div className={classes.accessRightsForm}>
|
|
52
|
+
<ContactAccessRightInput labels={labels.input} />
|
|
53
|
+
<span className={classes.accessRightsFormList}>
|
|
54
|
+
<ContactAccessRightsList labels={labels.list} />
|
|
55
|
+
<AccessRightsStats labels={labels.stats} />
|
|
56
|
+
</span>
|
|
57
|
+
<AccessRightsFormActions
|
|
58
|
+
labels={labels.actions}
|
|
59
|
+
resourceLink={resourceLink}
|
|
60
|
+
onCancel={onCancel}
|
|
61
|
+
/>
|
|
62
|
+
</div>
|
|
63
|
+
</AccessRightsFormProvider>
|
|
64
|
+
);
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
export { AccessRightsForm };
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { ReactElement } from 'react';
|
|
2
|
+
|
|
3
|
+
import { Link as LinkIcon } from '@mui/icons-material';
|
|
4
|
+
|
|
5
|
+
import { Button } from '../../Button';
|
|
6
|
+
import { useStyles } from '../Form.styles';
|
|
7
|
+
import { useCopyToClipboard } from '../../../utils';
|
|
8
|
+
|
|
9
|
+
import { useAccessRightsForm } from './useAccessRightsForm';
|
|
10
|
+
|
|
11
|
+
export type AccessRightsFormActionsProps = {
|
|
12
|
+
labels: AccessRightsFormActionsLabels;
|
|
13
|
+
onCancel?: () => void;
|
|
14
|
+
resourceLink: string;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
type AccessRightsFormActionsLabels = {
|
|
18
|
+
cancel: string;
|
|
19
|
+
copyLink: string;
|
|
20
|
+
copyLinkMessages: {
|
|
21
|
+
error: string;
|
|
22
|
+
success: string;
|
|
23
|
+
};
|
|
24
|
+
submit: string;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const AccessRightsFormActions = ({
|
|
28
|
+
labels,
|
|
29
|
+
onCancel,
|
|
30
|
+
resourceLink
|
|
31
|
+
}: AccessRightsFormActionsProps): ReactElement => {
|
|
32
|
+
const { classes } = useStyles();
|
|
33
|
+
const { isDirty, submit } = useAccessRightsForm();
|
|
34
|
+
const { copy } = useCopyToClipboard({
|
|
35
|
+
errorMessage: labels.copyLinkMessages.error,
|
|
36
|
+
successMessage: labels.copyLinkMessages.success
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
return (
|
|
40
|
+
<div className={classes.actions}>
|
|
41
|
+
<span>
|
|
42
|
+
<Button
|
|
43
|
+
aria-label={labels.copyLink}
|
|
44
|
+
data-testid="copy-link"
|
|
45
|
+
icon={<LinkIcon />}
|
|
46
|
+
iconVariant="start"
|
|
47
|
+
size="small"
|
|
48
|
+
variant="ghost"
|
|
49
|
+
onClick={() => copy(resourceLink)}
|
|
50
|
+
>
|
|
51
|
+
{labels.copyLink}
|
|
52
|
+
</Button>
|
|
53
|
+
</span>
|
|
54
|
+
<span>
|
|
55
|
+
<Button
|
|
56
|
+
aria-label={labels.cancel}
|
|
57
|
+
data-testid="cancel"
|
|
58
|
+
size="medium"
|
|
59
|
+
variant="secondary"
|
|
60
|
+
onClick={() => onCancel?.()}
|
|
61
|
+
>
|
|
62
|
+
{labels.cancel}
|
|
63
|
+
</Button>
|
|
64
|
+
<Button
|
|
65
|
+
aria-label={labels.submit}
|
|
66
|
+
data-testid="submit"
|
|
67
|
+
disabled={!isDirty}
|
|
68
|
+
size="medium"
|
|
69
|
+
type="submit"
|
|
70
|
+
variant="primary"
|
|
71
|
+
onClick={submit}
|
|
72
|
+
>
|
|
73
|
+
{labels.submit}
|
|
74
|
+
</Button>
|
|
75
|
+
</span>
|
|
76
|
+
</div>
|
|
77
|
+
);
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
export { AccessRightsFormActions };
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { ReactElement } from 'react';
|
|
2
|
+
|
|
3
|
+
import { useFormikContext } from 'formik';
|
|
4
|
+
|
|
5
|
+
import { Add as AddIcon } from '@mui/icons-material';
|
|
6
|
+
|
|
7
|
+
import { ContactAccessRightResource } from '../AccessRights.resource';
|
|
8
|
+
import { IconButton } from '../../../Button';
|
|
9
|
+
|
|
10
|
+
type AddActionProps = {
|
|
11
|
+
label?: string;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
const AddAction = ({ label }: AddActionProps): ReactElement => {
|
|
15
|
+
const { dirty, isValid, submitForm } =
|
|
16
|
+
useFormikContext<Partial<ContactAccessRightResource>>();
|
|
17
|
+
|
|
18
|
+
return (
|
|
19
|
+
<IconButton
|
|
20
|
+
aria-label={label}
|
|
21
|
+
data-testid="add"
|
|
22
|
+
disabled={!dirty || !isValid}
|
|
23
|
+
icon={<AddIcon />}
|
|
24
|
+
size="medium"
|
|
25
|
+
variant="primary"
|
|
26
|
+
onClick={submitForm}
|
|
27
|
+
/>
|
|
28
|
+
);
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export { AddAction };
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { ReactElement, ReactNode } from 'react';
|
|
2
|
+
|
|
3
|
+
import { Meta, StoryObj } from '@storybook/react';
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
AccessRightsFormProvider,
|
|
7
|
+
AccessRightsFormProviderProps
|
|
8
|
+
} from '../useAccessRightsForm';
|
|
9
|
+
import { contactsAndGroupsMock } from '../__fixtures__/contactAccessRight.mock';
|
|
10
|
+
|
|
11
|
+
import { ContactAccessRightInput } from './ContactAccessRightInput';
|
|
12
|
+
|
|
13
|
+
const meta: Meta<typeof ContactAccessRightInput> = {
|
|
14
|
+
component: ContactAccessRightInput,
|
|
15
|
+
title: 'components/Form/AccessRights/ContactAccessRightInput'
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export default meta;
|
|
19
|
+
type Story = StoryObj<typeof ContactAccessRightInput>;
|
|
20
|
+
|
|
21
|
+
const Wrapper = ({ children }: { children: ReactNode }): ReactElement => {
|
|
22
|
+
const options: AccessRightsFormProviderProps['options'] = {
|
|
23
|
+
contacts: contactsAndGroupsMock(25),
|
|
24
|
+
roles: [{ role: 'viewer' }, { role: 'editor' }]
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
return (
|
|
28
|
+
<AccessRightsFormProvider options={options}>
|
|
29
|
+
{children}
|
|
30
|
+
</AccessRightsFormProvider>
|
|
31
|
+
);
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export const Default: Story = {
|
|
35
|
+
args: {
|
|
36
|
+
labels: {
|
|
37
|
+
actions: {
|
|
38
|
+
add: 'Add'
|
|
39
|
+
},
|
|
40
|
+
fields: {
|
|
41
|
+
contact: {
|
|
42
|
+
group: 'group',
|
|
43
|
+
noOptionsText: 'No contacts found',
|
|
44
|
+
placeholder: 'Add Contact...'
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
render: (args) => (
|
|
50
|
+
<Wrapper>
|
|
51
|
+
<ContactAccessRightInput labels={args.labels} />
|
|
52
|
+
</Wrapper>
|
|
53
|
+
)
|
|
54
|
+
};
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { ReactElement } from 'react';
|
|
2
|
+
|
|
3
|
+
import { FormikProvider, useFormik } from 'formik';
|
|
4
|
+
import { mixed, object } from 'yup';
|
|
5
|
+
|
|
6
|
+
import { useAccessRightsForm } from '../useAccessRightsForm';
|
|
7
|
+
import { ContactAccessRightResource } from '../AccessRights.resource';
|
|
8
|
+
|
|
9
|
+
import { useStyles } from './ContactAccessRightsInput.styles';
|
|
10
|
+
import { ContactInputField, ContactInputFieldProps } from './ContactInputField';
|
|
11
|
+
import { AddAction } from './AddAction';
|
|
12
|
+
import { RoleInputField } from './RoleInputField';
|
|
13
|
+
|
|
14
|
+
export type ContactAccessRightInputProps = {
|
|
15
|
+
labels: ContactAccessRightInputLabels;
|
|
16
|
+
onAddContactAccessRight?: (value: ContactAccessRightResource) => void;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
type ContactAccessRightInputLabels = {
|
|
20
|
+
actions?: {
|
|
21
|
+
add: string;
|
|
22
|
+
};
|
|
23
|
+
fields: {
|
|
24
|
+
contact: ContactInputFieldProps['labels'];
|
|
25
|
+
role?: {
|
|
26
|
+
label: string;
|
|
27
|
+
};
|
|
28
|
+
};
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const ContactAccessRightInput = ({
|
|
32
|
+
labels,
|
|
33
|
+
onAddContactAccessRight
|
|
34
|
+
}: ContactAccessRightInputProps): ReactElement => {
|
|
35
|
+
const { classes } = useStyles();
|
|
36
|
+
const { addContactAccessRight, options } = useAccessRightsForm();
|
|
37
|
+
|
|
38
|
+
const formik = useFormik<ContactAccessRightResource>({
|
|
39
|
+
initialValues: { contact: null, role: 'viewer' },
|
|
40
|
+
onSubmit: (values, { resetForm }) => {
|
|
41
|
+
addContactAccessRight(values);
|
|
42
|
+
onAddContactAccessRight?.(values);
|
|
43
|
+
resetForm();
|
|
44
|
+
},
|
|
45
|
+
validationSchema: object({
|
|
46
|
+
contact: mixed()
|
|
47
|
+
.test(
|
|
48
|
+
'is-available-contact',
|
|
49
|
+
(d) => `${d.path} is not a contact available from the list`,
|
|
50
|
+
(value) => !!options.contacts?.find((c) => c.id === value?.id)
|
|
51
|
+
)
|
|
52
|
+
.required(),
|
|
53
|
+
role: mixed().oneOf(['viewer', 'editor']).required()
|
|
54
|
+
})
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
return (
|
|
58
|
+
<div className={classes.contactAccessRightsInput}>
|
|
59
|
+
<FormikProvider value={formik}>
|
|
60
|
+
<ContactInputField
|
|
61
|
+
id="contact"
|
|
62
|
+
labels={labels.fields.contact}
|
|
63
|
+
name="contact"
|
|
64
|
+
/>
|
|
65
|
+
<RoleInputField id="role" name="role" {...labels?.fields?.role} />
|
|
66
|
+
<AddAction />
|
|
67
|
+
</FormikProvider>
|
|
68
|
+
</div>
|
|
69
|
+
);
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
export { ContactAccessRightInput };
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { makeStyles } from 'tss-react/mui';
|
|
2
|
+
|
|
3
|
+
const useStyles = makeStyles()((theme) => ({
|
|
4
|
+
contactAccessRightsInput: {
|
|
5
|
+
alignItems: 'center',
|
|
6
|
+
display: 'flex',
|
|
7
|
+
flexGrow: 1,
|
|
8
|
+
gap: theme.spacing(2),
|
|
9
|
+
maxWidth: '520px',
|
|
10
|
+
|
|
11
|
+
width: '100%'
|
|
12
|
+
},
|
|
13
|
+
contactInput: {
|
|
14
|
+
'& .MuiOutlinedInput-root, & .MuiInputBase-input.MuiOutlinedInput-input': {
|
|
15
|
+
height: 'unset',
|
|
16
|
+
lineHeight: '1.5'
|
|
17
|
+
},
|
|
18
|
+
flexGrow: 1
|
|
19
|
+
}
|
|
20
|
+
}));
|
|
21
|
+
|
|
22
|
+
export { useStyles };
|