Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add signining document feature #156

Merged
merged 43 commits into from
Mar 5, 2024
Merged
Show file tree
Hide file tree
Changes from 30 commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
d297bc6
feat: add order
G3root Feb 23, 2024
93ed1fb
refactor: create document procedure
G3root Feb 24, 2024
1c97b8a
chore: replace button with div
G3root Feb 24, 2024
1a085a6
feat: add sign template mutation schema
G3root Feb 24, 2024
40a00a5
feat: add pdf lib
G3root Feb 24, 2024
82a5963
feat: export type
G3root Feb 24, 2024
dc94542
refactor: bucket handler
G3root Feb 24, 2024
677644f
feat: add sign template procedure
G3root Feb 24, 2024
30f3bfc
fix: name
G3root Feb 24, 2024
ccd01c8
feat: add basic pdf edit
G3root Feb 26, 2024
3e4be54
feat: add get file from s3 utility
G3root Feb 26, 2024
ab9a9ee
fix: algorithm
G3root Feb 26, 2024
eddd910
feat: add new viewport fields
G3root Feb 26, 2024
fa5d7ef
fix: pdf viewer style
G3root Feb 26, 2024
89685ff
fix: type
G3root Feb 26, 2024
1f154c0
feat: use react hook form
G3root Feb 27, 2024
ffd9b10
refactor: field provider
G3root Feb 27, 2024
d7abd62
feat: add signing fields
G3root Feb 27, 2024
65def7e
feat: field renderer
G3root Feb 27, 2024
b5a1955
feat: use splitted component
G3root Feb 27, 2024
43a9b6e
feat: add mode props
G3root Feb 27, 2024
f6059c7
feat: add signing field
G3root Feb 27, 2024
2d83da8
feat: add width
G3root Feb 27, 2024
a7a8b67
feat: position based on container
G3root Feb 27, 2024
02d4981
fix: positionaning
G3root Feb 28, 2024
df52461
fix: positioning
G3root Feb 28, 2024
545f780
feat: add page to table
G3root Feb 28, 2024
02f456b
feat: add page
G3root Feb 28, 2024
57f7146
feat: add page number
G3root Feb 28, 2024
9c07452
feat: add toast
G3root Feb 28, 2024
8110093
feat: add signature pad
G3root Mar 1, 2024
2bdd7ae
feat: add signature field
G3root Mar 1, 2024
05322b5
feat: add packages
G3root Mar 1, 2024
8d8ef16
feat: refactor signature field
G3root Mar 1, 2024
2312811
feat: refactor components and add new components
G3root Mar 1, 2024
240b35f
Merge branch 'main' into sign-document
G3root Mar 1, 2024
e9121cb
chore: add libs
G3root Mar 1, 2024
2be3c0d
fix: height
G3root Mar 2, 2024
64471d4
fix: id length
G3root Mar 4, 2024
4f06f0c
fix: add default value
G3root Mar 4, 2024
9eac4cc
Merge branch 'main' into sign-document
G3root Mar 4, 2024
a28c96c
fix: styles
G3root Mar 4, 2024
1b0ceab
chore: remove unused line
G3root Mar 4, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@
"next-auth": "^4.24.5",
"next-nprogress-bar": "^2.1.2",
"nodemailer": "^6.9.8",
"pdf-lib": "^1.17.1",
"prisma-json-types-generator": "^3.0.3",
"react": "18.2.0",
"react-dom": "18.2.0",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/*
Warnings:

- Added the required column `viewportHeight` to the `TemplateField` table without a default value. This is not possible if the table is not empty.
- Added the required column `viewportWidth` to the `TemplateField` table without a default value. This is not possible if the table is not empty.

*/
-- AlterTable
ALTER TABLE "TemplateField" ADD COLUMN "viewportHeight" INTEGER NOT NULL,
ADD COLUMN "viewportWidth" INTEGER NOT NULL;
8 changes: 8 additions & 0 deletions prisma/migrations/20240228095749_add_page_field/migration.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/*
Warnings:

- Added the required column `page` to the `TemplateField` table without a default value. This is not possible if the table is not empty.

*/
-- AlterTable
ALTER TABLE "TemplateField" ADD COLUMN "page" INTEGER NOT NULL;
25 changes: 14 additions & 11 deletions prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -284,17 +284,20 @@ enum FieldTypes {
}

