chore: show Super demo modal if user doesn't have subscription when switching editor to Super

This commit is contained in:
Aman Harwara
2023-12-22 16:40:42 +05:30
committed by GitHub
parent 485339be86
commit 29b7e989a6
26 changed files with 482 additions and 119 deletions

View File

@@ -1,4 +1,5 @@
export enum PremiumFeatureModalType {
UpgradePrompt,
UpgradeSuccess,
SuperDemo,
}

View File

@@ -5,6 +5,8 @@ import { FeatureName } from '@/Controllers/FeatureName'
import { SuccessPrompt } from './Subviews/SuccessPrompt'
import { UpgradePrompt } from './Subviews/UpgradePrompt'
import Modal from '../Modal/Modal'
import SuperDemo from './Subviews/SuperDemo'
import { MutuallyExclusiveMediaQueryBreakpoints, useMediaQuery } from '@/Hooks/useMediaQuery'
type Props = {
application: WebApplication
@@ -23,23 +25,41 @@ const PremiumFeaturesModal: FunctionComponent<Props> = ({
}) => {
const ctaButtonRef = useRef<HTMLButtonElement>(null)
return (
<Modal close={onClose} title="Upgrade" className="px-6 py-5" customHeader={<></>}>
<div tabIndex={-1} className="sn-component">
<div tabIndex={0}>
{type === PremiumFeatureModalType.UpgradePrompt && (
<UpgradePrompt
featureName={featureName}
ctaRef={ctaButtonRef}
application={application}
hasSubscription={hasSubscription}
onClose={onClose}
/>
)}
const isShowingSuperDemo = type === PremiumFeatureModalType.SuperDemo
{type === PremiumFeatureModalType.UpgradeSuccess && <SuccessPrompt ctaRef={ctaButtonRef} onClose={onClose} />}
</div>
</div>
const isMobileScreen = useMediaQuery(MutuallyExclusiveMediaQueryBreakpoints.sm)
return (
<Modal
close={onClose}
title={isShowingSuperDemo ? 'Try out Super' : 'Upgrade'}
className={isShowingSuperDemo ? '' : 'px-6 py-5'}
customHeader={isShowingSuperDemo ? undefined : <></>}
actions={
isShowingSuperDemo
? [
{
label: 'Done',
type: 'primary',
onClick: onClose,
hidden: !isMobileScreen,
mobileSlot: 'right',
},
]
: undefined
}
>
{type === PremiumFeatureModalType.UpgradePrompt && (
<UpgradePrompt
featureName={featureName}
ctaRef={ctaButtonRef}
application={application}
hasSubscription={hasSubscription}
onClose={onClose}
/>
)}
{type === PremiumFeatureModalType.UpgradeSuccess && <SuccessPrompt ctaRef={ctaButtonRef} onClose={onClose} />}
{type === PremiumFeatureModalType.SuperDemo && <SuperDemo hasSubscription={hasSubscription} onClose={onClose} />}
</Modal>
)
}

View File

@@ -0,0 +1,64 @@
import { BlocksEditor } from '@/Components/SuperEditor/BlocksEditor'
import { BlocksEditorComposer } from '@/Components/SuperEditor/BlocksEditorComposer'
import BlockPickerMenuPlugin from '@/Components/SuperEditor/Plugins/BlockPickerPlugin/BlockPickerPlugin'
import usePreference from '@/Hooks/usePreference'
import { useResponsiveEditorFontSize } from '@/Utils/getPlaintextFontSize'
import { EditorLineHeightValues, PrefKey, classNames } from '@standardnotes/snjs'
import { CSSProperties, useRef, useState } from 'react'
import { SuperDemoInitialValue } from './SuperDemoInitialValue'
import { UpgradePrompt } from './UpgradePrompt'
import { useApplication } from '@/Components/ApplicationProvider'
import { useAutoElementRect } from '@/Hooks/useElementRect'
const SuperDemo = ({ hasSubscription, onClose }: { hasSubscription: boolean; onClose: () => void }) => {
const application = useApplication()
const lineHeight = usePreference(PrefKey.EditorLineHeight)
const fontSize = usePreference(PrefKey.EditorFontSize)
const responsiveFontSize = useResponsiveEditorFontSize(fontSize, false)
const ctaRef = useRef<HTMLButtonElement>(null)
const [demoContainer, setDemoContainer] = useState<HTMLDivElement | null>(null)
const demoContainerRect = useAutoElementRect(demoContainer, {
updateOnWindowResize: true,
})
return (
<div className="flex h-full flex-col" ref={setDemoContainer}>
<div
className={classNames(
'flex-shrink-0 border-b border-border p-4',
demoContainerRect && demoContainerRect.height < 500 ? 'hidden md:block' : '',
)}
>
<UpgradePrompt
featureName="Super notes"
ctaRef={ctaRef}
application={application}
hasSubscription={hasSubscription}
inline
preferHorizontalLayout
onClick={onClose}
/>
</div>
<div
className="relative flex h-full min-h-0 flex-col"
style={
{
'--line-height': EditorLineHeightValues[lineHeight],
'--font-size': responsiveFontSize,
} as CSSProperties
}
>
<BlocksEditorComposer initialValue={SuperDemoInitialValue()}>
<BlocksEditor className="blocks-editor h-full bg-default">
<BlockPickerMenuPlugin popoverZIndex="z-modal" />
</BlocksEditor>
</BlocksEditorComposer>
</div>
</div>
)
}
export default SuperDemo

View File

@@ -0,0 +1,164 @@
import { HeadlessSuperConverter } from '@/Components/SuperEditor/Tools/HeadlessSuperConverter'
const InitialHTML = `<div>
<h1>This is a demo of Super notes</h1>
<p><br></p>
<p>Super notes are our new <b>rich text</b> experience. With Super notes, you can create <b>rich</b>, <i>dynamic</i> text with powerful options.</p>
<p><br></p>
<h2><span>Images</span></h2>
<p><br></p>
<p>You can add images to your note by selecting the "Image from URL" option from the <code spellcheck="false"><span>/</span></code> menu or Insert menu in the toolbar.</p>
<p><br></p>
<p><img src="https://standardnotes.com/static/292c6ba50c69a3ae4f8b1883e7f505f6/1f7f6/vault-wide.jpg" /></p>
<p><br></p>
<h2><span>Lists</span></h2>
<p><br></p>
<ul>
<li value="1"><span>Type </span><code spellcheck="false"><span>-</span></code><span> followed by a space in begin a
list</span></li>
<li value="2"><span>Type </span><code spellcheck="false"><span>1.</span></code><span> followed by a space in begin a numbered
list</span></li>
<li value="3"><span>Type </span><code spellcheck="false"><span>[]</span></code><span> followed by a space
to begin a checklist </span></li>
</ul>
<p><br></p>
<ul>
<li value="1"><span>A list</span></li>
<li value="2">
<ul>
<li value="1"><span>Indent the list</span></li>
<li value="2">
<ul>
<li value="1"><span>And even more</span></li>
</ul>
</li>
</ul>
</li>
</ul>
<p><br></p>
<ol>
<li value="1"><span>A numbered list</span></li>
<li value="2"><span>With multiple levels</span></li>
<li value="3"><span>And even more</span></li>
</ol>
<p><br></p>
<ul __lexicallisttype="check">
<li role="checkbox" tabindex="-1" aria-checked="false" value="1">
<span>Create</span>
</li>
<li role="checkbox" tabindex="-1" aria-checked="true" value="2">
<span>a</span>
</li>
<li role="checkbox" tabindex="-1" aria-checked="true" value="3">
<span>checklist</span>
</li>
</ul>
<p><br></p>
<h2><span>Collapsible sections</span></h2>
<p><br></p>
<details open="">
<summary><span>Collapsible section</span></summary>
<div data-lexical-collapsible-content="true">
<p><span>Collapsible sections can include all
other types of content like</span></p>
<p><br></p>
<h2><span>Heading</span></h2>
<p><br></p>
<ul>
<li value="1"><span>a list</span></li>
</ul>
<ol>
<li value="1"><span>numbered</span></li>
</ol>
<ul __lexicallisttype="check">
<li role="checkbox" tabindex="-1" aria-checked="false" value="1"><span>check
list</span>
</li>
</ul>
<p><br></p>
<pre spellcheck="false" data-highlight-language="javascript"><span>A</span><span> code block</span></pre>
<p><br></p>
<p><span>You can even nest collapsible
sections.</span></p>
<p><br></p>
<details open="">
<summary><span>Nested collapsible section</span></summary>
<div data-lexical-collapsible-content="true">
<blockquote><span>Quote</span></blockquote>
</div>
</details>
</div>
</details>
<p><br></p>
<h2><span>Code blocks</span></h2>
<p><br></p>
<p><span>Type </span><code spellcheck="false"><span >\`\`\`</span></code><span> followed by a space to create a code
block. You can choose the language when your
cursor is within the code block.</span></p>
<p><br></p>
<pre spellcheck="false"
data-highlight-language="js"><span >function</span><span> </span><span >main</span><span >(</span><span >)</span><span> </span><span >{</span><br><span> </span><span >const</span><span> variable </span><span >=</span><span> </span><span >"string"</span><span >;</span><br><span> </span><span >return</span><span> </span><span >TEST</span><span >;</span><br><span >}</span></pre>
<p><br></p>
<h2><span>Tables</span></h2>
<table>
<colgroup>
<col>
<col>
<col>
</colgroup>
<tbody>
<tr>
<th>
<p><span>Header</span></p>
</th>
<th>
<p><span>Column 1</span></p>
</th>
<th>
<p><span>Column 2</span></p>
</th>
</tr>
<tr>
<th>
<p><span>Row 1</span></p>
</th>
<td>
<p><span>Row 1 x Column 1</span></p>
</td>
<td>
<p><span>Row 1 x Column 2</span></p>
</td>
</tr>
<tr>
<th>
<p><span>Row 2</span></p>
</th>
<td>
<p><span>Row 2 x Column 1</span></p>
</td>
<td>
<p><span>Row 2 x Column 2</span></p>
</td>
</tr>
</tbody>
</table>
<p><br></p>
<h2><span>Passwords</span></h2>
<p><span>You can generate a secure password using
the "Generate password" command using the </span><code spellcheck="false"><span >/</span></code><span>
menu.</span></p>
<p><br></p>
<ul>
<li value="1"><span>}:hcMrIFgaijpkyz</span></li>
<li value="2"><span>*raF/qi$m?y?iiBS</span></li>
<li value="3"><span>YuVmWf(gOD&amp;=vjbB</span></li>
</ul>
</div>`
export function SuperDemoInitialValue() {
return new HeadlessSuperConverter().convertOtherFormatToSuperString(InitialHTML, 'html', {
html: {
addLineBreaks: false,
},
})
}

View File

@@ -2,6 +2,8 @@ import { useCallback } from 'react'
import { WebApplication } from '@/Application/WebApplication'
import Icon from '@/Components/Icon/Icon'
import { PremiumFeatureIconClass, PremiumFeatureIconName } from '@/Components/Icon/PremiumFeatureIcon'
import { classNames } from '@standardnotes/snjs'
import { requestCloseAllOpenModalsAndPopovers } from '@/Utils/CloseOpenModalsAndPopovers'
type Props = {
featureName?: string
@@ -12,10 +14,12 @@ type Props = {
} & (
| {
inline: true
preferHorizontalLayout?: boolean
onClose?: never
}
| {
inline?: false
preferHorizontalLayout?: never
onClose: () => void
}
)
@@ -28,11 +32,13 @@ export const UpgradePrompt = ({
onClose,
onClick,
inline,
preferHorizontalLayout = false,
}: Props) => {
const handleClick = useCallback(() => {
if (onClick) {
onClick()
}
requestCloseAllOpenModalsAndPopovers()
if (hasSubscription && !application.isNativeIOS()) {
void application.openSubscriptionDashboard.execute()
} else {
@@ -44,65 +50,76 @@ export const UpgradePrompt = ({
}, [application, hasSubscription, onClose, onClick])
return (
<>
<div>
<div className={preferHorizontalLayout ? 'flex flex-wrap items-center gap-4 md:flex-nowrap' : ''}>
{!inline && (
<div className="flex justify-end p-1">
{!inline && (
<button
className="flex cursor-pointer border-0 bg-transparent p-0"
onClick={onClose}
aria-label="Close modal"
>
<Icon className="text-neutral" type="close" />
</button>
)}
<button
className="flex cursor-pointer border-0 bg-transparent p-0"
onClick={onClose}
aria-label="Close modal"
>
<Icon className="text-neutral" type="close" />
</button>
</div>
)}
<div
className={classNames(
'flex items-center justify-center rounded-[50%] bg-contrast',
preferHorizontalLayout ? 'h-12 w-12 flex-shrink-0' : 'mx-auto mb-5 h-24 w-24',
)}
aria-hidden={true}
>
<Icon
className={classNames(preferHorizontalLayout ? 'h-8 w-8' : 'h-12 w-12', PremiumFeatureIconClass)}
size={'custom'}
type={PremiumFeatureIconName}
/>
</div>
<div className={preferHorizontalLayout ? '' : 'mb-2'}>
<div className={classNames('mb-1 text-lg font-bold', preferHorizontalLayout ? 'text-left' : 'text-center')}>
Enable Advanced Features
</div>
<div
className="mx-auto mb-5 flex h-24 w-24 items-center justify-center rounded-[50%] bg-contrast"
aria-hidden={true}
className={classNames('text-sm text-passive-1', preferHorizontalLayout ? 'text-left' : 'px-4.5 text-center')}
>
<Icon className={`h-12 w-12 ${PremiumFeatureIconClass}`} size={'custom'} type={PremiumFeatureIconName} />
{featureName && (
<span>
To take advantage of <span className="font-semibold">{featureName}</span> and other advanced features,
upgrade your current plan.
</span>
)}
{!featureName && (
<span>
To take advantage of all the advanced features Standard Notes has to offer, upgrade your current plan.
</span>
)}
{application.isNativeIOS() && (
<div className="mt-2">
<div className="mb-2 font-bold">The Professional Plan costs $119.99/year and includes benefits like</div>
<ul className="list-inside list-[circle]">
<li>100GB encrypted file storage</li>
<li>
Access to all note types, including Super, markdown, rich text, authenticator, tasks, and spreadsheets
</li>
<li>Access to Daily Notebooks and Moments journals</li>
<li>Note history going back indefinitely</li>
<li>Nested folders for your tags</li>
<li>Premium support</li>
</ul>
</div>
)}
</div>
<div className="mb-1 text-center text-lg font-bold">Enable Advanced Features</div>
</div>
<div className="mb-2 px-4.5 text-center text-sm text-passive-1">
{featureName && (
<span>
To take advantage of <span className="font-semibold">{featureName}</span> and other advanced features,
upgrade your current plan.
</span>
<button
onClick={handleClick}
className={classNames(
'no-border cursor-pointer rounded bg-info py-2 font-bold text-info-contrast hover:brightness-125 focus:brightness-125',
preferHorizontalLayout ? 'w-full px-4 md:ml-auto md:w-auto' : 'my-2 w-full',
)}
{!featureName && (
<span>
To take advantage of all the advanced features Standard Notes has to offer, upgrade your current plan.
</span>
)}
{application.isNativeIOS() && (
<div className="mt-2">
<div className="mb-2 font-bold">The Professional Plan costs $119.99/year and includes benefits like</div>
<ul className="list-inside list-[circle]">
<li>100GB encrypted file storage</li>
<li>
Access to all note types, including Super, markdown, rich text, authenticator, tasks, and spreadsheets
</li>
<li>Access to Daily Notebooks and Moments journals</li>
<li>Note history going back indefinitely</li>
<li>Nested folders for your tags</li>
<li>Premium support</li>
</ul>
</div>
)}
</div>
<div className="p-4">
<button
onClick={handleClick}
className="no-border w-full cursor-pointer rounded bg-info py-2 font-bold text-info-contrast hover:brightness-125 focus:brightness-125"
ref={ctaRef}
>
Upgrade
</button>
</div>
</>
ref={ctaRef}
>
Upgrade
</button>
</div>
)
}