fix: prefix hand-written URLs with basePath for subpath deployments
This commit is contained in:
@@ -4,7 +4,7 @@ import { Suspense, useEffect, useState } from "react";
|
|||||||
import { useRouter, useSearchParams } from "next/navigation";
|
import { useRouter, useSearchParams } from "next/navigation";
|
||||||
import { useTranslations } from "next-intl";
|
import { useTranslations } from "next-intl";
|
||||||
import { useAuthStore } from "@/stores/auth-store";
|
import { useAuthStore } from "@/stores/auth-store";
|
||||||
import { getPathPrefix } from "@/lib/browser-navigation";
|
import { apiFetch, getPathPrefix } from "@/lib/browser-navigation";
|
||||||
import { Loader2, AlertCircle } from "lucide-react";
|
import { Loader2, AlertCircle } from "lucide-react";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { useParams } from "next/navigation";
|
import { useParams } from "next/navigation";
|
||||||
@@ -96,7 +96,7 @@ function OAuthCallbackInner() {
|
|||||||
// the refresh-token cookie write for the same reason.
|
// the refresh-token cookie write for the same reason.
|
||||||
(async () => {
|
(async () => {
|
||||||
try {
|
try {
|
||||||
const res = await fetch("/api/auth/sso/complete", {
|
const res = await apiFetch("/api/auth/sso/complete", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: { "Content-Type": "application/json" },
|
headers: { "Content-Type": "application/json" },
|
||||||
credentials: "include",
|
credentials: "include",
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import { PaneSizeContext } from "@/hooks/use-pane-size";
|
|||||||
import { ProTabBar, PRO_TAB_DRAG_MIME } from "@/components/pro/pro-tab-bar";
|
import { ProTabBar, PRO_TAB_DRAG_MIME } from "@/components/pro/pro-tab-bar";
|
||||||
import { useProTabStore, type ProTab, type ProTabKind, type ProPaneId } from "@/stores/pro-tab-store";
|
import { useProTabStore, type ProTab, type ProTabKind, type ProPaneId } from "@/stores/pro-tab-store";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
|
import { getPathPrefix } from "@/lib/browser-navigation";
|
||||||
|
|
||||||
import MailPage from "@/app/(main)/[locale]/page";
|
import MailPage from "@/app/(main)/[locale]/page";
|
||||||
import CalendarPage from "@/app/(main)/[locale]/calendar/page";
|
import CalendarPage from "@/app/(main)/[locale]/calendar/page";
|
||||||
@@ -172,7 +173,7 @@ export default function ProHome() {
|
|||||||
// enabled it. If either precondition stops holding, hand the user back
|
// enabled it. If either precondition stops holding, hand the user back
|
||||||
// to the standard shell.
|
// to the standard shell.
|
||||||
if (isMobile || isTablet || !proInterface) {
|
if (isMobile || isTablet || !proInterface) {
|
||||||
window.location.replace("/");
|
window.location.replace(`${getPathPrefix()}/`);
|
||||||
}
|
}
|
||||||
}, [initialCheckDone, isMobile, isTablet, proInterface]);
|
}, [initialCheckDone, isMobile, isTablet, proInterface]);
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
import { useEffect, useState, type FormEvent, type ReactNode } from 'react';
|
import { useEffect, useState, type FormEvent, type ReactNode } from 'react';
|
||||||
import { useRouter, useSearchParams } from 'next/navigation';
|
import { useRouter, useSearchParams } from 'next/navigation';
|
||||||
import { CheckCircle2, AlertTriangle, AlertCircle, Server, ShieldCheck, KeyRound, FileText, Palette, Lock, ShieldAlert } from 'lucide-react';
|
import { CheckCircle2, AlertTriangle, AlertCircle, Server, ShieldCheck, KeyRound, FileText, Palette, Lock, ShieldAlert } from 'lucide-react';
|
||||||
import { apiFetch } from '@/lib/browser-navigation';
|
import { apiFetch, getPathPrefix } from '@/lib/browser-navigation';
|
||||||
|
|
||||||
type State = 'bootstrap' | 'configured' | 'env-managed';
|
type State = 'bootstrap' | 'configured' | 'env-managed';
|
||||||
|
|
||||||
@@ -258,7 +258,7 @@ export default function SetupWizardPage() {
|
|||||||
// edge cases that swallow client-side replaces after the
|
// edge cases that swallow client-side replaces after the
|
||||||
// setupComplete flag flips.
|
// setupComplete flag flips.
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
window.location.assign('/admin/login');
|
window.location.assign(`${getPathPrefix()}/admin/login`);
|
||||||
}, 1500);
|
}, 1500);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
@@ -340,13 +340,13 @@ function CompletedScreen() {
|
|||||||
</div>
|
</div>
|
||||||
<div className="mt-6 space-y-2">
|
<div className="mt-6 space-y-2">
|
||||||
<a
|
<a
|
||||||
href="/admin/login"
|
href={`${getPathPrefix()}/admin/login`}
|
||||||
className="block w-full rounded-md bg-primary text-primary-foreground text-center px-4 py-2.5 text-sm font-medium hover:bg-primary/90"
|
className="block w-full rounded-md bg-primary text-primary-foreground text-center px-4 py-2.5 text-sm font-medium hover:bg-primary/90"
|
||||||
>
|
>
|
||||||
Sign in to admin dashboard
|
Sign in to admin dashboard
|
||||||
</a>
|
</a>
|
||||||
<a
|
<a
|
||||||
href="/"
|
href={`${getPathPrefix()}/`}
|
||||||
className="block w-full rounded-md border border-border text-center px-4 py-2.5 text-sm font-medium hover:bg-muted"
|
className="block w-full rounded-md border border-border text-center px-4 py-2.5 text-sm font-medium hover:bg-muted"
|
||||||
>
|
>
|
||||||
Open webmail login
|
Open webmail login
|
||||||
@@ -422,13 +422,13 @@ function AlreadyConfiguredScreen() {
|
|||||||
</div>
|
</div>
|
||||||
<div className="mt-6 space-y-2">
|
<div className="mt-6 space-y-2">
|
||||||
<a
|
<a
|
||||||
href="/admin/login"
|
href={`${getPathPrefix()}/admin/login`}
|
||||||
className="block w-full rounded-md bg-primary text-primary-foreground text-center px-4 py-2.5 text-sm font-medium hover:bg-primary/90"
|
className="block w-full rounded-md bg-primary text-primary-foreground text-center px-4 py-2.5 text-sm font-medium hover:bg-primary/90"
|
||||||
>
|
>
|
||||||
Sign in to admin dashboard
|
Sign in to admin dashboard
|
||||||
</a>
|
</a>
|
||||||
<a
|
<a
|
||||||
href="/"
|
href={`${getPathPrefix()}/`}
|
||||||
className="block w-full rounded-md border border-border text-center px-4 py-2.5 text-sm font-medium hover:bg-muted"
|
className="block w-full rounded-md border border-border text-center px-4 py-2.5 text-sm font-medium hover:bg-muted"
|
||||||
>
|
>
|
||||||
Open webmail login
|
Open webmail login
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ import { getMaxAccounts } from "@/lib/account-utils";
|
|||||||
import { cn, formatFileSize } from "@/lib/utils";
|
import { cn, formatFileSize } from "@/lib/utils";
|
||||||
import { PluginSlot } from "@/components/plugins/plugin-slot";
|
import { PluginSlot } from "@/components/plugins/plugin-slot";
|
||||||
import { KeyboardShortcutsModal } from "@/components/keyboard-shortcuts-modal";
|
import { KeyboardShortcutsModal } from "@/components/keyboard-shortcuts-modal";
|
||||||
import { apiFetch } from "@/lib/browser-navigation";
|
import { apiFetch, getPathPrefix } from "@/lib/browser-navigation";
|
||||||
import { Avatar } from "@/components/ui/avatar";
|
import { Avatar } from "@/components/ui/avatar";
|
||||||
|
|
||||||
interface NavItem {
|
interface NavItem {
|
||||||
@@ -385,7 +385,7 @@ export function NavigationRail({
|
|||||||
{/* Admin (Stalwart admins) - hard nav because /admin lives outside the [locale] tree */}
|
{/* Admin (Stalwart admins) - hard nav because /admin lives outside the [locale] tree */}
|
||||||
{isStalwartAdmin && (
|
{isStalwartAdmin && (
|
||||||
<a
|
<a
|
||||||
href="/admin"
|
href={`${getPathPrefix()}/admin`}
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex flex-col items-center justify-center gap-1 py-2 px-1 min-h-[44px] grow shrink-0 basis-[64px]",
|
"flex flex-col items-center justify-center gap-1 py-2 px-1 min-h-[44px] grow shrink-0 basis-[64px]",
|
||||||
"transition-colors duration-150",
|
"transition-colors duration-150",
|
||||||
@@ -572,7 +572,7 @@ export function NavigationRail({
|
|||||||
<div className="mt-auto flex flex-col items-center gap-2 pb-3 px-1">
|
<div className="mt-auto flex flex-col items-center gap-2 pb-3 px-1">
|
||||||
{isStalwartAdmin && (
|
{isStalwartAdmin && (
|
||||||
<a
|
<a
|
||||||
href="/admin"
|
href={`${getPathPrefix()}/admin`}
|
||||||
className="flex items-center justify-center w-10 h-10 rounded-md transition-colors text-muted-foreground hover:text-foreground hover:bg-muted relative"
|
className="flex items-center justify-center w-10 h-10 rounded-md transition-colors text-muted-foreground hover:text-foreground hover:bg-muted relative"
|
||||||
title={t("admin") || "Admin"}
|
title={t("admin") || "Admin"}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import { usePolicyStore } from '@/stores/policy-store';
|
|||||||
import { useUpdateStore } from '@/stores/update-store';
|
import { useUpdateStore } from '@/stores/update-store';
|
||||||
import { ExternalLink } from 'lucide-react';
|
import { ExternalLink } from 'lucide-react';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
|
import { getPathPrefix } from '@/lib/browser-navigation';
|
||||||
import { SpamSiegeGame } from './spam-siege-game';
|
import { SpamSiegeGame } from './spam-siege-game';
|
||||||
|
|
||||||
const APP_VERSION = process.env.NEXT_PUBLIC_APP_VERSION || "0.0.0";
|
const APP_VERSION = process.env.NEXT_PUBLIC_APP_VERSION || "0.0.0";
|
||||||
@@ -123,12 +124,12 @@ export function AboutDataSettings() {
|
|||||||
<button onClick={handleLogoClick} className="flex items-center gap-4 flex-1 text-left focus:outline-none group/about cursor-pointer" aria-label="About">
|
<button onClick={handleLogoClick} className="flex items-center gap-4 flex-1 text-left focus:outline-none group/about cursor-pointer" aria-label="About">
|
||||||
<div className="shrink-0">
|
<div className="shrink-0">
|
||||||
<img
|
<img
|
||||||
src="/branding/Bulwark_Logo_Color.svg"
|
src={`${getPathPrefix()}/branding/Bulwark_Logo_Color.svg`}
|
||||||
alt="Bulwark"
|
alt="Bulwark"
|
||||||
className="w-12 h-12 object-contain dark:hidden group-hover/about:scale-105 group-active/about:scale-95 transition-transform"
|
className="w-12 h-12 object-contain dark:hidden group-hover/about:scale-105 group-active/about:scale-95 transition-transform"
|
||||||
/>
|
/>
|
||||||
<img
|
<img
|
||||||
src="/branding/Bulwark_Logo_White.svg"
|
src={`${getPathPrefix()}/branding/Bulwark_Logo_White.svg`}
|
||||||
alt="Bulwark"
|
alt="Bulwark"
|
||||||
className="w-12 h-12 object-contain hidden dark:block group-hover/about:scale-105 group-active/about:scale-95 transition-transform"
|
className="w-12 h-12 object-contain hidden dark:block group-hover/about:scale-105 group-active/about:scale-95 transition-transform"
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -10,6 +10,8 @@
|
|||||||
// installing their own code. Verification kicks in for server-managed
|
// installing their own code. Verification kicks in for server-managed
|
||||||
// bundles only (the `managed: true` flag on `InstalledPlugin`).
|
// bundles only (the `managed: true` flag on `InstalledPlugin`).
|
||||||
|
|
||||||
|
import { apiFetch } from '@/lib/browser-navigation';
|
||||||
|
|
||||||
let cachedPubKey: CryptoKey | null = null;
|
let cachedPubKey: CryptoKey | null = null;
|
||||||
let pubKeyPromise: Promise<CryptoKey | null> | null = null;
|
let pubKeyPromise: Promise<CryptoKey | null> | null = null;
|
||||||
|
|
||||||
@@ -26,7 +28,7 @@ async function importEd25519PublicKey(raw: Uint8Array): Promise<CryptoKey | null
|
|||||||
|
|
||||||
async function fetchPublicKey(): Promise<CryptoKey | null> {
|
async function fetchPublicKey(): Promise<CryptoKey | null> {
|
||||||
try {
|
try {
|
||||||
const res = await fetch('/api/plugin-signing-pubkey', { credentials: 'same-origin' });
|
const res = await apiFetch('/api/plugin-signing-pubkey', { credentials: 'same-origin' });
|
||||||
if (!res.ok) return null;
|
if (!res.ok) return null;
|
||||||
const data = await res.json() as { algorithm?: string; publicKey?: string };
|
const data = await res.json() as { algorithm?: string; publicKey?: string };
|
||||||
if (data.algorithm !== 'ed25519' || typeof data.publicKey !== 'string') return null;
|
if (data.algorithm !== 'ed25519' || typeof data.publicKey !== 'string') return null;
|
||||||
|
|||||||
Reference in New Issue
Block a user