Add shared replacement method
This commit is contained in:
@@ -5,6 +5,7 @@ import signale from 'signale';
|
||||
import {DASHBOARD_URI, LANDING_URI, STRIPE_ENABLED} from '../app/constants.js';
|
||||
import {prisma} from '../database/prisma.js';
|
||||
import {HttpException} from '../exceptions/index.js';
|
||||
import {renderTemplate} from '@plunk/shared';
|
||||
|
||||
import {BillingLimitService} from './BillingLimitService.js';
|
||||
import {DomainService} from './DomainService.js';
|
||||
@@ -552,28 +553,15 @@ export class EmailService {
|
||||
|
||||
/**
|
||||
* Format email template by replacing variables in subject and body
|
||||
* Supports {{variable}} and {{variable ?? defaultValue}} syntax
|
||||
* Uses shared template rendering from @plunk/shared
|
||||
*/
|
||||
public static format({subject, body, data}: {subject: string; body: string; data: Record<string, unknown>}): {
|
||||
subject: string;
|
||||
body: string;
|
||||
} {
|
||||
const replaceVariables = (text: string) => {
|
||||
return text.replace(/\{\{(.*?)\}\}/g, (match, key) => {
|
||||
const [mainKey, defaultValue] = key.split('??').map((s: string) => s.trim());
|
||||
|
||||
// Handle array values (for lists)
|
||||
if (Array.isArray(data[mainKey])) {
|
||||
return data[mainKey].map((e: string) => `<li>${e}</li>`).join('\n');
|
||||
}
|
||||
|
||||
return data[mainKey] ?? defaultValue ?? '';
|
||||
});
|
||||
};
|
||||
|
||||
return {
|
||||
subject: replaceVariables(subject),
|
||||
body: replaceVariables(body),
|
||||
subject: renderTemplate(subject, data),
|
||||
body: renderTemplate(body, data),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ import type {
|
||||
Workflow,
|
||||
} from '@plunk/db';
|
||||
import {StepExecutionStatus, WorkflowExecutionStatus} from '@plunk/db';
|
||||
import {WorkflowStepConfigSchemas} from '@plunk/shared';
|
||||
import {WorkflowStepConfigSchemas, renderTemplate} from '@plunk/shared';
|
||||
|
||||
import {prisma} from '../database/prisma.js';
|
||||
import {HttpException} from '../exceptions/index.js';
|
||||
@@ -863,16 +863,10 @@ export class WorkflowExecutionService {
|
||||
|
||||
/**
|
||||
* Helper: Render template with variables
|
||||
* Uses shared template rendering from @plunk/shared
|
||||
*/
|
||||
private static renderTemplate(template: string, variables: Record<string, unknown>): string {
|
||||
let rendered = template;
|
||||
|
||||
for (const [key, value] of Object.entries(variables)) {
|
||||
const regex = new RegExp(`\\{\\{${key}\\}\\}`, 'g');
|
||||
rendered = rendered.replace(regex, String(value || ''));
|
||||
}
|
||||
|
||||
return rendered;
|
||||
return renderTemplate(template, variables);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Vendored
+1
-1
@@ -1,6 +1,6 @@
|
||||
/// <reference types="next" />
|
||||
/// <reference types="next/image-types/global" />
|
||||
import "./.next/dev/types/routes.d.ts";
|
||||
import "./.next/types/routes.d.ts";
|
||||
|
||||
// NOTE: This file should not be edited
|
||||
// see https://nextjs.org/docs/pages/api-reference/config/typescript for more information.
|
||||
|
||||
@@ -12,6 +12,7 @@ import {ResizableImage} from './ResizableImage';
|
||||
import {useContactFields, useContacts} from '../../lib/hooks/useContacts';
|
||||
import {useConfig} from '../../lib/hooks/useConfig';
|
||||
import {useEffect, useRef, useState} from 'react';
|
||||
import {renderTemplate} from '@plunk/shared';
|
||||
import {
|
||||
Button,
|
||||
Dialog,
|
||||
@@ -276,32 +277,7 @@ export function EmailEditor({value, onChange, placeholder, subject, from, replyT
|
||||
};
|
||||
|
||||
const replaceVariables = (text: string, contactData: Record<string, unknown>) => {
|
||||
return text.replace(/\{\{(.*?)\}\}/g, (match, key) => {
|
||||
const [mainKey, defaultValue] = key.split('??').map((s: string) => s.trim());
|
||||
|
||||
// Handle special variables
|
||||
if (mainKey === 'unsubscribeUrl') return contactData.unsubscribeUrl || defaultValue || '#';
|
||||
if (mainKey === 'subscribeUrl') return contactData.subscribeUrl || defaultValue || '#';
|
||||
if (mainKey === 'manageUrl') return contactData.manageUrl || defaultValue || '#';
|
||||
|
||||
// Handle nested property access (e.g., data.firstName)
|
||||
const getValue = (obj: Record<string, unknown>, path: string): unknown => {
|
||||
return path.split('.').reduce((current: Record<string, unknown> | unknown, key) => {
|
||||
if (current && typeof current === 'object' && !Array.isArray(current)) {
|
||||
return (current as Record<string, unknown>)[key];
|
||||
}
|
||||
return undefined;
|
||||
}, obj);
|
||||
};
|
||||
|
||||
// Try multiple lookup strategies
|
||||
const value =
|
||||
getValue(contactData, mainKey) || // Try as nested path (e.g., data.firstName)
|
||||
contactData[mainKey] || // Try as top-level property
|
||||
(contactData.data as Record<string, unknown>)?.[mainKey]; // Try in data object
|
||||
|
||||
return value ?? defaultValue ?? '';
|
||||
});
|
||||
return renderTemplate(text, contactData);
|
||||
};
|
||||
|
||||
const getPreviewHtml = () => {
|
||||
|
||||
Reference in New Issue
Block a user