Compare commits

..
14 changed files with 193 additions and 156 deletions
@@ -9,7 +9,6 @@ import { useToast } from '@documenso/ui/primitives/use-toast';
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { DocumentStatus } from '@prisma/client';
import { CopyIcon } from 'lucide-react';
import { StackAvatar } from './stack-avatar';
@@ -41,8 +40,8 @@ export function AvatarWithRecipient({ recipient, documentStatus }: AvatarWithRec
return (
<div
className={cn('-mx-2 flex items-center gap-2.5 rounded-md px-2 py-1.5', {
'group cursor-pointer hover:bg-muted': signingToken,
className={cn('my-1 flex items-center gap-2', {
'cursor-pointer hover:underline': signingToken,
})}
role={signingToken ? 'button' : undefined}
title={signingToken ? _(msg`Click to copy signing link for sending to recipient`) : undefined}
@@ -53,19 +52,15 @@ export function AvatarWithRecipient({ recipient, documentStatus }: AvatarWithRec
key={recipient.id}
type={getRecipientType(recipient)}
fallbackText={recipientAbbreviation(recipient)}
className="h-8 w-8 shrink-0 border-0 text-xs"
/>
<div className="min-w-0 flex-1">
<p className="truncate font-medium text-foreground text-sm">{recipient.email || recipient.name}</p>
<p className="truncate text-muted-foreground text-xs">
{_(RECIPIENT_ROLES_DESCRIPTION[recipient.role].roleName)}
</p>
<div
className="text-muted-foreground text-sm"
title={signingToken ? _(msg`Click to copy signing link for sending to recipient`) : undefined}
>
<p>{recipient.email || recipient.name}</p>
<p className="text-muted-foreground/70 text-xs">{_(RECIPIENT_ROLES_DESCRIPTION[recipient.role].roleName)}</p>
</div>
{signingToken && (
<CopyIcon className="h-3.5 w-3.5 shrink-0 text-muted-foreground opacity-0 group-hover:opacity-100" />
)}
</div>
);
}
@@ -1,5 +1,4 @@
import { RecipientStatusType } from '@documenso/lib/client-only/recipient-type';
import { cn } from '@documenso/ui/lib/utils';
import { Avatar, AvatarFallback } from '@documenso/ui/primitives/avatar';
const ZIndexes: { [key: string]: string } = {
@@ -15,10 +14,9 @@ export type StackAvatarProps = {
zIndex?: string;
fallbackText?: string;
type: RecipientStatusType;
className?: string;
};
export const StackAvatar = ({ first, zIndex, fallbackText = '', type, className }: StackAvatarProps) => {
export const StackAvatar = ({ first, zIndex, fallbackText = '', type }: StackAvatarProps) => {
let classes = '';
let zIndexClass = '';
const firstClass = first ? '' : '-ml-3';
@@ -48,14 +46,7 @@ export const StackAvatar = ({ first, zIndex, fallbackText = '', type, className
}
return (
<Avatar
className={cn(
zIndexClass,
firstClass,
'h-10 w-10 border-2 border-white border-solid dark:border-border',
className,
)}
>
<Avatar className={` ${zIndexClass} ${firstClass} h-10 w-10 border-2 border-white border-solid dark:border-border`}>
<AvatarFallback className={classes}>{fallbackText}</AvatarFallback>
</Avatar>
);
@@ -2,7 +2,6 @@ import { getRecipientType, RecipientStatusType } from '@documenso/lib/client-onl
import { RECIPIENT_ROLES_DESCRIPTION } from '@documenso/lib/constants/recipient-roles';
import type { TRecipientLite } from '@documenso/lib/types/recipient';
import { recipientAbbreviation } from '@documenso/lib/utils/recipient-formatter';
import { cn } from '@documenso/ui/lib/utils';
import { PopoverHover } from '@documenso/ui/primitives/popover';
import { useLingui } from '@lingui/react';
import { Trans } from '@lingui/react/macro';
@@ -59,90 +58,94 @@ export const StackAvatarsWithTooltip = ({
];
}, [recipients]);
const sections = [
{
key: 'completed',
label: <Trans>Completed</Trans>,
dotClassName: 'bg-green-500',
labelClassName: 'text-green-600 dark:text-green-400',
recipients: completedRecipients,
isInteractive: false,
},
{
key: 'rejected',
label: <Trans>Rejected</Trans>,
dotClassName: 'bg-red-500',
labelClassName: 'text-red-600 dark:text-red-400',
recipients: rejectedRecipients,
isInteractive: false,
},
{
key: 'waiting',
label: <Trans>Waiting</Trans>,
dotClassName: 'bg-blue-500',
labelClassName: 'text-blue-600 dark:text-blue-400',
recipients: waitingRecipients,
isInteractive: true,
},
{
key: 'opened',
label: <Trans>Opened</Trans>,
dotClassName: 'bg-amber-400',
labelClassName: 'text-amber-600 dark:text-amber-400',
recipients: openedRecipients,
isInteractive: true,
},
{
key: 'uncompleted',
label: <Trans>Uncompleted</Trans>,
dotClassName: 'bg-gray-400',
labelClassName: 'text-muted-foreground',
recipients: uncompletedRecipients,
isInteractive: true,
},
].filter((section) => section.recipients.length > 0);
return (
<PopoverHover
trigger={children || <StackAvatars recipients={sortedRecipients} />}
contentProps={{
className: 'w-64 divide-y divide-border/50 overflow-hidden p-0 text-sm',
className: 'flex flex-col gap-y-5 py-2',
side: position,
}}
>
{sections.map((section) => (
<div key={section.key} className="px-3 pt-2.5 pb-1">
<div className="flex items-center gap-1.5 text-xs">
<span className={cn('h-1.5 w-1.5 rounded-full', section.dotClassName)} />
<span className={section.labelClassName}>{section.label}</span>
<span className="text-muted-foreground">{section.recipients.length}</span>
</div>
<div className="mt-1.5">
{section.isInteractive
? section.recipients.map((recipient) => (
<AvatarWithRecipient key={recipient.id} recipient={recipient} documentStatus={documentStatus} />
))
: section.recipients.map((recipient) => (
<div key={recipient.id} className="-mx-2 flex items-center gap-2.5 rounded-md px-2 py-1.5">
<StackAvatar
first={true}
type={getRecipientType(recipient)}
fallbackText={recipientAbbreviation(recipient)}
className="h-8 w-8 shrink-0 border-0 text-xs"
/>
<div className="min-w-0 flex-1">
<p className="truncate font-medium text-foreground">{recipient.email || recipient.name}</p>
<p className="truncate text-muted-foreground text-xs">
{_(RECIPIENT_ROLES_DESCRIPTION[recipient.role].roleName)}
</p>
</div>
</div>
))}
</div>
{completedRecipients.length > 0 && (
<div>
<h1 className="font-medium text-base">
<Trans>Completed</Trans>
</h1>
{completedRecipients.map((recipient) => (
<div key={recipient.id} className="my-1 flex items-center gap-2">
<StackAvatar
first={true}
key={recipient.id}
type={getRecipientType(recipient)}
fallbackText={recipientAbbreviation(recipient)}
/>
<div>
<p className="text-muted-foreground text-sm">{recipient.email || recipient.name}</p>
<p className="text-muted-foreground/70 text-xs">
{_(RECIPIENT_ROLES_DESCRIPTION[recipient.role].roleName)}
</p>
</div>
</div>
))}
</div>
))}
)}
{rejectedRecipients.length > 0 && (
<div>
<h1 className="font-medium text-base">
<Trans>Rejected</Trans>
</h1>
{rejectedRecipients.map((recipient) => (
<div key={recipient.id} className="my-1 flex items-center gap-2">
<StackAvatar
first={true}
key={recipient.id}
type={getRecipientType(recipient)}
fallbackText={recipientAbbreviation(recipient)}
/>
<div>
<p className="text-muted-foreground text-sm">{recipient.email || recipient.name}</p>
<p className="text-muted-foreground/70 text-xs">
{_(RECIPIENT_ROLES_DESCRIPTION[recipient.role].roleName)}
</p>
</div>
</div>
))}
</div>
)}
{waitingRecipients.length > 0 && (
<div>
<h1 className="font-medium text-base">
<Trans>Waiting</Trans>
</h1>
{waitingRecipients.map((recipient) => (
<AvatarWithRecipient key={recipient.id} recipient={recipient} documentStatus={documentStatus} />
))}
</div>
)}
{openedRecipients.length > 0 && (
<div>
<h1 className="font-medium text-base">
<Trans>Opened</Trans>
</h1>
{openedRecipients.map((recipient) => (
<AvatarWithRecipient key={recipient.id} recipient={recipient} documentStatus={documentStatus} />
))}
</div>
)}
{uncompletedRecipients.length > 0 && (
<div>
<h1 className="font-medium text-base">
<Trans>Uncompleted</Trans>
</h1>
{uncompletedRecipients.map((recipient) => (
<AvatarWithRecipient key={recipient.id} recipient={recipient} documentStatus={documentStatus} />
))}
</div>
)}
</PopoverHover>
);
};
@@ -38,11 +38,16 @@ export const AdminClaimsTable = ({ licenseFlags }: AdminClaimsTableProps) => {
const parsedSearchParams = ZUrlSearchParamsSchema.parse(Object.fromEntries(searchParams ?? []));
const { data, isLoading, isLoadingError } = trpc.admin.claims.find.useQuery({
query: parsedSearchParams.query,
page: parsedSearchParams.page,
perPage: parsedSearchParams.perPage,
});
const { data, isLoading, isLoadingError } = trpc.admin.claims.find.useQuery(
{
query: parsedSearchParams.query,
page: parsedSearchParams.page,
perPage: parsedSearchParams.perPage,
},
{
placeholderData: (previousData) => previousData,
},
);
const onPaginationChange = (page: number, perPage: number) => {
updateSearchParams({
@@ -19,11 +19,16 @@ export const AdminDocumentJobsTable = ({ envelopeId }: { envelopeId: string }) =
const parsedSearchParams = ZUrlSearchParamsSchema.parse(Object.fromEntries(searchParams ?? []));
const { data, isLoading, isLoadingError, refetch, isFetching } = trpc.admin.document.findJobs.useQuery({
envelopeId: envelopeId,
page: parsedSearchParams.page,
perPage: parsedSearchParams.perPage,
});
const { data, isLoading, isLoadingError, refetch, isFetching } = trpc.admin.document.findJobs.useQuery(
{
envelopeId: envelopeId,
page: parsedSearchParams.page,
perPage: parsedSearchParams.perPage,
},
{
placeholderData: (previousData) => previousData,
},
);
const onPaginationChange = (page: number, perPage: number) => {
updateSearchParams({
@@ -30,11 +30,16 @@ export const AdminEmailTransportsTable = () => {
const parsedSearchParams = ZUrlSearchParamsSchema.parse(Object.fromEntries(searchParams ?? []));
const { data, isLoading, isLoadingError } = trpc.admin.emailTransport.find.useQuery({
query: parsedSearchParams.query,
page: parsedSearchParams.page,
perPage: parsedSearchParams.perPage,
});
const { data, isLoading, isLoadingError } = trpc.admin.emailTransport.find.useQuery(
{
query: parsedSearchParams.query,
page: parsedSearchParams.page,
perPage: parsedSearchParams.perPage,
},
{
placeholderData: (previousData) => previousData,
},
);
const onPaginationChange = (page: number, perPage: number) => {
updateSearchParams({
@@ -77,15 +77,20 @@ export const AdminOrganisationStatsTable = ({ displayMode = 'usage' }: AdminOrga
const orderByColumn = parseOrderByColumn(searchParams?.get('orderByColumn') ?? undefined);
const orderByDirection = parseOrderByDirection(searchParams?.get('orderByDirection') ?? undefined);
const { data, isLoading, isLoadingError } = trpc.admin.organisation.stats.find.useQuery({
query: parsedSearchParams.query,
page: parsedSearchParams.page,
perPage: parsedSearchParams.perPage,
period,
claimId,
orderByColumn,
orderByDirection,
});
const { data, isLoading, isLoadingError } = trpc.admin.organisation.stats.find.useQuery(
{
query: parsedSearchParams.query,
page: parsedSearchParams.page,
perPage: parsedSearchParams.perPage,
period,
claimId,
orderByColumn,
orderByDirection,
},
{
placeholderData: (previousData) => previousData,
},
);
const onPaginationChange = (page: number, perPage: number) => {
updateSearchParams({
@@ -55,13 +55,18 @@ export const AdminOrganisationsTable = ({
const parsedSearchParams = ZUrlSearchParamsSchema.parse(Object.fromEntries(searchParams ?? []));
const { data, isLoading, isLoadingError } = trpc.admin.organisation.find.useQuery({
query: parsedSearchParams.query,
page: parsedSearchParams.page,
perPage: parsedSearchParams.perPage,
ownerUserId,
memberUserId,
});
const { data, isLoading, isLoadingError } = trpc.admin.organisation.find.useQuery(
{
query: parsedSearchParams.query,
page: parsedSearchParams.page,
perPage: parsedSearchParams.perPage,
ownerUserId,
memberUserId,
},
{
placeholderData: (previousData) => previousData,
},
);
const onPaginationChange = (page: number, perPage: number) => {
updateSearchParams({
@@ -28,12 +28,17 @@ export const AdminUserTeamsTable = ({ userId }: AdminUserTeamsTableProps) => {
const parsedSearchParams = ZUrlSearchParamsSchema.parse(Object.fromEntries(searchParams ?? []));
const { data, isLoading, isLoadingError } = trpc.admin.user.findTeams.useQuery({
userId,
query: parsedSearchParams.query,
page: parsedSearchParams.page,
perPage: parsedSearchParams.perPage,
});
const { data, isLoading, isLoadingError } = trpc.admin.user.findTeams.useQuery(
{
userId,
query: parsedSearchParams.query,
page: parsedSearchParams.page,
perPage: parsedSearchParams.perPage,
},
{
placeholderData: (previousData) => previousData,
},
);
const onPaginationChange = (page: number, perPage: number) => {
updateSearchParams({
@@ -46,10 +46,15 @@ export const InboxTable = () => {
const page = searchParams?.get?.('page') ? Number(searchParams.get('page')) : undefined;
const perPage = searchParams?.get?.('perPage') ? Number(searchParams.get('perPage')) : undefined;
const { data, isLoading, isLoadingError } = trpc.document.inbox.find.useQuery({
page: page || 1,
perPage: perPage || 10,
});
const { data, isLoading, isLoadingError } = trpc.document.inbox.find.useQuery(
{
page: page || 1,
perPage: perPage || 10,
},
{
placeholderData: (previousData) => previousData,
},
);
const columns = useMemo(() => {
return [
@@ -28,12 +28,17 @@ export const OrganisationTeamsTable = () => {
const parsedSearchParams = ZUrlSearchParamsSchema.parse(Object.fromEntries(searchParams ?? []));
const { data, isLoading, isLoadingError } = trpc.team.find.useQuery({
organisationId: organisation.id,
query: parsedSearchParams.query,
page: parsedSearchParams.page,
perPage: parsedSearchParams.perPage,
});
const { data, isLoading, isLoadingError } = trpc.team.find.useQuery(
{
organisationId: organisation.id,
query: parsedSearchParams.query,
page: parsedSearchParams.page,
perPage: parsedSearchParams.perPage,
},
{
placeholderData: (previousData) => previousData,
},
);
const onPaginationChange = (page: number, perPage: number) => {
updateSearchParams({
@@ -91,6 +91,7 @@ export default function DocumentsPage() {
folderId,
},
{
placeholderData: (previousData) => previousData,
...SKIP_QUERY_BATCH_META,
},
);
@@ -72,14 +72,19 @@ export default function WebhookPage({ params }: Route.ComponentProps) {
data,
isLoading: isLogsLoading,
isLoadingError: isLogsLoadingError,
} = trpc.webhook.calls.find.useQuery({
webhookId: params.id,
page: parsedSearchParams.page,
perPage: parsedSearchParams.perPage,
status: parsedSearchParams.status,
events: parsedSearchParams.events,
query: parsedSearchParams.query,
});
} = trpc.webhook.calls.find.useQuery(
{
webhookId: params.id,
page: parsedSearchParams.page,
perPage: parsedSearchParams.perPage,
status: parsedSearchParams.status,
events: parsedSearchParams.events,
query: parsedSearchParams.query,
},
{
placeholderData: (previousData) => previousData,
},
);
/**
* Handle debouncing the search query.
@@ -66,6 +66,7 @@ export default function TemplatesPage() {
},
{
enabled: !isOrgView,
placeholderData: (previousData) => previousData,
},
);
@@ -76,6 +77,7 @@ export default function TemplatesPage() {
},
{
enabled: isOrgView,
placeholderData: (previousData) => previousData,
},
);