Skip to content

Commit af0581b

Browse files
committed
feat: implement cookie consent banner and privacy policy page for compliance and user transparency
1 parent be7b64a commit af0581b

4 files changed

Lines changed: 323 additions & 10 deletions

File tree

src/components/CookieConsent.astro

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
---
2+
3+
---
4+
5+
<div
6+
id="cookie-consent-banner"
7+
class="fixed bottom-0 left-0 right-0 z-[60] translate-y-full transition-transform duration-500 ease-out"
8+
role="dialog"
9+
aria-label="Cookie consent"
10+
>
11+
<div class="bg-slate-900 border-t border-slate-700 shadow-2xl shadow-black/50">
12+
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-4">
13+
<div class="flex flex-col sm:flex-row items-start sm:items-center justify-between gap-4">
14+
<p class="text-sm text-slate-300">
15+
We use cookies to analyze site traffic and improve your experience. See our
16+
<a href="/privacy-policy" class="text-indigo-400 hover:text-indigo-300 underline underline-offset-2">
17+
Privacy Policy
18+
</a> for details.
19+
</p>
20+
<div class="flex items-center gap-3 shrink-0">
21+
<button
22+
id="cookie-decline"
23+
type="button"
24+
class="px-4 py-2 text-sm font-medium text-slate-400 hover:text-white border border-slate-600 hover:border-slate-500 rounded-lg transition-colors"
25+
>
26+
Decline
27+
</button>
28+
<button
29+
id="cookie-accept"
30+
type="button"
31+
class="px-4 py-2 text-sm font-medium text-white bg-indigo-600 hover:bg-indigo-500 rounded-lg transition-colors"
32+
>
33+
Accept
34+
</button>
35+
</div>
36+
</div>
37+
</div>
38+
</div>
39+
</div>
40+
41+
<noscript>
42+
<img height="1" width="1" style="display:none"
43+
src="https://www.facebook.com/tr?id=25949277121347381&ev=PageView&noscript=1"
44+
alt=""
45+
/>
46+
</noscript>
47+
48+
<script is:inline>
49+
(function () {
50+
var GA_ID = 'G-Q59L35YFPW';
51+
var META_PIXEL_ID = '25949277121347381';
52+
var STORAGE_KEY = 'libredb-cookie-consent';
53+
54+
function loadGA() {
55+
if (document.querySelector('script[src*="googletagmanager.com/gtag"]')) return;
56+
var s = document.createElement('script');
57+
s.async = true;
58+
s.src = 'https://www.googletagmanager.com/gtag/js?id=' + GA_ID;
59+
document.head.appendChild(s);
60+
window.dataLayer = window.dataLayer || [];
61+
function gtag() { dataLayer.push(arguments); }
62+
gtag('js', new Date());
63+
gtag('config', GA_ID);
64+
}
65+
66+
function loadMetaPixel() {
67+
if (window.fbq) return;
68+
!function(f,b,e,v,n,t,s)
69+
{if(f.fbq)return;n=f.fbq=function(){n.callMethod?
70+
n.callMethod.apply(n,arguments):n.queue.push(arguments)};
71+
if(!f._fbq)f._fbq=n;n.push=n;n.loaded=!0;n.version='2.0';
72+
n.queue=[];t=b.createElement(e);t.async=!0;
73+
t.src=v;s=b.getElementsByTagName(e)[0];
74+
s.parentNode.insertBefore(t,s)}(window,document,'script',
75+
'https://connect.facebook.net/en_US/fbevents.js');
76+
fbq('init', META_PIXEL_ID);
77+
fbq('track', 'PageView');
78+
}
79+
80+
function hideBanner() {
81+
var banner = document.getElementById('cookie-consent-banner');
82+
if (banner) {
83+
banner.classList.add('translate-y-full');
84+
banner.classList.remove('translate-y-0');
85+
}
86+
}
87+
88+
var consent = localStorage.getItem(STORAGE_KEY);
89+
90+
if (consent === 'accepted') {
91+
loadGA();
92+
loadMetaPixel();
93+
return;
94+
}
95+
96+
if (consent === 'declined') {
97+
return;
98+
}
99+
100+
// First visit: show banner after 1s delay
101+
setTimeout(function () {
102+
var banner = document.getElementById('cookie-consent-banner');
103+
if (banner) {
104+
banner.classList.remove('translate-y-full');
105+
banner.classList.add('translate-y-0');
106+
}
107+
}, 1000);
108+
109+
document.getElementById('cookie-accept')?.addEventListener('click', function () {
110+
localStorage.setItem(STORAGE_KEY, 'accepted');
111+
hideBanner();
112+
loadGA();
113+
loadMetaPixel();
114+
});
115+
116+
document.getElementById('cookie-decline')?.addEventListener('click', function () {
117+
localStorage.setItem(STORAGE_KEY, 'declined');
118+
hideBanner();
119+
});
120+
})();
121+
</script>

