Compare commits

...

32 Commits

Author SHA1 Message Date
flō
24036b0f24 fix typo 2023-09-11 17:03:14 +10:00
Lucas Smith
975d52a07e Merge pull request #362 from documenso/fix/hide-user-selection
fix: hide popover when user selects a recipient
2023-09-11 12:27:50 +10:00
Ephraim Atta-Duncan
f8a193c0f8 refactor: replace whole implementation with a state 2023-09-09 10:56:45 +00:00
Ephraim Atta-Duncan
9186cb4d7b fix: hide popover when user selects a recipients 2023-09-09 10:42:03 +00:00
Mythie
933076fa3f fix: update devcontainer 2023-09-09 15:49:40 +10:00
Mythie
abc91f7eac fix: update devcontainer 2023-09-09 15:44:10 +10:00
Mythie
35acf05997 feat: add devcontainer 2023-09-09 04:38:37 +00:00
Lucas Smith
ff957a2f82 Merge pull request #353 from documenso/feat/disable-sign
feat: disable signing and editing for completed documents
2023-09-06 20:53:23 +10:00
Ephraim Atta-Duncan
6640f0496a feat: disable signing and editing for completed documents 2023-09-06 10:40:45 +00:00
Lucas Smith
de3ebe16ee Merge pull request #349 from documenso/feat/marketing-mobile-nav
feat: update marketing mobile nav
2023-09-05 18:20:53 +10:00
David Nguyen
84a2d3baf6 feat: update marketing mobile menu 2023-09-05 18:08:29 +10:00
Lucas Smith
74180defd1 Merge pull request #339 from G3root/feat-api-error
feat: add alert banner for errors in sigin page
2023-09-05 15:36:55 +10:00
Lucas Smith
aeeaaf0d8d Merge pull request #340 from G3root/fix-username-updateable
fix: user name not updatable
2023-09-05 15:33:19 +10:00
Lucas Smith
2b84293c4e Merge pull request #341 from documenso/feat/add-email-field
feat: add email field to document sign page
2023-09-05 13:25:29 +10:00
Lucas Smith
b38ef6c0a7 Merge pull request #346 from documenso/chore/remove-console-log-warn
chore: removed console logs and warn
2023-09-05 13:23:57 +10:00
Mythie
17af4d25bd fix: actually make timeouts clear 2023-09-05 11:33:49 +10:00
Mythie
6e095921e6 fix: tidy up code 2023-09-05 11:29:23 +10:00
nafees nazik
150c42b246 fix: value 2023-09-04 22:24:42 +05:30
Catalin Pit
aecf2f32b9 chore: removed one more console.log 2023-09-04 16:41:28 +03:00
Catalin Pit
b23967d777 chore: removed console.logs and warn 2023-09-04 16:08:22 +03:00
Ephraim Atta-Duncan
b3291c65bc chore: remove console.log 2023-09-02 22:20:57 +00:00
Ephraim Atta-Duncan
4b849e286c feat: add missing email field to document sign page 2023-09-02 22:08:19 +00:00
nafees nazik
7bcc26a987 fix: user name not updatable 2023-09-02 12:11:07 +05:30
nafees nazik
692722d32e revert: fix: component style 2023-09-02 11:55:44 +05:30
Nafees Nazik
e4f06d8e30 Merge branch 'feat/refresh' into feat-api-error 2023-09-02 11:53:18 +05:30
nafees nazik
c799380787 chore: add comments 2023-09-02 11:51:21 +05:30
nafees nazik
5540fcf0d2 fix: use toast 2023-09-02 11:46:12 +05:30
nafees nazik
d9da09c1e7 fix: typo 2023-09-01 16:25:49 +05:30
nafees nazik
fe90aa3b7b feat: add api error 2023-09-01 16:25:27 +05:30
nafees nazik
0c680e0111 fix: component style 2023-09-01 16:25:00 +05:30
Mythie
7bcf5fbd86 feat: store signature on signup 2023-09-01 19:46:44 +10:00
Mythie
7218b950fe feat: store profile signature 2023-09-01 18:43:53 +10:00
25 changed files with 246 additions and 72 deletions

View File

