From 17df4d0ad7a71fd0c9e7bfca14a5ba4dc9ba730e Mon Sep 17 00:00:00 2001 From: test Date: Sun, 20 Sep 2026 14:49:34 +0200 Subject: [PATCH] Keep AdSense rendering limited to anonymous visitors. Show guest-only ad units on public pages and skip the AdSense script entirely for signed-in users. --- resources/js/Pages/ArtworkPage.jsx | 5 +++ resources/js/components/ads/AdSenseUnit.jsx | 45 +++++++++++++++++++ .../js/components/ads/AdSenseUnit.test.jsx | 43 ++++++++++++++++++ .../components/artwork/ArtworkActionBar.jsx | 9 ++-- resources/views/components/ad-unit.blade.php | 3 +- resources/views/layouts/nova.blade.php | 8 +++- resources/views/web/home.blade.php | 14 +++++- tests/Feature/HomePageTest.php | 16 +++++++ 8 files changed, 135 insertions(+), 8 deletions(-) create mode 100644 resources/js/components/ads/AdSenseUnit.jsx create mode 100644 resources/js/components/ads/AdSenseUnit.test.jsx diff --git a/resources/js/Pages/ArtworkPage.jsx b/resources/js/Pages/ArtworkPage.jsx index 9e3e5fa1..b66a8426 100644 --- a/resources/js/Pages/ArtworkPage.jsx +++ b/resources/js/Pages/ArtworkPage.jsx @@ -9,6 +9,7 @@ import ArtworkTags from '../components/artwork/ArtworkTags' import ArtworkDescription from '../components/artwork/ArtworkDescription' import ArtworkEvolutionPanel from '../components/artwork/ArtworkEvolutionPanel' import ArtworkComments from '../components/artwork/ArtworkComments' +import AdSenseUnit from '../components/ads/AdSenseUnit' import ArtworkActionBar from '../components/artwork/ArtworkActionBar' import ArtworkDetailsPanel from '../components/artwork/ArtworkDetailsPanel' import CreatorSpotlight from '../components/artwork/CreatorSpotlight' @@ -319,6 +320,10 @@ function publisherToGroupSummary(publisher) { {/* Tags & categories */} + {!isAuthenticated ? ( + + ) : null} + {/* Comments */} { + if (!enabled || !slot || pushed.current) { + return undefined + } + + pushed.current = true + + try { + window.adsbygoogle = window.adsbygoogle || [] + window.adsbygoogle.push({}) + } catch { + // AdSense may be blocked or not yet loaded (consent not granted). + } + + return undefined + }, [enabled, slot]) + + if (!enabled || !slot) { + return null + } + + return ( +
+ +
+ ) +} diff --git a/resources/js/components/ads/AdSenseUnit.test.jsx b/resources/js/components/ads/AdSenseUnit.test.jsx new file mode 100644 index 00000000..67d37842 --- /dev/null +++ b/resources/js/components/ads/AdSenseUnit.test.jsx @@ -0,0 +1,43 @@ +import React from 'react' +import { afterEach, describe, expect, it, vi } from 'vitest' +import { cleanup, render } from '@testing-library/react' +import AdSenseUnit from './AdSenseUnit' + +describe('AdSenseUnit', () => { + afterEach(() => { + cleanup() + vi.restoreAllMocks() + delete window.adsbygoogle + }) + + it('renders the dashboard horizontal slot and pushes once', () => { + window.adsbygoogle = [] + + const { container } = render() + const unit = container.querySelector('ins.adsbygoogle') + + expect(unit).not.toBeNull() + expect(container.firstChild.className).toContain('justify-center') + expect(unit.className).toContain('max-w-3xl') + expect(unit.getAttribute('data-ad-client')).toBe('ca-pub-6457864535683080') + expect(unit.getAttribute('data-ad-slot')).toBe('8766048277') + expect(unit.getAttribute('data-ad-format')).toBe('auto') + expect(unit.getAttribute('data-full-width-responsive')).toBe('true') + expect(window.adsbygoogle).toHaveLength(1) + }) + + it('renders nothing without a slot', () => { + const { container } = render() + + expect(container.querySelector('ins.adsbygoogle')).toBeNull() + }) + + it('renders nothing when disabled for authenticated users', () => { + window.adsbygoogle = [] + + const { container } = render() + + expect(container.querySelector('ins.adsbygoogle')).toBeNull() + expect(window.adsbygoogle).toHaveLength(0) + }) +}) diff --git a/resources/js/components/artwork/ArtworkActionBar.jsx b/resources/js/components/artwork/ArtworkActionBar.jsx index 181162b2..746083a7 100644 --- a/resources/js/components/artwork/ArtworkActionBar.jsx +++ b/resources/js/components/artwork/ArtworkActionBar.jsx @@ -209,6 +209,7 @@ export default function ArtworkActionBar({ artwork, stats, canonicalUrl, onStats const [reported, setReported] = useState(false) const [reportOpen, setReportOpen] = useState(false) const isLoggedIn = Boolean(artwork?.viewer?.is_authenticated) + const downloadUnavailable = ['missing', 'pending'].includes(String(artwork?.download_status || '').toLowerCase()) useEffect(() => { setFavorited(Boolean(artwork?.viewer?.is_favorited)) }, [artwork?.id, artwork?.viewer?.is_favorited]) @@ -404,7 +405,7 @@ export default function ArtworkActionBar({ artwork, stats, canonicalUrl, onStats {/* Download button */} - + } {/* ── Mobile fixed bottom bar ─────────────────────────────────── */} @@ -491,7 +492,7 @@ export default function ArtworkActionBar({ artwork, stats, canonicalUrl, onStats - + } diff --git a/resources/views/components/ad-unit.blade.php b/resources/views/components/ad-unit.blade.php index 3ac6c127..9ea52101 100644 --- a/resources/views/components/ad-unit.blade.php +++ b/resources/views/components/ad-unit.blade.php @@ -9,13 +9,14 @@ Renders nothing when: - GOOGLE_ADSENSE_PUBLISHER_ID is not set in .env + - The visitor is authenticated - User has not given consent (handled client-side via CSS class .ads-disabled) --}} @php $publisherId = config('services.google_adsense.publisher_id'); @endphp -@if($publisherId) +@if($publisherId && ! auth()->check())
guest() && config('services.google_adsense.publisher_id')) + {{-- Google AdSense — guests only, consent-gated loader --}} {{-- Script is only injected after the user accepts all cookies. --}} {{-- If consent was given on a previous visit it fires on page load. --}} @endif + @guest + + @endguest + @php $authBgRoutes = [ diff --git a/resources/views/web/home.blade.php b/resources/views/web/home.blade.php index e6db47e8..ad80b416 100644 --- a/resources/views/web/home.blade.php +++ b/resources/views/web/home.blade.php @@ -100,8 +100,20 @@ @include('web.home.sections.news', ['items' => $props['news'] ?? []]) @include('web.home.sections.cta', ['isLoggedIn' => true]) @else - @foreach ($guestArtworkSections as $section) + @foreach ($guestArtworkSections as $index => $section) @include('web.home.sections.artwork-section', $section) + @if($index === 0) + {{-- Skinbase Dashboard Horizontal — guests only --}} +
+ + +
+ @endif @endforeach @include('web.home.sections.collections', ['collections' => $props['collections_trending'] ?: ($props['collections_featured'] ?: ($props['collections_editorial'] ?? []))]) @include('web.home.sections.world-spotlight', ['world' => $props['world_spotlight'] ?? null]) diff --git a/tests/Feature/HomePageTest.php b/tests/Feature/HomePageTest.php index 981900fb..0bea12c6 100644 --- a/tests/Feature/HomePageTest.php +++ b/tests/Feature/HomePageTest.php @@ -64,6 +64,22 @@ it('authenticated home page sends private cache headers', function () { ->toContain('no-store'); }); +it('authenticated home page does not render ads', function () { + $user = User::factory()->create(); + + $this->actingAs($user) + ->get('/') + ->assertOk() + ->assertDontSee('data-ad-slot="8766048277"', false) + ->assertDontSee('pagead2.googlesyndication.com', false); +}); + +it('guest home page renders the dashboard horizontal ad', function () { + $this->get('/') + ->assertOk() + ->assertSee('data-ad-slot="8766048277"', false); +}); + it('home page includes a canonical link tag', function () { $this->get('/') ->assertStatus(200)