diff --git a/control/content/app.services.js b/control/content/app.services.js index 9128665..c197402 100644 --- a/control/content/app.services.js +++ b/control/content/app.services.js @@ -164,5 +164,291 @@ return deferred.promise; } }; - }]) + }]).factory('StateSeeder', ['Context', 'LoyaltyAPI', '$rootScope', 'Buildfire', '$timeout' ,function(Context, LoyaltyAPI, $rootScope, Buildfire, $timeout) { + let itemsList; + let orderedItems = []; + let currentUser; + let currentContext; + let stateSeederInstance; + let generateJSONTemplate = { + items: [ + { + title: "", + pointsToRedeem: 0, + description: "", + listImage: "", + }, + ], + }; + let importJSONTemplate = { + items: [ + { + title: "", + pointsToRedeem: 0, + pointsPerItem: 0, + description: "", + listImage: "", + }, + ], + }; + let handleAIReq = function(isImport, 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 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) => { + 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'); + } + }) + }) + }) + + 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: isImport ? "Each row must have a valid image URL." : "Bad AI request, please try changing your request.", + type: "danger", + }); + } + + // reset old data + deleteAll().then(() => { + // save new data + 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 => { + 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); + } + }) + } + }) + results.forEach(result => { + if (!sortedIds.includes(result._id)) + 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(() => { + sortedItems = []; + orderedItems = []; + $rootScope.reloadRewards = true; + buildfire.messaging.sendMessageToWidget({ + type: 'refresh' + }); + stateSeederInstance?.requestResult?.complete(); + }) + }); + }); + } else { + $timeout(() => { + $rootScope.reloadRewards = true; + orderedItems = []; + buildfire.messaging.sendMessageToWidget({ + type: 'refresh' + }); + stateSeederInstance?.requestResult?.complete(); + }) + } + }) + }).catch(err => console.warn('old data delete error', err)); + }) + }).catch(err => { + console.error(err); + stateSeederInstance?.requestResult?.complete(); + }) + } + + // UTILITIES + let _applyDefaults = function(item) { + if (item.title) { + const points = checkPoints(item.pointsToRedeem, item.pointsPerItem); + return { + title: item.title, + pointsToRedeem: points.pointsToRedeem, + description: item.description || "", + listImage: item.listImage || "", + pointsPerItem: points.pointsPerItem, + appId: currentContext.appId, + loyaltyUnqiueId: currentContext.instanceId, + userToken: currentUser && currentUser.userToken, + auth: currentUser && currentUser.auth, + } + } + return null + } + + 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 { + points.pointsPerItem = Math.ceil(points.pointsToRedeem * 0.1); + } + return points; + } + + 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); + } + }); + }) + } + + return { + initStateSeeder: function() { + 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: generateJSONTemplate, + callback: handleAIReq.bind(this, false), + hintText: 'Replace values between brackets to match your requirements.', + }, + importOptions: { + 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.', + systemMessage: ``, + callback: handleAIReq.bind(this, true), + }, + }).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..5af84e4 100644 --- a/control/content/assets/css/style.css +++ b/control/content/assets/css/style.css @@ -186,4 +186,4 @@ i { background-color:white; bottom:0px; z-index:1; - } \ 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..18dc822 100644 --- a/control/content/controllers/content.home.controller.js +++ b/control/content/controllers/content.home.controller.js @@ -3,11 +3,12 @@ (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; + ContentHome.defaultPassCode = '12345'; var _data = { - redemptionPasscode: '12345', + redemptionPasscode: ContentHome.defaultPassCode, unqiueId: context.instanceId, externalAppId: context.appId, appId: context.appId, @@ -24,7 +25,7 @@ }, image: [] }; - + let stateSeeder; //Scroll current view to top when page loaded. buildfire.navigation.scrollTop(); ContentHome.title = ""; @@ -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' }); @@ -127,8 +133,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 +151,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 +366,18 @@ 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/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); 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 @@ + 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.