@businessflow/reviews 1.0.0 → 2.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.
- package/dist/index.js +0 -99
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +0 -99
- package/dist/index.mjs.map +1 -1
- package/dist/server/index.d.mts +86 -1
- package/dist/server/index.d.ts +86 -1
- package/dist/server/index.js +223 -99
- package/dist/server/index.js.map +1 -1
- package/dist/server/index.mjs +219 -99
- package/dist/server/index.mjs.map +1 -1
- package/package.json +6 -6
package/dist/server/index.d.mts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import * as next_server from 'next/server';
|
|
1
2
|
import { NextRequest, NextResponse } from 'next/server';
|
|
2
3
|
|
|
3
4
|
interface Review {
|
|
@@ -186,4 +187,88 @@ declare function verifyRecaptcha(token: string, config: RecaptchaConfig): Promis
|
|
|
186
187
|
*/
|
|
187
188
|
declare function getRecaptchaErrorMessage(errorCodes: string[]): string;
|
|
188
189
|
|
|
189
|
-
|
|
190
|
+
/**
|
|
191
|
+
* Configuration for BusinessFlow CRM review integration
|
|
192
|
+
*/
|
|
193
|
+
interface BusinessFlowReviewConfig {
|
|
194
|
+
/** BusinessFlow API base URL (e.g., https://api.businessflow.co.za) */
|
|
195
|
+
apiUrl: string;
|
|
196
|
+
/** BusinessFlow API key for authentication */
|
|
197
|
+
apiKey: string;
|
|
198
|
+
/** Source URL for review attribution (optional, will use NEXT_PUBLIC_SITE_URL if not provided) */
|
|
199
|
+
sourceUrl?: string;
|
|
200
|
+
/** reCAPTCHA secret key for server-side verification (optional) */
|
|
201
|
+
recaptchaSecret?: string;
|
|
202
|
+
/** Minimum reCAPTCHA score for v3 (optional, defaults to 0.5) */
|
|
203
|
+
minimumScore?: number;
|
|
204
|
+
/** Custom success callback after review is submitted to BusinessFlow */
|
|
205
|
+
onSuccess?: (data: ReviewFormData, response: ReviewApiResponse) => Promise<void>;
|
|
206
|
+
/** Custom error callback if submission fails */
|
|
207
|
+
onError?: (data: ReviewFormData, error: Error) => Promise<void>;
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Create a NextJS API route handler that submits reviews to BusinessFlow CRM
|
|
211
|
+
*
|
|
212
|
+
* @example
|
|
213
|
+
* ```typescript
|
|
214
|
+
* // app/api/reviews/submit/route.ts
|
|
215
|
+
* import { createBusinessFlowReviewHandler } from '@businessflow/reviews/server';
|
|
216
|
+
*
|
|
217
|
+
* export const POST = createBusinessFlowReviewHandler({
|
|
218
|
+
* apiUrl: process.env.BUSINESS_FLOW_API_URL!,
|
|
219
|
+
* apiKey: process.env.BUSINESS_FLOW_API_KEY!,
|
|
220
|
+
* sourceUrl: process.env.SITE_URL!,
|
|
221
|
+
* recaptchaSecret: process.env.RECAPTCHA_SECRET_KEY!,
|
|
222
|
+
* });
|
|
223
|
+
* ```
|
|
224
|
+
*/
|
|
225
|
+
declare function createBusinessFlowReviewHandler(config: BusinessFlowReviewConfig): (request: next_server.NextRequest) => Promise<next_server.NextResponse>;
|
|
226
|
+
/**
|
|
227
|
+
* Create a NextJS API route handler that fetches featured reviews from BusinessFlow CRM
|
|
228
|
+
*
|
|
229
|
+
* @example
|
|
230
|
+
* ```typescript
|
|
231
|
+
* // app/api/reviews/featured/route.ts
|
|
232
|
+
* import { createBusinessFlowFeaturedHandler } from '@businessflow/reviews/server';
|
|
233
|
+
*
|
|
234
|
+
* export const GET = createBusinessFlowFeaturedHandler({
|
|
235
|
+
* apiUrl: process.env.BUSINESS_FLOW_API_URL!,
|
|
236
|
+
* apiKey: process.env.BUSINESS_FLOW_API_KEY!,
|
|
237
|
+
* });
|
|
238
|
+
* ```
|
|
239
|
+
*/
|
|
240
|
+
declare function createBusinessFlowFeaturedHandler(config: Pick<BusinessFlowReviewConfig, 'apiUrl' | 'apiKey'>): (request: next_server.NextRequest) => Promise<next_server.NextResponse>;
|
|
241
|
+
/**
|
|
242
|
+
* Simplified BusinessFlow review handler for basic use cases
|
|
243
|
+
* Uses environment variables for configuration
|
|
244
|
+
*
|
|
245
|
+
* @example
|
|
246
|
+
* ```typescript
|
|
247
|
+
* // app/api/reviews/submit/route.ts
|
|
248
|
+
* import { createSimpleBusinessFlowReviewHandler } from '@businessflow/reviews/server';
|
|
249
|
+
*
|
|
250
|
+
* export const POST = createSimpleBusinessFlowReviewHandler();
|
|
251
|
+
* ```
|
|
252
|
+
*
|
|
253
|
+
* Required environment variables:
|
|
254
|
+
* - BUSINESS_FLOW_API_URL
|
|
255
|
+
* - BUSINESS_FLOW_API_KEY
|
|
256
|
+
* - RECAPTCHA_SECRET_KEY (optional)
|
|
257
|
+
* - NEXT_PUBLIC_SITE_URL (optional, for sourceUrl)
|
|
258
|
+
*/
|
|
259
|
+
declare function createSimpleBusinessFlowReviewHandler(): (request: next_server.NextRequest) => Promise<next_server.NextResponse>;
|
|
260
|
+
/**
|
|
261
|
+
* Simplified BusinessFlow featured reviews handler for basic use cases
|
|
262
|
+
* Uses environment variables for configuration
|
|
263
|
+
*
|
|
264
|
+
* @example
|
|
265
|
+
* ```typescript
|
|
266
|
+
* // app/api/reviews/featured/route.ts
|
|
267
|
+
* import { createSimpleBusinessFlowFeaturedHandler } from '@businessflow/reviews/server';
|
|
268
|
+
*
|
|
269
|
+
* export const GET = createSimpleBusinessFlowFeaturedHandler();
|
|
270
|
+
* ```
|
|
271
|
+
*/
|
|
272
|
+
declare function createSimpleBusinessFlowFeaturedHandler(): (request: next_server.NextRequest) => Promise<next_server.NextResponse>;
|
|
273
|
+
|
|
274
|
+
export { type BusinessFlowReviewConfig, type RecaptchaConfig, type RecaptchaVerificationResult, type Review, type ReviewApiResponse, type ReviewFetchConfig, type ReviewFieldConfig, type ReviewFormConfig, type ReviewFormData, type ReviewHandlerConfig, type ReviewSubmissionConfig, type StarRatingProps, type TestimonialDisplayConfig, type ValidationErrors, type ValidationRule, createBusinessFlowFeaturedHandler, createBusinessFlowReviewHandler, createCorsHeaders, createReviewFetchHandler, createReviewHandler, createReviewSubmitHandler, createSimpleBusinessFlowFeaturedHandler, createSimpleBusinessFlowReviewHandler, getRecaptchaErrorMessage, handleOptions, verifyRecaptcha };
|
package/dist/server/index.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import * as next_server from 'next/server';
|
|
1
2
|
import { NextRequest, NextResponse } from 'next/server';
|
|
2
3
|
|
|
3
4
|
interface Review {
|
|
@@ -186,4 +187,88 @@ declare function verifyRecaptcha(token: string, config: RecaptchaConfig): Promis
|
|
|
186
187
|
*/
|
|
187
188
|
declare function getRecaptchaErrorMessage(errorCodes: string[]): string;
|
|
188
189
|
|
|
189
|
-
|
|
190
|
+
/**
|
|
191
|
+
* Configuration for BusinessFlow CRM review integration
|
|
192
|
+
*/
|
|
193
|
+
interface BusinessFlowReviewConfig {
|
|
194
|
+
/** BusinessFlow API base URL (e.g., https://api.businessflow.co.za) */
|
|
195
|
+
apiUrl: string;
|
|
196
|
+
/** BusinessFlow API key for authentication */
|
|
197
|
+
apiKey: string;
|
|
198
|
+
/** Source URL for review attribution (optional, will use NEXT_PUBLIC_SITE_URL if not provided) */
|
|
199
|
+
sourceUrl?: string;
|
|
200
|
+
/** reCAPTCHA secret key for server-side verification (optional) */
|
|
201
|
+
recaptchaSecret?: string;
|
|
202
|
+
/** Minimum reCAPTCHA score for v3 (optional, defaults to 0.5) */
|
|
203
|
+
minimumScore?: number;
|
|
204
|
+
/** Custom success callback after review is submitted to BusinessFlow */
|
|
205
|
+
onSuccess?: (data: ReviewFormData, response: ReviewApiResponse) => Promise<void>;
|
|
206
|
+
/** Custom error callback if submission fails */
|
|
207
|
+
onError?: (data: ReviewFormData, error: Error) => Promise<void>;
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Create a NextJS API route handler that submits reviews to BusinessFlow CRM
|
|
211
|
+
*
|
|
212
|
+
* @example
|
|
213
|
+
* ```typescript
|
|
214
|
+
* // app/api/reviews/submit/route.ts
|
|
215
|
+
* import { createBusinessFlowReviewHandler } from '@businessflow/reviews/server';
|
|
216
|
+
*
|
|
217
|
+
* export const POST = createBusinessFlowReviewHandler({
|
|
218
|
+
* apiUrl: process.env.BUSINESS_FLOW_API_URL!,
|
|
219
|
+
* apiKey: process.env.BUSINESS_FLOW_API_KEY!,
|
|
220
|
+
* sourceUrl: process.env.SITE_URL!,
|
|
221
|
+
* recaptchaSecret: process.env.RECAPTCHA_SECRET_KEY!,
|
|
222
|
+
* });
|
|
223
|
+
* ```
|
|
224
|
+
*/
|
|
225
|
+
declare function createBusinessFlowReviewHandler(config: BusinessFlowReviewConfig): (request: next_server.NextRequest) => Promise<next_server.NextResponse>;
|
|
226
|
+
/**
|
|
227
|
+
* Create a NextJS API route handler that fetches featured reviews from BusinessFlow CRM
|
|
228
|
+
*
|
|
229
|
+
* @example
|
|
230
|
+
* ```typescript
|
|
231
|
+
* // app/api/reviews/featured/route.ts
|
|
232
|
+
* import { createBusinessFlowFeaturedHandler } from '@businessflow/reviews/server';
|
|
233
|
+
*
|
|
234
|
+
* export const GET = createBusinessFlowFeaturedHandler({
|
|
235
|
+
* apiUrl: process.env.BUSINESS_FLOW_API_URL!,
|
|
236
|
+
* apiKey: process.env.BUSINESS_FLOW_API_KEY!,
|
|
237
|
+
* });
|
|
238
|
+
* ```
|
|
239
|
+
*/
|
|
240
|
+
declare function createBusinessFlowFeaturedHandler(config: Pick<BusinessFlowReviewConfig, 'apiUrl' | 'apiKey'>): (request: next_server.NextRequest) => Promise<next_server.NextResponse>;
|
|
241
|
+
/**
|
|
242
|
+
* Simplified BusinessFlow review handler for basic use cases
|
|
243
|
+
* Uses environment variables for configuration
|
|
244
|
+
*
|
|
245
|
+
* @example
|
|
246
|
+
* ```typescript
|
|
247
|
+
* // app/api/reviews/submit/route.ts
|
|
248
|
+
* import { createSimpleBusinessFlowReviewHandler } from '@businessflow/reviews/server';
|
|
249
|
+
*
|
|
250
|
+
* export const POST = createSimpleBusinessFlowReviewHandler();
|
|
251
|
+
* ```
|
|
252
|
+
*
|
|
253
|
+
* Required environment variables:
|
|
254
|
+
* - BUSINESS_FLOW_API_URL
|
|
255
|
+
* - BUSINESS_FLOW_API_KEY
|
|
256
|
+
* - RECAPTCHA_SECRET_KEY (optional)
|
|
257
|
+
* - NEXT_PUBLIC_SITE_URL (optional, for sourceUrl)
|
|
258
|
+
*/
|
|
259
|
+
declare function createSimpleBusinessFlowReviewHandler(): (request: next_server.NextRequest) => Promise<next_server.NextResponse>;
|
|
260
|
+
/**
|
|
261
|
+
* Simplified BusinessFlow featured reviews handler for basic use cases
|
|
262
|
+
* Uses environment variables for configuration
|
|
263
|
+
*
|
|
264
|
+
* @example
|
|
265
|
+
* ```typescript
|
|
266
|
+
* // app/api/reviews/featured/route.ts
|
|
267
|
+
* import { createSimpleBusinessFlowFeaturedHandler } from '@businessflow/reviews/server';
|
|
268
|
+
*
|
|
269
|
+
* export const GET = createSimpleBusinessFlowFeaturedHandler();
|
|
270
|
+
* ```
|
|
271
|
+
*/
|
|
272
|
+
declare function createSimpleBusinessFlowFeaturedHandler(): (request: next_server.NextRequest) => Promise<next_server.NextResponse>;
|
|
273
|
+
|
|
274
|
+
export { type BusinessFlowReviewConfig, type RecaptchaConfig, type RecaptchaVerificationResult, type Review, type ReviewApiResponse, type ReviewFetchConfig, type ReviewFieldConfig, type ReviewFormConfig, type ReviewFormData, type ReviewHandlerConfig, type ReviewSubmissionConfig, type StarRatingProps, type TestimonialDisplayConfig, type ValidationErrors, type ValidationRule, createBusinessFlowFeaturedHandler, createBusinessFlowReviewHandler, createCorsHeaders, createReviewFetchHandler, createReviewHandler, createReviewSubmitHandler, createSimpleBusinessFlowFeaturedHandler, createSimpleBusinessFlowReviewHandler, getRecaptchaErrorMessage, handleOptions, verifyRecaptcha };
|
package/dist/server/index.js
CHANGED
|
@@ -20,10 +20,14 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
20
20
|
// src/server/index.ts
|
|
21
21
|
var index_exports = {};
|
|
22
22
|
__export(index_exports, {
|
|
23
|
+
createBusinessFlowFeaturedHandler: () => createBusinessFlowFeaturedHandler,
|
|
24
|
+
createBusinessFlowReviewHandler: () => createBusinessFlowReviewHandler,
|
|
23
25
|
createCorsHeaders: () => createCorsHeaders,
|
|
24
26
|
createReviewFetchHandler: () => createReviewFetchHandler,
|
|
25
27
|
createReviewHandler: () => createReviewHandler,
|
|
26
28
|
createReviewSubmitHandler: () => createReviewSubmitHandler,
|
|
29
|
+
createSimpleBusinessFlowFeaturedHandler: () => createSimpleBusinessFlowFeaturedHandler,
|
|
30
|
+
createSimpleBusinessFlowReviewHandler: () => createSimpleBusinessFlowReviewHandler,
|
|
27
31
|
getRecaptchaErrorMessage: () => getRecaptchaErrorMessage,
|
|
28
32
|
handleOptions: () => handleOptions,
|
|
29
33
|
verifyRecaptcha: () => verifyRecaptcha
|
|
@@ -32,95 +36,6 @@ module.exports = __toCommonJS(index_exports);
|
|
|
32
36
|
|
|
33
37
|
// src/server/handler.ts
|
|
34
38
|
var import_server = require("next/server");
|
|
35
|
-
|
|
36
|
-
// src/server/recaptcha.ts
|
|
37
|
-
async function verifyRecaptcha(token, config) {
|
|
38
|
-
if (!config.secretKey) {
|
|
39
|
-
console.warn("RECAPTCHA_SECRET_KEY not configured, skipping verification");
|
|
40
|
-
return { success: true };
|
|
41
|
-
}
|
|
42
|
-
if (!token || typeof token !== "string") {
|
|
43
|
-
return {
|
|
44
|
-
success: false,
|
|
45
|
-
errorCodes: ["missing-input-response"]
|
|
46
|
-
};
|
|
47
|
-
}
|
|
48
|
-
const timeout = config.timeoutMs || 1e4;
|
|
49
|
-
try {
|
|
50
|
-
const controller = new AbortController();
|
|
51
|
-
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
52
|
-
const response = await fetch("https://www.google.com/recaptcha/api/siteverify", {
|
|
53
|
-
method: "POST",
|
|
54
|
-
headers: {
|
|
55
|
-
"Content-Type": "application/x-www-form-urlencoded"
|
|
56
|
-
},
|
|
57
|
-
body: new URLSearchParams({
|
|
58
|
-
secret: config.secretKey,
|
|
59
|
-
response: token
|
|
60
|
-
}),
|
|
61
|
-
signal: controller.signal
|
|
62
|
-
});
|
|
63
|
-
clearTimeout(timeoutId);
|
|
64
|
-
if (!response.ok) {
|
|
65
|
-
console.error("reCAPTCHA API returned non-OK status:", response.status);
|
|
66
|
-
return {
|
|
67
|
-
success: false,
|
|
68
|
-
errorCodes: ["recaptcha-api-error"]
|
|
69
|
-
};
|
|
70
|
-
}
|
|
71
|
-
const data = await response.json();
|
|
72
|
-
if (config.minimumScore !== void 0 && data.score !== void 0) {
|
|
73
|
-
if (data.score < config.minimumScore) {
|
|
74
|
-
return {
|
|
75
|
-
success: false,
|
|
76
|
-
score: data.score,
|
|
77
|
-
errorCodes: ["score-threshold-not-met"]
|
|
78
|
-
};
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
return {
|
|
82
|
-
success: data.success === true,
|
|
83
|
-
score: data.score,
|
|
84
|
-
action: data.action,
|
|
85
|
-
challengeTimestamp: data.challenge_ts,
|
|
86
|
-
hostname: data.hostname,
|
|
87
|
-
errorCodes: data["error-codes"] || []
|
|
88
|
-
};
|
|
89
|
-
} catch (error) {
|
|
90
|
-
if (error instanceof Error && error.name === "AbortError") {
|
|
91
|
-
console.error("reCAPTCHA verification timeout");
|
|
92
|
-
return {
|
|
93
|
-
success: false,
|
|
94
|
-
errorCodes: ["timeout-or-duplicate"]
|
|
95
|
-
};
|
|
96
|
-
}
|
|
97
|
-
console.error("reCAPTCHA verification error:", error);
|
|
98
|
-
return {
|
|
99
|
-
success: false,
|
|
100
|
-
errorCodes: ["network-error"]
|
|
101
|
-
};
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
function getRecaptchaErrorMessage(errorCodes) {
|
|
105
|
-
const errorMessages = {
|
|
106
|
-
"missing-input-secret": "reCAPTCHA secret key is missing",
|
|
107
|
-
"invalid-input-secret": "reCAPTCHA secret key is invalid",
|
|
108
|
-
"missing-input-response": "reCAPTCHA token is missing",
|
|
109
|
-
"invalid-input-response": "reCAPTCHA token is invalid or malformed",
|
|
110
|
-
"bad-request": "The request is invalid or malformed",
|
|
111
|
-
"timeout-or-duplicate": "reCAPTCHA verification timed out or token was already used",
|
|
112
|
-
"score-threshold-not-met": "reCAPTCHA score is below the required threshold",
|
|
113
|
-
"recaptcha-api-error": "reCAPTCHA service is unavailable",
|
|
114
|
-
"network-error": "Network error during reCAPTCHA verification"
|
|
115
|
-
};
|
|
116
|
-
if (!errorCodes || errorCodes.length === 0) {
|
|
117
|
-
return "reCAPTCHA verification failed";
|
|
118
|
-
}
|
|
119
|
-
const knownErrors = errorCodes.filter((code) => errorMessages[code]).map((code) => errorMessages[code]);
|
|
120
|
-
return knownErrors.length > 0 ? knownErrors.join(", ") : "reCAPTCHA verification failed";
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
// src/server/handler.ts
|
|
124
39
|
function createReviewSubmitHandler(config) {
|
|
125
40
|
return async function reviewSubmitHandler(request) {
|
|
126
41
|
if (request.method !== "POST") {
|
|
@@ -228,16 +143,6 @@ function createReviewSubmitHandler(config) {
|
|
|
228
143
|
}
|
|
229
144
|
}
|
|
230
145
|
}
|
|
231
|
-
if (config.recaptcha && body.RecaptchaToken) {
|
|
232
|
-
const recaptchaResult = await verifyRecaptcha(body.RecaptchaToken, config.recaptcha);
|
|
233
|
-
if (!recaptchaResult.success) {
|
|
234
|
-
const errorMessage = getRecaptchaErrorMessage(recaptchaResult.errorCodes || []);
|
|
235
|
-
return import_server.NextResponse.json(
|
|
236
|
-
{ error: `reCAPTCHA verification failed: ${errorMessage}` },
|
|
237
|
-
{ status: 400 }
|
|
238
|
-
);
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
146
|
let response;
|
|
242
147
|
try {
|
|
243
148
|
if (!config.onSubmit) {
|
|
@@ -389,12 +294,231 @@ function handleOptions(allowedOrigins) {
|
|
|
389
294
|
headers: createCorsHeaders(allowedOrigins)
|
|
390
295
|
});
|
|
391
296
|
}
|
|
297
|
+
|
|
298
|
+
// src/server/recaptcha.ts
|
|
299
|
+
async function verifyRecaptcha(token, config) {
|
|
300
|
+
if (!config.secretKey) {
|
|
301
|
+
console.warn("RECAPTCHA_SECRET_KEY not configured, skipping verification");
|
|
302
|
+
return { success: true };
|
|
303
|
+
}
|
|
304
|
+
if (!token || typeof token !== "string") {
|
|
305
|
+
return {
|
|
306
|
+
success: false,
|
|
307
|
+
errorCodes: ["missing-input-response"]
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
const timeout = config.timeoutMs || 1e4;
|
|
311
|
+
try {
|
|
312
|
+
const controller = new AbortController();
|
|
313
|
+
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
314
|
+
const response = await fetch("https://www.google.com/recaptcha/api/siteverify", {
|
|
315
|
+
method: "POST",
|
|
316
|
+
headers: {
|
|
317
|
+
"Content-Type": "application/x-www-form-urlencoded"
|
|
318
|
+
},
|
|
319
|
+
body: new URLSearchParams({
|
|
320
|
+
secret: config.secretKey,
|
|
321
|
+
response: token
|
|
322
|
+
}),
|
|
323
|
+
signal: controller.signal
|
|
324
|
+
});
|
|
325
|
+
clearTimeout(timeoutId);
|
|
326
|
+
if (!response.ok) {
|
|
327
|
+
console.error("reCAPTCHA API returned non-OK status:", response.status);
|
|
328
|
+
return {
|
|
329
|
+
success: false,
|
|
330
|
+
errorCodes: ["recaptcha-api-error"]
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
const data = await response.json();
|
|
334
|
+
if (config.minimumScore !== void 0 && data.score !== void 0) {
|
|
335
|
+
if (data.score < config.minimumScore) {
|
|
336
|
+
return {
|
|
337
|
+
success: false,
|
|
338
|
+
score: data.score,
|
|
339
|
+
errorCodes: ["score-threshold-not-met"]
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
return {
|
|
344
|
+
success: data.success === true,
|
|
345
|
+
score: data.score,
|
|
346
|
+
action: data.action,
|
|
347
|
+
challengeTimestamp: data.challenge_ts,
|
|
348
|
+
hostname: data.hostname,
|
|
349
|
+
errorCodes: data["error-codes"] || []
|
|
350
|
+
};
|
|
351
|
+
} catch (error) {
|
|
352
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
353
|
+
console.error("reCAPTCHA verification timeout");
|
|
354
|
+
return {
|
|
355
|
+
success: false,
|
|
356
|
+
errorCodes: ["timeout-or-duplicate"]
|
|
357
|
+
};
|
|
358
|
+
}
|
|
359
|
+
console.error("reCAPTCHA verification error:", error);
|
|
360
|
+
return {
|
|
361
|
+
success: false,
|
|
362
|
+
errorCodes: ["network-error"]
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
function getRecaptchaErrorMessage(errorCodes) {
|
|
367
|
+
const errorMessages = {
|
|
368
|
+
"missing-input-secret": "reCAPTCHA secret key is missing",
|
|
369
|
+
"invalid-input-secret": "reCAPTCHA secret key is invalid",
|
|
370
|
+
"missing-input-response": "reCAPTCHA token is missing",
|
|
371
|
+
"invalid-input-response": "reCAPTCHA token is invalid or malformed",
|
|
372
|
+
"bad-request": "The request is invalid or malformed",
|
|
373
|
+
"timeout-or-duplicate": "reCAPTCHA verification timed out or token was already used",
|
|
374
|
+
"score-threshold-not-met": "reCAPTCHA score is below the required threshold",
|
|
375
|
+
"recaptcha-api-error": "reCAPTCHA service is unavailable",
|
|
376
|
+
"network-error": "Network error during reCAPTCHA verification"
|
|
377
|
+
};
|
|
378
|
+
if (!errorCodes || errorCodes.length === 0) {
|
|
379
|
+
return "reCAPTCHA verification failed";
|
|
380
|
+
}
|
|
381
|
+
const knownErrors = errorCodes.filter((code) => errorMessages[code]).map((code) => errorMessages[code]);
|
|
382
|
+
return knownErrors.length > 0 ? knownErrors.join(", ") : "reCAPTCHA verification failed";
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
// src/server/businessflow.ts
|
|
386
|
+
function createBusinessFlowReviewHandler(config) {
|
|
387
|
+
const handlerConfig = {
|
|
388
|
+
onSubmit: async (data) => {
|
|
389
|
+
const { token, ...reviewData } = data;
|
|
390
|
+
const payload = {
|
|
391
|
+
ReviewerName: reviewData.reviewerName,
|
|
392
|
+
ReviewerEmail: reviewData.reviewerEmail,
|
|
393
|
+
Rating: reviewData.rating,
|
|
394
|
+
Content: reviewData.content || ""
|
|
395
|
+
};
|
|
396
|
+
try {
|
|
397
|
+
const response = await fetch(`${config.apiUrl}/api/Marketing/Review/Submit`, {
|
|
398
|
+
method: "POST",
|
|
399
|
+
headers: {
|
|
400
|
+
"Content-Type": "application/json",
|
|
401
|
+
"X-API-Key": config.apiKey
|
|
402
|
+
},
|
|
403
|
+
body: JSON.stringify(payload)
|
|
404
|
+
});
|
|
405
|
+
const responseText = await response.text();
|
|
406
|
+
let result;
|
|
407
|
+
try {
|
|
408
|
+
result = JSON.parse(responseText);
|
|
409
|
+
} catch {
|
|
410
|
+
result = {
|
|
411
|
+
success: response.ok,
|
|
412
|
+
message: response.ok ? "Review submitted successfully" : responseText || "Unknown error"
|
|
413
|
+
};
|
|
414
|
+
}
|
|
415
|
+
if (!response.ok) {
|
|
416
|
+
return {
|
|
417
|
+
success: false,
|
|
418
|
+
message: result.message || `HTTP ${response.status}: ${response.statusText}`,
|
|
419
|
+
data: result
|
|
420
|
+
};
|
|
421
|
+
}
|
|
422
|
+
return {
|
|
423
|
+
success: true,
|
|
424
|
+
message: result.message || "Review submitted successfully",
|
|
425
|
+
reviewId: result.reviewId,
|
|
426
|
+
status: result.status,
|
|
427
|
+
data: result
|
|
428
|
+
};
|
|
429
|
+
} catch (error) {
|
|
430
|
+
console.error("BusinessFlow Review API error:", error);
|
|
431
|
+
return {
|
|
432
|
+
success: false,
|
|
433
|
+
message: error instanceof Error ? error.message : "Network error occurred"
|
|
434
|
+
};
|
|
435
|
+
}
|
|
436
|
+
},
|
|
437
|
+
// Configure reCAPTCHA if provided
|
|
438
|
+
recaptcha: config.recaptchaSecret ? {
|
|
439
|
+
secretKey: config.recaptchaSecret,
|
|
440
|
+
minimumScore: config.minimumScore ?? 0.5
|
|
441
|
+
} : void 0,
|
|
442
|
+
// Pass through callbacks
|
|
443
|
+
onSuccess: config.onSuccess,
|
|
444
|
+
onError: config.onError
|
|
445
|
+
};
|
|
446
|
+
return createReviewSubmitHandler(handlerConfig);
|
|
447
|
+
}
|
|
448
|
+
function createBusinessFlowFeaturedHandler(config) {
|
|
449
|
+
return createReviewFetchHandler({
|
|
450
|
+
onFetch: async (params) => {
|
|
451
|
+
const limit = params?.limit || 10;
|
|
452
|
+
try {
|
|
453
|
+
const response = await fetch(`${config.apiUrl}/api/Marketing/Review/Featured?limit=${limit}`, {
|
|
454
|
+
method: "GET",
|
|
455
|
+
headers: {
|
|
456
|
+
"Content-Type": "application/json",
|
|
457
|
+
"X-API-Key": config.apiKey
|
|
458
|
+
}
|
|
459
|
+
});
|
|
460
|
+
if (!response.ok) {
|
|
461
|
+
const errorText = await response.text();
|
|
462
|
+
throw new Error(`Failed to fetch featured reviews: ${errorText}`);
|
|
463
|
+
}
|
|
464
|
+
const data = await response.json();
|
|
465
|
+
return data.map((item) => ({
|
|
466
|
+
id: `review_${Date.now()}_${Math.random()}`,
|
|
467
|
+
// Generate unique ID
|
|
468
|
+
reviewerName: item.reviewerName,
|
|
469
|
+
reviewerEmail: "",
|
|
470
|
+
// Not included in public response
|
|
471
|
+
rating: item.rating,
|
|
472
|
+
content: item.content,
|
|
473
|
+
createdAt: item.submittedAt,
|
|
474
|
+
featured: true,
|
|
475
|
+
approved: true
|
|
476
|
+
}));
|
|
477
|
+
} catch (error) {
|
|
478
|
+
console.error("BusinessFlow Featured Reviews API error:", error);
|
|
479
|
+
throw error;
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
});
|
|
483
|
+
}
|
|
484
|
+
function createSimpleBusinessFlowReviewHandler() {
|
|
485
|
+
const apiUrl = process.env.BUSINESS_FLOW_API_URL;
|
|
486
|
+
const apiKey = process.env.BUSINESS_FLOW_API_KEY;
|
|
487
|
+
if (!apiUrl || !apiKey) {
|
|
488
|
+
throw new Error(
|
|
489
|
+
"Missing required environment variables: BUSINESS_FLOW_API_URL and BUSINESS_FLOW_API_KEY must be set"
|
|
490
|
+
);
|
|
491
|
+
}
|
|
492
|
+
return createBusinessFlowReviewHandler({
|
|
493
|
+
apiUrl,
|
|
494
|
+
apiKey,
|
|
495
|
+
sourceUrl: process.env.NEXT_PUBLIC_SITE_URL,
|
|
496
|
+
recaptchaSecret: process.env.RECAPTCHA_SECRET_KEY
|
|
497
|
+
});
|
|
498
|
+
}
|
|
499
|
+
function createSimpleBusinessFlowFeaturedHandler() {
|
|
500
|
+
const apiUrl = process.env.BUSINESS_FLOW_API_URL;
|
|
501
|
+
const apiKey = process.env.BUSINESS_FLOW_API_KEY;
|
|
502
|
+
if (!apiUrl || !apiKey) {
|
|
503
|
+
throw new Error(
|
|
504
|
+
"Missing required environment variables: BUSINESS_FLOW_API_URL and BUSINESS_FLOW_API_KEY must be set"
|
|
505
|
+
);
|
|
506
|
+
}
|
|
507
|
+
return createBusinessFlowFeaturedHandler({
|
|
508
|
+
apiUrl,
|
|
509
|
+
apiKey
|
|
510
|
+
});
|
|
511
|
+
}
|
|
392
512
|
// Annotate the CommonJS export names for ESM import in node:
|
|
393
513
|
0 && (module.exports = {
|
|
514
|
+
createBusinessFlowFeaturedHandler,
|
|
515
|
+
createBusinessFlowReviewHandler,
|
|
394
516
|
createCorsHeaders,
|
|
395
517
|
createReviewFetchHandler,
|
|
396
518
|
createReviewHandler,
|
|
397
519
|
createReviewSubmitHandler,
|
|
520
|
+
createSimpleBusinessFlowFeaturedHandler,
|
|
521
|
+
createSimpleBusinessFlowReviewHandler,
|
|
398
522
|
getRecaptchaErrorMessage,
|
|
399
523
|
handleOptions,
|
|
400
524
|
verifyRecaptcha
|