From 9569df6a277b1cc902d0eb7cac674b60919adb49 Mon Sep 17 00:00:00 2001 From: Haitao Pan Date: Wed, 18 Mar 2026 16:23:50 +0800 Subject: [PATCH] fix(xworkmate): avoid preview route bailouts --- src/app/xworkmate/admin/page.tsx | 55 +----- src/app/xworkmate/integrations/page.tsx | 52 +----- .../xworkmate/XWorkmateProfileRoute.tsx | 161 ++++++++++++++++++ 3 files changed, 167 insertions(+), 101 deletions(-) create mode 100644 src/components/xworkmate/XWorkmateProfileRoute.tsx diff --git a/src/app/xworkmate/admin/page.tsx b/src/app/xworkmate/admin/page.tsx index cdcd94b..fd48c74 100644 --- a/src/app/xworkmate/admin/page.tsx +++ b/src/app/xworkmate/admin/page.tsx @@ -1,59 +1,10 @@ -export const dynamic = "force-dynamic"; - -import { headers } from "next/headers"; -import { connection } from "next/server"; -import { redirect } from "next/navigation"; -import { XWorkmateProfileEditor } from "@/components/xworkmate/XWorkmateProfileEditor"; -import { - buildSharedXWorkmateUrl, - isLegacyConsoleXWorkmateHost, - isSharedXWorkmateHost, - normalizeXWorkmateHost, -} from "@/lib/xworkmate/host"; -import { buildXWorkmateScopeKey } from "@/lib/xworkmate/types"; -import { getXWorkmateSessionContext } from "@/server/xworkmate/profile"; +import { XWorkmateProfileRoute } from "@/components/xworkmate/XWorkmateProfileRoute"; export const metadata = { title: "XWorkmate Shared Integrations", description: "Manage the shared XWorkmate integrations profile", }; -export default async function XWorkmateAdminPage() { - await connection(); - const requestHeaders = await headers(); - const requestHost = normalizeXWorkmateHost( - requestHeaders.get("x-forwarded-host") ?? requestHeaders.get("host"), - ); - - if (isLegacyConsoleXWorkmateHost(requestHost)) { - redirect(buildSharedXWorkmateUrl("/xworkmate/admin")); - } - - const { user, profile } = await getXWorkmateSessionContext(requestHost); - if (!profile) { - redirect("/xworkmate"); - } - if (!isSharedXWorkmateHost(requestHost)) { - redirect("/xworkmate/integrations"); - } - if ( - profile.profileScope !== "tenant-shared" || - !profile.canEditIntegrations - ) { - redirect("/xworkmate"); - } - - const scopeKey = buildXWorkmateScopeKey(profile, user?.id, requestHost); - - return ( -
-
- -
-
- ); +export default function XWorkmateAdminPage() { + return ; } diff --git a/src/app/xworkmate/integrations/page.tsx b/src/app/xworkmate/integrations/page.tsx index f1be89b..35dbccc 100644 --- a/src/app/xworkmate/integrations/page.tsx +++ b/src/app/xworkmate/integrations/page.tsx @@ -1,56 +1,10 @@ -export const dynamic = "force-dynamic"; - -import { headers } from "next/headers"; -import { connection } from "next/server"; -import { redirect } from "next/navigation"; -import { XWorkmateProfileEditor } from "@/components/xworkmate/XWorkmateProfileEditor"; -import { - buildSharedXWorkmateUrl, - isLegacyConsoleXWorkmateHost, - isSharedXWorkmateHost, - normalizeXWorkmateHost, -} from "@/lib/xworkmate/host"; -import { buildXWorkmateScopeKey } from "@/lib/xworkmate/types"; -import { getXWorkmateSessionContext } from "@/server/xworkmate/profile"; +import { XWorkmateProfileRoute } from "@/components/xworkmate/XWorkmateProfileRoute"; export const metadata = { title: "XWorkmate Personal Integrations", description: "Manage the personal XWorkmate integrations profile", }; -export default async function XWorkmateIntegrationsPage() { - await connection(); - const requestHeaders = await headers(); - const requestHost = normalizeXWorkmateHost( - requestHeaders.get("x-forwarded-host") ?? requestHeaders.get("host"), - ); - - if (isLegacyConsoleXWorkmateHost(requestHost)) { - redirect(buildSharedXWorkmateUrl("/xworkmate/integrations")); - } - - const { user, profile } = await getXWorkmateSessionContext(requestHost); - if (!profile) { - redirect("/xworkmate"); - } - if (isSharedXWorkmateHost(requestHost)) { - redirect(profile.canEditIntegrations ? "/xworkmate/admin" : "/xworkmate"); - } - if (profile.profileScope !== "user-private") { - redirect("/xworkmate"); - } - - const scopeKey = buildXWorkmateScopeKey(profile, user?.id, requestHost); - - return ( -
-
- -
-
- ); +export default function XWorkmateIntegrationsPage() { + return ; } diff --git a/src/components/xworkmate/XWorkmateProfileRoute.tsx b/src/components/xworkmate/XWorkmateProfileRoute.tsx new file mode 100644 index 0000000..adf4149 --- /dev/null +++ b/src/components/xworkmate/XWorkmateProfileRoute.tsx @@ -0,0 +1,161 @@ +"use client"; + +import { useEffect, useMemo, useState } from "react"; +import { useRouter } from "next/navigation"; + +import { XWorkmateLoading } from "@/app/xworkmate/XWorkmateLoading"; +import { useUserStore } from "@/lib/userStore"; +import { + buildSharedXWorkmateUrl, + isLegacyConsoleXWorkmateHost, + isSharedXWorkmateHost, + normalizeXWorkmateHost, +} from "@/lib/xworkmate/host"; +import { + buildXWorkmateScopeKey, + type XWorkmateProfileResponse, +} from "@/lib/xworkmate/types"; +import { XWorkmateProfileEditor } from "@/components/xworkmate/XWorkmateProfileEditor"; + +type ProfileRouteMode = "admin" | "integrations"; + +type XWorkmateProfileRouteProps = { + mode: ProfileRouteMode; +}; + +async function fetchProfile(): Promise { + const response = await fetch("/api/xworkmate/profile", { + credentials: "include", + cache: "no-store", + headers: { + Accept: "application/json", + }, + }); + + if (response.status === 401) { + return null; + } + + if (!response.ok) { + throw new Error(`xworkmate_profile_failed:${response.status}`); + } + + return (await response.json()) as XWorkmateProfileResponse; +} + +export function XWorkmateProfileRoute({ + mode, +}: XWorkmateProfileRouteProps): React.ReactNode { + const router = useRouter(); + const sessionUser = useUserStore((state) => state.user); + const [profile, setProfile] = useState(null); + const [status, setStatus] = useState<"loading" | "ready" | "redirecting">( + "loading", + ); + + const requestHost = useMemo(() => { + if (typeof window === "undefined") { + return ""; + } + return normalizeXWorkmateHost(window.location.host); + }, []); + + useEffect(() => { + let cancelled = false; + + async function load() { + try { + if (isLegacyConsoleXWorkmateHost(requestHost)) { + setStatus("redirecting"); + window.location.replace( + buildSharedXWorkmateUrl( + mode === "admin" ? "/xworkmate/admin" : "/xworkmate/integrations", + ), + ); + return; + } + + const nextProfile = await fetchProfile(); + if (cancelled) { + return; + } + + if (!nextProfile) { + setStatus("redirecting"); + router.replace("/xworkmate"); + return; + } + + if (mode === "admin") { + if (!isSharedXWorkmateHost(requestHost)) { + setStatus("redirecting"); + router.replace("/xworkmate/integrations"); + return; + } + + if ( + nextProfile.profileScope !== "tenant-shared" || + !nextProfile.canEditIntegrations + ) { + setStatus("redirecting"); + router.replace("/xworkmate"); + return; + } + } else { + if (isSharedXWorkmateHost(requestHost)) { + setStatus("redirecting"); + router.replace( + nextProfile.canEditIntegrations + ? "/xworkmate/admin" + : "/xworkmate", + ); + return; + } + + if (nextProfile.profileScope !== "user-private") { + setStatus("redirecting"); + router.replace("/xworkmate"); + return; + } + } + + setProfile(nextProfile); + setStatus("ready"); + } catch { + if (cancelled) { + return; + } + setStatus("redirecting"); + router.replace("/xworkmate"); + } + } + + void load(); + + return () => { + cancelled = true; + }; + }, [mode, requestHost, router]); + + if (status !== "ready" || !profile) { + return ; + } + + const scopeKey = buildXWorkmateScopeKey( + profile, + sessionUser?.id ?? sessionUser?.uuid ?? null, + requestHost, + ); + + return ( +
+
+ +
+
+ ); +}