mirror of
https://github.com/documenso/documenso.git
synced 2025-11-10 04:22:32 +10:00
Merge branch 'main' into feat/document-table-filters
This commit is contained in:
5
.github/PULL_REQUEST_TEMPLATE.md
vendored
5
.github/PULL_REQUEST_TEMPLATE.md
vendored
@ -1,8 +1,3 @@
|
||||
---
|
||||
name: Pull Request
|
||||
about: Submit changes to the project for review and inclusion
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!--- Describe the changes introduced by this pull request. -->
|
||||
|
||||
6
.gitignore
vendored
6
.gitignore
vendored
@ -52,4 +52,8 @@ yarn-error.log*
|
||||
!.vscode/extensions.json
|
||||
|
||||
# logs
|
||||
logs.json
|
||||
logs.json
|
||||
|
||||
# claude
|
||||
.claude
|
||||
CLAUDE.md
|
||||
@ -1,5 +1,6 @@
|
||||
{
|
||||
"index": "Get Started",
|
||||
"authentication": "Authentication",
|
||||
"rate-limits": "Rate Limits",
|
||||
"versioning": "Versioning"
|
||||
}
|
||||
|
||||
@ -33,7 +33,7 @@ Our new API V2 supports the following typed SDKs:
|
||||
|
||||
<Callout type="info">
|
||||
For the staging API, please use the following base URL:
|
||||
`https://stg-app.documenso.dev/api/v2-beta/`
|
||||
`https://stg-app.documenso.com/api/v2-beta/`
|
||||
</Callout>
|
||||
|
||||
🚀 [V2 Announcement](https://documen.so/sdk-blog)
|
||||
|
||||
@ -0,0 +1,54 @@
|
||||
import { Callout } from 'nextra/components';
|
||||
|
||||
# Rate Limits
|
||||
|
||||
Documenso enforces rate limits on all API endpoints to ensure service stability.
|
||||
|
||||
## HTTP Rate Limits
|
||||
|
||||
**Limit:** 100 requests per minute per IP address
|
||||
**Response:** 429 Too Many Requests
|
||||
|
||||
### Rate Limit Response
|
||||
|
||||
```json
|
||||
{
|
||||
"error": "Too many requests, please try again later."
|
||||
}
|
||||
```
|
||||
|
||||
<Callout type="warning">
|
||||
No rate limit headers are currently provided. When you receive a 429 response, wait at least 60
|
||||
seconds before retrying.
|
||||
</Callout>
|
||||
|
||||
## Resource Limits
|
||||
|
||||
Beyond HTTP rate limits, your account has usage limits based on your subscription plan.
|
||||
|
||||
### Plan Limits
|
||||
|
||||
| Resource | Free | Paid | Self-hosted | Enterprise |
|
||||
| ---------------- | ---- | --------- | ----------- | ---------- |
|
||||
| Documents/month | 5 | Unlimited | Unlimited | Unlimited |
|
||||
| Total Recipients | 10 | Unlimited | Unlimited | Unlimited |
|
||||
| Direct Templates | 3 | Unlimited | Unlimited | Unlimited |
|
||||
|
||||
### Error Response
|
||||
|
||||
When you exceed a resource limit:
|
||||
|
||||
```json
|
||||
{
|
||||
"error": "You have reached your document limit for this month. Please upgrade your plan.",
|
||||
"code": "LIMIT_EXCEEDED",
|
||||
"statusCode": 400
|
||||
}
|
||||
```
|
||||
|
||||
## Error Codes
|
||||
|
||||
| Code | Status | Description |
|
||||
| ------------------- | ------ | ----------------------------- |
|
||||
| `TOO_MANY_REQUESTS` | 429 | HTTP rate limit exceeded |
|
||||
| `LIMIT_EXCEEDED` | 400 | Resource usage limit exceeded |
|
||||
@ -619,6 +619,18 @@ Example payload for the `document.rejected` event:
|
||||
}
|
||||
```
|
||||
|
||||
## Webhook Events Testing
|
||||
|
||||
You can trigger test webhook events to test the webhook functionality. To trigger a test webhook, navigate to the [Webhooks page](/developers/webhooks) and click on the "Test Webhook" button.
|
||||
|
||||

|
||||
|
||||
This opens a dialog where you can select the event type to test.
|
||||
|
||||

|
||||
|
||||
Choose the appropriate event and click "Send Test Webhook." You’ll shortly receive a test payload from Documenso with sample data.
|
||||
|
||||
## Availability
|
||||
|
||||
Webhooks are available to individual users and teams.
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 45 KiB |
BIN
apps/documentation/public/webhook-images/test-webhooks-page.webp
Normal file
BIN
apps/documentation/public/webhook-images/test-webhooks-page.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 98 KiB |
@ -127,6 +127,16 @@ export const DocumentMoveToFolderDialog = ({
|
||||
return;
|
||||
}
|
||||
|
||||
if (error.code === AppErrorCode.UNAUTHORIZED) {
|
||||
toast({
|
||||
title: _(msg`Error`),
|
||||
description: _(msg`You are not allowed to move this document.`),
|
||||
variant: 'destructive',
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
toast({
|
||||
title: _(msg`Error`),
|
||||
description: _(msg`An error occurred while moving the document.`),
|
||||
|
||||
@ -116,8 +116,8 @@ export const FolderDeleteDialog = ({ folder, isOpen, onOpenChange }: FolderDelet
|
||||
<Alert variant="destructive">
|
||||
<AlertDescription>
|
||||
<Trans>
|
||||
This folder contains multiple items. Deleting it will also delete all items in the
|
||||
folder, including nested folders and their contents.
|
||||
This folder contains multiple items. Deleting it will remove all subfolders and move
|
||||
all nested documents and templates to the root folder.
|
||||
</Trans>
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
import { useEffect } from 'react';
|
||||
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
import type * as DialogPrimitive from '@radix-ui/react-dialog';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { z } from 'zod';
|
||||
@ -14,6 +14,7 @@ import type { TFolderWithSubfolders } from '@documenso/trpc/server/folder-router
|
||||
import { Button } from '@documenso/ui/primitives/button';
|
||||
import {
|
||||
Dialog,
|
||||
DialogClose,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
@ -40,7 +41,7 @@ import { useToast } from '@documenso/ui/primitives/use-toast';
|
||||
|
||||
import { useOptionalCurrentTeam } from '~/providers/team';
|
||||
|
||||
export type FolderSettingsDialogProps = {
|
||||
export type FolderUpdateDialogProps = {
|
||||
folder: TFolderWithSubfolders | null;
|
||||
isOpen: boolean;
|
||||
onOpenChange: (open: boolean) => void;
|
||||
@ -53,12 +54,8 @@ export const ZUpdateFolderFormSchema = z.object({
|
||||
|
||||
export type TUpdateFolderFormSchema = z.infer<typeof ZUpdateFolderFormSchema>;
|
||||
|
||||
export const FolderSettingsDialog = ({
|
||||
folder,
|
||||
isOpen,
|
||||
onOpenChange,
|
||||
}: FolderSettingsDialogProps) => {
|
||||
const { _ } = useLingui();
|
||||
export const FolderUpdateDialog = ({ folder, isOpen, onOpenChange }: FolderUpdateDialogProps) => {
|
||||
const { t } = useLingui();
|
||||
const team = useOptionalCurrentTeam();
|
||||
|
||||
const { toast } = useToast();
|
||||
@ -84,7 +81,9 @@ export const FolderSettingsDialog = ({
|
||||
}, [folder, form]);
|
||||
|
||||
const onFormSubmit = async (data: TUpdateFolderFormSchema) => {
|
||||
if (!folder) return;
|
||||
if (!folder) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await updateFolder({
|
||||
@ -96,7 +95,7 @@ export const FolderSettingsDialog = ({
|
||||
});
|
||||
|
||||
toast({
|
||||
title: _(msg`Folder updated successfully`),
|
||||
title: t`Folder updated successfully`,
|
||||
});
|
||||
|
||||
onOpenChange(false);
|
||||
@ -105,7 +104,7 @@ export const FolderSettingsDialog = ({
|
||||
|
||||
if (error.code === AppErrorCode.NOT_FOUND) {
|
||||
toast({
|
||||
title: _(msg`Folder not found`),
|
||||
title: t`Folder not found`,
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -115,8 +114,12 @@ export const FolderSettingsDialog = ({
|
||||
<Dialog open={isOpen} onOpenChange={onOpenChange}>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>Folder Settings</DialogTitle>
|
||||
<DialogDescription>Manage the settings for this folder.</DialogDescription>
|
||||
<DialogTitle>
|
||||
<Trans>Folder Settings</Trans>
|
||||
</DialogTitle>
|
||||
<DialogDescription>
|
||||
<Trans>Manage the settings for this folder.</Trans>
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
<Form {...form}>
|
||||
@ -126,7 +129,9 @@ export const FolderSettingsDialog = ({
|
||||
name="name"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Name</FormLabel>
|
||||
<FormLabel>
|
||||
<Trans>Name</Trans>
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} />
|
||||
</FormControl>
|
||||
@ -141,19 +146,25 @@ export const FolderSettingsDialog = ({
|
||||
name="visibility"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Visibility</FormLabel>
|
||||
<FormLabel>
|
||||
<Trans>Visibility</Trans>
|
||||
</FormLabel>
|
||||
<Select onValueChange={field.onChange} defaultValue={field.value}>
|
||||
<FormControl>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Select visibility" />
|
||||
<SelectValue placeholder={t`Select visibility`} />
|
||||
</SelectTrigger>
|
||||
</FormControl>
|
||||
<SelectContent>
|
||||
<SelectItem value={DocumentVisibility.EVERYONE}>Everyone</SelectItem>
|
||||
<SelectItem value={DocumentVisibility.MANAGER_AND_ABOVE}>
|
||||
Managers and above
|
||||
<SelectItem value={DocumentVisibility.EVERYONE}>
|
||||
<Trans>Everyone</Trans>
|
||||
</SelectItem>
|
||||
<SelectItem value={DocumentVisibility.MANAGER_AND_ABOVE}>
|
||||
<Trans>Managers and above</Trans>
|
||||
</SelectItem>
|
||||
<SelectItem value={DocumentVisibility.ADMIN}>
|
||||
<Trans>Admins only</Trans>
|
||||
</SelectItem>
|
||||
<SelectItem value={DocumentVisibility.ADMIN}>Admins only</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<FormMessage />
|
||||
@ -163,7 +174,15 @@ export const FolderSettingsDialog = ({
|
||||
)}
|
||||
|
||||
<DialogFooter>
|
||||
<Button type="submit">Save Changes</Button>
|
||||
<DialogClose asChild>
|
||||
<Button variant="secondary">
|
||||
<Trans>Cancel</Trans>
|
||||
</Button>
|
||||
</DialogClose>
|
||||
|
||||
<Button type="submit" loading={form.formState.isSubmitting}>
|
||||
<Trans>Update</Trans>
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</form>
|
||||
</Form>
|
||||
@ -19,6 +19,7 @@ import { IS_BILLING_ENABLED } from '@documenso/lib/constants/app';
|
||||
import { AppError } from '@documenso/lib/errors/app-error';
|
||||
import { INTERNAL_CLAIM_ID } from '@documenso/lib/types/subscription';
|
||||
import { parseMessageDescriptorMacro } from '@documenso/lib/utils/i18n';
|
||||
import { isPersonalLayout } from '@documenso/lib/utils/organisations';
|
||||
import { trpc } from '@documenso/trpc/react';
|
||||
import { ZCreateOrganisationRequestSchema } from '@documenso/trpc/server/organisation-router/create-organisation.types';
|
||||
import { cn } from '@documenso/ui/lib/utils';
|
||||
@ -46,6 +47,8 @@ import { SpinnerBox } from '@documenso/ui/primitives/spinner';
|
||||
import { Tabs, TabsList, TabsTrigger } from '@documenso/ui/primitives/tabs';
|
||||
import { useToast } from '@documenso/ui/primitives/use-toast';
|
||||
|
||||
import { IndividualPersonalLayoutCheckoutButton } from '../general/billing-plans';
|
||||
|
||||
export type OrganisationCreateDialogProps = {
|
||||
trigger?: React.ReactNode;
|
||||
} & Omit<DialogPrimitive.DialogProps, 'children'>;
|
||||
@ -59,10 +62,11 @@ export type TCreateOrganisationFormSchema = z.infer<typeof ZCreateOrganisationFo
|
||||
export const OrganisationCreateDialog = ({ trigger, ...props }: OrganisationCreateDialogProps) => {
|
||||
const { t } = useLingui();
|
||||
const { toast } = useToast();
|
||||
const { refreshSession } = useSession();
|
||||
const { refreshSession, organisations } = useSession();
|
||||
|
||||
const [searchParams] = useSearchParams();
|
||||
const updateSearchParams = useUpdateSearchParams();
|
||||
const isPersonalLayoutMode = isPersonalLayout(organisations);
|
||||
|
||||
const actionSearchParam = searchParams?.get('action');
|
||||
|
||||
@ -133,6 +137,13 @@ export const OrganisationCreateDialog = ({ trigger, ...props }: OrganisationCrea
|
||||
form.reset();
|
||||
}, [open, form]);
|
||||
|
||||
const isIndividualPlan = (priceId: string) => {
|
||||
return (
|
||||
plansData?.plans[INTERNAL_CLAIM_ID.INDIVIDUAL]?.monthlyPrice?.id === priceId ||
|
||||
plansData?.plans[INTERNAL_CLAIM_ID.INDIVIDUAL]?.yearlyPrice?.id === priceId
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
{...props}
|
||||
@ -177,9 +188,15 @@ export const OrganisationCreateDialog = ({ trigger, ...props }: OrganisationCrea
|
||||
<Trans>Cancel</Trans>
|
||||
</Button>
|
||||
|
||||
<Button type="submit" onClick={() => setStep('create')}>
|
||||
<Trans>Continue</Trans>
|
||||
</Button>
|
||||
{isIndividualPlan(selectedPriceId) && isPersonalLayoutMode ? (
|
||||
<IndividualPersonalLayoutCheckoutButton priceId={selectedPriceId}>
|
||||
<Trans>Checkout</Trans>
|
||||
</IndividualPersonalLayoutCheckoutButton>
|
||||
) : (
|
||||
<Button type="submit" onClick={() => setStep('create')}>
|
||||
<Trans>Continue</Trans>
|
||||
</Button>
|
||||
)}
|
||||
</DialogFooter>
|
||||
</fieldset>
|
||||
</>
|
||||
@ -306,7 +323,11 @@ const BillingPlanForm = ({
|
||||
}, [value]);
|
||||
|
||||
const onBillingPeriodChange = (billingPeriod: 'monthlyPrice' | 'yearlyPrice') => {
|
||||
const plan = dynamicPlans.find((plan) => plan[billingPeriod]?.id === value);
|
||||
const plan = dynamicPlans.find(
|
||||
(plan) =>
|
||||
// Purposely using the opposite billing period to get the correct plan.
|
||||
plan[billingPeriod === 'monthlyPrice' ? 'yearlyPrice' : 'monthlyPrice']?.id === value,
|
||||
);
|
||||
|
||||
setBillingPeriod(billingPeriod);
|
||||
|
||||
|
||||
@ -13,7 +13,7 @@ import { z } from 'zod';
|
||||
|
||||
import { downloadFile } from '@documenso/lib/client-only/download-file';
|
||||
import { useCurrentOrganisation } from '@documenso/lib/client-only/providers/organisation';
|
||||
import { IS_BILLING_ENABLED } from '@documenso/lib/constants/app';
|
||||
import { IS_BILLING_ENABLED, SUPPORT_EMAIL } from '@documenso/lib/constants/app';
|
||||
import { ORGANISATION_MEMBER_ROLE_HIERARCHY } from '@documenso/lib/constants/organisations';
|
||||
import { ORGANISATION_MEMBER_ROLE_MAP } from '@documenso/lib/constants/organisations-translations';
|
||||
import { INTERNAL_CLAIM_ID } from '@documenso/lib/types/subscription';
|
||||
@ -303,8 +303,8 @@ export const OrganisationMemberInviteDialog = ({
|
||||
<AlertDescription>
|
||||
<Trans>
|
||||
Your plan does not support inviting members. Please upgrade or your plan or
|
||||
contact sales at <a href="mailto:support@documenso.com">support@documenso.com</a>{' '}
|
||||
if you would like to discuss your options.
|
||||
contact sales at <a href={`mailto:${SUPPORT_EMAIL}`}>{SUPPORT_EMAIL}</a> if you
|
||||
would like to discuss your options.
|
||||
</Trans>
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
|
||||
@ -12,7 +12,11 @@ import type { z } from 'zod';
|
||||
import { useUpdateSearchParams } from '@documenso/lib/client-only/hooks/use-update-search-params';
|
||||
import { useCurrentOrganisation } from '@documenso/lib/client-only/providers/organisation';
|
||||
import { useSession } from '@documenso/lib/client-only/providers/session';
|
||||
import { IS_BILLING_ENABLED, NEXT_PUBLIC_WEBAPP_URL } from '@documenso/lib/constants/app';
|
||||
import {
|
||||
IS_BILLING_ENABLED,
|
||||
NEXT_PUBLIC_WEBAPP_URL,
|
||||
SUPPORT_EMAIL,
|
||||
} from '@documenso/lib/constants/app';
|
||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
import { trpc } from '@documenso/trpc/react';
|
||||
import { ZCreateTeamRequestSchema } from '@documenso/trpc/server/team-router/create-team.types';
|
||||
@ -193,8 +197,8 @@ export const TeamCreateDialog = ({ trigger, onCreated, ...props }: TeamCreateDia
|
||||
<AlertDescription className="mt-0">
|
||||
<Trans>
|
||||
You have reached the maximum number of teams for your plan. Please contact sales
|
||||
at <a href="mailto:support@documenso.com">support@documenso.com</a> if you would
|
||||
like to adjust your plan.
|
||||
at <a href={`mailto:${SUPPORT_EMAIL}`}>{SUPPORT_EMAIL}</a> if you would like to
|
||||
adjust your plan.
|
||||
</Trans>
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
|
||||
170
apps/remix/app/components/dialogs/webhook-test-dialog.tsx
Normal file
170
apps/remix/app/components/dialogs/webhook-test-dialog.tsx
Normal file
@ -0,0 +1,170 @@
|
||||
import { useState } from 'react';
|
||||
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
import type { Webhook } from '@prisma/client';
|
||||
import { WebhookTriggerEvents } from '@prisma/client';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { toFriendlyWebhookEventName } from '@documenso/lib/universal/webhook/to-friendly-webhook-event-name';
|
||||
import { trpc } from '@documenso/trpc/react';
|
||||
import { Button } from '@documenso/ui/primitives/button';
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from '@documenso/ui/primitives/dialog';
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from '@documenso/ui/primitives/form/form';
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from '@documenso/ui/primitives/select';
|
||||
import { useToast } from '@documenso/ui/primitives/use-toast';
|
||||
|
||||
import { useCurrentTeam } from '~/providers/team';
|
||||
|
||||
export type WebhookTestDialogProps = {
|
||||
webhook: Pick<Webhook, 'id' | 'webhookUrl' | 'eventTriggers'>;
|
||||
children: React.ReactNode;
|
||||
};
|
||||
|
||||
const ZTestWebhookFormSchema = z.object({
|
||||
event: z.nativeEnum(WebhookTriggerEvents),
|
||||
});
|
||||
|
||||
type TTestWebhookFormSchema = z.infer<typeof ZTestWebhookFormSchema>;
|
||||
|
||||
export const WebhookTestDialog = ({ webhook, children }: WebhookTestDialogProps) => {
|
||||
const { _ } = useLingui();
|
||||
const { toast } = useToast();
|
||||
|
||||
const team = useCurrentTeam();
|
||||
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
const { mutateAsync: testWebhook } = trpc.webhook.testWebhook.useMutation();
|
||||
|
||||
const form = useForm<TTestWebhookFormSchema>({
|
||||
resolver: zodResolver(ZTestWebhookFormSchema),
|
||||
defaultValues: {
|
||||
event: webhook.eventTriggers[0],
|
||||
},
|
||||
});
|
||||
|
||||
const onSubmit = async ({ event }: TTestWebhookFormSchema) => {
|
||||
try {
|
||||
await testWebhook({
|
||||
id: webhook.id,
|
||||
event,
|
||||
teamId: team.id,
|
||||
});
|
||||
|
||||
toast({
|
||||
title: _(msg`Test webhook sent`),
|
||||
description: _(msg`The test webhook has been successfully sent to your endpoint.`),
|
||||
duration: 5000,
|
||||
});
|
||||
|
||||
setOpen(false);
|
||||
} catch (error) {
|
||||
toast({
|
||||
title: _(msg`Test webhook failed`),
|
||||
description: _(
|
||||
msg`We encountered an error while sending the test webhook. Please check your endpoint and try again.`,
|
||||
),
|
||||
variant: 'destructive',
|
||||
duration: 5000,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={(value) => !form.formState.isSubmitting && setOpen(value)}>
|
||||
<DialogTrigger asChild>{children}</DialogTrigger>
|
||||
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>
|
||||
<Trans>Test Webhook</Trans>
|
||||
</DialogTitle>
|
||||
|
||||
<DialogDescription>
|
||||
<Trans>
|
||||
Send a test webhook with sample data to verify your integration is working correctly.
|
||||
</Trans>
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
<Form {...form}>
|
||||
<form onSubmit={form.handleSubmit(onSubmit)}>
|
||||
<fieldset
|
||||
className="flex h-full flex-col space-y-4"
|
||||
disabled={form.formState.isSubmitting}
|
||||
>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="event"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>
|
||||
<Trans>Event Type</Trans>
|
||||
</FormLabel>
|
||||
<Select onValueChange={field.onChange} value={field.value}>
|
||||
<FormControl>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Select an event type" />
|
||||
</SelectTrigger>
|
||||
</FormControl>
|
||||
<SelectContent>
|
||||
{webhook.eventTriggers.map((event) => (
|
||||
<SelectItem key={event} value={event}>
|
||||
{toFriendlyWebhookEventName(event)}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<div className="rounded-md border p-4">
|
||||
<h4 className="mb-2 text-sm font-medium">
|
||||
<Trans>Webhook URL</Trans>
|
||||
</h4>
|
||||
<p className="text-muted-foreground break-all text-sm">{webhook.webhookUrl}</p>
|
||||
</div>
|
||||
|
||||
<DialogFooter>
|
||||
<Button type="button" variant="secondary" onClick={() => setOpen(false)}>
|
||||
<Trans>Cancel</Trans>
|
||||
</Button>
|
||||
|
||||
<Button type="submit" loading={form.formState.isSubmitting}>
|
||||
<Trans>Send Test Webhook</Trans>
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</fieldset>
|
||||
</form>
|
||||
</Form>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
@ -10,7 +10,9 @@ import { useForm } from 'react-hook-form';
|
||||
import type { InternalClaimPlans } from '@documenso/ee/server-only/stripe/get-internal-claim-plans';
|
||||
import { useIsMounted } from '@documenso/lib/client-only/hooks/use-is-mounted';
|
||||
import { useCurrentOrganisation } from '@documenso/lib/client-only/providers/organisation';
|
||||
import { useSession } from '@documenso/lib/client-only/providers/session';
|
||||
import { INTERNAL_CLAIM_ID } from '@documenso/lib/types/subscription';
|
||||
import { isPersonalLayout } from '@documenso/lib/utils/organisations';
|
||||
import { trpc } from '@documenso/trpc/react';
|
||||
import { Button } from '@documenso/ui/primitives/button';
|
||||
import { Card, CardContent, CardTitle } from '@documenso/ui/primitives/card';
|
||||
@ -49,8 +51,12 @@ export type BillingPlansProps = {
|
||||
export const BillingPlans = ({ plans }: BillingPlansProps) => {
|
||||
const isMounted = useIsMounted();
|
||||
|
||||
const { organisations } = useSession();
|
||||
|
||||
const [interval, setInterval] = useState<'monthlyPrice' | 'yearlyPrice'>('yearlyPrice');
|
||||
|
||||
const isPersonalLayoutMode = isPersonalLayout(organisations);
|
||||
|
||||
const pricesToDisplay = useMemo(() => {
|
||||
const prices = [];
|
||||
|
||||
@ -126,12 +132,18 @@ export const BillingPlans = ({ plans }: BillingPlansProps) => {
|
||||
|
||||
<div className="flex-1" />
|
||||
|
||||
<BillingDialog
|
||||
priceId={price.id}
|
||||
planName={price.product.name}
|
||||
memberCount={price.memberCount}
|
||||
claim={price.claim}
|
||||
/>
|
||||
{isPersonalLayoutMode && price.claim === INTERNAL_CLAIM_ID.INDIVIDUAL ? (
|
||||
<IndividualPersonalLayoutCheckoutButton priceId={price.id}>
|
||||
<Trans>Subscribe</Trans>
|
||||
</IndividualPersonalLayoutCheckoutButton>
|
||||
) : (
|
||||
<BillingDialog
|
||||
priceId={price.id}
|
||||
planName={price.product.name}
|
||||
memberCount={price.memberCount}
|
||||
claim={price.claim}
|
||||
/>
|
||||
)}
|
||||
</CardContent>
|
||||
</MotionCard>
|
||||
))}
|
||||
@ -315,3 +327,48 @@ const BillingDialog = ({
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Custom checkout button for individual organisations in personal layout mode.
|
||||
*
|
||||
* This is so they don't create an additional organisation which is not needed since
|
||||
* it will clutter up the UI for them with unnecessary organisations.
|
||||
*/
|
||||
export const IndividualPersonalLayoutCheckoutButton = ({
|
||||
priceId,
|
||||
children,
|
||||
}: {
|
||||
priceId: string;
|
||||
children: React.ReactNode;
|
||||
}) => {
|
||||
const { t } = useLingui();
|
||||
const { toast } = useToast();
|
||||
const { organisations } = useSession();
|
||||
|
||||
const { mutateAsync: createSubscription, isPending } =
|
||||
trpc.billing.subscription.create.useMutation();
|
||||
|
||||
const onSubscribeClick = async () => {
|
||||
try {
|
||||
const createSubscriptionResponse = await createSubscription({
|
||||
organisationId: organisations[0].id,
|
||||
priceId,
|
||||
isPersonalLayoutMode: true,
|
||||
});
|
||||
|
||||
window.location.href = createSubscriptionResponse.redirectUrl;
|
||||
} catch (_err) {
|
||||
toast({
|
||||
title: t`Something went wrong`,
|
||||
description: t`An error occurred while trying to create a checkout session.`,
|
||||
variant: 'destructive',
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Button loading={isPending} onClick={() => void onSubscribeClick()}>
|
||||
{children}
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
|
||||
@ -17,6 +17,7 @@ import type {
|
||||
TSignFieldWithTokenMutationSchema,
|
||||
} from '@documenso/trpc/server/field-router/schema';
|
||||
import { FieldToolTip } from '@documenso/ui/components/field/field-tooltip';
|
||||
import { cn } from '@documenso/ui/lib/utils';
|
||||
import { Checkbox } from '@documenso/ui/primitives/checkbox';
|
||||
import { checkboxValidationSigns } from '@documenso/ui/primitives/document-flow/field-items-advanced-settings/constants';
|
||||
import { Label } from '@documenso/ui/primitives/label';
|
||||
@ -276,7 +277,14 @@ export const DocumentSigningCheckboxField = ({
|
||||
{validationSign?.label} {checkboxValidationLength}
|
||||
</FieldToolTip>
|
||||
)}
|
||||
<div className="z-50 my-0.5 flex flex-col gap-y-1">
|
||||
<div
|
||||
className={cn(
|
||||
'z-50 my-0.5 flex gap-1',
|
||||
parsedFieldMeta.direction === 'horizontal'
|
||||
? 'flex-row flex-wrap'
|
||||
: 'flex-col gap-y-1',
|
||||
)}
|
||||
>
|
||||
{values?.map((item: { id: number; value: string; checked: boolean }, index: number) => {
|
||||
const itemValue = item.value || `empty-value-${item.id}`;
|
||||
|
||||
@ -305,7 +313,12 @@ export const DocumentSigningCheckboxField = ({
|
||||
)}
|
||||
|
||||
{field.inserted && (
|
||||
<div className="my-0.5 flex flex-col gap-y-1">
|
||||
<div
|
||||
className={cn(
|
||||
'my-0.5 flex gap-1',
|
||||
parsedFieldMeta.direction === 'horizontal' ? 'flex-row flex-wrap' : 'flex-col gap-y-1',
|
||||
)}
|
||||
>
|
||||
{values?.map((item: { id: number; value: string; checked: boolean }, index: number) => {
|
||||
const itemValue = item.value || `empty-value-${item.id}`;
|
||||
|
||||
|
||||
@ -54,7 +54,7 @@ export const DocumentSigningNumberField = ({
|
||||
const { toast } = useToast();
|
||||
const { revalidate } = useRevalidator();
|
||||
|
||||
const { recipient, targetSigner, isAssistantMode } = useDocumentSigningRecipientContext();
|
||||
const { recipient, isAssistantMode } = useDocumentSigningRecipientContext();
|
||||
|
||||
const [showNumberModal, setShowNumberModal] = useState(false);
|
||||
|
||||
@ -62,8 +62,8 @@ export const DocumentSigningNumberField = ({
|
||||
const parsedFieldMeta = safeFieldMeta.success ? safeFieldMeta.data : null;
|
||||
|
||||
const defaultValue = parsedFieldMeta?.value;
|
||||
const [localNumber, setLocalNumber] = useState(
|
||||
parsedFieldMeta?.value ? String(parsedFieldMeta.value) : '0',
|
||||
const [localNumber, setLocalNumber] = useState(() =>
|
||||
parsedFieldMeta?.value ? String(parsedFieldMeta.value) : '',
|
||||
);
|
||||
|
||||
const initialErrors: ValidationErrors = {
|
||||
@ -213,16 +213,13 @@ export const DocumentSigningNumberField = ({
|
||||
|
||||
useEffect(() => {
|
||||
if (!showNumberModal) {
|
||||
setLocalNumber(parsedFieldMeta?.value ? String(parsedFieldMeta.value) : '0');
|
||||
setLocalNumber(parsedFieldMeta?.value ? String(parsedFieldMeta.value) : '');
|
||||
setErrors(initialErrors);
|
||||
}
|
||||
}, [showNumberModal]);
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
(!field.inserted && defaultValue && localNumber) ||
|
||||
(!field.inserted && parsedFieldMeta?.readOnly && defaultValue)
|
||||
) {
|
||||
if (!field.inserted && defaultValue) {
|
||||
void executeActionAuthProcedure({
|
||||
onReauthFormSubmit: async (authOptions) => await onSign(authOptions),
|
||||
actionTarget: field.type,
|
||||
@ -318,7 +315,7 @@ export const DocumentSigningNumberField = ({
|
||||
variant="secondary"
|
||||
onClick={() => {
|
||||
setShowNumberModal(false);
|
||||
setLocalNumber('');
|
||||
setLocalNumber(parsedFieldMeta?.value ? String(parsedFieldMeta.value) : '');
|
||||
}}
|
||||
>
|
||||
<Trans>Cancel</Trans>
|
||||
|
||||
@ -72,9 +72,11 @@ export const DocumentSigningPageView = ({
|
||||
}
|
||||
|
||||
const selectedSigner = allRecipients?.find((r) => r.id === selectedSignerId);
|
||||
const targetSigner =
|
||||
recipient.role === RecipientRole.ASSISTANT && selectedSigner ? selectedSigner : null;
|
||||
|
||||
return (
|
||||
<DocumentSigningRecipientProvider recipient={recipient} targetSigner={selectedSigner ?? null}>
|
||||
<DocumentSigningRecipientProvider recipient={recipient} targetSigner={targetSigner}>
|
||||
<div className="mx-auto w-full max-w-screen-xl">
|
||||
<h1
|
||||
className="mt-4 block max-w-[20rem] truncate text-2xl font-semibold md:max-w-[30rem] md:text-3xl"
|
||||
|
||||
@ -38,11 +38,6 @@ export const DocumentSigningRecipientProvider = ({
|
||||
recipient,
|
||||
targetSigner = null,
|
||||
}: DocumentSigningRecipientProviderProps) => {
|
||||
// console.log({
|
||||
// recipient,
|
||||
// targetSigner,
|
||||
// isAssistantMode: !!targetSigner,
|
||||
// });
|
||||
return (
|
||||
<DocumentSigningRecipientContext.Provider
|
||||
value={{
|
||||
|
||||
@ -13,7 +13,7 @@ import { Skeleton } from '@documenso/ui/primitives/skeleton';
|
||||
import { FolderCreateDialog } from '~/components/dialogs/folder-create-dialog';
|
||||
import { FolderDeleteDialog } from '~/components/dialogs/folder-delete-dialog';
|
||||
import { FolderMoveDialog } from '~/components/dialogs/folder-move-dialog';
|
||||
import { FolderSettingsDialog } from '~/components/dialogs/folder-settings-dialog';
|
||||
import { FolderUpdateDialog } from '~/components/dialogs/folder-update-dialog';
|
||||
import { TemplateCreateDialog } from '~/components/dialogs/template-create-dialog';
|
||||
import { DocumentUploadDropzone } from '~/components/general/document/document-upload';
|
||||
import { FolderCard, FolderCardEmpty } from '~/components/general/folder/folder-card';
|
||||
@ -219,7 +219,7 @@ export const FolderGrid = ({ type, parentId }: FolderGridProps) => {
|
||||
}}
|
||||
/>
|
||||
|
||||
<FolderSettingsDialog
|
||||
<FolderUpdateDialog
|
||||
folder={folderToSettings}
|
||||
isOpen={isSettingsFolderOpen}
|
||||
onOpenChange={(open) => {
|
||||
|
||||
@ -5,18 +5,23 @@ import { useLingui } from '@lingui/react';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
import { SubscriptionStatus } from '@prisma/client';
|
||||
import { AlertTriangle } from 'lucide-react';
|
||||
import { Link } from 'react-router';
|
||||
import { match } from 'ts-pattern';
|
||||
|
||||
import { useOptionalCurrentOrganisation } from '@documenso/lib/client-only/providers/organisation';
|
||||
import { SUPPORT_EMAIL } from '@documenso/lib/constants/app';
|
||||
import { canExecuteOrganisationAction } from '@documenso/lib/utils/organisations';
|
||||
import { trpc } from '@documenso/trpc/react';
|
||||
import { cn } from '@documenso/ui/lib/utils';
|
||||
import { Alert, AlertDescription } from '@documenso/ui/primitives/alert';
|
||||
import { Button } from '@documenso/ui/primitives/button';
|
||||
import {
|
||||
Dialog,
|
||||
DialogClose,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from '@documenso/ui/primitives/dialog';
|
||||
import { useToast } from '@documenso/ui/primitives/use-toast';
|
||||
@ -86,7 +91,7 @@ export const OrganisationBillingBanner = () => {
|
||||
className={cn({
|
||||
'text-yellow-900 hover:bg-yellow-100 dark:hover:bg-yellow-500':
|
||||
subscriptionStatus === SubscriptionStatus.PAST_DUE,
|
||||
'text-destructive-foreground hover:bg-destructive-foreground hover:text-white':
|
||||
'text-destructive-foreground hover:bg-destructive hover:text-white':
|
||||
subscriptionStatus === SubscriptionStatus.INACTIVE,
|
||||
})}
|
||||
disabled={isPending}
|
||||
@ -99,38 +104,78 @@ export const OrganisationBillingBanner = () => {
|
||||
</div>
|
||||
|
||||
<Dialog open={isOpen} onOpenChange={(value) => !isPending && setIsOpen(value)}>
|
||||
<DialogContent>
|
||||
<DialogTitle>
|
||||
<Trans>Payment overdue</Trans>
|
||||
</DialogTitle>
|
||||
{match(subscriptionStatus)
|
||||
.with(SubscriptionStatus.PAST_DUE, () => (
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>
|
||||
<Trans>Payment overdue</Trans>
|
||||
</DialogTitle>
|
||||
|
||||
{match(subscriptionStatus)
|
||||
.with(SubscriptionStatus.PAST_DUE, () => (
|
||||
<DialogDescription>
|
||||
<Trans>
|
||||
Your payment for teams is overdue. Please settle the payment to avoid any service
|
||||
disruptions.
|
||||
</Trans>
|
||||
</DialogDescription>
|
||||
))
|
||||
.with(SubscriptionStatus.INACTIVE, () => (
|
||||
<DialogDescription>
|
||||
<Trans>
|
||||
Due to an unpaid invoice, your team has been restricted. Please settle the payment
|
||||
to restore full access to your team.
|
||||
</Trans>
|
||||
</DialogDescription>
|
||||
))
|
||||
.otherwise(() => null)}
|
||||
<DialogDescription>
|
||||
<Trans>
|
||||
Your payment is overdue. Please settle the payment to avoid any service
|
||||
disruptions.
|
||||
</Trans>
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
{canExecuteOrganisationAction('MANAGE_BILLING', organisation.currentOrganisationRole) && (
|
||||
<DialogFooter>
|
||||
<Button loading={isPending} onClick={async () => handleCreatePortal(organisation.id)}>
|
||||
<Trans>Resolve payment</Trans>
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
)}
|
||||
</DialogContent>
|
||||
{canExecuteOrganisationAction(
|
||||
'MANAGE_BILLING',
|
||||
organisation.currentOrganisationRole,
|
||||
) && (
|
||||
<DialogFooter>
|
||||
<Button
|
||||
loading={isPending}
|
||||
onClick={async () => handleCreatePortal(organisation.id)}
|
||||
>
|
||||
<Trans>Resolve payment</Trans>
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
)}
|
||||
</DialogContent>
|
||||
))
|
||||
.with(SubscriptionStatus.INACTIVE, () => (
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>
|
||||
<Trans>Subscription invalid</Trans>
|
||||
</DialogTitle>
|
||||
|
||||
<DialogDescription>
|
||||
<Trans>
|
||||
Your plan is no longer valid. Please subscribe to a new plan to continue using
|
||||
Documenso.
|
||||
</Trans>
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
<Alert variant="neutral">
|
||||
<AlertDescription>
|
||||
<Trans>
|
||||
If there is any issue with your subscription, please contact us at{' '}
|
||||
<a href={`mailto:${SUPPORT_EMAIL}`}>{SUPPORT_EMAIL}</a>.
|
||||
</Trans>
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
|
||||
{canExecuteOrganisationAction(
|
||||
'MANAGE_BILLING',
|
||||
organisation.currentOrganisationRole,
|
||||
) && (
|
||||
<DialogFooter>
|
||||
<DialogClose asChild>
|
||||
<Button asChild>
|
||||
<Link to={`/o/${organisation.url}/settings/billing`}>
|
||||
<Trans>Manage Billing</Trans>
|
||||
</Link>
|
||||
</Button>
|
||||
</DialogClose>
|
||||
</DialogFooter>
|
||||
)}
|
||||
</DialogContent>
|
||||
))
|
||||
.otherwise(() => null)}
|
||||
</Dialog>
|
||||
</>
|
||||
);
|
||||
|
||||
@ -188,7 +188,7 @@ export const DocumentsTableActionDropdown = ({
|
||||
<Trans>Duplicate</Trans>
|
||||
</DropdownMenuItem>
|
||||
|
||||
{onMoveDocument && (
|
||||
{onMoveDocument && canManageDocument && (
|
||||
<DropdownMenuItem onClick={onMoveDocument} onSelect={(e) => e.preventDefault()}>
|
||||
<FolderInput className="mr-2 h-4 w-4" />
|
||||
<Trans>Move to Folder</Trans>
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import { useLingui } from '@lingui/react';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
import { SubscriptionStatus } from '@prisma/client';
|
||||
import { Loader } from 'lucide-react';
|
||||
import type Stripe from 'stripe';
|
||||
import { match } from 'ts-pattern';
|
||||
@ -134,7 +135,9 @@ export default function TeamsSettingBillingPage() {
|
||||
|
||||
<hr className="my-4" />
|
||||
|
||||
{!subscription && canManageBilling && <BillingPlans plans={plans} />}
|
||||
{(!subscription ||
|
||||
subscription.organisationSubscription.status === SubscriptionStatus.INACTIVE) &&
|
||||
canManageBilling && <BillingPlans plans={plans} />}
|
||||
|
||||
<section className="mt-6">
|
||||
<OrganisationBillingInvoicesTable
|
||||
|
||||
@ -14,7 +14,7 @@ import { Input } from '@documenso/ui/primitives/input';
|
||||
import { FolderCreateDialog } from '~/components/dialogs/folder-create-dialog';
|
||||
import { FolderDeleteDialog } from '~/components/dialogs/folder-delete-dialog';
|
||||
import { FolderMoveDialog } from '~/components/dialogs/folder-move-dialog';
|
||||
import { FolderSettingsDialog } from '~/components/dialogs/folder-settings-dialog';
|
||||
import { FolderUpdateDialog } from '~/components/dialogs/folder-update-dialog';
|
||||
import { FolderCard } from '~/components/general/folder/folder-card';
|
||||
import { useCurrentTeam } from '~/providers/team';
|
||||
import { appMetaTags } from '~/utils/meta';
|
||||
@ -177,7 +177,7 @@ export default function DocumentsFoldersPage() {
|
||||
}}
|
||||
/>
|
||||
|
||||
<FolderSettingsDialog
|
||||
<FolderUpdateDialog
|
||||
folder={folderToSettings}
|
||||
isOpen={isSettingsFolderOpen}
|
||||
onOpenChange={(open) => {
|
||||
|
||||
@ -2,13 +2,14 @@ import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
import { Loader } from 'lucide-react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { useRevalidator } from 'react-router';
|
||||
import { Link } from 'react-router';
|
||||
import type { z } from 'zod';
|
||||
|
||||
import { trpc } from '@documenso/trpc/react';
|
||||
import { ZEditWebhookRequestSchema } from '@documenso/trpc/server/webhook-router/schema';
|
||||
import { Alert, AlertDescription, AlertTitle } from '@documenso/ui/primitives/alert';
|
||||
import { Button } from '@documenso/ui/primitives/button';
|
||||
import {
|
||||
Form,
|
||||
@ -21,9 +22,12 @@ import {
|
||||
} from '@documenso/ui/primitives/form/form';
|
||||
import { Input } from '@documenso/ui/primitives/input';
|
||||
import { PasswordInput } from '@documenso/ui/primitives/password-input';
|
||||
import { SpinnerBox } from '@documenso/ui/primitives/spinner';
|
||||
import { Switch } from '@documenso/ui/primitives/switch';
|
||||
import { useToast } from '@documenso/ui/primitives/use-toast';
|
||||
|
||||
import { WebhookTestDialog } from '~/components/dialogs/webhook-test-dialog';
|
||||
import { GenericErrorLayout } from '~/components/general/generic-error-layout';
|
||||
import { SettingsHeader } from '~/components/general/settings-header';
|
||||
import { WebhookMultiSelectCombobox } from '~/components/general/webhook-multiselect-combobox';
|
||||
import { useCurrentTeam } from '~/providers/team';
|
||||
@ -92,25 +96,45 @@ export default function WebhookPage({ params }: Route.ComponentProps) {
|
||||
}
|
||||
};
|
||||
|
||||
if (isLoading) {
|
||||
return <SpinnerBox className="py-32" />;
|
||||
}
|
||||
|
||||
// Todo: Update UI, currently out of place.
|
||||
if (!webhook) {
|
||||
return (
|
||||
<GenericErrorLayout
|
||||
errorCode={404}
|
||||
errorCodeMap={{
|
||||
404: {
|
||||
heading: msg`Webhook not found`,
|
||||
subHeading: msg`404 Webhook not found`,
|
||||
message: msg`The webhook you are looking for may have been removed, renamed or may have never
|
||||
existed.`,
|
||||
},
|
||||
}}
|
||||
primaryButton={
|
||||
<Button asChild>
|
||||
<Link to={`/t/${team.url}/settings/webhooks`}>
|
||||
<Trans>Go back</Trans>
|
||||
</Link>
|
||||
</Button>
|
||||
}
|
||||
secondaryButton={null}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="max-w-2xl">
|
||||
<SettingsHeader
|
||||
title={_(msg`Edit webhook`)}
|
||||
subtitle={_(msg`On this page, you can edit the webhook and its settings.`)}
|
||||
/>
|
||||
|
||||
{isLoading && (
|
||||
<div className="absolute inset-0 z-50 flex items-center justify-center bg-white/50">
|
||||
<Loader className="h-8 w-8 animate-spin text-gray-500" />
|
||||
</div>
|
||||
)}
|
||||
|
||||
<Form {...form}>
|
||||
<form onSubmit={form.handleSubmit(onSubmit)}>
|
||||
<fieldset
|
||||
className="flex h-full max-w-xl flex-col gap-y-6"
|
||||
disabled={form.formState.isSubmitting}
|
||||
>
|
||||
<fieldset className="flex h-full flex-col gap-y-6" disabled={form.formState.isSubmitting}>
|
||||
<div className="flex flex-col-reverse gap-4 md:flex-row">
|
||||
<FormField
|
||||
control={form.control}
|
||||
@ -203,7 +227,7 @@ export default function WebhookPage({ params }: Route.ComponentProps) {
|
||||
)}
|
||||
/>
|
||||
|
||||
<div className="mt-4">
|
||||
<div className="flex justify-end gap-4">
|
||||
<Button type="submit" loading={form.formState.isSubmitting}>
|
||||
<Trans>Update webhook</Trans>
|
||||
</Button>
|
||||
@ -211,6 +235,30 @@ export default function WebhookPage({ params }: Route.ComponentProps) {
|
||||
</fieldset>
|
||||
</form>
|
||||
</Form>
|
||||
|
||||
<Alert
|
||||
className="mt-6 flex flex-col items-center justify-between gap-4 p-6 md:flex-row"
|
||||
variant="neutral"
|
||||
>
|
||||
<div>
|
||||
<AlertTitle>
|
||||
<Trans>Test Webhook</Trans>
|
||||
</AlertTitle>
|
||||
<AlertDescription className="mr-2">
|
||||
<Trans>
|
||||
Send a test webhook with sample data to verify your integration is working correctly.
|
||||
</Trans>
|
||||
</AlertDescription>
|
||||
</div>
|
||||
|
||||
<div className="flex-shrink-0">
|
||||
<WebhookTestDialog webhook={webhook}>
|
||||
<Button variant="outline" disabled={!webhook.enabled}>
|
||||
<Trans>Test Webhook</Trans>
|
||||
</Button>
|
||||
</WebhookTestDialog>
|
||||
</div>
|
||||
</Alert>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@ -54,7 +54,7 @@ export default function WebhookPage() {
|
||||
</div>
|
||||
)}
|
||||
{webhooks && webhooks.length > 0 && (
|
||||
<div className="mt-4 flex max-w-xl flex-col gap-y-4">
|
||||
<div className="mt-4 flex max-w-2xl flex-col gap-y-4">
|
||||
{webhooks?.map((webhook) => (
|
||||
<div
|
||||
key={webhook.id}
|
||||
|
||||
@ -14,7 +14,7 @@ import { Input } from '@documenso/ui/primitives/input';
|
||||
import { FolderCreateDialog } from '~/components/dialogs/folder-create-dialog';
|
||||
import { FolderDeleteDialog } from '~/components/dialogs/folder-delete-dialog';
|
||||
import { FolderMoveDialog } from '~/components/dialogs/folder-move-dialog';
|
||||
import { FolderSettingsDialog } from '~/components/dialogs/folder-settings-dialog';
|
||||
import { FolderUpdateDialog } from '~/components/dialogs/folder-update-dialog';
|
||||
import { FolderCard } from '~/components/general/folder/folder-card';
|
||||
import { useCurrentTeam } from '~/providers/team';
|
||||
import { appMetaTags } from '~/utils/meta';
|
||||
@ -177,7 +177,7 @@ export default function TemplatesFoldersPage() {
|
||||
}}
|
||||
/>
|
||||
|
||||
<FolderSettingsDialog
|
||||
<FolderUpdateDialog
|
||||
folder={folderToSettings}
|
||||
isOpen={isSettingsFolderOpen}
|
||||
onOpenChange={(open: boolean) => {
|
||||
|
||||
@ -1,10 +1,9 @@
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
import { Link } from 'react-router';
|
||||
|
||||
import { SUPPORT_EMAIL } from '@documenso/lib/constants/app';
|
||||
import { Button } from '@documenso/ui/primitives/button';
|
||||
|
||||
const SUPPORT_EMAIL = 'support@documenso.com';
|
||||
|
||||
export default function SignatureDisclosure() {
|
||||
return (
|
||||
<div>
|
||||
|
||||
@ -101,5 +101,5 @@
|
||||
"vite-plugin-babel-macros": "^1.0.6",
|
||||
"vite-tsconfig-paths": "^5.1.4"
|
||||
},
|
||||
"version": "1.12.2-rc.0"
|
||||
"version": "1.12.2-rc.2"
|
||||
}
|
||||
|
||||
6
package-lock.json
generated
6
package-lock.json
generated
@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@documenso/root",
|
||||
"version": "1.12.2-rc.0",
|
||||
"version": "1.12.2-rc.2",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@documenso/root",
|
||||
"version": "1.12.2-rc.0",
|
||||
"version": "1.12.2-rc.2",
|
||||
"workspaces": [
|
||||
"apps/*",
|
||||
"packages/*"
|
||||
@ -89,7 +89,7 @@
|
||||
},
|
||||
"apps/remix": {
|
||||
"name": "@documenso/remix",
|
||||
"version": "1.12.2-rc.0",
|
||||
"version": "1.12.2-rc.2",
|
||||
"dependencies": {
|
||||
"@documenso/api": "*",
|
||||
"@documenso/assets": "*",
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"private": true,
|
||||
"version": "1.12.2-rc.0",
|
||||
"version": "1.12.2-rc.2",
|
||||
"scripts": {
|
||||
"build": "turbo run build",
|
||||
"dev": "turbo run dev --filter=@documenso/remix",
|
||||
|
||||
@ -1,5 +1,10 @@
|
||||
import { initContract } from '@ts-rest/core';
|
||||
|
||||
import {
|
||||
ZCreateTemplateV2RequestSchema,
|
||||
ZCreateTemplateV2ResponseSchema,
|
||||
} from '@documenso/trpc/server/template-router/schema';
|
||||
|
||||
import {
|
||||
ZAuthorizationHeadersSchema,
|
||||
ZCreateDocumentFromTemplateMutationResponseSchema,
|
||||
@ -87,6 +92,18 @@ export const ApiContractV1 = c.router(
|
||||
summary: 'Upload a new document and get a presigned URL',
|
||||
},
|
||||
|
||||
createTemplate: {
|
||||
method: 'POST',
|
||||
path: '/api/v1/templates',
|
||||
body: ZCreateTemplateV2RequestSchema,
|
||||
responses: {
|
||||
200: ZCreateTemplateV2ResponseSchema,
|
||||
401: ZUnsuccessfulResponseSchema,
|
||||
404: ZUnsuccessfulResponseSchema,
|
||||
},
|
||||
summary: 'Create a new template and get a presigned URL',
|
||||
},
|
||||
|
||||
deleteTemplate: {
|
||||
method: 'DELETE',
|
||||
path: '/api/v1/templates/:id',
|
||||
|
||||
@ -30,6 +30,7 @@ import { setDocumentRecipients } from '@documenso/lib/server-only/recipient/set-
|
||||
import { updateDocumentRecipients } from '@documenso/lib/server-only/recipient/update-document-recipients';
|
||||
import { createDocumentFromTemplate } from '@documenso/lib/server-only/template/create-document-from-template';
|
||||
import { createDocumentFromTemplateLegacy } from '@documenso/lib/server-only/template/create-document-from-template-legacy';
|
||||
import { createTemplate } from '@documenso/lib/server-only/template/create-template';
|
||||
import { deleteTemplate } from '@documenso/lib/server-only/template/delete-template';
|
||||
import { findTemplates } from '@documenso/lib/server-only/template/find-templates';
|
||||
import { getTemplateById } from '@documenso/lib/server-only/template/get-template-by-id';
|
||||
@ -400,6 +401,109 @@ export const ApiContractV1Implementation = tsr.router(ApiContractV1, {
|
||||
}
|
||||
}),
|
||||
|
||||
createTemplate: authenticatedMiddleware(async (args, user, team) => {
|
||||
const { body } = args;
|
||||
const {
|
||||
title,
|
||||
folderId,
|
||||
externalId,
|
||||
visibility,
|
||||
globalAccessAuth,
|
||||
globalActionAuth,
|
||||
publicTitle,
|
||||
publicDescription,
|
||||
type,
|
||||
meta,
|
||||
} = body;
|
||||
|
||||
try {
|
||||
if (process.env.NEXT_PUBLIC_UPLOAD_TRANSPORT !== 's3') {
|
||||
return {
|
||||
status: 500,
|
||||
body: {
|
||||
message: 'Create template is not available without S3 transport.',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const dateFormat = meta?.dateFormat
|
||||
? DATE_FORMATS.find((format) => format.value === meta?.dateFormat)
|
||||
: DATE_FORMATS.find((format) => format.value === DEFAULT_DOCUMENT_DATE_FORMAT);
|
||||
|
||||
if (meta?.dateFormat && !dateFormat) {
|
||||
return {
|
||||
status: 400,
|
||||
body: {
|
||||
message: 'Invalid date format. Please provide a valid date format',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const timezone = meta?.timezone
|
||||
? TIME_ZONES.find((tz) => tz === meta?.timezone)
|
||||
: DEFAULT_DOCUMENT_TIME_ZONE;
|
||||
|
||||
const isTimeZoneValid = meta?.timezone ? TIME_ZONES.includes(String(timezone)) : true;
|
||||
|
||||
if (!isTimeZoneValid) {
|
||||
return {
|
||||
status: 400,
|
||||
body: {
|
||||
message: 'Invalid timezone. Please provide a valid timezone',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const fileName = title?.endsWith('.pdf') ? title : `${title}.pdf`;
|
||||
|
||||
const { url, key } = await getPresignPostUrl(fileName, 'application/pdf');
|
||||
|
||||
const templateDocumentData = await createDocumentData({
|
||||
data: key,
|
||||
type: DocumentDataType.S3_PATH,
|
||||
});
|
||||
|
||||
const createdTemplate = await createTemplate({
|
||||
userId: user.id,
|
||||
teamId: team.id,
|
||||
templateDocumentDataId: templateDocumentData.id,
|
||||
data: {
|
||||
title,
|
||||
folderId,
|
||||
externalId,
|
||||
visibility,
|
||||
globalAccessAuth,
|
||||
globalActionAuth,
|
||||
publicTitle,
|
||||
publicDescription,
|
||||
type,
|
||||
},
|
||||
meta,
|
||||
});
|
||||
|
||||
const fullTemplate = await getTemplateById({
|
||||
id: createdTemplate.id,
|
||||
userId: user.id,
|
||||
teamId: team.id,
|
||||
});
|
||||
|
||||
return {
|
||||
status: 200,
|
||||
body: {
|
||||
uploadUrl: url,
|
||||
template: fullTemplate,
|
||||
},
|
||||
};
|
||||
} catch (err) {
|
||||
return {
|
||||
status: 404,
|
||||
body: {
|
||||
message: 'An error has occured while creating the template',
|
||||
},
|
||||
};
|
||||
}
|
||||
}),
|
||||
|
||||
deleteTemplate: authenticatedMiddleware(async (args, user, team, { logger }) => {
|
||||
const { id: templateId } = args.params;
|
||||
|
||||
@ -1432,7 +1536,6 @@ const updateDocument = async ({
|
||||
return await prisma.document.update({
|
||||
where: {
|
||||
id: documentId,
|
||||
userId,
|
||||
team: buildTeamWhereQuery({ teamId, userId }),
|
||||
},
|
||||
data: {
|
||||
|
||||
@ -168,7 +168,7 @@ test('[TEAMS]: can rename a document folder', async ({ page }) => {
|
||||
await page.getByRole('menuitem', { name: 'Settings' }).click();
|
||||
|
||||
await page.getByLabel('Name').fill('Team Archive');
|
||||
await page.getByRole('button', { name: 'Save Changes' }).click();
|
||||
await page.getByRole('button', { name: 'Update' }).click();
|
||||
|
||||
await expect(page.getByText('Team Archive')).toBeVisible();
|
||||
});
|
||||
@ -470,7 +470,7 @@ test('[TEAMS]: can rename a template folder', async ({ page }) => {
|
||||
await page.getByRole('menuitem', { name: 'Settings' }).click();
|
||||
|
||||
await page.getByLabel('Name').fill('Updated Team Template Folder');
|
||||
await page.getByRole('button', { name: 'Save Changes' }).click();
|
||||
await page.getByRole('button', { name: 'Update' }).click();
|
||||
|
||||
await expect(page.getByText('Updated Team Template Folder')).toBeVisible();
|
||||
});
|
||||
|
||||
@ -1,13 +0,0 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
export const ZEarlyAdopterCheckoutMetadataSchema = z.object({
|
||||
name: z.string(),
|
||||
email: z.string(),
|
||||
signatureText: z.string(),
|
||||
signatureDataUrl: z.string().optional(),
|
||||
source: z.literal('marketing'),
|
||||
});
|
||||
|
||||
export type TEarlyAdopterCheckoutMetadataSchema = z.infer<
|
||||
typeof ZEarlyAdopterCheckoutMetadataSchema
|
||||
>;
|
||||
@ -81,17 +81,34 @@ export const onSubscriptionCreated = async ({ subscription }: OnSubscriptionCrea
|
||||
|
||||
const status = match(subscription.status)
|
||||
.with('active', () => SubscriptionStatus.ACTIVE)
|
||||
.with('trialing', () => SubscriptionStatus.ACTIVE)
|
||||
.with('past_due', () => SubscriptionStatus.PAST_DUE)
|
||||
.otherwise(() => SubscriptionStatus.INACTIVE);
|
||||
|
||||
await prisma.subscription.create({
|
||||
data: {
|
||||
const periodEnd =
|
||||
subscription.status === 'trialing' && subscription.trial_end
|
||||
? new Date(subscription.trial_end * 1000)
|
||||
: new Date(subscription.current_period_end * 1000);
|
||||
|
||||
await prisma.subscription.upsert({
|
||||
where: {
|
||||
organisationId,
|
||||
},
|
||||
create: {
|
||||
organisationId,
|
||||
status,
|
||||
customerId,
|
||||
planId: subscription.id,
|
||||
priceId: subscription.items.data[0].price.id,
|
||||
periodEnd: new Date(subscription.current_period_end * 1000),
|
||||
periodEnd,
|
||||
cancelAtPeriodEnd: subscription.cancel_at_period_end,
|
||||
},
|
||||
update: {
|
||||
status,
|
||||
customerId,
|
||||
planId: subscription.id,
|
||||
priceId: subscription.items.data[0].price.id,
|
||||
periodEnd,
|
||||
cancelAtPeriodEnd: subscription.cancel_at_period_end,
|
||||
},
|
||||
});
|
||||
@ -172,14 +189,17 @@ const handleOrganisationUpdate = async ({ customerId, claim }: HandleOrganisatio
|
||||
}
|
||||
|
||||
// Todo: logging
|
||||
if (organisation.subscription) {
|
||||
console.error('Organisation already has a subscription');
|
||||
if (
|
||||
organisation.subscription &&
|
||||
organisation.subscription.status !== SubscriptionStatus.INACTIVE
|
||||
) {
|
||||
console.error('Organisation already has an active subscription');
|
||||
|
||||
// This should never happen
|
||||
throw Response.json(
|
||||
{
|
||||
success: false,
|
||||
message: `Organisation already has a subscription`,
|
||||
message: `Organisation already has an active subscription`,
|
||||
} satisfies StripeWebhookResponse,
|
||||
{ status: 500 },
|
||||
);
|
||||
|
||||
@ -1,8 +1,9 @@
|
||||
import { SubscriptionStatus } from '@prisma/client';
|
||||
import { OrganisationType, SubscriptionStatus } from '@prisma/client';
|
||||
import { match } from 'ts-pattern';
|
||||
|
||||
import { createOrganisationClaimUpsertData } from '@documenso/lib/server-only/organisation/create-organisation';
|
||||
import { type Stripe, stripe } from '@documenso/lib/server-only/stripe';
|
||||
import { INTERNAL_CLAIM_ID } from '@documenso/lib/types/subscription';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
|
||||
export type OnSubscriptionUpdatedOptions = {
|
||||
@ -55,8 +56,12 @@ export const onSubscriptionUpdated = async ({
|
||||
);
|
||||
}
|
||||
|
||||
if (organisation.subscription?.planId !== subscription.id) {
|
||||
console.error('[WARNING]: Organisation has two subscriptions');
|
||||
if (
|
||||
organisation.subscription &&
|
||||
organisation.subscription.status !== SubscriptionStatus.INACTIVE &&
|
||||
organisation.subscription.planId !== subscription.id
|
||||
) {
|
||||
console.error('[WARNING]: Organisation might have two subscriptions');
|
||||
}
|
||||
|
||||
const previousItem = previousAttributes?.items?.data[0];
|
||||
@ -83,20 +88,41 @@ export const onSubscriptionUpdated = async ({
|
||||
|
||||
const status = match(subscription.status)
|
||||
.with('active', () => SubscriptionStatus.ACTIVE)
|
||||
.with('trialing', () => SubscriptionStatus.ACTIVE)
|
||||
.with('past_due', () => SubscriptionStatus.PAST_DUE)
|
||||
.otherwise(() => SubscriptionStatus.INACTIVE);
|
||||
|
||||
const periodEnd =
|
||||
subscription.status === 'trialing' && subscription.trial_end
|
||||
? new Date(subscription.trial_end * 1000)
|
||||
: new Date(subscription.current_period_end * 1000);
|
||||
|
||||
// Migrate the organisation type if it is no longer an individual plan.
|
||||
if (
|
||||
updatedSubscriptionClaim.id !== INTERNAL_CLAIM_ID.INDIVIDUAL &&
|
||||
updatedSubscriptionClaim.id !== INTERNAL_CLAIM_ID.FREE &&
|
||||
organisation.type === OrganisationType.PERSONAL
|
||||
) {
|
||||
await prisma.organisation.update({
|
||||
where: {
|
||||
id: organisation.id,
|
||||
},
|
||||
data: {
|
||||
type: OrganisationType.ORGANISATION,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
await prisma.$transaction(async (tx) => {
|
||||
await tx.subscription.update({
|
||||
where: {
|
||||
planId: subscription.id,
|
||||
organisationId: organisation.id,
|
||||
},
|
||||
data: {
|
||||
organisationId: organisation.id,
|
||||
status: status,
|
||||
planId: subscription.id,
|
||||
priceId: subscription.items.data[0].price.id,
|
||||
periodEnd: new Date(subscription.current_period_end * 1000),
|
||||
periodEnd,
|
||||
cancelAtPeriodEnd: subscription.cancel_at_period_end,
|
||||
},
|
||||
});
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
|
||||
import { getBoundingClientRect } from '@documenso/lib/client-only/get-bounding-client-rect';
|
||||
|
||||
@ -10,7 +10,7 @@ export const useElementBounds = (elementOrSelector: HTMLElement | string, withSc
|
||||
width: 0,
|
||||
});
|
||||
|
||||
const calculateBounds = () => {
|
||||
const calculateBounds = useCallback(() => {
|
||||
const $el =
|
||||
typeof elementOrSelector === 'string'
|
||||
? document.querySelector<HTMLElement>(elementOrSelector)
|
||||
@ -32,11 +32,11 @@ export const useElementBounds = (elementOrSelector: HTMLElement | string, withSc
|
||||
width,
|
||||
height,
|
||||
};
|
||||
};
|
||||
}, [elementOrSelector, withScroll]);
|
||||
|
||||
useEffect(() => {
|
||||
setBounds(calculateBounds());
|
||||
}, [calculateBounds]);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const onResize = () => {
|
||||
@ -48,7 +48,7 @@ export const useElementBounds = (elementOrSelector: HTMLElement | string, withSc
|
||||
return () => {
|
||||
window.removeEventListener('resize', onResize);
|
||||
};
|
||||
}, [calculateBounds]);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const $el =
|
||||
@ -69,7 +69,7 @@ export const useElementBounds = (elementOrSelector: HTMLElement | string, withSc
|
||||
return () => {
|
||||
observer.disconnect();
|
||||
};
|
||||
}, [calculateBounds]);
|
||||
}, []);
|
||||
|
||||
return bounds;
|
||||
};
|
||||
|
||||
@ -12,3 +12,5 @@ export const NEXT_PRIVATE_INTERNAL_WEBAPP_URL =
|
||||
export const IS_BILLING_ENABLED = () => env('NEXT_PUBLIC_FEATURE_BILLING_ENABLED') === 'true';
|
||||
|
||||
export const API_V2_BETA_URL = '/api/v2-beta';
|
||||
|
||||
export const SUPPORT_EMAIL = 'support@documenso.com';
|
||||
|
||||
@ -16,6 +16,7 @@ import { prefixedId } from '../../universal/id';
|
||||
import { getFileServerSide } from '../../universal/upload/get-file.server';
|
||||
import { putPdfFileServerSide } from '../../universal/upload/put-file.server';
|
||||
import { determineDocumentVisibility } from '../../utils/document-visibility';
|
||||
import { buildTeamWhereQuery } from '../../utils/teams';
|
||||
import { getTeamById } from '../team/get-team';
|
||||
import { getTeamSettings } from '../team/get-team-settings';
|
||||
import { triggerWebhook } from '../webhooks/trigger/trigger-webhook';
|
||||
@ -58,8 +59,10 @@ export const createDocument = async ({
|
||||
const folder = await prisma.folder.findFirst({
|
||||
where: {
|
||||
id: folderId,
|
||||
userId,
|
||||
teamId,
|
||||
team: buildTeamWhereQuery({
|
||||
teamId,
|
||||
userId,
|
||||
}),
|
||||
},
|
||||
select: {
|
||||
visibility: true,
|
||||
|
||||
@ -54,14 +54,7 @@ export const resendDocument = async ({
|
||||
const document = await prisma.document.findUnique({
|
||||
where: documentWhereInput,
|
||||
include: {
|
||||
recipients: {
|
||||
where: {
|
||||
id: {
|
||||
in: recipients,
|
||||
},
|
||||
signingStatus: SigningStatus.NOT_SIGNED,
|
||||
},
|
||||
},
|
||||
recipients: true,
|
||||
documentMeta: true,
|
||||
team: {
|
||||
select: {
|
||||
@ -90,6 +83,11 @@ export const resendDocument = async ({
|
||||
throw new Error('Can not send completed document');
|
||||
}
|
||||
|
||||
const recipientsToRemind = document.recipients.filter(
|
||||
(recipient) =>
|
||||
recipients.includes(recipient.id) && recipient.signingStatus === SigningStatus.NOT_SIGNED,
|
||||
);
|
||||
|
||||
const isRecipientSigningRequestEmailEnabled = extractDerivedDocumentEmailSettings(
|
||||
document.documentMeta,
|
||||
).recipientSigningRequest;
|
||||
@ -106,7 +104,7 @@ export const resendDocument = async ({
|
||||
});
|
||||
|
||||
await Promise.all(
|
||||
document.recipients.map(async (recipient) => {
|
||||
recipientsToRemind.map(async (recipient) => {
|
||||
if (recipient.role === RecipientRole.CC) {
|
||||
return;
|
||||
}
|
||||
@ -26,7 +26,6 @@ export const deleteField = async ({
|
||||
id: fieldId,
|
||||
document: {
|
||||
id: documentId,
|
||||
userId,
|
||||
team: buildTeamWhereQuery({ teamId, userId }),
|
||||
},
|
||||
},
|
||||
|
||||
@ -48,7 +48,6 @@ export const updateField = async ({
|
||||
id: fieldId,
|
||||
document: {
|
||||
id: documentId,
|
||||
userId,
|
||||
team: buildTeamWhereQuery({ teamId, userId }),
|
||||
},
|
||||
},
|
||||
|
||||
@ -4,6 +4,7 @@ import { match } from 'ts-pattern';
|
||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
|
||||
import { buildTeamWhereQuery } from '../../utils/teams';
|
||||
import { getTeamById } from '../team/get-team';
|
||||
|
||||
export interface DeleteFolderOptions {
|
||||
@ -18,8 +19,10 @@ export const deleteFolder = async ({ userId, teamId, folderId }: DeleteFolderOpt
|
||||
const folder = await prisma.folder.findFirst({
|
||||
where: {
|
||||
id: folderId,
|
||||
userId,
|
||||
teamId,
|
||||
team: buildTeamWhereQuery({
|
||||
teamId,
|
||||
userId,
|
||||
}),
|
||||
},
|
||||
include: {
|
||||
documents: true,
|
||||
|
||||
@ -2,6 +2,8 @@ import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
import type { ApiRequestMetadata } from '@documenso/lib/universal/extract-request-metadata';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
|
||||
import { buildTeamWhereQuery } from '../../utils/teams';
|
||||
|
||||
export interface MoveFolderOptions {
|
||||
userId: number;
|
||||
teamId?: number;
|
||||
@ -15,8 +17,10 @@ export const moveFolder = async ({ userId, teamId, folderId, parentId }: MoveFol
|
||||
const folder = await tx.folder.findFirst({
|
||||
where: {
|
||||
id: folderId,
|
||||
userId,
|
||||
teamId,
|
||||
team: buildTeamWhereQuery({
|
||||
teamId,
|
||||
userId,
|
||||
}),
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@ -2,6 +2,8 @@ import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
import { FolderType } from '@documenso/lib/types/folder-type';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
|
||||
import { buildTeamWhereQuery } from '../../utils/teams';
|
||||
|
||||
export interface MoveTemplateToFolderOptions {
|
||||
userId: number;
|
||||
teamId?: number;
|
||||
@ -15,45 +17,47 @@ export const moveTemplateToFolder = async ({
|
||||
templateId,
|
||||
folderId,
|
||||
}: MoveTemplateToFolderOptions) => {
|
||||
return await prisma.$transaction(async (tx) => {
|
||||
const template = await tx.template.findFirst({
|
||||
where: {
|
||||
id: templateId,
|
||||
userId,
|
||||
const template = await prisma.template.findFirst({
|
||||
where: {
|
||||
id: templateId,
|
||||
team: buildTeamWhereQuery({
|
||||
teamId,
|
||||
},
|
||||
userId,
|
||||
}),
|
||||
},
|
||||
});
|
||||
|
||||
if (!template) {
|
||||
throw new AppError(AppErrorCode.NOT_FOUND, {
|
||||
message: 'Template not found',
|
||||
});
|
||||
}
|
||||
|
||||
if (!template) {
|
||||
throw new AppError(AppErrorCode.NOT_FOUND, {
|
||||
message: 'Template not found',
|
||||
});
|
||||
}
|
||||
|
||||
if (folderId !== null) {
|
||||
const folder = await tx.folder.findFirst({
|
||||
where: {
|
||||
id: folderId,
|
||||
userId,
|
||||
teamId,
|
||||
type: FolderType.TEMPLATE,
|
||||
},
|
||||
});
|
||||
|
||||
if (!folder) {
|
||||
throw new AppError(AppErrorCode.NOT_FOUND, {
|
||||
message: 'Folder not found',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return await tx.template.update({
|
||||
if (folderId !== null) {
|
||||
const folder = await prisma.folder.findFirst({
|
||||
where: {
|
||||
id: templateId,
|
||||
},
|
||||
data: {
|
||||
folderId,
|
||||
id: folderId,
|
||||
team: buildTeamWhereQuery({
|
||||
teamId,
|
||||
userId,
|
||||
}),
|
||||
type: FolderType.TEMPLATE,
|
||||
},
|
||||
});
|
||||
|
||||
if (!folder) {
|
||||
throw new AppError(AppErrorCode.NOT_FOUND, {
|
||||
message: 'Folder not found',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return await prisma.template.update({
|
||||
where: {
|
||||
id: templateId,
|
||||
},
|
||||
data: {
|
||||
folderId,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
@ -2,6 +2,7 @@ import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
|
||||
import type { TFolderType } from '../../types/folder-type';
|
||||
import { buildTeamWhereQuery } from '../../utils/teams';
|
||||
|
||||
export interface PinFolderOptions {
|
||||
userId: number;
|
||||
@ -14,8 +15,10 @@ export const pinFolder = async ({ userId, teamId, folderId, type }: PinFolderOpt
|
||||
const folder = await prisma.folder.findFirst({
|
||||
where: {
|
||||
id: folderId,
|
||||
userId,
|
||||
teamId,
|
||||
team: buildTeamWhereQuery({
|
||||
teamId,
|
||||
userId,
|
||||
}),
|
||||
type,
|
||||
},
|
||||
});
|
||||
|
||||
@ -2,6 +2,7 @@ import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
|
||||
import type { TFolderType } from '../../types/folder-type';
|
||||
import { buildTeamWhereQuery } from '../../utils/teams';
|
||||
|
||||
export interface UnpinFolderOptions {
|
||||
userId: number;
|
||||
@ -14,8 +15,10 @@ export const unpinFolder = async ({ userId, teamId, folderId, type }: UnpinFolde
|
||||
const folder = await prisma.folder.findFirst({
|
||||
where: {
|
||||
id: folderId,
|
||||
userId,
|
||||
teamId,
|
||||
team: buildTeamWhereQuery({
|
||||
teamId,
|
||||
userId,
|
||||
}),
|
||||
type,
|
||||
},
|
||||
});
|
||||
|
||||
@ -4,6 +4,7 @@ import { DocumentVisibility } from '@documenso/prisma/generated/types';
|
||||
|
||||
import type { TFolderType } from '../../types/folder-type';
|
||||
import { FolderType } from '../../types/folder-type';
|
||||
import { buildTeamWhereQuery } from '../../utils/teams';
|
||||
|
||||
export interface UpdateFolderOptions {
|
||||
userId: number;
|
||||
@ -25,8 +26,10 @@ export const updateFolder = async ({
|
||||
const folder = await prisma.folder.findFirst({
|
||||
where: {
|
||||
id: folderId,
|
||||
userId,
|
||||
teamId,
|
||||
team: buildTeamWhereQuery({
|
||||
teamId,
|
||||
userId,
|
||||
}),
|
||||
type,
|
||||
},
|
||||
});
|
||||
|
||||
@ -1,47 +0,0 @@
|
||||
import { getPortalSession } from '@documenso/ee/server-only/stripe/get-portal-session';
|
||||
import { IS_BILLING_ENABLED } from '@documenso/lib/constants/app';
|
||||
import { TEAM_MEMBER_ROLE_PERMISSIONS_MAP } from '@documenso/lib/constants/teams';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
|
||||
export type CreateTeamBillingPortalOptions = {
|
||||
userId: number;
|
||||
teamId: number;
|
||||
};
|
||||
|
||||
export const createTeamBillingPortal = async ({
|
||||
userId,
|
||||
teamId,
|
||||
}: CreateTeamBillingPortalOptions) => {
|
||||
if (!IS_BILLING_ENABLED()) {
|
||||
throw new Error('Billing is not enabled');
|
||||
}
|
||||
|
||||
const team = await prisma.team.findFirstOrThrow({
|
||||
where: {
|
||||
id: teamId,
|
||||
members: {
|
||||
some: {
|
||||
userId,
|
||||
role: {
|
||||
in: TEAM_MEMBER_ROLE_PERMISSIONS_MAP['MANAGE_BILLING'],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
include: {
|
||||
subscription: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!team.subscription) {
|
||||
throw new Error('Team has no subscription');
|
||||
}
|
||||
|
||||
if (!team.customerId) {
|
||||
throw new Error('Team has no customerId');
|
||||
}
|
||||
|
||||
return getPortalSession({
|
||||
customerId: team.customerId,
|
||||
});
|
||||
};
|
||||
@ -240,35 +240,79 @@ export const insertFieldInPDF = async (pdf: PDFDocument, field: FieldWithSignatu
|
||||
}));
|
||||
|
||||
const selected: string[] = fromCheckboxValue(field.customText);
|
||||
const direction = meta.data.direction ?? 'vertical';
|
||||
|
||||
const topPadding = 12;
|
||||
const leftCheckboxPadding = 8;
|
||||
const leftCheckboxLabelPadding = 12;
|
||||
const checkboxSpaceY = 13;
|
||||
|
||||
for (const [index, item] of (values ?? []).entries()) {
|
||||
const offsetY = index * checkboxSpaceY + topPadding;
|
||||
if (direction === 'horizontal') {
|
||||
// Horizontal layout: arrange checkboxes side by side with wrapping
|
||||
let currentX = leftCheckboxPadding;
|
||||
let currentY = topPadding;
|
||||
const maxWidth = pageWidth - fieldX - leftCheckboxPadding * 2;
|
||||
|
||||
const checkbox = pdf.getForm().createCheckBox(`checkbox.${field.secondaryId}.${index}`);
|
||||
for (const [index, item] of (values ?? []).entries()) {
|
||||
const checkbox = pdf.getForm().createCheckBox(`checkbox.${field.secondaryId}.${index}`);
|
||||
|
||||
if (selected.includes(item.value)) {
|
||||
checkbox.check();
|
||||
if (selected.includes(item.value)) {
|
||||
checkbox.check();
|
||||
}
|
||||
|
||||
const labelText = item.value.includes('empty-value-') ? '' : item.value;
|
||||
const labelWidth = font.widthOfTextAtSize(labelText, 12);
|
||||
const itemWidth = leftCheckboxLabelPadding + labelWidth + 16; // checkbox + padding + label + margin
|
||||
|
||||
// Check if item fits on current line, if not wrap to next line
|
||||
if (currentX + itemWidth > maxWidth && index > 0) {
|
||||
currentX = leftCheckboxPadding;
|
||||
currentY += checkboxSpaceY;
|
||||
}
|
||||
|
||||
page.drawText(labelText, {
|
||||
x: fieldX + currentX + leftCheckboxLabelPadding,
|
||||
y: pageHeight - (fieldY + currentY),
|
||||
size: 12,
|
||||
font,
|
||||
rotate: degrees(pageRotationInDegrees),
|
||||
});
|
||||
|
||||
checkbox.addToPage(page, {
|
||||
x: fieldX + currentX,
|
||||
y: pageHeight - (fieldY + currentY),
|
||||
height: 8,
|
||||
width: 8,
|
||||
});
|
||||
|
||||
currentX += itemWidth;
|
||||
}
|
||||
} else {
|
||||
// Vertical layout: original behavior
|
||||
for (const [index, item] of (values ?? []).entries()) {
|
||||
const offsetY = index * checkboxSpaceY + topPadding;
|
||||
|
||||
page.drawText(item.value.includes('empty-value-') ? '' : item.value, {
|
||||
x: fieldX + leftCheckboxPadding + leftCheckboxLabelPadding,
|
||||
y: pageHeight - (fieldY + offsetY),
|
||||
size: 12,
|
||||
font,
|
||||
rotate: degrees(pageRotationInDegrees),
|
||||
});
|
||||
const checkbox = pdf.getForm().createCheckBox(`checkbox.${field.secondaryId}.${index}`);
|
||||
|
||||
checkbox.addToPage(page, {
|
||||
x: fieldX + leftCheckboxPadding,
|
||||
y: pageHeight - (fieldY + offsetY),
|
||||
height: 8,
|
||||
width: 8,
|
||||
});
|
||||
if (selected.includes(item.value)) {
|
||||
checkbox.check();
|
||||
}
|
||||
|
||||
page.drawText(item.value.includes('empty-value-') ? '' : item.value, {
|
||||
x: fieldX + leftCheckboxPadding + leftCheckboxLabelPadding,
|
||||
y: pageHeight - (fieldY + offsetY),
|
||||
size: 12,
|
||||
font,
|
||||
rotate: degrees(pageRotationInDegrees),
|
||||
});
|
||||
|
||||
checkbox.addToPage(page, {
|
||||
x: fieldX + leftCheckboxPadding,
|
||||
y: pageHeight - (fieldY + offsetY),
|
||||
height: 8,
|
||||
width: 8,
|
||||
});
|
||||
}
|
||||
}
|
||||
})
|
||||
.with({ type: FieldType.RADIO }, (field) => {
|
||||
|
||||
@ -228,6 +228,7 @@ const getUpdatedFieldMeta = (field: Field, prefillField?: TFieldMetaPrefillField
|
||||
type: 'checkbox',
|
||||
label: field.label,
|
||||
values: newValues,
|
||||
direction: checkboxMeta.direction ?? 'vertical',
|
||||
};
|
||||
|
||||
return meta;
|
||||
|
||||
@ -1,16 +1,31 @@
|
||||
import type { DocumentVisibility, Template, TemplateMeta } from '@prisma/client';
|
||||
import type { z } from 'zod';
|
||||
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import { TemplateSchema } from '@documenso/prisma/generated/zod/modelSchema//TemplateSchema';
|
||||
import type { TCreateTemplateMutationSchema } from '@documenso/trpc/server/template-router/schema';
|
||||
|
||||
import { AppError, AppErrorCode } from '../../errors/app-error';
|
||||
import type { TDocumentAccessAuthTypes, TDocumentActionAuthTypes } from '../../types/document-auth';
|
||||
import { createDocumentAuthOptions } from '../../utils/document-auth';
|
||||
import { buildTeamWhereQuery } from '../../utils/teams';
|
||||
import { getTeamSettings } from '../team/get-team-settings';
|
||||
|
||||
export type CreateTemplateOptions = TCreateTemplateMutationSchema & {
|
||||
export type CreateTemplateOptions = {
|
||||
userId: number;
|
||||
teamId: number;
|
||||
templateDocumentDataId: string;
|
||||
data: {
|
||||
title: string;
|
||||
folderId?: string;
|
||||
externalId?: string | null;
|
||||
visibility?: DocumentVisibility;
|
||||
globalAccessAuth?: TDocumentAccessAuthTypes[];
|
||||
globalActionAuth?: TDocumentActionAuthTypes[];
|
||||
publicTitle?: string;
|
||||
publicDescription?: string;
|
||||
type?: Template['type'];
|
||||
};
|
||||
meta?: Partial<Omit<TemplateMeta, 'id' | 'templateId'>>;
|
||||
};
|
||||
|
||||
export const ZCreateTemplateResponseSchema = TemplateSchema;
|
||||
@ -18,12 +33,14 @@ export const ZCreateTemplateResponseSchema = TemplateSchema;
|
||||
export type TCreateTemplateResponse = z.infer<typeof ZCreateTemplateResponseSchema>;
|
||||
|
||||
export const createTemplate = async ({
|
||||
title,
|
||||
userId,
|
||||
teamId,
|
||||
templateDocumentDataId,
|
||||
folderId,
|
||||
data,
|
||||
meta = {},
|
||||
}: CreateTemplateOptions) => {
|
||||
const { title, folderId } = data;
|
||||
|
||||
const team = await prisma.team.findFirst({
|
||||
where: buildTeamWhereQuery({ teamId, userId }),
|
||||
});
|
||||
@ -55,16 +72,27 @@ export const createTemplate = async ({
|
||||
return await prisma.template.create({
|
||||
data: {
|
||||
title,
|
||||
teamId,
|
||||
userId,
|
||||
templateDocumentDataId,
|
||||
teamId,
|
||||
folderId: folderId,
|
||||
folderId,
|
||||
externalId: data.externalId,
|
||||
visibility: data.visibility ?? settings.documentVisibility,
|
||||
authOptions: createDocumentAuthOptions({
|
||||
globalAccessAuth: data.globalAccessAuth || [],
|
||||
globalActionAuth: data.globalActionAuth || [],
|
||||
}),
|
||||
publicTitle: data.publicTitle,
|
||||
publicDescription: data.publicDescription,
|
||||
type: data.type,
|
||||
templateMeta: {
|
||||
create: {
|
||||
language: settings.documentLanguage,
|
||||
typedSignatureEnabled: settings.typedSignatureEnabled,
|
||||
uploadSignatureEnabled: settings.uploadSignatureEnabled,
|
||||
drawSignatureEnabled: settings.drawSignatureEnabled,
|
||||
...meta,
|
||||
language: meta?.language ?? settings.documentLanguage,
|
||||
typedSignatureEnabled: meta?.typedSignatureEnabled ?? settings.typedSignatureEnabled,
|
||||
uploadSignatureEnabled: meta?.uploadSignatureEnabled ?? settings.uploadSignatureEnabled,
|
||||
drawSignatureEnabled: meta?.drawSignatureEnabled ?? settings.drawSignatureEnabled,
|
||||
emailSettings: meta?.emailSettings || undefined,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@ -2,6 +2,8 @@ import type { WebhookTriggerEvents } from '@prisma/client';
|
||||
|
||||
import { prisma } from '@documenso/prisma';
|
||||
|
||||
import { buildTeamWhereQuery } from '../../utils/teams';
|
||||
|
||||
export type GetAllWebhooksByEventTriggerOptions = {
|
||||
event: WebhookTriggerEvents;
|
||||
userId: number;
|
||||
@ -19,22 +21,10 @@ export const getAllWebhooksByEventTrigger = async ({
|
||||
eventTriggers: {
|
||||
has: event,
|
||||
},
|
||||
team: {
|
||||
id: teamId,
|
||||
teamGroups: {
|
||||
some: {
|
||||
organisationGroup: {
|
||||
organisationGroupMembers: {
|
||||
some: {
|
||||
organisationMember: {
|
||||
userId,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
team: buildTeamWhereQuery({
|
||||
teamId,
|
||||
userId,
|
||||
}),
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
@ -1,5 +1,8 @@
|
||||
import { prisma } from '@documenso/prisma';
|
||||
|
||||
import { TEAM_MEMBER_ROLE_PERMISSIONS_MAP } from '../../constants/teams';
|
||||
import { buildTeamWhereQuery } from '../../utils/teams';
|
||||
|
||||
export type GetWebhookByIdOptions = {
|
||||
id: string;
|
||||
userId: number;
|
||||
@ -10,23 +13,11 @@ export const getWebhookById = async ({ id, userId, teamId }: GetWebhookByIdOptio
|
||||
return await prisma.webhook.findFirstOrThrow({
|
||||
where: {
|
||||
id,
|
||||
userId,
|
||||
team: {
|
||||
id: teamId,
|
||||
teamGroups: {
|
||||
some: {
|
||||
organisationGroup: {
|
||||
organisationGroupMembers: {
|
||||
some: {
|
||||
organisationMember: {
|
||||
userId,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
team: buildTeamWhereQuery({
|
||||
teamId,
|
||||
userId,
|
||||
roles: TEAM_MEMBER_ROLE_PERMISSIONS_MAP.MANAGE_TEAM,
|
||||
}),
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
44
packages/lib/server-only/webhooks/trigger-test-webhook.ts
Normal file
44
packages/lib/server-only/webhooks/trigger-test-webhook.ts
Normal file
@ -0,0 +1,44 @@
|
||||
import type { WebhookTriggerEvents } from '@prisma/client';
|
||||
|
||||
import { getWebhookById } from './get-webhook-by-id';
|
||||
import { generateSampleWebhookPayload } from './trigger/generate-sample-data';
|
||||
import { triggerWebhook } from './trigger/trigger-webhook';
|
||||
|
||||
export type TriggerTestWebhookOptions = {
|
||||
id: string;
|
||||
event: WebhookTriggerEvents;
|
||||
userId: number;
|
||||
teamId: number;
|
||||
};
|
||||
|
||||
export const triggerTestWebhook = async ({
|
||||
id,
|
||||
event,
|
||||
userId,
|
||||
teamId,
|
||||
}: TriggerTestWebhookOptions) => {
|
||||
const webhook = await getWebhookById({ id, userId, teamId });
|
||||
|
||||
if (!webhook.enabled) {
|
||||
throw new Error('Webhook is disabled');
|
||||
}
|
||||
|
||||
if (!webhook.eventTriggers.includes(event)) {
|
||||
throw new Error(`Webhook does not support event: ${event}`);
|
||||
}
|
||||
|
||||
const samplePayload = generateSampleWebhookPayload(event, webhook.webhookUrl);
|
||||
|
||||
try {
|
||||
await triggerWebhook({
|
||||
event,
|
||||
data: samplePayload,
|
||||
userId,
|
||||
teamId,
|
||||
});
|
||||
|
||||
return { success: true, message: 'Test webhook triggered successfully' };
|
||||
} catch (error) {
|
||||
return { success: false, error: error instanceof Error ? error.message : 'Unknown error' };
|
||||
}
|
||||
};
|
||||
@ -0,0 +1,485 @@
|
||||
import {
|
||||
DocumentDistributionMethod,
|
||||
DocumentSigningOrder,
|
||||
DocumentSource,
|
||||
DocumentStatus,
|
||||
DocumentVisibility,
|
||||
ReadStatus,
|
||||
RecipientRole,
|
||||
SendStatus,
|
||||
SigningStatus,
|
||||
WebhookTriggerEvents,
|
||||
} from '@prisma/client';
|
||||
|
||||
import type { WebhookPayload } from '../../../types/webhook-payload';
|
||||
|
||||
export const generateSampleWebhookPayload = (
|
||||
event: WebhookTriggerEvents,
|
||||
webhookUrl: string,
|
||||
): WebhookPayload => {
|
||||
const now = new Date();
|
||||
const basePayload = {
|
||||
id: 10,
|
||||
externalId: null,
|
||||
userId: 1,
|
||||
authOptions: null,
|
||||
formValues: null,
|
||||
visibility: DocumentVisibility.EVERYONE,
|
||||
title: 'documenso.pdf',
|
||||
status: DocumentStatus.DRAFT,
|
||||
documentDataId: 'hs8qz1ktr9204jn7mg6c5dxy0',
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
completedAt: null,
|
||||
deletedAt: null,
|
||||
teamId: null,
|
||||
templateId: null,
|
||||
source: DocumentSource.DOCUMENT,
|
||||
documentMeta: {
|
||||
id: 'doc_meta_123',
|
||||
subject: 'Please sign this document',
|
||||
message: 'Hello, please review and sign this document.',
|
||||
timezone: 'UTC',
|
||||
password: null,
|
||||
dateFormat: 'MM/DD/YYYY',
|
||||
redirectUrl: null,
|
||||
signingOrder: DocumentSigningOrder.PARALLEL,
|
||||
allowDictateNextSigner: false,
|
||||
typedSignatureEnabled: true,
|
||||
uploadSignatureEnabled: true,
|
||||
drawSignatureEnabled: true,
|
||||
language: 'en',
|
||||
distributionMethod: DocumentDistributionMethod.EMAIL,
|
||||
emailSettings: null,
|
||||
},
|
||||
recipients: [
|
||||
{
|
||||
id: 52,
|
||||
documentId: 10,
|
||||
templateId: null,
|
||||
email: 'signer@documenso.com',
|
||||
name: 'John Doe',
|
||||
token: 'SIGNING_TOKEN',
|
||||
documentDeletedAt: null,
|
||||
expired: null,
|
||||
signedAt: null,
|
||||
authOptions: null,
|
||||
signingOrder: 1,
|
||||
rejectionReason: null,
|
||||
role: RecipientRole.SIGNER,
|
||||
readStatus: ReadStatus.NOT_OPENED,
|
||||
signingStatus: SigningStatus.NOT_SIGNED,
|
||||
sendStatus: SendStatus.NOT_SENT,
|
||||
},
|
||||
],
|
||||
Recipient: [
|
||||
{
|
||||
id: 52,
|
||||
documentId: 10,
|
||||
templateId: null,
|
||||
email: 'signer@documenso.com',
|
||||
name: 'John Doe',
|
||||
token: 'SIGNING_TOKEN',
|
||||
documentDeletedAt: null,
|
||||
expired: null,
|
||||
signedAt: null,
|
||||
authOptions: null,
|
||||
signingOrder: 1,
|
||||
rejectionReason: null,
|
||||
role: RecipientRole.SIGNER,
|
||||
readStatus: ReadStatus.NOT_OPENED,
|
||||
signingStatus: SigningStatus.NOT_SIGNED,
|
||||
sendStatus: SendStatus.NOT_SENT,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
if (event === WebhookTriggerEvents.DOCUMENT_CREATED) {
|
||||
return {
|
||||
event,
|
||||
payload: {
|
||||
...basePayload,
|
||||
status: DocumentStatus.DRAFT,
|
||||
},
|
||||
createdAt: now.toISOString(),
|
||||
webhookEndpoint: webhookUrl,
|
||||
};
|
||||
}
|
||||
|
||||
if (event === WebhookTriggerEvents.DOCUMENT_SENT) {
|
||||
return {
|
||||
event,
|
||||
payload: {
|
||||
...basePayload,
|
||||
status: DocumentStatus.PENDING,
|
||||
recipients: [
|
||||
{
|
||||
...basePayload.recipients[0],
|
||||
email: 'signer2@documenso.com',
|
||||
name: 'Signer 2',
|
||||
role: RecipientRole.VIEWER,
|
||||
sendStatus: SendStatus.SENT,
|
||||
documentDeletedAt: null,
|
||||
expired: null,
|
||||
signedAt: null,
|
||||
authOptions: null,
|
||||
signingOrder: 1,
|
||||
rejectionReason: null,
|
||||
readStatus: ReadStatus.NOT_OPENED,
|
||||
signingStatus: SigningStatus.NOT_SIGNED,
|
||||
},
|
||||
],
|
||||
Recipient: [
|
||||
{
|
||||
...basePayload.Recipient[0],
|
||||
email: 'signer1@documenso.com',
|
||||
name: 'Signer 1',
|
||||
token: 'SIGNING_TOKEN',
|
||||
signingOrder: 2,
|
||||
role: RecipientRole.SIGNER,
|
||||
sendStatus: SendStatus.SENT,
|
||||
documentDeletedAt: null,
|
||||
expired: null,
|
||||
signedAt: null,
|
||||
authOptions: null,
|
||||
rejectionReason: null,
|
||||
readStatus: ReadStatus.NOT_OPENED,
|
||||
signingStatus: SigningStatus.NOT_SIGNED,
|
||||
},
|
||||
],
|
||||
},
|
||||
createdAt: now.toISOString(),
|
||||
webhookEndpoint: webhookUrl,
|
||||
};
|
||||
}
|
||||
|
||||
if (event === WebhookTriggerEvents.DOCUMENT_OPENED) {
|
||||
return {
|
||||
event,
|
||||
payload: {
|
||||
...basePayload,
|
||||
status: DocumentStatus.PENDING,
|
||||
recipients: [
|
||||
{
|
||||
...basePayload.recipients[0],
|
||||
email: 'signer2@documenso.com',
|
||||
name: 'Signer 2',
|
||||
role: RecipientRole.VIEWER,
|
||||
readStatus: ReadStatus.OPENED,
|
||||
sendStatus: SendStatus.SENT,
|
||||
documentDeletedAt: null,
|
||||
expired: null,
|
||||
signedAt: null,
|
||||
authOptions: null,
|
||||
signingOrder: 1,
|
||||
rejectionReason: null,
|
||||
signingStatus: SigningStatus.NOT_SIGNED,
|
||||
},
|
||||
],
|
||||
Recipient: [
|
||||
{
|
||||
...basePayload.Recipient[0],
|
||||
email: 'signer2@documenso.com',
|
||||
name: 'Signer 2',
|
||||
role: RecipientRole.VIEWER,
|
||||
readStatus: ReadStatus.OPENED,
|
||||
sendStatus: SendStatus.SENT,
|
||||
documentDeletedAt: null,
|
||||
expired: null,
|
||||
signedAt: null,
|
||||
authOptions: null,
|
||||
signingOrder: 1,
|
||||
rejectionReason: null,
|
||||
signingStatus: SigningStatus.NOT_SIGNED,
|
||||
},
|
||||
],
|
||||
},
|
||||
createdAt: now.toISOString(),
|
||||
webhookEndpoint: webhookUrl,
|
||||
};
|
||||
}
|
||||
|
||||
if (event === WebhookTriggerEvents.DOCUMENT_SIGNED) {
|
||||
return {
|
||||
event,
|
||||
payload: {
|
||||
...basePayload,
|
||||
status: DocumentStatus.COMPLETED,
|
||||
completedAt: now,
|
||||
recipients: [
|
||||
{
|
||||
...basePayload.recipients[0],
|
||||
id: 51,
|
||||
email: 'signer1@documenso.com',
|
||||
name: 'Signer 1',
|
||||
token: 'SIGNING_TOKEN',
|
||||
signedAt: now,
|
||||
authOptions: {
|
||||
accessAuth: null,
|
||||
actionAuth: null,
|
||||
},
|
||||
readStatus: ReadStatus.OPENED,
|
||||
signingStatus: SigningStatus.SIGNED,
|
||||
sendStatus: SendStatus.SENT,
|
||||
documentDeletedAt: null,
|
||||
expired: null,
|
||||
signingOrder: 1,
|
||||
rejectionReason: null,
|
||||
},
|
||||
],
|
||||
Recipient: [
|
||||
{
|
||||
...basePayload.Recipient[0],
|
||||
id: 51,
|
||||
email: 'signer1@documenso.com',
|
||||
name: 'Signer 1',
|
||||
token: 'SIGNING_TOKEN',
|
||||
signedAt: now,
|
||||
authOptions: {
|
||||
accessAuth: null,
|
||||
actionAuth: null,
|
||||
},
|
||||
readStatus: ReadStatus.OPENED,
|
||||
signingStatus: SigningStatus.SIGNED,
|
||||
sendStatus: SendStatus.SENT,
|
||||
documentDeletedAt: null,
|
||||
expired: null,
|
||||
signingOrder: 1,
|
||||
rejectionReason: null,
|
||||
},
|
||||
],
|
||||
},
|
||||
createdAt: now.toISOString(),
|
||||
webhookEndpoint: webhookUrl,
|
||||
};
|
||||
}
|
||||
|
||||
if (event === WebhookTriggerEvents.DOCUMENT_COMPLETED) {
|
||||
return {
|
||||
event,
|
||||
payload: {
|
||||
...basePayload,
|
||||
status: DocumentStatus.COMPLETED,
|
||||
completedAt: now,
|
||||
recipients: [
|
||||
{
|
||||
id: 50,
|
||||
documentId: 10,
|
||||
templateId: null,
|
||||
email: 'signer2@documenso.com',
|
||||
name: 'Signer 2',
|
||||
token: 'SIGNING_TOKEN',
|
||||
documentDeletedAt: null,
|
||||
expired: null,
|
||||
signedAt: now,
|
||||
authOptions: {
|
||||
accessAuth: null,
|
||||
actionAuth: null,
|
||||
},
|
||||
signingOrder: 1,
|
||||
rejectionReason: null,
|
||||
role: RecipientRole.VIEWER,
|
||||
readStatus: ReadStatus.OPENED,
|
||||
signingStatus: SigningStatus.SIGNED,
|
||||
sendStatus: SendStatus.SENT,
|
||||
},
|
||||
{
|
||||
id: 51,
|
||||
documentId: 10,
|
||||
templateId: null,
|
||||
email: 'signer1@documenso.com',
|
||||
name: 'Signer 1',
|
||||
token: 'SIGNING_TOKEN',
|
||||
documentDeletedAt: null,
|
||||
expired: null,
|
||||
signedAt: now,
|
||||
authOptions: {
|
||||
accessAuth: null,
|
||||
actionAuth: null,
|
||||
},
|
||||
signingOrder: 2,
|
||||
rejectionReason: null,
|
||||
role: RecipientRole.SIGNER,
|
||||
readStatus: ReadStatus.OPENED,
|
||||
signingStatus: SigningStatus.SIGNED,
|
||||
sendStatus: SendStatus.SENT,
|
||||
},
|
||||
],
|
||||
Recipient: [
|
||||
{
|
||||
id: 50,
|
||||
documentId: 10,
|
||||
templateId: null,
|
||||
email: 'signer2@documenso.com',
|
||||
name: 'Signer 2',
|
||||
token: 'SIGNING_TOKEN',
|
||||
documentDeletedAt: null,
|
||||
expired: null,
|
||||
signedAt: now,
|
||||
authOptions: {
|
||||
accessAuth: null,
|
||||
actionAuth: null,
|
||||
},
|
||||
signingOrder: 1,
|
||||
rejectionReason: null,
|
||||
role: RecipientRole.VIEWER,
|
||||
readStatus: ReadStatus.OPENED,
|
||||
signingStatus: SigningStatus.SIGNED,
|
||||
sendStatus: SendStatus.SENT,
|
||||
},
|
||||
{
|
||||
id: 51,
|
||||
documentId: 10,
|
||||
templateId: null,
|
||||
email: 'signer1@documenso.com',
|
||||
name: 'Signer 1',
|
||||
token: 'SIGNING_TOKEN',
|
||||
documentDeletedAt: null,
|
||||
expired: null,
|
||||
signedAt: now,
|
||||
authOptions: {
|
||||
accessAuth: null,
|
||||
actionAuth: null,
|
||||
},
|
||||
signingOrder: 2,
|
||||
rejectionReason: null,
|
||||
role: RecipientRole.SIGNER,
|
||||
readStatus: ReadStatus.OPENED,
|
||||
signingStatus: SigningStatus.SIGNED,
|
||||
sendStatus: SendStatus.SENT,
|
||||
},
|
||||
],
|
||||
},
|
||||
createdAt: now.toISOString(),
|
||||
webhookEndpoint: webhookUrl,
|
||||
};
|
||||
}
|
||||
|
||||
if (event === WebhookTriggerEvents.DOCUMENT_REJECTED) {
|
||||
return {
|
||||
event,
|
||||
payload: {
|
||||
...basePayload,
|
||||
status: DocumentStatus.PENDING,
|
||||
recipients: [
|
||||
{
|
||||
...basePayload.recipients[0],
|
||||
signedAt: now,
|
||||
authOptions: {
|
||||
accessAuth: null,
|
||||
actionAuth: null,
|
||||
},
|
||||
rejectionReason: 'I do not agree with the terms',
|
||||
readStatus: ReadStatus.OPENED,
|
||||
signingStatus: SigningStatus.REJECTED,
|
||||
sendStatus: SendStatus.SENT,
|
||||
documentDeletedAt: null,
|
||||
expired: null,
|
||||
signingOrder: 1,
|
||||
},
|
||||
],
|
||||
Recipient: [
|
||||
{
|
||||
...basePayload.Recipient[0],
|
||||
signedAt: now,
|
||||
authOptions: {
|
||||
accessAuth: null,
|
||||
actionAuth: null,
|
||||
},
|
||||
rejectionReason: 'I do not agree with the terms',
|
||||
readStatus: ReadStatus.OPENED,
|
||||
signingStatus: SigningStatus.REJECTED,
|
||||
sendStatus: SendStatus.SENT,
|
||||
documentDeletedAt: null,
|
||||
expired: null,
|
||||
signingOrder: 1,
|
||||
},
|
||||
],
|
||||
},
|
||||
createdAt: now.toISOString(),
|
||||
webhookEndpoint: webhookUrl,
|
||||
};
|
||||
}
|
||||
|
||||
if (event === WebhookTriggerEvents.DOCUMENT_CANCELLED) {
|
||||
return {
|
||||
event,
|
||||
payload: {
|
||||
...basePayload,
|
||||
id: 7,
|
||||
externalId: null,
|
||||
userId: 3,
|
||||
status: DocumentStatus.PENDING,
|
||||
documentDataId: 'cm6exvn93006hi02ru90a265a',
|
||||
documentMeta: {
|
||||
...basePayload.documentMeta,
|
||||
id: 'cm6exvn96006ji02rqvzjvwoy',
|
||||
subject: '',
|
||||
message: '',
|
||||
timezone: 'Etc/UTC',
|
||||
dateFormat: 'yyyy-MM-dd hh:mm a',
|
||||
redirectUrl: '',
|
||||
emailSettings: {
|
||||
documentDeleted: true,
|
||||
documentPending: true,
|
||||
recipientSigned: true,
|
||||
recipientRemoved: true,
|
||||
documentCompleted: true,
|
||||
ownerDocumentCompleted: true,
|
||||
recipientSigningRequest: true,
|
||||
},
|
||||
},
|
||||
recipients: [
|
||||
{
|
||||
id: 7,
|
||||
documentId: 7,
|
||||
templateId: null,
|
||||
email: 'signer1@documenso.com',
|
||||
name: 'Signer 1',
|
||||
token: 'SIGNING_TOKEN',
|
||||
documentDeletedAt: null,
|
||||
expired: null,
|
||||
signedAt: null,
|
||||
authOptions: {
|
||||
accessAuth: null,
|
||||
actionAuth: null,
|
||||
},
|
||||
signingOrder: 1,
|
||||
rejectionReason: null,
|
||||
role: RecipientRole.SIGNER,
|
||||
readStatus: ReadStatus.NOT_OPENED,
|
||||
signingStatus: SigningStatus.NOT_SIGNED,
|
||||
sendStatus: SendStatus.SENT,
|
||||
},
|
||||
],
|
||||
Recipient: [
|
||||
{
|
||||
id: 7,
|
||||
documentId: 7,
|
||||
templateId: null,
|
||||
email: 'signer@documenso.com',
|
||||
name: 'Signer',
|
||||
token: 'SIGNING_TOKEN',
|
||||
documentDeletedAt: null,
|
||||
expired: null,
|
||||
signedAt: null,
|
||||
authOptions: {
|
||||
accessAuth: null,
|
||||
actionAuth: null,
|
||||
},
|
||||
signingOrder: 1,
|
||||
rejectionReason: null,
|
||||
role: RecipientRole.SIGNER,
|
||||
readStatus: ReadStatus.NOT_OPENED,
|
||||
signingStatus: SigningStatus.NOT_SIGNED,
|
||||
sendStatus: SendStatus.SENT,
|
||||
},
|
||||
],
|
||||
},
|
||||
createdAt: now.toISOString(),
|
||||
webhookEndpoint: webhookUrl,
|
||||
};
|
||||
}
|
||||
|
||||
throw new Error(`Unsupported event type: ${event}`);
|
||||
};
|
||||
@ -8,7 +8,7 @@ msgstr ""
|
||||
"Language: es\n"
|
||||
"Project-Id-Version: documenso-app\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"PO-Revision-Date: 2025-06-19 06:05\n"
|
||||
"PO-Revision-Date: 2025-07-14 04:19\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Spanish\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
@ -1690,7 +1690,7 @@ msgstr "Con copia"
|
||||
|
||||
#: packages/lib/constants/recipient-roles.ts
|
||||
msgid "Ccers"
|
||||
msgstr ""
|
||||
msgstr "Firmantes"
|
||||
|
||||
#: packages/ui/primitives/document-flow/field-items-advanced-settings/text-field.tsx
|
||||
msgid "Character Limit"
|
||||
|
||||
@ -8,7 +8,7 @@ msgstr ""
|
||||
"Language: it\n"
|
||||
"Project-Id-Version: documenso-app\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"PO-Revision-Date: 2025-06-19 06:05\n"
|
||||
"PO-Revision-Date: 2025-07-14 04:19\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Italian\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
@ -2301,7 +2301,7 @@ msgstr "Struttura CSV"
|
||||
|
||||
#: apps/remix/app/routes/_authenticated+/admin+/stats.tsx
|
||||
msgid "Cumulative MAU (signed in)"
|
||||
msgstr ""
|
||||
msgstr "MAU cumulativi (autenticati)"
|
||||
|
||||
#: apps/remix/app/routes/_authenticated+/settings+/security.sessions.tsx
|
||||
msgid "Current"
|
||||
@ -4230,7 +4230,7 @@ msgstr "MAU (ha completato il documento)"
|
||||
|
||||
#: apps/remix/app/routes/_authenticated+/admin+/stats.tsx
|
||||
msgid "MAU (signed in)"
|
||||
msgstr ""
|
||||
msgstr "MAU (autenticati)"
|
||||
|
||||
#: packages/ui/primitives/document-flow/field-items-advanced-settings/number-field.tsx
|
||||
msgid "Max"
|
||||
|
||||
8732
packages/lib/translations/nl/web.po
Normal file
8732
packages/lib/translations/nl/web.po
Normal file
File diff suppressed because it is too large
Load Diff
@ -8,7 +8,7 @@ msgstr ""
|
||||
"Language: pl\n"
|
||||
"Project-Id-Version: documenso-app\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"PO-Revision-Date: 2025-06-19 06:05\n"
|
||||
"PO-Revision-Date: 2025-07-14 18:04\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"
|
||||
@ -29,7 +29,7 @@ msgstr " Włącz podpisywanie linku bezpośredniego"
|
||||
|
||||
#: apps/remix/app/components/embed/authoring/configure-document-upload.tsx
|
||||
msgid ".PDF documents accepted (max {APP_DOCUMENT_UPLOAD_SIZE_LIMIT}MB)"
|
||||
msgstr "Akceptowane dokumenty .PDF (maks. {APP_DOCUMENT_UPLOAD_SIZE_LIMIT}MB)"
|
||||
msgstr "Akceptowane dokumenty .PDF (maks. {APP_DOCUMENT_UPLOAD_SIZE_LIMIT} MB)"
|
||||
|
||||
#. placeholder {0}: field.customText
|
||||
#. placeholder {1}: timezone || ''
|
||||
@ -43,15 +43,15 @@ msgstr "Dokument \"{documentName}\" został usunięty przez administratora."
|
||||
|
||||
#: packages/email/template-components/template-document-pending.tsx
|
||||
msgid "“{documentName}” has been signed"
|
||||
msgstr "Dokument \"{documentName}\" został podpisany"
|
||||
msgstr "Dokument „{documentName}” został podpisany"
|
||||
|
||||
#: packages/email/template-components/template-document-completed.tsx
|
||||
msgid "“{documentName}” was signed by all signers"
|
||||
msgstr "Dokument \"{documentName}\" został podpisany przez wszystkich podpisujących"
|
||||
msgstr "Dokument „{documentName}” został podpisany przez wszystkich podpisujących"
|
||||
|
||||
#: apps/remix/app/components/dialogs/document-delete-dialog.tsx
|
||||
msgid "\"{documentTitle}\" has been successfully deleted"
|
||||
msgstr "Dokument \"{documentTitle}\" został usunięty"
|
||||
msgstr "Dokument „{documentTitle}” został usunięty"
|
||||
|
||||
#: apps/remix/app/components/forms/document-preferences-form.tsx
|
||||
msgid "\"{placeholderEmail}\" on behalf of \"Team Name\" has invited you to sign \"example document\"."
|
||||
@ -254,7 +254,7 @@ msgstr "Użytkownik {prefix} otworzył dokument"
|
||||
|
||||
#: packages/lib/utils/document-audit-logs.ts
|
||||
msgid "{prefix} prefilled a field"
|
||||
msgstr "{prefix} wstępnie wypełnił pole"
|
||||
msgstr "Użytkownik {prefix} wstępnie wypełnił pole"
|
||||
|
||||
#: packages/lib/utils/document-audit-logs.ts
|
||||
msgid "{prefix} removed a field"
|
||||
@ -304,7 +304,7 @@ msgstr "{prefix} zaktualizowane wymagania dotyczące autoryzacji dostępu do dok
|
||||
|
||||
#: packages/lib/utils/document-audit-logs.ts
|
||||
msgid "{prefix} updated the document external ID"
|
||||
msgstr "{prefix} zaktualizowane ID zewnętrzne dokumentu"
|
||||
msgstr "Użytkownik {prefix} zaktualizował identyfikator zewnętrzny dokumentu"
|
||||
|
||||
#: packages/lib/utils/document-audit-logs.ts
|
||||
msgid "{prefix} updated the document signing auth requirements"
|
||||
@ -383,7 +383,7 @@ msgstr "{userName} dodał CC do dokumentu"
|
||||
|
||||
#: packages/lib/utils/document-audit-logs.ts
|
||||
msgid "{userName} completed their task"
|
||||
msgstr "{userName} zakończył swoje zadanie"
|
||||
msgstr "Użytkownik {userName} zakończył swoje zadanie"
|
||||
|
||||
#: packages/lib/utils/document-audit-logs.ts
|
||||
msgid "{userName} rejected the document"
|
||||
@ -802,7 +802,7 @@ msgstr "Dodaj wszystkie istotne pola dla każdego odbiorcy."
|
||||
|
||||
#: apps/remix/app/components/general/template/template-edit-form.tsx
|
||||
msgid "Add all relevant placeholders for each recipient."
|
||||
msgstr "Dodaj wszystkie odpowiednie symbole zastępcze dla każdego odbiorcy."
|
||||
msgstr "Dodaj wszystkie odpowiednie teksty zastępcze dla każdego odbiorcy."
|
||||
|
||||
#: apps/remix/app/routes/_authenticated+/settings+/security._index.tsx
|
||||
msgid "Add an authenticator to serve as a secondary authentication method for signing documents."
|
||||
@ -814,11 +814,11 @@ msgstr "Dodaj autoryzator, aby służył jako dodatkowa metoda uwierzytelniania
|
||||
|
||||
#: packages/ui/primitives/document-flow/add-settings.tsx
|
||||
msgid "Add an external ID to the document. This can be used to identify the document in external systems."
|
||||
msgstr "Dodaj zewnętrzny ID do dokumentu. Może być używany do identyfikacji dokumentu w zewnętrznych systemach."
|
||||
msgstr "Dodaj identyfikator zewnętrzny dokumentu. Może być używany do identyfikacji dokumentu w zewnętrznych systemach."
|
||||
|
||||
#: packages/ui/primitives/template-flow/add-template-settings.tsx
|
||||
msgid "Add an external ID to the template. This can be used to identify in external systems."
|
||||
msgstr "Dodaj zewnętrzny ID do szablonu. Może być używany do identyfikacji w systemach zewnętrznych."
|
||||
msgstr "Dodaj identyfikator zewnętrzny szablonu. Może być używany do identyfikacji w zewnętrznych systemach."
|
||||
|
||||
#: packages/ui/primitives/document-flow/field-items-advanced-settings/dropdown-field.tsx
|
||||
msgid "Add another option"
|
||||
@ -896,7 +896,7 @@ msgstr "Dodaj podpisujących"
|
||||
|
||||
#: apps/remix/app/components/embed/authoring/configure-document-recipients.tsx
|
||||
msgid "Add signers and configure signing preferences"
|
||||
msgstr "Dodaj sygnatariuszy i skonfiguruj preferencje podpisywania"
|
||||
msgstr "Dodaj podpisujących i skonfiguruj ustawienia podpisywania"
|
||||
|
||||
#: apps/remix/app/components/dialogs/team-email-add-dialog.tsx
|
||||
msgid "Add team email"
|
||||
@ -1025,7 +1025,7 @@ msgstr "Zezwól odbiorcom dokumentów na bezpośrednią odpowiedź na ten adres
|
||||
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx
|
||||
#: packages/ui/primitives/document-flow/add-signers.tsx
|
||||
msgid "Allow signers to dictate next signer"
|
||||
msgstr "Pozwól sygnatariuszom wskazać następnego sygnatariusza"
|
||||
msgstr "Zezwalaj podpisującym wskazać następnego podpisującego"
|
||||
|
||||
#: apps/remix/app/components/embed/authoring/configure-document-advanced-settings.tsx
|
||||
#: packages/ui/primitives/template-flow/add-template-settings.tsx
|
||||
@ -1043,7 +1043,7 @@ msgstr "Pozwala na uwierzytelnianie za pomocą biometrii, menedżerów haseł, k
|
||||
|
||||
#: apps/remix/app/components/forms/signup.tsx
|
||||
msgid "Already have an account? <0>Sign in instead</0>"
|
||||
msgstr "Masz już konto? <0>Zaloguj się zamiast tego</0>"
|
||||
msgstr "Masz już konto? <0>Zaloguj się</0>"
|
||||
|
||||
#: apps/remix/app/components/tables/organisation-billing-invoices-table.tsx
|
||||
msgid "Amount"
|
||||
@ -1582,7 +1582,7 @@ msgstr "Kontynuując z Twoim podpisem elektronicznym, przyjmujesz i zgadzasz si
|
||||
|
||||
#: apps/remix/app/components/forms/signup.tsx
|
||||
msgid "By proceeding, you agree to our <0>Terms of Service</0> and <1>Privacy Policy</1>."
|
||||
msgstr "Kontynuując, zgadzasz się na nasze <0>Warunki korzystania z usługi</0> oraz <1>Politykę prywatności</1>."
|
||||
msgstr "Kontynuując, akceptujesz nasz <0>Regulamin</0> i <1>Politykę prywatności</1>."
|
||||
|
||||
#: apps/remix/app/routes/_unauthenticated+/articles.signature-disclosure.tsx
|
||||
msgid "By using the electronic signature feature, you are consenting to conduct transactions and receive disclosures electronically. You acknowledge that your electronic signature on documents is binding and that you accept the terms outlined in the documents you are signing."
|
||||
@ -1673,7 +1673,7 @@ msgstr "Nie można usunąć dokumentu"
|
||||
|
||||
#: packages/ui/primitives/document-flow/add-signers.tsx
|
||||
msgid "Cannot remove signer"
|
||||
msgstr "Nie można usunąć sygnatariusza"
|
||||
msgstr "Nie można usunąć podpisującego"
|
||||
|
||||
#: packages/lib/constants/recipient-roles.ts
|
||||
msgid "Cc"
|
||||
@ -2257,7 +2257,7 @@ msgstr "Utwórz swoje konto i zacznij korzystać z nowoczesnego podpisywania dok
|
||||
|
||||
#: apps/remix/app/components/forms/signup.tsx
|
||||
msgid "Create your account and start using state-of-the-art document signing. Open and beautiful signing is within your grasp."
|
||||
msgstr "Utwórz swoje konto i zacznij korzystać z nowoczesnego podpisywania dokumentów. Otwarty i piękny podpis jest w zasięgu ręki."
|
||||
msgstr "Utwórz konto i zacznij korzystać z nowoczesnego podpisywania dokumentów. Otwarty i piękny podpis jest w zasięgu ręki."
|
||||
|
||||
#: apps/remix/app/routes/_authenticated+/settings+/security.sessions.tsx
|
||||
#: apps/remix/app/routes/_authenticated+/admin+/documents._index.tsx
|
||||
@ -2676,7 +2676,7 @@ msgstr "Dokument anulowany"
|
||||
#: packages/lib/utils/document-audit-logs.ts
|
||||
#: packages/lib/utils/document-audit-logs.ts
|
||||
msgid "Document completed"
|
||||
msgstr "Dokument ukończony"
|
||||
msgstr "Zakończono dokument"
|
||||
|
||||
#: packages/ui/components/document/document-email-checkboxes.tsx
|
||||
#: packages/ui/components/document/document-email-checkboxes.tsx
|
||||
@ -2719,7 +2719,7 @@ msgstr "Tworzenie dokumentu"
|
||||
#: apps/remix/app/components/dialogs/admin-document-delete-dialog.tsx
|
||||
#: packages/lib/utils/document-audit-logs.ts
|
||||
msgid "Document deleted"
|
||||
msgstr "Dokument usunięty"
|
||||
msgstr "Usunięto dokument"
|
||||
|
||||
#: packages/ui/components/document/document-email-checkboxes.tsx
|
||||
msgid "Document deleted email"
|
||||
@ -2744,7 +2744,7 @@ msgstr "Dokument zduplikowany"
|
||||
|
||||
#: packages/lib/utils/document-audit-logs.ts
|
||||
msgid "Document external ID updated"
|
||||
msgstr "Zaktualizowane ID zewnętrzne dokumentu"
|
||||
msgstr "Zaktualizowano identyfikator zewnętrzny dokumentu"
|
||||
|
||||
#: apps/remix/app/components/general/document/document-certificate-qr-view.tsx
|
||||
msgid "Document found in your account"
|
||||
@ -2786,7 +2786,7 @@ msgstr "Dokument przeniesiony"
|
||||
|
||||
#: packages/lib/utils/document-audit-logs.ts
|
||||
msgid "Document moved to team"
|
||||
msgstr "Dokument przeniesiony do zespołu"
|
||||
msgstr "Przeniesiono dokument do zespołu"
|
||||
|
||||
#: apps/remix/app/routes/_recipient+/sign.$token+/complete.tsx
|
||||
msgid "Document no longer available to sign"
|
||||
@ -2959,7 +2959,7 @@ msgstr "Szkic"
|
||||
|
||||
#: apps/remix/app/components/general/app-command-menu.tsx
|
||||
msgid "Draft documents"
|
||||
msgstr "Dokumenty szkiców"
|
||||
msgstr "Szkice dokumentów"
|
||||
|
||||
#: apps/remix/app/routes/_authenticated+/admin+/stats.tsx
|
||||
msgid "Drafted Documents"
|
||||
@ -3085,7 +3085,7 @@ msgstr "Adres e-mail"
|
||||
|
||||
#: apps/remix/app/routes/_unauthenticated+/verify-email.$token.tsx
|
||||
msgid "Email already confirmed"
|
||||
msgstr "E-mail już potwierdzony"
|
||||
msgstr "Adres e-mail został już potwierdzony"
|
||||
|
||||
#: apps/remix/app/components/general/direct-template/direct-template-configure-form.tsx
|
||||
msgid "Email cannot already exist in the template"
|
||||
@ -3093,11 +3093,11 @@ msgstr "E-mail nie może już istnieć w szablonie"
|
||||
|
||||
#: apps/remix/app/routes/_unauthenticated+/verify-email.$token.tsx
|
||||
msgid "Email Confirmed!"
|
||||
msgstr "E-mail potwierdzony!"
|
||||
msgstr "Adres e-mail został potwierdzony!"
|
||||
|
||||
#: packages/ui/primitives/template-flow/add-template-settings.tsx
|
||||
msgid "Email Options"
|
||||
msgstr "Opcje e-mail"
|
||||
msgstr "Opcje adresu e-mail"
|
||||
|
||||
#: packages/lib/utils/document-audit-logs.ts
|
||||
msgid "Email resent"
|
||||
@ -3109,7 +3109,7 @@ msgstr "Wysłano wiadomość"
|
||||
|
||||
#: apps/remix/app/routes/_unauthenticated+/check-email.tsx
|
||||
msgid "Email sent!"
|
||||
msgstr "E-mail wysłany!"
|
||||
msgstr "Wiadomość została wysłana!"
|
||||
|
||||
#: apps/remix/app/components/dialogs/team-email-delete-dialog.tsx
|
||||
msgid "Email verification has been removed"
|
||||
@ -3209,7 +3209,7 @@ msgstr "Wprowadź szczegóły swojej marki"
|
||||
|
||||
#: apps/remix/app/components/general/claim-account.tsx
|
||||
msgid "Enter your email"
|
||||
msgstr "Wprowadź swój adres e-mail"
|
||||
msgstr "Wpisz adres e-mail"
|
||||
|
||||
#: apps/remix/app/components/general/direct-template/direct-template-configure-form.tsx
|
||||
msgid "Enter your email address to receive the completed document."
|
||||
@ -3217,7 +3217,7 @@ msgstr "Wprowadź swój adres e-mail, aby otrzymać ukończony dokument."
|
||||
|
||||
#: apps/remix/app/components/general/claim-account.tsx
|
||||
msgid "Enter your name"
|
||||
msgstr "Wprowadź swoje imię"
|
||||
msgstr "Wpisz nazwę"
|
||||
|
||||
#: apps/remix/app/components/general/document-signing/document-signing-auth-dialog.tsx
|
||||
msgid "Enter your password"
|
||||
@ -3321,7 +3321,7 @@ msgstr "Wygasa {0}"
|
||||
#: packages/ui/primitives/template-flow/add-template-settings.tsx
|
||||
#: packages/ui/primitives/document-flow/add-settings.tsx
|
||||
msgid "External ID"
|
||||
msgstr "Zewnętrzny ID"
|
||||
msgstr "Identyfikator zewnętrzny"
|
||||
|
||||
#: apps/remix/app/components/dialogs/folder-create-dialog.tsx
|
||||
msgid "Failed to create folder"
|
||||
@ -3333,7 +3333,7 @@ msgstr "Nie udało się utworzyć roszczenia subskrypcyjnego."
|
||||
|
||||
#: apps/remix/app/components/dialogs/folder-delete-dialog.tsx
|
||||
msgid "Failed to delete folder"
|
||||
msgstr "Nie udało się usunąć folder"
|
||||
msgstr "Nie udało się usunąć folderu"
|
||||
|
||||
#: apps/remix/app/components/dialogs/claim-delete-dialog.tsx
|
||||
msgid "Failed to delete subscription claim."
|
||||
@ -3365,7 +3365,7 @@ msgstr "Nie udało się wylogować ze wszystkich sesji"
|
||||
|
||||
#: apps/remix/app/routes/embed+/v1+/authoring+/document.edit.$id.tsx
|
||||
msgid "Failed to update document"
|
||||
msgstr "Nie udało się aktualizować dokumentu"
|
||||
msgstr "Nie udało się zaktualizować dokumentu"
|
||||
|
||||
#: apps/remix/app/components/tables/admin-document-recipient-item-table.tsx
|
||||
msgid "Failed to update recipient"
|
||||
@ -3385,7 +3385,7 @@ msgstr "Nie udało się zaktualizować webhooku"
|
||||
|
||||
#: packages/email/templates/bulk-send-complete.tsx
|
||||
msgid "Failed: {failedCount}"
|
||||
msgstr "Niepowodzenia: {failedCount}"
|
||||
msgstr "Niepowodzenie: {failedCount}"
|
||||
|
||||
#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx
|
||||
#: apps/remix/app/components/tables/admin-claims-table.tsx
|
||||
@ -3418,11 +3418,11 @@ msgstr "Etykieta pola"
|
||||
|
||||
#: packages/ui/primitives/document-flow/field-items-advanced-settings/text-field.tsx
|
||||
msgid "Field placeholder"
|
||||
msgstr "Zastępczy tekst pola"
|
||||
msgstr "Tekst zastępczy pola"
|
||||
|
||||
#: packages/lib/utils/document-audit-logs.ts
|
||||
msgid "Field prefilled by assistant"
|
||||
msgstr "Pole wstępnie wypełnione przez asystenta"
|
||||
msgstr "Wstępnie wypełniono pole przez asystenta"
|
||||
|
||||
#: packages/lib/utils/document-audit-logs.ts
|
||||
msgid "Field signed"
|
||||
@ -3461,19 +3461,19 @@ msgstr "Folder"
|
||||
|
||||
#: apps/remix/app/components/dialogs/folder-create-dialog.tsx
|
||||
msgid "Folder created successfully"
|
||||
msgstr "Folder utworzony pomyślnie"
|
||||
msgstr "Folder został utworzony"
|
||||
|
||||
#: apps/remix/app/components/dialogs/folder-delete-dialog.tsx
|
||||
msgid "Folder deleted successfully"
|
||||
msgstr "Folder został pomyślnie usunięty"
|
||||
msgstr "Folder został usunięty"
|
||||
|
||||
#: apps/remix/app/components/dialogs/folder-move-dialog.tsx
|
||||
msgid "Folder moved successfully"
|
||||
msgstr "Folder został pomyślnie przeniesiony"
|
||||
msgstr "Folder został przeniesiony"
|
||||
|
||||
#: apps/remix/app/components/dialogs/folder-create-dialog.tsx
|
||||
msgid "Folder Name"
|
||||
msgstr "Nazwa foldera"
|
||||
msgstr "Nazwa folderu"
|
||||
|
||||
#: apps/remix/app/components/dialogs/folder-settings-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/folder-move-dialog.tsx
|
||||
@ -3522,7 +3522,7 @@ msgstr "Darmowy"
|
||||
|
||||
#: packages/ui/primitives/document-flow/types.ts
|
||||
msgid "Free Signature"
|
||||
msgstr "Podpis wolny"
|
||||
msgstr "Swobodny podpis"
|
||||
|
||||
#: apps/remix/app/components/general/document-signing/document-signing-name-field.tsx
|
||||
#: apps/remix/app/components/general/document-signing/document-signing-form.tsx
|
||||
@ -3676,7 +3676,7 @@ msgstr "Asystent jako ostatni sygnatariusz oznacza, że nie będą oni mogli pod
|
||||
|
||||
#: apps/remix/app/components/embed/embed-document-signing-page.tsx
|
||||
msgid "Help complete the document for other signers."
|
||||
msgstr "Pomóc zakończyć dokument dla innych sygnatariuszy."
|
||||
msgstr "Pomóż zakończyć dokument dla innych podpisujących."
|
||||
|
||||
#: apps/remix/app/routes/_authenticated+/o.$orgUrl.settings.general.tsx
|
||||
msgid "Here you can edit your organisation details."
|
||||
@ -3742,11 +3742,11 @@ msgstr "Dom"
|
||||
#: apps/remix/app/components/dialogs/template-move-to-folder-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/document-move-to-folder-dialog.tsx
|
||||
msgid "Home (No Folder)"
|
||||
msgstr "Strona główna (Brak folderu)"
|
||||
msgstr "Strona główna (brak folderu)"
|
||||
|
||||
#: packages/lib/constants/recipient-roles.ts
|
||||
msgid "I am a signer of this document"
|
||||
msgstr "Jestem sygnatariuszem tego dokumentu"
|
||||
msgstr "Jestem podpisującym tego dokumentu"
|
||||
|
||||
#: packages/lib/constants/recipient-roles.ts
|
||||
msgid "I am a viewer of this document"
|
||||
@ -3775,11 +3775,11 @@ msgstr "Jestem pewny! Usuń to"
|
||||
|
||||
#: apps/remix/app/components/tables/admin-claims-table.tsx
|
||||
msgid "ID"
|
||||
msgstr "ID"
|
||||
msgstr "Identyfikator"
|
||||
|
||||
#: apps/remix/app/components/tables/admin-claims-table.tsx
|
||||
msgid "ID copied to clipboard"
|
||||
msgstr "ID skopiowane do schowka"
|
||||
msgstr "Identyfikator został skopiowany do schowka"
|
||||
|
||||
#: apps/remix/app/components/dialogs/passkey-create-dialog.tsx
|
||||
msgid "If you do not want to use the authenticator prompted, you can close it, which will then display the next available authenticator."
|
||||
@ -3854,16 +3854,16 @@ msgstr "Nieprawidłowy kod. Proszę spróbuj ponownie."
|
||||
|
||||
#: packages/ui/primitives/document-flow/add-signers.types.ts
|
||||
msgid "Invalid email"
|
||||
msgstr "Nieprawidłowy email"
|
||||
msgstr "Adres e-mail jest nieprawidłowy"
|
||||
|
||||
#: apps/remix/app/routes/_unauthenticated+/team.verify.email.$token.tsx
|
||||
msgid "Invalid link"
|
||||
msgstr "Nieprawidłowy link"
|
||||
msgstr "Link jest nieprawidłowy"
|
||||
|
||||
#: apps/remix/app/routes/_unauthenticated+/organisation.invite.$token.tsx
|
||||
#: apps/remix/app/routes/_unauthenticated+/organisation.decline.$token.tsx
|
||||
msgid "Invalid token"
|
||||
msgstr "Nieprawidłowy token"
|
||||
msgstr "Token jest nieprawidłowy"
|
||||
|
||||
#: apps/remix/app/components/forms/reset-password.tsx
|
||||
msgid "Invalid token provided. Please try again."
|
||||
@ -3871,16 +3871,16 @@ msgstr "Podano nieprawidłowy token. Spróbuj ponownie."
|
||||
|
||||
#: apps/remix/app/components/general/organisations/organisation-invitations.tsx
|
||||
msgid "Invitation accepted"
|
||||
msgstr "Zaproszenie zaakceptowane"
|
||||
msgstr "Zaproszenie został zaakceptowane"
|
||||
|
||||
#: apps/remix/app/routes/_unauthenticated+/organisation.invite.$token.tsx
|
||||
msgid "Invitation accepted!"
|
||||
msgstr "Zaproszenie zaakceptowane!"
|
||||
msgstr "Zaproszenie zostało zaakceptowane!"
|
||||
|
||||
#: apps/remix/app/routes/_unauthenticated+/organisation.decline.$token.tsx
|
||||
#: apps/remix/app/components/general/organisations/organisation-invitations.tsx
|
||||
msgid "Invitation declined"
|
||||
msgstr "Zaproszenie odrzucone"
|
||||
msgstr "Zaproszenie zzostało odrzucone"
|
||||
|
||||
#: apps/remix/app/components/tables/organisation-member-invites-table.tsx
|
||||
msgid "Invitation has been deleted"
|
||||
@ -4703,11 +4703,11 @@ msgstr "Administrator organizacji"
|
||||
|
||||
#: apps/remix/app/components/dialogs/admin-organisation-create-dialog.tsx
|
||||
msgid "Organisation created"
|
||||
msgstr "Organizacja utworzona"
|
||||
msgstr "Organizacja została utworzona"
|
||||
|
||||
#: apps/remix/app/routes/_authenticated+/o.$orgUrl.settings.groups.$id.tsx
|
||||
msgid "Organisation group not found"
|
||||
msgstr "Grupa organizacji nie znaleziona"
|
||||
msgstr "Grupa organizacji nie została znaleziona"
|
||||
|
||||
#: apps/remix/app/routes/_authenticated+/o.$orgUrl.settings.groups.$id.tsx
|
||||
msgid "Organisation Group Settings"
|
||||
@ -4716,7 +4716,7 @@ msgstr "Ustawienia grupy organizacji"
|
||||
#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx
|
||||
#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx
|
||||
msgid "Organisation has been updated successfully"
|
||||
msgstr "Organizacja zaktualizowana pomyślnie"
|
||||
msgstr "Organizacja została zaktualizowana"
|
||||
|
||||
#: apps/remix/app/routes/_unauthenticated+/organisation.invite.$token.tsx
|
||||
msgid "Organisation invitation"
|
||||
@ -4756,16 +4756,16 @@ msgstr "Organizacja nie znaleziona"
|
||||
|
||||
#: apps/remix/app/routes/_authenticated+/o.$orgUrl.settings.preferences.tsx
|
||||
msgid "Organisation Preferences"
|
||||
msgstr "Preferencje organizacji"
|
||||
msgstr "Ustawienia organizacji"
|
||||
|
||||
#: apps/remix/app/components/dialogs/organisation-group-create-dialog.tsx
|
||||
msgid "Organisation role"
|
||||
msgstr "Rola organizacyjna"
|
||||
msgstr "Rola w organizacji"
|
||||
|
||||
#: apps/remix/app/routes/_authenticated+/o.$orgUrl.settings.groups.$id.tsx
|
||||
#: apps/remix/app/components/dialogs/organisation-member-invite-dialog.tsx
|
||||
msgid "Organisation Role"
|
||||
msgstr "Rola organizacyjna"
|
||||
msgstr "Rola w organizacji"
|
||||
|
||||
#: apps/remix/app/components/general/org-menu-switcher.tsx
|
||||
msgid "Organisation settings"
|
||||
@ -4777,12 +4777,12 @@ msgstr "Ustawienia organizacji"
|
||||
|
||||
#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx
|
||||
msgid "Organisation Teams"
|
||||
msgstr "Zespoły organizacyjne"
|
||||
msgstr "Zespoły w organizacji"
|
||||
|
||||
#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx
|
||||
#: apps/remix/app/components/forms/organisation-update-form.tsx
|
||||
msgid "Organisation URL"
|
||||
msgstr "URL organizacji"
|
||||
msgstr "Adres URL organizacji"
|
||||
|
||||
#: apps/remix/app/routes/_authenticated+/dashboard.tsx
|
||||
#: apps/remix/app/routes/_authenticated+/settings+/organisations.tsx
|
||||
@ -4914,7 +4914,7 @@ msgstr "Hasło zostało zaktualizowane"
|
||||
|
||||
#: packages/email/template-components/template-reset-password.tsx
|
||||
msgid "Password updated!"
|
||||
msgstr "Hasło zaktualizowane!"
|
||||
msgstr "Hasło zostało zaktualizowane!"
|
||||
|
||||
#: packages/ui/primitives/data-table/data/data.tsx
|
||||
msgid "Past"
|
||||
@ -4958,7 +4958,7 @@ msgstr "miesięcznie"
|
||||
#: apps/remix/app/components/general/billing-plans.tsx
|
||||
#: apps/remix/app/components/dialogs/organisation-create-dialog.tsx
|
||||
msgid "per year"
|
||||
msgstr "na rok"
|
||||
msgstr "rocznie"
|
||||
|
||||
#: apps/remix/app/components/tables/user-organisations-table.tsx
|
||||
msgid "Personal"
|
||||
@ -4986,7 +4986,7 @@ msgstr "Wybierz hasło"
|
||||
|
||||
#: apps/remix/app/components/general/user-profile-timur.tsx
|
||||
msgid "Pick any of the following agreements below and start signing to get started"
|
||||
msgstr "Wybierz dowolną z poniższych umów i zacznij podpisywanie, aby rozpocząć"
|
||||
msgstr "Wybierz umowę i zacznij podpisywanie"
|
||||
|
||||
#: apps/remix/app/components/general/folder/folder-card.tsx
|
||||
msgid "Pin"
|
||||
@ -4996,7 +4996,7 @@ msgstr "Przypnij"
|
||||
#: packages/ui/primitives/document-flow/field-items-advanced-settings/number-field.tsx
|
||||
#: packages/ui/primitives/document-flow/field-items-advanced-settings/number-field.tsx
|
||||
msgid "Placeholder"
|
||||
msgstr "Zastępczy tekst"
|
||||
msgstr "Tekst zastępczy"
|
||||
|
||||
#. placeholder {0}: _(actionVerb).toLowerCase()
|
||||
#: packages/email/template-components/template-document-invite.tsx
|
||||
@ -5042,11 +5042,11 @@ msgstr "Najpierw skonfiguruj dokument"
|
||||
|
||||
#: packages/lib/server-only/auth/send-confirmation-email.ts
|
||||
msgid "Please confirm your email"
|
||||
msgstr "Proszę potwierdzić swój email"
|
||||
msgstr "Potwierdź adres e-mail"
|
||||
|
||||
#: packages/email/templates/confirm-email.tsx
|
||||
msgid "Please confirm your email address"
|
||||
msgstr "Proszę potwierdzić swój adres email"
|
||||
msgstr "Potwierdź adres e-mail"
|
||||
|
||||
#: apps/remix/app/components/dialogs/document-delete-dialog.tsx
|
||||
msgid "Please contact support if you would like to revert this action."
|
||||
@ -5064,7 +5064,7 @@ msgstr "Wpisz nazwę tokena. Pomoże to później w jego identyfikacji."
|
||||
#: apps/remix/app/components/forms/signup.tsx
|
||||
#: apps/remix/app/components/forms/profile.tsx
|
||||
msgid "Please enter a valid name."
|
||||
msgstr "Proszę wpisać poprawną nazwę."
|
||||
msgstr "Wpisz prawdłową nazwę."
|
||||
|
||||
#: apps/remix/app/components/general/document-signing/document-signing-form.tsx
|
||||
msgid "Please mark as viewed to complete"
|
||||
@ -5116,7 +5116,7 @@ msgstr "Proszę przejrzeć dokument przed podpisaniem."
|
||||
|
||||
#: apps/remix/app/components/dialogs/template-use-dialog.tsx
|
||||
msgid "Please select a PDF file"
|
||||
msgstr "Proszę wybrać plik PDF"
|
||||
msgstr "Wybierz plik PDF"
|
||||
|
||||
#: apps/remix/app/components/forms/send-confirmation-email.tsx
|
||||
msgid "Please try again and make sure you enter the correct email address."
|
||||
@ -5124,7 +5124,7 @@ msgstr "Spróbuj ponownie i upewnij się, że wprowadzasz poprawny adres email."
|
||||
|
||||
#: apps/remix/app/components/dialogs/template-create-dialog.tsx
|
||||
msgid "Please try again later."
|
||||
msgstr "Proszę spróbować ponownie później."
|
||||
msgstr "Spróbuj ponownie później."
|
||||
|
||||
#: packages/ui/primitives/pdf-viewer.tsx
|
||||
#: packages/ui/primitives/pdf-viewer.tsx
|
||||
@ -5157,7 +5157,7 @@ msgstr "Wstępnie sformatowany szablon CSV z przykładowymi danymi."
|
||||
#: apps/remix/app/components/general/teams/team-settings-nav-mobile.tsx
|
||||
#: apps/remix/app/components/general/teams/team-settings-nav-desktop.tsx
|
||||
msgid "Preferences"
|
||||
msgstr "Preferencje"
|
||||
msgstr "Ustawienia"
|
||||
|
||||
#: packages/ui/primitives/data-table/data/data.tsx
|
||||
msgid "Present"
|
||||
@ -5323,7 +5323,7 @@ msgstr "E-mail z prośbą o podpisanie przez odbiorcę"
|
||||
|
||||
#: apps/remix/app/components/tables/admin-document-recipient-item-table.tsx
|
||||
msgid "Recipient updated"
|
||||
msgstr "Odbiorca zaktualizowany"
|
||||
msgstr "Odbiorca został zaktualizowany"
|
||||
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/audit-log.tsx
|
||||
#: apps/remix/app/routes/_authenticated+/admin+/documents.$id.tsx
|
||||
@ -5343,7 +5343,7 @@ msgstr "Odbiorcy nadal zachowają swoją kopię dokumentu"
|
||||
|
||||
#: apps/remix/app/components/forms/2fa/recovery-code-list.tsx
|
||||
msgid "Recovery code copied"
|
||||
msgstr "Kod odzyskiwania skopiowany"
|
||||
msgstr "Kod odzyskiwania został skopiowany"
|
||||
|
||||
#: apps/remix/app/routes/_authenticated+/settings+/security._index.tsx
|
||||
msgid "Recovery codes"
|
||||
@ -5379,7 +5379,7 @@ msgstr "Odrzuć dokument"
|
||||
#: packages/ui/primitives/data-table/data/data.tsx
|
||||
#: packages/lib/constants/document.ts
|
||||
msgid "Rejected"
|
||||
msgstr "Odrzucony"
|
||||
msgstr "Odrzucono"
|
||||
|
||||
#: packages/email/template-components/template-document-rejection-confirmed.tsx
|
||||
msgid "Rejection Confirmed"
|
||||
@ -6025,7 +6025,7 @@ msgstr "Identyfikator podpisu"
|
||||
|
||||
#: packages/ui/primitives/signature-pad/signature-pad-draw.tsx
|
||||
msgid "Signature is too small"
|
||||
msgstr "Podpis jest za mały"
|
||||
msgstr "Podpis jest zbyt mały"
|
||||
|
||||
#: apps/remix/app/components/forms/profile.tsx
|
||||
msgid "Signature Pad cannot be empty."
|
||||
@ -6048,15 +6048,15 @@ msgstr "Podpisy pojawią się po ukończeniu dokumentu"
|
||||
#: packages/ui/components/document/document-read-only-fields.tsx
|
||||
#: packages/lib/constants/recipient-roles.ts
|
||||
msgid "Signed"
|
||||
msgstr "Podpisano"
|
||||
msgstr "Podpisał"
|
||||
|
||||
#: packages/lib/constants/recipient-roles.ts
|
||||
msgid "Signer"
|
||||
msgstr "Sygnatariusz"
|
||||
msgstr "Podpisujący"
|
||||
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
|
||||
msgid "Signer Events"
|
||||
msgstr "Wydarzenia sygnatariusza"
|
||||
msgstr "Zdarzenia podpisujących"
|
||||
|
||||
#: packages/lib/constants/recipient-roles.ts
|
||||
msgid "Signers"
|
||||
@ -6262,17 +6262,17 @@ msgstr "Stripe"
|
||||
|
||||
#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx
|
||||
msgid "Stripe customer created successfully"
|
||||
msgstr "Klient Stripe utworzony pomyślnie"
|
||||
msgstr "Klient Stripe został utworzony"
|
||||
|
||||
#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx
|
||||
msgid "Stripe Customer ID"
|
||||
msgstr "ID klienta Stripe"
|
||||
msgstr "Identyfikator klienta Stripe"
|
||||
|
||||
#: apps/remix/app/components/embed/authoring/configure-document-advanced-settings.tsx
|
||||
#: packages/ui/primitives/template-flow/add-template-settings.tsx
|
||||
#: packages/ui/primitives/document-flow/add-subject.tsx
|
||||
msgid "Subject <0>(Optional)</0>"
|
||||
msgstr "Temat <0>(Opcjonalnie)</0>"
|
||||
msgstr "Temat <0>(opcjonalnie)</0>"
|
||||
|
||||
#: apps/remix/app/components/general/billing-plans.tsx
|
||||
#: apps/remix/app/components/general/billing-plans.tsx
|
||||
@ -6395,19 +6395,19 @@ msgstr "Liczba zespołów"
|
||||
#: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings._index.tsx
|
||||
#: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings._index.tsx
|
||||
msgid "Team email"
|
||||
msgstr "E-mail zespołu"
|
||||
msgstr "Adres e-mail zespołu"
|
||||
|
||||
#: apps/remix/app/components/general/teams/team-email-usage.tsx
|
||||
msgid "Team Email"
|
||||
msgstr "E-mail zespołu"
|
||||
msgstr "Adres e-mail zespołu"
|
||||
|
||||
#: apps/remix/app/routes/_unauthenticated+/team.verify.email.$token.tsx
|
||||
msgid "Team email already verified!"
|
||||
msgstr "E-mail zespołu został już zweryfikowany!"
|
||||
msgstr "Adres e-mail zespołu został już zweryfikowany!"
|
||||
|
||||
#: apps/remix/app/components/dialogs/team-email-delete-dialog.tsx
|
||||
msgid "Team email has been removed"
|
||||
msgstr "E-mail zespołu został usunięty"
|
||||
msgstr "Adres e-mail zespołu został usunięty"
|
||||
|
||||
#. placeholder {0}: team.name
|
||||
#: packages/lib/server-only/team/delete-team-email.ts
|
||||
@ -6416,7 +6416,7 @@ msgstr "Email zespołowy został anulowany dla {0}"
|
||||
|
||||
#: packages/email/templates/team-email-removed.tsx
|
||||
msgid "Team email removed"
|
||||
msgstr "Email zespołowy usunięty"
|
||||
msgstr "Adres e-mail zespołu usunięty"
|
||||
|
||||
#: packages/email/templates/team-email-removed.tsx
|
||||
msgid "Team email removed for {teamName} on Documenso"
|
||||
@ -6424,15 +6424,15 @@ msgstr "Email zespołowy usunięty dla {teamName} na Documenso"
|
||||
|
||||
#: apps/remix/app/routes/_unauthenticated+/team.verify.email.$token.tsx
|
||||
msgid "Team email verification"
|
||||
msgstr "Weryfikacja e-maila zespołu"
|
||||
msgstr "Weryfikacja adresu e-mail zespołu"
|
||||
|
||||
#: apps/remix/app/routes/_unauthenticated+/team.verify.email.$token.tsx
|
||||
msgid "Team email verified!"
|
||||
msgstr "E-mail zespołu zweryfikowany!"
|
||||
msgstr "Adres e-mail zespołu został zweryfikowany!"
|
||||
|
||||
#: apps/remix/app/components/dialogs/team-email-update-dialog.tsx
|
||||
msgid "Team email was updated."
|
||||
msgstr "E-mail zespołu został zaktualizowany."
|
||||
msgstr "Adres e-mail zespołu został zaktualizowany."
|
||||
|
||||
#: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.groups.tsx
|
||||
msgid "Team Groups"
|
||||
@ -6476,7 +6476,7 @@ msgstr "Szablony tylko dla zespołu nie są nigdzie linkowane i są widoczne tyl
|
||||
|
||||
#: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.preferences.tsx
|
||||
msgid "Team Preferences"
|
||||
msgstr "Preferencje zespołu"
|
||||
msgstr "Ustawienia zespołu"
|
||||
|
||||
#: apps/remix/app/routes/_authenticated+/o.$orgUrl.settings.groups.$id.tsx
|
||||
#: apps/remix/app/components/dialogs/team-member-create-dialog.tsx
|
||||
@ -6528,19 +6528,19 @@ msgstr "Szablon"
|
||||
|
||||
#: apps/remix/app/routes/embed+/v1+/authoring_.completed.create.tsx
|
||||
msgid "Template Created"
|
||||
msgstr "Szablon utworzony"
|
||||
msgstr "Szablon został utworzony"
|
||||
|
||||
#: apps/remix/app/components/dialogs/template-delete-dialog.tsx
|
||||
msgid "Template deleted"
|
||||
msgstr "Szablon usunięty"
|
||||
msgstr "Szablon został usunięty"
|
||||
|
||||
#: apps/remix/app/components/dialogs/template-create-dialog.tsx
|
||||
msgid "Template document uploaded"
|
||||
msgstr "Dokument szablonu przesłany"
|
||||
msgstr "Szablon dokumentu został przesłany"
|
||||
|
||||
#: apps/remix/app/components/dialogs/template-duplicate-dialog.tsx
|
||||
msgid "Template duplicated"
|
||||
msgstr "Szablon skopiowany"
|
||||
msgstr "Szablon został zduplikowany"
|
||||
|
||||
#: apps/remix/app/components/dialogs/public-profile-template-manage-dialog.tsx
|
||||
msgid "Template has been removed from your public profile."
|
||||
@ -6556,11 +6556,11 @@ msgstr "Szablon używa starej metody wstawiania pól"
|
||||
|
||||
#: apps/remix/app/components/dialogs/template-move-to-folder-dialog.tsx
|
||||
msgid "Template moved"
|
||||
msgstr "Szablon przeniesiony"
|
||||
msgstr "Szablon został przeniesiony"
|
||||
|
||||
#: apps/remix/app/components/general/template/template-edit-form.tsx
|
||||
msgid "Template saved"
|
||||
msgstr "Szablon zapisany"
|
||||
msgstr "Szablon został zapisany"
|
||||
|
||||
#: packages/ui/primitives/template-flow/add-template-settings.tsx
|
||||
msgid "Template title"
|
||||
@ -6568,7 +6568,7 @@ msgstr "Tytuł szablonu"
|
||||
|
||||
#: apps/remix/app/routes/embed+/v1+/authoring+/template.edit.$id.tsx
|
||||
msgid "Template updated successfully"
|
||||
msgstr "Szablon zaktualizowany pomyślnie"
|
||||
msgstr "Szablon został zaktualizowany"
|
||||
|
||||
#: apps/remix/app/routes/_authenticated+/t.$teamUrl+/templates._index.tsx
|
||||
#: apps/remix/app/routes/_authenticated+/t.$teamUrl+/templates.$id._index.tsx
|
||||
@ -6618,15 +6618,15 @@ msgstr "To w porządku, zdarza się! Kliknij przycisk poniżej, aby zresetować
|
||||
|
||||
#: apps/remix/app/components/dialogs/admin-user-delete-dialog.tsx
|
||||
msgid "The account has been deleted successfully."
|
||||
msgstr "Konto zostało pomyślnie usunięte."
|
||||
msgstr "Konto zostało usunięte."
|
||||
|
||||
#: apps/remix/app/components/dialogs/admin-user-disable-dialog.tsx
|
||||
msgid "The account has been disabled successfully."
|
||||
msgstr "Konto zostało pomyślnie wyłączone."
|
||||
msgstr "Konto zostało wyłączone."
|
||||
|
||||
#: apps/remix/app/components/dialogs/admin-user-enable-dialog.tsx
|
||||
msgid "The account has been enabled successfully."
|
||||
msgstr "Konto zostało pomyślnie włączone."
|
||||
msgstr "Konto zostało włączone."
|
||||
|
||||
#: packages/ui/components/recipient/recipient-action-auth-select.tsx
|
||||
msgid "The authentication methods required for recipients to sign fields"
|
||||
@ -6745,7 +6745,7 @@ msgstr "Organizacja, której szukasz, mogła zostać usunięta, zmieniona nazwa
|
||||
|
||||
#: apps/remix/app/components/general/generic-error-layout.tsx
|
||||
msgid "The page you are looking for was moved, removed, renamed or might never have existed."
|
||||
msgstr "Strona, której szukasz, została przeniesiona, usunięta, zmieniona lub mogła nigdy nie istnieć."
|
||||
msgstr "Strona została przeniesiona, usunięta, zmieniona lub mogła nie istnieć."
|
||||
|
||||
#: apps/remix/app/components/forms/public-profile-form.tsx
|
||||
msgid "The profile link has been copied to your clipboard"
|
||||
@ -7255,7 +7255,7 @@ msgstr "Token został utworzony"
|
||||
|
||||
#: apps/remix/app/components/dialogs/token-delete-dialog.tsx
|
||||
msgid "Token deleted"
|
||||
msgstr "Token usunięty"
|
||||
msgstr "Token został usunięty"
|
||||
|
||||
#: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.tokens.tsx
|
||||
msgid "Token doesn't have an expiration date"
|
||||
@ -7267,7 +7267,7 @@ msgstr "Data wygaśnięcia tokena"
|
||||
|
||||
#: apps/remix/app/components/forms/reset-password.tsx
|
||||
msgid "Token has expired. Please try again."
|
||||
msgstr "Token wygasł. Proszę spróbować ponownie."
|
||||
msgstr "Token wygasł. Spróbuj ponownie."
|
||||
|
||||
#: apps/remix/app/components/forms/token.tsx
|
||||
msgid "Token name"
|
||||
@ -7460,7 +7460,7 @@ msgstr "Odepnij"
|
||||
|
||||
#: apps/remix/app/components/dialogs/team-group-create-dialog.tsx
|
||||
msgid "Untitled Group"
|
||||
msgstr "Grupa bez tytułu"
|
||||
msgstr "Grupa bez nazwy"
|
||||
|
||||
#: apps/remix/app/routes/_authenticated+/o.$orgUrl.settings.groups.$id.tsx
|
||||
#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx
|
||||
@ -7545,7 +7545,7 @@ msgstr "Zaktualizuj zespół"
|
||||
#: apps/remix/app/components/dialogs/team-email-update-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/team-email-update-dialog.tsx
|
||||
msgid "Update team email"
|
||||
msgstr "Zaktualizuj e-mail zespołu"
|
||||
msgstr "Zaktualizuj adres e-mail zespołu"
|
||||
|
||||
#: apps/remix/app/components/dialogs/team-group-update-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/team-group-update-dialog.tsx
|
||||
@ -7575,7 +7575,7 @@ msgstr "Aktualizowanie hasła..."
|
||||
|
||||
#: apps/remix/app/routes/_unauthenticated+/articles.signature-disclosure.tsx
|
||||
msgid "Updating Your Information"
|
||||
msgstr "Aktualizacja Twoich informacji"
|
||||
msgstr "Aktualizowanie informacji"
|
||||
|
||||
#: packages/ui/primitives/document-upload.tsx
|
||||
#: packages/ui/primitives/document-dropzone.tsx
|
||||
@ -7585,7 +7585,7 @@ msgstr "Ulepsz"
|
||||
#. placeholder {0}: organisation.name
|
||||
#: apps/remix/app/components/general/billing-plans.tsx
|
||||
msgid "Upgrade <0>{0}</0> to {planName}"
|
||||
msgstr "Uaktualnij <0>{0}</0> do {planName}"
|
||||
msgstr "Ulepsz <0>{0}</0> do {planName}"
|
||||
|
||||
#: apps/remix/app/components/general/document/document-drop-zone-wrapper.tsx
|
||||
msgid "Upgrade your plan to upload more documents"
|
||||
@ -7632,11 +7632,11 @@ msgstr "Prześlij podpis"
|
||||
#: packages/ui/primitives/document-upload.tsx
|
||||
#: packages/ui/primitives/document-dropzone.tsx
|
||||
msgid "Upload Template Document"
|
||||
msgstr "Prześlij dokument szablonu"
|
||||
msgstr "Prześlij szablon dokumentu"
|
||||
|
||||
#: apps/remix/app/components/forms/branding-preferences-form.tsx
|
||||
msgid "Upload your brand logo (max 5MB, JPG, PNG, or WebP)"
|
||||
msgstr "Prześlij logo swojej marki (maks. 5MB, JPG, PNG lub WebP)"
|
||||
msgstr "Prześlij swoje logo (maks. 5 MB, JPG, PNG lub WebP)"
|
||||
|
||||
#: apps/remix/app/components/general/template/template-page-view-information.tsx
|
||||
#: apps/remix/app/components/general/document/document-page-view-information.tsx
|
||||
@ -7745,11 +7745,11 @@ msgstr "Zweryfikuj teraz"
|
||||
|
||||
#: apps/remix/app/components/general/verify-email-banner.tsx
|
||||
msgid "Verify your email address"
|
||||
msgstr "Zweryfikuj swój adres e-mail"
|
||||
msgstr "Zweryfikuj adres e-mail"
|
||||
|
||||
#: apps/remix/app/components/general/verify-email-banner.tsx
|
||||
msgid "Verify your email address to unlock all features."
|
||||
msgstr "Zweryfikuj swój adres e-mail, aby odblokować wszystkie funkcje."
|
||||
msgstr "Zweryfikuj adres e-mail, aby odblokować wszystkie funkcje."
|
||||
|
||||
#: apps/remix/app/components/general/document/document-upload.tsx
|
||||
msgid "Verify your email to upload documents."
|
||||
@ -7757,7 +7757,7 @@ msgstr "Zweryfikuj adres e-mail, aby przesłać dokumenty."
|
||||
|
||||
#: packages/email/templates/confirm-team-email.tsx
|
||||
msgid "Verify your team email address"
|
||||
msgstr "Zweryfikuj swój adres e-mail zespołu"
|
||||
msgstr "Zweryfikuj adres e-mail zespołu"
|
||||
|
||||
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/audit-log.tsx
|
||||
msgid "Version History"
|
||||
@ -7864,7 +7864,7 @@ msgstr "Wyświetl zespoły"
|
||||
#: apps/remix/app/components/embed/multisign/multi-sign-document-list.tsx
|
||||
#: packages/lib/constants/recipient-roles.ts
|
||||
msgid "Viewed"
|
||||
msgstr "Wyświetlono"
|
||||
msgstr "Wyświetlił"
|
||||
|
||||
#: packages/lib/constants/recipient-roles.ts
|
||||
msgid "Viewer"
|
||||
@ -8210,7 +8210,7 @@ msgstr "Witaj"
|
||||
|
||||
#: apps/remix/app/routes/_unauthenticated+/signin.tsx
|
||||
msgid "Welcome back, we are lucky to have you."
|
||||
msgstr "Witamy z powrotem, mamy szczęście, że mamy cię."
|
||||
msgstr "Witaj ponownie. Mamy szczęście, że Cię mamy."
|
||||
|
||||
#: apps/remix/app/routes/_authenticated+/dashboard.tsx
|
||||
msgid "Welcome back! Here's an overview of your account."
|
||||
@ -8716,7 +8716,7 @@ msgstr "Twój dokument został pomyślnie utworzony na podstawie szablonu."
|
||||
|
||||
#: apps/remix/app/routes/embed+/v1+/authoring_.completed.create.tsx
|
||||
msgid "Your document has been created successfully"
|
||||
msgstr "Twój dokument został pomyślnie utworzony"
|
||||
msgstr "Dokument został utworzony"
|
||||
|
||||
#: packages/email/template-components/template-document-super-delete.tsx
|
||||
msgid "Your document has been deleted by an admin!"
|
||||
@ -8728,25 +8728,25 @@ msgstr "Twój dokument został pomyślnie ponownie wysłany."
|
||||
|
||||
#: apps/remix/app/components/general/document/document-edit-form.tsx
|
||||
msgid "Your document has been sent successfully."
|
||||
msgstr "Twój dokument został pomyślnie wysłany."
|
||||
msgstr "Dokument został wysłany."
|
||||
|
||||
#: apps/remix/app/components/dialogs/document-duplicate-dialog.tsx
|
||||
msgid "Your document has been successfully duplicated."
|
||||
msgstr "Twój dokument został pomyślnie zduplikowany."
|
||||
msgstr "Dokument został zduplikowany."
|
||||
|
||||
#: apps/remix/app/components/general/document/document-upload.tsx
|
||||
#: apps/remix/app/components/general/document/document-drop-zone-wrapper.tsx
|
||||
msgid "Your document has been uploaded successfully."
|
||||
msgstr "Twój dokument został pomyślnie załadowany."
|
||||
msgstr "Dokument został przesłany."
|
||||
|
||||
#: apps/remix/app/components/dialogs/template-create-dialog.tsx
|
||||
msgid "Your document has been uploaded successfully. You will be redirected to the template page."
|
||||
msgstr "Twój dokument został pomyślnie załadowany. Zostaniesz przekierowany na stronę szablonu."
|
||||
msgstr "Dokument został przesłany. Zostaniesz przekierowany na stronę szablonu."
|
||||
|
||||
#: apps/remix/app/routes/_authenticated+/o.$orgUrl.settings.preferences.tsx
|
||||
#: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.preferences.tsx
|
||||
msgid "Your document preferences have been updated"
|
||||
msgstr "Preferencje dokumentu zostały zaktualizowane"
|
||||
msgstr "Ustawienia dokumentu zostały zaktualizowane"
|
||||
|
||||
#: apps/remix/app/components/general/app-command-menu.tsx
|
||||
msgid "Your documents"
|
||||
@ -8754,11 +8754,11 @@ msgstr "Twoje dokumenty"
|
||||
|
||||
#: apps/remix/app/routes/_unauthenticated+/verify-email.$token.tsx
|
||||
msgid "Your email has already been confirmed. You can now use all features of Documenso."
|
||||
msgstr "Twój e-mail został już potwierdzony. Możesz teraz korzystać z wszystkich funkcji Documenso."
|
||||
msgstr "Adres e-mail został już potwierdzony. Możesz korzystać ze wszystkich funkcji Documenso."
|
||||
|
||||
#: apps/remix/app/routes/_unauthenticated+/verify-email.$token.tsx
|
||||
msgid "Your email has been successfully confirmed! You can now use all features of Documenso."
|
||||
msgstr "Twój adres e-mail został pomyślnie potwierdzony! Możesz teraz korzystać ze wszystkich funkcji Documenso."
|
||||
msgstr "Adres e-mail został potwierdzony! Możesz korzystać ze wszystkich funkcji Documenso."
|
||||
|
||||
#. placeholder {0}: teamEmail.team.name
|
||||
#. placeholder {1}: teamEmail.team.url
|
||||
@ -8781,15 +8781,15 @@ msgstr "Twoje nowe hasło nie może być takie samo jak stare hasło."
|
||||
|
||||
#: apps/remix/app/components/dialogs/organisation-create-dialog.tsx
|
||||
msgid "Your organisation has been created."
|
||||
msgstr "Twoja organizacja została utworzona."
|
||||
msgstr "Organizacja została utworzona."
|
||||
|
||||
#: apps/remix/app/components/dialogs/organisation-delete-dialog.tsx
|
||||
msgid "Your organisation has been successfully deleted."
|
||||
msgstr "Twoja organizacja została pomyślnie usunięta."
|
||||
msgstr "Organizacja została usunięta."
|
||||
|
||||
#: apps/remix/app/components/forms/organisation-update-form.tsx
|
||||
msgid "Your organisation has been successfully updated."
|
||||
msgstr "Twoja organizacja została pomyślnie zaktualizowana."
|
||||
msgstr "Organizacja została zaktualizowana."
|
||||
|
||||
#: apps/remix/app/components/forms/reset-password.tsx
|
||||
#: apps/remix/app/components/forms/password.tsx
|
||||
@ -8814,11 +8814,11 @@ msgstr "Twój plan nie wspiera zapraszania członków. Zaktualizuj swój plan lu
|
||||
|
||||
#: apps/remix/app/components/forms/profile.tsx
|
||||
msgid "Your profile has been updated successfully."
|
||||
msgstr "Twój profil został pomyślnie zaktualizowany."
|
||||
msgstr "Profil został zaktualizowany."
|
||||
|
||||
#: apps/remix/app/routes/_authenticated+/admin+/users.$id.tsx
|
||||
msgid "Your profile has been updated."
|
||||
msgstr "Twój profil został zaktualizowany."
|
||||
msgstr "Profil został zaktualizowany."
|
||||
|
||||
#: apps/remix/app/components/forms/public-profile-form.tsx
|
||||
msgid "Your public profile has been updated."
|
||||
@ -8835,35 +8835,35 @@ msgstr "Twoje kody odzyskiwania są wymienione poniżej. Proszę przechowywać j
|
||||
|
||||
#: apps/remix/app/components/dialogs/team-create-dialog.tsx
|
||||
msgid "Your team has been created."
|
||||
msgstr "Twój zespół został utworzony."
|
||||
msgstr "Zespół został utworzony."
|
||||
|
||||
#: apps/remix/app/components/dialogs/team-delete-dialog.tsx
|
||||
msgid "Your team has been successfully deleted."
|
||||
msgstr "Twój zespół został pomyślnie usunięty."
|
||||
msgstr "Zespół został usunięty."
|
||||
|
||||
#: apps/remix/app/components/forms/team-update-form.tsx
|
||||
msgid "Your team has been successfully updated."
|
||||
msgstr "Twój zespół został pomyślnie zaktualizowany."
|
||||
msgstr "Zespół został zaktualizowany."
|
||||
|
||||
#: apps/remix/app/routes/embed+/v1+/authoring_.completed.create.tsx
|
||||
msgid "Your template has been created successfully"
|
||||
msgstr "Twój szablon został pomyślnie utworzony"
|
||||
msgstr "Szablon został utworzony"
|
||||
|
||||
#: apps/remix/app/components/dialogs/template-duplicate-dialog.tsx
|
||||
msgid "Your template has been duplicated successfully."
|
||||
msgstr "Twój szablon został pomyślnie zduplikowany."
|
||||
msgstr "Szablon został zduplikowany."
|
||||
|
||||
#: apps/remix/app/components/dialogs/template-delete-dialog.tsx
|
||||
msgid "Your template has been successfully deleted."
|
||||
msgstr "Twój szablon został pomyślnie usunięty."
|
||||
msgstr "Szablon został usunięty."
|
||||
|
||||
#: apps/remix/app/components/dialogs/template-duplicate-dialog.tsx
|
||||
msgid "Your template will be duplicated."
|
||||
msgstr "Twój szablon zostanie zduplikowany."
|
||||
msgstr "Szablon zostanie zduplikowany."
|
||||
|
||||
#: apps/remix/app/components/general/template/template-edit-form.tsx
|
||||
msgid "Your templates has been saved successfully."
|
||||
msgstr "Twoje szablony zostały pomyślnie zapisane."
|
||||
msgstr "Szablony zostały zapisane."
|
||||
|
||||
#: apps/remix/app/routes/_unauthenticated+/verify-email.$token.tsx
|
||||
msgid "Your token has expired!"
|
||||
|
||||
@ -96,6 +96,7 @@ export const ZCheckboxFieldMeta = ZBaseFieldMeta.extend({
|
||||
.optional(),
|
||||
validationRule: z.string().optional(),
|
||||
validationLength: z.number().optional(),
|
||||
direction: z.enum(['vertical', 'horizontal']).optional().default('vertical'),
|
||||
});
|
||||
|
||||
export type TCheckboxFieldMeta = z.infer<typeof ZCheckboxFieldMeta>;
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import type { Document, DocumentMeta, Recipient } from '@prisma/client';
|
||||
import type { Document, DocumentMeta, Recipient, WebhookTriggerEvents } from '@prisma/client';
|
||||
import {
|
||||
DocumentDistributionMethod,
|
||||
DocumentSigningOrder,
|
||||
@ -87,6 +87,13 @@ export const ZWebhookDocumentSchema = z.object({
|
||||
export type TWebhookRecipient = z.infer<typeof ZWebhookRecipientSchema>;
|
||||
export type TWebhookDocument = z.infer<typeof ZWebhookDocumentSchema>;
|
||||
|
||||
export type WebhookPayload = {
|
||||
event: WebhookTriggerEvents;
|
||||
payload: TWebhookDocument;
|
||||
createdAt: string;
|
||||
webhookEndpoint: string;
|
||||
};
|
||||
|
||||
export const mapDocumentToWebhookDocumentPayload = (
|
||||
document: Document & {
|
||||
recipients: Recipient[];
|
||||
|
||||
@ -0,0 +1,11 @@
|
||||
-- DropForeignKey
|
||||
ALTER TABLE "Document" DROP CONSTRAINT "Document_folderId_fkey";
|
||||
|
||||
-- DropForeignKey
|
||||
ALTER TABLE "Template" DROP CONSTRAINT "Template_folderId_fkey";
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Document" ADD CONSTRAINT "Document_folderId_fkey" FOREIGN KEY ("folderId") REFERENCES "Folder"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Template" ADD CONSTRAINT "Template_folderId_fkey" FOREIGN KEY ("folderId") REFERENCES "Folder"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
@ -397,7 +397,7 @@ model Document {
|
||||
template Template? @relation(fields: [templateId], references: [id], onDelete: SetNull)
|
||||
|
||||
auditLogs DocumentAuditLog[]
|
||||
folder Folder? @relation(fields: [folderId], references: [id], onDelete: Cascade)
|
||||
folder Folder? @relation(fields: [folderId], references: [id], onDelete: SetNull)
|
||||
folderId String?
|
||||
|
||||
@@unique([documentDataId])
|
||||
@ -866,7 +866,7 @@ model Template {
|
||||
directLink TemplateDirectLink?
|
||||
documents Document[]
|
||||
|
||||
folder Folder? @relation(fields: [folderId], references: [id], onDelete: Cascade)
|
||||
folder Folder? @relation(fields: [folderId], references: [id], onDelete: SetNull)
|
||||
folderId String?
|
||||
|
||||
@@unique([templateDocumentDataId])
|
||||
|
||||
@ -207,7 +207,9 @@ export const seedTeamTemplateWithMeta = async (team: Team) => {
|
||||
const ownerUser = organisation.owner;
|
||||
|
||||
const template = await createTemplate({
|
||||
title: `[TEST] Template ${nanoid(8)} - Draft`,
|
||||
data: {
|
||||
title: `[TEST] Template ${nanoid(8)} - Draft`,
|
||||
},
|
||||
userId: ownerUser.id,
|
||||
teamId: team.id,
|
||||
templateDocumentDataId: documentData.id,
|
||||
|
||||
@ -12,7 +12,7 @@ import { ZCreateSubscriptionRequestSchema } from './create-subscription.types';
|
||||
export const createSubscriptionRoute = authenticatedProcedure
|
||||
.input(ZCreateSubscriptionRequestSchema)
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
const { organisationId, priceId } = input;
|
||||
const { organisationId, priceId, isPersonalLayoutMode } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
@ -70,10 +70,14 @@ export const createSubscriptionRoute = authenticatedProcedure
|
||||
});
|
||||
}
|
||||
|
||||
const returnUrl = isPersonalLayoutMode
|
||||
? `${NEXT_PUBLIC_WEBAPP_URL()}/settings/billing`
|
||||
: `${NEXT_PUBLIC_WEBAPP_URL()}/o/${organisation.url}/settings/billing`;
|
||||
|
||||
const redirectUrl = await createCheckoutSession({
|
||||
customerId,
|
||||
priceId,
|
||||
returnUrl: `${NEXT_PUBLIC_WEBAPP_URL()}/o/${organisation.url}/settings/billing`,
|
||||
returnUrl,
|
||||
});
|
||||
|
||||
if (!redirectUrl) {
|
||||
|
||||
@ -3,4 +3,5 @@ import { z } from 'zod';
|
||||
export const ZCreateSubscriptionRequestSchema = z.object({
|
||||
organisationId: z.string().describe('The organisation to create the subscription for'),
|
||||
priceId: z.string().describe('The price to create the subscription for'),
|
||||
isPersonalLayoutMode: z.boolean().optional(),
|
||||
});
|
||||
|
||||
@ -322,7 +322,7 @@ export const documentRouter = router({
|
||||
|
||||
return {
|
||||
document: createdDocument,
|
||||
folder: createdDocument.folder,
|
||||
folder: createdDocument.folder, // Todo: Remove this prior to api-v2 release.
|
||||
uploadUrl: url,
|
||||
};
|
||||
}),
|
||||
|
||||
@ -33,7 +33,9 @@ export const createEmbeddingTemplateRoute = procedure
|
||||
// First create the template
|
||||
const template = await createTemplate({
|
||||
userId: apiToken.userId,
|
||||
title,
|
||||
data: {
|
||||
title,
|
||||
},
|
||||
templateDocumentDataId: documentDataId,
|
||||
teamId: apiToken.teamId ?? undefined,
|
||||
});
|
||||
|
||||
@ -1,9 +1,11 @@
|
||||
import type { Document } from '@prisma/client';
|
||||
import { DocumentDataType } from '@prisma/client';
|
||||
import { TRPCError } from '@trpc/server';
|
||||
|
||||
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 { createDocumentData } from '@documenso/lib/server-only/document-data/create-document-data';
|
||||
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 {
|
||||
@ -23,6 +25,7 @@ import { findTemplates } from '@documenso/lib/server-only/template/find-template
|
||||
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 { updateTemplate } from '@documenso/lib/server-only/template/update-template';
|
||||
import { getPresignPostUrl } from '@documenso/lib/universal/upload/server-actions';
|
||||
|
||||
import { ZGenericSuccessResponse, ZSuccessResponseSchema } from '../document-router/schema';
|
||||
import { authenticatedProcedure, maybeAuthenticatedProcedure, router } from '../trpc';
|
||||
@ -34,6 +37,8 @@ import {
|
||||
ZCreateTemplateDirectLinkRequestSchema,
|
||||
ZCreateTemplateDirectLinkResponseSchema,
|
||||
ZCreateTemplateMutationSchema,
|
||||
ZCreateTemplateV2RequestSchema,
|
||||
ZCreateTemplateV2ResponseSchema,
|
||||
ZDeleteTemplateDirectLinkRequestSchema,
|
||||
ZDeleteTemplateMutationSchema,
|
||||
ZDuplicateTemplateMutationSchema,
|
||||
@ -141,12 +146,88 @@ export const templateRouter = router({
|
||||
return await createTemplate({
|
||||
userId: ctx.user.id,
|
||||
teamId,
|
||||
title,
|
||||
templateDocumentDataId,
|
||||
folderId,
|
||||
data: {
|
||||
title,
|
||||
folderId,
|
||||
},
|
||||
});
|
||||
}),
|
||||
|
||||
/**
|
||||
* Temporariy endpoint for V2 Beta until we allow passthrough documents on create.
|
||||
*
|
||||
* @public
|
||||
* @deprecated
|
||||
*/
|
||||
createTemplateTemporary: authenticatedProcedure
|
||||
.meta({
|
||||
openapi: {
|
||||
method: 'POST',
|
||||
path: '/template/create/beta',
|
||||
summary: 'Create template',
|
||||
description:
|
||||
'You will need to upload the PDF to the provided URL returned. Note: Once V2 API is released, this will be removed since we will allow direct uploads, instead of using an upload URL.',
|
||||
tags: ['Template'],
|
||||
},
|
||||
})
|
||||
.input(ZCreateTemplateV2RequestSchema)
|
||||
.output(ZCreateTemplateV2ResponseSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { teamId, user } = ctx;
|
||||
|
||||
const {
|
||||
title,
|
||||
folderId,
|
||||
externalId,
|
||||
visibility,
|
||||
globalAccessAuth,
|
||||
globalActionAuth,
|
||||
publicTitle,
|
||||
publicDescription,
|
||||
type,
|
||||
meta,
|
||||
} = input;
|
||||
|
||||
const fileName = title.endsWith('.pdf') ? title : `${title}.pdf`;
|
||||
|
||||
const { url, key } = await getPresignPostUrl(fileName, 'application/pdf');
|
||||
|
||||
const templateDocumentData = await createDocumentData({
|
||||
data: key,
|
||||
type: DocumentDataType.S3_PATH,
|
||||
});
|
||||
|
||||
const createdTemplate = await createTemplate({
|
||||
userId: user.id,
|
||||
teamId,
|
||||
templateDocumentDataId: templateDocumentData.id,
|
||||
data: {
|
||||
title,
|
||||
folderId,
|
||||
externalId,
|
||||
visibility,
|
||||
globalAccessAuth,
|
||||
globalActionAuth,
|
||||
publicTitle,
|
||||
publicDescription,
|
||||
type,
|
||||
},
|
||||
meta,
|
||||
});
|
||||
|
||||
const fullTemplate = await getTemplateById({
|
||||
id: createdTemplate.id,
|
||||
userId: user.id,
|
||||
teamId,
|
||||
});
|
||||
|
||||
return {
|
||||
template: fullTemplate,
|
||||
uploadUrl: url,
|
||||
};
|
||||
}),
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
|
||||
@ -30,6 +30,50 @@ import {
|
||||
} from '../document-router/schema';
|
||||
import { ZSignFieldWithTokenMutationSchema } from '../field-router/schema';
|
||||
|
||||
export const MAX_TEMPLATE_PUBLIC_TITLE_LENGTH = 50;
|
||||
export const MAX_TEMPLATE_PUBLIC_DESCRIPTION_LENGTH = 256;
|
||||
|
||||
export const ZTemplateTitleSchema = z
|
||||
.string()
|
||||
.trim()
|
||||
.min(1)
|
||||
.max(255)
|
||||
.describe('The title of the document.');
|
||||
|
||||
export const ZTemplatePublicTitleSchema = z
|
||||
.string()
|
||||
.trim()
|
||||
.min(1)
|
||||
.max(MAX_TEMPLATE_PUBLIC_TITLE_LENGTH)
|
||||
.describe(
|
||||
'The title of the template that will be displayed to the public. Only applicable for public templates.',
|
||||
);
|
||||
|
||||
export const ZTemplatePublicDescriptionSchema = z
|
||||
.string()
|
||||
.trim()
|
||||
.min(1)
|
||||
.max(MAX_TEMPLATE_PUBLIC_DESCRIPTION_LENGTH)
|
||||
.describe(
|
||||
'The description of the template that will be displayed to the public. Only applicable for public templates.',
|
||||
);
|
||||
|
||||
export const ZTemplateMetaUpsertSchema = z.object({
|
||||
subject: ZDocumentMetaSubjectSchema.optional(),
|
||||
message: ZDocumentMetaMessageSchema.optional(),
|
||||
timezone: ZDocumentMetaTimezoneSchema.optional(),
|
||||
dateFormat: ZDocumentMetaDateFormatSchema.optional(),
|
||||
distributionMethod: ZDocumentMetaDistributionMethodSchema.optional(),
|
||||
emailSettings: ZDocumentEmailSettingsSchema.optional(),
|
||||
redirectUrl: ZDocumentMetaRedirectUrlSchema.optional(),
|
||||
language: ZDocumentMetaLanguageSchema.optional(),
|
||||
typedSignatureEnabled: ZDocumentMetaTypedSignatureEnabledSchema.optional(),
|
||||
uploadSignatureEnabled: ZDocumentMetaUploadSignatureEnabledSchema.optional(),
|
||||
drawSignatureEnabled: ZDocumentMetaDrawSignatureEnabledSchema.optional(),
|
||||
signingOrder: z.nativeEnum(DocumentSigningOrder).optional(),
|
||||
allowDictateNextSigner: z.boolean().optional(),
|
||||
});
|
||||
|
||||
export const ZCreateTemplateMutationSchema = z.object({
|
||||
title: z.string().min(1).trim(),
|
||||
templateDocumentDataId: z.string().min(1),
|
||||
@ -123,57 +167,46 @@ export const ZDeleteTemplateMutationSchema = z.object({
|
||||
templateId: z.number(),
|
||||
});
|
||||
|
||||
export const MAX_TEMPLATE_PUBLIC_TITLE_LENGTH = 50;
|
||||
export const MAX_TEMPLATE_PUBLIC_DESCRIPTION_LENGTH = 256;
|
||||
/**
|
||||
* Note: This is the same between V1 and V2. Be careful when updating this schema and think of the consequences.
|
||||
*/
|
||||
export const ZCreateTemplateV2RequestSchema = z.object({
|
||||
title: ZTemplateTitleSchema,
|
||||
folderId: z.string().optional(),
|
||||
externalId: z.string().nullish(),
|
||||
visibility: z.nativeEnum(DocumentVisibility).optional(),
|
||||
globalAccessAuth: z.array(ZDocumentAccessAuthTypesSchema).optional().default([]),
|
||||
globalActionAuth: z.array(ZDocumentActionAuthTypesSchema).optional().default([]),
|
||||
publicTitle: ZTemplatePublicTitleSchema.optional(),
|
||||
publicDescription: ZTemplatePublicDescriptionSchema.optional(),
|
||||
type: z.nativeEnum(TemplateType).optional(),
|
||||
meta: ZTemplateMetaUpsertSchema.optional(),
|
||||
});
|
||||
|
||||
/**
|
||||
* Note: This is the same between V1 and V2. Be careful when updating this schema and think of the consequences.
|
||||
*/
|
||||
export const ZCreateTemplateV2ResponseSchema = z.object({
|
||||
template: ZTemplateSchema,
|
||||
uploadUrl: z.string().min(1),
|
||||
});
|
||||
|
||||
export const ZUpdateTemplateRequestSchema = z.object({
|
||||
templateId: z.number(),
|
||||
data: z
|
||||
.object({
|
||||
title: z.string().min(1).optional(),
|
||||
title: ZTemplateTitleSchema.optional(),
|
||||
externalId: z.string().nullish(),
|
||||
visibility: z.nativeEnum(DocumentVisibility).optional(),
|
||||
globalAccessAuth: z.array(ZDocumentAccessAuthTypesSchema).optional().default([]),
|
||||
globalActionAuth: z.array(ZDocumentActionAuthTypesSchema).optional().default([]),
|
||||
publicTitle: z
|
||||
.string()
|
||||
.trim()
|
||||
.min(1)
|
||||
.max(MAX_TEMPLATE_PUBLIC_TITLE_LENGTH)
|
||||
.describe(
|
||||
'The title of the template that will be displayed to the public. Only applicable for public templates.',
|
||||
)
|
||||
.optional(),
|
||||
publicDescription: z
|
||||
.string()
|
||||
.trim()
|
||||
.min(1)
|
||||
.max(MAX_TEMPLATE_PUBLIC_DESCRIPTION_LENGTH)
|
||||
.describe(
|
||||
'The description of the template that will be displayed to the public. Only applicable for public templates.',
|
||||
)
|
||||
.optional(),
|
||||
publicTitle: ZTemplatePublicTitleSchema.optional(),
|
||||
publicDescription: ZTemplatePublicDescriptionSchema.optional(),
|
||||
type: z.nativeEnum(TemplateType).optional(),
|
||||
useLegacyFieldInsertion: z.boolean().optional(),
|
||||
})
|
||||
.optional(),
|
||||
meta: z
|
||||
.object({
|
||||
subject: ZDocumentMetaSubjectSchema.optional(),
|
||||
message: ZDocumentMetaMessageSchema.optional(),
|
||||
timezone: ZDocumentMetaTimezoneSchema.optional(),
|
||||
dateFormat: ZDocumentMetaDateFormatSchema.optional(),
|
||||
distributionMethod: ZDocumentMetaDistributionMethodSchema.optional(),
|
||||
emailSettings: ZDocumentEmailSettingsSchema.optional(),
|
||||
redirectUrl: ZDocumentMetaRedirectUrlSchema.optional(),
|
||||
language: ZDocumentMetaLanguageSchema.optional(),
|
||||
typedSignatureEnabled: ZDocumentMetaTypedSignatureEnabledSchema.optional(),
|
||||
uploadSignatureEnabled: ZDocumentMetaUploadSignatureEnabledSchema.optional(),
|
||||
drawSignatureEnabled: ZDocumentMetaDrawSignatureEnabledSchema.optional(),
|
||||
signingOrder: z.nativeEnum(DocumentSigningOrder).optional(),
|
||||
allowDictateNextSigner: z.boolean().optional(),
|
||||
})
|
||||
.optional(),
|
||||
meta: ZTemplateMetaUpsertSchema.optional(),
|
||||
});
|
||||
|
||||
export const ZUpdateTemplateResponseSchema = ZTemplateLiteSchema;
|
||||
|
||||
@ -3,6 +3,7 @@ import { deleteWebhookById } from '@documenso/lib/server-only/webhooks/delete-we
|
||||
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 { authenticatedProcedure, router } from '../trpc';
|
||||
import {
|
||||
@ -11,6 +12,7 @@ import {
|
||||
ZEditWebhookRequestSchema,
|
||||
ZGetTeamWebhooksRequestSchema,
|
||||
ZGetWebhookByIdRequestSchema,
|
||||
ZTriggerTestWebhookRequestSchema,
|
||||
} from './schema';
|
||||
|
||||
export const webhookRouter = router({
|
||||
@ -106,4 +108,25 @@ export const webhookRouter = router({
|
||||
teamId,
|
||||
});
|
||||
}),
|
||||
|
||||
testWebhook: authenticatedProcedure
|
||||
.input(ZTriggerTestWebhookRequestSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { id, event, teamId } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
id,
|
||||
event,
|
||||
teamId,
|
||||
},
|
||||
});
|
||||
|
||||
return await triggerTestWebhook({
|
||||
id,
|
||||
event,
|
||||
userId: ctx.user.id,
|
||||
teamId,
|
||||
});
|
||||
}),
|
||||
});
|
||||
|
||||
@ -38,3 +38,11 @@ export const ZDeleteWebhookRequestSchema = z.object({
|
||||
});
|
||||
|
||||
export type TDeleteWebhookRequestSchema = z.infer<typeof ZDeleteWebhookRequestSchema>;
|
||||
|
||||
export const ZTriggerTestWebhookRequestSchema = z.object({
|
||||
id: z.string(),
|
||||
event: z.nativeEnum(WebhookTriggerEvents),
|
||||
teamId: z.number(),
|
||||
});
|
||||
|
||||
export type TTriggerTestWebhookRequestSchema = z.infer<typeof ZTriggerTestWebhookRequestSchema>;
|
||||
|
||||
@ -6,7 +6,7 @@ import { Tooltip, TooltipContent, TooltipTrigger } from '@documenso/ui/primitive
|
||||
export const DocumentSignatureSettingsTooltip = () => {
|
||||
return (
|
||||
<Tooltip>
|
||||
<TooltipTrigger>
|
||||
<TooltipTrigger type="button">
|
||||
<InfoIcon className="mx-2 h-4 w-4" />
|
||||
</TooltipTrigger>
|
||||
|
||||
|
||||
@ -3,7 +3,9 @@ import React, { useEffect, useMemo, useState } from 'react';
|
||||
import { type Field, FieldType } from '@prisma/client';
|
||||
import { createPortal } from 'react-dom';
|
||||
|
||||
import { useElementBounds } from '@documenso/lib/client-only/hooks/use-element-bounds';
|
||||
import { useFieldPageCoords } from '@documenso/lib/client-only/hooks/use-field-page-coords';
|
||||
import { PDF_VIEWER_PAGE_SELECTOR } from '@documenso/lib/constants/pdf-viewer';
|
||||
import { isFieldUnsignedAndRequired } from '@documenso/lib/utils/advanced-fields-helpers';
|
||||
|
||||
import type { RecipientColorStyles } from '../../lib/recipient-colors';
|
||||
@ -23,6 +25,11 @@ export function FieldContainerPortal({
|
||||
const alternativePortalRoot = document.getElementById('document-field-portal-root');
|
||||
|
||||
const coords = useFieldPageCoords(field);
|
||||
const $pageBounds = useElementBounds(
|
||||
`${PDF_VIEWER_PAGE_SELECTOR}[data-page-number="${field.page}"]`,
|
||||
);
|
||||
|
||||
const maxWidth = $pageBounds?.width ? $pageBounds.width - coords.x : undefined;
|
||||
|
||||
const isCheckboxOrRadioField = field.type === 'CHECKBOX' || field.type === 'RADIO';
|
||||
|
||||
@ -32,10 +39,14 @@ export function FieldContainerPortal({
|
||||
const bounds = {
|
||||
top: `${coords.y}px`,
|
||||
left: `${coords.x}px`,
|
||||
...(!isCheckboxOrRadioField && {
|
||||
height: `${coords.height}px`,
|
||||
width: `${coords.width}px`,
|
||||
}),
|
||||
...(!isCheckboxOrRadioField
|
||||
? {
|
||||
height: `${coords.height}px`,
|
||||
width: `${coords.width}px`,
|
||||
}
|
||||
: {
|
||||
maxWidth: `${maxWidth}px`,
|
||||
}),
|
||||
};
|
||||
|
||||
if (portalBounds) {
|
||||
@ -44,7 +55,7 @@ export function FieldContainerPortal({
|
||||
}
|
||||
|
||||
return bounds;
|
||||
}, [coords, isCheckboxOrRadioField]);
|
||||
}, [coords, maxWidth, isCheckboxOrRadioField]);
|
||||
|
||||
return createPortal(
|
||||
<div className={cn('absolute', className)} style={style}>
|
||||
|
||||
@ -206,8 +206,8 @@ export const AddSubjectFormPartial = ({
|
||||
|
||||
<p className="mt-2">
|
||||
<Trans>
|
||||
We will generate signing links for you, which you can send to the
|
||||
recipients through your method of choice.
|
||||
We will generate signing links for you, which you can send to the recipients
|
||||
through your method of choice.
|
||||
</Trans>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@ -38,13 +38,8 @@ export const FieldContent = ({ field, documentMeta }: FieldIconProps) => {
|
||||
|
||||
const { type, fieldMeta } = field;
|
||||
|
||||
// Only render checkbox if values exist, otherwise render the empty checkbox field content.
|
||||
if (
|
||||
field.type === FieldType.CHECKBOX &&
|
||||
field.fieldMeta?.type === 'checkbox' &&
|
||||
field.fieldMeta.values &&
|
||||
field.fieldMeta.values.length > 0
|
||||
) {
|
||||
// Render checkbox layout for checkbox fields, even if no values exist yet
|
||||
if (field.type === FieldType.CHECKBOX && field.fieldMeta?.type === 'checkbox') {
|
||||
let checkedValues: string[] = [];
|
||||
|
||||
try {
|
||||
@ -55,8 +50,32 @@ export const FieldContent = ({ field, documentMeta }: FieldIconProps) => {
|
||||
console.error(err);
|
||||
}
|
||||
|
||||
// If no values exist yet, show a placeholder checkbox
|
||||
if (!field.fieldMeta.values || field.fieldMeta.values.length === 0) {
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
'flex gap-1 py-0.5',
|
||||
field.fieldMeta.direction === 'horizontal' ? 'flex-row flex-wrap' : 'flex-col gap-y-1',
|
||||
)}
|
||||
>
|
||||
<div className="flex items-center">
|
||||
<Checkbox className="h-3 w-3" disabled />
|
||||
<Label className="text-foreground ml-1.5 text-xs font-normal opacity-50">
|
||||
Checkbox option
|
||||
</Label>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-y-1 py-0.5">
|
||||
<div
|
||||
className={cn(
|
||||
'flex gap-1 py-0.5',
|
||||
field.fieldMeta.direction === 'horizontal' ? 'flex-row flex-wrap' : 'flex-col gap-y-1',
|
||||
)}
|
||||
>
|
||||
{field.fieldMeta.values.map((item, index) => (
|
||||
<div key={index} className="flex items-center">
|
||||
<Checkbox
|
||||
|
||||
@ -129,6 +129,7 @@ const getDefaultState = (fieldType: FieldType): FieldMeta => {
|
||||
validationLength: 0,
|
||||
required: false,
|
||||
readOnly: false,
|
||||
direction: 'vertical',
|
||||
};
|
||||
case FieldType.DROPDOWN:
|
||||
return {
|
||||
|
||||
@ -8,6 +8,7 @@ import { createPortal } from 'react-dom';
|
||||
import { Rnd } from 'react-rnd';
|
||||
import { useSearchParams } from 'react-router';
|
||||
|
||||
import { useElementBounds } from '@documenso/lib/client-only/hooks/use-element-bounds';
|
||||
import { PDF_VIEWER_PAGE_SELECTOR } from '@documenso/lib/constants/pdf-viewer';
|
||||
import type { TFieldMetaSchema } from '@documenso/lib/types/field-meta';
|
||||
import { ZCheckboxFieldMeta, ZRadioFieldMeta } from '@documenso/lib/types/field-meta';
|
||||
@ -81,7 +82,11 @@ export const FieldItem = ({
|
||||
pageWidth: defaultWidth || 0,
|
||||
});
|
||||
const [settingsActive, setSettingsActive] = useState(false);
|
||||
const $el = useRef(null);
|
||||
const $el = useRef<HTMLDivElement>(null);
|
||||
|
||||
const $pageBounds = useElementBounds(
|
||||
`${PDF_VIEWER_PAGE_SELECTOR}[data-page-number="${field.pageNumber}"]`,
|
||||
);
|
||||
|
||||
const signerStyles = useRecipientColors(recipientIndex);
|
||||
|
||||
@ -233,9 +238,10 @@ export const FieldItem = ({
|
||||
default={{
|
||||
x: coords.pageX,
|
||||
y: coords.pageY,
|
||||
height: fixedSize ? '' : coords.pageHeight,
|
||||
width: fixedSize ? '' : coords.pageWidth,
|
||||
height: fixedSize ? 'auto' : coords.pageHeight,
|
||||
width: fixedSize ? 'auto' : coords.pageWidth,
|
||||
}}
|
||||
maxWidth={fixedSize && $pageBounds?.width ? $pageBounds.width - coords.pageX : undefined}
|
||||
bounds={`${PDF_VIEWER_PAGE_SELECTOR}[data-page-number="${field.pageNumber}"]`}
|
||||
onDragStart={() => onFieldActivate?.()}
|
||||
onResizeStart={() => onFieldActivate?.()}
|
||||
|
||||
@ -44,6 +44,9 @@ export const CheckboxFieldAdvancedSettings = ({
|
||||
const [required, setRequired] = useState(fieldState.required ?? false);
|
||||
const [validationLength, setValidationLength] = useState(fieldState.validationLength ?? 0);
|
||||
const [validationRule, setValidationRule] = useState(fieldState.validationRule ?? '');
|
||||
const [direction, setDirection] = useState<'vertical' | 'horizontal'>(
|
||||
fieldState.direction ?? 'vertical',
|
||||
);
|
||||
|
||||
const handleToggleChange = (field: keyof CheckboxFieldMeta, value: string | boolean) => {
|
||||
const readOnly = field === 'readOnly' ? Boolean(value) : Boolean(fieldState.readOnly);
|
||||
@ -52,11 +55,14 @@ export const CheckboxFieldAdvancedSettings = ({
|
||||
field === 'validationRule' ? String(value) : String(fieldState.validationRule);
|
||||
const validationLength =
|
||||
field === 'validationLength' ? Number(value) : Number(fieldState.validationLength);
|
||||
const currentDirection =
|
||||
field === 'direction' && String(value) === 'horizontal' ? 'horizontal' : 'vertical';
|
||||
|
||||
setReadOnly(readOnly);
|
||||
setRequired(required);
|
||||
setValidationRule(validationRule);
|
||||
setValidationLength(validationLength);
|
||||
setDirection(currentDirection);
|
||||
|
||||
const errors = validateCheckboxField(
|
||||
values.map((item) => item.value),
|
||||
@ -65,6 +71,7 @@ export const CheckboxFieldAdvancedSettings = ({
|
||||
required,
|
||||
validationRule,
|
||||
validationLength,
|
||||
direction: currentDirection,
|
||||
type: 'checkbox',
|
||||
},
|
||||
);
|
||||
@ -86,6 +93,7 @@ export const CheckboxFieldAdvancedSettings = ({
|
||||
required,
|
||||
validationRule,
|
||||
validationLength,
|
||||
direction: direction,
|
||||
type: 'checkbox',
|
||||
},
|
||||
);
|
||||
@ -137,6 +145,29 @@ export const CheckboxFieldAdvancedSettings = ({
|
||||
onChange={(e) => handleFieldChange('label', e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="mb-2">
|
||||
<Label>
|
||||
<Trans>Direction</Trans>
|
||||
</Label>
|
||||
<Select
|
||||
value={fieldState.direction ?? 'vertical'}
|
||||
onValueChange={(val) => handleToggleChange('direction', val)}
|
||||
>
|
||||
<SelectTrigger className="text-muted-foreground bg-background mt-2 w-full">
|
||||
<SelectValue placeholder={_(msg`Select direction`)} />
|
||||
</SelectTrigger>
|
||||
<SelectContent position="popper">
|
||||
<SelectItem value="vertical">
|
||||
<Trans>Vertical</Trans>
|
||||
</SelectItem>
|
||||
<SelectItem value="horizontal">
|
||||
<Trans>Horizontal</Trans>
|
||||
</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-row items-center gap-x-4">
|
||||
<div className="flex w-2/3 flex-col">
|
||||
<Label>
|
||||
|
||||
Reference in New Issue
Block a user