9fb6fab88e
8 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
2bc9715485 |
feat(campaigns): CSV report, manual recipients, template polish
Ops UI - CampaignDetailPage: "CSV" button — downloads per-recipient report (shortlinks, status, opened/clicked timestamps, mailjet UUID) - CampaignNewPage: "Saisie manuelle (sans CSV)" on Step 1 and "Ajouter manuellement" on Step 2 — both open the same dialog with firstname / email / gift_url / city / postal_code / language / amount override. Indigo "manuel" chip in the recipients table. - New "Ville" column shows city OR postal_code as fallback. Hub - GET /campaigns/:id/report.csv — RFC 4180 CSV with UTF-8 BOM so Excel auto-detects encoding. 20 columns including new "city". - Worker honours per-recipient amount override: r.amount > derive from r.gift_value_cents > params.amount > "50 $". Fixes manual-add showing campaign default instead of typed value. - Default subject "Un cadeau pour toi" (tutoyer). Templates - Order: Intro → ✅ Option 1 → 🎁 marques → CTA → prorata → ⏭️ Option 2. - New EN intro (manifesto): "Thank you for choosing local. Your support helps keep our community connected. / Because great connections aren't just about fiber — they're about people too." - Amazon logo removed (incongruent with "achat local" framing). - Body paragraphs: text-align justify (greeting/labels stay left). - Support line: "N'hésite pas à nous écrire / Feel free to email us" + dash format 514-448-0773, drop "Support 7j/7" overpromise. - Logo style fix: inline width:32px to beat Unlayer canvas CSS that was rendering brand pills full-width. Ignore template converter .bak-*.json backups. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
40a2e4e8f2 |
fix(campaigns/templates): drop 'sans engagement' claim from Option 2
The 'sans engagement ni carte-cadeau' wording in Option 2 was confusing
for customers with an existing multi-month commitment — implies their
subscription is commitment-free, which contradicts their actual contract.
Reworded to make zero claims about the customer's commitment status,
just describes what happens if they ignore the email:
FR:
before — 'Ne rien faire. Ton abonnement mensuel se poursuit normalement,
sans engagement ni carte-cadeau.'
after — 'Ne rien faire. Aucun changement à ton abonnement actuel.'
EN (Gemini copywriter version was different from earlier templates):
before — 'Just kick back! Your monthly subscription will continue as
usual, with no commitment and no gift card.'
after — 'Do nothing. No changes to your current subscription.'
Benefits:
• No false claim about engagement status
• Shorter, more direct
• Still preserves the explicit consent UX (customer knows ignoring
is a valid choice without consequence)
• No mention of 'no gift card' — that's implicit from not clicking
the CTA, doesn't need to be stated
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
||
|
|
d76a922777 |
feat(campaigns/templates): inline merchant logos via simple <img> sequence
The previous attempt (commit
|
||
|
|
3f72608a2f |
feat(campaigns/templates): inline merchant logos + objective prorata phrasing
Two refinements per user feedback:
1. Objective/factual prorata disclaimer (shorter, conditions-of-service tone)
FR:
before — "Si tu annules avant {{commitment_months}} mois, tu rembourses
seulement au prorata des mois restants."
after — "Annulation avant {{commitment_months}} mois : seulement à
rembourser au prorata des mois restants."
EN:
before — "If you cancel before {{commitment_months}} months, you only
refund the prorated amount for the remaining months."
after — "Cancellation before {{commitment_months}} months: only the
prorated amount for the remaining months is refundable."
The colon-prefixed structure ("X : Y") reads like a T&C bullet rather
than a marketing sentence — clearer, less wordy, no subject pronoun.
2. Inline row of 6 merchant logos in the offer info pill
Inserted between the "60 $ chez des centaines de marques" line and the
"Instant activation" line. 6 most recognizable QC brands at 32px wide:
Amazon · Tim Hortons · Walmart · Home Depot · IGA · Home Hardware
Followed by "et plus" / "and more" caption.
Uses the existing Mailjet-hosted brand logos (same URLs as the 4×3 grid
in the older rich variant). 32px width fits comfortably on one line
(~280px total in a 484px-wide pill). Email-safe single-row table layout
with vertical-align middle, padding-right 8px for spacing.
Visual effect: instant recognition for the reader — they see the brands
they'd actually redeem at, without dropping the full 12-logo grid that
bloated the previous design.
Applied to .html + .json (both FR + EN) via anchor-based injection:
finds the "🎁 {{amount}} chez/at hundreds of marques/brands" paragraph,
inserts the logo table immediately after its closing </p>. Both files
remain valid + the Unlayer editor will pick up the new table next load.
Verified live via test-send (35-37 KB output, recipient queue ok).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
||
|
|
5327112717 |
fix(campaigns/templates): clearer prorata phrasing — "mois restants" / "remaining months"
Iterating on the prorata disclaimer per user feedback. The previous
version ("tu rembourses le prorata non utilisé (20 $/mois)") still
read ambiguously — "non utilisé" could mean "the portion you haven't
spent" which is conceptually confusing for a one-time gift card, and
the hardcoded "$20/month" tied the template to the specific
$60/3-month campaign.
New phrasing makes the math explicit: refund only for the months
you're NOT staying.
FR:
before — "Si tu résilies avant {{commitment_months}} mois,
tu rembourses le prorata non utilisé (20 $/mois)."
after — "Si tu annules avant {{commitment_months}} mois,
tu rembourses seulement au prorata des mois restants."
EN:
before — "If you cancel before {{commitment_months}} months,
you refund the unused pro-rated amount ($20/month)."
after — "If you cancel before {{commitment_months}} months,
you only refund the prorated amount for the remaining months."
Wins:
• Subject ("tu" / "you") explicit — no ambiguity on who refunds
• Logic clarified — refund == months NOT STAYED, not "unused
portion of money" (which doesn't quite map to a one-time gift)
• Generic over campaign params — no hardcoded "$20/month" so the
template works at any gift amount + commitment combination
• "annules" (more common in QC consumer-facing) instead of
"résilies" (slightly more formal/legal-sounding)
Applied via direct find/replace on .html + .json (FR + EN). Live
test-send queued to confirm rendering.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
||
|
|
f37b1d2803 |
fix(campaigns/templates): clarify who refunds whom for early cancellation
User feedback: the prorata disclaimer was semantically wrong — both FR
("le prorata du montant est remboursable") and EN ("we'll refund the
pro-rated amount") read as if TARGO would refund the customer, when
actually the customer needs to refund the unused portion of the gift
they received if they cancel within the commitment period.
Plus: add the explicit per-month rate ($20/month at $60 / 3 months) so
the customer knows exactly what they'd owe at any cancellation date.
FR:
before — "🪂 En cas de départ avant {{commitment_months}} mois,
le prorata du montant est remboursable."
after — "🪂 Si tu résilies avant {{commitment_months}} mois,
tu rembourses le prorata non utilisé (20 $/mois)."
EN:
before — "🪂 If you decide to leave before {{commitment_months}}
months, we'll refund the pro-rated amount."
after — "🪂 If you cancel before {{commitment_months}} months,
you refund the unused pro-rated amount ($20/month)."
Both changes:
• Subject clarified: customer refunds, not TARGO
• Added explicit per-month value for transparency
• Kept warm tone (informal "tu" / "you")
• Mustache {{commitment_months}} preserved
Applied directly to .html + .json via string substitution (preserves
the Unlayer design tree intact except for that one phrase). The
"$20/month" figure is hardcoded for the current $60/3-month campaign;
a future {{monthly_prorata}} computed variable would generalize but
isn't needed yet.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
||
|
|
d716e69ef6 |
feat(campaigns/templates): mirror user's FR edits to EN + drop legacy .mjml
User edited gift-email-fr in the Unlayer editor with richer marketing
copy (loyalty thanks, brand manifesto, 3.5 Gbit/s upsell, helpful CTA).
Mirror those edits to the EN template via a one-shot translation script
so the bilingual pair stays in sync for the next campaign send.
Translation strategy: plain-string find/replace mapping with FR
phrases in longest-first order to avoid partial matches. Applied to
BOTH the rendered .html (what the recipient sees) AND the .json
(Unlayer design tree — so re-opening the EN editor preserves the
matching structure).
Mapping coverage:
• Intro paragraphs (greeting, gift announcement, loyalty thanks,
brand manifesto, speed upsell, "we're around the corner")
• Offer info pill (amount, instant activation, commitment)
• CTA button labels (Activer → Redeem, Choisir → Pick)
• Prorata refund disclaimer
• Option 2 "do nothing" text
• Signature ("Merci de faire rouler" → "Thanks for helping...thrive")
• Footer contact info + "Tous droits réservés" → "All rights reserved"
• <html lang="fr"> → <html lang="en">
23/28 translation rules matched; the 5 unused ones were for legacy
phrasing not present in the user's latest save (e.g. the old "Tu
choisis local" line that was replaced by the current intro).
Also: drop the obsolete .mjml source files. Now that Unlayer is the
canonical editor, the MJML→HTML compile pipeline is no longer used
on save (Unlayer outputs HTML directly). The .mjml files were stale
copies from the previous MJML-based editor. Removed from disk on
prod and from git history; rollback via git revert if needed.
Verified live: GET /campaigns/templates/gift-email-en returns the
translated content (9 EN markers detected in HTML). Test-send to
louis@targo.ca queued via Mailjet for visual QA.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
||
|
|
448e62177e |
feat(campaigns): convert existing HTML templates to Unlayer JSON designs
Solve the "editor starts blank" problem by writing a one-time converter
that wraps each compiled .html template into a minimal Unlayer design
JSON (one Custom HTML block containing the entire body content). On
next editor load, Unlayer reads .json and renders the template in the
canvas — instant visual fidelity without manual reconstruction.
Strategy choice: Unlayer's "Import HTML" is a Pro-only feature. Building
a real HTML→Unlayer-blocks parser is several days of work. The minimal-
viable conversion (1 row + 1 Custom HTML block) gets the user 90% there
immediately:
• Canvas shows the template visually (Unlayer renders the HTML)
• Variables ({{firstname}}, {{gift_url}}, etc.) preserved as text
• User can edit the HTML directly via the block's side panel
• User can incrementally REPLACE the HTML block with native Unlayer
blocks (Text, Image, Button) for any section they want decomposed —
on their own schedule, not blocking the campaign send
New file: services/targo-hub/scripts/convert-html-to-unlayer.js
• CLI: node scripts/convert-html-to-unlayer.js <template-name>
• Reads templates/<name>.html, extracts <body> inner content, detects
preheader from a hidden <div style="display:none">, builds Unlayer
design JSON with brand-appropriate body.values (Targo Green link
color #00C853, Plus Jakarta Sans font, F5FAF7 page background).
• Backs up existing .json before overwriting.
Generated outputs (committed):
templates/gift-email-fr.json — 34 KB (30 KB inner HTML + Unlayer chrome)
templates/gift-email-en.json — 33 KB
Live verification: GET /campaigns/templates/gift-email-fr now returns
{ design: {...Unlayer JSON...} } alongside html. The editor's
onReady() callback in TemplateEditorPage detects data.design and calls
editor.loadDesign(design) → canvas populated immediately.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|