Compare commits

...

11 Commits

Author SHA1 Message Date
Ephraim Atta-Duncan
5ef3523d89 fix: increase table col span to center text 2023-07-10 02:43:28 +00:00
Ephraim Atta-Duncan
a27c6c3eb2 refactor: use tailwind in styling icons 2023-07-05 20:53:08 +00:00
Ephraim Atta-Duncan
a5b3969f56 revert: include time in document created 2023-07-05 20:49:20 +00:00
Ephraim Atta-Duncan
f61bf00702 feat: pass document id as prop to the actions component 2023-07-04 00:59:25 +00:00
Ephraim Atta-Duncan
86b0d5ee9c feat: add actions to all documents table 2023-07-04 00:32:11 +00:00
Ephraim Atta-Duncan
1dd1bb617d feat: add action buttons to documents table 2023-07-04 00:29:26 +00:00
Mythie
60b150cc58 fix: add shadow to metric-card 2023-06-24 15:01:18 +10:00
Mythie
bd0db0f8fd feat: email templates
adds email templates using `react-email` which will be used for invites,
signing and document completion.

authored by @dephraiim
2023-06-24 14:59:08 +10:00
Mythie
d8a094a324 chore: use jsonprotocol 2023-06-23 18:06:02 +10:00
Lucas Smith
4b063b68ab Merge pull request #214 from doug-andrade/refresh/dashboard-filters
linking card metrics to filtered /documents
2023-06-22 08:09:16 +10:00
Doug Andrade
3c02331cb9 linking card metrics to filtered /documents 2023-06-21 17:57:02 -04:00
22 changed files with 6991 additions and 16 deletions

View File

@@ -15,6 +15,7 @@ import {
} from '@documenso/ui/primitives/table';
import { CardMetric } from '~/components/(dashboard)/metric-card/metric-card';
import { ActionButtons } from '~/components/(dashboard)/table/actions-component';
import { DocumentStatus } from '~/components/formatter/document-status';
import { LocaleDate } from '~/components/formatter/locale-date';
@@ -38,9 +39,15 @@ export default async function DashboardPage() {
<h1 className="text-4xl font-semibold">Dashboard</h1>
<div className="mt-8 grid grid-cols-1 gap-4 md:grid-cols-3">
<CardMetric icon={FileCheck} title="Completed" value={stats.COMPLETED} />
<CardMetric icon={File} title="Drafts" value={stats.DRAFT} />
<CardMetric icon={Clock} title="Pending" value={stats.PENDING} />
<Link href={'/documents?status=COMPLETED'} passHref>
<CardMetric icon={FileCheck} title="Completed" value={stats.COMPLETED} />
</Link>
<Link href={'/documents?status=DRAFT'} passHref>
<CardMetric icon={File} title="Drafts" value={stats.DRAFT} />
</Link>
<Link href={'/documents?status=PENDING'} passHref>
<CardMetric icon={Clock} title="Pending" value={stats.PENDING} />
</Link>
</div>
<div className="mt-12">
@@ -55,7 +62,8 @@ export default async function DashboardPage() {
<TableHead className="w-[100px]">ID</TableHead>
<TableHead>Title</TableHead>
<TableHead>Status</TableHead>
<TableHead className="text-right">Created</TableHead>
<TableHead>Created</TableHead>
<TableHead>Actions</TableHead>
</TableRow>
</TableHeader>
<TableBody>
@@ -73,14 +81,17 @@ export default async function DashboardPage() {
<TableCell>
<DocumentStatus status={document.status} />
</TableCell>
<TableCell className="text-right">
<TableCell>
<LocaleDate date={document.created} />
</TableCell>
<TableCell className="flex cursor-pointer gap-6">
<ActionButtons documentId={document.id} />
</TableCell>
</TableRow>
))}
{results.data.length === 0 && (
<TableRow>
<TableCell colSpan={4} className="h-24 text-center">
<TableCell colSpan={5} className="h-24 text-center">
No results.
</TableCell>
</TableRow>

View File

@@ -12,6 +12,7 @@ import { Document } from '@documenso/prisma/client';
import { DataTable } from '@documenso/ui/primitives/data-table';
import { DataTablePagination } from '@documenso/ui/primitives/data-table-pagination';
import { ActionButtons } from '~/components/(dashboard)/table/actions-component';
import { DocumentStatus } from '~/components/formatter/document-status';
import { LocaleDate } from '~/components/formatter/locale-date';
@@ -59,6 +60,11 @@ export const DocumentsDataTable = ({ results }: DocumentsDataTableProps) => {
accessorKey: 'created',
cell: ({ row }) => <LocaleDate date={row.getValue('created')} />,
},
{
header: 'Actions',
accessorKey: 'actions',
cell: ({ row }) => <ActionButtons documentId={row.original.id} />,
},
]}
data={results.data}
perPage={results.perPage}

