migrate header info back to client-side
This commit is contained in:
parent
dc11f8eb0c
commit
c416f5d5e8
11 changed files with 79 additions and 108 deletions
|
@ -4,15 +4,14 @@ import bundleAnalyzer from "@next/bundle-analyzer"
|
|||
const nextConfig = {
|
||||
reactStrictMode: true,
|
||||
experimental: {
|
||||
// esmExternals: true,
|
||||
appDir: true,
|
||||
appDir: true
|
||||
},
|
||||
rewrites() {
|
||||
return [
|
||||
{
|
||||
source: "/file/raw/:id",
|
||||
destination: `/api/raw/:id`
|
||||
},
|
||||
}
|
||||
]
|
||||
},
|
||||
images: {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
"use client"
|
||||
|
||||
import { startTransition, Suspense, useState } from "react"
|
||||
import { useState } from "react"
|
||||
import styles from "./auth.module.css"
|
||||
import Link from "../../../components/link"
|
||||
import { signIn } from "next-auth/react"
|
||||
|
@ -52,10 +52,7 @@ function Auth({
|
|||
})
|
||||
setSubmitting(false)
|
||||
} else {
|
||||
startTransition(() => {
|
||||
router.push("/new")
|
||||
router.refresh()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -75,10 +72,7 @@ function Auth({
|
|||
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
{/* Suspense boundary because useSearchParams causes static bailout */}
|
||||
<Suspense fallback={null}>
|
||||
<ErrorQueryParamsHandler />
|
||||
</Suspense>
|
||||
<div className={styles.form}>
|
||||
<div className={styles.formContentSpace}>
|
||||
<h1>Sign {signText}</h1>
|
||||
|
|
|
@ -2,9 +2,9 @@
|
|||
|
||||
import { useToasts } from "@components/toasts"
|
||||
import { useSearchParams } from "next/navigation"
|
||||
import { useEffect } from "react"
|
||||
import { Suspense, useEffect } from "react"
|
||||
|
||||
export function ErrorQueryParamsHandler() {
|
||||
function _ErrorQueryParamsHandler() {
|
||||
const queryParams = useSearchParams()
|
||||
const { setToast } = useToasts()
|
||||
|
||||
|
@ -19,3 +19,12 @@ export function ErrorQueryParamsHandler() {
|
|||
|
||||
return null
|
||||
}
|
||||
|
||||
export function ErrorQueryParamsHandler() {
|
||||
/* Suspense boundary because useSearchParams causes static bailout */
|
||||
return (
|
||||
<Suspense fallback={null}>
|
||||
<_ErrorQueryParamsHandler />
|
||||
</Suspense>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -6,10 +6,7 @@ import Header from "@components/header"
|
|||
import { Inter } from "next/font/google"
|
||||
import { getMetadata } from "src/app/lib/metadata"
|
||||
import dynamic from "next/dynamic"
|
||||
import { cookies } from "next/headers"
|
||||
const inter = Inter({ subsets: ["latin"], variable: "--inter-font" })
|
||||
import { THEME_COOKIE, DEFAULT_THEME, SIGNED_IN_COOKIE } from "@lib/constants"
|
||||
import { Suspense } from "react"
|
||||
|
||||
const CmdK = dynamic(() => import("@components/cmdk"), { ssr: false })
|
||||
|
||||
|
@ -18,10 +15,6 @@ export default async function RootLayout({
|
|||
}: {
|
||||
children: React.ReactNode
|
||||
}) {
|
||||
const cookiesList = cookies()
|
||||
const theme = cookiesList.get(THEME_COOKIE)?.value || DEFAULT_THEME
|
||||
const isAuthenticated = Boolean(cookiesList.get(SIGNED_IN_COOKIE)?.value)
|
||||
|
||||
return (
|
||||
// suppressHydrationWarning is required because of next-themes
|
||||
<html lang="en" className={inter.variable} suppressHydrationWarning>
|
||||
|
@ -30,9 +23,7 @@ export default async function RootLayout({
|
|||
<Providers>
|
||||
<Layout>
|
||||
<CmdK />
|
||||
<Suspense fallback={<>Loading...</>}>
|
||||
<Header theme={theme} isAuthenticated={isAuthenticated} />
|
||||
</Suspense>
|
||||
<Header />
|
||||
{children}
|
||||
</Layout>
|
||||
</Providers>
|
||||
|
|
|
@ -2,7 +2,6 @@ import { Command } from "cmdk"
|
|||
import { useTheme } from "next-themes"
|
||||
import { useRouter } from "next/navigation"
|
||||
import { FilePlus, Moon, Search, Settings, Sun } from "react-feather"
|
||||
import { setDriftTheme } from "src/app/lib/set-theme"
|
||||
import { CmdKPage } from ".."
|
||||
import Item from "../item"
|
||||
|
||||
|
@ -42,7 +41,7 @@ export default function HomePage({
|
|||
<Item
|
||||
shortcut="T"
|
||||
onSelect={() => {
|
||||
setDriftTheme(resolvedTheme === "dark" ? "light" : "dark", setTheme)
|
||||
setTheme(resolvedTheme === "dark" ? "light" : "dark")
|
||||
}}
|
||||
icon={resolvedTheme === "dark" ? <Sun /> : <Moon />}
|
||||
>
|
||||
|
|
|
@ -2,8 +2,8 @@
|
|||
|
||||
import { useSelectedLayoutSegments } from "next/navigation"
|
||||
import FadeIn from "@components/fade-in"
|
||||
import { setDriftTheme } from "src/app/lib/set-theme"
|
||||
import {
|
||||
Circle,
|
||||
Home,
|
||||
Moon,
|
||||
PlusCircle,
|
||||
|
@ -18,6 +18,7 @@ import Link from "@components/link"
|
|||
import { useSessionSWR } from "@lib/use-session-swr"
|
||||
import { useTheme } from "next-themes"
|
||||
import styles from "./buttons.module.css"
|
||||
import { useEffect, useState } from "react"
|
||||
|
||||
// constant width for sign in / sign out buttons to avoid CLS
|
||||
const SIGN_IN_WIDTH = 110
|
||||
|
@ -38,27 +39,6 @@ type Tab = {
|
|||
}
|
||||
)
|
||||
|
||||
export function HeaderButtons({
|
||||
isAuthenticated,
|
||||
theme: initialTheme
|
||||
}: {
|
||||
isAuthenticated: boolean
|
||||
theme: string
|
||||
}) {
|
||||
const { isAdmin, userId } = useSessionSWR()
|
||||
const { resolvedTheme } = useTheme()
|
||||
return (
|
||||
<>
|
||||
{getButtons({
|
||||
isAuthenticated,
|
||||
theme: resolvedTheme ? resolvedTheme : initialTheme,
|
||||
isAdmin,
|
||||
userId
|
||||
})}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
function NavButton(tab: Tab) {
|
||||
const segment = useSelectedLayoutSegments().slice(-1)[0]
|
||||
const isActive = segment === tab.value.toLowerCase()
|
||||
|
@ -89,32 +69,51 @@ function NavButton(tab: Tab) {
|
|||
}
|
||||
}
|
||||
|
||||
function ThemeButton({ theme }: { theme: string }) {
|
||||
const { setTheme } = useTheme()
|
||||
function ThemeButton() {
|
||||
const { setTheme, resolvedTheme } = useTheme()
|
||||
const [mounted, setMounted] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
setMounted(true)
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<>
|
||||
{!mounted && (
|
||||
<NavButton
|
||||
name="Theme"
|
||||
icon={theme === "dark" ? <Sun /> : <Moon />}
|
||||
icon={<Circle opacity={0.3} />}
|
||||
value="dark"
|
||||
href=""
|
||||
key="theme"
|
||||
/>
|
||||
)}
|
||||
{mounted && (
|
||||
<NavButton
|
||||
name="Theme"
|
||||
icon={
|
||||
<FadeIn>{resolvedTheme === "dark" ? <Sun /> : <Moon />}</FadeIn>
|
||||
}
|
||||
value="dark"
|
||||
onClick={() => {
|
||||
setDriftTheme(theme === "dark" ? "light" : "dark", setTheme)
|
||||
setTheme(resolvedTheme === "dark" ? "light" : "dark")
|
||||
}}
|
||||
key="theme"
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export function getButtons({
|
||||
isAuthenticated,
|
||||
theme,
|
||||
isAdmin,
|
||||
userId
|
||||
}: {
|
||||
isAuthenticated: boolean
|
||||
theme: string
|
||||
isAdmin?: boolean
|
||||
userId?: string
|
||||
}) {
|
||||
export function HeaderButtons(): JSX.Element {
|
||||
const { isAdmin, isAuthenticated, userId } = useSessionSWR()
|
||||
|
||||
useEffect(() => {
|
||||
if (isAuthenticated && !userId) {
|
||||
signOut()
|
||||
}
|
||||
}, [isAuthenticated, userId])
|
||||
|
||||
return (
|
||||
<>
|
||||
<NavButton
|
||||
|
@ -145,7 +144,7 @@ export function getButtons({
|
|||
href="/settings"
|
||||
key="settings"
|
||||
/>
|
||||
<ThemeButton key="theme-button" theme={theme} />
|
||||
<ThemeButton key="theme-button" />
|
||||
{isAdmin && (
|
||||
<FadeIn>
|
||||
<NavButton
|
||||
|
@ -181,6 +180,16 @@ export function getButtons({
|
|||
width={SIGN_IN_WIDTH}
|
||||
/>
|
||||
)}
|
||||
{isAuthenticated === undefined && (
|
||||
<NavButton
|
||||
name="Sign"
|
||||
key="signin"
|
||||
icon={<User />}
|
||||
value="signin"
|
||||
href="/signin"
|
||||
width={SIGN_IN_WIDTH}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -2,21 +2,15 @@ import styles from "./header.module.css"
|
|||
import { HeaderButtons } from "./buttons"
|
||||
import MobileHeader from "./mobile"
|
||||
|
||||
export default function Header({
|
||||
theme,
|
||||
isAuthenticated
|
||||
}: {
|
||||
theme: string
|
||||
isAuthenticated: boolean
|
||||
}) {
|
||||
export default function Header() {
|
||||
return (
|
||||
<header className={styles.header}>
|
||||
<div className={styles.tabs}>
|
||||
<div className={styles.buttons}>
|
||||
<HeaderButtons isAuthenticated={isAuthenticated} theme={theme} />
|
||||
<HeaderButtons />
|
||||
</div>
|
||||
</div>
|
||||
<MobileHeader isAuthenticated={isAuthenticated} theme={theme} />
|
||||
<MobileHeader />
|
||||
</header>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -6,23 +6,9 @@ import Button from "@components/button"
|
|||
import { Menu } from "react-feather"
|
||||
import clsx from "clsx"
|
||||
import styles from "./mobile.module.css"
|
||||
import { getButtons } from "./buttons"
|
||||
import { useSessionSWR } from "@lib/use-session-swr"
|
||||
|
||||
export default function MobileHeader({
|
||||
isAuthenticated,
|
||||
theme
|
||||
}: {
|
||||
isAuthenticated: boolean
|
||||
theme: string
|
||||
}) {
|
||||
const { isAdmin } = useSessionSWR()
|
||||
const buttons = getButtons({
|
||||
isAuthenticated,
|
||||
theme,
|
||||
isAdmin
|
||||
})
|
||||
import { HeaderButtons } from "./buttons"
|
||||
|
||||
export default function MobileHeader() {
|
||||
// TODO: this is a hack to close the radix ui menu when a next link is clicked
|
||||
const onClick = () => {
|
||||
document.dispatchEvent(new KeyboardEvent("keydown", { key: "Escape" }))
|
||||
|
@ -40,7 +26,7 @@ export default function MobileHeader({
|
|||
</DropdownMenu.Trigger>
|
||||
<DropdownMenu.Portal>
|
||||
<DropdownMenu.Content>
|
||||
{buttons.props.children.map((button: JSX.Element) => (
|
||||
{HeaderButtons().props.children.map((button: JSX.Element) => (
|
||||
<DropdownMenu.Item
|
||||
key={`mobile-${button?.key}`}
|
||||
className={styles.dropdownItem}
|
||||
|
|
|
@ -1,9 +0,0 @@
|
|||
import { THEME_COOKIE } from "@lib/constants"
|
||||
import { Cookies } from "react-cookie"
|
||||
|
||||
const cookies = new Cookies()
|
||||
|
||||
export function setDriftTheme(theme: string, setter: (theme: string) => void) {
|
||||
setter(theme)
|
||||
cookies.set(THEME_COOKIE, theme, { path: "/" })
|
||||
}
|
|
@ -10,7 +10,6 @@ export function isAllowedVisibilityForWebpage(
|
|||
export const DEFAULT_THEME = "dark"
|
||||
|
||||
export const SIGNED_IN_COOKIE = "next-auth.session-token"
|
||||
export const THEME_COOKIE = "drift-theme"
|
||||
|
||||
// Code files for uploading with drag and drop and syntax highlighting
|
||||
export const allowedFileTypes = [
|
||||
|
|
|
@ -10,7 +10,7 @@ export function useSessionSWR(swrOpts: SWRConfiguration = {}) {
|
|||
mutate
|
||||
} = useSWR<Session>("/api/auth/session", {
|
||||
fetcher: (url) => fetch(url).then((res) => res.json()) as Promise<Session>,
|
||||
revalidateOnFocus: false,
|
||||
revalidateOnFocus: true,
|
||||
...swrOpts
|
||||
})
|
||||
|
||||
|
|
Loading…
Reference in a new issue