chore: invites ui improvements
This commit is contained in:
@@ -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>
|
||||
)
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user