@wopr-network/platform-core 1.54.0 → 1.55.0

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.
@@ -11,12 +11,12 @@ export interface TemplateResult {
11
11
  html: string;
12
12
  text: string;
13
13
  }
14
- export declare function verifyEmailTemplate(verifyUrl: string, email: string): TemplateResult;
15
- export declare function welcomeTemplate(email: string): TemplateResult;
16
- export declare function passwordResetEmailTemplate(resetUrl: string, email: string): TemplateResult;
17
- export declare function creditPurchaseTemplate(email: string, amountDollars: string, newBalanceDollars?: string, creditsUrl?: string): TemplateResult;
18
- export declare function lowBalanceTemplate(email: string, balanceDollars: string, estimatedDaysRemaining?: number, creditsUrl?: string): TemplateResult;
19
- export declare function botSuspendedTemplate(email: string, botName: string, reason: string, creditsUrl?: string): TemplateResult;
20
- export declare function botDestructionTemplate(email: string, botName: string, daysRemaining: number, creditsUrl?: string): TemplateResult;
21
- export declare function dataDeletedTemplate(email: string, creditsUrl?: string): TemplateResult;
22
- export declare function orgInviteEmailTemplate(inviteUrl: string, orgName: string): TemplateResult;
14
+ export declare function verifyEmailTemplate(verifyUrl: string, email: string, brandName?: string): TemplateResult;
15
+ export declare function welcomeTemplate(email: string, brandName?: string): TemplateResult;
16
+ export declare function passwordResetEmailTemplate(resetUrl: string, email: string, brandName?: string): TemplateResult;
17
+ export declare function creditPurchaseTemplate(email: string, amountDollars: string, newBalanceDollars?: string, creditsUrl?: string, brandName?: string): TemplateResult;
18
+ export declare function lowBalanceTemplate(email: string, balanceDollars: string, estimatedDaysRemaining?: number, creditsUrl?: string, brandName?: string): TemplateResult;
19
+ export declare function botSuspendedTemplate(email: string, botName: string, reason: string, creditsUrl?: string, brandName?: string, supportEmail?: string): TemplateResult;
20
+ export declare function botDestructionTemplate(email: string, botName: string, daysRemaining: number, creditsUrl?: string, brandName?: string): TemplateResult;
21
+ export declare function dataDeletedTemplate(email: string, creditsUrl?: string, brandName?: string, supportEmail?: string): TemplateResult;
22
+ export declare function orgInviteEmailTemplate(inviteUrl: string, orgName: string, brandName?: string): TemplateResult;
@@ -9,7 +9,7 @@ import { escapeHtml } from "./resend-adapter.js";
9
9
  // ---------------------------------------------------------------------------
10
10
  // Shared layout helpers
11
11
  // ---------------------------------------------------------------------------
