Compare commits

..

4 Commits

Author SHA1 Message Date
Timur Ercan f0c6694343 Merge branch 'main' into chore/add-ctas 2024-07-29 17:09:59 +02:00
github-actions 79d3cbd0b7 chore: extract translations 2024-07-26 15:44:01 +00:00
Timur Ercan 24afaffd33 chore: add a sales and docs cta to marketing, remove soon for profile/ add text 2024-07-26 17:42:41 +02:00
Timur Ercan 8af257e319 chore: link self-hosting start to docs 2024-07-26 17:05:15 +02:00
64 changed files with 734 additions and 744 deletions
-7
View File
@@ -10,19 +10,12 @@ 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,6 +1,5 @@
{
"index": "Getting Started",
"signing-certificate": "Signing Certificate",
"how-to": "How To",
"setting-up-oauth-providers": "Setting up OAuth Providers"
}
"how-to": "How To"
}
@@ -1,29 +0,0 @@
---
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,6 +1,5 @@
{
"index": "Introduction",
"support": "Support",
"-- How To Use": {
"type": "separator",
"title": "How To Use"
@@ -14,7 +13,6 @@
"type": "separator",
"title": "Legal Overview"
},
"fair-use": "Fair Use Policy",
"licenses": "Licenses",
"compliance": "Compliance"
}
@@ -1,34 +0,0 @@
---
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 wont 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 { Callout, Steps } from 'nextra/components';
import { Steps } from 'nextra/components';
# Create Your Account
@@ -14,8 +14,6 @@ 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.
@@ -1,38 +0,0 @@
---
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.
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@documenso/marketing",
"version": "1.6.1-rc.1",
"version": "1.6.0",
"private": true,
"license": "AGPL-3.0",
"scripts": {
@@ -64,4 +64,4 @@
"next": "$next"
}
}
}
}
+2 -2
View File
@@ -8,6 +8,7 @@ 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';
@@ -40,11 +41,10 @@ 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,7 +71,11 @@ 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://github.com/documenso/documenso" target="_blank" rel="noreferrer">
<Link
href="https://docs.documenso.com/developers/self-hosting"
target="_blank"
rel="noreferrer"
>
<Trans>Get Started</Trans>
</Link>
</Button>
@@ -0,0 +1,49 @@
'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,9 +39,11 @@ 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 (Soon).</Trans>
<Trans>Easy Sharing.</Trans>
</strong>
<Trans>Receive your personal link to share with everyone you care about.</Trans>
<Trans>
Receive your personal profile link to share with everyone you care about.
</Trans>
</p>
<div className="flex items-center justify-center p-8">
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@documenso/web",
"version": "1.6.1-rc.1",
"version": "1.6.0",
"private": true,
"license": "AGPL-3.0",
"scripts": {
@@ -81,4 +81,4 @@
"next": "$next"
}
}
}
}
-2
View File
@@ -16,7 +16,5 @@ 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 teamId={team?.id} documentId={document.id} />
<DownloadAuditLogButton documentId={document.id} />
</div>
</div>
@@ -9,15 +9,10 @@ import { useToast } from '@documenso/ui/primitives/use-toast';
export type DownloadAuditLogButtonProps = {
className?: string;
teamId?: number;
documentId: number;
};
export const DownloadAuditLogButton = ({
className,
teamId,
documentId,
}: DownloadAuditLogButtonProps) => {
export const DownloadAuditLogButton = ({ className, documentId }: DownloadAuditLogButtonProps) => {
const { toast } = useToast();
const { mutateAsync: downloadAuditLogs, isLoading } =
@@ -25,7 +20,7 @@ export const DownloadAuditLogButton = ({
const onDownloadAuditLogsClick = async () => {
try {
const { url } = await downloadAuditLogs({ teamId, documentId });
const { url } = await downloadAuditLogs({ documentId });
const iframe = Object.assign(document.createElement('iframe'), {
src: url,
@@ -4,7 +4,6 @@ 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';
@@ -42,7 +41,6 @@ export const DeleteDocumentDialog = ({
const router = useRouter();
const { toast } = useToast();
const { refreshLimits } = useLimits();
const [inputValue, setInputValue] = useState('');
const [isDeleteEnabled, setIsDeleteEnabled] = useState(status === DocumentStatus.DRAFT);
@@ -50,7 +48,6 @@ 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, refreshLimits } = useLimits();
const { quota, remaining } = useLimits();
const [isLoading, setIsLoading] = useState(false);
@@ -71,8 +71,6 @@ 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, useSearchParams } from 'next/navigation';
import { useRouter } from 'next/navigation';
import { RECIPIENT_ROLES_DESCRIPTION } from '@documenso/lib/constants/recipient-roles';
import type { Field } from '@documenso/prisma/client';
@@ -39,7 +39,6 @@ export const DirectTemplatePageView = ({
directTemplateToken,
}: TemplatesDirectPageViewProps) => {
const router = useRouter();
const searchParams = useSearchParams();
const { toast } = useToast();
@@ -83,15 +82,8 @@ 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,
@@ -41,7 +41,6 @@ import { CheckboxField } from '~/app/(signing)/sign/[token]/checkbox-field';
import { DateField } from '~/app/(signing)/sign/[token]/date-field';
import { DropdownField } from '~/app/(signing)/sign/[token]/dropdown-field';
import { EmailField } from '~/app/(signing)/sign/[token]/email-field';
import { InitialsField } from '~/app/(signing)/sign/[token]/initials-field';
import { NameField } from '~/app/(signing)/sign/[token]/name-field';
import { NumberField } from '~/app/(signing)/sign/[token]/number-field';
import { useRequiredSigningContext } from '~/app/(signing)/sign/[token]/provider';
@@ -183,15 +182,6 @@ export const SignDirectTemplateForm = ({
onUnsignField={onUnsignField}
/>
))
.with(FieldType.INITIALS, () => (
<InitialsField
key={field.id}
field={field}
recipient={directRecipient}
onSignField={onSignField}
onUnsignField={onUnsignField}
/>
))
.with(FieldType.NAME, () => (
<NameField
key={field.id}
@@ -13,7 +13,6 @@ 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';
@@ -78,9 +77,6 @@ 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 ||
@@ -89,7 +85,7 @@ export default async function CompletedSigningPage({
const sessionData = await getServerSession();
const isLoggedIn = !!sessionData?.user;
const canSignUp = !isExistingUser && NEXT_PUBLIC_DISABLE_SIGNUP !== 'true';
const canSignUp = !isLoggedIn && NEXT_PUBLIC_DISABLE_SIGNUP !== 'true';
return (
<div
@@ -1,140 +0,0 @@
'use client';
import { useTransition } from 'react';
import { useRouter } from 'next/navigation';
import { Loader } from 'lucide-react';
import { DO_NOT_INVALIDATE_QUERY_ON_MUTATION } from '@documenso/lib/constants/trpc';
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
import type { TRecipientActionAuth } from '@documenso/lib/types/document-auth';
import { extractInitials } from '@documenso/lib/utils/recipient-formatter';
import type { Recipient } from '@documenso/prisma/client';
import type { FieldWithSignature } from '@documenso/prisma/types/field-with-signature';
import { trpc } from '@documenso/trpc/react';
import type {
TRemovedSignedFieldWithTokenMutationSchema,
TSignFieldWithTokenMutationSchema,
} from '@documenso/trpc/server/field-router/schema';
import { useToast } from '@documenso/ui/primitives/use-toast';
import { useRequiredSigningContext } from './provider';
import { SigningFieldContainer } from './signing-field-container';
export type InitialsFieldProps = {
field: FieldWithSignature;
recipient: Recipient;
onSignField?: (value: TSignFieldWithTokenMutationSchema) => Promise<void> | void;
onUnsignField?: (value: TRemovedSignedFieldWithTokenMutationSchema) => Promise<void> | void;
};
export const InitialsField = ({
field,
recipient,
onSignField,
onUnsignField,
}: InitialsFieldProps) => {
const router = useRouter();
const { toast } = useToast();
const { fullName } = useRequiredSigningContext();
const initials = extractInitials(fullName);
const [isPending, startTransition] = useTransition();
const { mutateAsync: signFieldWithToken, isLoading: isSignFieldWithTokenLoading } =
trpc.field.signFieldWithToken.useMutation(DO_NOT_INVALIDATE_QUERY_ON_MUTATION);
const {
mutateAsync: removeSignedFieldWithToken,
isLoading: isRemoveSignedFieldWithTokenLoading,
} = trpc.field.removeSignedFieldWithToken.useMutation(DO_NOT_INVALIDATE_QUERY_ON_MUTATION);
const isLoading = isSignFieldWithTokenLoading || isRemoveSignedFieldWithTokenLoading || isPending;
const onSign = async (authOptions?: TRecipientActionAuth) => {
try {
const value = initials ?? '';
const payload: TSignFieldWithTokenMutationSchema = {
token: recipient.token,
fieldId: field.id,
value,
isBase64: false,
authOptions,
};
if (onSignField) {
await onSignField(payload);
return;
}
await signFieldWithToken(payload);
startTransition(() => router.refresh());
} catch (err) {
const error = AppError.parseError(err);
if (error.code === AppErrorCode.UNAUTHORIZED) {
throw error;
}
console.error(err);
toast({
title: 'Error',
description: 'An error occurred while signing the document.',
variant: 'destructive',
});
}
};
const onRemove = async () => {
try {
const payload: TRemovedSignedFieldWithTokenMutationSchema = {
token: recipient.token,
fieldId: field.id,
};
if (onUnsignField) {
await onUnsignField(payload);
return;
}
await removeSignedFieldWithToken(payload);
startTransition(() => router.refresh());
} catch (err) {
console.error(err);
toast({
title: 'Error',
description: 'An error occurred while removing the signature.',
variant: 'destructive',
});
}
};
return (
<SigningFieldContainer field={field} onSign={onSign} onRemove={onRemove} type="Initials">
{isLoading && (
<div className="bg-background absolute inset-0 flex items-center justify-center rounded-md">
<Loader className="text-primary h-5 w-5 animate-spin md:h-8 md:w-8" />
</div>
)}
{!field.inserted && (
<p className="group-hover:text-primary text-muted-foreground duration-200 group-hover:text-yellow-300">
Initials
</p>
)}
{field.inserted && (
<p className="text-muted-foreground dark:text-background/80 truncate duration-200">
{field.customText}
</p>
)}
</SigningFieldContainer>
);
};
@@ -39,16 +39,7 @@ export type SignatureFieldProps = {
*/
onSign?: (documentAuthValue?: TRecipientActionAuth) => Promise<void> | void;
onRemove?: (fieldType?: string) => Promise<void> | void;
type?:
| 'Date'
| 'Initials'
| 'Email'
| 'Name'
| 'Signature'
| 'Radio'
| 'Dropdown'
| 'Number'
| 'Checkbox';
type?: 'Date' | 'Email' | 'Name' | 'Signature' | 'Radio' | 'Dropdown' | 'Number' | 'Checkbox';
tooltipText?: string | null;
};
@@ -26,7 +26,6 @@ import { DateField } from './date-field';
import { DropdownField } from './dropdown-field';
import { EmailField } from './email-field';
import { SigningForm } from './form';
import { InitialsField } from './initials-field';
import { NameField } from './name-field';
import { NumberField } from './number-field';
import { RadioField } from './radio-field';
@@ -102,9 +101,6 @@ export const SigningPageView = ({
.with(FieldType.SIGNATURE, () => (
<SignatureField key={field.id} field={field} recipient={recipient} />
))
.with(FieldType.INITIALS, () => (
<InitialsField key={field.id} field={field} recipient={recipient} />
))
.with(FieldType.NAME, () => (
<NameField key={field.id} field={field} recipient={recipient} />
))
@@ -4,11 +4,7 @@ import { redirect } from 'next/navigation';
import { env } from 'next-runtime-env';
import {
IS_GOOGLE_SSO_ENABLED,
IS_OIDC_SSO_ENABLED,
OIDC_PROVIDER_LABEL,
} from '@documenso/lib/constants/auth';
import { IS_GOOGLE_SSO_ENABLED, IS_OIDC_SSO_ENABLED } from '@documenso/lib/constants/auth';
import { decryptSecondaryData } from '@documenso/lib/server-only/crypto/decrypt';
import { SignInForm } from '~/components/forms/signin';
@@ -47,7 +43,6 @@ 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' && (
@@ -98,7 +98,6 @@ export const DocumentReadOnlyFields = ({ documentMeta, fields }: DocumentReadOnl
{
type: P.union(
FieldType.NAME,
FieldType.INITIALS,
FieldType.EMAIL,
FieldType.NUMBER,
FieldType.RADIO,
+1 -3
View File
@@ -71,7 +71,6 @@ export type SignInFormProps = {
initialEmail?: string;
isGoogleSSOEnabled?: boolean;
isOIDCSSOEnabled?: boolean;
oidcProviderLabel?: string;
};
export const SignInForm = ({
@@ -79,7 +78,6 @@ export const SignInForm = ({
initialEmail,
isGoogleSSOEnabled,
isOIDCSSOEnabled,
oidcProviderLabel,
}: SignInFormProps) => {
const { toast } = useToast();
const { getFlag } = useFeatureFlags();
@@ -371,7 +369,7 @@ export const SignInForm = ({
onClick={onSignInWithOIDCClick}
>
<FaIdCardClip className="mr-2 h-5 w-5" />
{oidcProviderLabel || 'OIDC'}
OIDC
</Button>
)}
+2 -12
View File
@@ -60,23 +60,13 @@ export default async function auth(req: NextApiRequest, res: NextApiResponse) {
},
});
},
linkAccount: async ({ user, account, profile }) => {
linkAccount: async ({ user }) => {
const userId = typeof user.id === 'string' ? parseInt(user.id) : user.id;
if (Number.isNaN(userId)) {
if (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,
+4 -4
View File
@@ -1,12 +1,12 @@
{
"name": "@documenso/root",
"version": "1.6.1-rc.1",
"version": "1.6.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@documenso/root",
"version": "1.6.1-rc.1",
"version": "1.6.0",
"workspaces": [
"apps/*",
"packages/*"
@@ -80,7 +80,7 @@
},
"apps/marketing": {
"name": "@documenso/marketing",
"version": "1.6.1-rc.1",
"version": "1.6.0",
"license": "AGPL-3.0",
"dependencies": {
"@documenso/assets": "*",
@@ -424,7 +424,7 @@
},
"apps/web": {
"name": "@documenso/web",
"version": "1.6.1-rc.1",
"version": "1.6.0",
"license": "AGPL-3.0",
"dependencies": {
"@documenso/api": "*",
+2 -2
View File
@@ -1,6 +1,6 @@
{
"private": true,
"version": "1.6.1-rc.1",
"version": "1.6.0",
"scripts": {
"build": "turbo run build",
"build:web": "turbo run build --filter=@documenso/web",
@@ -80,4 +80,4 @@
"trigger.dev": {
"endpointId": "documenso-app"
}
}
}
+1 -38
View File
@@ -2,9 +2,6 @@ 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';
@@ -225,36 +222,6 @@ 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');
@@ -277,11 +244,7 @@ export const ApiContractV1Implementation = createNextRoute(ApiContractV1, {
await upsertDocumentMeta({
documentId: document.id,
userId: user.id,
subject: body.meta.subject,
message: body.meta.message,
timezone,
dateFormat: dateFormat?.value,
redirectUrl: body.meta.redirectUrl,
...body.meta,
requestMetadata: extractNextApiRequestMetadata(args.req),
});
+2 -19
View File
@@ -1,9 +1,5 @@
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,
@@ -15,8 +11,6 @@ import {
TemplateType,
} from '@documenso/prisma/client';
extendZodWithOpenApi(z);
export const ZNoBodyMutationSchema = null;
/**
@@ -103,19 +97,8 @@ export const ZCreateDocumentMutationSchema = z.object({
.object({
subject: z.string(),
message: 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),
}),
timezone: z.string(),
dateFormat: z.string(),
redirectUrl: z.string(),
})
.partial(),
@@ -1,6 +1,6 @@
'use client';
import { createContext, useCallback, useContext, useEffect, useState } from 'react';
import { createContext, 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 & { refreshLimits: () => Promise<void> };
export type LimitsContextValue = TLimitsResponseSchema;
const LimitsContext = createContext<LimitsContextValue | null>(null);
@@ -23,7 +23,7 @@ export const useLimits = () => {
};
export type LimitsProviderProps = {
initialValue?: TLimitsResponseSchema;
initialValue?: LimitsContextValue;
teamId?: number;
children?: React.ReactNode;
};
@@ -38,7 +38,7 @@ export const LimitsProvider = ({
}: LimitsProviderProps) => {
const [limits, setLimits] = useState(() => initialValue);
const refreshLimits = useCallback(async () => {
const refreshLimits = 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,16 +64,7 @@ export const LimitsProvider = ({
return () => {
window.removeEventListener('focus', onFocus);
};
}, [refreshLimits]);
}, []);
return (
<LimitsContext.Provider
value={{
...limits,
refreshLimits,
}}
>
{children}
</LimitsContext.Provider>
);
return <LimitsContext.Provider value={limits}>{children}</LimitsContext.Provider>;
};
@@ -3,6 +3,7 @@
import { headers } from 'next/headers';
import { getLimits } from '../client';
import type { LimitsContextValue } from './client';
import { LimitsProvider as ClientLimitsProvider } from './client';
export type LimitsProviderProps = {
@@ -13,7 +14,7 @@ export type LimitsProviderProps = {
export const LimitsProvider = async ({ children, teamId }: LimitsProviderProps) => {
const requestHeaders = Object.fromEntries(headers().entries());
const limits = await getLimits({ headers: requestHeaders, teamId });
const limits: LimitsContextValue = await getLimits({ headers: requestHeaders, teamId });
return (
<ClientLimitsProvider initialValue={limits} teamId={teamId}>
-2
View File
@@ -18,8 +18,6 @@ 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',
+2
View File
@@ -0,0 +1,2 @@
export const URL_REGEX =
/^(https?):\/\/(?:www\.)?(?:[a-zA-Z0-9-]+\.)*[a-zA-Z0-9-]+\.[a-zA-Z0-9()]{2,}(?:\/[a-zA-Z0-9-._?&=/]*)?$/i;
+1 -10
View File
@@ -161,10 +161,7 @@ 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:
process.env.NEXT_PRIVATE_OIDC_SKIP_VERIFY === 'true' || profile.email_verified
? new Date().toISOString()
: null,
emailVerified: profile.email_verified ? new Date().toISOString() : null,
};
},
},
@@ -364,12 +361,6 @@ 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') {
+3 -3
View File
@@ -1,12 +1,12 @@
import { z } from 'zod';
import { isValidRedirectUrl } from '../utils/is-valid-redirect-url';
import { URL_REGEX } from '../constants/url-regex';
/**
* Note this allows empty strings.
*/
export const ZUrlSchema = z
.string()
.refine((value) => value === undefined || value === '' || isValidRedirectUrl(value), {
message: 'Please enter a valid URL, make sure you include http:// or https:// part of the url.',
.refine((value) => value === undefined || value === '' || URL_REGEX.test(value), {
message: 'Please enter a valid URL',
});
@@ -231,17 +231,10 @@ export const signFieldWithToken = async ({
type,
data: signatureImageAsBase64 || typedSignature || '',
}))
.with(
FieldType.DATE,
FieldType.EMAIL,
FieldType.NAME,
FieldType.TEXT,
FieldType.INITIALS,
(type) => ({
type,
data: updatedField.customText,
}),
)
.with(FieldType.DATE, FieldType.EMAIL, FieldType.NAME, FieldType.TEXT, (type) => ({
type,
data: updatedField.customText,
}))
.with(
FieldType.NUMBER,
FieldType.RADIO,
@@ -133,14 +133,9 @@ 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 (values ?? []).entries()) {
for (const [index, item] of (meta.data.values ?? []).entries()) {
const offsetY = index * 16;
const checkbox = pdf.getForm().createCheckBox(`checkbox.${field.secondaryId}.${index}`);
@@ -174,14 +169,9 @@ 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 (values ?? []).entries()) {
for (const [index, item] of (meta.data.values ?? []).entries()) {
const offsetY = index * 16;
const radio = pdf.getForm().createRadioGroup(`radio.${field.secondaryId}.${index}`);
@@ -44,7 +44,6 @@ export type CreateDocumentFromDirectTemplateOptions = {
directRecipientName?: string;
directRecipientEmail: string;
directTemplateToken: string;
directTemplateExternalId?: string;
signedFieldValues: TSignFieldWithTokenMutationSchema[];
templateUpdatedAt: Date;
requestMetadata: RequestMetadata;
@@ -64,7 +63,6 @@ export const createDocumentFromDirectTemplate = async ({
directRecipientName: initialDirectRecipientName,
directRecipientEmail,
directTemplateToken,
directTemplateExternalId,
signedFieldValues,
templateUpdatedAt,
requestMetadata,
@@ -229,7 +227,6 @@ export const createDocumentFromDirectTemplate = async ({
title: template.title,
createdAt: initialRequestTime,
status: DocumentStatus.PENDING,
externalId: directTemplateExternalId,
documentDataId: documentData.id,
authOptions: createDocumentAuthOptions({
globalAccessAuth: templateAuthOptions.globalAccessAuth,
@@ -468,7 +465,6 @@ export const createDocumentFromDirectTemplate = async ({
.with(
FieldType.DATE,
FieldType.EMAIL,
FieldType.INITIALS,
FieldType.NAME,
FieldType.TEXT,
FieldType.NUMBER,
File diff suppressed because one or more lines are too long
+45 -25
View File
@@ -66,11 +66,15 @@ 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:163
#: apps/marketing/src/app/(marketing)/pricing/page.tsx:167
msgid "Can I use Documenso commercially?"
msgstr "Kann ich Documenso kommerziell nutzen?"
@@ -98,7 +102,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:61
#: apps/marketing/src/components/(marketing)/share-connect-paid-widget-bento.tsx:63
msgid "Connections"
msgstr "Verbindungen"
@@ -106,7 +110,7 @@ msgstr "Verbindungen"
msgid "Contact Us"
msgstr "Kontaktiere uns"
#: apps/marketing/src/components/(marketing)/share-connect-paid-widget-bento.tsx:63
#: apps/marketing/src/components/(marketing)/share-connect-paid-widget-bento.tsx:65
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."
@@ -134,7 +138,7 @@ msgstr "Entwickelt für jede Phase Ihrer Reise."
msgid "Direct Link"
msgstr "Direktlink"
#: apps/marketing/src/app/(marketing)/pricing/page.tsx:181
#: apps/marketing/src/app/(marketing)/pricing/page.tsx:185
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."
@@ -152,13 +156,17 @@ msgstr "Unterschriften,<0/>endlich Open Source."
msgid "Documentation"
msgstr "Dokumentation"
#: apps/marketing/src/components/(marketing)/share-connect-paid-widget-bento.tsx:106
#: apps/marketing/src/components/(marketing)/share-connect-paid-widget-bento.tsx:108
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)."
#~ msgid "Easy Sharing (Soon)."
#~ msgstr "Einfaches Teilen (Bald)."
#: apps/marketing/src/components/(marketing)/share-connect-paid-widget-bento.tsx:42
msgid "Easy Sharing."
msgstr ""
#: apps/marketing/src/components/(marketing)/pricing-table.tsx:148
#: apps/marketing/src/components/(marketing)/pricing-table.tsx:192
@@ -217,7 +225,7 @@ msgstr "Aus dem Blog"
msgid "Full-Time"
msgstr "Vollzeit"
#: apps/marketing/src/components/(marketing)/share-connect-paid-widget-bento.tsx:83
#: apps/marketing/src/components/(marketing)/share-connect-paid-widget-bento.tsx:85
msgid "Get paid (Soon)."
msgstr "Lassen Sie sich bezahlen (Bald)."
@@ -225,7 +233,7 @@ msgstr "Lassen Sie sich bezahlen (Bald)."
msgid "Get started"
msgstr "Loslegen"
#: apps/marketing/src/app/(marketing)/pricing/page.tsx:75
#: apps/marketing/src/app/(marketing)/pricing/page.tsx:79
msgid "Get Started"
msgstr "Loslegen"
@@ -257,11 +265,11 @@ msgstr "Globale Gehaltsbänder"
msgid "Growth"
msgstr "Wachstum"
#: apps/marketing/src/app/(marketing)/pricing/page.tsx:134
#: apps/marketing/src/app/(marketing)/pricing/page.tsx:138
msgid "How can I contribute?"
msgstr "Wie kann ich beitragen?"
#: apps/marketing/src/app/(marketing)/pricing/page.tsx:105
#: apps/marketing/src/app/(marketing)/pricing/page.tsx:109
msgid "How do you handle my data?"
msgstr "Wie gehen Sie mit meinen Daten um?"
@@ -269,7 +277,7 @@ msgstr "Wie gehen Sie mit meinen Daten um?"
msgid "Individual"
msgstr "Einzelperson"
#: apps/marketing/src/components/(marketing)/share-connect-paid-widget-bento.tsx:85
#: apps/marketing/src/components/(marketing)/share-connect-paid-widget-bento.tsx:87
msgid "Integrated payments with Stripe so you dont have to worry about getting paid."
msgstr "Integrierte Zahlungen mit Stripe, sodass Sie sich keine Sorgen ums Bezahlen machen müssen."
@@ -293,6 +301,10 @@ 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"
@@ -396,13 +408,21 @@ msgstr "Datenschutz"
msgid "Profile"
msgstr "Profil"
#: apps/marketing/src/components/(marketing)/share-connect-paid-widget-bento.tsx:104
#: 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
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."
#~ 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 ""
#: apps/marketing/src/app/(marketing)/open/team-members.tsx:37
msgid "Role"
@@ -421,7 +441,7 @@ msgstr "Sparen Sie $60 oder $120"
msgid "Search languages..."
msgstr "Sprachen suchen..."
#: apps/marketing/src/app/(marketing)/pricing/page.tsx:109
#: apps/marketing/src/app/(marketing)/pricing/page.tsx:113
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."
@@ -496,7 +516,7 @@ msgstr "Teams"
msgid "Template Store (Soon)."
msgstr "Vorlagen-Shop (Demnächst)."
#: apps/marketing/src/app/(marketing)/pricing/page.tsx:138
#: apps/marketing/src/app/(marketing)/pricing/page.tsx:142
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 ❤️"
@@ -550,7 +570,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:123
#: apps/marketing/src/app/(marketing)/pricing/page.tsx:127
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."
@@ -558,11 +578,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:195
#: apps/marketing/src/app/(marketing)/pricing/page.tsx:199
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:89
#: apps/marketing/src/app/(marketing)/pricing/page.tsx:93
msgid "What is the difference between the plans?"
msgstr "Was ist der Unterschied zwischen den Plänen?"
@@ -570,15 +590,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:191
#: apps/marketing/src/app/(marketing)/pricing/page.tsx:195
msgid "Where can I get support?"
msgstr "Wo kann ich Unterstützung bekommen?"
#: apps/marketing/src/app/(marketing)/pricing/page.tsx:177
#: apps/marketing/src/app/(marketing)/pricing/page.tsx:181
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:119
#: apps/marketing/src/app/(marketing)/pricing/page.tsx:123
msgid "Why should I use your hosting service?"
msgstr "Warum sollte ich Ihren Hosting-Service nutzen?"
@@ -586,11 +606,11 @@ msgstr "Warum sollte ich Ihren Hosting-Service nutzen?"
msgid "Yearly"
msgstr "Jährlich"
#: apps/marketing/src/app/(marketing)/pricing/page.tsx:167
#: apps/marketing/src/app/(marketing)/pricing/page.tsx:171
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:93
#: apps/marketing/src/app/(marketing)/pricing/page.tsx:97
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
+45 -25
View File
@@ -61,11 +61,15 @@ msgstr "Because signing should be celebrated. Thats 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:163
#: apps/marketing/src/app/(marketing)/pricing/page.tsx:167
msgid "Can I use Documenso commercially?"
msgstr "Can I use Documenso commercially?"
@@ -93,7 +97,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:61
#: apps/marketing/src/components/(marketing)/share-connect-paid-widget-bento.tsx:63
msgid "Connections"
msgstr "Connections"
@@ -101,7 +105,7 @@ msgstr "Connections"
msgid "Contact Us"
msgstr "Contact Us"
#: apps/marketing/src/components/(marketing)/share-connect-paid-widget-bento.tsx:63
#: apps/marketing/src/components/(marketing)/share-connect-paid-widget-bento.tsx:65
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."
@@ -129,7 +133,7 @@ msgstr "Designed for every stage of your journey."
msgid "Direct Link"
msgstr "Direct Link"
#: apps/marketing/src/app/(marketing)/pricing/page.tsx:181
#: apps/marketing/src/app/(marketing)/pricing/page.tsx:185
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."
@@ -147,13 +151,17 @@ msgstr "Document signing,<0/>finally open source."
msgid "Documentation"
msgstr "Documentation"
#: apps/marketing/src/components/(marketing)/share-connect-paid-widget-bento.tsx:106
#: apps/marketing/src/components/(marketing)/share-connect-paid-widget-bento.tsx:108
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)."
#~ 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."
#: apps/marketing/src/components/(marketing)/pricing-table.tsx:148
#: apps/marketing/src/components/(marketing)/pricing-table.tsx:192
@@ -212,7 +220,7 @@ msgstr "From the blog"
msgid "Full-Time"
msgstr "Full-Time"
#: apps/marketing/src/components/(marketing)/share-connect-paid-widget-bento.tsx:83
#: apps/marketing/src/components/(marketing)/share-connect-paid-widget-bento.tsx:85
msgid "Get paid (Soon)."
msgstr "Get paid (Soon)."
@@ -220,7 +228,7 @@ msgstr "Get paid (Soon)."
msgid "Get started"
msgstr "Get started"
#: apps/marketing/src/app/(marketing)/pricing/page.tsx:75
#: apps/marketing/src/app/(marketing)/pricing/page.tsx:79
msgid "Get Started"
msgstr "Get Started"
@@ -252,11 +260,11 @@ msgstr "Global Salary Bands"
msgid "Growth"
msgstr "Growth"
#: apps/marketing/src/app/(marketing)/pricing/page.tsx:134
#: apps/marketing/src/app/(marketing)/pricing/page.tsx:138
msgid "How can I contribute?"
msgstr "How can I contribute?"
#: apps/marketing/src/app/(marketing)/pricing/page.tsx:105
#: apps/marketing/src/app/(marketing)/pricing/page.tsx:109
msgid "How do you handle my data?"
msgstr "How do you handle my data?"
@@ -264,7 +272,7 @@ msgstr "How do you handle my data?"
msgid "Individual"
msgstr "Individual"
#: apps/marketing/src/components/(marketing)/share-connect-paid-widget-bento.tsx:85
#: apps/marketing/src/components/(marketing)/share-connect-paid-widget-bento.tsx:87
msgid "Integrated payments with Stripe so you dont have to worry about getting paid."
msgstr "Integrated payments with Stripe so you dont have to worry about getting paid."
@@ -288,6 +296,10 @@ 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"
@@ -391,13 +403,21 @@ msgstr "Privacy"
msgid "Profile"
msgstr "Profile"
#: apps/marketing/src/components/(marketing)/share-connect-paid-widget-bento.tsx:104
#: 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
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."
#~ 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."
#: apps/marketing/src/app/(marketing)/open/team-members.tsx:37
msgid "Role"
@@ -416,7 +436,7 @@ msgstr "Save $60 or $120"
msgid "Search languages..."
msgstr "Search languages..."
#: apps/marketing/src/app/(marketing)/pricing/page.tsx:109
#: apps/marketing/src/app/(marketing)/pricing/page.tsx:113
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."
@@ -491,7 +511,7 @@ msgstr "Teams"
msgid "Template Store (Soon)."
msgstr "Template Store (Soon)."
#: apps/marketing/src/app/(marketing)/pricing/page.tsx:138
#: apps/marketing/src/app/(marketing)/pricing/page.tsx:142
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 ❤️"
@@ -545,7 +565,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:123
#: apps/marketing/src/app/(marketing)/pricing/page.tsx:127
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."
@@ -553,11 +573,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:195
#: apps/marketing/src/app/(marketing)/pricing/page.tsx:199
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:89
#: apps/marketing/src/app/(marketing)/pricing/page.tsx:93
msgid "What is the difference between the plans?"
msgstr "What is the difference between the plans?"
@@ -565,15 +585,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:191
#: apps/marketing/src/app/(marketing)/pricing/page.tsx:195
msgid "Where can I get support?"
msgstr "Where can I get support?"
#: apps/marketing/src/app/(marketing)/pricing/page.tsx:177
#: apps/marketing/src/app/(marketing)/pricing/page.tsx:181
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:119
#: apps/marketing/src/app/(marketing)/pricing/page.tsx:123
msgid "Why should I use your hosting service?"
msgstr "Why should I use your hosting service?"
@@ -581,11 +601,11 @@ msgstr "Why should I use your hosting service?"
msgid "Yearly"
msgstr "Yearly"
#: apps/marketing/src/app/(marketing)/pricing/page.tsx:167
#: apps/marketing/src/app/(marketing)/pricing/page.tsx:171
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:93
#: apps/marketing/src/app/(marketing)/pricing/page.tsx:97
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."
@@ -233,10 +233,6 @@ export const ZDocumentAuditLogEventDocumentFieldInsertedSchema = z.object({
// Organised into union to allow us to extend each field if required.
field: z.union([
z.object({
type: z.literal(FieldType.INITIALS),
data: z.string(),
}),
z.object({
type: z.literal(FieldType.EMAIL),
data: z.string(),
@@ -1,16 +0,0 @@
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;
}
};
+481
View File
@@ -0,0 +1,481 @@
import type { ColumnType } from 'kysely';
export type Generated<T> = T extends ColumnType<infer S, infer I, infer U>
? ColumnType<S, I | undefined, U>
: ColumnType<T, T | undefined, T>;
export type Timestamp = ColumnType<Date, Date | string, Date | string>;
export const IdentityProvider = {
DOCUMENSO: 'DOCUMENSO',
GOOGLE: 'GOOGLE',
OIDC: 'OIDC',
} as const;
export type IdentityProvider = (typeof IdentityProvider)[keyof typeof IdentityProvider];
export const Role = {
ADMIN: 'ADMIN',
USER: 'USER',
} as const;
export type Role = (typeof Role)[keyof typeof Role];
export const UserSecurityAuditLogType = {
ACCOUNT_PROFILE_UPDATE: 'ACCOUNT_PROFILE_UPDATE',
ACCOUNT_SSO_LINK: 'ACCOUNT_SSO_LINK',
AUTH_2FA_DISABLE: 'AUTH_2FA_DISABLE',
AUTH_2FA_ENABLE: 'AUTH_2FA_ENABLE',
PASSKEY_CREATED: 'PASSKEY_CREATED',
PASSKEY_DELETED: 'PASSKEY_DELETED',
PASSKEY_UPDATED: 'PASSKEY_UPDATED',
PASSWORD_RESET: 'PASSWORD_RESET',
PASSWORD_UPDATE: 'PASSWORD_UPDATE',
SIGN_OUT: 'SIGN_OUT',
SIGN_IN: 'SIGN_IN',
SIGN_IN_FAIL: 'SIGN_IN_FAIL',
SIGN_IN_2FA_FAIL: 'SIGN_IN_2FA_FAIL',
SIGN_IN_PASSKEY_FAIL: 'SIGN_IN_PASSKEY_FAIL',
} as const;
export type UserSecurityAuditLogType =
(typeof UserSecurityAuditLogType)[keyof typeof UserSecurityAuditLogType];
export const WebhookTriggerEvents = {
DOCUMENT_CREATED: 'DOCUMENT_CREATED',
DOCUMENT_SENT: 'DOCUMENT_SENT',
DOCUMENT_OPENED: 'DOCUMENT_OPENED',
DOCUMENT_SIGNED: 'DOCUMENT_SIGNED',
DOCUMENT_COMPLETED: 'DOCUMENT_COMPLETED',
} as const;
export type WebhookTriggerEvents = (typeof WebhookTriggerEvents)[keyof typeof WebhookTriggerEvents];
export const WebhookCallStatus = {
SUCCESS: 'SUCCESS',
FAILED: 'FAILED',
} as const;
export type WebhookCallStatus = (typeof WebhookCallStatus)[keyof typeof WebhookCallStatus];
export const ApiTokenAlgorithm = {
SHA512: 'SHA512',
} as const;
export type ApiTokenAlgorithm = (typeof ApiTokenAlgorithm)[keyof typeof ApiTokenAlgorithm];
export const SubscriptionStatus = {
ACTIVE: 'ACTIVE',
PAST_DUE: 'PAST_DUE',
INACTIVE: 'INACTIVE',
} as const;
export type SubscriptionStatus = (typeof SubscriptionStatus)[keyof typeof SubscriptionStatus];
export const DocumentStatus = {
DRAFT: 'DRAFT',
PENDING: 'PENDING',
COMPLETED: 'COMPLETED',
} as const;
export type DocumentStatus = (typeof DocumentStatus)[keyof typeof DocumentStatus];
export const DocumentSource = {
DOCUMENT: 'DOCUMENT',
TEMPLATE: 'TEMPLATE',
TEMPLATE_DIRECT_LINK: 'TEMPLATE_DIRECT_LINK',
} as const;
export type DocumentSource = (typeof DocumentSource)[keyof typeof DocumentSource];
export const DocumentDataType = {
S3_PATH: 'S3_PATH',
BYTES: 'BYTES',
BYTES_64: 'BYTES_64',
} as const;
export type DocumentDataType = (typeof DocumentDataType)[keyof typeof DocumentDataType];
export const ReadStatus = {
NOT_OPENED: 'NOT_OPENED',
OPENED: 'OPENED',
} as const;
export type ReadStatus = (typeof ReadStatus)[keyof typeof ReadStatus];
export const SendStatus = {
NOT_SENT: 'NOT_SENT',
SENT: 'SENT',
} as const;
export type SendStatus = (typeof SendStatus)[keyof typeof SendStatus];
export const SigningStatus = {
NOT_SIGNED: 'NOT_SIGNED',
SIGNED: 'SIGNED',
} as const;
export type SigningStatus = (typeof SigningStatus)[keyof typeof SigningStatus];
export const RecipientRole = {
CC: 'CC',
SIGNER: 'SIGNER',
VIEWER: 'VIEWER',
APPROVER: 'APPROVER',
} as const;
export type RecipientRole = (typeof RecipientRole)[keyof typeof RecipientRole];
export const FieldType = {
SIGNATURE: 'SIGNATURE',
FREE_SIGNATURE: 'FREE_SIGNATURE',
NAME: 'NAME',
EMAIL: 'EMAIL',
DATE: 'DATE',
TEXT: 'TEXT',
NUMBER: 'NUMBER',
RADIO: 'RADIO',
CHECKBOX: 'CHECKBOX',
DROPDOWN: 'DROPDOWN',
} as const;
export type FieldType = (typeof FieldType)[keyof typeof FieldType];
export const TeamMemberRole = {
ADMIN: 'ADMIN',
MANAGER: 'MANAGER',
MEMBER: 'MEMBER',
} as const;
export type TeamMemberRole = (typeof TeamMemberRole)[keyof typeof TeamMemberRole];
export const TeamMemberInviteStatus = {
ACCEPTED: 'ACCEPTED',
PENDING: 'PENDING',
} as const;
export type TeamMemberInviteStatus =
(typeof TeamMemberInviteStatus)[keyof typeof TeamMemberInviteStatus];
export const TemplateType = {
PUBLIC: 'PUBLIC',
PRIVATE: 'PRIVATE',
} as const;
export type TemplateType = (typeof TemplateType)[keyof typeof TemplateType];
export type Account = {
id: string;
userId: number;
type: string;
provider: string;
providerAccountId: string;
refresh_token: string | null;
access_token: string | null;
expires_at: number | null;
created_at: number | null;
ext_expires_in: number | null;
token_type: string | null;
scope: string | null;
id_token: string | null;
session_state: string | null;
};
export type AnonymousVerificationToken = {
id: string;
token: string;
expiresAt: Timestamp;
createdAt: Generated<Timestamp>;
};
export type ApiToken = {
id: Generated<number>;
name: string;
token: string;
algorithm: Generated<ApiTokenAlgorithm>;
expires: Timestamp | null;
createdAt: Generated<Timestamp>;
userId: number | null;
teamId: number | null;
};
export type Document = {
id: Generated<number>;
userId: number;
authOptions: unknown | null;
formValues: unknown | null;
title: string;
status: Generated<DocumentStatus>;
documentDataId: string;
createdAt: Generated<Timestamp>;
updatedAt: Generated<Timestamp>;
completedAt: Timestamp | null;
deletedAt: Timestamp | null;
teamId: number | null;
templateId: number | null;
source: DocumentSource;
};
export type DocumentAuditLog = {
id: string;
documentId: number;
createdAt: Generated<Timestamp>;
type: string;
data: unknown;
name: string | null;
email: string | null;
userId: number | null;
userAgent: string | null;
ipAddress: string | null;
};
export type DocumentData = {
id: string;
type: DocumentDataType;
data: string;
initialData: string;
};
export type DocumentMeta = {
id: string;
subject: string | null;
message: string | null;
timezone: Generated<string | null>;
password: string | null;
dateFormat: Generated<string | null>;
documentId: number;
redirectUrl: string | null;
};
export type DocumentShareLink = {
id: Generated<number>;
email: string;
slug: string;
documentId: number;
createdAt: Generated<Timestamp>;
updatedAt: Timestamp;
};
export type Field = {
id: Generated<number>;
secondaryId: string;
documentId: number | null;
templateId: number | null;
recipientId: number;
type: FieldType;
page: number;
positionX: Generated<string>;
positionY: Generated<string>;
width: Generated<string>;
height: Generated<string>;
customText: string;
inserted: boolean;
fieldMeta: unknown | null;
};
export type Passkey = {
id: string;
userId: number;
name: string;
createdAt: Generated<Timestamp>;
updatedAt: Generated<Timestamp>;
lastUsedAt: Timestamp | null;
credentialId: Buffer;
credentialPublicKey: Buffer;
counter: string;
credentialDeviceType: string;
credentialBackedUp: boolean;
transports: string[];
};
export type PasswordResetToken = {
id: Generated<number>;
token: string;
createdAt: Generated<Timestamp>;
expiry: Timestamp;
userId: number;
};
export type Recipient = {
id: Generated<number>;
documentId: number | null;
templateId: number | null;
email: string;
name: Generated<string>;
token: string;
documentDeletedAt: Timestamp | null;
expired: Timestamp | null;
signedAt: Timestamp | null;
authOptions: unknown | null;
role: Generated<RecipientRole>;
readStatus: Generated<ReadStatus>;
signingStatus: Generated<SigningStatus>;
sendStatus: Generated<SendStatus>;
};
export type Session = {
id: string;
sessionToken: string;
userId: number;
expires: Timestamp;
};
export type Signature = {
id: Generated<number>;
created: Generated<Timestamp>;
recipientId: number;
fieldId: number;
signatureImageAsBase64: string | null;
typedSignature: string | null;
};
export type SiteSettings = {
id: string;
enabled: Generated<boolean>;
data: unknown;
lastModifiedByUserId: number | null;
lastModifiedAt: Generated<Timestamp>;
};
export type Subscription = {
id: Generated<number>;
status: Generated<SubscriptionStatus>;
planId: string;
priceId: string;
periodEnd: Timestamp | null;
userId: number | null;
teamId: number | null;
createdAt: Generated<Timestamp>;
updatedAt: Timestamp;
cancelAtPeriodEnd: Generated<boolean>;
};
export type Team = {
id: Generated<number>;
name: string;
url: string;
createdAt: Generated<Timestamp>;
customerId: string | null;
ownerUserId: number;
};
export type TeamEmail = {
teamId: number;
createdAt: Generated<Timestamp>;
name: string;
email: string;
};
export type TeamEmailVerification = {
teamId: number;
name: string;
email: string;
token: string;
expiresAt: Timestamp;
createdAt: Generated<Timestamp>;
};
export type TeamMember = {
id: Generated<number>;
teamId: number;
createdAt: Generated<Timestamp>;
role: TeamMemberRole;
userId: number;
};
export type TeamMemberInvite = {
id: Generated<number>;
teamId: number;
createdAt: Generated<Timestamp>;
email: string;
status: Generated<TeamMemberInviteStatus>;
role: TeamMemberRole;
token: string;
};
export type TeamPending = {
id: Generated<number>;
name: string;
url: string;
createdAt: Generated<Timestamp>;
customerId: string;
ownerUserId: number;
};
export type TeamTransferVerification = {
teamId: number;
userId: number;
name: string;
email: string;
token: string;
expiresAt: Timestamp;
createdAt: Generated<Timestamp>;
clearPaymentMethods: Generated<boolean>;
};
export type Template = {
id: Generated<number>;
type: Generated<TemplateType>;
title: string;
userId: number;
teamId: number | null;
authOptions: unknown | null;
templateDocumentDataId: string;
createdAt: Generated<Timestamp>;
updatedAt: Generated<Timestamp>;
};
export type TemplateDirectLink = {
id: string;
templateId: number;
token: string;
createdAt: Generated<Timestamp>;
enabled: boolean;
directTemplateRecipientId: number;
};
export type TemplateMeta = {
id: string;
subject: string | null;
message: string | null;
timezone: Generated<string | null>;
password: string | null;
dateFormat: Generated<string | null>;
templateId: number;
redirectUrl: string | null;
};
export type User = {
id: Generated<number>;
name: string | null;
customerId: string | null;
email: string;
emailVerified: Timestamp | null;
password: string | null;
source: string | null;
signature: string | null;
createdAt: Generated<Timestamp>;
updatedAt: Generated<Timestamp>;
lastSignedIn: Generated<Timestamp>;
roles: Generated<Role[]>;
identityProvider: Generated<IdentityProvider>;
twoFactorSecret: string | null;
twoFactorEnabled: Generated<boolean>;
twoFactorBackupCodes: string | null;
url: string | null;
};
export type UserProfile = {
id: number;
bio: string | null;
};
export type UserSecurityAuditLog = {
id: Generated<number>;
userId: number;
createdAt: Generated<Timestamp>;
type: UserSecurityAuditLogType;
userAgent: string | null;
ipAddress: string | null;
};
export type VerificationToken = {
id: Generated<number>;
secondaryId: string;
identifier: string;
token: string;
expires: Timestamp;
createdAt: Generated<Timestamp>;
userId: number;
};
export type Webhook = {
id: string;
webhookUrl: string;
eventTriggers: WebhookTriggerEvents[];
secret: string | null;
enabled: Generated<boolean>;
createdAt: Generated<Timestamp>;
updatedAt: Generated<Timestamp>;
userId: number;
teamId: number | null;
};
export type WebhookCall = {
id: string;
status: WebhookCallStatus;
url: string;
event: WebhookTriggerEvents;
requestBody: unknown;
responseCode: number;
responseHeaders: unknown | null;
responseBody: unknown | null;
createdAt: Generated<Timestamp>;
webhookId: string;
};
export type DB = {
Account: Account;
AnonymousVerificationToken: AnonymousVerificationToken;
ApiToken: ApiToken;
Document: Document;
DocumentAuditLog: DocumentAuditLog;
DocumentData: DocumentData;
DocumentMeta: DocumentMeta;
DocumentShareLink: DocumentShareLink;
Field: Field;
Passkey: Passkey;
PasswordResetToken: PasswordResetToken;
Recipient: Recipient;
Session: Session;
Signature: Signature;
SiteSettings: SiteSettings;
Subscription: Subscription;
Team: Team;
TeamEmail: TeamEmail;
TeamEmailVerification: TeamEmailVerification;
TeamMember: TeamMember;
TeamMemberInvite: TeamMemberInvite;
TeamPending: TeamPending;
TeamTransferVerification: TeamTransferVerification;
Template: Template;
TemplateDirectLink: TemplateDirectLink;
TemplateMeta: TemplateMeta;
User: User;
UserProfile: UserProfile;
UserSecurityAuditLog: UserSecurityAuditLog;
VerificationToken: VerificationToken;
Webhook: Webhook;
WebhookCall: WebhookCall;
};
@@ -1,2 +0,0 @@
-- AlterEnum
ALTER TYPE "FieldType" ADD VALUE 'INITIALS';
-1
View File
@@ -409,7 +409,6 @@ model Recipient {
enum FieldType {
SIGNATURE
FREE_SIGNATURE
INITIALS
NAME
EMAIL
DATE
+2 -10
View File
@@ -29,7 +29,6 @@ import {
ZCreateDocumentMutationSchema,
ZDeleteDraftDocumentMutationSchema as ZDeleteDocumentMutationSchema,
ZDownloadAuditLogsMutationSchema,
ZDownloadCertificateMutationSchema,
ZFindDocumentAuditLogsQuerySchema,
ZGetDocumentByIdQuerySchema,
ZGetDocumentByTokenQuerySchema,
@@ -412,14 +411,7 @@ export const documentRouter = router({
id: documentId,
userId: ctx.user.id,
teamId,
}).catch(() => null);
if (!document || (teamId && document.teamId !== teamId)) {
throw new TRPCError({
code: 'FORBIDDEN',
message: 'You do not have access to this document.',
});
}
});
const encrypted = encryptSecondaryData({
data: document.id.toString(),
@@ -441,7 +433,7 @@ export const documentRouter = router({
}),
downloadCertificate: authenticatedProcedure
.input(ZDownloadCertificateMutationSchema)
.input(ZDownloadAuditLogsMutationSchema)
.mutation(async ({ input, ctx }) => {
try {
const { documentId, teamId } = input;
+5 -12
View File
@@ -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,9 +65,8 @@ export const ZSetSettingsForDocumentMutationSchema = z.object({
redirectUrl: z
.string()
.optional()
.refine((value) => value === undefined || value === '' || isValidRedirectUrl(value), {
message:
'Please enter a valid URL, make sure you include http:// or https:// part of the url.',
.refine((value) => value === undefined || value === '' || URL_REGEX.test(value), {
message: 'Please enter a valid URL',
}),
}),
});
@@ -132,9 +131,8 @@ export const ZSendDocumentMutationSchema = z.object({
redirectUrl: z
.string()
.optional()
.refine((value) => value === undefined || value === '' || isValidRedirectUrl(value), {
message:
'Please enter a valid URL, make sure you include http:// or https:// part of the url.',
.refine((value) => value === undefined || value === '' || URL_REGEX.test(value), {
message: 'Please enter a valid URL',
}),
}),
});
@@ -172,11 +170,6 @@ 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,7 +66,6 @@ export const templateRouter = router({
directRecipientName,
directRecipientEmail,
directTemplateToken,
directTemplateExternalId,
signedFieldValues,
templateUpdatedAt,
} = input;
@@ -77,7 +76,6 @@ 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,7 +20,6 @@ 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(),
});
@@ -97,9 +96,8 @@ export const ZUpdateTemplateSettingsMutationSchema = z.object({
redirectUrl: z
.string()
.optional()
.refine((value) => value === undefined || value === '' || isValidRedirectUrl(value), {
message:
'Please enter a valid URL, make sure you include http:// or https:// part of the url.',
.refine((value) => value === undefined || value === '' || URL_REGEX.test(value), {
message: 'Please enter a valid URL',
}),
})
.optional(),
-3
View File
@@ -9,9 +9,6 @@ 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;
@@ -10,7 +10,6 @@ import {
CheckSquare,
ChevronDown,
ChevronsUpDown,
Contact,
Disc,
Hash,
Info,
@@ -458,11 +457,10 @@ export const AddFieldsFormPartial = ({
{selectedField && (
<div
className={cn(
'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',
'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 dark:bg-black/20': !isFieldWithinBounds,
'dark:text-black/60': isFieldWithinBounds,
'-rotate-6 scale-90 opacity-50': !isFieldWithinBounds,
},
)}
style={{
@@ -651,32 +649,6 @@ export const AddFieldsFormPartial = ({
</Card>
</button>
<button
type="button"
className="group h-full w-full"
onClick={() => setSelectedField(FieldType.INITIALS)}
onMouseDown={() => setSelectedField(FieldType.INITIALS)}
data-selected={selectedField === FieldType.INITIALS ? true : undefined}
>
<Card
className={cn(
'flex h-full w-full cursor-pointer items-center justify-center group-disabled:opacity-50',
// selectedSignerStyles.borderClass,
)}
>
<CardContent className="flex flex-col items-center justify-center px-6 py-4">
<p
className={cn(
'text-muted-foreground group-data-[selected]:text-foreground flex items-center justify-center gap-x-1.5 text-sm font-normal',
)}
>
<Contact className="h-4 w-4" />
Initials
</p>
</CardContent>
</Card>
</button>
<button
type="button"
className="group h-full w-full"
@@ -690,7 +662,7 @@ export const AddFieldsFormPartial = ({
// selectedSignerStyles.borderClass,
)}
>
<CardContent className="flex flex-col items-center justify-center px-6 py-4">
<CardContent className="p-4">
<p
className={cn(
'text-muted-foreground group-data-[selected]:text-foreground flex items-center justify-center gap-x-1.5 text-sm font-normal',
@@ -82,12 +82,8 @@ export const AddSettingsFormPartial = ({
globalAccessAuth: documentAuthOption?.globalAccessAuth || undefined,
globalActionAuth: documentAuthOption?.globalActionAuth || undefined,
meta: {
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,
timezone: document.documentMeta?.timezone ?? DEFAULT_DOCUMENT_TIME_ZONE,
dateFormat: document.documentMeta?.dateFormat ?? DEFAULT_DOCUMENT_DATE_FORMAT,
redirectUrl: document.documentMeta?.redirectUrl ?? '',
},
},
@@ -102,20 +98,10 @@ 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 &&
!document.documentMeta?.timezone
) {
if (!form.formState.touchedFields.meta?.timezone && !documentHasBeenSent) {
form.setValue('meta.timezone', Intl.DateTimeFormat().resolvedOptions().timeZone);
}
}, [
documentHasBeenSent,
form,
form.setValue,
form.formState.touchedFields.meta?.timezone,
document.documentMeta?.timezone,
]);
}, [documentHasBeenSent, form, form.setValue, form.formState.touchedFields.meta?.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,9 +34,8 @@ export const ZAddSettingsFormSchema = z.object({
redirectUrl: z
.string()
.optional()
.refine((value) => value === undefined || value === '' || isValidRedirectUrl(value), {
message:
'Please enter a valid URL, make sure you include http:// or https:// part of the url.',
.refine((value) => value === undefined || value === '' || URL_REGEX.test(value), {
message: 'Please enter a valid URL',
}),
}),
});
@@ -1,14 +1,4 @@
import {
CalendarDays,
CheckSquare,
ChevronDown,
Contact,
Disc,
Hash,
Mail,
Type,
User,
} from 'lucide-react';
import { CalendarDays, CheckSquare, ChevronDown, Disc, Hash, Mail, Type, User } from 'lucide-react';
import type { TFieldMetaSchema as FieldMetaType } from '@documenso/lib/types/field-meta';
import { FieldType } from '@documenso/prisma/client';
@@ -23,7 +13,6 @@ type FieldIconProps = {
};
const fieldIcons = {
[FieldType.INITIALS]: { icon: Contact, label: 'Initials' },
[FieldType.EMAIL]: { icon: Mail, label: 'Email' },
[FieldType.NAME]: { icon: User, label: 'Name' },
[FieldType.DATE]: { icon: CalendarDays, label: 'Date' },
@@ -57,11 +46,9 @@ export const FieldIcon = ({
if (fieldMeta && (type === 'TEXT' || type === 'NUMBER')) {
if (type === 'TEXT' && 'text' in fieldMeta && fieldMeta.text && !fieldMeta.label) {
label =
fieldMeta.text.length > 10 ? fieldMeta.text.substring(0, 10) + '...' : fieldMeta.text;
label = fieldMeta.text.length > 10 ? fieldMeta.text.substring(0, 10) + '...' : fieldMeta.text;
} else if (fieldMeta.label) {
label =
fieldMeta.label.length > 10 ? fieldMeta.label.substring(0, 10) + '...' : fieldMeta.label;
label = fieldMeta.label.length > 10 ? fieldMeta.label.substring(0, 10) + '...' : fieldMeta.label;
} else {
label = fieldIcons[type]?.label;
}
@@ -71,7 +58,7 @@ export const FieldIcon = ({
return (
<div className="text-field-card-foreground flex items-center justify-center gap-x-1.5 text-sm">
<Icon className="h-4 w-4" /> {label}
<Icon className='h-4 w-4' /> {label}
</div>
);
}
@@ -46,7 +46,6 @@ export type TDocumentFlowFormSchema = z.infer<typeof ZDocumentFlowFormSchema>;
export const FRIENDLY_FIELD_TYPE: Record<FieldType, string> = {
[FieldType.SIGNATURE]: 'Signature',
[FieldType.FREE_SIGNATURE]: 'Free Signature',
[FieldType.INITIALS]: 'Initials',
[FieldType.TEXT]: 'Text',
[FieldType.DATE]: 'Date',
[FieldType.EMAIL]: 'Email',
@@ -9,7 +9,6 @@ import {
CheckSquare,
ChevronDown,
ChevronsUpDown,
Contact,
Disc,
Hash,
Mail,
@@ -384,11 +383,10 @@ export const AddTemplateFieldsFormPartial = ({
{selectedField && (
<div
className={cn(
'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',
'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 dark:bg-black/20': !isFieldWithinBounds,
'dark:text-black/60': isFieldWithinBounds,
'-rotate-6 scale-90 opacity-50': !isFieldWithinBounds,
},
)}
style={{
@@ -548,32 +546,6 @@ export const AddTemplateFieldsFormPartial = ({
</Card>
</button>
<button
type="button"
className="group h-full w-full"
onClick={() => setSelectedField(FieldType.INITIALS)}
onMouseDown={() => setSelectedField(FieldType.INITIALS)}
data-selected={selectedField === FieldType.INITIALS ? true : undefined}
>
<Card
className={cn(
'flex h-full w-full cursor-pointer items-center justify-center group-disabled:opacity-50',
// selectedSignerStyles.borderClass,
)}
>
<CardContent className="flex flex-col items-center justify-center px-6 py-4">
<p
className={cn(
'text-muted-foreground group-data-[selected]:text-foreground flex items-center justify-center gap-x-1.5 text-sm font-normal',
)}
>
<Contact className="h-4 w-4" />
Initials
</p>
</CardContent>
</Card>
</button>
<button
type="button"
className="group h-full w-full"
@@ -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,9 +27,8 @@ export const ZAddTemplateSettingsFormSchema = z.object({
redirectUrl: z
.string()
.optional()
.refine((value) => value === undefined || value === '' || isValidRedirectUrl(value), {
message:
'Please enter a valid URL, make sure you include http:// or https:// part of the url.',
.refine((value) => value === undefined || value === '' || URL_REGEX.test(value), {
message: 'Please enter a valid URL',
}),
}),
});
+3 -59
View File
@@ -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 && npx turbo run start --filter=@documenso/web
healthCheckPath: /api/health
startCommand: npx prisma migrate deploy --schema packages/prisma/schema.prisma && npm run start
healthCheckPath: /api/trpc/health
envVars:
# Node Version
@@ -98,62 +98,6 @@ 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
+20 -9
View File
@@ -2,12 +2,20 @@
"$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
@@ -23,7 +31,9 @@
"persistent": true
},
"start": {
"dependsOn": ["^build"],
"dependsOn": [
"^build"
],
"cache": false,
"persistent": true
},
@@ -31,14 +41,18 @@
"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",
@@ -69,9 +83,6 @@
"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",