From 66c050b0a8641458dc800880df44c45519616803 Mon Sep 17 00:00:00 2001 From: Abdalla Dimes Date: Wed, 13 Sep 2023 17:49:05 +0300 Subject: [PATCH 1/4] feat: Implemented AI state seeder as a service provider --- control/content/app.services.js | 202 +++++++++++++++++- .../controllers/content.home.controller.js | 27 ++- control/content/index.html | 1 + 3 files changed, 224 insertions(+), 6 deletions(-) diff --git a/control/content/app.services.js b/control/content/app.services.js index 9128665..b6a9e34 100644 --- a/control/content/app.services.js +++ b/control/content/app.services.js @@ -164,5 +164,205 @@ return deferred.promise; } }; - }]) + }]).factory('StateSeeder', ['Context', 'LoyaltyAPI', '$rootScope', 'Buildfire' ,function(Context, LoyaltyAPI, $rootScope, Buildfire) { + let itemsList; + let currentUser; + let currentContext; + let stateSeederInstance; + let jsonTemplate = { + items: [ + { + title: "", + pointsToRedeem: 0, + description: "", + listImage: "", + pointsPerItem: 0 + }, + ], + }; + let handleAIReq = function(err, data) { + if ( + err || + !data || + typeof data !== "object" || + !Object.keys(data).length || !data.data || !data.data.items || !data.data.items.length + ) { + return buildfire.dialog.toast({ + message: "Bad AI request, please try changing your request.", + type: "danger", + }); + } + + itemsList = data.data.items; + //Check image URLs + let items = itemsList.map(item => { + return new Promise((resolve, reject) => { + elimanateNotFoundImages(item.listImage).then(res => { + if (res.isValid) { + item.listImage = res.newURL; + resolve(item); + } else { + reject('image URL not valid'); + } + }) + }) + }) + + // Check image URLs + Promise.allSettled(items).then(results => { + itemsList = []; + results.forEach(res => { + if(res.status == 'fulfilled') { + const item = res.value; + if (item) { + itemsList.push(item); + } + } + }) + if (!itemsList.length) { + stateSeederInstance.requestResult?.complete(); + return buildfire.dialog.toast({ + message: "Bad AI request, please try changing your request.", + type: "danger", + }); + } + + // reset old data + deleteAll().then(() => { + // save new data + itemsList.forEach((item, i) => { + item = _applyDefaults(item); + LoyaltyAPI.addReward(item).then(result => { + console.info('Saved data result: ', result); + item.deepLinkUrl = Buildfire.deeplink.createLink({id: result._id}); + item = Object.assign(item, result); + }).catch(err => { + console.error('Error while saving data : ', err); + }).finally(() => { + if (i == (itemsList.length - 1)) { + $rootScope.reloadRewards = true; + buildfire.messaging.sendMessageToWidget({ + type: 'refresh' + }); + } + }) + }) + stateSeederInstance.requestResult?.complete(); + }).catch(err => console.warn('old data delete error', err)); + }) + } + + // UTILITIES + let _applyDefaults = function(item) { + if (item.title) { + return { + title: item.title, + pointsToRedeem: item.pointsToRedeem || 10, + description: item.description || "", + listImage: item.listImage || "", + pointsPerItem: item.pointsPerItem || 10, + appId: currentContext.appId, + loyaltyUnqiueId: currentContext.instanceId, + userToken: currentUser && currentUser.userToken, + auth: currentUser && currentUser.auth, + } + } + return null + } + + let elimanateNotFoundImages = function(url) { + const optimisedURL = url.replace('1080x720', '100x100'); + return new Promise((resolve) => { + if (url.includes("http")){ + const xhr = new XMLHttpRequest(); + xhr.open("GET", optimisedURL); + xhr.onerror = (error) => { + console.warn('provided URL is not a valid image', error); + resolve({isValid: false, newURL: null}); + } + xhr.onload = () => { + if (xhr.responseURL.includes('source-404') || xhr.status == 404) { + return resolve({isValid: false ,newURL: null}); + } else { + return resolve({isValid: true, newURL: xhr.responseURL.replace('h=100', 'h=720').replace('w=100', 'w=1080') }); + } + }; + xhr.send(); + } else resolve(false); + }); + }; + + let deleteAll = function() { + const data = { + userToken: currentUser.userToken, + auth: currentUser.auth, + appId: currentContext.appId + }; + return new Promise(resolve => { + if (stateSeederInstance.requestResult.resetData){ + LoyaltyAPI.getRewards(currentContext.instanceId).then(items => { + const promises = items.map((item) => deleteItem(item._id, data)); + resolve(Promise.all(promises)); + }).catch(err => console.warn('old data get error', err)); + } + else { + resolve(); + } + }) + } + + let deleteItem = function(itemId, data) { + return new Promise((resolve, reject) => { + LoyaltyAPI.removeReward(itemId, data).then(res => { + Deeplink.deleteById(itemId); + resolve(res); + }).catch(err => { + if (err) reject(err); + }) + }); + } + + let getCurrentUser = function() { + return new Promise(resolve => { + buildfire.auth.getCurrentUser(function (err, user) { + if (user && user._cpUser) { + resolve(user._cpUser); + } + }); + }) + } + + let getUserAndContext = function() { + getCurrentUser().then((user) => { + currentUser = user; + Context.getContext().then(context => { + currentContext = context; + }) + }) + } + return { + initStateSeeder: function() { + getUserAndContext(); + stateSeederInstance = new buildfire.components.aiStateSeeder({ + generateOptions: { + userMessage: `Generate a sample redeemable items for a new [business-type].`, + maxRecords: 5, + systemMessage: + 'listImage URL related to title and the list type use source.unsplash.com for image URL, URL should not have premium_photo or source.unsplash.com/random, cost to redeem which is a number greater than zero and less than 100, return description as HTML.', + jsonTemplate: jsonTemplate, + callback: handleAIReq.bind(this), + hintText: 'Replace values between brackets to match your requirements.', + }, + importOptions: { + jsonTemplate: jsonTemplate, + sampleCSV: "Hotel Voucher, 50, Redeem this voucher for a one-night stay at a luxurious hotel of your choice. Experience top-notch service and enjoy a comfortable stay, 15, https://source.unsplash.com/featured/?hotel\nFlight Upgrade, 30, Upgrade your economy class ticket to business class and enjoy a more luxurious and comfortable flight experience, 10, https://source.unsplash.com/featured/?flight\nCity Tour, 20, Explore the city with a guided tour that covers all the major attractions and landmarks. Get to know the history and culture of the city, 5, https://source.unsplash.com/featured/?city\nAdventure Activity, 80, Embark on an adrenaline-pumping adventure activity such as bungee jumping, skydiving, or white-water rafting. Experience the thrill of a lifetime!, 55, https://source.unsplash.com/featured/?adventure", + maxRecords: 15, + hintText: 'Please enter values in the following order: Item title, Cost to redeem, Item description, Points earned, Image URL', + systemMessage: ``, + callback: handleAIReq.bind(this), + }, + }).smartShowEmptyState(); + }, + } + }]) })(window.angular, window.buildfire); \ No newline at end of file diff --git a/control/content/controllers/content.home.controller.js b/control/content/controllers/content.home.controller.js index f089163..b0b0ae1 100644 --- a/control/content/controllers/content.home.controller.js +++ b/control/content/controllers/content.home.controller.js @@ -3,8 +3,8 @@ (function (angular, buildfire) { angular .module('loyaltyPluginContent') - .controller('ContentHomeCtrl', ['$scope', 'Buildfire', 'LoyaltyAPI', 'STATUS_CODE', '$modal', 'RewardCache', '$location', '$timeout', 'context', 'TAG_NAMES', - function ($scope, Buildfire, LoyaltyAPI, STATUS_CODE, $modal, RewardCache, $location, $timeout, context, TAG_NAMES) { + .controller('ContentHomeCtrl', ['$scope', 'Buildfire', 'LoyaltyAPI', 'STATUS_CODE', '$modal', 'RewardCache', '$location', '$timeout', 'context', 'TAG_NAMES', 'StateSeeder', '$rootScope', + function ($scope, Buildfire, LoyaltyAPI, STATUS_CODE, $modal, RewardCache, $location, $timeout, context, TAG_NAMES, StateSeeder, $rootScope) { var ContentHome = this; var _data = { redemptionPasscode: '12345', @@ -24,7 +24,7 @@ }, image: [] }; - + let stateSeeder; //Scroll current view to top when page loaded. buildfire.navigation.scrollTop(); ContentHome.title = ""; @@ -127,8 +127,12 @@ }; ContentHome.successloyaltyRewards = function (result) { ContentHome.loyaltyRewards = result; - if (!ContentHome.loyaltyRewards) - ContentHome.loyaltyRewards = []; + if (!ContentHome.loyaltyRewards) { + ContentHome.loyaltyRewards = []; + $rootScope.showEmptyState = true; + } else { + $rootScope.showEmptyState = false; + } ContentHome.loyaltyRewardsCloned = ContentHome.loyaltyRewards ContentHome.addDeepLinks(result); console.info('init success result loyaltyRewards:', result); @@ -141,12 +145,14 @@ } }; LoyaltyAPI.getRewards(context.instanceId).then(ContentHome.successloyaltyRewards, ContentHome.errorloyaltyRewards); + ContentHome.loyaltyRewards = []; LoyaltyAPI.getApplication(context.instanceId).then(ContentHome.success, ContentHome.error); buildfire.auth.getCurrentUser(function (err, user) { console.log("!!!!!!!!!!User!!!!!!!!!!!!", user); if (user && user._cpUser) { ContentHome.currentLoggedInUser = user._cpUser; if (!$scope.$$phase) $scope.$digest(); + $rootScope.reloadRewards = false; } }); }; @@ -354,6 +360,17 @@ return ContentHome.data; }, saveDataWithDelay, true); + $rootScope.$watch('reloadRewards', function(newValue, oldValue) { + if (newValue) { + ContentHome.init(); + } + }) + $rootScope.$watch('showEmptyState', function(newValue, oldValue) { + if ((typeof newValue === 'undefined' || newValue == true) && !stateSeeder) { + stateSeeder = StateSeeder.initStateSeeder(); + } + }); + ContentHome.init(); }]); diff --git a/control/content/index.html b/control/content/index.html index 132fff3..19d6925 100644 --- a/control/content/index.html +++ b/control/content/index.html @@ -19,6 +19,7 @@ + From adb9a8a864485b0752f40ea1a24257069e654bdb Mon Sep 17 00:00:00 2001 From: Abdalla Dimes Date: Wed, 13 Sep 2023 23:23:08 +0300 Subject: [PATCH 2/4] fix: Applied code review changes --- control/content/app.services.js | 274 ++++++++++-------- control/content/assets/css/style.css | 6 +- .../controllers/content.home.controller.js | 1 + .../controllers/content.reward.controller.js | 12 +- 4 files changed, 169 insertions(+), 124 deletions(-) diff --git a/control/content/app.services.js b/control/content/app.services.js index b6a9e34..7d0bcd5 100644 --- a/control/content/app.services.js +++ b/control/content/app.services.js @@ -164,7 +164,7 @@ return deferred.promise; } }; - }]).factory('StateSeeder', ['Context', 'LoyaltyAPI', '$rootScope', 'Buildfire' ,function(Context, LoyaltyAPI, $rootScope, Buildfire) { + }]).factory('StateSeeder', ['Context', 'LoyaltyAPI', '$rootScope', 'Buildfire', '$timeout' ,function(Context, LoyaltyAPI, $rootScope, Buildfire, $timeout) { let itemsList; let currentUser; let currentContext; @@ -176,23 +176,22 @@ pointsToRedeem: 0, description: "", listImage: "", - pointsPerItem: 0 }, ], }; - let handleAIReq = function(err, data) { - if ( - err || - !data || - typeof data !== "object" || - !Object.keys(data).length || !data.data || !data.data.items || !data.data.items.length - ) { - return buildfire.dialog.toast({ - message: "Bad AI request, please try changing your request.", - type: "danger", - }); - } - + let handleAIReq = function(err, data) { + if ( + err || + !data || + typeof data !== "object" || + !Object.keys(data).length || !data.data || !data.data.items || !data.data.items.length + ) { + return buildfire.dialog.toast({ + message: "Bad AI request, please try changing your request.", + type: "danger", + }); + } + getUserAndContext().then(() => { itemsList = data.data.items; //Check image URLs let items = itemsList.map(item => { @@ -220,7 +219,7 @@ } }) if (!itemsList.length) { - stateSeederInstance.requestResult?.complete(); + stateSeederInstance?.requestResult?.complete(); return buildfire.dialog.toast({ message: "Bad AI request, please try changing your request.", type: "danger", @@ -230,75 +229,98 @@ // reset old data deleteAll().then(() => { // save new data - itemsList.forEach((item, i) => { - item = _applyDefaults(item); - LoyaltyAPI.addReward(item).then(result => { - console.info('Saved data result: ', result); - item.deepLinkUrl = Buildfire.deeplink.createLink({id: result._id}); - item = Object.assign(item, result); - }).catch(err => { - console.error('Error while saving data : ', err); - }).finally(() => { - if (i == (itemsList.length - 1)) { - $rootScope.reloadRewards = true; - buildfire.messaging.sendMessageToWidget({ - type: 'refresh' - }); - } + let promises = itemsList.map((item, i) => { + return new Promise((resolve, reject) => { + itemsList[i] = _applyDefaults(item); + LoyaltyAPI.addReward(itemsList[i]).then(result => { + console.info('Saved data result: ', result); + itemsList[i].deepLinkUrl = Buildfire.deeplink.createLink({id: result._id}); + itemsList[i] = Object.assign(itemsList[i], result); + resolve(); + }) + .catch(err => { + console.error('Error while saving data : ', err); + resolve('Error while saving data : ', err); + }) + }) + }) + Promise.allSettled(promises).then(res => { + $timeout(() => { + $rootScope.reloadRewards = true; + buildfire.messaging.sendMessageToWidget({ + type: 'refresh' + }); + stateSeederInstance?.requestResult?.complete(); }) }) - stateSeederInstance.requestResult?.complete(); + stateSeederInstance?.requestResult?.complete(); }).catch(err => console.warn('old data delete error', err)); }) - } + }).catch(err => { + console.error(err); + stateSeederInstance?.requestResult?.complete(); + return buildfire.dialog.toast({ + message: "Bad AI request, please try changing your request.", + type: "danger", + }); + }) + } - // UTILITIES - let _applyDefaults = function(item) { - if (item.title) { - return { - title: item.title, - pointsToRedeem: item.pointsToRedeem || 10, - description: item.description || "", - listImage: item.listImage || "", - pointsPerItem: item.pointsPerItem || 10, - appId: currentContext.appId, - loyaltyUnqiueId: currentContext.instanceId, - userToken: currentUser && currentUser.userToken, - auth: currentUser && currentUser.auth, - } + // UTILITIES + let _applyDefaults = function(item) { + if (item.title) { + return { + title: item.title, + pointsToRedeem: checkPointtoRedeem(item.pointsToRedeem), + description: item.description || "", + listImage: item.listImage || "", + pointsPerItem: Math.ceil(checkPointtoRedeem(item.pointsToRedeem) * 0.1), + appId: currentContext.appId, + loyaltyUnqiueId: currentContext.instanceId, + userToken: currentUser && currentUser.userToken, + auth: currentUser && currentUser.auth, } - return null } + return null + } + + let checkPointtoRedeem = function(points) { + if (points && points > 0) { + return points; + } else { + return 100; + } + } - let elimanateNotFoundImages = function(url) { - const optimisedURL = url.replace('1080x720', '100x100'); - return new Promise((resolve) => { - if (url.includes("http")){ - const xhr = new XMLHttpRequest(); - xhr.open("GET", optimisedURL); - xhr.onerror = (error) => { - console.warn('provided URL is not a valid image', error); - resolve({isValid: false, newURL: null}); + let elimanateNotFoundImages = function(url) { + const optimisedURL = url.replace('1080x720', '100x100'); + return new Promise((resolve) => { + if (url.includes("http")){ + const xhr = new XMLHttpRequest(); + xhr.open("GET", optimisedURL); + xhr.onerror = (error) => { + console.warn('provided URL is not a valid image', error); + resolve({isValid: false, newURL: null}); + } + xhr.onload = () => { + if (xhr.responseURL.includes('source-404') || xhr.status == 404) { + return resolve({isValid: false ,newURL: null}); + } else { + return resolve({isValid: true, newURL: xhr.responseURL.replace('h=100', 'h=720').replace('w=100', 'w=1080') }); } - xhr.onload = () => { - if (xhr.responseURL.includes('source-404') || xhr.status == 404) { - return resolve({isValid: false ,newURL: null}); - } else { - return resolve({isValid: true, newURL: xhr.responseURL.replace('h=100', 'h=720').replace('w=100', 'w=1080') }); - } - }; - xhr.send(); - } else resolve(false); - }); - }; + }; + xhr.send(); + } else resolve(false); + }); + }; - let deleteAll = function() { - const data = { - userToken: currentUser.userToken, - auth: currentUser.auth, - appId: currentContext.appId - }; - return new Promise(resolve => { + let deleteAll = function() { + const data = { + userToken: currentUser.userToken, + auth: currentUser.auth, + appId: currentContext.appId + }; + return new Promise(resolve => { if (stateSeederInstance.requestResult.resetData){ LoyaltyAPI.getRewards(currentContext.instanceId).then(items => { const promises = items.map((item) => deleteItem(item._id, data)); @@ -309,60 +331,70 @@ resolve(); } }) - } - - let deleteItem = function(itemId, data) { - return new Promise((resolve, reject) => { - LoyaltyAPI.removeReward(itemId, data).then(res => { - Deeplink.deleteById(itemId); - resolve(res); - }).catch(err => { - if (err) reject(err); - }) - }); - } + } - let getCurrentUser = function() { - return new Promise(resolve => { - buildfire.auth.getCurrentUser(function (err, user) { - if (user && user._cpUser) { - resolve(user._cpUser); - } - }); + let deleteItem = function(itemId, data) { + return new Promise((resolve, reject) => { + LoyaltyAPI.removeReward(itemId, data).then(res => { + Deeplink.deleteById(itemId); + resolve(res); + }).catch(err => { + if (err) reject(err); }) - } + }); + } + + let getCurrentUser = function() { + return new Promise(resolve => { + buildfire.auth.getCurrentUser(function (err, user) { + if (user && user._cpUser) { + resolve(user._cpUser); + } + }); + }) + } - let getUserAndContext = function() { - getCurrentUser().then((user) => { - currentUser = user; + let getUserAndContext = function() { + let promises = [ + new Promise((resolve, reject) => { + getCurrentUser().then((user) => { + currentUser = user; + resolve(); + }).catch(err => reject(err)) + }), + new Promise((resolve, reject) => { Context.getContext().then(context => { currentContext = context; + resolve(); }) - }) - } + }).catch(err => reject(err)) + ] + return Promise.all(promises); + } + return { - initStateSeeder: function() { - getUserAndContext(); - stateSeederInstance = new buildfire.components.aiStateSeeder({ - generateOptions: { - userMessage: `Generate a sample redeemable items for a new [business-type].`, - maxRecords: 5, - systemMessage: - 'listImage URL related to title and the list type use source.unsplash.com for image URL, URL should not have premium_photo or source.unsplash.com/random, cost to redeem which is a number greater than zero and less than 100, return description as HTML.', - jsonTemplate: jsonTemplate, - callback: handleAIReq.bind(this), - hintText: 'Replace values between brackets to match your requirements.', - }, - importOptions: { - jsonTemplate: jsonTemplate, - sampleCSV: "Hotel Voucher, 50, Redeem this voucher for a one-night stay at a luxurious hotel of your choice. Experience top-notch service and enjoy a comfortable stay, 15, https://source.unsplash.com/featured/?hotel\nFlight Upgrade, 30, Upgrade your economy class ticket to business class and enjoy a more luxurious and comfortable flight experience, 10, https://source.unsplash.com/featured/?flight\nCity Tour, 20, Explore the city with a guided tour that covers all the major attractions and landmarks. Get to know the history and culture of the city, 5, https://source.unsplash.com/featured/?city\nAdventure Activity, 80, Embark on an adrenaline-pumping adventure activity such as bungee jumping, skydiving, or white-water rafting. Experience the thrill of a lifetime!, 55, https://source.unsplash.com/featured/?adventure", - maxRecords: 15, - hintText: 'Please enter values in the following order: Item title, Cost to redeem, Item description, Points earned, Image URL', - systemMessage: ``, - callback: handleAIReq.bind(this), - }, + initStateSeeder: function() { + getUserAndContext(); + stateSeederInstance = new buildfire.components.aiStateSeeder({ + generateOptions: { + userMessage: `Generate a sample of redeemable items for a new [business-type].`, + maxRecords: 5, + systemMessage: + 'listImage URL related to title and the list type. use source.unsplash.com for image URL, URL should not have premium_photo or source.unsplash.com/random, cost to redeem which is a number greater than zero and less than 100, return description as HTML.', + jsonTemplate: jsonTemplate, + callback: handleAIReq.bind(this), + hintText: 'Replace values between brackets to match your requirements.', + }, + importOptions: { + jsonTemplate: jsonTemplate, + sampleCSV: "Hotel Voucher, 50, Redeem this voucher for a one-night stay at a luxurious hotel of your choice, https://source.unsplash.com/featured/?hotel\nFlight Upgrade, 30, Upgrade your economy class ticket to business class, https://source.unsplash.com/featured/?flight\nCity Tour, 20, Explore the city with a guided tour that covers all the major attractions and landmarks, https://source.unsplash.com/featured/?city\nAdventure Activity, 80, Embark on an adrenaline-pumping adventure activity such as bungee jumping or skydiving, https://source.unsplash.com/featured/?adventure", + maxRecords: 15, + hintText: 'Each row sequentially starts with a Loyalty item title, Cost to redeem, Description, Image URL', + systemMessage: ``, + callback: handleAIReq.bind(this), + }, }).smartShowEmptyState(); - }, + }, } }]) })(window.angular, window.buildfire); \ No newline at end of file diff --git a/control/content/assets/css/style.css b/control/content/assets/css/style.css index 45116c0..9c1b953 100644 --- a/control/content/assets/css/style.css +++ b/control/content/assets/css/style.css @@ -186,4 +186,8 @@ i { background-color:white; bottom:0px; z-index:1; - } \ No newline at end of file + } + + p.ai-seeder-banner { + margin-bottom: 10px; +} \ No newline at end of file diff --git a/control/content/controllers/content.home.controller.js b/control/content/controllers/content.home.controller.js index b0b0ae1..7eb1a48 100644 --- a/control/content/controllers/content.home.controller.js +++ b/control/content/controllers/content.home.controller.js @@ -365,6 +365,7 @@ ContentHome.init(); } }) + $rootScope.$watch('showEmptyState', function(newValue, oldValue) { if ((typeof newValue === 'undefined' || newValue == true) && !stateSeeder) { stateSeeder = StateSeeder.initStateSeeder(); diff --git a/control/content/controllers/content.reward.controller.js b/control/content/controllers/content.reward.controller.js index d412fa3..40f7606 100644 --- a/control/content/controllers/content.reward.controller.js +++ b/control/content/controllers/content.reward.controller.js @@ -3,8 +3,8 @@ (function (angular, buildfire) { angular .module('loyaltyPluginContent') - .controller('ContentRewardCtrl', ['$scope', 'Buildfire', 'LoyaltyAPI', 'STATUS_CODE', '$location', '$routeParams', 'RewardCache', 'context', - function ($scope, Buildfire, LoyaltyAPI, STATUS_CODE, $location, $routeParams, RewardCache, context) { + .controller('ContentRewardCtrl', ['$scope', 'Buildfire', 'LoyaltyAPI', 'STATUS_CODE', '$location', '$routeParams', 'RewardCache', 'context', '$rootScope', + function ($scope, Buildfire, LoyaltyAPI, STATUS_CODE, $location, $routeParams, RewardCache, context, $rootScope) { var ContentReward = this; ContentReward.item = { title: "", @@ -339,6 +339,14 @@ * watch for changes in data and trigger the saveDataWithDelay function on change * */ + $rootScope.$watch('reloadRewards', function(newValue, oldValue) { + if (newValue) { + if ($location.$$path.includes("/reward")) { + $location.path("/"); + } + } + }) + $scope.$watch(function () { return ContentReward.item; }, saveDataWithDelay, true); From d7525f6a46076c2deec81abf9e5cbe6e7ce1c3c8 Mon Sep 17 00:00:00 2001 From: Abdalla Dimes Date: Mon, 18 Sep 2023 21:23:34 +0300 Subject: [PATCH 3/4] Fix: Applied QA round fixes --- control/content/app.services.js | 93 ++++++++++++++++++++++++++------- widget/assets/styles/style.css | 3 +- 2 files changed, 77 insertions(+), 19 deletions(-) diff --git a/control/content/app.services.js b/control/content/app.services.js index 7d0bcd5..09f15d4 100644 --- a/control/content/app.services.js +++ b/control/content/app.services.js @@ -166,10 +166,11 @@ }; }]).factory('StateSeeder', ['Context', 'LoyaltyAPI', '$rootScope', 'Buildfire', '$timeout' ,function(Context, LoyaltyAPI, $rootScope, Buildfire, $timeout) { let itemsList; + let orderedItems = []; let currentUser; let currentContext; let stateSeederInstance; - let jsonTemplate = { + let generateJSONTemplate = { items: [ { title: "", @@ -179,7 +180,18 @@ }, ], }; - let handleAIReq = function(err, data) { + let importJSONTemplate = { + items: [ + { + title: "", + pointsToRedeem: 0, + pointsPerItem: 0, + description: "", + listImage: "", + }, + ], + }; + let handleAIReq = function(isImport, err, data) { if ( err || !data || @@ -194,11 +206,13 @@ getUserAndContext().then(() => { itemsList = data.data.items; //Check image URLs - let items = itemsList.map(item => { + let items = itemsList.map((item, i) => { return new Promise((resolve, reject) => { elimanateNotFoundImages(item.listImage).then(res => { if (res.isValid) { item.listImage = res.newURL; + item.order = i; + orderedItems.push(item); resolve(item); } else { reject('image URL not valid'); @@ -221,7 +235,7 @@ if (!itemsList.length) { stateSeederInstance?.requestResult?.complete(); return buildfire.dialog.toast({ - message: "Bad AI request, please try changing your request.", + message: isImport ? "Each row must have a valid image URL." : "Bad AI request, please try changing your request.", type: "danger", }); } @@ -245,15 +259,46 @@ }) }) Promise.allSettled(promises).then(res => { - $timeout(() => { + if (isImport) { + let sortedItems = orderedItems.sort((a,b) => a.order - b.order); + let sortedIds =[]; + LoyaltyAPI.getRewards(currentContext.instanceId).then(results => { + sortedItems.forEach(item => { + if (results) { + results.forEach(result => { + if (item.title == result.title && item.listImage == result.listImage ) { + sortedIds.push(result._id); + } + }) + } + }) + const data = { + appId: currentContext.appId, + loyaltyUnqiueId: currentContext.instanceId, + userToken: currentUser && currentUser.userToken, + auth: currentUser && currentUser.auth, + loyaltyRewardIds: sortedIds + } + LoyaltyAPI.sortRewards(data).finally(() => { + $timeout(() => { + $rootScope.reloadRewards = true; + buildfire.messaging.sendMessageToWidget({ + type: 'refresh' + }); + stateSeederInstance?.requestResult?.complete(); + }) + }); + }); + } else { + $timeout(() => { $rootScope.reloadRewards = true; buildfire.messaging.sendMessageToWidget({ type: 'refresh' }); stateSeederInstance?.requestResult?.complete(); }) + } }) - stateSeederInstance?.requestResult?.complete(); }).catch(err => console.warn('old data delete error', err)); }) }).catch(err => { @@ -269,12 +314,13 @@ // UTILITIES let _applyDefaults = function(item) { if (item.title) { + const points = checkPoints(item.pointsToRedeem, item.pointsPerItem); return { title: item.title, - pointsToRedeem: checkPointtoRedeem(item.pointsToRedeem), + pointsToRedeem: points.pointsToRedeem, description: item.description || "", listImage: item.listImage || "", - pointsPerItem: Math.ceil(checkPointtoRedeem(item.pointsToRedeem) * 0.1), + pointsPerItem: points.pointsPerItem, appId: currentContext.appId, loyaltyUnqiueId: currentContext.instanceId, userToken: currentUser && currentUser.userToken, @@ -284,12 +330,23 @@ return null } - let checkPointtoRedeem = function(points) { - if (points && points > 0) { - return points; + let checkPoints = function(pointsToRedeem, pointsPerItem) { + let points = { + pointsToRedeem: 0, + pointsPerItem: 0, + }; + if (pointsToRedeem && pointsToRedeem > 0) { + points.pointsToRedeem = pointsToRedeem; + } else { + points.pointsToRedeem = 100; + } + + if (pointsPerItem && pointsPerItem > 0) { + points.pointsPerItem = pointsPerItem; } else { - return 100; + points.pointsPerItem = Math.ceil(points.pointsToRedeem * 0.1); } + return points; } let elimanateNotFoundImages = function(url) { @@ -381,17 +438,17 @@ maxRecords: 5, systemMessage: 'listImage URL related to title and the list type. use source.unsplash.com for image URL, URL should not have premium_photo or source.unsplash.com/random, cost to redeem which is a number greater than zero and less than 100, return description as HTML.', - jsonTemplate: jsonTemplate, - callback: handleAIReq.bind(this), + jsonTemplate: generateJSONTemplate, + callback: handleAIReq.bind(this, false), hintText: 'Replace values between brackets to match your requirements.', }, importOptions: { - jsonTemplate: jsonTemplate, - sampleCSV: "Hotel Voucher, 50, Redeem this voucher for a one-night stay at a luxurious hotel of your choice, https://source.unsplash.com/featured/?hotel\nFlight Upgrade, 30, Upgrade your economy class ticket to business class, https://source.unsplash.com/featured/?flight\nCity Tour, 20, Explore the city with a guided tour that covers all the major attractions and landmarks, https://source.unsplash.com/featured/?city\nAdventure Activity, 80, Embark on an adrenaline-pumping adventure activity such as bungee jumping or skydiving, https://source.unsplash.com/featured/?adventure", + jsonTemplate: importJSONTemplate, + sampleCSV: "Hotel Voucher, 50, 10, Redeem this voucher for a one-night stay at a luxurious hotel of your choice, https://source.unsplash.com/featured/?hotel\nFlight Upgrade, 30, 15, Upgrade your economy class ticket to business class, https://source.unsplash.com/featured/?flight\nCity Tour, 20, 5, Explore the city with a guided tour that covers all the major attractions and landmarks, https://source.unsplash.com/featured/?city\nAdventure Activity, 80, 30, Embark on an adrenaline-pumping adventure activity such as bungee jumping or skydiving, https://source.unsplash.com/featured/?adventure", maxRecords: 15, - hintText: 'Each row sequentially starts with a Loyalty item title, Cost to redeem, Description, Image URL', + hintText: 'Each row should start with a Loyalty item title, Cost to redeem, Points earned, Description, and Image URL', systemMessage: ``, - callback: handleAIReq.bind(this), + callback: handleAIReq.bind(this, true), }, }).smartShowEmptyState(); }, diff --git a/widget/assets/styles/style.css b/widget/assets/styles/style.css index 107eeb8..2726f55 100644 --- a/widget/assets/styles/style.css +++ b/widget/assets/styles/style.css @@ -130,9 +130,10 @@ display: inline-block } -.list-layout.layout1 .list-media-holder img{ +.layout1 .list-media-holder img{ width:100%; height:auto; + min-height: 12em; } /* List Items */ From fec118d436a64a27382e2d59a3c66518529192bb Mon Sep 17 00:00:00 2001 From: Abdalla Dimes Date: Wed, 11 Oct 2023 20:15:44 +0300 Subject: [PATCH 4/4] Fix: Fixed Image loading issue --- control/content/app.services.js | 49 +++++++++---------- control/content/assets/css/style.css | 4 -- .../controllers/content.home.controller.js | 16 ++++-- control/content/templates/home.html | 5 +- widget/app.js | 2 +- widget/assets/styles/style.css | 1 - widget/templates/Item_Details.html | 2 +- widget/templates/List_Layout_1.html | 2 +- widget/templates/List_Layout_2.html | 2 +- widget/templates/List_Layout_3.html | 2 +- 10 files changed, 43 insertions(+), 42 deletions(-) diff --git a/control/content/app.services.js b/control/content/app.services.js index 09f15d4..c197402 100644 --- a/control/content/app.services.js +++ b/control/content/app.services.js @@ -203,7 +203,21 @@ type: "danger", }); } - getUserAndContext().then(() => { + let initPromises = [ + new Promise((resolve, reject) => { + getCurrentUser().then((user) => { + currentUser = user; + resolve(); + }).catch(err => reject(err)) + }), + new Promise((resolve, reject) => { + Context.getContext().then(context => { + currentContext = context; + resolve(); + }).catch(err => reject(err)) + }) + ] + Promise.all(initPromises).then(() => { itemsList = data.data.items; //Check image URLs let items = itemsList.map((item, i) => { @@ -221,7 +235,6 @@ }) }) - // Check image URLs Promise.allSettled(items).then(results => { itemsList = []; results.forEach(res => { @@ -272,6 +285,10 @@ }) } }) + results.forEach(result => { + if (!sortedIds.includes(result._id)) + sortedIds.push(result._id); + }) const data = { appId: currentContext.appId, loyaltyUnqiueId: currentContext.instanceId, @@ -281,6 +298,8 @@ } LoyaltyAPI.sortRewards(data).finally(() => { $timeout(() => { + sortedItems = []; + orderedItems = []; $rootScope.reloadRewards = true; buildfire.messaging.sendMessageToWidget({ type: 'refresh' @@ -292,6 +311,7 @@ } else { $timeout(() => { $rootScope.reloadRewards = true; + orderedItems = []; buildfire.messaging.sendMessageToWidget({ type: 'refresh' }); @@ -304,10 +324,6 @@ }).catch(err => { console.error(err); stateSeederInstance?.requestResult?.complete(); - return buildfire.dialog.toast({ - message: "Bad AI request, please try changing your request.", - type: "danger", - }); }) } @@ -411,27 +427,8 @@ }) } - let getUserAndContext = function() { - let promises = [ - new Promise((resolve, reject) => { - getCurrentUser().then((user) => { - currentUser = user; - resolve(); - }).catch(err => reject(err)) - }), - new Promise((resolve, reject) => { - Context.getContext().then(context => { - currentContext = context; - resolve(); - }) - }).catch(err => reject(err)) - ] - return Promise.all(promises); - } - return { initStateSeeder: function() { - getUserAndContext(); stateSeederInstance = new buildfire.components.aiStateSeeder({ generateOptions: { userMessage: `Generate a sample of redeemable items for a new [business-type].`, @@ -446,7 +443,7 @@ jsonTemplate: importJSONTemplate, sampleCSV: "Hotel Voucher, 50, 10, Redeem this voucher for a one-night stay at a luxurious hotel of your choice, https://source.unsplash.com/featured/?hotel\nFlight Upgrade, 30, 15, Upgrade your economy class ticket to business class, https://source.unsplash.com/featured/?flight\nCity Tour, 20, 5, Explore the city with a guided tour that covers all the major attractions and landmarks, https://source.unsplash.com/featured/?city\nAdventure Activity, 80, 30, Embark on an adrenaline-pumping adventure activity such as bungee jumping or skydiving, https://source.unsplash.com/featured/?adventure", maxRecords: 15, - hintText: 'Each row should start with a Loyalty item title, Cost to redeem, Points earned, Description, and Image URL', + hintText: 'Each row should start with a loyalty item title, cost to redeem, points earned, description, and image URL.', systemMessage: ``, callback: handleAIReq.bind(this, true), }, diff --git a/control/content/assets/css/style.css b/control/content/assets/css/style.css index 9c1b953..5af84e4 100644 --- a/control/content/assets/css/style.css +++ b/control/content/assets/css/style.css @@ -187,7 +187,3 @@ i { bottom:0px; z-index:1; } - - p.ai-seeder-banner { - margin-bottom: 10px; -} \ No newline at end of file diff --git a/control/content/controllers/content.home.controller.js b/control/content/controllers/content.home.controller.js index 7eb1a48..18dc822 100644 --- a/control/content/controllers/content.home.controller.js +++ b/control/content/controllers/content.home.controller.js @@ -6,8 +6,9 @@ .controller('ContentHomeCtrl', ['$scope', 'Buildfire', 'LoyaltyAPI', 'STATUS_CODE', '$modal', 'RewardCache', '$location', '$timeout', 'context', 'TAG_NAMES', 'StateSeeder', '$rootScope', function ($scope, Buildfire, LoyaltyAPI, STATUS_CODE, $modal, RewardCache, $location, $timeout, context, TAG_NAMES, StateSeeder, $rootScope) { var ContentHome = this; + ContentHome.defaultPassCode = '12345'; var _data = { - redemptionPasscode: '12345', + redemptionPasscode: ContentHome.defaultPassCode, unqiueId: context.instanceId, externalAppId: context.appId, appId: context.appId, @@ -39,9 +40,6 @@ }; ContentHome.currentLoggedInUser = null; - - - function updateMasterItem(data) { ContentHome.masterData = angular.copy(data); } @@ -77,7 +75,7 @@ console.info('init success result:', result); ContentHome.data = result; if (!ContentHome.data) - ContentHome.data = angular.copy(_data); + ContentHome.data = angular.copy(_data); //make sure to assign to the right appId ContentHome.data.appId = _data.appId; @@ -87,6 +85,12 @@ buildfire.datastore.get(TAG_NAMES.LOYALTY_INFO,function(err,data){ ContentHome.settings = data.data.settings; + if (!ContentHome.settings || !ContentHome.settings.redemptionPasscode){ + ContentHome.showRedemptionPasscodeHint = true; + } else { + ContentHome.showRedemptionPasscodeHint = false; + } + if (!$scope.$$phase) $scope.$digest(); updateMasterItem(ContentHome.data); if(Number(ContentHome.data.pointsPerDollar) <= 0) { ContentHome.data.pointsPerDollar = 1; @@ -95,6 +99,7 @@ }); }; ContentHome.error = function (err) { + ContentHome.showRedemptionPasscodeHint = true; if (err && err.code == 2100) { console.error('Error while getting application:', err); var success = function (result) { @@ -108,6 +113,7 @@ , error = function (err) { console.log('Error while saving data : ', err); if(err && err.code == 2000) { + ContentHome.data = angular.copy(_data); buildfire.messaging.sendMessageToWidget({ type: 'AppCreated' }); diff --git a/control/content/templates/home.html b/control/content/templates/home.html index 191ef55..daab937 100644 --- a/control/content/templates/home.html +++ b/control/content/templates/home.html @@ -1,6 +1,9 @@
+

+ Your default staff confirmation passcode is {{ContentHome.defaultPassCode}}, you can change it in the Settings tab. +

- Add redemption items here. You can set how user earns points and reedmes items in the Settings tab. + Add redemption items here. You can set how the user earns points and redeem items in the Settings tab.

diff --git a/widget/app.js b/widget/app.js index d09b279..86cbf17 100644 --- a/widget/app.js +++ b/widget/app.js @@ -224,7 +224,7 @@ let croppedImage = buildfire.imageLib.cropImage( _img, - { size: "full_width", aspect: attrs.loadImage } + { size: "full_width", aspect: attrs.aspectRatio || '1:1' } ); replaceImg(croppedImage); diff --git a/widget/assets/styles/style.css b/widget/assets/styles/style.css index 2726f55..c18dad6 100644 --- a/widget/assets/styles/style.css +++ b/widget/assets/styles/style.css @@ -133,7 +133,6 @@ .layout1 .list-media-holder img{ width:100%; height:auto; - min-height: 12em; } /* List Items */ diff --git a/widget/templates/Item_Details.html b/widget/templates/Item_Details.html index 92b2934..d4e932d 100644 --- a/widget/templates/Item_Details.html +++ b/widget/templates/Item_Details.html @@ -5,7 +5,7 @@
- +
diff --git a/widget/templates/List_Layout_1.html b/widget/templates/List_Layout_1.html index 2bb0f02..44fe543 100644 --- a/widget/templates/List_Layout_1.html +++ b/widget/templates/List_Layout_1.html @@ -43,7 +43,7 @@
-
diff --git a/widget/templates/List_Layout_2.html b/widget/templates/List_Layout_2.html index e38f81f..afa0d04 100644 --- a/widget/templates/List_Layout_2.html +++ b/widget/templates/List_Layout_2.html @@ -41,7 +41,7 @@
-
diff --git a/widget/templates/List_Layout_3.html b/widget/templates/List_Layout_3.html index dab2bd4..82f11de 100644 --- a/widget/templates/List_Layout_3.html +++ b/widget/templates/List_Layout_3.html @@ -44,7 +44,7 @@
-