migrate header info back to client-side

This commit is contained in:
Max Leiter 2023-05-20 15:41:53 -07:00
parent dc11f8eb0c
commit c416f5d5e8
11 changed files with 79 additions and 108 deletions

View file

@ -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: {

View file

@ -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()
})
router.push("/new")
}
}
@ -75,10 +72,7 @@ function Auth({
return (
<div className={styles.container}>
{/* Suspense boundary because useSearchParams causes static bailout */}
<Suspense fallback={null}>
<ErrorQueryParamsHandler />
</Suspense>
<ErrorQueryParamsHandler />
<div className={styles.form}>
<div className={styles.formContentSpace}>
<h1>Sign {signText}</h1>

View file

@ -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>
)
}

View file

@ -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>

View file

@ -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 />}
>

View file

@ -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 (
<NavButton
name="Theme"
icon={theme === "dark" ? <Sun /> : <Moon />}
value="dark"
onClick={() => {
setDriftTheme(theme === "dark" ? "light" : "dark", setTheme)
}}
key="theme"
/>
<>
{!mounted && (
<NavButton
name="Theme"
icon={<Circle opacity={0.3} />}
value="dark"
href=""
key="theme"
/>
)}
{mounted && (
<NavButton
name="Theme"
icon={
<FadeIn>{resolvedTheme === "dark" ? <Sun /> : <Moon />}</FadeIn>
}
value="dark"
onClick={() => {
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}
/>
)}
</>
)
}

View file

@ -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>
)
}

View file

@ -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}

View file

@ -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: "/" })
}

View file

@ -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 = [

View file

@ -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
})