- src/seed.ts: idempotent seed script (admin, labels, SiteSettings, Navigation, TicketsConfig, homepage, sample blog/event) - src/app/(frontend)/loading.tsx: page skeleton loader with animate-pulse - next.config.ts: CSP + security headers (X-Frame-Options, Referrer-Policy, Permissions-Policy), localhost added to image remotePatterns - src/app/api/tickets/create/route.ts: rate limiting (10 req/min/IP) - src/app/api/tickets/webhook/route.ts: X-Webhook-Secret or ?secret query param verification - .env.example: EZY_WEBHOOK_SECRET, SEED_ADMIN_*, portal URLs Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
55 lines
2 KiB
TypeScript
55 lines
2 KiB
TypeScript
import { withPayload } from '@payloadcms/next/withPayload'
|
|
import type { NextConfig } from 'next'
|
|
|
|
const securityHeaders = [
|
|
{ key: 'X-Content-Type-Options', value: 'nosniff' },
|
|
{ key: 'X-Frame-Options', value: 'SAMEORIGIN' },
|
|
{ key: 'Referrer-Policy', value: 'strict-origin-when-cross-origin' },
|
|
{ key: 'Permissions-Policy', value: 'camera=(), microphone=(), geolocation=(self)' },
|
|
{
|
|
key: 'Content-Security-Policy',
|
|
value: [
|
|
"default-src 'self'",
|
|
// Scripts: self, GTM, GA4, Umami, inline eval for Next.js
|
|
"script-src 'self' 'unsafe-inline' 'unsafe-eval' https://www.googletagmanager.com https://www.google-analytics.com https://eu.umami.is https://cdn.binotel.com",
|
|
// Styles: self + inline (Tailwind/Next.js inject inline styles)
|
|
"style-src 'self' 'unsafe-inline' https://fonts.googleapis.com",
|
|
// Fonts
|
|
"font-src 'self' https://fonts.gstatic.com",
|
|
// Images: self + data URIs (QR codes) + our CDN
|
|
"img-src 'self' data: blob: https://shumiland.com.ua https://www.google-analytics.com",
|
|
// Connects: self + analytics + ezy + Binotel
|
|
"connect-src 'self' https://www.google-analytics.com https://analytics.google.com https://www.ezy.com.ua https://eu.umami.is https://api.binotel.com",
|
|
// Media: self
|
|
"media-src 'self' https://shumiland.com.ua",
|
|
// Frames: ezy payment page
|
|
"frame-src 'self' https://www.ezy.com.ua https://www.monobank.ua",
|
|
// Workers
|
|
"worker-src 'self' blob:",
|
|
].join('; '),
|
|
},
|
|
]
|
|
|
|
const nextConfig: NextConfig = {
|
|
output: 'standalone',
|
|
images: {
|
|
remotePatterns: [
|
|
{ protocol: 'https', hostname: 'shumiland.com.ua' },
|
|
{ protocol: 'http', hostname: 'localhost' },
|
|
],
|
|
},
|
|
experimental: {
|
|
reactCompiler: false,
|
|
},
|
|
async headers() {
|
|
return [
|
|
{
|
|
// Apply security headers to all routes except Payload admin (which needs its own)
|
|
source: '/((?!admin).*)',
|
|
headers: securityHeaders,
|
|
},
|
|
]
|
|
},
|
|
}
|
|
|
|
export default withPayload(nextConfig)
|