@xuda.io/account_module 1.2.2281 → 1.2.2282
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/index.mjs +42 -27
- package/package.json +1 -1
package/index.mjs
CHANGED
|
@@ -5921,15 +5921,23 @@ const _nl_avatar_url = (host, pic) => {
|
|
|
5921
5921
|
return '';
|
|
5922
5922
|
};
|
|
5923
5923
|
|
|
5924
|
-
const _nl_render_email = (issue, { host, first_name, fashion, partners, unsub_url }) => {
|
|
5924
|
+
const _nl_render_email = (issue, { host, first_name, fashion, partners, unsub_url, theme }) => {
|
|
5925
5925
|
const c = issue.content || {};
|
|
5926
5926
|
const esc = _nl_esc;
|
|
5927
|
+
const dark = theme === 'dark';
|
|
5928
|
+
// Palette: light (default/production) vs dark (the "whole box dark" variant).
|
|
5929
|
+
const pal = dark
|
|
5930
|
+
? { pageBg: '#0b1020', card: '#151c30', cardBorder: '#28324e', ink: '#ffffff', body: '#c3cbdb', muted: '#8b93a8', accentCard: '#13233c', accentInk: '#66BDFF', accentBody: '#c3cbdb', pill: '#152036', pillInk: '#8ecbff', imgBg: '#0e1424', footBorder: '#28324e' }
|
|
5931
|
+
: { pageBg: '#ffffff', card: '#ffffff', cardBorder: '#e9edf3', ink: '#0b1220', body: '#475569', muted: '#94a3b8', accentCard: '#eff6ff', accentInk: '#0b66c2', accentBody: '#334155', pill: '#eff6ff', pillInk: '#0b66c2', imgBg: '#f4f6fa', footBorder: '#e9edf3' };
|
|
5932
|
+
// A subtle border so the already-dark cards (news/feature/fashion) separate from
|
|
5933
|
+
// the dark page background instead of merging into it.
|
|
5934
|
+
const sep = dark ? ';border:1px solid #28324e' : '';
|
|
5927
5935
|
const btn = (href, txt, bg = '#0091FF', fg = '#fff') =>
|
|
5928
5936
|
`<a href="${esc(href)}" style="display:inline-block;background:${bg};color:${fg};text-decoration:none;padding:11px 24px;border-radius:9px;font-weight:700;font-size:14px">${esc(txt)} →</a>`;
|
|
5929
5937
|
|
|
5930
5938
|
// A white content card with an optional top image. imgFit 'cover' crops to a
|
|
5931
5939
|
// banner; 'contain' shows the WHOLE image (used for the t-shirt) on imgBg.
|
|
5932
|
-
const card = ({ img, imgAlt, emoji, label, title, href, text, btnText, btnHref, imgFit = 'cover', imgBg =
|
|
5940
|
+
const card = ({ img, imgAlt, emoji, label, title, href, text, btnText, btnHref, imgFit = 'cover', imgBg = pal.imgBg, imgMaxH = '210px' }) => {
|
|
5933
5941
|
let image = '';
|
|
5934
5942
|
if (img) {
|
|
5935
5943
|
const inner = imgFit === 'contain'
|
|
@@ -5937,15 +5945,15 @@ const _nl_render_email = (issue, { host, first_name, fashion, partners, unsub_ur
|
|
|
5937
5945
|
: `<img src="${esc(img)}" alt="${esc(imgAlt || '')}" width="100%" style="display:block;width:100%;max-height:${imgMaxH};object-fit:cover">`;
|
|
5938
5946
|
image = `<a href="${esc(href || btnHref || '#')}" style="text-decoration:none">${inner}</a>`;
|
|
5939
5947
|
}
|
|
5940
|
-
const titleHtml = href ? `<a href="${esc(href)}" style="color
|
|
5948
|
+
const titleHtml = href ? `<a href="${esc(href)}" style="color:${pal.ink};text-decoration:none">${esc(title)}</a>` : esc(title);
|
|
5941
5949
|
return `
|
|
5942
5950
|
<table cellpadding="0" cellspacing="0" width="100%" style="border-collapse:separate;margin:0 0 16px">
|
|
5943
|
-
<tr><td style="background
|
|
5951
|
+
<tr><td style="background:${pal.card};border:1px solid ${pal.cardBorder};border-radius:14px;overflow:hidden">
|
|
5944
5952
|
${image}
|
|
5945
5953
|
<div style="padding:18px 20px">
|
|
5946
5954
|
<div style="font-size:11px;letter-spacing:.06em;text-transform:uppercase;color:#0091FF;font-weight:700;margin-bottom:7px">${emoji} ${esc(label)}</div>
|
|
5947
|
-
<div style="font-size:18px;font-weight:700;line-height:1.28;color
|
|
5948
|
-
<p style="color
|
|
5955
|
+
<div style="font-size:18px;font-weight:700;line-height:1.28;color:${pal.ink};margin-bottom:7px">${titleHtml}</div>
|
|
5956
|
+
<p style="color:${pal.body};font-size:14px;line-height:1.55;margin:0 0 15px">${esc(text)}</p>
|
|
5949
5957
|
${btn(btnHref, btnText)}
|
|
5950
5958
|
</div>
|
|
5951
5959
|
</td></tr>
|
|
@@ -5982,10 +5990,10 @@ const _nl_render_email = (issue, { host, first_name, fashion, partners, unsub_ur
|
|
|
5982
5990
|
if (c.tip2) {
|
|
5983
5991
|
parts.push(`
|
|
5984
5992
|
<table cellpadding="0" cellspacing="0" width="100%" style="border-collapse:separate;margin:0 0 16px">
|
|
5985
|
-
<tr><td style="background
|
|
5986
|
-
<div style="font-size:11px;letter-spacing:.06em;text-transform:uppercase;color
|
|
5987
|
-
<div style="font-size:16px;font-weight:700;color
|
|
5988
|
-
<p style="color
|
|
5993
|
+
<tr><td style="background:${pal.accentCard};border-left:4px solid #0091FF;border-radius:10px;padding:16px 18px">
|
|
5994
|
+
<div style="font-size:11px;letter-spacing:.06em;text-transform:uppercase;color:${pal.accentInk};font-weight:700;margin-bottom:5px">⭐ Tip of the week</div>
|
|
5995
|
+
<div style="font-size:16px;font-weight:700;color:${pal.ink};margin:0 0 5px">${esc(c.tip2.title)}</div>
|
|
5996
|
+
<p style="color:${pal.accentBody};font-size:14px;line-height:1.55;margin:0 0 ${c.tip2.url ? '12px' : '0'}">${esc(c.tip2.text)}</p>
|
|
5989
5997
|
${c.tip2.url ? btn(c.tip2.url, 'See how') : ''}
|
|
5990
5998
|
</td></tr>
|
|
5991
5999
|
</table>`);
|
|
@@ -5996,7 +6004,7 @@ const _nl_render_email = (issue, { host, first_name, fashion, partners, unsub_ur
|
|
|
5996
6004
|
const nurl = `https://${host}/news/${c.news.slug}`;
|
|
5997
6005
|
parts.push(`
|
|
5998
6006
|
<table cellpadding="0" cellspacing="0" width="100%" style="border-collapse:separate;margin:0 0 16px">
|
|
5999
|
-
<tr><td style="background:linear-gradient(160deg,#0b1220,#10213b);border-radius:14px;overflow:hidden">
|
|
6007
|
+
<tr><td style="background:linear-gradient(160deg,#0b1220,#10213b);border-radius:14px;overflow:hidden${sep}">
|
|
6000
6008
|
<a href="${esc(nurl)}" style="text-decoration:none"><img src="https://${esc(host)}/news/${encodeURIComponent(c.news.slug)}/hero.png" alt="${esc(c.news.headline)}" width="100%" style="display:block;width:100%;max-height:230px;object-fit:cover"></a>
|
|
6001
6009
|
<div style="padding:18px 20px">
|
|
6002
6010
|
<div style="font-size:11px;letter-spacing:.06em;text-transform:uppercase;color:#66BDFF;font-weight:700;margin-bottom:7px">📰 News</div>
|
|
@@ -6023,7 +6031,7 @@ const _nl_render_email = (issue, { host, first_name, fashion, partners, unsub_ur
|
|
|
6023
6031
|
const gif = c.feature.gif ? _nl_gif_url(c.feature.gif) : '';
|
|
6024
6032
|
parts.push(`
|
|
6025
6033
|
<table cellpadding="0" cellspacing="0" width="100%" style="border-collapse:separate;margin:0 0 16px">
|
|
6026
|
-
<tr><td style="background:linear-gradient(160deg,#0b1220,#10213b);border-radius:14px;padding:22px">
|
|
6034
|
+
<tr><td style="background:linear-gradient(160deg,#0b1220,#10213b);border-radius:14px;padding:22px${sep}">
|
|
6027
6035
|
<div style="display:inline-block;background:#0091FF;color:#fff;font-size:11px;font-weight:700;letter-spacing:.5px;text-transform:uppercase;padding:4px 11px;border-radius:20px;margin-bottom:14px">⚡ Explore a feature</div>
|
|
6028
6036
|
${gif ? `<a href="https://${esc(host)}/dashboard" style="text-decoration:none"><img src="${esc(gif)}" alt="${esc(c.feature.label)}" width="100%" style="display:block;width:100%;border-radius:10px;border:1px solid #22344f;margin:0 0 14px"></a>` : ''}
|
|
6029
6037
|
<div style="font-size:20px;font-weight:700;color:#ffffff;margin:0 0 6px">${esc(c.feature.label)}</div>
|
|
@@ -6041,7 +6049,7 @@ const _nl_render_email = (issue, { host, first_name, fashion, partners, unsub_ur
|
|
|
6041
6049
|
: '';
|
|
6042
6050
|
parts.push(`
|
|
6043
6051
|
<table cellpadding="0" cellspacing="0" width="100%" style="border-collapse:separate;margin:0 0 16px">
|
|
6044
|
-
<tr><td style="background:linear-gradient(160deg,#160f2b,#0b1220);border-radius:14px;overflow:hidden">
|
|
6052
|
+
<tr><td style="background:linear-gradient(160deg,#160f2b,#0b1220);border-radius:14px;overflow:hidden${sep}">
|
|
6045
6053
|
${teeImg}
|
|
6046
6054
|
<div style="padding:16px 20px 20px">
|
|
6047
6055
|
<div style="font-size:11px;letter-spacing:.06em;text-transform:uppercase;color:#66BDFF;font-weight:700;margin-bottom:7px">👕 Wear the brand</div>
|
|
@@ -6062,27 +6070,27 @@ const _nl_render_email = (issue, { host, first_name, fashion, partners, unsub_ur
|
|
|
6062
6070
|
if (!p) return '';
|
|
6063
6071
|
const av = _nl_avatar_url(host, p.pic);
|
|
6064
6072
|
const avatar = av
|
|
6065
|
-
? `<img src="${esc(av)}" alt="${esc(p.name)}" width="76" height="76" style="border-radius:50%;object-fit:cover;border:2px solid
|
|
6073
|
+
? `<img src="${esc(av)}" alt="${esc(p.name)}" width="76" height="76" style="border-radius:50%;object-fit:cover;border:2px solid ${pal.cardBorder};display:block">`
|
|
6066
6074
|
: `<div style="width:76px;height:76px;border-radius:50%;background:#0091FF;color:#fff;font-size:30px;font-weight:800;line-height:76px;text-align:center">${esc((p.name || '?').slice(0, 1))}</div>`;
|
|
6067
6075
|
const role = _nl_titlecase(p.role || 'Partner');
|
|
6068
6076
|
const emoji = p.role === 'mentor' ? '🧭' : '📣';
|
|
6069
6077
|
const where = p.city ? `${role} in ${esc(_nl_titlecase(p.city))}` : role;
|
|
6070
6078
|
return `
|
|
6071
6079
|
<table cellpadding="0" cellspacing="0" width="100%" style="border-collapse:separate;margin:0 0 12px">
|
|
6072
|
-
<tr><td style="background
|
|
6080
|
+
<tr><td style="background:${pal.card};border:1px solid ${pal.cardBorder};border-radius:14px;padding:18px 20px">
|
|
6073
6081
|
<table cellpadding="0" cellspacing="0" width="100%"><tr>
|
|
6074
6082
|
<td width="88" style="vertical-align:middle">${avatar}</td>
|
|
6075
6083
|
<td style="vertical-align:middle;padding-left:16px">
|
|
6076
6084
|
<div style="font-size:11px;letter-spacing:.06em;text-transform:uppercase;color:#0091FF;font-weight:700;margin-bottom:3px">${emoji} ${esc(where)}</div>
|
|
6077
|
-
<div style="font-size:19px;font-weight:800;color
|
|
6078
|
-
<a href="${esc(p.url)}" style="display:inline-block;background
|
|
6085
|
+
<div style="font-size:19px;font-weight:800;color:${pal.ink};margin:0 0 10px">${esc(p.name)}</div>
|
|
6086
|
+
<a href="${esc(p.url)}" style="display:inline-block;background:${pal.accentCard};color:${pal.accentInk};text-decoration:none;padding:8px 16px;border-radius:8px;font-weight:700;font-size:13px">View profile →</a>
|
|
6079
6087
|
</td>
|
|
6080
6088
|
</tr></table>
|
|
6081
6089
|
</td></tr>
|
|
6082
6090
|
</table>`;
|
|
6083
6091
|
};
|
|
6084
6092
|
parts.push(`<p style="text-align:center;font-size:11px;letter-spacing:.06em;text-transform:uppercase;color:#0091FF;font-weight:700;margin:6px 0 4px">🤝 Your local Xuda team</p>
|
|
6085
|
-
<p style="text-align:center;color
|
|
6093
|
+
<p style="text-align:center;color:${pal.muted};font-size:13px;margin:0 0 14px">Your regional ambassador and mentor are here to help you build.</p>`);
|
|
6086
6094
|
if (amb) parts.push(person_card(amb));
|
|
6087
6095
|
if (men) parts.push(person_card(men));
|
|
6088
6096
|
}
|
|
@@ -6091,15 +6099,19 @@ const _nl_render_email = (issue, { host, first_name, fashion, partners, unsub_ur
|
|
|
6091
6099
|
let dateStr = '';
|
|
6092
6100
|
try { dateStr = new Date(issue.created_ts || Date.now()).toLocaleDateString('en-US', { month: 'long', day: 'numeric', year: 'numeric' }); } catch (e) {}
|
|
6093
6101
|
|
|
6102
|
+
// In dark mode the outer div paints a dark panel behind the whole newsletter.
|
|
6103
|
+
const shell = dark
|
|
6104
|
+
? `font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Helvetica,Arial,sans-serif;background:${pal.pageBg};padding:26px 20px 10px;border-radius:14px`
|
|
6105
|
+
: `font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Helvetica,Arial,sans-serif`;
|
|
6094
6106
|
return `
|
|
6095
|
-
<div style="
|
|
6107
|
+
<div style="${shell}">
|
|
6096
6108
|
<div style="text-align:center;margin:0 0 8px">
|
|
6097
|
-
<span style="display:inline-block;background
|
|
6109
|
+
<span style="display:inline-block;background:${pal.pill};color:${pal.pillInk};font-size:11px;font-weight:700;letter-spacing:.5px;text-transform:uppercase;padding:5px 13px;border-radius:20px">Issue #${esc(issue.issue_number)}${dateStr ? ' · ' + esc(dateStr) : ''}</span>
|
|
6098
6110
|
</div>
|
|
6099
|
-
<h1 style="text-align:center;font-size:28px;font-weight:800;color
|
|
6100
|
-
<p style="text-align:center;color
|
|
6111
|
+
<h1 style="text-align:center;font-size:28px;font-weight:800;color:${pal.ink};margin:0 0 8px">Xuda Weekly</h1>
|
|
6112
|
+
<p style="text-align:center;color:${pal.body};font-size:15px;line-height:1.55;margin:0 auto 24px;max-width:400px"><strong style="color:${pal.ink}">${greet}</strong> here's what's new at Xuda this week: a quick tip, fresh reads, a feature to try, and this week's deal.</p>
|
|
6101
6113
|
${parts.join('')}
|
|
6102
|
-
<p style="text-align:center;color
|
|
6114
|
+
<p style="text-align:center;color:${pal.muted};font-size:12px;line-height:1.7;margin:26px 0 0;border-top:1px solid ${pal.footBorder};padding-top:18px">You're receiving this because you opted in to Xuda updates.<br><a href="${esc(unsub_url)}" style="color:${pal.muted};text-decoration:underline">Unsubscribe</a> · <a href="https://${esc(host)}/dashboard" style="color:${pal.muted};text-decoration:underline">Your account</a></p>
|
|
6103
6115
|
</div>`;
|
|
6104
6116
|
};
|
|
6105
6117
|
|
|
@@ -6117,16 +6129,18 @@ const _nl_sample_recipient = (host) => ({
|
|
|
6117
6129
|
unsub_url: `https://${host}/newsletter/unsubscribe?u=SAMPLE&t=SAMPLE`,
|
|
6118
6130
|
});
|
|
6119
6131
|
|
|
6120
|
-
const _newsletter_send_sample_internal = async (issue, to_email) => {
|
|
6132
|
+
const _newsletter_send_sample_internal = async (issue, to_email, theme) => {
|
|
6121
6133
|
const host = _ops_host();
|
|
6122
6134
|
const [fpool, ppool] = await Promise.all([_nl_fashion_pool(), _nl_partner_pool()]);
|
|
6123
6135
|
const rcpt = _nl_sample_recipient(host);
|
|
6124
6136
|
rcpt.fashion = _nl_fashion_for(fpool, rcpt.country, rcpt.city, issue.issue_number);
|
|
6125
6137
|
rcpt.partners = _nl_partners_for(ppool, rcpt.cc, rcpt.country, rcpt.city);
|
|
6138
|
+
rcpt.theme = theme;
|
|
6126
6139
|
const html = _nl_render_email(issue, rcpt);
|
|
6127
6140
|
// email_ms (request/response) so we get a real {code} back — email_msa is
|
|
6128
6141
|
// fire-and-forget and would report undefined.
|
|
6129
|
-
|
|
6142
|
+
const tag = theme === 'dark' ? '[SAMPLE · DARK] ' : '[SAMPLE] ';
|
|
6143
|
+
return email_ms.send_email({ email: to_email, subject: `${tag}${issue.subject}`, body: html, display_type: 'info' });
|
|
6130
6144
|
};
|
|
6131
6145
|
|
|
6132
6146
|
// Build the weekly draft: content + coupon + numbered issue, then sample to ops.
|
|
@@ -6223,6 +6237,7 @@ export const newsletter_preview = async function (req = {}) {
|
|
|
6223
6237
|
const rcpt = _nl_sample_recipient(host);
|
|
6224
6238
|
rcpt.fashion = _nl_fashion_for(fpool, rcpt.country, rcpt.city, ret.data.issue_number);
|
|
6225
6239
|
rcpt.partners = _nl_partners_for(ppool, rcpt.cc, rcpt.country, rcpt.city);
|
|
6240
|
+
rcpt.theme = req.theme;
|
|
6226
6241
|
const html = _nl_render_email(ret.data, rcpt);
|
|
6227
6242
|
return { code: 1, data: { issue_number: ret.data.issue_number, subject: ret.data.subject, status: ret.data.status, html } };
|
|
6228
6243
|
} catch (err) {
|
|
@@ -6237,8 +6252,8 @@ export const newsletter_send_sample = async function (req = {}) {
|
|
|
6237
6252
|
const ret = await db_module.get_couch_doc(NEWSLETTER_DB, id);
|
|
6238
6253
|
if (ret.code < 0 || !ret.data) return { code: -1, data: 'issue not found' };
|
|
6239
6254
|
const to = req.email || 'info@xuda.ai';
|
|
6240
|
-
const sr = await _newsletter_send_sample_internal(ret.data, to);
|
|
6241
|
-
return { code: 1, data: { sent_to: to, email_code: sr && sr.code } };
|
|
6255
|
+
const sr = await _newsletter_send_sample_internal(ret.data, to, req.theme);
|
|
6256
|
+
return { code: 1, data: { sent_to: to, email_code: sr && sr.code, theme: req.theme || 'light' } };
|
|
6242
6257
|
} catch (err) {
|
|
6243
6258
|
return { code: -1, data: err.message };
|
|
6244
6259
|
}
|