12
- function wrapHtml(title, bodyContent) {
12
+ function wrapHtml(title, bodyContent, brandName = "WOPR") {
13
13
  return `<!DOCTYPE html>
14
14
  <html>
15
15
  <head>
@@ -24,7 +24,7 @@ function wrapHtml(title, bodyContent) {
24
24
  <table role="presentation" style="width: 600px; margin: 0 auto; background-color: #ffffff; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1);">
25
25
  ${bodyContent}
26
26
  </table>
27
- <p style="margin-top: 20px; color: #a0aec0; font-size: 12px;">&copy; ${new Date().getFullYear()} WOPR Network. All rights reserved.</p>
27
+ <p style="margin-top: 20px; color: #a0aec0; font-size: 12px;">&copy; ${new Date().getFullYear()} ${escapeHtml(brandName)}. All rights reserved.</p>
28
28
  </td>
29
29
  </tr>
30
30
  </table>
@@ -87,44 +87,46 @@ function buildUnsubscribeUrl(creditsUrl) {
87
87
  // ---------------------------------------------------------------------------
88
88
  // 1. Verify Email
89
89
  // ---------------------------------------------------------------------------
90
- export function verifyEmailTemplate(verifyUrl, email) {
90
+ export function verifyEmailTemplate(verifyUrl, email, brandName = "WOPR") {
91
91
  const escapedEmail = escapeHtml(email);
92
92
  const escapedUrl = escapeHtml(verifyUrl);
93
+ const b = escapeHtml(brandName);
93
94
  const html = wrapHtml("Verify Your Email", [
94
95
  heading("Verify Your Email"),
95
- paragraph(`<p>Thanks for signing up for WOPR! Please verify your email address (<strong>${escapedEmail}</strong>) to activate your account.</p>
96
+ paragraph(`<p>Thanks for signing up for ${b}! Please verify your email address (<strong>${escapedEmail}</strong>) to activate your account.</p>
96
97
  <p>Click the button below to verify. This link will expire in 24 hours.</p>`),
97
98
  button(verifyUrl, "Verify Email"),
98
99
  paragraph(`<p style="color: #718096; font-size: 14px;">Or copy and paste this URL into your browser:</p>
99
100
  <p style="word-break: break-all; color: #2563eb; font-size: 14px;">${escapedUrl}</p>`),
100
- footer("If you didn't create a WOPR account, you can safely ignore this email."),
101
- ].join("\n"));
101
+ footer(`If you didn't create a ${b} account, you can safely ignore this email.`),
102
+ ].join("\n"), brandName);
102
103
  const text = `Verify Your Email
103
104
 
104
- Thanks for signing up for WOPR! Please verify your email address (${email}) to activate your account.
105
+ Thanks for signing up for ${brandName}! Please verify your email address (${email}) to activate your account.
105
106
 
106
107
  Click the link below to verify. This link will expire in 24 hours.
107
108
 
108
109
  ${verifyUrl}
109
110
 
110
- If you didn't create a WOPR account, you can safely ignore this email.
111
+ If you didn't create a ${brandName} account, you can safely ignore this email.
111
112
 
112
- (c) ${new Date().getFullYear()} WOPR Network. All rights reserved.`;
113
- return { subject: "Verify your WOPR account", html, text };
113
+ (c) ${new Date().getFullYear()} ${brandName}. All rights reserved.`;
114
+ return { subject: `Verify your ${brandName} account`, html, text };
114
115
  }
115
116
  // ---------------------------------------------------------------------------
116
117
  // 2. Welcome
117
118
  // ---------------------------------------------------------------------------
118
- export function welcomeTemplate(email) {
119
+ export function welcomeTemplate(email, brandName = "WOPR") {
119
120
  const escapedEmail = escapeHtml(email);
120
- const html = wrapHtml("Welcome to WOPR", [
121
- heading("Welcome to WOPR!"),
121
+ const b = escapeHtml(brandName);
122
+ const html = wrapHtml(`Welcome to ${b}`, [
123
+ heading(`Welcome to ${b}!`),
122
124
  paragraph(`<p>Hi <strong>${escapedEmail}</strong>,</p>
123
125
  <p>Your email has been verified and your account is now active. You've been granted <strong>$5.00 in free credits</strong> to get started.</p>
124
126
  <p>You can now create bots, connect them to Discord, Slack, and more.</p>`),
125
127
  footer("Happy building!"),
126
- ].join("\n"));
127
- const text = `Welcome to WOPR!
128
+ ].join("\n"), brandName);
129
+ const text = `Welcome to ${brandName}!
128
130
 
129
131
  Hi ${email},
130
132
 
@@ -134,30 +136,31 @@ You can now create bots, connect them to Discord, Slack, and more.
134
136
 
135
137
  Happy building!
136
138
 
137
- (c) ${new Date().getFullYear()} WOPR Network. All rights reserved.`;
138
- return { subject: "Welcome to WOPR", html, text };
139
+ (c) ${new Date().getFullYear()} ${brandName}. All rights reserved.`;
140
+ return { subject: `Welcome to ${brandName}`, html, text };
139
141
  }
140
142
  // ---------------------------------------------------------------------------
141
143
  // 3. Password Reset (supersedes WOP-346 inline template)
142
144
  // ---------------------------------------------------------------------------
143
- export function passwordResetEmailTemplate(resetUrl, email) {
145
+ export function passwordResetEmailTemplate(resetUrl, email, brandName = "WOPR") {
144
146
  const escapedEmail = escapeHtml(email);
145
147
  const escapedUrl = escapeHtml(resetUrl);
148
+ const b = escapeHtml(brandName);
146
149
  const html = wrapHtml("Reset Your Password", [
147
150
  heading("Reset Your Password"),
148
151
  paragraph(`<p>Hi there,</p>
