mirror of
https://github.com/documenso/documenso.git
synced 2026-10-04 02:33:54 +10:00
Merge branch 'main' into feat/add-recipient-groups
This commit is contained in:
@@ -0,0 +1,61 @@
|
||||
name: 'One-Click Deploy Provider Request'
|
||||
description: Request a new one-click deployment provider (Railway, Render, etc.) to be added to our README
|
||||
title: 'One-Click Deploy Provider Request: [Provider Name]'
|
||||
labels: ['deploy-provider-request']
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Thanks for your interest in adding a one-click deploy option for Documenso!
|
||||
|
||||
Each provider we list requires us to create, test, and maintain a deployment template, which is ongoing work on top of everything else. To keep this manageable, we ask that providers (or users) **open an issue instead of a PR** so the community can signal interest.
|
||||
|
||||
**How this works:**
|
||||
|
||||
- 👍 this issue if you'd like to see Documenso deployable on this provider.
|
||||
- If community interest is high enough, we'll consider adding it to the README.
|
||||
- Opening an issue is not a guarantee of inclusion. PRs adding badges without a prior issue and demonstrated interest will be closed.
|
||||
- type: input
|
||||
attributes:
|
||||
label: Provider Name
|
||||
placeholder: e.g. Railway
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
attributes:
|
||||
label: Provider Website
|
||||
placeholder: e.g. https://railway.com
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
attributes:
|
||||
label: Deploy/Template URL
|
||||
description: A link to an existing deployment template or deploy button URL, if one exists.
|
||||
- type: dropdown
|
||||
attributes:
|
||||
label: Who creates and maintains the deployment template?
|
||||
options:
|
||||
- The provider
|
||||
- Me / the community
|
||||
- Nobody yet
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Testing & Maintenance
|
||||
description: Has the template been tested against the current Documenso release? How are updates handled when Documenso ships breaking changes (env vars, migrations, Docker changes)?
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Why this provider?
|
||||
description: Tell us why Documenso users would benefit — existing user base, region coverage, free tier, etc.
|
||||
validations:
|
||||
required: true
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: Please check the boxes that apply to this request.
|
||||
options:
|
||||
- label: I have searched existing issues to make sure this provider has not already been requested.
|
||||
- label: I understand that inclusion depends on community interest and is not guaranteed.
|
||||
- label: I understand that PRs adding deploy badges without a prior issue will be closed.
|
||||
@@ -186,21 +186,37 @@ For full instructions, requirements, and configuration details, see the [Self Ho
|
||||
|
||||
### One-Click Deploys
|
||||
|
||||
#### Railway
|
||||
> [!NOTE]
|
||||
> Want to see another provider listed here? Please [open a provider request](https://github.com/documenso/documenso/issues/new?template=deploy-provider-request.yml) instead of a PR so the community can signal interest. PRs adding deploy badges without a prior issue will be closed.
|
||||
|
||||
[](https://railway.com/deploy/DjrRRX?referralCode=EZR3s0&utm_medium=integration&utm_source=template&utm_campaign=generic)
|
||||
|
||||
#### Render
|
||||
|
||||
[](https://render.com/deploy?repo=https://github.com/documenso/documenso)
|
||||
|
||||
#### Koyeb
|
||||
|
||||
[](https://app.koyeb.com/deploy?type=git&repository=github.com/documenso/documenso&branch=main&name=documenso-app&builder=dockerfile&dockerfile=/docker/Dockerfile)
|
||||
|
||||
#### Elestio
|
||||
|
||||
[](https://elest.io/open-source/documenso)
|
||||
<table>
|
||||
<tr>
|
||||
<td align="center" width="200">
|
||||
<a href="https://railway.com/deploy/DjrRRX?referralCode=EZR3s0&utm_medium=integration&utm_source=template&utm_campaign=generic">
|
||||
<img src="https://railway.com/button.svg" alt="Deploy on Railway" height="40" />
|
||||
</a>
|
||||
</td>
|
||||
<td align="center" width="200">
|
||||
<a href="https://render.com/deploy?repo=https://github.com/documenso/documenso">
|
||||
<img src="https://render.com/images/deploy-to-render-button.svg" alt="Deploy to Render" height="40" />
|
||||
</a>
|
||||
</td>
|
||||
<td align="center" width="200">
|
||||
<a href="https://app.koyeb.com/deploy?type=git&repository=github.com/documenso/documenso&branch=main&name=documenso-app&builder=dockerfile&dockerfile=/docker/Dockerfile">
|
||||
<img src="https://www.koyeb.com/static/images/deploy/button.svg" alt="Deploy to Koyeb" height="40" />
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" width="200">
|
||||
<a href="https://elest.io/open-source/documenso">
|
||||
<img src="https://elest.io/images/logos/deploy-to-elestio-btn.png" alt="Deploy on Elestio" height="40" />
|
||||
</a>
|
||||
</td>
|
||||
<td align="center" width="200"></td>
|
||||
<td align="center" width="200"></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
## Security
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { useAnalytics } from '@documenso/lib/client-only/hooks/use-analytics';
|
||||
import { useCurrentEnvelopeEditor } from '@documenso/lib/client-only/providers/envelope-editor-provider';
|
||||
import { useCurrentOrganisation } from '@documenso/lib/client-only/providers/organisation';
|
||||
import { DO_NOT_INVALIDATE_QUERY_ON_MUTATION } from '@documenso/lib/constants/trpc';
|
||||
@@ -71,6 +72,7 @@ export const EnvelopeDistributeDialog = ({
|
||||
const { toast } = useToast();
|
||||
const { t, i18n } = useLingui();
|
||||
const navigate = useNavigate();
|
||||
const analytics = useAnalytics();
|
||||
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [isSyncing, setIsSyncing] = useState(false);
|
||||
@@ -200,6 +202,12 @@ export const EnvelopeDistributeDialog = ({
|
||||
} catch (err) {
|
||||
const error = AppError.parseError(err);
|
||||
|
||||
analytics.captureException(err, {
|
||||
source: 'editor',
|
||||
location: 'distribute_document',
|
||||
envelopeId: envelope.id,
|
||||
});
|
||||
|
||||
const errorMessage = getDistributeErrorMessage(error.code);
|
||||
|
||||
toast({
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { useAnalytics } from '@documenso/lib/client-only/hooks/use-analytics';
|
||||
import { getRecipientType } from '@documenso/lib/client-only/recipient-type';
|
||||
import { AppError } from '@documenso/lib/errors/app-error';
|
||||
import type { TEnvelope } from '@documenso/lib/types/envelope';
|
||||
@@ -51,6 +52,7 @@ export const EnvelopeRedistributeDialog = ({ envelope, envelopeType, trigger }:
|
||||
|
||||
const { toast } = useToast();
|
||||
const { t, i18n } = useLingui();
|
||||
const analytics = useAnalytics();
|
||||
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
|
||||
@@ -95,6 +97,13 @@ export const EnvelopeRedistributeDialog = ({ envelope, envelopeType, trigger }:
|
||||
setIsOpen(false);
|
||||
} catch (err) {
|
||||
const error = AppError.parseError(err);
|
||||
|
||||
analytics.captureException(err, {
|
||||
source: 'editor',
|
||||
location: 'redistribute_document',
|
||||
envelopeId: envelope.id,
|
||||
});
|
||||
|
||||
const errorMessage = getDistributeErrorMessage(error.code);
|
||||
|
||||
toast({
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { useAnalytics } from '@documenso/lib/client-only/hooks/use-analytics';
|
||||
import { useThrottleFn } from '@documenso/lib/client-only/hooks/use-throttle-fn';
|
||||
import { DEFAULT_DOCUMENT_DATE_FORMAT } from '@documenso/lib/constants/date-formats';
|
||||
import { APP_I18N_OPTIONS } from '@documenso/lib/constants/i18n';
|
||||
@@ -77,6 +78,7 @@ export const EmbedDirectTemplateClientPage = ({
|
||||
}: EmbedDirectTemplateClientPageProps) => {
|
||||
const { _ } = useLingui();
|
||||
const { toast } = useToast();
|
||||
const analytics = useAnalytics();
|
||||
|
||||
const [searchParams] = useSearchParams();
|
||||
|
||||
@@ -264,6 +266,13 @@ export const EmbedDirectTemplateClientPage = ({
|
||||
const error = AppError.parseError(err);
|
||||
const errorMessage = getDirectTemplateErrorMessage(error.code);
|
||||
|
||||
analytics.captureException(err, {
|
||||
source: 'embed',
|
||||
location: 'direct_template',
|
||||
recipientId: recipient.id,
|
||||
envelopeId,
|
||||
});
|
||||
|
||||
toast({
|
||||
title: _(errorMessage.title),
|
||||
description: _(errorMessage.description),
|
||||
@@ -308,6 +317,14 @@ export const EmbedDirectTemplateClientPage = ({
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
|
||||
analytics.captureException(err, {
|
||||
source: 'embed',
|
||||
location: 'embed_init',
|
||||
recipientId: recipient.id,
|
||||
envelopeId,
|
||||
});
|
||||
|
||||
setHasFinishedInit(true);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { useAnalytics } from '@documenso/lib/client-only/hooks/use-analytics';
|
||||
import { useThrottleFn } from '@documenso/lib/client-only/hooks/use-throttle-fn';
|
||||
import { APP_I18N_OPTIONS } from '@documenso/lib/constants/i18n';
|
||||
import { PDF_VIEWER_PAGE_SELECTOR } from '@documenso/lib/constants/pdf-viewer';
|
||||
@@ -75,6 +76,7 @@ export const EmbedSignDocumentV1ClientPage = ({
|
||||
}: EmbedSignDocumentV1ClientPageProps) => {
|
||||
const { _ } = useLingui();
|
||||
const { toast } = useToast();
|
||||
const analytics = useAnalytics();
|
||||
|
||||
const { fullName, email, signature, setFullName, setEmail, setSignature } = useRequiredDocumentSigningContext();
|
||||
|
||||
@@ -154,6 +156,14 @@ export const EmbedSignDocumentV1ClientPage = ({
|
||||
|
||||
setHasCompletedDocument(true);
|
||||
} catch (err) {
|
||||
analytics.captureException(err, {
|
||||
source: 'embed',
|
||||
location: 'complete_document',
|
||||
recipientId: recipient.id,
|
||||
documentId,
|
||||
envelopeId,
|
||||
});
|
||||
|
||||
if (window.parent) {
|
||||
window.parent.postMessage(
|
||||
{
|
||||
@@ -236,6 +246,15 @@ export const EmbedSignDocumentV1ClientPage = ({
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
|
||||
analytics.captureException(err, {
|
||||
source: 'embed',
|
||||
location: 'embed_init',
|
||||
recipientId: recipient.id,
|
||||
documentId,
|
||||
envelopeId,
|
||||
});
|
||||
|
||||
setHasFinishedInit(true);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { useAnalytics } from '@documenso/lib/client-only/hooks/use-analytics';
|
||||
import { APP_I18N_OPTIONS } from '@documenso/lib/constants/i18n';
|
||||
import { ZSignDocumentEmbedDataSchema } from '@documenso/lib/types/embed-document-sign-schema';
|
||||
import { mapSecondaryIdToDocumentId } from '@documenso/lib/utils/envelope';
|
||||
@@ -25,6 +26,7 @@ export const EmbedSignDocumentV2ClientPage = ({
|
||||
allowWhitelabelling = false,
|
||||
}: EmbedSignDocumentV2ClientPageProps) => {
|
||||
const { _ } = useLingui();
|
||||
const analytics = useAnalytics();
|
||||
|
||||
const { envelope, recipient, envelopeData, setFullName, setEmail, fullName, email } =
|
||||
useRequiredEnvelopeSigningContext();
|
||||
@@ -170,6 +172,14 @@ export const EmbedSignDocumentV2ClientPage = ({
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
|
||||
analytics.captureException(err, {
|
||||
source: 'embed',
|
||||
location: 'embed_init',
|
||||
recipientId: recipient.id,
|
||||
envelopeId: envelope.id,
|
||||
});
|
||||
|
||||
setHasFinishedInit(true);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { useAnalytics } from '@documenso/lib/client-only/hooks/use-analytics';
|
||||
import { PDF_VIEWER_PAGE_SELECTOR } from '@documenso/lib/constants/pdf-viewer';
|
||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
import { getDocumentDataUrlForPdfViewer } from '@documenso/lib/utils/envelope-download';
|
||||
@@ -57,6 +58,7 @@ export const MultiSignDocumentSigningView = ({
|
||||
}: MultiSignDocumentSigningViewProps) => {
|
||||
const { _ } = useLingui();
|
||||
const { toast } = useToast();
|
||||
const analytics = useAnalytics();
|
||||
|
||||
const { fullName, email, signature, setFullName, setSignature } = useRequiredDocumentSigningContext();
|
||||
|
||||
@@ -101,6 +103,13 @@ export const MultiSignDocumentSigningView = ({
|
||||
|
||||
console.error(err);
|
||||
|
||||
analytics.captureException(err, {
|
||||
source: 'embed',
|
||||
location: 'sign_field',
|
||||
recipientId,
|
||||
documentId: document?.id,
|
||||
});
|
||||
|
||||
toast({
|
||||
title: _(msg`Error`),
|
||||
description: _(msg`An error occurred while signing the document.`),
|
||||
@@ -120,6 +129,13 @@ export const MultiSignDocumentSigningView = ({
|
||||
}
|
||||
|
||||
console.error(err);
|
||||
|
||||
analytics.captureException(err, {
|
||||
source: 'embed',
|
||||
location: 'remove_field',
|
||||
recipientId,
|
||||
documentId: document?.id,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
@@ -140,6 +156,13 @@ export const MultiSignDocumentSigningView = ({
|
||||
recipientId,
|
||||
});
|
||||
} catch (err) {
|
||||
analytics.captureException(err, {
|
||||
source: 'embed',
|
||||
location: 'complete_document',
|
||||
recipientId,
|
||||
documentId: document?.id,
|
||||
});
|
||||
|
||||
onDocumentError?.();
|
||||
|
||||
const error = AppError.parseError(err);
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { useAnalytics } from '@documenso/lib/client-only/hooks/use-analytics';
|
||||
import { RECIPIENT_ROLES_DESCRIPTION } from '@documenso/lib/constants/recipient-roles';
|
||||
import { AppError } from '@documenso/lib/errors/app-error';
|
||||
import type { TTemplate } from '@documenso/lib/types/template';
|
||||
@@ -41,6 +42,7 @@ export const DirectTemplatePageView = ({
|
||||
|
||||
const { _ } = useLingui();
|
||||
const { toast } = useToast();
|
||||
const analytics = useAnalytics();
|
||||
|
||||
const { email, fullName, setEmail } = useRequiredDocumentSigningContext();
|
||||
const { recipient, setRecipient } = useRequiredDocumentSigningAuthContext();
|
||||
@@ -124,6 +126,13 @@ export const DirectTemplatePageView = ({
|
||||
const error = AppError.parseError(err);
|
||||
const errorMessage = getDirectTemplateErrorMessage(error.code);
|
||||
|
||||
analytics.captureException(err, {
|
||||
source: 'signing',
|
||||
location: 'direct_template',
|
||||
recipientId: directTemplateRecipient.id,
|
||||
envelopeId: template.envelopeId,
|
||||
});
|
||||
|
||||
toast({
|
||||
title: _(errorMessage.title),
|
||||
description: _(errorMessage.description),
|
||||
|
||||
+23
@@ -1,3 +1,4 @@
|
||||
import { useAnalytics } from '@documenso/lib/client-only/hooks/use-analytics';
|
||||
import { DO_NOT_INVALIDATE_QUERY_ON_MUTATION } from '@documenso/lib/constants/trpc';
|
||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
import type { TRecipientActionAuth } from '@documenso/lib/types/document-auth';
|
||||
@@ -39,6 +40,7 @@ export const DocumentSigningCheckboxField = ({
|
||||
const { _ } = useLingui();
|
||||
const { toast } = useToast();
|
||||
const { revalidate } = useRevalidator();
|
||||
const analytics = useAnalytics();
|
||||
|
||||
const { recipient, isAssistantMode } = useDocumentSigningRecipientContext();
|
||||
|
||||
@@ -126,6 +128,13 @@ export const DocumentSigningCheckboxField = ({
|
||||
|
||||
console.error(err);
|
||||
|
||||
analytics.captureException(err, {
|
||||
source: 'signing',
|
||||
location: 'sign_field',
|
||||
fieldType: field.type,
|
||||
recipientId: field.recipientId,
|
||||
});
|
||||
|
||||
toast({
|
||||
title: _(msg`Error`),
|
||||
description: isAssistantMode
|
||||
@@ -157,6 +166,13 @@ export const DocumentSigningCheckboxField = ({
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
|
||||
analytics.captureException(err, {
|
||||
source: 'signing',
|
||||
location: 'remove_field',
|
||||
fieldType: field.type,
|
||||
recipientId: field.recipientId,
|
||||
});
|
||||
|
||||
toast({
|
||||
title: _(msg`Error`),
|
||||
description: _(msg`An error occurred while removing the field.`),
|
||||
@@ -216,6 +232,13 @@ export const DocumentSigningCheckboxField = ({
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
|
||||
analytics.captureException(err, {
|
||||
source: 'signing',
|
||||
location: 'sign_field',
|
||||
fieldType: field.type,
|
||||
recipientId: field.recipientId,
|
||||
});
|
||||
|
||||
toast({
|
||||
title: _(msg`Error`),
|
||||
description: _(msg`An error occurred while updating the signature.`),
|
||||
|
||||
+7
@@ -1,3 +1,4 @@
|
||||
import { useAnalytics } from '@documenso/lib/client-only/hooks/use-analytics';
|
||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
import { type TRecipientAccessAuth, ZDocumentAccessAuthSchema } from '@documenso/lib/types/document-auth';
|
||||
import { fieldsContainUnsignedRequiredField } from '@documenso/lib/utils/advanced-fields-helpers';
|
||||
@@ -88,6 +89,7 @@ export const DocumentSigningCompleteDialog = ({
|
||||
position,
|
||||
disableNameInput = false,
|
||||
}: DocumentSigningCompleteDialogProps) => {
|
||||
const analytics = useAnalytics();
|
||||
const { t, i18n } = useLingui();
|
||||
const { toast } = useToast();
|
||||
|
||||
@@ -181,6 +183,11 @@ export const DocumentSigningCompleteDialog = ({
|
||||
return;
|
||||
}
|
||||
|
||||
analytics.captureException(error, {
|
||||
source: 'signing',
|
||||
location: 'complete_document',
|
||||
});
|
||||
|
||||
// This dialog owns the completion error toast for every signing surface
|
||||
// so the user gets a specific, actionable message. Callers should run
|
||||
// their own side effects (e.g. embeds posting document-error) and
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { useAnalytics } from '@documenso/lib/client-only/hooks/use-analytics';
|
||||
import { convertToLocalSystemFormat, DEFAULT_DOCUMENT_DATE_FORMAT } from '@documenso/lib/constants/date-formats';
|
||||
import { DEFAULT_DOCUMENT_TIME_ZONE } from '@documenso/lib/constants/time-zones';
|
||||
import { DO_NOT_INVALIDATE_QUERY_ON_MUTATION } from '@documenso/lib/constants/trpc';
|
||||
@@ -39,6 +40,7 @@ export const DocumentSigningDateField = ({
|
||||
const { _ } = useLingui();
|
||||
const { toast } = useToast();
|
||||
const { revalidate } = useRevalidator();
|
||||
const analytics = useAnalytics();
|
||||
|
||||
const { recipient, isAssistantMode } = useDocumentSigningRecipientContext();
|
||||
|
||||
@@ -85,6 +87,13 @@ export const DocumentSigningDateField = ({
|
||||
|
||||
console.error(err);
|
||||
|
||||
analytics.captureException(err, {
|
||||
source: 'signing',
|
||||
location: 'sign_field',
|
||||
fieldType: field.type,
|
||||
recipientId: field.recipientId,
|
||||
});
|
||||
|
||||
toast({
|
||||
title: _(msg`Error`),
|
||||
description: isAssistantMode
|
||||
@@ -113,6 +122,13 @@ export const DocumentSigningDateField = ({
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
|
||||
analytics.captureException(err, {
|
||||
source: 'signing',
|
||||
location: 'remove_field',
|
||||
fieldType: field.type,
|
||||
recipientId: field.recipientId,
|
||||
});
|
||||
|
||||
toast({
|
||||
title: _(msg`Error`),
|
||||
description: _(msg`An error occurred while removing the field.`),
|
||||
|
||||
+16
@@ -1,3 +1,4 @@
|
||||
import { useAnalytics } from '@documenso/lib/client-only/hooks/use-analytics';
|
||||
import { DO_NOT_INVALIDATE_QUERY_ON_MUTATION } from '@documenso/lib/constants/trpc';
|
||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
import type { TRecipientActionAuth } from '@documenso/lib/types/document-auth';
|
||||
@@ -35,6 +36,7 @@ export const DocumentSigningDropdownField = ({
|
||||
const { _ } = useLingui();
|
||||
const { toast } = useToast();
|
||||
const { revalidate } = useRevalidator();
|
||||
const analytics = useAnalytics();
|
||||
|
||||
const { recipient, isAssistantMode } = useDocumentSigningRecipientContext();
|
||||
|
||||
@@ -86,6 +88,13 @@ export const DocumentSigningDropdownField = ({
|
||||
|
||||
console.error(err);
|
||||
|
||||
analytics.captureException(err, {
|
||||
source: 'signing',
|
||||
location: 'sign_field',
|
||||
fieldType: field.type,
|
||||
recipientId: field.recipientId,
|
||||
});
|
||||
|
||||
toast({
|
||||
title: _(msg`Error`),
|
||||
description: isAssistantMode
|
||||
@@ -120,6 +129,13 @@ export const DocumentSigningDropdownField = ({
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
|
||||
analytics.captureException(err, {
|
||||
source: 'signing',
|
||||
location: 'remove_field',
|
||||
fieldType: field.type,
|
||||
recipientId: field.recipientId,
|
||||
});
|
||||
|
||||
toast({
|
||||
title: _(msg`Error`),
|
||||
description: _(msg`An error occurred while removing the field.`),
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { useAnalytics } from '@documenso/lib/client-only/hooks/use-analytics';
|
||||
import { DO_NOT_INVALIDATE_QUERY_ON_MUTATION } from '@documenso/lib/constants/trpc';
|
||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
import type { TRecipientActionAuth } from '@documenso/lib/types/document-auth';
|
||||
@@ -33,6 +34,7 @@ export const DocumentSigningEmailField = ({ field, onSignField, onUnsignField }:
|
||||
const { _ } = useLingui();
|
||||
const { toast } = useToast();
|
||||
const { revalidate } = useRevalidator();
|
||||
const analytics = useAnalytics();
|
||||
|
||||
const { email: providedEmail } = useRequiredDocumentSigningContext();
|
||||
|
||||
@@ -78,6 +80,13 @@ export const DocumentSigningEmailField = ({ field, onSignField, onUnsignField }:
|
||||
|
||||
console.error(err);
|
||||
|
||||
analytics.captureException(err, {
|
||||
source: 'signing',
|
||||
location: 'sign_field',
|
||||
fieldType: field.type,
|
||||
recipientId: field.recipientId,
|
||||
});
|
||||
|
||||
toast({
|
||||
title: _(msg`Error`),
|
||||
description: isAssistantMode
|
||||
@@ -106,6 +115,13 @@ export const DocumentSigningEmailField = ({ field, onSignField, onUnsignField }:
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
|
||||
analytics.captureException(err, {
|
||||
source: 'signing',
|
||||
location: 'remove_field',
|
||||
fieldType: field.type,
|
||||
recipientId: field.recipientId,
|
||||
});
|
||||
|
||||
toast({
|
||||
title: _(msg`Error`),
|
||||
description: _(msg`An error occurred while removing the field.`),
|
||||
|
||||
+16
@@ -1,3 +1,4 @@
|
||||
import { useAnalytics } from '@documenso/lib/client-only/hooks/use-analytics';
|
||||
import { DO_NOT_INVALIDATE_QUERY_ON_MUTATION } from '@documenso/lib/constants/trpc';
|
||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
import type { TRecipientActionAuth } from '@documenso/lib/types/document-auth';
|
||||
@@ -38,6 +39,7 @@ export const DocumentSigningInitialsField = ({
|
||||
const { toast } = useToast();
|
||||
const { _ } = useLingui();
|
||||
const { revalidate } = useRevalidator();
|
||||
const analytics = useAnalytics();
|
||||
|
||||
const { fullName } = useRequiredDocumentSigningContext();
|
||||
const { recipient, isAssistantMode } = useDocumentSigningRecipientContext();
|
||||
@@ -84,6 +86,13 @@ export const DocumentSigningInitialsField = ({
|
||||
|
||||
console.error(err);
|
||||
|
||||
analytics.captureException(err, {
|
||||
source: 'signing',
|
||||
location: 'sign_field',
|
||||
fieldType: field.type,
|
||||
recipientId: field.recipientId,
|
||||
});
|
||||
|
||||
toast({
|
||||
title: _(msg`Error`),
|
||||
description: isAssistantMode
|
||||
@@ -112,6 +121,13 @@ export const DocumentSigningInitialsField = ({
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
|
||||
analytics.captureException(err, {
|
||||
source: 'signing',
|
||||
location: 'remove_field',
|
||||
fieldType: field.type,
|
||||
recipientId: field.recipientId,
|
||||
});
|
||||
|
||||
toast({
|
||||
title: _(msg`Error`),
|
||||
description: _(msg`An error occurred while removing the field.`),
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { useAnalytics } from '@documenso/lib/client-only/hooks/use-analytics';
|
||||
import { DO_NOT_INVALIDATE_QUERY_ON_MUTATION } from '@documenso/lib/constants/trpc';
|
||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
import type { TRecipientActionAuth } from '@documenso/lib/types/document-auth';
|
||||
@@ -39,6 +40,7 @@ export const DocumentSigningNameField = ({ field, onSignField, onUnsignField }:
|
||||
const { _ } = useLingui();
|
||||
const { toast } = useToast();
|
||||
const { revalidate } = useRevalidator();
|
||||
const analytics = useAnalytics();
|
||||
|
||||
const { fullName: providedFullName, setFullName: setProvidedFullName } = useRequiredDocumentSigningContext();
|
||||
|
||||
@@ -116,6 +118,13 @@ export const DocumentSigningNameField = ({ field, onSignField, onUnsignField }:
|
||||
|
||||
console.error(err);
|
||||
|
||||
analytics.captureException(err, {
|
||||
source: 'signing',
|
||||
location: 'sign_field',
|
||||
fieldType: field.type,
|
||||
recipientId: field.recipientId,
|
||||
});
|
||||
|
||||
toast({
|
||||
title: _(msg`Error`),
|
||||
description: isAssistantMode
|
||||
@@ -144,6 +153,13 @@ export const DocumentSigningNameField = ({ field, onSignField, onUnsignField }:
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
|
||||
analytics.captureException(err, {
|
||||
source: 'signing',
|
||||
location: 'remove_field',
|
||||
fieldType: field.type,
|
||||
recipientId: field.recipientId,
|
||||
});
|
||||
|
||||
toast({
|
||||
title: _(msg`Error`),
|
||||
description: _(msg`An error occurred while removing the field.`),
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { validateNumberField } from '@documenso/lib/advanced-fields-validation/validate-number';
|
||||
import { useAnalytics } from '@documenso/lib/client-only/hooks/use-analytics';
|
||||
import { DO_NOT_INVALIDATE_QUERY_ON_MUTATION } from '@documenso/lib/constants/trpc';
|
||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
import type { TRecipientActionAuth } from '@documenso/lib/types/document-auth';
|
||||
@@ -47,6 +48,7 @@ export const DocumentSigningNumberField = ({ field, onSignField, onUnsignField }
|
||||
const { _ } = useLingui();
|
||||
const { toast } = useToast();
|
||||
const { revalidate } = useRevalidator();
|
||||
const analytics = useAnalytics();
|
||||
|
||||
const { recipient, isAssistantMode } = useDocumentSigningRecipientContext();
|
||||
|
||||
@@ -142,6 +144,13 @@ export const DocumentSigningNumberField = ({ field, onSignField, onUnsignField }
|
||||
|
||||
console.error(err);
|
||||
|
||||
analytics.captureException(err, {
|
||||
source: 'signing',
|
||||
location: 'sign_field',
|
||||
fieldType: field.type,
|
||||
recipientId: field.recipientId,
|
||||
});
|
||||
|
||||
toast({
|
||||
title: _(msg`Error`),
|
||||
description: isAssistantMode
|
||||
@@ -193,6 +202,13 @@ export const DocumentSigningNumberField = ({ field, onSignField, onUnsignField }
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
|
||||
analytics.captureException(err, {
|
||||
source: 'signing',
|
||||
location: 'remove_field',
|
||||
fieldType: field.type,
|
||||
recipientId: field.recipientId,
|
||||
});
|
||||
|
||||
toast({
|
||||
title: _(msg`Error`),
|
||||
description: _(msg`An error occurred while removing the field.`),
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { useAnalytics } from '@documenso/lib/client-only/hooks/use-analytics';
|
||||
import { DEFAULT_DOCUMENT_DATE_FORMAT } from '@documenso/lib/constants/date-formats';
|
||||
import { PDF_VIEWER_PAGE_SELECTOR } from '@documenso/lib/constants/pdf-viewer';
|
||||
import { DEFAULT_DOCUMENT_TIME_ZONE } from '@documenso/lib/constants/time-zones';
|
||||
@@ -90,8 +89,6 @@ export const DocumentSigningPageViewV1 = ({
|
||||
? authUser.twoFactorEnabled && authUser.email === recipient.email
|
||||
: false;
|
||||
|
||||
const analytics = useAnalytics();
|
||||
|
||||
const [selectedSignerId, setSelectedSignerId] = useState<number | null>(allRecipients?.[0]?.id);
|
||||
const [isExpanded, setIsExpanded] = useState(false);
|
||||
|
||||
@@ -125,12 +122,6 @@ export const DocumentSigningPageViewV1 = ({
|
||||
|
||||
await completeDocumentWithToken(payload);
|
||||
|
||||
analytics.capture('App: Recipient has completed signing', {
|
||||
signerId: recipient.id,
|
||||
documentId: document.id,
|
||||
timestamp: new Date().toISOString(),
|
||||
});
|
||||
|
||||
if (documentMeta?.redirectUrl) {
|
||||
window.location.href = documentMeta.redirectUrl;
|
||||
} else {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { useAnalytics } from '@documenso/lib/client-only/hooks/use-analytics';
|
||||
import { DO_NOT_INVALIDATE_QUERY_ON_MUTATION } from '@documenso/lib/constants/trpc';
|
||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
import type { TRecipientActionAuth } from '@documenso/lib/types/document-auth';
|
||||
@@ -31,6 +32,7 @@ export const DocumentSigningRadioField = ({ field, onSignField, onUnsignField }:
|
||||
const { _ } = useLingui();
|
||||
const { toast } = useToast();
|
||||
const { revalidate } = useRevalidator();
|
||||
const analytics = useAnalytics();
|
||||
|
||||
const { recipient, targetSigner, isAssistantMode } = useDocumentSigningRecipientContext();
|
||||
|
||||
@@ -91,6 +93,13 @@ export const DocumentSigningRadioField = ({ field, onSignField, onUnsignField }:
|
||||
|
||||
console.error(err);
|
||||
|
||||
analytics.captureException(err, {
|
||||
source: 'signing',
|
||||
location: 'sign_field',
|
||||
fieldType: field.type,
|
||||
recipientId: field.recipientId,
|
||||
});
|
||||
|
||||
toast({
|
||||
title: _(msg`Error`),
|
||||
description: isAssistantMode
|
||||
@@ -120,6 +129,13 @@ export const DocumentSigningRadioField = ({ field, onSignField, onUnsignField }:
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
|
||||
analytics.captureException(err, {
|
||||
source: 'signing',
|
||||
location: 'remove_field',
|
||||
fieldType: field.type,
|
||||
recipientId: field.recipientId,
|
||||
});
|
||||
|
||||
toast({
|
||||
title: _(msg`Error`),
|
||||
description: _(msg`An error occurred while removing the selection.`),
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { useAnalytics } from '@documenso/lib/client-only/hooks/use-analytics';
|
||||
import { trpc } from '@documenso/trpc/react';
|
||||
import { Button } from '@documenso/ui/primitives/button';
|
||||
import {
|
||||
@@ -41,6 +42,7 @@ export function DocumentSigningRejectDialog({
|
||||
}: DocumentSigningRejectDialogProps) {
|
||||
const { t } = useLingui();
|
||||
const { toast } = useToast();
|
||||
const analytics = useAnalytics();
|
||||
const [searchParams] = useSearchParams();
|
||||
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
@@ -76,6 +78,12 @@ export function DocumentSigningRejectDialog({
|
||||
window.location.href = `/sign/${token}/rejected`;
|
||||
}
|
||||
} catch (err) {
|
||||
analytics.captureException(err, {
|
||||
source: 'signing',
|
||||
location: 'reject_document',
|
||||
documentId,
|
||||
});
|
||||
|
||||
toast({
|
||||
title: t`Error`,
|
||||
description: t`An error occurred while rejecting the document. Please try again.`,
|
||||
|
||||
+16
@@ -1,3 +1,4 @@
|
||||
import { useAnalytics } from '@documenso/lib/client-only/hooks/use-analytics';
|
||||
import { DO_NOT_INVALIDATE_QUERY_ON_MUTATION } from '@documenso/lib/constants/trpc';
|
||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
import type { TRecipientActionAuth } from '@documenso/lib/types/document-auth';
|
||||
@@ -47,6 +48,7 @@ export const DocumentSigningSignatureField = ({
|
||||
const { _ } = useLingui();
|
||||
const { toast } = useToast();
|
||||
const { revalidate } = useRevalidator();
|
||||
const analytics = useAnalytics();
|
||||
|
||||
const { recipient } = useDocumentSigningRecipientContext();
|
||||
|
||||
@@ -157,6 +159,13 @@ export const DocumentSigningSignatureField = ({
|
||||
|
||||
console.error(err);
|
||||
|
||||
analytics.captureException(err, {
|
||||
source: 'signing',
|
||||
location: 'sign_field',
|
||||
fieldType: field.type,
|
||||
recipientId: field.recipientId,
|
||||
});
|
||||
|
||||
toast({
|
||||
title: _(msg`Error`),
|
||||
description: _(msg`An error occurred while signing the document.`),
|
||||
@@ -183,6 +192,13 @@ export const DocumentSigningSignatureField = ({
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
|
||||
analytics.captureException(err, {
|
||||
source: 'signing',
|
||||
location: 'remove_field',
|
||||
fieldType: field.type,
|
||||
recipientId: field.recipientId,
|
||||
});
|
||||
|
||||
toast({
|
||||
title: _(msg`Error`),
|
||||
description: _(msg`An error occurred while removing the signature.`),
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { validateTextField } from '@documenso/lib/advanced-fields-validation/validate-text';
|
||||
import { useAnalytics } from '@documenso/lib/client-only/hooks/use-analytics';
|
||||
import { DO_NOT_INVALIDATE_QUERY_ON_MUTATION } from '@documenso/lib/constants/trpc';
|
||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
import type { TRecipientActionAuth } from '@documenso/lib/types/document-auth';
|
||||
@@ -50,6 +51,7 @@ export const DocumentSigningTextField = ({ field, onSignField, onUnsignField }:
|
||||
const { _ } = useLingui();
|
||||
const { toast } = useToast();
|
||||
const { revalidate } = useRevalidator();
|
||||
const analytics = useAnalytics();
|
||||
|
||||
const { recipient, isAssistantMode } = useDocumentSigningRecipientContext();
|
||||
|
||||
@@ -170,6 +172,13 @@ export const DocumentSigningTextField = ({ field, onSignField, onUnsignField }:
|
||||
|
||||
console.error(err);
|
||||
|
||||
analytics.captureException(err, {
|
||||
source: 'signing',
|
||||
location: 'sign_field',
|
||||
fieldType: field.type,
|
||||
recipientId: field.recipientId,
|
||||
});
|
||||
|
||||
toast({
|
||||
title: _(msg`Error`),
|
||||
description: isAssistantMode
|
||||
@@ -200,6 +209,13 @@ export const DocumentSigningTextField = ({ field, onSignField, onUnsignField }:
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
|
||||
analytics.captureException(err, {
|
||||
source: 'signing',
|
||||
location: 'remove_field',
|
||||
fieldType: field.type,
|
||||
recipientId: field.recipientId,
|
||||
});
|
||||
|
||||
toast({
|
||||
title: _(msg`Error`),
|
||||
description: _(msg`An error occurred while removing the field.`),
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { useLimits } from '@documenso/ee/server-only/limits/provider/client';
|
||||
import { useAnalytics } from '@documenso/lib/client-only/hooks/use-analytics';
|
||||
import { useCurrentOrganisation } from '@documenso/lib/client-only/providers/organisation';
|
||||
import { useSession } from '@documenso/lib/client-only/providers/session';
|
||||
import { DEFAULT_DOCUMENT_TIME_ZONE, TIME_ZONES } from '@documenso/lib/constants/time-zones';
|
||||
@@ -38,7 +37,6 @@ export const DocumentUploadButtonLegacy = ({ className, type }: DocumentUploadBu
|
||||
const team = useCurrentTeam();
|
||||
|
||||
const navigate = useNavigate();
|
||||
const analytics = useAnalytics();
|
||||
const organisation = useCurrentOrganisation();
|
||||
|
||||
const userTimezone =
|
||||
@@ -103,12 +101,6 @@ export const DocumentUploadButtonLegacy = ({ className, type }: DocumentUploadBu
|
||||
description: _(msg`Your document has been uploaded successfully.`),
|
||||
duration: 5000,
|
||||
});
|
||||
|
||||
analytics.capture('App: Document Uploaded', {
|
||||
userId: user.id,
|
||||
documentId: id,
|
||||
timestamp: new Date().toISOString(),
|
||||
});
|
||||
}
|
||||
|
||||
// Handle legacy template creation.
|
||||
|
||||
+9
@@ -1,3 +1,4 @@
|
||||
import { useAnalytics } from '@documenso/lib/client-only/hooks/use-analytics';
|
||||
import { useDebouncedValue } from '@documenso/lib/client-only/hooks/use-debounced-value';
|
||||
import type { TLocalField } from '@documenso/lib/client-only/hooks/use-editor-fields';
|
||||
import { usePageRenderer } from '@documenso/lib/client-only/hooks/use-page-renderer';
|
||||
@@ -39,6 +40,7 @@ import { EnvelopeRecipientSelectorCommand } from './envelope-recipient-selector'
|
||||
|
||||
export const EnvelopeEditorFieldsPageRenderer = ({ pageData }: { pageData: PageRenderData }) => {
|
||||
const { t, i18n } = useLingui();
|
||||
const analytics = useAnalytics();
|
||||
const { envelope, editorFields, getRecipientColorKey } = useCurrentEnvelopeEditor();
|
||||
const { currentEnvelopeItem, setRenderError } = useCurrentEnvelopeRender();
|
||||
|
||||
@@ -276,6 +278,13 @@ export const EnvelopeEditorFieldsPageRenderer = ({ pageData }: { pageData: PageR
|
||||
unsafeRenderFieldOnLayer(field);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
|
||||
analytics.captureException(err, {
|
||||
source: 'editor',
|
||||
location: 'envelope_page_render',
|
||||
envelopeId: envelope.id,
|
||||
});
|
||||
|
||||
setRenderError(true);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { useLimits } from '@documenso/ee/server-only/limits/provider/client';
|
||||
import { useAnalytics } from '@documenso/lib/client-only/hooks/use-analytics';
|
||||
import { useEnvelopeAutosave } from '@documenso/lib/client-only/hooks/use-envelope-autosave';
|
||||
import { useCurrentEnvelopeEditor } from '@documenso/lib/client-only/providers/envelope-editor-provider';
|
||||
import { useCurrentOrganisation } from '@documenso/lib/client-only/providers/organisation';
|
||||
@@ -45,6 +46,7 @@ export const EnvelopeEditorUploadPage = () => {
|
||||
const { t, i18n } = useLingui();
|
||||
const { maximumEnvelopeItemCount, remaining } = useLimits();
|
||||
const { toast } = useToast();
|
||||
const analytics = useAnalytics();
|
||||
|
||||
const {
|
||||
envelope,
|
||||
@@ -213,6 +215,12 @@ export const EnvelopeEditorUploadPage = () => {
|
||||
const { data } = await createPromise.catch((error) => {
|
||||
console.error(error);
|
||||
|
||||
analytics.captureException(error, {
|
||||
source: isEmbedded ? 'embed' : 'editor',
|
||||
location: 'create_envelope_items',
|
||||
envelopeId: envelope.id,
|
||||
});
|
||||
|
||||
// Set error state on files in batch upload.
|
||||
setLocalFiles((prev) =>
|
||||
prev.map((uploadingFile) =>
|
||||
@@ -290,6 +298,12 @@ export const EnvelopeEditorUploadPage = () => {
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
|
||||
analytics.captureException(error, {
|
||||
source: isEmbedded ? 'embed' : 'editor',
|
||||
location: 'replace_pdf',
|
||||
envelopeId: envelope.id,
|
||||
});
|
||||
|
||||
toast({
|
||||
title: t`Replace failed`,
|
||||
description: t`Something went wrong while replacing the PDF`,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { useAnalytics } from '@documenso/lib/client-only/hooks/use-analytics';
|
||||
import { usePageRenderer } from '@documenso/lib/client-only/hooks/use-page-renderer';
|
||||
import {
|
||||
type PageRenderData,
|
||||
@@ -18,6 +19,7 @@ type GenericLocalField = TEnvelope['fields'][number] & {
|
||||
|
||||
export const EnvelopeGenericPageRenderer = ({ pageData }: { pageData: PageRenderData }) => {
|
||||
const { i18n } = useLingui();
|
||||
const analytics = useAnalytics();
|
||||
|
||||
const {
|
||||
envelopeStatus,
|
||||
@@ -114,6 +116,13 @@ export const EnvelopeGenericPageRenderer = ({ pageData }: { pageData: PageRender
|
||||
unsafeRenderFieldOnLayer(field);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
|
||||
analytics.captureException(err, {
|
||||
source: 'editor',
|
||||
location: 'envelope_page_render',
|
||||
envelopeId: currentEnvelopeItem?.envelopeId,
|
||||
});
|
||||
|
||||
setRenderError(true);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { useAnalytics } from '@documenso/lib/client-only/hooks/use-analytics';
|
||||
import { usePageRenderer } from '@documenso/lib/client-only/hooks/use-page-renderer';
|
||||
import {
|
||||
type PageRenderData,
|
||||
@@ -53,6 +54,7 @@ export const EnvelopeSignerPageRenderer = ({ pageData }: { pageData: PageRenderD
|
||||
|
||||
const { executeActionAuthProcedure } = useRequiredDocumentSigningAuthContext();
|
||||
const { toast } = useToast();
|
||||
const analytics = useAnalytics();
|
||||
|
||||
const {
|
||||
envelopeData,
|
||||
@@ -426,6 +428,14 @@ export const EnvelopeSignerPageRenderer = ({ pageData }: { pageData: PageRenderD
|
||||
unsafeRenderFieldOnLayer(unparsedField, fieldCanvasStyleCache);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
|
||||
analytics.captureException(err, {
|
||||
source: 'signing',
|
||||
location: 'page_render',
|
||||
recipientId: recipient.id,
|
||||
envelopeId: envelope.id,
|
||||
});
|
||||
|
||||
setRenderError(true);
|
||||
}
|
||||
};
|
||||
@@ -507,6 +517,14 @@ export const EnvelopeSignerPageRenderer = ({ pageData }: { pageData: PageRenderD
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
|
||||
analytics.captureException(err, {
|
||||
source: 'signing',
|
||||
location: 'sign_field',
|
||||
fieldType: payload.type,
|
||||
recipientId: recipient.id,
|
||||
envelopeId: envelope.id,
|
||||
});
|
||||
|
||||
toast({
|
||||
title: t`Error`,
|
||||
description: t`An error occurred while signing the field.`,
|
||||
|
||||
+14
-6
@@ -118,12 +118,6 @@ export const EnvelopeSignerCompleteDialog = () => {
|
||||
title: t`Document already signed`,
|
||||
description: t`This document was already signed and no further action was taken.`,
|
||||
});
|
||||
} else {
|
||||
analytics.capture('App: Recipient has completed signing', {
|
||||
signerId: recipient.id,
|
||||
documentId: envelope.id,
|
||||
timestamp: new Date().toISOString(),
|
||||
});
|
||||
}
|
||||
|
||||
if (onDocumentCompleted) {
|
||||
@@ -148,6 +142,13 @@ export const EnvelopeSignerCompleteDialog = () => {
|
||||
const error = AppError.parseError(err);
|
||||
|
||||
if (error.code !== AppErrorCode.TWO_FACTOR_AUTH_FAILED) {
|
||||
analytics.captureException(err, {
|
||||
source: 'signing',
|
||||
location: 'complete_document',
|
||||
recipientId: recipient.id,
|
||||
envelopeId: envelope.id,
|
||||
});
|
||||
|
||||
onDocumentError?.();
|
||||
}
|
||||
|
||||
@@ -221,6 +222,13 @@ export const EnvelopeSignerCompleteDialog = () => {
|
||||
} catch (err) {
|
||||
console.log('err', err);
|
||||
|
||||
analytics.captureException(err, {
|
||||
source: 'signing',
|
||||
location: 'complete_document_next_signer',
|
||||
recipientId: recipient.id,
|
||||
envelopeId: envelope.id,
|
||||
});
|
||||
|
||||
onDocumentError?.();
|
||||
|
||||
// Rethrow so DocumentSigningCompleteDialog can toast a specific
|
||||
|
||||
@@ -93,14 +93,6 @@ export const EnvelopeDropZoneWrapper = ({ children, type, className }: EnvelopeD
|
||||
duration: 5000,
|
||||
});
|
||||
|
||||
if (type === EnvelopeType.DOCUMENT) {
|
||||
analytics.capture('App: Document Uploaded', {
|
||||
userId: user.id,
|
||||
documentId: id,
|
||||
timestamp: new Date().toISOString(),
|
||||
});
|
||||
}
|
||||
|
||||
const pathPrefix = type === EnvelopeType.DOCUMENT ? formatDocumentsPath(team.url) : formatTemplatesPath(team.url);
|
||||
|
||||
const aiQueryParam = team.preferences.aiFeaturesEnabled ? '?ai=true' : '';
|
||||
@@ -109,6 +101,11 @@ export const EnvelopeDropZoneWrapper = ({ children, type, className }: EnvelopeD
|
||||
} catch (err) {
|
||||
const error = AppError.parseError(err);
|
||||
|
||||
analytics.captureException(err, {
|
||||
source: 'editor',
|
||||
location: 'upload_document',
|
||||
});
|
||||
|
||||
const errorMessage = getUploadErrorMessage(error.code);
|
||||
|
||||
toast({
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { useLimits } from '@documenso/ee/server-only/limits/provider/client';
|
||||
import { useAnalytics } from '@documenso/lib/client-only/hooks/use-analytics';
|
||||
import { useCurrentOrganisation } from '@documenso/lib/client-only/providers/organisation';
|
||||
import { useSession } from '@documenso/lib/client-only/providers/session';
|
||||
import { TIME_ZONES } from '@documenso/lib/constants/time-zones';
|
||||
@@ -34,6 +35,7 @@ export const EnvelopeUploadButton = ({ className, type, folderId }: EnvelopeUplo
|
||||
const { t, i18n } = useLingui();
|
||||
const { toast } = useToast();
|
||||
const { user } = useSession();
|
||||
const analytics = useAnalytics();
|
||||
|
||||
const team = useCurrentTeam();
|
||||
|
||||
@@ -112,6 +114,11 @@ export const EnvelopeUploadButton = ({ className, type, folderId }: EnvelopeUplo
|
||||
|
||||
console.error(err);
|
||||
|
||||
analytics.captureException(err, {
|
||||
source: 'editor',
|
||||
location: 'upload_document',
|
||||
});
|
||||
|
||||
const errorMessage = getUploadErrorMessage(error.code);
|
||||
|
||||
toast({
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { useAnalytics } from '@documenso/lib/client-only/hooks/use-analytics';
|
||||
import type { ImageLoadingState, PageRenderData } from '@documenso/lib/client-only/providers/envelope-render-provider';
|
||||
import { PDF_VIEWER_PAGE_CLASSNAME } from '@documenso/lib/constants/pdf-viewer';
|
||||
import { cn } from '@documenso/ui/lib/utils';
|
||||
@@ -68,6 +69,7 @@ export default function PDFViewer({
|
||||
}: PDFViewerProps) {
|
||||
const { t } = useLingui();
|
||||
const { toast } = useToast();
|
||||
const analytics = useAnalytics();
|
||||
|
||||
const $el = useRef<HTMLDivElement>(null);
|
||||
|
||||
@@ -150,6 +152,11 @@ export default function PDFViewer({
|
||||
console.error(err);
|
||||
setLoadingState('error');
|
||||
|
||||
analytics.captureException(err, {
|
||||
source: 'pdf_viewer',
|
||||
location: 'pdf_load',
|
||||
});
|
||||
|
||||
toast({
|
||||
title: t`Error`,
|
||||
description: t`An error occurred while loading the document.`,
|
||||
@@ -366,6 +373,8 @@ const PdfViewerPage = ({
|
||||
* Manages rendering a page from a pdf.
|
||||
*/
|
||||
const usePdfPageImage = ({ pageNumber, pdf, scale, scaledWidth, scaledHeight }: PdfViewerPageProps) => {
|
||||
const analytics = useAnalytics();
|
||||
|
||||
const [imageLoadingState, setImageLoadingState] = useState<ImageLoadingState>('loading');
|
||||
|
||||
const [imageUrl, setImageUrl] = useState('');
|
||||
@@ -457,6 +466,12 @@ const usePdfPageImage = ({ pageNumber, pdf, scale, scaledWidth, scaledHeight }:
|
||||
|
||||
if (!isCancelled) {
|
||||
console.error(err);
|
||||
|
||||
analytics.captureException(err, {
|
||||
source: 'pdf_viewer',
|
||||
location: 'pdf_page_render',
|
||||
});
|
||||
|
||||
setImageLoadingState('error');
|
||||
}
|
||||
} finally {
|
||||
|
||||
@@ -18,6 +18,23 @@ import './utils/polyfills/promise-with-resolvers';
|
||||
* the page early, leaving dead event handlers (broken dropdowns, native form
|
||||
* submits).
|
||||
*/
|
||||
/**
|
||||
* Signing and direct template URLs contain recipient tokens which must never
|
||||
* be sent to PostHog. Recipient context is attached explicitly via
|
||||
* `recipientId` where needed instead.
|
||||
*/
|
||||
const redactTokensFromUrl = (value: string) => {
|
||||
return value.replace(/(\/(?:sign|d|direct)\/)([^/?#]+)/g, '$1:token');
|
||||
};
|
||||
|
||||
const URL_EVENT_PROPERTIES = [
|
||||
'$current_url',
|
||||
'$pathname',
|
||||
'$referrer',
|
||||
'$initial_referrer',
|
||||
'$prev_pageview_pathname',
|
||||
] as const;
|
||||
|
||||
function initPosthog() {
|
||||
const postHogConfig = extractPostHogConfig();
|
||||
|
||||
@@ -25,7 +42,34 @@ function initPosthog() {
|
||||
void import('posthog-js').then(({ default: posthog }) => {
|
||||
posthog.init(postHogConfig.key, {
|
||||
api_host: postHogConfig.host,
|
||||
// Only create person profiles for identified (authenticated) users,
|
||||
// anonymous recipients on signing pages stay anonymous.
|
||||
person_profiles: 'identified_only',
|
||||
// Explicit events only, autocapture on signing pages blows up usage
|
||||
// without providing actionable data.
|
||||
autocapture: false,
|
||||
capture_pageview: true,
|
||||
capture_pageleave: false,
|
||||
capture_exceptions: true,
|
||||
before_send: (event) => {
|
||||
if (!event) {
|
||||
return null;
|
||||
}
|
||||
|
||||
for (const property of URL_EVENT_PROPERTIES) {
|
||||
const value = event.properties?.[property];
|
||||
|
||||
if (typeof value === 'string') {
|
||||
event.properties[property] = redactTokensFromUrl(value);
|
||||
}
|
||||
}
|
||||
|
||||
if (event.$set_once && typeof event.$set_once['$initial_current_url'] === 'string') {
|
||||
event.$set_once['$initial_current_url'] = redactTokensFromUrl(event.$set_once['$initial_current_url']);
|
||||
}
|
||||
|
||||
return event;
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { getOptionalSession } from '@documenso/auth/server/lib/utils/get-session';
|
||||
import { useAnalytics } from '@documenso/lib/client-only/hooks/use-analytics';
|
||||
import { SessionProvider } from '@documenso/lib/client-only/providers/session';
|
||||
import { getBasePath } from '@documenso/lib/constants/app';
|
||||
import { APP_I18N_OPTIONS, type SupportedLanguageCodes } from '@documenso/lib/constants/i18n';
|
||||
@@ -9,6 +10,7 @@ import { getOrganisationSession } from '@documenso/trpc/server/organisation-rout
|
||||
import { Toaster } from '@documenso/ui/primitives/toaster';
|
||||
import { TooltipProvider } from '@documenso/ui/primitives/tooltip';
|
||||
import { NuqsAdapter } from 'nuqs/adapters/react-router/v7';
|
||||
import { useEffect } from 'react';
|
||||
import {
|
||||
data,
|
||||
isRouteErrorResponse,
|
||||
@@ -200,11 +202,19 @@ export default function App() {
|
||||
}
|
||||
|
||||
export function ErrorBoundary({ error }: Route.ErrorBoundaryProps) {
|
||||
const analytics = useAnalytics();
|
||||
|
||||
const errorCode = isRouteErrorResponse(error) ? error.status : 500;
|
||||
|
||||
if (errorCode !== 404) {
|
||||
console.error('[RootErrorBoundary]', error);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (errorCode !== 404) {
|
||||
analytics.captureException(error, { source: 'app', location: 'root_boundary' });
|
||||
}
|
||||
}, [error]);
|
||||
|
||||
return <GenericErrorLayout errorCode={errorCode} />;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { getSession } from '@documenso/auth/server/lib/utils/get-session';
|
||||
import { useAnalytics } from '@documenso/lib/client-only/hooks/use-analytics';
|
||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
import { getEnvelopeById } from '@documenso/lib/server-only/envelope/get-envelope-by-id';
|
||||
import { getTeamByUrl } from '@documenso/lib/server-only/team/get-team';
|
||||
@@ -7,6 +8,7 @@ import { msg } from '@lingui/core/macro';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
import { EnvelopeType } from '@prisma/client';
|
||||
import { ChevronLeftIcon } from 'lucide-react';
|
||||
import { useEffect } from 'react';
|
||||
import type { ShouldRevalidateFunctionArgs } from 'react-router';
|
||||
import { isRouteErrorResponse, Link, Outlet, redirect } from 'react-router';
|
||||
|
||||
@@ -66,8 +68,18 @@ export default function DocumentsLayout() {
|
||||
}
|
||||
|
||||
export function ErrorBoundary({ error, params }: Route.ErrorBoundaryProps) {
|
||||
const analytics = useAnalytics();
|
||||
|
||||
const errorCode = isRouteErrorResponse(error) ? error.status : 500;
|
||||
|
||||
useEffect(() => {
|
||||
analytics.captureException(error, {
|
||||
source: 'editor',
|
||||
location: 'editor_layout_boundary',
|
||||
envelopeId: params.id,
|
||||
});
|
||||
}, [error]);
|
||||
|
||||
const errorCodeMap = {
|
||||
404: {
|
||||
subHeading: msg`404 Document not found`,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { getSession } from '@documenso/auth/server/lib/utils/get-session';
|
||||
import { useAnalytics } from '@documenso/lib/client-only/hooks/use-analytics';
|
||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
import { getEnvelopeById } from '@documenso/lib/server-only/envelope/get-envelope-by-id';
|
||||
import { getTeamByUrl } from '@documenso/lib/server-only/team/get-team';
|
||||
@@ -8,6 +9,7 @@ import { msg } from '@lingui/core/macro';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
import { EnvelopeType } from '@prisma/client';
|
||||
import { ChevronLeftIcon } from 'lucide-react';
|
||||
import { useEffect } from 'react';
|
||||
import { isRouteErrorResponse, Link, Outlet, redirect, type ShouldRevalidateFunctionArgs } from 'react-router';
|
||||
|
||||
import { GenericErrorLayout } from '~/components/general/generic-error-layout';
|
||||
@@ -84,8 +86,18 @@ export default function TemplatesLayout() {
|
||||
}
|
||||
|
||||
export function ErrorBoundary({ error, params }: Route.ErrorBoundaryProps) {
|
||||
const analytics = useAnalytics();
|
||||
|
||||
const errorCode = isRouteErrorResponse(error) ? error.status : 500;
|
||||
|
||||
useEffect(() => {
|
||||
analytics.captureException(error, {
|
||||
source: 'editor',
|
||||
location: 'editor_layout_boundary',
|
||||
envelopeId: params.id,
|
||||
});
|
||||
}, [error]);
|
||||
|
||||
const errorCodeMap = {
|
||||
404: {
|
||||
subHeading: msg`404 Template not found`,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { useAnalytics } from '@documenso/lib/client-only/hooks/use-analytics';
|
||||
import { useOptionalSession } from '@documenso/lib/client-only/providers/session';
|
||||
import { cn } from '@documenso/ui/lib/utils';
|
||||
import { Button } from '@documenso/ui/primitives/button';
|
||||
@@ -5,6 +6,7 @@ import { i18n } from '@lingui/core';
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
import { ChevronLeft } from 'lucide-react';
|
||||
import { useEffect } from 'react';
|
||||
import { isRouteErrorResponse, Link, Outlet } from 'react-router';
|
||||
import { Header as AuthenticatedHeader } from '~/components/general/app-header';
|
||||
import { GenericErrorLayout } from '~/components/general/generic-error-layout';
|
||||
@@ -48,8 +50,14 @@ export default function RecipientLayout({ matches }: Route.ComponentProps) {
|
||||
}
|
||||
|
||||
export function ErrorBoundary({ error }: Route.ErrorBoundaryProps) {
|
||||
const analytics = useAnalytics();
|
||||
|
||||
const errorCode = isRouteErrorResponse(error) ? error.status : 500;
|
||||
|
||||
useEffect(() => {
|
||||
analytics.captureException(error, { source: 'signing', location: 'recipient_layout_boundary' });
|
||||
}, [error]);
|
||||
|
||||
return (
|
||||
<GenericErrorLayout
|
||||
errorCode={errorCode}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { useAnalytics } from '@documenso/lib/client-only/hooks/use-analytics';
|
||||
import {
|
||||
IS_GOOGLE_SSO_ENABLED,
|
||||
IS_MICROSOFT_SSO_ENABLED,
|
||||
@@ -5,6 +6,7 @@ import {
|
||||
OIDC_PROVIDER_LABEL,
|
||||
} from '@documenso/lib/constants/auth';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
import { useEffect } from 'react';
|
||||
import { isRouteErrorResponse, Outlet, useRouteError } from 'react-router';
|
||||
|
||||
import { EmbedAuthenticationRequired } from '~/components/embed/embed-authentication-required';
|
||||
@@ -47,10 +49,30 @@ export default function Layout() {
|
||||
export function ErrorBoundary({ loaderData }: Route.ErrorBoundaryProps) {
|
||||
const { isGoogleSSOEnabled, isMicrosoftSSOEnabled, isOIDCSSOEnabled, oidcProviderLabel } = loaderData || {};
|
||||
|
||||
const analytics = useAnalytics();
|
||||
const error = useRouteError();
|
||||
|
||||
console.log({ routeError: error });
|
||||
|
||||
useEffect(() => {
|
||||
const isExpectedEmbedResponse =
|
||||
isRouteErrorResponse(error) &&
|
||||
[
|
||||
'embed-authentication-required',
|
||||
'embed-paywall',
|
||||
'embed-waiting-for-turn',
|
||||
'embed-recipient-expired',
|
||||
'embed-document-rejected',
|
||||
'embed-document-completed',
|
||||
].includes(error.data?.type);
|
||||
|
||||
if (isExpectedEmbedResponse) {
|
||||
return;
|
||||
}
|
||||
|
||||
analytics.captureException(error, { source: 'embed', location: 'embed_layout_boundary' });
|
||||
}, [error]);
|
||||
|
||||
if (isRouteErrorResponse(error)) {
|
||||
if (error.status === 401 && error.data.type === 'embed-authentication-required') {
|
||||
return (
|
||||
|
||||
@@ -2,11 +2,13 @@ import { getOptionalSession } from '@documenso/auth/server/lib/utils/get-session
|
||||
import { EnvelopeRenderProvider } from '@documenso/lib/client-only/providers/envelope-render-provider';
|
||||
import { IS_BILLING_ENABLED } from '@documenso/lib/constants/app';
|
||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
import { captureServerEvent } from '@documenso/lib/server-only/analytics/capture-server-event';
|
||||
import { getEnvelopeForDirectTemplateSigning } from '@documenso/lib/server-only/envelope/get-envelope-for-direct-template-signing';
|
||||
import { getEnvelopeRequiredAccessData } from '@documenso/lib/server-only/envelope/get-envelope-required-access-data';
|
||||
import { getOrganisationClaimByTeamId } from '@documenso/lib/server-only/organisation/get-organisation-claims';
|
||||
import { getTemplateByDirectLinkToken } from '@documenso/lib/server-only/template/get-template-by-direct-link-token';
|
||||
import { DocumentAccessAuth } from '@documenso/lib/types/document-auth';
|
||||
import { fireAndForget } from '@documenso/lib/universal/fire-and-forget';
|
||||
import { extractDocumentAuthMethods } from '@documenso/lib/utils/document-auth';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import { data } from 'react-router';
|
||||
@@ -93,6 +95,30 @@ async function handleV1Loader({ params, request }: Route.LoaderArgs) {
|
||||
|
||||
const fields = template.fields.filter((field) => field.recipientId === directTemplateRecipientId);
|
||||
|
||||
fireAndForget(async () => {
|
||||
const team = await prisma.team.findFirst({
|
||||
where: {
|
||||
id: template.teamId,
|
||||
},
|
||||
select: {
|
||||
organisationId: true,
|
||||
},
|
||||
});
|
||||
|
||||
captureServerEvent({
|
||||
event: 'App: Embed Session Started',
|
||||
userId: user?.id,
|
||||
organisationId: team?.organisationId,
|
||||
teamId: template.teamId,
|
||||
properties: {
|
||||
type: 'signing',
|
||||
version: 'v0',
|
||||
recipientId: recipient.id,
|
||||
envelopeId: template.envelopeId,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
return {
|
||||
token,
|
||||
user,
|
||||
@@ -194,6 +220,30 @@ async function handleV2Loader({ params, request }: Route.LoaderArgs) {
|
||||
);
|
||||
}
|
||||
|
||||
fireAndForget(async () => {
|
||||
const team = await prisma.team.findFirst({
|
||||
where: {
|
||||
id: envelope.teamId,
|
||||
},
|
||||
select: {
|
||||
organisationId: true,
|
||||
},
|
||||
});
|
||||
|
||||
captureServerEvent({
|
||||
event: 'App: Embed Session Started',
|
||||
userId: user?.id,
|
||||
organisationId: team?.organisationId,
|
||||
teamId: envelope.teamId,
|
||||
properties: {
|
||||
type: 'signing',
|
||||
version: 'v0',
|
||||
recipientId: recipient.id,
|
||||
envelopeId: envelope.id,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
return {
|
||||
token,
|
||||
user,
|
||||
|
||||
@@ -2,6 +2,7 @@ import { getOptionalSession } from '@documenso/auth/server/lib/utils/get-session
|
||||
import { EnvelopeRenderProvider } from '@documenso/lib/client-only/providers/envelope-render-provider';
|
||||
import { IS_BILLING_ENABLED } from '@documenso/lib/constants/app';
|
||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
import { captureServerEvent } from '@documenso/lib/server-only/analytics/capture-server-event';
|
||||
import { getDocumentAndSenderByToken } from '@documenso/lib/server-only/document/get-document-by-token';
|
||||
import { viewedDocument } from '@documenso/lib/server-only/document/viewed-document';
|
||||
import { getEnvelopeForRecipientSigning } from '@documenso/lib/server-only/envelope/get-envelope-for-recipient-signing';
|
||||
@@ -13,6 +14,7 @@ import { getIsRecipientsTurnToSign } from '@documenso/lib/server-only/recipient/
|
||||
import { getRecipientByToken } from '@documenso/lib/server-only/recipient/get-recipient-by-token';
|
||||
import { getRecipientsForAssistant } from '@documenso/lib/server-only/recipient/get-recipients-for-assistant';
|
||||
import { DocumentAccessAuth } from '@documenso/lib/types/document-auth';
|
||||
import { fireAndForget } from '@documenso/lib/universal/fire-and-forget';
|
||||
import { isDocumentCompleted } from '@documenso/lib/utils/document';
|
||||
import { extractDocumentAuthMethods } from '@documenso/lib/utils/document-auth';
|
||||
import { isRecipientExpired } from '@documenso/lib/utils/recipients';
|
||||
@@ -139,6 +141,30 @@ async function handleV1Loader({ params, request }: Route.LoaderArgs) {
|
||||
})
|
||||
: [];
|
||||
|
||||
fireAndForget(async () => {
|
||||
const team = await prisma.team.findFirst({
|
||||
where: {
|
||||
id: document.teamId,
|
||||
},
|
||||
select: {
|
||||
organisationId: true,
|
||||
},
|
||||
});
|
||||
|
||||
captureServerEvent({
|
||||
event: 'App: Embed Session Started',
|
||||
userId: user?.id,
|
||||
organisationId: team?.organisationId,
|
||||
teamId: document.teamId,
|
||||
properties: {
|
||||
type: 'signing',
|
||||
version: 'v0',
|
||||
recipientId: recipient.id,
|
||||
envelopeId: document.envelopeId,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
return {
|
||||
token,
|
||||
user,
|
||||
@@ -272,6 +298,30 @@ async function handleV2Loader({ params, request }: Route.LoaderArgs) {
|
||||
recipientAccessAuth: derivedRecipientAccessAuth,
|
||||
}).catch(() => null);
|
||||
|
||||
fireAndForget(async () => {
|
||||
const team = await prisma.team.findFirst({
|
||||
where: {
|
||||
id: envelope.teamId,
|
||||
},
|
||||
select: {
|
||||
organisationId: true,
|
||||
},
|
||||
});
|
||||
|
||||
captureServerEvent({
|
||||
event: 'App: Embed Session Started',
|
||||
userId: user?.id,
|
||||
organisationId: team?.organisationId,
|
||||
teamId: envelope.teamId,
|
||||
properties: {
|
||||
type: 'signing',
|
||||
version: 'v0',
|
||||
recipientId: recipient.id,
|
||||
envelopeId: envelope.id,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
return {
|
||||
token,
|
||||
user,
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
import { APP_I18N_OPTIONS } from '@documenso/lib/constants/i18n';
|
||||
import { captureServerEvent } from '@documenso/lib/server-only/analytics/capture-server-event';
|
||||
import { verifyEmbeddingPresignToken } from '@documenso/lib/server-only/embedding-presign/verify-embedding-presign-token';
|
||||
import { getOrganisationClaimByTeamId } from '@documenso/lib/server-only/organisation/get-organisation-claims';
|
||||
import { ZBaseEmbedAuthoringSchema } from '@documenso/lib/types/embed-authoring-base-schema';
|
||||
import { fireAndForget } from '@documenso/lib/universal/fire-and-forget';
|
||||
import { dynamicActivate } from '@documenso/lib/utils/i18n';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import { TrpcProvider } from '@documenso/trpc/react';
|
||||
import { Spinner } from '@documenso/ui/primitives/spinner';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
@@ -35,6 +38,40 @@ export const loader = async ({ request }: Route.LoaderArgs) => {
|
||||
});
|
||||
|
||||
allowEmbedAuthoringWhiteLabel = organisationClaim.flags.embedAuthoringWhiteLabel ?? false;
|
||||
|
||||
// Derive the kind of authoring session from the child route path, e.g.
|
||||
// /embed/v1/authoring/document/create -> resource 'document', mode 'create'.
|
||||
// Completed and error pages are not new sessions and are skipped.
|
||||
const [resource, mode] = url.pathname.split('/').filter(Boolean).slice(3);
|
||||
|
||||
const isAuthoringSession =
|
||||
(resource === 'document' || resource === 'template') && (mode === 'create' || mode === 'edit');
|
||||
|
||||
if (isAuthoringSession) {
|
||||
fireAndForget(async () => {
|
||||
const team = await prisma.team.findFirst({
|
||||
where: {
|
||||
id: result.teamId,
|
||||
},
|
||||
select: {
|
||||
organisationId: true,
|
||||
},
|
||||
});
|
||||
|
||||
captureServerEvent({
|
||||
event: 'App: Embed Session Started',
|
||||
userId: result.userId,
|
||||
organisationId: team?.organisationId,
|
||||
teamId: result.teamId,
|
||||
properties: {
|
||||
type: 'authoring',
|
||||
version: 'v1',
|
||||
resource,
|
||||
mode,
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
import { getOptionalSession } from '@documenso/auth/server/lib/utils/get-session';
|
||||
import { captureServerEvent } from '@documenso/lib/server-only/analytics/capture-server-event';
|
||||
import { getDocumentAndSenderByToken } from '@documenso/lib/server-only/document/get-document-by-token';
|
||||
import { getOrganisationClaimByTeamId } from '@documenso/lib/server-only/organisation/get-organisation-claims';
|
||||
import { getRecipientByToken } from '@documenso/lib/server-only/recipient/get-recipient-by-token';
|
||||
import { ZSignDocumentEmbedDataSchema } from '@documenso/lib/types/embed-document-sign-schema';
|
||||
import { fireAndForget } from '@documenso/lib/universal/fire-and-forget';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
import { SigningStatus } from '@prisma/client';
|
||||
import { useEffect, useLayoutEffect, useState } from 'react';
|
||||
@@ -42,6 +45,15 @@ export async function loader({ request }: Route.LoaderArgs) {
|
||||
const firstDocument = envelopes[0]?.document;
|
||||
|
||||
if (!firstDocument) {
|
||||
captureServerEvent({
|
||||
event: 'App: Embed Session Started',
|
||||
userId: user?.id,
|
||||
properties: {
|
||||
type: 'signing',
|
||||
version: 'v1',
|
||||
},
|
||||
});
|
||||
|
||||
return superLoaderJson({
|
||||
envelopes,
|
||||
user,
|
||||
@@ -55,6 +67,29 @@ export async function loader({ request }: Route.LoaderArgs) {
|
||||
const allowWhitelabelling = organisationClaim.flags.embedSigningWhiteLabel;
|
||||
const hidePoweredBy = organisationClaim.flags.hidePoweredBy;
|
||||
|
||||
fireAndForget(async () => {
|
||||
const team = await prisma.team.findFirst({
|
||||
where: {
|
||||
id: firstDocument.teamId,
|
||||
},
|
||||
select: {
|
||||
organisationId: true,
|
||||
},
|
||||
});
|
||||
|
||||
captureServerEvent({
|
||||
event: 'App: Embed Session Started',
|
||||
userId: user?.id,
|
||||
organisationId: team?.organisationId,
|
||||
teamId: firstDocument.teamId,
|
||||
properties: {
|
||||
type: 'signing',
|
||||
version: 'v1',
|
||||
envelopeId: firstDocument.envelopeId,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
return superLoaderJson({
|
||||
envelopes,
|
||||
user,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { PAID_PLAN_LIMITS } from '@documenso/ee/server-only/limits/constants';
|
||||
import { LimitsProvider } from '@documenso/ee/server-only/limits/provider/client';
|
||||
import { useAnalytics } from '@documenso/lib/client-only/hooks/use-analytics';
|
||||
import { OrganisationProvider } from '@documenso/lib/client-only/providers/organisation';
|
||||
import { APP_I18N_OPTIONS } from '@documenso/lib/constants/i18n';
|
||||
import { verifyEmbeddingPresignToken } from '@documenso/lib/server-only/embedding-presign/verify-embedding-presign-token';
|
||||
@@ -12,7 +13,7 @@ import type { OrganisationSession } from '@documenso/trpc/server/organisation-ro
|
||||
import { Spinner } from '@documenso/ui/primitives/spinner';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
import { OrganisationMemberRole, OrganisationType, TeamMemberRole } from '@prisma/client';
|
||||
import { useLayoutEffect, useState } from 'react';
|
||||
import { useEffect, useLayoutEffect, useState } from 'react';
|
||||
import { isRouteErrorResponse, Outlet, useLoaderData } from 'react-router';
|
||||
import { match } from 'ts-pattern';
|
||||
|
||||
@@ -166,8 +167,17 @@ export default function AuthoringLayout() {
|
||||
}
|
||||
|
||||
export function ErrorBoundary({ error }: Route.ErrorBoundaryProps) {
|
||||
const analytics = useAnalytics();
|
||||
|
||||
const errorCode = isRouteErrorResponse(error) ? error.status : 500;
|
||||
|
||||
useEffect(() => {
|
||||
analytics.captureException(error, {
|
||||
source: 'embed',
|
||||
location: 'embed_authoring_boundary',
|
||||
});
|
||||
}, [error]);
|
||||
|
||||
return (
|
||||
<div>
|
||||
{match(errorCode)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { EnvelopeEditorProvider } from '@documenso/lib/client-only/providers/envelope-editor-provider';
|
||||
import type { SupportedLanguageCodes } from '@documenso/lib/constants/i18n';
|
||||
import { captureServerEvent } from '@documenso/lib/server-only/analytics/capture-server-event';
|
||||
import { verifyEmbeddingPresignToken } from '@documenso/lib/server-only/embedding-presign/verify-embedding-presign-token';
|
||||
import { getTeamSettings } from '@documenso/lib/server-only/team/get-team-settings';
|
||||
import { ZDefaultRecipientsSchema } from '@documenso/lib/types/default-recipients';
|
||||
@@ -10,6 +11,7 @@ import {
|
||||
ZEmbedCreateEnvelopeAuthoringSchema,
|
||||
} from '@documenso/lib/types/envelope-editor';
|
||||
import type { TEnvelopeFieldAndMeta } from '@documenso/lib/types/field-meta';
|
||||
import { fireAndForget } from '@documenso/lib/universal/fire-and-forget';
|
||||
import { extractDerivedDocumentMeta } from '@documenso/lib/utils/document';
|
||||
import { buildEmbeddedEditorOptions, buildEmbeddedFeatures } from '@documenso/lib/utils/embed-config';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
@@ -76,6 +78,30 @@ export const loader = async ({ request }: Route.LoaderArgs) => {
|
||||
},
|
||||
});
|
||||
|
||||
fireAndForget(async () => {
|
||||
const team = result.teamId
|
||||
? await prisma.team.findFirst({
|
||||
where: {
|
||||
id: result.teamId,
|
||||
},
|
||||
select: {
|
||||
organisationId: true,
|
||||
},
|
||||
})
|
||||
: null;
|
||||
|
||||
captureServerEvent({
|
||||
event: 'App: Embed Session Started',
|
||||
userId: result.userId,
|
||||
organisationId: team?.organisationId,
|
||||
teamId: result.teamId ?? undefined,
|
||||
properties: {
|
||||
type: 'authoring',
|
||||
version: 'v2',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
return superLoaderJson({
|
||||
token,
|
||||
tokenUserId: result.userId,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { EnvelopeEditorProvider } from '@documenso/lib/client-only/providers/envelope-editor-provider';
|
||||
import type { SupportedLanguageCodes } from '@documenso/lib/constants/i18n';
|
||||
import { captureServerEvent } from '@documenso/lib/server-only/analytics/capture-server-event';
|
||||
import { verifyEmbeddingPresignToken } from '@documenso/lib/server-only/embedding-presign/verify-embedding-presign-token';
|
||||
import { getEditorEnvelopeById } from '@documenso/lib/server-only/envelope/get-editor-envelope-by-id';
|
||||
import { getTeamSettings } from '@documenso/lib/server-only/team/get-team-settings';
|
||||
@@ -94,6 +95,18 @@ export const loader = async ({ request, params }: Route.LoaderArgs) => {
|
||||
brandingLogo = settings.brandingLogo;
|
||||
}
|
||||
|
||||
captureServerEvent({
|
||||
event: 'App: Embed Session Started',
|
||||
userId: result.userId,
|
||||
organisationId: envelope.team.organisationId,
|
||||
teamId: result.teamId ?? undefined,
|
||||
properties: {
|
||||
type: 'authoring',
|
||||
version: 'v2',
|
||||
envelopeId: envelope.id,
|
||||
},
|
||||
});
|
||||
|
||||
return superLoaderJson({
|
||||
token,
|
||||
envelope,
|
||||
|
||||
@@ -31,18 +31,21 @@ export function useAnalytics() {
|
||||
};
|
||||
|
||||
/**
|
||||
* Capture an analytic event.
|
||||
* Capture an exception event.
|
||||
*
|
||||
* @param error The error to capture.
|
||||
* @param properties Properties to attach to the event.
|
||||
* @param properties Properties to attach to the event, such as `source`, `location`,
|
||||
* `recipientId` or `envelopeId`. Never attach recipient tokens.
|
||||
*/
|
||||
const captureException = (error: Error, properties?: Record<string, unknown>) => {
|
||||
const captureException = (error: unknown, properties?: Record<string, unknown>) => {
|
||||
if (!isPostHogEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
const errorToCapture = error instanceof Error ? error : new Error(String(error));
|
||||
|
||||
void getPosthog().then(({ default: posthog }) => {
|
||||
posthog.captureException(error, properties);
|
||||
posthog.captureException(errorToCapture, properties);
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ import { useSearchParams } from 'react-router';
|
||||
|
||||
import type { TDocumentEmailSettings } from '../../types/document-email';
|
||||
import { formatDocumentsPath, formatTemplatesPath } from '../../utils/teams';
|
||||
import { useAnalytics } from '../hooks/use-analytics';
|
||||
import type { TLocalField } from '../hooks/use-editor-fields';
|
||||
import { useEditorFields } from '../hooks/use-editor-fields';
|
||||
import { useEditorRecipients } from '../hooks/use-editor-recipients';
|
||||
@@ -115,6 +116,7 @@ export const EnvelopeEditorProvider = ({
|
||||
}: EnvelopeEditorProviderProps) => {
|
||||
const { t } = useLingui();
|
||||
const { toast } = useToast();
|
||||
const analytics = useAnalytics();
|
||||
|
||||
const [_searchParams, setSearchParams] = useSearchParams();
|
||||
|
||||
@@ -289,6 +291,12 @@ export const EnvelopeEditorProvider = ({
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
|
||||
analytics.captureException(err, {
|
||||
source: isEmbedded ? 'embed' : 'editor',
|
||||
location: 'autosave_recipients',
|
||||
envelopeId: envelope.id,
|
||||
});
|
||||
|
||||
setAutosaveError(true);
|
||||
|
||||
toast({
|
||||
@@ -352,6 +360,12 @@ export const EnvelopeEditorProvider = ({
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
|
||||
analytics.captureException(err, {
|
||||
source: isEmbedded ? 'embed' : 'editor',
|
||||
location: 'autosave_fields',
|
||||
envelopeId: envelope.id,
|
||||
});
|
||||
|
||||
setAutosaveError(true);
|
||||
|
||||
toast({
|
||||
@@ -407,6 +421,12 @@ export const EnvelopeEditorProvider = ({
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
|
||||
analytics.captureException(err, {
|
||||
source: isEmbedded ? 'embed' : 'editor',
|
||||
location: 'autosave_meta',
|
||||
envelopeId: envelope.id,
|
||||
});
|
||||
|
||||
setAutosaveError(true);
|
||||
|
||||
toast({
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
import crypto from 'node:crypto';
|
||||
import { PostHog } from 'posthog-node';
|
||||
|
||||
import { env } from '../../utils/env';
|
||||
|
||||
/**
|
||||
* Server-side product analytics client.
|
||||
*
|
||||
* Uses the same PostHog project as the client-side integration
|
||||
* (NEXT_PUBLIC_POSTHOG_KEY). Not to be confused with the self-hoster
|
||||
* telemetry client which reports to a separate project.
|
||||
*
|
||||
* Events are sent directly to PostHog rather than through the `/ingest`
|
||||
* reverse proxy used by the browser.
|
||||
*/
|
||||
const POSTHOG_HOST = 'https://eu.i.posthog.com';
|
||||
|
||||
let client: PostHog | null | undefined;
|
||||
|
||||
const getAnalyticsClient = () => {
|
||||
if (client !== undefined) {
|
||||
return client;
|
||||
}
|
||||
|
||||
const apiKey = env('NEXT_PUBLIC_POSTHOG_KEY');
|
||||
|
||||
// Low volume events, flush immediately so nothing is lost on shutdown.
|
||||
client = apiKey ? new PostHog(apiKey, { host: POSTHOG_HOST, flushAt: 1 }) : null;
|
||||
|
||||
return client;
|
||||
};
|
||||
|
||||
type CaptureServerEventOptions = {
|
||||
event: string;
|
||||
|
||||
/**
|
||||
* The authenticated user this event belongs to, if any.
|
||||
*
|
||||
* Used as the distinct ID so server events line up with client-side
|
||||
* identified users. Anonymous events (e.g. embed signer sessions) get a
|
||||
* random distinct ID instead.
|
||||
*/
|
||||
userId?: number;
|
||||
organisationId?: string;
|
||||
teamId?: number;
|
||||
properties?: Record<string, unknown>;
|
||||
};
|
||||
|
||||
/**
|
||||
* Capture a product analytics event from the server.
|
||||
*
|
||||
* No-ops when NEXT_PUBLIC_POSTHOG_KEY is not configured. Events are captured
|
||||
* as anonymous events (no person profile processing) to keep usage costs low,
|
||||
* cross-referencing is done via the `userId`, `organisationId` and `teamId`
|
||||
* event properties.
|
||||
*
|
||||
* Never pass recipient tokens or document contents as properties.
|
||||
*/
|
||||
export const captureServerEvent = ({
|
||||
event,
|
||||
userId,
|
||||
organisationId,
|
||||
teamId,
|
||||
properties = {},
|
||||
}: CaptureServerEventOptions) => {
|
||||
const posthog = getAnalyticsClient();
|
||||
|
||||
if (!posthog) {
|
||||
return;
|
||||
}
|
||||
|
||||
posthog.capture({
|
||||
distinctId: userId ? String(userId) : `anon_${crypto.randomUUID()}`,
|
||||
event,
|
||||
properties: {
|
||||
...properties,
|
||||
userId,
|
||||
organisationId,
|
||||
teamId,
|
||||
$process_person_profile: false,
|
||||
},
|
||||
});
|
||||
};
|
||||
@@ -8,7 +8,7 @@ msgstr ""
|
||||
"Language: pl\n"
|
||||
"Project-Id-Version: documenso-app\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"PO-Revision-Date: 2026-08-13 14:22\n"
|
||||
"PO-Revision-Date: 2026-08-27 12:58\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Polish\n"
|
||||
"Plural-Forms: nplurals=4; plural=(n==1 ? 0 : (n%10>=2 && n%10<=4) && (n%100<12 || n%100>14) ? 1 : n!=1 && (n%10>=0 && n%10<=1) || (n%10>=5 && n%10<=9) || (n%100>=12 && n%100<=14) ? 2 : 3);\n"
|
||||
@@ -316,7 +316,7 @@ msgstr "Sprawdź i {recipientActionVerb} dokument „{1}” utworzony przez zesp
|
||||
#: apps/remix/app/components/general/settings-upsell/branding-upsell.tsx
|
||||
#: apps/remix/app/components/general/settings-upsell/email-domains-upsell.tsx
|
||||
msgid "{0} has invited you to sign this document."
|
||||
msgstr ""
|
||||
msgstr "Organizacja {0} zaprosiła Cię do podpisania dokumentu."
|
||||
|
||||
#. placeholder {0}: team.name
|
||||
#: packages/lib/jobs/definitions/emails/send-signing-email.handler.ts
|
||||
@@ -442,7 +442,7 @@ msgstr "Sprawdź i {action} dokument „{documentName}” utworzony przez użytk
|
||||
|
||||
#: apps/remix/app/components/dialogs/envelopes-bulk-download-dialog.tsx
|
||||
msgid "{MAX_BULK_DOWNLOAD_ENVELOPES, plural, one {You can download up to # document at a time. Deselect some documents to continue.} other {You can download up to # documents at a time. Deselect some documents to continue.}}"
|
||||
msgstr ""
|
||||
msgstr "{MAX_BULK_DOWNLOAD_ENVELOPES, plural, one {Możesz pobrać jednocześnie # dokument. Odznacz niektóre dokumenty.} few {Możesz pobrać jednocześnie # dokumenty. Odznacz niektóre dokumenty.} many {Możesz pobrać jednocześnie # dokumentów. Odznacz niektóre dokumenty.} other {Możesz pobrać jednocześnie # dokumentów. Odznacz niektóre dokumenty.}}"
|
||||
|
||||
#: apps/remix/app/components/dialogs/passkey-create-dialog.tsx
|
||||
msgid "{MAXIMUM_PASSKEYS, plural, one {You cannot have more than # passkey.} other {You cannot have more than # passkeys.}}"
|
||||
@@ -525,11 +525,11 @@ msgstr "Sprawdź i {action} dokument „{documentName}” utworzony przez zespó
|
||||
|
||||
#: apps/remix/app/components/general/app-command-menu.tsx
|
||||
msgid "{totalVisibleCount, plural, one {# item} other {# items}}"
|
||||
msgstr ""
|
||||
msgstr "{totalVisibleCount, plural, one {# element} few {# elementy} many {# elementów} other {# elementów}}"
|
||||
|
||||
#: apps/remix/app/components/general/app-command-menu.tsx
|
||||
msgid "{totalVisibleCount, plural, one {# result} other {# results}}"
|
||||
msgstr ""
|
||||
msgstr "{totalVisibleCount, plural, one {# wynik} few {# wyniki} many {# wyników} other {# wyników}}"
|
||||
|
||||
#: packages/lib/utils/document-audit-logs.ts
|
||||
msgid "{user} added a field"
|
||||
@@ -1237,7 +1237,7 @@ msgstr "Konto zostało rozłączone"
|
||||
|
||||
#: apps/remix/app/components/general/settings-upsell/sso-portal-upsell.tsx
|
||||
msgid "Accounts are automatically added to your organisation on sign-in"
|
||||
msgstr ""
|
||||
msgstr "Automatyczne dodawanie kont do organizacji podczas logowania"
|
||||
|
||||
#: apps/remix/app/routes/_unauthenticated+/articles.signature-disclosure.tsx
|
||||
msgid "Acknowledgment"
|
||||
@@ -2455,7 +2455,7 @@ msgstr "Obramowanie"
|
||||
|
||||
#: apps/remix/app/components/dialogs/branding-preferences-reset-dialog.tsx
|
||||
msgid "Border radius"
|
||||
msgstr "Zaokrąglenia obramowania"
|
||||
msgstr "Zaokrąglenie obramowania"
|
||||
|
||||
#: apps/remix/app/components/forms/branding-preferences-form.tsx
|
||||
msgid "Border Radius"
|
||||
@@ -2471,7 +2471,7 @@ msgstr "Dół"
|
||||
|
||||
#: apps/remix/app/components/general/settings-upsell/branding-upsell.tsx
|
||||
msgid "Brand accent"
|
||||
msgstr ""
|
||||
msgstr "Kolor marki"
|
||||
|
||||
#: apps/remix/app/components/forms/branding-preferences-form.tsx
|
||||
msgid "Brand Colours"
|
||||
@@ -2479,7 +2479,7 @@ msgstr "Kolory marki"
|
||||
|
||||
#: apps/remix/app/components/dialogs/branding-preferences-reset-dialog.tsx
|
||||
msgid "Brand colours, including background, foreground, primary, and border colours"
|
||||
msgstr "Kolorów marki, w tym kolorów tła, przycisków i obramowań"
|
||||
msgstr "Kolory marki, w tym kolory tła, przycisków i obramowań"
|
||||
|
||||
#: apps/remix/app/components/forms/branding-preferences-form.tsx
|
||||
msgid "Brand Details"
|
||||
@@ -2491,7 +2491,7 @@ msgstr "Strona internetowa marki"
|
||||
|
||||
#: apps/remix/app/components/dialogs/branding-preferences-reset-dialog.tsx
|
||||
msgid "Brand website and brand details"
|
||||
msgstr "Strony internetowej i szczegółów marki"
|
||||
msgstr "Strona internetowa i szczegóły marki"
|
||||
|
||||
#: apps/remix/app/components/general/admin-global-settings-section.tsx
|
||||
#: packages/lib/utils/settings-nav.ts
|
||||
@@ -2581,7 +2581,7 @@ msgstr "Akceptując prośbę, umożliwisz zespołowi <0>{teamName}</0>:"
|
||||
|
||||
#: apps/remix/app/components/dialogs/envelope-delete-dialog.tsx
|
||||
msgid "By deleting this document, the following will occur:"
|
||||
msgstr "Usunięcie dokumentu spowoduje następujące skutki:"
|
||||
msgstr "Po usunięciu dokumentu:"
|
||||
|
||||
#: apps/remix/app/components/general/document-signing/document-signing-auth-2fa.tsx
|
||||
msgid "By enabling 2FA, you will be required to enter a code from your authenticator app every time you sign in using email password."
|
||||
@@ -3008,11 +3008,11 @@ msgstr "Komunikacja"
|
||||
|
||||
#: apps/remix/app/components/general/settings-upsell/branding-upsell.tsx
|
||||
msgid "Company details"
|
||||
msgstr ""
|
||||
msgstr "Szczegóły firmy"
|
||||
|
||||
#: apps/remix/app/components/general/settings-upsell/branding-upsell.tsx
|
||||
msgid "Company details and website in email footers"
|
||||
msgstr ""
|
||||
msgstr "Szczegóły firmy i strona internetowa w stopkach"
|
||||
|
||||
#: apps/remix/app/components/dialogs/organisation-create-dialog.tsx
|
||||
msgid "Compare all plans and features in detail"
|
||||
@@ -3203,7 +3203,7 @@ msgstr "Informacje kontaktowe"
|
||||
#: apps/remix/app/components/general/settings-upsell/email-domains-upsell.tsx
|
||||
#: apps/remix/app/components/general/settings-upsell/sso-portal-upsell.tsx
|
||||
msgid "Contact Sales"
|
||||
msgstr ""
|
||||
msgstr "Skontaktuj się z działem sprzedaży"
|
||||
|
||||
#: apps/remix/app/components/dialogs/organisation-create-dialog.tsx
|
||||
msgid "Contact sales here"
|
||||
@@ -3215,7 +3215,7 @@ msgstr "Skontaktuj się z nami"
|
||||
|
||||
#: apps/remix/app/components/general/settings-upsell/settings-upsell-card.tsx
|
||||
msgid "Contact your organisation owner to upgrade plans."
|
||||
msgstr ""
|
||||
msgstr "Skontaktuj się z właścicielem organizacji, aby zaktualizować plan."
|
||||
|
||||
#: apps/remix/app/components/general/admin-site-banner-section.tsx
|
||||
msgid "Content"
|
||||
@@ -3271,7 +3271,7 @@ msgstr "Przejdź do logowania"
|
||||
|
||||
#: apps/remix/app/components/general/settings-upsell/sso-portal-upsell.tsx
|
||||
msgid "Continue with SSO"
|
||||
msgstr ""
|
||||
msgstr "Logowanie SSO"
|
||||
|
||||
#: apps/remix/app/components/forms/reminder-preferences-form.tsx
|
||||
msgid "Controls how long recipients have to complete signing before the document expires. After expiration, recipients can no longer sign the document."
|
||||
@@ -3718,11 +3718,11 @@ msgstr "Niestandardowy plik {0} MB"
|
||||
|
||||
#: apps/remix/app/components/dialogs/branding-preferences-reset-dialog.tsx
|
||||
msgid "Custom branding enabled setting"
|
||||
msgstr "Niestandardowych ustawień marki"
|
||||
msgstr "Niestandardowe ustawienia marki"
|
||||
|
||||
#: apps/remix/app/components/dialogs/branding-preferences-reset-dialog.tsx
|
||||
msgid "Custom CSS"
|
||||
msgstr "Niestandardowego CSS"
|
||||
msgstr "Niestandardowy CSS"
|
||||
|
||||
#: apps/remix/app/components/forms/branding-preferences-form.tsx
|
||||
msgid "Custom CSS is sanitised on save. Layout-breaking properties, remote URLs, and pseudo-elements are stripped automatically. Any rules dropped during sanitisation will be shown after you save."
|
||||
@@ -4376,7 +4376,7 @@ msgstr "Wszystkie dokumenty"
|
||||
|
||||
#: apps/remix/app/utils/toast-error-messages.ts
|
||||
msgid "Document already completed"
|
||||
msgstr ""
|
||||
msgstr "Dokument został już zakończony"
|
||||
|
||||
#: apps/remix/app/components/general/envelope-signing/envelope-signing-complete-dialog.tsx
|
||||
msgid "Document already signed"
|
||||
@@ -4565,7 +4565,7 @@ msgstr "Przeniesiono dokument do zespołu"
|
||||
|
||||
#: apps/remix/app/utils/toast-error-messages.ts
|
||||
msgid "Document no longer available"
|
||||
msgstr ""
|
||||
msgstr "Dokument nie jest już dostępny"
|
||||
|
||||
#: apps/remix/app/routes/_recipient+/sign.$token+/complete.tsx
|
||||
msgid "Document no longer available to sign"
|
||||
@@ -4573,7 +4573,7 @@ msgstr "Dokument nie jest już dostępny do podpisania"
|
||||
|
||||
#: apps/remix/app/utils/toast-error-messages.ts
|
||||
msgid "Document not ready"
|
||||
msgstr ""
|
||||
msgstr "Dokument nie jest gotowy"
|
||||
|
||||
#: packages/lib/utils/document-audit-logs.ts
|
||||
msgctxt "Audit log format"
|
||||
@@ -5033,7 +5033,7 @@ msgstr "np. Resend (plan darmowy)"
|
||||
|
||||
#: apps/remix/app/components/general/settings-upsell/email-domains-upsell.tsx
|
||||
msgid "Easy DNS setup with auto-generated DKIM and SPF records"
|
||||
msgstr ""
|
||||
msgstr "Łatwa konfiguracja DNS z automatycznie generowanymi rekordami DKIM i SPF"
|
||||
|
||||
#: apps/remix/app/components/general/document/document-page-view-button.tsx
|
||||
#: apps/remix/app/components/general/document/document-page-view-dropdown.tsx
|
||||
@@ -5709,7 +5709,7 @@ msgstr "Wystąpił błąd podczas przesyłania pliku"
|
||||
|
||||
#: apps/remix/app/components/general/app-command-menu.tsx
|
||||
msgid "esc"
|
||||
msgstr "escape"
|
||||
msgstr "esc"
|
||||
|
||||
#: apps/remix/app/components/general/webhook-logs-sheet.tsx
|
||||
#: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.webhooks.$id._index.tsx
|
||||
@@ -6202,7 +6202,7 @@ msgstr "Niemiecki"
|
||||
|
||||
#: apps/remix/app/components/general/settings-upsell/sso-portal-upsell.tsx
|
||||
msgid "Give your members a dedicated single sign-on portal. The SSO portal is available on the Enterprise plan."
|
||||
msgstr ""
|
||||
msgstr "Zapewnij swoim użytkownikom logowanie SSO. Funkcja dostępna w planie Enterprise."
|
||||
|
||||
#: apps/remix/app/components/general/app-command-menu.tsx
|
||||
#: apps/remix/app/components/general/app-command-menu.tsx
|
||||
@@ -6438,7 +6438,7 @@ msgstr "Ukryj"
|
||||
|
||||
#: packages/ui/components/document/envelope-recipient-field-tooltip.tsx
|
||||
msgid "Hide field"
|
||||
msgstr ""
|
||||
msgstr "Ukryj pole"
|
||||
|
||||
#: apps/remix/app/components/general/admin-license-card.tsx
|
||||
msgid "Hide license key"
|
||||
@@ -6852,7 +6852,7 @@ msgstr "To nie jest Twoja kolej na podpisanie dokumentu. Gdy nadejdzie Twoja kol
|
||||
|
||||
#: apps/remix/app/utils/toast-error-messages.ts
|
||||
msgid "It's not your turn to sign yet"
|
||||
msgstr ""
|
||||
msgstr "To nie jest Twoja kolej na podpisanie dokumentu"
|
||||
|
||||
#: packages/lib/constants/i18n.ts
|
||||
msgid "Italian"
|
||||
@@ -6974,7 +6974,7 @@ msgstr "Użyto"
|
||||
|
||||
#: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.tokens.tsx
|
||||
msgid "Last Used"
|
||||
msgstr ""
|
||||
msgstr "Użyto"
|
||||
|
||||
#: apps/remix/app/routes/_authenticated+/admin+/email-domains._index.tsx
|
||||
#: apps/remix/app/routes/_authenticated+/admin+/email-domains.$id.tsx
|
||||
@@ -7603,7 +7603,7 @@ msgstr "Ustawienia nazwy"
|
||||
|
||||
#: apps/remix/app/components/general/settings-upsell/email-domains-upsell.tsx
|
||||
msgid "Named senders with defaults per team, template or document"
|
||||
msgstr ""
|
||||
msgstr "Zdefiniowani nadawcy z domyślnymi ustawieniami dla zespołu, szablonu lub dokumentu"
|
||||
|
||||
#: apps/remix/app/components/general/app-command-menu.tsx
|
||||
msgid "Navigate"
|
||||
@@ -7990,14 +7990,14 @@ msgstr "Na tej stronie możesz utworzyć nowe webhooki i zarządzać obecnymi."
|
||||
#: apps/remix/app/components/dialogs/branding-preferences-reset-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/document-preferences-reset-dialog.tsx
|
||||
msgid "Once confirmed, the following will be reset:"
|
||||
msgstr "Potwierdzenie akcji spowoduje zresetowanie:"
|
||||
msgstr "Po potwierdzeniu zresetowane zostaną:"
|
||||
|
||||
#: apps/remix/app/components/dialogs/envelope-cancel-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/envelope-delete-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/envelopes-bulk-cancel-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/envelopes-bulk-delete-dialog.tsx
|
||||
msgid "Once confirmed, the following will occur:"
|
||||
msgstr "Potwierdzenie akcji spowoduje następujące skutki:"
|
||||
msgstr "Po potwierdzeniu:"
|
||||
|
||||
#: packages/lib/constants/template.ts
|
||||
msgid "Once enabled, you can select any active recipient to be a direct link signing recipient, or create a new one. This recipient type cannot be edited or deleted."
|
||||
@@ -8021,7 +8021,7 @@ msgstr "Jeden z dokumentów ma niezgodną rolę w procesie podpisywania."
|
||||
|
||||
#: apps/remix/app/utils/toast-error-messages.ts
|
||||
msgid "One or more of your required fields have not been saved. Please refresh the page, complete any empty required fields, and try again."
|
||||
msgstr ""
|
||||
msgstr "Co najmniej jedno z wymaganych pól nie zostało zapisane. Odśwież stronę, uzupełnij wymagane pola i spróbuj ponownie."
|
||||
|
||||
#: apps/remix/app/components/forms/document-preferences-form.tsx
|
||||
msgid "Only admins can access and view the document"
|
||||
@@ -8756,7 +8756,7 @@ msgstr "Wybierz, w jaki sposób chcesz otrzymać swój kod weryfikacyjny."
|
||||
#: apps/remix/app/components/general/settings-upsell/branding-upsell.tsx
|
||||
#: apps/remix/app/components/general/settings-upsell/email-domains-upsell.tsx
|
||||
msgid "Please sign: Example.pdf"
|
||||
msgstr ""
|
||||
msgstr "Podpisz dokument: umowa.pdf"
|
||||
|
||||
#: apps/remix/app/components/dialogs/organisation-email-domain-create-dialog.tsx
|
||||
msgid "Please try a different domain."
|
||||
@@ -8789,6 +8789,10 @@ msgstr "Prześlij dokument, aby kontynuować"
|
||||
msgid "Please upload a logo"
|
||||
msgstr "Prześlij logo"
|
||||
|
||||
#: packages/ui/primitives/signature-pad/signature-pad-upload.tsx
|
||||
msgid "Please upload a valid image file."
|
||||
msgstr "Prześlij prawidłową grafikę."
|
||||
|
||||
#: packages/lib/constants/i18n.ts
|
||||
msgid "Polish"
|
||||
msgstr "Polski"
|
||||
@@ -8946,7 +8950,7 @@ msgstr "Szablony publiczne są powiązane z Twoim profilem publicznym. Wszelkie
|
||||
|
||||
#: apps/remix/app/components/general/settings-upsell/branding-upsell.tsx
|
||||
msgid "Put your own brand on every document you send. Branding is available on the Teams plan and above."
|
||||
msgstr ""
|
||||
msgstr "Umieść swoje logo na każdym wysyłanym dokumencie. Branding jest dostępny w planie Teams i wyższym."
|
||||
|
||||
#: apps/remix/app/components/general/envelope-editor/envelope-editor.tsx
|
||||
msgid "Quick Actions"
|
||||
@@ -9278,7 +9282,7 @@ msgstr "Przekierowywanie do {0}..."
|
||||
|
||||
#: apps/remix/app/components/general/settings-upsell/sso-portal-upsell.tsx
|
||||
msgid "Redirecting to your identity provider"
|
||||
msgstr ""
|
||||
msgstr "Przekierowywanie do dostawcy tożsamości"
|
||||
|
||||
#: apps/remix/app/components/forms/signup.tsx
|
||||
#: apps/remix/app/components/general/claim-account.tsx
|
||||
@@ -9650,7 +9654,7 @@ msgstr "Zapewniamy, że Twój dokument jest poufny i nigdy nie zostanie udostęp
|
||||
|
||||
#: apps/remix/app/components/general/settings-upsell/sso-portal-upsell.tsx
|
||||
msgid "Restrict sign-ins by email domain and choose the default role"
|
||||
msgstr ""
|
||||
msgstr "Ogranicz logowanie do wybranych domen i przypisz domyślną rolę"
|
||||
|
||||
#: apps/remix/app/components/general/organisations/organisation-billing-banner.tsx
|
||||
msgid "Restricted Access"
|
||||
@@ -9814,7 +9818,7 @@ msgstr "Szukaj identyfikatora lub nazwy"
|
||||
|
||||
#: apps/remix/app/routes/_authenticated+/admin+/documents._index.tsx
|
||||
msgid "Search by document title, recipient:123, team:123 or user:123"
|
||||
msgstr ""
|
||||
msgstr "Szukaj tytułu dokumentu, odbiorcy, zespołu lub użytkownika"
|
||||
|
||||
#: apps/remix/app/routes/_authenticated+/admin+/email-domains._index.tsx
|
||||
msgid "Search by domain or organisation name"
|
||||
@@ -10112,7 +10116,7 @@ msgstr "Wybierz widoczność"
|
||||
|
||||
#: apps/remix/app/components/tables/envelopes-table-bulk-action-bar.tsx
|
||||
msgid "selected"
|
||||
msgstr ""
|
||||
msgstr "zaznaczono"
|
||||
|
||||
#: apps/remix/app/components/dialogs/envelopes-bulk-delete-dialog.tsx
|
||||
msgid "Selected documents will be permanently deleted"
|
||||
@@ -10170,7 +10174,7 @@ msgstr "Wyślij dokument"
|
||||
|
||||
#: apps/remix/app/components/general/settings-upsell/email-domains-upsell.tsx
|
||||
msgid "Send documents from your own domain. Email domains are available on the Enterprise plan."
|
||||
msgstr ""
|
||||
msgstr "Wysyłaj dokumenty ze swojej domeny. Domeny są dostępne w planie Enterprise."
|
||||
|
||||
#: packages/email/templates/confirm-team-email.tsx
|
||||
msgid "Send documents on behalf of the team using the email address"
|
||||
@@ -10182,7 +10186,7 @@ msgstr "Wyślij dokumenty do odbiorców natychmiast"
|
||||
|
||||
#: apps/remix/app/components/general/settings-upsell/email-domains-upsell.tsx
|
||||
msgid "Send emails to recipients from your domain"
|
||||
msgstr ""
|
||||
msgstr "Wysyłaj wiadomości ze swojej domeny"
|
||||
|
||||
#: apps/remix/app/components/general/envelope-editor/envelope-editor.tsx
|
||||
msgid "Send Envelope"
|
||||
@@ -10232,11 +10236,11 @@ msgstr "Nadawca został zgłoszony"
|
||||
|
||||
#: apps/remix/app/components/general/settings-upsell/email-domains-upsell.tsx
|
||||
msgid "Sending from app.documenso.com"
|
||||
msgstr ""
|
||||
msgstr "Wysyłanie z app.documenso.com"
|
||||
|
||||
#: apps/remix/app/components/general/settings-upsell/email-domains-upsell.tsx
|
||||
msgid "Sending from your domain"
|
||||
msgstr ""
|
||||
msgstr "Wysyłanie z Twojej domeny"
|
||||
|
||||
#: apps/remix/app/components/forms/forgot-password.tsx
|
||||
msgid "Sending Reset Email..."
|
||||
@@ -10260,7 +10264,7 @@ msgstr "Wysłano w tym okresie"
|
||||
|
||||
#: apps/remix/app/components/general/settings-upsell/branding-upsell.tsx
|
||||
msgid "Separate branding per team"
|
||||
msgstr ""
|
||||
msgstr "Oddzielny branding dla zespołów"
|
||||
|
||||
#: apps/remix/app/routes/_authenticated+/settings+/security.sessions.tsx
|
||||
msgid "Session revoked"
|
||||
@@ -10501,6 +10505,7 @@ msgid "Signature ID"
|
||||
msgstr "Identyfikator podpisu"
|
||||
|
||||
#: packages/ui/primitives/signature-pad/signature-pad-draw.tsx
|
||||
#: packages/ui/primitives/signature-pad/signature-pad-upload.tsx
|
||||
msgid "Signature is too small"
|
||||
msgstr "Podpis jest zbyt mały"
|
||||
|
||||
@@ -10541,7 +10546,7 @@ msgstr "Podpisany"
|
||||
|
||||
#: apps/remix/app/components/general/settings-upsell/sso-portal-upsell.tsx
|
||||
msgid "Signed in"
|
||||
msgstr ""
|
||||
msgstr "Zalogowano"
|
||||
|
||||
#: apps/remix/app/components/general/envelope-signing/envelope-signer-header.tsx
|
||||
msgid "Signer"
|
||||
@@ -10609,7 +10614,7 @@ msgstr "Logowanie..."
|
||||
|
||||
#: apps/remix/app/utils/toast-error-messages.ts
|
||||
msgid "Signing link expired"
|
||||
msgstr ""
|
||||
msgstr "Link do podpisywania wygasł"
|
||||
|
||||
#: apps/remix/app/components/general/document/document-page-view-dropdown.tsx
|
||||
#: apps/remix/app/components/tables/documents-table-action-dropdown.tsx
|
||||
@@ -10665,7 +10670,7 @@ msgstr "Od {0}"
|
||||
|
||||
#: apps/remix/app/components/general/settings-upsell/sso-portal-upsell.tsx
|
||||
msgid "Single sign-on"
|
||||
msgstr ""
|
||||
msgstr "Logowanie SSO"
|
||||
|
||||
#: apps/remix/app/components/general/admin-site-banner-section.tsx
|
||||
msgid "Site Banner"
|
||||
@@ -10688,7 +10693,7 @@ msgstr "Niektóre pola są umieszczone jedno nad drugim. Może to utrudnić podp
|
||||
|
||||
#: apps/remix/app/utils/toast-error-messages.ts
|
||||
msgid "Some fields were not saved"
|
||||
msgstr ""
|
||||
msgstr "Niektóre pola nie zostały zapisane"
|
||||
|
||||
#: apps/remix/app/components/general/app-command-menu.tsx
|
||||
msgid "Some searches failed — results may be incomplete."
|
||||
@@ -11605,7 +11610,7 @@ msgstr "Dokument został odrzucony w zewnętrznej usłudze w imieniu odbiorcy"
|
||||
|
||||
#: apps/remix/app/components/dialogs/envelope-delete-dialog.tsx
|
||||
msgid "The document will be hidden from your account"
|
||||
msgstr "Dokument zostanie ukryty w Twoim koncie"
|
||||
msgstr "Dokument zostanie ukryty na Twoim koncie"
|
||||
|
||||
#: apps/remix/app/components/dialogs/template-use-dialog.tsx
|
||||
msgid "The document will be immediately sent to recipients if this is checked."
|
||||
@@ -11697,6 +11702,14 @@ msgstr "Brak pola podpisu dla następujących podpisujących:"
|
||||
msgid "The following team has been deleted. You will no longer be able to access this team and its documents"
|
||||
msgstr "Następujący zespół został usunięty. Nie będziesz mógł już uzyskać dostępu do tego zespołu i jego dokumentów"
|
||||
|
||||
#: packages/ui/primitives/signature-pad/signature-pad-upload.tsx
|
||||
msgid "The image could not be loaded. Please try again."
|
||||
msgstr "Nie można załadować grafiki. Spróbuj ponownie."
|
||||
|
||||
#: packages/ui/primitives/signature-pad/signature-pad-upload.tsx
|
||||
msgid "The image must be smaller than 5MB."
|
||||
msgstr "Maksymalny rozmiar grafiki to 5 MB."
|
||||
|
||||
#: apps/remix/app/components/general/admin-license-card.tsx
|
||||
msgid "The license key has been copied to your clipboard"
|
||||
msgstr "Klucz licencyjny został skopiowany do schowka"
|
||||
@@ -12027,7 +12040,7 @@ msgstr "Szablon bezpośredniego linku nie może zostać użyty, ponieważ co naj
|
||||
|
||||
#: apps/remix/app/utils/toast-error-messages.ts
|
||||
msgid "This document can no longer be signed. It may have been removed by the sender, or your signing access may have been revoked. Please contact the sender for a new signing link."
|
||||
msgstr ""
|
||||
msgstr "Nie można podpisać dokumentu. Być może został on usunięty przez nadawcę lub Twoje uprawnienia do podpisywania zostały unieważnione. Skontaktuj się z nadawcą, aby uzyskać nowy link do podpisywania."
|
||||
|
||||
#: packages/email/template-components/template-document-super-delete.tsx
|
||||
msgid "This document can not be recovered, if you would like to dispute the reason for future documents please contact support."
|
||||
@@ -12059,7 +12072,7 @@ msgstr "Nie można zapisać dokumentu jako szablonu. Spróbuj ponownie."
|
||||
|
||||
#: apps/remix/app/utils/toast-error-messages.ts
|
||||
msgid "This document has already been completed and no further signatures can be added."
|
||||
msgstr ""
|
||||
msgstr "Dokument został już zakończony i nie wymaga kolejnych podpisów."
|
||||
|
||||
#: apps/remix/app/components/general/envelope-editor/envelope-recipient-selector.tsx
|
||||
#: packages/ui/primitives/recipient-selector.tsx
|
||||
@@ -12081,7 +12094,7 @@ msgstr "Dokument został anulowany przez właściciela."
|
||||
|
||||
#: apps/remix/app/utils/toast-error-messages.ts
|
||||
msgid "This document has been cancelled by the sender and can no longer be signed. Please contact the sender if you believe this is a mistake."
|
||||
msgstr ""
|
||||
msgstr "Dokument został anulowany przez nadawcę i nie można go już podpisać. Jeśli uważasz to za błąd, skontaktuj się z nadawcą."
|
||||
|
||||
#: apps/remix/app/routes/_authenticated+/t.$teamUrl+/documents.$id._index.tsx
|
||||
msgid "This document has been rejected by a recipient"
|
||||
@@ -12089,7 +12102,7 @@ msgstr "Dokument został odrzucony przez odbiorcę"
|
||||
|
||||
#: apps/remix/app/utils/toast-error-messages.ts
|
||||
msgid "This document has been rejected by a recipient and can no longer be signed."
|
||||
msgstr ""
|
||||
msgstr "Dokument został odrzucony przez odbiorcę i nie można go już podpisać."
|
||||
|
||||
#: apps/remix/app/routes/_authenticated+/t.$teamUrl+/documents.$id._index.tsx
|
||||
msgid "This document has been signed by all recipients"
|
||||
@@ -12097,7 +12110,7 @@ msgstr "Dokument został podpisany przez wszystkich odbiorców"
|
||||
|
||||
#: apps/remix/app/utils/toast-error-messages.ts
|
||||
msgid "This document has not been sent for signing yet. Please wait for the sender to send it before signing."
|
||||
msgstr ""
|
||||
msgstr "Dokument nie został jeszcze wysłany do podpisania. Poczekaj na wysłanie go przez nadawcę."
|
||||
|
||||
#: apps/remix/app/utils/toast-error-messages.ts
|
||||
msgid "This document has too many recipients. Please remove some recipients or contact support if you need more."
|
||||
@@ -12113,7 +12126,7 @@ msgstr "Dokument jest szkicem i nie został wysłany"
|
||||
|
||||
#: apps/remix/app/utils/toast-error-messages.ts
|
||||
msgid "This document is signed in a set order and other recipients must sign before you. You will receive an email when it is your turn."
|
||||
msgstr ""
|
||||
msgstr "Dokument jest podpisywany zgodnie z ustaloną kolejnością. Gdy nadejdzie Twoja kolej, otrzymasz wiadomość."
|
||||
|
||||
#: apps/remix/app/components/general/legacy-field-warning-popover.tsx
|
||||
msgid "This document is using legacy field insertion, we recommend using the new field insertion method for more accurate results."
|
||||
@@ -12192,6 +12205,10 @@ msgstr "Typ pliku nie jest obsługiwany. Prześlij dokument PDF lub Word."
|
||||
msgid "This folder contains multiple items. Deleting it will remove all subfolders and move all nested documents and templates to the root folder."
|
||||
msgstr "Folder zawiera wiele elementów. Usunięcie go spowoduje przeniesienie wszystkich dokumentów i szablonów znajdujących się w tym folderze do folderu głównego."
|
||||
|
||||
#: packages/ui/primitives/signature-pad/signature-pad-upload.tsx
|
||||
msgid "This image is invalid, please upload a valid image file."
|
||||
msgstr "Grafika jest nieprawidłowa. Prześlij prawidłowy plik."
|
||||
|
||||
#: apps/remix/app/components/general/envelope-editor/envelope-editor-settings-dialog.tsx
|
||||
#: packages/ui/primitives/template-flow/add-template-settings.tsx
|
||||
msgid "This is how the document will reach the recipients once the document is ready for signing."
|
||||
@@ -12774,6 +12791,10 @@ msgstr "Nie można się zalogować"
|
||||
msgid "Unable to start the signing flow"
|
||||
msgstr "Nie można rozpocząć procesu podpisywania"
|
||||
|
||||
#: packages/ui/primitives/signature-pad/signature-pad-upload.tsx
|
||||
msgid "Unable to upload image"
|
||||
msgstr "Nie można przesłać grafiki"
|
||||
|
||||
#: apps/remix/app/components/general/document-signing/document-signing-auth-2fa.tsx
|
||||
#: apps/remix/app/components/general/document-signing/document-signing-auth-passkey.tsx
|
||||
#: apps/remix/app/components/general/document-signing/document-signing-auth-password.tsx
|
||||
@@ -12788,6 +12809,7 @@ msgstr "Niezakończono"
|
||||
|
||||
#: apps/remix/app/components/forms/form-sticky-save-bar.tsx
|
||||
#: packages/ui/primitives/signature-pad/signature-pad-draw.tsx
|
||||
#: packages/ui/primitives/signature-pad/signature-pad-draw.tsx
|
||||
msgid "Undo"
|
||||
msgstr "Cofnij"
|
||||
|
||||
@@ -12832,15 +12854,15 @@ msgstr "Rozłącz"
|
||||
|
||||
#: apps/remix/app/components/general/settings-upsell/branding-upsell.tsx
|
||||
msgid "Unlock Branding Preferences"
|
||||
msgstr ""
|
||||
msgstr "Odblokuj ustawienia brandingu"
|
||||
|
||||
#: apps/remix/app/components/general/settings-upsell/email-domains-upsell.tsx
|
||||
msgid "Unlock Email Domains"
|
||||
msgstr ""
|
||||
msgstr "Odblokuj domeny"
|
||||
|
||||
#: apps/remix/app/components/general/settings-upsell/sso-portal-upsell.tsx
|
||||
msgid "Unlock the Organisation SSO Portal"
|
||||
msgstr ""
|
||||
msgstr "Odblokuj logowanie SSO organizacji"
|
||||
|
||||
#: apps/remix/app/components/general/folder/folder-card.tsx
|
||||
msgid "Unpin"
|
||||
@@ -13017,7 +13039,7 @@ msgstr "Ulepsz organizację <0>{0}</0> do planu {planName}"
|
||||
|
||||
#: apps/remix/app/components/general/settings-upsell/settings-upsell-card.tsx
|
||||
msgid "Upgrade Plan"
|
||||
msgstr ""
|
||||
msgstr "Ulepsz plan"
|
||||
|
||||
#: apps/remix/app/components/general/envelope/envelope-drop-zone-wrapper.tsx
|
||||
msgid "Upgrade your plan to upload more documents"
|
||||
@@ -13078,6 +13100,10 @@ msgstr "Prześlij dokumenty i dodaj odbiorców"
|
||||
msgid "Upload failed"
|
||||
msgstr "Przesyłanie nie powiodło się"
|
||||
|
||||
#: packages/ui/primitives/signature-pad/signature-pad-upload.tsx
|
||||
msgid "Upload New Image"
|
||||
msgstr "Prześlij nową grafikę"
|
||||
|
||||
#: apps/remix/app/components/general/admin-global-settings-section.tsx
|
||||
msgid "Upload signature"
|
||||
msgstr "Podpis przesyłany"
|
||||
@@ -13506,7 +13532,7 @@ msgstr "Oczekiwanie na innych"
|
||||
|
||||
#: packages/lib/jobs/definitions/emails/send-document-pending-email.handler.ts
|
||||
msgid "Waiting for others to complete signing"
|
||||
msgstr ""
|
||||
msgstr "Oczekiwanie na zakończenie podpisywania przez innych"
|
||||
|
||||
#: apps/remix/app/routes/_recipient+/sign.$token+/complete.tsx
|
||||
msgid "Waiting for others to sign"
|
||||
@@ -14004,7 +14030,7 @@ msgstr "Witaj z powrotem! Oto podsumowanie Twojego konta."
|
||||
#. placeholder {0}: organisation.name
|
||||
#: apps/remix/app/components/general/settings-upsell/sso-portal-upsell.tsx
|
||||
msgid "Welcome to {0}"
|
||||
msgstr ""
|
||||
msgstr "Witaj w organizacji {0}"
|
||||
|
||||
#: apps/remix/app/routes/_unauthenticated+/o.$orgUrl.signin.tsx
|
||||
msgid "Welcome to {organisationName}"
|
||||
@@ -14085,7 +14111,7 @@ msgstr "W ramach limitu"
|
||||
|
||||
#: apps/remix/app/components/general/settings-upsell/sso-portal-upsell.tsx
|
||||
msgid "Works with any OIDC provider — Okta, Entra ID, Google and more"
|
||||
msgstr ""
|
||||
msgstr "Działa z dowolnym dostawcą OIDC: Okta, Entra ID, Google i więcej"
|
||||
|
||||
#: apps/remix/app/components/forms/public-profile-form.tsx
|
||||
msgid "Write a description to display on your public profile"
|
||||
@@ -15102,7 +15128,7 @@ msgstr "Koperta została ponownie wysłana."
|
||||
|
||||
#: apps/remix/app/components/general/settings-upsell/branding-upsell.tsx
|
||||
msgid "Your logo on signing pages and emails"
|
||||
msgstr ""
|
||||
msgstr "Twoje logo na stronach podpisywania i w wiadomościach"
|
||||
|
||||
#: apps/remix/app/components/general/document-signing/document-signing-complete-dialog.tsx
|
||||
msgid "Your Name"
|
||||
@@ -15239,7 +15265,7 @@ msgstr "Certyfikat podpisu jest nieważny, wygasł lub brakuje w nim wymaganego
|
||||
|
||||
#: apps/remix/app/utils/toast-error-messages.ts
|
||||
msgid "Your signing link has expired. Please contact the sender to request a new one."
|
||||
msgstr ""
|
||||
msgstr "Twój link do podpisywania wygasł. Skontaktuj się z nadawcą, aby otrzymać nowy link."
|
||||
|
||||
#: packages/lib/utils/document-audit-logs.ts
|
||||
msgid "Your signing provider authentication failed"
|
||||
@@ -15322,3 +15348,14 @@ msgstr "Twój kod weryfikacyjny:"
|
||||
#: apps/remix/app/routes/_authenticated+/o.$orgUrl.settings.sso.tsx
|
||||
msgid "your-domain.com another-domain.com"
|
||||
msgstr "twoja-domena.pl inna-domena.pl"
|
||||
|
||||
#: packages/ui/primitives/signature-pad/signature-pad-upload.tsx
|
||||
#: packages/ui/primitives/signature-pad/signature-pad-upload.tsx
|
||||
msgid "Zoom in"
|
||||
msgstr "Powiększ"
|
||||
|
||||
#: packages/ui/primitives/signature-pad/signature-pad-upload.tsx
|
||||
#: packages/ui/primitives/signature-pad/signature-pad-upload.tsx
|
||||
msgid "Zoom out"
|
||||
msgstr "Pomniejsz"
|
||||
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
/**
|
||||
* Run a function without awaiting it or letting it affect the caller.
|
||||
*
|
||||
* Both synchronous throws and promise rejections are swallowed, making this
|
||||
* safe to call from request handlers and loaders for side work such as
|
||||
* analytics that must never block or fail the response.
|
||||
*/
|
||||
export const fireAndForget = (fn: () => unknown) => {
|
||||
try {
|
||||
void Promise.resolve(fn()).catch(() => null);
|
||||
} catch {
|
||||
// Swallow synchronous errors.
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,32 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
import { renderCustomEmailTemplate } from './render-custom-email-template';
|
||||
|
||||
describe('renderCustomEmailTemplate', () => {
|
||||
it('replaces a single variable', () => {
|
||||
expect(renderCustomEmailTemplate('Hi {name}', { name: 'Sam' })).toBe('Hi Sam');
|
||||
});
|
||||
|
||||
it('replaces multiple variables separated by whitespace', () => {
|
||||
expect(
|
||||
renderCustomEmailTemplate('Hi {name}, sign at {url}', { name: 'Sam', url: 'https://x' }),
|
||||
).toBe('Hi Sam, sign at https://x');
|
||||
});
|
||||
|
||||
it('replaces adjacent variables and variables separated by a non-whitespace character', () => {
|
||||
expect(
|
||||
renderCustomEmailTemplate('{day}/{month}/{year}', { day: '01', month: '02', year: '2026' }),
|
||||
).toBe('01/02/2026');
|
||||
|
||||
expect(
|
||||
renderCustomEmailTemplate('{firstName}-{lastName}', {
|
||||
firstName: 'Ada',
|
||||
lastName: 'Lovelace',
|
||||
}),
|
||||
).toBe('Ada-Lovelace');
|
||||
});
|
||||
|
||||
it('leaves the key in place when the variable is not provided', () => {
|
||||
expect(renderCustomEmailTemplate('Unknown {missing} here', {})).toBe('Unknown missing here');
|
||||
});
|
||||
});
|
||||
@@ -1,5 +1,9 @@
|
||||
export const renderCustomEmailTemplate = <T extends Record<string, string>>(template: string, variables: T): string => {
|
||||
return template.replace(/\{(\S+)\}/g, (_, key) => {
|
||||
// Match a run of non-brace, non-whitespace characters between braces. Using
|
||||
// `\S+` here was greedy across braces, so placeholders separated by a
|
||||
// non-whitespace character (e.g. "{day}/{month}/{year}") were captured as a
|
||||
// single bogus key and left unrendered.
|
||||
return template.replace(/\{([^\s{}]+)\}/g, (_, key) => {
|
||||
if (key in variables) {
|
||||
return variables[key];
|
||||
}
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
import { captureServerEvent } from '@documenso/lib/server-only/analytics/capture-server-event';
|
||||
import { createApiToken } from '@documenso/lib/server-only/public-api/create-api-token';
|
||||
import { fireAndForget } from '@documenso/lib/universal/fire-and-forget';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
|
||||
import { authenticatedProcedure } from '../trpc';
|
||||
import { ZCreateApiTokenRequestSchema, ZCreateApiTokenResponseSchema } from './create-api-token.types';
|
||||
@@ -15,10 +18,26 @@ export const createApiTokenRoute = authenticatedProcedure
|
||||
},
|
||||
});
|
||||
|
||||
return await createApiToken({
|
||||
const createdToken = await createApiToken({
|
||||
userId: ctx.user.id,
|
||||
teamId,
|
||||
tokenName,
|
||||
expiresIn: expirationDate,
|
||||
});
|
||||
|
||||
fireAndForget(async () => {
|
||||
const team = await prisma.team.findFirst({
|
||||
where: { id: teamId },
|
||||
select: { organisationId: true },
|
||||
});
|
||||
|
||||
captureServerEvent({
|
||||
event: 'App: API Token Created',
|
||||
userId: ctx.user.id,
|
||||
teamId,
|
||||
organisationId: team?.organisationId,
|
||||
});
|
||||
});
|
||||
|
||||
return createdToken;
|
||||
});
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { getServerLimits } from '@documenso/ee/server-only/limits/server';
|
||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
import { jobs } from '@documenso/lib/jobs/client';
|
||||
import { captureServerEvent } from '@documenso/lib/server-only/analytics/capture-server-event';
|
||||
import { getDocumentWithDetailsById } from '@documenso/lib/server-only/document/get-document-with-details-by-id';
|
||||
import { sendDocument } from '@documenso/lib/server-only/document/send-document';
|
||||
import { convertToPdf } from '@documenso/lib/server-only/document-conversion';
|
||||
@@ -21,12 +22,14 @@ import { findTemplates } from '@documenso/lib/server-only/template/find-template
|
||||
import { getOrganisationTemplateById } from '@documenso/lib/server-only/template/get-organisation-template-by-id';
|
||||
import { getTemplateById } from '@documenso/lib/server-only/template/get-template-by-id';
|
||||
import { toggleTemplateDirectLink } from '@documenso/lib/server-only/template/toggle-template-direct-link';
|
||||
import { fireAndForget } from '@documenso/lib/universal/fire-and-forget';
|
||||
import { putNormalizedPdfFileServerSide } from '@documenso/lib/universal/upload/put-file.server';
|
||||
import { getPresignPostUrl } from '@documenso/lib/universal/upload/server-actions';
|
||||
import { mapSecondaryIdToTemplateId } from '@documenso/lib/utils/envelope';
|
||||
import { mapFieldToLegacyField } from '@documenso/lib/utils/fields';
|
||||
import { mapRecipientToLegacyRecipient } from '@documenso/lib/utils/recipients';
|
||||
import { mapEnvelopeToTemplateLite } from '@documenso/lib/utils/templates';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import type { Envelope } from '@prisma/client';
|
||||
import { DocumentDataType, EnvelopeType } from '@prisma/client';
|
||||
|
||||
@@ -744,7 +747,7 @@ export const templateRouter = router({
|
||||
});
|
||||
}
|
||||
|
||||
return await createTemplateDirectLink({
|
||||
const directLink = await createTemplateDirectLink({
|
||||
userId,
|
||||
teamId,
|
||||
id: {
|
||||
@@ -753,6 +756,25 @@ export const templateRouter = router({
|
||||
},
|
||||
directRecipientId,
|
||||
});
|
||||
|
||||
fireAndForget(async () => {
|
||||
const team = await prisma.team.findFirst({
|
||||
where: { id: template.teamId },
|
||||
select: { organisationId: true },
|
||||
});
|
||||
|
||||
captureServerEvent({
|
||||
event: 'App: Template Direct Link Enabled',
|
||||
userId,
|
||||
teamId: template.teamId,
|
||||
organisationId: team?.organisationId,
|
||||
properties: {
|
||||
envelopeId: template.envelopeId,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
return directLink;
|
||||
}),
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
import { captureServerEvent } from '@documenso/lib/server-only/analytics/capture-server-event';
|
||||
import { createWebhook } from '@documenso/lib/server-only/webhooks/create-webhook';
|
||||
import { deleteWebhookById } from '@documenso/lib/server-only/webhooks/delete-webhook-by-id';
|
||||
import { editWebhook } from '@documenso/lib/server-only/webhooks/edit-webhook';
|
||||
import { getWebhookById } from '@documenso/lib/server-only/webhooks/get-webhook-by-id';
|
||||
import { getWebhooksByTeamId } from '@documenso/lib/server-only/webhooks/get-webhooks-by-team-id';
|
||||
import { triggerTestWebhook } from '@documenso/lib/server-only/webhooks/trigger-test-webhook';
|
||||
import { fireAndForget } from '@documenso/lib/universal/fire-and-forget';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
|
||||
import { authenticatedProcedure, router } from '../trpc';
|
||||
import { findWebhookCallsRoute } from './find-webhook-calls';
|
||||
@@ -51,7 +54,7 @@ export const webhookRouter = router({
|
||||
createWebhook: authenticatedProcedure.input(ZCreateWebhookRequestSchema).mutation(async ({ input, ctx }) => {
|
||||
const { enabled, eventTriggers, secret, webhookUrl } = input;
|
||||
|
||||
return await createWebhook({
|
||||
const webhook = await createWebhook({
|
||||
enabled,
|
||||
secret,
|
||||
webhookUrl,
|
||||
@@ -59,6 +62,25 @@ export const webhookRouter = router({
|
||||
teamId: ctx.teamId,
|
||||
userId: ctx.user.id,
|
||||
});
|
||||
|
||||
fireAndForget(async () => {
|
||||
const team = await prisma.team.findFirst({
|
||||
where: { id: ctx.teamId },
|
||||
select: { organisationId: true },
|
||||
});
|
||||
|
||||
captureServerEvent({
|
||||
event: 'App: Webhook Created',
|
||||
userId: ctx.user.id,
|
||||
teamId: ctx.teamId,
|
||||
organisationId: team?.organisationId,
|
||||
properties: {
|
||||
triggerCount: eventTriggers.length,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
return webhook;
|
||||
}),
|
||||
|
||||
deleteWebhook: authenticatedProcedure.input(ZDeleteWebhookRequestSchema).mutation(async ({ input, ctx }) => {
|
||||
|
||||
Reference in New Issue
Block a user