feat(ee): page verification workflow (#2102)

* feat: page verification workflow

* feat: refactor page-verification

* sync

* fix type

* fix

* fix

* notification icon

* use full word

* accept .license file

* - update templates
- update migration and notification

* fix copy

* update audit labels

* sync

* add space name
This commit is contained in:
Philip Okugbe
2026-04-13 20:20:34 +01:00
committed by GitHub
parent d6068310b4
commit bd68e47e03
50 changed files with 3828 additions and 58 deletions
@@ -71,6 +71,12 @@ export enum QueueJob {
PAGE_MENTION_NOTIFICATION = 'page-mention-notification',
PAGE_PERMISSION_GRANTED = 'page-permission-granted',
PAGE_UPDATE_DIGEST = 'page-update-digest',
PAGE_VERIFICATION_EXPIRING = 'page-verification-expiring',
PAGE_VERIFICATION_EXPIRED = 'page-verification-expired',
VERIFICATION_RECONCILE = 'verification-reconcile',
PAGE_VERIFIED_NOTIFICATION = 'page-verified-notification',
PAGE_APPROVAL_REQUESTED_NOTIFICATION = 'page-approval-requested-notification',
PAGE_APPROVAL_REJECTED_NOTIFICATION = 'page-approval-rejected-notification',
AUDIT_LOG = 'audit-log',
AUDIT_CLEANUP = 'audit-cleanup',
@@ -76,3 +76,40 @@ export interface IPermissionGrantedNotificationJob {
actorId: string;
role: string;
}
export interface IVerificationExpiringNotificationJob {
verificationId: string;
}
export interface IVerificationExpiredNotificationJob {
verificationId: string;
}
export interface IVerificationReconcileJob {
// no payload
}
export interface IPageVerifiedNotificationJob {
pageId: string;
spaceId: string;
workspaceId: string;
actorId: string;
verifierIds: string[];
}
export interface IApprovalRequestedNotificationJob {
pageId: string;
spaceId: string;
workspaceId: string;
actorId: string;
verifierIds: string[];
}
export interface IApprovalRejectedNotificationJob {
pageId: string;
spaceId: string;
workspaceId: string;
actorId: string;
requestedById: string;
comment?: string;
}
@@ -0,0 +1,41 @@
import { Section, Text } from '@react-email/components';
import * as React from 'react';
import { content, paragraph } from '../css/styles';
import { EmailButton, MailBody } from '../partials/partials';
interface Props {
actorName: string;
pageTitle: string;
spaceName: string;
pageUrl: string;
comment?: string;
}
export const ApprovalRejectedEmail = ({
actorName,
pageTitle,
spaceName,
pageUrl,
comment,
}: Props) => {
return (
<MailBody>
<Section style={content}>
<Text style={paragraph}>Hi there,</Text>
<Text style={paragraph}>
<strong>{actorName}</strong> returned{' '}
<strong>{pageTitle}</strong> in the{' '}
<strong>{spaceName}</strong> space for revision.
</Text>
{comment && (
<Text style={{ ...paragraph, fontStyle: 'italic' }}>
&ldquo;{comment}&rdquo;
</Text>
)}
</Section>
<EmailButton href={pageUrl}>View page</EmailButton>
</MailBody>
);
};
export default ApprovalRejectedEmail;
@@ -0,0 +1,34 @@
import { Section, Text } from '@react-email/components';
import * as React from 'react';
import { content, paragraph } from '../css/styles';
import { EmailButton, MailBody } from '../partials/partials';
interface Props {
actorName: string;
pageTitle: string;
spaceName: string;
pageUrl: string;
}
export const ApprovalRequestedEmail = ({
actorName,
pageTitle,
spaceName,
pageUrl,
}: Props) => {
return (
<MailBody>
<Section style={content}>
<Text style={paragraph}>Hi there,</Text>
<Text style={paragraph}>
<strong>{actorName}</strong> submitted{' '}
<strong>{pageTitle}</strong> in the{' '}
<strong>{spaceName}</strong> space for your approval.
</Text>
</Section>
<EmailButton href={pageUrl}>Review page</EmailButton>
</MailBody>
);
};
export default ApprovalRequestedEmail;
@@ -27,7 +27,7 @@ export const PageUpdateEmail = ({
<Link href={pageUrl} style={link}>
<strong>{pageTitle}</strong>
</Link>{' '}
in <strong>{spaceName}</strong>.
in the <strong>{spaceName}</strong> space.
</Text>
</Section>
<EmailButton href={pageUrl}>View page</EmailButton>
@@ -0,0 +1,28 @@
import { Section, Text } from '@react-email/components';
import * as React from 'react';
import { content, paragraph } from '../css/styles';
import { EmailButton, MailBody } from '../partials/partials';
interface Props {
pageTitle: string;
spaceName: string;
pageUrl: string;
}
export const VerificationExpiredEmail = ({ pageTitle, spaceName, pageUrl }: Props) => {
return (
<MailBody>
<Section style={content}>
<Text style={paragraph}>Hi there,</Text>
<Text style={paragraph}>
The verification for <strong>{pageTitle}</strong> in the{' '}
<strong>{spaceName}</strong> space has expired. Please re-verify the
page to confirm it is still accurate.
</Text>
</Section>
<EmailButton href={pageUrl}>Re-verify page</EmailButton>
</MailBody>
);
};
export default VerificationExpiredEmail;
@@ -0,0 +1,34 @@
import { Section, Text } from '@react-email/components';
import * as React from 'react';
import { content, paragraph } from '../css/styles';
import { EmailButton, MailBody } from '../partials/partials';
interface Props {
pageTitle: string;
spaceName: string;
pageUrl: string;
expiresAt: string;
}
export const VerificationExpiringEmail = ({
pageTitle,
spaceName,
pageUrl,
expiresAt,
}: Props) => {
return (
<MailBody>
<Section style={content}>
<Text style={paragraph}>Hi there,</Text>
<Text style={paragraph}>
The page <strong>{pageTitle}</strong> in the{' '}
<strong>{spaceName}</strong> space needs to be re-verified. The
verification expires on <strong>{expiresAt}</strong>.
</Text>
</Section>
<EmailButton href={pageUrl}>Review page</EmailButton>
</MailBody>
);
};
export default VerificationExpiringEmail;