Compare commits

...

9 Commits

Author SHA1 Message Date
Mythie
af042a62cd fix: disable cancel button when there is no window history 2023-09-28 15:45:22 +10:00
Lucas Smith
df8bdda718 Merge pull request #450 from documenso/feat/resend-transport
feat: add resend mail transport
2023-09-28 14:07:18 +10:00
David Nguyen
873f99ae86 fix: resolve document title inconsistency (#452) 2023-09-28 13:56:22 +10:00
Lucas Smith
17fe135027 Merge pull request #451 from documenso/fix/445-signer-name-not-persisting
fix: do not overwrite new names or emails for signers
2023-09-28 13:07:26 +10:00
David Nguyen
2eed0ae063 feat: add posthog reverse proxy (#449) 2023-09-28 12:56:53 +10:00
Mythie
9bdff9a61f feat: add resend mail transport 2023-09-28 12:27:04 +10:00
Timur Ercan
cdb71c3a62 chore: greetings 2023-09-27 12:30:11 +02:00
Timur Ercan
39d7b3ca58 Merge branch 'feat/refresh' of https://github.com/documenso/documenso into feat/refresh 2023-09-27 12:19:14 +02:00
Timur Ercan
748c3636d5 chore: sync shop article and add missing and updated assets 2023-09-27 12:15:44 +02:00
22 changed files with 230 additions and 21 deletions

View File

@@ -50,7 +50,9 @@ NEXT_PRIVATE_SMTP_SECURE=
NEXT_PRIVATE_SMTP_FROM_NAME="No Reply @ Documenso"
# REQUIRED: Defines the email address to use as the from address.
NEXT_PRIVATE_SMTP_FROM_ADDRESS="noreply@documenso.com"
# OPTIONAL: The API key to use for the MailChannels proxy endpoint.
# OPTIONAL: The API key to use for Resend.com
NEXT_PRIVATE_RESEND_API_KEY=
# OPTIONAL: The API key to use for MailChannels.
NEXT_PRIVATE_MAILCHANNELS_API_KEY=
# OPTIONAL: The endpoint to use for the MailChannels API if using a proxy.
NEXT_PRIVATE_MAILCHANNELS_ENDPOINT=

View File

@@ -51,4 +51,7 @@ As Documenso 1.0 just hit the staging environment, we're calling a MALFUNCTION M
We don't have a specific end date for Malfunction Mania. We plan to move the staging version into the production environment by the end of the month once we're happy with the results. Bug reports and fixes are, of course, always welcome going forward.
Best from Hamburg
Timur
**[Follow Documenso on Twitter / X](https://documen.so/tw) and [join the Discord server](https://documen.so/discord) to get the latest about Malfunction Mania.**

View File

@@ -1,12 +1,12 @@
---
title: Shop and Limited Edition "Mania" Shirt
title: Merch Mania
description: Happy Launch Week Day 3. The limited edition "Malfunction Mania" shirt is here. Grab it, while you can.
authorName: 'Timur Ercan'
authorImage: '/blog/blog-author-timur.jpeg'
authorRole: 'Co-Founder'
date: 2023-09-27
tags:
- Testing
- Merch
- Rewrite
- Bounties
---
@@ -24,13 +24,13 @@ tags:
</figcaption>
</figure>
> TLDR; We have a fancy limited edition shirt. Contribute to Malfunction Mania to get one.
> TLDR; We have a fancy limited edition shirt. Contribute to [Malfunction Mania](https://documenso.com/blog/malfunction-mania) to get one.
We kicked off [Malfunction Mania](https://documenso.com/blog/malfunction-mania) yesterday, and the first [Issues](https://github.com/documenso/documenso/issues) are coming in. As mentioned, there will be dollar bounties, but we also wanted to celebrate entering the final stage of version 1.0 with something special. This is why we created the limited edition shirt for Malfunction Mania. It will only be available during the runtime of Malfunction Mania. We have yet to set an exact end date, the next event in October, however, is looming, ready to end MM.
We kicked off [Malfunction Mania](https://documenso.com/blog/malfunction-mania) yesterday, and the first [issues](https://github.com/documenso/documenso/issues) are coming in. As mentioned, there will be dollar bounties, but we also wanted to celebrate entering the final stage of version 1.0 with something special. This is why we created this limited edition shirt. It will only be available during the runtime of Malfunction Mania. We have yet to set an exact end date, the next event in October, however, is looming, ready to end MM.
## Documenso Merch Shop
The shirt will be available in our [merch shop](https://documen.so/shop) via a unique discount code. While the shirt will be gone after Malfunction Mania, the shop is here to stay and provide a well-deserved reward for great community members and contributors.
The shirt will be available in our [merch shop](https://documen.so/shop) via a unique discount code. While the shirt will be gone after Malfunction Mania, the shop is here to stay and provide a well-deserved reward for great community members and contributors. All items can be earned by contrinuting to Documenso.
<figure>
<MdxNextImage
@@ -45,7 +45,7 @@ The shirt will be available in our [merch shop](https://documen.so/shop) via a u
</figcaption>
</figure>
## How to get the shirt
## How earn the shirt
If you have been following us, you know we are not big on formalities but highly value rewarding merit. That being said, any worthwhile contribution has a chance to get one. To inspire, here are a few ideas on how to contribute to securing one:
@@ -56,7 +56,9 @@ If you have been following us, you know we are not big on formalities but highly
- Engage in discussion about the current version and its choices
- Raise awareness for Malfunction Mania and try out the [version currently in staging](https://documen.so/staging)
- Review the version with a video, stream, or screenshots and post about it
- Review existing or create missing documenso
- ...
- Review existing or create missing documentation
**[Follow Documenso on Twitter / X](https://documen.so/tw) and [join the Discord server](https://documen.so/discord) to get the latest about Malfunction Mania.**
Best from Hamburg
Timur
**[Follow Documenso on Twitter / X](https://documen.so/tw) and [join the Discord server](https://documen.so/discord) to get the latest updates about Malfunction Mania.**

View File

@@ -56,6 +56,14 @@ const config = {
},
];
},
async rewrites() {
return [
{
source: '/ingest/:path*',
destination: 'https://eu.posthog.com/:path*',
},
];
},
};
module.exports = withContentlayer(config);

Binary file not shown.

After

Width:  |  Height:  |  Size: 1007 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 149 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 956 KiB

After

Width:  |  Height:  |  Size: 258 KiB

View File

@@ -29,6 +29,14 @@ const config = {
transform: 'lucide-react/dist/esm/icons/{{ kebabCase member }}',
},
},
async rewrites() {
return [
{
source: '/ingest/:path*',
destination: 'https://eu.posthog.com/:path*',
},
];
},
};
module.exports = config;

View File

@@ -10,7 +10,7 @@ export default function Loading() {
Documents
</Link>
<h1 className="mt-4 max-w-xs grow-0 truncate text-2xl font-semibold md:text-3xl">
<h1 className="mt-4 grow-0 truncate text-2xl font-semibold md:text-3xl">
Loading Document...
</h1>
<div className="mt-8 grid h-[80vh] max-h-[60rem] w-full grid-cols-12 gap-x-8">

View File

@@ -65,10 +65,7 @@ export default async function DocumentPage({ params }: DocumentPageProps) {
Documents
</Link>
<h1
className="mt-4 max-w-xs truncate text-2xl font-semibold md:text-3xl"
title={document.title}
>
<h1 className="mt-4 truncate text-2xl font-semibold md:text-3xl" title={document.title}>
{document.title}
</h1>

View File

@@ -10,7 +10,7 @@ export default function DocumentSentPage() {
Documents
</Link>
<h1 className="mt-4 max-w-xs grow-0 truncate text-2xl font-semibold md:text-3xl">
<h1 className="mt-4 grow-0 truncate text-2xl font-semibold md:text-3xl">
Loading Document...
</h1>
</div>

View File

@@ -120,6 +120,7 @@ export const SigningForm = ({ document, recipient, fields }: SigningFormProps) =
className="dark:bg-muted dark:hover:bg-muted/80 w-full bg-black/5 hover:bg-black/10"
variant="secondary"
size="lg"
disabled={typeof window !== 'undefined' && window.history.length <= 1}
onClick={() => router.back()}
>
Cancel

26
package-lock.json generated
View File

@@ -16958,6 +16958,29 @@
"node": ">=0.10.5"
}
},
"node_modules/resend": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/resend/-/resend-1.1.0.tgz",
"integrity": "sha512-it8TIDVT+/gAiJsUlv2tdHuvzwCCv4Zwu+udDqIm/dIuByQwe68TtFDcPccxqpSVVrNCBxxXLzsdT1tsV+P3GA==",
"dependencies": {
"@react-email/render": "0.0.7",
"type-fest": "3.13.0"
},
"engines": {
"node": ">=18"
}
},
"node_modules/resend/node_modules/type-fest": {
"version": "3.13.0",
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-3.13.0.tgz",
"integrity": "sha512-Gur3yQGM9qiLNs0KPP7LPgeRbio2QTt4xXouobMCarR0/wyW3F+F/+OWwshg3NG0Adon7uQfSZBpB46NfhoF1A==",
"engines": {
"node": ">=14.16"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/resolve": {
"version": "1.22.2",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.2.tgz",
@@ -19773,7 +19796,8 @@
"dependencies": {
"@react-email/components": "^0.0.7",
"nodemailer": "^6.9.3",
"react-email": "^1.9.4"
"react-email": "^1.9.4",
"resend": "^1.1.0"
},
"devDependencies": {
"@documenso/tailwind-config": "*",

View File

@@ -1,6 +1,7 @@
import { createTransport } from 'nodemailer';
import { MailChannelsTransport } from './transports/mailchannels';
import { ResendTransport } from './transports/resend';
const getTransport = () => {
const transport = process.env.NEXT_PRIVATE_SMTP_TRANSPORT ?? 'smtp-auth';
@@ -14,6 +15,14 @@ const getTransport = () => {
);
}
if (transport === 'resend') {
return createTransport(
ResendTransport.makeTransport({
apiKey: process.env.NEXT_PRIVATE_RESEND_API_KEY || '',
}),
);
}
if (transport === 'smtp-api') {
if (!process.env.NEXT_PRIVATE_SMTP_HOST || !process.env.NEXT_PRIVATE_SMTP_APIKEY) {
throw new Error(

View File

@@ -19,7 +19,8 @@
"dependencies": {
"@react-email/components": "^0.0.7",
"nodemailer": "^6.9.3",
"react-email": "^1.9.4"
"react-email": "^1.9.4",
"resend": "^1.1.0"
},
"devDependencies": {
"@documenso/tailwind-config": "*",

View File

@@ -0,0 +1,145 @@
import { type SentMessageInfo, type Transport } from 'nodemailer';
import type Mail from 'nodemailer/lib/mailer';
import type MailMessage from 'nodemailer/lib/mailer/mail-message';
import { Resend } from 'resend';
const VERSION = '1.0.0';
type ResendTransportOptions = {
apiKey: string;
};
type ResendResponseError = {
statusCode: number;
name: string;
message: string;
};
const isResendResponseError = (error: unknown): error is ResendResponseError => {
// We could use Zod here, but it's not worth the extra bundle size
return (
typeof error === 'object' &&
error !== null &&
'statusCode' in error &&
typeof error.statusCode === 'number' &&
'name' in error &&
typeof error.name === 'string' &&
'message' in error &&
typeof error.message === 'string'
);
};
/**
* Transport for sending email via the Resend SDK.
*/
export class ResendTransport implements Transport<SentMessageInfo> {
public name = 'ResendMailTransport';
public version = VERSION;
private _client: Resend;
private _options: ResendTransportOptions;
public static makeTransport(options: Partial<ResendTransportOptions>) {
return new ResendTransport(options);
}
constructor(options: Partial<ResendTransportOptions>) {
const { apiKey = '' } = options;
this._options = {
apiKey,
};
this._client = new Resend(apiKey);
}
public send(mail: MailMessage, callback: (_err: Error | null, _info: SentMessageInfo) => void) {
if (!mail.data.to || !mail.data.from) {
return callback(new Error('Missing required fields "to" or "from"'), null);
}
this._client
.sendEmail({
subject: mail.data.subject ?? '',
from: this.toResendFromAddress(mail.data.from),
to: this.toResendAddresses(mail.data.to),
cc: this.toResendAddresses(mail.data.cc),
bcc: this.toResendAddresses(mail.data.bcc),
html: mail.data.html?.toString() || '',
text: mail.data.text?.toString() || '',
attachments: this.toResendAttachments(mail.data.attachments),
})
.then((response) => {
if (isResendResponseError(response)) {
throw new Error(`[${response.statusCode}]: ${response.name} ${response.message}`);
}
callback(null, response);
})
.catch((error) => {
callback(error, null);
});
}
private toResendAddresses(addresses: Mail.Options['to']) {
if (!addresses) {
return [];
}
if (typeof addresses === 'string') {
return [addresses];
}
if (Array.isArray(addresses)) {
return addresses.map((address) => {
if (typeof address === 'string') {
return address;
}
return address.address;
});
}
return [addresses.address];
}
private toResendFromAddress(address: Mail.Options['from']) {
if (!address) {
return '';
}
if (typeof address === 'string') {
return address;
}
return `${address.name} <${address.address}>`;
}
private toResendAttachments(attachments: Mail.Options['attachments']) {
if (!attachments) {
return [];
}
return attachments.map((attachment) => {
if (!attachment.filename || !attachment.content) {
throw new Error('Attachment is missing filename or content');
}
if (typeof attachment.content === 'string') {
return {
filename: attachment.filename,
content: Buffer.from(attachment.content),
};
}
if (attachment.content instanceof Buffer) {
return {
filename: attachment.filename,
content: attachment.content,
};
}
throw new Error('Attachment content must be a string or a buffer');
});
}
}

View File

@@ -1,5 +1,10 @@
{
"extends": "@documenso/tsconfig/react-library.json",
"compilerOptions": {
"types": [
"@documenso/tsconfig/process-env.d.ts",
]
},
"include": ["**/*.ts", "**/*.tsx", "**/*.d.ts", "**/*.json"],
"exclude": ["dist", "build", "node_modules"]
}

View File

@@ -1,3 +1,5 @@
import { APP_BASE_URL } from './app';
/**
* The flag name for global session recording feature flag.
*/
@@ -23,7 +25,7 @@ export const LOCAL_FEATURE_FLAGS: Record<string, boolean> = {
*/
export function extractPostHogConfig(): { key: string; host: string } | null {
const postHogKey = process.env.NEXT_PUBLIC_POSTHOG_KEY;
const postHogHost = process.env.NEXT_PUBLIC_POSTHOG_HOST;
const postHogHost = `${APP_BASE_URL}/ingest`;
if (!postHogKey || !postHogHost) {
return null;

View File

@@ -14,7 +14,6 @@ export const viewedDocument = async ({ token }: ViewedDocumentOptions) => {
});
if (!recipient) {
console.warn(`No recipient found for token ${token}`);
return;
}

View File

@@ -27,7 +27,9 @@ declare namespace NodeJS {
NEXT_PRIVATE_SIGNING_LOCAL_FILE_CONTENTS?: string;
NEXT_PRIVATE_SIGNING_LOCAL_FILE_ENCODING?: string;
NEXT_PRIVATE_SMTP_TRANSPORT?: 'mailchannels' | 'smtp-auth' | 'smtp-api';
NEXT_PRIVATE_SMTP_TRANSPORT?: 'mailchannels' | 'resend' | 'smtp-auth' | 'smtp-api';
NEXT_PRIVATE_RESEND_API_KEY?: string;
NEXT_PRIVATE_MAILCHANNELS_API_KEY?: string;
NEXT_PRIVATE_MAILCHANNELS_DKIM_DOMAIN?: string;

View File

@@ -50,6 +50,7 @@
"NEXT_PRIVATE_SIGNING_LOCAL_FILE_CONTENTS",
"NEXT_PRIVATE_SIGNING_LOCAL_FILE_ENCODING",
"NEXT_PRIVATE_SMTP_TRANSPORT",
"NEXT_PRIVATE_RESEND_API_KEY",
"NEXT_PRIVATE_MAILCHANNELS_API_KEY",
"NEXT_PRIVATE_MAILCHANNELS_ENDPOINT",
"NEXT_PRIVATE_MAILCHANNELS_DKIM_DOMAIN",