Accept cookies#61#62
Conversation
…and query based on who keys.
…ling service runs, and make cookieconsent CSS work.
There was a problem hiding this comment.
Pull request overview
Adds cookie consent handling, consent persistence, and conditional analytics loading to support user cookie preferences.
Changes:
- Adds a
Consentmodel, socket API, and tests for storing consent decisions. - Adds a React cookie consent component and integrates it into the main app.
- Removes server-rendered Google Analytics injection and updates CSP/package dependencies for cookie consent assets.
Reviewed changes
Copilot reviewed 10 out of 11 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
package.json |
Adds vanilla-cookieconsent dependency. |
package-lock.json |
Locks the new cookie consent dependency. |
app/socket-apis/save-consent.js |
Adds socket handler for saving consent records. |
app/socket-apis/__tests__/save-consent.js |
Adds tests for the consent socket handler. |
app/server/the-civil-server.js |
Allows the cookie consent stylesheet in CSP. |
app/server/routes/set-user-cookie.js |
Gates synuser cookie behavior on consent. |
app/server/routes/server-react-render.jsx |
Removes server-side Google Analytics injection. |
app/models/consent.js |
Adds consent persistence model and update helpers. |
app/models/__tests__/consent.js |
Adds model tests for consent creation/history. |
app/components/enciv-cookies.js |
Adds cookie consent UI and analytics start/stop logic. |
app/components/app.jsx |
Integrates cookie consent component into the app. |
Comments suppressed due to low confidence (2)
app/socket-apis/tests/save-consent.js:53
- This invocation has the same argument ordering issue:
.call(synuser, consentData, cb)does not pass asynuserargument tosaveConsent, so the Jest callback is never used and the test will fail before it verifies the update path. Include the socket context and the handler arguments separately.
await saveConsent.call(
synuser,
[
{
category: 'ConsentOption2',
app/socket-apis/tests/save-consent.js:91
- This invocation passes the consent data as the
synuserparameter and the Jest mock asformattedConsentData, so the handler calls its default callback rather thancb. To exercise the no-identifier path, passnullas the first function argument after thethiscontext.
await saveConsent.call(
null,
[
{
category: 'ConsentOption1',
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
ef8c7a6 to
dd9dd1c
Compare
| function startAnalytics() { | ||
| if (!window.process.env.GOOGLE_ANALYTICS) return | ||
| if (document.getElementById('googletagmanager')) return | ||
|
|
||
| window.dataLayer = window.dataLayer || [] | ||
| window.gtag = function () { | ||
| window.dataLayer.push(arguments) | ||
| } | ||
| window.gtag('js', new Date()) | ||
| window.gtag('config', `${window.process.env.GOOGLE_ANALYTICS}`) | ||
|
|
||
| const script = document.createElement('script') | ||
| script.src = `https://www.googletagmanager.com/gtag/js?id=${window.process.env.GOOGLE_ANALYTICS}` | ||
| script.id = 'googletagmanager' | ||
| script.async = true | ||
| document.head.appendChild(script) | ||
| console.log('Starting analytics') | ||
| } |
There was a problem hiding this comment.
Add a comment - The BrowserEnv component injects ENV selected vars from the server side to the client side.
| if (!hasRequiredCookieConsent(req)) { | ||
| res.clearCookie('synuser') | ||
| next() | ||
| return | ||
| } |
There was a problem hiding this comment.
Okay, this is good news. We don't need to revoke the synuser cookie. We just need to revoke the cookies for google analytics and other things.
We should also look at enciv-home. It adds cookies for google adds. We need an interface so that Google Ads cookie code can become part of cookie consent with out having to change the code in civil-server.
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
|
@copilot resolve the merge conflicts in this pull request |
…rver into accept-cookies#61
# Conflicts: # app/server/the-civil-server.js
Merge conflicts resolved in commit |
| styleSrc: [ | ||
| "'self'", | ||
| "'unsafe-inline'", | ||
| '*.googleapis.com', | ||
| 'https://cdn.jsdelivr.net/gh/orestbida/cookieconsent@3.0.1/dist/cookieconsent.css', | ||
| ], |
| "stream-browserify": "^3.0.0", | ||
| "superagent": "^5.3.1" | ||
| "superagent": "^5.3.1", | ||
| "vanilla-cookieconsent": "^3.0.1" |
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
| function stopAnalytics() { | ||
| console.log('Stopping analytics') | ||
|
|
||
| // Clear Google Analytics cookies for this session | ||
| ;['_ga', '_gid', '_gat'].forEach(name => { | ||
| document.cookie = `${name}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;` | ||
| }) | ||
|
|
||
| delete window.dataLayer | ||
| delete window.gtag | ||
| const gtmElement = document.getElementById('googletagmanager') | ||
| if (gtmElement) gtmElement.remove() | ||
|
|
||
| // Full opt-out requires a browser refresh to prevent GA from re-initializing and setting new cookies | ||
| alert('Analytics has been disabled. Please refresh your browser to fully complete the opt-out.') | ||
|
|
||
| // Static consent configuration — independent of component state | ||
| const modalSections = { |
| function parseConsentCookie(consentCookie) { | ||
| if (!consentCookie) return undefined | ||
| if (typeof consentCookie === 'object') return consentCookie | ||
|
|
||
| try { | ||
| return JSON.parse(consentCookie) | ||
| } catch (error) { | ||
| try { | ||
| return JSON.parse(decodeURIComponent(consentCookie)) | ||
| } catch (err) { | ||
| return undefined | ||
| } | ||
| } | ||
| } | ||
|
|
||
| function hasRequiredCookieConsent(req) { | ||
| const consent = parseConsentCookie(req.cookies && req.cookies.cc_cookie) | ||
| return !!(consent && Array.isArray(consent.categories) && consent.categories.includes('necessary')) | ||
| } |
| styleSrc: [ | ||
| "'self'", | ||
| "'unsafe-inline'", | ||
| '*.googleapis.com', | ||
| 'https://cdn.jsdelivr.net/gh/orestbida/cookieconsent@3.0.1/dist/cookieconsent.css', | ||
| ], |
| const Joi = require('@hapi/joi') | ||
| const { Mongo, Collection } = require('@enciv/mongo-collections') | ||
| const { ObjectId } = require('mongodb') |
| ipAddress: Joi.string() | ||
| .ip({ | ||
| version: ['ipv4', 'ipv6'], | ||
| cidr: 'optional', | ||
| }) | ||
| .optional() | ||
| .allow(''), | ||
| }) |
| } else { | ||
| console.log('No document to update provided.') | ||
| } |
| <ErrorBoundary> | ||
| <div style={{ position: 'relative' }}> | ||
| <Helmet> | ||
| <title>{iota?.subject || 'Candiate Conversations'}</title> |
| }, | ||
| }, | ||
| }) | ||
| }) |
|
|
||
| import Consent from '../models/consent' | ||
|
|
||
| async function saveConsent(formattedConsentData, cb = () => {}) { |
| return cb({ created: false }) | ||
| } |
No description provided.