mirror of
https://github.com/documenso/documenso.git
synced 2026-06-22 04:12:06 +10:00
feat: add delegate document ownership option (#2272)
When using an API key created in a team context, the documents/templates’ owner always defaults to the team API token creator, rather than the actual uploader. For example, John creates the API key for the team "Lawyers". Tom and Maria use the API key to upload documents. All the uploaded documents are attributed to John. This makes it impossible to see who actually uploaded a document. The new feature allows users to enable document ownership delegation from the organization/team settings.
This commit is contained in:
@@ -81,6 +81,7 @@ export type CreateEnvelopeOptions = {
|
||||
globalActionAuth?: TDocumentActionAuthTypes[];
|
||||
recipients?: CreateEnvelopeRecipientOptions[];
|
||||
folderId?: string;
|
||||
delegatedDocumentOwner?: string;
|
||||
};
|
||||
attachments?: Array<{
|
||||
label: string;
|
||||
@@ -114,6 +115,7 @@ export const createEnvelope = async ({
|
||||
publicTitle,
|
||||
publicDescription,
|
||||
visibility: visibilityOverride,
|
||||
delegatedDocumentOwner,
|
||||
} = data;
|
||||
|
||||
const team = await prisma.team.findFirst({
|
||||
@@ -256,6 +258,43 @@ export const createEnvelope = async ({
|
||||
? await incrementDocumentId().then((v) => v.formattedDocumentId)
|
||||
: await incrementTemplateId().then((v) => v.formattedTemplateId);
|
||||
|
||||
const getValidatedDelegatedOwner = async () => {
|
||||
if (
|
||||
!settings.delegateDocumentOwnership ||
|
||||
!delegatedDocumentOwner ||
|
||||
requestMetadata.source === 'app'
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const delegatedOwner = await prisma.user.findFirst({
|
||||
where: {
|
||||
email: delegatedDocumentOwner,
|
||||
},
|
||||
});
|
||||
|
||||
if (!delegatedOwner) {
|
||||
throw new AppError(AppErrorCode.UNAUTHORIZED, {
|
||||
message: 'Delegated document owner must be a member of the team',
|
||||
});
|
||||
}
|
||||
|
||||
const isTeamMember = await prisma.team.findFirst({
|
||||
where: buildTeamWhereQuery({ teamId, userId: delegatedOwner.id }),
|
||||
});
|
||||
|
||||
if (!isTeamMember) {
|
||||
throw new AppError(AppErrorCode.UNAUTHORIZED, {
|
||||
message: 'Delegated document owner must be a member of the team',
|
||||
});
|
||||
}
|
||||
|
||||
return delegatedOwner;
|
||||
};
|
||||
|
||||
const delegatedOwner = await getValidatedDelegatedOwner();
|
||||
const envelopeOwnerId = delegatedOwner?.id ?? userId;
|
||||
|
||||
return await prisma.$transaction(async (tx) => {
|
||||
const envelope = await tx.envelope.create({
|
||||
data: {
|
||||
@@ -285,7 +324,7 @@ export const createEnvelope = async ({
|
||||
})),
|
||||
},
|
||||
},
|
||||
userId,
|
||||
userId: envelopeOwnerId,
|
||||
teamId,
|
||||
authOptions,
|
||||
visibility,
|
||||
@@ -393,6 +432,9 @@ export const createEnvelope = async ({
|
||||
data: createDocumentAuditLogData({
|
||||
type: DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_CREATED,
|
||||
envelopeId: envelope.id,
|
||||
user: {
|
||||
id: envelopeOwnerId,
|
||||
},
|
||||
metadata: requestMetadata,
|
||||
data: {
|
||||
title,
|
||||
@@ -403,6 +445,25 @@ export const createEnvelope = async ({
|
||||
}),
|
||||
});
|
||||
|
||||
// Create audit log for delegated owner if validation passed
|
||||
if (delegatedOwner) {
|
||||
await tx.documentAuditLog.create({
|
||||
data: createDocumentAuditLogData({
|
||||
type: DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_DELEGATED_OWNER_CREATED,
|
||||
envelopeId: envelope.id,
|
||||
user: {
|
||||
id: userId,
|
||||
},
|
||||
metadata: requestMetadata,
|
||||
data: {
|
||||
delegatedOwnerName: delegatedOwner.name,
|
||||
delegatedOwnerEmail: delegatedOwner.email,
|
||||
teamName: team.name,
|
||||
},
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
await triggerWebhook({
|
||||
event: WebhookTriggerEvents.DOCUMENT_CREATED,
|
||||
data: ZWebhookDocumentSchema.parse(mapEnvelopeToWebhookDocumentPayload(createdEnvelope)),
|
||||
|
||||
@@ -44,6 +44,7 @@ export const ZDocumentAuditLogTypeSchema = z.enum([
|
||||
'DOCUMENT_TITLE_UPDATED', // When the document title is updated.
|
||||
'DOCUMENT_EXTERNAL_ID_UPDATED', // When the document external ID is updated.
|
||||
'DOCUMENT_MOVED_TO_TEAM', // When the document is moved to a team.
|
||||
'DOCUMENT_DELEGATED_OWNER_CREATED', // When the document delegated owner is created.
|
||||
|
||||
// ACCESS AUTH 2FA events.
|
||||
'DOCUMENT_ACCESS_AUTH_2FA_REQUESTED', // When ACCESS AUTH 2FA is requested.
|
||||
@@ -681,6 +682,18 @@ export const ZDocumentAuditLogEventDocumentMovedToTeamSchema = z.object({
|
||||
}),
|
||||
});
|
||||
|
||||
/**
|
||||
* Event: Document delegated owner created.
|
||||
*/
|
||||
export const ZDocumentAuditLogEventDocumentDelegatedOwnerCreatedSchema = z.object({
|
||||
type: z.literal(DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_DELEGATED_OWNER_CREATED),
|
||||
data: z.object({
|
||||
delegatedOwnerName: z.string().nullable(),
|
||||
delegatedOwnerEmail: z.string(),
|
||||
teamName: z.string(),
|
||||
}),
|
||||
});
|
||||
|
||||
export const ZDocumentAuditLogBaseSchema = z.object({
|
||||
id: z.string(),
|
||||
createdAt: z.date(),
|
||||
@@ -701,6 +714,7 @@ export const ZDocumentAuditLogSchema = ZDocumentAuditLogBaseSchema.and(
|
||||
ZDocumentAuditLogEventDocumentCreatedSchema,
|
||||
ZDocumentAuditLogEventDocumentDeletedSchema,
|
||||
ZDocumentAuditLogEventDocumentMovedToTeamSchema,
|
||||
ZDocumentAuditLogEventDocumentDelegatedOwnerCreatedSchema,
|
||||
ZDocumentAuditLogEventDocumentFieldsAutoInsertedSchema,
|
||||
ZDocumentAuditLogEventDocumentFieldInsertedSchema,
|
||||
ZDocumentAuditLogEventDocumentFieldUninsertedSchema,
|
||||
|
||||
@@ -530,6 +530,13 @@ export const formatDocumentAuditLogAction = (
|
||||
anonymous: msg`Envelope item deleted`,
|
||||
identified: msg`${prefix} deleted an envelope item with title ${data.envelopeItemTitle}`,
|
||||
}))
|
||||
.with({ type: DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_DELEGATED_OWNER_CREATED }, ({ data }) => ({
|
||||
anonymous: msg({
|
||||
message: `Document ownership delegated`,
|
||||
context: `Audit log format`,
|
||||
}),
|
||||
identified: msg`The document ownership was delegated to ${data.delegatedOwnerName || data.delegatedOwnerEmail} on behalf of ${data.teamName}`,
|
||||
}))
|
||||
.exhaustive();
|
||||
|
||||
return {
|
||||
|
||||
@@ -117,6 +117,7 @@ export const generateDefaultOrganisationSettings = (): Omit<
|
||||
documentLanguage: 'en',
|
||||
documentTimezone: null, // Null means local timezone.
|
||||
documentDateFormat: DEFAULT_DOCUMENT_DATE_FORMAT,
|
||||
delegateDocumentOwnership: false,
|
||||
|
||||
includeSenderDetails: true,
|
||||
includeSigningCertificate: true,
|
||||
|
||||
@@ -184,6 +184,7 @@ export const generateDefaultTeamSettings = (): Omit<TeamGlobalSettings, 'id' | '
|
||||
documentLanguage: null,
|
||||
documentTimezone: null,
|
||||
documentDateFormat: null,
|
||||
delegateDocumentOwnership: null,
|
||||
|
||||
includeSenderDetails: null,
|
||||
includeSigningCertificate: null,
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "OrganisationGlobalSettings" ADD COLUMN "delegateDocumentOwnership" BOOLEAN NOT NULL DEFAULT false;
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "TeamGlobalSettings" ADD COLUMN "delegateDocumentOwnership" BOOLEAN;
|
||||
@@ -818,6 +818,7 @@ model OrganisationGlobalSettings {
|
||||
includeAuditLog Boolean @default(false)
|
||||
documentTimezone String? // Nullable to allow using local timezones if not set.
|
||||
documentDateFormat String @default("yyyy-MM-dd hh:mm a")
|
||||
delegateDocumentOwnership Boolean @default(false)
|
||||
|
||||
typedSignatureEnabled Boolean @default(true)
|
||||
uploadSignatureEnabled Boolean @default(true)
|
||||
@@ -844,10 +845,11 @@ model TeamGlobalSettings {
|
||||
id String @id
|
||||
team Team?
|
||||
|
||||
documentVisibility DocumentVisibility?
|
||||
documentLanguage String?
|
||||
documentTimezone String?
|
||||
documentDateFormat String?
|
||||
documentVisibility DocumentVisibility?
|
||||
documentLanguage String?
|
||||
documentTimezone String?
|
||||
documentDateFormat String?
|
||||
delegateDocumentOwnership Boolean?
|
||||
|
||||
includeSenderDetails Boolean?
|
||||
includeSigningCertificate Boolean?
|
||||
|
||||
@@ -32,6 +32,7 @@ export const createEnvelopeRoute = authenticatedProcedure
|
||||
folderId,
|
||||
meta,
|
||||
attachments,
|
||||
delegatedDocumentOwner,
|
||||
} = payload;
|
||||
|
||||
ctx.logger.info({
|
||||
@@ -144,6 +145,7 @@ export const createEnvelopeRoute = authenticatedProcedure
|
||||
recipients: recipientsToCreate,
|
||||
folderId,
|
||||
envelopeItems,
|
||||
delegatedDocumentOwner,
|
||||
},
|
||||
attachments,
|
||||
meta,
|
||||
|
||||
@@ -41,6 +41,11 @@ export const createEnvelopeMeta: TrpcRouteMeta = {
|
||||
export const ZCreateEnvelopePayloadSchema = z.object({
|
||||
title: ZDocumentTitleSchema,
|
||||
type: z.nativeEnum(EnvelopeType),
|
||||
delegatedDocumentOwner: z
|
||||
.string()
|
||||
.email()
|
||||
.describe('The email of the user who will own the document.')
|
||||
.optional(),
|
||||
externalId: ZDocumentExternalIdSchema.optional(),
|
||||
visibility: ZDocumentVisibilitySchema.optional(),
|
||||
globalAccessAuth: z.array(ZDocumentAccessAuthTypesSchema).optional(),
|
||||
|
||||
@@ -36,6 +36,7 @@ export const updateOrganisationSettingsRoute = authenticatedProcedure
|
||||
typedSignatureEnabled,
|
||||
uploadSignatureEnabled,
|
||||
drawSignatureEnabled,
|
||||
delegateDocumentOwnership,
|
||||
|
||||
// Branding related settings.
|
||||
brandingEnabled,
|
||||
@@ -99,6 +100,10 @@ export const updateOrganisationSettingsRoute = authenticatedProcedure
|
||||
const derivedDrawSignatureEnabled =
|
||||
drawSignatureEnabled ?? organisation.organisationGlobalSettings.drawSignatureEnabled;
|
||||
|
||||
const derivedDelegateDocumentOwnership =
|
||||
delegateDocumentOwnership ??
|
||||
organisation.organisationGlobalSettings.delegateDocumentOwnership;
|
||||
|
||||
if (
|
||||
derivedTypedSignatureEnabled === false &&
|
||||
derivedUploadSignatureEnabled === false &&
|
||||
@@ -140,6 +145,7 @@ export const updateOrganisationSettingsRoute = authenticatedProcedure
|
||||
typedSignatureEnabled,
|
||||
uploadSignatureEnabled,
|
||||
drawSignatureEnabled,
|
||||
delegateDocumentOwnership: derivedDelegateDocumentOwnership,
|
||||
|
||||
// Branding related settings.
|
||||
brandingEnabled,
|
||||
|
||||
@@ -22,6 +22,7 @@ export const ZUpdateOrganisationSettingsRequestSchema = z.object({
|
||||
typedSignatureEnabled: z.boolean().optional(),
|
||||
uploadSignatureEnabled: z.boolean().optional(),
|
||||
drawSignatureEnabled: z.boolean().optional(),
|
||||
delegateDocumentOwnership: z.boolean().nullish(),
|
||||
|
||||
// Branding related settings.
|
||||
brandingEnabled: z.boolean().optional(),
|
||||
|
||||
@@ -39,6 +39,7 @@ export const updateTeamSettingsRoute = authenticatedProcedure
|
||||
typedSignatureEnabled,
|
||||
uploadSignatureEnabled,
|
||||
drawSignatureEnabled,
|
||||
delegateDocumentOwnership,
|
||||
|
||||
// Branding related settings.
|
||||
brandingEnabled,
|
||||
@@ -150,6 +151,7 @@ export const updateTeamSettingsRoute = authenticatedProcedure
|
||||
typedSignatureEnabled,
|
||||
uploadSignatureEnabled,
|
||||
drawSignatureEnabled,
|
||||
delegateDocumentOwnership,
|
||||
|
||||
// Branding related settings.
|
||||
brandingEnabled,
|
||||
|
||||
@@ -26,6 +26,7 @@ export const ZUpdateTeamSettingsRequestSchema = z.object({
|
||||
typedSignatureEnabled: z.boolean().nullish(),
|
||||
uploadSignatureEnabled: z.boolean().nullish(),
|
||||
drawSignatureEnabled: z.boolean().nullish(),
|
||||
delegateDocumentOwnership: z.boolean().nullish(),
|
||||
|
||||
// Branding related settings.
|
||||
brandingEnabled: z.boolean().nullish(),
|
||||
|
||||
@@ -626,7 +626,7 @@ export const AddFieldsFormPartial = ({
|
||||
{selectedField && (
|
||||
<div
|
||||
className={cn(
|
||||
'text-muted-foreground dark:text-muted-background pointer-events-none fixed z-50 flex cursor-pointer flex-col items-center justify-center rounded-[2px] bg-white ring-2 transition duration-200 [container-type:size]',
|
||||
'dark:text-muted-background pointer-events-none fixed z-50 flex cursor-pointer flex-col items-center justify-center rounded-[2px] bg-white text-muted-foreground ring-2 transition duration-200 [container-type:size]',
|
||||
selectedSignerStyles?.base,
|
||||
{
|
||||
'-rotate-6 scale-90 opacity-50 dark:bg-black/20': !isFieldWithinBounds,
|
||||
@@ -728,7 +728,7 @@ export const AddFieldsFormPartial = ({
|
||||
<CardContent className="flex flex-col items-center justify-center px-6 py-4">
|
||||
<p
|
||||
className={cn(
|
||||
'text-muted-foreground group-data-[selected]:text-foreground font-signature flex items-center justify-center gap-x-1.5 text-lg font-normal',
|
||||
'flex items-center justify-center gap-x-1.5 font-signature text-lg font-normal text-muted-foreground group-data-[selected]:text-foreground',
|
||||
)}
|
||||
>
|
||||
<Trans>Signature</Trans>
|
||||
@@ -752,7 +752,7 @@ export const AddFieldsFormPartial = ({
|
||||
<CardContent className="flex flex-col items-center justify-center px-6 py-4">
|
||||
<p
|
||||
className={cn(
|
||||
'text-muted-foreground group-data-[selected]:text-foreground flex items-center justify-center gap-x-1.5 text-sm font-normal',
|
||||
'flex items-center justify-center gap-x-1.5 text-sm font-normal text-muted-foreground group-data-[selected]:text-foreground',
|
||||
)}
|
||||
>
|
||||
<Contact className="h-4 w-4" />
|
||||
@@ -777,7 +777,7 @@ export const AddFieldsFormPartial = ({
|
||||
<CardContent className="flex flex-col items-center justify-center px-6 py-4">
|
||||
<p
|
||||
className={cn(
|
||||
'text-muted-foreground group-data-[selected]:text-foreground flex items-center justify-center gap-x-1.5 text-sm font-normal',
|
||||
'flex items-center justify-center gap-x-1.5 text-sm font-normal text-muted-foreground group-data-[selected]:text-foreground',
|
||||
)}
|
||||
>
|
||||
<Mail className="h-4 w-4" />
|
||||
@@ -802,7 +802,7 @@ export const AddFieldsFormPartial = ({
|
||||
<CardContent className="p-4">
|
||||
<p
|
||||
className={cn(
|
||||
'text-muted-foreground group-data-[selected]:text-foreground flex items-center justify-center gap-x-1.5 text-sm font-normal',
|
||||
'flex items-center justify-center gap-x-1.5 text-sm font-normal text-muted-foreground group-data-[selected]:text-foreground',
|
||||
)}
|
||||
>
|
||||
<User className="h-4 w-4" />
|
||||
@@ -827,7 +827,7 @@ export const AddFieldsFormPartial = ({
|
||||
<CardContent className="p-4">
|
||||
<p
|
||||
className={cn(
|
||||
'text-muted-foreground group-data-[selected]:text-foreground flex items-center justify-center gap-x-1.5 text-sm font-normal',
|
||||
'flex items-center justify-center gap-x-1.5 text-sm font-normal text-muted-foreground group-data-[selected]:text-foreground',
|
||||
)}
|
||||
>
|
||||
<CalendarDays className="h-4 w-4" />
|
||||
@@ -852,7 +852,7 @@ export const AddFieldsFormPartial = ({
|
||||
<CardContent className="p-4">
|
||||
<p
|
||||
className={cn(
|
||||
'text-muted-foreground group-data-[selected]:text-foreground flex items-center justify-center gap-x-1.5 text-sm font-normal',
|
||||
'flex items-center justify-center gap-x-1.5 text-sm font-normal text-muted-foreground group-data-[selected]:text-foreground',
|
||||
)}
|
||||
>
|
||||
<Type className="h-4 w-4" />
|
||||
@@ -877,7 +877,7 @@ export const AddFieldsFormPartial = ({
|
||||
<CardContent className="p-4">
|
||||
<p
|
||||
className={cn(
|
||||
'text-muted-foreground group-data-[selected]:text-foreground flex items-center justify-center gap-x-1.5 text-sm font-normal',
|
||||
'flex items-center justify-center gap-x-1.5 text-sm font-normal text-muted-foreground group-data-[selected]:text-foreground',
|
||||
)}
|
||||
>
|
||||
<Hash className="h-4 w-4" />
|
||||
@@ -902,7 +902,7 @@ export const AddFieldsFormPartial = ({
|
||||
<CardContent className="p-4">
|
||||
<p
|
||||
className={cn(
|
||||
'text-muted-foreground group-data-[selected]:text-foreground flex items-center justify-center gap-x-1.5 text-sm font-normal',
|
||||
'flex items-center justify-center gap-x-1.5 text-sm font-normal text-muted-foreground group-data-[selected]:text-foreground',
|
||||
)}
|
||||
>
|
||||
<Disc className="h-4 w-4" />
|
||||
@@ -927,7 +927,7 @@ export const AddFieldsFormPartial = ({
|
||||
<CardContent className="p-4">
|
||||
<p
|
||||
className={cn(
|
||||
'text-muted-foreground group-data-[selected]:text-foreground flex items-center justify-center gap-x-1.5 text-sm font-normal',
|
||||
'flex items-center justify-center gap-x-1.5 text-sm font-normal text-muted-foreground group-data-[selected]:text-foreground',
|
||||
)}
|
||||
>
|
||||
<CheckSquare className="h-4 w-4" />
|
||||
@@ -953,7 +953,7 @@ export const AddFieldsFormPartial = ({
|
||||
<CardContent className="p-4">
|
||||
<p
|
||||
className={cn(
|
||||
'text-muted-foreground group-data-[selected]:text-foreground flex items-center justify-center gap-x-1.5 text-sm font-normal',
|
||||
'flex items-center justify-center gap-x-1.5 text-sm font-normal text-muted-foreground group-data-[selected]:text-foreground',
|
||||
)}
|
||||
>
|
||||
<ChevronDown className="h-4 w-4" />
|
||||
|
||||
@@ -581,7 +581,7 @@ export const AddTemplateFieldsFormPartial = ({
|
||||
{selectedField && (
|
||||
<div
|
||||
className={cn(
|
||||
'text-muted-foreground dark:text-muted-background pointer-events-none fixed z-50 flex cursor-pointer flex-col items-center justify-center rounded-[2px] bg-white ring-2 transition duration-200 [container-type:size]',
|
||||
'dark:text-muted-background pointer-events-none fixed z-50 flex cursor-pointer flex-col items-center justify-center rounded-[2px] bg-white text-muted-foreground ring-2 transition duration-200 [container-type:size]',
|
||||
selectedSignerStyles?.base,
|
||||
{
|
||||
'-rotate-6 scale-90 opacity-50 dark:bg-black/20': !isFieldWithinBounds,
|
||||
@@ -650,7 +650,7 @@ export const AddTemplateFieldsFormPartial = ({
|
||||
variant="outline"
|
||||
role="combobox"
|
||||
className={cn(
|
||||
'bg-background text-muted-foreground hover:text-foreground mb-12 mt-2 justify-between font-normal',
|
||||
'mb-12 mt-2 justify-between bg-background font-normal text-muted-foreground hover:text-foreground',
|
||||
selectedSignerStyles?.comboxBoxTrigger,
|
||||
)}
|
||||
>
|
||||
@@ -681,7 +681,7 @@ export const AddTemplateFieldsFormPartial = ({
|
||||
<CommandInput />
|
||||
|
||||
<CommandEmpty>
|
||||
<span className="text-muted-foreground inline-block px-4">
|
||||
<span className="inline-block px-4 text-muted-foreground">
|
||||
<Trans>No recipient matching this description was found.</Trans>
|
||||
</span>
|
||||
</CommandEmpty>
|
||||
@@ -689,14 +689,14 @@ export const AddTemplateFieldsFormPartial = ({
|
||||
{/* Note: This is duplicated in `add-fields.tsx` */}
|
||||
{recipientsByRoleToDisplay.map(([role, roleRecipients], roleIndex) => (
|
||||
<CommandGroup key={roleIndex}>
|
||||
<div className="text-muted-foreground mb-1 ml-2 mt-2 text-xs font-medium">
|
||||
<div className="mb-1 ml-2 mt-2 text-xs font-medium text-muted-foreground">
|
||||
{_(RECIPIENT_ROLES_DESCRIPTION[role].roleNamePlural)}
|
||||
</div>
|
||||
|
||||
{roleRecipients.length === 0 && (
|
||||
<div
|
||||
key={`${role}-empty`}
|
||||
className="text-muted-foreground/80 px-4 pb-4 pt-2.5 text-center text-xs"
|
||||
className="px-4 pb-4 pt-2.5 text-center text-xs text-muted-foreground/80"
|
||||
>
|
||||
<Trans>No recipients with this role</Trans>
|
||||
</div>
|
||||
@@ -720,7 +720,7 @@ export const AddTemplateFieldsFormPartial = ({
|
||||
}}
|
||||
>
|
||||
<span
|
||||
className={cn('text-foreground/70 truncate', {
|
||||
className={cn('truncate text-foreground/70', {
|
||||
'text-foreground/80': recipient === selectedSigner,
|
||||
})}
|
||||
>
|
||||
@@ -768,7 +768,7 @@ export const AddTemplateFieldsFormPartial = ({
|
||||
<CardContent className="flex flex-col items-center justify-center px-6 py-4">
|
||||
<p
|
||||
className={cn(
|
||||
'text-muted-foreground group-data-[selected]:text-foreground font-signature flex items-center justify-center gap-x-1.5 text-lg font-normal',
|
||||
'flex items-center justify-center gap-x-1.5 font-signature text-lg font-normal text-muted-foreground group-data-[selected]:text-foreground',
|
||||
)}
|
||||
>
|
||||
<Trans>Signature</Trans>
|
||||
@@ -793,7 +793,7 @@ export const AddTemplateFieldsFormPartial = ({
|
||||
<CardContent className="flex flex-col items-center justify-center px-6 py-4">
|
||||
<p
|
||||
className={cn(
|
||||
'text-muted-foreground group-data-[selected]:text-foreground flex items-center justify-center gap-x-1.5 text-sm font-normal',
|
||||
'flex items-center justify-center gap-x-1.5 text-sm font-normal text-muted-foreground group-data-[selected]:text-foreground',
|
||||
)}
|
||||
>
|
||||
<Contact className="h-4 w-4" />
|
||||
@@ -819,7 +819,7 @@ export const AddTemplateFieldsFormPartial = ({
|
||||
<CardContent className="p-4">
|
||||
<p
|
||||
className={cn(
|
||||
'text-muted-foreground group-data-[selected]:text-foreground flex items-center justify-center gap-x-1.5 text-sm font-normal',
|
||||
'flex items-center justify-center gap-x-1.5 text-sm font-normal text-muted-foreground group-data-[selected]:text-foreground',
|
||||
)}
|
||||
>
|
||||
<Mail className="h-4 w-4" />
|
||||
@@ -845,7 +845,7 @@ export const AddTemplateFieldsFormPartial = ({
|
||||
<CardContent className="p-4">
|
||||
<p
|
||||
className={cn(
|
||||
'text-muted-foreground group-data-[selected]:text-foreground flex items-center justify-center gap-x-1.5 text-sm font-normal',
|
||||
'flex items-center justify-center gap-x-1.5 text-sm font-normal text-muted-foreground group-data-[selected]:text-foreground',
|
||||
)}
|
||||
>
|
||||
<User className="h-4 w-4" />
|
||||
@@ -871,7 +871,7 @@ export const AddTemplateFieldsFormPartial = ({
|
||||
<CardContent className="p-4">
|
||||
<p
|
||||
className={cn(
|
||||
'text-muted-foreground group-data-[selected]:text-foreground flex items-center justify-center gap-x-1.5 text-sm font-normal',
|
||||
'flex items-center justify-center gap-x-1.5 text-sm font-normal text-muted-foreground group-data-[selected]:text-foreground',
|
||||
)}
|
||||
>
|
||||
<CalendarDays className="h-4 w-4" />
|
||||
@@ -897,7 +897,7 @@ export const AddTemplateFieldsFormPartial = ({
|
||||
<CardContent className="p-4">
|
||||
<p
|
||||
className={cn(
|
||||
'text-muted-foreground group-data-[selected]:text-foreground flex items-center justify-center gap-x-1.5 text-sm font-normal',
|
||||
'flex items-center justify-center gap-x-1.5 text-sm font-normal text-muted-foreground group-data-[selected]:text-foreground',
|
||||
)}
|
||||
>
|
||||
<Type className="h-4 w-4" />
|
||||
@@ -923,7 +923,7 @@ export const AddTemplateFieldsFormPartial = ({
|
||||
<CardContent className="p-4">
|
||||
<p
|
||||
className={cn(
|
||||
'text-muted-foreground group-data-[selected]:text-foreground flex items-center justify-center gap-x-1.5 text-sm font-normal',
|
||||
'flex items-center justify-center gap-x-1.5 text-sm font-normal text-muted-foreground group-data-[selected]:text-foreground',
|
||||
)}
|
||||
>
|
||||
<Hash className="h-4 w-4" />
|
||||
@@ -949,7 +949,7 @@ export const AddTemplateFieldsFormPartial = ({
|
||||
<CardContent className="p-4">
|
||||
<p
|
||||
className={cn(
|
||||
'text-muted-foreground group-data-[selected]:text-foreground flex items-center justify-center gap-x-1.5 text-sm font-normal',
|
||||
'flex items-center justify-center gap-x-1.5 text-sm font-normal text-muted-foreground group-data-[selected]:text-foreground',
|
||||
)}
|
||||
>
|
||||
<Disc className="h-4 w-4" />
|
||||
@@ -975,7 +975,7 @@ export const AddTemplateFieldsFormPartial = ({
|
||||
<CardContent className="p-4">
|
||||
<p
|
||||
className={cn(
|
||||
'text-muted-foreground group-data-[selected]:text-foreground flex items-center justify-center gap-x-1.5 text-sm font-normal',
|
||||
'flex items-center justify-center gap-x-1.5 text-sm font-normal text-muted-foreground group-data-[selected]:text-foreground',
|
||||
)}
|
||||
>
|
||||
<CheckSquare className="h-4 w-4" />
|
||||
@@ -1002,7 +1002,7 @@ export const AddTemplateFieldsFormPartial = ({
|
||||
<CardContent className="p-4">
|
||||
<p
|
||||
className={cn(
|
||||
'text-muted-foreground group-data-[selected]:text-foreground flex items-center justify-center gap-x-1.5 text-sm font-normal',
|
||||
'flex items-center justify-center gap-x-1.5 text-sm font-normal text-muted-foreground group-data-[selected]:text-foreground',
|
||||
)}
|
||||
>
|
||||
<ChevronDown className="h-4 w-4" />
|
||||
|
||||
Reference in New Issue
Block a user