chore: invites ui improvements

This commit is contained in:
Aman Harwara
2023-08-08 22:15:58 +05:30
parent c167f2b29b
commit 02dda6d0fe
6 changed files with 93 additions and 81 deletions

View File

@@ -1,7 +1,13 @@
import { FunctionComponent, useCallback, useEffect, useMemo, useState } from 'react'
import Modal, { ModalAction } from '@/Components/Modal/Modal'
import { useApplication } from '@/Components/ApplicationProvider'
import { SharedVaultListingInterface, TrustedContactInterface, SharedVaultUserPermission } from '@standardnotes/snjs'
import {
SharedVaultListingInterface,
TrustedContactInterface,
SharedVaultUserPermission,
classNames,
} from '@standardnotes/snjs'
import Spinner from '@/Components/Spinner/Spinner'
type Props = {
vault: SharedVaultListingInterface
@@ -12,15 +18,18 @@ const ContactInviteModal: FunctionComponent<Props> = ({ vault, onCloseDialog })
const application = useApplication()
const [selectedContacts, setSelectedContacts] = useState<TrustedContactInterface[]>([])
const [isLoadingContacts, setIsLoadingContacts] = useState(false)
const [contacts, setContacts] = useState<TrustedContactInterface[]>([])
useEffect(() => {
const loadContacts = async () => {
setIsLoadingContacts(true)
const contacts = await application.vaultInvites.getInvitableContactsForSharedVault(vault)
setContacts(contacts)
setIsLoadingContacts(false)
}
void loadContacts()
}, [application.vaultInvites, vault])
}, [application.vaultInvites, contacts.length, vault])
const handleDialogClose = useCallback(() => {
onCloseDialog()
@@ -71,28 +80,25 @@ const ContactInviteModal: FunctionComponent<Props> = ({ vault, onCloseDialog })
return (
<Modal title="Add New Contact" close={handleDialogClose} actions={modalActions}>
<div className="px-4.5 py-4">
<div className="flex w-full flex-col">
<div className="mb-3">
{contacts.map((contact) => {
return (
<div key={contact.uuid} onClick={() => toggleContact(contact)}>
<div>
<input
type="checkbox"
checked={selectedContacts.includes(contact)}
onChange={() => toggleContact(contact)}
/>
</div>
<div>
{contact.name}
{contact.contactUuid}
</div>
</div>
)
})}
</div>
</div>
<div className={classNames('px-4.5 py-4 flex w-full flex-col gap-3', isLoadingContacts && 'items-center')}>
{isLoadingContacts ? (
<Spinner className="w-5 h-5" />
) : (
contacts.map((contact) => {
return (
<label className="grid grid-cols-[auto_1fr] gap-x-3 gap-y-0.5" key={contact.uuid}>
<input
className="accent-info w-4 h-4 self-center"
type="checkbox"
checked={selectedContacts.includes(contact)}
onChange={() => toggleContact(contact)}
/>
<div className="col-start-2 font-semibold text-sm">{contact.name}</div>
<div className="col-start-2">{contact.contactUuid}</div>
</label>
)
})
)}
</div>
</Modal>
)

View File

@@ -37,34 +37,36 @@ const InviteItem = ({ inviteRecord }: Props) => {
<EditContactModal fromInvite={inviteRecord} onCloseDialog={closeAddContactModal} />
</ModalOverlay>
<div className="bg-gray-100 flex flex-row gap-3.5 rounded-lg px-3.5 py-2.5 shadow-md">
<Icon type={'archive'} size="custom" className="mt-2.5 h-5.5 w-5.5 flex-shrink-0" />
<div className="flex flex-col gap-2 py-1.5">
<span className="mr-auto overflow-hidden text-ellipsis text-sm">Vault Name: {inviteData.metadata.name}</span>
<span className="mr-auto overflow-hidden text-ellipsis text-sm">
Vault Description: {inviteData.metadata.description}
</span>
<div className="flex gap-3.5 rounded-lg px-3.5 py-2.5 border border-border shadow">
<Icon type="archive" size="custom" className="mt-1.5 h-5.5 w-5.5 flex-shrink-0" />
<div className="flex flex-col gap-2 py-1.5 overflow-hidden">
<div className="mr-auto overflow-hidden text-ellipsis text-sm">Vault Name: {inviteData.metadata.name}</div>
{inviteData.metadata.description && (
<div className="mr-auto overflow-hidden text-ellipsis text-sm">
Vault Description: {inviteData.metadata.description}
</div>
)}
{trustedContact ? (
<div className="flex flex-row gap-2">
<div className="flex items-center gap-1">
<span className="overflow-hidden text-ellipsis text-sm">Trusted Sender: {trustedContact.name}</span>
<CheckmarkCircle />
<CheckmarkCircle className="!w-4 !h-4" />
</div>
) : (
<span className="mr-auto overflow-hidden text-ellipsis text-sm">
Sender CollaborationID: {collaborationId}
</span>
<div className="mr-auto text-sm w-full whitespace-pre-wrap break-words overflow-hidden">
Sender CollaborationID: <span className="text-xs font-mono">{collaborationId}</span>
</div>
)}
<div className="mt-2.5 flex flex-row">
<div className="mt-1.5">
{isTrusted ? (
<Button label="Accept Invite" className={'mr-3 text-xs'} onClick={acceptInvite} />
<Button label="Accept Invite" className="text-xs" onClick={acceptInvite} />
) : (
<div>
<div>
The sender of this invite is not trusted. To accept this invite, first add the sender as a trusted
contact.
</div>
<Button label="Add Trusted Contact" className={'mr-3 text-xs'} onClick={addAsTrustedContact} />
<Button label="Add Trusted Contact" className="mr-3 mt-2 text-xs" onClick={addAsTrustedContact} />
</div>
)}
</div>

View File

@@ -116,26 +116,22 @@ const VaultItem = ({ vault }: Props) => {
)}
<span className="mr-auto overflow-hidden text-ellipsis text-sm">Vault ID: {vault.systemIdentifier}</span>
<div className="mt-2 flex w-full flex-row justify-between">
<div className="flex flex-row">
<Button label="Edit" className="mr-3 text-xs" onClick={openEditModal} />
{isAdmin && <Button colorStyle="danger" label="Delete" className="mr-3 text-xs" onClick={deleteVault} />}
{!isAdmin && vault.isSharedVaultListing() && (
<Button label="Leave Vault" className="mr-3 text-xs" onClick={leaveVault} />
)}
</div>
<div className="flex flex-row">
{vault.isSharedVaultListing() ? (
<Button label="Invite Contacts" className="mr-3 text-xs" onClick={openInviteModal} />
) : (
<Button
colorStyle="info"
label="Enable Collaboration"
className="mr-3 text-xs"
onClick={convertToSharedVault}
/>
)}
</div>
<div className="mt-2 flex w-full">
<Button label="Edit" className="mr-3 text-xs" onClick={openEditModal} />
{isAdmin && <Button colorStyle="danger" label="Delete" className="mr-3 text-xs" onClick={deleteVault} />}
{!isAdmin && vault.isSharedVaultListing() && (
<Button label="Leave Vault" className="mr-3 text-xs" onClick={leaveVault} />
)}
{vault.isSharedVaultListing() ? (
<Button colorStyle="info" label="Invite Contacts" className="mr-3 text-xs" onClick={openInviteModal} />
) : (
<Button
colorStyle="info"
label="Enable Collaboration"
className="mr-3 text-xs"
onClick={convertToSharedVault}
/>
)}
</div>
</div>
</div>

View File

@@ -287,7 +287,9 @@ const EditVaultModal: FunctionComponent<Props> = ({ onCloseDialog, existingVault
<VaultModalMembers vault={existingVault} members={members} onChange={reloadVaultInfo} isAdmin={isAdmin} />
)}
{existingVault && <VaultModalInvites invites={invites} onChange={reloadVaultInfo} isAdmin={isAdmin} />}
{existingVault && invites.length > 0 && (
<VaultModalInvites invites={invites} onChange={reloadVaultInfo} isAdmin={isAdmin} />
)}
<PasswordTypePreference
value={passwordType}

View File

@@ -29,23 +29,26 @@ export const VaultModalInvites = ({
{invites.map((invite) => {
const contact = application.contacts.findContactForInvite(invite)
return (
<div key={invite.uuid} className="bg-gray-100 flex flex-row gap-3.5 rounded-lg px-3.5 py-2.5 shadow-md">
<Icon type={'user'} size="custom" className="mt-2.5 h-5.5 w-5.5 flex-shrink-0" />
<div key={invite.uuid} className="flex gap-3.5 rounded-lg px-3.5 py-2.5 border border-border shadow">
<Icon type="user" size="custom" className="mt-2 h-5.5 w-5.5 flex-shrink-0" />
<div className="flex flex-col gap-2 py-1.5">
<span className="mr-auto overflow-hidden text-ellipsis text-base font-bold">
{contact?.name || invite.user_uuid}
</span>
{contact && <span className="text-info">Trusted</span>}
{!contact && (
<div>
<span className="text-base">Untrusted</span>
</div>
)}
<div className="flex items-center gap-2 overflow-hidden text-ellipsis text-base font-bold">
<span>{contact?.name || invite.user_uuid}</span>
{contact ? (
<div className="flex items-center bg-success text-success-contrast rounded gap-1 text-xs px-1 py-0.5">
<Icon type="check-circle" size="small" />
Trusted
</div>
) : (
<div className="flex items-center bg-danger text-danger-contrast rounded gap-1 text-xs px-1 pr-1.5 py-0.5">
<Icon type="clear-circle-filled" size="small" />
Untrusted
</div>
)}
</div>
{isAdmin && (
<div className="mt-2.5 flex flex-row">
<Button label="Cancel Invite" className={'mr-3 text-xs'} onClick={() => deleteInvite(invite)} />
</div>
<Button label="Cancel Invite" className="mt-1 mr-3 text-xs" onClick={() => deleteInvite(invite)} />
)}
</div>
</div>

View File

@@ -1,13 +1,16 @@
import Icon from '@/Components/Icon/Icon'
import { classNames } from '@standardnotes/snjs'
export const CheckmarkCircle = () => {
export const CheckmarkCircle = ({ className }: { className?: string }) => {
return (
<button
className={
'peer flex h-5 w-5 flex-shrink-0 items-center justify-center rounded-full bg-success text-success-contrast'
}
<div
role="presentation"
className={classNames(
'peer flex h-5 w-5 flex-shrink-0 items-center justify-center rounded-full bg-success text-success-contrast',
className,
)}
>
<Icon type={'check'} size="small" />
</button>
<Icon type="check" size="small" />
</div>
)
}