Compare commits
22 Commits
feat/doc-c
...
feat/accep
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
536adf6e0a | ||
|
|
be3ab09738 | ||
|
|
1c4a5449bb | ||
|
|
144bd4782b | ||
|
|
857e35c10a | ||
|
|
e9d6c24137 | ||
|
|
dd5f39205a | ||
|
|
f6ce7be61f | ||
|
|
9979d32a56 | ||
|
|
daa541d570 | ||
|
|
1d91a9e813 | ||
|
|
560352492d | ||
|
|
075e15d428 | ||
|
|
27e5ef0a51 | ||
|
|
84b0c2756b | ||
|
|
58b3a127ea | ||
|
|
7e71e06e04 | ||
|
|
0b8e84b6b7 | ||
|
|
e17e4566cd | ||
|
|
d73ef57794 | ||
|
|
a71078cbd5 | ||
|
|
6ad3edb6c8 |
@@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
title: Announcing Pre-Seed and Open Metrics
|
title: Announcing Pre-Seed and Open Metrics
|
||||||
description: We are exicited to report the closing of our Pre-Seed round. You can find the juicy details on our new /open page. Yes, it was signed using Documenso.
|
description: We are excited to report the closing of our Pre-Seed round. You can find the juicy details on our new /open page. Yes, it was signed using Documenso.
|
||||||
authorName: 'Timur Ercan'
|
authorName: 'Timur Ercan'
|
||||||
authorImage: '/blog/blog-author-timur.jpeg'
|
authorImage: '/blog/blog-author-timur.jpeg'
|
||||||
authorRole: 'Co-Founder'
|
authorRole: 'Co-Founder'
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ We kicked off [Malfunction Mania](https://documenso.com/blog/malfunction-mania)
|
|||||||
|
|
||||||
## Documenso Merch Shop
|
## Documenso Merch Shop
|
||||||
|
|
||||||
The shirt will be available in our [merch shop](https://documen.so/shop) via a unique discount code. While the shirt will be gone after Malfunction Mania, the shop is here to stay and provide a well-deserved reward for great community members and contributors. All items can be earned by contrinuting to Documenso.
|
The shirt will be available in our [merch shop](https://documen.so/shop) via a unique discount code. While the shirt will be gone after Malfunction Mania, the shop is here to stay and provide a well-deserved reward for great community members and contributors. All items can be earned by contributing to Documenso.
|
||||||
|
|
||||||
<figure>
|
<figure>
|
||||||
<MdxNextImage
|
<MdxNextImage
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import type { HTMLAttributes, KeyboardEvent } from 'react';
|
|
||||||
import { useMemo, useState } from 'react';
|
import { useMemo, useState } from 'react';
|
||||||
|
import type { HTMLAttributes, KeyboardEvent } from 'react';
|
||||||
|
|
||||||
import { zodResolver } from '@hookform/resolvers/zod';
|
import { zodResolver } from '@hookform/resolvers/zod';
|
||||||
import { AnimatePresence, motion } from 'framer-motion';
|
import { AnimatePresence, motion } from 'framer-motion';
|
||||||
@@ -355,6 +355,7 @@ export const Widget = ({ className, children, ...props }: WidgetProps) => {
|
|||||||
<div
|
<div
|
||||||
className="absolute inset-x-0 bottom-0 flex cursor-auto items-center justify-between px-4 pb-2"
|
className="absolute inset-x-0 bottom-0 flex cursor-auto items-center justify-between px-4 pb-2"
|
||||||
onClick={(e) => e.stopPropagation()}
|
onClick={(e) => e.stopPropagation()}
|
||||||
|
onKeyDown={(e) => e.stopPropagation()}
|
||||||
>
|
>
|
||||||
<Input
|
<Input
|
||||||
id="signatureText"
|
id="signatureText"
|
||||||
@@ -392,10 +393,11 @@ export const Widget = ({ className, children, ...props }: WidgetProps) => {
|
|||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
|
|
||||||
<DialogDescription>
|
<DialogDescription>
|
||||||
By signing you signal your support of Documenso's mission in a <br></br>
|
By signing you signal your support of Documenso's mission in a <br />
|
||||||
<strong>non-legally binding, but heartfelt way</strong>. <br></br>
|
<strong>non-legally binding, but heartfelt way</strong>. <br />
|
||||||
<br></br>You also unlock the option to purchase the early supporter plan including
|
<br />
|
||||||
everything we build this year for fixed price.
|
You also unlock the option to purchase the early supporter plan including everything we
|
||||||
|
build this year for fixed price.
|
||||||
</DialogDescription>
|
</DialogDescription>
|
||||||
|
|
||||||
<SignaturePad
|
<SignaturePad
|
||||||
|
|||||||
@@ -17,6 +17,10 @@ const FONT_CAVEAT_BYTES = fs.readFileSync(
|
|||||||
path.join(__dirname, '../../packages/assets/fonts/caveat.ttf'),
|
path.join(__dirname, '../../packages/assets/fonts/caveat.ttf'),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const FONT_DANCING_SCRIPT_BYTES = fs.readFileSync(
|
||||||
|
path.join(__dirname, '../../packages/assets/fonts/dancing-script.ttf'),
|
||||||
|
);
|
||||||
|
|
||||||
/** @type {import('next').NextConfig} */
|
/** @type {import('next').NextConfig} */
|
||||||
const config = {
|
const config = {
|
||||||
output: process.env.DOCKER_OUTPUT ? 'standalone' : undefined,
|
output: process.env.DOCKER_OUTPUT ? 'standalone' : undefined,
|
||||||
@@ -40,6 +44,7 @@ const config = {
|
|||||||
APP_VERSION: version,
|
APP_VERSION: version,
|
||||||
NEXT_PUBLIC_PROJECT: 'web',
|
NEXT_PUBLIC_PROJECT: 'web',
|
||||||
FONT_CAVEAT_URI: `data:font/ttf;base64,${FONT_CAVEAT_BYTES.toString('base64')}`,
|
FONT_CAVEAT_URI: `data:font/ttf;base64,${FONT_CAVEAT_BYTES.toString('base64')}`,
|
||||||
|
FONT_DANCING_SCRIPT_URI: `data:font/ttf;base64,${FONT_DANCING_SCRIPT_BYTES.toString('base64')}`,
|
||||||
},
|
},
|
||||||
modularizeImports: {
|
modularizeImports: {
|
||||||
'lucide-react': {
|
'lucide-react': {
|
||||||
|
|||||||
@@ -24,8 +24,6 @@ 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;
|
||||||
@@ -181,70 +179,60 @@ export const EditDocumentForm = ({
|
|||||||
const currentDocumentFlow = documentFlow[step];
|
const currentDocumentFlow = documentFlow[step];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div className={cn('grid w-full grid-cols-12 gap-8', className)}>
|
||||||
<div className={cn('grid w-full grid-cols-12 gap-8', className)}>
|
<Card
|
||||||
<Card
|
className="relative col-span-12 rounded-xl before:rounded-xl lg:col-span-6 xl:col-span-7"
|
||||||
className="relative col-span-12 rounded-xl before:rounded-xl lg:col-span-6 xl:col-span-7"
|
gradient
|
||||||
gradient
|
>
|
||||||
>
|
<CardContent className="p-2">
|
||||||
<CardContent className="p-2">
|
<LazyPDFViewer key={documentData.id} documentData={documentData} />
|
||||||
<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>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -4,10 +4,18 @@ import { useMemo, useState } from 'react';
|
|||||||
|
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
|
|
||||||
|
import { zodResolver } from '@hookform/resolvers/zod';
|
||||||
import { useSession } from 'next-auth/react';
|
import { useSession } from 'next-auth/react';
|
||||||
import { useForm } from 'react-hook-form';
|
import { useForm } from 'react-hook-form';
|
||||||
|
import { z } from 'zod';
|
||||||
|
|
||||||
import { useAnalytics } from '@documenso/lib/client-only/hooks/use-analytics';
|
import { useAnalytics } from '@documenso/lib/client-only/hooks/use-analytics';
|
||||||
|
import { useElementScaleSize } from '@documenso/lib/client-only/hooks/use-element-scale-size';
|
||||||
|
import { useFieldPageCoords } from '@documenso/lib/client-only/hooks/use-field-page-coords';
|
||||||
|
import {
|
||||||
|
DEFAULT_HANDWRITING_FONT_SIZE,
|
||||||
|
MIN_HANDWRITING_FONT_SIZE,
|
||||||
|
} from '@documenso/lib/constants/pdf';
|
||||||
import { sortFieldsByPosition, validateFieldsInserted } from '@documenso/lib/utils/fields';
|
import { sortFieldsByPosition, validateFieldsInserted } from '@documenso/lib/utils/fields';
|
||||||
import type { Document, Field, Recipient } from '@documenso/prisma/client';
|
import type { Document, Field, Recipient } from '@documenso/prisma/client';
|
||||||
import { trpc } from '@documenso/trpc/react';
|
import { trpc } from '@documenso/trpc/react';
|
||||||
@@ -28,7 +36,27 @@ export type SigningFormProps = {
|
|||||||
fields: Field[];
|
fields: Field[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export const SigningForm = ({ document, recipient, fields }: SigningFormProps) => {
|
const ZSigningpadSchema = z.union([
|
||||||
|
z.object({
|
||||||
|
signatureDataUrl: z.string().min(1),
|
||||||
|
signatureText: z.null().or(z.string().max(0)),
|
||||||
|
}),
|
||||||
|
z.object({
|
||||||
|
signatureDataUrl: z.null().or(z.string().max(0)),
|
||||||
|
signatureText: z.string().trim().min(1),
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
|
||||||
|
export type TSigningpadSchema = z.infer<typeof ZSigningpadSchema>;
|
||||||
|
|
||||||
|
export const SigningForm = ({ document: _document, recipient, fields }: SigningFormProps) => {
|
||||||
|
const fontVariable = '--font-signature';
|
||||||
|
const minFontSize = MIN_HANDWRITING_FONT_SIZE;
|
||||||
|
const maxFontSize = DEFAULT_HANDWRITING_FONT_SIZE;
|
||||||
|
const fontVariableValue = getComputedStyle(document.documentElement).getPropertyValue(
|
||||||
|
fontVariable,
|
||||||
|
);
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const analytics = useAnalytics();
|
const analytics = useAnalytics();
|
||||||
const { data: session } = useSession();
|
const { data: session } = useSession();
|
||||||
@@ -41,9 +69,24 @@ export const SigningForm = ({ document, recipient, fields }: SigningFormProps) =
|
|||||||
trpc.recipient.completeDocumentWithToken.useMutation();
|
trpc.recipient.completeDocumentWithToken.useMutation();
|
||||||
|
|
||||||
const {
|
const {
|
||||||
|
register,
|
||||||
handleSubmit,
|
handleSubmit,
|
||||||
|
setValue,
|
||||||
|
watch,
|
||||||
formState: { isSubmitting },
|
formState: { isSubmitting },
|
||||||
} = useForm();
|
} = useForm<TSigningpadSchema>({
|
||||||
|
mode: 'onChange',
|
||||||
|
defaultValues: {
|
||||||
|
signatureDataUrl: signature || null,
|
||||||
|
signatureText: '',
|
||||||
|
},
|
||||||
|
resolver: zodResolver(ZSigningpadSchema),
|
||||||
|
});
|
||||||
|
|
||||||
|
const { height, width } = useFieldPageCoords(fields.find((field) => field.type === 'SIGNATURE')!);
|
||||||
|
|
||||||
|
const signatureDataUrl = watch('signatureDataUrl');
|
||||||
|
const signatureText = watch('signatureText');
|
||||||
|
|
||||||
const uninsertedFields = useMemo(() => {
|
const uninsertedFields = useMemo(() => {
|
||||||
return sortFieldsByPosition(fields.filter((field) => !field.inserted));
|
return sortFieldsByPosition(fields.filter((field) => !field.inserted));
|
||||||
@@ -65,18 +108,30 @@ export const SigningForm = ({ document, recipient, fields }: SigningFormProps) =
|
|||||||
|
|
||||||
await completeDocumentWithToken({
|
await completeDocumentWithToken({
|
||||||
token: recipient.token,
|
token: recipient.token,
|
||||||
documentId: document.id,
|
documentId: _document.id,
|
||||||
});
|
});
|
||||||
|
|
||||||
analytics.capture('App: Recipient has completed signing', {
|
analytics.capture('App: Recipient has completed signing', {
|
||||||
signerId: recipient.id,
|
signerId: recipient.id,
|
||||||
documentId: document.id,
|
documentId: _document.id,
|
||||||
timestamp: new Date().toISOString(),
|
timestamp: new Date().toISOString(),
|
||||||
});
|
});
|
||||||
|
|
||||||
router.push(`/sign/${recipient.token}/complete`);
|
router.push(`/sign/${recipient.token}/complete`);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const scalingFactor = useElementScaleSize(
|
||||||
|
{
|
||||||
|
height,
|
||||||
|
width,
|
||||||
|
},
|
||||||
|
signatureText || '',
|
||||||
|
maxFontSize,
|
||||||
|
fontVariableValue,
|
||||||
|
);
|
||||||
|
|
||||||
|
const fontSize = maxFontSize * scalingFactor;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form
|
<form
|
||||||
className={cn(
|
className={cn(
|
||||||
@@ -128,15 +183,79 @@ export const SigningForm = ({ document, recipient, fields }: SigningFormProps) =
|
|||||||
<div>
|
<div>
|
||||||
<Label htmlFor="Signature">Signature</Label>
|
<Label htmlFor="Signature">Signature</Label>
|
||||||
|
|
||||||
<Card className="mt-2" gradient degrees={-120}>
|
<Card id="signature" className="mt-4" degrees={-120} gradient>
|
||||||
<CardContent className="p-0">
|
<CardContent role="button" className="relative cursor-pointer pt-6">
|
||||||
<SignaturePad
|
<div className="flex h-44 max-w-[18rem] items-center justify-center pb-6">
|
||||||
className="h-44 w-full"
|
{!signatureText && (
|
||||||
defaultValue={signature ?? undefined}
|
<SignaturePad
|
||||||
onChange={(value) => {
|
className="h-44"
|
||||||
setSignature(value);
|
defaultValue={signature ?? undefined}
|
||||||
}}
|
clearSignatureClassName="absolute -bottom-6 -right-2 z-10 cursor-pointer"
|
||||||
/>
|
undoSignatureClassName="absolute -top-32 -left-4 z-10 cursor-pointer"
|
||||||
|
onChange={(value) => {
|
||||||
|
setSignature(value);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{signatureText && (
|
||||||
|
<p
|
||||||
|
style={{
|
||||||
|
fontSize: `clamp(${minFontSize}px, ${fontSize}px, ${maxFontSize}px)`,
|
||||||
|
fontFamily: `var(${fontVariable})`,
|
||||||
|
}}
|
||||||
|
className={cn(
|
||||||
|
'text-foreground font-signature max-w-[18rem] text-4xl font-semibold',
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{signatureText}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
className="absolute inset-x-0 bottom-0 flex cursor-auto items-end justify-between px-4 pb-1 pt-2"
|
||||||
|
onClick={(e) => e.stopPropagation()}
|
||||||
|
onKeyDown={(e) => e.stopPropagation()}
|
||||||
|
>
|
||||||
|
<Input
|
||||||
|
id="signatureText"
|
||||||
|
className="text-foreground placeholder:text-muted-foreground max-w-[15rem] border-0 border-none bg-transparent p-0 text-sm focus-visible:ring-transparent"
|
||||||
|
placeholder="Draw or type your name here"
|
||||||
|
disabled={isSubmitting || signature?.startsWith('data:')}
|
||||||
|
{...register('signatureText', {
|
||||||
|
onChange: (e) => {
|
||||||
|
if (e.target.value !== '') {
|
||||||
|
setValue('signatureDataUrl', null);
|
||||||
|
}
|
||||||
|
|
||||||
|
setValue('signatureText', e.target.value);
|
||||||
|
},
|
||||||
|
|
||||||
|
onBlur: (e) => {
|
||||||
|
if (e.target.value === '') {
|
||||||
|
return setValue('signatureText', '');
|
||||||
|
}
|
||||||
|
|
||||||
|
setSignature(e.target.value.trimStart());
|
||||||
|
},
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
{signatureText && (
|
||||||
|
<div className="absolute bottom-3 right-4 z-10 cursor-pointer">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="focus-visible:ring-ring ring-offset-background text-muted-foreground rounded-full p-0 text-xs focus-visible:outline-none focus-visible:ring-2"
|
||||||
|
onClick={() => {
|
||||||
|
setValue('signatureText', '');
|
||||||
|
setValue('signatureDataUrl', null);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Clear Signature
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
@@ -157,7 +276,7 @@ export const SigningForm = ({ document, recipient, fields }: SigningFormProps) =
|
|||||||
<SignDialog
|
<SignDialog
|
||||||
isSubmitting={isSubmitting}
|
isSubmitting={isSubmitting}
|
||||||
onSignatureComplete={handleSubmit(onFormSubmit)}
|
onSignatureComplete={handleSubmit(onFormSubmit)}
|
||||||
document={document}
|
document={_document}
|
||||||
fields={fields}
|
fields={fields}
|
||||||
fieldsValidated={fieldsValidated}
|
fieldsValidated={fieldsValidated}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ export const SignatureField = ({ field, recipient }: SignatureFieldProps) => {
|
|||||||
token: recipient.token,
|
token: recipient.token,
|
||||||
fieldId: field.id,
|
fieldId: field.id,
|
||||||
value,
|
value,
|
||||||
isBase64: true,
|
isBase64: typeof value === 'string' && value.startsWith('data:image/png;base64,'),
|
||||||
});
|
});
|
||||||
|
|
||||||
if (source === 'local' && !providedSignature) {
|
if (source === 'local' && !providedSignature) {
|
||||||
|
|||||||
@@ -95,8 +95,7 @@ export function CommandMenu({ open, onOpenChange }: CommandMenuProps) {
|
|||||||
|
|
||||||
const currentPage = pages[pages.length - 1];
|
const currentPage = pages[pages.length - 1];
|
||||||
|
|
||||||
const toggleOpen = (e: KeyboardEvent) => {
|
const toggleOpen = () => {
|
||||||
e.preventDefault();
|
|
||||||
setIsOpen((isOpen) => !isOpen);
|
setIsOpen((isOpen) => !isOpen);
|
||||||
onOpenChange?.(!isOpen);
|
onOpenChange?.(!isOpen);
|
||||||
|
|
||||||
@@ -136,7 +135,7 @@ export function CommandMenu({ open, onOpenChange }: CommandMenuProps) {
|
|||||||
const goToDocuments = useCallback(() => push(DOCUMENTS_PAGES[0].path), [push]);
|
const goToDocuments = useCallback(() => push(DOCUMENTS_PAGES[0].path), [push]);
|
||||||
const goToTemplates = useCallback(() => push(TEMPLATES_PAGES[0].path), [push]);
|
const goToTemplates = useCallback(() => push(TEMPLATES_PAGES[0].path), [push]);
|
||||||
|
|
||||||
useHotkeys(['ctrl+k', 'meta+k'], toggleOpen);
|
useHotkeys(['ctrl+k', 'meta+k'], toggleOpen, { preventDefault: true });
|
||||||
useHotkeys(SETTINGS_PAGE_SHORTCUT, goToSettings);
|
useHotkeys(SETTINGS_PAGE_SHORTCUT, goToSettings);
|
||||||
useHotkeys(DOCUMENTS_PAGE_SHORTCUT, goToDocuments);
|
useHotkeys(DOCUMENTS_PAGE_SHORTCUT, goToDocuments);
|
||||||
useHotkeys(TEMPLATES_PAGE_SHORTCUT, goToTemplates);
|
useHotkeys(TEMPLATES_PAGE_SHORTCUT, goToTemplates);
|
||||||
|
|||||||
@@ -1,29 +0,0 @@
|
|||||||
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>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
'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,7 +124,6 @@ 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,7 +45,6 @@
|
|||||||
"name": "@documenso/root",
|
"name": "@documenso/root",
|
||||||
"workspaces": [
|
"workspaces": [
|
||||||
"apps/*",
|
"apps/*",
|
||||||
|
|
||||||
"packages/*"
|
"packages/*"
|
||||||
],
|
],
|
||||||
"dependencies": {},
|
"dependencies": {},
|
||||||
|
|||||||
BIN
packages/assets/fonts/dancing-script.ttf
Normal file
BIN
packages/assets/fonts/dancing-script.ttf
Normal file
Binary file not shown.
@@ -1,7 +1,7 @@
|
|||||||
import { APP_BASE_URL } from './app';
|
import { APP_BASE_URL } from './app';
|
||||||
|
|
||||||
export const DEFAULT_STANDARD_FONT_SIZE = 15;
|
export const DEFAULT_STANDARD_FONT_SIZE = 15;
|
||||||
export const DEFAULT_HANDWRITING_FONT_SIZE = 50;
|
export const DEFAULT_HANDWRITING_FONT_SIZE = 30;
|
||||||
|
|
||||||
export const MIN_STANDARD_FONT_SIZE = 8;
|
export const MIN_STANDARD_FONT_SIZE = 8;
|
||||||
export const MIN_HANDWRITING_FONT_SIZE = 20;
|
export const MIN_HANDWRITING_FONT_SIZE = 20;
|
||||||
|
|||||||
@@ -1,33 +0,0 @@
|
|||||||
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);
|
|
||||||
};
|
|
||||||
};
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
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,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
|
||||||
@@ -12,7 +12,7 @@ import { isSignatureFieldType } from '@documenso/prisma/guards/is-signature-fiel
|
|||||||
import type { FieldWithSignature } from '@documenso/prisma/types/field-with-signature';
|
import type { FieldWithSignature } from '@documenso/prisma/types/field-with-signature';
|
||||||
|
|
||||||
export const insertFieldInPDF = async (pdf: PDFDocument, field: FieldWithSignature) => {
|
export const insertFieldInPDF = async (pdf: PDFDocument, field: FieldWithSignature) => {
|
||||||
const fontCaveat = await fetch(process.env.FONT_CAVEAT_URI).then(async (res) =>
|
const fontDancingScript = await fetch(process.env.FONT_DANCING_SCRIPT_URI).then(async (res) =>
|
||||||
res.arrayBuffer(),
|
res.arrayBuffer(),
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -40,14 +40,28 @@ 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);
|
||||||
|
|
||||||
const font = await pdf.embedFont(isSignatureField ? fontCaveat : StandardFonts.Helvetica);
|
const font = await pdf.embedFont(isSignatureField ? fontDancingScript : StandardFonts.Helvetica, {
|
||||||
|
subset: true,
|
||||||
|
features: {
|
||||||
|
liga: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
if (field.type === FieldType.SIGNATURE || field.type === FieldType.FREE_SIGNATURE) {
|
if (field.type === FieldType.SIGNATURE || field.type === FieldType.FREE_SIGNATURE) {
|
||||||
await pdf.embedFont(fontCaveat);
|
await pdf.embedFont(fontDancingScript, {
|
||||||
|
subset: true,
|
||||||
|
features: {
|
||||||
|
liga: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const CUSTOM_TEXT = field.customText || field.Signature?.typedSignature || '';
|
||||||
|
|
||||||
const isInsertingImage =
|
const isInsertingImage =
|
||||||
isSignatureField && typeof field.Signature?.signatureImageAsBase64 === 'string';
|
isSignatureField &&
|
||||||
|
typeof field.Signature?.signatureImageAsBase64 === 'string' &&
|
||||||
|
field.Signature?.signatureImageAsBase64.startsWith('data:image/png;base64,');
|
||||||
|
|
||||||
if (isSignatureField && isInsertingImage) {
|
if (isSignatureField && isInsertingImage) {
|
||||||
const image = await pdf.embedPng(field.Signature?.signatureImageAsBase64 ?? '');
|
const image = await pdf.embedPng(field.Signature?.signatureImageAsBase64 ?? '');
|
||||||
@@ -73,13 +87,13 @@ export const insertFieldInPDF = async (pdf: PDFDocument, field: FieldWithSignatu
|
|||||||
height: imageHeight,
|
height: imageHeight,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
let textWidth = font.widthOfTextAtSize(field.customText, fontSize);
|
let textWidth = font.widthOfTextAtSize(CUSTOM_TEXT, fontSize);
|
||||||
const textHeight = font.heightAtSize(fontSize);
|
const textHeight = font.heightAtSize(fontSize);
|
||||||
|
|
||||||
const scalingFactor = Math.min(fieldWidth / textWidth, fieldHeight / textHeight, 1);
|
const scalingFactor = Math.min(fieldWidth / textWidth, fieldHeight / textHeight, 1);
|
||||||
|
|
||||||
fontSize = Math.max(Math.min(fontSize * scalingFactor, maxFontSize), minFontSize);
|
fontSize = Math.max(Math.min(fontSize * scalingFactor, maxFontSize), minFontSize);
|
||||||
textWidth = font.widthOfTextAtSize(field.customText, fontSize);
|
textWidth = font.widthOfTextAtSize(CUSTOM_TEXT, fontSize);
|
||||||
|
|
||||||
const textX = fieldX + (fieldWidth - textWidth) / 2;
|
const textX = fieldX + (fieldWidth - textWidth) / 2;
|
||||||
let textY = fieldY + (fieldHeight - textHeight) / 2;
|
let textY = fieldY + (fieldHeight - textHeight) / 2;
|
||||||
@@ -87,7 +101,7 @@ 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
|
||||||
textY = pageHeight - textY - textHeight;
|
textY = pageHeight - textY - textHeight;
|
||||||
|
|
||||||
page.drawText(field.customText, {
|
page.drawText(CUSTOM_TEXT, {
|
||||||
x: textX,
|
x: textX,
|
||||||
y: textY,
|
y: textY,
|
||||||
size: fontSize,
|
size: fontSize,
|
||||||
|
|||||||
@@ -1,17 +0,0 @@
|
|||||||
-- 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;
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
-- 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,8 +41,7 @@ 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])
|
||||||
}
|
}
|
||||||
@@ -122,43 +121,27 @@ 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
|
||||||
@@ -178,8 +161,8 @@ model DocumentMeta {
|
|||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
subject String?
|
subject String?
|
||||||
message String?
|
message String?
|
||||||
timezone String? @default("Etc/UTC") @db.Text
|
timezone String? @db.Text @default("Etc/UTC")
|
||||||
dateFormat String? @default("yyyy-MM-dd hh:mm a") @db.Text
|
dateFormat String? @db.Text @default("yyyy-MM-dd hh:mm a")
|
||||||
documentId Int @unique
|
documentId Int @unique
|
||||||
document Document @relation(fields: [documentId], references: [id], onDelete: Cascade)
|
document Document @relation(fields: [documentId], references: [id], onDelete: Cascade)
|
||||||
}
|
}
|
||||||
@@ -200,19 +183,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[]
|
||||||
|
|
||||||
@@ -296,10 +279,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])
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +0,0 @@
|
|||||||
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();
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
@@ -238,7 +238,6 @@ export const documentRouter = router({
|
|||||||
userId: ctx.user.id,
|
userId: ctx.user.id,
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log(err);
|
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: 'BAD_REQUEST',
|
code: 'BAD_REQUEST',
|
||||||
message: 'We are unable to duplicate this document. Please try again later.',
|
message: 'We are unable to duplicate this document. Please try again later.',
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
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';
|
||||||
@@ -22,7 +21,6 @@ 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;
|
||||||
|
|||||||
1
packages/tsconfig/process-env.d.ts
vendored
1
packages/tsconfig/process-env.d.ts
vendored
@@ -64,6 +64,7 @@ declare namespace NodeJS {
|
|||||||
|
|
||||||
DEPLOYMENT_TARGET?: 'webapp' | 'marketing';
|
DEPLOYMENT_TARGET?: 'webapp' | 'marketing';
|
||||||
FONT_CAVEAT_URI: string;
|
FONT_CAVEAT_URI: string;
|
||||||
|
FONT_DANCING_SCRIPT_URI: string;
|
||||||
|
|
||||||
POSTGRES_URL?: string;
|
POSTGRES_URL?: string;
|
||||||
DATABASE_URL?: string;
|
DATABASE_URL?: string;
|
||||||
|
|||||||
@@ -121,6 +121,7 @@ export const FieldItem = ({
|
|||||||
<button
|
<button
|
||||||
className="text-muted-foreground/50 hover:text-muted-foreground/80 bg-background absolute -right-2 -top-2 z-20 flex h-8 w-8 items-center justify-center rounded-full border"
|
className="text-muted-foreground/50 hover:text-muted-foreground/80 bg-background absolute -right-2 -top-2 z-20 flex h-8 w-8 items-center justify-center rounded-full border"
|
||||||
onClick={() => onRemove?.()}
|
onClick={() => onRemove?.()}
|
||||||
|
onTouchEnd={() => onRemove?.()}
|
||||||
>
|
>
|
||||||
<Trash className="h-4 w-4" />
|
<Trash className="h-4 w-4" />
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -16,12 +16,16 @@ const DPI = 2;
|
|||||||
export type SignaturePadProps = Omit<HTMLAttributes<HTMLCanvasElement>, 'onChange'> & {
|
export type SignaturePadProps = Omit<HTMLAttributes<HTMLCanvasElement>, 'onChange'> & {
|
||||||
onChange?: (_signatureDataUrl: string | null) => void;
|
onChange?: (_signatureDataUrl: string | null) => void;
|
||||||
containerClassName?: string;
|
containerClassName?: string;
|
||||||
|
clearSignatureClassName?: string;
|
||||||
|
undoSignatureClassName?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const SignaturePad = ({
|
export const SignaturePad = ({
|
||||||
className,
|
className,
|
||||||
containerClassName,
|
containerClassName,
|
||||||
defaultValue,
|
defaultValue,
|
||||||
|
clearSignatureClassName,
|
||||||
|
undoSignatureClassName,
|
||||||
onChange,
|
onChange,
|
||||||
...props
|
...props
|
||||||
}: SignaturePadProps) => {
|
}: SignaturePadProps) => {
|
||||||
@@ -227,7 +231,7 @@ export const SignaturePad = ({
|
|||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div className="absolute bottom-4 right-4 flex gap-2">
|
<div className={cn('absolute bottom-4 right-4', clearSignatureClassName)}>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="focus-visible:ring-ring ring-offset-background text-muted-foreground/60 hover:text-muted-foreground rounded-full p-0 text-xs focus-visible:outline-none focus-visible:ring-2"
|
className="focus-visible:ring-ring ring-offset-background text-muted-foreground/60 hover:text-muted-foreground rounded-full p-0 text-xs focus-visible:outline-none focus-visible:ring-2"
|
||||||
@@ -238,7 +242,7 @@ export const SignaturePad = ({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{lines.length > 0 && (
|
{lines.length > 0 && (
|
||||||
<div className="absolute bottom-4 left-4 flex gap-2">
|
<div className={cn('absolute bottom-4 left-4 flex gap-2', undoSignatureClassName)}>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
title="undo"
|
title="undo"
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ export const ThemeSwitcher = () => {
|
|||||||
>
|
>
|
||||||
{isMounted && theme === THEMES_TYPE.LIGHT && (
|
{isMounted && theme === THEMES_TYPE.LIGHT && (
|
||||||
<motion.div
|
<motion.div
|
||||||
className="bg-background absolute inset-0 rounded-full mix-blend-exclusion"
|
className="bg-background absolute inset-0 rounded-full mix-blend-color-burn"
|
||||||
layoutId="selected-theme"
|
layoutId="selected-theme"
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -86,6 +86,7 @@
|
|||||||
"VERCEL_URL",
|
"VERCEL_URL",
|
||||||
"DEPLOYMENT_TARGET",
|
"DEPLOYMENT_TARGET",
|
||||||
"FONT_CAVEAT_URI",
|
"FONT_CAVEAT_URI",
|
||||||
|
"FONT_DANCING_SCRIPT_URI",
|
||||||
"POSTGRES_URL",
|
"POSTGRES_URL",
|
||||||
"DATABASE_URL",
|
"DATABASE_URL",
|
||||||
"POSTGRES_PRISMA_URL",
|
"POSTGRES_PRISMA_URL",
|
||||||
|
|||||||
Reference in New Issue
Block a user