diff --git a/jsapp/js/account/organization/membersQuery.ts b/jsapp/js/account/organization/membersQuery.ts index 1bec128e80..1f429767b6 100644 --- a/jsapp/js/account/organization/membersQuery.ts +++ b/jsapp/js/account/organization/membersQuery.ts @@ -1,9 +1,22 @@ -import {keepPreviousData, useQuery} from '@tanstack/react-query'; +// Libraries +import { + useQuery, + useQueryClient, + useMutation, + keepPreviousData, +} from '@tanstack/react-query'; + +// Stores, hooks and utilities +import {fetchGet, fetchPatch, fetchDelete} from 'js/api'; +import { + useOrganizationQuery, + type OrganizationUserRole +} from './organizationQuery'; + +// Constants and types import {endpoints} from 'js/api.endpoints'; import type {PaginatedResponse} from 'js/dataInterface'; -import {fetchGet} from 'js/api'; import {QueryKeys} from 'js/query/queryKeys'; -import {useOrganizationQuery, type OrganizationUserRole} from './organizationQuery'; export interface OrganizationMember { /** @@ -34,6 +47,62 @@ export interface OrganizationMember { }; } +function getMemberEndpoint(orgId: string, username: string) { + return endpoints.ORGANIZATION_MEMBER_URL + .replace(':organization_id', orgId) + .replace(':username', username); +} + +/** + * Mutation hook for updating organization member. It ensures that all related + * queries refetch data (are invalidated). + */ +export function usePatchOrganizationMember(username: string) { + const queryClient = useQueryClient(); + + const orgQuery = useOrganizationQuery(); + const orgId = orgQuery.data?.id; + + return useMutation({ + mutationFn: async (data: Partial) => ( + // We're asserting the `orgId` is not `undefined` here, because the parent + // query (`useOrganizationMembersQuery`) wouldn't be enabled without it. + // Plus all the organization-related UI (that would use this hook) is + // accessible only to logged in users. + fetchPatch(getMemberEndpoint(orgId!, username), data) + ), + onSettled: () => { + // We invalidate query, so it will refetch (instead of refetching it + // directly, see: https://github.com/TanStack/query/discussions/2468) + queryClient.invalidateQueries({queryKey: [QueryKeys.organizationMembers]}); + }, + }); +} + +/** + * Mutation hook for removing member from organiztion. It ensures that all + * related queries refetch data (are invalidated). + */ +export function useRemoveOrganizationMember() { + const queryClient = useQueryClient(); + + const orgQuery = useOrganizationQuery(); + const orgId = orgQuery.data?.id; + + return useMutation({ + mutationFn: async (username: string) => ( + // We're asserting the `orgId` is not `undefined` here, because the parent + // query (`useOrganizationMembersQuery`) wouldn't be enabled without it. + // Plus all the organization-related UI (that would use this hook) is + // accessible only to logged in users. + fetchDelete(getMemberEndpoint(orgId!, username)) + ), + onSettled: () => { + queryClient.invalidateQueries({queryKey: [QueryKeys.organizationMembers]}); + }, + }); +} + /** * Fetches paginated list of members for given organization. * This is mainly needed for `useOrganizationMembersQuery`, so you most probably @@ -49,7 +118,8 @@ async function getOrganizationMembers( offset: offset.toString(), }); - const apiUrl = endpoints.ORGANIZATION_MEMBERS_URL.replace(':organization_id', orgId); + const apiUrl = endpoints.ORGANIZATION_MEMBERS_URL + .replace(':organization_id', orgId); return fetchGet>( apiUrl + '?' + params, diff --git a/jsapp/js/account/organization/organizationQuery.ts b/jsapp/js/account/organization/organizationQuery.ts index 604a2116a5..6eda653dea 100644 --- a/jsapp/js/account/organization/organizationQuery.ts +++ b/jsapp/js/account/organization/organizationQuery.ts @@ -37,6 +37,10 @@ export interface Organization { request_user_role: OrganizationUserRole; } +/** + * Note that it's only possible to update the role via API to either `admin` or + * `member`. + */ export enum OrganizationUserRole { member = 'member', admin = 'admin', diff --git a/jsapp/js/api.endpoints.ts b/jsapp/js/api.endpoints.ts index a6096989f1..d07960d7f5 100644 --- a/jsapp/js/api.endpoints.ts +++ b/jsapp/js/api.endpoints.ts @@ -6,6 +6,7 @@ export const endpoints = { SUBSCRIPTION_URL: '/api/v2/stripe/subscriptions/', ADD_ONS_URL: '/api/v2/stripe/addons/', ORGANIZATION_MEMBERS_URL: '/api/v2/organizations/:organization_id/members/', + ORGANIZATION_MEMBER_URL: '/api/v2/organizations/:organization_id/members/:username/', /** Expected parameters: price_id and organization_id **/ CHECKOUT_URL: '/api/v2/stripe/checkout-link', /** Expected parameter: organization_id **/