diff --git a/gridwalk-ui/src/app/workspace/[workspaceId]/addMemberModal.tsx b/gridwalk-ui/src/app/workspace/[workspaceId]/addMemberModal.tsx index 053817e..1aea694 100644 --- a/gridwalk-ui/src/app/workspace/[workspaceId]/addMemberModal.tsx +++ b/gridwalk-ui/src/app/workspace/[workspaceId]/addMemberModal.tsx @@ -1,12 +1,7 @@ "use client"; import React, { useState } from "react"; import { useRouter } from "next/navigation"; - -interface AddWorkspaceMemberModalProps { - isOpen: boolean; - onClose: () => void; - onSubmit: (email: string, role: "Admin" | "Read") => Promise; -} +import { AddWorkspaceMemberModalProps } from "./types"; export const AddWorkspaceMemberModal: React.FC< AddWorkspaceMemberModalProps diff --git a/gridwalk-ui/src/app/workspace/[workspaceId]/projectModal.tsx b/gridwalk-ui/src/app/workspace/[workspaceId]/projectModal.tsx index 44af702..0e773b8 100644 --- a/gridwalk-ui/src/app/workspace/[workspaceId]/projectModal.tsx +++ b/gridwalk-ui/src/app/workspace/[workspaceId]/projectModal.tsx @@ -1,6 +1,6 @@ "use client"; import React, { useState } from "react"; -import { CreateProjectModalProps } from "./types"; +import { CreateProjectModalProps, DeleteProjectModalProps } from "./types"; export const CreateProjectModal: React.FC = ({ isOpen, @@ -20,7 +20,11 @@ export const CreateProjectModal: React.FC = ({ onClose(); setProjectName(""); } catch (err) { - setError(err instanceof Error ? err.message : "An error occurred"); + setError( + err instanceof Error + ? err.message + : "An error occurred creating a new project" + ); } finally { setIsLoading(false); } @@ -96,13 +100,6 @@ export const CreateProjectModal: React.FC = ({ ); }; -interface DeleteProjectModalProps { - isOpen: boolean; - onClose: () => void; - projectName: string; - onConfirm: () => Promise; -} - export const DeleteProjectModal: React.FC = ({ isOpen, onClose, @@ -112,13 +109,13 @@ export const DeleteProjectModal: React.FC = ({ const [isLoading, setIsLoading] = useState(false); const [error, setError] = useState(null); const [confirmText, setConfirmText] = useState(""); - + const isDeleteEnabled = confirmText === "DELETE"; const handleDelete = async (e: React.FormEvent) => { e.preventDefault(); if (!isDeleteEnabled) return; - + setIsLoading(true); setError(null); diff --git a/gridwalk-ui/src/app/workspace/[workspaceId]/types.ts b/gridwalk-ui/src/app/workspace/[workspaceId]/types.ts index 5a23892..32014be 100644 --- a/gridwalk-ui/src/app/workspace/[workspaceId]/types.ts +++ b/gridwalk-ui/src/app/workspace/[workspaceId]/types.ts @@ -11,3 +11,28 @@ export interface CreateProjectModalProps { onClose: () => void; onSubmit: (name: string) => Promise; } + +export interface DeleteProjectModalProps { + isOpen: boolean; + onClose: () => void; + projectName: string; + onConfirm: () => Promise; +} + +export interface AddWorkspaceMemberModalProps { + isOpen: boolean; + onClose: () => void; + onSubmit: (email: string, role: "Admin" | "Read") => Promise; +} + +export interface ViewWorkspaceConnectionsModalProps { + isOpen: boolean; + onClose: () => void; + workspaceId: string; +} + +export interface ViewWorkspaceMemberModalProps { + isOpen: boolean; + onClose: () => void; + workspaceId: string; +} diff --git a/gridwalk-ui/src/app/workspace/[workspaceId]/viewConnectionsModal.tsx b/gridwalk-ui/src/app/workspace/[workspaceId]/viewConnectionsModal.tsx index abb5d2d..90e8f29 100644 --- a/gridwalk-ui/src/app/workspace/[workspaceId]/viewConnectionsModal.tsx +++ b/gridwalk-ui/src/app/workspace/[workspaceId]/viewConnectionsModal.tsx @@ -1,17 +1,12 @@ "use client"; import React, { useEffect, useState } from "react"; import { Database, Loader2 } from "lucide-react"; +import { ViewWorkspaceConnectionsModalProps } from "./types"; import { getWorkspaceConnections, type WorkspaceConnection, } from "./actions/workspace/get_connections"; -interface ViewWorkspaceConnectionsModalProps { - isOpen: boolean; - onClose: () => void; - workspaceId: string; -} - export const ViewWorkspaceConnectionsModal: React.FC< ViewWorkspaceConnectionsModalProps > = ({ isOpen, onClose, workspaceId }) => { diff --git a/gridwalk-ui/src/app/workspace/[workspaceId]/viewMembersModal.tsx b/gridwalk-ui/src/app/workspace/[workspaceId]/viewMembersModal.tsx index d917083..42142e6 100644 --- a/gridwalk-ui/src/app/workspace/[workspaceId]/viewMembersModal.tsx +++ b/gridwalk-ui/src/app/workspace/[workspaceId]/viewMembersModal.tsx @@ -4,12 +4,7 @@ import { getWorkspaceMembers } from "./actions/workspace/get_members"; import { Shield, Mail, Loader2, X } from "lucide-react"; import { removeWorkspaceMember } from "./actions/workspace/remove_members"; import { useRouter } from "next/navigation"; - -interface ViewWorkspaceMemberModalProps { - isOpen: boolean; - onClose: () => void; - workspaceId: string; -} +import { ViewWorkspaceMemberModalProps } from "./types"; type WorkspaceMember = { email: string; @@ -23,6 +18,10 @@ export const ViewWorkspaceMemberModal: React.FC< const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [removing, setRemoving] = useState(null); + const [confirmText, setConfirmText] = useState(""); + const [memberToDelete, setMemberToDelete] = useState( + null + ); const router = useRouter(); useEffect(() => { @@ -35,7 +34,9 @@ export const ViewWorkspaceMemberModal: React.FC< setMembers(fetchedMembers); } catch (err) { setError( - err instanceof Error ? err.message : "Failed to fetch members", + err instanceof Error + ? err.message + : "Failed to fetch workspace members" ); } finally { setLoading(false); @@ -46,132 +47,224 @@ export const ViewWorkspaceMemberModal: React.FC< } }, [isOpen, workspaceId]); - if (!isOpen) return null; - - const getRoleColor = (role: "Admin" | "Read") => { - switch (role) { - case "Admin": - return "bg-blue-100 text-blue-800"; - case "Read": - return "bg-gray-100 text-gray-800"; - } + const handleRemoveMember = async (member: WorkspaceMember) => { + setMemberToDelete(member); + setConfirmText(""); }; - const handleRemoveMember = async (email: string) => { + const confirmRemoveMember = async (e: React.FormEvent) => { + e.preventDefault(); + if (!memberToDelete || confirmText !== "DELETE") return; + try { - setRemoving(email); + setRemoving(memberToDelete.email); await removeWorkspaceMember({ workspace_id: workspaceId, - email: email, + email: memberToDelete.email, }); - setMembers(members.filter((member) => member.email !== email)); + setMembers( + members.filter((member) => member.email !== memberToDelete.email) + ); router.refresh(); + setMemberToDelete(null); + setConfirmText(""); } catch (err) { - setError(err instanceof Error ? err.message : "Failed to remove member"); + setError( + err instanceof Error ? err.message : "Failed to remove workspace member" + ); } finally { setRemoving(null); } }; - return ( -
-
-
-

Workspace Members

- -
+ if (!isOpen) return null; -
- {loading ? ( -
- -
- ) : error ? ( -
{error}
- ) : members.length === 0 ? ( -
- No members found -
- ) : ( -
- - - - - - - - - - {members.map((member, index) => ( - - - - - - ))} - -
- Email - - Role - - Remove -
-
- - - {member.email} - -
-
-
- - - {member.role} - -
-
- -
+ const getRoleColor = (role: "Admin" | "Read") => { + switch (role) { + case "Admin": + return "bg-blue-100 text-blue-800"; + case "Read": + return "bg-gray-100 text-gray-800"; + } + }; + + // Delete confirmation modal + const DeleteConfirmation = () => { + if (!memberToDelete) return null; + + return ( +
+
+
+

Remove Member

+ +
+ +
+

+ Are you sure you want to remove{" "} + {memberToDelete.email}? +

+

+ This action cannot be undone. The member will lose access to this + workspace. +

+

+ Type DELETE to + confirm: +

+ setConfirmText(e.target.value)} + className="w-full px-3 py-2 border border-gray-300 rounded-md text-gray-900 placeholder:text-gray-500 focus:outline-none focus:ring-2 focus:ring-red-500" + placeholder="Enter text..." + disabled={removing === memberToDelete.email} + /> +
+ +
+
+ +
- )} +
+
+ ); + }; + + return ( + <> +
+
+
+

+ Workspace Members +

+ +
+ +
+ {loading ? ( +
+ +
+ ) : error ? ( +
{error}
+ ) : members.length === 0 ? ( +
+ No members found +
+ ) : ( +
+ + + + + + + + + + {members.map((member, index) => ( + + + + + + ))} + +
+ Email + + Role + + Remove +
+
+ + + {member.email} + +
+
+
+ + + {member.role} + +
+
+ +
+
+ )} +
-
- +
+ +
-
+ + ); }; diff --git a/gridwalk-ui/src/app/workspace/[workspaceId]/workspaceProjects.tsx b/gridwalk-ui/src/app/workspace/[workspaceId]/workspaceProjects.tsx index 9dcd016..68b2172 100644 --- a/gridwalk-ui/src/app/workspace/[workspaceId]/workspaceProjects.tsx +++ b/gridwalk-ui/src/app/workspace/[workspaceId]/workspaceProjects.tsx @@ -9,6 +9,7 @@ import { Trash2, HelpCircle, Database, + User2, } from "lucide-react"; import { CreateProjectModal, DeleteProjectModal } from "./projectModal"; import { HelpSupportModal } from "../supportModal"; @@ -172,6 +173,10 @@ export default function WorkspaceProjectsClient({ View Connections +
)} @@ -205,6 +210,16 @@ export default function WorkspaceProjectsClient({ )}
+ {/* Back Button */} +
+ +
+ {/* Help Button */}
-
-
-
- -
-

- No Workspace Selected + {/* Stats Grid */} +
+ + + +
+ + {/* Workspaces List */} +
+
+

+ Your Workspaces

-

- Choose a workspace from the sidebar to view and manage your - projects. Each workspace helps you organise related projects and - collaborate with your team. -

+
+
+ {workspaces.length > 0 ? ( +
+ {workspaces.map((workspace) => ( +
+
+ +
+

{workspace.name}

+

+ 0 projects • 0 members +

+
+
+
+ ))} +
+ ) : ( +
+ +

+ No workspaces yet +

+

+ Create your first workspace to get started +

+ +
+ )}

- {/* Help Button - Fixed to bottom right */} + {/* Help Button */}
); -} \ No newline at end of file +} diff --git a/gridwalk-ui/src/app/workspace/sidebar.tsx b/gridwalk-ui/src/app/workspace/sidebar.tsx index 092b1a6..564e736 100644 --- a/gridwalk-ui/src/app/workspace/sidebar.tsx +++ b/gridwalk-ui/src/app/workspace/sidebar.tsx @@ -8,7 +8,7 @@ import { SheetTrigger, } from "@/components/ui/sheet"; import { Button } from "@/components/ui/button"; -import { Menu, LogOut, ChevronDown, Plus, ChevronRight } from "lucide-react"; +import { Menu, LogOut, ChevronDown, Plus, Briefcase } from "lucide-react"; import { ProfileData, Workspaces, logout, createWorkspace } from "./actions"; import { CreateWorkspaceSidebar } from "./modal"; import { useRouter } from "next/navigation"; @@ -65,14 +65,16 @@ const WorkspaceAccordion = ({ workspaces }: { workspaces: Workspaces }) => {
-

GW

+

+ GridWalk +

-

Workspaces

+

New Workspace

@@ -80,11 +82,11 @@ const WorkspaceAccordion = ({ workspaces }: { workspaces: Workspaces }) => {