refactor: replace 'preact' with 'react' (#1048)

This commit is contained in:
Aman Harwara
2022-05-30 12:42:52 +05:30
committed by GitHub
parent e74b4953ea
commit 8c368dd96b
231 changed files with 4794 additions and 4302 deletions

View File

@@ -0,0 +1,60 @@
import { FunctionComponent } from 'react'
import { observer } from 'mobx-react-lite'
import { PreferencesMenu } from './PreferencesMenu'
import Backups from '@/Components/Preferences/Panes/Backups/Backups'
import Appearance from './Panes/Appearance'
import General from './Panes/General/General'
import AccountPreferences from './Panes/Account/AccountPreferences'
import Security from './Panes/Security/Security'
import Listed from './Panes/Listed/Listed'
import HelpAndFeedback from './Panes/HelpFeedback'
import { PreferencesProps } from './PreferencesProps'
const PaneSelector: FunctionComponent<PreferencesProps & { menu: PreferencesMenu }> = ({
menu,
appState,
application,
mfaProvider,
userProvider,
}) => {
switch (menu.selectedPaneId) {
case 'general':
return (
<General
appState={appState}
application={application}
extensionsLatestVersions={menu.extensionsLatestVersions}
/>
)
case 'account':
return <AccountPreferences application={application} appState={appState} />
case 'appearance':
return <Appearance application={application} />
case 'security':
return (
<Security mfaProvider={mfaProvider} userProvider={userProvider} appState={appState} application={application} />
)
case 'backups':
return <Backups application={application} appState={appState} />
case 'listed':
return <Listed application={application} />
case 'shortcuts':
return null
case 'accessibility':
return null
case 'get-free-month':
return null
case 'help-feedback':
return <HelpAndFeedback />
default:
return (
<General
appState={appState}
application={application}
extensionsLatestVersions={menu.extensionsLatestVersions}
/>
)
}
}
export default observer(PaneSelector)

View File

@@ -1,20 +1,20 @@
import { PreferencesPane } from '@/Components/Preferences/PreferencesComponents'
import { observer } from 'mobx-react-lite'
import { WebApplication } from '@/UIModels/Application'
import { AppState } from '@/UIModels/AppState'
import { Authentication } from './Authentication'
import { Credentials } from './Credentials'
import { Sync } from './Sync'
import { Subscription } from './Subscription/Subscription'
import { SignOutWrapper } from './SignOutView'
import { FilesSection } from './Files'
import Authentication from './Authentication'
import Credentials from './Credentials'
import Sync from './Sync'
import Subscription from './Subscription/Subscription'
import SignOutWrapper from './SignOutView'
import FilesSection from './Files'
import PreferencesPane from '../../PreferencesComponents/PreferencesPane'
type Props = {
application: WebApplication
appState: AppState
}
export const AccountPreferences = observer(({ application, appState }: Props) => (
const AccountPreferences = ({ application, appState }: Props) => (
<PreferencesPane>
{!application.hasAccount() ? (
<Authentication application={application} appState={appState} />
@@ -28,4 +28,6 @@ export const AccountPreferences = observer(({ application, appState }: Props) =>
{application.hasAccount() && appState.features.hasFiles && <FilesSection application={application} />}
<SignOutWrapper application={application} appState={appState} />
</PreferencesPane>
))
)
export default observer(AccountPreferences)

View File

@@ -1,20 +1,21 @@
import { FunctionalComponent } from 'preact'
import { PreferencesGroup, PreferencesSegment } from '@/Components/Preferences/PreferencesComponents'
import { OfflineSubscription } from '@/Components/Preferences/Panes/Account/OfflineSubscription'
import { FunctionComponent } from 'react'
import OfflineSubscription from '@/Components/Preferences/Panes/Account/OfflineSubscription'
import { WebApplication } from '@/UIModels/Application'
import { observer } from 'mobx-react-lite'
import { AppState } from '@/UIModels/AppState'
import { Extensions } from '@/Components/Preferences/Panes/Extensions/Extensions'
import Extensions from '@/Components/Preferences/Panes/Extensions/Extensions'
import { ExtensionsLatestVersions } from '@/Components/Preferences/Panes/Extensions/ExtensionsLatestVersions'
import { AccordionItem } from '@/Components/Shared/AccordionItem'
import AccordionItem from '@/Components/Shared/AccordionItem'
import PreferencesGroup from '../../PreferencesComponents/PreferencesGroup'
import PreferencesSegment from '../../PreferencesComponents/PreferencesSegment'
interface IProps {
type Props = {
application: WebApplication
appState: AppState
extensionsLatestVersions: ExtensionsLatestVersions
}
export const Advanced: FunctionalComponent<IProps> = observer(({ application, appState, extensionsLatestVersions }) => {
const Advanced: FunctionComponent<Props> = ({ application, appState, extensionsLatestVersions }) => {
return (
<PreferencesGroup>
<PreferencesSegment>
@@ -33,4 +34,6 @@ export const Advanced: FunctionalComponent<IProps> = observer(({ application, ap
</PreferencesSegment>
</PreferencesGroup>
)
})
}
export default observer(Advanced)

View File

@@ -1,16 +1,20 @@
import { Button } from '@/Components/Button/Button'
import { PreferencesGroup, PreferencesSegment, Text, Title } from '@/Components/Preferences/PreferencesComponents'
import Button from '@/Components/Button/Button'
import { Text, Title } from '@/Components/Preferences/PreferencesComponents/Content'
import { WebApplication } from '@/UIModels/Application'
import { AppState } from '@/UIModels/AppState'
import { observer } from 'mobx-react-lite'
import { FunctionComponent } from 'preact'
import { FunctionComponent } from 'react'
import { AccountIllustration } from '@standardnotes/icons'
import { AccountMenuPane } from '@/Components/AccountMenu/AccountMenuPane'
import PreferencesGroup from '../../PreferencesComponents/PreferencesGroup'
import PreferencesSegment from '../../PreferencesComponents/PreferencesSegment'
export const Authentication: FunctionComponent<{
type Props = {
application: WebApplication
appState: AppState
}> = observer(({ appState }) => {
}
const Authentication: FunctionComponent<Props> = ({ appState }) => {
const clickSignIn = () => {
appState.preferences.closePreferences()
appState.accountMenu.setCurrentPane(AccountMenuPane.SignIn)
@@ -43,4 +47,6 @@ export const Authentication: FunctionComponent<{
</PreferencesSegment>
</PreferencesGroup>
)
})
}
export default observer(Authentication)

View File

@@ -1,16 +1,13 @@
import { useState } from '@node_modules/preact/hooks'
import {
ModalDialog,
ModalDialogButtons,
ModalDialogDescription,
ModalDialogLabel,
} from '@/Components/Shared/ModalDialog'
import { Button } from '@/Components/Button/Button'
import { FunctionalComponent } from 'preact'
import ModalDialog from '@/Components/Shared/ModalDialog'
import ModalDialogButtons from '@/Components/Shared/ModalDialogButtons'
import ModalDialogDescription from '@/Components/Shared/ModalDialogDescription'
import ModalDialogLabel from '@/Components/Shared/ModalDialogLabel'
import Button from '@/Components/Button/Button'
import { FunctionComponent, useState } from 'react'
import { WebApplication } from '@/UIModels/Application'
import { useBeforeUnload } from '@/Hooks/useBeforeUnload'
import { ChangeEmailForm } from './ChangeEmailForm'
import { ChangeEmailSuccess } from './ChangeEmailSuccess'
import ChangeEmailForm from './ChangeEmailForm'
import ChangeEmailSuccess from './ChangeEmailSuccess'
import { isEmailValid } from '@/Utils'
enum SubmitButtonTitles {
@@ -29,7 +26,7 @@ type Props = {
application: WebApplication
}
export const ChangeEmail: FunctionalComponent<Props> = ({ onCloseDialog, application }) => {
const ChangeEmail: FunctionComponent<Props> = ({ onCloseDialog, application }) => {
const [currentPassword, setCurrentPassword] = useState('')
const [newEmail, setNewEmail] = useState('')
const [isContinuing, setIsContinuing] = useState(false)
@@ -158,3 +155,5 @@ export const ChangeEmail: FunctionalComponent<Props> = ({ onCloseDialog, applica
</div>
)
}
export default ChangeEmail

View File

@@ -1,16 +1,15 @@
import { StateUpdater } from 'preact/hooks'
import { FunctionalComponent } from 'preact'
import { Dispatch, SetStateAction, FunctionComponent } from 'react'
type Props = {
setNewEmail: StateUpdater<string>
setCurrentPassword: StateUpdater<string>
setNewEmail: Dispatch<SetStateAction<string>>
setCurrentPassword: Dispatch<SetStateAction<string>>
}
const labelClassName = 'block mb-1'
const inputClassName = 'sk-input contrast'
export const ChangeEmailForm: FunctionalComponent<Props> = ({ setNewEmail, setCurrentPassword }) => {
const ChangeEmailForm: FunctionComponent<Props> = ({ setNewEmail, setCurrentPassword }) => {
return (
<div className="w-full flex flex-col">
<div className="mt-2 mb-3">
@@ -42,3 +41,5 @@ export const ChangeEmailForm: FunctionalComponent<Props> = ({ setNewEmail, setCu
</div>
)
}
export default ChangeEmailForm

View File

@@ -1,6 +1,6 @@
import { FunctionalComponent } from 'preact'
import { FunctionComponent } from 'react'
export const ChangeEmailSuccess: FunctionalComponent = () => {
const ChangeEmailSuccess: FunctionComponent = () => {
return (
<div>
<div className={'sk-label sk-bold info mt-2'}>Your email has been successfully changed.</div>
@@ -11,3 +11,5 @@ export const ChangeEmailSuccess: FunctionalComponent = () => {
</div>
)
}
export default ChangeEmailSuccess

View File

@@ -0,0 +1,30 @@
import Button from '@/Components/Button/Button'
import { AppState } from '@/UIModels/AppState'
import { observer } from 'mobx-react-lite'
import { FunctionComponent } from 'react'
import { Title, Text } from '../../PreferencesComponents/Content'
import PreferencesGroup from '../../PreferencesComponents/PreferencesGroup'
import PreferencesSegment from '../../PreferencesComponents/PreferencesSegment'
const ClearSessionDataView: FunctionComponent<{
appState: AppState
}> = ({ appState }) => {
return (
<PreferencesGroup>
<PreferencesSegment>
<Title>Clear workspace</Title>
<Text>Remove all data related to the current workspace from the application.</Text>
<div className="min-h-3" />
<Button
dangerStyle={true}
label="Clear workspace"
onClick={() => {
appState.accountMenu.setSigningOut(true)
}}
/>
</PreferencesSegment>
</PreferencesGroup>
)
}
export default observer(ClearSessionDataView)

View File

@@ -1,28 +1,24 @@
import {
PreferencesGroup,
PreferencesSegment,
Subtitle,
Text,
Title,
} from '@/Components/Preferences/PreferencesComponents'
import { Button } from '@/Components/Button/Button'
import { Subtitle, Text, Title } from '@/Components/Preferences/PreferencesComponents/Content'
import Button from '@/Components/Button/Button'
import { WebApplication } from '@/UIModels/Application'
import { observer } from '@node_modules/mobx-react-lite'
import { HorizontalSeparator } from '@/Components/Shared/HorizontalSeparator'
import HorizontalSeparator from '@/Components/Shared/HorizontalSeparator'
import { dateToLocalizedString } from '@standardnotes/snjs'
import { useCallback, useState } from 'preact/hooks'
import { ChangeEmail } from '@/Components/Preferences/Panes/Account/ChangeEmail/ChangeEmail'
import { FunctionComponent, render } from 'preact'
import { useCallback, useState, FunctionComponent } from 'react'
import ChangeEmail from '@/Components/Preferences/Panes/Account/ChangeEmail/ChangeEmail'
import { AppState } from '@/UIModels/AppState'
import { PasswordWizard } from '@/Components/PasswordWizard/PasswordWizard'
import PasswordWizard from '@/Components/PasswordWizard/PasswordWizard'
import PreferencesGroup from '../../PreferencesComponents/PreferencesGroup'
import PreferencesSegment from '../../PreferencesComponents/PreferencesSegment'
type Props = {
application: WebApplication
appState: AppState
}
export const Credentials: FunctionComponent<Props> = observer(({ application }: Props) => {
const Credentials: FunctionComponent<Props> = ({ application }: Props) => {
const [isChangeEmailDialogOpen, setIsChangeEmailDialogOpen] = useState(false)
const [shouldShowPasswordWizard, setShouldShowPasswordWizard] = useState(false)
const user = application.getUser()
@@ -30,35 +26,46 @@ export const Credentials: FunctionComponent<Props> = observer(({ application }:
const passwordCreatedOn = dateToLocalizedString(passwordCreatedAtTimestamp)
const presentPasswordWizard = useCallback(() => {
render(<PasswordWizard application={application} />, document.body.appendChild(document.createElement('div')))
}, [application])
setShouldShowPasswordWizard(true)
}, [])
const dismissPasswordWizard = useCallback(() => {
setShouldShowPasswordWizard(false)
}, [])
return (
<PreferencesGroup>
<PreferencesSegment>
<Title>Credentials</Title>
<Subtitle>Email</Subtitle>
<Text>
You're signed in as <span className="font-bold wrap">{user?.email}</span>
</Text>
<Button
className="min-w-20 mt-3"
variant="normal"
label="Change email"
onClick={() => {
setIsChangeEmailDialogOpen(true)
}}
/>
<HorizontalSeparator classes="mt-5 mb-3" />
<Subtitle>Password</Subtitle>
<Text>
Current password was set on <span className="font-bold">{passwordCreatedOn}</span>
</Text>
<Button className="min-w-20 mt-3" variant="normal" label="Change password" onClick={presentPasswordWizard} />
{isChangeEmailDialogOpen && (
<ChangeEmail onCloseDialog={() => setIsChangeEmailDialogOpen(false)} application={application} />
)}
</PreferencesSegment>
</PreferencesGroup>
<>
<PreferencesGroup>
<PreferencesSegment>
<Title>Credentials</Title>
<Subtitle>Email</Subtitle>
<Text>
You're signed in as <span className="font-bold wrap">{user?.email}</span>
</Text>
<Button
className="min-w-20 mt-3"
variant="normal"
label="Change email"
onClick={() => {
setIsChangeEmailDialogOpen(true)
}}
/>
<HorizontalSeparator classes="my-4" />
<Subtitle>Password</Subtitle>
<Text>
Current password was set on <span className="font-bold">{passwordCreatedOn}</span>
</Text>
<Button className="min-w-20 mt-3" variant="normal" label="Change password" onClick={presentPasswordWizard} />
{isChangeEmailDialogOpen && (
<ChangeEmail onCloseDialog={() => setIsChangeEmailDialogOpen(false)} application={application} />
)}
</PreferencesSegment>
</PreferencesGroup>
{shouldShowPasswordWizard ? (
<PasswordWizard application={application} dismissModal={dismissPasswordWizard} />
) : null}
</>
)
})
}
export default observer(Credentials)

View File

@@ -1,15 +1,16 @@
import { WebApplication } from '@/UIModels/Application'
import { formatSizeToReadableString } from '@standardnotes/filepicker'
import { SubscriptionSettingName } from '@standardnotes/snjs'
import { FunctionComponent } from 'preact'
import { useEffect, useState } from 'preact/hooks'
import { PreferencesGroup, PreferencesSegment, Subtitle, Title } from '../../PreferencesComponents'
import { FunctionComponent, useEffect, useState } from 'react'
import { Subtitle, Title } from '../../PreferencesComponents/Content'
import PreferencesGroup from '../../PreferencesComponents/PreferencesGroup'
import PreferencesSegment from '../../PreferencesComponents/PreferencesSegment'
type Props = {
application: WebApplication
}
export const FilesSection: FunctionComponent<Props> = ({ application }) => {
const FilesSection: FunctionComponent<Props> = ({ application }) => {
const [isLoading, setIsLoading] = useState(true)
const [filesQuotaUsed, setFilesQuotaUsed] = useState<number>(0)
const [filesQuotaTotal, setFilesQuotaTotal] = useState<number>(0)
@@ -63,3 +64,5 @@ export const FilesSection: FunctionComponent<Props> = ({ application }) => {
</PreferencesGroup>
)
}
export default FilesSection

View File

@@ -1,21 +1,20 @@
import { FunctionalComponent } from 'preact'
import { Subtitle } from '@/Components/Preferences/PreferencesComponents'
import { DecoratedInput } from '@/Components/Input/DecoratedInput'
import { Button } from '@/Components/Button/Button'
import { useEffect, useState } from 'preact/hooks'
import React, { FunctionComponent, useEffect, useState } from 'react'
import { Subtitle } from '@/Components/Preferences/PreferencesComponents/Content'
import DecoratedInput from '@/Components/Input/DecoratedInput'
import Button from '@/Components/Button/Button'
import { WebApplication } from '@/UIModels/Application'
import { AppState } from '@/UIModels/AppState'
import { observer } from 'mobx-react-lite'
import { STRING_REMOVE_OFFLINE_KEY_CONFIRMATION } from '@/Strings'
import { ButtonType, ClientDisplayableError } from '@standardnotes/snjs'
import { HorizontalSeparator } from '@/Components/Shared/HorizontalSeparator'
import HorizontalSeparator from '@/Components/Shared/HorizontalSeparator'
interface IProps {
type Props = {
application: WebApplication
appState: AppState
}
export const OfflineSubscription: FunctionalComponent<IProps> = observer(({ application }) => {
const OfflineSubscription: FunctionComponent<Props> = ({ application }) => {
const [activationCode, setActivationCode] = useState('')
const [isSuccessfullyActivated, setIsSuccessfullyActivated] = useState(false)
const [isSuccessfullyRemoved, setIsSuccessfullyRemoved] = useState(false)
@@ -31,7 +30,7 @@ export const OfflineSubscription: FunctionalComponent<IProps> = observer(({ appl
return !application.hasAccount() || application.isThirdPartyHostUsed() || hasUserPreviouslyStoredCode
}
const handleSubscriptionCodeSubmit = async (event: Event) => {
const handleSubscriptionCodeSubmit = async (event: React.FormEvent) => {
event.preventDefault()
const result = await application.features.setOfflineFeaturesCode(activationCode)
@@ -123,4 +122,6 @@ export const OfflineSubscription: FunctionalComponent<IProps> = observer(({ appl
<HorizontalSeparator classes="mt-8 mb-5" />
</>
)
})
}
export default observer(OfflineSubscription)

View File

@@ -1,21 +1,21 @@
import { Button } from '@/Components/Button/Button'
import { OtherSessionsSignOutContainer } from '@/Components/OtherSessionsSignOut/OtherSessionsSignOut'
import {
PreferencesGroup,
PreferencesSegment,
Subtitle,
Text,
Title,
} from '@/Components/Preferences/PreferencesComponents'
import Button from '@/Components/Button/Button'
import OtherSessionsSignOutContainer from '@/Components/OtherSessionsSignOut/OtherSessionsSignOut'
import HorizontalSeparator from '@/Components/Shared/HorizontalSeparator'
import { WebApplication } from '@/UIModels/Application'
import { AppState } from '@/UIModels/AppState'
import { observer } from 'mobx-react-lite'
import { FunctionComponent } from 'preact'
import { FunctionComponent } from 'react'
import { Subtitle, Title, Text } from '../../PreferencesComponents/Content'
import PreferencesGroup from '../../PreferencesComponents/PreferencesGroup'
import PreferencesSegment from '../../PreferencesComponents/PreferencesSegment'
import ClearSessionDataView from './ClearSessionDataView'
const SignOutView: FunctionComponent<{
type Props = {
application: WebApplication
appState: AppState
}> = observer(({ application, appState }) => {
}
const SignOutView: FunctionComponent<Props> = observer(({ application, appState }) => {
return (
<>
<PreferencesGroup>
@@ -36,6 +36,7 @@ const SignOutView: FunctionComponent<{
<Button variant="normal" label="Manage sessions" onClick={() => appState.openSessionsModal()} />
</div>
</PreferencesSegment>
<HorizontalSeparator classes="my-4" />
<PreferencesSegment>
<Subtitle>This workspace</Subtitle>
<Text>Remove all data related to the current workspace from the application.</Text>
@@ -54,33 +55,13 @@ const SignOutView: FunctionComponent<{
)
})
const ClearSessionDataView: FunctionComponent<{
appState: AppState
}> = observer(({ appState }) => {
return (
<PreferencesGroup>
<PreferencesSegment>
<Title>Clear workspace</Title>
<Text>Remove all data related to the current workspace from the application.</Text>
<div className="min-h-3" />
<Button
dangerStyle={true}
label="Clear workspace"
onClick={() => {
appState.accountMenu.setSigningOut(true)
}}
/>
</PreferencesSegment>
</PreferencesGroup>
)
})
SignOutView.displayName = 'SignOutView'
export const SignOutWrapper: FunctionComponent<{
application: WebApplication
appState: AppState
}> = observer(({ application, appState }) => {
const SignOutWrapper: FunctionComponent<Props> = ({ application, appState }) => {
if (!application.hasAccount()) {
return <ClearSessionDataView appState={appState} />
}
return <SignOutView appState={appState} application={application} />
})
}
export default observer(SignOutWrapper)

View File

@@ -1,13 +1,14 @@
import { FunctionalComponent } from 'preact'
import { LinkButton, Text } from '@/Components/Preferences/PreferencesComponents'
import { Button } from '@/Components/Button/Button'
import { FunctionComponent, useState } from 'react'
import { LinkButton, Text } from '@/Components/Preferences/PreferencesComponents/Content'
import Button from '@/Components/Button/Button'
import { WebApplication } from '@/UIModels/Application'
import { useState } from 'preact/hooks'
import { loadPurchaseFlowUrl } from '@/Components/PurchaseFlow/PurchaseFlowFunctions'
export const NoSubscription: FunctionalComponent<{
type Props = {
application: WebApplication
}> = ({ application }) => {
}
const NoSubscription: FunctionComponent<Props> = ({ application }) => {
const [isLoadingPurchaseFlow, setIsLoadingPurchaseFlow] = useState(false)
const [purchaseFlowError, setPurchaseFlowError] = useState<string | undefined>(undefined)
@@ -39,3 +40,5 @@ export const NoSubscription: FunctionalComponent<{
</>
)
}
export default NoSubscription

View File

@@ -0,0 +1,61 @@
import { SubscriptionState } from '@/UIModels/AppState/SubscriptionState'
import { observer } from 'mobx-react-lite'
import { Text } from '@/Components/Preferences/PreferencesComponents/Content'
type Props = { subscriptionState: SubscriptionState }
const StatusText = ({ subscriptionState }: Props) => {
const {
userSubscriptionName,
userSubscriptionExpirationDate,
isUserSubscriptionExpired,
isUserSubscriptionCanceled,
} = subscriptionState
const expirationDateString = userSubscriptionExpirationDate?.toLocaleString()
if (isUserSubscriptionCanceled) {
return (
<Text className="mt-1">
Your{' '}
<span className="font-bold">
Standard Notes{userSubscriptionName ? ' ' : ''}
{userSubscriptionName}
</span>{' '}
subscription has been canceled{' '}
{isUserSubscriptionExpired ? (
<span className="font-bold">and expired on {expirationDateString}</span>
) : (
<span className="font-bold">but will remain valid until {expirationDateString}</span>
)}
. You may resubscribe below if you wish.
</Text>
)
}
if (isUserSubscriptionExpired) {
return (
<Text className="mt-1">
Your{' '}
<span className="font-bold">
Standard Notes{userSubscriptionName ? ' ' : ''}
{userSubscriptionName}
</span>{' '}
subscription <span className="font-bold">expired on {expirationDateString}</span>. You may resubscribe below if
you wish.
</Text>
)
}
return (
<Text className="mt-1">
Your{' '}
<span className="font-bold">
Standard Notes{userSubscriptionName ? ' ' : ''}
{userSubscriptionName}
</span>{' '}
subscription will be <span className="font-bold">renewed on {expirationDateString}</span>.
</Text>
)
}
export default observer(StatusText)

View File

@@ -1,17 +1,19 @@
import { PreferencesGroup, PreferencesSegment, Title } from '@/Components/Preferences/PreferencesComponents'
import { Title } from '@/Components/Preferences/PreferencesComponents/Content'
import { WebApplication } from '@/UIModels/Application'
import { SubscriptionInformation } from './SubscriptionInformation'
import { NoSubscription } from './NoSubscription'
import SubscriptionInformation from './SubscriptionInformation'
import NoSubscription from './NoSubscription'
import { observer } from 'mobx-react-lite'
import { FunctionComponent } from 'preact'
import { FunctionComponent } from 'react'
import { AppState } from '@/UIModels/AppState'
import PreferencesGroup from '@/Components/Preferences/PreferencesComponents/PreferencesGroup'
import PreferencesSegment from '@/Components/Preferences/PreferencesComponents/PreferencesSegment'
type Props = {
application: WebApplication
appState: AppState
}
export const Subscription: FunctionComponent<Props> = observer(({ application, appState }: Props) => {
const Subscription: FunctionComponent<Props> = ({ application, appState }: Props) => {
const subscriptionState = appState.subscription
const { userSubscription } = subscriptionState
@@ -33,4 +35,6 @@ export const Subscription: FunctionComponent<Props> = observer(({ application, a
</PreferencesSegment>
</PreferencesGroup>
)
})
}
export default observer(Subscription)

View File

@@ -1,70 +1,16 @@
import { observer } from 'mobx-react-lite'
import { SubscriptionState } from '@/UIModels/AppState/SubscriptionState'
import { Text } from '@/Components/Preferences/PreferencesComponents'
import { Button } from '@/Components/Button/Button'
import Button from '@/Components/Button/Button'
import { WebApplication } from '@/UIModels/Application'
import { openSubscriptionDashboard } from '@/Utils/ManageSubscription'
import StatusText from './StatusText'
type Props = {
subscriptionState: SubscriptionState
application: WebApplication
}
const StatusText = observer(({ subscriptionState }: { subscriptionState: Props['subscriptionState'] }) => {
const {
userSubscriptionName,
userSubscriptionExpirationDate,
isUserSubscriptionExpired,
isUserSubscriptionCanceled,
} = subscriptionState
const expirationDateString = userSubscriptionExpirationDate?.toLocaleString()
if (isUserSubscriptionCanceled) {
return (
<Text className="mt-1">
Your{' '}
<span className="font-bold">
Standard Notes{userSubscriptionName ? ' ' : ''}
{userSubscriptionName}
</span>{' '}
subscription has been canceled{' '}
{isUserSubscriptionExpired ? (
<span className="font-bold">and expired on {expirationDateString}</span>
) : (
<span className="font-bold">but will remain valid until {expirationDateString}</span>
)}
. You may resubscribe below if you wish.
</Text>
)
}
if (isUserSubscriptionExpired) {
return (
<Text className="mt-1">
Your{' '}
<span className="font-bold">
Standard Notes{userSubscriptionName ? ' ' : ''}
{userSubscriptionName}
</span>{' '}
subscription <span className="font-bold">expired on {expirationDateString}</span>. You may resubscribe below if
you wish.
</Text>
)
}
return (
<Text className="mt-1">
Your{' '}
<span className="font-bold">
Standard Notes{userSubscriptionName ? ' ' : ''}
{userSubscriptionName}
</span>{' '}
subscription will be <span className="font-bold">renewed on {expirationDateString}</span>.
</Text>
)
})
export const SubscriptionInformation = observer(({ subscriptionState, application }: Props) => {
const SubscriptionInformation = ({ subscriptionState, application }: Props) => {
const manageSubscription = async () => {
openSubscriptionDashboard(application)
}
@@ -80,4 +26,6 @@ export const SubscriptionInformation = observer(({ subscriptionState, applicatio
/>
</>
)
})
}
export default observer(SubscriptionInformation)

View File

@@ -1,21 +1,19 @@
import { PreferencesGroup, PreferencesSegment, Text, Title } from '@/Components/Preferences/PreferencesComponents'
import { Button } from '@/Components/Button/Button'
import { SyncQueueStrategy, dateToLocalizedString } from '@standardnotes/snjs'
import { Text, Title } from '@/Components/Preferences/PreferencesComponents/Content'
import Button from '@/Components/Button/Button'
import { SyncQueueStrategy } from '@standardnotes/snjs'
import { STRING_GENERIC_SYNC_ERROR } from '@/Strings'
import { useState } from '@node_modules/preact/hooks'
import { observer } from 'mobx-react-lite'
import { WebApplication } from '@/UIModels/Application'
import { FunctionComponent } from 'preact'
import { FunctionComponent, useState } from 'react'
import { formatLastSyncDate } from '@/Utils/FormatLastSyncDate'
import PreferencesGroup from '../../PreferencesComponents/PreferencesGroup'
import PreferencesSegment from '../../PreferencesComponents/PreferencesSegment'
type Props = {
application: WebApplication
}
export const formatLastSyncDate = (lastUpdatedDate: Date) => {
return dateToLocalizedString(lastUpdatedDate)
}
export const Sync: FunctionComponent<Props> = observer(({ application }: Props) => {
const Sync: FunctionComponent<Props> = ({ application }: Props) => {
const [isSyncingInProgress, setIsSyncingInProgress] = useState(false)
const [lastSyncDate, setLastSyncDate] = useState(formatLastSyncDate(application.sync.getLastSyncDate() as Date))
@@ -55,4 +53,6 @@ export const Sync: FunctionComponent<Props> = observer(({ application }: Props)
</PreferencesSegment>
</PreferencesGroup>
)
})
}
export default observer(Sync)

View File

@@ -1,27 +1,23 @@
import { Dropdown, DropdownItem } from '@/Components/Dropdown/Dropdown'
import Dropdown from '@/Components/Dropdown/Dropdown'
import { DropdownItem } from '@/Components/Dropdown/DropdownItem'
import { usePremiumModal } from '@/Hooks/usePremiumModal'
import { HorizontalSeparator } from '@/Components/Shared/HorizontalSeparator'
import { Switch } from '@/Components/Switch/Switch'
import HorizontalSeparator from '@/Components/Shared/HorizontalSeparator'
import Switch from '@/Components/Switch/Switch'
import { WebApplication } from '@/UIModels/Application'
import { ContentType, FeatureIdentifier, FeatureStatus, PrefKey, GetFeatures, SNTheme } from '@standardnotes/snjs'
import { observer } from 'mobx-react-lite'
import { FunctionComponent } from 'preact'
import { useEffect, useState } from 'preact/hooks'
import {
PreferencesGroup,
PreferencesPane,
PreferencesSegment,
Subtitle,
Title,
Text,
} from '@/Components/Preferences/PreferencesComponents'
import { FunctionComponent, useEffect, useState } from 'react'
import { Subtitle, Title, Text } from '@/Components/Preferences/PreferencesComponents/Content'
import { sortThemes } from '@/Utils/SortThemes'
import PreferencesPane from '../PreferencesComponents/PreferencesPane'
import PreferencesGroup from '../PreferencesComponents/PreferencesGroup'
import PreferencesSegment from '../PreferencesComponents/PreferencesSegment'
type Props = {
application: WebApplication
}
export const Appearance: FunctionComponent<Props> = observer(({ application }) => {
const Appearance: FunctionComponent<Props> = ({ application }) => {
const premiumModal = usePremiumModal()
const isEntitledToMidnightTheme =
application.features.getFeatureStatus(FeatureIdentifier.MidnightTheme) === FeatureStatus.Entitled
@@ -120,7 +116,7 @@ export const Appearance: FunctionComponent<Props> = observer(({ application }) =
</div>
<Switch onChange={toggleUseDeviceSettings} checked={useDeviceSettings} />
</div>
<HorizontalSeparator classes="mt-5 mb-3" />
<HorizontalSeparator classes="my-4" />
<div>
<Subtitle>Automatic Light Theme</Subtitle>
<Text>Theme to be used for system light mode:</Text>
@@ -135,7 +131,7 @@ export const Appearance: FunctionComponent<Props> = observer(({ application }) =
/>
</div>
</div>
<HorizontalSeparator classes="mt-5 mb-3" />
<HorizontalSeparator classes="my-4" />
<div>
<Subtitle>Automatic Dark Theme</Subtitle>
<Text>Theme to be used for system dark mode:</Text>
@@ -155,4 +151,6 @@ export const Appearance: FunctionComponent<Props> = observer(({ application }) =
</PreferencesGroup>
</PreferencesPane>
)
})
}
export default observer(Appearance)

View File

@@ -1,24 +1,27 @@
import { WebApplication } from '@/UIModels/Application'
import { AppState } from '@/UIModels/AppState'
import { FunctionComponent } from 'preact'
import { PreferencesPane } from '@/Components/Preferences/PreferencesComponents'
import { CloudLink } from './CloudBackups/CloudBackups'
import { DataBackups } from './DataBackups'
import { EmailBackups } from './EmailBackups'
import { FileBackups } from './Files/FileBackups'
import { FunctionComponent } from 'react'
import PreferencesPane from '@/Components/Preferences/PreferencesComponents/PreferencesPane'
import CloudLink from './CloudBackups/CloudBackups'
import DataBackups from './DataBackups'
import EmailBackups from './EmailBackups'
import FileBackupsCrossPlatform from './Files/FileBackupsCrossPlatform'
import { observer } from 'mobx-react-lite'
interface Props {
type Props = {
appState: AppState
application: WebApplication
}
export const Backups: FunctionComponent<Props> = ({ application, appState }) => {
const Backups: FunctionComponent<Props> = ({ application, appState }) => {
return (
<PreferencesPane>
<DataBackups application={application} appState={appState} />
<FileBackups application={application} />
<FileBackupsCrossPlatform application={application} />
<EmailBackups application={application} />
<CloudLink application={application} />
</PreferencesPane>
)
}
export default observer(Backups)

View File

@@ -1,4 +1,12 @@
import { useCallback, useEffect, useState } from 'preact/hooks'
import {
useCallback,
useEffect,
useState,
FunctionComponent,
KeyboardEventHandler,
ChangeEventHandler,
MouseEventHandler,
} from 'react'
import {
ButtonType,
SettingName,
@@ -8,11 +16,10 @@ import {
OneDriveBackupFrequency,
} from '@standardnotes/snjs'
import { WebApplication } from '@/UIModels/Application'
import { Button } from '@/Components/Button/Button'
import Button from '@/Components/Button/Button'
import { isDev, openInNewTab } from '@/Utils'
import { Subtitle } from '@/Components/Preferences/PreferencesComponents'
import { Subtitle } from '@/Components/Preferences/PreferencesComponents/Content'
import { KeyboardKey } from '@/Services/IOService'
import { FunctionComponent } from 'preact'
type Props = {
application: WebApplication
@@ -20,17 +27,13 @@ type Props = {
isEntitledToCloudBackups: boolean
}
export const CloudBackupProvider: FunctionComponent<Props> = ({
application,
providerName,
isEntitledToCloudBackups,
}) => {
const CloudBackupProvider: FunctionComponent<Props> = ({ application, providerName, isEntitledToCloudBackups }) => {
const [authBegan, setAuthBegan] = useState(false)
const [successfullyInstalled, setSuccessfullyInstalled] = useState(false)
const [backupFrequency, setBackupFrequency] = useState<string | undefined>(undefined)
const [confirmation, setConfirmation] = useState('')
const disable = async (event: Event) => {
const disable: MouseEventHandler = async (event) => {
event.stopPropagation()
try {
@@ -52,7 +55,7 @@ export const CloudBackupProvider: FunctionComponent<Props> = ({
}
}
const installIntegration = (event: Event) => {
const installIntegration: MouseEventHandler = (event) => {
if (!isEntitledToCloudBackups) {
return
}
@@ -117,7 +120,7 @@ export const CloudBackupProvider: FunctionComponent<Props> = ({
return urlSearchParams.get(integrationTokenKeyInUrl)
}
const handleKeyPress = async (event: KeyboardEvent) => {
const handleKeyPress: KeyboardEventHandler = async (event) => {
if (event.key === KeyboardKey.Enter) {
try {
const decryptedCode = atob(confirmation)
@@ -145,8 +148,8 @@ export const CloudBackupProvider: FunctionComponent<Props> = ({
}
}
const handleChange = (event: Event) => {
setConfirmation((event.target as HTMLInputElement).value)
const handleChange: ChangeEventHandler<HTMLInputElement> = (event) => {
setConfirmation(event.target.value)
}
const getIntegrationStatus = useCallback(async () => {
@@ -219,3 +222,5 @@ export const CloudBackupProvider: FunctionComponent<Props> = ({
</div>
)
}
export default CloudBackupProvider

View File

@@ -1,14 +1,8 @@
import { CloudBackupProvider } from './CloudBackupProvider'
import { useCallback, useEffect, useState } from 'preact/hooks'
import CloudBackupProvider from './CloudBackupProvider'
import { useCallback, useEffect, useState, FunctionComponent, Fragment } from 'react'
import { WebApplication } from '@/UIModels/Application'
import {
PreferencesGroup,
PreferencesSegment,
Subtitle,
Text,
Title,
} from '@/Components/Preferences/PreferencesComponents'
import { HorizontalSeparator } from '@/Components/Shared/HorizontalSeparator'
import { Subtitle, Text, Title } from '@/Components/Preferences/PreferencesComponents/Content'
import HorizontalSeparator from '@/Components/Shared/HorizontalSeparator'
import {
FeatureStatus,
FeatureIdentifier,
@@ -16,11 +10,12 @@ import {
MuteFailedCloudBackupsEmailsOption,
SettingName,
} from '@standardnotes/snjs'
import { FunctionComponent } from 'preact'
import { Switch } from '@/Components/Switch/Switch'
import Switch from '@/Components/Switch/Switch'
import { convertStringifiedBooleanToBoolean } from '@/Utils'
import { STRING_FAILED_TO_UPDATE_USER_SETTING } from '@/Strings'
import PreferencesGroup from '@/Components/Preferences/PreferencesComponents/PreferencesGroup'
import PreferencesSegment from '@/Components/Preferences/PreferencesComponents/PreferencesSegment'
const providerData = [{ name: CloudProvider.Dropbox }, { name: CloudProvider.Google }, { name: CloudProvider.OneDrive }]
@@ -28,7 +23,7 @@ type Props = {
application: WebApplication
}
export const CloudLink: FunctionComponent<Props> = ({ application }) => {
const CloudLink: FunctionComponent<Props> = ({ application }) => {
const [isEntitledToCloudBackups, setIsEntitledToCloudBackups] = useState(false)
const [isFailedCloudBackupEmailMuted, setIsFailedCloudBackupEmailMuted] = useState(true)
const [isLoading, setIsLoading] = useState(false)
@@ -121,14 +116,14 @@ export const CloudLink: FunctionComponent<Props> = ({ application }) => {
<HorizontalSeparator classes={`mt-3 mb-3 ${additionalClass}`} />
<div>
{providerData.map(({ name }) => (
<>
<Fragment key={name}>
<CloudBackupProvider
application={application}
providerName={name}
isEntitledToCloudBackups={isEntitledToCloudBackups}
/>
<HorizontalSeparator classes={`mt-3 mb-3 ${additionalClass}`} />
</>
</Fragment>
))}
</div>
</div>
@@ -155,3 +150,5 @@ export const CloudLink: FunctionComponent<Props> = ({ application }) => {
</PreferencesGroup>
)
}
export default CloudLink

View File

@@ -11,27 +11,22 @@ import {
STRING_ENC_NOT_ENABLED,
} from '@/Strings'
import { BackupFile } from '@standardnotes/snjs'
import { useCallback, useEffect, useRef, useState } from 'preact/hooks'
import { ChangeEventHandler, MouseEventHandler, useCallback, useEffect, useRef, useState } from 'react'
import { WebApplication } from '@/UIModels/Application'
import { JSXInternal } from 'preact/src/jsx'
import TargetedEvent = JSXInternal.TargetedEvent
import { AppState } from '@/UIModels/AppState'
import { observer } from 'mobx-react-lite'
import {
PreferencesGroup,
PreferencesSegment,
Title,
Text,
Subtitle,
} from '@/Components/Preferences/PreferencesComponents'
import { Button } from '@/Components/Button/Button'
import { Title, Text, Subtitle } from '@/Components/Preferences/PreferencesComponents/Content'
import Button from '@/Components/Button/Button'
import PreferencesGroup from '../../PreferencesComponents/PreferencesGroup'
import PreferencesSegment from '../../PreferencesComponents/PreferencesSegment'
import HorizontalSeparator from '@/Components/Shared/HorizontalSeparator'
type Props = {
application: WebApplication
appState: AppState
}
export const DataBackups = observer(({ application, appState }: Props) => {
const DataBackups = ({ application, appState }: Props) => {
const fileInputRef = useRef<HTMLInputElement>(null)
const [isImportDataLoading, setIsImportDataLoading] = useState(false)
const {
@@ -109,8 +104,8 @@ export const DataBackups = observer(({ application, appState }: Props) => {
})
}
const importFileSelected = async (event: TargetedEvent<HTMLInputElement, Event>) => {
const { files } = event.target as HTMLInputElement
const importFileSelected: ChangeEventHandler<HTMLInputElement> = async (event) => {
const { files } = event.target
if (!files) {
return
@@ -136,7 +131,7 @@ export const DataBackups = observer(({ application, appState }: Props) => {
}
// Whenever "Import Backup" is either clicked or key-pressed, proceed the import
const handleImportFile = (event: TargetedEvent<HTMLSpanElement, Event> | KeyboardEvent) => {
const handleImportFile: MouseEventHandler = (event) => {
if (event instanceof KeyboardEvent) {
const { code } = event
@@ -158,7 +153,7 @@ export const DataBackups = observer(({ application, appState }: Props) => {
<PreferencesSegment>
<Title>Data Backups</Title>
{!isDesktopApplication() && (
{isDesktopApplication() && (
<Text className="mb-3">
Backups are automatically created on desktop and can be managed via the "Backups" top-level menu.
</Text>
@@ -183,10 +178,11 @@ export const DataBackups = observer(({ application, appState }: Props) => {
<Button variant="normal" onClick={downloadDataArchive} label="Download backup" className="mt-2" />
</PreferencesSegment>
<HorizontalSeparator classes="my-4" />
<PreferencesSegment>
<Subtitle>Import a previously saved backup file</Subtitle>
<div class="flex flex-row items-center mt-3">
<div className="flex flex-row items-center mt-3">
<Button variant="normal" label="Import backup" onClick={handleImportFile} />
<input type="file" ref={fileInputRef} onChange={importFileSelected} className="hidden" />
{isImportDataLoading && <div className="sk-spinner normal info ml-4" />}
@@ -195,4 +191,6 @@ export const DataBackups = observer(({ application, appState }: Props) => {
</PreferencesGroup>
</>
)
})
}
export default observer(DataBackups)

View File

@@ -1,18 +1,13 @@
import { convertStringifiedBooleanToBoolean, isDesktopApplication } from '@/Utils'
import { STRING_FAILED_TO_UPDATE_USER_SETTING } from '@/Strings'
import { useCallback, useEffect, useState } from 'preact/hooks'
import { useCallback, useEffect, useState } from 'react'
import { WebApplication } from '@/UIModels/Application'
import { observer } from 'mobx-react-lite'
import {
PreferencesGroup,
PreferencesSegment,
Subtitle,
Text,
Title,
} from '@/Components/Preferences/PreferencesComponents'
import { Dropdown, DropdownItem } from '@/Components/Dropdown/Dropdown'
import { Switch } from '@/Components/Switch/Switch'
import { HorizontalSeparator } from '@/Components/Shared/HorizontalSeparator'
import { Subtitle, Text, Title } from '@/Components/Preferences/PreferencesComponents/Content'
import Dropdown from '@/Components/Dropdown/Dropdown'
import { DropdownItem } from '@/Components/Dropdown/DropdownItem'
import Switch from '@/Components/Switch/Switch'
import HorizontalSeparator from '@/Components/Shared/HorizontalSeparator'
import {
FeatureStatus,
FeatureIdentifier,
@@ -20,12 +15,14 @@ import {
MuteFailedBackupsEmailsOption,
SettingName,
} from '@standardnotes/snjs'
import PreferencesGroup from '../../PreferencesComponents/PreferencesGroup'
import PreferencesSegment from '../../PreferencesComponents/PreferencesSegment'
type Props = {
application: WebApplication
}
export const EmailBackups = observer(({ application }: Props) => {
const EmailBackups = ({ application }: Props) => {
const [isLoading, setIsLoading] = useState(false)
const [emailFrequency, setEmailFrequency] = useState<EmailBackupFrequency>(EmailBackupFrequency.Disabled)
const [emailFrequencyOptions, setEmailFrequencyOptions] = useState<DropdownItem[]>([])
@@ -132,7 +129,7 @@ export const EmailBackups = observer(({ application }: Props) => {
</a>
.
</Text>
<HorizontalSeparator classes="mt-3 mb-3" />
<HorizontalSeparator classes="my-4" />
</>
)}
<div className={isEntitledToEmailBackups ? '' : 'faded cursor-default pointer-events-none'}>
@@ -157,7 +154,7 @@ export const EmailBackups = observer(({ application }: Props) => {
/>
)}
</div>
<HorizontalSeparator classes="mt-5 mb-4" />
<HorizontalSeparator classes="my-4" />
<Subtitle>Email preferences</Subtitle>
<div className="flex items-center justify-between">
<div className="flex flex-col">
@@ -177,4 +174,6 @@ export const EmailBackups = observer(({ application }: Props) => {
</PreferencesSegment>
</PreferencesGroup>
)
})
}
export default observer(EmailBackups)

View File

@@ -1,20 +1,20 @@
import { PreferencesSegment, Title, Text, Subtitle } from '@/Components/Preferences/PreferencesComponents'
import { useCallback, useEffect, useMemo, useState } from 'preact/hooks'
import { Button } from '@/Components/Button/Button'
import { Title, Text, Subtitle } from '@/Components/Preferences/PreferencesComponents/Content'
import { useCallback, useEffect, useMemo, useState, FunctionComponent } from 'react'
import Button from '@/Components/Button/Button'
import { FileBackupMetadataFile, FileBackupsConstantsV1, FileItem, FileHandleRead } from '@standardnotes/snjs'
import { HorizontalSeparator } from '@/Components/Shared/HorizontalSeparator'
import { EncryptionStatusItem } from '../../Security/Encryption'
import { Icon } from '@/Components/Icon/Icon'
import HorizontalSeparator from '@/Components/Shared/HorizontalSeparator'
import Icon from '@/Components/Icon/Icon'
import { StreamingFileApi } from '@standardnotes/filepicker'
import { FunctionComponent } from 'preact'
import { isHandlingBackupDrag } from '@/Utils/DragTypeCheck'
import { WebApplication } from '@/UIModels/Application'
import EncryptionStatusItem from '../../Security/EncryptionStatusItem'
import PreferencesSegment from '@/Components/Preferences/PreferencesComponents/PreferencesSegment'
type Props = {
application: WebApplication
}
export const BackupsDropZone: FunctionComponent<Props> = ({ application }) => {
const BackupsDropZone: FunctionComponent<Props> = ({ application }) => {
const [droppedFile, setDroppedFile] = useState<FileBackupMetadataFile | undefined>(undefined)
const [decryptedFileItem, setDecryptedFileItem] = useState<FileItem | undefined>(undefined)
const [binaryFile, setBinaryFile] = useState<FileHandleRead | undefined>(undefined)
@@ -225,3 +225,5 @@ export const BackupsDropZone: FunctionComponent<Props> = ({ application }) => {
</>
)
}
export default BackupsDropZone

View File

@@ -0,0 +1,34 @@
import { Subtitle, Title, Text } from '@/Components/Preferences/PreferencesComponents/Content'
import PreferencesGroup from '@/Components/Preferences/PreferencesComponents/PreferencesGroup'
import PreferencesSegment from '@/Components/Preferences/PreferencesComponents/PreferencesSegment'
import { WebApplication } from '@/UIModels/Application'
import { useMemo } from 'react'
import BackupsDropZone from './BackupsDropZone'
import FileBackupsDesktop from './FileBackupsDesktop'
type Props = {
application: WebApplication
}
const FileBackupsCrossPlatform = ({ application }: Props) => {
const fileBackupsService = useMemo(() => application.fileBackups, [application])
return fileBackupsService ? (
<FileBackupsDesktop application={application} backupsService={fileBackupsService} />
) : (
<>
<PreferencesGroup>
<PreferencesSegment>
<Title>File Backups</Title>
<Subtitle>Automatically save encrypted backups of files uploaded to any device to this computer.</Subtitle>
<Text className="mt-3">To enable file backups, use the Standard Notes desktop application.</Text>
</PreferencesSegment>
<PreferencesSegment>
<BackupsDropZone application={application} />
</PreferencesSegment>
</PreferencesGroup>
</>
)
}
export default FileBackupsCrossPlatform

View File

@@ -1,45 +1,24 @@
import { WebApplication } from '@/UIModels/Application'
import { observer } from 'mobx-react-lite'
import {
PreferencesGroup,
PreferencesSegment,
Title,
Text,
Subtitle,
} from '@/Components/Preferences/PreferencesComponents'
import { useCallback, useEffect, useMemo, useState } from 'preact/hooks'
import { Button } from '@/Components/Button/Button'
import { Switch } from '@/Components/Switch/Switch'
import { HorizontalSeparator } from '@/Components/Shared/HorizontalSeparator'
import { EncryptionStatusItem } from '../../Security/Encryption'
import { Icon } from '@/Components/Icon/Icon'
import { BackupsDropZone } from './BackupsDropZone'
import { Title, Text, Subtitle } from '@/Components/Preferences/PreferencesComponents/Content'
import { useCallback, useEffect, useState } from 'react'
import Button from '@/Components/Button/Button'
import Switch from '@/Components/Switch/Switch'
import HorizontalSeparator from '@/Components/Shared/HorizontalSeparator'
import Icon from '@/Components/Icon/Icon'
import BackupsDropZone from './BackupsDropZone'
import EncryptionStatusItem from '../../Security/EncryptionStatusItem'
import PreferencesGroup from '@/Components/Preferences/PreferencesComponents/PreferencesGroup'
import PreferencesSegment from '@/Components/Preferences/PreferencesComponents/PreferencesSegment'
type Props = {
application: WebApplication
backupsService: NonNullable<WebApplication['fileBackups']>
}
export const FileBackups = observer(({ application }: Props) => {
const FileBackupsDesktop = ({ application, backupsService }: Props) => {
const [backupsEnabled, setBackupsEnabled] = useState(false)
const [backupsLocation, setBackupsLocation] = useState('')
const backupsService = useMemo(() => application.fileBackups, [application])
if (!backupsService) {
return (
<>
<PreferencesGroup>
<PreferencesSegment>
<Title>File Backups</Title>
<Subtitle>Automatically save encrypted backups of files uploaded to any device to this computer.</Subtitle>
<Text className="mt-3">To enable file backups, use the Standard Notes desktop application.</Text>
</PreferencesSegment>
<PreferencesSegment>
<BackupsDropZone application={application} />
</PreferencesSegment>
</PreferencesGroup>
</>
)
}
useEffect(() => {
void backupsService.isFilesBackupsEnabled().then(setBackupsEnabled)
@@ -108,6 +87,7 @@ export const FileBackups = observer(({ application }: Props) => {
icon={[<Icon type="attachment-file" className="min-w-5 min-h-5" />]}
checkmark={false}
/>
<div className="flex flex-row mt-5">
<Button
variant="normal"
@@ -133,4 +113,6 @@ export const FileBackups = observer(({ application }: Props) => {
</PreferencesGroup>
</>
)
})
}
export default observer(FileBackupsDesktop)

View File

@@ -1,15 +1,10 @@
import { FunctionComponent } from 'preact'
import {
Title,
Subtitle,
Text,
LinkButton,
PreferencesGroup,
PreferencesPane,
PreferencesSegment,
} from '@/Components/Preferences/PreferencesComponents'
import { FunctionComponent } from 'react'
import { Title, Subtitle, Text, LinkButton } from '@/Components/Preferences/PreferencesComponents/Content'
import PreferencesPane from '../PreferencesComponents/PreferencesPane'
import PreferencesGroup from '../PreferencesComponents/PreferencesGroup'
import PreferencesSegment from '../PreferencesComponents/PreferencesSegment'
export const CloudLink: FunctionComponent = () => (
const CloudLink: FunctionComponent = () => (
<PreferencesPane>
<PreferencesGroup>
<PreferencesSegment>
@@ -83,3 +78,5 @@ export const CloudLink: FunctionComponent = () => (
</PreferencesGroup>
</PreferencesPane>
)
export default CloudLink

View File

@@ -1,10 +1,11 @@
import { DisplayStringForContentType } from '@standardnotes/snjs'
import { Button } from '@/Components/Button/Button'
import { FunctionComponent } from 'preact'
import { Title, Text, Subtitle, PreferencesSegment } from '@/Components/Preferences/PreferencesComponents'
import Button from '@/Components/Button/Button'
import { Fragment, FunctionComponent } from 'react'
import { Title, Text, Subtitle } from '@/Components/Preferences/PreferencesComponents/Content'
import { AnyExtension } from './AnyExtension'
import PreferencesSegment from '../../PreferencesComponents/PreferencesSegment'
export const ConfirmCustomExtension: FunctionComponent<{
const ConfirmCustomExtension: FunctionComponent<{
component: AnyExtension
callback: (confirmed: boolean) => void
}> = ({ component, callback }) => {
@@ -44,11 +45,11 @@ export const ConfirmCustomExtension: FunctionComponent<{
return undefined
}
return (
<>
<Fragment key={field.value}>
<Subtitle>{field.label}</Subtitle>
<Text className={'wrap'}>{field.value}</Text>
<div className="min-h-2" />
</>
</Fragment>
)
})}
@@ -64,3 +65,5 @@ export const ConfirmCustomExtension: FunctionComponent<{
</PreferencesSegment>
)
}
export default ConfirmCustomExtension

View File

@@ -1,11 +1,12 @@
import { FunctionComponent } from 'preact'
import { useState, useRef, useEffect } from 'preact/hooks'
import { FunctionComponent, useState, useRef, useEffect } from 'react'
export const ExtensionInfoCell: FunctionComponent<{
type Props = {
extensionName: string
changeName: (newName: string) => void
isThirdParty: boolean
}> = ({ extensionName, changeName, isThirdParty }) => {
}
const ExtensionInfoCell: FunctionComponent<Props> = ({ extensionName, changeName, isThirdParty }) => {
const [isRenaming, setIsRenaming] = useState(false)
const [newExtensionName, setNewExtensionName] = useState<string>(extensionName)
@@ -42,7 +43,7 @@ export const ExtensionInfoCell: FunctionComponent<{
<input
ref={inputRef}
disabled={!isRenaming || !renameable}
autocomplete="off"
autoComplete="off"
className="flex-grow text-base font-bold no-border bg-default px-0 color-text"
type="text"
value={newExtensionName}
@@ -71,3 +72,5 @@ export const ExtensionInfoCell: FunctionComponent<{
</div>
)
}
export default ExtensionInfoCell

View File

@@ -1,45 +1,32 @@
import { FunctionComponent } from 'preact'
import { SNComponent } from '@standardnotes/snjs'
import { PreferencesSegment, SubtitleLight } from '@/Components/Preferences/PreferencesComponents'
import { Switch } from '@/Components/Switch/Switch'
import { WebApplication } from '@/UIModels/Application'
import { useState } from 'preact/hooks'
import { Button } from '@/Components/Button/Button'
import { ExtensionInfoCell } from './ExtensionInfoCell'
import { AnyExtension } from './AnyExtension'
import { FunctionComponent, useState } from 'react'
import { ComponentMutator, SNComponent } from '@standardnotes/snjs'
import { SubtitleLight } from '@/Components/Preferences/PreferencesComponents/Content'
import Switch from '@/Components/Switch/Switch'
import Button from '@/Components/Button/Button'
import ExtensionInfoCell from './ExtensionInfoCell'
import { ExtensionItemProps } from './ExtensionItemProps'
import PreferencesSegment from '../../PreferencesComponents/PreferencesSegment'
const UseHosted: FunctionComponent<{
offlineOnly: boolean
toggleOfllineOnly: () => void
}> = ({ offlineOnly, toggleOfllineOnly }) => (
toggleOfflineOnly: () => void
}> = ({ offlineOnly, toggleOfflineOnly }) => (
<div className="flex flex-row">
<SubtitleLight className="flex-grow">Use hosted when local is unavailable</SubtitleLight>
<Switch onChange={toggleOfllineOnly} checked={!offlineOnly} />
<Switch onChange={toggleOfflineOnly} checked={!offlineOnly} />
</div>
)
export interface ExtensionItemProps {
application: WebApplication
extension: AnyExtension
first: boolean
latestVersion: string | undefined
uninstall: (extension: AnyExtension) => void
toggleActivate?: (extension: AnyExtension) => void
}
export const ExtensionItem: FunctionComponent<ExtensionItemProps> = ({ application, extension, uninstall }) => {
const ExtensionItem: FunctionComponent<ExtensionItemProps> = ({ application, extension, uninstall }) => {
const [offlineOnly, setOfflineOnly] = useState(extension instanceof SNComponent ? extension.offlineOnly : false)
const [extensionName, setExtensionName] = useState(extension.displayName)
const toggleOffllineOnly = () => {
const toggleOfflineOnly = () => {
const newOfflineOnly = !offlineOnly
setOfflineOnly(newOfflineOnly)
application.mutator
.changeAndSaveItem(extension, (m: any) => {
if (m.content == undefined) {
m.content = {}
}
m.content.offlineOnly = newOfflineOnly
.changeAndSaveItem<ComponentMutator>(extension, (mutator) => {
mutator.offlineOnly = newOfflineOnly
})
.then((item) => {
const component = item as SNComponent
@@ -53,11 +40,8 @@ export const ExtensionItem: FunctionComponent<ExtensionItemProps> = ({ applicati
const changeExtensionName = (newName: string) => {
setExtensionName(newName)
application.mutator
.changeAndSaveItem(extension, (m: any) => {
if (m.content == undefined) {
m.content = {}
}
m.content.name = newName
.changeAndSaveItem<ComponentMutator>(extension, (mutator) => {
mutator.name = newName
})
.then((item) => {
const component = item as SNComponent
@@ -76,9 +60,7 @@ export const ExtensionItem: FunctionComponent<ExtensionItemProps> = ({ applicati
<div className="min-h-2" />
{isThirParty && localInstallable && (
<UseHosted offlineOnly={offlineOnly} toggleOfllineOnly={toggleOffllineOnly} />
)}
{isThirParty && localInstallable && <UseHosted offlineOnly={offlineOnly} toggleOfflineOnly={toggleOfflineOnly} />}
<>
<div className="min-h-2" />
@@ -94,3 +76,5 @@ export const ExtensionItem: FunctionComponent<ExtensionItemProps> = ({ applicati
</PreferencesSegment>
)
}
export default ExtensionItem

View File

@@ -0,0 +1,11 @@
import { WebApplication } from '@/UIModels/Application'
import { AnyExtension } from './AnyExtension'
export interface ExtensionItemProps {
application: WebApplication
extension: AnyExtension
first: boolean
latestVersion: string | undefined
uninstall: (extension: AnyExtension) => void
toggleActivate?: (extension: AnyExtension) => void
}

View File

@@ -1,24 +1,26 @@
import { ButtonType, ContentType, SNComponent } from '@standardnotes/snjs'
import { Button } from '@/Components/Button/Button'
import { DecoratedInput } from '@/Components/Input/DecoratedInput'
import Button from '@/Components/Button/Button'
import DecoratedInput from '@/Components/Input/DecoratedInput'
import { WebApplication } from '@/UIModels/Application'
import { FunctionComponent } from 'preact'
import { Title, PreferencesSegment } from '@/Components/Preferences/PreferencesComponents'
import { useEffect, useRef, useState } from 'preact/hooks'
import { FunctionComponent, useEffect, useRef, useState } from 'react'
import { Title } from '@/Components/Preferences/PreferencesComponents/Content'
import { observer } from 'mobx-react-lite'
import { ExtensionsLatestVersions } from './ExtensionsLatestVersions'
import { ExtensionItem } from './ExtensionItem'
import { ConfirmCustomExtension } from './ConfirmCustomExtension'
import ExtensionItem from './ExtensionItem'
import ConfirmCustomExtension from './ConfirmCustomExtension'
import { AnyExtension } from './AnyExtension'
import PreferencesSegment from '../../PreferencesComponents/PreferencesSegment'
const loadExtensions = (application: WebApplication) =>
application.items.getItems([ContentType.ActionsExtension, ContentType.Component, ContentType.Theme]) as AnyExtension[]
export const Extensions: FunctionComponent<{
type Props = {
application: WebApplication
extensionsLatestVersions: ExtensionsLatestVersions
className?: string
}> = observer(({ application, extensionsLatestVersions, className = '' }) => {
}
const Extensions: FunctionComponent<Props> = ({ application, extensionsLatestVersions, className = '' }) => {
const [customUrl, setCustomUrl] = useState('')
const [confirmableExtension, setConfirmableExtension] = useState<AnyExtension | undefined>(undefined)
const [extensions, setExtensions] = useState(loadExtensions(application))
@@ -134,4 +136,6 @@ export const Extensions: FunctionComponent<{
</div>
</div>
)
})
}
export default observer(Extensions)

View File

@@ -1,18 +1,14 @@
import { Dropdown, DropdownItem } from '@/Components/Dropdown/Dropdown'
import Dropdown from '@/Components/Dropdown/Dropdown'
import { DropdownItem } from '@/Components/Dropdown/DropdownItem'
import { FeatureIdentifier, PrefKey, ComponentArea, ComponentMutator, SNComponent } from '@standardnotes/snjs'
import {
PreferencesGroup,
PreferencesSegment,
Subtitle,
Text,
Title,
} from '@/Components/Preferences/PreferencesComponents'
import { Subtitle, Text, Title } from '@/Components/Preferences/PreferencesComponents/Content'
import { WebApplication } from '@/UIModels/Application'
import { FunctionComponent } from 'preact'
import { useEffect, useState } from 'preact/hooks'
import { HorizontalSeparator } from '@/Components/Shared/HorizontalSeparator'
import { Switch } from '@/Components/Switch/Switch'
import { FunctionComponent, useEffect, useState } from 'react'
import HorizontalSeparator from '@/Components/Shared/HorizontalSeparator'
import Switch from '@/Components/Switch/Switch'
import { PLAIN_EDITOR_NAME } from '@/Constants'
import PreferencesGroup from '../../PreferencesComponents/PreferencesGroup'
import PreferencesSegment from '../../PreferencesComponents/PreferencesSegment'
type Props = {
application: WebApplication
@@ -47,7 +43,7 @@ const getDefaultEditor = (application: WebApplication) => {
return application.componentManager.componentsForArea(ComponentArea.Editor).filter((e) => e.isDefaultEditor())[0]
}
export const Defaults: FunctionComponent<Props> = ({ application }) => {
const Defaults: FunctionComponent<Props> = ({ application }) => {
const [editorItems, setEditorItems] = useState<DropdownItem[]>([])
const [defaultEditorValue, setDefaultEditorValue] = useState(
() => getDefaultEditor(application)?.package_info?.identifier || 'plain-editor',
@@ -123,7 +119,7 @@ export const Defaults: FunctionComponent<Props> = ({ application }) => {
/>
</div>
</div>
<HorizontalSeparator classes="mt-5 mb-3" />
<HorizontalSeparator classes="my-4" />
<div className="flex items-center justify-between">
<div className="flex flex-col">
<Subtitle>Spellcheck</Subtitle>
@@ -134,7 +130,7 @@ export const Defaults: FunctionComponent<Props> = ({ application }) => {
</div>
<Switch onChange={toggleSpellcheck} checked={spellcheck} />
</div>
<HorizontalSeparator classes="mt-5 mb-3" />
<HorizontalSeparator classes="my-4" />
<div className="flex items-center justify-between">
<div className="flex flex-col">
<Subtitle>Add all parent tags when adding a nested tag to a note</Subtitle>
@@ -152,3 +148,5 @@ export const Defaults: FunctionComponent<Props> = ({ application }) => {
</PreferencesGroup>
)
}
export default Defaults

View File

@@ -1,27 +1,27 @@
import { WebApplication } from '@/UIModels/Application'
import { AppState } from '@/UIModels/AppState'
import { FunctionComponent } from 'preact'
import { PreferencesPane } from '@/Components/Preferences/PreferencesComponents'
import { FunctionComponent } from 'react'
import { ExtensionsLatestVersions } from '@/Components/Preferences/Panes/Extensions/ExtensionsLatestVersions'
import { observer } from 'mobx-react-lite'
import { Tools } from './Tools'
import { Defaults } from './Defaults'
import { LabsPane } from './Labs'
import { Advanced } from '@/Components/Preferences/Panes/Account/Advanced'
import Tools from './Tools'
import Defaults from './Defaults'
import LabsPane from './Labs'
import Advanced from '@/Components/Preferences/Panes/Account/Advanced'
import PreferencesPane from '../../PreferencesComponents/PreferencesPane'
interface GeneralProps {
type Props = {
appState: AppState
application: WebApplication
extensionsLatestVersions: ExtensionsLatestVersions
}
export const General: FunctionComponent<GeneralProps> = observer(
({ appState, application, extensionsLatestVersions }) => (
<PreferencesPane>
<Tools application={application} />
<Defaults application={application} />
<LabsPane application={application} />
<Advanced application={application} appState={appState} extensionsLatestVersions={extensionsLatestVersions} />
</PreferencesPane>
),
const General: FunctionComponent<Props> = ({ appState, application, extensionsLatestVersions }) => (
<PreferencesPane>
<Tools application={application} />
<Defaults application={application} />
<LabsPane application={application} />
<Advanced application={application} appState={appState} extensionsLatestVersions={extensionsLatestVersions} />
</PreferencesPane>
)
export default observer(General)

View File

@@ -1,17 +1,12 @@
import { Switch } from '@/Components/Switch/Switch'
import {
PreferencesGroup,
PreferencesSegment,
Subtitle,
Text,
Title,
} from '@/Components/Preferences/PreferencesComponents'
import Switch from '@/Components/Switch/Switch'
import { Subtitle, Text, Title } from '@/Components/Preferences/PreferencesComponents/Content'
import { WebApplication } from '@/UIModels/Application'
import { FeatureIdentifier, FeatureStatus, FindNativeFeature } from '@standardnotes/snjs'
import { FunctionComponent } from 'preact'
import { useCallback, useEffect, useState } from 'preact/hooks'
import { Fragment, FunctionComponent, useCallback, useEffect, useState } from 'react'
import { usePremiumModal } from '@/Hooks/usePremiumModal'
import { HorizontalSeparator } from '@/Components/Shared/HorizontalSeparator'
import HorizontalSeparator from '@/Components/Shared/HorizontalSeparator'
import PreferencesGroup from '../../PreferencesComponents/PreferencesGroup'
import PreferencesSegment from '../../PreferencesComponents/PreferencesSegment'
type ExperimentalFeatureItem = {
identifier: FeatureIdentifier
@@ -25,7 +20,7 @@ type Props = {
application: WebApplication
}
export const LabsPane: FunctionComponent<Props> = ({ application }) => {
const LabsPane: FunctionComponent<Props> = ({ application }) => {
const [experimentalFeatures, setExperimentalFeatures] = useState<ExperimentalFeatureItem[]>([])
const reloadExperimentalFeatures = useCallback(() => {
@@ -67,7 +62,7 @@ export const LabsPane: FunctionComponent<Props> = ({ application }) => {
const showHorizontalSeparator = experimentalFeatures.length > 1 && index !== experimentalFeatures.length - 1
return (
<>
<Fragment key={identifier}>
<div className="flex items-center justify-between">
<div className="flex flex-col">
<Subtitle>{name}</Subtitle>
@@ -76,7 +71,7 @@ export const LabsPane: FunctionComponent<Props> = ({ application }) => {
<Switch onChange={toggleFeature} checked={isEnabled} />
</div>
{showHorizontalSeparator && <HorizontalSeparator classes="mt-5 mb-3" />}
</>
</Fragment>
)
})}
{experimentalFeatures.length === 0 && (
@@ -91,3 +86,5 @@ export const LabsPane: FunctionComponent<Props> = ({ application }) => {
</PreferencesGroup>
)
}
export default LabsPane

View File

@@ -1,23 +1,18 @@
import { HorizontalSeparator } from '@/Components/Shared/HorizontalSeparator'
import { Switch } from '@/Components/Switch/Switch'
import {
PreferencesGroup,
PreferencesSegment,
Subtitle,
Text,
Title,
} from '@/Components/Preferences/PreferencesComponents'
import HorizontalSeparator from '@/Components/Shared/HorizontalSeparator'
import Switch from '@/Components/Switch/Switch'
import { Subtitle, Text, Title } from '@/Components/Preferences/PreferencesComponents/Content'
import { WebApplication } from '@/UIModels/Application'
import { PrefKey } from '@standardnotes/snjs'
import { observer } from 'mobx-react-lite'
import { FunctionalComponent } from 'preact'
import { useState } from 'preact/hooks'
import { FunctionComponent, useState } from 'react'
import PreferencesGroup from '../../PreferencesComponents/PreferencesGroup'
import PreferencesSegment from '../../PreferencesComponents/PreferencesSegment'
type Props = {
application: WebApplication
}
export const Tools: FunctionalComponent<Props> = observer(({ application }: Props) => {
const Tools: FunctionComponent<Props> = ({ application }: Props) => {
const [monospaceFont, setMonospaceFont] = useState(() =>
application.getPreference(PrefKey.EditorMonospaceEnabled, true),
)
@@ -47,7 +42,7 @@ export const Tools: FunctionalComponent<Props> = observer(({ application }: Prop
</div>
<Switch onChange={toggleMonospaceFont} checked={monospaceFont} />
</div>
<HorizontalSeparator classes="mt-5 mb-3" />
<HorizontalSeparator classes="my-4" />
<div className="flex items-center justify-between">
<div className="flex flex-col">
<Subtitle>Margin Resizers</Subtitle>
@@ -59,4 +54,6 @@ export const Tools: FunctionalComponent<Props> = observer(({ application }: Prop
</PreferencesSegment>
</PreferencesGroup>
)
})
}
export default observer(Tools)

View File

@@ -1,15 +1,11 @@
import { FunctionComponent } from 'preact'
import {
Title,
Subtitle,
Text,
LinkButton,
PreferencesGroup,
PreferencesPane,
PreferencesSegment,
} from '@/Components/Preferences/PreferencesComponents'
import { FunctionComponent } from 'react'
import { Title, Subtitle, Text, LinkButton } from '@/Components/Preferences/PreferencesComponents/Content'
import HorizontalSeparator from '@/Components/Shared/HorizontalSeparator'
import PreferencesPane from '../PreferencesComponents/PreferencesPane'
import PreferencesGroup from '../PreferencesComponents/PreferencesGroup'
import PreferencesSegment from '../PreferencesComponents/PreferencesSegment'
export const HelpAndFeedback: FunctionComponent = () => (
const HelpAndFeedback: FunctionComponent = () => (
<PreferencesPane>
<PreferencesGroup>
<PreferencesSegment>
@@ -26,6 +22,7 @@ export const HelpAndFeedback: FunctionComponent = () => (
</a>
</Text>
</PreferencesSegment>
<HorizontalSeparator classes="my-4" />
<PreferencesSegment>
<Subtitle>Can I collaborate with others on a note?</Subtitle>
<Text>
@@ -34,6 +31,7 @@ export const HelpAndFeedback: FunctionComponent = () => (
conflicts, which may result in the duplication of notes.
</Text>
</PreferencesSegment>
<HorizontalSeparator classes="my-4" />
<PreferencesSegment>
<Subtitle>Can I use Standard Notes totally offline?</Subtitle>
<Text>
@@ -44,6 +42,7 @@ export const HelpAndFeedback: FunctionComponent = () => (
</a>
</Text>
</PreferencesSegment>
<HorizontalSeparator classes="my-4" />
<PreferencesSegment>
<Subtitle>Cant find your question here?</Subtitle>
<LinkButton className="mt-3" label="Open FAQ" link="https://standardnotes.com/help" />
@@ -83,3 +82,5 @@ export const HelpAndFeedback: FunctionComponent = () => (
</PreferencesGroup>
</PreferencesPane>
)
export default HelpAndFeedback

View File

@@ -1,23 +1,20 @@
import {
PreferencesGroup,
PreferencesPane,
PreferencesSegment,
Title,
Subtitle,
Text,
} from '@/Components/Preferences/PreferencesComponents'
import { Title, Subtitle, Text } from '@/Components/Preferences/PreferencesComponents/Content'
import { observer } from 'mobx-react-lite'
import { WebApplication } from '@/UIModels/Application'
import { ButtonType, ListedAccount } from '@standardnotes/snjs'
import { useCallback, useEffect, useState } from 'preact/hooks'
import { ListedAccountItem } from './BlogItem'
import { Button } from '@/Components/Button/Button'
import { useCallback, useEffect, useState } from 'react'
import ListedAccountItem from './ListedAccountItem'
import Button from '@/Components/Button/Button'
import HorizontalSeparator from '@/Components/Shared/HorizontalSeparator'
import PreferencesPane from '../../PreferencesComponents/PreferencesPane'
import PreferencesGroup from '../../PreferencesComponents/PreferencesGroup'
import PreferencesSegment from '../../PreferencesComponents/PreferencesSegment'
type Props = {
application: WebApplication
}
export const Listed = observer(({ application }: Props) => {
const Listed = ({ application }: Props) => {
const [accounts, setAccounts] = useState<ListedAccount[]>([])
const [requestingAccount, setRequestingAccount] = useState<boolean>()
@@ -92,19 +89,24 @@ export const Listed = observer(({ application }: Props) => {
</Text>
</PreferencesSegment>
{application.getUser() && (
<PreferencesSegment>
<Subtitle>Get Started</Subtitle>
<Text>Create a free Listed author account to get started.</Text>
<Button
className="mt-3"
variant="normal"
disabled={requestingAccount}
label={requestingAccount ? 'Creating account...' : 'Create new author'}
onClick={registerNewAccount}
/>
</PreferencesSegment>
<>
<HorizontalSeparator classes="my-4" />
<PreferencesSegment>
<Subtitle>Get Started</Subtitle>
<Text>Create a free Listed author account to get started.</Text>
<Button
className="mt-3"
variant="normal"
disabled={requestingAccount}
label={requestingAccount ? 'Creating account...' : 'Create new author'}
onClick={registerNewAccount}
/>
</PreferencesSegment>
</>
)}
</PreferencesGroup>
</PreferencesPane>
)
})
}
export default observer(Listed)

View File

@@ -1,9 +1,8 @@
import { HorizontalSeparator } from '@/Components/Shared/HorizontalSeparator'
import { LinkButton, Subtitle } from '@/Components/Preferences/PreferencesComponents'
import HorizontalSeparator from '@/Components/Shared/HorizontalSeparator'
import { LinkButton, Subtitle } from '@/Components/Preferences/PreferencesComponents/Content'
import { WebApplication } from '@/UIModels/Application'
import { ListedAccount, ListedAccountInfo } from '@standardnotes/snjs'
import { FunctionalComponent } from 'preact'
import { useEffect, useState } from 'preact/hooks'
import { FunctionComponent, useEffect, useState } from 'react'
type Props = {
account: ListedAccount
@@ -11,7 +10,7 @@ type Props = {
application: WebApplication
}
export const ListedAccountItem: FunctionalComponent<Props> = ({ account, showSeparator, application }) => {
const ListedAccountItem: FunctionComponent<Props> = ({ account, showSeparator, application }) => {
const [isLoading, setIsLoading] = useState(false)
const [accountInfo, setAccountInfo] = useState<ListedAccountInfo>()
@@ -42,3 +41,5 @@ export const ListedAccountItem: FunctionalComponent<Props> = ({ account, showSep
</>
)
}
export default ListedAccountItem

View File

@@ -1,54 +1,15 @@
import { Icon } from '@/Components/Icon/Icon'
import { STRING_E2E_ENABLED, STRING_ENC_NOT_ENABLED, STRING_LOCAL_ENC_ENABLED } from '@/Strings'
import { AppState } from '@/UIModels/AppState'
import { observer } from 'mobx-react-lite'
import { ComponentChild, FunctionComponent } from 'preact'
import { PreferencesGroup, PreferencesSegment, Text, Title } from '@/Components/Preferences/PreferencesComponents'
import { FunctionComponent } from 'react'
import { Title, Text } from '../../PreferencesComponents/Content'
import PreferencesGroup from '../../PreferencesComponents/PreferencesGroup'
import PreferencesSegment from '../../PreferencesComponents/PreferencesSegment'
import EncryptionEnabled from './EncryptionEnabled'
const formatCount = (count: number, itemType: string) => `${count} / ${count} ${itemType}`
type Props = { appState: AppState }
export const EncryptionStatusItem: FunctionComponent<{
icon: ComponentChild
status: string
checkmark?: boolean
}> = ({ icon, status, checkmark = true }) => (
<div className="w-full rounded py-1.5 px-3 text-input my-1 min-h-8 flex flex-row items-center bg-contrast no-border focus-within:ring-info">
{icon}
<div className="min-w-3 min-h-1" />
<div className="flex-grow color-text text-sm">{status}</div>
<div className="min-w-3 min-h-1" />
{checkmark && <Icon className="success min-w-4 min-h-4" type="check-bold" />}
</div>
)
const EncryptionEnabled: FunctionComponent<{ appState: AppState }> = observer(({ appState }) => {
const count = appState.accountMenu.structuredNotesAndTagsCount
const notes = formatCount(count.notes, 'notes')
const tags = formatCount(count.tags, 'tags')
const archived = formatCount(count.archived, 'archived notes')
const deleted = formatCount(count.deleted, 'trashed notes')
const noteIcon = <Icon type="rich-text" className="min-w-5 min-h-5" />
const tagIcon = <Icon type="hashtag" className="min-w-5 min-h-5" />
const archiveIcon = <Icon type="archive" className="min-w-5 min-h-5" />
const trashIcon = <Icon type="trash" className="min-w-5 min-h-5" />
return (
<>
<div className="flex flex-row items-start pb-1 pt-1.5">
<EncryptionStatusItem status={notes} icon={[noteIcon]} />
<div className="min-w-3" />
<EncryptionStatusItem status={tags} icon={[tagIcon]} />
</div>
<div className="flex flex-row items-start">
<EncryptionStatusItem status={archived} icon={[archiveIcon]} />
<div className="min-w-3" />
<EncryptionStatusItem status={deleted} icon={[trashIcon]} />
</div>
</>
)
})
export const Encryption: FunctionComponent<{ appState: AppState }> = observer(({ appState }) => {
const Encryption: FunctionComponent<Props> = ({ appState }) => {
const app = appState.application
const hasUser = app.hasAccount()
const hasPasscode = app.hasPasscode()
@@ -70,4 +31,6 @@ export const Encryption: FunctionComponent<{ appState: AppState }> = observer(({
</PreferencesSegment>
</PreferencesGroup>
)
})
}
export default observer(Encryption)

View File

@@ -0,0 +1,39 @@
import Icon from '@/Components/Icon/Icon'
import { AppState } from '@/UIModels/AppState'
import { observer } from 'mobx-react-lite'
import { FunctionComponent } from 'react'
import EncryptionStatusItem from './EncryptionStatusItem'
import { formatCount } from './formatCount'
type Props = {
appState: AppState
}
const EncryptionEnabled: FunctionComponent<Props> = ({ appState }) => {
const count = appState.accountMenu.structuredNotesAndTagsCount
const notes = formatCount(count.notes, 'notes')
const tags = formatCount(count.tags, 'tags')
const archived = formatCount(count.archived, 'archived notes')
const deleted = formatCount(count.deleted, 'trashed notes')
const noteIcon = <Icon type="rich-text" className="min-w-5 min-h-5" />
const tagIcon = <Icon type="hashtag" className="min-w-5 min-h-5" />
const archiveIcon = <Icon type="archive" className="min-w-5 min-h-5" />
const trashIcon = <Icon type="trash" className="min-w-5 min-h-5" />
return (
<>
<div className="flex flex-row items-start pb-1 pt-1.5">
<EncryptionStatusItem status={notes} icon={noteIcon} />
<div className="min-w-3" />
<EncryptionStatusItem status={tags} icon={tagIcon} />
</div>
<div className="flex flex-row items-start">
<EncryptionStatusItem status={archived} icon={archiveIcon} />
<div className="min-w-3" />
<EncryptionStatusItem status={deleted} icon={trashIcon} />
</div>
</>
)
}
export default observer(EncryptionEnabled)

View File

@@ -0,0 +1,20 @@
import Icon from '@/Components/Icon/Icon'
import { FunctionComponent, ReactNode } from 'react'
type Props = {
icon: ReactNode
status: string
checkmark?: boolean
}
const EncryptionStatusItem: FunctionComponent<Props> = ({ icon, status, checkmark = true }) => (
<div className="w-full rounded py-1.5 px-3 text-input my-1 min-h-8 flex flex-row items-center bg-contrast no-border focus-within:ring-info">
{icon}
<div className="min-w-3 min-h-1" />
<div className="flex-grow color-text text-sm">{status}</div>
<div className="min-w-3 min-h-1" />
{checkmark && <Icon className="success min-w-4 min-h-4" type="check-bold" />}
</div>
)
export default EncryptionStatusItem

View File

@@ -1,26 +1,21 @@
import { AppState } from '@/UIModels/AppState'
import { observer } from 'mobx-react-lite'
import { FunctionComponent } from 'preact'
import {
PreferencesGroup,
PreferencesSegment,
Text,
Title,
Subtitle,
} from '@/Components/Preferences/PreferencesComponents'
import { Fragment, FunctionComponent, useState } from 'react'
import { Text, Title, Subtitle } from '@/Components/Preferences/PreferencesComponents/Content'
import {
ButtonType,
ClientDisplayableError,
DisplayStringForContentType,
EncryptedItemInterface,
} from '@standardnotes/snjs'
import { Button } from '@/Components/Button/Button'
import { HorizontalSeparator } from '@/Components/Shared/HorizontalSeparator'
import { useState } from 'preact/hooks'
import Button from '@/Components/Button/Button'
import HorizontalSeparator from '@/Components/Shared/HorizontalSeparator'
import PreferencesSegment from '../../PreferencesComponents/PreferencesSegment'
import PreferencesGroup from '../../PreferencesComponents/PreferencesGroup'
type Props = { appState: AppState }
export const ErroredItems: FunctionComponent<Props> = observer(({ appState }: Props) => {
const ErroredItems: FunctionComponent<Props> = ({ appState }: Props) => {
const app = appState.application
const [erroredItems, setErroredItems] = useState(app.items.invalidItems)
@@ -96,7 +91,7 @@ export const ErroredItems: FunctionComponent<Props> = observer(({ appState }: Pr
{erroredItems.map((item, index) => {
return (
<>
<Fragment key={item.uuid}>
<div className="flex items-center justify-between">
<div className="flex flex-col">
<Subtitle>{`${getContentTypeDisplay(item)} created on ${item.createdAtString}`}</Subtitle>
@@ -134,10 +129,12 @@ export const ErroredItems: FunctionComponent<Props> = observer(({ appState }: Pr
</div>
</div>
{index < erroredItems.length - 1 && <HorizontalSeparator classes="mt-5 mb-3" />}
</>
</Fragment>
)
})}
</PreferencesSegment>
</PreferencesGroup>
)
})
}
export default observer(ErroredItems)

View File

@@ -10,23 +10,22 @@ import {
} from '@/Strings'
import { WebApplication } from '@/UIModels/Application'
import { preventRefreshing } from '@/Utils'
import { JSXInternal } from 'preact/src/jsx'
import TargetedEvent = JSXInternal.TargetedEvent
import TargetedMouseEvent = JSXInternal.TargetedMouseEvent
import { alertDialog } from '@/Services/AlertService'
import { useCallback, useEffect, useRef, useState } from 'preact/hooks'
import { ChangeEventHandler, FormEvent, useCallback, useEffect, useRef, useState } from 'react'
import { ApplicationEvent } from '@standardnotes/snjs'
import { observer } from 'mobx-react-lite'
import { AppState } from '@/UIModels/AppState'
import { PreferencesSegment, Title, Text, PreferencesGroup } from '@/Components/Preferences/PreferencesComponents'
import { Button } from '@/Components/Button/Button'
import { Title, Text } from '@/Components/Preferences/PreferencesComponents/Content'
import Button from '@/Components/Button/Button'
import PreferencesGroup from '../../PreferencesComponents/PreferencesGroup'
import PreferencesSegment from '../../PreferencesComponents/PreferencesSegment'
type Props = {
application: WebApplication
appState: AppState
}
export const PasscodeLock = observer(({ application, appState }: Props) => {
const PasscodeLock = ({ application, appState }: Props) => {
const keyStorageInfo = StringUtils.keyStorageInfo(application)
const passcodeAutoLockOptions = application.getAutolockService().getAutoLockIntervalOptions()
@@ -34,8 +33,8 @@ export const PasscodeLock = observer(({ application, appState }: Props) => {
const passcodeInputRef = useRef<HTMLInputElement>(null)
const [passcode, setPasscode] = useState<string | undefined>(undefined)
const [passcodeConfirmation, setPasscodeConfirmation] = useState<string | undefined>(undefined)
const [passcode, setPasscode] = useState<string>()
const [passcodeConfirmation, setPasscodeConfirmation] = useState<string>()
const [selectedAutoLockInterval, setSelectedAutoLockInterval] = useState<unknown>(null)
const [isPasscodeFocused, setIsPasscodeFocused] = useState(false)
const [showPasscodeForm, setShowPasscodeForm] = useState(false)
@@ -93,17 +92,17 @@ export const PasscodeLock = observer(({ application, appState }: Props) => {
})
}
const handlePasscodeChange = (event: TargetedEvent<HTMLInputElement>) => {
const { value } = event.target as HTMLInputElement
const handlePasscodeChange: ChangeEventHandler<HTMLInputElement> = (event) => {
const { value } = event.target
setPasscode(value)
}
const handleConfirmPasscodeChange = (event: TargetedEvent<HTMLInputElement>) => {
const { value } = event.target as HTMLInputElement
const handleConfirmPasscodeChange: ChangeEventHandler<HTMLInputElement> = (event) => {
const { value } = event.target
setPasscodeConfirmation(value)
}
const submitPasscodeForm = async (event: TargetedEvent<HTMLFormElement> | TargetedMouseEvent<HTMLButtonElement>) => {
const submitPasscodeForm = async (event: MouseEvent | FormEvent) => {
event.preventDefault()
if (!passcode || passcode.length === 0) {
@@ -167,6 +166,12 @@ export const PasscodeLock = observer(({ application, appState }: Props) => {
}
}, [application])
const cancelPasscodeForm = () => {
setShowPasscodeForm(false)
setPasscode(undefined)
setPasscodeConfirmation(undefined)
}
return (
<>
<PreferencesGroup>
@@ -197,20 +202,20 @@ export const PasscodeLock = observer(({ application, appState }: Props) => {
className="sk-input contrast"
type="password"
ref={passcodeInputRef}
value={passcode}
value={passcode ? passcode : ''}
onChange={handlePasscodeChange}
placeholder="Passcode"
/>
<input
className="sk-input contrast"
type="password"
value={passcodeConfirmation}
value={passcodeConfirmation ? passcodeConfirmation : ''}
onChange={handleConfirmPasscodeChange}
placeholder="Confirm Passcode"
/>
<div className="min-h-2" />
<Button variant="primary" onClick={submitPasscodeForm} label="Set Passcode" className="mr-3" />
<Button variant="normal" onClick={() => setShowPasscodeForm(false)} label="Cancel" />
<Button variant="normal" onClick={cancelPasscodeForm} label="Cancel" />
</form>
)}
@@ -237,6 +242,7 @@ export const PasscodeLock = observer(({ application, appState }: Props) => {
{passcodeAutoLockOptions.map((option) => {
return (
<a
key={option.value}
className={`sk-a info mr-3 ${option.value === selectedAutoLockInterval ? 'boxed' : ''}`}
onClick={() => selectAutoLockInterval(option.value)}
>
@@ -251,4 +257,6 @@ export const PasscodeLock = observer(({ application, appState }: Props) => {
)}
</>
)
})
}
export default observer(PasscodeLock)

View File

@@ -1,31 +1,26 @@
import { HorizontalSeparator } from '@/Components/Shared/HorizontalSeparator'
import { Switch } from '@/Components/Switch/Switch'
import {
PreferencesGroup,
PreferencesSegment,
Subtitle,
Text,
Title,
} from '@/Components/Preferences/PreferencesComponents'
import HorizontalSeparator from '@/Components/Shared/HorizontalSeparator'
import Switch from '@/Components/Switch/Switch'
import { Subtitle, Text, Title } from '@/Components/Preferences/PreferencesComponents/Content'
import { WebApplication } from '@/UIModels/Application'
import { MuteSignInEmailsOption, LogSessionUserAgentOption, SettingName } from '@standardnotes/snjs'
import { observer } from 'mobx-react-lite'
import { FunctionalComponent } from 'preact'
import { useCallback, useEffect, useState } from 'preact/hooks'
import { FunctionComponent, useCallback, useEffect, useState } from 'react'
import { STRING_FAILED_TO_UPDATE_USER_SETTING } from '@/Strings'
import PreferencesGroup from '../../PreferencesComponents/PreferencesGroup'
import PreferencesSegment from '../../PreferencesComponents/PreferencesSegment'
type Props = {
application: WebApplication
}
export const Privacy: FunctionalComponent<Props> = observer(({ application }: Props) => {
const Privacy: FunctionComponent<Props> = ({ application }: Props) => {
const [signInEmailsMutedValue, setSignInEmailsMutedValue] = useState<MuteSignInEmailsOption>(
MuteSignInEmailsOption.NotMuted,
)
const [sessionUaLoggingValue, setSessionUaLoggingValue] = useState<LogSessionUserAgentOption>(
LogSessionUserAgentOption.Enabled,
)
const [isLoading, setIsLoading] = useState(false)
const [isLoading, setIsLoading] = useState(true)
const updateSetting = async (settingName: SettingName, payload: string): Promise<boolean> => {
try {
@@ -110,7 +105,7 @@ export const Privacy: FunctionalComponent<Props> = observer(({ application }: Pr
</Text>
</div>
{isLoading ? (
<div className={'sk-spinner info small'} />
<div className={'sk-spinner info small flex-shrink-0 ml-2'} />
) : (
<Switch
onChange={toggleMuteSignInEmails}
@@ -118,7 +113,7 @@ export const Privacy: FunctionalComponent<Props> = observer(({ application }: Pr
/>
)}
</div>
<HorizontalSeparator classes="mt-5 mb-3" />
<HorizontalSeparator classes="my-4" />
<div className="flex items-center justify-between">
<div className="flex flex-col">
<Subtitle>Session user agent logging</Subtitle>
@@ -129,7 +124,7 @@ export const Privacy: FunctionalComponent<Props> = observer(({ application }: Pr
</Text>
</div>
{isLoading ? (
<div className={'sk-spinner info small'} />
<div className={'sk-spinner info small flex-shrink-0 ml-2'} />
) : (
<Switch
onChange={toggleSessionLogging}
@@ -141,4 +136,6 @@ export const Privacy: FunctionalComponent<Props> = observer(({ application }: Pr
</PreferencesSegment>
</PreferencesGroup>
)
})
}
export default observer(Privacy)

View File

@@ -1,16 +1,17 @@
import { WebApplication } from '@/UIModels/Application'
import { FunctionalComponent } from 'preact'
import { useCallback, useState, useEffect } from 'preact/hooks'
import { FunctionComponent, useCallback, useState, useEffect } from 'react'
import { ApplicationEvent } from '@standardnotes/snjs'
import { isSameDay } from '@/Utils'
import { PreferencesGroup, PreferencesSegment, Title, Text } from '@/Components/Preferences/PreferencesComponents'
import { Button } from '@/Components/Button/Button'
import Button from '@/Components/Button/Button'
import PreferencesGroup from '../../PreferencesComponents/PreferencesGroup'
import PreferencesSegment from '../../PreferencesComponents/PreferencesSegment'
import { Title, Text } from '../../PreferencesComponents/Content'
type Props = {
application: WebApplication
}
export const Protections: FunctionalComponent<Props> = ({ application }) => {
const Protections: FunctionComponent<Props> = ({ application }) => {
const enableProtections = () => {
application.clearProtectionSession().catch(console.error)
}
@@ -88,3 +89,5 @@ export const Protections: FunctionalComponent<Props> = ({ application }) => {
</PreferencesGroup>
)
}
export default Protections

View File

@@ -1,25 +1,21 @@
import { WebApplication } from '@/UIModels/Application'
import { AppState } from '@/UIModels/AppState'
import { FunctionComponent } from 'preact'
import { PreferencesPane } from '@/Components/Preferences/PreferencesComponents'
import { TwoFactorAuthWrapper } from '../TwoFactorAuth/TwoFactorAuthWrapper'
import { FunctionComponent } from 'react'
import TwoFactorAuthWrapper from '../TwoFactorAuth/TwoFactorAuthWrapper'
import { MfaProps } from '../TwoFactorAuth/MfaProps'
import { Encryption } from './Encryption'
import { PasscodeLock } from './PasscodeLock'
import { Privacy } from './Privacy'
import { Protections } from './Protections'
import { ErroredItems } from './ErroredItems'
import Encryption from './Encryption'
import PasscodeLock from './PasscodeLock'
import Privacy from './Privacy'
import Protections from './Protections'
import ErroredItems from './ErroredItems'
import PreferencesPane from '@/Components/Preferences/PreferencesComponents/PreferencesPane'
interface SecurityProps extends MfaProps {
appState: AppState
application: WebApplication
}
export const securityPrefsHasBubble = (application: WebApplication): boolean => {
return application.items.invalidItems.length > 0
}
export const Security: FunctionComponent<SecurityProps> = (props) => (
const Security: FunctionComponent<SecurityProps> = (props) => (
<PreferencesPane>
<Encryption appState={props.appState} />
{props.application.items.invalidItems.length > 0 && <ErroredItems appState={props.appState} />}
@@ -29,3 +25,5 @@ export const Security: FunctionComponent<SecurityProps> = (props) => (
{props.application.getUser() && <Privacy application={props.application} />}
</PreferencesPane>
)
export default Security

View File

@@ -0,0 +1 @@
export const formatCount = (count: number, itemType: string) => `${count} / ${count} ${itemType}`

View File

@@ -0,0 +1,5 @@
import { WebApplication } from '@/UIModels/Application'
export const securityPrefsHasBubble = (application: WebApplication): boolean => {
return application.items.invalidItems.length > 0
}

View File

@@ -1,15 +1,16 @@
import { Icon } from '@/Components/Icon/Icon'
import Icon from '@/Components/Icon/Icon'
import { Disclosure, DisclosureButton, DisclosurePanel } from '@reach/disclosure'
import { FunctionComponent } from 'preact'
import { useState, useRef, useEffect } from 'preact/hooks'
import { FunctionComponent, useState, useRef, useEffect, MouseEventHandler } from 'react'
import { IconType } from '@standardnotes/snjs'
const DisclosureIconButton: FunctionComponent<{
type Props = {
className?: string
icon: IconType
onMouseEnter?: any
onMouseLeave?: any
}> = ({ className = '', icon, onMouseEnter, onMouseLeave }) => (
onMouseEnter?: MouseEventHandler<HTMLButtonElement>
onMouseLeave?: MouseEventHandler<HTMLButtonElement>
}
const DisclosureIconButton: FunctionComponent<Props> = ({ className = '', icon, onMouseEnter, onMouseLeave }) => (
<DisclosureButton
onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}
@@ -26,7 +27,7 @@ const DisclosureIconButton: FunctionComponent<{
* Note: it can be generalized but more use cases are required
* @returns
*/
export const AuthAppInfoTooltip: FunctionComponent = () => {
const AuthAppInfoTooltip: FunctionComponent = () => {
const [isClicked, setClicked] = useState(false)
const [isHover, setHover] = useState(false)
const ref = useRef(null)
@@ -61,3 +62,5 @@ py-1.5 px-2 absolute w-103 -top-10 -left-51`}
</Disclosure>
)
}
export default AuthAppInfoTooltip

View File

@@ -1,5 +1,11 @@
import { FunctionComponent } from 'preact'
import { FunctionComponent } from 'react'
export const Bullet: FunctionComponent<{ className?: string }> = ({ className = '' }) => (
type Props = {
className?: string
}
const Bullet: FunctionComponent<Props> = ({ className = '' }) => (
<div className={`min-w-1 min-h-1 rounded-full bg-inverted-default ${className} mr-2`} />
)
export default Bullet

View File

@@ -1,10 +1,11 @@
import { FunctionComponent } from 'preact'
import { FunctionComponent, useState } from 'react'
import IconButton from '@/Components/Button/IconButton'
import { IconButton } from '@/Components/Button/IconButton'
type Props = {
copyValue: string
}
import { useState } from 'preact/hooks'
export const CopyButton: FunctionComponent<{ copyValue: string }> = ({ copyValue: secretKey }) => {
const CopyButton: FunctionComponent<Props> = ({ copyValue: secretKey }) => {
const [isCopied, setCopied] = useState(false)
return (
<IconButton
@@ -19,3 +20,5 @@ export const CopyButton: FunctionComponent<{ copyValue: string }> = ({ copyValue
/>
)
}
export default CopyButton

View File

@@ -1,22 +1,22 @@
import { Button } from '@/Components/Button/Button'
import { DecoratedInput } from '@/Components/Input/DecoratedInput'
import { IconButton } from '@/Components/Button/IconButton'
import Button from '@/Components/Button/Button'
import DecoratedInput from '@/Components/Input/DecoratedInput'
import IconButton from '@/Components/Button/IconButton'
import { observer } from 'mobx-react-lite'
import { FunctionComponent } from 'preact'
import { CopyButton } from './CopyButton'
import { Bullet } from './Bullet'
import { FunctionComponent } from 'react'
import CopyButton from './CopyButton'
import Bullet from './Bullet'
import { downloadSecretKey } from './download-secret-key'
import { TwoFactorActivation } from './TwoFactorActivation'
import {
ModalDialog,
ModalDialogButtons,
ModalDialogDescription,
ModalDialogLabel,
} from '@/Components/Shared/ModalDialog'
import ModalDialog from '@/Components/Shared/ModalDialog'
import ModalDialogButtons from '@/Components/Shared/ModalDialogButtons'
import ModalDialogDescription from '@/Components/Shared/ModalDialogDescription'
import ModalDialogLabel from '@/Components/Shared/ModalDialogLabel'
export const SaveSecretKey: FunctionComponent<{
type Props = {
activation: TwoFactorActivation
}> = observer(({ activation: act }) => {
}
const SaveSecretKey: FunctionComponent<Props> = ({ activation: act }) => {
const download = (
<IconButton
focusable={false}
@@ -81,4 +81,6 @@ export const SaveSecretKey: FunctionComponent<{
</ModalDialogButtons>
</ModalDialog>
)
})
}
export default observer(SaveSecretKey)

View File

@@ -1,22 +1,22 @@
import { FunctionComponent } from 'preact'
import { FunctionComponent } from 'react'
import { observer } from 'mobx-react-lite'
import QRCode from 'qrcode.react'
import { DecoratedInput } from '@/Components/Input/DecoratedInput'
import { Button } from '@/Components/Button/Button'
import DecoratedInput from '@/Components/Input/DecoratedInput'
import Button from '@/Components/Button/Button'
import { TwoFactorActivation } from './TwoFactorActivation'
import { AuthAppInfoTooltip } from './AuthAppInfoPopup'
import {
ModalDialog,
ModalDialogButtons,
ModalDialogDescription,
ModalDialogLabel,
} from '@/Components/Shared/ModalDialog'
import { CopyButton } from './CopyButton'
import { Bullet } from './Bullet'
import AuthAppInfoTooltip from './AuthAppInfoPopup'
import ModalDialog from '@/Components/Shared/ModalDialog'
import ModalDialogButtons from '@/Components/Shared/ModalDialogButtons'
import ModalDialogDescription from '@/Components/Shared/ModalDialogDescription'
import ModalDialogLabel from '@/Components/Shared/ModalDialogLabel'
import CopyButton from './CopyButton'
import Bullet from './Bullet'
export const ScanQRCode: FunctionComponent<{
type Props = {
activation: TwoFactorActivation
}> = observer(({ activation: act }) => {
}
const ScanQRCode: FunctionComponent<Props> = ({ activation: act }) => {
return (
<ModalDialog>
<ModalDialogLabel closeDialog={act.cancelActivation}>Step 1 of 3 - Scan QR code</ModalDialogLabel>
@@ -58,4 +58,6 @@ export const ScanQRCode: FunctionComponent<{
</ModalDialogButtons>
</ModalDialog>
)
})
}
export default observer(ScanQRCode)

View File

@@ -1,14 +1,16 @@
import { observer } from 'mobx-react-lite'
import { FunctionComponent } from 'preact'
import { FunctionComponent } from 'react'
import { TwoFactorActivation } from './TwoFactorActivation'
import { SaveSecretKey } from './SaveSecretKey'
import { ScanQRCode } from './ScanQRCode'
import { Verification } from './Verification'
import { TwoFactorSuccess } from './TwoFactorSuccess'
import SaveSecretKey from './SaveSecretKey'
import ScanQRCode from './ScanQRCode'
import Verification from './Verification'
import TwoFactorSuccess from './TwoFactorSuccess'
export const TwoFactorActivationView: FunctionComponent<{
type Props = {
activation: TwoFactorActivation
}> = observer(({ activation: act }) => {
}
const TwoFactorActivationView: FunctionComponent<Props> = ({ activation: act }) => {
switch (act.activationStep) {
case 'scan-qr-code':
return <ScanQRCode activation={act} />
@@ -19,4 +21,6 @@ export const TwoFactorActivationView: FunctionComponent<{
case 'success':
return <TwoFactorSuccess activation={act} />
}
})
}
export default observer(TwoFactorActivationView)

View File

@@ -1,77 +0,0 @@
import { FunctionComponent } from 'preact'
import { Title, Text, PreferencesGroup, PreferencesSegment } from '@/Components/Preferences/PreferencesComponents'
import { Switch } from '@/Components/Switch/Switch'
import { observer } from 'mobx-react-lite'
import { is2FAActivation, is2FADisabled, TwoFactorAuth } from './TwoFactorAuth'
import { TwoFactorActivationView } from './TwoFactorActivationView'
const TwoFactorTitle: FunctionComponent<{ auth: TwoFactorAuth }> = observer(({ auth }) => {
if (!auth.isLoggedIn()) {
return <Title>Two-factor authentication not available</Title>
}
if (!auth.isMfaFeatureAvailable()) {
return <Title>Two-factor authentication not available</Title>
}
return <Title>Two-factor authentication</Title>
})
const TwoFactorDescription: FunctionComponent<{ auth: TwoFactorAuth }> = observer(({ auth }) => {
if (!auth.isLoggedIn()) {
return <Text>Sign in or register for an account to configure 2FA.</Text>
}
if (!auth.isMfaFeatureAvailable()) {
return (
<Text>
A paid subscription plan is required to enable 2FA.{' '}
<a target="_blank" href="https://standardnotes.com/features">
Learn more
</a>
.
</Text>
)
}
return <Text>An extra layer of security when logging in to your account.</Text>
})
const TwoFactorSwitch: FunctionComponent<{ auth: TwoFactorAuth }> = observer(({ auth }) => {
if (!(auth.isLoggedIn() && auth.isMfaFeatureAvailable())) {
return null
}
if (auth.status === 'fetching') {
return <div class="sk-spinner normal info" />
}
return <Switch checked={!is2FADisabled(auth.status)} onChange={auth.toggle2FA} />
})
export const TwoFactorAuthView: FunctionComponent<{
auth: TwoFactorAuth
}> = observer(({ auth }) => {
return (
<>
<PreferencesGroup>
<PreferencesSegment>
<div className="flex flex-row items-center">
<div className="flex-grow flex flex-col">
<TwoFactorTitle auth={auth} />
<TwoFactorDescription auth={auth} />
</div>
<div className="flex flex-col justify-center items-center min-w-15">
<TwoFactorSwitch auth={auth} />
</div>
</div>
</PreferencesSegment>
{auth.errorMessage != null && (
<PreferencesSegment>
<Text className="color-danger">{auth.errorMessage}</Text>
</PreferencesSegment>
)}
</PreferencesGroup>
{auth.status !== 'fetching' && is2FAActivation(auth.status) && (
<TwoFactorActivationView activation={auth.status} />
)}
</>
)
})

View File

@@ -0,0 +1,45 @@
import { FunctionComponent } from 'react'
import { Text } from '@/Components/Preferences/PreferencesComponents/Content'
import { observer } from 'mobx-react-lite'
import { is2FAActivation, TwoFactorAuth } from '../TwoFactorAuth'
import TwoFactorActivationView from '../TwoFactorActivationView'
import TwoFactorTitle from './TwoFactorTitle'
import TwoFactorDescription from './TwoFactorDescription'
import TwoFactorSwitch from './TwoFactorSwitch'
import PreferencesGroup from '@/Components/Preferences/PreferencesComponents/PreferencesGroup'
import PreferencesSegment from '@/Components/Preferences/PreferencesComponents/PreferencesSegment'
type Props = {
auth: TwoFactorAuth
}
const TwoFactorAuthView: FunctionComponent<Props> = ({ auth }) => {
return (
<>
<PreferencesGroup>
<PreferencesSegment>
<div className="flex flex-row items-center">
<div className="flex-grow flex flex-col">
<TwoFactorTitle auth={auth} />
<TwoFactorDescription auth={auth} />
</div>
<div className="flex flex-col justify-center items-center min-w-15">
<TwoFactorSwitch auth={auth} />
</div>
</div>
</PreferencesSegment>
{auth.errorMessage != null && (
<PreferencesSegment>
<Text className="color-danger">{auth.errorMessage}</Text>
</PreferencesSegment>
)}
</PreferencesGroup>
{auth.status !== 'fetching' && is2FAActivation(auth.status) && (
<TwoFactorActivationView activation={auth.status} />
)}
</>
)
}
export default observer(TwoFactorAuthView)

View File

@@ -0,0 +1,28 @@
import { FunctionComponent } from 'react'
import { Text } from '@/Components/Preferences/PreferencesComponents/Content'
import { observer } from 'mobx-react-lite'
import { TwoFactorAuth } from '../TwoFactorAuth'
type Props = {
auth: TwoFactorAuth
}
const TwoFactorDescription: FunctionComponent<Props> = ({ auth }) => {
if (!auth.isLoggedIn()) {
return <Text>Sign in or register for an account to configure 2FA.</Text>
}
if (!auth.isMfaFeatureAvailable()) {
return (
<Text>
A paid subscription plan is required to enable 2FA.{' '}
<a target="_blank" href="https://standardnotes.com/features">
Learn more
</a>
.
</Text>
)
}
return <Text>An extra layer of security when logging in to your account.</Text>
}
export default observer(TwoFactorDescription)

View File

@@ -0,0 +1,22 @@
import { FunctionComponent } from 'react'
import Switch from '@/Components/Switch/Switch'
import { observer } from 'mobx-react-lite'
import { is2FADisabled, TwoFactorAuth } from '../TwoFactorAuth'
type Props = {
auth: TwoFactorAuth
}
const TwoFactorSwitch: FunctionComponent<Props> = ({ auth }) => {
if (!(auth.isLoggedIn() && auth.isMfaFeatureAvailable())) {
return null
}
if (auth.status === 'fetching') {
return <div className="sk-spinner normal info" />
}
return <Switch checked={!is2FADisabled(auth.status)} onChange={auth.toggle2FA} />
}
export default observer(TwoFactorSwitch)

View File

@@ -0,0 +1,20 @@
import { FunctionComponent } from 'react'
import { Title } from '@/Components/Preferences/PreferencesComponents/Content'
import { observer } from 'mobx-react-lite'
import { TwoFactorAuth } from '../TwoFactorAuth'
type Props = {
auth: TwoFactorAuth
}
const TwoFactorTitle: FunctionComponent<Props> = ({ auth }) => {
if (!auth.isLoggedIn()) {
return <Title>Two-factor authentication not available</Title>
}
if (!auth.isMfaFeatureAvailable()) {
return <Title>Two-factor authentication not available</Title>
}
return <Title>Two-factor authentication</Title>
}
export default observer(TwoFactorTitle)

View File

@@ -1,11 +1,12 @@
import { FunctionComponent } from 'preact'
import { useState } from 'preact/hooks'
import { FunctionComponent, useState } from 'react'
import { MfaProps } from './MfaProps'
import { TwoFactorAuth } from './TwoFactorAuth'
import { TwoFactorAuthView } from './TwoFactorAuthView'
import TwoFactorAuthView from './TwoFactorAuthView/TwoFactorAuthView'
export const TwoFactorAuthWrapper: FunctionComponent<MfaProps> = (props) => {
const TwoFactorAuthWrapper: FunctionComponent<MfaProps> = (props) => {
const [auth] = useState(() => new TwoFactorAuth(props.mfaProvider, props.userProvider))
auth.fetchStatus()
return <TwoFactorAuthView auth={auth} />
}
export default TwoFactorAuthWrapper

View File

@@ -1,17 +1,18 @@
import { Button } from '@/Components/Button/Button'
import ModalDialog, {
ModalDialogButtons,
ModalDialogDescription,
ModalDialogLabel,
} from '@/Components/Shared/ModalDialog'
import { Subtitle } from '@/Components/Preferences/PreferencesComponents'
import Button from '@/Components/Button/Button'
import ModalDialog from '@/Components/Shared/ModalDialog'
import ModalDialogButtons from '@/Components/Shared/ModalDialogButtons'
import ModalDialogDescription from '@/Components/Shared/ModalDialogDescription'
import ModalDialogLabel from '@/Components/Shared/ModalDialogLabel'
import { Subtitle } from '@/Components/Preferences/PreferencesComponents/Content'
import { observer } from 'mobx-react-lite'
import { FunctionComponent } from 'preact'
import { FunctionComponent } from 'react'
import { TwoFactorActivation } from './TwoFactorActivation'
export const TwoFactorSuccess: FunctionComponent<{
type Props = {
activation: TwoFactorActivation
}> = observer(({ activation: act }) => (
}
const TwoFactorSuccess: FunctionComponent<Props> = ({ activation: act }) => (
<ModalDialog>
<ModalDialogLabel closeDialog={act.finishActivation}>Successfully Enabled</ModalDialogLabel>
<ModalDialogDescription>
@@ -23,4 +24,6 @@ export const TwoFactorSuccess: FunctionComponent<{
<Button className="min-w-20" variant="primary" label="Finish" onClick={act.finishActivation} />
</ModalDialogButtons>
</ModalDialog>
))
)
export default observer(TwoFactorSuccess)

View File

@@ -1,19 +1,19 @@
import { Button } from '@/Components/Button/Button'
import { DecoratedInput } from '@/Components/Input/DecoratedInput'
import Button from '@/Components/Button/Button'
import DecoratedInput from '@/Components/Input/DecoratedInput'
import { observer } from 'mobx-react-lite'
import { FunctionComponent } from 'preact'
import { Bullet } from './Bullet'
import { FunctionComponent } from 'react'
import Bullet from './Bullet'
import { TwoFactorActivation } from './TwoFactorActivation'
import {
ModalDialog,
ModalDialogButtons,
ModalDialogDescription,
ModalDialogLabel,
} from '@/Components/Shared/ModalDialog'
import ModalDialog from '@/Components/Shared/ModalDialog'
import ModalDialogButtons from '@/Components/Shared/ModalDialogButtons'
import ModalDialogDescription from '@/Components/Shared/ModalDialogDescription'
import ModalDialogLabel from '@/Components/Shared/ModalDialogLabel'
export const Verification: FunctionComponent<{
type Props = {
activation: TwoFactorActivation
}> = observer(({ activation: act }) => {
}
const Verification: FunctionComponent<Props> = ({ activation: act }) => {
const secretKeyClass = act.verificationStatus === 'invalid-secret' ? 'border-danger' : ''
const authTokenClass = act.verificationStatus === 'invalid-auth-code' ? 'border-danger' : ''
return (
@@ -53,4 +53,6 @@ export const Verification: FunctionComponent<{
</ModalDialogButtons>
</ModalDialog>
)
})
}
export default observer(Verification)

View File

@@ -1,5 +0,0 @@
export * from './HelpFeedback'
export * from './Security/Security'
export * from './Account/AccountPreferences'
export * from './Listed/Listed'
export * from './General/General'

View File

@@ -0,0 +1,15 @@
import { FunctionComponent } from 'react'
import { observer } from 'mobx-react-lite'
import { PreferencesMenu } from './PreferencesMenu'
import PreferencesMenuView from './PreferencesMenuView'
import PaneSelector from './PaneSelector'
import { PreferencesProps } from './PreferencesProps'
const PreferencesCanvas: FunctionComponent<PreferencesProps & { menu: PreferencesMenu }> = (props) => (
<div className="flex flex-row flex-grow min-h-0 justify-between">
<PreferencesMenuView menu={props.menu} />
<PaneSelector {...props} />
</div>
)
export default observer(PreferencesCanvas)

View File

@@ -1,4 +1,4 @@
import { FunctionComponent } from 'preact'
import { FunctionComponent } from 'react'
export const Title: FunctionComponent = ({ children }) => (
<>

View File

@@ -1,5 +1,5 @@
import { Icon } from '@/Components/Icon/Icon'
import { FunctionComponent } from 'preact'
import Icon from '@/Components/Icon/Icon'
import { FunctionComponent } from 'react'
import { IconType } from '@standardnotes/snjs'
interface Props {
@@ -10,7 +10,7 @@ interface Props {
onClick: () => void
}
export const MenuItem: FunctionComponent<Props> = ({ iconType, label, selected, onClick, hasBubble }) => (
const PreferencesMenuItem: FunctionComponent<Props> = ({ iconType, label, selected, onClick, hasBubble }) => (
<div
className={`preferences-menu-item select-none ${selected ? 'selected' : ''}`}
onClick={(e) => {
@@ -24,3 +24,5 @@ export const MenuItem: FunctionComponent<Props> = ({ iconType, label, selected,
{hasBubble && <span className="ml-1 color-warning"></span>}
</div>
)
export default PreferencesMenuItem

View File

@@ -1,21 +1,7 @@
import { FunctionComponent } from 'preact'
import { HorizontalSeparator } from '@/Components/Shared/HorizontalSeparator'
import { FunctionComponent } from 'react'
const HorizontalLine: FunctionComponent<{ index: number; length: number }> = ({ index, length }) => {
return index < length - 1 ? <HorizontalSeparator classes="my-4" /> : null
}
export const PreferencesGroup: FunctionComponent = ({ children }) => (
<div className="bg-default border-1 border-solid rounded border-main px-6 py-6 flex flex-col mb-3">
{Array.isArray(children)
? children
.filter((child) => child != undefined && child !== '' && child !== false)
.map((child, i, arr) => (
<>
{child}
<HorizontalLine index={i} length={arr.length} />
</>
))
: children}
</div>
const PreferencesGroup: FunctionComponent = ({ children }) => (
<div className="bg-default border-1 border-solid rounded border-main px-6 py-6 flex flex-col mb-3">{children}</div>
)
export default PreferencesGroup

View File

@@ -1,6 +1,6 @@
import { FunctionComponent } from 'preact'
import { FunctionComponent } from 'react'
export const PreferencesPane: FunctionComponent = ({ children }) => (
const PreferencesPane: FunctionComponent = ({ children }) => (
<div className="color-foreground flex-grow flex flex-row overflow-y-auto min-h-0">
<div className="flex-grow flex flex-col py-6 items-center">
<div className="w-125 max-w-125 flex flex-col">
@@ -10,3 +10,5 @@ export const PreferencesPane: FunctionComponent = ({ children }) => (
<div className="flex-basis-55 flex-shrink" />
</div>
)
export default PreferencesPane

View File

@@ -1,8 +1,10 @@
import { FunctionComponent } from 'preact'
import { FunctionComponent } from 'react'
type Props = {
classes?: string
}
export const PreferencesSegment: FunctionComponent<Props> = ({ children, classes = '' }) => (
const PreferencesSegment: FunctionComponent<Props> = ({ children, classes = '' }) => (
<div className={`flex flex-col ${classes}`}>{children}</div>
)
export default PreferencesSegment

View File

@@ -1,5 +0,0 @@
export * from './Content'
export * from './MenuItem'
export * from './PreferencesPane'
export * from './PreferencesGroup'
export * from './PreferencesSegment'

View File

@@ -2,7 +2,7 @@ import { action, makeAutoObservable, observable } from 'mobx'
import { IconType } from '@standardnotes/snjs'
import { WebApplication } from '@/UIModels/Application'
import { ExtensionsLatestVersions } from './Panes/Extensions/ExtensionsLatestVersions'
import { securityPrefsHasBubble } from './Panes'
import { securityPrefsHasBubble } from './Panes/Security/securityPrefsHasBubble'
const PREFERENCE_IDS = [
'general',

View File

@@ -1,14 +1,16 @@
import { observer } from 'mobx-react-lite'
import { FunctionComponent } from 'preact'
import { MenuItem } from './PreferencesComponents'
import { FunctionComponent } from 'react'
import PreferencesMenuItem from './PreferencesComponents/MenuItem'
import { PreferencesMenu } from './PreferencesMenu'
export const PreferencesMenuView: FunctionComponent<{
type Props = {
menu: PreferencesMenu
}> = observer(({ menu }) => (
}
const PreferencesMenuView: FunctionComponent<Props> = ({ menu }) => (
<div className="min-w-55 overflow-y-auto flex flex-col px-3 py-6">
{menu.menuItems.map((pref) => (
<MenuItem
<PreferencesMenuItem
key={pref.id}
iconType={pref.icon}
label={pref.label}
@@ -18,4 +20,6 @@ export const PreferencesMenuView: FunctionComponent<{
/>
))}
</div>
))
)
export default observer(PreferencesMenuView)

View File

@@ -0,0 +1,9 @@
import { WebApplication } from '@/UIModels/Application'
import { MfaProps } from './Panes/TwoFactorAuth/MfaProps'
import { AppState } from '@/UIModels/AppState'
export interface PreferencesProps extends MfaProps {
application: WebApplication
appState: AppState
closePreferences: () => void
}

View File

@@ -1,80 +1,13 @@
import { RoundIconButton } from '@/Components/Button/RoundIconButton'
import { TitleBar } from '@/Components/TitleBar/TitleBar'
import { Title } from '@/Components/TitleBar/Title'
import { FunctionComponent } from 'preact'
import RoundIconButton from '@/Components/Button/RoundIconButton'
import TitleBar from '@/Components/TitleBar/TitleBar'
import Title from '@/Components/TitleBar/Title'
import { FunctionComponent, useEffect, useMemo } from 'react'
import { observer } from 'mobx-react-lite'
import { AccountPreferences, HelpAndFeedback, Listed, General, Security } from './Panes'
import { PreferencesMenu } from './PreferencesMenu'
import { PreferencesMenuView } from './PreferencesMenuView'
import { WebApplication } from '@/UIModels/Application'
import { MfaProps } from './Panes/TwoFactorAuth/MfaProps'
import { AppState } from '@/UIModels/AppState'
import { useEffect, useMemo } from 'preact/hooks'
import { Backups } from '@/Components/Preferences/Panes/Backups/Backups'
import { Appearance } from './Panes/Appearance'
import PreferencesCanvas from './PreferencesCanvas'
import { PreferencesProps } from './PreferencesProps'
interface PreferencesProps extends MfaProps {
application: WebApplication
appState: AppState
closePreferences: () => void
}
const PaneSelector: FunctionComponent<PreferencesProps & { menu: PreferencesMenu }> = observer(
({ menu, appState, application, mfaProvider, userProvider }) => {
switch (menu.selectedPaneId) {
case 'general':
return (
<General
appState={appState}
application={application}
extensionsLatestVersions={menu.extensionsLatestVersions}
/>
)
case 'account':
return <AccountPreferences application={application} appState={appState} />
case 'appearance':
return <Appearance application={application} />
case 'security':
return (
<Security
mfaProvider={mfaProvider}
userProvider={userProvider}
appState={appState}
application={application}
/>
)
case 'backups':
return <Backups application={application} appState={appState} />
case 'listed':
return <Listed application={application} />
case 'shortcuts':
return null
case 'accessibility':
return null
case 'get-free-month':
return null
case 'help-feedback':
return <HelpAndFeedback />
default:
return (
<General
appState={appState}
application={application}
extensionsLatestVersions={menu.extensionsLatestVersions}
/>
)
}
},
)
const PreferencesCanvas: FunctionComponent<PreferencesProps & { menu: PreferencesMenu }> = observer((props) => (
<div className="flex flex-row flex-grow min-h-0 justify-between">
<PreferencesMenuView menu={props.menu} />
<PaneSelector {...props} />
</div>
))
export const PreferencesView: FunctionComponent<PreferencesProps> = observer((props) => {
const PreferencesView: FunctionComponent<PreferencesProps> = (props) => {
const menu = useMemo(
() => new PreferencesMenu(props.application, props.appState.enableUnfinishedFeatures),
[props.appState.enableUnfinishedFeatures, props.application],
@@ -97,7 +30,6 @@ export const PreferencesView: FunctionComponent<PreferencesProps> = observer((pr
return (
<div className="h-full w-full absolute top-left-0 flex flex-col bg-contrast z-index-preferences">
<TitleBar className="items-center justify-between">
{/* div is added so flex justify-between can center the title */}
<div className="h-8 w-8" />
<Title className="text-lg">Your preferences for Standard Notes</Title>
<RoundIconButton
@@ -111,4 +43,6 @@ export const PreferencesView: FunctionComponent<PreferencesProps> = observer((pr
<PreferencesCanvas {...props} menu={menu} />
</div>
)
})
}
export default observer(PreferencesView)

View File

@@ -1,28 +1,22 @@
import { FunctionComponent } from 'preact'
import { FunctionComponent } from 'react'
import { observer } from 'mobx-react-lite'
import { WebApplication } from '@/UIModels/Application'
import { PreferencesView } from './PreferencesView'
import { AppState } from '@/UIModels/AppState'
import PreferencesView from './PreferencesView'
import { PreferencesViewWrapperProps } from './PreferencesViewWrapperProps'
export interface PreferencesViewWrapperProps {
appState: AppState
application: WebApplication
const PreferencesViewWrapper: FunctionComponent<PreferencesViewWrapperProps> = ({ appState, application }) => {
if (!appState.preferences?.isOpen) {
return null
}
return (
<PreferencesView
closePreferences={() => appState.preferences.closePreferences()}
application={application}
appState={appState}
mfaProvider={application}
userProvider={application}
/>
)
}
export const PreferencesViewWrapper: FunctionComponent<PreferencesViewWrapperProps> = observer(
({ appState, application }) => {
if (!appState.preferences?.isOpen) {
return null
}
return (
<PreferencesView
closePreferences={() => appState.preferences.closePreferences()}
application={application}
appState={appState}
mfaProvider={application}
userProvider={application}
/>
)
},
)
export default observer(PreferencesViewWrapper)

View File

@@ -0,0 +1,7 @@
import { WebApplication } from '@/UIModels/Application'
import { AppState } from '@/UIModels/AppState'
export interface PreferencesViewWrapperProps {
appState: AppState
application: WebApplication
}