fix: resolve issues with signing document stickiness

This commit is contained in:
Mythie
2023-09-28 17:32:31 +10:00
parent 2f66eca925
commit 4885cf5154
20 changed files with 66 additions and 43 deletions

View File

@ -12,7 +12,7 @@ export type AdminSectionLayoutProps = {
}; };
export default async function AdminSectionLayout({ children }: AdminSectionLayoutProps) { export default async function AdminSectionLayout({ children }: AdminSectionLayoutProps) {
const user = await getRequiredServerComponentSession(); const { user } = await getRequiredServerComponentSession();
if (!isAdmin(user)) { if (!isAdmin(user)) {
redirect('/documents'); redirect('/documents');

View File

@ -30,11 +30,11 @@ export default async function DocumentPage({ params }: DocumentPageProps) {
redirect('/documents'); redirect('/documents');
} }
const session = await getRequiredServerComponentSession(); const { user } = await getRequiredServerComponentSession();
const document = await getDocumentById({ const document = await getDocumentById({
id: documentId, id: documentId,
userId: session.id, userId: user.id,
}).catch(() => null); }).catch(() => null);
if (!document || !document.documentData) { if (!document || !document.documentData) {
@ -50,11 +50,11 @@ export default async function DocumentPage({ params }: DocumentPageProps) {
const [recipients, fields] = await Promise.all([ const [recipients, fields] = await Promise.all([
await getRecipientsForDocument({ await getRecipientsForDocument({
documentId, documentId,
userId: session.id, userId: user.id,
}), }),
await getFieldsForDocument({ await getFieldsForDocument({
documentId, documentId,
userId: session.id, userId: user.id,
}), }),
]); ]);
@ -87,7 +87,7 @@ export default async function DocumentPage({ params }: DocumentPageProps) {
<EditDocumentForm <EditDocumentForm
className="mt-8" className="mt-8"
document={document} document={document}
user={session} user={user}
recipients={recipients} recipients={recipients}
fields={fields} fields={fields}
dataUrl={documentDataUrl} dataUrl={documentDataUrl}

View File

@ -25,7 +25,7 @@ export type DocumentsPageProps = {
}; };
export default async function DocumentsPage({ searchParams = {} }: DocumentsPageProps) { export default async function DocumentsPage({ searchParams = {} }: DocumentsPageProps) {
const user = await getRequiredServerComponentSession(); const { user } = await getRequiredServerComponentSession();
const stats = await getStats({ const stats = await getStats({
user, user,

View File

@ -24,7 +24,7 @@ export default async function AuthenticatedDashboardLayout({
redirect('/signin'); redirect('/signin');
} }
const user = await getRequiredServerComponentSession(); const { user } = await getRequiredServerComponentSession();
return ( return (
<NextAuthProvider session={session}> <NextAuthProvider session={session}>

View File

@ -12,7 +12,7 @@ import { Button } from '@documenso/ui/primitives/button';
import { LocaleDate } from '~/components/formatter/locale-date'; import { LocaleDate } from '~/components/formatter/locale-date';
export default async function BillingSettingsPage() { export default async function BillingSettingsPage() {
const user = await getRequiredServerComponentSession(); const { user } = await getRequiredServerComponentSession();
const isBillingEnabled = await getServerComponentFlag('app_billing'); const isBillingEnabled = await getServerComponentFlag('app_billing');

View File

@ -3,7 +3,7 @@ import { getRequiredServerComponentSession } from '@documenso/lib/next-auth/get-
import { PasswordForm } from '~/components/forms/password'; import { PasswordForm } from '~/components/forms/password';
export default async function PasswordSettingsPage() { export default async function PasswordSettingsPage() {
const user = await getRequiredServerComponentSession(); const { user } = await getRequiredServerComponentSession();
return ( return (
<div> <div>

View File

@ -3,7 +3,7 @@ import { getRequiredServerComponentSession } from '@documenso/lib/next-auth/get-
import { ProfileForm } from '~/components/forms/profile'; import { ProfileForm } from '~/components/forms/profile';
export default async function ProfileSettingsPage() { export default async function ProfileSettingsPage() {
const user = await getRequiredServerComponentSession(); const { user } = await getRequiredServerComponentSession();
return ( return (
<div> <div>

View File

@ -53,7 +53,7 @@ export default async function CompletedSigningPage({
recipient.email; recipient.email;
return ( return (
<div className="flex flex-col items-center pt-24 lg:pt-36 xl:pt-44"> <div className="-mx-4 flex max-w-[100vw] flex-col items-center overflow-x-hidden px-4 pt-24 md:-mx-8 md:px-8 lg:pt-36 xl:pt-44">
{/* Card with recipient */} {/* Card with recipient */}
<SigningCard3D name={recipientName} signingCelebrationImage={signingCelebration} /> <SigningCard3D name={recipientName} signingCelebrationImage={signingCelebration} />

View File

@ -106,7 +106,7 @@ export const ShareButton = ({ token, documentId }: ShareButtonProps) => {
</Button> </Button>
</DialogTrigger> </DialogTrigger>
<DialogContent> <DialogContent position="end">
<DialogHeader> <DialogHeader>
<DialogTitle>Share</DialogTitle> <DialogTitle>Share</DialogTitle>
@ -119,7 +119,7 @@ export const ShareButton = ({ token, documentId }: ShareButtonProps) => {
<span className="font-medium text-blue-400">@documenso</span> <span className="font-medium text-blue-400">@documenso</span>
. Check it out! . Check it out!
<span className="mt-2 block" /> <span className="mt-2 block" />
<span className="font-medium text-blue-400"> <span className="break-all font-medium text-blue-400">
{window.location.origin}/share/{shareLink?.slug || '...'} {window.location.origin}/share/{shareLink?.slug || '...'}
</span> </span>
</div> </div>

View File

@ -4,6 +4,7 @@ import { useMemo, useState } from 'react';
import { useRouter } from 'next/navigation'; import { useRouter } from 'next/navigation';
import { useSession } from 'next-auth/react';
import { useForm } from 'react-hook-form'; import { useForm } from 'react-hook-form';
import { completeDocumentWithToken } from '@documenso/lib/server-only/document/complete-document-with-token'; import { completeDocumentWithToken } from '@documenso/lib/server-only/document/complete-document-with-token';
@ -27,6 +28,7 @@ export type SigningFormProps = {
export const SigningForm = ({ document, recipient, fields }: SigningFormProps) => { export const SigningForm = ({ document, recipient, fields }: SigningFormProps) => {
const router = useRouter(); const router = useRouter();
const { data: session } = useSession();
const { fullName, signature, setFullName, setSignature } = useRequiredSigningContext(); const { fullName, signature, setFullName, setSignature } = useRequiredSigningContext();
@ -60,7 +62,11 @@ export const SigningForm = ({ document, recipient, fields }: SigningFormProps) =
return ( return (
<form <form
className={cn( className={cn(
'dark:bg-background border-border bg-widget sticky top-20 flex h-full max-h-[80rem] flex-col rounded-xl border px-4 py-6', 'dark:bg-background border-border bg-widget sticky flex h-full flex-col rounded-xl border px-4 py-6',
{
'top-20 max-h-[min(68rem,calc(100vh-6rem))]': session,
'top-4 max-h-[min(68rem,calc(100vh-2rem))]': !session,
},
)} )}
onSubmit={handleSubmit(onFormSubmit)} onSubmit={handleSubmit(onFormSubmit)}
> >

View File

@ -10,11 +10,11 @@ export type SigningLayoutProps = {
}; };
export default async function SigningLayout({ children }: SigningLayoutProps) { export default async function SigningLayout({ children }: SigningLayoutProps) {
const user = await getServerComponentSession(); const { user, session } = await getServerComponentSession();
return ( return (
<NextAuthProvider> <NextAuthProvider session={session}>
<div className="min-h-screen overflow-hidden"> <div className="min-h-screen">
{user && <AuthenticatedHeader user={user} />} {user && <AuthenticatedHeader user={user} />}
<main className="mb-8 mt-8 px-4 md:mb-12 md:mt-12 md:px-8">{children}</main> <main className="mb-8 mt-8 px-4 md:mb-12 md:mt-12 md:px-8">{children}</main>

View File

@ -51,7 +51,7 @@ export default async function SigningPage({ params: { token } }: SigningPageProp
.then((buffer) => Buffer.from(buffer).toString('base64')) .then((buffer) => Buffer.from(buffer).toString('base64'))
.then((data) => `data:application/pdf;base64,${data}`); .then((data) => `data:application/pdf;base64,${data}`);
const user = await getServerComponentSession(); const { user } = await getServerComponentSession();
if ( if (
document.status === DocumentStatus.COMPLETED || document.status === DocumentStatus.COMPLETED ||
@ -62,7 +62,7 @@ export default async function SigningPage({ params: { token } }: SigningPageProp
return ( return (
<SigningProvider email={recipient.email} fullName={recipient.name} signature={user?.signature}> <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">
<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}
</h1> </h1>

View File

@ -6,7 +6,7 @@ import { Button } from '@documenso/ui/primitives/button';
import NotFoundPartial from '~/components/partials/not-found'; import NotFoundPartial from '~/components/partials/not-found';
export default async function NotFound() { export default async function NotFound() {
const session = await getServerComponentSession(); const { session } = await getServerComponentSession();
return ( return (
<NotFoundPartial> <NotFoundPartial>

View File

@ -11,10 +11,10 @@ export type AddFieldsActionInput = TAddFieldsFormSchema & {
export const addFields = async ({ documentId, fields }: AddFieldsActionInput) => { export const addFields = async ({ documentId, fields }: AddFieldsActionInput) => {
'use server'; 'use server';
const { id: userId } = await getRequiredServerComponentSession(); const { user } = await getRequiredServerComponentSession();
await setFieldsForDocument({ await setFieldsForDocument({
userId, userId: user.id,
documentId, documentId,
fields: fields.map((field) => ({ fields: fields.map((field) => ({
id: field.nativeId, id: field.nativeId,

View File

@ -11,10 +11,10 @@ export type AddSignersActionInput = TAddSignersFormSchema & {
export const addSigners = async ({ documentId, signers }: AddSignersActionInput) => { export const addSigners = async ({ documentId, signers }: AddSignersActionInput) => {
'use server'; 'use server';
const { id: userId } = await getRequiredServerComponentSession(); const { user } = await getRequiredServerComponentSession();
await setRecipientsForDocument({ await setRecipientsForDocument({
userId, userId: user.id,
documentId, documentId,
recipients: signers.map((signer) => ({ recipients: signers.map((signer) => ({
id: signer.nativeId, id: signer.nativeId,

View File

@ -12,7 +12,7 @@ export type CompleteDocumentActionInput = TAddSubjectFormSchema & {
export const completeDocument = async ({ documentId, email }: CompleteDocumentActionInput) => { export const completeDocument = async ({ documentId, email }: CompleteDocumentActionInput) => {
'use server'; 'use server';
const { id: userId } = await getRequiredServerComponentSession(); const { user } = await getRequiredServerComponentSession();
if (email.message || email.subject) { if (email.message || email.subject) {
await upsertDocumentMeta({ await upsertDocumentMeta({
@ -23,7 +23,7 @@ export const completeDocument = async ({ documentId, email }: CompleteDocumentAc
} }
return await sendDocument({ return await sendDocument({
userId, userId: user.id,
documentId, documentId,
}); });
}; };

View File

@ -15,7 +15,7 @@ export const getServerSession = async ({ req, res }: GetServerSessionOptions) =>
const session = await getNextAuthServerSession(req, res, NEXT_AUTH_OPTIONS); const session = await getNextAuthServerSession(req, res, NEXT_AUTH_OPTIONS);
if (!session || !session.user?.email) { if (!session || !session.user?.email) {
return null; return { user: null, session: null };
} }
const user = await prisma.user.findFirstOrThrow({ const user = await prisma.user.findFirstOrThrow({
@ -24,14 +24,14 @@ export const getServerSession = async ({ req, res }: GetServerSessionOptions) =>
}, },
}); });
return user; return { user, session };
}; };
export const getServerComponentSession = async () => { export const getServerComponentSession = async () => {
const session = await getNextAuthServerSession(NEXT_AUTH_OPTIONS); const session = await getNextAuthServerSession(NEXT_AUTH_OPTIONS);
if (!session || !session.user?.email) { if (!session || !session.user?.email) {
return null; return { user: null, session: null };
} }
const user = await prisma.user.findFirstOrThrow({ const user = await prisma.user.findFirstOrThrow({
@ -40,15 +40,15 @@ export const getServerComponentSession = async () => {
}, },
}); });
return user; return { user, session };
}; };
export const getRequiredServerComponentSession = async () => { export const getRequiredServerComponentSession = async () => {
const session = await getServerComponentSession(); const { user, session } = await getServerComponentSession();
if (!session) { if (!user || !session) {
throw new Error('No session found'); throw new Error('No session found');
} }
return session; return { user, session };
}; };

View File

@ -17,7 +17,7 @@ import { alphaid } from '../id';
export const getPresignPostUrl = async (fileName: string, contentType: string) => { export const getPresignPostUrl = async (fileName: string, contentType: string) => {
const client = getS3Client(); const client = getS3Client();
const user = await getServerComponentSession(); const { user } = await getServerComponentSession();
// Get the basename and extension for the file // Get the basename and extension for the file
const { name, ext } = path.parse(fileName); const { name, ext } = path.parse(fileName);

View File

@ -3,7 +3,7 @@ import { CreateNextContextOptions } from '@trpc/server/adapters/next';
import { getServerSession } from '@documenso/lib/next-auth/get-server-session'; import { getServerSession } from '@documenso/lib/next-auth/get-server-session';
export const createTrpcContext = async ({ req, res }: CreateNextContextOptions) => { export const createTrpcContext = async ({ req, res }: CreateNextContextOptions) => {
const session = await getServerSession({ req, res }); const { session, user } = await getServerSession({ req, res });
if (!session) { if (!session) {
return { return {
@ -12,9 +12,16 @@ export const createTrpcContext = async ({ req, res }: CreateNextContextOptions)
}; };
} }
if (!user) {
return {
session: null,
user: null,
};
}
return { return {
session, session,
user: session, user,
}; };
}; };

View File

@ -11,9 +11,19 @@ const Dialog = DialogPrimitive.Root;
const DialogTrigger = DialogPrimitive.Trigger; const DialogTrigger = DialogPrimitive.Trigger;
const DialogPortal = ({ className, children, ...props }: DialogPrimitive.DialogPortalProps) => ( const DialogPortal = ({
className,
children,
position = 'start',
...props
}: DialogPrimitive.DialogPortalProps & { position?: 'start' | 'end' }) => (
<DialogPrimitive.Portal className={cn(className)} {...props}> <DialogPrimitive.Portal className={cn(className)} {...props}>
<div className="fixed inset-0 z-50 flex items-start justify-center sm:items-center"> <div
className={cn('fixed inset-0 z-50 flex justify-center sm:items-center', {
'items-start': position === 'start',
'items-end': position === 'end',
})}
>
{children} {children}
</div> </div>
</DialogPrimitive.Portal> </DialogPrimitive.Portal>
@ -39,14 +49,14 @@ 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> React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content> & { position?: 'start' | 'end' }
>(({ className, children, ...props }, ref) => ( >(({ className, children, position = 'start', ...props }, ref) => (
<DialogPortal> <DialogPortal position={position}>
<DialogOverlay /> <DialogOverlay />
<DialogPrimitive.Content <DialogPrimitive.Content
ref={ref} ref={ref}
className={cn( className={cn(
'bg-background animate-in data-[state=open]:fade-in-90 data-[state=open]:slide-in-from-bottom-10 sm:zoom-in-90 data-[state=open]:sm:slide-in-from-bottom-0 fixed z-50 grid w-full gap-4 rounded-b-lg border p-6 shadow-lg sm:max-w-lg sm:rounded-lg', 'bg-background animate-in data-[state=open]:fade-in-90 sm:zoom-in-90 data-[state=open]:slide-in-from-bottom-10 data-[state=open]:sm:slide-in-from-bottom-0 fixed z-50 grid w-full gap-4 rounded-b-lg border p-6 shadow-lg sm:max-w-lg sm:rounded-lg',
className, className,
)} )}
{...props} {...props}