fix: server-side UA mobile detection — prevents desktop sidebar flash on mobile before JS hydration
This commit is contained in:
@@ -33,6 +33,7 @@ export function DashboardShell({
|
||||
initialPrefs,
|
||||
pendingMatchCount = 0,
|
||||
role = "member",
|
||||
serverIsMobile = false,
|
||||
}: {
|
||||
user: ShellUser;
|
||||
company: ShellCompany;
|
||||
@@ -40,6 +41,7 @@ export function DashboardShell({
|
||||
initialPrefs: ThemePrefs;
|
||||
pendingMatchCount?: number;
|
||||
role?: ShellRole;
|
||||
serverIsMobile?: boolean;
|
||||
}) {
|
||||
const [themeCustomizerOpen, setThemeCustomizerOpen] = React.useState(false);
|
||||
const { config } = useSidebarConfig();
|
||||
@@ -47,6 +49,8 @@ export function DashboardShell({
|
||||
return (
|
||||
<IconContext.Provider value={{ weight: "bold" }}>
|
||||
<SidebarProvider
|
||||
defaultOpen={!serverIsMobile}
|
||||
defaultIsMobile={serverIsMobile}
|
||||
style={
|
||||
{
|
||||
"--sidebar-width": "16rem",
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { redirect } from "next/navigation";
|
||||
import { headers } from "next/headers";
|
||||
import { Query } from "node-appwrite";
|
||||
|
||||
import { getActiveContext } from "@/lib/appwrite/active-context";
|
||||
@@ -9,11 +10,19 @@ import type { ThemePrefs } from "@/lib/appwrite/theme-prefs-actions";
|
||||
import { PLAN_LIMITS } from "@/lib/plans";
|
||||
import { DashboardShell } from "./dashboard-shell";
|
||||
|
||||
function detectMobileUA(ua: string | null): boolean {
|
||||
if (!ua) return false;
|
||||
return /Android|iPhone|iPad|iPod|Opera Mini|IEMobile|WPDesktop/i.test(ua);
|
||||
}
|
||||
|
||||
export default async function DashboardLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
const hdrs = await headers();
|
||||
const serverIsMobile = detectMobileUA(hdrs.get("user-agent"));
|
||||
|
||||
const sessionUser = await getCurrentUser();
|
||||
if (!sessionUser) redirect("/sign-in");
|
||||
|
||||
@@ -61,7 +70,7 @@ export default async function DashboardLayout({
|
||||
};
|
||||
|
||||
return (
|
||||
<DashboardShell user={user} company={company} initialPrefs={themePrefs} pendingMatchCount={pendingMatchCount} role={ctx.role}>
|
||||
<DashboardShell user={user} company={company} initialPrefs={themePrefs} pendingMatchCount={pendingMatchCount} role={ctx.role} serverIsMobile={serverIsMobile}>
|
||||
{children}
|
||||
</DashboardShell>
|
||||
);
|
||||
|
||||
@@ -54,6 +54,7 @@ function useSidebar() {
|
||||
|
||||
function SidebarProvider({
|
||||
defaultOpen = true,
|
||||
defaultIsMobile = false,
|
||||
open: openProp,
|
||||
onOpenChange: setOpenProp,
|
||||
className,
|
||||
@@ -62,10 +63,11 @@ function SidebarProvider({
|
||||
...props
|
||||
}: React.ComponentProps<"div"> & {
|
||||
defaultOpen?: boolean
|
||||
defaultIsMobile?: boolean
|
||||
open?: boolean
|
||||
onOpenChange?: (open: boolean) => void
|
||||
}) {
|
||||
const isMobile = useIsMobile()
|
||||
const isMobile = useIsMobile(defaultIsMobile)
|
||||
const [openMobile, setOpenMobile] = React.useState(false)
|
||||
|
||||
// This is the internal state of the sidebar.
|
||||
|
||||
+8
-18
@@ -2,26 +2,16 @@ import * as React from "react"
|
||||
|
||||
const MOBILE_BREAKPOINT = 768
|
||||
|
||||
export function useIsMobile() {
|
||||
const [isMobile, setIsMobile] = React.useState<boolean | undefined>(undefined)
|
||||
export function useIsMobile(defaultValue = false) {
|
||||
const [isMobile, setIsMobile] = React.useState<boolean>(defaultValue)
|
||||
|
||||
React.useEffect(() => {
|
||||
const mql = typeof window !== "undefined" ? window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`) : null
|
||||
const onChange = () => {
|
||||
setIsMobile(typeof window !== "undefined" ? window.innerWidth < MOBILE_BREAKPOINT : false)
|
||||
}
|
||||
|
||||
if (mql) {
|
||||
mql.addEventListener("change", onChange)
|
||||
}
|
||||
setIsMobile(typeof window !== "undefined" ? window.innerWidth < MOBILE_BREAKPOINT : false)
|
||||
|
||||
return () => {
|
||||
if (mql) {
|
||||
mql.removeEventListener("change", onChange)
|
||||
}
|
||||
}
|
||||
const update = () => setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
|
||||
const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`)
|
||||
mql.addEventListener("change", update)
|
||||
update()
|
||||
return () => mql.removeEventListener("change", update)
|
||||
}, [])
|
||||
|
||||
return !!isMobile
|
||||
return isMobile
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user