@consilioweb/payload-support 0.13.1 → 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +303 -85
- package/dist/index.js +303 -85
- package/package.json +1 -1
- package/src/collections/KnowledgeBase.ts +29 -0
- package/src/collections/SupportClients.ts +115 -0
- package/src/collections/TicketMessages.ts +21 -3
- package/src/collections/Tickets.ts +22 -3
- package/src/collections/TimeEntries.ts +12 -0
- package/src/hooks/ticketStatusEmail.ts +38 -27
- package/src/utils/emailTemplate.ts +153 -61
package/dist/index.cjs
CHANGED
|
@@ -1525,17 +1525,31 @@ function createSlaCheckEndpoint(slugs) {
|
|
|
1525
1525
|
}
|
|
1526
1526
|
|
|
1527
1527
|
// src/utils/emailTemplate.ts
|
|
1528
|
+
var COLORS = {
|
|
1529
|
+
ink: "#1d2b4d",
|
|
1530
|
+
inkSoft: "#3a486a",
|
|
1531
|
+
teal: "#17807c",
|
|
1532
|
+
amber: "#c2410c",
|
|
1533
|
+
paper: "#faf8f5",
|
|
1534
|
+
card: "#ffffff",
|
|
1535
|
+
sand: "#f4f1ec",
|
|
1536
|
+
line: "#e7e2d9",
|
|
1537
|
+
text: "#2a3142",
|
|
1538
|
+
muted: "#6b7280",
|
|
1539
|
+
faint: "#9aa1ad"
|
|
1540
|
+
};
|
|
1528
1541
|
var DEFAULT_CONFIG = {
|
|
1529
1542
|
brandName: "Support",
|
|
1530
|
-
brandColor:
|
|
1531
|
-
secondaryColor:
|
|
1532
|
-
accentColor:
|
|
1543
|
+
brandColor: COLORS.teal,
|
|
1544
|
+
secondaryColor: COLORS.ink,
|
|
1545
|
+
accentColor: COLORS.amber,
|
|
1533
1546
|
logoUrl: "",
|
|
1534
1547
|
supportEmail: process.env.SUPPORT_EMAIL || "",
|
|
1535
1548
|
websiteUrl: process.env.NEXT_PUBLIC_SERVER_URL || "",
|
|
1536
1549
|
phone: "",
|
|
1537
1550
|
location: "",
|
|
1538
|
-
brandInitials: ""
|
|
1551
|
+
brandInitials: "",
|
|
1552
|
+
preferencesUrl: ""
|
|
1539
1553
|
};
|
|
1540
1554
|
function resolveConfig(config) {
|
|
1541
1555
|
return { ...DEFAULT_CONFIG, ...config };
|
|
@@ -1574,30 +1588,30 @@ function emailRichContent(html, config) {
|
|
|
1574
1588
|
}
|
|
1575
1589
|
let result = html.replace(/src="\/([^"]+)"/g, `src="${baseUrl}/$1"`);
|
|
1576
1590
|
result = convertFencedCodeBlocks(result);
|
|
1577
|
-
result = styleTag(result, "blockquote", `border-left:
|
|
1591
|
+
result = styleTag(result, "blockquote", `border-left: 3px solid ${COLORS.teal}; margin: 16px 0; padding: 12px 20px; background: ${COLORS.sand}; border-radius: 0 8px 8px 0; color: ${COLORS.inkSoft};`);
|
|
1578
1592
|
result = styleTag(result, "img", "max-width: 100%; height: auto; border-radius: 8px; margin: 12px 0; display: block;");
|
|
1579
|
-
result = styleTag(result, "a", `color: ${
|
|
1593
|
+
result = styleTag(result, "a", `color: ${COLORS.teal}; text-decoration: underline; font-weight: 600;`);
|
|
1580
1594
|
result = styleTag(result, "ul", "margin: 8px 0; padding-left: 24px;");
|
|
1581
1595
|
result = styleTag(result, "ol", "margin: 8px 0; padding-left: 24px;");
|
|
1582
|
-
result = styleTag(result, "li",
|
|
1583
|
-
result = styleTag(result, "p",
|
|
1584
|
-
result = result.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, "").replace(/<style\b[^<]*(?:(?!<\/style>)<[^<]*)*<\/style>/gi, "").replace(/\bon\w+\s*=\s*"[^"]*"/gi, "").replace(/\bon\w+\s*=\s*'[^']*'/gi, "").replace(/javascript:/gi, "");
|
|
1585
|
-
return `<div style="font-size: 15px; line-height: 1.75; color:
|
|
1596
|
+
result = styleTag(result, "li", `margin: 4px 0; line-height: 1.6; color: ${COLORS.text};`);
|
|
1597
|
+
result = styleTag(result, "p", `margin: 0 0 12px 0; line-height: 1.75; font-size: 15px; color: ${COLORS.text};`);
|
|
1598
|
+
result = result.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, "").replace(/<style\b[^<]*(?:(?!<\/style>)<[^<]*)*<\/style>/gi, "").replace(/<iframe\b[^<]*(?:(?!<\/iframe>)<[^<]*)*<\/iframe>/gi, "").replace(/<iframe\b[^>]*\/?>/gi, "").replace(/<embed\b[^>]*\/?>/gi, "").replace(/<object\b[^<]*(?:(?!<\/object>)<[^<]*)*<\/object>/gi, "").replace(/\bon\w+\s*=\s*"[^"]*"/gi, "").replace(/\bon\w+\s*=\s*'[^']*'/gi, "").replace(/\bon\w+\s*=\s*[^\s>]+/gi, "").replace(/javascript:/gi, "");
|
|
1599
|
+
return `<div style="font-size: 15px; line-height: 1.75; color: ${COLORS.text};">${result}</div>`;
|
|
1586
1600
|
}
|
|
1587
|
-
function getButtonColors(
|
|
1601
|
+
function getButtonColors(_config) {
|
|
1588
1602
|
return {
|
|
1589
|
-
primary: { bg:
|
|
1590
|
-
secondary: { bg:
|
|
1591
|
-
dark: { bg:
|
|
1603
|
+
primary: { bg: COLORS.teal, text: "#ffffff", border: COLORS.teal },
|
|
1604
|
+
secondary: { bg: COLORS.sand, text: COLORS.ink, border: COLORS.line },
|
|
1605
|
+
dark: { bg: COLORS.ink, text: "#ffffff", border: COLORS.ink }
|
|
1592
1606
|
};
|
|
1593
1607
|
}
|
|
1594
1608
|
function emailButton(text, url, color = "primary", config) {
|
|
1595
|
-
|
|
1596
|
-
const colors = getButtonColors(
|
|
1609
|
+
resolveConfig(config);
|
|
1610
|
+
const colors = getButtonColors();
|
|
1597
1611
|
const bc = colors[color];
|
|
1598
1612
|
return `
|
|
1599
1613
|
<div style="text-align: center; margin: 32px 0;">
|
|
1600
|
-
<a href="${url}" style="display: inline-block; padding:
|
|
1614
|
+
<a href="${url}" style="display: inline-block; padding: 14px 36px; background: ${bc.bg}; color: ${bc.text}; font-weight: 700; font-size: 15px; text-decoration: none; border-radius: 10px; border: 1px solid ${bc.border}; letter-spacing: 0.01em;">
|
|
1601
1615
|
${text}
|
|
1602
1616
|
</a>
|
|
1603
1617
|
</div>
|
|
@@ -1612,8 +1626,9 @@ function renderCodeBlockHtml(lang, code) {
|
|
|
1612
1626
|
</div>`;
|
|
1613
1627
|
}
|
|
1614
1628
|
function emailQuote(content, borderColor, config) {
|
|
1615
|
-
|
|
1616
|
-
const color = borderColor ||
|
|
1629
|
+
resolveConfig(config);
|
|
1630
|
+
const color = borderColor || COLORS.teal;
|
|
1631
|
+
const panel = `margin: 24px 0; padding: 18px 22px; background: ${COLORS.sand}; border-left: 3px solid ${color}; border-radius: 0 8px 8px 0;`;
|
|
1617
1632
|
if (content && content.includes("```")) {
|
|
1618
1633
|
const parts = [];
|
|
1619
1634
|
const regex = /```(\w*)\n?([\s\S]*?)```/g;
|
|
@@ -1622,62 +1637,77 @@ function emailQuote(content, borderColor, config) {
|
|
|
1622
1637
|
while ((match = regex.exec(content)) !== null) {
|
|
1623
1638
|
const textBefore = content.slice(lastIndex, match.index).trim();
|
|
1624
1639
|
if (textBefore) {
|
|
1625
|
-
parts.push(`<p style="margin: 0 0 12px 0; font-size: 15px; line-height: 1.75; color:
|
|
1640
|
+
parts.push(`<p style="margin: 0 0 12px 0; font-size: 15px; line-height: 1.75; color: ${COLORS.inkSoft}; white-space: pre-wrap;">${escapeHtml(textBefore)}</p>`);
|
|
1626
1641
|
}
|
|
1627
1642
|
parts.push(renderCodeBlockHtml(match[1] || "", match[2] || ""));
|
|
1628
1643
|
lastIndex = match.index + match[0].length;
|
|
1629
1644
|
}
|
|
1630
1645
|
const textAfter = content.slice(lastIndex).trim();
|
|
1631
1646
|
if (textAfter) {
|
|
1632
|
-
parts.push(`<p style="margin: 12px 0 0 0; font-size: 15px; line-height: 1.75; color:
|
|
1647
|
+
parts.push(`<p style="margin: 12px 0 0 0; font-size: 15px; line-height: 1.75; color: ${COLORS.inkSoft}; white-space: pre-wrap;">${escapeHtml(textAfter)}</p>`);
|
|
1633
1648
|
}
|
|
1634
1649
|
return `
|
|
1635
|
-
<div style="
|
|
1650
|
+
<div style="${panel}">
|
|
1636
1651
|
${parts.join("")}
|
|
1637
1652
|
</div>
|
|
1638
1653
|
`;
|
|
1639
1654
|
}
|
|
1640
1655
|
return `
|
|
1641
|
-
<div style="
|
|
1642
|
-
<p style="margin: 0; font-size: 15px; line-height: 1.75; color:
|
|
1656
|
+
<div style="${panel}">
|
|
1657
|
+
<p style="margin: 0; font-size: 15px; line-height: 1.75; color: ${COLORS.inkSoft}; white-space: pre-wrap;">${escapeHtml(content)}</p>
|
|
1643
1658
|
</div>
|
|
1644
1659
|
`;
|
|
1645
1660
|
}
|
|
1646
1661
|
function emailParagraph(text) {
|
|
1647
|
-
return `<p style="margin: 0 0 18px 0; font-size: 15px; line-height: 1.75; color:
|
|
1662
|
+
return `<p style="margin: 0 0 18px 0; font-size: 15px; line-height: 1.75; color: ${COLORS.text};">${text}</p>`;
|
|
1663
|
+
}
|
|
1664
|
+
function getKindStyle(kind) {
|
|
1665
|
+
switch (kind) {
|
|
1666
|
+
case "reply":
|
|
1667
|
+
return { eyebrow: "R\xE9ponse de votre \xE9quipe", accent: COLORS.teal };
|
|
1668
|
+
case "alert":
|
|
1669
|
+
return { eyebrow: "Action requise", accent: COLORS.amber };
|
|
1670
|
+
case "system":
|
|
1671
|
+
default:
|
|
1672
|
+
return { eyebrow: "Notification automatique", accent: COLORS.inkSoft };
|
|
1673
|
+
}
|
|
1648
1674
|
}
|
|
1649
1675
|
function emailFooter(config) {
|
|
1650
1676
|
const logoHtml = config.logoUrl ? `<a href="${config.websiteUrl}">
|
|
1651
1677
|
<img src="${config.logoUrl}" alt="${escapeHtml(config.brandName)}" width="100" height="47" style="display: block; border: 0;" />
|
|
1652
|
-
</a>` : `<a href="${config.websiteUrl}" style="font-size:
|
|
1678
|
+
</a>` : `<a href="${config.websiteUrl}" style="font-size: 16px; font-weight: 800; color: ${COLORS.ink}; text-decoration: none; letter-spacing: -0.01em;">${escapeHtml(config.brandName)}</a>`;
|
|
1653
1679
|
const contactParts = [];
|
|
1654
1680
|
if (config.supportEmail) {
|
|
1655
|
-
contactParts.push(`<a href="mailto:${config.supportEmail}" style="color:
|
|
1681
|
+
contactParts.push(`<a href="mailto:${config.supportEmail}" style="color: ${COLORS.muted}; text-decoration: none;">${config.supportEmail}</a>`);
|
|
1656
1682
|
}
|
|
1657
1683
|
if (config.phone) {
|
|
1658
|
-
contactParts.push(`<a href="tel:${config.phone.replace(/\s/g, "")}" style="color:
|
|
1684
|
+
contactParts.push(`<a href="tel:${config.phone.replace(/\s/g, "")}" style="color: ${COLORS.muted}; text-decoration: none;">${config.phone}</a>`);
|
|
1659
1685
|
}
|
|
1660
1686
|
const locationParts = [];
|
|
1661
1687
|
if (config.websiteUrl) {
|
|
1662
1688
|
const displayUrl = config.websiteUrl.replace(/^https?:\/\//, "");
|
|
1663
|
-
locationParts.push(`<a href="${config.websiteUrl}" style="color: ${
|
|
1689
|
+
locationParts.push(`<a href="${config.websiteUrl}" style="color: ${COLORS.teal}; text-decoration: none; font-weight: 600;">${displayUrl}</a>`);
|
|
1664
1690
|
}
|
|
1665
1691
|
if (config.location) {
|
|
1666
1692
|
locationParts.push(config.location);
|
|
1667
1693
|
}
|
|
1668
|
-
const
|
|
1669
|
-
|
|
1670
|
-
|
|
1694
|
+
const manageUrl = config.preferencesUrl || (config.websiteUrl ? `${config.websiteUrl}/support/profile` : "") || (config.supportEmail ? `mailto:${config.supportEmail}?subject=Notifications` : "");
|
|
1695
|
+
const preferencesLinks = [];
|
|
1696
|
+
if (manageUrl) {
|
|
1697
|
+
preferencesLinks.push(`<a href="${manageUrl}" style="color: ${COLORS.muted}; text-decoration: underline;">G\xE9rer mes notifications</a>`);
|
|
1698
|
+
}
|
|
1699
|
+
if (config.supportEmail) {
|
|
1700
|
+
preferencesLinks.push(`<a href="mailto:${config.supportEmail}?subject=Unsubscribe" style="color: ${COLORS.faint}; text-decoration: underline;">Se d\xE9sinscrire</a>`);
|
|
1701
|
+
}
|
|
1702
|
+
const preferencesHtml = preferencesLinks.length > 0 ? `<p style="margin: 10px 0 0 0; font-size: 11px; color: ${COLORS.faint}; line-height: 1.5;">${preferencesLinks.join(" · ")}</p>` : "";
|
|
1671
1703
|
return `
|
|
1672
1704
|
<!-- Spacer -->
|
|
1673
|
-
<div style="height:
|
|
1705
|
+
<div style="height: 28px;"></div>
|
|
1674
1706
|
|
|
1675
|
-
<!--
|
|
1676
|
-
<table cellpadding="0" cellspacing="0" border="0" width="100%" style="margin-bottom:
|
|
1707
|
+
<!-- Hairline separator -->
|
|
1708
|
+
<table cellpadding="0" cellspacing="0" border="0" width="100%" style="margin-bottom: 22px;">
|
|
1677
1709
|
<tr>
|
|
1678
|
-
<td
|
|
1679
|
-
<td width="25%" height="3" bgcolor="${config.secondaryColor}" style="font-size:1px;line-height:1px;"> </td>
|
|
1680
|
-
<td width="25%" height="3" bgcolor="${config.accentColor}" style="font-size:1px;line-height:1px;"> </td>
|
|
1710
|
+
<td height="1" bgcolor="${COLORS.line}" style="font-size:1px;line-height:1px;"> </td>
|
|
1681
1711
|
</tr>
|
|
1682
1712
|
</table>
|
|
1683
1713
|
|
|
@@ -1688,10 +1718,10 @@ function emailFooter(config) {
|
|
|
1688
1718
|
${logoHtml}
|
|
1689
1719
|
</td>
|
|
1690
1720
|
<td valign="top">
|
|
1691
|
-
<p style="margin: 0; font-size:
|
|
1692
|
-
${contactParts.length > 0 ? `<p style="margin: 4px 0 0 0; font-size: 13px; color:
|
|
1693
|
-
${locationParts.length > 0 ? `<p style="margin: 4px 0 0 0; font-size: 12px; color:
|
|
1694
|
-
${
|
|
1721
|
+
<p style="margin: 0; font-size: 13px; font-weight: 700; color: ${COLORS.ink}; letter-spacing: 0.01em;">${escapeHtml(config.brandName)}</p>
|
|
1722
|
+
${contactParts.length > 0 ? `<p style="margin: 4px 0 0 0; font-size: 13px; color: ${COLORS.muted}; line-height: 1.5;">${contactParts.join(" · ")}</p>` : ""}
|
|
1723
|
+
${locationParts.length > 0 ? `<p style="margin: 4px 0 0 0; font-size: 12px; color: ${COLORS.faint}; line-height: 1.4;">${locationParts.join(" · ")}</p>` : ""}
|
|
1724
|
+
${preferencesHtml}
|
|
1695
1725
|
</td>
|
|
1696
1726
|
</tr>
|
|
1697
1727
|
</table>
|
|
@@ -1699,11 +1729,13 @@ function emailFooter(config) {
|
|
|
1699
1729
|
}
|
|
1700
1730
|
function emailWrapper(title, body, options = {}, config) {
|
|
1701
1731
|
const c = resolveConfig(config);
|
|
1702
|
-
const {
|
|
1703
|
-
const
|
|
1732
|
+
const { preheader, kind } = options;
|
|
1733
|
+
const kindStyle = kind ? getKindStyle(kind) : null;
|
|
1734
|
+
const accent = kindStyle?.accent || COLORS.teal;
|
|
1735
|
+
const eyebrowHtml = kindStyle ? `<p style="margin: 0 0 6px 0; font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.08em; color: ${kindStyle.accent};">${escapeHtml(kindStyle.eyebrow)}</p>` : "";
|
|
1704
1736
|
const badgeHtml = c.brandInitials ? `<td width="48" align="right" valign="middle">
|
|
1705
|
-
<div style="width:
|
|
1706
|
-
<span style="color: #fff; font-weight:
|
|
1737
|
+
<div style="width: 38px; height: 38px; border-radius: 10px; background: ${COLORS.ink}; display: inline-block; text-align: center; line-height: 38px;">
|
|
1738
|
+
<span style="color: #fff; font-weight: 800; font-size: 14px; letter-spacing: 0.04em;">${escapeHtml(c.brandInitials)}</span>
|
|
1707
1739
|
</div>
|
|
1708
1740
|
</td>` : "";
|
|
1709
1741
|
return `<!DOCTYPE html>
|
|
@@ -1718,23 +1750,29 @@ function emailWrapper(title, body, options = {}, config) {
|
|
|
1718
1750
|
</style>
|
|
1719
1751
|
<![endif]-->
|
|
1720
1752
|
</head>
|
|
1721
|
-
<body style="margin: 0; padding: 0; background:
|
|
1722
|
-
${preheader ? `<div style="display: none; max-height: 0; overflow: hidden; mso-hide: all;">${escapeHtml(preheader)}
|
|
1753
|
+
<body style="margin: 0; padding: 0; background: ${COLORS.paper}; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale;">
|
|
1754
|
+
${preheader ? `<div style="display: none; max-height: 0; overflow: hidden; mso-hide: all; font-size: 1px; line-height: 1px; color: ${COLORS.paper};">${escapeHtml(preheader)} ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌</div>` : ""}
|
|
1723
1755
|
|
|
1724
1756
|
<!-- Outer wrapper -->
|
|
1725
|
-
<table cellpadding="0" cellspacing="0" border="0" width="100%" style="background:
|
|
1757
|
+
<table cellpadding="0" cellspacing="0" border="0" width="100%" style="background: ${COLORS.paper}; padding: 40px 16px;">
|
|
1726
1758
|
<tr>
|
|
1727
1759
|
<td align="center">
|
|
1728
|
-
<!-- Main container \u2014
|
|
1729
|
-
<table cellpadding="0" cellspacing="0" border="0" width="
|
|
1760
|
+
<!-- Main container \u2014 640px -->
|
|
1761
|
+
<table cellpadding="0" cellspacing="0" border="0" width="640" style="max-width: 640px; width: 100%;">
|
|
1762
|
+
|
|
1763
|
+
<!-- Accent rule -->
|
|
1764
|
+
<tr>
|
|
1765
|
+
<td style="height: 4px; background: ${accent}; border-radius: 12px 12px 0 0; font-size: 1px; line-height: 1px;"> </td>
|
|
1766
|
+
</tr>
|
|
1730
1767
|
|
|
1731
1768
|
<!-- Header -->
|
|
1732
1769
|
<tr>
|
|
1733
|
-
<td style="background: ${
|
|
1770
|
+
<td style="background: ${COLORS.card}; padding: 30px 40px 22px 40px; border: 1px solid ${COLORS.line}; border-top: none; border-bottom: none;">
|
|
1734
1771
|
<table cellpadding="0" cellspacing="0" border="0" width="100%">
|
|
1735
1772
|
<tr>
|
|
1736
|
-
<td>
|
|
1737
|
-
|
|
1773
|
+
<td valign="middle">
|
|
1774
|
+
${eyebrowHtml}
|
|
1775
|
+
<h1 style="margin: 0; color: ${COLORS.ink}; font-size: 21px; font-weight: 800; line-height: 1.3; letter-spacing: -0.01em;">
|
|
1738
1776
|
${escapeHtml(title)}
|
|
1739
1777
|
</h1>
|
|
1740
1778
|
</td>
|
|
@@ -1746,7 +1784,7 @@ function emailWrapper(title, body, options = {}, config) {
|
|
|
1746
1784
|
|
|
1747
1785
|
<!-- Body -->
|
|
1748
1786
|
<tr>
|
|
1749
|
-
<td style="background:
|
|
1787
|
+
<td style="background: ${COLORS.card}; padding: 8px 40px 40px 40px; border: 1px solid ${COLORS.line}; border-top: none; border-radius: 0 0 12px 12px;">
|
|
1750
1788
|
${body}
|
|
1751
1789
|
${emailFooter(c)}
|
|
1752
1790
|
</td>
|
|
@@ -5925,8 +5963,12 @@ async function _sendToEndpoint(endpoint, body, payload, slugs) {
|
|
|
5925
5963
|
var STATUS_LABELS = {
|
|
5926
5964
|
open: "Ouvert",
|
|
5927
5965
|
waiting_client: "En attente client",
|
|
5928
|
-
|
|
5966
|
+
waiting_support: "En attente support",
|
|
5967
|
+
resolved: "Resolu",
|
|
5968
|
+
closed: "Ferme",
|
|
5969
|
+
escalated: "Escalade"
|
|
5929
5970
|
};
|
|
5971
|
+
var SILENT_STATUSES = /* @__PURE__ */ new Set(["waiting_client", "waiting_support", "resolved"]);
|
|
5930
5972
|
async function resolveClient(doc, payload, clientSlug) {
|
|
5931
5973
|
const client = doc.client;
|
|
5932
5974
|
if (!client) return null;
|
|
@@ -5978,21 +6020,24 @@ function createTicketStatusEmail(slugs) {
|
|
|
5978
6020
|
emailParagraph(`Nous avons bien recu votre demande de support. Voici les details :`),
|
|
5979
6021
|
`<table cellpadding="0" cellspacing="0" border="0" width="100%" style="margin-bottom: 20px;">
|
|
5980
6022
|
<tr>
|
|
5981
|
-
<td style="padding: 8px 0; font-size:
|
|
5982
|
-
<td style="padding: 8px 0; font-size: 15px; color: #
|
|
6023
|
+
<td style="padding: 8px 0; font-size: 11px; font-weight: 700; color: #6b7280; width: 150px; vertical-align: top; text-transform: uppercase; letter-spacing: 0.04em;">N° Ticket</td>
|
|
6024
|
+
<td style="padding: 8px 0; font-size: 15px; color: #2a3142; line-height: 1.5;"><strong>${escapeHtml(String(ticketNumber))}</strong></td>
|
|
5983
6025
|
</tr>
|
|
5984
6026
|
<tr>
|
|
5985
|
-
<td style="padding: 8px 0; font-size:
|
|
5986
|
-
<td style="padding: 8px 0; font-size: 15px; color: #
|
|
6027
|
+
<td style="padding: 8px 0; font-size: 11px; font-weight: 700; color: #6b7280; width: 150px; vertical-align: top; text-transform: uppercase; letter-spacing: 0.04em;">Sujet</td>
|
|
6028
|
+
<td style="padding: 8px 0; font-size: 15px; color: #2a3142; line-height: 1.5;">${escapeHtml(String(subject))}</td>
|
|
5987
6029
|
</tr>
|
|
5988
6030
|
<tr>
|
|
5989
|
-
<td style="padding: 8px 0; font-size:
|
|
5990
|
-
<td style="padding: 8px 0; font-size: 15px; color: #
|
|
6031
|
+
<td style="padding: 8px 0; font-size: 11px; font-weight: 700; color: #6b7280; width: 150px; vertical-align: top; text-transform: uppercase; letter-spacing: 0.04em;">Statut</td>
|
|
6032
|
+
<td style="padding: 8px 0; font-size: 15px; color: #2a3142; line-height: 1.5;">${escapeHtml(STATUS_LABELS[doc.status] || String(doc.status))}</td>
|
|
5991
6033
|
</tr>
|
|
5992
6034
|
</table>`,
|
|
5993
6035
|
emailParagraph("Notre equipe vous repondra dans les meilleurs delais."),
|
|
5994
6036
|
emailButton("Consulter le ticket", ticketUrl)
|
|
5995
|
-
].join("")
|
|
6037
|
+
].join(""), {
|
|
6038
|
+
kind: "system",
|
|
6039
|
+
preheader: `Votre demande ${ticketNumber} est bien enregistree. Nous revenons vers vous rapidement.`
|
|
6040
|
+
})
|
|
5996
6041
|
});
|
|
5997
6042
|
payload.logger.info(`[tickets] Creation notification sent to ${client.email} for ${ticketNumber}`);
|
|
5998
6043
|
} catch (error) {
|
|
@@ -6001,7 +6046,7 @@ function createTicketStatusEmail(slugs) {
|
|
|
6001
6046
|
return doc;
|
|
6002
6047
|
}
|
|
6003
6048
|
if (operation === "update" && previousDoc?.status !== doc.status) {
|
|
6004
|
-
if (doc.status
|
|
6049
|
+
if (SILENT_STATUSES.has(doc.status)) return doc;
|
|
6005
6050
|
try {
|
|
6006
6051
|
const client = await resolveClient(doc, payload, slugs.supportClients);
|
|
6007
6052
|
if (!client?.email) return doc;
|
|
@@ -6011,12 +6056,6 @@ function createTicketStatusEmail(slugs) {
|
|
|
6011
6056
|
const statusLabel = STATUS_LABELS[doc.status] || doc.status;
|
|
6012
6057
|
const previousStatusLabel = STATUS_LABELS[previousDoc?.status] || previousDoc?.status || "N/A";
|
|
6013
6058
|
const ticketUrl = `${baseUrl}/support/tickets/${doc.id}`;
|
|
6014
|
-
let contextMessage = "";
|
|
6015
|
-
if (doc.status === "waiting_client") {
|
|
6016
|
-
contextMessage = emailParagraph(
|
|
6017
|
-
"Nous avons besoin d'informations supplementaires de votre part pour continuer le traitement de votre demande. Merci de consulter le ticket et de nous repondre."
|
|
6018
|
-
);
|
|
6019
|
-
}
|
|
6020
6059
|
await payload.sendEmail({
|
|
6021
6060
|
to: client.email,
|
|
6022
6061
|
...supportEmail ? { replyTo: supportEmail } : {},
|
|
@@ -6026,25 +6065,27 @@ function createTicketStatusEmail(slugs) {
|
|
|
6026
6065
|
emailParagraph(`Le statut de votre ticket a ete mis a jour :`),
|
|
6027
6066
|
`<table cellpadding="0" cellspacing="0" border="0" width="100%" style="margin-bottom: 20px;">
|
|
6028
6067
|
<tr>
|
|
6029
|
-
<td style="padding: 8px 0; font-size:
|
|
6030
|
-
<td style="padding: 8px 0; font-size: 15px; color: #
|
|
6068
|
+
<td style="padding: 8px 0; font-size: 11px; font-weight: 700; color: #6b7280; width: 150px; vertical-align: top; text-transform: uppercase; letter-spacing: 0.04em;">N° Ticket</td>
|
|
6069
|
+
<td style="padding: 8px 0; font-size: 15px; color: #2a3142; line-height: 1.5;"><strong>${escapeHtml(String(ticketNumber))}</strong></td>
|
|
6031
6070
|
</tr>
|
|
6032
6071
|
<tr>
|
|
6033
|
-
<td style="padding: 8px 0; font-size:
|
|
6034
|
-
<td style="padding: 8px 0; font-size: 15px; color: #
|
|
6072
|
+
<td style="padding: 8px 0; font-size: 11px; font-weight: 700; color: #6b7280; width: 150px; vertical-align: top; text-transform: uppercase; letter-spacing: 0.04em;">Sujet</td>
|
|
6073
|
+
<td style="padding: 8px 0; font-size: 15px; color: #2a3142; line-height: 1.5;">${escapeHtml(String(subject))}</td>
|
|
6035
6074
|
</tr>
|
|
6036
6075
|
<tr>
|
|
6037
|
-
<td style="padding: 8px 0; font-size:
|
|
6038
|
-
<td style="padding: 8px 0; font-size: 15px; color: #
|
|
6076
|
+
<td style="padding: 8px 0; font-size: 11px; font-weight: 700; color: #6b7280; width: 150px; vertical-align: top; text-transform: uppercase; letter-spacing: 0.04em;">Ancien statut</td>
|
|
6077
|
+
<td style="padding: 8px 0; font-size: 15px; color: #2a3142; line-height: 1.5;">${escapeHtml(String(previousStatusLabel))}</td>
|
|
6039
6078
|
</tr>
|
|
6040
6079
|
<tr>
|
|
6041
|
-
<td style="padding: 8px 0; font-size:
|
|
6042
|
-
<td style="padding: 8px 0; font-size: 15px; color: #
|
|
6080
|
+
<td style="padding: 8px 0; font-size: 11px; font-weight: 700; color: #6b7280; width: 150px; vertical-align: top; text-transform: uppercase; letter-spacing: 0.04em;">Nouveau statut</td>
|
|
6081
|
+
<td style="padding: 8px 0; font-size: 15px; color: #2a3142; line-height: 1.5;"><strong>${escapeHtml(String(statusLabel))}</strong></td>
|
|
6043
6082
|
</tr>
|
|
6044
6083
|
</table>`,
|
|
6045
|
-
contextMessage,
|
|
6046
6084
|
emailButton("Consulter le ticket", ticketUrl)
|
|
6047
|
-
].join("")
|
|
6085
|
+
].join(""), {
|
|
6086
|
+
kind: "system",
|
|
6087
|
+
preheader: `Ticket ${ticketNumber} \u2014 nouveau statut : ${statusLabel}`
|
|
6088
|
+
})
|
|
6048
6089
|
});
|
|
6049
6090
|
payload.logger.info(`[tickets] Status change notification sent to ${client.email} for ${ticketNumber} (${previousDoc?.status} -> ${doc.status})`);
|
|
6050
6091
|
} catch (error) {
|
|
@@ -6560,16 +6601,27 @@ function createNotifyClientOnResolve(slugs) {
|
|
|
6560
6601
|
depth: 0,
|
|
6561
6602
|
overrideAccess: true
|
|
6562
6603
|
}) : null;
|
|
6563
|
-
if (!clientData?.email
|
|
6604
|
+
if (!clientData?.email) return doc;
|
|
6605
|
+
if (clientData.notifyResolution === false || clientData.notifyOnStatusChange === false) return doc;
|
|
6564
6606
|
const settings = await readSupportSettings(req.payload);
|
|
6565
6607
|
const ticketNumber = doc.ticketNumber || "TK-????";
|
|
6608
|
+
const subject = doc.subject || "Support";
|
|
6566
6609
|
const baseUrl = process.env.NEXT_PUBLIC_SERVER_URL || "";
|
|
6567
6610
|
const replyTo = settings.email.replyToAddress || process.env.SUPPORT_EMAIL || "";
|
|
6611
|
+
const ticketUrl = `${baseUrl}/support/tickets/${doc.id}`;
|
|
6568
6612
|
await req.payload.sendEmail({
|
|
6569
6613
|
to: clientData.email,
|
|
6570
6614
|
...replyTo ? { replyTo } : {},
|
|
6571
6615
|
subject: `[${ticketNumber}] Ticket resolu`,
|
|
6572
|
-
html:
|
|
6616
|
+
html: emailWrapper(`Votre ticket a ete resolu`, [
|
|
6617
|
+
emailParagraph(`Bonjour <strong>${escapeHtml(clientData.firstName || "")}</strong>,`),
|
|
6618
|
+
emailParagraph(`Votre ticket <strong>${escapeHtml(ticketNumber)}</strong> \u2014 <em>${escapeHtml(String(subject))}</em> \u2014 a ete resolu.`),
|
|
6619
|
+
emailParagraph("Si le probleme persiste ou si vous avez une question, repondez simplement a cet email pour rouvrir le ticket."),
|
|
6620
|
+
emailButton("Consulter le ticket", ticketUrl)
|
|
6621
|
+
].join(""), {
|
|
6622
|
+
kind: "system",
|
|
6623
|
+
preheader: `Votre ticket ${ticketNumber} a ete resolu.`
|
|
6624
|
+
})
|
|
6573
6625
|
});
|
|
6574
6626
|
} catch (err) {
|
|
6575
6627
|
console.error("[support] Failed to notify client on resolve:", err);
|
|
@@ -7166,10 +7218,13 @@ function createNotifyClient(slugs) {
|
|
|
7166
7218
|
const baseUrl = process.env.NEXT_PUBLIC_SERVER_URL || "";
|
|
7167
7219
|
const supportEmail = settings.email.replyToAddress || process.env.SUPPORT_EMAIL || "";
|
|
7168
7220
|
const portalUrl = `${baseUrl}/support/tickets/${ticketId}`;
|
|
7221
|
+
const internalBcc = process.env.SUPPORT_CLIENT_BCC || void 0;
|
|
7169
7222
|
const rawContent = doc.bodyHtml ? emailRichContent(doc.bodyHtml) : emailQuote(doc.body?.length > 500 ? doc.body.slice(0, 500) + "..." : doc.body);
|
|
7223
|
+
const replyPreview = (doc.body || "").replace(/\s+/g, " ").trim().slice(0, 90);
|
|
7170
7224
|
await req.payload.sendEmail({
|
|
7171
7225
|
to: client.email,
|
|
7172
7226
|
...supportEmail ? { replyTo: supportEmail } : {},
|
|
7227
|
+
...internalBcc ? { bcc: internalBcc } : {},
|
|
7173
7228
|
subject: `Re: [${ticketNumber}] ${subject}`,
|
|
7174
7229
|
html: emailWrapper(`Nouvelle reponse \u2014 ${ticketNumber}`, [
|
|
7175
7230
|
emailParagraph(`Bonjour <strong>${escapeHtml(client.firstName || "")}</strong>,`),
|
|
@@ -7178,7 +7233,10 @@ function createNotifyClient(slugs) {
|
|
|
7178
7233
|
emailButton("Consulter le ticket", portalUrl),
|
|
7179
7234
|
emailParagraph('<span style="font-size: 13px; color: #6b7280;">Vous pouvez egalement repondre directement a cet email. Votre message sera automatiquement ajoute au ticket.</span>'),
|
|
7180
7235
|
emailTrackingPixel(ticketId, doc.id)
|
|
7181
|
-
].join("")
|
|
7236
|
+
].join(""), {
|
|
7237
|
+
kind: "reply",
|
|
7238
|
+
preheader: replyPreview ? `${replyPreview}\u2026` : `Nouvelle reponse a votre ticket ${ticketNumber}`
|
|
7239
|
+
})
|
|
7182
7240
|
});
|
|
7183
7241
|
await req.payload.update({
|
|
7184
7242
|
collection: slugs.ticketMessages,
|
|
@@ -7288,6 +7346,10 @@ function createNotifyAdminOnClientMessage(slugs, notificationSlug) {
|
|
|
7288
7346
|
const headerTitle = isNewTicket ? `Nouveau ticket ${ticketNumber}` : `Nouveau message \u2014 ${ticketNumber}`;
|
|
7289
7347
|
if (primaryEmail) {
|
|
7290
7348
|
await payload.sendEmail({
|
|
7349
|
+
// INTERNAL email (to the support team) — recipients are the contact
|
|
7350
|
+
// inbox + assigned agent. The cc here is internal→internal and stays
|
|
7351
|
+
// a cc on purpose. Do NOT confuse with the client reply email
|
|
7352
|
+
// (createNotifyClient), where internal copies must be bcc.
|
|
7291
7353
|
to: primaryEmail,
|
|
7292
7354
|
...ccEmail ? { cc: ccEmail } : {},
|
|
7293
7355
|
...clientEmail !== "inconnu" ? { replyTo: clientEmail } : supportEmail ? { replyTo: supportEmail } : {},
|
|
@@ -7295,9 +7357,9 @@ function createNotifyAdminOnClientMessage(slugs, notificationSlug) {
|
|
|
7295
7357
|
html: emailWrapper(headerTitle, [
|
|
7296
7358
|
emailParagraph(`<strong>${escapeHtml(clientName)}</strong> (${escapeHtml(clientEmail)}) a ${isNewTicket ? "ouvert un nouveau ticket" : "repondu au ticket"} <strong>${escapeHtml(ticketNumber)}</strong> :`),
|
|
7297
7359
|
`<p style="margin: 0 0 4px 0; font-size: 14px; font-weight: 600; color: #374151;">Sujet : ${escapeHtml(subject)}</p>`,
|
|
7298
|
-
emailQuote(preview, isNewTicket ? "#
|
|
7360
|
+
emailQuote(preview, isNewTicket ? "#1d2b4d" : "#17807c"),
|
|
7299
7361
|
emailButton("Ouvrir dans l'admin", adminUrl, "dark")
|
|
7300
|
-
].join(""), {
|
|
7362
|
+
].join(""), { kind: "system" })
|
|
7301
7363
|
});
|
|
7302
7364
|
}
|
|
7303
7365
|
console.log(`[support] Admin notified for ${ticketNumber} (${isNewTicket ? "new" : "reply"})`);
|
|
@@ -7556,6 +7618,79 @@ function createSupportClientsCollection(slugs) {
|
|
|
7556
7618
|
}
|
|
7557
7619
|
]
|
|
7558
7620
|
},
|
|
7621
|
+
{
|
|
7622
|
+
type: "row",
|
|
7623
|
+
fields: [
|
|
7624
|
+
{
|
|
7625
|
+
name: "city",
|
|
7626
|
+
type: "text",
|
|
7627
|
+
label: "Ville",
|
|
7628
|
+
admin: { width: "50%" }
|
|
7629
|
+
},
|
|
7630
|
+
{
|
|
7631
|
+
name: "country",
|
|
7632
|
+
type: "text",
|
|
7633
|
+
label: "Pays",
|
|
7634
|
+
admin: { width: "50%" }
|
|
7635
|
+
}
|
|
7636
|
+
]
|
|
7637
|
+
},
|
|
7638
|
+
{
|
|
7639
|
+
name: "additionalContacts",
|
|
7640
|
+
type: "array",
|
|
7641
|
+
label: "Contacts additionnels",
|
|
7642
|
+
labels: {
|
|
7643
|
+
singular: "Contact",
|
|
7644
|
+
plural: "Contacts"
|
|
7645
|
+
},
|
|
7646
|
+
fields: [
|
|
7647
|
+
{
|
|
7648
|
+
type: "row",
|
|
7649
|
+
fields: [
|
|
7650
|
+
{
|
|
7651
|
+
name: "email",
|
|
7652
|
+
type: "email",
|
|
7653
|
+
required: true,
|
|
7654
|
+
label: "Email",
|
|
7655
|
+
admin: { width: "50%" }
|
|
7656
|
+
},
|
|
7657
|
+
{
|
|
7658
|
+
name: "name",
|
|
7659
|
+
type: "text",
|
|
7660
|
+
label: "Nom",
|
|
7661
|
+
admin: { width: "50%" }
|
|
7662
|
+
}
|
|
7663
|
+
]
|
|
7664
|
+
}
|
|
7665
|
+
]
|
|
7666
|
+
},
|
|
7667
|
+
{
|
|
7668
|
+
name: "opportunities",
|
|
7669
|
+
type: "textarea",
|
|
7670
|
+
label: "Opportunites commerciales",
|
|
7671
|
+
admin: {
|
|
7672
|
+
description: "Notes commerciales (visible uniquement par les admins)"
|
|
7673
|
+
}
|
|
7674
|
+
},
|
|
7675
|
+
{
|
|
7676
|
+
name: "tier",
|
|
7677
|
+
type: "select",
|
|
7678
|
+
label: "Plan",
|
|
7679
|
+
options: [
|
|
7680
|
+
{ label: "Free", value: "free" },
|
|
7681
|
+
{ label: "Basic", value: "basic" },
|
|
7682
|
+
{ label: "Pro", value: "pro" },
|
|
7683
|
+
{ label: "Enterprise", value: "enterprise" }
|
|
7684
|
+
],
|
|
7685
|
+
admin: { position: "sidebar" }
|
|
7686
|
+
},
|
|
7687
|
+
{
|
|
7688
|
+
name: "accountManager",
|
|
7689
|
+
type: "relationship",
|
|
7690
|
+
relationTo: slugs.users,
|
|
7691
|
+
label: "Agent referent",
|
|
7692
|
+
admin: { position: "sidebar" }
|
|
7693
|
+
},
|
|
7559
7694
|
{
|
|
7560
7695
|
name: "twoFactorEnabled",
|
|
7561
7696
|
type: "checkbox",
|
|
@@ -7596,6 +7731,48 @@ function createSupportClientsCollection(slugs) {
|
|
|
7596
7731
|
position: "sidebar"
|
|
7597
7732
|
}
|
|
7598
7733
|
},
|
|
7734
|
+
{
|
|
7735
|
+
name: "notifyReminders",
|
|
7736
|
+
type: "checkbox",
|
|
7737
|
+
defaultValue: true,
|
|
7738
|
+
label: "Notifications rappels",
|
|
7739
|
+
admin: {
|
|
7740
|
+
description: "Recevoir un email pour les rappels",
|
|
7741
|
+
position: "sidebar"
|
|
7742
|
+
}
|
|
7743
|
+
},
|
|
7744
|
+
{
|
|
7745
|
+
name: "notifyResolution",
|
|
7746
|
+
type: "checkbox",
|
|
7747
|
+
defaultValue: true,
|
|
7748
|
+
label: "Notifications resolution",
|
|
7749
|
+
admin: {
|
|
7750
|
+
description: "Recevoir un email a la resolution d'un ticket",
|
|
7751
|
+
position: "sidebar"
|
|
7752
|
+
}
|
|
7753
|
+
},
|
|
7754
|
+
{
|
|
7755
|
+
name: "notifyNews",
|
|
7756
|
+
type: "checkbox",
|
|
7757
|
+
defaultValue: false,
|
|
7758
|
+
label: "Notifications actualites",
|
|
7759
|
+
admin: {
|
|
7760
|
+
description: "Recevoir les actualites et nouveautes",
|
|
7761
|
+
position: "sidebar"
|
|
7762
|
+
}
|
|
7763
|
+
},
|
|
7764
|
+
{
|
|
7765
|
+
name: "notificationFrequency",
|
|
7766
|
+
type: "select",
|
|
7767
|
+
defaultValue: "immediate",
|
|
7768
|
+
label: "Frequence des notifications",
|
|
7769
|
+
options: [
|
|
7770
|
+
{ label: "Immediate", value: "immediate" },
|
|
7771
|
+
{ label: "Quotidienne", value: "daily" },
|
|
7772
|
+
{ label: "Hebdomadaire", value: "weekly" }
|
|
7773
|
+
],
|
|
7774
|
+
admin: { position: "sidebar" }
|
|
7775
|
+
},
|
|
7599
7776
|
{
|
|
7600
7777
|
name: "notes",
|
|
7601
7778
|
type: "textarea",
|
|
@@ -7708,6 +7885,18 @@ function createTimeEntriesCollection(slugs) {
|
|
|
7708
7885
|
name: "description",
|
|
7709
7886
|
type: "textarea",
|
|
7710
7887
|
label: "Description du travail"
|
|
7888
|
+
},
|
|
7889
|
+
{
|
|
7890
|
+
name: "billable",
|
|
7891
|
+
type: "checkbox",
|
|
7892
|
+
defaultValue: true,
|
|
7893
|
+
label: "Facturable"
|
|
7894
|
+
},
|
|
7895
|
+
{
|
|
7896
|
+
name: "agent",
|
|
7897
|
+
type: "relationship",
|
|
7898
|
+
relationTo: slugs.users,
|
|
7899
|
+
label: "Agent"
|
|
7711
7900
|
}
|
|
7712
7901
|
],
|
|
7713
7902
|
hooks: {
|
|
@@ -8008,6 +8197,14 @@ function createKnowledgeBaseCollection(slugs) {
|
|
|
8008
8197
|
{ label: "G\xE9n\xE9ral", value: "general" }
|
|
8009
8198
|
]
|
|
8010
8199
|
},
|
|
8200
|
+
{
|
|
8201
|
+
name: "excerpt",
|
|
8202
|
+
type: "textarea",
|
|
8203
|
+
label: "R\xE9sum\xE9",
|
|
8204
|
+
admin: {
|
|
8205
|
+
description: "Court r\xE9sum\xE9 affich\xE9 dans les listes"
|
|
8206
|
+
}
|
|
8207
|
+
},
|
|
8011
8208
|
{
|
|
8012
8209
|
name: "body",
|
|
8013
8210
|
type: "richText",
|
|
@@ -8021,12 +8218,33 @@ function createKnowledgeBaseCollection(slugs) {
|
|
|
8021
8218
|
label: "Publi\xE9",
|
|
8022
8219
|
admin: { position: "sidebar" }
|
|
8023
8220
|
},
|
|
8221
|
+
{
|
|
8222
|
+
name: "popular",
|
|
8223
|
+
type: "checkbox",
|
|
8224
|
+
defaultValue: false,
|
|
8225
|
+
label: "Populaire",
|
|
8226
|
+
admin: { position: "sidebar" }
|
|
8227
|
+
},
|
|
8024
8228
|
{
|
|
8025
8229
|
name: "sortOrder",
|
|
8026
8230
|
type: "number",
|
|
8027
8231
|
defaultValue: 0,
|
|
8028
8232
|
label: "Ordre",
|
|
8029
8233
|
admin: { position: "sidebar" }
|
|
8234
|
+
},
|
|
8235
|
+
{
|
|
8236
|
+
name: "helpfulYes",
|
|
8237
|
+
type: "number",
|
|
8238
|
+
defaultValue: 0,
|
|
8239
|
+
label: "Votes utiles",
|
|
8240
|
+
admin: { position: "sidebar", readOnly: true }
|
|
8241
|
+
},
|
|
8242
|
+
{
|
|
8243
|
+
name: "helpfulNo",
|
|
8244
|
+
type: "number",
|
|
8245
|
+
defaultValue: 0,
|
|
8246
|
+
label: "Votes non utiles",
|
|
8247
|
+
admin: { position: "sidebar", readOnly: true }
|
|
8030
8248
|
}
|
|
8031
8249
|
],
|
|
8032
8250
|
hooks: {
|