View File

@@ -13,7 +13,12 @@ export type CardMetricProps = {
export const CardMetric = ({ icon: Icon, title, value, className }: CardMetricProps) => {
return (
<div className={cn('border-border bg-background overflow-hidden rounded-lg border', className)}>
<div
className={cn(
'border-border bg-background overflow-hidden rounded-lg border shadow shadow-transparent duration-200 hover:shadow-slate-100',
className,
)}
>
<div className="px-4 pb-6 pt-4 sm:px-4 sm:pb-8 sm:pt-4">
<div className="flex items-start">
{Icon && <Icon className="mr-2 h-4 w-4 text-slate-500" />}

View File

@@ -0,0 +1,30 @@
'use client';
import React from 'react';
import { Download, Edit, Trash } from 'lucide-react';
export function ActionButtons({ documentId }: { documentId: number }) {
return (
<div className="flex cursor-pointer gap-6">
<Edit
className="text-primary h-5 w-5"
onClick={() => {
console.log('Edit Document with id: ', documentId);
}}
/>
<Download
className="text-primary h-5 w-5"
onClick={() => {
console.log('Download Document with id: ', documentId);
}}
/>
<Trash
className="text-primary h-5 w-5"
onClick={() => {
console.log('Delete Document with id: ', documentId);
}}
/>
</div>
);
}

6535
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -19,6 +19,7 @@
"packageManager": "npm@8.19.2",
"workspaces": [
"apps/*",
"packages/*"
"packages/*",
"packages/email/.react-email"
]
}

1
packages/email/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
.react-email/

1
packages/email/ambient.d.ts vendored Normal file
View File

@@ -0,0 +1 @@
declare module '@documenso/tailwind-config';

1
packages/email/index.ts Normal file
View File

@@ -0,0 +1 @@
export { render, renderAsync } from '@react-email/components';

View File

@@ -0,0 +1,22 @@
{
"name": "@documenso/email",
"version": "1.0.0",
"main": "./index.ts",
"types": "./index.ts",
"license": "MIT",
"files": [
"templates/"
],
"scripts": {
"dev": "email dev --port 3002 --dir templates"
},
"dependencies": {
"@documenso/tsconfig": "*",
"@documenso/tailwind-config": "*",
"@documenso/ui": "*",
"@react-email/components": "^0.0.7"
},
"devDependencies": {
"react-email": "^1.9.4"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 784 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 708 B

View File

@@ -0,0 +1,11 @@
/* eslint-disable @typescript-eslint/no-var-requires */
const baseConfig = require('@documenso/tailwind-config');
const path = require('path');
module.exports = {
...baseConfig,
content: [
`templates/**/*.{ts,tsx}`,
`${path.join(require.resolve('@documenso/ui'), '..')}/**/*.{ts,tsx}`,
],
};

View File

@@ -0,0 +1,129 @@
import {
Body,
Button,
Container,
Head,
Html,
Img,
Link,
Preview,
Section,
Tailwind,
Text,
} from '@react-email/components';
import * as config from '@documenso/tailwind-config';
interface DocumentCompletedEmailTemplateProps {
downloadLink?: string;
reviewLink?: string;
documentName?: string;
assetBaseUrl?: string;
}
export const DocumentCompletedEmailTemplate = ({
downloadLink = 'https://documenso.com',
reviewLink = 'https://documenso.com',
documentName = 'Open Source Pledge.pdf',
assetBaseUrl = 'http://localhost:3002',
}: DocumentCompletedEmailTemplateProps) => {
const previewText = `Completed Document`;
const getAssetUrl = (path: string) => {
return new URL(path, assetBaseUrl).toString();
};
return (
<Html>
<Head />
<Preview>{previewText}</Preview>
<Tailwind
config={{
theme: {
extend: {
colors: config.theme.extend.colors,
},
},
}}
>
<Body className="mx-auto my-auto font-sans">
<Section className="bg-white">
<Container className="mx-auto mb-2 mt-8 max-w-xl rounded-lg border border-solid border-slate-200 p-2 backdrop-blur-sm">
<Section className="p-2">
<Img src={getAssetUrl('/static/logo.png')} alt="Documenso Logo" className="h-6" />
<Section className="mt-4 flex-row items-center justify-center">
<div className="flex items-center justify-center p-4">
<Img
className="h-42"
src={getAssetUrl('/static/document.png')}
alt="Documenso"
/>
</div>
<Text className="mb-4 flex items-center justify-center text-center text-base font-semibold text-[#7AC455]">
<Img
src={getAssetUrl('/static/completed.png')}
className="-mb-0.5 mr-2 inline h-7 w-7"
/>
Completed
</Text>
<Text className="text-primary mb-0 text-center text-lg font-semibold">
{documentName} was signed by all signers
</Text>
<Text className="my-1 text-center text-base text-slate-400">
Continue by downloading or reviewing the document.
</Text>
<Section className="mb-6 mt-8 text-center">
<Button
className="mr-4 inline-flex items-center justify-center rounded-lg border border-solid border-slate-200 px-4 py-2 text-center text-sm font-medium text-black no-underline"
href={reviewLink}
>
<Img
src={getAssetUrl('/static/review.png')}
className="-mb-1 mr-2 inline h-5 w-5"
/>
Review
</Button>
<Button
className="inline-flex items-center justify-center rounded-lg border border-solid border-slate-200 px-4 py-2 text-center text-sm font-medium text-black no-underline"
href={downloadLink}
>
<Img
src={getAssetUrl('/static/download.png')}
className="-mb-1 mr-2 inline h-5 w-5"
/>
Download
</Button>
</Section>
</Section>
</Section>
</Container>
<Container className="mx-auto max-w-xl">
<Section>
<Text className="my-4 text-base text-slate-400">
This document was sent using{' '}
<Link className="text-[#7AC455]" href="https://documenso.com">
Documenso.
</Link>
</Text>
<Text className="my-8 text-sm text-slate-400">
Documenso
<br />
2261 Market Street, #5211, San Francisco, CA 94114, USA
</Text>
</Section>
</Container>
</Section>
</Body>
</Tailwind>
</Html>
);
};
export default DocumentCompletedEmailTemplate;

View File

@@ -0,0 +1,127 @@
import {
Body,
Button,
Container,
Head,
Hr,
Html,
Img,
Link,
Preview,
Section,
Tailwind,
Text,
} from '@react-email/components';
import * as config from '@documenso/tailwind-config';
interface DocumentInviteEmailTemplateProps {
inviterName?: string;
inviterEmail?: string;
documentName?: string;
signDocumentLink?: string;
assetBaseUrl?: string;
}
export const DocumentInviteEmailTemplate = ({
inviterName = 'Lucas Smith',
inviterEmail = 'lucas@documenso.com',
documentName = 'Open Source Pledge.pdf',
signDocumentLink = 'https://documenso.com',
assetBaseUrl = 'http://localhost:3002',
}: DocumentInviteEmailTemplateProps) => {
const previewText = `Completed Document`;
const getAssetUrl = (path: string) => {
return new URL(path, assetBaseUrl).toString();
};
return (
<Html>
<Head />
<Preview>{previewText}</Preview>
<Tailwind
config={{
theme: {
extend: {
colors: config.theme.extend.colors,
},
},
}}
>
<Body className="mx-auto my-auto bg-white font-sans">
<Section>
<Container className="mx-auto mb-2 mt-8 max-w-xl rounded-lg border border-solid border-slate-200 p-2 backdrop-blur-sm">
<Section className="p-2">
<Img src={getAssetUrl('/static/logo.png')} alt="Documenso Logo" className="h-6" />
<Section className="mt-4 flex-row items-center justify-center">
<div className="flex items-center justify-center p-4">
<Img
className="h-42"
src={getAssetUrl('/static/document.png')}
alt="Documenso"
/>
</div>
<Text className="text-primary mx-auto mb-0 max-w-[80%] text-center text-lg font-semibold">
{inviterName} has invited you to sign "{documentName}"
</Text>
<Text className="my-1 text-center text-base text-slate-400">
Continue by signing the document.
</Text>
<Section className="mb-6 mt-8 text-center">
<Button
className="bg-documenso-500 inline-flex items-center justify-center rounded-lg px-6 py-3 text-center text-sm font-medium text-black no-underline"
href={signDocumentLink}
>
Sign Document
</Button>
</Section>
</Section>
</Section>
</Container>
<Container className="mx-auto mt-12 max-w-xl">
<Section>
<Text className="my-4 text-base font-semibold">
{inviterName}{' '}
<Link className="font-normal text-slate-400" href="mailto:{inviterEmail}">
({inviterEmail})
</Link>
</Text>
<Text className="mt-2 text-base text-slate-400">
{inviterName} has invited you to sign the document "{documentName}".
</Text>
</Section>
</Container>
<Hr className="mx-auto mt-12 max-w-xl" />
<Container className="mx-auto max-w-xl">
<Section>
<Text className="my-4 text-base text-slate-400">
This document was sent using{' '}
<Link className="text-[#7AC455]" href="https://documenso.com">
Documenso.
</Link>
</Text>
<Text className="my-8 text-sm text-slate-400">
Documenso
<br />
2261 Market Street, #5211, San Francisco, CA 94114, USA
</Text>
</Section>
</Container>
</Section>
</Body>
</Tailwind>
</Html>
);
};
export default DocumentInviteEmailTemplate;

View File

@@ -0,0 +1,104 @@
import {
Body,
Button,
Container,
Head,
Html,
Img,
Link,
Preview,
Section,
Tailwind,
Text,
} from '@react-email/components';
import * as config from '@documenso/tailwind-config';
interface DocumentPendingEmailTemplateProps {
documentName?: string;
assetBaseUrl?: string;
}
export const DocumentPendingEmailTemplate = ({
documentName = 'Open Source Pledge.pdf',
assetBaseUrl = 'http://localhost:3002',
}: DocumentPendingEmailTemplateProps) => {
const previewText = `Pending Document`;
const getAssetUrl = (path: string) => {
return new URL(path, assetBaseUrl).toString();
};
return (
<Html>
<Head />
<Preview>{previewText}</Preview>
<Tailwind
config={{
theme: {
extend: {
colors: config.theme.extend.colors,
},
},
}}
>
<Body className="mx-auto my-auto font-sans">
<Section className="bg-white">
<Container className="mx-auto mb-2 mt-8 max-w-xl rounded-lg border border-solid border-slate-200 p-2 backdrop-blur-sm">
<Section className="p-2">
<Img src={getAssetUrl('/static/logo.png')} alt="Documenso Logo" className="h-6" />
<Section className="mt-4 flex-row items-center justify-center">
<div className="flex items-center justify-center p-4">
<Img
className="h-42"
src={getAssetUrl('/static/document.png')}
alt="Documenso"
/>
</div>
<Text className="mb-4 flex items-center justify-center text-center text-base font-semibold text-blue-500">
<Img
src={getAssetUrl('/static/clock.png')}
className="-mb-0.5 mr-2 inline h-7 w-7"
/>
Waiting for others
</Text>
<Text className="text-primary mb-0 text-center text-lg font-semibold">
{documentName} has been signed
</Text>
<Text className="mx-auto mb-6 mt-1 max-w-[80%] text-center text-base text-slate-400">
We're still waiting for other signers to sign this document.
<br />
We'll notify you as soon as it's ready.
</Text>
</Section>
</Section>
</Container>
<Container className="mx-auto max-w-xl">
<Section>
<Text className="my-4 text-base text-slate-400">
This document was sent using{' '}
<Link className="text-[#7AC455]" href="https://documenso.com">
Documenso.
</Link>
</Text>
<Text className="my-8 text-sm text-slate-400">
Documenso
<br />
2261 Market Street, #5211, San Francisco, CA 94114, USA
</Text>
</Section>
</Container>
</Section>
</Body>
</Tailwind>
</Html>
);
};
export default DocumentPendingEmailTemplate;

View File

@@ -0,0 +1,5 @@
{
"extends": "@documenso/tsconfig/react-library.json",
"include": ["."],
"exclude": ["dist", "build", "node_modules"]
}

View File

@@ -1,6 +1,6 @@
generator client {
provider = "prisma-client-js"
previewFeatures = ["extendedWhereUnique"]
previewFeatures = ["extendedWhereUnique", "jsonProtocol"]
}
datasource db {