feat: document authoring

This commit is contained in:
Mythie
2023-08-17 19:56:18 +10:00
parent 9fdc9dcbf7
commit 48ceb1e5c7
50 changed files with 2037 additions and 93 deletions

View File

@ -12,7 +12,7 @@ export type StackAvatarProps = {
first?: boolean;
zIndex?: string;
fallbackText?: string;
type: 'unsigned' | 'waiting' | 'completed';
type: 'unsigned' | 'waiting' | 'opened' | 'completed';
};
export const StackAvatar = ({ first, zIndex, fallbackText, type }: StackAvatarProps) => {
@ -28,6 +28,9 @@ export const StackAvatar = ({ first, zIndex, fallbackText, type }: StackAvatarPr
case 'unsigned':
classes = 'bg-dawn-200 text-dawn-900';
break;
case 'opened':
classes = 'bg-yellow-200 text-yellow-700';
break;
case 'waiting':
classes = 'bg-water text-water-700';
break;

View File

@ -13,15 +13,19 @@ import { StackAvatars } from './stack-avatars';
export const StackAvatarsWithTooltip = ({ recipients }: { recipients: Recipient[] }) => {
const waitingRecipients = recipients.filter(
(recipient) => recipient.sendStatus === 'SENT' && recipient.signingStatus === 'NOT_SIGNED',
(recipient) => getRecipientType(recipient) === 'waiting',
);
const openedRecipients = recipients.filter(
(recipient) => getRecipientType(recipient) === 'opened',
);
const completedRecipients = recipients.filter(
(recipient) => recipient.sendStatus === 'SENT' && recipient.signingStatus === 'SIGNED',
(recipient) => getRecipientType(recipient) === 'completed',
);
const uncompletedRecipients = recipients.filter(
(recipient) => recipient.sendStatus === 'NOT_SENT' && recipient.signingStatus === 'NOT_SIGNED',
(recipient) => getRecipientType(recipient) === 'unsigned',
);
return (
@ -66,6 +70,23 @@ export const StackAvatarsWithTooltip = ({ recipients }: { recipients: Recipient[
</div>
)}
{openedRecipients.length > 0 && (
<div>
<h1 className="text-base font-medium">Opened</h1>
{openedRecipients.map((recipient: Recipient) => (
<div key={recipient.id} className="my-1 flex items-center gap-2">
<StackAvatar
first={true}
key={recipient.id}
type={getRecipientType(recipient)}
fallbackText={initials(recipient.name)}
/>
<span className="text-sm text-gray-500">{recipient.email}</span>
</div>
))}
</div>
)}
{uncompletedRecipients.length > 0 && (
<div>
<h1 className="text-base font-medium">Uncompleted</h1>

View File

@ -0,0 +1,19 @@
'use client';
import dynamic from 'next/dynamic';
import { Loader } from 'lucide-react';
export const LazyPDFViewer = dynamic(
async () => import('~/components/(dashboard)/pdf-viewer/pdf-viewer'),
{
ssr: false,
loading: () => (
<div className="dark:bg-background flex min-h-[80vh] flex-col items-center justify-center bg-white/50">
<Loader className="text-documenso h-12 w-12 animate-spin" />
<p className="text-muted-foreground mt-4">Loading document...</p>
</div>
),
},
);

View File

@ -0,0 +1,23 @@
'use client';
import { useCallback, useEffect } from 'react';
import { useRouter } from 'next/navigation';
export const RefreshOnFocus = () => {
const { refresh } = useRouter();
const onFocus = useCallback(() => {
refresh();
}, [refresh]);
useEffect(() => {
window.addEventListener('focus', onFocus);
return () => {
window.removeEventListener('focus', onFocus);
};
}, [onFocus]);
return null;
};

View File

@ -1,3 +1,5 @@
'use client';
import React, { HTMLAttributes } from 'react';
import { Loader } from 'lucide-react';

View File

@ -1,3 +1,5 @@
'use client';
import { useCallback, useEffect, useState } from 'react';
import { Trash } from 'lucide-react';

View File

@ -1,53 +0,0 @@
import React, { createContext, useRef } from 'react';
import { OnPDFViewerPageClick } from '~/components/(dashboard)/pdf-viewer/pdf-viewer';
type EditFormContextValue = {
firePageClickEvent: OnPDFViewerPageClick;
registerPageClickHandler: (_handler: OnPDFViewerPageClick) => void;
unregisterPageClickHandler: (_handler: OnPDFViewerPageClick) => void;
} | null;
const EditFormContext = createContext<EditFormContextValue>(null);
export type EditFormProviderProps = {
children: React.ReactNode;
};
export const useEditForm = () => {
const context = React.useContext(EditFormContext);
if (!context) {
throw new Error('useEditForm must be used within a EditFormProvider');
}
return context;
};
export const EditFormProvider = ({ children }: EditFormProviderProps) => {
const handlers = useRef(new Set<OnPDFViewerPageClick>());
const firePageClickEvent: OnPDFViewerPageClick = (event) => {
handlers.current.forEach((handler) => handler(event));
};
const registerPageClickHandler = (handler: OnPDFViewerPageClick) => {
handlers.current.add(handler);
};
const unregisterPageClickHandler = (handler: OnPDFViewerPageClick) => {
handlers.current.delete(handler);
};
return (
<EditFormContext.Provider
value={{
firePageClickEvent,
registerPageClickHandler,
unregisterPageClickHandler,
}}
>
{children}
</EditFormContext.Provider>
);
};

View File

@ -1,5 +1,7 @@
'use client';
import { useRouter } from 'next/navigation';
import { zodResolver } from '@hookform/resolvers/zod';
import { Loader } from 'lucide-react';
import { Controller, useForm } from 'react-hook-form';
@ -30,6 +32,8 @@ export type ProfileFormProps = {
};
export const ProfileForm = ({ className, user }: ProfileFormProps) => {
const router = useRouter();
const { toast } = useToast();
const {
@ -59,6 +63,8 @@ export const ProfileForm = ({ className, user }: ProfileFormProps) => {
description: 'Your profile has been updated successfully.',
duration: 5000,
});
router.refresh();
} catch (err) {
if (err instanceof TRPCClientError && err.data?.code === 'BAD_REQUEST') {
toast({

View File

@ -0,0 +1,7 @@
'use client';
import { motion } from 'framer-motion';
export * from 'framer-motion';
export const MotionDiv = motion.div;

View File

@ -24,7 +24,12 @@ export type SignaturePadProps = Omit<HTMLAttributes<HTMLCanvasElement>, 'onChang
onChange?: (_signatureDataUrl: string | null) => void;
};
export const SignaturePad = ({ className, onChange, ...props }: SignaturePadProps) => {
export const SignaturePad = ({
className,
defaultValue,
onChange,
...props
}: SignaturePadProps) => {
const $el = useRef<HTMLCanvasElement>(null);
const [isPressed, setIsPressed] = useState(false);
@ -127,7 +132,7 @@ export const SignaturePad = ({ className, onChange, ...props }: SignaturePadProp
setPoints(newPoints);
}
if ($el.current) {
if ($el.current && newPoints.length > 0) {
const ctx = $el.current.getContext('2d');
if (ctx) {
@ -188,6 +193,23 @@ export const SignaturePad = ({ className, onChange, ...props }: SignaturePadProp
}
}, []);
useEffect(() => {
console.log({ defaultValue });
if ($el.current && typeof defaultValue === 'string') {
const ctx = $el.current.getContext('2d');
const { width, height } = $el.current;
const img = new Image();
img.onload = () => {
ctx?.drawImage(img, 0, 0, Math.min(width, img.width), Math.min(height, img.height));
};
img.src = defaultValue;
}
}, [defaultValue]);
return (
<div className="relative block">
<canvas
@ -202,10 +224,10 @@ export const SignaturePad = ({ className, onChange, ...props }: SignaturePadProp
{...props}
/>
<div className="absolute bottom-2 right-2">
<div className="absolute bottom-4 right-4">
<button
type="button"
className="focus-visible:ring-ring ring-offset-background rounded-full p-2 text-xs text-slate-500 focus-visible:outline-none focus-visible:ring-2"
className="focus-visible:ring-ring ring-offset-background rounded-full p-0 text-xs text-slate-500 focus-visible:outline-none focus-visible:ring-2"
onClick={() => onClearClick()}
>
Clear Signature