Compare commits
No commits in common. "3f39a3d05a644d6ac31823652bdc20a8c7a0159d" and "76fa04d1295831f02968764cdeeb7b4c57686df2" have entirely different histories.
3f39a3d05a
...
76fa04d129
Binary file not shown.
@ -57,11 +57,6 @@ export default function AdminChatPage() {
|
||||
|
||||
const typingTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||
const socketRef = useRef<Socket | null>(null);
|
||||
const getTokenRef = useRef(getToken);
|
||||
const activeThreadIdRef = useRef<string | null>(null);
|
||||
const subscribedThreadIdsRef = useRef<Set<string>>(new Set());
|
||||
const loadingThreadsRef = useRef(false);
|
||||
const loadingMessagesRef = useRef<Record<string, boolean>>({});
|
||||
|
||||
const activeMessages = useMemo(
|
||||
() => (activeThreadId ? (messagesByThreadId[activeThreadId] ?? []) : []),
|
||||
@ -74,28 +69,14 @@ export default function AdminChatPage() {
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
getTokenRef.current = getToken;
|
||||
}, [getToken]);
|
||||
|
||||
useEffect(() => {
|
||||
activeThreadIdRef.current = activeThreadId;
|
||||
}, [activeThreadId]);
|
||||
|
||||
const loadThreads = async (showLoader = true) => {
|
||||
if (loadingThreadsRef.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
const token = await getTokenRef.current();
|
||||
const loadThreads = async () => {
|
||||
const token = await getToken();
|
||||
if (!token) {
|
||||
return;
|
||||
}
|
||||
|
||||
loadingThreadsRef.current = true;
|
||||
try {
|
||||
if (showLoader) {
|
||||
setLoadingThreads(true);
|
||||
}
|
||||
const response = await axios.get<{ threads: ChatThread[] }>(
|
||||
"/api/chat/threads",
|
||||
{
|
||||
@ -104,33 +85,33 @@ export default function AdminChatPage() {
|
||||
);
|
||||
|
||||
setThreads(response.data.threads);
|
||||
setActiveThreadId((prev) => prev ?? response.data.threads[0]?.id ?? null);
|
||||
setActiveThreadId(
|
||||
(prev) => prev ?? response.data.threads[0]?.id ?? null,
|
||||
);
|
||||
setReadByThreadId({});
|
||||
} finally {
|
||||
if (showLoader) {
|
||||
setLoadingThreads(false);
|
||||
}
|
||||
loadingThreadsRef.current = false;
|
||||
}
|
||||
};
|
||||
|
||||
const loadMessages = async (threadId: string, showLoader = true) => {
|
||||
if (loadingMessagesRef.current[threadId]) {
|
||||
void loadThreads();
|
||||
}, [getToken]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!activeThreadId) {
|
||||
return;
|
||||
}
|
||||
|
||||
const token = await getTokenRef.current();
|
||||
const loadMessages = async () => {
|
||||
const token = await getToken();
|
||||
if (!token) {
|
||||
return;
|
||||
}
|
||||
|
||||
loadingMessagesRef.current[threadId] = true;
|
||||
try {
|
||||
if (showLoader) {
|
||||
setLoadingMessages(true);
|
||||
}
|
||||
const response = await axios.get<{ messages: ChatMessage[] }>(
|
||||
`/api/chat/threads/${threadId}/messages`,
|
||||
`/api/chat/threads/${activeThreadId}/messages`,
|
||||
{
|
||||
headers: { Authorization: `Bearer ${token}` },
|
||||
},
|
||||
@ -138,33 +119,21 @@ export default function AdminChatPage() {
|
||||
|
||||
setMessagesByThreadId((prev) => ({
|
||||
...prev,
|
||||
[threadId]: response.data.messages,
|
||||
[activeThreadId]: response.data.messages,
|
||||
}));
|
||||
} finally {
|
||||
if (showLoader) {
|
||||
setLoadingMessages(false);
|
||||
}
|
||||
loadingMessagesRef.current[threadId] = false;
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
void loadThreads(true);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (!activeThreadId) {
|
||||
return;
|
||||
}
|
||||
|
||||
void loadMessages(activeThreadId, true);
|
||||
}, [activeThreadId]);
|
||||
void loadMessages();
|
||||
}, [activeThreadId, getToken]);
|
||||
|
||||
useEffect(() => {
|
||||
let mounted = true;
|
||||
|
||||
const setupSocket = async () => {
|
||||
const token = await getTokenRef.current();
|
||||
const token = await getToken();
|
||||
if (!token || !mounted) {
|
||||
return;
|
||||
}
|
||||
@ -182,14 +151,14 @@ export default function AdminChatPage() {
|
||||
|
||||
socket.on("connect", () => {
|
||||
setSocketConnected(true);
|
||||
subscribedThreadIdsRef.current.clear();
|
||||
void loadThreads(false);
|
||||
if (activeThreadId) {
|
||||
socket.emit("chat:subscribe", { threadId: activeThreadId });
|
||||
}
|
||||
});
|
||||
|
||||
socket.on("disconnect", () => {
|
||||
setSocketConnected(false);
|
||||
setTypingByThreadId({});
|
||||
subscribedThreadIdsRef.current.clear();
|
||||
});
|
||||
|
||||
socket.on(
|
||||
@ -216,7 +185,7 @@ export default function AdminChatPage() {
|
||||
lastMessageAt: event.message.createdAt,
|
||||
lastMessageBody: event.message.body,
|
||||
unreadCount:
|
||||
event.threadId === activeThreadIdRef.current ||
|
||||
event.threadId === activeThreadId ||
|
||||
event.message.senderUserId === userId
|
||||
? thread.unreadCount
|
||||
: thread.unreadCount + 1,
|
||||
@ -294,8 +263,6 @@ export default function AdminChatPage() {
|
||||
[event.userId!]: event.lastReadMessageId!,
|
||||
},
|
||||
}));
|
||||
|
||||
void loadThreads(false);
|
||||
});
|
||||
|
||||
socket.on("chat:error", (event: { code?: string; message?: string }) => {
|
||||
@ -317,57 +284,28 @@ export default function AdminChatPage() {
|
||||
socketRef.current = null;
|
||||
}
|
||||
};
|
||||
}, [userId]);
|
||||
}, [activeThreadId, getToken, userId]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!socketRef.current || !socketConnected) {
|
||||
return;
|
||||
}
|
||||
|
||||
const socket = socketRef.current;
|
||||
const nextIds = new Set(threads.map((thread) => thread.id));
|
||||
|
||||
threads.forEach((thread) => {
|
||||
if (!subscribedThreadIdsRef.current.has(thread.id)) {
|
||||
socket.emit("chat:subscribe", { threadId: thread.id });
|
||||
subscribedThreadIdsRef.current.add(thread.id);
|
||||
}
|
||||
});
|
||||
|
||||
Array.from(subscribedThreadIdsRef.current).forEach((threadId) => {
|
||||
if (!nextIds.has(threadId)) {
|
||||
socket.emit("chat:unsubscribe", { threadId });
|
||||
subscribedThreadIdsRef.current.delete(threadId);
|
||||
}
|
||||
});
|
||||
}, [socketConnected, threads]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!activeThreadId) {
|
||||
if (!activeThreadId || !socketRef.current || !socketConnected) {
|
||||
return;
|
||||
}
|
||||
|
||||
socketRef.current.emit("chat:subscribe", { threadId: activeThreadId });
|
||||
setThreads((prev) =>
|
||||
prev.map((thread) =>
|
||||
thread.id === activeThreadId ? { ...thread, unreadCount: 0 } : thread,
|
||||
),
|
||||
);
|
||||
}, [activeThreadId]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!socketConnected) {
|
||||
const interval = setInterval(() => {
|
||||
void loadThreads(false);
|
||||
if (activeThreadIdRef.current) {
|
||||
void loadMessages(activeThreadIdRef.current, false);
|
||||
}
|
||||
}, 5000);
|
||||
|
||||
return () => {
|
||||
clearInterval(interval);
|
||||
};
|
||||
if (socketRef.current) {
|
||||
socketRef.current.emit("chat:unsubscribe", {
|
||||
threadId: activeThreadId,
|
||||
});
|
||||
}
|
||||
}, [socketConnected]);
|
||||
};
|
||||
}, [activeThreadId, socketConnected]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!activeThreadId) {
|
||||
@ -381,7 +319,7 @@ export default function AdminChatPage() {
|
||||
}
|
||||
|
||||
const markRead = async () => {
|
||||
const token = await getTokenRef.current();
|
||||
const token = await getToken();
|
||||
if (!token) {
|
||||
return;
|
||||
}
|
||||
@ -413,7 +351,7 @@ export default function AdminChatPage() {
|
||||
};
|
||||
|
||||
void markRead();
|
||||
}, [activeThreadId, messagesByThreadId, socketConnected, userId]);
|
||||
}, [activeThreadId, getToken, messagesByThreadId, socketConnected, userId]);
|
||||
|
||||
const onChangeDraft = (value: string) => {
|
||||
setDraft(value);
|
||||
@ -481,7 +419,7 @@ export default function AdminChatPage() {
|
||||
return;
|
||||
}
|
||||
|
||||
const token = await getTokenRef.current();
|
||||
const token = await getToken();
|
||||
if (!token) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -74,26 +74,16 @@ export function ChatProvider({ children }: { children: React.ReactNode }) {
|
||||
>({});
|
||||
|
||||
const socketRef = useRef<Socket | null>(null);
|
||||
const getTokenRef = useRef(getToken);
|
||||
const lastMarkedReadByThreadRef = useRef<Record<string, string>>({});
|
||||
const activeThreadIdRef = useRef<string | null>(null);
|
||||
const currentUserIdRef = useRef<string | undefined>(undefined);
|
||||
const subscribedThreadIdsRef = useRef<Set<string>>(new Set());
|
||||
const refreshThreadsRef = useRef<(showLoader?: boolean) => Promise<void>>(
|
||||
const refreshThreadsRef = useRef<() => Promise<void>>(async () => {});
|
||||
const refreshMessagesRef = useRef<(threadId: string) => Promise<void>>(
|
||||
async () => {},
|
||||
);
|
||||
const refreshMessagesRef = useRef<
|
||||
(threadId: string, showLoader?: boolean) => Promise<void>
|
||||
>(async () => {});
|
||||
const markThreadReadRef = useRef<
|
||||
(threadId: string, lastReadMessageId?: string) => Promise<void>
|
||||
>(async () => {});
|
||||
const refreshingThreadsRef = useRef(false);
|
||||
const refreshingMessagesRef = useRef<Record<string, boolean>>({});
|
||||
|
||||
useEffect(() => {
|
||||
getTokenRef.current = getToken;
|
||||
}, [getToken]);
|
||||
|
||||
const clearAll = useCallback(() => {
|
||||
setThreads([]);
|
||||
@ -107,7 +97,6 @@ export function ChatProvider({ children }: { children: React.ReactNode }) {
|
||||
setSocketConnected(false);
|
||||
setTypingByThreadId({});
|
||||
lastMarkedReadByThreadRef.current = {};
|
||||
subscribedThreadIdsRef.current.clear();
|
||||
|
||||
if (socketRef.current) {
|
||||
socketRef.current.disconnect();
|
||||
@ -115,28 +104,18 @@ export function ChatProvider({ children }: { children: React.ReactNode }) {
|
||||
}
|
||||
}, []);
|
||||
|
||||
const refreshThreads = useCallback(
|
||||
async (showLoader = true) => {
|
||||
const refreshThreads = useCallback(async () => {
|
||||
if (!isSignedIn) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (refreshingThreadsRef.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
refreshingThreadsRef.current = true;
|
||||
|
||||
const token = await getTokenRef.current();
|
||||
const token = await getToken();
|
||||
if (!token) {
|
||||
refreshingThreadsRef.current = false;
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
if (showLoader) {
|
||||
setLoadingThreads(true);
|
||||
}
|
||||
if (isClientUser) {
|
||||
try {
|
||||
await chatApi.getMyDmThread(token);
|
||||
@ -158,37 +137,23 @@ export function ChatProvider({ children }: { children: React.ReactNode }) {
|
||||
} catch (error) {
|
||||
log.warn("Failed to refresh chat threads", { error });
|
||||
} finally {
|
||||
if (showLoader) {
|
||||
setLoadingThreads(false);
|
||||
}
|
||||
refreshingThreadsRef.current = false;
|
||||
}
|
||||
},
|
||||
[isSignedIn, isClientUser],
|
||||
);
|
||||
}, [getToken, isSignedIn, isClientUser]);
|
||||
|
||||
const refreshMessages = useCallback(
|
||||
async (threadId: string, showLoader = true) => {
|
||||
async (threadId: string) => {
|
||||
if (!isSignedIn) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (refreshingMessagesRef.current[threadId]) {
|
||||
return;
|
||||
}
|
||||
|
||||
refreshingMessagesRef.current[threadId] = true;
|
||||
|
||||
const token = await getTokenRef.current();
|
||||
const token = await getToken();
|
||||
if (!token) {
|
||||
refreshingMessagesRef.current[threadId] = false;
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
if (showLoader) {
|
||||
setLoadingMessages(true);
|
||||
}
|
||||
const response = await chatApi.getThreadMessages(threadId, token);
|
||||
setMessagesByThreadId((prev) => ({
|
||||
...prev,
|
||||
@ -207,13 +172,10 @@ export function ChatProvider({ children }: { children: React.ReactNode }) {
|
||||
} catch (error) {
|
||||
log.warn("Failed to refresh chat messages", { threadId, error });
|
||||
} finally {
|
||||
if (showLoader) {
|
||||
setLoadingMessages(false);
|
||||
}
|
||||
refreshingMessagesRef.current[threadId] = false;
|
||||
}
|
||||
},
|
||||
[isSignedIn],
|
||||
[getToken, isSignedIn],
|
||||
);
|
||||
|
||||
const loadOlderMessages = useCallback(
|
||||
@ -231,7 +193,7 @@ export function ChatProvider({ children }: { children: React.ReactNode }) {
|
||||
return;
|
||||
}
|
||||
|
||||
const token = await getTokenRef.current();
|
||||
const token = await getToken();
|
||||
if (!token) {
|
||||
return;
|
||||
}
|
||||
@ -273,7 +235,7 @@ export function ChatProvider({ children }: { children: React.ReactNode }) {
|
||||
setLoadingOlderByThreadId((prev) => ({ ...prev, [threadId]: false }));
|
||||
}
|
||||
},
|
||||
[isSignedIn, loadingOlderByThreadId, nextCursorByThreadId],
|
||||
[getToken, isSignedIn, loadingOlderByThreadId, nextCursorByThreadId],
|
||||
);
|
||||
|
||||
const markThreadRead = useCallback(
|
||||
@ -282,7 +244,7 @@ export function ChatProvider({ children }: { children: React.ReactNode }) {
|
||||
return;
|
||||
}
|
||||
|
||||
const token = await getTokenRef.current();
|
||||
const token = await getToken();
|
||||
if (!token) {
|
||||
return;
|
||||
}
|
||||
@ -309,7 +271,7 @@ export function ChatProvider({ children }: { children: React.ReactNode }) {
|
||||
log.warn("Failed to mark thread read", { threadId, error });
|
||||
}
|
||||
},
|
||||
[isSignedIn, user?.id],
|
||||
[getToken, isSignedIn, user?.id],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
@ -339,7 +301,7 @@ export function ChatProvider({ children }: { children: React.ReactNode }) {
|
||||
return;
|
||||
}
|
||||
|
||||
const token = await getTokenRef.current();
|
||||
const token = await getToken();
|
||||
if (!token) {
|
||||
return;
|
||||
}
|
||||
@ -398,7 +360,7 @@ export function ChatProvider({ children }: { children: React.ReactNode }) {
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
[isSignedIn, socketConnected, user?.id],
|
||||
[getToken, isSignedIn, socketConnected, user?.id],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
@ -410,7 +372,7 @@ export function ChatProvider({ children }: { children: React.ReactNode }) {
|
||||
let mounted = true;
|
||||
|
||||
const setupSocket = async () => {
|
||||
const token = await getTokenRef.current();
|
||||
const token = await getToken();
|
||||
if (!token || !mounted) {
|
||||
return;
|
||||
}
|
||||
@ -420,7 +382,7 @@ export function ChatProvider({ children }: { children: React.ReactNode }) {
|
||||
|
||||
socket.on("connect", () => {
|
||||
setSocketConnected(true);
|
||||
void refreshThreadsRef.current(false);
|
||||
void refreshThreadsRef.current();
|
||||
|
||||
if (activeThreadIdRef.current) {
|
||||
socket.emit("chat:subscribe", {
|
||||
@ -432,7 +394,6 @@ export function ChatProvider({ children }: { children: React.ReactNode }) {
|
||||
socket.on("disconnect", () => {
|
||||
setSocketConnected(false);
|
||||
setTypingByThreadId({});
|
||||
subscribedThreadIdsRef.current.clear();
|
||||
});
|
||||
|
||||
socket.on(
|
||||
@ -499,8 +460,6 @@ export function ChatProvider({ children }: { children: React.ReactNode }) {
|
||||
: thread,
|
||||
),
|
||||
);
|
||||
} else if (event.threadId !== activeThreadIdRef.current) {
|
||||
void refreshThreadsRef.current(false);
|
||||
}
|
||||
},
|
||||
);
|
||||
@ -564,7 +523,7 @@ export function ChatProvider({ children }: { children: React.ReactNode }) {
|
||||
},
|
||||
}));
|
||||
|
||||
void refreshThreadsRef.current(false);
|
||||
void refreshThreadsRef.current();
|
||||
},
|
||||
);
|
||||
|
||||
@ -616,14 +575,14 @@ export function ChatProvider({ children }: { children: React.ReactNode }) {
|
||||
socketRef.current = null;
|
||||
}
|
||||
};
|
||||
}, [clearAll, isSignedIn]);
|
||||
}, [clearAll, getToken, isSignedIn]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isSignedIn) {
|
||||
return;
|
||||
}
|
||||
|
||||
void refreshThreadsRef.current(true);
|
||||
void refreshThreadsRef.current();
|
||||
}, [isSignedIn, userRole]);
|
||||
|
||||
useEffect(() => {
|
||||
@ -635,7 +594,7 @@ export function ChatProvider({ children }: { children: React.ReactNode }) {
|
||||
socketRef.current.emit("chat:subscribe", { threadId: activeThreadId });
|
||||
}
|
||||
|
||||
void refreshMessagesRef.current(activeThreadId, true);
|
||||
void refreshMessagesRef.current(activeThreadId);
|
||||
|
||||
setThreads((prev) =>
|
||||
prev.map((thread) =>
|
||||
@ -652,46 +611,6 @@ export function ChatProvider({ children }: { children: React.ReactNode }) {
|
||||
};
|
||||
}, [activeThreadId, socketConnected]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!socketRef.current || !socketConnected) {
|
||||
return;
|
||||
}
|
||||
|
||||
const socket = socketRef.current;
|
||||
const nextIds = new Set(threads.map((thread) => thread.id));
|
||||
|
||||
threads.forEach((thread) => {
|
||||
if (!subscribedThreadIdsRef.current.has(thread.id)) {
|
||||
socket.emit("chat:subscribe", { threadId: thread.id });
|
||||
subscribedThreadIdsRef.current.add(thread.id);
|
||||
}
|
||||
});
|
||||
|
||||
Array.from(subscribedThreadIdsRef.current).forEach((threadId) => {
|
||||
if (!nextIds.has(threadId)) {
|
||||
socket.emit("chat:unsubscribe", { threadId });
|
||||
subscribedThreadIdsRef.current.delete(threadId);
|
||||
}
|
||||
});
|
||||
}, [socketConnected, threads]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isSignedIn || socketConnected) {
|
||||
return;
|
||||
}
|
||||
|
||||
const interval = setInterval(() => {
|
||||
void refreshThreadsRef.current(false);
|
||||
if (activeThreadIdRef.current) {
|
||||
void refreshMessagesRef.current(activeThreadIdRef.current, false);
|
||||
}
|
||||
}, 5000);
|
||||
|
||||
return () => {
|
||||
clearInterval(interval);
|
||||
};
|
||||
}, [isSignedIn, socketConnected]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!activeThreadId) {
|
||||
return;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user