Add StartFreeTrialButton

GitOrigin-RevId: dd9ab4bb12e8b9071b2a869e2d452945b49d9cd7
This commit is contained in:
Alf Eaton 2021-04-30 21:20:16 +01:00 committed by Copybot
parent e6acfda6e3
commit e832c9ed70
11 changed files with 142 additions and 41 deletions

View file

@ -89,4 +89,9 @@ export const decorators = [withTheme]
window.ExposedSettings = { window.ExposedSettings = {
maxEntitiesPerProject: 10, maxEntitiesPerProject: 10,
maxUploadSize: 5 * 1024 * 1024, maxUploadSize: 5 * 1024 * 1024,
enableSubscriptions: true,
}
window.user = {
id: 'storybook',
} }

View file

@ -244,6 +244,7 @@
"unlimited_projects": "", "unlimited_projects": "",
"unlink_github_repository": "", "unlink_github_repository": "",
"unlinking": "", "unlinking": "",
"upgrade": "",
"upgrade_for_longer_compiles": "", "upgrade_for_longer_compiles": "",
"upload": "", "upload": "",
"use_your_own_machine": "", "use_your_own_machine": "",

View file

@ -5,7 +5,7 @@ import PreviewLogsPaneEntry from './preview-logs-pane-entry'
import Icon from '../../../shared/components/icon' import Icon from '../../../shared/components/icon'
import { useApplicationContext } from '../../../shared/context/application-context' import { useApplicationContext } from '../../../shared/context/application-context'
import { useEditorContext } from '../../../shared/context/editor-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 }) { function PreviewError({ name }) {
const { isProjectOwner } = useEditorContext({ const { isProjectOwner } = useEditorContext({
@ -84,10 +84,6 @@ function PreviewError({ name }) {
function TimeoutUpgradePrompt({ isProjectOwner }) { function TimeoutUpgradePrompt({ isProjectOwner }) {
const { t } = useTranslation() const { t } = useTranslation()
function handleStartFreeTrialClick() {
startFreeTrial('compile-timeout')
}
const timeoutUpgradePromptContent = ( const timeoutUpgradePromptContent = (
<> <>
<p>{t('free_accounts_have_timeout_upgrade_to_increase')}</p> <p>{t('free_accounts_have_timeout_upgrade_to_increase')}</p>
@ -128,12 +124,11 @@ function TimeoutUpgradePrompt({ isProjectOwner }) {
</div> </div>
{isProjectOwner ? ( {isProjectOwner ? (
<p className="text-center"> <p className="text-center">
<button <StartFreeTrialButton
className="btn btn-success row-spaced-small" source="compile-timeout"
onClick={handleStartFreeTrialClick} buttonStyle="success"
> classes={{ button: 'row-spaced-small' }}
{t('start_free_trial')} />
</button>
</p> </p>
) : null} ) : null}
</> </>

View file

@ -2,12 +2,10 @@ import React, { useState } from 'react'
import { Trans } from 'react-i18next' import { Trans } from 'react-i18next'
import { Button } from 'react-bootstrap' import { Button } from 'react-bootstrap'
import Icon from '../../../shared/components/icon' import Icon from '../../../shared/components/icon'
import { startFreeTrial, upgradePlan } from '../../../main/account-upgrade' import { upgradePlan } from '../../../main/account-upgrade'
import { useShareProjectContext } from './share-project-modal' import StartFreeTrialButton from '../../../shared/components/start-free-trial-button'
export default function AddCollaboratorsUpgrade() { export default function AddCollaboratorsUpgrade() {
const { eventTracking } = useShareProjectContext()
const [startedFreeTrial, setStartedFreeTrial] = useState(false) const [startedFreeTrial, setStartedFreeTrial] = useState(false)
return ( return (
@ -54,20 +52,11 @@ export default function AddCollaboratorsUpgrade() {
<p className="text-center row-spaced-thin"> <p className="text-center row-spaced-thin">
{window.user.allowedFreeTrial ? ( {window.user.allowedFreeTrial ? (
<Button <StartFreeTrialButton
bsStyle="success" buttonStyle="success"
onClick={() => { setStartedFreeTrial={setStartedFreeTrial}
startFreeTrial( source="projectMembers"
'projectMembers', />
undefined,
undefined,
eventTracking
)
setStartedFreeTrial(true)
}}
>
<Trans i18nKey="start_free_trial" />
</Button>
) : ( ) : (
<Button <Button
bsStyle="success" bsStyle="success"
@ -76,7 +65,7 @@ export default function AddCollaboratorsUpgrade() {
setStartedFreeTrial(true) setStartedFreeTrial(true)
}} }}
> >
<Trans i18nKey="start_free_trial" /> <Trans i18nKey="upgrade" />
</Button> </Button>
)} )}
</p> </p>

View file

@ -16,9 +16,6 @@ ShareProjectContext.Provider.propTypes = {
isAdmin: PropTypes.bool.isRequired, isAdmin: PropTypes.bool.isRequired,
updateProject: PropTypes.func.isRequired, updateProject: PropTypes.func.isRequired,
monitorRequest: PropTypes.func.isRequired, monitorRequest: PropTypes.func.isRequired,
eventTracking: PropTypes.shape({
sendMB: PropTypes.func.isRequired,
}),
inFlight: PropTypes.bool, inFlight: PropTypes.bool,
setInFlight: PropTypes.func, setInFlight: PropTypes.func,
error: PropTypes.oneOfType([PropTypes.bool, PropTypes.string]), error: PropTypes.oneOfType([PropTypes.bool, PropTypes.string]),
@ -87,7 +84,6 @@ export default function ShareProjectModal({
show, show,
animation = true, animation = true,
isAdmin, isAdmin,
eventTracking,
ide, ide,
}) { }) {
const [inFlight, setInFlight] = useState(false) const [inFlight, setInFlight] = useState(false)
@ -148,7 +144,6 @@ export default function ShareProjectModal({
value={{ value={{
isAdmin, isAdmin,
updateProject, updateProject,
eventTracking,
monitorRequest, monitorRequest,
inFlight, inFlight,
setInFlight, setInFlight,
@ -176,7 +171,4 @@ ShareProjectModal.propTypes = {
$scope: PropTypes.object.isRequired, $scope: PropTypes.object.isRequired,
}).isRequired, }).isRequired,
show: PropTypes.bool.isRequired, show: PropTypes.bool.isRequired,
eventTracking: PropTypes.shape({
sendMB: PropTypes.func.isRequired,
}),
} }

View file

@ -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
})
}

View file

@ -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 (
<Button
bsStyle={buttonStyle}
onClick={handleClick}
className={classes.button}
>
{children || t('start_free_trial')}
</Button>
)
}
StartFreeTrialButton.propTypes = {
buttonStyle: PropTypes.string,
children: PropTypes.any,
classes: PropTypes.shape({
button: PropTypes.string.isRequired,
}),
setStartedFreeTrial: PropTypes.func,
source: PropTypes.string.isRequired,
}

View file

@ -4,6 +4,7 @@ export function setupContext() {
window.project_id = '1234' window.project_id = '1234'
window.user = { window.user = {
id: 'fake_user', id: 'fake_user',
allowedFreeTrial: true,
} }
let $scope = {} let $scope = {}
if (window._ide) { if (window._ide) {

View file

@ -100,7 +100,7 @@ function SampleHumanReadableHintComponent() {
} }
export default { export default {
title: 'PreviewLogsPaneEntry', title: 'Preview Logs / Entry',
component: PreviewLogsPaneEntry, component: PreviewLogsPaneEntry,
args: { args: {
sourceLocation: { sourceLocation: {
@ -113,7 +113,7 @@ export default {
The LaTeX compiler output The LaTeX compiler output
* With a lot of details * With a lot of details
Wrapped in an HTML <pre> element with Wrapped in an HTML <pre> element with
preformatted text which is to be presented exactly preformatted text which is to be presented exactly
as written in the HTML file as written in the HTML file
@ -129,7 +129,7 @@ LaTeX Font Info: External font \`cmex10' loaded for size
<recently read> \\Zlpha <recently read> \\Zlpha
main.tex, line 23 main.tex, line 23
`, `,
}, },
} }

View file

@ -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 (
<ApplicationProvider>
<EditorProvider ide={ide} settings={{}}>
<PreviewLogsPane {...args} />
</EditorProvider>
</ApplicationProvider>
)
}
TimedOutError.args = {
errors: {
timedout: {},
},
}
export default {
title: 'Preview Logs / Pane',
component: PreviewLogsPane,
argTypes: {
onLogEntryLocationClick: { action: 'log entry location' },
onClearCache: { action: 'clear cache' },
},
}

View file

@ -71,6 +71,8 @@ const setupFetchMock = () => {
.post('express:/project/:projectId/invite/:inviteId/resend', 200, { .post('express:/project/:projectId/invite/:inviteId/resend', 200, {
delay, delay,
}) })
// send analytics event
.post('express:/event/:key', 200)
} }
const ideWithProject = project => { const ideWithProject = project => {