mirror of
https://github.com/documenso/documenso.git
synced 2026-07-25 17:35:05 +10:00
Merge branch 'main' into feat/signing-required-field-colors
Resolves conflict in document-signing-field-container.tsx (kept HEAD's required-field-color logic) and applies biome formatting to the auto-merged envelope-signer-page-renderer.tsx.
This commit is contained in:
@@ -1,6 +1,5 @@
|
||||
import { match } from 'ts-pattern';
|
||||
|
||||
import { checkboxValidationSigns } from '@documenso/ui/primitives/document-flow/field-items-advanced-settings/constants';
|
||||
import { match } from 'ts-pattern';
|
||||
|
||||
import type { TCheckboxFieldMeta } from '../types/field-meta';
|
||||
|
||||
@@ -45,17 +44,13 @@ export const validateCheckboxField = (
|
||||
|
||||
switch (validation.value) {
|
||||
case '=':
|
||||
lengthCondition = isSigningPage
|
||||
? values.length !== validationLength
|
||||
: values.length < validationLength;
|
||||
lengthCondition = isSigningPage ? values.length !== validationLength : values.length < validationLength;
|
||||
break;
|
||||
case '>=':
|
||||
lengthCondition = values.length < validationLength;
|
||||
break;
|
||||
case '<=':
|
||||
lengthCondition = isSigningPage
|
||||
? values.length > validationLength
|
||||
: values.length < validationLength;
|
||||
lengthCondition = isSigningPage ? values.length > validationLength : values.length < validationLength;
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
@@ -32,12 +32,7 @@ const versionToFilenameSuffix = (version: DocumentVersion): string => {
|
||||
}
|
||||
};
|
||||
|
||||
export const downloadPDF = async ({
|
||||
envelopeItem,
|
||||
token,
|
||||
fileName,
|
||||
version = 'signed',
|
||||
}: DownloadPDFProps) => {
|
||||
export const downloadPDF = async ({ envelopeItem, token, fileName, version = 'signed' }: DownloadPDFProps) => {
|
||||
const downloadUrl = getEnvelopeItemPdfUrl({
|
||||
type: 'download',
|
||||
envelopeItem: envelopeItem,
|
||||
|
||||
@@ -1,7 +1,15 @@
|
||||
import { posthog } from 'posthog-js';
|
||||
|
||||
import { extractPostHogConfig } from '@documenso/lib/constants/feature-flags';
|
||||
|
||||
let posthogPromise: Promise<typeof import('posthog-js')> | null = null;
|
||||
|
||||
const getPosthog = async () => {
|
||||
if (!posthogPromise) {
|
||||
posthogPromise = import('posthog-js');
|
||||
}
|
||||
|
||||
return posthogPromise;
|
||||
};
|
||||
|
||||
export function useAnalytics() {
|
||||
// const featureFlags = useFeatureFlags();
|
||||
const isPostHogEnabled = extractPostHogConfig();
|
||||
@@ -17,7 +25,9 @@ export function useAnalytics() {
|
||||
return;
|
||||
}
|
||||
|
||||
posthog.capture(event, properties);
|
||||
void getPosthog().then(({ default: posthog }) => {
|
||||
posthog.capture(event, properties);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -31,7 +41,9 @@ export function useAnalytics() {
|
||||
return;
|
||||
}
|
||||
|
||||
posthog.captureException(error, properties);
|
||||
void getPosthog().then(({ default: posthog }) => {
|
||||
posthog.captureException(error, properties);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -5,10 +5,7 @@ type SaveRequest<T, R> = {
|
||||
onResponse?: (response: R) => void;
|
||||
};
|
||||
|
||||
export const useAutoSave = <T, R = void>(
|
||||
onSave: (data: T) => Promise<R>,
|
||||
options: { delay?: number } = {},
|
||||
) => {
|
||||
export const useAutoSave = <T, R = void>(onSave: (data: T) => Promise<R>, options: { delay?: number } = {}) => {
|
||||
const { delay = 2000 } = options;
|
||||
|
||||
const saveTimeoutRef = useRef<NodeJS.Timeout>();
|
||||
|
||||
@@ -11,8 +11,7 @@ export type UseCopyShareLinkOptions = {
|
||||
export function useCopyShareLink({ onSuccess, onError }: UseCopyShareLinkOptions) {
|
||||
const [, copyToClipboard] = useCopyToClipboard();
|
||||
|
||||
const { mutateAsync: createOrGetShareLink, isPending: isCreatingShareLink } =
|
||||
trpc.document.share.useMutation();
|
||||
const { mutateAsync: createOrGetShareLink, isPending: isCreatingShareLink } = trpc.document.share.useMutation();
|
||||
|
||||
/**
|
||||
* Copy a newly created, or pre-existing share link to the user's clipboard.
|
||||
|
||||
@@ -18,9 +18,7 @@ export function useCopyToClipboard(): [CopiedValue, CopyFn] {
|
||||
|
||||
// Try to save to clipboard then save it in the state if worked
|
||||
try {
|
||||
isClipboardApiSupported
|
||||
? await handleClipboardApiCopy(text, blobType)
|
||||
: await handleWriteTextCopy(text);
|
||||
isClipboardApiSupported ? await handleClipboardApiCopy(text, blobType) : await handleWriteTextCopy(text);
|
||||
|
||||
setCopiedText(await text);
|
||||
return true;
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { useCallback } from 'react';
|
||||
|
||||
import { getBoundingClientRect } from '@documenso/lib/client-only/get-bounding-client-rect';
|
||||
import { useCallback } from 'react';
|
||||
|
||||
export const useDocumentElement = () => {
|
||||
/**
|
||||
@@ -15,9 +14,7 @@ export const useDocumentElement = () => {
|
||||
|
||||
const $page =
|
||||
target.closest<HTMLElement>(pageSelector) ??
|
||||
document
|
||||
.elementsFromPoint(event.clientX, event.clientY)
|
||||
.find((el) => el.matches(pageSelector));
|
||||
document.elementsFromPoint(event.clientX, event.clientY).find((el) => el.matches(pageSelector));
|
||||
|
||||
if (!$page) {
|
||||
return null;
|
||||
@@ -31,19 +28,9 @@ export const useDocumentElement = () => {
|
||||
* as a percentage of the page width and height.
|
||||
*/
|
||||
const getFieldPosition = (page: HTMLElement, field: HTMLElement) => {
|
||||
const {
|
||||
top: pageTop,
|
||||
left: pageLeft,
|
||||
height: pageHeight,
|
||||
width: pageWidth,
|
||||
} = getBoundingClientRect(page);
|
||||
const { top: pageTop, left: pageLeft, height: pageHeight, width: pageWidth } = getBoundingClientRect(page);
|
||||
|
||||
const {
|
||||
top: fieldTop,
|
||||
left: fieldLeft,
|
||||
height: fieldHeight,
|
||||
width: fieldWidth,
|
||||
} = getBoundingClientRect(field);
|
||||
const { top: fieldTop, left: fieldLeft, height: fieldHeight, width: fieldWidth } = getBoundingClientRect(field);
|
||||
|
||||
return {
|
||||
x: ((fieldLeft - pageLeft) / pageWidth) * 100,
|
||||
@@ -60,31 +47,28 @@ export const useDocumentElement = () => {
|
||||
* @param mouseWidth The artifical width of the mouse.
|
||||
* @param mouseHeight The artifical height of the mouse.
|
||||
*/
|
||||
const isWithinPageBounds = useCallback(
|
||||
(event: MouseEvent, pageSelector: string, mouseWidth = 0, mouseHeight = 0) => {
|
||||
const $page = getPage(event, pageSelector);
|
||||
const isWithinPageBounds = useCallback((event: MouseEvent, pageSelector: string, mouseWidth = 0, mouseHeight = 0) => {
|
||||
const $page = getPage(event, pageSelector);
|
||||
|
||||
if (!$page) {
|
||||
return false;
|
||||
}
|
||||
if (!$page) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const { top, left, height, width } = $page.getBoundingClientRect();
|
||||
const { top, left, height, width } = $page.getBoundingClientRect();
|
||||
|
||||
const halfMouseWidth = mouseWidth / 2;
|
||||
const halfMouseHeight = mouseHeight / 2;
|
||||
const halfMouseWidth = mouseWidth / 2;
|
||||
const halfMouseHeight = mouseHeight / 2;
|
||||
|
||||
if (event.clientY > top + height - halfMouseHeight || event.clientY < top + halfMouseHeight) {
|
||||
return false;
|
||||
}
|
||||
if (event.clientY > top + height - halfMouseHeight || event.clientY < top + halfMouseHeight) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (event.clientX > left + width - halfMouseWidth || event.clientX < left + halfMouseWidth) {
|
||||
return false;
|
||||
}
|
||||
if (event.clientX > left + width - halfMouseWidth || event.clientX < left + halfMouseWidth) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
[],
|
||||
);
|
||||
return true;
|
||||
}, []);
|
||||
|
||||
return {
|
||||
getPage,
|
||||
|
||||
@@ -1,15 +1,13 @@
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import type { Field } from '@prisma/client';
|
||||
import { FieldType } from '@prisma/client';
|
||||
import { useFieldArray, useForm } from 'react-hook-form';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { getPdfPagesCount } from '@documenso/lib/constants/pdf-viewer';
|
||||
import type { TEditorEnvelope } from '@documenso/lib/types/envelope-editor';
|
||||
import { ZFieldMetaSchema } from '@documenso/lib/types/field-meta';
|
||||
import { nanoid } from '@documenso/lib/universal/id';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import type { Field } from '@prisma/client';
|
||||
import { FieldType } from '@prisma/client';
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { useFieldArray, useForm } from 'react-hook-form';
|
||||
import { z } from 'zod';
|
||||
|
||||
export const ZLocalFieldSchema = z.object({
|
||||
// This is the actual ID of the field if created.
|
||||
@@ -67,10 +65,7 @@ type UseEditorFieldsResponse = {
|
||||
resetForm: (fields?: Field[]) => void;
|
||||
};
|
||||
|
||||
export const useEditorFields = ({
|
||||
envelope,
|
||||
handleFieldsUpdate,
|
||||
}: EditorFieldsProps): UseEditorFieldsResponse => {
|
||||
export const useEditorFields = ({ envelope, handleFieldsUpdate }: EditorFieldsProps): UseEditorFieldsResponse => {
|
||||
const [selectedFieldFormId, setSelectedFieldFormId] = useState<string | null>(null);
|
||||
const [selectedRecipientId, setSelectedRecipientId] = useState<number | null>(null);
|
||||
|
||||
@@ -123,9 +118,7 @@ export const useEditorFields = ({
|
||||
}
|
||||
|
||||
const foundField = localFields.find((field) => field.formId === formId);
|
||||
const recipient = envelope.recipients.find(
|
||||
(recipient) => recipient.id === foundField?.recipientId,
|
||||
);
|
||||
const recipient = envelope.recipients.find((recipient) => recipient.id === foundField?.recipientId);
|
||||
|
||||
if (recipient) {
|
||||
setSelectedRecipient(recipient.id);
|
||||
@@ -319,9 +312,7 @@ export const useEditorFields = ({
|
||||
};
|
||||
};
|
||||
|
||||
const restrictFieldPosValues = (
|
||||
field: Pick<TLocalField, 'positionX' | 'positionY' | 'width' | 'height'>,
|
||||
) => {
|
||||
const restrictFieldPosValues = (field: Pick<TLocalField, 'positionX' | 'positionY' | 'width' | 'height'>) => {
|
||||
return {
|
||||
positionX: Math.max(0, Math.min(100, field.positionX)),
|
||||
positionY: Math.max(0, Math.min(100, field.positionY)),
|
||||
|
||||
@@ -1,19 +1,14 @@
|
||||
import { useId } from 'react';
|
||||
|
||||
import { ZRecipientActionAuthTypesSchema, ZRecipientAuthOptionsSchema } from '@documenso/lib/types/document-auth';
|
||||
import type { TEditorEnvelope } from '@documenso/lib/types/envelope-editor';
|
||||
import { ZRecipientEmailSchema } from '@documenso/lib/types/recipient';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { DocumentSigningOrder, RecipientRole } from '@prisma/client';
|
||||
import { useId } from 'react';
|
||||
import type { UseFormReturn } from 'react-hook-form';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { prop, sortBy } from 'remeda';
|
||||
import { z } from 'zod';
|
||||
|
||||
import {
|
||||
ZRecipientActionAuthTypesSchema,
|
||||
ZRecipientAuthOptionsSchema,
|
||||
} from '@documenso/lib/types/document-auth';
|
||||
import type { TEditorEnvelope } from '@documenso/lib/types/envelope-editor';
|
||||
import { ZRecipientEmailSchema } from '@documenso/lib/types/recipient';
|
||||
|
||||
const LocalRecipientSchema = z.object({
|
||||
formId: z.string().min(1),
|
||||
id: z.number().optional(),
|
||||
@@ -48,9 +43,7 @@ type UseEditorRecipientsResponse = {
|
||||
resetForm: (options?: ResetFormOptions) => void;
|
||||
};
|
||||
|
||||
export const useEditorRecipients = ({
|
||||
envelope,
|
||||
}: EditorRecipientsProps): UseEditorRecipientsResponse => {
|
||||
export const useEditorRecipients = ({ envelope }: EditorRecipientsProps): UseEditorRecipientsResponse => {
|
||||
const initialId = useId();
|
||||
|
||||
const generateDefaultValues = (options?: ResetFormOptions) => {
|
||||
@@ -83,8 +76,7 @@ export const useEditorRecipients = ({
|
||||
return {
|
||||
signers,
|
||||
signingOrder: documentMeta?.signingOrder ?? envelope.documentMeta.signingOrder,
|
||||
allowDictateNextSigner:
|
||||
documentMeta?.allowDictateNextSigner ?? envelope.documentMeta.allowDictateNextSigner,
|
||||
allowDictateNextSigner: documentMeta?.allowDictateNextSigner ?? envelope.documentMeta.allowDictateNextSigner,
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
|
||||
import { getBoundingClientRect } from '@documenso/lib/client-only/get-bounding-client-rect';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
|
||||
export const useElementBounds = (elementOrSelector: HTMLElement | string, withScroll = false) => {
|
||||
const [bounds, setBounds] = useState({
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/* eslint-disable @typescript-eslint/consistent-type-assertions */
|
||||
import { RefObject, useEffect, useState } from 'react';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
/**
|
||||
* Calculate the width and height of a text element.
|
||||
|
||||
@@ -15,7 +15,9 @@ export function useEnvelopeAutosave<T>(saveFn: (data: T) => Promise<void>, delay
|
||||
// A debounce or promise means something is pending
|
||||
setIsPending(true);
|
||||
|
||||
if (timeoutRef.current) clearTimeout(timeoutRef.current);
|
||||
if (timeoutRef.current) {
|
||||
clearTimeout(timeoutRef.current);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
||||
timeoutRef.current = setTimeout(async () => {
|
||||
|
||||
@@ -1,16 +1,9 @@
|
||||
import { getBoundingClientRect } from '@documenso/lib/client-only/get-bounding-client-rect';
|
||||
import { PDF_VIEWER_CONTENT_SELECTOR, PDF_VIEWER_PAGE_SELECTOR } from '@documenso/lib/constants/pdf-viewer';
|
||||
import type { Field } from '@prisma/client';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
|
||||
import type { Field } from '@prisma/client';
|
||||
|
||||
import { getBoundingClientRect } from '@documenso/lib/client-only/get-bounding-client-rect';
|
||||
import {
|
||||
PDF_VIEWER_CONTENT_SELECTOR,
|
||||
PDF_VIEWER_PAGE_SELECTOR,
|
||||
} from '@documenso/lib/constants/pdf-viewer';
|
||||
|
||||
export const useFieldPageCoords = (
|
||||
field: Pick<Field, 'positionX' | 'positionY' | 'width' | 'height' | 'page'>,
|
||||
) => {
|
||||
export const useFieldPageCoords = (field: Pick<Field, 'positionX' | 'positionY' | 'width' | 'height' | 'page'>) => {
|
||||
const [coords, setCoords] = useState({
|
||||
x: 0,
|
||||
y: 0,
|
||||
@@ -19,9 +12,7 @@ export const useFieldPageCoords = (
|
||||
});
|
||||
|
||||
const calculateCoords = useCallback(() => {
|
||||
const $page = document.querySelector<HTMLElement>(
|
||||
`${PDF_VIEWER_PAGE_SELECTOR}[data-page-number="${field.page}"]`,
|
||||
);
|
||||
const $page = document.querySelector<HTMLElement>(`${PDF_VIEWER_PAGE_SELECTOR}[data-page-number="${field.page}"]`);
|
||||
|
||||
if (!$page) {
|
||||
return;
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
import { PDF_VIEWER_CONTENT_SELECTOR, PDF_VIEWER_PAGE_SELECTOR } from '@documenso/lib/constants/pdf-viewer';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
import {
|
||||
PDF_VIEWER_CONTENT_SELECTOR,
|
||||
PDF_VIEWER_PAGE_SELECTOR,
|
||||
} from '@documenso/lib/constants/pdf-viewer';
|
||||
|
||||
/**
|
||||
* Returns whether the PDF page element for the given page number is currently
|
||||
* present in the DOM. With virtual list rendering only pages near the viewport
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import Konva from 'konva';
|
||||
import { useEffect, useMemo, useRef } from 'react';
|
||||
|
||||
import Konva from 'konva';
|
||||
|
||||
import { type PageRenderData } from '../providers/envelope-render-provider';
|
||||
import type { PageRenderData } from '../providers/envelope-render-provider';
|
||||
|
||||
type RenderFunction = (props: { stage: Konva.Stage; pageLayer: Konva.Layer }) => void;
|
||||
|
||||
@@ -66,7 +65,7 @@ export const usePageRenderer = (renderFunction: RenderFunction, pageData: PageRe
|
||||
pageLayer: pageLayer.current,
|
||||
});
|
||||
|
||||
void document.fonts.ready.then(function () {
|
||||
void document.fonts.ready.then(() => {
|
||||
pageLayer.current?.batchDraw();
|
||||
});
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import * as React from 'react';
|
||||
import type { Dispatch, SetStateAction } from 'react';
|
||||
import * as React from 'react';
|
||||
|
||||
function dispatchStorageEvent(key: string, newValue: string | null) {
|
||||
window.dispatchEvent(new StorageEvent('storage', { key, newValue }));
|
||||
@@ -25,20 +25,13 @@ const useSessionStorageSubscribe = (callback: (event: StorageEvent) => void) =>
|
||||
return () => window.removeEventListener('storage', callback);
|
||||
};
|
||||
|
||||
export function useSessionStorage<T>(
|
||||
key: string,
|
||||
initialValue: T,
|
||||
): [T, Dispatch<SetStateAction<T>>] {
|
||||
export function useSessionStorage<T>(key: string, initialValue: T): [T, Dispatch<SetStateAction<T>>] {
|
||||
const serializedInitialValue = JSON.stringify(initialValue);
|
||||
|
||||
const getSnapshot = () => getSessionStorageItem(key);
|
||||
const getServerSnapshot = () => serializedInitialValue;
|
||||
|
||||
const store = React.useSyncExternalStore(
|
||||
useSessionStorageSubscribe,
|
||||
getSnapshot,
|
||||
getServerSnapshot,
|
||||
);
|
||||
const store = React.useSyncExternalStore(useSessionStorageSubscribe, getSnapshot, getServerSnapshot);
|
||||
|
||||
const setState: Dispatch<SetStateAction<T>> = React.useCallback(
|
||||
(v) => {
|
||||
|
||||
@@ -1,9 +1,3 @@
|
||||
import React, { createContext, useCallback, useContext, useMemo, useRef, useState } from 'react';
|
||||
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
import { EnvelopeType, Prisma, ReadStatus, SendStatus, SigningStatus } from '@prisma/client';
|
||||
import { useSearchParams } from 'react-router';
|
||||
|
||||
import { DO_NOT_INVALIDATE_QUERY_ON_MUTATION } from '@documenso/lib/constants/trpc';
|
||||
import {
|
||||
DEFAULT_EDITOR_CONFIG,
|
||||
@@ -17,11 +11,16 @@ import type { TUpdateEnvelopeRequest } from '@documenso/trpc/server/envelope-rou
|
||||
import type { TRecipientColor } from '@documenso/ui/lib/recipient-colors';
|
||||
import { getRecipientColor } from '@documenso/ui/lib/recipient-colors';
|
||||
import { useToast } from '@documenso/ui/primitives/use-toast';
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
import { EnvelopeType, Prisma, ReadStatus, SendStatus, SigningStatus } from '@prisma/client';
|
||||
import type React from 'react';
|
||||
import { createContext, useCallback, useContext, useMemo, useRef, useState } from 'react';
|
||||
import { useSearchParams } from 'react-router';
|
||||
|
||||
import type { TDocumentEmailSettings } from '../../types/document-email';
|
||||
import { formatDocumentsPath, formatTemplatesPath } from '../../utils/teams';
|
||||
import { useEditorFields } from '../hooks/use-editor-fields';
|
||||
import type { TLocalField } from '../hooks/use-editor-fields';
|
||||
import { useEditorFields } from '../hooks/use-editor-fields';
|
||||
import { useEditorRecipients } from '../hooks/use-editor-recipients';
|
||||
import { useEnvelopeAutosave } from '../hooks/use-envelope-autosave';
|
||||
|
||||
@@ -176,16 +175,12 @@ export const EnvelopeEditorProvider = ({
|
||||
setEnvelope((prev) => ({
|
||||
...prev,
|
||||
recipients,
|
||||
fields: prev.fields.filter((field) =>
|
||||
recipients.some((recipient) => recipient.id === field.recipientId),
|
||||
),
|
||||
fields: prev.fields.filter((field) => recipients.some((recipient) => recipient.id === field.recipientId)),
|
||||
}));
|
||||
|
||||
// Reset the local fields to ensure deleted recipient fields are removed.
|
||||
editorFields.resetForm(
|
||||
envelope.fields.filter((field) =>
|
||||
recipients.some((recipient) => recipient.id === field.recipientId),
|
||||
),
|
||||
envelope.fields.filter((field) => recipients.some((recipient) => recipient.id === field.recipientId)),
|
||||
);
|
||||
|
||||
setAutosaveError(false);
|
||||
@@ -203,9 +198,7 @@ export const EnvelopeEditorProvider = ({
|
||||
}
|
||||
}, 1000);
|
||||
|
||||
const setRecipientsAsync = async (
|
||||
localRecipients: TSetEnvelopeRecipientsRequest['recipients'],
|
||||
) => {
|
||||
const setRecipientsAsync = async (localRecipients: TSetEnvelopeRecipientsRequest['recipients']) => {
|
||||
setRecipientsDebounced(localRecipients);
|
||||
await flushSetRecipients();
|
||||
};
|
||||
@@ -346,8 +339,7 @@ export const EnvelopeEditorProvider = ({
|
||||
};
|
||||
|
||||
const getRecipientColorKey = useCallback(
|
||||
(recipientId: number) =>
|
||||
getRecipientColor(envelope.recipients.findIndex((r) => r.id === recipientId)),
|
||||
(recipientId: number) => getRecipientColor(envelope.recipients.findIndex((r) => r.id === recipientId)),
|
||||
[envelope.recipients],
|
||||
);
|
||||
|
||||
@@ -536,10 +528,7 @@ const mapLocalRecipientsToRecipients = ({
|
||||
documentDeletedAt: foundRecipient?.documentDeletedAt || null,
|
||||
expired: foundRecipient?.expired || null,
|
||||
signedAt: foundRecipient?.signedAt || null,
|
||||
authOptions:
|
||||
recipient.actionAuth.length > 0
|
||||
? { actionAuth: recipient.actionAuth, accessAuth: [] }
|
||||
: null,
|
||||
authOptions: recipient.actionAuth.length > 0 ? { actionAuth: recipient.actionAuth, accessAuth: [] } : null,
|
||||
signingOrder: recipient.signingOrder ?? null,
|
||||
rejectionReason: foundRecipient?.rejectionReason || null,
|
||||
role: recipient.role,
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
import { createContext, useCallback, useContext, useEffect, useMemo, useState } from 'react';
|
||||
import React from 'react';
|
||||
|
||||
import { type Field, type Recipient } from '@prisma/client';
|
||||
|
||||
import type { DocumentDataVersion } from '@documenso/lib/types/document';
|
||||
import { getDocumentDataUrl } from '@documenso/lib/utils/envelope-download';
|
||||
import type { TRecipientColor } from '@documenso/ui/lib/recipient-colors';
|
||||
import { getRecipientColor } from '@documenso/ui/lib/recipient-colors';
|
||||
import type { Field, Recipient } from '@prisma/client';
|
||||
import type React from 'react';
|
||||
import { createContext, useCallback, useContext, useEffect, useMemo, useState } from 'react';
|
||||
|
||||
import type { TEnvelope } from '../../types/envelope';
|
||||
import type { FieldRenderMode } from '../../universal/field-renderer/render-field';
|
||||
@@ -215,10 +213,7 @@ export const EnvelopeRenderProvider = ({
|
||||
}
|
||||
}, [currentItem, envelopeItems]);
|
||||
|
||||
const recipientIds = useMemo(
|
||||
() => recipients.map((recipient) => recipient.id).sort(),
|
||||
[recipients],
|
||||
);
|
||||
const recipientIds = useMemo(() => recipients.map((recipient) => recipient.id).sort(), [recipients]);
|
||||
|
||||
const getRecipientColorKey = useCallback(
|
||||
(recipientId: number) => getRecipientColor(recipientIds.findIndex((id) => id === recipientId)),
|
||||
|
||||
@@ -1,11 +1,7 @@
|
||||
import type { I18n, Messages } from '@lingui/core';
|
||||
import { setupI18n } from '@lingui/core';
|
||||
|
||||
import {
|
||||
APP_I18N_OPTIONS,
|
||||
SUPPORTED_LANGUAGE_CODES,
|
||||
isValidLanguageCode,
|
||||
} from '../../constants/i18n';
|
||||
import { APP_I18N_OPTIONS, isValidLanguageCode, SUPPORTED_LANGUAGE_CODES } from '../../constants/i18n';
|
||||
import { env } from '../../utils/env';
|
||||
import { remember } from '../../utils/remember';
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { useState } from 'react';
|
||||
|
||||
import { type Messages, setupI18n } from '@lingui/core';
|
||||
import { I18nProvider } from '@lingui/react';
|
||||
import { useState } from 'react';
|
||||
|
||||
import type { I18nLocaleData } from '../../constants/i18n';
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { createContext, useContext } from 'react';
|
||||
import React from 'react';
|
||||
|
||||
import type { OrganisationSession } from '@documenso/trpc/server/organisation-router/get-organisation-session.types';
|
||||
import type React from 'react';
|
||||
import { createContext, useContext } from 'react';
|
||||
|
||||
type OrganisationProviderValue = OrganisationSession;
|
||||
|
||||
@@ -27,7 +26,5 @@ export const useOptionalCurrentOrganisation = () => {
|
||||
};
|
||||
|
||||
export const OrganisationProvider = ({ children, organisation }: OrganisationProviderProps) => {
|
||||
return (
|
||||
<OrganisationContext.Provider value={organisation}>{children}</OrganisationContext.Provider>
|
||||
);
|
||||
return <OrganisationContext.Provider value={organisation}>{children}</OrganisationContext.Provider>;
|
||||
};
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
import { createContext, useCallback, useContext, useEffect, useState } from 'react';
|
||||
import React from 'react';
|
||||
|
||||
import type { Session } from '@prisma/client';
|
||||
import { useLocation } from 'react-router';
|
||||
|
||||
import { authClient } from '@documenso/auth/client';
|
||||
import type { SessionUser } from '@documenso/auth/server/lib/session/session';
|
||||
import { trpc } from '@documenso/trpc/client';
|
||||
import type { TGetOrganisationSessionResponse } from '@documenso/trpc/server/organisation-router/get-organisation-session.types';
|
||||
import type { Session } from '@prisma/client';
|
||||
import type React from 'react';
|
||||
import { createContext, useCallback, useContext, useEffect, useState } from 'react';
|
||||
import { useLocation } from 'react-router';
|
||||
|
||||
import { SKIP_QUERY_BATCH_META } from '../../constants/trpc';
|
||||
|
||||
@@ -74,8 +72,7 @@ export const SessionProvider = ({ children, initialSession }: SessionProviderPro
|
||||
.catch((e) => {
|
||||
const errorMessage = typeof e.message === 'string' ? e.message.toLowerCase() : '';
|
||||
|
||||
const isNetworkError =
|
||||
errorMessage.includes('networkerror') || errorMessage.includes('failed to fetch');
|
||||
const isNetworkError = errorMessage.includes('networkerror') || errorMessage.includes('failed to fetch');
|
||||
|
||||
// If the error is a transient network/abort error (e.g. page refresh while
|
||||
// fetch was in-flight), return null to signal we should skip the state update.
|
||||
|
||||
@@ -1,11 +1,5 @@
|
||||
import type { Recipient } from '@prisma/client';
|
||||
import {
|
||||
DocumentDistributionMethod,
|
||||
ReadStatus,
|
||||
RecipientRole,
|
||||
SendStatus,
|
||||
SigningStatus,
|
||||
} from '@prisma/client';
|
||||
import { DocumentDistributionMethod, ReadStatus, RecipientRole, SendStatus, SigningStatus } from '@prisma/client';
|
||||
|
||||
type RecipientForType = Pick<Recipient, 'role' | 'signingStatus' | 'readStatus' | 'sendStatus'>;
|
||||
|
||||
@@ -29,10 +23,7 @@ export const getRecipientType = (
|
||||
return RecipientStatusType.REJECTED;
|
||||
}
|
||||
|
||||
if (
|
||||
recipient.readStatus === ReadStatus.OPENED &&
|
||||
recipient.signingStatus === SigningStatus.NOT_SIGNED
|
||||
) {
|
||||
if (recipient.readStatus === ReadStatus.OPENED && recipient.signingStatus === SigningStatus.NOT_SIGNED) {
|
||||
return RecipientStatusType.OPENED;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
import { env } from '@documenso/lib/utils/env';
|
||||
|
||||
export const APP_DOCUMENT_UPLOAD_SIZE_LIMIT =
|
||||
Number(env('NEXT_PUBLIC_DOCUMENT_SIZE_UPLOAD_LIMIT')) || 50;
|
||||
export const APP_DOCUMENT_UPLOAD_SIZE_LIMIT = Number(env('NEXT_PUBLIC_DOCUMENT_SIZE_UPLOAD_LIMIT')) || 50;
|
||||
|
||||
export const NEXT_PUBLIC_WEBAPP_URL = () =>
|
||||
env('NEXT_PUBLIC_WEBAPP_URL') ?? 'http://localhost:3000';
|
||||
export const NEXT_PUBLIC_WEBAPP_URL = () => env('NEXT_PUBLIC_WEBAPP_URL') ?? 'http://localhost:3000';
|
||||
|
||||
export const NEXT_PUBLIC_SIGNING_CONTACT_INFO = () =>
|
||||
env('NEXT_PUBLIC_SIGNING_CONTACT_INFO') ?? NEXT_PUBLIC_WEBAPP_URL();
|
||||
@@ -22,11 +20,9 @@ export const API_V2_URL = '/api/v2';
|
||||
|
||||
export const SUPPORT_EMAIL = env('NEXT_PUBLIC_SUPPORT_EMAIL') ?? 'support@documenso.com';
|
||||
|
||||
export const USE_INTERNAL_URL_BROWSERLESS = () =>
|
||||
env('NEXT_PUBLIC_USE_INTERNAL_URL_BROWSERLESS') === 'true';
|
||||
export const USE_INTERNAL_URL_BROWSERLESS = () => env('NEXT_PUBLIC_USE_INTERNAL_URL_BROWSERLESS') === 'true';
|
||||
|
||||
export const IS_AI_FEATURES_CONFIGURED = () =>
|
||||
!!env('GOOGLE_VERTEX_PROJECT_ID') && !!env('GOOGLE_VERTEX_API_KEY');
|
||||
export const IS_AI_FEATURES_CONFIGURED = () => !!env('GOOGLE_VERTEX_PROJECT_ID') && !!env('GOOGLE_VERTEX_API_KEY');
|
||||
|
||||
/**
|
||||
* Temporary flag to toggle between Playwright-based and Konva-based PDF generation
|
||||
@@ -34,8 +30,6 @@ export const IS_AI_FEATURES_CONFIGURED = () =>
|
||||
*
|
||||
* @deprecated This is a temporary flag and will be removed once Konva-based generation is stable.
|
||||
*/
|
||||
export const NEXT_PRIVATE_USE_PLAYWRIGHT_PDF = () =>
|
||||
env('NEXT_PRIVATE_USE_PLAYWRIGHT_PDF') === 'true';
|
||||
export const NEXT_PRIVATE_USE_PLAYWRIGHT_PDF = () => env('NEXT_PRIVATE_USE_PLAYWRIGHT_PDF') === 'true';
|
||||
|
||||
export const NEXT_PRIVATE_SIGNING_TIMESTAMP_AUTHORITY = () =>
|
||||
env('NEXT_PRIVATE_SIGNING_TIMESTAMP_AUTHORITY');
|
||||
export const NEXT_PRIVATE_SIGNING_TIMESTAMP_AUTHORITY = () => env('NEXT_PRIVATE_SIGNING_TIMESTAMP_AUTHORITY');
|
||||
|
||||
@@ -34,9 +34,7 @@ export const IS_MICROSOFT_SSO_ENABLED = Boolean(
|
||||
);
|
||||
|
||||
export const IS_OIDC_SSO_ENABLED = Boolean(
|
||||
env('NEXT_PRIVATE_OIDC_WELL_KNOWN') &&
|
||||
env('NEXT_PRIVATE_OIDC_CLIENT_ID') &&
|
||||
env('NEXT_PRIVATE_OIDC_CLIENT_SECRET'),
|
||||
env('NEXT_PRIVATE_OIDC_WELL_KNOWN') && env('NEXT_PRIVATE_OIDC_CLIENT_ID') && env('NEXT_PRIVATE_OIDC_CLIENT_SECRET'),
|
||||
);
|
||||
|
||||
export const OIDC_PROVIDER_LABEL = env('NEXT_PRIVATE_OIDC_PROVIDER_LABEL');
|
||||
@@ -121,3 +119,22 @@ export const isEmailDomainAllowedForSignup = (email: string): boolean => {
|
||||
|
||||
return allowedDomains.includes(emailDomain);
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if signup is enabled for the given provider.
|
||||
* The master switch takes precedence over the per-provider flags.
|
||||
*/
|
||||
export const isSignupEnabledForProvider = (provider: 'email' | 'google' | 'microsoft' | 'oidc'): boolean => {
|
||||
if (env('NEXT_PUBLIC_DISABLE_SIGNUP') === 'true') {
|
||||
return false;
|
||||
}
|
||||
|
||||
const flagMap = {
|
||||
email: 'NEXT_PUBLIC_DISABLE_EMAIL_PASSWORD_SIGNUP',
|
||||
google: 'NEXT_PUBLIC_DISABLE_GOOGLE_SIGNUP',
|
||||
microsoft: 'NEXT_PUBLIC_DISABLE_MICROSOFT_SIGNUP',
|
||||
oidc: 'NEXT_PUBLIC_DISABLE_OIDC_SIGNUP',
|
||||
} as const;
|
||||
|
||||
return env(flagMap[provider]) !== 'true';
|
||||
};
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
/**
|
||||
* Maximum length (in characters) of the user-supplied custom CSS for branding.
|
||||
* Bound enforced at the TRPC request boundary on both the organisation and
|
||||
* team settings update routes. The sanitiser is run after this check; this
|
||||
* limit is purely a request-size guard.
|
||||
*
|
||||
* 256 KB — generous enough for hand-written branding CSS and the occasional
|
||||
* compiled-from-Tailwind-or-similar paste, while still keeping a request
|
||||
* cap so a malicious or runaway payload can't exhaust PostCSS/server memory.
|
||||
*/
|
||||
export const BRANDING_CSS_MAX_LENGTH = 256 * 1024;
|
||||
@@ -1,8 +1,7 @@
|
||||
import { DocumentVisibility } from '@documenso/lib/types/document-visibility';
|
||||
import type { MessageDescriptor } from '@lingui/core';
|
||||
import { msg } from '@lingui/core/macro';
|
||||
|
||||
import { DocumentVisibility } from '@documenso/lib/types/document-visibility';
|
||||
|
||||
import type { TDocumentVisibility } from '../types/document-visibility';
|
||||
|
||||
type DocumentVisibilityTypeData = {
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import type { MessageDescriptor } from '@lingui/core';
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { DocumentDistributionMethod, DocumentStatus } from '@prisma/client';
|
||||
|
||||
/**
|
||||
* Workaround for E2E tests to not import `msg`.
|
||||
*/
|
||||
import { DocumentSignatureType } from '@documenso/lib/utils/teams';
|
||||
import type { MessageDescriptor } from '@lingui/core';
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { DocumentDistributionMethod, DocumentStatus } from '@prisma/client';
|
||||
|
||||
export { DocumentSignatureType };
|
||||
|
||||
|
||||
@@ -19,10 +19,7 @@ export const ZEnvelopeExpirationPeriod = z.union([
|
||||
export type TEnvelopeExpirationPeriod = z.infer<typeof ZEnvelopeExpirationPeriod>;
|
||||
export type TEnvelopeExpirationDurationPeriod = z.infer<typeof ZEnvelopeExpirationDurationPeriod>;
|
||||
|
||||
const UNIT_TO_LUXON_KEY: Record<
|
||||
TEnvelopeExpirationDurationPeriod['unit'],
|
||||
keyof DurationLikeObject
|
||||
> = {
|
||||
const UNIT_TO_LUXON_KEY: Record<TEnvelopeExpirationDurationPeriod['unit'], keyof DurationLikeObject> = {
|
||||
day: 'days',
|
||||
week: 'weeks',
|
||||
month: 'months',
|
||||
@@ -34,9 +31,7 @@ export const DEFAULT_ENVELOPE_EXPIRATION_PERIOD: TEnvelopeExpirationDurationPeri
|
||||
amount: 3,
|
||||
};
|
||||
|
||||
export const getEnvelopeExpirationDuration = (
|
||||
period: TEnvelopeExpirationDurationPeriod,
|
||||
): Duration => {
|
||||
export const getEnvelopeExpirationDuration = (period: TEnvelopeExpirationDurationPeriod): Duration => {
|
||||
return Duration.fromObject({ [UNIT_TO_LUXON_KEY[period.unit]]: period.amount });
|
||||
};
|
||||
|
||||
|
||||
@@ -11,10 +11,7 @@ export const ZEnvelopeReminderDisabledPeriod = z.object({
|
||||
disabled: z.literal(true),
|
||||
});
|
||||
|
||||
export const ZEnvelopeReminderPeriod = z.union([
|
||||
ZEnvelopeReminderDurationPeriod,
|
||||
ZEnvelopeReminderDisabledPeriod,
|
||||
]);
|
||||
export const ZEnvelopeReminderPeriod = z.union([ZEnvelopeReminderDurationPeriod, ZEnvelopeReminderDisabledPeriod]);
|
||||
|
||||
export type TEnvelopeReminderPeriod = z.infer<typeof ZEnvelopeReminderPeriod>;
|
||||
export type TEnvelopeReminderDurationPeriod = z.infer<typeof ZEnvelopeReminderDurationPeriod>;
|
||||
@@ -39,12 +36,11 @@ export const DEFAULT_ENVELOPE_REMINDER_SETTINGS: TEnvelopeReminderSettings = {
|
||||
*/
|
||||
export const MAX_REMINDER_WINDOW_DAYS = 30;
|
||||
|
||||
const UNIT_TO_LUXON_KEY: Record<TEnvelopeReminderDurationPeriod['unit'], keyof DurationLikeObject> =
|
||||
{
|
||||
day: 'days',
|
||||
week: 'weeks',
|
||||
month: 'months',
|
||||
};
|
||||
const UNIT_TO_LUXON_KEY: Record<TEnvelopeReminderDurationPeriod['unit'], keyof DurationLikeObject> = {
|
||||
day: 'days',
|
||||
week: 'weeks',
|
||||
month: 'months',
|
||||
};
|
||||
|
||||
export const getEnvelopeReminderDuration = (period: TEnvelopeReminderDurationPeriod): Duration => {
|
||||
return Duration.fromObject({ [UNIT_TO_LUXON_KEY[period.unit]]: period.amount });
|
||||
@@ -75,9 +71,7 @@ export const resolveNextReminderAt = (options: {
|
||||
return null;
|
||||
}
|
||||
|
||||
const maxReminderAt = new Date(
|
||||
sentAt.getTime() + Duration.fromObject({ days: MAX_REMINDER_WINDOW_DAYS }).toMillis(),
|
||||
);
|
||||
const maxReminderAt = new Date(sentAt.getTime() + Duration.fromObject({ days: MAX_REMINDER_WINDOW_DAYS }).toMillis());
|
||||
|
||||
let candidate: Date;
|
||||
|
||||
|
||||
@@ -1,18 +1,6 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
export const SUPPORTED_LANGUAGE_CODES = [
|
||||
'de',
|
||||
'en',
|
||||
'fr',
|
||||
'es',
|
||||
'it',
|
||||
'nl',
|
||||
'pl',
|
||||
'pt-BR',
|
||||
'ja',
|
||||
'ko',
|
||||
'zh',
|
||||
] as const;
|
||||
export const SUPPORTED_LANGUAGE_CODES = ['de', 'en', 'fr', 'es', 'it', 'nl', 'pl', 'pt-BR', 'ja', 'ko', 'zh'] as const;
|
||||
|
||||
export type SupportedLanguageCodes = (typeof SUPPORTED_LANGUAGE_CODES)[number];
|
||||
|
||||
|
||||
@@ -6,19 +6,13 @@ import type { MessageDescriptor } from '@lingui/core';
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import type { OrganisationMemberRole } from '@prisma/client';
|
||||
|
||||
export const ORGANISATION_MEMBER_ROLE_MAP: Record<
|
||||
keyof typeof OrganisationMemberRole,
|
||||
MessageDescriptor
|
||||
> = {
|
||||
export const ORGANISATION_MEMBER_ROLE_MAP: Record<keyof typeof OrganisationMemberRole, MessageDescriptor> = {
|
||||
ADMIN: msg`Admin`,
|
||||
MANAGER: msg`Manager`,
|
||||
MEMBER: msg`Member`,
|
||||
};
|
||||
|
||||
export const EXTENDED_ORGANISATION_MEMBER_ROLE_MAP: Record<
|
||||
keyof typeof OrganisationMemberRole,
|
||||
MessageDescriptor
|
||||
> = {
|
||||
export const EXTENDED_ORGANISATION_MEMBER_ROLE_MAP: Record<keyof typeof OrganisationMemberRole, MessageDescriptor> = {
|
||||
ADMIN: msg`Organisation Admin`,
|
||||
MANAGER: msg`Organisation Manager`,
|
||||
MEMBER: msg`Organisation Member`,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { OrganisationGroupType, OrganisationMemberRole } from '@prisma/client';
|
||||
|
||||
export const ORGANISATION_URL_ROOT_REGEX = new RegExp('^/t/[^/]+/?$');
|
||||
export const ORGANISATION_URL_REGEX = new RegExp('^/t/[^/]+');
|
||||
export const ORGANISATION_URL_ROOT_REGEX = /^\/t\/[^/]+\/?$/;
|
||||
export const ORGANISATION_URL_REGEX = /^\/t\/[^/]+/;
|
||||
|
||||
export const ORGANISATION_INTERNAL_GROUPS: {
|
||||
organisationRole: OrganisationMemberRole;
|
||||
|
||||
@@ -5,9 +5,7 @@ export const PDF_VIEWER_PAGE_CLASSNAME = 'react-pdf__Page z-0';
|
||||
export const PDF_VIEWER_CONTENT_SELECTOR = '[data-pdf-content]';
|
||||
|
||||
export const getPdfPagesCount = () => {
|
||||
const pageCountAttr = document
|
||||
.querySelector(PDF_VIEWER_CONTENT_SELECTOR)
|
||||
?.getAttribute('data-page-count');
|
||||
const pageCountAttr = document.querySelector(PDF_VIEWER_CONTENT_SELECTOR)?.getAttribute('data-page-count');
|
||||
|
||||
const totalPages = Number(pageCountAttr);
|
||||
|
||||
|
||||
@@ -8,9 +8,8 @@ export const TEAM_MEMBER_ROLE_MAP: Record<keyof typeof TeamMemberRole, MessageDe
|
||||
MEMBER: msg`Member`,
|
||||
};
|
||||
|
||||
export const EXTENDED_TEAM_MEMBER_ROLE_MAP: Record<keyof typeof TeamMemberRole, MessageDescriptor> =
|
||||
{
|
||||
ADMIN: msg`Team Admin`,
|
||||
MANAGER: msg`Team Manager`,
|
||||
MEMBER: msg`Team Member`,
|
||||
};
|
||||
export const EXTENDED_TEAM_MEMBER_ROLE_MAP: Record<keyof typeof TeamMemberRole, MessageDescriptor> = {
|
||||
ADMIN: msg`Team Admin`,
|
||||
MANAGER: msg`Team Manager`,
|
||||
MEMBER: msg`Team Member`,
|
||||
};
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { DocumentVisibility, OrganisationGroupType, TeamMemberRole } from '@prisma/client';
|
||||
|
||||
export const TEAM_URL_ROOT_REGEX = new RegExp('^/t/[^/]+/?$');
|
||||
export const TEAM_URL_REGEX = new RegExp('^/t/[^/]+');
|
||||
export const TEAM_URL_ROOT_REGEX = /^\/t\/[^/]+\/?$/;
|
||||
export const TEAM_URL_REGEX = /^\/t\/[^/]+/;
|
||||
|
||||
export const LOWEST_TEAM_ROLE = TeamMemberRole.MEMBER;
|
||||
|
||||
@@ -34,11 +34,7 @@ export const TEAM_MEMBER_ROLE_PERMISSIONS_MAP = {
|
||||
} satisfies Record<string, TeamMemberRole[]>;
|
||||
|
||||
export const TEAM_DOCUMENT_VISIBILITY_MAP = {
|
||||
[TeamMemberRole.ADMIN]: [
|
||||
DocumentVisibility.ADMIN,
|
||||
DocumentVisibility.MANAGER_AND_ABOVE,
|
||||
DocumentVisibility.EVERYONE,
|
||||
],
|
||||
[TeamMemberRole.ADMIN]: [DocumentVisibility.ADMIN, DocumentVisibility.MANAGER_AND_ABOVE, DocumentVisibility.EVERYONE],
|
||||
[TeamMemberRole.MANAGER]: [DocumentVisibility.MANAGER_AND_ABOVE, DocumentVisibility.EVERYONE],
|
||||
[TeamMemberRole.MEMBER]: [DocumentVisibility.EVERYONE],
|
||||
} satisfies Record<TeamMemberRole, DocumentVisibility[]>;
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
import type { TCssVarsSchema } from '../types/css-vars';
|
||||
|
||||
/**
|
||||
* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||
*
|
||||
* KEEP THIS FILE IN SYNC WITH `packages/ui/styles/theme.css`.
|
||||
*
|
||||
* These are the light-mode default values for the CSS custom properties
|
||||
* defined under `:root` in the theme stylesheet, exposed here as hex strings
|
||||
* so they can be used as defaults for colour-picker UI components and other
|
||||
* places that don't render through CSS variables.
|
||||
*
|
||||
* If you change a value in `theme.css`, update it here too. There is NO
|
||||
* automated check linking the two files; they have drifted historically
|
||||
* and will drift again unless you update both.
|
||||
*
|
||||
* Computed via `colord({ h, s, l }).toHex()` — see the inline HSL comments
|
||||
* for the source-of-truth values from `theme.css`.
|
||||
*
|
||||
* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||
*/
|
||||
export const DEFAULT_BRAND_COLORS = {
|
||||
background: '#ffffff', // 0 0% 100%
|
||||
foreground: '#0f172a', // 222.2 47.4% 11.2%
|
||||
muted: '#f1f5f9', // 210 40% 96.1%
|
||||
mutedForeground: '#64748b', // 215.4 16.3% 46.9%
|
||||
popover: '#ffffff', // 0 0% 100%
|
||||
popoverForeground: '#0f172a', // 222.2 47.4% 11.2%
|
||||
card: '#ffffff', // 0 0% 100%
|
||||
cardBorder: '#e2e8f0', // 214.3 31.8% 91.4%
|
||||
cardForeground: '#0f172a', // 222.2 47.4% 11.2%
|
||||
fieldCard: '#e2f8d3', // 95 74% 90%
|
||||
fieldCardBorder: '#a2e771', // 95.08 71.08% 67.45%
|
||||
fieldCardForeground: '#0f172a', // 222.2 47.4% 11.2%
|
||||
widget: '#f7f7f7', // 0 0% 97%
|
||||
widgetForeground: '#f2f2f2', // 0 0% 95%
|
||||
border: '#e2e8f0', // 214.3 31.8% 91.4%
|
||||
input: '#e2e8f0', // 214.3 31.8% 91.4%
|
||||
primary: '#a2e771', // 95.08 71.08% 67.45%
|
||||
primaryForeground: '#162c07', // 95.08 71.08% 10%
|
||||
secondary: '#f1f5f9', // 210 40% 96.1%
|
||||
secondaryForeground: '#0f172a', // 222.2 47.4% 11.2%
|
||||
accent: '#f1f5f9', // 210 40% 96.1%
|
||||
accentForeground: '#0f172a', // 222.2 47.4% 11.2%
|
||||
destructive: '#ff0000', // 0 100% 50%
|
||||
destructiveForeground: '#f8fafc', // 210 40% 98%
|
||||
ring: '#a2e771', // 95.08 71.08% 67.45%
|
||||
warning: '#e1cb05', // 54 96% 45%
|
||||
envelopeEditorBackground: '#f8fafc', //210 40% 98.04%
|
||||
// `cardBorderTint` is intentionally excluded from the colour-picker UI:
|
||||
// unlike the rest of these tokens it is consumed via `rgb(var(--token))`
|
||||
// (not `hsl(...)`) and stored as raw RGB triplets in `theme.css`. It does
|
||||
// not flow through `toNativeCssVars` and is not user-customisable from the
|
||||
// branding form. `radius` is a length, not a colour, so it lives in
|
||||
// `DEFAULT_BRAND_RADIUS` below.
|
||||
} as const satisfies Record<keyof Omit<TCssVarsSchema, 'radius' | 'cardBorderTint'>, string>;
|
||||
|
||||
export const DEFAULT_BRAND_RADIUS = '0.5rem';
|
||||
@@ -5,53 +5,52 @@ import { z } from 'zod';
|
||||
* Generic application error codes.
|
||||
*/
|
||||
export enum AppErrorCode {
|
||||
'ALREADY_EXISTS' = 'ALREADY_EXISTS',
|
||||
'EXPIRED_CODE' = 'EXPIRED_CODE',
|
||||
'INVALID_BODY' = 'INVALID_BODY',
|
||||
'INVALID_REQUEST' = 'INVALID_REQUEST',
|
||||
'RECIPIENT_EXPIRED' = 'RECIPIENT_EXPIRED',
|
||||
'LIMIT_EXCEEDED' = 'LIMIT_EXCEEDED',
|
||||
'NOT_FOUND' = 'NOT_FOUND',
|
||||
'NOT_IMPLEMENTED' = 'NOT_IMPLEMENTED',
|
||||
'NOT_SETUP' = 'NOT_SETUP',
|
||||
'INVALID_CAPTCHA' = 'INVALID_CAPTCHA',
|
||||
'UNAUTHORIZED' = 'UNAUTHORIZED',
|
||||
'FORBIDDEN' = 'FORBIDDEN',
|
||||
'UNKNOWN_ERROR' = 'UNKNOWN_ERROR',
|
||||
'RETRY_EXCEPTION' = 'RETRY_EXCEPTION',
|
||||
'SCHEMA_FAILED' = 'SCHEMA_FAILED',
|
||||
'TOO_MANY_REQUESTS' = 'TOO_MANY_REQUESTS',
|
||||
'TWO_FACTOR_AUTH_FAILED' = 'TWO_FACTOR_AUTH_FAILED',
|
||||
'WEBHOOK_INVALID_REQUEST' = 'WEBHOOK_INVALID_REQUEST',
|
||||
'ENVELOPE_DRAFT' = 'ENVELOPE_DRAFT',
|
||||
'ENVELOPE_COMPLETED' = 'ENVELOPE_COMPLETED',
|
||||
'ENVELOPE_REJECTED' = 'ENVELOPE_REJECTED',
|
||||
'ENVELOPE_LEGACY' = 'ENVELOPE_LEGACY',
|
||||
ALREADY_EXISTS = 'ALREADY_EXISTS',
|
||||
EXPIRED_CODE = 'EXPIRED_CODE',
|
||||
INVALID_BODY = 'INVALID_BODY',
|
||||
INVALID_REQUEST = 'INVALID_REQUEST',
|
||||
RECIPIENT_EXPIRED = 'RECIPIENT_EXPIRED',
|
||||
LIMIT_EXCEEDED = 'LIMIT_EXCEEDED',
|
||||
NOT_FOUND = 'NOT_FOUND',
|
||||
NOT_IMPLEMENTED = 'NOT_IMPLEMENTED',
|
||||
NOT_SETUP = 'NOT_SETUP',
|
||||
INVALID_CAPTCHA = 'INVALID_CAPTCHA',
|
||||
UNAUTHORIZED = 'UNAUTHORIZED',
|
||||
FORBIDDEN = 'FORBIDDEN',
|
||||
UNKNOWN_ERROR = 'UNKNOWN_ERROR',
|
||||
RETRY_EXCEPTION = 'RETRY_EXCEPTION',
|
||||
SCHEMA_FAILED = 'SCHEMA_FAILED',
|
||||
TOO_MANY_REQUESTS = 'TOO_MANY_REQUESTS',
|
||||
TWO_FACTOR_AUTH_FAILED = 'TWO_FACTOR_AUTH_FAILED',
|
||||
WEBHOOK_INVALID_REQUEST = 'WEBHOOK_INVALID_REQUEST',
|
||||
ENVELOPE_DRAFT = 'ENVELOPE_DRAFT',
|
||||
ENVELOPE_COMPLETED = 'ENVELOPE_COMPLETED',
|
||||
ENVELOPE_REJECTED = 'ENVELOPE_REJECTED',
|
||||
ENVELOPE_LEGACY = 'ENVELOPE_LEGACY',
|
||||
}
|
||||
|
||||
export const genericErrorCodeToTrpcErrorCodeMap: Record<string, { code: string; status: number }> =
|
||||
{
|
||||
[AppErrorCode.ALREADY_EXISTS]: { code: 'BAD_REQUEST', status: 400 },
|
||||
[AppErrorCode.RECIPIENT_EXPIRED]: { code: 'BAD_REQUEST', status: 400 },
|
||||
[AppErrorCode.EXPIRED_CODE]: { code: 'BAD_REQUEST', status: 400 },
|
||||
[AppErrorCode.INVALID_BODY]: { code: 'BAD_REQUEST', status: 400 },
|
||||
[AppErrorCode.INVALID_REQUEST]: { code: 'BAD_REQUEST', status: 400 },
|
||||
[AppErrorCode.INVALID_CAPTCHA]: { code: 'BAD_REQUEST', status: 400 },
|
||||
[AppErrorCode.NOT_FOUND]: { code: 'NOT_FOUND', status: 404 },
|
||||
[AppErrorCode.NOT_IMPLEMENTED]: { code: 'INTERNAL_SERVER_ERROR', status: 501 },
|
||||
[AppErrorCode.NOT_SETUP]: { code: 'BAD_REQUEST', status: 400 },
|
||||
[AppErrorCode.UNAUTHORIZED]: { code: 'UNAUTHORIZED', status: 401 },
|
||||
[AppErrorCode.FORBIDDEN]: { code: 'FORBIDDEN', status: 403 },
|
||||
[AppErrorCode.UNKNOWN_ERROR]: { code: 'INTERNAL_SERVER_ERROR', status: 500 },
|
||||
[AppErrorCode.RETRY_EXCEPTION]: { code: 'INTERNAL_SERVER_ERROR', status: 500 },
|
||||
[AppErrorCode.SCHEMA_FAILED]: { code: 'INTERNAL_SERVER_ERROR', status: 500 },
|
||||
[AppErrorCode.TOO_MANY_REQUESTS]: { code: 'TOO_MANY_REQUESTS', status: 429 },
|
||||
[AppErrorCode.TWO_FACTOR_AUTH_FAILED]: { code: 'UNAUTHORIZED', status: 401 },
|
||||
[AppErrorCode.ENVELOPE_DRAFT]: { code: 'BAD_REQUEST', status: 400 },
|
||||
[AppErrorCode.ENVELOPE_COMPLETED]: { code: 'BAD_REQUEST', status: 400 },
|
||||
[AppErrorCode.ENVELOPE_REJECTED]: { code: 'BAD_REQUEST', status: 400 },
|
||||
[AppErrorCode.ENVELOPE_LEGACY]: { code: 'BAD_REQUEST', status: 400 },
|
||||
};
|
||||
export const genericErrorCodeToTrpcErrorCodeMap: Record<string, { code: string; status: number }> = {
|
||||
[AppErrorCode.ALREADY_EXISTS]: { code: 'BAD_REQUEST', status: 400 },
|
||||
[AppErrorCode.RECIPIENT_EXPIRED]: { code: 'BAD_REQUEST', status: 400 },
|
||||
[AppErrorCode.EXPIRED_CODE]: { code: 'BAD_REQUEST', status: 400 },
|
||||
[AppErrorCode.INVALID_BODY]: { code: 'BAD_REQUEST', status: 400 },
|
||||
[AppErrorCode.INVALID_REQUEST]: { code: 'BAD_REQUEST', status: 400 },
|
||||
[AppErrorCode.INVALID_CAPTCHA]: { code: 'BAD_REQUEST', status: 400 },
|
||||
[AppErrorCode.NOT_FOUND]: { code: 'NOT_FOUND', status: 404 },
|
||||
[AppErrorCode.NOT_IMPLEMENTED]: { code: 'INTERNAL_SERVER_ERROR', status: 501 },
|
||||
[AppErrorCode.NOT_SETUP]: { code: 'BAD_REQUEST', status: 400 },
|
||||
[AppErrorCode.UNAUTHORIZED]: { code: 'UNAUTHORIZED', status: 401 },
|
||||
[AppErrorCode.FORBIDDEN]: { code: 'FORBIDDEN', status: 403 },
|
||||
[AppErrorCode.UNKNOWN_ERROR]: { code: 'INTERNAL_SERVER_ERROR', status: 500 },
|
||||
[AppErrorCode.RETRY_EXCEPTION]: { code: 'INTERNAL_SERVER_ERROR', status: 500 },
|
||||
[AppErrorCode.SCHEMA_FAILED]: { code: 'INTERNAL_SERVER_ERROR', status: 500 },
|
||||
[AppErrorCode.TOO_MANY_REQUESTS]: { code: 'TOO_MANY_REQUESTS', status: 429 },
|
||||
[AppErrorCode.TWO_FACTOR_AUTH_FAILED]: { code: 'UNAUTHORIZED', status: 401 },
|
||||
[AppErrorCode.ENVELOPE_DRAFT]: { code: 'BAD_REQUEST', status: 400 },
|
||||
[AppErrorCode.ENVELOPE_COMPLETED]: { code: 'BAD_REQUEST', status: 400 },
|
||||
[AppErrorCode.ENVELOPE_REJECTED]: { code: 'BAD_REQUEST', status: 400 },
|
||||
[AppErrorCode.ENVELOPE_LEGACY]: { code: 'BAD_REQUEST', status: 400 },
|
||||
};
|
||||
|
||||
export const ZAppErrorJsonSchema = z.object({
|
||||
code: z.string(),
|
||||
@@ -155,8 +154,7 @@ export class AppError extends Error {
|
||||
|
||||
const validCode: string | null = typeof code === 'string' ? code : AppErrorCode.UNKNOWN_ERROR;
|
||||
const validMessage: string | undefined = typeof message === 'string' ? message : undefined;
|
||||
const validUserMessage: string | undefined =
|
||||
typeof userMessage === 'string' ? userMessage : undefined;
|
||||
const validUserMessage: string | undefined = typeof userMessage === 'string' ? userMessage : undefined;
|
||||
|
||||
const validStatusCode = typeof statusCode === 'number' ? statusCode : undefined;
|
||||
|
||||
|
||||
@@ -47,10 +47,7 @@ export type JobDefinition<Name extends string = string, Schema = any> = {
|
||||
|
||||
export interface JobRunIO {
|
||||
// stableRun<T extends Json | void>(cacheKey: string, callback: (io: JobRunIO) => T | Promise<T>): Promise<T>;
|
||||
runTask<T extends Json | void | undefined>(
|
||||
cacheKey: string,
|
||||
callback: () => Promise<T>,
|
||||
): Promise<T>;
|
||||
runTask<T extends Json | void | undefined>(cacheKey: string, callback: () => Promise<T>): Promise<T>;
|
||||
triggerJob(cacheKey: string, options: SimpleTriggerJobOptions): Promise<unknown>;
|
||||
wait(cacheKey: string, ms: number): Promise<void>;
|
||||
logger: {
|
||||
@@ -62,6 +59,4 @@ export interface JobRunIO {
|
||||
};
|
||||
}
|
||||
|
||||
export const defineJob = <N extends string, T = unknown>(
|
||||
job: JobDefinition<N, T>,
|
||||
): JobDefinition<N, T> => job;
|
||||
export const defineJob = <N extends string, T = unknown>(job: JobDefinition<N, T>): JobDefinition<N, T> => job;
|
||||
|
||||
@@ -1,18 +1,17 @@
|
||||
import { createRequire } from 'node:module';
|
||||
import path from 'node:path';
|
||||
import { createBullBoard } from '@bull-board/api';
|
||||
import { BullMQAdapter } from '@bull-board/api/bullMQAdapter';
|
||||
import { HonoAdapter } from '@bull-board/hono';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import { serveStatic } from '@hono/node-server/serve-static';
|
||||
import { sha256 } from '@noble/hashes/sha2';
|
||||
import { BackgroundJobStatus, Prisma } from '@prisma/client';
|
||||
import { Queue, Worker } from 'bullmq';
|
||||
import type { Job } from 'bullmq';
|
||||
import { Hono } from 'hono';
|
||||
import { Queue, Worker } from 'bullmq';
|
||||
import type { Context as HonoContext } from 'hono';
|
||||
import { Hono } from 'hono';
|
||||
import IORedis from 'ioredis';
|
||||
import { createRequire } from 'node:module';
|
||||
import path from 'node:path';
|
||||
|
||||
import { prisma } from '@documenso/prisma';
|
||||
|
||||
import { env } from '../../utils/env';
|
||||
import type { JobDefinition, JobRunIO, SimpleTriggerJobOptions } from './_internal/job';
|
||||
@@ -42,9 +41,7 @@ export class BullMQJobProvider extends BaseJobProvider {
|
||||
const redisUrl = env('NEXT_PRIVATE_REDIS_URL');
|
||||
|
||||
if (!redisUrl) {
|
||||
throw new Error(
|
||||
'[JOBS]: NEXT_PRIVATE_REDIS_URL is required when using the BullMQ jobs provider',
|
||||
);
|
||||
throw new Error('[JOBS]: NEXT_PRIVATE_REDIS_URL is required when using the BullMQ jobs provider');
|
||||
}
|
||||
|
||||
const prefix = env('NEXT_PRIVATE_REDIS_PREFIX') || 'documenso';
|
||||
@@ -135,9 +132,7 @@ export class BullMQJobProvider extends BaseJobProvider {
|
||||
}
|
||||
|
||||
public async triggerJob(options: SimpleTriggerJobOptions) {
|
||||
const eligibleJobs = Object.values(this._jobDefinitions).filter(
|
||||
(job) => job.trigger.name === options.name,
|
||||
);
|
||||
const eligibleJobs = Object.values(this._jobDefinitions).filter((job) => job.trigger.name === options.name);
|
||||
|
||||
await Promise.all(
|
||||
eligibleJobs.map(async (job) => {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import type { Context as HonoContext } from 'hono';
|
||||
import type { Context, Handler, InngestFunction } from 'inngest';
|
||||
import type { Context, Handler, InngestFunction, Logger } from 'inngest';
|
||||
import { Inngest as InngestClient } from 'inngest';
|
||||
import type { Logger } from 'inngest';
|
||||
import { serve as createHonoPagesRoute } from 'inngest/hono';
|
||||
|
||||
import { env } from '../../utils/env';
|
||||
@@ -12,8 +11,7 @@ export class InngestJobProvider extends BaseJobProvider {
|
||||
private static _instance: InngestJobProvider;
|
||||
|
||||
private _client: InngestClient;
|
||||
private _functions: Array<InngestFunction<InngestFunction.Options, Handler.Any, Handler.Any>> =
|
||||
[];
|
||||
private _functions: Array<InngestFunction<InngestFunction.Options, Handler.Any, Handler.Any>> = [];
|
||||
|
||||
private constructor(options: { client: InngestClient }) {
|
||||
super();
|
||||
@@ -22,17 +20,17 @@ export class InngestJobProvider extends BaseJobProvider {
|
||||
}
|
||||
|
||||
static getInstance() {
|
||||
if (!this._instance) {
|
||||
if (!InngestJobProvider._instance) {
|
||||
const client = new InngestClient({
|
||||
id: env('NEXT_PRIVATE_INNGEST_APP_ID') || 'documenso-app',
|
||||
eventKey: env('INNGEST_EVENT_KEY') || env('NEXT_PRIVATE_INNGEST_EVENT_KEY'),
|
||||
logger: console,
|
||||
});
|
||||
|
||||
this._instance = new InngestJobProvider({ client });
|
||||
InngestJobProvider._instance = new InngestJobProvider({ client });
|
||||
}
|
||||
|
||||
return this._instance;
|
||||
return InngestJobProvider._instance;
|
||||
}
|
||||
|
||||
public defineJob<N extends string, T>(job: JobDefinition<N, T>): void {
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import { sha256 } from '@noble/hashes/sha2';
|
||||
import { BackgroundJobStatus, Prisma } from '@prisma/client';
|
||||
import { CronExpressionParser } from 'cron-parser';
|
||||
import type { Context as HonoContext } from 'hono';
|
||||
|
||||
import { prisma } from '@documenso/prisma';
|
||||
|
||||
import { NEXT_PRIVATE_INTERNAL_WEBAPP_URL } from '../../constants/app';
|
||||
import { sign } from '../../server-only/crypto/sign';
|
||||
import { verify } from '../../server-only/crypto/verify';
|
||||
@@ -50,11 +49,11 @@ export class LocalJobProvider extends BaseJobProvider {
|
||||
}
|
||||
|
||||
static getInstance() {
|
||||
if (!this._instance) {
|
||||
this._instance = new LocalJobProvider();
|
||||
if (!LocalJobProvider._instance) {
|
||||
LocalJobProvider._instance = new LocalJobProvider();
|
||||
}
|
||||
|
||||
return this._instance;
|
||||
return LocalJobProvider._instance;
|
||||
}
|
||||
|
||||
public defineJob<N extends string, T>(definition: JobDefinition<N, T>) {
|
||||
@@ -192,9 +191,7 @@ export class LocalJobProvider extends BaseJobProvider {
|
||||
}
|
||||
|
||||
public async triggerJob(options: SimpleTriggerJobOptions) {
|
||||
const eligibleJobs = Object.values(this._jobDefinitions).filter(
|
||||
(job) => job.trigger.name === options.name,
|
||||
);
|
||||
const eligibleJobs = Object.values(this._jobDefinitions).filter((job) => job.trigger.name === options.name);
|
||||
|
||||
await Promise.all(
|
||||
eligibleJobs.map(async (job) => {
|
||||
@@ -245,11 +242,7 @@ export class LocalJobProvider extends BaseJobProvider {
|
||||
|
||||
const definition = this._jobDefinitions[options.name];
|
||||
|
||||
if (
|
||||
typeof jobId !== 'string' ||
|
||||
typeof signature !== 'string' ||
|
||||
typeof options !== 'object'
|
||||
) {
|
||||
if (typeof jobId !== 'string' || typeof signature !== 'string' || typeof options !== 'object') {
|
||||
return c.text('Bad request', 400);
|
||||
}
|
||||
|
||||
@@ -318,8 +311,7 @@ export class LocalJobProvider extends BaseJobProvider {
|
||||
|
||||
const taskHasExceededRetries = error instanceof BackgroundTaskExceededRetriesError;
|
||||
const jobHasExceededRetries =
|
||||
backgroundJob.retried >= backgroundJob.maxRetries &&
|
||||
!(error instanceof BackgroundTaskFailedError);
|
||||
backgroundJob.retried >= backgroundJob.maxRetries && !(error instanceof BackgroundTaskFailedError);
|
||||
|
||||
if (taskHasExceededRetries || jobHasExceededRetries) {
|
||||
backgroundJob = await prisma.backgroundJob.update({
|
||||
|
||||
@@ -10,9 +10,7 @@ const SEND_CONFIRMATION_EMAIL_JOB_DEFINITION_SCHEMA = z.object({
|
||||
force: z.boolean().optional(),
|
||||
});
|
||||
|
||||
export type TSendConfirmationEmailJobDefinition = z.infer<
|
||||
typeof SEND_CONFIRMATION_EMAIL_JOB_DEFINITION_SCHEMA
|
||||
>;
|
||||
export type TSendConfirmationEmailJobDefinition = z.infer<typeof SEND_CONFIRMATION_EMAIL_JOB_DEFINITION_SCHEMA>;
|
||||
|
||||
export const SEND_CONFIRMATION_EMAIL_JOB_DEFINITION = {
|
||||
id: SEND_CONFIRMATION_EMAIL_JOB_DEFINITION_ID,
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
import { createElement } from 'react';
|
||||
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { EnvelopeType, ReadStatus, SendStatus, SigningStatus } from '@prisma/client';
|
||||
|
||||
import { mailer } from '@documenso/email/mailer';
|
||||
import DocumentCancelTemplate from '@documenso/email/templates/document-cancel';
|
||||
import { isRecipientEmailValidForSending } from '@documenso/lib/utils/recipients';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { EnvelopeType, ReadStatus, SendStatus, SigningStatus } from '@prisma/client';
|
||||
import { createElement } from 'react';
|
||||
|
||||
import { getI18nInstance } from '../../../client-only/providers/i18n-server';
|
||||
import { NEXT_PUBLIC_WEBAPP_URL } from '../../../constants/app';
|
||||
@@ -17,13 +15,7 @@ import { renderEmailWithI18N } from '../../../utils/render-email-with-i18n';
|
||||
import type { JobRunIO } from '../../client/_internal/job';
|
||||
import type { TSendDocumentCancelledEmailsJobDefinition } from './send-document-cancelled-emails';
|
||||
|
||||
export const run = async ({
|
||||
payload,
|
||||
io,
|
||||
}: {
|
||||
payload: TSendDocumentCancelledEmailsJobDefinition;
|
||||
io: JobRunIO;
|
||||
}) => {
|
||||
export const run = async ({ payload, io }: { payload: TSendDocumentCancelledEmailsJobDefinition; io: JobRunIO }) => {
|
||||
const { documentId, cancellationReason } = payload;
|
||||
|
||||
const envelope = await prisma.envelope.findFirstOrThrow({
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import { type JobDefinition } from '../../client/_internal/job';
|
||||
import type { JobDefinition } from '../../client/_internal/job';
|
||||
|
||||
const SEND_DOCUMENT_CANCELLED_EMAILS_JOB_DEFINITION_ID = 'send.document.cancelled.emails';
|
||||
|
||||
|
||||
+3
-9
@@ -1,10 +1,8 @@
|
||||
import { createElement } from 'react';
|
||||
|
||||
import { msg } from '@lingui/core/macro';
|
||||
|
||||
import { mailer } from '@documenso/email/mailer';
|
||||
import { DocumentCreatedFromDirectTemplateEmailTemplate } from '@documenso/email/templates/document-created-from-direct-template';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { createElement } from 'react';
|
||||
|
||||
import { getI18nInstance } from '../../../client-only/providers/i18n-server';
|
||||
import { NEXT_PUBLIC_WEBAPP_URL } from '../../../constants/app';
|
||||
@@ -13,11 +11,7 @@ import { renderEmailWithI18N } from '../../../utils/render-email-with-i18n';
|
||||
import { formatDocumentsPath } from '../../../utils/teams';
|
||||
import type { TSendDocumentCreatedFromDirectTemplateEmailJobDefinition } from './send-document-created-from-direct-template-email';
|
||||
|
||||
export const run = async ({
|
||||
payload,
|
||||
}: {
|
||||
payload: TSendDocumentCreatedFromDirectTemplateEmailJobDefinition;
|
||||
}) => {
|
||||
export const run = async ({ payload }: { payload: TSendDocumentCreatedFromDirectTemplateEmailJobDefinition }) => {
|
||||
const { envelopeId, recipientId } = payload;
|
||||
|
||||
const envelope = await prisma.envelope.findFirst({
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import { type JobDefinition } from '../../client/_internal/job';
|
||||
import type { JobDefinition } from '../../client/_internal/job';
|
||||
|
||||
const SEND_DOCUMENT_CREATED_FROM_DIRECT_TEMPLATE_EMAIL_JOB_DEFINITION_ID =
|
||||
'send.document.created.from.direct.template.email';
|
||||
|
||||
+32
-37
@@ -1,10 +1,8 @@
|
||||
import { createElement } from 'react';
|
||||
|
||||
import { msg } from '@lingui/core/macro';
|
||||
|
||||
import { mailer } from '@documenso/email/mailer';
|
||||
import OrganisationJoinEmailTemplate from '@documenso/email/templates/organisation-join';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { createElement } from 'react';
|
||||
|
||||
import { getI18nInstance } from '../../../client-only/providers/i18n-server';
|
||||
import { NEXT_PUBLIC_WEBAPP_URL } from '../../../constants/app';
|
||||
@@ -80,41 +78,38 @@ export const run = async ({
|
||||
continue;
|
||||
}
|
||||
|
||||
await io.runTask(
|
||||
`send-organisation-member-joined-email--${invitedMember.id}_${member.id}`,
|
||||
async () => {
|
||||
const emailContent = createElement(OrganisationJoinEmailTemplate, {
|
||||
assetBaseUrl: NEXT_PUBLIC_WEBAPP_URL(),
|
||||
baseUrl: NEXT_PUBLIC_WEBAPP_URL(),
|
||||
memberName: invitedMember.user.name || '',
|
||||
memberEmail: invitedMember.user.email,
|
||||
organisationName: organisation.name,
|
||||
organisationUrl: organisation.url,
|
||||
});
|
||||
await io.runTask(`send-organisation-member-joined-email--${invitedMember.id}_${member.id}`, async () => {
|
||||
const emailContent = createElement(OrganisationJoinEmailTemplate, {
|
||||
assetBaseUrl: NEXT_PUBLIC_WEBAPP_URL(),
|
||||
baseUrl: NEXT_PUBLIC_WEBAPP_URL(),
|
||||
memberName: invitedMember.user.name || '',
|
||||
memberEmail: invitedMember.user.email,
|
||||
organisationName: organisation.name,
|
||||
organisationUrl: organisation.url,
|
||||
});
|
||||
|
||||
// !: Replace with the actual language of the recipient later
|
||||
const [html, text] = await Promise.all([
|
||||
renderEmailWithI18N(emailContent, {
|
||||
lang: emailLanguage,
|
||||
branding,
|
||||
}),
|
||||
renderEmailWithI18N(emailContent, {
|
||||
lang: emailLanguage,
|
||||
branding,
|
||||
plainText: true,
|
||||
}),
|
||||
]);
|
||||
// !: Replace with the actual language of the recipient later
|
||||
const [html, text] = await Promise.all([
|
||||
renderEmailWithI18N(emailContent, {
|
||||
lang: emailLanguage,
|
||||
branding,
|
||||
}),
|
||||
renderEmailWithI18N(emailContent, {
|
||||
lang: emailLanguage,
|
||||
branding,
|
||||
plainText: true,
|
||||
}),
|
||||
]);
|
||||
|
||||
const i18n = await getI18nInstance(emailLanguage);
|
||||
const i18n = await getI18nInstance(emailLanguage);
|
||||
|
||||
await mailer.sendMail({
|
||||
to: member.user.email,
|
||||
from: senderEmail,
|
||||
subject: i18n._(msg`A new member has joined your organisation`),
|
||||
html,
|
||||
text,
|
||||
});
|
||||
},
|
||||
);
|
||||
await mailer.sendMail({
|
||||
to: member.user.email,
|
||||
from: senderEmail,
|
||||
subject: i18n._(msg`A new member has joined your organisation`),
|
||||
html,
|
||||
text,
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
@@ -2,8 +2,7 @@ import { z } from 'zod';
|
||||
|
||||
import type { JobDefinition } from '../../client/_internal/job';
|
||||
|
||||
const SEND_ORGANISATION_MEMBER_JOINED_EMAIL_JOB_DEFINITION_ID =
|
||||
'send.organisation-member-joined.email';
|
||||
const SEND_ORGANISATION_MEMBER_JOINED_EMAIL_JOB_DEFINITION_ID = 'send.organisation-member-joined.email';
|
||||
|
||||
const SEND_ORGANISATION_MEMBER_JOINED_EMAIL_JOB_DEFINITION_SCHEMA = z.object({
|
||||
organisationId: z.string(),
|
||||
|
||||
+31
-36
@@ -1,10 +1,8 @@
|
||||
import { createElement } from 'react';
|
||||
|
||||
import { msg } from '@lingui/core/macro';
|
||||
|
||||
import { mailer } from '@documenso/email/mailer';
|
||||
import OrganisationLeaveEmailTemplate from '@documenso/email/templates/organisation-leave';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { createElement } from 'react';
|
||||
|
||||
import { getI18nInstance } from '../../../client-only/providers/i18n-server';
|
||||
import { NEXT_PUBLIC_WEBAPP_URL } from '../../../constants/app';
|
||||
@@ -75,40 +73,37 @@ export const run = async ({
|
||||
continue;
|
||||
}
|
||||
|
||||
await io.runTask(
|
||||
`send-organisation-member-left-email--${oldMember.id}_${member.id}`,
|
||||
async () => {
|
||||
const emailContent = createElement(OrganisationLeaveEmailTemplate, {
|
||||
assetBaseUrl: NEXT_PUBLIC_WEBAPP_URL(),
|
||||
baseUrl: NEXT_PUBLIC_WEBAPP_URL(),
|
||||
memberName: oldMember.name || '',
|
||||
memberEmail: oldMember.email,
|
||||
organisationName: organisation.name,
|
||||
organisationUrl: organisation.url,
|
||||
});
|
||||
await io.runTask(`send-organisation-member-left-email--${oldMember.id}_${member.id}`, async () => {
|
||||
const emailContent = createElement(OrganisationLeaveEmailTemplate, {
|
||||
assetBaseUrl: NEXT_PUBLIC_WEBAPP_URL(),
|
||||
baseUrl: NEXT_PUBLIC_WEBAPP_URL(),
|
||||
memberName: oldMember.name || '',
|
||||
memberEmail: oldMember.email,
|
||||
organisationName: organisation.name,
|
||||
organisationUrl: organisation.url,
|
||||
});
|
||||
|
||||
const [html, text] = await Promise.all([
|
||||
renderEmailWithI18N(emailContent, {
|
||||
lang: emailLanguage,
|
||||
branding,
|
||||
}),
|
||||
renderEmailWithI18N(emailContent, {
|
||||
lang: emailLanguage,
|
||||
branding,
|
||||
plainText: true,
|
||||
}),
|
||||
]);
|
||||
const [html, text] = await Promise.all([
|
||||
renderEmailWithI18N(emailContent, {
|
||||
lang: emailLanguage,
|
||||
branding,
|
||||
}),
|
||||
renderEmailWithI18N(emailContent, {
|
||||
lang: emailLanguage,
|
||||
branding,
|
||||
plainText: true,
|
||||
}),
|
||||
]);
|
||||
|
||||
const i18n = await getI18nInstance(emailLanguage);
|
||||
const i18n = await getI18nInstance(emailLanguage);
|
||||
|
||||
await mailer.sendMail({
|
||||
to: member.user.email,
|
||||
from: senderEmail,
|
||||
subject: i18n._(msg`A member has left your organisation`),
|
||||
html,
|
||||
text,
|
||||
});
|
||||
},
|
||||
);
|
||||
await mailer.sendMail({
|
||||
to: member.user.email,
|
||||
from: senderEmail,
|
||||
subject: i18n._(msg`A member has left your organisation`),
|
||||
html,
|
||||
text,
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
import { createElement } from 'react';
|
||||
|
||||
import { msg } from '@lingui/core/macro';
|
||||
|
||||
import { mailer } from '@documenso/email/mailer';
|
||||
import { RecipientExpiredTemplate } from '@documenso/email/templates/recipient-expired';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { createElement } from 'react';
|
||||
|
||||
import { getI18nInstance } from '../../../client-only/providers/i18n-server';
|
||||
import { NEXT_PUBLIC_WEBAPP_URL } from '../../../constants/app';
|
||||
@@ -15,13 +13,7 @@ import { formatDocumentsPath } from '../../../utils/teams';
|
||||
import type { JobRunIO } from '../../client/_internal/job';
|
||||
import type { TSendOwnerRecipientExpiredEmailJobDefinition } from './send-owner-recipient-expired-email';
|
||||
|
||||
export const run = async ({
|
||||
payload,
|
||||
io,
|
||||
}: {
|
||||
payload: TSendOwnerRecipientExpiredEmailJobDefinition;
|
||||
io: JobRunIO;
|
||||
}) => {
|
||||
export const run = async ({ payload, io }: { payload: TSendOwnerRecipientExpiredEmailJobDefinition; io: JobRunIO }) => {
|
||||
const { recipientId, envelopeId } = payload;
|
||||
|
||||
const envelope = await prisma.envelope.findFirst({
|
||||
@@ -107,9 +99,7 @@ export const run = async ({
|
||||
address: documentOwner.email,
|
||||
},
|
||||
from: senderEmail,
|
||||
subject: i18n._(
|
||||
msg`Signing window expired for "${recipient.name || recipient.email}" on "${envelope.title}"`,
|
||||
),
|
||||
subject: i18n._(msg`Signing window expired for "${recipient.name || recipient.email}" on "${envelope.title}"`),
|
||||
html,
|
||||
text,
|
||||
});
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import { type JobDefinition } from '../../client/_internal/job';
|
||||
import type { JobDefinition } from '../../client/_internal/job';
|
||||
|
||||
const SEND_OWNER_RECIPIENT_EXPIRED_EMAIL_JOB_DEFINITION_ID = 'send.owner.recipient.expired.email';
|
||||
|
||||
|
||||
@@ -1,11 +1,7 @@
|
||||
import { sendResetPassword } from '../../../server-only/auth/send-reset-password';
|
||||
import type { TSendPasswordResetSuccessEmailJobDefinition } from './send-password-reset-success-email';
|
||||
|
||||
export const run = async ({
|
||||
payload,
|
||||
}: {
|
||||
payload: TSendPasswordResetSuccessEmailJobDefinition;
|
||||
}) => {
|
||||
export const run = async ({ payload }: { payload: TSendPasswordResetSuccessEmailJobDefinition }) => {
|
||||
await sendResetPassword({
|
||||
userId: payload.userId,
|
||||
});
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
import { createElement } from 'react';
|
||||
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { EnvelopeType } from '@prisma/client';
|
||||
|
||||
import { mailer } from '@documenso/email/mailer';
|
||||
import { DocumentRecipientSignedEmailTemplate } from '@documenso/email/templates/document-recipient-signed';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { EnvelopeType } from '@prisma/client';
|
||||
import { createElement } from 'react';
|
||||
|
||||
import { getI18nInstance } from '../../../client-only/providers/i18n-server';
|
||||
import { NEXT_PUBLIC_WEBAPP_URL } from '../../../constants/app';
|
||||
@@ -17,13 +15,7 @@ import { renderEmailWithI18N } from '../../../utils/render-email-with-i18n';
|
||||
import type { JobRunIO } from '../../client/_internal/job';
|
||||
import type { TSendRecipientSignedEmailJobDefinition } from './send-recipient-signed-email';
|
||||
|
||||
export const run = async ({
|
||||
payload,
|
||||
io,
|
||||
}: {
|
||||
payload: TSendRecipientSignedEmailJobDefinition;
|
||||
io: JobRunIO;
|
||||
}) => {
|
||||
export const run = async ({ payload, io }: { payload: TSendRecipientSignedEmailJobDefinition; io: JobRunIO }) => {
|
||||
const { documentId, recipientId } = payload;
|
||||
|
||||
const envelope = await prisma.envelope.findFirst({
|
||||
@@ -66,9 +58,7 @@ export const run = async ({
|
||||
throw new Error('Document has no recipients');
|
||||
}
|
||||
|
||||
const isRecipientSignedEmailEnabled = extractDerivedDocumentEmailSettings(
|
||||
envelope.documentMeta,
|
||||
).recipientSigned;
|
||||
const isRecipientSignedEmailEnabled = extractDerivedDocumentEmailSettings(envelope.documentMeta).recipientSigned;
|
||||
|
||||
if (!isRecipientSignedEmailEnabled) {
|
||||
return;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import { type JobDefinition } from '../../client/_internal/job';
|
||||
import type { JobDefinition } from '../../client/_internal/job';
|
||||
|
||||
const SEND_RECIPIENT_SIGNED_EMAIL_JOB_DEFINITION_ID = 'send.recipient.signed.email';
|
||||
|
||||
@@ -9,9 +9,7 @@ const SEND_RECIPIENT_SIGNED_EMAIL_JOB_DEFINITION_SCHEMA = z.object({
|
||||
recipientId: z.number(),
|
||||
});
|
||||
|
||||
export type TSendRecipientSignedEmailJobDefinition = z.infer<
|
||||
typeof SEND_RECIPIENT_SIGNED_EMAIL_JOB_DEFINITION_SCHEMA
|
||||
>;
|
||||
export type TSendRecipientSignedEmailJobDefinition = z.infer<typeof SEND_RECIPIENT_SIGNED_EMAIL_JOB_DEFINITION_SCHEMA>;
|
||||
|
||||
export const SEND_RECIPIENT_SIGNED_EMAIL_JOB_DEFINITION = {
|
||||
id: SEND_RECIPIENT_SIGNED_EMAIL_JOB_DEFINITION_ID,
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
import { createElement } from 'react';
|
||||
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { EnvelopeType, SendStatus, SigningStatus } from '@prisma/client';
|
||||
|
||||
import { mailer } from '@documenso/email/mailer';
|
||||
import DocumentRejectedEmail from '@documenso/email/templates/document-rejected';
|
||||
import DocumentRejectionConfirmedEmail from '@documenso/email/templates/document-rejection-confirmed';
|
||||
import { isRecipientEmailValidForSending } from '@documenso/lib/utils/recipients';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { EnvelopeType, SendStatus, SigningStatus } from '@prisma/client';
|
||||
import { createElement } from 'react';
|
||||
|
||||
import { getI18nInstance } from '../../../client-only/providers/i18n-server';
|
||||
import { NEXT_PUBLIC_WEBAPP_URL } from '../../../constants/app';
|
||||
@@ -20,13 +18,7 @@ import { formatDocumentsPath } from '../../../utils/teams';
|
||||
import type { JobRunIO } from '../../client/_internal/job';
|
||||
import type { TSendSigningRejectionEmailsJobDefinition } from './send-rejection-emails';
|
||||
|
||||
export const run = async ({
|
||||
payload,
|
||||
io,
|
||||
}: {
|
||||
payload: TSendSigningRejectionEmailsJobDefinition;
|
||||
io: JobRunIO;
|
||||
}) => {
|
||||
export const run = async ({ payload, io }: { payload: TSendSigningRejectionEmailsJobDefinition; io: JobRunIO }) => {
|
||||
const { documentId, recipientId } = payload;
|
||||
|
||||
const [envelope, recipient] = await Promise.all([
|
||||
@@ -66,9 +58,7 @@ export const run = async ({
|
||||
|
||||
const { user: documentOwner } = envelope;
|
||||
|
||||
const isEmailEnabled = extractDerivedDocumentEmailSettings(
|
||||
envelope.documentMeta,
|
||||
).recipientSigningRequest;
|
||||
const isEmailEnabled = extractDerivedDocumentEmailSettings(envelope.documentMeta).recipientSigningRequest;
|
||||
|
||||
if (!isEmailEnabled) {
|
||||
return;
|
||||
@@ -124,9 +114,7 @@ export const run = async ({
|
||||
const ownerTemplate = createElement(DocumentRejectedEmail, {
|
||||
recipientName: recipient.name,
|
||||
documentName: envelope.title,
|
||||
documentUrl: `${NEXT_PUBLIC_WEBAPP_URL()}${formatDocumentsPath(envelope.team?.url)}/${
|
||||
envelope.id
|
||||
}`,
|
||||
documentUrl: `${NEXT_PUBLIC_WEBAPP_URL()}${formatDocumentsPath(envelope.team?.url)}/${envelope.id}`,
|
||||
rejectionReason: recipient.rejectionReason || '',
|
||||
assetBaseUrl: NEXT_PUBLIC_WEBAPP_URL(),
|
||||
});
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import { type JobDefinition } from '../../client/_internal/job';
|
||||
import type { JobDefinition } from '../../client/_internal/job';
|
||||
|
||||
const SEND_SIGNING_REJECTION_EMAILS_JOB_DEFINITION_ID = 'send.signing.rejected.emails';
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { createElement } from 'react';
|
||||
|
||||
import { mailer } from '@documenso/email/mailer';
|
||||
import DocumentInviteEmailTemplate from '@documenso/email/templates/document-invite';
|
||||
import { isRecipientEmailValidForSending } from '@documenso/lib/utils/recipients';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import {
|
||||
DocumentSource,
|
||||
@@ -9,18 +11,11 @@ import {
|
||||
RecipientRole,
|
||||
SendStatus,
|
||||
} from '@prisma/client';
|
||||
|
||||
import { mailer } from '@documenso/email/mailer';
|
||||
import DocumentInviteEmailTemplate from '@documenso/email/templates/document-invite';
|
||||
import { isRecipientEmailValidForSending } from '@documenso/lib/utils/recipients';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import { createElement } from 'react';
|
||||
|
||||
import { getI18nInstance } from '../../../client-only/providers/i18n-server';
|
||||
import { NEXT_PUBLIC_WEBAPP_URL } from '../../../constants/app';
|
||||
import {
|
||||
RECIPIENT_ROLES_DESCRIPTION,
|
||||
RECIPIENT_ROLE_TO_EMAIL_TYPE,
|
||||
} from '../../../constants/recipient-roles';
|
||||
import { RECIPIENT_ROLE_TO_EMAIL_TYPE, RECIPIENT_ROLES_DESCRIPTION } from '../../../constants/recipient-roles';
|
||||
import { getEmailContext } from '../../../server-only/email/get-email-context';
|
||||
import { updateRecipientNextReminder } from '../../../server-only/recipient/update-recipient-next-reminder';
|
||||
import { DOCUMENT_AUDIT_LOG_TYPE } from '../../../types/document-audit-logs';
|
||||
@@ -32,13 +27,7 @@ import { renderEmailWithI18N } from '../../../utils/render-email-with-i18n';
|
||||
import type { JobRunIO } from '../../client/_internal/job';
|
||||
import type { TSendSigningEmailJobDefinition } from './send-signing-email';
|
||||
|
||||
export const run = async ({
|
||||
payload,
|
||||
io,
|
||||
}: {
|
||||
payload: TSendSigningEmailJobDefinition;
|
||||
io: JobRunIO;
|
||||
}) => {
|
||||
export const run = async ({ payload, io }: { payload: TSendSigningEmailJobDefinition; io: JobRunIO }) => {
|
||||
const { userId, documentId, recipientId, requestMetadata } = payload;
|
||||
|
||||
const [user, envelope, recipient] = await Promise.all([
|
||||
@@ -94,15 +83,14 @@ export const run = async ({
|
||||
return;
|
||||
}
|
||||
|
||||
const { branding, emailLanguage, settings, organisationType, senderEmail, replyToEmail } =
|
||||
await getEmailContext({
|
||||
emailType: 'RECIPIENT',
|
||||
source: {
|
||||
type: 'team',
|
||||
teamId: envelope.teamId,
|
||||
},
|
||||
meta: envelope.documentMeta,
|
||||
});
|
||||
const { branding, emailLanguage, settings, organisationType, senderEmail, replyToEmail } = await getEmailContext({
|
||||
emailType: 'RECIPIENT',
|
||||
source: {
|
||||
type: 'team',
|
||||
teamId: envelope.teamId,
|
||||
},
|
||||
meta: envelope.documentMeta,
|
||||
});
|
||||
|
||||
const customEmail = envelope?.documentMeta;
|
||||
const isDirectTemplate = envelope.source === DocumentSource.TEMPLATE_DIRECT_LINK;
|
||||
@@ -114,9 +102,7 @@ export const run = async ({
|
||||
|
||||
const i18n = await getI18nInstance(emailLanguage);
|
||||
|
||||
const recipientActionVerb = i18n
|
||||
._(RECIPIENT_ROLES_DESCRIPTION[recipient.role].actionVerb)
|
||||
.toLowerCase();
|
||||
const recipientActionVerb = i18n._(RECIPIENT_ROLES_DESCRIPTION[recipient.role].actionVerb).toLowerCase();
|
||||
|
||||
let emailMessage = customEmail?.message || '';
|
||||
let emailSubject = i18n._(msg`Please ${recipientActionVerb} this document`);
|
||||
@@ -132,9 +118,7 @@ export const run = async ({
|
||||
emailMessage = i18n._(
|
||||
msg`A document was created by your direct template that requires you to ${recipientActionVerb} it.`,
|
||||
);
|
||||
emailSubject = i18n._(
|
||||
msg`Please ${recipientActionVerb} this document created by your direct template`,
|
||||
);
|
||||
emailSubject = i18n._(msg`Please ${recipientActionVerb} this document created by your direct template`);
|
||||
}
|
||||
|
||||
if (organisationType === OrganisationType.ORGANISATION) {
|
||||
@@ -165,9 +149,7 @@ export const run = async ({
|
||||
documentName: envelope.title,
|
||||
inviterName: user.name || undefined,
|
||||
inviterEmail:
|
||||
organisationType === OrganisationType.ORGANISATION
|
||||
? team?.teamEmail?.email || user.email
|
||||
: user.email,
|
||||
organisationType === OrganisationType.ORGANISATION ? team?.teamEmail?.email || user.email : user.email,
|
||||
assetBaseUrl,
|
||||
signDocumentLink,
|
||||
customBody: renderCustomEmailTemplate(emailMessage, customEmailTemplate),
|
||||
@@ -197,10 +179,7 @@ export const run = async ({
|
||||
},
|
||||
from: senderEmail,
|
||||
replyTo: replyToEmail,
|
||||
subject: renderCustomEmailTemplate(
|
||||
documentMeta?.subject || emailSubject,
|
||||
customEmailTemplate,
|
||||
),
|
||||
subject: renderCustomEmailTemplate(documentMeta?.subject || emailSubject, customEmailTemplate),
|
||||
html,
|
||||
text,
|
||||
});
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import { ZRequestMetadataSchema } from '../../../universal/extract-request-metadata';
|
||||
import { type JobDefinition } from '../../client/_internal/job';
|
||||
import type { JobDefinition } from '../../client/_internal/job';
|
||||
|
||||
const SEND_SIGNING_EMAIL_JOB_DEFINITION_ID = 'send.signing.requested.email';
|
||||
|
||||
@@ -12,9 +12,7 @@ const SEND_SIGNING_EMAIL_JOB_DEFINITION_SCHEMA = z.object({
|
||||
requestMetadata: ZRequestMetadataSchema.optional(),
|
||||
});
|
||||
|
||||
export type TSendSigningEmailJobDefinition = z.infer<
|
||||
typeof SEND_SIGNING_EMAIL_JOB_DEFINITION_SCHEMA
|
||||
>;
|
||||
export type TSendSigningEmailJobDefinition = z.infer<typeof SEND_SIGNING_EMAIL_JOB_DEFINITION_SCHEMA>;
|
||||
|
||||
export const SEND_SIGNING_EMAIL_JOB_DEFINITION = {
|
||||
id: SEND_SIGNING_EMAIL_JOB_DEFINITION_ID,
|
||||
@@ -29,7 +27,4 @@ export const SEND_SIGNING_EMAIL_JOB_DEFINITION = {
|
||||
|
||||
await handler.run({ payload, io });
|
||||
},
|
||||
} as const satisfies JobDefinition<
|
||||
typeof SEND_SIGNING_EMAIL_JOB_DEFINITION_ID,
|
||||
TSendSigningEmailJobDefinition
|
||||
>;
|
||||
} as const satisfies JobDefinition<typeof SEND_SIGNING_EMAIL_JOB_DEFINITION_ID, TSendSigningEmailJobDefinition>;
|
||||
|
||||
@@ -2,13 +2,7 @@ import { sendTeamDeleteEmail } from '../../../server-only/team/delete-team';
|
||||
import type { JobRunIO } from '../../client/_internal/job';
|
||||
import type { TSendTeamDeletedEmailJobDefinition } from './send-team-deleted-email';
|
||||
|
||||
export const run = async ({
|
||||
payload,
|
||||
io,
|
||||
}: {
|
||||
payload: TSendTeamDeletedEmailJobDefinition;
|
||||
io: JobRunIO;
|
||||
}) => {
|
||||
export const run = async ({ payload, io }: { payload: TSendTeamDeletedEmailJobDefinition; io: JobRunIO }) => {
|
||||
const { team, members, organisationId } = payload;
|
||||
|
||||
for (const member of members) {
|
||||
|
||||
@@ -19,9 +19,7 @@ const SEND_TEAM_DELETED_EMAIL_JOB_DEFINITION_SCHEMA = z.object({
|
||||
),
|
||||
});
|
||||
|
||||
export type TSendTeamDeletedEmailJobDefinition = z.infer<
|
||||
typeof SEND_TEAM_DELETED_EMAIL_JOB_DEFINITION_SCHEMA
|
||||
>;
|
||||
export type TSendTeamDeletedEmailJobDefinition = z.infer<typeof SEND_TEAM_DELETED_EMAIL_JOB_DEFINITION_SCHEMA>;
|
||||
|
||||
export const SEND_TEAM_DELETED_EMAIL_JOB_DEFINITION = {
|
||||
id: SEND_TEAM_DELETED_EMAIL_JOB_DEFINITION_ID,
|
||||
|
||||
@@ -4,13 +4,7 @@ import { AppError, AppErrorCode } from '../../../errors/app-error';
|
||||
import type { JobRunIO } from '../../client/_internal/job';
|
||||
import type { TBackportSubscriptionClaimJobDefinition } from './backport-subscription-claims';
|
||||
|
||||
export const run = async ({
|
||||
payload,
|
||||
io,
|
||||
}: {
|
||||
payload: TBackportSubscriptionClaimJobDefinition;
|
||||
io: JobRunIO;
|
||||
}) => {
|
||||
export const run = async ({ payload, io }: { payload: TBackportSubscriptionClaimJobDefinition; io: JobRunIO }) => {
|
||||
const { subscriptionClaimId, flags } = payload;
|
||||
|
||||
const subscriptionClaim = await prisma.subscriptionClaim.findFirst({
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import { type JobDefinition } from '../../client/_internal/job';
|
||||
import type { JobDefinition } from '../../client/_internal/job';
|
||||
|
||||
const BACKPORT_SUBSCRIPTION_CLAIM_JOB_DEFINITION_ID = 'internal.backport-subscription-claims';
|
||||
|
||||
@@ -25,9 +25,7 @@ const BACKPORT_SUBSCRIPTION_CLAIM_JOB_DEFINITION_SCHEMA = z.object({
|
||||
}),
|
||||
});
|
||||
|
||||
export type TBackportSubscriptionClaimJobDefinition = z.infer<
|
||||
typeof BACKPORT_SUBSCRIPTION_CLAIM_JOB_DEFINITION_SCHEMA
|
||||
>;
|
||||
export type TBackportSubscriptionClaimJobDefinition = z.infer<typeof BACKPORT_SUBSCRIPTION_CLAIM_JOB_DEFINITION_SCHEMA>;
|
||||
|
||||
export const BACKPORT_SUBSCRIPTION_CLAIM_JOB_DEFINITION = {
|
||||
id: BACKPORT_SUBSCRIPTION_CLAIM_JOB_DEFINITION_ID,
|
||||
|
||||
@@ -1,9 +1,3 @@
|
||||
import { createElement } from 'react';
|
||||
|
||||
import { msg } from '@lingui/macro';
|
||||
import { parse } from 'csv-parse/sync';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { mailer } from '@documenso/email/mailer';
|
||||
import { BulkSendCompleteEmail } from '@documenso/email/templates/bulk-send-complete';
|
||||
import { sendDocument } from '@documenso/lib/server-only/document/send-document';
|
||||
@@ -11,6 +5,10 @@ import { createDocumentFromTemplate } from '@documenso/lib/server-only/template/
|
||||
import { getTemplateById } from '@documenso/lib/server-only/template/get-template-by-id';
|
||||
import { zEmail } from '@documenso/lib/utils/zod';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import { msg } from '@lingui/macro';
|
||||
import { parse } from 'csv-parse/sync';
|
||||
import { createElement } from 'react';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { getI18nInstance } from '../../../client-only/providers/i18n-server';
|
||||
import { NEXT_PUBLIC_WEBAPP_URL } from '../../../constants/app';
|
||||
@@ -28,13 +26,7 @@ const ZRecipientRowSchema = z.object({
|
||||
]),
|
||||
});
|
||||
|
||||
export const run = async ({
|
||||
payload,
|
||||
io,
|
||||
}: {
|
||||
payload: TBulkSendTemplateJobDefinition;
|
||||
io: JobRunIO;
|
||||
}) => {
|
||||
export const run = async ({ payload, io }: { payload: TBulkSendTemplateJobDefinition; io: JobRunIO }) => {
|
||||
const { userId, teamId, templateId, csvContent, sendImmediately, requestMetadata } = payload;
|
||||
|
||||
const template = await getTemplateById({
|
||||
@@ -82,7 +74,7 @@ export const run = async ({
|
||||
const results = {
|
||||
success: 0,
|
||||
failed: 0,
|
||||
errors: Array<string>(),
|
||||
errors: [] as string[],
|
||||
};
|
||||
|
||||
// Process each row
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import { ZRequestMetadataSchema } from '../../../universal/extract-request-metadata';
|
||||
import { type JobDefinition } from '../../client/_internal/job';
|
||||
import type { JobDefinition } from '../../client/_internal/job';
|
||||
|
||||
const BULK_SEND_TEMPLATE_JOB_DEFINITION_ID = 'internal.bulk-send-template';
|
||||
|
||||
@@ -14,9 +14,7 @@ const BULK_SEND_TEMPLATE_JOB_DEFINITION_SCHEMA = z.object({
|
||||
requestMetadata: ZRequestMetadataSchema.optional(),
|
||||
});
|
||||
|
||||
export type TBulkSendTemplateJobDefinition = z.infer<
|
||||
typeof BULK_SEND_TEMPLATE_JOB_DEFINITION_SCHEMA
|
||||
>;
|
||||
export type TBulkSendTemplateJobDefinition = z.infer<typeof BULK_SEND_TEMPLATE_JOB_DEFINITION_SCHEMA>;
|
||||
|
||||
export const BULK_SEND_TEMPLATE_JOB_DEFINITION = {
|
||||
id: BULK_SEND_TEMPLATE_JOB_DEFINITION_ID,
|
||||
@@ -31,7 +29,4 @@ export const BULK_SEND_TEMPLATE_JOB_DEFINITION = {
|
||||
|
||||
await handler.run({ payload, io });
|
||||
},
|
||||
} as const satisfies JobDefinition<
|
||||
typeof BULK_SEND_TEMPLATE_JOB_DEFINITION_ID,
|
||||
TBulkSendTemplateJobDefinition
|
||||
>;
|
||||
} as const satisfies JobDefinition<typeof BULK_SEND_TEMPLATE_JOB_DEFINITION_ID, TBulkSendTemplateJobDefinition>;
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { DateTime } from 'luxon';
|
||||
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import { DateTime } from 'luxon';
|
||||
|
||||
import type { JobRunIO } from '../../client/_internal/job';
|
||||
import type { TCleanupRateLimitsJobDefinition } from './cleanup-rate-limits';
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import { type JobDefinition } from '../../client/_internal/job';
|
||||
import type { JobDefinition } from '../../client/_internal/job';
|
||||
|
||||
const CLEANUP_RATE_LIMITS_JOB_DEFINITION_ID = 'internal.cleanup-rate-limits';
|
||||
|
||||
const CLEANUP_RATE_LIMITS_JOB_DEFINITION_SCHEMA = z.object({});
|
||||
|
||||
export type TCleanupRateLimitsJobDefinition = z.infer<
|
||||
typeof CLEANUP_RATE_LIMITS_JOB_DEFINITION_SCHEMA
|
||||
>;
|
||||
export type TCleanupRateLimitsJobDefinition = z.infer<typeof CLEANUP_RATE_LIMITS_JOB_DEFINITION_SCHEMA>;
|
||||
|
||||
export const CLEANUP_RATE_LIMITS_JOB_DEFINITION = {
|
||||
id: CLEANUP_RATE_LIMITS_JOB_DEFINITION_ID,
|
||||
@@ -24,7 +22,4 @@ export const CLEANUP_RATE_LIMITS_JOB_DEFINITION = {
|
||||
|
||||
await handler.run({ payload, io });
|
||||
},
|
||||
} as const satisfies JobDefinition<
|
||||
typeof CLEANUP_RATE_LIMITS_JOB_DEFINITION_ID,
|
||||
TCleanupRateLimitsJobDefinition
|
||||
>;
|
||||
} as const satisfies JobDefinition<typeof CLEANUP_RATE_LIMITS_JOB_DEFINITION_ID, TCleanupRateLimitsJobDefinition>;
|
||||
|
||||
@@ -1,19 +1,12 @@
|
||||
import type { Prisma } from '@prisma/client';
|
||||
import { WebhookCallStatus } from '@prisma/client';
|
||||
|
||||
import { executeWebhookCall } from '@documenso/lib/server-only/webhooks/execute-webhook-call';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import type { Prisma } from '@prisma/client';
|
||||
import { WebhookCallStatus } from '@prisma/client';
|
||||
|
||||
import type { JobRunIO } from '../../client/_internal/job';
|
||||
import type { TExecuteWebhookJobDefinition } from './execute-webhook';
|
||||
|
||||
export const run = async ({
|
||||
payload,
|
||||
io: _io,
|
||||
}: {
|
||||
payload: TExecuteWebhookJobDefinition;
|
||||
io: JobRunIO;
|
||||
}) => {
|
||||
export const run = async ({ payload, io: _io }: { payload: TExecuteWebhookJobDefinition; io: JobRunIO }) => {
|
||||
const { event, webhookId, data } = payload;
|
||||
|
||||
const webhook = await prisma.webhook.findUniqueOrThrow({
|
||||
|
||||
@@ -2,7 +2,7 @@ import { WebhookTriggerEvents } from '@prisma/client';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { ZRequestMetadataSchema } from '../../../universal/extract-request-metadata';
|
||||
import { type JobDefinition } from '../../client/_internal/job';
|
||||
import type { JobDefinition } from '../../client/_internal/job';
|
||||
|
||||
const EXECUTE_WEBHOOK_JOB_DEFINITION_ID = 'internal.execute-webhook';
|
||||
|
||||
@@ -28,7 +28,4 @@ export const EXECUTE_WEBHOOK_JOB_DEFINITION = {
|
||||
|
||||
await handler.run({ payload, io });
|
||||
},
|
||||
} as const satisfies JobDefinition<
|
||||
typeof EXECUTE_WEBHOOK_JOB_DEFINITION_ID,
|
||||
TExecuteWebhookJobDefinition
|
||||
>;
|
||||
} as const satisfies JobDefinition<typeof EXECUTE_WEBHOOK_JOB_DEFINITION_ID, TExecuteWebhookJobDefinition>;
|
||||
|
||||
@@ -1,17 +1,11 @@
|
||||
import { DocumentStatus, SigningStatus } from '@prisma/client';
|
||||
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import { DocumentStatus, SigningStatus } from '@prisma/client';
|
||||
|
||||
import { jobs } from '../../client';
|
||||
import type { JobRunIO } from '../../client/_internal/job';
|
||||
import type { TExpireRecipientsSweepJobDefinition } from './expire-recipients-sweep';
|
||||
|
||||
export const run = async ({
|
||||
io,
|
||||
}: {
|
||||
payload: TExpireRecipientsSweepJobDefinition;
|
||||
io: JobRunIO;
|
||||
}) => {
|
||||
export const run = async ({ io }: { payload: TExpireRecipientsSweepJobDefinition; io: JobRunIO }) => {
|
||||
const now = new Date();
|
||||
|
||||
const expiredRecipients = await prisma.recipient.findMany({
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import { type JobDefinition } from '../../client/_internal/job';
|
||||
import type { JobDefinition } from '../../client/_internal/job';
|
||||
|
||||
const EXPIRE_RECIPIENTS_SWEEP_JOB_DEFINITION_ID = 'internal.expire-recipients-sweep';
|
||||
|
||||
const EXPIRE_RECIPIENTS_SWEEP_JOB_DEFINITION_SCHEMA = z.object({});
|
||||
|
||||
export type TExpireRecipientsSweepJobDefinition = z.infer<
|
||||
typeof EXPIRE_RECIPIENTS_SWEEP_JOB_DEFINITION_SCHEMA
|
||||
>;
|
||||
export type TExpireRecipientsSweepJobDefinition = z.infer<typeof EXPIRE_RECIPIENTS_SWEEP_JOB_DEFINITION_SCHEMA>;
|
||||
|
||||
export const EXPIRE_RECIPIENTS_SWEEP_JOB_DEFINITION = {
|
||||
id: EXPIRE_RECIPIENTS_SWEEP_JOB_DEFINITION_ID,
|
||||
|
||||
@@ -1,25 +1,15 @@
|
||||
import { SigningStatus, WebhookTriggerEvents } from '@prisma/client';
|
||||
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import { SigningStatus, WebhookTriggerEvents } from '@prisma/client';
|
||||
|
||||
import { triggerWebhook } from '../../../server-only/webhooks/trigger/trigger-webhook';
|
||||
import { DOCUMENT_AUDIT_LOG_TYPE } from '../../../types/document-audit-logs';
|
||||
import {
|
||||
ZWebhookDocumentSchema,
|
||||
mapEnvelopeToWebhookDocumentPayload,
|
||||
} from '../../../types/webhook-payload';
|
||||
import { mapEnvelopeToWebhookDocumentPayload, ZWebhookDocumentSchema } from '../../../types/webhook-payload';
|
||||
import { createDocumentAuditLogData } from '../../../utils/document-audit-logs';
|
||||
import { jobs } from '../../client';
|
||||
import type { JobRunIO } from '../../client/_internal/job';
|
||||
import type { TProcessRecipientExpiredJobDefinition } from './process-recipient-expired';
|
||||
|
||||
export const run = async ({
|
||||
payload,
|
||||
io,
|
||||
}: {
|
||||
payload: TProcessRecipientExpiredJobDefinition;
|
||||
io: JobRunIO;
|
||||
}) => {
|
||||
export const run = async ({ payload, io }: { payload: TProcessRecipientExpiredJobDefinition; io: JobRunIO }) => {
|
||||
const { recipientId } = payload;
|
||||
|
||||
// Atomic idempotency guard — only one concurrent worker wins.
|
||||
@@ -57,9 +47,7 @@ export const run = async ({
|
||||
|
||||
const { envelope } = recipient;
|
||||
|
||||
io.logger.info(
|
||||
`Recipient ${recipientId} (${recipient.email}) expired on envelope ${recipient.envelopeId}`,
|
||||
);
|
||||
io.logger.info(`Recipient ${recipientId} (${recipient.email}) expired on envelope ${recipient.envelopeId}`);
|
||||
|
||||
// Create audit log entry — wrapped so a retry skips this if it already succeeded.
|
||||
await io.runTask('create-audit-log', async () => {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import { type JobDefinition } from '../../client/_internal/job';
|
||||
import type { JobDefinition } from '../../client/_internal/job';
|
||||
|
||||
const PROCESS_RECIPIENT_EXPIRED_JOB_DEFINITION_ID = 'internal.process-recipient-expired';
|
||||
|
||||
@@ -8,9 +8,7 @@ const PROCESS_RECIPIENT_EXPIRED_JOB_DEFINITION_SCHEMA = z.object({
|
||||
recipientId: z.number(),
|
||||
});
|
||||
|
||||
export type TProcessRecipientExpiredJobDefinition = z.infer<
|
||||
typeof PROCESS_RECIPIENT_EXPIRED_JOB_DEFINITION_SCHEMA
|
||||
>;
|
||||
export type TProcessRecipientExpiredJobDefinition = z.infer<typeof PROCESS_RECIPIENT_EXPIRED_JOB_DEFINITION_SCHEMA>;
|
||||
|
||||
export const PROCESS_RECIPIENT_EXPIRED_JOB_DEFINITION = {
|
||||
id: PROCESS_RECIPIENT_EXPIRED_JOB_DEFINITION_ID,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { createElement } from 'react';
|
||||
|
||||
import { mailer } from '@documenso/email/mailer';
|
||||
import DocumentReminderEmailTemplate from '@documenso/email/templates/document-reminder';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import {
|
||||
DocumentDistributionMethod,
|
||||
@@ -10,10 +11,7 @@ import {
|
||||
SigningStatus,
|
||||
WebhookTriggerEvents,
|
||||
} from '@prisma/client';
|
||||
|
||||
import { mailer } from '@documenso/email/mailer';
|
||||
import DocumentReminderEmailTemplate from '@documenso/email/templates/document-reminder';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import { createElement } from 'react';
|
||||
|
||||
import { getI18nInstance } from '../../../client-only/providers/i18n-server';
|
||||
import { NEXT_PUBLIC_WEBAPP_URL } from '../../../constants/app';
|
||||
@@ -23,23 +21,14 @@ import { updateRecipientNextReminder } from '../../../server-only/recipient/upda
|
||||
import { triggerWebhook } from '../../../server-only/webhooks/trigger/trigger-webhook';
|
||||
import { DOCUMENT_AUDIT_LOG_TYPE, DOCUMENT_EMAIL_TYPE } from '../../../types/document-audit-logs';
|
||||
import { extractDerivedDocumentEmailSettings } from '../../../types/document-email';
|
||||
import {
|
||||
ZWebhookDocumentSchema,
|
||||
mapEnvelopeToWebhookDocumentPayload,
|
||||
} from '../../../types/webhook-payload';
|
||||
import { mapEnvelopeToWebhookDocumentPayload, ZWebhookDocumentSchema } from '../../../types/webhook-payload';
|
||||
import { createDocumentAuditLogData } from '../../../utils/document-audit-logs';
|
||||
import { renderCustomEmailTemplate } from '../../../utils/render-custom-email-template';
|
||||
import { renderEmailWithI18N } from '../../../utils/render-email-with-i18n';
|
||||
import type { JobRunIO } from '../../client/_internal/job';
|
||||
import type { TProcessSigningReminderJobDefinition } from './process-signing-reminder';
|
||||
|
||||
export const run = async ({
|
||||
payload,
|
||||
io,
|
||||
}: {
|
||||
payload: TProcessSigningReminderJobDefinition;
|
||||
io: JobRunIO;
|
||||
}) => {
|
||||
export const run = async ({ payload, io }: { payload: TProcessSigningReminderJobDefinition; io: JobRunIO }) => {
|
||||
const { recipientId } = payload;
|
||||
const now = new Date();
|
||||
|
||||
@@ -111,30 +100,23 @@ export const run = async ({
|
||||
return;
|
||||
}
|
||||
|
||||
const { branding, emailLanguage, organisationType, senderEmail, replyToEmail } =
|
||||
await getEmailContext({
|
||||
emailType: 'RECIPIENT',
|
||||
source: {
|
||||
type: 'team',
|
||||
teamId: envelope.teamId,
|
||||
},
|
||||
meta: envelope.documentMeta,
|
||||
});
|
||||
const { branding, emailLanguage, organisationType, senderEmail, replyToEmail } = await getEmailContext({
|
||||
emailType: 'RECIPIENT',
|
||||
source: {
|
||||
type: 'team',
|
||||
teamId: envelope.teamId,
|
||||
},
|
||||
meta: envelope.documentMeta,
|
||||
});
|
||||
|
||||
const i18n = await getI18nInstance(emailLanguage);
|
||||
|
||||
const recipientActionVerb = i18n
|
||||
._(RECIPIENT_ROLES_DESCRIPTION[recipient.role].actionVerb)
|
||||
.toLowerCase();
|
||||
const recipientActionVerb = i18n._(RECIPIENT_ROLES_DESCRIPTION[recipient.role].actionVerb).toLowerCase();
|
||||
|
||||
let emailSubject = i18n._(
|
||||
msg`Reminder: Please ${recipientActionVerb} the document "${envelope.title}"`,
|
||||
);
|
||||
let emailSubject = i18n._(msg`Reminder: Please ${recipientActionVerb} the document "${envelope.title}"`);
|
||||
|
||||
if (organisationType === OrganisationType.ORGANISATION) {
|
||||
emailSubject = i18n._(
|
||||
msg`Reminder: ${envelope.team.name} invited you to ${recipientActionVerb} a document`,
|
||||
);
|
||||
emailSubject = i18n._(msg`Reminder: ${envelope.team.name} invited you to ${recipientActionVerb} a document`);
|
||||
}
|
||||
|
||||
const customEmailTemplate = {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import { type JobDefinition } from '../../client/_internal/job';
|
||||
import type { JobDefinition } from '../../client/_internal/job';
|
||||
|
||||
const PROCESS_SIGNING_REMINDER_JOB_DEFINITION_ID = 'internal.process-signing-reminder';
|
||||
|
||||
@@ -8,9 +8,7 @@ const PROCESS_SIGNING_REMINDER_JOB_DEFINITION_SCHEMA = z.object({
|
||||
recipientId: z.number(),
|
||||
});
|
||||
|
||||
export type TProcessSigningReminderJobDefinition = z.infer<
|
||||
typeof PROCESS_SIGNING_REMINDER_JOB_DEFINITION_SCHEMA
|
||||
>;
|
||||
export type TProcessSigningReminderJobDefinition = z.infer<typeof PROCESS_SIGNING_REMINDER_JOB_DEFINITION_SCHEMA>;
|
||||
|
||||
export const PROCESS_SIGNING_REMINDER_JOB_DEFINITION = {
|
||||
id: PROCESS_SIGNING_REMINDER_JOB_DEFINITION_ID,
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { kyselyPrisma, sql } from '@documenso/prisma';
|
||||
import { DocumentStatus, EnvelopeType, RecipientRole, SigningStatus } from '@prisma/client';
|
||||
import { DateTime } from 'luxon';
|
||||
|
||||
import { kyselyPrisma, sql } from '@documenso/prisma';
|
||||
|
||||
import { mapSecondaryIdToDocumentId } from '../../../utils/envelope';
|
||||
import { jobs } from '../../client';
|
||||
import type { JobRunIO } from '../../client/_internal/job';
|
||||
@@ -30,9 +29,7 @@ export const run = async ({ io }: { payload: TSealDocumentSweepJobDefinition; io
|
||||
.where('Envelope.type', '=', sql.lit(EnvelopeType.DOCUMENT))
|
||||
.where('Envelope.deletedAt', 'is', null)
|
||||
// Ensure there is at least one recipient.
|
||||
.where((eb) =>
|
||||
eb.exists(eb.selectFrom('Recipient').whereRef('Recipient.envelopeId', '=', 'Envelope.id')),
|
||||
)
|
||||
.where((eb) => eb.exists(eb.selectFrom('Recipient').whereRef('Recipient.envelopeId', '=', 'Envelope.id')))
|
||||
// Document is ready to seal: all recipients are SIGNED/CC, or any recipient REJECTED.
|
||||
.where((eb) =>
|
||||
eb.or([
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import { type JobDefinition } from '../../client/_internal/job';
|
||||
import type { JobDefinition } from '../../client/_internal/job';
|
||||
|
||||
const SEAL_DOCUMENT_SWEEP_JOB_DEFINITION_ID = 'internal.seal-document-sweep';
|
||||
|
||||
const SEAL_DOCUMENT_SWEEP_JOB_DEFINITION_SCHEMA = z.object({});
|
||||
|
||||
export type TSealDocumentSweepJobDefinition = z.infer<
|
||||
typeof SEAL_DOCUMENT_SWEEP_JOB_DEFINITION_SCHEMA
|
||||
>;
|
||||
export type TSealDocumentSweepJobDefinition = z.infer<typeof SEAL_DOCUMENT_SWEEP_JOB_DEFINITION_SCHEMA>;
|
||||
|
||||
export const SEAL_DOCUMENT_SWEEP_JOB_DEFINITION = {
|
||||
id: SEAL_DOCUMENT_SWEEP_JOB_DEFINITION_ID,
|
||||
@@ -24,7 +22,4 @@ export const SEAL_DOCUMENT_SWEEP_JOB_DEFINITION = {
|
||||
|
||||
await handler.run({ payload, io });
|
||||
},
|
||||
} as const satisfies JobDefinition<
|
||||
typeof SEAL_DOCUMENT_SWEEP_JOB_DEFINITION_ID,
|
||||
TSealDocumentSweepJobDefinition
|
||||
>;
|
||||
} as const satisfies JobDefinition<typeof SEAL_DOCUMENT_SWEEP_JOB_DEFINITION_ID, TSealDocumentSweepJobDefinition>;
|
||||
|
||||
@@ -1,23 +1,16 @@
|
||||
import { PDFDocument } from '@cantoo/pdf-lib';
|
||||
import { PDF } from '@libpdf/core';
|
||||
import type { DocumentData, Envelope, EnvelopeItem, Field } from '@prisma/client';
|
||||
import {
|
||||
DocumentStatus,
|
||||
EnvelopeType,
|
||||
RecipientRole,
|
||||
SigningStatus,
|
||||
WebhookTriggerEvents,
|
||||
} from '@prisma/client';
|
||||
import { nanoid } from 'nanoid';
|
||||
import path from 'node:path';
|
||||
import { groupBy } from 'remeda';
|
||||
|
||||
import { PDFDocument } from '@cantoo/pdf-lib';
|
||||
import { addRejectionStampToPdf } from '@documenso/lib/server-only/pdf/add-rejection-stamp-to-pdf';
|
||||
import { generateAuditLogPdf } from '@documenso/lib/server-only/pdf/generate-audit-log-pdf';
|
||||
import { generateCertificatePdf } from '@documenso/lib/server-only/pdf/generate-certificate-pdf';
|
||||
import { getLastPageDimensions } from '@documenso/lib/server-only/pdf/get-page-size';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import { signPdf } from '@documenso/signing';
|
||||
import { PDF } from '@libpdf/core';
|
||||
import type { DocumentData, Envelope, EnvelopeItem, Field } from '@prisma/client';
|
||||
import { DocumentStatus, EnvelopeType, RecipientRole, SigningStatus, WebhookTriggerEvents } from '@prisma/client';
|
||||
import { nanoid } from 'nanoid';
|
||||
import { groupBy } from 'remeda';
|
||||
|
||||
import { NEXT_PRIVATE_USE_PLAYWRIGHT_PDF } from '../../../constants/app';
|
||||
import { AppError, AppErrorCode } from '../../../errors/app-error';
|
||||
@@ -29,14 +22,8 @@ import { insertFieldInPDFV2 } from '../../../server-only/pdf/insert-field-in-pdf
|
||||
import { legacy_insertFieldInPDF } from '../../../server-only/pdf/legacy-insert-field-in-pdf';
|
||||
import { getTeamSettings } from '../../../server-only/team/get-team-settings';
|
||||
import { triggerWebhook } from '../../../server-only/webhooks/trigger/trigger-webhook';
|
||||
import {
|
||||
DOCUMENT_AUDIT_LOG_TYPE,
|
||||
type TDocumentAuditLog,
|
||||
} from '../../../types/document-audit-logs';
|
||||
import {
|
||||
ZWebhookDocumentSchema,
|
||||
mapEnvelopeToWebhookDocumentPayload,
|
||||
} from '../../../types/webhook-payload';
|
||||
import { DOCUMENT_AUDIT_LOG_TYPE, type TDocumentAuditLog } from '../../../types/document-audit-logs';
|
||||
import { mapEnvelopeToWebhookDocumentPayload, ZWebhookDocumentSchema } from '../../../types/webhook-payload';
|
||||
import { prefixedId } from '../../../universal/id';
|
||||
import { getFileServerSide } from '../../../universal/upload/get-file.server';
|
||||
import { putPdfFileServerSide } from '../../../universal/upload/put-file.server';
|
||||
@@ -47,13 +34,7 @@ import { mapDocumentIdToSecondaryId } from '../../../utils/envelope';
|
||||
import type { JobRunIO } from '../../client/_internal/job';
|
||||
import type { TSealDocumentJobDefinition } from './seal-document';
|
||||
|
||||
export const run = async ({
|
||||
payload,
|
||||
io,
|
||||
}: {
|
||||
payload: TSealDocumentJobDefinition;
|
||||
io: JobRunIO;
|
||||
}) => {
|
||||
export const run = async ({ payload, io }: { payload: TSealDocumentJobDefinition; io: JobRunIO }) => {
|
||||
const { documentId, sendEmail = true, isResealing = false, requestMetadata } = payload;
|
||||
|
||||
const { envelopeId, envelopeStatus, isRejected } = await io.runTask('seal-document', async () => {
|
||||
@@ -112,8 +93,7 @@ export const run = async ({
|
||||
const isComplete =
|
||||
envelope.recipients.some((recipient) => recipient.signingStatus === SigningStatus.REJECTED) ||
|
||||
envelope.recipients.every(
|
||||
(recipient) =>
|
||||
recipient.signingStatus === SigningStatus.SIGNED || recipient.role === RecipientRole.CC,
|
||||
(recipient) => recipient.signingStatus === SigningStatus.SIGNED || recipient.role === RecipientRole.CC,
|
||||
);
|
||||
|
||||
if (!isComplete) {
|
||||
@@ -130,9 +110,7 @@ export const run = async ({
|
||||
throw new Error(`Document ${envelope.id} has no envelope items`);
|
||||
}
|
||||
|
||||
const recipientsWithoutCCers = envelope.recipients.filter(
|
||||
(recipient) => recipient.role !== RecipientRole.CC,
|
||||
);
|
||||
const recipientsWithoutCCers = envelope.recipients.filter((recipient) => recipient.role !== RecipientRole.CC);
|
||||
|
||||
// Determine if the document has been rejected by checking if any recipient has rejected it
|
||||
const rejectedRecipient = recipientsWithoutCCers.find(
|
||||
@@ -204,9 +182,7 @@ export const run = async ({
|
||||
const newDocumentData: Array<{ oldDocumentDataId: string; newDocumentDataId: string }> = [];
|
||||
|
||||
for (const { envelopeItem, pdfData } of prefetchedItems) {
|
||||
const envelopeItemFields = envelope.envelopeItems.find(
|
||||
(item) => item.id === envelopeItem.id,
|
||||
)?.field;
|
||||
const envelopeItemFields = envelope.envelopeItems.find((item) => item.id === envelopeItem.id)?.field;
|
||||
|
||||
if (!envelopeItemFields) {
|
||||
throw new Error(`Envelope item fields not found for envelope item ${envelopeItem.id}`);
|
||||
@@ -344,9 +320,7 @@ export const run = async ({
|
||||
});
|
||||
|
||||
await triggerWebhook({
|
||||
event: isRejected
|
||||
? WebhookTriggerEvents.DOCUMENT_REJECTED
|
||||
: WebhookTriggerEvents.DOCUMENT_COMPLETED,
|
||||
event: isRejected ? WebhookTriggerEvents.DOCUMENT_REJECTED : WebhookTriggerEvents.DOCUMENT_COMPLETED,
|
||||
data: ZWebhookDocumentSchema.parse(mapEnvelopeToWebhookDocumentPayload(updatedEnvelope)),
|
||||
userId: updatedEnvelope.userId,
|
||||
teamId: updatedEnvelope.teamId ?? undefined,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import { ZRequestMetadataSchema } from '../../../universal/extract-request-metadata';
|
||||
import { type JobDefinition } from '../../client/_internal/job';
|
||||
import type { JobDefinition } from '../../client/_internal/job';
|
||||
|
||||
const SEAL_DOCUMENT_JOB_DEFINITION_ID = 'internal.seal-document';
|
||||
|
||||
@@ -28,7 +28,4 @@ export const SEAL_DOCUMENT_JOB_DEFINITION = {
|
||||
|
||||
await handler.run({ payload, io });
|
||||
},
|
||||
} as const satisfies JobDefinition<
|
||||
typeof SEAL_DOCUMENT_JOB_DEFINITION_ID,
|
||||
TSealDocumentJobDefinition
|
||||
>;
|
||||
} as const satisfies JobDefinition<typeof SEAL_DOCUMENT_JOB_DEFINITION_ID, TSealDocumentJobDefinition>;
|
||||
|
||||
@@ -1,17 +1,11 @@
|
||||
import { DocumentStatus, RecipientRole, SendStatus, SigningStatus } from '@prisma/client';
|
||||
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import { DocumentStatus, RecipientRole, SendStatus, SigningStatus } from '@prisma/client';
|
||||
|
||||
import { jobs } from '../../client';
|
||||
import type { JobRunIO } from '../../client/_internal/job';
|
||||
import type { TSendSigningRemindersSweepJobDefinition } from './send-signing-reminders-sweep';
|
||||
|
||||
export const run = async ({
|
||||
io,
|
||||
}: {
|
||||
payload: TSendSigningRemindersSweepJobDefinition;
|
||||
io: JobRunIO;
|
||||
}) => {
|
||||
export const run = async ({ io }: { payload: TSendSigningRemindersSweepJobDefinition; io: JobRunIO }) => {
|
||||
const now = new Date();
|
||||
|
||||
const recipients = await prisma.recipient.findMany({
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import { type JobDefinition } from '../../client/_internal/job';
|
||||
import type { JobDefinition } from '../../client/_internal/job';
|
||||
|
||||
const SEND_SIGNING_REMINDERS_SWEEP_JOB_DEFINITION_ID = 'internal.send-signing-reminders-sweep';
|
||||
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { DateTime } from 'luxon';
|
||||
|
||||
import { reregisterEmailDomain } from '@documenso/ee/server-only/lib/reregister-email-domain';
|
||||
import { verifyEmailDomain } from '@documenso/ee/server-only/lib/verify-email-domain';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import { DateTime } from 'luxon';
|
||||
|
||||
import type { JobRunIO } from '../../client/_internal/job';
|
||||
import type { TSyncEmailDomainsJobDefinition } from './sync-email-domains';
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import { type JobDefinition } from '../../client/_internal/job';
|
||||
import type { JobDefinition } from '../../client/_internal/job';
|
||||
|
||||
const SYNC_EMAIL_DOMAINS_JOB_DEFINITION_ID = 'internal.sync-email-domains';
|
||||
|
||||
const SYNC_EMAIL_DOMAINS_JOB_DEFINITION_SCHEMA = z.object({});
|
||||
|
||||
export type TSyncEmailDomainsJobDefinition = z.infer<
|
||||
typeof SYNC_EMAIL_DOMAINS_JOB_DEFINITION_SCHEMA
|
||||
>;
|
||||
export type TSyncEmailDomainsJobDefinition = z.infer<typeof SYNC_EMAIL_DOMAINS_JOB_DEFINITION_SCHEMA>;
|
||||
|
||||
export const SYNC_EMAIL_DOMAINS_JOB_DEFINITION = {
|
||||
id: SYNC_EMAIL_DOMAINS_JOB_DEFINITION_ID,
|
||||
@@ -24,7 +22,4 @@ export const SYNC_EMAIL_DOMAINS_JOB_DEFINITION = {
|
||||
|
||||
await handler.run({ payload, io });
|
||||
},
|
||||
} as const satisfies JobDefinition<
|
||||
typeof SYNC_EMAIL_DOMAINS_JOB_DEFINITION_ID,
|
||||
TSyncEmailDomainsJobDefinition
|
||||
>;
|
||||
} as const satisfies JobDefinition<typeof SYNC_EMAIL_DOMAINS_JOB_DEFINITION_ID, TSyncEmailDomainsJobDefinition>;
|
||||
|
||||
@@ -12,8 +12,6 @@
|
||||
"scripts": {
|
||||
"test": "vitest run",
|
||||
"test:watch": "vitest",
|
||||
"lint": "eslint .",
|
||||
"lint:fix": "eslint . --fix",
|
||||
"clean": "rimraf node_modules"
|
||||
},
|
||||
"dependencies": {
|
||||
@@ -59,6 +57,8 @@
|
||||
"pino": "^9.14.0",
|
||||
"pino-pretty": "^13.1.2",
|
||||
"playwright": "1.56.1",
|
||||
"postcss": "^8.5.6",
|
||||
"postcss-selector-parser": "^7.1.0",
|
||||
"posthog-js": "^1.297.2",
|
||||
"posthog-node": "4.18.0",
|
||||
"react": "^18",
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { PlainClient } from '@team-plain/typescript-sdk';
|
||||
|
||||
import { env } from '@documenso/lib/utils/env';
|
||||
import { PlainClient } from '@team-plain/typescript-sdk';
|
||||
|
||||
export const plainClient = new PlainClient({
|
||||
apiKey: env('NEXT_PRIVATE_PLAIN_API_KEY') ?? '',
|
||||
|
||||
@@ -1,17 +1,13 @@
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import type { User } from '@prisma/client';
|
||||
import { UserSecurityAuditLogType } from '@prisma/client';
|
||||
|
||||
import { prisma } from '@documenso/prisma';
|
||||
|
||||
import { AppError, AppErrorCode } from '../../errors/app-error';
|
||||
import type { RequestMetadata } from '../../universal/extract-request-metadata';
|
||||
import { validateTwoFactorAuthentication } from './validate-2fa';
|
||||
|
||||
type DisableTwoFactorAuthenticationOptions = {
|
||||
user: Pick<
|
||||
User,
|
||||
'id' | 'email' | 'twoFactorEnabled' | 'twoFactorSecret' | 'twoFactorBackupCodes'
|
||||
>;
|
||||
user: Pick<User, 'id' | 'email' | 'twoFactorEnabled' | 'twoFactorSecret' | 'twoFactorBackupCodes'>;
|
||||
totpCode?: string;
|
||||
backupCode?: string;
|
||||
requestMetadata?: RequestMetadata;
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
import { createElement } from 'react';
|
||||
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { EnvelopeType } from '@prisma/client';
|
||||
|
||||
import { mailer } from '@documenso/email/mailer';
|
||||
import { AccessAuth2FAEmailTemplate } from '@documenso/email/templates/access-auth-2fa';
|
||||
import { isRecipientEmailValidForSending } from '@documenso/lib/utils/recipients';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { EnvelopeType } from '@prisma/client';
|
||||
import { createElement } from 'react';
|
||||
|
||||
import { getI18nInstance } from '../../../client-only/providers/i18n-server';
|
||||
import { NEXT_PUBLIC_WEBAPP_URL } from '../../../constants/app';
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { type User, UserSecurityAuditLogType } from '@prisma/client';
|
||||
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import { type User, UserSecurityAuditLogType } from '@prisma/client';
|
||||
|
||||
import { AppError } from '../../errors/app-error';
|
||||
import type { RequestMetadata } from '../../universal/extract-request-metadata';
|
||||
|
||||
@@ -25,9 +25,7 @@ export const getBackupCodes = ({ user }: GetBackupCodesOptions) => {
|
||||
throw new Error('User has no backup codes');
|
||||
}
|
||||
|
||||
const secret = Buffer.from(symmetricDecrypt({ key, data: user.twoFactorBackupCodes })).toString(
|
||||
'utf-8',
|
||||
);
|
||||
const secret = Buffer.from(symmetricDecrypt({ key, data: user.twoFactorBackupCodes })).toString('utf-8');
|
||||
|
||||
const data = JSON.parse(secret);
|
||||
|
||||
|
||||
@@ -6,8 +6,6 @@ type IsTwoFactorAuthenticationEnabledOptions = {
|
||||
user: User;
|
||||
};
|
||||
|
||||
export const isTwoFactorAuthenticationEnabled = ({
|
||||
user,
|
||||
}: IsTwoFactorAuthenticationEnabledOptions) => {
|
||||
export const isTwoFactorAuthenticationEnabled = ({ user }: IsTwoFactorAuthenticationEnabledOptions) => {
|
||||
return user.twoFactorEnabled && typeof DOCUMENSO_ENCRYPTION_KEY === 'string';
|
||||
};
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import { type User } from '@prisma/client';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import type { User } from '@prisma/client';
|
||||
import { base32 } from '@scure/base';
|
||||
import crypto from 'crypto';
|
||||
import { createTOTPKeyURI } from 'oslo/otp';
|
||||
|
||||
import { prisma } from '@documenso/prisma';
|
||||
|
||||
import { DOCUMENSO_ENCRYPTION_KEY } from '../../constants/crypto';
|
||||
import { symmetricEncrypt } from '../../universal/crypto';
|
||||
|
||||
@@ -14,9 +13,7 @@ type SetupTwoFactorAuthenticationOptions = {
|
||||
|
||||
const ISSUER = 'Documenso';
|
||||
|
||||
export const setupTwoFactorAuthentication = async ({
|
||||
user,
|
||||
}: SetupTwoFactorAuthenticationOptions) => {
|
||||
export const setupTwoFactorAuthentication = async ({ user }: SetupTwoFactorAuthenticationOptions) => {
|
||||
const key = DOCUMENSO_ENCRYPTION_KEY;
|
||||
|
||||
if (!key) {
|
||||
|
||||
@@ -7,10 +7,7 @@ import { verifyBackupCode } from './verify-backup-code';
|
||||
type ValidateTwoFactorAuthenticationOptions = {
|
||||
totpCode?: string;
|
||||
backupCode?: string;
|
||||
user: Pick<
|
||||
User,
|
||||
'id' | 'email' | 'twoFactorEnabled' | 'twoFactorSecret' | 'twoFactorBackupCodes'
|
||||
>;
|
||||
user: Pick<User, 'id' | 'email' | 'twoFactorEnabled' | 'twoFactorSecret' | 'twoFactorBackupCodes'>;
|
||||
};
|
||||
|
||||
export const validateTwoFactorAuthentication = async ({
|
||||
|
||||
@@ -30,9 +30,7 @@ export const verifyTwoFactorAuthenticationToken = async ({
|
||||
throw new Error('user missing 2fa secret');
|
||||
}
|
||||
|
||||
const secret = Buffer.from(symmetricDecrypt({ key, data: user.twoFactorSecret })).toString(
|
||||
'utf-8',
|
||||
);
|
||||
const secret = Buffer.from(symmetricDecrypt({ key, data: user.twoFactorSecret })).toString('utf-8');
|
||||
|
||||
const decodedSecret = base32.decode(secret);
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { compare } from '@node-rs/bcrypt';
|
||||
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import { compare } from '@node-rs/bcrypt';
|
||||
|
||||
type VerifyPasswordOptions = {
|
||||
userId: number;
|
||||
|
||||
@@ -5,10 +5,7 @@ import { getBackupCodes } from './get-backup-code';
|
||||
import { validateTwoFactorAuthentication } from './validate-2fa';
|
||||
|
||||
type ViewBackupCodesOptions = {
|
||||
user: Pick<
|
||||
User,
|
||||
'id' | 'email' | 'twoFactorEnabled' | 'twoFactorSecret' | 'twoFactorBackupCodes'
|
||||
>;
|
||||
user: Pick<User, 'id' | 'email' | 'twoFactorEnabled' | 'twoFactorSecret' | 'twoFactorBackupCodes'>;
|
||||
token: string;
|
||||
};
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { EnvelopeType, type Prisma } from '@prisma/client';
|
||||
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import { EnvelopeType, type Prisma } from '@prisma/client';
|
||||
|
||||
import type { FindResultResponse } from '../../types/search-params';
|
||||
|
||||
@@ -10,11 +9,7 @@ export interface AdminFindDocumentsOptions {
|
||||
perPage?: number;
|
||||
}
|
||||
|
||||
export const adminFindDocuments = async ({
|
||||
query,
|
||||
page = 1,
|
||||
perPage = 10,
|
||||
}: AdminFindDocumentsOptions) => {
|
||||
export const adminFindDocuments = async ({ query, page = 1, perPage = 10 }: AdminFindDocumentsOptions) => {
|
||||
let termFilters: Prisma.EnvelopeWhereInput | undefined = !query
|
||||
? undefined
|
||||
: {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { DocumentStatus, EnvelopeType, RecipientRole, SigningStatus } from '@prisma/client';
|
||||
|
||||
import { kyselyPrisma, sql } from '@documenso/prisma';
|
||||
import { DocumentStatus, EnvelopeType, RecipientRole, SigningStatus } from '@prisma/client';
|
||||
|
||||
import type { FindResultResponse } from '../../types/search-params';
|
||||
|
||||
@@ -35,9 +34,7 @@ export const adminFindUnsealedDocuments = async ({
|
||||
.where('Envelope.type', '=', sql.lit(EnvelopeType.DOCUMENT))
|
||||
.where('Envelope.deletedAt', 'is', null)
|
||||
// Must have at least one recipient.
|
||||
.where((eb) =>
|
||||
eb.exists(eb.selectFrom('Recipient').whereRef('Recipient.envelopeId', '=', 'Envelope.id')),
|
||||
)
|
||||
.where((eb) => eb.exists(eb.selectFrom('Recipient').whereRef('Recipient.envelopeId', '=', 'Envelope.id')))
|
||||
// Document is ready to seal: all recipients are SIGNED/CC, or any recipient REJECTED.
|
||||
.where((eb) =>
|
||||
eb.or([
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
import { createElement } from 'react';
|
||||
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { DocumentStatus, SendStatus } from '@prisma/client';
|
||||
|
||||
import { mailer } from '@documenso/email/mailer';
|
||||
import DocumentCancelTemplate from '@documenso/email/templates/document-cancel';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { DocumentStatus, SendStatus } from '@prisma/client';
|
||||
import { createElement } from 'react';
|
||||
|
||||
import { getI18nInstance } from '../../client-only/providers/i18n-server';
|
||||
import { NEXT_PUBLIC_WEBAPP_URL } from '../../constants/app';
|
||||
@@ -23,10 +21,7 @@ export type AdminSuperDeleteDocumentOptions = {
|
||||
requestMetadata?: RequestMetadata;
|
||||
};
|
||||
|
||||
export const adminSuperDeleteDocument = async ({
|
||||
envelopeId,
|
||||
requestMetadata,
|
||||
}: AdminSuperDeleteDocumentOptions) => {
|
||||
export const adminSuperDeleteDocument = async ({ envelopeId, requestMetadata }: AdminSuperDeleteDocumentOptions) => {
|
||||
const envelope = await prisma.envelope.findUnique({
|
||||
where: {
|
||||
id: envelopeId,
|
||||
@@ -61,20 +56,12 @@ export const adminSuperDeleteDocument = async ({
|
||||
|
||||
const { status, user } = envelope;
|
||||
|
||||
const isDocumentDeletedEmailEnabled = extractDerivedDocumentEmailSettings(
|
||||
envelope.documentMeta,
|
||||
).documentDeleted;
|
||||
const isDocumentDeletedEmailEnabled = extractDerivedDocumentEmailSettings(envelope.documentMeta).documentDeleted;
|
||||
|
||||
const recipientsToNotify = envelope.recipients.filter((recipient) =>
|
||||
isRecipientEmailValidForSending(recipient),
|
||||
);
|
||||
const recipientsToNotify = envelope.recipients.filter((recipient) => isRecipientEmailValidForSending(recipient));
|
||||
|
||||
// if the document is pending, send cancellation emails to all recipients
|
||||
if (
|
||||
status === DocumentStatus.PENDING &&
|
||||
recipientsToNotify.length > 0 &&
|
||||
isDocumentDeletedEmailEnabled
|
||||
) {
|
||||
if (status === DocumentStatus.PENDING && recipientsToNotify.length > 0 && isDocumentDeletedEmailEnabled) {
|
||||
await Promise.all(
|
||||
recipientsToNotify.map(async (recipient) => {
|
||||
if (recipient.sendStatus !== SendStatus.SENT) {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { EnvelopeType } from '@prisma/client';
|
||||
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import { ExtendedDocumentStatus } from '@documenso/prisma/types/extended-document-status';
|
||||
import { EnvelopeType } from '@prisma/client';
|
||||
|
||||
export const getDocumentStats = async () => {
|
||||
const counts = await prisma.envelope.groupBy({
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import type { EnvelopeType } from '@prisma/client';
|
||||
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import type { EnvelopeType } from '@prisma/client';
|
||||
|
||||
import { AppError, AppErrorCode } from '../../errors/app-error';
|
||||
import type { EnvelopeIdOptions } from '../../utils/envelope';
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user