From 7c905a042c0ce0456f59280903915787d5c20e0e Mon Sep 17 00:00:00 2001 From: veeso Date: Fri, 31 Jan 2025 16:47:25 +0100 Subject: [PATCH] =?UTF-8?q?feat:=20google=20analytics=C2=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 + package.json | 6 +- src/index.html | 62 +++++--- src/index.tsx | 1 + src/js/components/App/Routes.tsx | 2 + src/js/components/App/pages/Cookies.tsx | 184 +++++++++++++++++++----- src/js/components/CookieBar.tsx | 77 ++++++++++ src/js/components/reusable/Switch.tsx | 32 +++++ src/js/declaration.d.ts | 5 + src/js/utils/analytics.ts | 34 +++++ src/js/utils/cookies.ts | 47 ++++++ yarn.lock | 38 +++-- 12 files changed, 420 insertions(+), 70 deletions(-) create mode 100644 src/js/components/CookieBar.tsx create mode 100644 src/js/components/reusable/Switch.tsx create mode 100644 src/js/utils/analytics.ts create mode 100644 src/js/utils/cookies.ts diff --git a/README.md b/README.md index 9398119..ceacc68 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,8 @@ Powered by **Internet Computer** +GA TAG G-3SYTZ509HY + --- - [Marketplace D-App](#marketplace-d-app) diff --git a/package.json b/package.json index 52f4a25..c7fc21b 100644 --- a/package.json +++ b/package.json @@ -30,8 +30,10 @@ "dependencies": { "@ensdomains/ensjs": "^4.0.2", "@metamask/jazzicon": "^2.0.0", + "animate.css": "^4.1.1", "byte-size": "^9.0.1", "chart.js": "^4.4.2", + "js-cookie": "^3.0.5", "leaflet": "^1.9.4", "metamask-react": "^2.7.0", "property-information": "^6.5.0", @@ -40,7 +42,7 @@ "react-dom": "^19", "react-helmet": "^6.1.0", "react-icons": "^5.4.0", - "react-leaflet": "v5.0.0-rc.1", + "react-leaflet": "v5.0.0", "react-loading-skeleton": "^3.5.0", "react-markdown": "^9.0.1", "react-router-dom": "^6.26.1", @@ -51,6 +53,8 @@ "@parcel/reporter-bundle-analyzer": "^2.13.0", "@parcel/transformer-typescript-tsc": "^2.13.0", "@types/byte-size": "^8.1.2", + "@types/gtag.js": "^0.0.20", + "@types/js-cookie": "^3.0.6", "@types/leaflet": "^1.9.15", "@types/node": "^22.10.2", "@types/react": "^19", diff --git a/src/index.html b/src/index.html index 131ee48..6f55119 100644 --- a/src/index.html +++ b/src/index.html @@ -1,26 +1,42 @@ + + + + + + + + + + + + + EKOKE DAO + + + - - - - - - - - - - - - - EKOKE DAO - - - - -
- - - - \ No newline at end of file + +
+ + + diff --git a/src/index.tsx b/src/index.tsx index 0ed5777..86e84f3 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -1,5 +1,6 @@ import * as React from 'react'; import * as ReactDOM from 'react-dom/client'; +import 'animate.css'; import App from './App'; diff --git a/src/js/components/App/Routes.tsx b/src/js/components/App/Routes.tsx index 0bf57cd..0da57dc 100644 --- a/src/js/components/App/Routes.tsx +++ b/src/js/components/App/Routes.tsx @@ -4,6 +4,7 @@ import { Route as RouterRoute, Routes } from 'react-router-dom'; import { Route } from '../../utils/routes'; import SeoEngine from '../SeoEngine'; import Container from '../reusable/Container'; +import CookieBar from '../CookieBar'; // pages const Home = React.lazy(() => import('./pages/Home')); @@ -181,6 +182,7 @@ const AppRouter = () => ( } /> + ); diff --git a/src/js/components/App/pages/Cookies.tsx b/src/js/components/App/pages/Cookies.tsx index 495f0ed..8921db5 100644 --- a/src/js/components/App/pages/Cookies.tsx +++ b/src/js/components/App/pages/Cookies.tsx @@ -4,43 +4,153 @@ import Container from '../../reusable/Container'; import Heading from '../../reusable/Heading'; import Paragraph from '../../reusable/Paragraph'; import Link from '../../reusable/Link'; +import { + CookiePreferences, + getCookiePreferences, + setAnalyticsCookiesPreference, +} from '../../../utils/cookies'; +import { acceptGa4, rejectGa4 } from '../../../utils/analytics'; +import Switch from '../../reusable/Switch'; -const Cookies = () => ( - - Coookie Policy - - On ekokedao.com, we do not use any cookies to track user - behavior - - What are cookies? - - Cookies are small text files that are placed on your computer by websites - that you visit. They are widely used in order to make websites work, or - work more efficiently, as well as to provide information to the owners of - the site. - - How can I control cookies? - - You have the right to decide whether to accept or reject cookies. You can - exercise your cookie preferences by clicking on the appropriate opt-out - links provided in the cookie table above. - - How can I opt out of Cookies? - - If you do not wish to have cookies placed on your device, we suggest - setting your cookie preferences in your browser. If you want to remove - previously stored cookies, you can manually delete them at any time. - However, this will not prevent additional cookies from being stored on - your device in the future when you visit this website. - - - For more information on how to manage cookies, please visit{' '} - - www.allaboutcookies.org/manage-cookies/ - - - Last updated: 2025-12-13 - -); +const Cookies = () => { + const [cookies, setCookies] = React.useState( + getCookiePreferences(), + ); + + const onToggleAnalytics = () => { + const analytics = !cookies.analytics; + setAnalyticsCookiesPreference(analytics); + setCookies({ ...cookies, analytics }); + + if (analytics) { + acceptGa4(); + } else { + rejectGa4(); + } + }; + + return ( + + Coookie Policy + + On ekokedao.com, we use Google Analytics cookies to + analyze our website traffic. These cookies help us to improve the + website's performance and user experience. Google Analytics is a + popular web analytics service that helps us understand how visitors + engage with our website. Google Analytics cookies may track things such + as how long you spend on the website and the pages that you visit so we + can continue to produce engaging content. + + Google Analytics + + This website uses Google Analytics 4 (GA4) to analyze user interactions + and improve the overall experience. GA4 collects data through cookies, + which help us understand visitor behavior, traffic sources, and website + performance. The information gathered is anonymous and does not allow + for direct identification of users. By using this website, you consent + to the processing of data by Google in the manner and for the purposes + described in{' '} + + Google's Privacy Policy + + . You can disable Google Analytics cookies at any time by adjusting your + browser settings or using the{' '} + + Google Analytics Opt-out Add-on + + . + + The Cookies we use + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
CookieDescriptionDuration
_ga + Used to distinguish users by assigning a unique ID. + 2 years
+ _ga_G-3SYTZ509HY + + Stores session state for GA4. + 2 years
_gid + Used to distinguish users. + 24 hours
_gat + Used to throttle request rate. + 1 minute
+ _gac_G-3SYTZ509HY + + Contains campaign-related information for the user. + 90 days
+ Change your cookie preferences + + + + + + What are cookies? + + Cookies are small text files that are placed on your computer by + websites that you visit. They are widely used in order to make websites + work, or work more efficiently, as well as to provide information to the + owners of the site. + + How can I control cookies? + + You have the right to decide whether to accept or reject cookies. You + can exercise your cookie preferences by clicking on the appropriate + opt-out links provided in the cookie table above. + + How can I opt out of Cookies? + + If you do not wish to have cookies placed on your device, we suggest + setting your cookie preferences in your browser. If you want to remove + previously stored cookies, you can manually delete them at any time. + However, this will not prevent additional cookies from being stored on + your device in the future when you visit this website. + + + For more information on how to manage cookies, please visit{' '} + + www.allaboutcookies.org/manage-cookies/ + + + Last updated: 2025-01-31 +
+ ); +}; export default Cookies; diff --git a/src/js/components/CookieBar.tsx b/src/js/components/CookieBar.tsx new file mode 100644 index 0000000..cc8e8eb --- /dev/null +++ b/src/js/components/CookieBar.tsx @@ -0,0 +1,77 @@ +import * as React from 'react'; + +import { hasCookiePreferences, acceptAllCookies } from '../utils/cookies'; +import { Route } from '../utils/routes'; +import Container from './reusable/Container'; +import Heading from './reusable/Heading'; +import Paragraph from './reusable/Paragraph'; +import Link from './reusable/Link'; +import Button from './reusable/Button'; +import { acceptGa4, initGa4 } from '../utils/analytics'; + +const CookieBar = () => { + const [hasCookieBar, setHasCookieBar] = React.useState(false); + + const onAcceptAll = () => { + acceptAllCookies(); + onCookieBarClose(); + acceptGa4(); + }; + + const onCookieBarClose = () => { + setHasCookieBar(false); + }; + + const onGoToPolicy = () => { + window.location.pathname = Route.url(Route.COOKIES); + }; + + // init GA consent + React.useEffect(() => { + initGa4(); + setTimeout(() => { + setHasCookieBar(!hasCookiePreferences()); + }, 1000); + }, []); + + return ( + + + + + Cookie Policy + + This site uses cookies to enhance users' browsing experience and + to collect information about the site's usage. We use both + technical cookies and third-party cookies to analyze user behavior + for analytics. You can find more details by consulting our{' '} + + Cookie Policy. + + + + + + Accept All + + + Customize + + + + + + ); +}; + +export default CookieBar; diff --git a/src/js/components/reusable/Switch.tsx b/src/js/components/reusable/Switch.tsx new file mode 100644 index 0000000..c7b539b --- /dev/null +++ b/src/js/components/reusable/Switch.tsx @@ -0,0 +1,32 @@ +import * as React from 'react'; +import Container from './Container'; + +interface Props { + checked: boolean; + disabled?: boolean; + label: string; + onChange?: () => void; +} + +const Switch = (props: Props) => ( + + + {props.label} + +); + +export default Switch; diff --git a/src/js/declaration.d.ts b/src/js/declaration.d.ts index ba61bf2..0d11a01 100644 --- a/src/js/declaration.d.ts +++ b/src/js/declaration.d.ts @@ -12,3 +12,8 @@ declare module '*.gif' { const value: any; export = value; } + +interface Window { + dataLayer: any[]; + gtag?: (...args: any[]) => void; +} diff --git a/src/js/utils/analytics.ts b/src/js/utils/analytics.ts new file mode 100644 index 0000000..98ccd49 --- /dev/null +++ b/src/js/utils/analytics.ts @@ -0,0 +1,34 @@ +export const initGa4 = () => { + window.dataLayer = window.dataLayer || []; + + gtag('js', new Date()); + gtag('consent', 'default', { + ad_storage: 'denied', + analytics_storage: 'denied', + personalization_storage: 'denied', + functionality_storage: 'granted', // Functional cookies are OK + security_storage: 'granted', + }); +}; + +export const acceptGa4 = () => { + gtag('consent', 'update', { + ad_storage: 'granted', + analytics_storage: 'granted', + personalization_storage: 'granted', + }); + gtag('config', 'G-3SYTZ509HY'); +}; + +export const rejectGa4 = () => { + gtag('consent', 'update', { + ad_storage: 'denied', + analytics_storage: 'denied', + personalization_storage: 'denied', + }); +}; + +const gtag = (...args: any[]) => { + console.log('gtag', args); + window.dataLayer.push(args); +}; diff --git a/src/js/utils/cookies.ts b/src/js/utils/cookies.ts new file mode 100644 index 0000000..9cef620 --- /dev/null +++ b/src/js/utils/cookies.ts @@ -0,0 +1,47 @@ +import Cookie from 'js-cookie'; + +const COOKIE_POLICY = 'cookie-preferences'; + +export interface CookiePreferences { + technical: boolean; + analytics: boolean; +} + +export const getCookiePreferences = (): CookiePreferences => { + const payload = Cookie.get(COOKIE_POLICY); + if (payload) { + return JSON.parse(payload) as CookiePreferences; + } + + return { + technical: true, + analytics: false, + }; +}; + +const setCookiePreferences = (prefs: CookiePreferences) => { + const expiringDate = new Date(); + expiringDate.setFullYear(expiringDate.getFullYear() + 5); + Cookie.set(COOKIE_POLICY, JSON.stringify(prefs), { + expires: expiringDate, + path: '/', + }); +}; + +export const hasCookiePreferences = (): boolean => + Cookie.get(COOKIE_POLICY) !== undefined; + +export const acceptAllCookies = () => { + setCookiePreferences({ technical: true, analytics: true }); +}; + +export const setAnalyticsCookiesPreference = (choice: boolean) => { + const prefs = getCookiePreferences(); + prefs.analytics = choice; + + setCookiePreferences(prefs); +}; + +export const isAnalyticsCookiesConsentGiven = (): boolean => { + return getCookiePreferences().analytics; +}; diff --git a/yarn.lock b/yarn.lock index 2db6ab2..1e1d945 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1143,10 +1143,10 @@ resolved "https://registry.yarnpkg.com/@pkgjs/parseargs/-/parseargs-0.11.0.tgz#a77ea742fab25775145434eb1d2328cf5013ac33" integrity sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg== -"@react-leaflet/core@^3.0.0-rc.1": - version "3.0.0-rc.2" - resolved "https://registry.yarnpkg.com/@react-leaflet/core/-/core-3.0.0-rc.2.tgz#5000770e40d6002298890cb8c31a2b7d83607b20" - integrity sha512-x7prjaw6CM999LrQiAGoRc2OYcoC4DcL3iQo6BMOCkQ0S3jrI75e55hg/J5E++sBgFJFl/6DSfwcOWC0NsEBIg== +"@react-leaflet/core@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@react-leaflet/core/-/core-3.0.0.tgz#34ccc280ce7d8ac5c09f2b3d5fffded450bdf1a2" + integrity sha512-3EWmekh4Nz+pGcr+xjf0KNyYfC3U2JjnkWsh0zcqaexYqmmB5ZhH37kz41JXGmKzpaMZCnPofBBm64i+YrEvGQ== "@remix-run/router@1.19.1": version "1.19.1" @@ -1292,6 +1292,11 @@ resolved "https://registry.yarnpkg.com/@types/geojson/-/geojson-7946.0.15.tgz#f9d55fd5a0aa2de9dc80b1b04e437538b7298868" integrity sha512-9oSxFzDCT2Rj6DfcHF8G++jxBKS7mBqXl5xrRW+Kbvjry6Uduya2iiwqHPhVXpasAVMBYKkEPGgKhd3+/HZ6xA== +"@types/gtag.js@^0.0.20": + version "0.0.20" + resolved "https://registry.yarnpkg.com/@types/gtag.js/-/gtag.js-0.0.20.tgz#e47edabb4ed5ecac90a079275958e6c929d7c08a" + integrity sha512-wwAbk3SA2QeU67unN7zPxjEHmPmlXwZXZvQEpbEUQuMCRGgKyE1m6XDuTUA9b6pCGb/GqJmdfMOY5LuDjJSbbg== + "@types/hast@^3.0.0": version "3.0.4" resolved "https://registry.yarnpkg.com/@types/hast/-/hast-3.0.4.tgz#1d6b39993b82cea6ad783945b0508c25903e15aa" @@ -1299,6 +1304,11 @@ dependencies: "@types/unist" "*" +"@types/js-cookie@^3.0.6": + version "3.0.6" + resolved "https://registry.yarnpkg.com/@types/js-cookie/-/js-cookie-3.0.6.tgz#a04ca19e877687bd449f5ad37d33b104b71fdf95" + integrity sha512-wkw9yd1kEXOPnvEeEV1Go1MmxtBJL0RR79aOTAApecWFVu7w0NNXNqhcWgvw2YgZDYadliXkl14pa3WXw5jlCQ== + "@types/leaflet@^1.9.15": version "1.9.15" resolved "https://registry.yarnpkg.com/@types/leaflet/-/leaflet-1.9.15.tgz#5054d3cfe115da79bffb7a3a70acc6b310bd7c60" @@ -1483,6 +1493,11 @@ ajv@^6.12.4: json-schema-traverse "^0.4.1" uri-js "^4.2.2" +animate.css@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/animate.css/-/animate.css-4.1.1.tgz#614ec5a81131d7e4dc362a58143f7406abd68075" + integrity sha512-+mRmCTv6SbCmtYJCN4faJMNFVNN5EuCTTprDTAo7YzIGji2KADmakjVA3+8mVDkZ2Bf09vayB35lSQIex2+QaQ== + ansi-regex@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" @@ -2491,6 +2506,11 @@ jiti@^1.21.0: resolved "https://registry.yarnpkg.com/jiti/-/jiti-1.21.6.tgz#6c7f7398dd4b3142767f9a168af2f317a428d268" integrity sha512-2yTgeWTWzMWkHu6Jp9NKgePDaYHbntiwvYuuJLbbN9vl7DC9DvXKOB2BC3ZZ92D3cvV/aflH0osDfwpHepQ53w== +js-cookie@^3.0.5: + version "3.0.5" + resolved "https://registry.yarnpkg.com/js-cookie/-/js-cookie-3.0.5.tgz#0b7e2fd0c01552c58ba86e0841f94dc2557dcdbc" + integrity sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw== + "js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" @@ -3608,12 +3628,12 @@ react-is@^16.13.1: resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== -react-leaflet@v5.0.0-rc.1: - version "5.0.0-rc.1" - resolved "https://registry.yarnpkg.com/react-leaflet/-/react-leaflet-5.0.0-rc.1.tgz#a849cc0892236fd080c04f367fe785532d7e4e7d" - integrity sha512-pqxGH/rgUQAUuimnIt/6xLeNAcWymM/kxPpm6v1plXMNHd41Iy6i2sYbzsrRVmspXRT0HtuxQc7jWF+FRHyhYg== +react-leaflet@v5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/react-leaflet/-/react-leaflet-5.0.0.tgz#945d40bad13b69e8606278b19446b00bab57376a" + integrity sha512-CWbTpr5vcHw5bt9i4zSlPEVQdTVcML390TjeDG0cK59z1ylexpqC6M1PJFjV8jD7CF+ACBFsLIDs6DRMoLEofw== dependencies: - "@react-leaflet/core" "^3.0.0-rc.1" + "@react-leaflet/core" "^3.0.0" react-loading-skeleton@^3.5.0: version "3.5.0"