Compare commits

..

30 Commits

Author SHA1 Message Date
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
27 changed files with 247 additions and 116 deletions
+20
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
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
View File
@@ -0,0 +1,3 @@
#!/usr/bin/env bash
npm run dev
-42
View File
@@ -1,42 +0,0 @@
---
title: Design System
---
# We're building a beautiful, open-source alternative to DocuSign
> Read more about our design culture here:
>
> [https://documenso.com/blog/design-system](https://documenso.com/blog/design-system)
At Documenso, we aim to be a design-driven company.
We believe that design isn't just about how things look, but also how they work. We want to make sure that the product is easy to use and intuitive. We also want to ensure that the website, desktop and mobile apps are consistent and look and feel like they belong together.
To achieve this, we've created Documenso's design system containing tokens, primitives, and components, screens, and brand assets.
We're open-sourcing this design system so you can see how we build the product and think about design as a whole.
## Check out the design system
<iframe
src="https://documen.so/design-system-embed"
className="aspect-square w-full border-none"
frameBorder="0"
/>
## Remix and Share the community version on Figma
<a href="documen.so/design" target="_blank">
<figure>
<MdxNextImage
src="/blog/designsystem.png"
width="1260"
height="630"
alt="Documenso's Design System"
/>
<figcaption className="text-center">
Documenso's Design System ✨
</figcaption>
</figure>
</a>
@@ -21,7 +21,6 @@ const FOOTER_LINKS = [
{ href: '/open', text: 'Open' },
{ href: 'https://shop.documenso.com', text: 'Shop', target: '_blank' },
{ href: 'https://status.documenso.com', text: 'Status', target: '_blank' },
{ href: '/design-system', text: 'Design' },
{ href: 'mailto:support@documenso.com', text: 'Support' },
{ href: '/privacy', text: 'Privacy' },
];
@@ -44,7 +43,7 @@ export const Footer = ({ className, ...props }: FooterProps) => {
</div>
</div>
<div className="grid max-w-xs flex-1 grid-cols-2 gap-x-4 gap-y-2">
<div className="flex flex-wrap items-center gap-x-4 gap-y-2.5">
{FOOTER_LINKS.map((link, index) => (
<Link
key={index}
@@ -22,6 +22,10 @@ export const MENU_NAVIGATION_LINKS = [
href: '/pricing',
text: 'Pricing',
},
{
href: '/open',
text: 'Open',
},
{
href: 'https://status.documenso.com',
text: 'Status',
@@ -59,7 +63,7 @@ export const MobileNavigation = ({ isMenuOpen, onMenuOpenChange }: MobileNavigat
initial="initial"
animate="animate"
transition={{
staggerChildren: 0.2,
staggerChildren: 0.03,
}}
>
{MENU_NAVIGATION_LINKS.map(({ href, text }) => (
@@ -75,6 +79,7 @@ export const MobileNavigation = ({ isMenuOpen, onMenuOpenChange }: MobileNavigat
x: 0,
transition: {
duration: 0.5,
ease: 'backInOut',
},
},
}}
@@ -82,14 +82,14 @@ export const DataTableActionDropdown = ({ row }: DataTableActionDropdownProps) =
<DropdownMenuContent className="w-52" align="start" forceMount>
<DropdownMenuLabel>Action</DropdownMenuLabel>
<DropdownMenuItem disabled={!recipient} asChild>
<DropdownMenuItem disabled={!recipient || isComplete} asChild>
<Link href={`/sign/${recipient?.token}`}>
<Pencil className="mr-2 h-4 w-4" />
Sign
</Link>
</DropdownMenuItem>
<DropdownMenuItem disabled={!isOwner} asChild>
<DropdownMenuItem disabled={!isOwner || isComplete} asChild>
<Link href={`/documents/${row.id}`}>
<Edit className="mr-2 h-4 w-4" />
Edit
@@ -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>
);
};
@@ -3,6 +3,7 @@ import { notFound } from 'next/navigation';
import { match } from 'ts-pattern';
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 { viewedDocument } from '@documenso/lib/server-only/document/viewed-document';
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 { DateField } from './date-field';
import { EmailField } from './email-field';
import { SigningForm } from './form';
import { NameField } from './name-field';
import { SigningProvider } from './provider';
@@ -42,10 +44,12 @@ export default async function SigningPage({ params: { token } }: SigningPageProp
return notFound();
}
const user = await getServerComponentSession();
const documentUrl = `data:application/pdf;base64,${document.document}`;
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">
<h1 className="mt-4 truncate text-2xl font-semibold md:text-3xl" title={document.title}>
{document.title}
@@ -84,6 +88,9 @@ export default async function SigningPage({ params: { token } }: SigningPageProp
.with(FieldType.DATE, () => (
<DateField key={field.id} field={field} recipient={recipient} />
))
.with(FieldType.EMAIL, () => (
<EmailField key={field.id} field={field} recipient={recipient} />
))
.otherwise(() => null),
)}
</ElementVisible>
@@ -28,9 +28,9 @@ export const useRequiredSigningContext = () => {
};
export interface SigningProviderProps {
fullName?: string;
email?: string;
signature?: string;
fullName?: string | null;
email?: string | null;
signature?: string | null;
children: React.ReactNode;
}
+2 -1
View File
@@ -44,7 +44,7 @@ export const ProfileForm = ({ className, user }: ProfileFormProps) => {
} = useForm<TProfileFormSchema>({
values: {
name: user.name ?? '',
signature: '',
signature: user.signature || '',
},
resolver: zodResolver(ZProfileFormSchema),
});
@@ -118,6 +118,7 @@ export const ProfileForm = ({ className, user }: ProfileFormProps) => {
render={({ field: { onChange } }) => (
<SignaturePad
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 ?? '')}
/>
)}
+34
View File
@@ -1,5 +1,9 @@
'use client';
import { useEffect } from 'react';
import { useSearchParams } from 'next/navigation';
import { zodResolver } from '@hookform/resolvers/zod';
import { Loader } from 'lucide-react';
import { signIn } from 'next-auth/react';
@@ -7,12 +11,20 @@ import { useForm } from 'react-hook-form';
import { FcGoogle } from 'react-icons/fc';
import { z } from 'zod';
import { ErrorCode, isErrorCode } from '@documenso/lib/next-auth/error-codes';
import { cn } from '@documenso/ui/lib/utils';
import { Button } from '@documenso/ui/primitives/button';
import { Input } from '@documenso/ui/primitives/input';
import { Label } from '@documenso/ui/primitives/label';
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({
email: z.string().email().min(1),
password: z.string().min(6).max(72),
@@ -26,6 +38,7 @@ export type SignInFormProps = {
export const SignInForm = ({ className }: SignInFormProps) => {
const { toast } = useToast();
const searchParams = useSearchParams();
const {
register,
@@ -39,6 +52,27 @@ export const SignInForm = ({ className }: SignInFormProps) => {
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) => {
try {
await signIn('credentials', {
+19 -4
View File
@@ -3,13 +3,14 @@
import { zodResolver } from '@hookform/resolvers/zod';
import { Loader } from 'lucide-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 { TRPCClientError } from '@documenso/trpc/client';
import { trpc } from '@documenso/trpc/react';
import { cn } from '@documenso/ui/lib/utils';
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 { Label } from '@documenso/ui/primitives/label';
import { SignaturePad } from '@documenso/ui/primitives/signature-pad';
@@ -19,6 +20,7 @@ export const ZSignUpFormSchema = z.object({
name: z.string().min(1),
email: z.string().email().min(1),
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>;
@@ -31,6 +33,7 @@ export const SignUpForm = ({ className }: SignUpFormProps) => {
const { toast } = useToast();
const {
control,
register,
handleSubmit,
formState: { errors, isSubmitting },
@@ -39,15 +42,16 @@ export const SignUpForm = ({ className }: SignUpFormProps) => {
name: '',
email: '',
password: '',
signature: '',
},
resolver: zodResolver(ZSignUpFormSchema),
});
const { mutateAsync: signup } = trpc.auth.signup.useMutation();
const onFormSubmit = async ({ name, email, password }: TSignUpFormSchema) => {
const onFormSubmit = async ({ name, email, password, signature }: TSignUpFormSchema) => {
try {
await signup({ name, email, password });
await signup({ name, email, password, signature });
await signIn('credentials', {
email,
@@ -119,8 +123,19 @@ export const SignUpForm = ({ className }: SignUpFormProps) => {
</Label>
<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>
<FormErrorMessage className="mt-1.5" error={errors.signature} />
</div>
<Button size="lg" disabled={isSubmitting} className="dark:bg-documenso dark:hover:opacity-90">
+1 -1
View File
@@ -2,7 +2,7 @@
"private": true,
"scripts": {
"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",
"lint": "turbo run lint",
"format": "prettier --write \"**/*.{js,jsx,cjs,mjs,ts,tsx,cts,mts,mdx}\"",
+5 -5
View File
@@ -7,7 +7,7 @@ import GoogleProvider, { GoogleProfile } from 'next-auth/providers/google';
import { prisma } from '@documenso/prisma';
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 = {
adapter: PrismaAdapter(prisma),
@@ -24,23 +24,23 @@ export const NEXT_AUTH_OPTIONS: AuthOptions = {
},
authorize: async (credentials, _req) => {
if (!credentials) {
throw new Error(ErrorCodes.CredentialsNotFound);
throw new Error(ErrorCode.CREDENTIALS_NOT_FOUND);
}
const { email, password } = credentials;
const user = await getUserByEmail({ email }).catch(() => {
throw new Error(ErrorCodes.IncorrectEmailPassword);
throw new Error(ErrorCode.INCORRECT_EMAIL_PASSWORD);
});
if (!user.password) {
throw new Error(ErrorCodes.UserMissingPassword);
throw new Error(ErrorCode.USER_MISSING_PASSWORD);
}
const isPasswordsSame = await compare(password, user.password);
if (!isPasswordsSame) {
throw new Error(ErrorCodes.IncorrectEmailPassword);
throw new Error(ErrorCode.INCORRECT_EMAIL_PASSWORD);
}
return {
+10 -4
View File
@@ -1,5 +1,11 @@
export const ErrorCodes = {
IncorrectEmailPassword: 'incorrect-email-password',
UserMissingPassword: 'missing-password',
CredentialsNotFound: 'credentials-not-found',
export const isErrorCode = (code: unknown): code is ErrorCode => {
return typeof code === 'string' && code in ErrorCode;
};
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;
@@ -83,10 +83,7 @@ export const completeDocumentWithToken = async ({
},
});
console.log('documents', documents);
if (documents.count > 0) {
console.log('sealing document');
await sealDocument({ documentId: document.id });
}
};
@@ -53,10 +53,6 @@ export const sealDocument = async ({ documentId }: SealDocumentOptions) => {
const doc = await PDFDocument.load(pdfData);
for (const field of fields) {
console.log('inserting field', {
...field,
Signature: null,
});
await insertFieldInPDF(doc, field);
}
@@ -35,15 +35,6 @@ export const insertFieldInPDF = async (pdf: PDFDocument, field: FieldWithSignatu
const fieldX = pageWidth * (Number(field.positionX) / 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);
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
imageY = pageHeight - imageY - imageHeight;
console.log({
initialDimensions,
scalingFactor,
imageWidth,
imageHeight,
imageX,
imageY,
});
page.drawImage(image, {
x: imageX,
y: imageY,
@@ -107,17 +89,6 @@ export const insertFieldInPDF = async (pdf: PDFDocument, field: FieldWithSignatu
const textX = fieldX + (fieldWidth - textWidth) / 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
textY = pageHeight - textY - textHeight;
+3 -1
View File
@@ -9,9 +9,10 @@ export interface CreateUserOptions {
name: string;
email: 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 userExists = await prisma.user.findFirst({
@@ -29,6 +30,7 @@ export const createUser = async ({ name, email, password }: CreateUserOptions) =
name,
email: email.toLowerCase(),
password: hashedPassword,
signature,
identityProvider: IdentityProvider.DOCUMENSO,
},
});
@@ -6,12 +6,7 @@ export type UpdateProfileOptions = {
signature: string;
};
export const updateProfile = async ({
userId,
name,
// TODO: Actually use signature
signature: _signature,
}: UpdateProfileOptions) => {
export const updateProfile = async ({ userId, name, signature }: UpdateProfileOptions) => {
// Existence check
await prisma.user.findFirstOrThrow({
where: {
@@ -25,7 +20,7 @@ export const updateProfile = async ({
},
data: {
name,
// signature,
signature,
},
});
@@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "User" ADD COLUMN "signature" TEXT;
+1
View File
@@ -20,6 +20,7 @@ model User {
emailVerified DateTime?
password String?
source String?
signature String?
identityProvider IdentityProvider @default(DOCUMENSO)
accounts Account[]
sessions Session[]
+1 -1
View File
@@ -4,7 +4,7 @@ const { fontFamily } = require('tailwindcss/defaultTheme');
/** @type {import('tailwindcss').Config} */
module.exports = {
darkMode: ['class'],
content: ['src/**/*.{ts,tsx}', 'content/**/*.{md,mdx}'],
content: ['src/**/*.{ts,tsx}'],
theme: {
extend: {
fontFamily: {
+2 -2
View File
@@ -8,9 +8,9 @@ import { ZSignUpMutationSchema } from './schema';
export const authRouter = router({
signup: procedure.input(ZSignUpMutationSchema).mutation(async ({ input }) => {
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) {
console.error(err);
@@ -4,6 +4,7 @@ export const ZSignUpMutationSchema = z.object({
name: z.string().min(1),
email: z.string().email(),
password: z.string().min(6),
signature: z.string().min(1, { message: 'A signature is required.' }),
});
export type TSignUpMutationSchema = z.infer<typeof ZSignUpMutationSchema>;
@@ -102,6 +102,7 @@ export const AddFieldsFormPartial = ({
const [selectedField, setSelectedField] = useState<FieldType | null>(null);
const [selectedSigner, setSelectedSigner] = useState<Recipient | null>(null);
const [showRecipientsSelector, setShowRecipientsSelector] = useState(false);
const hasSelectedSignerBeenSent = selectedSigner?.sendStatus === SendStatus.SENT;
@@ -314,7 +315,7 @@ export const AddFieldsFormPartial = ({
))}
{!hideRecipients && (
<Popover>
<Popover open={showRecipientsSelector} onOpenChange={setShowRecipientsSelector}>
<PopoverTrigger asChild>
<Button
type="button"
@@ -324,7 +325,7 @@ export const AddFieldsFormPartial = ({
>
{selectedSigner?.email && (
<span className="flex-1 truncate text-left">
{selectedSigner?.email} ({selectedSigner?.email})
{selectedSigner?.name} ({selectedSigner?.email})
</span>
)}
@@ -348,7 +349,10 @@ export const AddFieldsFormPartial = ({
className={cn({
'text-muted-foreground': recipient.sendStatus === SendStatus.SENT,
})}
onSelect={() => setSelectedSigner(recipient)}
onSelect={() => {
setSelectedSigner(recipient);
setShowRecipientsSelector(false);
}}
>
{recipient.sendStatus !== SendStatus.SENT ? (
<Check