chore: ui for designating survivor for shared vault & warning when no survivor (#2544)

This commit is contained in:
Aman Harwara
2023-09-29 16:38:38 +05:30
committed by GitHub
parent 7c8816e229
commit 79c5da6c5b
8 changed files with 162 additions and 15 deletions

View File

@@ -0,0 +1,96 @@
import { useApplication } from '@/Components/ApplicationProvider'
import Modal, { ModalAction } from '@/Components/Modal/Modal'
import Spinner from '@/Components/Spinner/Spinner'
import { SharedVaultUserServerHash, VaultListingInterface } from '@standardnotes/snjs'
import { useCallback, useMemo, useState } from 'react'
const DesignateSurvivorModal = ({
vault,
members,
closeModal,
}: {
vault: VaultListingInterface
members: SharedVaultUserServerHash[]
closeModal: () => void
}) => {
const application = useApplication()
const [selectedSurvivor, setSelectedSurvivor] = useState<SharedVaultUserServerHash | null>(null)
const [isDesignating, setIsDesignating] = useState(false)
const designateSelectedSurvivor = useCallback(async () => {
if (!selectedSurvivor) {
return
}
if (!vault.isSharedVaultListing()) {
return
}
try {
setIsDesignating(true)
const result = await application.vaultUsers.designateSurvivor(vault, selectedSurvivor.user_uuid)
if (result.isFailed()) {
throw new Error(result.getError())
}
await application.sync.sync()
closeModal()
} catch (error) {
console.error(error)
} finally {
setIsDesignating(false)
}
}, [application.sync, application.vaultUsers, closeModal, selectedSurvivor, vault])
const modalActions = useMemo(
(): ModalAction[] => [
{
label: isDesignating ? <Spinner className="h-5 w-5 border-info-contrast" /> : 'Designate survivor',
onClick: designateSelectedSurvivor,
type: 'primary',
mobileSlot: 'right',
disabled: !selectedSurvivor || isDesignating,
hidden: members.length === 0,
},
{
label: 'Cancel',
onClick: closeModal,
type: 'cancel',
mobileSlot: 'left',
},
],
[closeModal, designateSelectedSurvivor, isDesignating, members.length, selectedSurvivor],
)
return (
<Modal title="Designate survivor" close={closeModal} actions={modalActions} className="px-4.5 py-4">
<div className="flex flex-col gap-3">
{members.map((member) => {
const isSelected = selectedSurvivor?.uuid === member.uuid
const contact = application.contacts.findContactForServerUser(member)
if (!contact) {
return null
}
const isOwner = application.vaultUsers.isVaultUserOwner(member)
if (isOwner) {
return null
}
return (
<label className="grid grid-cols-[auto_1fr] gap-x-3 gap-y-0.5" key={member.uuid}>
<input
className="h-4 w-4 self-center accent-info"
type="radio"
name="survivor"
checked={isSelected}
onClick={() => setSelectedSurvivor(member)}
/>
<div className="col-start-2 text-sm font-semibold">{contact.name}</div>
<div className="col-start-2 opacity-90">{contact.contactUuid}</div>
</label>
)
})}
</div>
</Modal>
)
}
export default DesignateSurvivorModal

View File

@@ -1,8 +1,10 @@
import { useCallback } from 'react'
import { useCallback, useState } from 'react'
import { useApplication } from '@/Components/ApplicationProvider'
import { SharedVaultUserServerHash, VaultListingInterface } from '@standardnotes/snjs'
import Icon from '@/Components/Icon/Icon'
import Button from '@/Components/Button/Button'
import ModalOverlay from '@/Components/Modal/ModalOverlay'
import DesignateSurvivorModal from './DesignateSurvivorModal'
export const VaultModalMembers = ({
members,
@@ -27,9 +29,30 @@ export const VaultModalMembers = ({
[application.vaultUsers, vault, onChange],
)
const vaultHasNoDesignatedSurvivor = vault.isSharedVaultListing() && !vault.sharing.designatedSurvivor
const [isDesignateSurvivorModalOpen, setIsDesignateSurvivorModalOpen] = useState(false)
const openDesignateSurvivorModal = () => setIsDesignateSurvivorModalOpen(true)
const closeDesignateSurvivorModal = () => setIsDesignateSurvivorModalOpen(false)
return (
<div>
<div className="mb-3 text-lg">Vault Members</div>
{vaultHasNoDesignatedSurvivor && members.length > 1 && isCurrentUserAdmin && (
<div className="bg-danger-faded mb-3 grid grid-cols-[auto,1fr] gap-x-[0.65rem] gap-y-0.5 overflow-hidden rounded p-2.5 text-danger">
<Icon type="warning" className="place-self-center" />
<div className="text-base font-semibold">No designated survivor</div>
<div className="col-start-2">
Vaults that have no designated survivor will be deleted when the owner account is deleted. In order to
ensure that no data is lost, please designate a survivor who will be transferred ownership of the vault.
</div>
<Button small className="col-start-2 mt-1.5" onClick={openDesignateSurvivorModal}>
Designate survivor
</Button>
<ModalOverlay isOpen={isDesignateSurvivorModalOpen} close={closeDesignateSurvivorModal}>
<DesignateSurvivorModal vault={vault} members={members} closeModal={closeDesignateSurvivorModal} />
</ModalOverlay>
</div>
)}
<div className="space-y-3.5">
{members.map((member) => {
const isMemberVaultOwner = application.vaultUsers.isVaultUserOwner(member)
@@ -55,6 +78,12 @@ export const VaultModalMembers = ({
Untrusted
</div>
)}
{member.is_designated_survivor && (
<div className="flex items-center gap-1 rounded bg-info px-1 py-0.5 text-xs text-success-contrast">
<Icon type="security" size="small" />
Designated survivor
</div>
)}
</div>
<div className="col-start-2 row-start-2">{permission}</div>
{isCurrentUserAdmin && !isMemberVaultOwner && (