fix(campaigns/reminder): softer tone + render expiry in tests
The reminder copy read as pushy on test sends ("Hâte-toi! ... Tu n'as
encore rien fait, et le délai approche"). Toned down to factual and
friendly: state availability + offer the no-pressure path.
FR before / after:
⏰ Hâte-toi! Ton cadeau de 60 $ expire le ___. (red bold)
→ 🎁 Ton cadeau de 60 $ reste disponible jusqu'au 1 juillet 2026.
(brand dark green)
Tu n'as encore rien fait, et le délai approche. Si tu n'utilises
pas ton cadeau d'ici là, il ne pourra plus être réclamé.
→ On voulait juste s'assurer que tu ne l'as pas manqué — la carte-
cadeau qu'on t'a envoyée peut s'utiliser chez des centaines de
marques canadiennes, en quelques clics.
Si tu préfères ne pas l'utiliser, aucun souci — pas besoin de
répondre à ce courriel.
EN copy mirrored.
Also: {{expires_at_date}} was rendering empty in test sends and
previews because neither the test-send endpoint, the preview
endpoint, nor the editor's testSendForm.vars seeded it. Three fixes:
- Hub preview endpoint: compute now+30d as default sample date.
- Hub test-send endpoint: same default + expose view_url='' so the
Mustache section block collapses cleanly in internal tests.
- Editor test-send dialog: pre-fill expires_at_date (and expires_in_
days) with the same now+30d value, plus expose both fields as
editable inputs so the operator can override per-test.
Verified live on prod: the preview endpoint with no vars now renders
"Ton cadeau de 60 $ reste disponible jusqu'au 1 juillet 2026."
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
e64e1e6a1f
commit
73c42d6997
|
|
@ -160,6 +160,8 @@
|
||||||
<q-input v-model="testSendForm.vars.gift_url" label="gift_url" outlined dense class="col-12" />
|
<q-input v-model="testSendForm.vars.gift_url" label="gift_url" outlined dense class="col-12" />
|
||||||
<q-input v-model="testSendForm.vars.description" label="description" outlined dense class="col-12" />
|
<q-input v-model="testSendForm.vars.description" label="description" outlined dense class="col-12" />
|
||||||
<q-input v-model="testSendForm.vars.expiry" label="expiry" outlined dense class="col-12" />
|
<q-input v-model="testSendForm.vars.expiry" label="expiry" outlined dense class="col-12" />
|
||||||
|
<q-input v-model="testSendForm.vars.expires_at_date" label="expires_at_date (auto, date du wrapper)" outlined dense class="col-6" />
|
||||||
|
<q-input v-model="testSendForm.vars.expires_in_days" label="expires_in_days" outlined dense class="col-6" />
|
||||||
</div>
|
</div>
|
||||||
</q-card-section>
|
</q-card-section>
|
||||||
<q-card-section class="bg-grey-2">
|
<q-card-section class="bg-grey-2">
|
||||||
|
|
@ -529,6 +531,13 @@ async function openPreview () {
|
||||||
// ── Test-send dialog ────────────────────────────────────────────────────────
|
// ── Test-send dialog ────────────────────────────────────────────────────────
|
||||||
const testSendOpen = ref(false)
|
const testSendOpen = ref(false)
|
||||||
const testSending = ref(false)
|
const testSending = ref(false)
|
||||||
|
// Sample expiry — 30 days from now, locale-formatted FR. The Mustache
|
||||||
|
// section {{#expires_at_date}}…{{/expires_at_date}} guards visible
|
||||||
|
// blocks, so leaving this blank would hide them in tests. We compute
|
||||||
|
// a realistic value so test sends and previews show the real layout.
|
||||||
|
const sampleExpiryDate = new Date(Date.now() + 30 * 86400 * 1000)
|
||||||
|
.toLocaleDateString('fr-CA', { day: 'numeric', month: 'long', year: 'numeric' })
|
||||||
|
|
||||||
const testSendForm = ref({
|
const testSendForm = ref({
|
||||||
to: 'louis@targo.ca',
|
to: 'louis@targo.ca',
|
||||||
subject: '[TEST] Aperçu du courriel TARGO',
|
subject: '[TEST] Aperçu du courriel TARGO',
|
||||||
|
|
@ -538,6 +547,8 @@ const testSendForm = ref({
|
||||||
gift_url: 'https://gft.link/TEST123',
|
gift_url: 'https://gft.link/TEST123',
|
||||||
description: '123 Rue de Test, Ste-Clotilde',
|
description: '123 Rue de Test, Ste-Clotilde',
|
||||||
expiry: '31 décembre 2026',
|
expiry: '31 décembre 2026',
|
||||||
|
expires_at_date: sampleExpiryDate,
|
||||||
|
expires_in_days: '30',
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -228,8 +228,8 @@ table, td { color: #1B2E24; } #u_body a { color: #00C853; text-decoration: under
|
||||||
align="left" style="font-size:0px;padding:10px 25px;padding-bottom:14px;word-break:break-word;"
|
align="left" style="font-size:0px;padding:10px 25px;padding-bottom:14px;word-break:break-word;"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
style="font-family:Plus Jakarta Sans, Helvetica, Arial, sans-serif;font-size:18px;font-weight:700;line-height:1.4;text-align:left;color:#D03A0A;"
|
style="font-family:Plus Jakarta Sans, Helvetica, Arial, sans-serif;font-size:17px;font-weight:600;line-height:1.4;text-align:left;color:#1B2E24;"
|
||||||
>⏰ Hurry — your {{amount}} gift expires on {{expires_at_date}}.</div>
|
>🎁 Your {{amount}} gift is still available until {{expires_at_date}}.</div>
|
||||||
|
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
@ -240,8 +240,8 @@ table, td { color: #1B2E24; } #u_body a { color: #00C853; text-decoration: under
|
||||||
align="left" style="font-size:0px;padding:10px 25px;padding-bottom:0;word-break:break-word;"
|
align="left" style="font-size:0px;padding:10px 25px;padding-bottom:0;word-break:break-word;"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
style="font-family:Plus Jakarta Sans, Helvetica, Arial, sans-serif;font-size:16px;line-height:1.5;text-align:justify;color:#374151;">We sent you a gift card you can redeem at hundreds of Canadian brands — one click is all it takes.<br />
|
style="font-family:Plus Jakarta Sans, Helvetica, Arial, sans-serif;font-size:16px;line-height:1.5;text-align:justify;color:#374151;">We just wanted to make sure you didn't miss it — the gift card we sent you can be redeemed at hundreds of Canadian brands in just a few clicks.<br />
|
||||||
The window is closing soon. If you don't claim your gift before then, it'll no longer be available.</div>
|
If you'd rather not use it, no worries — no need to reply to this email.</div>
|
||||||
|
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
|
|
@ -232,8 +232,8 @@ table, td { color: #1B2E24; } #u_body a { color: #00C853; text-decoration: under
|
||||||
align="left" style="font-size:0px;padding:10px 25px;padding-bottom:14px;word-break:break-word;"
|
align="left" style="font-size:0px;padding:10px 25px;padding-bottom:14px;word-break:break-word;"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
style="font-family:Plus Jakarta Sans, Helvetica, Arial, sans-serif;font-size:18px;font-weight:700;line-height:1.4;text-align:left;color:#D03A0A;"
|
style="font-family:Plus Jakarta Sans, Helvetica, Arial, sans-serif;font-size:17px;font-weight:600;line-height:1.4;text-align:left;color:#1B2E24;"
|
||||||
>⏰ Hâte-toi ! Ton cadeau de {{amount}} expire le {{expires_at_date}}.</div>
|
>🎁 Ton cadeau de {{amount}} reste disponible jusqu'au {{expires_at_date}}.</div>
|
||||||
|
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
@ -244,8 +244,8 @@ table, td { color: #1B2E24; } #u_body a { color: #00C853; text-decoration: under
|
||||||
align="left" style="font-size:0px;padding:10px 25px;padding-bottom:0;word-break:break-word;"
|
align="left" style="font-size:0px;padding:10px 25px;padding-bottom:0;word-break:break-word;"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
style="font-family:Plus Jakarta Sans, Helvetica, Arial, sans-serif;font-size:16px;line-height:1.5;text-align:justify;color:#374151;">On t'a envoyé une carte-cadeau à utiliser chez des centaines de marques canadiennes — il te suffit d'un clic pour la réclamer.<br />
|
style="font-family:Plus Jakarta Sans, Helvetica, Arial, sans-serif;font-size:16px;line-height:1.5;text-align:justify;color:#374151;">On voulait juste s'assurer que tu ne l'as pas manqué — la carte-cadeau qu'on t'a envoyée peut s'utiliser chez des centaines de marques canadiennes, en quelques clics.<br />
|
||||||
Tu n'as encore rien fait, et le délai approche. Si tu n'utilises pas ton cadeau d'ici là, il ne pourra plus être réclamé.</div>
|
Si tu préfères ne pas l'utiliser, aucun souci — pas besoin de répondre à ce courriel.</div>
|
||||||
|
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
|
|
@ -1815,6 +1815,12 @@ async function handle (req, res, method, path) {
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return json(res, 404, { error: 'template not found', detail: e.message })
|
return json(res, 404, { error: 'template not found', detail: e.message })
|
||||||
}
|
}
|
||||||
|
// Sample wrapper expiry — 30 days out, locale-formatted FR. This is
|
||||||
|
// critical for the reminder template which uses {{expires_at_date}}
|
||||||
|
// as its main urgency line; without it the test email shows an
|
||||||
|
// empty space where the date should be.
|
||||||
|
const sampleExpAt = new Date(Date.now() + 30 * 86400 * 1000)
|
||||||
|
.toLocaleDateString('fr-CA', { day: 'numeric', month: 'long', year: 'numeric' })
|
||||||
const vars = {
|
const vars = {
|
||||||
firstname: 'Louis',
|
firstname: 'Louis',
|
||||||
lastname: 'Test',
|
lastname: 'Test',
|
||||||
|
|
@ -1823,8 +1829,14 @@ async function handle (req, res, method, path) {
|
||||||
gift_url: 'https://gft.link/TEST123',
|
gift_url: 'https://gft.link/TEST123',
|
||||||
amount: '60 $',
|
amount: '60 $',
|
||||||
expiry: '31 décembre 2026',
|
expiry: '31 décembre 2026',
|
||||||
|
expires_at_date: sampleExpAt,
|
||||||
|
expires_in_days: '30',
|
||||||
commitment_months: '3',
|
commitment_months: '3',
|
||||||
year: new Date().getFullYear(),
|
year: new Date().getFullYear(),
|
||||||
|
// view_url left empty so the {{#view_url}} section collapses —
|
||||||
|
// test emails go to internal addresses and don't need the web
|
||||||
|
// fallback link.
|
||||||
|
view_url: '',
|
||||||
...(body.vars || {}),
|
...(body.vars || {}),
|
||||||
}
|
}
|
||||||
const rendered = renderTemplate(html, vars)
|
const rendered = renderTemplate(html, vars)
|
||||||
|
|
@ -1855,10 +1867,14 @@ async function handle (req, res, method, path) {
|
||||||
if (tplPreview && method === 'POST') {
|
if (tplPreview && method === 'POST') {
|
||||||
const body = await parseBody(req)
|
const body = await parseBody(req)
|
||||||
const html = body.html || fs.readFileSync(templatePath(tplPreview[1]), 'utf8')
|
const html = body.html || fs.readFileSync(templatePath(tplPreview[1]), 'utf8')
|
||||||
|
const sampleExpAt = new Date(Date.now() + 30 * 86400 * 1000)
|
||||||
|
.toLocaleDateString('fr-CA', { day: 'numeric', month: 'long', year: 'numeric' })
|
||||||
const vars = {
|
const vars = {
|
||||||
firstname: 'Louis', lastname: 'Paul', email: 'louis@targo.ca',
|
firstname: 'Louis', lastname: 'Paul', email: 'louis@targo.ca',
|
||||||
description: '123 Rue de Test', gift_url: 'http://gtbt.co/PREVIEW',
|
description: '123 Rue de Test', gift_url: 'http://gtbt.co/PREVIEW',
|
||||||
amount: '60 $', expiry: '31 décembre 2026', commitment_months: '3',
|
amount: '60 $', expiry: '31 décembre 2026', commitment_months: '3',
|
||||||
|
expires_at_date: sampleExpAt, expires_in_days: '30',
|
||||||
|
view_url: '',
|
||||||
...(body.vars || {}),
|
...(body.vars || {}),
|
||||||
}
|
}
|
||||||
return json(res, 200, { rendered: renderTemplate(html, vars) })
|
return json(res, 200, { rendered: renderTemplate(html, vars) })
|
||||||
|
|
|
||||||
|
|
@ -228,8 +228,8 @@ table, td { color: #1B2E24; } #u_body a { color: #00C853; text-decoration: under
|
||||||
align="left" style="font-size:0px;padding:10px 25px;padding-bottom:14px;word-break:break-word;"
|
align="left" style="font-size:0px;padding:10px 25px;padding-bottom:14px;word-break:break-word;"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
style="font-family:Plus Jakarta Sans, Helvetica, Arial, sans-serif;font-size:18px;font-weight:700;line-height:1.4;text-align:left;color:#D03A0A;"
|
style="font-family:Plus Jakarta Sans, Helvetica, Arial, sans-serif;font-size:17px;font-weight:600;line-height:1.4;text-align:left;color:#1B2E24;"
|
||||||
>⏰ Hurry — your {{amount}} gift expires on {{expires_at_date}}.</div>
|
>🎁 Your {{amount}} gift is still available until {{expires_at_date}}.</div>
|
||||||
|
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
@ -240,8 +240,8 @@ table, td { color: #1B2E24; } #u_body a { color: #00C853; text-decoration: under
|
||||||
align="left" style="font-size:0px;padding:10px 25px;padding-bottom:0;word-break:break-word;"
|
align="left" style="font-size:0px;padding:10px 25px;padding-bottom:0;word-break:break-word;"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
style="font-family:Plus Jakarta Sans, Helvetica, Arial, sans-serif;font-size:16px;line-height:1.5;text-align:justify;color:#374151;">We sent you a gift card you can redeem at hundreds of Canadian brands — one click is all it takes.<br />
|
style="font-family:Plus Jakarta Sans, Helvetica, Arial, sans-serif;font-size:16px;line-height:1.5;text-align:justify;color:#374151;">We just wanted to make sure you didn't miss it — the gift card we sent you can be redeemed at hundreds of Canadian brands in just a few clicks.<br />
|
||||||
The window is closing soon. If you don't claim your gift before then, it'll no longer be available.</div>
|
If you'd rather not use it, no worries — no need to reply to this email.</div>
|
||||||
|
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
|
|
@ -232,8 +232,8 @@ table, td { color: #1B2E24; } #u_body a { color: #00C853; text-decoration: under
|
||||||
align="left" style="font-size:0px;padding:10px 25px;padding-bottom:14px;word-break:break-word;"
|
align="left" style="font-size:0px;padding:10px 25px;padding-bottom:14px;word-break:break-word;"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
style="font-family:Plus Jakarta Sans, Helvetica, Arial, sans-serif;font-size:18px;font-weight:700;line-height:1.4;text-align:left;color:#D03A0A;"
|
style="font-family:Plus Jakarta Sans, Helvetica, Arial, sans-serif;font-size:17px;font-weight:600;line-height:1.4;text-align:left;color:#1B2E24;"
|
||||||
>⏰ Hâte-toi ! Ton cadeau de {{amount}} expire le {{expires_at_date}}.</div>
|
>🎁 Ton cadeau de {{amount}} reste disponible jusqu'au {{expires_at_date}}.</div>
|
||||||
|
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
@ -244,8 +244,8 @@ table, td { color: #1B2E24; } #u_body a { color: #00C853; text-decoration: under
|
||||||
align="left" style="font-size:0px;padding:10px 25px;padding-bottom:0;word-break:break-word;"
|
align="left" style="font-size:0px;padding:10px 25px;padding-bottom:0;word-break:break-word;"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
style="font-family:Plus Jakarta Sans, Helvetica, Arial, sans-serif;font-size:16px;line-height:1.5;text-align:justify;color:#374151;">On t'a envoyé une carte-cadeau à utiliser chez des centaines de marques canadiennes — il te suffit d'un clic pour la réclamer.<br />
|
style="font-family:Plus Jakarta Sans, Helvetica, Arial, sans-serif;font-size:16px;line-height:1.5;text-align:justify;color:#374151;">On voulait juste s'assurer que tu ne l'as pas manqué — la carte-cadeau qu'on t'a envoyée peut s'utiliser chez des centaines de marques canadiennes, en quelques clics.<br />
|
||||||
Tu n'as encore rien fait, et le délai approche. Si tu n'utilises pas ton cadeau d'ici là, il ne pourra plus être réclamé.</div>
|
Si tu préfères ne pas l'utiliser, aucun souci — pas besoin de répondre à ce courriel.</div>
|
||||||
|
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue
Block a user