@dev.smartpricing/message-composer-utils 3.1.2
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/dist/types/api/aiIntegration.js +7 -0
- package/dist/types/api/aiIntegration.js.map +1 -0
- package/dist/types/api/apiMessages.js +2 -0
- package/dist/types/api/apiMessages.js.map +1 -0
- package/dist/types/api/index.js +3 -0
- package/dist/types/api/index.js.map +1 -0
- package/dist/types/app.js +23 -0
- package/dist/types/app.js.map +1 -0
- package/dist/types/brand.js +31 -0
- package/dist/types/brand.js.map +1 -0
- package/dist/types/dynamic-values.js +5 -0
- package/dist/types/dynamic-values.js.map +1 -0
- package/dist/types/emailblocks.v1.defaults.js +83 -0
- package/dist/types/emailblocks.v1.defaults.js.map +1 -0
- package/dist/types/emailblocks.v1.js +339 -0
- package/dist/types/emailblocks.v1.js.map +1 -0
- package/dist/types/index.js +16 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/kafka-events.js +2 -0
- package/dist/types/kafka-events.js.map +1 -0
- package/dist/types/language.js +2 -0
- package/dist/types/language.js.map +1 -0
- package/dist/types/media.js +6 -0
- package/dist/types/media.js.map +1 -0
- package/dist/types/message.js +41 -0
- package/dist/types/message.js.map +1 -0
- package/dist/types/meta-template.js +2 -0
- package/dist/types/meta-template.js.map +1 -0
- package/dist/types/meta-webhook.js +2 -0
- package/dist/types/meta-webhook.js.map +1 -0
- package/dist/types/submission-job.js +2 -0
- package/dist/types/submission-job.js.map +1 -0
- package/dist/types/tag.js +2 -0
- package/dist/types/tag.js.map +1 -0
- package/dist/types/utils.js +2 -0
- package/dist/types/utils.js.map +1 -0
- package/dist/types/whatsappblocks.v1.js +24 -0
- package/dist/types/whatsappblocks.v1.js.map +1 -0
- package/dist/utils/array.js +26 -0
- package/dist/utils/array.js.map +1 -0
- package/dist/utils/async.js +22 -0
- package/dist/utils/async.js.map +1 -0
- package/dist/utils/date.js +37 -0
- package/dist/utils/date.js.map +1 -0
- package/dist/utils/index.js +34 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/utils/intl.js +37 -0
- package/dist/utils/intl.js.map +1 -0
- package/dist/utils/meta-status.js +21 -0
- package/dist/utils/meta-status.js.map +1 -0
- package/dist/utils/number.js +24 -0
- package/dist/utils/number.js.map +1 -0
- package/dist/utils/object.js +87 -0
- package/dist/utils/object.js.map +1 -0
- package/dist/utils/string.js +22 -0
- package/dist/utils/string.js.map +1 -0
- package/dist/utils/templates/brand-cascade.js +116 -0
- package/dist/utils/templates/brand-cascade.js.map +1 -0
- package/dist/utils/templates/email-compiler.js +126 -0
- package/dist/utils/templates/email-compiler.js.map +1 -0
- package/dist/utils/templates/email-mjml.js +444 -0
- package/dist/utils/templates/email-mjml.js.map +1 -0
- package/dist/utils/templates/email.js +580 -0
- package/dist/utils/templates/email.js.map +1 -0
- package/dist/utils/templates/html-sanitizer.js +95 -0
- package/dist/utils/templates/html-sanitizer.js.map +1 -0
- package/dist/utils/templates/messages.js +33 -0
- package/dist/utils/templates/messages.js.map +1 -0
- package/dist/utils/templates/meta.js +246 -0
- package/dist/utils/templates/meta.js.map +1 -0
- package/dist/utils/templates/tiptap.js +167 -0
- package/dist/utils/templates/tiptap.js.map +1 -0
- package/dist/utils/templates/whatsapp-compiler.js +168 -0
- package/dist/utils/templates/whatsapp-compiler.js.map +1 -0
- package/dist/utils/templates/whatsapp.js +329 -0
- package/dist/utils/templates/whatsapp.js.map +1 -0
- package/dist/utils/validations.js +39 -0
- package/dist/utils/validations.js.map +1 -0
- package/package.json +43 -0
- package/src/types/api/aiIntegration.ts +59 -0
- package/src/types/api/apiMessages.ts +22 -0
- package/src/types/api/index.ts +2 -0
- package/src/types/app.ts +76 -0
- package/src/types/brand.ts +80 -0
- package/src/types/dynamic-values.ts +47 -0
- package/src/types/emailblocks.v1.defaults.ts +123 -0
- package/src/types/emailblocks.v1.ts +655 -0
- package/src/types/index.ts +15 -0
- package/src/types/kafka-events.ts +46 -0
- package/src/types/language.ts +9 -0
- package/src/types/media.ts +26 -0
- package/src/types/message.ts +635 -0
- package/src/types/meta-template.ts +50 -0
- package/src/types/meta-webhook.ts +32 -0
- package/src/types/submission-job.ts +30 -0
- package/src/types/tag.ts +8 -0
- package/src/types/utils.ts +14 -0
- package/src/types/whatsappblocks.v1.ts +102 -0
- package/src/utils/array.ts +33 -0
- package/src/utils/async.ts +30 -0
- package/src/utils/date.ts +39 -0
- package/src/utils/index.ts +44 -0
- package/src/utils/intl.ts +51 -0
- package/src/utils/meta-status.ts +24 -0
- package/src/utils/number.ts +36 -0
- package/src/utils/object.ts +101 -0
- package/src/utils/string.ts +24 -0
- package/src/utils/templates/brand-cascade.ts +156 -0
- package/src/utils/templates/email-compiler.ts +155 -0
- package/src/utils/templates/email-mjml.ts +602 -0
- package/src/utils/templates/email.ts +713 -0
- package/src/utils/templates/html-sanitizer.ts +121 -0
- package/src/utils/templates/messages.ts +44 -0
- package/src/utils/templates/meta.ts +336 -0
- package/src/utils/templates/tiptap.ts +247 -0
- package/src/utils/templates/whatsapp-compiler.ts +212 -0
- package/src/utils/templates/whatsapp.ts +408 -0
- package/src/utils/validations.ts +49 -0
- package/tsconfig.json +10 -0
|
@@ -0,0 +1,408 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
MessageFromDb,
|
|
3
|
+
PostEntity,
|
|
4
|
+
WaHeaderTypeV1Url,
|
|
5
|
+
WhatsappMessage,
|
|
6
|
+
} from "../../types/index.js";
|
|
7
|
+
import {
|
|
8
|
+
WA_HEADER_TEXT_MAX_LENGTH,
|
|
9
|
+
WA_BODY_MAX_LENGTH,
|
|
10
|
+
WA_FOOTER_MAX_LENGTH,
|
|
11
|
+
WA_BUTTON_TEXT_MAX_LENGTH,
|
|
12
|
+
} from "../../types/index.js";
|
|
13
|
+
import { generateHTML, generateText } from "@tiptap/core";
|
|
14
|
+
import type { Content, JSONContent } from "@tiptap/core";
|
|
15
|
+
import { shortcodeToEmoji, gitHubEmojis } from "@tiptap/extension-emoji";
|
|
16
|
+
import { getTipTapExtensions } from "./tiptap.js";
|
|
17
|
+
import { isValidPhoneNumber, isValidUrl } from "../validations.js";
|
|
18
|
+
|
|
19
|
+
export function getWhatsappEmpty(
|
|
20
|
+
languageId: string,
|
|
21
|
+
): PostEntity<WhatsappMessage> {
|
|
22
|
+
return {
|
|
23
|
+
user_id: "",
|
|
24
|
+
status: "draft",
|
|
25
|
+
render_type: "whatsapp_v1",
|
|
26
|
+
body: {
|
|
27
|
+
header: {
|
|
28
|
+
type: "none",
|
|
29
|
+
},
|
|
30
|
+
body: {
|
|
31
|
+
enabled: true,
|
|
32
|
+
content: undefined,
|
|
33
|
+
},
|
|
34
|
+
footer: {
|
|
35
|
+
enabled: false,
|
|
36
|
+
content: "",
|
|
37
|
+
},
|
|
38
|
+
buttons: {
|
|
39
|
+
enabled: false,
|
|
40
|
+
buttons: [],
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
metadata: {},
|
|
44
|
+
language_id: languageId,
|
|
45
|
+
message_group_id: "",
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export interface WhatsappValidationResult {
|
|
50
|
+
valid: boolean;
|
|
51
|
+
errors: string[];
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Validates WhatsApp message compliance with Meta requirements
|
|
56
|
+
* Checks:
|
|
57
|
+
* - Body content exists and not empty
|
|
58
|
+
* - Header text length (max 60 chars)
|
|
59
|
+
* - Body text length (max 1024 chars)
|
|
60
|
+
* - Footer text length (max 60 chars)
|
|
61
|
+
* - Button text length (max 20 chars per button)
|
|
62
|
+
* - Max 10 buttons total
|
|
63
|
+
* - Max 1 link button
|
|
64
|
+
* - Max 1 phone button
|
|
65
|
+
* - Phone numbers are valid E.164 format
|
|
66
|
+
* - Max 1 unsubscribe button
|
|
67
|
+
* - Max 3 quick_reply buttons
|
|
68
|
+
*/
|
|
69
|
+
export function isWhatsappValid(
|
|
70
|
+
message: PostEntity<WhatsappMessage> | MessageFromDb,
|
|
71
|
+
): WhatsappValidationResult {
|
|
72
|
+
const errors: string[] = [];
|
|
73
|
+
|
|
74
|
+
if (message.render_type !== "whatsapp_v1") {
|
|
75
|
+
return {
|
|
76
|
+
valid: false,
|
|
77
|
+
errors: ["editor.validation.error.invalidRenderType"],
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Validate header text length (use markdown output to match what Meta receives)
|
|
82
|
+
if (
|
|
83
|
+
message.body.header.type === "text" &&
|
|
84
|
+
message.body.header.content &&
|
|
85
|
+
!isContentEmpty(message.body.header.content)
|
|
86
|
+
) {
|
|
87
|
+
const headerText = convertToWhatsappMarkdown(message.body.header.content);
|
|
88
|
+
if (headerText.length > WA_HEADER_TEXT_MAX_LENGTH) {
|
|
89
|
+
errors.push("editor.validation.error.headerTooLong");
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Validate header media url
|
|
94
|
+
if (["image", "video", "document"].includes(message.body.header.type)) {
|
|
95
|
+
if (
|
|
96
|
+
!("url" in message.body.header) ||
|
|
97
|
+
!(message.body.header as WaHeaderTypeV1Url).url?.trim()
|
|
98
|
+
) {
|
|
99
|
+
errors.push("editor.validation.error.headerMediaRequired");
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Validate body content
|
|
104
|
+
if (!message.body.body.content) {
|
|
105
|
+
errors.push("editor.validation.error.bodyRequired");
|
|
106
|
+
} else if (isContentEmpty(message.body.body.content)) {
|
|
107
|
+
errors.push("editor.validation.error.bodyEmpty");
|
|
108
|
+
} else {
|
|
109
|
+
// Validate body text length (use markdown output to match what Meta receives)
|
|
110
|
+
const bodyText = convertToWhatsappMarkdown(message.body.body.content);
|
|
111
|
+
if (bodyText.length > WA_BODY_MAX_LENGTH) {
|
|
112
|
+
errors.push("editor.validation.error.bodyTooLong");
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Validate footer text length
|
|
117
|
+
if (message.body.footer?.enabled && message.body.footer.content) {
|
|
118
|
+
if (message.body.footer.content.length > WA_FOOTER_MAX_LENGTH) {
|
|
119
|
+
errors.push("editor.validation.error.footerTooLong");
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Validate buttons if enabled
|
|
124
|
+
if (
|
|
125
|
+
message.body.buttons?.enabled &&
|
|
126
|
+
message.body.buttons.buttons.length > 0
|
|
127
|
+
) {
|
|
128
|
+
const buttons = message.body.buttons.buttons;
|
|
129
|
+
|
|
130
|
+
// Check max total buttons
|
|
131
|
+
if (buttons.length > 10) {
|
|
132
|
+
errors.push("editor.validation.error.maxButtons");
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Count button types
|
|
136
|
+
const linkButtons = buttons.filter((b) => b.type === "link").length;
|
|
137
|
+
const phoneButtons = buttons.filter((b) => b.type === "phone").length;
|
|
138
|
+
const unsubscribeButtons = buttons.filter(
|
|
139
|
+
(b) => b.type === "unsubscribe",
|
|
140
|
+
).length;
|
|
141
|
+
const quickReplyButtons = buttons.filter(
|
|
142
|
+
(b) => b.type === "quick_reply",
|
|
143
|
+
).length;
|
|
144
|
+
|
|
145
|
+
// Validate button type limits
|
|
146
|
+
if (linkButtons > 1) {
|
|
147
|
+
errors.push("editor.validation.error.maxLinkButtons");
|
|
148
|
+
}
|
|
149
|
+
if (phoneButtons > 1) {
|
|
150
|
+
errors.push("editor.validation.error.maxPhoneButtons");
|
|
151
|
+
}
|
|
152
|
+
if (unsubscribeButtons > 1) {
|
|
153
|
+
errors.push("editor.validation.error.maxUnsubscribeButtons");
|
|
154
|
+
}
|
|
155
|
+
if (quickReplyButtons > 3) {
|
|
156
|
+
errors.push("editor.validation.error.maxQuickReplyButtons");
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Validate button text length, phone numbers, and URLs
|
|
160
|
+
buttons.forEach((button) => {
|
|
161
|
+
// Validate button text
|
|
162
|
+
const buttonText = typeof button.text === "string" ? button.text : "";
|
|
163
|
+
if (!buttonText.trim()) {
|
|
164
|
+
errors.push(`editor.validation.error.buttonTextRequired`);
|
|
165
|
+
} else if (buttonText.length > WA_BUTTON_TEXT_MAX_LENGTH) {
|
|
166
|
+
errors.push(`editor.validation.error.buttonTextTooLong`);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if (button.type === "phone") {
|
|
170
|
+
const phone = button.phoneNumber?.trim();
|
|
171
|
+
if (!phone) {
|
|
172
|
+
errors.push(`editor.validation.error.phoneRequired`);
|
|
173
|
+
} else if (!isValidPhoneNumber(phone)) {
|
|
174
|
+
errors.push(`editor.validation.error.phoneInvalid`);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
if (button.type === "link") {
|
|
178
|
+
const url = button.url?.trim();
|
|
179
|
+
if (!url) {
|
|
180
|
+
errors.push(`editor.validation.error.urlRequired`);
|
|
181
|
+
} else if (!isValidUrl(url)) {
|
|
182
|
+
errors.push(`editor.validation.error.urlInvalid`);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
return {
|
|
189
|
+
valid: errors.length === 0,
|
|
190
|
+
errors,
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
export function isContentEmpty(content: Content): boolean {
|
|
195
|
+
if (content == null) {
|
|
196
|
+
return true;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
if (typeof content === "string") {
|
|
200
|
+
return content === "";
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
if (Array.isArray(content)) {
|
|
204
|
+
return content.every((item) => isContentEmpty(item));
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if (!content.content) {
|
|
208
|
+
return true;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
return content.content.every((item) => !item.content);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
export function generateWhatsappTextPreview(
|
|
215
|
+
message: PostEntity<WhatsappMessage> | undefined,
|
|
216
|
+
) {
|
|
217
|
+
if (!message?.body.body.content) return "";
|
|
218
|
+
return generateWhatsappText(message.body.body.content);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
export function generateWhatsappText(content: JSONContent | undefined) {
|
|
222
|
+
if (!content) return "";
|
|
223
|
+
return generateText(content, getTipTapExtensions({ preset: "whatsapp" }));
|
|
224
|
+
}
|
|
225
|
+
export function generateWhatsappHtml(content: JSONContent | undefined) {
|
|
226
|
+
if (!content) return "";
|
|
227
|
+
return generateHTML(content, getTipTapExtensions({ preset: "whatsapp" }));
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Converts TipTap JSONContent to WhatsApp markdown format
|
|
232
|
+
* Supports: *bold*, _italic_, ~strikethrough~, ```code```
|
|
233
|
+
*/
|
|
234
|
+
export function convertToWhatsappMarkdown(
|
|
235
|
+
content: Content | undefined,
|
|
236
|
+
): string {
|
|
237
|
+
if (!content) return "";
|
|
238
|
+
|
|
239
|
+
function processNode(node: Content): string {
|
|
240
|
+
if (typeof node === "string") {
|
|
241
|
+
return node;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
if (Array.isArray(node)) {
|
|
245
|
+
return node.map(processNode).join("");
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
if (!node || typeof node !== "object") {
|
|
249
|
+
return "";
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
let text = "";
|
|
253
|
+
|
|
254
|
+
// Process content recursively
|
|
255
|
+
if (node.content) {
|
|
256
|
+
text = node.content.map(processNode).join("");
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// Handle text nodes with marks
|
|
260
|
+
if (node.type === "text" && node.text) {
|
|
261
|
+
text = node.text;
|
|
262
|
+
|
|
263
|
+
// Apply formatting based on marks
|
|
264
|
+
if (node.marks) {
|
|
265
|
+
for (const mark of node.marks) {
|
|
266
|
+
switch (mark.type) {
|
|
267
|
+
case "bold":
|
|
268
|
+
text = `*${text}*`;
|
|
269
|
+
break;
|
|
270
|
+
case "italic":
|
|
271
|
+
text = `_${text}_`;
|
|
272
|
+
break;
|
|
273
|
+
case "strike":
|
|
274
|
+
text = `~${text}~`;
|
|
275
|
+
break;
|
|
276
|
+
case "code":
|
|
277
|
+
text = `\`\`\`${text}\`\`\``;
|
|
278
|
+
break;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// Handle block-level elements
|
|
285
|
+
switch (node.type) {
|
|
286
|
+
case "paragraph":
|
|
287
|
+
return text + "\n";
|
|
288
|
+
case "mention":
|
|
289
|
+
return `{{${node.attrs?.label}}}`;
|
|
290
|
+
case "emoji": {
|
|
291
|
+
const emojiItem = shortcodeToEmoji(node.attrs?.name, gitHubEmojis);
|
|
292
|
+
return emojiItem?.emoji || `:${node.attrs?.name}:`;
|
|
293
|
+
}
|
|
294
|
+
case "hardBreak":
|
|
295
|
+
return "\n";
|
|
296
|
+
case "doc":
|
|
297
|
+
return text.trim();
|
|
298
|
+
default:
|
|
299
|
+
return text;
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
return processNode(content);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Calculates the translation completion percentage for a WhatsApp message
|
|
308
|
+
* Compares against a base message to determine which fields need translation
|
|
309
|
+
* @param message - The message to calculate percentage for
|
|
310
|
+
* @param baseMessage - The base language message to compare structure against
|
|
311
|
+
* @returns A number between 0-100 representing the completion percentage
|
|
312
|
+
*/
|
|
313
|
+
export function calculateWhatsappTranslationPercentage(
|
|
314
|
+
message: PostEntity<WhatsappMessage> | MessageFromDb | undefined,
|
|
315
|
+
baseMessage: PostEntity<WhatsappMessage> | MessageFromDb | undefined,
|
|
316
|
+
): number {
|
|
317
|
+
if (!message || message.render_type !== "whatsapp_v1") return 0;
|
|
318
|
+
if (!baseMessage || baseMessage.render_type !== "whatsapp_v1") return 0;
|
|
319
|
+
|
|
320
|
+
let totalFields = 0;
|
|
321
|
+
let filledFields = 0;
|
|
322
|
+
|
|
323
|
+
// Body content is always required
|
|
324
|
+
totalFields++;
|
|
325
|
+
if (message.body.body.content && !isContentEmpty(message.body.body.content)) {
|
|
326
|
+
filledFields++;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// Check header if it exists in base message
|
|
330
|
+
if (baseMessage.body.header.type === "text") {
|
|
331
|
+
totalFields++;
|
|
332
|
+
if (
|
|
333
|
+
message.body.header.type === "text" &&
|
|
334
|
+
message.body.header.content &&
|
|
335
|
+
!isContentEmpty(message.body.header.content)
|
|
336
|
+
) {
|
|
337
|
+
filledFields++;
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// Check media header if it exists in base message
|
|
342
|
+
if (["image", "video", "document"].includes(baseMessage.body.header.type)) {
|
|
343
|
+
totalFields++;
|
|
344
|
+
if (
|
|
345
|
+
message.body.header.type === baseMessage.body.header.type &&
|
|
346
|
+
"url" in message.body.header &&
|
|
347
|
+
message.body.header.url?.trim()
|
|
348
|
+
) {
|
|
349
|
+
filledFields++;
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// Check footer if enabled in base message
|
|
354
|
+
if (baseMessage.body.footer.enabled) {
|
|
355
|
+
totalFields++;
|
|
356
|
+
if (
|
|
357
|
+
message.body.footer.enabled &&
|
|
358
|
+
message.body.footer.content &&
|
|
359
|
+
message.body.footer.content.trim() !== ""
|
|
360
|
+
) {
|
|
361
|
+
filledFields++;
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
// Check buttons if they exist in base message
|
|
366
|
+
if (
|
|
367
|
+
baseMessage.body.buttons?.buttons &&
|
|
368
|
+
baseMessage.body.buttons.buttons.length > 0
|
|
369
|
+
) {
|
|
370
|
+
for (let i = 0; i < baseMessage.body.buttons.buttons.length; i++) {
|
|
371
|
+
const baseButton = baseMessage.body.buttons.buttons[i];
|
|
372
|
+
const messageButton = message.body.buttons?.buttons?.[i];
|
|
373
|
+
|
|
374
|
+
// Button text
|
|
375
|
+
totalFields++;
|
|
376
|
+
if (messageButton?.text && messageButton.text.trim() !== "") {
|
|
377
|
+
filledFields++;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
// URL for link buttons
|
|
381
|
+
if (baseButton.type === "link") {
|
|
382
|
+
totalFields++;
|
|
383
|
+
if (
|
|
384
|
+
messageButton?.type === "link" &&
|
|
385
|
+
messageButton.url &&
|
|
386
|
+
messageButton.url.trim() !== ""
|
|
387
|
+
) {
|
|
388
|
+
filledFields++;
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
// Phone number for phone buttons
|
|
393
|
+
if (baseButton.type === "phone") {
|
|
394
|
+
totalFields++;
|
|
395
|
+
if (
|
|
396
|
+
messageButton?.type === "phone" &&
|
|
397
|
+
messageButton.phoneNumber &&
|
|
398
|
+
messageButton.phoneNumber.trim() !== ""
|
|
399
|
+
) {
|
|
400
|
+
filledFields++;
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
if (totalFields === 0) return 0;
|
|
407
|
+
return Math.round((filledFields / totalFields) * 100);
|
|
408
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
// Common validation functions
|
|
2
|
+
export const isEmail = (email: string): boolean => {
|
|
3
|
+
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
4
|
+
return emailRegex.test(email);
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
export const isRequired = (value: unknown): boolean => {
|
|
8
|
+
if (value === null || value === undefined) return false;
|
|
9
|
+
if (typeof value === "string") return value.trim().length > 0;
|
|
10
|
+
if (Array.isArray(value)) return value.length > 0;
|
|
11
|
+
return true;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export const minLength = (value: string, min: number): boolean => {
|
|
15
|
+
return value.length >= min;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export const maxLength = (value: string, max: number): boolean => {
|
|
19
|
+
return value.length <= max;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export const isNumeric = (value: string): boolean => {
|
|
23
|
+
return !isNaN(Number(value)) && !isNaN(parseFloat(value));
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export const isPositiveNumber = (value: number): boolean => {
|
|
27
|
+
return value > 0;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export interface ValidationResult {
|
|
31
|
+
isValid: boolean;
|
|
32
|
+
errors: string[];
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// WhatsApp Button validations
|
|
36
|
+
export const isValidButtonText = (text: string): boolean => {
|
|
37
|
+
return text.length > 0 && text.length <= 20;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export const VALID_PHONE_NUMBER_REGEX = /^\+?[1-9]\d{8,14}$/;
|
|
41
|
+
export const isValidPhoneNumber = (phoneNumber: string): boolean => {
|
|
42
|
+
return VALID_PHONE_NUMBER_REGEX.test(phoneNumber);
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export const VALID_URL_REGEX =
|
|
46
|
+
/^https?:\/\/(?:www\.)?[-a-zA-Z0-9@:%._\+~#={}]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//={}]*)$/;
|
|
47
|
+
export const isValidUrl = (url: string): boolean => {
|
|
48
|
+
return VALID_URL_REGEX.test(url);
|
|
49
|
+
};
|