149
- <p>You requested a password reset for your WOPR account (<strong>${escapedEmail}</strong>).</p>
152
+ <p>You requested a password reset for your ${b} account (<strong>${escapedEmail}</strong>).</p>
150
153
  <p>Click the button below to create a new password. This link will expire in 1 hour.</p>`),
151
154
  button(resetUrl, "Reset Password"),
152
155
  paragraph(`<p style="color: #718096; font-size: 14px;">Or copy and paste this URL into your browser:</p>
153
156
  <p style="word-break: break-all; color: #2563eb; font-size: 14px;">${escapedUrl}</p>`),
154
157
  footer("If you didn't request this password reset, you can safely ignore this email."),
155
- ].join("\n"));
158
+ ].join("\n"), brandName);
156
159
  const text = `Reset Your Password
157
160
 
158
161
  Hi there,
159
162
 
160
- You requested a password reset for your WOPR account (${email}).
163
+ You requested a password reset for your ${brandName} account (${email}).
161
164
 
162
165
  Click the link below to create a new password. This link will expire in 1 hour.
163
166
 
@@ -165,15 +168,16 @@ ${resetUrl}
165
168
 
166
169
  If you didn't request this password reset, you can safely ignore this email.
167
170
 
168
- (c) ${new Date().getFullYear()} WOPR Network. All rights reserved.`;
169
- return { subject: "Reset your WOPR password", html, text };
171
+ (c) ${new Date().getFullYear()} ${brandName}. All rights reserved.`;
172
+ return { subject: `Reset your ${brandName} password`, html, text };
170
173
  }
171
174
  // ---------------------------------------------------------------------------
172
175
  // 4. Credit Purchase
173
176
  // ---------------------------------------------------------------------------
