Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 | 217x 217x 217x 217x 308x 308x 123x 123x 123x 123x 123x 29x 44x 15x 29x 44x 308x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 16x 22x 22x 304x 109x 109x 107x 64x 107x 107x 107x | // Utility functions for WhatsApp template parameter handling.
// Templates store their structure as a JSON string in the `components` column.
// These helpers parse that JSON, extract {{N}} variable slots, build the
// WhatsApp Cloud API `components` payload, and render preview text.
export type TemplateComponent = {
type: string; // "HEADER" | "BODY" | "FOOTER" | "BUTTONS"
format?: string; // "TEXT" | "IMAGE" | etc.
text?: string;
example?: { body_text?: string[][]; header_text?: string[] };
};
export type ParameterSlot = {
componentType: "HEADER" | "BODY";
index: number; // 1-based (matches {{1}}, {{2}}, etc.)
};
/**
* Safely parse the JSON `components` string stored on a template.
* Returns an empty array on null/invalid input.
*/
export function parseTemplateComponents(
componentsJson: string | null,
): TemplateComponent[] {
Iif (!componentsJson) return [];
try {
const parsed = JSON.parse(componentsJson);
return Array.isArray(parsed) ? parsed : [];
} catch {
return [];
}
}
/**
* Scan parsed components for `{{N}}` placeholders and return a flat list
* of slots grouped by component type (HEADER first, then BODY).
*/
export function extractParameterSlots(
components: TemplateComponent[],
): ParameterSlot[] {
const slots: ParameterSlot[] = [];
for (const comp of components) {
const type = comp.type?.toUpperCase();
Iif (type !== "HEADER" && type !== "BODY") continue;
Iif (!comp.text) continue;
const matches = comp.text.match(/\{\{(\d+)\}\}/g);
if (!matches) continue;
const indices = [
...new Set(
matches.map((m) => Number.parseInt(m.replace(/\{|\}/g, ""), 10)),
),
].sort((a, b) => a - b);
for (const idx of indices) {
slots.push({
componentType: type as "HEADER" | "BODY",
index: idx,
});
}
}
return slots;
}
/**
* Build the WhatsApp Cloud API `components` array from slot values.
*
* Example output:
* ```json
* [
* { "type": "body", "parameters": [
* { "type": "text", "text": "John" },
* { "type": "text", "text": "kitchen remodel" }
* ]}
* ]
* ```
*/
export function buildSendComponents(
templateComponents: TemplateComponent[],
paramValues: Record<string, string>,
): unknown[] {
const slots = extractParameterSlots(templateComponents);
Iif (slots.length === 0) return [];
// Group slots by component type
const grouped: Record<string, ParameterSlot[]> = {};
for (const slot of slots) {
const key = slot.componentType.toLowerCase();
Eif (!grouped[key]) grouped[key] = [];
grouped[key].push(slot);
}
const result: unknown[] = [];
for (const [type, typeSlots] of Object.entries(grouped)) {
const parameters = typeSlots.map((slot) => ({
type: "text" as const,
text: paramValues[`${slot.componentType}_${slot.index}`] || "",
}));
result.push({ type, parameters });
}
return result;
}
/**
* Replace `{{N}}` placeholders in text with actual values for preview.
* `values` is a map keyed like "BODY_1", "BODY_2", "HEADER_1", etc.
*/
export function renderPreviewText(
text: string,
componentType: "HEADER" | "BODY",
values: Record<string, string>,
): string {
return text.replace(/\{\{(\d+)\}\}/g, (match, num) => {
const key = `${componentType}_${num}`;
return values[key] || match;
});
}
/**
* Check whether a template has an approved status (case-insensitive).
*/
export function isApprovedTemplate(template: { status: string }): boolean {
return template.status === "APPROVED" || template.status === "approved";
}
/**
* Get the body text from parsed template components.
*/
export function getTemplateBodyText(
components: TemplateComponent[],
): string | null {
const body = components.find((c) => c.type?.toUpperCase() === "BODY");
return body?.text ?? null;
}
/**
* Get the header text from parsed template components (text headers only).
*/
export function getTemplateHeaderText(
components: TemplateComponent[],
): string | null {
const header = components.find(
(c) =>
c.type?.toUpperCase() === "HEADER" &&
c.format?.toUpperCase() === "TEXT",
);
return header?.text ?? null;
}
/**
* Get the footer text from parsed template components.
*/
export function getTemplateFooterText(
components: TemplateComponent[],
): string | null {
const footer = components.find((c) => c.type?.toUpperCase() === "FOOTER");
return footer?.text ?? null;
}
|