fix(cms): render partner mail templates
This commit is contained in:
@@ -21,6 +21,9 @@ export const EMAIL_TEMPLATE_KEYS = [
|
|||||||
'waitlist',
|
'waitlist',
|
||||||
'event-reminder',
|
'event-reminder',
|
||||||
'application-acknowledgement',
|
'application-acknowledgement',
|
||||||
|
'partner-review-submitted',
|
||||||
|
'partner-review-decision',
|
||||||
|
'partner-review-published',
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
export type EmailTemplateKey = (typeof EMAIL_TEMPLATE_KEYS)[number];
|
export type EmailTemplateKey = (typeof EMAIL_TEMPLATE_KEYS)[number];
|
||||||
@@ -95,4 +98,34 @@ export const DEFAULT_EMAIL_TEMPLATES: Readonly<
|
|||||||
body: ACKNOWLEDGEMENT_WITH_CALL.en,
|
body: ACKNOWLEDGEMENT_WITH_CALL.en,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
'partner-review-submitted': {
|
||||||
|
ar: {
|
||||||
|
subject: 'مسودة الشريك جاهزة للمراجعة',
|
||||||
|
body: 'قدّم أحد المساهمين من الشركاء {label}.\n\nيمكنك مراجعته في مدير المحتوى في Strapi. إنها مسودة ولن تكون متاحة للجمهور حتى ينشرها محرر.',
|
||||||
|
},
|
||||||
|
en: {
|
||||||
|
subject: '[SHARP-ER] Partner draft ready for review',
|
||||||
|
body: 'A partner contributor submitted {label}.\n\nReview it in Strapi Content Manager. It is a draft and is not public until an Editor publishes it.',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'partner-review-decision': {
|
||||||
|
ar: {
|
||||||
|
subject: 'تحديث بشأن مراجعة {label}',
|
||||||
|
body: 'تمت مراجعة {label}، والقرار هو: {decision}.\n\nملاحظات المراجع:\n{feedback}\n\nيرجى تحديث مسودتك في مدير المحتوى في Strapi وحفظها لإعادة تقديمها.',
|
||||||
|
},
|
||||||
|
en: {
|
||||||
|
subject: '[SHARP-ER] Review update: {label}',
|
||||||
|
body: 'Your {label} has been marked {decision}.\n\nReviewer feedback:\n{feedback}\n\nPlease update your draft in Strapi Content Manager and save it to resubmit.',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'partner-review-published': {
|
||||||
|
ar: {
|
||||||
|
subject: 'تم نشر محتواك',
|
||||||
|
body: 'تمت الموافقة على {label} ونشره على موقع SHARP-ER.',
|
||||||
|
},
|
||||||
|
en: {
|
||||||
|
subject: '[SHARP-ER] Your content has been published',
|
||||||
|
body: 'Your {label} has been approved and published on the SHARP-ER website.',
|
||||||
|
},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
import type { Core } from '@strapi/strapi';
|
import type { Core } from '@strapi/strapi';
|
||||||
|
|
||||||
import { trySendMail } from './event-mail';
|
import { DEFAULT_EMAIL_TEMPLATES, type EmailTemplateKey } from './email-template-defaults';
|
||||||
|
import { buildBilingualMail, trySendMail } from './event-mail';
|
||||||
|
import type { PlaceholderValues } from './email-templates';
|
||||||
|
|
||||||
const PARTNER_CONTENT = new Set([
|
const PARTNER_CONTENT = new Set([
|
||||||
'api::resource.resource',
|
'api::resource.resource',
|
||||||
@@ -12,6 +14,10 @@ const PARTNER_CONTENT = new Set([
|
|||||||
const NOTIFIABLE_ACTIONS = new Set(['create', 'update', 'publish']);
|
const NOTIFIABLE_ACTIONS = new Set(['create', 'update', 'publish']);
|
||||||
const REVIEW_RETURN_STATUSES = new Set(['changes-requested', 'rejected']);
|
const REVIEW_RETURN_STATUSES = new Set(['changes-requested', 'rejected']);
|
||||||
const LOG_PREFIX = '[partner-review-mail]';
|
const LOG_PREFIX = '[partner-review-mail]';
|
||||||
|
type PartnerReviewTemplateKey = Extract<
|
||||||
|
EmailTemplateKey,
|
||||||
|
'partner-review-submitted' | 'partner-review-decision' | 'partner-review-published'
|
||||||
|
>;
|
||||||
|
|
||||||
type ReviewDocument = {
|
type ReviewDocument = {
|
||||||
documentId?: string;
|
documentId?: string;
|
||||||
@@ -83,27 +89,50 @@ const getSubmitter = async (strapi: Core.Strapi, id: number): Promise<AdminUser
|
|||||||
select: ['id', 'email', 'isActive'],
|
select: ['id', 'email', 'isActive'],
|
||||||
})) as AdminUser | null;
|
})) as AdminUser | null;
|
||||||
|
|
||||||
const entryLabel = (uid: string, document?: ReviewDocument | null): string => {
|
const ENTRY_TYPE_LABELS: Record<string, { ar: string; en: string }> = {
|
||||||
|
resource: { ar: 'مورد', en: 'resource' },
|
||||||
|
'news-article': { ar: 'مقال إخباري', en: 'news article' },
|
||||||
|
activity: { ar: 'نشاط', en: 'activity' },
|
||||||
|
event: { ar: 'فعالية', en: 'event' },
|
||||||
|
opportunity: { ar: 'فرصة', en: 'opportunity' },
|
||||||
|
};
|
||||||
|
|
||||||
|
const entryLabel = (uid: string, document: ReviewDocument | null | undefined, locale: 'ar' | 'en'): string => {
|
||||||
const title = document?.title?.trim() || document?.slug?.trim();
|
const title = document?.title?.trim() || document?.slug?.trim();
|
||||||
const type = uid.split('.')[1]?.replace(/-/g, ' ') ?? 'content';
|
const typeSlug = uid.split('.')[1] ?? 'content';
|
||||||
return title ? `${type}: ${title}` : `a ${type} entry`;
|
const type = ENTRY_TYPE_LABELS[typeSlug]?.[locale] ?? (locale === 'ar' ? 'محتوى' : typeSlug.replace(/-/g, ' '));
|
||||||
|
if (title) return `${type}: ${title}`;
|
||||||
|
return locale === 'ar' ? `عنصر من نوع ${type}` : `a ${type} entry`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const buildReviewMail = (
|
||||||
|
key: PartnerReviewTemplateKey,
|
||||||
|
vars: { ar: PlaceholderValues; en: PlaceholderValues }
|
||||||
|
): ReturnType<typeof buildBilingualMail> => {
|
||||||
|
const template = DEFAULT_EMAIL_TEMPLATES[key];
|
||||||
|
return buildBilingualMail({
|
||||||
|
subjectAr: template.ar.subject,
|
||||||
|
subjectEn: template.en.subject,
|
||||||
|
bodyAr: template.ar.body,
|
||||||
|
bodyEn: template.en.body,
|
||||||
|
vars,
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const sendReviewNotification = async (
|
const sendReviewNotification = async (
|
||||||
strapi: Core.Strapi,
|
strapi: Core.Strapi,
|
||||||
to: string[],
|
to: string[],
|
||||||
subject: string,
|
mail: ReturnType<typeof buildBilingualMail>
|
||||||
body: string
|
|
||||||
): Promise<void> => {
|
): Promise<void> => {
|
||||||
if (to.length === 0) {
|
if (to.length === 0) {
|
||||||
strapi.log.warn(`${LOG_PREFIX} no recipient configured for: ${subject}`);
|
strapi.log.warn(`${LOG_PREFIX} no recipient configured for: ${mail.subject}`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
to.map((address) =>
|
to.map((address) =>
|
||||||
trySendMail(strapi, { to: address, subject, text: body }).then((sent) => {
|
trySendMail(strapi, { to: address, ...mail }).then((sent) => {
|
||||||
if (sent) strapi.log.info(`${LOG_PREFIX} sent "${subject}" to ${address}.`);
|
if (sent) strapi.log.info(`${LOG_PREFIX} sent "${mail.subject}" to ${address}.`);
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
@@ -130,7 +159,8 @@ const notifyAfterSave = async ({
|
|||||||
const submitterId = after.submittedByAdminUserId;
|
const submitterId = after.submittedByAdminUserId;
|
||||||
if (typeof submitterId !== 'number') return;
|
if (typeof submitterId !== 'number') return;
|
||||||
|
|
||||||
const label = entryLabel(uid, after);
|
const labelAr = entryLabel(uid, after, 'ar');
|
||||||
|
const labelEn = entryLabel(uid, after, 'en');
|
||||||
|
|
||||||
if (
|
if (
|
||||||
(action === 'create' && after.reviewStatus === 'pending') ||
|
(action === 'create' && after.reviewStatus === 'pending') ||
|
||||||
@@ -140,8 +170,10 @@ const notifyAfterSave = async ({
|
|||||||
await sendReviewNotification(
|
await sendReviewNotification(
|
||||||
strapi,
|
strapi,
|
||||||
recipients,
|
recipients,
|
||||||
`[SHARP-ER] Partner draft ready for review`,
|
buildReviewMail('partner-review-submitted', {
|
||||||
`A partner contributor submitted ${label}.\n\nReview it in Strapi Content Manager. It is a draft and is not public until an Editor publishes it.`
|
ar: { label: labelAr },
|
||||||
|
en: { label: labelEn },
|
||||||
|
})
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -158,13 +190,24 @@ const notifyAfterSave = async ({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const decision = after.reviewStatus === 'rejected' ? 'rejected' : 'changes requested';
|
const decisionEn = after.reviewStatus === 'rejected' ? 'rejected' : 'changes requested';
|
||||||
const feedback = after.reviewFeedback?.trim() || '(No review feedback was provided.)';
|
const decisionAr = after.reviewStatus === 'rejected' ? 'الرفض' : 'طلب إجراء تغييرات';
|
||||||
|
const feedback = after.reviewFeedback?.trim();
|
||||||
await sendReviewNotification(
|
await sendReviewNotification(
|
||||||
strapi,
|
strapi,
|
||||||
[submitter.email],
|
[submitter.email],
|
||||||
`[SHARP-ER] Review update: ${label}`,
|
buildReviewMail('partner-review-decision', {
|
||||||
`Your ${label} has been marked ${decision}.\n\nReviewer feedback:\n${feedback}\n\nPlease update your draft in Strapi Content Manager and save it to resubmit.`
|
ar: {
|
||||||
|
label: labelAr,
|
||||||
|
decision: decisionAr,
|
||||||
|
feedback: feedback || '(لم تُقدَّم ملاحظات للمراجعة.)',
|
||||||
|
},
|
||||||
|
en: {
|
||||||
|
label: labelEn,
|
||||||
|
decision: decisionEn,
|
||||||
|
feedback: feedback || '(No review feedback was provided.)',
|
||||||
|
},
|
||||||
|
})
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -179,8 +222,10 @@ const notifyAfterSave = async ({
|
|||||||
await sendReviewNotification(
|
await sendReviewNotification(
|
||||||
strapi,
|
strapi,
|
||||||
[submitter.email],
|
[submitter.email],
|
||||||
`[SHARP-ER] Your content has been published`,
|
buildReviewMail('partner-review-published', {
|
||||||
`Your ${label} has been approved and published on the SHARP-ER website.`
|
ar: { label: labelAr },
|
||||||
|
en: { label: labelEn },
|
||||||
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user