internal: change password preprocessing step (#2347)
This commit is contained in:
@@ -0,0 +1,17 @@
|
||||
import { CheckmarkCircle } from '../UIElements/CheckmarkCircle'
|
||||
|
||||
export const FinishStep = () => {
|
||||
return (
|
||||
<div className="flex flex-col gap-1">
|
||||
<div className="flex flex-row items-start gap-3">
|
||||
<div className="pt-1">
|
||||
<CheckmarkCircle />
|
||||
</div>
|
||||
<div className="flex flex-col">
|
||||
<div className="text-base font-bold">Your password has been successfully changed.</div>
|
||||
<p>Ensure you are running the latest version of Standard Notes on all platforms for maximum compatibility.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
import { useState } from 'react'
|
||||
import DecoratedPasswordInput from '../Input/DecoratedPasswordInput'
|
||||
|
||||
export const PasswordStep = ({
|
||||
onCurrentPasswordChange,
|
||||
onNewPasswordChange,
|
||||
onNewPasswordConfirmationChange,
|
||||
}: {
|
||||
onCurrentPasswordChange: (value: string) => void
|
||||
onNewPasswordChange: (value: string) => void
|
||||
onNewPasswordConfirmationChange: (value: string) => void
|
||||
}) => {
|
||||
const [currentPassword, setCurrentPassword] = useState<string>('')
|
||||
const [newPassword, setNewPassword] = useState<string>('')
|
||||
const [newPasswordConfirmation, setNewPasswordConfirmation] = useState<string>('')
|
||||
|
||||
const handleCurrentPasswordChange = (value: string) => {
|
||||
setCurrentPassword(value)
|
||||
onCurrentPasswordChange(value)
|
||||
}
|
||||
|
||||
const handleNewPasswordChange = (value: string) => {
|
||||
setNewPassword(value)
|
||||
onNewPasswordChange(value)
|
||||
}
|
||||
|
||||
const handleNewPasswordConfirmationChange = (value: string) => {
|
||||
setNewPasswordConfirmation(value)
|
||||
onNewPasswordConfirmationChange(value)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex flex-col pb-1.5">
|
||||
<form>
|
||||
<label htmlFor="password-wiz-current-password" className="mb-1 block">
|
||||
Current Password
|
||||
</label>
|
||||
|
||||
<DecoratedPasswordInput
|
||||
autofocus={true}
|
||||
id="password-wiz-current-password"
|
||||
value={currentPassword}
|
||||
onChange={handleCurrentPasswordChange}
|
||||
type="password"
|
||||
/>
|
||||
|
||||
<div className="min-h-2" />
|
||||
|
||||
<label htmlFor="password-wiz-new-password" className="mb-1 block">
|
||||
New Password
|
||||
</label>
|
||||
|
||||
<DecoratedPasswordInput
|
||||
id="password-wiz-new-password"
|
||||
value={newPassword}
|
||||
onChange={handleNewPasswordChange}
|
||||
type="password"
|
||||
/>
|
||||
|
||||
<div className="min-h-2" />
|
||||
|
||||
<label htmlFor="password-wiz-confirm-new-password" className="mb-1 block">
|
||||
Confirm New Password
|
||||
</label>
|
||||
|
||||
<DecoratedPasswordInput
|
||||
id="password-wiz-confirm-new-password"
|
||||
value={newPasswordConfirmation}
|
||||
onChange={handleNewPasswordConfirmationChange}
|
||||
type="password"
|
||||
/>
|
||||
</form>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,10 +1,12 @@
|
||||
import { WebApplication } from '@/Application/WebApplication'
|
||||
import { createRef } from 'react'
|
||||
import { AbstractComponent } from '@/Components/Abstract/PureComponent'
|
||||
import DecoratedPasswordInput from '../Input/DecoratedPasswordInput'
|
||||
import Modal from '../Modal/Modal'
|
||||
import { isMobileScreen } from '@/Utils'
|
||||
import Spinner from '../Spinner/Spinner'
|
||||
import { PasswordStep } from './PasswordStep'
|
||||
import { FinishStep } from './FinishStep'
|
||||
import { PreprocessingStep } from './PreprocessingStep'
|
||||
import { featureTrunkVaultsEnabled } from '@/FeatureTrunk'
|
||||
|
||||
interface Props {
|
||||
application: WebApplication
|
||||
@@ -19,7 +21,6 @@ type State = {
|
||||
processing?: boolean
|
||||
showSpinner?: boolean
|
||||
step: Steps
|
||||
title: string
|
||||
}
|
||||
|
||||
const DEFAULT_CONTINUE_TITLE = 'Continue'
|
||||
@@ -27,8 +28,9 @@ const GENERATING_CONTINUE_TITLE = 'Generating Keys...'
|
||||
const FINISH_CONTINUE_TITLE = 'Finish'
|
||||
|
||||
enum Steps {
|
||||
PasswordStep = 1,
|
||||
FinishStep = 2,
|
||||
PreprocessingStep = 'preprocessing-step',
|
||||
PasswordStep = 'password-step',
|
||||
FinishStep = 'finish-step',
|
||||
}
|
||||
|
||||
type FormData = {
|
||||
@@ -39,22 +41,32 @@ type FormData = {
|
||||
}
|
||||
|
||||
class PasswordWizard extends AbstractComponent<Props, State> {
|
||||
private currentPasswordInput = createRef<HTMLInputElement>()
|
||||
|
||||
constructor(props: Props) {
|
||||
super(props, props.application)
|
||||
this.registerWindowUnloadStopper()
|
||||
this.state = {
|
||||
|
||||
const baseState = {
|
||||
formData: {},
|
||||
continueTitle: DEFAULT_CONTINUE_TITLE,
|
||||
step: Steps.PasswordStep,
|
||||
title: 'Change Password',
|
||||
}
|
||||
|
||||
if (featureTrunkVaultsEnabled()) {
|
||||
this.state = {
|
||||
...baseState,
|
||||
lockContinue: true,
|
||||
step: Steps.PreprocessingStep,
|
||||
}
|
||||
} else {
|
||||
this.state = {
|
||||
...baseState,
|
||||
lockContinue: false,
|
||||
step: Steps.PasswordStep,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override componentDidMount(): void {
|
||||
super.componentDidMount()
|
||||
this.currentPasswordInput.current?.focus()
|
||||
}
|
||||
|
||||
override componentWillUnmount(): void {
|
||||
@@ -83,6 +95,15 @@ class PasswordWizard extends AbstractComponent<Props, State> {
|
||||
|
||||
if (this.state.step === Steps.FinishStep) {
|
||||
this.dismiss()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if (this.state.step === Steps.PreprocessingStep) {
|
||||
this.setState({
|
||||
step: Steps.PasswordStep,
|
||||
})
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@@ -142,7 +163,6 @@ class PasswordWizard extends AbstractComponent<Props, State> {
|
||||
return false
|
||||
}
|
||||
|
||||
/** Validate current password */
|
||||
const success = await this.application.validateAccountPassword(this.state.formData.currentPassword as string)
|
||||
if (!success) {
|
||||
this.application.alertService
|
||||
@@ -192,7 +212,7 @@ class PasswordWizard extends AbstractComponent<Props, State> {
|
||||
}
|
||||
|
||||
dismiss = () => {
|
||||
if (this.state.lockContinue) {
|
||||
if (this.state.processing) {
|
||||
this.application.alertService.alert('Cannot close window until pending tasks are complete.').catch(console.error)
|
||||
} else {
|
||||
this.props.dismissModal()
|
||||
@@ -226,11 +246,32 @@ class PasswordWizard extends AbstractComponent<Props, State> {
|
||||
}).catch(console.error)
|
||||
}
|
||||
|
||||
setContinueEnabled = (enabled: boolean) => {
|
||||
this.setState({
|
||||
lockContinue: !enabled,
|
||||
})
|
||||
}
|
||||
|
||||
nextStepFromPreprocessing = () => {
|
||||
if (this.state.lockContinue) {
|
||||
this.setState(
|
||||
{
|
||||
lockContinue: false,
|
||||
},
|
||||
() => {
|
||||
void this.nextStep()
|
||||
},
|
||||
)
|
||||
} else {
|
||||
void this.nextStep()
|
||||
}
|
||||
}
|
||||
|
||||
override render() {
|
||||
return (
|
||||
<div className="sn-component h-full w-full md:h-auto md:w-auto" id="password-wizard">
|
||||
<Modal
|
||||
title={this.state.title}
|
||||
title={'Change Password'}
|
||||
close={this.dismiss}
|
||||
actions={[
|
||||
{
|
||||
@@ -253,59 +294,23 @@ class PasswordWizard extends AbstractComponent<Props, State> {
|
||||
},
|
||||
]}
|
||||
>
|
||||
<div className="px-4 py-4">
|
||||
<div className="px-4.5 py-4">
|
||||
{this.state.step === Steps.PreprocessingStep && (
|
||||
<PreprocessingStep
|
||||
onContinue={this.nextStepFromPreprocessing}
|
||||
setContinueEnabled={this.setContinueEnabled}
|
||||
/>
|
||||
)}
|
||||
|
||||
{this.state.step === Steps.PasswordStep && (
|
||||
<div className="flex flex-col pb-1.5">
|
||||
<form>
|
||||
<label htmlFor="password-wiz-current-password" className="mb-1 block">
|
||||
Current Password
|
||||
</label>
|
||||
|
||||
<DecoratedPasswordInput
|
||||
ref={this.currentPasswordInput}
|
||||
id="password-wiz-current-password"
|
||||
value={this.state.formData.currentPassword}
|
||||
onChange={this.handleCurrentPasswordInputChange}
|
||||
type="password"
|
||||
/>
|
||||
|
||||
<div className="min-h-2" />
|
||||
|
||||
<label htmlFor="password-wiz-new-password" className="mb-1 block">
|
||||
New Password
|
||||
</label>
|
||||
|
||||
<DecoratedPasswordInput
|
||||
id="password-wiz-new-password"
|
||||
value={this.state.formData.newPassword}
|
||||
onChange={this.handleNewPasswordInputChange}
|
||||
type="password"
|
||||
/>
|
||||
|
||||
<div className="min-h-2" />
|
||||
|
||||
<label htmlFor="password-wiz-confirm-new-password" className="mb-1 block">
|
||||
Confirm New Password
|
||||
</label>
|
||||
|
||||
<DecoratedPasswordInput
|
||||
id="password-wiz-confirm-new-password"
|
||||
value={this.state.formData.newPasswordConfirmation}
|
||||
onChange={this.handleNewPasswordConfirmationInputChange}
|
||||
type="password"
|
||||
/>
|
||||
</form>
|
||||
</div>
|
||||
)}
|
||||
{this.state.step === Steps.FinishStep && (
|
||||
<div className="flex flex-col">
|
||||
<div className="mb-1 font-bold text-info">Your password has been successfully changed.</div>
|
||||
<p className="sk-p">
|
||||
Please ensure you are running the latest version of Standard Notes on all platforms to ensure maximum
|
||||
compatibility.
|
||||
</p>
|
||||
</div>
|
||||
<PasswordStep
|
||||
onCurrentPasswordChange={this.handleCurrentPasswordInputChange}
|
||||
onNewPasswordChange={this.handleNewPasswordInputChange}
|
||||
onNewPasswordConfirmationChange={this.handleNewPasswordConfirmationInputChange}
|
||||
/>
|
||||
)}
|
||||
|
||||
{this.state.step === Steps.FinishStep && <FinishStep />}
|
||||
</div>
|
||||
</Modal>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
import Spinner from '../Spinner/Spinner'
|
||||
import { useApplication } from '../ApplicationProvider'
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
|
||||
export const PreprocessingStep = ({
|
||||
onContinue,
|
||||
setContinueEnabled,
|
||||
}: {
|
||||
onContinue: () => void
|
||||
setContinueEnabled: (disabled: boolean) => void
|
||||
}) => {
|
||||
const application = useApplication()
|
||||
|
||||
const [isProcessingSync, setIsProcessingSync] = useState<boolean>(true)
|
||||
const [isProcessingMessages, setIsProcessingMessages] = useState<boolean>(true)
|
||||
const [isProcessingInvites, setIsProcessingInvites] = useState<boolean>(true)
|
||||
const [needsUserConfirmation, setNeedsUserConfirmation] = useState<'yes' | 'no'>()
|
||||
|
||||
const continueIfPossible = useCallback(() => {
|
||||
if (isProcessingMessages || isProcessingInvites || isProcessingSync) {
|
||||
setContinueEnabled(false)
|
||||
return
|
||||
}
|
||||
|
||||
if (needsUserConfirmation === 'yes') {
|
||||
setContinueEnabled(true)
|
||||
return
|
||||
}
|
||||
|
||||
onContinue()
|
||||
}, [
|
||||
isProcessingInvites,
|
||||
isProcessingMessages,
|
||||
isProcessingSync,
|
||||
needsUserConfirmation,
|
||||
onContinue,
|
||||
setContinueEnabled,
|
||||
])
|
||||
|
||||
useEffect(() => {
|
||||
continueIfPossible()
|
||||
}, [isProcessingInvites, isProcessingMessages, isProcessingSync, continueIfPossible])
|
||||
|
||||
useEffect(() => {
|
||||
const processPendingSync = async () => {
|
||||
await application.sync.sync()
|
||||
setIsProcessingSync(false)
|
||||
}
|
||||
|
||||
void processPendingSync()
|
||||
}, [application.sync])
|
||||
|
||||
useEffect(() => {
|
||||
const processPendingMessages = async () => {
|
||||
await application.asymmetric.downloadAndProcessInboundMessages()
|
||||
setIsProcessingMessages(false)
|
||||
}
|
||||
|
||||
void processPendingMessages()
|
||||
}, [application.asymmetric])
|
||||
|
||||
useEffect(() => {
|
||||
const processPendingInvites = async () => {
|
||||
await application.sharedVaults.downloadInboundInvites()
|
||||
const hasPendingInvites = application.sharedVaults.getCachedPendingInviteRecords().length > 0
|
||||
setNeedsUserConfirmation(hasPendingInvites ? 'yes' : 'no')
|
||||
setIsProcessingInvites(false)
|
||||
}
|
||||
|
||||
void processPendingInvites()
|
||||
}, [application.sharedVaults])
|
||||
|
||||
const isProcessing = isProcessingSync || isProcessingMessages || isProcessingInvites
|
||||
|
||||
if (isProcessing) {
|
||||
return (
|
||||
<div className="flex flex-row items-center gap-3">
|
||||
<Spinner className="h-3 w-3" />
|
||||
<p className="">Checking for data conflicts...</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
if (needsUserConfirmation === 'no') {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex flex-col">
|
||||
<p>
|
||||
You have pending vault invites. Changing your password will delete these invites. It is recommended you accept
|
||||
or decline these invites before changing your password. If you choose to continue, these invites will be
|
||||
deleted.
|
||||
</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user