diff --git a/services/web/app/views/project/editor.pug b/services/web/app/views/project/editor.pug index d4f49be53e..2d5543483f 100644 --- a/services/web/app/views/project/editor.pug +++ b/services/web/app/views/project/editor.pug @@ -66,6 +66,8 @@ block content .system-message-content | {{htmlContent}} + grammarly-advert() + if hasFeature('saas') legacy-editor-warning(delay=10000) diff --git a/services/web/frontend/js/features/source-editor/components/grammarly-advert.tsx b/services/web/frontend/js/features/source-editor/components/grammarly-advert.tsx new file mode 100644 index 0000000000..4b86917e26 --- /dev/null +++ b/services/web/frontend/js/features/source-editor/components/grammarly-advert.tsx @@ -0,0 +1,72 @@ +import { useCallback, useEffect, useState } from 'react' +import MaterialIcon from '@/shared/components/material-icon' +import * as eventTracking from '../../../infrastructure/event-tracking' +import customLocalStorage from '../../../infrastructure/local-storage' +import grammarlyExtensionPresent from '../../../shared/utils/grammarly' +export default function GrammarlyAdvert() { + const [show, setShow] = useState(false) + + // grammarly can take some time to load, and wont tell us when they do... so we need to run the check after a bit of time + const [grammarlyInstalled, setGrammarlyInstalled] = useState(false) + useEffect(() => { + const timer = setTimeout( + () => setGrammarlyInstalled(grammarlyExtensionPresent()), + 5000 + ) + return () => clearTimeout(timer) + }, []) + + useEffect(() => { + const hasDismissedGrammarlyAdvert = customLocalStorage.getItem( + 'editor.has_dismissed_grammarly_advert' + ) + // promotion ends on december 16th, 2023 at 00:00 UTC + const promotionEnded = + new Date() > new Date(Date.UTC(2023, 11, 16, 0, 0, 0)) + + const showGrammarlyAdvert = + grammarlyInstalled && !hasDismissedGrammarlyAdvert && !promotionEnded + + if (showGrammarlyAdvert) { + eventTracking.sendMB('grammarly-advert-shown') + setShow(true) + } + }, [grammarlyInstalled, setShow]) + + const handleClose = useCallback(() => { + setShow(false) + customLocalStorage.setItem('editor.has_dismissed_grammarly_advert', true) + eventTracking.sendMB('grammarly-advert-dismissed') + }, []) + + if (!show) { + return null + } + + return ( +
+
+
+

+ Overleafers get a limited-time 30% discount on Grammarly Premium. + (Hurry! Offer ends December 16.) +

+ eventTracking.sendMB('grammarly-advert-clicked')} + href="https://www.grammarly.com/upgrade?transaction_id=10264119b53f745bed2cdca3c3e4a5&affiliateNetwork=ho&utm_campaign=Overleaf&affiliateID=142242&utm_source=program" + target="_blank" + rel="noopener" + > + Claim my discount + +
+
+ +
+
+
+ ) +} diff --git a/services/web/frontend/js/features/source-editor/controllers/grammarly-advert-controller.js b/services/web/frontend/js/features/source-editor/controllers/grammarly-advert-controller.js new file mode 100644 index 0000000000..9e27cf2ec0 --- /dev/null +++ b/services/web/frontend/js/features/source-editor/controllers/grammarly-advert-controller.js @@ -0,0 +1,9 @@ +import App from '../../../base' +import { react2angular } from 'react2angular' +import { rootContext } from '../../../shared/context/root-context' +import GrammarlyAdvert from '../components/grammarly-advert' + +App.component( + 'grammarlyAdvert', + react2angular(rootContext.use(GrammarlyAdvert)) +) diff --git a/services/web/frontend/js/ide.js b/services/web/frontend/js/ide.js index c0b110a385..94f8b8df92 100644 --- a/services/web/frontend/js/ide.js +++ b/services/web/frontend/js/ide.js @@ -56,6 +56,7 @@ import './shared/context/controllers/root-context-controller' import './features/editor-navigation-toolbar/controllers/editor-navigation-toolbar-controller' import './features/pdf-preview/controllers/pdf-preview-controller' import './features/share-project-modal/controllers/react-share-project-modal-controller' +import './features/source-editor/controllers/grammarly-advert-controller' import './features/history/controllers/history-controller' import './features/editor-left-menu/controllers/editor-left-menu-controller' import { cleanupServiceWorker } from './utils/service-worker-cleanup' diff --git a/services/web/frontend/stylesheets/app/editor.less b/services/web/frontend/stylesheets/app/editor.less index ecdf04a268..623d76a05a 100644 --- a/services/web/frontend/stylesheets/app/editor.less +++ b/services/web/frontend/stylesheets/app/editor.less @@ -711,6 +711,7 @@ CodeMirror } } +.grammarly-advert, .legacy-editor-warning { width: 500px; @@ -740,6 +741,14 @@ CodeMirror } } +.grammarly-advert-container { + display: flex; + + .grammarly-notification-close-btn > button { + background-color: @ol-blue; + } +} + grammarly-extension[data-grammarly-shadow-root='true'] { z-index: 1; }