Compare commits
14 Commits
chore/add-
...
v1.6.1-rc.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ef3ecc33f1 | ||
|
|
0244f021ab | ||
|
|
e5f73452b3 | ||
|
|
c605877924 | ||
|
|
e0065a8731 | ||
|
|
f74265850b | ||
|
|
909c38f47e | ||
|
|
1beb434a72 | ||
|
|
5582f29bda | ||
|
|
7ed0a909eb | ||
|
|
a9025b5d97 | ||
|
|
0c744a1123 | ||
|
|
0f86eed6ac | ||
|
|
4b485268ca |
@@ -10,12 +10,19 @@ NEXT_PRIVATE_ENCRYPTION_KEY="CAFEBABE"
|
||||
NEXT_PRIVATE_ENCRYPTION_SECONDARY_KEY="DEADBEEF"
|
||||
|
||||
# [[AUTH OPTIONAL]]
|
||||
# Find documentation on setting up Google OAuth here:
|
||||
# https://docs.documenso.com/developers/self-hosting/setting-up-oauth-providers#google-oauth-gmail
|
||||
NEXT_PRIVATE_GOOGLE_CLIENT_ID=""
|
||||
NEXT_PRIVATE_GOOGLE_CLIENT_SECRET=""
|
||||
|
||||
NEXT_PRIVATE_OIDC_WELL_KNOWN=""
|
||||
NEXT_PRIVATE_OIDC_CLIENT_ID=""
|
||||
NEXT_PRIVATE_OIDC_CLIENT_SECRET=""
|
||||
NEXT_PRIVATE_OIDC_PROVIDER_LABEL="OIDC"
|
||||
# This can be used to still allow signups for OIDC connections
|
||||
# when signup is disabled via `NEXT_PUBLIC_DISABLE_SIGNUP`
|
||||
NEXT_PRIVATE_OIDC_ALLOW_SIGNUP=""
|
||||
NEXT_PRIVATE_OIDC_SKIP_VERIFY=""
|
||||
|
||||
# [[URLS]]
|
||||
NEXT_PUBLIC_WEBAPP_URL="http://localhost:3000"
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"index": "Getting Started",
|
||||
"signing-certificate": "Signing Certificate",
|
||||
"how-to": "How To"
|
||||
"how-to": "How To",
|
||||
"setting-up-oauth-providers": "Setting up OAuth Providers"
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
---
|
||||
title: Setting up OAuth Providers
|
||||
description: Learn how to set up OAuth providers for your own instance of Documenso.
|
||||
---
|
||||
|
||||
## Google OAuth (Gmail)
|
||||
|
||||
To use Google OAuth, you will need to create a Google Cloud Platform project and enable the Google Identity and Access Management (IAM) API. You will also need to create a new OAuth client ID and download the client secret.
|
||||
|
||||
### Create and configure a new OAuth client ID
|
||||
|
||||
1. Go to the [Google Cloud Platform Console](https://console.cloud.google.com/)
|
||||
2. From the projects list, select a project or create a new one
|
||||
3. If the APIs & services page isn't already open, open the console left side menu and select APIs & services
|
||||
4. On the left, click Credentials
|
||||
5. Click New Credentials, then select OAuth client ID
|
||||
6. When prompted to select an application type, select Web application
|
||||
7. Enter a name for your client ID, and click Create
|
||||
8. Click the download button to download the client secret
|
||||
9. Set the authorized javascript origins to `https://<documenso-domain>`
|
||||
10. Set the authorized redirect URIs to `https://<documenso-domain>/api/auth/callback/google`
|
||||
11. In the Documenso environment variables, set the following:
|
||||
|
||||
```
|
||||
NEXT_PRIVATE_GOOGLE_CLIENT_ID=<your-client-id>
|
||||
NEXT_PRIVATE_GOOGLE_CLIENT_SECRET=<your-client-secret>
|
||||
```
|
||||
|
||||
Finally verify the signing in with Google works by signing in with your Google account and checking the email address in your profile.
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"index": "Introduction",
|
||||
"support": "Support",
|
||||
"-- How To Use": {
|
||||
"type": "separator",
|
||||
"title": "How To Use"
|
||||
@@ -13,6 +14,7 @@
|
||||
"type": "separator",
|
||||
"title": "Legal Overview"
|
||||
},
|
||||
"fair-use": "Fair Use Policy",
|
||||
"licenses": "Licenses",
|
||||
"compliance": "Compliance"
|
||||
}
|
||||
|
||||
34
apps/documentation/pages/users/fair-use.mdx
Normal file
34
apps/documentation/pages/users/fair-use.mdx
Normal file
@@ -0,0 +1,34 @@
|
||||
---
|
||||
title: Fair Use Policy
|
||||
description: Learn about our fair use policy, which enables us to have unlimited plans.
|
||||
---
|
||||
|
||||
import { Callout } from 'nextra/components';
|
||||
|
||||
# Fair Use Policy
|
||||
|
||||
### Why
|
||||
|
||||
We offer our plans without any limits on volume because we want our users and customers to make the most of their accounts. Estimating volume is incredibly hard, especially for shorter intervals like a quarter. We are not interested in selling volume packages our customers end up not using. This is why the individual plan and the team plan do not include a limit on signing or API volume. If you are a customer of these [plans](https://documen.so/pricing), we ask you to abide by this fair use policy:
|
||||
|
||||
### Spirit of the Plan
|
||||
|
||||
> Use the limitless accounts as much as you like (they are meant to offer a lot) while respecting the spirit and intended scope of the account.
|
||||
|
||||
<Callout type="info">
|
||||
What happens if I violate this policy? We will ask you to upgrade to a fitting plan or custom
|
||||
pricing. We won’t block your account without reaching out. [Message
|
||||
us](mailto:support@documenso.com) for questions. It's probably fine, though.
|
||||
</Callout>
|
||||
|
||||
### DO
|
||||
|
||||
- Sign as many documents with the individual plan for your single business or organization you are part of
|
||||
- Use the API and Zapier to automate all your signing to sign as much as possible
|
||||
- Experiment with the plans and integrations, testing what you want to build: When in doubt, do it. Especially if you are just starting.
|
||||
|
||||
### DON'T
|
||||
|
||||
- Use the individual account's API to power a platform
|
||||
- Run a huge company, signing thousands of documents per day on a two-user team plan using the API
|
||||
- Let this policy make you overthink. If you are a paying customer, we want you to win, and it's probably fine
|
||||
@@ -3,7 +3,7 @@ title: Create Your Account
|
||||
description: Learn how to create an account on Documenso.
|
||||
---
|
||||
|
||||
import { Steps } from 'nextra/components';
|
||||
import { Callout, Steps } from 'nextra/components';
|
||||
|
||||
# Create Your Account
|
||||
|
||||
@@ -14,6 +14,8 @@ The first step to start using Documenso is to pick a plan and create an account.
|
||||
|
||||
Explore each plan's features and choose the one that best suits your needs. The [pricing page](https://documen.so/pricing) has more information about the plans.
|
||||
|
||||
<Callout>All plans are subject to our [Fair Use Policy](/users/fair-use).</Callout>
|
||||
|
||||
### Create an account
|
||||
|
||||
If you are unsure which plan to choose, you can start with the free plan and upgrade later.
|
||||
|
||||
38
apps/documentation/pages/users/support.mdx
Normal file
38
apps/documentation/pages/users/support.mdx
Normal file
@@ -0,0 +1,38 @@
|
||||
---
|
||||
title: Support
|
||||
description: Learn what types of support we offer.
|
||||
---
|
||||
|
||||
# Support
|
||||
|
||||
## Community Support
|
||||
|
||||
If you are a developer or free user, you can reach out to the community or raise an issue:
|
||||
|
||||
### [Create Github Issues](https://github.com/documenso/documenso/issues)
|
||||
|
||||
The community and the core team address GitHub issues. Be sure to check if a similar issue already exists. Please note that while we want to address everything immediately, we must prioritize.
|
||||
|
||||
### [Join our Discord](https://documen.so/discord)
|
||||
|
||||
You can ask for help in the [community help channel](https://discord.com/channels/1132216843537485854/1133419426524430376).
|
||||
|
||||
## Paid Account Support
|
||||
|
||||
### Email: support@documenso.com
|
||||
|
||||
If you are paying customers facing issues, email our customer support, especially in urgent cases.
|
||||
|
||||
### Private Discord channel
|
||||
|
||||
If you prefer Discord, we can invite you to a private channel. Message support to make this happen.
|
||||
|
||||
## Enterprise Support
|
||||
|
||||
### Email: support@documenso.com
|
||||
|
||||
If you are paying customers facing issues, email our customer support, especially in urgent cases.
|
||||
|
||||
### Slack
|
||||
|
||||
If your team is on Slack, we can create a private workspace to support you more closely.
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@documenso/marketing",
|
||||
"version": "1.6.0",
|
||||
"version": "1.6.1-rc.0",
|
||||
"private": true,
|
||||
"license": "AGPL-3.0",
|
||||
"scripts": {
|
||||
|
||||
@@ -8,7 +8,6 @@ import { cn } from '@documenso/ui/lib/utils';
|
||||
import { Callout } from '~/components/(marketing)/callout';
|
||||
import { FasterSmarterBeautifulBento } from '~/components/(marketing)/faster-smarter-beautiful-bento';
|
||||
import { Hero } from '~/components/(marketing)/hero';
|
||||
import { LearnMoreCallout } from '~/components/(marketing)/learn-more-callout';
|
||||
import { OpenBuildTemplateBento } from '~/components/(marketing)/open-build-template-bento';
|
||||
import { ShareConnectPaidWidgetBento } from '~/components/(marketing)/share-connect-paid-widget-bento';
|
||||
|
||||
@@ -41,10 +40,11 @@ export default async function IndexPage() {
|
||||
return (
|
||||
<div className={cn('mt-12', fontCaveat.variable)}>
|
||||
<Hero starCount={starCount} />
|
||||
<LearnMoreCallout className="my-48"></LearnMoreCallout>
|
||||
|
||||
<FasterSmarterBeautifulBento className="my-48" />
|
||||
<ShareConnectPaidWidgetBento className="my-48" />
|
||||
<OpenBuildTemplateBento className="my-48" />
|
||||
|
||||
<Callout starCount={starCount} />
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -71,11 +71,7 @@ export default function PricingPage() {
|
||||
|
||||
<div className="mt-4 flex justify-center">
|
||||
<Button variant="outline" size="lg" className="rounded-full hover:cursor-pointer" asChild>
|
||||
<Link
|
||||
href="https://docs.documenso.com/developers/self-hosting"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
<Link href="https://github.com/documenso/documenso" target="_blank" rel="noreferrer">
|
||||
<Trans>Get Started</Trans>
|
||||
</Link>
|
||||
</Button>
|
||||
|
||||
@@ -1,49 +0,0 @@
|
||||
'use client';
|
||||
|
||||
import Link from 'next/link';
|
||||
|
||||
import { Trans } from '@lingui/macro';
|
||||
import { usePlausible } from 'next-plausible';
|
||||
import { LuBookOpen } from 'react-icons/lu';
|
||||
|
||||
import { cn } from '@documenso/ui/lib/utils';
|
||||
import { Button } from '@documenso/ui/primitives/button';
|
||||
|
||||
export type CalloutProps = {
|
||||
className?: string;
|
||||
[key: string]: unknown;
|
||||
};
|
||||
|
||||
export const LearnMoreCallout = ({ className }: CalloutProps) => {
|
||||
const event = usePlausible();
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn('mt-8 flex flex-wrap items-center justify-center gap-x-6 gap-y-4', className)}
|
||||
>
|
||||
<Link href="https://documen.so/sales">
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
className="rounded-full bg-transparent backdrop-blur-sm"
|
||||
>
|
||||
<Trans>Questions?</Trans>
|
||||
<span className="bg-primary dark:text-background -mr-2.5 ml-2.5 rounded-full px-2 py-1.5 text-xs font-medium">
|
||||
<Trans>Book a Sales Call</Trans>
|
||||
</span>
|
||||
</Button>
|
||||
</Link>
|
||||
|
||||
<Link
|
||||
target="_blank"
|
||||
href="https://docs.documenso.com/users/get-started/account-creation?utm_source=learn-more-callout"
|
||||
onClick={() => event('view-github')}
|
||||
>
|
||||
<Button variant="outline" className="rounded-full bg-transparent backdrop-blur-sm">
|
||||
<LuBookOpen className="mr-2 inline h-5 w-5" />
|
||||
<Trans>Learn More</Trans>
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -39,11 +39,9 @@ export const ShareConnectPaidWidgetBento = ({
|
||||
<CardContent className="grid grid-cols-1 gap-8 p-6">
|
||||
<p className="text-foreground/80 leading-relaxed">
|
||||
<strong className="block">
|
||||
<Trans>Easy Sharing.</Trans>
|
||||
<Trans>Easy Sharing (Soon).</Trans>
|
||||
</strong>
|
||||
<Trans>
|
||||
Receive your personal profile link to share with everyone you care about.
|
||||
</Trans>
|
||||
<Trans>Receive your personal link to share with everyone you care about.</Trans>
|
||||
</p>
|
||||
|
||||
<div className="flex items-center justify-center p-8">
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@documenso/web",
|
||||
"version": "1.6.0",
|
||||
"version": "1.6.1-rc.0",
|
||||
"private": true,
|
||||
"license": "AGPL-3.0",
|
||||
"scripts": {
|
||||
|
||||
2
apps/web/process-env.d.ts
vendored
2
apps/web/process-env.d.ts
vendored
@@ -16,5 +16,7 @@ declare namespace NodeJS {
|
||||
NEXT_PRIVATE_OIDC_WELL_KNOWN: string;
|
||||
NEXT_PRIVATE_OIDC_CLIENT_ID: string;
|
||||
NEXT_PRIVATE_OIDC_CLIENT_SECRET: string;
|
||||
NEXT_PRIVATE_OIDC_ALLOW_SIGNUP?: string;
|
||||
NEXT_PRIVATE_OIDC_SKIP_VERIFY?: string;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -139,7 +139,7 @@ export const DocumentLogsPageView = async ({ params, team }: DocumentLogsPageVie
|
||||
documentStatus={document.status}
|
||||
/>
|
||||
|
||||
<DownloadAuditLogButton documentId={document.id} />
|
||||
<DownloadAuditLogButton teamId={team?.id} documentId={document.id} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -9,10 +9,15 @@ import { useToast } from '@documenso/ui/primitives/use-toast';
|
||||
|
||||
export type DownloadAuditLogButtonProps = {
|
||||
className?: string;
|
||||
teamId?: number;
|
||||
documentId: number;
|
||||
};
|
||||
|
||||
export const DownloadAuditLogButton = ({ className, documentId }: DownloadAuditLogButtonProps) => {
|
||||
export const DownloadAuditLogButton = ({
|
||||
className,
|
||||
teamId,
|
||||
documentId,
|
||||
}: DownloadAuditLogButtonProps) => {
|
||||
const { toast } = useToast();
|
||||
|
||||
const { mutateAsync: downloadAuditLogs, isLoading } =
|
||||
@@ -20,7 +25,7 @@ export const DownloadAuditLogButton = ({ className, documentId }: DownloadAuditL
|
||||
|
||||
const onDownloadAuditLogsClick = async () => {
|
||||
try {
|
||||
const { url } = await downloadAuditLogs({ documentId });
|
||||
const { url } = await downloadAuditLogs({ teamId, documentId });
|
||||
|
||||
const iframe = Object.assign(document.createElement('iframe'), {
|
||||
src: url,
|
||||
|
||||
@@ -4,6 +4,7 @@ import { useRouter } from 'next/navigation';
|
||||
|
||||
import { match } from 'ts-pattern';
|
||||
|
||||
import { useLimits } from '@documenso/ee/server-only/limits/provider/client';
|
||||
import { DocumentStatus } from '@documenso/prisma/client';
|
||||
import { trpc as trpcReact } from '@documenso/trpc/react';
|
||||
import { Alert, AlertDescription } from '@documenso/ui/primitives/alert';
|
||||
@@ -41,6 +42,7 @@ export const DeleteDocumentDialog = ({
|
||||
const router = useRouter();
|
||||
|
||||
const { toast } = useToast();
|
||||
const { refreshLimits } = useLimits();
|
||||
|
||||
const [inputValue, setInputValue] = useState('');
|
||||
const [isDeleteEnabled, setIsDeleteEnabled] = useState(status === DocumentStatus.DRAFT);
|
||||
@@ -48,6 +50,7 @@ export const DeleteDocumentDialog = ({
|
||||
const { mutateAsync: deleteDocument, isLoading } = trpcReact.document.deleteDocument.useMutation({
|
||||
onSuccess: () => {
|
||||
router.refresh();
|
||||
void refreshLimits();
|
||||
|
||||
toast({
|
||||
title: 'Document deleted',
|
||||
|
||||
@@ -36,7 +36,7 @@ export const UploadDocument = ({ className, team }: UploadDocumentProps) => {
|
||||
|
||||
const { toast } = useToast();
|
||||
|
||||
const { quota, remaining } = useLimits();
|
||||
const { quota, remaining, refreshLimits } = useLimits();
|
||||
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
@@ -71,6 +71,8 @@ export const UploadDocument = ({ className, team }: UploadDocumentProps) => {
|
||||
teamId: team?.id,
|
||||
});
|
||||
|
||||
void refreshLimits();
|
||||
|
||||
toast({
|
||||
title: 'Document uploaded',
|
||||
description: 'Your document has been uploaded successfully.',
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import { useState } from 'react';
|
||||
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useRouter, useSearchParams } from 'next/navigation';
|
||||
|
||||
import { RECIPIENT_ROLES_DESCRIPTION } from '@documenso/lib/constants/recipient-roles';
|
||||
import type { Field } from '@documenso/prisma/client';
|
||||
@@ -39,6 +39,7 @@ export const DirectTemplatePageView = ({
|
||||
directTemplateToken,
|
||||
}: TemplatesDirectPageViewProps) => {
|
||||
const router = useRouter();
|
||||
const searchParams = useSearchParams();
|
||||
|
||||
const { toast } = useToast();
|
||||
|
||||
@@ -82,8 +83,15 @@ export const DirectTemplatePageView = ({
|
||||
|
||||
const onSignDirectTemplateSubmit = async (fields: DirectTemplateLocalField[]) => {
|
||||
try {
|
||||
let directTemplateExternalId = searchParams?.get('externalId') || undefined;
|
||||
|
||||
if (directTemplateExternalId) {
|
||||
directTemplateExternalId = decodeURIComponent(directTemplateExternalId);
|
||||
}
|
||||
|
||||
const token = await createDocumentFromDirectTemplate({
|
||||
directTemplateToken,
|
||||
directTemplateExternalId,
|
||||
directRecipientName: fullName,
|
||||
directRecipientEmail: recipient.email,
|
||||
templateUpdatedAt: template.updatedAt,
|
||||
|
||||
@@ -13,6 +13,7 @@ import { isRecipientAuthorized } from '@documenso/lib/server-only/document/is-re
|
||||
import { getFieldsForToken } from '@documenso/lib/server-only/field/get-fields-for-token';
|
||||
import { getRecipientByToken } from '@documenso/lib/server-only/recipient/get-recipient-by-token';
|
||||
import { getRecipientSignatures } from '@documenso/lib/server-only/recipient/get-recipient-signatures';
|
||||
import { getUserByEmail } from '@documenso/lib/server-only/user/get-user-by-email';
|
||||
import { DocumentStatus, FieldType, RecipientRole } from '@documenso/prisma/client';
|
||||
import { DocumentDownloadButton } from '@documenso/ui/components/document/document-download-button';
|
||||
import { DocumentShareButton } from '@documenso/ui/components/document/document-share-button';
|
||||
@@ -77,6 +78,9 @@ export default async function CompletedSigningPage({
|
||||
}
|
||||
|
||||
const signatures = await getRecipientSignatures({ recipientId: recipient.id });
|
||||
const isExistingUser = await getUserByEmail({ email: recipient.email })
|
||||
.then((u) => !!u)
|
||||
.catch(() => false);
|
||||
|
||||
const recipientName =
|
||||
recipient.name ||
|
||||
@@ -85,7 +89,7 @@ export default async function CompletedSigningPage({
|
||||
|
||||
const sessionData = await getServerSession();
|
||||
const isLoggedIn = !!sessionData?.user;
|
||||
const canSignUp = !isLoggedIn && NEXT_PUBLIC_DISABLE_SIGNUP !== 'true';
|
||||
const canSignUp = !isExistingUser && NEXT_PUBLIC_DISABLE_SIGNUP !== 'true';
|
||||
|
||||
return (
|
||||
<div
|
||||
|
||||
@@ -4,7 +4,11 @@ import { redirect } from 'next/navigation';
|
||||
|
||||
import { env } from 'next-runtime-env';
|
||||
|
||||
import { IS_GOOGLE_SSO_ENABLED, IS_OIDC_SSO_ENABLED } from '@documenso/lib/constants/auth';
|
||||
import {
|
||||
IS_GOOGLE_SSO_ENABLED,
|
||||
IS_OIDC_SSO_ENABLED,
|
||||
OIDC_PROVIDER_LABEL,
|
||||
} from '@documenso/lib/constants/auth';
|
||||
import { decryptSecondaryData } from '@documenso/lib/server-only/crypto/decrypt';
|
||||
|
||||
import { SignInForm } from '~/components/forms/signin';
|
||||
@@ -43,6 +47,7 @@ export default function SignInPage({ searchParams }: SignInPageProps) {
|
||||
initialEmail={email || undefined}
|
||||
isGoogleSSOEnabled={IS_GOOGLE_SSO_ENABLED}
|
||||
isOIDCSSOEnabled={IS_OIDC_SSO_ENABLED}
|
||||
oidcProviderLabel={OIDC_PROVIDER_LABEL}
|
||||
/>
|
||||
|
||||
{NEXT_PUBLIC_DISABLE_SIGNUP !== 'true' && (
|
||||
|
||||
@@ -71,6 +71,7 @@ export type SignInFormProps = {
|
||||
initialEmail?: string;
|
||||
isGoogleSSOEnabled?: boolean;
|
||||
isOIDCSSOEnabled?: boolean;
|
||||
oidcProviderLabel?: string;
|
||||
};
|
||||
|
||||
export const SignInForm = ({
|
||||
@@ -78,6 +79,7 @@ export const SignInForm = ({
|
||||
initialEmail,
|
||||
isGoogleSSOEnabled,
|
||||
isOIDCSSOEnabled,
|
||||
oidcProviderLabel,
|
||||
}: SignInFormProps) => {
|
||||
const { toast } = useToast();
|
||||
const { getFlag } = useFeatureFlags();
|
||||
@@ -369,7 +371,7 @@ export const SignInForm = ({
|
||||
onClick={onSignInWithOIDCClick}
|
||||
>
|
||||
<FaIdCardClip className="mr-2 h-5 w-5" />
|
||||
OIDC
|
||||
{oidcProviderLabel || 'OIDC'}
|
||||
</Button>
|
||||
)}
|
||||
|
||||
|
||||
@@ -60,13 +60,23 @@ export default async function auth(req: NextApiRequest, res: NextApiResponse) {
|
||||
},
|
||||
});
|
||||
},
|
||||
linkAccount: async ({ user }) => {
|
||||
linkAccount: async ({ user, account, profile }) => {
|
||||
const userId = typeof user.id === 'string' ? parseInt(user.id) : user.id;
|
||||
|
||||
if (isNaN(userId)) {
|
||||
if (Number.isNaN(userId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If the user is linking an OIDC account and the email verified date is set then update it in the db.
|
||||
if (account.provider === 'oidc' && profile.emailVerified !== null) {
|
||||
await prisma.user.update({
|
||||
where: { id: userId },
|
||||
data: {
|
||||
emailVerified: profile.emailVerified,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
await prisma.userSecurityAuditLog.create({
|
||||
data: {
|
||||
userId,
|
||||
|
||||
8
package-lock.json
generated
8
package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@documenso/root",
|
||||
"version": "1.6.0",
|
||||
"version": "1.6.1-rc.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@documenso/root",
|
||||
"version": "1.6.0",
|
||||
"version": "1.6.1-rc.0",
|
||||
"workspaces": [
|
||||
"apps/*",
|
||||
"packages/*"
|
||||
@@ -80,7 +80,7 @@
|
||||
},
|
||||
"apps/marketing": {
|
||||
"name": "@documenso/marketing",
|
||||
"version": "1.6.0",
|
||||
"version": "1.6.1-rc.0",
|
||||
"license": "AGPL-3.0",
|
||||
"dependencies": {
|
||||
"@documenso/assets": "*",
|
||||
@@ -424,7 +424,7 @@
|
||||
},
|
||||
"apps/web": {
|
||||
"name": "@documenso/web",
|
||||
"version": "1.6.0",
|
||||
"version": "1.6.1-rc.0",
|
||||
"license": "AGPL-3.0",
|
||||
"dependencies": {
|
||||
"@documenso/api": "*",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"private": true,
|
||||
"version": "1.6.0",
|
||||
"version": "1.6.1-rc.0",
|
||||
"scripts": {
|
||||
"build": "turbo run build",
|
||||
"build:web": "turbo run build --filter=@documenso/web",
|
||||
|
||||
@@ -2,6 +2,9 @@ import { createNextRoute } from '@ts-rest/next';
|
||||
|
||||
import { getServerLimits } from '@documenso/ee/server-only/limits/server';
|
||||
import { NEXT_PUBLIC_WEBAPP_URL } from '@documenso/lib/constants/app';
|
||||
import { DATE_FORMATS, DEFAULT_DOCUMENT_DATE_FORMAT } from '@documenso/lib/constants/date-formats';
|
||||
import '@documenso/lib/constants/time-zones';
|
||||
import { DEFAULT_DOCUMENT_TIME_ZONE, TIME_ZONES } from '@documenso/lib/constants/time-zones';
|
||||
import { AppError } from '@documenso/lib/errors/app-error';
|
||||
import { createDocumentData } from '@documenso/lib/server-only/document-data/create-document-data';
|
||||
import { upsertDocumentMeta } from '@documenso/lib/server-only/document-meta/upsert-document-meta';
|
||||
@@ -222,6 +225,36 @@ export const ApiContractV1Implementation = createNextRoute(ApiContractV1, {
|
||||
};
|
||||
}
|
||||
|
||||
const dateFormat = body.meta.dateFormat
|
||||
? DATE_FORMATS.find((format) => format.label === body.meta.dateFormat)
|
||||
: DATE_FORMATS.find((format) => format.value === DEFAULT_DOCUMENT_DATE_FORMAT);
|
||||
const timezone = body.meta.timezone
|
||||
? TIME_ZONES.find((tz) => tz === body.meta.timezone)
|
||||
: DEFAULT_DOCUMENT_TIME_ZONE;
|
||||
|
||||
const isDateFormatValid = body.meta.dateFormat
|
||||
? DATE_FORMATS.some((format) => format.label === dateFormat?.label)
|
||||
: true;
|
||||
const isTimeZoneValid = body.meta.timezone ? TIME_ZONES.includes(String(timezone)) : true;
|
||||
|
||||
if (!isDateFormatValid) {
|
||||
return {
|
||||
status: 400,
|
||||
body: {
|
||||
message: 'Invalid date format. Please provide a valid date format',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
if (!isTimeZoneValid) {
|
||||
return {
|
||||
status: 400,
|
||||
body: {
|
||||
message: 'Invalid timezone. Please provide a valid timezone',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const fileName = body.title.endsWith('.pdf') ? body.title : `${body.title}.pdf`;
|
||||
|
||||
const { url, key } = await getPresignPostUrl(fileName, 'application/pdf');
|
||||
@@ -244,7 +277,11 @@ export const ApiContractV1Implementation = createNextRoute(ApiContractV1, {
|
||||
await upsertDocumentMeta({
|
||||
documentId: document.id,
|
||||
userId: user.id,
|
||||
...body.meta,
|
||||
subject: body.meta.subject,
|
||||
message: body.meta.message,
|
||||
timezone,
|
||||
dateFormat: dateFormat?.value,
|
||||
redirectUrl: body.meta.redirectUrl,
|
||||
requestMetadata: extractNextApiRequestMetadata(args.req),
|
||||
});
|
||||
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
import { extendZodWithOpenApi } from '@anatine/zod-openapi';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { DATE_FORMATS, DEFAULT_DOCUMENT_DATE_FORMAT } from '@documenso/lib/constants/date-formats';
|
||||
import '@documenso/lib/constants/time-zones';
|
||||
import { DEFAULT_DOCUMENT_TIME_ZONE, TIME_ZONES } from '@documenso/lib/constants/time-zones';
|
||||
import { ZUrlSchema } from '@documenso/lib/schemas/common';
|
||||
import {
|
||||
DocumentDataType,
|
||||
@@ -11,6 +15,8 @@ import {
|
||||
TemplateType,
|
||||
} from '@documenso/prisma/client';
|
||||
|
||||
extendZodWithOpenApi(z);
|
||||
|
||||
export const ZNoBodyMutationSchema = null;
|
||||
|
||||
/**
|
||||
@@ -97,8 +103,19 @@ export const ZCreateDocumentMutationSchema = z.object({
|
||||
.object({
|
||||
subject: z.string(),
|
||||
message: z.string(),
|
||||
timezone: z.string(),
|
||||
dateFormat: z.string(),
|
||||
timezone: z.string().default(DEFAULT_DOCUMENT_TIME_ZONE).openapi({
|
||||
description:
|
||||
'The timezone of the date. Must be one of the options listed in the list below.',
|
||||
enum: TIME_ZONES,
|
||||
}),
|
||||
dateFormat: z
|
||||
.string()
|
||||
.default(DEFAULT_DOCUMENT_DATE_FORMAT)
|
||||
.openapi({
|
||||
description:
|
||||
'The format of the date. Must be one of the options listed in the list below.',
|
||||
enum: DATE_FORMATS.map((format) => format.value),
|
||||
}),
|
||||
redirectUrl: z.string(),
|
||||
})
|
||||
.partial(),
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
'use client';
|
||||
|
||||
import { createContext, useContext, useEffect, useState } from 'react';
|
||||
import { createContext, useCallback, useContext, useEffect, useState } from 'react';
|
||||
|
||||
import { equals } from 'remeda';
|
||||
|
||||
@@ -8,7 +8,7 @@ import { getLimits } from '../client';
|
||||
import { FREE_PLAN_LIMITS } from '../constants';
|
||||
import type { TLimitsResponseSchema } from '../schema';
|
||||
|
||||
export type LimitsContextValue = TLimitsResponseSchema;
|
||||
export type LimitsContextValue = TLimitsResponseSchema & { refreshLimits: () => Promise<void> };
|
||||
|
||||
const LimitsContext = createContext<LimitsContextValue | null>(null);
|
||||
|
||||
@@ -23,7 +23,7 @@ export const useLimits = () => {
|
||||
};
|
||||
|
||||
export type LimitsProviderProps = {
|
||||
initialValue?: LimitsContextValue;
|
||||
initialValue?: TLimitsResponseSchema;
|
||||
teamId?: number;
|
||||
children?: React.ReactNode;
|
||||
};
|
||||
@@ -38,7 +38,7 @@ export const LimitsProvider = ({
|
||||
}: LimitsProviderProps) => {
|
||||
const [limits, setLimits] = useState(() => initialValue);
|
||||
|
||||
const refreshLimits = async () => {
|
||||
const refreshLimits = useCallback(async () => {
|
||||
const newLimits = await getLimits({ teamId });
|
||||
|
||||
setLimits((oldLimits) => {
|
||||
@@ -48,11 +48,11 @@ export const LimitsProvider = ({
|
||||
|
||||
return newLimits;
|
||||
});
|
||||
};
|
||||
}, [teamId]);
|
||||
|
||||
useEffect(() => {
|
||||
void refreshLimits();
|
||||
}, []);
|
||||
}, [refreshLimits]);
|
||||
|
||||
useEffect(() => {
|
||||
const onFocus = () => {
|
||||
@@ -64,7 +64,16 @@ export const LimitsProvider = ({
|
||||
return () => {
|
||||
window.removeEventListener('focus', onFocus);
|
||||
};
|
||||
}, []);
|
||||
}, [refreshLimits]);
|
||||
|
||||
return <LimitsContext.Provider value={limits}>{children}</LimitsContext.Provider>;
|
||||
return (
|
||||
<LimitsContext.Provider
|
||||
value={{
|
||||
...limits,
|
||||
refreshLimits,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</LimitsContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
import { headers } from 'next/headers';
|
||||
|
||||
import { getLimits } from '../client';
|
||||
import type { LimitsContextValue } from './client';
|
||||
import { LimitsProvider as ClientLimitsProvider } from './client';
|
||||
|
||||
export type LimitsProviderProps = {
|
||||
@@ -14,7 +13,7 @@ export type LimitsProviderProps = {
|
||||
export const LimitsProvider = async ({ children, teamId }: LimitsProviderProps) => {
|
||||
const requestHeaders = Object.fromEntries(headers().entries());
|
||||
|
||||
const limits: LimitsContextValue = await getLimits({ headers: requestHeaders, teamId });
|
||||
const limits = await getLimits({ headers: requestHeaders, teamId });
|
||||
|
||||
return (
|
||||
<ClientLimitsProvider initialValue={limits} teamId={teamId}>
|
||||
|
||||
@@ -18,6 +18,8 @@ export const IS_OIDC_SSO_ENABLED = Boolean(
|
||||
process.env.NEXT_PRIVATE_OIDC_CLIENT_SECRET,
|
||||
);
|
||||
|
||||
export const OIDC_PROVIDER_LABEL = process.env.NEXT_PRIVATE_OIDC_PROVIDER_LABEL;
|
||||
|
||||
export const USER_SECURITY_AUDIT_LOG_MAP: { [key in UserSecurityAuditLogType]: string } = {
|
||||
[UserSecurityAuditLogType.ACCOUNT_SSO_LINK]: 'Linked account to SSO',
|
||||
[UserSecurityAuditLogType.ACCOUNT_PROFILE_UPDATE]: 'Profile updated',
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
export const URL_REGEX =
|
||||
/^(https?):\/\/(?:www\.)?(?:[a-zA-Z0-9-]+\.)*[a-zA-Z0-9-]+\.[a-zA-Z0-9()]{2,}(?:\/[a-zA-Z0-9-._?&=/]*)?$/i;
|
||||
@@ -161,7 +161,10 @@ export const NEXT_AUTH_OPTIONS: AuthOptions = {
|
||||
id: profile.sub,
|
||||
email: profile.email || profile.preferred_username,
|
||||
name: profile.name || `${profile.given_name} ${profile.family_name}`.trim(),
|
||||
emailVerified: profile.email_verified ? new Date().toISOString() : null,
|
||||
emailVerified:
|
||||
process.env.NEXT_PRIVATE_OIDC_SKIP_VERIFY === 'true' || profile.email_verified
|
||||
? new Date().toISOString()
|
||||
: null,
|
||||
};
|
||||
},
|
||||
},
|
||||
@@ -361,6 +364,12 @@ export const NEXT_AUTH_OPTIONS: AuthOptions = {
|
||||
},
|
||||
|
||||
async signIn({ user }) {
|
||||
// This statement appears above so we can stil allow `oidc` connections
|
||||
// while other signups are disabled.
|
||||
if (env('NEXT_PRIVATE_OIDC_ALLOW_SIGNUP') === 'true') {
|
||||
return true;
|
||||
}
|
||||
|
||||
// We do this to stop OAuth providers from creating an account
|
||||
// when signups are disabled
|
||||
if (env('NEXT_PUBLIC_DISABLE_SIGNUP') === 'true') {
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import { URL_REGEX } from '../constants/url-regex';
|
||||
import { isValidRedirectUrl } from '../utils/is-valid-redirect-url';
|
||||
|
||||
/**
|
||||
* Note this allows empty strings.
|
||||
*/
|
||||
export const ZUrlSchema = z
|
||||
.string()
|
||||
.refine((value) => value === undefined || value === '' || URL_REGEX.test(value), {
|
||||
message: 'Please enter a valid URL',
|
||||
.refine((value) => value === undefined || value === '' || isValidRedirectUrl(value), {
|
||||
message: 'Please enter a valid URL, make sure you include http:// or https:// part of the url.',
|
||||
});
|
||||
|
||||
@@ -133,9 +133,14 @@ export const insertFieldInPDF = async (pdf: PDFDocument, field: FieldWithSignatu
|
||||
throw new Error('Invalid checkbox field meta');
|
||||
}
|
||||
|
||||
const values = meta.data.values?.map((item) => ({
|
||||
...item,
|
||||
value: item.value.length > 0 ? item.value : `empty-value-${item.id}`,
|
||||
}));
|
||||
|
||||
const selected = field.customText.split(',');
|
||||
|
||||
for (const [index, item] of (meta.data.values ?? []).entries()) {
|
||||
for (const [index, item] of (values ?? []).entries()) {
|
||||
const offsetY = index * 16;
|
||||
|
||||
const checkbox = pdf.getForm().createCheckBox(`checkbox.${field.secondaryId}.${index}`);
|
||||
@@ -169,9 +174,14 @@ export const insertFieldInPDF = async (pdf: PDFDocument, field: FieldWithSignatu
|
||||
throw new Error('Invalid radio field meta');
|
||||
}
|
||||
|
||||
const values = meta?.data.values?.map((item) => ({
|
||||
...item,
|
||||
value: item.value.length > 0 ? item.value : `empty-value-${item.id}`,
|
||||
}));
|
||||
|
||||
const selected = field.customText.split(',');
|
||||
|
||||
for (const [index, item] of (meta.data.values ?? []).entries()) {
|
||||
for (const [index, item] of (values ?? []).entries()) {
|
||||
const offsetY = index * 16;
|
||||
|
||||
const radio = pdf.getForm().createRadioGroup(`radio.${field.secondaryId}.${index}`);
|
||||
|
||||
@@ -44,6 +44,7 @@ export type CreateDocumentFromDirectTemplateOptions = {
|
||||
directRecipientName?: string;
|
||||
directRecipientEmail: string;
|
||||
directTemplateToken: string;
|
||||
directTemplateExternalId?: string;
|
||||
signedFieldValues: TSignFieldWithTokenMutationSchema[];
|
||||
templateUpdatedAt: Date;
|
||||
requestMetadata: RequestMetadata;
|
||||
@@ -63,6 +64,7 @@ export const createDocumentFromDirectTemplate = async ({
|
||||
directRecipientName: initialDirectRecipientName,
|
||||
directRecipientEmail,
|
||||
directTemplateToken,
|
||||
directTemplateExternalId,
|
||||
signedFieldValues,
|
||||
templateUpdatedAt,
|
||||
requestMetadata,
|
||||
@@ -227,6 +229,7 @@ export const createDocumentFromDirectTemplate = async ({
|
||||
title: template.title,
|
||||
createdAt: initialRequestTime,
|
||||
status: DocumentStatus.PENDING,
|
||||
externalId: directTemplateExternalId,
|
||||
documentDataId: documentData.id,
|
||||
authOptions: createDocumentAuthOptions({
|
||||
globalAccessAuth: templateAuthOptions.globalAccessAuth,
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -66,15 +66,11 @@ msgstr "Weil Unterschriften gefeiert werden sollten. Deshalb kümmern wir uns um
|
||||
msgid "Blog"
|
||||
msgstr "Blog"
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/learn-more-callout.tsx:32
|
||||
msgid "Book a Sales Call"
|
||||
msgstr ""
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/open-build-template-bento.tsx:60
|
||||
msgid "Build on top."
|
||||
msgstr "Aufbauen oben drauf."
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/pricing/page.tsx:167
|
||||
#: apps/marketing/src/app/(marketing)/pricing/page.tsx:163
|
||||
msgid "Can I use Documenso commercially?"
|
||||
msgstr "Kann ich Documenso kommerziell nutzen?"
|
||||
|
||||
@@ -102,7 +98,7 @@ msgstr "Fertige Dokumente"
|
||||
msgid "Completed Documents per Month"
|
||||
msgstr "Fertige Dokumente pro Monat"
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/share-connect-paid-widget-bento.tsx:63
|
||||
#: apps/marketing/src/components/(marketing)/share-connect-paid-widget-bento.tsx:61
|
||||
msgid "Connections"
|
||||
msgstr "Verbindungen"
|
||||
|
||||
@@ -110,7 +106,7 @@ msgstr "Verbindungen"
|
||||
msgid "Contact Us"
|
||||
msgstr "Kontaktiere uns"
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/share-connect-paid-widget-bento.tsx:65
|
||||
#: apps/marketing/src/components/(marketing)/share-connect-paid-widget-bento.tsx:63
|
||||
msgid "Create connections and automations with Zapier and more to integrate with your favorite tools."
|
||||
msgstr "Erstellen Sie Verbindungen und Automatisierungen mit Zapier und mehr, um sich mit Ihren Lieblingstools zu integrieren."
|
||||
|
||||
@@ -138,7 +134,7 @@ msgstr "Entwickelt für jede Phase Ihrer Reise."
|
||||
msgid "Direct Link"
|
||||
msgstr "Direktlink"
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/pricing/page.tsx:185
|
||||
#: apps/marketing/src/app/(marketing)/pricing/page.tsx:181
|
||||
msgid "Documenso is a community effort to create an open and vibrant ecosystem around a tool, everybody is free to use and adapt. By being truly open we want to create trusted infrastructure for the future of the internet."
|
||||
msgstr "Documenso ist eine Gemeinschaftsanstrengung, um ein offenes und lebendiges Ökosystem um ein Werkzeug zu schaffen, das jeder frei nutzen und anpassen kann. Indem wir wirklich offen sind, wollen wir vertrauenswürdige Infrastruktur für die Zukunft des Internets schaffen."
|
||||
|
||||
@@ -156,17 +152,13 @@ msgstr "Unterschriften,<0/>endlich Open Source."
|
||||
msgid "Documentation"
|
||||
msgstr "Dokumentation"
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/share-connect-paid-widget-bento.tsx:108
|
||||
#: apps/marketing/src/components/(marketing)/share-connect-paid-widget-bento.tsx:106
|
||||
msgid "Easily embed Documenso into your product. Simply copy and paste our react widget into your application."
|
||||
msgstr "Betten Sie Documenso ganz einfach in Ihr Produkt ein. Kopieren und fügen Sie einfach unser React-Widget in Ihre Anwendung ein."
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/share-connect-paid-widget-bento.tsx:42
|
||||
#~ msgid "Easy Sharing (Soon)."
|
||||
#~ msgstr "Einfaches Teilen (Bald)."
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/share-connect-paid-widget-bento.tsx:42
|
||||
msgid "Easy Sharing."
|
||||
msgstr ""
|
||||
msgid "Easy Sharing (Soon)."
|
||||
msgstr "Einfaches Teilen (Bald)."
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/pricing-table.tsx:148
|
||||
#: apps/marketing/src/components/(marketing)/pricing-table.tsx:192
|
||||
@@ -225,7 +217,7 @@ msgstr "Aus dem Blog"
|
||||
msgid "Full-Time"
|
||||
msgstr "Vollzeit"
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/share-connect-paid-widget-bento.tsx:85
|
||||
#: apps/marketing/src/components/(marketing)/share-connect-paid-widget-bento.tsx:83
|
||||
msgid "Get paid (Soon)."
|
||||
msgstr "Lassen Sie sich bezahlen (Bald)."
|
||||
|
||||
@@ -233,7 +225,7 @@ msgstr "Lassen Sie sich bezahlen (Bald)."
|
||||
msgid "Get started"
|
||||
msgstr "Loslegen"
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/pricing/page.tsx:79
|
||||
#: apps/marketing/src/app/(marketing)/pricing/page.tsx:75
|
||||
msgid "Get Started"
|
||||
msgstr "Loslegen"
|
||||
|
||||
@@ -265,11 +257,11 @@ msgstr "Globale Gehaltsbänder"
|
||||
msgid "Growth"
|
||||
msgstr "Wachstum"
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/pricing/page.tsx:138
|
||||
#: apps/marketing/src/app/(marketing)/pricing/page.tsx:134
|
||||
msgid "How can I contribute?"
|
||||
msgstr "Wie kann ich beitragen?"
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/pricing/page.tsx:109
|
||||
#: apps/marketing/src/app/(marketing)/pricing/page.tsx:105
|
||||
msgid "How do you handle my data?"
|
||||
msgstr "Wie gehen Sie mit meinen Daten um?"
|
||||
|
||||
@@ -277,7 +269,7 @@ msgstr "Wie gehen Sie mit meinen Daten um?"
|
||||
msgid "Individual"
|
||||
msgstr "Einzelperson"
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/share-connect-paid-widget-bento.tsx:87
|
||||
#: apps/marketing/src/components/(marketing)/share-connect-paid-widget-bento.tsx:85
|
||||
msgid "Integrated payments with Stripe so you don’t have to worry about getting paid."
|
||||
msgstr "Integrierte Zahlungen mit Stripe, sodass Sie sich keine Sorgen ums Bezahlen machen müssen."
|
||||
|
||||
@@ -301,10 +293,6 @@ msgstr "Eintrittsdatum"
|
||||
msgid "Join the Open Signing Movement"
|
||||
msgstr "Treten Sie der Open Signing-Bewegung bei"
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/learn-more-callout.tsx:44
|
||||
msgid "Learn More"
|
||||
msgstr ""
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/open/team-members.tsx:46
|
||||
msgid "Location"
|
||||
msgstr "Standort"
|
||||
@@ -408,21 +396,13 @@ msgstr "Datenschutz"
|
||||
msgid "Profile"
|
||||
msgstr "Profil"
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/learn-more-callout.tsx:30
|
||||
msgid "Questions?"
|
||||
msgstr ""
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/share-connect-paid-widget-bento.tsx:106
|
||||
#: apps/marketing/src/components/(marketing)/share-connect-paid-widget-bento.tsx:104
|
||||
msgid "React Widget (Soon)."
|
||||
msgstr "React Widget (Demnächst)."
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/share-connect-paid-widget-bento.tsx:44
|
||||
#~ msgid "Receive your personal link to share with everyone you care about."
|
||||
#~ msgstr "Erhalten Sie Ihren persönlichen Link zum Teilen mit allen, die Ihnen wichtig sind."
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/share-connect-paid-widget-bento.tsx:44
|
||||
msgid "Receive your personal profile link to share with everyone you care about."
|
||||
msgstr ""
|
||||
msgid "Receive your personal link to share with everyone you care about."
|
||||
msgstr "Erhalten Sie Ihren persönlichen Link zum Teilen mit allen, die Ihnen wichtig sind."
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/open/team-members.tsx:37
|
||||
msgid "Role"
|
||||
@@ -441,7 +421,7 @@ msgstr "Sparen Sie $60 oder $120"
|
||||
msgid "Search languages..."
|
||||
msgstr "Sprachen suchen..."
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/pricing/page.tsx:113
|
||||
#: apps/marketing/src/app/(marketing)/pricing/page.tsx:109
|
||||
msgid "Securely. Our data centers are located in Frankfurt (Germany), giving us the best local privacy laws. We are very aware of the sensitive nature of our data and follow best practices to ensure the security and integrity of the data entrusted to us."
|
||||
msgstr "Sicher. Unsere Rechenzentren befinden sich in Frankfurt (Deutschland) und bieten uns die besten lokalen Datenschutzgesetze. Uns ist die sensible Natur unserer Daten sehr bewusst und wir folgen bewährten Praktiken, um die Sicherheit und Integrität der uns anvertrauten Daten zu gewährleisten."
|
||||
|
||||
@@ -516,7 +496,7 @@ msgstr "Teams"
|
||||
msgid "Template Store (Soon)."
|
||||
msgstr "Vorlagen-Shop (Demnächst)."
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/pricing/page.tsx:142
|
||||
#: apps/marketing/src/app/(marketing)/pricing/page.tsx:138
|
||||
msgid "That's awesome. You can take a look at the current <0>Issues</0> and join our <1>Discord Community</1> to keep up to date, on what the current priorities are. In any case, we are an open community and welcome all input, technical and non-technical ❤️"
|
||||
msgstr "Das ist großartig. Sie können sich die aktuellen <0>Issues</0> ansehen und unserer <1>Discord-Community</1> beitreten, um auf dem neuesten Stand zu bleiben, was die aktuellen Prioritäten sind. In jedem Fall sind wir eine offene Gemeinschaft und begrüßen jegliche Beiträge, technische und nicht-technische ❤️"
|
||||
|
||||
@@ -570,7 +550,7 @@ msgstr "Unbegrenzte Dokumente pro Monat"
|
||||
msgid "Up to 10 recipients per document"
|
||||
msgstr "Bis zu 10 Empfänger pro Dokument"
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/pricing/page.tsx:127
|
||||
#: apps/marketing/src/app/(marketing)/pricing/page.tsx:123
|
||||
msgid "Using our hosted version is the easiest way to get started, you can simply subscribe and start signing your documents. We take care of the infrastructure, so you can focus on your business. Additionally, when using our hosted version you benefit from our trusted signing certificates which helps you to build trust with your customers."
|
||||
msgstr "Die Nutzung unserer gehosteten Version ist der einfachste Weg, um zu starten. Sie können einfach abonnieren und mit der Unterzeichnung Ihrer Dokumente beginnen. Wir kümmern uns um die Infrastruktur, damit Sie sich auf Ihr Geschäft konzentrieren können. Zudem profitieren Sie bei der Nutzung unserer gehosteten Version von unseren vertrauenswürdigen Signaturzertifikaten, die Ihnen helfen, Vertrauen bei Ihren Kunden aufzubauen."
|
||||
|
||||
@@ -578,11 +558,11 @@ msgstr "Die Nutzung unserer gehosteten Version ist der einfachste Weg, um zu sta
|
||||
msgid "View all stats"
|
||||
msgstr "Alle Statistiken anzeigen"
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/pricing/page.tsx:199
|
||||
#: apps/marketing/src/app/(marketing)/pricing/page.tsx:195
|
||||
msgid "We are happy to assist you at <0>support@documenso.com</0> or <1>in our Discord-Support-Channel</1> please message either Lucas or Timur to get added to the channel if you are not already a member."
|
||||
msgstr "Wir helfen Ihnen gerne unter <0>support@documenso.com</0> oder <1>in unserem Discord-Support-Kanal</1>. Bitte senden Sie Lucas oder Timur eine Nachricht, um dem Kanal beizutreten, falls Sie noch kein Mitglied sind."
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/pricing/page.tsx:93
|
||||
#: apps/marketing/src/app/(marketing)/pricing/page.tsx:89
|
||||
msgid "What is the difference between the plans?"
|
||||
msgstr "Was ist der Unterschied zwischen den Plänen?"
|
||||
|
||||
@@ -590,15 +570,15 @@ msgstr "Was ist der Unterschied zwischen den Plänen?"
|
||||
msgid "When it comes to sending or receiving a contract, you can count on lightning-fast speeds."
|
||||
msgstr "Wenn es um das Senden oder Empfangen eines Vertrags geht, können Sie auf blitzschnelle Geschwindigkeiten zählen."
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/pricing/page.tsx:195
|
||||
#: apps/marketing/src/app/(marketing)/pricing/page.tsx:191
|
||||
msgid "Where can I get support?"
|
||||
msgstr "Wo kann ich Unterstützung bekommen?"
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/pricing/page.tsx:181
|
||||
#: apps/marketing/src/app/(marketing)/pricing/page.tsx:177
|
||||
msgid "Why should I prefer Documenso over DocuSign or some other signing tool?"
|
||||
msgstr "Warum sollte ich Documenso gegenüber DocuSign oder einem anderen Signatur-Tool bevorzugen?"
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/pricing/page.tsx:123
|
||||
#: apps/marketing/src/app/(marketing)/pricing/page.tsx:119
|
||||
msgid "Why should I use your hosting service?"
|
||||
msgstr "Warum sollte ich Ihren Hosting-Service nutzen?"
|
||||
|
||||
@@ -606,11 +586,11 @@ msgstr "Warum sollte ich Ihren Hosting-Service nutzen?"
|
||||
msgid "Yearly"
|
||||
msgstr "Jährlich"
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/pricing/page.tsx:171
|
||||
#: apps/marketing/src/app/(marketing)/pricing/page.tsx:167
|
||||
msgid "Yes! Documenso is offered under the GNU AGPL V3 open source license. This means you can use it for free and even modify it to fit your needs, as long as you publish your changes under the same license."
|
||||
msgstr "Ja! Documenso wird unter der GNU AGPL V3 Open-Source-Lizenz angeboten. Das bedeutet, dass Sie es kostenlos nutzen und sogar an Ihre Bedürfnisse anpassen können, solange Sie Ihre Änderungen unter derselben Lizenz veröffentlichen."
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/pricing/page.tsx:97
|
||||
#: apps/marketing/src/app/(marketing)/pricing/page.tsx:93
|
||||
msgid "You can self-host Documenso for free or use our ready-to-use hosted version. The hosted version comes with additional support, painless scalability and more. Early adopters will get access to all features we build this year, for no additional cost! Forever! Yes, that includes multiple users per account later. If you want Documenso for your enterprise, we are happy to talk about your needs."
|
||||
msgstr "Sie können Documenso kostenlos selbst hosten oder unsere sofort einsatzbereite gehostete Version nutzen. Die gehostete Version bietet zusätzlichen Support, schmerzfreie Skalierbarkeit und mehr. Frühzeitige Anwender erhalten in diesem Jahr Zugriff auf alle Funktionen, die wir entwickeln, ohne zusätzliche Kosten! Für immer! Ja, das beinhaltet später mehrere Benutzer pro Konto. Wenn Sie Documenso für Ihr Unternehmen möchten, sprechen wir gerne über Ihre Bedürfnisse."
|
||||
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -61,15 +61,11 @@ msgstr "Because signing should be celebrated. That’s why we care about the sma
|
||||
msgid "Blog"
|
||||
msgstr "Blog"
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/learn-more-callout.tsx:32
|
||||
msgid "Book a Sales Call"
|
||||
msgstr "Book a Sales Call"
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/open-build-template-bento.tsx:60
|
||||
msgid "Build on top."
|
||||
msgstr "Build on top."
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/pricing/page.tsx:167
|
||||
#: apps/marketing/src/app/(marketing)/pricing/page.tsx:163
|
||||
msgid "Can I use Documenso commercially?"
|
||||
msgstr "Can I use Documenso commercially?"
|
||||
|
||||
@@ -97,7 +93,7 @@ msgstr "Completed Documents"
|
||||
msgid "Completed Documents per Month"
|
||||
msgstr "Completed Documents per Month"
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/share-connect-paid-widget-bento.tsx:63
|
||||
#: apps/marketing/src/components/(marketing)/share-connect-paid-widget-bento.tsx:61
|
||||
msgid "Connections"
|
||||
msgstr "Connections"
|
||||
|
||||
@@ -105,7 +101,7 @@ msgstr "Connections"
|
||||
msgid "Contact Us"
|
||||
msgstr "Contact Us"
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/share-connect-paid-widget-bento.tsx:65
|
||||
#: apps/marketing/src/components/(marketing)/share-connect-paid-widget-bento.tsx:63
|
||||
msgid "Create connections and automations with Zapier and more to integrate with your favorite tools."
|
||||
msgstr "Create connections and automations with Zapier and more to integrate with your favorite tools."
|
||||
|
||||
@@ -133,7 +129,7 @@ msgstr "Designed for every stage of your journey."
|
||||
msgid "Direct Link"
|
||||
msgstr "Direct Link"
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/pricing/page.tsx:185
|
||||
#: apps/marketing/src/app/(marketing)/pricing/page.tsx:181
|
||||
msgid "Documenso is a community effort to create an open and vibrant ecosystem around a tool, everybody is free to use and adapt. By being truly open we want to create trusted infrastructure for the future of the internet."
|
||||
msgstr "Documenso is a community effort to create an open and vibrant ecosystem around a tool, everybody is free to use and adapt. By being truly open we want to create trusted infrastructure for the future of the internet."
|
||||
|
||||
@@ -151,17 +147,13 @@ msgstr "Document signing,<0/>finally open source."
|
||||
msgid "Documentation"
|
||||
msgstr "Documentation"
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/share-connect-paid-widget-bento.tsx:108
|
||||
#: apps/marketing/src/components/(marketing)/share-connect-paid-widget-bento.tsx:106
|
||||
msgid "Easily embed Documenso into your product. Simply copy and paste our react widget into your application."
|
||||
msgstr "Easily embed Documenso into your product. Simply copy and paste our react widget into your application."
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/share-connect-paid-widget-bento.tsx:42
|
||||
#~ msgid "Easy Sharing (Soon)."
|
||||
#~ msgstr "Easy Sharing (Soon)."
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/share-connect-paid-widget-bento.tsx:42
|
||||
msgid "Easy Sharing."
|
||||
msgstr "Easy Sharing."
|
||||
msgid "Easy Sharing (Soon)."
|
||||
msgstr "Easy Sharing (Soon)."
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/pricing-table.tsx:148
|
||||
#: apps/marketing/src/components/(marketing)/pricing-table.tsx:192
|
||||
@@ -220,7 +212,7 @@ msgstr "From the blog"
|
||||
msgid "Full-Time"
|
||||
msgstr "Full-Time"
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/share-connect-paid-widget-bento.tsx:85
|
||||
#: apps/marketing/src/components/(marketing)/share-connect-paid-widget-bento.tsx:83
|
||||
msgid "Get paid (Soon)."
|
||||
msgstr "Get paid (Soon)."
|
||||
|
||||
@@ -228,7 +220,7 @@ msgstr "Get paid (Soon)."
|
||||
msgid "Get started"
|
||||
msgstr "Get started"
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/pricing/page.tsx:79
|
||||
#: apps/marketing/src/app/(marketing)/pricing/page.tsx:75
|
||||
msgid "Get Started"
|
||||
msgstr "Get Started"
|
||||
|
||||
@@ -260,11 +252,11 @@ msgstr "Global Salary Bands"
|
||||
msgid "Growth"
|
||||
msgstr "Growth"
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/pricing/page.tsx:138
|
||||
#: apps/marketing/src/app/(marketing)/pricing/page.tsx:134
|
||||
msgid "How can I contribute?"
|
||||
msgstr "How can I contribute?"
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/pricing/page.tsx:109
|
||||
#: apps/marketing/src/app/(marketing)/pricing/page.tsx:105
|
||||
msgid "How do you handle my data?"
|
||||
msgstr "How do you handle my data?"
|
||||
|
||||
@@ -272,7 +264,7 @@ msgstr "How do you handle my data?"
|
||||
msgid "Individual"
|
||||
msgstr "Individual"
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/share-connect-paid-widget-bento.tsx:87
|
||||
#: apps/marketing/src/components/(marketing)/share-connect-paid-widget-bento.tsx:85
|
||||
msgid "Integrated payments with Stripe so you don’t have to worry about getting paid."
|
||||
msgstr "Integrated payments with Stripe so you don’t have to worry about getting paid."
|
||||
|
||||
@@ -296,10 +288,6 @@ msgstr "Join Date"
|
||||
msgid "Join the Open Signing Movement"
|
||||
msgstr "Join the Open Signing Movement"
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/learn-more-callout.tsx:44
|
||||
msgid "Learn More"
|
||||
msgstr "Learn More"
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/open/team-members.tsx:46
|
||||
msgid "Location"
|
||||
msgstr "Location"
|
||||
@@ -403,21 +391,13 @@ msgstr "Privacy"
|
||||
msgid "Profile"
|
||||
msgstr "Profile"
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/learn-more-callout.tsx:30
|
||||
msgid "Questions?"
|
||||
msgstr "Questions?"
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/share-connect-paid-widget-bento.tsx:106
|
||||
#: apps/marketing/src/components/(marketing)/share-connect-paid-widget-bento.tsx:104
|
||||
msgid "React Widget (Soon)."
|
||||
msgstr "React Widget (Soon)."
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/share-connect-paid-widget-bento.tsx:44
|
||||
#~ msgid "Receive your personal link to share with everyone you care about."
|
||||
#~ msgstr "Receive your personal link to share with everyone you care about."
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/share-connect-paid-widget-bento.tsx:44
|
||||
msgid "Receive your personal profile link to share with everyone you care about."
|
||||
msgstr "Receive your personal profile link to share with everyone you care about."
|
||||
msgid "Receive your personal link to share with everyone you care about."
|
||||
msgstr "Receive your personal link to share with everyone you care about."
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/open/team-members.tsx:37
|
||||
msgid "Role"
|
||||
@@ -436,7 +416,7 @@ msgstr "Save $60 or $120"
|
||||
msgid "Search languages..."
|
||||
msgstr "Search languages..."
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/pricing/page.tsx:113
|
||||
#: apps/marketing/src/app/(marketing)/pricing/page.tsx:109
|
||||
msgid "Securely. Our data centers are located in Frankfurt (Germany), giving us the best local privacy laws. We are very aware of the sensitive nature of our data and follow best practices to ensure the security and integrity of the data entrusted to us."
|
||||
msgstr "Securely. Our data centers are located in Frankfurt (Germany), giving us the best local privacy laws. We are very aware of the sensitive nature of our data and follow best practices to ensure the security and integrity of the data entrusted to us."
|
||||
|
||||
@@ -511,7 +491,7 @@ msgstr "Teams"
|
||||
msgid "Template Store (Soon)."
|
||||
msgstr "Template Store (Soon)."
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/pricing/page.tsx:142
|
||||
#: apps/marketing/src/app/(marketing)/pricing/page.tsx:138
|
||||
msgid "That's awesome. You can take a look at the current <0>Issues</0> and join our <1>Discord Community</1> to keep up to date, on what the current priorities are. In any case, we are an open community and welcome all input, technical and non-technical ❤️"
|
||||
msgstr "That's awesome. You can take a look at the current <0>Issues</0> and join our <1>Discord Community</1> to keep up to date, on what the current priorities are. In any case, we are an open community and welcome all input, technical and non-technical ❤️"
|
||||
|
||||
@@ -565,7 +545,7 @@ msgstr "Unlimited Documents per Month"
|
||||
msgid "Up to 10 recipients per document"
|
||||
msgstr "Up to 10 recipients per document"
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/pricing/page.tsx:127
|
||||
#: apps/marketing/src/app/(marketing)/pricing/page.tsx:123
|
||||
msgid "Using our hosted version is the easiest way to get started, you can simply subscribe and start signing your documents. We take care of the infrastructure, so you can focus on your business. Additionally, when using our hosted version you benefit from our trusted signing certificates which helps you to build trust with your customers."
|
||||
msgstr "Using our hosted version is the easiest way to get started, you can simply subscribe and start signing your documents. We take care of the infrastructure, so you can focus on your business. Additionally, when using our hosted version you benefit from our trusted signing certificates which helps you to build trust with your customers."
|
||||
|
||||
@@ -573,11 +553,11 @@ msgstr "Using our hosted version is the easiest way to get started, you can simp
|
||||
msgid "View all stats"
|
||||
msgstr "View all stats"
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/pricing/page.tsx:199
|
||||
#: apps/marketing/src/app/(marketing)/pricing/page.tsx:195
|
||||
msgid "We are happy to assist you at <0>support@documenso.com</0> or <1>in our Discord-Support-Channel</1> please message either Lucas or Timur to get added to the channel if you are not already a member."
|
||||
msgstr "We are happy to assist you at <0>support@documenso.com</0> or <1>in our Discord-Support-Channel</1> please message either Lucas or Timur to get added to the channel if you are not already a member."
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/pricing/page.tsx:93
|
||||
#: apps/marketing/src/app/(marketing)/pricing/page.tsx:89
|
||||
msgid "What is the difference between the plans?"
|
||||
msgstr "What is the difference between the plans?"
|
||||
|
||||
@@ -585,15 +565,15 @@ msgstr "What is the difference between the plans?"
|
||||
msgid "When it comes to sending or receiving a contract, you can count on lightning-fast speeds."
|
||||
msgstr "When it comes to sending or receiving a contract, you can count on lightning-fast speeds."
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/pricing/page.tsx:195
|
||||
#: apps/marketing/src/app/(marketing)/pricing/page.tsx:191
|
||||
msgid "Where can I get support?"
|
||||
msgstr "Where can I get support?"
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/pricing/page.tsx:181
|
||||
#: apps/marketing/src/app/(marketing)/pricing/page.tsx:177
|
||||
msgid "Why should I prefer Documenso over DocuSign or some other signing tool?"
|
||||
msgstr "Why should I prefer Documenso over DocuSign or some other signing tool?"
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/pricing/page.tsx:123
|
||||
#: apps/marketing/src/app/(marketing)/pricing/page.tsx:119
|
||||
msgid "Why should I use your hosting service?"
|
||||
msgstr "Why should I use your hosting service?"
|
||||
|
||||
@@ -601,11 +581,11 @@ msgstr "Why should I use your hosting service?"
|
||||
msgid "Yearly"
|
||||
msgstr "Yearly"
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/pricing/page.tsx:171
|
||||
#: apps/marketing/src/app/(marketing)/pricing/page.tsx:167
|
||||
msgid "Yes! Documenso is offered under the GNU AGPL V3 open source license. This means you can use it for free and even modify it to fit your needs, as long as you publish your changes under the same license."
|
||||
msgstr "Yes! Documenso is offered under the GNU AGPL V3 open source license. This means you can use it for free and even modify it to fit your needs, as long as you publish your changes under the same license."
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/pricing/page.tsx:97
|
||||
#: apps/marketing/src/app/(marketing)/pricing/page.tsx:93
|
||||
msgid "You can self-host Documenso for free or use our ready-to-use hosted version. The hosted version comes with additional support, painless scalability and more. Early adopters will get access to all features we build this year, for no additional cost! Forever! Yes, that includes multiple users per account later. If you want Documenso for your enterprise, we are happy to talk about your needs."
|
||||
msgstr "You can self-host Documenso for free or use our ready-to-use hosted version. The hosted version comes with additional support, painless scalability and more. Early adopters will get access to all features we build this year, for no additional cost! Forever! Yes, that includes multiple users per account later. If you want Documenso for your enterprise, we are happy to talk about your needs."
|
||||
|
||||
|
||||
16
packages/lib/utils/is-valid-redirect-url.ts
Normal file
16
packages/lib/utils/is-valid-redirect-url.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
const ALLOWED_PROTOCOLS = ['http', 'https'];
|
||||
|
||||
export const isValidRedirectUrl = (value: string) => {
|
||||
try {
|
||||
const url = new URL(value);
|
||||
|
||||
console.log({ protocol: url.protocol });
|
||||
if (!ALLOWED_PROTOCOLS.includes(url.protocol.slice(0, -1).toLowerCase())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
@@ -29,6 +29,7 @@ import {
|
||||
ZCreateDocumentMutationSchema,
|
||||
ZDeleteDraftDocumentMutationSchema as ZDeleteDocumentMutationSchema,
|
||||
ZDownloadAuditLogsMutationSchema,
|
||||
ZDownloadCertificateMutationSchema,
|
||||
ZFindDocumentAuditLogsQuerySchema,
|
||||
ZGetDocumentByIdQuerySchema,
|
||||
ZGetDocumentByTokenQuerySchema,
|
||||
@@ -411,7 +412,14 @@ export const documentRouter = router({
|
||||
id: documentId,
|
||||
userId: ctx.user.id,
|
||||
teamId,
|
||||
});
|
||||
}).catch(() => null);
|
||||
|
||||
if (!document || document.teamId !== teamId) {
|
||||
throw new TRPCError({
|
||||
code: 'FORBIDDEN',
|
||||
message: 'You do not have access to this document.',
|
||||
});
|
||||
}
|
||||
|
||||
const encrypted = encryptSecondaryData({
|
||||
data: document.id.toString(),
|
||||
@@ -433,7 +441,7 @@ export const documentRouter = router({
|
||||
}),
|
||||
|
||||
downloadCertificate: authenticatedProcedure
|
||||
.input(ZDownloadAuditLogsMutationSchema)
|
||||
.input(ZDownloadCertificateMutationSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
try {
|
||||
const { documentId, teamId } = input;
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import { URL_REGEX } from '@documenso/lib/constants/url-regex';
|
||||
import {
|
||||
ZDocumentAccessAuthTypesSchema,
|
||||
ZDocumentActionAuthTypesSchema,
|
||||
} from '@documenso/lib/types/document-auth';
|
||||
import { ZBaseTableSearchParamsSchema } from '@documenso/lib/types/search-params';
|
||||
import { isValidRedirectUrl } from '@documenso/lib/utils/is-valid-redirect-url';
|
||||
import { FieldType, RecipientRole } from '@documenso/prisma/client';
|
||||
|
||||
export const ZFindDocumentAuditLogsQuerySchema = ZBaseTableSearchParamsSchema.extend({
|
||||
@@ -65,8 +65,9 @@ export const ZSetSettingsForDocumentMutationSchema = z.object({
|
||||
redirectUrl: z
|
||||
.string()
|
||||
.optional()
|
||||
.refine((value) => value === undefined || value === '' || URL_REGEX.test(value), {
|
||||
message: 'Please enter a valid URL',
|
||||
.refine((value) => value === undefined || value === '' || isValidRedirectUrl(value), {
|
||||
message:
|
||||
'Please enter a valid URL, make sure you include http:// or https:// part of the url.',
|
||||
}),
|
||||
}),
|
||||
});
|
||||
@@ -131,8 +132,9 @@ export const ZSendDocumentMutationSchema = z.object({
|
||||
redirectUrl: z
|
||||
.string()
|
||||
.optional()
|
||||
.refine((value) => value === undefined || value === '' || URL_REGEX.test(value), {
|
||||
message: 'Please enter a valid URL',
|
||||
.refine((value) => value === undefined || value === '' || isValidRedirectUrl(value), {
|
||||
message:
|
||||
'Please enter a valid URL, make sure you include http:// or https:// part of the url.',
|
||||
}),
|
||||
}),
|
||||
});
|
||||
@@ -170,6 +172,11 @@ export const ZDownloadAuditLogsMutationSchema = z.object({
|
||||
teamId: z.number().optional(),
|
||||
});
|
||||
|
||||
export const ZDownloadCertificateMutationSchema = z.object({
|
||||
documentId: z.number(),
|
||||
teamId: z.number().optional(),
|
||||
});
|
||||
|
||||
export const ZMoveDocumentsToTeamSchema = z.object({
|
||||
documentId: z.number(),
|
||||
teamId: z.number(),
|
||||
|
||||
@@ -66,6 +66,7 @@ export const templateRouter = router({
|
||||
directRecipientName,
|
||||
directRecipientEmail,
|
||||
directTemplateToken,
|
||||
directTemplateExternalId,
|
||||
signedFieldValues,
|
||||
templateUpdatedAt,
|
||||
} = input;
|
||||
@@ -76,6 +77,7 @@ export const templateRouter = router({
|
||||
directRecipientName,
|
||||
directRecipientEmail,
|
||||
directTemplateToken,
|
||||
directTemplateExternalId,
|
||||
signedFieldValues,
|
||||
templateUpdatedAt,
|
||||
user: ctx.user
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import { URL_REGEX } from '@documenso/lib/constants/url-regex';
|
||||
import {
|
||||
ZDocumentAccessAuthTypesSchema,
|
||||
ZDocumentActionAuthTypesSchema,
|
||||
} from '@documenso/lib/types/document-auth';
|
||||
import { ZBaseTableSearchParamsSchema } from '@documenso/lib/types/search-params';
|
||||
import { isValidRedirectUrl } from '@documenso/lib/utils/is-valid-redirect-url';
|
||||
import { TemplateType } from '@documenso/prisma/client';
|
||||
|
||||
import { ZSignFieldWithTokenMutationSchema } from '../field-router/schema';
|
||||
@@ -20,6 +20,7 @@ export const ZCreateDocumentFromDirectTemplateMutationSchema = z.object({
|
||||
directRecipientName: z.string().optional(),
|
||||
directRecipientEmail: z.string().email(),
|
||||
directTemplateToken: z.string().min(1),
|
||||
directTemplateExternalId: z.string().optional(),
|
||||
signedFieldValues: z.array(ZSignFieldWithTokenMutationSchema),
|
||||
templateUpdatedAt: z.date(),
|
||||
});
|
||||
@@ -96,8 +97,9 @@ export const ZUpdateTemplateSettingsMutationSchema = z.object({
|
||||
redirectUrl: z
|
||||
.string()
|
||||
.optional()
|
||||
.refine((value) => value === undefined || value === '' || URL_REGEX.test(value), {
|
||||
message: 'Please enter a valid URL',
|
||||
.refine((value) => value === undefined || value === '' || isValidRedirectUrl(value), {
|
||||
message:
|
||||
'Please enter a valid URL, make sure you include http:// or https:// part of the url.',
|
||||
}),
|
||||
})
|
||||
.optional(),
|
||||
|
||||
3
packages/tsconfig/process-env.d.ts
vendored
3
packages/tsconfig/process-env.d.ts
vendored
@@ -9,6 +9,9 @@ declare namespace NodeJS {
|
||||
NEXT_PRIVATE_OIDC_WELL_KNOWN?: string;
|
||||
NEXT_PRIVATE_OIDC_CLIENT_ID?: string;
|
||||
NEXT_PRIVATE_OIDC_CLIENT_SECRET?: string;
|
||||
NEXT_PRIVATE_OIDC_PROVIDER_LABEL?: string;
|
||||
NEXT_PRIVATE_OIDC_ALLOW_SIGNUP?: string;
|
||||
NEXT_PRIVATE_OIDC_SKIP_VERIFY?: string;
|
||||
|
||||
NEXT_PRIVATE_DATABASE_URL: string;
|
||||
NEXT_PRIVATE_ENCRYPTION_KEY: string;
|
||||
|
||||
@@ -457,10 +457,11 @@ export const AddFieldsFormPartial = ({
|
||||
{selectedField && (
|
||||
<div
|
||||
className={cn(
|
||||
'pointer-events-none fixed z-50 flex cursor-pointer flex-col items-center justify-center bg-white transition duration-200',
|
||||
'text-muted-foreground dark:text-muted-background pointer-events-none fixed z-50 flex cursor-pointer flex-col items-center justify-center bg-white transition duration-200',
|
||||
selectedSignerStyles.default.base,
|
||||
{
|
||||
'-rotate-6 scale-90 opacity-50': !isFieldWithinBounds,
|
||||
'-rotate-6 scale-90 opacity-50 dark:bg-black/20': !isFieldWithinBounds,
|
||||
'dark:text-black/60': isFieldWithinBounds,
|
||||
},
|
||||
)}
|
||||
style={{
|
||||
|
||||
@@ -82,8 +82,12 @@ export const AddSettingsFormPartial = ({
|
||||
globalAccessAuth: documentAuthOption?.globalAccessAuth || undefined,
|
||||
globalActionAuth: documentAuthOption?.globalActionAuth || undefined,
|
||||
meta: {
|
||||
timezone: document.documentMeta?.timezone ?? DEFAULT_DOCUMENT_TIME_ZONE,
|
||||
dateFormat: document.documentMeta?.dateFormat ?? DEFAULT_DOCUMENT_DATE_FORMAT,
|
||||
timezone:
|
||||
TIME_ZONES.find((timezone) => timezone === document.documentMeta?.timezone) ??
|
||||
DEFAULT_DOCUMENT_TIME_ZONE,
|
||||
dateFormat:
|
||||
DATE_FORMATS.find((format) => format.label === document.documentMeta?.dateFormat)
|
||||
?.value ?? DEFAULT_DOCUMENT_DATE_FORMAT,
|
||||
redirectUrl: document.documentMeta?.redirectUrl ?? '',
|
||||
},
|
||||
},
|
||||
@@ -98,10 +102,20 @@ export const AddSettingsFormPartial = ({
|
||||
// We almost always want to set the timezone to the user's local timezone to avoid confusion
|
||||
// when the document is signed.
|
||||
useEffect(() => {
|
||||
if (!form.formState.touchedFields.meta?.timezone && !documentHasBeenSent) {
|
||||
if (
|
||||
!form.formState.touchedFields.meta?.timezone &&
|
||||
!documentHasBeenSent &&
|
||||
!document.documentMeta?.timezone
|
||||
) {
|
||||
form.setValue('meta.timezone', Intl.DateTimeFormat().resolvedOptions().timeZone);
|
||||
}
|
||||
}, [documentHasBeenSent, form, form.setValue, form.formState.touchedFields.meta?.timezone]);
|
||||
}, [
|
||||
documentHasBeenSent,
|
||||
form,
|
||||
form.setValue,
|
||||
form.formState.touchedFields.meta?.timezone,
|
||||
document.documentMeta?.timezone,
|
||||
]);
|
||||
|
||||
return (
|
||||
<>
|
||||
|
||||
@@ -2,11 +2,11 @@ import { z } from 'zod';
|
||||
|
||||
import { DEFAULT_DOCUMENT_DATE_FORMAT } from '@documenso/lib/constants/date-formats';
|
||||
import { DEFAULT_DOCUMENT_TIME_ZONE } from '@documenso/lib/constants/time-zones';
|
||||
import { URL_REGEX } from '@documenso/lib/constants/url-regex';
|
||||
import {
|
||||
ZDocumentAccessAuthTypesSchema,
|
||||
ZDocumentActionAuthTypesSchema,
|
||||
} from '@documenso/lib/types/document-auth';
|
||||
import { isValidRedirectUrl } from '@documenso/lib/utils/is-valid-redirect-url';
|
||||
|
||||
export const ZMapNegativeOneToUndefinedSchema = z
|
||||
.string()
|
||||
@@ -34,8 +34,9 @@ export const ZAddSettingsFormSchema = z.object({
|
||||
redirectUrl: z
|
||||
.string()
|
||||
.optional()
|
||||
.refine((value) => value === undefined || value === '' || URL_REGEX.test(value), {
|
||||
message: 'Please enter a valid URL',
|
||||
.refine((value) => value === undefined || value === '' || isValidRedirectUrl(value), {
|
||||
message:
|
||||
'Please enter a valid URL, make sure you include http:// or https:// part of the url.',
|
||||
}),
|
||||
}),
|
||||
});
|
||||
|
||||
@@ -2,11 +2,11 @@ import { z } from 'zod';
|
||||
|
||||
import { DEFAULT_DOCUMENT_DATE_FORMAT } from '@documenso/lib/constants/date-formats';
|
||||
import { DEFAULT_DOCUMENT_TIME_ZONE } from '@documenso/lib/constants/time-zones';
|
||||
import { URL_REGEX } from '@documenso/lib/constants/url-regex';
|
||||
import {
|
||||
ZDocumentAccessAuthTypesSchema,
|
||||
ZDocumentActionAuthTypesSchema,
|
||||
} from '@documenso/lib/types/document-auth';
|
||||
import { isValidRedirectUrl } from '@documenso/lib/utils/is-valid-redirect-url';
|
||||
|
||||
import { ZMapNegativeOneToUndefinedSchema } from '../document-flow/add-settings.types';
|
||||
|
||||
@@ -27,8 +27,9 @@ export const ZAddTemplateSettingsFormSchema = z.object({
|
||||
redirectUrl: z
|
||||
.string()
|
||||
.optional()
|
||||
.refine((value) => value === undefined || value === '' || URL_REGEX.test(value), {
|
||||
message: 'Please enter a valid URL',
|
||||
.refine((value) => value === undefined || value === '' || isValidRedirectUrl(value), {
|
||||
message:
|
||||
'Please enter a valid URL, make sure you include http:// or https:// part of the url.',
|
||||
}),
|
||||
}),
|
||||
});
|
||||
|
||||
62
render.yaml
62
render.yaml
@@ -1,11 +1,11 @@
|
||||
services:
|
||||
- type: web
|
||||
runtime: node
|
||||
name: documenso-app
|
||||
env: node
|
||||
plan: free
|
||||
buildCommand: npm i && npm run build:web
|
||||
startCommand: npx prisma migrate deploy --schema packages/prisma/schema.prisma && npm run start
|
||||
healthCheckPath: /api/trpc/health
|
||||
startCommand: npx prisma migrate deploy --schema packages/prisma/schema.prisma && npx turbo run start --filter=@documenso/web
|
||||
healthCheckPath: /api/health
|
||||
|
||||
envVars:
|
||||
# Node Version
|
||||
@@ -98,6 +98,62 @@ services:
|
||||
- key: NEXT_PRIVATE_UPLOAD_SECRET_ACCESS_KEY
|
||||
sync: false
|
||||
|
||||
# Crypto
|
||||
- key: NEXT_PRIVATE_ENCRYPTION_KEY
|
||||
generateValue: true
|
||||
- key: NEXT_PRIVATE_ENCRYPTION_SECONDARY_KEY
|
||||
generateValue: true
|
||||
|
||||
# Auth Optional
|
||||
- key: NEXT_PRIVATE_GOOGLE_CLIENT_ID
|
||||
sync: false
|
||||
- key: NEXT_PRIVATE_GOOGLE_CLIENT_SECRET
|
||||
sync: false
|
||||
|
||||
# Signing
|
||||
- key: NEXT_PRIVATE_SIGNING_TRANSPORT
|
||||
sync: false
|
||||
- key: NEXT_PRIVATE_SIGNING_PASSPHRASE
|
||||
sync: false
|
||||
- key: NEXT_PRIVATE_SIGNING_LOCAL_FILE_PATH
|
||||
sync: false
|
||||
- key: NEXT_PRIVATE_SIGNING_LOCAL_FILE_CONTENTS
|
||||
sync: false
|
||||
- key: NEXT_PRIVATE_SIGNING_GCLOUD_HSM_KEY_PATH
|
||||
sync: false
|
||||
- key: NEXT_PRIVATE_SIGNING_GCLOUD_HSM_PUBLIC_CRT_FILE_PATH
|
||||
sync: false
|
||||
- key: NEXT_PRIVATE_SIGNING_GCLOUD_HSM_PUBLIC_CRT_FILE_CONTENTS
|
||||
sync: false
|
||||
- key: NEXT_PRIVATE_SIGNING_GCLOUD_APPLICATION_CREDENTIALS_CONTENTS
|
||||
sync: false
|
||||
|
||||
# SMTP Optional
|
||||
- key: NEXT_PRIVATE_SMTP_APIKEY_USER
|
||||
sync: false
|
||||
- key: NEXT_PRIVATE_SMTP_APIKEY
|
||||
sync: false
|
||||
- key: NEXT_PRIVATE_SMTP_SECURE
|
||||
sync: false
|
||||
- key: NEXT_PRIVATE_RESEND_API_KEY
|
||||
sync: false
|
||||
- key: NEXT_PRIVATE_MAILCHANNELS_API_KEY
|
||||
sync: false
|
||||
- key: NEXT_PRIVATE_MAILCHANNELS_ENDPOINT
|
||||
sync: false
|
||||
- key: NEXT_PRIVATE_MAILCHANNELS_DKIM_DOMAIN
|
||||
sync: false
|
||||
- key: NEXT_PRIVATE_MAILCHANNELS_DKIM_SELECTOR
|
||||
sync: false
|
||||
- key: NEXT_PRIVATE_MAILCHANNELS_DKIM_PRIVATE_KEY
|
||||
sync: false
|
||||
- key: NEXT_PUBLIC_DOCUMENT_SIZE_UPLOAD_LIMIT
|
||||
sync: false
|
||||
|
||||
# Features Optional
|
||||
- key: NEXT_PUBLIC_DISABLE_SIGNUP
|
||||
sync: false
|
||||
|
||||
databases:
|
||||
- name: documenso-db
|
||||
plan: free
|
||||
|
||||
29
turbo.json
29
turbo.json
@@ -2,20 +2,12 @@
|
||||
"$schema": "https://turbo.build/schema.json",
|
||||
"pipeline": {
|
||||
"build": {
|
||||
"dependsOn": [
|
||||
"prebuild",
|
||||
"^build"
|
||||
],
|
||||
"outputs": [
|
||||
".next/**",
|
||||
"!.next/cache/**"
|
||||
]
|
||||
"dependsOn": ["prebuild", "^build"],
|
||||
"outputs": [".next/**", "!.next/cache/**"]
|
||||
},
|
||||
"prebuild": {
|
||||
"cache": false,
|
||||
"dependsOn": [
|
||||
"^prebuild"
|
||||
]
|
||||
"dependsOn": ["^prebuild"]
|
||||
},
|
||||
"lint": {
|
||||
"cache": false
|
||||
@@ -31,9 +23,7 @@
|
||||
"persistent": true
|
||||
},
|
||||
"start": {
|
||||
"dependsOn": [
|
||||
"^build"
|
||||
],
|
||||
"dependsOn": ["^build"],
|
||||
"cache": false,
|
||||
"persistent": true
|
||||
},
|
||||
@@ -41,18 +31,14 @@
|
||||
"cache": false
|
||||
},
|
||||
"test:e2e": {
|
||||
"dependsOn": [
|
||||
"^build"
|
||||
],
|
||||
"dependsOn": ["^build"],
|
||||
"cache": false
|
||||
},
|
||||
"translate:compile": {
|
||||
"cache": false
|
||||
}
|
||||
},
|
||||
"globalDependencies": [
|
||||
"**/.env.*local"
|
||||
],
|
||||
"globalDependencies": ["**/.env.*local"],
|
||||
"globalEnv": [
|
||||
"APP_VERSION",
|
||||
"NEXT_PRIVATE_ENCRYPTION_KEY",
|
||||
@@ -83,6 +69,9 @@
|
||||
"NEXT_PRIVATE_OIDC_WELL_KNOWN",
|
||||
"NEXT_PRIVATE_OIDC_CLIENT_ID",
|
||||
"NEXT_PRIVATE_OIDC_CLIENT_SECRET",
|
||||
"NEXT_PRIVATE_OIDC_PROVIDER_LABEL",
|
||||
"NEXT_PRIVATE_OIDC_ALLOW_SIGNUP",
|
||||
"NEXT_PRIVATE_OIDC_SKIP_VERIFY",
|
||||
"NEXT_PUBLIC_UPLOAD_TRANSPORT",
|
||||
"NEXT_PRIVATE_UPLOAD_ENDPOINT",
|
||||
"NEXT_PRIVATE_UPLOAD_FORCE_PATH_STYLE",
|
||||
|
||||
Reference in New Issue
Block a user