Compare commits

...

31 Commits

Author SHA1 Message Date
David Nguyen
f728dd13c5 fix: add cascade delete for share links 2023-10-13 12:45:39 +11:00
David Nguyen
c803d2c4ba feat: single-player-mode-polish (#435) 2023-10-12 18:10:52 +11:00
Udit Takkar
eb5f5f7a90 fix: background color of signature page (#487) 2023-10-12 14:08:26 +11:00
Abhinav-Developer-23
2ea5ff2c94 fix: bypass signature fix (#536) (#547) 2023-10-12 11:33:01 +11:00
Lucas Smith
565602f8e1 Merge pull request #530 from anikdhabal/issue#518
fix: Add gitpod configuration
2023-10-11 16:56:59 +11:00
Nafees Nazik
e0271cace3 feat: delete draft document (#491) 2023-10-10 13:55:58 +11:00
Anik Dhabal Babu
cc8c4b8297 Merge branch 'feat/refresh' into issue#518 2023-10-09 15:21:33 +05:30
Mythie
a287aab4f4 chore: disable dependabot for now 2023-10-09 20:35:19 +11:00
Lucas Smith
b5ed703553 Merge pull request #545 from anikdhabal/fix_dotenv-cli
fix: mismatch the version of dotenv-cli
2023-10-09 19:49:07 +11:00
Anik Dhabal Babu
f49880125a fix: mismatch the version of dotenv-cli 2023-10-09 08:41:13 +00:00
Anik Dhabal Babu
8380c357d9 Merge branch 'feat/refresh' into issue#518 2023-10-09 10:03:58 +05:30
Anik Dhabal Babu
4e010c5624 fix : add gittpod configuration 2023-10-09 09:58:12 +05:30
hallidayo
f53cdbace9 fix: frequency focus ring (#533) 2023-10-09 12:04:01 +11:00
Lucas Smith
b4d04e2ce9 Merge pull request #516 from adityadeshlahre/feat/refresh
fix(script): [fix : DOC-36] Use dotenv for Prisma package scripts #523
2023-10-09 10:12:08 +11:00
Mythie
2470aeee1f fix: update script, docs and devcontainer 2023-10-08 21:51:15 +11:00
Lucas Smith
fd07b47325 Merge pull request #526 from mittalsam98/fix/507-signature-modal-center-align
fix: non responsiveness of Add your sign modal
2023-10-07 22:31:18 +11:00
Lucas Smith
9257a05831 Merge pull request #527 from documenso/docs/render-deploy
docs: add render one click deploy for refresh
2023-10-07 22:25:42 +11:00
Ephraim Atta-Duncan
1faa6f2944 Merge pull request #528 from documenso/chore/github-templates 2023-10-07 02:35:35 +00:00
Anik Dhabal Babu
cc65537ea3 fix: Add gitpod configuration 2023-10-06 23:03:13 +05:30
Anik Dhabal Babu
04a80b7c03 fix: add gitpod configuration 2023-10-06 11:06:34 +05:30
Anik Dhabal Babu
c71a89d1b7 fix: Add gitpod configuration 2023-10-05 12:21:34 +00:00
Aditya Deshlahre
e2abfd2312 Merge branch 'documenso:feat/refresh' into feat/refresh 2023-10-05 15:46:14 +05:30
Aditya @ArchLinux
0dadec3b8d fix(script): minor change on scipt 2023-10-05 15:26:53 +05:30
Aditya Deshlahre
e2d8591d66 Merge branch 'documenso:feat/refresh' into feat/refresh 2023-10-05 15:19:42 +05:30
Ephraim Atta-Duncan
b6f9d70fec docs: add render one click deploy for refresh 2023-10-04 19:58:07 +00:00
Sachin
7c54913bf5 fix: non responsiveness of Add your sign modal 2023-10-05 00:09:30 +05:30
Aditya @ArchLinux
ddf097ede3 Merge branch 'adityadeshlahre/documenso' into feat/refresh 2023-10-04 20:05:33 +05:30
Aditya Deshlahre
1bad85e1d6 Merge branch 'documenso:feat/refresh' into feat/refresh 2023-10-04 20:04:48 +05:30
Aditya @ArchLinux
68458b50d2 fix(script): added script envprisma in root package.json
dotenv loads all environment variable before running prisma:migrate-dev script
2023-10-04 20:04:07 +05:30
Aditya Deshlahre
4cc34ec50a Merge branch 'documenso:feat/refresh' into feat/refresh 2023-10-04 13:00:52 +05:30
Aditya @ArchLinux
f637381198 style(ui/ux): added margin to dialogprimitive.content & dialogprimitive.close (m-4) 2023-10-04 01:39:16 +05:30
33 changed files with 405 additions and 68 deletions

View File

@@ -9,10 +9,5 @@ npm install
# Copy the env file # Copy the env file
cp .env.example .env cp .env.example .env
# Source the env file, export the variables
set -a
source .env
set +a
# Run the migrations # Run the migrations
npm run -w @documenso/prisma prisma:migrate-dev npm run prisma:migrate-dev

View File

@@ -9,7 +9,7 @@ updates:
labels: labels:
- "ci dependencies" - "ci dependencies"
- "ci" - "ci"
open-pull-requests-limit: 2 open-pull-requests-limit: 0
- package-ecosystem: "npm" - package-ecosystem: "npm"
directory: "/apps/marketing" directory: "/apps/marketing"
@@ -19,7 +19,7 @@ updates:
labels: labels:
- "npm dependencies" - "npm dependencies"
- "frontend" - "frontend"
open-pull-requests-limit: 2 open-pull-requests-limit: 0
- package-ecosystem: "npm" - package-ecosystem: "npm"
directory: "/apps/web" directory: "/apps/web"
@@ -29,4 +29,4 @@ updates:
labels: labels:
- "npm dependencies" - "npm dependencies"
- "frontend" - "frontend"
open-pull-requests-limit: 2 open-pull-requests-limit: 0

55
.gitpod.yml Normal file
View File

@@ -0,0 +1,55 @@
tasks:
- init: |
npm i &&
npm run dx:up &&
cp .env.example .env &&
set -a; source .env &&
export NEXTAUTH_URL="$(gp url 3000)" &&
export NEXT_PUBLIC_WEBAPP_URL="$(gp url 3000)" &&
export NEXT_PUBLIC_MARKETING_URL="$(gp url 3001)"
command: npm run d
ports:
- port: 3000
visibility: public
onOpen: open-preview
- port: 3001
visibility: public
onOpen: open-preview
- port: 9000
visibility: public
onOpen: ignore
- port: 1100
visibility: private
onOpen: ignore
- port: 2500
visibility: private
onOpen: ignore
- port: 54320
visibility: private
onOpen: ignore
github:
prebuilds:
master: true
pullRequests: true
pullRequestsFromForks: true
addCheck: true
addComment: true
addBadge: true
vscode:
extensions:
- aaron-bond.better-comments
- bradlc.vscode-tailwindcss
- dbaeumer.vscode-eslint
- esbenp.prettier-vscode
- mikestead.dotenv
- unifiedjs.vscode-mdx
- GitHub.copilot-chat
- GitHub.copilot-labs
- GitHub.copilot
- GitHub.vscode-pull-request-github
- Prisma.prisma
- VisualStudioExptTeam.vscodeintellicode

View File

@@ -179,7 +179,7 @@ git clone https://github.com/documenso/documenso
- NEXT_PRIVATE_SMTP_FROM_NAME - NEXT_PRIVATE_SMTP_FROM_NAME
- NEXT_PRIVATE_SMTP_FROM_ADDRESS - NEXT_PRIVATE_SMTP_FROM_ADDRESS
5. Create the database schema by running `npm run prisma:migrate-dev -w @documenso/prisma` 5. Create the database schema by running `npm run prisma:migrate-dev`
6. Run `npm run dev` root directory to start 6. Run `npm run dev` root directory to start
@@ -254,6 +254,22 @@ containers:
- '::' - '::'
``` ```
### I can't see environment variables in my package scripts
Wrap your package script with the `with:env` script like such:
```
npm run with:env -- npm run myscript
```
The same can be done when using `npx` for one of bin scripts:
```
npm run with:env -- npx myscript
```
This will load environment variables from your `.env` and `.env.local` files.
## Repo Activity ## Repo Activity
![Repository Activity](https://repobeats.axiom.co/api/embed/622a2e9aa709696f7226304b5b7178a5741b3868.svg) ![Repository Activity](https://repobeats.axiom.co/api/embed/622a2e9aa709696f7226304b5b7178a5741b3868.svg)

View File

@@ -2,6 +2,8 @@
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import { usePathname } from 'next/navigation';
import { cn } from '@documenso/ui/lib/utils'; import { cn } from '@documenso/ui/lib/utils';
import { Footer } from '~/components/(marketing)/footer'; import { Footer } from '~/components/(marketing)/footer';
@@ -13,6 +15,7 @@ export type MarketingLayoutProps = {
export default function MarketingLayout({ children }: MarketingLayoutProps) { export default function MarketingLayout({ children }: MarketingLayoutProps) {
const [scrollY, setScrollY] = useState(0); const [scrollY, setScrollY] = useState(0);
const pathname = usePathname();
useEffect(() => { useEffect(() => {
const onScroll = () => { const onScroll = () => {
@@ -25,7 +28,11 @@ export default function MarketingLayout({ children }: MarketingLayoutProps) {
}, []); }, []);
return ( return (
<div className="relative max-w-[100vw] overflow-y-auto overflow-x-hidden pt-20 md:pt-28"> <div
className={cn('relative max-w-[100vw] pt-20 md:pt-28', {
'overflow-y-auto overflow-x-hidden': pathname !== '/singleplayer',
})}
>
<div <div
className={cn('fixed left-0 top-0 z-50 w-full bg-transparent', { className={cn('fixed left-0 top-0 z-50 w-full bg-transparent', {
'bg-background/50 backdrop-blur-md': scrollY > 5, 'bg-background/50 backdrop-blur-md': scrollY > 5,

View File

@@ -130,7 +130,7 @@ export default function SinglePlayerModePage() {
signer: data.email, signer: data.email,
}); });
router.push(`/single-player-mode/${documentToken}/success`); router.push(`/singleplayer/${documentToken}/success`);
} catch { } catch {
toast({ toast({
title: 'Something went wrong', title: 'Something went wrong',

View File

@@ -23,7 +23,7 @@ const SOCIAL_LINKS = [
const FOOTER_LINKS = [ const FOOTER_LINKS = [
{ href: '/pricing', text: 'Pricing' }, { href: '/pricing', text: 'Pricing' },
{ href: '/single-player-mode', text: 'Single Player Mode' }, { href: '/singleplayer', text: 'Singleplayer' },
{ href: '/blog', text: 'Blog' }, { href: '/blog', text: 'Blog' },
{ href: '/open', text: 'Open' }, { href: '/open', text: 'Open' },
{ href: 'https://shop.documenso.com', text: 'Shop', target: '_blank' }, { href: 'https://shop.documenso.com', text: 'Shop', target: '_blank' },

View File

@@ -35,7 +35,7 @@ export const Header = ({ className, ...props }: HeaderProps) => {
{isSinglePlayerModeMarketingEnabled && ( {isSinglePlayerModeMarketingEnabled && (
<Link <Link
href="/single-player-mode" href="/singleplayer"
className="bg-primary dark:text-background rounded-full px-2 py-1 text-xs font-semibold sm:px-3" className="bg-primary dark:text-background rounded-full px-2 py-1 text-xs font-semibold sm:px-3"
> >
Try now! Try now!

View File

@@ -134,9 +134,9 @@ export const Hero = ({ className, ...props }: HeroProps) => {
variants={HeroTitleVariants} variants={HeroTitleVariants}
initial="initial" initial="initial"
animate="animate" animate="animate"
className="border-primary bg-background hover:bg-muted mx-auto mt-8 w-60 rounded-xl border transition duration-300" className="border-primary bg-background hover:bg-muted mx-auto mt-8 w-60 rounded-xl border transition-colors duration-300"
> >
<Link href="/single-player-mode" className="block px-4 py-2 text-center"> <Link href="/singleplayer" className="block px-4 py-2 text-center">
<h2 className="text-muted-foreground text-xs font-semibold"> <h2 className="text-muted-foreground text-xs font-semibold">
Introducing Single Player Mode Introducing Single Player Mode
</h2> </h2>

View File

@@ -17,8 +17,8 @@ export type MobileNavigationProps = {
export const MENU_NAVIGATION_LINKS = [ export const MENU_NAVIGATION_LINKS = [
{ {
href: '/single-player-mode', href: '/singleplayer',
text: 'Single Player Mode', text: 'Singleplayer',
}, },
{ {
href: '/blog', href: '/blog',

View File

@@ -377,7 +377,7 @@ export const Widget = ({ className, children, ...props }: WidgetProps) => {
</Card> </Card>
<Dialog open={showSigningDialog} onOpenChange={setShowSigningDialog}> <Dialog open={showSigningDialog} onOpenChange={setShowSigningDialog}>
<DialogContent> <DialogContent position="center">
<DialogHeader> <DialogHeader>
<DialogTitle>Add your signature</DialogTitle> <DialogTitle>Add your signature</DialogTitle>
</DialogHeader> </DialogHeader>

View File

@@ -1,5 +1,7 @@
'use client'; 'use client';
import { useState } from 'react';
import Link from 'next/link'; import Link from 'next/link';
import { import {
@@ -32,6 +34,8 @@ import { useToast } from '@documenso/ui/primitives/use-toast';
import { useCopyToClipboard } from '~/hooks/use-copy-to-clipboard'; import { useCopyToClipboard } from '~/hooks/use-copy-to-clipboard';
import { DeleteDraftDocumentDialog } from './delete-draft-document-dialog';
export type DataTableActionDropdownProps = { export type DataTableActionDropdownProps = {
row: Document & { row: Document & {
User: Pick<User, 'id' | 'name' | 'email'>; User: Pick<User, 'id' | 'name' | 'email'>;
@@ -44,6 +48,8 @@ export const DataTableActionDropdown = ({ row }: DataTableActionDropdownProps) =
const { toast } = useToast(); const { toast } = useToast();
const [, copyToClipboard] = useCopyToClipboard(); const [, copyToClipboard] = useCopyToClipboard();
const [isDeleteDialogOpen, setDeleteDialogOpen] = useState(false);
if (!session) { if (!session) {
return null; return null;
} }
@@ -55,10 +61,11 @@ export const DataTableActionDropdown = ({ row }: DataTableActionDropdownProps) =
const isOwner = row.User.id === session.user.id; const isOwner = row.User.id === session.user.id;
// const isRecipient = !!recipient; // const isRecipient = !!recipient;
// const isDraft = row.status === DocumentStatus.DRAFT; const isDraft = row.status === DocumentStatus.DRAFT;
// const isPending = row.status === DocumentStatus.PENDING; // const isPending = row.status === DocumentStatus.PENDING;
const isComplete = row.status === DocumentStatus.COMPLETED; const isComplete = row.status === DocumentStatus.COMPLETED;
// const isSigned = recipient?.signingStatus === SigningStatus.SIGNED; // const isSigned = recipient?.signingStatus === SigningStatus.SIGNED;
const isDocumentDeletable = isOwner && row.status === DocumentStatus.DRAFT;
const onShareClick = async () => { const onShareClick = async () => {
const { slug } = await createOrGetShareLink({ const { slug } = await createOrGetShareLink({
@@ -147,7 +154,7 @@ export const DataTableActionDropdown = ({ row }: DataTableActionDropdownProps) =
Void Void
</DropdownMenuItem> </DropdownMenuItem>
<DropdownMenuItem disabled> <DropdownMenuItem onClick={() => setDeleteDialogOpen(true)} disabled={!isDocumentDeletable}>
<Trash2 className="mr-2 h-4 w-4" /> <Trash2 className="mr-2 h-4 w-4" />
Delete Delete
</DropdownMenuItem> </DropdownMenuItem>
@@ -159,7 +166,7 @@ export const DataTableActionDropdown = ({ row }: DataTableActionDropdownProps) =
Resend Resend
</DropdownMenuItem> </DropdownMenuItem>
<DropdownMenuItem onClick={onShareClick}> <DropdownMenuItem disabled={isDraft} onClick={onShareClick}>
{isCreatingShareLink ? ( {isCreatingShareLink ? (
<Loader className="mr-2 h-4 w-4" /> <Loader className="mr-2 h-4 w-4" />
) : ( ) : (
@@ -168,6 +175,14 @@ export const DataTableActionDropdown = ({ row }: DataTableActionDropdownProps) =
Share Share
</DropdownMenuItem> </DropdownMenuItem>
</DropdownMenuContent> </DropdownMenuContent>
{isDocumentDeletable && (
<DeleteDraftDocumentDialog
id={row.id}
open={isDeleteDialogOpen}
onOpenChange={setDeleteDialogOpen}
/>
)}
</DropdownMenu> </DropdownMenu>
); );
}; };

View File

@@ -0,0 +1,89 @@
import { useRouter } from 'next/navigation';
import { trpc as trpcReact } from '@documenso/trpc/react';
import { Button } from '@documenso/ui/primitives/button';
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
} from '@documenso/ui/primitives/dialog';
import { useToast } from '@documenso/ui/primitives/use-toast';
type DeleteDraftDocumentDialogProps = {
id: number;
open: boolean;
onOpenChange: (_open: boolean) => void;
};
export const DeleteDraftDocumentDialog = ({
id,
open,
onOpenChange,
}: DeleteDraftDocumentDialogProps) => {
const router = useRouter();
const { toast } = useToast();
const { mutateAsync: deleteDocument, isLoading } =
trpcReact.document.deleteDraftDocument.useMutation({
onSuccess: () => {
router.refresh();
toast({
title: 'Document deleted',
description: 'Your document has been successfully deleted.',
duration: 5000,
});
onOpenChange(false);
},
});
const onDraftDelete = async () => {
try {
await deleteDocument({ id });
} catch {
toast({
title: 'Something went wrong',
description: 'This document could not be deleted at this time. Please try again.',
variant: 'destructive',
duration: 7500,
});
}
};
return (
<Dialog open={open} onOpenChange={(value) => !isLoading && onOpenChange(value)}>
<DialogContent>
<DialogHeader>
<DialogTitle>Do you want to delete this document?</DialogTitle>
<DialogDescription>
Please note that this action is irreversible. Once confirmed, your document will be
permanently deleted.
</DialogDescription>
</DialogHeader>
<DialogFooter>
<div className="flex w-full flex-1 flex-nowrap gap-4">
<Button
type="button"
variant="secondary"
onClick={() => onOpenChange(false)}
className="flex-1"
>
Cancel
</Button>
<Button type="button" loading={isLoading} onClick={onDraftDelete} className="flex-1">
Delete
</Button>
</div>
</DialogFooter>
</DialogContent>
</Dialog>
);
};

View File

@@ -66,7 +66,7 @@ export default async function DocumentsPage({ searchParams = {} }: DocumentsPage
<div className="mt-12 flex flex-wrap items-center justify-between gap-x-4 gap-y-8"> <div className="mt-12 flex flex-wrap items-center justify-between gap-x-4 gap-y-8">
<h1 className="text-4xl font-semibold">Documents</h1> <h1 className="text-4xl font-semibold">Documents</h1>
<div className="flex flex-wrap gap-x-4 gap-y-6 overflow-hidden"> <div className="-m-1 flex flex-wrap gap-x-4 gap-y-6 overflow-hidden p-1">
<Tabs defaultValue={status} className="overflow-x-auto"> <Tabs defaultValue={status} className="overflow-x-auto">
<TabsList> <TabsList>
{[ {[

View File

@@ -1,6 +1,6 @@
'use client'; 'use client';
import { useMemo, useState, useTransition } from 'react'; import { useEffect, useMemo, useState, useTransition } from 'react';
import { useRouter } from 'next/navigation'; import { useRouter } from 'next/navigation';
@@ -48,6 +48,7 @@ export const SignatureField = ({ field, recipient }: SignatureFieldProps) => {
const [showSignatureModal, setShowSignatureModal] = useState(false); const [showSignatureModal, setShowSignatureModal] = useState(false);
const [localSignature, setLocalSignature] = useState<string | null>(null); const [localSignature, setLocalSignature] = useState<string | null>(null);
const [isLocalSignatureSet, setIsLocalSignatureSet] = useState(false);
const state = useMemo<SignatureFieldState>(() => { const state = useMemo<SignatureFieldState>(() => {
if (!field.inserted) { if (!field.inserted) {
@@ -61,9 +62,16 @@ export const SignatureField = ({ field, recipient }: SignatureFieldProps) => {
return 'signed-text'; return 'signed-text';
}, [field.inserted, signature?.signatureImageAsBase64]); }, [field.inserted, signature?.signatureImageAsBase64]);
useEffect(() => {
if (!showSignatureModal && !isLocalSignatureSet) {
setLocalSignature(null);
}
}, [showSignatureModal, isLocalSignatureSet]);
const onSign = async (source: 'local' | 'provider' = 'provider') => { const onSign = async (source: 'local' | 'provider' = 'provider') => {
try { try {
if (!providedSignature && !localSignature) { if (!providedSignature && !localSignature) {
setIsLocalSignatureSet(false);
setShowSignatureModal(true); setShowSignatureModal(true);
return; return;
} }
@@ -178,6 +186,7 @@ export const SignatureField = ({ field, recipient }: SignatureFieldProps) => {
disabled={!localSignature} disabled={!localSignature}
onClick={() => { onClick={() => {
setShowSignatureModal(false); setShowSignatureModal(false);
setIsLocalSignatureSet(true);
void onSign('local'); void onSign('local');
}} }}
> >

View File

@@ -117,7 +117,8 @@ export const ProfileForm = ({ className, user }: ProfileFormProps) => {
name="signature" name="signature"
render={({ field: { onChange } }) => ( render={({ field: { onChange } }) => (
<SignaturePad <SignaturePad
className="h-44 w-full rounded-lg border bg-white backdrop-blur-sm dark:border-[#e2d7c5] dark:bg-[#fcf8ee]" className="h-44 w-full"
containerClassName="rounded-lg border bg-background"
defaultValue={user.signature ?? undefined} defaultValue={user.signature ?? undefined}
onChange={(v) => onChange(v ?? '')} onChange={(v) => onChange(v ?? '')}
/> />

View File

@@ -147,7 +147,8 @@ export const SignUpForm = ({ className }: SignUpFormProps) => {
name="signature" name="signature"
render={({ field: { onChange } }) => ( render={({ field: { onChange } }) => (
<SignaturePad <SignaturePad
className="mt-2 h-36 w-full rounded-lg border bg-white dark:border-[#e2d7c5] dark:bg-[#fcf8ee]" className="h-36 w-full"
containerClassName="mt-2 rounded-lg border bg-background"
onChange={(v) => onChange(v ?? '')} onChange={(v) => onChange(v ?? '')}
/> />
)} )}

17
package-lock.json generated
View File

@@ -15,8 +15,8 @@
"devDependencies": { "devDependencies": {
"@commitlint/cli": "^17.7.1", "@commitlint/cli": "^17.7.1",
"@commitlint/config-conventional": "^17.7.0", "@commitlint/config-conventional": "^17.7.0",
"dotenv": "^16.0.3", "dotenv": "^16.3.1",
"dotenv-cli": "^7.2.1", "dotenv-cli": "^7.3.0",
"eslint": "^8.40.0", "eslint": "^8.40.0",
"eslint-config-custom": "*", "eslint-config-custom": "*",
"husky": "^8.0.0", "husky": "^8.0.0",
@@ -9103,7 +9103,6 @@
"version": "16.3.1", "version": "16.3.1",
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.3.1.tgz", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.3.1.tgz",
"integrity": "sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ==", "integrity": "sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ==",
"dev": true,
"engines": { "engines": {
"node": ">=12" "node": ">=12"
}, },
@@ -9112,13 +9111,12 @@
} }
}, },
"node_modules/dotenv-cli": { "node_modules/dotenv-cli": {
"version": "7.2.1", "version": "7.3.0",
"resolved": "https://registry.npmjs.org/dotenv-cli/-/dotenv-cli-7.2.1.tgz", "resolved": "https://registry.npmjs.org/dotenv-cli/-/dotenv-cli-7.3.0.tgz",
"integrity": "sha512-ODHbGTskqRtXAzZapDPvgNuDVQApu4oKX8lZW7Y0+9hKA6le1ZJlyRS687oU9FXjOVEDU/VFV6zI125HzhM1UQ==", "integrity": "sha512-314CA4TyK34YEJ6ntBf80eUY+t1XaFLyem1k9P0sX1gn30qThZ5qZr/ZwE318gEnzyYP9yj9HJk6SqwE0upkfw==",
"dev": true,
"dependencies": { "dependencies": {
"cross-spawn": "^7.0.3", "cross-spawn": "^7.0.3",
"dotenv": "^16.0.0", "dotenv": "^16.3.0",
"dotenv-expand": "^10.0.0", "dotenv-expand": "^10.0.0",
"minimist": "^1.2.6" "minimist": "^1.2.6"
}, },
@@ -9130,7 +9128,6 @@
"version": "10.0.0", "version": "10.0.0",
"resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-10.0.0.tgz", "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-10.0.0.tgz",
"integrity": "sha512-GopVGCpVS1UKH75VKHGuQFqS1Gusej0z4FyQkPdwjil2gNIv+LNsqBlboOzpJFZKVT95GkCyWJbBSdFEFUWI2A==", "integrity": "sha512-GopVGCpVS1UKH75VKHGuQFqS1Gusej0z4FyQkPdwjil2gNIv+LNsqBlboOzpJFZKVT95GkCyWJbBSdFEFUWI2A==",
"dev": true,
"engines": { "engines": {
"node": ">=12" "node": ">=12"
} }
@@ -19889,6 +19886,8 @@
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@prisma/client": "5.3.1", "@prisma/client": "5.3.1",
"dotenv": "^16.3.1",
"dotenv-cli": "^7.3.0",
"prisma": "5.3.1" "prisma": "5.3.1"
}, },
"devDependencies": { "devDependencies": {

View File

@@ -2,6 +2,7 @@
"private": true, "private": true,
"scripts": { "scripts": {
"build": "turbo run build", "build": "turbo run build",
"build:web": "turbo run build --filter=@documenso/web",
"dev": "turbo run dev --filter=@documenso/web --filter=@documenso/marketing", "dev": "turbo run dev --filter=@documenso/web --filter=@documenso/marketing",
"start": "cd apps && cd web && next start", "start": "cd apps && cd web && next start",
"lint": "turbo run lint", "lint": "turbo run lint",
@@ -10,9 +11,12 @@
"commitlint": "commitlint --edit", "commitlint": "commitlint --edit",
"clean": "turbo run clean && rimraf node_modules", "clean": "turbo run clean && rimraf node_modules",
"d": "npm run dx && npm run dev", "d": "npm run dx && npm run dev",
"dx": "npm i && npm run dx:up && npm run prisma:migrate-dev -w @documenso/prisma", "dx": "npm i && npm run dx:up && npm run prisma:migrate-dev",
"dx:up": "docker compose -f docker/compose-services.yml up -d", "dx:up": "docker compose -f docker/compose-services.yml up -d",
"dx:down": "docker compose -f docker/compose-services.yml down" "dx:down": "docker compose -f docker/compose-services.yml down",
"prisma:migrate-dev": "npm run with:env -- npm run prisma:migrate-dev -w @documenso/prisma",
"prisma:migrate-deploy": "npm run with:env -- npm run prisma:migrate-deploy -w @documenso/prisma",
"with:env": "dotenv -e .env -e .env.local --"
}, },
"engines": { "engines": {
"npm": ">=8.6.0", "npm": ">=8.6.0",
@@ -21,8 +25,8 @@
"devDependencies": { "devDependencies": {
"@commitlint/cli": "^17.7.1", "@commitlint/cli": "^17.7.1",
"@commitlint/config-conventional": "^17.7.0", "@commitlint/config-conventional": "^17.7.0",
"dotenv": "^16.0.3", "dotenv": "^16.3.1",
"dotenv-cli": "^7.2.1", "dotenv-cli": "^7.3.0",
"eslint": "^8.40.0", "eslint": "^8.40.0",
"eslint-config-custom": "*", "eslint-config-custom": "*",
"husky": "^8.0.0", "husky": "^8.0.0",

View File

@@ -60,26 +60,17 @@ export const calculateTextScaleSize = (
*/ */
export function useElementScaleSize( export function useElementScaleSize(
container: { width: number; height: number }, container: { width: number; height: number },
child: RefObject<HTMLElement | null>, text: string,
fontSize: number, fontSize: number,
fontFamily: string, fontFamily: string,
) { ) {
const [scalingFactor, setScalingFactor] = useState(1); const [scalingFactor, setScalingFactor] = useState(1);
useEffect(() => { useEffect(() => {
if (!child.current) { const scaleSize = calculateTextScaleSize(container, text, `${fontSize}px`, fontFamily);
return;
}
const scaleSize = calculateTextScaleSize(
container,
child.current.innerText,
`${fontSize}px`,
fontFamily,
);
setScalingFactor(scaleSize); setScalingFactor(scaleSize);
}, [child, container, fontFamily, fontSize]); }, [text, container, fontFamily, fontSize]);
return scalingFactor; return scalingFactor;
} }

View File

@@ -0,0 +1,13 @@
'use server';
import { prisma } from '@documenso/prisma';
import { DocumentStatus } from '@documenso/prisma/client';
export type DeleteDraftDocumentOptions = {
id: number;
userId: number;
};
export const deleteDraftDocument = async ({ id, userId }: DeleteDraftDocumentOptions) => {
return await prisma.document.delete({ where: { id, userId, status: DocumentStatus.DRAFT } });
};

View File

@@ -0,0 +1,5 @@
-- DropForeignKey
ALTER TABLE "DocumentShareLink" DROP CONSTRAINT "DocumentShareLink_documentId_fkey";
-- AddForeignKey
ALTER TABLE "DocumentShareLink" ADD CONSTRAINT "DocumentShareLink_documentId_fkey" FOREIGN KEY ("documentId") REFERENCES "Document"("id") ON DELETE CASCADE ON UPDATE CASCADE;

View File

@@ -18,6 +18,8 @@
}, },
"dependencies": { "dependencies": {
"@prisma/client": "5.3.1", "@prisma/client": "5.3.1",
"dotenv": "^16.3.1",
"dotenv-cli": "^7.3.0",
"prisma": "5.3.1" "prisma": "5.3.1"
}, },
"devDependencies": { "devDependencies": {

View File

@@ -219,7 +219,7 @@ model DocumentShareLink {
createdAt DateTime @default(now()) createdAt DateTime @default(now())
updatedAt DateTime @updatedAt updatedAt DateTime @updatedAt
document Document @relation(fields: [documentId], references: [id]) document Document @relation(fields: [documentId], references: [id], onDelete: Cascade)
@@unique([documentId, email]) @@unique([documentId, email])
} }

View File

@@ -1,6 +1,7 @@
import { TRPCError } from '@trpc/server'; import { TRPCError } from '@trpc/server';
import { createDocument } from '@documenso/lib/server-only/document/create-document'; import { createDocument } from '@documenso/lib/server-only/document/create-document';
import { deleteDraftDocument } from '@documenso/lib/server-only/document/delete-draft-document';
import { getDocumentById } from '@documenso/lib/server-only/document/get-document-by-id'; import { getDocumentById } from '@documenso/lib/server-only/document/get-document-by-id';
import { getDocumentAndSenderByToken } from '@documenso/lib/server-only/document/get-document-by-token'; import { getDocumentAndSenderByToken } from '@documenso/lib/server-only/document/get-document-by-token';
import { sendDocument } from '@documenso/lib/server-only/document/send-document'; import { sendDocument } from '@documenso/lib/server-only/document/send-document';
@@ -10,6 +11,7 @@ import { setRecipientsForDocument } from '@documenso/lib/server-only/recipient/s
import { authenticatedProcedure, procedure, router } from '../trpc'; import { authenticatedProcedure, procedure, router } from '../trpc';
import { import {
ZCreateDocumentMutationSchema, ZCreateDocumentMutationSchema,
ZDeleteDraftDocumentMutationSchema,
ZGetDocumentByIdQuerySchema, ZGetDocumentByIdQuerySchema,
ZGetDocumentByTokenQuerySchema, ZGetDocumentByTokenQuerySchema,
ZSendDocumentMutationSchema, ZSendDocumentMutationSchema,
@@ -76,6 +78,25 @@ export const documentRouter = router({
} }
}), }),
deleteDraftDocument: authenticatedProcedure
.input(ZDeleteDraftDocumentMutationSchema)
.mutation(async ({ input, ctx }) => {
try {
const { id } = input;
const userId = ctx.user.id;
return await deleteDraftDocument({ id, userId });
} catch (err) {
console.error(err);
throw new TRPCError({
code: 'BAD_REQUEST',
message: 'We were unable to delete this document. Please try again later.',
});
}
}),
setRecipientsForDocument: authenticatedProcedure setRecipientsForDocument: authenticatedProcedure
.input(ZSetRecipientsForDocumentMutationSchema) .input(ZSetRecipientsForDocumentMutationSchema)
.mutation(async ({ input, ctx }) => { .mutation(async ({ input, ctx }) => {

View File

@@ -61,3 +61,9 @@ export const ZSendDocumentMutationSchema = z.object({
}); });
export type TSendDocumentMutationSchema = z.infer<typeof ZSendDocumentMutationSchema>; export type TSendDocumentMutationSchema = z.infer<typeof ZSendDocumentMutationSchema>;
export const ZDeleteDraftDocumentMutationSchema = z.object({
id: z.number().min(1),
});
export type TDeleteDraftDocumentMutationSchema = z.infer<typeof ZDeleteDraftDocumentMutationSchema>;

View File

@@ -6,7 +6,9 @@ import { shareLinkRouter } from './share-link-router/router';
import { procedure, router } from './trpc'; import { procedure, router } from './trpc';
export const appRouter = router({ export const appRouter = router({
hello: procedure.query(() => 'Hello, world!'), health: procedure.query(() => {
return { status: 'ok' };
}),
auth: authRouter, auth: authRouter,
profile: profileRouter, profile: profileRouter,
document: documentRouter, document: documentRouter,

View File

@@ -56,7 +56,7 @@ export const SigningCard3D = ({ className, name, signingCelebrationImage }: Sign
const sheenGradient = useMotionTemplate`linear-gradient( const sheenGradient = useMotionTemplate`linear-gradient(
30deg, 30deg,
transparent, transparent,
rgba(var(--sheen-color) / ${trackMouse ? sheenOpacity : 0}) ${sheenPosition}%, rgba(var(--sheen-color) / ${sheenOpacity}) ${sheenPosition}%,
transparent)`; transparent)`;
const cardRef = useRef<HTMLDivElement>(null); const cardRef = useRef<HTMLDivElement>(null);
@@ -98,10 +98,12 @@ export const SigningCard3D = ({ className, name, signingCelebrationImage }: Sign
void animate(cardX, 0, { duration: 2, ease: 'backInOut' }); void animate(cardX, 0, { duration: 2, ease: 'backInOut' });
void animate(cardY, 0, { duration: 2, ease: 'backInOut' }); void animate(cardY, 0, { duration: 2, ease: 'backInOut' });
void animate(sheenOpacity, 0, { duration: 2, ease: 'backInOut' });
setTrackMouse(false); setTrackMouse(false);
}, 1000); }, 1000);
}, },
[cardX, cardY, cardCenterPosition, trackMouse], [cardX, cardY, cardCenterPosition, trackMouse, sheenOpacity],
); );
useEffect(() => { useEffect(() => {
@@ -126,7 +128,6 @@ export const SigningCard3D = ({ className, name, signingCelebrationImage }: Sign
transformStyle: 'preserve-3d', transformStyle: 'preserve-3d',
rotateX, rotateX,
rotateY, rotateY,
// willChange: 'transform background-image',
}} }}
> >
<SigningCardContent className="bg-transparent" name={name} /> <SigningCardContent className="bg-transparent" name={name} />

View File

@@ -16,12 +16,13 @@ const DialogPortal = ({
children, children,
position = 'start', position = 'start',
...props ...props
}: DialogPrimitive.DialogPortalProps & { position?: 'start' | 'end' }) => ( }: DialogPrimitive.DialogPortalProps & { position?: 'start' | 'end' | 'center' }) => (
<DialogPrimitive.Portal className={cn(className)} {...props}> <DialogPrimitive.Portal className={cn(className)} {...props}>
<div <div
className={cn('fixed inset-0 z-50 flex justify-center sm:items-center', { className={cn('fixed inset-0 z-50 flex justify-center sm:items-center', {
'items-start': position === 'start', 'items-start': position === 'start',
'items-end': position === 'end', 'items-end': position === 'end',
'items-center': position === 'center',
})} })}
> >
{children} {children}
@@ -49,7 +50,9 @@ DialogOverlay.displayName = DialogPrimitive.Overlay.displayName;
const DialogContent = React.forwardRef< const DialogContent = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Content>, React.ElementRef<typeof DialogPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content> & { position?: 'start' | 'end' } React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content> & {
position?: 'start' | 'end' | 'center';
}
>(({ className, children, position = 'start', ...props }, ref) => ( >(({ className, children, position = 'start', ...props }, ref) => (
<DialogPortal position={position}> <DialogPortal position={position}>
<DialogOverlay /> <DialogOverlay />

View File

@@ -70,25 +70,23 @@ export function SinglePlayerModeSignatureField({
throw new Error('Invalid field type'); throw new Error('Invalid field type');
} }
const $paragraphEl = useRef<HTMLParagraphElement>(null);
const { height, width } = useFieldPageCoords(field); const { height, width } = useFieldPageCoords(field);
const insertedBase64Signature = field.inserted && field.Signature?.signatureImageAsBase64;
const insertedTypeSignature = field.inserted && field.Signature?.typedSignature;
const scalingFactor = useElementScaleSize( const scalingFactor = useElementScaleSize(
{ {
height, height,
width, width,
}, },
$paragraphEl, insertedTypeSignature || '',
maxFontSize, maxFontSize,
fontVariableValue, fontVariableValue,
); );
const fontSize = maxFontSize * scalingFactor; const fontSize = maxFontSize * scalingFactor;
const insertedBase64Signature = field.inserted && field.Signature?.signatureImageAsBase64;
const insertedTypeSignature = field.inserted && field.Signature?.typedSignature;
return ( return (
<SinglePlayerModeFieldCardContainer field={field}> <SinglePlayerModeFieldCardContainer field={field}>
{insertedBase64Signature ? ( {insertedBase64Signature ? (
@@ -99,7 +97,6 @@ export function SinglePlayerModeSignatureField({
/> />
) : insertedTypeSignature ? ( ) : insertedTypeSignature ? (
<p <p
ref={$paragraphEl}
style={{ style={{
fontSize: `clamp(${minFontSize}px, ${fontSize}px, ${maxFontSize}px)`, fontSize: `clamp(${minFontSize}px, ${fontSize}px, ${maxFontSize}px)`,
fontFamily: `var(${fontVariable})`, fontFamily: `var(${fontVariable})`,
@@ -145,7 +142,7 @@ export function SinglePlayerModeCustomTextField({
height, height,
width, width,
}, },
$paragraphEl, field.customText,
maxFontSize, maxFontSize,
fontVariableValue, fontVariableValue,
); );

View File

@@ -22,10 +22,12 @@ const DPI = 2;
export type SignaturePadProps = Omit<HTMLAttributes<HTMLCanvasElement>, 'onChange'> & { export type SignaturePadProps = Omit<HTMLAttributes<HTMLCanvasElement>, 'onChange'> & {
onChange?: (_signatureDataUrl: string | null) => void; onChange?: (_signatureDataUrl: string | null) => void;
containerClassName?: string;
}; };
export const SignaturePad = ({ export const SignaturePad = ({
className, className,
containerClassName,
defaultValue, defaultValue,
onChange, onChange,
...props ...props
@@ -210,7 +212,7 @@ export const SignaturePad = ({
}, [defaultValue]); }, [defaultValue]);
return ( return (
<div className="relative block"> <div className={cn('relative block', containerClassName)}>
<canvas <canvas
ref={$el} ref={$el}
className={cn('relative block dark:invert', className)} className={cn('relative block dark:invert', className)}
@@ -226,7 +228,7 @@ export const SignaturePad = ({
<div className="absolute bottom-4 right-4"> <div className="absolute bottom-4 right-4">
<button <button
type="button" type="button"
className="focus-visible:ring-ring ring-offset-background rounded-full p-0 text-xs text-slate-500 focus-visible:outline-none focus-visible:ring-2" className="focus-visible:ring-ring ring-offset-background text-muted-foreground rounded-full p-0 text-xs focus-visible:outline-none focus-visible:ring-2"
onClick={() => onClearClick()} onClick={() => onClearClick()}
> >
Clear Signature Clear Signature

103
render.yaml Normal file
View File

@@ -0,0 +1,103 @@
services:
- type: web
name: documenso-app
env: node
plan: free
buildCommand: npm i && npm run build:web
startCommand: npx prisma migrate deploy --schema packages/prisma/schema.prisma && npm run start
healthCheckPath: /api/trpc/health
envVars:
# Node Version
- key: NODE_VERSION
value: 18.17.0
- key: PORT
value: 10000
# Auth
- key: NEXTAUTH_URL
fromService:
name: documenso-app
type: web
envVarKey: RENDER_EXTERNAL_URL
- key: NEXTAUTH_SECRET
generateValue: true
# Database
- key: NEXT_PRIVATE_DATABASE_URL
fromDatabase:
name: documenso-db
property: connectionString
- key: NEXT_PRIVATE_DIRECT_DATABASE_URL
fromDatabase:
name: documenso-db
property: connectionString
# URLs
- key: NEXT_PUBLIC_WEBAPP_URL
fromService:
name: documenso-app
type: web
envVarKey: RENDER_EXTERNAL_URL
- key: NEXT_PUBLIC_MARKETING_URL
value: 'http://localhost:3001'
# SMTP
- key: NEXT_PRIVATE_SMTP_TRANSPORT
value: 'smtp-auth'
- key: NEXT_PRIVATE_SMTP_HOST
sync: false
- key: NEXT_PRIVATE_SMTP_PORT
sync: false
- key: NEXT_PRIVATE_SMTP_USERNAME
sync: false
- key: NEXT_PRIVATE_SMTP_PASSWORD
sync: false
- key: NEXT_PRIVATE_SMTP_FROM_NAME
sync: false
- key: NEXT_PRIVATE_SMTP_FROM_ADDRESS
sync: false
# Stripe
- key: NEXT_PRIVATE_STRIPE_API_KEY
sync: false
- key: NEXT_PRIVATE_STRIPE_WEBHOOK_SECRET
sync: false
- key: NEXT_PUBLIC_STRIPE_COMMUNITY_PLAN_MONTHLY_PRICE_ID
sync: false
- key: NEXT_PUBLIC_STRIPE_COMMUNITY_PLAN_YEARLY_PRICE_ID
sync: false
# Features
- key: NEXT_PUBLIC_POSTHOG_KEY
sync: false
- key: NEXT_PUBLIC_POSTHOG_HOST
value: 'https://eu.posthog.com'
- key: NEXT_PUBLIC_FEATURE_BILLING_ENABLED
sync: false
# Redis (Only required for marketing site, but added for completeness)
- key: NEXT_PRIVATE_REDIS_URL
sync: false
- key: NEXT_PRIVATE_REDIS_TOKEN
sync: false
# Storage
- key: NEXT_PUBLIC_UPLOAD_TRANSPORT
value: 'database'
- key: NEXT_PRIVATE_UPLOAD_ENDPOINT
sync: false
- key: NEXT_PRIVATE_UPLOAD_REGION
sync: false
- key: NEXT_PRIVATE_UPLOAD_BUCKET
sync: false
- key: NEXT_PRIVATE_UPLOAD_ACCESS_KEY_ID
sync: false
- key: NEXT_PRIVATE_UPLOAD_SECRET_ACCESS_KEY
sync: false
databases:
- name: documenso-db
plan: free