diff --git a/src/hooks/index.ts b/src/hooks/index.ts index 21d38fdb..869ecc94 100644 --- a/src/hooks/index.ts +++ b/src/hooks/index.ts @@ -3,6 +3,7 @@ import { WebsocketClient } from '@graasp/sdk'; import configureItemHooks from '../item/hooks.js'; import configureItemPublicationHooks from '../item/publication/hooks.js'; import configureMemberHooks from '../member/hooks.js'; +import configureMemberPasswordHooks from '../member/password/hooks.js'; import configurePublicProfileHooks from '../member/publicProfile/hooks.js'; import configureSubscriptionHooks from '../member/subscription/hooks.js'; import configureMembershipRequestHooks from '../membership/request/hooks.js'; @@ -62,6 +63,7 @@ export default ( ...configureEmbeddedLinkHooks(queryConfig), ...configureItemPublicationHooks(queryConfig), ...configureMembershipRequestHooks(queryConfig), + ...configureMemberPasswordHooks(queryConfig), useDebounce, }; }; diff --git a/src/keys.ts b/src/keys.ts index 306980d8..0bd25035 100644 --- a/src/keys.ts +++ b/src/keys.ts @@ -272,6 +272,9 @@ export const memberKeys = { ], // apps used mostly by the member mostUsedApps: [...currentBaseKey, 'mostUsedApps'] as const, + + // password status + passwordStatus: [...currentBaseKey, 'passwordStatus'], }; }, }; diff --git a/src/member/mutations.ts b/src/member/mutations.ts index da229497..25fafa3a 100644 --- a/src/member/mutations.ts +++ b/src/member/mutations.ts @@ -179,8 +179,9 @@ export default (queryConfig: QueryClientConfig) => { * Mutation to create a member password * @param {Password} password new password to set on current member */ - const useCreatePassword = () => - useMutation({ + const useCreatePassword = () => { + const queryClient = useQueryClient(); + return useMutation({ mutationFn: (payload: { password: Password }) => Api.createPassword(payload, queryConfig), onSuccess: () => { @@ -195,7 +196,13 @@ export default (queryConfig: QueryClientConfig) => { payload: { error }, }); }, + onSettled: () => { + queryClient.invalidateQueries({ + queryKey: memberKeys.current().passwordStatus, + }); + }, }); + }; const useUpdateMemberEmail = () => useMutation({ diff --git a/src/member/password/api.ts b/src/member/password/api.ts new file mode 100644 index 00000000..6abd9f71 --- /dev/null +++ b/src/member/password/api.ts @@ -0,0 +1,12 @@ +import { PartialQueryConfigForApi } from '../../types.js'; +import { buildGetPasswordStatusRoute } from './routes.js'; + +export const getPasswordStatus = async ({ + API_HOST, + axios, +}: PartialQueryConfigForApi) => + axios + .get<{ + hasPassword: boolean; + }>(`${API_HOST}/${buildGetPasswordStatusRoute()}`) + .then(({ data }) => data); diff --git a/src/member/password/hooks.test.ts b/src/member/password/hooks.test.ts new file mode 100644 index 00000000..0d911c11 --- /dev/null +++ b/src/member/password/hooks.test.ts @@ -0,0 +1,52 @@ +import { StatusCodes } from 'http-status-codes'; +import nock from 'nock'; +import { afterEach, describe, expect, it } from 'vitest'; + +import { UNAUTHORIZED_RESPONSE } from '../../../test/constants.js'; +import { mockHook, setUpTest } from '../../../test/utils.js'; +import { memberKeys } from '../../keys.js'; +import { buildGetPasswordStatusRoute } from './routes.js'; + +const { hooks, wrapper, queryClient } = setUpTest(); +describe('Member Hooks', () => { + afterEach(() => { + queryClient.clear(); + nock.cleanAll(); + }); + + describe('usePasswordStatus', () => { + const route = `/${buildGetPasswordStatusRoute()}`; + const hook = hooks.usePasswordStatus; + const key = memberKeys.current().passwordStatus; + + it(`Receive password status`, async () => { + const response = { hasPassword: true }; + const endpoints = [{ route, response }]; + const { data } = await mockHook({ endpoints, hook, wrapper }); + + expect(data).toMatchObject(response); + // verify cache keys + expect(queryClient.getQueryData(key)).toMatchObject(data!); + }); + + it(`Unauthorized`, async () => { + const endpoints = [ + { + route, + response: UNAUTHORIZED_RESPONSE, + statusCode: StatusCodes.UNAUTHORIZED, + }, + ]; + const { data, isError } = await mockHook({ + endpoints, + hook, + wrapper, + }); + + expect(data).toBeFalsy(); + + expect(isError).toBeTruthy(); + expect(queryClient.getQueryData(key)).toBeFalsy(); + }); + }); +}); diff --git a/src/member/password/hooks.ts b/src/member/password/hooks.ts new file mode 100644 index 00000000..c9527aea --- /dev/null +++ b/src/member/password/hooks.ts @@ -0,0 +1,18 @@ +import { useQuery } from '@tanstack/react-query'; + +import { memberKeys } from '../../keys.js'; +import { QueryClientConfig } from '../../types.js'; +import { getPasswordStatus } from './api.js'; + +export default (queryConfig: QueryClientConfig) => { + const { defaultQueryOptions } = queryConfig; + + return { + usePasswordStatus: () => + useQuery({ + queryKey: memberKeys.current().passwordStatus, + queryFn: () => getPasswordStatus(queryConfig), + ...defaultQueryOptions, + }), + }; +}; diff --git a/src/member/password/routes.ts b/src/member/password/routes.ts new file mode 100644 index 00000000..26997210 --- /dev/null +++ b/src/member/password/routes.ts @@ -0,0 +1,4 @@ +import { MEMBERS_ROUTE } from '../routes.js'; + +export const buildGetPasswordStatusRoute = () => + `${MEMBERS_ROUTE}/current/password/status`;