From 4dc7c29134a69b277d58810609540388c3a4e2c6 Mon Sep 17 00:00:00 2001 From: Timo Knuth Date: Tue, 27 Jan 2026 12:29:44 +0100 Subject: [PATCH] press releases --- .eslintrc.json | 6 + package-lock.json | 16 +- package.json | 2 +- public/grid-pattern.svg | 3 + src/app/(main)/(marketing)/page.tsx | 15 +- src/app/(main)/(marketing)/press/page.tsx | 139 +++++++++++++++++ .../(main)/(marketing)/testimonials/page.tsx | 142 ++++++++++++++++++ src/app/sitemap.ts | 12 ++ src/components/ads/AdBanner.tsx | 4 +- .../marketing/AIComingSoonBanner.tsx | 2 +- src/components/marketing/HomePageClient.tsx | 6 + src/components/marketing/StatsStrip.tsx | 9 ++ src/components/marketing/Testimonials.tsx | 132 ++++++++++++++++ src/components/ui/Footer.tsx | 2 + src/lib/pillar-data.ts | 4 +- src/lib/schema.ts | 43 +++++- src/lib/testimonial-data.ts | 58 +++++++ src/lib/types.ts | 25 +++ src/middleware.ts | 2 + 19 files changed, 606 insertions(+), 16 deletions(-) create mode 100644 .eslintrc.json create mode 100644 public/grid-pattern.svg create mode 100644 src/app/(main)/(marketing)/press/page.tsx create mode 100644 src/app/(main)/(marketing)/testimonials/page.tsx create mode 100644 src/components/marketing/Testimonials.tsx create mode 100644 src/lib/testimonial-data.ts diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 0000000..e74f007 --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,6 @@ +{ + "extends": [ + "next/core-web-vitals", + "next/typescript" + ] +} diff --git a/package-lock.json b/package-lock.json index 1d7c0f3..8dc3fc1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -68,7 +68,7 @@ "autoprefixer": "^10.4.16", "cross-env": "^10.1.0", "eslint": "^8.56.0", - "eslint-config-next": "^16.1.1", + "eslint-config-next": "16.1.5", "postcss": "^8.4.32", "prettier": "^3.1.1", "prisma": "^5.7.0", @@ -2625,9 +2625,9 @@ "license": "MIT" }, "node_modules/@next/eslint-plugin-next": { - "version": "16.1.1", - "resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-16.1.1.tgz", - "integrity": "sha512-Ovb/6TuLKbE1UiPcg0p39Ke3puyTCIKN9hGbNItmpQsp+WX3qrjO3WaMVSi6JHr9X1NrmthqIguVHodMJbh/dw==", + "version": "16.1.5", + "resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-16.1.5.tgz", + "integrity": "sha512-gUWcEsOl+1W7XakmouClcJ0TNFCkblvDUho31wulbDY9na0C6mGtBTSXGRU5GXJY65GjGj0zNaCD/GaBp888Mg==", "dev": true, "license": "MIT", "dependencies": { @@ -6783,13 +6783,13 @@ } }, "node_modules/eslint-config-next": { - "version": "16.1.1", - "resolved": "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-16.1.1.tgz", - "integrity": "sha512-55nTpVWm3qeuxoQKLOjQVciKZJUphKrNM0fCcQHAIOGl6VFXgaqeMfv0aKJhs7QtcnlAPhNVqsqRfRjeKBPIUA==", + "version": "16.1.5", + "resolved": "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-16.1.5.tgz", + "integrity": "sha512-XwXyv65DC1HXI3gMxm13jvgx0IxKu6XhZhIWTfCDt4c45njHYUM2pk1Y8QXMAWMMnqPy94I2OLMmvIrNGcwLwQ==", "dev": true, "license": "MIT", "dependencies": { - "@next/eslint-plugin-next": "16.1.1", + "@next/eslint-plugin-next": "16.1.5", "eslint-import-resolver-node": "^0.3.6", "eslint-import-resolver-typescript": "^3.5.2", "eslint-plugin-import": "^2.32.0", diff --git a/package.json b/package.json index 5e33ddf..a3e53a8 100644 --- a/package.json +++ b/package.json @@ -87,7 +87,7 @@ "autoprefixer": "^10.4.16", "cross-env": "^10.1.0", "eslint": "^8.56.0", - "eslint-config-next": "^16.1.1", + "eslint-config-next": "16.1.5", "postcss": "^8.4.32", "prettier": "^3.1.1", "prisma": "^5.7.0", diff --git a/public/grid-pattern.svg b/public/grid-pattern.svg new file mode 100644 index 0000000..c30076a --- /dev/null +++ b/public/grid-pattern.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/app/(main)/(marketing)/page.tsx b/src/app/(main)/(marketing)/page.tsx index cecf7dc..1cc9a7c 100644 --- a/src/app/(main)/(marketing)/page.tsx +++ b/src/app/(main)/(marketing)/page.tsx @@ -1,7 +1,8 @@ import React from 'react'; import type { Metadata } from 'next'; import SeoJsonLd from '@/components/SeoJsonLd'; -import { organizationSchema, websiteSchema, softwareApplicationSchema } from '@/lib/schema'; +import { organizationSchema, websiteSchema, softwareApplicationSchema, reviewSchema, aggregateRatingSchema } from '@/lib/schema'; +import { getFeaturedTestimonials, getAggregateRating } from '@/lib/testimonial-data'; import HomePageClient from '@/components/marketing/HomePageClient'; function truncateAtWord(text: string, maxLength: number): string { @@ -52,9 +53,19 @@ export async function generateMetadata(): Promise { } export default function HomePage() { + const featuredTestimonials = getFeaturedTestimonials(); + const aggregateRating = getAggregateRating(); + const reviewSchemas = featuredTestimonials.map(t => reviewSchema(t)); + return ( <> - + {/* Server-rendered SEO content for crawlers */}
diff --git a/src/app/(main)/(marketing)/press/page.tsx b/src/app/(main)/(marketing)/press/page.tsx new file mode 100644 index 0000000..5a845bb --- /dev/null +++ b/src/app/(main)/(marketing)/press/page.tsx @@ -0,0 +1,139 @@ +import React from 'react'; +import { Metadata } from 'next'; +import Link from 'next/link'; +import { Button } from '@/components/ui/Button'; +import SeoJsonLd from '@/components/SeoJsonLd'; +import { organizationSchema, websiteSchema } from '@/lib/schema'; +import { ChevronRight, ExternalLink, Newspaper, Award, Calendar } from 'lucide-react'; + +export const metadata: Metadata = { + title: 'Press & News | QR Master', + description: 'Latest news, press releases, and updates from QR Master. Stay informed about new features, company announcements, and industry insights.', + keywords: ['qr master press', 'qr code generator news', 'company updates', 'press releases'], + openGraph: { + title: 'Press & News | QR Master', + description: 'Latest news, press releases, and updates from QR Master.', + url: 'https://www.qrmaster.net/press', + type: 'website', + } +}; + +export default function PressPage() { + const pressReleases = [ + { + id: "launch-2026", + title: "qrmaster.net Launches Free, Professional QR Code Generator for Global Users", + date: "January 27, 2026", + excerpt: "Duesseldorf-based startup unveils qrmaster.net, a comprehensive, free online QR code generator designed to serve as the ultimate bridge between the physical and digital worlds.", + bullets: [ + "Advanced tracking capabilities with UTM builder for GA4 integration", + "100% Free professional templates and design customization", + "Privacy-focused architecture with no data selling", + "Dynamic QR codes that can be edited after printing" + ], + link: "https://www.prlog.org/13123883-qrmasternet-launches-free-professional-qr-code-generator-for-global-users.html", + source: "PRLog" + } + ]; + + return ( + <> + + +
+ {/* Hero Section */} +
+
+
+
+

+ Newsroom +

+

+ Latest updates, press releases, and announcements from the QR Master team. +

+
+
+
+ + {/* Press Releases List */} +
+
+
+ +

Latest Releases

+
+ +
+ {pressReleases.map((pr) => ( +
+
+ +
+
+
+ + {pr.date} +
+ + Press Release + +
+ +

+ + + {pr.title} + +

+ +

+ {pr.excerpt} +

+ +
+

Highlights

+
    + {pr.bullets.map((bullet, idx) => ( +
  • + + {bullet} +
  • + ))} +
+
+ +
+
+ Source: + {pr.source} +
+
+ Read full release +
+
+
+
+ ))} +
+
+
+ + {/* Media Kit CTA (Future proofing) */} +
+
+

Media Inquiries

+

+ For press inquiries, assets, or interview requests from our leadership team. +

+ + + +
+
+
+ + ); +} diff --git a/src/app/(main)/(marketing)/testimonials/page.tsx b/src/app/(main)/(marketing)/testimonials/page.tsx new file mode 100644 index 0000000..6ea6fa9 --- /dev/null +++ b/src/app/(main)/(marketing)/testimonials/page.tsx @@ -0,0 +1,142 @@ +import React from 'react'; +import Link from 'next/link'; +import { Metadata } from 'next'; +import { Button } from '@/components/ui/Button'; +import SeoJsonLd from '@/components/SeoJsonLd'; +import { organizationSchema, reviewSchema, aggregateRatingSchema } from '@/lib/schema'; +import { testimonials, getAggregateRating } from '@/lib/testimonial-data'; +import { Testimonials } from '@/components/marketing/Testimonials'; +import { Star } from 'lucide-react'; + +function truncateAtWord(text: string, maxLength: number): string { + if (text.length <= maxLength) return text; + const truncated = text.slice(0, maxLength); + const lastSpace = truncated.lastIndexOf(' '); + return lastSpace > 0 ? truncated.slice(0, lastSpace) : truncated; +} + +export async function generateMetadata(): Promise { + const title = truncateAtWord('Customer Testimonials | QR Master Reviews', 60); + const description = truncateAtWord( + 'Read what our customers say about QR Master. Real reviews from businesses using dynamic QR codes for restaurants, pottery, retail, events, and more.', + 160 + ); + + return { + title, + description, + keywords: ['qr master reviews', 'qr code testimonials', 'customer reviews', 'qr code generator reviews', 'dynamic qr code reviews'], + alternates: { + canonical: 'https://www.qrmaster.net/testimonials', + }, + openGraph: { + title, + description, + url: 'https://www.qrmaster.net/testimonials', + type: 'website', + images: [ + { + url: 'https://www.qrmaster.net/og-image.png', + width: 1200, + height: 630, + alt: 'QR Master Customer Testimonials', + }, + ], + }, + twitter: { + title, + description, + }, + }; +} + +export default function TestimonialsPage() { + const aggregateRating = getAggregateRating(); + const reviewSchemas = testimonials.map(t => reviewSchema(t)); + + return ( + <> + + +
+ {/* Hero Section with Aggregate Rating */} +
+
+

+ Customer Testimonials +

+

+ Real experiences from businesses using QR Master to create dynamic QR codes +

+ + {/* Aggregate Rating Display */} +
+
+ {[...Array(5)].map((_, index) => ( + + ))} +
+

+ {aggregateRating.ratingValue} out of 5 stars +

+

+ Based on {aggregateRating.reviewCount} {aggregateRating.reviewCount === 1 ? 'review' : 'reviews'} +

+
+ +
+ + + +
+
+
+ + {/* Testimonials Grid */} + + + {/* CTA Section */} +
+
+

+ Ready to create your own QR codes? +

+

+ Join businesses using QR Master to create dynamic, trackable QR codes for their products, menus, events, and campaigns. +

+
+ + + + + + +
+
+
+
+ + ); +} diff --git a/src/app/sitemap.ts b/src/app/sitemap.ts index 9ea7a11..73c2c64 100644 --- a/src/app/sitemap.ts +++ b/src/app/sitemap.ts @@ -175,6 +175,18 @@ export default function sitemap(): MetadataRoute.Sitemap { changeFrequency: 'yearly', priority: 0.6, }, + { + url: `${baseUrl}/press`, + lastModified: new Date(), + changeFrequency: 'monthly', + priority: 0.7, + }, + { + url: `${baseUrl}/testimonials`, + lastModified: new Date(), + changeFrequency: 'monthly', + priority: 0.7, + }, ...toolPages, diff --git a/src/components/ads/AdBanner.tsx b/src/components/ads/AdBanner.tsx index ee7e79d..66e9421 100644 --- a/src/components/ads/AdBanner.tsx +++ b/src/components/ads/AdBanner.tsx @@ -50,7 +50,7 @@ export default function AdBanner({ session.user.plan === 'LIFETIME' ); - if (shouldExclude) return null; + useEffect(() => { // Don't load if loading session or if user is paid @@ -92,6 +92,8 @@ export default function AdBanner({ // Don't render anything while session is loading if (status === 'loading') return null; + if (shouldExclude) return null; + return (
{
- Coming Soon + The Future is Here
diff --git a/src/components/marketing/HomePageClient.tsx b/src/components/marketing/HomePageClient.tsx index 728f059..46c4e22 100644 --- a/src/components/marketing/HomePageClient.tsx +++ b/src/components/marketing/HomePageClient.tsx @@ -14,11 +14,14 @@ import { Button } from '@/components/ui/Button'; import { ReprintCalculatorTeaser } from '@/components/marketing/ReprintCalculatorTeaser'; import { ScrollToTop } from '@/components/ui/ScrollToTop'; import { FreeToolsGrid } from '@/components/marketing/FreeToolsGrid'; +import { Testimonials } from '@/components/marketing/Testimonials'; +import { getFeaturedTestimonials } from '@/lib/testimonial-data'; import en from '@/i18n/en.json'; export default function HomePageClient() { // Always use English for marketing pages const t = en; + const featuredTestimonials = getFeaturedTestimonials(); const industries = [ 'Restaurant Chain', @@ -41,6 +44,9 @@ export default function HomePageClient() { {/* Free Tools Grid */} + {/* Testimonials Section */} + + diff --git a/src/components/marketing/StatsStrip.tsx b/src/components/marketing/StatsStrip.tsx index e275589..e80b80e 100644 --- a/src/components/marketing/StatsStrip.tsx +++ b/src/components/marketing/StatsStrip.tsx @@ -29,6 +29,15 @@ export const StatsStrip: React.FC = ({ t }) => {
))}
+ + {/* Market Context - Citation / AEO Trust Signal */} +
+

+ Market Context: The global QR code payment market is projected to grow from $15.9B (2025) to $38B (2030) (Yahoo Finance). + + 94% of marketers increased their QR code usage in 2025 (Bitly). +

+
); diff --git a/src/components/marketing/Testimonials.tsx b/src/components/marketing/Testimonials.tsx new file mode 100644 index 0000000..e4e182d --- /dev/null +++ b/src/components/marketing/Testimonials.tsx @@ -0,0 +1,132 @@ +'use client'; + +import React from 'react'; +import { motion } from 'framer-motion'; +import { Star, CheckCircle } from 'lucide-react'; +import { Card, CardHeader, CardContent } from '@/components/ui/Card'; +import type { Testimonial } from '@/lib/types'; + +interface TestimonialsProps { + testimonials: Testimonial[]; + title?: string; + subtitle?: string; + showAll?: boolean; +} + +export const Testimonials: React.FC = ({ + testimonials, + title = "What Our Customers Say", + subtitle = "Real experiences from businesses using QR Master", + showAll = false +}) => { + const displayTestimonials = showAll ? testimonials : testimonials.slice(0, 3); + + const renderStars = (rating: number) => { + return ( +
+ {[...Array(5)].map((_, index) => ( + + ))} +
+ ); + }; + + return ( +
+
+ +

+ {title} +

+

+ {subtitle} +

+
+ +
+ {displayTestimonials.map((testimonial, index) => ( + + + +
+ {renderStars(testimonial.rating)} + {testimonial.verified && ( + + + Verified + + )} +
+

+ {testimonial.title} +

+
+ +

+ {testimonial.content} +

+
+
+ + {testimonial.author.name} + +
+ {testimonial.author.company && ( + {testimonial.author.company} + )} + {testimonial.author.company && testimonial.author.location && ( + + )} + {testimonial.author.location && ( + {testimonial.author.location} + )} +
+ + {testimonial.date} + +
+
+
+
+
+ ))} +
+ + + {!showAll && ( + + )} +
+
+ ); +}; diff --git a/src/components/ui/Footer.tsx b/src/components/ui/Footer.tsx index 0f53098..db97cd3 100644 --- a/src/components/ui/Footer.tsx +++ b/src/components/ui/Footer.tsx @@ -44,6 +44,8 @@ export function Footer({ variant = 'marketing', t }: FooterProps) {
  • {translations.features}
  • About
  • +
  • Press
  • +
  • Testimonials
  • Timo Knuth (Author)
  • {translations.pricing}
  • QR Analytics
  • diff --git a/src/lib/pillar-data.ts b/src/lib/pillar-data.ts index 9438cce..cce9d72 100644 --- a/src/lib/pillar-data.ts +++ b/src/lib/pillar-data.ts @@ -43,8 +43,8 @@ export const pillarMeta: PillarMeta[] = [ description: "Quishing prevention and safe QR rollouts.", quickAnswer: "Security is critical for trust. Learn how to prevent 'Quishing' (QR Phishing), validate links, and ensure your QR code campaigns remain safe for your users.", miniFaq: [ - { question: "What is Quishing?", answer: "Quishing (QR Phishing) tricks users into scanning malicious QR codes that steal credentials or install malware." }, - { question: "How to prevent QR code fraud?", answer: "Use short, branded links. Enable URL preview before redirect. Educate users to check the destination before scanning unknown codes." }, + { question: "What is Quishing?", answer: "Quishing (QR Phishing) tricks users into scanning malicious QR codes. (Source: FBI IC3 Warning Jan 2026)" }, + { question: "How to prevent QR code fraud?", answer: "Verify the source. Malicious QR campaigns rose significantly in 2025. (Source: Bitly Trends)" }, { question: "Are dynamic QR codes secure?", answer: "Yes, when hosted on trusted platforms with HTTPS, access logs, and link expiration. Avoid free generators with sketchy redirects." }, { question: "Can QR codes be hacked?", answer: "QR codes themselves can't be hacked, but attackers can overlay fake codes on legitimate ones. Use tamper-proof stickers and regular audits." } ], diff --git a/src/lib/schema.ts b/src/lib/schema.ts index 3e04e06..11a4782 100644 --- a/src/lib/schema.ts +++ b/src/lib/schema.ts @@ -1,4 +1,4 @@ -import type { BlogPost, AuthorProfile, PillarMeta } from "./types"; +import type { BlogPost, AuthorProfile, PillarMeta, Testimonial, AggregateRating } from "./types"; const SITE_URL = "https://www.qrmaster.net"; @@ -244,3 +244,44 @@ export function articleSchema(params: { url: params.url, }; } + +export function reviewSchema(testimonial: Testimonial) { + return { + '@context': 'https://schema.org', + '@type': 'Review', + itemReviewed: { + '@type': 'SoftwareApplication', + name: 'QR Master', + applicationCategory: 'BusinessApplication', + operatingSystem: 'Web Browser' + }, + reviewRating: { + '@type': 'Rating', + ratingValue: testimonial.rating, + bestRating: 5, + worstRating: 1 + }, + author: { + '@type': 'Person', + name: testimonial.author.name + }, + datePublished: testimonial.datePublished, + reviewBody: testimonial.content, + headline: testimonial.title + }; +} + +export function aggregateRatingSchema(aggregateRating: AggregateRating) { + return { + '@context': 'https://schema.org', + '@type': 'Product', + name: 'QR Master', + aggregateRating: { + '@type': 'AggregateRating', + ratingValue: aggregateRating.ratingValue, + reviewCount: aggregateRating.reviewCount, + bestRating: aggregateRating.bestRating, + worstRating: aggregateRating.worstRating + } + }; +} diff --git a/src/lib/testimonial-data.ts b/src/lib/testimonial-data.ts new file mode 100644 index 0000000..ad49338 --- /dev/null +++ b/src/lib/testimonial-data.ts @@ -0,0 +1,58 @@ +export type Testimonial = { + id: string; + rating: number; + title: string; + content: string; + author: { + name: string; + location?: string; + company?: string; + role?: string; + }; + date: string; + datePublished: string; + verified: boolean; + featured: boolean; + useCase?: string; +}; + +export type AggregateRating = { + ratingValue: number; + reviewCount: number; + bestRating: number; + worstRating: number; +}; + +export const testimonials: Testimonial[] = [ + { + id: "pottery-claudia-knuth-001", + rating: 5, + title: "Perfect for my pottery", + content: "I use QR-Master for my pottery as a link to my homepage and as a digital business card. I place the codes directly on my pottery pieces so interested customers can instantly access my website. Reliable and practical – a great solution!", + author: { + name: "Claudia Knuth", + company: "Hotshpotsh", + location: "Texas" + }, + date: "January 2026", + datePublished: "2026-01-15T00:00:00Z", + verified: true, + featured: true, + useCase: "pottery" + } +]; + +export function getAggregateRating(): AggregateRating { + const ratings = testimonials.map(t => t.rating); + const avgRating = ratings.reduce((a, b) => a + b, 0) / ratings.length; + return { + ratingValue: Number(avgRating.toFixed(1)), + reviewCount: testimonials.length, + bestRating: 5, + worstRating: 1 + }; +} + +export function getFeaturedTestimonials(): Testimonial[] { + return testimonials.filter(t => t.featured); +} diff --git a/src/lib/types.ts b/src/lib/types.ts index 84da903..23fc803 100644 --- a/src/lib/types.ts +++ b/src/lib/types.ts @@ -65,3 +65,28 @@ export type PillarMeta = { miniFaq?: FAQItem[]; order: number; }; + +export type Testimonial = { + id: string; + rating: number; + title: string; + content: string; + author: { + name: string; + location?: string; + company?: string; + role?: string; + }; + date: string; + datePublished: string; + verified: boolean; + featured: boolean; + useCase?: string; +}; + +export type AggregateRating = { + ratingValue: number; + reviewCount: number; + bestRating: number; + worstRating: number; +}; diff --git a/src/middleware.ts b/src/middleware.ts index d03a853..e4109d9 100644 --- a/src/middleware.ts +++ b/src/middleware.ts @@ -44,6 +44,8 @@ export function middleware(req: NextRequest) { '/about', '/learn', '/authors', + '/press', + '/testimonials', ]; // Check if path is public