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:
Jeff Emmett 2025-12-11 13:09:11 -05:00
commit 3e926fee0e
8 changed files with 1247 additions and 0 deletions

154
NETCUP_DPA_ANNEX3_GUIDE.md Normal file
View File

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

229
PRIVACY_POLICY_TEMPLATE.md Normal file
View File

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

221
README.md Normal file
View File

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

View File

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

15
backlog/config.yml Normal file
View File

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

View File

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

View File

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

View File

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