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