Skip to content

Commit 561d830

Browse files
authored
✨ feat: implement analytics consent banner and tracking functionality
1 parent f27864e commit 561d830

14 files changed

Lines changed: 470 additions & 27 deletions

File tree

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<div
2+
id="analytics-consent-banner"
3+
class="fixed inset-x-4 bottom-4 z-[140] hidden rounded-2xl border border-[var(--border-strong)] bg-[var(--surface-elevated)] p-4 shadow-xl md:inset-x-auto md:right-6 md:max-w-md"
4+
role="dialog"
5+
aria-live="polite"
6+
aria-label="Analytics consent"
7+
>
8+
<p class="text-sm font-semibold text-[var(--text-strong)]">Privacy settings</p>
9+
<p class="mt-1 text-sm text-[var(--text-muted)]">
10+
This site uses analytics to measure performance and improve content. You can accept or decline tracking.
11+
</p>
12+
<div class="mt-4 flex flex-wrap gap-2">
13+
<button
14+
type="button"
15+
data-consent-action="decline"
16+
class="inline-flex min-h-[2.3rem] items-center justify-center rounded-lg border border-[var(--border-strong)] px-3 py-2 text-xs font-semibold uppercase tracking-[0.06em] text-[var(--text-strong)]"
17+
>
18+
Decline
19+
</button>
20+
<button
21+
type="button"
22+
data-consent-action="accept"
23+
class="inline-flex min-h-[2.3rem] items-center justify-center rounded-lg bg-[linear-gradient(120deg,#db7a38,#b05422)] px-3 py-2 text-xs font-semibold uppercase tracking-[0.06em] text-white"
24+
>
25+
Accept analytics
26+
</button>
27+
</div>
28+
</div>

