From b4a1a3c363f89cefe7ddb4e9ac74fcfd03a246a3 Mon Sep 17 00:00:00 2001 From: Yoann Fievez Date: Wed, 16 Oct 2024 17:28:31 +0200 Subject: [PATCH] feat(pci-load-balancer): add create member modal ref: DTCORE-2660 Signed-off-by: Yoann Fievez --- .../ManagerButton/ManagerButton.spec.tsx | 14 +- .../ManagerText/ManagerText.spec.tsx | 11 +- .../content/headers/headers.spec.tsx | 5 +- .../navigation/menus/action/action.spec.tsx | 5 +- .../pools/members/create/Messages_de_DE.json | 9 + .../pools/members/create/Messages_en_GB.json | 9 + .../pools/members/create/Messages_es_ES.json | 9 + .../pools/members/create/Messages_fr_CA.json | 9 + .../pools/members/create/Messages_fr_FR.json | 9 + .../pools/members/create/Messages_it_IT.json | 9 + .../pools/members/create/Messages_pl_PL.json | 9 + .../pools/members/create/Messages_pt_PT.json | 9 + .../src/api/data/pool-member.ts | 15 ++ .../src/api/hook/usePoolMember.tsx | 33 ++++ .../apps/pci-load-balancer/src/constants.ts | 4 + .../detail/members/create/Create.page.tsx | 185 ++++++++++++++++++ .../apps/pci-load-balancer/src/routes.tsx | 8 + 17 files changed, 339 insertions(+), 13 deletions(-) create mode 100644 packages/manager/apps/pci-load-balancer/public/translations/pools/members/create/Messages_de_DE.json create mode 100644 packages/manager/apps/pci-load-balancer/public/translations/pools/members/create/Messages_en_GB.json create mode 100644 packages/manager/apps/pci-load-balancer/public/translations/pools/members/create/Messages_es_ES.json create mode 100644 packages/manager/apps/pci-load-balancer/public/translations/pools/members/create/Messages_fr_CA.json create mode 100644 packages/manager/apps/pci-load-balancer/public/translations/pools/members/create/Messages_fr_FR.json create mode 100644 packages/manager/apps/pci-load-balancer/public/translations/pools/members/create/Messages_it_IT.json create mode 100644 packages/manager/apps/pci-load-balancer/public/translations/pools/members/create/Messages_pl_PL.json create mode 100644 packages/manager/apps/pci-load-balancer/public/translations/pools/members/create/Messages_pt_PT.json create mode 100644 packages/manager/apps/pci-load-balancer/src/pages/detail/pools/detail/members/create/Create.page.tsx diff --git a/packages/manager-react-components/src/components/ManagerButton/ManagerButton.spec.tsx b/packages/manager-react-components/src/components/ManagerButton/ManagerButton.spec.tsx index a6689c9ba664..f6fbe4f19ab9 100644 --- a/packages/manager-react-components/src/components/ManagerButton/ManagerButton.spec.tsx +++ b/packages/manager-react-components/src/components/ManagerButton/ManagerButton.spec.tsx @@ -11,8 +11,9 @@ const renderComponent = (props: ManagerButtonProps) => { return render(); }; -const mockedHook = - useAuthorizationIam as unknown as jest.Mock; +const mockedHook = (useAuthorizationIam as unknown) as jest.Mock< + IamAuthorizationResponse +>; describe('ManagerButton tests', () => { afterEach(() => { @@ -27,7 +28,8 @@ describe('ManagerButton tests', () => { isFetched: true, }); renderComponent({ - urn: 'urn:v9:eu:resource:manager-react-components:vrz-a878-dsflkds-fdsfsd', + urn: + 'urn:v9:eu:resource:manager-react-components:vrz-a878-dsflkds-fdsfsd', iamActions: [ 'manager-react-components:apiovh:manager-react-components/attach-action', ], @@ -43,7 +45,8 @@ describe('ManagerButton tests', () => { isFetched: true, }); renderComponent({ - urn: 'urn:v9:eu:resource:manager-react-components:vrz-a878-dsflkds-fdsfsd', + urn: + 'urn:v9:eu:resource:manager-react-components:vrz-a878-dsflkds-fdsfsd', iamActions: [ 'manager-react-components:apiovh:manager-react-components/attach', ], @@ -64,7 +67,8 @@ describe('ManagerButton tests', () => { isFetched: true, }); renderComponent({ - urn: 'urn:v9:eu:resource:manager-react-components:vrz-a878-dsflkds-fdsfsd', + urn: + 'urn:v9:eu:resource:manager-react-components:vrz-a878-dsflkds-fdsfsd', iamActions: [ 'manager-react-components:apiovh:manager-react-components/attach-action', ], diff --git a/packages/manager-react-components/src/components/ManagerText/ManagerText.spec.tsx b/packages/manager-react-components/src/components/ManagerText/ManagerText.spec.tsx index faeb70215395..010d4cd11ad4 100644 --- a/packages/manager-react-components/src/components/ManagerText/ManagerText.spec.tsx +++ b/packages/manager-react-components/src/components/ManagerText/ManagerText.spec.tsx @@ -10,8 +10,9 @@ jest.mock('../../hooks/iam'); const renderComponent = (props: ManagerTextProps) => { return render(); }; -const mockedHook = - useAuthorizationIam as unknown as jest.Mock; +const mockedHook = (useAuthorizationIam as unknown) as jest.Mock< + IamAuthorizationResponse +>; describe('ManagerText tests', () => { afterEach(() => { @@ -26,7 +27,8 @@ describe('ManagerText tests', () => { isFetched: true, }); renderComponent({ - urn: 'urn:v9:eu:resource:manager-react-components:vrz-a878-dsflkds-fdsfsd', + urn: + 'urn:v9:eu:resource:manager-react-components:vrz-a878-dsflkds-fdsfsd', iamActions: [ 'manager-react-components:apiovh:manager-react-components/get-display', ], @@ -43,7 +45,8 @@ describe('ManagerText tests', () => { isFetched: true, }); renderComponent({ - urn: 'urn:v9:eu:resource:manager-react-components:vrz-a878-dsflkds-fdsfsd', + urn: + 'urn:v9:eu:resource:manager-react-components:vrz-a878-dsflkds-fdsfsd', iamActions: [ 'manager-react-components:apiovh:manager-react-components/get-display', ], diff --git a/packages/manager-react-components/src/components/content/headers/headers.spec.tsx b/packages/manager-react-components/src/components/content/headers/headers.spec.tsx index 53b1eb6cb9f1..44cff6c17ef2 100644 --- a/packages/manager-react-components/src/components/content/headers/headers.spec.tsx +++ b/packages/manager-react-components/src/components/content/headers/headers.spec.tsx @@ -11,8 +11,9 @@ import { useAuthorizationIam } from '../../../hooks/iam'; jest.mock('../../../hooks/iam'); -const mockedHook = - useAuthorizationIam as unknown as jest.Mock; +const mockedHook = (useAuthorizationIam as unknown) as jest.Mock< + IamAuthorizationResponse +>; describe('Headers component', () => { beforeEach(() => { diff --git a/packages/manager-react-components/src/components/navigation/menus/action/action.spec.tsx b/packages/manager-react-components/src/components/navigation/menus/action/action.spec.tsx index b041053b341e..21f912d293bb 100644 --- a/packages/manager-react-components/src/components/navigation/menus/action/action.spec.tsx +++ b/packages/manager-react-components/src/components/navigation/menus/action/action.spec.tsx @@ -29,8 +29,9 @@ const actionItems: ActionMenuProps = { const setupSpecTest = async (customProps?: Partial) => waitFor(() => render()); -const mockedHook = - useAuthorizationIam as unknown as jest.Mock; +const mockedHook = (useAuthorizationIam as unknown) as jest.Mock< + IamAuthorizationResponse +>; describe('ActionMenu', () => { it('renders menu actions correctly', async () => { diff --git a/packages/manager/apps/pci-load-balancer/public/translations/pools/members/create/Messages_de_DE.json b/packages/manager/apps/pci-load-balancer/public/translations/pools/members/create/Messages_de_DE.json new file mode 100644 index 000000000000..d58221cfbdfb --- /dev/null +++ b/packages/manager/apps/pci-load-balancer/public/translations/pools/members/create/Messages_de_DE.json @@ -0,0 +1,9 @@ +{ + "octavia_load_balancer_pools_detail_members_create_title": "Manuell hinzufügen", + "octavia_load_balancer_pools_detail_members_create_description": "Fügen Sie ein Mitglied manuell hinzu, wenn es nicht in der Tabelle der verfügbaren Instanzen aufgeführt ist.", + "octavia_load_balancer_pools_detail_members_create_cancel": "Abbrechen", + "octavia_load_balancer_pools_detail_members_create_confirm": "Hinzufügen", + "octavia_load_balancer_pools_detail_members_create_name_label": "Name (optional)", + "octavia_load_balancer_pools_detail_members_create_address_ip_label": "IP-Adresse", + "octavia_load_balancer_pools_detail_members_create_port_label": "Port" +} diff --git a/packages/manager/apps/pci-load-balancer/public/translations/pools/members/create/Messages_en_GB.json b/packages/manager/apps/pci-load-balancer/public/translations/pools/members/create/Messages_en_GB.json new file mode 100644 index 000000000000..1707a0443ec4 --- /dev/null +++ b/packages/manager/apps/pci-load-balancer/public/translations/pools/members/create/Messages_en_GB.json @@ -0,0 +1,9 @@ +{ + "octavia_load_balancer_pools_detail_members_create_title": "Add Manually", + "octavia_load_balancer_pools_detail_members_create_description": "Manually add a member if it is not found in the available instances table.", + "octavia_load_balancer_pools_detail_members_create_cancel": "Cancel", + "octavia_load_balancer_pools_detail_members_create_confirm": "Add", + "octavia_load_balancer_pools_detail_members_create_name_label": "Surname (optional)", + "octavia_load_balancer_pools_detail_members_create_address_ip_label": "IP address", + "octavia_load_balancer_pools_detail_members_create_port_label": "Port" +} diff --git a/packages/manager/apps/pci-load-balancer/public/translations/pools/members/create/Messages_es_ES.json b/packages/manager/apps/pci-load-balancer/public/translations/pools/members/create/Messages_es_ES.json new file mode 100644 index 000000000000..3668337a0ba2 --- /dev/null +++ b/packages/manager/apps/pci-load-balancer/public/translations/pools/members/create/Messages_es_ES.json @@ -0,0 +1,9 @@ +{ + "octavia_load_balancer_pools_detail_members_create_title": "Añadir manualmente", + "octavia_load_balancer_pools_detail_members_create_description": "Añadir un miembro manualmente si no aparece en la tabla de instancias disponibles.", + "octavia_load_balancer_pools_detail_members_create_cancel": "Cancelar", + "octavia_load_balancer_pools_detail_members_create_confirm": "Añadir", + "octavia_load_balancer_pools_detail_members_create_name_label": "Nombre (opcional)", + "octavia_load_balancer_pools_detail_members_create_address_ip_label": "Dirección IP", + "octavia_load_balancer_pools_detail_members_create_port_label": "Puerto" +} diff --git a/packages/manager/apps/pci-load-balancer/public/translations/pools/members/create/Messages_fr_CA.json b/packages/manager/apps/pci-load-balancer/public/translations/pools/members/create/Messages_fr_CA.json new file mode 100644 index 000000000000..a4f5d86a7342 --- /dev/null +++ b/packages/manager/apps/pci-load-balancer/public/translations/pools/members/create/Messages_fr_CA.json @@ -0,0 +1,9 @@ +{ + "octavia_load_balancer_pools_detail_members_create_title": "Ajouter Manuellement", + "octavia_load_balancer_pools_detail_members_create_description": "Ajouter un membre manuellement s'il est introuvable dans le tableau des instances disponibles.", + "octavia_load_balancer_pools_detail_members_create_cancel": "Annuler", + "octavia_load_balancer_pools_detail_members_create_confirm": "Ajouter", + "octavia_load_balancer_pools_detail_members_create_name_label": "Nom (optionnel)", + "octavia_load_balancer_pools_detail_members_create_address_ip_label": "Adresse IP", + "octavia_load_balancer_pools_detail_members_create_port_label": "Port" +} diff --git a/packages/manager/apps/pci-load-balancer/public/translations/pools/members/create/Messages_fr_FR.json b/packages/manager/apps/pci-load-balancer/public/translations/pools/members/create/Messages_fr_FR.json new file mode 100644 index 000000000000..a4f5d86a7342 --- /dev/null +++ b/packages/manager/apps/pci-load-balancer/public/translations/pools/members/create/Messages_fr_FR.json @@ -0,0 +1,9 @@ +{ + "octavia_load_balancer_pools_detail_members_create_title": "Ajouter Manuellement", + "octavia_load_balancer_pools_detail_members_create_description": "Ajouter un membre manuellement s'il est introuvable dans le tableau des instances disponibles.", + "octavia_load_balancer_pools_detail_members_create_cancel": "Annuler", + "octavia_load_balancer_pools_detail_members_create_confirm": "Ajouter", + "octavia_load_balancer_pools_detail_members_create_name_label": "Nom (optionnel)", + "octavia_load_balancer_pools_detail_members_create_address_ip_label": "Adresse IP", + "octavia_load_balancer_pools_detail_members_create_port_label": "Port" +} diff --git a/packages/manager/apps/pci-load-balancer/public/translations/pools/members/create/Messages_it_IT.json b/packages/manager/apps/pci-load-balancer/public/translations/pools/members/create/Messages_it_IT.json new file mode 100644 index 000000000000..a90396c235cd --- /dev/null +++ b/packages/manager/apps/pci-load-balancer/public/translations/pools/members/create/Messages_it_IT.json @@ -0,0 +1,9 @@ +{ + "octavia_load_balancer_pools_detail_members_create_title": "Aggiungi manualmente", + "octavia_load_balancer_pools_detail_members_create_description": "Aggiungi un membro manualmente se non riesci a trovarlo nella tabella delle istanze disponibili.", + "octavia_load_balancer_pools_detail_members_create_cancel": "Annullare", + "octavia_load_balancer_pools_detail_members_create_confirm": "Aggiungi", + "octavia_load_balancer_pools_detail_members_create_name_label": "Nome (facoltativo)", + "octavia_load_balancer_pools_detail_members_create_address_ip_label": "Indirizzo IP", + "octavia_load_balancer_pools_detail_members_create_port_label": "Porta" +} diff --git a/packages/manager/apps/pci-load-balancer/public/translations/pools/members/create/Messages_pl_PL.json b/packages/manager/apps/pci-load-balancer/public/translations/pools/members/create/Messages_pl_PL.json new file mode 100644 index 000000000000..babb2a71ba34 --- /dev/null +++ b/packages/manager/apps/pci-load-balancer/public/translations/pools/members/create/Messages_pl_PL.json @@ -0,0 +1,9 @@ +{ + "octavia_load_balancer_pools_detail_members_create_title": "Dodaj ręcznie", + "octavia_load_balancer_pools_detail_members_create_description": "Jeśli użytkownika nie ma w tabeli dostępnych instancji, dodaj go ręcznie.", + "octavia_load_balancer_pools_detail_members_create_cancel": "Anuluj", + "octavia_load_balancer_pools_detail_members_create_confirm": "Dodaj", + "octavia_load_balancer_pools_detail_members_create_name_label": "Nazwa (opcjonalnie)", + "octavia_load_balancer_pools_detail_members_create_address_ip_label": "Adres IP", + "octavia_load_balancer_pools_detail_members_create_port_label": "Port" +} diff --git a/packages/manager/apps/pci-load-balancer/public/translations/pools/members/create/Messages_pt_PT.json b/packages/manager/apps/pci-load-balancer/public/translations/pools/members/create/Messages_pt_PT.json new file mode 100644 index 000000000000..d2a78affcf67 --- /dev/null +++ b/packages/manager/apps/pci-load-balancer/public/translations/pools/members/create/Messages_pt_PT.json @@ -0,0 +1,9 @@ +{ + "octavia_load_balancer_pools_detail_members_create_title": "Adicionar Manualmente", + "octavia_load_balancer_pools_detail_members_create_description": "Adicionar um membro manualmente, se não for possível encontrá-lo na tabela das instâncias disponíveis.", + "octavia_load_balancer_pools_detail_members_create_cancel": "Anular", + "octavia_load_balancer_pools_detail_members_create_confirm": "Adicionar", + "octavia_load_balancer_pools_detail_members_create_name_label": "Sobrenome (facultativo)", + "octavia_load_balancer_pools_detail_members_create_address_ip_label": "Endereço IP", + "octavia_load_balancer_pools_detail_members_create_port_label": "Porta" +} diff --git a/packages/manager/apps/pci-load-balancer/src/api/data/pool-member.ts b/packages/manager/apps/pci-load-balancer/src/api/data/pool-member.ts index c4fc975e6b36..27f171b48c22 100644 --- a/packages/manager/apps/pci-load-balancer/src/api/data/pool-member.ts +++ b/packages/manager/apps/pci-load-balancer/src/api/data/pool-member.ts @@ -65,3 +65,18 @@ export const updatePoolMemberName = async ( ); return data; }; + +export const createPoolMembers = async ( + projectId: string, + region: string, + poolId: string, + members: TPoolMember[], +) => { + const { data } = await v6.post( + `/cloud/project/${projectId}/region/${region}/loadbalancing/pool/${poolId}/member`, + { + members, + }, + ); + return data; +}; diff --git a/packages/manager/apps/pci-load-balancer/src/api/hook/usePoolMember.tsx b/packages/manager/apps/pci-load-balancer/src/api/hook/usePoolMember.tsx index d0694732eeb1..30a901243a7a 100644 --- a/packages/manager/apps/pci-load-balancer/src/api/hook/usePoolMember.tsx +++ b/packages/manager/apps/pci-load-balancer/src/api/hook/usePoolMember.tsx @@ -4,6 +4,7 @@ import { useMutation, useQuery } from '@tanstack/react-query'; import { useMemo } from 'react'; import { TPoolMember, + createPoolMembers, deletePoolMember, getPoolMember, getPoolMembers, @@ -139,3 +140,35 @@ export const useUpdatePoolMember = ({ ...mutation, }; }; + +type CreatePoolMembersProps = { + projectId: string; + poolId: string; + region: string; + onError: (cause: Error) => void; + onSuccess: () => void; +}; + +export const useCreatePoolMembers = ({ + projectId, + poolId, + region, + onError, + onSuccess, +}: CreatePoolMembersProps) => { + const mutation = useMutation({ + mutationFn: async (members: TPoolMember[]) => + createPoolMembers(projectId, region, poolId, members), + onError, + onSuccess: async () => { + await queryClient.invalidateQueries({ + queryKey: ['poolMembers', projectId], + }); + onSuccess(); + }, + }); + return { + createPoolMembers: (members: TPoolMember[]) => mutation.mutate(members), + ...mutation, + }; +}; diff --git a/packages/manager/apps/pci-load-balancer/src/constants.ts b/packages/manager/apps/pci-load-balancer/src/constants.ts index 2c19b867eba1..4d5f0fd1833d 100644 --- a/packages/manager/apps/pci-load-balancer/src/constants.ts +++ b/packages/manager/apps/pci-load-balancer/src/constants.ts @@ -251,3 +251,7 @@ export const VALUE_REGEX_BY_TYPE = { }; export const KEY_REGEX = "^[a-zA-Z0-9!#$%&'*+-.^_`|~]+$"; + +export const REGEX = { + ip: /^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?).(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?).(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?).(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/, +}; diff --git a/packages/manager/apps/pci-load-balancer/src/pages/detail/pools/detail/members/create/Create.page.tsx b/packages/manager/apps/pci-load-balancer/src/pages/detail/pools/detail/members/create/Create.page.tsx new file mode 100644 index 000000000000..83e56300d8f8 --- /dev/null +++ b/packages/manager/apps/pci-load-balancer/src/pages/detail/pools/detail/members/create/Create.page.tsx @@ -0,0 +1,185 @@ +import { useNotifications } from '@ovh-ux/manager-react-components'; +import { useMemo, useState } from 'react'; +import { Translation, useTranslation } from 'react-i18next'; +import { useNavigate, useParams } from 'react-router-dom'; +import { ApiError } from '@ovh-ux/manager-core-api'; +import { PciModal } from '@ovh-ux/manager-pci-common'; +import { OsdsFormField, OsdsInput } from '@ovhcloud/ods-components/react'; +import { ODS_INPUT_TYPE } from '@ovhcloud/ods-components'; +import { useCreatePoolMembers } from '@/api/hook/usePoolMember'; +import { TPoolMember } from '@/api/data/pool-member'; +import LabelComponent from '@/components/form/Label.component'; +import { REGEX } from '@/constants'; + +export default function CreatePage() { + const { addSuccess, addError } = useNotifications(); + const { projectId, region, poolId } = useParams(); + const { t: tCreate } = useTranslation('pools/members/create'); + const { t: tPciCommon } = useTranslation('pci-common'); + const navigate = useNavigate(); + const onClose = () => { + navigate('..'); + }; + + const [poolMember, setPoolMember] = useState({ + name: '', + address: '', + protocolPort: 9000, + } as TPoolMember); + const [isTouched, setIsTouched] = useState({ + address: false, + protocolPort: false, + }); + const { + createPoolMembers, + isPending: isPendingCreate, + } = useCreatePoolMembers({ + projectId, + region, + poolId, + onError(error: ApiError) { + addError( + + {(_t) => + _t('octavia_load_balancer_global_error', { + message: error?.response?.data?.message || error?.message || null, + requestId: error?.config?.headers['X-OVH-MANAGER-REQUEST-ID'], + }) + } + , + true, + ); + onClose(); + }, + onSuccess() { + addSuccess( + + {(_t) => + _t('octavia_load_balancer_pools_detail_members_create_success') + } + , + true, + ); + navigate('..'); + }, + }); + const onConfirm = () => { + createPoolMembers([poolMember]); + }; + + const onCancel = () => { + navigate('..'); + }; + const errorMessageAddress = useMemo(() => { + if (isTouched.address) { + if (!poolMember.address.trim()) { + return tPciCommon('common_field_error_required'); + } + if (!RegExp(REGEX.ip).test(poolMember.address)) { + return tPciCommon('common_field_error_pattern'); + } + } + return ''; + }, [poolMember.address, isTouched.address]); + + const errorMessageProtocolPort = useMemo(() => { + if (isTouched.protocolPort) { + if (!poolMember.protocolPort) { + return tPciCommon('common_field_error_required'); + } + if (!(poolMember.protocolPort >= 1 && poolMember.protocolPort <= 65535)) { + return tPciCommon('common_field_error_max', { max: 65535 }); + } + } + return ''; + }, [poolMember.protocolPort, isTouched.protocolPort]); + return ( + + + + { + setPoolMember((state) => ({ + ...state, + name: event.detail.value.trim(), + })); + }} + /> + + + + { + setPoolMember((state) => ({ + ...state, + address: event.detail.value.trim(), + })); + }} + onOdsInputBlur={() => { + setIsTouched((state) => ({ + ...state, + address: true, + })); + }} + /> + + + + { + setPoolMember((state) => ({ + ...state, + protocolPort: event.detail.value + ? parseInt(event.detail.value, 10) + : 0, + })); + }} + onOdsInputBlur={() => { + setIsTouched((state) => ({ + ...state, + protocolPort: true, + })); + }} + /> + + + ); +} diff --git a/packages/manager/apps/pci-load-balancer/src/routes.tsx b/packages/manager/apps/pci-load-balancer/src/routes.tsx index d1779c283da8..babc9f2cf281 100644 --- a/packages/manager/apps/pci-load-balancer/src/routes.tsx +++ b/packages/manager/apps/pci-load-balancer/src/routes.tsx @@ -31,6 +31,7 @@ export const ROUTE_PATHS = { POOL_DETAIL: ':region/:loadBalancerId/pools/:poolId', POOL_MEMBERS: 'members', POOL_MEMBERS_LIST: 'list', + POOL_MEMBERS_CREATE: 'create', POOL_MEMBERS_DELETE: ':memberId/delete', POOL_MEMBERS_EDIT: ':memberId/edit', STATISTICS: 'statistics', @@ -100,6 +101,9 @@ const PoolsMembersDeletePage = lazy(() => const PoolsMembersEditPage = lazy(() => import('@/pages/detail/pools/detail/members/edit/Edit.page'), ); +const PoolsMembersCreatePage = lazy(() => + import('@/pages/detail/pools/detail/members/create/Create.page'), +); const PoolsMembersPage = lazy(() => import('@/pages/detail/pools/detail/members/Member.page'), ); @@ -190,6 +194,10 @@ const Routes = ( path={ROUTE_PATHS.POOL_MEMBERS_EDIT} Component={PoolsMembersEditPage} /> +