@@ -0,0 +1,20 @@
{
"name": "Documenso",
"image": "mcr.microsoft.com/devcontainers/base:bullseye",
"features": {
"ghcr.io/devcontainers/features/docker-in-docker:2": {
"version": "latest",
"enableNonRootDocker": "true",
"moby": "true"
},
"ghcr.io/devcontainers/features/node:1": {}
},
"onCreateCommand": "./.devcontainer/on-create.sh",
"forwardPorts": [
3000,
54320,
9000,
2500,
1100
]
}

18
.devcontainer/on-create.sh Executable file
View File

@@ -0,0 +1,18 @@
#!/usr/bin/env bash
# Start the database and mailserver
docker compose -f ./docker/compose-without-app.yml up -d
# Install dependencies
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

3
.devcontainer/post-start.sh Executable file
View File

@@ -0,0 +1,3 @@
#!/usr/bin/env bash
npm run dev

View File

@@ -12,7 +12,7 @@ tags:
Since we launched [Documenso 0.9 on Product Hunt](https://producthunt.com/products/documenso#documenso) last May, the team's been hard at work behind the scenes to ramp up development and design to deliver an excellent next version. Since we launched [Documenso 0.9 on Product Hunt](https://producthunt.com/products/documenso#documenso) last May, the team's been hard at work behind the scenes to ramp up development and design to deliver an excellent next version.
Last week, Lucas shared the reasoning how [why we're doing a rewrite](https://documenso.com/blog/why-were-doing-a-rewrite). Last week, Lucas shared the reasoning on [why we're doing a rewrite](https://documenso.com/blog/why-were-doing-a-rewrite).
Today, I'm pleased to share with you a preview of the next Documenso. Today, I'm pleased to share with you a preview of the next Documenso.

View File

@@ -22,6 +22,10 @@ export const MENU_NAVIGATION_LINKS = [
href: '/pricing', href: '/pricing',
text: 'Pricing', text: 'Pricing',
}, },
{
href: '/open',
text: 'Open',
},
{ {
href: 'https://status.documenso.com', href: 'https://status.documenso.com',
text: 'Status', text: 'Status',
@@ -59,7 +63,7 @@ export const MobileNavigation = ({ isMenuOpen, onMenuOpenChange }: MobileNavigat
initial="initial" initial="initial"
animate="animate" animate="animate"
transition={{ transition={{
staggerChildren: 0.2, staggerChildren: 0.03,
}} }}
> >
{MENU_NAVIGATION_LINKS.map(({ href, text }) => ( {MENU_NAVIGATION_LINKS.map(({ href, text }) => (
@@ -75,6 +79,7 @@ export const MobileNavigation = ({ isMenuOpen, onMenuOpenChange }: MobileNavigat
x: 0, x: 0,
transition: { transition: {
duration: 0.5, duration: 0.5,
ease: 'backInOut',
}, },
}, },
}} }}

View File

@@ -82,14 +82,14 @@ export const DataTableActionDropdown = ({ row }: DataTableActionDropdownProps) =
<DropdownMenuContent className="w-52" align="start" forceMount> <DropdownMenuContent className="w-52" align="start" forceMount>
<DropdownMenuLabel>Action</DropdownMenuLabel> <DropdownMenuLabel>Action</DropdownMenuLabel>
<DropdownMenuItem disabled={!recipient} asChild> <DropdownMenuItem disabled={!recipient || isComplete} asChild>
<Link href={`/sign/${recipient?.token}`}> <Link href={`/sign/${recipient?.token}`}>
<Pencil className="mr-2 h-4 w-4" /> <Pencil className="mr-2 h-4 w-4" />
Sign Sign
</Link> </Link>
</DropdownMenuItem> </DropdownMenuItem>
<DropdownMenuItem disabled={!isOwner} asChild> <DropdownMenuItem disabled={!isOwner || isComplete} asChild>
<Link href={`/documents/${row.id}`}> <Link href={`/documents/${row.id}`}>
<Edit className="mr-2 h-4 w-4" /> <Edit className="mr-2 h-4 w-4" />
Edit Edit

View File

@@ -0,0 +1,96 @@
'use client';
import { useTransition } from 'react';
import { useRouter } from 'next/navigation';
import { Loader } from 'lucide-react';
import { Recipient } from '@documenso/prisma/client';
import { FieldWithSignature } from '@documenso/prisma/types/field-with-signature';
import { trpc } from '@documenso/trpc/react';
import { useToast } from '@documenso/ui/primitives/use-toast';
import { useRequiredSigningContext } from './provider';
import { SigningFieldContainer } from './signing-field-container';
export type EmailFieldProps = {
field: FieldWithSignature;
recipient: Recipient;
};
export const EmailField = ({ field, recipient }: EmailFieldProps) => {
const router = useRouter();
const { toast } = useToast();
const { email: providedEmail } = useRequiredSigningContext();
const [isPending, startTransition] = useTransition();
const { mutateAsync: signFieldWithToken, isLoading: isSignFieldWithTokenLoading } =
trpc.field.signFieldWithToken.useMutation();
const {
mutateAsync: removeSignedFieldWithToken,
isLoading: isRemoveSignedFieldWithTokenLoading,
} = trpc.field.removeSignedFieldWithToken.useMutation();
const isLoading = isSignFieldWithTokenLoading || isRemoveSignedFieldWithTokenLoading || isPending;
const onSign = async () => {
try {
await signFieldWithToken({
token: recipient.token,
fieldId: field.id,
value: providedEmail ?? '',
isBase64: false,
});
startTransition(() => router.refresh());
} catch (err) {
console.error(err);
toast({
title: 'Error',
description: 'An error occurred while signing the document.',
variant: 'destructive',
});
}
};
const onRemove = async () => {
try {
await removeSignedFieldWithToken({
token: recipient.token,
fieldId: field.id,
});
startTransition(() => router.refresh());
} catch (err) {
console.error(err);
toast({
title: 'Error',
description: 'An error occurred while removing the signature.',
variant: 'destructive',
});
}
};
return (
<SigningFieldContainer field={field} onSign={onSign} onRemove={onRemove}>
{isLoading && (
<div className="bg-background absolute inset-0 flex items-center justify-center">
<Loader className="text-primary h-5 w-5 animate-spin md:h-8 md:w-8" />
</div>
)}
{!field.inserted && (
<p className="group-hover:text-primary text-muted-foreground text-lg duration-200">Email</p>
)}
{field.inserted && <p className="text-muted-foreground duration-200">{field.customText}</p>}
</SigningFieldContainer>
);
};

View File

@@ -3,6 +3,7 @@ import { notFound } from 'next/navigation';
import { match } from 'ts-pattern'; import { match } from 'ts-pattern';
import { PDF_VIEWER_PAGE_SELECTOR } from '@documenso/lib/constants/pdf-viewer'; import { PDF_VIEWER_PAGE_SELECTOR } from '@documenso/lib/constants/pdf-viewer';
import { getServerComponentSession } from '@documenso/lib/next-auth/get-server-session';
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 { viewedDocument } from '@documenso/lib/server-only/document/viewed-document'; import { viewedDocument } from '@documenso/lib/server-only/document/viewed-document';
import { getFieldsForToken } from '@documenso/lib/server-only/field/get-fields-for-token'; import { getFieldsForToken } from '@documenso/lib/server-only/field/get-fields-for-token';
@@ -13,6 +14,7 @@ import { ElementVisible } from '@documenso/ui/primitives/element-visible';
import { LazyPDFViewer } from '@documenso/ui/primitives/lazy-pdf-viewer'; import { LazyPDFViewer } from '@documenso/ui/primitives/lazy-pdf-viewer';
import { DateField } from './date-field'; import { DateField } from './date-field';
import { EmailField } from './email-field';
import { SigningForm } from './form'; import { SigningForm } from './form';
import { NameField } from './name-field'; import { NameField } from './name-field';
import { SigningProvider } from './provider'; import { SigningProvider } from './provider';
@@ -42,10 +44,12 @@ export default async function SigningPage({ params: { token } }: SigningPageProp
return notFound(); return notFound();
} }
const user = await getServerComponentSession();
const documentUrl = `data:application/pdf;base64,${document.document}`; const documentUrl = `data:application/pdf;base64,${document.document}`;
return ( return (
<SigningProvider email={recipient.email} fullName={recipient.name}> <SigningProvider email={recipient.email} fullName={recipient.name} signature={user?.signature}>
<div className="mx-auto w-full max-w-screen-xl px-4 md:px-8"> <div className="mx-auto w-full max-w-screen-xl px-4 md:px-8">
<h1 className="mt-4 truncate text-2xl font-semibold md:text-3xl" title={document.title}> <h1 className="mt-4 truncate text-2xl font-semibold md:text-3xl" title={document.title}>
{document.title} {document.title}
@@ -84,6 +88,9 @@ export default async function SigningPage({ params: { token } }: SigningPageProp
.with(FieldType.DATE, () => ( .with(FieldType.DATE, () => (
<DateField key={field.id} field={field} recipient={recipient} /> <DateField key={field.id} field={field} recipient={recipient} />
)) ))
.with(FieldType.EMAIL, () => (
<EmailField key={field.id} field={field} recipient={recipient} />
))
.otherwise(() => null), .otherwise(() => null),
)} )}
</ElementVisible> </ElementVisible>

View File

@@ -28,9 +28,9 @@ export const useRequiredSigningContext = () => {
}; };
export interface SigningProviderProps { export interface SigningProviderProps {
fullName?: string; fullName?: string | null;
email?: string; email?: string | null;
signature?: string; signature?: string | null;
children: React.ReactNode; children: React.ReactNode;
} }

View File

@@ -44,7 +44,7 @@ export const ProfileForm = ({ className, user }: ProfileFormProps) => {
} = useForm<TProfileFormSchema>({ } = useForm<TProfileFormSchema>({
values: { values: {
name: user.name ?? '', name: user.name ?? '',
signature: '', signature: user.signature || '',
}, },
resolver: zodResolver(ZProfileFormSchema), resolver: zodResolver(ZProfileFormSchema),
}); });
@@ -118,6 +118,7 @@ export const ProfileForm = ({ className, user }: ProfileFormProps) => {
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 rounded-lg border bg-white backdrop-blur-sm dark:border-[#e2d7c5] dark:bg-[#fcf8ee]"
defaultValue={user.signature ?? undefined}
onChange={(v) => onChange(v ?? '')} onChange={(v) => onChange(v ?? '')}
/> />
)} )}

