chore: migrate to payload / nextjs
This commit is contained in:
68
src/app/(frontend)/(sitemaps)/pages-sitemap.xml/route.ts
Normal file
68
src/app/(frontend)/(sitemaps)/pages-sitemap.xml/route.ts
Normal file
@@ -0,0 +1,68 @@
|
||||
import { getServerSideSitemap } from 'next-sitemap'
|
||||
import { getPayload } from 'payload'
|
||||
import config from '@payload-config'
|
||||
import { unstable_cache } from 'next/cache'
|
||||
|
||||
const getPagesSitemap = unstable_cache(
|
||||
async () => {
|
||||
const payload = await getPayload({ config })
|
||||
const SITE_URL =
|
||||
process.env.NEXT_PUBLIC_SERVER_URL ||
|
||||
process.env.VERCEL_PROJECT_PRODUCTION_URL ||
|
||||
'https://example.com'
|
||||
|
||||
const results = await payload.find({
|
||||
collection: 'pages',
|
||||
overrideAccess: false,
|
||||
draft: false,
|
||||
depth: 0,
|
||||
limit: 1000,
|
||||
pagination: false,
|
||||
where: {
|
||||
_status: {
|
||||
equals: 'published',
|
||||
},
|
||||
},
|
||||
select: {
|
||||
slug: true,
|
||||
updatedAt: true,
|
||||
},
|
||||
})
|
||||
|
||||
const dateFallback = new Date().toISOString()
|
||||
|
||||
const defaultSitemap = [
|
||||
{
|
||||
loc: `${SITE_URL}/search`,
|
||||
lastmod: dateFallback,
|
||||
},
|
||||
{
|
||||
loc: `${SITE_URL}/posts`,
|
||||
lastmod: dateFallback,
|
||||
},
|
||||
]
|
||||
|
||||
const sitemap = results.docs
|
||||
? results.docs
|
||||
.filter((page) => Boolean(page?.slug))
|
||||
.map((page) => {
|
||||
return {
|
||||
loc: page?.slug === 'home' ? `${SITE_URL}/` : `${SITE_URL}/${page?.slug}`,
|
||||
lastmod: page.updatedAt || dateFallback,
|
||||
}
|
||||
})
|
||||
: []
|
||||
|
||||
return [...defaultSitemap, ...sitemap]
|
||||
},
|
||||
['pages-sitemap'],
|
||||
{
|
||||
tags: ['pages-sitemap'],
|
||||
},
|
||||
)
|
||||
|
||||
export async function GET() {
|
||||
const sitemap = await getPagesSitemap()
|
||||
|
||||
return getServerSideSitemap(sitemap)
|
||||
}
|
||||
55
src/app/(frontend)/(sitemaps)/posts-sitemap.xml/route.ts
Normal file
55
src/app/(frontend)/(sitemaps)/posts-sitemap.xml/route.ts
Normal file
@@ -0,0 +1,55 @@
|
||||
import { getServerSideSitemap } from 'next-sitemap'
|
||||
import { getPayload } from 'payload'
|
||||
import config from '@payload-config'
|
||||
import { unstable_cache } from 'next/cache'
|
||||
|
||||
const getPostsSitemap = unstable_cache(
|
||||
async () => {
|
||||
const payload = await getPayload({ config })
|
||||
const SITE_URL =
|
||||
process.env.NEXT_PUBLIC_SERVER_URL ||
|
||||
process.env.VERCEL_PROJECT_PRODUCTION_URL ||
|
||||
'https://example.com'
|
||||
|
||||
const results = await payload.find({
|
||||
collection: 'posts',
|
||||
overrideAccess: false,
|
||||
draft: false,
|
||||
depth: 0,
|
||||
limit: 1000,
|
||||
pagination: false,
|
||||
where: {
|
||||
_status: {
|
||||
equals: 'published',
|
||||
},
|
||||
},
|
||||
select: {
|
||||
slug: true,
|
||||
updatedAt: true,
|
||||
},
|
||||
})
|
||||
|
||||
const dateFallback = new Date().toISOString()
|
||||
|
||||
const sitemap = results.docs
|
||||
? results.docs
|
||||
.filter((post) => Boolean(post?.slug))
|
||||
.map((post) => ({
|
||||
loc: `${SITE_URL}/posts/${post?.slug}`,
|
||||
lastmod: post.updatedAt || dateFallback,
|
||||
}))
|
||||
: []
|
||||
|
||||
return sitemap
|
||||
},
|
||||
['posts-sitemap'],
|
||||
{
|
||||
tags: ['posts-sitemap'],
|
||||
},
|
||||
)
|
||||
|
||||
export async function GET() {
|
||||
const sitemap = await getPostsSitemap()
|
||||
|
||||
return getServerSideSitemap(sitemap)
|
||||
}
|
||||
12
src/app/(frontend)/[[...slug]]/page.tsx
Normal file
12
src/app/(frontend)/[[...slug]]/page.tsx
Normal file
@@ -0,0 +1,12 @@
|
||||
import NextApp from '@/spa/NextApp'
|
||||
|
||||
type Args = {
|
||||
params: Promise<{ slug?: string[] }>
|
||||
}
|
||||
|
||||
export default async function Page({ params }: Args) {
|
||||
const { slug = [] } = await params
|
||||
const pathname = `/${slug.join('/')}`.replace(/\/$/, '') || '/'
|
||||
|
||||
return <NextApp pathname={pathname} />
|
||||
}
|
||||
124
src/app/(frontend)/globals.css
Normal file
124
src/app/(frontend)/globals.css
Normal file
@@ -0,0 +1,124 @@
|
||||
@import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Sans:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;1,100;1,200;1,300;1,400;1,500;1,600;1,700&family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&display=swap');
|
||||
@import 'tailwindcss';
|
||||
|
||||
@theme {
|
||||
--font-display: "IBM Plex Sans", sans-serif;
|
||||
--font-body: "Inter", sans-serif;
|
||||
|
||||
--color-background: var(--background);
|
||||
--color-foreground: var(--foreground);
|
||||
--color-card: var(--card);
|
||||
--color-card-foreground: var(--card-foreground);
|
||||
--color-popover: var(--popover);
|
||||
--color-popover-foreground: var(--popover-foreground);
|
||||
--color-primary: var(--primary);
|
||||
--color-primary-foreground: var(--primary-foreground);
|
||||
--color-secondary: var(--secondary);
|
||||
--color-secondary-foreground: var(--secondary-foreground);
|
||||
--color-muted: var(--muted);
|
||||
--color-muted-foreground: var(--muted-foreground);
|
||||
--color-accent: var(--accent);
|
||||
--color-accent-foreground: var(--accent-foreground);
|
||||
--color-destructive: var(--destructive);
|
||||
--color-border: var(--border);
|
||||
--color-input: var(--input);
|
||||
--color-ring: var(--ring);
|
||||
|
||||
--radius-lg: 0.5rem;
|
||||
--radius-md: calc(0.5rem - 2px);
|
||||
--radius-sm: calc(0.5rem - 4px);
|
||||
}
|
||||
|
||||
:root {
|
||||
--background: #E4E2E3;
|
||||
--foreground: #020617;
|
||||
--card: #ffffff;
|
||||
--card-foreground: #020617;
|
||||
--popover: #ffffff;
|
||||
--popover-foreground: #020617;
|
||||
--primary: #03093A;
|
||||
--primary-foreground: #ffffff;
|
||||
--secondary: #f8fafc;
|
||||
--secondary-foreground: #03093A;
|
||||
--muted: #f1f5f9;
|
||||
--muted-foreground: #64748b;
|
||||
--accent: #EFBF04;
|
||||
--accent-foreground: #ffffff;
|
||||
--destructive: #ef4444;
|
||||
--border: #e2e8f0;
|
||||
--input: #e2e8f0;
|
||||
--ring: #03093A;
|
||||
}
|
||||
|
||||
.dark {
|
||||
--background: #020617;
|
||||
--foreground: #f8fafc;
|
||||
--card: #1e293b;
|
||||
--card-foreground: #f8fafc;
|
||||
--popover: #020617;
|
||||
--popover-foreground: #f8fafc;
|
||||
--primary: #03093A;
|
||||
--primary-foreground: #ffffff;
|
||||
--secondary: #1e293b;
|
||||
--secondary-foreground: #f8fafc;
|
||||
--muted: #1e293b;
|
||||
--muted-foreground: #94a3b8;
|
||||
--accent: #EFBF04;
|
||||
--accent-foreground: #ffffff;
|
||||
--destructive: #7f1d1d;
|
||||
--border: rgba(255, 255, 255, 0.1);
|
||||
--input: rgba(255, 255, 255, 0.15);
|
||||
--ring: #94a3b8;
|
||||
}
|
||||
|
||||
@layer base {
|
||||
* {
|
||||
@apply border-border outline-ring/50;
|
||||
}
|
||||
body {
|
||||
@apply bg-background text-foreground font-body;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
::selection {
|
||||
background: #EFBF04;
|
||||
color: #03093A;
|
||||
}
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
@apply font-display font-bold;
|
||||
}
|
||||
|
||||
blockquote, q,
|
||||
.quote, [class*="italic"] {
|
||||
font-family: "Inter", sans-serif;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
label,
|
||||
.label {
|
||||
font-family: "IBM Plex Sans", sans-serif;
|
||||
}
|
||||
|
||||
input, textarea, select, button {
|
||||
font-family: "Inter", sans-serif;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes fade-in {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(20px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
@layer utilities {
|
||||
.animate-fade-in {
|
||||
animation: fade-in 0.6s ease-out;
|
||||
}
|
||||
}
|
||||
21
src/app/(frontend)/layout.tsx
Normal file
21
src/app/(frontend)/layout.tsx
Normal file
@@ -0,0 +1,21 @@
|
||||
import type { Metadata } from 'next'
|
||||
import React from 'react'
|
||||
|
||||
import './globals.css'
|
||||
|
||||
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<html lang="de">
|
||||
<head>
|
||||
<link href="/favicon.ico" rel="icon" sizes="32x32" />
|
||||
<link href="/favicon.svg" rel="icon" type="image/svg+xml" />
|
||||
</head>
|
||||
<body>{children}</body>
|
||||
</html>
|
||||
)
|
||||
}
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: 'Bayerischer Mittelstandspreis 2026',
|
||||
description: 'Der Preis des Bayerischen Mittelstands',
|
||||
}
|
||||
7
src/app/(frontend)/next/exit-preview/route.ts
Normal file
7
src/app/(frontend)/next/exit-preview/route.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { draftMode } from 'next/headers'
|
||||
|
||||
export async function GET(): Promise<Response> {
|
||||
const draft = await draftMode()
|
||||
draft.disable()
|
||||
return new Response('Draft mode is disabled')
|
||||
}
|
||||
59
src/app/(frontend)/next/preview/route.ts
Normal file
59
src/app/(frontend)/next/preview/route.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
import type { PayloadRequest } from 'payload'
|
||||
import { getPayload } from 'payload'
|
||||
|
||||
import { draftMode } from 'next/headers'
|
||||
import { redirect } from 'next/navigation'
|
||||
import { NextRequest } from 'next/server'
|
||||
|
||||
import configPromise from '@payload-config'
|
||||
|
||||
export type PreviewSearchParams = {
|
||||
path: string
|
||||
previewSecret: string
|
||||
}
|
||||
|
||||
export async function GET(req: NextRequest): Promise<Response> {
|
||||
const payload = await getPayload({ config: configPromise })
|
||||
|
||||
const { searchParams } = new URL(req.url)
|
||||
|
||||
const path = searchParams.get('path')
|
||||
const previewSecret = searchParams.get('previewSecret')
|
||||
|
||||
if (previewSecret !== process.env.PREVIEW_SECRET) {
|
||||
return new Response('You are not allowed to preview this page', { status: 403 })
|
||||
}
|
||||
|
||||
if (!path) {
|
||||
return new Response('Insufficient search params', { status: 404 })
|
||||
}
|
||||
|
||||
if (!path.startsWith('/')) {
|
||||
return new Response('This endpoint can only be used for relative previews', { status: 500 })
|
||||
}
|
||||
|
||||
let user
|
||||
|
||||
try {
|
||||
user = await payload.auth({
|
||||
req: req as unknown as PayloadRequest,
|
||||
headers: req.headers,
|
||||
})
|
||||
} catch (error) {
|
||||
payload.logger.error({ err: error }, 'Error verifying token for live preview')
|
||||
return new Response('You are not allowed to preview this page', { status: 403 })
|
||||
}
|
||||
|
||||
const draft = await draftMode()
|
||||
|
||||
if (!user) {
|
||||
draft.disable()
|
||||
return new Response('You are not allowed to preview this page', { status: 403 })
|
||||
}
|
||||
|
||||
// You can add additional checks here to see if the user is allowed to preview this page
|
||||
|
||||
draft.enable()
|
||||
|
||||
redirect(path)
|
||||
}
|
||||
31
src/app/(frontend)/next/seed/route.ts
Normal file
31
src/app/(frontend)/next/seed/route.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import { createLocalReq, getPayload } from 'payload'
|
||||
import { seed } from '@/endpoints/seed'
|
||||
import config from '@payload-config'
|
||||
import { headers } from 'next/headers'
|
||||
|
||||
export const maxDuration = 60 // This function can run for a maximum of 60 seconds
|
||||
|
||||
export async function POST(): Promise<Response> {
|
||||
const payload = await getPayload({ config })
|
||||
const requestHeaders = await headers()
|
||||
|
||||
// Authenticate by passing request headers
|
||||
const { user } = await payload.auth({ headers: requestHeaders })
|
||||
|
||||
if (!user) {
|
||||
return new Response('Action forbidden.', { status: 403 })
|
||||
}
|
||||
|
||||
try {
|
||||
// Create a Payload request object to pass to the Local API for transactions
|
||||
// At this point you should pass in a user, locale, and any other context you need for the Local API
|
||||
const payloadReq = await createLocalReq({ user }, payload)
|
||||
|
||||
await seed({ payload, req: payloadReq })
|
||||
|
||||
return Response.json({ success: true })
|
||||
} catch (e) {
|
||||
payload.logger.error({ err: e, message: 'Error seeding data' })
|
||||
return new Response('Error seeding data.', { status: 500 })
|
||||
}
|
||||
}
|
||||
18
src/app/(frontend)/not-found.tsx
Normal file
18
src/app/(frontend)/not-found.tsx
Normal file
@@ -0,0 +1,18 @@
|
||||
import Link from 'next/link'
|
||||
import React from 'react'
|
||||
|
||||
import { Button } from '@/components/ui/button'
|
||||
|
||||
export default function NotFound() {
|
||||
return (
|
||||
<div className="container py-28">
|
||||
<div className="prose max-w-none">
|
||||
<h1 style={{ marginBottom: 0 }}>404</h1>
|
||||
<p className="mb-4">This page could not be found.</p>
|
||||
</div>
|
||||
<Button asChild variant="default">
|
||||
<Link href="/">Go home</Link>
|
||||
</Button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user