feat: Adsterra integration, code splitting, cleanup, Onidel affiliate
This commit is contained in:
@@ -1,35 +1,48 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import React, { useEffect, useRef } from 'react';
|
||||
|
||||
const AdBlock = ({ className = '', adKey = 'e0ca7c61c83457f093bbc2e261b43d31' }) => {
|
||||
const iframeRef = useRef(null);
|
||||
|
||||
/**
|
||||
* AdBlock Component - Individual ad unit
|
||||
* Displays a single Google AdSense ad
|
||||
*/
|
||||
const AdBlock = ({ slot, size = '300x250', className = '' }) => {
|
||||
useEffect(() => {
|
||||
try {
|
||||
// Push ad to AdSense queue
|
||||
(window.adsbygoogle = window.adsbygoogle || []).push({});
|
||||
} catch (e) {
|
||||
console.error('AdSense error:', e);
|
||||
}
|
||||
}, []);
|
||||
if (!iframeRef.current) return;
|
||||
|
||||
const [width, height] = size.split('x');
|
||||
const iframe = iframeRef.current;
|
||||
const doc = iframe.contentDocument || iframe.contentWindow.document;
|
||||
|
||||
doc.open();
|
||||
doc.write(`
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<style>
|
||||
body { margin: 0; padding: 0; display: flex; justify-content: center; align-items: center; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<script type="text/javascript">
|
||||
atOptions = {
|
||||
'key' : '${adKey}',
|
||||
'format' : 'iframe',
|
||||
'height' : 250,
|
||||
'width' : 300,
|
||||
'params' : {}
|
||||
};
|
||||
</script>
|
||||
<script type="text/javascript" src="https://bustleplaguereed.com/${adKey}/invoke.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
`);
|
||||
doc.close();
|
||||
}, [adKey]);
|
||||
|
||||
return (
|
||||
<div className={`bg-gray-100 dark:bg-gray-800 rounded-lg overflow-hidden ${className}`}>
|
||||
<ins
|
||||
className="adsbygoogle"
|
||||
style={{
|
||||
display: 'block',
|
||||
width: `${width}px`,
|
||||
height: `${height}px`
|
||||
}}
|
||||
data-ad-client="ca-pub-8644544686212757"
|
||||
data-ad-slot={slot}
|
||||
data-ad-format="fixed"
|
||||
/>
|
||||
</div>
|
||||
<iframe
|
||||
ref={iframeRef}
|
||||
className={`bg-gray-100 dark:bg-gray-800 rounded-lg overflow-hidden ${className}`}
|
||||
style={{ width: '300px', height: '250px', border: 'none' }}
|
||||
title="Advertisement"
|
||||
sandbox="allow-scripts allow-same-origin"
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,29 +1,15 @@
|
||||
import React from 'react';
|
||||
import AdBlock from './AdBlock';
|
||||
import OfferBlock from './OfferBlock';
|
||||
import AffiliateBlock from './AffiliateBlock';
|
||||
|
||||
/**
|
||||
* AdColumn Component - Desktop sidebar with 3 ad units
|
||||
* Hidden on mobile/tablet, visible on desktop (1200px+)
|
||||
* All ads are 300x250 (Medium Rectangle) to comply with Google AdSense policies
|
||||
* - Ads must be fully viewable without scrolling
|
||||
* - No scrollable containers allowed
|
||||
*/
|
||||
const AdColumn = ({
|
||||
slot1 = 'REPLACE_WITH_SLOT_1',
|
||||
slot2 = 'REPLACE_WITH_SLOT_2',
|
||||
slot3 = 'REPLACE_WITH_SLOT_3'
|
||||
}) => {
|
||||
const AdColumn = () => {
|
||||
return (
|
||||
<aside className="hidden xl:block w-[300px] flex-shrink-0">
|
||||
<div className="fixed top-20 right-8 w-[300px] space-y-5">
|
||||
{/* Ad 1: Medium Rectangle */}
|
||||
<AdBlock slot={slot1} size="300x250" />
|
||||
|
||||
{/* Ad 2: Medium Rectangle */}
|
||||
<AdBlock slot={slot2} size="300x250" />
|
||||
|
||||
{/* Ad 3: Medium Rectangle */}
|
||||
<AdBlock slot={slot3} size="300x250" />
|
||||
<div className="sticky top-20 space-y-5">
|
||||
<AdBlock />
|
||||
<OfferBlock />
|
||||
<AffiliateBlock />
|
||||
</div>
|
||||
</aside>
|
||||
);
|
||||
|
||||
21
src/components/AffiliateBlock.js
Normal file
21
src/components/AffiliateBlock.js
Normal file
@@ -0,0 +1,21 @@
|
||||
import React from 'react';
|
||||
|
||||
const AffiliateBlock = () => {
|
||||
return (
|
||||
<a
|
||||
href="https://onidel.com/?referral=1568841"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer sponsored"
|
||||
className="block w-[300px] h-[250px] rounded-lg overflow-hidden shadow-md hover:shadow-xl transition-shadow duration-300"
|
||||
>
|
||||
<img
|
||||
src="/images/onidel-banner.webp"
|
||||
alt="Onidel - Professional Development Services"
|
||||
className="w-full h-full object-cover"
|
||||
loading="lazy"
|
||||
/>
|
||||
</a>
|
||||
);
|
||||
};
|
||||
|
||||
export default AffiliateBlock;
|
||||
18
src/components/Loading.js
Normal file
18
src/components/Loading.js
Normal file
@@ -0,0 +1,18 @@
|
||||
import React from 'react';
|
||||
import { Loader2 } from 'lucide-react';
|
||||
|
||||
const Loading = () => {
|
||||
return (
|
||||
<div className="flex flex-col items-center justify-center min-h-[50vh] w-full">
|
||||
<div className="relative">
|
||||
<div className="absolute inset-0 bg-blue-500/20 blur-xl rounded-full animate-pulse"></div>
|
||||
<Loader2 className="h-12 w-12 text-blue-600 dark:text-blue-400 animate-spin relative z-10" />
|
||||
</div>
|
||||
<p className="mt-4 text-slate-500 dark:text-slate-400 font-medium animate-pulse">
|
||||
Loading...
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Loading;
|
||||
@@ -1,17 +1,12 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import { X } from 'lucide-react';
|
||||
|
||||
/**
|
||||
* MobileAdBanner Component - Sticky bottom banner for mobile
|
||||
* Visible only on mobile/tablet, hidden on desktop
|
||||
* Includes close button for better UX
|
||||
*/
|
||||
const MobileAdBanner = ({ slot = 'REPLACE_WITH_MOBILE_SLOT' }) => {
|
||||
const MobileAdBanner = () => {
|
||||
const [visible, setVisible] = useState(true);
|
||||
const [closed, setClosed] = useState(false);
|
||||
const iframeRef = useRef(null);
|
||||
|
||||
useEffect(() => {
|
||||
// Check if user previously closed the banner (session storage)
|
||||
const wasClosed = sessionStorage.getItem('mobileAdClosed');
|
||||
if (wasClosed === 'true') {
|
||||
setClosed(true);
|
||||
@@ -20,13 +15,41 @@ const MobileAdBanner = ({ slot = 'REPLACE_WITH_MOBILE_SLOT' }) => {
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (visible && !closed) {
|
||||
try {
|
||||
(window.adsbygoogle = window.adsbygoogle || []).push({});
|
||||
} catch (e) {
|
||||
console.error('AdSense error:', e);
|
||||
}
|
||||
}
|
||||
if (!visible || closed || !iframeRef.current) return;
|
||||
|
||||
const timer = setTimeout(() => {
|
||||
if (!iframeRef.current) return;
|
||||
|
||||
const iframe = iframeRef.current;
|
||||
const doc = iframe.contentDocument || iframe.contentWindow.document;
|
||||
|
||||
doc.open();
|
||||
doc.write(`
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<style>
|
||||
body { margin: 0; padding: 0; display: flex; justify-content: center; align-items: center; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<script type="text/javascript">
|
||||
atOptions = {
|
||||
'key' : '2965bcf877388cafa84160592c550f5a',
|
||||
'format' : 'iframe',
|
||||
'height' : 50,
|
||||
'width' : 320,
|
||||
'params' : {}
|
||||
};
|
||||
</script>
|
||||
<script type="text/javascript" src="https://bustleplaguereed.com/2965bcf877388cafa84160592c550f5a/invoke.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
`);
|
||||
doc.close();
|
||||
}, 500);
|
||||
|
||||
return () => clearTimeout(timer);
|
||||
}, [visible, closed]);
|
||||
|
||||
const handleClose = () => {
|
||||
@@ -41,22 +64,17 @@ const MobileAdBanner = ({ slot = 'REPLACE_WITH_MOBILE_SLOT' }) => {
|
||||
<div className="xl:hidden fixed bottom-0 left-0 right-0 z-50 bg-white dark:bg-gray-900 shadow-lg border-t border-gray-200 dark:border-gray-700">
|
||||
<button
|
||||
onClick={handleClose}
|
||||
className="absolute top-1 right-1 p-1 text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200 bg-white dark:bg-gray-800 rounded-full shadow-sm"
|
||||
className="absolute -top-2 right-2 p-1 text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200 bg-white dark:bg-gray-800 rounded-full shadow-sm z-10"
|
||||
aria-label="Close ad"
|
||||
>
|
||||
<X className="h-4 w-4" />
|
||||
</button>
|
||||
<div className="flex justify-center py-2">
|
||||
<ins
|
||||
className="adsbygoogle"
|
||||
style={{
|
||||
display: 'inline-block',
|
||||
width: '320px',
|
||||
height: '50px'
|
||||
}}
|
||||
data-ad-client="ca-pub-8644544686212757"
|
||||
data-ad-slot={slot}
|
||||
data-ad-format="fixed"
|
||||
<div className="flex justify-center items-center py-2">
|
||||
<iframe
|
||||
ref={iframeRef}
|
||||
style={{ width: '320px', height: '50px', border: 'none' }}
|
||||
title="Mobile Advertisement"
|
||||
sandbox="allow-scripts allow-same-origin"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
22
src/components/OfferBlock.js
Normal file
22
src/components/OfferBlock.js
Normal file
@@ -0,0 +1,22 @@
|
||||
import React from 'react';
|
||||
|
||||
const OfferBlock = () => {
|
||||
return (
|
||||
<div className="w-[300px] h-[250px] bg-gradient-to-br from-indigo-500 to-purple-600 rounded-lg flex flex-col items-center justify-center text-white p-6 text-center shadow-lg hover:shadow-xl transition-shadow duration-300">
|
||||
<span className="bg-white/20 text-xs font-bold px-2 py-1 rounded mb-4 backdrop-blur-sm">
|
||||
SPECIAL OFFER
|
||||
</span>
|
||||
<h3 className="text-2xl font-bold mb-2">
|
||||
Upgrade to PRO
|
||||
</h3>
|
||||
<p className="text-indigo-100 text-sm mb-6">
|
||||
Get unlimited access to all developer tools and features.
|
||||
</p>
|
||||
<button className="bg-white text-indigo-600 font-bold py-2 px-6 rounded-full hover:bg-indigo-50 transition-colors shadow-md">
|
||||
Learn More
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default OfferBlock;
|
||||
Reference in New Issue
Block a user