Skip to content

Commit 0a4cebb

Browse files
committed
Merge branch 'collaboration-service-frontend' into integrate-execution-collaboration
2 parents 9e445d0 + 0374254 commit 0a4cebb

File tree

16 files changed

+1386
-3
lines changed

16 files changed

+1386
-3
lines changed

frontend/package.json

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,16 @@
1616
"@emotion/react": "^11.14.0",
1717
"@emotion/style": "^0.8.0",
1818
"@emotion/styled": "^11.14.1",
19+
"@monaco-editor/react": "^4.7.0",
1920
"@mui/icons-material": "^7.3.4",
2021
"@mui/material": "^7.3.4",
2122
"@radix-ui/react-alert-dialog": "^1.1.15",
2223
"@radix-ui/react-dialog": "^1.1.15",
2324
"@radix-ui/react-label": "^2.1.7",
2425
"@radix-ui/react-navigation-menu": "^1.2.14",
26+
"@radix-ui/react-scroll-area": "^1.2.10",
27+
"@radix-ui/react-select": "^2.2.6",
28+
"@radix-ui/react-separator": "^1.1.7",
2529
"@radix-ui/react-slot": "^1.2.3",
2630
"@radix-ui/react-tabs": "^1.1.13",
2731
"@tanstack/react-query": "^5.90.2",
@@ -31,15 +35,21 @@
3135
"embla-carousel-react": "^8.6.0",
3236
"input-otp": "^1.4.2",
3337
"lucide-react": "^0.544.0",
38+
"monaco-editor": "^0.54.0",
39+
"motion": "^12.23.22",
3440
"next": "15.5.4",
3541
"node-domexception": "^2.0.2",
3642
"react": "19.1.0",
3743
"react-dom": "19.1.0",
3844
"react-hook-form": "^7.65.0",
39-
"tailwind-merge": "^3.3.1",
4045
"react-icons": "^5.5.0",
46+
"react-resizable-panels": "^3.0.6",
4147
"react-toastify": "^11.0.5",
4248
"socket.io-client": "^4.8.1",
49+
"tailwind-merge": "^3.3.1",
50+
"y-monaco": "^0.1.6",
51+
"y-websocket": "^3.0.0",
52+
"yjs": "^13.6.27",
4353
"zustand": "^5.0.8"
4454
},
4555
"devDependencies": {

frontend/src/app/globals.css

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,3 +121,16 @@
121121
@apply bg-background text-foreground;
122122
}
123123
}
124+
125+
/* Yjs Awareness Cursor Styles */
126+
.yRemoteSelection {
127+
background-color: var(--selection-color, oklch(0.7 0.2 150 / 0.3));
128+
}
129+
130+
.yRemoteSelectionHead {
131+
position: absolute;
132+
border-left: 2px solid var(--cursor-color, oklch(0.7 0.2 150 / 0.3));
133+
border-top: 2px solid var(--cursor-color, oklch(0.7 0.2 150 / 0.3));
134+
border-bottom: 2px solid var(--cursor-color, oklch(0.7 0.2 150 / 0.3));
135+
height: 100%;
136+
}
Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
"use client";
2+
3+
import { useEffect } from "react";
4+
import { useRouter, useParams } from "next/navigation";
5+
import dynamic from "next/dynamic";
6+
import { ResizableHandle, ResizablePanel, ResizablePanelGroup } from "@/components/ui/resizable";
7+
import Header from "@/components/ui/header";
8+
import QuestionPanel from "@/components/practice/question-panel";
9+
import CommunicationPanel from "@/components/practice/communication-panel";
10+
import CodeOutputPanel from "@/components/practice/code-output-panel";
11+
import { Button } from "@/components/ui/button";
12+
import { Spinner } from "@/components/ui/spinner";
13+
import { useCollaborationState, useCollaborationActions } from "@/stores/collaboration-store";
14+
15+
const CodeEditorPanel = dynamic(() => import("@/components/practice/code-editor-panel"), {
16+
ssr: false,
17+
loading: () => <div className="h-full flex items-center justify-center">Loading editor...</div>,
18+
});
19+
20+
export default function PracticePage() {
21+
const router = useRouter();
22+
const params = useParams();
23+
const roomId = params?.id as string;
24+
25+
const { roomDetails, isLoading, error } = useCollaborationState();
26+
const { fetchRoomDetails, reset } = useCollaborationActions();
27+
28+
// Fetch room details on mount
29+
useEffect(() => {
30+
if (roomId) {
31+
fetchRoomDetails(roomId).catch((err) => {
32+
console.error("Failed to fetch room details:", err);
33+
});
34+
}
35+
36+
// Cleanup on unmount
37+
return () => {
38+
reset();
39+
};
40+
}, [fetchRoomDetails, reset, roomId]);
41+
42+
// Handle leave session
43+
const handleLeaveSession = () => {
44+
// Simply redirect to home page
45+
router.push("/");
46+
};
47+
48+
// Loading state
49+
if (isLoading) {
50+
return (
51+
<div className="h-screen w-full flex items-center justify-center">
52+
<div className="flex flex-col items-center gap-4">
53+
<Spinner />
54+
<p className="text-muted-foreground">Loading collaboration room...</p>
55+
</div>
56+
</div>
57+
);
58+
}
59+
60+
// Error state
61+
if (error || !roomDetails) {
62+
return (
63+
<div className="h-screen w-full flex items-center justify-center">
64+
<div className="flex flex-col items-center gap-4 text-center">
65+
<p className="text-destructive font-semibold">Failed to load room</p>
66+
<p className="text-muted-foreground">{error || "Room not found"}</p>
67+
<Button onClick={() => router.push("/")}>Go Back Home</Button>
68+
</div>
69+
</div>
70+
);
71+
}
72+
73+
// Read-only mode (room is closed)
74+
const isReadOnly = !roomDetails.isActive;
75+
76+
return (
77+
<div className="h-screen w-full flex flex-col">
78+
<Header>
79+
<div className="flex items-center gap-4">
80+
<Button variant={"destructive"} onClick={handleLeaveSession}>
81+
Leave Room
82+
</Button>
83+
</div>
84+
</Header>
85+
86+
<div className="flex-1">
87+
{isReadOnly ? (
88+
// Read-only layout: Only question and read-only code editor
89+
<ResizablePanelGroup direction="horizontal" className="h-full w-full">
90+
{/* Left Panel */}
91+
<ResizablePanel>
92+
<div className="h-full overflow-y-auto">
93+
<QuestionPanel />
94+
</div>
95+
</ResizablePanel>
96+
97+
<ResizableHandle className="bg-gray-400 hover:bg-gray-600 w-1 cursor-col-resize" />
98+
99+
{/* Right Panel */}
100+
<ResizablePanel>
101+
<div className="h-full overflow-y-auto">
102+
<CodeEditorPanel readOnly={true} />
103+
</div>
104+
</ResizablePanel>
105+
</ResizablePanelGroup>
106+
) : (
107+
// Full layout with all panels
108+
<ResizablePanelGroup direction="horizontal" className="h-full w-full">
109+
{/* Left Panel */}
110+
<ResizablePanel>
111+
<ResizablePanelGroup direction="vertical">
112+
<ResizablePanel>
113+
<div className="h-full overflow-y-auto">
114+
<QuestionPanel />
115+
</div>
116+
</ResizablePanel>
117+
<ResizableHandle className="bg-gray-400 hover:bg-gray-600 w-1 cursor-col-resize" />
118+
<ResizablePanel>
119+
<div className="h-full overflow-y-auto">
120+
<CommunicationPanel />
121+
</div>
122+
</ResizablePanel>
123+
</ResizablePanelGroup>
124+
</ResizablePanel>
125+
126+
<ResizableHandle className="bg-gray-400 hover:bg-gray-600 w-1 cursor-col-resize" />
127+
128+
{/* Right Panel */}
129+
<ResizablePanel>
130+
<ResizablePanelGroup direction="vertical">
131+
<ResizablePanel>
132+
<div className="h-full overflow-y-auto">
133+
<CodeEditorPanel readOnly={false} />
134+
</div>
135+
</ResizablePanel>
136+
<ResizableHandle className="bg-gray-400 hover:bg-gray-600 w-1 cursor-col-resize" />
137+
<ResizablePanel>
138+
<div className="h-full overflow-y-auto">
139+
<CodeOutputPanel />
140+
</div>
141+
</ResizablePanel>
142+
</ResizablePanelGroup>
143+
</ResizablePanel>
144+
</ResizablePanelGroup>
145+
)}
146+
</div>
147+
</div>
148+
);
149+
}

0 commit comments

Comments
 (0)