mirror of
https://github.com/documenso/documenso.git
synced 2025-11-13 00:03:33 +10:00
Compare commits
1 Commits
88371b665a
...
exp/effect
| Author | SHA1 | Date | |
|---|---|---|---|
| db4d33d039 |
@ -1,4 +1,4 @@
|
|||||||
import { useEffect, useState } from 'react';
|
import { useState } from 'react';
|
||||||
|
|
||||||
import { zodResolver } from '@hookform/resolvers/zod';
|
import { zodResolver } from '@hookform/resolvers/zod';
|
||||||
import { useLingui } from '@lingui/react/macro';
|
import { useLingui } from '@lingui/react/macro';
|
||||||
@ -96,16 +96,17 @@ export const AdminOrganisationCreateDialog = ({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
const handleOpenChange = (value: boolean) => {
|
||||||
form.reset();
|
if (!value) {
|
||||||
}, [open, form]);
|
form.reset();
|
||||||
|
}
|
||||||
|
if (!form.formState.isSubmitting) {
|
||||||
|
setOpen(value);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog
|
<Dialog {...props} open={open} onOpenChange={handleOpenChange}>
|
||||||
{...props}
|
|
||||||
open={open}
|
|
||||||
onOpenChange={(value) => !form.formState.isSubmitting && setOpen(value)}
|
|
||||||
>
|
|
||||||
<DialogTrigger onClick={(e) => e.stopPropagation()} asChild={true}>
|
<DialogTrigger onClick={(e) => e.stopPropagation()} asChild={true}>
|
||||||
{trigger ?? (
|
{trigger ?? (
|
||||||
<Button className="flex-shrink-0" variant="secondary">
|
<Button className="flex-shrink-0" variant="secondary">
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { useEffect, useState } 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';
|
||||||
@ -73,20 +73,23 @@ export const DocumentDeleteDialog = ({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (open) {
|
|
||||||
setInputValue('');
|
|
||||||
setIsDeleteEnabled(status === DocumentStatus.DRAFT);
|
|
||||||
}
|
|
||||||
}, [open, status]);
|
|
||||||
|
|
||||||
const onInputChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
const onInputChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
setInputValue(event.target.value);
|
setInputValue(event.target.value);
|
||||||
setIsDeleteEnabled(event.target.value === _(deleteMessage));
|
setIsDeleteEnabled(event.target.value === _(deleteMessage));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleOpenChange = (value: boolean) => {
|
||||||
|
if (value) {
|
||||||
|
setInputValue('');
|
||||||
|
setIsDeleteEnabled(status === DocumentStatus.DRAFT);
|
||||||
|
}
|
||||||
|
if (!isPending) {
|
||||||
|
onOpenChange(value);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog open={open} onOpenChange={(value) => !isPending && onOpenChange(value)}>
|
<Dialog open={open} onOpenChange={handleOpenChange}>
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>
|
<DialogTitle>
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { useEffect, useState } from 'react';
|
import { useState } from 'react';
|
||||||
|
|
||||||
import { zodResolver } from '@hookform/resolvers/zod';
|
import { zodResolver } from '@hookform/resolvers/zod';
|
||||||
import { msg } from '@lingui/core/macro';
|
import { msg } from '@lingui/core/macro';
|
||||||
@ -83,15 +83,6 @@ export const DocumentMoveToFolderDialog = ({
|
|||||||
|
|
||||||
const { mutateAsync: moveDocumentToFolder } = trpc.folder.moveDocumentToFolder.useMutation();
|
const { mutateAsync: moveDocumentToFolder } = trpc.folder.moveDocumentToFolder.useMutation();
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!open) {
|
|
||||||
form.reset();
|
|
||||||
setSearchTerm('');
|
|
||||||
} else {
|
|
||||||
form.reset({ folderId: currentFolderId });
|
|
||||||
}
|
|
||||||
}, [open, currentFolderId, form]);
|
|
||||||
|
|
||||||
const onSubmit = async (data: TMoveDocumentFormSchema) => {
|
const onSubmit = async (data: TMoveDocumentFormSchema) => {
|
||||||
try {
|
try {
|
||||||
await moveDocumentToFolder({
|
await moveDocumentToFolder({
|
||||||
@ -145,12 +136,22 @@ export const DocumentMoveToFolderDialog = ({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleOpenChange = (value: boolean) => {
|
||||||
|
if (!value) {
|
||||||
|
form.reset();
|
||||||
|
setSearchTerm('');
|
||||||
|
} else {
|
||||||
|
form.reset({ folderId: currentFolderId });
|
||||||
|
}
|
||||||
|
onOpenChange(value);
|
||||||
|
};
|
||||||
|
|
||||||
const filteredFolders = folders?.data.filter((folder) =>
|
const filteredFolders = folders?.data.filter((folder) =>
|
||||||
folder.name.toLowerCase().includes(searchTerm.toLowerCase()),
|
folder.name.toLowerCase().includes(searchTerm.toLowerCase()),
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog {...props} open={open} onOpenChange={onOpenChange}>
|
<Dialog {...props} open={open} onOpenChange={handleOpenChange}>
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>
|
<DialogTitle>
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { useEffect, useState } from 'react';
|
import { useState } from 'react';
|
||||||
|
|
||||||
import { zodResolver } from '@hookform/resolvers/zod';
|
import { zodResolver } from '@hookform/resolvers/zod';
|
||||||
import { Trans, useLingui } from '@lingui/react/macro';
|
import { Trans, useLingui } from '@lingui/react/macro';
|
||||||
@ -80,14 +80,15 @@ export const FolderCreateDialog = ({ type, trigger, ...props }: FolderCreateDial
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
const handleOpenChange = (value: boolean) => {
|
||||||
if (!isCreateFolderOpen) {
|
if (!value) {
|
||||||
form.reset();
|
form.reset();
|
||||||
}
|
}
|
||||||
}, [isCreateFolderOpen, form]);
|
setIsCreateFolderOpen(value);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog {...props} open={isCreateFolderOpen} onOpenChange={setIsCreateFolderOpen}>
|
<Dialog {...props} open={isCreateFolderOpen} onOpenChange={handleOpenChange}>
|
||||||
<DialogTrigger asChild>
|
<DialogTrigger asChild>
|
||||||
{trigger ?? (
|
{trigger ?? (
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
@ -1,5 +1,3 @@
|
|||||||
import { useEffect } from 'react';
|
|
||||||
|
|
||||||
import { zodResolver } from '@hookform/resolvers/zod';
|
import { zodResolver } from '@hookform/resolvers/zod';
|
||||||
import { useLingui } from '@lingui/react/macro';
|
import { useLingui } from '@lingui/react/macro';
|
||||||
import { Trans } from '@lingui/react/macro';
|
import { Trans } from '@lingui/react/macro';
|
||||||
@ -92,14 +90,15 @@ export const FolderDeleteDialog = ({ folder, isOpen, onOpenChange }: FolderDelet
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
const handleOpenChange = (value: boolean) => {
|
||||||
if (!isOpen) {
|
if (!value) {
|
||||||
form.reset();
|
form.reset();
|
||||||
}
|
}
|
||||||
}, [isOpen]);
|
onOpenChange(value);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog open={isOpen} onOpenChange={onOpenChange}>
|
<Dialog open={isOpen} onOpenChange={handleOpenChange}>
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>
|
<DialogTitle>
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { useEffect, useState } from 'react';
|
import { useState } from 'react';
|
||||||
|
|
||||||
import { zodResolver } from '@hookform/resolvers/zod';
|
import { zodResolver } from '@hookform/resolvers/zod';
|
||||||
import { useLingui } from '@lingui/react/macro';
|
import { useLingui } from '@lingui/react/macro';
|
||||||
@ -97,12 +97,13 @@ export const FolderMoveDialog = ({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
const handleOpenChange = (value: boolean) => {
|
||||||
if (!isOpen) {
|
if (!value) {
|
||||||
form.reset();
|
form.reset();
|
||||||
setSearchTerm('');
|
setSearchTerm('');
|
||||||
}
|
}
|
||||||
}, [isOpen, form]);
|
onOpenChange(value);
|
||||||
|
};
|
||||||
|
|
||||||
// Filter out the current folder, only show folders of the same type, and filter by search term
|
// Filter out the current folder, only show folders of the same type, and filter by search term
|
||||||
const filteredFolders = foldersData?.filter(
|
const filteredFolders = foldersData?.filter(
|
||||||
@ -113,7 +114,7 @@ export const FolderMoveDialog = ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog open={isOpen} onOpenChange={onOpenChange}>
|
<Dialog open={isOpen} onOpenChange={handleOpenChange}>
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>
|
<DialogTitle>
|
||||||
|
|||||||
@ -1,5 +1,3 @@
|
|||||||
import { useEffect } from 'react';
|
|
||||||
|
|
||||||
import { zodResolver } from '@hookform/resolvers/zod';
|
import { zodResolver } from '@hookform/resolvers/zod';
|
||||||
import { useLingui } from '@lingui/react/macro';
|
import { useLingui } from '@lingui/react/macro';
|
||||||
import { Trans } from '@lingui/react/macro';
|
import { Trans } from '@lingui/react/macro';
|
||||||
@ -71,15 +69,6 @@ export const FolderUpdateDialog = ({ folder, isOpen, onOpenChange }: FolderUpdat
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (folder) {
|
|
||||||
form.reset({
|
|
||||||
name: folder.name,
|
|
||||||
visibility: folder.visibility ?? DocumentVisibility.EVERYONE,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}, [folder, form]);
|
|
||||||
|
|
||||||
const onFormSubmit = async (data: TUpdateFolderFormSchema) => {
|
const onFormSubmit = async (data: TUpdateFolderFormSchema) => {
|
||||||
if (!folder) {
|
if (!folder) {
|
||||||
return;
|
return;
|
||||||
@ -110,8 +99,18 @@ export const FolderUpdateDialog = ({ folder, isOpen, onOpenChange }: FolderUpdat
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleOpenChange = (value: boolean) => {
|
||||||
|
if (value && folder) {
|
||||||
|
form.reset({
|
||||||
|
name: folder.name,
|
||||||
|
visibility: folder.visibility ?? DocumentVisibility.EVERYONE,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
onOpenChange(value);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog open={isOpen} onOpenChange={onOpenChange}>
|
<Dialog open={isOpen} onOpenChange={handleOpenChange}>
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>
|
<DialogTitle>
|
||||||
|
|||||||
@ -76,7 +76,7 @@ export const OrganisationCreateDialog = ({ trigger, ...props }: OrganisationCrea
|
|||||||
|
|
||||||
const [selectedPriceId, setSelectedPriceId] = useState<string>('');
|
const [selectedPriceId, setSelectedPriceId] = useState<string>('');
|
||||||
|
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(actionSearchParam === 'add-organisation');
|
||||||
|
|
||||||
const form = useForm({
|
const form = useForm({
|
||||||
resolver: zodResolver(ZCreateOrganisationFormSchema),
|
resolver: zodResolver(ZCreateOrganisationFormSchema),
|
||||||
@ -91,6 +91,19 @@ export const OrganisationCreateDialog = ({ trigger, ...props }: OrganisationCrea
|
|||||||
enabled: IS_BILLING_ENABLED(),
|
enabled: IS_BILLING_ENABLED(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const handleOpenChange = (value: boolean) => {
|
||||||
|
if (!value) {
|
||||||
|
form.reset();
|
||||||
|
if (actionSearchParam === 'add-organisation') {
|
||||||
|
updateSearchParams({ action: null });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!form.formState.isSubmitting) {
|
||||||
|
setOpen(value);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const onFormSubmit = async ({ name }: TCreateOrganisationFormSchema) => {
|
const onFormSubmit = async ({ name }: TCreateOrganisationFormSchema) => {
|
||||||
try {
|
try {
|
||||||
const response = await createOrganisation({
|
const response = await createOrganisation({
|
||||||
@ -126,17 +139,6 @@ export const OrganisationCreateDialog = ({ trigger, ...props }: OrganisationCrea
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (actionSearchParam === 'add-organisation') {
|
|
||||||
setOpen(true);
|
|
||||||
updateSearchParams({ action: null });
|
|
||||||
}
|
|
||||||
}, [actionSearchParam, open]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
form.reset();
|
|
||||||
}, [open, form]);
|
|
||||||
|
|
||||||
const isIndividualPlan = (priceId: string) => {
|
const isIndividualPlan = (priceId: string) => {
|
||||||
return (
|
return (
|
||||||
plansData?.plans[INTERNAL_CLAIM_ID.INDIVIDUAL]?.monthlyPrice?.id === priceId ||
|
plansData?.plans[INTERNAL_CLAIM_ID.INDIVIDUAL]?.monthlyPrice?.id === priceId ||
|
||||||
@ -145,11 +147,7 @@ export const OrganisationCreateDialog = ({ trigger, ...props }: OrganisationCrea
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog
|
<Dialog {...props} open={open} onOpenChange={handleOpenChange}>
|
||||||
{...props}
|
|
||||||
open={open}
|
|
||||||
onOpenChange={(value) => !form.formState.isSubmitting && setOpen(value)}
|
|
||||||
>
|
|
||||||
<DialogTrigger onClick={(e) => e.stopPropagation()} asChild={true}>
|
<DialogTrigger onClick={(e) => e.stopPropagation()} asChild={true}>
|
||||||
{trigger ?? (
|
{trigger ?? (
|
||||||
<Button className="flex-shrink-0" variant="secondary">
|
<Button className="flex-shrink-0" variant="secondary">
|
||||||
@ -314,13 +312,16 @@ const BillingPlanForm = ({
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}, [plans]);
|
}, [plans, t]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (value === '' && !canCreateFreeOrganisation) {
|
if (value === '' && !canCreateFreeOrganisation && dynamicPlans.length > 0) {
|
||||||
onChange(dynamicPlans[0][billingPeriod]?.id ?? '');
|
const defaultValue = dynamicPlans[0][billingPeriod]?.id ?? '';
|
||||||
|
if (defaultValue) {
|
||||||
|
onChange(defaultValue);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}, [value]);
|
}, [canCreateFreeOrganisation, dynamicPlans, billingPeriod, onChange, value]);
|
||||||
|
|
||||||
const onBillingPeriodChange = (billingPeriod: 'monthlyPrice' | 'yearlyPrice') => {
|
const onBillingPeriodChange = (billingPeriod: 'monthlyPrice' | 'yearlyPrice') => {
|
||||||
const plan = dynamicPlans.find(
|
const plan = dynamicPlans.find(
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { useEffect, useState } from 'react';
|
import { useState } from 'react';
|
||||||
|
|
||||||
import { zodResolver } from '@hookform/resolvers/zod';
|
import { zodResolver } from '@hookform/resolvers/zod';
|
||||||
import { msg } from '@lingui/core/macro';
|
import { msg } from '@lingui/core/macro';
|
||||||
@ -93,14 +93,19 @@ export const OrganisationDeleteDialog = ({ trigger }: OrganisationDeleteDialogPr
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
const handleOpenChange = (value: boolean) => {
|
||||||
if (!open) {
|
if (form.formState.isSubmitting) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!value) {
|
||||||
form.reset();
|
form.reset();
|
||||||
}
|
}
|
||||||
}, [open, form]);
|
setOpen(value);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog open={open} onOpenChange={(value) => !form.formState.isSubmitting && setOpen(value)}>
|
<Dialog open={open} onOpenChange={handleOpenChange}>
|
||||||
<DialogTrigger asChild>
|
<DialogTrigger asChild>
|
||||||
{trigger ?? (
|
{trigger ?? (
|
||||||
<Button variant="destructive">
|
<Button variant="destructive">
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { useEffect, useState } from 'react';
|
import { useState } from 'react';
|
||||||
|
|
||||||
import { zodResolver } from '@hookform/resolvers/zod';
|
import { zodResolver } from '@hookform/resolvers/zod';
|
||||||
import { useLingui } from '@lingui/react/macro';
|
import { useLingui } from '@lingui/react/macro';
|
||||||
@ -73,13 +73,6 @@ export const OrganisationEmailCreateDialog = ({
|
|||||||
const { mutateAsync: createOrganisationEmail, isPending } =
|
const { mutateAsync: createOrganisationEmail, isPending } =
|
||||||
trpc.enterprise.organisation.email.create.useMutation();
|
trpc.enterprise.organisation.email.create.useMutation();
|
||||||
|
|
||||||
// Reset state when dialog closes
|
|
||||||
useEffect(() => {
|
|
||||||
if (!open) {
|
|
||||||
form.reset();
|
|
||||||
}
|
|
||||||
}, [open, form]);
|
|
||||||
|
|
||||||
const onFormSubmit = async (data: TCreateOrganisationEmailFormSchema) => {
|
const onFormSubmit = async (data: TCreateOrganisationEmailFormSchema) => {
|
||||||
try {
|
try {
|
||||||
await createOrganisationEmail({
|
await createOrganisationEmail({
|
||||||
@ -114,8 +107,17 @@ export const OrganisationEmailCreateDialog = ({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleOpenChange = (value: boolean) => {
|
||||||
|
if (!value) {
|
||||||
|
form.reset();
|
||||||
|
}
|
||||||
|
if (!isPending) {
|
||||||
|
setOpen(value);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog {...props} open={open} onOpenChange={(value) => !isPending && setOpen(value)}>
|
<Dialog {...props} open={open} onOpenChange={handleOpenChange}>
|
||||||
<DialogTrigger onClick={(e) => e.stopPropagation()} asChild={true}>
|
<DialogTrigger onClick={(e) => e.stopPropagation()} asChild={true}>
|
||||||
{trigger ?? (
|
{trigger ?? (
|
||||||
<Button className="flex-shrink-0" variant="secondary">
|
<Button className="flex-shrink-0" variant="secondary">
|
||||||
|
|||||||
@ -36,6 +36,12 @@ export const OrganisationEmailDeleteDialog = ({
|
|||||||
|
|
||||||
const organisation = useCurrentOrganisation();
|
const organisation = useCurrentOrganisation();
|
||||||
|
|
||||||
|
const handleOpenChange = (value: boolean) => {
|
||||||
|
if (!isDeleting) {
|
||||||
|
setOpen(value);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const { mutateAsync: deleteEmail, isPending: isDeleting } =
|
const { mutateAsync: deleteEmail, isPending: isDeleting } =
|
||||||
trpc.enterprise.organisation.email.delete.useMutation({
|
trpc.enterprise.organisation.email.delete.useMutation({
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
@ -58,7 +64,7 @@ export const OrganisationEmailDeleteDialog = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog open={open} onOpenChange={(value) => !isDeleting && setOpen(value)}>
|
<Dialog open={open} onOpenChange={handleOpenChange}>
|
||||||
<DialogTrigger asChild>
|
<DialogTrigger asChild>
|
||||||
{trigger ?? (
|
{trigger ?? (
|
||||||
<Button variant="secondary">
|
<Button variant="secondary">
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { useEffect, useState } from 'react';
|
import { useState } from 'react';
|
||||||
|
|
||||||
import { zodResolver } from '@hookform/resolvers/zod';
|
import { zodResolver } from '@hookform/resolvers/zod';
|
||||||
import { useLingui } from '@lingui/react/macro';
|
import { useLingui } from '@lingui/react/macro';
|
||||||
@ -75,14 +75,6 @@ export const OrganisationEmailDomainCreateDialog = ({
|
|||||||
const { mutateAsync: createOrganisationEmail } =
|
const { mutateAsync: createOrganisationEmail } =
|
||||||
trpc.enterprise.organisation.emailDomain.create.useMutation();
|
trpc.enterprise.organisation.emailDomain.create.useMutation();
|
||||||
|
|
||||||
// Reset state when dialog closes
|
|
||||||
useEffect(() => {
|
|
||||||
if (!open) {
|
|
||||||
form.reset();
|
|
||||||
setStep('domain');
|
|
||||||
}
|
|
||||||
}, [open, form]);
|
|
||||||
|
|
||||||
const onFormSubmit = async ({ domain }: TCreateOrganisationEmailDomainFormSchema) => {
|
const onFormSubmit = async ({ domain }: TCreateOrganisationEmailDomainFormSchema) => {
|
||||||
try {
|
try {
|
||||||
const { records } = await createOrganisationEmail({
|
const { records } = await createOrganisationEmail({
|
||||||
@ -118,12 +110,18 @@ export const OrganisationEmailDomainCreateDialog = ({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleOpenChange = (value: boolean) => {
|
||||||
|
if (!value) {
|
||||||
|
form.reset();
|
||||||
|
setStep('domain');
|
||||||
|
}
|
||||||
|
if (!form.formState.isSubmitting) {
|
||||||
|
setOpen(value);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog
|
<Dialog {...props} open={open} onOpenChange={handleOpenChange}>
|
||||||
{...props}
|
|
||||||
open={open}
|
|
||||||
onOpenChange={(value) => !form.formState.isSubmitting && setOpen(value)}
|
|
||||||
>
|
|
||||||
<DialogTrigger onClick={(e) => e.stopPropagation()} asChild={true}>
|
<DialogTrigger onClick={(e) => e.stopPropagation()} asChild={true}>
|
||||||
{trigger ?? (
|
{trigger ?? (
|
||||||
<Button className="flex-shrink-0" variant="secondary">
|
<Button className="flex-shrink-0" variant="secondary">
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { useEffect, useState } from 'react';
|
import { useState } from 'react';
|
||||||
|
|
||||||
import { zodResolver } from '@hookform/resolvers/zod';
|
import { zodResolver } from '@hookform/resolvers/zod';
|
||||||
import { useLingui } from '@lingui/react/macro';
|
import { useLingui } from '@lingui/react/macro';
|
||||||
@ -87,23 +87,20 @@ export const OrganisationEmailUpdateDialog = ({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
const handleOpenChange = (value: boolean) => {
|
||||||
if (!open) {
|
if (value) {
|
||||||
return;
|
form.reset({
|
||||||
|
emailName: organisationEmail.emailName,
|
||||||
|
// replyTo: organisationEmail.replyTo ?? undefined,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
if (!form.formState.isSubmitting) {
|
||||||
form.reset({
|
setOpen(value);
|
||||||
emailName: organisationEmail.emailName,
|
}
|
||||||
// replyTo: organisationEmail.replyTo ?? undefined,
|
};
|
||||||
});
|
|
||||||
}, [open, form]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog
|
<Dialog {...props} open={open} onOpenChange={handleOpenChange}>
|
||||||
{...props}
|
|
||||||
open={open}
|
|
||||||
onOpenChange={(value) => !form.formState.isSubmitting && setOpen(value)}
|
|
||||||
>
|
|
||||||
<DialogTrigger onClick={(e) => e.stopPropagation()} asChild>
|
<DialogTrigger onClick={(e) => e.stopPropagation()} asChild>
|
||||||
{trigger}
|
{trigger}
|
||||||
</DialogTrigger>
|
</DialogTrigger>
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { useEffect, useState } from 'react';
|
import { useState } from 'react';
|
||||||
|
|
||||||
import { zodResolver } from '@hookform/resolvers/zod';
|
import { zodResolver } from '@hookform/resolvers/zod';
|
||||||
import { useLingui } from '@lingui/react/macro';
|
import { useLingui } from '@lingui/react/macro';
|
||||||
@ -117,16 +117,17 @@ export const OrganisationGroupCreateDialog = ({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
const handleOpenChange = (value: boolean) => {
|
||||||
form.reset();
|
if (!value) {
|
||||||
}, [open, form]);
|
form.reset();
|
||||||
|
}
|
||||||
|
if (!form.formState.isSubmitting) {
|
||||||
|
setOpen(value);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog
|
<Dialog {...props} open={open} onOpenChange={handleOpenChange}>
|
||||||
{...props}
|
|
||||||
open={open}
|
|
||||||
onOpenChange={(value) => !form.formState.isSubmitting && setOpen(value)}
|
|
||||||
>
|
|
||||||
<DialogTrigger onClick={(e) => e.stopPropagation()} asChild={true}>
|
<DialogTrigger onClick={(e) => e.stopPropagation()} asChild={true}>
|
||||||
{trigger ?? (
|
{trigger ?? (
|
||||||
<Button className="flex-shrink-0" variant="secondary">
|
<Button className="flex-shrink-0" variant="secondary">
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { useEffect, useMemo, useRef, useState } from 'react';
|
import { useMemo, useRef, useState } from 'react';
|
||||||
|
|
||||||
import { zodResolver } from '@hookform/resolvers/zod';
|
import { zodResolver } from '@hookform/resolvers/zod';
|
||||||
import { msg } from '@lingui/core/macro';
|
import { msg } from '@lingui/core/macro';
|
||||||
@ -193,13 +193,6 @@ export const OrganisationMemberInviteDialog = ({
|
|||||||
return 'form';
|
return 'form';
|
||||||
}, [fullOrganisation]);
|
}, [fullOrganisation]);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!open) {
|
|
||||||
form.reset();
|
|
||||||
setInvitationType('INDIVIDUAL');
|
|
||||||
}
|
|
||||||
}, [open, form]);
|
|
||||||
|
|
||||||
const onFileInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
const onFileInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
if (!e.target.files?.length) {
|
if (!e.target.files?.length) {
|
||||||
return;
|
return;
|
||||||
@ -267,12 +260,18 @@ export const OrganisationMemberInviteDialog = ({
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleOpenChange = (value: boolean) => {
|
||||||
|
if (!value) {
|
||||||
|
form.reset();
|
||||||
|
setInvitationType('INDIVIDUAL');
|
||||||
|
}
|
||||||
|
if (!form.formState.isSubmitting) {
|
||||||
|
setOpen(value);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog
|
<Dialog {...props} open={open} onOpenChange={handleOpenChange}>
|
||||||
{...props}
|
|
||||||
open={open}
|
|
||||||
onOpenChange={(value) => !form.formState.isSubmitting && setOpen(value)}
|
|
||||||
>
|
|
||||||
<DialogTrigger onClick={(e) => e.stopPropagation()} asChild>
|
<DialogTrigger onClick={(e) => e.stopPropagation()} asChild>
|
||||||
{trigger ?? (
|
{trigger ?? (
|
||||||
<Button variant="secondary">
|
<Button variant="secondary">
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { useEffect, useState } from 'react';
|
import { useState } from 'react';
|
||||||
|
|
||||||
import { zodResolver } from '@hookform/resolvers/zod';
|
import { zodResolver } from '@hookform/resolvers/zod';
|
||||||
import { msg } from '@lingui/core/macro';
|
import { msg } from '@lingui/core/macro';
|
||||||
@ -106,32 +106,27 @@ export const OrganisationMemberUpdateDialog = ({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
const handleOpenChange = (value: boolean) => {
|
||||||
if (!open) {
|
if (value) {
|
||||||
return;
|
form.reset();
|
||||||
|
if (
|
||||||
|
!isOrganisationRoleWithinUserHierarchy(currentUserOrganisationRole, organisationMemberRole)
|
||||||
|
) {
|
||||||
|
setOpen(false);
|
||||||
|
toast({
|
||||||
|
title: _(msg`You cannot modify a organisation member who has a higher role than you.`),
|
||||||
|
variant: 'destructive',
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
if (!form.formState.isSubmitting) {
|
||||||
form.reset();
|
setOpen(value);
|
||||||
|
|
||||||
if (
|
|
||||||
!isOrganisationRoleWithinUserHierarchy(currentUserOrganisationRole, organisationMemberRole)
|
|
||||||
) {
|
|
||||||
setOpen(false);
|
|
||||||
|
|
||||||
toast({
|
|
||||||
title: _(msg`You cannot modify a organisation member who has a higher role than you.`),
|
|
||||||
variant: 'destructive',
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
};
|
||||||
}, [open, currentUserOrganisationRole, organisationMemberRole, form, toast]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog
|
<Dialog {...props} open={open} onOpenChange={handleOpenChange}>
|
||||||
{...props}
|
|
||||||
open={open}
|
|
||||||
onOpenChange={(value) => !form.formState.isSubmitting && setOpen(value)}
|
|
||||||
>
|
|
||||||
<DialogTrigger onClick={(e) => e.stopPropagation()} asChild>
|
<DialogTrigger onClick={(e) => e.stopPropagation()} asChild>
|
||||||
{trigger ?? (
|
{trigger ?? (
|
||||||
<Button variant="secondary">
|
<Button variant="secondary">
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { useEffect, useState } from 'react';
|
import { useState } from 'react';
|
||||||
|
|
||||||
import { zodResolver } from '@hookform/resolvers/zod';
|
import { zodResolver } from '@hookform/resolvers/zod';
|
||||||
import { msg } from '@lingui/core/macro';
|
import { msg } from '@lingui/core/macro';
|
||||||
@ -120,24 +120,21 @@ export const PasskeyCreateDialog = ({ trigger, onSuccess, ...props }: PasskeyCre
|
|||||||
return passkeyName;
|
return passkeyName;
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
const handleDialogOpenChange = (value: boolean) => {
|
||||||
if (!open) {
|
if (!value) {
|
||||||
const defaultPasskeyName = extractDefaultPasskeyName();
|
const defaultPasskeyName = extractDefaultPasskeyName();
|
||||||
|
|
||||||
form.reset({
|
form.reset({
|
||||||
passkeyName: defaultPasskeyName,
|
passkeyName: defaultPasskeyName,
|
||||||
});
|
});
|
||||||
|
|
||||||
setFormError(null);
|
setFormError(null);
|
||||||
}
|
}
|
||||||
}, [open, form]);
|
if (!form.formState.isSubmitting) {
|
||||||
|
setOpen(value);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog
|
<Dialog {...props} open={open} onOpenChange={handleDialogOpenChange}>
|
||||||
{...props}
|
|
||||||
open={open}
|
|
||||||
onOpenChange={(value) => !form.formState.isSubmitting && setOpen(value)}
|
|
||||||
>
|
|
||||||
<DialogTrigger onClick={(e) => e.stopPropagation()} asChild={true}>
|
<DialogTrigger onClick={(e) => e.stopPropagation()} asChild={true}>
|
||||||
{trigger ?? (
|
{trigger ?? (
|
||||||
<Button variant="secondary" loading={isPending}>
|
<Button variant="secondary" loading={isPending}>
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { useEffect, useMemo, useState } from 'react';
|
import { useMemo, useState } from 'react';
|
||||||
|
|
||||||
import { zodResolver } from '@hookform/resolvers/zod';
|
import { zodResolver } from '@hookform/resolvers/zod';
|
||||||
import { msg } from '@lingui/core/macro';
|
import { msg } from '@lingui/core/macro';
|
||||||
@ -66,14 +66,14 @@ export const TeamCreateDialog = ({ trigger, onCreated, ...props }: TeamCreateDia
|
|||||||
const updateSearchParams = useUpdateSearchParams();
|
const updateSearchParams = useUpdateSearchParams();
|
||||||
const organisation = useCurrentOrganisation();
|
const organisation = useCurrentOrganisation();
|
||||||
|
|
||||||
const [open, setOpen] = useState(false);
|
const actionSearchParam = searchParams?.get('action');
|
||||||
|
const shouldOpenDialog = actionSearchParam === 'add-team';
|
||||||
|
const [open, setOpen] = useState(shouldOpenDialog);
|
||||||
|
|
||||||
const { data: fullOrganisation } = trpc.organisation.get.useQuery({
|
const { data: fullOrganisation } = trpc.organisation.get.useQuery({
|
||||||
organisationReference: organisation.id,
|
organisationReference: organisation.id,
|
||||||
});
|
});
|
||||||
|
|
||||||
const actionSearchParam = searchParams?.get('action');
|
|
||||||
|
|
||||||
const form = useForm({
|
const form = useForm({
|
||||||
resolver: zodResolver(ZCreateTeamFormSchema),
|
resolver: zodResolver(ZCreateTeamFormSchema),
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
@ -85,6 +85,18 @@ export const TeamCreateDialog = ({ trigger, onCreated, ...props }: TeamCreateDia
|
|||||||
|
|
||||||
const { mutateAsync: createTeam } = trpc.team.create.useMutation();
|
const { mutateAsync: createTeam } = trpc.team.create.useMutation();
|
||||||
|
|
||||||
|
const handleOpenChange = (value: boolean) => {
|
||||||
|
if (!value) {
|
||||||
|
form.reset();
|
||||||
|
if (shouldOpenDialog) {
|
||||||
|
updateSearchParams({ action: null });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!form.formState.isSubmitting) {
|
||||||
|
setOpen(value);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const onFormSubmit = async ({ teamName, teamUrl, inheritMembers }: TCreateTeamFormSchema) => {
|
const onFormSubmit = async ({ teamName, teamUrl, inheritMembers }: TCreateTeamFormSchema) => {
|
||||||
try {
|
try {
|
||||||
await createTeam({
|
await createTeam({
|
||||||
@ -150,23 +162,8 @@ export const TeamCreateDialog = ({ trigger, onCreated, ...props }: TeamCreateDia
|
|||||||
return 'form';
|
return 'form';
|
||||||
}, [fullOrganisation]);
|
}, [fullOrganisation]);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (actionSearchParam === 'add-team') {
|
|
||||||
setOpen(true);
|
|
||||||
updateSearchParams({ action: null });
|
|
||||||
}
|
|
||||||
}, [actionSearchParam, open]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
form.reset();
|
|
||||||
}, [open, form]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog
|
<Dialog {...props} open={open} onOpenChange={handleOpenChange}>
|
||||||
{...props}
|
|
||||||
open={open}
|
|
||||||
onOpenChange={(value) => !form.formState.isSubmitting && setOpen(value)}
|
|
||||||
>
|
|
||||||
<DialogTrigger onClick={(e) => e.stopPropagation()} asChild={true}>
|
<DialogTrigger onClick={(e) => e.stopPropagation()} asChild={true}>
|
||||||
{trigger ?? (
|
{trigger ?? (
|
||||||
<Button className="flex-shrink-0" variant="secondary">
|
<Button className="flex-shrink-0" variant="secondary">
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { useEffect, useState } from 'react';
|
import { useState } from 'react';
|
||||||
|
|
||||||
import { zodResolver } from '@hookform/resolvers/zod';
|
import { zodResolver } from '@hookform/resolvers/zod';
|
||||||
import { msg } from '@lingui/core/macro';
|
import { msg } from '@lingui/core/macro';
|
||||||
@ -114,14 +114,17 @@ export const TeamDeleteDialog = ({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
const handleOpenChange = (value: boolean) => {
|
||||||
if (!open) {
|
if (!value) {
|
||||||
form.reset();
|
form.reset();
|
||||||
}
|
}
|
||||||
}, [open, form]);
|
if (!form.formState.isSubmitting) {
|
||||||
|
setOpen(value);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog open={open} onOpenChange={(value) => !form.formState.isSubmitting && setOpen(value)}>
|
<Dialog open={open} onOpenChange={handleOpenChange}>
|
||||||
<DialogTrigger asChild>
|
<DialogTrigger asChild>
|
||||||
{trigger ?? (
|
{trigger ?? (
|
||||||
<Button variant="destructive">
|
<Button variant="destructive">
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { useEffect, useState } from 'react';
|
import { useState } from 'react';
|
||||||
|
|
||||||
import { zodResolver } from '@hookform/resolvers/zod';
|
import { zodResolver } from '@hookform/resolvers/zod';
|
||||||
import { msg } from '@lingui/core/macro';
|
import { msg } from '@lingui/core/macro';
|
||||||
@ -103,18 +103,17 @@ export const TeamEmailAddDialog = ({ teamId, trigger, ...props }: TeamEmailAddDi
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
const handleOpenChange = (value: boolean) => {
|
||||||
if (!open) {
|
if (!value) {
|
||||||
form.reset();
|
form.reset();
|
||||||
}
|
}
|
||||||
}, [open, form]);
|
if (!form.formState.isSubmitting) {
|
||||||
|
setOpen(value);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog
|
<Dialog {...props} open={open} onOpenChange={handleOpenChange}>
|
||||||
{...props}
|
|
||||||
open={open}
|
|
||||||
onOpenChange={(value) => !form.formState.isSubmitting && setOpen(value)}
|
|
||||||
>
|
|
||||||
<DialogTrigger onClick={(e) => e.stopPropagation()} asChild={true}>
|
<DialogTrigger onClick={(e) => e.stopPropagation()} asChild={true}>
|
||||||
{trigger ?? (
|
{trigger ?? (
|
||||||
<Button variant="outline" loading={isPending} className="bg-background">
|
<Button variant="outline" loading={isPending} className="bg-background">
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { useEffect, useState } from 'react';
|
import { useState } from 'react';
|
||||||
|
|
||||||
import { zodResolver } from '@hookform/resolvers/zod';
|
import { zodResolver } from '@hookform/resolvers/zod';
|
||||||
import { msg } from '@lingui/core/macro';
|
import { msg } from '@lingui/core/macro';
|
||||||
@ -92,18 +92,17 @@ export const TeamEmailUpdateDialog = ({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
const handleOpenChange = (value: boolean) => {
|
||||||
if (!open) {
|
if (!value) {
|
||||||
form.reset();
|
form.reset();
|
||||||
}
|
}
|
||||||
}, [open, form]);
|
if (!form.formState.isSubmitting) {
|
||||||
|
setOpen(value);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog
|
<Dialog {...props} open={open} onOpenChange={handleOpenChange}>
|
||||||
{...props}
|
|
||||||
open={open}
|
|
||||||
onOpenChange={(value) => !form.formState.isSubmitting && setOpen(value)}
|
|
||||||
>
|
|
||||||
<DialogTrigger onClick={(e) => e.stopPropagation()} asChild>
|
<DialogTrigger onClick={(e) => e.stopPropagation()} asChild>
|
||||||
{trigger ?? (
|
{trigger ?? (
|
||||||
<Button variant="outline" className="bg-background">
|
<Button variant="outline" className="bg-background">
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { useEffect, useMemo, useState } from 'react';
|
import { useMemo, useState } from 'react';
|
||||||
|
|
||||||
import { zodResolver } from '@hookform/resolvers/zod';
|
import { zodResolver } from '@hookform/resolvers/zod';
|
||||||
import { Trans, useLingui } from '@lingui/react/macro';
|
import { Trans, useLingui } from '@lingui/react/macro';
|
||||||
@ -107,7 +107,7 @@ export const TeamGroupCreateDialog = ({ ...props }: TeamGroupCreateDialogProps)
|
|||||||
duration: 5000,
|
duration: 5000,
|
||||||
});
|
});
|
||||||
|
|
||||||
setOpen(false);
|
handleClose();
|
||||||
} catch {
|
} catch {
|
||||||
toast({
|
toast({
|
||||||
title: t`An unknown error occurred`,
|
title: t`An unknown error occurred`,
|
||||||
@ -117,17 +117,23 @@ export const TeamGroupCreateDialog = ({ ...props }: TeamGroupCreateDialogProps)
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
const handleClose = () => {
|
||||||
if (!open) {
|
setOpen(false);
|
||||||
form.reset();
|
form.reset();
|
||||||
setStep('SELECT');
|
setStep('SELECT');
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleOpenChange = (isOpen: boolean) => {
|
||||||
|
if (!isOpen) {
|
||||||
|
handleClose();
|
||||||
}
|
}
|
||||||
}, [open, form]);
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog
|
<Dialog
|
||||||
{...props}
|
{...props}
|
||||||
open={open}
|
open={open}
|
||||||
|
onOpenChange={handleOpenChange}
|
||||||
// Disable automatic onOpenChange events to prevent dialog from closing if auser 'accidentally' clicks the overlay.
|
// Disable automatic onOpenChange events to prevent dialog from closing if auser 'accidentally' clicks the overlay.
|
||||||
// Since it would be annoying to redo the whole process.
|
// Since it would be annoying to redo the whole process.
|
||||||
>
|
>
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { useEffect, useState } from 'react';
|
import { useState } from 'react';
|
||||||
|
|
||||||
import { zodResolver } from '@hookform/resolvers/zod';
|
import { zodResolver } from '@hookform/resolvers/zod';
|
||||||
import { msg } from '@lingui/core/macro';
|
import { msg } from '@lingui/core/macro';
|
||||||
@ -106,22 +106,17 @@ export const TeamGroupUpdateDialog = ({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
const handleOpenChange = (value: boolean) => {
|
||||||
if (!open) {
|
if (!form.formState.isSubmitting) {
|
||||||
return;
|
if (value) {
|
||||||
|
form.reset();
|
||||||
|
}
|
||||||
|
setOpen(value);
|
||||||
}
|
}
|
||||||
|
};
|
||||||
form.reset();
|
|
||||||
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
}, [open, team.currentTeamRole, teamGroupRole, form, toast]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog
|
<Dialog {...props} open={open} onOpenChange={handleOpenChange}>
|
||||||
{...props}
|
|
||||||
open={open}
|
|
||||||
onOpenChange={(value) => !form.formState.isSubmitting && setOpen(value)}
|
|
||||||
>
|
|
||||||
<DialogTrigger onClick={(e) => e.stopPropagation()} asChild>
|
<DialogTrigger onClick={(e) => e.stopPropagation()} asChild>
|
||||||
{trigger ?? (
|
{trigger ?? (
|
||||||
<Button variant="secondary">
|
<Button variant="secondary">
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { useEffect, useMemo, useState } from 'react';
|
import { useMemo, useState } from 'react';
|
||||||
|
|
||||||
import { zodResolver } from '@hookform/resolvers/zod';
|
import { zodResolver } from '@hookform/resolvers/zod';
|
||||||
import { Trans, useLingui } from '@lingui/react/macro';
|
import { Trans, useLingui } from '@lingui/react/macro';
|
||||||
@ -119,20 +119,17 @@ export const TeamMemberCreateDialog = ({ trigger, ...props }: TeamMemberCreateDi
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
const handleOpenChange = (value: boolean) => {
|
||||||
if (!open) {
|
if (!value) {
|
||||||
form.reset();
|
form.reset();
|
||||||
setStep('SELECT');
|
setStep('SELECT');
|
||||||
}
|
}
|
||||||
}, [open, form]);
|
// Disable automatic onOpenChange events to prevent dialog from closing if user 'accidentally' clicks the overlay.
|
||||||
|
// Since it would be annoying to redo the whole process, we handle open state manually
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog
|
<Dialog {...props} open={open} onOpenChange={handleOpenChange}>
|
||||||
{...props}
|
|
||||||
open={open}
|
|
||||||
// Disable automatic onOpenChange events to prevent dialog from closing if auser 'accidentally' clicks the overlay.
|
|
||||||
// Since it would be annoying to redo the whole process.
|
|
||||||
>
|
|
||||||
<DialogTrigger onClick={(e) => e.stopPropagation()} asChild>
|
<DialogTrigger onClick={(e) => e.stopPropagation()} asChild>
|
||||||
<Button variant="secondary" onClick={() => setOpen(true)}>
|
<Button variant="secondary" onClick={() => setOpen(true)}>
|
||||||
<Trans>Add members</Trans>
|
<Trans>Add members</Trans>
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { useEffect, useState } from 'react';
|
import { useState } from 'react';
|
||||||
|
|
||||||
import { zodResolver } from '@hookform/resolvers/zod';
|
import { zodResolver } from '@hookform/resolvers/zod';
|
||||||
import { msg } from '@lingui/core/macro';
|
import { msg } from '@lingui/core/macro';
|
||||||
@ -106,30 +106,25 @@ export const TeamMemberUpdateDialog = ({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
const handleOpenChange = (value: boolean) => {
|
||||||
if (!open) {
|
if (value) {
|
||||||
return;
|
form.reset();
|
||||||
|
if (!isTeamRoleWithinUserHierarchy(currentUserTeamRole, memberTeamRole)) {
|
||||||
|
setOpen(false);
|
||||||
|
toast({
|
||||||
|
title: _(msg`You cannot modify a team member who has a higher role than you.`),
|
||||||
|
variant: 'destructive',
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
if (!form.formState.isSubmitting) {
|
||||||
form.reset();
|
setOpen(value);
|
||||||
|
|
||||||
if (!isTeamRoleWithinUserHierarchy(currentUserTeamRole, memberTeamRole)) {
|
|
||||||
setOpen(false);
|
|
||||||
|
|
||||||
toast({
|
|
||||||
title: _(msg`You cannot modify a team member who has a higher role than you.`),
|
|
||||||
variant: 'destructive',
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
};
|
||||||
}, [open, currentUserTeamRole, memberTeamRole, form, toast]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog
|
<Dialog {...props} open={open} onOpenChange={handleOpenChange}>
|
||||||
{...props}
|
|
||||||
open={open}
|
|
||||||
onOpenChange={(value) => !form.formState.isSubmitting && setOpen(value)}
|
|
||||||
>
|
|
||||||
<DialogTrigger onClick={(e) => e.stopPropagation()} asChild>
|
<DialogTrigger onClick={(e) => e.stopPropagation()} asChild>
|
||||||
{trigger ?? (
|
{trigger ?? (
|
||||||
<Button variant="secondary">
|
<Button variant="secondary">
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { useEffect, useMemo, useState } from 'react';
|
import { 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';
|
||||||
@ -186,16 +186,20 @@ export const TemplateDirectLinkDialog = ({
|
|||||||
const isLoading =
|
const isLoading =
|
||||||
isCreatingTemplateDirectLink || isTogglingTemplateAccess || isDeletingTemplateDirectLink;
|
isCreatingTemplateDirectLink || isTogglingTemplateAccess || isDeletingTemplateDirectLink;
|
||||||
|
|
||||||
useEffect(() => {
|
const handleOpenChange = (value: boolean) => {
|
||||||
resetCreateTemplateDirectLink();
|
if (!isLoading) {
|
||||||
setCurrentStep(token ? 'MANAGE' : 'ONBOARD');
|
if (value) {
|
||||||
setSelectedRecipientId(null);
|
resetCreateTemplateDirectLink();
|
||||||
|
setCurrentStep(token ? 'MANAGE' : 'ONBOARD');
|
||||||
|
setSelectedRecipientId(null);
|
||||||
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
onOpenChange(value);
|
||||||
}, [open]);
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog open={open} onOpenChange={(value) => !isLoading && onOpenChange(value)}>
|
<Dialog open={open} onOpenChange={handleOpenChange}>
|
||||||
<fieldset disabled={isLoading} className="relative">
|
<fieldset disabled={isLoading} className="relative">
|
||||||
<AnimateGenericFadeInOut motionKey={currentStep}>
|
<AnimateGenericFadeInOut motionKey={currentStep}>
|
||||||
{match({ token, currentStep })
|
{match({ token, currentStep })
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { useEffect, useState } from 'react';
|
import { useState } from 'react';
|
||||||
|
|
||||||
import { zodResolver } from '@hookform/resolvers/zod';
|
import { zodResolver } from '@hookform/resolvers/zod';
|
||||||
import { msg } from '@lingui/core/macro';
|
import { msg } from '@lingui/core/macro';
|
||||||
@ -85,15 +85,6 @@ export function TemplateMoveToFolderDialog({
|
|||||||
|
|
||||||
const { mutateAsync: moveTemplateToFolder } = trpc.folder.moveTemplateToFolder.useMutation();
|
const { mutateAsync: moveTemplateToFolder } = trpc.folder.moveTemplateToFolder.useMutation();
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!isOpen) {
|
|
||||||
form.reset();
|
|
||||||
setSearchTerm('');
|
|
||||||
} else {
|
|
||||||
form.reset({ folderId: currentFolderId ?? null });
|
|
||||||
}
|
|
||||||
}, [isOpen, currentFolderId, form]);
|
|
||||||
|
|
||||||
const onSubmit = async (data: TMoveTemplateFormSchema) => {
|
const onSubmit = async (data: TMoveTemplateFormSchema) => {
|
||||||
try {
|
try {
|
||||||
await moveTemplateToFolder({
|
await moveTemplateToFolder({
|
||||||
@ -137,12 +128,22 @@ export function TemplateMoveToFolderDialog({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleOpenChange = (value: boolean) => {
|
||||||
|
if (!value) {
|
||||||
|
form.reset();
|
||||||
|
setSearchTerm('');
|
||||||
|
} else {
|
||||||
|
form.reset({ folderId: currentFolderId ?? null });
|
||||||
|
}
|
||||||
|
onOpenChange(value);
|
||||||
|
};
|
||||||
|
|
||||||
const filteredFolders = folders?.data?.filter((folder) =>
|
const filteredFolders = folders?.data?.filter((folder) =>
|
||||||
folder.name.toLowerCase().includes(searchTerm.toLowerCase()),
|
folder.name.toLowerCase().includes(searchTerm.toLowerCase()),
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog {...props} open={isOpen} onOpenChange={onOpenChange}>
|
<Dialog {...props} open={isOpen} onOpenChange={handleOpenChange}>
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>
|
<DialogTitle>
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { useEffect, useState } from 'react';
|
import { useState } from 'react';
|
||||||
|
|
||||||
import { zodResolver } from '@hookform/resolvers/zod';
|
import { zodResolver } from '@hookform/resolvers/zod';
|
||||||
import { msg } from '@lingui/core/macro';
|
import { msg } from '@lingui/core/macro';
|
||||||
@ -203,14 +203,17 @@ export function TemplateUseDialog({
|
|||||||
name: 'recipients',
|
name: 'recipients',
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
const handleOpenChange = (value: boolean) => {
|
||||||
if (!open) {
|
if (form.formState.isSubmitting) return;
|
||||||
|
|
||||||
|
if (!value) {
|
||||||
form.reset();
|
form.reset();
|
||||||
}
|
}
|
||||||
}, [open, form]);
|
setOpen(value);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog open={open} onOpenChange={(value) => !form.formState.isSubmitting && setOpen(value)}>
|
<Dialog open={open} onOpenChange={handleOpenChange}>
|
||||||
<DialogTrigger asChild>
|
<DialogTrigger asChild>
|
||||||
{trigger || (
|
{trigger || (
|
||||||
<Button variant="outline" className="bg-background">
|
<Button variant="outline" className="bg-background">
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { useEffect, useState } from 'react';
|
import { useState } from 'react';
|
||||||
|
|
||||||
import { zodResolver } from '@hookform/resolvers/zod';
|
import { zodResolver } from '@hookform/resolvers/zod';
|
||||||
import { msg } from '@lingui/core/macro';
|
import { msg } from '@lingui/core/macro';
|
||||||
@ -95,17 +95,17 @@ export default function TokenDeleteDialog({ token, onDelete, children }: TokenDe
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
const handleOpenChange = (value: boolean) => {
|
||||||
if (!isOpen) {
|
if (!value) {
|
||||||
form.reset();
|
form.reset();
|
||||||
}
|
}
|
||||||
}, [isOpen, form]);
|
if (!form.formState.isSubmitting) {
|
||||||
|
setIsOpen(value);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog
|
<Dialog open={isOpen} onOpenChange={handleOpenChange}>
|
||||||
open={isOpen}
|
|
||||||
onOpenChange={(value) => !form.formState.isSubmitting && setIsOpen(value)}
|
|
||||||
>
|
|
||||||
<DialogTrigger asChild={true}>
|
<DialogTrigger asChild={true}>
|
||||||
{children ?? (
|
{children ?? (
|
||||||
<Button className="mr-4" variant="destructive">
|
<Button className="mr-4" variant="destructive">
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { useEffect, useState } from 'react';
|
import { useState } from 'react';
|
||||||
|
|
||||||
import { zodResolver } from '@hookform/resolvers/zod';
|
import { zodResolver } from '@hookform/resolvers/zod';
|
||||||
import { msg } from '@lingui/core/macro';
|
import { msg } from '@lingui/core/macro';
|
||||||
@ -88,14 +88,17 @@ export const WebhookDeleteDialog = ({ webhook, children }: WebhookDeleteDialogPr
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
const handleOpenChange = (value: boolean) => {
|
||||||
if (!open) {
|
if (!value) {
|
||||||
form.reset();
|
form.reset();
|
||||||
}
|
}
|
||||||
}, [open, form]);
|
if (!form.formState.isSubmitting) {
|
||||||
|
setOpen(value);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog open={open} onOpenChange={(value) => !form.formState.isSubmitting && setOpen(value)}>
|
<Dialog open={open} onOpenChange={handleOpenChange}>
|
||||||
<DialogTrigger asChild>
|
<DialogTrigger asChild>
|
||||||
{children ?? (
|
{children ?? (
|
||||||
<Button className="mr-4" variant="destructive">
|
<Button className="mr-4" variant="destructive">
|
||||||
|
|||||||
@ -1,12 +1,10 @@
|
|||||||
import { useEffect, useState } from 'react';
|
import { useEffect } from 'react';
|
||||||
|
|
||||||
import { Trans } from '@lingui/react/macro';
|
import { Trans } from '@lingui/react/macro';
|
||||||
|
|
||||||
export const EmbedDocumentWaitingForTurn = () => {
|
export const EmbedDocumentWaitingForTurn = () => {
|
||||||
const [hasPostedMessage, setHasPostedMessage] = useState(false);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (window.parent && !hasPostedMessage) {
|
if (window.parent) {
|
||||||
window.parent.postMessage(
|
window.parent.postMessage(
|
||||||
{
|
{
|
||||||
action: 'document-waiting-for-turn',
|
action: 'document-waiting-for-turn',
|
||||||
@ -15,13 +13,7 @@ export const EmbedDocumentWaitingForTurn = () => {
|
|||||||
'*',
|
'*',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
}, []);
|
||||||
setHasPostedMessage(true);
|
|
||||||
}, [hasPostedMessage]);
|
|
||||||
|
|
||||||
if (!hasPostedMessage) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="embed--WaitingForTurn relative mx-auto flex min-h-[100dvh] max-w-screen-lg flex-col items-center justify-center p-6">
|
<div className="embed--WaitingForTurn relative mx-auto flex min-h-[100dvh] max-w-screen-lg flex-col items-center justify-center p-6">
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { useEffect, useState } from 'react';
|
import { useState } from 'react';
|
||||||
|
|
||||||
import { zodResolver } from '@hookform/resolvers/zod';
|
import { zodResolver } from '@hookform/resolvers/zod';
|
||||||
import { msg } from '@lingui/core/macro';
|
import { msg } from '@lingui/core/macro';
|
||||||
@ -136,18 +136,19 @@ export const EnableAuthenticatorAppDialog = ({ onSuccess }: EnableAuthenticatorA
|
|||||||
setIsOpen(true);
|
setIsOpen(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
const handleOpenChange = (open: boolean) => {
|
||||||
enable2FAForm.reset();
|
setIsOpen(open);
|
||||||
|
|
||||||
if (!isOpen && recoveryCodes && recoveryCodes.length > 0) {
|
if (!open) {
|
||||||
setRecoveryCodes(null);
|
enable2FAForm.reset();
|
||||||
|
if (recoveryCodes && recoveryCodes.length > 0) {
|
||||||
|
setRecoveryCodes(null);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
};
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
}, [isOpen]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog open={isOpen} onOpenChange={setIsOpen}>
|
<Dialog open={isOpen} onOpenChange={handleOpenChange}>
|
||||||
<DialogTrigger asChild={true}>
|
<DialogTrigger asChild={true}>
|
||||||
<Button
|
<Button
|
||||||
className="flex-shrink-0"
|
className="flex-shrink-0"
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { useEffect, useMemo, useState } from 'react';
|
import { useMemo, useState } from 'react';
|
||||||
|
|
||||||
import { zodResolver } from '@hookform/resolvers/zod';
|
import { zodResolver } from '@hookform/resolvers/zod';
|
||||||
import type { MessageDescriptor } from '@lingui/core';
|
import type { MessageDescriptor } from '@lingui/core';
|
||||||
@ -116,9 +116,18 @@ export const SignInForm = ({
|
|||||||
const { mutateAsync: createPasskeySigninOptions } =
|
const { mutateAsync: createPasskeySigninOptions } =
|
||||||
trpc.auth.createPasskeySigninOptions.useMutation();
|
trpc.auth.createPasskeySigninOptions.useMutation();
|
||||||
|
|
||||||
|
const emailFromHash = useMemo(() => {
|
||||||
|
if (typeof window === 'undefined') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const hash = window.location.hash.slice(1);
|
||||||
|
const params = new URLSearchParams(hash);
|
||||||
|
return params.get('email');
|
||||||
|
}, []);
|
||||||
|
|
||||||
const form = useForm<TSignInFormSchema>({
|
const form = useForm<TSignInFormSchema>({
|
||||||
values: {
|
values: {
|
||||||
email: initialEmail ?? '',
|
email: emailFromHash ?? initialEmail ?? '',
|
||||||
password: '',
|
password: '',
|
||||||
totpCode: '',
|
totpCode: '',
|
||||||
backupCode: '',
|
backupCode: '',
|
||||||
@ -287,18 +296,6 @@ export const SignInForm = ({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const hash = window.location.hash.slice(1);
|
|
||||||
|
|
||||||
const params = new URLSearchParams(hash);
|
|
||||||
|
|
||||||
const email = params.get('email');
|
|
||||||
|
|
||||||
if (email) {
|
|
||||||
form.setValue('email', email);
|
|
||||||
}
|
|
||||||
}, [form]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Form {...form}>
|
<Form {...form}>
|
||||||
<form
|
<form
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { useEffect } from 'react';
|
import { useMemo } from 'react';
|
||||||
|
|
||||||
import { zodResolver } from '@hookform/resolvers/zod';
|
import { zodResolver } from '@hookform/resolvers/zod';
|
||||||
import type { MessageDescriptor } from '@lingui/core';
|
import type { MessageDescriptor } from '@lingui/core';
|
||||||
@ -84,10 +84,19 @@ export const SignUpForm = ({
|
|||||||
|
|
||||||
const utmSrc = searchParams.get('utm_source') ?? null;
|
const utmSrc = searchParams.get('utm_source') ?? null;
|
||||||
|
|
||||||
|
const emailFromHash = useMemo(() => {
|
||||||
|
if (typeof window === 'undefined') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const hash = window.location.hash.slice(1);
|
||||||
|
const params = new URLSearchParams(hash);
|
||||||
|
return params.get('email');
|
||||||
|
}, []);
|
||||||
|
|
||||||
const form = useForm<TSignUpFormSchema>({
|
const form = useForm<TSignUpFormSchema>({
|
||||||
values: {
|
values: {
|
||||||
name: '',
|
name: '',
|
||||||
email: initialEmail ?? '',
|
email: emailFromHash ?? initialEmail ?? '',
|
||||||
password: '',
|
password: '',
|
||||||
signature: '',
|
signature: '',
|
||||||
},
|
},
|
||||||
@ -162,18 +171,6 @@ export const SignUpForm = ({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const hash = window.location.hash.slice(1);
|
|
||||||
|
|
||||||
const params = new URLSearchParams(hash);
|
|
||||||
|
|
||||||
const email = params.get('email');
|
|
||||||
|
|
||||||
if (email) {
|
|
||||||
form.setValue('email', email);
|
|
||||||
}
|
|
||||||
}, [form]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={cn('flex justify-center gap-x-12', className)}>
|
<div className={cn('flex justify-center gap-x-12', className)}>
|
||||||
<div className="border-border relative hidden flex-1 overflow-hidden rounded-xl border xl:flex">
|
<div className="border-border relative hidden flex-1 overflow-hidden rounded-xl border xl:flex">
|
||||||
|
|||||||
@ -9,7 +9,6 @@ import { useForm } from 'react-hook-form';
|
|||||||
import { useRevalidator } from 'react-router';
|
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 { AUTO_SIGNABLE_FIELD_TYPES } from '@documenso/lib/constants/autosign';
|
import { AUTO_SIGNABLE_FIELD_TYPES } from '@documenso/lib/constants/autosign';
|
||||||
import { DocumentAuth } from '@documenso/lib/types/document-auth';
|
import { DocumentAuth } from '@documenso/lib/types/document-auth';
|
||||||
import { extractInitials } from '@documenso/lib/utils/recipient-formatter';
|
import { extractInitials } from '@documenso/lib/utils/recipient-formatter';
|
||||||
@ -61,12 +60,6 @@ export const DocumentSigningAutoSign = ({ recipient, fields }: DocumentSigningAu
|
|||||||
const { email, fullName } = useRequiredDocumentSigningContext();
|
const { email, fullName } = useRequiredDocumentSigningContext();
|
||||||
const { derivedRecipientActionAuth } = useRequiredDocumentSigningAuthContext();
|
const { derivedRecipientActionAuth } = useRequiredDocumentSigningAuthContext();
|
||||||
|
|
||||||
const [open, setOpen] = useState(false);
|
|
||||||
|
|
||||||
const form = useForm();
|
|
||||||
|
|
||||||
const { mutateAsync: signFieldWithToken } = trpc.field.signFieldWithToken.useMutation();
|
|
||||||
|
|
||||||
const autoSignableFields = fields.filter((field) => {
|
const autoSignableFields = fields.filter((field) => {
|
||||||
if (field.inserted) {
|
if (field.inserted) {
|
||||||
return false;
|
return false;
|
||||||
@ -95,6 +88,14 @@ export const DocumentSigningAutoSign = ({ recipient, fields }: DocumentSigningAu
|
|||||||
(actionAuth) => !NON_AUTO_SIGNABLE_ACTION_AUTH_TYPES.includes(actionAuth),
|
(actionAuth) => !NON_AUTO_SIGNABLE_ACTION_AUTH_TYPES.includes(actionAuth),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const [open, setOpen] = useState(() => {
|
||||||
|
return actionAuthAllowsAutoSign && autoSignableFields.length > AUTO_SIGN_THRESHOLD;
|
||||||
|
});
|
||||||
|
|
||||||
|
const form = useForm();
|
||||||
|
|
||||||
|
const { mutateAsync: signFieldWithToken } = trpc.field.signFieldWithToken.useMutation();
|
||||||
|
|
||||||
const onSubmit = async () => {
|
const onSubmit = async () => {
|
||||||
const results = await Promise.allSettled(
|
const results = await Promise.allSettled(
|
||||||
autoSignableFields.map(async (field) => {
|
autoSignableFields.map(async (field) => {
|
||||||
@ -152,12 +153,6 @@ export const DocumentSigningAutoSign = ({ recipient, fields }: DocumentSigningAu
|
|||||||
await revalidate();
|
await revalidate();
|
||||||
};
|
};
|
||||||
|
|
||||||
unsafe_useEffectOnce(() => {
|
|
||||||
if (actionAuthAllowsAutoSign && autoSignableFields.length > AUTO_SIGN_THRESHOLD) {
|
|
||||||
setOpen(true);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog open={open} onOpenChange={setOpen}>
|
<Dialog open={open} onOpenChange={setOpen}>
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { useEffect, useState } from 'react';
|
import { 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';
|
||||||
@ -80,20 +80,16 @@ export const DocumentSigningTextField = ({
|
|||||||
const parsedFieldMeta = safeFieldMeta.success ? safeFieldMeta.data : null;
|
const parsedFieldMeta = safeFieldMeta.success ? safeFieldMeta.data : null;
|
||||||
|
|
||||||
const isLoading = isSignFieldWithTokenLoading || isRemoveSignedFieldWithTokenLoading;
|
const isLoading = isSignFieldWithTokenLoading || isRemoveSignedFieldWithTokenLoading;
|
||||||
const shouldAutoSignField =
|
const shouldAutoSignField = useMemo(
|
||||||
(!field.inserted && parsedFieldMeta?.text) ||
|
() =>
|
||||||
(!field.inserted && parsedFieldMeta?.text && parsedFieldMeta?.readOnly);
|
(!field.inserted && parsedFieldMeta?.text) ||
|
||||||
|
(!field.inserted && parsedFieldMeta?.text && parsedFieldMeta?.readOnly),
|
||||||
|
[field.inserted, parsedFieldMeta?.text, parsedFieldMeta?.readOnly],
|
||||||
|
);
|
||||||
|
|
||||||
const [showCustomTextModal, setShowCustomTextModal] = useState(false);
|
const [showCustomTextModal, setShowCustomTextModal] = useState(false);
|
||||||
const [localText, setLocalCustomText] = useState(parsedFieldMeta?.text ?? '');
|
const [localText, setLocalCustomText] = useState(parsedFieldMeta?.text ?? '');
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!showCustomTextModal) {
|
|
||||||
setLocalCustomText(parsedFieldMeta?.text ?? '');
|
|
||||||
setErrors(initialErrors);
|
|
||||||
}
|
|
||||||
}, [showCustomTextModal]);
|
|
||||||
|
|
||||||
const handleTextChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
|
const handleTextChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
|
||||||
const text = e.target.value;
|
const text = e.target.value;
|
||||||
setLocalCustomText(text);
|
setLocalCustomText(text);
|
||||||
@ -216,14 +212,12 @@ export const DocumentSigningTextField = ({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
if (shouldAutoSignField) {
|
||||||
if (shouldAutoSignField) {
|
void executeActionAuthProcedure({
|
||||||
void executeActionAuthProcedure({
|
onReauthFormSubmit: async (authOptions) => await onSign(authOptions),
|
||||||
onReauthFormSubmit: async (authOptions) => await onSign(authOptions),
|
actionTarget: field.type,
|
||||||
actionTarget: field.type,
|
});
|
||||||
});
|
}
|
||||||
}
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const parsedField = field.fieldMeta ? ZTextFieldMeta.parse(field.fieldMeta) : undefined;
|
const parsedField = field.fieldMeta ? ZTextFieldMeta.parse(field.fieldMeta) : undefined;
|
||||||
|
|
||||||
@ -318,7 +312,8 @@ export const DocumentSigningTextField = ({
|
|||||||
className="flex-1"
|
className="flex-1"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setShowCustomTextModal(false);
|
setShowCustomTextModal(false);
|
||||||
setLocalCustomText('');
|
setLocalCustomText(parsedFieldMeta?.text ?? '');
|
||||||
|
setErrors(initialErrors);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Trans>Cancel</Trans>
|
<Trans>Cancel</Trans>
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { useCallback, useEffect, useState } from 'react';
|
import { useCallback, 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';
|
||||||
@ -26,16 +26,13 @@ export const DocumentSearch = ({ initialValue = '' }: { initialValue?: string })
|
|||||||
|
|
||||||
setSearchParams(params);
|
setSearchParams(params);
|
||||||
},
|
},
|
||||||
[searchParams],
|
[searchParams, setSearchParams],
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
const currentQuery = searchParams?.get('query') ?? '';
|
||||||
const currentQueryParam = searchParams.get('query') || '';
|
if (currentQuery !== debouncedSearchTerm) {
|
||||||
|
handleSearch(debouncedSearchTerm);
|
||||||
if (debouncedSearchTerm !== currentQueryParam) {
|
}
|
||||||
handleSearch(debouncedSearchTerm);
|
|
||||||
}
|
|
||||||
}, [debouncedSearchTerm, searchParams]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Input
|
<Input
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { useEffect, useState } 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';
|
||||||
@ -22,11 +22,34 @@ export type VerifyEmailBannerProps = {
|
|||||||
|
|
||||||
const RESEND_CONFIRMATION_EMAIL_TIMEOUT = 20 * ONE_SECOND;
|
const RESEND_CONFIRMATION_EMAIL_TIMEOUT = 20 * ONE_SECOND;
|
||||||
|
|
||||||
|
const shouldShowDialog = () => {
|
||||||
|
try {
|
||||||
|
const emailVerificationDialogLastShown = localStorage.getItem(
|
||||||
|
'emailVerificationDialogLastShown',
|
||||||
|
);
|
||||||
|
|
||||||
|
if (emailVerificationDialogLastShown) {
|
||||||
|
const lastShownTimestamp = parseInt(emailVerificationDialogLastShown);
|
||||||
|
|
||||||
|
if (Date.now() - lastShownTimestamp < ONE_DAY) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the timestamp when showing the dialog
|
||||||
|
localStorage.setItem('emailVerificationDialogLastShown', Date.now().toString());
|
||||||
|
return true;
|
||||||
|
} catch {
|
||||||
|
// In case localStorage is not available (SSR, incognito mode, etc.)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
export const VerifyEmailBanner = ({ email }: VerifyEmailBannerProps) => {
|
export const VerifyEmailBanner = ({ email }: VerifyEmailBannerProps) => {
|
||||||
const { _ } = useLingui();
|
const { _ } = useLingui();
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
|
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
const [isOpen, setIsOpen] = useState(shouldShowDialog);
|
||||||
const [isPending, setIsPending] = useState(false);
|
const [isPending, setIsPending] = useState(false);
|
||||||
|
|
||||||
const [isButtonDisabled, setIsButtonDisabled] = useState(false);
|
const [isButtonDisabled, setIsButtonDisabled] = useState(false);
|
||||||
@ -62,27 +85,6 @@ export const VerifyEmailBanner = ({ email }: VerifyEmailBannerProps) => {
|
|||||||
setIsPending(false);
|
setIsPending(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
// Check localStorage to see if we've recently automatically displayed the dialog
|
|
||||||
// if it was within the past 24 hours, don't show it again
|
|
||||||
// otherwise, show it again and update the localStorage timestamp
|
|
||||||
const emailVerificationDialogLastShown = localStorage.getItem(
|
|
||||||
'emailVerificationDialogLastShown',
|
|
||||||
);
|
|
||||||
|
|
||||||
if (emailVerificationDialogLastShown) {
|
|
||||||
const lastShownTimestamp = parseInt(emailVerificationDialogLastShown);
|
|
||||||
|
|
||||||
if (Date.now() - lastShownTimestamp < ONE_DAY) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
setIsOpen(true);
|
|
||||||
|
|
||||||
localStorage.setItem('emailVerificationDialogLastShown', Date.now().toString());
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="bg-yellow-200 dark:bg-yellow-400">
|
<div className="bg-yellow-200 dark:bg-yellow-400">
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { useEffect, useState } from 'react';
|
import { useCallback, 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';
|
||||||
@ -29,22 +29,29 @@ export default function TeamsSettingsMembersPage() {
|
|||||||
/**
|
/**
|
||||||
* Handle debouncing the search query.
|
* Handle debouncing the search query.
|
||||||
*/
|
*/
|
||||||
useEffect(() => {
|
const handleSearchQueryChange = useCallback(
|
||||||
const params = new URLSearchParams(searchParams?.toString());
|
(newQuery: string) => {
|
||||||
|
const params = new URLSearchParams(searchParams?.toString());
|
||||||
|
|
||||||
params.set('query', debouncedSearchQuery);
|
if (newQuery.trim()) {
|
||||||
|
params.set('query', newQuery);
|
||||||
|
} else {
|
||||||
|
params.delete('query');
|
||||||
|
}
|
||||||
|
|
||||||
if (debouncedSearchQuery === '') {
|
if (params.toString() === searchParams?.toString()) {
|
||||||
params.delete('query');
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If nothing to change then do nothing.
|
setSearchParams(params);
|
||||||
if (params.toString() === searchParams?.toString()) {
|
},
|
||||||
return;
|
[searchParams, setSearchParams],
|
||||||
}
|
);
|
||||||
|
|
||||||
setSearchParams(params);
|
const currentParamQuery = searchParams?.get('query') ?? '';
|
||||||
}, [debouncedSearchQuery, pathname, searchParams]);
|
if (currentParamQuery !== debouncedSearchQuery) {
|
||||||
|
handleSearchQueryChange(debouncedSearchQuery);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { useEffect, useState } from 'react';
|
import { useCallback, useState } from 'react';
|
||||||
|
|
||||||
import { useLingui } from '@lingui/react/macro';
|
import { useLingui } from '@lingui/react/macro';
|
||||||
import { useSearchParams } from 'react-router';
|
import { useSearchParams } from 'react-router';
|
||||||
@ -20,21 +20,28 @@ export default function OrganisationSettingsTeamsPage() {
|
|||||||
const [searchQuery, setSearchQuery] = useState(() => searchParams?.get('query') ?? '');
|
const [searchQuery, setSearchQuery] = useState(() => searchParams?.get('query') ?? '');
|
||||||
|
|
||||||
const debouncedSearchQuery = useDebouncedValue(searchQuery, 500);
|
const debouncedSearchQuery = useDebouncedValue(searchQuery, 500);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle debouncing the search query.
|
* Handle debouncing the search query.
|
||||||
*/
|
*/
|
||||||
useEffect(() => {
|
const handleSearchQueryChange = useCallback(
|
||||||
const params = new URLSearchParams(searchParams?.toString());
|
(newQuery: string) => {
|
||||||
|
const params = new URLSearchParams(searchParams?.toString());
|
||||||
|
|
||||||
params.set('query', debouncedSearchQuery);
|
if (newQuery.trim()) {
|
||||||
|
params.set('query', newQuery);
|
||||||
|
} else {
|
||||||
|
params.delete('query');
|
||||||
|
}
|
||||||
|
|
||||||
if (debouncedSearchQuery === '') {
|
setSearchParams(params);
|
||||||
params.delete('query');
|
},
|
||||||
}
|
[searchParams, setSearchParams],
|
||||||
|
);
|
||||||
|
|
||||||
setSearchParams(params);
|
const currentParamQuery = searchParams?.get('query') ?? '';
|
||||||
}, [debouncedSearchQuery, pathname, searchParams]);
|
if (currentParamQuery !== debouncedSearchQuery) {
|
||||||
|
handleSearchQueryChange(debouncedSearchQuery);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
@ -1,5 +1,3 @@
|
|||||||
import { useEffect } from 'react';
|
|
||||||
|
|
||||||
import { Outlet, useNavigate } from 'react-router';
|
import { Outlet, useNavigate } from 'react-router';
|
||||||
|
|
||||||
import { OrganisationProvider } from '@documenso/lib/client-only/providers/organisation';
|
import { OrganisationProvider } from '@documenso/lib/client-only/providers/organisation';
|
||||||
@ -30,13 +28,8 @@ export default function Layout() {
|
|||||||
const currentOrganisation = organisations[0];
|
const currentOrganisation = organisations[0];
|
||||||
const team = currentOrganisation?.teams[0] || null;
|
const team = currentOrganisation?.teams[0] || null;
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!isPersonalLayoutMode || !team) {
|
|
||||||
void navigate('/settings/profile');
|
|
||||||
}
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
if (!isPersonalLayoutMode || !team) {
|
if (!isPersonalLayoutMode || !team) {
|
||||||
|
void navigate('/settings/profile');
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { useEffect, useMemo, useState } from 'react';
|
import { useMemo, useState } from 'react';
|
||||||
|
|
||||||
import { Trans } from '@lingui/react/macro';
|
import { Trans } from '@lingui/react/macro';
|
||||||
import { FolderType, OrganisationType } from '@prisma/client';
|
import { FolderType, OrganisationType } from '@prisma/client';
|
||||||
@ -12,10 +12,7 @@ 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';
|
||||||
import {
|
import { ZFindDocumentsInternalRequestSchema } from '@documenso/trpc/server/document-router/schema';
|
||||||
type TFindDocumentsInternalResponse,
|
|
||||||
ZFindDocumentsInternalRequestSchema,
|
|
||||||
} from '@documenso/trpc/server/document-router/schema';
|
|
||||||
import { Avatar, AvatarFallback, AvatarImage } from '@documenso/ui/primitives/avatar';
|
import { Avatar, AvatarFallback, AvatarImage } from '@documenso/ui/primitives/avatar';
|
||||||
import { Tabs, TabsList, TabsTrigger } from '@documenso/ui/primitives/tabs';
|
import { Tabs, TabsList, TabsTrigger } from '@documenso/ui/primitives/tabs';
|
||||||
|
|
||||||
@ -55,15 +52,6 @@ export default function DocumentsPage() {
|
|||||||
const [isMovingDocument, setIsMovingDocument] = useState(false);
|
const [isMovingDocument, setIsMovingDocument] = useState(false);
|
||||||
const [documentToMove, setDocumentToMove] = useState<number | null>(null);
|
const [documentToMove, setDocumentToMove] = useState<number | null>(null);
|
||||||
|
|
||||||
const [stats, setStats] = useState<TFindDocumentsInternalResponse['stats']>({
|
|
||||||
[ExtendedDocumentStatus.DRAFT]: 0,
|
|
||||||
[ExtendedDocumentStatus.PENDING]: 0,
|
|
||||||
[ExtendedDocumentStatus.COMPLETED]: 0,
|
|
||||||
[ExtendedDocumentStatus.REJECTED]: 0,
|
|
||||||
[ExtendedDocumentStatus.INBOX]: 0,
|
|
||||||
[ExtendedDocumentStatus.ALL]: 0,
|
|
||||||
});
|
|
||||||
|
|
||||||
const findDocumentSearchParams = useMemo(
|
const findDocumentSearchParams = useMemo(
|
||||||
() => ZSearchParamsSchema.safeParse(Object.fromEntries(searchParams.entries())).data || {},
|
() => ZSearchParamsSchema.safeParse(Object.fromEntries(searchParams.entries())).data || {},
|
||||||
[searchParams],
|
[searchParams],
|
||||||
@ -74,6 +62,19 @@ export default function DocumentsPage() {
|
|||||||
folderId,
|
folderId,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const stats = useMemo(
|
||||||
|
() =>
|
||||||
|
data?.stats ?? {
|
||||||
|
[ExtendedDocumentStatus.DRAFT]: 0,
|
||||||
|
[ExtendedDocumentStatus.PENDING]: 0,
|
||||||
|
[ExtendedDocumentStatus.COMPLETED]: 0,
|
||||||
|
[ExtendedDocumentStatus.REJECTED]: 0,
|
||||||
|
[ExtendedDocumentStatus.INBOX]: 0,
|
||||||
|
[ExtendedDocumentStatus.ALL]: 0,
|
||||||
|
},
|
||||||
|
[data?.stats],
|
||||||
|
);
|
||||||
|
|
||||||
const getTabHref = (value: keyof typeof ExtendedDocumentStatus) => {
|
const getTabHref = (value: keyof typeof ExtendedDocumentStatus) => {
|
||||||
const params = new URLSearchParams(searchParams);
|
const params = new URLSearchParams(searchParams);
|
||||||
|
|
||||||
@ -104,12 +105,6 @@ export default function DocumentsPage() {
|
|||||||
return path;
|
return path;
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (data?.stats) {
|
|
||||||
setStats(data.stats);
|
|
||||||
}
|
|
||||||
}, [data?.stats]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DocumentDropZoneWrapper>
|
<DocumentDropZoneWrapper>
|
||||||
<div className="mx-auto w-full max-w-screen-xl px-4 md:px-8">
|
<div className="mx-auto w-full max-w-screen-xl px-4 md:px-8">
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { useEffect, useState } from 'react';
|
import { useCallback, useState } from 'react';
|
||||||
|
|
||||||
import { useLingui } from '@lingui/react/macro';
|
import { useLingui } from '@lingui/react/macro';
|
||||||
import { useLocation, useSearchParams } from 'react-router';
|
import { useLocation, useSearchParams } from 'react-router';
|
||||||
@ -19,26 +19,33 @@ export default function TeamsSettingsMembersPage() {
|
|||||||
const [searchQuery, setSearchQuery] = useState(() => searchParams?.get('query') ?? '');
|
const [searchQuery, setSearchQuery] = useState(() => searchParams?.get('query') ?? '');
|
||||||
|
|
||||||
const debouncedSearchQuery = useDebouncedValue(searchQuery, 500);
|
const debouncedSearchQuery = useDebouncedValue(searchQuery, 500);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle debouncing the search query.
|
* Handle debouncing the search query.
|
||||||
*/
|
*/
|
||||||
useEffect(() => {
|
const handleSearchQueryChange = useCallback(
|
||||||
const params = new URLSearchParams(searchParams?.toString());
|
(newQuery: string) => {
|
||||||
|
const params = new URLSearchParams(searchParams?.toString());
|
||||||
|
|
||||||
params.set('query', debouncedSearchQuery);
|
if (newQuery.trim()) {
|
||||||
|
params.set('query', newQuery);
|
||||||
|
} else {
|
||||||
|
params.delete('query');
|
||||||
|
}
|
||||||
|
|
||||||
if (debouncedSearchQuery === '') {
|
// If nothing to change then do nothing.
|
||||||
params.delete('query');
|
if (params.toString() === searchParams?.toString()) {
|
||||||
}
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// If nothing to change then do nothing.
|
setSearchParams(params);
|
||||||
if (params.toString() === searchParams?.toString()) {
|
},
|
||||||
return;
|
[searchParams, setSearchParams],
|
||||||
}
|
);
|
||||||
|
|
||||||
setSearchParams(params);
|
const currentParamQuery = searchParams?.get('query') ?? '';
|
||||||
}, [debouncedSearchQuery, pathname, searchParams]);
|
if (currentParamQuery !== debouncedSearchQuery) {
|
||||||
|
handleSearchQueryChange(debouncedSearchQuery);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { useEffect, useMemo, useState } from 'react';
|
import { useMemo, useState } from 'react';
|
||||||
|
|
||||||
import { useLingui } from '@lingui/react/macro';
|
import { useLingui } from '@lingui/react/macro';
|
||||||
import { Trans } from '@lingui/react/macro';
|
import { Trans } from '@lingui/react/macro';
|
||||||
@ -130,12 +130,8 @@ export default function PublicProfilePage({ loaderData }: Route.ComponentProps)
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setIsPublicProfileVisible(profile.enabled);
|
|
||||||
}, [profile.enabled]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="max-w-2xl">
|
<div key={team.id} className="max-w-2xl">
|
||||||
<SettingsHeader
|
<SettingsHeader
|
||||||
title={t`Public Profile`}
|
title={t`Public Profile`}
|
||||||
subtitle={t`You can choose to enable or disable the profile for public view.`}
|
subtitle={t`You can choose to enable or disable the profile for public view.`}
|
||||||
|
|||||||
@ -38,33 +38,48 @@ export default function VerifyEmailPage({ loaderData }: Route.ComponentProps) {
|
|||||||
const [state, setState] = useState<keyof typeof EMAIL_VERIFICATION_STATE | null>(null);
|
const [state, setState] = useState<keyof typeof EMAIL_VERIFICATION_STATE | null>(null);
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
|
||||||
const verifyToken = async () => {
|
|
||||||
setIsLoading(true);
|
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await authClient.emailPassword.verifyEmail({
|
|
||||||
token,
|
|
||||||
});
|
|
||||||
|
|
||||||
await refreshSession();
|
|
||||||
|
|
||||||
setState(response.state);
|
|
||||||
} catch (err) {
|
|
||||||
console.error(err);
|
|
||||||
|
|
||||||
toast({
|
|
||||||
title: _(msg`Something went wrong`),
|
|
||||||
description: _(msg`We were unable to verify your email at this time.`),
|
|
||||||
});
|
|
||||||
|
|
||||||
await navigate('/verify-email');
|
|
||||||
}
|
|
||||||
|
|
||||||
setIsLoading(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
void verifyToken();
|
let ignore = false;
|
||||||
|
|
||||||
|
const verify = async () => {
|
||||||
|
try {
|
||||||
|
setIsLoading(true);
|
||||||
|
|
||||||
|
const response = await authClient.emailPassword.verifyEmail({
|
||||||
|
token,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (ignore) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await refreshSession();
|
||||||
|
setState(response.state);
|
||||||
|
} catch (err) {
|
||||||
|
if (ignore) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.error(err);
|
||||||
|
|
||||||
|
toast({
|
||||||
|
title: _(msg`Something went wrong`),
|
||||||
|
description: _(msg`We were unable to verify your email at this time.`),
|
||||||
|
});
|
||||||
|
|
||||||
|
await navigate('/verify-email');
|
||||||
|
} finally {
|
||||||
|
if (!ignore) {
|
||||||
|
setIsLoading(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
void verify();
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
ignore = true;
|
||||||
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
if (isLoading || state === null) {
|
if (isLoading || state === null) {
|
||||||
|
|||||||
@ -1,23 +1,23 @@
|
|||||||
import { useCallback, useEffect, useState } from 'react';
|
import { useEffect, useMemo, useState } from 'react';
|
||||||
|
|
||||||
import { getBoundingClientRect } from '@documenso/lib/client-only/get-bounding-client-rect';
|
import { getBoundingClientRect } from '@documenso/lib/client-only/get-bounding-client-rect';
|
||||||
|
|
||||||
export const useElementBounds = (elementOrSelector: HTMLElement | string, withScroll = false) => {
|
export const useElementBounds = (elementOrSelector: HTMLElement | string, withScroll = false) => {
|
||||||
const [bounds, setBounds] = useState({
|
const [forceRecalc, setForceRecalc] = useState(0);
|
||||||
top: 0,
|
|
||||||
left: 0,
|
|
||||||
height: 0,
|
|
||||||
width: 0,
|
|
||||||
});
|
|
||||||
|
|
||||||
const calculateBounds = useCallback(() => {
|
const bounds = useMemo(() => {
|
||||||
const $el =
|
const $el =
|
||||||
typeof elementOrSelector === 'string'
|
typeof elementOrSelector === 'string'
|
||||||
? document.querySelector<HTMLElement>(elementOrSelector)
|
? document.querySelector<HTMLElement>(elementOrSelector)
|
||||||
: elementOrSelector;
|
: elementOrSelector;
|
||||||
|
|
||||||
if (!$el) {
|
if (!$el) {
|
||||||
throw new Error('Element not found');
|
return {
|
||||||
|
top: 0,
|
||||||
|
left: 0,
|
||||||
|
height: 0,
|
||||||
|
width: 0,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (withScroll) {
|
if (withScroll) {
|
||||||
@ -32,15 +32,11 @@ export const useElementBounds = (elementOrSelector: HTMLElement | string, withSc
|
|||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
};
|
};
|
||||||
}, [elementOrSelector, withScroll]);
|
}, [elementOrSelector, withScroll, forceRecalc]);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setBounds(calculateBounds());
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const onResize = () => {
|
const onResize = () => {
|
||||||
setBounds(calculateBounds());
|
setForceRecalc((prev) => prev + 1);
|
||||||
};
|
};
|
||||||
|
|
||||||
window.addEventListener('resize', onResize);
|
window.addEventListener('resize', onResize);
|
||||||
@ -61,7 +57,7 @@ export const useElementBounds = (elementOrSelector: HTMLElement | string, withSc
|
|||||||
}
|
}
|
||||||
|
|
||||||
const observer = new ResizeObserver(() => {
|
const observer = new ResizeObserver(() => {
|
||||||
setBounds(calculateBounds());
|
setForceRecalc((prev) => prev + 1);
|
||||||
});
|
});
|
||||||
|
|
||||||
observer.observe($el);
|
observer.observe($el);
|
||||||
@ -69,7 +65,7 @@ export const useElementBounds = (elementOrSelector: HTMLElement | string, withSc
|
|||||||
return () => {
|
return () => {
|
||||||
observer.disconnect();
|
observer.disconnect();
|
||||||
};
|
};
|
||||||
}, []);
|
}, [elementOrSelector]);
|
||||||
|
|
||||||
return bounds;
|
return bounds;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
/* eslint-disable @typescript-eslint/consistent-type-assertions */
|
/* eslint-disable @typescript-eslint/consistent-type-assertions */
|
||||||
import { RefObject, useEffect, useState } from 'react';
|
import { useMemo } from 'react';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Calculate the width and height of a text element.
|
* Calculate the width and height of a text element.
|
||||||
@ -64,13 +64,7 @@ export function useElementScaleSize(
|
|||||||
fontSize: number,
|
fontSize: number,
|
||||||
fontFamily: string,
|
fontFamily: string,
|
||||||
) {
|
) {
|
||||||
const [scalingFactor, setScalingFactor] = useState(1);
|
return useMemo(() => {
|
||||||
|
return calculateTextScaleSize(container, text, `${fontSize}px`, fontFamily);
|
||||||
useEffect(() => {
|
}, [container, text, fontSize, fontFamily]);
|
||||||
const scaleSize = calculateTextScaleSize(container, text, `${fontSize}px`, fontFamily);
|
|
||||||
|
|
||||||
setScalingFactor(scaleSize);
|
|
||||||
}, [text, container, fontFamily, fontSize]);
|
|
||||||
|
|
||||||
return scalingFactor;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { useCallback, useEffect, useState } from 'react';
|
import { useEffect, useMemo, useState } from 'react';
|
||||||
|
|
||||||
import type { Field } from '@prisma/client';
|
import type { Field } from '@prisma/client';
|
||||||
|
|
||||||
@ -6,20 +6,20 @@ import { getBoundingClientRect } from '@documenso/lib/client-only/get-bounding-c
|
|||||||
import { PDF_VIEWER_PAGE_SELECTOR } from '@documenso/lib/constants/pdf-viewer';
|
import { PDF_VIEWER_PAGE_SELECTOR } from '@documenso/lib/constants/pdf-viewer';
|
||||||
|
|
||||||
export const useFieldPageCoords = (field: Field) => {
|
export const useFieldPageCoords = (field: Field) => {
|
||||||
const [coords, setCoords] = useState({
|
const [forceRecalc, setForceRecalc] = useState(0);
|
||||||
x: 0,
|
|
||||||
y: 0,
|
|
||||||
height: 0,
|
|
||||||
width: 0,
|
|
||||||
});
|
|
||||||
|
|
||||||
const calculateCoords = useCallback(() => {
|
const coords = useMemo(() => {
|
||||||
const $page = document.querySelector<HTMLElement>(
|
const $page = document.querySelector<HTMLElement>(
|
||||||
`${PDF_VIEWER_PAGE_SELECTOR}[data-page-number="${field.page}"]`,
|
`${PDF_VIEWER_PAGE_SELECTOR}[data-page-number="${field.page}"]`,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!$page) {
|
if (!$page) {
|
||||||
return;
|
return {
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
height: 0,
|
||||||
|
width: 0,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const { top, left, height, width } = getBoundingClientRect($page);
|
const { top, left, height, width } = getBoundingClientRect($page);
|
||||||
@ -31,21 +31,17 @@ export const useFieldPageCoords = (field: Field) => {
|
|||||||
const fieldHeight = (Number(field.height) / 100) * height;
|
const fieldHeight = (Number(field.height) / 100) * height;
|
||||||
const fieldWidth = (Number(field.width) / 100) * width;
|
const fieldWidth = (Number(field.width) / 100) * width;
|
||||||
|
|
||||||
setCoords({
|
return {
|
||||||
x: fieldX,
|
x: fieldX,
|
||||||
y: fieldY,
|
y: fieldY,
|
||||||
height: fieldHeight,
|
height: fieldHeight,
|
||||||
width: fieldWidth,
|
width: fieldWidth,
|
||||||
});
|
};
|
||||||
}, [field.height, field.page, field.positionX, field.positionY, field.width]);
|
}, [field.height, field.page, field.positionX, field.positionY, field.width, forceRecalc]);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
calculateCoords();
|
|
||||||
}, [calculateCoords]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const onResize = () => {
|
const onResize = () => {
|
||||||
calculateCoords();
|
setForceRecalc((prev) => prev + 1);
|
||||||
};
|
};
|
||||||
|
|
||||||
window.addEventListener('resize', onResize);
|
window.addEventListener('resize', onResize);
|
||||||
@ -53,7 +49,7 @@ export const useFieldPageCoords = (field: Field) => {
|
|||||||
return () => {
|
return () => {
|
||||||
window.removeEventListener('resize', onResize);
|
window.removeEventListener('resize', onResize);
|
||||||
};
|
};
|
||||||
}, [calculateCoords]);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const $page = document.querySelector<HTMLElement>(
|
const $page = document.querySelector<HTMLElement>(
|
||||||
@ -65,7 +61,7 @@ export const useFieldPageCoords = (field: Field) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const observer = new ResizeObserver(() => {
|
const observer = new ResizeObserver(() => {
|
||||||
calculateCoords();
|
setForceRecalc((prev) => prev + 1);
|
||||||
});
|
});
|
||||||
|
|
||||||
observer.observe($page);
|
observer.observe($page);
|
||||||
@ -73,7 +69,7 @@ export const useFieldPageCoords = (field: Field) => {
|
|||||||
return () => {
|
return () => {
|
||||||
observer.disconnect();
|
observer.disconnect();
|
||||||
};
|
};
|
||||||
}, [calculateCoords, field.page]);
|
}, [field.page]);
|
||||||
|
|
||||||
return coords;
|
return coords;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,11 +1,17 @@
|
|||||||
import { useEffect, useState } from 'react';
|
import { useSyncExternalStore } from 'react';
|
||||||
|
|
||||||
|
const subscribe = () => {
|
||||||
|
return () => {};
|
||||||
|
};
|
||||||
|
|
||||||
|
const getSnapshot = () => {
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getServerSnapshot = () => {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
export const useIsMounted = () => {
|
export const useIsMounted = () => {
|
||||||
const [isMounted, setIsMounted] = useState(false);
|
return useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setIsMounted(true);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return isMounted;
|
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,11 +1,23 @@
|
|||||||
import { useEffect, useState } from 'react';
|
import { useSyncExternalStore } from 'react';
|
||||||
|
|
||||||
export const ClientOnly = async ({ children }: { children: React.ReactNode }) => {
|
const subscribe = () => {
|
||||||
const [mounted, setMounted] = useState(false);
|
return () => {};
|
||||||
|
};
|
||||||
useEffect(() => {
|
|
||||||
setMounted(true);
|
const getSnapshot = () => {
|
||||||
}, []);
|
return true;
|
||||||
|
};
|
||||||
return mounted ? children : null;
|
|
||||||
|
const getServerSnapshot = () => {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ClientOnly = ({
|
||||||
|
children,
|
||||||
|
}: {
|
||||||
|
children: React.ReactNode;
|
||||||
|
}): React.ReactElement | null => {
|
||||||
|
const isClient = useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
|
||||||
|
|
||||||
|
return isClient ? <>{children}</> : null;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -18,7 +18,6 @@ import {
|
|||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import { useFieldArray, useForm } from 'react-hook-form';
|
import { useFieldArray, useForm } from 'react-hook-form';
|
||||||
import { useHotkeys } from 'react-hotkeys-hook';
|
import { useHotkeys } from 'react-hotkeys-hook';
|
||||||
import { prop, sortBy } from 'remeda';
|
|
||||||
|
|
||||||
import { getBoundingClientRect } from '@documenso/lib/client-only/get-bounding-client-rect';
|
import { getBoundingClientRect } from '@documenso/lib/client-only/get-bounding-client-rect';
|
||||||
import { useDocumentElement } from '@documenso/lib/client-only/hooks/use-document-element';
|
import { useDocumentElement } from '@documenso/lib/client-only/hooks/use-document-element';
|
||||||
@ -103,6 +102,10 @@ export const AddFieldsFormPartial = ({
|
|||||||
|
|
||||||
const [isMissingSignatureDialogVisible, setIsMissingSignatureDialogVisible] = useState(false);
|
const [isMissingSignatureDialogVisible, setIsMissingSignatureDialogVisible] = useState(false);
|
||||||
|
|
||||||
|
const handleMissingSignatureDialogOpenChange = (value: boolean) => {
|
||||||
|
setIsMissingSignatureDialogVisible(value);
|
||||||
|
};
|
||||||
|
|
||||||
const { isWithinPageBounds, getFieldPosition, getPage } = useDocumentElement();
|
const { isWithinPageBounds, getFieldPosition, getPage } = useDocumentElement();
|
||||||
const { currentStep, totalSteps, previousStep } = useStep();
|
const { currentStep, totalSteps, previousStep } = useStep();
|
||||||
const canRenderBackButtonAsRemove =
|
const canRenderBackButtonAsRemove =
|
||||||
@ -511,18 +514,24 @@ export const AddFieldsFormPartial = ({
|
|||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
const defaultSelectedSigner = useMemo(() => {
|
||||||
const recipientsByRoleToDisplay = recipients.filter(
|
const recipientsByRoleToDisplay = recipients.filter(
|
||||||
(recipient) =>
|
(recipient) =>
|
||||||
recipient.role !== RecipientRole.CC && recipient.role !== RecipientRole.ASSISTANT,
|
recipient.role !== RecipientRole.CC && recipient.role !== RecipientRole.ASSISTANT,
|
||||||
);
|
);
|
||||||
|
|
||||||
setSelectedSigner(
|
return (
|
||||||
recipientsByRoleToDisplay.find((r) => r.sendStatus !== SendStatus.SENT) ??
|
recipientsByRoleToDisplay.find((r) => r.sendStatus !== SendStatus.SENT) ??
|
||||||
recipientsByRoleToDisplay[0],
|
recipientsByRoleToDisplay[0]
|
||||||
);
|
);
|
||||||
}, [recipients]);
|
}, [recipients]);
|
||||||
|
|
||||||
|
if (selectedSigner && !recipients.find((r) => r.id === selectedSigner.id)) {
|
||||||
|
setSelectedSigner(defaultSelectedSigner);
|
||||||
|
} else if (!selectedSigner && defaultSelectedSigner) {
|
||||||
|
setSelectedSigner(defaultSelectedSigner);
|
||||||
|
}
|
||||||
|
|
||||||
const recipientsByRole = useMemo(() => {
|
const recipientsByRole = useMemo(() => {
|
||||||
const recipientsByRole: Record<RecipientRole, Recipient[]> = {
|
const recipientsByRole: Record<RecipientRole, Recipient[]> = {
|
||||||
CC: [],
|
CC: [],
|
||||||
@ -539,29 +548,6 @@ export const AddFieldsFormPartial = ({
|
|||||||
return recipientsByRole;
|
return recipientsByRole;
|
||||||
}, [recipients]);
|
}, [recipients]);
|
||||||
|
|
||||||
const recipientsByRoleToDisplay = useMemo(() => {
|
|
||||||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
||||||
return (Object.entries(recipientsByRole) as [RecipientRole, Recipient[]][])
|
|
||||||
.filter(
|
|
||||||
([role]) =>
|
|
||||||
role !== RecipientRole.CC &&
|
|
||||||
role !== RecipientRole.VIEWER &&
|
|
||||||
role !== RecipientRole.ASSISTANT,
|
|
||||||
)
|
|
||||||
.map(
|
|
||||||
([role, roleRecipients]) =>
|
|
||||||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
||||||
[
|
|
||||||
role,
|
|
||||||
sortBy(
|
|
||||||
roleRecipients,
|
|
||||||
[(r) => r.signingOrder || Number.MAX_SAFE_INTEGER, 'asc'],
|
|
||||||
[prop('id'), 'asc'],
|
|
||||||
),
|
|
||||||
] as [RecipientRole, Recipient[]],
|
|
||||||
);
|
|
||||||
}, [recipientsByRole]);
|
|
||||||
|
|
||||||
const handleAdvancedSettings = () => {
|
const handleAdvancedSettings = () => {
|
||||||
setShowAdvancedSettings((prev) => !prev);
|
setShowAdvancedSettings((prev) => !prev);
|
||||||
};
|
};
|
||||||
@ -995,7 +981,7 @@ export const AddFieldsFormPartial = ({
|
|||||||
|
|
||||||
<MissingSignatureFieldDialog
|
<MissingSignatureFieldDialog
|
||||||
isOpen={isMissingSignatureDialogVisible}
|
isOpen={isMissingSignatureDialogVisible}
|
||||||
onOpenChange={(value) => setIsMissingSignatureDialogVisible(value)}
|
onOpenChange={handleMissingSignatureDialogOpenChange}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { forwardRef, useEffect, useState } from 'react';
|
import { forwardRef, useMemo, useState } from 'react';
|
||||||
|
|
||||||
import type { MessageDescriptor } from '@lingui/core';
|
import type { MessageDescriptor } from '@lingui/core';
|
||||||
import { msg } from '@lingui/core/macro';
|
import { msg } from '@lingui/core/macro';
|
||||||
@ -160,22 +160,32 @@ export const FieldAdvancedSettings = forwardRef<HTMLDivElement, FieldAdvancedSet
|
|||||||
|
|
||||||
const defaultState: FieldMeta = getDefaultState(field.type);
|
const defaultState: FieldMeta = getDefaultState(field.type);
|
||||||
|
|
||||||
const [fieldState, setFieldState] = useState(() => {
|
const initialFieldState = useMemo(() => {
|
||||||
const savedState = localStorage.getItem(localStorageKey);
|
|
||||||
return savedState ? { ...defaultState, ...JSON.parse(savedState) } : defaultState;
|
|
||||||
});
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (fieldMeta && typeof fieldMeta === 'object') {
|
if (fieldMeta && typeof fieldMeta === 'object') {
|
||||||
const parsedFieldMeta = ZFieldMetaSchema.parse(fieldMeta);
|
const parsedFieldMeta = ZFieldMetaSchema.parse(fieldMeta);
|
||||||
|
return {
|
||||||
setFieldState({
|
|
||||||
...defaultState,
|
...defaultState,
|
||||||
...parsedFieldMeta,
|
...parsedFieldMeta,
|
||||||
});
|
};
|
||||||
}
|
}
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
}, [fieldMeta]);
|
const savedState = localStorage.getItem(localStorageKey);
|
||||||
|
return savedState ? { ...defaultState, ...JSON.parse(savedState) } : defaultState;
|
||||||
|
}, [fieldMeta, defaultState, localStorageKey]);
|
||||||
|
|
||||||
|
const [fieldState, setFieldState] = useState(initialFieldState);
|
||||||
|
|
||||||
|
if (fieldMeta && typeof fieldMeta === 'object') {
|
||||||
|
const parsedFieldMeta = ZFieldMetaSchema.parse(fieldMeta);
|
||||||
|
const expectedState = {
|
||||||
|
...defaultState,
|
||||||
|
...parsedFieldMeta,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (JSON.stringify(fieldState) !== JSON.stringify(expectedState)) {
|
||||||
|
setFieldState(expectedState);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const handleFieldChange = (
|
const handleFieldChange = (
|
||||||
key: FieldMetaKeys,
|
key: FieldMetaKeys,
|
||||||
|
|||||||
@ -1,12 +1,12 @@
|
|||||||
import type { MouseEvent, PointerEvent, RefObject, TouchEvent } from 'react';
|
import type { MouseEvent, PointerEvent, RefObject, TouchEvent } from 'react';
|
||||||
import { useMemo, useRef, useState } from 'react';
|
import { useMemo, useRef, useState } from 'react';
|
||||||
|
import { useLayoutEffect } from 'react';
|
||||||
|
|
||||||
import { Trans } from '@lingui/react/macro';
|
import { Trans } from '@lingui/react/macro';
|
||||||
import { Undo2 } from 'lucide-react';
|
import { Undo2 } from 'lucide-react';
|
||||||
import type { StrokeOptions } from 'perfect-freehand';
|
import type { StrokeOptions } from 'perfect-freehand';
|
||||||
import { getStroke } from 'perfect-freehand';
|
import { getStroke } from 'perfect-freehand';
|
||||||
|
|
||||||
import { unsafe_useEffectOnce } from '@documenso/lib/client-only/hooks/use-effect-once';
|
|
||||||
import {
|
import {
|
||||||
SIGNATURE_CANVAS_DPI,
|
SIGNATURE_CANVAS_DPI,
|
||||||
SIGNATURE_MIN_COVERAGE_THRESHOLD,
|
SIGNATURE_MIN_COVERAGE_THRESHOLD,
|
||||||
@ -247,7 +247,7 @@ export const SignaturePadDraw = ({
|
|||||||
onChange?.($el.current.toDataURL());
|
onChange?.($el.current.toDataURL());
|
||||||
};
|
};
|
||||||
|
|
||||||
unsafe_useEffectOnce(() => {
|
useLayoutEffect(() => {
|
||||||
if ($el.current) {
|
if ($el.current) {
|
||||||
$el.current.width = $el.current.clientWidth * SIGNATURE_CANVAS_DPI;
|
$el.current.width = $el.current.clientWidth * SIGNATURE_CANVAS_DPI;
|
||||||
$el.current.height = $el.current.clientHeight * SIGNATURE_CANVAS_DPI;
|
$el.current.height = $el.current.clientHeight * SIGNATURE_CANVAS_DPI;
|
||||||
@ -270,7 +270,7 @@ export const SignaturePadDraw = ({
|
|||||||
|
|
||||||
img.src = value;
|
img.src = value;
|
||||||
}
|
}
|
||||||
});
|
}, [value]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={cn('h-full w-full', className)}>
|
<div className={cn('h-full w-full', className)}>
|
||||||
|
|||||||
@ -1,10 +1,9 @@
|
|||||||
import { useRef } from 'react';
|
import { useLayoutEffect, useRef } from 'react';
|
||||||
|
|
||||||
import { Trans } from '@lingui/react/macro';
|
import { Trans } from '@lingui/react/macro';
|
||||||
import { motion } from 'framer-motion';
|
import { motion } from 'framer-motion';
|
||||||
import { UploadCloudIcon } from 'lucide-react';
|
import { UploadCloudIcon } from 'lucide-react';
|
||||||
|
|
||||||
import { unsafe_useEffectOnce } from '@documenso/lib/client-only/hooks/use-effect-once';
|
|
||||||
import { SIGNATURE_CANVAS_DPI } from '@documenso/lib/constants/signatures';
|
import { SIGNATURE_CANVAS_DPI } from '@documenso/lib/constants/signatures';
|
||||||
|
|
||||||
import { cn } from '../../lib/utils';
|
import { cn } from '../../lib/utils';
|
||||||
@ -97,7 +96,7 @@ export const SignaturePadUpload = ({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
unsafe_useEffectOnce(() => {
|
useLayoutEffect(() => {
|
||||||
// Todo: Not really sure if this is required for uploaded images.
|
// Todo: Not really sure if this is required for uploaded images.
|
||||||
if ($el.current) {
|
if ($el.current) {
|
||||||
$el.current.width = $el.current.clientWidth * SIGNATURE_CANVAS_DPI;
|
$el.current.width = $el.current.clientWidth * SIGNATURE_CANVAS_DPI;
|
||||||
@ -121,7 +120,7 @@ export const SignaturePadUpload = ({
|
|||||||
|
|
||||||
img.src = value;
|
img.src = value;
|
||||||
}
|
}
|
||||||
});
|
}, [value]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={cn('relative h-full w-full', className)}>
|
<div className={cn('relative h-full w-full', className)}>
|
||||||
|
|||||||
@ -1,5 +1,3 @@
|
|||||||
import { useEffect } from 'react';
|
|
||||||
|
|
||||||
import { zodResolver } from '@hookform/resolvers/zod';
|
import { zodResolver } from '@hookform/resolvers/zod';
|
||||||
import { useLingui } from '@lingui/react/macro';
|
import { useLingui } from '@lingui/react/macro';
|
||||||
import { Trans } from '@lingui/react/macro';
|
import { Trans } from '@lingui/react/macro';
|
||||||
@ -16,7 +14,7 @@ import {
|
|||||||
DOCUMENT_SIGNATURE_TYPES,
|
DOCUMENT_SIGNATURE_TYPES,
|
||||||
} from '@documenso/lib/constants/document';
|
} from '@documenso/lib/constants/document';
|
||||||
import { SUPPORTED_LANGUAGES } from '@documenso/lib/constants/i18n';
|
import { SUPPORTED_LANGUAGES } from '@documenso/lib/constants/i18n';
|
||||||
import { DEFAULT_DOCUMENT_TIME_ZONE, TIME_ZONES } from '@documenso/lib/constants/time-zones';
|
import { TIME_ZONES } from '@documenso/lib/constants/time-zones';
|
||||||
import { ZDocumentEmailSettingsSchema } from '@documenso/lib/types/document-email';
|
import { ZDocumentEmailSettingsSchema } from '@documenso/lib/types/document-email';
|
||||||
import type { TTemplate } from '@documenso/lib/types/template';
|
import type { TTemplate } from '@documenso/lib/types/template';
|
||||||
import { extractDocumentAuthMethods } from '@documenso/lib/utils/document-auth';
|
import { extractDocumentAuthMethods } from '@documenso/lib/utils/document-auth';
|
||||||
@ -113,7 +111,8 @@ export const AddTemplateSettingsFormPartial = ({
|
|||||||
meta: {
|
meta: {
|
||||||
subject: template.templateMeta?.subject ?? '',
|
subject: template.templateMeta?.subject ?? '',
|
||||||
message: template.templateMeta?.message ?? '',
|
message: template.templateMeta?.message ?? '',
|
||||||
timezone: template.templateMeta?.timezone ?? DEFAULT_DOCUMENT_TIME_ZONE,
|
timezone:
|
||||||
|
template.templateMeta?.timezone ?? Intl.DateTimeFormat().resolvedOptions().timeZone,
|
||||||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
||||||
dateFormat: (template.templateMeta?.dateFormat ??
|
dateFormat: (template.templateMeta?.dateFormat ??
|
||||||
DEFAULT_DOCUMENT_DATE_FORMAT) as TDocumentMetaDateFormat,
|
DEFAULT_DOCUMENT_DATE_FORMAT) as TDocumentMetaDateFormat,
|
||||||
@ -152,14 +151,6 @@ export const AddTemplateSettingsFormPartial = ({
|
|||||||
)
|
)
|
||||||
.otherwise(() => false);
|
.otherwise(() => false);
|
||||||
|
|
||||||
// We almost always want to set the timezone to the user's local timezone to avoid confusion
|
|
||||||
// when the document is signed.
|
|
||||||
useEffect(() => {
|
|
||||||
if (!form.formState.touchedFields.meta?.timezone && !template.templateMeta?.timezone) {
|
|
||||||
form.setValue('meta.timezone', Intl.DateTimeFormat().resolvedOptions().timeZone);
|
|
||||||
}
|
|
||||||
}, [form, form.setValue, form.formState.touchedFields.meta?.timezone]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<DocumentFlowFormContainerHeader
|
<DocumentFlowFormContainerHeader
|
||||||
|
|||||||
Reference in New Issue
Block a user