model TemplateField {
id String @id @default(cuid())
name String
type FieldTypes @default(TEXT)
placeholder String @default("")
required Boolean @default(false)
top Int
left Int
width Int
height Int
template Template @relation(fields: [templateId], references: [id])
templateId String
id String @id @default(cuid())
name String
type FieldTypes @default(TEXT)
placeholder String @default("")
required Boolean @default(false)
top Int
left Int
width Int
height Int
template Template @relation(fields: [templateId], references: [id])
templateId String
viewportHeight Int
viewportWidth Int
page Int

createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { api } from "@/trpc/server";
import { FieldContextProvider } from "@/contexts/field-canvas-context";
import { CanvasToolbar } from "@/components/template/canavs-toolbar";
import { PdfCanvas } from "@/components/template/pdf-canvas";
import { TemplateFieldProvider } from "@/providers/template-field-provider";
import { TemplateFieldForm } from "@/components/template/template-field-form";

const TemplateDetailPage = async ({
params: { templatePublicId },
Expand All @@ -13,12 +14,14 @@ const TemplateDetailPage = async ({
});

return (
<FieldContextProvider fields={fields}>
<div className="grid grid-cols-12">
<CanvasToolbar templatePublicId={templatePublicId} />
<PdfCanvas url={url} />
</div>
</FieldContextProvider>
<TemplateFieldProvider fields={fields}>
<TemplateFieldForm templatePublicId={templatePublicId}>
<div className="grid grid-cols-12">
<CanvasToolbar />
<PdfCanvas url={url} />
</div>
</TemplateFieldForm>
</TemplateFieldProvider>
);
};

Expand Down
40 changes: 40 additions & 0 deletions src/app/(documentSigning)/sign/[token]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { PdfCanvas } from "@/components/template/pdf-canvas";
import { SigningFields } from "@/components/template/signing-fields";
import { TemplateFieldProvider } from "@/providers/template-field-provider";
import { getServerAuthSession } from "@/server/auth";
import { api } from "@/trpc/server";

interface SigningPageProps {
params: {
token: string;
};
}

export default async function SigningPage(props: SigningPageProps) {
const { fields, url } = await api.template.get.query({
publicId: props.params.token,
});

const session = await getServerAuthSession();
const companyPublicId = session?.user.companyPublicId;
return (
<div className="flex min-h-screen bg-gray-50">
<div className="flex h-full flex-grow flex-col">
<div className="mx-auto min-h-full w-full px-5 py-10 lg:px-8 2xl:max-w-screen-xl">
<TemplateFieldProvider fields={fields}>
<div className="grid grid-cols-12">
<PdfCanvas mode="readonly" url={url} />
</div>
</TemplateFieldProvider>
</div>
</div>
<div className="sticky top-0 flex min-h-full w-80 flex-col lg:border-l">
<SigningFields
token={props.params.token}
fields={fields}
companyPublicId={companyPublicId}
/>
</div>
</div>
);
}
21 changes: 21 additions & 0 deletions src/common/uploads.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {
getPresignedGetUrl,
getPresignedPutUrl,
type getPresignedUrlOptions,
} from "@/server/file-uploads";
Expand Down Expand Up @@ -56,3 +57,23 @@ export const uploadFile = async (
};

export type TUploadFile = Awaited<ReturnType<typeof uploadFile>>;

export const getFileFromS3 = async (key: string) => {
const { url } = await getPresignedGetUrl(key);

const response = await fetch(url, {
method: "GET",
});

if (!response.ok) {
throw new Error(
`Failed to get file "${key}", failed with status code ${response.status}`,
);
}

const buffer = await response.arrayBuffer();

const binaryData = new Uint8Array(buffer);

return binaryData;
};
83 changes: 39 additions & 44 deletions src/components/template/canavs-toolbar/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,64 +3,59 @@
import { Button } from "@/components/ui/button";
import * as Toolbar from "@radix-ui/react-toolbar";
import { type FieldTypes } from "@/prisma-enums";
import { useFieldCanvasContext } from "@/contexts/field-canvas-context";
import { FieldTypeData } from "../field-type-data";
import { api } from "@/trpc/react";
import { useToast } from "@/components/ui/use-toast";

interface CanvasToolbarProps {
templatePublicId: string;
}
import { useFormContext, useFormState } from "react-hook-form";
import { FormField } from "@/components/ui/form";
import { type TemplateFieldForm } from "@/providers/template-field-provider";

export function CanvasToolbar({ templatePublicId }: CanvasToolbarProps) {
const { toast } = useToast();
const { handleFieldType, fieldType, fields } = useFieldCanvasContext();
const { mutateAsync } = api.templateField.add.useMutation({
onSuccess: () => {
toast({
variant: "default",
title: "🎉 Successfully created",
description: "Your template fields has been created.",
});
},
export function CanvasToolbar() {
const { control } = useFormContext<TemplateFieldForm>();
const { isDirty } = useFormState({
control,
name: "fields",
});

const handleSave = async () => {
await mutateAsync({ templatePublicId, data: fields });
};
const isDisabled = !isDirty;

return (
<div className="relative z-30 col-span-12 mb-20">
<Toolbar.Root
className="fixed flex w-full max-w-[74.4%] items-center justify-between rounded-md bg-white p-2 shadow-sm"
aria-label="Formatting options"
>
<Toolbar.ToggleGroup
onValueChange={(value) => {
handleFieldType(value as FieldTypes);
}}
value={fieldType}
className="flex gap-x-2"
type="single"
>
{FieldTypeData.map((item) => (
<Toolbar.ToggleItem key={item.value} value={item.value} asChild>
<Button
aria-label={item.label}
className="flex h-auto flex-col gap-y-1 data-[state=on]:bg-accent"
variant="ghost"
>
<span>
<item.icon className="h-4 w-4" aria-hidden />
</span>
<span className="text-xs">{item.label}</span>
</Button>
</Toolbar.ToggleItem>
))}
</Toolbar.ToggleGroup>
<FormField
name="fieldType"
control={control}
render={({ field }) => (
<Toolbar.ToggleGroup
onValueChange={(value) => {
field.onChange(value as FieldTypes);
}}
value={field.value}
className="flex gap-x-2"
type="single"
>
{FieldTypeData.map((item) => (
<Toolbar.ToggleItem key={item.value} value={item.value} asChild>
<Button
aria-label={item.label}
className="flex h-auto flex-col gap-y-1 data-[state=on]:bg-accent"
variant="ghost"
>
<span>
<item.icon className="h-4 w-4" aria-hidden />
</span>
<span className="text-xs">{item.label}</span>
</Button>
</Toolbar.ToggleItem>
))}
</Toolbar.ToggleGroup>
)}
/>

<Toolbar.Button asChild>
<Button disabled={!fields.length} onClick={handleSave}>
<Button disabled={isDisabled} type="submit">
Save
</Button>
</Toolbar.Button>
Expand Down
Loading