feat(canvas): add shared workspace access and controls

This commit is contained in:
2026-07-10 14:47:19 +08:00
parent f288eb1365
commit 14eaece0ac
18 changed files with 1149 additions and 80 deletions
+49
View File
@@ -2,7 +2,10 @@
import { Loader2 } from "lucide-react";
import { useEffect, useState } from "react";
import type { ResolveShareResponse } from "@/domain/design";
import { readRoute, type AppRoute } from "@/application/route";
import { designGateway } from "@/infrastructure/designGateway";
import { httpStatusOf } from "@/infrastructure/userMessages";
import { BrandMark } from "@/ui/components/BrandMark";
import { CanvasWorkspace } from "@/ui/pages/CanvasWorkspace";
import { HomePage } from "@/ui/pages/HomePage";
@@ -41,11 +44,57 @@ export function App() {
if (route.name === "project" && !isAuthenticated) return <ProjectAuthPreview />;
if (route.name === "project") return <CanvasWorkspace projectId={route.id} />;
if (route.name === "share") return <SharedCanvasRoute shareId={route.id} />;
if (route.name === "projects") return <ProjectsRoutePage />;
if (route.name === "notFound") return <NotFoundPage />;
return <HomePage />;
}
function SharedCanvasRoute({ shareId }: { shareId: string }) {
const { isAuthenticated, openRequiredLogin } = useAuth();
const [resolved, setResolved] = useState<ResolveShareResponse | null>(null);
const [state, setState] = useState<"loading" | "not-found" | "error">("loading");
useEffect(() => {
let active = true;
let releaseRequiredLogin: (() => void) | undefined;
setResolved(null);
setState("loading");
designGateway
.resolveShare(shareId)
.then((response) => {
if (!active) return;
setResolved(response);
})
.catch((error: unknown) => {
if (!active) return;
const status = httpStatusOf(error);
if (status === 401 && !isAuthenticated) {
releaseRequiredLogin = openRequiredLogin();
return;
}
setState(status === 403 || status === 404 ? "not-found" : "error");
});
return () => {
active = false;
releaseRequiredLogin?.();
};
}, [isAuthenticated, openRequiredLogin, shareId]);
if (resolved) {
return <CanvasWorkspace projectId={resolved.project.id} shareAccess={resolved.access} />;
}
if (state === "not-found") return <NotFoundPage />;
if (state === "error") {
return (
<div className="workspace-loading">
<span>Unable to open this shared canvas.</span>
</div>
);
}
return <ProjectAuthPreview />;
}
function ProjectAuthPreview() {
return (
<div className="project-auth-shell" aria-hidden="true">