From eb5113b9756d95e41a5b8d8b62e3d0b4a3c6e570 Mon Sep 17 00:00:00 2001 From: hardikm10 Date: Wed, 3 Sep 2025 15:49:39 +0530 Subject: [PATCH 1/4] Add Hardik to humans.txt (#38391) --- apps/docs/public/humans.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/docs/public/humans.txt b/apps/docs/public/humans.txt index dab101fd1c284..6d07b1a9a3155 100644 --- a/apps/docs/public/humans.txt +++ b/apps/docs/public/humans.txt @@ -54,6 +54,7 @@ Greg Kress Greg P Greg Richardson Guilherme Souza +Hardik Maheshwari Haydn Maley Hieu Pham Ignacio Dobronich From 2feda5ee1902699542f38cb9f4a6a3a5e8b1827c Mon Sep 17 00:00:00 2001 From: Francesco Sansalvadore Date: Wed, 3 Sep 2025 14:49:28 +0200 Subject: [PATCH 2/4] cms www blog (#38045) * show cms blog posts in www * remove contentlayer from www * outputFileTracingExcludes * update remotePatterns * fetch cms posts server-side with revalidation * add cms env vars to turbo.json * add www env vars to turbo.json * include cms posts in www sitemap * add migration to remove image from cms post * update cms meta image mapping in www --- .github/CODEOWNERS | 2 + apps/cms/.env | 2 - apps/cms/next.config.mjs | 20 +- apps/cms/package.json | 41 +- apps/cms/src/blocks/Code/config.ts | 34 +- apps/cms/src/collections/Authors.ts | 35 +- apps/cms/src/collections/Categories.ts | 5 + apps/cms/src/collections/Customers/index.ts | 21 +- apps/cms/src/collections/Events/index.ts | 25 +- apps/cms/src/collections/Posts/index.ts | 156 +- apps/cms/src/collections/Tags.ts | 1 + .../BeforeDashboard/SeedButton/index.scss | 12 - .../BeforeDashboard/SeedButton/index.tsx | 88 - .../src/components/BeforeDashboard/index.tsx | 2 - apps/cms/src/environment.d.ts | 2 +- .../20250819_222013_update_authors.json | 7439 ++++++++++++++++ .../20250819_222013_update_authors.ts | 15 + apps/cms/src/migrations/20250821_151248.json | 7514 +++++++++++++++++ apps/cms/src/migrations/20250821_151248.ts | 23 + apps/cms/src/migrations/20250821_165446.json | 7451 ++++++++++++++++ apps/cms/src/migrations/20250821_165446.ts | 21 + apps/cms/src/migrations/20250902_112449.json | 7383 ++++++++++++++++ apps/cms/src/migrations/20250902_112449.ts | 23 + apps/cms/src/migrations/index.ts | 32 +- apps/cms/src/payload-types.ts | 78 +- apps/cms/src/payload.config.ts | 38 +- apps/cms/src/utilities/constants.ts | 8 + apps/cms/src/utilities/generatePreviewPath.ts | 5 +- apps/cms/supabase/config.toml | 5 +- apps/cms/vercel.json | 1 - apps/www/.env | 2 +- apps/www/.env.local.example | 4 +- apps/www/.gitignore | 1 + apps/www/.vercelignore | 73 + apps/www/app/ConsentWrapper.tsx | 24 + apps/www/app/api-v2/cms-posts/route.ts | 723 ++ .../www/app/api-v2/cms/disable-draft/route.ts | 15 + apps/www/app/api-v2/cms/preview/route.ts | 35 + apps/www/app/api-v2/cms/revalidate/route.ts | 27 + apps/www/app/blog/BlogClient.tsx | 70 + apps/www/app/blog/[slug]/BlogPostClient.tsx | 173 + apps/www/app/blog/[slug]/page.tsx | 328 + .../app/blog/categories/[category]/page.tsx | 66 + apps/www/app/blog/page.tsx | 34 + apps/www/app/blog/tags/[tag]/page.tsx | 59 + apps/www/app/layout.tsx | 53 + apps/www/app/providers.tsx | 46 + apps/www/components/Avatar.tsx | 4 +- apps/www/components/Blog/BlogFilters.tsx | 237 +- apps/www/components/Blog/BlogGridItem.tsx | 20 +- apps/www/components/Blog/BlogHeader.tsx | 19 - apps/www/components/Blog/BlogListItem.tsx | 47 +- apps/www/components/Blog/BlogPostRenderer.tsx | 382 + apps/www/components/Blog/DraftModeBanner.tsx | 53 + apps/www/components/Blog/FeaturedThumb.tsx | 79 +- apps/www/components/Charts/PGCharts.tsx | 2 + apps/www/components/CodeBlock/CodeBlock.tsx | 2 + .../CustomerStories/CustomersFilters.tsx | 32 +- .../CustomerStories/CustomersSliderMobile.tsx | 2 + .../CutomsersSliderDesktop.tsx | 2 + apps/www/components/CustomerStories/index.tsx | 2 + apps/www/components/Events/EventsFilters.tsx | 23 +- apps/www/components/Footer/index.tsx | 9 +- apps/www/components/ImageFadeStack.tsx | 2 + apps/www/components/ImageGrid.tsx | 2 + .../15/Ticketing/LW15TicketPageProxy.tsx | 4 +- .../components/Layouts/SectionContainer.tsx | 2 + .../www/components/Nav/DevelopersDropdown.tsx | 116 +- apps/www/components/Nav/GitHubButton.tsx | 8 +- apps/www/components/Nav/MobileMenu.tsx | 22 +- apps/www/components/Nav/ProductDropdown.tsx | 6 +- .../components/Nav/RightClickBrandLogo.tsx | 20 +- apps/www/components/Nav/index.tsx | 13 +- apps/www/components/Nav/useDropdownMenu.tsx | 4 +- apps/www/components/ScrollProgress.tsx | 8 +- apps/www/contentlayer.config.ts | 36 - apps/www/hooks/useActiveAnchors.tsx | 2 + apps/www/hooks/useDarkLaunchWeeks.tsx | 12 +- apps/www/hooks/useDraftMode.ts | 21 + apps/www/hooks/useParams.ts | 2 + apps/www/internals/generate-sitemap.mjs | 232 +- apps/www/lib/cms/README.md | 85 + apps/www/lib/cms/convertRichTextToMarkdown.ts | 270 + apps/www/lib/cms/processCMSContent.ts | 82 + apps/www/lib/constants.ts | 11 + apps/www/lib/get-cms-posts.tsx | 596 ++ apps/www/lib/mdx/mdxSerialize.ts | 82 +- apps/www/lib/posts.tsx | 1 - apps/www/lib/remotePatterns.js | 48 + apps/www/lib/static-content.ts | 45 + apps/www/lib/telemetry.ts | 23 +- apps/www/lib/theme.utils.ts | 4 +- apps/www/lib/toc.ts | 40 + apps/www/next.config.mjs | 42 +- apps/www/package.json | 5 +- apps/www/pages/blog.tsx | 130 - apps/www/pages/blog/[slug].tsx | 395 - apps/www/pages/blog/categories/[category].tsx | 68 - apps/www/pages/blog/tags/[tag].tsx | 68 - apps/www/pages/features.tsx | 19 +- apps/www/public/customers-rss.xml | 6 +- apps/www/public/sitemap_www.xml | 512 +- apps/www/scripts/generateStaticContent.mjs | 185 +- apps/www/types/post.ts | 189 +- pnpm-lock.yaml | 540 +- turbo.json | 5 + 106 files changed, 34967 insertions(+), 2126 deletions(-) delete mode 100644 apps/cms/src/components/BeforeDashboard/SeedButton/index.scss delete mode 100644 apps/cms/src/components/BeforeDashboard/SeedButton/index.tsx create mode 100644 apps/cms/src/migrations/20250819_222013_update_authors.json create mode 100644 apps/cms/src/migrations/20250819_222013_update_authors.ts create mode 100644 apps/cms/src/migrations/20250821_151248.json create mode 100644 apps/cms/src/migrations/20250821_151248.ts create mode 100644 apps/cms/src/migrations/20250821_165446.json create mode 100644 apps/cms/src/migrations/20250821_165446.ts create mode 100644 apps/cms/src/migrations/20250902_112449.json create mode 100644 apps/cms/src/migrations/20250902_112449.ts create mode 100644 apps/cms/src/utilities/constants.ts create mode 100644 apps/www/.vercelignore create mode 100644 apps/www/app/ConsentWrapper.tsx create mode 100644 apps/www/app/api-v2/cms-posts/route.ts create mode 100644 apps/www/app/api-v2/cms/disable-draft/route.ts create mode 100644 apps/www/app/api-v2/cms/preview/route.ts create mode 100644 apps/www/app/api-v2/cms/revalidate/route.ts create mode 100644 apps/www/app/blog/BlogClient.tsx create mode 100644 apps/www/app/blog/[slug]/BlogPostClient.tsx create mode 100644 apps/www/app/blog/[slug]/page.tsx create mode 100644 apps/www/app/blog/categories/[category]/page.tsx create mode 100644 apps/www/app/blog/page.tsx create mode 100644 apps/www/app/blog/tags/[tag]/page.tsx create mode 100644 apps/www/app/layout.tsx create mode 100644 apps/www/app/providers.tsx delete mode 100644 apps/www/components/Blog/BlogHeader.tsx create mode 100644 apps/www/components/Blog/BlogPostRenderer.tsx create mode 100644 apps/www/components/Blog/DraftModeBanner.tsx delete mode 100644 apps/www/contentlayer.config.ts create mode 100644 apps/www/hooks/useDraftMode.ts create mode 100644 apps/www/lib/cms/README.md create mode 100644 apps/www/lib/cms/convertRichTextToMarkdown.ts create mode 100644 apps/www/lib/cms/processCMSContent.ts create mode 100644 apps/www/lib/get-cms-posts.tsx create mode 100644 apps/www/lib/static-content.ts create mode 100644 apps/www/lib/toc.ts delete mode 100644 apps/www/pages/blog.tsx delete mode 100644 apps/www/pages/blog/[slug].tsx delete mode 100644 apps/www/pages/blog/categories/[category].tsx delete mode 100644 apps/www/pages/blog/tags/[tag].tsx diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 2da257c744c39..414afee868ffa 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -5,6 +5,8 @@ /apps/studio/ @supabase/Dashboard +/apps/cms/ @supabase/marketing + /apps/www/ @supabase/marketing /apps/www/public/images/blog @supabase/marketing /apps/www/lib/redirects.js diff --git a/apps/cms/.env b/apps/cms/.env index 94f021dce4775..5972f7950940e 100644 --- a/apps/cms/.env +++ b/apps/cms/.env @@ -11,7 +11,5 @@ S3_SECRET_ACCESS_KEY=850181e4652dd023b7a98c58ae0d2d34bd487ee0cc3254aed6eda373074 S3_REGION=local S3_ENDPOINT=http://127.0.0.1:34321/storage/v1/s3 -BLOG_APP_URL=http://localhost:3000 -NEXT_PUBLIC_SERVER_URL=http://localhost:3000/blog CRON_SECRET=secret PREVIEW_SECRET=secret \ No newline at end of file diff --git a/apps/cms/next.config.mjs b/apps/cms/next.config.mjs index 8c87e87b2f05e..3317297c82500 100644 --- a/apps/cms/next.config.mjs +++ b/apps/cms/next.config.mjs @@ -19,20 +19,24 @@ const redirects = async () => { return redirects } -const NEXT_PUBLIC_SERVER_URL = process.env.VERCEL_PROJECT_PRODUCTION_URL - ? `https://${process.env.VERCEL_PROJECT_PRODUCTION_URL}` - : undefined || process.env.NEXT_PUBLIC_SERVER_URL || 'http://localhost:3000' +const WWW_SITE_ORIGIN = + process.env.NEXT_PUBLIC_VERCEL_ENV === 'production' + ? 'https://supabase.com' + : process.env.NEXT_PUBLIC_VERCEL_BRANCH_URL && + typeof process.env.NEXT_PUBLIC_VERCEL_BRANCH_URL === 'string' + ? `https://${process.env.NEXT_PUBLIC_VERCEL_BRANCH_URL.replace('cms-git-', 'zone-www-dot-com-git-')}` + : 'http://localhost:3000' /** @type {import('next').NextConfig} */ const nextConfig = { images: { remotePatterns: [ - ...[NEXT_PUBLIC_SERVER_URL /* 'https://example.com' */].map((item) => { + ...[WWW_SITE_ORIGIN /* 'https://example.com' */].map((item) => { const url = new URL(item) return { hostname: url.hostname, - protocol: url.protocol.replace(':', ''), + protocol: url.protocol?.replace(':', ''), } }), ], @@ -43,6 +47,12 @@ const nextConfig = { // We are already running linting via GH action, this will skip linting during production build on Vercel ignoreDuringBuilds: true, }, + experimental: { + // Ensure compatibility with Turbopack and Sharp + serverComponentsExternalPackages: ['sharp'], + }, + // Configure Sharp as an external package for server-side rendering + serverExternalPackages: ['sharp'], } export default withPayload(nextConfig, { devBundleServerPackages: false }) diff --git a/apps/cms/package.json b/apps/cms/package.json index ea0d762bd1aff..13e5841a04e9f 100644 --- a/apps/cms/package.json +++ b/apps/cms/package.json @@ -20,23 +20,26 @@ "typecheck_IGNORED": "tsc --noEmit" }, "dependencies": { - "@payloadcms/admin-bar": "^3.50.0", - "@payloadcms/db-postgres": "^3.50.0", - "@payloadcms/live-preview-react": "^3.50.0", - "@payloadcms/next": "3.50.0", - "@payloadcms/payload-cloud": "3.50.0", - "@payloadcms/plugin-form-builder": "3.50.0", - "@payloadcms/plugin-nested-docs": "3.50.0", - "@payloadcms/plugin-seo": "3.50.0", - "@payloadcms/richtext-lexical": "3.50.0", - "@payloadcms/storage-s3": "3.50.0", - "@payloadcms/ui": "3.50.0", + "@payloadcms/admin-bar": "^3.52.0", + "@payloadcms/db-postgres": "^3.52.0", + "@payloadcms/live-preview-react": "^3.52.0", + "@payloadcms/next": "3.52.0", + "@payloadcms/payload-cloud": "3.52.0", + "@payloadcms/plugin-form-builder": "3.52.0", + "@payloadcms/plugin-nested-docs": "3.52.0", + "@payloadcms/plugin-seo": "3.52.0", + "@payloadcms/richtext-lexical": "3.52.0", + "@payloadcms/storage-s3": "3.52.0", + "@payloadcms/ui": "3.52.0", "@radix-ui/react-checkbox": "^1.3.2", "@radix-ui/react-label": "^2.1.7", "@radix-ui/react-select": "^2.0.0", "@radix-ui/react-slot": "^1.2.3", + "@types/node": "catalog:", + "@types/react": "catalog:", + "@types/react-dom": "catalog:", "class-variance-authority": "^0.7.1", - "clsx": "^1.2.1", + "clsx": "^2.1.1", "common": "workspace:*", "config": "workspace:*", "cross-env": "^7.0.3", @@ -45,19 +48,17 @@ "image-size": "2.0.2", "lucide-react": "^0.511.0", "next": "catalog:", - "payload": "3.50.0", + "payload": "3.52.0", "pg": "^8.16.3", "prism-react-renderer": "^2.3.1", "react": "catalog:", "react-dom": "catalog:", "sharp": "0.32.6", - "tailwind-merge": "^1.13.2" - }, - "devDependencies": { - "@types/node": "catalog:", - "@types/react": "catalog:", - "@types/react-dom": "catalog:", + "tailwind-merge": "^1.13.2", "tsx": "^4.19.3", - "typescript": "5.7.3" + "typescript": "~5.5.0" + }, + "externals": { + "sharp": "commonjs sharp" } } diff --git a/apps/cms/src/blocks/Code/config.ts b/apps/cms/src/blocks/Code/config.ts index 7b26f805db2ef..98a81114197b8 100644 --- a/apps/cms/src/blocks/Code/config.ts +++ b/apps/cms/src/blocks/Code/config.ts @@ -10,16 +10,40 @@ export const Code: Block = { defaultValue: 'typescript', options: [ { - label: 'Typescript', - value: 'typescript', + label: 'SQL', + value: 'sql', + }, + { + label: 'JSON', + value: 'json', + }, + { + label: 'bash', + value: 'bash', }, { label: 'Javascript', - value: 'javascript', + value: 'js', + }, + { + label: 'Typescript', + value: 'ts', + }, + { + label: 'tsx', + value: 'tsx', + }, + { + label: 'Python', + value: 'py', + }, + { + label: 'kotlin', + value: 'kotlin', }, { - label: 'CSS', - value: 'css', + label: 'yaml', + value: 'yaml', }, ], }, diff --git a/apps/cms/src/collections/Authors.ts b/apps/cms/src/collections/Authors.ts index 44cb98321cd61..b302059b041b9 100644 --- a/apps/cms/src/collections/Authors.ts +++ b/apps/cms/src/collections/Authors.ts @@ -4,6 +4,7 @@ export const Authors: CollectionConfig = { slug: 'authors', admin: { useAsTitle: 'author', + defaultColumns: ['author', 'position', 'author_id'], }, access: { read: () => true, @@ -13,28 +14,54 @@ export const Authors: CollectionConfig = { name: 'author', type: 'text', required: true, + label: 'Author Name', + index: true, }, { name: 'author_id', type: 'text', + required: true, + unique: true, + label: 'Author ID', + admin: { + description: 'Unique identifier for the author', + }, + }, + { + name: 'username', + type: 'text', + label: 'Username', + admin: { + description: 'GitHub/social username', + }, }, { name: 'position', type: 'text', + label: 'Position', + }, + { + name: 'company', + type: 'text', + label: 'Company', + admin: { + description: 'Company name (for external/guest authors)', + }, }, { name: 'author_url', type: 'text', + label: 'Profile URL', + admin: { + description: 'Link to GitHub, Twitter, LinkedIn, etc.', + }, }, { name: 'author_image_url', type: 'upload', relationTo: 'media', required: false, - }, - { - name: 'username', - type: 'text', + label: 'Profile Image', }, ], timestamps: true, diff --git a/apps/cms/src/collections/Categories.ts b/apps/cms/src/collections/Categories.ts index 7fc90e96c2aef..d5b20b6d0e384 100644 --- a/apps/cms/src/collections/Categories.ts +++ b/apps/cms/src/collections/Categories.ts @@ -1,4 +1,5 @@ import type { CollectionConfig } from 'payload' +import { isAdmin } from '../access/isAdmin' export const Categories: CollectionConfig = { slug: 'categories', @@ -6,13 +7,17 @@ export const Categories: CollectionConfig = { useAsTitle: 'name', }, access: { + create: isAdmin, read: () => true, + update: isAdmin, + delete: isAdmin, }, fields: [ { name: 'name', type: 'text', required: true, + index: true, }, ], timestamps: true, diff --git a/apps/cms/src/collections/Customers/index.ts b/apps/cms/src/collections/Customers/index.ts index a7108d200e359..56bf14bf3187c 100644 --- a/apps/cms/src/collections/Customers/index.ts +++ b/apps/cms/src/collections/Customers/index.ts @@ -27,6 +27,7 @@ import { PreviewField, } from '@payloadcms/plugin-seo/fields' import { slugField } from '../../fields/slug/index.ts' +import { WWW_SITE_ORIGIN } from '../../utilities/constants.ts' const industryOptions = [ { label: 'Healthcare', value: 'healthcare' }, @@ -73,16 +74,20 @@ export const Customers: CollectionConfig = { useAsTitle: 'name', defaultColumns: ['name', 'slug', 'updatedAt'], preview: (data) => { - const baseUrl = process.env.BLOG_APP_URL || 'http://localhost:3000' + const baseUrl = WWW_SITE_ORIGIN || 'http://localhost:3000' const isDraft = data?._status === 'draft' return `${baseUrl}/customers/${data?.slug}${isDraft ? '?preview=true' : ''}` }, }, access: { - create: isAuthenticated, - delete: isAuthenticated, - read: isAnyone, - update: isAuthenticated, + // create: isAuthenticated, + // delete: isAuthenticated, + // read: isAnyone, + // update: isAuthenticated, + create: () => false, + delete: () => false, + read: () => false, + update: () => false, }, defaultPopulate: { name: true, @@ -303,9 +308,9 @@ export const Customers: CollectionConfig = { }, versions: { drafts: { - autosave: { - interval: 100, - }, + // autosave: { + // interval: 100, + // }, schedulePublish: true, }, maxPerDoc: 50, diff --git a/apps/cms/src/collections/Events/index.ts b/apps/cms/src/collections/Events/index.ts index ad99e840e2d2d..de909c08e8cac 100644 --- a/apps/cms/src/collections/Events/index.ts +++ b/apps/cms/src/collections/Events/index.ts @@ -28,6 +28,7 @@ import { } from '@payloadcms/plugin-seo/fields' import { slugField } from '../../fields/slug/index.ts' import { timezoneOptions } from '../../utilities/timezones.ts' +import { WWW_SITE_ORIGIN } from '../../utilities/constants.ts' const eventTypeOptions = [ { label: 'Conference', value: 'conference' }, @@ -47,16 +48,20 @@ export const Events: CollectionConfig = { useAsTitle: 'title', defaultColumns: ['title', 'slug', 'updatedAt'], preview: (data) => { - const baseUrl = process.env.BLOG_APP_URL || 'http://localhost:3000' - const isDraft = data?._status === 'draft' - return `${baseUrl}/events/${data?.slug}${isDraft ? '?preview=true' : ''}` + const baseUrl = WWW_SITE_ORIGIN || 'http://localhost:3000' + // Always use the preview route to ensure draft mode is enabled + return `${baseUrl}/api-v2/cms/preview?slug=${data?.slug}&path=events&secret=${process.env.PREVIEW_SECRET || 'secret'}` }, }, access: { - create: isAuthenticated, - delete: isAuthenticated, - read: isAnyone, - update: isAuthenticated, + // create: isAuthenticated, + // delete: isAuthenticated, + // read: isAnyone, + // update: isAuthenticated, + create: () => false, + delete: () => false, + read: () => false, + update: () => false, }, defaultPopulate: { title: true, @@ -393,9 +398,9 @@ export const Events: CollectionConfig = { }, versions: { drafts: { - autosave: { - interval: 100, - }, + // autosave: { + // interval: 100, + // }, schedulePublish: true, }, maxPerDoc: 50, diff --git a/apps/cms/src/collections/Posts/index.ts b/apps/cms/src/collections/Posts/index.ts index 7c20c7988672d..72e8830bb3993 100644 --- a/apps/cms/src/collections/Posts/index.ts +++ b/apps/cms/src/collections/Posts/index.ts @@ -28,6 +28,7 @@ import { PreviewField, } from '@payloadcms/plugin-seo/fields' import { slugField } from '../../fields/slug/index.ts' +import { WWW_SITE_ORIGIN } from '../../utilities/constants.ts' const launchweekOptions = [ { label: '6', value: '6' }, @@ -45,38 +46,11 @@ export const Posts: CollectionConfig = { slug: 'posts', admin: { useAsTitle: 'title', - defaultColumns: ['title', 'slug', 'updatedAt'], - livePreview: { - url: ({ data }) => { - const baseUrl = process.env.BLOG_APP_URL || 'http://localhost:3000' - // Always use the preview route for live preview to ensure draft mode is enabled - return `${baseUrl}/api/preview?slug=${data?.slug}&secret=${process.env.PREVIEW_SECRET || 'preview-secret'}` - }, - breakpoints: [ - { - label: 'Desktop', - name: 'desktop', - width: 1920, - height: 1080, - }, - { - label: 'Tablet', - name: 'tablet', - width: 768, - height: 1024, - }, - { - label: 'Mobile', - name: 'mobile', - width: 375, - height: 667, - }, - ], - }, + defaultColumns: ['title', 'slug', 'updatedAt', 'publishedAt'], preview: (data) => { - const baseUrl = process.env.BLOG_APP_URL || 'http://localhost:3000' + const baseUrl = WWW_SITE_ORIGIN || 'http://localhost:3000' // Always use the preview route to ensure draft mode is enabled - return `${baseUrl}/api/preview?slug=${data?.slug}&secret=${process.env.PREVIEW_SECRET || 'preview-secret'}` + return `${baseUrl}/api-v2/cms/preview?slug=${data?.slug}&secret=${process.env.PREVIEW_SECRET || 'secret'}` }, }, access: { @@ -99,8 +73,17 @@ export const Posts: CollectionConfig = { name: 'title', type: 'text', required: true, + index: true, }, ...slugField(), + { + name: 'description', + type: 'textarea', + label: 'Description / subtitle', + admin: { + description: 'Appears as subheading in the blog post preview.', + }, + }, { type: 'tabs', tabs: [ @@ -134,29 +117,26 @@ export const Posts: CollectionConfig = { type: 'upload', relationTo: 'media', required: false, + admin: { + description: 'Will show up as the blog post cover. Required.', + }, }, { - name: 'image', - type: 'upload', - relationTo: 'media', - required: false, + name: 'authors', + type: 'relationship', + relationTo: 'authors', + hasMany: true, + admin: { + description: 'Authors must be one or more. Required.', + }, }, { name: 'categories', type: 'relationship', - admin: { - position: 'sidebar', - }, hasMany: true, relationTo: 'categories', - }, - { - name: 'launchweek', - type: 'select', - options: launchweekOptions, admin: { - description: - 'Select a launch week to show launch week summary at the bottom of the blog post.', + description: 'Select only one category. Required.', }, }, { @@ -167,40 +147,29 @@ export const Posts: CollectionConfig = { }, }, { - name: 'date', - type: 'date', + name: 'tags', + type: 'relationship', + relationTo: 'tags', + hasMany: true, admin: { - position: 'sidebar', + description: 'Tags can be one or more. Optional.', }, }, { name: 'toc_depth', type: 'number', - defaultValue: 2, + defaultValue: 3, admin: { - position: 'sidebar', - }, - }, - { - name: 'description', - type: 'textarea', - }, - { - name: 'authors', - type: 'relationship', - relationTo: 'authors', - hasMany: true, - admin: { - position: 'sidebar', + hidden: true, }, }, { - name: 'tags', - type: 'relationship', - relationTo: 'tags', - hasMany: true, + name: 'launchweek', + type: 'select', + options: launchweekOptions, admin: { - position: 'sidebar', + description: + 'Select a launch week to show launch week summary at the bottom of the blog post. Optional.', }, }, ], @@ -217,12 +186,28 @@ export const Posts: CollectionConfig = { }), MetaTitleField({ hasGenerateFn: true, + overrides: { + admin: { + description: 'Defaults to the title of the post, if not set.', + }, + }, }), MetaImageField({ relationTo: 'media', + overrides: { + admin: { + description: 'Defaults to the "thumb" image, if not set.', + }, + }, }), - MetaDescriptionField({}), + MetaDescriptionField({ + overrides: { + admin: { + description: 'Defaults to the description of the post, if not set.', + }, + }, + }), PreviewField({ // if the `generateUrl` function is configured hasGenerateFn: true, @@ -235,6 +220,9 @@ export const Posts: CollectionConfig = { }, ], }, + /** + * "publishedAt" is only internal to cms to determine if the blog post is published or not, but it's not used for sorting blog posts in www + * */ { name: 'publishedAt', type: 'date', @@ -243,10 +231,17 @@ export const Posts: CollectionConfig = { pickerAppearance: 'dayAndTime', }, position: 'sidebar', + hidden: true, }, hooks: { beforeChange: [ ({ siblingData, value }) => { + /** + * Set the "date" field to the current date if user doesn't set it + */ + if (!siblingData.date) { + siblingData.date = new Date() + } if (siblingData._status === 'published' && !value) { return new Date() } @@ -255,6 +250,21 @@ export const Posts: CollectionConfig = { ], }, }, + /** + * "date" is used to determine the chronological order of the blog post in www + */ + { + name: 'date', + type: 'date', + label: 'Blog Post Date', + admin: { + date: { + pickerAppearance: 'dayAndTime', + }, + description: 'This date will determine the chronological order of the blog post. Required.', + position: 'sidebar', + }, + }, ], timestamps: true, hooks: { @@ -264,10 +274,12 @@ export const Posts: CollectionConfig = { }, versions: { drafts: { - autosave: { - interval: 200, - }, - schedulePublish: true, + // NOTE: disabled autosave as it might overload connections if many users are editing at the same time + // autosave: { + // interval: 200, + // }, + // TODO: enable schedulePublish to work with cron job + // schedulePublish: true, }, maxPerDoc: 50, }, diff --git a/apps/cms/src/collections/Tags.ts b/apps/cms/src/collections/Tags.ts index d21dd5a4728bd..c3570155bdd07 100644 --- a/apps/cms/src/collections/Tags.ts +++ b/apps/cms/src/collections/Tags.ts @@ -13,6 +13,7 @@ export const Tags: CollectionConfig = { name: 'name', type: 'text', required: true, + index: true, }, ], timestamps: true, diff --git a/apps/cms/src/components/BeforeDashboard/SeedButton/index.scss b/apps/cms/src/components/BeforeDashboard/SeedButton/index.scss deleted file mode 100644 index 3ed9bcb109196..0000000000000 --- a/apps/cms/src/components/BeforeDashboard/SeedButton/index.scss +++ /dev/null @@ -1,12 +0,0 @@ -.seedButton { - appearance: none; - background: none; - border: none; - padding: 0; - text-decoration: underline; - - &:hover { - cursor: pointer; - opacity: 0.85; - } -} diff --git a/apps/cms/src/components/BeforeDashboard/SeedButton/index.tsx b/apps/cms/src/components/BeforeDashboard/SeedButton/index.tsx deleted file mode 100644 index 707c183db5aac..0000000000000 --- a/apps/cms/src/components/BeforeDashboard/SeedButton/index.tsx +++ /dev/null @@ -1,88 +0,0 @@ -'use client' - -import React, { Fragment, useCallback, useState } from 'react' -import { toast } from '@payloadcms/ui' - -import './index.scss' - -const SuccessMessage: React.FC = () => ( -
- Database seeded! You can now{' '} - - visit your website - -
-) - -export const SeedButton: React.FC = () => { - const [loading, setLoading] = useState(false) - const [seeded, setSeeded] = useState(false) - const [error, setError] = useState(null) - - const handleClick = useCallback( - async (e: React.MouseEvent) => { - e.preventDefault() - - if (seeded) { - toast.info('Database already seeded.') - return - } - if (loading) { - toast.info('Seeding already in progress.') - return - } - if (error) { - toast.error(`An error occurred, please refresh and try again.`) - return - } - - setLoading(true) - - try { - toast.promise( - new Promise((resolve, reject) => { - try { - fetch('/next/seed', { method: 'POST', credentials: 'include' }) - .then((res) => { - if (res.ok) { - resolve(true) - setSeeded(true) - } else { - reject('An error occurred while seeding.') - } - }) - .catch((error) => { - reject(error) - }) - } catch (error) { - reject(error) - } - }), - { - loading: 'Seeding with data....', - success: , - error: 'An error occurred while seeding.', - } - ) - } catch (err) { - const error = err instanceof Error ? err.message : String(err) - setError(error) - } - }, - [loading, seeded, error] - ) - - let message = '' - if (loading) message = ' (seeding...)' - if (seeded) message = ' (done!)' - if (error) message = ` (error: ${error})` - - return ( - - - {message} - - ) -} diff --git a/apps/cms/src/components/BeforeDashboard/index.tsx b/apps/cms/src/components/BeforeDashboard/index.tsx index 907eae47fd6dc..78bb10241e6db 100644 --- a/apps/cms/src/components/BeforeDashboard/index.tsx +++ b/apps/cms/src/components/BeforeDashboard/index.tsx @@ -1,7 +1,6 @@ import { Banner } from '@payloadcms/ui/elements/Banner' import React from 'react' -import { SeedButton } from './SeedButton' import './index.scss' const baseClass = 'before-dashboard' @@ -15,7 +14,6 @@ const BeforeDashboard: React.FC = () => { Here's what to do next: