feat(web): wire workspaces settings page to real API (MS21-UI-003) (#574)
All checks were successful
ci/woodpecker/push/web Pipeline was successful
All checks were successful
ci/woodpecker/push/web Pipeline was successful
Co-authored-by: Jason Woltje <jason@diversecanvas.com> Co-committed-by: Jason Woltje <jason@diversecanvas.com>
This commit was merged in pull request #574.
This commit is contained in:
@@ -1,72 +1,74 @@
|
||||
"use client";
|
||||
|
||||
import type { ReactElement } from "react";
|
||||
import type { ReactElement, SyntheticEvent } from "react";
|
||||
|
||||
import { useState } from "react";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { WorkspaceCard } from "@/components/workspace/WorkspaceCard";
|
||||
import { ComingSoon } from "@/components/ui/ComingSoon";
|
||||
import { WorkspaceMemberRole } from "@mosaic/shared";
|
||||
import { createWorkspace, fetchUserWorkspaces, type UserWorkspace } from "@/lib/api/workspaces";
|
||||
import Link from "next/link";
|
||||
|
||||
// Check if we're in development mode
|
||||
const isDevelopment = process.env.NODE_ENV === "development";
|
||||
function getErrorMessage(error: unknown, fallback: string): string {
|
||||
if (error instanceof Error) {
|
||||
return error.message;
|
||||
}
|
||||
|
||||
// Mock data - TODO: Replace with real API calls (development only)
|
||||
const mockWorkspaces = [
|
||||
{
|
||||
id: "ws-1",
|
||||
name: "Personal Workspace",
|
||||
ownerId: "user-1",
|
||||
settings: {},
|
||||
createdAt: new Date("2024-01-15"),
|
||||
updatedAt: new Date("2024-01-15"),
|
||||
},
|
||||
{
|
||||
id: "ws-2",
|
||||
name: "Team Alpha",
|
||||
ownerId: "user-2",
|
||||
settings: {},
|
||||
createdAt: new Date("2024-01-20"),
|
||||
updatedAt: new Date("2024-01-20"),
|
||||
},
|
||||
];
|
||||
|
||||
const mockMemberships = [
|
||||
{ workspaceId: "ws-1", role: WorkspaceMemberRole.OWNER, memberCount: 1 },
|
||||
{ workspaceId: "ws-2", role: WorkspaceMemberRole.MEMBER, memberCount: 5 },
|
||||
];
|
||||
return fallback;
|
||||
}
|
||||
|
||||
/**
|
||||
* Workspaces Page Content - Development Only
|
||||
* Shows mock workspace data for development purposes
|
||||
* Workspaces Page
|
||||
* Fetches and creates workspaces through the real API.
|
||||
*/
|
||||
function WorkspacesPageContent(): ReactElement {
|
||||
export default function WorkspacesPage(): ReactElement {
|
||||
const [workspaces, setWorkspaces] = useState<UserWorkspace[]>([]);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [loadError, setLoadError] = useState<string | null>(null);
|
||||
const [isCreating, setIsCreating] = useState(false);
|
||||
const [newWorkspaceName, setNewWorkspaceName] = useState("");
|
||||
const [createError, setCreateError] = useState<string | null>(null);
|
||||
|
||||
// TODO: Replace with real API call
|
||||
const workspacesWithRoles = mockWorkspaces.map((workspace) => {
|
||||
const membership = mockMemberships.find((m) => m.workspaceId === workspace.id);
|
||||
return {
|
||||
...workspace,
|
||||
userRole: membership?.role ?? WorkspaceMemberRole.GUEST,
|
||||
memberCount: membership?.memberCount ?? 0,
|
||||
};
|
||||
});
|
||||
const loadWorkspaces = useCallback(async (): Promise<void> => {
|
||||
setIsLoading(true);
|
||||
|
||||
const handleCreateWorkspace = async (e: React.SyntheticEvent<HTMLFormElement>): Promise<void> => {
|
||||
try {
|
||||
const data = await fetchUserWorkspaces();
|
||||
setWorkspaces(data);
|
||||
setLoadError(null);
|
||||
} catch (error) {
|
||||
setLoadError(getErrorMessage(error, "Failed to load workspaces"));
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
void loadWorkspaces();
|
||||
}, [loadWorkspaces]);
|
||||
|
||||
const workspacesWithRoles = workspaces.map((workspace) => ({
|
||||
...workspace,
|
||||
settings: {},
|
||||
createdAt: new Date(workspace.createdAt),
|
||||
updatedAt: new Date(workspace.createdAt),
|
||||
userRole: workspace.role,
|
||||
memberCount: 1,
|
||||
}));
|
||||
|
||||
const handleCreateWorkspace = async (e: SyntheticEvent<HTMLFormElement>): Promise<void> => {
|
||||
e.preventDefault();
|
||||
if (!newWorkspaceName.trim()) return;
|
||||
|
||||
const workspaceName = newWorkspaceName.trim();
|
||||
if (!workspaceName) return;
|
||||
|
||||
setIsCreating(true);
|
||||
setCreateError(null);
|
||||
|
||||
try {
|
||||
// TODO: Replace with real API call
|
||||
await new Promise((resolve) => setTimeout(resolve, 1000)); // Simulate API call
|
||||
alert(`Workspace "${newWorkspaceName}" created successfully!`);
|
||||
await createWorkspace({ name: workspaceName });
|
||||
setNewWorkspaceName("");
|
||||
} catch (_error) {
|
||||
console.error("Failed to create workspace:", _error);
|
||||
alert("Failed to create workspace");
|
||||
await loadWorkspaces();
|
||||
} catch (error) {
|
||||
setCreateError(getErrorMessage(error, "Failed to create workspace"));
|
||||
} finally {
|
||||
setIsCreating(false);
|
||||
}
|
||||
@@ -106,14 +108,27 @@ function WorkspacesPageContent(): ReactElement {
|
||||
{isCreating ? "Creating..." : "Create Workspace"}
|
||||
</button>
|
||||
</form>
|
||||
{createError !== null && (
|
||||
<div className="mt-3 rounded-md border border-red-200 bg-red-50 px-3 py-2 text-sm text-red-700">
|
||||
{createError}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Workspace List */}
|
||||
<div className="space-y-4">
|
||||
<h2 className="text-xl font-semibold text-gray-900">
|
||||
Your Workspaces ({workspacesWithRoles.length})
|
||||
Your Workspaces ({isLoading ? "..." : workspacesWithRoles.length})
|
||||
</h2>
|
||||
{workspacesWithRoles.length === 0 ? (
|
||||
{loadError !== null ? (
|
||||
<div className="rounded-md border border-red-200 bg-red-50 px-4 py-3 text-red-700">
|
||||
{loadError}
|
||||
</div>
|
||||
) : isLoading ? (
|
||||
<div className="bg-white rounded-lg shadow-sm border border-gray-200 p-12 text-center text-gray-600">
|
||||
Loading workspaces...
|
||||
</div>
|
||||
) : workspacesWithRoles.length === 0 ? (
|
||||
<div className="bg-white rounded-lg shadow-sm border border-gray-200 p-12 text-center">
|
||||
<svg
|
||||
className="mx-auto h-12 w-12 text-gray-400 mb-4"
|
||||
@@ -147,26 +162,3 @@ function WorkspacesPageContent(): ReactElement {
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Workspaces Page Entry Point
|
||||
* Shows development content or Coming Soon based on environment
|
||||
*/
|
||||
export default function WorkspacesPage(): ReactElement {
|
||||
// In production, show Coming Soon placeholder
|
||||
if (!isDevelopment) {
|
||||
return (
|
||||
<ComingSoon
|
||||
feature="Workspace Management"
|
||||
description="Create and manage workspaces to organize your projects and collaborate with your team. This feature is currently under development."
|
||||
>
|
||||
<Link href="/settings" className="text-sm text-blue-600 hover:text-blue-700">
|
||||
Back to Settings
|
||||
</Link>
|
||||
</ComingSoon>
|
||||
);
|
||||
}
|
||||
|
||||
// In development, show the full page with mock data
|
||||
return <WorkspacesPageContent />;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user