chore: ui for designating survivor for shared vault & warning when no survivor (#2544)
This commit is contained in:
@@ -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
|
||||
@@ -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 && (
|
||||
|
||||
Reference in New Issue
Block a user