Compare commits
31 Commits
chore/gith
...
fix/cascad
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f728dd13c5 | ||
|
|
c803d2c4ba | ||
|
|
eb5f5f7a90 | ||
|
|
2ea5ff2c94 | ||
|
|
565602f8e1 | ||
|
|
e0271cace3 | ||
|
|
cc8c4b8297 | ||
|
|
a287aab4f4 | ||
|
|
b5ed703553 | ||
|
|
f49880125a | ||
|
|
8380c357d9 | ||
|
|
4e010c5624 | ||
|
|
f53cdbace9 | ||
|
|
b4d04e2ce9 | ||
|
|
2470aeee1f | ||
|
|
fd07b47325 | ||
|
|
9257a05831 | ||
|
|
1faa6f2944 | ||
|
|
cc65537ea3 | ||
|
|
04a80b7c03 | ||
|
|
c71a89d1b7 | ||
|
|
e2abfd2312 | ||
|
|
0dadec3b8d | ||
|
|
e2d8591d66 | ||
|
|
b6f9d70fec | ||
|
|
7c54913bf5 | ||
|
|
ddf097ede3 | ||
|
|
1bad85e1d6 | ||
|
|
68458b50d2 | ||
|
|
4cc34ec50a | ||
|
|
f637381198 |
@@ -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
|
||||||
|
|||||||
6
.github/dependabot.yml
vendored
6
.github/dependabot.yml
vendored
@@ -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
55
.gitpod.yml
Normal 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
|
||||||
18
README.md
18
README.md
@@ -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
|
||||||
|
|
||||||

|

|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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',
|
||||||
@@ -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' },
|
||||||
|
|||||||
@@ -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!
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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',
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -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>
|
||||||
{[
|
{[
|
||||||
|
|||||||
@@ -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');
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -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 ?? '')}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -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
17
package-lock.json
generated
@@ -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": {
|
||||||
|
|||||||
12
package.json
12
package.json
@@ -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",
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
13
packages/lib/server-only/document/delete-draft-document.ts
Normal file
13
packages/lib/server-only/document/delete-draft-document.ts
Normal 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 } });
|
||||||
|
};
|
||||||
@@ -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;
|
||||||
@@ -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": {
|
||||||
|
|||||||
@@ -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])
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 }) => {
|
||||||
|
|||||||
@@ -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>;
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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} />
|
||||||
|
|||||||
@@ -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 />
|
||||||
|
|||||||
@@ -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,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -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
103
render.yaml
Normal 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
|
||||||
Reference in New Issue
Block a user