mirror of
https://github.com/documenso/documenso.git
synced 2026-07-11 05:25:08 +10:00
Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ac51bc72ee | |||
| 6b81699cce | |||
| c849b8b017 | |||
| 13e7084836 | |||
| d83572dd53 | |||
| 08e5acd0b2 | |||
| 1b55b3f7ea |
@@ -0,0 +1,140 @@
|
|||||||
|
import { Alert, AlertDescription } from '@documenso/ui/primitives/alert';
|
||||||
|
import { Button } from '@documenso/ui/primitives/button';
|
||||||
|
import {
|
||||||
|
Dialog,
|
||||||
|
DialogClose,
|
||||||
|
DialogContent,
|
||||||
|
DialogDescription,
|
||||||
|
DialogFooter,
|
||||||
|
DialogHeader,
|
||||||
|
DialogTitle,
|
||||||
|
DialogTrigger,
|
||||||
|
} from '@documenso/ui/primitives/dialog';
|
||||||
|
import { Trans } from '@lingui/react/macro';
|
||||||
|
import { useState } from 'react';
|
||||||
|
|
||||||
|
export type DocumentPreferencesResetDialogProps = {
|
||||||
|
disabled?: boolean;
|
||||||
|
isSubmitting: boolean;
|
||||||
|
onReset: () => Promise<void>;
|
||||||
|
showAiFeatures?: boolean;
|
||||||
|
showDocumentVisibility?: boolean;
|
||||||
|
showIncludeSenderDetails?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const DocumentPreferencesResetDialog = ({
|
||||||
|
disabled = false,
|
||||||
|
isSubmitting,
|
||||||
|
onReset,
|
||||||
|
showAiFeatures = false,
|
||||||
|
showDocumentVisibility = false,
|
||||||
|
showIncludeSenderDetails = false,
|
||||||
|
}: DocumentPreferencesResetDialogProps) => {
|
||||||
|
const [open, setOpen] = useState(false);
|
||||||
|
const [isResetting, setIsResetting] = useState(false);
|
||||||
|
|
||||||
|
const isLoading = isSubmitting || isResetting;
|
||||||
|
|
||||||
|
const handleResetToDefaults = async () => {
|
||||||
|
setIsResetting(true);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await onReset();
|
||||||
|
setOpen(false);
|
||||||
|
} finally {
|
||||||
|
setIsResetting(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog open={open} onOpenChange={(value) => !isLoading && setOpen(value)}>
|
||||||
|
<DialogTrigger asChild>
|
||||||
|
<Button variant="destructive" type="button" size="sm" disabled={disabled || isLoading}>
|
||||||
|
<Trans>Reset to defaults</Trans>
|
||||||
|
</Button>
|
||||||
|
</DialogTrigger>
|
||||||
|
|
||||||
|
<DialogContent>
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle>
|
||||||
|
<Trans>Reset document preferences</Trans>
|
||||||
|
</DialogTitle>
|
||||||
|
|
||||||
|
<DialogDescription>
|
||||||
|
<Trans>
|
||||||
|
This will reset all document preferences to their default values and save the changes immediately.
|
||||||
|
</Trans>
|
||||||
|
</DialogDescription>
|
||||||
|
</DialogHeader>
|
||||||
|
|
||||||
|
<Alert variant="warning">
|
||||||
|
<AlertDescription>
|
||||||
|
<p>
|
||||||
|
<Trans>Once confirmed, the following will be reset:</Trans>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<ul className="mt-0.5 list-inside list-disc">
|
||||||
|
{showDocumentVisibility && (
|
||||||
|
<li>
|
||||||
|
<Trans>Default document visibility</Trans>
|
||||||
|
</li>
|
||||||
|
)}
|
||||||
|
<li>
|
||||||
|
<Trans>Default document language</Trans>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<Trans>Default date format</Trans>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<Trans>Default time zone</Trans>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<Trans>Default signature settings</Trans>
|
||||||
|
</li>
|
||||||
|
{showIncludeSenderDetails && (
|
||||||
|
<li>
|
||||||
|
<Trans>Send on behalf of team</Trans>
|
||||||
|
</li>
|
||||||
|
)}
|
||||||
|
<li>
|
||||||
|
<Trans>Include the signing certificate in the document</Trans>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<Trans>Include the audit logs in the document</Trans>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<Trans>Default recipients</Trans>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<Trans>Delegate document ownership</Trans>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<Trans>Default envelope expiration</Trans>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<Trans>Default signing reminders</Trans>
|
||||||
|
</li>
|
||||||
|
{showAiFeatures && (
|
||||||
|
<li>
|
||||||
|
<Trans>AI features</Trans>
|
||||||
|
</li>
|
||||||
|
)}
|
||||||
|
</ul>
|
||||||
|
</AlertDescription>
|
||||||
|
</Alert>
|
||||||
|
|
||||||
|
<DialogFooter>
|
||||||
|
<DialogClose asChild>
|
||||||
|
<Button type="button" variant="secondary" disabled={isLoading}>
|
||||||
|
<Trans>Cancel</Trans>
|
||||||
|
</Button>
|
||||||
|
</DialogClose>
|
||||||
|
|
||||||
|
<Button type="button" variant="destructive" loading={isLoading} onClick={() => void handleResetToDefaults()}>
|
||||||
|
<Trans>Reset to defaults</Trans>
|
||||||
|
</Button>
|
||||||
|
</DialogFooter>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -11,10 +11,10 @@ import { isValidLanguageCode, SUPPORTED_LANGUAGE_CODES, SUPPORTED_LANGUAGES } fr
|
|||||||
import { TIME_ZONES } from '@documenso/lib/constants/time-zones';
|
import { TIME_ZONES } from '@documenso/lib/constants/time-zones';
|
||||||
import type { TDefaultRecipients } from '@documenso/lib/types/default-recipients';
|
import type { TDefaultRecipients } from '@documenso/lib/types/default-recipients';
|
||||||
import { ZDefaultRecipientsSchema } from '@documenso/lib/types/default-recipients';
|
import { ZDefaultRecipientsSchema } from '@documenso/lib/types/default-recipients';
|
||||||
import { type TDocumentMetaDateFormat, ZDocumentMetaTimezoneSchema } from '@documenso/lib/types/document-meta';
|
import { type TDocumentMetaDateFormat, ZDocumentMetaDateFormatSchema } from '@documenso/lib/types/document-meta';
|
||||||
import { isPersonalLayout } from '@documenso/lib/utils/organisations';
|
import { generateDefaultOrganisationSettings, isPersonalLayout } from '@documenso/lib/utils/organisations';
|
||||||
import { recipientAbbreviation } from '@documenso/lib/utils/recipient-formatter';
|
import { recipientAbbreviation } from '@documenso/lib/utils/recipient-formatter';
|
||||||
import { extractTeamSignatureSettings } from '@documenso/lib/utils/teams';
|
import { extractTeamSignatureSettings, generateDefaultTeamSettings } from '@documenso/lib/utils/teams';
|
||||||
import { DocumentSignatureSettingsTooltip } from '@documenso/ui/components/document/document-signature-settings-tooltip';
|
import { DocumentSignatureSettingsTooltip } from '@documenso/ui/components/document/document-signature-settings-tooltip';
|
||||||
import { ExpirationPeriodPicker } from '@documenso/ui/components/document/expiration-period-picker';
|
import { ExpirationPeriodPicker } from '@documenso/ui/components/document/expiration-period-picker';
|
||||||
import { ReminderSettingsPicker } from '@documenso/ui/components/document/reminder-settings-picker';
|
import { ReminderSettingsPicker } from '@documenso/ui/components/document/reminder-settings-picker';
|
||||||
@@ -37,11 +37,11 @@ import { zodResolver } from '@hookform/resolvers/zod';
|
|||||||
import { msg, t } from '@lingui/core/macro';
|
import { msg, t } from '@lingui/core/macro';
|
||||||
import { useLingui } from '@lingui/react';
|
import { useLingui } from '@lingui/react';
|
||||||
import { Trans } from '@lingui/react/macro';
|
import { Trans } from '@lingui/react/macro';
|
||||||
import type { TeamGlobalSettings } from '@prisma/client';
|
import { DocumentVisibility, OrganisationType, type RecipientRole, type TeamGlobalSettings } from '@prisma/client';
|
||||||
import { DocumentVisibility, OrganisationType, type RecipientRole } from '@prisma/client';
|
|
||||||
import { useForm } from 'react-hook-form';
|
import { useForm } from 'react-hook-form';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
|
import { DocumentPreferencesResetDialog } from '~/components/dialogs/document-preferences-reset-dialog';
|
||||||
import { useOptionalCurrentTeam } from '~/providers/team';
|
import { useOptionalCurrentTeam } from '~/providers/team';
|
||||||
|
|
||||||
import { DefaultRecipientsMultiSelectCombobox } from '../general/default-recipients-multiselect-combobox';
|
import { DefaultRecipientsMultiSelectCombobox } from '../general/default-recipients-multiselect-combobox';
|
||||||
@@ -93,6 +93,26 @@ export type DocumentPreferencesFormProps = {
|
|||||||
onFormSubmit: (data: TDocumentPreferencesFormSchema) => Promise<void>;
|
onFormSubmit: (data: TDocumentPreferencesFormSchema) => Promise<void>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getDocumentPreferencesFormValues = (settings: SettingsSubset): TDocumentPreferencesFormSchema => {
|
||||||
|
const parsedDocumentDateFormat = ZDocumentMetaDateFormatSchema.safeParse(settings.documentDateFormat);
|
||||||
|
|
||||||
|
return {
|
||||||
|
documentVisibility: settings.documentVisibility,
|
||||||
|
documentLanguage: isValidLanguageCode(settings.documentLanguage) ? settings.documentLanguage : null,
|
||||||
|
documentTimezone: settings.documentTimezone,
|
||||||
|
documentDateFormat: parsedDocumentDateFormat.success ? parsedDocumentDateFormat.data : null,
|
||||||
|
includeSenderDetails: settings.includeSenderDetails,
|
||||||
|
includeSigningCertificate: settings.includeSigningCertificate,
|
||||||
|
includeAuditLog: settings.includeAuditLog,
|
||||||
|
signatureTypes: extractTeamSignatureSettings({ ...settings }),
|
||||||
|
defaultRecipients: settings.defaultRecipients ? ZDefaultRecipientsSchema.parse(settings.defaultRecipients) : null,
|
||||||
|
delegateDocumentOwnership: settings.delegateDocumentOwnership,
|
||||||
|
aiFeaturesEnabled: settings.aiFeaturesEnabled,
|
||||||
|
envelopeExpirationPeriod: settings.envelopeExpirationPeriod ?? null,
|
||||||
|
reminderSettings: settings.reminderSettings ?? null,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
export const DocumentPreferencesForm = ({
|
export const DocumentPreferencesForm = ({
|
||||||
settings,
|
settings,
|
||||||
onFormSubmit,
|
onFormSubmit,
|
||||||
@@ -113,7 +133,7 @@ export const DocumentPreferencesForm = ({
|
|||||||
documentVisibility: z.nativeEnum(DocumentVisibility).nullable(),
|
documentVisibility: z.nativeEnum(DocumentVisibility).nullable(),
|
||||||
documentLanguage: z.enum(SUPPORTED_LANGUAGE_CODES).nullable(),
|
documentLanguage: z.enum(SUPPORTED_LANGUAGE_CODES).nullable(),
|
||||||
documentTimezone: z.string().nullable(),
|
documentTimezone: z.string().nullable(),
|
||||||
documentDateFormat: ZDocumentMetaTimezoneSchema.nullable(),
|
documentDateFormat: ZDocumentMetaDateFormatSchema.nullable(),
|
||||||
includeSenderDetails: z.boolean().nullable(),
|
includeSenderDetails: z.boolean().nullable(),
|
||||||
includeSigningCertificate: z.boolean().nullable(),
|
includeSigningCertificate: z.boolean().nullable(),
|
||||||
includeAuditLog: z.boolean().nullable(),
|
includeAuditLog: z.boolean().nullable(),
|
||||||
@@ -127,26 +147,27 @@ export const DocumentPreferencesForm = ({
|
|||||||
reminderSettings: ZEnvelopeReminderSettings.nullable(),
|
reminderSettings: ZEnvelopeReminderSettings.nullable(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const defaultValues = getDocumentPreferencesFormValues(settings);
|
||||||
|
const defaultSettings = canInherit ? generateDefaultTeamSettings() : generateDefaultOrganisationSettings();
|
||||||
|
const baseResetValues = getDocumentPreferencesFormValues(defaultSettings);
|
||||||
|
const resetValues = {
|
||||||
|
...baseResetValues,
|
||||||
|
aiFeaturesEnabled: isAiFeaturesConfigured ? baseResetValues.aiFeaturesEnabled : defaultValues.aiFeaturesEnabled,
|
||||||
|
};
|
||||||
|
|
||||||
const form = useForm<TDocumentPreferencesFormSchema>({
|
const form = useForm<TDocumentPreferencesFormSchema>({
|
||||||
defaultValues: {
|
defaultValues,
|
||||||
documentVisibility: settings.documentVisibility,
|
|
||||||
documentLanguage: isValidLanguageCode(settings.documentLanguage) ? settings.documentLanguage : null,
|
|
||||||
documentTimezone: settings.documentTimezone,
|
|
||||||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
||||||
documentDateFormat: settings.documentDateFormat as TDocumentMetaDateFormat | null,
|
|
||||||
includeSenderDetails: settings.includeSenderDetails,
|
|
||||||
includeSigningCertificate: settings.includeSigningCertificate,
|
|
||||||
includeAuditLog: settings.includeAuditLog,
|
|
||||||
signatureTypes: extractTeamSignatureSettings({ ...settings }),
|
|
||||||
defaultRecipients: settings.defaultRecipients ? ZDefaultRecipientsSchema.parse(settings.defaultRecipients) : null,
|
|
||||||
delegateDocumentOwnership: settings.delegateDocumentOwnership,
|
|
||||||
aiFeaturesEnabled: settings.aiFeaturesEnabled,
|
|
||||||
envelopeExpirationPeriod: settings.envelopeExpirationPeriod ?? null,
|
|
||||||
reminderSettings: settings.reminderSettings ?? null,
|
|
||||||
},
|
|
||||||
resolver: zodResolver(ZDocumentPreferencesFormSchema),
|
resolver: zodResolver(ZDocumentPreferencesFormSchema),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const currentValues = form.watch();
|
||||||
|
const isResetDisabled = !form.formState.isDirty && JSON.stringify(currentValues) === JSON.stringify(resetValues);
|
||||||
|
|
||||||
|
const handleResetToDefaults = async () => {
|
||||||
|
await onFormSubmit(resetValues);
|
||||||
|
form.reset(resetValues);
|
||||||
|
};
|
||||||
|
|
||||||
const handleFormSubmit = form.handleSubmit(async (data) => {
|
const handleFormSubmit = form.handleSubmit(async (data) => {
|
||||||
try {
|
try {
|
||||||
await onFormSubmit(data);
|
await onFormSubmit(data);
|
||||||
@@ -772,6 +793,16 @@ export const DocumentPreferencesForm = ({
|
|||||||
isDirty={form.formState.isDirty}
|
isDirty={form.formState.isDirty}
|
||||||
isSubmitting={form.formState.isSubmitting}
|
isSubmitting={form.formState.isSubmitting}
|
||||||
onReset={() => form.reset()}
|
onReset={() => form.reset()}
|
||||||
|
resetToDefaults={
|
||||||
|
<DocumentPreferencesResetDialog
|
||||||
|
disabled={isResetDisabled}
|
||||||
|
isSubmitting={form.formState.isSubmitting}
|
||||||
|
onReset={handleResetToDefaults}
|
||||||
|
showAiFeatures={isAiFeaturesConfigured}
|
||||||
|
showDocumentVisibility={!isPersonalLayoutMode}
|
||||||
|
showIncludeSenderDetails={!isPersonalLayoutMode && !isPersonalOrganisation}
|
||||||
|
/>
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@@ -3,12 +3,17 @@ import { Button } from '@documenso/ui/primitives/button';
|
|||||||
import { Trans, useLingui } from '@lingui/react/macro';
|
import { Trans, useLingui } from '@lingui/react/macro';
|
||||||
import { AnimatePresence, motion } from 'framer-motion';
|
import { AnimatePresence, motion } from 'framer-motion';
|
||||||
import { AlertTriangleIcon } from 'lucide-react';
|
import { AlertTriangleIcon } from 'lucide-react';
|
||||||
import { useEffect, useRef, useState } from 'react';
|
import { type ReactNode, useEffect, useRef, useState } from 'react';
|
||||||
|
|
||||||
export type FormStickySaveBarProps = {
|
export type FormStickySaveBarProps = {
|
||||||
isDirty: boolean;
|
isDirty: boolean;
|
||||||
isSubmitting: boolean;
|
isSubmitting: boolean;
|
||||||
onReset: () => void;
|
onReset: () => void;
|
||||||
|
/**
|
||||||
|
* Slot for a "reset to defaults" action, rendered after the Save button. Only shown
|
||||||
|
* while the form is unchanged so it never competes with the Undo/unsaved-changes UI.
|
||||||
|
*/
|
||||||
|
resetToDefaults?: ReactNode;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -24,7 +29,7 @@ export type FormStickySaveBarProps = {
|
|||||||
* shared-layout morph). A 1px sentinel below it detects the stuck state so we can toggle
|
* shared-layout morph). A 1px sentinel below it detects the stuck state so we can toggle
|
||||||
* the pill chrome.
|
* the pill chrome.
|
||||||
*/
|
*/
|
||||||
export const FormStickySaveBar = ({ isDirty, isSubmitting, onReset }: FormStickySaveBarProps) => {
|
export const FormStickySaveBar = ({ isDirty, isSubmitting, onReset, resetToDefaults }: FormStickySaveBarProps) => {
|
||||||
const { t } = useLingui();
|
const { t } = useLingui();
|
||||||
|
|
||||||
const sentinelRef = useRef<HTMLDivElement>(null);
|
const sentinelRef = useRef<HTMLDivElement>(null);
|
||||||
@@ -109,6 +114,8 @@ export const FormStickySaveBar = ({ isDirty, isSubmitting, onReset }: FormSticky
|
|||||||
<Button type="submit" className="shrink-0" size="sm" loading={isSubmitting} disabled={!isDirty}>
|
<Button type="submit" className="shrink-0" size="sm" loading={isSubmitting} disabled={!isDirty}>
|
||||||
<Trans>Save changes</Trans>
|
<Trans>Save changes</Trans>
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
|
{!isDirty && resetToDefaults}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -96,7 +96,7 @@ export const SignUpForm = ({
|
|||||||
password: '',
|
password: '',
|
||||||
signature: '',
|
signature: '',
|
||||||
},
|
},
|
||||||
mode: 'onChange',
|
mode: 'onBlur',
|
||||||
resolver: zodResolver(ZSignUpFormSchema),
|
resolver: zodResolver(ZSignUpFormSchema),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -88,7 +88,7 @@ export default function OrganisationSettingsDocumentPage() {
|
|||||||
typedSignatureEnabled: signatureTypes.includes(DocumentSignatureType.TYPE),
|
typedSignatureEnabled: signatureTypes.includes(DocumentSignatureType.TYPE),
|
||||||
uploadSignatureEnabled: signatureTypes.includes(DocumentSignatureType.UPLOAD),
|
uploadSignatureEnabled: signatureTypes.includes(DocumentSignatureType.UPLOAD),
|
||||||
drawSignatureEnabled: signatureTypes.includes(DocumentSignatureType.DRAW),
|
drawSignatureEnabled: signatureTypes.includes(DocumentSignatureType.DRAW),
|
||||||
delegateDocumentOwnership: delegateDocumentOwnership,
|
delegateDocumentOwnership,
|
||||||
aiFeaturesEnabled,
|
aiFeaturesEnabled,
|
||||||
envelopeExpirationPeriod: envelopeExpirationPeriod ?? undefined,
|
envelopeExpirationPeriod: envelopeExpirationPeriod ?? undefined,
|
||||||
reminderSettings: reminderSettings ?? undefined,
|
reminderSettings: reminderSettings ?? undefined,
|
||||||
|
|||||||
@@ -82,7 +82,7 @@ export default function TeamsSettingsPage() {
|
|||||||
uploadSignatureEnabled: signatureTypes.includes(DocumentSignatureType.UPLOAD),
|
uploadSignatureEnabled: signatureTypes.includes(DocumentSignatureType.UPLOAD),
|
||||||
drawSignatureEnabled: signatureTypes.includes(DocumentSignatureType.DRAW),
|
drawSignatureEnabled: signatureTypes.includes(DocumentSignatureType.DRAW),
|
||||||
}),
|
}),
|
||||||
delegateDocumentOwnership: delegateDocumentOwnership,
|
delegateDocumentOwnership,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user