From 84c7c738c4f315a9ce1dfc5b471db7ea11c8198b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Francisco=20Ign=C3=A1cio=20Fontoura=20Solla?= Date: Sun, 15 Feb 2026 23:52:33 -0300 Subject: [PATCH] feat(petition): add petition collection with live preview and public page - create collection with localized admin labels and rich text body - enable drafts/autosave/scheduled publish and collection-level live preview URL - register in - add frontend route with static params and enabled check - add helper to fetch collection docs for static generation - regenerate and admin import map for new collection/editor imports --- .../(frontend)/abaixo-assinado/[id]/page.tsx | 30 +++++++++ src/app/(payload)/admin/importMap.js | 46 +++++++++++++ src/collections/Petition.ts | 66 +++++++++++++++++++ src/payload-types.ts | 52 +++++++++++++++ src/payload.config.ts | 3 +- src/utilities/documents.ts | 3 + 6 files changed, 199 insertions(+), 1 deletion(-) create mode 100644 src/app/(frontend)/abaixo-assinado/[id]/page.tsx create mode 100644 src/collections/Petition.ts diff --git a/src/app/(frontend)/abaixo-assinado/[id]/page.tsx b/src/app/(frontend)/abaixo-assinado/[id]/page.tsx new file mode 100644 index 00000000..cf2ef2d5 --- /dev/null +++ b/src/app/(frontend)/abaixo-assinado/[id]/page.tsx @@ -0,0 +1,30 @@ +import { getCachedDocumentById, getDocuments } from '@/utilities/documents' +import { convertLexicalToHTML } from '@payloadcms/richtext-lexical/html' +import { notFound } from 'next/navigation' +import { headers as nextHeaders } from 'next/headers' + +export async function generateStaticParams() { + const payload = await getDocuments('petition') + + return payload.docs.map((doc) => ({ + id: doc.id, + })) +} + +export default async function Page({ params }: { params: Promise<{ id: string }> }) { + const petition = await getCachedDocumentById('petition', (await params).id)() + + if (!petition || !petition.enabled) { + return notFound() + } + + return ( +
+

{petition.title}

+

{petition.subtitle}

+
+
+ ) +} diff --git a/src/app/(payload)/admin/importMap.js b/src/app/(payload)/admin/importMap.js index eb1ae47a..d275fa91 100644 --- a/src/app/(payload)/admin/importMap.js +++ b/src/app/(payload)/admin/importMap.js @@ -1,5 +1,51 @@ +import { RscEntryLexicalCell as RscEntryLexicalCell_44fe37237e0ebf4470c9990d8cb7b07e } from '@payloadcms/richtext-lexical/rsc' +import { RscEntryLexicalField as RscEntryLexicalField_44fe37237e0ebf4470c9990d8cb7b07e } from '@payloadcms/richtext-lexical/rsc' +import { LexicalDiffComponent as LexicalDiffComponent_44fe37237e0ebf4470c9990d8cb7b07e } from '@payloadcms/richtext-lexical/rsc' +import { InlineToolbarFeatureClient as InlineToolbarFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client' +import { HorizontalRuleFeatureClient as HorizontalRuleFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client' +import { UploadFeatureClient as UploadFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client' +import { BlockquoteFeatureClient as BlockquoteFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client' +import { RelationshipFeatureClient as RelationshipFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client' +import { LinkFeatureClient as LinkFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client' +import { ChecklistFeatureClient as ChecklistFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client' +import { OrderedListFeatureClient as OrderedListFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client' +import { UnorderedListFeatureClient as UnorderedListFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client' +import { IndentFeatureClient as IndentFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client' +import { AlignFeatureClient as AlignFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client' +import { HeadingFeatureClient as HeadingFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client' +import { ParagraphFeatureClient as ParagraphFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client' +import { InlineCodeFeatureClient as InlineCodeFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client' +import { SuperscriptFeatureClient as SuperscriptFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client' +import { SubscriptFeatureClient as SubscriptFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client' +import { StrikethroughFeatureClient as StrikethroughFeatureClient_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 { ItalicFeatureClient as ItalicFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client' import { CollectionCards as CollectionCards_f9c02e79a4aed9a3924487c0cd4cafb1 } from '@payloadcms/next/rsc' export const importMap = { + "@payloadcms/richtext-lexical/rsc#RscEntryLexicalCell": RscEntryLexicalCell_44fe37237e0ebf4470c9990d8cb7b07e, + "@payloadcms/richtext-lexical/rsc#RscEntryLexicalField": RscEntryLexicalField_44fe37237e0ebf4470c9990d8cb7b07e, + "@payloadcms/richtext-lexical/rsc#LexicalDiffComponent": LexicalDiffComponent_44fe37237e0ebf4470c9990d8cb7b07e, + "@payloadcms/richtext-lexical/client#InlineToolbarFeatureClient": InlineToolbarFeatureClient_e70f5e05f09f93e00b997edb1ef0c864, + "@payloadcms/richtext-lexical/client#HorizontalRuleFeatureClient": HorizontalRuleFeatureClient_e70f5e05f09f93e00b997edb1ef0c864, + "@payloadcms/richtext-lexical/client#UploadFeatureClient": UploadFeatureClient_e70f5e05f09f93e00b997edb1ef0c864, + "@payloadcms/richtext-lexical/client#BlockquoteFeatureClient": BlockquoteFeatureClient_e70f5e05f09f93e00b997edb1ef0c864, + "@payloadcms/richtext-lexical/client#RelationshipFeatureClient": RelationshipFeatureClient_e70f5e05f09f93e00b997edb1ef0c864, + "@payloadcms/richtext-lexical/client#LinkFeatureClient": LinkFeatureClient_e70f5e05f09f93e00b997edb1ef0c864, + "@payloadcms/richtext-lexical/client#ChecklistFeatureClient": ChecklistFeatureClient_e70f5e05f09f93e00b997edb1ef0c864, + "@payloadcms/richtext-lexical/client#OrderedListFeatureClient": OrderedListFeatureClient_e70f5e05f09f93e00b997edb1ef0c864, + "@payloadcms/richtext-lexical/client#UnorderedListFeatureClient": UnorderedListFeatureClient_e70f5e05f09f93e00b997edb1ef0c864, + "@payloadcms/richtext-lexical/client#IndentFeatureClient": IndentFeatureClient_e70f5e05f09f93e00b997edb1ef0c864, + "@payloadcms/richtext-lexical/client#AlignFeatureClient": AlignFeatureClient_e70f5e05f09f93e00b997edb1ef0c864, + "@payloadcms/richtext-lexical/client#HeadingFeatureClient": HeadingFeatureClient_e70f5e05f09f93e00b997edb1ef0c864, + "@payloadcms/richtext-lexical/client#ParagraphFeatureClient": ParagraphFeatureClient_e70f5e05f09f93e00b997edb1ef0c864, + "@payloadcms/richtext-lexical/client#InlineCodeFeatureClient": InlineCodeFeatureClient_e70f5e05f09f93e00b997edb1ef0c864, + "@payloadcms/richtext-lexical/client#SuperscriptFeatureClient": SuperscriptFeatureClient_e70f5e05f09f93e00b997edb1ef0c864, + "@payloadcms/richtext-lexical/client#SubscriptFeatureClient": SubscriptFeatureClient_e70f5e05f09f93e00b997edb1ef0c864, + "@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/next/rsc#CollectionCards": CollectionCards_f9c02e79a4aed9a3924487c0cd4cafb1 } diff --git a/src/collections/Petition.ts b/src/collections/Petition.ts new file mode 100644 index 00000000..0a4b31b7 --- /dev/null +++ b/src/collections/Petition.ts @@ -0,0 +1,66 @@ +import { revalidateDocumentById } from '@/utilities/documents' +import type { CollectionConfig } from 'payload' + +const slug = 'petition' + +export const Petition: CollectionConfig = { + slug, + labels: { + singular: 'Abaixo-assinado', + plural: 'Abaixo-assinados', + }, + admin: { + group: 'Coleções', + useAsTitle: 'title', + livePreview: { + url: ({ data }) => `/abaixo-assinado/${data.id}`, + }, + }, + versions: { + drafts: { + autosave: { + interval: 1000, + }, + schedulePublish: true, + }, + maxPerDoc: 5, + }, + access: { + read: () => true, + }, + hooks: { + beforeChange: [({ data }) => revalidateDocumentById(slug, data.id)], + }, + fields: [ + { + name: 'id', + type: 'text', + label: 'Trecho Identificador da URL', + required: true, + }, + { + name: 'title', + type: 'text', + label: 'Título', + required: true, + }, + { + name: 'subtitle', + type: 'text', + label: 'Subtítulo', + required: true, + }, + { + name: 'enabled', + type: 'checkbox', + label: 'Ativo', + required: true, + }, + { + name: 'body', + type: 'richText', + label: 'Corpo', + required: true, + }, + ], +} diff --git a/src/payload-types.ts b/src/payload-types.ts index 6f6a34f1..5bb8f1ff 100644 --- a/src/payload-types.ts +++ b/src/payload-types.ts @@ -69,6 +69,7 @@ export interface Config { collections: { users: User; media: Media; + petition: Petition; 'payload-kv': PayloadKv; 'payload-jobs': PayloadJob; 'payload-locked-documents': PayloadLockedDocument; @@ -79,6 +80,7 @@ export interface Config { collectionsSelect: { users: UsersSelect | UsersSelect; media: MediaSelect | MediaSelect; + petition: PetitionSelect | PetitionSelect; 'payload-kv': PayloadKvSelect | PayloadKvSelect; 'payload-jobs': PayloadJobsSelect | PayloadJobsSelect; 'payload-locked-documents': PayloadLockedDocumentsSelect | PayloadLockedDocumentsSelect; @@ -177,6 +179,34 @@ export interface Media { focalX?: number | null; focalY?: number | null; } +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "petition". + */ +export interface Petition { + id: string; + title: string; + subtitle: string; + enabled: boolean; + body: { + root: { + type: string; + children: { + type: any; + version: number; + [k: string]: unknown; + }[]; + direction: ('ltr' | 'rtl') | null; + format: 'left' | 'start' | 'center' | 'right' | 'end' | 'justify' | ''; + indent: number; + version: number; + }; + [k: string]: unknown; + }; + updatedAt: string; + createdAt: string; + _status?: ('draft' | 'published') | null; +} /** * This interface was referenced by `Config`'s JSON-Schema * via the `definition` "payload-kv". @@ -300,6 +330,10 @@ export interface PayloadLockedDocument { | ({ relationTo: 'media'; value: number | Media; + } | null) + | ({ + relationTo: 'petition'; + value: string | Petition; } | null); globalSlug?: string | null; user: { @@ -383,6 +417,20 @@ export interface MediaSelect { focalX?: T; focalY?: T; } +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "petition_select". + */ +export interface PetitionSelect { + id?: T; + title?: T; + subtitle?: T; + enabled?: T; + body?: T; + updatedAt?: T; + createdAt?: T; + _status?: T; +} /** * This interface was referenced by `Config`'s JSON-Schema * via the `definition` "payload-kv_select". @@ -581,6 +629,10 @@ export interface TaskSchedulePublish { input: { type?: ('publish' | 'unpublish') | null; locale?: string | null; + doc?: { + relationTo: 'petition'; + value: string | Petition; + } | null; global?: 'site-settings' | null; user?: (number | null) | User; }; diff --git a/src/payload.config.ts b/src/payload.config.ts index 82f212a2..33961723 100644 --- a/src/payload.config.ts +++ b/src/payload.config.ts @@ -11,6 +11,7 @@ import { SiteSettings } from './globals/SiteSettings' import { pt } from 'payload/i18n/pt' import { HomePage } from './globals/HomePage' import { Metadata } from './globals/Metadata' +import { Petition } from './collections/Petition' const filename = fileURLToPath(import.meta.url) const dirname = path.dirname(filename) @@ -26,7 +27,7 @@ export default buildConfig({ globals: [SiteSettings.slug, HomePage.slug], }, }, - collections: [Users, Media], + collections: [Users, Media, Petition], globals: [SiteSettings, HomePage, Metadata], editor: lexicalEditor(), secret: process.env.PAYLOAD_SECRET || '', diff --git a/src/utilities/documents.ts b/src/utilities/documents.ts index 867ca841..2bee5a09 100644 --- a/src/utilities/documents.ts +++ b/src/utilities/documents.ts @@ -7,6 +7,9 @@ import { getPayload } from 'payload' type Collection = keyof Config['collections'] +export const getDocuments = (collection: Slug, depth?: number) => + getPayload({ config: configPromise }).then((payload) => payload.find({ collection, depth })) + export const getDocumentById = ( collection: Slug, id: string,