mirror of
https://github.com/documenso/documenso.git
synced 2026-09-30 08:44:39 +10:00
Compare commits
5
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2cc6007c82 | ||
|
|
1de57a9e43 | ||
|
|
7f11c163fe | ||
|
|
5beb57dbf0 | ||
|
|
ead3104896 |
@@ -290,10 +290,11 @@ export const EnvelopesBulkDownloadDialog = ({
|
||||
{isOverDownloadLimit && (
|
||||
<Alert variant="warning">
|
||||
<AlertDescription>
|
||||
<Trans>
|
||||
You can download up to {MAX_BULK_DOWNLOAD_ENVELOPES} documents at a time. Deselect some documents to
|
||||
continue.
|
||||
</Trans>
|
||||
<Plural
|
||||
value={MAX_BULK_DOWNLOAD_ENVELOPES}
|
||||
one="You can download up to # document at a time. Deselect some documents to continue."
|
||||
other="You can download up to # documents at a time. Deselect some documents to continue."
|
||||
/>
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
@@ -17,7 +17,7 @@ import { useToast } from '@documenso/ui/primitives/use-toast';
|
||||
import type { MessageDescriptor } from '@lingui/core';
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
import { Plural, Trans } from '@lingui/react/macro';
|
||||
import { keepPreviousData } from '@tanstack/react-query';
|
||||
import { defaultFilter as commandScore } from 'cmdk';
|
||||
import {
|
||||
@@ -596,9 +596,21 @@ export const AppCommandMenu = ({ open, onOpenChange }: AppCommandMenuProps) => {
|
||||
|
||||
<span className="ml-auto text-muted-foreground text-xs">
|
||||
{hasValidSearch ? (
|
||||
<Trans>{formatChipCount(totalVisibleCount, isVisibleCountCapped)} results</Trans>
|
||||
isVisibleCountCapped ? (
|
||||
<Trans>{formatChipCount(totalVisibleCount, isVisibleCountCapped)} results</Trans>
|
||||
) : (
|
||||
<Plural
|
||||
value={totalVisibleCount}
|
||||
one="# result"
|
||||
other="# results"
|
||||
/>
|
||||
)
|
||||
) : (
|
||||
<Trans>{totalVisibleCount} items</Trans>
|
||||
<Plural
|
||||
value={totalVisibleCount}
|
||||
one="# item"
|
||||
other="# items"
|
||||
/>
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -131,7 +131,7 @@ export default function AdminDocumentsPage() {
|
||||
<div>
|
||||
<Input
|
||||
type="search"
|
||||
placeholder={_(msg`Search by document title, team:123 or user:123`)}
|
||||
placeholder={_(msg`Search by document title, team:123, user:123 or recipient:email@example.com`)}
|
||||
value={term}
|
||||
onChange={(e) => setTerm(e.target.value)}
|
||||
/>
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
/**
|
||||
* The maximum value of a Postgres `INT4` (32-bit signed integer) column, which
|
||||
* is what Prisma `Int` ID columns are stored as. Filtering an `Int` column by
|
||||
* a larger value makes Prisma throw a conversion error at runtime.
|
||||
*/
|
||||
export const MAX_POSTGRES_INT = 2147483647;
|
||||
@@ -93,7 +93,7 @@ export const run = async ({ payload }: { payload: TSendDocumentPendingEmailJobDe
|
||||
},
|
||||
from: senderEmail,
|
||||
replyTo: replyToEmail,
|
||||
subject: i18n._(msg`Waiting for others to complete signing.`),
|
||||
subject: i18n._(msg`Waiting for others to complete signing`),
|
||||
html,
|
||||
text,
|
||||
});
|
||||
|
||||
@@ -2,6 +2,7 @@ import { prisma } from '@documenso/prisma';
|
||||
import { EnvelopeType, type Prisma } from '@prisma/client';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { MAX_POSTGRES_INT } from '../../constants/database';
|
||||
import type { FindResultResponse } from '../../types/search-params';
|
||||
|
||||
export interface AdminFindDocumentsOptions {
|
||||
@@ -10,7 +11,7 @@ export interface AdminFindDocumentsOptions {
|
||||
perPage?: number;
|
||||
}
|
||||
|
||||
const ZPositiveIntegerSchema = z.coerce.number().int().positive();
|
||||
const ZPositiveIntegerSchema = z.coerce.number().int().positive().max(MAX_POSTGRES_INT);
|
||||
|
||||
const emptyResponse = {
|
||||
data: [],
|
||||
@@ -58,6 +59,40 @@ export const adminFindDocuments = async ({ query, page = 1, perPage = 10 }: Admi
|
||||
}
|
||||
}
|
||||
|
||||
if (query?.startsWith('recipient:')) {
|
||||
const recipientQuery = query.slice('recipient:'.length).trim();
|
||||
|
||||
if (!recipientQuery) {
|
||||
return emptyResponse;
|
||||
}
|
||||
|
||||
// Match admin-global-search semantics: only bare digit strings are ID
|
||||
// lookups, so numeric-looking email fragments (1e3, 0x10, +40) stay text.
|
||||
const isRecipientIdLookup = /^\d+$/.test(recipientQuery);
|
||||
|
||||
const parsedRecipientId = ZPositiveIntegerSchema.safeParse(recipientQuery);
|
||||
|
||||
termFilters =
|
||||
isRecipientIdLookup && parsedRecipientId.success
|
||||
? {
|
||||
recipients: {
|
||||
some: {
|
||||
id: parsedRecipientId.data,
|
||||
},
|
||||
},
|
||||
}
|
||||
: {
|
||||
recipients: {
|
||||
some: {
|
||||
email: {
|
||||
contains: recipientQuery,
|
||||
mode: 'insensitive',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
if (query && query?.startsWith('envelope_')) {
|
||||
termFilters = {
|
||||
id: {
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import { EnvelopeType } from '@prisma/client';
|
||||
|
||||
export const ADMIN_SEARCH_RESULTS_PER_TYPE = 5;
|
||||
import { MAX_POSTGRES_INT } from '../../constants/database';
|
||||
|
||||
const MAX_POSTGRES_INT = 2147483647;
|
||||
export const ADMIN_SEARCH_RESULTS_PER_TYPE = 5;
|
||||
|
||||
const GROUP_ORDER = ['document', 'user', 'organisation', 'team', 'recipient', 'subscription'] as const;
|
||||
|
||||
|
||||
@@ -142,6 +142,7 @@ export function MultiSelectCombobox<T = OptionValue>({
|
||||
{showClearButton && !loading && (
|
||||
<div className="absolute top-0 right-8 bottom-0 flex items-center justify-center">
|
||||
<button
|
||||
type="button"
|
||||
className="flex h-4 w-4 items-center justify-center rounded-full bg-muted-foreground/20"
|
||||
onClick={() => onChange([])}
|
||||
>
|
||||
|
||||
@@ -431,6 +431,7 @@ const MultiSelect = ({
|
||||
>
|
||||
{option.label}
|
||||
<button
|
||||
type="button"
|
||||
className="absolute -inset-y-px -end-px flex size-7 items-center justify-center rounded-e-md border border-transparent p-0 text-muted-foreground/80 outline-none transition-[color,box-shadow] hover:text-foreground focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50"
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Enter') {
|
||||
|
||||
Reference in New Issue
Block a user