feat: support subpath deployment with NEXT_PUBLIC_BASE_PATH environment variable
This commit is contained in:
@@ -411,7 +411,8 @@ export default function LoginPage() {
|
||||
const verifier = generateCodeVerifier();
|
||||
const challenge = await generateCodeChallenge(verifier);
|
||||
const state = generateState();
|
||||
const redirectUri = `${window.location.origin}/${params.locale}/auth/callback`;
|
||||
const prefix = getPathPrefix(params.locale as string);
|
||||
const redirectUri = `${window.location.origin}${prefix}/${params.locale}/auth/callback`;
|
||||
|
||||
sessionStorage.setItem("oauth_code_verifier", verifier);
|
||||
sessionStorage.setItem("oauth_state", state);
|
||||
|
||||
+20
-14
@@ -2,6 +2,12 @@ import type { MetadataRoute } from "next";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
// Manifest paths must include the deployment subpath - browsers resolve them
|
||||
// against the document origin, not the manifest's location, and Next.js does
|
||||
// not auto-prefix string literals inside MetadataRoute payloads.
|
||||
const BASE_PATH = (process.env.NEXT_PUBLIC_BASE_PATH ?? "").replace(/\/+$/, "");
|
||||
const withBase = (p: string) => `${BASE_PATH}${p}`;
|
||||
|
||||
export default function manifest(): MetadataRoute.Manifest {
|
||||
const appName =
|
||||
process.env.APP_NAME ||
|
||||
@@ -21,26 +27,26 @@ export default function manifest(): MetadataRoute.Manifest {
|
||||
|
||||
const icons: MetadataRoute.Manifest["icons"] = hasCustomIcon
|
||||
? [
|
||||
{ src: "/api/pwa-icon/192", sizes: "192x192", type: "image/png", purpose: "any" },
|
||||
{ src: "/api/pwa-icon/512", sizes: "512x512", type: "image/png", purpose: "any" },
|
||||
{ src: "/api/pwa-icon/192", sizes: "192x192", type: "image/png", purpose: "maskable" },
|
||||
{ src: "/api/pwa-icon/512", sizes: "512x512", type: "image/png", purpose: "maskable" },
|
||||
{ src: withBase("/api/pwa-icon/192"), sizes: "192x192", type: "image/png", purpose: "any" },
|
||||
{ src: withBase("/api/pwa-icon/512"), sizes: "512x512", type: "image/png", purpose: "any" },
|
||||
{ src: withBase("/api/pwa-icon/192"), sizes: "192x192", type: "image/png", purpose: "maskable" },
|
||||
{ src: withBase("/api/pwa-icon/512"), sizes: "512x512", type: "image/png", purpose: "maskable" },
|
||||
]
|
||||
: [
|
||||
{ src: "/icon-192x192.png", sizes: "192x192", type: "image/png", purpose: "any" },
|
||||
{ src: "/icon-512x512.png", sizes: "512x512", type: "image/png", purpose: "any" },
|
||||
{ src: "/icon-maskable-light-192x192.png", sizes: "192x192", type: "image/png", purpose: "maskable" },
|
||||
{ src: "/icon-maskable-light-512x512.png", sizes: "512x512", type: "image/png", purpose: "maskable" },
|
||||
{ src: "/icon-maskable-dark-192x192.png", sizes: "192x192", type: "image/png", purpose: "maskable" },
|
||||
{ src: "/icon-maskable-dark-512x512.png", sizes: "512x512", type: "image/png", purpose: "maskable" },
|
||||
{ src: withBase("/icon-192x192.png"), sizes: "192x192", type: "image/png", purpose: "any" },
|
||||
{ src: withBase("/icon-512x512.png"), sizes: "512x512", type: "image/png", purpose: "any" },
|
||||
{ src: withBase("/icon-maskable-light-192x192.png"), sizes: "192x192", type: "image/png", purpose: "maskable" },
|
||||
{ src: withBase("/icon-maskable-light-512x512.png"), sizes: "512x512", type: "image/png", purpose: "maskable" },
|
||||
{ src: withBase("/icon-maskable-dark-192x192.png"), sizes: "192x192", type: "image/png", purpose: "maskable" },
|
||||
{ src: withBase("/icon-maskable-dark-512x512.png"), sizes: "512x512", type: "image/png", purpose: "maskable" },
|
||||
];
|
||||
|
||||
return {
|
||||
name: appName,
|
||||
short_name: shortName,
|
||||
description,
|
||||
start_url: "/",
|
||||
scope: "/",
|
||||
start_url: withBase("/"),
|
||||
scope: withBase("/"),
|
||||
display: "standalone",
|
||||
orientation: "portrait-primary",
|
||||
theme_color: themeColor,
|
||||
@@ -48,8 +54,8 @@ export default function manifest(): MetadataRoute.Manifest {
|
||||
icons,
|
||||
categories: ["productivity"],
|
||||
screenshots: [
|
||||
{ src: "/screenshot-540x720.png", sizes: "540x720", type: "image/png" },
|
||||
{ src: "/screenshot-1280x720.png", sizes: "1280x720", type: "image/png" },
|
||||
{ src: withBase("/screenshot-540x720.png"), sizes: "540x720", type: "image/png" },
|
||||
{ src: withBase("/screenshot-1280x720.png"), sizes: "1280x720", type: "image/png" },
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
+16
-7
@@ -2,34 +2,43 @@
|
||||
|
||||
import { useEffect } from "react";
|
||||
import { useAuthStore } from "@/stores/auth-store";
|
||||
import { getPathPrefix } from "@/lib/browser-navigation";
|
||||
|
||||
export default function NotFound() {
|
||||
const isAuthenticated = useAuthStore((s) => s.isAuthenticated);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isAuthenticated) {
|
||||
// Don't redirect admin routes to the webmail login page
|
||||
const isAdminRoute = window.location.pathname === '/admin' || window.location.pathname.startsWith('/admin/');
|
||||
const prefix = getPathPrefix();
|
||||
// Don't redirect admin routes to the webmail login page. Admin paths
|
||||
// are mounted relative to the deployment prefix, so account for it.
|
||||
const adminBase = `${prefix}/admin`;
|
||||
const isAdminRoute = window.location.pathname === adminBase || window.location.pathname.startsWith(`${adminBase}/`);
|
||||
if (!isAdminRoute) {
|
||||
window.location.href = "/login";
|
||||
window.location.href = `${prefix}/login`;
|
||||
}
|
||||
}
|
||||
}, [isAuthenticated]);
|
||||
|
||||
if (!isAuthenticated) {
|
||||
// Allow admin routes to render the 404 without redirecting
|
||||
const isAdmin = typeof window !== 'undefined' &&
|
||||
(window.location.pathname === '/admin' || window.location.pathname.startsWith('/admin/'));
|
||||
let isAdmin = false;
|
||||
if (typeof window !== 'undefined') {
|
||||
const prefix = getPathPrefix();
|
||||
const adminBase = `${prefix}/admin`;
|
||||
isAdmin = window.location.pathname === adminBase || window.location.pathname.startsWith(`${adminBase}/`);
|
||||
}
|
||||
if (!isAdmin) return null;
|
||||
}
|
||||
|
||||
const prefix = typeof window !== 'undefined' ? getPathPrefix() : '';
|
||||
|
||||
return (
|
||||
<div className="min-h-screen flex items-center justify-center bg-background">
|
||||
<div className="text-center max-w-md px-4">
|
||||
<h1 className="text-4xl font-bold text-foreground mb-2">404</h1>
|
||||
<p className="text-muted-foreground mb-6">This page could not be found.</p>
|
||||
<a
|
||||
href="/"
|
||||
href={`${prefix}/`}
|
||||
className="inline-flex items-center px-4 py-2 bg-primary text-primary-foreground rounded-lg hover:opacity-90 transition-opacity"
|
||||
>
|
||||
Go home
|
||||
|
||||
Reference in New Issue
Block a user