Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import '@bsg/ui-styles'
import { Button } from '@bsg/ui/button'
import { TooltipWrapper } from "@bsg/components/TooltipWrapper";
import { useUserStore } from '@/stores/useUserStore';
Expand Down
45 changes: 40 additions & 5 deletions bsg-frontend/apps/extension/hooks/useChatSocket.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export const useChatSocket = () => {

const socketRef = useRef<WebSocket | null>(null);
const pendingRoomIDRef = useRef<string | null>(null);
const joinedRoomIDRef = useRef<string | null>(null);
const inputRef = useRef<HTMLTextAreaElement | null>(null);
const counterRef = useRef<HTMLDivElement | null>(null);
const containerRef = useRef<HTMLDivElement | null>(null);
Expand Down Expand Up @@ -48,16 +49,22 @@ export const useChatSocket = () => {

//race-condition prevention joinRoom was happening before
//the wb connection
if(userEmail && roomId){
const targetRoomID = pendingRoomIDRef.current || roomId;
if(userEmail && targetRoomID){
if (joinedRoomIDRef.current === targetRoomID) {
return;
}
const payload ={
name:userEmail,
"request-type": "join-room",
data: JSON.stringify({
userHandle: userEmail,
roomID: pendingRoomIDRef.current
roomID: targetRoomID
})
};
ws.send(JSON.stringify(payload));
joinedRoomIDRef.current = targetRoomID;
console.log("Sent join-room on socket open", { roomID: targetRoomID });
}
};

Expand All @@ -79,6 +86,10 @@ export const useChatSocket = () => {
isSystem: false
}]);
} else if (responseType === 'system-announcement') {
// Ignore connection-level join acks to avoid repeated chat noise on reconnects.
if (message?.data === 'Join Room Request') {
return;
}
console.log('recieved system message: ' + message);
setMessages(prev => [...prev, {
userHandle: 'System',
Expand Down Expand Up @@ -129,6 +140,7 @@ export const useChatSocket = () => {

ws.onclose = () => {
setIsConnected(false);
joinedRoomIDRef.current = null;
};

return () => {
Expand All @@ -140,6 +152,12 @@ export const useChatSocket = () => {
// Clear messages when joining a new room so we don't see chat history from previous rooms
setMessages([]);
setLastGameEvent(null);

if (joinedRoomIDRef.current === roomID) {
pendingRoomIDRef.current = roomID;
return;
}

pendingRoomIDRef.current = roomID;

if (socketRef.current && socketRef.current.readyState === WebSocket.OPEN && userEmail) {
Expand All @@ -152,6 +170,7 @@ export const useChatSocket = () => {
})
};
socketRef.current.send(JSON.stringify(payload));
joinedRoomIDRef.current = roomID;
}
}, [userEmail]);

Expand Down Expand Up @@ -241,12 +260,28 @@ export const useChatSocket = () => {

useEffect(() => {
const textArea = inputRef.current;
const container = containerRef.current;
if (!textArea) return;

const resizeOberserver = new ResizeObserver(handleExpand);
let frameID: number | null = null;
const scheduleExpand = () => {
if (frameID !== null) {
cancelAnimationFrame(frameID);
}
frameID = requestAnimationFrame(() => {
frameID = null;
handleExpand();
});
};
const resizeOberserver = new ResizeObserver(scheduleExpand);

resizeOberserver.observe(textArea);
return () => resizeOberserver.disconnect();
resizeOberserver.observe(container || textArea);
return () => {
if (frameID !== null) {
cancelAnimationFrame(frameID);
}
resizeOberserver.disconnect();
};
}, []);

// Derived from messages so will persist between tabs as well
Expand Down
92 changes: 75 additions & 17 deletions bsg-frontend/apps/extension/hooks/useRoomChoice.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,22 @@
import {useState} from "react";
import {useEffect, useState} from "react";
import {Topic} from "@/pages/room-choice-page";
import { SERVER_URL } from '../lib/config';

export const useRoomChoice = (props: { onJoin: any, onCreate: any }) => {
type ProblemTagStat = {
id: number;
tag: string;
totalCount: number;
easyCount: number;
mediumCount: number;
hardCount: number;
}

type RoomActionResult = { success: true } | { success: false; message: string }

export const useRoomChoice = (props: {
onJoin: (roomCode: string) => Promise<RoomActionResult>,
onCreate: (roomCode: string, options: { easy: number; medium: number; hard: number; duration: number; tags: string[] }) => Promise<RoomActionResult>
}) => {
const [joinCode, setJoinCode] = useState('')
const [showCreateOptions, setShowCreateOptions] = useState(false)

Expand All @@ -13,20 +28,41 @@ export const useRoomChoice = (props: { onJoin: any, onCreate: any }) => {
const minNumberOfProblems = 0
const maxNumberOfProblems = 10

const [topics, setTopics] = useState<Topic[]>([
{name: "Arrays", numberOfProblems: 214, isSelected: false},
{name: "Strings", numberOfProblems: 180, isSelected: false},
{name: "Hash Tables", numberOfProblems: 156, isSelected: false},
{name: "Dynamic Programming", numberOfProblems: 203, isSelected: false},
{name: "Trees", numberOfProblems: 175, isSelected: false},
{name: "Graphs", numberOfProblems: 142, isSelected: false},
{name: "Linked Lists", numberOfProblems: 98, isSelected: false},
{name: "Binary Search", numberOfProblems: 87, isSelected: false},
{name: "Two Pointers", numberOfProblems: 125, isSelected: false},
{name: "Sliding Window", numberOfProblems: 76, isSelected: false},
{name: "Backtracking", numberOfProblems: 91, isSelected: false},
{name: "Greedy", numberOfProblems: 134, isSelected: false},
])
const [topics, setTopics] = useState<Topic[]>([])
const [formError, setFormError] = useState<string | null>(null)
const [isSubmittingCreate, setIsSubmittingCreate] = useState(false)
const [isSubmittingJoin, setIsSubmittingJoin] = useState(false)

useEffect(() => {
const loadTopics = async () => {
try {
const response = await fetch(`${SERVER_URL}/problems/tags`, {
credentials: 'include'
});

if (!response.ok) {
throw new Error(`Failed to fetch topics: ${response.status}`);
}

const payload = await response.json();
const stats: ProblemTagStat[] = payload?.data || [];

setTopics(prevTopics => {
const selected = new Set(prevTopics.filter(t => t.isSelected).map(t => t.name));
return stats.map((stat) => ({
name: stat.tag,
numberOfProblems: stat.totalCount,
isSelected: selected.has(stat.tag),
}));
});
} catch (error) {
console.error('Failed to load tag stats', error);
setTopics([]);
}
};

loadTopics();
}, []);

const decrement = (setter: (v: number) => void, val: number) => {
if (total <= 1 || val <= minNumberOfProblems) return
Expand All @@ -41,21 +77,39 @@ export const useRoomChoice = (props: { onJoin: any, onCreate: any }) => {
}

const handleCreateRoom = () => {
setFormError(null)
const selectedTags = topics.filter((topic) => topic.isSelected).map((topic) => topic.name.trim());
const roomSettings = {
easy: numberOfEasyProblems,
medium: numberOfMediumProblems,
hard: numberOfHardProblems,
duration
duration,
tags: selectedTags,
}
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
let code = ''
for (let i = 0; i < 5; i++) code += chars.charAt(Math.floor(Math.random() * chars.length))
setIsSubmittingCreate(true)
props.onCreate(code, roomSettings)
.then((result) => {
if (!result.success) {
setFormError(result.message)
}
})
.finally(() => setIsSubmittingCreate(false))
}

const handleJoinRoom = () => {
setFormError(null)
if (!joinCode.trim()) return
setIsSubmittingJoin(true)
props.onJoin(joinCode.trim())
.then((result) => {
if (!result.success) {
setFormError(result.message)
}
})
.finally(() => setIsSubmittingJoin(false))
}

const toggleTopic = (index: number) => {
Expand Down Expand Up @@ -85,5 +139,9 @@ export const useRoomChoice = (props: { onJoin: any, onCreate: any }) => {
handleJoinRoom,
joinCode,
setJoinCode,
formError,
setFormError,
isSubmittingCreate,
isSubmittingJoin,
}
}
7 changes: 6 additions & 1 deletion bsg-frontend/apps/extension/hooks/useRoomEvents.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,12 @@ export function useRoomEvents() {
}

if (problems.length > 0) {
window.open(`https://leetcode.com/problems/${problems[0]}/`, '_top');
const targetSlug = problems[0];
const currentPath = typeof window !== 'undefined' ? window.location.pathname : "";
const alreadyOnTarget = currentPath.includes(`/problems/${targetSlug}/`);
if (!alreadyOnTarget) {
window.open(`https://leetcode.com/problems/${targetSlug}/`, '_top');
}
}
} else if (lastGameEvent.type === 'next-problem') {
let eventData = lastGameEvent.data;
Expand Down
Loading
Loading