mirror of
https://github.com/yu-i-i/overleaf-cep.git
synced 2026-05-23 17:19:37 +02:00
Merge pull request #27254 from overleaf/td-project-dashboard-cookie-banner
Implement React cookie banner on project dashboard GitOrigin-RevId: 95d2778d7ce7cb3054a06b06486b815a3453a623
This commit is contained in:
@@ -1,13 +1,13 @@
|
||||
section.cookie-banner.hidden-print.hidden(aria-label='Cookie banner')
|
||||
.cookie-banner-content We only use cookies for essential purposes and to improve your experience on our site. You can find out more in our <a href="/legal#Cookies">cookie policy</a>.
|
||||
section.cookie-banner.hidden-print.hidden(aria-label=translate('cookie_banner'))
|
||||
.cookie-banner-content !{translate('cookie_banner_info', {}, [{ name: 'a', attrs: { href: '/legal#Cookies' }}])}
|
||||
.cookie-banner-actions
|
||||
button(
|
||||
type='button'
|
||||
class='btn btn-link btn-sm'
|
||||
data-ol-cookie-banner-set-consent='essential'
|
||||
) Essential cookies only
|
||||
) #{translate('essential_cookies_only')}
|
||||
button(
|
||||
type='button'
|
||||
class='btn btn-primary btn-sm'
|
||||
data-ol-cookie-banner-set-consent='all'
|
||||
) Accept all cookies
|
||||
) #{translate('accept_all_cookies')}
|
||||
|
||||
@@ -4,7 +4,7 @@ block vars
|
||||
- var suppressNavbar = true
|
||||
- var suppressFooter = true
|
||||
- var suppressSkipToContent = true
|
||||
- var suppressCookieBanner = true
|
||||
- var suppressPugCookieBanner = true
|
||||
|
||||
block content
|
||||
.content.content-alt
|
||||
|
||||
@@ -24,7 +24,7 @@ block body
|
||||
else
|
||||
include layout/fat-footer
|
||||
|
||||
if typeof suppressCookieBanner == 'undefined'
|
||||
if typeof suppressPugCookieBanner == 'undefined'
|
||||
include _cookie_banner
|
||||
|
||||
if bootstrapVersion === 5
|
||||
|
||||
@@ -69,5 +69,5 @@ block body
|
||||
else
|
||||
include layout/fat-footer-react-bootstrap-5
|
||||
|
||||
if typeof suppressCookieBanner === 'undefined'
|
||||
if typeof suppressPugCookieBanner === 'undefined'
|
||||
include _cookie_banner
|
||||
|
||||
@@ -27,7 +27,7 @@ block body
|
||||
else
|
||||
include layout/fat-footer-website-redesign
|
||||
|
||||
if typeof suppressCookieBanner == 'undefined'
|
||||
if typeof suppressPugCookieBanner == 'undefined'
|
||||
include _cookie_banner
|
||||
|
||||
block contactModal
|
||||
|
||||
@@ -2,7 +2,7 @@ extends ../../layout-marketing
|
||||
|
||||
block vars
|
||||
- var suppressFooter = true
|
||||
- var suppressCookieBanner = true
|
||||
- var suppressPugCookieBanner = true
|
||||
- var suppressSkipToContent = true
|
||||
|
||||
block content
|
||||
|
||||
@@ -7,7 +7,7 @@ block vars
|
||||
- var suppressNavbar = true
|
||||
- var suppressFooter = true
|
||||
- var suppressSkipToContent = true
|
||||
- var suppressCookieBanner = true
|
||||
- var suppressPugCookieBanner = true
|
||||
- metadata.robotsNoindexNofollow = true
|
||||
|
||||
block content
|
||||
|
||||
@@ -7,6 +7,7 @@ block vars
|
||||
- const suppressNavContentLinks = true
|
||||
- const suppressNavbar = true
|
||||
- const suppressFooter = true
|
||||
- const suppressPugCookieBanner = true
|
||||
|
||||
block append meta
|
||||
meta(
|
||||
|
||||
@@ -5,7 +5,7 @@ block entrypointVar
|
||||
|
||||
block vars
|
||||
- var suppressFooter = true
|
||||
- var suppressCookieBanner = true
|
||||
- var suppressPugCookieBanner = true
|
||||
- var suppressSkipToContent = true
|
||||
|
||||
block append meta
|
||||
|
||||
@@ -5,7 +5,7 @@ block entrypointVar
|
||||
|
||||
block vars
|
||||
- var suppressFooter = true
|
||||
- var suppressCookieBanner = true
|
||||
- var suppressPugCookieBanner = true
|
||||
- var suppressSkipToContent = true
|
||||
|
||||
block append meta
|
||||
|
||||
@@ -35,6 +35,7 @@
|
||||
"about_to_remove_user_preamble": "",
|
||||
"about_to_trash_projects": "",
|
||||
"abstract": "",
|
||||
"accept_all_cookies": "",
|
||||
"accept_and_continue": "",
|
||||
"accept_change": "",
|
||||
"accept_change_error_description": "",
|
||||
@@ -332,6 +333,8 @@
|
||||
"continue_to": "",
|
||||
"continue_using_free_features": "",
|
||||
"continue_with_free_plan": "",
|
||||
"cookie_banner": "",
|
||||
"cookie_banner_info": "",
|
||||
"copied": "",
|
||||
"copy": "",
|
||||
"copy_code": "",
|
||||
@@ -544,6 +547,7 @@
|
||||
"error_opening_document_detail": "",
|
||||
"error_performing_request": "",
|
||||
"error_processing_file": "",
|
||||
"essential_cookies_only": "",
|
||||
"example_project": "",
|
||||
"existing_plan_active_until_term_end": "",
|
||||
"expand": "",
|
||||
|
||||
@@ -1,53 +0,0 @@
|
||||
import getMeta from '@/utils/meta'
|
||||
|
||||
function loadGA() {
|
||||
if (window.olLoadGA) {
|
||||
window.olLoadGA()
|
||||
}
|
||||
}
|
||||
|
||||
function setConsent(value) {
|
||||
document.querySelector('.cookie-banner').classList.add('hidden')
|
||||
const cookieDomain = getMeta('ol-ExposedSettings').cookieDomain
|
||||
const oneYearInSeconds = 60 * 60 * 24 * 365
|
||||
const cookieAttributes =
|
||||
'; path=/' +
|
||||
'; domain=' +
|
||||
cookieDomain +
|
||||
'; max-age=' +
|
||||
oneYearInSeconds +
|
||||
'; SameSite=Lax; Secure'
|
||||
if (value === 'all') {
|
||||
document.cookie = 'oa=1' + cookieAttributes
|
||||
loadGA()
|
||||
window.dispatchEvent(new CustomEvent('cookie-consent', { detail: true }))
|
||||
} else {
|
||||
document.cookie = 'oa=0' + cookieAttributes
|
||||
window.dispatchEvent(new CustomEvent('cookie-consent', { detail: false }))
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
getMeta('ol-ExposedSettings').gaToken ||
|
||||
getMeta('ol-ExposedSettings').gaTokenV4 ||
|
||||
getMeta('ol-ExposedSettings').propensityId ||
|
||||
getMeta('ol-ExposedSettings').hotjarId
|
||||
) {
|
||||
document
|
||||
.querySelectorAll('[data-ol-cookie-banner-set-consent]')
|
||||
.forEach(el => {
|
||||
el.addEventListener('click', function (e) {
|
||||
e.preventDefault()
|
||||
const consentType = el.getAttribute('data-ol-cookie-banner-set-consent')
|
||||
setConsent(consentType)
|
||||
})
|
||||
})
|
||||
|
||||
const oaCookie = document.cookie.split('; ').find(c => c.startsWith('oa='))
|
||||
if (!oaCookie) {
|
||||
const cookieBannerEl = document.querySelector('.cookie-banner')
|
||||
if (cookieBannerEl) {
|
||||
cookieBannerEl.classList.remove('hidden')
|
||||
}
|
||||
}
|
||||
}
|
||||
32
services/web/frontend/js/features/cookie-banner/index.ts
Normal file
32
services/web/frontend/js/features/cookie-banner/index.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
import {
|
||||
CookieConsentValue,
|
||||
cookieBannerRequired,
|
||||
hasMadeCookieChoice,
|
||||
setConsent,
|
||||
} from '@/features/cookie-banner/utils'
|
||||
|
||||
function toggleCookieBanner(hidden: boolean) {
|
||||
const cookieBannerEl = document.querySelector('.cookie-banner')
|
||||
if (cookieBannerEl) {
|
||||
cookieBannerEl.classList.toggle('hidden', hidden)
|
||||
}
|
||||
}
|
||||
|
||||
if (cookieBannerRequired()) {
|
||||
document
|
||||
.querySelectorAll('[data-ol-cookie-banner-set-consent]')
|
||||
.forEach(el => {
|
||||
el.addEventListener('click', function (e) {
|
||||
e.preventDefault()
|
||||
toggleCookieBanner(true)
|
||||
const consentType = el.getAttribute(
|
||||
'data-ol-cookie-banner-set-consent'
|
||||
) as CookieConsentValue | null
|
||||
setConsent(consentType)
|
||||
})
|
||||
})
|
||||
|
||||
if (!hasMadeCookieChoice()) {
|
||||
toggleCookieBanner(false)
|
||||
}
|
||||
}
|
||||
43
services/web/frontend/js/features/cookie-banner/utils.ts
Normal file
43
services/web/frontend/js/features/cookie-banner/utils.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
import getMeta from '@/utils/meta'
|
||||
|
||||
export type CookieConsentValue = 'all' | 'essential'
|
||||
|
||||
function loadGA() {
|
||||
if (window.olLoadGA) {
|
||||
window.olLoadGA()
|
||||
}
|
||||
}
|
||||
|
||||
export function setConsent(value: CookieConsentValue | null) {
|
||||
const cookieDomain = getMeta('ol-ExposedSettings').cookieDomain
|
||||
const oneYearInSeconds = 60 * 60 * 24 * 365
|
||||
const cookieAttributes =
|
||||
'; path=/' +
|
||||
'; domain=' +
|
||||
cookieDomain +
|
||||
'; max-age=' +
|
||||
oneYearInSeconds +
|
||||
'; SameSite=Lax; Secure'
|
||||
if (value === 'all') {
|
||||
document.cookie = 'oa=1' + cookieAttributes
|
||||
loadGA()
|
||||
window.dispatchEvent(new CustomEvent('cookie-consent', { detail: true }))
|
||||
} else {
|
||||
document.cookie = 'oa=0' + cookieAttributes
|
||||
window.dispatchEvent(new CustomEvent('cookie-consent', { detail: false }))
|
||||
}
|
||||
}
|
||||
|
||||
export function cookieBannerRequired() {
|
||||
const exposedSettings = getMeta('ol-ExposedSettings')
|
||||
return Boolean(
|
||||
exposedSettings.gaToken ||
|
||||
exposedSettings.gaTokenV4 ||
|
||||
exposedSettings.propensityId ||
|
||||
exposedSettings.hotjarId
|
||||
)
|
||||
}
|
||||
|
||||
export function hasMadeCookieChoice() {
|
||||
return document.cookie.split('; ').some(c => c.startsWith('oa='))
|
||||
}
|
||||
@@ -20,6 +20,7 @@ import Footer from '@/features/ui/components/bootstrap-5/footer/footer'
|
||||
import SidebarDsNav from '@/features/project-list/components/sidebar/sidebar-ds-nav'
|
||||
import SystemMessages from '@/shared/components/system-messages'
|
||||
import overleafLogo from '@/shared/svgs/overleaf-a-ds-solution-mallard.svg'
|
||||
import CookieBanner from '@/shared/components/cookie-banner'
|
||||
|
||||
export function ProjectListDsNav() {
|
||||
const navbarProps = getMeta('ol-navbar')
|
||||
@@ -125,6 +126,7 @@ export function ProjectListDsNav() {
|
||||
</div>
|
||||
<Footer {...footerProps} />
|
||||
</div>
|
||||
<CookieBanner />
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
@@ -18,6 +18,7 @@ import Footer from '@/features/ui/components/bootstrap-5/footer/footer'
|
||||
import WelcomePageContent from '@/features/project-list/components/welcome-page-content'
|
||||
import { ProjectListDsNav } from '@/features/project-list/components/project-list-ds-nav'
|
||||
import { DsNavStyleProvider } from '@/features/project-list/components/use-is-ds-nav'
|
||||
import CookieBanner from '@/shared/components/cookie-banner'
|
||||
|
||||
function ProjectListRoot() {
|
||||
const { isReady } = useWaitForI18n()
|
||||
@@ -88,9 +89,12 @@ function ProjectListPageContent() {
|
||||
|
||||
if (totalProjectsCount === 0) {
|
||||
return (
|
||||
<DefaultPageContentWrapper>
|
||||
<WelcomePageContent />
|
||||
</DefaultPageContentWrapper>
|
||||
<>
|
||||
<DefaultPageContentWrapper>
|
||||
<WelcomePageContent />
|
||||
</DefaultPageContentWrapper>
|
||||
<CookieBanner />
|
||||
</>
|
||||
)
|
||||
}
|
||||
return (
|
||||
|
||||
58
services/web/frontend/js/shared/components/cookie-banner.tsx
Normal file
58
services/web/frontend/js/shared/components/cookie-banner.tsx
Normal file
@@ -0,0 +1,58 @@
|
||||
import OLButton from '@/features/ui/components/ol/ol-button'
|
||||
import { Trans, useTranslation } from 'react-i18next'
|
||||
import React, { useState } from 'react'
|
||||
import {
|
||||
CookieConsentValue,
|
||||
cookieBannerRequired,
|
||||
hasMadeCookieChoice,
|
||||
setConsent,
|
||||
} from '@/features/cookie-banner/utils'
|
||||
|
||||
function CookieBanner() {
|
||||
const { t } = useTranslation()
|
||||
const [hidden, setHidden] = useState(
|
||||
() => !cookieBannerRequired() || hasMadeCookieChoice()
|
||||
)
|
||||
|
||||
function makeCookieChoice(value: CookieConsentValue) {
|
||||
setConsent(value)
|
||||
setHidden(true)
|
||||
}
|
||||
|
||||
if (hidden) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<section
|
||||
className="cookie-banner hidden-print"
|
||||
aria-label={t('cookie_banner')}
|
||||
>
|
||||
<div className="cookie-banner-content">
|
||||
<Trans
|
||||
i18nKey="cookie_banner_info"
|
||||
// eslint-disable-next-line react/jsx-key, jsx-a11y/anchor-has-content
|
||||
components={[<a href="/legal#Cookies" />]}
|
||||
/>
|
||||
</div>
|
||||
<div className="cookie-banner-actions">
|
||||
<OLButton
|
||||
variant="link"
|
||||
size="sm"
|
||||
onClick={() => makeCookieChoice('essential')}
|
||||
>
|
||||
{t('essential_cookies_only')}
|
||||
</OLButton>
|
||||
<OLButton
|
||||
variant="primary"
|
||||
size="sm"
|
||||
onClick={() => makeCookieChoice('all')}
|
||||
>
|
||||
{t('accept_all_cookies')}
|
||||
</OLButton>
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
export default CookieBanner
|
||||
@@ -255,6 +255,12 @@
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
> * {
|
||||
@include media-breakpoint-up(md) {
|
||||
border-left: 1px solid var(--border-divider);
|
||||
}
|
||||
}
|
||||
|
||||
.project-ds-nav-content {
|
||||
flex-grow: 1;
|
||||
overflow-y: auto;
|
||||
@@ -263,10 +269,20 @@
|
||||
|
||||
@include media-breakpoint-up(md) {
|
||||
border-top-left-radius: var(--border-radius-large);
|
||||
border-left: 1px solid var(--border-divider);
|
||||
border-top: 1px solid var(--border-divider);
|
||||
}
|
||||
}
|
||||
|
||||
.cookie-banner {
|
||||
position: static;
|
||||
background-color: var(--bg-light-primary);
|
||||
|
||||
// Remove the parts of the shadow that stick out of the sides
|
||||
clip-path: inset(-13px 0 0 0);
|
||||
|
||||
// Prevent the cookie banner being overlaid on top of the navigation
|
||||
z-index: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -38,6 +38,7 @@
|
||||
"about_to_trash_projects": "You are about to trash the following projects:",
|
||||
"abstract": "Abstract",
|
||||
"accept": "Accept",
|
||||
"accept_all_cookies": "Accept all cookies",
|
||||
"accept_and_continue": "Accept and continue",
|
||||
"accept_change": "Accept change",
|
||||
"accept_change_error_description": "There was an error accepting a track change. Please try again in a few moments.",
|
||||
@@ -433,6 +434,8 @@
|
||||
"continue_using_free_features": "Continue using our free features",
|
||||
"continue_with_free_plan": "Continue with free plan",
|
||||
"continue_with_service": "Continue with __service__",
|
||||
"cookie_banner": "Cookie banner",
|
||||
"cookie_banner_info": "We only use cookies for essential purposes and to improve your experience on our site. You can find out more in our <0>cookie policy</0>.",
|
||||
"copied": "Copied",
|
||||
"copy": "Copy",
|
||||
"copy_code": "Copy code",
|
||||
@@ -700,6 +703,7 @@
|
||||
"error_performing_request": "An error has occurred while performing your request.",
|
||||
"error_processing_file": "Sorry, something went wrong processing this file. Please try again.",
|
||||
"es": "Spanish",
|
||||
"essential_cookies_only": "Essential cookies only",
|
||||
"estimated_number_of_overleaf_users": "Estimated number of __appName__ users",
|
||||
"every": "per",
|
||||
"everything_in_free_plus": "Everything in Free, plus…",
|
||||
|
||||
@@ -27,5 +27,6 @@ declare global {
|
||||
gtag?: (...args: any) => void
|
||||
|
||||
propensity?: (propensityId?: string) => void
|
||||
olLoadGA?: () => void
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user