diff --git a/node_modules/.package-lock.json b/node_modules/.package-lock.json new file mode 100644 index 0000000..428a431 --- /dev/null +++ b/node_modules/.package-lock.json @@ -0,0 +1,15 @@ +{ + "name": "precision-medicine-portal-frontend", + "lockfileVersion": 3, + "requires": true, + "packages": { + "node_modules/js-cookie": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.5.tgz", + "integrity": "sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==", + "engines": { + "node": ">=14" + } + } + } +} diff --git a/node_modules/js-cookie/LICENSE b/node_modules/js-cookie/LICENSE new file mode 100644 index 0000000..3c581ca --- /dev/null +++ b/node_modules/js-cookie/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018 Copyright 2018 Klaus Hartl, Fagner Brack, GitHub Contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/node_modules/js-cookie/README.md b/node_modules/js-cookie/README.md new file mode 100644 index 0000000..e397e51 --- /dev/null +++ b/node_modules/js-cookie/README.md @@ -0,0 +1,305 @@ +

+ +

+ +# JavaScript Cookie [![CI](https://github.com/js-cookie/js-cookie/actions/workflows/ci.yml/badge.svg)](https://github.com/js-cookie/js-cookie/actions/workflows/ci.yml) [![BrowserStack](https://github.com/js-cookie/js-cookie/actions/workflows/browserstack.yml/badge.svg)](https://github.com/js-cookie/js-cookie/actions/workflows/browserstack.yml) [![JavaScript Style Guide](https://img.shields.io/badge/code_style-standard-brightgreen.svg)](https://standardjs.com) [![Code Climate](https://codeclimate.com/github/js-cookie/js-cookie.svg)](https://codeclimate.com/github/js-cookie/js-cookie) [![npm](https://img.shields.io/github/package-json/v/js-cookie/js-cookie)](https://www.npmjs.com/package/js-cookie) [![size](https://img.shields.io/bundlephobia/minzip/js-cookie/3)](https://www.npmjs.com/package/js-cookie) [![jsDelivr Hits](https://data.jsdelivr.com/v1/package/npm/js-cookie/badge?style=rounded)](https://www.jsdelivr.com/package/npm/js-cookie) + +A simple, lightweight JavaScript API for handling cookies + +- Works in [all](https://www.browserstack.com/automate/public-build/b3VDaHAxVDg0NDdCRmtUOWg0SlQzK2NsRVhWTjlDQS9qdGJoak1GMzJiVT0tLVhwZHNvdGRoY284YVRrRnI3eU1JTnc9PQ==--5e88ffb3ca116001d7ef2cfb97a4128ac31174c2) browsers +- Accepts [any](#encoding) character +- [Heavily](test) tested +- No dependency +- Supports ES modules +- Supports AMD/CommonJS +- [RFC 6265](https://tools.ietf.org/html/rfc6265) compliant +- Useful [Wiki](https://github.com/js-cookie/js-cookie/wiki) +- Enable [custom encoding/decoding](#converters) +- **< 800 bytes** gzipped! + +**πŸ‘‰πŸ‘‰ If you're viewing this at https://github.com/js-cookie/js-cookie, you're reading the documentation for the main branch. +[View documentation for the latest release.](https://github.com/js-cookie/js-cookie/tree/latest#readme) πŸ‘ˆπŸ‘ˆ** + +## Installation + +### NPM + +JavaScript Cookie supports [npm](https://www.npmjs.com/package/js-cookie) under the name `js-cookie`. + +```bash +npm i js-cookie +``` + +The npm package has a `module` field pointing to an ES module variant of the library, mainly to provide support for ES module aware bundlers, whereas its `browser` field points to an UMD module for full backward compatibility. + +_Not all browsers support ES modules natively yet_. For this reason the npm package/release provides both the ES and UMD module variant and you may want to include the ES module along with the UMD fallback to account for this: + +### CDN + +Alternatively, include js-cookie via [jsDelivr CDN](https://www.jsdelivr.com/package/npm/js-cookie). + +## Basic Usage + +Create a cookie, valid across the entire site: + +```javascript +Cookies.set('name', 'value') +``` + +Create a cookie that expires 7 days from now, valid across the entire site: + +```javascript +Cookies.set('name', 'value', { expires: 7 }) +``` + +Create an expiring cookie, valid to the path of the current page: + +```javascript +Cookies.set('name', 'value', { expires: 7, path: '' }) +``` + +Read cookie: + +```javascript +Cookies.get('name') // => 'value' +Cookies.get('nothing') // => undefined +``` + +Read all visible cookies: + +```javascript +Cookies.get() // => { name: 'value' } +``` + +_Note: It is not possible to read a particular cookie by passing one of the cookie attributes (which may or may not +have been used when writing the cookie in question):_ + +```javascript +Cookies.get('foo', { domain: 'sub.example.com' }) // `domain` won't have any effect...! +``` + +The cookie with the name `foo` will only be available on `.get()` if it's visible from where the +code is called; the domain and/or path attribute will not have an effect when reading. + +Delete cookie: + +```javascript +Cookies.remove('name') +``` + +Delete a cookie valid to the path of the current page: + +```javascript +Cookies.set('name', 'value', { path: '' }) +Cookies.remove('name') // fail! +Cookies.remove('name', { path: '' }) // removed! +``` + +_IMPORTANT! When deleting a cookie and you're not relying on the [default attributes](#cookie-attributes), you must pass the exact same path and domain attributes that were used to set the cookie:_ + +```javascript +Cookies.remove('name', { path: '', domain: '.yourdomain.com' }) +``` + +_Note: Removing a nonexistent cookie neither raises any exception nor returns any value._ + +## Namespace conflicts + +If there is any danger of a conflict with the namespace `Cookies`, the `noConflict` method will allow you to define a new namespace and preserve the original one. This is especially useful when running the script on third party sites e.g. as part of a widget or SDK. + +```javascript +// Assign the js-cookie api to a different variable and restore the original "window.Cookies" +var Cookies2 = Cookies.noConflict() +Cookies2.set('name', 'value') +``` + +_Note: The `.noConflict` method is not necessary when using AMD or CommonJS, thus it is not exposed in those environments._ + +## Encoding + +This project is [RFC 6265](http://tools.ietf.org/html/rfc6265#section-4.1.1) compliant. All special characters that are not allowed in the cookie-name or cookie-value are encoded with each one's UTF-8 Hex equivalent using [percent-encoding](http://en.wikipedia.org/wiki/Percent-encoding). +The only character in cookie-name or cookie-value that is allowed and still encoded is the percent `%` character, it is escaped in order to interpret percent input as literal. +Please note that the default encoding/decoding strategy is meant to be interoperable [only between cookies that are read/written by js-cookie](https://github.com/js-cookie/js-cookie/pull/200#discussion_r63270778). To override the default encoding/decoding strategy you need to use a [converter](#converters). + +_Note: According to [RFC 6265](https://tools.ietf.org/html/rfc6265#section-6.1), your cookies may get deleted if they are too big or there are too many cookies in the same domain, [more details here](https://github.com/js-cookie/js-cookie/wiki/Frequently-Asked-Questions#why-are-my-cookies-being-deleted)._ + +## Cookie Attributes + +Cookie attribute defaults can be set globally by creating an instance of the api via `withAttributes()`, or individually for each call to `Cookies.set(...)` by passing a plain object as the last argument. Per-call attributes override the default attributes. + +### expires + +Define when the cookie will be removed. Value must be a [`Number`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number) which will be interpreted as days from time of creation or a [`Date`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date) instance. If omitted, the cookie becomes a session cookie. + +To create a cookie that expires in less than a day, you can check the [FAQ on the Wiki](https://github.com/js-cookie/js-cookie/wiki/Frequently-Asked-Questions#expire-cookies-in-less-than-a-day). + +**Default:** Cookie is removed when the user closes the browser. + +**Examples:** + +```javascript +Cookies.set('name', 'value', { expires: 365 }) +Cookies.get('name') // => 'value' +Cookies.remove('name') +``` + +### path + +A [`String`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String) indicating the path where the cookie is visible. + +**Default:** `/` + +**Examples:** + +```javascript +Cookies.set('name', 'value', { path: '' }) +Cookies.get('name') // => 'value' +Cookies.remove('name', { path: '' }) +``` + +**Note regarding Internet Explorer:** + +> Due to an obscure bug in the underlying WinINET InternetGetCookie implementation, IE’s document.cookie will not return a cookie if it was set with a path attribute containing a filename. + +(From [Internet Explorer Cookie Internals (FAQ)](http://blogs.msdn.com/b/ieinternals/archive/2009/08/20/wininet-ie-cookie-internals-faq.aspx)) + +This means one cannot set a path using `window.location.pathname` in case such pathname contains a filename like so: `/check.html` (or at least, such cookie cannot be read correctly). + +In fact, you should never allow untrusted input to set the cookie attributes or you might be exposed to a [XSS attack](https://github.com/js-cookie/js-cookie/issues/396). + +### domain + +A [`String`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String) indicating a valid domain where the cookie should be visible. The cookie will also be visible to all subdomains. + +**Default:** Cookie is visible only to the domain or subdomain of the page where the cookie was created, except for Internet Explorer (see below). + +**Examples:** + +Assuming a cookie that is being created on `site.com`: + +```javascript +Cookies.set('name', 'value', { domain: 'subdomain.site.com' }) +Cookies.get('name') // => undefined (need to read at 'subdomain.site.com') +``` + +**Note regarding Internet Explorer default behavior:** + +> Q3: If I don’t specify a DOMAIN attribute (for) a cookie, IE sends it to all nested subdomains anyway? +> A: Yes, a cookie set on example.com will be sent to sub2.sub1.example.com. +> Internet Explorer differs from other browsers in this regard. + +(From [Internet Explorer Cookie Internals (FAQ)](http://blogs.msdn.com/b/ieinternals/archive/2009/08/20/wininet-ie-cookie-internals-faq.aspx)) + +This means that if you omit the `domain` attribute, it will be visible for a subdomain in IE. + +### secure + +Either `true` or `false`, indicating if the cookie transmission requires a secure protocol (https). + +**Default:** No secure protocol requirement. + +**Examples:** + +```javascript +Cookies.set('name', 'value', { secure: true }) +Cookies.get('name') // => 'value' +Cookies.remove('name') +``` + +### sameSite + +A [`String`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String), allowing to control whether the browser is sending a cookie along with cross-site requests. + +Default: not set. + +**Note that more recent browsers are making "Lax" the default value even without specifiying anything here.** + +**Examples:** + +```javascript +Cookies.set('name', 'value', { sameSite: 'strict' }) +Cookies.get('name') // => 'value' +Cookies.remove('name') +``` + +### Setting up defaults + +```javascript +const api = Cookies.withAttributes({ path: '/', domain: '.example.com' }) +``` + +## Converters + +### Read + +Create a new instance of the api that overrides the default decoding implementation. All get methods that rely in a proper decoding to work, such as `Cookies.get()` and `Cookies.get('name')`, will run the given converter for each cookie. The returned value will be used as the cookie value. + +Example from reading one of the cookies that can only be decoded using the `escape` function: + +```javascript +document.cookie = 'escaped=%u5317' +document.cookie = 'default=%E5%8C%97' +var cookies = Cookies.withConverter({ + read: function (value, name) { + if (name === 'escaped') { + return unescape(value) + } + // Fall back to default for all other cookies + return Cookies.converter.read(value, name) + } +}) +cookies.get('escaped') // εŒ— +cookies.get('default') // εŒ— +cookies.get() // { escaped: 'εŒ—', default: 'εŒ—' } +``` + +### Write + +Create a new instance of the api that overrides the default encoding implementation: + +```javascript +Cookies.withConverter({ + write: function (value, name) { + return value.toUpperCase() + } +}) +``` + +## TypeScript declarations + +```bash +npm i @types/js-cookie +``` + +## Server-side integration + +Check out the [Servers Docs](SERVER_SIDE.md) + +## Contributing + +Check out the [Contributing Guidelines](CONTRIBUTING.md) + +## Security + +For vulnerability reports, send an e-mail to `js-cookie at googlegroups dot com` + +## Releasing + +Releasing should be done via the `Release` GitHub Actions workflow, so that published packages on npmjs.com have package provenance. + +GitHub releases are created as a draft and need to be published manually! +(This is so we are able to craft suitable release notes before publishing.) + +## Supporters + +

+ +

+ +Many thanks to [BrowserStack](https://www.browserstack.com/) for providing unlimited browser testing free of cost. + +## Authors + +- [Klaus Hartl](https://github.com/carhartl) +- [Fagner Brack](https://github.com/FagnerMartinsBrack) +- And awesome [contributors](https://github.com/js-cookie/js-cookie/graphs/contributors) diff --git a/node_modules/js-cookie/dist/js.cookie.js b/node_modules/js-cookie/dist/js.cookie.js new file mode 100644 index 0000000..373bef7 --- /dev/null +++ b/node_modules/js-cookie/dist/js.cookie.js @@ -0,0 +1,147 @@ +/*! js-cookie v3.0.5 | MIT */ +; +(function (global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : + typeof define === 'function' && define.amd ? define(factory) : + (global = typeof globalThis !== 'undefined' ? globalThis : global || self, (function () { + var current = global.Cookies; + var exports = global.Cookies = factory(); + exports.noConflict = function () { global.Cookies = current; return exports; }; + })()); +})(this, (function () { 'use strict'; + + /* eslint-disable no-var */ + function assign (target) { + for (var i = 1; i < arguments.length; i++) { + var source = arguments[i]; + for (var key in source) { + target[key] = source[key]; + } + } + return target + } + /* eslint-enable no-var */ + + /* eslint-disable no-var */ + var defaultConverter = { + read: function (value) { + if (value[0] === '"') { + value = value.slice(1, -1); + } + return value.replace(/(%[\dA-F]{2})+/gi, decodeURIComponent) + }, + write: function (value) { + return encodeURIComponent(value).replace( + /%(2[346BF]|3[AC-F]|40|5[BDE]|60|7[BCD])/g, + decodeURIComponent + ) + } + }; + /* eslint-enable no-var */ + + /* eslint-disable no-var */ + + function init (converter, defaultAttributes) { + function set (name, value, attributes) { + if (typeof document === 'undefined') { + return + } + + attributes = assign({}, defaultAttributes, attributes); + + if (typeof attributes.expires === 'number') { + attributes.expires = new Date(Date.now() + attributes.expires * 864e5); + } + if (attributes.expires) { + attributes.expires = attributes.expires.toUTCString(); + } + + name = encodeURIComponent(name) + .replace(/%(2[346B]|5E|60|7C)/g, decodeURIComponent) + .replace(/[()]/g, escape); + + var stringifiedAttributes = ''; + for (var attributeName in attributes) { + if (!attributes[attributeName]) { + continue + } + + stringifiedAttributes += '; ' + attributeName; + + if (attributes[attributeName] === true) { + continue + } + + // Considers RFC 6265 section 5.2: + // ... + // 3. If the remaining unparsed-attributes contains a %x3B (";") + // character: + // Consume the characters of the unparsed-attributes up to, + // not including, the first %x3B (";") character. + // ... + stringifiedAttributes += '=' + attributes[attributeName].split(';')[0]; + } + + return (document.cookie = + name + '=' + converter.write(value, name) + stringifiedAttributes) + } + + function get (name) { + if (typeof document === 'undefined' || (arguments.length && !name)) { + return + } + + // To prevent the for loop in the first place assign an empty array + // in case there are no cookies at all. + var cookies = document.cookie ? document.cookie.split('; ') : []; + var jar = {}; + for (var i = 0; i < cookies.length; i++) { + var parts = cookies[i].split('='); + var value = parts.slice(1).join('='); + + try { + var found = decodeURIComponent(parts[0]); + jar[found] = converter.read(value, found); + + if (name === found) { + break + } + } catch (e) {} + } + + return name ? jar[name] : jar + } + + return Object.create( + { + set, + get, + remove: function (name, attributes) { + set( + name, + '', + assign({}, attributes, { + expires: -1 + }) + ); + }, + withAttributes: function (attributes) { + return init(this.converter, assign({}, this.attributes, attributes)) + }, + withConverter: function (converter) { + return init(assign({}, this.converter, converter), this.attributes) + } + }, + { + attributes: { value: Object.freeze(defaultAttributes) }, + converter: { value: Object.freeze(converter) } + } + ) + } + + var api = init(defaultConverter, { path: '/' }); + /* eslint-enable no-var */ + + return api; + +})); diff --git a/node_modules/js-cookie/dist/js.cookie.min.js b/node_modules/js-cookie/dist/js.cookie.min.js new file mode 100644 index 0000000..962d48d --- /dev/null +++ b/node_modules/js-cookie/dist/js.cookie.min.js @@ -0,0 +1,2 @@ +/*! js-cookie v3.0.5 | MIT */ +!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):(e="undefined"!=typeof globalThis?globalThis:e||self,function(){var n=e.Cookies,o=e.Cookies=t();o.noConflict=function(){return e.Cookies=n,o}}())}(this,(function(){"use strict";function e(e){for(var t=1;t=14" + } +} diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..6d57c38 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,20 @@ +{ + "name": "precision-medicine-portal-frontend", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "dependencies": { + "js-cookie": "^3.0.5" + } + }, + "node_modules/js-cookie": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.5.tgz", + "integrity": "sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==", + "engines": { + "node": ">=14" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..26ac64e --- /dev/null +++ b/package.json @@ -0,0 +1,5 @@ +{ + "dependencies": { + "js-cookie": "^3.0.5" + } +} diff --git a/react-app/package-lock.json b/react-app/package-lock.json index e071345..3220125 100644 --- a/react-app/package-lock.json +++ b/react-app/package-lock.json @@ -27,6 +27,7 @@ }, "devDependencies": { "@tailwindcss/typography": "^0.5.10", + "@types/js-cookie": "^3.0.6", "daisyui": "^4.4.20", "tailwindcss": "^3.3.6" } @@ -4149,6 +4150,12 @@ "pretty-format": "^27.0.0" } }, + "node_modules/@types/js-cookie": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/js-cookie/-/js-cookie-3.0.6.tgz", + "integrity": "sha512-wkw9yd1kEXOPnvEeEV1Go1MmxtBJL0RR79aOTAApecWFVu7w0NNXNqhcWgvw2YgZDYadliXkl14pa3WXw5jlCQ==", + "dev": true + }, "node_modules/@types/json-schema": { "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", diff --git a/react-app/package.json b/react-app/package.json index 133b09d..536da64 100644 --- a/react-app/package.json +++ b/react-app/package.json @@ -46,6 +46,7 @@ }, "devDependencies": { "@tailwindcss/typography": "^0.5.10", + "@types/js-cookie": "^3.0.6", "daisyui": "^4.4.20", "tailwindcss": "^3.3.6" } diff --git a/react-app/src/App.tsx b/react-app/src/App.tsx index 09fab75..d2780ea 100644 --- a/react-app/src/App.tsx +++ b/react-app/src/App.tsx @@ -1,10 +1,23 @@ //import React from 'react'; -import { ReactElement } from 'react'; +import { ReactElement, useState } from 'react'; import { Outlet } from 'react-router-dom'; import FooterComponent from './components/FooterComponent'; import HeaderComponent from './components/HeaderComponent'; +import Cookies from 'js-cookie'; export default function App(): ReactElement { + + // the statement in the useState tries to do Cookies.get('trackingEnabled'), if that cookie doesn't exist + // it should return undefined. Since the statement is negated then isTrackingCookieSet will be set to true if + // the useState statement is False, i.e. when the trackingEnabled cookie doesn't exist. + const [isTrackingCookieSet, setTrackingCookie] = useState(!(Cookies.get('trackingEnabled') === undefined)); + + // only set a new cookie to 'true' if no cookies have been set yet + if (!isTrackingCookieSet) { + Cookies.set('trackingEnabled', 'true', { expires: 365 }); + setTrackingCookie(true); + }; + return (
diff --git a/react-app/src/components/CardComponent.tsx b/react-app/src/components/CardComponent.tsx index 609aa6f..ae31f0d 100644 --- a/react-app/src/components/CardComponent.tsx +++ b/react-app/src/components/CardComponent.tsx @@ -10,7 +10,7 @@ export default function CardComponent(prop: { cardConfig: ICardConfig, cardConte
{prop.cardContent.imageAlt}
); - var buttonClasses: string = "card-actions " + prop.cardConfig.buttonPlacement; + let buttonClasses: string = "card-actions " + prop.cardConfig.buttonPlacement; const button: ReactElement = (
diff --git a/react-app/src/components/FooterComponent.tsx b/react-app/src/components/FooterComponent.tsx index 14e1af3..9fb49eb 100644 --- a/react-app/src/components/FooterComponent.tsx +++ b/react-app/src/components/FooterComponent.tsx @@ -5,22 +5,22 @@ import { LINK_CLASSES } from '../constants'; export default function FooterComponent(): ReactElement { - var linksCol1: { [id: string] : ILink; } = { + let linksCol1: { [id: string] : ILink; } = { 'l1': { text: 'Anonymization Tool', classes: LINK_CLASSES, link: '/' }, 'l2': { text: 'Data Search', classes: LINK_CLASSES, link: '/' }, 'l3': { text: 'Data Types', classes: LINK_CLASSES, link: '/' }, 'l4': { text: 'Events & News', classes: LINK_CLASSES, link: '/' }, }; - var linksCol2: { [id: string] : ILink; } = { + let linksCol2: { [id: string] : ILink; } = { 'l1': { text: 'About us', classes: LINK_CLASSES, link: '/about' }, 'l2': { text: 'Contact', classes: LINK_CLASSES, link: '/' }, 'l3': { text: 'Open Source Contribution', classes: LINK_CLASSES, link: '/' }, 'l4': { text: 'Privacy Policy', classes: LINK_CLASSES, link: '/privacy' }, }; - var svgConfig: string[] = ['/', 'http://www.w3.org/2000/svg', '24', '24', '0 0 24 24', 'fill-current'] - var svgs: { [id: string] : ISVG; } = { + let svgConfig: string[] = ['/', 'http://www.w3.org/2000/svg', '24', '24', '0 0 24 24', 'fill-current'] + let svgs: { [id: string] : ISVG; } = { 'svgX': {href: svgConfig[0], xmlns: svgConfig[1], width: svgConfig[2], height: svgConfig[3], viewBox: svgConfig[4], classes: svgConfig[5], svg: 'M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z'}, 'svgYT': {href: svgConfig[0], xmlns: svgConfig[1], width: svgConfig[2], height: svgConfig[3], viewBox: svgConfig[4], classes: svgConfig[5], svg: 'M19.615 3.184c-3.604-.246-11.631-.245-15.23 0-3.897.266-4.356 2.62-4.385 8.816.029 6.185.484 8.549 4.385 8.816 3.6.245 11.626.246 15.23 0 3.897-.266 4.356-2.62 4.385-8.816-.029-6.185-.484-8.549-4.385-8.816zm-10.615 12.816v-8l8 3.993-8 4.007z'}, 'svgFB': {href: svgConfig[0], xmlns: svgConfig[1], width: svgConfig[2], height: svgConfig[3], viewBox: svgConfig[4], classes: svgConfig[5], svg: 'M9 8h-3v4h3v12h5v-12h3.642l.358-4h-4v-1.667c0-.955.192-1.333 1.115-1.333h2.885v-5h-3.808c-3.596 0-5.192 1.583-5.192 4.615v3.385z'}, diff --git a/react-app/src/components/HeaderComponent.tsx b/react-app/src/components/HeaderComponent.tsx index c0f7c98..7f72e6b 100644 --- a/react-app/src/components/HeaderComponent.tsx +++ b/react-app/src/components/HeaderComponent.tsx @@ -4,7 +4,7 @@ import { BUTTON_TYPE_ONE, LINK_CLASSES } from '../constants'; export default function HeaderComponent() { - var links: { [id: string] : ILink; } = { + let links: { [id: string] : ILink; } = { 'l1': { text: 'Random text for now', classes: 'pointer-events-none', link: '/#' }, 'l2': { text: 'Data', classes: LINK_CLASSES, link: 'data' }, 'l3': { text: 'Events & News', classes: LINK_CLASSES, link: 'eventsandnews' }, @@ -12,7 +12,7 @@ export default function HeaderComponent() { 'l5': { text: 'About', classes: LINK_CLASSES, link: 'about' }, }; - var buttons: { [id: string] : ILink; } = { + let buttons: { [id: string] : ILink; } = { 'b1': { text: 'Sign In', classes: BUTTON_TYPE_ONE, link: 'signin' }, }; diff --git a/react-app/src/index.tsx b/react-app/src/index.tsx index f9d9427..3588353 100644 --- a/react-app/src/index.tsx +++ b/react-app/src/index.tsx @@ -3,10 +3,26 @@ import ReactDOM from 'react-dom/client'; import { MatomoProvider, createInstance } from '@jonkoops/matomo-tracker-react' import './index.css'; import Routes from './components/Routes'; +import { cookieIsSetToTrue } from './util/cookiesHandling'; + +// the cookie is set in App.tsx for the first time. From testing +// it seems like App doesn't set the cookie before code here has run, so if it's a first visit +// we need to conditionally set trackingEnabled to true for the matomo instance. +// Tried moving the script creating the cookie here but we cannot use hooks at the top level, so this +// workaround works for now. +let trackingEnabled: Boolean; +try { + trackingEnabled = cookieIsSetToTrue('trackingEnabled') ? true : false; +} +catch(e) { + console.log(e); + trackingEnabled = true; +} const instance = createInstance({ urlBase: 'https://matomo.dc.scilifelab.se/', siteId: 9, + disabled: !trackingEnabled, } ) diff --git a/react-app/src/pages/AboutFAQPage.tsx b/react-app/src/pages/AboutFAQPage.tsx index 8c73edd..92cf2cf 100644 --- a/react-app/src/pages/AboutFAQPage.tsx +++ b/react-app/src/pages/AboutFAQPage.tsx @@ -1,14 +1,9 @@ import React, { ReactElement } from 'react'; -import { useMatomo } from '@jonkoops/matomo-tracker-react'; import AccordionComponent from '../components/AccordionComponent'; +import { TrackPageViewIfEnabled } from '../util/cookiesHandling'; export default function AboutFAQPage(): ReactElement { - const { trackPageView,} = useMatomo() - - // Track page view - React.useEffect(() => { - trackPageView() - }, []) + TrackPageViewIfEnabled(); return ( <> diff --git a/react-app/src/pages/AboutPage.tsx b/react-app/src/pages/AboutPage.tsx index 83101b8..6581160 100644 --- a/react-app/src/pages/AboutPage.tsx +++ b/react-app/src/pages/AboutPage.tsx @@ -1,5 +1,4 @@ import React, { ReactElement } from 'react'; -import { useMatomo } from '@jonkoops/matomo-tracker-react'; import TextBarComponent from '../components/TextBarComponent'; import { BODY_CLASSES, @@ -8,14 +7,10 @@ import { } from '../constants'; import { Link, NavLink, Outlet } from 'react-router-dom'; import { ILink, } from '../interfaces/types'; +import { TrackPageViewIfEnabled } from '../util/cookiesHandling'; export default function AboutPage(): ReactElement { - const { trackPageView,} = useMatomo() - - // Track page view - React.useEffect(() => { - trackPageView() - }, []) + TrackPageViewIfEnabled(); var pageTitle: string = "About Us"; var textBarContent: string = "Get to know about the team behind the [product name] and our mission to connect you with the data you need."; diff --git a/react-app/src/pages/AboutPartnersPage.tsx b/react-app/src/pages/AboutPartnersPage.tsx index 6f95bb2..0e291ae 100644 --- a/react-app/src/pages/AboutPartnersPage.tsx +++ b/react-app/src/pages/AboutPartnersPage.tsx @@ -1,15 +1,10 @@ import React, { ReactElement } from 'react'; -import { useMatomo } from '@jonkoops/matomo-tracker-react'; import { ICardConfig, ICardContent } from '../interfaces/types'; import CardComponent from '../components/CardComponent'; +import { TrackPageViewIfEnabled } from '../util/cookiesHandling'; export default function AboutPartnersPage(): ReactElement { - const { trackPageView,} = useMatomo() - - // Track page view - React.useEffect(() => { - trackPageView() - }, []) + TrackPageViewIfEnabled(); var cardConfig: { [id: string] : ICardConfig; } = { 'fundersAndPartnersCard': { diff --git a/react-app/src/pages/AboutProductPage.tsx b/react-app/src/pages/AboutProductPage.tsx index 2368907..03560e1 100644 --- a/react-app/src/pages/AboutProductPage.tsx +++ b/react-app/src/pages/AboutProductPage.tsx @@ -1,15 +1,10 @@ import React, { ReactElement } from 'react'; -import { useMatomo } from '@jonkoops/matomo-tracker-react'; import { ICardConfig, ICardContent } from '../interfaces/types'; import CardComponent from '../components/CardComponent'; +import { TrackPageViewIfEnabled } from '../util/cookiesHandling'; export default function AboutProductPage(): ReactElement { - const { trackPageView,} = useMatomo() - - // Track page view - React.useEffect(() => { - trackPageView() - }, []) + TrackPageViewIfEnabled(); var PMDDescription: string = ` Ut rhoncus ante in metus lobortis, eu euismod magna dignissim. Duis nec condimentum purus. diff --git a/react-app/src/pages/ContactPage.tsx b/react-app/src/pages/ContactPage.tsx index ea97944..98ceeab 100644 --- a/react-app/src/pages/ContactPage.tsx +++ b/react-app/src/pages/ContactPage.tsx @@ -1,13 +1,8 @@ import React, { ReactElement } from 'react'; -import { useMatomo } from '@jonkoops/matomo-tracker-react'; +import { TrackPageViewIfEnabled } from '../util/cookiesHandling'; export default function ContactPage(): ReactElement { - const { trackPageView, } = useMatomo() - - // Track page view - React.useEffect(() => { - trackPageView() - }, []) + TrackPageViewIfEnabled(); return (
diff --git a/react-app/src/pages/DataPage.tsx b/react-app/src/pages/DataPage.tsx index 4929817..803110f 100644 --- a/react-app/src/pages/DataPage.tsx +++ b/react-app/src/pages/DataPage.tsx @@ -1,13 +1,8 @@ import React, { ReactElement } from 'react'; -import { useMatomo } from '@jonkoops/matomo-tracker-react'; +import { TrackPageViewIfEnabled } from '../util/cookiesHandling'; export default function DataPage(): ReactElement { - const { trackPageView, } = useMatomo() - - // Track page view - React.useEffect(() => { - trackPageView() - }, []) + TrackPageViewIfEnabled(); return (
diff --git a/react-app/src/pages/EventsAndNewsPage.tsx b/react-app/src/pages/EventsAndNewsPage.tsx index 2d22389..65565f5 100644 --- a/react-app/src/pages/EventsAndNewsPage.tsx +++ b/react-app/src/pages/EventsAndNewsPage.tsx @@ -1,13 +1,8 @@ import React, { ReactElement } from 'react'; -import { useMatomo } from '@jonkoops/matomo-tracker-react'; +import { TrackPageViewIfEnabled } from '../util/cookiesHandling'; export default function EventsAndNewsPage(): ReactElement { - const { trackPageView, } = useMatomo() - - // Track page view - React.useEffect(() => { - trackPageView() - }, []) + TrackPageViewIfEnabled(); return (
diff --git a/react-app/src/pages/HomePage.tsx b/react-app/src/pages/HomePage.tsx index 756a56d..0b317af 100644 --- a/react-app/src/pages/HomePage.tsx +++ b/react-app/src/pages/HomePage.tsx @@ -5,15 +5,10 @@ import ImageCarouselComponent from "../components/ImageCarouselComponent"; import TextBarComponent from "../components/TextBarComponent"; import { BODY_CLASSES, BUTTON_TYPE_ONE, H_1, PAGE_DESCRIPTION_TEXT_BAR_CLASSES } from '../constants'; import { ICardConfig, ICardContent } from '../interfaces/types'; +import { TrackPageViewIfEnabled } from '../util/cookiesHandling'; export default function HomePage(): ReactElement { - - const { trackPageView, } = useMatomo() - // const { trackPageView, trackEvent } = useMatomo() , trackEvent to track clicks and other events - // Track page view - React.useEffect(() => { - trackPageView() - }, []) + TrackPageViewIfEnabled(); var cardTitleClasses: string = "text-center text-white text-xl font-semibold"; var cardTextClasses: string = "text-center"; diff --git a/react-app/src/pages/PrivacyPage.tsx b/react-app/src/pages/PrivacyPage.tsx index b981760..7097247 100644 --- a/react-app/src/pages/PrivacyPage.tsx +++ b/react-app/src/pages/PrivacyPage.tsx @@ -10,14 +10,12 @@ import { } from '../constants'; import { Link } from 'react-router-dom'; import { ILink } from '../interfaces/types'; +import Cookies from 'js-cookie'; +import { cookieIsSetToTrue, TrackPageViewIfEnabled } from '../util/cookiesHandling'; export default function PrivacyPage(): ReactElement { - const { trackPageView,} = useMatomo() - // Track page view - React.useEffect(() => { - trackPageView() - }, []) + TrackPageViewIfEnabled(); var textBarContent: string = "Transparency is one of our guiding principles. Get acquainted with how we're creating a secure space for you."; var policyText: string = ` @@ -81,7 +79,11 @@ export default function PrivacyPage(): ReactElement { 'Contact Us', ]; - var alertMessage: string = "we use cookies for no reason."; + var alertMessage: string = "Opt out of tracking?"; + + const handleOptOut = () => { + Cookies.set('trackingEnabled', 'false', { expires: 365 }); + }; return ( <> @@ -103,8 +105,7 @@ export default function PrivacyPage(): ReactElement { {alertMessage}
- - +
{dividers[2]}
diff --git a/react-app/src/pages/SignInPage.tsx b/react-app/src/pages/SignInPage.tsx index 902480d..3712fa1 100644 --- a/react-app/src/pages/SignInPage.tsx +++ b/react-app/src/pages/SignInPage.tsx @@ -1,13 +1,9 @@ import React, { ReactElement } from 'react'; import { useMatomo } from '@jonkoops/matomo-tracker-react'; +import { cookieIsSetToTrue, TrackPageViewIfEnabled } from '../util/cookiesHandling'; export default function SignInPage(): ReactElement { - const { trackPageView, } = useMatomo() - - // Track page view - React.useEffect(() => { - trackPageView() - }, []) + TrackPageViewIfEnabled(); return (
diff --git a/react-app/src/util/cookiesHandling.ts b/react-app/src/util/cookiesHandling.ts new file mode 100644 index 0000000..61efcb1 --- /dev/null +++ b/react-app/src/util/cookiesHandling.ts @@ -0,0 +1,27 @@ +import { useMatomo } from '@jonkoops/matomo-tracker-react'; +import Cookie from 'js-cookie'; +import React from 'react'; + +// cookies are stored as strings, so they cannot be directly used as boolean type +export function cookieIsSetToTrue(cookieName: string): Boolean { + let cookieValue: string | undefined = Cookie.get(cookieName); + if (!cookieValue) { + throw new Error('Cookie with name "'+ cookieName +'" does not exist or is not set yet.') + } + else { + return (Cookie.get(cookieName) === 'true'); + } +} + +export function TrackPageViewIfEnabled() { + // const { trackPageView, trackEvent } = useMatomo() , trackEvent to track clicks and other events + const { trackPageView,} = useMatomo() + + // track page visit if trackingEnabled cookie is set to 'true' + + React.useEffect(() => { + if (cookieIsSetToTrue('trackingEnabled')) { + trackPageView() + } + }, []) +} \ No newline at end of file