diff --git a/.devcontainer/on-create.sh b/.devcontainer/on-create.sh index a66491ef7..fbd5351f2 100755 --- a/.devcontainer/on-create.sh +++ b/.devcontainer/on-create.sh @@ -9,10 +9,5 @@ npm install # Copy the env file cp .env.example .env -# Source the env file, export the variables -set -a -source .env -set +a - # Run the migrations -npm run -w @documenso/prisma prisma:migrate-dev +npm run prisma:migrate-dev diff --git a/.github/ISSUE_TEMPLATE/bug-report.md b/.github/ISSUE_TEMPLATE/bug-report.md new file mode 100644 index 000000000..d12bdad59 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug-report.md @@ -0,0 +1,50 @@ +--- +name: Bug Report +about: Create a bug report to help us improve +--- + + + +## Issue Description + + + +## Steps to Reproduce + + + + +1. Step 1 +2. Step 2 +3. ... + +## Expected Behavior + + + +## Current Behavior + + + +## Screenshots (optional) + + + +## Environment + + + +- OS: [e.g., Windows 10] +- Browser: [e.g., Chrome, Firefox] +- Version: [e.g., 2.0.1] + +## Checklist + + + + +- [ ] I have searched the existing issues to make sure this is not a duplicate. +- [ ] I have provided steps to reproduce the issue. +- [ ] I have included relevant environment information. +- [ ] I have included any relevant screenshots. +- [ ] I understand that this is a voluntary contribution and that there is no guarantee of resolution. diff --git a/.github/ISSUE_TEMPLATE/feature-request.md b/.github/ISSUE_TEMPLATE/feature-request.md new file mode 100644 index 000000000..a850a7a9a --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature-request.md @@ -0,0 +1,41 @@ +--- +name: Feature Request +about: Suggest a new idea or enhancement for this project +--- + + + +## Feature Description + + + + +## Use Case + + + + +## Proposed Solution + + + + +## Alternatives (optional) + + + + +## Additional Context + + + +## Checklist + + + + +- [ ] I have searched the existing feature requests to make sure this is not a duplicate. +- [ ] I have provided a detailed description of the requested feature. +- [ ] I have explained the use case or scenario for this feature. +- [ ] I have included any relevant technical details or design suggestions. +- [ ] I understand that this is a suggestion and that there is no guarantee of implementation. diff --git a/.github/ISSUE_TEMPLATE/improvement.md b/.github/ISSUE_TEMPLATE/improvement.md new file mode 100644 index 000000000..709d3441f --- /dev/null +++ b/.github/ISSUE_TEMPLATE/improvement.md @@ -0,0 +1,41 @@ +--- +name: General Improvement +about: Suggest a minor enhancement or improvement for this project +--- + + + +## Improvement Description + + + + +## Rationale + + + + +## Proposed Solution + + + + +## Alternatives (optional) + + + + +## Additional Context + + + +## Checklist + + + + +- [ ] I have searched the existing issues and improvement suggestions to avoid duplication. +- [ ] I have provided a clear description of the improvement being suggested. +- [ ] I have explained the rationale behind this improvement. +- [ ] I have included any relevant technical details or design suggestions. +- [ ] I understand that this is a suggestion and that there is no guarantee of implementation. diff --git a/.github/PULL_REQUEST_TEMPLATE/generic.md b/.github/PULL_REQUEST_TEMPLATE/generic.md new file mode 100644 index 000000000..70b668b5c --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE/generic.md @@ -0,0 +1,49 @@ +--- +name: Pull Request +about: Submit changes to the project for review and inclusion +--- + +## Description + + + + +## Related Issue + + + + +## Changes Made + + + + +- Change 1 +- Change 2 +- ... + +## Testing Performed + + + + +- Tested feature X in scenario Y. +- Ran unit tests for component Z. +- Tested on browsers A, B, and C. +- ... + +## Checklist + + + + +- [ ] I have tested these changes locally and they work as expected. +- [ ] I have added/updated tests that prove the effectiveness of these changes. +- [ ] I have updated the documentation to reflect these changes, if applicable. +- [ ] I have followed the project's coding style guidelines. +- [ ] I have addressed the code review feedback from the previous submission, if applicable. + +## Additional Notes + + + diff --git a/.github/PULL_REQUEST_TEMPLATE/test-addition.md b/.github/PULL_REQUEST_TEMPLATE/test-addition.md new file mode 100644 index 000000000..f93c81493 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE/test-addition.md @@ -0,0 +1,40 @@ +--- +name: Test Addition +about: Submit a new test, either unit or end-to-end (E2E), for review and inclusion +--- + +## Description + + + + +## Related Issue + + + + +## Test Details + + + + +- Test Name: Name of the test +- Type: [Unit / E2E] +- Description: Brief description of what the test checks +- Inputs: What inputs the test uses (if applicable) +- Expected Output: What output or behavior the test expects + +## Checklist + + + + +- [ ] I have written the new test and ensured it works as intended. +- [ ] I have added necessary documentation to explain the purpose of the test. +- [ ] I have followed the project's testing guidelines and coding style. +- [ ] I have addressed any review feedback from previous submissions, if applicable. + +## Additional Notes + + + diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 74fcb319b..1269fd6c5 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -9,7 +9,7 @@ updates: labels: - "ci dependencies" - "ci" - open-pull-requests-limit: 2 + open-pull-requests-limit: 0 - package-ecosystem: "npm" directory: "/apps/marketing" @@ -19,7 +19,7 @@ updates: labels: - "npm dependencies" - "frontend" - open-pull-requests-limit: 2 + open-pull-requests-limit: 0 - package-ecosystem: "npm" directory: "/apps/web" @@ -29,4 +29,4 @@ updates: labels: - "npm dependencies" - "frontend" - open-pull-requests-limit: 2 + open-pull-requests-limit: 0 diff --git a/.gitpod.yml b/.gitpod.yml new file mode 100644 index 000000000..b03003160 --- /dev/null +++ b/.gitpod.yml @@ -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 diff --git a/README.md b/README.md index 2795d206d..f488b4a63 100644 --- a/README.md +++ b/README.md @@ -179,7 +179,7 @@ git clone https://github.com/documenso/documenso - NEXT_PRIVATE_SMTP_FROM_NAME - 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 @@ -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 ![Repository Activity](https://repobeats.axiom.co/api/embed/622a2e9aa709696f7226304b5b7178a5741b3868.svg) diff --git a/apps/marketing/src/app/(marketing)/layout.tsx b/apps/marketing/src/app/(marketing)/layout.tsx index 36241e8e2..365d8a5d1 100644 --- a/apps/marketing/src/app/(marketing)/layout.tsx +++ b/apps/marketing/src/app/(marketing)/layout.tsx @@ -2,6 +2,8 @@ import React, { useEffect, useState } from 'react'; +import { usePathname } from 'next/navigation'; + import { cn } from '@documenso/ui/lib/utils'; import { Footer } from '~/components/(marketing)/footer'; @@ -13,6 +15,7 @@ export type MarketingLayoutProps = { export default function MarketingLayout({ children }: MarketingLayoutProps) { const [scrollY, setScrollY] = useState(0); + const pathname = usePathname(); useEffect(() => { const onScroll = () => { @@ -25,7 +28,11 @@ export default function MarketingLayout({ children }: MarketingLayoutProps) { }, []); return ( -
+
5, diff --git a/apps/marketing/src/app/(marketing)/single-player-mode/[token]/success/page.tsx b/apps/marketing/src/app/(marketing)/singleplayer/[token]/success/page.tsx similarity index 100% rename from apps/marketing/src/app/(marketing)/single-player-mode/[token]/success/page.tsx rename to apps/marketing/src/app/(marketing)/singleplayer/[token]/success/page.tsx diff --git a/apps/marketing/src/app/(marketing)/single-player-mode/page.tsx b/apps/marketing/src/app/(marketing)/singleplayer/page.tsx similarity index 99% rename from apps/marketing/src/app/(marketing)/single-player-mode/page.tsx rename to apps/marketing/src/app/(marketing)/singleplayer/page.tsx index 3c76ebac0..0ae72c788 100644 --- a/apps/marketing/src/app/(marketing)/single-player-mode/page.tsx +++ b/apps/marketing/src/app/(marketing)/singleplayer/page.tsx @@ -130,7 +130,7 @@ export default function SinglePlayerModePage() { signer: data.email, }); - router.push(`/single-player-mode/${documentToken}/success`); + router.push(`/singleplayer/${documentToken}/success`); } catch { toast({ title: 'Something went wrong', diff --git a/apps/marketing/src/components/(marketing)/footer.tsx b/apps/marketing/src/components/(marketing)/footer.tsx index b1f5e39bf..11d368c22 100644 --- a/apps/marketing/src/components/(marketing)/footer.tsx +++ b/apps/marketing/src/components/(marketing)/footer.tsx @@ -23,7 +23,7 @@ const SOCIAL_LINKS = [ const FOOTER_LINKS = [ { href: '/pricing', text: 'Pricing' }, - { href: '/single-player-mode', text: 'Single Player Mode' }, + { href: '/singleplayer', text: 'Singleplayer' }, { href: '/blog', text: 'Blog' }, { href: '/open', text: 'Open' }, { href: 'https://shop.documenso.com', text: 'Shop', target: '_blank' }, diff --git a/apps/marketing/src/components/(marketing)/header.tsx b/apps/marketing/src/components/(marketing)/header.tsx index 117f47319..e73e3af83 100644 --- a/apps/marketing/src/components/(marketing)/header.tsx +++ b/apps/marketing/src/components/(marketing)/header.tsx @@ -35,7 +35,7 @@ export const Header = ({ className, ...props }: HeaderProps) => { {isSinglePlayerModeMarketingEnabled && ( Try now! diff --git a/apps/marketing/src/components/(marketing)/hero.tsx b/apps/marketing/src/components/(marketing)/hero.tsx index a6a24131b..4ff8fbcc2 100644 --- a/apps/marketing/src/components/(marketing)/hero.tsx +++ b/apps/marketing/src/components/(marketing)/hero.tsx @@ -134,9 +134,9 @@ export const Hero = ({ className, ...props }: HeroProps) => { variants={HeroTitleVariants} initial="initial" 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" > - +

Introducing Single Player Mode

diff --git a/apps/marketing/src/components/(marketing)/mobile-navigation.tsx b/apps/marketing/src/components/(marketing)/mobile-navigation.tsx index c9bd07631..a1897ce58 100644 --- a/apps/marketing/src/components/(marketing)/mobile-navigation.tsx +++ b/apps/marketing/src/components/(marketing)/mobile-navigation.tsx @@ -17,8 +17,8 @@ export type MobileNavigationProps = { export const MENU_NAVIGATION_LINKS = [ { - href: '/single-player-mode', - text: 'Single Player Mode', + href: '/singleplayer', + text: 'Singleplayer', }, { href: '/blog', diff --git a/apps/marketing/src/components/(marketing)/widget.tsx b/apps/marketing/src/components/(marketing)/widget.tsx index d81888463..d53f349e6 100644 --- a/apps/marketing/src/components/(marketing)/widget.tsx +++ b/apps/marketing/src/components/(marketing)/widget.tsx @@ -377,7 +377,7 @@ export const Widget = ({ className, children, ...props }: WidgetProps) => { - + Add your signature diff --git a/apps/web/src/app/(dashboard)/documents/data-table-action-dropdown.tsx b/apps/web/src/app/(dashboard)/documents/data-table-action-dropdown.tsx index ef5cb0b24..5b2a8c44c 100644 --- a/apps/web/src/app/(dashboard)/documents/data-table-action-dropdown.tsx +++ b/apps/web/src/app/(dashboard)/documents/data-table-action-dropdown.tsx @@ -1,5 +1,7 @@ 'use client'; +import { useState } from 'react'; + import Link from 'next/link'; import { @@ -29,6 +31,8 @@ import { DropdownMenuTrigger, } from '@documenso/ui/primitives/dropdown-menu'; +import { DeleteDraftDocumentDialog } from './delete-draft-document-dialog'; + export type DataTableActionDropdownProps = { row: Document & { User: Pick; @@ -41,6 +45,8 @@ export const DataTableActionDropdown = ({ row }: DataTableActionDropdownProps) = const { createAndCopyShareLink, isCopyingShareLink } = useCopyShareLink(); + const [isDeleteDialogOpen, setDeleteDialogOpen] = useState(false); + if (!session) { return null; } @@ -53,6 +59,7 @@ export const DataTableActionDropdown = ({ row }: DataTableActionDropdownProps) = // const isPending = row.status === DocumentStatus.PENDING; const isComplete = row.status === DocumentStatus.COMPLETED; // const isSigned = recipient?.signingStatus === SigningStatus.SIGNED; + const isDocumentDeletable = isOwner && row.status === DocumentStatus.DRAFT; const onDownloadClick = async () => { let document: DocumentWithData | null = null; @@ -127,7 +134,7 @@ export const DataTableActionDropdown = ({ row }: DataTableActionDropdownProps) = Void - + setDeleteDialogOpen(true)} disabled={!isDocumentDeletable}> Delete @@ -155,6 +162,14 @@ export const DataTableActionDropdown = ({ row }: DataTableActionDropdownProps) = Share + + {isDocumentDeletable && ( + + )} ); }; diff --git a/apps/web/src/app/(dashboard)/documents/delete-draft-document-dialog.tsx b/apps/web/src/app/(dashboard)/documents/delete-draft-document-dialog.tsx new file mode 100644 index 000000000..1a458a13d --- /dev/null +++ b/apps/web/src/app/(dashboard)/documents/delete-draft-document-dialog.tsx @@ -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 ( + !isLoading && onOpenChange(value)}> + + + Do you want to delete this document? + + + Please note that this action is irreversible. Once confirmed, your document will be + permanently deleted. + + + + +
+ + + +
+
+
+
+ ); +}; diff --git a/apps/web/src/app/(dashboard)/documents/page.tsx b/apps/web/src/app/(dashboard)/documents/page.tsx index d200fe262..d8a5a5bd8 100644 --- a/apps/web/src/app/(dashboard)/documents/page.tsx +++ b/apps/web/src/app/(dashboard)/documents/page.tsx @@ -66,7 +66,7 @@ export default async function DocumentsPage({ searchParams = {} }: DocumentsPage

Documents

-
+
{[ diff --git a/apps/web/src/app/(signing)/sign/[token]/signature-field.tsx b/apps/web/src/app/(signing)/sign/[token]/signature-field.tsx index 020af41c2..01923bd6c 100644 --- a/apps/web/src/app/(signing)/sign/[token]/signature-field.tsx +++ b/apps/web/src/app/(signing)/sign/[token]/signature-field.tsx @@ -1,6 +1,6 @@ 'use client'; -import { useMemo, useState, useTransition } from 'react'; +import { useEffect, useMemo, useState, useTransition } from 'react'; import { useRouter } from 'next/navigation'; @@ -48,6 +48,7 @@ export const SignatureField = ({ field, recipient }: SignatureFieldProps) => { const [showSignatureModal, setShowSignatureModal] = useState(false); const [localSignature, setLocalSignature] = useState(null); + const [isLocalSignatureSet, setIsLocalSignatureSet] = useState(false); const state = useMemo(() => { if (!field.inserted) { @@ -61,9 +62,16 @@ export const SignatureField = ({ field, recipient }: SignatureFieldProps) => { return 'signed-text'; }, [field.inserted, signature?.signatureImageAsBase64]); + useEffect(() => { + if (!showSignatureModal && !isLocalSignatureSet) { + setLocalSignature(null); + } + }, [showSignatureModal, isLocalSignatureSet]); + const onSign = async (source: 'local' | 'provider' = 'provider') => { try { if (!providedSignature && !localSignature) { + setIsLocalSignatureSet(false); setShowSignatureModal(true); return; } @@ -178,6 +186,7 @@ export const SignatureField = ({ field, recipient }: SignatureFieldProps) => { disabled={!localSignature} onClick={() => { setShowSignatureModal(false); + setIsLocalSignatureSet(true); void onSign('local'); }} > diff --git a/apps/web/src/components/forms/profile.tsx b/apps/web/src/components/forms/profile.tsx index 0082147b4..0630bfdd1 100644 --- a/apps/web/src/components/forms/profile.tsx +++ b/apps/web/src/components/forms/profile.tsx @@ -117,7 +117,8 @@ export const ProfileForm = ({ className, user }: ProfileFormProps) => { name="signature" render={({ field: { onChange } }) => ( onChange(v ?? '')} /> diff --git a/apps/web/src/components/forms/signup.tsx b/apps/web/src/components/forms/signup.tsx index 7d8100c26..a1f3ab81e 100644 --- a/apps/web/src/components/forms/signup.tsx +++ b/apps/web/src/components/forms/signup.tsx @@ -147,7 +147,8 @@ export const SignUpForm = ({ className }: SignUpFormProps) => { name="signature" render={({ field: { onChange } }) => ( onChange(v ?? '')} /> )} diff --git a/package-lock.json b/package-lock.json index 46ff2f745..8fb6594de 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,8 +15,8 @@ "devDependencies": { "@commitlint/cli": "^17.7.1", "@commitlint/config-conventional": "^17.7.0", - "dotenv": "^16.0.3", - "dotenv-cli": "^7.2.1", + "dotenv": "^16.3.1", + "dotenv-cli": "^7.3.0", "eslint": "^8.40.0", "eslint-config-custom": "*", "husky": "^8.0.0", @@ -9103,7 +9103,6 @@ "version": "16.3.1", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.3.1.tgz", "integrity": "sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ==", - "dev": true, "engines": { "node": ">=12" }, @@ -9112,13 +9111,12 @@ } }, "node_modules/dotenv-cli": { - "version": "7.2.1", - "resolved": "https://registry.npmjs.org/dotenv-cli/-/dotenv-cli-7.2.1.tgz", - "integrity": "sha512-ODHbGTskqRtXAzZapDPvgNuDVQApu4oKX8lZW7Y0+9hKA6le1ZJlyRS687oU9FXjOVEDU/VFV6zI125HzhM1UQ==", - "dev": true, + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/dotenv-cli/-/dotenv-cli-7.3.0.tgz", + "integrity": "sha512-314CA4TyK34YEJ6ntBf80eUY+t1XaFLyem1k9P0sX1gn30qThZ5qZr/ZwE318gEnzyYP9yj9HJk6SqwE0upkfw==", "dependencies": { "cross-spawn": "^7.0.3", - "dotenv": "^16.0.0", + "dotenv": "^16.3.0", "dotenv-expand": "^10.0.0", "minimist": "^1.2.6" }, @@ -9130,7 +9128,6 @@ "version": "10.0.0", "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-10.0.0.tgz", "integrity": "sha512-GopVGCpVS1UKH75VKHGuQFqS1Gusej0z4FyQkPdwjil2gNIv+LNsqBlboOzpJFZKVT95GkCyWJbBSdFEFUWI2A==", - "dev": true, "engines": { "node": ">=12" } @@ -19889,6 +19886,8 @@ "license": "MIT", "dependencies": { "@prisma/client": "5.3.1", + "dotenv": "^16.3.1", + "dotenv-cli": "^7.3.0", "prisma": "5.3.1" }, "devDependencies": { diff --git a/package.json b/package.json index bb574f3ca..57fcb5998 100644 --- a/package.json +++ b/package.json @@ -2,6 +2,7 @@ "private": true, "scripts": { "build": "turbo run build", + "build:web": "turbo run build --filter=@documenso/web", "dev": "turbo run dev --filter=@documenso/web --filter=@documenso/marketing", "start": "cd apps && cd web && next start", "lint": "turbo run lint", @@ -10,9 +11,12 @@ "commitlint": "commitlint --edit", "clean": "turbo run clean && rimraf node_modules", "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: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": { "npm": ">=8.6.0", @@ -21,8 +25,8 @@ "devDependencies": { "@commitlint/cli": "^17.7.1", "@commitlint/config-conventional": "^17.7.0", - "dotenv": "^16.0.3", - "dotenv-cli": "^7.2.1", + "dotenv": "^16.3.1", + "dotenv-cli": "^7.3.0", "eslint": "^8.40.0", "eslint-config-custom": "*", "husky": "^8.0.0", diff --git a/packages/lib/client-only/hooks/use-element-scale-size.ts b/packages/lib/client-only/hooks/use-element-scale-size.ts index 3e9b34b3f..1c8ab320e 100644 --- a/packages/lib/client-only/hooks/use-element-scale-size.ts +++ b/packages/lib/client-only/hooks/use-element-scale-size.ts @@ -60,26 +60,17 @@ export const calculateTextScaleSize = ( */ export function useElementScaleSize( container: { width: number; height: number }, - child: RefObject, + text: string, fontSize: number, fontFamily: string, ) { const [scalingFactor, setScalingFactor] = useState(1); useEffect(() => { - if (!child.current) { - return; - } - - const scaleSize = calculateTextScaleSize( - container, - child.current.innerText, - `${fontSize}px`, - fontFamily, - ); + const scaleSize = calculateTextScaleSize(container, text, `${fontSize}px`, fontFamily); setScalingFactor(scaleSize); - }, [child, container, fontFamily, fontSize]); + }, [text, container, fontFamily, fontSize]); return scalingFactor; } diff --git a/packages/lib/server-only/document/delete-draft-document.ts b/packages/lib/server-only/document/delete-draft-document.ts new file mode 100644 index 000000000..6b0bc3511 --- /dev/null +++ b/packages/lib/server-only/document/delete-draft-document.ts @@ -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 } }); +}; diff --git a/packages/prisma/package.json b/packages/prisma/package.json index 1b12a18a4..efd494ed6 100644 --- a/packages/prisma/package.json +++ b/packages/prisma/package.json @@ -18,6 +18,8 @@ }, "dependencies": { "@prisma/client": "5.3.1", + "dotenv": "^16.3.1", + "dotenv-cli": "^7.3.0", "prisma": "5.3.1" }, "devDependencies": { diff --git a/packages/trpc/server/document-router/router.ts b/packages/trpc/server/document-router/router.ts index e436bb391..6d19afc0b 100644 --- a/packages/trpc/server/document-router/router.ts +++ b/packages/trpc/server/document-router/router.ts @@ -1,6 +1,7 @@ import { TRPCError } from '@trpc/server'; 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 { getDocumentAndSenderByToken } from '@documenso/lib/server-only/document/get-document-by-token'; 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 { ZCreateDocumentMutationSchema, + ZDeleteDraftDocumentMutationSchema, ZGetDocumentByIdQuerySchema, ZGetDocumentByTokenQuerySchema, 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 .input(ZSetRecipientsForDocumentMutationSchema) .mutation(async ({ input, ctx }) => { diff --git a/packages/trpc/server/document-router/schema.ts b/packages/trpc/server/document-router/schema.ts index c95417306..e5b27c0ea 100644 --- a/packages/trpc/server/document-router/schema.ts +++ b/packages/trpc/server/document-router/schema.ts @@ -61,3 +61,9 @@ export const ZSendDocumentMutationSchema = z.object({ }); export type TSendDocumentMutationSchema = z.infer; + +export const ZDeleteDraftDocumentMutationSchema = z.object({ + id: z.number().min(1), +}); + +export type TDeleteDraftDocumentMutationSchema = z.infer; diff --git a/packages/trpc/server/router.ts b/packages/trpc/server/router.ts index 6f9fc7660..b4c65b1d4 100644 --- a/packages/trpc/server/router.ts +++ b/packages/trpc/server/router.ts @@ -6,7 +6,9 @@ import { shareLinkRouter } from './share-link-router/router'; import { procedure, router } from './trpc'; export const appRouter = router({ - hello: procedure.query(() => 'Hello, world!'), + health: procedure.query(() => { + return { status: 'ok' }; + }), auth: authRouter, profile: profileRouter, document: documentRouter, diff --git a/packages/ui/components/signing-card.tsx b/packages/ui/components/signing-card.tsx index 24a6b6e28..3c65ba9dc 100644 --- a/packages/ui/components/signing-card.tsx +++ b/packages/ui/components/signing-card.tsx @@ -56,7 +56,7 @@ export const SigningCard3D = ({ className, name, signingCelebrationImage }: Sign const sheenGradient = useMotionTemplate`linear-gradient( 30deg, transparent, - rgba(var(--sheen-color) / ${trackMouse ? sheenOpacity : 0}) ${sheenPosition}%, + rgba(var(--sheen-color) / ${sheenOpacity}) ${sheenPosition}%, transparent)`; const cardRef = useRef(null); @@ -98,10 +98,12 @@ export const SigningCard3D = ({ className, name, signingCelebrationImage }: Sign void animate(cardX, 0, { duration: 2, ease: 'backInOut' }); void animate(cardY, 0, { duration: 2, ease: 'backInOut' }); + void animate(sheenOpacity, 0, { duration: 2, ease: 'backInOut' }); + setTrackMouse(false); }, 1000); }, - [cardX, cardY, cardCenterPosition, trackMouse], + [cardX, cardY, cardCenterPosition, trackMouse, sheenOpacity], ); useEffect(() => { @@ -126,7 +128,6 @@ export const SigningCard3D = ({ className, name, signingCelebrationImage }: Sign transformStyle: 'preserve-3d', rotateX, rotateY, - // willChange: 'transform background-image', }} > diff --git a/packages/ui/primitives/dialog.tsx b/packages/ui/primitives/dialog.tsx index 8a0d8b21e..a67e35b03 100644 --- a/packages/ui/primitives/dialog.tsx +++ b/packages/ui/primitives/dialog.tsx @@ -16,12 +16,13 @@ const DialogPortal = ({ children, position = 'start', ...props -}: DialogPrimitive.DialogPortalProps & { position?: 'start' | 'end' }) => ( +}: DialogPrimitive.DialogPortalProps & { position?: 'start' | 'end' | 'center' }) => (
{children} @@ -49,7 +50,9 @@ DialogOverlay.displayName = DialogPrimitive.Overlay.displayName; const DialogContent = React.forwardRef< React.ElementRef, - React.ComponentPropsWithoutRef & { position?: 'start' | 'end' } + React.ComponentPropsWithoutRef & { + position?: 'start' | 'end' | 'center'; + } >(({ className, children, position = 'start', ...props }, ref) => ( diff --git a/packages/ui/primitives/document-flow/single-player-mode-fields.tsx b/packages/ui/primitives/document-flow/single-player-mode-fields.tsx index 022b05d60..04c093efc 100644 --- a/packages/ui/primitives/document-flow/single-player-mode-fields.tsx +++ b/packages/ui/primitives/document-flow/single-player-mode-fields.tsx @@ -70,25 +70,23 @@ export function SinglePlayerModeSignatureField({ throw new Error('Invalid field type'); } - const $paragraphEl = useRef(null); - const { height, width } = useFieldPageCoords(field); + const insertedBase64Signature = field.inserted && field.Signature?.signatureImageAsBase64; + const insertedTypeSignature = field.inserted && field.Signature?.typedSignature; + const scalingFactor = useElementScaleSize( { height, width, }, - $paragraphEl, + insertedTypeSignature || '', maxFontSize, fontVariableValue, ); const fontSize = maxFontSize * scalingFactor; - const insertedBase64Signature = field.inserted && field.Signature?.signatureImageAsBase64; - const insertedTypeSignature = field.inserted && field.Signature?.typedSignature; - return ( {insertedBase64Signature ? ( @@ -99,7 +97,6 @@ export function SinglePlayerModeSignatureField({ /> ) : insertedTypeSignature ? (

, 'onChange'> & { onChange?: (_signatureDataUrl: string | null) => void; + containerClassName?: string; }; export const SignaturePad = ({ className, + containerClassName, defaultValue, onChange, ...props @@ -210,7 +212,7 @@ export const SignaturePad = ({ }, [defaultValue]); return ( -

+