@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.
Files changed (2) hide show
  1. package/index.mjs +42 -27
  2. 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)} &rarr;</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 = '#f4f6fa', imgMaxH = '210px' }) => {
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:#0b1220;text-decoration:none">${esc(title)}</a>` : esc(title);
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:#ffffff;border:1px solid #e9edf3;border-radius:14px;overflow:hidden">
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:#0b1220;margin-bottom:7px">${titleHtml}</div>
5948
- <p style="color:#475569;font-size:14px;line-height:1.55;margin:0 0 15px">${esc(text)}</p>
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:#eff6ff;border-left:4px solid #0091FF;border-radius:10px;padding:16px 18px">
5986
- <div style="font-size:11px;letter-spacing:.06em;text-transform:uppercase;color:#0b66c2;font-weight:700;margin-bottom:5px">⭐ Tip of the week</div>
5987
- <div style="font-size:16px;font-weight:700;color:#0b1220;margin:0 0 5px">${esc(c.tip2.title)}</div>
5988
- <p style="color:#334155;font-size:14px;line-height:1.55;margin:0 0 ${c.tip2.url ? '12px' : '0'}">${esc(c.tip2.text)}</p>
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 #e9edf3;display:block">`
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:#ffffff;border:1px solid #e9edf3;border-radius:14px;padding:18px 20px">
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:#0b1220;margin:0 0 10px">${esc(p.name)}</div>
6078
- <a href="${esc(p.url)}" style="display:inline-block;background:#eff6ff;color:#0b66c2;text-decoration:none;padding:8px 16px;border-radius:8px;font-weight:700;font-size:13px">View profile &rarr;</a>
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 &rarr;</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:#94a3b8;font-size:13px;margin:0 0 14px">Your regional ambassador and mentor are here to help you build.</p>`);
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="font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Helvetica,Arial,sans-serif">
6107
+ <div style="${shell}">
6096
6108
  <div style="text-align:center;margin:0 0 8px">
6097
- <span style="display:inline-block;background:#eff6ff;color:#0b66c2;font-size:11px;font-weight:700;letter-spacing:.5px;text-transform:uppercase;padding:5px 13px;border-radius:20px">Issue #${esc(issue.issue_number)}${dateStr ? ' &middot; ' + esc(dateStr) : ''}</span>
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 ? ' &middot; ' + esc(dateStr) : ''}</span>
6098
6110
  </div>
6099
- <h1 style="text-align:center;font-size:28px;font-weight:800;color:#0b1220;margin:0 0 8px">Xuda Weekly</h1>
6100
- <p style="text-align:center;color:#475569;font-size:15px;line-height:1.55;margin:0 auto 24px;max-width:400px"><strong style="color:#0b1220">${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>
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:#94a3b8;font-size:12px;line-height:1.7;margin:26px 0 0;border-top:1px solid #e9edf3;padding-top:18px">You're receiving this because you opted in to Xuda updates.<br><a href="${esc(unsub_url)}" style="color:#94a3b8;text-decoration:underline">Unsubscribe</a> &nbsp;&middot;&nbsp; <a href="https://${esc(host)}/dashboard" style="color:#94a3b8;text-decoration:underline">Your account</a></p>
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> &nbsp;&middot;&nbsp; <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
- return email_ms.send_email({ email: to_email, subject: `[SAMPLE] ${issue.subject}`, body: html, display_type: 'info' });
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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xuda.io/account_module",
3
- "version": "1.2.2281",
3
+ "version": "1.2.2282",
4
4
  "description": "Xuda Account Server Module",
5
5
  "main": "index.mjs",
6
6
  "dependencies": {