diff --git a/web/src/api/buster_rest/users/queryRequests.ts b/web/src/api/buster_rest/users/queryRequests.ts index f5a058624..c3910ce5d 100644 --- a/web/src/api/buster_rest/users/queryRequests.ts +++ b/web/src/api/buster_rest/users/queryRequests.ts @@ -11,12 +11,15 @@ import { deleteUserFavorite, updateUserFavorites, getUserList, - getUserList_server + getUserList_server, + inviteUser } from './requests'; import { useMemoizedFn } from '@/hooks'; import { QueryClient, useQueryClient } from '@tanstack/react-query'; import { queryKeys } from '@/api/query_keys'; import type { UserRequestUserListPayload } from '@/api/request_interfaces/user/interfaces'; +import { useBusterNotifications } from '@/context/BusterNotifications'; +import { useCreateOrganization } from '../organizations/queryRequests'; export const useGetMyUserInfo = () => { return useQuery({ @@ -115,7 +118,7 @@ export const useDeleteUserFavorite = () => { mutationFn: deleteUserFavorite, onMutate: (params) => { queryClient.setQueryData(queryKeys.favoritesGetList.queryKey, (prev) => { - return prev?.filter((fav) => fav.id !== params.id); + return prev?.filter((fav) => !params.some((p) => p.id === fav.id)); }); }, onSettled: () => { @@ -161,3 +164,38 @@ export const prefetchGetUserList = async ( }); return queryClient; }; + +export const useInviteUser = () => { + const { openSuccessMessage } = useBusterNotifications(); + + return useMutation({ + mutationFn: inviteUser, + onSuccess: () => { + openSuccessMessage('Invites sent'); + // queryClient.invalidateQueries(queryKeys.userGetUserList); + } + }); +}; + +export const useCreateUserOrganization = () => { + const { data: userResponse, refetch: refetchUserResponse } = useGetMyUserInfo(); + const { mutateAsync: createOrganization } = useCreateOrganization(); + const { mutateAsync: updateUserInfo } = useUpdateUser(); + + const onCreateUserOrganization = useMemoizedFn( + async ({ name, company }: { name: string; company: string }) => { + const alreadyHasOrganization = !!userResponse?.organizations?.[0]; + + if (!alreadyHasOrganization) await createOrganization({ name: company }); + if (userResponse) + await updateUserInfo({ + userId: userResponse.user.id, + name + }); + + await refetchUserResponse(); + } + ); + + return onCreateUserOrganization; +}; diff --git a/web/src/api/query_keys/users.ts b/web/src/api/query_keys/users.ts index 1ae9c56a8..b4e3d78de 100644 --- a/web/src/api/query_keys/users.ts +++ b/web/src/api/query_keys/users.ts @@ -14,7 +14,9 @@ import type { UserRequestUserListPayload } from '@/api/request_interfaces/user/i const favoritesGetList = queryOptions({ queryKey: ['users', 'favorites', 'list'] as const, - staleTime: 1000 * 60 * 60 // 1 hour + staleTime: 1000 * 60 * 60, // 1 hour, + initialData: [], + initialDataUpdatedAt: 0 }); const userGetUserMyself = queryOptions({ diff --git a/web/src/api/request_interfaces/user/interfaces.ts b/web/src/api/request_interfaces/user/interfaces.ts index 515310393..e038b237d 100644 --- a/web/src/api/request_interfaces/user/interfaces.ts +++ b/web/src/api/request_interfaces/user/interfaces.ts @@ -7,10 +7,10 @@ export interface UsersFavoritePostPayload { name: string; } -export interface UserFavoriteDeletePayload { +export type UserFavoriteDeletePayload = { id: string; asset_type: ShareAssetType; -} +}[]; export interface UserUpdateFavoritesPayload { favorites: string[]; // Array of favorite ids diff --git a/web/src/app/app/(primary_layout)/new-user/_NewUserController.tsx b/web/src/app/app/(primary_layout)/new-user/_NewUserController.tsx index 9cb2be61c..6f20a3a73 100644 --- a/web/src/app/app/(primary_layout)/new-user/_NewUserController.tsx +++ b/web/src/app/app/(primary_layout)/new-user/_NewUserController.tsx @@ -11,11 +11,12 @@ import { BusterRoutes } from '@/routes/busterRoutes'; import { useBusterNotifications } from '@/context/BusterNotifications'; import { Button } from '@/components/ui/buttons'; import { Input } from '@/components/ui/inputs'; +import { useCreateUserOrganization } from '@/api/buster_rest/users'; export const NewUserController = () => { const [started, setStarted] = useState(false); const onChangePage = useAppLayoutContextSelector((s) => s.onChangePage); - const onCreateUserOrganization = useUserConfigContextSelector((s) => s.onCreateUserOrganization); + const onCreateUserOrganization = useCreateUserOrganization(); const user = useUserConfigContextSelector((s) => s.user); const userOrganizations = useUserConfigContextSelector((s) => s.userOrganizations); const { openInfoMessage } = useBusterNotifications(); diff --git a/web/src/components/features/list/FavoriteStar.tsx b/web/src/components/features/list/FavoriteStar.tsx index 01c16b2eb..5b7611cde 100644 --- a/web/src/components/features/list/FavoriteStar.tsx +++ b/web/src/components/features/list/FavoriteStar.tsx @@ -7,6 +7,11 @@ import { Button } from '@/components/ui/buttons'; import { cn } from '@/lib/classMerge'; import { Star } from '@/components/ui/icons'; import { cva } from 'class-variance-authority'; +import { + useAddUserFavorite, + useDeleteUserFavorite, + useGetUserFavorites +} from '@/api/buster_rest/users'; const favoriteStarVariants = cva('transition-colors', { variants: { @@ -32,11 +37,9 @@ export const FavoriteStar: React.FC<{ className?: string; iconStyle?: 'default' | 'tertiary'; }> = React.memo(({ title: name, id, type, className = '', iconStyle = 'default' }) => { - const userFavorites = useUserConfigContextSelector((state) => state.userFavorites); - const removeItemFromFavorite = useUserConfigContextSelector( - (state) => state.removeItemFromFavorite - ); - const addItemToFavorite = useUserConfigContextSelector((state) => state.addItemToFavorite); + const { data: userFavorites } = useGetUserFavorites(); + const { mutateAsync: removeItemFromFavorite } = useDeleteUserFavorite(); + const { mutateAsync: addItemToFavorite } = useAddUserFavorite(); const isFavorited = useMemo(() => { return userFavorites?.some((favorite) => favorite.id === id || favorite.collection_id === id); @@ -52,10 +55,12 @@ export const FavoriteStar: React.FC<{ name }); - await removeItemFromFavorite({ - asset_type: type, - id - }); + await removeItemFromFavorite([ + { + asset_type: type, + id + } + ]); }); const tooltipText = isFavorited ? 'Remove from favorites' : 'Add to favorites'; diff --git a/web/src/components/features/modal/InvitePeopleModal.tsx b/web/src/components/features/modal/InvitePeopleModal.tsx index 484e1978d..df834c5f0 100644 --- a/web/src/components/features/modal/InvitePeopleModal.tsx +++ b/web/src/components/features/modal/InvitePeopleModal.tsx @@ -1,23 +1,18 @@ import React, { useMemo } from 'react'; import { useMemoizedFn } from '@/hooks'; import { AppModal } from '@/components/ui/modal'; -import { useUserConfigContextSelector } from '@/context/Users'; import { TagInput } from '@/components/ui/inputs/InputTagInput'; +import { useInviteUser } from '@/api/buster_rest/users'; export const InvitePeopleModal: React.FC<{ open: boolean; onClose: () => void; }> = React.memo(({ open, onClose }) => { const [emails, setEmails] = React.useState([]); - const [inviting, setInviting] = React.useState(false); - const inviteUsers = useUserConfigContextSelector((state) => state.inviteUsers); - const isAdmin = useUserConfigContextSelector((state) => state.isAdmin); - const userTeams = useUserConfigContextSelector((state) => state.userTeams); + const { mutateAsync: inviteUsers, isPending: inviting } = useInviteUser(); const handleInvite = useMemoizedFn(async () => { - setInviting(true); - await inviteUsers(emails); - setInviting(false); + await inviteUsers({ emails }); onClose(); }); diff --git a/web/src/components/features/sidebars/SidebarPrimary.tsx b/web/src/components/features/sidebars/SidebarPrimary.tsx index dcc0afdf6..71b776bb4 100644 --- a/web/src/components/features/sidebars/SidebarPrimary.tsx +++ b/web/src/components/features/sidebars/SidebarPrimary.tsx @@ -18,6 +18,7 @@ import { SupportModal } from '../modal/SupportModal'; import { InvitePeopleModal } from '../modal/InvitePeopleModal'; import { useMemoizedFn } from '@/hooks'; import { SidebarUserFooter } from './SidebarUserFooter/SidebarUserFooter'; +import { useGetUserFavorites } from '@/api/buster_rest'; const topItems: ISidebarList = { items: [ @@ -106,7 +107,7 @@ const tryGroup = (onClickInvitePeople: () => void, onClickLeaveFeedback: () => v export const SidebarPrimary = React.memo(() => { const isAdmin = useUserConfigContextSelector((x) => x.isAdmin); - const favorites = useUserConfigContextSelector((state) => state.userFavorites); + const { data: favorites } = useGetUserFavorites(); const currentRoute = useAppLayoutContextSelector((x) => x.currentRoute); const onToggleSupportModal = useAppLayoutContextSelector((s) => s.onToggleSupportModal); const onToggleInviteModal = useAppLayoutContextSelector((s) => s.onToggleInviteModal); diff --git a/web/src/context/Metrics/BusterMetricsIndividualProvider/useMetricUpdateAssosciations.ts b/web/src/context/Metrics/BusterMetricsIndividualProvider/useMetricUpdateAssosciations.ts index 791e4148c..c3e8c31d9 100644 --- a/web/src/context/Metrics/BusterMetricsIndividualProvider/useMetricUpdateAssosciations.ts +++ b/web/src/context/Metrics/BusterMetricsIndividualProvider/useMetricUpdateAssosciations.ts @@ -1,9 +1,8 @@ import { useMemoizedFn } from '@/hooks'; import type { IBusterMetric } from '@/api/asset_interfaces/metric'; -import { useUserConfigContextSelector } from '@/context/Users'; import { useBusterNotifications } from '@/context/BusterNotifications'; import { useUpdateMetricConfig } from './useMetricUpdateConfig'; - +import { useGetUserFavorites } from '@/api/buster_rest/users'; export const useUpdateMetricAssosciations = ({ getMetricMemoized, updateMetricMutation @@ -11,8 +10,7 @@ export const useUpdateMetricAssosciations = ({ getMetricMemoized: ({ metricId }: { metricId?: string }) => IBusterMetric; updateMetricMutation: ReturnType['updateMetricMutation']; }) => { - const userFavorites = useUserConfigContextSelector((state) => state.userFavorites); - const refreshFavoritesList = useUserConfigContextSelector((x) => x.refreshFavoritesList); + const { data: userFavorites, refetch: refreshFavoritesList } = useGetUserFavorites(); const { openConfirmModal } = useBusterNotifications(); diff --git a/web/src/context/Users/UserConfigProvider.tsx b/web/src/context/Users/UserConfigProvider.tsx index 523a35188..a88a48b7d 100644 --- a/web/src/context/Users/UserConfigProvider.tsx +++ b/web/src/context/Users/UserConfigProvider.tsx @@ -2,29 +2,15 @@ import type { BusterUserResponse } from '@/api/asset_interfaces/users'; import React, { PropsWithChildren } from 'react'; -import { useFavoriteProvider } from './useFavoriteProvider'; import { useGetMyUserInfo } from '@/api/buster_rest/users'; import { useSupabaseContext } from '../Supabase'; import { createContext, useContextSelector } from 'use-context-selector'; -import { useUserOrganization } from './useUserOrganization'; -import { useInviteUser } from './useInviteUser'; import { checkIfUserIsAdmin } from '@/lib/user'; export const useUserConfigProvider = ({ userInfo }: { userInfo: BusterUserResponse | null }) => { const isAnonymousUser = useSupabaseContext((state) => state.isAnonymousUser); - - const { data: userResponseData, refetch: refetchUserResponse } = useGetMyUserInfo(); + const { data: userResponseData } = useGetMyUserInfo(); const userResponse = userResponseData || userInfo; - - const favoriteConfig = useFavoriteProvider(); - - const inviteUsers = useInviteUser(); - - const { onCreateUserOrganization } = useUserOrganization({ - userResponse, - refetchUserResponse - }); - const user = userResponse?.user; const userTeams = userResponse?.teams || []; const userOrganizations = userResponse?.organizations?.[0]; @@ -35,16 +21,13 @@ export const useUserConfigProvider = ({ userInfo }: { userInfo: BusterUserRespon const isAdmin = checkIfUserIsAdmin(userResponse); return { - onCreateUserOrganization, userTeams, user, userRole, isAdmin, userOrganizations, isUserRegistered, - isAnonymousUser, - ...inviteUsers, - ...favoriteConfig + isAnonymousUser }; }; diff --git a/web/src/context/Users/useFavoriteProvider.tsx b/web/src/context/Users/useFavoriteProvider.tsx deleted file mode 100644 index 19c4507b1..000000000 --- a/web/src/context/Users/useFavoriteProvider.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import { useMemoizedFn } from '@/hooks'; -import type { BusterUserFavorite } from '@/api/asset_interfaces/users'; -import isEmpty from 'lodash/isEmpty'; -import { - useAddUserFavorite, - useDeleteUserFavorite, - useGetUserFavorites -} from '@/api/buster_rest/users'; - -const DEFAULT_FAVORITES: BusterUserFavorite[] = []; - -export const useFavoriteProvider = () => { - const { data: userFavorites, refetch: refreshFavoritesList } = useGetUserFavorites(); - - const { mutate: addItemToFavorite } = useAddUserFavorite(); - - const { mutate: removeItemFromFavorite } = useDeleteUserFavorite(); - - const bulkEditFavorites = useMemoizedFn(async (favorites: string[]) => { - // return updateFavorites({ favorites }); - alert('TODO - feature not implemented yet'); - }); - - return { - bulkEditFavorites, - refreshFavoritesList, - userFavorites: isEmpty(userFavorites) ? DEFAULT_FAVORITES : userFavorites!, - addItemToFavorite, - removeItemFromFavorite - }; -}; diff --git a/web/src/context/Users/useInviteUser.ts b/web/src/context/Users/useInviteUser.ts deleted file mode 100644 index 892de59e1..000000000 --- a/web/src/context/Users/useInviteUser.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { useMemoizedFn } from '@/hooks'; -import { timeout } from '@/lib'; -import { useBusterNotifications } from '../BusterNotifications'; -import { inviteUser as inviteUserRest } from '@/api/buster_rest'; - -export const useInviteUser = () => { - const { openSuccessMessage } = useBusterNotifications(); - - const inviteUsers = useMemoizedFn(async (emails: string[], team_ids?: string[]) => { - await inviteUserRest({ emails, team_ids }); - await timeout(100); - openSuccessMessage('Invites sent'); - }); - - return { - inviteUsers - }; -}; diff --git a/web/src/context/Users/useUserOrganization.ts b/web/src/context/Users/useUserOrganization.ts deleted file mode 100644 index 2845543cc..000000000 --- a/web/src/context/Users/useUserOrganization.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { useMemoizedFn } from '@/hooks'; -import type { BusterUserResponse } from '@/api/asset_interfaces/users'; -import { useCreateOrganization, useUpdateUser } from '@/api/buster_rest'; - -export const useUserOrganization = ({ - userResponse, - refetchUserResponse -}: { - userResponse: BusterUserResponse | null | undefined; - refetchUserResponse: () => Promise; -}) => { - const { mutateAsync: createOrganization } = useCreateOrganization(); - - const { mutateAsync: updateUserInfo } = useUpdateUser(); - - const onCreateUserOrganization = useMemoizedFn( - async ({ name, company }: { name: string; company: string }) => { - const alreadyHasOrganization = !!userResponse?.organizations?.[0]; - - if (!alreadyHasOrganization) await createOrganization({ name: company }); - if (userResponse) - await updateUserInfo({ - userId: userResponse.user.id, - name - }); - - await refetchUserResponse(); - } - ); - - return { - onCreateUserOrganization - }; -}; diff --git a/web/src/controllers/ChatsListController/ChatItemsSelectedPopup.tsx b/web/src/controllers/ChatsListController/ChatItemsSelectedPopup.tsx index 311c5993f..b65879237 100644 --- a/web/src/controllers/ChatsListController/ChatItemsSelectedPopup.tsx +++ b/web/src/controllers/ChatsListController/ChatItemsSelectedPopup.tsx @@ -6,12 +6,17 @@ import { BusterListSelectedOptionPopupContainer } from '@/components/ui/list'; import { Dropdown, DropdownItems } from '@/components/ui/dropdown'; import { Button } from '@/components/ui/buttons'; import { useBusterMetricsIndividualContextSelector } from '@/context/Metrics'; -import { useUserConfigContextSelector } from '@/context/Users'; import { useMemoizedFn } from '@/hooks'; import { SaveToCollectionsDropdown } from '@/components/features/dropdowns/SaveToCollectionsDropdown'; import { useBusterNotifications } from '@/context/BusterNotifications'; import { ASSET_ICONS } from '@/components/features/config/assetIcons'; import { useDeleteMetric } from '@/api/buster_rest/metrics'; +import { + useAddUserFavorite, + useDeleteUserFavorite, + useGetUserFavorites +} from '@/api/buster_rest/users'; +import { ShareAssetType } from '@/api/asset_interfaces/share'; export const ChatSelectedOptionPopup: React.FC<{ selectedRowKeys: string[]; @@ -137,8 +142,8 @@ const ThreeDotButton: React.FC<{ selectedRowKeys: string[]; onSelectChange: (selectedRowKeys: string[]) => void; }> = ({ selectedRowKeys, onSelectChange }) => { - const bulkEditFavorites = useUserConfigContextSelector((state) => state.bulkEditFavorites); - const userFavorites = useUserConfigContextSelector((state) => state.userFavorites); + const { mutateAsync: removeUserFavorite } = useDeleteUserFavorite(); + const { data: userFavorites } = useGetUserFavorites(); const dropdownOptions: DropdownItems = [ { @@ -156,10 +161,10 @@ const ThreeDotButton: React.FC<{ icon: , value: 'remove-from-favorites', onClick: async () => { - const allFavorites: string[] = userFavorites - .map((f) => f.id) - .filter((id) => !selectedRowKeys.includes(id)); - bulkEditFavorites(allFavorites); + const allFavorites: Parameters[0] = userFavorites + .filter((f) => !selectedRowKeys.includes(f.id)) + .map((f) => ({ id: f.id, asset_type: ShareAssetType.METRIC })); + await removeUserFavorite(allFavorites); } } ];