146 lines
3.6 KiB
TypeScript
146 lines
3.6 KiB
TypeScript
"use client";
|
|
|
|
import { useEffect } from "react";
|
|
import { useRouter } from "next/navigation";
|
|
import { useAuth, userCanAccessApp } from "@/components/AuthProvider";
|
|
import { getAccessToken, getRefreshToken } from "@/lib/meshkee-api";
|
|
import {
|
|
getCustomerDashboardOrigin,
|
|
hostAppFor,
|
|
type HostApp,
|
|
} from "@/lib/host";
|
|
|
|
function LoadingScreen({ message = "Loading" }: { message?: string }) {
|
|
return (
|
|
<div
|
|
style={{
|
|
minHeight: "100vh",
|
|
display: "grid",
|
|
placeItems: "center",
|
|
background: "#f6f6f6",
|
|
fontSize: 12,
|
|
letterSpacing: "0.2em",
|
|
textTransform: "uppercase",
|
|
color: "#8e8e8e",
|
|
padding: 24,
|
|
textAlign: "center",
|
|
}}
|
|
>
|
|
{message}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
const websiteUrl =
|
|
process.env.NEXT_PUBLIC_WEBSITE_URL?.replace(/\/$/, "") ||
|
|
"http://localhost:3000";
|
|
|
|
function handoffToWebsite(next: string) {
|
|
try {
|
|
const target = new URL(next);
|
|
if (target.origin !== new URL(websiteUrl).origin) return false;
|
|
const access = getAccessToken();
|
|
const refresh = getRefreshToken();
|
|
if (!access || !refresh) return false;
|
|
const hash = new URLSearchParams({
|
|
access,
|
|
refresh,
|
|
next: `${target.pathname}${target.search}${target.hash}` || "/register",
|
|
}).toString();
|
|
window.location.replace(`${target.origin}/auth/handoff#${hash}`);
|
|
return true;
|
|
} catch {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
export function ProtectedRoute({
|
|
children,
|
|
requiredApp,
|
|
}: {
|
|
children: React.ReactNode;
|
|
requiredApp?: HostApp;
|
|
}) {
|
|
const { user, isLoading } = useAuth();
|
|
const router = useRouter();
|
|
const app =
|
|
requiredApp ??
|
|
hostAppFor(typeof window !== "undefined" ? window.location.host : "");
|
|
|
|
useEffect(() => {
|
|
if (isLoading) return;
|
|
if (!user) {
|
|
if (app === "business") {
|
|
// Do not auto-bounce to admin (that was the manage-business loop).
|
|
// Owners can sign in on the customer host.
|
|
const params = new URLSearchParams(window.location.search);
|
|
if (params.has("businessId")) {
|
|
return;
|
|
}
|
|
window.location.replace(`${getCustomerDashboardOrigin()}/login`);
|
|
return;
|
|
}
|
|
router.replace("/login");
|
|
return;
|
|
}
|
|
if (!userCanAccessApp(user, app)) {
|
|
if (app === "admin") {
|
|
window.location.replace(`${getCustomerDashboardOrigin()}/`);
|
|
return;
|
|
}
|
|
router.replace("/login");
|
|
}
|
|
}, [isLoading, user, router, app]);
|
|
|
|
if (isLoading) {
|
|
return <LoadingScreen />;
|
|
}
|
|
|
|
if (!user) {
|
|
if (
|
|
app === "business" &&
|
|
typeof window !== "undefined" &&
|
|
new URLSearchParams(window.location.search).has("businessId")
|
|
) {
|
|
return (
|
|
<LoadingScreen message="Could not open this business. Go back and use Manage business again." />
|
|
);
|
|
}
|
|
return <LoadingScreen message="Opening…" />;
|
|
}
|
|
|
|
if (!userCanAccessApp(user, app)) {
|
|
return <LoadingScreen />;
|
|
}
|
|
|
|
return children;
|
|
}
|
|
|
|
export function GuestRoute({ children }: { children: React.ReactNode }) {
|
|
const { user, isLoading } = useAuth();
|
|
const router = useRouter();
|
|
|
|
useEffect(() => {
|
|
if (isLoading || !user) return;
|
|
|
|
const nextParam = new URLSearchParams(window.location.search).get("next");
|
|
if (nextParam && handoffToWebsite(nextParam)) return;
|
|
|
|
const app = hostAppFor(window.location.host);
|
|
if (app === "admin") {
|
|
router.replace("/");
|
|
return;
|
|
}
|
|
if (app === "business") {
|
|
router.replace("/profile");
|
|
return;
|
|
}
|
|
router.replace("/");
|
|
}, [isLoading, user, router]);
|
|
|
|
if (isLoading) return <LoadingScreen />;
|
|
if (user) return null;
|
|
|
|
return children;
|
|
}
|