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 new file mode 100644 index 0000000..c93657a --- /dev/null +++ b/remote-ide/app/CodeEditor/[id]/FileExplorer/AddItemPopup.tsx @@ -0,0 +1,133 @@ +//AddItemPopup.tsx + +"use client"; + +import { useState, useEffect, useRef } from "react"; +import { X } from "lucide-react"; +import { useFileStore } from "../../../../hooks/useFileStore"; + +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 { addFile, addFolder } = useFileStore(); + + // Focus input when popup opens + useEffect(() => { + if (inputRef.current) { + inputRef.current.focus(); + } + }, []); + + // 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(); + } + }; + + document.addEventListener("mousedown", handleClickOutside); + return () => document.removeEventListener("mousedown", handleClickOutside); + }, [onClose]); + + const handleAdd = () => { + const result = + type === "file" ? addFile(name, parentId) : addFolder(name, parentId); + if (!result.ok) { + setError(result.error); + return; + } + 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}
} + +
+ + +
+
+ ); +}; diff --git a/remote-ide/app/CodeEditor/[id]/FileExplorer/DeletePopup.tsx b/remote-ide/app/CodeEditor/[id]/FileExplorer/DeletePopup.tsx new file mode 100644 index 0000000..9b492b2 --- /dev/null +++ b/remote-ide/app/CodeEditor/[id]/FileExplorer/DeletePopup.tsx @@ -0,0 +1,100 @@ +// deletePopup.tsx + +"use client"; + +import { useEffect, useRef, useState } from "react"; +import { X } from "lucide-react"; +import { useFileStore } from "../../../../hooks/useFileStore"; + +interface DeletePopupProps { + itemId: string; + itemName: string; + itemType: "file" | "folder"; + onClose: () => void; +} + +export const DeletePopup = ({ + itemId, + itemName, + itemType, + onClose, +}: DeletePopupProps) => { + const [error, setError] = useState(""); + const popupRef = useRef(null); + const { deleteNode } = useFileStore(); + + useEffect(() => { + if (typeof window === "undefined") return; + + const handleClickOutside = (event: MouseEvent) => { + if (popupRef.current && !popupRef.current.contains(event.target as Node)) { + onClose(); + } + }; + + const handleKeyDown = (event: KeyboardEvent) => { + if (event.key === "Escape") { + onClose(); + } + }; + + document.addEventListener("mousedown", handleClickOutside); + window.addEventListener("keydown", handleKeyDown); + return () => { + document.removeEventListener("mousedown", handleClickOutside); + window.removeEventListener("keydown", handleKeyDown); + }; + }, [onClose]); + + const handleDelete = () => { + setError(""); + const result = deleteNode(itemId); + if (!result.ok) { + setError(result.error); + return; + } + onClose(); + }; + + const title = itemType === "file" ? "Delete File" : "Delete Folder"; + const description = + itemType === "folder" + ? `Delete \"${itemName}\" and all its contents?` + : `Delete \"${itemName}\"?`; + + return ( +
+
+ {title} + +
+ +
{description}
+ + {error &&
{error}
} + +
+ + +
+
+ ); +}; diff --git a/remote-ide/app/CodeEditor/[id]/FileExplorer/FileCom.tsx b/remote-ide/app/CodeEditor/[id]/FileExplorer/FileCom.tsx index e71e845..0cc8f73 100644 --- a/remote-ide/app/CodeEditor/[id]/FileExplorer/FileCom.tsx +++ b/remote-ide/app/CodeEditor/[id]/FileExplorer/FileCom.tsx @@ -1,11 +1,66 @@ -import { File } from "lucide-react"; -import { FileTree} from "./types"; +//FileCom.tsx +"use client"; + +import { useState } from "react"; +import { File, Pencil, Trash2 } from "lucide-react"; +import { useFileStore } from "../../../../hooks/useFileStore"; +import { RenamePopup } from "./RenamePopup"; +import { DeletePopup } from "./DeletePopup"; + +export const FileCom = ({ fileId }: { fileId: string }) => { + const [showRenamePopup, setShowRenamePopup] = useState(false); + const [showDeletePopup, setShowDeletePopup] = useState(false); + const { files, setActiveFile } = useFileStore(); + const file = files.find((f) => f.id === fileId); + + if (!file) return null; -export const FileCom = ({ data }: { data: FileTree }) => { return ( -
- - {data.name} +
+
setActiveFile(fileId)} + > + + {file.name} + { + e.stopPropagation(); + setShowRenamePopup(true); + }} + /> + { + e.stopPropagation(); + setShowDeletePopup(true); + }} + /> +
+ + {showRenamePopup && ( +
+ setShowRenamePopup(false)} + /> +
+ )} + + {showDeletePopup && ( +
+ setShowDeletePopup(false)} + /> +
+ )}
); }; diff --git a/remote-ide/app/CodeEditor/[id]/FileExplorer/FileExplorer.tsx b/remote-ide/app/CodeEditor/[id]/FileExplorer/FileExplorer.tsx index 97352b1..bc56635 100644 --- a/remote-ide/app/CodeEditor/[id]/FileExplorer/FileExplorer.tsx +++ b/remote-ide/app/CodeEditor/[id]/FileExplorer/FileExplorer.tsx @@ -1,21 +1,56 @@ +"use client"; + +// FileExplorer.tsx + +import { useState } from "react"; import { FilePlus, FolderPlus, Search } from "lucide-react"; import { FolderCom } from "./FolderCom"; -import { Folder_Example,FileTree } from "./types"; +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 (
-
+
- Files + Files
- - + { + setPopupType("folder"); + setShowPopup(true); + }} + /> + { + setPopupType("file"); + setShowPopup(true); + }} + />
+ {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 cd9e2ab..e7dd0eb 100644 --- a/remote-ide/app/CodeEditor/[id]/FileExplorer/FolderCom.tsx +++ b/remote-ide/app/CodeEditor/[id]/FileExplorer/FolderCom.tsx @@ -1,95 +1,103 @@ +// FolderCom.tsx +"use client"; + import { useState } from "react"; -import { Folder, ChevronDown, ChevronRight, Plus } from "lucide-react"; -import { FileTree } from "./types"; +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 = ({ data }: { data: FileTree }) => { +export const FolderCom = ({ folderId }: { folderId: string }) => { const [open, setOpen] = useState(true); - const [showPopup, setShowPopup] = useState(false); - const [newItemName, setNewItemName] = useState(""); - const [newItemType, setNewItemType] = useState("File"); - - const add_To_FileTree = () => { - setShowPopup(true); - }; + const [showAddPopup, setShowAddPopup] = useState(false); + const [showRenamePopup, setShowRenamePopup] = useState(false); + const [showDeletePopup, setShowDeletePopup] = useState(false); - const handleAddItem = () => { - if (!newItemName.trim()) return; + const { files } = useFileStore(); - const newItem: FileTree = { - id: Math.random().toString(36), - name: newItemName, - type: newItemType as "Folder" | "File", - children: [], - }; + const children = files.filter((f) => f.parentId === folderId); + const folder = files.find((f) => f.id === folderId); - data.children?.push(newItem); - setNewItemName(""); - setShowPopup(false); - }; + if (!folder) return null; return ( -
+
setOpen(!open)} - onAuxClick={add_To_FileTree} - className="cursor-pointer hover:bg-blue-900/50 p-1 rounded flex gap-1 items-center transition" > - {open ? ( - - ) : ( - - )} - - {data.name} - + {open ? : } + + {folder.name} +
+ { + e.stopPropagation(); + setShowRenamePopup(true); + }} + /> + { + e.stopPropagation(); + setShowDeletePopup(true); + }} + /> + { + e.stopPropagation(); + setShowAddPopup(true); + }} + /> +
+ {open && ( -
- {data.children?.map((child) => { - if (child.type === "Folder") { - return ; - } else { - return ; - } - })} +
+ {children.map((child) => + child.type === "folder" ? ( + + ) : ( + + ) + )} +
+ )} + + {showAddPopup && ( +
+ setShowAddPopup(false)} + /> +
+ )} + + {showRenamePopup && ( +
+ setShowRenamePopup(false)} + />
)} - {showPopup && ( -
- setNewItemName(e.target.value)} - className="p-1 rounded text-black" + {showDeletePopup && ( +
+ setShowDeletePopup(false)} /> - -
- - -
)}
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..3d5e66a --- /dev/null +++ b/remote-ide/app/CodeEditor/[id]/FileExplorer/RenamePopup.tsx @@ -0,0 +1,130 @@ +// RenamePopup.tsx + +"use client"; + +import { useState, useEffect, useRef } from "react"; +import { X } from "lucide-react"; +import { useFileStore } from "../../../../hooks/useFileStore"; + +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(() => { + // Only run on client + if (typeof window === "undefined") return; + + 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 handleRename = () => { + if (name.trim() === currentName) { + onClose(); + return; + } + + const result = renameFile(itemId, name); + if (!result.ok) { + setError(result.error); + return; + } + + 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}
+ )} + +
+ + +
+
+ ); +}; + 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 + tree: TreeNode + files: Record + editor: EditorState +} export const Folder_Example = { - "id": "root", - "name": "SDE", - "type": "Folder", - "children": [ - { - "id": "main.cpp", - "name": "main.cpp", - "type": "File" - }, - ] + "id": "root", + "name": "SDE", + "type": "Folder", + "children": [ + { + "id": "main.cpp", + "name": "main.cpp", + "type": "File" + }, + ] } \ No newline at end of file diff --git a/remote-ide/app/CodeEditor/[id]/Header.tsx b/remote-ide/app/CodeEditor/[id]/Header.tsx index 53b28d4..0737014 100644 --- a/remote-ide/app/CodeEditor/[id]/Header.tsx +++ b/remote-ide/app/CodeEditor/[id]/Header.tsx @@ -1,10 +1,6 @@ -import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, -} from "@/components/ui/select"; +// Header.tsx +"use client" + import { Tooltip, TooltipContent, @@ -12,48 +8,51 @@ import { } from "@/components/ui/tooltip"; import { Play, Settings, Code2, BotMessageSquare } from "lucide-react"; import { useState } from "react"; -import { HeaderProps } from "./page"; import { ExecuteCode } from "./api"; -import ExecuteCodecloud from "./CloudApi"; -import { LANGUAGE_VERSIONS } from "./constants"; import Link from "next/link"; import { Chatbot } from "./chatbot"; +import { RUN_OPEN_FILE_WARNNING } from "./constants"; -export const languageOptions = Object.keys(LANGUAGE_VERSIONS).map((key) => ({ - value: key, - label: `${key.charAt(0).toUpperCase() + key.slice(1)} (${ - LANGUAGE_VERSIONS[key as keyof typeof LANGUAGE_VERSIONS] - })`, -})); +interface HeaderProps { + code: string; + language: string; + fileName?: string; + setoutput: (output: string) => void; + seterror: (error: boolean) => void; +} export const Header = ({ code, - setLanguage, - Language, + language, + fileName, setoutput, seterror, }: HeaderProps) => { const [loading, setLoading] = useState(false); - const [alert, setAlert] = useState(false); const [AIhelp, setAIhelp] = useState(false); + const isRunnable = Boolean(language); const toggleAIHelp = () => { setAIhelp(!AIhelp); }; + + const getExtensionFromFileName = (name?: string) => { + if (!name) return ""; + const idx = name.lastIndexOf("."); + if (idx < 0) return ""; + return name.slice(idx + 1).toLowerCase(); + }; + const handleRun = async () => { - if (!Language || !code) { + if (!isRunnable || !code) { seterror(true); - setAlert(true); - setTimeout(() => { - setAlert(false); - }, 3000); - setoutput("Please select a language and write some code"); + setoutput(RUN_OPEN_FILE_WARNNING); return; } setLoading(true); try { // @ts-ignore - const Sourcecode = await ExecuteCode(Language, code, seterror); + const Sourcecode = await ExecuteCode(language as any, code, seterror); setoutput(Sourcecode); seterror(false); } catch (error) { @@ -64,28 +63,8 @@ export const Header = ({ } }; const handleRuncloud = async () => { - if (!Language || !code) { - seterror(true); - setAlert(true); - setTimeout(() => { - setAlert(false); - }, 3000); - setoutput("Please select a language and write some code"); - return; - } - setLoading(true); - try { - console.log(code); - // @ts-ignore - const outputcode = await ExecuteCodecloud(Language, code, seterror); - setoutput(outputcode); - seterror(false); - } catch (error) { - setoutput("Error: " + error); - seterror(true); - } finally { - setLoading(false); - } + // LOGIC FOR HITTING THE Backend to hit cloud spin up some containers + // and return stdio / stderr }; return (
@@ -111,22 +90,6 @@ export const Header = ({
- - diff --git a/remote-ide/app/CodeEditor/[id]/api.tsx b/remote-ide/app/CodeEditor/[id]/api.tsx index 033dc5d..558bc0d 100644 --- a/remote-ide/app/CodeEditor/[id]/api.tsx +++ b/remote-ide/app/CodeEditor/[id]/api.tsx @@ -1,7 +1,7 @@ import axios from "axios"; import { LANGUAGE_VERSIONS } from "./constants"; -const ExecuteCodeAPI = axios.create({ +const ExecuteCodeapi = axios.create({ baseURL: "https://emkc.org/api/v2/piston", }); @@ -22,7 +22,7 @@ export const ExecuteCode = async ( version: LANGUAGE_VERSIONS[language], files: [{ content: SourceCode }], }) - const response = await ExecuteCodeAPI.post("/execute", { + const response = await ExecuteCodeapi.post("/execute", { language, version: LANGUAGE_VERSIONS[language], files: [{ content: SourceCode }], diff --git a/remote-ide/app/CodeEditor/[id]/chatbot.tsx b/remote-ide/app/CodeEditor/[id]/chatbot.tsx index b4a9ec4..8c9d54b 100644 --- a/remote-ide/app/CodeEditor/[id]/chatbot.tsx +++ b/remote-ide/app/CodeEditor/[id]/chatbot.tsx @@ -55,8 +55,9 @@ export function Chatbot({ code ,toggleAIHelp}: ChatbotProps) { setMessages((prev) => [...prev, newMessage]); + const thinkingId = messages.length + 2; const thinkingMessage: Message = { - id: messages.length + 2, + id: thinkingId, text: "Thinking...", sender: "ai", timestamp: new Date(), @@ -71,12 +72,12 @@ export function Chatbot({ code ,toggleAIHelp}: ChatbotProps) { const aiMessage: Message = { id: messages.length + 3, - text: AIresponse, + text: typeof AIresponse === "string" ? AIresponse : String(AIresponse), sender: "ai", timestamp: new Date(), }; - setMessages((prev) => [...prev, aiMessage]); + setMessages((prev) => [...prev.filter((m) => m.id !== thinkingId), aiMessage]); setInputMessage(""); setLoading(false); }; @@ -128,7 +129,7 @@ export function Chatbot({ code ,toggleAIHelp}: ChatbotProps) { > {/* Using ReactMarkdown to render message.*/} - {message.text} + {typeof message.text === "string" ? message.text : String(message.text)}
diff --git a/remote-ide/app/CodeEditor/[id]/constants.ts b/remote-ide/app/CodeEditor/[id]/constants.ts index fd1e07f..07c79af 100644 --- a/remote-ide/app/CodeEditor/[id]/constants.ts +++ b/remote-ide/app/CodeEditor/[id]/constants.ts @@ -1,3 +1,9 @@ +export const DEFAULT_TEXT_FOR_FILE = "Terminal output will appear here..." +export const DEFAULT_WARNING_UNKNOWN_FILES = "Warning: Unsupported or missing file extension. This file will be treated as plain text and cannot be run." +export const PLAINTEXT ="plaintext" +export const OPEN_FILE_WARNNING ="Please open a file." +export const RUN_OPEN_FILE_WARNNING ="Please open a file and write some code" + export const LANGUAGE_VERSIONS = { cpp: "10.2.0", javascript: "18.15.0", @@ -12,12 +18,13 @@ export const LANGUAGE_VERSIONS = { kotlin: "1.8.20", swift: "5.3.3", sqlite3: "3.36.0", - c:"10.2.0", + c: "10.2.0", powershell: "7.1.4", bash: "5.2.0", matl: "22.7.4", dart: "2.19.6", }; + export const CODE_SNIPPETS = { cpp: `#include \n\nint main() {\n\tstd::cout << "Hello, Avan!" << std::endl;\n\treturn 0;\n}\n`, javascript: `function greet(name) {\n\tconsole.log("Hello, " + name + "!");\n}\n\ngreet("Avan");\n`, @@ -40,27 +47,27 @@ export const CODE_SNIPPETS = { }; export const FILE_NAMES = { - cpp: "program.cpp", - javascript: "app.js", - python: "script.py", - java: "MainClass.java", - csharp: "Program.cs", - typescript: "app.ts", - php: "index.php", - go: "main.go", - ruby: "program.rb", - rust: "main.rs", - kotlin: "Application.kt", - swift: "App.swift", - sqlite3: "queries.sql", - c: "main.c", - powershell: "script.ps1", - bash: "script.sh", - matl: "function.m", - dart: "main.dart", + cpp: "program.cpp", + javascript: "app.js", + python: "script.py", + java: "MainClass.java", + csharp: "Program.cs", + typescript: "app.ts", + php: "index.php", + go: "main.go", + ruby: "program.rb", + rust: "main.rs", + kotlin: "Application.kt", + swift: "App.swift", + sqlite3: "queries.sql", + c: "main.c", + powershell: "script.ps1", + bash: "script.sh", + matl: "function.m", + dart: "main.dart", }; -export const THEMES=[ +export const THEMES = [ "vs-dark", "vs-light", "hc-black" diff --git a/remote-ide/app/CodeEditor/[id]/layout.tsx b/remote-ide/app/CodeEditor/[id]/layout.tsx index e70cf23..4b3e7a5 100644 --- a/remote-ide/app/CodeEditor/[id]/layout.tsx +++ b/remote-ide/app/CodeEditor/[id]/layout.tsx @@ -1,20 +1,19 @@ -"use client"; +"use client"; import { TooltipProvider } from "@radix-ui/react-tooltip"; import { motion } from "framer-motion"; export default function Layout({ children }: { children: React.ReactNode }) { return ( - - - - {children} - + + {children} + ); } diff --git a/remote-ide/app/CodeEditor/[id]/page.tsx b/remote-ide/app/CodeEditor/[id]/page.tsx index 6696dcf..3440f7a 100644 --- a/remote-ide/app/CodeEditor/[id]/page.tsx +++ b/remote-ide/app/CodeEditor/[id]/page.tsx @@ -1,48 +1,114 @@ +// CodeEditor/page.tsx + "use client"; import { useState, useRef, useEffect } from "react"; import { Panel, PanelGroup, PanelResizeHandle } from "react-resizable-panels"; -import { - Terminal -} from "lucide-react"; +import { Terminal } from "lucide-react"; import Editor from "@monaco-editor/react"; import { Header } from "./Header"; -import { CODE_SNIPPETS, FILE_NAMES, THEMES } from "./constants"; +import { + CODE_SNIPPETS, + DEFAULT_TEXT_FOR_FILE, + DEFAULT_WARNING_UNKNOWN_FILES, + FILE_NAMES, + OPEN_FILE_WARNNING, + PLAINTEXT, + THEMES, +} from "./constants"; import { Button } from "@/components/ui/button"; import { FileExplorer } from "./FileExplorer/FileExplorer"; - -export interface HeaderProps { - code: string; - setLanguage: React.Dispatch>; - Language: string; - setoutput: (output: string) => void; - seterror: (error: boolean) => void; -} +import { useFileStore } from "@/hooks/useFileStore"; +import { useEditorStore } from "@/hooks/useEditorStore"; +import { useParams } from "next/navigation"; export default function CodeEditor() { - const [Language, setLanguage] = useState(""); - const [code, setCode] = useState(""); + const editorRef = useRef(null); - const [output, setOutput] = useState("Terminal output will appear here..."); + const [output, setOutput] = useState(DEFAULT_TEXT_FOR_FILE); const [error, setError] = useState(false); - useEffect(() => { - //@ts-ignore - setCode(CODE_SNIPPETS[Language]); - }, [Language]); + const activeFileId = useFileStore((state) => state.activeFileId); + const resetFileStore = useFileStore(s => s.reset); + const files = useFileStore((state) => state.files); + const hydrateFromBackend = useFileStore((state) => state.hydrateFromBackend); + + const { contents, updateContent } = useEditorStore(); + const { id: projectId } = useParams<{ id: string }>(); + + const code = activeFileId ? contents[activeFileId] ?? "" : ""; + // Get the active file + const activeFile = activeFileId + ? files.find((f) => f.id === activeFileId) + : null; + function handleEditorDidMount(editor: any) { editorRef.current = editor; editor.focus(); } + const language = activeFile?.language ?? ""; + const editorLanguage = language || PLAINTEXT; + const isRunnable = Boolean(language); + + useEffect(() => { + if (!activeFileId) return; + + if (!activeFile) { + setError(false); + setOutput(OPEN_FILE_WARNNING); + return; + } + + if (!isRunnable) { + setError(true); + setOutput(DEFAULT_WARNING_UNKNOWN_FILES); + } + }, [activeFileId, activeFile?.name, isRunnable]); + + // Initialize code snippet when file is first opened + useEffect(() => { + if (!activeFileId || !activeFile) return; + + // Only initialize if file has no content and has a language + if (contents[activeFileId] === undefined && activeFile.language) { + const snippet = + CODE_SNIPPETS[activeFile.language as keyof typeof CODE_SNIPPETS] ?? ""; + if (snippet) { + updateContent(activeFileId, snippet); + } + } + }, [activeFileId, activeFile?.language]); + + useEffect(() => { + if (!projectId) return; + + resetFileStore(); // flush old folder structure + + const loadProject = async () => { + try { + const res = await fetch(`/api/projects/${projectId}`); + if (!res.ok) return; + + const data = await res.json(); + console.log(data); + hydrateFromBackend(data.tree); + } catch (err) { + console.error("Failed to load project", err); + } + }; + + loadProject(); + }, [projectId]); + return (
@@ -54,7 +120,7 @@ export default function CodeEditor() { minSize={20} className="bg-gray-900 bg-gradient-to-br from-[#000000] via-[#0A0A0A] to-[#000000] rounded-lg shadow-md border border-blue-900" > - + {/* Main Editor Panel updated with gradient, shadow, and side borders */} @@ -65,19 +131,24 @@ export default function CodeEditor() { >
setCode(value || "")} + onChange={(val) => + activeFileId && updateContent(activeFileId, val || "") + } options={{ minimap: { enabled: false }, padding: { bottom: 4, top: 6 }, diff --git a/remote-ide/app/DashBoard/CreateProject.tsx b/remote-ide/app/DashBoard/CreateProject.tsx index 51d9019..85dc91f 100644 --- a/remote-ide/app/DashBoard/CreateProject.tsx +++ b/remote-ide/app/DashBoard/CreateProject.tsx @@ -1,3 +1,5 @@ +"use client"; + import { useState } from "react"; import { Button } from "@/components/ui/button"; import { @@ -18,48 +20,45 @@ import { SelectValue, } from "@/components/ui/select"; import { Plus } from "lucide-react"; -import { TProjectCard } from "./types"; -import { languageOptions } from "../CodeEditor/[id]/Header"; import { useToast } from "@/hooks/use-toast"; -import { randomUUID } from "crypto"; -import { Folder_Example } from "../CodeEditor/[id]/FileExplorer/types"; interface CreateProjectDialogProps { - onCreateProject: (project: TProjectCard) => void; + onCreateProject: (data: { + name: string; + track: "WebDevelopment" | "SoftWareDevelopment"; + }) => void; } export function CreateProjectDialog({ onCreateProject, }: CreateProjectDialogProps) { - const { toast } = useToast(); // Toast function - const [open, setOpen] = useState(false); // Dialog state + const { toast } = useToast(); + const [open, setOpen] = useState(false); const [name, setName] = useState(""); - const [type, setType] = useState<"SoftwareDev" | "WebDev">("WebDev"); - const [language, setLanguage] = useState("React"); + const [track, setTrack] = useState<"WebDevelopment" | "SoftWareDevelopment">( + "WebDevelopment" + ); const handleSubmit = () => { - const newProject = { - name, - type, - date: new Date(), - language, - folder: Folder_Example - }; + if (!name.trim()) { + toast({ + title: "Invalid name", + description: "Project name cannot be empty", + variant: "destructive", + }); + return; + } - onCreateProject(newProject); - console.log(newProject); + onCreateProject({ name, track }); - // Show success toast toast({ title: "Project Created", - description: `New project "${name}" added successfully.`, + description: `Project "${name}" created successfully.`, }); - // Close dialog and reset fields setOpen(false); setName(""); - setType("WebDev"); - setLanguage("React"); + setTrack("WebDevelopment"); }; return ( @@ -72,14 +71,15 @@ export function CreateProjectDialog({ Create Project + Create New Project - Fill in the details for your new project. Click create when you're - done. + Provide basic details for your new project. +
+
-
-
- - -
+ + + ); + } + return ( <> - {filteredProjects.length === 0 ? ( - -

- No Recent Projects -

-

- You haven't created any projects yet. Click below to start a new one! -

- -
- ) : ( - filteredProjects.map((project) => ( - - {/* Metallic Shine */} -
-
+ {filteredProjects.map((project) => ( + +
+
-
-

- {project.name} -

-
-
- - {project.type} - - - {project.language} - -
-
- - {new Date(project.date).toLocaleDateString("en-US", { - year: "numeric", - month: "short", - day: "numeric", - })} - - +

+ {project.name} +

+ +
+
+ + {project.track} + +
+ +
+ + {new Date(project.createdAt).toLocaleDateString("en-US", { + year: "numeric", + month: "short", + day: "numeric", + })} + + + + - -
+ Open + +
- - )) - )} +
+ + ))} ); }; diff --git a/remote-ide/app/DashBoard/page.tsx b/remote-ide/app/DashBoard/page.tsx index 12a34fd..d70c515 100644 --- a/remote-ide/app/DashBoard/page.tsx +++ b/remote-ide/app/DashBoard/page.tsx @@ -1,29 +1,43 @@ "use client"; -import { useState } from "react"; -import { Search, FileCode2, Command} from "lucide-react"; +import { useEffect, useState } from "react"; +import { Search, FileCode2, Command } from "lucide-react"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { ProjectCard } from "./ProjectCard"; import { SideBar } from "./sidebar"; import Link from "next/link"; +import { useUserStore } from "@/hooks/useUserStore"; import { TProjectCard } from "./types"; -import { projectcard_example } from "@/components/Constants/constants"; -import { useUserdata } from "../context/UserDataContext"; - export default function Dashboard() { const [searchQuery, setSearchQuery] = useState(""); - const { user, setUser } = useUserdata(); - - const [projects, setProjects] = useState(user?.projects || projectcard_example); - + const { user, setUser } = useUserStore(); + // todo + const [projects, setProjects] = useState([]); + + useEffect(() => { + const init = async () => { + try { + const userRes = await fetch("/api/user", { method: "POST" }); + const userData = await userRes.json(); + setUser(userData); + const projectRes = await fetch("/api/projects"); + const projectData = await projectRes.json(); + setProjects(projectData); + } catch (err) { + console.error("Dashboard init failed:", err); + } + }; + + init(); + }, [setUser]); return (
{/* Top Navigation */}
diff --git a/remote-ide/app/WebDev/[id]/layout.tsx b/remote-ide/app/WebEditor/[id]/layout.tsx similarity index 100% rename from remote-ide/app/WebDev/[id]/layout.tsx rename to remote-ide/app/WebEditor/[id]/layout.tsx diff --git a/remote-ide/app/WebDev/[id]/page.tsx b/remote-ide/app/WebEditor/[id]/page.tsx similarity index 96% rename from remote-ide/app/WebDev/[id]/page.tsx rename to remote-ide/app/WebEditor/[id]/page.tsx index 0d9870f..a44dbac 100644 --- a/remote-ide/app/WebDev/[id]/page.tsx +++ b/remote-ide/app/WebEditor/[id]/page.tsx @@ -11,9 +11,7 @@ import { FileExplorer } from "./FileExplorer/FileExplorer"; import { LiveProvider, LiveEditor, LivePreview, LiveError } from "react-live"; import React from "react"; import { default_code } from "@/components/Constants/constants"; -import { FileTree } from "./FileExplorer/types"; -import { useUserdata } from "@/app/context/UserDataContext"; -import { useParams } from "next/navigation"; + export default function WebDev() { diff --git a/remote-ide/app/context/UserDataContext.tsx b/remote-ide/app/context/UserDataContext.tsx deleted file mode 100644 index c4f4dca..0000000 --- a/remote-ide/app/context/UserDataContext.tsx +++ /dev/null @@ -1,31 +0,0 @@ -"use client"; - -import { createContext, useContext, useState, ReactNode } from "react"; -import { UserType } from "../CodeEditor/[id]/FileExplorer/types"; - -// shape of context value -type UserContextType = { - user: UserType | null; - setUser: (user: UserType) => void; -}; - -// Create the context with default undefined -const UserContext = createContext(undefined); - -// Provider -export const UserProvider = ({ children }: { children: ReactNode }) => { - const [user, setUser] = useState(null); - - return ( - - {children} - - ); -}; - -// Hook to use context -export const useUserdata = () => { - const context = useContext(UserContext); - if (!context) throw new Error("useUser must be used inside UserProvider"); - return context; -}; diff --git a/remote-ide/app/layout.tsx b/remote-ide/app/layout.tsx index 42c45f7..f2bfc0e 100644 --- a/remote-ide/app/layout.tsx +++ b/remote-ide/app/layout.tsx @@ -4,7 +4,6 @@ import { ClerkProvider } from "@clerk/nextjs"; import { Geist, Geist_Mono } from "next/font/google"; import "./globals.css"; import { motion } from "framer-motion"; -import { UserProvider } from "./context/UserDataContext"; // ✅ Make sure path is correct const geistSans = Geist({ variable: "--font-geist-sans", @@ -27,16 +26,14 @@ export default function RootLayout({ - - - {children} - - + + {children} + diff --git a/remote-ide/app/page.tsx b/remote-ide/app/page.tsx index 3981657..205ea9d 100644 --- a/remote-ide/app/page.tsx +++ b/remote-ide/app/page.tsx @@ -2,7 +2,7 @@ import Footer from "@/components/elements/Footer"; import Hero from "@/components/elements/Hero"; -import Hero_4 from "@/components/elements/Hero2"; +import Hero_4 from "@/components/elements/Hero2"; import Hero_3 from "@/components/elements/Hero4"; import Hero_1 from "@/components/elements/Hero5"; import Hero_2 from "@/components/elements/Hero7"; diff --git a/remote-ide/components/Constants/constants.ts b/remote-ide/components/Constants/constants.ts index c3af2dc..896c5e4 100644 --- a/remote-ide/components/Constants/constants.ts +++ b/remote-ide/components/Constants/constants.ts @@ -136,7 +136,7 @@ export const projectcard_example = [ }, { id: "4", - name: "Task Management API", + name: "Task Management api", type: "SoftwareDev", language: "Node.js", date: new Date("2024-03-10"), diff --git a/remote-ide/components/elements/ChatbotRes.tsx b/remote-ide/components/elements/ChatbotRes.tsx index cd4773b..d4cbe6e 100644 --- a/remote-ide/components/elements/ChatbotRes.tsx +++ b/remote-ide/components/elements/ChatbotRes.tsx @@ -1,15 +1,15 @@ import axios from "axios"; -const API_URL = +const api_URL = "https://generativelanguage.googleapis.com/v1/models/gemini-1.5-flash:generateContent"; -const API_KEY = "AIzaSyBCVruyIS0egMeoZOOMiqbexLHOavEau8c"; // Replace with your actual API key +const api_KEY = "AIzaSyBCVruyIS0egMeoZOOMiqbexLHOavEau8c"; // Replace with your actual api key export const fetchAIResponse = async ({ prompt }: { prompt: string }) => { console.log("in"); try { const response = await axios.post( - `${API_URL}?key=${API_KEY}`, + `${api_URL}?key=${api_KEY}`, { contents: [ { @@ -26,7 +26,23 @@ export const fetchAIResponse = async ({ prompt }: { prompt: string }) => { console.log(response.data); return response.data.candidates[0].content.parts[0].text; - } catch (error) { - return error; + } catch (error: unknown) { + if (axios.isAxiosError(error)) { + const status = error.response?.status; + if (status === 429) { + return "Rate limit reached . Please wait a bit and try again."; + } + + const apiMessage = + (error.response?.data as any)?.error?.message || + (error.response?.data as any)?.message; + return `Request failed${status ? ` (${status})` : ""}: ${apiMessage || error.message}`; + } + + if (error instanceof Error) { + return `Error: ${error.message}`; + } + + return "Error: Unknown error occurred."; } }; diff --git a/remote-ide/components/elements/Footer.tsx b/remote-ide/components/elements/Footer.tsx index c91a324..4b237ee 100644 --- a/remote-ide/components/elements/Footer.tsx +++ b/remote-ide/components/elements/Footer.tsx @@ -24,7 +24,7 @@ const Footer = () => { {/* Resources */}

Resources

- {['Documentation', 'API Reference', 'Code Snippets', 'Developer Blog', 'Community Forum'].map((item, i) => ( + {['Documentation', 'api Reference', 'Code Snippets', 'Developer Blog', 'Community Forum'].map((item, i) => (

{item}

))}
diff --git a/remote-ide/components/elements/Hero2.tsx b/remote-ide/components/elements/Hero2.tsx index e746cf3..f6b5d37 100644 --- a/remote-ide/components/elements/Hero2.tsx +++ b/remote-ide/components/elements/Hero2.tsx @@ -41,7 +41,7 @@ const Hero2 = () => { arr.size() - 1; {"\n\n"}   while (left - >= right) {"{\n"} + <= right) {"{\n"}     int mid = left + (right - left) /{" "} 2;{"\n\n"} diff --git a/remote-ide/components/elements/Navbar.tsx b/remote-ide/components/elements/Navbar.tsx index 8da542a..f78c2ef 100644 --- a/remote-ide/components/elements/Navbar.tsx +++ b/remote-ide/components/elements/Navbar.tsx @@ -9,16 +9,16 @@ import Link from "next/link"; import { useClerk, useUser } from "@clerk/nextjs"; import { useState, useEffect } from "react"; import { Avatar, AvatarFallback } from "@/components/ui/avatar"; -import { useUserdata } from "../../app/context/UserDataContext"; import axios from "axios"; -import { User, User2Icon } from "lucide-react"; +import { User2Icon } from "lucide-react"; +import { useUserStore } from "@/hooks/useUserStore"; export default function Navbar() { const clerk = useClerk(); const [showRedirectPopup, setShowRedirectPopup] = useState(false); const [showWelcomePopup, setShowWelcomePopup] = useState(false); const { user: clerkUser, isSignedIn } = useUser(); - const { user: contextUser, setUser } = useUserdata(); + const { user, setUser, clearUser } = useUserStore(); const handleStartBuilding = (e: React.MouseEvent) => { if (!isSignedIn) { @@ -32,11 +32,10 @@ export default function Navbar() { }; const DBcall = async () => { - console.log("🔥 DBcall triggered"); + console.log("DBcall triggered"); try { if (clerkUser && clerkUser.id) { - const response = await axios.post("/Api/User", { - userId: clerkUser.id, + const response = await axios.post("/api/user", { name: clerkUser.firstName, }); @@ -52,6 +51,7 @@ export default function Navbar() { const logouthandler = async () => { try { + clearUser(); await clerk.signOut(); } catch (error) { console.error("Error signing out:", error); @@ -61,7 +61,7 @@ export default function Navbar() { useEffect(() => { const runEffect = async () => { if (isSignedIn) { - await DBcall(); + await DBcall(); setShowWelcomePopup(true); setTimeout(() => setShowWelcomePopup(false), 1000); } @@ -77,7 +77,7 @@ export default function Navbar() {