diff --git a/services/web/.storybook/preview.js b/services/web/.storybook/preview.js
index 1fc9cffca6..2b14b3e8b4 100644
--- a/services/web/.storybook/preview.js
+++ b/services/web/.storybook/preview.js
@@ -89,4 +89,9 @@ export const decorators = [withTheme]
window.ExposedSettings = {
maxEntitiesPerProject: 10,
maxUploadSize: 5 * 1024 * 1024,
+ enableSubscriptions: true,
+}
+
+window.user = {
+ id: 'storybook',
}
diff --git a/services/web/frontend/extracted-translations.json b/services/web/frontend/extracted-translations.json
index 751c8e0d22..85a4846a38 100644
--- a/services/web/frontend/extracted-translations.json
+++ b/services/web/frontend/extracted-translations.json
@@ -244,6 +244,7 @@
"unlimited_projects": "",
"unlink_github_repository": "",
"unlinking": "",
+ "upgrade": "",
"upgrade_for_longer_compiles": "",
"upload": "",
"use_your_own_machine": "",
diff --git a/services/web/frontend/js/features/preview/components/preview-error.js b/services/web/frontend/js/features/preview/components/preview-error.js
index 1a0906fea7..088804e3bf 100644
--- a/services/web/frontend/js/features/preview/components/preview-error.js
+++ b/services/web/frontend/js/features/preview/components/preview-error.js
@@ -5,7 +5,7 @@ import PreviewLogsPaneEntry from './preview-logs-pane-entry'
import Icon from '../../../shared/components/icon'
import { useApplicationContext } from '../../../shared/context/application-context'
import { useEditorContext } from '../../../shared/context/editor-context'
-import { startFreeTrial } from '../../../main/account-upgrade'
+import StartFreeTrialButton from '../../../shared/components/start-free-trial-button'
function PreviewError({ name }) {
const { isProjectOwner } = useEditorContext({
@@ -84,10 +84,6 @@ function PreviewError({ name }) {
function TimeoutUpgradePrompt({ isProjectOwner }) {
const { t } = useTranslation()
- function handleStartFreeTrialClick() {
- startFreeTrial('compile-timeout')
- }
-
const timeoutUpgradePromptContent = (
<>
{t('free_accounts_have_timeout_upgrade_to_increase')}
@@ -128,12 +124,11 @@ function TimeoutUpgradePrompt({ isProjectOwner }) {
{isProjectOwner ? (
-
+
) : null}
>
diff --git a/services/web/frontend/js/features/share-project-modal/components/add-collaborators-upgrade.js b/services/web/frontend/js/features/share-project-modal/components/add-collaborators-upgrade.js
index 21977a5a80..cf178c2e13 100644
--- a/services/web/frontend/js/features/share-project-modal/components/add-collaborators-upgrade.js
+++ b/services/web/frontend/js/features/share-project-modal/components/add-collaborators-upgrade.js
@@ -2,12 +2,10 @@ import React, { useState } from 'react'
import { Trans } from 'react-i18next'
import { Button } from 'react-bootstrap'
import Icon from '../../../shared/components/icon'
-import { startFreeTrial, upgradePlan } from '../../../main/account-upgrade'
-import { useShareProjectContext } from './share-project-modal'
+import { upgradePlan } from '../../../main/account-upgrade'
+import StartFreeTrialButton from '../../../shared/components/start-free-trial-button'
export default function AddCollaboratorsUpgrade() {
- const { eventTracking } = useShareProjectContext()
-
const [startedFreeTrial, setStartedFreeTrial] = useState(false)
return (
@@ -54,20 +52,11 @@ export default function AddCollaboratorsUpgrade() {
{window.user.allowedFreeTrial ? (
-
+
) : (
)}
diff --git a/services/web/frontend/js/features/share-project-modal/components/share-project-modal.js b/services/web/frontend/js/features/share-project-modal/components/share-project-modal.js
index 9283046c7d..c93c51631c 100644
--- a/services/web/frontend/js/features/share-project-modal/components/share-project-modal.js
+++ b/services/web/frontend/js/features/share-project-modal/components/share-project-modal.js
@@ -16,9 +16,6 @@ ShareProjectContext.Provider.propTypes = {
isAdmin: PropTypes.bool.isRequired,
updateProject: PropTypes.func.isRequired,
monitorRequest: PropTypes.func.isRequired,
- eventTracking: PropTypes.shape({
- sendMB: PropTypes.func.isRequired,
- }),
inFlight: PropTypes.bool,
setInFlight: PropTypes.func,
error: PropTypes.oneOfType([PropTypes.bool, PropTypes.string]),
@@ -87,7 +84,6 @@ export default function ShareProjectModal({
show,
animation = true,
isAdmin,
- eventTracking,
ide,
}) {
const [inFlight, setInFlight] = useState(false)
@@ -148,7 +144,6 @@ export default function ShareProjectModal({
value={{
isAdmin,
updateProject,
- eventTracking,
monitorRequest,
inFlight,
setInFlight,
@@ -176,7 +171,4 @@ ShareProjectModal.propTypes = {
$scope: PropTypes.object.isRequired,
}).isRequired,
show: PropTypes.bool.isRequired,
- eventTracking: PropTypes.shape({
- sendMB: PropTypes.func.isRequired,
- }),
}
diff --git a/services/web/frontend/js/infrastructure/event-tracking.js b/services/web/frontend/js/infrastructure/event-tracking.js
new file mode 100644
index 0000000000..81efb218d0
--- /dev/null
+++ b/services/web/frontend/js/infrastructure/event-tracking.js
@@ -0,0 +1,13 @@
+import { postJSON } from './fetch-json'
+
+export function send(category, action, label, value) {
+ if (typeof window.ga === 'function') {
+ window.ga('send', 'event', category, action, label, value)
+ }
+}
+
+export function sendMB(key, body = {}) {
+ postJSON(`/event/${key}`, { body }).catch(() => {
+ // ignore errors
+ })
+}
diff --git a/services/web/frontend/js/shared/components/start-free-trial-button.js b/services/web/frontend/js/shared/components/start-free-trial-button.js
new file mode 100644
index 0000000000..ab78a1bc4c
--- /dev/null
+++ b/services/web/frontend/js/shared/components/start-free-trial-button.js
@@ -0,0 +1,59 @@
+import React, { useCallback } from 'react'
+import { useTranslation } from 'react-i18next'
+import { Button } from 'react-bootstrap'
+import PropTypes from 'prop-types'
+import * as eventTracking from '../../infrastructure/event-tracking'
+
+export default function StartFreeTrialButton({
+ buttonStyle = 'info',
+ children,
+ classes = {},
+ setStartedFreeTrial,
+ source,
+}) {
+ const { t } = useTranslation()
+
+ const handleClick = useCallback(
+ event => {
+ event.preventDefault()
+
+ eventTracking.send('subscription-funnel', 'upgraded-free-trial', source)
+
+ const plan = 'collaborator_free_trial_7_days'
+
+ eventTracking.sendMB('subscription-start-trial', { source, plan })
+
+ if (setStartedFreeTrial) {
+ setStartedFreeTrial(true)
+ }
+
+ const params = new URLSearchParams({
+ planCode: plan,
+ ssp: 'true',
+ itm_campaign: source,
+ })
+
+ window.open(`/user/subscription/new?${params}`)
+ },
+ [setStartedFreeTrial, source]
+ )
+
+ return (
+
+ )
+}
+StartFreeTrialButton.propTypes = {
+ buttonStyle: PropTypes.string,
+ children: PropTypes.any,
+ classes: PropTypes.shape({
+ button: PropTypes.string.isRequired,
+ }),
+ setStartedFreeTrial: PropTypes.func,
+ source: PropTypes.string.isRequired,
+}
diff --git a/services/web/frontend/stories/fixtures/context.js b/services/web/frontend/stories/fixtures/context.js
index 342135e5b9..72375ef8fd 100644
--- a/services/web/frontend/stories/fixtures/context.js
+++ b/services/web/frontend/stories/fixtures/context.js
@@ -4,6 +4,7 @@ export function setupContext() {
window.project_id = '1234'
window.user = {
id: 'fake_user',
+ allowedFreeTrial: true,
}
let $scope = {}
if (window._ide) {
diff --git a/services/web/frontend/stories/preview-logs-pane-entry.stories.js b/services/web/frontend/stories/preview-logs-pane-entry.stories.js
index 747b328cfa..c8210c1cd6 100644
--- a/services/web/frontend/stories/preview-logs-pane-entry.stories.js
+++ b/services/web/frontend/stories/preview-logs-pane-entry.stories.js
@@ -100,7 +100,7 @@ function SampleHumanReadableHintComponent() {
}
export default {
- title: 'PreviewLogsPaneEntry',
+ title: 'Preview Logs / Entry',
component: PreviewLogsPaneEntry,
args: {
sourceLocation: {
@@ -113,7 +113,7 @@ export default {
The LaTeX compiler output
* With a lot of details
-Wrapped in an HTML element with
+Wrapped in an HTML element with
preformatted text which is to be presented exactly
as written in the HTML file
@@ -129,7 +129,7 @@ LaTeX Font Info: External font \`cmex10' loaded for size
\\Zlpha
main.tex, line 23
-
+
`,
},
}
diff --git a/services/web/frontend/stories/preview-logs-pane.stories.js b/services/web/frontend/stories/preview-logs-pane.stories.js
new file mode 100644
index 0000000000..b1b8e97b38
--- /dev/null
+++ b/services/web/frontend/stories/preview-logs-pane.stories.js
@@ -0,0 +1,44 @@
+import React from 'react'
+import PreviewLogsPane from '../js/features/preview/components/preview-logs-pane'
+import { EditorProvider } from '../js/shared/context/editor-context'
+import { ApplicationProvider } from '../js/shared/context/application-context'
+import useFetchMock from './hooks/use-fetch-mock'
+
+export const TimedOutError = args => {
+ useFetchMock(fetchMock => {
+ fetchMock.post('express:/event/:key', 202)
+ })
+
+ const ide = {
+ $scope: {
+ $watch: () => () => null,
+ project: {
+ owner: {
+ _id: window.user.id,
+ },
+ },
+ },
+ }
+
+ return (
+
+
+
+
+
+ )
+}
+TimedOutError.args = {
+ errors: {
+ timedout: {},
+ },
+}
+
+export default {
+ title: 'Preview Logs / Pane',
+ component: PreviewLogsPane,
+ argTypes: {
+ onLogEntryLocationClick: { action: 'log entry location' },
+ onClearCache: { action: 'clear cache' },
+ },
+}
diff --git a/services/web/frontend/stories/share-project-modal.stories.js b/services/web/frontend/stories/share-project-modal.stories.js
index 61647983fd..7f8819b0dd 100644
--- a/services/web/frontend/stories/share-project-modal.stories.js
+++ b/services/web/frontend/stories/share-project-modal.stories.js
@@ -71,6 +71,8 @@ const setupFetchMock = () => {
.post('express:/project/:projectId/invite/:inviteId/resend', 200, {
delay,
})
+ // send analytics event
+ .post('express:/event/:key', 200)
}
const ideWithProject = project => {