added adminbar to layout

This commit is contained in:
2025-10-10 13:35:09 -04:00
parent d8fbcbbf4d
commit 8bf4c7516f
13 changed files with 157 additions and 17 deletions

View File

@@ -3,6 +3,7 @@
"workspaces": { "workspaces": {
"": { "": {
"dependencies": { "dependencies": {
"@payloadcms/admin-bar": "^3.59.1",
"@payloadcms/db-postgres": "3.59.1", "@payloadcms/db-postgres": "3.59.1",
"@payloadcms/next": "3.59.1", "@payloadcms/next": "3.59.1",
"@payloadcms/payload-cloud": "3.59.1", "@payloadcms/payload-cloud": "3.59.1",
@@ -16,6 +17,7 @@
"react": "19.1.0", "react": "19.1.0",
"react-dom": "19.1.0", "react-dom": "19.1.0",
"sharp": "0.34.2", "sharp": "0.34.2",
"tailwind-merge": "^3.3.1",
}, },
"devDependencies": { "devDependencies": {
"@playwright/test": "1.54.1", "@playwright/test": "1.54.1",
@@ -455,6 +457,8 @@
"@nolyfill/is-core-module": ["@nolyfill/is-core-module@1.0.39", "", {}, "sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA=="], "@nolyfill/is-core-module": ["@nolyfill/is-core-module@1.0.39", "", {}, "sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA=="],
"@payloadcms/admin-bar": ["@payloadcms/admin-bar@3.59.1", "", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-gaOP7fWypaNHUfxLhdgmKV9qfrKmRUOjy32deBE/75lOSTtrZfOgMbq6W5cVLYl0yjV7xlSFoINIWmawmkQCjA=="],
"@payloadcms/db-postgres": ["@payloadcms/db-postgres@3.59.1", "", { "dependencies": { "@payloadcms/drizzle": "3.59.1", "@types/pg": "8.10.2", "console-table-printer": "2.12.1", "drizzle-kit": "0.31.4", "drizzle-orm": "0.44.2", "pg": "8.16.3", "prompts": "2.4.2", "to-snake-case": "1.0.0", "uuid": "10.0.0" }, "peerDependencies": { "payload": "3.59.1" } }, "sha512-lTxItMJ8wDt9gQ19zFRV2XJBsuoMtQiEOtHNBxULu4SVdee0vdcuRFZmjQYB5tZ0VOUEp96sCx+aMUh7RwRmzw=="], "@payloadcms/db-postgres": ["@payloadcms/db-postgres@3.59.1", "", { "dependencies": { "@payloadcms/drizzle": "3.59.1", "@types/pg": "8.10.2", "console-table-printer": "2.12.1", "drizzle-kit": "0.31.4", "drizzle-orm": "0.44.2", "pg": "8.16.3", "prompts": "2.4.2", "to-snake-case": "1.0.0", "uuid": "10.0.0" }, "peerDependencies": { "payload": "3.59.1" } }, "sha512-lTxItMJ8wDt9gQ19zFRV2XJBsuoMtQiEOtHNBxULu4SVdee0vdcuRFZmjQYB5tZ0VOUEp96sCx+aMUh7RwRmzw=="],
"@payloadcms/drizzle": ["@payloadcms/drizzle@3.59.1", "", { "dependencies": { "console-table-printer": "2.12.1", "dequal": "2.0.3", "drizzle-orm": "0.44.2", "prompts": "2.4.2", "to-snake-case": "1.0.0", "uuid": "9.0.0" }, "peerDependencies": { "payload": "3.59.1" } }, "sha512-2n25PQfbFJ7uIlQxLilf8lrdbIGJ9p56ZNtViHl57DCq8SYGcB7Rz+5MSNdGwMnK28oYMAObJ4bBI5JG9DhR/g=="], "@payloadcms/drizzle": ["@payloadcms/drizzle@3.59.1", "", { "dependencies": { "console-table-printer": "2.12.1", "dequal": "2.0.3", "drizzle-orm": "0.44.2", "prompts": "2.4.2", "to-snake-case": "1.0.0", "uuid": "9.0.0" }, "peerDependencies": { "payload": "3.59.1" } }, "sha512-2n25PQfbFJ7uIlQxLilf8lrdbIGJ9p56ZNtViHl57DCq8SYGcB7Rz+5MSNdGwMnK28oYMAObJ4bBI5JG9DhR/g=="],
@@ -1707,6 +1711,8 @@
"tabbable": ["tabbable@6.2.0", "", {}, "sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew=="], "tabbable": ["tabbable@6.2.0", "", {}, "sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew=="],
"tailwind-merge": ["tailwind-merge@3.3.1", "", {}, "sha512-gBXpgUm/3rp1lMZZrM/w7D8GKqshif0zAymAhbCyIt8KMe+0v9DQ7cdYLR4FHH/cKpdTXb+A/tKKU3eolfsI+g=="],
"thread-stream": ["thread-stream@3.1.0", "", { "dependencies": { "real-require": "^0.2.0" } }, "sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A=="], "thread-stream": ["thread-stream@3.1.0", "", { "dependencies": { "real-require": "^0.2.0" } }, "sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A=="],
"tinybench": ["tinybench@2.9.0", "", {}, "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg=="], "tinybench": ["tinybench@2.9.0", "", {}, "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg=="],

View File

@@ -18,6 +18,8 @@
"test:int": "cross-env NODE_OPTIONS=--no-deprecation vitest run --config ./vitest.config.mts" "test:int": "cross-env NODE_OPTIONS=--no-deprecation vitest run --config ./vitest.config.mts"
}, },
"dependencies": { "dependencies": {
"@payloadcms/admin-bar": "^3.59.1",
"@payloadcms/db-postgres": "3.59.1",
"@payloadcms/next": "3.59.1", "@payloadcms/next": "3.59.1",
"@payloadcms/payload-cloud": "3.59.1", "@payloadcms/payload-cloud": "3.59.1",
"@payloadcms/richtext-lexical": "3.59.1", "@payloadcms/richtext-lexical": "3.59.1",
@@ -30,7 +32,7 @@
"react": "19.1.0", "react": "19.1.0",
"react-dom": "19.1.0", "react-dom": "19.1.0",
"sharp": "0.34.2", "sharp": "0.34.2",
"@payloadcms/db-postgres": "3.59.1" "tailwind-merge": "^3.3.1"
}, },
"devDependencies": { "devDependencies": {
"@playwright/test": "1.54.1", "@playwright/test": "1.54.1",

View File

@@ -1,5 +1,6 @@
import React from 'react' import React from 'react'
import './styles.css' import './styles.css'
import { AdminBar } from '@/src/components/AdminBar'
export const metadata = { export const metadata = {
description: 'A blank template using Payload in a Next.js app.', description: 'A blank template using Payload in a Next.js app.',
@@ -12,6 +13,7 @@ export default async function RootLayout(props: { children: React.ReactNode }) {
return ( return (
<html lang="en"> <html lang="en">
<body> <body>
<AdminBar />
<main>{children}</main> <main>{children}</main>
</body> </body>
</html> </html>

View File

@@ -3,9 +3,8 @@ import Image from 'next/image'
import { getPayload } from 'payload' import { getPayload } from 'payload'
import type { Payload } from 'payload' import type { Payload } from 'payload'
import React from 'react' import React from 'react'
import { fileURLToPath } from 'url'
import config from '@/payload.config' import config from '@/src/payload.config'
import './styles.css' import './styles.css'
import { RichText } from '@payloadcms/richtext-lexical/react' import { RichText } from '@payloadcms/richtext-lexical/react'
@@ -15,8 +14,6 @@ export default async function HomePage() {
const payload = await getPayload({ config: payloadConfig }) const payload = await getPayload({ config: payloadConfig })
const { user } = await payload.auth({ headers }) const { user } = await payload.auth({ headers })
const fileURL = `vscode://file/${fileURLToPath(import.meta.url)}`
const homePage = await payload.find({ const homePage = await payload.find({
collection: 'pages', collection: 'pages',
where: { where: {
@@ -28,8 +25,6 @@ export default async function HomePage() {
const page = homePage.docs?.[0] const page = homePage.docs?.[0]
console.log(page)
return ( return (
<div className="home"> <div className="home">
<div className="content"> <div className="content">
@@ -53,7 +48,7 @@ export default async function HomePage() {
/> />
</picture> </picture>
{!user && <h1>Welcome to your new project.</h1>} {!user && <h1>Welcome to your new project.</h1>}
{user && <h1>Welcome back, {user.email}</h1>} {user && <h1>Welcome back, {user.name}</h1>}
<div className="links"> <div className="links">
<a <a
className="admin" className="admin"
@@ -75,9 +70,7 @@ export default async function HomePage() {
</div> </div>
<div className="footer"> <div className="footer">
<p>Update this page by editing</p> <p>Update this page by editing</p>
<a className="codeLink" href={fileURL}> <code>app/(frontend)/page.tsx</code>
<code>app/(frontend)/page.tsx</code>
</a>
</div> </div>
</div> </div>
) )

View File

@@ -22,6 +22,7 @@ import { StrikethroughFeatureClient as StrikethroughFeatureClient_e70f5e05f09f93
import { UnderlineFeatureClient as UnderlineFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client' import { UnderlineFeatureClient as UnderlineFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client'
import { BoldFeatureClient as BoldFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client' import { BoldFeatureClient as BoldFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client'
import { ItalicFeatureClient as ItalicFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client' import { ItalicFeatureClient as ItalicFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client'
import { SlugField as SlugField_3817bf644402e67bfe6577f60ef982de } from '@payloadcms/ui'
export const importMap = { export const importMap = {
"@payloadcms/richtext-lexical/rsc#RscEntryLexicalCell": RscEntryLexicalCell_44fe37237e0ebf4470c9990d8cb7b07e, "@payloadcms/richtext-lexical/rsc#RscEntryLexicalCell": RscEntryLexicalCell_44fe37237e0ebf4470c9990d8cb7b07e,
@@ -47,5 +48,6 @@ export const importMap = {
"@payloadcms/richtext-lexical/client#StrikethroughFeatureClient": StrikethroughFeatureClient_e70f5e05f09f93e00b997edb1ef0c864, "@payloadcms/richtext-lexical/client#StrikethroughFeatureClient": StrikethroughFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
"@payloadcms/richtext-lexical/client#UnderlineFeatureClient": UnderlineFeatureClient_e70f5e05f09f93e00b997edb1ef0c864, "@payloadcms/richtext-lexical/client#UnderlineFeatureClient": UnderlineFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
"@payloadcms/richtext-lexical/client#BoldFeatureClient": BoldFeatureClient_e70f5e05f09f93e00b997edb1ef0c864, "@payloadcms/richtext-lexical/client#BoldFeatureClient": BoldFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
"@payloadcms/richtext-lexical/client#ItalicFeatureClient": ItalicFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 "@payloadcms/richtext-lexical/client#ItalicFeatureClient": ItalicFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
"@payloadcms/ui#SlugField": SlugField_3817bf644402e67bfe6577f60ef982de
} }

View File

@@ -1,5 +1,6 @@
import { lexicalEditor } from '@payloadcms/richtext-lexical' import { lexicalEditor } from '@payloadcms/richtext-lexical'
import type { CollectionConfig } from 'payload' import type { CollectionConfig } from 'payload'
import { slugField } from 'payload'
export const Page: CollectionConfig = { export const Page: CollectionConfig = {
slug: 'pages', slug: 'pages',
@@ -7,9 +8,6 @@ export const Page: CollectionConfig = {
title: true, title: true,
slug: true, slug: true,
}, },
access: {
read: () => true,
},
admin: { admin: {
defaultColumns: ['title', 'slug', 'updatedAt',], defaultColumns: ['title', 'slug', 'updatedAt',],
}, },
@@ -22,6 +20,7 @@ export const Page: CollectionConfig = {
{ {
name: 'richText', name: 'richText',
type: 'richText' type: 'richText'
} },
slugField(),
], ],
} }

View File

@@ -0,0 +1,7 @@
@import '~@payloadcms/ui/scss';
.admin-bar {
@include small-break {
display: none;
}
}

View File

@@ -0,0 +1,89 @@
'use client'
import type { PayloadAdminBarProps, PayloadMeUser } from '@payloadcms/admin-bar'
import { cn } from '@/utilities/ui'
import { useSelectedLayoutSegments } from 'next/navigation'
import { PayloadAdminBar } from '@payloadcms/admin-bar'
import React, { useState } from 'react'
import { useRouter } from 'next/navigation'
import './index.scss'
import { getClientSideURL } from '@/utilities/getURL'
const baseClass = 'admin-bar'
const collectionLabels = {
pages: {
plural: 'Pages',
singular: 'Page',
},
posts: {
plural: 'Posts',
singular: 'Post',
},
projects: {
plural: 'Projects',
singular: 'Project',
},
}
const Title: React.FC = () => <span>Dashboard</span>
export const AdminBar: React.FC<{
adminBarProps?: PayloadAdminBarProps
}> = (props) => {
const { adminBarProps } = props || {}
const segments = useSelectedLayoutSegments()
const [show, setShow] = useState(false)
const collection = (
collectionLabels[segments?.[1] as keyof typeof collectionLabels] ? segments[1] : 'pages'
) as keyof typeof collectionLabels
const router = useRouter()
const onAuthChange = React.useCallback((user: PayloadMeUser) => {
setShow(Boolean(user?.id))
}, [])
return (
<div
className={cn(baseClass, 'py-2 bg-black text-white', {
block: show,
hidden: !show,
})}
>
<div className="container">
<PayloadAdminBar
{...adminBarProps}
className="py-2 text-white"
classNames={{
controls: 'font-medium text-white',
logo: 'text-white',
user: 'text-white',
}}
cmsURL={getClientSideURL()}
collectionSlug={collection}
collectionLabels={{
plural: collectionLabels[collection]?.plural || 'Pages',
singular: collectionLabels[collection]?.singular || 'Page',
}}
logo={<Title />}
onAuthChange={onAuthChange}
onPreviewExit={() => {
fetch('/next/exit-preview').then(() => {
router.push('/')
router.refresh()
})
}}
style={{
backgroundColor: 'transparent',
padding: 0,
position: 'relative',
zIndex: 'unset',
}}
/>
</div>
</div>
)
}

View File

@@ -182,6 +182,11 @@ export interface Page {
}; };
[k: string]: unknown; [k: string]: unknown;
} | null; } | null;
/**
* When enabled, the slug will auto-generate from the title field on save and autosave.
*/
generateSlug?: boolean | null;
slug: string;
updatedAt: string; updatedAt: string;
createdAt: string; createdAt: string;
} }
@@ -295,6 +300,8 @@ export interface MediaSelect<T extends boolean = true> {
export interface PagesSelect<T extends boolean = true> { export interface PagesSelect<T extends boolean = true> {
title?: T; title?: T;
richText?: T; richText?: T;
generateSlug?: T;
slug?: T;
updatedAt?: T; updatedAt?: T;
createdAt?: T; createdAt?: T;
} }

View File

@@ -24,7 +24,7 @@
], ],
"paths": { "paths": {
"@/*": [ "@/*": [
"./src/*" "./*"
], ],
"@payload-config": [ "@payload-config": [
"./src/payload.config.ts" "./src/payload.config.ts"

1
utilities/canUseDOM.ts Normal file
View File

@@ -0,0 +1 @@
export default !!(typeof window !== 'undefined' && window.document && window.document.createElement)

26
utilities/getURL.ts Normal file
View File

@@ -0,0 +1,26 @@
import canUseDOM from './canUseDOM'
export const getServerSideURL = () => {
return (
process.env.NEXT_PUBLIC_SERVER_URL ||
(process.env.VERCEL_PROJECT_PRODUCTION_URL
? `https://${process.env.VERCEL_PROJECT_PRODUCTION_URL}`
: 'http://localhost:3000')
)
}
export const getClientSideURL = () => {
if (canUseDOM) {
const protocol = window.location.protocol
const domain = window.location.hostname
const port = window.location.port
return `${protocol}//${domain}${port ? `:${port}` : ''}`
}
if (process.env.VERCEL_PROJECT_PRODUCTION_URL) {
return `https://${process.env.VERCEL_PROJECT_PRODUCTION_URL}`
}
return process.env.NEXT_PUBLIC_SERVER_URL || ''
}

6
utilities/ui.ts Normal file
View File

@@ -0,0 +1,6 @@
import { type ClassValue, clsx } from 'clsx'
import { twMerge } from 'tailwind-merge'
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
}