From 76a76258e8ad806cb4b0189af970a4a91d0bcdd8 Mon Sep 17 00:00:00 2001 From: Timo Knuth Date: Mon, 26 Jan 2026 19:51:00 +0100 Subject: [PATCH] fixes --- Dockerfile | 126 ++--- public/bb6dfaacf1ed41a880281c426c54ed7c.txt | 2 +- scripts/validate-data.ts | 41 ++ .../(marketing)/authors/[slug]/page.tsx | 2 +- .../(marketing)/learn/[pillar]/page.tsx | 2 +- src/app/(main)/(marketing)/learn/page.tsx | 8 +- .../whatsapp-qr-code/WhatsAppGeneratorDE.tsx | 530 +++++++++--------- 7 files changed, 378 insertions(+), 333 deletions(-) create mode 100644 scripts/validate-data.ts diff --git a/Dockerfile b/Dockerfile index 3ade151..89f9a89 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,64 +1,64 @@ -# ---- deps ---- -FROM node:20-alpine AS deps -# Install OpenSSL for Prisma -RUN apk add --no-cache openssl -WORKDIR /app -COPY package.json package-lock.json* pnpm-lock.yaml* yarn.lock* .npmrc* ./ -# Copy prisma schema for postinstall script -COPY prisma ./prisma -RUN \ - if [ -f pnpm-lock.yaml ]; then \ - npm i -g pnpm && pnpm i --frozen-lockfile; \ - elif [ -f yarn.lock ]; then \ - yarn --frozen-lockfile; \ - elif [ -f package-lock.json ]; then \ - npm ci; \ - else \ - npm install --legacy-peer-deps; \ - fi - -# ---- builder ---- -FROM node:20-alpine AS builder -# Install OpenSSL for Prisma -RUN apk add --no-cache openssl -WORKDIR /app -COPY --from=deps /app/node_modules ./node_modules -COPY . . -ENV NEXT_TELEMETRY_DISABLED=1 -# Add build-time environment variables with defaults -ENV NEXTAUTH_URL="https://www.qrmaster.net" -ENV NEXTAUTH_SECRET="build-time-secret" -ENV IP_SALT="build-time-salt" -ENV STRIPE_SECRET_KEY="sk_test_placeholder_for_build" -ENV RESEND_API_KEY="re_placeholder_for_build" -ENV NEXT_PUBLIC_APP_URL="https://www.qrmaster.net" -# PostHog Analytics - REQUIRED at build time for client-side bundle -ENV NEXT_PUBLIC_POSTHOG_KEY="phc_97JBJVVQlqqiZuTVRHuBnnG9HasOv3GSsdeVjossizJ" -ENV NEXT_PUBLIC_POSTHOG_HOST="https://us.i.posthog.com" -ENV NEXT_PUBLIC_INDEXABLE="true" -ENV NEXT_PUBLIC_FACEBOOK_PIXEL_ID="1601718491252690" -RUN npx prisma generate -RUN npm run build - -# ---- runner ---- -FROM node:20-alpine AS runner -# Install OpenSSL for Prisma runtime -RUN apk add --no-cache openssl -WORKDIR /app -ENV NODE_ENV=production -ENV NEXT_TELEMETRY_DISABLED=1 - -RUN addgroup --system --gid 1001 nodejs -RUN adduser --system --uid 1001 nextjs - -COPY --from=builder /app/node_modules ./node_modules -COPY --from=builder /app/prisma ./prisma -COPY --from=builder /app/.next/standalone ./ -COPY --from=builder /app/.next/static ./.next/static -COPY --from=builder /app/public ./public - -USER nextjs - -EXPOSE 3000 - +# ---- deps ---- +FROM node:20-alpine AS deps +# Install OpenSSL for Prisma +RUN apk add --no-cache openssl +WORKDIR /app +COPY package.json package-lock.json* pnpm-lock.yaml* yarn.lock* .npmrc* ./ +# Copy prisma schema for postinstall script +COPY prisma ./prisma +RUN \ + if [ -f pnpm-lock.yaml ]; then \ + npm i -g pnpm && pnpm i --frozen-lockfile; \ + elif [ -f yarn.lock ]; then \ + yarn --frozen-lockfile; \ + elif [ -f package-lock.json ]; then \ + npm ci; \ + else \ + npm install --legacy-peer-deps; \ + fi + +# ---- builder ---- +FROM node:20-alpine AS builder +# Install OpenSSL for Prisma +RUN apk add --no-cache openssl +WORKDIR /app +COPY --from=deps /app/node_modules ./node_modules +COPY . . +ENV NEXT_TELEMETRY_DISABLED=1 +# Add build-time environment variables with defaults +ENV NEXTAUTH_URL="https://www.qrmaster.net" +ENV NEXTAUTH_SECRET="build-time-secret" +ENV IP_SALT="build-time-salt" +ENV STRIPE_SECRET_KEY="sk_test_placeholder_for_build" +ENV RESEND_API_KEY="re_placeholder_for_build" +ENV NEXT_PUBLIC_APP_URL="https://www.qrmaster.net" +# PostHog Analytics - REQUIRED at build time for client-side bundle +ENV NEXT_PUBLIC_POSTHOG_KEY="phc_97JBJVVQlqqiZuTVRHuBnnG9HasOv3GSsdeVjossizJ" +ENV NEXT_PUBLIC_POSTHOG_HOST="https://us.i.posthog.com" +ENV NEXT_PUBLIC_INDEXABLE="true" +ENV NEXT_PUBLIC_FACEBOOK_PIXEL_ID="1601718491252690" +RUN npx prisma generate +RUN npm run build + +# ---- runner ---- +FROM node:20-alpine AS runner +# Install OpenSSL for Prisma runtime +RUN apk add --no-cache openssl +WORKDIR /app +ENV NODE_ENV=production +ENV NEXT_TELEMETRY_DISABLED=1 + +RUN addgroup --system --gid 1001 nodejs +RUN adduser --system --uid 1001 nextjs + +COPY --from=builder /app/node_modules ./node_modules +COPY --from=builder /app/prisma ./prisma +COPY --from=builder /app/.next/standalone ./ +COPY --from=builder /app/.next/static ./.next/static +COPY --from=builder /app/public ./public + +USER nextjs + +EXPOSE 3000 + CMD ["node", "server.js"] \ No newline at end of file diff --git a/public/bb6dfaacf1ed41a880281c426c54ed7c.txt b/public/bb6dfaacf1ed41a880281c426c54ed7c.txt index f202b25..44c1146 100644 --- a/public/bb6dfaacf1ed41a880281c426c54ed7c.txt +++ b/public/bb6dfaacf1ed41a880281c426c54ed7c.txt @@ -1 +1 @@ -bb6dfaacf1ed41a880281c426c54ed7c +bb6dfaacf1ed41a880281c426c54ed7c diff --git a/scripts/validate-data.ts b/scripts/validate-data.ts new file mode 100644 index 0000000..b9498d2 --- /dev/null +++ b/scripts/validate-data.ts @@ -0,0 +1,41 @@ +import { blogPosts } from '../src/lib/blog-data'; +import { authors } from '../src/lib/author-data'; +import { getPublishedPosts, getPostsByAuthor } from '../src/lib/content'; + +console.log("Validating Author Data..."); +authors.forEach(author => { + console.log(`Checking author: ${author.slug}`); + if (!author.name) console.error(`Error: Author ${author.slug} missing name`); + if (!author.image) console.warn(`Warning: Author ${author.slug} missing image`); +}); + +console.log("\nValidating Blog Data..."); +blogPosts.forEach(post => { + try { + if (!post.slug) console.error("Error: Post missing slug", post); + if (!post.datePublished && !post.date) console.error(`Error: Post ${post.slug} missing date`); + + const d = new Date(post.datePublished || post.date); + if (isNaN(d.getTime())) { + console.error(`Error: Post ${post.slug} has invalid date: ${post.datePublished || post.date}`); + } + } catch (e) { + console.error(`Exception checking post ${post.slug || 'unknown'}:`, e); + } +}); + +console.log("\nTesting Content Functions..."); +try { + const published = getPublishedPosts(); + console.log(`getPublishedPosts returned ${published.length} posts.`); + + authors.forEach(author => { + const posts = getPostsByAuthor(author.slug); + console.log(`Author ${author.slug} has ${posts.length} posts.`); + }); + +} catch (e) { + console.error("Error running content functions:", e); +} + +console.log("\nValidation Complete."); diff --git a/src/app/(main)/(marketing)/authors/[slug]/page.tsx b/src/app/(main)/(marketing)/authors/[slug]/page.tsx index ade63f7..773f9eb 100644 --- a/src/app/(main)/(marketing)/authors/[slug]/page.tsx +++ b/src/app/(main)/(marketing)/authors/[slug]/page.tsx @@ -81,7 +81,7 @@ export default function AuthorPage({ params }: { params: { slug: string } }) {
{posts.map(p => ( -
{p.date}
+
{new Date(p.datePublished || p.date).toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' })}

{p.title}

{p.description}

diff --git a/src/app/(main)/(marketing)/learn/[pillar]/page.tsx b/src/app/(main)/(marketing)/learn/[pillar]/page.tsx index 891672e..ae581c4 100644 --- a/src/app/(main)/(marketing)/learn/[pillar]/page.tsx +++ b/src/app/(main)/(marketing)/learn/[pillar]/page.tsx @@ -55,7 +55,7 @@ export default function PillarPage({ params }: { params: { pillar: PillarKey } }
{posts.map(p => ( -
{p.date}
+
{new Date(p.datePublished || p.date).toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' })}
{p.title}
{p.description}
diff --git a/src/app/(main)/(marketing)/learn/page.tsx b/src/app/(main)/(marketing)/learn/page.tsx index ed2664d..afd7eb0 100644 --- a/src/app/(main)/(marketing)/learn/page.tsx +++ b/src/app/(main)/(marketing)/learn/page.tsx @@ -10,7 +10,11 @@ export const metadata = { export default function LearnHubPage() { const posts = getPublishedPosts(); // Sort by date descending - const topLatest = [...posts].sort((a, b) => (new Date(a.datePublished).getTime() < new Date(b.datePublished).getTime() ? 1 : -1)).slice(0, 6); + const topLatest = [...posts].sort((a, b) => { + const dateA = a.datePublished ? new Date(a.datePublished) : new Date(a.date); + const dateB = b.datePublished ? new Date(b.datePublished) : new Date(b.date); + return dateB.getTime() - dateA.getTime(); + }).slice(0, 6); return (
@@ -41,7 +45,7 @@ export default function LearnHubPage() {
{p.pillar?.toUpperCase() || 'GUIDE'}
-
{p.date}
+
{new Date(p.datePublished || p.date).toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' })}
{p.title}
{p.description}
diff --git a/src/app/(marketing-de)/whatsapp-qr-code/WhatsAppGeneratorDE.tsx b/src/app/(marketing-de)/whatsapp-qr-code/WhatsAppGeneratorDE.tsx index e9cf1c1..9a91f79 100644 --- a/src/app/(marketing-de)/whatsapp-qr-code/WhatsAppGeneratorDE.tsx +++ b/src/app/(marketing-de)/whatsapp-qr-code/WhatsAppGeneratorDE.tsx @@ -1,265 +1,265 @@ -'use client'; - -import React, { useState, useRef } from 'react'; -import Link from 'next/link'; -import { QRCodeSVG } from 'qrcode.react'; -import { - Phone, - Download, - Check, - Sparkles, - MessageCircle, - Send -} from 'lucide-react'; -import { Button } from '@/components/ui/Button'; -import { Input } from '@/components/ui/Input'; -import { cn } from '@/lib/utils'; -import { Textarea } from '@/components/ui/Textarea'; - -// Brand Colors -const BRAND = { - paleGrey: '#EBEBDF', - richBlue: '#1A1265', - richBlueLight: '#2A2275', -}; - -// QR Color Options - WhatsApp Theme -const QR_COLORS = [ - { name: 'WhatsApp Grün', value: '#25D366' }, - { name: 'Teal', value: '#128C7E' }, - { name: 'Klassisches Schwarz', value: '#000000' }, - { name: 'Sattes Blau', value: '#1A1265' }, - { name: 'Lila', value: '#7C3AED' }, - { name: 'Smaragd', value: '#10B981' }, - { name: 'Rose', value: '#F43F5E' }, -]; - -// Frame Options -const FRAME_OPTIONS = [ - { id: 'none', label: 'Kein Rahmen' }, - { id: 'scanme', label: 'Scan Mich' }, - { id: 'chat', label: 'Chat starten' }, - { id: 'support', label: 'Support' }, -]; - -export default function WhatsAppGeneratorDE() { - const [phone, setPhone] = useState(''); - const [message, setMessage] = useState(''); - const [qrColor, setQrColor] = useState('#25D366'); - const [frameType, setFrameType] = useState('none'); - - const qrRef = useRef(null); - - // WhatsApp URL: https://wa.me/number?text=message - const getUrl = () => { - const cleanPhone = phone.replace(/\D/g, ''); // Remove non-digits - const encodedMessage = encodeURIComponent(message); - return `https://wa.me/${cleanPhone}?text=${encodedMessage}`; - }; - - const handleDownload = async (format: 'png' | 'svg') => { - if (!qrRef.current) return; - try { - if (format === 'png') { - const { toPng } = await import('html-to-image'); - const dataUrl = await toPng(qrRef.current, { cacheBust: true, pixelRatio: 3 }); - const link = document.createElement('a'); - link.download = `whatsapp-qr-code.png`; - link.href = dataUrl; - link.click(); - } else { - const svgData = qrRef.current.querySelector('svg')?.outerHTML; - if (svgData) { - const blob = new Blob([svgData], { type: 'image/svg+xml;charset=utf-8' }); - const url = URL.createObjectURL(blob); - const link = document.createElement('a'); - link.href = url; - link.download = `whatsapp-qr-code.svg`; - link.click(); - } - } - } catch (err) { - console.error('Download failed', err); - } - }; - - const getFrameLabel = () => { - const frame = FRAME_OPTIONS.find(f => f.id === frameType); - return frame?.id !== 'none' ? frame?.label : null; - }; - - return ( -
- - {/* Main Generator Card */} -
-
- - {/* LEFT: Input Section */} -
- - {/* WhatsApp Details */} -
-

- - WhatsApp Details -

- -
- - setPhone(e.target.value)} - className="h-12 text-base rounded-xl border-slate-200 focus:border-[#25D366] focus:ring-[#25D366]" - /> -

Mit Ländervorwahl (z.B. 49 für DE). Kein '+' Symbol.

-
- -
- -