feat: Adsterra integration, code splitting, cleanup, Onidel affiliate

This commit is contained in:
dwindown
2026-02-18 16:50:45 +07:00
parent 7ba289be5c
commit 9dc3285adb
40 changed files with 352 additions and 2933 deletions

View File

@@ -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"
/>
);
};

View File

@@ -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>
);

View 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
View 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;

View File

@@ -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>

View 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;