From 808632ac9f81e1d1b13ac99cbb2996331aa12a9e Mon Sep 17 00:00:00 2001 From: Nikhil Kothari Date: Fri, 3 May 2024 22:59:39 +0530 Subject: [PATCH] fix: active users indicator fixed --- .../chat-header/ViewChannelDetailsButton.tsx | 8 ++- .../command-palette/CommandPalette.tsx | 8 ++- .../src/hooks/fetchers/useFetchActiveUsers.ts | 55 +++++++++++++++++++ raven-app/src/hooks/useActiveState.tsx | 18 +++++- raven-app/src/hooks/useIsUserActive.ts | 9 +-- raven-app/src/pages/MainPage.tsx | 47 +++++++++------- .../src/utils/channel/ChannelRedirect.tsx | 5 +- .../src/utils/users/ActiveUsersProvider.tsx | 17 ------ 8 files changed, 115 insertions(+), 52 deletions(-) create mode 100644 raven-app/src/hooks/fetchers/useFetchActiveUsers.ts delete mode 100644 raven-app/src/utils/users/ActiveUsersProvider.tsx diff --git a/raven-app/src/components/feature/chat-header/ViewChannelDetailsButton.tsx b/raven-app/src/components/feature/chat-header/ViewChannelDetailsButton.tsx index 4beb439f3..d30a7740d 100644 --- a/raven-app/src/components/feature/chat-header/ViewChannelDetailsButton.tsx +++ b/raven-app/src/components/feature/chat-header/ViewChannelDetailsButton.tsx @@ -1,12 +1,12 @@ import { ViewChannelDetailsModalContent } from "../channels/ViewChannelDetailsModal" -import { useContext, useState } from "react" -import { ActiveUsersContext } from "@/utils/users/ActiveUsersProvider" +import { useState } from "react" import { ChannelListItem } from "@/utils/channel/ChannelListProvider" import { ChannelMembers } from "@/utils/channel/ChannelMembersProvider" import { Button, Dialog, Tooltip } from "@radix-ui/themes" import { UserAvatar } from "@/components/common/UserAvatar" import { BiSolidUser } from "react-icons/bi" import { clsx } from "clsx" +import useFetchActiveUsers from "@/hooks/fetchers/useFetchActiveUsers" interface ViewChannelDetailsButtonProps { channelData: ChannelListItem, @@ -23,7 +23,9 @@ export const ViewChannelDetailsButton = ({ channelData, allowAddMembers, channel setOpen(false) } - const activeUsers = useContext(ActiveUsersContext) + const { data } = useFetchActiveUsers() + const activeUsers = data?.message ?? [] + const totalMembers = Object.keys(channelMembers).length return ( diff --git a/raven-app/src/components/feature/command-palette/CommandPalette.tsx b/raven-app/src/components/feature/command-palette/CommandPalette.tsx index f16725715..afba9caa9 100644 --- a/raven-app/src/components/feature/command-palette/CommandPalette.tsx +++ b/raven-app/src/components/feature/command-palette/CommandPalette.tsx @@ -8,9 +8,9 @@ import { useNavigate } from 'react-router-dom' import { UserContext } from '../../../utils/auth/UserProvider' import { BiSearch, BiX } from 'react-icons/bi' import { UserListContext } from '@/utils/users/UserListProvider' -import { ActiveUsersContext } from '@/utils/users/ActiveUsersProvider' import { ModalTypes, useModalManager } from '@/hooks/useModalManager' import { Flex, IconButton, Box, Text, Link } from '@radix-ui/themes' +import useFetchActiveUsers from '@/hooks/fetchers/useFetchActiveUsers' interface CommandPaletteProps { isOpen: boolean, @@ -37,7 +37,11 @@ export const CommandPalette = ({ isOpen, onClose }: CommandPaletteProps) => { const isHome = activePage === '' const debouncedText = useDebounce(inputValue, 200) const { currentUser } = useContext(UserContext) - const activeUsers = useContext(ActiveUsersContext) + + const { data } = useFetchActiveUsers() + + const activeUsers = data?.message ?? [] + const { call, reset } = useFrappePostCall<{ message: string }>("raven.api.raven_channel.create_direct_message_channel") let navigate = useNavigate() diff --git a/raven-app/src/hooks/fetchers/useFetchActiveUsers.ts b/raven-app/src/hooks/fetchers/useFetchActiveUsers.ts new file mode 100644 index 000000000..5200a7026 --- /dev/null +++ b/raven-app/src/hooks/fetchers/useFetchActiveUsers.ts @@ -0,0 +1,55 @@ +import { UserContext } from '@/utils/auth/UserProvider' +import { useFrappeEventListener, useFrappeGetCall, useSWRConfig } from 'frappe-react-sdk' +import { useContext } from 'react' +import { useActiveState } from '../useActiveState' + +/** + * Hook to fetch active users from the server. + * SWRKey: active_users + */ +const useFetchActiveUsers = () => { + const res = useFrappeGetCall<{ message: string[] }>('raven.api.user_availability.get_active_users', + undefined, + 'active_users', + { + dedupingInterval: 1000 * 60 * 5, // 5 minutes - do not refetch if the data is fresh + } + ) + + return res +} + +/** + * Hook to listen to user_active_state_updated event and update the active_users list in realtime + * Also handles the user's active state via visibilty change and idle timer + */ +export const useFetchActiveUsersRealtime = () => { + const { currentUser } = useContext(UserContext) + + const { mutate } = useSWRConfig() + + useActiveState() + + /** Hook to listen to user_active_state */ + useFrappeEventListener('raven:user_active_state_updated', (data) => { + if (data.user !== currentUser) { + // If the user is not the current user, update the active_users list + // No need to revalidate the data as the websocket event has emitted the new data for that user + mutate('active_users', (res?: { message: string[] }) => { + if (res) { + if (data.active) { + return { message: [...res.message, data.user] } + } else { + return { message: res.message.filter(user => user !== data.user) } + } + } else { + return undefined + } + }, { + revalidate: false + }) + } + }) +} + +export default useFetchActiveUsers \ No newline at end of file diff --git a/raven-app/src/hooks/useActiveState.tsx b/raven-app/src/hooks/useActiveState.tsx index ac8ba71d4..692e7d345 100644 --- a/raven-app/src/hooks/useActiveState.tsx +++ b/raven-app/src/hooks/useActiveState.tsx @@ -62,8 +62,24 @@ export const useActiveState = () => { useEffect(() => { // Update user availability when the app is opened call.get('raven.api.user_availability.refresh_user_active_state', { - deactivate + deactivate: false + }) + + document.addEventListener('visibilitychange', () => { + if (document.visibilityState === 'visible') { + updateUserActiveState().then(activate) + } + else { + updateUserActiveState(true).then(deactivate) + } }) + + return () => { + // Update user availability when the app is closed + call.get('raven.api.user_availability.refresh_user_active_state', { + deactivate: true + }) + } }, []) return isActive diff --git a/raven-app/src/hooks/useIsUserActive.ts b/raven-app/src/hooks/useIsUserActive.ts index 2fb4a6ce9..62b9dc087 100644 --- a/raven-app/src/hooks/useIsUserActive.ts +++ b/raven-app/src/hooks/useIsUserActive.ts @@ -1,21 +1,22 @@ import { useContext, useMemo } from 'react'; -import { ActiveUsersContext } from '../utils/users/ActiveUsersProvider'; import { UserContext } from '@/utils/auth/UserProvider'; +import useFetchActiveUsers from './fetchers/useFetchActiveUsers'; export const useIsUserActive = (userID?: string): boolean => { const { currentUser } = useContext(UserContext) - const activeUsers = useContext(ActiveUsersContext) + + const { data } = useFetchActiveUsers() const isActive = useMemo(() => { if (userID === currentUser) { return true } else if (userID) { - return activeUsers.includes(userID) + return data?.message.includes(userID) ?? false } else { return false } - }, [userID, activeUsers]) + }, [userID, data]) return isActive } \ No newline at end of file diff --git a/raven-app/src/pages/MainPage.tsx b/raven-app/src/pages/MainPage.tsx index e66160c7f..3a5ef59e7 100644 --- a/raven-app/src/pages/MainPage.tsx +++ b/raven-app/src/pages/MainPage.tsx @@ -4,12 +4,12 @@ import { lazy, Suspense } from 'react' import { Sidebar } from '../components/layout/Sidebar/Sidebar' import { ChannelListProvider } from '../utils/channel/ChannelListProvider' import { UserListProvider } from '@/utils/users/UserListProvider' -import { ActiveUsersProvider } from '@/utils/users/ActiveUsersProvider' import { hasRavenUserRole } from '@/utils/roles' import { FullPageLoader } from '@/components/layout/Loaders' import { MobileAppRedirectBanner } from '@/components/layout/AlertBanner' import '../components/layout/AlertBanner/styles.css' import CommandMenu from '@/components/feature/CommandMenu/CommandMenu' +import { useFetchActiveUsersRealtime } from '@/hooks/fetchers/useFetchActiveUsers' const AddRavenUsersPage = lazy(() => import('@/pages/AddRavenUsersPage')) @@ -19,26 +19,7 @@ export const MainPage = () => { if (isRavenUser) { return ( - - - -
- - - - - - - - -
-
- -
- -
-
-
+ ) } else { // If the user does not have the Raven User role, then show an error message if the user cannot add more people. @@ -48,4 +29,28 @@ export const MainPage = () => { } +} + +const MainPageContent = () => { + + useFetchActiveUsersRealtime() + + return + +
+ + + + + + + + +
+
+ +
+ +
+
} \ No newline at end of file diff --git a/raven-app/src/utils/channel/ChannelRedirect.tsx b/raven-app/src/utils/channel/ChannelRedirect.tsx index 77345a237..4cac5deea 100644 --- a/raven-app/src/utils/channel/ChannelRedirect.tsx +++ b/raven-app/src/utils/channel/ChannelRedirect.tsx @@ -3,7 +3,6 @@ import { Outlet, useLocation, useNavigate } from 'react-router-dom' import { hasRavenUserRole } from '../roles' import { FullPageLoader } from '@/components/layout/Loaders' import AddRavenUsersPage from '@/pages/AddRavenUsersPage' -import { ActiveUsersProvider } from '../users/ActiveUsersProvider' import { UserListProvider } from '../users/UserListProvider' import { ChannelListProvider } from './ChannelListProvider' @@ -30,9 +29,7 @@ export const ChannelRedirect = () => { return ( - - - + ) diff --git a/raven-app/src/utils/users/ActiveUsersProvider.tsx b/raven-app/src/utils/users/ActiveUsersProvider.tsx deleted file mode 100644 index e21a43abd..000000000 --- a/raven-app/src/utils/users/ActiveUsersProvider.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import { useFrappeEventListener, useFrappeGetCall } from "frappe-react-sdk"; -import { PropsWithChildren, createContext } from "react"; - -export const ActiveUsersContext = createContext([]) - -export const ActiveUsersProvider = ({ children }: PropsWithChildren) => { - - const { data, mutate } = useFrappeGetCall<{ message: string[] }>('raven.api.user_availability.get_active_users') - - useFrappeEventListener('raven:user_active_state_updated', (data) => { - mutate() - }) - - return - {children} - -} \ No newline at end of file