mirror of
https://github.com/documenso/documenso.git
synced 2025-11-19 19:21:39 +10:00
fix: wip
This commit is contained in:
@ -5,6 +5,7 @@ module.exports = {
|
|||||||
rules: {
|
rules: {
|
||||||
'@next/next/no-img-element': 'off',
|
'@next/next/no-img-element': 'off',
|
||||||
'no-unreachable': 'error',
|
'no-unreachable': 'error',
|
||||||
|
'react-hooks/exhaustive-deps': 'off',
|
||||||
},
|
},
|
||||||
settings: {
|
settings: {
|
||||||
next: {
|
next: {
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import { Trans } from '@lingui/react/macro';
|
|||||||
import type * as DialogPrimitive from '@radix-ui/react-dialog';
|
import type * as DialogPrimitive from '@radix-ui/react-dialog';
|
||||||
import { Plus } from 'lucide-react';
|
import { Plus } from 'lucide-react';
|
||||||
import { useForm } from 'react-hook-form';
|
import { useForm } from 'react-hook-form';
|
||||||
|
import { useRevalidator } from 'react-router';
|
||||||
import type { z } from 'zod';
|
import type { z } from 'zod';
|
||||||
|
|
||||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||||
@ -50,6 +51,7 @@ export const TeamEmailAddDialog = ({ teamId, trigger, ...props }: TeamEmailAddDi
|
|||||||
|
|
||||||
const { _ } = useLingui();
|
const { _ } = useLingui();
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
|
const { revalidate } = useRevalidator();
|
||||||
|
|
||||||
const form = useForm<TCreateTeamEmailFormSchema>({
|
const form = useForm<TCreateTeamEmailFormSchema>({
|
||||||
resolver: zodResolver(ZCreateTeamEmailFormSchema),
|
resolver: zodResolver(ZCreateTeamEmailFormSchema),
|
||||||
@ -76,7 +78,7 @@ export const TeamEmailAddDialog = ({ teamId, trigger, ...props }: TeamEmailAddDi
|
|||||||
duration: 5000,
|
duration: 5000,
|
||||||
});
|
});
|
||||||
|
|
||||||
// router.refresh(); // Todo
|
await revalidate();
|
||||||
|
|
||||||
setOpen(false);
|
setOpen(false);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import { msg } from '@lingui/core/macro';
|
|||||||
import { useLingui } from '@lingui/react';
|
import { useLingui } from '@lingui/react';
|
||||||
import { Trans } from '@lingui/react/macro';
|
import { Trans } from '@lingui/react/macro';
|
||||||
import type { Prisma } from '@prisma/client';
|
import type { Prisma } from '@prisma/client';
|
||||||
|
import { useRevalidator } from 'react-router';
|
||||||
|
|
||||||
import { formatAvatarUrl } from '@documenso/lib/utils/avatars';
|
import { formatAvatarUrl } from '@documenso/lib/utils/avatars';
|
||||||
import { extractInitials } from '@documenso/lib/utils/recipient-formatter';
|
import { extractInitials } from '@documenso/lib/utils/recipient-formatter';
|
||||||
@ -44,6 +45,7 @@ export const TeamEmailDeleteDialog = ({ trigger, teamName, team }: TeamEmailDele
|
|||||||
|
|
||||||
const { _ } = useLingui();
|
const { _ } = useLingui();
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
|
const { revalidate } = useRevalidator();
|
||||||
|
|
||||||
const { mutateAsync: deleteTeamEmail, isPending: isDeletingTeamEmail } =
|
const { mutateAsync: deleteTeamEmail, isPending: isDeletingTeamEmail } =
|
||||||
trpc.team.deleteTeamEmail.useMutation({
|
trpc.team.deleteTeamEmail.useMutation({
|
||||||
@ -92,7 +94,7 @@ export const TeamEmailDeleteDialog = ({ trigger, teamName, team }: TeamEmailDele
|
|||||||
await deleteTeamEmailVerification({ teamId: team.id });
|
await deleteTeamEmailVerification({ teamId: team.id });
|
||||||
}
|
}
|
||||||
|
|
||||||
// router.refresh(); // Todo
|
await revalidate();
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import { Trans } from '@lingui/react/macro';
|
|||||||
import type { TeamEmail } from '@prisma/client';
|
import type { TeamEmail } from '@prisma/client';
|
||||||
import type * as DialogPrimitive from '@radix-ui/react-dialog';
|
import type * as DialogPrimitive from '@radix-ui/react-dialog';
|
||||||
import { useForm } from 'react-hook-form';
|
import { useForm } from 'react-hook-form';
|
||||||
|
import { useRevalidator } from 'react-router';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
import { trpc } from '@documenso/trpc/react';
|
import { trpc } from '@documenso/trpc/react';
|
||||||
@ -51,6 +52,7 @@ export const TeamEmailUpdateDialog = ({
|
|||||||
|
|
||||||
const { _ } = useLingui();
|
const { _ } = useLingui();
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
|
const { revalidate } = useRevalidator();
|
||||||
|
|
||||||
const form = useForm<TUpdateTeamEmailFormSchema>({
|
const form = useForm<TUpdateTeamEmailFormSchema>({
|
||||||
resolver: zodResolver(ZUpdateTeamEmailFormSchema),
|
resolver: zodResolver(ZUpdateTeamEmailFormSchema),
|
||||||
@ -76,7 +78,7 @@ export const TeamEmailUpdateDialog = ({
|
|||||||
duration: 5000,
|
duration: 5000,
|
||||||
});
|
});
|
||||||
|
|
||||||
// router.refresh(); // Todo
|
await revalidate();
|
||||||
|
|
||||||
setOpen(false);
|
setOpen(false);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|||||||
@ -46,9 +46,9 @@ import {
|
|||||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@documenso/ui/primitives/tabs';
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@documenso/ui/primitives/tabs';
|
||||||
import { useToast } from '@documenso/ui/primitives/use-toast';
|
import { useToast } from '@documenso/ui/primitives/use-toast';
|
||||||
|
|
||||||
|
import { useCurrentTeam } from '~/providers/team';
|
||||||
|
|
||||||
export type TeamMemberInviteDialogProps = {
|
export type TeamMemberInviteDialogProps = {
|
||||||
currentUserTeamRole: TeamMemberRole;
|
|
||||||
teamId: number;
|
|
||||||
trigger?: React.ReactNode;
|
trigger?: React.ReactNode;
|
||||||
} & Omit<DialogPrimitive.DialogProps, 'children'>;
|
} & Omit<DialogPrimitive.DialogProps, 'children'>;
|
||||||
|
|
||||||
@ -95,12 +95,7 @@ const ZImportTeamMemberSchema = z.array(
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
export const TeamMemberInviteDialog = ({
|
export const TeamMemberInviteDialog = ({ trigger, ...props }: TeamMemberInviteDialogProps) => {
|
||||||
currentUserTeamRole,
|
|
||||||
teamId,
|
|
||||||
trigger,
|
|
||||||
...props
|
|
||||||
}: TeamMemberInviteDialogProps) => {
|
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||||
const [invitationType, setInvitationType] = useState<TabTypes>('INDIVIDUAL');
|
const [invitationType, setInvitationType] = useState<TabTypes>('INDIVIDUAL');
|
||||||
@ -108,6 +103,8 @@ export const TeamMemberInviteDialog = ({
|
|||||||
const { _ } = useLingui();
|
const { _ } = useLingui();
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
|
|
||||||
|
const team = useCurrentTeam();
|
||||||
|
|
||||||
const form = useForm<TInviteTeamMembersFormSchema>({
|
const form = useForm<TInviteTeamMembersFormSchema>({
|
||||||
resolver: zodResolver(ZInviteTeamMembersFormSchema),
|
resolver: zodResolver(ZInviteTeamMembersFormSchema),
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
@ -141,7 +138,7 @@ export const TeamMemberInviteDialog = ({
|
|||||||
const onFormSubmit = async ({ invitations }: TInviteTeamMembersFormSchema) => {
|
const onFormSubmit = async ({ invitations }: TInviteTeamMembersFormSchema) => {
|
||||||
try {
|
try {
|
||||||
await createTeamMemberInvites({
|
await createTeamMemberInvites({
|
||||||
teamId,
|
teamId: team.id,
|
||||||
invitations,
|
invitations,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -203,7 +200,7 @@ export const TeamMemberInviteDialog = ({
|
|||||||
|
|
||||||
setInvitationType('INDIVIDUAL');
|
setInvitationType('INDIVIDUAL');
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err.message);
|
console.error(err);
|
||||||
|
|
||||||
toast({
|
toast({
|
||||||
title: _(msg`Something went wrong`),
|
title: _(msg`Something went wrong`),
|
||||||
@ -324,11 +321,13 @@ export const TeamMemberInviteDialog = ({
|
|||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
|
|
||||||
<SelectContent position="popper">
|
<SelectContent position="popper">
|
||||||
{TEAM_MEMBER_ROLE_HIERARCHY[currentUserTeamRole].map((role) => (
|
{TEAM_MEMBER_ROLE_HIERARCHY[team.currentTeamMember.role].map(
|
||||||
<SelectItem key={role} value={role}>
|
(role) => (
|
||||||
{_(TEAM_MEMBER_ROLE_MAP[role]) ?? role}
|
<SelectItem key={role} value={role}>
|
||||||
</SelectItem>
|
{_(TEAM_MEMBER_ROLE_MAP[role]) ?? role}
|
||||||
))}
|
</SelectItem>
|
||||||
|
),
|
||||||
|
)}
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import { useLingui } from '@lingui/react';
|
|||||||
import { Trans } from '@lingui/react/macro';
|
import { Trans } from '@lingui/react/macro';
|
||||||
import { Loader } from 'lucide-react';
|
import { Loader } from 'lucide-react';
|
||||||
import { useForm } from 'react-hook-form';
|
import { useForm } from 'react-hook-form';
|
||||||
|
import { useRevalidator } from 'react-router';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
import { IS_BILLING_ENABLED } from '@documenso/lib/constants/app';
|
import { IS_BILLING_ENABLED } from '@documenso/lib/constants/app';
|
||||||
@ -56,6 +57,7 @@ export const TeamTransferDialog = ({
|
|||||||
|
|
||||||
const { _ } = useLingui();
|
const { _ } = useLingui();
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
|
const { revalidate } = useRevalidator();
|
||||||
|
|
||||||
const { mutateAsync: requestTeamOwnershipTransfer } =
|
const { mutateAsync: requestTeamOwnershipTransfer } =
|
||||||
trpc.team.requestTeamOwnershipTransfer.useMutation();
|
trpc.team.requestTeamOwnershipTransfer.useMutation();
|
||||||
@ -98,7 +100,7 @@ export const TeamTransferDialog = ({
|
|||||||
clearPaymentMethods,
|
clearPaymentMethods,
|
||||||
});
|
});
|
||||||
|
|
||||||
// router.refresh(); // Todo
|
await revalidate();
|
||||||
|
|
||||||
toast({
|
toast({
|
||||||
title: _(msg`Success`),
|
title: _(msg`Success`),
|
||||||
|
|||||||
@ -10,7 +10,7 @@ import {
|
|||||||
type TemplateDirectLink,
|
type TemplateDirectLink,
|
||||||
} from '@prisma/client';
|
} from '@prisma/client';
|
||||||
import { CircleDotIcon, CircleIcon, ClipboardCopyIcon, InfoIcon, LoaderIcon } from 'lucide-react';
|
import { CircleDotIcon, CircleIcon, ClipboardCopyIcon, InfoIcon, LoaderIcon } from 'lucide-react';
|
||||||
import { Link } from 'react-router';
|
import { Link, useRevalidator } from 'react-router';
|
||||||
import { P, match } from 'ts-pattern';
|
import { P, match } from 'ts-pattern';
|
||||||
|
|
||||||
import { useLimits } from '@documenso/ee/server-only/limits/provider/client';
|
import { useLimits } from '@documenso/ee/server-only/limits/provider/client';
|
||||||
@ -64,6 +64,7 @@ export const TemplateDirectLinkDialog = ({
|
|||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
const { quota, remaining } = useLimits();
|
const { quota, remaining } = useLimits();
|
||||||
const { _ } = useLingui();
|
const { _ } = useLingui();
|
||||||
|
const { revalidate } = useRevalidator();
|
||||||
|
|
||||||
const [, copy] = useCopyToClipboard();
|
const [, copy] = useCopyToClipboard();
|
||||||
|
|
||||||
@ -84,7 +85,9 @@ export const TemplateDirectLinkDialog = ({
|
|||||||
isPending: isCreatingTemplateDirectLink,
|
isPending: isCreatingTemplateDirectLink,
|
||||||
reset: resetCreateTemplateDirectLink,
|
reset: resetCreateTemplateDirectLink,
|
||||||
} = trpcReact.template.createTemplateDirectLink.useMutation({
|
} = trpcReact.template.createTemplateDirectLink.useMutation({
|
||||||
onSuccess: (data) => {
|
onSuccess: async (data) => {
|
||||||
|
await revalidate();
|
||||||
|
|
||||||
setToken(data.token);
|
setToken(data.token);
|
||||||
setIsEnabled(data.enabled);
|
setIsEnabled(data.enabled);
|
||||||
setCurrentStep('MANAGE');
|
setCurrentStep('MANAGE');
|
||||||
@ -102,7 +105,9 @@ export const TemplateDirectLinkDialog = ({
|
|||||||
|
|
||||||
const { mutateAsync: toggleTemplateDirectLink, isPending: isTogglingTemplateAccess } =
|
const { mutateAsync: toggleTemplateDirectLink, isPending: isTogglingTemplateAccess } =
|
||||||
trpcReact.template.toggleTemplateDirectLink.useMutation({
|
trpcReact.template.toggleTemplateDirectLink.useMutation({
|
||||||
onSuccess: (data) => {
|
onSuccess: async (data) => {
|
||||||
|
await revalidate();
|
||||||
|
|
||||||
const enabledDescription = msg`Direct link signing has been enabled`;
|
const enabledDescription = msg`Direct link signing has been enabled`;
|
||||||
const disabledDescription = msg`Direct link signing has been disabled`;
|
const disabledDescription = msg`Direct link signing has been disabled`;
|
||||||
|
|
||||||
@ -125,7 +130,9 @@ export const TemplateDirectLinkDialog = ({
|
|||||||
|
|
||||||
const { mutateAsync: deleteTemplateDirectLink, isPending: isDeletingTemplateDirectLink } =
|
const { mutateAsync: deleteTemplateDirectLink, isPending: isDeletingTemplateDirectLink } =
|
||||||
trpcReact.template.deleteTemplateDirectLink.useMutation({
|
trpcReact.template.deleteTemplateDirectLink.useMutation({
|
||||||
onSuccess: () => {
|
onSuccess: async () => {
|
||||||
|
await revalidate();
|
||||||
|
|
||||||
onOpenChange(false);
|
onOpenChange(false);
|
||||||
setToken(null);
|
setToken(null);
|
||||||
|
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import { useLingui } from '@lingui/react';
|
|||||||
import { Trans } from '@lingui/react/macro';
|
import { Trans } from '@lingui/react/macro';
|
||||||
import { flushSync } from 'react-dom';
|
import { flushSync } from 'react-dom';
|
||||||
import { useForm } from 'react-hook-form';
|
import { useForm } from 'react-hook-form';
|
||||||
|
import { useRevalidator } from 'react-router';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
import { trpc } from '@documenso/trpc/react';
|
import { trpc } from '@documenso/trpc/react';
|
||||||
@ -41,6 +42,7 @@ export type TDisable2FAForm = z.infer<typeof ZDisable2FAForm>;
|
|||||||
export const DisableAuthenticatorAppDialog = () => {
|
export const DisableAuthenticatorAppDialog = () => {
|
||||||
const { _ } = useLingui();
|
const { _ } = useLingui();
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
|
const { revalidate } = useRevalidator();
|
||||||
|
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
const [twoFactorDisableMethod, setTwoFactorDisableMethod] = useState<'totp' | 'backup'>('totp');
|
const [twoFactorDisableMethod, setTwoFactorDisableMethod] = useState<'totp' | 'backup'>('totp');
|
||||||
@ -92,8 +94,7 @@ export const DisableAuthenticatorAppDialog = () => {
|
|||||||
onCloseTwoFactorDisableDialog();
|
onCloseTwoFactorDisableDialog();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Todo
|
await revalidate();
|
||||||
// router.refresh();
|
|
||||||
} catch (_err) {
|
} catch (_err) {
|
||||||
toast({
|
toast({
|
||||||
title: _(msg`Unable to disable two-factor authentication`),
|
title: _(msg`Unable to disable two-factor authentication`),
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import { msg } from '@lingui/core/macro';
|
|||||||
import { useLingui } from '@lingui/react';
|
import { useLingui } from '@lingui/react';
|
||||||
import { Trans } from '@lingui/react/macro';
|
import { Trans } from '@lingui/react/macro';
|
||||||
import { useForm } from 'react-hook-form';
|
import { useForm } from 'react-hook-form';
|
||||||
|
import { useRevalidator } from 'react-router';
|
||||||
import { renderSVG } from 'uqr';
|
import { renderSVG } from 'uqr';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
@ -47,6 +48,7 @@ export type EnableAuthenticatorAppDialogProps = {
|
|||||||
export const EnableAuthenticatorAppDialog = ({ onSuccess }: EnableAuthenticatorAppDialogProps) => {
|
export const EnableAuthenticatorAppDialog = ({ onSuccess }: EnableAuthenticatorAppDialogProps) => {
|
||||||
const { _ } = useLingui();
|
const { _ } = useLingui();
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
|
const { revalidate } = useRevalidator();
|
||||||
|
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
const [recoveryCodes, setRecoveryCodes] = useState<string[] | null>(null);
|
const [recoveryCodes, setRecoveryCodes] = useState<string[] | null>(null);
|
||||||
@ -128,8 +130,7 @@ export const EnableAuthenticatorAppDialog = ({ onSuccess }: EnableAuthenticatorA
|
|||||||
|
|
||||||
if (!isOpen && recoveryCodes && recoveryCodes.length > 0) {
|
if (!isOpen && recoveryCodes && recoveryCodes.length > 0) {
|
||||||
setRecoveryCodes(null);
|
setRecoveryCodes(null);
|
||||||
// Todo
|
void revalidate();
|
||||||
// router.refresh();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import { msg } from '@lingui/core/macro';
|
|||||||
import { useLingui } from '@lingui/react';
|
import { useLingui } from '@lingui/react';
|
||||||
import { Trans } from '@lingui/react/macro';
|
import { Trans } from '@lingui/react/macro';
|
||||||
import { useForm } from 'react-hook-form';
|
import { useForm } from 'react-hook-form';
|
||||||
|
import { useRevalidator } from 'react-router';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
import { useSession } from '@documenso/lib/client-only/providers/session';
|
import { useSession } from '@documenso/lib/client-only/providers/session';
|
||||||
@ -42,6 +43,7 @@ export const ProfileForm = ({ className }: ProfileFormProps) => {
|
|||||||
const { _ } = useLingui();
|
const { _ } = useLingui();
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
const { user } = useSession();
|
const { user } = useSession();
|
||||||
|
const { revalidate } = useRevalidator();
|
||||||
|
|
||||||
const form = useForm<TProfileFormSchema>({
|
const form = useForm<TProfileFormSchema>({
|
||||||
values: {
|
values: {
|
||||||
@ -68,7 +70,7 @@ export const ProfileForm = ({ className }: ProfileFormProps) => {
|
|||||||
duration: 5000,
|
duration: 5000,
|
||||||
});
|
});
|
||||||
|
|
||||||
// router.refresh(); // Todo
|
await revalidate();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
toast({
|
toast({
|
||||||
title: _(msg`An unknown error occurred`),
|
title: _(msg`An unknown error occurred`),
|
||||||
|
|||||||
@ -168,7 +168,6 @@ export const SignUpForm = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const onNextClick = async () => {
|
const onNextClick = async () => {
|
||||||
console.log('hello-world');
|
|
||||||
const valid = await form.trigger(['name', 'email', 'password', 'signature']);
|
const valid = await form.trigger(['name', 'email', 'password', 'signature']);
|
||||||
|
|
||||||
if (valid) {
|
if (valid) {
|
||||||
|
|||||||
@ -73,7 +73,7 @@ export const TeamUpdateForm = ({ teamId, teamName, teamUrl }: UpdateTeamDialogPr
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (url !== teamUrl) {
|
if (url !== teamUrl) {
|
||||||
await navigate(`${NEXT_PUBLIC_WEBAPP_URL()}/t/${url}/settings`);
|
await navigate(`/t/${url}/settings`);
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
const error = AppError.parseError(err);
|
const error = AppError.parseError(err);
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { useState, useTransition } from 'react';
|
import { useState } from 'react';
|
||||||
|
|
||||||
import { msg } from '@lingui/core/macro';
|
import { msg } from '@lingui/core/macro';
|
||||||
import { useLingui } from '@lingui/react';
|
import { useLingui } from '@lingui/react';
|
||||||
@ -6,6 +6,7 @@ import { Plural, Trans } from '@lingui/react/macro';
|
|||||||
import type { Field, Recipient } from '@prisma/client';
|
import type { Field, Recipient } from '@prisma/client';
|
||||||
import { FieldType } from '@prisma/client';
|
import { FieldType } from '@prisma/client';
|
||||||
import { useForm } from 'react-hook-form';
|
import { useForm } from 'react-hook-form';
|
||||||
|
import { useRevalidator } from 'react-router';
|
||||||
import { P, match } from 'ts-pattern';
|
import { P, match } from 'ts-pattern';
|
||||||
|
|
||||||
import { unsafe_useEffectOnce } from '@documenso/lib/client-only/hooks/use-effect-once';
|
import { unsafe_useEffectOnce } from '@documenso/lib/client-only/hooks/use-effect-once';
|
||||||
@ -60,12 +61,12 @@ export type DocumentSigningAutoSignProps = {
|
|||||||
export const DocumentSigningAutoSign = ({ recipient, fields }: DocumentSigningAutoSignProps) => {
|
export const DocumentSigningAutoSign = ({ recipient, fields }: DocumentSigningAutoSignProps) => {
|
||||||
const { _ } = useLingui();
|
const { _ } = useLingui();
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
|
const { revalidate } = useRevalidator();
|
||||||
|
|
||||||
const { email, fullName } = useRequiredDocumentSigningContext();
|
const { email, fullName } = useRequiredDocumentSigningContext();
|
||||||
const { derivedRecipientActionAuth } = useRequiredDocumentSigningAuthContext();
|
const { derivedRecipientActionAuth } = useRequiredDocumentSigningAuthContext();
|
||||||
|
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
const [isPending, startTransition] = useTransition();
|
|
||||||
|
|
||||||
const form = useForm();
|
const form = useForm();
|
||||||
|
|
||||||
@ -153,12 +154,7 @@ export const DocumentSigningAutoSign = ({ recipient, fields }: DocumentSigningAu
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
startTransition(() => {
|
await revalidate();
|
||||||
// Todo
|
|
||||||
// router.refresh();
|
|
||||||
|
|
||||||
setOpen(false);
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
unsafe_useEffectOnce(() => {
|
unsafe_useEffectOnce(() => {
|
||||||
@ -219,7 +215,7 @@ export const DocumentSigningAutoSign = ({ recipient, fields }: DocumentSigningAu
|
|||||||
<Button
|
<Button
|
||||||
type="submit"
|
type="submit"
|
||||||
className="min-w-[6rem]"
|
className="min-w-[6rem]"
|
||||||
loading={form.formState.isSubmitting || isPending}
|
loading={form.formState.isSubmitting}
|
||||||
disabled={!autoSignableFields.length}
|
disabled={!autoSignableFields.length}
|
||||||
>
|
>
|
||||||
<Trans>Sign</Trans>
|
<Trans>Sign</Trans>
|
||||||
|
|||||||
@ -1,9 +1,10 @@
|
|||||||
import { useEffect, useMemo, useState, useTransition } from 'react';
|
import { useEffect, useMemo, useState } from 'react';
|
||||||
|
|
||||||
import { msg } from '@lingui/core/macro';
|
import { msg } from '@lingui/core/macro';
|
||||||
import { useLingui } from '@lingui/react';
|
import { useLingui } from '@lingui/react';
|
||||||
import type { Recipient } from '@prisma/client';
|
import type { Recipient } from '@prisma/client';
|
||||||
import { Loader } from 'lucide-react';
|
import { Loader } from 'lucide-react';
|
||||||
|
import { useRevalidator } from 'react-router';
|
||||||
|
|
||||||
import { DO_NOT_INVALIDATE_QUERY_ON_MUTATION } from '@documenso/lib/constants/trpc';
|
import { DO_NOT_INVALIDATE_QUERY_ON_MUTATION } from '@documenso/lib/constants/trpc';
|
||||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||||
@ -40,8 +41,8 @@ export const DocumentSigningCheckboxField = ({
|
|||||||
}: DocumentSigningCheckboxFieldProps) => {
|
}: DocumentSigningCheckboxFieldProps) => {
|
||||||
const { _ } = useLingui();
|
const { _ } = useLingui();
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
|
const { revalidate } = useRevalidator();
|
||||||
|
|
||||||
const [isPending, startTransition] = useTransition();
|
|
||||||
const { executeActionAuthProcedure } = useRequiredDocumentSigningAuthContext();
|
const { executeActionAuthProcedure } = useRequiredDocumentSigningAuthContext();
|
||||||
|
|
||||||
const parsedFieldMeta = ZCheckboxFieldMeta.parse(field.fieldMeta);
|
const parsedFieldMeta = ZCheckboxFieldMeta.parse(field.fieldMeta);
|
||||||
@ -84,7 +85,7 @@ export const DocumentSigningCheckboxField = ({
|
|||||||
isPending: isRemoveSignedFieldWithTokenLoading,
|
isPending: isRemoveSignedFieldWithTokenLoading,
|
||||||
} = trpc.field.removeSignedFieldWithToken.useMutation(DO_NOT_INVALIDATE_QUERY_ON_MUTATION);
|
} = trpc.field.removeSignedFieldWithToken.useMutation(DO_NOT_INVALIDATE_QUERY_ON_MUTATION);
|
||||||
|
|
||||||
const isLoading = isSignFieldWithTokenLoading || isRemoveSignedFieldWithTokenLoading || isPending;
|
const isLoading = isSignFieldWithTokenLoading || isRemoveSignedFieldWithTokenLoading;
|
||||||
const shouldAutoSignField =
|
const shouldAutoSignField =
|
||||||
(!field.inserted && checkedValues.length > 0 && isLengthConditionMet) ||
|
(!field.inserted && checkedValues.length > 0 && isLengthConditionMet) ||
|
||||||
(!field.inserted && isReadOnly && isLengthConditionMet);
|
(!field.inserted && isReadOnly && isLengthConditionMet);
|
||||||
@ -105,8 +106,7 @@ export const DocumentSigningCheckboxField = ({
|
|||||||
await signFieldWithToken(payload);
|
await signFieldWithToken(payload);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Todo
|
await revalidate();
|
||||||
// startTransition(() => router.refresh());
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
const error = AppError.parseError(err);
|
const error = AppError.parseError(err);
|
||||||
|
|
||||||
@ -141,8 +141,7 @@ export const DocumentSigningCheckboxField = ({
|
|||||||
setCheckedValues([]);
|
setCheckedValues([]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Todo
|
await revalidate();
|
||||||
// startTransition(() => router.refresh());
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
|
|
||||||
@ -214,9 +213,7 @@ export const DocumentSigningCheckboxField = ({
|
|||||||
});
|
});
|
||||||
} finally {
|
} finally {
|
||||||
setCheckedValues(updatedValues);
|
setCheckedValues(updatedValues);
|
||||||
|
await revalidate();
|
||||||
// Todo
|
|
||||||
// startTransition(() => router.refresh());
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -1,10 +1,9 @@
|
|||||||
import { useTransition } from 'react';
|
|
||||||
|
|
||||||
import { msg } from '@lingui/core/macro';
|
import { msg } from '@lingui/core/macro';
|
||||||
import { useLingui } from '@lingui/react';
|
import { useLingui } from '@lingui/react';
|
||||||
import { Trans } from '@lingui/react/macro';
|
import { Trans } from '@lingui/react/macro';
|
||||||
import type { Recipient } from '@prisma/client';
|
import type { Recipient } from '@prisma/client';
|
||||||
import { Loader } from 'lucide-react';
|
import { Loader } from 'lucide-react';
|
||||||
|
import { useRevalidator } from 'react-router';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
DEFAULT_DOCUMENT_DATE_FORMAT,
|
DEFAULT_DOCUMENT_DATE_FORMAT,
|
||||||
@ -43,8 +42,7 @@ export const DocumentSigningDateField = ({
|
|||||||
}: DocumentSigningDateFieldProps) => {
|
}: DocumentSigningDateFieldProps) => {
|
||||||
const { _ } = useLingui();
|
const { _ } = useLingui();
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
|
const { revalidate } = useRevalidator();
|
||||||
const [isPending, startTransition] = useTransition();
|
|
||||||
|
|
||||||
const { mutateAsync: signFieldWithToken, isPending: isSignFieldWithTokenLoading } =
|
const { mutateAsync: signFieldWithToken, isPending: isSignFieldWithTokenLoading } =
|
||||||
trpc.field.signFieldWithToken.useMutation(DO_NOT_INVALIDATE_QUERY_ON_MUTATION);
|
trpc.field.signFieldWithToken.useMutation(DO_NOT_INVALIDATE_QUERY_ON_MUTATION);
|
||||||
@ -54,14 +52,14 @@ export const DocumentSigningDateField = ({
|
|||||||
isPending: isRemoveSignedFieldWithTokenLoading,
|
isPending: isRemoveSignedFieldWithTokenLoading,
|
||||||
} = trpc.field.removeSignedFieldWithToken.useMutation(DO_NOT_INVALIDATE_QUERY_ON_MUTATION);
|
} = trpc.field.removeSignedFieldWithToken.useMutation(DO_NOT_INVALIDATE_QUERY_ON_MUTATION);
|
||||||
|
|
||||||
const isLoading = isSignFieldWithTokenLoading || isRemoveSignedFieldWithTokenLoading || isPending;
|
const isLoading = isSignFieldWithTokenLoading || isRemoveSignedFieldWithTokenLoading;
|
||||||
|
|
||||||
const localDateString = convertToLocalSystemFormat(field.customText, dateFormat, timezone);
|
const localDateString = convertToLocalSystemFormat(field.customText, dateFormat, timezone);
|
||||||
|
|
||||||
const isDifferentTime = field.inserted && localDateString !== field.customText;
|
const isDifferentTime = field.inserted && localDateString !== field.customText;
|
||||||
|
|
||||||
const tooltipText = _(
|
const tooltipText = _(
|
||||||
msg`"${field.customText}" will appear on the document as it has a timezone of "${timezone}".`,
|
msg`"${field.customText}" will appear on the document as it has a timezone of "${timezone || ''}".`,
|
||||||
);
|
);
|
||||||
|
|
||||||
const onSign = async (authOptions?: TRecipientActionAuth) => {
|
const onSign = async (authOptions?: TRecipientActionAuth) => {
|
||||||
@ -80,8 +78,7 @@ export const DocumentSigningDateField = ({
|
|||||||
|
|
||||||
await signFieldWithToken(payload);
|
await signFieldWithToken(payload);
|
||||||
|
|
||||||
// Todo
|
await revalidate();
|
||||||
// startTransition(() => router.refresh());
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
const error = AppError.parseError(err);
|
const error = AppError.parseError(err);
|
||||||
|
|
||||||
@ -113,8 +110,7 @@ export const DocumentSigningDateField = ({
|
|||||||
|
|
||||||
await removeSignedFieldWithToken(payload);
|
await removeSignedFieldWithToken(payload);
|
||||||
|
|
||||||
// Todo
|
await revalidate();
|
||||||
// startTransition(() => router.refresh());
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
|
|
||||||
|
|||||||
@ -1,9 +1,10 @@
|
|||||||
import { useEffect, useState, useTransition } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
|
|
||||||
import { msg } from '@lingui/core/macro';
|
import { msg } from '@lingui/core/macro';
|
||||||
import { useLingui } from '@lingui/react';
|
import { useLingui } from '@lingui/react';
|
||||||
import type { Recipient } from '@prisma/client';
|
import type { Recipient } from '@prisma/client';
|
||||||
import { Loader } from 'lucide-react';
|
import { Loader } from 'lucide-react';
|
||||||
|
import { useRevalidator } from 'react-router';
|
||||||
|
|
||||||
import { DO_NOT_INVALIDATE_QUERY_ON_MUTATION } from '@documenso/lib/constants/trpc';
|
import { DO_NOT_INVALIDATE_QUERY_ON_MUTATION } from '@documenso/lib/constants/trpc';
|
||||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||||
@ -43,8 +44,7 @@ export const DocumentSigningDropdownField = ({
|
|||||||
}: DocumentSigningDropdownFieldProps) => {
|
}: DocumentSigningDropdownFieldProps) => {
|
||||||
const { _ } = useLingui();
|
const { _ } = useLingui();
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
|
const { revalidate } = useRevalidator();
|
||||||
const [isPending, startTransition] = useTransition();
|
|
||||||
|
|
||||||
const { executeActionAuthProcedure } = useRequiredDocumentSigningAuthContext();
|
const { executeActionAuthProcedure } = useRequiredDocumentSigningAuthContext();
|
||||||
|
|
||||||
@ -61,7 +61,7 @@ export const DocumentSigningDropdownField = ({
|
|||||||
isPending: isRemoveSignedFieldWithTokenLoading,
|
isPending: isRemoveSignedFieldWithTokenLoading,
|
||||||
} = trpc.field.removeSignedFieldWithToken.useMutation(DO_NOT_INVALIDATE_QUERY_ON_MUTATION);
|
} = trpc.field.removeSignedFieldWithToken.useMutation(DO_NOT_INVALIDATE_QUERY_ON_MUTATION);
|
||||||
|
|
||||||
const isLoading = isSignFieldWithTokenLoading || isRemoveSignedFieldWithTokenLoading || isPending;
|
const isLoading = isSignFieldWithTokenLoading || isRemoveSignedFieldWithTokenLoading;
|
||||||
const shouldAutoSignField =
|
const shouldAutoSignField =
|
||||||
(!field.inserted && localChoice) || (!field.inserted && isReadOnly && defaultValue);
|
(!field.inserted && localChoice) || (!field.inserted && isReadOnly && defaultValue);
|
||||||
|
|
||||||
@ -87,8 +87,7 @@ export const DocumentSigningDropdownField = ({
|
|||||||
|
|
||||||
setLocalChoice('');
|
setLocalChoice('');
|
||||||
|
|
||||||
// Todo
|
await revalidate();
|
||||||
// startTransition(() => router.refresh());
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
const error = AppError.parseError(err);
|
const error = AppError.parseError(err);
|
||||||
|
|
||||||
@ -126,8 +125,7 @@ export const DocumentSigningDropdownField = ({
|
|||||||
|
|
||||||
setLocalChoice('');
|
setLocalChoice('');
|
||||||
|
|
||||||
// Todo
|
await revalidate();
|
||||||
// startTransition(() => router.refresh());
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
|
|
||||||
|
|||||||
@ -1,10 +1,9 @@
|
|||||||
import { useTransition } from 'react';
|
|
||||||
|
|
||||||
import { msg } from '@lingui/core/macro';
|
import { msg } from '@lingui/core/macro';
|
||||||
import { useLingui } from '@lingui/react';
|
import { useLingui } from '@lingui/react';
|
||||||
import { Trans } from '@lingui/react/macro';
|
import { Trans } from '@lingui/react/macro';
|
||||||
import type { Recipient } from '@prisma/client';
|
import type { Recipient } from '@prisma/client';
|
||||||
import { Loader } from 'lucide-react';
|
import { Loader } from 'lucide-react';
|
||||||
|
import { useRevalidator } from 'react-router';
|
||||||
|
|
||||||
import { DO_NOT_INVALIDATE_QUERY_ON_MUTATION } from '@documenso/lib/constants/trpc';
|
import { DO_NOT_INVALIDATE_QUERY_ON_MUTATION } from '@documenso/lib/constants/trpc';
|
||||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||||
@ -35,11 +34,10 @@ export const DocumentSigningEmailField = ({
|
|||||||
}: DocumentSigningEmailFieldProps) => {
|
}: DocumentSigningEmailFieldProps) => {
|
||||||
const { _ } = useLingui();
|
const { _ } = useLingui();
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
|
const { revalidate } = useRevalidator();
|
||||||
|
|
||||||
const { email: providedEmail } = useRequiredDocumentSigningContext();
|
const { email: providedEmail } = useRequiredDocumentSigningContext();
|
||||||
|
|
||||||
const [isPending, startTransition] = useTransition();
|
|
||||||
|
|
||||||
const { mutateAsync: signFieldWithToken, isPending: isSignFieldWithTokenLoading } =
|
const { mutateAsync: signFieldWithToken, isPending: isSignFieldWithTokenLoading } =
|
||||||
trpc.field.signFieldWithToken.useMutation(DO_NOT_INVALIDATE_QUERY_ON_MUTATION);
|
trpc.field.signFieldWithToken.useMutation(DO_NOT_INVALIDATE_QUERY_ON_MUTATION);
|
||||||
|
|
||||||
@ -48,7 +46,7 @@ export const DocumentSigningEmailField = ({
|
|||||||
isPending: isRemoveSignedFieldWithTokenLoading,
|
isPending: isRemoveSignedFieldWithTokenLoading,
|
||||||
} = trpc.field.removeSignedFieldWithToken.useMutation(DO_NOT_INVALIDATE_QUERY_ON_MUTATION);
|
} = trpc.field.removeSignedFieldWithToken.useMutation(DO_NOT_INVALIDATE_QUERY_ON_MUTATION);
|
||||||
|
|
||||||
const isLoading = isSignFieldWithTokenLoading || isRemoveSignedFieldWithTokenLoading || isPending;
|
const isLoading = isSignFieldWithTokenLoading || isRemoveSignedFieldWithTokenLoading;
|
||||||
|
|
||||||
const onSign = async (authOptions?: TRecipientActionAuth) => {
|
const onSign = async (authOptions?: TRecipientActionAuth) => {
|
||||||
try {
|
try {
|
||||||
@ -69,8 +67,7 @@ export const DocumentSigningEmailField = ({
|
|||||||
|
|
||||||
await signFieldWithToken(payload);
|
await signFieldWithToken(payload);
|
||||||
|
|
||||||
// Todo
|
await revalidate();
|
||||||
// startTransition(() => router.refresh());
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
const error = AppError.parseError(err);
|
const error = AppError.parseError(err);
|
||||||
|
|
||||||
@ -102,8 +99,7 @@ export const DocumentSigningEmailField = ({
|
|||||||
|
|
||||||
await removeSignedFieldWithToken(payload);
|
await removeSignedFieldWithToken(payload);
|
||||||
|
|
||||||
// Todo
|
await revalidate();
|
||||||
// startTransition(() => router.refresh());
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
|
|
||||||
|
|||||||
@ -107,7 +107,11 @@ export const DocumentSigningForm = ({
|
|||||||
timestamp: new Date().toISOString(),
|
timestamp: new Date().toISOString(),
|
||||||
});
|
});
|
||||||
|
|
||||||
await navigate(redirectUrl ? redirectUrl : `/sign/${recipient.token}/complete`);
|
if (redirectUrl) {
|
||||||
|
window.location.href = redirectUrl;
|
||||||
|
} else {
|
||||||
|
await navigate(`/sign/${recipient.token}/complete`);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@ -1,10 +1,9 @@
|
|||||||
import { useTransition } from 'react';
|
|
||||||
|
|
||||||
import { msg } from '@lingui/core/macro';
|
import { msg } from '@lingui/core/macro';
|
||||||
import { useLingui } from '@lingui/react';
|
import { useLingui } from '@lingui/react';
|
||||||
import { Trans } from '@lingui/react/macro';
|
import { Trans } from '@lingui/react/macro';
|
||||||
import type { Recipient } from '@prisma/client';
|
import type { Recipient } from '@prisma/client';
|
||||||
import { Loader } from 'lucide-react';
|
import { Loader } from 'lucide-react';
|
||||||
|
import { useRevalidator } from 'react-router';
|
||||||
|
|
||||||
import { DO_NOT_INVALIDATE_QUERY_ON_MUTATION } from '@documenso/lib/constants/trpc';
|
import { DO_NOT_INVALIDATE_QUERY_ON_MUTATION } from '@documenso/lib/constants/trpc';
|
||||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||||
@ -36,12 +35,11 @@ export const DocumentSigningInitialsField = ({
|
|||||||
}: DocumentSigningInitialsFieldProps) => {
|
}: DocumentSigningInitialsFieldProps) => {
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
const { _ } = useLingui();
|
const { _ } = useLingui();
|
||||||
|
const { revalidate } = useRevalidator();
|
||||||
|
|
||||||
const { fullName } = useRequiredDocumentSigningContext();
|
const { fullName } = useRequiredDocumentSigningContext();
|
||||||
const initials = extractInitials(fullName);
|
const initials = extractInitials(fullName);
|
||||||
|
|
||||||
const [isPending, startTransition] = useTransition();
|
|
||||||
|
|
||||||
const { mutateAsync: signFieldWithToken, isPending: isSignFieldWithTokenLoading } =
|
const { mutateAsync: signFieldWithToken, isPending: isSignFieldWithTokenLoading } =
|
||||||
trpc.field.signFieldWithToken.useMutation(DO_NOT_INVALIDATE_QUERY_ON_MUTATION);
|
trpc.field.signFieldWithToken.useMutation(DO_NOT_INVALIDATE_QUERY_ON_MUTATION);
|
||||||
|
|
||||||
@ -50,7 +48,7 @@ export const DocumentSigningInitialsField = ({
|
|||||||
isPending: isRemoveSignedFieldWithTokenLoading,
|
isPending: isRemoveSignedFieldWithTokenLoading,
|
||||||
} = trpc.field.removeSignedFieldWithToken.useMutation(DO_NOT_INVALIDATE_QUERY_ON_MUTATION);
|
} = trpc.field.removeSignedFieldWithToken.useMutation(DO_NOT_INVALIDATE_QUERY_ON_MUTATION);
|
||||||
|
|
||||||
const isLoading = isSignFieldWithTokenLoading || isRemoveSignedFieldWithTokenLoading || isPending;
|
const isLoading = isSignFieldWithTokenLoading || isRemoveSignedFieldWithTokenLoading;
|
||||||
|
|
||||||
const onSign = async (authOptions?: TRecipientActionAuth) => {
|
const onSign = async (authOptions?: TRecipientActionAuth) => {
|
||||||
try {
|
try {
|
||||||
@ -71,8 +69,7 @@ export const DocumentSigningInitialsField = ({
|
|||||||
|
|
||||||
await signFieldWithToken(payload);
|
await signFieldWithToken(payload);
|
||||||
|
|
||||||
// Tod
|
await revalidate();
|
||||||
// startTransition(() => router.refresh());
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
const error = AppError.parseError(err);
|
const error = AppError.parseError(err);
|
||||||
|
|
||||||
@ -104,7 +101,7 @@ export const DocumentSigningInitialsField = ({
|
|||||||
|
|
||||||
await removeSignedFieldWithToken(payload);
|
await removeSignedFieldWithToken(payload);
|
||||||
|
|
||||||
// startTransition(() => router.refresh());
|
await revalidate();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
|
|
||||||
|
|||||||
@ -1,10 +1,11 @@
|
|||||||
import { useState, useTransition } from 'react';
|
import { useState } from 'react';
|
||||||
|
|
||||||
import { msg } from '@lingui/core/macro';
|
import { msg } from '@lingui/core/macro';
|
||||||
import { useLingui } from '@lingui/react';
|
import { useLingui } from '@lingui/react';
|
||||||
import { Trans } from '@lingui/react/macro';
|
import { Trans } from '@lingui/react/macro';
|
||||||
import { type Recipient } from '@prisma/client';
|
import { type Recipient } from '@prisma/client';
|
||||||
import { Loader } from 'lucide-react';
|
import { Loader } from 'lucide-react';
|
||||||
|
import { useRevalidator } from 'react-router';
|
||||||
|
|
||||||
import { DO_NOT_INVALIDATE_QUERY_ON_MUTATION } from '@documenso/lib/constants/trpc';
|
import { DO_NOT_INVALIDATE_QUERY_ON_MUTATION } from '@documenso/lib/constants/trpc';
|
||||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||||
@ -40,14 +41,13 @@ export const DocumentSigningNameField = ({
|
|||||||
}: DocumentSigningNameFieldProps) => {
|
}: DocumentSigningNameFieldProps) => {
|
||||||
const { _ } = useLingui();
|
const { _ } = useLingui();
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
|
const { revalidate } = useRevalidator();
|
||||||
|
|
||||||
const { fullName: providedFullName, setFullName: setProvidedFullName } =
|
const { fullName: providedFullName, setFullName: setProvidedFullName } =
|
||||||
useRequiredDocumentSigningContext();
|
useRequiredDocumentSigningContext();
|
||||||
|
|
||||||
const { executeActionAuthProcedure } = useRequiredDocumentSigningAuthContext();
|
const { executeActionAuthProcedure } = useRequiredDocumentSigningAuthContext();
|
||||||
|
|
||||||
const [isPending, startTransition] = useTransition();
|
|
||||||
|
|
||||||
const { mutateAsync: signFieldWithToken, isPending: isSignFieldWithTokenLoading } =
|
const { mutateAsync: signFieldWithToken, isPending: isSignFieldWithTokenLoading } =
|
||||||
trpc.field.signFieldWithToken.useMutation(DO_NOT_INVALIDATE_QUERY_ON_MUTATION);
|
trpc.field.signFieldWithToken.useMutation(DO_NOT_INVALIDATE_QUERY_ON_MUTATION);
|
||||||
|
|
||||||
@ -56,7 +56,7 @@ export const DocumentSigningNameField = ({
|
|||||||
isPending: isRemoveSignedFieldWithTokenLoading,
|
isPending: isRemoveSignedFieldWithTokenLoading,
|
||||||
} = trpc.field.removeSignedFieldWithToken.useMutation(DO_NOT_INVALIDATE_QUERY_ON_MUTATION);
|
} = trpc.field.removeSignedFieldWithToken.useMutation(DO_NOT_INVALIDATE_QUERY_ON_MUTATION);
|
||||||
|
|
||||||
const isLoading = isSignFieldWithTokenLoading || isRemoveSignedFieldWithTokenLoading || isPending;
|
const isLoading = isSignFieldWithTokenLoading || isRemoveSignedFieldWithTokenLoading;
|
||||||
|
|
||||||
const [showFullNameModal, setShowFullNameModal] = useState(false);
|
const [showFullNameModal, setShowFullNameModal] = useState(false);
|
||||||
const [localFullName, setLocalFullName] = useState('');
|
const [localFullName, setLocalFullName] = useState('');
|
||||||
@ -107,7 +107,7 @@ export const DocumentSigningNameField = ({
|
|||||||
|
|
||||||
await signFieldWithToken(payload);
|
await signFieldWithToken(payload);
|
||||||
|
|
||||||
// startTransition(() => router.refresh());
|
await revalidate();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
const error = AppError.parseError(err);
|
const error = AppError.parseError(err);
|
||||||
|
|
||||||
@ -139,7 +139,7 @@ export const DocumentSigningNameField = ({
|
|||||||
|
|
||||||
await removeSignedFieldWithToken(payload);
|
await removeSignedFieldWithToken(payload);
|
||||||
|
|
||||||
// startTransition(() => router.refresh());
|
await revalidate();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
|
|
||||||
|
|||||||
@ -1,10 +1,11 @@
|
|||||||
import { useEffect, useState, useTransition } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
|
|
||||||
import { msg } from '@lingui/core/macro';
|
import { msg } from '@lingui/core/macro';
|
||||||
import { useLingui } from '@lingui/react';
|
import { useLingui } from '@lingui/react';
|
||||||
import { Trans } from '@lingui/react/macro';
|
import { Trans } from '@lingui/react/macro';
|
||||||
import type { Recipient } from '@prisma/client';
|
import type { Recipient } from '@prisma/client';
|
||||||
import { Hash, Loader } from 'lucide-react';
|
import { Hash, Loader } from 'lucide-react';
|
||||||
|
import { useRevalidator } from 'react-router';
|
||||||
|
|
||||||
import { validateNumberField } from '@documenso/lib/advanced-fields-validation/validate-number';
|
import { validateNumberField } from '@documenso/lib/advanced-fields-validation/validate-number';
|
||||||
import { DO_NOT_INVALIDATE_QUERY_ON_MUTATION } from '@documenso/lib/constants/trpc';
|
import { DO_NOT_INVALIDATE_QUERY_ON_MUTATION } from '@documenso/lib/constants/trpc';
|
||||||
@ -49,8 +50,8 @@ export const DocumentSigningNumberField = ({
|
|||||||
}: DocumentSigningNumberFieldProps) => {
|
}: DocumentSigningNumberFieldProps) => {
|
||||||
const { _ } = useLingui();
|
const { _ } = useLingui();
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
|
const { revalidate } = useRevalidator();
|
||||||
|
|
||||||
const [isPending, startTransition] = useTransition();
|
|
||||||
const [showRadioModal, setShowRadioModal] = useState(false);
|
const [showRadioModal, setShowRadioModal] = useState(false);
|
||||||
|
|
||||||
const parsedFieldMeta = field.fieldMeta ? ZNumberFieldMeta.parse(field.fieldMeta) : null;
|
const parsedFieldMeta = field.fieldMeta ? ZNumberFieldMeta.parse(field.fieldMeta) : null;
|
||||||
@ -80,7 +81,7 @@ export const DocumentSigningNumberField = ({
|
|||||||
isPending: isRemoveSignedFieldWithTokenLoading,
|
isPending: isRemoveSignedFieldWithTokenLoading,
|
||||||
} = trpc.field.removeSignedFieldWithToken.useMutation(DO_NOT_INVALIDATE_QUERY_ON_MUTATION);
|
} = trpc.field.removeSignedFieldWithToken.useMutation(DO_NOT_INVALIDATE_QUERY_ON_MUTATION);
|
||||||
|
|
||||||
const isLoading = isSignFieldWithTokenLoading || isRemoveSignedFieldWithTokenLoading || isPending;
|
const isLoading = isSignFieldWithTokenLoading || isRemoveSignedFieldWithTokenLoading;
|
||||||
|
|
||||||
const handleNumberChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
const handleNumberChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
const text = e.target.value;
|
const text = e.target.value;
|
||||||
@ -136,8 +137,7 @@ export const DocumentSigningNumberField = ({
|
|||||||
|
|
||||||
setLocalNumber('');
|
setLocalNumber('');
|
||||||
|
|
||||||
// Todo
|
await revalidate();
|
||||||
// startTransition(() => router.refresh());
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
const error = AppError.parseError(err);
|
const error = AppError.parseError(err);
|
||||||
|
|
||||||
@ -188,8 +188,7 @@ export const DocumentSigningNumberField = ({
|
|||||||
|
|
||||||
setLocalNumber(parsedFieldMeta?.value ? String(parsedFieldMeta?.value) : '');
|
setLocalNumber(parsedFieldMeta?.value ? String(parsedFieldMeta?.value) : '');
|
||||||
|
|
||||||
// Todo
|
await revalidate();
|
||||||
// startTransition(() => router.refresh());
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
|
|
||||||
|
|||||||
@ -1,9 +1,10 @@
|
|||||||
import { useEffect, useState, useTransition } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
|
|
||||||
import { msg } from '@lingui/core/macro';
|
import { msg } from '@lingui/core/macro';
|
||||||
import { useLingui } from '@lingui/react';
|
import { useLingui } from '@lingui/react';
|
||||||
import type { Recipient } from '@prisma/client';
|
import type { Recipient } from '@prisma/client';
|
||||||
import { Loader } from 'lucide-react';
|
import { Loader } from 'lucide-react';
|
||||||
|
import { useRevalidator } from 'react-router';
|
||||||
|
|
||||||
import { DO_NOT_INVALIDATE_QUERY_ON_MUTATION } from '@documenso/lib/constants/trpc';
|
import { DO_NOT_INVALIDATE_QUERY_ON_MUTATION } from '@documenso/lib/constants/trpc';
|
||||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||||
@ -37,8 +38,7 @@ export const DocumentSigningRadioField = ({
|
|||||||
}: DocumentSigningRadioFieldProps) => {
|
}: DocumentSigningRadioFieldProps) => {
|
||||||
const { _ } = useLingui();
|
const { _ } = useLingui();
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
|
const { revalidate } = useRevalidator();
|
||||||
const [isPending, startTransition] = useTransition();
|
|
||||||
|
|
||||||
const parsedFieldMeta = ZRadioFieldMeta.parse(field.fieldMeta);
|
const parsedFieldMeta = ZRadioFieldMeta.parse(field.fieldMeta);
|
||||||
const values = parsedFieldMeta.values?.map((item) => ({
|
const values = parsedFieldMeta.values?.map((item) => ({
|
||||||
@ -60,7 +60,7 @@ export const DocumentSigningRadioField = ({
|
|||||||
isPending: isRemoveSignedFieldWithTokenLoading,
|
isPending: isRemoveSignedFieldWithTokenLoading,
|
||||||
} = trpc.field.removeSignedFieldWithToken.useMutation(DO_NOT_INVALIDATE_QUERY_ON_MUTATION);
|
} = trpc.field.removeSignedFieldWithToken.useMutation(DO_NOT_INVALIDATE_QUERY_ON_MUTATION);
|
||||||
|
|
||||||
const isLoading = isSignFieldWithTokenLoading || isRemoveSignedFieldWithTokenLoading || isPending;
|
const isLoading = isSignFieldWithTokenLoading || isRemoveSignedFieldWithTokenLoading;
|
||||||
const shouldAutoSignField =
|
const shouldAutoSignField =
|
||||||
(!field.inserted && selectedOption) ||
|
(!field.inserted && selectedOption) ||
|
||||||
(!field.inserted && defaultValue) ||
|
(!field.inserted && defaultValue) ||
|
||||||
@ -88,8 +88,7 @@ export const DocumentSigningRadioField = ({
|
|||||||
|
|
||||||
setSelectedOption('');
|
setSelectedOption('');
|
||||||
|
|
||||||
// Todo
|
await revalidate();
|
||||||
// startTransition(() => router.refresh());
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
const error = AppError.parseError(err);
|
const error = AppError.parseError(err);
|
||||||
|
|
||||||
@ -122,8 +121,7 @@ export const DocumentSigningRadioField = ({
|
|||||||
|
|
||||||
setSelectedOption('');
|
setSelectedOption('');
|
||||||
|
|
||||||
// Todo
|
await revalidate();
|
||||||
// startTransition(() => router.refresh());
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
|
|
||||||
|
|||||||
@ -1,10 +1,11 @@
|
|||||||
import { useLayoutEffect, useMemo, useRef, useState, useTransition } from 'react';
|
import { useLayoutEffect, useMemo, useRef, useState } from 'react';
|
||||||
|
|
||||||
import { msg } from '@lingui/core/macro';
|
import { msg } from '@lingui/core/macro';
|
||||||
import { useLingui } from '@lingui/react';
|
import { useLingui } from '@lingui/react';
|
||||||
import { Trans } from '@lingui/react/macro';
|
import { Trans } from '@lingui/react/macro';
|
||||||
import { type Recipient } from '@prisma/client';
|
import { type Recipient } from '@prisma/client';
|
||||||
import { Loader } from 'lucide-react';
|
import { Loader } from 'lucide-react';
|
||||||
|
import { useRevalidator } from 'react-router';
|
||||||
|
|
||||||
import { DO_NOT_INVALIDATE_QUERY_ON_MUTATION } from '@documenso/lib/constants/trpc';
|
import { DO_NOT_INVALIDATE_QUERY_ON_MUTATION } from '@documenso/lib/constants/trpc';
|
||||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||||
@ -45,6 +46,7 @@ export const DocumentSigningSignatureField = ({
|
|||||||
}: DocumentSigningSignatureFieldProps) => {
|
}: DocumentSigningSignatureFieldProps) => {
|
||||||
const { _ } = useLingui();
|
const { _ } = useLingui();
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
|
const { revalidate } = useRevalidator();
|
||||||
|
|
||||||
const signatureRef = useRef<HTMLParagraphElement>(null);
|
const signatureRef = useRef<HTMLParagraphElement>(null);
|
||||||
const containerRef = useRef<HTMLDivElement>(null);
|
const containerRef = useRef<HTMLDivElement>(null);
|
||||||
@ -59,8 +61,6 @@ export const DocumentSigningSignatureField = ({
|
|||||||
|
|
||||||
const { executeActionAuthProcedure } = useRequiredDocumentSigningAuthContext();
|
const { executeActionAuthProcedure } = useRequiredDocumentSigningAuthContext();
|
||||||
|
|
||||||
const [isPending, startTransition] = useTransition();
|
|
||||||
|
|
||||||
const { mutateAsync: signFieldWithToken, isPending: isSignFieldWithTokenLoading } =
|
const { mutateAsync: signFieldWithToken, isPending: isSignFieldWithTokenLoading } =
|
||||||
trpc.field.signFieldWithToken.useMutation(DO_NOT_INVALIDATE_QUERY_ON_MUTATION);
|
trpc.field.signFieldWithToken.useMutation(DO_NOT_INVALIDATE_QUERY_ON_MUTATION);
|
||||||
|
|
||||||
@ -71,7 +71,7 @@ export const DocumentSigningSignatureField = ({
|
|||||||
|
|
||||||
const { signature } = field;
|
const { signature } = field;
|
||||||
|
|
||||||
const isLoading = isSignFieldWithTokenLoading || isRemoveSignedFieldWithTokenLoading || isPending;
|
const isLoading = isSignFieldWithTokenLoading || isRemoveSignedFieldWithTokenLoading;
|
||||||
|
|
||||||
const [showSignatureModal, setShowSignatureModal] = useState(false);
|
const [showSignatureModal, setShowSignatureModal] = useState(false);
|
||||||
const [localSignature, setLocalSignature] = useState<string | null>(null);
|
const [localSignature, setLocalSignature] = useState<string | null>(null);
|
||||||
@ -143,13 +143,11 @@ export const DocumentSigningSignatureField = ({
|
|||||||
|
|
||||||
if (onSignField) {
|
if (onSignField) {
|
||||||
await onSignField(payload);
|
await onSignField(payload);
|
||||||
return;
|
} else {
|
||||||
|
await signFieldWithToken(payload);
|
||||||
}
|
}
|
||||||
|
|
||||||
await signFieldWithToken(payload);
|
await revalidate();
|
||||||
|
|
||||||
// Todo
|
|
||||||
// startTransition(() => router.refresh());
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
const error = AppError.parseError(err);
|
const error = AppError.parseError(err);
|
||||||
|
|
||||||
@ -177,12 +175,11 @@ export const DocumentSigningSignatureField = ({
|
|||||||
if (onUnsignField) {
|
if (onUnsignField) {
|
||||||
await onUnsignField(payload);
|
await onUnsignField(payload);
|
||||||
return;
|
return;
|
||||||
|
} else {
|
||||||
|
await removeSignedFieldWithToken(payload);
|
||||||
}
|
}
|
||||||
|
|
||||||
await removeSignedFieldWithToken(payload);
|
await revalidate();
|
||||||
|
|
||||||
// Todo
|
|
||||||
// startTransition(() => router.refresh());
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
|
|
||||||
|
|||||||
@ -1,10 +1,11 @@
|
|||||||
import { useEffect, useState, useTransition } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
|
|
||||||
import { msg } from '@lingui/core/macro';
|
import { msg } from '@lingui/core/macro';
|
||||||
import { useLingui } from '@lingui/react';
|
import { useLingui } from '@lingui/react';
|
||||||
import { Plural, Trans } from '@lingui/react/macro';
|
import { Plural, Trans } from '@lingui/react/macro';
|
||||||
import type { Recipient } from '@prisma/client';
|
import type { Recipient } from '@prisma/client';
|
||||||
import { Loader, Type } from 'lucide-react';
|
import { Loader, Type } from 'lucide-react';
|
||||||
|
import { useRevalidator } from 'react-router';
|
||||||
|
|
||||||
import { validateTextField } from '@documenso/lib/advanced-fields-validation/validate-text';
|
import { validateTextField } from '@documenso/lib/advanced-fields-validation/validate-text';
|
||||||
import { DO_NOT_INVALIDATE_QUERY_ON_MUTATION } from '@documenso/lib/constants/trpc';
|
import { DO_NOT_INVALIDATE_QUERY_ON_MUTATION } from '@documenso/lib/constants/trpc';
|
||||||
@ -41,6 +42,7 @@ export const DocumentSigningTextField = ({
|
|||||||
}: DocumentSigningTextFieldProps) => {
|
}: DocumentSigningTextFieldProps) => {
|
||||||
const { _ } = useLingui();
|
const { _ } = useLingui();
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
|
const { revalidate } = useRevalidator();
|
||||||
|
|
||||||
const initialErrors: Record<string, string[]> = {
|
const initialErrors: Record<string, string[]> = {
|
||||||
required: [],
|
required: [],
|
||||||
@ -52,8 +54,6 @@ export const DocumentSigningTextField = ({
|
|||||||
|
|
||||||
const { executeActionAuthProcedure } = useRequiredDocumentSigningAuthContext();
|
const { executeActionAuthProcedure } = useRequiredDocumentSigningAuthContext();
|
||||||
|
|
||||||
const [isPending, startTransition] = useTransition();
|
|
||||||
|
|
||||||
const { mutateAsync: signFieldWithToken, isPending: isSignFieldWithTokenLoading } =
|
const { mutateAsync: signFieldWithToken, isPending: isSignFieldWithTokenLoading } =
|
||||||
trpc.field.signFieldWithToken.useMutation(DO_NOT_INVALIDATE_QUERY_ON_MUTATION);
|
trpc.field.signFieldWithToken.useMutation(DO_NOT_INVALIDATE_QUERY_ON_MUTATION);
|
||||||
|
|
||||||
@ -64,7 +64,7 @@ export const DocumentSigningTextField = ({
|
|||||||
|
|
||||||
const parsedFieldMeta = field.fieldMeta ? ZTextFieldMeta.parse(field.fieldMeta) : null;
|
const parsedFieldMeta = field.fieldMeta ? ZTextFieldMeta.parse(field.fieldMeta) : null;
|
||||||
|
|
||||||
const isLoading = isSignFieldWithTokenLoading || isRemoveSignedFieldWithTokenLoading || isPending;
|
const isLoading = isSignFieldWithTokenLoading || isRemoveSignedFieldWithTokenLoading;
|
||||||
const shouldAutoSignField =
|
const shouldAutoSignField =
|
||||||
(!field.inserted && parsedFieldMeta?.text) ||
|
(!field.inserted && parsedFieldMeta?.text) ||
|
||||||
(!field.inserted && parsedFieldMeta?.text && parsedFieldMeta?.readOnly);
|
(!field.inserted && parsedFieldMeta?.text && parsedFieldMeta?.readOnly);
|
||||||
@ -153,8 +153,7 @@ export const DocumentSigningTextField = ({
|
|||||||
|
|
||||||
setLocalCustomText('');
|
setLocalCustomText('');
|
||||||
|
|
||||||
// Todo
|
await revalidate();
|
||||||
// startTransition(() => router.refresh());
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
const error = AppError.parseError(err);
|
const error = AppError.parseError(err);
|
||||||
|
|
||||||
@ -188,8 +187,7 @@ export const DocumentSigningTextField = ({
|
|||||||
|
|
||||||
setLocalCustomText(parsedFieldMeta?.text ?? '');
|
setLocalCustomText(parsedFieldMeta?.text ?? '');
|
||||||
|
|
||||||
// Todo
|
await revalidate();
|
||||||
// startTransition(() => router.refresh());
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
|
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import { useCallback, useEffect, useState } from 'react';
|
|||||||
|
|
||||||
import { msg } from '@lingui/core/macro';
|
import { msg } from '@lingui/core/macro';
|
||||||
import { useLingui } from '@lingui/react';
|
import { useLingui } from '@lingui/react';
|
||||||
import { useNavigate, useSearchParams } from 'react-router';
|
import { useSearchParams } from 'react-router';
|
||||||
|
|
||||||
import { useDebouncedValue } from '@documenso/lib/client-only/hooks/use-debounced-value';
|
import { useDebouncedValue } from '@documenso/lib/client-only/hooks/use-debounced-value';
|
||||||
import { Input } from '@documenso/ui/primitives/input';
|
import { Input } from '@documenso/ui/primitives/input';
|
||||||
@ -10,8 +10,7 @@ import { Input } from '@documenso/ui/primitives/input';
|
|||||||
export const DocumentSearch = ({ initialValue = '' }: { initialValue?: string }) => {
|
export const DocumentSearch = ({ initialValue = '' }: { initialValue?: string }) => {
|
||||||
const { _ } = useLingui();
|
const { _ } = useLingui();
|
||||||
|
|
||||||
const navigate = useNavigate();
|
const [searchParams, setSearchParams] = useSearchParams();
|
||||||
const [searchParams] = useSearchParams();
|
|
||||||
|
|
||||||
const [searchTerm, setSearchTerm] = useState(initialValue);
|
const [searchTerm, setSearchTerm] = useState(initialValue);
|
||||||
const debouncedSearchTerm = useDebouncedValue(searchTerm, 500);
|
const debouncedSearchTerm = useDebouncedValue(searchTerm, 500);
|
||||||
@ -20,12 +19,12 @@ export const DocumentSearch = ({ initialValue = '' }: { initialValue?: string })
|
|||||||
(term: string) => {
|
(term: string) => {
|
||||||
const params = new URLSearchParams(searchParams?.toString() ?? '');
|
const params = new URLSearchParams(searchParams?.toString() ?? '');
|
||||||
if (term) {
|
if (term) {
|
||||||
params.set('search', term);
|
params.set('query', term);
|
||||||
} else {
|
} else {
|
||||||
params.delete('search');
|
params.delete('query');
|
||||||
}
|
}
|
||||||
|
|
||||||
void navigate(`?${params.toString()}`);
|
setSearchParams(params);
|
||||||
},
|
},
|
||||||
[searchParams],
|
[searchParams],
|
||||||
);
|
);
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import { useLingui } from '@lingui/react';
|
|||||||
import { Trans } from '@lingui/react/macro';
|
import { Trans } from '@lingui/react/macro';
|
||||||
import type { TeamMemberRole, TeamTransferVerification } from '@prisma/client';
|
import type { TeamMemberRole, TeamTransferVerification } from '@prisma/client';
|
||||||
import { AnimatePresence } from 'framer-motion';
|
import { AnimatePresence } from 'framer-motion';
|
||||||
|
import { useRevalidator } from 'react-router';
|
||||||
|
|
||||||
import { canExecuteTeamAction } from '@documenso/lib/utils/teams';
|
import { canExecuteTeamAction } from '@documenso/lib/utils/teams';
|
||||||
import { isTokenExpired } from '@documenso/lib/utils/token-verification';
|
import { isTokenExpired } from '@documenso/lib/utils/token-verification';
|
||||||
@ -28,12 +29,13 @@ export const TeamTransferStatus = ({
|
|||||||
}: TeamTransferStatusProps) => {
|
}: TeamTransferStatusProps) => {
|
||||||
const { _ } = useLingui();
|
const { _ } = useLingui();
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
|
const { revalidate } = useRevalidator();
|
||||||
|
|
||||||
const isExpired = transferVerification && isTokenExpired(transferVerification.expiresAt);
|
const isExpired = transferVerification && isTokenExpired(transferVerification.expiresAt);
|
||||||
|
|
||||||
const { mutateAsync: deleteTeamTransferRequest, isPending } =
|
const { mutateAsync: deleteTeamTransferRequest, isPending } =
|
||||||
trpc.team.deleteTeamTransferRequest.useMutation({
|
trpc.team.deleteTeamTransferRequest.useMutation({
|
||||||
onSuccess: () => {
|
onSuccess: async () => {
|
||||||
if (!isExpired) {
|
if (!isExpired) {
|
||||||
toast({
|
toast({
|
||||||
title: _(msg`Success`),
|
title: _(msg`Success`),
|
||||||
@ -42,8 +44,7 @@ export const TeamTransferStatus = ({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// todo?
|
await revalidate();
|
||||||
// router.refresh();
|
|
||||||
},
|
},
|
||||||
onError: () => {
|
onError: () => {
|
||||||
toast({
|
toast({
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import { useLingui } from '@lingui/react';
|
|||||||
import { Trans } from '@lingui/react/macro';
|
import { Trans } from '@lingui/react/macro';
|
||||||
import { type Field, type Recipient, type Signature, SigningStatus } from '@prisma/client';
|
import { type Field, type Recipient, type Signature, SigningStatus } from '@prisma/client';
|
||||||
import { useForm } from 'react-hook-form';
|
import { useForm } from 'react-hook-form';
|
||||||
|
import { useRevalidator } from 'react-router';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
import { trpc } from '@documenso/trpc/react';
|
import { trpc } from '@documenso/trpc/react';
|
||||||
@ -42,6 +43,7 @@ export type RecipientItemProps = {
|
|||||||
export const AdminDocumentRecipientItemTable = ({ recipient }: RecipientItemProps) => {
|
export const AdminDocumentRecipientItemTable = ({ recipient }: RecipientItemProps) => {
|
||||||
const { _ } = useLingui();
|
const { _ } = useLingui();
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
|
const { revalidate } = useRevalidator();
|
||||||
|
|
||||||
const form = useForm<TAdminUpdateRecipientFormSchema>({
|
const form = useForm<TAdminUpdateRecipientFormSchema>({
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
@ -109,8 +111,7 @@ export const AdminDocumentRecipientItemTable = ({ recipient }: RecipientItemProp
|
|||||||
description: _(msg`The recipient has been updated successfully`),
|
description: _(msg`The recipient has been updated successfully`),
|
||||||
});
|
});
|
||||||
|
|
||||||
// todo
|
await revalidate();
|
||||||
// router.refresh();
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
toast({
|
toast({
|
||||||
title: _(msg`Failed to update recipient`),
|
title: _(msg`Failed to update recipient`),
|
||||||
|
|||||||
@ -4,7 +4,6 @@ import { msg } from '@lingui/core/macro';
|
|||||||
import { useLingui } from '@lingui/react';
|
import { useLingui } from '@lingui/react';
|
||||||
import { Trans } from '@lingui/react/macro';
|
import { Trans } from '@lingui/react/macro';
|
||||||
import { Link, useSearchParams } from 'react-router';
|
import { Link, useSearchParams } from 'react-router';
|
||||||
import { useNavigate } from 'react-router';
|
|
||||||
import { useLocation } from 'react-router';
|
import { useLocation } from 'react-router';
|
||||||
|
|
||||||
import { useDebouncedValue } from '@documenso/lib/client-only/hooks/use-debounced-value';
|
import { useDebouncedValue } from '@documenso/lib/client-only/hooks/use-debounced-value';
|
||||||
@ -18,8 +17,7 @@ import { UserSettingsPendingTeamsDataTable } from './user-settings-pending-teams
|
|||||||
export const UserSettingsTeamsPageDataTable = () => {
|
export const UserSettingsTeamsPageDataTable = () => {
|
||||||
const { _ } = useLingui();
|
const { _ } = useLingui();
|
||||||
|
|
||||||
const [searchParams] = useSearchParams();
|
const [searchParams, setSearchParams] = useSearchParams();
|
||||||
const navigate = useNavigate();
|
|
||||||
const { pathname } = useLocation();
|
const { pathname } = useLocation();
|
||||||
|
|
||||||
const [searchQuery, setSearchQuery] = useState(() => searchParams?.get('query') ?? '');
|
const [searchQuery, setSearchQuery] = useState(() => searchParams?.get('query') ?? '');
|
||||||
@ -39,10 +37,6 @@ export const UserSettingsTeamsPageDataTable = () => {
|
|||||||
* Handle debouncing the search query.
|
* Handle debouncing the search query.
|
||||||
*/
|
*/
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!pathname) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const params = new URLSearchParams(searchParams?.toString());
|
const params = new URLSearchParams(searchParams?.toString());
|
||||||
|
|
||||||
params.set('query', debouncedSearchQuery);
|
params.set('query', debouncedSearchQuery);
|
||||||
@ -51,8 +45,8 @@ export const UserSettingsTeamsPageDataTable = () => {
|
|||||||
params.delete('query');
|
params.delete('query');
|
||||||
}
|
}
|
||||||
|
|
||||||
void navigate(`${pathname}?${params.toString()}`);
|
setSearchParams(params);
|
||||||
}, [debouncedSearchQuery, pathname, navigate, searchParams]);
|
}, [debouncedSearchQuery, pathname, searchParams]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import { msg } from '@lingui/core/macro';
|
|||||||
import { useLingui } from '@lingui/react';
|
import { useLingui } from '@lingui/react';
|
||||||
import { Trans } from '@lingui/react/macro';
|
import { Trans } from '@lingui/react/macro';
|
||||||
import { useForm } from 'react-hook-form';
|
import { useForm } from 'react-hook-form';
|
||||||
|
import { useRevalidator } from 'react-router';
|
||||||
import type { z } from 'zod';
|
import type { z } from 'zod';
|
||||||
|
|
||||||
import { getSiteSettings } from '@documenso/lib/server-only/site-settings/get-site-settings';
|
import { getSiteSettings } from '@documenso/lib/server-only/site-settings/get-site-settings';
|
||||||
@ -47,6 +48,7 @@ export default function AdminBannerPage({ loaderData }: Route.ComponentProps) {
|
|||||||
|
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
const { _ } = useLingui();
|
const { _ } = useLingui();
|
||||||
|
const { revalidate } = useRevalidator();
|
||||||
|
|
||||||
const form = useForm<TBannerFormSchema>({
|
const form = useForm<TBannerFormSchema>({
|
||||||
resolver: zodResolver(ZBannerFormSchema),
|
resolver: zodResolver(ZBannerFormSchema),
|
||||||
@ -80,8 +82,7 @@ export default function AdminBannerPage({ loaderData }: Route.ComponentProps) {
|
|||||||
duration: 5000,
|
duration: 5000,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Todo
|
await revalidate();
|
||||||
// router.refresh();
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
toast({
|
toast({
|
||||||
title: _(msg`An unknown error occurred`),
|
title: _(msg`An unknown error occurred`),
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import { msg } from '@lingui/core/macro';
|
|||||||
import { useLingui } from '@lingui/react';
|
import { useLingui } from '@lingui/react';
|
||||||
import { Trans } from '@lingui/react/macro';
|
import { Trans } from '@lingui/react/macro';
|
||||||
import { useForm } from 'react-hook-form';
|
import { useForm } from 'react-hook-form';
|
||||||
|
import { useRevalidator } from 'react-router';
|
||||||
import type { z } from 'zod';
|
import type { z } from 'zod';
|
||||||
|
|
||||||
import { trpc } from '@documenso/trpc/react';
|
import { trpc } from '@documenso/trpc/react';
|
||||||
@ -32,6 +33,7 @@ type TUserFormSchema = z.infer<typeof ZUserFormSchema>;
|
|||||||
export default function UserPage({ params }: { params: { id: number } }) {
|
export default function UserPage({ params }: { params: { id: number } }) {
|
||||||
const { _ } = useLingui();
|
const { _ } = useLingui();
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
|
const { revalidate } = useRevalidator();
|
||||||
|
|
||||||
const { data: user } = trpc.profile.getUser.useQuery(
|
const { data: user } = trpc.profile.getUser.useQuery(
|
||||||
{
|
{
|
||||||
@ -64,8 +66,7 @@ export default function UserPage({ params }: { params: { id: number } }) {
|
|||||||
roles,
|
roles,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Todo
|
await revalidate();
|
||||||
// router.refresh();
|
|
||||||
|
|
||||||
toast({
|
toast({
|
||||||
title: _(msg`Profile updated`),
|
title: _(msg`Profile updated`),
|
||||||
|
|||||||
@ -3,8 +3,10 @@ import { useEffect, useMemo, useState } from 'react';
|
|||||||
import { Trans } from '@lingui/react/macro';
|
import { Trans } from '@lingui/react/macro';
|
||||||
import { useSearchParams } from 'react-router';
|
import { useSearchParams } from 'react-router';
|
||||||
import { Link } from 'react-router';
|
import { Link } from 'react-router';
|
||||||
|
import { z } from 'zod';
|
||||||
|
|
||||||
import { formatAvatarUrl } from '@documenso/lib/utils/avatars';
|
import { formatAvatarUrl } from '@documenso/lib/utils/avatars';
|
||||||
|
import { parseToIntegerArray } from '@documenso/lib/utils/params';
|
||||||
import { formatDocumentsPath } from '@documenso/lib/utils/teams';
|
import { formatDocumentsPath } from '@documenso/lib/utils/teams';
|
||||||
import { ExtendedDocumentStatus } from '@documenso/prisma/types/extended-document-status';
|
import { ExtendedDocumentStatus } from '@documenso/prisma/types/extended-document-status';
|
||||||
import { trpc } from '@documenso/trpc/react';
|
import { trpc } from '@documenso/trpc/react';
|
||||||
@ -33,8 +35,9 @@ const ZSearchParamsSchema = ZFindDocumentsInternalRequestSchema.pick({
|
|||||||
period: true,
|
period: true,
|
||||||
page: true,
|
page: true,
|
||||||
perPage: true,
|
perPage: true,
|
||||||
senderIds: true,
|
|
||||||
query: true,
|
query: true,
|
||||||
|
}).extend({
|
||||||
|
senderIds: z.string().transform(parseToIntegerArray).optional().catch([]),
|
||||||
});
|
});
|
||||||
|
|
||||||
export default function DocumentsPage() {
|
export default function DocumentsPage() {
|
||||||
@ -59,7 +62,7 @@ export default function DocumentsPage() {
|
|||||||
...findDocumentSearchParams,
|
...findDocumentSearchParams,
|
||||||
});
|
});
|
||||||
|
|
||||||
const getTabHref = (value: typeof status) => {
|
const getTabHref = (value: keyof typeof ExtendedDocumentStatus) => {
|
||||||
const params = new URLSearchParams(searchParams);
|
const params = new URLSearchParams(searchParams);
|
||||||
|
|
||||||
params.set('status', value);
|
params.set('status', value);
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import { useLingui } from '@lingui/react';
|
|||||||
import { Trans } from '@lingui/react/macro';
|
import { Trans } from '@lingui/react/macro';
|
||||||
import { Loader } from 'lucide-react';
|
import { Loader } from 'lucide-react';
|
||||||
import { useForm } from 'react-hook-form';
|
import { useForm } from 'react-hook-form';
|
||||||
import { useParams } from 'react-router';
|
import { useParams, useRevalidator } from 'react-router';
|
||||||
import type { z } from 'zod';
|
import type { z } from 'zod';
|
||||||
|
|
||||||
import { trpc } from '@documenso/trpc/react';
|
import { trpc } from '@documenso/trpc/react';
|
||||||
@ -36,6 +36,7 @@ export default function WebhookPage() {
|
|||||||
|
|
||||||
const { _ } = useLingui();
|
const { _ } = useLingui();
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
|
const { revalidate } = useRevalidator();
|
||||||
|
|
||||||
const webhookId = params.id || '';
|
const webhookId = params.id || '';
|
||||||
|
|
||||||
@ -71,8 +72,7 @@ export default function WebhookPage() {
|
|||||||
duration: 5000,
|
duration: 5000,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Todo
|
await revalidate();
|
||||||
// router.refresh();
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
toast({
|
toast({
|
||||||
title: _(msg`Failed to update webhook`),
|
title: _(msg`Failed to update webhook`),
|
||||||
|
|||||||
@ -3,7 +3,7 @@ import { useEffect, useState } from 'react';
|
|||||||
import { msg } from '@lingui/core/macro';
|
import { msg } from '@lingui/core/macro';
|
||||||
import { useLingui } from '@lingui/react';
|
import { useLingui } from '@lingui/react';
|
||||||
import { Trans } from '@lingui/react/macro';
|
import { Trans } from '@lingui/react/macro';
|
||||||
import { Link, useLocation, useNavigate, useSearchParams } from 'react-router';
|
import { Link, useLocation, useSearchParams } from 'react-router';
|
||||||
|
|
||||||
import { useDebouncedValue } from '@documenso/lib/client-only/hooks/use-debounced-value';
|
import { useDebouncedValue } from '@documenso/lib/client-only/hooks/use-debounced-value';
|
||||||
import { Input } from '@documenso/ui/primitives/input';
|
import { Input } from '@documenso/ui/primitives/input';
|
||||||
@ -13,15 +13,11 @@ import { TeamMemberInviteDialog } from '~/components/dialogs/team-member-invite-
|
|||||||
import { SettingsHeader } from '~/components/general/settings-header';
|
import { SettingsHeader } from '~/components/general/settings-header';
|
||||||
import { TeamSettingsMemberInvitesTable } from '~/components/tables/team-settings-member-invites-table';
|
import { TeamSettingsMemberInvitesTable } from '~/components/tables/team-settings-member-invites-table';
|
||||||
import { TeamSettingsMembersDataTable } from '~/components/tables/team-settings-members-table';
|
import { TeamSettingsMembersDataTable } from '~/components/tables/team-settings-members-table';
|
||||||
import { useCurrentTeam } from '~/providers/team';
|
|
||||||
|
|
||||||
export default function TeamsSettingsMembersPage() {
|
export default function TeamsSettingsMembersPage() {
|
||||||
const { _ } = useLingui();
|
const { _ } = useLingui();
|
||||||
|
|
||||||
const team = useCurrentTeam();
|
const [searchParams, setSearchParams] = useSearchParams();
|
||||||
|
|
||||||
const [searchParams] = useSearchParams();
|
|
||||||
const navigate = useNavigate();
|
|
||||||
const { pathname } = useLocation();
|
const { pathname } = useLocation();
|
||||||
|
|
||||||
const [searchQuery, setSearchQuery] = useState(() => searchParams?.get('query') ?? '');
|
const [searchQuery, setSearchQuery] = useState(() => searchParams?.get('query') ?? '');
|
||||||
@ -34,10 +30,6 @@ export default function TeamsSettingsMembersPage() {
|
|||||||
* Handle debouncing the search query.
|
* Handle debouncing the search query.
|
||||||
*/
|
*/
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!pathname) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const params = new URLSearchParams(searchParams?.toString());
|
const params = new URLSearchParams(searchParams?.toString());
|
||||||
|
|
||||||
params.set('query', debouncedSearchQuery);
|
params.set('query', debouncedSearchQuery);
|
||||||
@ -46,8 +38,13 @@ export default function TeamsSettingsMembersPage() {
|
|||||||
params.delete('query');
|
params.delete('query');
|
||||||
}
|
}
|
||||||
|
|
||||||
void navigate(`${pathname}?${params.toString()}`);
|
// If nothing to change then do nothing.
|
||||||
}, [debouncedSearchQuery, pathname, navigate, searchParams]);
|
if (params.toString() === searchParams?.toString()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setSearchParams(params);
|
||||||
|
}, [debouncedSearchQuery, pathname, searchParams]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
@ -55,10 +52,7 @@ export default function TeamsSettingsMembersPage() {
|
|||||||
title={_(msg`Members`)}
|
title={_(msg`Members`)}
|
||||||
subtitle={_(msg`Manage the members or invite new members.`)}
|
subtitle={_(msg`Manage the members or invite new members.`)}
|
||||||
>
|
>
|
||||||
<TeamMemberInviteDialog
|
<TeamMemberInviteDialog />
|
||||||
teamId={team.id}
|
|
||||||
currentUserTeamRole={team.currentTeamMember.role}
|
|
||||||
/>
|
|
||||||
</SettingsHeader>
|
</SettingsHeader>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import { useLingui } from '@lingui/react';
|
|||||||
import { Trans } from '@lingui/react/macro';
|
import { Trans } from '@lingui/react/macro';
|
||||||
import { Loader } from 'lucide-react';
|
import { Loader } from 'lucide-react';
|
||||||
import { useForm } from 'react-hook-form';
|
import { useForm } from 'react-hook-form';
|
||||||
|
import { useRevalidator } from 'react-router';
|
||||||
import type { z } from 'zod';
|
import type { z } from 'zod';
|
||||||
|
|
||||||
import { trpc } from '@documenso/trpc/react';
|
import { trpc } from '@documenso/trpc/react';
|
||||||
@ -36,6 +37,7 @@ type TEditWebhookFormSchema = z.infer<typeof ZEditWebhookFormSchema>;
|
|||||||
export default function WebhookPage({ params }: Route.ComponentProps) {
|
export default function WebhookPage({ params }: Route.ComponentProps) {
|
||||||
const { _ } = useLingui();
|
const { _ } = useLingui();
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
|
const { revalidate } = useRevalidator();
|
||||||
|
|
||||||
const team = useCurrentTeam();
|
const team = useCurrentTeam();
|
||||||
|
|
||||||
@ -73,8 +75,7 @@ export default function WebhookPage({ params }: Route.ComponentProps) {
|
|||||||
duration: 5000,
|
duration: 5000,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Todo
|
await revalidate();
|
||||||
// router.refresh();
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
toast({
|
toast({
|
||||||
title: _(msg`Failed to update webhook`),
|
title: _(msg`Failed to update webhook`),
|
||||||
|
|||||||
@ -48,7 +48,7 @@ export async function loader({ params }: Route.LoaderArgs) {
|
|||||||
|
|
||||||
// Ensure typesafety when we add more options.
|
// Ensure typesafety when we add more options.
|
||||||
const isAccessAuthValid = match(derivedRecipientAccessAuth)
|
const isAccessAuthValid = match(derivedRecipientAccessAuth)
|
||||||
.with(DocumentAccessAuth.ACCOUNT, () => session?.user !== null)
|
.with(DocumentAccessAuth.ACCOUNT, () => Boolean(session?.user))
|
||||||
.with(null, () => true)
|
.with(null, () => true)
|
||||||
.exhaustive();
|
.exhaustive();
|
||||||
|
|
||||||
|
|||||||
@ -1,9 +1,11 @@
|
|||||||
|
import { useEffect } from 'react';
|
||||||
|
|
||||||
import { msg } from '@lingui/core/macro';
|
import { msg } from '@lingui/core/macro';
|
||||||
import { useLingui } from '@lingui/react';
|
import { useLingui } from '@lingui/react';
|
||||||
import { Trans } from '@lingui/react/macro';
|
import { Trans } from '@lingui/react/macro';
|
||||||
import { DocumentStatus, FieldType, RecipientRole } from '@prisma/client';
|
import { type Document, DocumentStatus, FieldType, RecipientRole } from '@prisma/client';
|
||||||
import { CheckCircle2, Clock8, FileSearch } from 'lucide-react';
|
import { CheckCircle2, Clock8, FileSearch } from 'lucide-react';
|
||||||
import { Link } from 'react-router';
|
import { Link, useRevalidator } from 'react-router';
|
||||||
import { getOptionalLoaderSession } from 'server/utils/get-loader-session';
|
import { getOptionalLoaderSession } from 'server/utils/get-loader-session';
|
||||||
import { match } from 'ts-pattern';
|
import { match } from 'ts-pattern';
|
||||||
|
|
||||||
@ -254,35 +256,32 @@ export default function CompletedSigningPage({ loaderData }: Route.ComponentProp
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Todo */}
|
<PollUntilDocumentCompleted document={document} />
|
||||||
{/* Todo */}
|
|
||||||
{/* <PollUntilDocumentCompleted document={document} /> */}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Todo: Refresh on focus? Was in a layout w it before.
|
export type PollUntilDocumentCompletedProps = {
|
||||||
// Todo:
|
document: Pick<Document, 'id' | 'status' | 'deletedAt'>;
|
||||||
// export type PollUntilDocumentCompletedProps = {
|
};
|
||||||
// document: Pick<Document, 'id' | 'status' | 'deletedAt'>;
|
|
||||||
// };
|
|
||||||
|
|
||||||
// export const PollUntilDocumentCompleted = ({ document }: PollUntilDocumentCompletedProps) => {
|
export const PollUntilDocumentCompleted = ({ document }: PollUntilDocumentCompletedProps) => {
|
||||||
// const router = useRouter();
|
const { revalidate } = useRevalidator();
|
||||||
|
|
||||||
// useEffect(() => {
|
useEffect(() => {
|
||||||
// if (document.status === DocumentStatus.COMPLETED) {
|
if (document.status === DocumentStatus.COMPLETED) {
|
||||||
// return;
|
return;
|
||||||
// }
|
}
|
||||||
|
|
||||||
// const interval = setInterval(() => {
|
const interval = setInterval(() => {
|
||||||
// if (window.document.hasFocus()) {
|
if (window.document.hasFocus()) {
|
||||||
// router.refresh();
|
void revalidate();
|
||||||
// }
|
}
|
||||||
// }, 5000);
|
}, 5000);
|
||||||
|
|
||||||
// return () => clearInterval(interval);
|
return () => clearInterval(interval);
|
||||||
// }, [router, document.status]);
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [document.status]);
|
||||||
|
|
||||||
// return <></>;
|
return <></>;
|
||||||
// };
|
};
|
||||||
|
|||||||
@ -8,7 +8,7 @@ import { Link, redirect, useNavigate } from 'react-router';
|
|||||||
import { match } from 'ts-pattern';
|
import { match } from 'ts-pattern';
|
||||||
|
|
||||||
import { authClient } from '@documenso/auth/client';
|
import { authClient } from '@documenso/auth/client';
|
||||||
import { EMAIL_VERIFICATION_STATE } from '@documenso/lib/server-only/user/verify-email';
|
import { EMAIL_VERIFICATION_STATE } from '@documenso/lib/constants/email';
|
||||||
import { Button } from '@documenso/ui/primitives/button';
|
import { Button } from '@documenso/ui/primitives/button';
|
||||||
import { useToast } from '@documenso/ui/primitives/use-toast';
|
import { useToast } from '@documenso/ui/primitives/use-toast';
|
||||||
|
|
||||||
|
|||||||
@ -85,6 +85,7 @@
|
|||||||
"@types/formidable": "^2.0.6",
|
"@types/formidable": "^2.0.6",
|
||||||
"@types/luxon": "^3.3.1",
|
"@types/luxon": "^3.3.1",
|
||||||
"@types/node": "^20",
|
"@types/node": "^20",
|
||||||
|
"@types/papaparse": "^5.3.15",
|
||||||
"@types/react": "^18",
|
"@types/react": "^18",
|
||||||
"@types/react-dom": "^18",
|
"@types/react-dom": "^18",
|
||||||
"@types/ua-parser-js": "^0.7.39",
|
"@types/ua-parser-js": "^0.7.39",
|
||||||
|
|||||||
@ -53,7 +53,7 @@ export const appContext = async (c: Context, next: Next) => {
|
|||||||
|
|
||||||
const result = await Promise.all([
|
const result = await Promise.all([
|
||||||
getTeams({ userId: session.user.id }),
|
getTeams({ userId: session.user.id }),
|
||||||
teamUrl ? getTeamByUrl({ userId: session.user.id, teamUrl }) : null,
|
teamUrl ? getTeamByUrl({ userId: session.user.id, teamUrl }).catch(() => null) : null,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
teams = result[0];
|
teams = result[0];
|
||||||
|
|||||||
@ -24,8 +24,10 @@ export interface HonoEnv {
|
|||||||
|
|
||||||
const app = new Hono<HonoEnv>();
|
const app = new Hono<HonoEnv>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attach session and context to requests.
|
||||||
|
*/
|
||||||
app.use(contextStorage());
|
app.use(contextStorage());
|
||||||
|
|
||||||
app.use(appContext);
|
app.use(appContext);
|
||||||
|
|
||||||
// App middleware.
|
// App middleware.
|
||||||
|
|||||||
@ -37,12 +37,18 @@ export default defineConfig({
|
|||||||
],
|
],
|
||||||
ssr: {
|
ssr: {
|
||||||
noExternal: ['react-dropzone', 'plausible-tracker', 'pdfjs-dist'],
|
noExternal: ['react-dropzone', 'plausible-tracker', 'pdfjs-dist'],
|
||||||
external: ['@node-rs/bcrypt', '@prisma/client'],
|
external: ['@node-rs/bcrypt', '@node-rs/bcrypt-wasm32-wasi', '@prisma/client'],
|
||||||
},
|
},
|
||||||
optimizeDeps: {
|
optimizeDeps: {
|
||||||
entries: ['./app/**/*', '../../packages/ui/**/*', '../../packages/lib/**/*'],
|
entries: ['./app/**/*', '../../packages/ui/**/*', '../../packages/lib/**/*'],
|
||||||
include: ['prop-types', 'file-selector', 'attr-accept'],
|
include: ['prop-types', 'file-selector', 'attr-accept'],
|
||||||
exclude: ['node_modules', '@node-rs/bcrypt', '@documenso/pdf-sign', 'sharp'],
|
exclude: [
|
||||||
|
'node_modules',
|
||||||
|
'@node-rs/bcrypt',
|
||||||
|
'@node-rs/bcrypt-wasm32-wasi',
|
||||||
|
'@documenso/pdf-sign',
|
||||||
|
'sharp',
|
||||||
|
],
|
||||||
},
|
},
|
||||||
resolve: {
|
resolve: {
|
||||||
alias: {
|
alias: {
|
||||||
|
|||||||
11
package-lock.json
generated
11
package-lock.json
generated
@ -168,6 +168,7 @@
|
|||||||
"@types/formidable": "^2.0.6",
|
"@types/formidable": "^2.0.6",
|
||||||
"@types/luxon": "^3.3.1",
|
"@types/luxon": "^3.3.1",
|
||||||
"@types/node": "^20",
|
"@types/node": "^20",
|
||||||
|
"@types/papaparse": "^5.3.15",
|
||||||
"@types/react": "^18",
|
"@types/react": "^18",
|
||||||
"@types/react-dom": "^18",
|
"@types/react-dom": "^18",
|
||||||
"@types/ua-parser-js": "^0.7.39",
|
"@types/ua-parser-js": "^0.7.39",
|
||||||
@ -13458,6 +13459,16 @@
|
|||||||
"integrity": "sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==",
|
"integrity": "sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/papaparse": {
|
||||||
|
"version": "5.3.15",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/papaparse/-/papaparse-5.3.15.tgz",
|
||||||
|
"integrity": "sha512-JHe6vF6x/8Z85nCX4yFdDslN11d+1pr12E526X8WAfhadOeaOTx5AuIkvDKIBopfvlzpzkdMx4YyvSKCM9oqtw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@types/parse-json": {
|
"node_modules/@types/parse-json": {
|
||||||
"version": "4.0.2",
|
"version": "4.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz",
|
||||||
|
|||||||
@ -13,6 +13,7 @@ import {
|
|||||||
import { seedTestEmail, seedUser } from '@documenso/prisma/seed/users';
|
import { seedTestEmail, seedUser } from '@documenso/prisma/seed/users';
|
||||||
|
|
||||||
import { apiSignin, apiSignout } from '../fixtures/authentication';
|
import { apiSignin, apiSignout } from '../fixtures/authentication';
|
||||||
|
import { signSignaturePad } from '../fixtures/signature';
|
||||||
|
|
||||||
test.describe.configure({ mode: 'parallel', timeout: 60000 });
|
test.describe.configure({ mode: 'parallel', timeout: 60000 });
|
||||||
|
|
||||||
@ -35,15 +36,7 @@ test('[DOCUMENT_AUTH]: should allow signing when no auth setup', async ({ page }
|
|||||||
await page.goto(signUrl);
|
await page.goto(signUrl);
|
||||||
await expect(page.getByRole('heading', { name: 'Sign Document' })).toBeVisible();
|
await expect(page.getByRole('heading', { name: 'Sign Document' })).toBeVisible();
|
||||||
|
|
||||||
// Add signature.
|
await signSignaturePad(page);
|
||||||
const canvas = page.locator('canvas').first();
|
|
||||||
const box = await canvas.boundingBox();
|
|
||||||
if (box) {
|
|
||||||
await page.mouse.move(box.x + 40, box.y + 40);
|
|
||||||
await page.mouse.down();
|
|
||||||
await page.mouse.move(box.x + box.width - 2, box.y + box.height - 2);
|
|
||||||
await page.mouse.up();
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const field of fields) {
|
for (const field of fields) {
|
||||||
await page.locator(`#field-${field.id}`).getByRole('button').click();
|
await page.locator(`#field-${field.id}`).getByRole('button').click();
|
||||||
@ -92,15 +85,7 @@ test('[DOCUMENT_AUTH]: should allow signing with valid global auth', async ({ pa
|
|||||||
|
|
||||||
await expect(page.getByRole('heading', { name: 'Sign Document' })).toBeVisible();
|
await expect(page.getByRole('heading', { name: 'Sign Document' })).toBeVisible();
|
||||||
|
|
||||||
// Add signature.
|
await signSignaturePad(page);
|
||||||
const canvas = page.locator('canvas').first();
|
|
||||||
const box = await canvas.boundingBox();
|
|
||||||
if (box) {
|
|
||||||
await page.mouse.move(box.x + 40, box.y + 40);
|
|
||||||
await page.mouse.down();
|
|
||||||
await page.mouse.move(box.x + box.width - 2, box.y + box.height - 2);
|
|
||||||
await page.mouse.up();
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const field of fields) {
|
for (const field of fields) {
|
||||||
await page.locator(`#field-${field.id}`).getByRole('button').click();
|
await page.locator(`#field-${field.id}`).getByRole('button').click();
|
||||||
@ -261,15 +246,7 @@ test('[DOCUMENT_AUTH]: should allow field signing when required for recipient au
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add signature.
|
await signSignaturePad(page);
|
||||||
const canvas = page.locator('canvas').first();
|
|
||||||
const box = await canvas.boundingBox();
|
|
||||||
if (box) {
|
|
||||||
await page.mouse.move(box.x + 40, box.y + 40);
|
|
||||||
await page.mouse.down();
|
|
||||||
await page.mouse.move(box.x + box.width - 2, box.y + box.height - 2);
|
|
||||||
await page.mouse.up();
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const field of fields) {
|
for (const field of fields) {
|
||||||
await page.locator(`#field-${field.id}`).getByRole('button').click();
|
await page.locator(`#field-${field.id}`).getByRole('button').click();
|
||||||
@ -372,15 +349,7 @@ test('[DOCUMENT_AUTH]: should allow field signing when required for recipient an
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add signature.
|
await signSignaturePad(page);
|
||||||
const canvas = page.locator('canvas').first();
|
|
||||||
const box = await canvas.boundingBox();
|
|
||||||
if (box) {
|
|
||||||
await page.mouse.move(box.x + 40, box.y + 40);
|
|
||||||
await page.mouse.down();
|
|
||||||
await page.mouse.move(box.x + box.width - 2, box.y + box.height - 2);
|
|
||||||
await page.mouse.up();
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const field of fields) {
|
for (const field of fields) {
|
||||||
await page.locator(`#field-${field.id}`).getByRole('button').click();
|
await page.locator(`#field-${field.id}`).getByRole('button').click();
|
||||||
|
|||||||
@ -18,6 +18,7 @@ import {
|
|||||||
import { seedUser } from '@documenso/prisma/seed/users';
|
import { seedUser } from '@documenso/prisma/seed/users';
|
||||||
|
|
||||||
import { apiSignin } from '../fixtures/authentication';
|
import { apiSignin } from '../fixtures/authentication';
|
||||||
|
import { signSignaturePad } from '../fixtures/signature';
|
||||||
|
|
||||||
// Can't use the function in server-only/document due to it indirectly using
|
// Can't use the function in server-only/document due to it indirectly using
|
||||||
// require imports.
|
// require imports.
|
||||||
@ -368,15 +369,7 @@ test('[DOCUMENT_FLOW]: should be able to approve a document', async ({ page }) =
|
|||||||
}),
|
}),
|
||||||
).toBeVisible();
|
).toBeVisible();
|
||||||
|
|
||||||
// Add signature.
|
await signSignaturePad(page);
|
||||||
const canvas = page.locator('canvas');
|
|
||||||
const box = await canvas.boundingBox();
|
|
||||||
if (box) {
|
|
||||||
await page.mouse.move(box.x + 40, box.y + 40);
|
|
||||||
await page.mouse.down();
|
|
||||||
await page.mouse.move(box.x + box.width - 2, box.y + box.height - 2);
|
|
||||||
await page.mouse.up();
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const field of fields) {
|
for (const field of fields) {
|
||||||
await page.locator(`#field-${field.id}`).getByRole('button').click();
|
await page.locator(`#field-${field.id}`).getByRole('button').click();
|
||||||
@ -607,19 +600,10 @@ test('[DOCUMENT_FLOW]: should be able to create and sign a document with 3 recip
|
|||||||
|
|
||||||
await page.goto(`/sign/${recipient?.token}`);
|
await page.goto(`/sign/${recipient?.token}`);
|
||||||
await expect(page.getByRole('heading', { name: 'Sign Document' })).toBeVisible();
|
await expect(page.getByRole('heading', { name: 'Sign Document' })).toBeVisible();
|
||||||
|
await signSignaturePad(page);
|
||||||
|
|
||||||
await page.locator(`#field-${recipientField.id}`).getByRole('button').click();
|
await page.locator(`#field-${recipientField.id}`).getByRole('button').click();
|
||||||
|
|
||||||
const canvas = page.locator('canvas#signature');
|
|
||||||
const box = await canvas.boundingBox();
|
|
||||||
if (box) {
|
|
||||||
await page.mouse.move(box.x + 40, box.y + 40);
|
|
||||||
await page.mouse.down();
|
|
||||||
await page.mouse.move(box.x + box.width - 2, box.y + box.height - 2);
|
|
||||||
await page.mouse.up();
|
|
||||||
}
|
|
||||||
|
|
||||||
await page.getByRole('button', { name: 'Sign', exact: true }).click();
|
|
||||||
await page.getByRole('button', { name: 'Complete' }).click();
|
await page.getByRole('button', { name: 'Complete' }).click();
|
||||||
await page.getByRole('button', { name: 'Sign' }).click();
|
await page.getByRole('button', { name: 'Sign' }).click();
|
||||||
|
|
||||||
|
|||||||
@ -10,6 +10,7 @@ import { seedTeam } from '@documenso/prisma/seed/teams';
|
|||||||
import { seedUser } from '@documenso/prisma/seed/users';
|
import { seedUser } from '@documenso/prisma/seed/users';
|
||||||
|
|
||||||
import { apiSignin } from '../fixtures/authentication';
|
import { apiSignin } from '../fixtures/authentication';
|
||||||
|
import { signSignaturePad } from '../fixtures/signature';
|
||||||
|
|
||||||
test.describe('Signing Certificate Tests', () => {
|
test.describe('Signing Certificate Tests', () => {
|
||||||
test('individual document should always include signing certificate', async ({ page }) => {
|
test('individual document should always include signing certificate', async ({ page }) => {
|
||||||
@ -36,14 +37,7 @@ test.describe('Signing Certificate Tests', () => {
|
|||||||
// Sign the document
|
// Sign the document
|
||||||
await page.goto(`/sign/${recipient.token}`);
|
await page.goto(`/sign/${recipient.token}`);
|
||||||
|
|
||||||
const canvas = page.locator('canvas');
|
await signSignaturePad(page);
|
||||||
const box = await canvas.boundingBox();
|
|
||||||
if (box) {
|
|
||||||
await page.mouse.move(box.x + 40, box.y + 40);
|
|
||||||
await page.mouse.down();
|
|
||||||
await page.mouse.move(box.x + box.width - 2, box.y + box.height - 2);
|
|
||||||
await page.mouse.up();
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const field of recipient.fields) {
|
for (const field of recipient.fields) {
|
||||||
await page.locator(`#field-${field.id}`).getByRole('button').click();
|
await page.locator(`#field-${field.id}`).getByRole('button').click();
|
||||||
@ -113,14 +107,7 @@ test.describe('Signing Certificate Tests', () => {
|
|||||||
// Sign the document
|
// Sign the document
|
||||||
await page.goto(`/sign/${recipient.token}`);
|
await page.goto(`/sign/${recipient.token}`);
|
||||||
|
|
||||||
const canvas = page.locator('canvas');
|
await signSignaturePad(page);
|
||||||
const box = await canvas.boundingBox();
|
|
||||||
if (box) {
|
|
||||||
await page.mouse.move(box.x + 40, box.y + 40);
|
|
||||||
await page.mouse.down();
|
|
||||||
await page.mouse.move(box.x + box.width - 2, box.y + box.height - 2);
|
|
||||||
await page.mouse.up();
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const field of recipient.fields) {
|
for (const field of recipient.fields) {
|
||||||
await page.locator(`#field-${field.id}`).getByRole('button').click();
|
await page.locator(`#field-${field.id}`).getByRole('button').click();
|
||||||
@ -190,14 +177,7 @@ test.describe('Signing Certificate Tests', () => {
|
|||||||
// Sign the document
|
// Sign the document
|
||||||
await page.goto(`/sign/${recipient.token}`);
|
await page.goto(`/sign/${recipient.token}`);
|
||||||
|
|
||||||
const canvas = page.locator('canvas');
|
await signSignaturePad(page);
|
||||||
const box = await canvas.boundingBox();
|
|
||||||
if (box) {
|
|
||||||
await page.mouse.move(box.x + 40, box.y + 40);
|
|
||||||
await page.mouse.down();
|
|
||||||
await page.mouse.move(box.x + box.width - 2, box.y + box.height - 2);
|
|
||||||
await page.mouse.up();
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const field of recipient.fields) {
|
for (const field of recipient.fields) {
|
||||||
await page.locator(`#field-${field.id}`).getByRole('button').click();
|
await page.locator(`#field-${field.id}`).getByRole('button').click();
|
||||||
|
|||||||
@ -21,31 +21,23 @@ export const apiSignin = async ({
|
|||||||
}: LoginOptions) => {
|
}: LoginOptions) => {
|
||||||
const { request } = page.context();
|
const { request } = page.context();
|
||||||
|
|
||||||
const csrfToken = await getCsrfToken(page);
|
// const csrfToken = await getCsrfToken(page);
|
||||||
|
|
||||||
await request.post(`${NEXT_PUBLIC_WEBAPP_URL()}/api/auth/callback/credentials`, {
|
await request.post(`${NEXT_PUBLIC_WEBAPP_URL()}/api/auth/email-password/authorize`, {
|
||||||
form: {
|
data: {
|
||||||
email,
|
email,
|
||||||
password,
|
password,
|
||||||
json: true,
|
|
||||||
csrfToken,
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
await page.goto(`${NEXT_PUBLIC_WEBAPP_URL()}${redirectPath}`);
|
await page.goto(`${NEXT_PUBLIC_WEBAPP_URL()}${redirectPath}`);
|
||||||
|
await page.waitForTimeout(500);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const apiSignout = async ({ page }: { page: Page }) => {
|
export const apiSignout = async ({ page }: { page: Page }) => {
|
||||||
const { request } = page.context();
|
const { request } = page.context();
|
||||||
|
|
||||||
const csrfToken = await getCsrfToken(page);
|
await request.post(`${NEXT_PUBLIC_WEBAPP_URL()}/api/auth/signout`);
|
||||||
|
|
||||||
await request.post(`${NEXT_PUBLIC_WEBAPP_URL()}/api/auth/signout`, {
|
|
||||||
form: {
|
|
||||||
csrfToken,
|
|
||||||
json: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
await page.goto(`${NEXT_PUBLIC_WEBAPP_URL()}/signin`);
|
await page.goto(`${NEXT_PUBLIC_WEBAPP_URL()}/signin`);
|
||||||
};
|
};
|
||||||
|
|||||||
40
packages/app-tests/e2e/fixtures/signature.ts
Normal file
40
packages/app-tests/e2e/fixtures/signature.ts
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
import type { Page } from '@playwright/test';
|
||||||
|
|
||||||
|
export const signSignaturePad = async (page: Page) => {
|
||||||
|
await page.waitForTimeout(200);
|
||||||
|
|
||||||
|
const canvas = page.getByTestId('signature-pad');
|
||||||
|
|
||||||
|
const box = await canvas.boundingBox();
|
||||||
|
|
||||||
|
if (!box) {
|
||||||
|
throw new Error('Signature pad not found');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate center point
|
||||||
|
const centerX = box.x + box.width / 2;
|
||||||
|
const centerY = box.y + box.height / 2;
|
||||||
|
|
||||||
|
// Calculate square size (making it slightly smaller than the canvas)
|
||||||
|
const squareSize = Math.min(box.width, box.height) * 0.4; // 40% of the smallest dimension
|
||||||
|
|
||||||
|
// Move to center
|
||||||
|
await page.mouse.move(centerX, centerY);
|
||||||
|
await page.mouse.down();
|
||||||
|
|
||||||
|
// Draw square clockwise from center
|
||||||
|
// Move right
|
||||||
|
await page.mouse.move(centerX + squareSize, centerY, { steps: 10 });
|
||||||
|
// Move down
|
||||||
|
await page.mouse.move(centerX + squareSize, centerY + squareSize, { steps: 10 });
|
||||||
|
// Move left
|
||||||
|
await page.mouse.move(centerX - squareSize, centerY + squareSize, { steps: 10 });
|
||||||
|
// Move up
|
||||||
|
await page.mouse.move(centerX - squareSize, centerY - squareSize, { steps: 10 });
|
||||||
|
// Move right
|
||||||
|
await page.mouse.move(centerX + squareSize, centerY - squareSize, { steps: 10 });
|
||||||
|
// Move down to close the square
|
||||||
|
await page.mouse.move(centerX + squareSize, centerY, { steps: 10 });
|
||||||
|
|
||||||
|
await page.mouse.up();
|
||||||
|
};
|
||||||
@ -61,7 +61,7 @@ test('[TEAMS]: search respects team document visibility', async ({ page }) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
await page.getByPlaceholder('Search documents...').fill('Searchable');
|
await page.getByPlaceholder('Search documents...').fill('Searchable');
|
||||||
await page.waitForURL(/search=Searchable/);
|
await page.waitForURL(/query=Searchable/);
|
||||||
|
|
||||||
await checkDocumentTabCount(page, 'All', visibleDocs);
|
await checkDocumentTabCount(page, 'All', visibleDocs);
|
||||||
|
|
||||||
@ -103,7 +103,7 @@ test('[TEAMS]: search does not reveal documents from other teams', async ({ page
|
|||||||
});
|
});
|
||||||
|
|
||||||
await page.getByPlaceholder('Search documents...').fill('Unique');
|
await page.getByPlaceholder('Search documents...').fill('Unique');
|
||||||
await page.waitForURL(/search=Unique/);
|
await page.waitForURL(/query=Unique/);
|
||||||
|
|
||||||
await checkDocumentTabCount(page, 'All', 1);
|
await checkDocumentTabCount(page, 'All', 1);
|
||||||
await expect(page.getByRole('link', { name: 'Unique Team A Document' })).toBeVisible();
|
await expect(page.getByRole('link', { name: 'Unique Team A Document' })).toBeVisible();
|
||||||
@ -144,7 +144,7 @@ test('[PERSONAL]: search does not reveal team documents in personal account', as
|
|||||||
});
|
});
|
||||||
|
|
||||||
await page.getByPlaceholder('Search documents...').fill('Unique');
|
await page.getByPlaceholder('Search documents...').fill('Unique');
|
||||||
await page.waitForURL(/search=Unique/);
|
await page.waitForURL(/query=Unique/);
|
||||||
|
|
||||||
await checkDocumentTabCount(page, 'All', 1);
|
await checkDocumentTabCount(page, 'All', 1);
|
||||||
await expect(page.getByRole('link', { name: 'Personal Unique Document' })).toBeVisible();
|
await expect(page.getByRole('link', { name: 'Personal Unique Document' })).toBeVisible();
|
||||||
@ -179,7 +179,7 @@ test('[TEAMS]: search respects recipient visibility regardless of team visibilit
|
|||||||
});
|
});
|
||||||
|
|
||||||
await page.getByPlaceholder('Search documents...').fill('Admin Document');
|
await page.getByPlaceholder('Search documents...').fill('Admin Document');
|
||||||
await page.waitForURL(/search=Admin(%20|\+|\s)Document/);
|
await page.waitForURL(/query=Admin(%20|\+|\s)Document/);
|
||||||
|
|
||||||
await checkDocumentTabCount(page, 'All', 1);
|
await checkDocumentTabCount(page, 'All', 1);
|
||||||
await expect(
|
await expect(
|
||||||
@ -221,7 +221,7 @@ test('[TEAMS]: search by recipient name respects visibility', async ({ page }) =
|
|||||||
});
|
});
|
||||||
|
|
||||||
await page.getByPlaceholder('Search documents...').fill('Unique Recipient');
|
await page.getByPlaceholder('Search documents...').fill('Unique Recipient');
|
||||||
await page.waitForURL(/search=Unique(%20|\+|\s)Recipient/);
|
await page.waitForURL(/query=Unique(%20|\+|\s)Recipient/);
|
||||||
|
|
||||||
await checkDocumentTabCount(page, 'All', 1);
|
await checkDocumentTabCount(page, 'All', 1);
|
||||||
await expect(
|
await expect(
|
||||||
@ -238,7 +238,7 @@ test('[TEAMS]: search by recipient name respects visibility', async ({ page }) =
|
|||||||
});
|
});
|
||||||
|
|
||||||
await page.getByPlaceholder('Search documents...').fill('Unique Recipient');
|
await page.getByPlaceholder('Search documents...').fill('Unique Recipient');
|
||||||
await page.waitForURL(/search=Unique(%20|\+|\s)Recipient/);
|
await page.waitForURL(/query=Unique(%20|\+|\s)Recipient/);
|
||||||
|
|
||||||
await checkDocumentTabCount(page, 'All', 0);
|
await checkDocumentTabCount(page, 'All', 0);
|
||||||
await expect(
|
await expect(
|
||||||
|
|||||||
@ -113,7 +113,7 @@ test('[TEAMS]: check team documents count with internal team email', async ({ pa
|
|||||||
await apiSignin({
|
await apiSignin({
|
||||||
page,
|
page,
|
||||||
email: user.email,
|
email: user.email,
|
||||||
redirectPath: `/t/${team.url}/documents`,
|
redirectPath: `/t/${team.url}/documents?perPage=20`,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Check document counts.
|
// Check document counts.
|
||||||
|
|||||||
@ -33,7 +33,6 @@ test('[TEAMS]: update team member role', async ({ page }) => {
|
|||||||
await page.getByLabel('Manager').click();
|
await page.getByLabel('Manager').click();
|
||||||
await page.getByRole('button', { name: 'Update' }).click();
|
await page.getByRole('button', { name: 'Update' }).click();
|
||||||
|
|
||||||
// TODO: Remove me, but i don't care for now
|
|
||||||
await page.reload();
|
await page.reload();
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
|
|||||||
@ -117,7 +117,7 @@ test('[DIRECT_TEMPLATES]: toggle direct template link', async ({ page }) => {
|
|||||||
|
|
||||||
// Check that the direct template link is no longer accessible.
|
// Check that the direct template link is no longer accessible.
|
||||||
await page.goto(formatDirectTemplatePath(template.directLink?.token || ''));
|
await page.goto(formatDirectTemplatePath(template.directLink?.token || ''));
|
||||||
await expect(page.getByText('Template not found')).toBeVisible();
|
await expect(page.getByText('404 not found')).toBeVisible();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -162,7 +162,7 @@ test('[DIRECT_TEMPLATES]: delete direct template link', async ({ page }) => {
|
|||||||
|
|
||||||
// Check that the direct template link is no longer accessible.
|
// Check that the direct template link is no longer accessible.
|
||||||
await page.goto(formatDirectTemplatePath(template.directLink?.token || ''));
|
await page.goto(formatDirectTemplatePath(template.directLink?.token || ''));
|
||||||
await expect(page.getByText('Template not found')).toBeVisible();
|
await expect(page.getByText('404 not found')).toBeVisible();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -42,13 +42,12 @@ test('[TEMPLATES]: view templates', async ({ page }) => {
|
|||||||
redirectPath: '/templates',
|
redirectPath: '/templates',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Only should only see their personal template.
|
||||||
|
await expect(page.getByTestId('data-table-count')).toContainText('Showing 1 result');
|
||||||
|
|
||||||
// Owner should see both team templates.
|
// Owner should see both team templates.
|
||||||
await page.goto(`${NEXT_PUBLIC_WEBAPP_URL()}/t/${team.url}/templates`);
|
await page.goto(`${NEXT_PUBLIC_WEBAPP_URL()}/t/${team.url}/templates`);
|
||||||
await expect(page.getByRole('main')).toContainText('Showing 2 results');
|
await expect(page.getByTestId('data-table-count')).toContainText('Showing 2 results');
|
||||||
|
|
||||||
// Only should only see their personal template.
|
|
||||||
await page.goto(`${NEXT_PUBLIC_WEBAPP_URL()}/templates`);
|
|
||||||
await expect(page.getByRole('main')).toContainText('Showing 1 result');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('[TEMPLATES]: delete template', async ({ page }) => {
|
test('[TEMPLATES]: delete template', async ({ page }) => {
|
||||||
@ -142,7 +141,7 @@ test('[TEMPLATES]: duplicate template', async ({ page }) => {
|
|||||||
await page.getByRole('menuitem', { name: 'Duplicate' }).click();
|
await page.getByRole('menuitem', { name: 'Duplicate' }).click();
|
||||||
await page.getByRole('button', { name: 'Duplicate' }).click();
|
await page.getByRole('button', { name: 'Duplicate' }).click();
|
||||||
await expect(page.getByText('Template duplicated').first()).toBeVisible();
|
await expect(page.getByText('Template duplicated').first()).toBeVisible();
|
||||||
await expect(page.getByRole('main')).toContainText('Showing 2 results');
|
await expect(page.getByTestId('data-table-count')).toContainText('Showing 2 results');
|
||||||
|
|
||||||
await page.goto(`${NEXT_PUBLIC_WEBAPP_URL()}/t/${team.url}/templates`);
|
await page.goto(`${NEXT_PUBLIC_WEBAPP_URL()}/t/${team.url}/templates`);
|
||||||
|
|
||||||
@ -151,7 +150,7 @@ test('[TEMPLATES]: duplicate template', async ({ page }) => {
|
|||||||
await page.getByRole('menuitem', { name: 'Duplicate' }).click();
|
await page.getByRole('menuitem', { name: 'Duplicate' }).click();
|
||||||
await page.getByRole('button', { name: 'Duplicate' }).click();
|
await page.getByRole('button', { name: 'Duplicate' }).click();
|
||||||
await expect(page.getByText('Template duplicated').first()).toBeVisible();
|
await expect(page.getByText('Template duplicated').first()).toBeVisible();
|
||||||
await expect(page.getByRole('main')).toContainText('Showing 2 results');
|
await expect(page.getByTestId('data-table-count')).toContainText('Showing 2 results');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('[TEMPLATES]: use template', async ({ page }) => {
|
test('[TEMPLATES]: use template', async ({ page }) => {
|
||||||
@ -194,7 +193,7 @@ test('[TEMPLATES]: use template', async ({ page }) => {
|
|||||||
await page.waitForURL(/documents/);
|
await page.waitForURL(/documents/);
|
||||||
await page.getByRole('main').getByRole('link', { name: 'Documents' }).click();
|
await page.getByRole('main').getByRole('link', { name: 'Documents' }).click();
|
||||||
await page.waitForURL('/documents');
|
await page.waitForURL('/documents');
|
||||||
await expect(page.getByRole('main')).toContainText('Showing 1 result');
|
await expect(page.getByTestId('data-table-count')).toContainText('Showing 1 result');
|
||||||
|
|
||||||
await page.goto(`${NEXT_PUBLIC_WEBAPP_URL()}/t/${team.url}/templates`);
|
await page.goto(`${NEXT_PUBLIC_WEBAPP_URL()}/t/${team.url}/templates`);
|
||||||
await page.waitForTimeout(1000);
|
await page.waitForTimeout(1000);
|
||||||
@ -212,5 +211,5 @@ test('[TEMPLATES]: use template', async ({ page }) => {
|
|||||||
await page.waitForURL(/\/t\/.+\/documents/);
|
await page.waitForURL(/\/t\/.+\/documents/);
|
||||||
await page.getByRole('main').getByRole('link', { name: 'Documents' }).click();
|
await page.getByRole('main').getByRole('link', { name: 'Documents' }).click();
|
||||||
await page.waitForURL(`/t/${team.url}/documents`);
|
await page.waitForURL(`/t/${team.url}/documents`);
|
||||||
await expect(page.getByRole('main')).toContainText('Showing 1 result');
|
await expect(page.getByTestId('data-table-count')).toContainText('Showing 1 result');
|
||||||
});
|
});
|
||||||
|
|||||||
@ -6,6 +6,8 @@ import {
|
|||||||
seedUser,
|
seedUser,
|
||||||
} from '@documenso/prisma/seed/users';
|
} from '@documenso/prisma/seed/users';
|
||||||
|
|
||||||
|
import { signSignaturePad } from '../fixtures/signature';
|
||||||
|
|
||||||
test.use({ storageState: { cookies: [], origins: [] } });
|
test.use({ storageState: { cookies: [], origins: [] } });
|
||||||
|
|
||||||
test('[USER] can sign up with email and password', async ({ page }: { page: Page }) => {
|
test('[USER] can sign up with email and password', async ({ page }: { page: Page }) => {
|
||||||
@ -18,14 +20,7 @@ test('[USER] can sign up with email and password', async ({ page }: { page: Page
|
|||||||
await page.getByLabel('Email').fill(email);
|
await page.getByLabel('Email').fill(email);
|
||||||
await page.getByLabel('Password', { exact: true }).fill(password);
|
await page.getByLabel('Password', { exact: true }).fill(password);
|
||||||
|
|
||||||
const canvas = page.locator('canvas').first();
|
await signSignaturePad(page);
|
||||||
const box = await canvas.boundingBox();
|
|
||||||
if (box) {
|
|
||||||
await page.mouse.move(box.x + 40, box.y + 40);
|
|
||||||
await page.mouse.down();
|
|
||||||
await page.mouse.move(box.x + box.width - 2, box.y + box.height - 2);
|
|
||||||
await page.mouse.up();
|
|
||||||
}
|
|
||||||
|
|
||||||
await page.getByRole('button', { name: 'Next', exact: true }).click();
|
await page.getByRole('button', { name: 'Next', exact: true }).click();
|
||||||
await page.getByLabel('Public profile username').fill(Date.now().toString());
|
await page.getByLabel('Public profile username').fill(Date.now().toString());
|
||||||
@ -41,7 +36,7 @@ test('[USER] can sign up with email and password', async ({ page }: { page: Page
|
|||||||
await expect(page.getByRole('heading')).toContainText('Email Confirmed!');
|
await expect(page.getByRole('heading')).toContainText('Email Confirmed!');
|
||||||
|
|
||||||
// We now automatically redirect to the home page
|
// We now automatically redirect to the home page
|
||||||
// await page.getByRole('link', { name: 'Go back home' }).click();
|
await page.getByRole('link', { name: 'Continue' }).click();
|
||||||
|
|
||||||
await page.waitForURL('/documents');
|
await page.waitForURL('/documents');
|
||||||
|
|
||||||
|
|||||||
@ -28,9 +28,7 @@ export const auth = new Hono<HonoAuthContext>()
|
|||||||
* Handle errors.
|
* Handle errors.
|
||||||
*/
|
*/
|
||||||
auth.onError((err, c) => {
|
auth.onError((err, c) => {
|
||||||
console.error(`-----------`);
|
// Todo Remove
|
||||||
console.error(`-----------`);
|
|
||||||
console.error(`-----------`);
|
|
||||||
console.error(`${err}`);
|
console.error(`${err}`);
|
||||||
|
|
||||||
if (err instanceof HTTPException) {
|
if (err instanceof HTTPException) {
|
||||||
|
|||||||
@ -2,6 +2,7 @@ import type { Context } from 'hono';
|
|||||||
import { deleteCookie, getSignedCookie, setSignedCookie } from 'hono/cookie';
|
import { deleteCookie, getSignedCookie, setSignedCookie } from 'hono/cookie';
|
||||||
|
|
||||||
import { NEXT_PUBLIC_WEBAPP_URL } from '@documenso/lib/constants/app';
|
import { NEXT_PUBLIC_WEBAPP_URL } from '@documenso/lib/constants/app';
|
||||||
|
import { useSecureCookies } from '@documenso/lib/constants/auth';
|
||||||
import { appLog } from '@documenso/lib/utils/debugger';
|
import { appLog } from '@documenso/lib/utils/debugger';
|
||||||
import { env } from '@documenso/lib/utils/env';
|
import { env } from '@documenso/lib/utils/env';
|
||||||
|
|
||||||
@ -23,6 +24,18 @@ const getAuthDomain = () => {
|
|||||||
return url.hostname;
|
return url.hostname;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generic auth session cookie options.
|
||||||
|
*/
|
||||||
|
export const sessionCookieOptions = {
|
||||||
|
httpOnly: true,
|
||||||
|
path: '/',
|
||||||
|
sameSite: useSecureCookies ? 'none' : 'lax',
|
||||||
|
secure: useSecureCookies,
|
||||||
|
domain: getAuthDomain(),
|
||||||
|
// Todo: Max age for specific auth cookies.
|
||||||
|
} as const;
|
||||||
|
|
||||||
export const extractSessionCookieFromHeaders = (headers: Headers): string | null => {
|
export const extractSessionCookieFromHeaders = (headers: Headers): string | null => {
|
||||||
const cookieHeader = headers.get('cookie') || '';
|
const cookieHeader = headers.get('cookie') || '';
|
||||||
const cookiePairs = cookieHeader.split(';');
|
const cookiePairs = cookieHeader.split(';');
|
||||||
@ -54,12 +67,13 @@ export const getSessionCookie = async (c: Context): Promise<string | null> => {
|
|||||||
* @param sessionToken - The session token to set.
|
* @param sessionToken - The session token to set.
|
||||||
*/
|
*/
|
||||||
export const setSessionCookie = async (c: Context, sessionToken: string) => {
|
export const setSessionCookie = async (c: Context, sessionToken: string) => {
|
||||||
await setSignedCookie(c, sessionCookieName, sessionToken, getAuthSecret(), {
|
await setSignedCookie(
|
||||||
path: '/',
|
c,
|
||||||
// sameSite: '', // whats the default? we need to change this for embed right?
|
sessionCookieName,
|
||||||
// secure: true,
|
sessionToken,
|
||||||
domain: getAuthDomain(),
|
getAuthSecret(),
|
||||||
}).catch((err) => {
|
sessionCookieOptions,
|
||||||
|
).catch((err) => {
|
||||||
appLog('SetSessionCookie', `Error setting signed cookie: ${err}`);
|
appLog('SetSessionCookie', `Error setting signed cookie: ${err}`);
|
||||||
|
|
||||||
throw err;
|
throw err;
|
||||||
@ -73,9 +87,5 @@ export const setSessionCookie = async (c: Context, sessionToken: string) => {
|
|||||||
* @param sessionToken - The session token to set.
|
* @param sessionToken - The session token to set.
|
||||||
*/
|
*/
|
||||||
export const deleteSessionCookie = (c: Context) => {
|
export const deleteSessionCookie = (c: Context) => {
|
||||||
deleteCookie(c, sessionCookieName, {
|
deleteCookie(c, sessionCookieName, sessionCookieOptions);
|
||||||
path: '/',
|
|
||||||
secure: true,
|
|
||||||
domain: getAuthDomain(),
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { sha256 } from '@oslojs/crypto/sha2';
|
import { sha256 } from '@oslojs/crypto/sha2';
|
||||||
import { encodeBase32LowerCaseNoPadding, encodeHexLowerCase } from '@oslojs/encoding';
|
import { encodeBase32LowerCaseNoPadding, encodeHexLowerCase } from '@oslojs/encoding';
|
||||||
import type { Session, User } from '@prisma/client';
|
import { type Session, type User, UserSecurityAuditLogType } from '@prisma/client';
|
||||||
|
|
||||||
import type { RequestMetadata } from '@documenso/lib/universal/extract-request-metadata';
|
import type { RequestMetadata } from '@documenso/lib/universal/extract-request-metadata';
|
||||||
import { prisma } from '@documenso/prisma';
|
import { prisma } from '@documenso/prisma';
|
||||||
@ -49,6 +49,15 @@ export const createSession = async (
|
|||||||
data: session,
|
data: session,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
await prisma.userSecurityAuditLog.create({
|
||||||
|
data: {
|
||||||
|
userId,
|
||||||
|
ipAddress: metadata.ipAddress,
|
||||||
|
userAgent: metadata.userAgent,
|
||||||
|
type: UserSecurityAuditLogType.SIGN_IN,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
return session;
|
return session;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -103,6 +112,18 @@ export const validateSessionToken = async (token: string): Promise<SessionValida
|
|||||||
return { session, user, isAuthenticated: true };
|
return { session, user, isAuthenticated: true };
|
||||||
};
|
};
|
||||||
|
|
||||||
export const invalidateSession = async (sessionId: string): Promise<void> => {
|
export const invalidateSession = async (
|
||||||
await prisma.session.delete({ where: { id: sessionId } });
|
sessionId: string,
|
||||||
|
metadata: RequestMetadata,
|
||||||
|
): Promise<void> => {
|
||||||
|
const session = await prisma.session.delete({ where: { id: sessionId } });
|
||||||
|
|
||||||
|
await prisma.userSecurityAuditLog.create({
|
||||||
|
data: {
|
||||||
|
userId: session.userId,
|
||||||
|
ipAddress: metadata.ipAddress,
|
||||||
|
userAgent: metadata.userAgent,
|
||||||
|
type: UserSecurityAuditLogType.SIGN_OUT,
|
||||||
|
},
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@ -19,4 +19,12 @@ export const onAuthorize = async (user: AuthorizeUser, c: Context<HonoAuthContex
|
|||||||
await createSession(sessionToken, user.userId, metadata);
|
await createSession(sessionToken, user.userId, metadata);
|
||||||
|
|
||||||
await setSessionCookie(c, sessionToken);
|
await setSessionCookie(c, sessionToken);
|
||||||
|
|
||||||
|
// Todo.
|
||||||
|
// Create the Stripe customer and attach it to the user if it doesn't exist.
|
||||||
|
// if (user.customerId === null && IS_BILLING_ENABLED()) {
|
||||||
|
// await getStripeCustomerByUser(user).catch((err) => {
|
||||||
|
// console.error(err);
|
||||||
|
// });
|
||||||
|
// }
|
||||||
};
|
};
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import { DateTime } from 'luxon';
|
|||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
import { IS_BILLING_ENABLED } from '@documenso/lib/constants/app';
|
import { IS_BILLING_ENABLED } from '@documenso/lib/constants/app';
|
||||||
|
import { EMAIL_VERIFICATION_STATE } from '@documenso/lib/constants/email';
|
||||||
import { AppError } from '@documenso/lib/errors/app-error';
|
import { AppError } from '@documenso/lib/errors/app-error';
|
||||||
import { jobsClient } from '@documenso/lib/jobs/client';
|
import { jobsClient } from '@documenso/lib/jobs/client';
|
||||||
import { disableTwoFactorAuthentication } from '@documenso/lib/server-only/2fa/disable-2fa';
|
import { disableTwoFactorAuthentication } from '@documenso/lib/server-only/2fa/disable-2fa';
|
||||||
@ -18,10 +19,7 @@ import { forgotPassword } from '@documenso/lib/server-only/user/forgot-password'
|
|||||||
import { getMostRecentVerificationTokenByUserId } from '@documenso/lib/server-only/user/get-most-recent-verification-token-by-user-id';
|
import { getMostRecentVerificationTokenByUserId } from '@documenso/lib/server-only/user/get-most-recent-verification-token-by-user-id';
|
||||||
import { resetPassword } from '@documenso/lib/server-only/user/reset-password';
|
import { resetPassword } from '@documenso/lib/server-only/user/reset-password';
|
||||||
import { updatePassword } from '@documenso/lib/server-only/user/update-password';
|
import { updatePassword } from '@documenso/lib/server-only/user/update-password';
|
||||||
import {
|
import { verifyEmail } from '@documenso/lib/server-only/user/verify-email';
|
||||||
EMAIL_VERIFICATION_STATE,
|
|
||||||
verifyEmail,
|
|
||||||
} from '@documenso/lib/server-only/user/verify-email';
|
|
||||||
import { env } from '@documenso/lib/utils/env';
|
import { env } from '@documenso/lib/utils/env';
|
||||||
import { prisma } from '@documenso/prisma';
|
import { prisma } from '@documenso/prisma';
|
||||||
import { UserSecurityAuditLogType } from '@documenso/prisma/client';
|
import { UserSecurityAuditLogType } from '@documenso/prisma/client';
|
||||||
|
|||||||
@ -10,6 +10,7 @@ import { env } from '@documenso/lib/utils/env';
|
|||||||
import { prisma } from '@documenso/prisma';
|
import { prisma } from '@documenso/prisma';
|
||||||
|
|
||||||
import { AuthenticationErrorCode } from '../lib/errors/error-codes';
|
import { AuthenticationErrorCode } from '../lib/errors/error-codes';
|
||||||
|
import { sessionCookieOptions } from '../lib/session/session-cookies';
|
||||||
import { onAuthorize } from '../lib/utils/authorizer';
|
import { onAuthorize } from '../lib/utils/authorizer';
|
||||||
import type { HonoAuthContext } from '../types/context';
|
import type { HonoAuthContext } from '../types/context';
|
||||||
|
|
||||||
@ -43,28 +44,22 @@ export const googleRoute = new Hono<HonoAuthContext>()
|
|||||||
const { redirectPath } = c.req.valid('json');
|
const { redirectPath } = c.req.valid('json');
|
||||||
|
|
||||||
setCookie(c, 'google_oauth_state', state, {
|
setCookie(c, 'google_oauth_state', state, {
|
||||||
path: '/',
|
...sessionCookieOptions,
|
||||||
httpOnly: true,
|
sameSite: 'lax', // Todo
|
||||||
secure: env('NODE_ENV') === 'production', // Todo: Check.
|
maxAge: 60 * 10, // 10 minutes.
|
||||||
maxAge: 60 * 10, // 10 minutes
|
|
||||||
sameSite: 'lax', // Todo??
|
|
||||||
});
|
});
|
||||||
|
|
||||||
setCookie(c, 'google_code_verifier', codeVerifier, {
|
setCookie(c, 'google_code_verifier', codeVerifier, {
|
||||||
path: '/',
|
...sessionCookieOptions,
|
||||||
httpOnly: true,
|
sameSite: 'lax', // Todo
|
||||||
secure: env('NODE_ENV') === 'production', // Todo: Check.
|
maxAge: 60 * 10, // 10 minutes.
|
||||||
maxAge: 60 * 10, // 10 minutes
|
|
||||||
sameSite: 'lax', // Todo??
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (redirectPath) {
|
if (redirectPath) {
|
||||||
setCookie(c, 'google_redirect_path', `${state}:${redirectPath}`, {
|
setCookie(c, 'google_redirect_path', `${state}:${redirectPath}`, {
|
||||||
path: '/',
|
...sessionCookieOptions,
|
||||||
httpOnly: true,
|
sameSite: 'lax', // Todo
|
||||||
secure: env('NODE_ENV') === 'production', // Todo: Check.
|
maxAge: 60 * 10, // 10 minutes.
|
||||||
maxAge: 60 * 10, // 10 minutes
|
|
||||||
sameSite: 'lax', // Todo??
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -81,6 +76,7 @@ export const googleRoute = new Hono<HonoAuthContext>()
|
|||||||
|
|
||||||
const storedState = deleteCookie(c, 'google_oauth_state');
|
const storedState = deleteCookie(c, 'google_oauth_state');
|
||||||
const storedCodeVerifier = deleteCookie(c, 'google_code_verifier');
|
const storedCodeVerifier = deleteCookie(c, 'google_code_verifier');
|
||||||
|
const storedredirectPath = deleteCookie(c, 'google_redirect_path') ?? '';
|
||||||
|
|
||||||
if (!code || !storedState || state !== storedState || !storedCodeVerifier) {
|
if (!code || !storedState || state !== storedState || !storedCodeVerifier) {
|
||||||
throw new AppError(AppErrorCode.INVALID_REQUEST, {
|
throw new AppError(AppErrorCode.INVALID_REQUEST, {
|
||||||
@ -88,8 +84,6 @@ export const googleRoute = new Hono<HonoAuthContext>()
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const storedredirectPath = deleteCookie(c, 'google_redirect_path') ?? '';
|
|
||||||
|
|
||||||
// eslint-disable-next-line prefer-const
|
// eslint-disable-next-line prefer-const
|
||||||
let [redirectState, redirectPath] = storedredirectPath.split(':');
|
let [redirectState, redirectPath] = storedredirectPath.split(':');
|
||||||
|
|
||||||
|
|||||||
@ -2,8 +2,11 @@ import { Hono } from 'hono';
|
|||||||
|
|
||||||
import { invalidateSession, validateSessionToken } from '../lib/session/session';
|
import { invalidateSession, validateSessionToken } from '../lib/session/session';
|
||||||
import { deleteSessionCookie, getSessionCookie } from '../lib/session/session-cookies';
|
import { deleteSessionCookie, getSessionCookie } from '../lib/session/session-cookies';
|
||||||
|
import type { HonoAuthContext } from '../types/context';
|
||||||
|
|
||||||
|
export const signOutRoute = new Hono<HonoAuthContext>().post('/signout', async (c) => {
|
||||||
|
const metadata = c.get('requestMetadata');
|
||||||
|
|
||||||
export const signOutRoute = new Hono().post('/signout', async (c) => {
|
|
||||||
const sessionId = await getSessionCookie(c);
|
const sessionId = await getSessionCookie(c);
|
||||||
|
|
||||||
if (!sessionId) {
|
if (!sessionId) {
|
||||||
@ -16,7 +19,7 @@ export const signOutRoute = new Hono().post('/signout', async (c) => {
|
|||||||
return new Response('No session found', { status: 401 });
|
return new Response('No session found', { status: 401 });
|
||||||
}
|
}
|
||||||
|
|
||||||
await invalidateSession(session.id);
|
await invalidateSession(session.id, metadata);
|
||||||
|
|
||||||
deleteSessionCookie(c);
|
deleteSessionCookie(c);
|
||||||
|
|
||||||
|
|||||||
@ -47,9 +47,11 @@ export const PASSKEY_TIMEOUT = 60000;
|
|||||||
*/
|
*/
|
||||||
export const MAXIMUM_PASSKEYS = 50;
|
export const MAXIMUM_PASSKEYS = 50;
|
||||||
|
|
||||||
|
// Todo: nextuauth_url ??
|
||||||
export const useSecureCookies =
|
export const useSecureCookies =
|
||||||
env('NODE_ENV') === 'production' && String(env('NEXTAUTH_URL')).startsWith('https://');
|
env('NODE_ENV') === 'production' && String(env('NEXTAUTH_URL')).startsWith('https://');
|
||||||
|
|
||||||
|
// Todo: Test secure cookies prefix in remix.
|
||||||
const secureCookiePrefix = useSecureCookies ? '__Secure-' : '';
|
const secureCookiePrefix = useSecureCookies ? '__Secure-' : '';
|
||||||
|
|
||||||
export const formatSecureCookieName = (name: string) => `${secureCookiePrefix}${name}`;
|
export const formatSecureCookieName = (name: string) => `${secureCookiePrefix}${name}`;
|
||||||
|
|||||||
@ -4,3 +4,10 @@ export const FROM_ADDRESS = env('NEXT_PRIVATE_SMTP_FROM_ADDRESS') || 'noreply@do
|
|||||||
export const FROM_NAME = env('NEXT_PRIVATE_SMTP_FROM_NAME') || 'Documenso';
|
export const FROM_NAME = env('NEXT_PRIVATE_SMTP_FROM_NAME') || 'Documenso';
|
||||||
|
|
||||||
export const SERVICE_USER_EMAIL = 'serviceaccount@documenso.com';
|
export const SERVICE_USER_EMAIL = 'serviceaccount@documenso.com';
|
||||||
|
|
||||||
|
export const EMAIL_VERIFICATION_STATE = {
|
||||||
|
NOT_FOUND: 'NOT_FOUND',
|
||||||
|
VERIFIED: 'VERIFIED',
|
||||||
|
EXPIRED: 'EXPIRED',
|
||||||
|
ALREADY_VERIFIED: 'ALREADY_VERIFIED',
|
||||||
|
} as const;
|
||||||
|
|||||||
@ -68,13 +68,12 @@ export class LocalJobProvider extends BaseJobProvider {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public getApiHandler(): (context: HonoContext) => Promise<Response | void> {
|
public getApiHandler(): (c: HonoContext) => Promise<Response | void> {
|
||||||
return async (context: HonoContext) => {
|
return async (c: HonoContext) => {
|
||||||
const req = context.req;
|
const req = c.req;
|
||||||
|
|
||||||
if (req.method !== 'POST') {
|
if (req.method !== 'POST') {
|
||||||
context.text('Method not allowed', 405);
|
return c.text('Method not allowed', 405);
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const jobId = req.header('x-job-id');
|
const jobId = req.header('x-job-id');
|
||||||
@ -89,8 +88,7 @@ export class LocalJobProvider extends BaseJobProvider {
|
|||||||
.catch(() => null);
|
.catch(() => null);
|
||||||
|
|
||||||
if (!options) {
|
if (!options) {
|
||||||
context.text('Bad request', 400);
|
return c.text('Bad request', 400);
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const definition = this._jobDefinitions[options.name];
|
const definition = this._jobDefinitions[options.name];
|
||||||
@ -100,33 +98,28 @@ export class LocalJobProvider extends BaseJobProvider {
|
|||||||
typeof signature !== 'string' ||
|
typeof signature !== 'string' ||
|
||||||
typeof options !== 'object'
|
typeof options !== 'object'
|
||||||
) {
|
) {
|
||||||
context.text('Bad request', 400);
|
return c.text('Bad request', 400);
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!definition) {
|
if (!definition) {
|
||||||
context.text('Job not found', 404);
|
return c.text('Job not found', 404);
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (definition && !definition.enabled) {
|
if (definition && !definition.enabled) {
|
||||||
console.log('Attempted to trigger a disabled job', options.name);
|
console.log('Attempted to trigger a disabled job', options.name);
|
||||||
|
|
||||||
context.text('Job not found', 404);
|
return c.text('Job not found', 404);
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!signature || !verify(options, signature)) {
|
if (!signature || !verify(options, signature)) {
|
||||||
context.text('Unauthorized', 401);
|
return c.text('Unauthorized', 401);
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (definition.trigger.schema) {
|
if (definition.trigger.schema) {
|
||||||
const result = definition.trigger.schema.safeParse(options.payload);
|
const result = definition.trigger.schema.safeParse(options.payload);
|
||||||
|
|
||||||
if (!result.success) {
|
if (!result.success) {
|
||||||
context.text('Bad request', 400);
|
return c.text('Bad request', 400);
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -149,8 +142,7 @@ export class LocalJobProvider extends BaseJobProvider {
|
|||||||
.catch(() => null);
|
.catch(() => null);
|
||||||
|
|
||||||
if (!backgroundJob) {
|
if (!backgroundJob) {
|
||||||
context.text('Job not found', 404);
|
return c.text('Job not found', 404);
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -189,8 +181,7 @@ export class LocalJobProvider extends BaseJobProvider {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
context.text('Task exceeded retries', 500);
|
return c.text('Task exceeded retries', 500);
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
backgroundJob = await prisma.backgroundJob.update({
|
backgroundJob = await prisma.backgroundJob.update({
|
||||||
@ -210,7 +201,7 @@ export class LocalJobProvider extends BaseJobProvider {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
context.text('OK', 200);
|
return c.text('OK', 200);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -134,7 +134,7 @@ export const resendDocument = async ({
|
|||||||
emailMessage =
|
emailMessage =
|
||||||
customEmail?.message ||
|
customEmail?.message ||
|
||||||
i18n._(
|
i18n._(
|
||||||
msg`${user.name} on behalf of "${document.team.name}" has invited you to ${recipientActionVerb} the document "${document.title}".`,
|
msg`${user.name || user.email} on behalf of "${document.team.name}" has invited you to ${recipientActionVerb} the document "${document.title}".`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -164,20 +164,20 @@ export const resendDocument = async ({
|
|||||||
? teamGlobalSettingsToBranding(document.team.teamGlobalSettings)
|
? teamGlobalSettingsToBranding(document.team.teamGlobalSettings)
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
|
const [html, text] = await Promise.all([
|
||||||
|
renderEmailWithI18N(template, {
|
||||||
|
lang: document.documentMeta?.language,
|
||||||
|
branding,
|
||||||
|
}),
|
||||||
|
renderEmailWithI18N(template, {
|
||||||
|
lang: document.documentMeta?.language,
|
||||||
|
branding,
|
||||||
|
plainText: true,
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
|
||||||
await prisma.$transaction(
|
await prisma.$transaction(
|
||||||
async (tx) => {
|
async (tx) => {
|
||||||
const [html, text] = await Promise.all([
|
|
||||||
renderEmailWithI18N(template, {
|
|
||||||
lang: document.documentMeta?.language,
|
|
||||||
branding,
|
|
||||||
}),
|
|
||||||
renderEmailWithI18N(template, {
|
|
||||||
lang: document.documentMeta?.language,
|
|
||||||
branding,
|
|
||||||
plainText: true,
|
|
||||||
}),
|
|
||||||
]);
|
|
||||||
|
|
||||||
await mailer.sendMail({
|
await mailer.sendMail({
|
||||||
to: {
|
to: {
|
||||||
address: email,
|
address: email,
|
||||||
|
|||||||
@ -2,15 +2,9 @@ import { DateTime } from 'luxon';
|
|||||||
|
|
||||||
import { prisma } from '@documenso/prisma';
|
import { prisma } from '@documenso/prisma';
|
||||||
|
|
||||||
|
import { EMAIL_VERIFICATION_STATE } from '../../constants/email';
|
||||||
import { jobsClient } from '../../jobs/client';
|
import { jobsClient } from '../../jobs/client';
|
||||||
|
|
||||||
export const EMAIL_VERIFICATION_STATE = {
|
|
||||||
NOT_FOUND: 'NOT_FOUND',
|
|
||||||
VERIFIED: 'VERIFIED',
|
|
||||||
EXPIRED: 'EXPIRED',
|
|
||||||
ALREADY_VERIFIED: 'ALREADY_VERIFIED',
|
|
||||||
} as const;
|
|
||||||
|
|
||||||
export type VerifyEmailProps = {
|
export type VerifyEmailProps = {
|
||||||
token: string;
|
token: string;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -477,6 +477,7 @@ export const SignaturePad = ({
|
|||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<canvas
|
<canvas
|
||||||
|
data-testid="signature-pad"
|
||||||
ref={$el}
|
ref={$el}
|
||||||
className={cn(
|
className={cn(
|
||||||
'relative block',
|
'relative block',
|
||||||
|
|||||||
Reference in New Issue
Block a user