@businessflow/reviews 2.1.0 → 2.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/index.js +23 -2
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +23 -2
- package/dist/index.mjs.map +1 -1
- package/dist/server/index.js +55 -14
- package/dist/server/index.js.map +1 -1
- package/dist/server/index.mjs +55 -14
- package/dist/server/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -390,11 +390,32 @@ function createReviewSubmitHandler(config) {
|
|
|
390
390
|
}
|
|
391
391
|
try {
|
|
392
392
|
let body;
|
|
393
|
+
const contentType = request.headers.get("content-type") || "";
|
|
393
394
|
try {
|
|
394
|
-
|
|
395
|
+
if (contentType.includes("multipart/form-data")) {
|
|
396
|
+
const formData = await request.formData();
|
|
397
|
+
body = {
|
|
398
|
+
reviewerName: formData.get("reviewerName"),
|
|
399
|
+
reviewerEmail: formData.get("reviewerEmail"),
|
|
400
|
+
rating: parseInt(formData.get("rating")),
|
|
401
|
+
content: formData.get("content") || void 0,
|
|
402
|
+
reviewerTitle: formData.get("reviewerTitle") || void 0,
|
|
403
|
+
reviewerCompany: formData.get("reviewerCompany") || void 0
|
|
404
|
+
};
|
|
405
|
+
const imageFile = formData.get("image");
|
|
406
|
+
if (imageFile && imageFile instanceof File) {
|
|
407
|
+
body.image = imageFile;
|
|
408
|
+
}
|
|
409
|
+
const token = formData.get("token");
|
|
410
|
+
if (token) {
|
|
411
|
+
body.token = token;
|
|
412
|
+
}
|
|
413
|
+
} else {
|
|
414
|
+
body = await request.json();
|
|
415
|
+
}
|
|
395
416
|
} catch (parseError) {
|
|
396
417
|
return import_server.NextResponse.json(
|
|
397
|
-
{ error: "Invalid
|
|
418
|
+
{ error: "Invalid request body format" },
|
|
398
419
|
{ status: 400 }
|
|
399
420
|
);
|
|
400
421
|
}
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/client/client.ts","../src/client/validation.ts","../src/server/handler.ts","../src/react/components/StarRating.tsx","../src/react/hooks/useTestimonials.ts","../src/react/components/TestimonialsSection.tsx","../src/react/hooks/useReviewSubmission.ts","../src/react/hooks/useRecaptcha.ts","../src/react/components/ReviewForm.tsx"],"sourcesContent":["// Main entry point - exports commonly used utilities\nexport * from './types';\n\n// Re-export key client utilities\nexport { ReviewClient } from './client';\n\n// Re-export key server utilities\nexport { createReviewHandler } from './server';\n\n// Re-export key React components (most commonly used)\nexport { StarRating } from './react/components/StarRating';\nexport { TestimonialsSection } from './react/components/TestimonialsSection';\nexport { ReviewForm } from './react/components/ReviewForm';\n\n// Version info\nexport const version = '1.0.0';","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}","import { NextRequest, NextResponse } from 'next/server';\nimport { ReviewHandlerConfig, ReviewFormData, ReviewApiResponse, Review } from '../types';\n\n/**\n * Create a generic NextJS API route handler for review submission\n */\nexport function createReviewSubmitHandler(config: ReviewHandlerConfig) {\n return async function reviewSubmitHandler(request: NextRequest): Promise<NextResponse> {\n // Only allow POST requests\n if (request.method !== 'POST') {\n return NextResponse.json(\n { error: 'Method not allowed' },\n { status: 405, headers: { Allow: 'POST' } }\n );\n }\n\n try {\n // Parse request body\n let body: ReviewFormData;\n try {\n body = await request.json();\n } catch (parseError) {\n return NextResponse.json(\n { error: 'Invalid JSON in request body' },\n { status: 400 }\n );\n }\n\n // Rate limiting (if configured)\n if (config.rateLimiter) {\n const allowed = await config.rateLimiter(request);\n if (!allowed) {\n return NextResponse.json(\n { error: 'Too many requests. Please try again later.' },\n { status: 429 }\n );\n }\n }\n\n // Validate required fields (basic validation)\n if (!body.reviewerName || typeof body.reviewerName !== 'string' || body.reviewerName.trim().length === 0) {\n return NextResponse.json(\n { error: 'Reviewer name is required' },\n { status: 400 }\n );\n }\n\n if (!body.reviewerEmail || typeof body.reviewerEmail !== 'string' || body.reviewerEmail.trim().length === 0) {\n return NextResponse.json(\n { error: 'Email is required' },\n { status: 400 }\n );\n }\n\n // Email format validation\n const emailRegex = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/;\n if (!emailRegex.test(body.reviewerEmail.trim())) {\n return NextResponse.json(\n { error: 'Please enter a valid email address' },\n { status: 400 }\n );\n }\n\n // Rating validation\n if (typeof body.rating !== 'number' || body.rating < 1 || body.rating > 5 || !Number.isInteger(body.rating)) {\n return NextResponse.json(\n { error: 'Rating must be between 1 and 5 stars' },\n { status: 400 }\n );\n }\n\n // Custom validation (if configured)\n if (config.validation) {\n for (const [field, rules] of Object.entries(config.validation)) {\n const value = (body as any)[field];\n \n for (const rule of rules) {\n let isValid = true;\n let errorMessage = rule.message || 'Validation failed';\n\n switch (rule.type) {\n case 'required':\n isValid = value != null && value !== '' && \n (typeof value !== 'string' || value.trim() !== '');\n break;\n \n case 'email':\n if (value) {\n isValid = emailRegex.test(String(value).trim());\n }\n break;\n \n case 'rating':\n if (value !== undefined) {\n isValid = typeof value === 'number' && Number.isInteger(value) && value >= 1 && value <= 5;\n }\n break;\n \n case 'minLength':\n if (value && typeof rule.value === 'number') {\n isValid = String(value).length >= rule.value;\n }\n break;\n \n case 'maxLength':\n if (value && typeof rule.value === 'number') {\n isValid = String(value).length <= rule.value;\n }\n break;\n \n case 'pattern':\n if (value && typeof rule.value === 'string') {\n const regex = new RegExp(rule.value);\n isValid = regex.test(String(value));\n }\n break;\n \n case 'custom':\n if (rule.validator) {\n const result = rule.validator(value);\n isValid = result === true;\n if (typeof result === 'string') {\n errorMessage = result;\n }\n }\n break;\n }\n\n if (!isValid) {\n return NextResponse.json(\n { error: errorMessage },\n { status: 400 }\n );\n }\n }\n }\n }\n\n // reCAPTCHA verification (if configured)\n // if (config.recaptcha && (body as any).RecaptchaToken) {\n // const recaptchaResult = await verifyRecaptcha((body as any).RecaptchaToken, config.recaptcha);\n \n // if (!recaptchaResult.success) {\n // const errorMessage = getRecaptchaErrorMessage(recaptchaResult.errorCodes || []);\n // return NextResponse.json(\n // { error: `reCAPTCHA verification failed: ${errorMessage}` },\n // { status: 400 }\n // );\n // }\n // }\n\n // Call the user-provided submit function\n let response: ReviewApiResponse;\n try {\n if (!config.onSubmit) {\n return NextResponse.json(\n { error: 'Review submission not configured' },\n { status: 500 }\n );\n }\n\n response = await config.onSubmit(body);\n } catch (submitError) {\n console.error('Review submission error:', submitError);\n \n // Call error callback if provided\n if (config.onError) {\n await config.onError(body, submitError instanceof Error ? submitError : new Error(String(submitError)));\n }\n\n return NextResponse.json(\n { error: 'Failed to submit review. Please try again.' },\n { status: 500 }\n );\n }\n\n // Validate response from submit function\n if (!response || typeof response !== 'object') {\n console.error('Invalid response from submit function:', response);\n return NextResponse.json(\n { error: 'Internal server error' },\n { status: 500 }\n );\n }\n\n // Handle unsuccessful submission\n if (!response.success) {\n const statusCode = response.message?.includes('validation') ? 400 : 500;\n return NextResponse.json(\n { \n error: response.message || 'Failed to submit review',\n data: response.data \n },\n { status: statusCode }\n );\n }\n\n // Call success callback if provided\n if (config.onSuccess) {\n try {\n await config.onSuccess(body, response);\n } catch (callbackError) {\n console.error('Success callback error:', callbackError);\n // Don't fail the request if callback fails\n }\n }\n\n // Return successful response\n return NextResponse.json({\n success: true,\n message: response.message || 'Review submitted successfully',\n reviewId: response.reviewId,\n status: response.status,\n data: response.data\n });\n\n } catch (error) {\n console.error('Unexpected error in review submit handler:', error);\n \n // Try to call error callback\n if (config.onError) {\n try {\n await config.onError({} as ReviewFormData, error instanceof Error ? error : new Error(String(error)));\n } catch (callbackError) {\n console.error('Error callback failed:', callbackError);\n }\n }\n\n return NextResponse.json(\n { error: 'Internal server error' },\n { status: 500 }\n );\n }\n };\n}\n\n/**\n * Create a generic NextJS API route handler for fetching reviews\n */\nexport function createReviewFetchHandler(config: ReviewHandlerConfig) {\n return async function reviewFetchHandler(request: NextRequest): Promise<NextResponse> {\n // Only allow GET requests\n if (request.method !== 'GET') {\n return NextResponse.json(\n { error: 'Method not allowed' },\n { status: 405, headers: { Allow: 'GET' } }\n );\n }\n\n try {\n // Parse query parameters\n const searchParams = request.nextUrl.searchParams;\n const params = {\n limit: searchParams.get('limit') ? parseInt(searchParams.get('limit')!) : undefined,\n offset: searchParams.get('offset') ? parseInt(searchParams.get('offset')!) : undefined,\n featured: searchParams.get('featured') === 'true' ? true : undefined,\n minRating: searchParams.get('minRating') ? parseInt(searchParams.get('minRating')!) : undefined,\n sortBy: searchParams.get('sortBy') as 'date' | 'rating' | 'name' | undefined,\n sortOrder: searchParams.get('sortOrder') as 'asc' | 'desc' | undefined,\n };\n\n // Call the user-provided fetch function\n let reviews: Review[];\n try {\n if (!config.onFetch) {\n return NextResponse.json(\n { error: 'Review fetching not configured' },\n { status: 500 }\n );\n }\n\n reviews = await config.onFetch(params);\n } catch (fetchError) {\n console.error('Review fetch error:', fetchError);\n \n return NextResponse.json(\n { error: 'Failed to fetch reviews. Please try again.' },\n { status: 500 }\n );\n }\n\n // Validate response from fetch function\n if (!Array.isArray(reviews)) {\n console.error('Invalid response from fetch function, expected array:', reviews);\n return NextResponse.json(\n { error: 'Internal server error' },\n { status: 500 }\n );\n }\n\n // Return successful response\n return NextResponse.json(reviews);\n\n } catch (error) {\n console.error('Unexpected error in review fetch handler:', error);\n \n return NextResponse.json(\n { error: 'Internal server error' },\n { status: 500 }\n );\n }\n };\n}\n\n/**\n * Combined handler that supports both GET (fetch) and POST (submit)\n */\nexport function createReviewHandler(config: ReviewHandlerConfig) {\n const fetchHandler = createReviewFetchHandler(config);\n const submitHandler = createReviewSubmitHandler(config);\n\n return async function combinedHandler(request: NextRequest): Promise<NextResponse> {\n if (request.method === 'GET') {\n return fetchHandler(request);\n } else if (request.method === 'POST') {\n return submitHandler(request);\n } else {\n return NextResponse.json(\n { error: 'Method not allowed' },\n { status: 405, headers: { Allow: 'GET, POST' } }\n );\n }\n };\n}\n\n/**\n * Helper function to create CORS headers for the response\n */\nexport function createCorsHeaders(allowedOrigins?: string[]): HeadersInit {\n const headers: HeadersInit = {\n 'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',\n 'Access-Control-Allow-Headers': 'Content-Type, Authorization',\n };\n\n if (allowedOrigins && allowedOrigins.length > 0) {\n headers['Access-Control-Allow-Origin'] = allowedOrigins.join(', ');\n } else {\n headers['Access-Control-Allow-Origin'] = '*';\n }\n\n return headers;\n}\n\n/**\n * Helper to handle OPTIONS requests for CORS\n */\nexport function handleOptions(allowedOrigins?: string[]): NextResponse {\n return new NextResponse(null, {\n status: 200,\n headers: createCorsHeaders(allowedOrigins)\n });\n}","import React from 'react';\nimport { StarRatingProps } from '../../types';\n\n/**\n * Star rating component for displaying and selecting ratings\n */\nexport const StarRating: React.FC<StarRatingProps> = ({\n rating,\n maxRating = 5,\n editable = false,\n onChange,\n size = 'medium',\n className = ''\n}) => {\n const handleStarClick = (starRating: number) => {\n if (editable && onChange) {\n onChange(starRating);\n }\n };\n\n const handleKeyDown = (event: React.KeyboardEvent, starRating: number) => {\n if (editable && onChange && (event.key === 'Enter' || event.key === ' ')) {\n event.preventDefault();\n onChange(starRating);\n }\n };\n\n const renderStar = (starIndex: number) => {\n const filled = starIndex <= rating;\n const starClasses = [\n 'star',\n size,\n filled ? 'filled' : 'empty',\n editable ? 'editable' : 'readonly'\n ].join(' ');\n\n return (\n <span\n key={starIndex}\n className={starClasses}\n onClick={() => handleStarClick(starIndex)}\n onKeyDown={(e) => handleKeyDown(e, starIndex)}\n role={editable ? 'button' : undefined}\n tabIndex={editable ? 0 : undefined}\n aria-label={`${starIndex} star${starIndex !== 1 ? 's' : ''}`}\n >\n {filled ? '★' : '☆'}\n </span>\n );\n };\n\n return (\n <div \n className={`star-rating ${className}`}\n role=\"img\"\n aria-label={`${rating} out of ${maxRating} stars`}\n >\n {Array.from({ length: maxRating }, (_, index) => renderStar(index + 1))}\n </div>\n );\n};\n\n// Default styles (can be overridden)\nexport const defaultStarRatingStyles = `\n .star-rating {\n display: inline-flex;\n gap: 2px;\n }\n\n .star {\n display: inline-block;\n cursor: default;\n user-select: none;\n transition: color 0.2s ease;\n }\n\n .star.editable {\n cursor: pointer;\n }\n\n .star.editable:hover {\n transform: scale(1.1);\n }\n\n .star.editable:focus {\n outline: 2px solid #3b82f6;\n outline-offset: 2px;\n border-radius: 2px;\n }\n\n .star.small {\n font-size: 1rem;\n }\n\n .star.medium {\n font-size: 1.5rem;\n }\n\n .star.large {\n font-size: 2rem;\n }\n\n .star.filled {\n color: #fbbf24;\n }\n\n .star.empty {\n color: #d1d5db;\n }\n\n .star.editable.empty:hover {\n color: #fbbf24;\n }\n`;","import { useState, useEffect, useCallback } from 'react';\nimport { Review, ReviewFetchConfig } from '../../types';\nimport { ReviewClient } from '../../client/client';\n\nexport interface TestimonialsState {\n testimonials: Review[];\n isLoading: boolean;\n error: string | null;\n retryCount: number;\n canRetry: boolean;\n lastFetchTime: number | null;\n}\n\nexport interface UseTestimonialsConfig {\n fetchConfig: ReviewFetchConfig;\n limit?: number;\n featured?: boolean;\n minRating?: number;\n autoRefresh?: boolean;\n refreshInterval?: number; // in milliseconds\n maxRetries?: number;\n}\n\nexport interface UseTestimonialsReturn {\n testimonials: Review[];\n isLoading: boolean;\n error: string | null;\n retryCount: number;\n canRetry: boolean;\n refetch: () => Promise<void>;\n handleRetry: () => Promise<void>;\n}\n\n/**\n * Generic hook for fetching and managing testimonials/reviews\n */\nexport const useTestimonials = (config: UseTestimonialsConfig): UseTestimonialsReturn => {\n const {\n fetchConfig,\n limit = 10,\n featured,\n minRating,\n autoRefresh = false,\n refreshInterval = 60000, // 1 minute\n maxRetries = 3\n } = config;\n\n const [state, setState] = useState<TestimonialsState>({\n testimonials: [],\n isLoading: false,\n error: null,\n retryCount: 0,\n canRetry: false,\n lastFetchTime: null\n });\n\n const [client] = useState(() => new ReviewClient({\n fetchConfig,\n maxRetries,\n }));\n\n /**\n * Internal fetch logic shared between initial load and retry\n */\n const performFetch = useCallback(async (isRetry = false): Promise<void> => {\n const fetchStartTime = Date.now();\n\n // Reset state for new fetch\n setState(prev => ({\n ...prev,\n isLoading: true,\n error: null,\n canRetry: false,\n lastFetchTime: fetchStartTime,\n retryCount: isRetry ? prev.retryCount + 1 : 0\n }));\n\n try {\n const fetchParams = {\n limit,\n featured,\n minRating\n };\n\n const testimonials = await client.fetchReviews(fetchParams);\n\n setState(prev => ({\n ...prev,\n testimonials,\n isLoading: false,\n error: null,\n canRetry: false,\n retryCount: 0 // Reset on successful fetch\n }));\n\n } catch (error) {\n const errorMessage = error instanceof Error \n ? error.message \n : 'Failed to load testimonials';\n\n // Determine if the error is retryable\n const isRetryableError = error instanceof Error && \n (error as any).retryable === true;\n\n const canRetryAgain = isRetryableError && state.retryCount < maxRetries;\n\n setState(prev => ({\n ...prev,\n isLoading: false,\n error: errorMessage,\n canRetry: canRetryAgain\n }));\n }\n }, [client, limit, featured, minRating, maxRetries, state.retryCount]);\n\n /**\n * Handles initial fetch and manual refetch\n */\n const refetch = useCallback(async (): Promise<void> => {\n await performFetch(false);\n }, [performFetch]);\n\n /**\n * Handles retry attempts with incremented retry count\n */\n const handleRetry = useCallback(async (): Promise<void> => {\n if (state.canRetry) {\n await performFetch(true);\n }\n }, [performFetch, state.canRetry]);\n\n /**\n * Initial fetch on mount\n */\n useEffect(() => {\n refetch();\n }, [refetch]);\n\n /**\n * Auto-refresh functionality\n */\n useEffect(() => {\n if (!autoRefresh || refreshInterval <= 0) return;\n\n const interval = setInterval(() => {\n if (!state.isLoading) {\n refetch();\n }\n }, refreshInterval);\n\n return () => clearInterval(interval);\n }, [autoRefresh, refreshInterval, state.isLoading, refetch]);\n\n return {\n testimonials: state.testimonials,\n isLoading: state.isLoading,\n error: state.error,\n retryCount: state.retryCount,\n canRetry: state.canRetry,\n refetch,\n handleRetry\n };\n};","import React from 'react';\nimport { Review, TestimonialDisplayConfig, ReviewFetchConfig } from '../../types';\nimport { useTestimonials } from '../hooks/useTestimonials';\nimport { StarRating } from './StarRating';\n\nexport interface TestimonialsSectionProps extends TestimonialDisplayConfig {\n fetchConfig: ReviewFetchConfig;\n className?: string;\n renderTestimonial?: (testimonial: Review) => React.ReactNode;\n renderLoading?: () => React.ReactNode;\n renderError?: (error: string, canRetry: boolean, onRetry: () => void) => React.ReactNode;\n renderEmpty?: () => React.ReactNode;\n}\n\n/**\n * Generic testimonials display component\n */\nexport const TestimonialsSection: React.FC<TestimonialsSectionProps> = ({\n fetchConfig,\n limit = 6,\n showRating = true,\n showDate = false,\n showAvatar = false,\n showCompany = true,\n showTitle = true,\n layout = 'grid',\n columns = 3,\n autoRefresh = false,\n refreshInterval = 60000,\n styling = {},\n className = '',\n renderTestimonial,\n renderLoading,\n renderError,\n renderEmpty\n}) => {\n const {\n testimonials,\n isLoading,\n error,\n canRetry,\n handleRetry\n } = useTestimonials({\n fetchConfig,\n limit,\n featured: true,\n autoRefresh,\n refreshInterval\n });\n\n // Loading state\n if (isLoading && testimonials.length === 0) {\n if (renderLoading) {\n return <div className={className}>{renderLoading()}</div>;\n }\n\n return (\n <div className={`testimonials-section loading ${className}`}>\n <div className=\"loading-spinner\">Loading testimonials...</div>\n </div>\n );\n }\n\n // Error state\n if (error && testimonials.length === 0) {\n if (renderError) {\n return <div className={className}>{renderError(error, canRetry, handleRetry)}</div>;\n }\n\n return (\n <div className={`testimonials-section error ${className}`}>\n <div className=\"error-message\">\n <p>{error}</p>\n {canRetry && (\n <button onClick={handleRetry} className=\"retry-button\">\n Try Again\n </button>\n )}\n </div>\n </div>\n );\n }\n\n // Empty state\n if (testimonials.length === 0) {\n if (renderEmpty) {\n return <div className={className}>{renderEmpty()}</div>;\n }\n\n return (\n <div className={`testimonials-section empty ${className}`}>\n <p>No testimonials available at this time.</p>\n </div>\n );\n }\n\n // Format date helper\n const formatDate = (dateString: string | Date) => {\n const date = new Date(dateString);\n return date.toLocaleDateString('en-US', {\n year: 'numeric',\n month: 'long',\n day: 'numeric'\n });\n };\n\n // Default testimonial renderer\n const defaultRenderTestimonial = (testimonial: Review) => (\n <div key={testimonial.id} className={`testimonial-card ${styling.cardClassName || ''}`}>\n <div className={`testimonial-header ${styling.headerClassName || ''}`}>\n <div className=\"reviewer-info\">\n {showAvatar && testimonial.reviewerAvatar && (\n <img \n src={testimonial.reviewerAvatar} \n alt={`${testimonial.reviewerName} avatar`}\n className=\"reviewer-avatar\"\n />\n )}\n <div className=\"reviewer-details\">\n <h4 className=\"reviewer-name\">{testimonial.reviewerName}</h4>\n {showTitle && testimonial.reviewerTitle && (\n <p className=\"reviewer-title\">{testimonial.reviewerTitle}</p>\n )}\n {showCompany && testimonial.reviewerCompany && (\n <p className=\"reviewer-company\">{testimonial.reviewerCompany}</p>\n )}\n </div>\n </div>\n \n {showRating && (\n <div className={`rating ${styling.ratingClassName || ''}`}>\n <StarRating rating={testimonial.rating} size=\"small\" />\n </div>\n )}\n </div>\n\n {testimonial.content && (\n <div className={`testimonial-content ${styling.contentClassName || ''}`}>\n <blockquote>\"{testimonial.content}\"</blockquote>\n </div>\n )}\n\n {showDate && (\n <div className=\"testimonial-date\">\n {formatDate(testimonial.createdAt)}\n </div>\n )}\n </div>\n );\n\n const layoutClasses = {\n grid: `testimonials-grid columns-${columns}`,\n list: 'testimonials-list',\n slider: 'testimonials-slider'\n };\n\n return (\n <div className={`testimonials-section ${layout} ${styling.containerClassName || ''} ${className}`}>\n <div className={`testimonials-container ${layoutClasses[layout]}`}>\n {testimonials.map(testimonial => \n renderTestimonial ? renderTestimonial(testimonial) : defaultRenderTestimonial(testimonial)\n )}\n </div>\n\n {/* Show loading indicator during refresh */}\n {isLoading && testimonials.length > 0 && (\n <div className=\"refresh-indicator\">\n Refreshing...\n </div>\n )}\n </div>\n );\n};\n\n// Default styles (can be overridden)\nexport const defaultTestimonialsStyles = `\n .testimonials-section {\n width: 100%;\n }\n\n .testimonials-grid {\n display: grid;\n gap: 1.5rem;\n }\n\n .testimonials-grid.columns-1 {\n grid-template-columns: 1fr;\n }\n\n .testimonials-grid.columns-2 {\n grid-template-columns: repeat(2, 1fr);\n }\n\n .testimonials-grid.columns-3 {\n grid-template-columns: repeat(3, 1fr);\n }\n\n .testimonials-grid.columns-4 {\n grid-template-columns: repeat(4, 1fr);\n }\n\n @media (max-width: 768px) {\n .testimonials-grid.columns-3,\n .testimonials-grid.columns-4 {\n grid-template-columns: 1fr;\n }\n \n .testimonials-grid.columns-2 {\n grid-template-columns: 1fr;\n }\n }\n\n @media (max-width: 1024px) and (min-width: 769px) {\n .testimonials-grid.columns-3,\n .testimonials-grid.columns-4 {\n grid-template-columns: repeat(2, 1fr);\n }\n }\n\n .testimonials-list {\n display: flex;\n flex-direction: column;\n gap: 1.5rem;\n }\n\n .testimonials-slider {\n display: flex;\n overflow-x: auto;\n gap: 1.5rem;\n padding-bottom: 1rem;\n scrollbar-width: thin;\n }\n\n .testimonials-slider::-webkit-scrollbar {\n height: 8px;\n }\n\n .testimonials-slider::-webkit-scrollbar-track {\n background: #f1f5f9;\n border-radius: 4px;\n }\n\n .testimonials-slider::-webkit-scrollbar-thumb {\n background: #cbd5e1;\n border-radius: 4px;\n }\n\n .testimonials-slider .testimonial-card {\n flex: 0 0 300px;\n }\n\n .testimonial-card {\n background: white;\n border: 1px solid #e2e8f0;\n border-radius: 8px;\n padding: 1.5rem;\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);\n transition: box-shadow 0.2s ease;\n }\n\n .testimonial-card:hover {\n box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);\n }\n\n .testimonial-header {\n display: flex;\n justify-content: space-between;\n align-items: flex-start;\n margin-bottom: 1rem;\n }\n\n .reviewer-info {\n display: flex;\n gap: 0.75rem;\n align-items: center;\n }\n\n .reviewer-avatar {\n width: 48px;\n height: 48px;\n border-radius: 50%;\n object-fit: cover;\n }\n\n .reviewer-name {\n margin: 0 0 0.25rem 0;\n font-size: 1rem;\n font-weight: 600;\n color: #1f2937;\n }\n\n .reviewer-title,\n .reviewer-company {\n margin: 0;\n font-size: 0.875rem;\n color: #6b7280;\n }\n\n .testimonial-content {\n margin-bottom: 1rem;\n }\n\n .testimonial-content blockquote {\n margin: 0;\n font-style: italic;\n color: #374151;\n line-height: 1.6;\n }\n\n .testimonial-date {\n font-size: 0.75rem;\n color: #9ca3af;\n text-align: right;\n }\n\n .loading-spinner {\n text-align: center;\n padding: 3rem;\n color: #6b7280;\n }\n\n .error-message {\n text-align: center;\n padding: 3rem;\n color: #ef4444;\n }\n\n .retry-button {\n background-color: #3b82f6;\n color: white;\n border: none;\n padding: 0.5rem 1rem;\n border-radius: 4px;\n cursor: pointer;\n margin-top: 1rem;\n }\n\n .retry-button:hover {\n background-color: #2563eb;\n }\n\n .refresh-indicator {\n text-align: center;\n padding: 1rem;\n font-size: 0.875rem;\n color: #6b7280;\n }\n\n .empty {\n text-align: center;\n padding: 3rem;\n color: #6b7280;\n }\n`;","import { useState, useCallback } from 'react';\nimport { ReviewFormData, ReviewApiResponse, ValidationErrors, ReviewFormConfig } from '../../types';\nimport { validateReviewData, sanitizeReviewData } from '../../client/validation';\nimport { ReviewClient } from '../../client/client';\nimport { useRecaptcha } from './useRecaptcha';\n\nexport interface ReviewSubmissionState {\n isSubmitting: boolean;\n isSuccess: boolean;\n error: string | null;\n retryCount: number;\n canRetry: boolean;\n lastSubmissionTime: number | null;\n}\n\nexport interface UseReviewSubmissionConfig extends ReviewFormConfig {\n endpoint?: string;\n onSubmit?: (data: ReviewFormData) => Promise<ReviewApiResponse>;\n onSuccess?: (response: ReviewApiResponse) => void;\n onError?: (error: Error) => void;\n maxRetries?: number;\n}\n\nexport interface UseReviewSubmissionReturn {\n formData: ReviewFormData;\n errors: ValidationErrors;\n state: ReviewSubmissionState;\n handleChange: (field: keyof ReviewFormData, value: any) => void;\n handleSubmit: () => Promise<void>;\n handleRetry: () => Promise<void>;\n resetForm: () => void;\n client: ReviewClient | null;\n}\n\nconst DEFAULT_FORM_DATA: ReviewFormData = {\n reviewerName: '',\n reviewerEmail: '',\n rating: 0\n};\n\nconst DEFAULT_STATE: ReviewSubmissionState = {\n isSubmitting: false,\n isSuccess: false,\n error: null,\n retryCount: 0,\n canRetry: false,\n lastSubmissionTime: null\n};\n\nconst DEFAULT_ERROR_MESSAGES = {\n SUBMISSION_FAILED: 'Failed to submit review',\n SERVER_ERROR: 'Server error. Please try again.',\n NETWORK_ERROR: 'Network error. Please check your connection.',\n RECAPTCHA_FAILED: 'Security verification failed. Please try again.',\n RECAPTCHA_UNAVAILABLE: 'Security verification is unavailable. Please refresh the page.',\n GENERAL_ERROR: 'An unexpected error occurred',\n RETRY_LIMIT_EXCEEDED: 'Maximum retry attempts reached. Please try again later.'\n};\n\n/**\n * Generic review submission hook with validation, submission, and retry logic\n */\nexport const useReviewSubmission = (config: UseReviewSubmissionConfig): UseReviewSubmissionReturn => {\n const [formData, setFormData] = useState<ReviewFormData>(DEFAULT_FORM_DATA);\n const [errors, setErrors] = useState<ValidationErrors>({});\n const [state, setState] = useState<ReviewSubmissionState>(DEFAULT_STATE);\n const [client, setClient] = useState<ReviewClient | null>(null);\n\n const maxRetries = config.maxRetries || 3;\n\n // Initialize reCAPTCHA if configured\n const recaptchaConfig = config.recaptcha \n ? { siteKey: config.recaptcha.siteKey, action: config.recaptcha.action || 'review' }\n : null;\n \n const recaptcha = recaptchaConfig ? useRecaptcha(recaptchaConfig) : null;\n\n // Initialize client when config changes\n useState(() => {\n if (config.endpoint || config.onSubmit) {\n const newClient = new ReviewClient({\n submitConfig: {\n endpoint: config.endpoint || '/api/reviews/submit',\n onSuccess: config.onSuccess,\n onError: config.onError,\n transformRequest: (data: ReviewFormData) => {\n // Add reCAPTCHA token if available\n const transformedData = { ...data };\n return transformedData;\n }\n }\n });\n setClient(newClient);\n }\n });\n\n /**\n * Handle form field changes with real-time validation\n */\n const handleChange = useCallback((field: keyof ReviewFormData, value: any) => {\n setFormData(prev => ({\n ...prev,\n [field]: value\n }));\n\n // Clear field error\n const newErrors = { ...errors };\n delete newErrors[field];\n setErrors(newErrors);\n\n // Clear success state when user makes changes\n if (state.isSuccess) {\n setState(prev => ({\n ...prev,\n isSuccess: false,\n error: null,\n canRetry: false\n }));\n }\n }, [state.isSuccess]);\n\n /**\n * Validate the entire form\n */\n const validateForm = useCallback((): boolean => {\n const customRules = config.fields ? Object.entries(config.fields).reduce((acc, [field, fieldConfig]) => {\n if (fieldConfig?.validation) {\n acc[field] = fieldConfig.validation;\n }\n return acc;\n }, {} as { [field: string]: any }) : undefined;\n\n const validationErrors = validateReviewData(formData, customRules);\n \n if (validationErrors) {\n setErrors(validationErrors);\n return false;\n }\n\n setErrors({});\n return true;\n }, [formData, config.fields]);\n\n /**\n * Internal submission logic\n */\n const performSubmission = useCallback(async (isRetry = false): Promise<void> => {\n const submissionStartTime = Date.now();\n\n setState(prev => ({\n ...prev,\n isSubmitting: true,\n isSuccess: false,\n error: null,\n canRetry: false,\n lastSubmissionTime: submissionStartTime\n }));\n\n try {\n // Get reCAPTCHA token if configured\n let recaptchaToken: string | undefined;\n if (recaptcha) {\n try {\n recaptchaToken = await recaptcha.executeRecaptcha();\n } catch (recaptchaError) {\n throw new Error(DEFAULT_ERROR_MESSAGES.RECAPTCHA_FAILED);\n }\n }\n\n // Sanitize form data\n const sanitizedData = sanitizeReviewData(formData);\n \n // Add reCAPTCHA token if available\n const submissionData = recaptchaToken \n ? { ...sanitizedData, RecaptchaToken: recaptchaToken }\n : sanitizedData;\n\n let response: ReviewApiResponse;\n\n // Use custom submit function if provided, otherwise use client\n if (config.onSubmit) {\n response = await config.onSubmit(submissionData);\n } else if (client) {\n response = await client.submitReview(submissionData);\n } else {\n throw new Error('No submission method configured');\n }\n\n // Handle successful submission\n if (response.success) {\n setState(prev => ({\n ...prev,\n isSubmitting: false,\n isSuccess: true,\n error: null,\n canRetry: false,\n retryCount: 0\n }));\n\n setErrors({});\n\n if (config.onSuccess) {\n config.onSuccess(response);\n }\n } else {\n throw new Error(response.message || DEFAULT_ERROR_MESSAGES.SUBMISSION_FAILED);\n }\n\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : DEFAULT_ERROR_MESSAGES.GENERAL_ERROR;\n \n // Determine if error is retryable\n const canRetry = state.retryCount < maxRetries && (\n errorMessage.includes('Network error') ||\n errorMessage.includes('Server error') ||\n errorMessage.includes('timeout') ||\n errorMessage.includes('reCAPTCHA')\n );\n\n setState(prev => ({\n ...prev,\n isSubmitting: false,\n isSuccess: false,\n error: errorMessage,\n canRetry,\n retryCount: isRetry ? prev.retryCount + 1 : prev.retryCount\n }));\n\n setErrors({ general: errorMessage });\n\n if (config.onError && error instanceof Error) {\n config.onError(error);\n }\n }\n }, [formData, state.retryCount, maxRetries, recaptcha, client, config]);\n\n /**\n * Handle form submission\n */\n const handleSubmit = useCallback(async (): Promise<void> => {\n // Prevent multiple submissions\n if (state.isSubmitting) return;\n\n // Validate form\n if (!validateForm()) return;\n\n // Check reCAPTCHA if configured\n if (recaptcha && !recaptcha.isLoaded) {\n setErrors({ general: DEFAULT_ERROR_MESSAGES.RECAPTCHA_UNAVAILABLE });\n return;\n }\n\n // Reset retry count\n setState(prev => ({ ...prev, retryCount: 0 }));\n\n await performSubmission(false);\n }, [state.isSubmitting, validateForm, recaptcha, performSubmission]);\n\n /**\n * Handle retry of failed submission\n */\n const handleRetry = useCallback(async (): Promise<void> => {\n if (state.isSubmitting || !state.canRetry) return;\n\n if (state.retryCount >= maxRetries) {\n setState(prev => ({\n ...prev,\n error: DEFAULT_ERROR_MESSAGES.RETRY_LIMIT_EXCEEDED,\n canRetry: false\n }));\n setErrors({ general: DEFAULT_ERROR_MESSAGES.RETRY_LIMIT_EXCEEDED });\n return;\n }\n\n await performSubmission(true);\n }, [state.isSubmitting, state.canRetry, state.retryCount, maxRetries, performSubmission]);\n\n /**\n * Reset form to initial state\n */\n const resetForm = useCallback((): void => {\n setFormData(DEFAULT_FORM_DATA);\n setErrors({});\n setState(DEFAULT_STATE);\n }, []);\n\n return {\n formData,\n errors,\n state,\n handleChange,\n handleSubmit,\n handleRetry,\n resetForm,\n client\n };\n};","import { useCallback, useEffect, useState } from 'react';\n\nexport interface UseRecaptchaConfig {\n siteKey: string;\n action?: string;\n}\n\nexport interface UseRecaptchaReturn {\n executeRecaptcha: () => Promise<string>;\n isLoaded: boolean;\n error: string | null;\n}\n\ninterface RecaptchaError extends Error {\n code?: string;\n}\n\ndeclare global {\n interface Window {\n grecaptcha: {\n ready: (callback: () => void) => void;\n execute: (siteKey: string, options: { action: string }) => Promise<string>;\n };\n }\n}\n\nconst RECAPTCHA_SCRIPT_ID = 'recaptcha-script';\nconst MAX_RETRIES = 3;\nconst RETRY_DELAY = 1000; // 1 second\n\n/**\n * Generic reCAPTCHA hook for form submissions\n */\nexport const useRecaptcha = (config: UseRecaptchaConfig): UseRecaptchaReturn => {\n const [isLoaded, setIsLoaded] = useState(false);\n const [error, setError] = useState<string | null>(null);\n\n const { siteKey, action = 'submit' } = config;\n\n useEffect(() => {\n if (!siteKey) {\n const errorMsg = 'reCAPTCHA site key is not configured';\n console.error(errorMsg);\n setError(errorMsg);\n return;\n }\n\n // Check if script is already loaded\n if (window.grecaptcha) {\n setIsLoaded(true);\n setError(null);\n return;\n }\n\n // Check if script element already exists\n if (document.getElementById(RECAPTCHA_SCRIPT_ID)) {\n return;\n }\n\n const loadRecaptchaScript = () => {\n const script = document.createElement('script');\n script.id = RECAPTCHA_SCRIPT_ID;\n script.src = `https://www.google.com/recaptcha/api.js?render=${siteKey}`;\n script.async = true;\n script.defer = true;\n\n script.onload = () => {\n if (window.grecaptcha) {\n window.grecaptcha.ready(() => {\n setIsLoaded(true);\n setError(null);\n });\n }\n };\n\n script.onerror = () => {\n const errorMsg = 'Failed to load reCAPTCHA script';\n setError(errorMsg);\n setIsLoaded(false);\n };\n\n document.head.appendChild(script);\n };\n\n loadRecaptchaScript();\n\n // Cleanup function\n return () => {\n const script = document.getElementById(RECAPTCHA_SCRIPT_ID);\n if (script) {\n document.head.removeChild(script);\n }\n };\n }, [siteKey]);\n\n const executeRecaptcha = useCallback(async (): Promise<string> => {\n if (!siteKey) {\n throw new Error('reCAPTCHA site key is not configured');\n }\n\n if (!isLoaded || !window.grecaptcha) {\n throw new Error('reCAPTCHA is not loaded');\n }\n\n const executeWithRetry = async (attempt: number = 1): Promise<string> => {\n try {\n return await window.grecaptcha.execute(siteKey, { action });\n } catch (error) {\n const recaptchaError = error as RecaptchaError;\n \n if (attempt < MAX_RETRIES) {\n // Wait before retrying\n await new Promise(resolve => setTimeout(resolve, RETRY_DELAY * attempt));\n return executeWithRetry(attempt + 1);\n }\n \n // If all retries failed, throw the error\n throw new Error(`reCAPTCHA execution failed after ${MAX_RETRIES} attempts: ${recaptchaError.message}`);\n }\n };\n\n return executeWithRetry();\n }, [siteKey, action, isLoaded]);\n\n return {\n executeRecaptcha,\n isLoaded: isLoaded && !error,\n error\n };\n};","import React from 'react';\nimport { ReviewFormData } from '../../types';\nimport { useReviewSubmission, UseReviewSubmissionConfig } from '../hooks/useReviewSubmission';\nimport { StarRating } from './StarRating';\n\nexport interface ReviewFormProps extends UseReviewSubmissionConfig {\n className?: string;\n children?: React.ReactNode;\n renderField?: (field: {\n name: keyof ReviewFormData;\n label: string;\n value: any;\n type: string;\n required: boolean;\n placeholder?: string;\n error?: string;\n onChange: (value: any) => void;\n }) => React.ReactNode;\n renderSubmitButton?: (props: {\n isSubmitting: boolean;\n isDisabled: boolean;\n onClick: () => void;\n }) => React.ReactNode;\n renderSuccess?: (message: string) => React.ReactNode;\n renderError?: (error: string, canRetry: boolean, onRetry: () => void) => React.ReactNode;\n}\n\n/**\n * Generic review form component with customizable rendering\n */\nexport const ReviewForm: React.FC<ReviewFormProps> = ({\n className = '',\n children,\n renderField,\n renderSubmitButton,\n renderSuccess,\n renderError,\n ...config\n}) => {\n const {\n formData,\n errors,\n state,\n handleChange,\n handleSubmit,\n handleRetry,\n resetForm\n } = useReviewSubmission(config);\n\n // Default field configuration\n const defaultFields = {\n reviewerName: { show: true, required: true, label: 'Your Name', type: 'text', placeholder: 'Enter your name' },\n reviewerEmail: { show: true, required: true, label: 'Email', type: 'email', placeholder: 'Enter your email' },\n reviewerTitle: { show: false, required: false, label: 'Job Title', type: 'text', placeholder: 'Enter your job title' },\n reviewerCompany: { show: false, required: false, label: 'Company', type: 'text', placeholder: 'Enter your company' },\n rating: { show: true, required: true, label: 'Rating', type: 'rating', placeholder: undefined },\n content: { show: true, required: false, label: 'Review', type: 'textarea', placeholder: 'Write your review' }\n };\n\n const fields = { ...defaultFields, ...(config.fields || {}) };\n\n const handleFieldChange = (field: keyof ReviewFormData) => (\n e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement> | any\n ) => {\n const value = e && e.target ? e.target.value : e;\n handleChange(field, value);\n };\n\n const onSubmit = (e: React.FormEvent) => {\n e.preventDefault();\n handleSubmit();\n };\n\n // Success state\n if (state.isSuccess) {\n if (renderSuccess) {\n return <div className={className}>{renderSuccess('Your review has been submitted successfully!')}</div>;\n }\n \n return (\n <div className={`success-message ${className}`}>\n <h3>Thank you for your review!</h3>\n <p>Your review has been submitted successfully and is pending approval.</p>\n <button type=\"button\" onClick={resetForm}>\n Submit another review\n </button>\n </div>\n );\n }\n\n return (\n <form className={`review-form ${className}`} onSubmit={onSubmit} noValidate>\n {/* Render form fields */}\n {Object.entries(fields).map(([fieldName, fieldConfig]) => {\n if (!fieldConfig?.show) return null;\n\n const field = fieldName as keyof ReviewFormData;\n const value = formData[field] || (field === 'rating' ? 0 : '');\n const error = errors[field];\n\n if (renderField) {\n return (\n <div key={fieldName}>\n {renderField({\n name: field,\n label: fieldConfig.label || fieldName,\n value,\n type: fieldConfig.type || 'text',\n required: fieldConfig.required || false,\n placeholder: fieldConfig.placeholder,\n error,\n onChange: (newValue) => handleChange(field, newValue)\n })}\n </div>\n );\n }\n\n // Default field rendering\n return (\n <div key={fieldName} className=\"form-field\">\n <label htmlFor={fieldName} className=\"form-label\">\n {fieldConfig.label || fieldName}\n {fieldConfig.required && <span className=\"required\">*</span>}\n </label>\n \n {fieldConfig.type === 'rating' ? (\n <div className=\"rating-field\">\n <StarRating\n rating={value as number}\n editable={!state.isSubmitting}\n onChange={(rating) => handleChange(field, rating)}\n size=\"large\"\n />\n <span className=\"rating-label\">\n {value === 0 ? 'Select a rating' : `${value} star${value !== 1 ? 's' : ''}`}\n </span>\n </div>\n ) : fieldConfig.type === 'textarea' ? (\n <textarea\n id={fieldName}\n name={fieldName}\n value={value as string}\n onChange={handleFieldChange(field)}\n placeholder={fieldConfig.placeholder}\n required={fieldConfig.required}\n className={`form-input ${error ? 'error' : ''}`}\n rows={4}\n disabled={state.isSubmitting}\n />\n ) : (\n <input\n id={fieldName}\n name={fieldName}\n type={fieldConfig.type || 'text'}\n value={value as string}\n onChange={handleFieldChange(field)}\n placeholder={fieldConfig.placeholder}\n required={fieldConfig.required}\n className={`form-input ${error ? 'error' : ''}`}\n disabled={state.isSubmitting}\n />\n )}\n \n {error && (\n <span className=\"form-error\">{error}</span>\n )}\n </div>\n );\n })}\n\n {/* General error display */}\n {state.error && (\n <div className=\"form-error general-error\">\n {renderError ? (\n renderError(state.error, state.canRetry, handleRetry)\n ) : (\n <>\n <p>{state.error}</p>\n {state.canRetry && (\n <button type=\"button\" onClick={handleRetry} disabled={state.isSubmitting}>\n Try Again\n </button>\n )}\n </>\n )}\n </div>\n )}\n\n {/* Submit button */}\n <div className=\"form-actions\">\n {renderSubmitButton ? (\n renderSubmitButton({\n isSubmitting: state.isSubmitting,\n isDisabled: state.isSubmitting || Object.keys(errors).length > 0,\n onClick: handleSubmit\n })\n ) : (\n <button\n type=\"submit\"\n disabled={state.isSubmitting}\n className={`submit-button ${state.isSubmitting ? 'submitting' : ''}`}\n >\n {state.isSubmitting ? 'Submitting Review...' : 'Submit Review'}\n </button>\n )}\n </div>\n\n {/* Additional children */}\n {children}\n </form>\n );\n};\n\n// Default styles (can be overridden)\nexport const defaultReviewFormStyles = `\n .review-form .form-field {\n margin-bottom: 1rem;\n }\n\n .review-form .form-label {\n display: block;\n margin-bottom: 0.25rem;\n font-weight: 500;\n }\n\n .review-form .required {\n color: #e53e3e;\n margin-left: 0.25rem;\n }\n\n .review-form .form-input {\n width: 100%;\n padding: 0.75rem;\n border: 1px solid #d1d5db;\n border-radius: 4px;\n font-size: 1rem;\n transition: border-color 0.2s;\n }\n\n .review-form .form-input:focus {\n outline: none;\n border-color: #3b82f6;\n box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);\n }\n\n .review-form .form-input.error {\n border-color: #e53e3e;\n }\n\n .review-form .rating-field {\n display: flex;\n align-items: center;\n gap: 1rem;\n }\n\n .review-form .rating-label {\n font-size: 0.875rem;\n color: #6b7280;\n }\n\n .review-form .form-error {\n color: #e53e3e;\n font-size: 0.875rem;\n margin-top: 0.25rem;\n display: block;\n }\n\n .review-form .general-error {\n background-color: #fee2e2;\n border: 1px solid #fecaca;\n border-radius: 4px;\n padding: 1rem;\n margin-bottom: 1rem;\n }\n\n .review-form .general-error p {\n margin: 0 0 0.5rem 0;\n }\n\n .review-form .general-error button {\n background-color: #ef4444;\n color: white;\n padding: 0.5rem 1rem;\n border: none;\n border-radius: 4px;\n cursor: pointer;\n }\n\n .review-form .submit-button {\n background-color: #3b82f6;\n color: white;\n padding: 0.75rem 1.5rem;\n border: none;\n border-radius: 4px;\n font-size: 1rem;\n cursor: pointer;\n transition: background-color 0.2s;\n width: 100%;\n }\n\n .review-form .submit-button:hover:not(:disabled) {\n background-color: #2563eb;\n }\n\n .review-form .submit-button:disabled {\n opacity: 0.6;\n cursor: not-allowed;\n }\n\n .success-message {\n text-align: center;\n padding: 2rem 1rem;\n background-color: #f0fdf4;\n border: 1px solid #bbf7d0;\n border-radius: 8px;\n }\n\n .success-message h3 {\n color: #059669;\n margin-bottom: 1rem;\n }\n\n .success-message button {\n background-color: #6b7280;\n color: white;\n padding: 0.5rem 1rem;\n border: none;\n border-radius: 4px;\n cursor: pointer;\n margin-top: 1rem;\n }\n`;"],"mappings":"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","names":["import_jsx_runtime","import_react","import_react","error","DEFAULT_ERROR_MESSAGES","import_jsx_runtime"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/client/client.ts","../src/client/validation.ts","../src/server/handler.ts","../src/react/components/StarRating.tsx","../src/react/hooks/useTestimonials.ts","../src/react/components/TestimonialsSection.tsx","../src/react/hooks/useReviewSubmission.ts","../src/react/hooks/useRecaptcha.ts","../src/react/components/ReviewForm.tsx"],"sourcesContent":["// Main entry point - exports commonly used utilities\nexport * from './types';\n\n// Re-export key client utilities\nexport { ReviewClient } from './client';\n\n// Re-export key server utilities\nexport { createReviewHandler } from './server';\n\n// Re-export key React components (most commonly used)\nexport { StarRating } from './react/components/StarRating';\nexport { TestimonialsSection } from './react/components/TestimonialsSection';\nexport { ReviewForm } from './react/components/ReviewForm';\n\n// Version info\nexport const version = '1.0.0';","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}","import { NextRequest, NextResponse } from 'next/server';\nimport { ReviewHandlerConfig, ReviewFormData, ReviewApiResponse, Review } from '../types';\n\n/**\n * Create a generic NextJS API route handler for review submission\n */\nexport function createReviewSubmitHandler(config: ReviewHandlerConfig) {\n return async function reviewSubmitHandler(request: NextRequest): Promise<NextResponse> {\n // Only allow POST requests\n if (request.method !== 'POST') {\n return NextResponse.json(\n { error: 'Method not allowed' },\n { status: 405, headers: { Allow: 'POST' } }\n );\n }\n\n try {\n // Parse request body (JSON or FormData)\n let body: ReviewFormData;\n const contentType = request.headers.get('content-type') || '';\n\n try {\n if (contentType.includes('multipart/form-data')) {\n // Parse FormData\n const formData = await request.formData();\n body = {\n reviewerName: formData.get('reviewerName') as string,\n reviewerEmail: formData.get('reviewerEmail') as string,\n rating: parseInt(formData.get('rating') as string),\n content: (formData.get('content') as string) || undefined,\n reviewerTitle: (formData.get('reviewerTitle') as string) || undefined,\n reviewerCompany: (formData.get('reviewerCompany') as string) || undefined,\n };\n\n // Include image file if present\n const imageFile = formData.get('image');\n if (imageFile && imageFile instanceof File) {\n (body as any).image = imageFile;\n }\n\n // Include token if present\n const token = formData.get('token');\n if (token) {\n (body as any).token = token as string;\n }\n } else {\n // Parse JSON\n body = await request.json();\n }\n } catch (parseError) {\n return NextResponse.json(\n { error: 'Invalid request body format' },\n { status: 400 }\n );\n }\n\n // Rate limiting (if configured)\n if (config.rateLimiter) {\n const allowed = await config.rateLimiter(request);\n if (!allowed) {\n return NextResponse.json(\n { error: 'Too many requests. Please try again later.' },\n { status: 429 }\n );\n }\n }\n\n // Validate required fields (basic validation)\n if (!body.reviewerName || typeof body.reviewerName !== 'string' || body.reviewerName.trim().length === 0) {\n return NextResponse.json(\n { error: 'Reviewer name is required' },\n { status: 400 }\n );\n }\n\n if (!body.reviewerEmail || typeof body.reviewerEmail !== 'string' || body.reviewerEmail.trim().length === 0) {\n return NextResponse.json(\n { error: 'Email is required' },\n { status: 400 }\n );\n }\n\n // Email format validation\n const emailRegex = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/;\n if (!emailRegex.test(body.reviewerEmail.trim())) {\n return NextResponse.json(\n { error: 'Please enter a valid email address' },\n { status: 400 }\n );\n }\n\n // Rating validation\n if (typeof body.rating !== 'number' || body.rating < 1 || body.rating > 5 || !Number.isInteger(body.rating)) {\n return NextResponse.json(\n { error: 'Rating must be between 1 and 5 stars' },\n { status: 400 }\n );\n }\n\n // Custom validation (if configured)\n if (config.validation) {\n for (const [field, rules] of Object.entries(config.validation)) {\n const value = (body as any)[field];\n \n for (const rule of rules) {\n let isValid = true;\n let errorMessage = rule.message || 'Validation failed';\n\n switch (rule.type) {\n case 'required':\n isValid = value != null && value !== '' && \n (typeof value !== 'string' || value.trim() !== '');\n break;\n \n case 'email':\n if (value) {\n isValid = emailRegex.test(String(value).trim());\n }\n break;\n \n case 'rating':\n if (value !== undefined) {\n isValid = typeof value === 'number' && Number.isInteger(value) && value >= 1 && value <= 5;\n }\n break;\n \n case 'minLength':\n if (value && typeof rule.value === 'number') {\n isValid = String(value).length >= rule.value;\n }\n break;\n \n case 'maxLength':\n if (value && typeof rule.value === 'number') {\n isValid = String(value).length <= rule.value;\n }\n break;\n \n case 'pattern':\n if (value && typeof rule.value === 'string') {\n const regex = new RegExp(rule.value);\n isValid = regex.test(String(value));\n }\n break;\n \n case 'custom':\n if (rule.validator) {\n const result = rule.validator(value);\n isValid = result === true;\n if (typeof result === 'string') {\n errorMessage = result;\n }\n }\n break;\n }\n\n if (!isValid) {\n return NextResponse.json(\n { error: errorMessage },\n { status: 400 }\n );\n }\n }\n }\n }\n\n // reCAPTCHA verification (if configured)\n // if (config.recaptcha && (body as any).RecaptchaToken) {\n // const recaptchaResult = await verifyRecaptcha((body as any).RecaptchaToken, config.recaptcha);\n \n // if (!recaptchaResult.success) {\n // const errorMessage = getRecaptchaErrorMessage(recaptchaResult.errorCodes || []);\n // return NextResponse.json(\n // { error: `reCAPTCHA verification failed: ${errorMessage}` },\n // { status: 400 }\n // );\n // }\n // }\n\n // Call the user-provided submit function\n let response: ReviewApiResponse;\n try {\n if (!config.onSubmit) {\n return NextResponse.json(\n { error: 'Review submission not configured' },\n { status: 500 }\n );\n }\n\n response = await config.onSubmit(body);\n } catch (submitError) {\n console.error('Review submission error:', submitError);\n \n // Call error callback if provided\n if (config.onError) {\n await config.onError(body, submitError instanceof Error ? submitError : new Error(String(submitError)));\n }\n\n return NextResponse.json(\n { error: 'Failed to submit review. Please try again.' },\n { status: 500 }\n );\n }\n\n // Validate response from submit function\n if (!response || typeof response !== 'object') {\n console.error('Invalid response from submit function:', response);\n return NextResponse.json(\n { error: 'Internal server error' },\n { status: 500 }\n );\n }\n\n // Handle unsuccessful submission\n if (!response.success) {\n const statusCode = response.message?.includes('validation') ? 400 : 500;\n return NextResponse.json(\n { \n error: response.message || 'Failed to submit review',\n data: response.data \n },\n { status: statusCode }\n );\n }\n\n // Call success callback if provided\n if (config.onSuccess) {\n try {\n await config.onSuccess(body, response);\n } catch (callbackError) {\n console.error('Success callback error:', callbackError);\n // Don't fail the request if callback fails\n }\n }\n\n // Return successful response\n return NextResponse.json({\n success: true,\n message: response.message || 'Review submitted successfully',\n reviewId: response.reviewId,\n status: response.status,\n data: response.data\n });\n\n } catch (error) {\n console.error('Unexpected error in review submit handler:', error);\n \n // Try to call error callback\n if (config.onError) {\n try {\n await config.onError({} as ReviewFormData, error instanceof Error ? error : new Error(String(error)));\n } catch (callbackError) {\n console.error('Error callback failed:', callbackError);\n }\n }\n\n return NextResponse.json(\n { error: 'Internal server error' },\n { status: 500 }\n );\n }\n };\n}\n\n/**\n * Create a generic NextJS API route handler for fetching reviews\n */\nexport function createReviewFetchHandler(config: ReviewHandlerConfig) {\n return async function reviewFetchHandler(request: NextRequest): Promise<NextResponse> {\n // Only allow GET requests\n if (request.method !== 'GET') {\n return NextResponse.json(\n { error: 'Method not allowed' },\n { status: 405, headers: { Allow: 'GET' } }\n );\n }\n\n try {\n // Parse query parameters\n const searchParams = request.nextUrl.searchParams;\n const params = {\n limit: searchParams.get('limit') ? parseInt(searchParams.get('limit')!) : undefined,\n offset: searchParams.get('offset') ? parseInt(searchParams.get('offset')!) : undefined,\n featured: searchParams.get('featured') === 'true' ? true : undefined,\n minRating: searchParams.get('minRating') ? parseInt(searchParams.get('minRating')!) : undefined,\n sortBy: searchParams.get('sortBy') as 'date' | 'rating' | 'name' | undefined,\n sortOrder: searchParams.get('sortOrder') as 'asc' | 'desc' | undefined,\n };\n\n // Call the user-provided fetch function\n let reviews: Review[];\n try {\n if (!config.onFetch) {\n return NextResponse.json(\n { error: 'Review fetching not configured' },\n { status: 500 }\n );\n }\n\n reviews = await config.onFetch(params);\n } catch (fetchError) {\n console.error('Review fetch error:', fetchError);\n \n return NextResponse.json(\n { error: 'Failed to fetch reviews. Please try again.' },\n { status: 500 }\n );\n }\n\n // Validate response from fetch function\n if (!Array.isArray(reviews)) {\n console.error('Invalid response from fetch function, expected array:', reviews);\n return NextResponse.json(\n { error: 'Internal server error' },\n { status: 500 }\n );\n }\n\n // Return successful response\n return NextResponse.json(reviews);\n\n } catch (error) {\n console.error('Unexpected error in review fetch handler:', error);\n \n return NextResponse.json(\n { error: 'Internal server error' },\n { status: 500 }\n );\n }\n };\n}\n\n/**\n * Combined handler that supports both GET (fetch) and POST (submit)\n */\nexport function createReviewHandler(config: ReviewHandlerConfig) {\n const fetchHandler = createReviewFetchHandler(config);\n const submitHandler = createReviewSubmitHandler(config);\n\n return async function combinedHandler(request: NextRequest): Promise<NextResponse> {\n if (request.method === 'GET') {\n return fetchHandler(request);\n } else if (request.method === 'POST') {\n return submitHandler(request);\n } else {\n return NextResponse.json(\n { error: 'Method not allowed' },\n { status: 405, headers: { Allow: 'GET, POST' } }\n );\n }\n };\n}\n\n/**\n * Helper function to create CORS headers for the response\n */\nexport function createCorsHeaders(allowedOrigins?: string[]): HeadersInit {\n const headers: HeadersInit = {\n 'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',\n 'Access-Control-Allow-Headers': 'Content-Type, Authorization',\n };\n\n if (allowedOrigins && allowedOrigins.length > 0) {\n headers['Access-Control-Allow-Origin'] = allowedOrigins.join(', ');\n } else {\n headers['Access-Control-Allow-Origin'] = '*';\n }\n\n return headers;\n}\n\n/**\n * Helper to handle OPTIONS requests for CORS\n */\nexport function handleOptions(allowedOrigins?: string[]): NextResponse {\n return new NextResponse(null, {\n status: 200,\n headers: createCorsHeaders(allowedOrigins)\n });\n}","import React from 'react';\nimport { StarRatingProps } from '../../types';\n\n/**\n * Star rating component for displaying and selecting ratings\n */\nexport const StarRating: React.FC<StarRatingProps> = ({\n rating,\n maxRating = 5,\n editable = false,\n onChange,\n size = 'medium',\n className = ''\n}) => {\n const handleStarClick = (starRating: number) => {\n if (editable && onChange) {\n onChange(starRating);\n }\n };\n\n const handleKeyDown = (event: React.KeyboardEvent, starRating: number) => {\n if (editable && onChange && (event.key === 'Enter' || event.key === ' ')) {\n event.preventDefault();\n onChange(starRating);\n }\n };\n\n const renderStar = (starIndex: number) => {\n const filled = starIndex <= rating;\n const starClasses = [\n 'star',\n size,\n filled ? 'filled' : 'empty',\n editable ? 'editable' : 'readonly'\n ].join(' ');\n\n return (\n <span\n key={starIndex}\n className={starClasses}\n onClick={() => handleStarClick(starIndex)}\n onKeyDown={(e) => handleKeyDown(e, starIndex)}\n role={editable ? 'button' : undefined}\n tabIndex={editable ? 0 : undefined}\n aria-label={`${starIndex} star${starIndex !== 1 ? 's' : ''}`}\n >\n {filled ? '★' : '☆'}\n </span>\n );\n };\n\n return (\n <div \n className={`star-rating ${className}`}\n role=\"img\"\n aria-label={`${rating} out of ${maxRating} stars`}\n >\n {Array.from({ length: maxRating }, (_, index) => renderStar(index + 1))}\n </div>\n );\n};\n\n// Default styles (can be overridden)\nexport const defaultStarRatingStyles = `\n .star-rating {\n display: inline-flex;\n gap: 2px;\n }\n\n .star {\n display: inline-block;\n cursor: default;\n user-select: none;\n transition: color 0.2s ease;\n }\n\n .star.editable {\n cursor: pointer;\n }\n\n .star.editable:hover {\n transform: scale(1.1);\n }\n\n .star.editable:focus {\n outline: 2px solid #3b82f6;\n outline-offset: 2px;\n border-radius: 2px;\n }\n\n .star.small {\n font-size: 1rem;\n }\n\n .star.medium {\n font-size: 1.5rem;\n }\n\n .star.large {\n font-size: 2rem;\n }\n\n .star.filled {\n color: #fbbf24;\n }\n\n .star.empty {\n color: #d1d5db;\n }\n\n .star.editable.empty:hover {\n color: #fbbf24;\n }\n`;","import { useState, useEffect, useCallback } from 'react';\nimport { Review, ReviewFetchConfig } from '../../types';\nimport { ReviewClient } from '../../client/client';\n\nexport interface TestimonialsState {\n testimonials: Review[];\n isLoading: boolean;\n error: string | null;\n retryCount: number;\n canRetry: boolean;\n lastFetchTime: number | null;\n}\n\nexport interface UseTestimonialsConfig {\n fetchConfig: ReviewFetchConfig;\n limit?: number;\n featured?: boolean;\n minRating?: number;\n autoRefresh?: boolean;\n refreshInterval?: number; // in milliseconds\n maxRetries?: number;\n}\n\nexport interface UseTestimonialsReturn {\n testimonials: Review[];\n isLoading: boolean;\n error: string | null;\n retryCount: number;\n canRetry: boolean;\n refetch: () => Promise<void>;\n handleRetry: () => Promise<void>;\n}\n\n/**\n * Generic hook for fetching and managing testimonials/reviews\n */\nexport const useTestimonials = (config: UseTestimonialsConfig): UseTestimonialsReturn => {\n const {\n fetchConfig,\n limit = 10,\n featured,\n minRating,\n autoRefresh = false,\n refreshInterval = 60000, // 1 minute\n maxRetries = 3\n } = config;\n\n const [state, setState] = useState<TestimonialsState>({\n testimonials: [],\n isLoading: false,\n error: null,\n retryCount: 0,\n canRetry: false,\n lastFetchTime: null\n });\n\n const [client] = useState(() => new ReviewClient({\n fetchConfig,\n maxRetries,\n }));\n\n /**\n * Internal fetch logic shared between initial load and retry\n */\n const performFetch = useCallback(async (isRetry = false): Promise<void> => {\n const fetchStartTime = Date.now();\n\n // Reset state for new fetch\n setState(prev => ({\n ...prev,\n isLoading: true,\n error: null,\n canRetry: false,\n lastFetchTime: fetchStartTime,\n retryCount: isRetry ? prev.retryCount + 1 : 0\n }));\n\n try {\n const fetchParams = {\n limit,\n featured,\n minRating\n };\n\n const testimonials = await client.fetchReviews(fetchParams);\n\n setState(prev => ({\n ...prev,\n testimonials,\n isLoading: false,\n error: null,\n canRetry: false,\n retryCount: 0 // Reset on successful fetch\n }));\n\n } catch (error) {\n const errorMessage = error instanceof Error \n ? error.message \n : 'Failed to load testimonials';\n\n // Determine if the error is retryable\n const isRetryableError = error instanceof Error && \n (error as any).retryable === true;\n\n const canRetryAgain = isRetryableError && state.retryCount < maxRetries;\n\n setState(prev => ({\n ...prev,\n isLoading: false,\n error: errorMessage,\n canRetry: canRetryAgain\n }));\n }\n }, [client, limit, featured, minRating, maxRetries, state.retryCount]);\n\n /**\n * Handles initial fetch and manual refetch\n */\n const refetch = useCallback(async (): Promise<void> => {\n await performFetch(false);\n }, [performFetch]);\n\n /**\n * Handles retry attempts with incremented retry count\n */\n const handleRetry = useCallback(async (): Promise<void> => {\n if (state.canRetry) {\n await performFetch(true);\n }\n }, [performFetch, state.canRetry]);\n\n /**\n * Initial fetch on mount\n */\n useEffect(() => {\n refetch();\n }, [refetch]);\n\n /**\n * Auto-refresh functionality\n */\n useEffect(() => {\n if (!autoRefresh || refreshInterval <= 0) return;\n\n const interval = setInterval(() => {\n if (!state.isLoading) {\n refetch();\n }\n }, refreshInterval);\n\n return () => clearInterval(interval);\n }, [autoRefresh, refreshInterval, state.isLoading, refetch]);\n\n return {\n testimonials: state.testimonials,\n isLoading: state.isLoading,\n error: state.error,\n retryCount: state.retryCount,\n canRetry: state.canRetry,\n refetch,\n handleRetry\n };\n};","import React from 'react';\nimport { Review, TestimonialDisplayConfig, ReviewFetchConfig } from '../../types';\nimport { useTestimonials } from '../hooks/useTestimonials';\nimport { StarRating } from './StarRating';\n\nexport interface TestimonialsSectionProps extends TestimonialDisplayConfig {\n fetchConfig: ReviewFetchConfig;\n className?: string;\n renderTestimonial?: (testimonial: Review) => React.ReactNode;\n renderLoading?: () => React.ReactNode;\n renderError?: (error: string, canRetry: boolean, onRetry: () => void) => React.ReactNode;\n renderEmpty?: () => React.ReactNode;\n}\n\n/**\n * Generic testimonials display component\n */\nexport const TestimonialsSection: React.FC<TestimonialsSectionProps> = ({\n fetchConfig,\n limit = 6,\n showRating = true,\n showDate = false,\n showAvatar = false,\n showCompany = true,\n showTitle = true,\n layout = 'grid',\n columns = 3,\n autoRefresh = false,\n refreshInterval = 60000,\n styling = {},\n className = '',\n renderTestimonial,\n renderLoading,\n renderError,\n renderEmpty\n}) => {\n const {\n testimonials,\n isLoading,\n error,\n canRetry,\n handleRetry\n } = useTestimonials({\n fetchConfig,\n limit,\n featured: true,\n autoRefresh,\n refreshInterval\n });\n\n // Loading state\n if (isLoading && testimonials.length === 0) {\n if (renderLoading) {\n return <div className={className}>{renderLoading()}</div>;\n }\n\n return (\n <div className={`testimonials-section loading ${className}`}>\n <div className=\"loading-spinner\">Loading testimonials...</div>\n </div>\n );\n }\n\n // Error state\n if (error && testimonials.length === 0) {\n if (renderError) {\n return <div className={className}>{renderError(error, canRetry, handleRetry)}</div>;\n }\n\n return (\n <div className={`testimonials-section error ${className}`}>\n <div className=\"error-message\">\n <p>{error}</p>\n {canRetry && (\n <button onClick={handleRetry} className=\"retry-button\">\n Try Again\n </button>\n )}\n </div>\n </div>\n );\n }\n\n // Empty state\n if (testimonials.length === 0) {\n if (renderEmpty) {\n return <div className={className}>{renderEmpty()}</div>;\n }\n\n return (\n <div className={`testimonials-section empty ${className}`}>\n <p>No testimonials available at this time.</p>\n </div>\n );\n }\n\n // Format date helper\n const formatDate = (dateString: string | Date) => {\n const date = new Date(dateString);\n return date.toLocaleDateString('en-US', {\n year: 'numeric',\n month: 'long',\n day: 'numeric'\n });\n };\n\n // Default testimonial renderer\n const defaultRenderTestimonial = (testimonial: Review) => (\n <div key={testimonial.id} className={`testimonial-card ${styling.cardClassName || ''}`}>\n <div className={`testimonial-header ${styling.headerClassName || ''}`}>\n <div className=\"reviewer-info\">\n {showAvatar && testimonial.reviewerAvatar && (\n <img \n src={testimonial.reviewerAvatar} \n alt={`${testimonial.reviewerName} avatar`}\n className=\"reviewer-avatar\"\n />\n )}\n <div className=\"reviewer-details\">\n <h4 className=\"reviewer-name\">{testimonial.reviewerName}</h4>\n {showTitle && testimonial.reviewerTitle && (\n <p className=\"reviewer-title\">{testimonial.reviewerTitle}</p>\n )}\n {showCompany && testimonial.reviewerCompany && (\n <p className=\"reviewer-company\">{testimonial.reviewerCompany}</p>\n )}\n </div>\n </div>\n \n {showRating && (\n <div className={`rating ${styling.ratingClassName || ''}`}>\n <StarRating rating={testimonial.rating} size=\"small\" />\n </div>\n )}\n </div>\n\n {testimonial.content && (\n <div className={`testimonial-content ${styling.contentClassName || ''}`}>\n <blockquote>\"{testimonial.content}\"</blockquote>\n </div>\n )}\n\n {showDate && (\n <div className=\"testimonial-date\">\n {formatDate(testimonial.createdAt)}\n </div>\n )}\n </div>\n );\n\n const layoutClasses = {\n grid: `testimonials-grid columns-${columns}`,\n list: 'testimonials-list',\n slider: 'testimonials-slider'\n };\n\n return (\n <div className={`testimonials-section ${layout} ${styling.containerClassName || ''} ${className}`}>\n <div className={`testimonials-container ${layoutClasses[layout]}`}>\n {testimonials.map(testimonial => \n renderTestimonial ? renderTestimonial(testimonial) : defaultRenderTestimonial(testimonial)\n )}\n </div>\n\n {/* Show loading indicator during refresh */}\n {isLoading && testimonials.length > 0 && (\n <div className=\"refresh-indicator\">\n Refreshing...\n </div>\n )}\n </div>\n );\n};\n\n// Default styles (can be overridden)\nexport const defaultTestimonialsStyles = `\n .testimonials-section {\n width: 100%;\n }\n\n .testimonials-grid {\n display: grid;\n gap: 1.5rem;\n }\n\n .testimonials-grid.columns-1 {\n grid-template-columns: 1fr;\n }\n\n .testimonials-grid.columns-2 {\n grid-template-columns: repeat(2, 1fr);\n }\n\n .testimonials-grid.columns-3 {\n grid-template-columns: repeat(3, 1fr);\n }\n\n .testimonials-grid.columns-4 {\n grid-template-columns: repeat(4, 1fr);\n }\n\n @media (max-width: 768px) {\n .testimonials-grid.columns-3,\n .testimonials-grid.columns-4 {\n grid-template-columns: 1fr;\n }\n \n .testimonials-grid.columns-2 {\n grid-template-columns: 1fr;\n }\n }\n\n @media (max-width: 1024px) and (min-width: 769px) {\n .testimonials-grid.columns-3,\n .testimonials-grid.columns-4 {\n grid-template-columns: repeat(2, 1fr);\n }\n }\n\n .testimonials-list {\n display: flex;\n flex-direction: column;\n gap: 1.5rem;\n }\n\n .testimonials-slider {\n display: flex;\n overflow-x: auto;\n gap: 1.5rem;\n padding-bottom: 1rem;\n scrollbar-width: thin;\n }\n\n .testimonials-slider::-webkit-scrollbar {\n height: 8px;\n }\n\n .testimonials-slider::-webkit-scrollbar-track {\n background: #f1f5f9;\n border-radius: 4px;\n }\n\n .testimonials-slider::-webkit-scrollbar-thumb {\n background: #cbd5e1;\n border-radius: 4px;\n }\n\n .testimonials-slider .testimonial-card {\n flex: 0 0 300px;\n }\n\n .testimonial-card {\n background: white;\n border: 1px solid #e2e8f0;\n border-radius: 8px;\n padding: 1.5rem;\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);\n transition: box-shadow 0.2s ease;\n }\n\n .testimonial-card:hover {\n box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);\n }\n\n .testimonial-header {\n display: flex;\n justify-content: space-between;\n align-items: flex-start;\n margin-bottom: 1rem;\n }\n\n .reviewer-info {\n display: flex;\n gap: 0.75rem;\n align-items: center;\n }\n\n .reviewer-avatar {\n width: 48px;\n height: 48px;\n border-radius: 50%;\n object-fit: cover;\n }\n\n .reviewer-name {\n margin: 0 0 0.25rem 0;\n font-size: 1rem;\n font-weight: 600;\n color: #1f2937;\n }\n\n .reviewer-title,\n .reviewer-company {\n margin: 0;\n font-size: 0.875rem;\n color: #6b7280;\n }\n\n .testimonial-content {\n margin-bottom: 1rem;\n }\n\n .testimonial-content blockquote {\n margin: 0;\n font-style: italic;\n color: #374151;\n line-height: 1.6;\n }\n\n .testimonial-date {\n font-size: 0.75rem;\n color: #9ca3af;\n text-align: right;\n }\n\n .loading-spinner {\n text-align: center;\n padding: 3rem;\n color: #6b7280;\n }\n\n .error-message {\n text-align: center;\n padding: 3rem;\n color: #ef4444;\n }\n\n .retry-button {\n background-color: #3b82f6;\n color: white;\n border: none;\n padding: 0.5rem 1rem;\n border-radius: 4px;\n cursor: pointer;\n margin-top: 1rem;\n }\n\n .retry-button:hover {\n background-color: #2563eb;\n }\n\n .refresh-indicator {\n text-align: center;\n padding: 1rem;\n font-size: 0.875rem;\n color: #6b7280;\n }\n\n .empty {\n text-align: center;\n padding: 3rem;\n color: #6b7280;\n }\n`;","import { useState, useCallback } from 'react';\nimport { ReviewFormData, ReviewApiResponse, ValidationErrors, ReviewFormConfig } from '../../types';\nimport { validateReviewData, sanitizeReviewData } from '../../client/validation';\nimport { ReviewClient } from '../../client/client';\nimport { useRecaptcha } from './useRecaptcha';\n\nexport interface ReviewSubmissionState {\n isSubmitting: boolean;\n isSuccess: boolean;\n error: string | null;\n retryCount: number;\n canRetry: boolean;\n lastSubmissionTime: number | null;\n}\n\nexport interface UseReviewSubmissionConfig extends ReviewFormConfig {\n endpoint?: string;\n onSubmit?: (data: ReviewFormData) => Promise<ReviewApiResponse>;\n onSuccess?: (response: ReviewApiResponse) => void;\n onError?: (error: Error) => void;\n maxRetries?: number;\n}\n\nexport interface UseReviewSubmissionReturn {\n formData: ReviewFormData;\n errors: ValidationErrors;\n state: ReviewSubmissionState;\n handleChange: (field: keyof ReviewFormData, value: any) => void;\n handleSubmit: () => Promise<void>;\n handleRetry: () => Promise<void>;\n resetForm: () => void;\n client: ReviewClient | null;\n}\n\nconst DEFAULT_FORM_DATA: ReviewFormData = {\n reviewerName: '',\n reviewerEmail: '',\n rating: 0\n};\n\nconst DEFAULT_STATE: ReviewSubmissionState = {\n isSubmitting: false,\n isSuccess: false,\n error: null,\n retryCount: 0,\n canRetry: false,\n lastSubmissionTime: null\n};\n\nconst DEFAULT_ERROR_MESSAGES = {\n SUBMISSION_FAILED: 'Failed to submit review',\n SERVER_ERROR: 'Server error. Please try again.',\n NETWORK_ERROR: 'Network error. Please check your connection.',\n RECAPTCHA_FAILED: 'Security verification failed. Please try again.',\n RECAPTCHA_UNAVAILABLE: 'Security verification is unavailable. Please refresh the page.',\n GENERAL_ERROR: 'An unexpected error occurred',\n RETRY_LIMIT_EXCEEDED: 'Maximum retry attempts reached. Please try again later.'\n};\n\n/**\n * Generic review submission hook with validation, submission, and retry logic\n */\nexport const useReviewSubmission = (config: UseReviewSubmissionConfig): UseReviewSubmissionReturn => {\n const [formData, setFormData] = useState<ReviewFormData>(DEFAULT_FORM_DATA);\n const [errors, setErrors] = useState<ValidationErrors>({});\n const [state, setState] = useState<ReviewSubmissionState>(DEFAULT_STATE);\n const [client, setClient] = useState<ReviewClient | null>(null);\n\n const maxRetries = config.maxRetries || 3;\n\n // Initialize reCAPTCHA if configured\n const recaptchaConfig = config.recaptcha \n ? { siteKey: config.recaptcha.siteKey, action: config.recaptcha.action || 'review' }\n : null;\n \n const recaptcha = recaptchaConfig ? useRecaptcha(recaptchaConfig) : null;\n\n // Initialize client when config changes\n useState(() => {\n if (config.endpoint || config.onSubmit) {\n const newClient = new ReviewClient({\n submitConfig: {\n endpoint: config.endpoint || '/api/reviews/submit',\n onSuccess: config.onSuccess,\n onError: config.onError,\n transformRequest: (data: ReviewFormData) => {\n // Add reCAPTCHA token if available\n const transformedData = { ...data };\n return transformedData;\n }\n }\n });\n setClient(newClient);\n }\n });\n\n /**\n * Handle form field changes with real-time validation\n */\n const handleChange = useCallback((field: keyof ReviewFormData, value: any) => {\n setFormData(prev => ({\n ...prev,\n [field]: value\n }));\n\n // Clear field error\n const newErrors = { ...errors };\n delete newErrors[field];\n setErrors(newErrors);\n\n // Clear success state when user makes changes\n if (state.isSuccess) {\n setState(prev => ({\n ...prev,\n isSuccess: false,\n error: null,\n canRetry: false\n }));\n }\n }, [state.isSuccess]);\n\n /**\n * Validate the entire form\n */\n const validateForm = useCallback((): boolean => {\n const customRules = config.fields ? Object.entries(config.fields).reduce((acc, [field, fieldConfig]) => {\n if (fieldConfig?.validation) {\n acc[field] = fieldConfig.validation;\n }\n return acc;\n }, {} as { [field: string]: any }) : undefined;\n\n const validationErrors = validateReviewData(formData, customRules);\n \n if (validationErrors) {\n setErrors(validationErrors);\n return false;\n }\n\n setErrors({});\n return true;\n }, [formData, config.fields]);\n\n /**\n * Internal submission logic\n */\n const performSubmission = useCallback(async (isRetry = false): Promise<void> => {\n const submissionStartTime = Date.now();\n\n setState(prev => ({\n ...prev,\n isSubmitting: true,\n isSuccess: false,\n error: null,\n canRetry: false,\n lastSubmissionTime: submissionStartTime\n }));\n\n try {\n // Get reCAPTCHA token if configured\n let recaptchaToken: string | undefined;\n if (recaptcha) {\n try {\n recaptchaToken = await recaptcha.executeRecaptcha();\n } catch (recaptchaError) {\n throw new Error(DEFAULT_ERROR_MESSAGES.RECAPTCHA_FAILED);\n }\n }\n\n // Sanitize form data\n const sanitizedData = sanitizeReviewData(formData);\n \n // Add reCAPTCHA token if available\n const submissionData = recaptchaToken \n ? { ...sanitizedData, RecaptchaToken: recaptchaToken }\n : sanitizedData;\n\n let response: ReviewApiResponse;\n\n // Use custom submit function if provided, otherwise use client\n if (config.onSubmit) {\n response = await config.onSubmit(submissionData);\n } else if (client) {\n response = await client.submitReview(submissionData);\n } else {\n throw new Error('No submission method configured');\n }\n\n // Handle successful submission\n if (response.success) {\n setState(prev => ({\n ...prev,\n isSubmitting: false,\n isSuccess: true,\n error: null,\n canRetry: false,\n retryCount: 0\n }));\n\n setErrors({});\n\n if (config.onSuccess) {\n config.onSuccess(response);\n }\n } else {\n throw new Error(response.message || DEFAULT_ERROR_MESSAGES.SUBMISSION_FAILED);\n }\n\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : DEFAULT_ERROR_MESSAGES.GENERAL_ERROR;\n \n // Determine if error is retryable\n const canRetry = state.retryCount < maxRetries && (\n errorMessage.includes('Network error') ||\n errorMessage.includes('Server error') ||\n errorMessage.includes('timeout') ||\n errorMessage.includes('reCAPTCHA')\n );\n\n setState(prev => ({\n ...prev,\n isSubmitting: false,\n isSuccess: false,\n error: errorMessage,\n canRetry,\n retryCount: isRetry ? prev.retryCount + 1 : prev.retryCount\n }));\n\n setErrors({ general: errorMessage });\n\n if (config.onError && error instanceof Error) {\n config.onError(error);\n }\n }\n }, [formData, state.retryCount, maxRetries, recaptcha, client, config]);\n\n /**\n * Handle form submission\n */\n const handleSubmit = useCallback(async (): Promise<void> => {\n // Prevent multiple submissions\n if (state.isSubmitting) return;\n\n // Validate form\n if (!validateForm()) return;\n\n // Check reCAPTCHA if configured\n if (recaptcha && !recaptcha.isLoaded) {\n setErrors({ general: DEFAULT_ERROR_MESSAGES.RECAPTCHA_UNAVAILABLE });\n return;\n }\n\n // Reset retry count\n setState(prev => ({ ...prev, retryCount: 0 }));\n\n await performSubmission(false);\n }, [state.isSubmitting, validateForm, recaptcha, performSubmission]);\n\n /**\n * Handle retry of failed submission\n */\n const handleRetry = useCallback(async (): Promise<void> => {\n if (state.isSubmitting || !state.canRetry) return;\n\n if (state.retryCount >= maxRetries) {\n setState(prev => ({\n ...prev,\n error: DEFAULT_ERROR_MESSAGES.RETRY_LIMIT_EXCEEDED,\n canRetry: false\n }));\n setErrors({ general: DEFAULT_ERROR_MESSAGES.RETRY_LIMIT_EXCEEDED });\n return;\n }\n\n await performSubmission(true);\n }, [state.isSubmitting, state.canRetry, state.retryCount, maxRetries, performSubmission]);\n\n /**\n * Reset form to initial state\n */\n const resetForm = useCallback((): void => {\n setFormData(DEFAULT_FORM_DATA);\n setErrors({});\n setState(DEFAULT_STATE);\n }, []);\n\n return {\n formData,\n errors,\n state,\n handleChange,\n handleSubmit,\n handleRetry,\n resetForm,\n client\n };\n};","import { useCallback, useEffect, useState } from 'react';\n\nexport interface UseRecaptchaConfig {\n siteKey: string;\n action?: string;\n}\n\nexport interface UseRecaptchaReturn {\n executeRecaptcha: () => Promise<string>;\n isLoaded: boolean;\n error: string | null;\n}\n\ninterface RecaptchaError extends Error {\n code?: string;\n}\n\ndeclare global {\n interface Window {\n grecaptcha: {\n ready: (callback: () => void) => void;\n execute: (siteKey: string, options: { action: string }) => Promise<string>;\n };\n }\n}\n\nconst RECAPTCHA_SCRIPT_ID = 'recaptcha-script';\nconst MAX_RETRIES = 3;\nconst RETRY_DELAY = 1000; // 1 second\n\n/**\n * Generic reCAPTCHA hook for form submissions\n */\nexport const useRecaptcha = (config: UseRecaptchaConfig): UseRecaptchaReturn => {\n const [isLoaded, setIsLoaded] = useState(false);\n const [error, setError] = useState<string | null>(null);\n\n const { siteKey, action = 'submit' } = config;\n\n useEffect(() => {\n if (!siteKey) {\n const errorMsg = 'reCAPTCHA site key is not configured';\n console.error(errorMsg);\n setError(errorMsg);\n return;\n }\n\n // Check if script is already loaded\n if (window.grecaptcha) {\n setIsLoaded(true);\n setError(null);\n return;\n }\n\n // Check if script element already exists\n if (document.getElementById(RECAPTCHA_SCRIPT_ID)) {\n return;\n }\n\n const loadRecaptchaScript = () => {\n const script = document.createElement('script');\n script.id = RECAPTCHA_SCRIPT_ID;\n script.src = `https://www.google.com/recaptcha/api.js?render=${siteKey}`;\n script.async = true;\n script.defer = true;\n\n script.onload = () => {\n if (window.grecaptcha) {\n window.grecaptcha.ready(() => {\n setIsLoaded(true);\n setError(null);\n });\n }\n };\n\n script.onerror = () => {\n const errorMsg = 'Failed to load reCAPTCHA script';\n setError(errorMsg);\n setIsLoaded(false);\n };\n\n document.head.appendChild(script);\n };\n\n loadRecaptchaScript();\n\n // Cleanup function\n return () => {\n const script = document.getElementById(RECAPTCHA_SCRIPT_ID);\n if (script) {\n document.head.removeChild(script);\n }\n };\n }, [siteKey]);\n\n const executeRecaptcha = useCallback(async (): Promise<string> => {\n if (!siteKey) {\n throw new Error('reCAPTCHA site key is not configured');\n }\n\n if (!isLoaded || !window.grecaptcha) {\n throw new Error('reCAPTCHA is not loaded');\n }\n\n const executeWithRetry = async (attempt: number = 1): Promise<string> => {\n try {\n return await window.grecaptcha.execute(siteKey, { action });\n } catch (error) {\n const recaptchaError = error as RecaptchaError;\n \n if (attempt < MAX_RETRIES) {\n // Wait before retrying\n await new Promise(resolve => setTimeout(resolve, RETRY_DELAY * attempt));\n return executeWithRetry(attempt + 1);\n }\n \n // If all retries failed, throw the error\n throw new Error(`reCAPTCHA execution failed after ${MAX_RETRIES} attempts: ${recaptchaError.message}`);\n }\n };\n\n return executeWithRetry();\n }, [siteKey, action, isLoaded]);\n\n return {\n executeRecaptcha,\n isLoaded: isLoaded && !error,\n error\n };\n};","import React from 'react';\nimport { ReviewFormData } from '../../types';\nimport { useReviewSubmission, UseReviewSubmissionConfig } from '../hooks/useReviewSubmission';\nimport { StarRating } from './StarRating';\n\nexport interface ReviewFormProps extends UseReviewSubmissionConfig {\n className?: string;\n children?: React.ReactNode;\n renderField?: (field: {\n name: keyof ReviewFormData;\n label: string;\n value: any;\n type: string;\n required: boolean;\n placeholder?: string;\n error?: string;\n onChange: (value: any) => void;\n }) => React.ReactNode;\n renderSubmitButton?: (props: {\n isSubmitting: boolean;\n isDisabled: boolean;\n onClick: () => void;\n }) => React.ReactNode;\n renderSuccess?: (message: string) => React.ReactNode;\n renderError?: (error: string, canRetry: boolean, onRetry: () => void) => React.ReactNode;\n}\n\n/**\n * Generic review form component with customizable rendering\n */\nexport const ReviewForm: React.FC<ReviewFormProps> = ({\n className = '',\n children,\n renderField,\n renderSubmitButton,\n renderSuccess,\n renderError,\n ...config\n}) => {\n const {\n formData,\n errors,\n state,\n handleChange,\n handleSubmit,\n handleRetry,\n resetForm\n } = useReviewSubmission(config);\n\n // Default field configuration\n const defaultFields = {\n reviewerName: { show: true, required: true, label: 'Your Name', type: 'text', placeholder: 'Enter your name' },\n reviewerEmail: { show: true, required: true, label: 'Email', type: 'email', placeholder: 'Enter your email' },\n reviewerTitle: { show: false, required: false, label: 'Job Title', type: 'text', placeholder: 'Enter your job title' },\n reviewerCompany: { show: false, required: false, label: 'Company', type: 'text', placeholder: 'Enter your company' },\n rating: { show: true, required: true, label: 'Rating', type: 'rating', placeholder: undefined },\n content: { show: true, required: false, label: 'Review', type: 'textarea', placeholder: 'Write your review' }\n };\n\n const fields = { ...defaultFields, ...(config.fields || {}) };\n\n const handleFieldChange = (field: keyof ReviewFormData) => (\n e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement> | any\n ) => {\n const value = e && e.target ? e.target.value : e;\n handleChange(field, value);\n };\n\n const onSubmit = (e: React.FormEvent) => {\n e.preventDefault();\n handleSubmit();\n };\n\n // Success state\n if (state.isSuccess) {\n if (renderSuccess) {\n return <div className={className}>{renderSuccess('Your review has been submitted successfully!')}</div>;\n }\n \n return (\n <div className={`success-message ${className}`}>\n <h3>Thank you for your review!</h3>\n <p>Your review has been submitted successfully and is pending approval.</p>\n <button type=\"button\" onClick={resetForm}>\n Submit another review\n </button>\n </div>\n );\n }\n\n return (\n <form className={`review-form ${className}`} onSubmit={onSubmit} noValidate>\n {/* Render form fields */}\n {Object.entries(fields).map(([fieldName, fieldConfig]) => {\n if (!fieldConfig?.show) return null;\n\n const field = fieldName as keyof ReviewFormData;\n const value = formData[field] || (field === 'rating' ? 0 : '');\n const error = errors[field];\n\n if (renderField) {\n return (\n <div key={fieldName}>\n {renderField({\n name: field,\n label: fieldConfig.label || fieldName,\n value,\n type: fieldConfig.type || 'text',\n required: fieldConfig.required || false,\n placeholder: fieldConfig.placeholder,\n error,\n onChange: (newValue) => handleChange(field, newValue)\n })}\n </div>\n );\n }\n\n // Default field rendering\n return (\n <div key={fieldName} className=\"form-field\">\n <label htmlFor={fieldName} className=\"form-label\">\n {fieldConfig.label || fieldName}\n {fieldConfig.required && <span className=\"required\">*</span>}\n </label>\n \n {fieldConfig.type === 'rating' ? (\n <div className=\"rating-field\">\n <StarRating\n rating={value as number}\n editable={!state.isSubmitting}\n onChange={(rating) => handleChange(field, rating)}\n size=\"large\"\n />\n <span className=\"rating-label\">\n {value === 0 ? 'Select a rating' : `${value} star${value !== 1 ? 's' : ''}`}\n </span>\n </div>\n ) : fieldConfig.type === 'textarea' ? (\n <textarea\n id={fieldName}\n name={fieldName}\n value={value as string}\n onChange={handleFieldChange(field)}\n placeholder={fieldConfig.placeholder}\n required={fieldConfig.required}\n className={`form-input ${error ? 'error' : ''}`}\n rows={4}\n disabled={state.isSubmitting}\n />\n ) : (\n <input\n id={fieldName}\n name={fieldName}\n type={fieldConfig.type || 'text'}\n value={value as string}\n onChange={handleFieldChange(field)}\n placeholder={fieldConfig.placeholder}\n required={fieldConfig.required}\n className={`form-input ${error ? 'error' : ''}`}\n disabled={state.isSubmitting}\n />\n )}\n \n {error && (\n <span className=\"form-error\">{error}</span>\n )}\n </div>\n );\n })}\n\n {/* General error display */}\n {state.error && (\n <div className=\"form-error general-error\">\n {renderError ? (\n renderError(state.error, state.canRetry, handleRetry)\n ) : (\n <>\n <p>{state.error}</p>\n {state.canRetry && (\n <button type=\"button\" onClick={handleRetry} disabled={state.isSubmitting}>\n Try Again\n </button>\n )}\n </>\n )}\n </div>\n )}\n\n {/* Submit button */}\n <div className=\"form-actions\">\n {renderSubmitButton ? (\n renderSubmitButton({\n isSubmitting: state.isSubmitting,\n isDisabled: state.isSubmitting || Object.keys(errors).length > 0,\n onClick: handleSubmit\n })\n ) : (\n <button\n type=\"submit\"\n disabled={state.isSubmitting}\n className={`submit-button ${state.isSubmitting ? 'submitting' : ''}`}\n >\n {state.isSubmitting ? 'Submitting Review...' : 'Submit Review'}\n </button>\n )}\n </div>\n\n {/* Additional children */}\n {children}\n </form>\n );\n};\n\n// Default styles (can be overridden)\nexport const defaultReviewFormStyles = `\n .review-form .form-field {\n margin-bottom: 1rem;\n }\n\n .review-form .form-label {\n display: block;\n margin-bottom: 0.25rem;\n font-weight: 500;\n }\n\n .review-form .required {\n color: #e53e3e;\n margin-left: 0.25rem;\n }\n\n .review-form .form-input {\n width: 100%;\n padding: 0.75rem;\n border: 1px solid #d1d5db;\n border-radius: 4px;\n font-size: 1rem;\n transition: border-color 0.2s;\n }\n\n .review-form .form-input:focus {\n outline: none;\n border-color: #3b82f6;\n box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);\n }\n\n .review-form .form-input.error {\n border-color: #e53e3e;\n }\n\n .review-form .rating-field {\n display: flex;\n align-items: center;\n gap: 1rem;\n }\n\n .review-form .rating-label {\n font-size: 0.875rem;\n color: #6b7280;\n }\n\n .review-form .form-error {\n color: #e53e3e;\n font-size: 0.875rem;\n margin-top: 0.25rem;\n display: block;\n }\n\n .review-form .general-error {\n background-color: #fee2e2;\n border: 1px solid #fecaca;\n border-radius: 4px;\n padding: 1rem;\n margin-bottom: 1rem;\n }\n\n .review-form .general-error p {\n margin: 0 0 0.5rem 0;\n }\n\n .review-form .general-error button {\n background-color: #ef4444;\n color: white;\n padding: 0.5rem 1rem;\n border: none;\n border-radius: 4px;\n cursor: pointer;\n }\n\n .review-form .submit-button {\n background-color: #3b82f6;\n color: white;\n padding: 0.75rem 1.5rem;\n border: none;\n border-radius: 4px;\n font-size: 1rem;\n cursor: pointer;\n transition: background-color 0.2s;\n width: 100%;\n }\n\n .review-form .submit-button:hover:not(:disabled) {\n background-color: #2563eb;\n }\n\n .review-form .submit-button:disabled {\n opacity: 0.6;\n cursor: not-allowed;\n }\n\n .success-message {\n text-align: center;\n padding: 2rem 1rem;\n background-color: #f0fdf4;\n border: 1px solid #bbf7d0;\n border-radius: 8px;\n }\n\n .success-message h3 {\n color: #059669;\n margin-bottom: 1rem;\n }\n\n .success-message button {\n background-color: #6b7280;\n color: white;\n padding: 0.5rem 1rem;\n border: none;\n border-radius: 4px;\n cursor: pointer;\n margin-top: 1rem;\n }\n`;"],"mappings":"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","names":["import_jsx_runtime","import_react","import_react","error","DEFAULT_ERROR_MESSAGES","import_jsx_runtime"]}
|
package/dist/index.mjs
CHANGED
|
@@ -359,11 +359,32 @@ function createReviewSubmitHandler(config) {
|
|
|
359
359
|
}
|
|
360
360
|
try {
|
|
361
361
|
let body;
|
|
362
|
+
const contentType = request.headers.get("content-type") || "";
|
|
362
363
|
try {
|
|
363
|
-
|
|
364
|
+
if (contentType.includes("multipart/form-data")) {
|
|
365
|
+
const formData = await request.formData();
|
|
366
|
+
body = {
|
|
367
|
+
reviewerName: formData.get("reviewerName"),
|
|
368
|
+
reviewerEmail: formData.get("reviewerEmail"),
|
|
369
|
+
rating: parseInt(formData.get("rating")),
|
|
370
|
+
content: formData.get("content") || void 0,
|
|
371
|
+
reviewerTitle: formData.get("reviewerTitle") || void 0,
|
|
372
|
+
reviewerCompany: formData.get("reviewerCompany") || void 0
|
|
373
|
+
};
|
|
374
|
+
const imageFile = formData.get("image");
|
|
375
|
+
if (imageFile && imageFile instanceof File) {
|
|
376
|
+
body.image = imageFile;
|
|
377
|
+
}
|
|
378
|
+
const token = formData.get("token");
|
|
379
|
+
if (token) {
|
|
380
|
+
body.token = token;
|
|
381
|
+
}
|
|
382
|
+
} else {
|
|
383
|
+
body = await request.json();
|
|
384
|
+
}
|
|
364
385
|
} catch (parseError) {
|
|
365
386
|
return NextResponse.json(
|
|
366
|
-
{ error: "Invalid
|
|
387
|
+
{ error: "Invalid request body format" },
|
|
367
388
|
{ status: 400 }
|
|
368
389
|
);
|
|
369
390
|
}
|