Quellcode durchsuchen

Merge pull request #15046 from overleaf/jdt-gramr-ad

creating notification for grammarly collab ad

GitOrigin-RevId: 984869bb561a2b2d3210f1018b93b24aedef9783
Jimmy Domagala-Tang vor 2 Jahren
Ursprung
Commit
e57806b204

+ 2 - 0
services/web/app/views/project/editor.pug

@@ -66,6 +66,8 @@ block content
 					.system-message-content
 					.system-message-content
 						| {{htmlContent}}
 						| {{htmlContent}}
 
 
+			grammarly-advert()
+
 			if hasFeature('saas')
 			if hasFeature('saas')
 				legacy-editor-warning(delay=10000)
 				legacy-editor-warning(delay=10000)
 
 

+ 72 - 0
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 (
+    <div className="alert alert-info grammarly-advert" role="alert">
+      <div className="grammarly-advert-container">
+        <div className="advert-content">
+          <p>
+            Overleafers get a limited-time 30% discount on Grammarly Premium.
+            (Hurry! Offer ends December 16.)
+          </p>
+          <a
+            className="advert-link"
+            onClick={() => 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
+          </a>
+        </div>
+        <div className="grammarly-notification-close-btn">
+          <button aria-label="Close" onClick={handleClose}>
+            <MaterialIcon type="close" />
+          </button>
+        </div>
+      </div>
+    </div>
+  )
+}

+ 9 - 0
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))
+)

+ 1 - 0
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/editor-navigation-toolbar/controllers/editor-navigation-toolbar-controller'
 import './features/pdf-preview/controllers/pdf-preview-controller'
 import './features/pdf-preview/controllers/pdf-preview-controller'
 import './features/share-project-modal/controllers/react-share-project-modal-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/history/controllers/history-controller'
 import './features/editor-left-menu/controllers/editor-left-menu-controller'
 import './features/editor-left-menu/controllers/editor-left-menu-controller'
 import { cleanupServiceWorker } from './utils/service-worker-cleanup'
 import { cleanupServiceWorker } from './utils/service-worker-cleanup'

+ 9 - 0
services/web/frontend/stylesheets/app/editor.less

@@ -711,6 +711,7 @@ CodeMirror
   }
   }
 }
 }
 
 
+.grammarly-advert,
 .legacy-editor-warning {
 .legacy-editor-warning {
   width: 500px;
   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'] {
 grammarly-extension[data-grammarly-shadow-root='true'] {
   z-index: 1;
   z-index: 1;
 }
 }