ываыв

This commit is contained in:
Kirill
2026-05-28 22:42:33 +05:00
parent 5b9c2f4c46
commit 4ddf95dadf
3 changed files with 107 additions and 1 deletions
@@ -1,6 +1,7 @@
// server/src/lib/notifications/channels/email-channel.js // server/src/lib/notifications/channels/email-channel.js
import { sendNotificationEmail } from '../../email.js' import { sendNotificationEmail } from '../../email.js'
import { import {
renderAdminOrderMessageEmail,
renderOrderCreatedEmail, renderOrderCreatedEmail,
renderOrderStatusChangedEmail, renderOrderStatusChangedEmail,
renderOrderMessageEmail, renderOrderMessageEmail,
@@ -17,7 +18,7 @@ const templateRenderers = {
'orderMessage:adminReply': renderOrderMessageEmail, 'orderMessage:adminReply': renderOrderMessageEmail,
'payment:statusChanged': renderPaymentStatusChangedEmail, 'payment:statusChanged': renderPaymentStatusChangedEmail,
'order:created:admin': renderAdminOrderCreatedEmail, 'order:created:admin': renderAdminOrderCreatedEmail,
'orderMessage:sent': renderOrderMessageEmail, 'orderMessage:sent': renderAdminOrderMessageEmail,
'review:created': renderAdminNewReviewEmail, 'review:created': renderAdminNewReviewEmail,
'auth:codeRequested': renderAuthCodeEmail, 'auth:codeRequested': renderAuthCodeEmail,
'order:deliveryFeeAdjusted': renderDeliveryFeeAdjustedEmail, 'order:deliveryFeeAdjusted': renderDeliveryFeeAdjustedEmail,
@@ -0,0 +1,54 @@
import { afterEach, describe, expect, it } from 'vitest'
import {
renderAdminOrderMessageEmail,
renderDeliveryFeeAdjustedEmail,
renderOrderCreatedEmail,
renderOrderMessageEmail,
renderOrderStatusChangedEmail,
renderPaymentStatusChangedEmail,
} from '../email-templates.js'
const originalClientPublicUrl = process.env.CLIENT_PUBLIC_URL
const orderId = 'order-123456789'
afterEach(() => {
if (originalClientPublicUrl === undefined) {
delete process.env.CLIENT_PUBLIC_URL
return
}
process.env.CLIENT_PUBLIC_URL = originalClientPublicUrl
})
describe('email templates', () => {
it('adds personal account order links to order emails', () => {
process.env.CLIENT_PUBLIC_URL = 'https://shop.example.com/'
const expectedUrl = `https://shop.example.com/me/orders/${orderId}`
const emails = [
renderOrderCreatedEmail({ orderId, totalCents: 120000, itemsCount: 2, deliveryType: 'pickup' }),
renderOrderStatusChangedEmail({ orderId, oldStatus: 'PENDING_PAYMENT', newStatus: 'PAID' }),
renderPaymentStatusChangedEmail({ orderId, paymentStatus: 'confirmed' }),
renderDeliveryFeeAdjustedEmail({ orderId, totalCents: 135000 }),
]
for (const email of emails) {
expect(email.html).toContain(`href="${expectedUrl}"`)
}
})
it('adds personal account messages link to order message emails', () => {
process.env.CLIENT_PUBLIC_URL = 'https://shop.example.com'
const email = renderOrderMessageEmail({ orderId, preview: 'Здравствуйте' })
expect(email.html).toContain('href="https://shop.example.com/me/messages"')
})
it('adds admin orders link to admin order message emails', () => {
process.env.CLIENT_PUBLIC_URL = 'https://shop.example.com'
const email = renderAdminOrderMessageEmail({ orderId, preview: 'Нужна консультация' })
expect(email.html).toContain('href="https://shop.example.com/admin/orders"')
})
})
@@ -14,6 +14,36 @@ function baseLayout(title, body) {
</html>` </html>`
} }
function getClientPublicUrl() {
return (process.env.CLIENT_PUBLIC_URL || 'http://127.0.0.1:5173').replace(/\/$/, '')
}
function buildClientUrl(path) {
return `${getClientPublicUrl()}${path}`
}
function renderActionLink(url, label) {
return `
<p style="margin:20px 0;">
<a href="${url}" style="display:inline-block;background:#1f2937;color:#fff;text-decoration:none;padding:10px 16px;border-radius:6px;font-weight:600;">
${label}
</a>
</p>
`
}
function buildOrderUrl(orderId) {
return buildClientUrl(`/me/orders/${encodeURIComponent(orderId)}`)
}
function buildMessagesUrl() {
return buildClientUrl('/me/messages')
}
function buildAdminOrdersUrl() {
return buildClientUrl('/admin/orders')
}
export function renderOrderCreatedEmail({ orderId, totalCents, itemsCount, deliveryType }) { export function renderOrderCreatedEmail({ orderId, totalCents, itemsCount, deliveryType }) {
const total = (totalCents / 100).toLocaleString('ru-RU') const total = (totalCents / 100).toLocaleString('ru-RU')
const nextAction = const nextAction =
@@ -22,6 +52,7 @@ export function renderOrderCreatedEmail({ orderId, totalCents, itemsCount, deliv
<p>Ваш заказ <b>#${orderId.slice(0, 8)}</b> успешно создан.</p> <p>Ваш заказ <b>#${orderId.slice(0, 8)}</b> успешно создан.</p>
<p>Товаров: ${itemsCount} | Сумма: <b>${total} ₽</b></p> <p>Товаров: ${itemsCount} | Сумма: <b>${total} ₽</b></p>
<p>${nextAction}</p> <p>${nextAction}</p>
${renderActionLink(buildOrderUrl(orderId), 'Открыть заказ в личном кабинете')}
` `
return { subject: 'Заказ создан', html: baseLayout('Заказ создан', body) } return { subject: 'Заказ создан', html: baseLayout('Заказ создан', body) }
} }
@@ -42,6 +73,7 @@ export function renderOrderStatusChangedEmail({ orderId, oldStatus, newStatus })
const body = ` const body = `
<p>Статус заказа <b>#${orderId.slice(0, 8)}</b> изменён.</p> <p>Статус заказа <b>#${orderId.slice(0, 8)}</b> изменён.</p>
<p><b>${oldLabel}</b> → <b>${newLabel}</b></p> <p><b>${oldLabel}</b> → <b>${newLabel}</b></p>
${renderActionLink(buildOrderUrl(orderId), 'Открыть заказ в личном кабинете')}
` `
return { return {
subject: `Статус заказа изменён — ${newLabel}`, subject: `Статус заказа изменён — ${newLabel}`,
@@ -57,6 +89,23 @@ export function renderOrderMessageEmail({ orderId, preview }) {
${truncated} ${truncated}
</div> </div>
<p>Ответьте в личном кабинете.</p> <p>Ответьте в личном кабинете.</p>
${renderActionLink(buildMessagesUrl(), 'Открыть сообщения в личном кабинете')}
`
return {
subject: 'Новое сообщение к заказу',
html: baseLayout('Новое сообщение', body),
}
}
export function renderAdminOrderMessageEmail({ orderId, preview }) {
const truncated = preview.length > 200 ? preview.slice(0, 197) + '...' : preview
const body = `
<p>Новое сообщение к заказу <b>#${orderId.slice(0, 8)}</b>:</p>
<div style="background:#f0f0f0;padding:12px;border-radius:6px;margin:12px 0;">
${truncated}
</div>
<p>Ответьте в админ-панели.</p>
${renderActionLink(buildAdminOrdersUrl(), 'Открыть заказы в админ-панели')}
` `
return { return {
subject: 'Новое сообщение к заказу', subject: 'Новое сообщение к заказу',
@@ -73,6 +122,7 @@ export function renderPaymentStatusChangedEmail({ orderId, paymentStatus }) {
const label = statusLabels[paymentStatus] || paymentStatus const label = statusLabels[paymentStatus] || paymentStatus
const body = ` const body = `
<p>Статус оплаты заказа <b>#${orderId.slice(0, 8)}</b>: <b>${label}</b>.</p> <p>Статус оплаты заказа <b>#${orderId.slice(0, 8)}</b>: <b>${label}</b>.</p>
${renderActionLink(buildOrderUrl(orderId), 'Открыть заказ в личном кабинете')}
` `
return { return {
subject: `Оплата заказа — ${label}`, subject: `Оплата заказа — ${label}`,
@@ -115,6 +165,7 @@ export function renderDeliveryFeeAdjustedEmail({ orderId, totalCents }) {
<p>Стоимость доставки заказа <b>#${orderId.slice(0, 8)}</b> скорректирована.</p> <p>Стоимость доставки заказа <b>#${orderId.slice(0, 8)}</b> скорректирована.</p>
<p>Новая сумма: <b>${total} ₽</b></p> <p>Новая сумма: <b>${total} ₽</b></p>
<p>Ожидает оплаты. Проверьте статус заказа в личном кабинете.</p> <p>Ожидает оплаты. Проверьте статус заказа в личном кабинете.</p>
${renderActionLink(buildOrderUrl(orderId), 'Открыть заказ в личном кабинете')}
` `
return { return {
subject: 'Стоимость доставки скорректирована', subject: 'Стоимость доставки скорректирована',