Files
moteva/frontend/src/ui/App.tsx
T

153 lines
4.9 KiB
TypeScript
Raw Normal View History

"use client";
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 { BrandKitRoutePage } from "@/ui/pages/BrandKitRoutePage";
import { CanvasWorkspace } from "@/ui/pages/CanvasWorkspace";
import { HomePage } from "@/ui/pages/HomePage";
import { NotFoundPage } from "@/ui/pages/NotFoundPage";
import { ProjectsRoutePage } from "@/ui/pages/ProjectsRoutePage";
import { useAuth } from "@/ui/auth/AuthProvider";
export function App() {
const { isAuthenticated, openLogin } = useAuth();
const [route, setRoute] = useState<AppRoute | null>(null);
useEffect(() => {
setRoute(readRoute());
const handleRoute = () => setRoute(readRoute());
window.addEventListener("hashchange", handleRoute);
window.addEventListener("popstate", handleRoute);
return () => {
window.removeEventListener("hashchange", handleRoute);
window.removeEventListener("popstate", handleRoute);
};
}, []);
useEffect(() => {
if (route?.name === "project" && !isAuthenticated) {
openLogin();
}
}, [isAuthenticated, openLogin, route]);
if (!route) {
return (
<div className="workspace-loading">
<Loader2 className="spin" size={28} />
</div>
);
}
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 === "brandKit") return <BrandKitRoutePage />;
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">
<div className="project-auth-blur">
<header className="project-auth-topbar">
<div className="project-auth-title">
<BrandMark className="project-auth-mark" />
<span />
</div>
<div className="project-auth-top-actions">
<i />
<i />
<b />
</div>
</header>
<main className="project-auth-main">
<section className="project-auth-stage">
<div className="project-auth-grid" />
<div className="project-auth-frame primary" />
<div className="project-auth-frame secondary" />
<div className="project-auth-frame tertiary" />
<div className="project-auth-toolbar">
<i />
<i />
<i />
<i />
</div>
</section>
<aside className="project-auth-panel">
<div className="project-auth-panel-head">
<span />
<i />
<i />
</div>
<div className="project-auth-thread">
<b />
<span />
<span />
<span />
</div>
<div className="project-auth-composer">
<span />
<div>
<i />
<i />
</div>
</div>
</aside>
</main>
</div>
</div>
);
}