From 79c5da6c5b38edbb9fb32a0c70d90dbd1bb6dff3 Mon Sep 17 00:00:00 2001 From: Aman Harwara Date: Fri, 29 Sep 2023 16:38:38 +0530 Subject: [PATCH] chore: ui for designating survivor for shared vault & warning when no survivor (#2544) --- .../SharedVaults/SharedVaultUserServerHash.ts | 1 + .../SharedVaults/SharedVaultService.spec.ts | 1 + .../Domain/SharedVaults/SharedVaultService.ts | 12 ++- .../Application/Dependencies/Dependencies.ts | 1 + packages/snjs/mocha/lib/VaultsContext.js | 4 +- .../VaultModal/DesignateSurvivorModal.tsx | 96 +++++++++++++++++++ .../Vaults/VaultModal/VaultModalMembers.tsx | 31 +++++- packages/web/src/stylesheets/_sn.scss | 31 ++++-- 8 files changed, 162 insertions(+), 15 deletions(-) create mode 100644 packages/web/src/javascripts/Components/Preferences/Panes/Vaults/Vaults/VaultModal/DesignateSurvivorModal.tsx diff --git a/packages/responses/src/Domain/SharedVaults/SharedVaultUserServerHash.ts b/packages/responses/src/Domain/SharedVaults/SharedVaultUserServerHash.ts index 2bbface37..4d486b388 100644 --- a/packages/responses/src/Domain/SharedVaults/SharedVaultUserServerHash.ts +++ b/packages/responses/src/Domain/SharedVaults/SharedVaultUserServerHash.ts @@ -4,4 +4,5 @@ export interface SharedVaultUserServerHash { user_uuid: string permission: string updated_at_timestamp: number + is_designated_survivor: boolean } diff --git a/packages/services/src/Domain/SharedVaults/SharedVaultService.spec.ts b/packages/services/src/Domain/SharedVaults/SharedVaultService.spec.ts index 9ef46b5dc..888f8735f 100644 --- a/packages/services/src/Domain/SharedVaults/SharedVaultService.spec.ts +++ b/packages/services/src/Domain/SharedVaults/SharedVaultService.spec.ts @@ -49,6 +49,7 @@ describe('SharedVaultService', () => { eventBus.addEventHandler = jest.fn() service = new SharedVaultService( + sync, items, session, vaultUsers, diff --git a/packages/services/src/Domain/SharedVaults/SharedVaultService.ts b/packages/services/src/Domain/SharedVaults/SharedVaultService.ts index 21d084727..4679443f7 100644 --- a/packages/services/src/Domain/SharedVaults/SharedVaultService.ts +++ b/packages/services/src/Domain/SharedVaults/SharedVaultService.ts @@ -34,12 +34,14 @@ import { FindContact } from '../Contacts/UseCase/FindContact' import { GetOwnedSharedVaults } from './UseCase/GetOwnedSharedVaults' import { SyncLocalVaultsWithRemoteSharedVaults } from './UseCase/SyncLocalVaultsWithRemoteSharedVaults' import { VaultUserServiceInterface } from '../VaultUser/VaultUserServiceInterface' +import { SyncServiceInterface } from '../Sync/SyncServiceInterface' export class SharedVaultService extends AbstractService implements SharedVaultServiceInterface, InternalEventHandlerInterface { constructor( + private sync: SyncServiceInterface, private items: ItemManagerInterface, private session: SessionsClientInterface, private vaultUsers: VaultUserServiceInterface, @@ -123,9 +125,13 @@ export class SharedVaultService .invalidateVaultUsersCache(event.eventPayload.props.primaryIdentifier.value) .catch(console.error) - await this._syncLocalVaultsWithRemoteSharedVaults.execute([vault]) - - void this.notifyEvent(SharedVaultServiceEvent.SharedVaultStatusChanged) + this.sync + .sync() + .then(async () => { + await this._syncLocalVaultsWithRemoteSharedVaults.execute([vault]) + void this.notifyEvent(SharedVaultServiceEvent.SharedVaultStatusChanged) + }) + .catch(console.error) } break } diff --git a/packages/snjs/lib/Application/Dependencies/Dependencies.ts b/packages/snjs/lib/Application/Dependencies/Dependencies.ts index 4a1536de2..2c0b9410c 100644 --- a/packages/snjs/lib/Application/Dependencies/Dependencies.ts +++ b/packages/snjs/lib/Application/Dependencies/Dependencies.ts @@ -921,6 +921,7 @@ export class Dependencies { this.factory.set(TYPES.SharedVaultService, () => { return new SharedVaultService( + this.get(TYPES.SyncService), this.get(TYPES.ItemManager), this.get(TYPES.SessionManager), this.get(TYPES.VaultUserService), diff --git a/packages/snjs/mocha/lib/VaultsContext.js b/packages/snjs/mocha/lib/VaultsContext.js index 9692bb2e6..400b30d9a 100644 --- a/packages/snjs/mocha/lib/VaultsContext.js +++ b/packages/snjs/mocha/lib/VaultsContext.js @@ -44,9 +44,11 @@ export class VaultsContext extends AppContext { await this.awaitPromiseOrDoNothing( promise, 1, - 'Waiting for notifications timed out. Notifications might have been processed in previous sync.' + 'Waiting for notifications timed out. Notifications might have been processed in previous sync.', ) + await this.sync() + if (this.notifications['handleReceivedNotifications'].restore) { this.notifications['handleReceivedNotifications'].restore() } diff --git a/packages/web/src/javascripts/Components/Preferences/Panes/Vaults/Vaults/VaultModal/DesignateSurvivorModal.tsx b/packages/web/src/javascripts/Components/Preferences/Panes/Vaults/Vaults/VaultModal/DesignateSurvivorModal.tsx new file mode 100644 index 000000000..525e19e68 --- /dev/null +++ b/packages/web/src/javascripts/Components/Preferences/Panes/Vaults/Vaults/VaultModal/DesignateSurvivorModal.tsx @@ -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(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 ? : '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 ( + +
+ {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 ( + + ) + })} +
+
+ ) +} + +export default DesignateSurvivorModal diff --git a/packages/web/src/javascripts/Components/Preferences/Panes/Vaults/Vaults/VaultModal/VaultModalMembers.tsx b/packages/web/src/javascripts/Components/Preferences/Panes/Vaults/Vaults/VaultModal/VaultModalMembers.tsx index c84fca359..c9004e363 100644 --- a/packages/web/src/javascripts/Components/Preferences/Panes/Vaults/Vaults/VaultModal/VaultModalMembers.tsx +++ b/packages/web/src/javascripts/Components/Preferences/Panes/Vaults/Vaults/VaultModal/VaultModalMembers.tsx @@ -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 (
Vault Members
+ {vaultHasNoDesignatedSurvivor && members.length > 1 && isCurrentUserAdmin && ( +
+ +
No designated survivor
+
+ 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. +
+ + + + +
+ )}
{members.map((member) => { const isMemberVaultOwner = application.vaultUsers.isVaultUserOwner(member) @@ -55,6 +78,12 @@ export const VaultModalMembers = ({ Untrusted
)} + {member.is_designated_survivor && ( +
+ + Designated survivor +
+ )}
{permission}
{isCurrentUserAdmin && !isMemberVaultOwner && ( diff --git a/packages/web/src/stylesheets/_sn.scss b/packages/web/src/stylesheets/_sn.scss index 22816e9c6..d9c128786 100644 --- a/packages/web/src/stylesheets/_sn.scss +++ b/packages/web/src/stylesheets/_sn.scss @@ -116,21 +116,32 @@ } @mixin DimmedBackground($color, $opacity) { - content: ''; - width: 100%; - height: 100%; - position: absolute; - left: 0; - top: 0; - background-color: $color; - opacity: $opacity; + position: relative; + z-index: 1; + + &::after { + content: ''; + width: 100%; + height: 100%; + position: absolute; + left: 0; + top: 0; + background-color: $color; + opacity: $opacity; + z-index: -1; + pointer-events: none; + } } -.bg-warning-faded::after { +.bg-warning-faded { @include DimmedBackground(var(--sn-stylekit-warning-color), 0.08); } -.bg-info-faded::after { +.bg-danger-faded { + @include DimmedBackground(var(--sn-stylekit-danger-color), 0.08); +} + +.bg-info-faded { @include DimmedBackground(var(--sn-stylekit-info-color), 0.08); }