Migrate from unstable_cache to use cache. And start Petition Form

This commit is contained in:
Francisco Ignácio Fontoura Solla 2026-02-17 22:16:53 -03:00
parent 0e9dcceb83
commit 8f3bd3845b
15 changed files with 356 additions and 145 deletions

View file

@ -35,11 +35,16 @@
"test:int": "cross-env NODE_OPTIONS=--no-deprecation vitest run --config ./vitest.config.mts"
},
"dependencies": {
"@hookform/resolvers": "^5.2.2",
"@payloadcms/db-postgres": "3.76.1",
"@payloadcms/live-preview-react": "^3.76.1",
"@payloadcms/next": "3.76.1",
"@payloadcms/richtext-lexical": "3.76.1",
"@payloadcms/ui": "3.76.1",
"@radix-ui/react-label": "^2.1.8",
"@radix-ui/react-separator": "^1.1.8",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"cross-env": "^7.0.3",
"dotenv": "16.4.7",
"graphql": "^16.8.1",
@ -47,12 +52,15 @@
"payload": "3.76.1",
"react": "^19.2.4",
"react-dom": "^19.2.4",
"react-hook-form": "^7.71.1",
"sharp": "0.34.2",
"tailwind-merge": "^3.4.1",
"tailwindcss-animate": "^1.0.7",
"tw-animate-css": "^1.4.0"
"tw-animate-css": "^1.4.0",
"zod": "^4.3.6"
},
"devDependencies": {
"@next/eslint-plugin-next": "^16.1.6",
"@next/eslint-plugin-next": "@hookform/resolvers/zod",
"@playwright/test": "1.58.2",
"@tailwindcss/postcss": "^4.1.18",
"@testing-library/react": "16.3.0",

View file

@ -8,6 +8,9 @@ importers:
.:
dependencies:
'@hookform/resolvers':
specifier: ^5.2.2
version: 5.2.2(react-hook-form@7.71.1(react@19.2.4))
'@payloadcms/db-postgres':
specifier: 3.76.1
version: 3.76.1(payload@3.76.1(graphql@16.12.0)(typescript@5.7.3))
@ -23,6 +26,18 @@ importers:
'@payloadcms/ui':
specifier: 3.76.1
version: 3.76.1(@types/react@19.2.14)(monaco-editor@0.55.1)(next@16.1.6(@babel/core@7.29.0)(@playwright/test@1.58.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.77.4))(payload@3.76.1(graphql@16.12.0)(typescript@5.7.3))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.7.3)
'@radix-ui/react-label':
specifier: ^2.1.8
version: 2.1.8(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
'@radix-ui/react-separator':
specifier: ^1.1.8
version: 1.1.8(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
class-variance-authority:
specifier: ^0.7.1
version: 0.7.1
clsx:
specifier: ^2.1.1
version: 2.1.1
cross-env:
specifier: ^7.0.3
version: 7.0.3
@ -44,18 +59,27 @@ importers:
react-dom:
specifier: ^19.2.4
version: 19.2.4(react@19.2.4)
react-hook-form:
specifier: ^7.71.1
version: 7.71.1(react@19.2.4)
sharp:
specifier: 0.34.2
version: 0.34.2
tailwind-merge:
specifier: ^3.4.1
version: 3.4.1
tailwindcss-animate:
specifier: ^1.0.7
version: 1.0.7(tailwindcss@4.1.18)
tw-animate-css:
specifier: ^1.4.0
version: 1.4.0
zod:
specifier: ^4.3.6
version: 4.3.6
devDependencies:
'@next/eslint-plugin-next':
specifier: ^16.1.6
specifier: '@hookform/resolvers/zod'
version: 16.1.6
'@playwright/test':
specifier: 1.58.2
@ -880,6 +904,11 @@ packages:
'@floating-ui/utils@0.2.10':
resolution: {integrity: sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==}
'@hookform/resolvers@5.2.2':
resolution: {integrity: sha512-A/IxlMLShx3KjV/HeTcTfaMxdwy690+L/ZADoeaTltLx+CVuzkeVIPuybK3jrRfw7YZnmdKsVVHAlEPIAEUNlA==}
peerDependencies:
react-hook-form: ^7.55.0
'@humanfs/core@0.19.1':
resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==}
engines: {node: '>=18.18.0'}
@ -1426,6 +1455,63 @@ packages:
engines: {node: '>=18'}
hasBin: true
'@radix-ui/react-compose-refs@1.1.2':
resolution: {integrity: sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==}
peerDependencies:
'@types/react': '*'
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
peerDependenciesMeta:
'@types/react':
optional: true
'@radix-ui/react-label@2.1.8':
resolution: {integrity: sha512-FmXs37I6hSBVDlO4y764TNz1rLgKwjJMQ0EGte6F3Cb3f4bIuHB/iLa/8I9VKkmOy+gNHq8rql3j686ACVV21A==}
peerDependencies:
'@types/react': '*'
'@types/react-dom': '*'
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
peerDependenciesMeta:
'@types/react':
optional: true
'@types/react-dom':
optional: true
'@radix-ui/react-primitive@2.1.4':
resolution: {integrity: sha512-9hQc4+GNVtJAIEPEqlYqW5RiYdrr8ea5XQ0ZOnD6fgru+83kqT15mq2OCcbe8KnjRZl5vF3ks69AKz3kh1jrhg==}
peerDependencies:
'@types/react': '*'
'@types/react-dom': '*'
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
peerDependenciesMeta:
'@types/react':
optional: true
'@types/react-dom':
optional: true
'@radix-ui/react-separator@1.1.8':
resolution: {integrity: sha512-sDvqVY4itsKwwSMEe0jtKgfTh+72Sy3gPmQpjqcQneqQ4PFmr/1I0YA+2/puilhggCe2gJcx5EBAYFkWkdpa5g==}
peerDependencies:
'@types/react': '*'
'@types/react-dom': '*'
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
peerDependenciesMeta:
'@types/react':
optional: true
'@types/react-dom':
optional: true
'@radix-ui/react-slot@1.2.4':
resolution: {integrity: sha512-Jl+bCv8HxKnlTLVrcDE8zTMJ09R9/ukw4qBs/oZClOfoQk/cOTbDn+NceXfV7j09YPVQUryJPHurafcSg6EVKA==}
peerDependencies:
'@types/react': '*'
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
peerDependenciesMeta:
'@types/react':
optional: true
'@rolldown/pluginutils@1.0.0-beta.11':
resolution: {integrity: sha512-L/gAA/hyCSuzTF1ftlzUSI/IKr2POHsv1Dd78GfqkR83KMNuswWD61JxGV2L7nRwBBBSDr6R1gCkdTmoN7W4ag==}
@ -1560,6 +1646,9 @@ packages:
'@standard-schema/spec@1.1.0':
resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==}
'@standard-schema/utils@0.3.0':
resolution: {integrity: sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==}
'@swc/helpers@0.5.15':
resolution: {integrity: sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==}
@ -2170,6 +2259,9 @@ packages:
resolution: {integrity: sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg==}
engines: {node: '>=8'}
class-variance-authority@0.7.1:
resolution: {integrity: sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==}
client-only@0.0.1:
resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==}
@ -3737,6 +3829,12 @@ packages:
peerDependencies:
react: '>=16.13.1'
react-hook-form@7.71.1:
resolution: {integrity: sha512-9SUJKCGKo8HUSsCO+y0CtqkqI5nNuaDqTxyqPsZPqIwudpj4rCrAz/jZV+jn57bx5gtZKOh3neQu94DXMc+w5w==}
engines: {node: '>=18.0.0'}
peerDependencies:
react: ^16.8.0 || ^17 || ^18 || ^19
react-image-crop@10.1.8:
resolution: {integrity: sha512-4rb8XtXNx7ZaOZarKKnckgz4xLMvds/YrU6mpJfGhGAsy2Mg4mIw1x+DCCGngVGq2soTBVVOxx2s/C6mTX9+pA==}
peerDependencies:
@ -4038,6 +4136,9 @@ packages:
tabbable@6.4.0:
resolution: {integrity: sha512-05PUHKSNE8ou2dwIxTngl4EzcnsCDZGJ/iCLtDflR/SHB/ny14rXc+qU5P4mG9JkusiV7EivzY9Mhm55AzAvCg==}
tailwind-merge@3.4.1:
resolution: {integrity: sha512-2OA0rFqWOkITEAOFWSBSApYkDeH9t2B3XSJuI4YztKBzK3mX0737A2qtxDZ7xkw9Zfh0bWl+r34sF3HXV+Ig7Q==}
tailwindcss-animate@1.0.7:
resolution: {integrity: sha512-bl6mpH3T7I3UFxuvDEXLxy/VuFxBk5bbzplh7tXI68mwMokNYd1t9qPBHlnyTwfa4JGC4zP516I1hYYtQ/vspA==}
peerDependencies:
@ -5043,6 +5144,11 @@ snapshots:
'@floating-ui/utils@0.2.10': {}
'@hookform/resolvers@5.2.2(react-hook-form@7.71.1(react@19.2.4))':
dependencies:
'@standard-schema/utils': 0.3.0
react-hook-form: 7.71.1(react@19.2.4)
'@humanfs/core@0.19.1': {}
'@humanfs/node@0.16.7':
@ -5687,6 +5793,46 @@ snapshots:
dependencies:
playwright: 1.58.2
'@radix-ui/react-compose-refs@1.1.2(@types/react@19.2.14)(react@19.2.4)':
dependencies:
react: 19.2.4
optionalDependencies:
'@types/react': 19.2.14
'@radix-ui/react-label@2.1.8(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
dependencies:
'@radix-ui/react-primitive': 2.1.4(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
react: 19.2.4
react-dom: 19.2.4(react@19.2.4)
optionalDependencies:
'@types/react': 19.2.14
'@types/react-dom': 19.2.3(@types/react@19.2.14)
'@radix-ui/react-primitive@2.1.4(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
dependencies:
'@radix-ui/react-slot': 1.2.4(@types/react@19.2.14)(react@19.2.4)
react: 19.2.4
react-dom: 19.2.4(react@19.2.4)
optionalDependencies:
'@types/react': 19.2.14
'@types/react-dom': 19.2.3(@types/react@19.2.14)
'@radix-ui/react-separator@1.1.8(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
dependencies:
'@radix-ui/react-primitive': 2.1.4(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
react: 19.2.4
react-dom: 19.2.4(react@19.2.4)
optionalDependencies:
'@types/react': 19.2.14
'@types/react-dom': 19.2.3(@types/react@19.2.14)
'@radix-ui/react-slot@1.2.4(@types/react@19.2.14)(react@19.2.4)':
dependencies:
'@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.4)
react: 19.2.4
optionalDependencies:
'@types/react': 19.2.14
'@rolldown/pluginutils@1.0.0-beta.11': {}
'@rollup/rollup-android-arm-eabi@4.57.1':
@ -5768,6 +5914,8 @@ snapshots:
'@standard-schema/spec@1.1.0': {}
'@standard-schema/utils@0.3.0': {}
'@swc/helpers@0.5.15':
dependencies:
tslib: 2.8.1
@ -6396,6 +6544,10 @@ snapshots:
ci-info@4.4.0: {}
class-variance-authority@0.7.1:
dependencies:
clsx: 2.1.1
client-only@0.0.1: {}
clsx@2.1.1: {}
@ -8234,6 +8386,10 @@ snapshots:
'@babel/runtime': 7.28.6
react: 19.2.4
react-hook-form@7.71.1(react@19.2.4):
dependencies:
react: 19.2.4
react-image-crop@10.1.8(react@19.2.4):
dependencies:
react: 19.2.4
@ -8646,6 +8802,8 @@ snapshots:
tabbable@6.4.0: {}
tailwind-merge@3.4.1: {}
tailwindcss-animate@1.0.7(tailwindcss@4.1.18):
dependencies:
tailwindcss: 4.1.18

View file

@ -1,5 +1,7 @@
export default {
const config = {
plugins: {
'@tailwindcss/postcss': {},
},
}
export default config

View file

@ -1,11 +1,8 @@
import { PetitionForm } from '@/components/PetitionForm'
import { getCachedDocumentById, getDocuments } from '@/utilities/documents'
import { convertLexicalToHTML } from '@payloadcms/richtext-lexical/html'
import { notFound } from 'next/navigation'
import Link from 'next/link'
import { FieldDescription, FieldLegend, FieldSet } from '@/components/ui/field'
import z from 'zod'
import { useForm } from 'react-hook-form'
import { zodResolver } from '@hookform/resolvers/zod'
import { notFound } from 'next/navigation'
export async function generateStaticParams() {
const payload = await getDocuments('petition')
@ -16,29 +13,12 @@ export async function generateStaticParams() {
}
export default async function Page({ params }: { params: Promise<{ id: string }> }) {
const petition = await getCachedDocumentById('petition', (await params).id)()
const methods = useForm<z.infer<typeof formSchema>>({
resolver: zodResolver(formSchema),
defaultValues: {
name: '',
email: '',
phone: '',
state: '',
city: '',
postalCode: '',
comment: '',
},
})
const petition = await getCachedDocumentById('petition', (await params).id)
if (!petition || !petition.enabled) {
return notFound()
}
const onSubmit = (data: z.infer<typeof formSchema>) => {
console.log(data)
}
return (
<main>
<section className="h-screen w-screen flex items-center justify-center">
@ -49,73 +29,7 @@ export default async function Page({ params }: { params: Promise<{ id: string }>
<article
dangerouslySetInnerHTML={{ __html: convertLexicalToHTML({ data: petition.body }) }}
/>
<form id="formulario" onSubmit={methods.handleSubmit(onSubmit)}>
<FieldSet>
<FieldLegend>{petition.formTitle}</FieldLegend>
<FieldDescription>{petition.formDescription}</FieldDescription>
</FieldSet>
</form>
<PetitionForm id="formulario" petition={petition} />
</main>
)
}
const STATES = [
'AC',
'AL',
'AM',
'AP',
'BA',
'CE',
'DF',
'ES',
'GO',
'MA',
'MG',
'MS',
'MT',
'PA',
'PB',
'PE',
'PI',
'PR',
'RJ',
'RN',
'RO',
'RR',
'RS',
'SC',
'SE',
'SP',
'TO',
]
const formSchema = z.object({
name: z
.string()
.trim()
.min(2, 'Nome deve ter pelo menos 2 caracteres')
.max(120, 'Nome deve ter no máximo 100 caracteres')
.regex(
/^(?=.* )[\p{L}\p{M}]+(?:[- ][\p{L}\p{M}]+)*$/u,
'Informe nome e sobrenome. Use apenas letras, no máximo um espaço ou hífen entre termos, e sem espaço no início ou no fim.',
),
email: z.email('Email inválido'),
phone: z
.string()
.trim()
.length(11, 'Telefone celular inválido')
.regex(/^\d{11}$/, 'Telefone celular inválido'),
state: z.literal(STATES, 'Estado inválido'),
city: z.string().trim().min(3, 'Cidade inválida').max(100, 'Cidade muito longa'),
postalCode: z
.string()
.trim()
.regex(/^(?:\d{8})?$/, 'CEP inválido')
.optional(),
comment: z
.string()
.trim()
.max(1000, 'Comentário muito longo')
.refine((v) => !/<\/?[a-z][\s\S]*>/i.test(v), 'O comentário não pode conter HTML')
.optional(),
})

View file

@ -1,19 +1,19 @@
import { Footer } from '@/components/Footer'
import { Header } from '@/components/Header'
import React from 'react'
import './styles.css'
import { getCachedDocumentById } from '@/utilities/documents'
import { getCachedGlobal } from '@/utilities/globals'
import { RefreshRouteOnSave } from '@/utilities/RefreshRouteOnSave'
import { Metadata } from 'next'
import { getCachedGlobal } from '@/utilities/globals'
import { getCachedDocumentById } from '@/utilities/documents'
import React from 'react'
import './styles.css'
export async function generateMetadata(): Promise<Metadata> {
const payload = await getCachedGlobal('metadata')()
const payload = await getCachedGlobal('metadata')
let image = payload.image
if (typeof image === 'number') {
image = await getCachedDocumentById('media', String(payload.image))()
image = await getCachedDocumentById('media', String(payload.image))
}
return {

View file

@ -1,18 +1,15 @@
import { getCachedGlobal } from '@/utilities/globals'
import Image from 'next/image'
import { getPayload } from 'payload'
import React from 'react'
import configPromise from '@payload-config'
import { Media } from '@/payload-types'
import { getCachedDocumentById } from '@/utilities/documents'
import { getCachedGlobal } from '@/utilities/globals'
import Image from 'next/image'
export default async function HomePage() {
const payload = await getCachedGlobal('home', 2)()
const payload = await getCachedGlobal('home', 2)
let image: Media
if (typeof payload.image === 'number') {
image = await getCachedDocumentById('media', `${payload.image}`)()
image = await getCachedDocumentById('media', `${payload.image}`)
} else {
image = payload.image
}

View file

@ -1,12 +0,0 @@
import configPromise from '@payload-config'
import { getPayload } from 'payload'
export const GET = async (request: Request) => {
const payload = await getPayload({
config: configPromise,
})
return Response.json({
message: 'Este é um exemplo de rota personalizada.',
})
}

View file

@ -2,7 +2,7 @@ import { getCachedGlobal } from '@/utilities/globals'
import { SocialLinks } from './socialLinks'
export const Header = async () => {
const settings = await getCachedGlobal('site-settings', 2)()
const settings = await getCachedGlobal('site-settings', 2)
return (
<header className="flex items-center justify-between p-4 absolute top-0 left-0 right-0 bg-black">

View file

@ -0,0 +1,122 @@
'use client'
import { Field, FieldDescription, FieldGroup, FieldLabel, FieldLegend, FieldSet } from '@/components/ui/field'
import { Input } from '@/components/ui/input'
import { Petition } from '@/payload-types'
import { zodResolver } from '@hookform/resolvers/zod'
import { useForm } from 'react-hook-form'
import z from 'zod'
interface PetitionFormProps {
id: string
petition: Petition
}
export const PetitionForm = ({ id, petition }: PetitionFormProps) => {
const methods = useForm<z.infer<typeof formSchema>>({
resolver: zodResolver(formSchema),
defaultValues: {
name: '',
email: '',
phone: '',
state: '',
city: '',
postalCode: '',
comment: '',
},
})
const onSubmit = (data: z.infer<typeof formSchema>) => {
console.log(data)
}
return (
<form id={id} onSubmit={methods.handleSubmit(onSubmit)}>
<FieldSet>
<FieldLegend>Revogar concessão da orla de Salvador</FieldLegend>
<FieldDescription>Assine esta petição e ajude a manter a orla de Salvador para todos nós!</FieldDescription>
<FieldGroup>
<Field>
<FieldLabel htmlFor="name">
Nome Completo
</FieldLabel>
<Input id="name" placeholder="Digite seu nome completo" autoComplete='name' maxLength={120} required />
</Field>
<Field>
<FieldLabel htmlFor="email">
E-mail
</FieldLabel>
<Input id="email" placeholder="Digite seu e-mail" autoComplete='email' inputMode="email" type='text' maxLength={254} required />
</Field>
<Field>
<FieldLabel htmlFor="phone">
Celular
</FieldLabel>
<Input id="phone" placeholder="(71) 99999-9999" autoComplete='tel' inputMode="tel" type='tel' maxLength={15} required />
</Field>
</FieldGroup>
</FieldSet>
</form>
)
}
const STATES = [
'AC',
'AL',
'AM',
'AP',
'BA',
'CE',
'DF',
'ES',
'GO',
'MA',
'MG',
'MS',
'MT',
'PA',
'PB',
'PE',
'PI',
'PR',
'RJ',
'RN',
'RO',
'RR',
'RS',
'SC',
'SE',
'SP',
'TO',
]
const formSchema = z.object({
name: z
.string()
.trim()
.min(2, 'Nome deve ter pelo menos 2 caracteres')
.max(120, 'Nome deve ter no máximo 100 caracteres')
.regex(
/^(?=.* )[\p{L}\p{M}]+(?:[- ][\p{L}\p{M}]+)*$/u,
'Informe nome e sobrenome. Use apenas letras, no máximo um espaço ou hífen entre termos, e sem espaço no início ou no fim.',
),
email: z.email('Email inválido'),
phone: z
.string()
.trim()
.length(11, 'Telefone celular inválido')
.regex(/^\d{11}$/, 'Telefone celular inválido'),
state: z.literal(STATES, 'Estado inválido'),
city: z.string().trim().min(3, 'Cidade inválida').max(100, 'Cidade muito longa'),
postalCode: z
.string()
.trim()
.regex(/^(?:\d{8})?$/, 'CEP inválido')
.optional(),
comment: z
.string()
.trim()
.max(1000, 'Comentário muito longo')
.refine((v) => !/<\/?[a-z][\s\S]*>/i.test(v), 'O comentário não pode conter HTML')
.optional(),
})

View file

@ -79,7 +79,7 @@ type SocialLinksProps = {
}
export const SocialLinks = async ({ ariaLabel }: SocialLinksProps) => {
const { socialLinks: links } = await getCachedGlobal('site-settings', 2)()
const { socialLinks: links } = await getCachedGlobal('site-settings', 2)
if (!links?.length) return null

View file

@ -0,0 +1,22 @@
import * as React from "react"
import { cn } from "@/lib/utils"
const Input = React.forwardRef<HTMLInputElement, React.ComponentProps<"input">>(
({ className, type, ...props }, ref) => {
return (
<input
type={type}
className={cn(
"flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-base shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
className
)}
ref={ref}
{...props}
/>
)
}
)
Input.displayName = "Input"
export { Input }

View file

@ -1,8 +1,7 @@
import { Config } from '@/payload-types'
import configPromise from '@payload-config'
import { revalidateTag } from 'next/cache'
import { unstable_cache } from 'next/cache'
import { cacheTag, revalidateTag } from 'next/cache'
import { getPayload } from 'payload'
type Collection = keyof Config['collections']
@ -22,14 +21,15 @@ export const getDocumentById = <Slug extends Collection>(
const getTag = <Slug extends Collection>(collection: Slug, id: string) =>
`document_${collection}:${id}`
export const getCachedDocumentById = <Slug extends Collection>(
export const getCachedDocumentById = async <Slug extends Collection>(
collection: Slug,
id: string,
depth?: number,
) =>
unstable_cache(() => getDocumentById(collection, id, depth), [id], {
tags: [getTag(collection, id)],
})
) => {
'use cache'
cacheTag(getTag(collection, id))
return await getDocumentById(collection, id, depth)
}
export const revalidateDocumentById = <Slug extends Collection>(collection: Slug, id: string) =>
revalidateTag(getTag(collection, id))
revalidateTag(getTag(collection, id), 'max')

View file

@ -1,13 +1,12 @@
import type { Config } from 'src/payload-types'
import configPromise from '@payload-config'
import { unstable_cache } from 'next/cache'
import { cacheTag, revalidateTag } from 'next/cache'
import { getPayload } from 'payload'
import { revalidateTag } from 'next/cache'
type Global = keyof Config['globals']
export async function getGlobal<Slug extends Global>(slug: Slug, depth = 0) {
export async function getGlobal<Slug extends Global>(slug: Slug, depth?: number) {
const payload = await getPayload({ config: configPromise })
const global = await payload.findGlobal({
@ -18,12 +17,12 @@ export async function getGlobal<Slug extends Global>(slug: Slug, depth = 0) {
return global
}
/**
* Returns a unstable_cache function mapped with the cache tag for the slug
*/
export const getCachedGlobal = <Slug extends Global>(slug: Slug, depth = 0) =>
unstable_cache(async () => getGlobal(slug, depth), [slug], {
tags: [`global_${slug}`],
})
const getTag = (slug: string) => `global_${slug}`
export const revalidateGlobal = <Slug extends Global>(slug: Slug) => revalidateTag(`global_${slug}`)
export const getCachedGlobal = async <Slug extends Global>(slug: Slug, depth?: number) => {
'use cache'
cacheTag(getTag(slug))
return await getGlobal(slug, depth)
}
export const revalidateGlobal = <Slug extends Global>(slug: Slug) => revalidateTag(getTag(slug), 'max')

View file

@ -1,11 +1,11 @@
import { test, expect, Page } from '@playwright/test'
import { expect, Page, test } from '@playwright/test'
import { login } from '../helpers/login'
import { seedTestUser, cleanupTestUser, testUser } from '../helpers/seedUser'
import { cleanupTestUser, seedTestUser, testUser } from '../helpers/seedUser'
test.describe('Admin Panel', () => {
let page: Page
test.beforeAll(async ({ browser }, testInfo) => {
test.beforeAll(async ({ browser }) => {
await seedTestUser()
const context = await browser.newContext()

View file

@ -1,9 +1,10 @@
import { test, expect, Page } from '@playwright/test'
import { expect, Page, test } from '@playwright/test'
test.describe('Frontend', () => {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
let page: Page
test.beforeAll(async ({ browser }, testInfo) => {
test.beforeAll(async ({ browser }) => {
const context = await browser.newContext()
page = await context.newPage()
})