152 lines
5.2 KiB
TypeScript
152 lines
5.2 KiB
TypeScript
import { useModalSearchParams } from '@/hooks/useModalSearchParams'
|
|
import { Modal } from '@/shared/Modal/Modal'
|
|
import { MODAL_PARAMS_KEYS } from '@/types/modal'
|
|
import { Box, Stack, Typography } from '@mui/material'
|
|
import { StyledButton, StyledSettingContainer, StyledSynchedText } from './styled'
|
|
import { SectionTitle } from '@/shared/SectionTitle/SectionTitle'
|
|
import { CheckmarkIcon } from '@/assets'
|
|
import { Input } from '@/shared/Input/Input'
|
|
import { ChangeEvent, FC, useEffect, useState } from 'react'
|
|
import { Checkbox } from '@/shared/Checkbox/Checkbox'
|
|
import { useEnqueueSnackbar } from '@/hooks/useEnqueueSnackbar'
|
|
import { swicCall } from '@/modules/swic'
|
|
import { useParams } from 'react-router-dom'
|
|
import { dbi } from '@/modules/db'
|
|
import { usePassword } from '@/hooks/usePassword'
|
|
import { useAppSelector } from '@/store/hooks/redux'
|
|
import { selectKeys } from '@/store'
|
|
import { isValidPassphase, isWeakPassphase } from '@/modules/keys'
|
|
import { LoadingSpinner } from '@/shared/LoadingSpinner/LoadingSpinner'
|
|
|
|
type ModalSettingsProps = {
|
|
isSynced: boolean
|
|
}
|
|
|
|
export const ModalSettings: FC<ModalSettingsProps> = ({ isSynced }) => {
|
|
const { getModalOpened, createHandleCloseReplace } = useModalSearchParams()
|
|
const { npub = '' } = useParams<{ npub: string }>()
|
|
const keys = useAppSelector(selectKeys)
|
|
|
|
const notify = useEnqueueSnackbar()
|
|
|
|
const isModalOpened = getModalOpened(MODAL_PARAMS_KEYS.SETTINGS)
|
|
const handleCloseModal = createHandleCloseReplace(MODAL_PARAMS_KEYS.SETTINGS)
|
|
|
|
const { hidePassword, inputProps } = usePassword()
|
|
|
|
const [enteredPassword, setEnteredPassword] = useState('')
|
|
const [isPasswordInvalid, setIsPasswordInvalid] = useState(false)
|
|
|
|
const [isChecked, setIsChecked] = useState(false)
|
|
const [isLoading, setIsLoading] = useState(false)
|
|
|
|
useEffect(() => setIsChecked(isSynced), [isModalOpened, isSynced])
|
|
|
|
useEffect(() => {
|
|
return () => {
|
|
if (isModalOpened) {
|
|
// modal closed
|
|
hidePassword()
|
|
}
|
|
}
|
|
}, [hidePassword, isModalOpened])
|
|
|
|
const isNpubExists = npub.trim().length && keys.some((key) => key.npub === npub)
|
|
|
|
if (isModalOpened && !isNpubExists) {
|
|
handleCloseModal()
|
|
return null
|
|
}
|
|
|
|
const handlePasswordChange = (e: ChangeEvent<HTMLInputElement>) => {
|
|
const password = e.target.value
|
|
setIsPasswordInvalid(!!password && !isValidPassphase(password))
|
|
setEnteredPassword(password)
|
|
}
|
|
|
|
const onClose = () => {
|
|
handleCloseModal()
|
|
setEnteredPassword('')
|
|
setIsPasswordInvalid(false)
|
|
}
|
|
|
|
const handleChangeCheckbox = (e: unknown, checked: boolean) => {
|
|
setIsChecked(checked)
|
|
}
|
|
|
|
const handleSubmit = async (e: React.FormEvent) => {
|
|
e.preventDefault()
|
|
setIsPasswordInvalid(false)
|
|
|
|
if (!isValidPassphase(enteredPassword)) {
|
|
return setIsPasswordInvalid(true)
|
|
}
|
|
try {
|
|
setIsLoading(true)
|
|
await swicCall('saveKey', npub, enteredPassword)
|
|
notify('Key saved', 'success')
|
|
dbi.addSynced(npub) // Sync npub
|
|
setEnteredPassword('')
|
|
setIsPasswordInvalid(false)
|
|
setIsLoading(false)
|
|
} catch (error) {
|
|
setIsPasswordInvalid(false)
|
|
setIsLoading(false)
|
|
}
|
|
}
|
|
|
|
return (
|
|
<Modal open={isModalOpened} onClose={onClose} title="Settings">
|
|
<Stack gap={'1rem'}>
|
|
<StyledSettingContainer onSubmit={handleSubmit}>
|
|
<Stack direction={'row'} justifyContent={'space-between'}>
|
|
<SectionTitle>Cloud sync</SectionTitle>
|
|
{isSynced && (
|
|
<StyledSynchedText>
|
|
<CheckmarkIcon /> Synched
|
|
</StyledSynchedText>
|
|
)}
|
|
</Stack>
|
|
<Box>
|
|
<Checkbox onChange={handleChangeCheckbox} checked={isChecked} />
|
|
<Typography variant="caption">Use this key on multiple devices</Typography>
|
|
</Box>
|
|
<Input
|
|
fullWidth
|
|
{...inputProps}
|
|
onChange={handlePasswordChange}
|
|
value={enteredPassword}
|
|
placeholder="Enter a password"
|
|
disabled={!isChecked}
|
|
/>
|
|
{isPasswordInvalid ? (
|
|
<Typography variant="body2" color={'red'}>
|
|
Password must include 6+ English letters, numbers or punctuation marks.
|
|
</Typography>
|
|
) : !!enteredPassword && isWeakPassphase(enteredPassword) ? (
|
|
<Typography variant="body2" color={'orange'}>
|
|
Weak password
|
|
</Typography>
|
|
) : !!enteredPassword && !isPasswordInvalid ? (
|
|
<Typography variant="body2" color={'green'}>
|
|
Good password
|
|
</Typography>
|
|
) : isSynced ? (
|
|
<Typography variant="body2" color={'GrayText'}>
|
|
To change your password, type a new one and sync.
|
|
</Typography>
|
|
) : (
|
|
<Typography variant="body2" color={'GrayText'}>
|
|
This key will be encrypted and stored on our server. You can use the password to download this key onto
|
|
another device.
|
|
</Typography>
|
|
)}
|
|
<StyledButton type="submit" fullWidth disabled={!isChecked}>
|
|
Sync {isLoading && <LoadingSpinner mode="secondary" />}
|
|
</StyledButton>
|
|
</StyledSettingContainer>
|
|
</Stack>
|
|
</Modal>
|
|
)
|
|
}
|