Merge branch 'master' of https://gitea.bizmatch.net/tknuth/QR-master
This commit is contained in:
commit
7b2788da7a
|
|
@ -36,6 +36,7 @@ ENV NEXT_PUBLIC_APP_URL="https://www.qrmaster.net"
|
||||||
ENV NEXT_PUBLIC_POSTHOG_KEY="phc_97JBJVVQlqqiZuTVRHuBnnG9HasOv3GSsdeVjossizJ"
|
ENV NEXT_PUBLIC_POSTHOG_KEY="phc_97JBJVVQlqqiZuTVRHuBnnG9HasOv3GSsdeVjossizJ"
|
||||||
ENV NEXT_PUBLIC_POSTHOG_HOST="https://us.i.posthog.com"
|
ENV NEXT_PUBLIC_POSTHOG_HOST="https://us.i.posthog.com"
|
||||||
ENV NEXT_PUBLIC_INDEXABLE="true"
|
ENV NEXT_PUBLIC_INDEXABLE="true"
|
||||||
|
ENV NEXT_PUBLIC_FACEBOOK_PIXEL_ID="1601718491252690"
|
||||||
RUN npx prisma generate
|
RUN npx prisma generate
|
||||||
RUN npm run build
|
RUN npm run build
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -46,6 +46,7 @@
|
||||||
"react-chartjs-2": "^5.2.0",
|
"react-chartjs-2": "^5.2.0",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
"react-dropzone": "^14.2.3",
|
"react-dropzone": "^14.2.3",
|
||||||
|
"react-facebook-pixel": "^1.0.4",
|
||||||
"react-i18next": "^13.5.0",
|
"react-i18next": "^13.5.0",
|
||||||
"react-simple-maps": "^3.0.0",
|
"react-simple-maps": "^3.0.0",
|
||||||
"resend": "^6.4.2",
|
"resend": "^6.4.2",
|
||||||
|
|
@ -10556,6 +10557,12 @@
|
||||||
"react": ">= 16.8 || 18.0.0"
|
"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": {
|
"node_modules/react-i18next": {
|
||||||
"version": "13.5.0",
|
"version": "13.5.0",
|
||||||
"resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-13.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-13.5.0.tgz",
|
||||||
|
|
|
||||||
|
|
@ -65,6 +65,7 @@
|
||||||
"react-chartjs-2": "^5.2.0",
|
"react-chartjs-2": "^5.2.0",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
"react-dropzone": "^14.2.3",
|
"react-dropzone": "^14.2.3",
|
||||||
|
"react-facebook-pixel": "^1.0.4",
|
||||||
"react-i18next": "^13.5.0",
|
"react-i18next": "^13.5.0",
|
||||||
"react-simple-maps": "^3.0.0",
|
"react-simple-maps": "^3.0.0",
|
||||||
"resend": "^6.4.2",
|
"resend": "^6.4.2",
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,11 @@
|
||||||
import type { Metadata } from 'next';
|
import type { Metadata } from 'next';
|
||||||
|
import AdBanner from '@/components/ads/AdBanner';
|
||||||
import '@/styles/globals.css';
|
import '@/styles/globals.css';
|
||||||
import { Providers } from '@/components/Providers';
|
import { Providers } from '@/components/Providers';
|
||||||
import MarketingLayout from './MarketingLayout';
|
import MarketingLayout from './MarketingLayout';
|
||||||
// Import schema functions from library
|
// Import schema functions from library
|
||||||
import { organizationSchema, websiteSchema } from '@/lib/schema';
|
import { organizationSchema, websiteSchema } from '@/lib/schema';
|
||||||
|
import FacebookPixel from '@/components/analytics/FacebookPixel';
|
||||||
|
|
||||||
const isIndexable = process.env.NEXT_PUBLIC_INDEXABLE === 'true';
|
const isIndexable = process.env.NEXT_PUBLIC_INDEXABLE === 'true';
|
||||||
|
|
||||||
|
|
@ -45,6 +47,11 @@ export const metadata: Metadata = {
|
||||||
],
|
],
|
||||||
locale: 'en_US',
|
locale: 'en_US',
|
||||||
},
|
},
|
||||||
|
verification: {
|
||||||
|
other: {
|
||||||
|
'facebook-domain-verification': process.env.NEXT_PUBLIC_FACEBOOK_DOMAIN_VERIFICATION || '',
|
||||||
|
},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function MarketingGroupLayout({
|
export default function MarketingGroupLayout({
|
||||||
|
|
@ -61,6 +68,15 @@ export default function MarketingGroupLayout({
|
||||||
<MarketingLayout>
|
<MarketingLayout>
|
||||||
{children}
|
{children}
|
||||||
</MarketingLayout>
|
</MarketingLayout>
|
||||||
|
{/* Global Marketing Ad - Exclusions handled in AdBanner */}
|
||||||
|
<div className="container mx-auto px-4 sm:px-6 lg:px-8 max-w-7xl pb-8">
|
||||||
|
<AdBanner
|
||||||
|
dataAdSlot="2607110637"
|
||||||
|
dataAdFormat="auto"
|
||||||
|
fullWidthResponsive={true}
|
||||||
|
className="bg-slate-50 rounded-xl p-4 border border-slate-100"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ export default function ToolsLayout({
|
||||||
{/* AdBanner handles its own visibility - only shows when an ad is filled */}
|
{/* AdBanner handles its own visibility - only shows when an ad is filled */}
|
||||||
<div className="container mx-auto px-4 sm:px-6 lg:px-8 max-w-7xl pb-8">
|
<div className="container mx-auto px-4 sm:px-6 lg:px-8 max-w-7xl pb-8">
|
||||||
<AdBanner
|
<AdBanner
|
||||||
dataAdSlot="1234567890" // Placeholder
|
dataAdSlot="2607110637" // Anzeige 1
|
||||||
dataAdFormat="auto"
|
dataAdFormat="auto"
|
||||||
fullWidthResponsive={true}
|
fullWidthResponsive={true}
|
||||||
className="bg-slate-50 rounded-xl p-4 border border-slate-100"
|
className="bg-slate-50 rounded-xl p-4 border border-slate-100"
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ import { Suspense } from 'react';
|
||||||
import '@/styles/globals.css';
|
import '@/styles/globals.css';
|
||||||
import { Providers } from '@/components/Providers';
|
import { Providers } from '@/components/Providers';
|
||||||
import AdSenseScript from '@/components/ads/AdSenseScript';
|
import AdSenseScript from '@/components/ads/AdSenseScript';
|
||||||
|
import FacebookPixel from '@/components/analytics/FacebookPixel';
|
||||||
|
|
||||||
const isIndexable = process.env.NEXT_PUBLIC_INDEXABLE === 'true';
|
const isIndexable = process.env.NEXT_PUBLIC_INDEXABLE === 'true';
|
||||||
|
|
||||||
|
|
@ -58,6 +59,9 @@ export default function RootLayout({
|
||||||
<Suspense fallback={null}>
|
<Suspense fallback={null}>
|
||||||
<Providers>
|
<Providers>
|
||||||
<AdSenseScript />
|
<AdSenseScript />
|
||||||
|
<Suspense fallback={null}>
|
||||||
|
<FacebookPixel />
|
||||||
|
</Suspense>
|
||||||
{children}
|
{children}
|
||||||
</Providers>
|
</Providers>
|
||||||
</Suspense>
|
</Suspense>
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import { Providers } from '@/components/Providers';
|
||||||
import MarketingDeLayout from './MarketingDeLayout';
|
import MarketingDeLayout from './MarketingDeLayout';
|
||||||
import { organizationSchema, websiteSchema } from '@/lib/schema';
|
import { organizationSchema, websiteSchema } from '@/lib/schema';
|
||||||
import AdSenseScript from '@/components/ads/AdSenseScript';
|
import AdSenseScript from '@/components/ads/AdSenseScript';
|
||||||
|
import FacebookPixel from '@/components/analytics/FacebookPixel';
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: {
|
title: {
|
||||||
|
|
@ -49,8 +50,15 @@ export const metadata: Metadata = {
|
||||||
'de': 'https://www.qrmaster.net/qr-code-erstellen',
|
'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({
|
export default function MarketingDeGroupLayout({
|
||||||
children,
|
children,
|
||||||
}: {
|
}: {
|
||||||
|
|
@ -62,6 +70,7 @@ export default function MarketingDeGroupLayout({
|
||||||
<Suspense fallback={null}>
|
<Suspense fallback={null}>
|
||||||
<Providers>
|
<Providers>
|
||||||
<AdSenseScript />
|
<AdSenseScript />
|
||||||
|
<FacebookPixel />
|
||||||
<script
|
<script
|
||||||
type="application/ld+json"
|
type="application/ld+json"
|
||||||
dangerouslySetInnerHTML={{ __html: JSON.stringify(organizationSchema()) }}
|
dangerouslySetInnerHTML={{ __html: JSON.stringify(organizationSchema()) }}
|
||||||
|
|
@ -72,6 +81,16 @@ export default function MarketingDeGroupLayout({
|
||||||
/>
|
/>
|
||||||
<MarketingDeLayout>
|
<MarketingDeLayout>
|
||||||
{children}
|
{children}
|
||||||
|
|
||||||
|
{/* Global Marketing Ad - Exclusions handled in AdBanner */}
|
||||||
|
<div className="container mx-auto px-4 sm:px-6 lg:px-8 max-w-7xl pb-8">
|
||||||
|
<AdBanner
|
||||||
|
dataAdSlot="2607110637"
|
||||||
|
dataAdFormat="auto"
|
||||||
|
fullWidthResponsive={true}
|
||||||
|
className="bg-slate-50 rounded-xl p-4 border border-slate-100"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</MarketingDeLayout>
|
</MarketingDeLayout>
|
||||||
</Providers>
|
</Providers>
|
||||||
</Suspense>
|
</Suspense>
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,8 @@
|
||||||
import { useEffect, useState, useRef } from 'react';
|
import { useEffect, useState, useRef } from 'react';
|
||||||
import { useSession } from 'next-auth/react';
|
import { useSession } from 'next-auth/react';
|
||||||
|
|
||||||
|
import { usePathname } from 'next/navigation';
|
||||||
|
|
||||||
interface AdBannerProps {
|
interface AdBannerProps {
|
||||||
dataAdSlot: string;
|
dataAdSlot: string;
|
||||||
dataAdFormat?: string;
|
dataAdFormat?: string;
|
||||||
|
|
@ -17,9 +19,30 @@ export default function AdBanner({
|
||||||
className = '',
|
className = '',
|
||||||
}: AdBannerProps) {
|
}: AdBannerProps) {
|
||||||
const { data: session, status } = useSession();
|
const { data: session, status } = useSession();
|
||||||
|
const pathname = usePathname();
|
||||||
const adRef = useRef<HTMLModElement>(null);
|
const adRef = useRef<HTMLModElement>(null);
|
||||||
const [adFilled, setAdFilled] = useState(false);
|
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
|
// Check if user has a paid plan
|
||||||
const isPaidUser = session?.user && (
|
const isPaidUser = session?.user && (
|
||||||
session.user.plan === 'PRO' ||
|
session.user.plan === 'PRO' ||
|
||||||
|
|
@ -27,6 +50,8 @@ export default function AdBanner({
|
||||||
session.user.plan === 'LIFETIME'
|
session.user.plan === 'LIFETIME'
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (shouldExclude) return null;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Don't load if loading session or if user is paid
|
// Don't load if loading session or if user is paid
|
||||||
if (status === 'loading' || isPaidUser) return;
|
if (status === 'loading' || isPaidUser) return;
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue