mirror of
https://github.com/documenso/documenso.git
synced 2025-11-13 08:13:56 +10:00
Merge branch 'main' into feat/document-auth
This commit is contained in:
13
README.md
13
README.md
@ -30,17 +30,8 @@
|
|||||||
<a href="CODE_OF_CONDUCT.md"><img src="https://img.shields.io/badge/Contributor%20Covenant-2.1-4baaaa.svg" alt="Contributor Covenant"></a>
|
<a href="CODE_OF_CONDUCT.md"><img src="https://img.shields.io/badge/Contributor%20Covenant-2.1-4baaaa.svg" alt="Contributor Covenant"></a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div>
|
<div align="center">
|
||||||
<img style="display: block; height: 120px; width: 24%"
|
<img src="https://github.com/documenso/documenso/assets/13398220/d96ed533-6f34-4a97-be9b-442bdb189c69" style="width: 80%;" />
|
||||||
src="https://github.com/documenso/documenso/assets/1309312/67e08c98-c153-4115-aa2d-77979bb12c94)">
|
|
||||||
<img style="display: block; height: 120px; width: 24%"
|
|
||||||
src="https://github.com/documenso/documenso/assets/1309312/040cfbae-3438-4ca3-87f2-ce52c793dcaf">
|
|
||||||
<img style="display: block; height: 120px; width: 24%"
|
|
||||||
src="https://github.com/documenso/documenso/assets/1309312/72d445be-41e5-4936-bdba-87ef8e70fa09">
|
|
||||||
<img style="display: block; height: 120px; width: 24%"
|
|
||||||
src="https://github.com/documenso/documenso/assets/1309312/d7b86c0f-a755-4476-a022-a608db2c4633">
|
|
||||||
<img style="display: block; height: 120px; width: 24%"
|
|
||||||
src=https://github.com/documenso/documenso/assets/1309312/c0f55116-ab82-433f-a266-f3fc8571d69f">
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
## About this project
|
## About this project
|
||||||
|
|||||||
@ -93,7 +93,7 @@ export const MenuSwitcher = ({ user, teams: initialTeamsData }: MenuSwitcherProp
|
|||||||
<Button
|
<Button
|
||||||
data-testid="menu-switcher"
|
data-testid="menu-switcher"
|
||||||
variant="none"
|
variant="none"
|
||||||
className="relative flex h-12 flex-row items-center px-2 py-2 ring-0 focus-visible:border-0 focus-visible:ring-0"
|
className="relative flex h-12 flex-row items-center px-2 py-2 ring-0 focus:outline-none focus-visible:border-0 focus-visible:ring-0 focus-visible:ring-transparent"
|
||||||
>
|
>
|
||||||
<AvatarWithText
|
<AvatarWithText
|
||||||
avatarFallback={formatAvatarFallback(selectedTeam?.name)}
|
avatarFallback={formatAvatarFallback(selectedTeam?.name)}
|
||||||
|
|||||||
@ -1,11 +1,30 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
|
import { useEffect } from 'react';
|
||||||
|
|
||||||
|
import { useTheme } from 'next-themes';
|
||||||
import SwaggerUI from 'swagger-ui-react';
|
import SwaggerUI from 'swagger-ui-react';
|
||||||
import 'swagger-ui-react/swagger-ui.css';
|
import 'swagger-ui-react/swagger-ui.css';
|
||||||
|
|
||||||
import { OpenAPIV1 } from '@documenso/api/v1/openapi';
|
import { OpenAPIV1 } from '@documenso/api/v1/openapi';
|
||||||
|
|
||||||
export const OpenApiDocsPage = () => {
|
export const OpenApiDocsPage = () => {
|
||||||
|
const { resolvedTheme } = useTheme();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const body = document.body;
|
||||||
|
|
||||||
|
if (resolvedTheme === 'dark') {
|
||||||
|
body.classList.add('swagger-dark-theme');
|
||||||
|
} else {
|
||||||
|
body.classList.remove('swagger-dark-theme');
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
body.classList.remove('swagger-dark-theme');
|
||||||
|
};
|
||||||
|
}, [resolvedTheme]);
|
||||||
|
|
||||||
return <SwaggerUI spec={OpenAPIV1} displayOperationId={true} />;
|
return <SwaggerUI spec={OpenAPIV1} displayOperationId={true} />;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -24,6 +24,13 @@ export const sendCompletedEmail = async ({ documentId, requestMetadata }: SendDo
|
|||||||
include: {
|
include: {
|
||||||
documentData: true,
|
documentData: true,
|
||||||
Recipient: true,
|
Recipient: true,
|
||||||
|
User: true,
|
||||||
|
team: {
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
url: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -35,27 +42,35 @@ export const sendCompletedEmail = async ({ documentId, requestMetadata }: SendDo
|
|||||||
throw new Error('Document has no recipients');
|
throw new Error('Document has no recipients');
|
||||||
}
|
}
|
||||||
|
|
||||||
const buffer = await getFile(document.documentData);
|
const { User: owner } = document;
|
||||||
|
|
||||||
await Promise.all(
|
const completedDocument = await getFile(document.documentData);
|
||||||
document.Recipient.map(async (recipient) => {
|
|
||||||
const { email, name, token } = recipient;
|
|
||||||
|
|
||||||
const assetBaseUrl = NEXT_PUBLIC_WEBAPP_URL() || 'http://localhost:3000';
|
const assetBaseUrl = NEXT_PUBLIC_WEBAPP_URL() || 'http://localhost:3000';
|
||||||
|
|
||||||
|
let documentOwnerDownloadLink = `${NEXT_PUBLIC_WEBAPP_URL()}/documents/${document.id}`;
|
||||||
|
|
||||||
|
if (document.team?.url) {
|
||||||
|
documentOwnerDownloadLink = `${NEXT_PUBLIC_WEBAPP_URL()}/t/${document.team.url}/documents/${
|
||||||
|
document.id
|
||||||
|
}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the document owner is not a recipient then send the email to them separately
|
||||||
|
if (!document.Recipient.find((recipient) => recipient.email === owner.email)) {
|
||||||
const template = createElement(DocumentCompletedEmailTemplate, {
|
const template = createElement(DocumentCompletedEmailTemplate, {
|
||||||
documentName: document.title,
|
documentName: document.title,
|
||||||
assetBaseUrl,
|
assetBaseUrl,
|
||||||
downloadLink: `${NEXT_PUBLIC_WEBAPP_URL()}/sign/${token}/complete`,
|
downloadLink: documentOwnerDownloadLink,
|
||||||
});
|
});
|
||||||
|
|
||||||
await prisma.$transaction(
|
|
||||||
async (tx) => {
|
|
||||||
await mailer.sendMail({
|
await mailer.sendMail({
|
||||||
to: {
|
to: [
|
||||||
address: email,
|
{
|
||||||
name,
|
name: owner.name || '',
|
||||||
|
address: owner.email,
|
||||||
},
|
},
|
||||||
|
],
|
||||||
from: {
|
from: {
|
||||||
name: process.env.NEXT_PRIVATE_SMTP_FROM_NAME || 'Documenso',
|
name: process.env.NEXT_PRIVATE_SMTP_FROM_NAME || 'Documenso',
|
||||||
address: process.env.NEXT_PRIVATE_SMTP_FROM_ADDRESS || 'noreply@documenso.com',
|
address: process.env.NEXT_PRIVATE_SMTP_FROM_ADDRESS || 'noreply@documenso.com',
|
||||||
@ -66,12 +81,62 @@ export const sendCompletedEmail = async ({ documentId, requestMetadata }: SendDo
|
|||||||
attachments: [
|
attachments: [
|
||||||
{
|
{
|
||||||
filename: document.title,
|
filename: document.title,
|
||||||
content: Buffer.from(buffer),
|
content: Buffer.from(completedDocument),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
await tx.documentAuditLog.create({
|
await prisma.documentAuditLog.create({
|
||||||
|
data: createDocumentAuditLogData({
|
||||||
|
type: DOCUMENT_AUDIT_LOG_TYPE.EMAIL_SENT,
|
||||||
|
documentId: document.id,
|
||||||
|
user: null,
|
||||||
|
requestMetadata,
|
||||||
|
data: {
|
||||||
|
emailType: 'DOCUMENT_COMPLETED',
|
||||||
|
recipientEmail: owner.email,
|
||||||
|
recipientName: owner.name,
|
||||||
|
recipientId: owner.id,
|
||||||
|
recipientRole: 'OWNER',
|
||||||
|
isResending: false,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
await Promise.all(
|
||||||
|
document.Recipient.map(async (recipient) => {
|
||||||
|
const downloadLink = `${NEXT_PUBLIC_WEBAPP_URL()}/sign/${recipient.token}/complete`;
|
||||||
|
|
||||||
|
const template = createElement(DocumentCompletedEmailTemplate, {
|
||||||
|
documentName: document.title,
|
||||||
|
assetBaseUrl,
|
||||||
|
downloadLink: recipient.email === owner.email ? documentOwnerDownloadLink : downloadLink,
|
||||||
|
});
|
||||||
|
|
||||||
|
await mailer.sendMail({
|
||||||
|
to: [
|
||||||
|
{
|
||||||
|
name: recipient.name,
|
||||||
|
address: recipient.email,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
from: {
|
||||||
|
name: process.env.NEXT_PRIVATE_SMTP_FROM_NAME || 'Documenso',
|
||||||
|
address: process.env.NEXT_PRIVATE_SMTP_FROM_ADDRESS || 'noreply@documenso.com',
|
||||||
|
},
|
||||||
|
subject: 'Signing Complete!',
|
||||||
|
html: render(template),
|
||||||
|
text: render(template, { plainText: true }),
|
||||||
|
attachments: [
|
||||||
|
{
|
||||||
|
filename: document.title,
|
||||||
|
content: Buffer.from(completedDocument),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
await prisma.documentAuditLog.create({
|
||||||
data: createDocumentAuditLogData({
|
data: createDocumentAuditLogData({
|
||||||
type: DOCUMENT_AUDIT_LOG_TYPE.EMAIL_SENT,
|
type: DOCUMENT_AUDIT_LOG_TYPE.EMAIL_SENT,
|
||||||
documentId: document.id,
|
documentId: document.id,
|
||||||
@ -87,9 +152,6 @@ export const sendCompletedEmail = async ({ documentId, requestMetadata }: SendDo
|
|||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
},
|
|
||||||
{ timeout: 30_000 },
|
|
||||||
);
|
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -65,7 +65,7 @@ export function DataTablePagination<TData>({
|
|||||||
</div>
|
</div>
|
||||||
<div className="flex flex-wrap items-center gap-x-6 gap-y-4 lg:gap-x-8">
|
<div className="flex flex-wrap items-center gap-x-6 gap-y-4 lg:gap-x-8">
|
||||||
<div className="flex items-center text-sm font-medium md:justify-center">
|
<div className="flex items-center text-sm font-medium md:justify-center">
|
||||||
Page {table.getState().pagination.pageIndex + 1} of {table.getPageCount()}
|
Page {table.getState().pagination.pageIndex + 1} of {table.getPageCount() || 1}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center gap-x-2">
|
<div className="flex items-center gap-x-2">
|
||||||
|
|||||||
@ -129,3 +129,12 @@
|
|||||||
.custom-scrollbar::-webkit-scrollbar-thumb:hover {
|
.custom-scrollbar::-webkit-scrollbar-thumb:hover {
|
||||||
background: rgb(100 116 139 / 0.5);
|
background: rgb(100 116 139 / 0.5);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Custom Swagger Dark Theme */
|
||||||
|
.swagger-dark-theme .swagger-ui {
|
||||||
|
filter: invert(88%) hue-rotate(180deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.swagger-dark-theme .swagger-ui .microlight {
|
||||||
|
filter: invert(100%) hue-rotate(180deg);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user