feat: Add GDPR compliance kit with cookie consent and privacy templates
- Privacy policy template with GDPR Art. 13/14 requirements - Cookie consent React component with granular controls - GDPR-compliant analytics wrapper for Vercel/GA - Netcup DPA Annex 3 completion guide - Records of Processing Activities (ROPA) template - High-priority backlog task for deployment to all sites 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
commit
3e926fee0e
|
|
@ -0,0 +1,154 @@
|
|||
# Netcup DPA Annex 3 - Processing Specifications Guide
|
||||
|
||||
This guide helps you fill out Annex 3 of the Netcup Data Processing Agreement.
|
||||
|
||||
## Your Specific Situation
|
||||
|
||||
Based on your infrastructure:
|
||||
- **Hosting Provider**: netcup GmbH (Germany)
|
||||
- **CDN/Security**: Cloudflare
|
||||
- **Newsletter**: Listmonk (self-hosted on Netcup)
|
||||
- **Analytics**: Vercel Analytics
|
||||
|
||||
---
|
||||
|
||||
## Section 1: Subject (Nature & Purpose) of the Processing
|
||||
|
||||
**Recommended text to enter:**
|
||||
|
||||
```
|
||||
Web hosting and delivery of websites and web applications. This includes:
|
||||
- Serving static and dynamic web content to visitors
|
||||
- Processing contact form submissions
|
||||
- Managing newsletter subscriptions (via self-hosted Listmonk)
|
||||
- Collecting anonymized website analytics
|
||||
- Storing user-generated content where applicable
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Section 2: Duration of the Processing
|
||||
|
||||
This is automatically determined by your contract term with Netcup.
|
||||
|
||||
---
|
||||
|
||||
## Section 3: Location of the Processing
|
||||
|
||||
The location is determined by your Netcup server location. For your RS 8000 G12 Pro:
|
||||
- **Primary Location**: Nuremberg, Germany (EU)
|
||||
- **Additional Processing**: Via Cloudflare's global network (with EU data residency options)
|
||||
|
||||
---
|
||||
|
||||
## Section 4: Categories of Data Subjects
|
||||
|
||||
**Check the following boxes:**
|
||||
|
||||
- [x] **Customers** - if you have any e-commerce or client portals
|
||||
- [x] **Interested parties** - potential customers visiting your sites
|
||||
- [ ] **Suppliers** - only if you process supplier data
|
||||
- [x] **Visitors to the website** - all website visitors
|
||||
- [ ] **Employees of the Client** - only if you have employee data on the sites
|
||||
- [ ] **External employees** - only if applicable
|
||||
- [ ] **Data processors, other processors** - only if you subcontract
|
||||
- [x] **Newsletter subscribers** - you use Listmonk
|
||||
|
||||
**Additional data subjects (if any):**
|
||||
```
|
||||
Event attendees (if you host events/conferences)
|
||||
Community members (if you have user accounts)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Section 5: Categories of Personal Data
|
||||
|
||||
**Check the following boxes:**
|
||||
|
||||
- [x] **Name data** - contact forms, newsletter signups
|
||||
- [ ] **Date of birth** - only if you collect this
|
||||
- [ ] **Bank and payment data** - only if you handle payments directly
|
||||
- [ ] **Location and geographic information data** - only if you track location
|
||||
- [ ] **Education data** - only if relevant to your sites
|
||||
- [ ] **Traffic data** - only if you log detailed traffic
|
||||
- [ ] **Data relevant to criminal law** - NO
|
||||
- [x] **Contact and address data** - contact forms
|
||||
- [ ] **Customer contract data** - only if you have customer portals
|
||||
- [ ] **Login and authentication** - only if you have user accounts
|
||||
- [ ] **Preference and behavior data** - only if you track preferences
|
||||
- [ ] **Motion profile data** - NO
|
||||
- [ ] **Photo, video, or audio data** - only if you store media
|
||||
|
||||
**Additional data types:**
|
||||
```
|
||||
Email addresses
|
||||
IP addresses (anonymized for analytics)
|
||||
Browser/device information (anonymized)
|
||||
Cookie consent preferences
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Special Categories of Data (Art. 9 GDPR)
|
||||
|
||||
**IMPORTANT**: Select the first option unless you specifically process sensitive data.
|
||||
|
||||
- [x] **No special categories of personal data ("sensitive data") according to Art 9 GDPR are processed.**
|
||||
|
||||
If any of your sites deal with health, religion, political opinions, biometric data, etc., you would need to check the second option and specify which categories.
|
||||
|
||||
---
|
||||
|
||||
## Complete Form Example
|
||||
|
||||
Here's how your completed Annex 3 should look:
|
||||
|
||||
### 1. Subject Matter
|
||||
```
|
||||
Web hosting and content delivery for multiple websites and web applications including:
|
||||
- Static and dynamic website hosting
|
||||
- Newsletter subscription management (Listmonk)
|
||||
- Contact form processing
|
||||
- Anonymized web analytics collection
|
||||
- Content management systems
|
||||
```
|
||||
|
||||
### 4. Data Subjects (check these):
|
||||
- [x] Interested parties
|
||||
- [x] Visitors to the website
|
||||
- [x] Newsletter subscribers
|
||||
- [x] Customers (if applicable)
|
||||
|
||||
### 5. Personal Data Categories (check these):
|
||||
- [x] Name data
|
||||
- [x] Contact and address data
|
||||
|
||||
**Additional data:**
|
||||
```
|
||||
Email addresses
|
||||
IP addresses (anonymized)
|
||||
Browser user agent information
|
||||
Cookie consent preferences
|
||||
Website usage data (anonymized)
|
||||
```
|
||||
|
||||
### Special Categories:
|
||||
- [x] No special categories of personal data are processed
|
||||
|
||||
---
|
||||
|
||||
## After Submitting
|
||||
|
||||
1. **Save a copy** of the completed agreement for your records
|
||||
2. **Date it** when you submit
|
||||
3. **Review annually** to ensure it still accurately reflects your processing activities
|
||||
|
||||
---
|
||||
|
||||
## Tips
|
||||
|
||||
1. **Be conservative** - only check categories you actually process
|
||||
2. **When in doubt, exclude** - you can always add categories later
|
||||
3. **Keep it updated** - if you add new features that collect data, update the DPA
|
||||
4. **Document everything** - maintain your own Records of Processing Activities (ROPA)
|
||||
|
|
@ -0,0 +1,229 @@
|
|||
# Privacy Policy
|
||||
|
||||
**Last Updated: [DATE]**
|
||||
|
||||
## 1. Introduction
|
||||
|
||||
Welcome to [WEBSITE_NAME] ("we," "our," or "us"). We are committed to protecting your personal data and respecting your privacy in accordance with the General Data Protection Regulation (GDPR) and other applicable data protection laws.
|
||||
|
||||
This Privacy Policy explains how we collect, use, disclose, and safeguard your information when you visit our website [WEBSITE_URL] (the "Site").
|
||||
|
||||
## 2. Data Controller
|
||||
|
||||
The data controller responsible for your personal data is:
|
||||
|
||||
**Jeff Emmett**
|
||||
23 Birchpark Dr
|
||||
L3M 4M9 Grimsby, Canada
|
||||
|
||||
Email: [CONTACT_EMAIL]
|
||||
|
||||
## 3. What Data We Collect
|
||||
|
||||
### 3.1 Data You Provide to Us
|
||||
|
||||
We may collect the following categories of personal data that you voluntarily provide:
|
||||
|
||||
- **Contact Information**: Name, email address when you contact us or subscribe to our newsletter
|
||||
- **Communication Data**: Content of messages you send us through contact forms or email
|
||||
|
||||
### 3.2 Data Collected Automatically
|
||||
|
||||
When you visit our Site, we may automatically collect:
|
||||
|
||||
- **Technical Data**: IP address (anonymized), browser type, operating system, device type
|
||||
- **Usage Data**: Pages visited, time spent on pages, referring website, click patterns
|
||||
- **Cookie Data**: See our Cookie Policy section below
|
||||
|
||||
### 3.3 Data We Do NOT Collect
|
||||
|
||||
We do not collect:
|
||||
- Special category data (health, religion, political opinions, etc.)
|
||||
- Financial/payment data (unless you make a purchase, handled by third-party processors)
|
||||
- Data from children under 16 years of age
|
||||
|
||||
## 4. How We Use Your Data
|
||||
|
||||
We process your personal data for the following purposes and legal bases:
|
||||
|
||||
| Purpose | Legal Basis (GDPR Art. 6) |
|
||||
|---------|--------------------------|
|
||||
| Responding to your inquiries | Legitimate interest / Contract performance |
|
||||
| Sending newsletters (if subscribed) | Consent |
|
||||
| Website analytics and improvement | Legitimate interest / Consent |
|
||||
| Security and fraud prevention | Legitimate interest |
|
||||
| Legal compliance | Legal obligation |
|
||||
|
||||
## 5. Newsletter & Email Communications
|
||||
|
||||
If you subscribe to our newsletter:
|
||||
- We use **Listmonk** (self-hosted) to manage subscriptions
|
||||
- You can unsubscribe at any time using the link in every email
|
||||
- We will never share your email with third parties for marketing
|
||||
- Legal basis: Your explicit consent (GDPR Art. 6(1)(a))
|
||||
|
||||
## 6. Cookies and Tracking
|
||||
|
||||
### 6.1 What Are Cookies?
|
||||
|
||||
Cookies are small text files stored on your device when you visit websites. We use cookies to:
|
||||
- Remember your preferences (e.g., cookie consent choice)
|
||||
- Understand how you use our website (analytics)
|
||||
|
||||
### 6.2 Types of Cookies We Use
|
||||
|
||||
| Cookie Type | Purpose | Duration | Consent Required? |
|
||||
|-------------|---------|----------|-------------------|
|
||||
| **Strictly Necessary** | Essential for site functionality | Session | No |
|
||||
| **Analytics** | Understand site usage patterns | 1 year | Yes |
|
||||
| **Preferences** | Remember your settings | 1 year | Yes |
|
||||
|
||||
### 6.3 Analytics
|
||||
|
||||
We use [Vercel Analytics / Plausible / other] to understand how visitors interact with our Site. This service:
|
||||
- [Collects anonymized usage data / Collects IP addresses]
|
||||
- [Does not use cookies / Uses first-party cookies]
|
||||
- Data is processed in [location]
|
||||
|
||||
### 6.4 Managing Cookies
|
||||
|
||||
You can manage cookies through:
|
||||
- Our cookie consent banner (appears on first visit)
|
||||
- Your browser settings
|
||||
- Links at the bottom of our pages
|
||||
|
||||
To opt-out of analytics, you can:
|
||||
- Click "Reject" on our cookie consent banner
|
||||
- Use browser extensions like uBlock Origin or Privacy Badger
|
||||
|
||||
## 7. Data Sharing and Third Parties
|
||||
|
||||
We may share your data with:
|
||||
|
||||
### 7.1 Infrastructure Providers (Data Processors)
|
||||
|
||||
| Provider | Service | Location | DPA |
|
||||
|----------|---------|----------|-----|
|
||||
| **netcup GmbH** | Web hosting infrastructure | Germany (EU) | Yes |
|
||||
| **Cloudflare, Inc.** | CDN, security, DNS | Global (US company, EU processing) | Yes |
|
||||
| **Vercel Inc.** | Analytics | US | Yes |
|
||||
|
||||
### 7.2 We Never:
|
||||
- Sell your personal data
|
||||
- Share data with advertisers
|
||||
- Transfer data without appropriate safeguards
|
||||
|
||||
### 7.3 International Transfers
|
||||
|
||||
Some of our service providers are based outside the EU/EEA. When we transfer data internationally, we ensure appropriate safeguards such as:
|
||||
- EU Standard Contractual Clauses (SCCs)
|
||||
- Data Processing Agreements
|
||||
- Adequacy decisions where applicable
|
||||
|
||||
## 8. Data Retention
|
||||
|
||||
We retain your personal data only for as long as necessary:
|
||||
|
||||
| Data Type | Retention Period |
|
||||
|-----------|-----------------|
|
||||
| Contact form submissions | 2 years |
|
||||
| Newsletter subscriptions | Until you unsubscribe + 30 days |
|
||||
| Analytics data | 14 months |
|
||||
| Server logs | 14 days |
|
||||
|
||||
## 9. Your Rights Under GDPR
|
||||
|
||||
You have the following rights regarding your personal data:
|
||||
|
||||
### 9.1 Right of Access (Art. 15)
|
||||
Request a copy of your personal data we hold.
|
||||
|
||||
### 9.2 Right to Rectification (Art. 16)
|
||||
Request correction of inaccurate or incomplete data.
|
||||
|
||||
### 9.3 Right to Erasure (Art. 17)
|
||||
Request deletion of your data ("right to be forgotten").
|
||||
|
||||
### 9.4 Right to Restrict Processing (Art. 18)
|
||||
Request limitation of how we process your data.
|
||||
|
||||
### 9.5 Right to Data Portability (Art. 20)
|
||||
Receive your data in a structured, commonly used format.
|
||||
|
||||
### 9.6 Right to Object (Art. 21)
|
||||
Object to processing based on legitimate interests, including profiling.
|
||||
|
||||
### 9.7 Right to Withdraw Consent (Art. 7)
|
||||
Withdraw consent at any time (does not affect prior lawful processing).
|
||||
|
||||
### 9.8 How to Exercise Your Rights
|
||||
|
||||
To exercise any of these rights, contact us at:
|
||||
- Email: [CONTACT_EMAIL]
|
||||
- Subject line: "GDPR Data Request - [Your Right]"
|
||||
|
||||
We will respond within **30 days** of receiving your request. We may ask for identification to verify your identity.
|
||||
|
||||
### 9.9 Right to Lodge a Complaint
|
||||
|
||||
If you believe we have violated your data protection rights, you have the right to lodge a complaint with a supervisory authority. Since our hosting is in Germany, you may contact:
|
||||
|
||||
**Landesbeauftragte für den Datenschutz und die Informationsfreiheit Baden-Württemberg**
|
||||
Website: https://www.baden-wuerttemberg.datenschutz.de/
|
||||
|
||||
Or your local data protection authority.
|
||||
|
||||
## 10. Security Measures
|
||||
|
||||
We implement appropriate technical and organizational measures to protect your data:
|
||||
|
||||
- **Encryption**: All data transmitted via HTTPS/TLS
|
||||
- **Access Controls**: Limited access to personal data
|
||||
- **Infrastructure Security**: ISO 27001 certified data centers (netcup/Anexia)
|
||||
- **Regular Updates**: Security patches and updates applied promptly
|
||||
|
||||
## 11. Children's Privacy
|
||||
|
||||
Our Site is not intended for children under 16 years of age. We do not knowingly collect personal data from children. If you believe we have collected data from a child, please contact us immediately.
|
||||
|
||||
## 12. Changes to This Privacy Policy
|
||||
|
||||
We may update this Privacy Policy from time to time. We will notify you of any material changes by:
|
||||
- Posting the new policy on this page
|
||||
- Updating the "Last Updated" date
|
||||
- [Sending an email notification for significant changes]
|
||||
|
||||
## 13. Contact Us
|
||||
|
||||
If you have any questions about this Privacy Policy or our data practices, please contact us:
|
||||
|
||||
**Jeff Emmett**
|
||||
Email: [CONTACT_EMAIL]
|
||||
Website: [WEBSITE_URL]
|
||||
|
||||
---
|
||||
|
||||
## Appendix A: Specific Processing Activities for [WEBSITE_NAME]
|
||||
|
||||
### Data Processing Summary
|
||||
|
||||
**Categories of Data Subjects:**
|
||||
- [ ] Website visitors
|
||||
- [ ] Newsletter subscribers
|
||||
- [ ] Contact form users
|
||||
- [ ] Customers/clients
|
||||
- [ ] Other: _______________
|
||||
|
||||
**Categories of Personal Data:**
|
||||
- [ ] Name
|
||||
- [ ] Email address
|
||||
- [ ] IP address (anonymized)
|
||||
- [ ] Usage/analytics data
|
||||
- [ ] Other: _______________
|
||||
|
||||
**Special Categories of Data (Art. 9):**
|
||||
- [x] No special categories processed
|
||||
|
||||
---
|
||||
|
||||
*This privacy policy template is provided for informational purposes. Consider consulting with a legal professional to ensure full compliance with applicable laws.*
|
||||
|
|
@ -0,0 +1,221 @@
|
|||
# GDPR Compliance Kit
|
||||
|
||||
A comprehensive toolkit for making your websites GDPR compliant.
|
||||
|
||||
## Contents
|
||||
|
||||
```
|
||||
gdpr-compliance-kit/
|
||||
├── README.md # This file
|
||||
├── PRIVACY_POLICY_TEMPLATE.md # Customizable privacy policy
|
||||
├── NETCUP_DPA_ANNEX3_GUIDE.md # Guide for Netcup DPA form
|
||||
├── RECORDS_OF_PROCESSING_ACTIVITIES.md # ROPA template (Art. 30)
|
||||
└── components/
|
||||
├── CookieConsent.tsx # React cookie banner component
|
||||
└── GDPRAnalytics.tsx # Consent-aware analytics wrapper
|
||||
```
|
||||
|
||||
## Quick Start
|
||||
|
||||
### 1. Add Cookie Consent to Your Site
|
||||
|
||||
Copy the components to your Next.js project:
|
||||
|
||||
```bash
|
||||
cp components/CookieConsent.tsx your-project/components/
|
||||
cp components/GDPRAnalytics.tsx your-project/components/
|
||||
```
|
||||
|
||||
Add to your `app/layout.tsx`:
|
||||
|
||||
```tsx
|
||||
import { CookieConsent } from "@/components/CookieConsent";
|
||||
import { GDPRAnalytics } from "@/components/GDPRAnalytics";
|
||||
|
||||
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<body>
|
||||
{children}
|
||||
<CookieConsent />
|
||||
<GDPRAnalytics />
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
**Remove** the standard Analytics import from your layout:
|
||||
```diff
|
||||
- import { Analytics } from "@vercel/analytics/next";
|
||||
...
|
||||
- <Analytics />
|
||||
+ <GDPRAnalytics />
|
||||
```
|
||||
|
||||
### 2. Create Your Privacy Policy
|
||||
|
||||
1. Copy `PRIVACY_POLICY_TEMPLATE.md` to your project
|
||||
2. Replace all `[PLACEHOLDERS]` with your actual info:
|
||||
- `[DATE]` - Current date
|
||||
- `[WEBSITE_NAME]` - Your site name
|
||||
- `[WEBSITE_URL]` - Your domain
|
||||
- `[CONTACT_EMAIL]` - Your contact email
|
||||
3. Review and customize based on your specific data processing
|
||||
4. Add as a page at `/privacy` on your site
|
||||
|
||||
### 3. Complete Netcup DPA
|
||||
|
||||
1. Log into Netcup CCP
|
||||
2. Go to Master Data → Agreement on contract data processing
|
||||
3. Follow `NETCUP_DPA_ANNEX3_GUIDE.md` to fill out the form
|
||||
4. Submit the agreement
|
||||
5. Save a copy for your records
|
||||
|
||||
### 4. Maintain Records of Processing
|
||||
|
||||
1. Customize `RECORDS_OF_PROCESSING_ACTIVITIES.md` with your specific activities
|
||||
2. Store it securely (not publicly accessible)
|
||||
3. Review and update annually or when processing changes
|
||||
|
||||
## Component Details
|
||||
|
||||
### CookieConsent.tsx
|
||||
|
||||
Features:
|
||||
- Three-tier consent (Necessary, Analytics, Preferences)
|
||||
- Customizable appearance via Tailwind classes
|
||||
- Stores consent in localStorage
|
||||
- Provides hooks for checking consent status
|
||||
|
||||
**Custom styling:**
|
||||
The component uses Tailwind CSS classes. Customize by editing the className props.
|
||||
|
||||
**Integration with other consent managers:**
|
||||
The component stores consent in localStorage under `gdpr_consent`. You can read this from other parts of your app:
|
||||
|
||||
```tsx
|
||||
const consent = JSON.parse(localStorage.getItem('gdpr_consent') || '{}');
|
||||
if (consent.analytics) {
|
||||
// Load analytics
|
||||
}
|
||||
```
|
||||
|
||||
### GDPRAnalytics.tsx
|
||||
|
||||
Wraps Vercel Analytics and only loads it after consent:
|
||||
|
||||
```tsx
|
||||
// Instead of:
|
||||
import { Analytics } from "@vercel/analytics/react";
|
||||
<Analytics />
|
||||
|
||||
// Use:
|
||||
import { GDPRAnalytics } from "@/components/GDPRAnalytics";
|
||||
<GDPRAnalytics />
|
||||
```
|
||||
|
||||
For Google Analytics, use `GDPRGoogleAnalytics`:
|
||||
```tsx
|
||||
<GDPRGoogleAnalytics measurementId="G-XXXXXXXXXX" />
|
||||
```
|
||||
|
||||
### ConsentGate Component
|
||||
|
||||
Conditionally render content based on consent:
|
||||
|
||||
```tsx
|
||||
import { ConsentGate } from "@/components/CookieConsent";
|
||||
|
||||
function MyComponent() {
|
||||
return (
|
||||
<ConsentGate type="analytics" fallback={<p>Analytics disabled</p>}>
|
||||
<AnalyticsWidget />
|
||||
</ConsentGate>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Checklist
|
||||
|
||||
### Technical Implementation
|
||||
- [ ] Cookie consent banner added to all sites
|
||||
- [ ] Analytics wrapped with consent check
|
||||
- [ ] Privacy policy page created at `/privacy`
|
||||
- [ ] Cookie settings accessible from footer
|
||||
- [ ] Contact forms have privacy notice
|
||||
|
||||
### Legal/Administrative
|
||||
- [ ] Netcup DPA Annex 3 completed and submitted
|
||||
- [ ] Records of Processing Activities created
|
||||
- [ ] Privacy policy reviewed for accuracy
|
||||
- [ ] Data retention periods documented
|
||||
- [ ] Data breach response plan documented
|
||||
|
||||
### Ongoing Maintenance
|
||||
- [ ] Annual privacy policy review scheduled
|
||||
- [ ] ROPA review scheduled
|
||||
- [ ] Processor agreements reviewed
|
||||
- [ ] Staff awareness training (if applicable)
|
||||
|
||||
## Deployment Script
|
||||
|
||||
To add GDPR compliance to all your sites at once:
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
# deploy-gdpr.sh
|
||||
|
||||
SITES=(
|
||||
"mycofi-earth-website"
|
||||
"canvas-website"
|
||||
# Add more sites
|
||||
)
|
||||
|
||||
for site in "${SITES[@]}"; do
|
||||
echo "Adding GDPR components to $site..."
|
||||
cp components/CookieConsent.tsx /opt/websites/$site/components/
|
||||
cp components/GDPRAnalytics.tsx /opt/websites/$site/components/
|
||||
echo "Done with $site"
|
||||
done
|
||||
```
|
||||
|
||||
## FAQ
|
||||
|
||||
**Q: Do I need a cookie banner if I don't use cookies?**
|
||||
A: If you use ANY analytics (even Vercel Analytics), you should have consent. Many analytics tools set cookies or use similar tracking technologies.
|
||||
|
||||
**Q: What about Cloudflare's cookies?**
|
||||
A: Cloudflare sets some strictly necessary cookies for security (like `__cf_bm`). These don't require consent but should be mentioned in your privacy policy.
|
||||
|
||||
**Q: Do I need a DPO?**
|
||||
A: Likely not. A Data Protection Officer is required only if:
|
||||
- You're a public authority
|
||||
- Your core activities involve large-scale monitoring
|
||||
- You process special categories of data at large scale
|
||||
|
||||
**Q: What if someone requests data deletion?**
|
||||
A: You have 30 days to respond. Delete from:
|
||||
1. Your databases
|
||||
2. Backups (when they rotate)
|
||||
3. Notify any processors (they should delete too)
|
||||
|
||||
**Q: Is this enough for full GDPR compliance?**
|
||||
A: This covers the major technical requirements. Full compliance also requires:
|
||||
- Organizational measures (policies, training)
|
||||
- Proper contracts with processors
|
||||
- Breach response procedures
|
||||
- Ongoing compliance monitoring
|
||||
|
||||
Consider consulting a legal professional for your specific situation.
|
||||
|
||||
## Resources
|
||||
|
||||
- [GDPR Full Text](https://gdpr-info.eu/)
|
||||
- [ICO Guide to GDPR](https://ico.org.uk/for-organisations/guide-to-data-protection/guide-to-the-general-data-protection-regulation-gdpr/)
|
||||
- [Netcup DPA Information](https://www.netcup.de/dsgvo-gdpr-processing)
|
||||
- [Cloudflare GDPR](https://www.cloudflare.com/gdpr/introduction/)
|
||||
|
||||
## License
|
||||
|
||||
This kit is provided as-is for informational purposes. Not legal advice. Consult with a legal professional for your specific compliance needs.
|
||||
|
|
@ -0,0 +1,206 @@
|
|||
# Records of Processing Activities (ROPA)
|
||||
|
||||
**Data Controller:** Jeff Emmett
|
||||
**Last Updated:** [DATE]
|
||||
**Version:** 1.0
|
||||
|
||||
This document fulfills the requirement under GDPR Article 30 to maintain records of processing activities.
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
| Item | Details |
|
||||
|------|---------|
|
||||
| **Controller Name** | Jeff Emmett |
|
||||
| **Controller Address** | 23 Birchpark Dr, L3M 4M9 Grimsby, Canada |
|
||||
| **Contact Email** | [YOUR_EMAIL] |
|
||||
| **Data Protection Officer** | Not required (< 250 employees, no large-scale processing) |
|
||||
| **EU Representative** | Not required (processing not regular/large-scale) |
|
||||
|
||||
---
|
||||
|
||||
## Processing Activity 1: Website Hosting & Analytics
|
||||
|
||||
| Field | Details |
|
||||
|-------|---------|
|
||||
| **Activity Name** | Website Hosting and Analytics |
|
||||
| **Purpose** | Hosting websites, collecting anonymized usage analytics to improve user experience |
|
||||
| **Legal Basis** | Legitimate Interest (Art. 6(1)(f)) for basic hosting; Consent (Art. 6(1)(a)) for analytics |
|
||||
| **Data Subjects** | Website visitors |
|
||||
| **Personal Data Categories** | IP address (anonymized), browser type, pages visited, referrer URL, device type |
|
||||
| **Special Categories** | None |
|
||||
| **Data Sources** | Direct collection via website |
|
||||
| **Recipients** | netcup GmbH (hosting), Cloudflare Inc (CDN), Vercel Inc (analytics) |
|
||||
| **Third Country Transfers** | USA (Cloudflare, Vercel) - protected by SCCs/DPA |
|
||||
| **Retention Period** | Server logs: 14 days; Analytics: 14 months |
|
||||
| **Security Measures** | TLS encryption, access controls, ISO 27001 certified infrastructure |
|
||||
|
||||
### Websites Covered:
|
||||
- jeffemmett.com
|
||||
- mycofi.earth
|
||||
- bondingcurve.tech
|
||||
- convictionvoting.xyz
|
||||
- decolonizeti.me
|
||||
- [Add all your domains]
|
||||
|
||||
---
|
||||
|
||||
## Processing Activity 2: Newsletter Subscriptions
|
||||
|
||||
| Field | Details |
|
||||
|-------|---------|
|
||||
| **Activity Name** | Newsletter Management |
|
||||
| **Purpose** | Sending newsletters and updates to subscribers |
|
||||
| **Legal Basis** | Consent (Art. 6(1)(a)) - explicit opt-in |
|
||||
| **Data Subjects** | Newsletter subscribers |
|
||||
| **Personal Data Categories** | Email address, name (optional), subscription date, open/click tracking |
|
||||
| **Special Categories** | None |
|
||||
| **Data Sources** | Direct collection via subscription forms |
|
||||
| **Recipients** | Self-hosted (Listmonk on netcup infrastructure) |
|
||||
| **Third Country Transfers** | None (self-hosted in Germany) |
|
||||
| **Retention Period** | Until unsubscribe + 30 days |
|
||||
| **Security Measures** | TLS encryption, authentication required, database encryption |
|
||||
|
||||
### Consent Mechanism:
|
||||
- Double opt-in required
|
||||
- Clear unsubscribe link in every email
|
||||
- Consent records stored with timestamp
|
||||
|
||||
---
|
||||
|
||||
## Processing Activity 3: Contact Form Submissions
|
||||
|
||||
| Field | Details |
|
||||
|-------|---------|
|
||||
| **Activity Name** | Contact Form Processing |
|
||||
| **Purpose** | Responding to inquiries from website visitors |
|
||||
| **Legal Basis** | Legitimate Interest (Art. 6(1)(f)) / Pre-contractual measures (Art. 6(1)(b)) |
|
||||
| **Data Subjects** | People who submit contact forms |
|
||||
| **Personal Data Categories** | Name, email address, message content |
|
||||
| **Special Categories** | None |
|
||||
| **Data Sources** | Direct submission via website forms |
|
||||
| **Recipients** | Self-hosted email (or specify email provider) |
|
||||
| **Third Country Transfers** | Depends on email provider |
|
||||
| **Retention Period** | 2 years after last communication |
|
||||
| **Security Measures** | TLS encryption, spam filtering |
|
||||
|
||||
---
|
||||
|
||||
## Processing Activity 4: User Accounts (if applicable)
|
||||
|
||||
| Field | Details |
|
||||
|-------|---------|
|
||||
| **Activity Name** | User Account Management |
|
||||
| **Purpose** | Providing authenticated access to services |
|
||||
| **Legal Basis** | Contract performance (Art. 6(1)(b)) |
|
||||
| **Data Subjects** | Registered users |
|
||||
| **Personal Data Categories** | Email, username, hashed password, account settings |
|
||||
| **Special Categories** | None |
|
||||
| **Data Sources** | User registration |
|
||||
| **Recipients** | Self-hosted only |
|
||||
| **Third Country Transfers** | None |
|
||||
| **Retention Period** | Account lifetime + 30 days after deletion request |
|
||||
| **Security Measures** | Password hashing (bcrypt), session management, 2FA optional |
|
||||
|
||||
---
|
||||
|
||||
## Data Processors (Sub-processors)
|
||||
|
||||
| Processor | Service | Location | DPA Signed | Contact |
|
||||
|-----------|---------|----------|------------|---------|
|
||||
| netcup GmbH | Web hosting infrastructure | Germany | Yes (online) | support@netcup.de |
|
||||
| Cloudflare, Inc. | CDN, DNS, DDoS protection | USA (with EU options) | Yes (standard) | privacy@cloudflare.com |
|
||||
| Vercel Inc. | Web analytics | USA | Yes (ToS) | privacy@vercel.com |
|
||||
|
||||
---
|
||||
|
||||
## Technical and Organizational Measures (TOMs)
|
||||
|
||||
### Confidentiality
|
||||
- [x] TLS/SSL encryption for all websites
|
||||
- [x] Access controls for server infrastructure
|
||||
- [x] SSH key authentication (no password auth)
|
||||
- [x] Firewall and network segmentation
|
||||
|
||||
### Integrity
|
||||
- [x] Regular backups
|
||||
- [x] Version control for code
|
||||
- [x] Audit logging
|
||||
|
||||
### Availability
|
||||
- [x] Redundant infrastructure
|
||||
- [x] DDoS protection (Cloudflare)
|
||||
- [x] Monitoring and alerting
|
||||
|
||||
### Resilience
|
||||
- [x] Disaster recovery procedures
|
||||
- [x] Regular backup testing
|
||||
|
||||
---
|
||||
|
||||
## Data Subject Rights Procedures
|
||||
|
||||
### Access Requests (Art. 15)
|
||||
1. Receive request via email
|
||||
2. Verify identity
|
||||
3. Compile data within 30 days
|
||||
4. Provide data in machine-readable format
|
||||
|
||||
### Erasure Requests (Art. 17)
|
||||
1. Receive request via email
|
||||
2. Verify identity
|
||||
3. Delete from: databases, backups (when rotated), analytics
|
||||
4. Confirm deletion within 30 days
|
||||
|
||||
### Portability Requests (Art. 20)
|
||||
1. Receive request via email
|
||||
2. Verify identity
|
||||
3. Export data as JSON/CSV
|
||||
4. Provide within 30 days
|
||||
|
||||
---
|
||||
|
||||
## Data Breach Response Plan
|
||||
|
||||
### Detection
|
||||
- Monitoring systems in place
|
||||
- Log analysis for anomalies
|
||||
|
||||
### Assessment (within 24 hours)
|
||||
1. Identify scope of breach
|
||||
2. Assess risk to data subjects
|
||||
3. Document findings
|
||||
|
||||
### Notification (within 72 hours if required)
|
||||
1. Notify supervisory authority if risk to rights/freedoms
|
||||
2. Notify affected individuals if high risk
|
||||
3. Document all actions
|
||||
|
||||
### Recovery
|
||||
1. Contain breach
|
||||
2. Remediate vulnerabilities
|
||||
3. Review and update security measures
|
||||
|
||||
---
|
||||
|
||||
## Review Schedule
|
||||
|
||||
| Review Type | Frequency | Last Review | Next Review |
|
||||
|-------------|-----------|-------------|-------------|
|
||||
| ROPA Update | Annually | [DATE] | [DATE + 1 year] |
|
||||
| Security Audit | Annually | [DATE] | [DATE + 1 year] |
|
||||
| Processor Review | Annually | [DATE] | [DATE + 1 year] |
|
||||
| Privacy Policy Review | Annually | [DATE] | [DATE + 1 year] |
|
||||
|
||||
---
|
||||
|
||||
## Change Log
|
||||
|
||||
| Date | Version | Changes | Author |
|
||||
|------|---------|---------|--------|
|
||||
| [DATE] | 1.0 | Initial creation | Jeff Emmett |
|
||||
|
||||
---
|
||||
|
||||
*This document should be kept up to date and reviewed at least annually or whenever there are significant changes to processing activities.*
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
project_name: "GDPR Compliance Kit"
|
||||
default_status: "To Do"
|
||||
statuses: ["To Do", "In Progress", "Done"]
|
||||
labels: []
|
||||
milestones: []
|
||||
date_format: yyyy-mm-dd
|
||||
max_column_width: 20
|
||||
default_editor: "nvim"
|
||||
auto_open_browser: true
|
||||
default_port: 6420
|
||||
remote_operations: true
|
||||
auto_commit: false
|
||||
bypass_git_hooks: false
|
||||
check_active_branches: true
|
||||
active_branch_days: 30
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
---
|
||||
id: task-high.1
|
||||
title: Deploy GDPR compliance to all websites
|
||||
status: To Do
|
||||
assignee: []
|
||||
created_date: '2025-12-11 18:04'
|
||||
labels: []
|
||||
dependencies: []
|
||||
parent_task_id: task-high
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
Deploy cookie consent banners, privacy policies, and GDPR-compliant analytics to all hosted websites. Complete Netcup DPA Annex 3 form.
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
|
||||
## Acceptance Criteria
|
||||
<!-- AC:BEGIN -->
|
||||
- [ ] #1 Cookie consent component deployed to all sites
|
||||
- [ ] #2 Privacy policy pages created for each domain
|
||||
- [ ] #3 Netcup DPA Annex 3 form completed and submitted
|
||||
- [ ] #4 Analytics wrapped with consent checks
|
||||
- [ ] #5 ROPA document stored securely
|
||||
<!-- AC:END -->
|
||||
|
|
@ -0,0 +1,275 @@
|
|||
"use client";
|
||||
|
||||
import { useState, useEffect } from "react";
|
||||
import Link from "next/link";
|
||||
|
||||
type ConsentState = {
|
||||
necessary: boolean;
|
||||
analytics: boolean;
|
||||
preferences: boolean;
|
||||
timestamp: string;
|
||||
};
|
||||
|
||||
const CONSENT_COOKIE_NAME = "gdpr_consent";
|
||||
const CONSENT_VERSION = "1.0";
|
||||
|
||||
export function CookieConsent() {
|
||||
const [showBanner, setShowBanner] = useState(false);
|
||||
const [showDetails, setShowDetails] = useState(false);
|
||||
const [consent, setConsent] = useState<ConsentState>({
|
||||
necessary: true, // Always required
|
||||
analytics: false,
|
||||
preferences: false,
|
||||
timestamp: "",
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
// Check if consent has already been given
|
||||
const savedConsent = localStorage.getItem(CONSENT_COOKIE_NAME);
|
||||
if (savedConsent) {
|
||||
try {
|
||||
const parsed = JSON.parse(savedConsent);
|
||||
setConsent(parsed);
|
||||
// Apply saved consent settings
|
||||
applyConsent(parsed);
|
||||
} catch {
|
||||
// Invalid consent data, show banner
|
||||
setShowBanner(true);
|
||||
}
|
||||
} else {
|
||||
// No consent yet, show banner
|
||||
setShowBanner(true);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const applyConsent = (consentState: ConsentState) => {
|
||||
// Enable/disable analytics based on consent
|
||||
if (consentState.analytics) {
|
||||
// Enable analytics (e.g., Vercel Analytics, Plausible, etc.)
|
||||
window.localStorage.setItem("va_disabled", "false");
|
||||
// If using Google Analytics, you would enable it here
|
||||
// window.gtag?.('consent', 'update', { analytics_storage: 'granted' });
|
||||
} else {
|
||||
// Disable analytics
|
||||
window.localStorage.setItem("va_disabled", "true");
|
||||
// window.gtag?.('consent', 'update', { analytics_storage: 'denied' });
|
||||
}
|
||||
};
|
||||
|
||||
const saveConsent = (consentState: ConsentState) => {
|
||||
const consentWithTimestamp = {
|
||||
...consentState,
|
||||
timestamp: new Date().toISOString(),
|
||||
version: CONSENT_VERSION,
|
||||
};
|
||||
localStorage.setItem(CONSENT_COOKIE_NAME, JSON.stringify(consentWithTimestamp));
|
||||
setConsent(consentWithTimestamp);
|
||||
applyConsent(consentWithTimestamp);
|
||||
setShowBanner(false);
|
||||
};
|
||||
|
||||
const acceptAll = () => {
|
||||
saveConsent({
|
||||
necessary: true,
|
||||
analytics: true,
|
||||
preferences: true,
|
||||
timestamp: "",
|
||||
});
|
||||
};
|
||||
|
||||
const rejectAll = () => {
|
||||
saveConsent({
|
||||
necessary: true,
|
||||
analytics: false,
|
||||
preferences: false,
|
||||
timestamp: "",
|
||||
});
|
||||
};
|
||||
|
||||
const saveCustom = () => {
|
||||
saveConsent(consent);
|
||||
};
|
||||
|
||||
if (!showBanner) return null;
|
||||
|
||||
return (
|
||||
<div
|
||||
className="fixed bottom-0 left-0 right-0 z-50 p-4 bg-background/95 backdrop-blur-sm border-t border-border shadow-lg"
|
||||
role="dialog"
|
||||
aria-label="Cookie consent"
|
||||
aria-modal="true"
|
||||
>
|
||||
<div className="max-w-4xl mx-auto">
|
||||
{!showDetails ? (
|
||||
// Simple banner view
|
||||
<div className="flex flex-col sm:flex-row items-start sm:items-center justify-between gap-4">
|
||||
<div className="flex-1">
|
||||
<h2 className="text-lg font-semibold mb-1">We value your privacy</h2>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
We use cookies to improve your experience and analyze site usage.
|
||||
You can choose which cookies to accept.{" "}
|
||||
<Link href="/privacy" className="underline hover:text-foreground">
|
||||
Learn more
|
||||
</Link>
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<button
|
||||
onClick={() => setShowDetails(true)}
|
||||
className="px-4 py-2 text-sm border border-border rounded-md hover:bg-accent transition-colors"
|
||||
>
|
||||
Customize
|
||||
</button>
|
||||
<button
|
||||
onClick={rejectAll}
|
||||
className="px-4 py-2 text-sm border border-border rounded-md hover:bg-accent transition-colors"
|
||||
>
|
||||
Reject All
|
||||
</button>
|
||||
<button
|
||||
onClick={acceptAll}
|
||||
className="px-4 py-2 text-sm bg-primary text-primary-foreground rounded-md hover:bg-primary/90 transition-colors"
|
||||
>
|
||||
Accept All
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
// Detailed settings view
|
||||
<div>
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<h2 className="text-lg font-semibold">Cookie Settings</h2>
|
||||
<button
|
||||
onClick={() => setShowDetails(false)}
|
||||
className="text-muted-foreground hover:text-foreground"
|
||||
aria-label="Close details"
|
||||
>
|
||||
<svg className="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="space-y-4 mb-6">
|
||||
{/* Necessary Cookies */}
|
||||
<div className="flex items-start justify-between p-3 bg-accent/50 rounded-lg">
|
||||
<div className="flex-1">
|
||||
<div className="flex items-center gap-2">
|
||||
<h3 className="font-medium">Strictly Necessary</h3>
|
||||
<span className="text-xs bg-primary/20 text-primary px-2 py-0.5 rounded">Always Active</span>
|
||||
</div>
|
||||
<p className="text-sm text-muted-foreground mt-1">
|
||||
Essential for the website to function properly. These cannot be disabled.
|
||||
</p>
|
||||
</div>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={true}
|
||||
disabled
|
||||
className="mt-1 h-4 w-4 rounded border-border"
|
||||
aria-label="Necessary cookies (always enabled)"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Analytics Cookies */}
|
||||
<div className="flex items-start justify-between p-3 border border-border rounded-lg">
|
||||
<div className="flex-1">
|
||||
<h3 className="font-medium">Analytics</h3>
|
||||
<p className="text-sm text-muted-foreground mt-1">
|
||||
Help us understand how visitors interact with our website by collecting anonymous usage data.
|
||||
</p>
|
||||
</div>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={consent.analytics}
|
||||
onChange={(e) => setConsent({ ...consent, analytics: e.target.checked })}
|
||||
className="mt-1 h-4 w-4 rounded border-border cursor-pointer"
|
||||
aria-label="Enable analytics cookies"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Preference Cookies */}
|
||||
<div className="flex items-start justify-between p-3 border border-border rounded-lg">
|
||||
<div className="flex-1">
|
||||
<h3 className="font-medium">Preferences</h3>
|
||||
<p className="text-sm text-muted-foreground mt-1">
|
||||
Remember your settings and preferences like theme choice and language.
|
||||
</p>
|
||||
</div>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={consent.preferences}
|
||||
onChange={(e) => setConsent({ ...consent, preferences: e.target.checked })}
|
||||
className="mt-1 h-4 w-4 rounded border-border cursor-pointer"
|
||||
aria-label="Enable preference cookies"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-wrap gap-2 justify-end">
|
||||
<button
|
||||
onClick={rejectAll}
|
||||
className="px-4 py-2 text-sm border border-border rounded-md hover:bg-accent transition-colors"
|
||||
>
|
||||
Reject All
|
||||
</button>
|
||||
<button
|
||||
onClick={saveCustom}
|
||||
className="px-4 py-2 text-sm bg-primary text-primary-foreground rounded-md hover:bg-primary/90 transition-colors"
|
||||
>
|
||||
Save Preferences
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Hook to check consent status from other components
|
||||
export function useGDPRConsent() {
|
||||
const [consent, setConsent] = useState<ConsentState | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const savedConsent = localStorage.getItem(CONSENT_COOKIE_NAME);
|
||||
if (savedConsent) {
|
||||
try {
|
||||
setConsent(JSON.parse(savedConsent));
|
||||
} catch {
|
||||
setConsent(null);
|
||||
}
|
||||
}
|
||||
}, []);
|
||||
|
||||
return {
|
||||
hasConsented: consent !== null,
|
||||
analyticsEnabled: consent?.analytics ?? false,
|
||||
preferencesEnabled: consent?.preferences ?? false,
|
||||
resetConsent: () => {
|
||||
localStorage.removeItem(CONSENT_COOKIE_NAME);
|
||||
window.location.reload();
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
// Component to conditionally render based on consent
|
||||
export function ConsentGate({
|
||||
children,
|
||||
type,
|
||||
fallback,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
type: "analytics" | "preferences";
|
||||
fallback?: React.ReactNode;
|
||||
}) {
|
||||
const { analyticsEnabled, preferencesEnabled } = useGDPRConsent();
|
||||
|
||||
const hasConsent = type === "analytics" ? analyticsEnabled : preferencesEnabled;
|
||||
|
||||
if (!hasConsent) {
|
||||
return fallback || null;
|
||||
}
|
||||
|
||||
return <>{children}</>;
|
||||
}
|
||||
|
|
@ -0,0 +1,122 @@
|
|||
"use client";
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
import { Analytics } from "@vercel/analytics/react";
|
||||
|
||||
const CONSENT_COOKIE_NAME = "gdpr_consent";
|
||||
|
||||
/**
|
||||
* GDPR-Compliant Analytics Wrapper
|
||||
*
|
||||
* This component wraps Vercel Analytics (or any analytics provider)
|
||||
* and only loads it if the user has consented to analytics cookies.
|
||||
*
|
||||
* Usage in your layout.tsx:
|
||||
*
|
||||
* import { GDPRAnalytics } from "@/components/GDPRAnalytics";
|
||||
*
|
||||
* export default function RootLayout({ children }) {
|
||||
* return (
|
||||
* <html>
|
||||
* <body>
|
||||
* {children}
|
||||
* <GDPRAnalytics />
|
||||
* </body>
|
||||
* </html>
|
||||
* );
|
||||
* }
|
||||
*/
|
||||
export function GDPRAnalytics() {
|
||||
const [analyticsEnabled, setAnalyticsEnabled] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const checkConsent = () => {
|
||||
const savedConsent = localStorage.getItem(CONSENT_COOKIE_NAME);
|
||||
if (savedConsent) {
|
||||
try {
|
||||
const parsed = JSON.parse(savedConsent);
|
||||
setAnalyticsEnabled(parsed.analytics === true);
|
||||
} catch {
|
||||
setAnalyticsEnabled(false);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Check on mount
|
||||
checkConsent();
|
||||
|
||||
// Listen for consent changes
|
||||
const handleStorageChange = (e: StorageEvent) => {
|
||||
if (e.key === CONSENT_COOKIE_NAME) {
|
||||
checkConsent();
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener("storage", handleStorageChange);
|
||||
return () => window.removeEventListener("storage", handleStorageChange);
|
||||
}, []);
|
||||
|
||||
// Only render Analytics if user has consented
|
||||
if (!analyticsEnabled) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return <Analytics />;
|
||||
}
|
||||
|
||||
/**
|
||||
* Alternative: Google Analytics 4 GDPR wrapper
|
||||
*
|
||||
* If you use Google Analytics instead of Vercel Analytics,
|
||||
* use this component.
|
||||
*/
|
||||
export function GDPRGoogleAnalytics({ measurementId }: { measurementId: string }) {
|
||||
const [analyticsEnabled, setAnalyticsEnabled] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const savedConsent = localStorage.getItem(CONSENT_COOKIE_NAME);
|
||||
if (savedConsent) {
|
||||
try {
|
||||
const parsed = JSON.parse(savedConsent);
|
||||
setAnalyticsEnabled(parsed.analytics === true);
|
||||
} catch {
|
||||
setAnalyticsEnabled(false);
|
||||
}
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (!analyticsEnabled) return;
|
||||
|
||||
// Load Google Analytics script dynamically
|
||||
const script = document.createElement("script");
|
||||
script.src = `https://www.googletagmanager.com/gtag/js?id=${measurementId}`;
|
||||
script.async = true;
|
||||
document.head.appendChild(script);
|
||||
|
||||
// Initialize gtag
|
||||
window.dataLayer = window.dataLayer || [];
|
||||
function gtag(...args: unknown[]) {
|
||||
window.dataLayer.push(args);
|
||||
}
|
||||
gtag("js", new Date());
|
||||
gtag("config", measurementId, {
|
||||
anonymize_ip: true, // GDPR requirement
|
||||
cookie_flags: "SameSite=None;Secure",
|
||||
});
|
||||
|
||||
return () => {
|
||||
document.head.removeChild(script);
|
||||
};
|
||||
}, [analyticsEnabled, measurementId]);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// Type declaration for Google Analytics
|
||||
declare global {
|
||||
interface Window {
|
||||
dataLayer: unknown[];
|
||||
gtag?: (...args: unknown[]) => void;
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue