diff --git a/web/gds-user-ui/src/App.tsx b/web/gds-user-ui/src/App.tsx index a304e0a79..aa6c63768 100644 --- a/web/gds-user-ui/src/App.tsx +++ b/web/gds-user-ui/src/App.tsx @@ -5,14 +5,15 @@ import { ErrorBoundary } from 'react-error-boundary'; import ErrorFallback from 'components/ErrorFallback'; import { isMaintenanceMode } from './application/config/index'; import Maintenance from 'components/Maintenance'; -import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import { QueryClientProvider } from '@tanstack/react-query'; import { ReactQueryDevtools } from '@tanstack/react-query-devtools'; import { isProdEnv } from 'application/config'; -const query = new QueryClient(); +import { queryClient } from 'utils/react-query'; + const App: React.FC = () => { return ( - +
{isMaintenanceMode() ? : }
diff --git a/web/gds-user-ui/src/application/routes/routes.ts b/web/gds-user-ui/src/application/routes/routes.ts index 9e0c59aea..a4e5afb58 100644 --- a/web/gds-user-ui/src/application/routes/routes.ts +++ b/web/gds-user-ui/src/application/routes/routes.ts @@ -136,7 +136,6 @@ const appRoutes = [ component: SwitchOrganization, layout: 'dashboard', route: '/organization/switch' - }, { path: '/dashboard/certificate-management', @@ -173,6 +172,13 @@ const appRoutes = [ component: Collaborators, layout: 'dashboard' }, + // { + // path: '/dashboard/organisation/select', + // name: 'Certificate Inventory', + // component: ChooseAnAccount, + // layout: 'dashboard', + // route: '/organisation/select' + // }, // -------ERROR ROUTES------- { path: '/not-found', diff --git a/web/gds-user-ui/src/components/Account/Account.spec.tsx b/web/gds-user-ui/src/components/Account/Account.spec.tsx index 679dbe59e..0d4b0481f 100644 --- a/web/gds-user-ui/src/components/Account/Account.spec.tsx +++ b/web/gds-user-ui/src/components/Account/Account.spec.tsx @@ -11,13 +11,13 @@ describe('', () => { }); it('should display props correctly', () => { - const { username, vaspName } = { - vaspName: faker.internet.domainName(), - username: faker.internet.userName() + const { name, domain } = { + name: faker.internet.domainName(), + domain: faker.internet.userName() }; - render(); + render(); expect(screen.getByTestId('vaspName')).toBeInTheDocument(); - expect(screen.getByTestId('username')).toBeInTheDocument(); + expect(screen.getByTestId('vaspDomain')).toBeInTheDocument(); }); }); diff --git a/web/gds-user-ui/src/components/Account/index.tsx b/web/gds-user-ui/src/components/Account/index.tsx index 02bc82bba..1ae37c421 100644 --- a/web/gds-user-ui/src/components/Account/index.tsx +++ b/web/gds-user-ui/src/components/Account/index.tsx @@ -1,15 +1,14 @@ import { Avatar, Box, HStack, Text } from '@chakra-ui/react'; +import { Organization } from 'modules/dashboard/organization/organizationType'; import { BsChevronRight } from 'react-icons/bs'; import { Link } from 'react-router-dom'; type AccountProps = { - vaspName: string; - username: string; src?: string; alt?: string; -}; +} & Partial; -export const Account = ({ vaspName, username, src, alt }: AccountProps) => ( +export const Account = ({ domain, name, src }: AccountProps) => ( ( transition="background 400ms"> - + - {vaspName} + {name} - {username} + {domain} diff --git a/web/gds-user-ui/src/components/AddNewVaspModal.tsx b/web/gds-user-ui/src/components/AddNewVaspModal.tsx new file mode 100644 index 000000000..73b01b470 --- /dev/null +++ b/web/gds-user-ui/src/components/AddNewVaspModal.tsx @@ -0,0 +1,162 @@ +import { + Button, + Modal, + ModalBody, + ModalContent, + ModalFooter, + ModalHeader, + ModalOverlay, + Stack, + Text, + useDisclosure, + chakra, + useToast +} from '@chakra-ui/react'; +import { t, Trans } from '@lingui/macro'; +import { usePostOrganizations } from 'modules/dashboard/organization/usePostOrganization'; +import { FormProvider, useForm } from 'react-hook-form'; +import CheckboxFormControl from './ui/CheckboxFormControl'; +import InputFormControl from './ui/InputFormControl'; +import * as Yup from 'yup'; +import { yupResolver } from '@hookform/resolvers/yup'; +import { queryClient } from 'utils/react-query'; +import { FETCH_ORGANIZATION } from 'constants/query-keys'; + +const validationSchema = Yup.object().shape({ + name: Yup.string().required(t`The VASP Name is required.`), + domain: Yup.string() + .url(t`The Domain Name is invalid.`) + .required(t`The Domain Name is required.`) +}); + +function AddNewVaspModal() { + const { isOpen, onOpen, onClose } = useDisclosure(); + const methods = useForm({ + defaultValues: { + name: '', + domain: '', + accept: false + }, + mode: 'onSubmit', + resolver: yupResolver(validationSchema) + }); + const { + register, + watch, + handleSubmit, + reset, + formState: { errors, isSubmitting } + } = methods; + const { mutate, isLoading } = usePostOrganizations(); + const toast = useToast(); + const isCreatingVasp = isSubmitting || isLoading; + + const accept = watch('accept'); + const onSubmit = (values: any) => { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const { accept: _accept, ...payload } = values; + + mutate(payload, { + onSuccess() { + queryClient.invalidateQueries([FETCH_ORGANIZATION]); + reset(); + onClose(); + }, + onError: (error) => { + console.log('[mutate] error', error.response?.data.error); + toast({ + title: error.response?.data?.error || error.message, + status: 'error', + position: 'top-right' + }); + } + }); + }; + + return ( + <> + + + + + + Modal Title + + + +
+ + + Please input the name of the new managed Virtual Asset Service Provider (VASP). + When the entity is created, you will have the ability to add collaborators, + start and complete the certificate registration process, and manage the VASP + account. Please acknowledge below and provide the name of the entity. + + + + + TRISA is a network of trusted members. I acknowledge that the new VASP has a + legitimate business purpose to join TRISA. + + + + + + VASP Name + {' '} + (required) + + } + /> + + + VASP Domain + {' '} + (required) + + } + /> + + + + + +
+
+
+
+
+ + ); +} + +export default AddNewVaspModal; diff --git a/web/gds-user-ui/src/components/ChooseAnAccount/ChooseAnAccount.stories.tsx b/web/gds-user-ui/src/components/ChooseAnAccount/ChooseAnAccount.stories.tsx deleted file mode 100644 index 323d06127..000000000 --- a/web/gds-user-ui/src/components/ChooseAnAccount/ChooseAnAccount.stories.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import { Meta, Story } from '@storybook/react'; -import ChooseAnAccount from '.'; - -type ComponentType = typeof ChooseAnAccount; - -export default { - title: 'components/ChooseAnAccount', - component: ChooseAnAccount -} as Meta; - -const Template: Story = (args) => ; - -export const Standard = Template.bind({}); -Template.args = {}; diff --git a/web/gds-user-ui/src/components/ChooseAnAccount/index.tsx b/web/gds-user-ui/src/components/ChooseAnAccount/index.tsx deleted file mode 100644 index c9714443f..000000000 --- a/web/gds-user-ui/src/components/ChooseAnAccount/index.tsx +++ /dev/null @@ -1,45 +0,0 @@ -import { - Avatar, - Box, - Button, - Grid, - GridItem, - HStack, - Select, - Stack, - StackDivider, - Text -} from '@chakra-ui/react'; -import { Account } from 'components/Account'; -import InputFormControl from 'components/ui/InputFormControl'; -import SelectFormControl from 'components/ui/SelectFormControl'; - -const OPTIONS = [ - { label: 'Newest registrations', value: 'NEWEST_REGISTRATIONS' }, - { label: 'Most recently logged in', value: 'MOST_RECENTLY_LOGGED_IN' }, - { label: 'Alphabetical', value: 'ALPHABETICAL' } -]; - -function ChooseAnAccount() { - return ( - - - - - - - - - - } p={2}> - - - - - - ); -} - -export default ChooseAnAccount; diff --git a/web/gds-user-ui/src/components/ChooseAnOrganization/ChooseAnAccount.stories.tsx b/web/gds-user-ui/src/components/ChooseAnOrganization/ChooseAnAccount.stories.tsx new file mode 100644 index 000000000..014cf78d3 --- /dev/null +++ b/web/gds-user-ui/src/components/ChooseAnOrganization/ChooseAnAccount.stories.tsx @@ -0,0 +1,12 @@ +import { Meta, Story } from '@storybook/react'; +import ChooseAnOrganization, { ChooseAnAccountProps } from '.'; + +export default { + title: 'components/ChooseAnAccount', + component: ChooseAnOrganization +} as Meta; + +const Template: Story = (args) => ; + +export const Standard = Template.bind({}); +Template.args = {}; diff --git a/web/gds-user-ui/src/components/ChooseAnOrganization/index.tsx b/web/gds-user-ui/src/components/ChooseAnOrganization/index.tsx new file mode 100644 index 000000000..1980c7539 --- /dev/null +++ b/web/gds-user-ui/src/components/ChooseAnOrganization/index.tsx @@ -0,0 +1,74 @@ +import { + Grid, + GridItem, + HStack, + Modal, + ModalBody, + ModalCloseButton, + ModalContent, + ModalOverlay, + Stack, + StackDivider, + Text +} from '@chakra-ui/react'; +import { Account } from 'components/Account'; +import AddNewVaspModal from 'components/AddNewVaspModal'; +import InputFormControl from 'components/ui/InputFormControl'; +import SelectFormControl from 'components/ui/SelectFormControl'; +import { useOrganizationListQuery } from 'modules/dashboard/organization/useOrganizationListQuery'; + +const OPTIONS = [ + { label: 'Newest registrations', value: 'NEWEST_REGISTRATIONS' }, + { label: 'Most recently logged in', value: 'MOST_RECENTLY_LOGGED_IN' }, + { label: 'Alphabetical', value: 'ALPHABETICAL' } +]; + +export type ChooseAnAccountProps = { + isOpen: boolean; + onClose: () => void; +}; + +function ChooseAnOrganization({ isOpen, onClose }: ChooseAnAccountProps) { + const { organizations } = useOrganizationListQuery(); + + return ( + <> + + + + + + +
+ + + + Select an VASP from the Managed VASP List + + + + + + + + +
+ } p={2}> + {organizations?.map((organization) => ( + + ))} + +
+
+
+
+ + ); +} + +export default ChooseAnOrganization; diff --git a/web/gds-user-ui/src/components/Sidebar/MobileNav.tsx b/web/gds-user-ui/src/components/Sidebar/MobileNav.tsx index e5e878e5d..09b24800e 100644 --- a/web/gds-user-ui/src/components/Sidebar/MobileNav.tsx +++ b/web/gds-user-ui/src/components/Sidebar/MobileNav.tsx @@ -13,7 +13,8 @@ import { MenuList, MenuItem, MenuDivider, - Show + Show, + useDisclosure } from '@chakra-ui/react'; import { FiMenu } from 'react-icons/fi'; import LanguagesDropdown from 'components/LanguagesDropdown'; @@ -24,12 +25,19 @@ import DefaultAvatar from 'assets/default_avatar.svg'; import { resetStore } from 'application/store'; import { userSelector, logout } from 'modules/auth/login/user.slice'; import { Trans } from '@lingui/react'; +import ChooseAnOrganization from 'components/ChooseAnOrganization'; interface MobileProps extends FlexProps { onOpen: () => void; isLoading?: boolean; } const MobileNav = ({ onOpen, ...rest }: MobileProps) => { + const { + isOpen: isAccountSwitchOpen, + onOpen: onAccountSwitchOpen, + onClose: onAccountSwitchClose + } = useDisclosure(); + const dispatch = useDispatch(); const { user } = useSelector(userSelector); const navigate = useNavigate(); @@ -92,6 +100,10 @@ const MobileNav = ({ onOpen, ...rest }: MobileProps) => { navigate('/dashboard/profile')}> Profile + + Switch accounts + + Sign out diff --git a/web/gds-user-ui/src/constants/query-keys.ts b/web/gds-user-ui/src/constants/query-keys.ts new file mode 100644 index 000000000..8f42df896 --- /dev/null +++ b/web/gds-user-ui/src/constants/query-keys.ts @@ -0,0 +1 @@ +export const FETCH_ORGANIZATION = 'fetch-organization'; diff --git a/web/gds-user-ui/src/modules/dashboard/organization/organizationService.ts b/web/gds-user-ui/src/modules/dashboard/organization/organizationService.ts index 4e0b8aab0..fc7e20b96 100644 --- a/web/gds-user-ui/src/modules/dashboard/organization/organizationService.ts +++ b/web/gds-user-ui/src/modules/dashboard/organization/organizationService.ts @@ -1,23 +1,21 @@ - import axiosInstance from 'utils/axios'; - export const GetAllOrganisations = async () => { - const response = await axiosInstance.get(`/organisations`); - return response; + const response = await axiosInstance.get(`/organizations`); + return response; }; export const GetOrganisation = async (id: string) => { - const response = await axiosInstance.get(`/organisations/${id}`); - return response; + const response = await axiosInstance.get(`/organizations/${id}`); + return response; }; export const CreateOrganisation = async (data: any) => { - const response = await axiosInstance.post(`/organisations`, data); - return response; + const response = await axiosInstance.post(`/organizations`, data); + return response; }; export const UpdateOrganisation = async (id: string, data: any) => { - const response = await axiosInstance.put(`/organisations/${id}`, data); - return response; + const response = await axiosInstance.put(`/organizations/${id}`, data); + return response; }; diff --git a/web/gds-user-ui/src/modules/dashboard/organization/useOrganizationListQuery.tsx b/web/gds-user-ui/src/modules/dashboard/organization/useOrganizationListQuery.tsx index f9159d24a..09200e118 100644 --- a/web/gds-user-ui/src/modules/dashboard/organization/useOrganizationListQuery.tsx +++ b/web/gds-user-ui/src/modules/dashboard/organization/useOrganizationListQuery.tsx @@ -1,17 +1,19 @@ import { useQuery } from '@tanstack/react-query'; import { GetAllOrganisations } from './organizationService'; import type { Organization, OrganizationQuery } from './organizationType'; +import { FETCH_ORGANIZATION } from 'constants/query-keys'; export function useOrganizationListQuery(): OrganizationQuery { - const query = useQuery(['fetch-organization'], GetAllOrganisations, { + const query = useQuery([FETCH_ORGANIZATION], GetAllOrganisations, { refetchOnWindowFocus: false, refetchOnMount: true, // set state time to 5 minutes staleTime: 1000 * 60 * 5 }); + return { getAllOrganizations: query.refetch, - organizations: query.data?.data?.organizations as Organization[], + organizations: query.data?.data as Organization[], hasOrganizationFailed: query.isError, wasOrganizationFetched: query.isSuccess, isFetching: query.isFetching, diff --git a/web/gds-user-ui/src/modules/dashboard/organization/usePostOrganization.ts b/web/gds-user-ui/src/modules/dashboard/organization/usePostOrganization.ts new file mode 100644 index 000000000..b6c1e73b1 --- /dev/null +++ b/web/gds-user-ui/src/modules/dashboard/organization/usePostOrganization.ts @@ -0,0 +1,7 @@ +import { AxiosError } from 'axios'; +import { CreateOrganisation } from './organizationService'; +import { useMutation } from '@tanstack/react-query'; + +export function usePostOrganizations() { + return useMutation(CreateOrganisation); +} diff --git a/web/gds-user-ui/src/utils/react-query.ts b/web/gds-user-ui/src/utils/react-query.ts new file mode 100644 index 000000000..6d46de591 --- /dev/null +++ b/web/gds-user-ui/src/utils/react-query.ts @@ -0,0 +1,3 @@ +import { QueryClient } from '@tanstack/react-query'; + +export const queryClient = new QueryClient();