Erste brauchbare Version

This commit is contained in:
timg
2026-08-31 19:55:41 +02:00
parent 080c4d0f39
commit 892a4d4747
35 changed files with 7103 additions and 219 deletions
+10
View File
@@ -0,0 +1,10 @@
DATABASE_URL=postgresql://...
PAYLOAD_SECRET=ein-langer-zufälliger-secret-string
MAIL_HOST=smtp.example.org
MAIL_PORT=465
MAIL_DISPLAYNAME=My Company Name
MAIL_USER=noreply@example.org
MAIL_PASSWORD=password
NEXT_PUBLIC_PAYLOAD_URL=http://example.org
+4
View File
@@ -39,3 +39,7 @@ yarn-error.log*
# typescript
*.tsbuildinfo
next-env.d.ts
# Payload CMS
/media/
*.db
+9 -2
View File
@@ -1,6 +1,9 @@
import { type ReactNode } from 'react';
import { type Metadata } from 'next';
import '@/app/globals.css';
import { getPreview } from '@/lib/preview';
import RefreshRouteOnSave from '@/components/RefreshRouteOnSave';
import EndPreview from '@/components/EndPreviewButton';
import '@/app/(app)/globals.css';
type Props = Readonly<{
children: ReactNode;
@@ -18,11 +21,15 @@ export const metadata: Metadata = {
}
};
export default function RootLayout({ children }: Props) {
export default async function RootLayout({ children }: Props) {
const { isPreview } = await getPreview();
return (
<html lang="de" >
<body>
<RefreshRouteOnSave />
{children}
{isPreview && <EndPreview />}
</body>
</html>
);
+29
View File
@@ -0,0 +1,29 @@
import Link from 'next/link';
import { getPreview } from '@/lib/preview';
import config from '@payload-config';
import { getPayload } from 'payload';
export default async function Page() {
const { isPreview, where } = await getPreview();
const payload = await getPayload({ config });
const posts = await payload.find({
collection: 'posts',
where,
draft: isPreview
});
return (
<main className="flex flex-col">
{posts.docs.map((post) => (
<Link
key={post.id}
className="border border-1 border-primary p-4 m-4"
href={`/post/${post.slug}`}
>
{post.title}
</Link>
))}
</main>
);
}
+15
View File
@@ -0,0 +1,15 @@
import Link from 'next/link';
export default function Page() {
return (
<div className="min-h-screen flex flex-col justify-center items-center">
<p>Post nicht gefunden</p>
<Link
className="border border-1 border-primary"
href="/"
>
Zurück
</Link>
</div>
);
}
+48
View File
@@ -0,0 +1,48 @@
import { notFound } from 'next/navigation';
import Link from 'next/link';
import { getPreview } from '@/lib/preview';
import config from '@payload-config';
import { getPayload } from 'payload';
import { RichText } from '@payloadcms/richtext-lexical/react';
type Props = Readonly<{
params: Promise<{
slug: string;
}>;
}>;
export default async function Page({ params }: Props) {
const { isPreview, where } = await getPreview();
const { slug } = await params;
const payload = await getPayload({ config });
const result = await payload.find({
collection: 'posts',
where: {
...where,
slug: {
equals: slug
}
},
draft: isPreview,
limit: 1
});
const post = result.docs[0];
if (!post)
return notFound();
return (
<div className="m-4">
<Link
className="border border-1 border-primary"
href="/"
>
Zurück
</Link>
<h1 className="font-bold">{post.title}</h1>
<RichText data={post.content} />
</div>
);
}
@@ -0,0 +1,29 @@
import config from '@payload-config';
import { NotFoundPage, generatePageMetadata } from '@payloadcms/next/views';
import { importMap } from '../importMap';
type Props = Readonly<{
params: Promise<{
segments: string[];
}>;
searchParams: Promise<{
[key: string]: string | string[];
}>;
}>;
export function generateMetadata({ params, searchParams }: Props) {
return generatePageMetadata({
config,
params,
searchParams
});
}
export default function NotFound({ params, searchParams }: Props) {
return NotFoundPage({
config,
params,
searchParams,
importMap
});
}
@@ -0,0 +1,29 @@
import config from '@payload-config';
import { RootPage, generatePageMetadata } from '@payloadcms/next/views';
import { importMap } from '../importMap';
type Props = Readonly<{
params: Promise<{
segments: string[];
}>;
searchParams: Promise<{
[key: string]: string | string[];
}>;
}>;
export function generateMetadata({ params, searchParams}: Props) {
return generatePageMetadata({
config,
params,
searchParams
});
}
export default function Page({ params, searchParams }: Props) {
return RootPage({
config,
params,
searchParams,
importMap
});
}
+58
View File
@@ -0,0 +1,58 @@
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 { UploadFeatureClient as UploadFeatureClient_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 { UnderlineFeatureClient as UnderlineFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client'
import { StrikethroughFeatureClient as StrikethroughFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client'
import { SubscriptFeatureClient as SubscriptFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client'
import { SuperscriptFeatureClient as SuperscriptFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client'
import { InlineCodeFeatureClient as InlineCodeFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client'
import { ParagraphFeatureClient as ParagraphFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client'
import { HeadingFeatureClient as HeadingFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client'
import { AlignFeatureClient as AlignFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client'
import { IndentFeatureClient as IndentFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client'
import { UnorderedListFeatureClient as UnorderedListFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client'
import { OrderedListFeatureClient as OrderedListFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client'
import { ChecklistFeatureClient as ChecklistFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client'
import { LinkFeatureClient as LinkFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client'
import { RelationshipFeatureClient as RelationshipFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client'
import { BlockquoteFeatureClient as BlockquoteFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client'
import { HorizontalRuleFeatureClient as HorizontalRuleFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client'
import { InlineToolbarFeatureClient as InlineToolbarFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client'
import { Icon as Icon_f934946492fabd3bfa95068204328949 } from '../../../components/PayloadInjects'
import { Logo as Logo_f934946492fabd3bfa95068204328949 } from '../../../components/PayloadInjects'
import { Action as Action_f934946492fabd3bfa95068204328949 } from '../../../components/PayloadInjects'
import { CollectionCards as CollectionCards_f9c02e79a4aed9a3924487c0cd4cafb1 } from '@payloadcms/next/rsc'
/** @type import('payload').ImportMap */
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#UploadFeatureClient": UploadFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
"@payloadcms/richtext-lexical/client#BoldFeatureClient": BoldFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
"@payloadcms/richtext-lexical/client#ItalicFeatureClient": ItalicFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
"@payloadcms/richtext-lexical/client#UnderlineFeatureClient": UnderlineFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
"@payloadcms/richtext-lexical/client#StrikethroughFeatureClient": StrikethroughFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
"@payloadcms/richtext-lexical/client#SubscriptFeatureClient": SubscriptFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
"@payloadcms/richtext-lexical/client#SuperscriptFeatureClient": SuperscriptFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
"@payloadcms/richtext-lexical/client#InlineCodeFeatureClient": InlineCodeFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
"@payloadcms/richtext-lexical/client#ParagraphFeatureClient": ParagraphFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
"@payloadcms/richtext-lexical/client#HeadingFeatureClient": HeadingFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
"@payloadcms/richtext-lexical/client#AlignFeatureClient": AlignFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
"@payloadcms/richtext-lexical/client#IndentFeatureClient": IndentFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
"@payloadcms/richtext-lexical/client#UnorderedListFeatureClient": UnorderedListFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
"@payloadcms/richtext-lexical/client#OrderedListFeatureClient": OrderedListFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
"@payloadcms/richtext-lexical/client#ChecklistFeatureClient": ChecklistFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
"@payloadcms/richtext-lexical/client#LinkFeatureClient": LinkFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
"@payloadcms/richtext-lexical/client#RelationshipFeatureClient": RelationshipFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
"@payloadcms/richtext-lexical/client#BlockquoteFeatureClient": BlockquoteFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
"@payloadcms/richtext-lexical/client#HorizontalRuleFeatureClient": HorizontalRuleFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
"@payloadcms/richtext-lexical/client#InlineToolbarFeatureClient": InlineToolbarFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
"/components/PayloadInjects#Icon": Icon_f934946492fabd3bfa95068204328949,
"/components/PayloadInjects#Logo": Logo_f934946492fabd3bfa95068204328949,
"/components/PayloadInjects#Action": Action_f934946492fabd3bfa95068204328949,
"@payloadcms/next/rsc#CollectionCards": CollectionCards_f9c02e79a4aed9a3924487c0cd4cafb1
}
+55
View File
@@ -0,0 +1,55 @@
import { RscEntryLexicalCell } from'@payloadcms/richtext-lexical/rsc';
import { RscEntryLexicalField } from'@payloadcms/richtext-lexical/rsc';
import { LexicalDiffComponent } from'@payloadcms/richtext-lexical/rsc';
import { InlineToolbarFeatureClient } from'@payloadcms/richtext-lexical/client';
import { HorizontalRuleFeatureClient } from'@payloadcms/richtext-lexical/client';
import { UploadFeatureClient } from'@payloadcms/richtext-lexical/client';
import { BlockquoteFeatureClient } from'@payloadcms/richtext-lexical/client';
import { RelationshipFeatureClient } from'@payloadcms/richtext-lexical/client';
import { LinkFeatureClient } from'@payloadcms/richtext-lexical/client';
import { ChecklistFeatureClient } from'@payloadcms/richtext-lexical/client';
import { OrderedListFeatureClient } from'@payloadcms/richtext-lexical/client';
import { UnorderedListFeatureClient } from'@payloadcms/richtext-lexical/client';
import { IndentFeatureClient } from'@payloadcms/richtext-lexical/client';
import { AlignFeatureClient } from'@payloadcms/richtext-lexical/client';
import { HeadingFeatureClient } from'@payloadcms/richtext-lexical/client';
import { ParagraphFeatureClient } from'@payloadcms/richtext-lexical/client';
import { InlineCodeFeatureClient } from'@payloadcms/richtext-lexical/client';
import { SuperscriptFeatureClient } from'@payloadcms/richtext-lexical/client';
import { SubscriptFeatureClient } from'@payloadcms/richtext-lexical/client';
import { StrikethroughFeatureClient } from'@payloadcms/richtext-lexical/client';
import { UnderlineFeatureClient } from'@payloadcms/richtext-lexical/client';
import { BoldFeatureClient } from'@payloadcms/richtext-lexical/client';
import { ItalicFeatureClient } from'@payloadcms/richtext-lexical/client';
import { CollectionCards } from'@payloadcms/next/rsc';
import { Logo, Icon, Action } from '@/components/PayloadInjects';
export const importMap = {
'@payloadcms/richtext-lexical/rsc#RscEntryLexicalCell': RscEntryLexicalCell,
'@payloadcms/richtext-lexical/rsc#RscEntryLexicalField': RscEntryLexicalField,
'@payloadcms/richtext-lexical/rsc#LexicalDiffComponent': LexicalDiffComponent,
'@payloadcms/richtext-lexical/client#InlineToolbarFeatureClient': InlineToolbarFeatureClient,
'@payloadcms/richtext-lexical/client#HorizontalRuleFeatureClient': HorizontalRuleFeatureClient,
'@payloadcms/richtext-lexical/client#UploadFeatureClient': UploadFeatureClient,
'@payloadcms/richtext-lexical/client#BlockquoteFeatureClient': BlockquoteFeatureClient,
'@payloadcms/richtext-lexical/client#RelationshipFeatureClient': RelationshipFeatureClient,
'@payloadcms/richtext-lexical/client#LinkFeatureClient': LinkFeatureClient,
'@payloadcms/richtext-lexical/client#ChecklistFeatureClient': ChecklistFeatureClient,
'@payloadcms/richtext-lexical/client#OrderedListFeatureClient': OrderedListFeatureClient,
'@payloadcms/richtext-lexical/client#UnorderedListFeatureClient': UnorderedListFeatureClient,
'@payloadcms/richtext-lexical/client#IndentFeatureClient': IndentFeatureClient,
'@payloadcms/richtext-lexical/client#AlignFeatureClient': AlignFeatureClient,
'@payloadcms/richtext-lexical/client#HeadingFeatureClient': HeadingFeatureClient,
'@payloadcms/richtext-lexical/client#ParagraphFeatureClient': ParagraphFeatureClient,
'@payloadcms/richtext-lexical/client#InlineCodeFeatureClient': InlineCodeFeatureClient,
'@payloadcms/richtext-lexical/client#SuperscriptFeatureClient': SuperscriptFeatureClient,
'@payloadcms/richtext-lexical/client#SubscriptFeatureClient': SubscriptFeatureClient,
'@payloadcms/richtext-lexical/client#StrikethroughFeatureClient': StrikethroughFeatureClient,
'@payloadcms/richtext-lexical/client#UnderlineFeatureClient': UnderlineFeatureClient,
'@payloadcms/richtext-lexical/client#BoldFeatureClient': BoldFeatureClient,
'@payloadcms/richtext-lexical/client#ItalicFeatureClient': ItalicFeatureClient,
'@payloadcms/next/rsc#CollectionCards': CollectionCards,
'/components/PayloadInjects#Logo': Logo,
'/components/PayloadInjects#Icon': Icon,
'/components/PayloadInjects#Action': Action
};
+10
View File
@@ -0,0 +1,10 @@
import config from '@payload-config';
import { REST_DELETE, REST_GET, REST_OPTIONS, REST_PATCH, REST_POST, REST_PUT } from '@payloadcms/next/routes';
import '@payloadcms/next/css';
export const GET = REST_GET(config);
export const POST = REST_POST(config);
export const DELETE = REST_DELETE(config);
export const PATCH = REST_PATCH(config);
export const PUT = REST_PUT(config);
export const OPTIONS = REST_OPTIONS(config);
+35
View File
@@ -0,0 +1,35 @@
import { draftMode } from 'next/headers';
import { redirect } from 'next/navigation';
import { getPayload } from 'payload';
import config from '@payload-config';
export async function GET(request: Request) {
const { searchParams } = new URL(request.url);
const slug = searchParams.get('slug');
if (!slug)
return new Response('Missing slug', {
status: 400
});
const payload = await getPayload({
config
});
const { user } = await payload.auth({
headers: request.headers
});
if (!user)
return new Response('Unauthorized', {
status: 401
});
const draft = await draftMode();
draft.enable();
// redirect(`/${slug}`);
redirect('/');
}
@@ -0,0 +1,5 @@
import config from '@payload-config';
import { GRAPHQL_PLAYGROUND_GET } from '@payloadcms/next/routes';
import '@payloadcms/next/css';
export const GET = GRAPHQL_PLAYGROUND_GET(config);
+5
View File
@@ -0,0 +1,5 @@
import config from '@payload-config';
import { GRAPHQL_POST, REST_OPTIONS } from '@payloadcms/next/routes';
export const POST = GRAPHQL_POST(config);
export const OPTIONS = REST_OPTIONS(config);
+32
View File
@@ -0,0 +1,32 @@
import { type ReactNode } from 'react';
import config from '@payload-config';
import { type ServerFunctionClientArgs } from 'payload';
import { handleServerFunctions, RootLayout } from '@payloadcms/next/layouts';
import { importMap } from './admin/importMap';
import '@payloadcms/next/css';
type Props = Readonly<{
children: ReactNode;
}>;
async function serverFunction(args: ServerFunctionClientArgs) {
'use server';
return handleServerFunctions({
...args,
config,
importMap
});
}
export default function Layout({ children }: Props) {
return (
<RootLayout
config={config}
importMap={importMap}
serverFunction={serverFunction}
>
{children}
</RootLayout>
);
}
-5
View File
@@ -1,5 +0,0 @@
export default function Page() {
return (
<p>Works!</p>
);
}
+36
View File
@@ -0,0 +1,36 @@
import { type CollectionConfig } from 'payload';
export const Media: CollectionConfig = {
slug: 'media',
upload: {
staticDir: 'media'
},
labels: {
singular: 'Medium',
plural: 'Medien'
},
admin: {
useAsTitle: 'title',
group: 'Inhalte',
description: 'Artikel verwalten',
defaultColumns: [ 'id', 'title', 'alt' ],
hideAPIURL: true
},
fields: [
{
name: 'title',
label: 'Titel',
type: 'text',
required: true
},
{
name: 'alt',
label: 'Alt-Tag',
type: 'text'
}
]
};
+83
View File
@@ -0,0 +1,83 @@
import { type CollectionConfig } from 'payload';
import { lexicalEditor, UploadFeature } from '@payloadcms/richtext-lexical';
export const Posts: CollectionConfig = {
slug: 'posts',
labels: {
singular: 'Artikel',
plural: 'Artikel'
},
admin: {
useAsTitle: 'title',
group: 'Inhalte',
description: 'Artikel verwalten',
defaultColumns: [ 'id', 'slug', 'title', 'author', '_status' ],
hideAPIURL: true,
preview: doc =>
`${process.env.NEXT_PUBLIC_PAYLOAD_URL}/post/${doc.slug}/?preview`
},
versions: {
drafts: true
},
access: {
read: ({ data, req }) => (data?._status && data?._status !== 'draft') || [ 'admin', 'superadmin' ].includes(req.user?.role),
create: ({ req }) => [ 'admin', 'superadmin' ].includes(req.user?.role),
update: ({ req }) => [ 'admin', 'superadmin' ].includes(req.user?.role),
delete: ({ req }) => [ 'admin', 'superadmin' ].includes(req.user?.role)
},
orderable: true,
fields: [
{
name: 'title',
label: 'Titel',
type: 'text',
required: true
},
{
name: 'slug',
label: 'Slug',
type: 'text',
required: true,
unique: true,
admin: {
placeholder: 'Zum Beispiel: mein-toller-post'
}
},
{
name: 'content',
label: 'Inhalt',
type: 'richText',
editor: lexicalEditor({
features: ({ defaultFeatures }) => [
...defaultFeatures,
UploadFeature({
collections: {
media: {
fields: []
}
}
})
]
})
},
{
name: 'author',
label: 'Autor',
type: 'relationship',
relationTo: 'users',
filterOptions: () => {
return {
// eslint-disable-next-line camelcase
role: { not_equals: 'superadmin' }
};
}
}
]
};
+89
View File
@@ -0,0 +1,89 @@
import { type CollectionConfig, type AccessArgs } from 'payload';
function isSelfOrAdmin({ req }: AccessArgs) {
if (req.user?.role === 'superadmin')
return true;
if (req.user?.role === 'admin')
return {
id: {
equals: req.user?.id
}
};
return false;
}
export const Users: CollectionConfig = {
slug: 'users',
labels: {
singular: 'Benutzer',
plural: 'Benutzer'
},
admin: {
useAsTitle: 'fullName',
group: 'System',
description: 'Benutzer verwalten',
defaultColumns: [ 'id', 'fullName', 'email', 'role' ],
hideAPIURL: true
},
access: {
read: isSelfOrAdmin,
create: ({ req }) => req.user?.role === 'superadmin',
update: isSelfOrAdmin,
delete: ({ req }) => req.user?.role === 'superadmin'
},
auth: true,
fields: [
{
name: 'firstName',
label: 'Vorname',
type: 'text',
required: true
},
{
name: 'lastName',
label: 'Nachname',
type: 'text',
required: true
},
{
name: 'role',
label: 'Rolle',
type: 'select',
required: true,
access: {
update: ({ req }) => req.user?.role === 'superadmin'
},
options: [
{
label: 'Super-Admin (GSH)',
value: 'superadmin'
},
{
label: 'Administrator',
value: 'admin'
}
]
},
{
name: 'fullName',
label: 'Name',
type: 'text',
admin: {
hidden: true,
readOnly: true
},
hooks: {
beforeChange: [
({ data }) => `${data?.firstName} ${data?.lastName}`
]
}
}
]
};
+29
View File
@@ -0,0 +1,29 @@
'use client';
import { useRouter } from 'next/navigation';
export default function EndPreviewButton() {
const router = useRouter();
const endPreview = () => {
// Cookie entfernen
document.cookie = 'payload-preview=; Max-Age=0; Path=/';
// ?preview entfernen
const url = new URL(window.location.href);
url.searchParams.delete('preview');
// Neuladen
router.replace(url.pathname + url.search);
router.refresh();
};
return (
<button
className="fixed bottom-0 left-0 bg-red-500 rounded-md p-2 m-2 text-sm text-white cursor-pointer"
onClick={endPreview}
>
Preview beenden
</button>
);
}
+38
View File
@@ -0,0 +1,38 @@
'use client';
import Image from 'next/image';
import { Button } from '@payloadcms/ui';
export function Logo() {
return (
<Image
src="/logo.png"
width={7845}
height={956}
alt=""
/>
);
}
export function Icon() {
return (
<Image
src="/icon.png"
width={836}
height={956}
alt=""
/>
);
}
export function Action() {
function goToSite() {
window.location.href = window.location.origin;
}
return (
<Button onClick={goToSite}>
Zur Website wechseln
</Button>
);
}
+15
View File
@@ -0,0 +1,15 @@
'use client';
import { useRouter } from 'next/navigation';
import { RefreshRouteOnSave as PayloadLivePreview } from '@payloadcms/live-preview-react';
export default function RefreshRouteOnSave() {
const router = useRouter();
return (
<PayloadLivePreview
refresh={() => router.refresh()}
serverURL={process.env.NEXT_PUBLIC_PAYLOAD_URL ?? ''}
/>
);
}
+35
View File
@@ -0,0 +1,35 @@
import { cookies, headers } from 'next/headers';
import { getPayload, type Where } from 'payload';
import config from '@payload-config';
export async function getPreview() {
const cookieStore = await cookies();
let isPreview = cookieStore.get('payload-preview')?.value !== undefined;
if (isPreview) {
const payload = await getPayload({ config });
const headersList = await headers();
const { user } = await payload.auth({
headers: headersList
});
if (!user)
isPreview = false;
}
const where: Where = {};
if (!isPreview)
where._status = {
equals: 'published'
};
return {
isPreview,
where
};
};
+2 -1
View File
@@ -1,8 +1,9 @@
import { type NextConfig } from 'next';
import { withPayload } from '@payloadcms/next/withPayload';
const nextConfig: NextConfig = {
output: 'standalone',
poweredByHeader: false
};
export default nextConfig;
export default withPayload(nextConfig);
+6292 -209
View File
File diff suppressed because it is too large Load Diff
+8 -1
View File
@@ -9,9 +9,16 @@
"lint": "eslint"
},
"dependencies": {
"@payloadcms/db-postgres": "^3.88.0",
"@payloadcms/db-sqlite": "^3.88.0",
"@payloadcms/email-nodemailer": "^3.88.0",
"@payloadcms/live-preview-react": "^3.88.0",
"@payloadcms/next": "^3.88.0",
"@payloadcms/richtext-lexical": "^3.88.0",
"next": "16.2.10",
"react": "19.2.4",
"react-dom": "19.2.4"
"react-dom": "19.2.4",
"sharp": "^0.35.3"
},
"devDependencies": {
"@tailwindcss/postcss": "^4",
+83
View File
@@ -0,0 +1,83 @@
import sharp from 'sharp';
import { lexicalEditor } from '@payloadcms/richtext-lexical';
import { postgresAdapter } from '@payloadcms/db-postgres';
import { sqliteAdapter } from '@payloadcms/db-sqlite';
import { nodemailerAdapter } from '@payloadcms/email-nodemailer';
import { buildConfig } from 'payload';
import { de } from '@payloadcms/translations/languages/de';
import { Users } from '@/collections/Users';
import { Media } from '@/collections/Media';
import { Posts } from '@/collections/Posts';
export default buildConfig({
admin: {
user: 'users',
avatar: 'default',
meta: {
icons: [
{
rel: 'icon',
url: '/favicon.ico'
}
]
},
components: {
graphics: {
Logo: '/components/PayloadInjects#Logo',
Icon: '/components/PayloadInjects#Icon'
},
actions: [ '/components/PayloadInjects#Action' ]
},
livePreview: {
url: ({ collectionConfig, data }) => {
if (collectionConfig?.slug === 'posts')
return `${process.env.NEXT_PUBLIC_PAYLOAD_URL}/post/${data.slug}/?preview`;
return process.env.NEXT_PUBLIC_PAYLOAD_URL;
},
collections: [ 'posts' ]
}
},
i18n: {
supportedLanguages: { de }
},
editor: lexicalEditor(),
collections: [
Users,
Media,
Posts
],
secret: process.env.PAYLOAD_SECRET || '',
// db: postgresAdapter({
// pool: {
// connectionString: process.env.DATABASE_URL
// }
// }),
db: sqliteAdapter({
client: {
url: process.env.DATABASE_URL || '',
authToken: process.env.DATABASE_AUTH_TOKEN
}
}),
email: nodemailerAdapter({
defaultFromAddress: process.env.MAIL_USER ?? '',
defaultFromName: process.env.MAIL_DISPLAYNAME ?? '',
transportOptions: {
host: process.env.MAIL_HOST,
port: process.env.MAIL_PORT,
auth: {
user: process.env.MAIL_USER,
pass: process.env.MAIL_PASSWORD
}
}
}),
sharp
});
+18
View File
@@ -0,0 +1,18 @@
import { NextResponse, type NextRequest } from 'next/server';
export async function proxy(request: NextRequest) {
const isPreview = request.nextUrl.searchParams.get('preview') !== null;
if (!isPreview)
return NextResponse.next();
// ?preview entfernen
const url = request.nextUrl.clone();
url.searchParams.delete('preview');
const response = NextResponse.redirect(url);
// Cookie hinzufügen
response.cookies.set('payload-preview', '1');
return response;
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 275 KiB

+2 -1
View File
@@ -19,7 +19,8 @@
}
],
"paths": {
"@/*": ["./*"]
"@/*": ["./*"],
"@payload-config": ["./payload.config.ts"]
}
},
"include": [