Events: personal rsvp_url per-language, address field, logo spacing
- rsvpLink() now appends &lang=<en|fr> when generating a recipient's personal link, so the English invitation's link opens the English RSVP form instead of defaulting to French. Threaded through sendTest and the mass-send worker. - New optional per-language "address" field (editor input, SEED default for fete-20-ans, Unlayer template var), rendered under the date with a pin icon in both the festive email and the RSVP page. - Logo: +20px bottom margin in both the email template and the RSVP page. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
cab1a9c94a
commit
2a9f201826
|
|
@ -156,6 +156,9 @@
|
||||||
<q-input v-if="lg === 'en'" v-model="form.en.name" dense outlined label="Name" />
|
<q-input v-if="lg === 'en'" v-model="form.en.name" dense outlined label="Name" />
|
||||||
<q-input v-model="form[lg].tagline" dense outlined autogrow label="Accroche" />
|
<q-input v-model="form[lg].tagline" dense outlined autogrow label="Accroche" />
|
||||||
<q-input v-model="form[lg].when" dense outlined :label="lg === 'fr' ? 'Quand (texte)' : 'When (text)'" hint="ex. 1ᵉʳ août, de 10 h à 15 h" />
|
<q-input v-model="form[lg].when" dense outlined :label="lg === 'fr' ? 'Quand (texte)' : 'When (text)'" hint="ex. 1ᵉʳ août, de 10 h à 15 h" />
|
||||||
|
<q-input v-model="form[lg].address" dense outlined :label="lg === 'fr' ? 'Adresse (texte, optionnel)' : 'Address (text, optional)'" hint="ex. À nos bureaux : 1867 chemin de la rivière, Ste-Clotilde">
|
||||||
|
<template #prepend><q-icon name="place" /></template>
|
||||||
|
</q-input>
|
||||||
<q-input v-model="form[lg].body" dense outlined type="textarea" autogrow :label="lg === 'fr' ? 'Texte d\'invitation' : 'Invitation text'" />
|
<q-input v-model="form[lg].body" dense outlined type="textarea" autogrow :label="lg === 'fr' ? 'Texte d\'invitation' : 'Invitation text'" />
|
||||||
|
|
||||||
<div class="text-weight-medium text-grey-8 q-mt-sm">Programme</div>
|
<div class="text-weight-medium text-grey-8 q-mt-sm">Programme</div>
|
||||||
|
|
@ -542,7 +545,7 @@ const uploadingAtt = ref(false)
|
||||||
function attLangLabel (l) { return l === 'fr' ? 'FR' : l === 'en' ? 'EN' : 'Les deux' }
|
function attLangLabel (l) { return l === 'fr' ? 'FR' : l === 'en' ? 'EN' : 'Les deux' }
|
||||||
function attLangColor (l) { return l === 'fr' ? 'blue-7' : l === 'en' ? 'deep-orange-7' : 'grey-6' }
|
function attLangColor (l) { return l === 'fr' ? 'blue-7' : l === 'en' ? 'deep-orange-7' : 'grey-6' }
|
||||||
|
|
||||||
const emptyLang = () => ({ name: '', tagline: '', when: '', body: '', program: [], program_line: '', limited: '', closing: '', notes: [] })
|
const emptyLang = () => ({ name: '', tagline: '', when: '', address: '', body: '', program: [], program_line: '', limited: '', closing: '', notes: [] })
|
||||||
const emptyForm = () => ({ id: '', active: true, date_iso: '', badge_top: '', badge_bottom: '', capacity: 0, email_template: '', fr: emptyLang(), en: emptyLang() })
|
const emptyForm = () => ({ id: '', active: true, date_iso: '', badge_top: '', badge_bottom: '', capacity: 0, email_template: '', fr: emptyLang(), en: emptyLang() })
|
||||||
const form = ref(emptyForm())
|
const form = ref(emptyForm())
|
||||||
|
|
||||||
|
|
@ -581,7 +584,7 @@ async function openEdit () {
|
||||||
for (const lg of ['fr', 'en']) {
|
for (const lg of ['fr', 'en']) {
|
||||||
const src = c[lg] || {}
|
const src = c[lg] || {}
|
||||||
f[lg] = {
|
f[lg] = {
|
||||||
name: src.name || '', tagline: src.tagline || '', when: src.when || '', body: src.body || '',
|
name: src.name || '', tagline: src.tagline || '', when: src.when || '', address: src.address || '', body: src.body || '',
|
||||||
program: Array.isArray(src.program) ? src.program.map(p => ({ icon: p.icon || '', label: p.label || '', sub: p.sub || '' })) : [],
|
program: Array.isArray(src.program) ? src.program.map(p => ({ icon: p.icon || '', label: p.label || '', sub: p.sub || '' })) : [],
|
||||||
program_line: src.program_line || '', limited: src.limited || '', closing: src.closing || '',
|
program_line: src.program_line || '', limited: src.limited || '', closing: src.closing || '',
|
||||||
notes: Array.isArray(src.notes) ? [...src.notes] : [],
|
notes: Array.isArray(src.notes) ? [...src.notes] : [],
|
||||||
|
|
@ -605,7 +608,7 @@ async function openInvitePreview () { invitePreviewOpen.value = true; await load
|
||||||
|
|
||||||
function cleanLang (c) {
|
function cleanLang (c) {
|
||||||
return {
|
return {
|
||||||
name: (c.name || '').trim(), tagline: (c.tagline || '').trim(), when: (c.when || '').trim(), body: (c.body || '').trim(),
|
name: (c.name || '').trim(), tagline: (c.tagline || '').trim(), when: (c.when || '').trim(), address: (c.address || '').trim(), body: (c.body || '').trim(),
|
||||||
program: (c.program || []).filter(p => (p.label || '').trim() || (p.icon || '').trim()),
|
program: (c.program || []).filter(p => (p.label || '').trim() || (p.icon || '').trim()),
|
||||||
program_line: (c.program_line || '').trim(), limited: (c.limited || '').trim(), closing: (c.closing || '').trim(),
|
program_line: (c.program_line || '').trim(), limited: (c.limited || '').trim(), closing: (c.closing || '').trim(),
|
||||||
notes: (c.notes || []).map(n => (n || '').trim()).filter(Boolean),
|
notes: (c.notes || []).map(n => (n || '').trim()).filter(Boolean),
|
||||||
|
|
|
||||||
|
|
@ -119,6 +119,7 @@ const SEED_CONTENT = {
|
||||||
name: 'Targo fête ses 20 ans',
|
name: 'Targo fête ses 20 ans',
|
||||||
tagline: "Toute l'équipe de Targo est heureuse de vous inviter à notre grande fête d'anniversaire !",
|
tagline: "Toute l'équipe de Targo est heureuse de vous inviter à notre grande fête d'anniversaire !",
|
||||||
when: '1ᵉʳ août, de 10 h à 15 h',
|
when: '1ᵉʳ août, de 10 h à 15 h',
|
||||||
|
address: 'À nos bureaux : 1867 chemin de la rivière, Ste-Clotilde',
|
||||||
body: "Joignez-vous à nous pour une journée de plaisir, de rencontres et de célébration en compagnie des employés de Targo et de leurs familles.",
|
body: "Joignez-vous à nous pour une journée de plaisir, de rencontres et de célébration en compagnie des employés de Targo et de leurs familles.",
|
||||||
program: [
|
program: [
|
||||||
{ icon: '🚚', label: 'Food truck', sub: 'repas inclus' },
|
{ icon: '🚚', label: 'Food truck', sub: 'repas inclus' },
|
||||||
|
|
@ -139,6 +140,7 @@ const SEED_CONTENT = {
|
||||||
name: 'Targo turns 20!',
|
name: 'Targo turns 20!',
|
||||||
tagline: 'The whole Targo team is happy to invite you to our big anniversary celebration!',
|
tagline: 'The whole Targo team is happy to invite you to our big anniversary celebration!',
|
||||||
when: 'August 1st, 10 a.m. to 3 p.m.',
|
when: 'August 1st, 10 a.m. to 3 p.m.',
|
||||||
|
address: 'At our offices: 1867 chemin de la rivière, Ste-Clotilde',
|
||||||
body: 'Join us for a day of fun, meeting people and celebrating alongside the Targo team and their families.',
|
body: 'Join us for a day of fun, meeting people and celebrating alongside the Targo team and their families.',
|
||||||
program: [
|
program: [
|
||||||
{ icon: '🚚', label: 'Food truck', sub: 'meal included' },
|
{ icon: '🚚', label: 'Food truck', sub: 'meal included' },
|
||||||
|
|
@ -224,6 +226,7 @@ function sanitizeLangContent (c) {
|
||||||
name: String(c.name || '').slice(0, 160),
|
name: String(c.name || '').slice(0, 160),
|
||||||
tagline: String(c.tagline || '').slice(0, 300),
|
tagline: String(c.tagline || '').slice(0, 300),
|
||||||
when: String(c.when || '').slice(0, 120),
|
when: String(c.when || '').slice(0, 120),
|
||||||
|
address: String(c.address || '').slice(0, 200),
|
||||||
body: String(c.body || '').slice(0, 1200),
|
body: String(c.body || '').slice(0, 1200),
|
||||||
program: sanitizeProgram(c.program),
|
program: sanitizeProgram(c.program),
|
||||||
program_line: String(c.program_line || '').slice(0, 200),
|
program_line: String(c.program_line || '').slice(0, 200),
|
||||||
|
|
@ -299,9 +302,14 @@ function keyFor (resolved, form) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Lien perso + courriel d'invitation ─────────────────────────────────────
|
// ── Lien perso + courriel d'invitation ─────────────────────────────────────
|
||||||
function rsvpLink (eventId, customerId, name, email, ttlHours = 60 * 24) {
|
// `lang` (optionnel) → ajouté en ?lang= EXPLICITE sur l'URL : la page RSVP priorise TOUJOURS ce
|
||||||
|
// paramètre de requête sur la langue du jeton (cf handle() : `if (!url.searchParams.get('lang') && p.lang)`).
|
||||||
|
// Sans ça, la version EN du courriel pointait vers la même URL que la FR (défaut FR). Nécessaire
|
||||||
|
// même si le modèle éditable (Unlayer) ne permet pas de fixer un href différent par langue.
|
||||||
|
function rsvpLink (eventId, customerId, name, email, ttlHours = 60 * 24, lang = '') {
|
||||||
const tok = generateCustomerToken(customerId, name, email, ttlHours)
|
const tok = generateCustomerToken(customerId, name, email, ttlHours)
|
||||||
return `${pub()}/rsvp/${encodeURIComponent(eventId)}?t=${encodeURIComponent(tok)}`
|
const l = lang ? `&lang=${encodeURIComponent(normLang(lang))}` : ''
|
||||||
|
return `${pub()}/rsvp/${encodeURIComponent(eventId)}?t=${encodeURIComponent(tok)}${l}`
|
||||||
}
|
}
|
||||||
function inviteSubject (eventId, lang) {
|
function inviteSubject (eventId, lang) {
|
||||||
const e = getEvent(eventId); if (!e) return ''
|
const e = getEvent(eventId); if (!e) return ''
|
||||||
|
|
@ -312,7 +320,7 @@ function inviteTemplateVars (e, lang, name, rsvpUrl) {
|
||||||
const t = e[lang]
|
const t = e[lang]
|
||||||
return {
|
return {
|
||||||
firstname: firstName(name), name: name || '', rsvp_url: rsvpUrl, gift_url: rsvpUrl, // gift_url = alias (templates cadeau réutilisables)
|
firstname: firstName(name), name: name || '', rsvp_url: rsvpUrl, gift_url: rsvpUrl, // gift_url = alias (templates cadeau réutilisables)
|
||||||
event_name: t.name, tagline: t.tagline, when: t.when, body: t.body, closing: t.closing,
|
event_name: t.name, tagline: t.tagline, when: t.when, address: t.address, body: t.body, closing: t.closing,
|
||||||
year: String(new Date().getFullYear()), lang,
|
year: String(new Date().getFullYear()), lang,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -345,12 +353,13 @@ function inviteEmailFestive (e, lang, name, rsvpUrl) {
|
||||||
+ `<tr><td align="center" style="padding:22px 30px 0">`
|
+ `<tr><td align="center" style="padding:22px 30px 0">`
|
||||||
+ `<div style="font-size:24px;letter-spacing:6px;line-height:1">🎈🎉🎈</div>`
|
+ `<div style="font-size:24px;letter-spacing:6px;line-height:1">🎈🎉🎈</div>`
|
||||||
+ (badge ? `<div style="display:inline-block;background:#21a34a;color:#fff;font-weight:800;font-size:12px;letter-spacing:1.5px;padding:6px 16px;border-radius:999px;margin:10px 0 8px">🎉 ${esc(badge)} 🎉</div><br>` : '<div style="height:10px"></div>')
|
+ (badge ? `<div style="display:inline-block;background:#21a34a;color:#fff;font-weight:800;font-size:12px;letter-spacing:1.5px;padding:6px 16px;border-radius:999px;margin:10px 0 8px">🎉 ${esc(badge)} 🎉</div><br>` : '<div style="height:10px"></div>')
|
||||||
+ `<img src="${LOGO}" alt="TARGO" width="150" style="width:150px;max-width:150px;height:auto;display:inline-block;border:0;margin:2px 0"></td></tr>`
|
+ `<img src="${LOGO}" alt="TARGO" width="150" style="width:150px;max-width:150px;height:auto;display:inline-block;border:0;margin:2px 0 22px"></td></tr>`
|
||||||
+ `<tr><td align="center" style="padding:8px 26px 0"><h1 style="margin:6px 0 4px;font-size:27px;font-weight:800;color:#1f2937;line-height:1.15">${esc(t.name)} 🎉</h1>`
|
+ `<tr><td align="center" style="padding:8px 26px 0"><h1 style="margin:6px 0 4px;font-size:27px;font-weight:800;color:#1f2937;line-height:1.15">${esc(t.name)} 🎉</h1>`
|
||||||
+ `<p style="margin:0;font-size:15px;font-weight:700;color:#21a34a;line-height:1.4">${esc(t.tagline)}</p></td></tr>`
|
+ `<p style="margin:0;font-size:15px;font-weight:700;color:#21a34a;line-height:1.4">${esc(t.tagline)}</p></td></tr>`
|
||||||
+ `<tr><td style="padding:16px 34px 4px"><p style="margin:0 0 10px;font-size:15px;line-height:1.6;color:#475569">${esc(t.greet(firstName(name)) || '')}</p>`
|
+ `<tr><td style="padding:16px 34px 4px"><p style="margin:0 0 10px;font-size:15px;line-height:1.6;color:#475569">${esc(t.greet(firstName(name)) || '')}</p>`
|
||||||
+ `<p style="margin:0;font-size:15px;line-height:1.6;color:#475569">${esc(t.body)}</p></td></tr>`
|
+ `<p style="margin:0;font-size:15px;line-height:1.6;color:#475569">${esc(t.body)}</p></td></tr>`
|
||||||
+ (t.when ? `<tr><td align="center" style="padding:16px 30px 6px"><table role="presentation" cellpadding="0" cellspacing="0"><tr><td style="background:#1f2937;border-radius:12px;padding:13px 24px;color:#fff;font-size:18px;font-weight:800">📅 ${esc(t.when)}</td></tr></table></td></tr>` : '')
|
+ (t.when ? `<tr><td align="center" style="padding:16px 30px 6px"><table role="presentation" cellpadding="0" cellspacing="0"><tr><td style="background:#1f2937;border-radius:12px;padding:13px 24px;color:#fff;font-size:18px;font-weight:800">📅 ${esc(t.when)}</td></tr></table></td></tr>` : '')
|
||||||
|
+ (t.address ? `<tr><td align="center" style="padding:2px 30px 6px"><p style="margin:0;font-size:14px;font-weight:600;color:#374151">📍 ${esc(t.address)}</p></td></tr>` : '')
|
||||||
+ ((t.program || []).length ? `<tr><td style="padding:14px 18px 4px"><table role="presentation" width="100%" cellpadding="0" cellspacing="0"><tr>${prog}</tr></table></td></tr>` : '')
|
+ ((t.program || []).length ? `<tr><td style="padding:14px 18px 4px"><table role="presentation" width="100%" cellpadding="0" cellspacing="0"><tr>${prog}</tr></table></td></tr>` : '')
|
||||||
+ (t.program_line ? `<tr><td align="center" style="padding:2px 30px 8px"><p style="margin:0;font-size:13px;font-style:italic;color:#94a3b8">${esc(t.program_line)}</p></td></tr>` : '')
|
+ (t.program_line ? `<tr><td align="center" style="padding:2px 30px 8px"><p style="margin:0;font-size:13px;font-style:italic;color:#94a3b8">${esc(t.program_line)}</p></td></tr>` : '')
|
||||||
+ (t.limited ? `<tr><td style="padding:6px 30px 4px"><table role="presentation" width="100%" cellpadding="0" cellspacing="0" style="background:#f3f4f6;border:1px solid #e5e7eb;border-radius:12px"><tr><td style="padding:12px 16px;font-size:14px;line-height:1.6;color:#374151">⏳ ${esc(t.limited)}</td></tr></table></td></tr>` : '')
|
+ (t.limited ? `<tr><td style="padding:6px 30px 4px"><table role="presentation" width="100%" cellpadding="0" cellspacing="0" style="background:#f3f4f6;border:1px solid #e5e7eb;border-radius:12px"><tr><td style="padding:12px 16px;font-size:14px;line-height:1.6;color:#374151">⏳ ${esc(t.limited)}</td></tr></table></td></tr>` : '')
|
||||||
|
|
@ -440,7 +449,7 @@ async function sendTest ({ eventId, emails, channel = 'mailjet', from = '', lang
|
||||||
const atts = attachmentPayload(channel, loadSendAttachments(event, lang)) // pièces jointes de la langue testée (+ 'both')
|
const atts = attachmentPayload(channel, loadSendAttachments(event, lang)) // pièces jointes de la langue testée (+ 'both')
|
||||||
const results = []
|
const results = []
|
||||||
for (const em of emails) {
|
for (const em of emails) {
|
||||||
const link = rsvpLink(eventId, 'TEST-' + em, name || 'Test', em, 24)
|
const link = rsvpLink(eventId, 'TEST-' + em, name || 'Test', em, 24, lang)
|
||||||
const html = inviteEmail(eventId, lang, name, link)
|
const html = inviteEmail(eventId, lang, name, link)
|
||||||
const subject = '[TEST] ' + inviteSubject(eventId, lang)
|
const subject = '[TEST] ' + inviteSubject(eventId, lang)
|
||||||
try { const r = await sendInviteMessage({ channel, to: em, subject, html, from, attachments: atts }); results.push({ email: em, ok: r.ok, error: r.error }) }
|
try { const r = await sendInviteMessage({ channel, to: em, subject, html, from, attachments: atts }); results.push({ email: em, ok: r.ok, error: r.error }) }
|
||||||
|
|
@ -490,7 +499,7 @@ async function sendEventCampaignAsync (id, eventId, channel, from) {
|
||||||
if (r.status !== 'pending') continue
|
if (r.status !== 'pending') continue
|
||||||
const lang = normLang(r.language)
|
const lang = normLang(r.language)
|
||||||
const name = ((r.firstname || '') + ' ' + (r.lastname || '')).trim()
|
const name = ((r.firstname || '') + ' ' + (r.lastname || '')).trim()
|
||||||
const rsvpUrl = rsvpLink(eventId, r.customer_id || ('x-' + r.email), name, r.email)
|
const rsvpUrl = rsvpLink(eventId, r.customer_id || ('x-' + r.email), name, r.email, 60 * 24, lang)
|
||||||
r.rsvp_url = rsvpUrl
|
r.rsvp_url = rsvpUrl
|
||||||
const html = inviteEmail(eventId, lang, name, rsvpUrl)
|
const html = inviteEmail(eventId, lang, name, rsvpUrl)
|
||||||
const subject = inviteSubject(eventId, lang)
|
const subject = inviteSubject(eventId, lang)
|
||||||
|
|
@ -749,7 +758,7 @@ body{margin:0;font-family:-apple-system,'Segoe UI',Roboto,Helvetica,Arial,sans-s
|
||||||
background:var(--g);color:#fff;font-weight:800;line-height:1;box-shadow:0 8px 20px rgba(33,163,74,.3);margin-bottom:12px}
|
background:var(--g);color:#fff;font-weight:800;line-height:1;box-shadow:0 8px 20px rgba(33,163,74,.3);margin-bottom:12px}
|
||||||
.badge b{font-size:1.35rem}.badge span{font-size:.6rem;letter-spacing:1px}
|
.badge b{font-size:1.35rem}.badge span{font-size:.6rem;letter-spacing:1px}
|
||||||
.badge.emoji{font-size:2rem}
|
.badge.emoji{font-size:2rem}
|
||||||
.logo{height:34px;width:auto;display:block;margin:0 auto 14px}
|
.logo{height:34px;width:auto;display:block;margin:0 auto 34px}
|
||||||
h1{font-size:1.7rem;font-weight:800;letter-spacing:-.5px;margin:0 0 6px;color:var(--ink)}
|
h1{font-size:1.7rem;font-weight:800;letter-spacing:-.5px;margin:0 0 6px;color:var(--ink)}
|
||||||
.tag{color:var(--g);font-weight:700;font-size:1.02rem;margin:0 0 4px}
|
.tag{color:var(--g);font-weight:700;font-size:1.02rem;margin:0 0 4px}
|
||||||
.card{background:#fff;border:1px solid #eef2f0;border-radius:20px;box-shadow:0 16px 44px rgba(23,58,94,.09);padding:26px 22px;margin-bottom:16px}
|
.card{background:#fff;border:1px solid #eef2f0;border-radius:20px;box-shadow:0 16px 44px rgba(23,58,94,.09);padding:26px 22px;margin-bottom:16px}
|
||||||
|
|
@ -757,6 +766,7 @@ p{line-height:1.6;font-size:1rem;margin:0 0 12px;color:#334155}
|
||||||
.when{display:flex;align-items:center;justify-content:center;gap:10px;background:var(--ink);color:#fff;font-weight:700;
|
.when{display:flex;align-items:center;justify-content:center;gap:10px;background:var(--ink);color:#fff;font-weight:700;
|
||||||
font-size:1.12rem;border-radius:14px;padding:13px 18px;margin:4px 0 16px}
|
font-size:1.12rem;border-radius:14px;padding:13px 18px;margin:4px 0 16px}
|
||||||
.when .cal{font-size:1.3rem}
|
.when .cal{font-size:1.3rem}
|
||||||
|
.addr{display:flex;align-items:center;justify-content:center;gap:6px;color:#374151;font-weight:600;font-size:.92rem;margin:-8px 0 16px;text-align:center}
|
||||||
.prog-wrap{display:flex;flex-wrap:wrap;gap:10px;justify-content:center;margin:6px 0 4px}
|
.prog-wrap{display:flex;flex-wrap:wrap;gap:10px;justify-content:center;margin:6px 0 4px}
|
||||||
.prog{flex:1 1 90px;max-width:110px;text-align:center;background:#f8fafc;border:1px solid #eef2f6;border-radius:14px;padding:12px 6px}
|
.prog{flex:1 1 90px;max-width:110px;text-align:center;background:#f8fafc;border:1px solid #eef2f6;border-radius:14px;padding:12px 6px}
|
||||||
.prog-ic{font-size:1.7rem;line-height:1}
|
.prog-ic{font-size:1.7rem;line-height:1}
|
||||||
|
|
@ -799,6 +809,7 @@ button:hover{filter:brightness(1.05)}button:active{transform:translateY(1px)}but
|
||||||
|
|
||||||
<div id="invite" class="card">
|
<div id="invite" class="card">
|
||||||
${t.when ? `<div class="when"><span class="cal">📅</span><span>${esc(t.when)}</span></div>` : ''}
|
${t.when ? `<div class="when"><span class="cal">📅</span><span>${esc(t.when)}</span></div>` : ''}
|
||||||
|
${t.address ? `<div class="addr">📍 ${esc(t.address)}</div>` : ''}
|
||||||
<p>${esc(t.body)}</p>
|
<p>${esc(t.body)}</p>
|
||||||
${program ? `<div class="prog-wrap">${program}</div>` : ''}
|
${program ? `<div class="prog-wrap">${program}</div>` : ''}
|
||||||
${t.program_line ? `<p style="text-align:center;font-style:italic;color:var(--muted);margin-top:12px">${esc(t.program_line)}</p>` : ''}
|
${t.program_line ? `<p style="text-align:center;font-style:italic;color:var(--muted);margin-top:12px">${esc(t.program_line)}</p>` : ''}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user