refactor: repo (#1070)

This commit is contained in:
Mo
2022-06-07 07:18:41 -05:00
committed by GitHub
parent 4c65784421
commit f4ef63693c
1102 changed files with 5786 additions and 3366 deletions

View File

@@ -0,0 +1,190 @@
import Button from '@/Components/Button/Button'
import { WebApplication } from '@/Application/Application'
import { ViewControllerManager } from '@/Services/ViewControllerManager'
import { PurchaseFlowPane } from '@/Controllers/PurchaseFlow/PurchaseFlowPane'
import { observer } from 'mobx-react-lite'
import { ChangeEventHandler, FunctionComponent, useEffect, useRef, useState } from 'react'
import FloatingLabelInput from '@/Components/Input/FloatingLabelInput'
import { isEmailValid } from '@/Utils'
import { BlueDotIcon, CircleIcon, DiamondIcon, CreateAccountIllustration } from '@standardnotes/icons'
import { loadPurchaseFlowUrl } from '../PurchaseFlowFunctions'
type Props = {
viewControllerManager: ViewControllerManager
application: WebApplication
}
const CreateAccount: FunctionComponent<Props> = ({ viewControllerManager, application }) => {
const { setCurrentPane } = viewControllerManager.purchaseFlowController
const [email, setEmail] = useState('')
const [password, setPassword] = useState('')
const [confirmPassword, setConfirmPassword] = useState('')
const [isCreatingAccount, setIsCreatingAccount] = useState(false)
const [isEmailInvalid, setIsEmailInvalid] = useState(false)
const [isPasswordNotMatching, setIsPasswordNotMatching] = useState(false)
const emailInputRef = useRef<HTMLInputElement>(null)
const passwordInputRef = useRef<HTMLInputElement>(null)
const confirmPasswordInputRef = useRef<HTMLInputElement>(null)
useEffect(() => {
if (emailInputRef.current) {
emailInputRef.current?.focus()
}
}, [])
const handleEmailChange: ChangeEventHandler<HTMLInputElement> = (e) => {
setEmail(e.target.value)
setIsEmailInvalid(false)
}
const handlePasswordChange: ChangeEventHandler<HTMLInputElement> = (e) => {
setPassword(e.target.value)
}
const handleConfirmPasswordChange: ChangeEventHandler<HTMLInputElement> = (e) => {
setConfirmPassword(e.target.value)
setIsPasswordNotMatching(false)
}
const handleSignInInstead = () => {
setCurrentPane(PurchaseFlowPane.SignIn)
}
const subscribeWithoutAccount = () => {
loadPurchaseFlowUrl(application).catch((err) => {
console.error(err)
application.alertService.alert(err).catch(console.error)
})
}
const handleCreateAccount = async () => {
if (!email) {
emailInputRef?.current?.focus()
return
}
if (!isEmailValid(email)) {
setIsEmailInvalid(true)
emailInputRef?.current?.focus()
return
}
if (!password) {
passwordInputRef?.current?.focus()
return
}
if (!confirmPassword) {
confirmPasswordInputRef?.current?.focus()
return
}
if (password !== confirmPassword) {
setConfirmPassword('')
setIsPasswordNotMatching(true)
confirmPasswordInputRef?.current?.focus()
return
}
setIsCreatingAccount(true)
try {
await application.register(email, password)
loadPurchaseFlowUrl(application).catch((err) => {
console.error(err)
application.alertService.alert(err).catch(console.error)
})
} catch (err) {
console.error(err)
application.alertService.alert(err as string).catch(console.error)
} finally {
setIsCreatingAccount(false)
}
}
return (
<div className="flex items-center">
<CircleIcon className="absolute w-8 h-8 top-40% -left-28" />
<BlueDotIcon className="absolute w-4 h-4 top-35% -left-10" />
<DiamondIcon className="absolute w-26 h-26 -bottom-5 left-0 -translate-x-1/2 -z-index-1" />
<CircleIcon className="absolute w-8 h-8 bottom-35% -right-20" />
<BlueDotIcon className="absolute w-4 h-4 bottom-25% -right-10" />
<DiamondIcon className="absolute w-18 h-18 top-0 -right-2 translate-x-1/2 -z-index-1" />
<div className="mr-12 md:mr-0">
<h1 className="mt-0 mb-2 text-2xl">Create your free account</h1>
<div className="mb-4 font-medium text-sm">to continue to Standard Notes.</div>
<form onSubmit={handleCreateAccount}>
<div className="flex flex-col">
<FloatingLabelInput
className={`min-w-90 xs:min-w-auto ${isEmailInvalid ? 'mb-2' : 'mb-4'}`}
id="purchase-sign-in-email"
type="email"
label="Email"
value={email}
onChange={handleEmailChange}
ref={emailInputRef}
disabled={isCreatingAccount}
isInvalid={isEmailInvalid}
/>
{isEmailInvalid ? <div className="color-danger mb-4">Please provide a valid email.</div> : null}
<FloatingLabelInput
className="min-w-90 xs:min-w-auto mb-4"
id="purchase-create-account-password"
type="password"
label="Password"
value={password}
onChange={handlePasswordChange}
ref={passwordInputRef}
disabled={isCreatingAccount}
/>
<FloatingLabelInput
className={`min-w-90 xs:min-w-auto ${isPasswordNotMatching ? 'mb-2' : 'mb-4'}`}
id="create-account-confirm"
type="password"
label="Repeat password"
value={confirmPassword}
onChange={handleConfirmPasswordChange}
ref={confirmPasswordInputRef}
disabled={isCreatingAccount}
isInvalid={isPasswordNotMatching}
/>
{isPasswordNotMatching ? (
<div className="color-danger mb-4">Passwords don't match. Please try again.</div>
) : null}
</div>
</form>
<div className="flex xs:flex-col-reverse xs:items-start items-center justify-between">
<div className="flex flex-col">
<button
onClick={handleSignInInstead}
disabled={isCreatingAccount}
className="flex items-start p-0 mb-2 bg-default border-0 font-medium color-info cursor-pointer hover:underline"
>
Sign in instead
</button>
<button
onClick={subscribeWithoutAccount}
disabled={isCreatingAccount}
className="flex items-start p-0 bg-default border-0 font-medium color-info cursor-pointer hover:underline"
>
Subscribe without account
</button>
</div>
<Button
className="py-2.5 xs:mb-4"
variant="primary"
label={isCreatingAccount ? 'Creating account...' : 'Create account'}
onClick={handleCreateAccount}
disabled={isCreatingAccount}
/>
</div>
</div>
<CreateAccountIllustration className="md:hidden" />
</div>
)
}
export default observer(CreateAccount)

View File

@@ -0,0 +1,160 @@
import Button from '@/Components/Button/Button'
import { WebApplication } from '@/Application/Application'
import { ViewControllerManager } from '@/Services/ViewControllerManager'
import { PurchaseFlowPane } from '@/Controllers/PurchaseFlow/PurchaseFlowPane'
import { observer } from 'mobx-react-lite'
import { ChangeEventHandler, FunctionComponent, useEffect, useRef, useState } from 'react'
import FloatingLabelInput from '@/Components/Input/FloatingLabelInput'
import { isEmailValid } from '@/Utils'
import { BlueDotIcon, CircleIcon, DiamondIcon } from '@standardnotes/icons'
import { loadPurchaseFlowUrl } from '../PurchaseFlowFunctions'
type Props = {
viewControllerManager: ViewControllerManager
application: WebApplication
}
const SignIn: FunctionComponent<Props> = ({ viewControllerManager, application }) => {
const { setCurrentPane } = viewControllerManager.purchaseFlowController
const [email, setEmail] = useState('')
const [password, setPassword] = useState('')
const [isSigningIn, setIsSigningIn] = useState(false)
const [isEmailInvalid, setIsEmailInvalid] = useState(false)
const [isPasswordInvalid, setIsPasswordInvalid] = useState(false)
const [otherErrorMessage, setOtherErrorMessage] = useState('')
const emailInputRef = useRef<HTMLInputElement>(null)
const passwordInputRef = useRef<HTMLInputElement>(null)
useEffect(() => {
if (emailInputRef.current) {
emailInputRef.current?.focus()
}
}, [])
const handleEmailChange: ChangeEventHandler<HTMLInputElement> = (e) => {
setEmail(e.target.value)
setIsEmailInvalid(false)
}
const handlePasswordChange: ChangeEventHandler<HTMLInputElement> = (e) => {
setPassword(e.target.value)
setIsPasswordInvalid(false)
setOtherErrorMessage('')
}
const handleCreateAccountInstead = () => {
if (isSigningIn) {
return
}
setCurrentPane(PurchaseFlowPane.CreateAccount)
}
const handleSignIn = async () => {
if (!email) {
emailInputRef?.current?.focus()
return
}
if (!isEmailValid(email)) {
setIsEmailInvalid(true)
emailInputRef?.current?.focus()
return
}
if (!password) {
passwordInputRef?.current?.focus()
return
}
setIsSigningIn(true)
try {
const response = await application.signIn(email, password)
if (response.error || response.data?.error) {
throw new Error(response.error?.message || response.data?.error?.message)
} else {
loadPurchaseFlowUrl(application).catch((err) => {
console.error(err)
application.alertService.alert(err).catch(console.error)
})
}
} catch (err) {
console.error(err)
if ((err as Error).toString().includes('Invalid email or password')) {
setIsSigningIn(false)
setIsEmailInvalid(true)
setIsPasswordInvalid(true)
setOtherErrorMessage('Invalid email or password.')
setPassword('')
} else {
application.alertService.alert(err as string).catch(console.error)
}
}
}
return (
<div className="flex items-center">
<CircleIcon className="absolute w-8 h-8 top-35% -left-56" />
<BlueDotIcon className="absolute w-4 h-4 top-30% -left-40" />
<DiamondIcon className="absolute w-26 h-26 -bottom-5 left-0 -translate-x-1/2 -z-index-1" />
<CircleIcon className="absolute w-8 h-8 bottom-30% -right-56" />
<BlueDotIcon className="absolute w-4 h-4 bottom-20% -right-44" />
<DiamondIcon className="absolute w-18 h-18 top-0 -right-2 translate-x-1/2 -z-index-1" />
<div>
<h1 className="mt-0 mb-2 text-2xl">Sign in</h1>
<div className="mb-4 font-medium text-sm">to continue to Standard Notes.</div>
<form onSubmit={handleSignIn}>
<div className="flex flex-col">
<FloatingLabelInput
className={`min-w-90 xs:min-w-auto ${isEmailInvalid && !otherErrorMessage ? 'mb-2' : 'mb-4'}`}
id="purchase-sign-in-email"
type="email"
label="Email"
value={email}
onChange={handleEmailChange}
ref={emailInputRef}
disabled={isSigningIn}
isInvalid={isEmailInvalid}
/>
{isEmailInvalid && !otherErrorMessage ? (
<div className="color-danger mb-4">Please provide a valid email.</div>
) : null}
<FloatingLabelInput
className={`min-w-90 xs:min-w-auto ${otherErrorMessage ? 'mb-2' : 'mb-4'}`}
id="purchase-sign-in-password"
type="password"
label="Password"
value={password}
onChange={handlePasswordChange}
ref={passwordInputRef}
disabled={isSigningIn}
isInvalid={isPasswordInvalid}
/>
{otherErrorMessage ? <div className="color-danger mb-4">{otherErrorMessage}</div> : null}
</div>
<Button
className={`${isSigningIn ? 'min-w-30' : 'min-w-24'} py-2.5 mb-5`}
variant="primary"
label={isSigningIn ? 'Signing in...' : 'Sign in'}
onClick={handleSignIn}
disabled={isSigningIn}
/>
</form>
<div className="text-sm font-medium color-passive-1">
Dont have an account yet?{' '}
<a
className={`color-info ${isSigningIn ? 'cursor-not-allowed' : 'cursor-pointer '}`}
onClick={handleCreateAccountInstead}
>
Create account
</a>
</div>
</div>
</div>
)
}
export default observer(SignIn)

View File

@@ -0,0 +1,27 @@
import { WebApplication } from '@/Application/Application'
import { getWindowUrlParams, isDesktopApplication } from '@/Utils'
export const getPurchaseFlowUrl = async (application: WebApplication): Promise<string | undefined> => {
const currentUrl = window.location.origin
const successUrl = isDesktopApplication() ? 'standardnotes://' : currentUrl
if (application.noAccount()) {
return `${window.purchaseUrl}/offline?&success_url=${successUrl}`
}
const token = await application.getNewSubscriptionToken()
if (token) {
return `${window.purchaseUrl}?subscription_token=${token}&success_url=${successUrl}`
}
return undefined
}
export const loadPurchaseFlowUrl = async (application: WebApplication): Promise<boolean> => {
const url = await getPurchaseFlowUrl(application)
const params = getWindowUrlParams()
const period = params.get('period') ? `&period=${params.get('period')}` : ''
const plan = params.get('plan') ? `&plan=${params.get('plan')}` : ''
if (url) {
window.location.assign(`${url}${period}${plan}`)
return true
}
return false
}

View File

@@ -0,0 +1,69 @@
import { WebApplication } from '@/Application/Application'
import { ViewControllerManager } from '@/Services/ViewControllerManager'
import { PurchaseFlowPane } from '@/Controllers/PurchaseFlow/PurchaseFlowPane'
import { observer } from 'mobx-react-lite'
import { FunctionComponent } from 'react'
import CreateAccount from './Panes/CreateAccount'
import SignIn from './Panes/SignIn'
import { SNLogoFull } from '@standardnotes/icons'
type PaneSelectorProps = {
currentPane: PurchaseFlowPane
} & PurchaseFlowViewProps
type PurchaseFlowViewProps = {
viewControllerManager: ViewControllerManager
application: WebApplication
}
const PurchaseFlowPaneSelector: FunctionComponent<PaneSelectorProps> = ({
currentPane,
viewControllerManager,
application,
}) => {
switch (currentPane) {
case PurchaseFlowPane.CreateAccount:
return <CreateAccount viewControllerManager={viewControllerManager} application={application} />
case PurchaseFlowPane.SignIn:
return <SignIn viewControllerManager={viewControllerManager} application={application} />
}
}
const PurchaseFlowView: FunctionComponent<PurchaseFlowViewProps> = ({ viewControllerManager, application }) => {
const { currentPane } = viewControllerManager.purchaseFlowController
return (
<div className="flex items-center justify-center overflow-hidden h-full w-full absolute top-left-0 z-index-purchase-flow bg-passive-super-light">
<div className="relative fit-content">
<div className="relative p-12 xs:px-8 mb-4 bg-default border-1 border-solid border-main rounded xs:rounded-0">
<SNLogoFull className="mb-5" />
<PurchaseFlowPaneSelector
currentPane={currentPane}
viewControllerManager={viewControllerManager}
application={application}
/>
</div>
<div className="flex justify-end xs:px-4">
<a
className="mr-3 font-medium color-passive-1"
href="https://standardnotes.com/privacy"
target="_blank"
rel="noopener noreferrer"
>
Privacy
</a>
<a
className="font-medium color-passive-1"
href="https://standardnotes.com/help"
target="_blank"
rel="noopener noreferrer"
>
Help
</a>
</div>
</div>
</div>
)
}
export default observer(PurchaseFlowView)

View File

@@ -0,0 +1,14 @@
import { observer } from 'mobx-react-lite'
import { FunctionComponent } from 'react'
import PurchaseFlowView from './PurchaseFlowView'
import { PurchaseFlowWrapperProps } from './PurchaseFlowWrapperProps'
const PurchaseFlowWrapper: FunctionComponent<PurchaseFlowWrapperProps> = ({ viewControllerManager, application }) => {
if (!viewControllerManager.purchaseFlowController.isOpen) {
return null
}
return <PurchaseFlowView viewControllerManager={viewControllerManager} application={application} />
}
export default observer(PurchaseFlowWrapper)

View File

@@ -0,0 +1,7 @@
import { WebApplication } from '@/Application/Application'
import { ViewControllerManager } from '@/Services/ViewControllerManager'
export type PurchaseFlowWrapperProps = {
viewControllerManager: ViewControllerManager
application: WebApplication
}