mirror of
https://github.com/documenso/documenso.git
synced 2026-07-25 09:25:08 +10:00
chore: merge main
This commit is contained in:
@@ -10,6 +10,7 @@ import { match } from 'ts-pattern';
|
||||
|
||||
import { useLimits } from '@documenso/ee/server-only/limits/provider/client';
|
||||
import { useAnalytics } from '@documenso/lib/client-only/hooks/use-analytics';
|
||||
import { useCurrentOrganisation } from '@documenso/lib/client-only/providers/organisation';
|
||||
import { useSession } from '@documenso/lib/client-only/providers/session';
|
||||
import { APP_DOCUMENT_UPLOAD_SIZE_LIMIT, IS_BILLING_ENABLED } from '@documenso/lib/constants/app';
|
||||
import { DEFAULT_DOCUMENT_TIME_ZONE, TIME_ZONES } from '@documenso/lib/constants/time-zones';
|
||||
@@ -21,7 +22,7 @@ import { trpc } from '@documenso/trpc/react';
|
||||
import { cn } from '@documenso/ui/lib/utils';
|
||||
import { useToast } from '@documenso/ui/primitives/use-toast';
|
||||
|
||||
import { useOptionalCurrentTeam } from '~/providers/team';
|
||||
import { useCurrentTeam } from '~/providers/team';
|
||||
|
||||
export interface DocumentDropZoneWrapperProps {
|
||||
children: ReactNode;
|
||||
@@ -34,10 +35,11 @@ export const DocumentDropZoneWrapper = ({ children, className }: DocumentDropZon
|
||||
const { user } = useSession();
|
||||
const { folderId } = useParams();
|
||||
|
||||
const team = useOptionalCurrentTeam();
|
||||
const team = useCurrentTeam();
|
||||
|
||||
const navigate = useNavigate();
|
||||
const analytics = useAnalytics();
|
||||
const organisation = useCurrentOrganisation();
|
||||
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
@@ -53,7 +55,7 @@ export const DocumentDropZoneWrapper = ({ children, className }: DocumentDropZon
|
||||
|
||||
const onFileDrop = async (file: File) => {
|
||||
if (isUploadDisabled && IS_BILLING_ENABLED()) {
|
||||
await navigate('/settings/billing');
|
||||
await navigate(`/o/${organisation.url}/settings/billing`);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -83,11 +85,7 @@ export const DocumentDropZoneWrapper = ({ children, className }: DocumentDropZon
|
||||
timestamp: new Date().toISOString(),
|
||||
});
|
||||
|
||||
await navigate(
|
||||
folderId
|
||||
? `${formatDocumentsPath(team?.url)}/f/${folderId}/${id}/edit`
|
||||
: `${formatDocumentsPath(team?.url)}/${id}/edit`,
|
||||
);
|
||||
await navigate(`${formatDocumentsPath(team.url)}/${id}/edit`);
|
||||
} catch (err) {
|
||||
const error = AppError.parseError(err);
|
||||
|
||||
@@ -156,7 +154,7 @@ export const DocumentDropZoneWrapper = ({ children, className }: DocumentDropZon
|
||||
|
||||
{isUploadDisabled && IS_BILLING_ENABLED() && (
|
||||
<Link
|
||||
to="/settings/billing"
|
||||
to={`/o/${organisation.url}/settings/billing`}
|
||||
className="mt-4 text-sm text-amber-500 hover:underline dark:text-amber-400"
|
||||
>
|
||||
<Trans>Upgrade your plan to upload more documents</Trans>
|
||||
|
||||
@@ -4,6 +4,7 @@ import { msg } from '@lingui/core/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import { DocumentDistributionMethod, DocumentStatus } from '@prisma/client';
|
||||
import { useNavigate, useSearchParams } from 'react-router';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { DocumentSignatureType } from '@documenso/lib/constants/document';
|
||||
import { isValidLanguageCode } from '@documenso/lib/constants/i18n';
|
||||
@@ -12,6 +13,7 @@ import {
|
||||
SKIP_QUERY_BATCH_META,
|
||||
} from '@documenso/lib/constants/trpc';
|
||||
import type { TDocument } from '@documenso/lib/types/document';
|
||||
import { ZDocumentAccessAuthTypesSchema } from '@documenso/lib/types/document-auth';
|
||||
import { trpc } from '@documenso/trpc/react';
|
||||
import { cn } from '@documenso/ui/lib/utils';
|
||||
import { Card, CardContent } from '@documenso/ui/primitives/card';
|
||||
@@ -29,13 +31,12 @@ import { PDFViewer } from '@documenso/ui/primitives/pdf-viewer';
|
||||
import { Stepper } from '@documenso/ui/primitives/stepper';
|
||||
import { useToast } from '@documenso/ui/primitives/use-toast';
|
||||
|
||||
import { useOptionalCurrentTeam } from '~/providers/team';
|
||||
import { useCurrentTeam } from '~/providers/team';
|
||||
|
||||
export type DocumentEditFormProps = {
|
||||
className?: string;
|
||||
initialDocument: TDocument;
|
||||
documentRootPath: string;
|
||||
isDocumentEnterprise: boolean;
|
||||
};
|
||||
|
||||
type EditDocumentStep = 'settings' | 'signers' | 'fields' | 'subject';
|
||||
@@ -45,7 +46,6 @@ export const DocumentEditForm = ({
|
||||
className,
|
||||
initialDocument,
|
||||
documentRootPath,
|
||||
isDocumentEnterprise,
|
||||
}: DocumentEditFormProps) => {
|
||||
const { toast } = useToast();
|
||||
const { _ } = useLingui();
|
||||
@@ -53,7 +53,7 @@ export const DocumentEditForm = ({
|
||||
const navigate = useNavigate();
|
||||
|
||||
const [searchParams] = useSearchParams();
|
||||
const team = useOptionalCurrentTeam();
|
||||
const team = useCurrentTeam();
|
||||
|
||||
const [isDocumentPdfLoaded, setIsDocumentPdfLoaded] = useState(false);
|
||||
|
||||
@@ -177,15 +177,19 @@ export const DocumentEditForm = ({
|
||||
try {
|
||||
const { timezone, dateFormat, redirectUrl, language, signatureTypes } = data.meta;
|
||||
|
||||
const parsedGlobalAccessAuth = z
|
||||
.array(ZDocumentAccessAuthTypesSchema)
|
||||
.safeParse(data.globalAccessAuth);
|
||||
|
||||
await updateDocument({
|
||||
documentId: document.id,
|
||||
data: {
|
||||
title: data.title,
|
||||
externalId: data.externalId || null,
|
||||
visibility: data.visibility,
|
||||
globalAccessAuth: data.globalAccessAuth ?? null,
|
||||
globalActionAuth: data.globalActionAuth ?? null,
|
||||
attachments: data.attachments ?? [],
|
||||
globalAccessAuth: parsedGlobalAccessAuth.success ? parsedGlobalAccessAuth.data : [],
|
||||
globalActionAuth: data.globalActionAuth ?? [],
|
||||
},
|
||||
meta: {
|
||||
timezone,
|
||||
@@ -230,7 +234,7 @@ export const DocumentEditForm = ({
|
||||
recipients: data.signers.map((signer) => ({
|
||||
...signer,
|
||||
// Explicitly set to null to indicate we want to remove auth if required.
|
||||
actionAuth: signer.actionAuth || null,
|
||||
actionAuth: signer.actionAuth ?? [],
|
||||
})),
|
||||
}),
|
||||
]);
|
||||
@@ -356,10 +360,9 @@ export const DocumentEditForm = ({
|
||||
key={recipients.length}
|
||||
documentFlow={documentFlow.settings}
|
||||
document={document}
|
||||
currentTeamMemberRole={team?.currentTeamMember?.role}
|
||||
currentTeamMemberRole={team.currentTeamRole}
|
||||
recipients={recipients}
|
||||
fields={fields}
|
||||
isDocumentEnterprise={isDocumentEnterprise}
|
||||
isDocumentPdfLoaded={isDocumentPdfLoaded}
|
||||
onSubmit={onAddSettingsFormSubmit}
|
||||
/>
|
||||
@@ -371,7 +374,6 @@ export const DocumentEditForm = ({
|
||||
signingOrder={document.documentMeta?.signingOrder}
|
||||
allowDictateNextSigner={document.documentMeta?.allowDictateNextSigner}
|
||||
fields={fields}
|
||||
isDocumentEnterprise={isDocumentEnterprise}
|
||||
onSubmit={onAddSignersFormSubmit}
|
||||
isDocumentPdfLoaded={isDocumentPdfLoaded}
|
||||
/>
|
||||
@@ -383,7 +385,7 @@ export const DocumentEditForm = ({
|
||||
fields={fields}
|
||||
onSubmit={onAddFieldsFormSubmit}
|
||||
isDocumentPdfLoaded={isDocumentPdfLoaded}
|
||||
teamId={team?.id}
|
||||
teamId={team.id}
|
||||
/>
|
||||
|
||||
<AddSubjectFormPartial
|
||||
|
||||
@@ -81,11 +81,15 @@ export const DocumentHistorySheet = ({
|
||||
* @param text The text to format
|
||||
* @returns The formatted text
|
||||
*/
|
||||
const formatGenericText = (text?: string | null) => {
|
||||
const formatGenericText = (text?: string | string[] | null): string => {
|
||||
if (!text) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if (Array.isArray(text)) {
|
||||
return text.map((t) => formatGenericText(t)).join(', ');
|
||||
}
|
||||
|
||||
return (text.charAt(0).toUpperCase() + text.slice(1).toLowerCase()).replaceAll('_', ' ');
|
||||
};
|
||||
|
||||
@@ -245,11 +249,19 @@ export const DocumentHistorySheet = ({
|
||||
values={[
|
||||
{
|
||||
key: 'Old',
|
||||
value: DOCUMENT_AUTH_TYPES[data.from || '']?.value || 'None',
|
||||
value: Array.isArray(data.from)
|
||||
? data.from
|
||||
.map((f) => DOCUMENT_AUTH_TYPES[f]?.value || 'None')
|
||||
.join(', ')
|
||||
: DOCUMENT_AUTH_TYPES[data.from || '']?.value || 'None',
|
||||
},
|
||||
{
|
||||
key: 'New',
|
||||
value: DOCUMENT_AUTH_TYPES[data.to || '']?.value || 'None',
|
||||
value: Array.isArray(data.to)
|
||||
? data.to
|
||||
.map((f) => DOCUMENT_AUTH_TYPES[f]?.value || 'None')
|
||||
.join(', ')
|
||||
: DOCUMENT_AUTH_TYPES[data.to || '']?.value || 'None',
|
||||
},
|
||||
]}
|
||||
/>
|
||||
|
||||
@@ -19,7 +19,7 @@ export type DocumentPageViewButtonProps = {
|
||||
document: Document & {
|
||||
user: Pick<User, 'id' | 'name' | 'email'>;
|
||||
recipients: Recipient[];
|
||||
team: Pick<Team, 'id' | 'url'> | null;
|
||||
team: Pick<Team, 'id' | 'url'>;
|
||||
};
|
||||
};
|
||||
|
||||
@@ -37,10 +37,8 @@ export const DocumentPageViewButton = ({ document }: DocumentPageViewButtonProps
|
||||
const isSigned = recipient?.signingStatus === SigningStatus.SIGNED;
|
||||
const role = recipient?.role;
|
||||
|
||||
const documentsPath = formatDocumentsPath(document.team?.url);
|
||||
const formatPath = document.folderId
|
||||
? `${documentsPath}/f/${document.folderId}/${document.id}/edit`
|
||||
: `${documentsPath}/${document.id}/edit`;
|
||||
const documentsPath = formatDocumentsPath(document.team.url);
|
||||
const formatPath = `${documentsPath}/${document.id}/edit`;
|
||||
|
||||
const onDownloadClick = async () => {
|
||||
try {
|
||||
|
||||
@@ -36,7 +36,7 @@ import { DocumentDeleteDialog } from '~/components/dialogs/document-delete-dialo
|
||||
import { DocumentDuplicateDialog } from '~/components/dialogs/document-duplicate-dialog';
|
||||
import { DocumentResendDialog } from '~/components/dialogs/document-resend-dialog';
|
||||
import { DocumentRecipientLinkCopyDialog } from '~/components/general/document/document-recipient-link-copy-dialog';
|
||||
import { useOptionalCurrentTeam } from '~/providers/team';
|
||||
import { useCurrentTeam } from '~/providers/team';
|
||||
|
||||
export type DocumentPageViewDropdownProps = {
|
||||
document: Document & {
|
||||
@@ -52,7 +52,7 @@ export const DocumentPageViewDropdown = ({ document }: DocumentPageViewDropdownP
|
||||
const { _ } = useLingui();
|
||||
|
||||
const navigate = useNavigate();
|
||||
const team = useOptionalCurrentTeam();
|
||||
const team = useCurrentTeam();
|
||||
|
||||
const [isDeleteDialogOpen, setDeleteDialogOpen] = useState(false);
|
||||
const [isDuplicateDialogOpen, setDuplicateDialogOpen] = useState(false);
|
||||
@@ -67,7 +67,7 @@ export const DocumentPageViewDropdown = ({ document }: DocumentPageViewDropdownP
|
||||
const isCurrentTeamDocument = team && document.team?.url === team.url;
|
||||
const canManageDocument = Boolean(isOwner || isCurrentTeamDocument);
|
||||
|
||||
const documentsPath = formatDocumentsPath(team?.url);
|
||||
const documentsPath = formatDocumentsPath(team.url);
|
||||
|
||||
const onDownloadClick = async () => {
|
||||
try {
|
||||
|
||||
@@ -30,8 +30,12 @@ export const DocumentSearch = ({ initialValue = '' }: { initialValue?: string })
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
handleSearch(searchTerm);
|
||||
}, [debouncedSearchTerm]);
|
||||
const currentQueryParam = searchParams.get('query') || '';
|
||||
|
||||
if (debouncedSearchTerm !== currentQueryParam) {
|
||||
handleSearch(debouncedSearchTerm);
|
||||
}
|
||||
}, [debouncedSearchTerm, searchParams]);
|
||||
|
||||
return (
|
||||
<Input
|
||||
|
||||
@@ -3,12 +3,12 @@ import { useMemo, useState } from 'react';
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
import { Loader } from 'lucide-react';
|
||||
import { useNavigate, useParams } from 'react-router';
|
||||
import { match } from 'ts-pattern';
|
||||
|
||||
import { useLimits } from '@documenso/ee/server-only/limits/provider/client';
|
||||
import { useAnalytics } from '@documenso/lib/client-only/hooks/use-analytics';
|
||||
import { useCurrentOrganisation } from '@documenso/lib/client-only/providers/organisation';
|
||||
import { useSession } from '@documenso/lib/client-only/providers/session';
|
||||
import { APP_DOCUMENT_UPLOAD_SIZE_LIMIT } from '@documenso/lib/constants/app';
|
||||
import { DEFAULT_DOCUMENT_TIME_ZONE, TIME_ZONES } from '@documenso/lib/constants/time-zones';
|
||||
@@ -26,7 +26,7 @@ import {
|
||||
} from '@documenso/ui/primitives/tooltip';
|
||||
import { useToast } from '@documenso/ui/primitives/use-toast';
|
||||
|
||||
import { useOptionalCurrentTeam } from '~/providers/team';
|
||||
import { useCurrentTeam } from '~/providers/team';
|
||||
|
||||
export type DocumentUploadDropzoneProps = {
|
||||
className?: string;
|
||||
@@ -38,10 +38,11 @@ export const DocumentUploadDropzone = ({ className }: DocumentUploadDropzoneProp
|
||||
const { user } = useSession();
|
||||
const { folderId } = useParams();
|
||||
|
||||
const team = useOptionalCurrentTeam();
|
||||
const team = useCurrentTeam();
|
||||
|
||||
const navigate = useNavigate();
|
||||
const analytics = useAnalytics();
|
||||
const organisation = useCurrentOrganisation();
|
||||
|
||||
const userTimezone =
|
||||
TIME_ZONES.find((timezone) => timezone === Intl.DateTimeFormat().resolvedOptions().timeZone) ??
|
||||
@@ -54,10 +55,12 @@ export const DocumentUploadDropzone = ({ className }: DocumentUploadDropzoneProp
|
||||
const { mutateAsync: createDocument } = trpc.document.createDocument.useMutation();
|
||||
|
||||
const disabledMessage = useMemo(() => {
|
||||
if (organisation.subscription && remaining.documents === 0) {
|
||||
return msg`Document upload disabled due to unpaid invoices`;
|
||||
}
|
||||
|
||||
if (remaining.documents === 0) {
|
||||
return team
|
||||
? msg`Document upload disabled due to unpaid invoices`
|
||||
: msg`You have reached your document limit.`;
|
||||
return msg`You have reached your document limit.`;
|
||||
}
|
||||
|
||||
if (!user.emailVerified) {
|
||||
@@ -81,6 +84,8 @@ export const DocumentUploadDropzone = ({ className }: DocumentUploadDropzoneProp
|
||||
|
||||
void refreshLimits();
|
||||
|
||||
await navigate(`${formatDocumentsPath(team.url)}/${id}/edit`);
|
||||
|
||||
toast({
|
||||
title: _(msg`Document uploaded`),
|
||||
description: _(msg`Your document has been uploaded successfully.`),
|
||||
@@ -92,12 +97,6 @@ export const DocumentUploadDropzone = ({ className }: DocumentUploadDropzoneProp
|
||||
documentId: id,
|
||||
timestamp: new Date().toISOString(),
|
||||
});
|
||||
|
||||
await navigate(
|
||||
folderId
|
||||
? `${formatDocumentsPath(team?.url)}/f/${folderId}/${id}/edit`
|
||||
: `${formatDocumentsPath(team?.url)}/${id}/edit`,
|
||||
);
|
||||
} catch (err) {
|
||||
const error = AppError.parseError(err);
|
||||
|
||||
@@ -138,6 +137,7 @@ export const DocumentUploadDropzone = ({ className }: DocumentUploadDropzoneProp
|
||||
<TooltipTrigger asChild>
|
||||
<div>
|
||||
<DocumentDropzone
|
||||
loading={isLoading}
|
||||
disabled={remaining.documents === 0 || !user.emailVerified}
|
||||
disabledMessage={disabledMessage}
|
||||
onDrop={onFileDrop}
|
||||
@@ -145,6 +145,7 @@ export const DocumentUploadDropzone = ({ className }: DocumentUploadDropzoneProp
|
||||
/>
|
||||
</div>
|
||||
</TooltipTrigger>
|
||||
|
||||
{team?.id === undefined &&
|
||||
remaining.documents > 0 &&
|
||||
Number.isFinite(remaining.documents) && (
|
||||
@@ -158,12 +159,6 @@ export const DocumentUploadDropzone = ({ className }: DocumentUploadDropzoneProp
|
||||
)}
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
|
||||
{isLoading && (
|
||||
<div className="bg-background/50 absolute inset-0 flex items-center justify-center rounded-lg">
|
||||
<Loader className="text-muted-foreground h-12 w-12 animate-spin" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user