mirror of
https://github.com/documenso/documenso.git
synced 2025-11-11 04:52:41 +10:00
Compare commits
1 Commits
v1.5.5-rc.
...
feat/doc-c
| Author | SHA1 | Date | |
|---|---|---|---|
| 13d23b6111 |
@ -24,6 +24,8 @@ import { LazyPDFViewer } from '@documenso/ui/primitives/lazy-pdf-viewer';
|
|||||||
import { Stepper } from '@documenso/ui/primitives/stepper';
|
import { Stepper } from '@documenso/ui/primitives/stepper';
|
||||||
import { useToast } from '@documenso/ui/primitives/use-toast';
|
import { useToast } from '@documenso/ui/primitives/use-toast';
|
||||||
|
|
||||||
|
import { Comments } from '~/components/forms/comments';
|
||||||
|
|
||||||
export type EditDocumentFormProps = {
|
export type EditDocumentFormProps = {
|
||||||
className?: string;
|
className?: string;
|
||||||
user: User;
|
user: User;
|
||||||
@ -179,60 +181,70 @@ export const EditDocumentForm = ({
|
|||||||
const currentDocumentFlow = documentFlow[step];
|
const currentDocumentFlow = documentFlow[step];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={cn('grid w-full grid-cols-12 gap-8', className)}>
|
<div>
|
||||||
<Card
|
<div className={cn('grid w-full grid-cols-12 gap-8', className)}>
|
||||||
className="relative col-span-12 rounded-xl before:rounded-xl lg:col-span-6 xl:col-span-7"
|
<Card
|
||||||
gradient
|
className="relative col-span-12 rounded-xl before:rounded-xl lg:col-span-6 xl:col-span-7"
|
||||||
>
|
gradient
|
||||||
<CardContent className="p-2">
|
>
|
||||||
<LazyPDFViewer key={documentData.id} documentData={documentData} />
|
<CardContent className="p-2">
|
||||||
|
<LazyPDFViewer key={documentData.id} documentData={documentData} />
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<div className="col-span-12 lg:col-span-6 xl:col-span-5">
|
||||||
|
<DocumentFlowFormContainer
|
||||||
|
className="lg:h-[calc(100vh-6rem)]"
|
||||||
|
onSubmit={(e) => e.preventDefault()}
|
||||||
|
>
|
||||||
|
<Stepper
|
||||||
|
currentStep={currentDocumentFlow.stepIndex}
|
||||||
|
setCurrentStep={(step) => setStep(EditDocumentSteps[step - 1])}
|
||||||
|
>
|
||||||
|
<AddTitleFormPartial
|
||||||
|
key={recipients.length}
|
||||||
|
documentFlow={documentFlow.title}
|
||||||
|
recipients={recipients}
|
||||||
|
fields={fields}
|
||||||
|
document={document}
|
||||||
|
onSubmit={onAddTitleFormSubmit}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<AddSignersFormPartial
|
||||||
|
key={recipients.length}
|
||||||
|
documentFlow={documentFlow.signers}
|
||||||
|
document={document}
|
||||||
|
recipients={recipients}
|
||||||
|
fields={fields}
|
||||||
|
onSubmit={onAddSignersFormSubmit}
|
||||||
|
/>
|
||||||
|
<AddFieldsFormPartial
|
||||||
|
key={fields.length}
|
||||||
|
documentFlow={documentFlow.fields}
|
||||||
|
recipients={recipients}
|
||||||
|
fields={fields}
|
||||||
|
onSubmit={onAddFieldsFormSubmit}
|
||||||
|
/>
|
||||||
|
<AddSubjectFormPartial
|
||||||
|
key={recipients.length}
|
||||||
|
documentFlow={documentFlow.subject}
|
||||||
|
document={document}
|
||||||
|
recipients={recipients}
|
||||||
|
fields={fields}
|
||||||
|
onSubmit={onAddSubjectFormSubmit}
|
||||||
|
/>
|
||||||
|
</Stepper>
|
||||||
|
</DocumentFlowFormContainer>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Card className="my-8" gradient={true} degrees={200}>
|
||||||
|
<CardContent className="mt-8 flex flex-col">
|
||||||
|
<h2 className="text-foreground text-2xl font-semibold">Comments</h2>
|
||||||
|
<hr className="border-border mb-4 mt-4" />
|
||||||
|
<Comments />
|
||||||
|
<hr className="border-border -mt-4 mb-4" />
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
<div className="col-span-12 lg:col-span-6 xl:col-span-5">
|
|
||||||
<DocumentFlowFormContainer
|
|
||||||
className="lg:h-[calc(100vh-6rem)]"
|
|
||||||
onSubmit={(e) => e.preventDefault()}
|
|
||||||
>
|
|
||||||
<Stepper
|
|
||||||
currentStep={currentDocumentFlow.stepIndex}
|
|
||||||
setCurrentStep={(step) => setStep(EditDocumentSteps[step - 1])}
|
|
||||||
>
|
|
||||||
<AddTitleFormPartial
|
|
||||||
key={recipients.length}
|
|
||||||
documentFlow={documentFlow.title}
|
|
||||||
recipients={recipients}
|
|
||||||
fields={fields}
|
|
||||||
document={document}
|
|
||||||
onSubmit={onAddTitleFormSubmit}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<AddSignersFormPartial
|
|
||||||
key={recipients.length}
|
|
||||||
documentFlow={documentFlow.signers}
|
|
||||||
document={document}
|
|
||||||
recipients={recipients}
|
|
||||||
fields={fields}
|
|
||||||
onSubmit={onAddSignersFormSubmit}
|
|
||||||
/>
|
|
||||||
<AddFieldsFormPartial
|
|
||||||
key={fields.length}
|
|
||||||
documentFlow={documentFlow.fields}
|
|
||||||
recipients={recipients}
|
|
||||||
fields={fields}
|
|
||||||
onSubmit={onAddFieldsFormSubmit}
|
|
||||||
/>
|
|
||||||
<AddSubjectFormPartial
|
|
||||||
key={recipients.length}
|
|
||||||
documentFlow={documentFlow.subject}
|
|
||||||
document={document}
|
|
||||||
recipients={recipients}
|
|
||||||
fields={fields}
|
|
||||||
onSubmit={onAddSubjectFormSubmit}
|
|
||||||
/>
|
|
||||||
</Stepper>
|
|
||||||
</DocumentFlowFormContainer>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
29
apps/web/src/components/comments/comment-card.tsx
Normal file
29
apps/web/src/components/comments/comment-card.tsx
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
import { cn } from '@documenso/ui/lib/utils';
|
||||||
|
import { Button } from '@documenso/ui/primitives/button';
|
||||||
|
|
||||||
|
import { LocaleDate } from '~/components/formatter/locale-date';
|
||||||
|
|
||||||
|
export type CommentCardProps = {
|
||||||
|
comment: any;
|
||||||
|
className?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const CommentCard = ({ comment, className }: CommentCardProps) => {
|
||||||
|
return (
|
||||||
|
<div className={cn('mb-8', className)} key={comment.id}>
|
||||||
|
<p className="font-semibold">{comment.User.name}</p>
|
||||||
|
<p className="text-sm">
|
||||||
|
<LocaleDate
|
||||||
|
date={comment.createdAt}
|
||||||
|
format={{
|
||||||
|
month: 'long',
|
||||||
|
day: 'numeric',
|
||||||
|
year: 'numeric',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</p>
|
||||||
|
<p className="mb-2 mt-2 text-base">{comment.comment}</p>
|
||||||
|
<Button>Reply</Button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
32
apps/web/src/components/forms/comments.tsx
Normal file
32
apps/web/src/components/forms/comments.tsx
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { CornerDownRight } from 'lucide-react';
|
||||||
|
|
||||||
|
import { trpc } from '@documenso/trpc/react';
|
||||||
|
|
||||||
|
import { CommentCard } from '~/components/comments/comment-card';
|
||||||
|
|
||||||
|
export const Comments = () => {
|
||||||
|
const { data: comments } = trpc.comment.getComments.useQuery();
|
||||||
|
|
||||||
|
console.log(comments);
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{comments?.map((comment) => (
|
||||||
|
<div key={comment.id}>
|
||||||
|
<CommentCard comment={comment} className="mb-8" />
|
||||||
|
{comment.replies && comment.replies.length > 0 ? (
|
||||||
|
<div>
|
||||||
|
{comment.replies.map((reply) => (
|
||||||
|
<div className="ml-6 flex" key={reply.id}>
|
||||||
|
<CornerDownRight className="flex shrink-0" />
|
||||||
|
<CommentCard comment={reply} className="ml-6" />
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -124,6 +124,7 @@ export const ProfileForm = ({ className, user }: ProfileFormProps) => {
|
|||||||
className="h-44 w-full"
|
className="h-44 w-full"
|
||||||
containerClassName="rounded-lg border bg-background"
|
containerClassName="rounded-lg border bg-background"
|
||||||
defaultValue={user.signature ?? undefined}
|
defaultValue={user.signature ?? undefined}
|
||||||
|
|
||||||
onChange={(v) => onChange(v ?? '')}
|
onChange={(v) => onChange(v ?? '')}
|
||||||
/>
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
|||||||
@ -45,6 +45,7 @@
|
|||||||
"name": "@documenso/root",
|
"name": "@documenso/root",
|
||||||
"workspaces": [
|
"workspaces": [
|
||||||
"apps/*",
|
"apps/*",
|
||||||
|
|
||||||
"packages/*"
|
"packages/*"
|
||||||
],
|
],
|
||||||
"dependencies": {},
|
"dependencies": {},
|
||||||
|
|||||||
33
packages/lib/server-only/auth/api-auth-middleware.ts
Normal file
33
packages/lib/server-only/auth/api-auth-middleware.ts
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
import { getUserByApiToken } from '@documenso/lib/server-only/public-api/get-user-by-token';
|
||||||
|
|
||||||
|
export type Headers = {
|
||||||
|
headers: {
|
||||||
|
authorization: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const authenticatedMiddleware = <T extends Headers>(fn: (args: T) => Promise<any>) => {
|
||||||
|
return async (args: T) => {
|
||||||
|
if (!args.headers.authorization) {
|
||||||
|
return {
|
||||||
|
status: 401,
|
||||||
|
body: {
|
||||||
|
message: 'Unauthorized access',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await getUserByApiToken({ token: args.headers.authorization });
|
||||||
|
} catch (err) {
|
||||||
|
return {
|
||||||
|
status: 401,
|
||||||
|
body: {
|
||||||
|
message: 'Unauthorized access',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return fn(args);
|
||||||
|
};
|
||||||
|
};
|
||||||
25
packages/lib/server-only/comment/find-comments.ts
Normal file
25
packages/lib/server-only/comment/find-comments.ts
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import { prisma } from '@documenso/prisma';
|
||||||
|
|
||||||
|
export const findComments = async () => {
|
||||||
|
return await prisma.documentComment.findMany({
|
||||||
|
where: {
|
||||||
|
parentId: null,
|
||||||
|
},
|
||||||
|
include: {
|
||||||
|
User: {
|
||||||
|
select: {
|
||||||
|
name: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
replies: {
|
||||||
|
include: {
|
||||||
|
User: {
|
||||||
|
select: {
|
||||||
|
name: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
@ -0,0 +1,17 @@
|
|||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "DocumentComment" (
|
||||||
|
"id" SERIAL NOT NULL,
|
||||||
|
"documentId" INTEGER NOT NULL,
|
||||||
|
"userId" INTEGER NOT NULL,
|
||||||
|
"comment" TEXT NOT NULL,
|
||||||
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
|
||||||
|
CONSTRAINT "DocumentComment_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "DocumentComment" ADD CONSTRAINT "DocumentComment_documentId_fkey" FOREIGN KEY ("documentId") REFERENCES "Document"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "DocumentComment" ADD CONSTRAINT "DocumentComment_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "DocumentComment" ADD COLUMN "parentId" INTEGER;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "DocumentComment" ADD CONSTRAINT "DocumentComment_parentId_fkey" FOREIGN KEY ("parentId") REFERENCES "DocumentComment"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
@ -41,7 +41,8 @@ model User {
|
|||||||
twoFactorEnabled Boolean @default(false)
|
twoFactorEnabled Boolean @default(false)
|
||||||
twoFactorBackupCodes String?
|
twoFactorBackupCodes String?
|
||||||
VerificationToken VerificationToken[]
|
VerificationToken VerificationToken[]
|
||||||
Template Template[]
|
Template Template[]
|
||||||
|
DocumentComment DocumentComment[]
|
||||||
|
|
||||||
@@index([email])
|
@@index([email])
|
||||||
}
|
}
|
||||||
@ -121,27 +122,43 @@ enum DocumentStatus {
|
|||||||
}
|
}
|
||||||
|
|
||||||
model Document {
|
model Document {
|
||||||
id Int @id @default(autoincrement())
|
id Int @id @default(autoincrement())
|
||||||
userId Int
|
userId Int
|
||||||
User User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
User User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||||
title String
|
title String
|
||||||
status DocumentStatus @default(DRAFT)
|
status DocumentStatus @default(DRAFT)
|
||||||
Recipient Recipient[]
|
Recipient Recipient[]
|
||||||
Field Field[]
|
Field Field[]
|
||||||
ShareLink DocumentShareLink[]
|
ShareLink DocumentShareLink[]
|
||||||
documentDataId String
|
documentDataId String
|
||||||
documentData DocumentData @relation(fields: [documentDataId], references: [id], onDelete: Cascade)
|
documentData DocumentData @relation(fields: [documentDataId], references: [id], onDelete: Cascade)
|
||||||
documentMeta DocumentMeta?
|
documentMeta DocumentMeta?
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updatedAt DateTime @default(now()) @updatedAt
|
updatedAt DateTime @default(now()) @updatedAt
|
||||||
completedAt DateTime?
|
completedAt DateTime?
|
||||||
deletedAt DateTime?
|
deletedAt DateTime?
|
||||||
|
DocumentComments DocumentComment[]
|
||||||
|
|
||||||
@@unique([documentDataId])
|
@@unique([documentDataId])
|
||||||
@@index([userId])
|
@@index([userId])
|
||||||
@@index([status])
|
@@index([status])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
model DocumentComment {
|
||||||
|
id Int @id @default(autoincrement())
|
||||||
|
documentId Int
|
||||||
|
userId Int
|
||||||
|
comment String
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @default(now()) @updatedAt
|
||||||
|
parentId Int?
|
||||||
|
parent DocumentComment? @relation("CommentReplies", fields: [parentId], references: [id], onDelete: Cascade)
|
||||||
|
replies DocumentComment[] @relation("CommentReplies")
|
||||||
|
|
||||||
|
Document Document @relation(fields: [documentId], references: [id], onDelete: Cascade)
|
||||||
|
User User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||||
|
}
|
||||||
|
|
||||||
enum DocumentDataType {
|
enum DocumentDataType {
|
||||||
S3_PATH
|
S3_PATH
|
||||||
BYTES
|
BYTES
|
||||||
@ -161,8 +178,8 @@ model DocumentMeta {
|
|||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
subject String?
|
subject String?
|
||||||
message String?
|
message String?
|
||||||
timezone String? @db.Text @default("Etc/UTC")
|
timezone String? @default("Etc/UTC") @db.Text
|
||||||
dateFormat String? @db.Text @default("yyyy-MM-dd hh:mm a")
|
dateFormat String? @default("yyyy-MM-dd hh:mm a") @db.Text
|
||||||
documentId Int @unique
|
documentId Int @unique
|
||||||
document Document @relation(fields: [documentId], references: [id], onDelete: Cascade)
|
document Document @relation(fields: [documentId], references: [id], onDelete: Cascade)
|
||||||
}
|
}
|
||||||
@ -183,19 +200,19 @@ enum SigningStatus {
|
|||||||
}
|
}
|
||||||
|
|
||||||
model Recipient {
|
model Recipient {
|
||||||
id Int @id @default(autoincrement())
|
id Int @id @default(autoincrement())
|
||||||
documentId Int?
|
documentId Int?
|
||||||
templateId Int?
|
templateId Int?
|
||||||
email String @db.VarChar(255)
|
email String @db.VarChar(255)
|
||||||
name String @default("") @db.VarChar(255)
|
name String @default("") @db.VarChar(255)
|
||||||
token String
|
token String
|
||||||
expired DateTime?
|
expired DateTime?
|
||||||
signedAt DateTime?
|
signedAt DateTime?
|
||||||
readStatus ReadStatus @default(NOT_OPENED)
|
readStatus ReadStatus @default(NOT_OPENED)
|
||||||
signingStatus SigningStatus @default(NOT_SIGNED)
|
signingStatus SigningStatus @default(NOT_SIGNED)
|
||||||
sendStatus SendStatus @default(NOT_SENT)
|
sendStatus SendStatus @default(NOT_SENT)
|
||||||
Document Document? @relation(fields: [documentId], references: [id], onDelete: Cascade)
|
Document Document? @relation(fields: [documentId], references: [id], onDelete: Cascade)
|
||||||
Template Template? @relation(fields: [templateId], references: [id], onDelete: Cascade)
|
Template Template? @relation(fields: [templateId], references: [id], onDelete: Cascade)
|
||||||
Field Field[]
|
Field Field[]
|
||||||
Signature Signature[]
|
Signature Signature[]
|
||||||
|
|
||||||
@ -279,10 +296,10 @@ model Template {
|
|||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updatedAt DateTime @default(now()) @updatedAt
|
updatedAt DateTime @default(now()) @updatedAt
|
||||||
|
|
||||||
templateDocumentData DocumentData @relation(fields: [templateDocumentDataId], references: [id], onDelete: Cascade)
|
templateDocumentData DocumentData @relation(fields: [templateDocumentDataId], references: [id], onDelete: Cascade)
|
||||||
User User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
User User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||||
Recipient Recipient[]
|
Recipient Recipient[]
|
||||||
Field Field[]
|
Field Field[]
|
||||||
|
|
||||||
@@unique([templateDocumentDataId])
|
@@unique([templateDocumentDataId])
|
||||||
}
|
}
|
||||||
|
|||||||
9
packages/trpc/server/comment-router/router.ts
Normal file
9
packages/trpc/server/comment-router/router.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import { findComments } from '@documenso/lib/server-only/comment/find-comments';
|
||||||
|
|
||||||
|
import { procedure, router } from '../trpc';
|
||||||
|
|
||||||
|
export const commentRouter = router({
|
||||||
|
getComments: procedure.query(async () => {
|
||||||
|
return await findComments();
|
||||||
|
}),
|
||||||
|
});
|
||||||
0
packages/trpc/server/comment-router/schema.ts
Normal file
0
packages/trpc/server/comment-router/schema.ts
Normal file
@ -1,5 +1,6 @@
|
|||||||
import { adminRouter } from './admin-router/router';
|
import { adminRouter } from './admin-router/router';
|
||||||
import { authRouter } from './auth-router/router';
|
import { authRouter } from './auth-router/router';
|
||||||
|
import { commentRouter } from './comment-router/router';
|
||||||
import { documentRouter } from './document-router/router';
|
import { documentRouter } from './document-router/router';
|
||||||
import { fieldRouter } from './field-router/router';
|
import { fieldRouter } from './field-router/router';
|
||||||
import { profileRouter } from './profile-router/router';
|
import { profileRouter } from './profile-router/router';
|
||||||
@ -21,6 +22,7 @@ export const appRouter = router({
|
|||||||
singleplayer: singleplayerRouter,
|
singleplayer: singleplayerRouter,
|
||||||
twoFactorAuthentication: twoFactorAuthenticationRouter,
|
twoFactorAuthentication: twoFactorAuthenticationRouter,
|
||||||
template: templateRouter,
|
template: templateRouter,
|
||||||
|
comment: commentRouter,
|
||||||
});
|
});
|
||||||
|
|
||||||
export type AppRouter = typeof appRouter;
|
export type AppRouter = typeof appRouter;
|
||||||
|
|||||||
Reference in New Issue
Block a user