@embedreach/components 0.3.32 → 0.3.33
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/chunks/index.js +3342 -2296
- package/dist/chunks/sandbox-loading-screen.js +726 -399
- package/dist/index.d.ts +13 -4
- package/dist/index.umd.js +11 -11
- package/dist/styles.css +1 -1
- package/package.json +1 -1
|
@@ -20663,27 +20663,22 @@ const getPlatformConfigKey = (platformId) => {
|
|
|
20663
20663
|
const defaultPlatformConfigs = {
|
|
20664
20664
|
default: {
|
|
20665
20665
|
id: "default",
|
|
20666
|
-
name: "
|
|
20666
|
+
name: "ACME SaaS",
|
|
20667
20667
|
brandColors: ["#2563eb", "#059669", "#dc2626", "#9333ea"],
|
|
20668
20668
|
locationNames: ["Manhattan", "Brooklyn", "Queens", "Bronx"],
|
|
20669
20669
|
locationAreas: ["New York, NY", "Brooklyn, NY", "Queens, NY", "Bronx, NY"]
|
|
20670
20670
|
},
|
|
20671
20671
|
goose: {
|
|
20672
20672
|
id: "goose",
|
|
20673
|
-
name: "
|
|
20673
|
+
name: "Paws Pet Hotel",
|
|
20674
20674
|
brandColors: ["#f59e0b", "#dc2626", "#059669", "#7c3aed"],
|
|
20675
20675
|
locationNames: [
|
|
20676
|
-
"
|
|
20677
|
-
"
|
|
20678
|
-
"
|
|
20679
|
-
"
|
|
20676
|
+
"Paws Pet Hotel Manhattan",
|
|
20677
|
+
"Paws Pet Hotel Brooklyn",
|
|
20678
|
+
"Paws Pet Hotel Queens",
|
|
20679
|
+
"Paws Pet Hotel Bronx"
|
|
20680
20680
|
],
|
|
20681
|
-
locationAreas: [
|
|
20682
|
-
"San Francisco, CA",
|
|
20683
|
-
"Oakland, CA",
|
|
20684
|
-
"Berkeley, CA",
|
|
20685
|
-
"Palo Alto, CA"
|
|
20686
|
-
]
|
|
20681
|
+
locationAreas: ["Manhattan, NY", "Brooklyn, NY", "Queens, NY", "Bronx, NY"]
|
|
20687
20682
|
},
|
|
20688
20683
|
renterra: {
|
|
20689
20684
|
id: "renterra",
|
|
@@ -20710,7 +20705,6 @@ const getPlatformConfig = (platformId) => {
|
|
|
20710
20705
|
return defaultPlatformConfigs[configKey];
|
|
20711
20706
|
};
|
|
20712
20707
|
const createGbpConnectionFactory = (platformConfig, businessId, status = "connected") => {
|
|
20713
|
-
const accountName = `${platformConfig.name} GBP Account`;
|
|
20714
20708
|
const locations = Array.from(
|
|
20715
20709
|
{ length: Math.min(platformConfig.locationNames.length, 4) },
|
|
20716
20710
|
(_2, index) => ({
|
|
@@ -20734,12 +20728,15 @@ const createGbpConnectionFactory = (platformConfig, businessId, status = "connec
|
|
|
20734
20728
|
platform: "gbp",
|
|
20735
20729
|
selectedAccount: {
|
|
20736
20730
|
name: "accounts/123456789",
|
|
20737
|
-
accountName,
|
|
20731
|
+
accountName: platformConfig.name,
|
|
20738
20732
|
permissionLevel: "OWNER_LEVEL",
|
|
20739
20733
|
verificationState: "VERIFIED",
|
|
20740
20734
|
locationCount: locations.length
|
|
20741
20735
|
},
|
|
20742
|
-
selectedLocations: locations
|
|
20736
|
+
selectedLocations: locations.map((location2) => ({
|
|
20737
|
+
...location2,
|
|
20738
|
+
locationState: location2.locationState
|
|
20739
|
+
}))
|
|
20743
20740
|
},
|
|
20744
20741
|
created_at: f.date.past().toISOString(),
|
|
20745
20742
|
updated_at: f.date.recent().toISOString()
|
|
@@ -20760,7 +20757,7 @@ const createPartnerLocationsFactory = (platformConfig, businessId, platformId, c
|
|
|
20760
20757
|
phone: f.phone.number(),
|
|
20761
20758
|
email: f.internet.email(),
|
|
20762
20759
|
website: `https://${platformConfig.name.toLowerCase().replace(/\s+/g, "-")}.com`,
|
|
20763
|
-
googleBusinessProfilePlaceId:
|
|
20760
|
+
googleBusinessProfilePlaceId: `accounts/123456789/locations/${2e3 + index}`,
|
|
20764
20761
|
businessId,
|
|
20765
20762
|
platformId,
|
|
20766
20763
|
createdAt: f.date.past(),
|
|
@@ -20826,12 +20823,11 @@ const createChannelDataFactory = (platformConfig) => {
|
|
|
20826
20823
|
];
|
|
20827
20824
|
return { channelSenders, channelAccounts };
|
|
20828
20825
|
};
|
|
20829
|
-
const createReputationConfigFactory = (platformConfig, businessId, partnerLocations
|
|
20826
|
+
const createReputationConfigFactory = (platformConfig, businessId, partnerLocations) => {
|
|
20830
20827
|
const locationMappings = {};
|
|
20831
|
-
|
|
20832
|
-
|
|
20833
|
-
|
|
20834
|
-
}
|
|
20828
|
+
partnerLocations.forEach((location2) => {
|
|
20829
|
+
locationMappings[location2.externalId] = location2.googleBusinessProfilePlaceId || null;
|
|
20830
|
+
});
|
|
20835
20831
|
return {
|
|
20836
20832
|
id: f.string.uuid(),
|
|
20837
20833
|
businessId,
|
|
@@ -20860,12 +20856,10 @@ const createSandboxReputationData = (platformId, businessId) => {
|
|
|
20860
20856
|
);
|
|
20861
20857
|
const { channelSenders, channelAccounts } = createChannelDataFactory(platformConfig);
|
|
20862
20858
|
const smsApplication = createSmsApplicationFactory();
|
|
20863
|
-
const gbpLocations = gbpConnection?.metadata?.selectedLocations || [];
|
|
20864
20859
|
const reputationConfig = createReputationConfigFactory(
|
|
20865
20860
|
platformConfig,
|
|
20866
20861
|
businessId,
|
|
20867
|
-
partnerLocations
|
|
20868
|
-
gbpLocations
|
|
20862
|
+
partnerLocations
|
|
20869
20863
|
);
|
|
20870
20864
|
return {
|
|
20871
20865
|
gbpConnection,
|
|
@@ -20879,8 +20873,8 @@ const createSandboxReputationData = (platformId, businessId) => {
|
|
|
20879
20873
|
f.seed(42);
|
|
20880
20874
|
const businessConfigs = {
|
|
20881
20875
|
default: {
|
|
20882
|
-
name: "
|
|
20883
|
-
website: "https://
|
|
20876
|
+
name: "ACME SaaS",
|
|
20877
|
+
website: "https://acme-saas.com",
|
|
20884
20878
|
tagline: "Your sandbox environment for testing",
|
|
20885
20879
|
locations: [
|
|
20886
20880
|
{
|
|
@@ -20893,7 +20887,7 @@ const businessConfigs = {
|
|
|
20893
20887
|
}
|
|
20894
20888
|
],
|
|
20895
20889
|
branding: {
|
|
20896
|
-
brandName: "
|
|
20890
|
+
brandName: "ACME SaaS",
|
|
20897
20891
|
primaryColor: "#2563eb",
|
|
20898
20892
|
secondaryColors: ["#059669", "#dc2626", "#9333ea"],
|
|
20899
20893
|
brandTagline: "Your sandbox environment for testing",
|
|
@@ -20907,7 +20901,8 @@ const businessConfigs = {
|
|
|
20907
20901
|
accent: ["#059669", "#dc2626", "#9333ea"],
|
|
20908
20902
|
background: ["#ffffff", "#f8fafc", "#f1f5f9"],
|
|
20909
20903
|
text: ["#1e293b", "#475569", "#64748b"]
|
|
20910
|
-
}
|
|
20904
|
+
},
|
|
20905
|
+
logoUrl: "https://media.licdn.com/dms/image/v2/C4D0BAQEqer-Bku3Feg/company-logo_200_200/company-logo_200_200/0/1631339163505?e=2147483647&v=beta&t=90F79drLbd8Tiz6oOGqrEDPvC7WbP4Dyp0qGTRToPIo"
|
|
20911
20906
|
}
|
|
20912
20907
|
},
|
|
20913
20908
|
goose: {
|
|
@@ -20939,7 +20934,8 @@ const businessConfigs = {
|
|
|
20939
20934
|
accent: ["#dc2626", "#059669", "#7c3aed"],
|
|
20940
20935
|
background: ["#fef3c7", "#fef2f2", "#f0fdf4"],
|
|
20941
20936
|
text: ["#92400e", "#991b1b", "#166534"]
|
|
20942
|
-
}
|
|
20937
|
+
},
|
|
20938
|
+
logoUrl: "https://static.vecteezy.com/system/resources/previews/009/651/674/non_2x/cute-dog-logo-free-vector.jpg"
|
|
20943
20939
|
}
|
|
20944
20940
|
},
|
|
20945
20941
|
renterra: {
|
|
@@ -20971,7 +20967,8 @@ const businessConfigs = {
|
|
|
20971
20967
|
accent: ["#059669", "#dc2626", "#7c2d12"],
|
|
20972
20968
|
background: ["#dbeafe", "#f0fdf4", "#fef2f2"],
|
|
20973
20969
|
text: ["#1e3a8a", "#166534", "#991b1b"]
|
|
20974
|
-
}
|
|
20970
|
+
},
|
|
20971
|
+
logoUrl: "https://static.vecteezy.com/system/resources/previews/043/794/103/non_2x/heavy-equipment-rental-and-service-logo-design-vector.jpg"
|
|
20975
20972
|
}
|
|
20976
20973
|
}
|
|
20977
20974
|
};
|
|
@@ -21434,11 +21431,7 @@ const createWebPresenceContentFactory = (platformConfig, businessConfig) => {
|
|
|
21434
21431
|
const createSandboxMeasureAndAcquireData = (platformId, businessId) => {
|
|
21435
21432
|
const platformConfigKey = getPlatformConfigKey(platformId);
|
|
21436
21433
|
const platformConfig = {
|
|
21437
|
-
id: platformConfigKey
|
|
21438
|
-
name: platformConfigKey === "goose" ? "ACME Pet Spa" : platformConfigKey === "renterra" ? "Tomers Rentals" : "Default",
|
|
21439
|
-
brandColors: [],
|
|
21440
|
-
locationNames: [],
|
|
21441
|
-
locationAreas: []
|
|
21434
|
+
id: platformConfigKey
|
|
21442
21435
|
};
|
|
21443
21436
|
const businessConfig = getBusinessConfig(platformId);
|
|
21444
21437
|
const businessesMe = createBusinessDataFactory(
|
|
@@ -21705,7 +21698,7 @@ const native = {
|
|
|
21705
21698
|
randomUUID
|
|
21706
21699
|
};
|
|
21707
21700
|
function v4(options, buf, offset) {
|
|
21708
|
-
if (native.randomUUID &&
|
|
21701
|
+
if (native.randomUUID && true && !options) {
|
|
21709
21702
|
return native.randomUUID();
|
|
21710
21703
|
}
|
|
21711
21704
|
options = options || {};
|
|
@@ -22582,9 +22575,18 @@ const generateReputationResponsesData = ({
|
|
|
22582
22575
|
}
|
|
22583
22576
|
if (searchTerm && searchTerm.length >= 3) {
|
|
22584
22577
|
const searchLower = searchTerm.toLowerCase();
|
|
22585
|
-
filteredResponses = filteredResponses.filter(
|
|
22586
|
-
|
|
22587
|
-
|
|
22578
|
+
filteredResponses = filteredResponses.filter((response) => {
|
|
22579
|
+
if (response.userName.toLowerCase().includes(searchLower)) {
|
|
22580
|
+
return true;
|
|
22581
|
+
}
|
|
22582
|
+
if (response.content.toLowerCase().includes(searchLower)) {
|
|
22583
|
+
return true;
|
|
22584
|
+
}
|
|
22585
|
+
if (response.replies && "reply_comment" in response.replies && typeof response.replies.reply_comment === "string" && response.replies.reply_comment.toLowerCase().includes(searchLower)) {
|
|
22586
|
+
return true;
|
|
22587
|
+
}
|
|
22588
|
+
return false;
|
|
22589
|
+
});
|
|
22588
22590
|
}
|
|
22589
22591
|
filteredResponses.sort(
|
|
22590
22592
|
(a2, b2) => new Date(b2.createdAt).getTime() - new Date(a2.createdAt).getTime()
|
|
@@ -22641,41 +22643,79 @@ function createGoogleReview(platformConfig, partnerLocations, startDate, endDate
|
|
|
22641
22643
|
]);
|
|
22642
22644
|
const reviewContent = generateReviewContent(platformConfig, rating);
|
|
22643
22645
|
const location2 = partnerLocations.length > 0 ? f.helpers.arrayElement(partnerLocations) : null;
|
|
22646
|
+
const hasReply = f.helpers.weightedArrayElement([
|
|
22647
|
+
{ weight: 30, value: true },
|
|
22648
|
+
// 30% have business replies
|
|
22649
|
+
{ weight: 70, value: false }
|
|
22650
|
+
]);
|
|
22651
|
+
const replyData = hasReply ? {
|
|
22652
|
+
source: "google_business_profile",
|
|
22653
|
+
reply_comment: generateReplyContent(platformConfig, rating),
|
|
22654
|
+
reply_updated_at: f.date.between({
|
|
22655
|
+
from: new Date(
|
|
22656
|
+
f.date.between({ from: startDate, to: endDate }).getTime() + 24 * 60 * 60 * 1e3
|
|
22657
|
+
),
|
|
22658
|
+
// At least 1 day after review
|
|
22659
|
+
to: endDate
|
|
22660
|
+
}).toISOString()
|
|
22661
|
+
} : void 0;
|
|
22662
|
+
const isLinked = f.helpers.weightedArrayElement([
|
|
22663
|
+
{ weight: 30, value: true },
|
|
22664
|
+
// 30% are linked
|
|
22665
|
+
{ weight: 70, value: false }
|
|
22666
|
+
// 70% are not linked
|
|
22667
|
+
]);
|
|
22668
|
+
const userName = isLinked ? f.person.fullName() : "Anonymous";
|
|
22644
22669
|
return {
|
|
22645
22670
|
id: f.string.uuid(),
|
|
22646
22671
|
source: "google",
|
|
22672
|
+
reviewSource: "google_business_profile",
|
|
22647
22673
|
rating,
|
|
22648
|
-
userName
|
|
22649
|
-
userEmail:
|
|
22650
|
-
// In real Google reviews, email might not be available
|
|
22674
|
+
userName,
|
|
22675
|
+
// userEmail: faker.internet.email(), // In real Google reviews, email might not be available
|
|
22651
22676
|
content: reviewContent,
|
|
22652
22677
|
createdAt: f.date.between({ from: startDate, to: endDate }).toISOString(),
|
|
22653
|
-
hasReply
|
|
22654
|
-
{ weight: 30, value: true },
|
|
22655
|
-
// 30% have business replies
|
|
22656
|
-
{ weight: 70, value: false }
|
|
22657
|
-
]),
|
|
22678
|
+
hasReply,
|
|
22658
22679
|
isResponded: false,
|
|
22659
22680
|
// Not applicable for Google reviews
|
|
22660
|
-
|
|
22661
|
-
|
|
22681
|
+
locationId: location2?.externalId || void 0,
|
|
22682
|
+
...replyData ? { replies: replyData } : {},
|
|
22683
|
+
partnerUserId: isLinked ? f.string.uuid() : void 0,
|
|
22684
|
+
reviewExternalId: f.string.alphanumeric(21)
|
|
22662
22685
|
};
|
|
22663
22686
|
}
|
|
22664
22687
|
function createInternalFeedback(platformConfig, partnerLocations, startDate, endDate) {
|
|
22665
22688
|
const rating = f.helpers.weightedArrayElement([
|
|
22666
|
-
{ weight:
|
|
22667
|
-
//
|
|
22668
|
-
{ weight:
|
|
22669
|
-
//
|
|
22689
|
+
{ weight: 15, value: 10 },
|
|
22690
|
+
// 15% - 10 rating (promoters)
|
|
22691
|
+
{ weight: 25, value: 9 },
|
|
22692
|
+
// 25% - 9 rating (promoters)
|
|
22693
|
+
{ weight: 15, value: 8 },
|
|
22694
|
+
// 15% - 8 rating (passives)
|
|
22695
|
+
{ weight: 10, value: 7 },
|
|
22696
|
+
// 10% - 7 rating (passives)
|
|
22697
|
+
{ weight: 8, value: 6 },
|
|
22698
|
+
// 8% - 6 rating (detractors)
|
|
22699
|
+
{ weight: 7, value: 5 },
|
|
22700
|
+
// 7% - 5 rating (detractors)
|
|
22701
|
+
{ weight: 6, value: 4 },
|
|
22702
|
+
// 6% - 4 rating (detractors)
|
|
22703
|
+
{ weight: 5, value: 3 },
|
|
22704
|
+
// 5% - 3 rating (detractors)
|
|
22705
|
+
{ weight: 4, value: 2 },
|
|
22706
|
+
// 4% - 2 rating (detractors)
|
|
22707
|
+
{ weight: 5, value: 1 }
|
|
22708
|
+
// 5% - 1 rating (detractors)
|
|
22670
22709
|
]);
|
|
22671
22710
|
const feedbackContent = generateFeedbackContent(platformConfig, rating);
|
|
22672
22711
|
const location2 = partnerLocations.length > 0 ? f.helpers.arrayElement(partnerLocations) : null;
|
|
22673
22712
|
return {
|
|
22674
22713
|
id: f.string.uuid(),
|
|
22675
22714
|
source: "internal",
|
|
22715
|
+
reviewSource: "internal_feedback",
|
|
22676
22716
|
rating,
|
|
22677
22717
|
userName: f.person.fullName(),
|
|
22678
|
-
userEmail:
|
|
22718
|
+
// userEmail: faker.internet.email(),
|
|
22679
22719
|
content: feedbackContent,
|
|
22680
22720
|
createdAt: f.date.between({ from: startDate, to: endDate }).toISOString(),
|
|
22681
22721
|
hasReply: false,
|
|
@@ -22685,8 +22725,6 @@ function createInternalFeedback(platformConfig, partnerLocations, startDate, end
|
|
|
22685
22725
|
// 40% have customer responses
|
|
22686
22726
|
{ weight: 60, value: false }
|
|
22687
22727
|
]),
|
|
22688
|
-
actionUrl: void 0,
|
|
22689
|
-
// Internal feedback doesn't have external action URLs
|
|
22690
22728
|
locationId: location2?.externalId || void 0
|
|
22691
22729
|
};
|
|
22692
22730
|
}
|
|
@@ -22759,42 +22797,78 @@ function getReviewTemplates(platformId, rating) {
|
|
|
22759
22797
|
if (rating <= 2) return commonNegative;
|
|
22760
22798
|
return commonNeutral;
|
|
22761
22799
|
}
|
|
22800
|
+
function generateReplyContent(platformConfig, rating) {
|
|
22801
|
+
const platformName = platformConfig.name || "Business";
|
|
22802
|
+
const positiveReplies = [
|
|
22803
|
+
`Thank you so much for your kind words! We're thrilled to hear you had a great experience with ${platformName}. We look forward to serving you again soon!`,
|
|
22804
|
+
`We really appreciate your positive feedback! It's wonderful to know that our team at ${platformName} was able to exceed your expectations. Thank you for choosing us!`,
|
|
22805
|
+
`Thank you for taking the time to leave such a wonderful review! We're so glad you enjoyed your experience at ${platformName}. See you again soon!`,
|
|
22806
|
+
`Your review made our day! We're delighted that you had such a positive experience with ${platformName}. Thank you for your support!`
|
|
22807
|
+
];
|
|
22808
|
+
const neutralReplies = [
|
|
22809
|
+
`Thank you for your feedback. We appreciate you taking the time to share your experience with ${platformName}. We're always working to improve our services and would love to hear more about how we can serve you better.`,
|
|
22810
|
+
`We appreciate your honest feedback. At ${platformName}, we're committed to continuous improvement. Please feel free to reach out to us directly if there's anything specific we can address.`,
|
|
22811
|
+
`Thank you for your review. We value your input and are always looking for ways to enhance our customers' experience at ${platformName}. We hope to have the opportunity to serve you better in the future.`
|
|
22812
|
+
];
|
|
22813
|
+
const negativeReplies = [
|
|
22814
|
+
`We sincerely apologize that your experience at ${platformName} did not meet your expectations. Your feedback is very important to us, and we would like the opportunity to make this right. Please contact us directly so we can address your concerns.`,
|
|
22815
|
+
`Thank you for bringing this to our attention. We're sorry to hear about your experience at ${platformName}. We take all feedback seriously and would appreciate the chance to discuss this with you further. Please reach out to us at your convenience.`,
|
|
22816
|
+
`We're truly sorry about your disappointing experience with ${platformName}. This is not the standard of service we strive for. We'd like to learn more about what happened and work towards a resolution. Please contact our team directly.`,
|
|
22817
|
+
`Your feedback is concerning to us, and we apologize for falling short of your expectations at ${platformName}. We'd very much like to understand what went wrong and how we can improve. Please get in touch with us so we can make this right.`
|
|
22818
|
+
];
|
|
22819
|
+
if (rating >= 4) return f.helpers.arrayElement(positiveReplies);
|
|
22820
|
+
if (rating === 3) return f.helpers.arrayElement(neutralReplies);
|
|
22821
|
+
return f.helpers.arrayElement(negativeReplies);
|
|
22822
|
+
}
|
|
22762
22823
|
function getFeedbackTemplates(platformId, rating) {
|
|
22763
|
-
const
|
|
22764
|
-
"I wanted to share my
|
|
22765
|
-
"
|
|
22766
|
-
"
|
|
22824
|
+
const commonPromoters = [
|
|
22825
|
+
"I wanted to share my exceptional experience with {business}. The entire process was smooth and professional. The staff was knowledgeable and took the time to explain everything clearly. I'm extremely satisfied with the service and would definitely return.",
|
|
22826
|
+
"Outstanding service from {business}! From the initial consultation to the final delivery, everything exceeded my expectations. The team was responsive to my questions and made sure I was completely satisfied with the outcome.",
|
|
22827
|
+
"Excellent experience with {business}. The quality of work was exceptional and the customer service was top-notch. I appreciated the attention to detail and the follow-up communication. Highly recommend!",
|
|
22828
|
+
"Absolutely fantastic service! {business} went above and beyond my expectations. Professional, efficient, and genuinely cared about delivering the best possible outcome."
|
|
22767
22829
|
];
|
|
22768
|
-
const
|
|
22830
|
+
const commonPassives = [
|
|
22831
|
+
"My experience with {business} was good overall. The service was completed as requested, with only minor issues along the way. The staff was generally helpful, though response times could be improved. Met my expectations.",
|
|
22832
|
+
"Solid experience with {business}. Some aspects of the service were very good, while others were just okay. The pricing was reasonable, and the outcome was satisfactory. Room for improvement but generally positive.",
|
|
22833
|
+
"Decent service from {business}. The work was completed professionally, though not exceptional. Staff was courteous and the process was straightforward. Would consider using them again."
|
|
22834
|
+
];
|
|
22835
|
+
const commonDetractors = [
|
|
22769
22836
|
"I had several issues with my recent experience at {business}. The initial promise was not met, and when I tried to address my concerns, the customer service was lacking. The quality of service was below what I expected based on the pricing.",
|
|
22770
22837
|
"Unfortunately, my experience with {business} was disappointing. There were delays in service delivery, and the communication was poor throughout the process. I hope they can improve their operations for future customers.",
|
|
22771
|
-
"Not satisfied with the service from {business}. The staff seemed unprepared and the final result didn't match what was discussed initially. For the price paid, I expected much better quality and professionalism."
|
|
22772
|
-
|
|
22773
|
-
const commonNeutral = [
|
|
22774
|
-
"My experience with {business} was average. The service was completed as requested, but there were some minor issues along the way. The staff was generally helpful, though response times could be improved. Overall, it met basic expectations.",
|
|
22775
|
-
"Mixed feelings about {business}. Some aspects of the service were good, while others fell short. The pricing was fair, but the execution could have been better. They have potential but need some improvements."
|
|
22838
|
+
"Not satisfied with the service from {business}. The staff seemed unprepared and the final result didn't match what was discussed initially. For the price paid, I expected much better quality and professionalism.",
|
|
22839
|
+
"Poor experience overall. {business} needs significant improvements in customer service and service delivery. Multiple issues that were not resolved satisfactorily."
|
|
22776
22840
|
];
|
|
22777
22841
|
if (platformId === "goose") {
|
|
22778
|
-
const
|
|
22779
|
-
"My experience at {business} was wonderful. The staff clearly has extensive training in animal care and grooming techniques. My pet was comfortable throughout the entire process, and the grooming results
|
|
22780
|
-
]
|
|
22842
|
+
const petSpaPromoters = [
|
|
22843
|
+
"My experience at {business} was absolutely wonderful. The staff clearly has extensive training in animal care and grooming techniques. My pet was comfortable throughout the entire process, and the grooming results exceeded what I requested. The facility is immaculate and well-organized, and I truly appreciate the exceptional care they take with each animal."
|
|
22844
|
+
];
|
|
22845
|
+
const petSpaPassives = [
|
|
22846
|
+
"Good experience at {business}. The staff was knowledgeable and my pet was handled well. The grooming results were as requested, though the wait time was a bit longer than expected. Overall satisfied with the service."
|
|
22847
|
+
];
|
|
22848
|
+
const petSpaDetractors = [
|
|
22781
22849
|
"I had concerns about my pet's experience at {business}. While the grooming was completed, my animal seemed stressed afterward. The staff could benefit from additional training on handling anxious pets. The facility was clean, but the process could be more gentle and patient."
|
|
22782
22850
|
];
|
|
22783
|
-
if (rating >=
|
|
22784
|
-
if (rating
|
|
22851
|
+
if (rating >= 9) return [...commonPromoters, ...petSpaPromoters];
|
|
22852
|
+
if (rating >= 7) return [...commonPassives, ...petSpaPassives];
|
|
22853
|
+
return [...commonDetractors, ...petSpaDetractors];
|
|
22785
22854
|
}
|
|
22786
22855
|
if (platformId === "renterra") {
|
|
22787
|
-
const
|
|
22788
|
-
"
|
|
22789
|
-
]
|
|
22856
|
+
const rentalPromoters = [
|
|
22857
|
+
"Exceptional equipment rental experience with {business}! The reservation process was seamless, and the equipment was delivered ahead of schedule in perfect working condition. The staff provided comprehensive instructions and was incredibly responsive to our questions throughout the rental period."
|
|
22858
|
+
];
|
|
22859
|
+
const rentalPassives = [
|
|
22860
|
+
"Solid rental experience with {business}. The equipment worked as expected and delivery was on time. Staff was helpful when contacted. Met our needs for the project, though nothing particularly stood out as exceptional."
|
|
22861
|
+
];
|
|
22862
|
+
const rentalDetractors = [
|
|
22790
22863
|
"Had some challenges with my rental from {business}. The equipment arrived later than scheduled, which impacted our project timeline. Additionally, the equipment showed signs of wear that weren't disclosed upfront. Customer service was slow to respond when we reported issues."
|
|
22791
22864
|
];
|
|
22792
|
-
if (rating >=
|
|
22793
|
-
if (rating
|
|
22865
|
+
if (rating >= 9) return [...commonPromoters, ...rentalPromoters];
|
|
22866
|
+
if (rating >= 7) return [...commonPassives, ...rentalPassives];
|
|
22867
|
+
return [...commonDetractors, ...rentalDetractors];
|
|
22794
22868
|
}
|
|
22795
|
-
if (rating >=
|
|
22796
|
-
if (rating
|
|
22797
|
-
return
|
|
22869
|
+
if (rating >= 9) return commonPromoters;
|
|
22870
|
+
if (rating >= 7) return commonPassives;
|
|
22871
|
+
return commonDetractors;
|
|
22798
22872
|
}
|
|
22799
22873
|
const generateReviewAnalyticsData = ({
|
|
22800
22874
|
platformData,
|
|
@@ -22820,36 +22894,55 @@ const generateReviewAnalyticsData = ({
|
|
|
22820
22894
|
false,
|
|
22821
22895
|
isAllTimeQuery || false
|
|
22822
22896
|
);
|
|
22823
|
-
const
|
|
22824
|
-
|
|
22825
|
-
|
|
22826
|
-
|
|
22827
|
-
|
|
22828
|
-
|
|
22829
|
-
|
|
22830
|
-
|
|
22831
|
-
|
|
22832
|
-
|
|
22833
|
-
|
|
22834
|
-
|
|
22835
|
-
|
|
22836
|
-
|
|
22837
|
-
|
|
22838
|
-
|
|
22839
|
-
|
|
22840
|
-
|
|
22841
|
-
|
|
22842
|
-
|
|
22843
|
-
|
|
22897
|
+
const previousPeriodData = generateReviewPeriodData(
|
|
22898
|
+
platformConfig,
|
|
22899
|
+
scalingFactor,
|
|
22900
|
+
true,
|
|
22901
|
+
// isPrevious = true
|
|
22902
|
+
isAllTimeQuery || false
|
|
22903
|
+
);
|
|
22904
|
+
const totalData = generateReviewPeriodData(
|
|
22905
|
+
platformConfig,
|
|
22906
|
+
1,
|
|
22907
|
+
// No scaling for all-time
|
|
22908
|
+
false,
|
|
22909
|
+
true
|
|
22910
|
+
// isAllTime = true
|
|
22911
|
+
);
|
|
22912
|
+
const totalCountChange = currentPeriodData.totalCount - previousPeriodData.totalCount;
|
|
22913
|
+
const totalCountChangePercent = previousPeriodData.totalCount > 0 ? parseFloat(
|
|
22914
|
+
(totalCountChange / previousPeriodData.totalCount * 100).toFixed(1)
|
|
22915
|
+
) : null;
|
|
22916
|
+
const averageRatingChange = parseFloat(
|
|
22917
|
+
(currentPeriodData.averageRating - previousPeriodData.averageRating).toFixed(1)
|
|
22918
|
+
);
|
|
22919
|
+
const averageRatingChangePercent = previousPeriodData.averageRating > 0 ? parseFloat(
|
|
22920
|
+
(averageRatingChange / previousPeriodData.averageRating * 100).toFixed(1)
|
|
22921
|
+
) : 0;
|
|
22922
|
+
const fullResponse = {
|
|
22844
22923
|
reviewSource: "google_business_profile",
|
|
22845
|
-
|
|
22846
|
-
|
|
22847
|
-
|
|
22924
|
+
current: {
|
|
22925
|
+
totalCount: currentPeriodData.totalCount,
|
|
22926
|
+
averageRating: currentPeriodData.averageRating,
|
|
22927
|
+
ratingDistribution: currentPeriodData.ratingDistribution
|
|
22928
|
+
},
|
|
22929
|
+
previous: {
|
|
22930
|
+
totalCount: previousPeriodData.totalCount,
|
|
22931
|
+
averageRating: previousPeriodData.averageRating,
|
|
22932
|
+
ratingDistribution: previousPeriodData.ratingDistribution
|
|
22933
|
+
},
|
|
22934
|
+
total: totalData,
|
|
22935
|
+
comparison: {
|
|
22936
|
+
totalCountChange,
|
|
22937
|
+
totalCountChangePercent,
|
|
22938
|
+
averageRatingChange,
|
|
22939
|
+
averageRatingChangePercent
|
|
22940
|
+
}
|
|
22848
22941
|
};
|
|
22849
|
-
return
|
|
22942
|
+
return fullResponse;
|
|
22850
22943
|
};
|
|
22851
22944
|
function generateReviewPeriodData(platformConfig, scalingFactor, isPrevious = false, isAllTime = false) {
|
|
22852
|
-
const baseMetrics = isAllTime ? getAllTimePlatformReviewMetrics(platformConfig) : getBasePlatformReviewMetrics
|
|
22945
|
+
const baseMetrics = isAllTime ? getAllTimePlatformReviewMetrics(platformConfig) : getBasePlatformReviewMetrics(platformConfig);
|
|
22853
22946
|
const scaledTotalCount = isAllTime ? baseMetrics.totalCount : Math.max(1, Math.round(baseMetrics.totalCount * scalingFactor));
|
|
22854
22947
|
const performanceMultiplier = isPrevious ? 0.9 : 1;
|
|
22855
22948
|
const adjustedTotalCount = Math.round(
|
|
@@ -22857,7 +22950,7 @@ function generateReviewPeriodData(platformConfig, scalingFactor, isPrevious = fa
|
|
|
22857
22950
|
);
|
|
22858
22951
|
const ratingDelta = isPrevious ? -0.2 : 0;
|
|
22859
22952
|
const adjustedAverageRating = baseMetrics.averageRating + ratingDelta;
|
|
22860
|
-
const ratingDistribution = generateRatingDistribution(
|
|
22953
|
+
const ratingDistribution = generateRatingDistribution$1(
|
|
22861
22954
|
adjustedTotalCount,
|
|
22862
22955
|
adjustedAverageRating
|
|
22863
22956
|
);
|
|
@@ -22867,22 +22960,24 @@ function generateReviewPeriodData(platformConfig, scalingFactor, isPrevious = fa
|
|
|
22867
22960
|
ratingDistribution
|
|
22868
22961
|
};
|
|
22869
22962
|
}
|
|
22870
|
-
function getBasePlatformReviewMetrics
|
|
22963
|
+
function getBasePlatformReviewMetrics(platformConfig) {
|
|
22871
22964
|
const industryMultipliers = {
|
|
22872
22965
|
// Pet spa industry (Goose) - high customer satisfaction
|
|
22873
|
-
"
|
|
22874
|
-
totalCount:
|
|
22875
|
-
//
|
|
22876
|
-
averageRating: 4.
|
|
22966
|
+
"Paws Pet Hotel": {
|
|
22967
|
+
totalCount: 12,
|
|
22968
|
+
// ~12 reviews per 30 days
|
|
22969
|
+
averageRating: 4.5
|
|
22877
22970
|
},
|
|
22878
22971
|
// Equipment rental (Renterra) - moderate review volume
|
|
22879
22972
|
"Tomer's Rentals": {
|
|
22880
|
-
totalCount:
|
|
22973
|
+
totalCount: 8,
|
|
22974
|
+
// ~8 reviews per 30 days
|
|
22881
22975
|
averageRating: 4.2
|
|
22882
22976
|
},
|
|
22883
22977
|
// Default platform
|
|
22884
22978
|
default: {
|
|
22885
|
-
totalCount:
|
|
22979
|
+
totalCount: 10,
|
|
22980
|
+
// ~10 reviews per 30 days
|
|
22886
22981
|
averageRating: 4.3
|
|
22887
22982
|
}
|
|
22888
22983
|
};
|
|
@@ -22892,27 +22987,28 @@ function getBasePlatformReviewMetrics$1(platformConfig) {
|
|
|
22892
22987
|
function getAllTimePlatformReviewMetrics(platformConfig) {
|
|
22893
22988
|
const industryMultipliers = {
|
|
22894
22989
|
// Pet spa industry (Goose) - high customer satisfaction
|
|
22895
|
-
"
|
|
22896
|
-
totalCount:
|
|
22897
|
-
//
|
|
22898
|
-
averageRating: 4.
|
|
22899
|
-
// Slightly lower all-time average
|
|
22990
|
+
"Paws Pet Hotel": {
|
|
22991
|
+
totalCount: 40,
|
|
22992
|
+
// ~100 days of reviews (12 per 30 days)
|
|
22993
|
+
averageRating: 4.4
|
|
22900
22994
|
},
|
|
22901
22995
|
// Equipment rental (Renterra) - moderate review volume
|
|
22902
22996
|
"Tomer's Rentals": {
|
|
22903
|
-
totalCount:
|
|
22997
|
+
totalCount: 27,
|
|
22998
|
+
// ~100 days of reviews (8 per 30 days)
|
|
22904
22999
|
averageRating: 4.1
|
|
22905
23000
|
},
|
|
22906
23001
|
// Default platform
|
|
22907
23002
|
default: {
|
|
22908
|
-
totalCount:
|
|
23003
|
+
totalCount: 33,
|
|
23004
|
+
// ~100 days of reviews (10 per 30 days)
|
|
22909
23005
|
averageRating: 4.2
|
|
22910
23006
|
}
|
|
22911
23007
|
};
|
|
22912
23008
|
const baseKey = platformConfig.name in industryMultipliers ? platformConfig.name : "default";
|
|
22913
23009
|
return industryMultipliers[baseKey];
|
|
22914
23010
|
}
|
|
22915
|
-
function generateRatingDistribution(totalCount, targetAverage) {
|
|
23011
|
+
function generateRatingDistribution$1(totalCount, targetAverage) {
|
|
22916
23012
|
const baseDistribution = {
|
|
22917
23013
|
5: 0.6,
|
|
22918
23014
|
// 60% 5-star
|
|
@@ -22953,28 +23049,6 @@ function adjustDistributionForAverage(baseDistribution, targetAverage) {
|
|
|
22953
23049
|
}
|
|
22954
23050
|
return adjusted;
|
|
22955
23051
|
}
|
|
22956
|
-
function calculateReviewComparison(currentPeriod, previousPeriod) {
|
|
22957
|
-
const totalCountChange = currentPeriod.totalCount - previousPeriod.totalCount;
|
|
22958
|
-
const totalCountChangePercent = previousPeriod.totalCount > 0 ? parseFloat(
|
|
22959
|
-
(totalCountChange / previousPeriod.totalCount * 100).toFixed(1)
|
|
22960
|
-
) : null;
|
|
22961
|
-
const averageRatingChange = currentPeriod.averageRating && previousPeriod.averageRating ? parseFloat(
|
|
22962
|
-
(currentPeriod.averageRating - previousPeriod.averageRating).toFixed(
|
|
22963
|
-
2
|
|
22964
|
-
)
|
|
22965
|
-
) : null;
|
|
22966
|
-
const averageRatingChangePercent = averageRatingChange && previousPeriod.averageRating ? parseFloat(
|
|
22967
|
-
(averageRatingChange / previousPeriod.averageRating * 100).toFixed(
|
|
22968
|
-
1
|
|
22969
|
-
)
|
|
22970
|
-
) : null;
|
|
22971
|
-
return {
|
|
22972
|
-
totalCountChange,
|
|
22973
|
-
totalCountChangePercent,
|
|
22974
|
-
averageRatingChange,
|
|
22975
|
-
averageRatingChangePercent
|
|
22976
|
-
};
|
|
22977
|
-
}
|
|
22978
23052
|
const generateFeedbackAnalyticsData = ({
|
|
22979
23053
|
platformData,
|
|
22980
23054
|
queryParams
|
|
@@ -22999,38 +23073,86 @@ const generateFeedbackAnalyticsData = ({
|
|
|
22999
23073
|
false,
|
|
23000
23074
|
isAllTimeQuery
|
|
23001
23075
|
);
|
|
23002
|
-
|
|
23003
|
-
|
|
23004
|
-
|
|
23005
|
-
|
|
23006
|
-
|
|
23007
|
-
|
|
23008
|
-
|
|
23009
|
-
|
|
23010
|
-
|
|
23011
|
-
|
|
23012
|
-
|
|
23013
|
-
|
|
23014
|
-
|
|
23015
|
-
|
|
23016
|
-
|
|
23017
|
-
|
|
23018
|
-
|
|
23019
|
-
|
|
23020
|
-
|
|
23021
|
-
|
|
23022
|
-
|
|
23023
|
-
|
|
23024
|
-
|
|
23025
|
-
|
|
23026
|
-
|
|
23027
|
-
|
|
23028
|
-
|
|
23076
|
+
const previousPeriodData = generateFeedbackPeriodData(
|
|
23077
|
+
platformConfig,
|
|
23078
|
+
scalingFactor,
|
|
23079
|
+
true,
|
|
23080
|
+
// isPrevious = true
|
|
23081
|
+
isAllTimeQuery
|
|
23082
|
+
);
|
|
23083
|
+
const totalData = generateFeedbackPeriodData(
|
|
23084
|
+
platformConfig,
|
|
23085
|
+
1,
|
|
23086
|
+
// No scaling for all-time
|
|
23087
|
+
false,
|
|
23088
|
+
true
|
|
23089
|
+
// isAllTime = true
|
|
23090
|
+
);
|
|
23091
|
+
const totalSentChange = currentPeriodData.totalSent - previousPeriodData.totalSent;
|
|
23092
|
+
const totalSentChangePercent = previousPeriodData.totalSent > 0 ? parseFloat(
|
|
23093
|
+
(totalSentChange / previousPeriodData.totalSent * 100).toFixed(1)
|
|
23094
|
+
) : null;
|
|
23095
|
+
const npsScoreChange = currentPeriodData.npsScore - previousPeriodData.npsScore;
|
|
23096
|
+
const npsScoreChangePercent = previousPeriodData.npsScore !== 0 ? parseFloat(
|
|
23097
|
+
(npsScoreChange / Math.abs(previousPeriodData.npsScore) * 100).toFixed(1)
|
|
23098
|
+
) : null;
|
|
23099
|
+
const responseRateChange = parseFloat(
|
|
23100
|
+
(currentPeriodData.responseRate - previousPeriodData.responseRate).toFixed(
|
|
23101
|
+
1
|
|
23102
|
+
)
|
|
23103
|
+
);
|
|
23104
|
+
const responseRateChangePercent = previousPeriodData.responseRate > 0 ? parseFloat(
|
|
23105
|
+
(responseRateChange / previousPeriodData.responseRate * 100).toFixed(1)
|
|
23106
|
+
) : 0;
|
|
23107
|
+
const totalRespondedChange = currentPeriodData.totalResponded - previousPeriodData.totalResponded;
|
|
23108
|
+
const totalRespondedChangePercent = previousPeriodData.totalResponded > 0 ? parseFloat(
|
|
23109
|
+
(totalRespondedChange / previousPeriodData.totalResponded * 100).toFixed(1)
|
|
23110
|
+
) : null;
|
|
23111
|
+
const averageRatingChange = parseFloat(
|
|
23112
|
+
(currentPeriodData.averageRating - previousPeriodData.averageRating).toFixed(1)
|
|
23113
|
+
);
|
|
23114
|
+
const averageRatingChangePercent = previousPeriodData.averageRating > 0 ? parseFloat(
|
|
23115
|
+
(averageRatingChange / previousPeriodData.averageRating * 100).toFixed(1)
|
|
23116
|
+
) : 0;
|
|
23117
|
+
const fullResponse = {
|
|
23118
|
+
current: {
|
|
23119
|
+
totalSent: currentPeriodData.totalSent,
|
|
23120
|
+
totalResponded: currentPeriodData.totalResponded,
|
|
23121
|
+
averageRating: currentPeriodData.averageRating,
|
|
23122
|
+
responseRate: currentPeriodData.responseRate,
|
|
23123
|
+
positiveResponses: currentPeriodData.positiveResponses,
|
|
23124
|
+
negativeResponses: currentPeriodData.negativeResponses,
|
|
23125
|
+
passiveResponses: currentPeriodData.passiveResponses,
|
|
23126
|
+
npsScore: currentPeriodData.npsScore
|
|
23127
|
+
},
|
|
23128
|
+
previous: {
|
|
23129
|
+
totalSent: previousPeriodData.totalSent,
|
|
23130
|
+
totalResponded: previousPeriodData.totalResponded,
|
|
23131
|
+
averageRating: previousPeriodData.averageRating,
|
|
23132
|
+
responseRate: previousPeriodData.responseRate,
|
|
23133
|
+
positiveResponses: previousPeriodData.positiveResponses,
|
|
23134
|
+
negativeResponses: previousPeriodData.negativeResponses,
|
|
23135
|
+
passiveResponses: previousPeriodData.passiveResponses,
|
|
23136
|
+
npsScore: previousPeriodData.npsScore
|
|
23137
|
+
},
|
|
23138
|
+
total: totalData,
|
|
23139
|
+
comparison: {
|
|
23140
|
+
totalSentChange,
|
|
23141
|
+
totalSentChangePercent,
|
|
23142
|
+
npsScoreChange,
|
|
23143
|
+
npsScoreChangePercent,
|
|
23144
|
+
responseRateChange,
|
|
23145
|
+
responseRateChangePercent,
|
|
23146
|
+
totalRespondedChange,
|
|
23147
|
+
totalRespondedChangePercent,
|
|
23148
|
+
averageRatingChange,
|
|
23149
|
+
averageRatingChangePercent
|
|
23150
|
+
}
|
|
23029
23151
|
};
|
|
23030
|
-
return
|
|
23152
|
+
return fullResponse;
|
|
23031
23153
|
};
|
|
23032
23154
|
function generateFeedbackPeriodData(platformConfig, scalingFactor, isPrevious = false, isAllTime = false) {
|
|
23033
|
-
const baseMetrics = isAllTime ? getAllTimePlatformFeedbackMetrics(platformConfig) : getBasePlatformFeedbackMetrics
|
|
23155
|
+
const baseMetrics = isAllTime ? getAllTimePlatformFeedbackMetrics(platformConfig) : getBasePlatformFeedbackMetrics(platformConfig);
|
|
23034
23156
|
const scaledTotalSent = isAllTime ? baseMetrics.totalSent : Math.max(1, Math.round(baseMetrics.totalSent * scalingFactor));
|
|
23035
23157
|
const performanceMultiplier = isPrevious ? 0.82 : 1;
|
|
23036
23158
|
const adjustedTotalSent = Math.round(scaledTotalSent * performanceMultiplier);
|
|
@@ -23068,10 +23190,10 @@ function generateFeedbackPeriodData(platformConfig, scalingFactor, isPrevious =
|
|
|
23068
23190
|
// real NPS score
|
|
23069
23191
|
};
|
|
23070
23192
|
}
|
|
23071
|
-
function getBasePlatformFeedbackMetrics
|
|
23193
|
+
function getBasePlatformFeedbackMetrics(platformConfig) {
|
|
23072
23194
|
const industryMultipliers = {
|
|
23073
23195
|
// Pet spa industry (Goose) - high engagement, positive feedback
|
|
23074
|
-
"
|
|
23196
|
+
"Paws Pet Hotel": {
|
|
23075
23197
|
totalSent: 1250,
|
|
23076
23198
|
// Much higher - businesses send way more emails than get reviews
|
|
23077
23199
|
responseRate: 42,
|
|
@@ -23114,7 +23236,7 @@ function getBasePlatformFeedbackMetrics$1(platformConfig) {
|
|
|
23114
23236
|
function getAllTimePlatformFeedbackMetrics(platformConfig) {
|
|
23115
23237
|
const industryMultipliers = {
|
|
23116
23238
|
// Pet spa industry (Goose) - high engagement, positive feedback
|
|
23117
|
-
"
|
|
23239
|
+
"Paws Pet Hotel": {
|
|
23118
23240
|
totalSent: 18500,
|
|
23119
23241
|
// WAY higher - businesses send tons of emails over time
|
|
23120
23242
|
responseRate: 39,
|
|
@@ -23163,48 +23285,6 @@ function calculateAverageFromNPS(promoters, passives, detractors) {
|
|
|
23163
23285
|
const weightedSum = promoters * promoterAverage + passives * passiveAverage + detractors * detractorAverage;
|
|
23164
23286
|
return weightedSum / totalResponses;
|
|
23165
23287
|
}
|
|
23166
|
-
function calculateFeedbackComparison(currentPeriod, previousPeriod) {
|
|
23167
|
-
const totalSentChange = currentPeriod.totalSent - previousPeriod.totalSent;
|
|
23168
|
-
const totalSentChangePercent = previousPeriod.totalSent > 0 ? parseFloat(
|
|
23169
|
-
(totalSentChange / previousPeriod.totalSent * 100).toFixed(1)
|
|
23170
|
-
) : null;
|
|
23171
|
-
const totalRespondedChange = currentPeriod.totalResponded - previousPeriod.totalResponded;
|
|
23172
|
-
const totalRespondedChangePercent = previousPeriod.totalResponded > 0 ? parseFloat(
|
|
23173
|
-
(totalRespondedChange / previousPeriod.totalResponded * 100).toFixed(1)
|
|
23174
|
-
) : null;
|
|
23175
|
-
const averageRatingChange = currentPeriod.averageRating && previousPeriod.averageRating ? parseFloat(
|
|
23176
|
-
(currentPeriod.averageRating - previousPeriod.averageRating).toFixed(
|
|
23177
|
-
2
|
|
23178
|
-
)
|
|
23179
|
-
) : null;
|
|
23180
|
-
const averageRatingChangePercent = averageRatingChange && previousPeriod.averageRating ? parseFloat(
|
|
23181
|
-
(averageRatingChange / previousPeriod.averageRating * 100).toFixed(
|
|
23182
|
-
1
|
|
23183
|
-
)
|
|
23184
|
-
) : null;
|
|
23185
|
-
const responseRateChange = currentPeriod.responseRate - previousPeriod.responseRate;
|
|
23186
|
-
const responseRateChangePercent = previousPeriod.responseRate > 0 ? parseFloat(
|
|
23187
|
-
(responseRateChange / previousPeriod.responseRate * 100).toFixed(1)
|
|
23188
|
-
) : null;
|
|
23189
|
-
const npsScoreChange = currentPeriod.npsScore - previousPeriod.npsScore;
|
|
23190
|
-
const npsScoreChangePercent = previousPeriod.npsScore !== 0 ? parseFloat(
|
|
23191
|
-
(npsScoreChange / Math.abs(previousPeriod.npsScore) * 100).toFixed(
|
|
23192
|
-
1
|
|
23193
|
-
)
|
|
23194
|
-
) : null;
|
|
23195
|
-
return {
|
|
23196
|
-
totalSentChange,
|
|
23197
|
-
totalSentChangePercent,
|
|
23198
|
-
totalRespondedChange,
|
|
23199
|
-
totalRespondedChangePercent,
|
|
23200
|
-
averageRatingChange,
|
|
23201
|
-
averageRatingChangePercent,
|
|
23202
|
-
responseRateChange: parseFloat(responseRateChange.toFixed(1)),
|
|
23203
|
-
responseRateChangePercent,
|
|
23204
|
-
npsScoreChange,
|
|
23205
|
-
npsScoreChangePercent
|
|
23206
|
-
};
|
|
23207
|
-
}
|
|
23208
23288
|
const generateAuditLogData = ({
|
|
23209
23289
|
startDate,
|
|
23210
23290
|
endDate,
|
|
@@ -23270,24 +23350,27 @@ function generateAuditLogPool(startDate, endDate, count) {
|
|
|
23270
23350
|
const logs = [];
|
|
23271
23351
|
const timeSpan = endDate.getTime() - startDate.getTime();
|
|
23272
23352
|
const eventSequences = [
|
|
23273
|
-
//
|
|
23353
|
+
// Private Feedback flow
|
|
23274
23354
|
{
|
|
23275
23355
|
type: "SURVEY_REQUEST",
|
|
23276
23356
|
channel: "EMAIL",
|
|
23277
23357
|
statuses: ["SENT", "DELIVERED", "OPENED", "COMPLETED"],
|
|
23278
23358
|
messages: [
|
|
23279
|
-
"
|
|
23280
|
-
"
|
|
23281
|
-
"Customer opened
|
|
23282
|
-
"
|
|
23359
|
+
"Private feedback request sent to customer",
|
|
23360
|
+
"Private feedback request delivered to customer",
|
|
23361
|
+
"Customer opened private feedback request",
|
|
23362
|
+
"Private feedback completed by customer"
|
|
23283
23363
|
]
|
|
23284
23364
|
},
|
|
23285
|
-
// Review flow
|
|
23365
|
+
// Google Review flow
|
|
23286
23366
|
{
|
|
23287
23367
|
type: "REVIEW_REQUEST",
|
|
23288
23368
|
channel: "SMS",
|
|
23289
23369
|
statuses: ["SENT", "DELIVERED"],
|
|
23290
|
-
messages: [
|
|
23370
|
+
messages: [
|
|
23371
|
+
"Google review request sent to",
|
|
23372
|
+
"Google review request delivered to"
|
|
23373
|
+
]
|
|
23291
23374
|
},
|
|
23292
23375
|
// Google review flow
|
|
23293
23376
|
{
|
|
@@ -23304,13 +23387,19 @@ function generateAuditLogPool(startDate, endDate, count) {
|
|
|
23304
23387
|
type: "SURVEY_REMINDER",
|
|
23305
23388
|
channel: "EMAIL",
|
|
23306
23389
|
statuses: ["SENT", "DELIVERED"],
|
|
23307
|
-
messages: [
|
|
23390
|
+
messages: [
|
|
23391
|
+
"Private feedback reminder sent to",
|
|
23392
|
+
"Private feedback reminder delivered to"
|
|
23393
|
+
]
|
|
23308
23394
|
},
|
|
23309
23395
|
{
|
|
23310
23396
|
type: "REVIEW_REMINDER",
|
|
23311
23397
|
channel: "SMS",
|
|
23312
23398
|
statuses: ["SENT", "DELIVERED"],
|
|
23313
|
-
messages: [
|
|
23399
|
+
messages: [
|
|
23400
|
+
"Google review reminder sent to",
|
|
23401
|
+
"Google review reminder delivered to"
|
|
23402
|
+
]
|
|
23314
23403
|
}
|
|
23315
23404
|
];
|
|
23316
23405
|
for (let i2 = 0; i2 < count; i2++) {
|
|
@@ -23352,125 +23441,112 @@ const generateReviewTimeSeriesData = ({
|
|
|
23352
23441
|
platformData,
|
|
23353
23442
|
queryParams
|
|
23354
23443
|
}) => {
|
|
23355
|
-
const
|
|
23356
|
-
platformData
|
|
23357
|
-
|
|
23444
|
+
const analyticsData = generateReviewAnalyticsData({
|
|
23445
|
+
platformData,
|
|
23446
|
+
queryParams
|
|
23447
|
+
});
|
|
23358
23448
|
const { requestedDays, startDate, endDate } = parseDateRange(
|
|
23359
23449
|
queryParams.start_date,
|
|
23360
23450
|
queryParams.end_date,
|
|
23361
23451
|
30
|
|
23362
23452
|
);
|
|
23363
|
-
const
|
|
23364
|
-
const basePeriodDays = 30;
|
|
23365
|
-
const scalingFactor = requestedDays / basePeriodDays;
|
|
23366
|
-
const totalScaledReviews = Math.max(
|
|
23367
|
-
1,
|
|
23368
|
-
Math.round(baseMetrics.totalReviews * scalingFactor)
|
|
23369
|
-
);
|
|
23453
|
+
const isAllTimeQuery = !queryParams.start_date || !queryParams.end_date || queryParams.start_date.startsWith("2020-");
|
|
23370
23454
|
let intervalDays = 7;
|
|
23371
23455
|
if (requestedDays <= 30)
|
|
23372
23456
|
intervalDays = 1;
|
|
23373
23457
|
else if (requestedDays <= 90) intervalDays = 3;
|
|
23374
23458
|
const numDataPoints = Math.max(1, Math.ceil(requestedDays / intervalDays));
|
|
23375
23459
|
const dataPoints = generateReviewTimeSeriesPoints({
|
|
23376
|
-
|
|
23460
|
+
analyticsData,
|
|
23377
23461
|
startDate,
|
|
23378
23462
|
endDate,
|
|
23379
23463
|
numDataPoints,
|
|
23380
|
-
intervalDays
|
|
23464
|
+
intervalDays,
|
|
23465
|
+
isAllTimeQuery
|
|
23381
23466
|
});
|
|
23382
23467
|
return {
|
|
23383
23468
|
data: dataPoints
|
|
23384
23469
|
};
|
|
23385
23470
|
};
|
|
23386
23471
|
function generateReviewTimeSeriesPoints({
|
|
23387
|
-
|
|
23472
|
+
analyticsData,
|
|
23388
23473
|
startDate,
|
|
23389
23474
|
endDate,
|
|
23390
23475
|
numDataPoints,
|
|
23391
|
-
intervalDays
|
|
23476
|
+
intervalDays,
|
|
23477
|
+
isAllTimeQuery
|
|
23392
23478
|
}) {
|
|
23479
|
+
const reviewCounts = distributeReviewsAcrossPoints(
|
|
23480
|
+
analyticsData.current.totalCount,
|
|
23481
|
+
numDataPoints
|
|
23482
|
+
);
|
|
23483
|
+
const targetRating = analyticsData.total.averageRating ?? 4.2;
|
|
23484
|
+
const startingRating = targetRating - 0.3;
|
|
23485
|
+
const endingRating = targetRating + 0.15;
|
|
23393
23486
|
const dataPoints = [];
|
|
23394
23487
|
let currentDate = new Date(startDate);
|
|
23395
|
-
let
|
|
23396
|
-
|
|
23397
|
-
const
|
|
23398
|
-
const
|
|
23399
|
-
const
|
|
23488
|
+
let runningReviewCount = 0;
|
|
23489
|
+
let runningWeightedSum = 0;
|
|
23490
|
+
const targetEndCumulative = targetRating;
|
|
23491
|
+
const ratingChange = analyticsData.comparison.averageRatingChange ?? 0;
|
|
23492
|
+
const targetStartCumulative = targetRating - ratingChange;
|
|
23400
23493
|
for (let i2 = 0; i2 < numDataPoints; i2++) {
|
|
23401
23494
|
const pointDateStr = currentDate.toISOString().split("T")[0];
|
|
23402
|
-
|
|
23403
|
-
|
|
23404
|
-
|
|
23405
|
-
|
|
23406
|
-
|
|
23407
|
-
|
|
23408
|
-
|
|
23409
|
-
|
|
23410
|
-
|
|
23495
|
+
const currentPointReviews = reviewCounts[i2];
|
|
23496
|
+
const progress = numDataPoints > 1 ? i2 / (numDataPoints - 1) : 1;
|
|
23497
|
+
let currentPointRating = startingRating + (endingRating - startingRating) * Math.pow(progress, 1.3);
|
|
23498
|
+
currentPointRating += (Math.random() - 0.5) * 0.08;
|
|
23499
|
+
currentPointRating = Math.max(1, Math.min(5, currentPointRating));
|
|
23500
|
+
let cumulativeAverageRating;
|
|
23501
|
+
if (isAllTimeQuery) {
|
|
23502
|
+
runningReviewCount += currentPointReviews;
|
|
23503
|
+
runningWeightedSum += currentPointReviews * currentPointRating;
|
|
23504
|
+
cumulativeAverageRating = runningReviewCount > 0 ? parseFloat((runningWeightedSum / runningReviewCount).toFixed(2)) : null;
|
|
23411
23505
|
} else {
|
|
23412
|
-
|
|
23413
|
-
|
|
23506
|
+
const cumulativeProgress = numDataPoints > 1 ? i2 / (numDataPoints - 1) : 1;
|
|
23507
|
+
cumulativeAverageRating = parseFloat(
|
|
23508
|
+
(targetStartCumulative + (targetEndCumulative - targetStartCumulative) * cumulativeProgress).toFixed(2)
|
|
23509
|
+
);
|
|
23414
23510
|
}
|
|
23415
|
-
currentPointReviews = Math.max(0, currentPointReviews);
|
|
23416
|
-
currentPointRating = Math.max(1, Math.min(5, currentPointRating));
|
|
23417
|
-
cumulativeReviews += currentPointReviews;
|
|
23418
23511
|
const { promoterCount, detractorCount } = calculatePromoterDetractorSplit(
|
|
23419
23512
|
currentPointReviews,
|
|
23420
23513
|
currentPointRating
|
|
23421
23514
|
);
|
|
23515
|
+
const ratingDistribution = generateRatingDistribution(
|
|
23516
|
+
currentPointReviews,
|
|
23517
|
+
currentPointRating
|
|
23518
|
+
);
|
|
23422
23519
|
dataPoints.push({
|
|
23423
23520
|
date: pointDateStr,
|
|
23424
23521
|
totalReviews: currentPointReviews,
|
|
23425
23522
|
averageRating: currentPointReviews > 0 ? parseFloat(currentPointRating.toFixed(1)) : null,
|
|
23523
|
+
cumulativeAverageRating,
|
|
23426
23524
|
promoterCount,
|
|
23427
|
-
detractorCount
|
|
23525
|
+
detractorCount,
|
|
23526
|
+
ratingDistribution
|
|
23428
23527
|
});
|
|
23429
23528
|
currentDate.setDate(currentDate.getDate() + intervalDays);
|
|
23430
23529
|
if (currentDate > endDate && i2 < numDataPoints - 1) {
|
|
23431
23530
|
currentDate = new Date(endDate);
|
|
23432
|
-
if (dataPoints.length > 0 && dataPoints[dataPoints.length - 1].date === currentDate.toISOString().split("T")[0]) {
|
|
23433
|
-
break;
|
|
23434
|
-
}
|
|
23435
23531
|
}
|
|
23436
23532
|
}
|
|
23437
23533
|
return dataPoints;
|
|
23438
23534
|
}
|
|
23439
|
-
function
|
|
23440
|
-
|
|
23441
|
-
|
|
23442
|
-
|
|
23443
|
-
|
|
23444
|
-
|
|
23445
|
-
|
|
23446
|
-
|
|
23447
|
-
|
|
23448
|
-
|
|
23449
|
-
|
|
23450
|
-
|
|
23451
|
-
|
|
23452
|
-
|
|
23453
|
-
|
|
23454
|
-
averageRating: 4.2,
|
|
23455
|
-
promoterRate: 0.55,
|
|
23456
|
-
// 55% 5-star
|
|
23457
|
-
detractorRate: 0.2
|
|
23458
|
-
// 20% 1-3 star
|
|
23459
|
-
},
|
|
23460
|
-
// Default platform
|
|
23461
|
-
default: {
|
|
23462
|
-
totalReviews: 95,
|
|
23463
|
-
// Match all-time analytics exactly
|
|
23464
|
-
averageRating: 4.2,
|
|
23465
|
-
// Match all-time analytics exactly
|
|
23466
|
-
promoterRate: 0.6,
|
|
23467
|
-
// 60% 5-star
|
|
23468
|
-
detractorRate: 0.18
|
|
23469
|
-
// 18% 1-3 star
|
|
23470
|
-
}
|
|
23471
|
-
};
|
|
23472
|
-
const baseKey = platformConfig.name in industryMetrics ? platformConfig.name : "default";
|
|
23473
|
-
return industryMetrics[baseKey];
|
|
23535
|
+
function distributeReviewsAcrossPoints(totalReviews, numDataPoints) {
|
|
23536
|
+
if (numDataPoints === 0) return [];
|
|
23537
|
+
if (numDataPoints === 1) return [totalReviews];
|
|
23538
|
+
const counts = [];
|
|
23539
|
+
let remainingReviews = totalReviews;
|
|
23540
|
+
const basePerPoint = totalReviews / numDataPoints;
|
|
23541
|
+
for (let i2 = 0; i2 < numDataPoints - 1; i2++) {
|
|
23542
|
+
const variation = 0.7 + Math.random() * 0.6;
|
|
23543
|
+
const count = Math.round(basePerPoint * variation);
|
|
23544
|
+
const actualCount = Math.max(0, Math.min(count, remainingReviews));
|
|
23545
|
+
counts.push(actualCount);
|
|
23546
|
+
remainingReviews -= actualCount;
|
|
23547
|
+
}
|
|
23548
|
+
counts.push(Math.max(0, remainingReviews));
|
|
23549
|
+
return counts;
|
|
23474
23550
|
}
|
|
23475
23551
|
function calculatePromoterDetractorSplit(totalReviews, averageRating) {
|
|
23476
23552
|
if (totalReviews === 0) {
|
|
@@ -23497,33 +23573,80 @@ function calculatePromoterDetractorSplit(totalReviews, averageRating) {
|
|
|
23497
23573
|
const detractorCount = Math.round(totalReviews * detractorRate);
|
|
23498
23574
|
return { promoterCount, detractorCount };
|
|
23499
23575
|
}
|
|
23576
|
+
function generateRatingDistribution(totalReviews, averageRating) {
|
|
23577
|
+
if (totalReviews === 0) {
|
|
23578
|
+
return { 1: 0, 2: 0, 3: 0, 4: 0, 5: 0 };
|
|
23579
|
+
}
|
|
23580
|
+
let distribution = { 1: 0, 2: 0, 3: 0, 4: 0, 5: 0 };
|
|
23581
|
+
if (averageRating >= 4.5) {
|
|
23582
|
+
distribution[5] = Math.round(totalReviews * 0.6);
|
|
23583
|
+
distribution[4] = Math.round(totalReviews * 0.3);
|
|
23584
|
+
distribution[3] = Math.round(totalReviews * 0.08);
|
|
23585
|
+
distribution[2] = Math.round(totalReviews * 0.015);
|
|
23586
|
+
distribution[1] = Math.round(totalReviews * 5e-3);
|
|
23587
|
+
} else if (averageRating >= 4) {
|
|
23588
|
+
distribution[5] = Math.round(totalReviews * 0.45);
|
|
23589
|
+
distribution[4] = Math.round(totalReviews * 0.35);
|
|
23590
|
+
distribution[3] = Math.round(totalReviews * 0.15);
|
|
23591
|
+
distribution[2] = Math.round(totalReviews * 0.04);
|
|
23592
|
+
distribution[1] = Math.round(totalReviews * 0.01);
|
|
23593
|
+
} else if (averageRating >= 3.5) {
|
|
23594
|
+
distribution[5] = Math.round(totalReviews * 0.3);
|
|
23595
|
+
distribution[4] = Math.round(totalReviews * 0.35);
|
|
23596
|
+
distribution[3] = Math.round(totalReviews * 0.25);
|
|
23597
|
+
distribution[2] = Math.round(totalReviews * 0.07);
|
|
23598
|
+
distribution[1] = Math.round(totalReviews * 0.03);
|
|
23599
|
+
} else {
|
|
23600
|
+
distribution[5] = Math.round(totalReviews * 0.15);
|
|
23601
|
+
distribution[4] = Math.round(totalReviews * 0.25);
|
|
23602
|
+
distribution[3] = Math.round(totalReviews * 0.35);
|
|
23603
|
+
distribution[2] = Math.round(totalReviews * 0.15);
|
|
23604
|
+
distribution[1] = Math.round(totalReviews * 0.1);
|
|
23605
|
+
}
|
|
23606
|
+
Object.keys(distribution).forEach((rating) => {
|
|
23607
|
+
const key = parseInt(rating);
|
|
23608
|
+
const variation = Math.round(
|
|
23609
|
+
distribution[key] * (Math.random() - 0.5) * 0.2
|
|
23610
|
+
);
|
|
23611
|
+
distribution[key] = Math.max(0, distribution[key] + variation);
|
|
23612
|
+
});
|
|
23613
|
+
const currentTotal = Object.values(distribution).reduce(
|
|
23614
|
+
(sum, count) => sum + count,
|
|
23615
|
+
0
|
|
23616
|
+
);
|
|
23617
|
+
const difference = totalReviews - currentTotal;
|
|
23618
|
+
if (difference !== 0) {
|
|
23619
|
+
const mostCommonRating = Object.entries(distribution).reduce(
|
|
23620
|
+
(max, [rating, count]) => count > distribution[max] ? rating : max,
|
|
23621
|
+
"5"
|
|
23622
|
+
);
|
|
23623
|
+
distribution[mostCommonRating] = Math.max(
|
|
23624
|
+
0,
|
|
23625
|
+
distribution[mostCommonRating] + difference
|
|
23626
|
+
);
|
|
23627
|
+
}
|
|
23628
|
+
return distribution;
|
|
23629
|
+
}
|
|
23500
23630
|
const generateFeedbackTimeSeriesData = ({
|
|
23501
23631
|
platformData,
|
|
23502
23632
|
queryParams
|
|
23503
23633
|
}) => {
|
|
23504
|
-
const
|
|
23505
|
-
platformData
|
|
23506
|
-
|
|
23634
|
+
const analyticsData = generateFeedbackAnalyticsData({
|
|
23635
|
+
platformData,
|
|
23636
|
+
queryParams
|
|
23637
|
+
});
|
|
23507
23638
|
const { requestedDays, startDate, endDate } = parseDateRange(
|
|
23508
23639
|
queryParams.start_date,
|
|
23509
23640
|
queryParams.end_date,
|
|
23510
23641
|
30
|
|
23511
23642
|
);
|
|
23512
|
-
const baseMetrics = getBasePlatformFeedbackMetrics(platformConfig);
|
|
23513
|
-
const basePeriodDays = 30;
|
|
23514
|
-
const scalingFactor = requestedDays / basePeriodDays;
|
|
23515
|
-
const totalScaledSent = Math.max(
|
|
23516
|
-
1,
|
|
23517
|
-
Math.round(baseMetrics.totalSent * scalingFactor)
|
|
23518
|
-
);
|
|
23519
23643
|
let intervalDays = 7;
|
|
23520
23644
|
if (requestedDays <= 30)
|
|
23521
23645
|
intervalDays = 1;
|
|
23522
23646
|
else if (requestedDays <= 90) intervalDays = 3;
|
|
23523
23647
|
const numDataPoints = Math.max(1, Math.ceil(requestedDays / intervalDays));
|
|
23524
23648
|
const dataPoints = generateFeedbackTimeSeriesPoints({
|
|
23525
|
-
|
|
23526
|
-
baseMetrics,
|
|
23649
|
+
analyticsData,
|
|
23527
23650
|
startDate,
|
|
23528
23651
|
endDate,
|
|
23529
23652
|
numDataPoints,
|
|
@@ -23533,52 +23656,54 @@ const generateFeedbackTimeSeriesData = ({
|
|
|
23533
23656
|
data: dataPoints
|
|
23534
23657
|
};
|
|
23535
23658
|
};
|
|
23659
|
+
function distributeFeedbackAcrossPoints(total, numDataPoints) {
|
|
23660
|
+
if (numDataPoints === 0) return [];
|
|
23661
|
+
if (numDataPoints === 1) return [total];
|
|
23662
|
+
const counts = [];
|
|
23663
|
+
let remaining = total;
|
|
23664
|
+
const basePerPoint = total / numDataPoints;
|
|
23665
|
+
for (let i2 = 0; i2 < numDataPoints - 1; i2++) {
|
|
23666
|
+
const variation = 0.7 + Math.random() * 0.6;
|
|
23667
|
+
const count = Math.round(basePerPoint * variation);
|
|
23668
|
+
const actualCount = Math.max(0, Math.min(count, remaining));
|
|
23669
|
+
counts.push(actualCount);
|
|
23670
|
+
remaining -= actualCount;
|
|
23671
|
+
}
|
|
23672
|
+
counts.push(Math.max(0, remaining));
|
|
23673
|
+
return counts;
|
|
23674
|
+
}
|
|
23536
23675
|
function generateFeedbackTimeSeriesPoints({
|
|
23537
|
-
|
|
23538
|
-
baseMetrics,
|
|
23676
|
+
analyticsData,
|
|
23539
23677
|
startDate,
|
|
23540
23678
|
endDate,
|
|
23541
23679
|
numDataPoints,
|
|
23542
23680
|
intervalDays
|
|
23543
23681
|
}) {
|
|
23682
|
+
const sentCounts = distributeFeedbackAcrossPoints(
|
|
23683
|
+
analyticsData.current.totalSent,
|
|
23684
|
+
numDataPoints
|
|
23685
|
+
);
|
|
23686
|
+
const respondedCounts = distributeFeedbackAcrossPoints(
|
|
23687
|
+
analyticsData.current.totalResponded,
|
|
23688
|
+
numDataPoints
|
|
23689
|
+
);
|
|
23690
|
+
const targetEndNPS = analyticsData.total.npsScore;
|
|
23691
|
+
const npsChange = analyticsData.comparison.npsScoreChange ?? 0;
|
|
23692
|
+
const targetStartNPS = targetEndNPS - npsChange;
|
|
23544
23693
|
const dataPoints = [];
|
|
23545
23694
|
let currentDate = new Date(startDate);
|
|
23546
|
-
let cumulativeSent = 0;
|
|
23547
|
-
const basePointSent = totalScaledSent / numDataPoints;
|
|
23548
|
-
const startingResponseRate = baseMetrics.responseRate * 0.75;
|
|
23549
|
-
const endingResponseRate = baseMetrics.responseRate * 1.15;
|
|
23550
|
-
const startingNPS = 30;
|
|
23551
|
-
const endingNPS = 42;
|
|
23552
23695
|
for (let i2 = 0; i2 < numDataPoints; i2++) {
|
|
23553
23696
|
const pointDateStr = currentDate.toISOString().split("T")[0];
|
|
23554
|
-
|
|
23555
|
-
|
|
23556
|
-
|
|
23557
|
-
|
|
23558
|
-
|
|
23559
|
-
basePointSent * (1 + (Math.random() - 0.5) * 0.5)
|
|
23560
|
-
);
|
|
23561
|
-
const progress = i2 / (numDataPoints - 1);
|
|
23562
|
-
currentPointResponseRate = startingResponseRate + (endingResponseRate - startingResponseRate) * Math.pow(progress, 1.3);
|
|
23563
|
-
currentPointNPS = startingNPS + (endingNPS - startingNPS) * Math.pow(progress, 1.2);
|
|
23564
|
-
currentPointResponseRate *= 1 + (Math.random() - 0.5) * 0.1;
|
|
23565
|
-
currentPointNPS *= 1 + (Math.random() - 0.5) * 0.1;
|
|
23566
|
-
} else {
|
|
23567
|
-
currentPointSent = Math.max(0, totalScaledSent - cumulativeSent);
|
|
23568
|
-
currentPointResponseRate = baseMetrics.responseRate;
|
|
23569
|
-
currentPointNPS = 42;
|
|
23570
|
-
}
|
|
23571
|
-
currentPointSent = Math.max(0, currentPointSent);
|
|
23572
|
-
currentPointResponseRate = Math.max(
|
|
23573
|
-
5,
|
|
23574
|
-
Math.min(80, currentPointResponseRate)
|
|
23575
|
-
);
|
|
23697
|
+
const currentPointSent = sentCounts[i2];
|
|
23698
|
+
const currentPointResponded = respondedCounts[i2];
|
|
23699
|
+
const progress = numDataPoints > 1 ? i2 / (numDataPoints - 1) : 1;
|
|
23700
|
+
let currentPointNPS = targetStartNPS + (targetEndNPS - targetStartNPS) * Math.pow(progress, 1.2);
|
|
23701
|
+
currentPointNPS += (Math.random() - 0.5) * 8;
|
|
23576
23702
|
currentPointNPS = Math.max(-100, Math.min(100, currentPointNPS));
|
|
23577
|
-
cumulativeSent += currentPointSent;
|
|
23578
|
-
const currentPointResponded = Math.round(
|
|
23579
|
-
currentPointSent * (currentPointResponseRate / 100)
|
|
23580
|
-
);
|
|
23581
23703
|
const { promoterCount, detractorCount, passiveCount } = calculateNPSBreakdown(currentPointResponded, currentPointNPS);
|
|
23704
|
+
const cumulativeNPS = parseFloat(
|
|
23705
|
+
(targetStartNPS + (targetEndNPS - targetStartNPS) * progress).toFixed(1)
|
|
23706
|
+
);
|
|
23582
23707
|
dataPoints.push({
|
|
23583
23708
|
date: pointDateStr,
|
|
23584
23709
|
totalSent: currentPointSent,
|
|
@@ -23586,56 +23711,16 @@ function generateFeedbackTimeSeriesPoints({
|
|
|
23586
23711
|
promoterCount,
|
|
23587
23712
|
detractorCount,
|
|
23588
23713
|
passiveCount,
|
|
23589
|
-
npsScore:
|
|
23590
|
-
// Use
|
|
23714
|
+
npsScore: cumulativeNPS
|
|
23715
|
+
// Use cumulative NPS for the chart line
|
|
23591
23716
|
});
|
|
23592
23717
|
currentDate.setDate(currentDate.getDate() + intervalDays);
|
|
23593
23718
|
if (currentDate > endDate && i2 < numDataPoints - 1) {
|
|
23594
23719
|
currentDate = new Date(endDate);
|
|
23595
|
-
if (dataPoints.length > 0 && dataPoints[dataPoints.length - 1].date === currentDate.toISOString().split("T")[0]) {
|
|
23596
|
-
break;
|
|
23597
|
-
}
|
|
23598
23720
|
}
|
|
23599
23721
|
}
|
|
23600
23722
|
return dataPoints;
|
|
23601
23723
|
}
|
|
23602
|
-
function getBasePlatformFeedbackMetrics(platformConfig) {
|
|
23603
|
-
const industryMetrics = {
|
|
23604
|
-
// Pet spa industry (Goose) - high engagement, positive feedback
|
|
23605
|
-
"ACME Pet Spa": {
|
|
23606
|
-
totalSent: 1200,
|
|
23607
|
-
// Much higher - realistic email volume
|
|
23608
|
-
responseRate: 44,
|
|
23609
|
-
// 44% response rate
|
|
23610
|
-
targetNPS: 42,
|
|
23611
|
-
// Match the all-time NPS score (42)
|
|
23612
|
-
promoterRate: 0.78
|
|
23613
|
-
// 78% promoters
|
|
23614
|
-
},
|
|
23615
|
-
// Equipment rental (Renterra) - moderate engagement
|
|
23616
|
-
"Tomer's Rentals": {
|
|
23617
|
-
totalSent: 950,
|
|
23618
|
-
responseRate: 37,
|
|
23619
|
-
// 37% response rate
|
|
23620
|
-
targetNPS: 55,
|
|
23621
|
-
// Good NPS for B2B
|
|
23622
|
-
promoterRate: 0.72
|
|
23623
|
-
// 72% promoters
|
|
23624
|
-
},
|
|
23625
|
-
// Default platform
|
|
23626
|
-
default: {
|
|
23627
|
-
totalSent: 1050,
|
|
23628
|
-
responseRate: 40,
|
|
23629
|
-
// 40% response rate
|
|
23630
|
-
targetNPS: 60,
|
|
23631
|
-
// Good overall NPS
|
|
23632
|
-
promoterRate: 0.75
|
|
23633
|
-
// 75% promoters
|
|
23634
|
-
}
|
|
23635
|
-
};
|
|
23636
|
-
const baseKey = platformConfig.name in industryMetrics ? platformConfig.name : "default";
|
|
23637
|
-
return industryMetrics[baseKey];
|
|
23638
|
-
}
|
|
23639
23724
|
function calculateNPSBreakdown(totalResponses, targetNPS) {
|
|
23640
23725
|
if (totalResponses === 0) {
|
|
23641
23726
|
return { promoterCount: 0, detractorCount: 0, passiveCount: 0 };
|
|
@@ -23666,7 +23751,8 @@ function calculateNPSBreakdown(totalResponses, targetNPS) {
|
|
|
23666
23751
|
};
|
|
23667
23752
|
}
|
|
23668
23753
|
const mockStore = {
|
|
23669
|
-
reputationConfig: null
|
|
23754
|
+
reputationConfig: null,
|
|
23755
|
+
partnerLocations: null
|
|
23670
23756
|
};
|
|
23671
23757
|
const reputationHandlers = [
|
|
23672
23758
|
// External OAuth - GBP Connection
|
|
@@ -23681,14 +23767,51 @@ const reputationHandlers = [
|
|
|
23681
23767
|
// Partner Locations
|
|
23682
23768
|
http.get(`${HOSTNAME}/api/partner-locations`, () => {
|
|
23683
23769
|
const platformData = getSandboxDataForPlatform(currentSandboxPlatformId);
|
|
23770
|
+
const locations = mockStore.partnerLocations || platformData.reputationData?.partnerLocations || [];
|
|
23684
23771
|
return HttpResponse.json({
|
|
23685
23772
|
success: true,
|
|
23686
23773
|
message: "Success (Sandbox)",
|
|
23687
23774
|
data: {
|
|
23688
|
-
locations
|
|
23775
|
+
locations
|
|
23689
23776
|
}
|
|
23690
23777
|
});
|
|
23691
23778
|
}),
|
|
23779
|
+
// Update Partner Location (for Google Business Profile mapping)
|
|
23780
|
+
http.put(
|
|
23781
|
+
`${HOSTNAME}/api/partner-locations/:id`,
|
|
23782
|
+
async ({ request, params }) => {
|
|
23783
|
+
const locationId = params.id;
|
|
23784
|
+
const body = await request.json();
|
|
23785
|
+
const platformData = getSandboxDataForPlatform(currentSandboxPlatformId);
|
|
23786
|
+
if (!mockStore.partnerLocations) {
|
|
23787
|
+
mockStore.partnerLocations = [
|
|
23788
|
+
...platformData.reputationData?.partnerLocations || []
|
|
23789
|
+
];
|
|
23790
|
+
}
|
|
23791
|
+
const locationIndex = mockStore.partnerLocations.findIndex(
|
|
23792
|
+
(loc) => loc.externalId === locationId
|
|
23793
|
+
);
|
|
23794
|
+
if (locationIndex === -1) {
|
|
23795
|
+
return HttpResponse.json(
|
|
23796
|
+
{
|
|
23797
|
+
success: false,
|
|
23798
|
+
message: `Partner location with ID ${locationId} not found (Sandbox)`
|
|
23799
|
+
},
|
|
23800
|
+
{ status: 404 }
|
|
23801
|
+
);
|
|
23802
|
+
}
|
|
23803
|
+
mockStore.partnerLocations[locationIndex] = {
|
|
23804
|
+
...mockStore.partnerLocations[locationIndex],
|
|
23805
|
+
googleBusinessProfilePlaceId: body.googleBusinessProfilePlaceId || null,
|
|
23806
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
23807
|
+
};
|
|
23808
|
+
return HttpResponse.json({
|
|
23809
|
+
success: true,
|
|
23810
|
+
message: "Partner location updated successfully (Sandbox)",
|
|
23811
|
+
data: mockStore.partnerLocations[locationIndex]
|
|
23812
|
+
});
|
|
23813
|
+
}
|
|
23814
|
+
),
|
|
23692
23815
|
// Channel Senders
|
|
23693
23816
|
http.get(`${HOSTNAME}/api/channel/senders`, () => {
|
|
23694
23817
|
const platformData = getSandboxDataForPlatform(currentSandboxPlatformId);
|
|
@@ -23714,10 +23837,19 @@ const reputationHandlers = [
|
|
|
23714
23837
|
// Reputation Config
|
|
23715
23838
|
http.get(`${HOSTNAME}/api/reputation/config`, () => {
|
|
23716
23839
|
if (mockStore.reputationConfig) {
|
|
23840
|
+
const currentLocations = mockStore.partnerLocations || getSandboxDataForPlatform(currentSandboxPlatformId).reputationData?.partnerLocations || [];
|
|
23841
|
+
const locationMappings = {};
|
|
23842
|
+
currentLocations.forEach((location2) => {
|
|
23843
|
+
locationMappings[location2.externalId] = location2.googleBusinessProfilePlaceId || null;
|
|
23844
|
+
});
|
|
23717
23845
|
const response2 = {
|
|
23718
23846
|
success: true,
|
|
23719
23847
|
message: "Success (Sandbox)",
|
|
23720
|
-
data:
|
|
23848
|
+
data: {
|
|
23849
|
+
...mockStore.reputationConfig,
|
|
23850
|
+
locationMappings
|
|
23851
|
+
// Always return current mappings from partner locations
|
|
23852
|
+
}
|
|
23721
23853
|
};
|
|
23722
23854
|
return HttpResponse.json(response2);
|
|
23723
23855
|
}
|
|
@@ -23737,7 +23869,7 @@ const reputationHandlers = [
|
|
|
23737
23869
|
if (currentConfig) {
|
|
23738
23870
|
const updatedConfig = {
|
|
23739
23871
|
...currentConfig,
|
|
23740
|
-
|
|
23872
|
+
// Note: locationMappings are now stored in partner_locations table, not here
|
|
23741
23873
|
emailChannelSenderId: body.emailChannelSenderId,
|
|
23742
23874
|
smsChannelSenderId: body.smsChannelSenderId,
|
|
23743
23875
|
// Preserve existing completedOnboardingAt if not provided in update
|
|
@@ -23746,10 +23878,19 @@ const reputationHandlers = [
|
|
|
23746
23878
|
};
|
|
23747
23879
|
mockStore.reputationConfig = updatedConfig;
|
|
23748
23880
|
}
|
|
23881
|
+
const currentLocations = mockStore.partnerLocations || getSandboxDataForPlatform(currentSandboxPlatformId).reputationData?.partnerLocations || [];
|
|
23882
|
+
const locationMappings = {};
|
|
23883
|
+
currentLocations.forEach((location2) => {
|
|
23884
|
+
locationMappings[location2.externalId] = location2.googleBusinessProfilePlaceId || null;
|
|
23885
|
+
});
|
|
23749
23886
|
const response = {
|
|
23750
23887
|
success: true,
|
|
23751
23888
|
message: "Configuration updated successfully (Sandbox)",
|
|
23752
|
-
data:
|
|
23889
|
+
data: {
|
|
23890
|
+
...mockStore.reputationConfig,
|
|
23891
|
+
locationMappings
|
|
23892
|
+
// Include current mappings from partner locations
|
|
23893
|
+
}
|
|
23753
23894
|
};
|
|
23754
23895
|
return HttpResponse.json(response);
|
|
23755
23896
|
}),
|
|
@@ -23899,14 +24040,11 @@ const reputationHandlers = [
|
|
|
23899
24040
|
const startDate = url.searchParams.get("start_date");
|
|
23900
24041
|
const endDate = url.searchParams.get("end_date");
|
|
23901
24042
|
const locationIds = url.searchParams.get("location_ids");
|
|
23902
|
-
const includeComparison = url.searchParams.get("include_comparison") === "true";
|
|
23903
24043
|
const platformData = getSandboxDataForPlatform(currentSandboxPlatformId);
|
|
23904
24044
|
const queryParams = {
|
|
23905
|
-
platform: "google_business_profile",
|
|
23906
24045
|
start_date: startDate || void 0,
|
|
23907
24046
|
end_date: endDate || void 0,
|
|
23908
|
-
location_ids: locationIds ? locationIds.split(",").filter(Boolean) : void 0
|
|
23909
|
-
include_comparison: includeComparison
|
|
24047
|
+
location_ids: locationIds ? locationIds.split(",").filter(Boolean) : void 0
|
|
23910
24048
|
};
|
|
23911
24049
|
const analyticsData = generateReviewAnalyticsData({
|
|
23912
24050
|
platformData,
|
|
@@ -23954,11 +24092,8 @@ const reputationHandlers = [
|
|
|
23954
24092
|
const url = new URL(request.url);
|
|
23955
24093
|
const startDate = url.searchParams.get("start_date");
|
|
23956
24094
|
const endDate = url.searchParams.get("end_date");
|
|
23957
|
-
const includeComparison = url.searchParams.get("include_comparison") === "true";
|
|
23958
24095
|
const platformData = getSandboxDataForPlatform(currentSandboxPlatformId);
|
|
23959
|
-
const queryParams = {
|
|
23960
|
-
include_comparison: includeComparison
|
|
23961
|
-
};
|
|
24096
|
+
const queryParams = {};
|
|
23962
24097
|
if (startDate) {
|
|
23963
24098
|
queryParams.start_date = startDate;
|
|
23964
24099
|
}
|
|
@@ -24028,6 +24163,198 @@ const reputationHandlers = [
|
|
|
24028
24163
|
message: "Success (Sandbox)",
|
|
24029
24164
|
data: auditLogResponse
|
|
24030
24165
|
});
|
|
24166
|
+
}),
|
|
24167
|
+
// Reviews Data Availability
|
|
24168
|
+
http.get(`${HOSTNAME}/api/reviews/data-availability`, () => {
|
|
24169
|
+
const now = /* @__PURE__ */ new Date();
|
|
24170
|
+
const earliestDate = new Date(now);
|
|
24171
|
+
earliestDate.setDate(earliestDate.getDate() - 100);
|
|
24172
|
+
return HttpResponse.json({
|
|
24173
|
+
success: true,
|
|
24174
|
+
message: "Success (Sandbox)",
|
|
24175
|
+
data: {
|
|
24176
|
+
dataAvailableFrom: earliestDate.toISOString()
|
|
24177
|
+
}
|
|
24178
|
+
});
|
|
24179
|
+
}),
|
|
24180
|
+
// Feedback Data Availability
|
|
24181
|
+
http.get(`${HOSTNAME}/api/feedback/data-availability`, () => {
|
|
24182
|
+
const now = /* @__PURE__ */ new Date();
|
|
24183
|
+
const earliestDate = new Date(now);
|
|
24184
|
+
earliestDate.setDate(earliestDate.getDate() - 100);
|
|
24185
|
+
return HttpResponse.json({
|
|
24186
|
+
success: true,
|
|
24187
|
+
message: "Success (Sandbox)",
|
|
24188
|
+
data: {
|
|
24189
|
+
dataAvailableFrom: earliestDate.toISOString()
|
|
24190
|
+
}
|
|
24191
|
+
});
|
|
24192
|
+
}),
|
|
24193
|
+
// Reputation Data Availability
|
|
24194
|
+
http.get(`${HOSTNAME}/api/reputation/data-availability`, () => {
|
|
24195
|
+
const now = /* @__PURE__ */ new Date();
|
|
24196
|
+
const earliestDate = new Date(now);
|
|
24197
|
+
earliestDate.setDate(earliestDate.getDate() - 100);
|
|
24198
|
+
return HttpResponse.json({
|
|
24199
|
+
success: true,
|
|
24200
|
+
message: "Success (Sandbox)",
|
|
24201
|
+
data: {
|
|
24202
|
+
dataAvailableFrom: earliestDate.toISOString()
|
|
24203
|
+
}
|
|
24204
|
+
});
|
|
24205
|
+
}),
|
|
24206
|
+
// Reputation Automation Template
|
|
24207
|
+
http.get(`${HOSTNAME}/api/reputation/reputation-template`, () => {
|
|
24208
|
+
const platformData = getSandboxDataForPlatform(currentSandboxPlatformId);
|
|
24209
|
+
const businessName = platformData.businessesMe?.name || "Our Business";
|
|
24210
|
+
const primaryColor = platformData.businessesMe?.branding?.primaryColor || "#2563eb";
|
|
24211
|
+
const feedbackHtml = `<!DOCTYPE html>
|
|
24212
|
+
<html>
|
|
24213
|
+
<head>
|
|
24214
|
+
<meta charset="UTF-8">
|
|
24215
|
+
<title>Feedback Request</title>
|
|
24216
|
+
<style>
|
|
24217
|
+
body { font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto; padding: 20px; background: #f5f5f5; }
|
|
24218
|
+
.header { background: ${primaryColor}; color: white; padding: 30px 20px; text-align: center; border-radius: 8px 8px 0 0; }
|
|
24219
|
+
.content { background: white; padding: 30px 20px; border-radius: 0 0 8px 8px; }
|
|
24220
|
+
.button { background: ${primaryColor}; color: white; padding: 14px 28px; text-decoration: none; border-radius: 6px; display: inline-block; margin: 20px 0; font-weight: 600; }
|
|
24221
|
+
.rating-scale { display: flex; gap: 8px; justify-content: center; margin: 20px 0; }
|
|
24222
|
+
.rating-box { border: 2px solid #ddd; padding: 12px 16px; border-radius: 4px; font-weight: bold; }
|
|
24223
|
+
</style>
|
|
24224
|
+
</head>
|
|
24225
|
+
<body>
|
|
24226
|
+
<div class="header">
|
|
24227
|
+
<h1>💬 How was your experience?</h1>
|
|
24228
|
+
</div>
|
|
24229
|
+
<div class="content">
|
|
24230
|
+
<p>Hi there! 👋</p>
|
|
24231
|
+
<p>Thank you for choosing <strong>${businessName}</strong>! We hope you had a great experience with us.</p>
|
|
24232
|
+
<p>We'd love to hear your honest feedback. On a scale of 1-10, how would you rate your experience?</p>
|
|
24233
|
+
<div class="rating-scale">
|
|
24234
|
+
<div class="rating-box">1</div>
|
|
24235
|
+
<div class="rating-box">...</div>
|
|
24236
|
+
<div class="rating-box">10</div>
|
|
24237
|
+
</div>
|
|
24238
|
+
<a href="{{feedback_url}}" class="button">Share Your Feedback</a>
|
|
24239
|
+
<p>Your response helps us improve and serve you better.</p>
|
|
24240
|
+
<p>Thank you for your time!</p>
|
|
24241
|
+
<p>Best regards,<br>The ${businessName} Team</p>
|
|
24242
|
+
</div>
|
|
24243
|
+
</body>
|
|
24244
|
+
</html>`;
|
|
24245
|
+
const googleReviewHtml = `<!DOCTYPE html>
|
|
24246
|
+
<html>
|
|
24247
|
+
<head>
|
|
24248
|
+
<meta charset="UTF-8">
|
|
24249
|
+
<title>Review Request</title>
|
|
24250
|
+
<style>
|
|
24251
|
+
body { font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto; padding: 20px; background: #f5f5f5; }
|
|
24252
|
+
.header { background: ${primaryColor}; color: white; padding: 30px 20px; text-align: center; border-radius: 8px 8px 0 0; }
|
|
24253
|
+
.content { background: white; padding: 30px 20px; border-radius: 0 0 8px 8px; }
|
|
24254
|
+
.button { background: ${primaryColor}; color: white; padding: 14px 28px; text-decoration: none; border-radius: 6px; display: inline-block; margin: 20px 0; font-weight: 600; }
|
|
24255
|
+
.stars { color: #fbbf24; font-size: 24px; }
|
|
24256
|
+
</style>
|
|
24257
|
+
</head>
|
|
24258
|
+
<body>
|
|
24259
|
+
<div class="header">
|
|
24260
|
+
<h1>⭐ We'd love your Google review!</h1>
|
|
24261
|
+
</div>
|
|
24262
|
+
<div class="content">
|
|
24263
|
+
<p>Hi there! 👋</p>
|
|
24264
|
+
<p>Thank you so much for your positive feedback about <strong>${businessName}</strong>!</p>
|
|
24265
|
+
<p>Since you had a great experience, we'd be incredibly grateful if you could share your thoughts on Google. It only takes a moment and helps other customers discover us.</p>
|
|
24266
|
+
<div class="stars">⭐ ⭐ ⭐ ⭐ ⭐</div>
|
|
24267
|
+
<a href="{{external_review_url}}" class="button">Leave a Google Review</a>
|
|
24268
|
+
<p>Your support means the world to us and helps our business grow!</p>
|
|
24269
|
+
<p>Thank you so much!</p>
|
|
24270
|
+
<p>Warmly,<br>The ${businessName} Team</p>
|
|
24271
|
+
</div>
|
|
24272
|
+
</body>
|
|
24273
|
+
</html>`;
|
|
24274
|
+
const templates = [
|
|
24275
|
+
{
|
|
24276
|
+
id: "0199110d-eac1-7211-aeb3-1d9d9b60e77b",
|
|
24277
|
+
name: "Private Feedback Request",
|
|
24278
|
+
version: "1.0.0",
|
|
24279
|
+
status: "active",
|
|
24280
|
+
feature: "reputation",
|
|
24281
|
+
visibility: "visible",
|
|
24282
|
+
contents: {
|
|
24283
|
+
name: "reputation-feedback-request",
|
|
24284
|
+
description: "Request private direct feedback from customers",
|
|
24285
|
+
feature: "reputation",
|
|
24286
|
+
triggerMetadata: {
|
|
24287
|
+
cooldownSeconds: 86400,
|
|
24288
|
+
// 1 day after purchase
|
|
24289
|
+
eventFilter: {
|
|
24290
|
+
segment_id: "all_customers"
|
|
24291
|
+
}
|
|
24292
|
+
},
|
|
24293
|
+
actionData: [
|
|
24294
|
+
{
|
|
24295
|
+
type: "send_communication",
|
|
24296
|
+
index: 0,
|
|
24297
|
+
subject: `How was your experience with ${businessName}?`,
|
|
24298
|
+
previewText: "We'd love your honest feedback",
|
|
24299
|
+
compiledHtml: feedbackHtml
|
|
24300
|
+
}
|
|
24301
|
+
]
|
|
24302
|
+
}
|
|
24303
|
+
},
|
|
24304
|
+
{
|
|
24305
|
+
id: "0199110d-eac1-7211-aeb3-1d9d9b60e77c",
|
|
24306
|
+
name: "Google Review Request",
|
|
24307
|
+
version: "1.0.0",
|
|
24308
|
+
status: "active",
|
|
24309
|
+
feature: "reputation",
|
|
24310
|
+
visibility: "visible",
|
|
24311
|
+
contents: {
|
|
24312
|
+
name: "reputation-google-review-request",
|
|
24313
|
+
description: "Request Google review from satisfied customers (9-10 rating)",
|
|
24314
|
+
feature: "reputation",
|
|
24315
|
+
triggerMetadata: {
|
|
24316
|
+
cooldownSeconds: 0,
|
|
24317
|
+
// Sent immediately after positive feedback
|
|
24318
|
+
eventFilter: {
|
|
24319
|
+
segment_id: "satisfied_customers"
|
|
24320
|
+
}
|
|
24321
|
+
},
|
|
24322
|
+
actionData: [
|
|
24323
|
+
{
|
|
24324
|
+
type: "send_communication",
|
|
24325
|
+
index: 0,
|
|
24326
|
+
subject: `Would you leave us a Google review? - ${businessName}`,
|
|
24327
|
+
previewText: "Share your positive experience with others",
|
|
24328
|
+
compiledHtml: googleReviewHtml
|
|
24329
|
+
}
|
|
24330
|
+
]
|
|
24331
|
+
}
|
|
24332
|
+
}
|
|
24333
|
+
];
|
|
24334
|
+
const sortedTemplates = templates.sort(
|
|
24335
|
+
(a2, b2) => a2.name.localeCompare(b2.name, void 0, { sensitivity: "base" })
|
|
24336
|
+
);
|
|
24337
|
+
return HttpResponse.json({
|
|
24338
|
+
success: true,
|
|
24339
|
+
message: "Success (Sandbox)",
|
|
24340
|
+
data: sortedTemplates
|
|
24341
|
+
});
|
|
24342
|
+
}),
|
|
24343
|
+
// Activate Automations
|
|
24344
|
+
http.post(`${HOSTNAME}/api/reputation/activate-automations`, () => {
|
|
24345
|
+
return HttpResponse.json({
|
|
24346
|
+
success: true,
|
|
24347
|
+
message: "Success (Sandbox)",
|
|
24348
|
+
data: {}
|
|
24349
|
+
});
|
|
24350
|
+
}),
|
|
24351
|
+
// Sync Google Reviews
|
|
24352
|
+
http.post(`${HOSTNAME}/api/reputation/sync-google-reviews`, () => {
|
|
24353
|
+
return HttpResponse.json({
|
|
24354
|
+
success: true,
|
|
24355
|
+
message: "Success (Sandbox)",
|
|
24356
|
+
data: {}
|
|
24357
|
+
});
|
|
24031
24358
|
})
|
|
24032
24359
|
];
|
|
24033
24360
|
const getHandlersByFeatures = (features) => {
|