diff --git a/Dockerfile b/Dockerfile
index b7b0891..3ade151 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -36,6 +36,7 @@ ENV NEXT_PUBLIC_APP_URL="https://www.qrmaster.net"
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
diff --git a/package-lock.json b/package-lock.json
index 0fb645d..1d7c0f3 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -46,6 +46,7 @@
"react-chartjs-2": "^5.2.0",
"react-dom": "^18.2.0",
"react-dropzone": "^14.2.3",
+ "react-facebook-pixel": "^1.0.4",
"react-i18next": "^13.5.0",
"react-simple-maps": "^3.0.0",
"resend": "^6.4.2",
@@ -10556,6 +10557,12 @@
"react": ">= 16.8 || 18.0.0"
}
},
+ "node_modules/react-facebook-pixel": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/react-facebook-pixel/-/react-facebook-pixel-1.0.4.tgz",
+ "integrity": "sha512-givZY8MS0v/mdbRzvcvouBo/j0TtDiu/93f4gIjJXwDDgwlf6bYUiQvb2qcqjluOOD/hIKUQHNYLNsSOnoEklg==",
+ "license": "MIT"
+ },
"node_modules/react-i18next": {
"version": "13.5.0",
"resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-13.5.0.tgz",
diff --git a/package.json b/package.json
index ad47aef..5e33ddf 100644
--- a/package.json
+++ b/package.json
@@ -65,6 +65,7 @@
"react-chartjs-2": "^5.2.0",
"react-dom": "^18.2.0",
"react-dropzone": "^14.2.3",
+ "react-facebook-pixel": "^1.0.4",
"react-i18next": "^13.5.0",
"react-simple-maps": "^3.0.0",
"resend": "^6.4.2",
diff --git a/src/app/(main)/(marketing)/layout.tsx b/src/app/(main)/(marketing)/layout.tsx
index afe84e7..037df38 100644
--- a/src/app/(main)/(marketing)/layout.tsx
+++ b/src/app/(main)/(marketing)/layout.tsx
@@ -1,9 +1,11 @@
import type { Metadata } from 'next';
+import AdBanner from '@/components/ads/AdBanner';
import '@/styles/globals.css';
import { Providers } from '@/components/Providers';
import MarketingLayout from './MarketingLayout';
// Import schema functions from library
import { organizationSchema, websiteSchema } from '@/lib/schema';
+import FacebookPixel from '@/components/analytics/FacebookPixel';
const isIndexable = process.env.NEXT_PUBLIC_INDEXABLE === 'true';
@@ -45,6 +47,11 @@ export const metadata: Metadata = {
],
locale: 'en_US',
},
+ verification: {
+ other: {
+ 'facebook-domain-verification': process.env.NEXT_PUBLIC_FACEBOOK_DOMAIN_VERIFICATION || '',
+ },
+ },
};
export default function MarketingGroupLayout({
@@ -61,6 +68,15 @@ export default function MarketingGroupLayout({
{children}
+ {/* Global Marketing Ad - Exclusions handled in AdBanner */}
+
>
);
}
diff --git a/src/app/(main)/(marketing)/tools/layout.tsx b/src/app/(main)/(marketing)/tools/layout.tsx
index 8141639..4abcf02 100644
--- a/src/app/(main)/(marketing)/tools/layout.tsx
+++ b/src/app/(main)/(marketing)/tools/layout.tsx
@@ -19,7 +19,7 @@ export default function ToolsLayout({
{/* AdBanner handles its own visibility - only shows when an ad is filled */}
+
+
+
{children}
diff --git a/src/app/(marketing-de)/layout.tsx b/src/app/(marketing-de)/layout.tsx
index cbc222d..3f43d1c 100644
--- a/src/app/(marketing-de)/layout.tsx
+++ b/src/app/(marketing-de)/layout.tsx
@@ -5,6 +5,7 @@ import { Providers } from '@/components/Providers';
import MarketingDeLayout from './MarketingDeLayout';
import { organizationSchema, websiteSchema } from '@/lib/schema';
import AdSenseScript from '@/components/ads/AdSenseScript';
+import FacebookPixel from '@/components/analytics/FacebookPixel';
export const metadata: Metadata = {
title: {
@@ -49,8 +50,15 @@ export const metadata: Metadata = {
'de': 'https://www.qrmaster.net/qr-code-erstellen',
},
},
+ verification: {
+ other: {
+ 'facebook-domain-verification': process.env.NEXT_PUBLIC_FACEBOOK_DOMAIN_VERIFICATION || '',
+ },
+ },
};
+import AdBanner from '@/components/ads/AdBanner'; // Import AdBanner
+
export default function MarketingDeGroupLayout({
children,
}: {
@@ -62,6 +70,7 @@ export default function MarketingDeGroupLayout({
+
{children}
+
+ {/* Global Marketing Ad - Exclusions handled in AdBanner */}
+
diff --git a/src/components/ads/AdBanner.tsx b/src/components/ads/AdBanner.tsx
index 9443b25..ee7e79d 100644
--- a/src/components/ads/AdBanner.tsx
+++ b/src/components/ads/AdBanner.tsx
@@ -3,6 +3,8 @@
import { useEffect, useState, useRef } from 'react';
import { useSession } from 'next-auth/react';
+import { usePathname } from 'next/navigation';
+
interface AdBannerProps {
dataAdSlot: string;
dataAdFormat?: string;
@@ -17,9 +19,30 @@ export default function AdBanner({
className = '',
}: AdBannerProps) {
const { data: session, status } = useSession();
+ const pathname = usePathname();
const adRef = useRef(null);
const [adFilled, setAdFilled] = useState(false);
+ // Paths where ads should NOT be shown
+ const excludedPaths = [
+ '/', // English Home
+ '/de', // German Home
+ '/qr-code-erstellen', // German Landing
+ '/contact', // Contact
+ '/about', // About
+ '/legal', // Legal pages
+ '/privacy',
+ '/terms',
+ '/cookie-policy',
+ '/impressum',
+ ];
+
+ // Check if current path matches strictly or starts with excluded path (for nested legal/blog pages if needed, though mostly exact matches here)
+ const shouldExclude = excludedPaths.some(path => {
+ if (path === '/') return pathname === '/';
+ return pathname === path || pathname?.startsWith(`${path}/`);
+ });
+
// Check if user has a paid plan
const isPaidUser = session?.user && (
session.user.plan === 'PRO' ||
@@ -27,6 +50,8 @@ export default function AdBanner({
session.user.plan === 'LIFETIME'
);
+ if (shouldExclude) return null;
+
useEffect(() => {
// Don't load if loading session or if user is paid
if (status === 'loading' || isPaidUser) return;
diff --git a/src/components/analytics/FacebookPixel.tsx b/src/components/analytics/FacebookPixel.tsx
new file mode 100644
index 0000000..872499f
--- /dev/null
+++ b/src/components/analytics/FacebookPixel.tsx
@@ -0,0 +1,38 @@
+'use client';
+
+import { useEffect, useState } from 'react';
+import { usePathname, useSearchParams } from 'next/navigation';
+
+export default function FacebookPixel() {
+ const [loaded, setLoaded] = useState(false);
+ const pathname = usePathname();
+ const searchParams = useSearchParams();
+
+ useEffect(() => {
+ // Only load if ID is present
+ const pixelId = process.env.NEXT_PUBLIC_FACEBOOK_PIXEL_ID;
+ if (!pixelId) return;
+
+ // Check consent
+ const cookieConsent = localStorage.getItem('cookieConsent');
+ if (cookieConsent !== 'accepted') return;
+
+ if (!loaded) {
+ import('react-facebook-pixel')
+ .then((x) => x.default)
+ .then((ReactPixel) => {
+ ReactPixel.init(pixelId);
+ ReactPixel.pageView();
+ setLoaded(true);
+ });
+ } else {
+ import('react-facebook-pixel')
+ .then((x) => x.default)
+ .then((ReactPixel) => {
+ ReactPixel.pageView();
+ });
+ }
+ }, [pathname, searchParams, loaded]);
+
+ return null;
+}