mirror of
https://github.com/fsolla/teqo.git
synced 2026-11-12 22:13:05 +00:00
feat(cms): add homepage global and wire frontend to Payload content
- add global () with required media upload field - register new global in and include it in admin live preview - replace hardcoded frontend landing content with CMS-driven homepage image - add typed cached utilities for globals/documents + centralized revalidation helpers - migrate components to new global utility module and remove deprecated helper - update revalidation hook to use shared global revalidation - refine frontend layout/header and simplify legacy homepage CSS - regenerate for new global/schema changes
This commit is contained in:
parent
375398af84
commit
b955c48091
11 changed files with 122 additions and 136 deletions
|
|
@ -18,7 +18,7 @@ export default async function RootLayout(props: { children: React.ReactNode }) {
|
|||
<body>
|
||||
<RefreshRouteOnSave />
|
||||
<Header />
|
||||
<main>{children}</main>
|
||||
{children}
|
||||
<Footer />
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -1,61 +1,28 @@
|
|||
import { headers as getHeaders } from 'next/headers.js'
|
||||
import { getCachedGlobal } from '@/utilities/globals'
|
||||
import Image from 'next/image'
|
||||
import { getPayload } from 'payload'
|
||||
import React from 'react'
|
||||
import { fileURLToPath } from 'url'
|
||||
|
||||
import config from '@/payload.config'
|
||||
import configPromise from '@payload-config'
|
||||
import { Media } from '@/payload-types'
|
||||
import { getCachedDocumentById } from '@/utilities/documents'
|
||||
|
||||
export default async function HomePage() {
|
||||
const headers = await getHeaders()
|
||||
const payloadConfig = await config
|
||||
const payload = await getPayload({ config: payloadConfig })
|
||||
const { user } = await payload.auth({ headers })
|
||||
const payload = await getCachedGlobal('home', 2)()
|
||||
|
||||
const fileURL = `vscode://file/${fileURLToPath(import.meta.url)}`
|
||||
let image: Media
|
||||
|
||||
if (typeof payload.image === 'number') {
|
||||
image = await getCachedDocumentById('media', `${payload.image}`)()
|
||||
} else {
|
||||
image = payload.image
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="home">
|
||||
<div className="content">
|
||||
<picture>
|
||||
<source srcSet="https://raw.githubusercontent.com/payloadcms/payload/main/packages/ui/src/assets/payload-favicon.svg" />
|
||||
<Image
|
||||
alt="Payload Logo"
|
||||
height={65}
|
||||
src="https://raw.githubusercontent.com/payloadcms/payload/main/packages/ui/src/assets/payload-favicon.svg"
|
||||
width={65}
|
||||
/>
|
||||
</picture>
|
||||
{!user && <h1>Boas-vindas ao seu novo projeto.</h1>}
|
||||
{user && <h1>Que bom ver você de volta, {user.email}.</h1>}
|
||||
<div className="links">
|
||||
<a
|
||||
className="admin"
|
||||
href={payloadConfig.routes.admin}
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
Ir para o painel administrativo
|
||||
</a>
|
||||
<a
|
||||
className="docs"
|
||||
href="https://payloadcms.com/docs"
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
Documentação
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div className="footer">
|
||||
<p>Atualize esta página editando</p>
|
||||
<a className="codeLink" href={fileURL}>
|
||||
<code>app/(frontend)/page.tsx</code>
|
||||
</a>
|
||||
</div>
|
||||
<div className="mt-4 rounded-md border border-white bg-white px-3 py-1 text-sm text-black">
|
||||
Tailwind está ativo: as classes utilitárias estão sendo aplicadas.
|
||||
</div>
|
||||
</div>
|
||||
// Main element occupies the whole screen width and the height of screen minus height of header
|
||||
<main className="w-screen h-screen overflow-hidden">
|
||||
{image?.url ? (
|
||||
<Image src={image.url} alt={image.alt} width={image.width!} height={image.height!} />
|
||||
) : null}
|
||||
</main>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
@import "tailwindcss";
|
||||
@import 'tailwindcss';
|
||||
@source "../../**/*.{js,ts,jsx,tsx,mdx}";
|
||||
|
||||
:root {
|
||||
|
|
@ -92,76 +92,6 @@ svg {
|
|||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.home {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
height: 100vh;
|
||||
padding: 45px;
|
||||
max-width: 1024px;
|
||||
margin: 0 auto;
|
||||
overflow: hidden;
|
||||
|
||||
@media (max-width: 400px) {
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
.content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-grow: 1;
|
||||
|
||||
h1 {
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
.links {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
padding: 0.25rem 0.5rem;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.admin {
|
||||
color: rgb(0, 0, 0);
|
||||
background: rgb(1000, 1000, 1000);
|
||||
border: 1px solid rgb(0, 0, 0);
|
||||
}
|
||||
|
||||
.docs {
|
||||
color: rgb(1000, 1000, 1000);
|
||||
background: rgb(0, 0, 0);
|
||||
border: 1px solid rgb(1000, 1000, 1000);
|
||||
}
|
||||
}
|
||||
|
||||
.footer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
|
||||
@media (max-width: 1024px) {
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.codeLink {
|
||||
text-decoration: none;
|
||||
padding: 0 0.5rem;
|
||||
background: rgb(60, 60, 60);
|
||||
border-radius: 4px;
|
||||
}
|
||||
}
|
||||
button {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,13 +1,11 @@
|
|||
import { getCachedGlobal } from '@/utilities/getGlobals'
|
||||
import { getCachedGlobal } from '@/utilities/globals'
|
||||
import { SocialLinks } from './socialLinks'
|
||||
|
||||
export const Header = async () => {
|
||||
const settings = await getCachedGlobal('site-settings', 2)()
|
||||
|
||||
console.log(settings)
|
||||
|
||||
return (
|
||||
<header className="flex items-center justify-between p-4">
|
||||
<header className="flex items-center justify-between p-4 absolute top-0 left-0 right-0 bg-black">
|
||||
{settings.headerTitle ? <h2>{settings.headerTitle}</h2> : null}
|
||||
<SocialLinks ariaLabel="Redes sociais" />
|
||||
</header>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { getCachedGlobal } from '@/utilities/getGlobals'
|
||||
import { getCachedGlobal } from '@/utilities/globals'
|
||||
import Link from 'next/link'
|
||||
|
||||
type IconProps = {
|
||||
|
|
|
|||
30
src/globals/HomePage.ts
Normal file
30
src/globals/HomePage.ts
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
import { revalidateGlobal } from '@/utilities/globals'
|
||||
import { GlobalConfig } from 'payload'
|
||||
|
||||
const slug = 'home'
|
||||
|
||||
const revalidate = async () => revalidateGlobal(slug)
|
||||
|
||||
export const HomePage: GlobalConfig = {
|
||||
slug,
|
||||
label: 'Pagina Inicial',
|
||||
admin: {
|
||||
group: 'Paginas',
|
||||
},
|
||||
access: {
|
||||
read: () => true,
|
||||
update: ({ req: { user } }) => Boolean(user),
|
||||
},
|
||||
hooks: {
|
||||
afterChange: [revalidate],
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'image',
|
||||
type: 'upload',
|
||||
label: 'Imagem',
|
||||
relationTo: 'media',
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
}
|
||||
|
|
@ -1,10 +1,13 @@
|
|||
import { revalidateGlobal } from '@/utilities/globals'
|
||||
import { revalidateTag } from 'next/cache'
|
||||
import type { GlobalConfig } from 'payload'
|
||||
|
||||
const revalidate = async () => revalidateTag('global_site-settings')
|
||||
const slug = 'site-settings'
|
||||
|
||||
const revalidate = async () => revalidateGlobal(slug)
|
||||
|
||||
export const SiteSettings: GlobalConfig = {
|
||||
slug: 'site-settings',
|
||||
slug,
|
||||
label: 'Configurações do site',
|
||||
admin: {
|
||||
group: 'Configurações',
|
||||
|
|
|
|||
|
|
@ -91,9 +91,11 @@ export interface Config {
|
|||
fallbackLocale: null;
|
||||
globals: {
|
||||
'site-settings': SiteSetting;
|
||||
home: Home;
|
||||
};
|
||||
globalsSelect: {
|
||||
'site-settings': SiteSettingsSelect<false> | SiteSettingsSelect<true>;
|
||||
home: HomeSelect<false> | HomeSelect<true>;
|
||||
};
|
||||
locale: null;
|
||||
user: User;
|
||||
|
|
@ -473,6 +475,16 @@ export interface SiteSetting {
|
|||
updatedAt?: string | null;
|
||||
createdAt?: string | null;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "home".
|
||||
*/
|
||||
export interface Home {
|
||||
id: number;
|
||||
image: number | Media;
|
||||
updatedAt?: string | null;
|
||||
createdAt?: string | null;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "site-settings_select".
|
||||
|
|
@ -493,6 +505,16 @@ export interface SiteSettingsSelect<T extends boolean = true> {
|
|||
createdAt?: T;
|
||||
globalType?: T;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "home_select".
|
||||
*/
|
||||
export interface HomeSelect<T extends boolean = true> {
|
||||
image?: T;
|
||||
updatedAt?: T;
|
||||
createdAt?: T;
|
||||
globalType?: T;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "TaskSchedulePublish".
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import { Users } from './collections/Users'
|
|||
import { Media } from './collections/Media'
|
||||
import { SiteSettings } from './globals/SiteSettings'
|
||||
import { pt } from 'payload/i18n/pt'
|
||||
import { HomePage } from './globals/HomePage'
|
||||
|
||||
const filename = fileURLToPath(import.meta.url)
|
||||
const dirname = path.dirname(filename)
|
||||
|
|
@ -21,11 +22,11 @@ export default buildConfig({
|
|||
},
|
||||
livePreview: {
|
||||
url: 'http://localhost:3000',
|
||||
globals: ['site-settings'],
|
||||
globals: ['site-settings', 'home'],
|
||||
},
|
||||
},
|
||||
collections: [Users, Media],
|
||||
globals: [SiteSettings],
|
||||
globals: [SiteSettings, HomePage],
|
||||
editor: lexicalEditor(),
|
||||
secret: process.env.PAYLOAD_SECRET || '',
|
||||
typescript: {
|
||||
|
|
|
|||
32
src/utilities/documents.ts
Normal file
32
src/utilities/documents.ts
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
import { Config } from '@/payload-types'
|
||||
|
||||
import configPromise from '@payload-config'
|
||||
import { revalidateTag } from 'next/cache'
|
||||
import { unstable_cache } from 'next/cache'
|
||||
import { getPayload } from 'payload'
|
||||
|
||||
type Collection = keyof Config['collections']
|
||||
|
||||
export const getDocumentById = <Slug extends Collection>(
|
||||
collection: Slug,
|
||||
id: string,
|
||||
depth?: number,
|
||||
) =>
|
||||
getPayload({ config: configPromise }).then((payload) =>
|
||||
payload.findByID({ collection, id, depth }),
|
||||
)
|
||||
|
||||
const getTag = <Slug extends Collection>(collection: Slug, id: string) =>
|
||||
`document_${collection}:${id}`
|
||||
|
||||
export const getCachedDocumentById = <Slug extends Collection>(
|
||||
collection: Slug,
|
||||
id: string,
|
||||
depth?: number,
|
||||
) =>
|
||||
unstable_cache(() => getDocumentById(collection, id, depth), [id], {
|
||||
tags: [getTag(collection, id)],
|
||||
})
|
||||
|
||||
export const revalidateDocumentById = <Slug extends Collection>(collection: Slug, id: string) =>
|
||||
revalidateTag(getTag(collection, id))
|
||||
|
|
@ -3,10 +3,11 @@ import type { Config } from 'src/payload-types'
|
|||
import configPromise from '@payload-config'
|
||||
import { unstable_cache } from 'next/cache'
|
||||
import { getPayload } from 'payload'
|
||||
import { revalidateTag } from 'next/cache'
|
||||
|
||||
type Global = keyof Config['globals']
|
||||
|
||||
export async function getGlobal(slug: Global, depth = 0) {
|
||||
export async function getGlobal<Slug extends Global>(slug: Slug, depth = 0) {
|
||||
const payload = await getPayload({ config: configPromise })
|
||||
|
||||
const global = await payload.findGlobal({
|
||||
|
|
@ -20,7 +21,9 @@ export async function getGlobal(slug: Global, depth = 0) {
|
|||
/**
|
||||
* Returns a unstable_cache function mapped with the cache tag for the slug
|
||||
*/
|
||||
export const getCachedGlobal = (slug: Global, depth = 0) =>
|
||||
export const getCachedGlobal = <Slug extends Global>(slug: Slug, depth = 0) =>
|
||||
unstable_cache(async () => getGlobal(slug, depth), [slug], {
|
||||
tags: [`global_${slug}`],
|
||||
})
|
||||
|
||||
export const revalidateGlobal = <Slug extends Global>(slug: Slug) => revalidateTag(`global_${slug}`)
|
||||
Loading…
Reference in a new issue