Skip to content

Commit 1562a5d

Browse files
committed
Add CookieConsent component
1 parent e197984 commit 1562a5d

File tree

10 files changed

+186
-48
lines changed

10 files changed

+186
-48
lines changed

client/constants.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,3 +145,5 @@ export const STOP_LOADING = 'STOP_LOADING';
145145

146146
export const START_SAVING_PROJECT = 'START_SAVING_PROJECT';
147147
export const END_SAVING_PROJECT = 'END_SAVING_PROJECT';
148+
149+
export const SET_COOKIE_CONSENT = 'SET_COOKIE_CONSENT';

client/modules/App/App.jsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import getConfig from '../../utils/getConfig';
55
import DevTools from './components/DevTools';
66
import { setPreviousPath } from '../IDE/actions/ide';
77
import { setLanguage } from '../IDE/actions/preferences';
8+
import CookieConsent from '../User/components/CookieConsent';
89

910
class App extends React.Component {
1011
constructor(props, context) {
@@ -41,6 +42,7 @@ class App extends React.Component {
4142
render() {
4243
return (
4344
<div className="app">
45+
<CookieConsent />
4446
{this.state.isMounted &&
4547
!window.devToolsExtension &&
4648
getConfig('NODE_ENV') === 'development' && <DevTools />}

client/modules/User/actions.js

Lines changed: 12 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,6 @@ export function loginUser(formValues) {
2020
return apiClient.post('/login', formValues);
2121
}
2222

23-
export function loginUserSuccess(user) {
24-
return {
25-
type: ActionTypes.AUTH_USER,
26-
user
27-
};
28-
}
29-
3023
export function authenticateUser(user) {
3124
return {
3225
type: ActionTypes.AUTH_USER,
@@ -55,7 +48,7 @@ export function validateAndLoginUser(formProps) {
5548
return new Promise((resolve) => {
5649
loginUser(formProps)
5750
.then((response) => {
58-
dispatch(loginUserSuccess(response.data));
51+
dispatch(authenticateUser(response.data));
5952
dispatch(setPreferences(response.data.preferences));
6053
dispatch(
6154
setLanguage(response.data.preferences.language, {
@@ -102,10 +95,7 @@ export function getUser() {
10295
apiClient
10396
.get('/session')
10497
.then((response) => {
105-
dispatch({
106-
type: ActionTypes.AUTH_USER,
107-
user: response.data
108-
});
98+
dispatch(authenticateUser(response.data));
10999
dispatch({
110100
type: ActionTypes.SET_PREFERENCES,
111101
preferences: response.data.preferences
@@ -259,7 +249,7 @@ export function updatePassword(formValues, token) {
259249
apiClient
260250
.post(`/reset-password/${token}`, formValues)
261251
.then((response) => {
262-
dispatch(loginUserSuccess(response.data));
252+
dispatch(authenticateUser(response.data));
263253
browserHistory.push('/');
264254
resolve();
265255
})
@@ -339,10 +329,7 @@ export function unlinkService(service) {
339329
apiClient
340330
.delete(`/auth/${service}`)
341331
.then((response) => {
342-
dispatch({
343-
type: ActionTypes.AUTH_USER,
344-
user: response.data
345-
});
332+
dispatch(authenticateUser(response.data));
346333
})
347334
.catch((error) => {
348335
const { response } = error;
@@ -351,3 +338,11 @@ export function unlinkService(service) {
351338
});
352339
};
353340
}
341+
342+
export function setUserCookieConsent(cookieConsent) {
343+
// maybe also send this to the server rn?
344+
return {
345+
type: ActionTypes.SET_COOKIE_CONSENT,
346+
cookieConsent
347+
};
348+
}
Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
import React, { useState, useEffect } from 'react';
2+
import { useSelector, useDispatch } from 'react-redux';
3+
import Cookies from 'js-cookie';
4+
import styled from 'styled-components';
5+
import getConfig from '../../../utils/getConfig';
6+
import { setUserCookieConsent } from '../actions';
7+
import { remSize, prop } from '../../../theme';
8+
import Button from '../../../common/Button';
9+
10+
const CookieConsentContainer = styled.div`
11+
position: fixed;
12+
bottom: 0;
13+
left: 0;
14+
right: 0;
15+
z-index: 9999;
16+
`;
17+
18+
const CookieConsentDialog = styled.div`
19+
width: 100%;
20+
height: 100%;
21+
background: ${prop('Modal.background')};
22+
border-top: 1px solid ${prop('Separator')};
23+
padding: ${remSize(40)} ${remSize(60)};
24+
`;
25+
26+
const CookieConsentHeader = styled.h2`
27+
margin-bottom: ${remSize(20)};
28+
`;
29+
30+
const CookieConsentContent = styled.div`
31+
display: flex;
32+
justify-content: space-between;
33+
`;
34+
35+
const CookieConsentCopy = styled.p``;
36+
37+
const CookieConsentButtons = styled.div`
38+
display: flex;
39+
align-items: center;
40+
margin-left: ${remSize(60)};
41+
& button:not(:last-child) {
42+
margin-right: ${remSize(20)};
43+
}
44+
`;
45+
46+
function CookieConsent() {
47+
const user = useSelector((state) => state.user);
48+
const [cookieConsent, setBrowserCookieConsent] = useState('none');
49+
const dispatch = useDispatch();
50+
51+
function initializeCookieConsent() {
52+
if (user.authenticated) {
53+
setBrowserCookieConsent(user.cookieConsent);
54+
Cookies.set('p5-cookie-consent', user.cookieConsent, { expires: 365 });
55+
return;
56+
}
57+
setBrowserCookieConsent('none');
58+
Cookies.set('p5-cookie-consent', 'none', { expires: 365 });
59+
}
60+
61+
function acceptAllCookies() {
62+
if (user.authenticated) {
63+
dispatch(setUserCookieConsent('all'));
64+
return;
65+
}
66+
setBrowserCookieConsent('all');
67+
Cookies.set('p5-cookie-consent', 'all', { expires: 365 });
68+
}
69+
70+
function acceptEssentialCookies() {
71+
if (user.authenticated) {
72+
dispatch(setUserCookieConsent('essential'));
73+
return;
74+
}
75+
setBrowserCookieConsent('essential');
76+
Cookies.set('p5-cookie-consent', 'essential', { expires: 365 });
77+
}
78+
79+
function mergeCookieConsent() {
80+
if (user.authenticated) {
81+
if (user.cookieConsent === 'none' && cookieConsent !== 'none') {
82+
dispatch(setUserCookieConsent(cookieConsent));
83+
} else if (user.cookieConsent !== 'none') {
84+
setBrowserCookieConsent(user.cookieConsent);
85+
Cookies.set('p5-cookie-consent', user.cookieConsent, { expires: 365 });
86+
}
87+
}
88+
}
89+
90+
useEffect(() => {
91+
const p5CookieConsent = Cookies.get('p5-cookie-consent');
92+
if (p5CookieConsent) {
93+
setBrowserCookieConsent(p5CookieConsent);
94+
} else {
95+
initializeCookieConsent();
96+
}
97+
}, []);
98+
99+
useEffect(() => {
100+
mergeCookieConsent();
101+
}, [user.authenticated]);
102+
103+
// Turn off Google Analytics
104+
useEffect(() => {
105+
if (cookieConsent === 'essential' || user.cookieConsent === 'essential') {
106+
window[`ga-disable-${getConfig('GA_MEASUREMENT_ID')}`] = true;
107+
}
108+
}, [cookieConsent, user.cookieConsent]);
109+
110+
const showCookieConsent =
111+
(user.authenticated && user.cookieConsent === 'none') ||
112+
(!user.authenticated && cookieConsent === 'none');
113+
114+
if (!showCookieConsent) return null;
115+
116+
return (
117+
<CookieConsentContainer>
118+
<CookieConsentDialog role="dialog" tabIndex="0">
119+
{/* <button aria-label="Close" tabIndex="0"></button> */}
120+
<CookieConsentHeader>Cookies</CookieConsentHeader>
121+
<CookieConsentContent>
122+
<CookieConsentCopy>
123+
The p5.js Editor uses cookies. Some are essential to the website
124+
functionality and allow you to manage an account and preferences.
125+
Others are used for analytics allow us to gather information and
126+
make improvements. You can decide which cookies you would like to
127+
allow.
128+
</CookieConsentCopy>
129+
<CookieConsentButtons>
130+
<Button onClick={acceptAllCookies}>Allow All</Button>
131+
<Button onClick={acceptEssentialCookies}>Allow Essential</Button>
132+
</CookieConsentButtons>
133+
</CookieConsentContent>
134+
</CookieConsentDialog>
135+
</CookieConsentContainer>
136+
);
137+
}
138+
// TODO need to merge browser cookie with user when u login
139+
140+
export default CookieConsent;

client/modules/User/reducers.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ const user = (state = { authenticated: false }, action) => {
4141
return { ...state, ...action.user };
4242
case ActionTypes.API_KEY_CREATED:
4343
return { ...state, ...action.user };
44+
case ActionTypes.SET_COOKIE_CONSENT:
45+
return { ...state, cookieConsent: action.cookieConsent };
4446
default:
4547
return state;
4648
}

package-lock.json

Lines changed: 9 additions & 21 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,7 @@
179179
"i18next-http-backend": "^1.0.21",
180180
"is-url": "^1.2.4",
181181
"jest-express": "^1.11.0",
182+
"js-cookie": "^2.2.1",
182183
"jsdom": "^9.8.3",
183184
"jshint": "^2.11.0",
184185
"lodash": "^4.17.21",

server/controllers/user.controller.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@ export function userResponse(user) {
1717
id: user._id,
1818
totalSize: user.totalSize,
1919
github: user.github,
20-
google: user.google
20+
google: user.google,
21+
cookieConsent: user.cookieConsent
2122
};
2223
}
2324

server/models/user.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,12 @@ const userSchema = new Schema(
7676
language: { type: String, default: 'en-US' },
7777
autocloseBracketsQuotes: { type: Boolean, default: true }
7878
},
79-
totalSize: { type: Number, default: 0 }
79+
totalSize: { type: Number, default: 0 },
80+
cookieConsent: {
81+
type: String,
82+
enum: ['none', 'essential', 'all'],
83+
default: 'none'
84+
}
8085
},
8186
{ timestamps: true, usePushEach: true }
8287
);

server/views/index.js

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -35,21 +35,23 @@ export function renderIndex() {
3535
window.process.env.MOBILE_ENABLED = ${process.env.MOBILE_ENABLED ? `${process.env.MOBILE_ENABLED}` : undefined};
3636
window.process.env.TRANSLATIONS_ENABLED = ${process.env.TRANSLATIONS_ENABLED === 'true' ? true : false};
3737
window.process.env.PREVIEW_URL = '${process.env.PREVIEW_URL}';
38+
window.process.env.GA_MEASUREMENT_ID='${process.env.GA_MEASUREMENT_ID}';
3839
</script>
3940
</head>
4041
<body>
4142
<div id="root" class="root-app">
4243
</div>
4344
<script src='${process.env.NODE_ENV === 'production' ? `${assetsManifest['/app.js']}` : '/app.js'}'></script>
4445
<script>
45-
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
46-
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
47-
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
48-
})(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
49-
50-
ga('create', 'UA-53383000-1', 'auto');
51-
ga('send', 'pageview');
52-
46+
${process.env.GA_MEASUREMENT_ID &&
47+
`(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
48+
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
49+
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
50+
})(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
51+
52+
ga('create', '${process.env.GA_MEASUREMENT_ID}', 'auto');
53+
ga('send', 'pageview');`
54+
}
5355
</script>
5456
</body>
5557
</html>

0 commit comments

Comments
 (0)