diff --git a/peerprep/frontend/package-lock.json b/peerprep/frontend/package-lock.json index 176b30cabb..d2b78d705e 100644 --- a/peerprep/frontend/package-lock.json +++ b/peerprep/frontend/package-lock.json @@ -12,6 +12,7 @@ "@types/testing-library__react": "^10.2.0", "axios": "^1.7.7", "codemirror": "^5.65.18", + "lucide-react": "^0.456.0", "openai": "^4.71.0", "react": "^18.3.1", "react-chatbotify": "^2.0.0-beta.22", @@ -5533,6 +5534,15 @@ "license": "MIT", "optional": true }, + "node_modules/lucide-react": { + "version": "0.456.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.456.0.tgz", + "integrity": "sha512-DIIGJqTT5X05sbAsQ+OhA8OtJYyD4NsEMCA/HQW/Y6ToPQ7gwbtujIoeAaup4HpHzV35SQOarKAWH8LYglB6eA==", + "license": "ISC", + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0-rc" + } + }, "node_modules/lz-string": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz", diff --git a/peerprep/frontend/package.json b/peerprep/frontend/package.json index ef047fd9dc..52fe89e7e6 100644 --- a/peerprep/frontend/package.json +++ b/peerprep/frontend/package.json @@ -15,6 +15,7 @@ "@types/testing-library__react": "^10.2.0", "axios": "^1.7.7", "codemirror": "^5.65.18", + "lucide-react": "^0.456.0", "openai": "^4.71.0", "react": "^18.3.1", "react-chatbotify": "^2.0.0-beta.22", diff --git a/peerprep/frontend/src/App.tsx b/peerprep/frontend/src/App.tsx index 1d55b1c669..462fd5581a 100644 --- a/peerprep/frontend/src/App.tsx +++ b/peerprep/frontend/src/App.tsx @@ -28,7 +28,6 @@ import SignUp from './views/UserServiceViews/SignUp'; // Import the new SignUp c import QuestionService from './views/QuestionServiceViews/QuestionManagement'; import MatchingService from './views/MatchingServiceViews/MatchingServiceMainView'; import SessionStubView from './views/MatchingServiceViews/SessionStubView'; -import CollaborationServiceView from './views/CollabServiceViews/CollabServiceMainView'; import CollaborationServiceIntegratedView from './views/CollabServiceViews/CollabServiceIntegratedView'; const App: React.FC = () => { @@ -47,9 +46,6 @@ const App: React.FC = () => { {/* Public Route for Session View (Stub for now) */} } /> - {/* Public Route for Collaboration Service View without sockets*/} - } /> - {/* Public Route for Collaboration Service Integrated View */} } /> diff --git a/peerprep/frontend/src/styles/App.css b/peerprep/frontend/src/styles/App.css index 7e5dda6b37..194fa0fb89 100644 --- a/peerprep/frontend/src/styles/App.css +++ b/peerprep/frontend/src/styles/App.css @@ -1,7 +1,5 @@ #root { margin: 0 auto; - padding: 5rem; - text-align: center; background-color: white; font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; line-height: 1.5; @@ -87,6 +85,7 @@ h2 { display: flex; justify-content: space-between; gap: 20px; + margin-top: 5rem; } .login-container { @@ -112,6 +111,7 @@ h2 { /* Right panel for detailed view */ .right-panel { + margin-right: 20px; flex: 1; max-width: 35%; padding: 20px; @@ -557,136 +557,6 @@ h2 { transition: width 1s ease-in-out; } -.status-message { - margin-top: 10px; - font-size: 16px; - color: #333; - font-weight: bold; -} - -/* Ensure the container takes up the full page */ -.editor-container-parent { - display: flex; - flex-direction: column; - justify-content: space-between; /* Adjust layout */ - align-items: center; - - height: 85vh; /* Full height of the viewport */ - width: 80vw; /* Full width of the viewport */ - - overflow-y: scroll; - overflow-x: hidden; - background-color: white; - padding: 20px; - box-sizing: border-box; /* Include padding in width and height calculations */ - scrollbar-width: none; -} - -/* Hide scrollbar for Webkit browsers (Chrome, Safari) */ -.editor-container-parent::-webkit-scrollbar { - display: none; -} - -.CodeMirror { - min-height: 1000px; - width: 100%; -} - -.code-and-chat { - display: flex; - width: 100%; - height: 100%; - max-width: 900px; - max-height: 200px; - min-height: 200px; - flex-grow: 1; - position: relative; -} - -.editor-container { - width: 70%; - height: 100%; - max-width: 900px; - max-height: 200px; - min-height: 200px; - flex-grow: 1; - border: 2px solid #ccc; - border-radius: 10px; - box-shadow: 0px 4px 8px rgba(0, 0, 0, 0.1); - background-color: white; - color: white; - - /* Add these properties */ - overflow-y: auto; - text-align: left; /* Ensure text aligns to the left */ - vertical-align: top; /* Ensures text starts from the top */ -} - -/* Hide scrollbar for Webkit browsers (Chrome, Safari) */ -.editor-container::-webkit-scrollbar { - display: none; -} - -/* Hide scrollbar for Firefox */ -.editor-container { - scrollbar-width: none; /* Firefox */ -} - -.chat-box { - width: 30%; - display: flex; - flex-direction: column; - height: 100%; - max-height: 200px; - min-height: 100px; - border: 2px solid #ccc; - border-radius: 10px; - box-shadow: 0px 4px 8px rgba(0, 0, 0, 0.1); - background-color: white; - color: black; /* Changed to black for better readability */ -} - -.chat-heading{ - margin: 1px; -} - -.messages { - font-size: small; - flex-grow: 1; - overflow-y: auto; - padding: 5px; -} - -.message { - margin-bottom: 5px; - padding: 3px; - padding-left: 5px; - border-radius: 5px; - max-width: 60%; - overflow-x: scroll; - display: flex; -} - -.sent { - background-color: #dcf8c6; - align-self: flex-end; - margin-left: auto; -} - -.received { - background-color: #f1f0f0; - align-self: flex-start; - margin-right: auto; -} - -.input-container { - display: flex; - background-color: white; - padding-left: 2px; - padding-right: 2px; - padding-bottom: 2px; -} - input { flex-grow: 1; padding: 5px; @@ -698,96 +568,6 @@ input { min-width: 0px; } -.sendBtn { - padding: 5px 10px; - border: none; - background-color: #007bff; - color: white; - border-radius: 5px; - cursor: pointer; - font-size: small; -} - -.sendBtn:hover { - background-color: #0056b3; -} - -/* Header section styling */ -.editor-header { - text-align: center; - margin-bottom: -10px; - margin-top: -45px; -} - -.leave-btn, -.run-btn { - margin-top: 10px; /* Additional spacing if needed for each button */ -} - -#language-select { - margin-bottom: 20px; -} - -/* Leave session button */ -.leave-btn { - background-color: #ff4d4d; - color: white; - border: none; - border-radius: 6px; - padding: 10px 20px; - cursor: pointer; - margin-top: 10px; -} - -.leave-btn:hover { - background-color: #ff1a1a; -} - -/* Styles for the collaborative cursor to look like a blinking bar */ -.CodeMirror-cursor { - position: absolute; - width: 2px; - background-color: blue; /* Adjust the color per user */ - opacity: 0.8; - animation: blink 1s step-end infinite; /* Blinking effect */ -} - -.CodeMirror-cursor { - border-left: 1px solid white; - border-right: 1px solid black; -} - -/* Styles for the label (optional) */ -.CodeMirror-cursor-label { - position: absolute; - background-color: orange; - color: white; - padding: 2px 5px; - border-radius: 3px; - font-size: 12px; - top: -20px; -} - -/* Blinking animation */ -@keyframes blink { - from, to { - opacity: 0; - } - 50% { - opacity: 1; - } -} - -.editor-header2 { - text-align: center; - margin-bottom: 20px; - margin-top: 10px; - display: flex; - flex-direction: row; /* Arrange items in a vertical stack */ - align-items: center; /* Align items to the left */ - gap: 60px; /* Add vertical spacing between items */ -} - .matching-form2 { padding: 0px; max-width: 40%; @@ -865,47 +645,18 @@ input { text-align: center; } -.output-container{ - overflow-y: scroll; - min-height: 50px; - max-height: 100px; - max-width: 900px; -} - -/* Hide scrollbar for Webkit browsers (Chrome, Safari) */ -.output-container::-webkit-scrollbar { - display: none; -} -/* Hide scrollbar for Firefox */ -.output-container { - scrollbar-width: none; /* Firefox */ -} - -.testcases-table { - margin: 20px auto; /* Center the table horizontally */ - max-width: 900px; /* Set max width */ - width: 100%; /* Allow the table to expand to max-width */ -} - -.testcases-table table { - border-collapse: collapse; - border: 2px solid #333 !important; /* Outer border around the table */ - text-align: center; - width: 100%; /* Ensure the table fills the container width */ -} - - -.testcases-table th, .testcases-table td { - border: 1px solid #333 !important; /* Border around each cell */ - padding: 8px; +/* STYLES FOR CODEMIRROR */ +.react-codemirror2 { + height: 100%; + width: 100%; } -.testcases-table th { - background-color: #f2f2f2; - font-weight: bold; +.CodeMirror{ + height: 100%; + width: 100%; } -.testcases-table td { - background-color: #fff; -} +.CodeMirror-cursor{ + height: 19px !important; +} \ No newline at end of file diff --git a/peerprep/frontend/src/views/CollabServiceViews/CollabServiceIntegratedView.tsx b/peerprep/frontend/src/views/CollabServiceViews/CollabServiceIntegratedView.tsx index b61129ff0e..02c390bc36 100644 --- a/peerprep/frontend/src/views/CollabServiceViews/CollabServiceIntegratedView.tsx +++ b/peerprep/frontend/src/views/CollabServiceViews/CollabServiceIntegratedView.tsx @@ -4,6 +4,7 @@ import { useParams, useNavigate } from 'react-router-dom'; import axios from 'axios'; import * as Y from 'yjs'; import Chat from './components/Chat.tsx'; +import { GripVertical, GripHorizontal, Code2 } from 'lucide-react'; import 'codemirror/lib/codemirror.css'; import 'codemirror/theme/material.css'; @@ -22,7 +23,7 @@ import { assessCode } from '../../api/assesscodeApi.ts'; import { CodemirrorBinding } from 'y-codemirror'; import { WebsocketProvider } from 'y-websocket'; -import { deleteMatchedSession} from "../../api/matchingApi.ts"; +import { deleteMatchedSession } from "../../api/matchingApi.ts"; import { getQuestionById } from '../../api/questionApi.ts'; const CollaborationServiceIntegratedView: React.FC = () => { @@ -34,14 +35,6 @@ const CollaborationServiceIntegratedView: React.FC = () => { const editorRef = useRef(null); const navigate = useNavigate(); const [yText, setYText] = useState(null); - - // const [commentoutput, setCommentOutput] = useState(null); - // console.log(commentoutput); - - //let topic = 'topic'; - //let difficulty = 'difficulty'; - // Declare question object - //extract questionID from session id (eg. 670d81daf90653ef4b9162b8-67094dcc6be97361a2e7cb1a-1730832550120-Q672890c43266d81a769bfaee) const [input1, setInput1] = useState('N/A'); const [output1, setOutput1] = useState('N/A'); const [input2, setInput2] = useState('N/A'); @@ -53,6 +46,82 @@ const CollaborationServiceIntegratedView: React.FC = () => { console.log("session id is " + sessionId); const questionId = sessionId ? sessionId.split('-Q')[1] : "N/A"; + const [leftPaneWidth, setLeftPaneWidth] = useState(35); + const [codeEditorHeight, setCodeEditorHeight] = useState(90); + const [chatHeight, setChatHeight] = useState(70); + + + interface ResizeEvent extends MouseEvent { + clientX: number; + } + interface VerticalResizeEvent extends MouseEvent { + clientY: number; + } + + interface ChatResizeEvent extends MouseEvent { + clientY: number; + } + interface ContainerRect { + left: number; + width: number; + top: number; + height: number; + } + + const handleHorizontalResize = (e: React.MouseEvent): void => { + const container = e.currentTarget.parentElement as HTMLDivElement; + const containerRect: ContainerRect = container.getBoundingClientRect(); + + const handleResize = (moveEvent: ResizeEvent): void => { + const newWidth = ((moveEvent.clientX - containerRect.left) / containerRect.width) * 100; + setLeftPaneWidth(Math.min(Math.max(20, newWidth), 80)); // Limit between 20% and 80% + }; + + const removeListeners = (): void => { + document.removeEventListener('mousemove', handleResize); + document.removeEventListener('mouseup', removeListeners); + }; + + document.addEventListener('mousemove', handleResize); + document.addEventListener('mouseup', removeListeners); + }; + + const handleVerticalResize = (e: React.MouseEvent): void => { + const container = e.currentTarget.parentElement as HTMLDivElement; + const containerRect: ContainerRect = container.getBoundingClientRect(); + + const handleResize = (moveEvent: VerticalResizeEvent): void => { + const newHeight = ((moveEvent.clientY - containerRect.top) / containerRect.height) * 100; + setCodeEditorHeight(Math.min(Math.max(30, newHeight), 90)); // Limit between 30% and 90% + }; + + const removeListeners = (): void => { + document.removeEventListener('mousemove', handleResize); + document.removeEventListener('mouseup', removeListeners); + }; + + document.addEventListener('mousemove', handleResize); + document.addEventListener('mouseup', removeListeners); + }; + + const handleChatResize = (e: React.MouseEvent): void => { + const container = e.currentTarget.parentElement as HTMLDivElement; + const containerRect: ContainerRect = container.getBoundingClientRect(); + + const handleResize = (moveEvent: ChatResizeEvent): void => { + const newHeight = ((moveEvent.clientY - containerRect.top) / containerRect.height) * 100; + setChatHeight(Math.min(Math.max(40, newHeight), 90)); + }; + + const removeListeners = (): void => { + document.removeEventListener('mousemove', handleResize); + document.removeEventListener('mouseup', removeListeners); + }; + + document.addEventListener('mousemove', handleResize); + document.addEventListener('mouseup', removeListeners); + }; + //set topic, difficulty, questionId by calling the API useEffect(() => { const fetchData = async () => { @@ -85,13 +154,6 @@ const CollaborationServiceIntegratedView: React.FC = () => { console.log(`Session ID: ${sessionId}, Topics: ${topics}, Difficulty: ${difficulty}`); }, [sessionId, topics, difficulty, questionId]); - useEffect(() => { - document.body.style.overflow = 'hidden'; - return () => { - document.body.style.overflow = 'auto'; - }; - }, []); - useEffect(() => { const ydoc = new Y.Doc(); const provider = new WebsocketProvider('ws://localhost:1234/' + sessionId, 'collaborative-doc', ydoc); @@ -126,14 +188,14 @@ const CollaborationServiceIntegratedView: React.FC = () => { setSyntaxFullLang(e.target.textContent!); setSyntaxLang( e.target.value === '63' ? 'javascript' - : e.target.value === '54' ? 'text/x-c++src' - : e.target.value === '50' ? 'text/x-csrc' - : e.target.value === '71' ? 'python' - : e.target.value === '62' ? 'text/x-java' - : e.target.value === '72' ? 'ruby' - : e.target.value === '51' ? 'text/x-csharp' // New language - : e.target.value === '78' ? 'text/x-kotlin' // New language - : 'javascript' + : e.target.value === '54' ? 'text/x-c++src' + : e.target.value === '50' ? 'text/x-csrc' + : e.target.value === '71' ? 'python' + : e.target.value === '62' ? 'text/x-java' + : e.target.value === '72' ? 'ruby' + : e.target.value === '51' ? 'text/x-csharp' // New language + : e.target.value === '78' ? 'text/x-kotlin' // New language + : 'javascript' ); }; @@ -145,6 +207,8 @@ const CollaborationServiceIntegratedView: React.FC = () => { return; } + setOutput('Executing code...'); + const currentCode = yText.toString(); console.log('Submitting code for execution:', currentCode); @@ -197,6 +261,7 @@ const CollaborationServiceIntegratedView: React.FC = () => { console.error('Error executing code:', error); setOutput('Error executing code'); } + setCodeEditorHeight(50); }; const handleAssessCode = async () => { @@ -221,164 +286,185 @@ const CollaborationServiceIntegratedView: React.FC = () => { console.error('Error executing OpenAI API call:', error); setOutput('Error executing code'); } + setCodeEditorHeight(50); }; return ( -
-
-

Collaboration Session

-

Topics: {topics} | Difficulty: {difficulty} | Question: {questionTitle}

-

Description: {questionDescription}

-
- -
- - -
-
- +
+
+
+

Q. {questionTitle}

+ +
+ {topics && ( + + {topics} + + )} + {difficulty && ( + + {difficulty} + + )}
-
- - -
-
-
- { - editor.on('keyup', (cm: any, event: any) => { - // Only trigger autocomplete on specific characters - const triggerKeys = /^[a-zA-Z0-9_]$/; // Allow letters, numbers, and underscore - if ( - triggerKeys.test(event.key) && - !cm.state.completionActive // Ensure that completion is not already active - ) { - cm.showHint({ completeSingle: false }); - } - }); - }} - onChange={() => { - // Let Yjs handle all updates; do not use setCode here - }} - /> +

{questionDescription}

+ +
+

Test Cases

+ +
+

Test Case 1:

+

+ Input: {input1} +

+

+ Output: {output1} +

+
+ +
+

Test Case 2:

+

+ Input: {input2} +

+

+ Output: {output2} +

+
+
- {sessionId && } + {/* Chat section fixed at the bottom */} + {sessionId && (<> +
+ +
+
+ +
+ + )}
-

Output

-
-
{output}
+
+
- {/*
-
{commentoutput}
-
*/} - -
-

Test Cases

- - - - - - - - - - - - - - - - - -
Input 1Output 1Input 2Output 2
{input1}{output1}{input2}{output2}
-
-
- ); -}; -export default CollaborationServiceIntegratedView; - -/** Sample code snippets in different languages for testing */ -/** TO BE USED FOR INTERNAL TESTING WHERE REQUIRED */ -/* -KOTLIN -fun greet(name: String) { - println("Hello, $name!") -} - -fun main() { - greet("World") -} +
+
+
+
+ +

Code

+
+ +
+
+
+ +
+ ⌄ +
+
+ + +
+ + +
-C# -using System; +
-class Program { - static void Greet(string name) { - Console.WriteLine("Hello, " + name + "!"); - } +
+ { + editor.on('keyup', (cm: any, event: any) => { + // Only trigger autocomplete on specific characters + const triggerKeys = /^[a-zA-Z0-9_]$/; // Allow letters, numbers, and underscore + if ( + triggerKeys.test(event.key) && + !cm.state.completionActive // Ensure that completion is not already active + ) { + cm.showHint({ completeSingle: false }); + } + }); + }} + onChange={() => { + // Let Yjs handle all updates; do not use setCode here + }} + /> +
+
- static void Main() { - Greet("World"); - } -} +
+ +
-RUBY -def greet(name) - puts "Hello, #{name}!" -end +
+
+

Output

+
{output}
+
+
+
+
+ ); +}; -greet("World") -*/ +export default CollaborationServiceIntegratedView; \ No newline at end of file diff --git a/peerprep/frontend/src/views/CollabServiceViews/CollabServiceMainView.tsx b/peerprep/frontend/src/views/CollabServiceViews/CollabServiceMainView.tsx deleted file mode 100644 index e25177c102..0000000000 --- a/peerprep/frontend/src/views/CollabServiceViews/CollabServiceMainView.tsx +++ /dev/null @@ -1,70 +0,0 @@ -import React, { useState, useEffect } from 'react'; -import { Controlled as CodeMirror } from 'react-codemirror2'; // CodeMirror component -import { useParams, useNavigate } from 'react-router-dom'; // For routing and session handling -import 'codemirror/lib/codemirror.css'; // Default CodeMirror styles -import 'codemirror/theme/material.css'; // CodeMirror theme -import 'codemirror/mode/javascript/javascript'; // Support for JavaScript mode - -interface CollaborationServiceViewProps { - topic: string; - difficulty: string; - sessionId: string; -} - -const CollaborationServiceView: React.FC = ({ topic, difficulty, sessionId }) => { - const [code, setCode] = useState('// Start coding here...\n'); - const [users, setUsers] = useState([]); // Placeholder for active users - const navigate = useNavigate(); // For navigation - - useEffect(() => { - // Assume we later use WebSocket or Socket.io to sync code - console.log(`Session ID: ${sessionId}, Topic: ${topic}, Difficulty: ${difficulty}`); - }, [sessionId, topic, difficulty]); - - - useEffect(() => { - // Lock scroll when this component mounts - document.body.style.overflow = 'hidden'; - - // Cleanup function to unlock scroll when component unmounts - return () => { - document.body.style.overflow = 'auto'; - }; - }, []); - - const handleCodeChange = (editor: any, data: any, value: string) => { - setCode(value); // Update local code state - // TODO: Later sync this code change with the backend using WebSocket/Socket.io - }; - - const handleLeaveSession = () => { - navigate('/matching'); // Navigate back to home on session exit - }; - - return ( -
-
-

Collaboration Session

-

Topic: {topic} | Difficulty: {difficulty}

-

Session ID: {sessionId}

- -
- -
- -
-
- ); -}; - -export default CollaborationServiceView; \ No newline at end of file diff --git a/peerprep/frontend/src/views/CollabServiceViews/components/Chat.tsx b/peerprep/frontend/src/views/CollabServiceViews/components/Chat.tsx index 28452bd9ba..6d63cf11d2 100644 --- a/peerprep/frontend/src/views/CollabServiceViews/components/Chat.tsx +++ b/peerprep/frontend/src/views/CollabServiceViews/components/Chat.tsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect } from 'react'; +import React, { useState, useEffect, useRef } from 'react'; import io from 'socket.io-client'; const socket = io('http://localhost:1234', { @@ -15,6 +15,7 @@ const Chat: React.FC = ({ sessionId }) => { const [message, setMessage] = useState(''); const [messages, setMessages] = useState<{ text: string; sender: boolean }[]>([]); const [connectionError, setConnectionError] = useState(null); + const messagesEndRef = useRef(null); const joinRoom = () => { if (room !== '') { @@ -24,12 +25,12 @@ const Chat: React.FC = ({ sessionId }) => { const sendMessage = () => { if (!message) return; // Checking if there's a message to send - + const messageData = { message, room, senderId: socket.id }; - + setMessages((prevMessages) => [...prevMessages, { text: message, sender: true }]); setMessage(''); - + socket.emit('send_message', messageData, (ackError: string | null) => { if (ackError) { setConnectionError("Failed to send message. Please try again."); @@ -37,7 +38,13 @@ const Chat: React.FC = ({ sessionId }) => { } }); }; - + + const handleKeyDown = (e: React.KeyboardEvent) => { + if (e.key === 'Enter') { + e.preventDefault(); + sendMessage(); + } + }; useEffect(() => { joinRoom(); // Automatically join the room based on sessionId @@ -68,28 +75,57 @@ const Chat: React.FC = ({ sessionId }) => { }; }, [room]); + useEffect(() => { + if (messagesEndRef.current) { + messagesEndRef.current.scrollIntoView({ behavior: 'smooth' }); + } + }, [messages]); + return ( -
-

Chat

- {connectionError &&
{connectionError}
} -
- {messages.map((msg, index) => ( -
+
+

Chat

+ + {connectionError && ( +
{connectionError}
+ )} + +
+ {messages.map((msg, index) => (<> +
{msg.text}
+
+ ))}
-
+ +
{ - setMessage(event.target.value); - }} + onChange={(e) => setMessage(e.target.value)} + onKeyDown={handleKeyDown} + className="flex-grow border border-gray-300 text-sm rounded-md bg-white text-black focus:outline-none focus:border-green-500 min-w-0 px-1 py-px" /> - +
+ ); }; diff --git a/peerprep/frontend/yarn.lock b/peerprep/frontend/yarn.lock index f34c413100..5546981db3 100644 --- a/peerprep/frontend/yarn.lock +++ b/peerprep/frontend/yarn.lock @@ -2904,6 +2904,11 @@ ltgt@^2.1.2: resolved "https://registry.npmjs.org/ltgt/-/ltgt-2.2.1.tgz" integrity sha512-AI2r85+4MquTw9ZYqabu4nMwy9Oftlfa/e/52t9IjtfG+mGBbTNdAoZ3RQKLHR6r0wQnwZnPIEh/Ya6XTWAKNA== +lucide-react@^0.456.0: + version "0.456.0" + resolved "https://registry.npmjs.org/lucide-react/-/lucide-react-0.456.0.tgz" + integrity sha512-DIIGJqTT5X05sbAsQ+OhA8OtJYyD4NsEMCA/HQW/Y6ToPQ7gwbtujIoeAaup4HpHzV35SQOarKAWH8LYglB6eA== + lz-string@^1.5.0: version "1.5.0" resolved "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz" @@ -3383,7 +3388,7 @@ react-router@6.26.2: dependencies: "@remix-run/router" "1.19.2" -react@^18.0.0, react@^18.3.1, "react@>=15.5 <=18.x", "react@>=16.14.0 <20.0.0 || ^19.0.0-0", react@>=16.8: +"react@^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0-rc", react@^18.0.0, react@^18.3.1, "react@>=15.5 <=18.x", "react@>=16.14.0 <20.0.0 || ^19.0.0-0", react@>=16.8: version "18.3.1" resolved "https://registry.npmjs.org/react/-/react-18.3.1.tgz" integrity sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==