Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,25 @@ CMS_API_TOKEN=your_strapi_api_token
# Generate with: openssl rand -hex 32
ALTCHA_HMAC_SECRET=your_altcha_hmac_secret

##################################
# ******* Consent / c15t ******** #
##################################

# Operating mode for the c15t consent manager.
# - "offline" (default) stores consent locally only and uses bundled offline policies
# - "hosted" persists consent records to a hosted c15t backend (consent.io)
# - "c15t" alias of "hosted" for legacy installations
NEXT_PUBLIC_C15T_MODE=offline

# Only relevant in "hosted" mode. URL of your c15t backend (e.g. via a Next.js
# rewrite like `/api/c15t` or a direct consent.io endpoint).
NEXT_PUBLIC_C15T_BACKEND_URL=/api/c15t

# Google Analytics 4 / gtag.js measurement ID (starts with G-, AW- or DC-).
# When unset, the gtag script is not loaded at all. When set, it is loaded
# only after the user consents to the `measurement` category.
NEXT_PUBLIC_GA_ID=

##################################
# ** Strapi CMS .env (reference) #
##################################
Expand Down
2 changes: 2 additions & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ const customJestConfig = {
'^@/(.*)$': '<rootDir>/src/$1',
'^~/(.*)$': '<rootDir>/public/$1',
'^.+\\.(svg)$': '<rootDir>/src/__mocks__/svg.tsx',
'^@c15t/nextjs$': '<rootDir>/src/__mocks__/c15t-nextjs.tsx',
'^@c15t/scripts/.*$': '<rootDir>/src/__mocks__/c15t-scripts.ts',
},
};

Expand Down
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@
]
},
"dependencies": {
"@c15t/nextjs": "2.0.0",
"@c15t/scripts": "2.0.0",
"@hookform/resolvers": "5.2.2",
"@react-email/components": "1.0.12",
"@strapi/blocks-react-renderer": "1.0.2",
Expand Down Expand Up @@ -90,6 +92,7 @@
"jest-environment-jsdom": "30.3.0",
"lint-staged": "16.4.0",
"postcss": "8.5.10",
"postcss-import": "16.1.1",
"prettier": "3.8.3",
"prettier-plugin-tailwindcss": "0.7.2",
"tailwindcss": "3.4.19",
Expand Down
154 changes: 145 additions & 9 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions postcss.config.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
module.exports = {
plugins: {
'postcss-import': {},
tailwindcss: {},
autoprefixer: {},
},
Expand Down
27 changes: 27 additions & 0 deletions src/__mocks__/c15t-nextjs.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/**
* Jest mock for `@c15t/nextjs`.
*
* The real package pulls in Next.js server-only modules (via
* `next/cache`) which are not available inside `jest-environment-jsdom`.
* This mock provides the minimal surface used by our own components.
*/
import type { ReactNode } from 'react';

export const ConsentManagerProvider = ({
children,
}: {
children: ReactNode;
}) => <>{children}</>;

export const ConsentBanner = () => null;
export const ConsentDialog = () => null;
export const ConsentWidget = () => null;

export const useConsentManager = () => ({
setActiveUI: () => undefined,
saveConsents: () => undefined,
resetConsents: () => undefined,
gdprConsents: {},
});

export type Theme = Record<string, unknown>;
25 changes: 25 additions & 0 deletions src/__mocks__/c15t-scripts.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/**
* Jest mock for `@c15t/scripts/*` helper packages (e.g. `google-tag`).
*
* Returns a plain `Script`-shaped object so that any consumer calling
* `gtag(...)` or similar factory keeps working in the test environment.
*/
export const gtag = (options: { id: string; category: string }) => ({
id: `gtag-${options.id}`,
category: options.category,
src: `https://www.googletagmanager.com/gtag/js?id=${options.id}`,
});

const scriptFactory = (name: string) => () => ({
id: name,
category: 'measurement',
});

export const metaPixel = scriptFactory('meta-pixel');
export const googleTagManager = scriptFactory('google-tag-manager');
export const linkedinInsights = scriptFactory('linkedin-insights');
export const microsoftUet = scriptFactory('microsoft-uet');
export const tiktokPixel = scriptFactory('tiktok-pixel');
export const xPixel = scriptFactory('x-pixel');
export const posthog = scriptFactory('posthog');
export const databuddy = scriptFactory('databuddy');
13 changes: 13 additions & 0 deletions src/app/cookies/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { Metadata } from 'next';

import CookiePolicy from '@/components/templates/CookiePolicy';

export const metadata: Metadata = {
title: 'Cookie Policy, Project Sentiment (dot) org',
description:
'Cookie policy and transparent consent control center for the SENTIMENT research project. Part of the German government\'s research framework program on IT security "Digital. Secure. Sovereign".',
};

export default function CookiesPage() {
return <CookiePolicy />;
}
Loading