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

View File

@@ -37,34 +37,36 @@ const InviteItem = ({ inviteRecord }: Props) => {
<EditContactModal fromInvite={inviteRecord} onCloseDialog={closeAddContactModal} /> <EditContactModal fromInvite={inviteRecord} onCloseDialog={closeAddContactModal} />
</ModalOverlay> </ModalOverlay>
<div className="bg-gray-100 flex flex-row gap-3.5 rounded-lg px-3.5 py-2.5 shadow-md"> <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-2.5 h-5.5 w-5.5 flex-shrink-0" /> <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"> <div className="flex flex-col gap-2 py-1.5 overflow-hidden">
<span className="mr-auto overflow-hidden text-ellipsis text-sm">Vault Name: {inviteData.metadata.name}</span> <div className="mr-auto overflow-hidden text-ellipsis text-sm">Vault Name: {inviteData.metadata.name}</div>
<span className="mr-auto overflow-hidden text-ellipsis text-sm"> {inviteData.metadata.description && (
Vault Description: {inviteData.metadata.description} <div className="mr-auto overflow-hidden text-ellipsis text-sm">
</span> Vault Description: {inviteData.metadata.description}
</div>
)}
{trustedContact ? ( {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> <span className="overflow-hidden text-ellipsis text-sm">Trusted Sender: {trustedContact.name}</span>
<CheckmarkCircle /> <CheckmarkCircle className="!w-4 !h-4" />
</div> </div>
) : ( ) : (
<span className="mr-auto overflow-hidden text-ellipsis text-sm"> <div className="mr-auto text-sm w-full whitespace-pre-wrap break-words overflow-hidden">
Sender CollaborationID: {collaborationId} Sender CollaborationID: <span className="text-xs font-mono">{collaborationId}</span>
</span> </div>
)} )}
<div className="mt-2.5 flex flex-row"> <div className="mt-1.5">
{isTrusted ? ( {isTrusted ? (
<Button label="Accept Invite" className={'mr-3 text-xs'} onClick={acceptInvite} /> <Button label="Accept Invite" className="text-xs" onClick={acceptInvite} />
) : ( ) : (
<div> <div>
<div> <div>
The sender of this invite is not trusted. To accept this invite, first add the sender as a trusted The sender of this invite is not trusted. To accept this invite, first add the sender as a trusted
contact. contact.
</div> </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>
)} )}
</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> <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="mt-2 flex w-full">
<div className="flex flex-row"> <Button label="Edit" className="mr-3 text-xs" onClick={openEditModal} />
<Button label="Edit" className="mr-3 text-xs" onClick={openEditModal} /> {isAdmin && <Button colorStyle="danger" label="Delete" className="mr-3 text-xs" onClick={deleteVault} />}
{isAdmin && <Button colorStyle="danger" label="Delete" className="mr-3 text-xs" onClick={deleteVault} />} {!isAdmin && vault.isSharedVaultListing() && (
{!isAdmin && vault.isSharedVaultListing() && ( <Button label="Leave Vault" className="mr-3 text-xs" onClick={leaveVault} />
<Button label="Leave Vault" className="mr-3 text-xs" onClick={leaveVault} /> )}
)} {vault.isSharedVaultListing() ? (
</div> <Button colorStyle="info" label="Invite Contacts" className="mr-3 text-xs" onClick={openInviteModal} />
<div className="flex flex-row"> ) : (
{vault.isSharedVaultListing() ? ( <Button
<Button label="Invite Contacts" className="mr-3 text-xs" onClick={openInviteModal} /> colorStyle="info"
) : ( label="Enable Collaboration"
<Button className="mr-3 text-xs"
colorStyle="info" onClick={convertToSharedVault}
label="Enable Collaboration" />
className="mr-3 text-xs" )}
onClick={convertToSharedVault}
/>
)}
</div>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -287,7 +287,9 @@ const EditVaultModal: FunctionComponent<Props> = ({ onCloseDialog, existingVault
<VaultModalMembers vault={existingVault} members={members} onChange={reloadVaultInfo} isAdmin={isAdmin} /> <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 <PasswordTypePreference
value={passwordType} value={passwordType}

View File

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

View File

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