diff --git a/bun.lock b/bun.lock index 49edca2..0a2bfef 100644 --- a/bun.lock +++ b/bun.lock @@ -3,6 +3,7 @@ "workspaces": { "": { "dependencies": { + "@payloadcms/admin-bar": "^3.59.1", "@payloadcms/db-postgres": "3.59.1", "@payloadcms/next": "3.59.1", "@payloadcms/payload-cloud": "3.59.1", @@ -16,6 +17,7 @@ "react": "19.1.0", "react-dom": "19.1.0", "sharp": "0.34.2", + "tailwind-merge": "^3.3.1", }, "devDependencies": { "@playwright/test": "1.54.1", @@ -455,6 +457,8 @@ "@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/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=="], + "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=="], "tinybench": ["tinybench@2.9.0", "", {}, "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg=="], diff --git a/package.json b/package.json index 95ab216..00d574e 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,8 @@ "test:int": "cross-env NODE_OPTIONS=--no-deprecation vitest run --config ./vitest.config.mts" }, "dependencies": { + "@payloadcms/admin-bar": "^3.59.1", + "@payloadcms/db-postgres": "3.59.1", "@payloadcms/next": "3.59.1", "@payloadcms/payload-cloud": "3.59.1", "@payloadcms/richtext-lexical": "3.59.1", @@ -30,7 +32,7 @@ "react": "19.1.0", "react-dom": "19.1.0", "sharp": "0.34.2", - "@payloadcms/db-postgres": "3.59.1" + "tailwind-merge": "^3.3.1" }, "devDependencies": { "@playwright/test": "1.54.1", diff --git a/src/app/(frontend)/layout.tsx b/src/app/(frontend)/layout.tsx index e7681f7..41e2ce2 100644 --- a/src/app/(frontend)/layout.tsx +++ b/src/app/(frontend)/layout.tsx @@ -1,5 +1,6 @@ import React from 'react' import './styles.css' +import { AdminBar } from '@/src/components/AdminBar' export const metadata = { 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 ( +
{children}
diff --git a/src/app/(frontend)/page.tsx b/src/app/(frontend)/page.tsx index de615ae..6b10aa3 100644 --- a/src/app/(frontend)/page.tsx +++ b/src/app/(frontend)/page.tsx @@ -3,9 +3,8 @@ import Image from 'next/image' import { getPayload } from 'payload' import type { Payload } from 'payload' import React from 'react' -import { fileURLToPath } from 'url' -import config from '@/payload.config' +import config from '@/src/payload.config' import './styles.css' import { RichText } from '@payloadcms/richtext-lexical/react' @@ -15,8 +14,6 @@ export default async function HomePage() { const payload = await getPayload({ config: payloadConfig }) const { user } = await payload.auth({ headers }) - const fileURL = `vscode://file/${fileURLToPath(import.meta.url)}` - const homePage = await payload.find({ collection: 'pages', where: { @@ -28,8 +25,6 @@ export default async function HomePage() { const page = homePage.docs?.[0] - console.log(page) - return (
@@ -53,7 +48,7 @@ export default async function HomePage() { /> {!user &&

Welcome to your new project.

} - {user &&

Welcome back, {user.email}

} + {user &&

Welcome back, {user.name}

}

Update this page by editing

-
- app/(frontend)/page.tsx - + app/(frontend)/page.tsx
) diff --git a/src/app/(payload)/admin/importMap.js b/src/app/(payload)/admin/importMap.js index b6bb80e..881803e 100644 --- a/src/app/(payload)/admin/importMap.js +++ b/src/app/(payload)/admin/importMap.js @@ -22,6 +22,7 @@ import { StrikethroughFeatureClient as StrikethroughFeatureClient_e70f5e05f09f93 import { UnderlineFeatureClient as UnderlineFeatureClient_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 { SlugField as SlugField_3817bf644402e67bfe6577f60ef982de } from '@payloadcms/ui' export const importMap = { "@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#UnderlineFeatureClient": UnderlineFeatureClient_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 } diff --git a/src/collections/Page.ts b/src/collections/Page.ts index 4decf75..d404dd0 100644 --- a/src/collections/Page.ts +++ b/src/collections/Page.ts @@ -1,5 +1,6 @@ import { lexicalEditor } from '@payloadcms/richtext-lexical' import type { CollectionConfig } from 'payload' +import { slugField } from 'payload' export const Page: CollectionConfig = { slug: 'pages', @@ -7,9 +8,6 @@ export const Page: CollectionConfig = { title: true, slug: true, }, - access: { - read: () => true, - }, admin: { defaultColumns: ['title', 'slug', 'updatedAt',], }, @@ -22,6 +20,7 @@ export const Page: CollectionConfig = { { name: 'richText', type: 'richText' - } + }, + slugField(), ], } diff --git a/src/components/AdminBar/index.scss b/src/components/AdminBar/index.scss new file mode 100644 index 0000000..e72eb34 --- /dev/null +++ b/src/components/AdminBar/index.scss @@ -0,0 +1,7 @@ +@import '~@payloadcms/ui/scss'; + +.admin-bar { + @include small-break { + display: none; + } +} \ No newline at end of file diff --git a/src/components/AdminBar/index.tsx b/src/components/AdminBar/index.tsx new file mode 100644 index 0000000..54eb939 --- /dev/null +++ b/src/components/AdminBar/index.tsx @@ -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 = () => Dashboard + +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 ( +
+
+ } + onAuthChange={onAuthChange} + onPreviewExit={() => { + fetch('/next/exit-preview').then(() => { + router.push('/') + router.refresh() + }) + }} + style={{ + backgroundColor: 'transparent', + padding: 0, + position: 'relative', + zIndex: 'unset', + }} + /> +
+
+ ) +} \ No newline at end of file diff --git a/src/payload-types.ts b/src/payload-types.ts index 90c8353..5e16c46 100644 --- a/src/payload-types.ts +++ b/src/payload-types.ts @@ -182,6 +182,11 @@ export interface Page { }; [k: string]: unknown; } | null; + /** + * When enabled, the slug will auto-generate from the title field on save and autosave. + */ + generateSlug?: boolean | null; + slug: string; updatedAt: string; createdAt: string; } @@ -295,6 +300,8 @@ export interface MediaSelect { export interface PagesSelect { title?: T; richText?: T; + generateSlug?: T; + slug?: T; updatedAt?: T; createdAt?: T; } diff --git a/tsconfig.json b/tsconfig.json index 477c25a..dd246d9 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -24,7 +24,7 @@ ], "paths": { "@/*": [ - "./src/*" + "./*" ], "@payload-config": [ "./src/payload.config.ts" diff --git a/utilities/canUseDOM.ts b/utilities/canUseDOM.ts new file mode 100644 index 0000000..df6703f --- /dev/null +++ b/utilities/canUseDOM.ts @@ -0,0 +1 @@ +export default !!(typeof window !== 'undefined' && window.document && window.document.createElement) \ No newline at end of file diff --git a/utilities/getURL.ts b/utilities/getURL.ts new file mode 100644 index 0000000..6766950 --- /dev/null +++ b/utilities/getURL.ts @@ -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 || '' +} \ No newline at end of file diff --git a/utilities/ui.ts b/utilities/ui.ts new file mode 100644 index 0000000..312ce6a --- /dev/null +++ b/utilities/ui.ts @@ -0,0 +1,6 @@ +import { type ClassValue, clsx } from 'clsx' +import { twMerge } from 'tailwind-merge' + +export function cn(...inputs: ClassValue[]) { + return twMerge(clsx(inputs)) +} \ No newline at end of file