refactor: migrate to pnpm monorepo with Payload CMS backend and Astro frontend to support scalable website development and AI-assisted workflows
This commit is contained in:
42
apps/backend/src/Header/Component.client.tsx
Normal file
42
apps/backend/src/Header/Component.client.tsx
Normal file
@@ -0,0 +1,42 @@
|
||||
'use client'
|
||||
import { useHeaderTheme } from '@/providers/HeaderTheme'
|
||||
import Link from 'next/link'
|
||||
import { usePathname } from 'next/navigation'
|
||||
import React, { useEffect, useState } from 'react'
|
||||
|
||||
import type { Header } from '@/payload-types'
|
||||
|
||||
import { Logo } from '@/components/Logo/Logo'
|
||||
import { HeaderNav } from './Nav'
|
||||
|
||||
interface HeaderClientProps {
|
||||
data: Header
|
||||
}
|
||||
|
||||
export const HeaderClient: React.FC<HeaderClientProps> = ({ data }) => {
|
||||
/* Storing the value in a useState to avoid hydration errors */
|
||||
const [theme, setTheme] = useState<string | null>(null)
|
||||
const { headerTheme, setHeaderTheme } = useHeaderTheme()
|
||||
const pathname = usePathname()
|
||||
|
||||
useEffect(() => {
|
||||
setHeaderTheme(null)
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [pathname])
|
||||
|
||||
useEffect(() => {
|
||||
if (headerTheme && headerTheme !== theme) setTheme(headerTheme)
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [headerTheme])
|
||||
|
||||
return (
|
||||
<header className="container relative z-20 " {...(theme ? { 'data-theme': theme } : {})}>
|
||||
<div className="py-8 flex justify-between">
|
||||
<Link href="/">
|
||||
<Logo loading="eager" priority="high" className="invert dark:invert-0" />
|
||||
</Link>
|
||||
<HeaderNav data={data} />
|
||||
</div>
|
||||
</header>
|
||||
)
|
||||
}
|
||||
11
apps/backend/src/Header/Component.tsx
Normal file
11
apps/backend/src/Header/Component.tsx
Normal file
@@ -0,0 +1,11 @@
|
||||
import { HeaderClient } from './Component.client'
|
||||
import { getCachedGlobal } from '@/utilities/getGlobals'
|
||||
import React from 'react'
|
||||
|
||||
import type { Header } from '@/payload-types'
|
||||
|
||||
export async function Header() {
|
||||
const headerData: Header = await getCachedGlobal('header', 1)()
|
||||
|
||||
return <HeaderClient data={headerData} />
|
||||
}
|
||||
25
apps/backend/src/Header/Nav/index.tsx
Normal file
25
apps/backend/src/Header/Nav/index.tsx
Normal file
@@ -0,0 +1,25 @@
|
||||
'use client'
|
||||
|
||||
import React from 'react'
|
||||
|
||||
import type { Header as HeaderType } from '@/payload-types'
|
||||
|
||||
import { CMSLink } from '@/components/Link'
|
||||
import Link from 'next/link'
|
||||
import { SearchIcon } from 'lucide-react'
|
||||
|
||||
export const HeaderNav: React.FC<{ data: HeaderType }> = ({ data }) => {
|
||||
const navItems = data?.navItems || []
|
||||
|
||||
return (
|
||||
<nav className="flex gap-3 items-center">
|
||||
{navItems.map(({ link }, i) => {
|
||||
return <CMSLink key={i} {...link} appearance="link" />
|
||||
})}
|
||||
<Link href="/search">
|
||||
<span className="sr-only">Search</span>
|
||||
<SearchIcon className="w-5 text-primary" />
|
||||
</Link>
|
||||
</nav>
|
||||
)
|
||||
}
|
||||
13
apps/backend/src/Header/RowLabel.tsx
Normal file
13
apps/backend/src/Header/RowLabel.tsx
Normal file
@@ -0,0 +1,13 @@
|
||||
'use client'
|
||||
import { Header } from '@/payload-types'
|
||||
import { RowLabelProps, useRowLabel } from '@payloadcms/ui'
|
||||
|
||||
export const RowLabel: React.FC<RowLabelProps> = () => {
|
||||
const data = useRowLabel<NonNullable<Header['navItems']>[number]>()
|
||||
|
||||
const label = data?.data?.link?.label
|
||||
? `Nav item ${data.rowNumber !== undefined ? data.rowNumber + 1 : ''}: ${data?.data?.link?.label}`
|
||||
: 'Row'
|
||||
|
||||
return <div>{label}</div>
|
||||
}
|
||||
32
apps/backend/src/Header/config.ts
Normal file
32
apps/backend/src/Header/config.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
import type { GlobalConfig } from 'payload'
|
||||
|
||||
import { link } from '@/fields/link'
|
||||
import { revalidateHeader } from './hooks/revalidateHeader'
|
||||
|
||||
export const Header: GlobalConfig = {
|
||||
slug: 'header',
|
||||
access: {
|
||||
read: () => true,
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'navItems',
|
||||
type: 'array',
|
||||
fields: [
|
||||
link({
|
||||
appearances: false,
|
||||
}),
|
||||
],
|
||||
maxRows: 6,
|
||||
admin: {
|
||||
initCollapsed: true,
|
||||
components: {
|
||||
RowLabel: '@/Header/RowLabel#RowLabel',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
hooks: {
|
||||
afterChange: [revalidateHeader],
|
||||
},
|
||||
}
|
||||
13
apps/backend/src/Header/hooks/revalidateHeader.ts
Normal file
13
apps/backend/src/Header/hooks/revalidateHeader.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import type { GlobalAfterChangeHook } from 'payload'
|
||||
|
||||
import { revalidateTag } from 'next/cache'
|
||||
|
||||
export const revalidateHeader: GlobalAfterChangeHook = ({ doc, req: { payload, context } }) => {
|
||||
if (!context.disableRevalidate) {
|
||||
payload.logger.info(`Revalidating header`)
|
||||
|
||||
revalidateTag('global_header')
|
||||
}
|
||||
|
||||
return doc
|
||||
}
|
||||
Reference in New Issue
Block a user