- Mount UploadModal globally in AppShell so upload works from any page - Add /forgot-password, /reset-password, /s, /api/survey/submit to public paths in middleware - Fix IMAP test URL (/api/email/test -> /api/email-watch/test) and body shape - Fix IMAP scan URL to use /api/email-watch with action body - Add FTP server settings section to Upload Sources page with test connection - Add "Surveys" nav item in sidebar with dedicated page showing links, QR codes - Include org slug in form-templates API response for survey URL construction - Add pre-creation "Test Connection" button on new integration page - Create /api/integrations/test endpoint for pre-creation connection testing - Replace overflow-hidden with overflow-clip on Card to fix click/z-index issues Made-with: Cursor
108 lines
2.4 KiB
TypeScript
108 lines
2.4 KiB
TypeScript
import { NextRequest, NextResponse } from "next/server";
|
|
import { getToken } from "next-auth/jwt";
|
|
|
|
const publicPaths = [
|
|
"/welcome",
|
|
"/features",
|
|
"/pricing",
|
|
"/privacy",
|
|
"/terms",
|
|
"/login",
|
|
"/signup",
|
|
"/invite",
|
|
"/forgot-password",
|
|
"/reset-password",
|
|
"/setup",
|
|
"/s",
|
|
"/api/auth",
|
|
"/api/health",
|
|
"/api/setup",
|
|
"/api/onboarding",
|
|
"/api/invitations/verify",
|
|
"/api/survey/submit",
|
|
"/api/jobs/process",
|
|
"/api/email-watch/poll",
|
|
"/api/ftp-watch/poll",
|
|
];
|
|
|
|
const workspaceSetupExemptPaths = [
|
|
"/workspace-setup",
|
|
"/onboarding",
|
|
"/api/onboarding",
|
|
"/api/org/create-personal",
|
|
"/api/org/list",
|
|
"/api/auth",
|
|
];
|
|
|
|
const onboardingExemptPaths = [
|
|
"/onboarding",
|
|
"/api/onboarding",
|
|
"/api/auth",
|
|
];
|
|
|
|
function isPublic(pathname: string) {
|
|
return publicPaths.some(
|
|
(p) => pathname === p || pathname.startsWith(p + "/")
|
|
);
|
|
}
|
|
|
|
function isOnboardingExempt(pathname: string) {
|
|
return onboardingExemptPaths.some(
|
|
(p) => pathname === p || pathname.startsWith(p + "/")
|
|
);
|
|
}
|
|
|
|
export async function middleware(req: NextRequest) {
|
|
const { pathname } = req.nextUrl;
|
|
|
|
const isStaticFile = pathname.startsWith("/_next/") ||
|
|
pathname.startsWith("/favicon") ||
|
|
/^\/(.*\.(ico|svg|png|jpg|jpeg|gif|webp|woff2?|ttf|css|js|map))$/.test(pathname);
|
|
if (isStaticFile) {
|
|
return NextResponse.next();
|
|
}
|
|
|
|
if (isPublic(pathname)) {
|
|
return NextResponse.next();
|
|
}
|
|
|
|
const secureCookie =
|
|
req.headers.get("x-forwarded-proto") === "https" ||
|
|
req.nextUrl.protocol === "https:";
|
|
const token = await getToken({
|
|
req,
|
|
secret: process.env.AUTH_SECRET,
|
|
secureCookie,
|
|
});
|
|
|
|
if (!token) {
|
|
if (pathname === "/login" || pathname === "/signup") {
|
|
return NextResponse.next();
|
|
}
|
|
const welcomeUrl = new URL("/welcome", req.url);
|
|
return NextResponse.redirect(welcomeUrl);
|
|
}
|
|
|
|
const hasOrg = !!token.orgId;
|
|
const onboardingDone = token.onboardingComplete === true;
|
|
|
|
function isWorkspaceSetupExempt(p: string) {
|
|
return workspaceSetupExemptPaths.some(
|
|
(x) => p === x || p.startsWith(x + "/")
|
|
);
|
|
}
|
|
|
|
if (!hasOrg && !isWorkspaceSetupExempt(pathname)) {
|
|
return NextResponse.redirect(new URL("/workspace-setup", req.url));
|
|
}
|
|
|
|
if (hasOrg && !onboardingDone && !isOnboardingExempt(pathname)) {
|
|
return NextResponse.redirect(new URL("/onboarding", req.url));
|
|
}
|
|
|
|
return NextResponse.next();
|
|
}
|
|
|
|
export const config = {
|
|
matcher: ["/((?!_next/static|_next/image|favicon.ico).*)"],
|
|
};
|