diff --git a/apps/web/src/app/[orgShortCode]/convo/_components/convo-list.tsx b/apps/web/src/app/[orgShortCode]/convo/_components/convo-list.tsx
index bcb2535f..84471e78 100644
--- a/apps/web/src/app/[orgShortCode]/convo/_components/convo-list.tsx
+++ b/apps/web/src/app/[orgShortCode]/convo/_components/convo-list.tsx
@@ -2,16 +2,19 @@
import { type RouterOutputs, api } from '@/src/lib/trpc';
import { useGlobalStore } from '@/src/providers/global-store-provider';
-import { useEffect, useMemo, useRef } from 'react';
+import { useState, useEffect, useMemo, useRef } from 'react';
import { useVirtualizer } from '@tanstack/react-virtual';
import useTimeAgo from '@/src/hooks/use-time-ago';
import { formatParticipantData } from '../utils';
import Link from 'next/link';
import AvatarPlus from '@/src/components/avatar-plus';
+import { Button } from '@/src/components/shadcn-ui/button';
+import { ms } from '@u22n/utils/ms';
export default function ConvoList() {
const orgShortCode = useGlobalStore((state) => state.currentOrg.shortCode);
const scrollableRef = useRef(null);
+ const [showHidden, setShowHidden] = useState(false);
const {
data: convos,
@@ -21,10 +24,12 @@ export default function ConvoList() {
isFetchingNextPage
} = api.convos.getOrgMemberConvos.useInfiniteQuery(
{
- orgShortCode
+ orgShortCode,
+ includeHidden: showHidden ? true : undefined
},
{
- getNextPageParam: (lastPage) => lastPage.cursor ?? undefined
+ getNextPageParam: (lastPage) => lastPage.cursor ?? undefined,
+ staleTime: ms('1 hour')
}
);
@@ -57,43 +62,53 @@ export default function ConvoList() {
]);
return (
-
+
{isLoading ? (
Loading...
) : (
-
+ <>
+ {/* TODO: Replace this according to designs later */}
+
+
+
- {convosVirtualizer.getVirtualItems().map((virtualItem) => {
- const isLoader = virtualItem.index > allConvos.length - 1;
- const convo = allConvos[virtualItem.index]!;
+ className="h-full max-h-full w-full max-w-full overflow-y-auto overflow-x-hidden"
+ ref={scrollableRef}>
+
+ {convosVirtualizer.getVirtualItems().map((virtualItem) => {
+ const isLoader = virtualItem.index > allConvos.length - 1;
+ const convo = allConvos[virtualItem.index]!;
- return (
-
- {isLoader ? (
-
- {hasNextPage ? 'Loading...' : ''}
-
- ) : (
-
-
-
- )}
-
- );
- })}
+ return (
+
+ {isLoader ? (
+
+ {hasNextPage ? 'Loading...' : ''}
+
+ ) : (
+
+
+
+ )}
+
+ );
+ })}
+
-
+ >
)}
);
diff --git a/apps/web/src/app/[orgShortCode]/convo/utils.ts b/apps/web/src/app/[orgShortCode]/convo/utils.ts
index 432a28db..f72c271e 100644
--- a/apps/web/src/app/[orgShortCode]/convo/utils.ts
+++ b/apps/web/src/app/[orgShortCode]/convo/utils.ts
@@ -1,6 +1,8 @@
import { api, type RouterOutputs } from '@/src/lib/trpc';
import { useGlobalStore } from '@/src/providers/global-store-provider';
import { type TypeId } from '@u22n/utils/typeid';
+import { type InfiniteData } from '@tanstack/react-query';
+import { useCallback } from 'react';
export function formatParticipantData(
participant: RouterOutputs['convos']['getOrgMemberConvos']['data'][number]['participants'][number]
@@ -56,9 +58,9 @@ export function formatParticipantData(
export function useAddSingleConvo$Cache() {
const orgShortCode = useGlobalStore((state) => state.currentOrg.shortCode);
- const convoListApi = api.useUtils().convos.getOrgMemberConvos;
- const getOrgMemberSpecificConvoApi =
- api.useUtils().convos.getOrgMemberSpecificConvo;
+ const utils = api.useUtils();
+ const convoListApi = utils.convos.getOrgMemberConvos;
+ const getOrgMemberSpecificConvoApi = utils.convos.getOrgMemberSpecificConvo;
return async (convoId: TypeId<'convos'>) => {
const convo = await getOrgMemberSpecificConvoApi.fetch({
@@ -78,10 +80,11 @@ export function useAddSingleConvo$Cache() {
export function useDeleteConvo$Cache() {
const orgShortCode = useGlobalStore((state) => state.currentOrg.shortCode);
const convoListApi = api.useUtils().convos.getOrgMemberConvos;
-
- return async (convoId: TypeId<'convos'>) => {
- await convoListApi.cancel({ orgShortCode });
- convoListApi.setInfiniteData({ orgShortCode }, (updater) => {
+ const deleteFn = useCallback(
+ (
+ convoId: TypeId<'convos'>,
+ updater?: InfiniteData
+ ) => {
if (!updater) return;
const clonedUpdater = structuredClone(updater);
for (const page of clonedUpdater.pages) {
@@ -93,52 +96,58 @@ export function useDeleteConvo$Cache() {
break;
}
return clonedUpdater;
- });
+ },
+ []
+ );
+
+ return async (convoId: TypeId<'convos'>) => {
+ await convoListApi.cancel({ orgShortCode });
+ await convoListApi.cancel({ orgShortCode, includeHidden: true });
+
+ convoListApi.setInfiniteData({ orgShortCode }, (updater) =>
+ deleteFn(convoId, updater)
+ );
+ convoListApi.setInfiniteData(
+ { orgShortCode, includeHidden: true },
+ (updater) => deleteFn(convoId, updater)
+ );
};
}
+// TODO: Simplify this function later, its too complex
export function useToggleConvoHidden$Cache() {
const orgShortCode = useGlobalStore((state) => state.currentOrg.shortCode);
- const convoApi = api.useUtils().convos.getConvo;
- const convoListApi = api.useUtils().convos.getOrgMemberConvos;
- const specificConvoApi = api.useUtils().convos.getOrgMemberSpecificConvo;
+ const utils = api.useUtils();
+ const convoApi = utils.convos.getConvo;
+ const convoListApi = utils.convos.getOrgMemberConvos;
+ const specificConvoApi = utils.convos.getOrgMemberSpecificConvo;
- return async (convoId: TypeId<'convos'>, hide = false) => {
- const convoToAdd = !hide
- ? await specificConvoApi.fetch({
- convoPublicId: convoId,
- orgShortCode
- })
- : null;
-
- await convoApi.cancel({ convoPublicId: convoId, orgShortCode });
- convoApi.setData({ convoPublicId: convoId, orgShortCode }, (updater) => {
+ // This function is a bit complex, but basically what it does is updates the provided updater by either removing or adding a convo based on the parameters
+ const convoListUpdaterFn = useCallback(
+ (
+ hideFromList: boolean,
+ convoToAdd: RouterOutputs['convos']['getOrgMemberSpecificConvo'] | null,
+ convoToRemove: TypeId<'convos'> | null,
+ updater?: InfiniteData
+ ) => {
if (!updater) return;
const clonedUpdater = structuredClone(updater);
- const participantIndex = clonedUpdater.data.participants.findIndex(
- (participant) => participant.publicId === updater.ownParticipantPublicId
- );
- if (participantIndex === -1) return;
- clonedUpdater.data.participants[participantIndex]!.hidden = hide;
- return clonedUpdater;
- });
- await convoListApi.cancel({ orgShortCode });
- convoListApi.setInfiniteData({ orgShortCode }, (updater) => {
- if (!updater) return;
- const clonedUpdater = structuredClone(updater);
-
- if (hide) {
+ if (hideFromList) {
for (const page of clonedUpdater.pages) {
const convoIndex = page.data.findIndex(
- (convo) => convo.publicId === convoId
+ (convo) => convo.publicId === convoToRemove
);
if (convoIndex === -1) continue;
page.data.splice(convoIndex, 1);
break;
}
} else {
- const clonedConvo = structuredClone(convoToAdd)!; // We know it's not null as we are not hiding
+ if (!convoToAdd)
+ throw new Error(
+ 'Trying to unhide from convo list without providing the convo to add'
+ );
+ const clonedConvo = structuredClone(convoToAdd);
let convoAlreadyAdded = false;
for (const page of clonedUpdater.pages) {
const insertIndex = page.data.findIndex(
@@ -159,14 +168,81 @@ export function useToggleConvoHidden$Cache() {
}
}
return clonedUpdater;
+ },
+ []
+ );
+
+ return async (convoId: TypeId<'convos'>, hide = false) => {
+ await convoApi.cancel({ convoPublicId: convoId, orgShortCode });
+ convoApi.setData({ convoPublicId: convoId, orgShortCode }, (updater) => {
+ if (!updater) return;
+ const clonedUpdater = structuredClone(updater);
+ const participantIndex = clonedUpdater.data.participants.findIndex(
+ (participant) => participant.publicId === updater.ownParticipantPublicId
+ );
+ if (participantIndex === -1) return;
+ clonedUpdater.data.participants[participantIndex]!.hidden = hide;
+ return clonedUpdater;
});
+
+ const convoToAdd = await specificConvoApi.fetch({
+ convoPublicId: convoId,
+ orgShortCode
+ });
+
+ // Update both hidden and non-hidden convo lists
+ await convoListApi.cancel({ orgShortCode, includeHidden: true });
+ await convoListApi.cancel({ orgShortCode });
+
+ // if we are hiding a convo, we need to remove it from the non-hidden list and add to hidden list
+ if (hide) {
+ convoListApi.setInfiniteData({ orgShortCode }, (updater) =>
+ convoListUpdaterFn(
+ /* hide from non-hidden */ true,
+ null,
+ convoId,
+ updater
+ )
+ );
+ convoListApi.setInfiniteData(
+ { orgShortCode, includeHidden: true },
+ (updater) =>
+ convoListUpdaterFn(
+ /* add from hidden */ false,
+ convoToAdd,
+ null,
+ updater
+ )
+ );
+ } else {
+ // if we are un-hiding a convo, we need to remove it from the hidden list and add to non-hidden list
+ convoListApi.setInfiniteData({ orgShortCode }, (updater) =>
+ convoListUpdaterFn(
+ /* add to non-hidden */ false,
+ convoToAdd,
+ null,
+ updater
+ )
+ );
+ convoListApi.setInfiniteData(
+ { orgShortCode, includeHidden: true },
+ (updater) =>
+ convoListUpdaterFn(
+ /* hide from hidden */ true,
+ null,
+ convoId,
+ updater
+ )
+ );
+ }
};
}
export function useUpdateConvoMessageList$Cache() {
const orgShortCode = useGlobalStore((state) => state.currentOrg.shortCode);
- const convoEntiresApi = api.useUtils().convos.entries.getConvoEntries;
- const singleConvoEntryApi = api.useUtils().convos.entries.getConvoSingleEntry;
+ const utils = api.useUtils();
+ const convoEntiresApi = utils.convos.entries.getConvoEntries;
+ const singleConvoEntryApi = utils.convos.entries.getConvoSingleEntry;
// TODO: make the reply mutation return the new convo entry, to save one API call
return async (