View File

@@ -1,5 +1,9 @@
'use client'; 'use client';
import { useEffect } from 'react';
import { useSearchParams } from 'next/navigation';
import { zodResolver } from '@hookform/resolvers/zod'; import { zodResolver } from '@hookform/resolvers/zod';
import { Loader } from 'lucide-react'; import { Loader } from 'lucide-react';
import { signIn } from 'next-auth/react'; import { signIn } from 'next-auth/react';
@@ -7,12 +11,20 @@ import { useForm } from 'react-hook-form';
import { FcGoogle } from 'react-icons/fc'; import { FcGoogle } from 'react-icons/fc';
import { z } from 'zod'; import { z } from 'zod';
import { ErrorCode, isErrorCode } from '@documenso/lib/next-auth/error-codes';
import { cn } from '@documenso/ui/lib/utils'; import { cn } from '@documenso/ui/lib/utils';
import { Button } from '@documenso/ui/primitives/button'; import { Button } from '@documenso/ui/primitives/button';
import { Input } from '@documenso/ui/primitives/input'; import { Input } from '@documenso/ui/primitives/input';
import { Label } from '@documenso/ui/primitives/label'; import { Label } from '@documenso/ui/primitives/label';
import { useToast } from '@documenso/ui/primitives/use-toast'; import { useToast } from '@documenso/ui/primitives/use-toast';
const ErrorMessages = {
[ErrorCode.CREDENTIALS_NOT_FOUND]: 'The email or password provided is incorrect',
[ErrorCode.INCORRECT_EMAIL_PASSWORD]: 'The email or password provided is incorrect',
[ErrorCode.USER_MISSING_PASSWORD]:
'This account appears to be using a social login method, please sign in using that method',
};
export const ZSignInFormSchema = z.object({ export const ZSignInFormSchema = z.object({
email: z.string().email().min(1), email: z.string().email().min(1),
password: z.string().min(6).max(72), password: z.string().min(6).max(72),
@@ -26,6 +38,7 @@ export type SignInFormProps = {
export const SignInForm = ({ className }: SignInFormProps) => { export const SignInForm = ({ className }: SignInFormProps) => {
const { toast } = useToast(); const { toast } = useToast();
const searchParams = useSearchParams();
const { const {
register, register,
@@ -39,6 +52,27 @@ export const SignInForm = ({ className }: SignInFormProps) => {
resolver: zodResolver(ZSignInFormSchema), resolver: zodResolver(ZSignInFormSchema),
}); });
const errorCode = searchParams?.get('error');
useEffect(() => {
let timeout: NodeJS.Timeout | null = null;
if (isErrorCode(errorCode)) {
timeout = setTimeout(() => {
toast({
variant: 'destructive',
description: ErrorMessages[errorCode] ?? 'An unknown error occurred',
});
}, 0);
}
return () => {
if (timeout) {
clearTimeout(timeout);
}
};
}, [errorCode, toast]);
const onFormSubmit = async ({ email, password }: TSignInFormSchema) => { const onFormSubmit = async ({ email, password }: TSignInFormSchema) => {
try { try {
await signIn('credentials', { await signIn('credentials', {

View File

@@ -3,13 +3,14 @@
import { zodResolver } from '@hookform/resolvers/zod'; import { zodResolver } from '@hookform/resolvers/zod';
import { Loader } from 'lucide-react'; import { Loader } from 'lucide-react';
import { signIn } from 'next-auth/react'; import { signIn } from 'next-auth/react';
import { useForm } from 'react-hook-form'; import { Controller, useForm } from 'react-hook-form';
import { z } from 'zod'; import { z } from 'zod';
import { TRPCClientError } from '@documenso/trpc/client'; import { TRPCClientError } from '@documenso/trpc/client';
import { trpc } from '@documenso/trpc/react'; import { trpc } from '@documenso/trpc/react';
import { cn } from '@documenso/ui/lib/utils'; import { cn } from '@documenso/ui/lib/utils';
import { Button } from '@documenso/ui/primitives/button'; import { Button } from '@documenso/ui/primitives/button';
import { FormErrorMessage } from '@documenso/ui/primitives/form/form-error-message';
import { Input } from '@documenso/ui/primitives/input'; import { Input } from '@documenso/ui/primitives/input';
import { Label } from '@documenso/ui/primitives/label'; import { Label } from '@documenso/ui/primitives/label';
import { SignaturePad } from '@documenso/ui/primitives/signature-pad'; import { SignaturePad } from '@documenso/ui/primitives/signature-pad';
@@ -19,6 +20,7 @@ export const ZSignUpFormSchema = z.object({
name: z.string().min(1), name: z.string().min(1),
email: z.string().email().min(1), email: z.string().email().min(1),
password: z.string().min(6).max(72), password: z.string().min(6).max(72),
signature: z.string().min(1, { message: 'We need your signature to sign documents' }),
}); });
export type TSignUpFormSchema = z.infer<typeof ZSignUpFormSchema>; export type TSignUpFormSchema = z.infer<typeof ZSignUpFormSchema>;
@@ -31,6 +33,7 @@ export const SignUpForm = ({ className }: SignUpFormProps) => {
const { toast } = useToast(); const { toast } = useToast();
const { const {
control,
register, register,
handleSubmit, handleSubmit,
formState: { errors, isSubmitting }, formState: { errors, isSubmitting },
@@ -39,15 +42,16 @@ export const SignUpForm = ({ className }: SignUpFormProps) => {
name: '', name: '',
email: '', email: '',
password: '', password: '',
signature: '',
}, },
resolver: zodResolver(ZSignUpFormSchema), resolver: zodResolver(ZSignUpFormSchema),
}); });
const { mutateAsync: signup } = trpc.auth.signup.useMutation(); const { mutateAsync: signup } = trpc.auth.signup.useMutation();
const onFormSubmit = async ({ name, email, password }: TSignUpFormSchema) => { const onFormSubmit = async ({ name, email, password, signature }: TSignUpFormSchema) => {
try { try {
await signup({ name, email, password }); await signup({ name, email, password, signature });
await signIn('credentials', { await signIn('credentials', {
email, email,
@@ -119,8 +123,19 @@ export const SignUpForm = ({ className }: SignUpFormProps) => {
</Label> </Label>
<div> <div>
<SignaturePad className="mt-2 h-36 w-full rounded-lg border bg-white dark:border-[#e2d7c5] dark:bg-[#fcf8ee]" /> <Controller
control={control}
name="signature"
render={({ field: { onChange } }) => (
<SignaturePad
className="mt-2 h-36 w-full rounded-lg border bg-white dark:border-[#e2d7c5] dark:bg-[#fcf8ee]"
onChange={(v) => onChange(v ?? '')}
/>
)}
/>
</div> </div>
<FormErrorMessage className="mt-1.5" error={errors.signature} />
</div> </div>
<Button size="lg" disabled={isSubmitting} className="dark:bg-documenso dark:hover:opacity-90"> <Button size="lg" disabled={isSubmitting} className="dark:bg-documenso dark:hover:opacity-90">

View File

@@ -2,7 +2,7 @@
"private": true, "private": true,
"scripts": { "scripts": {
"build": "turbo run build", "build": "turbo run build",
"dev": "turbo run dev --filter=@documenso/{web,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",
"format": "prettier --write \"**/*.{js,jsx,cjs,mjs,ts,tsx,cts,mts,mdx}\"", "format": "prettier --write \"**/*.{js,jsx,cjs,mjs,ts,tsx,cts,mts,mdx}\"",

View File

@@ -7,7 +7,7 @@ import GoogleProvider, { GoogleProfile } from 'next-auth/providers/google';
import { prisma } from '@documenso/prisma'; import { prisma } from '@documenso/prisma';
import { getUserByEmail } from '../server-only/user/get-user-by-email'; import { getUserByEmail } from '../server-only/user/get-user-by-email';
import { ErrorCodes } from './error-codes'; import { ErrorCode } from './error-codes';
export const NEXT_AUTH_OPTIONS: AuthOptions = { export const NEXT_AUTH_OPTIONS: AuthOptions = {
adapter: PrismaAdapter(prisma), adapter: PrismaAdapter(prisma),
@@ -24,23 +24,23 @@ export const NEXT_AUTH_OPTIONS: AuthOptions = {
}, },
authorize: async (credentials, _req) => { authorize: async (credentials, _req) => {
if (!credentials) { if (!credentials) {
throw new Error(ErrorCodes.CredentialsNotFound); throw new Error(ErrorCode.CREDENTIALS_NOT_FOUND);
} }
const { email, password } = credentials; const { email, password } = credentials;
const user = await getUserByEmail({ email }).catch(() => { const user = await getUserByEmail({ email }).catch(() => {
throw new Error(ErrorCodes.IncorrectEmailPassword); throw new Error(ErrorCode.INCORRECT_EMAIL_PASSWORD);
}); });
if (!user.password) { if (!user.password) {
throw new Error(ErrorCodes.UserMissingPassword); throw new Error(ErrorCode.USER_MISSING_PASSWORD);
} }
const isPasswordsSame = await compare(password, user.password); const isPasswordsSame = await compare(password, user.password);
if (!isPasswordsSame) { if (!isPasswordsSame) {
throw new Error(ErrorCodes.IncorrectEmailPassword); throw new Error(ErrorCode.INCORRECT_EMAIL_PASSWORD);
} }
return { return {

View File

@@ -1,5 +1,11 @@
export const ErrorCodes = { export const isErrorCode = (code: unknown): code is ErrorCode => {
IncorrectEmailPassword: 'incorrect-email-password', return typeof code === 'string' && code in ErrorCode;
UserMissingPassword: 'missing-password', };
CredentialsNotFound: 'credentials-not-found',
export type ErrorCode = (typeof ErrorCode)[keyof typeof ErrorCode];
export const ErrorCode = {
INCORRECT_EMAIL_PASSWORD: 'INCORRECT_EMAIL_PASSWORD',
USER_MISSING_PASSWORD: 'USER_MISSING_PASSWORD',
CREDENTIALS_NOT_FOUND: 'CREDENTIALS_NOT_FOUND',
} as const; } as const;

View File

@@ -83,10 +83,7 @@ export const completeDocumentWithToken = async ({
}, },
}); });
console.log('documents', documents);
if (documents.count > 0) { if (documents.count > 0) {
console.log('sealing document');
await sealDocument({ documentId: document.id }); await sealDocument({ documentId: document.id });
} }
}; };

View File

@@ -53,10 +53,6 @@ export const sealDocument = async ({ documentId }: SealDocumentOptions) => {
const doc = await PDFDocument.load(pdfData); const doc = await PDFDocument.load(pdfData);
for (const field of fields) { for (const field of fields) {
console.log('inserting field', {
...field,
Signature: null,
});
await insertFieldInPDF(doc, field); await insertFieldInPDF(doc, field);
} }

View File

@@ -35,15 +35,6 @@ export const insertFieldInPDF = async (pdf: PDFDocument, field: FieldWithSignatu
const fieldX = pageWidth * (Number(field.positionX) / 100); const fieldX = pageWidth * (Number(field.positionX) / 100);
const fieldY = pageHeight * (Number(field.positionY) / 100); const fieldY = pageHeight * (Number(field.positionY) / 100);
console.log({
fieldWidth,
fieldHeight,
fieldX,
fieldY,
pageWidth,
pageHeight,
});
const font = await pdf.embedFont(isSignatureField ? fontCaveat : StandardFonts.Helvetica); const font = await pdf.embedFont(isSignatureField ? fontCaveat : StandardFonts.Helvetica);
if (field.type === FieldType.SIGNATURE || field.type === FieldType.FREE_SIGNATURE) { if (field.type === FieldType.SIGNATURE || field.type === FieldType.FREE_SIGNATURE) {
@@ -75,15 +66,6 @@ export const insertFieldInPDF = async (pdf: PDFDocument, field: FieldWithSignatu
// Invert the Y axis since PDFs use a bottom-left coordinate system // Invert the Y axis since PDFs use a bottom-left coordinate system
imageY = pageHeight - imageY - imageHeight; imageY = pageHeight - imageY - imageHeight;
console.log({
initialDimensions,
scalingFactor,
imageWidth,
imageHeight,
imageX,
imageY,
});
page.drawImage(image, { page.drawImage(image, {
x: imageX, x: imageX,
y: imageY, y: imageY,
@@ -107,17 +89,6 @@ export const insertFieldInPDF = async (pdf: PDFDocument, field: FieldWithSignatu
const textX = fieldX + (fieldWidth - textWidth) / 2; const textX = fieldX + (fieldWidth - textWidth) / 2;
let textY = fieldY + (fieldHeight - textHeight) / 2; let textY = fieldY + (fieldHeight - textHeight) / 2;
console.log({
initialDimensions,
scalingFactor,
textWidth,
textHeight,
textX,
textY,
pageWidth,
pageHeight,
});
// Invert the Y axis since PDFs use a bottom-left coordinate system // Invert the Y axis since PDFs use a bottom-left coordinate system
textY = pageHeight - textY - textHeight; textY = pageHeight - textY - textHeight;

View File

@@ -9,9 +9,10 @@ export interface CreateUserOptions {
name: string; name: string;
email: string; email: string;
password: string; password: string;
signature?: string | null;
} }
export const createUser = async ({ name, email, password }: CreateUserOptions) => { export const createUser = async ({ name, email, password, signature }: CreateUserOptions) => {
const hashedPassword = await hash(password, SALT_ROUNDS); const hashedPassword = await hash(password, SALT_ROUNDS);
const userExists = await prisma.user.findFirst({ const userExists = await prisma.user.findFirst({
@@ -29,6 +30,7 @@ export const createUser = async ({ name, email, password }: CreateUserOptions) =
name, name,
email: email.toLowerCase(), email: email.toLowerCase(),
password: hashedPassword, password: hashedPassword,
signature,
identityProvider: IdentityProvider.DOCUMENSO, identityProvider: IdentityProvider.DOCUMENSO,
}, },
}); });

View File

@@ -6,12 +6,7 @@ export type UpdateProfileOptions = {
signature: string; signature: string;
}; };
export const updateProfile = async ({ export const updateProfile = async ({ userId, name, signature }: UpdateProfileOptions) => {
userId,
name,
// TODO: Actually use signature
signature: _signature,
}: UpdateProfileOptions) => {
// Existence check // Existence check
await prisma.user.findFirstOrThrow({ await prisma.user.findFirstOrThrow({
where: { where: {
@@ -25,7 +20,7 @@ export const updateProfile = async ({
}, },
data: { data: {
name, name,
// signature, signature,
}, },
}); });

View File

@@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "User" ADD COLUMN "signature" TEXT;

View File

@@ -20,6 +20,7 @@ model User {
emailVerified DateTime? emailVerified DateTime?
password String? password String?
source String? source String?
signature String?
identityProvider IdentityProvider @default(DOCUMENSO) identityProvider IdentityProvider @default(DOCUMENSO)
accounts Account[] accounts Account[]
sessions Session[] sessions Session[]

View File

@@ -8,9 +8,9 @@ import { ZSignUpMutationSchema } from './schema';
export const authRouter = router({ export const authRouter = router({
signup: procedure.input(ZSignUpMutationSchema).mutation(async ({ input }) => { signup: procedure.input(ZSignUpMutationSchema).mutation(async ({ input }) => {
try { try {
const { name, email, password } = input; const { name, email, password, signature } = input;
return await createUser({ name, email, password }); return await createUser({ name, email, password, signature });
} catch (err) { } catch (err) {
console.error(err); console.error(err);

View File

@@ -4,6 +4,7 @@ export const ZSignUpMutationSchema = z.object({
name: z.string().min(1), name: z.string().min(1),
email: z.string().email(), email: z.string().email(),
password: z.string().min(6), password: z.string().min(6),
signature: z.string().min(1, { message: 'A signature is required.' }),
}); });
export type TSignUpMutationSchema = z.infer<typeof ZSignUpMutationSchema>; export type TSignUpMutationSchema = z.infer<typeof ZSignUpMutationSchema>;

View File

@@ -102,6 +102,7 @@ export const AddFieldsFormPartial = ({
const [selectedField, setSelectedField] = useState<FieldType | null>(null); const [selectedField, setSelectedField] = useState<FieldType | null>(null);
const [selectedSigner, setSelectedSigner] = useState<Recipient | null>(null); const [selectedSigner, setSelectedSigner] = useState<Recipient | null>(null);
const [showRecipientsSelector, setShowRecipientsSelector] = useState(false);
const hasSelectedSignerBeenSent = selectedSigner?.sendStatus === SendStatus.SENT; const hasSelectedSignerBeenSent = selectedSigner?.sendStatus === SendStatus.SENT;
@@ -314,7 +315,7 @@ export const AddFieldsFormPartial = ({
))} ))}
{!hideRecipients && ( {!hideRecipients && (
<Popover> <Popover open={showRecipientsSelector} onOpenChange={setShowRecipientsSelector}>
<PopoverTrigger asChild> <PopoverTrigger asChild>
<Button <Button
type="button" type="button"
@@ -324,7 +325,7 @@ export const AddFieldsFormPartial = ({
> >
{selectedSigner?.email && ( {selectedSigner?.email && (
<span className="flex-1 truncate text-left"> <span className="flex-1 truncate text-left">
{selectedSigner?.email} ({selectedSigner?.email}) {selectedSigner?.name} ({selectedSigner?.email})
</span> </span>
)} )}
@@ -348,7 +349,10 @@ export const AddFieldsFormPartial = ({
className={cn({ className={cn({
'text-muted-foreground': recipient.sendStatus === SendStatus.SENT, 'text-muted-foreground': recipient.sendStatus === SendStatus.SENT,
})} })}
onSelect={() => setSelectedSigner(recipient)} onSelect={() => {
setSelectedSigner(recipient);
setShowRecipientsSelector(false);
}}
> >
{recipient.sendStatus !== SendStatus.SENT ? ( {recipient.sendStatus !== SendStatus.SENT ? (
<Check <Check