@@ -77,7 +110,9 @@ export default function CodeEditor() {
defaultValue="Start typing your code here..."
onMount={handleEditorDidMount}
value={code}
- onChange={(value) => setCode(value || "")}
+ onChange={(val) =>
+ activeFileId && updateContent(activeFileId, val || "")
+ }
options={{
minimap: { enabled: false },
padding: { bottom: 4, top: 6 },
diff --git a/remote-ide/package-lock.json b/remote-ide/package-lock.json
index def7dc8..d9249c8 100644
--- a/remote-ide/package-lock.json
+++ b/remote-ide/package-lock.json
@@ -39,8 +39,10 @@
"tailwind-merge": "^3.0.1",
"tailwindcss-animate": "^1.0.7",
"use-sound": "^5.0.0",
+ "uuid": "^13.0.0",
"vscode-icons-js": "^11.6.1",
- "zod": "^3.24.2"
+ "zod": "^3.24.2",
+ "zustand": "^5.0.9"
},
"devDependencies": {
"@eslint/eslintrc": "^3",
@@ -9517,6 +9519,19 @@
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
"license": "MIT"
},
+ "node_modules/uuid": {
+ "version": "13.0.0",
+ "resolved": "https://registry.npmjs.org/uuid/-/uuid-13.0.0.tgz",
+ "integrity": "sha512-XQegIaBTVUjSHliKqcnFqYypAd4S+WCYt5NIeRs6w/UAry7z8Y9j5ZwRRL4kzq9U3sD6v+85er9FvkEaBpji2w==",
+ "funding": [
+ "https://github.com/sponsors/broofa",
+ "https://github.com/sponsors/ctavan"
+ ],
+ "license": "MIT",
+ "bin": {
+ "uuid": "dist-node/bin/uuid"
+ }
+ },
"node_modules/vest-utils": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/vest-utils/-/vest-utils-1.3.3.tgz",
@@ -9817,6 +9832,35 @@
"url": "https://github.com/sponsors/colinhacks"
}
},
+ "node_modules/zustand": {
+ "version": "5.0.9",
+ "resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.9.tgz",
+ "integrity": "sha512-ALBtUj0AfjJt3uNRQoL1tL2tMvj6Gp/6e39dnfT6uzpelGru8v1tPOGBzayOWbPJvujM8JojDk3E1LxeFisBNg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.20.0"
+ },
+ "peerDependencies": {
+ "@types/react": ">=18.0.0",
+ "immer": ">=9.0.6",
+ "react": ">=18.0.0",
+ "use-sync-external-store": ">=1.2.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "immer": {
+ "optional": true
+ },
+ "react": {
+ "optional": true
+ },
+ "use-sync-external-store": {
+ "optional": true
+ }
+ }
+ },
"node_modules/zwitch": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz",
diff --git a/remote-ide/package.json b/remote-ide/package.json
index 96d4816..3afea43 100644
--- a/remote-ide/package.json
+++ b/remote-ide/package.json
@@ -40,8 +40,10 @@
"tailwind-merge": "^3.0.1",
"tailwindcss-animate": "^1.0.7",
"use-sound": "^5.0.0",
+ "uuid": "^13.0.0",
"vscode-icons-js": "^11.6.1",
- "zod": "^3.24.2"
+ "zod": "^3.24.2",
+ "zustand": "^5.0.9"
},
"devDependencies": {
"@eslint/eslintrc": "^3",
From 8dee411d8d9c7d9735dfc082e96f9a222d9a868f Mon Sep 17 00:00:00 2001
From: Avan14 <145996250+Avan14@users.noreply.github.com>
Date: Sat, 13 Dec 2025 22:46:23 +0530
Subject: [PATCH 02/11] Enhance FileExplorer components with improved Zustand
integration for better state management. Refined file and folder interactions
in FileCom and FolderCom, and ensured CodeEditor accurately reflects active
file content and language. Updated dependencies accordingly.
---
remote-ide/hooks/useEditorStore.tsx | 17 +++++++++++++++++
1 file changed, 17 insertions(+)
create mode 100644 remote-ide/hooks/useEditorStore.tsx
diff --git a/remote-ide/hooks/useEditorStore.tsx b/remote-ide/hooks/useEditorStore.tsx
new file mode 100644
index 0000000..f868ebc
--- /dev/null
+++ b/remote-ide/hooks/useEditorStore.tsx
@@ -0,0 +1,17 @@
+import { create } from "zustand";
+
+type EditorStore = {
+ contents: Record
;
+ updateContent: (fileId: string, code: string) => void;
+};
+
+export const useEditorStore = create((set) => ({
+ contents: {},
+ updateContent: (fileId, code) =>
+ set((state) => ({
+ contents: {
+ ...state.contents,
+ [fileId]: code,
+ },
+ })),
+}));
From b24ac543d5426bc0a642cc9d349f65ffb5263571 Mon Sep 17 00:00:00 2001
From: Avan14 <145996250+Avan14@users.noreply.github.com>
Date: Sat, 13 Dec 2025 22:47:48 +0530
Subject: [PATCH 03/11] Refactor FileExplorer components to enhance Zustand
state management, improving file and folder interactions. Updated CodeEditor
for better synchronization with active file content and language. Adjusted
dependencies for optimal performance.
---
remote-ide/hooks/useFileStore.ts | 104 +++++++++++++++++++++++++++++++
1 file changed, 104 insertions(+)
create mode 100644 remote-ide/hooks/useFileStore.ts
diff --git a/remote-ide/hooks/useFileStore.ts b/remote-ide/hooks/useFileStore.ts
new file mode 100644
index 0000000..3b4f7fe
--- /dev/null
+++ b/remote-ide/hooks/useFileStore.ts
@@ -0,0 +1,104 @@
+import { create } from "zustand";
+import { v4 as uuid } from "uuid";
+
+export type FileNode = {
+ id: string;
+ name: string;
+ type: "file" | "folder";
+ parentId: string | null;
+ language?: string;
+};
+
+// Helper function to infer language from file extension
+function inferLanguageFromFileName(fileName: string): string | undefined {
+ const ext = fileName.split('.').pop()?.toLowerCase();
+ if (!ext) return undefined;
+
+ const extensionMap: Record = {
+ 'cpp': 'cpp',
+ 'cc': 'cpp',
+ 'cxx': 'cpp',
+ 'c': 'c',
+ 'js': 'javascript',
+ 'jsx': 'javascript',
+ 'py': 'python',
+ 'java': 'java',
+ 'cs': 'csharp',
+ 'ts': 'typescript',
+ 'tsx': 'typescript',
+ 'php': 'php',
+ 'go': 'go',
+ 'rb': 'ruby',
+ 'rs': 'rust',
+ 'kt': 'kotlin',
+ 'swift': 'swift',
+ 'sql': 'sqlite3',
+ 'ps1': 'powershell',
+ 'sh': 'bash',
+ 'm': 'matl',
+ 'dart': 'dart',
+ };
+
+ return extensionMap[ext];
+}
+
+type FileStore = {
+ files: FileNode[];
+ activeFileId: string | null;
+
+ addFile: (name: string, parentId: string) => void;
+ addFolder: (name: string, parentId: string) => void;
+ setActiveFile: (id: string) => void;
+ updateFileLanguage: (fileId: string, language: string) => void;
+};
+
+export const useFileStore = create((set) => ({
+ files: [
+ {
+ id: "root",
+ name: "SDE",
+ type: "folder",
+ parentId: null,
+ },
+ ],
+ activeFileId: null,
+
+ addFile: (name, parentId) =>
+ set((state) => {
+ const inferredLanguage = inferLanguageFromFileName(name);
+ return {
+ files: [
+ ...state.files,
+ {
+ id: uuid(),
+ name,
+ type: "file",
+ parentId,
+ language: inferredLanguage || "cpp", // Default to cpp if can't infer
+ },
+ ],
+ };
+ }),
+
+ addFolder: (name, parentId) =>
+ set((state) => ({
+ files: [
+ ...state.files,
+ {
+ id: uuid(),
+ name,
+ type: "folder",
+ parentId,
+ },
+ ],
+ })),
+
+ setActiveFile: (id) => set({ activeFileId: id }),
+
+ updateFileLanguage: (fileId, language) =>
+ set((state) => ({
+ files: state.files.map((file) =>
+ file.id === fileId ? { ...file, language } : file
+ ),
+ })),
+}));
From dcf28e9a7b90f688c4002979c7bfabbb1e32a3da Mon Sep 17 00:00:00 2001
From: Avan14 <145996250+Avan14@users.noreply.github.com>
Date: Sat, 13 Dec 2025 22:58:06 +0530
Subject: [PATCH 04/11] Update FolderCom styles for improved UI consistency by
adding padding, rounded corners, and text color to the Plus button.
---
remote-ide/app/CodeEditor/[id]/FileExplorer/FolderCom.tsx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/remote-ide/app/CodeEditor/[id]/FileExplorer/FolderCom.tsx b/remote-ide/app/CodeEditor/[id]/FileExplorer/FolderCom.tsx
index b0253bc..6945e9e 100644
--- a/remote-ide/app/CodeEditor/[id]/FileExplorer/FolderCom.tsx
+++ b/remote-ide/app/CodeEditor/[id]/FileExplorer/FolderCom.tsx
@@ -33,7 +33,7 @@ export const FolderCom = ({ folderId }: { folderId: string }) => {
{folder.name}
{
e.stopPropagation();
setShowPopup(true);
From 0a1f008fdb28d2171e878cde8ca939e25a5eb8fe Mon Sep 17 00:00:00 2001
From: Avan14 <145996250+Avan14@users.noreply.github.com>
Date: Sat, 13 Dec 2025 23:07:20 +0530
Subject: [PATCH 05/11] Implement AddItemPopup for file and folder creation in
FileExplorer, enhancing user interaction. Refactor FolderCom to utilize the
new popup for adding items, improving UI consistency and state management.
---
.../[id]/FileExplorer/FileExplorer.tsx | 32 ++++++++++++++++---
.../[id]/FileExplorer/FolderCom.tsx | 28 ++++++----------
2 files changed, 37 insertions(+), 23 deletions(-)
diff --git a/remote-ide/app/CodeEditor/[id]/FileExplorer/FileExplorer.tsx b/remote-ide/app/CodeEditor/[id]/FileExplorer/FileExplorer.tsx
index 48122cd..4a4ab44 100644
--- a/remote-ide/app/CodeEditor/[id]/FileExplorer/FileExplorer.tsx
+++ b/remote-ide/app/CodeEditor/[id]/FileExplorer/FileExplorer.tsx
@@ -1,20 +1,44 @@
// FileExplorer.tsx
+import { useState } from "react";
import { FilePlus, FolderPlus, Search } from "lucide-react";
import { FolderCom } from "./FolderCom";
+import { AddItemPopup } from "./AddItemPopup";
export const FileExplorer = () => {
+ const [showPopup, setShowPopup] = useState(false);
+ const [popupType, setPopupType] = useState<"file" | "folder">("file");
+
return (
-
+
- Files
+ Files
-
-
+ {
+ setPopupType("folder");
+ setShowPopup(true);
+ }}
+ />
+ {
+ setPopupType("file");
+ setShowPopup(true);
+ }}
+ />
+ {showPopup && (
+
setShowPopup(false)}
+ />
+ )}
diff --git a/remote-ide/app/CodeEditor/[id]/FileExplorer/FolderCom.tsx b/remote-ide/app/CodeEditor/[id]/FileExplorer/FolderCom.tsx
index 6945e9e..2e96eaa 100644
--- a/remote-ide/app/CodeEditor/[id]/FileExplorer/FolderCom.tsx
+++ b/remote-ide/app/CodeEditor/[id]/FileExplorer/FolderCom.tsx
@@ -2,29 +2,21 @@ import { useState } from "react";
import { Folder, ChevronDown, ChevronRight, Plus } from "lucide-react";
import { useFileStore } from "../../../../hooks/useFileStore";
import { FileCom } from "./FileCom";
+import { AddItemPopup } from "./AddItemPopup";
export const FolderCom = ({ folderId }: { folderId: string }) => {
const [open, setOpen] = useState(true);
const [showPopup, setShowPopup] = useState(false);
- const [name, setName] = useState("");
- const [type, setType] = useState<"file" | "folder">("file");
- const { files, addFile, addFolder } = useFileStore();
+ const { files } = useFileStore();
const children = files.filter((f) => f.parentId === folderId);
const folder = files.find((f) => f.id === folderId);
if (!folder) return null;
- const handleAdd = () => {
- if (!name.trim()) return;
- type === "file" ? addFile(name, folderId) : addFolder(name, folderId);
- setName("");
- setShowPopup(false);
- };
-
return (
-
+
setOpen(!open)}
@@ -33,7 +25,7 @@ export const FolderCom = ({ folderId }: { folderId: string }) => {
{folder.name}
{
e.stopPropagation();
setShowPopup(true);
@@ -54,13 +46,11 @@ export const FolderCom = ({ folderId }: { folderId: string }) => {
)}
{showPopup && (
-
-
setName(e.target.value)} />
-
-
+
+
setShowPopup(false)}
+ />
)}
From f2058eec4e84e6ade1c3fdbe973d2f5bfbbeb505 Mon Sep 17 00:00:00 2001
From: Avan14 <145996250+Avan14@users.noreply.github.com>
Date: Sat, 13 Dec 2025 23:07:41 +0530
Subject: [PATCH 06/11] add file structure
---
.../[id]/FileExplorer/AddItemPopup.tsx | 171 ++++++++++++++++++
1 file changed, 171 insertions(+)
create mode 100644 remote-ide/app/CodeEditor/[id]/FileExplorer/AddItemPopup.tsx
diff --git a/remote-ide/app/CodeEditor/[id]/FileExplorer/AddItemPopup.tsx b/remote-ide/app/CodeEditor/[id]/FileExplorer/AddItemPopup.tsx
new file mode 100644
index 0000000..edeb01a
--- /dev/null
+++ b/remote-ide/app/CodeEditor/[id]/FileExplorer/AddItemPopup.tsx
@@ -0,0 +1,171 @@
+import { useState, useEffect, useRef } from "react";
+import { X } from "lucide-react";
+import { useFileStore } from "../../../../hooks/useFileStore";
+
+// Invalid characters for file/folder names (Windows + Unix)
+const INVALID_CHARS = /[<>:"/\\|?*\x00-\x1f]/;
+const INVALID_NAMES = ['CON', 'PRN', 'AUX', 'NUL', 'COM1', 'COM2', 'COM3', 'COM4', 'COM5', 'COM6', 'COM7', 'COM8', 'COM9', 'LPT1', 'LPT2', 'LPT3', 'LPT4', 'LPT5', 'LPT6', 'LPT7', 'LPT8', 'LPT9'];
+
+interface AddItemPopupProps {
+ parentId: string;
+ initialType?: "file" | "folder";
+ onClose: () => void;
+}
+
+export const AddItemPopup = ({ parentId, initialType = "file", onClose }: AddItemPopupProps) => {
+ const [name, setName] = useState("");
+ const [type, setType] = useState<"file" | "folder">(initialType);
+ const [error, setError] = useState("");
+ const inputRef = useRef(null);
+ const popupRef = useRef(null);
+
+ const { files, addFile, addFolder } = useFileStore();
+
+ // Focus input when popup opens
+ useEffect(() => {
+ if (inputRef.current) {
+ inputRef.current.focus();
+ }
+ }, []);
+
+ // Close popup when clicking outside
+ useEffect(() => {
+ const handleClickOutside = (event: MouseEvent) => {
+ if (popupRef.current && !popupRef.current.contains(event.target as Node)) {
+ onClose();
+ }
+ };
+
+ document.addEventListener("mousedown", handleClickOutside);
+ return () => document.removeEventListener("mousedown", handleClickOutside);
+ }, [onClose]);
+
+ const children = files.filter((f) => f.parentId === parentId);
+
+ const validateName = (fileName: string, isFile: boolean): string | null => {
+ const trimmed = fileName.trim();
+
+ if (!trimmed) {
+ return "Name cannot be empty";
+ }
+
+ if (trimmed.length > 255) {
+ return "Name is too long (max 255 characters)";
+ }
+
+ if (INVALID_CHARS.test(trimmed)) {
+ return "Name contains invalid characters: < > : \" / \\ | ? * and control characters";
+ }
+
+ // Check for invalid Windows names
+ const nameUpper = trimmed.toUpperCase();
+ if (INVALID_NAMES.includes(nameUpper) || INVALID_NAMES.some(invalid => nameUpper.startsWith(invalid + '.'))) {
+ return "This name is reserved and cannot be used";
+ }
+
+ // Check for leading/trailing spaces or dots (Windows)
+ if (trimmed.startsWith('.') || trimmed.endsWith('.') || trimmed.endsWith(' ')) {
+ return "Name cannot start with a dot or end with a dot or space";
+ }
+
+ // Check for duplicate names in the same folder
+ const siblingNames = children.map(c => c.name.toLowerCase());
+ if (siblingNames.includes(trimmed.toLowerCase())) {
+ return "A file or folder with this name already exists";
+ }
+
+ return null;
+ };
+
+ const handleAdd = () => {
+ const validationError = validateName(name, type === "file");
+
+ if (validationError) {
+ setError(validationError);
+ return;
+ }
+
+ const trimmedName = name.trim();
+ if (type === "file") {
+ addFile(trimmedName, parentId);
+ } else {
+ addFolder(trimmedName, parentId);
+ }
+
+ onClose();
+ };
+
+ const handleKeyDown = (e: React.KeyboardEvent) => {
+ if (e.key === "Enter") {
+ e.preventDefault();
+ handleAdd();
+ } else if (e.key === "Escape") {
+ onClose();
+ }
+ };
+
+ return (
+
+
+
+ New {type === "file" ? "File" : "Folder"}
+
+
+
+
+
{
+ setName(e.target.value);
+ setError("");
+ }}
+ onKeyDown={handleKeyDown}
+ placeholder={`Enter ${type === "file" ? "file" : "folder"} name`}
+ className="w-full px-2 py-1 bg-gray-900 border border-gray-600 rounded text-white text-sm mb-2 focus:outline-none focus:border-blue-500"
+ />
+
+
+
+ {error && (
+
{error}
+ )}
+
+
+
+
+
+
+ );
+};
+
From efc9d1bf9bd258d826aaf6ea9956d1919c8a3bee Mon Sep 17 00:00:00 2001
From: Avan14 <145996250+Avan14@users.noreply.github.com>
Date: Sat, 13 Dec 2025 23:17:11 +0530
Subject: [PATCH 07/11] rename Popupadded
---
.../[id]/FileExplorer/RenamePopup.tsx | 168 ++++++++++++++++++
1 file changed, 168 insertions(+)
create mode 100644 remote-ide/app/CodeEditor/[id]/FileExplorer/RenamePopup.tsx
diff --git a/remote-ide/app/CodeEditor/[id]/FileExplorer/RenamePopup.tsx b/remote-ide/app/CodeEditor/[id]/FileExplorer/RenamePopup.tsx
new file mode 100644
index 0000000..e406259
--- /dev/null
+++ b/remote-ide/app/CodeEditor/[id]/FileExplorer/RenamePopup.tsx
@@ -0,0 +1,168 @@
+import { useState, useEffect, useRef } from "react";
+import { X } from "lucide-react";
+import { useFileStore } from "../../../../hooks/useFileStore";
+
+// Invalid characters for file/folder names (Windows + Unix)
+const INVALID_CHARS = /[<>:"/\\|?*\x00-\x1f]/;
+const INVALID_NAMES = ['CON', 'PRN', 'AUX', 'NUL', 'COM1', 'COM2', 'COM3', 'COM4', 'COM5', 'COM6', 'COM7', 'COM8', 'COM9', 'LPT1', 'LPT2', 'LPT3', 'LPT4', 'LPT5', 'LPT6', 'LPT7', 'LPT8', 'LPT9'];
+
+interface RenamePopupProps {
+ itemId: string;
+ currentName: string;
+ onClose: () => void;
+}
+
+export const RenamePopup = ({ itemId, currentName, onClose }: RenamePopupProps) => {
+ const [name, setName] = useState(currentName);
+ const [error, setError] = useState("");
+ const inputRef = useRef(null);
+ const popupRef = useRef(null);
+
+ const { files, renameFile } = useFileStore();
+
+ // Focus input when popup opens and select the name part (without extension for files)
+ useEffect(() => {
+ if (inputRef.current) {
+ inputRef.current.focus();
+ // Select name without extension for easier renaming
+ const lastDotIndex = currentName.lastIndexOf('.');
+ if (lastDotIndex > 0) {
+ inputRef.current.setSelectionRange(0, lastDotIndex);
+ } else {
+ inputRef.current.select();
+ }
+ }
+ }, [currentName]);
+
+ // Close popup when clicking outside
+ useEffect(() => {
+ const handleClickOutside = (event: MouseEvent) => {
+ if (popupRef.current && !popupRef.current.contains(event.target as Node)) {
+ onClose();
+ }
+ };
+
+ document.addEventListener("mousedown", handleClickOutside);
+ return () => document.removeEventListener("mousedown", handleClickOutside);
+ }, [onClose]);
+
+ const item = files.find(f => f.id === itemId);
+ if (!item) return null;
+
+ const parentId = item.parentId;
+ const siblings = files.filter(f => f.parentId === parentId && f.id !== itemId);
+
+ const validateName = (fileName: string): string | null => {
+ const trimmed = fileName.trim();
+
+ if (!trimmed) {
+ return "Name cannot be empty";
+ }
+
+ if (trimmed.length > 255) {
+ return "Name is too long (max 255 characters)";
+ }
+
+ if (INVALID_CHARS.test(trimmed)) {
+ return "Name contains invalid characters: < > : \" / \\ | ? * and control characters";
+ }
+
+ // Check for invalid Windows names
+ const nameUpper = trimmed.toUpperCase();
+ if (INVALID_NAMES.includes(nameUpper) || INVALID_NAMES.some(invalid => nameUpper.startsWith(invalid + '.'))) {
+ return "This name is reserved and cannot be used";
+ }
+
+ // Check for leading/trailing spaces or dots (Windows)
+ if (trimmed.startsWith('.') || trimmed.endsWith('.') || trimmed.endsWith(' ')) {
+ return "Name cannot start with a dot or end with a dot or space";
+ }
+
+ // Check for duplicate names in the same folder
+ const siblingNames = siblings.map(c => c.name.toLowerCase());
+ if (siblingNames.includes(trimmed.toLowerCase())) {
+ return "A file or folder with this name already exists";
+ }
+
+ return null;
+ };
+
+ const handleRename = () => {
+ if (name.trim() === currentName) {
+ onClose();
+ return;
+ }
+
+ const validationError = validateName(name);
+
+ if (validationError) {
+ setError(validationError);
+ return;
+ }
+
+ renameFile(itemId, name.trim());
+ onClose();
+ };
+
+ const handleKeyDown = (e: React.KeyboardEvent) => {
+ if (e.key === "Enter") {
+ e.preventDefault();
+ handleRename();
+ } else if (e.key === "Escape") {
+ onClose();
+ }
+ };
+
+ return (
+
+
+
+ Rename {item.type === "file" ? "File" : "Folder"}
+
+
+
+
+
{
+ setName(e.target.value);
+ setError("");
+ }}
+ onKeyDown={handleKeyDown}
+ placeholder="Enter new name"
+ className="w-full px-2 py-1 bg-gray-900 border border-gray-600 rounded text-white text-sm mb-2 focus:outline-none focus:border-blue-500"
+ />
+
+ {error && (
+
{error}
+ )}
+
+
+
+
+
+
+ );
+};
+
From 226c6fac1b992748730c11033f9d579cd4b31858 Mon Sep 17 00:00:00 2001
From: Avan14 <145996250+Avan14@users.noreply.github.com>
Date: Sun, 14 Dec 2025 15:17:55 +0530
Subject: [PATCH 08/11] buttton fixes
---
.../CodeEditor/[id]/FileExplorer/FileCom.tsx | 37 +++++++++++++----
.../[id]/FileExplorer/FolderCom.tsx | 41 +++++++++++++++----
.../[id]/FileExplorer/RenamePopup.tsx | 3 +-
remote-ide/hooks/useFileStore.ts | 22 ++++++++++
4 files changed, 85 insertions(+), 18 deletions(-)
diff --git a/remote-ide/app/CodeEditor/[id]/FileExplorer/FileCom.tsx b/remote-ide/app/CodeEditor/[id]/FileExplorer/FileCom.tsx
index a4ee279..f40194a 100644
--- a/remote-ide/app/CodeEditor/[id]/FileExplorer/FileCom.tsx
+++ b/remote-ide/app/CodeEditor/[id]/FileExplorer/FileCom.tsx
@@ -1,19 +1,42 @@
-import { File } from "lucide-react";
+import { useState } from "react";
+import { File, Pencil } from "lucide-react";
import { useFileStore } from "../../../../hooks/useFileStore";
+import { RenamePopup } from "./RenamePopup";
export const FileCom = ({ fileId }: { fileId: string }) => {
+ const [showRenamePopup, setShowRenamePopup] = useState(false);
const { files, setActiveFile } = useFileStore();
const file = files.find((f) => f.id === fileId);
if (!file) return null;
return (
- setActiveFile(fileId)}
- >
-
-
{file.name}
+
+
setActiveFile(fileId)}
+ >
+
+
{file.name}
+
{
+ e.stopPropagation();
+ setShowRenamePopup(true);
+ }}
+ />
+
+
+ {showRenamePopup && (
+
+ setShowRenamePopup(false)}
+ />
+
+ )}
);
};
diff --git a/remote-ide/app/CodeEditor/[id]/FileExplorer/FolderCom.tsx b/remote-ide/app/CodeEditor/[id]/FileExplorer/FolderCom.tsx
index 2e96eaa..043eb3a 100644
--- a/remote-ide/app/CodeEditor/[id]/FileExplorer/FolderCom.tsx
+++ b/remote-ide/app/CodeEditor/[id]/FileExplorer/FolderCom.tsx
@@ -1,12 +1,14 @@
import { useState } from "react";
-import { Folder, ChevronDown, ChevronRight, Plus } from "lucide-react";
+import { Folder, ChevronDown, ChevronRight, Plus, Pencil } from "lucide-react";
import { useFileStore } from "../../../../hooks/useFileStore";
import { FileCom } from "./FileCom";
import { AddItemPopup } from "./AddItemPopup";
+import { RenamePopup } from "./RenamePopup";
export const FolderCom = ({ folderId }: { folderId: string }) => {
const [open, setOpen] = useState(true);
const [showPopup, setShowPopup] = useState(false);
+ const [showRenamePopup, setShowRenamePopup] = useState(false);
const { files } = useFileStore();
@@ -18,19 +20,30 @@ export const FolderCom = ({ folderId }: { folderId: string }) => {
return (
setOpen(!open)}
>
{open ?
:
}
{folder.name}
-
{
- e.stopPropagation();
- setShowPopup(true);
- }}
- />
+
+
{
+ e.stopPropagation();
+ setShowRenamePopup(true);
+ }}
+ />
+ {
+ e.stopPropagation();
+ setShowPopup(true);
+ }}
+ />
+
{open && (
@@ -53,6 +66,16 @@ export const FolderCom = ({ folderId }: { folderId: string }) => {
/>
)}
+
+ {showRenamePopup && (
+
+ setShowRenamePopup(false)}
+ />
+
+ )}
);
};
diff --git a/remote-ide/app/CodeEditor/[id]/FileExplorer/RenamePopup.tsx b/remote-ide/app/CodeEditor/[id]/FileExplorer/RenamePopup.tsx
index e406259..1b96161 100644
--- a/remote-ide/app/CodeEditor/[id]/FileExplorer/RenamePopup.tsx
+++ b/remote-ide/app/CodeEditor/[id]/FileExplorer/RenamePopup.tsx
@@ -116,8 +116,7 @@ export const RenamePopup = ({ itemId, currentName, onClose }: RenamePopupProps)
return (
diff --git a/remote-ide/hooks/useFileStore.ts b/remote-ide/hooks/useFileStore.ts
index 3b4f7fe..3604e5f 100644
--- a/remote-ide/hooks/useFileStore.ts
+++ b/remote-ide/hooks/useFileStore.ts
@@ -50,6 +50,7 @@ type FileStore = {
addFolder: (name: string, parentId: string) => void;
setActiveFile: (id: string) => void;
updateFileLanguage: (fileId: string, language: string) => void;
+ renameFile: (fileId: string, newName: string) => void;
};
export const useFileStore = create((set) => ({
@@ -101,4 +102,25 @@ export const useFileStore = create((set) => ({
file.id === fileId ? { ...file, language } : file
),
})),
+
+ renameFile: (fileId, newName) =>
+ set((state) => {
+ // Update language if it's a file and extension changed
+ const file = state.files.find((f) => f.id === fileId);
+ const inferredLanguage = file?.type === "file"
+ ? inferLanguageFromFileName(newName)
+ : undefined;
+
+ return {
+ files: state.files.map((file) =>
+ file.id === fileId
+ ? {
+ ...file,
+ name: newName,
+ ...(inferredLanguage && { language: inferredLanguage }),
+ }
+ : file
+ ),
+ };
+ }),
}));
From 1384b07e6a18b4b3d3f1d07edee5242813389fe0 Mon Sep 17 00:00:00 2001
From: Avan14 <145996250+Avan14@users.noreply.github.com>
Date: Sun, 14 Dec 2025 15:30:24 +0530
Subject: [PATCH 09/11] updates
---
remote-ide/app/CodeEditor/[id]/FileExplorer/AddItemPopup.tsx | 5 +++++
remote-ide/app/CodeEditor/[id]/FileExplorer/FileCom.tsx | 4 +++-
remote-ide/app/CodeEditor/[id]/FileExplorer/FileExplorer.tsx | 2 ++
remote-ide/app/CodeEditor/[id]/FileExplorer/FolderCom.tsx | 2 ++
remote-ide/app/CodeEditor/[id]/FileExplorer/RenamePopup.tsx | 5 +++++
5 files changed, 17 insertions(+), 1 deletion(-)
diff --git a/remote-ide/app/CodeEditor/[id]/FileExplorer/AddItemPopup.tsx b/remote-ide/app/CodeEditor/[id]/FileExplorer/AddItemPopup.tsx
index edeb01a..d7c3fa2 100644
--- a/remote-ide/app/CodeEditor/[id]/FileExplorer/AddItemPopup.tsx
+++ b/remote-ide/app/CodeEditor/[id]/FileExplorer/AddItemPopup.tsx
@@ -1,3 +1,5 @@
+"use client";
+
import { useState, useEffect, useRef } from "react";
import { X } from "lucide-react";
import { useFileStore } from "../../../../hooks/useFileStore";
@@ -30,6 +32,9 @@ export const AddItemPopup = ({ parentId, initialType = "file", onClose }: AddIte
// Close popup when clicking outside
useEffect(() => {
+ // Only run on client
+ if (typeof window === "undefined") return;
+
const handleClickOutside = (event: MouseEvent) => {
if (popupRef.current && !popupRef.current.contains(event.target as Node)) {
onClose();
diff --git a/remote-ide/app/CodeEditor/[id]/FileExplorer/FileCom.tsx b/remote-ide/app/CodeEditor/[id]/FileExplorer/FileCom.tsx
index f40194a..d4f2ca4 100644
--- a/remote-ide/app/CodeEditor/[id]/FileExplorer/FileCom.tsx
+++ b/remote-ide/app/CodeEditor/[id]/FileExplorer/FileCom.tsx
@@ -1,3 +1,5 @@
+"use client";
+
import { useState } from "react";
import { File, Pencil } from "lucide-react";
import { useFileStore } from "../../../../hooks/useFileStore";
@@ -19,7 +21,7 @@ export const FileCom = ({ fileId }: { fileId: string }) => {
{file.name}
{
e.stopPropagation();
diff --git a/remote-ide/app/CodeEditor/[id]/FileExplorer/FileExplorer.tsx b/remote-ide/app/CodeEditor/[id]/FileExplorer/FileExplorer.tsx
index 4a4ab44..6e156c4 100644
--- a/remote-ide/app/CodeEditor/[id]/FileExplorer/FileExplorer.tsx
+++ b/remote-ide/app/CodeEditor/[id]/FileExplorer/FileExplorer.tsx
@@ -1,3 +1,5 @@
+"use client";
+
// FileExplorer.tsx
import { useState } from "react";
diff --git a/remote-ide/app/CodeEditor/[id]/FileExplorer/FolderCom.tsx b/remote-ide/app/CodeEditor/[id]/FileExplorer/FolderCom.tsx
index 043eb3a..17e5a52 100644
--- a/remote-ide/app/CodeEditor/[id]/FileExplorer/FolderCom.tsx
+++ b/remote-ide/app/CodeEditor/[id]/FileExplorer/FolderCom.tsx
@@ -1,3 +1,5 @@
+"use client";
+
import { useState } from "react";
import { Folder, ChevronDown, ChevronRight, Plus, Pencil } from "lucide-react";
import { useFileStore } from "../../../../hooks/useFileStore";
diff --git a/remote-ide/app/CodeEditor/[id]/FileExplorer/RenamePopup.tsx b/remote-ide/app/CodeEditor/[id]/FileExplorer/RenamePopup.tsx
index 1b96161..46d698f 100644
--- a/remote-ide/app/CodeEditor/[id]/FileExplorer/RenamePopup.tsx
+++ b/remote-ide/app/CodeEditor/[id]/FileExplorer/RenamePopup.tsx
@@ -1,3 +1,5 @@
+"use client";
+
import { useState, useEffect, useRef } from "react";
import { X } from "lucide-react";
import { useFileStore } from "../../../../hooks/useFileStore";
@@ -36,6 +38,9 @@ export const RenamePopup = ({ itemId, currentName, onClose }: RenamePopupProps)
// Close popup when clicking outside
useEffect(() => {
+ // Only run on client
+ if (typeof window === "undefined") return;
+
const handleClickOutside = (event: MouseEvent) => {
if (popupRef.current && !popupRef.current.contains(event.target as Node)) {
onClose();
From 4b06fcb1bc76ffa63223fc27231b4a1fd120b2a1 Mon Sep 17 00:00:00 2001
From: Avan14 <145996250+Avan14@users.noreply.github.com>
Date: Sat, 20 Dec 2025 17:22:46 +0530
Subject: [PATCH 10/11] data flow re-designed
---
remote-ide/app/Api/User/modals/User.ts | 37 --
remote-ide/app/Api/User/route.ts | 83 ++---
remote-ide/app/Api/User/schema/types.ts | 80 -----
.../app/Api/documents/[documentId]/route.ts | 0
remote-ide/app/Api/documents/route.ts | 0
.../app/Api/projects/[projectId]/route.ts | 74 ++++
remote-ide/app/Api/projects/route.ts | 80 +++++
remote-ide/app/CodeEditor/[id]/CloudApi.tsx | 25 +-
.../[id]/FileExplorer/AddItemPopup.tsx | 85 ++---
.../[id]/FileExplorer/DeletePopup.tsx | 100 ++++++
.../CodeEditor/[id]/FileExplorer/FileCom.tsx | 24 +-
.../[id]/FileExplorer/FileExplorer.tsx | 22 +-
.../[id]/FileExplorer/FolderCom.tsx | 32 +-
.../[id]/FileExplorer/RenamePopup.tsx | 52 +--
.../app/CodeEditor/[id]/FileExplorer/types.ts | 98 ++++--
remote-ide/app/CodeEditor/[id]/Header.tsx | 105 ++----
remote-ide/app/CodeEditor/[id]/api.tsx | 4 +-
remote-ide/app/CodeEditor/[id]/chatbot.tsx | 9 +-
remote-ide/app/CodeEditor/[id]/constants.ts | 47 +--
remote-ide/app/CodeEditor/[id]/layout.tsx | 13 +-
remote-ide/app/CodeEditor/[id]/page.tsx | 97 ++++--
remote-ide/app/DashBoard/CreateProject.tsx | 96 ++---
remote-ide/app/DashBoard/ProjectCard.tsx | 134 +++----
remote-ide/app/DashBoard/page.tsx | 36 +-
remote-ide/app/DashBoard/sidebar.tsx | 50 +--
remote-ide/app/DashBoard/types.ts | 20 +-
.../[id]/FileExplorer/FileCom.tsx | 0
.../[id]/FileExplorer/FileExplorer.tsx | 0
.../[id]/FileExplorer/FolderCom.tsx | 0
.../[id]/FileExplorer/types.ts | 0
.../app/{WebDev => WebEditor}/[id]/Header.tsx | 0
.../{WebDev => WebEditor}/[id]/chatbot.tsx | 9 +-
.../app/{WebDev => WebEditor}/[id]/layout.tsx | 0
.../app/{WebDev => WebEditor}/[id]/page.tsx | 4 +-
remote-ide/app/context/UserDataContext.tsx | 31 --
remote-ide/app/layout.tsx | 19 +-
remote-ide/app/page.tsx | 2 +-
remote-ide/components/Constants/constants.ts | 2 +-
remote-ide/components/elements/ChatbotRes.tsx | 26 +-
remote-ide/components/elements/Footer.tsx | 2 +-
remote-ide/components/elements/Hero2.tsx | 2 +-
remote-ide/components/elements/Navbar.tsx | 20 +-
remote-ide/hooks/useEditorStore.ts | 34 ++
remote-ide/hooks/useEditorStore.tsx | 17 -
remote-ide/hooks/useFileStore.ts | 329 ++++++++++++++----
remote-ide/hooks/useUserStore.ts | 22 ++
remote-ide/middleware.ts | 2 +-
remote-ide/package-lock.json | 10 +
remote-ide/package.json | 1 +
remote-ide/server/db/mongoose.ts | 34 ++
remote-ide/server/models/models.ts | 50 +++
51 files changed, 1203 insertions(+), 816 deletions(-)
delete mode 100644 remote-ide/app/Api/User/modals/User.ts
delete mode 100644 remote-ide/app/Api/User/schema/types.ts
create mode 100644 remote-ide/app/Api/documents/[documentId]/route.ts
create mode 100644 remote-ide/app/Api/documents/route.ts
create mode 100644 remote-ide/app/Api/projects/[projectId]/route.ts
create mode 100644 remote-ide/app/Api/projects/route.ts
create mode 100644 remote-ide/app/CodeEditor/[id]/FileExplorer/DeletePopup.tsx
rename remote-ide/app/{WebDev => WebEditor}/[id]/FileExplorer/FileCom.tsx (100%)
rename remote-ide/app/{WebDev => WebEditor}/[id]/FileExplorer/FileExplorer.tsx (100%)
rename remote-ide/app/{WebDev => WebEditor}/[id]/FileExplorer/FolderCom.tsx (100%)
rename remote-ide/app/{WebDev => WebEditor}/[id]/FileExplorer/types.ts (100%)
rename remote-ide/app/{WebDev => WebEditor}/[id]/Header.tsx (100%)
rename remote-ide/app/{WebDev => WebEditor}/[id]/chatbot.tsx (93%)
rename remote-ide/app/{WebDev => WebEditor}/[id]/layout.tsx (100%)
rename remote-ide/app/{WebDev => WebEditor}/[id]/page.tsx (96%)
delete mode 100644 remote-ide/app/context/UserDataContext.tsx
create mode 100644 remote-ide/hooks/useEditorStore.ts
delete mode 100644 remote-ide/hooks/useEditorStore.tsx
create mode 100644 remote-ide/hooks/useUserStore.ts
create mode 100644 remote-ide/server/db/mongoose.ts
create mode 100644 remote-ide/server/models/models.ts
diff --git a/remote-ide/app/Api/User/modals/User.ts b/remote-ide/app/Api/User/modals/User.ts
deleted file mode 100644
index d0e6160..0000000
--- a/remote-ide/app/Api/User/modals/User.ts
+++ /dev/null
@@ -1,37 +0,0 @@
-import mongoose from "mongoose";
-
-const FileFolderSchema = new mongoose.Schema({
- id: String,
- name: String,
- type: {
- type: String,
- enum: ["File", "Folder"],
- required: true,
- },
- code: String,
- children: [mongoose.Schema.Types.Mixed], // placeholder to allow nesting
-});
-
-// This sets children as an array of the same schema (recursive)
-FileFolderSchema.add({
- children: [FileFolderSchema],
-});
-
-const ProjectSchema = new mongoose.Schema({
- project_id: String,
- name: String,
- type: String,
- date: Date,
- language: String,
- folder: FileFolderSchema,
-});
-
-const UserSchema = new mongoose.Schema({
- id: String,
- name: String,
- projects: [ProjectSchema],
-});
-
-const UserModel = mongoose.models.User || mongoose.model("User", UserSchema);
-
-export default UserModel;
diff --git a/remote-ide/app/Api/User/route.ts b/remote-ide/app/Api/User/route.ts
index c759dff..d4750a0 100644
--- a/remote-ide/app/Api/User/route.ts
+++ b/remote-ide/app/Api/User/route.ts
@@ -1,71 +1,40 @@
-import mongoose from "mongoose";
-import UserModel from "./modals/User";
-import { NextRequest, NextResponse } from "next/server";
-
-// Connect to MongoDB
-async function connectDB() {
- try {
- await mongoose.connect(process.env.MONGODB_URI as string);
- console.log("Connected to MongoDB");
- } catch (error) {
- console.error("MongoDB connection error:", error);
- throw error;
- }
-}
-await connectDB();
+import { NextRequest, NextResponse } from "next/server"
+import { auth } from "@clerk/nextjs/server"
+import { connectToDB } from "@/server/db/mongoose"
+import { User } from "@/server/models/models"
export async function POST(req: NextRequest) {
- const body = await req.json();
- const { userId, name } = body;
+ const { userId } = await auth()
- try {
-
- // Check if user exists
- const existingUser = await UserModel.findOne({ id: userId });
- if (existingUser) {
- return NextResponse.json(existingUser, { status: 200 });
- }
-
- // Create new user if not exists
- const newUser = {
- id: userId,
- name: name,
- projects: [],
- };
-
- const createdUser = await UserModel.create(newUser);
-
- return NextResponse.json(createdUser, { status: 200 });
- } catch (error) {
- console.error("Error creating user:", error);
- return NextResponse.json({ error: "Server error" }, { status: 500 });
+ if (!userId) {
+ return NextResponse.json({ error: "Unauthorized" }, { status: 401 })
}
-}
+ const body = await req.json().catch(() => ({}))
+ const name = body?.name ?? "User"
-// end-point for updating projects in the userd database
-export async function PUT(req: NextRequest) {
try {
- const body = await req.json();
- const { userId, updatedprojects } = body;
-
- if (!userId || !Array.isArray(updatedprojects)) {
- return NextResponse.json({ error: "Invalid data" }, { status: 400 });
- }
+ await connectToDB();
- const updatedUser = await UserModel.findOneAndUpdate(
- { id: userId },
- { $set: { projects: updatedprojects } },
- { new: true }
- );
+ let user = await User.findOne({ authId: userId })
- if (!updatedUser) {
- return NextResponse.json({ error: "User not found" }, { status: 404 });
+ if (!user) {
+ user = await User.create({
+ authId: userId,
+ name,
+ })
}
- return NextResponse.json({ message: "User updated", user: updatedUser }, { status: 200 });
+ return NextResponse.json(
+ {
+ id: user._id.toString(),
+ name: user.name,
+ createdAt: user.createdAt,
+ },
+ { status: 201 }
+ )
} catch (err) {
- console.error("Error updating user:", err);
- return NextResponse.json({ error: "Internal server error" }, { status: 500 });
+ console.error("User init error:", err)
+ return NextResponse.json({ error: "Server error" }, { status: 500 })
}
}
diff --git a/remote-ide/app/Api/User/schema/types.ts b/remote-ide/app/Api/User/schema/types.ts
deleted file mode 100644
index efd7875..0000000
--- a/remote-ide/app/Api/User/schema/types.ts
+++ /dev/null
@@ -1,80 +0,0 @@
-import { z } from "zod";
-
-export type FileTreeType = {
- id: string;
- name: string;
- type: "Folder" | "File";
- code: string;
- children: FileTreeType[];
-};
-
-const FileTreeSchemaType: z.ZodType = z.lazy(() => z.object({
- id: z.string(),
- name: z.string(),
- type: z.enum(["Folder", "File"]),
- code: z.string(),
- children: z.array(FileTreeSchemaType),
-}));
-
-const ProjectSchemaType = z.object({
- project_id: z.string(),
- name: z.string(),
- type: z.string(),
- date: z.coerce.date(),
- language: z.string(),
- folder: FileTreeSchemaType,
-});
-
-const UserSchemaType = z.object({
- id: z.string(),
- name: z.string(),
- projects: z.array(ProjectSchemaType),
-});
-
-export { FileTreeSchemaType, ProjectSchemaType, UserSchemaType };
-
-const exampleUser = {
- id: "user_123",
- name: "John Doe",
- projects: [
- {
- project_id: "proj_456",
- name: "AI Chatbot",
- type: "Web Application",
- date: new Date("2024-04-03"),
- language: "TypeScript",
- folder: {
- id: "folder_1",
- name: "src",
- type: "Folder",
- code: "",
- children: [
- {
- id: "file_1",
- name: "index.ts",
- type: "File",
- code: "console.log('Hello, World!');",
- children: []
- },
- {
- id: "folder_2",
- name: "components",
- type: "Folder",
- code: "",
- children: [
- {
- id: "file_2",
- name: "Chatbot.tsx",
- type: "File",
- code: "export function Chatbot() { return Chatbot
; }",
- children: []
- }
- ]
- }
- ]
- }
- }
- ]
-};
-
-
diff --git a/remote-ide/app/Api/documents/[documentId]/route.ts b/remote-ide/app/Api/documents/[documentId]/route.ts
new file mode 100644
index 0000000..e69de29
diff --git a/remote-ide/app/Api/documents/route.ts b/remote-ide/app/Api/documents/route.ts
new file mode 100644
index 0000000..e69de29
diff --git a/remote-ide/app/Api/projects/[projectId]/route.ts b/remote-ide/app/Api/projects/[projectId]/route.ts
new file mode 100644
index 0000000..66fc32a
--- /dev/null
+++ b/remote-ide/app/Api/projects/[projectId]/route.ts
@@ -0,0 +1,74 @@
+import { NextRequest, NextResponse } from "next/server"
+import { auth } from "@clerk/nextjs/server"
+import { connectToDB } from "@/server/db/mongoose"
+import { Project, Document } from "@/server/models/models"
+import mongoose from "mongoose"
+
+// builds nested list
+function buildTree(nodes: any[]) {
+ const map = new Map()
+ let root = null
+
+ for (const node of nodes) {
+ map.set(node._id.toString(), {
+ id: node._id.toString(),
+ name: node.name,
+ type: node.type,
+ extension: node.extension,
+ children: [],
+ })
+ }
+
+ for (const node of nodes) {
+ const id = node._id.toString()
+ if (node.parent) {
+ const parent = map.get(node.parent.toString())
+ parent.children.push(map.get(id))
+ } else {
+ root = map.get(id)
+ }
+ }
+ return root;
+}
+
+export async function GET(
+ req: NextRequest,
+ ctx: { params: Promise<{ projectId: string }> }
+) {
+ const { userId } = await auth()
+ if (!userId) {
+ return NextResponse.json({ error: "Unauthorized" }, { status: 401 })
+ }
+
+ const { projectId } = await ctx.params
+
+ if (!mongoose.Types.ObjectId.isValid(projectId)) {
+ return NextResponse.json({ error: "Invalid project id" }, { status: 400 })
+ }
+
+ await connectToDB()
+
+ const project = await Project.findById(projectId).populate("owner")
+
+ if (!project || project.owner.authId !== userId) {
+ return NextResponse.json({ error: "Not found" }, { status: 404 })
+ }
+
+ const documents = await Document.find({
+ project: project._id,
+ isDeleted: false,
+ }).lean()
+
+ const tree = buildTree(documents)
+
+ console.log(tree);
+ return NextResponse.json({
+ project: {
+ id: project._id.toString(),
+ name: project.name,
+ track: project.track,
+ },
+ tree,
+ })
+}
+
diff --git a/remote-ide/app/Api/projects/route.ts b/remote-ide/app/Api/projects/route.ts
new file mode 100644
index 0000000..22b58cc
--- /dev/null
+++ b/remote-ide/app/Api/projects/route.ts
@@ -0,0 +1,80 @@
+import { NextRequest, NextResponse } from 'next/server'
+import { auth } from '@clerk/nextjs/server'
+import slugify from 'slugify'
+import { connectToDB } from '@/server/db/mongoose'
+import { Project, User } from '@/server/models/models'
+
+export async function GET(req: NextRequest) {
+ try {
+ await connectToDB();
+
+ const { userId } = await auth()
+ if (!userId) {
+ return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
+ }
+
+ const user = await User.findOne({ authId: userId })
+ if (!user) {
+ return NextResponse.json([], { status: 200 })
+ }
+
+ const projects = await Project.find({ owner: user._id })
+ .sort({ createdAt: -1 })
+ .select('_id name slug track createdAt')
+
+ return NextResponse.json(
+ projects.map(p => ({
+ id: p._id.toString(),
+ name: p.name,
+ slug: p.slug,
+ track: p.track,
+ createdAt: p.createdAt,
+ }))
+ )
+ }
+ catch {
+ return NextResponse.json({ error: 'Internal server error' }, { status: 500 })
+ }
+}
+export async function PUT(req: NextRequest) {
+ try {
+
+ await connectToDB()
+ const { userId } = await auth()
+ if (!userId) {
+ return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
+ }
+
+ const { name, track } = await req.json()
+
+ const user = await User.findOne({ authId: userId })
+ if (!user) {
+ return NextResponse.json({ error: 'User not found' }, { status: 404 })
+ }
+
+ const project = await Project.create({
+ owner: user._id,
+ name,
+ track,
+ slug: slugify(name, { lower: true, strict: true }),
+ })
+
+ return NextResponse.json({
+ id: project._id.toString(),
+ name: project.name,
+ slug: project.slug,
+ track: project.track,
+ createdAt: project.createdAt,
+ })
+ } catch (err: any) {
+ if (err.code === 11000) {
+ return NextResponse.json(
+ { error: 'Project with same name already exists' },
+ { status: 409 }
+ )
+ }
+
+ return NextResponse.json({ error: 'Internal server error' }, { status: 500 })
+ }
+}
+
diff --git a/remote-ide/app/CodeEditor/[id]/CloudApi.tsx b/remote-ide/app/CodeEditor/[id]/CloudApi.tsx
index a038011..8f3408e 100644
--- a/remote-ide/app/CodeEditor/[id]/CloudApi.tsx
+++ b/remote-ide/app/CodeEditor/[id]/CloudApi.tsx
@@ -3,23 +3,26 @@ import { LANGUAGE_VERSIONS } from "./constants";
// const BASE_URL = process.env.CLOUD_URL as string;
+
+// this should hit the backend first then goto the cloud
type Language = keyof typeof LANGUAGE_VERSIONS;
- const ExecuteCode = async (
- language: Language,
+const ExecuteCode = async (
+ extension: string,
SourceCode: string,
) => {
- console.log("hi");
- // console.log(BASE_URL);
+ console.log("hi");
+ // console.log(BASE_URL);
try {
console.log(SourceCode);
- const response = await axios.post( "http://143.110.245.12:3000/run", {
- code: SourceCode,
- }, {
- headers: {
- "Content-Type": "application/json"
- }
- });
+ const response = await axios.post("http://143.110.245.12:3000/run", {
+ code: SourceCode,
+ extension,
+ }, {
+ headers: {
+ "Content-Type": "application/json"
+ }
+ });
return response.data.output || response.data.error || "No output.";
} catch (error: any) {
diff --git a/remote-ide/app/CodeEditor/[id]/FileExplorer/AddItemPopup.tsx b/remote-ide/app/CodeEditor/[id]/FileExplorer/AddItemPopup.tsx
index d7c3fa2..c93657a 100644
--- a/remote-ide/app/CodeEditor/[id]/FileExplorer/AddItemPopup.tsx
+++ b/remote-ide/app/CodeEditor/[id]/FileExplorer/AddItemPopup.tsx
@@ -1,27 +1,29 @@
+//AddItemPopup.tsx
+
"use client";
import { useState, useEffect, useRef } from "react";
import { X } from "lucide-react";
import { useFileStore } from "../../../../hooks/useFileStore";
-// Invalid characters for file/folder names (Windows + Unix)
-const INVALID_CHARS = /[<>:"/\\|?*\x00-\x1f]/;
-const INVALID_NAMES = ['CON', 'PRN', 'AUX', 'NUL', 'COM1', 'COM2', 'COM3', 'COM4', 'COM5', 'COM6', 'COM7', 'COM8', 'COM9', 'LPT1', 'LPT2', 'LPT3', 'LPT4', 'LPT5', 'LPT6', 'LPT7', 'LPT8', 'LPT9'];
-
interface AddItemPopupProps {
parentId: string;
initialType?: "file" | "folder";
onClose: () => void;
}
-export const AddItemPopup = ({ parentId, initialType = "file", onClose }: AddItemPopupProps) => {
+export const AddItemPopup = ({
+ parentId,
+ initialType = "file",
+ onClose,
+}: AddItemPopupProps) => {
const [name, setName] = useState("");
const [type, setType] = useState<"file" | "folder">(initialType);
const [error, setError] = useState("");
const inputRef = useRef(null);
const popupRef = useRef(null);
- const { files, addFile, addFolder } = useFileStore();
+ const { addFile, addFolder } = useFileStore();
// Focus input when popup opens
useEffect(() => {
@@ -36,7 +38,10 @@ export const AddItemPopup = ({ parentId, initialType = "file", onClose }: AddIte
if (typeof window === "undefined") return;
const handleClickOutside = (event: MouseEvent) => {
- if (popupRef.current && !popupRef.current.contains(event.target as Node)) {
+ if (
+ popupRef.current &&
+ !popupRef.current.contains(event.target as Node)
+ ) {
onClose();
}
};
@@ -45,58 +50,13 @@ export const AddItemPopup = ({ parentId, initialType = "file", onClose }: AddIte
return () => document.removeEventListener("mousedown", handleClickOutside);
}, [onClose]);
- const children = files.filter((f) => f.parentId === parentId);
-
- const validateName = (fileName: string, isFile: boolean): string | null => {
- const trimmed = fileName.trim();
-
- if (!trimmed) {
- return "Name cannot be empty";
- }
-
- if (trimmed.length > 255) {
- return "Name is too long (max 255 characters)";
- }
-
- if (INVALID_CHARS.test(trimmed)) {
- return "Name contains invalid characters: < > : \" / \\ | ? * and control characters";
- }
-
- // Check for invalid Windows names
- const nameUpper = trimmed.toUpperCase();
- if (INVALID_NAMES.includes(nameUpper) || INVALID_NAMES.some(invalid => nameUpper.startsWith(invalid + '.'))) {
- return "This name is reserved and cannot be used";
- }
-
- // Check for leading/trailing spaces or dots (Windows)
- if (trimmed.startsWith('.') || trimmed.endsWith('.') || trimmed.endsWith(' ')) {
- return "Name cannot start with a dot or end with a dot or space";
- }
-
- // Check for duplicate names in the same folder
- const siblingNames = children.map(c => c.name.toLowerCase());
- if (siblingNames.includes(trimmed.toLowerCase())) {
- return "A file or folder with this name already exists";
- }
-
- return null;
- };
-
const handleAdd = () => {
- const validationError = validateName(name, type === "file");
-
- if (validationError) {
- setError(validationError);
+ const result =
+ type === "file" ? addFile(name, parentId) : addFolder(name, parentId);
+ if (!result.ok) {
+ setError(result.error);
return;
}
-
- const trimmedName = name.trim();
- if (type === "file") {
- addFile(trimmedName, parentId);
- } else {
- addFolder(trimmedName, parentId);
- }
-
onClose();
};
@@ -110,10 +70,10 @@ export const AddItemPopup = ({ parentId, initialType = "file", onClose }: AddIte
};
return (
-
@@ -126,7 +86,7 @@ export const AddItemPopup = ({ parentId, initialType = "file", onClose }: AddIte
-
+
-
+
- {error && (
-
{error}
- )}
+ {error &&
{error}
}
{showRenamePopup && (
@@ -39,6 +50,17 @@ export const FileCom = ({ fileId }: { fileId: string }) => {
/>
)}
+
+ {showDeletePopup && (
+
+ setShowDeletePopup(false)}
+ />
+
+ )}
);
};
diff --git a/remote-ide/app/CodeEditor/[id]/FileExplorer/FileExplorer.tsx b/remote-ide/app/CodeEditor/[id]/FileExplorer/FileExplorer.tsx
index 6e156c4..bc56635 100644
--- a/remote-ide/app/CodeEditor/[id]/FileExplorer/FileExplorer.tsx
+++ b/remote-ide/app/CodeEditor/[id]/FileExplorer/FileExplorer.tsx
@@ -6,10 +6,18 @@ import { useState } from "react";
import { FilePlus, FolderPlus, Search } from "lucide-react";
import { FolderCom } from "./FolderCom";
import { AddItemPopup } from "./AddItemPopup";
+import { useFileStore } from "@/hooks/useFileStore";
export const FileExplorer = () => {
const [showPopup, setShowPopup] = useState(false);
const [popupType, setPopupType] = useState<"file" | "folder">("file");
+ const files = useFileStore((s) => s.files);
+ let root = files.find((f) => f.parentId === null);
+ if (!root) {
+ // additional check
+ root = files.find((f) => f.parentId === null);
+ if (!root) return null;
+ }
return (
@@ -18,15 +26,15 @@ export const FileExplorer = () => {
Files
- {
setPopupType("folder");
setShowPopup(true);
}}
/>
- {
setPopupType("file");
setShowPopup(true);
@@ -35,14 +43,14 @@ export const FileExplorer = () => {
{showPopup && (
-
setShowPopup(false)}
/>
)}
-
+
);
-};
\ No newline at end of file
+};
diff --git a/remote-ide/app/CodeEditor/[id]/FileExplorer/FolderCom.tsx b/remote-ide/app/CodeEditor/[id]/FileExplorer/FolderCom.tsx
index 17e5a52..e7dd0eb 100644
--- a/remote-ide/app/CodeEditor/[id]/FileExplorer/FolderCom.tsx
+++ b/remote-ide/app/CodeEditor/[id]/FileExplorer/FolderCom.tsx
@@ -1,16 +1,19 @@
+// FolderCom.tsx
"use client";
import { useState } from "react";
-import { Folder, ChevronDown, ChevronRight, Plus, Pencil } from "lucide-react";
+import { Folder, ChevronDown, ChevronRight, Plus, Pencil, Trash2 } from "lucide-react";
import { useFileStore } from "../../../../hooks/useFileStore";
import { FileCom } from "./FileCom";
import { AddItemPopup } from "./AddItemPopup";
import { RenamePopup } from "./RenamePopup";
+import { DeletePopup } from "./DeletePopup";
export const FolderCom = ({ folderId }: { folderId: string }) => {
const [open, setOpen] = useState(true);
- const [showPopup, setShowPopup] = useState(false);
+ const [showAddPopup, setShowAddPopup] = useState(false);
const [showRenamePopup, setShowRenamePopup] = useState(false);
+ const [showDeletePopup, setShowDeletePopup] = useState(false);
const { files } = useFileStore();
@@ -37,12 +40,20 @@ export const FolderCom = ({ folderId }: { folderId: string }) => {
setShowRenamePopup(true);
}}
/>
+
{
+ e.stopPropagation();
+ setShowDeletePopup(true);
+ }}
+ />
{
e.stopPropagation();
- setShowPopup(true);
+ setShowAddPopup(true);
}}
/>
@@ -60,11 +71,11 @@ export const FolderCom = ({ folderId }: { folderId: string }) => {
)}
- {showPopup && (
+ {showAddPopup && (
setShowPopup(false)}
+ onClose={() => setShowAddPopup(false)}
/>
)}
@@ -78,6 +89,17 @@ export const FolderCom = ({ folderId }: { folderId: string }) => {
/>
)}
+
+ {showDeletePopup && (
+
+ setShowDeletePopup(false)}
+ />
+
+ )}
);
};
diff --git a/remote-ide/app/CodeEditor/[id]/FileExplorer/RenamePopup.tsx b/remote-ide/app/CodeEditor/[id]/FileExplorer/RenamePopup.tsx
index 46d698f..3d5e66a 100644
--- a/remote-ide/app/CodeEditor/[id]/FileExplorer/RenamePopup.tsx
+++ b/remote-ide/app/CodeEditor/[id]/FileExplorer/RenamePopup.tsx
@@ -1,13 +1,11 @@
+// RenamePopup.tsx
+
"use client";
import { useState, useEffect, useRef } from "react";
import { X } from "lucide-react";
import { useFileStore } from "../../../../hooks/useFileStore";
-// Invalid characters for file/folder names (Windows + Unix)
-const INVALID_CHARS = /[<>:"/\\|?*\x00-\x1f]/;
-const INVALID_NAMES = ['CON', 'PRN', 'AUX', 'NUL', 'COM1', 'COM2', 'COM3', 'COM4', 'COM5', 'COM6', 'COM7', 'COM8', 'COM9', 'LPT1', 'LPT2', 'LPT3', 'LPT4', 'LPT5', 'LPT6', 'LPT7', 'LPT8', 'LPT9'];
-
interface RenamePopupProps {
itemId: string;
currentName: string;
@@ -54,58 +52,18 @@ export const RenamePopup = ({ itemId, currentName, onClose }: RenamePopupProps)
const item = files.find(f => f.id === itemId);
if (!item) return null;
- const parentId = item.parentId;
- const siblings = files.filter(f => f.parentId === parentId && f.id !== itemId);
-
- const validateName = (fileName: string): string | null => {
- const trimmed = fileName.trim();
-
- if (!trimmed) {
- return "Name cannot be empty";
- }
-
- if (trimmed.length > 255) {
- return "Name is too long (max 255 characters)";
- }
-
- if (INVALID_CHARS.test(trimmed)) {
- return "Name contains invalid characters: < > : \" / \\ | ? * and control characters";
- }
-
- // Check for invalid Windows names
- const nameUpper = trimmed.toUpperCase();
- if (INVALID_NAMES.includes(nameUpper) || INVALID_NAMES.some(invalid => nameUpper.startsWith(invalid + '.'))) {
- return "This name is reserved and cannot be used";
- }
-
- // Check for leading/trailing spaces or dots (Windows)
- if (trimmed.startsWith('.') || trimmed.endsWith('.') || trimmed.endsWith(' ')) {
- return "Name cannot start with a dot or end with a dot or space";
- }
-
- // Check for duplicate names in the same folder
- const siblingNames = siblings.map(c => c.name.toLowerCase());
- if (siblingNames.includes(trimmed.toLowerCase())) {
- return "A file or folder with this name already exists";
- }
-
- return null;
- };
-
const handleRename = () => {
if (name.trim() === currentName) {
onClose();
return;
}
- const validationError = validateName(name);
-
- if (validationError) {
- setError(validationError);
+ const result = renameFile(itemId, name);
+ if (!result.ok) {
+ setError(result.error);
return;
}
- renameFile(itemId, name.trim());
onClose();
};
diff --git a/remote-ide/app/CodeEditor/[id]/FileExplorer/types.ts b/remote-ide/app/CodeEditor/[id]/FileExplorer/types.ts
index fc44e0f..3e45112 100644
--- a/remote-ide/app/CodeEditor/[id]/FileExplorer/types.ts
+++ b/remote-ide/app/CodeEditor/[id]/FileExplorer/types.ts
@@ -1,39 +1,71 @@
// types.ts
-export type FileTree = {
- id: string;
- name: string;
- type: "Folder" | "File";
- code?: string;
- children?: FileTree[];
- };
-
- export type ProjectType = {
- project_id: string;
- name: string;
- type: string;
- date: Date;
- language: string;
- folder: FileTree;
- };
-
- export type UserType = {
- id: string;
- name: string;
- projects: ProjectType[];
- };
-
+// user
+export type UserType = {
+ id: string;
+ name: string;
+};
+// projects
+export type ProjectState = {
+ id: string
+ name: string
+ slug?: string
+ track: 'WebDevelopment' | 'SoftWareDevelopment'
+ isPrivate: boolean
+ createdAt: string
+}
+// files and folders
+export type DocumentNode = {
+ id: string
+ projectId: string
+ parentId: string | null
+ type: 'file' | 'folder'
+ name: string
+ extension: string | null
+ isDeleted: boolean
+}
+
+
+// folder tree made from doc node
+export type TreeNode = {
+ id: string
+ type: DocumentType
+ name: string
+ extension: string | null
+ children?: TreeNode[]
+}
+// file content
+export type FileContentState = {
+ fileId: string
+ content: string
+ language: string | null
+ isDirty: boolean
+ lastSavedAt: string | null
+}
+
+export type EditorState = {
+ activeFileId: string | null
+ openFileIds: string[]
+}
+
+export type ProjectWorkspaceState = {
+ project: ProjectState
+ documents: Record