chore: merge main, resolve biome formatting conflicts

Merge origin/main into feat/bulk-download. Take PR's bulk action bar
design (escape-key dismiss, popover pill with count badge, optional
download button via onDownloadClick) and the documents page bulk
download state (envelopeMetaCache cached across pages, selected
envelopes mapped from cache for download).

Imports reordered into biome's @documenso → external → relative
groups; combined the @prisma/client import line to include
DocumentStatus as PrismaDocumentStatus alongside EnvelopeType,
FolderType, OrganisationType.
This commit is contained in:
ephraimduncan
2026-05-12 11:50:10 +00:00
1495 changed files with 22084 additions and 33478 deletions
@@ -1,10 +1,3 @@
import { useMemo } from 'react';
import { useLingui } from '@lingui/react/macro';
import { Trans } from '@lingui/react/macro';
import { EditIcon, MoreHorizontalIcon, Trash2Icon } from 'lucide-react';
import { Link, useSearchParams } from 'react-router';
import { useUpdateSearchParams } from '@documenso/lib/client-only/hooks/use-update-search-params';
import type { TLicenseClaim } from '@documenso/lib/types/license';
import { ZUrlSearchParamsSchema } from '@documenso/lib/types/search-params';
@@ -24,6 +17,10 @@ import {
import { Skeleton } from '@documenso/ui/primitives/skeleton';
import { TableCell } from '@documenso/ui/primitives/table';
import { useToast } from '@documenso/ui/primitives/use-toast';
import { Trans, useLingui } from '@lingui/react/macro';
import { EditIcon, MoreHorizontalIcon, Trash2Icon } from 'lucide-react';
import { useMemo } from 'react';
import { Link, useSearchParams } from 'react-router';
import { ClaimDeleteDialog } from '../dialogs/claim-delete-dialog';
import { ClaimUpdateDialog } from '../dialogs/claim-update-dialog';
@@ -68,20 +65,13 @@ export const AdminClaimsTable = ({ licenseFlags }: AdminClaimsTableProps) => {
accessorKey: 'id',
maxSize: 50,
cell: ({ row }) => (
<CopyTextButton
value={row.original.id}
onCopySuccess={() => toast({ title: t`ID copied to clipboard` })}
/>
<CopyTextButton value={row.original.id} onCopySuccess={() => toast({ title: t`ID copied to clipboard` })} />
),
},
{
header: t`Name`,
accessorKey: 'name',
cell: ({ row }) => (
<Link to={`/admin/organisations?query=claim:${row.original.id}`}>
{row.original.name}
</Link>
),
cell: ({ row }) => <Link to={`/admin/organisations?query=claim:${row.original.id}`}>{row.original.name}</Link>,
},
{
header: t`Allowed teams`,
@@ -97,16 +87,14 @@ export const AdminClaimsTable = ({ licenseFlags }: AdminClaimsTableProps) => {
{
header: t`Feature Flags`,
cell: ({ row }) => {
const flags = Object.values(SUBSCRIPTION_CLAIM_FEATURE_FLAGS).filter(
({ key }) => row.original.flags[key],
);
const flags = Object.values(SUBSCRIPTION_CLAIM_FEATURE_FLAGS).filter(({ key }) => row.original.flags[key]);
if (flags.length === 0) {
return <p className="text-xs text-muted-foreground">{t`None`}</p>;
return <p className="text-muted-foreground text-xs">{t`None`}</p>;
}
return (
<ul className="list-disc space-y-1 text-xs text-muted-foreground">
<ul className="list-disc space-y-1 text-muted-foreground text-xs">
{flags.map(({ key, label }) => (
<li key={key}>{label}</li>
))}
@@ -1,12 +1,3 @@
import { useEffect, useMemo, useState, useTransition } from 'react';
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { Trans } from '@lingui/react/macro';
import type { Role, Subscription } from '@prisma/client';
import { Edit, Loader } from 'lucide-react';
import { Link } from 'react-router';
import { useDebouncedValue } from '@documenso/lib/client-only/hooks/use-debounced-value';
import { useUpdateSearchParams } from '@documenso/lib/client-only/hooks/use-update-search-params';
import { Button } from '@documenso/ui/primitives/button';
@@ -14,6 +5,13 @@ import type { DataTableColumnDef } from '@documenso/ui/primitives/data-table';
import { DataTable } from '@documenso/ui/primitives/data-table';
import { DataTablePagination } from '@documenso/ui/primitives/data-table-pagination';
import { Input } from '@documenso/ui/primitives/input';
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { Trans } from '@lingui/react/macro';
import type { Role, Subscription } from '@prisma/client';
import { Edit, Loader } from 'lucide-react';
import { useEffect, useMemo, useState, useTransition } from 'react';
import { Link } from 'react-router';
type UserData = {
id: number;
@@ -24,10 +22,7 @@ type UserData = {
documentCount: number;
};
type SubscriptionLite = Pick<
Subscription,
'id' | 'status' | 'planId' | 'priceId' | 'createdAt' | 'periodEnd'
>;
type SubscriptionLite = Pick<Subscription, 'id' | 'status' | 'planId' | 'priceId' | 'createdAt' | 'periodEnd'>;
type AdminDashboardUsersTableProps = {
users: UserData[];
@@ -36,12 +31,7 @@ type AdminDashboardUsersTableProps = {
page: number;
};
export const AdminDashboardUsersTable = ({
users,
totalPages,
perPage,
page,
}: AdminDashboardUsersTableProps) => {
export const AdminDashboardUsersTable = ({ users, totalPages, perPage, page }: AdminDashboardUsersTableProps) => {
const { _ } = useLingui();
const [isPending, startTransition] = useTransition();
@@ -82,7 +72,7 @@ export const AdminDashboardUsersTable = ({
return (
<Button className="w-24" asChild>
<Link to={`/admin/users/${row.original.id}`}>
<Edit className="-ml-1 mr-2 h-4 w-4" />
<Edit className="mr-2 -ml-1 h-4 w-4" />
<Trans>Edit</Trans>
</Link>
</Button>
@@ -1,9 +1,3 @@
import { useMemo } from 'react';
import { useLingui } from '@lingui/react/macro';
import { Trans } from '@lingui/react/macro';
import { useSearchParams } from 'react-router';
import { useUpdateSearchParams } from '@documenso/lib/client-only/hooks/use-update-search-params';
import { ZUrlSearchParamsSchema } from '@documenso/lib/types/search-params';
import { trpc } from '@documenso/trpc/react';
@@ -13,6 +7,9 @@ import { DataTable } from '@documenso/ui/primitives/data-table';
import { DataTablePagination } from '@documenso/ui/primitives/data-table-pagination';
import { Skeleton } from '@documenso/ui/primitives/skeleton';
import { TableCell } from '@documenso/ui/primitives/table';
import { Trans, useLingui } from '@lingui/react/macro';
import { useMemo } from 'react';
import { useSearchParams } from 'react-router';
export const AdminDocumentJobsTable = ({ envelopeId }: { envelopeId: string }) => {
const { t, i18n } = useLingui();
@@ -22,12 +19,11 @@ 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,
});
const onPaginationChange = (page: number, perPage: number) => {
updateSearchParams({
@@ -65,8 +61,7 @@ export const AdminDocumentJobsTable = ({ envelopeId }: { envelopeId: string }) =
{
header: t`Last Retried`,
accessorKey: 'lastRetriedAt',
cell: ({ row }) =>
row.original.lastRetriedAt ? i18n.date(row.original.lastRetriedAt) : 'N/A',
cell: ({ row }) => (row.original.lastRetriedAt ? i18n.date(row.original.lastRetriedAt) : 'N/A'),
},
{
header: t`Completed`,
@@ -79,7 +74,7 @@ export const AdminDocumentJobsTable = ({ envelopeId }: { envelopeId: string }) =
return (
<div className="space-y-4">
<div className="flex items-center justify-between">
<h2 className="text-lg font-semibold">
<h2 className="font-semibold text-lg">
<Trans>Background Jobs</Trans>
</h2>
@@ -126,9 +121,7 @@ export const AdminDocumentJobsTable = ({ envelopeId }: { envelopeId: string }) =
}}
>
{(table) =>
results.totalPages > 1 && (
<DataTablePagination additionalInformation="VisibleCount" table={table} />
)
results.totalPages > 1 && <DataTablePagination additionalInformation="VisibleCount" table={table} />
}
</DataTable>
</div>
@@ -1,13 +1,3 @@
import { useMemo, useState } from 'react';
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { Trans } from '@lingui/react/macro';
import { DateTime } from 'luxon';
import type { DateTimeFormatOptions } from 'luxon';
import { useSearchParams } from 'react-router';
import { UAParser } from 'ua-parser-js';
import { useUpdateSearchParams } from '@documenso/lib/client-only/hooks/use-update-search-params';
import type { TDocumentAuditLog } from '@documenso/lib/types/document-audit-logs';
import { ZUrlSearchParamsSchema } from '@documenso/lib/types/search-params';
@@ -22,6 +12,14 @@ import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@documenso/ui/
import { Skeleton } from '@documenso/ui/primitives/skeleton';
import { TableCell } from '@documenso/ui/primitives/table';
import { useToast } from '@documenso/ui/primitives/use-toast';
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { Trans } from '@lingui/react/macro';
import type { DateTimeFormatOptions } from 'luxon';
import { DateTime } from 'luxon';
import { useMemo, useState } from 'react';
import { useSearchParams } from 'react-router';
import { UAParser } from 'ua-parser-js';
export type AdminDocumentLogsTableProps = {
envelopeId: string;
@@ -104,9 +102,7 @@ export const AdminDocumentLogsTable = ({ envelopeId }: AdminDocumentLogsTablePro
{
header: _(msg`Action`),
accessorKey: 'type',
cell: ({ row }) => (
<span>{formatDocumentAuditLogAction(i18n, row.original).description}</span>
),
cell: ({ row }) => <span>{formatDocumentAuditLogAction(i18n, row.original).description}</span>,
},
{
header: _(msg`IP Address`),
@@ -193,14 +189,14 @@ export const AdminDocumentLogsTable = ({ envelopeId }: AdminDocumentLogsTablePro
{selectedAuditLog && (
<div className="group relative">
<div className="absolute right-2 top-2 opacity-0 transition-opacity group-hover:opacity-100">
<div className="absolute top-2 right-2 opacity-0 transition-opacity group-hover:opacity-100">
<CopyTextButton
value={JSON.stringify(selectedAuditLog, null, 2)}
onCopySuccess={() => toast({ title: _(msg`Copied to clipboard`) })}
/>
</div>
<pre className="max-h-[60vh] overflow-auto whitespace-pre-wrap break-all rounded-lg border border-border bg-muted/50 p-4 font-mono text-xs leading-relaxed text-foreground">
<pre className="max-h-[60vh] overflow-auto whitespace-pre-wrap break-all rounded-lg border border-border bg-muted/50 p-4 font-mono text-foreground text-xs leading-relaxed">
{JSON.stringify(selectedAuditLog, null, 2)}
</pre>
</div>
@@ -1,41 +1,20 @@
import { useMemo } from 'react';
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { Trans } from '@lingui/react/macro';
import {
type Field,
type Recipient,
RecipientRole,
type Signature,
SigningStatus,
} from '@prisma/client';
import { useForm } from 'react-hook-form';
import { useRevalidator } from 'react-router';
import { z } from 'zod';
import { zEmail } from '@documenso/lib/utils/zod';
import { trpc } from '@documenso/trpc/react';
import { Button } from '@documenso/ui/primitives/button';
import type { DataTableColumnDef } from '@documenso/ui/primitives/data-table';
import { DataTable } from '@documenso/ui/primitives/data-table';
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from '@documenso/ui/primitives/form/form';
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from '@documenso/ui/primitives/form/form';
import { Input } from '@documenso/ui/primitives/input';
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from '@documenso/ui/primitives/select';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@documenso/ui/primitives/select';
import { useToast } from '@documenso/ui/primitives/use-toast';
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { Trans } from '@lingui/react/macro';
import { type Field, type Recipient, RecipientRole, type Signature, SigningStatus } from '@prisma/client';
import { useMemo } from 'react';
import { useForm } from 'react-hook-form';
import { useRevalidator } from 'react-router';
import { z } from 'zod';
const RECIPIENT_ROLE_LABELS: Record<RecipientRole, string> = {
[RecipientRole.SIGNER]: 'Signer',
@@ -105,9 +84,7 @@ export const AdminDocumentRecipientItemTable = ({ recipient }: RecipientItemProp
accessorKey: 'signature',
cell: ({ row }) => (
<div>
{row.original.signature?.typedSignature && (
<span>{row.original.signature.typedSignature}</span>
)}
{row.original.signature?.typedSignature && <span>{row.original.signature.typedSignature}</span>}
{row.original.signature?.signatureImageAsBase64 && (
<img
@@ -122,11 +99,7 @@ export const AdminDocumentRecipientItemTable = ({ recipient }: RecipientItemProp
] satisfies DataTableColumnDef<(typeof recipient)['fields'][number]>[];
}, []);
const onUpdateRecipientFormSubmit = async ({
name,
email,
role,
}: TAdminUpdateRecipientFormSchema) => {
const onUpdateRecipientFormSubmit = async ({ name, email, role }: TAdminUpdateRecipientFormSchema) => {
try {
await updateRecipient({
id: recipient.id,
@@ -156,9 +129,7 @@ export const AdminDocumentRecipientItemTable = ({ recipient }: RecipientItemProp
<form onSubmit={form.handleSubmit(onUpdateRecipientFormSubmit)}>
<fieldset
className="flex h-full max-w-xl flex-col gap-y-4"
disabled={
form.formState.isSubmitting || recipient.signingStatus === SigningStatus.SIGNED
}
disabled={form.formState.isSubmitting || recipient.signingStatus === SigningStatus.SIGNED}
>
<FormField
control={form.control}
@@ -209,10 +180,7 @@ export const AdminDocumentRecipientItemTable = ({ recipient }: RecipientItemProp
<Select
value={field.value}
onValueChange={field.onChange}
disabled={
form.formState.isSubmitting ||
recipient.signingStatus === SigningStatus.SIGNED
}
disabled={form.formState.isSubmitting || recipient.signingStatus === SigningStatus.SIGNED}
>
<SelectTrigger>
<SelectValue />
@@ -244,7 +212,7 @@ export const AdminDocumentRecipientItemTable = ({ recipient }: RecipientItemProp
<hr className="my-4" />
<h2 className="mb-4 text-lg font-semibold">
<h2 className="mb-4 font-semibold text-lg">
<Trans>Fields</Trans>
</h2>
@@ -1,11 +1,3 @@
import { useEffect, useMemo, useState, useTransition } from 'react';
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { Trans } from '@lingui/react/macro';
import { ChevronDownIcon, ChevronUpIcon, ChevronsUpDown, Loader } from 'lucide-react';
import { Link } from 'react-router';
import { useDebouncedValue } from '@documenso/lib/client-only/hooks/use-debounced-value';
import { useUpdateSearchParams } from '@documenso/lib/client-only/hooks/use-update-search-params';
import type { DateRange } from '@documenso/lib/types/search-params';
@@ -13,6 +5,12 @@ import type { DataTableColumnDef } from '@documenso/ui/primitives/data-table';
import { DataTable } from '@documenso/ui/primitives/data-table';
import { DataTablePagination } from '@documenso/ui/primitives/data-table-pagination';
import { Input } from '@documenso/ui/primitives/input';
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { Trans } from '@lingui/react/macro';
import { ChevronDownIcon, ChevronsUpDown, ChevronUpIcon, Loader } from 'lucide-react';
import { useEffect, useMemo, useState, useTransition } from 'react';
import { Link } from 'react-router';
export type OrganisationOverview = {
id: string;
@@ -56,10 +54,7 @@ export const AdminOrganisationOverviewTable = ({
return [
{
header: () => (
<div
className="flex cursor-pointer items-center"
onClick={() => handleColumnSort('name')}
>
<div className="flex cursor-pointer items-center" onClick={() => handleColumnSort('name')}>
{_(msg`Name`)}
{sortBy === 'name' ? (
sortOrder === 'asc' ? (
@@ -89,10 +84,7 @@ export const AdminOrganisationOverviewTable = ({
},
{
header: () => (
<div
className="flex cursor-pointer items-center"
onClick={() => handleColumnSort('signingVolume')}
>
<div className="flex cursor-pointer items-center" onClick={() => handleColumnSort('signingVolume')}>
<span className="whitespace-nowrap">
<Trans>Document Volume</Trans>
</span>
@@ -130,10 +122,7 @@ export const AdminOrganisationOverviewTable = ({
{
header: () => {
return (
<div
className="flex cursor-pointer items-center"
onClick={() => handleColumnSort('createdAt')}
>
<div className="flex cursor-pointer items-center" onClick={() => handleColumnSort('createdAt')}>
<span className="whitespace-nowrap">
<Trans>Created</Trans>
</span>
@@ -1,17 +1,3 @@
import { useMemo, useState } from 'react';
import { useLingui } from '@lingui/react/macro';
import { Trans } from '@lingui/react/macro';
import {
ArrowRightLeftIcon,
CreditCardIcon,
ExternalLinkIcon,
MoreHorizontalIcon,
SettingsIcon,
UserIcon,
} from 'lucide-react';
import { Link, useSearchParams } from 'react-router';
import { useUpdateSearchParams } from '@documenso/lib/client-only/hooks/use-update-search-params';
import { SUBSCRIPTION_STATUS_MAP } from '@documenso/lib/constants/billing';
import { ZUrlSearchParamsSchema } from '@documenso/lib/types/search-params';
@@ -29,6 +15,17 @@ import {
} from '@documenso/ui/primitives/dropdown-menu';
import { Skeleton } from '@documenso/ui/primitives/skeleton';
import { TableCell } from '@documenso/ui/primitives/table';
import { Trans, useLingui } from '@lingui/react/macro';
import {
ArrowRightLeftIcon,
CreditCardIcon,
ExternalLinkIcon,
MoreHorizontalIcon,
SettingsIcon,
UserIcon,
} from 'lucide-react';
import { useMemo, useState } from 'react';
import { Link, useSearchParams } from 'react-router';
import { AdminSwapSubscriptionDialog } from '~/components/dialogs/admin-swap-subscription-dialog';
@@ -85,9 +82,7 @@ export const AdminOrganisationsTable = ({
{
header: t`Organisation`,
accessorKey: 'name',
cell: ({ row }) => (
<Link to={`/admin/organisations/${row.original.id}`}>{row.original.name}</Link>
),
cell: ({ row }) => <Link to={`/admin/organisations/${row.original.id}`}>{row.original.name}</Link>,
},
{
header: t`Created At`,
@@ -97,17 +92,13 @@ export const AdminOrganisationsTable = ({
{
header: t`Owner`,
accessorKey: 'owner',
cell: ({ row }) => (
<Link to={`/admin/users/${row.original.owner.id}`}>{row.original.owner.name}</Link>
),
cell: ({ row }) => <Link to={`/admin/users/${row.original.owner.id}`}>{row.original.owner.name}</Link>,
},
{
id: 'role',
header: t`Role`,
cell: ({ row }) => (
<Badge variant="neutral">
{row.original.owner.id === memberUserId ? t`Owner` : t`Member`}
</Badge>
<Badge variant="neutral">{row.original.owner.id === memberUserId ? t`Owner` : t`Member`}</Badge>
),
},
{
@@ -118,7 +109,7 @@ export const AdminOrganisationsTable = ({
const isPaid = subscription && subscription.status === 'ACTIVE';
return (
<div
className={`inline-flex items-center rounded-full px-2 py-1 text-xs font-medium ${
className={`inline-flex items-center rounded-full px-2 py-1 font-medium text-xs ${
isPaid ? 'bg-green-100 text-green-800' : 'bg-gray-100 text-gray-800'
}`}
>
@@ -183,8 +174,7 @@ export const AdminOrganisationsTable = ({
</DropdownMenuItem>
{row.original.subscription &&
(row.original.subscription.status === 'ACTIVE' ||
row.original.subscription.status === 'PAST_DUE') && (
(row.original.subscription.status === 'ACTIVE' || row.original.subscription.status === 'PAST_DUE') && (
<DropdownMenuItem
onClick={() =>
setSwapSource({
@@ -1,9 +1,3 @@
import { useMemo } from 'react';
import { useLingui } from '@lingui/react';
import { useLingui as useLinguiMacro } from '@lingui/react/macro';
import { Link, useSearchParams } from 'react-router';
import { useUpdateSearchParams } from '@documenso/lib/client-only/hooks/use-update-search-params';
import { TEAM_MEMBER_ROLE_MAP } from '@documenso/lib/constants/teams-translations';
import { ZUrlSearchParamsSchema } from '@documenso/lib/types/search-params';
@@ -16,6 +10,10 @@ import { DataTablePagination } from '@documenso/ui/primitives/data-table-paginat
import { HoverCard, HoverCardContent, HoverCardTrigger } from '@documenso/ui/primitives/hover-card';
import { Skeleton } from '@documenso/ui/primitives/skeleton';
import { TableCell } from '@documenso/ui/primitives/table';
import { useLingui } from '@lingui/react';
import { useLingui as useLinguiMacro } from '@lingui/react/macro';
import { useMemo } from 'react';
import { Link, useSearchParams } from 'react-router';
type AdminUserTeamsTableProps = {
userId: number;
@@ -61,10 +59,7 @@ export const AdminUserTeamsTable = ({ userId }: AdminUserTeamsTableProps) => {
<HoverCardTrigger className="cursor-default underline decoration-dotted underline-offset-4">
{row.original.name}
</HoverCardTrigger>
<HoverCardContent
className="w-auto font-mono text-xs text-muted-foreground"
align="start"
>
<HoverCardContent className="w-auto font-mono text-muted-foreground text-xs" align="start">
<dl className="grid grid-cols-[auto_1fr] gap-x-3 gap-y-1">
<dt>id</dt>
<dd>{row.original.id}</dd>
@@ -79,10 +74,7 @@ export const AdminUserTeamsTable = ({ userId }: AdminUserTeamsTableProps) => {
header: t`Organisation`,
accessorKey: 'organisation',
cell: ({ row }) => (
<Link
to={`/admin/organisations/${row.original.organisation.id}`}
className="hover:underline"
>
<Link to={`/admin/organisations/${row.original.organisation.id}`} className="hover:underline">
{row.original.organisation.name}
</Link>
),
@@ -91,9 +83,7 @@ export const AdminUserTeamsTable = ({ userId }: AdminUserTeamsTableProps) => {
header: t`Role`,
accessorKey: 'teamRole',
cell: ({ row }) => (
<Badge variant="neutral">
{i18n._(TEAM_MEMBER_ROLE_MAP[row.original.teamRole as TeamMemberRole])}
</Badge>
<Badge variant="neutral">{i18n._(TEAM_MEMBER_ROLE_MAP[row.original.teamRole as TeamMemberRole])}</Badge>
),
},
{
@@ -137,9 +127,7 @@ export const AdminUserTeamsTable = ({ userId }: AdminUserTeamsTableProps) => {
}}
>
{(table) =>
table.getPageCount() > 1 ? (
<DataTablePagination additionalInformation="VisibleCount" table={table} />
) : null
table.getPageCount() > 1 ? <DataTablePagination additionalInformation="VisibleCount" table={table} /> : null
}
</DataTable>
);
@@ -1,13 +1,3 @@
import { useMemo } from 'react';
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { Trans } from '@lingui/react/macro';
import { DateTime } from 'luxon';
import type { DateTimeFormatOptions } from 'luxon';
import { useSearchParams } from 'react-router';
import { UAParser } from 'ua-parser-js';
import { useUpdateSearchParams } from '@documenso/lib/client-only/hooks/use-update-search-params';
import { ZUrlSearchParamsSchema } from '@documenso/lib/types/search-params';
import { formatDocumentAuditLogAction } from '@documenso/lib/utils/document-audit-logs';
@@ -17,6 +7,14 @@ import { DataTable } from '@documenso/ui/primitives/data-table';
import { DataTablePagination } from '@documenso/ui/primitives/data-table-pagination';
import { Skeleton } from '@documenso/ui/primitives/skeleton';
import { TableCell } from '@documenso/ui/primitives/table';
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { Trans } from '@lingui/react/macro';
import type { DateTimeFormatOptions } from 'luxon';
import { DateTime } from 'luxon';
import { useMemo } from 'react';
import { useSearchParams } from 'react-router';
import { UAParser } from 'ua-parser-js';
export type DocumentLogsTableProps = {
documentId: number;
@@ -97,9 +95,7 @@ export const DocumentLogsTable = ({ documentId, userId }: DocumentLogsTableProps
{
header: _(msg`Action`),
accessorKey: 'type',
cell: ({ row }) => (
<span>{formatDocumentAuditLogAction(i18n, row.original, userId).description}</span>
),
cell: ({ row }) => <span>{formatDocumentAuditLogAction(i18n, row.original, userId).description}</span>,
},
{
header: _(msg`IP Address`),
@@ -1,15 +1,14 @@
import { Trans } from '@lingui/react/macro';
import { DocumentStatus, RecipientRole, SigningStatus } from '@prisma/client';
import { CheckCircle, Download, Edit, EyeIcon, Pencil } from 'lucide-react';
import { Link } from 'react-router';
import { match } from 'ts-pattern';
import { useSession } from '@documenso/lib/client-only/providers/session';
import type { TDocumentMany as TDocumentRow } from '@documenso/lib/types/document';
import { isDocumentCompleted } from '@documenso/lib/utils/document';
import { findRecipientByEmail } from '@documenso/lib/utils/recipients';
import { formatDocumentsPath } from '@documenso/lib/utils/teams';
import { Button } from '@documenso/ui/primitives/button';
import { Trans } from '@lingui/react/macro';
import { DocumentStatus, RecipientRole, SigningStatus } from '@prisma/client';
import { CheckCircle, Download, Edit, EyeIcon, Pencil } from 'lucide-react';
import { Link } from 'react-router';
import { match } from 'ts-pattern';
import { useCurrentTeam } from '~/providers/team';
@@ -57,36 +56,33 @@ export const DocumentsTableActionButton = ({ row }: DocumentsTableActionButtonPr
isCurrentTeamDocument,
internalVersion: row.internalVersion,
})
.with(
isOwner ? { isDraft: true, isOwner: true } : { isDraft: true, isCurrentTeamDocument: true },
() => (
<Button className="w-32" asChild>
<Link to={formatPath}>
<Edit className="-ml-1 mr-2 h-4 w-4" />
<Trans>Edit</Trans>
</Link>
</Button>
),
)
.with(isOwner ? { isDraft: true, isOwner: true } : { isDraft: true, isCurrentTeamDocument: true }, () => (
<Button className="w-32" asChild>
<Link to={formatPath}>
<Edit className="mr-2 -ml-1 h-4 w-4" />
<Trans>Edit</Trans>
</Link>
</Button>
))
.with({ isRecipient: true, isPending: true, isSigned: false }, () => (
<Button className="w-32" asChild>
<Link to={`/sign/${recipient?.token}`}>
{match(role)
.with(RecipientRole.SIGNER, () => (
<>
<Pencil className="-ml-1 mr-2 h-4 w-4" />
<Pencil className="mr-2 -ml-1 h-4 w-4" />
<Trans>Sign</Trans>
</>
))
.with(RecipientRole.APPROVER, () => (
<>
<CheckCircle className="-ml-1 mr-2 h-4 w-4" />
<CheckCircle className="mr-2 -ml-1 h-4 w-4" />
<Trans>Approve</Trans>
</>
))
.otherwise(() => (
<>
<EyeIcon className="-ml-1 mr-2 h-4 w-4" />
<EyeIcon className="mr-2 -ml-1 h-4 w-4" />
<Trans>View</Trans>
</>
))}
@@ -95,7 +91,7 @@ export const DocumentsTableActionButton = ({ row }: DocumentsTableActionButtonPr
))
.with({ isPending: true, isSigned: true }, () => (
<Button className="w-32" disabled={true}>
<EyeIcon className="-ml-1 mr-2 h-4 w-4" />
<EyeIcon className="mr-2 -ml-1 h-4 w-4" />
<Trans>View</Trans>
</Button>
))
@@ -106,7 +102,7 @@ export const DocumentsTableActionButton = ({ row }: DocumentsTableActionButtonPr
token={recipient?.token}
trigger={
<Button className="w-32">
<Download className="-ml-1 mr-2 inline h-4 w-4" />
<Download className="mr-2 -ml-1 inline h-4 w-4" />
<Trans>Download</Trans>
</Button>
}
@@ -1,5 +1,18 @@
import { useState } from 'react';
import { useSession } from '@documenso/lib/client-only/providers/session';
import type { TDocumentMany as TDocumentRow } from '@documenso/lib/types/document';
import { isDocumentCompleted } from '@documenso/lib/utils/document';
import { getEnvelopeItemPermissions } from '@documenso/lib/utils/envelope';
import { findRecipientByEmail } from '@documenso/lib/utils/recipients';
import { formatDocumentsPath } from '@documenso/lib/utils/teams';
import { trpc as trpcReact } from '@documenso/trpc/react';
import { DocumentShareButton } from '@documenso/ui/components/document/document-share-button';
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuTrigger,
} from '@documenso/ui/primitives/dropdown-menu';
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { Trans } from '@lingui/react/macro';
@@ -18,24 +31,9 @@ import {
Share,
Trash2,
} from 'lucide-react';
import { useState } from 'react';
import { Link } from 'react-router';
import { useSession } from '@documenso/lib/client-only/providers/session';
import type { TDocumentMany as TDocumentRow } from '@documenso/lib/types/document';
import { isDocumentCompleted } from '@documenso/lib/utils/document';
import { getEnvelopeItemPermissions } from '@documenso/lib/utils/envelope';
import { findRecipientByEmail } from '@documenso/lib/utils/recipients';
import { formatDocumentsPath } from '@documenso/lib/utils/teams';
import { trpc as trpcReact } from '@documenso/trpc/react';
import { DocumentShareButton } from '@documenso/ui/components/document/document-share-button';
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuTrigger,
} from '@documenso/ui/primitives/dropdown-menu';
import { DocumentResendDialog } from '~/components/dialogs/document-resend-dialog';
import { EnvelopeDeleteDialog } from '~/components/dialogs/envelope-delete-dialog';
import { EnvelopeDuplicateDialog } from '~/components/dialogs/envelope-duplicate-dialog';
@@ -51,10 +49,7 @@ export type DocumentsTableActionDropdownProps = {
onMoveDocument?: () => void;
};
export const DocumentsTableActionDropdown = ({
row,
onMoveDocument,
}: DocumentsTableActionDropdownProps) => {
export const DocumentsTableActionDropdown = ({ row, onMoveDocument }: DocumentsTableActionDropdownProps) => {
const { user } = useSession();
const team = useCurrentTeam();
@@ -152,7 +147,8 @@ export const DocumentsTableActionDropdown = ({
<EnvelopeDownloadDialog
envelopeId={row.envelopeId}
envelopeStatus={row.status}
token={recipient?.token}
isLegacy={row.internalVersion === 1}
token={canManageDocument ? undefined : recipient?.token}
trigger={
<DropdownMenuItem asChild onSelect={(e) => e.preventDefault()}>
<div>
@@ -1,10 +1,9 @@
import { ExtendedDocumentStatus } from '@documenso/prisma/types/extended-document-status';
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { Bird, CheckCircle2 } from 'lucide-react';
import { match } from 'ts-pattern';
import { ExtendedDocumentStatus } from '@documenso/prisma/types/extended-document-status';
export type DocumentsTableEmptyStateProps = { status: ExtendedDocumentStatus };
export const DocumentsTableEmptyState = ({ status }: DocumentsTableEmptyStateProps) => {
@@ -38,13 +37,13 @@ export const DocumentsTableEmptyState = ({ status }: DocumentsTableEmptyStatePro
return (
<div
className="text-muted-foreground/60 flex h-60 flex-col items-center justify-center gap-y-4"
className="flex h-60 flex-col items-center justify-center gap-y-4 text-muted-foreground/60"
data-testid="empty-document-state"
>
<Icon className="h-12 w-12" strokeWidth={1.5} />
<div className="text-center">
<h3 className="text-lg font-semibold">{_(title)}</h3>
<h3 className="font-semibold text-lg">{_(title)}</h3>
<p className="mt-2 max-w-[60ch]">{_(message)}</p>
</div>
@@ -1,10 +1,9 @@
import { msg } from '@lingui/core/macro';
import { Trans } from '@lingui/react/macro';
import { useLocation, useNavigate, useSearchParams } from 'react-router';
import { useIsMounted } from '@documenso/lib/client-only/hooks/use-is-mounted';
import { trpc } from '@documenso/trpc/react';
import { MultiSelectCombobox } from '@documenso/ui/primitives/multi-select-combobox';
import { msg } from '@lingui/core/macro';
import { Trans } from '@lingui/react/macro';
import { useLocation, useNavigate, useSearchParams } from 'react-router';
type DocumentsTableSenderFilterProps = {
teamId: number;
@@ -17,9 +16,7 @@ export const DocumentsTableSenderFilter = ({ teamId }: DocumentsTableSenderFilte
const isMounted = useIsMounted();
const senderIds = (searchParams?.get('senderIds') ?? '')
.split(',')
.filter((value) => value !== '');
const senderIds = (searchParams?.get('senderIds') ?? '').split(',').filter((value) => value !== '');
const { data, isLoading } = trpc.team.member.getMany.useQuery({
teamId,
@@ -49,7 +46,7 @@ export const DocumentsTableSenderFilter = ({ teamId }: DocumentsTableSenderFilte
return (
<MultiSelectCombobox
emptySelectionPlaceholder={
<p className="text-muted-foreground font-normal">
<p className="font-normal text-muted-foreground">
<Trans>
<span className="text-muted-foreground/70">Sender:</span> All
</Trans>
@@ -1,10 +1,9 @@
import { Link } from 'react-router';
import { match } from 'ts-pattern';
import { useSession } from '@documenso/lib/client-only/providers/session';
import type { TDocumentMany as TDocumentRow } from '@documenso/lib/types/document';
import { findRecipientByEmail } from '@documenso/lib/utils/recipients';
import { formatDocumentsPath } from '@documenso/lib/utils/teams';
import { Link } from 'react-router';
import { match } from 'ts-pattern';
import { useCurrentTeam } from '~/providers/team';
@@ -53,8 +52,6 @@ export const DataTableTitle = ({ row, teamUrl }: DataTableTitleProps) => {
</Link>
))
.otherwise(() => (
<span className="block max-w-[10rem] truncate font-medium hover:underline md:max-w-[20rem]">
{row.title}
</span>
<span className="block max-w-[10rem] truncate font-medium hover:underline md:max-w-[20rem]">{row.title}</span>
));
};
@@ -1,12 +1,3 @@
import { useMemo, useTransition } from 'react';
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { Loader } from 'lucide-react';
import { DateTime } from 'luxon';
import { Link } from 'react-router';
import { match } from 'ts-pattern';
import { useUpdateSearchParams } from '@documenso/lib/client-only/hooks/use-update-search-params';
import { useSession } from '@documenso/lib/client-only/providers/session';
import { isDocumentCompleted } from '@documenso/lib/utils/document';
@@ -19,6 +10,13 @@ import { DataTable } from '@documenso/ui/primitives/data-table';
import { DataTablePagination } from '@documenso/ui/primitives/data-table-pagination';
import { Skeleton } from '@documenso/ui/primitives/skeleton';
import { TableCell } from '@documenso/ui/primitives/table';
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { Loader } from 'lucide-react';
import { DateTime } from 'luxon';
import { useMemo, useTransition } from 'react';
import { Link } from 'react-router';
import { match } from 'ts-pattern';
import { DocumentStatus } from '~/components/general/document/document-status';
import { useCurrentTeam } from '~/providers/team';
@@ -87,18 +85,11 @@ export const DocumentsTable = ({
{
header: _(msg`Created`),
accessorKey: 'createdAt',
cell: ({ row }) =>
i18n.date(row.original.createdAt, { ...DateTime.DATETIME_SHORT, hourCycle: 'h12' }),
cell: ({ row }) => i18n.date(row.original.createdAt, { ...DateTime.DATETIME_SHORT, hourCycle: 'h12' }),
},
{
header: _(msg`Title`),
cell: ({ row }) => (
<DataTableTitle
row={row.original}
teamUrl={team?.url}
teamEmail={team?.teamEmail?.email}
/>
),
cell: ({ row }) => <DataTableTitle row={row.original} teamUrl={team?.url} teamEmail={team?.teamEmail?.email} />,
},
{
id: 'sender',
@@ -109,10 +100,7 @@ export const DocumentsTable = ({
header: _(msg`Recipient`),
accessorKey: 'recipient',
cell: ({ row }) => (
<StackAvatarsWithTooltip
recipients={row.original.recipients}
documentStatus={row.original.status}
/>
<StackAvatarsWithTooltip recipients={row.original.recipients} documentStatus={row.original.status} />
),
},
{
@@ -263,8 +251,6 @@ const DataTableTitle = ({ row, teamUrl, teamEmail }: DataTableTitleProps) => {
</Link>
))
.otherwise(() => (
<span className="block max-w-[10rem] truncate font-medium hover:underline md:max-w-[20rem]">
{row.title}
</span>
<span className="block max-w-[10rem] truncate font-medium hover:underline md:max-w-[20rem]">{row.title}</span>
));
};
@@ -1,10 +1,7 @@
import { useEffect } from 'react';
import { useLingui } from '@lingui/react/macro';
import { Trans } from '@lingui/react/macro';
import { DownloadIcon, FolderInputIcon, Trash2Icon, XIcon } from 'lucide-react';
import { Button } from '@documenso/ui/primitives/button';
import { Trans, useLingui } from '@lingui/react/macro';
import { DownloadIcon, FolderInputIcon, Trash2Icon, XIcon } from 'lucide-react';
import { useEffect } from 'react';
export type EnvelopesTableBulkActionBarProps = {
selectedCount: number;
@@ -45,10 +42,10 @@ export const EnvelopesTableBulkActionBar = ({
return (
<div className="fixed bottom-6 left-1/2 z-50 flex -translate-x-1/2 items-center gap-x-1 rounded-xl bg-popover p-1.5 text-popover-foreground shadow-lg ring-1 ring-black/10 dark:ring-white/10">
<div className="flex items-center gap-x-2 px-2">
<span className="flex h-5 min-w-5 items-center justify-center rounded-md bg-primary px-1 text-xs font-semibold tabular-nums text-primary-foreground">
<span className="flex h-5 min-w-5 items-center justify-center rounded-md bg-primary px-1 font-semibold text-primary-foreground text-xs tabular-nums">
{selectedCount}
</span>
<span className="max-[420px]:hidden text-sm font-medium text-foreground">
<span className="font-medium text-foreground text-sm max-[420px]:hidden">
<Trans>selected</Trans>
</span>
</div>
@@ -60,7 +57,7 @@ export const EnvelopesTableBulkActionBar = ({
variant="ghost"
size="sm"
onClick={onMoveClick}
className="h-8 gap-x-1.5 py-1.5 pl-2 pr-2.5"
className="h-8 gap-x-1.5 py-1.5 pr-2.5 pl-2"
>
<FolderInputIcon className="size-4 shrink-0" />
<Trans>Move</Trans>
@@ -72,7 +69,7 @@ export const EnvelopesTableBulkActionBar = ({
variant="ghost"
size="sm"
onClick={onDownloadClick}
className="h-8 gap-x-1.5 py-1.5 pl-2 pr-2.5"
className="h-8 gap-x-1.5 py-1.5 pr-2.5 pl-2"
>
<DownloadIcon className="size-4 shrink-0" />
<Trans>Download</Trans>
@@ -84,7 +81,7 @@ export const EnvelopesTableBulkActionBar = ({
variant="ghost"
size="sm"
onClick={onDeleteClick}
className="h-8 gap-x-1.5 py-1.5 pl-2 pr-2.5 text-destructive hover:bg-destructive/10 hover:text-destructive"
className="h-8 gap-x-1.5 py-1.5 pr-2.5 pl-2 text-destructive hover:bg-destructive/10 hover:text-destructive"
>
<Trash2Icon className="size-4 shrink-0" />
<Trans>Delete</Trans>
@@ -1,15 +1,3 @@
import { useMemo, useTransition } from 'react';
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { Trans } from '@lingui/react/macro';
import { DocumentStatus as DocumentStatusEnum } from '@prisma/client';
import { RecipientRole, SigningStatus } from '@prisma/client';
import { CheckCircleIcon, DownloadIcon, EyeIcon, Loader, PencilIcon } from 'lucide-react';
import { DateTime } from 'luxon';
import { Link, useSearchParams } from 'react-router';
import { match } from 'ts-pattern';
import { useUpdateSearchParams } from '@documenso/lib/client-only/hooks/use-update-search-params';
import { useSession } from '@documenso/lib/client-only/providers/session';
import { isDocumentCompleted } from '@documenso/lib/utils/document';
@@ -22,6 +10,15 @@ import { DataTablePagination } from '@documenso/ui/primitives/data-table-paginat
import { Skeleton } from '@documenso/ui/primitives/skeleton';
import { TableCell } from '@documenso/ui/primitives/table';
import { useToast } from '@documenso/ui/primitives/use-toast';
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { Trans } from '@lingui/react/macro';
import { DocumentStatus as DocumentStatusEnum, RecipientRole, SigningStatus } from '@prisma/client';
import { CheckCircleIcon, DownloadIcon, EyeIcon, Loader, PencilIcon } from 'lucide-react';
import { DateTime } from 'luxon';
import { useMemo, useTransition } from 'react';
import { Link, useSearchParams } from 'react-router';
import { match } from 'ts-pattern';
import { DocumentStatus } from '~/components/general/document/document-status';
import { useOptionalCurrentTeam } from '~/providers/team';
@@ -59,15 +56,12 @@ export const InboxTable = () => {
{
header: _(msg`Created`),
accessorKey: 'createdAt',
cell: ({ row }) =>
i18n.date(row.original.createdAt, { ...DateTime.DATETIME_SHORT, hourCycle: 'h12' }),
cell: ({ row }) => i18n.date(row.original.createdAt, { ...DateTime.DATETIME_SHORT, hourCycle: 'h12' }),
},
{
header: _(msg`Title`),
cell: ({ row }) => (
<span className="block max-w-[10rem] truncate font-medium md:max-w-[20rem]">
{row.original.title}
</span>
<span className="block max-w-[10rem] truncate font-medium md:max-w-[20rem]">{row.original.title}</span>
),
},
{
@@ -79,10 +73,7 @@ export const InboxTable = () => {
header: _(msg`Recipient`),
accessorKey: 'recipient',
cell: ({ row }) => (
<StackAvatarsWithTooltip
recipients={row.original.recipients}
documentStatus={row.original.status}
/>
<StackAvatarsWithTooltip recipients={row.original.recipients} documentStatus={row.original.status} />
),
},
{
@@ -130,7 +121,7 @@ export const InboxTable = () => {
enable: isLoadingError || false,
}}
emptyState={
<div className="text-muted-foreground/60 flex h-60 flex-col items-center justify-center gap-y-4">
<div className="flex h-60 flex-col items-center justify-center gap-y-4 text-muted-foreground/60">
<p>
<Trans>Documents that require your attention will appear here</Trans>
</p>
@@ -163,15 +154,13 @@ export const InboxTable = () => {
}}
>
{(table) =>
results.totalPages > 1 && (
<DataTablePagination additionalInformation="VisibleCount" table={table} />
)
results.totalPages > 1 && <DataTablePagination additionalInformation="VisibleCount" table={table} />
}
</DataTable>
{isPending && (
<div className="bg-background/50 absolute inset-0 flex items-center justify-center">
<Loader className="text-muted-foreground h-8 w-8 animate-spin" />
<div className="absolute inset-0 flex items-center justify-center bg-background/50">
<Loader className="h-8 w-8 animate-spin text-muted-foreground" />
</div>
)}
</div>
@@ -215,19 +204,19 @@ export const InboxTableActionButton = ({ row }: InboxTableActionButtonProps) =>
{match(role)
.with(RecipientRole.SIGNER, () => (
<>
<PencilIcon className="-ml-1 mr-2 h-4 w-4" />
<PencilIcon className="mr-2 -ml-1 h-4 w-4" />
<Trans>Sign</Trans>
</>
))
.with(RecipientRole.APPROVER, () => (
<>
<CheckCircleIcon className="-ml-1 mr-2 h-4 w-4" />
<CheckCircleIcon className="mr-2 -ml-1 h-4 w-4" />
<Trans>Approve</Trans>
</>
))
.otherwise(() => (
<>
<EyeIcon className="-ml-1 mr-2 h-4 w-4" />
<EyeIcon className="mr-2 -ml-1 h-4 w-4" />
<Trans>View</Trans>
</>
))}
@@ -236,7 +225,7 @@ export const InboxTableActionButton = ({ row }: InboxTableActionButtonProps) =>
))
.with({ isPending: true, isSigned: true }, () => (
<Button className="w-32" disabled={true}>
<EyeIcon className="-ml-1 mr-2 h-4 w-4" />
<EyeIcon className="mr-2 -ml-1 h-4 w-4" />
<Trans>View</Trans>
</Button>
))
@@ -247,7 +236,7 @@ export const InboxTableActionButton = ({ row }: InboxTableActionButtonProps) =>
token={recipient?.token}
trigger={
<Button className="w-32">
<DownloadIcon className="-ml-1 mr-2 inline h-4 w-4" />
<DownloadIcon className="mr-2 -ml-1 inline h-4 w-4" />
<Trans>Download</Trans>
</Button>
}
@@ -1,19 +1,15 @@
import { APP_I18N_OPTIONS } from '@documenso/lib/constants/i18n';
import { DOCUMENT_AUDIT_LOG_TYPE, type TDocumentAuditLog } from '@documenso/lib/types/document-audit-logs';
import { formatDocumentAuditLogAction } from '@documenso/lib/utils/document-audit-logs';
import { cn } from '@documenso/ui/lib/utils';
import { Card, CardContent } from '@documenso/ui/primitives/card';
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import type { DateTimeFormatOptions } from 'luxon';
import { DateTime } from 'luxon';
import { P, match } from 'ts-pattern';
import { match, P } from 'ts-pattern';
import { UAParser } from 'ua-parser-js';
import { APP_I18N_OPTIONS } from '@documenso/lib/constants/i18n';
import {
DOCUMENT_AUDIT_LOG_TYPE,
type TDocumentAuditLog,
} from '@documenso/lib/types/document-audit-logs';
import { formatDocumentAuditLogAction } from '@documenso/lib/utils/document-audit-logs';
import { cn } from '@documenso/ui/lib/utils';
import { Card, CardContent } from '@documenso/ui/primitives/card';
export type AuditLogDataTableProps = {
logs: TDocumentAuditLog[];
};
@@ -33,10 +29,7 @@ const getAuditLogIndicatorColor = (type: string) =>
.with(DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_RECIPIENT_REJECTED, () => 'bg-red-500')
.with(DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_SENT, () => 'bg-orange-500')
.with(
P.union(
DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_FIELD_INSERTED,
DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_FIELD_UNINSERTED,
),
P.union(DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_FIELD_INSERTED, DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_FIELD_UNINSERTED),
() => 'bg-blue-500',
)
.otherwise(() => 'bg-muted');
@@ -90,22 +83,20 @@ export const InternalAuditLogTable = ({ logs }: AuditLogDataTableProps) => {
{/* Header Section with indicator, event type, and timestamp */}
<div className="mb-3 flex items-start justify-between">
<div className="flex items-baseline gap-3">
<div
className={cn(`h-2 w-2 rounded-full`, getAuditLogIndicatorColor(log.type))}
/>
<div className={cn(`h-2 w-2 rounded-full`, getAuditLogIndicatorColor(log.type))} />
<div>
<div className="text-sm font-medium uppercase tracking-wide text-muted-foreground print:text-[8pt]">
<div className="font-medium text-muted-foreground text-sm uppercase tracking-wide print:text-[8pt]">
{log.type.replace(/_/g, ' ')}
</div>
<div className="text-sm font-medium text-foreground print:text-[8pt]">
<div className="font-medium text-foreground text-sm print:text-[8pt]">
{formattedAction.description}
</div>
</div>
</div>
<div className="text-sm text-muted-foreground print:text-[8pt]">
<div className="text-muted-foreground text-sm print:text-[8pt]">
{DateTime.fromJSDate(log.createdAt)
.setLocale(APP_I18N_OPTIONS.defaultLocale)
.toLocaleString(dateFormat)}
@@ -117,15 +108,13 @@ export const InternalAuditLogTable = ({ logs }: AuditLogDataTableProps) => {
{/* Details Section - Two column layout */}
<div className="grid grid-cols-2 gap-x-8 gap-y-2 text-xs print:text-[6pt]">
<div>
<div className="font-medium uppercase tracking-wide text-muted-foreground/70">
{_(msg`User`)}
</div>
<div className="font-medium text-muted-foreground/70 uppercase tracking-wide">{_(msg`User`)}</div>
<div className="mt-1 font-mono text-foreground">{log.email || 'N/A'}</div>
</div>
<div className="text-right">
<div className="font-medium uppercase tracking-wide text-muted-foreground/70">
<div className="font-medium text-muted-foreground/70 uppercase tracking-wide">
{_(msg`IP Address`)}
</div>
@@ -133,13 +122,11 @@ export const InternalAuditLogTable = ({ logs }: AuditLogDataTableProps) => {
</div>
<div className="col-span-2">
<div className="font-medium uppercase tracking-wide text-muted-foreground/70">
<div className="font-medium text-muted-foreground/70 uppercase tracking-wide">
{_(msg`User Agent`)}
</div>
<div className="mt-1 text-foreground">
{_(formatUserAgent(log.userAgent, userAgentInfo))}
</div>
<div className="mt-1 text-foreground">{_(formatUserAgent(log.userAgent, userAgentInfo))}</div>
</div>
</div>
</CardContent>
@@ -1,18 +1,16 @@
import { useMemo } from 'react';
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { Trans } from '@lingui/react/macro';
import { File } from 'lucide-react';
import { DateTime } from 'luxon';
import { Link } from 'react-router';
import { trpc } from '@documenso/trpc/react';
import { Button } from '@documenso/ui/primitives/button';
import type { DataTableColumnDef } from '@documenso/ui/primitives/data-table';
import { DataTable } from '@documenso/ui/primitives/data-table';
import { Skeleton } from '@documenso/ui/primitives/skeleton';
import { TableCell } from '@documenso/ui/primitives/table';
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { Trans } from '@lingui/react/macro';
import { File } from 'lucide-react';
import { DateTime } from 'luxon';
import { useMemo } from 'react';
import { Link } from 'react-router';
export type OrganisationBillingInvoicesTableProps = {
organisationId: string;
@@ -59,7 +57,7 @@ export const OrganisationBillingInvoicesTable = ({
<div className="flex max-w-xs items-center gap-2">
<File className="h-6 w-6" />
<div className="text-foreground/80 text-sm font-semibold">
<div className="font-semibold text-foreground/80 text-sm">
{DateTime.fromSeconds(row.original.created).toFormat('MMMM yyyy')}
</div>
</div>
@@ -87,21 +85,13 @@ export const OrganisationBillingInvoicesTable = ({
id: 'actions',
cell: ({ row }) => (
<div className="flex justify-end space-x-2">
<Button
variant="outline"
asChild
disabled={typeof row.original.hosted_invoice_url !== 'string'}
>
<Button variant="outline" asChild disabled={typeof row.original.hosted_invoice_url !== 'string'}>
<Link to={row.original.hosted_invoice_url ?? ''} target="_blank">
<Trans>View</Trans>
</Link>
</Button>
<Button
variant="outline"
asChild
disabled={typeof row.original.invoice_pdf !== 'string'}
>
<Button variant="outline" asChild disabled={typeof row.original.invoice_pdf !== 'string'}>
<Link to={row.original.invoice_pdf ?? ''} target="_blank">
<Trans>Download</Trans>
</Link>
@@ -1,11 +1,3 @@
import { useMemo } from 'react';
import { Trans, useLingui } from '@lingui/react/macro';
import { EmailDomainStatus } from '@prisma/client';
import { CheckCircle2Icon, ClockIcon } from 'lucide-react';
import { Link, useSearchParams } from 'react-router';
import { match } from 'ts-pattern';
import { useUpdateSearchParams } from '@documenso/lib/client-only/hooks/use-update-search-params';
import { useCurrentOrganisation } from '@documenso/lib/client-only/providers/organisation';
import { ZUrlSearchParamsSchema } from '@documenso/lib/types/search-params';
@@ -20,6 +12,12 @@ import { DataTablePagination } from '@documenso/ui/primitives/data-table-paginat
import { Skeleton } from '@documenso/ui/primitives/skeleton';
import { TableCell } from '@documenso/ui/primitives/table';
import { useToast } from '@documenso/ui/primitives/use-toast';
import { Trans, useLingui } from '@lingui/react/macro';
import { EmailDomainStatus } from '@prisma/client';
import { CheckCircle2Icon, ClockIcon } from 'lucide-react';
import { useMemo } from 'react';
import { Link, useSearchParams } from 'react-router';
import { match } from 'ts-pattern';
import { OrganisationEmailDomainDeleteDialog } from '../dialogs/organisation-email-domain-delete-dialog';
@@ -43,18 +41,17 @@ export const OrganisationEmailDomainsDataTable = () => {
},
});
const { data, isLoading, isLoadingError } =
trpc.enterprise.organisation.emailDomain.find.useQuery(
{
organisationId: organisation.id,
query: parsedSearchParams.query,
page: parsedSearchParams.page,
perPage: parsedSearchParams.perPage,
},
{
placeholderData: (previousData) => previousData,
},
);
const { data, isLoading, isLoadingError } = trpc.enterprise.organisation.emailDomain.find.useQuery(
{
organisationId: organisation.id,
query: parsedSearchParams.query,
page: parsedSearchParams.page,
perPage: parsedSearchParams.perPage,
},
{
placeholderData: (previousData) => previousData,
},
);
const onPaginationChange = (page: number, perPage: number) => {
updateSearchParams({
@@ -105,9 +102,7 @@ export const OrganisationEmailDomainsDataTable = () => {
cell: ({ row }) => (
<div className="flex justify-end space-x-2">
<Button asChild variant="outline">
<Link to={`/o/${organisation.url}/settings/email-domains/${row.original.id}`}>
Manage
</Link>
<Link to={`/o/${organisation.url}/settings/email-domains/${row.original.id}`}>Manage</Link>
</Button>
<OrganisationEmailDomainDeleteDialog
@@ -162,27 +157,20 @@ export const OrganisationEmailDomainsDataTable = () => {
}}
>
{(table) =>
results.totalPages > 1 && (
<DataTablePagination additionalInformation="VisibleCount" table={table} />
)
results.totalPages > 1 && <DataTablePagination additionalInformation="VisibleCount" table={table} />
}
</DataTable>
<AnimateGenericFadeInOut key={results.data.length}>
{results.data.length > 0 && (
<Alert
className="mt-2 flex flex-col justify-between p-6 sm:flex-row sm:items-center"
variant="neutral"
>
<Alert className="mt-2 flex flex-col justify-between p-6 sm:flex-row sm:items-center" variant="neutral">
<div className="mb-4 sm:mb-0">
<AlertTitle>
<Trans>Sync Email Domains</Trans>
</AlertTitle>
<AlertDescription className="mr-2">
<Trans>
This will check and sync the status of all email domains for this organisation
</Trans>
<Trans>This will check and sync the status of all email domains for this organisation</Trans>
</AlertDescription>
</div>
@@ -1,11 +1,3 @@
import { useMemo } from 'react';
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { Trans } from '@lingui/react/macro';
import { OrganisationGroupType } from '@prisma/client';
import { Link, useSearchParams } from 'react-router';
import { useUpdateSearchParams } from '@documenso/lib/client-only/hooks/use-update-search-params';
import { useCurrentOrganisation } from '@documenso/lib/client-only/providers/organisation';
import { EXTENDED_ORGANISATION_MEMBER_ROLE_MAP } from '@documenso/lib/constants/organisations-translations';
@@ -17,6 +9,12 @@ import { DataTable } from '@documenso/ui/primitives/data-table';
import { DataTablePagination } from '@documenso/ui/primitives/data-table-pagination';
import { Skeleton } from '@documenso/ui/primitives/skeleton';
import { TableCell } from '@documenso/ui/primitives/table';
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { Trans } from '@lingui/react/macro';
import { OrganisationGroupType } from '@prisma/client';
import { useMemo } from 'react';
import { Link, useSearchParams } from 'react-router';
import { OrganisationGroupDeleteDialog } from '../dialogs/organisation-group-delete-dialog';
@@ -141,11 +139,7 @@ export const OrganisationGroupsDataTable = () => {
),
}}
>
{(table) =>
results.totalPages > 1 && (
<DataTablePagination additionalInformation="VisibleCount" table={table} />
)
}
{(table) => results.totalPages > 1 && <DataTablePagination additionalInformation="VisibleCount" table={table} />}
</DataTable>
);
};
@@ -1,11 +1,3 @@
import { useTransition } from 'react';
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { Building2, Loader, TrendingUp, Users } from 'lucide-react';
import { Link } from 'react-router';
import { useNavigation } from 'react-router';
import { useUpdateSearchParams } from '@documenso/lib/client-only/hooks/use-update-search-params';
import type { OrganisationDetailedInsights } from '@documenso/lib/server-only/admin/get-organisation-detailed-insights';
import type { DateRange } from '@documenso/lib/types/search-params';
@@ -14,6 +6,11 @@ import { Button } from '@documenso/ui/primitives/button';
import type { DataTableColumnDef } from '@documenso/ui/primitives/data-table';
import { DataTable } from '@documenso/ui/primitives/data-table';
import { DataTablePagination } from '@documenso/ui/primitives/data-table-pagination';
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { Building2, Loader, TrendingUp, Users } from 'lucide-react';
import { useTransition } from 'react';
import { Link, useNavigation } from 'react-router';
import { DateRangeFilter } from '~/components/filters/date-range-filter';
import { DocumentStatus } from '~/components/general/document/document-status';
@@ -63,10 +60,7 @@ export const OrganisationInsightsTable = ({
header: _(msg`Team Name`),
accessorKey: 'name',
cell: ({ row }) => (
<Link
className="block max-w-full truncate hover:underline"
to={`/admin/teams/${row.original.id}`}
>
<Link className="block max-w-full truncate hover:underline" to={`/admin/teams/${row.original.id}`}>
{row.getValue('name')}
</Link>
),
@@ -97,10 +91,7 @@ export const OrganisationInsightsTable = ({
header: () => <span className="whitespace-nowrap">{_(msg`Name`)}</span>,
accessorKey: 'name',
cell: ({ row }) => (
<Link
className="block max-w-full truncate hover:underline"
to={`/admin/users/${row.original.id}`}
>
<Link className="block max-w-full truncate hover:underline" to={`/admin/users/${row.original.id}`}>
{(row.getValue('name') as string) || (row.getValue('email') as string)}
</Link>
),
@@ -150,9 +141,7 @@ export const OrganisationInsightsTable = ({
{
header: () => <span className="whitespace-nowrap">{_(msg`Status`)}</span>,
accessorKey: 'status',
cell: ({ row }) => (
<DocumentStatus status={row.getValue('status') as ExtendedDocumentStatus} />
),
cell: ({ row }) => <DocumentStatus status={row.getValue('status') as ExtendedDocumentStatus} />,
size: 120,
},
{
@@ -286,9 +275,9 @@ const SummaryCard = ({
<div className="flex items-start gap-x-2 rounded-lg border bg-card px-4 py-3">
<Icon className="h-4 w-4 items-start text-muted-foreground" />
<div className="-mt-0.5 space-y-2">
<p className="text-sm font-medium text-muted-foreground">{title}</p>
<p className="text-2xl font-bold">{value}</p>
{subtitle && <p className="text-xs text-muted-foreground">{subtitle}</p>}
<p className="font-medium text-muted-foreground text-sm">{title}</p>
<p className="font-bold text-2xl">{value}</p>
{subtitle && <p className="text-muted-foreground text-xs">{subtitle}</p>}
</div>
</div>
);
@@ -1,12 +1,3 @@
import { useMemo } from 'react';
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { Trans } from '@lingui/react/macro';
import { OrganisationMemberInviteStatus } from '@prisma/client';
import { History, MoreHorizontal, Trash2 } from 'lucide-react';
import { useSearchParams } from 'react-router';
import { useUpdateSearchParams } from '@documenso/lib/client-only/hooks/use-update-search-params';
import { useCurrentOrganisation } from '@documenso/lib/client-only/providers/organisation';
import { ORGANISATION_MEMBER_ROLE_MAP } from '@documenso/lib/constants/organisations-translations';
@@ -27,6 +18,13 @@ import {
import { Skeleton } from '@documenso/ui/primitives/skeleton';
import { TableCell } from '@documenso/ui/primitives/table';
import { useToast } from '@documenso/ui/primitives/use-toast';
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { Trans } from '@lingui/react/macro';
import { OrganisationMemberInviteStatus } from '@prisma/client';
import { History, MoreHorizontal, Trash2 } from 'lucide-react';
import { useMemo } from 'react';
import { useSearchParams } from 'react-router';
export const OrganisationMemberInvitesTable = () => {
const [searchParams] = useSearchParams();
@@ -51,39 +49,37 @@ export const OrganisationMemberInvitesTable = () => {
},
);
const { mutateAsync: resendOrganisationMemberInvitation } =
trpc.organisation.member.invite.resend.useMutation({
onSuccess: () => {
toast({
title: _(msg`Success`),
description: _(msg`Invitation has been resent`),
});
},
onError: () => {
toast({
title: _(msg`Something went wrong`),
description: _(msg`Unable to resend invitation. Please try again.`),
variant: 'destructive',
});
},
});
const { mutateAsync: resendOrganisationMemberInvitation } = trpc.organisation.member.invite.resend.useMutation({
onSuccess: () => {
toast({
title: _(msg`Success`),
description: _(msg`Invitation has been resent`),
});
},
onError: () => {
toast({
title: _(msg`Something went wrong`),
description: _(msg`Unable to resend invitation. Please try again.`),
variant: 'destructive',
});
},
});
const { mutateAsync: deleteOrganisationMemberInvitations } =
trpc.organisation.member.invite.deleteMany.useMutation({
onSuccess: () => {
toast({
title: _(msg`Success`),
description: _(msg`Invitation has been deleted`),
});
},
onError: () => {
toast({
title: _(msg`Something went wrong`),
description: _(msg`Unable to delete invitation. Please try again.`),
variant: 'destructive',
});
},
});
const { mutateAsync: deleteOrganisationMemberInvitations } = trpc.organisation.member.invite.deleteMany.useMutation({
onSuccess: () => {
toast({
title: _(msg`Success`),
description: _(msg`Invitation has been deleted`),
});
},
onError: () => {
toast({
title: _(msg`Something went wrong`),
description: _(msg`Unable to delete invitation. Please try again.`),
variant: 'destructive',
});
},
});
const onPaginationChange = (page: number, perPage: number) => {
updateSearchParams({
@@ -108,9 +104,7 @@ export const OrganisationMemberInvitesTable = () => {
<AvatarWithText
avatarClass="h-12 w-12"
avatarFallback={row.original.email.slice(0, 1).toUpperCase()}
primaryText={
<span className="font-semibold text-foreground/80">{row.original.email}</span>
}
primaryText={<span className="font-semibold text-foreground/80">{row.original.email}</span>}
/>
);
},
@@ -208,11 +202,7 @@ export const OrganisationMemberInvitesTable = () => {
),
}}
>
{(table) =>
results.totalPages > 1 && (
<DataTablePagination additionalInformation="VisibleCount" table={table} />
)
}
{(table) => results.totalPages > 1 && <DataTablePagination additionalInformation="VisibleCount" table={table} />}
</DataTable>
);
};
@@ -1,12 +1,3 @@
import { useMemo } from 'react';
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { Trans } from '@lingui/react/macro';
import { OrganisationGroupType } from '@prisma/client';
import { Edit, MoreHorizontal, Trash2 } from 'lucide-react';
import { useSearchParams } from 'react-router';
import { useUpdateSearchParams } from '@documenso/lib/client-only/hooks/use-update-search-params';
import { useCurrentOrganisation } from '@documenso/lib/client-only/providers/organisation';
import { EXTENDED_ORGANISATION_MEMBER_ROLE_MAP } from '@documenso/lib/constants/organisations-translations';
@@ -27,6 +18,13 @@ import {
} from '@documenso/ui/primitives/dropdown-menu';
import { Skeleton } from '@documenso/ui/primitives/skeleton';
import { TableCell } from '@documenso/ui/primitives/table';
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { Trans } from '@lingui/react/macro';
import { OrganisationGroupType } from '@prisma/client';
import { Edit, MoreHorizontal, Trash2 } from 'lucide-react';
import { useMemo } from 'react';
import { useSearchParams } from 'react-router';
import { OrganisationMemberDeleteDialog } from '~/components/dialogs/organisation-member-delete-dialog';
import { OrganisationMemberUpdateDialog } from '~/components/dialogs/organisation-member-update-dialog';
@@ -79,9 +77,7 @@ export const OrganisationMembersDataTable = () => {
<AvatarWithText
avatarClass="h-12 w-12"
avatarFallback={avatarFallbackText}
primaryText={
<span className="text-foreground/80 font-semibold">{row.original.name}</span>
}
primaryText={<span className="font-semibold text-foreground/80">{row.original.name}</span>}
secondaryText={row.original.email}
/>
);
@@ -102,15 +98,14 @@ export const OrganisationMembersDataTable = () => {
},
{
header: _(msg`Groups`),
cell: ({ row }) =>
row.original.groups.filter((group) => group.type === OrganisationGroupType.CUSTOM).length,
cell: ({ row }) => row.original.groups.filter((group) => group.type === OrganisationGroupType.CUSTOM).length,
},
{
header: _(msg`Actions`),
cell: ({ row }) => (
<DropdownMenu>
<DropdownMenuTrigger>
<MoreHorizontal className="text-muted-foreground h-5 w-5" />
<MoreHorizontal className="h-5 w-5 text-muted-foreground" />
</DropdownMenuTrigger>
<DropdownMenuContent className="w-52" align="start" forceMount>
@@ -209,11 +204,7 @@ export const OrganisationMembersDataTable = () => {
),
}}
>
{(table) =>
results.totalPages > 1 && (
<DataTablePagination additionalInformation="VisibleCount" table={table} />
)
}
{(table) => results.totalPages > 1 && <DataTablePagination additionalInformation="VisibleCount" table={table} />}
</DataTable>
);
};
@@ -1,11 +1,3 @@
import { useMemo } from 'react';
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { Trans } from '@lingui/react/macro';
import { useSearchParams } from 'react-router';
import { Link } from 'react-router';
import { useUpdateSearchParams } from '@documenso/lib/client-only/hooks/use-update-search-params';
import { useCurrentOrganisation } from '@documenso/lib/client-only/providers/organisation';
import { NEXT_PUBLIC_WEBAPP_URL } from '@documenso/lib/constants/app';
@@ -19,6 +11,11 @@ import { DataTable } from '@documenso/ui/primitives/data-table';
import { DataTablePagination } from '@documenso/ui/primitives/data-table-pagination';
import { Skeleton } from '@documenso/ui/primitives/skeleton';
import { TableCell } from '@documenso/ui/primitives/table';
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { Trans } from '@lingui/react/macro';
import { useMemo } from 'react';
import { Link, useSearchParams } from 'react-router';
import { TeamDeleteDialog } from '../dialogs/team-delete-dialog';
@@ -63,9 +60,7 @@ export const OrganisationTeamsTable = () => {
avatarSrc={formatAvatarUrl(row.original.avatarImageId)}
avatarClass="h-12 w-12"
avatarFallback={row.original.name.slice(0, 1).toUpperCase()}
primaryText={
<span className="font-semibold text-foreground/80">{row.original.name}</span>
}
primaryText={<span className="font-semibold text-foreground/80">{row.original.name}</span>}
secondaryText={`${NEXT_PUBLIC_WEBAPP_URL()}/t/${row.original.url}`}
/>
</Link>
@@ -143,11 +138,7 @@ export const OrganisationTeamsTable = () => {
),
}}
>
{(table) =>
results.totalPages > 1 && (
<DataTablePagination additionalInformation="VisibleCount" table={table} />
)
}
{(table) => results.totalPages > 1 && <DataTablePagination additionalInformation="VisibleCount" table={table} />}
</DataTable>
);
};
@@ -1,11 +1,3 @@
import { useMemo, useState } from 'react';
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { Trans } from '@lingui/react/macro';
import { type TemplateDirectLink, TemplateType } from '@prisma/client';
import { EditIcon, FileIcon, LinkIcon, MoreHorizontalIcon, Trash2Icon } from 'lucide-react';
import { useCopyToClipboard } from '@documenso/lib/client-only/hooks/use-copy-to-clipboard';
import { formatDirectTemplatePath } from '@documenso/lib/utils/templates';
import { trpc } from '@documenso/trpc/react';
@@ -19,6 +11,12 @@ import {
} from '@documenso/ui/primitives/dropdown-menu';
import { Skeleton } from '@documenso/ui/primitives/skeleton';
import { useToast } from '@documenso/ui/primitives/use-toast';
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { Trans } from '@lingui/react/macro';
import { type TemplateDirectLink, TemplateType } from '@prisma/client';
import { EditIcon, FileIcon, LinkIcon, MoreHorizontalIcon, Trash2Icon } from 'lucide-react';
import { useMemo, useState } from 'react';
import { ManagePublicTemplateDialog } from '~/components/dialogs/public-profile-template-manage-dialog';
@@ -82,10 +80,7 @@ export const SettingsPublicProfileTemplatesTable = () => {
Array(3)
.fill(0)
.map((_, index) => (
<div
key={index}
className="flex items-center justify-between gap-x-6 bg-background p-4"
>
<div key={index} className="flex items-center justify-between gap-x-6 bg-background p-4">
<div className="flex gap-x-2">
<FileIcon className="h-8 w-8 text-muted-foreground/40" strokeWidth={1.5} />
@@ -100,7 +95,7 @@ export const SettingsPublicProfileTemplatesTable = () => {
))}
{isLoadingError && (
<div className="flex h-32 flex-col items-center justify-center text-sm text-muted-foreground">
<div className="flex h-32 flex-col items-center justify-center text-muted-foreground text-sm">
<Trans>Unable to load your public profile templates at this time</Trans>
<button
onClick={(e) => {
@@ -114,7 +109,7 @@ export const SettingsPublicProfileTemplatesTable = () => {
)}
{!isLoading && (
<div className="flex h-32 flex-col items-center justify-center text-sm text-muted-foreground">
<div className="flex h-32 flex-col items-center justify-center text-muted-foreground text-sm">
<Trans>No public profile templates found</Trans>
<ManagePublicTemplateDialog
directTemplates={privateDirectTemplates}
@@ -131,19 +126,13 @@ export const SettingsPublicProfileTemplatesTable = () => {
{/* Public templates list. */}
{publicDirectTemplates.map((template) => (
<div
key={template.id}
className="flex items-center justify-between gap-x-6 bg-background p-4"
>
<div key={template.id} className="flex items-center justify-between gap-x-6 bg-background p-4">
<div className="flex gap-x-2">
<FileIcon
className="h-8 w-8 flex-shrink-0 text-muted-foreground/40"
strokeWidth={1.5}
/>
<FileIcon className="h-8 w-8 flex-shrink-0 text-muted-foreground/40" strokeWidth={1.5} />
<div>
<p className="break-all text-sm">{template.publicTitle}</p>
<p className="break-all text-xs text-neutral-400">{template.publicDescription}</p>
<p className="break-all text-neutral-400 text-xs">{template.publicDescription}</p>
</div>
</div>
@@ -1,12 +1,3 @@
import { useMemo } from 'react';
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import type { DateTimeFormatOptions } from 'luxon';
import { DateTime } from 'luxon';
import { useLocation, useNavigate, useSearchParams } from 'react-router';
import { UAParser } from 'ua-parser-js';
import { useUpdateSearchParams } from '@documenso/lib/client-only/hooks/use-update-search-params';
import { USER_SECURITY_AUDIT_LOG_MAP } from '@documenso/lib/constants/auth';
import { ZUrlSearchParamsSchema } from '@documenso/lib/types/search-params';
@@ -16,6 +7,13 @@ import { DataTable } from '@documenso/ui/primitives/data-table';
import { DataTablePagination } from '@documenso/ui/primitives/data-table-pagination';
import { Skeleton } from '@documenso/ui/primitives/skeleton';
import { TableCell } from '@documenso/ui/primitives/table';
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import type { DateTimeFormatOptions } from 'luxon';
import { DateTime } from 'luxon';
import { useMemo } from 'react';
import { useLocation, useNavigate, useSearchParams } from 'react-router';
import { UAParser } from 'ua-parser-js';
const dateFormat: DateTimeFormatOptions = {
...DateTime.DATETIME_SHORT,
@@ -1,12 +1,3 @@
import { useState } from 'react';
import { zodResolver } from '@hookform/resolvers/zod';
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { Trans } from '@lingui/react/macro';
import { useForm } from 'react-hook-form';
import { z } from 'zod';
import { trpc } from '@documenso/trpc/react';
import { cn } from '@documenso/ui/lib/utils';
import { Button } from '@documenso/ui/primitives/button';
@@ -20,16 +11,16 @@ import {
DialogTitle,
DialogTrigger,
} from '@documenso/ui/primitives/dialog';
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from '@documenso/ui/primitives/form/form';
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from '@documenso/ui/primitives/form/form';
import { Input } from '@documenso/ui/primitives/input';
import { useToast } from '@documenso/ui/primitives/use-toast';
import { zodResolver } from '@hookform/resolvers/zod';
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { Trans } from '@lingui/react/macro';
import { useState } from 'react';
import { useForm } from 'react-hook-form';
import { z } from 'zod';
export type SettingsSecurityPasskeyTableActionsProps = {
className?: string;
@@ -61,56 +52,47 @@ export const SettingsSecurityPasskeyTableActions = ({
},
});
const { mutateAsync: updatePasskey, isPending: isUpdatingPasskey } =
trpc.auth.passkey.update.useMutation({
onSuccess: () => {
toast({
title: _(msg`Success`),
description: _(msg`Passkey has been updated`),
});
const { mutateAsync: updatePasskey, isPending: isUpdatingPasskey } = trpc.auth.passkey.update.useMutation({
onSuccess: () => {
toast({
title: _(msg`Success`),
description: _(msg`Passkey has been updated`),
});
setIsUpdateDialogOpen(false);
},
onError: () => {
toast({
title: _(msg`Something went wrong`),
description: _(
msg`We are unable to update this passkey at the moment. Please try again later.`,
),
duration: 10000,
variant: 'destructive',
});
},
});
setIsUpdateDialogOpen(false);
},
onError: () => {
toast({
title: _(msg`Something went wrong`),
description: _(msg`We are unable to update this passkey at the moment. Please try again later.`),
duration: 10000,
variant: 'destructive',
});
},
});
const { mutateAsync: deletePasskey, isPending: isDeletingPasskey } =
trpc.auth.passkey.delete.useMutation({
onSuccess: () => {
toast({
title: _(msg`Success`),
description: _(msg`Passkey has been removed`),
});
const { mutateAsync: deletePasskey, isPending: isDeletingPasskey } = trpc.auth.passkey.delete.useMutation({
onSuccess: () => {
toast({
title: _(msg`Success`),
description: _(msg`Passkey has been removed`),
});
setIsDeleteDialogOpen(false);
},
onError: () => {
toast({
title: _(msg`Something went wrong`),
description: _(
msg`We are unable to remove this passkey at the moment. Please try again later.`,
),
duration: 10000,
variant: 'destructive',
});
},
});
setIsDeleteDialogOpen(false);
},
onError: () => {
toast({
title: _(msg`Something went wrong`),
description: _(msg`We are unable to remove this passkey at the moment. Please try again later.`),
duration: 10000,
variant: 'destructive',
});
},
});
return (
<div className={cn('flex justify-end space-x-2', className)}>
<Dialog
open={isUpdateDialogOpen}
onOpenChange={(value) => !isUpdatingPasskey && setIsUpdateDialogOpen(value)}
>
<Dialog open={isUpdateDialogOpen} onOpenChange={(value) => !isUpdatingPasskey && setIsUpdateDialogOpen(value)}>
<DialogTrigger onClick={(e) => e.stopPropagation()} asChild>
<Button variant="outline">
<Trans>Edit</Trans>
@@ -173,10 +155,7 @@ export const SettingsSecurityPasskeyTableActions = ({
</DialogContent>
</Dialog>
<Dialog
open={isDeleteDialogOpen}
onOpenChange={(value) => !isDeletingPasskey && setIsDeleteDialogOpen(value)}
>
<Dialog open={isDeleteDialogOpen} onOpenChange={(value) => !isDeletingPasskey && setIsDeleteDialogOpen(value)}>
<DialogTrigger onClick={(e) => e.stopPropagation()} asChild={true}>
<Button variant="destructive">
<Trans>Delete</Trans>
@@ -1,10 +1,3 @@
import { useMemo } from 'react';
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { DateTime } from 'luxon';
import { useLocation, useNavigate, useSearchParams } from 'react-router';
import { useUpdateSearchParams } from '@documenso/lib/client-only/hooks/use-update-search-params';
import { ZUrlSearchParamsSchema } from '@documenso/lib/types/search-params';
import { trpc } from '@documenso/trpc/react';
@@ -13,6 +6,11 @@ import { DataTable } from '@documenso/ui/primitives/data-table';
import { DataTablePagination } from '@documenso/ui/primitives/data-table-pagination';
import { Skeleton } from '@documenso/ui/primitives/skeleton';
import { TableCell } from '@documenso/ui/primitives/table';
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { DateTime } from 'luxon';
import { useMemo } from 'react';
import { useLocation, useNavigate, useSearchParams } from 'react-router';
import { SettingsSecurityPasskeyTableActions } from './settings-security-passkey-table-actions';
@@ -66,9 +64,7 @@ export const SettingsSecurityPasskeyTable = () => {
header: _(msg`Last used`),
accessorKey: 'updatedAt',
cell: ({ row }) =>
row.original.lastUsedAt
? DateTime.fromJSDate(row.original.lastUsedAt).toRelative()
: _(msg`Never`),
row.original.lastUsedAt ? DateTime.fromJSDate(row.original.lastUsedAt).toRelative() : _(msg`Never`),
},
{
id: 'actions',
@@ -1,12 +1,3 @@
import { useMemo } from 'react';
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { Trans } from '@lingui/react/macro';
import { OrganisationGroupType } from '@prisma/client';
import { EditIcon, MoreHorizontalIcon, Trash2Icon } from 'lucide-react';
import { useSearchParams } from 'react-router';
import { useUpdateSearchParams } from '@documenso/lib/client-only/hooks/use-update-search-params';
import { EXTENDED_TEAM_MEMBER_ROLE_MAP } from '@documenso/lib/constants/teams-translations';
import { ZUrlSearchParamsSchema } from '@documenso/lib/types/search-params';
@@ -23,6 +14,13 @@ import {
} from '@documenso/ui/primitives/dropdown-menu';
import { Skeleton } from '@documenso/ui/primitives/skeleton';
import { TableCell } from '@documenso/ui/primitives/table';
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { Trans } from '@lingui/react/macro';
import { OrganisationGroupType } from '@prisma/client';
import { EditIcon, MoreHorizontalIcon, Trash2Icon } from 'lucide-react';
import { useMemo } from 'react';
import { useSearchParams } from 'react-router';
import { useCurrentTeam } from '~/providers/team';
@@ -86,7 +84,7 @@ export const TeamGroupsTable = () => {
cell: ({ row }) => (
<DropdownMenu>
<DropdownMenuTrigger>
<MoreHorizontalIcon className="text-muted-foreground h-5 w-5" />
<MoreHorizontalIcon className="h-5 w-5 text-muted-foreground" />
</DropdownMenuTrigger>
<DropdownMenuContent className="w-52" align="start" forceMount>
@@ -99,10 +97,7 @@ export const TeamGroupsTable = () => {
teamGroupName={row.original.name ?? ''}
teamGroupRole={row.original.teamRole}
trigger={
<DropdownMenuItem
onSelect={(e) => e.preventDefault()}
title="Update team group role"
>
<DropdownMenuItem onSelect={(e) => e.preventDefault()} title="Update team group role">
<EditIcon className="mr-2 h-4 w-4" />
<Trans>Update role</Trans>
</DropdownMenuItem>
@@ -139,7 +134,7 @@ export const TeamGroupsTable = () => {
enable: isLoadingError,
}}
emptyState={
<div className="text-muted-foreground/60 flex h-60 flex-col items-center justify-center gap-y-4">
<div className="flex h-60 flex-col items-center justify-center gap-y-4 text-muted-foreground/60">
<p>
<Trans>No team groups found</Trans>
</p>
@@ -173,11 +168,7 @@ export const TeamGroupsTable = () => {
),
}}
>
{(table) =>
results.totalPages > 1 && (
<DataTablePagination additionalInformation="VisibleCount" table={table} />
)
}
{(table) => results.totalPages > 1 && <DataTablePagination additionalInformation="VisibleCount" table={table} />}
</DataTable>
);
};
@@ -1,12 +1,3 @@
import { useMemo } from 'react';
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { Trans } from '@lingui/react/macro';
import { OrganisationGroupType, OrganisationMemberRole } from '@prisma/client';
import { EditIcon, MoreHorizontal, Trash2Icon } from 'lucide-react';
import { useSearchParams } from 'react-router';
import { useUpdateSearchParams } from '@documenso/lib/client-only/hooks/use-update-search-params';
import { useCurrentOrganisation } from '@documenso/lib/client-only/providers/organisation';
import { EXTENDED_TEAM_MEMBER_ROLE_MAP } from '@documenso/lib/constants/teams-translations';
@@ -28,6 +19,13 @@ import {
} from '@documenso/ui/primitives/dropdown-menu';
import { Skeleton } from '@documenso/ui/primitives/skeleton';
import { TableCell } from '@documenso/ui/primitives/table';
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { Trans } from '@lingui/react/macro';
import { OrganisationGroupType, OrganisationMemberRole } from '@prisma/client';
import { EditIcon, MoreHorizontal, Trash2Icon } from 'lucide-react';
import { useMemo } from 'react';
import { useSearchParams } from 'react-router';
import { useCurrentTeam } from '~/providers/team';
@@ -100,9 +98,7 @@ export const TeamMembersTable = () => {
<AvatarWithText
avatarClass="h-12 w-12"
avatarFallback={avatarFallbackText}
primaryText={
<span className="text-foreground/80 font-semibold">{row.original.name}</span>
}
primaryText={<span className="font-semibold text-foreground/80">{row.original.name}</span>}
secondaryText={row.original.email}
/>
);
@@ -130,7 +126,7 @@ export const TeamMembersTable = () => {
cell: ({ row }) => (
<DropdownMenu>
<DropdownMenuTrigger>
<MoreHorizontal className="text-muted-foreground h-5 w-5" />
<MoreHorizontal className="h-5 w-5 text-muted-foreground" />
</DropdownMenuTrigger>
<DropdownMenuContent className="w-52" align="start" forceMount>
@@ -231,9 +227,7 @@ export const TeamMembersTable = () => {
</DataTable>
<AnimateGenericFadeInOut key={groupQuery.isPending ? 'pending' : 'fetched'}>
{!groupQuery.isPending && (
<TeamInheritMemberAlert memberAccessTeamGroup={memberAccessTeamGroup || null} />
)}
{!groupQuery.isPending && <TeamInheritMemberAlert memberAccessTeamGroup={memberAccessTeamGroup || null} />}
</AnimateGenericFadeInOut>
</div>
);
@@ -1,24 +1,3 @@
import { useState } from 'react';
import { Trans } from '@lingui/react/macro';
import {
DocumentStatus,
EnvelopeType,
type Recipient,
type TemplateDirectLink,
} from '@prisma/client';
import {
Copy,
Edit,
FolderIcon,
MoreHorizontal,
Pencil,
Share2Icon,
Trash2,
Upload,
} from 'lucide-react';
import { Link } from 'react-router';
import type { TRecipientLite } from '@documenso/lib/types/recipient';
import { trpc as trpcReact } from '@documenso/trpc/react';
import {
@@ -28,8 +7,14 @@ import {
DropdownMenuLabel,
DropdownMenuTrigger,
} from '@documenso/ui/primitives/dropdown-menu';
import { Trans } from '@lingui/react/macro';
import { DocumentStatus, EnvelopeType, type TemplateDirectLink } from '@prisma/client';
import { Copy, Download, Edit, FolderIcon, MoreHorizontal, Pencil, Share2Icon, Trash2, Upload } from 'lucide-react';
import { useState } from 'react';
import { Link } from 'react-router';
import { EnvelopeDeleteDialog } from '../dialogs/envelope-delete-dialog';
import { EnvelopeDownloadDialog } from '../dialogs/envelope-download-dialog';
import { EnvelopeDuplicateDialog } from '../dialogs/envelope-duplicate-dialog';
import { EnvelopeRenameDialog } from '../dialogs/envelope-rename-dialog';
import { TemplateBulkSendDialog } from '../dialogs/template-bulk-send-dialog';
@@ -77,87 +62,94 @@ export const TemplatesTableActionDropdown = ({
<DropdownMenuContent className="w-52" align="start" forceMount>
<DropdownMenuLabel>Action</DropdownMenuLabel>
<DropdownMenuItem disabled={!canMutate} asChild>
<Link to={formatPath}>
<Edit className="mr-2 h-4 w-4" />
<Trans>Edit</Trans>
</Link>
</DropdownMenuItem>
{canMutate && (
<DropdownMenuItem onClick={() => setRenameDialogOpen(true)}>
<Pencil className="mr-2 h-4 w-4" />
<Trans>Rename</Trans>
</DropdownMenuItem>
)}
{canMutate && (
<EnvelopeDuplicateDialog
envelopeId={row.envelopeId}
envelopeType={EnvelopeType.TEMPLATE}
trigger={
<DropdownMenuItem asChild onSelect={(e) => e.preventDefault()}>
<div>
<Copy className="mr-2 h-4 w-4" />
<Trans>Duplicate</Trans>
</div>
</DropdownMenuItem>
}
/>
)}
{canMutate && (
<TemplateDirectLinkDialog
templateId={row.id}
recipients={row.recipients}
directLink={row.directLink}
trigger={
<div
data-testid="template-direct-link"
className="relative flex cursor-pointer select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors hover:bg-accent hover:text-accent-foreground"
>
<Share2Icon className="mr-2 h-4 w-4" />
<Trans>Direct link</Trans>
<EnvelopeDownloadDialog
envelopeId={row.envelopeId}
envelopeStatus={DocumentStatus.DRAFT}
trigger={
<DropdownMenuItem asChild onSelect={(e) => e.preventDefault()}>
<div>
<Download className="mr-2 h-4 w-4" />
<Trans>Download</Trans>
</div>
}
/>
)}
<DropdownMenuItem disabled={!canMutate} onClick={() => setMoveToFolderDialogOpen(true)}>
<FolderIcon className="mr-2 h-4 w-4" />
<Trans>Move to Folder</Trans>
</DropdownMenuItem>
</DropdownMenuItem>
}
/>
{canMutate && (
<TemplateBulkSendDialog
templateId={row.id}
recipients={row.recipients}
trigger={
<div className="relative flex cursor-pointer select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors hover:bg-accent hover:text-accent-foreground">
<Upload className="mr-2 h-4 w-4" />
<Trans>Bulk Send via CSV</Trans>
</div>
}
/>
)}
<>
<DropdownMenuItem asChild>
<Link to={formatPath}>
<Edit className="mr-2 h-4 w-4" />
<Trans>Edit</Trans>
</Link>
</DropdownMenuItem>
{canMutate && (
<EnvelopeDeleteDialog
id={row.envelopeId}
type={EnvelopeType.TEMPLATE}
status={DocumentStatus.DRAFT}
title={row.title}
canManageDocument={canMutate}
onDelete={onDelete}
trigger={
<DropdownMenuItem asChild onSelect={(e) => e.preventDefault()}>
<div>
<Trash2 className="mr-2 h-4 w-4" />
<Trans>Delete</Trans>
<DropdownMenuItem onClick={() => setRenameDialogOpen(true)}>
<Pencil className="mr-2 h-4 w-4" />
<Trans>Rename</Trans>
</DropdownMenuItem>
<EnvelopeDuplicateDialog
envelopeId={row.envelopeId}
envelopeType={EnvelopeType.TEMPLATE}
trigger={
<DropdownMenuItem asChild onSelect={(e) => e.preventDefault()}>
<div>
<Copy className="mr-2 h-4 w-4" />
<Trans>Duplicate</Trans>
</div>
</DropdownMenuItem>
}
/>
<TemplateDirectLinkDialog
templateId={row.id}
recipients={row.recipients}
directLink={row.directLink}
trigger={
<div
data-testid="template-direct-link"
className="relative flex cursor-pointer select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors hover:bg-accent hover:text-accent-foreground"
>
<Share2Icon className="mr-2 h-4 w-4" />
<Trans>Direct link</Trans>
</div>
</DropdownMenuItem>
}
/>
}
/>
<DropdownMenuItem onClick={() => setMoveToFolderDialogOpen(true)}>
<FolderIcon className="mr-2 h-4 w-4" />
<Trans>Move to Folder</Trans>
</DropdownMenuItem>
<TemplateBulkSendDialog
templateId={row.id}
recipients={row.recipients}
trigger={
<div className="relative flex cursor-pointer select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors hover:bg-accent hover:text-accent-foreground">
<Upload className="mr-2 h-4 w-4" />
<Trans>Bulk Send via CSV</Trans>
</div>
}
/>
<EnvelopeDeleteDialog
id={row.envelopeId}
type={EnvelopeType.TEMPLATE}
status={DocumentStatus.DRAFT}
title={row.title}
canManageDocument={canMutate}
onDelete={onDelete}
trigger={
<DropdownMenuItem asChild onSelect={(e) => e.preventDefault()}>
<div>
<Trash2 className="mr-2 h-4 w-4" />
<Trans>Delete</Trans>
</div>
</DropdownMenuItem>
}
/>
</>
)}
</DropdownMenuContent>
@@ -1,19 +1,3 @@
import { useMemo, useTransition } from 'react';
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { Trans } from '@lingui/react/macro';
import {
AlertTriangle,
Building2Icon,
Globe2Icon,
InfoIcon,
Link2Icon,
Loader,
LockIcon,
} from 'lucide-react';
import { Link } from 'react-router';
import { useLimits } from '@documenso/ee/server-only/limits/provider/client';
import { useUpdateSearchParams } from '@documenso/lib/client-only/hooks/use-update-search-params';
import { useCurrentOrganisation } from '@documenso/lib/client-only/providers/organisation';
@@ -27,6 +11,12 @@ import { DataTablePagination } from '@documenso/ui/primitives/data-table-paginat
import { Skeleton } from '@documenso/ui/primitives/skeleton';
import { TableCell } from '@documenso/ui/primitives/table';
import { Tooltip, TooltipContent, TooltipTrigger } from '@documenso/ui/primitives/tooltip';
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { Trans } from '@lingui/react/macro';
import { AlertTriangle, Building2Icon, Globe2Icon, InfoIcon, Link2Icon, Loader, LockIcon } from 'lucide-react';
import { useMemo, useTransition } from 'react';
import { Link } from 'react-router';
import { TemplateType } from '~/components/general/template/template-type';
import { useCurrentTeam } from '~/providers/team';
@@ -128,7 +118,7 @@ export const TemplatesTable = ({
<InfoIcon className="mx-2 h-4 w-4" />
</TooltipTrigger>
<TooltipContent className="max-w-md space-y-2 !p-0 text-foreground">
<TooltipContent className="!p-0 max-w-md space-y-2 text-foreground">
<ul className="space-y-0.5 divide-y text-muted-foreground [&>li]:p-4">
<li>
<h2 className="mb-2 flex flex-row items-center font-semibold">
@@ -138,8 +128,8 @@ export const TemplatesTable = ({
<p>
<Trans>
Public templates are connected to your public profile. Any modifications to
public templates will also appear in your public profile.
Public templates are connected to your public profile. Any modifications to public templates
will also appear in your public profile.
</Trans>
</p>
</li>
@@ -151,9 +141,8 @@ export const TemplatesTable = ({
<p>
<Trans>
Direct link templates contain one dynamic recipient placeholder. Anyone with
access to this link can sign the document, and it will then appear on your
documents page.
Direct link templates contain one dynamic recipient placeholder. Anyone with access to this link
can sign the document, and it will then appear on your documents page.
</Trans>
</p>
</li>
@@ -165,10 +154,7 @@ export const TemplatesTable = ({
<p>
{team?.id ? (
<Trans>
Team only templates are not linked anywhere and are visible only to your
team.
</Trans>
<Trans>Team only templates are not linked anywhere and are visible only to your team.</Trans>
) : (
<Trans>Private templates can only be modified and viewed by you.</Trans>
)}
@@ -182,8 +168,8 @@ export const TemplatesTable = ({
<p>
<Trans>
Organisation templates are shared across all teams within the same
organisation. Only the owning team can edit them.
Organisation templates are shared across all teams within the same organisation. Only the owning
team can edit them.
</Trans>
</p>
</li>
@@ -201,9 +187,7 @@ export const TemplatesTable = ({
<TemplateType type={row.original.type} />
{isFromOtherTeam && row.original.team?.name && (
<span className="ml-2 text-xs text-muted-foreground">
({row.original.team.name})
</span>
<span className="ml-2 text-muted-foreground text-xs">({row.original.team.name})</span>
)}
{row.original.directLink?.token && (
@@ -232,11 +216,7 @@ export const TemplatesTable = ({
documentRootPath={documentRootPath}
/>
<TemplatesTableActionDropdown
row={row.original}
teamId={team?.id}
templateRootPath={templateRootPath}
/>
<TemplatesTableActionDropdown row={row.original} teamId={team?.id} templateRootPath={templateRootPath} />
</div>
);
},
@@ -273,10 +253,7 @@ export const TemplatesTable = ({
<AlertDescription className="mt-2">
<Trans>
You have reached your document limit.{' '}
<Link
className="underline underline-offset-4"
to={`/o/${organisation.url}/settings/billing`}
>
<Link className="underline underline-offset-4" to={`/o/${organisation.url}/settings/billing`}>
Upgrade your account to continue!
</Link>
</Trans>
@@ -1,10 +1,3 @@
import { useMemo } from 'react';
import { Trans, useLingui } from '@lingui/react/macro';
import { SubscriptionStatus } from '@prisma/client';
import { Link } from 'react-router';
import { match } from 'ts-pattern';
import { useSession } from '@documenso/lib/client-only/providers/session';
import { NEXT_PUBLIC_WEBAPP_URL } from '@documenso/lib/constants/app';
import { formatAvatarUrl } from '@documenso/lib/utils/avatars';
@@ -14,15 +7,18 @@ import { Badge } from '@documenso/ui/primitives/badge';
import { Button } from '@documenso/ui/primitives/button';
import type { DataTableColumnDef } from '@documenso/ui/primitives/data-table';
import { DataTable } from '@documenso/ui/primitives/data-table';
import { Trans, useLingui } from '@lingui/react/macro';
import { SubscriptionStatus } from '@prisma/client';
import { useMemo } from 'react';
import { Link } from 'react-router';
import { match } from 'ts-pattern';
export const UserBillingOrganisationsTable = () => {
const { t } = useLingui();
const { organisations } = useSession();
const billingOrganisations = useMemo(() => {
return organisations.filter((org) =>
canExecuteOrganisationAction('MANAGE_BILLING', org.currentOrganisationRole),
);
return organisations.filter((org) => canExecuteOrganisationAction('MANAGE_BILLING', org.currentOrganisationRole));
}, [organisations]);
const getSubscriptionStatusDisplay = (status: SubscriptionStatus | undefined) => {
@@ -56,9 +52,7 @@ export const UserBillingOrganisationsTable = () => {
avatarSrc={formatAvatarUrl(row.original.avatarImageId)}
avatarClass="h-12 w-12"
avatarFallback={row.original.name.slice(0, 1).toUpperCase()}
primaryText={
<span className="text-foreground/80 font-semibold">{row.original.name}</span>
}
primaryText={<span className="font-semibold text-foreground/80">{row.original.name}</span>}
secondaryText={`${NEXT_PUBLIC_WEBAPP_URL()}/o/${row.original.url}`}
/>
</Link>
@@ -91,7 +85,7 @@ export const UserBillingOrganisationsTable = () => {
if (billingOrganisations.length === 0) {
return (
<div className="text-muted-foreground flex flex-col items-center justify-center rounded-lg border border-dashed py-12 text-center">
<div className="flex flex-col items-center justify-center rounded-lg border border-dashed py-12 text-center text-muted-foreground">
<p className="text-sm">
<Trans>You don't manage billing for any organisations.</Trans>
</p>
@@ -1,10 +1,3 @@
import { useMemo } from 'react';
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { Trans } from '@lingui/react/macro';
import { Link } from 'react-router';
import { useSession } from '@documenso/lib/client-only/providers/session';
import { NEXT_PUBLIC_WEBAPP_URL } from '@documenso/lib/constants/app';
import { ORGANISATION_MEMBER_ROLE_MAP } from '@documenso/lib/constants/organisations-translations';
@@ -17,6 +10,11 @@ import type { DataTableColumnDef } from '@documenso/ui/primitives/data-table';
import { DataTable } from '@documenso/ui/primitives/data-table';
import { Skeleton } from '@documenso/ui/primitives/skeleton';
import { TableCell } from '@documenso/ui/primitives/table';
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { Trans } from '@lingui/react/macro';
import { useMemo } from 'react';
import { Link } from 'react-router';
import { OrganisationLeaveDialog } from '../dialogs/organisation-leave-dialog';
@@ -55,7 +53,7 @@ export const UserOrganisationsTable = () => {
avatarClass="h-12 w-12"
avatarFallback={row.original.name.slice(0, 1).toUpperCase()}
primaryText={
<span className="text-foreground/80 font-semibold">
<span className="font-semibold text-foreground/80">
{isPersonalLayoutMode
? _(
msg({
@@ -92,10 +90,7 @@ export const UserOrganisationsTable = () => {
id: 'actions',
cell: ({ row }) => (
<div className="flex justify-end space-x-2">
{canExecuteOrganisationAction(
'MANAGE_ORGANISATION',
row.original.currentOrganisationRole,
) && (
{canExecuteOrganisationAction('MANAGE_ORGANISATION', row.original.currentOrganisationRole) && (
<Button variant="outline" asChild>
<Link to={`/o/${row.original.url}/settings`}>
<Trans>Manage</Trans>