diff --git a/services/web/app/src/Features/Project/ProjectController.js b/services/web/app/src/Features/Project/ProjectController.js
index 485eedaef7..ad7cc45934 100644
--- a/services/web/app/src/Features/Project/ProjectController.js
+++ b/services/web/app/src/Features/Project/ProjectController.js
@@ -607,6 +607,13 @@ const ProjectController = {
)
}
},
+ tprPromptAssignment(cb) {
+ if (anonymous) {
+ cb()
+ } else {
+ SplitTestHandler.getAssignment(req, res, 'bib-file-tpr-prompt', cb)
+ }
+ },
compileLogEventsAssignment(cb) {
SplitTestHandler.getAssignment(req, res, 'compile-log-events', cb)
},
diff --git a/services/web/app/src/Features/Tutorial/TutorialController.js b/services/web/app/src/Features/Tutorial/TutorialController.js
index 5c5a3ba307..1390e18931 100644
--- a/services/web/app/src/Features/Tutorial/TutorialController.js
+++ b/services/web/app/src/Features/Tutorial/TutorialController.js
@@ -7,6 +7,7 @@ const VALID_KEYS = [
'table-generator-promotion',
'writefull-integration',
'writefull-oauth-promotion',
+ 'bib-file-tpr-prompt',
]
async function completeTutorial(req, res, next) {
diff --git a/services/web/config/settings.defaults.js b/services/web/config/settings.defaults.js
index 0c6b456264..d0c80dd274 100644
--- a/services/web/config/settings.defaults.js
+++ b/services/web/config/settings.defaults.js
@@ -899,6 +899,7 @@ module.exports = {
tprFileViewRefreshError: [],
tprFileViewRefreshButton: [],
tprFileViewNotOriginalImporter: [],
+ newFilePromotions: [],
contactUsModal: [],
editorToolbarButtons: [],
sourceEditorExtensions: [],
@@ -907,7 +908,7 @@ module.exports = {
sourceEditorCompletionSources: [],
sourceEditorSymbolPalette: [],
sourceEditorToolbarComponents: [],
- writefullEditorPromotion: [],
+ editorPromotions: [],
langFeedbackLinkingWidgets: [],
integrationLinkingWidgets: [],
referenceLinkingWidgets: [],
diff --git a/services/web/frontend/extracted-translations.json b/services/web/frontend/extracted-translations.json
index 5ece4880a8..34ab4a8c21 100644
--- a/services/web/frontend/extracted-translations.json
+++ b/services/web/frontend/extracted-translations.json
@@ -56,6 +56,7 @@
"add_your_first_group_member_now": "",
"added_by_on": "",
"adding": "",
+ "adding_a_bibliography": "",
"additional_certificate": "",
"additional_licenses": "",
"address_line_1": "",
@@ -106,6 +107,7 @@
"beta_program_already_participating": "",
"beta_program_benefits": "",
"beta_program_not_participating": "",
+ "better_bibliographies": "",
"binary_history_error": "",
"blank_project": "",
"blocked_filename": "",
@@ -333,6 +335,7 @@
"duplicate_file": "",
"duplicate_projects": "",
"each_user_will_have_access_to": "",
+ "easily_import_and_sync_your_references": "",
"easily_manage_your_project_files_everywhere": "",
"edit": "",
"edit_dictionary": "",
diff --git a/services/web/frontend/js/features/file-tree/components/file-tree-create/modes/file-tree-create-new-doc.jsx b/services/web/frontend/js/features/file-tree/components/file-tree-create/modes/file-tree-create-new-doc.jsx
index c47a8b5f8e..0f11ef1761 100644
--- a/services/web/frontend/js/features/file-tree/components/file-tree-create/modes/file-tree-create-new-doc.jsx
+++ b/services/web/frontend/js/features/file-tree/components/file-tree-create/modes/file-tree-create-new-doc.jsx
@@ -5,12 +5,14 @@ import { useFileTreeCreateName } from '../../../contexts/file-tree-create-name'
import { useFileTreeCreateForm } from '../../../contexts/file-tree-create-form'
import * as eventTracking from '../../../../../infrastructure/event-tracking'
import ErrorMessage from '../error-message'
+import importOverleafModules from '../../../../../../macros/import-overleaf-module.macro'
+
+const newFilePromotionComponents = importOverleafModules('newFilePromotions')
export default function FileTreeCreateNewDoc() {
const { name, validName } = useFileTreeCreateName()
const { setValid } = useFileTreeCreateForm()
const { error, finishCreatingDoc, inFlight } = useFileTreeActionable()
-
// form validation: name is valid
useEffect(() => {
setValid(validName)
@@ -33,8 +35,12 @@ export default function FileTreeCreateNewDoc() {
return (
)
}
diff --git a/services/web/frontend/js/features/source-editor/components/source-editor.tsx b/services/web/frontend/js/features/source-editor/components/source-editor.tsx
index 7466912998..8fdd2ba14b 100644
--- a/services/web/frontend/js/features/source-editor/components/source-editor.tsx
+++ b/services/web/frontend/js/features/source-editor/components/source-editor.tsx
@@ -5,9 +5,7 @@ import { ErrorBoundaryFallback } from '../../../shared/components/error-boundary
import importOverleafModules from '../../../../macros/import-overleaf-module.macro'
import GrammarlyAdvert from './grammarly-advert'
-const writefullPromotion = importOverleafModules(
- 'writefullEditorPromotion'
-) as {
+const editorPromotions = importOverleafModules('editorPromotions') as {
import: { default: ElementType }
path: string
}[]
@@ -20,7 +18,7 @@ const CodeMirrorEditor = lazy(
function SourceEditor() {
return (
}>
- {writefullPromotion.map(({ import: { default: Component }, path }) => (
+ {editorPromotions.map(({ import: { default: Component }, path }) => (
))}
diff --git a/services/web/frontend/js/shared/hooks/promotions/use-tutorial.tsx b/services/web/frontend/js/shared/hooks/promotions/use-tutorial.tsx
index 5d98de682e..5b898fbe5f 100644
--- a/services/web/frontend/js/shared/hooks/promotions/use-tutorial.tsx
+++ b/services/web/frontend/js/shared/hooks/promotions/use-tutorial.tsx
@@ -50,26 +50,35 @@ export const useTutorial = (
}, [completeTutorial])
// try to show the popup if we don't already have one showing, returns true if it can show, false if it can't
- const tryShowingPopup = useCallback(() => {
- if (currentPopup === null) {
+ const tryShowingPopup = useCallback(
+ (eventName: string = 'promo-prompt') => {
+ if (currentPopup === null) {
+ setCurrentPopup(tutorialKey)
+ setShowPopup(true)
+ eventTracking.sendMB(eventName, eventData)
+ return true
+ }
+ return false
+ },
+ [currentPopup, setCurrentPopup, tutorialKey, eventData]
+ )
+
+ const clearPopup = useCallback(() => {
+ // popups should only clear themselves, in cases they need to cleanup or shouldnt show anymore
+ // allow forcing the clear if needed, eg: higher prio alert needs to show
+ if (currentPopup === tutorialKey) {
+ setCurrentPopup(null)
+ setShowPopup(false)
+ }
+ }, [setCurrentPopup, setShowPopup, currentPopup, tutorialKey])
+
+ const clearAndShow = useCallback(
+ (eventName: string = 'promo-prompt') => {
setCurrentPopup(tutorialKey)
setShowPopup(true)
- eventTracking.sendMB('promo-prompt', eventData)
- return true
- }
- return false
- }, [currentPopup, setCurrentPopup, tutorialKey, eventData])
-
- const clearPopup = useCallback(
- (force: boolean = false) => {
- // popups should only clear themselves, in cases they need to cleanup or shouldnt show anymore
- // allow forcing the clear if needed, eg: higher prio alert needs to show
- if (force || currentPopup === tutorialKey) {
- setCurrentPopup(null)
- setShowPopup(false)
- }
+ eventTracking.sendMB(eventName, eventData)
},
- [setCurrentPopup, setShowPopup, currentPopup, tutorialKey]
+ [setCurrentPopup, setShowPopup, tutorialKey, eventData]
)
return {
@@ -78,6 +87,7 @@ export const useTutorial = (
maybeLater,
tryShowingPopup,
clearPopup,
+ clearAndShow,
showPopup,
}
}
diff --git a/services/web/frontend/stylesheets/main-style.less b/services/web/frontend/stylesheets/main-style.less
index e87af30da9..6426fc3db0 100644
--- a/services/web/frontend/stylesheets/main-style.less
+++ b/services/web/frontend/stylesheets/main-style.less
@@ -154,3 +154,4 @@
@import 'modules/group-settings.less';
@import 'modules/overleaf-integration.less';
@import 'modules/writefull.less';
+@import 'modules/third-party-references.less';
diff --git a/services/web/frontend/stylesheets/modules/third-party-references.less b/services/web/frontend/stylesheets/modules/third-party-references.less
new file mode 100644
index 0000000000..ec44a7d8c6
--- /dev/null
+++ b/services/web/frontend/stylesheets/modules/third-party-references.less
@@ -0,0 +1,29 @@
+/* Animation keyframes */
+@keyframes slide-in {
+ 0% {
+ transform: translateY(25%);
+ opacity: 0;
+ bottom: 12px;
+ }
+ 100% {
+ transform: translateY(0);
+ opacity: 1;
+ bottom: 16px;
+ }
+}
+
+.fade-slide-in {
+ opacity: 0;
+ transition: opacity 0.3s ease; /* Smooth opacity transition */
+ animation: slide-in 0.3s ease-in-out forwards;
+ animation-delay: 300ms;
+}
+
+.tpr-editor-prompt-container {
+ position: absolute;
+ z-index: 1;
+ bottom: 16px;
+ right: 16px;
+ width: 90%;
+ max-width: @popover-dark-max-width;
+}
diff --git a/services/web/locales/en.json b/services/web/locales/en.json
index feb4073019..fdcf40d44b 100644
--- a/services/web/locales/en.json
+++ b/services/web/locales/en.json
@@ -77,6 +77,7 @@
"added": "added",
"added_by_on": "Added by __name__ on __date__",
"adding": "Adding",
+ "adding_a_bibliography": "Adding a bibliography?",
"additional_certificate": "Additional certificate",
"additional_licenses": "Your subscription includes <0>__additionalLicenses__0> additional license(s) for a total of <1>__totalLicenses__1> licenses.",
"address": "Address",
@@ -163,6 +164,7 @@
"beta_program_not_participating": "You are not enrolled in the Beta Program",
"beta_program_opt_in_action": "Opt-In to Beta Program",
"beta_program_opt_out_action": "Opt-Out of Beta Program",
+ "better_bibliographies": "Better bibliographies",
"bibliographies": "Bibliographies",
"binary_history_error": "Preview not available for this file type",
"blank_project": "Blank Project",
@@ -469,6 +471,7 @@
"duplicate_file": "Duplicate File",
"duplicate_projects": "This user has projects with duplicate names",
"each_user_will_have_access_to": "Each user will have access to",
+ "easily_import_and_sync_your_references": "Easily import and sync your references from Zotero or Mendeley when you upgrade your Overleaf plan.",
"easily_manage_your_project_files_everywhere": "Easily manage your project files, everywhere",
"edit": "Edit",
"edit_dictionary": "Edit Dictionary",
diff --git a/services/web/public/img/third-party-references/book-illlustration.svg b/services/web/public/img/third-party-references/book-illlustration.svg
new file mode 100644
index 0000000000..e79278cf9a
--- /dev/null
+++ b/services/web/public/img/third-party-references/book-illlustration.svg
@@ -0,0 +1,7 @@
+