src/components/Footer.astro

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,13 @@ const socialLinks = [
77
kind: "linkedin" as const,
88
href: "https://www.linkedin.com/in/ar10dev",
99
label: "LinkedIn",
10+
trackId: "footer_text_linkedin",
1011
},
1112
{
1213
kind: "github" as const,
1314
href: "https://github.com/AR10Dev",
1415
label: "GitHub",
16+
trackId: "footer_text_github",
1517
},
1618
];
1719
---
@@ -26,6 +28,7 @@ const socialLinks = [
2628
Made by <a
2729
class="font-bold text-[var(--accent-strong)] hover:underline"
2830
href="https://github.com/AR10Dev"
31+
data-track="footer_text_made_by"
2932
rel="me"
3033
>Avaab Razzaq (AR10Dev)
3134
</a>

src/components/FooterNav.astro

Lines changed: 34 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,25 +8,47 @@ const socialLinks = [
88
kind: "linkedin" as const,
99
href: "https://www.linkedin.com/in/ar10dev",
1010
label: "LinkedIn",
11+
trackId: "footer_open_linkedin",
1112
},
1213
{
1314
kind: "github" as const,
1415
href: "https://github.com/AR10Dev",
1516
label: "GitHub",
17+
trackId: "footer_open_github",
1618
},
1719
];
1820
1921
const footerLinks = {
2022
services: [
21-
{ label: "AI & Automation", href: "/services/ai-automation/" },
22-
{ label: "Web Development", href: "/services/web-development/" },
23-
{ label: "SEO & Growth", href: "/services/seo-growth/" },
23+
{
24+
label: "AI & Automation",
25+
href: "/services/ai-automation/",
26+
trackId: "footer_service_ai_automation",
27+
},
28+
{
29+
label: "Web Development",
30+
href: "/services/web-development/",
31+
trackId: "footer_service_web_development",
32+
},
33+
{
34+
label: "SEO & Growth",
35+
href: "/services/seo-growth/",
36+
trackId: "footer_service_seo_growth",
37+
},
2438
],
2539
company: [
26-
{ label: "About", href: "/about/" },
27-
{ label: "Portfolio", href: "/portfolio/" },
28-
{ label: "Blog", href: "/blog/" },
29-
{ label: "Contact", href: "/contact/" },
40+
{ label: "About", href: "/about/", trackId: "footer_company_about" },
41+
{
42+
label: "Portfolio",
43+
href: "/portfolio/",
44+
trackId: "footer_company_portfolio",
45+
},
46+
{ label: "Blog", href: "/blog/", trackId: "footer_company_blog" },
47+
{
48+
label: "Contact",
49+
href: "/contact/",
50+
trackId: "footer_company_contact",
51+
},
3052
],
3153
};
3254
@@ -42,7 +64,7 @@ const contactInfo = {
4264
<div class="grid gap-10 md:grid-cols-2 lg:grid-cols-4">
4365
{/* Brand Column */}
4466
<div class="lg:col-span-1">
45-
<a href="/" class="text-xl font-bold text-[var(--text-strong)]">
67+
<a href="/" class="text-xl font-bold text-[var(--text-strong)]" data-track="footer_brand_home">
4668
Avaab Razzaq
4769
</a>
4870
<p class="mt-3 text-sm text-[var(--text-muted)] leading-relaxed">
@@ -61,6 +83,7 @@ const contactInfo = {
6183
<li>
6284
<a
6385
href={link.href}
86+
data-track={link.trackId}
6487
class="text-sm text-[var(--text-muted)] hover:text-[var(--accent-strong)] transition-colors"
6588
>
6689
{link.label}
@@ -80,6 +103,7 @@ const contactInfo = {
80103
<li>
81104
<a
82105
href={link.href}
106+
data-track={link.trackId}
83107
class="text-sm text-[var(--text-muted)] hover:text-[var(--accent-strong)] transition-colors"
84108
>
85109
{link.label}
@@ -114,13 +138,14 @@ const contactInfo = {
114138
<path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"/>
115139
<polyline points="22,6 12,13 2,6"/>
116140
</svg>
117-
<a href={`mailto:${contactInfo.email}`} class="hover:text-[var(--accent-strong)] transition-colors">
141+
<a href={`mailto:${contactInfo.email}`} data-track="footer_contact_email" class="hover:text-[var(--accent-strong)] transition-colors">
118142
{contactInfo.email}
119143
</a>
120144
</li>
121145
</ul>
122146
<a
123147
href="/contact/"
148+
data-track="footer_contact_cta"
124149
class="mt-5 inline-flex items-center gap-2 rounded-lg bg-[linear-gradient(120deg,#db7a38,#b05422)] px-4 py-2 text-sm font-bold text-white shadow-md hover:shadow-lg transition-shadow"
125150
>
126151
Get in Touch

src/components/GoogleAnalytics.astro

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,54 @@ const { id } = Astro.props;
1414
></script>
1515

1616
<script is:inline type="text/partytown" define:vars={{ id }}>
17+
const deniedConsent = {
18+
ad_storage: "denied",
19+
analytics_storage: "denied",
20+
ad_user_data: "denied",
21+
ad_personalization: "denied",
22+
};
23+
const grantedConsent = {
24+
ad_storage: "granted",
25+
analytics_storage: "granted",
26+
ad_user_data: "granted",
27+
ad_personalization: "granted",
28+
};
29+
let hasConfiguredAnalytics = false;
30+
31+
const applyConsent = (consent) => {
32+
if (consent === "granted") {
33+
window.gtag("consent", "update", grantedConsent);
34+
35+
if (!hasConfiguredAnalytics) {
36+
window.gtag("config", id);
37+
hasConfiguredAnalytics = true;
38+
}
39+
40+
return;
41+
}
42+
43+
window.gtag("consent", "update", deniedConsent);
44+
};
45+
1746
window.dataLayer = window.dataLayer || [];
1847
window.gtag = function () {
1948
dataLayer.push(arguments);
2049
};
50+
2151
window.gtag('js', new Date());
22-
window.gtag('config', id);
52+
window.gtag("consent", "default", {
53+
...deniedConsent,
54+
wait_for_update: 500,
55+
});
56+
57+
applyConsent("denied");
58+
59+
window.addEventListener("analytics:consent", (event) => {
60+
const consent =
61+
event instanceof CustomEvent && event.detail?.consent === "granted"
62+
? "granted"
63+
: "denied";
64+
65+
applyConsent(consent);
66+
});
2367
</script>

src/components/HeaderNav.astro

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ const headerBackground =
2929

3030
<header class={`fixed inset-x-0 top-0 z-50 w-full transition-all duration-300 ${headerBackground}`}>
3131
<div class="mx-auto flex max-w-screen-lg items-center gap-3 px-4 py-3 xl:max-w-screen-xl">
32-
<a href="/" class="flex-none" aria-label="Avaab Razzaq - Home">
32+
<a href="/" class="flex-none" aria-label="Avaab Razzaq - Home" data-track="nav_logo_home">
3333
<Image
3434
src={logo}
3535
alt="Avaab Razzaq - AI Growth Engineer"

src/components/solid/Navigation.tsx

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,12 @@ const Navigation: Component<NavigationProps> = (props) => {
4848
const mobileActiveClass =
4949
"!text-[var(--accent-strong)] bg-[var(--surface-elevated)]";
5050

51+
const toTrackToken = (value: string): string =>
52+
value
53+
.toLowerCase()
54+
.replace(/[^a-z0-9]+/g, "_")
55+
.replace(/^_+|_+$/g, "");
56+
5157
return (
5258
<nav class="flex items-center" aria-label="Main navigation">
5359
{/* Desktop Navigation */}
@@ -58,6 +64,7 @@ const Navigation: Component<NavigationProps> = (props) => {
5864
<a
5965
class={`${baseClass} ${isActive(link.href) ? activeClass : ""}`}
6066
href={link.href}
67+
data-track={`nav_desktop_${toTrackToken(link.label)}`}
6168
aria-current={isActive(link.href) ? "page" : undefined}
6269
>
6370
{link.label}
@@ -162,6 +169,7 @@ const Navigation: Component<NavigationProps> = (props) => {
162169
<a
163170
class={`${mobileBaseClass} ${isActive(link.href) ? mobileActiveClass : ""}`}
164171
href={link.href}
172+
data-track={`nav_mobile_${toTrackToken(link.label)}`}
165173
onClick={() => setIsOpen(false)}
166174
aria-current={isActive(link.href) ? "page" : undefined}
167175
>
@@ -175,6 +183,7 @@ const Navigation: Component<NavigationProps> = (props) => {
175183
<a
176184
href="/contact/"
177185
class="block w-full rounded-lg bg-[linear-gradient(120deg,#db7a38,#b05422)] px-4 py-3 text-center font-bold text-white shadow-lg"
186+
data-track="nav_mobile_contact_cta"
178187
onClick={() => setIsOpen(false)}
179188
>
180189
Get in Touch

src/env.d.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,9 @@
11
/// <reference types="astro/client" />
2+
3+
interface ImportMetaEnv {
4+
readonly PUBLIC_GA_MEASUREMENT_ID?: string;
5+
}
6+
7+
interface ImportMeta {
8+
readonly env: ImportMetaEnv;
9+
}

src/layouts/BaseLayout.astro

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
---
2+
import AnalyticsConsent from "@components/AnalyticsConsent.astro";
23
import FooterNav from "@components/FooterNav.astro";
34
import GoogleAnalytics from "@components/GoogleAnalytics.astro";
45
import HeaderNav from "@components/HeaderNav.astro";
@@ -28,6 +29,7 @@ const {
2829
const defaultKeywords =
2930
"AI Growth Engineer, AI Automation, Full-Stack Developer, SEO Expert, Miami Florida, Web Development, LLM Integration, Workflow Automation";
3031
const finalKeywords = keywords || defaultKeywords;
32+
const gaMeasurementId = import.meta.env.PUBLIC_GA_MEASUREMENT_ID?.trim();
3133
3234
const currentPath = Astro.url.pathname;
3335
---
@@ -37,8 +39,12 @@ const currentPath = Astro.url.pathname;
3739
<head>
3840
<meta charset="UTF-8" />
3941

40-
<!-- Google Analytics -->
41-
<GoogleAnalytics id="G-LKVQYYL9XZ" />
42+
{import.meta.env.PROD && gaMeasurementId && (
43+
<>
44+
<!-- Google Analytics -->
45+
<GoogleAnalytics id={gaMeasurementId} />
46+
</>
47+
)}
4248

4349
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=5.0" />
4450
<meta name="description" content={description} />
@@ -121,6 +127,8 @@ const currentPath = Astro.url.pathname;
121127

122128
<FooterNav />
123129

130+
{import.meta.env.PROD && gaMeasurementId && <AnalyticsConsent />}
131+
124132
<script>
125133
import "../scripts/homepage.ts";
126134
</script>

src/layouts/Layout.astro

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
---
2+
import AnalyticsConsent from "@components/AnalyticsConsent.astro";
23
import Footer from "@components/Footer.astro";
34
import GoogleAnalytics from "@components/GoogleAnalytics.astro";
45
import Header from "@components/Header.astro";
@@ -22,14 +23,19 @@ const {
2223
structuredData,
2324
headerBackground,
2425
} = Astro.props as Props;
26+
const gaMeasurementId = import.meta.env.PUBLIC_GA_MEASUREMENT_ID?.trim();
2527
---
2628

2729
<html lang="en" class="scroll-smooth">
2830
<head>
2931
<meta charset="UTF-8" />
3032

31-
<!-- Google Analytics -->
32-
<GoogleAnalytics id="G-LKVQYYL9XZ" />
33+
{import.meta.env.PROD && gaMeasurementId && (
34+
<>
35+
<!-- Google Analytics -->
36+
<GoogleAnalytics id={gaMeasurementId} />
37+
</>
38+
)}
3339

3440
<meta
3541
name="viewport"
@@ -98,6 +104,7 @@ const {
98104
</main>
99105

100106
<Footer />
107+
{import.meta.env.PROD && gaMeasurementId && <AnalyticsConsent />}
101108
<script>
102109
import "../scripts/homepage.ts";
103110
</script>

0 commit comments

Comments
 (0)