handleColumnSort('createdAt')}>
Created
diff --git a/apps/remix/app/components/tables/admin-organisation-stats-table.tsx b/apps/remix/app/components/tables/admin-organisation-stats-table.tsx
new file mode 100644
index 000000000..d3233bacf
--- /dev/null
+++ b/apps/remix/app/components/tables/admin-organisation-stats-table.tsx
@@ -0,0 +1,269 @@
+import { useUpdateSearchParams } from '@documenso/lib/client-only/hooks/use-update-search-params';
+import { ZUrlSearchParamsSchema } from '@documenso/lib/types/search-params';
+import { currentMonthlyPeriod } from '@documenso/lib/universal/monthly-period';
+import { trpc } from '@documenso/trpc/react';
+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 { Skeleton } from '@documenso/ui/primitives/skeleton';
+import { TableCell } from '@documenso/ui/primitives/table';
+import { useLingui } from '@lingui/react/macro';
+import { ChevronDownIcon, ChevronsUpDownIcon, ChevronUpIcon } from 'lucide-react';
+import { useMemo } from 'react';
+import { Link, useSearchParams } from 'react-router';
+
+type OrderByColumn = 'documentCount' | 'emailCount' | 'apiCount' | 'emailReports' | 'totalCount';
+type OrderByDirection = 'asc' | 'desc';
+
+const parseOrderByColumn = (value: string | undefined): OrderByColumn | undefined => {
+ if (
+ value === 'documentCount' ||
+ value === 'emailCount' ||
+ value === 'apiCount' ||
+ value === 'emailReports' ||
+ value === 'totalCount'
+ ) {
+ return value;
+ }
+
+ return undefined;
+};
+
+const parseOrderByDirection = (value: string | undefined): OrderByDirection => {
+ return value === 'asc' ? 'asc' : 'desc';
+};
+
+/**
+ * Number of days to divide the period's usage by to get a per-day average.
+ *
+ * For the in-progress (current) month we divide by today's UTC day-of-month so the
+ * average reflects elapsed days only. For a fully-elapsed past month we divide by the
+ * total number of days in that month.
+ */
+const getPeriodDivisor = (period: string): number => {
+ if (period === currentMonthlyPeriod()) {
+ return new Date().getUTCDate();
+ }
+
+ const [yearStr, monthStr] = period.split('-');
+ const year = Number(yearStr);
+ const month = Number(monthStr);
+
+ if (Number.isNaN(year) || Number.isNaN(month)) {
+ return new Date().getUTCDate();
+ }
+
+ // Day 0 of the following month resolves to the last day of `month`.
+ return new Date(Date.UTC(year, month, 0)).getUTCDate();
+};
+
+export type OrganisationStatsDisplayMode = 'usage' | 'quotas' | 'averages';
+
+type AdminOrganisationStatsTableProps = {
+ displayMode?: OrganisationStatsDisplayMode;
+};
+
+export const AdminOrganisationStatsTable = ({ displayMode = 'usage' }: AdminOrganisationStatsTableProps) => {
+ const { t } = useLingui();
+
+ const [searchParams, setSearchParams] = useSearchParams();
+ const updateSearchParams = useUpdateSearchParams();
+
+ const parsedSearchParams = ZUrlSearchParamsSchema.parse(Object.fromEntries(searchParams ?? []));
+
+ // Default to the current month.
+ const period = searchParams?.get('period') ?? currentMonthlyPeriod();
+ const claimId = searchParams?.get('claimId') || undefined;
+ const orderByColumn = parseOrderByColumn(searchParams?.get('orderByColumn') ?? undefined);
+ const orderByDirection = parseOrderByDirection(searchParams?.get('orderByDirection') ?? undefined);
+
+ const { data, isLoading, isLoadingError } = trpc.admin.organisation.stats.find.useQuery({
+ query: parsedSearchParams.query,
+ page: parsedSearchParams.page,
+ perPage: parsedSearchParams.perPage,
+ period,
+ claimId,
+ orderByColumn,
+ orderByDirection,
+ });
+
+ const onPaginationChange = (page: number, perPage: number) => {
+ updateSearchParams({
+ page,
+ perPage,
+ });
+ };
+
+ const handleColumnSort = (column: OrderByColumn) => {
+ const nextDirection = orderByColumn === column && orderByDirection === 'desc' ? 'asc' : 'desc';
+
+ // Use the functional updater so we merge onto the latest params. Reading the
+ // captured `searchParams` here would drop filters (e.g. claimId) that changed
+ // after this handler was memoised into the column definitions.
+ setSearchParams((previous) => {
+ const next = new URLSearchParams(previous);
+
+ next.set('orderByColumn', column);
+ next.set('orderByDirection', nextDirection);
+ next.set('page', '1');
+
+ return next;
+ });
+ };
+
+ const results = data ?? {
+ data: [],
+ perPage: 10,
+ currentPage: 1,
+ totalPages: 1,
+ };
+
+ const columns = useMemo(() => {
+ const divisor = getPeriodDivisor(period);
+
+ const formatPerDay = (used: number) => {
+ const perDay = divisor > 0 ? used / divisor : 0;
+ const rounded = Math.round(perDay * 10) / 10;
+
+ return Number.isInteger(rounded) ? String(rounded) : rounded.toFixed(1);
+ };
+
+ const renderUsageCell = (used: number, quota: number | null) => {
+ if (displayMode === 'averages') {
+ return formatPerDay(used);
+ }
+
+ if (displayMode === 'quotas') {
+ return (
+
+ {used}/{quota === null ? '∞' : quota}
+
+ );
+ }
+
+ return
{used} ;
+ };
+
+ const sortableHeader = (label: string, column: OrderByColumn) => (
+
handleColumnSort(column)}
+ >
+ {label}
+ {orderByColumn === column ? (
+ orderByDirection === 'asc' ? (
+
+ ) : (
+
+ )
+ ) : (
+
+ )}
+
+ );
+
+ return [
+ {
+ header: t`Organisation`,
+ accessorKey: 'organisationName',
+ cell: ({ row }) => (
+
+ {row.original.organisationName}
+
+ ),
+ },
+ {
+ header: t`Claim`,
+ accessorKey: 'originalClaimId',
+ cell: ({ row }) =>
{row.original.originalClaimId ?? '—'} ,
+ },
+ {
+ header: t`Period`,
+ accessorKey: 'period',
+ cell: ({ row }) =>
{row.original.period} ,
+ },
+ {
+ header: () => sortableHeader(t`Documents`, 'documentCount'),
+ accessorKey: 'documentCount',
+ cell: ({ row }) => renderUsageCell(row.original.documentCount, row.original.documentQuota),
+ },
+ {
+ header: () => sortableHeader(t`Emails`, 'emailCount'),
+ accessorKey: 'emailCount',
+ cell: ({ row }) => renderUsageCell(row.original.emailCount, row.original.emailQuota),
+ },
+ {
+ header: () => sortableHeader(t`API`, 'apiCount'),
+ accessorKey: 'apiCount',
+ cell: ({ row }) => renderUsageCell(row.original.apiCount, row.original.apiQuota),
+ },
+ {
+ header: () => sortableHeader(t`Reports`, 'emailReports'),
+ accessorKey: 'emailReports',
+ cell: ({ row }) => row.original.emailReports,
+ },
+ {
+ header: () => sortableHeader(t`Total`, 'totalCount'),
+ accessorKey: 'totalCount',
+ cell: ({ row }) =>
{row.original.totalCount} ,
+ },
+ ] satisfies DataTableColumnDef<(typeof results)['data'][number]>[];
+ // `searchParams` must be a dependency: `handleColumnSort` closes over `setSearchParams`,
+ // whose functional updater is bound to the `searchParams` captured at creation time.
+ // Without this, changing a filter (e.g. claimId) wouldn't refresh the memoised handler,
+ // and sorting would merge onto stale params and drop the active filter.
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [t, orderByColumn, orderByDirection, period, displayMode, searchParams]);
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ >
+ ),
+ }}
+ >
+ {(table) => }
+
+
+ );
+};
diff --git a/apps/remix/app/components/tables/admin-organisations-table.tsx b/apps/remix/app/components/tables/admin-organisations-table.tsx
index 144fb20b8..ee336af8d 100644
--- a/apps/remix/app/components/tables/admin-organisations-table.tsx
+++ b/apps/remix/app/components/tables/admin-organisations-table.tsx
@@ -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 }) => (
-
{row.original.name}
- ),
+ cell: ({ row }) =>
{row.original.name},
},
{
header: t`Created At`,
@@ -97,17 +92,13 @@ export const AdminOrganisationsTable = ({
{
header: t`Owner`,
accessorKey: 'owner',
- cell: ({ row }) => (
-
{row.original.owner.name}
- ),
+ cell: ({ row }) =>
{row.original.owner.name},
},
{
id: 'role',
header: t`Role`,
cell: ({ row }) => (
-
- {row.original.owner.id === memberUserId ? t`Owner` : t`Member`}
-
+
{row.original.owner.id === memberUserId ? t`Owner` : t`Member`}
),
},
{
@@ -118,7 +109,7 @@ export const AdminOrganisationsTable = ({
const isPaid = subscription && subscription.status === 'ACTIVE';
return (
@@ -183,8 +174,7 @@ export const AdminOrganisationsTable = ({
{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') && (
setSwapSource({
@@ -248,7 +238,7 @@ export const AdminOrganisationsTable = ({
}}
>
{(table) =>
- !hidePaginationUntilOverflow || 1 > table.getPageCount() ? (
+ !hidePaginationUntilOverflow || table.getPageCount() > 1 ? (
) : null
}
diff --git a/apps/remix/app/components/tables/admin-user-teams-table.tsx b/apps/remix/app/components/tables/admin-user-teams-table.tsx
index 39eaaa5b4..901205b1c 100644
--- a/apps/remix/app/components/tables/admin-user-teams-table.tsx
+++ b/apps/remix/app/components/tables/admin-user-teams-table.tsx
@@ -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) => {
{row.original.name}
-
+
id
{row.original.id}
@@ -79,10 +74,7 @@ export const AdminUserTeamsTable = ({ userId }: AdminUserTeamsTableProps) => {
header: t`Organisation`,
accessorKey: 'organisation',
cell: ({ row }) => (
-
+
{row.original.organisation.name}
),
@@ -91,9 +83,7 @@ export const AdminUserTeamsTable = ({ userId }: AdminUserTeamsTableProps) => {
header: t`Role`,
accessorKey: 'teamRole',
cell: ({ row }) => (
-
- {i18n._(TEAM_MEMBER_ROLE_MAP[row.original.teamRole as TeamMemberRole])}
-
+ {i18n._(TEAM_MEMBER_ROLE_MAP[row.original.teamRole as TeamMemberRole])}
),
},
{
@@ -137,9 +127,7 @@ export const AdminUserTeamsTable = ({ userId }: AdminUserTeamsTableProps) => {
}}
>
{(table) =>
- table.getPageCount() > 1 ? (
-
- ) : null
+ table.getPageCount() > 1 ? : null
}
);
diff --git a/apps/remix/app/components/tables/document-logs-table.tsx b/apps/remix/app/components/tables/document-logs-table.tsx
index c37235130..892a27a50 100644
--- a/apps/remix/app/components/tables/document-logs-table.tsx
+++ b/apps/remix/app/components/tables/document-logs-table.tsx
@@ -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 }) => (
- {formatDocumentAuditLogAction(i18n, row.original, userId).description}
- ),
+ cell: ({ row }) => {formatDocumentAuditLogAction(i18n, row.original, userId).description} ,
},
{
header: _(msg`IP Address`),
diff --git a/apps/remix/app/components/tables/documents-table-action-button.tsx b/apps/remix/app/components/tables/documents-table-action-button.tsx
index 506c6afa0..a6e9a9edd 100644
--- a/apps/remix/app/components/tables/documents-table-action-button.tsx
+++ b/apps/remix/app/components/tables/documents-table-action-button.tsx
@@ -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 },
- () => (
-
-
-
- Edit
-
-
- ),
- )
+ .with(isOwner ? { isDraft: true, isOwner: true } : { isDraft: true, isCurrentTeamDocument: true }, () => (
+
+
+
+ Edit
+
+
+ ))
.with({ isRecipient: true, isPending: true, isSigned: false }, () => (
{match(role)
.with(RecipientRole.SIGNER, () => (
<>
-
+
Sign
>
))
.with(RecipientRole.APPROVER, () => (
<>
-
+
Approve
>
))
.otherwise(() => (
<>
-
+
View
>
))}
@@ -95,7 +91,7 @@ export const DocumentsTableActionButton = ({ row }: DocumentsTableActionButtonPr
))
.with({ isPending: true, isSigned: true }, () => (
-
+
View
))
@@ -106,7 +102,7 @@ export const DocumentsTableActionButton = ({ row }: DocumentsTableActionButtonPr
token={recipient?.token}
trigger={
-
+
Download
}
diff --git a/apps/remix/app/components/tables/documents-table-action-dropdown.tsx b/apps/remix/app/components/tables/documents-table-action-dropdown.tsx
index 509be3880..1555d54a2 100644
--- a/apps/remix/app/components/tables/documents-table-action-dropdown.tsx
+++ b/apps/remix/app/components/tables/documents-table-action-dropdown.tsx
@@ -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 = ({
e.preventDefault()}>
diff --git a/apps/remix/app/components/tables/documents-table-empty-state.tsx b/apps/remix/app/components/tables/documents-table-empty-state.tsx
index e02a1c2bd..1b049cdac 100644
--- a/apps/remix/app/components/tables/documents-table-empty-state.tsx
+++ b/apps/remix/app/components/tables/documents-table-empty-state.tsx
@@ -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 (
-
{_(title)}
+
{_(title)}
{_(message)}
diff --git a/apps/remix/app/components/tables/documents-table-sender-filter.tsx b/apps/remix/app/components/tables/documents-table-sender-filter.tsx
index 4a44edd51..c4c2bbd4a 100644
--- a/apps/remix/app/components/tables/documents-table-sender-filter.tsx
+++ b/apps/remix/app/components/tables/documents-table-sender-filter.tsx
@@ -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 (
+
Sender: All
diff --git a/apps/remix/app/components/tables/documents-table-title.tsx b/apps/remix/app/components/tables/documents-table-title.tsx
index 914a59625..b9ff67c2e 100644
--- a/apps/remix/app/components/tables/documents-table-title.tsx
+++ b/apps/remix/app/components/tables/documents-table-title.tsx
@@ -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) => {
))
.otherwise(() => (
-
- {row.title}
-
+ {row.title}
));
};
diff --git a/apps/remix/app/components/tables/documents-table.tsx b/apps/remix/app/components/tables/documents-table.tsx
index 7f1d104dd..ddffc8a9c 100644
--- a/apps/remix/app/components/tables/documents-table.tsx
+++ b/apps/remix/app/components/tables/documents-table.tsx
@@ -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 }) => (
-
- ),
+ cell: ({ row }) => ,
},
{
id: 'sender',
@@ -109,10 +100,7 @@ export const DocumentsTable = ({
header: _(msg`Recipient`),
accessorKey: 'recipient',
cell: ({ row }) => (
-
+
),
},
{
@@ -263,8 +251,6 @@ const DataTableTitle = ({ row, teamUrl, teamEmail }: DataTableTitleProps) => {
))
.otherwise(() => (
-
- {row.title}
-
+ {row.title}
));
};
diff --git a/apps/remix/app/components/tables/envelopes-table-bulk-action-bar.tsx b/apps/remix/app/components/tables/envelopes-table-bulk-action-bar.tsx
index a7e0efa25..3a7d89dc5 100644
--- a/apps/remix/app/components/tables/envelopes-table-bulk-action-bar.tsx
+++ b/apps/remix/app/components/tables/envelopes-table-bulk-action-bar.tsx
@@ -1,8 +1,6 @@
-import { useLingui } from '@lingui/react/macro';
-import { Trans } from '@lingui/react/macro';
-import { FolderInputIcon, Trash2Icon, XIcon } from 'lucide-react';
-
import { Button } from '@documenso/ui/primitives/button';
+import { Trans, useLingui } from '@lingui/react/macro';
+import { FolderInputIcon, Trash2Icon, XIcon } from 'lucide-react';
export type EnvelopesTableBulkActionBarProps = {
selectedCount: number;
@@ -25,7 +23,7 @@ export const EnvelopesTableBulkActionBar = ({
return (
-
+
{selectedCount} selected
diff --git a/apps/remix/app/components/tables/inbox-table.tsx b/apps/remix/app/components/tables/inbox-table.tsx
index eb72219b4..d0d6a9e21 100644
--- a/apps/remix/app/components/tables/inbox-table.tsx
+++ b/apps/remix/app/components/tables/inbox-table.tsx
@@ -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 }) => (
-
- {row.original.title}
-
+ {row.original.title}
),
},
{
@@ -79,10 +73,7 @@ export const InboxTable = () => {
header: _(msg`Recipient`),
accessorKey: 'recipient',
cell: ({ row }) => (
-
+
),
},
{
@@ -130,7 +121,7 @@ export const InboxTable = () => {
enable: isLoadingError || false,
}}
emptyState={
-
+
Documents that require your attention will appear here
@@ -163,15 +154,13 @@ export const InboxTable = () => {
}}
>
{(table) =>
- results.totalPages > 1 && (
-
- )
+ results.totalPages > 1 &&
}
{isPending && (
-
@@ -215,19 +204,19 @@ export const InboxTableActionButton = ({ row }: InboxTableActionButtonProps) =>
{match(role)
.with(RecipientRole.SIGNER, () => (
<>
-
+
Sign
>
))
.with(RecipientRole.APPROVER, () => (
<>
-
+
Approve
>
))
.otherwise(() => (
<>
-
+
View
>
))}
@@ -236,7 +225,7 @@ export const InboxTableActionButton = ({ row }: InboxTableActionButtonProps) =>
))
.with({ isPending: true, isSigned: true }, () => (
-
+
View
))
@@ -247,7 +236,7 @@ export const InboxTableActionButton = ({ row }: InboxTableActionButtonProps) =>
token={recipient?.token}
trigger={
-
+
Download
}
diff --git a/apps/remix/app/components/tables/internal-audit-log-table.tsx b/apps/remix/app/components/tables/internal-audit-log-table.tsx
index 5449bb052..03cff1d83 100644
--- a/apps/remix/app/components/tables/internal-audit-log-table.tsx
+++ b/apps/remix/app/components/tables/internal-audit-log-table.tsx
@@ -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 */}
-
+
-
+
{log.type.replace(/_/g, ' ')}
-
+
{formattedAction.description}
-
+
{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 */}
-
- {_(msg`User`)}
-
+
{_(msg`User`)}
{log.email || 'N/A'}
-
+
{_(msg`IP Address`)}
@@ -133,13 +122,11 @@ export const InternalAuditLogTable = ({ logs }: AuditLogDataTableProps) => {
-
+
{_(msg`User Agent`)}
-
- {_(formatUserAgent(log.userAgent, userAgentInfo))}
-
+
{_(formatUserAgent(log.userAgent, userAgentInfo))}
diff --git a/apps/remix/app/components/tables/organisation-billing-invoices-table.tsx b/apps/remix/app/components/tables/organisation-billing-invoices-table.tsx
index 9cc84d903..19cf98cda 100644
--- a/apps/remix/app/components/tables/organisation-billing-invoices-table.tsx
+++ b/apps/remix/app/components/tables/organisation-billing-invoices-table.tsx
@@ -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 = ({
-
+
{DateTime.fromSeconds(row.original.created).toFormat('MMMM yyyy')}
@@ -87,21 +85,13 @@ export const OrganisationBillingInvoicesTable = ({
id: 'actions',
cell: ({ row }) => (
-
+
View
-
+
Download
diff --git a/apps/remix/app/components/tables/organisation-email-domains-table.tsx b/apps/remix/app/components/tables/organisation-email-domains-table.tsx
index e864b02a3..a1fdf3e05 100644
--- a/apps/remix/app/components/tables/organisation-email-domains-table.tsx
+++ b/apps/remix/app/components/tables/organisation-email-domains-table.tsx
@@ -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 }) => (
-
- Manage
-
+ Manage
{
}}
>
{(table) =>
- results.totalPages > 1 && (
-
- )
+ results.totalPages > 1 &&
}
{results.data.length > 0 && (
-
+
Sync Email Domains
-
- This will check and sync the status of all email domains for this organisation
-
+ This will check and sync the status of all email domains for this organisation
diff --git a/apps/remix/app/components/tables/organisation-groups-table.tsx b/apps/remix/app/components/tables/organisation-groups-table.tsx
index 5656c7580..a6e586d85 100644
--- a/apps/remix/app/components/tables/organisation-groups-table.tsx
+++ b/apps/remix/app/components/tables/organisation-groups-table.tsx
@@ -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 && (
-
- )
- }
+ {(table) => results.totalPages > 1 && }
);
};
diff --git a/apps/remix/app/components/tables/organisation-insights-table.tsx b/apps/remix/app/components/tables/organisation-insights-table.tsx
index 5d6c5dc44..417ee2b46 100644
--- a/apps/remix/app/components/tables/organisation-insights-table.tsx
+++ b/apps/remix/app/components/tables/organisation-insights-table.tsx
@@ -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 }) => (
-
+
{row.getValue('name')}
),
@@ -97,10 +91,7 @@ export const OrganisationInsightsTable = ({
header: () => {_(msg`Name`)} ,
accessorKey: 'name',
cell: ({ row }) => (
-
+
{(row.getValue('name') as string) || (row.getValue('email') as string)}
),
@@ -150,9 +141,7 @@ export const OrganisationInsightsTable = ({
{
header: () => {_(msg`Status`)} ,
accessorKey: 'status',
- cell: ({ row }) => (
-
- ),
+ cell: ({ row }) => ,
size: 120,
},
{
@@ -218,7 +207,7 @@ export const OrganisationInsightsTable = ({
)}
@@ -280,15 +269,15 @@ const SummaryCard = ({
}: {
icon: React.ComponentType<{ className?: string }>;
title: string;
- value: number;
+ value: number | string;
subtitle?: string;
}) => (
-
{title}
-
{value}
- {subtitle &&
{subtitle}
}
+
{title}
+
{value}
+ {subtitle &&
{subtitle}
}
);
diff --git a/apps/remix/app/components/tables/organisation-member-invites-table.tsx b/apps/remix/app/components/tables/organisation-member-invites-table.tsx
index 5ce733df6..08cd46ee4 100644
--- a/apps/remix/app/components/tables/organisation-member-invites-table.tsx
+++ b/apps/remix/app/components/tables/organisation-member-invites-table.tsx
@@ -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 = () => {
{row.original.email}
- }
+ primaryText={{row.original.email} }
/>
);
},
@@ -208,11 +202,7 @@ export const OrganisationMemberInvitesTable = () => {
),
}}
>
- {(table) =>
- results.totalPages > 1 && (
-
- )
- }
+ {(table) => results.totalPages > 1 && }
);
};
diff --git a/apps/remix/app/components/tables/organisation-members-table.tsx b/apps/remix/app/components/tables/organisation-members-table.tsx
index 8295efe2a..f98fc1889 100644
--- a/apps/remix/app/components/tables/organisation-members-table.tsx
+++ b/apps/remix/app/components/tables/organisation-members-table.tsx
@@ -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 = () => {
{row.original.name}
- }
+ primaryText={{row.original.name} }
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 }) => (
-
+
@@ -209,11 +204,7 @@ export const OrganisationMembersDataTable = () => {
),
}}
>
- {(table) =>
- results.totalPages > 1 && (
-
- )
- }
+ {(table) => results.totalPages > 1 && }
);
};
diff --git a/apps/remix/app/components/tables/organisation-teams-table.tsx b/apps/remix/app/components/tables/organisation-teams-table.tsx
index 73632814f..86d1e2161 100644
--- a/apps/remix/app/components/tables/organisation-teams-table.tsx
+++ b/apps/remix/app/components/tables/organisation-teams-table.tsx
@@ -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={
- {row.original.name}
- }
+ primaryText={{row.original.name} }
secondaryText={`${NEXT_PUBLIC_WEBAPP_URL()}/t/${row.original.url}`}
/>
@@ -143,11 +138,7 @@ export const OrganisationTeamsTable = () => {
),
}}
>
- {(table) =>
- results.totalPages > 1 && (
-
- )
- }
+ {(table) => results.totalPages > 1 && }
);
};
diff --git a/apps/remix/app/components/tables/settings-public-profile-templates-table.tsx b/apps/remix/app/components/tables/settings-public-profile-templates-table.tsx
index 6fa8f1d07..1a2593000 100644
--- a/apps/remix/app/components/tables/settings-public-profile-templates-table.tsx
+++ b/apps/remix/app/components/tables/settings-public-profile-templates-table.tsx
@@ -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) => (
-
+
@@ -100,7 +95,7 @@ export const SettingsPublicProfileTemplatesTable = () => {
))}
{isLoadingError && (
-
+
Unable to load your public profile templates at this time
{
@@ -114,7 +109,7 @@ export const SettingsPublicProfileTemplatesTable = () => {
)}
{!isLoading && (
-
+
No public profile templates found
{
{/* Public templates list. */}
{publicDirectTemplates.map((template) => (
-
+
-
+
{template.publicTitle}
-
{template.publicDescription}
+
{template.publicDescription}
diff --git a/apps/remix/app/components/tables/settings-security-activity-table.tsx b/apps/remix/app/components/tables/settings-security-activity-table.tsx
index 18bba7d62..79cb0fc66 100644
--- a/apps/remix/app/components/tables/settings-security-activity-table.tsx
+++ b/apps/remix/app/components/tables/settings-security-activity-table.tsx
@@ -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,
diff --git a/apps/remix/app/components/tables/settings-security-passkey-table-actions.tsx b/apps/remix/app/components/tables/settings-security-passkey-table-actions.tsx
index c14ae8036..7feb0e191 100644
--- a/apps/remix/app/components/tables/settings-security-passkey-table-actions.tsx
+++ b/apps/remix/app/components/tables/settings-security-passkey-table-actions.tsx
@@ -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`),
- });
-
- 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`),
- });
-
- 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',
- });
- },
- });
+ 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',
+ });
+ },
+ });
+
+ 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',
+ });
+ },
+ });
return (
-
!isUpdatingPasskey && setIsUpdateDialogOpen(value)}
- >
+ !isUpdatingPasskey && setIsUpdateDialogOpen(value)}>
e.stopPropagation()} asChild>
Edit
@@ -173,10 +155,7 @@ export const SettingsSecurityPasskeyTableActions = ({
- !isDeletingPasskey && setIsDeleteDialogOpen(value)}
- >
+ !isDeletingPasskey && setIsDeleteDialogOpen(value)}>
e.stopPropagation()} asChild={true}>
Delete
diff --git a/apps/remix/app/components/tables/settings-security-passkey-table.tsx b/apps/remix/app/components/tables/settings-security-passkey-table.tsx
index b2fe09621..33b566956 100644
--- a/apps/remix/app/components/tables/settings-security-passkey-table.tsx
+++ b/apps/remix/app/components/tables/settings-security-passkey-table.tsx
@@ -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',
diff --git a/apps/remix/app/components/tables/team-groups-table.tsx b/apps/remix/app/components/tables/team-groups-table.tsx
index 3bbf072b4..4ad463cbf 100644
--- a/apps/remix/app/components/tables/team-groups-table.tsx
+++ b/apps/remix/app/components/tables/team-groups-table.tsx
@@ -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 }) => (
-
+
@@ -99,10 +97,7 @@ export const TeamGroupsTable = () => {
teamGroupName={row.original.name ?? ''}
teamGroupRole={row.original.teamRole}
trigger={
- e.preventDefault()}
- title="Update team group role"
- >
+ e.preventDefault()} title="Update team group role">
Update role
@@ -139,7 +134,7 @@ export const TeamGroupsTable = () => {
enable: isLoadingError,
}}
emptyState={
-
+
No team groups found
@@ -173,11 +168,7 @@ export const TeamGroupsTable = () => {
),
}}
>
- {(table) =>
- results.totalPages > 1 && (
-
- )
- }
+ {(table) => results.totalPages > 1 &&
}
);
};
diff --git a/apps/remix/app/components/tables/team-members-table.tsx b/apps/remix/app/components/tables/team-members-table.tsx
index 02771c322..c8a9f0faa 100644
--- a/apps/remix/app/components/tables/team-members-table.tsx
+++ b/apps/remix/app/components/tables/team-members-table.tsx
@@ -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 = () => {
{row.original.name}
- }
+ primaryText={{row.original.name} }
secondaryText={row.original.email}
/>
);
@@ -130,7 +126,7 @@ export const TeamMembersTable = () => {
cell: ({ row }) => (
-
+
@@ -231,9 +227,7 @@ export const TeamMembersTable = () => {
- {!groupQuery.isPending && (
-
- )}
+ {!groupQuery.isPending && }
);
diff --git a/apps/remix/app/components/tables/templates-table-action-dropdown.tsx b/apps/remix/app/components/tables/templates-table-action-dropdown.tsx
index 05b6a08fe..8872a1a78 100644
--- a/apps/remix/app/components/tables/templates-table-action-dropdown.tsx
+++ b/apps/remix/app/components/tables/templates-table-action-dropdown.tsx
@@ -1,20 +1,3 @@
-import { useState } from 'react';
-
-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 { Link } from 'react-router';
-
import type { TRecipientLite } from '@documenso/lib/types/recipient';
import { trpc as trpcReact } from '@documenso/trpc/react';
import {
@@ -24,6 +7,11 @@ 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';
diff --git a/apps/remix/app/components/tables/templates-table.tsx b/apps/remix/app/components/tables/templates-table.tsx
index 46240d4ca..cc7754ffd 100644
--- a/apps/remix/app/components/tables/templates-table.tsx
+++ b/apps/remix/app/components/tables/templates-table.tsx
@@ -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 = ({
-
+
@@ -138,8 +128,8 @@ export const TemplatesTable = ({
- 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.
@@ -151,9 +141,8 @@ export const TemplatesTable = ({
- 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.
@@ -165,10 +154,7 @@ export const TemplatesTable = ({
{team?.id ? (
-
- Team only templates are not linked anywhere and are visible only to your
- team.
-
+ Team only templates are not linked anywhere and are visible only to your team.
) : (
Private templates can only be modified and viewed by you.
)}
@@ -182,8 +168,8 @@ export const TemplatesTable = ({
- 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.
@@ -201,9 +187,7 @@ export const TemplatesTable = ({
{isFromOtherTeam && row.original.team?.name && (
-
- ({row.original.team.name})
-
+ ({row.original.team.name})
)}
{row.original.directLink?.token && (
@@ -232,11 +216,7 @@ export const TemplatesTable = ({
documentRootPath={documentRootPath}
/>
-
+
);
},
@@ -273,10 +253,7 @@ export const TemplatesTable = ({
You have reached your document limit.{' '}
-
+
Upgrade your account to continue!
diff --git a/apps/remix/app/components/tables/user-billing-organisations-table.tsx b/apps/remix/app/components/tables/user-billing-organisations-table.tsx
index bc6a2c604..9cf3aebfc 100644
--- a/apps/remix/app/components/tables/user-billing-organisations-table.tsx
+++ b/apps/remix/app/components/tables/user-billing-organisations-table.tsx
@@ -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={
- {row.original.name}
- }
+ primaryText={{row.original.name} }
secondaryText={`${NEXT_PUBLIC_WEBAPP_URL()}/o/${row.original.url}`}
/>
@@ -91,7 +85,7 @@ export const UserBillingOrganisationsTable = () => {
if (billingOrganisations.length === 0) {
return (
-
+
You don't manage billing for any organisations.
diff --git a/apps/remix/app/components/tables/user-organisations-table.tsx b/apps/remix/app/components/tables/user-organisations-table.tsx
index 6bfdedadc..ca1131a81 100644
--- a/apps/remix/app/components/tables/user-organisations-table.tsx
+++ b/apps/remix/app/components/tables/user-organisations-table.tsx
@@ -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={
-
+
{isPersonalLayoutMode
? _(
msg({
@@ -92,10 +90,7 @@ export const UserOrganisationsTable = () => {
id: 'actions',
cell: ({ row }) => (
- {canExecuteOrganisationAction(
- 'MANAGE_ORGANISATION',
- row.original.currentOrganisationRole,
- ) && (
+ {canExecuteOrganisationAction('MANAGE_ORGANISATION', row.original.currentOrganisationRole) && (
Manage
diff --git a/apps/remix/app/entry.client.tsx b/apps/remix/app/entry.client.tsx
index c32cdc4e1..86e949d7a 100644
--- a/apps/remix/app/entry.client.tsx
+++ b/apps/remix/app/entry.client.tsx
@@ -1,15 +1,12 @@
-import { StrictMode, startTransition, useEffect } from 'react';
-
+import { extractPostHogConfig } from '@documenso/lib/constants/feature-flags';
+import { dynamicActivate } from '@documenso/lib/utils/i18n';
import { i18n } from '@lingui/core';
import { detect, fromHtmlTag } from '@lingui/detect-locale';
import { I18nProvider } from '@lingui/react';
-import posthog from 'posthog-js';
+import { StrictMode, startTransition, useEffect } from 'react';
import { hydrateRoot } from 'react-dom/client';
import { HydratedRouter } from 'react-router/dom';
-import { extractPostHogConfig } from '@documenso/lib/constants/feature-flags';
-import { dynamicActivate } from '@documenso/lib/utils/i18n';
-
import './utils/polyfills/promise-with-resolvers';
function PosthogInit() {
@@ -17,9 +14,11 @@ function PosthogInit() {
useEffect(() => {
if (postHogConfig) {
- posthog.init(postHogConfig.key, {
- api_host: postHogConfig.host,
- capture_exceptions: true,
+ void import('posthog-js').then(({ default: posthog }) => {
+ posthog.init(postHogConfig.key, {
+ api_host: postHogConfig.host,
+ capture_exceptions: true,
+ });
});
}
}, []);
diff --git a/apps/remix/app/entry.server.tsx b/apps/remix/app/entry.server.tsx
index d38ef3dea..7f28001f4 100644
--- a/apps/remix/app/entry.server.tsx
+++ b/apps/remix/app/entry.server.tsx
@@ -1,16 +1,15 @@
+import { PassThrough } from 'node:stream';
+import { APP_I18N_OPTIONS } from '@documenso/lib/constants/i18n';
+import { dynamicActivate, extractLocaleData } from '@documenso/lib/utils/i18n';
import { i18n } from '@lingui/core';
import { I18nProvider } from '@lingui/react';
import { createReadableStreamFromReadable } from '@react-router/node';
import { isbot } from 'isbot';
-import { PassThrough } from 'node:stream';
import type { RenderToPipeableStreamOptions } from 'react-dom/server';
import { renderToPipeableStream } from 'react-dom/server';
import type { AppLoadContext, EntryContext } from 'react-router';
import { ServerRouter } from 'react-router';
-import { APP_I18N_OPTIONS } from '@documenso/lib/constants/i18n';
-import { dynamicActivate, extractLocaleData } from '@documenso/lib/utils/i18n';
-
import { langCookie } from './storage/lang-cookie.server';
export const streamTimeout = 5_000;
diff --git a/apps/remix/app/providers/team.tsx b/apps/remix/app/providers/team.tsx
index d77c77428..7f309fdbd 100644
--- a/apps/remix/app/providers/team.tsx
+++ b/apps/remix/app/providers/team.tsx
@@ -1,7 +1,6 @@
-import { createContext, useContext } from 'react';
-import React from 'react';
-
import type { TeamSession } from '@documenso/trpc/server/organisation-router/get-organisation-session.types';
+import type React from 'react';
+import { createContext, useContext } from 'react';
type TeamProviderValue = TeamSession;
diff --git a/apps/remix/app/root.tsx b/apps/remix/app/root.tsx
index 9fbcecb86..096b1504e 100644
--- a/apps/remix/app/root.tsx
+++ b/apps/remix/app/root.tsx
@@ -1,26 +1,26 @@
+import { getOptionalSession } from '@documenso/auth/server/lib/utils/get-session';
+import { SessionProvider } from '@documenso/lib/client-only/providers/session';
+import { APP_I18N_OPTIONS, type SupportedLanguageCodes } from '@documenso/lib/constants/i18n';
+import { createPublicEnv } from '@documenso/lib/utils/env';
+import { extractLocaleData } from '@documenso/lib/utils/i18n';
+import { TrpcProvider } from '@documenso/trpc/react';
+import { getOrganisationSession } from '@documenso/trpc/server/organisation-router/get-organisation-session';
+import { Toaster } from '@documenso/ui/primitives/toaster';
+import { TooltipProvider } from '@documenso/ui/primitives/tooltip';
import { NuqsAdapter } from 'nuqs/adapters/react-router/v7';
import {
+ data,
+ isRouteErrorResponse,
Links,
Meta,
Outlet,
Scripts,
ScrollRestoration,
- data,
- isRouteErrorResponse,
useLoaderData,
+ useMatches,
} from 'react-router';
import { PreventFlashOnWrongTheme, ThemeProvider, useTheme } from 'remix-themes';
-import { getOptionalSession } from '@documenso/auth/server/lib/utils/get-session';
-import { SessionProvider } from '@documenso/lib/client-only/providers/session';
-import { APP_I18N_OPTIONS, type SupportedLanguageCodes } from '@documenso/lib/constants/i18n';
-import { createPublicEnv } from '@documenso/lib/utils/env';
-import { extractLocaleData } from '@documenso/lib/utils/i18n';
-import { TrpcProvider } from '@documenso/trpc/react';
-import { getOrganisationSession } from '@documenso/trpc/server/organisation-router/get-organisation-session';
-import { Toaster } from '@documenso/ui/primitives/toaster';
-import { TooltipProvider } from '@documenso/ui/primitives/tooltip';
-
import type { Route } from './+types/root';
import stylesheet from './app.css?url';
import { GenericErrorLayout } from './components/general/generic-error-layout';
@@ -111,6 +111,13 @@ export function LayoutContent({ children }: { children: React.ReactNode }) {
const [theme] = useTheme();
+ // Recipient routes (signing pages) put `documenso-branded` on so the
+ // inside a string value', () => {
+ const result = sanitizeBrandingCss('.x { font-family: " "; }');
+
+ expect(result.css.toLowerCase()).not.toContain(' inside a CSS comment', () => {
+ const result = sanitizeBrandingCss('.x { color: red; /* */ }');
+
+ expect(result.css.toLowerCase()).not.toContain(' inside an at-rule params block', () => {
+ const result = sanitizeBrandingCss(
+ '@media screen and (foo: bar) { .x { color: red; } }',
+ );
+
+ expect(result.css.toLowerCase()).not.toContain(' in a value', () => {
+ const result = sanitizeBrandingCss('.x { font-family: "foo"; }');
+
+ expect(result.css.toLowerCase()).not.toContain(' in an attribute selector value', () => {
+ const result = sanitizeBrandingCss('[data-x=""] { color: red; }');
+
+ expect(result.css.toLowerCase()).not.toContain(' {
+ // `"; }');
+
+ // The output keeps the literal `` end tag's `<` for the same reason it'd escape ``.
+ expect(result.css).toContain('