src/components/Footer.astro

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,13 +32,20 @@ const sections = [
3232
{ label: "Contributing", href: "https://github.com/libredb/libredb-studio#contributing", external: true },
3333
],
3434
},
35+
{
36+
id: "legal",
37+
title: "Legal",
38+
links: [
39+
{ label: "Privacy Policy", href: "/privacy-policy" },
40+
],
41+
},
3542
];
3643
---
3744

3845
<footer class="border-t border-slate-800 bg-slate-950 pb-20 md:pb-0">
3946
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8 md:py-12">
4047
<!-- Brand Section -->
41-
<div class="mb-8 md:mb-0 md:grid md:grid-cols-2 lg:grid-cols-5 md:gap-8">
48+
<div class="mb-8 md:mb-0 md:grid md:grid-cols-2 lg:grid-cols-6 md:gap-8">
4249
<div class="lg:col-span-2 mb-6 md:mb-0">
4350
<a href="/" class="flex items-center gap-3 mb-4">
4451
<img src="/logo.svg" alt="LibreDB Studio" class="w-8 h-8 md:w-10 md:h-10" />
@@ -129,6 +136,22 @@ const sections = [
129136
))}
130137
</ul>
131138
</div>
139+
140+
<div class="hidden md:block">
141+
<h4 class="text-sm font-semibold text-white uppercase tracking-wider mb-4">Legal</h4>
142+
<ul class="space-y-3">
143+
{sections[3].links.map((link) => (
144+
<li>
145+
<a
146+
href={link.href}
147+
class="text-sm text-slate-400 hover:text-white transition-colors"
148+
>
149+
{link.label}
150+
</a>
151+
</li>
152+
))}
153+
</ul>
154+
</div>
132155
</div>
133156

134157
<!-- Mobile Accordion (visible only on mobile) -->