174
- export function creditPurchaseTemplate(email, amountDollars, newBalanceDollars, creditsUrl) {
177
+ export function creditPurchaseTemplate(email, amountDollars, newBalanceDollars, creditsUrl, brandName = "WOPR") {
175
178
  const escapedEmail = escapeHtml(email);
176
179
  const escapedAmount = escapeHtml(amountDollars);
180
+ const b = escapeHtml(brandName);
177
181
  const balanceLine = newBalanceDollars
178
182
  ? `<p>Your new balance is <strong>${escapeHtml(newBalanceDollars)}</strong>.</p>`
179
183
  : "<p>Your updated balance is now available in your dashboard.</p>";
@@ -183,32 +187,33 @@ export function creditPurchaseTemplate(email, amountDollars, newBalanceDollars,
183
187
  const parts = [
184
188
  heading("Credits Added to Your Account"),
185
189
  paragraph(`<p>Hi <strong>${escapedEmail}</strong>,</p>
186
- <p><strong>${escapedAmount}</strong> in credits has been added to your WOPR account.</p>
190
+ <p><strong>${escapedAmount}</strong> in credits has been added to your ${b} account.</p>
187
191
  ${balanceLine}`),
188
192
  ];
189
193
  if (creditsUrl)
190
194
  parts.push(button(creditsUrl, "View Credits"));
191
- parts.push(footer("Thank you for supporting WOPR!"));
195
+ parts.push(footer(`Thank you for supporting ${b}!`));
192
196
  parts.push(unsubscribeFooter(creditsUrl ? buildUnsubscribeUrl(creditsUrl) : undefined));
193
- const html = wrapHtml("Credits Added", parts.join("\n"));
197
+ const html = wrapHtml("Credits Added", parts.join("\n"), brandName);
194
198
  const text = `Credits Added to Your Account
195
199
 
196
200
  Hi ${email},
197
201
 
198
- ${amountDollars} in credits has been added to your WOPR account.
202
+ ${amountDollars} in credits has been added to your ${brandName} account.
199
203
  ${balanceTextLine}
200
204
  ${creditsUrl ? `\nView your credits: ${creditsUrl}` : ""}
201
- Thank you for supporting WOPR!${unsubscribeText(creditsUrl ? buildUnsubscribeUrl(creditsUrl) : undefined)}
205
+ Thank you for supporting ${brandName}!${unsubscribeText(creditsUrl ? buildUnsubscribeUrl(creditsUrl) : undefined)}
202
206
 
203
- (c) ${new Date().getFullYear()} WOPR Network. All rights reserved.`;
207
+ (c) ${new Date().getFullYear()} ${brandName}. All rights reserved.`;
204
208
  return { subject: "Credits added to your account", html, text };
205
209
  }
206
210
  // ---------------------------------------------------------------------------
207
211
  // 5. Low Balance
208
212
  // ---------------------------------------------------------------------------
209
- export function lowBalanceTemplate(email, balanceDollars, estimatedDaysRemaining, creditsUrl) {
213
+ export function lowBalanceTemplate(email, balanceDollars, estimatedDaysRemaining, creditsUrl, brandName = "WOPR") {
210
214
  const escapedEmail = escapeHtml(email);
211
215
  const escapedBalance = escapeHtml(balanceDollars);
216
+ const b = escapeHtml(brandName);
212
217
  const daysLine = estimatedDaysRemaining != null
213
218
  ? `<p>At your current usage, your credits will run out in approximately <strong>${estimatedDaysRemaining} day${estimatedDaysRemaining === 1 ? "" : "s"}</strong>.</p>`
214
219
  : "";
@@ -216,9 +221,9 @@ export function lowBalanceTemplate(email, balanceDollars, estimatedDaysRemaining
216
221
  ? `At your current usage, your credits will run out in approximately ${estimatedDaysRemaining} day${estimatedDaysRemaining === 1 ? "" : "s"}.`
217
222
  : "";
218
223
  const parts = [
219
- heading("Your WOPR Credits Are Running Low"),
224
+ heading(`Your ${b} Credits Are Running Low`),
220
225
  paragraph(`<p>Hi <strong>${escapedEmail}</strong>,</p>
221
- <p>Your WOPR credit balance is now <strong>${escapedBalance}</strong>. When your balance reaches $0, your bots will be paused.</p>
226
+ <p>Your ${b} credit balance is now <strong>${escapedBalance}</strong>. When your balance reaches $0, your bots will be paused.</p>
222
227
  ${daysLine}
223
228
  <p>Top up your credits to keep your bots running.</p>`),
224
229
  ];
@@ -226,23 +231,23 @@ export function lowBalanceTemplate(email, balanceDollars, estimatedDaysRemaining
226
231
  parts.push(button(creditsUrl, "Buy Credits"));
227
232
  parts.push(footer("This is an automated notification based on your account balance."));
228
233
  parts.push(unsubscribeFooter(creditsUrl ? buildUnsubscribeUrl(creditsUrl) : undefined));
229
- const html = wrapHtml("Low Balance", parts.join("\n"));
230
- const text = `Your WOPR Credits Are Running Low
234
+ const html = wrapHtml("Low Balance", parts.join("\n"), brandName);
235
+ const text = `Your ${brandName} Credits Are Running Low
231
236
 
232
237
  Hi ${email},
233
238
 
234
- Your WOPR credit balance is now ${balanceDollars}. When your balance reaches $0, your bots will be paused.
239
+ Your ${brandName} credit balance is now ${balanceDollars}. When your balance reaches $0, your bots will be paused.
235
240
  ${daysTextLine ? `${daysTextLine}\n` : ""}
236
241
  Top up your credits to keep your bots running.
237
242
  ${creditsUrl ? `\nBuy credits: ${creditsUrl}` : ""}${unsubscribeText(creditsUrl ? buildUnsubscribeUrl(creditsUrl) : undefined)}
238
243
 
239
- (c) ${new Date().getFullYear()} WOPR Network. All rights reserved.`;
240
- return { subject: "Your WOPR credits are running low", html, text };
244
+ (c) ${new Date().getFullYear()} ${brandName}. All rights reserved.`;
245
+ return { subject: `Your ${brandName} credits are running low`, html, text };
241
246
  }
242
247
  // ---------------------------------------------------------------------------
243
248
  // 6. Bot Suspended
244
249
  // ---------------------------------------------------------------------------
245
- export function botSuspendedTemplate(email, botName, reason, creditsUrl) {
250
+ export function botSuspendedTemplate(email, botName, reason, creditsUrl, brandName = "WOPR", supportEmail = "support@wopr.bot") {
246
251
  const escapedEmail = escapeHtml(email);
247
252
  const escapedBotName = escapeHtml(botName);
248
253
  const escapedReason = escapeHtml(reason);
@@ -255,9 +260,9 @@ export function botSuspendedTemplate(email, botName, reason, creditsUrl) {
255
260
  ];
256
261
  if (creditsUrl)
257
262
  parts.push(button(creditsUrl, "Buy Credits to Reactivate"));
258
- parts.push(footer("If you need help, reply to this email or contact support@wopr.bot."));
263
+ parts.push(footer(`If you need help, reply to this email or contact ${escapeHtml(supportEmail)}.`));
259
264
  parts.push(unsubscribeFooter(creditsUrl ? buildUnsubscribeUrl(creditsUrl) : undefined));
260
- const html = wrapHtml("Bot Suspended", parts.join("\n"));
265
+ const html = wrapHtml("Bot Suspended", parts.join("\n"), brandName);
261
266
  const text = `Your Bot Has Been Suspended
262
267
 
263
268
  Hi ${email},
@@ -268,15 +273,15 @@ Reason: ${reason}
268
273
 
269
274
  Buy credits to reactivate instantly. Your data is preserved for 30 days.
270
275
  ${creditsUrl ? `\nBuy credits: ${creditsUrl}` : ""}
271
- If you need help, reply to this email or contact support@wopr.bot.${unsubscribeText(creditsUrl ? buildUnsubscribeUrl(creditsUrl) : undefined)}
276
+ If you need help, reply to this email or contact ${supportEmail}.${unsubscribeText(creditsUrl ? buildUnsubscribeUrl(creditsUrl) : undefined)}
272
277
 
273
- (c) ${new Date().getFullYear()} WOPR Network. All rights reserved.`;
278
+ (c) ${new Date().getFullYear()} ${brandName}. All rights reserved.`;
274
279
  return { subject: "Your bot has been suspended", html, text };
275
280
  }
276
281
  // ---------------------------------------------------------------------------
277
282
  // 7. Bot Destruction Warning
278
283
  // ---------------------------------------------------------------------------
279
- export function botDestructionTemplate(email, botName, daysRemaining, creditsUrl) {
284
+ export function botDestructionTemplate(email, botName, daysRemaining, creditsUrl, brandName = "WOPR") {
280
285
  const escapedEmail = escapeHtml(email);
281
286
  const escapedBotName = escapeHtml(botName);
282
287
  const days = String(daysRemaining);
@@ -295,7 +300,7 @@ export function botDestructionTemplate(email, botName, daysRemaining, creditsUrl
295
300
  parts.push(button(creditsUrl, "Buy Credits Now", "#dc2626"));
296
301
  parts.push(footer("This action is irreversible. All bot configuration, history, and connected services will be removed."));
297
302
  parts.push(unsubscribeFooter(creditsUrl ? buildUnsubscribeUrl(creditsUrl) : undefined));
298
- const html = wrapHtml("Bot Data Deletion", parts.join("\n"));
303
+ const html = wrapHtml("Bot Data Deletion", parts.join("\n"), brandName);
299
304
  const text = `URGENT: Bot Data Will Be Deleted
300
305
 
301
306
  Hi ${email},
@@ -306,13 +311,13 @@ Buy credits before the deadline to save your data.
306
311
  ${creditsUrl ? `\nBuy credits now: ${creditsUrl}` : ""}
307
312
  This action is irreversible. All bot configuration, history, and connected services will be removed.${unsubscribeText(creditsUrl ? buildUnsubscribeUrl(creditsUrl) : undefined)}
308
313
 
309
- (c) ${new Date().getFullYear()} WOPR Network. All rights reserved.`;
314
+ (c) ${new Date().getFullYear()} ${brandName}. All rights reserved.`;
310
315
  return { subject: `URGENT: Your bot data will be deleted in ${daysRemaining} days`, html, text };
311
316
  }
312
317
  // ---------------------------------------------------------------------------
313
318
  // 8. Data Deleted Confirmation
314
319
  // ---------------------------------------------------------------------------
315
- export function dataDeletedTemplate(email, creditsUrl) {
320
+ export function dataDeletedTemplate(email, creditsUrl, brandName = "WOPR", supportEmail = "support@wopr.bot") {
316
321
  const escapedEmail = escapeHtml(email);
317
322
  const parts = [
318
323
  heading("Your Bot Data Has Been Deleted"),
@@ -322,9 +327,9 @@ export function dataDeletedTemplate(email, creditsUrl) {
322
327
  ];
323
328
  if (creditsUrl)
324
329
  parts.push(button(creditsUrl, "Add Credits"));
325
- parts.push(footer("If you have questions, contact support@wopr.bot."));
330
+ parts.push(footer(`If you have questions, contact ${escapeHtml(supportEmail)}.`));
326
331
  parts.push(unsubscribeFooter(creditsUrl ? buildUnsubscribeUrl(creditsUrl) : undefined));
327
- const html = wrapHtml("Data Deleted", parts.join("\n"));
332
+ const html = wrapHtml("Data Deleted", parts.join("\n"), brandName);
328
333
  const text = `Your Bot Data Has Been Deleted
329
334
 
330
335
  Hi ${email},
@@ -333,24 +338,25 @@ Your suspended bot data has been permanently deleted after 30 days of inactivity
333
338
 
334
339
  You can create a new bot anytime by adding credits to your account.
335
340
  ${creditsUrl ? `\nAdd credits: ${creditsUrl}` : ""}
336
- If you have questions, contact support@wopr.bot.${unsubscribeText(creditsUrl ? buildUnsubscribeUrl(creditsUrl) : undefined)}
341
+ If you have questions, contact ${supportEmail}.${unsubscribeText(creditsUrl ? buildUnsubscribeUrl(creditsUrl) : undefined)}
337
342
 
338
- (c) ${new Date().getFullYear()} WOPR Network. All rights reserved.`;
343
+ (c) ${new Date().getFullYear()} ${brandName}. All rights reserved.`;
339
344
  return { subject: "Your bot data has been deleted", html, text };
340
345
  }
341
346
  // ---------------------------------------------------------------------------
342
347
  // Org Invite
343
348
  // ---------------------------------------------------------------------------
344
- export function orgInviteEmailTemplate(inviteUrl, orgName) {
349
+ export function orgInviteEmailTemplate(inviteUrl, orgName, brandName = "WOPR") {
345
350
  const safeOrg = escapeHtml(orgName);
346
351
  const safeUrl = escapeHtml(inviteUrl);
352
+ const b = escapeHtml(brandName);
347
353
  const html = wrapHtml(`You're invited to join ${safeOrg}`, [
348
354
  heading(`Join ${safeOrg}`),
349
- paragraph(`You've been invited to join <strong>${safeOrg}</strong> on WOPR Network. Click the button below to accept the invitation.`),
355
+ paragraph(`You've been invited to join <strong>${safeOrg}</strong> on ${b}. Click the button below to accept the invitation.`),
350
356
  button(safeUrl, "Accept Invitation"),
351
357
  footer("This invitation expires in 7 days. If you didn't expect this email, you can safely ignore it."),
352
- ].join("\n"));
353
- const text = `You're invited to join ${orgName} on WOPR Network.
358
+ ].join("\n"), brandName);
359
+ const text = `You're invited to join ${orgName} on ${brandName}.
354
360
 
355
361
  Accept the invitation: ${inviteUrl}
356
362
 
@@ -134,7 +134,7 @@ describe("all templates", () => {
134
134
  expect(t.html).toContain("<!DOCTYPE html>");
135
135
  expect(t.html).toContain("<html>");
136
136
  expect(t.html).toContain("</html>");
137
- expect(t.html).toContain("WOPR Network");
137
+ expect(t.html).toContain("WOPR");
138
138
  expect(t.subject.length).toBeGreaterThan(0);
139
139
  expect(t.text.length).toBeGreaterThan(0);
140
140
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wopr-network/platform-core",
3
- "version": "1.54.0",
3
+ "version": "1.55.0",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",