chore: show Super demo modal if user doesn't have subscription when switching editor to Super
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
export enum PremiumFeatureModalType {
|
||||
UpgradePrompt,
|
||||
UpgradeSuccess,
|
||||
SuperDemo,
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -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&=vjbB</span></li>
|
||||
</ul>
|
||||
</div>`
|
||||
|
||||
export function SuperDemoInitialValue() {
|
||||
return new HeadlessSuperConverter().convertOtherFormatToSuperString(InitialHTML, 'html', {
|
||||
html: {
|
||||
addLineBreaks: false,
|
||||
},
|
||||
})
|
||||
}
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user