src/layouts/Layout.astro

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
---
2+
import CookieConsent from '../components/CookieConsent.astro';
3+
24
interface Props {
35
title: string;
46
description?: string;
@@ -35,15 +37,6 @@ const structuredData = {
3537
<!doctype html>
3638
<html lang="en" class="scroll-smooth">
3739
<head>
38-
<!-- Google tag (gtag.js) -->
39-
<script is:inline async src="https://www.googletagmanager.com/gtag/js?id=G-Q59L35YFPW"></script>
40-
<script is:inline>
41-
window.dataLayer = window.dataLayer || [];
42-
function gtag(){dataLayer.push(arguments);}
43-
gtag('js', new Date());
44-
gtag('config', 'G-Q59L35YFPW');
45-
</script>
46-
4740
<meta charset="UTF-8" />
4841
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
4942
<meta name="description" content={description} />
@@ -90,6 +83,7 @@ const structuredData = {
9083
</head>
9184
<body class="bg-slate-950 text-slate-100 font-sans antialiased">
9285
<slot />
86+
<CookieConsent />
9387
</body>
9488
</html>
9589

src/pages/privacy-policy.astro

Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
---
2+
import Layout from '../layouts/Layout.astro';
3+
import Header from '../components/Header.astro';
4+
import Footer from '../components/Footer.astro';
5+
---
6+
7+
<Layout title="Privacy Policy - LibreDB Studio">
8+
<Header />
9+
<main class="pt-24 pb-16">
10+
<div class="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8">
11+
<h1 class="text-3xl sm:text-4xl font-bold text-white mb-2">Privacy Policy</h1>
12+
<p class="text-sm text-slate-400 mb-12">Last updated: March 1, 2026</p>
13+
14+
<!-- Introduction -->
15+
<section class="mb-10">
16+
<h2 class="text-xl font-semibold text-white mb-4">Introduction</h2>
17+
<p class="text-slate-300 leading-relaxed">
18+
LibreDB Studio ("we", "our", or "us") respects your privacy. This Privacy Policy explains how we collect,
19+
use, and protect information when you visit our website at
20+
<a href="https://libredb.org" class="text-indigo-400 hover:text-indigo-300 underline underline-offset-2">libredb.org</a>.
21+
LibreDB Studio is an open-source project and this website serves as its landing page.
22+
</p>
23+
</section>
24+
25+
<!-- Information We Collect -->
26+
<section class="mb-10">
27+
<h2 class="text-xl font-semibold text-white mb-4">Information We Collect</h2>
28+
<p class="text-slate-300 leading-relaxed mb-4">
29+
We do not require you to create an account or provide personal information to use this website.
30+
The only data we collect is through analytics cookies, and only if you give your consent.
31+
</p>
32+
<p class="text-slate-300 leading-relaxed">
33+
When you consent to analytics, Google Analytics may collect:
34+
</p>
35+
<ul class="mt-3 space-y-2 text-slate-300 list-disc list-inside">
36+
<li>Pages visited and time spent on each page</li>
37+
<li>Referring website or source</li>
38+
<li>Browser type and operating system</li>
39+
<li>Approximate geographic location (country/city level)</li>
40+
<li>Device type (desktop, mobile, tablet)</li>
41+
</ul>
42+
</section>
43+
44+
<!-- Cookies -->
45+
<section class="mb-10">
46+
<h2 class="text-xl font-semibold text-white mb-4">Cookies</h2>
47+
<p class="text-slate-300 leading-relaxed mb-4">
48+
Cookies are small text files stored on your device. Below is a summary of the cookies used on this site:
49+
</p>
50+
<div class="overflow-x-auto rounded-lg border border-slate-700">
51+
<table class="w-full text-sm text-left">
52+
<thead class="bg-slate-800 text-slate-300 uppercase text-xs tracking-wider">
53+
<tr>
54+
<th class="px-4 py-3">Cookie</th>
55+
<th class="px-4 py-3">Provider</th>
56+
<th class="px-4 py-3">Purpose</th>
57+
<th class="px-4 py-3">Duration</th>
58+
</tr>
59+
</thead>
60+
<tbody class="divide-y divide-slate-700">
61+
<tr class="bg-slate-900/50">
62+
<td class="px-4 py-3 text-slate-300 font-mono text-xs">_ga</td>
63+
<td class="px-4 py-3 text-slate-400">Google Analytics</td>
64+
<td class="px-4 py-3 text-slate-400">Distinguishes unique visitors</td>
65+
<td class="px-4 py-3 text-slate-400">2 years</td>
66+
</tr>
67+
<tr class="bg-slate-900/30">
68+
<td class="px-4 py-3 text-slate-300 font-mono text-xs">_ga_*</td>
69+
<td class="px-4 py-3 text-slate-400">Google Analytics</td>
70+
<td class="px-4 py-3 text-slate-400">Maintains session state</td>
71+
<td class="px-4 py-3 text-slate-400">2 years</td>
72+
</tr>
73+
</tbody>
74+
</table>
75+
</div>
76+
<p class="text-slate-400 text-sm mt-3">
77+
We also store your cookie consent preference in <code class="text-indigo-400 bg-slate-800 px-1.5 py-0.5 rounded text-xs">localStorage</code>
78+
(key: <code class="text-indigo-400 bg-slate-800 px-1.5 py-0.5 rounded text-xs">libredb-cookie-consent</code>).
79+
This is not a cookie and is not sent to any server.
80+
</p>
81+
</section>
82+
83+
<!-- Your Choices -->
84+
<section class="mb-10">
85+
<h2 class="text-xl font-semibold text-white mb-4">Your Choices</h2>
86+
<p class="text-slate-300 leading-relaxed mb-4">
87+
When you first visit our site, a cookie consent banner will appear. You can choose to:
88+
</p>
89+
<ul class="space-y-2 text-slate-300 list-disc list-inside">
90+
<li><strong class="text-white">Accept</strong> &mdash; analytics cookies will be set and Google Analytics will load.</li>
91+
<li><strong class="text-white">Decline</strong> &mdash; no analytics cookies will be set and Google Analytics will not load.</li>
92+
</ul>
93+
<p class="text-slate-300 leading-relaxed mt-4">
94+
You can change your preference at any time by clearing your browser's local storage for this site and reloading the page.
95+
</p>
96+
</section>
97+
98+
<!-- Data Sharing -->
99+
<section class="mb-10">
100+
<h2 class="text-xl font-semibold text-white mb-4">Data Sharing</h2>
101+
<p class="text-slate-300 leading-relaxed">
102+
If you accept analytics cookies, anonymized usage data is processed by Google Analytics under
103+
Google's <a href="https://policies.google.com/privacy" target="_blank" rel="noopener noreferrer" class="text-indigo-400 hover:text-indigo-300 underline underline-offset-2">Privacy Policy</a>.
104+
We do not sell, rent, or share your personal data with any other third parties.
105+
</p>
106+
</section>
107+
108+
<!-- Data Retention -->
109+
<section class="mb-10">
110+
<h2 class="text-xl font-semibold text-white mb-4">Data Retention</h2>
111+
<p class="text-slate-300 leading-relaxed">
112+
Google Analytics data is retained for 14 months, after which it is automatically deleted.
113+
Your consent preference stored in local storage persists until you clear it manually.
114+
</p>
115+
</section>
116+
117+
<!-- GDPR Rights -->
118+
<section class="mb-10">
119+
<h2 class="text-xl font-semibold text-white mb-4">Your Rights Under GDPR</h2>
120+
<p class="text-slate-300 leading-relaxed mb-4">
121+
If you are located in the European Economic Area (EEA), you have the following rights:
122+
</p>
123+
<ul class="space-y-2 text-slate-300 list-disc list-inside">
124+
<li><strong class="text-white">Right to access</strong> &mdash; request a copy of the data we hold about you.</li>
125+
<li><strong class="text-white">Right to rectification</strong> &mdash; request correction of inaccurate data.</li>
126+
<li><strong class="text-white">Right to erasure</strong> &mdash; request deletion of your data.</li>
127+
<li><strong class="text-white">Right to restrict processing</strong> &mdash; request that we limit how we use your data.</li>
128+
<li><strong class="text-white">Right to data portability</strong> &mdash; request your data in a machine-readable format.</li>
129+
<li><strong class="text-white">Right to object</strong> &mdash; object to our processing of your data.</li>
130+
<li><strong class="text-white">Right to withdraw consent</strong> &mdash; withdraw your consent at any time by clearing local storage.</li>
131+
</ul>
132+
</section>
133+
134+
<!-- Children's Privacy -->
135+
<section class="mb-10">
136+
<h2 class="text-xl font-semibold text-white mb-4">Children's Privacy</h2>
137+
<p class="text-slate-300 leading-relaxed">
138+
This website is not directed at children under 16. We do not knowingly collect personal data from children.
139+
</p>
140+
</section>
141+
142+
<!-- Changes to This Policy -->
143+
<section class="mb-10">
144+
<h2 class="text-xl font-semibold text-white mb-4">Changes to This Policy</h2>
145+
<p class="text-slate-300 leading-relaxed">
146+
We may update this Privacy Policy from time to time. Changes will be posted on this page with an updated
147+
"Last updated" date. We encourage you to review this page periodically.
148+
</p>
149+
</section>
150+
151+
<!-- Contact -->
152+
<section class="mb-10">
153+
<h2 class="text-xl font-semibold text-white mb-4">Contact</h2>
154+
<p class="text-slate-300 leading-relaxed">
155+
If you have questions about this Privacy Policy, please open an issue on our
156+
<a href="https://github.com/libredb/libredb-studio/issues" target="_blank" rel="noopener noreferrer" class="text-indigo-400 hover:text-indigo-300 underline underline-offset-2">GitHub repository</a>.
157+
</p>
158+
</section>
159+
160+
<!-- Back to Home -->
161+
<div class="pt-6 border-t border-slate-800">
162+
<a
163+
href="/"
164+
class="inline-flex items-center gap-2 text-sm text-indigo-400 hover:text-indigo-300 transition-colors"
165+
>
166+
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
167+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"></path>
168+
</svg>
169+
Back to Home
170+
</a>
171+
</div>
172+
</div>
173+
</main>
174+
<Footer />
175+
</Layout>

0 commit comments

Comments
 (0)