@businessflow/reviews 1.0.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.
@@ -0,0 +1,400 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/client/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ DEFAULT_REVIEW_VALIDATION_RULES: () => DEFAULT_REVIEW_VALIDATION_RULES,
24
+ ReviewClient: () => ReviewClient,
25
+ isValidRating: () => isValidRating,
26
+ sanitizeReviewData: () => sanitizeReviewData,
27
+ validateReviewData: () => validateReviewData,
28
+ validateSingleReviewField: () => validateSingleReviewField
29
+ });
30
+ module.exports = __toCommonJS(index_exports);
31
+
32
+ // src/client/client.ts
33
+ var logDebug = (context, data) => {
34
+ if (typeof process !== "undefined" && process.env?.NODE_ENV === "development") {
35
+ console.log(`[ReviewClient] ${context}`, data ? ":" : "");
36
+ if (data) {
37
+ console.log(`[ReviewClient] Data:`, data);
38
+ }
39
+ }
40
+ };
41
+ var logError = (context, error, additionalData) => {
42
+ if (typeof process !== "undefined" && process.env?.NODE_ENV === "development") {
43
+ console.error(`[ReviewClient] ${context}:`, error);
44
+ if (additionalData) {
45
+ console.error(`[ReviewClient] Additional data:`, additionalData);
46
+ }
47
+ }
48
+ };
49
+ var DEFAULT_ERROR_MESSAGES = {
50
+ SUBMISSION_FAILED: "Failed to submit review",
51
+ FETCH_FAILED: "Failed to fetch reviews",
52
+ SERVER_ERROR: "Server error. Please try again.",
53
+ NETWORK_ERROR: "Network error. Please check your connection.",
54
+ GENERAL_ERROR: "An unexpected error occurred"
55
+ };
56
+ var ReviewClient = class {
57
+ // 30 seconds
58
+ constructor(config) {
59
+ this.maxRetries = 3;
60
+ this.timeout = 3e4;
61
+ this.fetchConfig = config.fetchConfig;
62
+ this.submitConfig = config.submitConfig;
63
+ if (config.maxRetries !== void 0) this.maxRetries = config.maxRetries;
64
+ if (config.timeout !== void 0) this.timeout = config.timeout;
65
+ }
66
+ /**
67
+ * Fetch reviews with retry logic
68
+ */
69
+ async fetchReviews(params, retryCount = 0) {
70
+ if (!this.fetchConfig) {
71
+ throw new Error("Fetch configuration not provided");
72
+ }
73
+ const maxRetries = this.fetchConfig.maxRetries ?? this.maxRetries;
74
+ const timeout = this.fetchConfig.timeout ?? this.timeout;
75
+ const retryDelay = Math.min(1e3 * Math.pow(2, retryCount), 5e3);
76
+ try {
77
+ const controller = new AbortController();
78
+ const timeoutId = setTimeout(() => controller.abort(), timeout);
79
+ const queryParams = new URLSearchParams();
80
+ const mergedParams = { ...this.fetchConfig.params, ...params };
81
+ Object.entries(mergedParams).forEach(([key, value]) => {
82
+ if (value !== void 0 && value !== null) {
83
+ queryParams.append(key, String(value));
84
+ }
85
+ });
86
+ const url = `${this.fetchConfig.endpoint}?${queryParams.toString()}`;
87
+ logError("Fetching reviews", null, { url, params: mergedParams, retryCount });
88
+ const response = await fetch(url, {
89
+ method: this.fetchConfig.method || "GET",
90
+ headers: {
91
+ "Content-Type": "application/json",
92
+ "Accept": "application/json",
93
+ ...this.fetchConfig.headers
94
+ },
95
+ signal: controller.signal
96
+ });
97
+ clearTimeout(timeoutId);
98
+ if (!response.ok) {
99
+ const errorDetails = {
100
+ status: response.status,
101
+ statusText: response.statusText,
102
+ url: response.url
103
+ };
104
+ logError("API response error", null, errorDetails);
105
+ if (response.status >= 500) {
106
+ const error = new Error(DEFAULT_ERROR_MESSAGES.SERVER_ERROR);
107
+ error.retryable = true;
108
+ throw error;
109
+ } else {
110
+ throw new Error(`${DEFAULT_ERROR_MESSAGES.FETCH_FAILED}: HTTP ${response.status}`);
111
+ }
112
+ }
113
+ let responseData;
114
+ try {
115
+ responseData = await response.json();
116
+ } catch (parseError) {
117
+ logError("Failed to parse response", parseError);
118
+ throw new Error("Invalid response format from server");
119
+ }
120
+ const reviews = this.fetchConfig.transformResponse ? this.fetchConfig.transformResponse(responseData) : Array.isArray(responseData) ? responseData : [];
121
+ logDebug("Reviews fetched successfully", { count: reviews.length });
122
+ return reviews;
123
+ } catch (error) {
124
+ logError("Review fetch error", error, { retryCount, maxRetries });
125
+ if (error instanceof Error && error.name === "AbortError") {
126
+ const timeoutError = new Error("Request timeout. Please try again.");
127
+ timeoutError.retryable = true;
128
+ throw timeoutError;
129
+ }
130
+ if (error instanceof TypeError && (error.message.includes("fetch") || error.message.includes("network") || error.message.includes("Failed to fetch"))) {
131
+ const networkError = new Error(DEFAULT_ERROR_MESSAGES.NETWORK_ERROR);
132
+ networkError.retryable = true;
133
+ throw networkError;
134
+ }
135
+ const isRetryable = error.retryable === true;
136
+ if (isRetryable && retryCount < maxRetries) {
137
+ logDebug(`Retrying in ${retryDelay}ms`, { retryCount: retryCount + 1 });
138
+ await new Promise((resolve) => setTimeout(resolve, retryDelay));
139
+ return this.fetchReviews(params, retryCount + 1);
140
+ }
141
+ if (this.fetchConfig.onError && error instanceof Error) {
142
+ this.fetchConfig.onError(error);
143
+ }
144
+ if (error instanceof Error) {
145
+ throw error;
146
+ }
147
+ throw new Error(DEFAULT_ERROR_MESSAGES.GENERAL_ERROR);
148
+ }
149
+ }
150
+ /**
151
+ * Submit a review with retry logic
152
+ */
153
+ async submitReview(reviewData, retryCount = 0) {
154
+ if (!this.submitConfig) {
155
+ throw new Error("Submit configuration not provided");
156
+ }
157
+ const maxRetries = this.submitConfig.maxRetries ?? this.maxRetries;
158
+ const timeout = this.submitConfig.timeout ?? this.timeout;
159
+ const retryDelay = Math.min(1e3 * Math.pow(2, retryCount), 5e3);
160
+ try {
161
+ const controller = new AbortController();
162
+ const timeoutId = setTimeout(() => controller.abort(), timeout);
163
+ const requestData = this.submitConfig.transformRequest ? this.submitConfig.transformRequest(reviewData) : reviewData;
164
+ logDebug("Submitting review", {
165
+ endpoint: this.submitConfig.endpoint,
166
+ retryCount,
167
+ reviewData: { ...reviewData, RecaptchaToken: reviewData.RecaptchaToken ? "[REDACTED]" : void 0 }
168
+ });
169
+ const response = await fetch(this.submitConfig.endpoint, {
170
+ method: this.submitConfig.method || "POST",
171
+ headers: {
172
+ "Content-Type": "application/json",
173
+ "Accept": "application/json",
174
+ ...this.submitConfig.headers
175
+ },
176
+ body: JSON.stringify(requestData),
177
+ signal: controller.signal
178
+ });
179
+ clearTimeout(timeoutId);
180
+ if (!response.ok) {
181
+ const errorDetails = {
182
+ status: response.status,
183
+ statusText: response.statusText,
184
+ url: response.url
185
+ };
186
+ logError("API response error", null, errorDetails);
187
+ if (response.status >= 400 && response.status < 500) {
188
+ let errorText = "Client error";
189
+ try {
190
+ errorText = await response.text();
191
+ } catch (parseError) {
192
+ logError("Failed to parse error response", parseError);
193
+ }
194
+ throw new Error(`${DEFAULT_ERROR_MESSAGES.SUBMISSION_FAILED}: ${errorText}`);
195
+ } else if (response.status >= 500) {
196
+ const error = new Error(DEFAULT_ERROR_MESSAGES.SERVER_ERROR);
197
+ error.retryable = true;
198
+ throw error;
199
+ } else {
200
+ throw new Error(`${DEFAULT_ERROR_MESSAGES.SUBMISSION_FAILED}: HTTP ${response.status}`);
201
+ }
202
+ }
203
+ let responseData;
204
+ try {
205
+ responseData = await response.json();
206
+ } catch (parseError) {
207
+ logError("Failed to parse successful response", parseError);
208
+ throw new Error("Invalid response format from server");
209
+ }
210
+ const finalResponse = this.submitConfig.transformResponse ? this.submitConfig.transformResponse(responseData) : {
211
+ success: true,
212
+ message: responseData.message || "Review submitted successfully",
213
+ reviewId: responseData.reviewId || responseData.id,
214
+ status: responseData.status,
215
+ data: responseData
216
+ };
217
+ logDebug("Review submitted successfully", { reviewId: finalResponse.reviewId });
218
+ if (this.submitConfig.onSuccess) {
219
+ this.submitConfig.onSuccess(finalResponse);
220
+ }
221
+ return finalResponse;
222
+ } catch (error) {
223
+ logError("Review submission error", error, { retryCount, maxRetries });
224
+ if (error instanceof Error && error.name === "AbortError") {
225
+ const timeoutError = new Error("Request timeout. Please try again.");
226
+ timeoutError.retryable = true;
227
+ throw timeoutError;
228
+ }
229
+ if (error instanceof TypeError && (error.message.includes("fetch") || error.message.includes("network") || error.message.includes("Failed to fetch"))) {
230
+ const networkError = new Error(DEFAULT_ERROR_MESSAGES.NETWORK_ERROR);
231
+ networkError.retryable = true;
232
+ throw networkError;
233
+ }
234
+ const isRetryable = error.retryable === true;
235
+ if (isRetryable && retryCount < maxRetries) {
236
+ logDebug(`Retrying in ${retryDelay}ms`, { retryCount: retryCount + 1 });
237
+ await new Promise((resolve) => setTimeout(resolve, retryDelay));
238
+ return this.submitReview(reviewData, retryCount + 1);
239
+ }
240
+ if (this.submitConfig.onError && error instanceof Error) {
241
+ this.submitConfig.onError(error);
242
+ }
243
+ if (error instanceof Error) {
244
+ throw error;
245
+ }
246
+ throw new Error(DEFAULT_ERROR_MESSAGES.GENERAL_ERROR);
247
+ }
248
+ }
249
+ /**
250
+ * Update configuration
251
+ */
252
+ updateConfig(config) {
253
+ if (config.fetchConfig) {
254
+ this.fetchConfig = { ...this.fetchConfig, ...config.fetchConfig };
255
+ }
256
+ if (config.submitConfig) {
257
+ this.submitConfig = { ...this.submitConfig, ...config.submitConfig };
258
+ }
259
+ }
260
+ };
261
+
262
+ // src/client/validation.ts
263
+ var DEFAULT_REVIEW_VALIDATION_RULES = {
264
+ reviewerName: [
265
+ { type: "required", message: "Name is required" },
266
+ { type: "minLength", value: 2, message: "Name must be at least 2 characters" },
267
+ { type: "maxLength", value: 100, message: "Name must be less than 100 characters" }
268
+ ],
269
+ reviewerEmail: [
270
+ { type: "required", message: "Email is required" },
271
+ { type: "email", message: "Please enter a valid email address" }
272
+ ],
273
+ rating: [
274
+ { type: "required", message: "Rating is required" },
275
+ { type: "rating", message: "Please select a rating between 1 and 5 stars" }
276
+ ],
277
+ content: [
278
+ { type: "maxLength", value: 2e3, message: "Review content must be less than 2000 characters" }
279
+ ],
280
+ reviewerTitle: [
281
+ { type: "maxLength", value: 100, message: "Title must be less than 100 characters" }
282
+ ],
283
+ reviewerCompany: [
284
+ { type: "maxLength", value: 100, message: "Company name must be less than 100 characters" }
285
+ ]
286
+ };
287
+ function validateField(value, rules) {
288
+ for (const rule of rules) {
289
+ switch (rule.type) {
290
+ case "required":
291
+ if (!value || typeof value === "string" && value.trim().length === 0) {
292
+ return rule.message || "This field is required";
293
+ }
294
+ break;
295
+ case "email":
296
+ if (value && typeof value === "string") {
297
+ const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
298
+ if (!emailRegex.test(value.trim())) {
299
+ return rule.message || "Please enter a valid email address";
300
+ }
301
+ }
302
+ break;
303
+ case "rating":
304
+ if (typeof value === "number") {
305
+ if (value < 1 || value > 5 || !Number.isInteger(value)) {
306
+ return rule.message || "Rating must be between 1 and 5 stars";
307
+ }
308
+ } else if (value !== void 0 && value !== null) {
309
+ return rule.message || "Rating must be a number between 1 and 5";
310
+ }
311
+ break;
312
+ case "minLength":
313
+ if (value && typeof value === "string" && typeof rule.value === "number") {
314
+ if (value.trim().length < rule.value) {
315
+ return rule.message || `Must be at least ${rule.value} characters`;
316
+ }
317
+ }
318
+ break;
319
+ case "maxLength":
320
+ if (value && typeof value === "string" && typeof rule.value === "number") {
321
+ if (value.trim().length > rule.value) {
322
+ return rule.message || `Must be less than ${rule.value} characters`;
323
+ }
324
+ }
325
+ break;
326
+ case "pattern":
327
+ if (value && typeof value === "string" && typeof rule.value === "string") {
328
+ const regex = new RegExp(rule.value);
329
+ if (!regex.test(value)) {
330
+ return rule.message || "Invalid format";
331
+ }
332
+ }
333
+ break;
334
+ case "custom":
335
+ if (rule.validator) {
336
+ const result = rule.validator(value);
337
+ if (result !== true) {
338
+ return typeof result === "string" ? result : rule.message || "Validation failed";
339
+ }
340
+ }
341
+ break;
342
+ }
343
+ }
344
+ return null;
345
+ }
346
+ function validateReviewData(data, customRules) {
347
+ const errors = {};
348
+ const rules = { ...DEFAULT_REVIEW_VALIDATION_RULES, ...customRules };
349
+ Object.entries(rules).forEach(([field, fieldRules]) => {
350
+ const value = data[field];
351
+ const error = validateField(value, fieldRules);
352
+ if (error) {
353
+ errors[field] = error;
354
+ }
355
+ });
356
+ return Object.keys(errors).length > 0 ? errors : null;
357
+ }
358
+ function validateSingleReviewField(field, value, customRules) {
359
+ const rules = { ...DEFAULT_REVIEW_VALIDATION_RULES, ...customRules };
360
+ const fieldRules = rules[field];
361
+ if (!fieldRules) {
362
+ return null;
363
+ }
364
+ return validateField(value, fieldRules);
365
+ }
366
+ function isValidRating(rating) {
367
+ return Number.isInteger(rating) && rating >= 1 && rating <= 5;
368
+ }
369
+ function sanitizeReviewData(data) {
370
+ const sanitized = {
371
+ reviewerName: typeof data.reviewerName === "string" ? data.reviewerName.trim() : "",
372
+ reviewerEmail: typeof data.reviewerEmail === "string" ? data.reviewerEmail.trim().toLowerCase() : "",
373
+ rating: data.rating
374
+ };
375
+ if (data.reviewerTitle && typeof data.reviewerTitle === "string" && data.reviewerTitle.trim()) {
376
+ sanitized.reviewerTitle = data.reviewerTitle.trim();
377
+ }
378
+ if (data.reviewerCompany && typeof data.reviewerCompany === "string" && data.reviewerCompany.trim()) {
379
+ sanitized.reviewerCompany = data.reviewerCompany.trim();
380
+ }
381
+ if (data.content && typeof data.content === "string" && data.content.trim()) {
382
+ sanitized.content = data.content.trim();
383
+ }
384
+ Object.keys(data).forEach((key) => {
385
+ if (!["reviewerName", "reviewerEmail", "rating", "reviewerTitle", "reviewerCompany", "content"].includes(key)) {
386
+ sanitized[key] = data[key];
387
+ }
388
+ });
389
+ return sanitized;
390
+ }
391
+ // Annotate the CommonJS export names for ESM import in node:
392
+ 0 && (module.exports = {
393
+ DEFAULT_REVIEW_VALIDATION_RULES,
394
+ ReviewClient,
395
+ isValidRating,
396
+ sanitizeReviewData,
397
+ validateReviewData,
398
+ validateSingleReviewField
399
+ });
400
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/client/index.ts","../../src/client/client.ts","../../src/client/validation.ts"],"sourcesContent":["// Client-side utilities for review management\nexport { ReviewClient } from './client';\nexport { \n validateReviewData, \n validateSingleReviewField, \n sanitizeReviewData,\n isValidRating,\n DEFAULT_REVIEW_VALIDATION_RULES \n} from './validation';\nexport * from '../types';","import { Review, ReviewFormData, ReviewApiResponse, ReviewFetchConfig, ReviewSubmissionConfig } from '../types';\n\n/**\n * Development logging utilities\n */\nconst logDebug = (context: string, data?: any) => {\n if (typeof process !== 'undefined' && process.env?.NODE_ENV === 'development') {\n console.log(`[ReviewClient] ${context}`, data ? ':' : '');\n if (data) {\n console.log(`[ReviewClient] Data:`, data);\n }\n }\n};\n\nconst logError = (context: string, error: unknown, additionalData?: any) => {\n if (typeof process !== 'undefined' && process.env?.NODE_ENV === 'development') {\n console.error(`[ReviewClient] ${context}:`, error);\n if (additionalData) {\n console.error(`[ReviewClient] Additional data:`, additionalData);\n }\n }\n};\n\n/**\n * Default error messages\n */\nconst DEFAULT_ERROR_MESSAGES = {\n SUBMISSION_FAILED: 'Failed to submit review',\n FETCH_FAILED: 'Failed to fetch reviews',\n SERVER_ERROR: 'Server error. Please try again.',\n NETWORK_ERROR: 'Network error. Please check your connection.',\n GENERAL_ERROR: 'An unexpected error occurred'\n};\n\n/**\n * Generic review client for fetching and submitting reviews\n */\nexport class ReviewClient {\n private fetchConfig?: ReviewFetchConfig;\n private submitConfig?: ReviewSubmissionConfig;\n private maxRetries: number = 3;\n private timeout: number = 30000; // 30 seconds\n\n constructor(config: {\n fetchConfig?: ReviewFetchConfig;\n submitConfig?: ReviewSubmissionConfig;\n maxRetries?: number;\n timeout?: number;\n }) {\n this.fetchConfig = config.fetchConfig;\n this.submitConfig = config.submitConfig;\n // Global defaults, can be overridden per operation\n if (config.maxRetries !== undefined) this.maxRetries = config.maxRetries;\n if (config.timeout !== undefined) this.timeout = config.timeout;\n }\n\n /**\n * Fetch reviews with retry logic\n */\n async fetchReviews(params?: {\n limit?: number;\n offset?: number;\n featured?: boolean;\n minRating?: number;\n }, retryCount = 0): Promise<Review[]> {\n if (!this.fetchConfig) {\n throw new Error('Fetch configuration not provided');\n }\n\n // Use fetchConfig-specific values, fall back to global defaults\n const maxRetries = this.fetchConfig.maxRetries ?? this.maxRetries;\n const timeout = this.fetchConfig.timeout ?? this.timeout;\n const retryDelay = Math.min(1000 * Math.pow(2, retryCount), 5000);\n\n try {\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), timeout);\n\n // Build query parameters\n const queryParams = new URLSearchParams();\n const mergedParams = { ...this.fetchConfig.params, ...params };\n \n Object.entries(mergedParams).forEach(([key, value]) => {\n if (value !== undefined && value !== null) {\n queryParams.append(key, String(value));\n }\n });\n\n const url = `${this.fetchConfig.endpoint}?${queryParams.toString()}`;\n\n logError('Fetching reviews', null, { url, params: mergedParams, retryCount });\n\n const response = await fetch(url, {\n method: this.fetchConfig.method || 'GET',\n headers: {\n 'Content-Type': 'application/json',\n 'Accept': 'application/json',\n ...this.fetchConfig.headers\n },\n signal: controller.signal\n });\n\n clearTimeout(timeoutId);\n\n if (!response.ok) {\n const errorDetails = {\n status: response.status,\n statusText: response.statusText,\n url: response.url\n };\n\n logError('API response error', null, errorDetails);\n\n if (response.status >= 500) {\n const error = new Error(DEFAULT_ERROR_MESSAGES.SERVER_ERROR);\n (error as any).retryable = true;\n throw error;\n } else {\n throw new Error(`${DEFAULT_ERROR_MESSAGES.FETCH_FAILED}: HTTP ${response.status}`);\n }\n }\n\n let responseData;\n try {\n responseData = await response.json();\n } catch (parseError) {\n logError('Failed to parse response', parseError);\n throw new Error('Invalid response format from server');\n }\n\n // Transform response if transformer provided\n const reviews = this.fetchConfig.transformResponse \n ? this.fetchConfig.transformResponse(responseData)\n : (Array.isArray(responseData) ? responseData : []);\n\n logDebug('Reviews fetched successfully', { count: reviews.length });\n\n return reviews;\n\n } catch (error) {\n logError('Review fetch error', error, { retryCount, maxRetries });\n\n // Handle timeout/abort errors\n if (error instanceof Error && error.name === 'AbortError') {\n const timeoutError = new Error('Request timeout. Please try again.');\n (timeoutError as any).retryable = true;\n throw timeoutError;\n }\n\n // Handle network errors\n if (error instanceof TypeError && (\n error.message.includes('fetch') ||\n error.message.includes('network') ||\n error.message.includes('Failed to fetch')\n )) {\n const networkError = new Error(DEFAULT_ERROR_MESSAGES.NETWORK_ERROR);\n (networkError as any).retryable = true;\n throw networkError;\n }\n\n // Retry logic\n const isRetryable = (error as any).retryable === true;\n if (isRetryable && retryCount < maxRetries) {\n logDebug(`Retrying in ${retryDelay}ms`, { retryCount: retryCount + 1 });\n await new Promise(resolve => setTimeout(resolve, retryDelay));\n return this.fetchReviews(params, retryCount + 1);\n }\n\n // Call error callback if provided\n if (this.fetchConfig.onError && error instanceof Error) {\n this.fetchConfig.onError(error);\n }\n\n if (error instanceof Error) {\n throw error;\n }\n\n throw new Error(DEFAULT_ERROR_MESSAGES.GENERAL_ERROR);\n }\n }\n\n /**\n * Submit a review with retry logic\n */\n async submitReview(reviewData: ReviewFormData, retryCount = 0): Promise<ReviewApiResponse> {\n if (!this.submitConfig) {\n throw new Error('Submit configuration not provided');\n }\n\n // Use submitConfig-specific values, fall back to global defaults\n const maxRetries = this.submitConfig.maxRetries ?? this.maxRetries;\n const timeout = this.submitConfig.timeout ?? this.timeout;\n const retryDelay = Math.min(1000 * Math.pow(2, retryCount), 5000);\n\n try {\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), timeout);\n\n // Transform request data if transformer provided\n const requestData = this.submitConfig.transformRequest\n ? this.submitConfig.transformRequest(reviewData)\n : reviewData;\n\n logDebug('Submitting review', {\n endpoint: this.submitConfig.endpoint,\n retryCount,\n reviewData: { ...reviewData, RecaptchaToken: reviewData.RecaptchaToken ? '[REDACTED]' : undefined }\n });\n\n const response = await fetch(this.submitConfig.endpoint, {\n method: this.submitConfig.method || 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'Accept': 'application/json',\n ...this.submitConfig.headers\n },\n body: JSON.stringify(requestData),\n signal: controller.signal\n });\n\n clearTimeout(timeoutId);\n\n if (!response.ok) {\n const errorDetails = {\n status: response.status,\n statusText: response.statusText,\n url: response.url\n };\n\n logError('API response error', null, errorDetails);\n\n if (response.status >= 400 && response.status < 500) {\n let errorText = 'Client error';\n try {\n errorText = await response.text();\n } catch (parseError) {\n logError('Failed to parse error response', parseError);\n }\n throw new Error(`${DEFAULT_ERROR_MESSAGES.SUBMISSION_FAILED}: ${errorText}`);\n } else if (response.status >= 500) {\n const error = new Error(DEFAULT_ERROR_MESSAGES.SERVER_ERROR);\n (error as any).retryable = true;\n throw error;\n } else {\n throw new Error(`${DEFAULT_ERROR_MESSAGES.SUBMISSION_FAILED}: HTTP ${response.status}`);\n }\n }\n\n let responseData;\n try {\n responseData = await response.json();\n } catch (parseError) {\n logError('Failed to parse successful response', parseError);\n throw new Error('Invalid response format from server');\n }\n\n // Transform response if transformer provided\n const finalResponse = this.submitConfig.transformResponse\n ? this.submitConfig.transformResponse(responseData)\n : {\n success: true,\n message: responseData.message || 'Review submitted successfully',\n reviewId: responseData.reviewId || responseData.id,\n status: responseData.status,\n data: responseData\n };\n\n logDebug('Review submitted successfully', { reviewId: finalResponse.reviewId });\n\n // Call success callback if provided\n if (this.submitConfig.onSuccess) {\n this.submitConfig.onSuccess(finalResponse);\n }\n\n return finalResponse;\n\n } catch (error) {\n logError('Review submission error', error, { retryCount, maxRetries });\n\n // Handle timeout/abort errors\n if (error instanceof Error && error.name === 'AbortError') {\n const timeoutError = new Error('Request timeout. Please try again.');\n (timeoutError as any).retryable = true;\n throw timeoutError;\n }\n\n // Handle network errors\n if (error instanceof TypeError && (\n error.message.includes('fetch') ||\n error.message.includes('network') ||\n error.message.includes('Failed to fetch')\n )) {\n const networkError = new Error(DEFAULT_ERROR_MESSAGES.NETWORK_ERROR);\n (networkError as any).retryable = true;\n throw networkError;\n }\n\n // Retry logic\n const isRetryable = (error as any).retryable === true;\n if (isRetryable && retryCount < maxRetries) {\n logDebug(`Retrying in ${retryDelay}ms`, { retryCount: retryCount + 1 });\n await new Promise(resolve => setTimeout(resolve, retryDelay));\n return this.submitReview(reviewData, retryCount + 1);\n }\n\n // Call error callback if provided\n if (this.submitConfig.onError && error instanceof Error) {\n this.submitConfig.onError(error);\n }\n\n if (error instanceof Error) {\n throw error;\n }\n\n throw new Error(DEFAULT_ERROR_MESSAGES.GENERAL_ERROR);\n }\n }\n\n /**\n * Update configuration\n */\n updateConfig(config: {\n fetchConfig?: Partial<ReviewFetchConfig>;\n submitConfig?: Partial<ReviewSubmissionConfig>;\n }) {\n if (config.fetchConfig) {\n this.fetchConfig = { ...this.fetchConfig, ...config.fetchConfig } as ReviewFetchConfig;\n }\n if (config.submitConfig) {\n this.submitConfig = { ...this.submitConfig, ...config.submitConfig } as ReviewSubmissionConfig;\n }\n }\n}","import { ReviewFormData, ValidationErrors, ValidationRule } from '../types';\n\n/**\n * Default validation rules for review forms\n */\nexport const DEFAULT_REVIEW_VALIDATION_RULES = {\n reviewerName: [\n { type: 'required' as const, message: 'Name is required' },\n { type: 'minLength' as const, value: 2, message: 'Name must be at least 2 characters' },\n { type: 'maxLength' as const, value: 100, message: 'Name must be less than 100 characters' }\n ],\n reviewerEmail: [\n { type: 'required' as const, message: 'Email is required' },\n { type: 'email' as const, message: 'Please enter a valid email address' }\n ],\n rating: [\n { type: 'required' as const, message: 'Rating is required' },\n { type: 'rating' as const, message: 'Please select a rating between 1 and 5 stars' }\n ],\n content: [\n { type: 'maxLength' as const, value: 2000, message: 'Review content must be less than 2000 characters' }\n ],\n reviewerTitle: [\n { type: 'maxLength' as const, value: 100, message: 'Title must be less than 100 characters' }\n ],\n reviewerCompany: [\n { type: 'maxLength' as const, value: 100, message: 'Company name must be less than 100 characters' }\n ]\n};\n\n/**\n * Validate a single field value against validation rules\n */\nfunction validateField(value: any, rules: ValidationRule[]): string | null {\n for (const rule of rules) {\n switch (rule.type) {\n case 'required':\n if (!value || (typeof value === 'string' && value.trim().length === 0)) {\n return rule.message || 'This field is required';\n }\n break;\n \n case 'email':\n if (value && typeof value === 'string') {\n const emailRegex = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/;\n if (!emailRegex.test(value.trim())) {\n return rule.message || 'Please enter a valid email address';\n }\n }\n break;\n \n case 'rating':\n if (typeof value === 'number') {\n if (value < 1 || value > 5 || !Number.isInteger(value)) {\n return rule.message || 'Rating must be between 1 and 5 stars';\n }\n } else if (value !== undefined && value !== null) {\n return rule.message || 'Rating must be a number between 1 and 5';\n }\n break;\n \n case 'minLength':\n if (value && typeof value === 'string' && typeof rule.value === 'number') {\n if (value.trim().length < rule.value) {\n return rule.message || `Must be at least ${rule.value} characters`;\n }\n }\n break;\n \n case 'maxLength':\n if (value && typeof value === 'string' && typeof rule.value === 'number') {\n if (value.trim().length > rule.value) {\n return rule.message || `Must be less than ${rule.value} characters`;\n }\n }\n break;\n \n case 'pattern':\n if (value && typeof value === 'string' && typeof rule.value === 'string') {\n const regex = new RegExp(rule.value);\n if (!regex.test(value)) {\n return rule.message || 'Invalid format';\n }\n }\n break;\n \n case 'custom':\n if (rule.validator) {\n const result = rule.validator(value);\n if (result !== true) {\n return typeof result === 'string' ? result : rule.message || 'Validation failed';\n }\n }\n break;\n }\n }\n \n return null;\n}\n\n/**\n * Validate review form data\n */\nexport function validateReviewData(\n data: ReviewFormData, \n customRules?: { [field: string]: ValidationRule[] }\n): ValidationErrors | null {\n const errors: ValidationErrors = {};\n const rules = { ...DEFAULT_REVIEW_VALIDATION_RULES, ...customRules };\n \n // Validate each field that has rules\n Object.entries(rules).forEach(([field, fieldRules]) => {\n const value = (data as any)[field];\n const error = validateField(value, fieldRules);\n if (error) {\n errors[field] = error;\n }\n });\n \n // Return null if no errors, otherwise return errors object\n return Object.keys(errors).length > 0 ? errors : null;\n}\n\n/**\n * Validate a single field - useful for real-time validation\n */\nexport function validateSingleReviewField(\n field: string,\n value: any,\n customRules?: { [field: string]: ValidationRule[] }\n): string | null {\n const rules = { ...DEFAULT_REVIEW_VALIDATION_RULES, ...customRules };\n const fieldRules = rules[field as keyof typeof rules];\n \n if (!fieldRules) {\n return null;\n }\n \n return validateField(value, fieldRules);\n}\n\n/**\n * Check if rating is valid (1-5 stars)\n */\nexport function isValidRating(rating: number): boolean {\n return Number.isInteger(rating) && rating >= 1 && rating <= 5;\n}\n\n/**\n * Sanitize review form data - trim strings and remove empty optional fields\n */\nexport function sanitizeReviewData(data: ReviewFormData): ReviewFormData {\n const sanitized: ReviewFormData = {\n reviewerName: typeof data.reviewerName === 'string' ? data.reviewerName.trim() : '',\n reviewerEmail: typeof data.reviewerEmail === 'string' ? data.reviewerEmail.trim().toLowerCase() : '',\n rating: data.rating\n };\n \n // Add optional fields only if they have values\n if (data.reviewerTitle && typeof data.reviewerTitle === 'string' && data.reviewerTitle.trim()) {\n sanitized.reviewerTitle = data.reviewerTitle.trim();\n }\n \n if (data.reviewerCompany && typeof data.reviewerCompany === 'string' && data.reviewerCompany.trim()) {\n sanitized.reviewerCompany = data.reviewerCompany.trim();\n }\n \n if (data.content && typeof data.content === 'string' && data.content.trim()) {\n sanitized.content = data.content.trim();\n }\n \n // Preserve any additional fields\n Object.keys(data).forEach(key => {\n if (!['reviewerName', 'reviewerEmail', 'rating', 'reviewerTitle', 'reviewerCompany', 'content'].includes(key)) {\n (sanitized as any)[key] = (data as any)[key];\n }\n });\n \n return sanitized;\n}"],"mappings":"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","names":[]}