2361 lines
102 KiB
JavaScript
2361 lines
102 KiB
JavaScript
/*****************************************************************************
|
||
Component Name : ShopifyCredentials
|
||
Created Date : 17/4/2024
|
||
@description : This component manages the configuration of Shopify stores by handling the creation of new, editing, and deleting actions,
|
||
as well as managing currency and pricebook settings.
|
||
@author : SME Team
|
||
Modification Log:
|
||
Ver Date Author
|
||
1.0 30/4/2024 SME Team
|
||
*****************************************************************************/
|
||
|
||
|
||
|
||
import { LightningElement, track, api, wire } from 'lwc';
|
||
|
||
// Auto Configuration
|
||
import { getRecord } from 'lightning/uiRecordApi';
|
||
import USER_EMAIL from '@salesforce/schema/User.Email';
|
||
import USER_ID from '@salesforce/user/Id';
|
||
|
||
|
||
// FETCHING RESOURCES FROM THE STATIC RESOURCES
|
||
|
||
// import pageLoadingGif from "@salesforce/resourceUrl/SMEPageLoadingGif";
|
||
import storeulgetinfo from '@salesforce/resourceUrl/storeulgetinfo';
|
||
import storepassword from '@salesforce/resourceUrl/Storepassword';
|
||
|
||
import Resources from "@salesforce/resourceUrl/Resources";
|
||
import SMEPageLoadingGIF2 from "@salesforce/resourceUrl/SMEPageLoadingGIF2";
|
||
import syncLogo1 from '@salesforce/resourceUrl/syncLogo1';
|
||
import SMELOGO from '@salesforce/resourceUrl/SMELOGO';
|
||
import StoreLogo from '@salesforce/resourceUrl/StoreLogo';
|
||
import SFLogo from '@salesforce/resourceUrl/SFLogo';
|
||
|
||
// CREATING INSTANCES OF APEX METHODS
|
||
//import getConfigration from '@salesforce/apex/ShopifyController.getConfigration';
|
||
import getSiteURLs from '@salesforce/apex/ShopifyController.getSiteURLs';
|
||
import getActivePricebook from '@salesforce/apex/ShopifyController.getActivePricebook';
|
||
import getErrorMailList from '@salesforce/apex/ShopifyController.getErrorMailList';
|
||
import RemoveStore from '@salesforce/apex/ShopifyController.removeStore';
|
||
import getConfigrations from '@salesforce/apex/ShopifyController.getConfigrations';
|
||
import getConfigrationForDynamicRow from '@salesforce/apex/ShopifyController.getConfigrationForDynamicRow';
|
||
import saveEmailSettings from '@salesforce/apex/ShopifyController.saveEmailSettings';
|
||
import getActiveCurrencies from '@salesforce/apex/ShopifyController.getActiveCurrencies';
|
||
import createRemoteSiteSettings from '@salesforce/apex/ShopifyController.createRemoteSiteSettings';
|
||
import checkCreds from '@salesforce/apex/ShopifyController.checkCreds';
|
||
import checkAccesScope from '@salesforce/apex/ShopifyController.checkAccesScope';
|
||
import checkStoreNameRemoteSite from '@salesforce/apex/ShopifyController.checkStoreNameRemoteSite';
|
||
import getOrgEnvironment from '@salesforce/apex/ShopifyController.getOrgEnvironment';
|
||
import initShopifyTokenGeneration from '@salesforce/apex/ShopifyController.initShopifyTokenGeneration';
|
||
//Add new impor for default site url
|
||
import getDefaultSiteUrl from '@salesforce/apex/ShopifyController.getDefaultSiteUrl';
|
||
|
||
// New Method to imrpove performance
|
||
import updatePriceBookCurrencyStage from '@salesforce/apex/ShopifyController.updatePriceBookCurrencyStage';
|
||
|
||
// DISPLAY-ONLY: last synced order timestamp for the hero "Last order synced X ago" text
|
||
import getLastOrderSyncTime from '@salesforce/apex/ShopifyController.getLastOrderSyncTime';
|
||
import getOrganizationName from '@salesforce/apex/ShopifyController.getOrganizationName';
|
||
|
||
//CUSTOM LABELS
|
||
import Product from '@salesforce/label/c.Product';
|
||
import Customer from '@salesforce/label/c.Customer';
|
||
import Draft_Order from '@salesforce/label/c.Draft_Order';
|
||
import Order from '@salesforce/label/c.Order';
|
||
import Collection from '@salesforce/label/c.Collection';
|
||
import Inventory from '@salesforce/label/c.Inventory';
|
||
import Location from '@salesforce/label/c.Location';
|
||
import Please_enter_your_store_name from '@salesforce/label/c.Please_enter_your_store_name';
|
||
import Get_your_Shopify_password_from_private_app from '@salesforce/label/c.Get_your_Shopify_password_from_private_app';
|
||
import THIS_ACTION_REQUIRES_MERCHANT_APPROVAL from '@salesforce/label/c.THIS_ACTION_REQUIRES_MERCHANT_APPROVAL';
|
||
import Select_a_Site_URL from '@salesforce/label/c.Select_a_Site_URL';
|
||
import Generate_Access_Token_Info from '@salesforce/label/c.Generate_Access_Token_Info';
|
||
import Salesforce_Sites_enables_you_to_create_public from '@salesforce/label/c.Salesforce_Sites_enables_you_to_create_public';
|
||
import click_here from '@salesforce/label/c.click_here';
|
||
import Shopify_Store_Name from '@salesforce/label/c.Shopify_Store_Name';
|
||
import The_standard_price_book_is_the_master_list from '@salesforce/label/c.The_standard_price_book_is_the_master_list';
|
||
import Select_a_Currency from '@salesforce/label/c.Select_a_Currency';
|
||
import Multicurrency from '@salesforce/label/c.Multicurrency';
|
||
import Default_Currency from '@salesforce/label/c.Default_Currency';
|
||
import Default_Organization_Currency_ISO_Code from '@salesforce/label/c.Default_Organization_Currency_ISO_Code';
|
||
import Real_time_Sync from '@salesforce/label/c.Real_time_Sync';
|
||
import Some_objects_are_mandatory from '@salesforce/label/c.Some_objects_are_mandatory';
|
||
import Please_add_email_for_notifications from '@salesforce/label/c.Please_add_email_for_notifications';
|
||
import You_can_add_upto_3_emails from '@salesforce/label/c.You_can_add_upto_3_emails';
|
||
import Close from '@salesforce/label/c.Close';
|
||
import Proceed from '@salesforce/label/c.Proceed';
|
||
import About_Credentials from '@salesforce/label/c.About_Credentials';
|
||
import Attention from '@salesforce/label/c.Attention';
|
||
import Select_a_Pricebook from '@salesforce/label/c.Select_a_Pricebook';
|
||
import Admin_API_access_token from '@salesforce/label/c.Admin_API_access_token';
|
||
import Error from '@salesforce/label/c.Error';
|
||
import Please_enter_Email from '@salesforce/label/c.Please_enter_Email';
|
||
import Please_enter_valid_Email from '@salesforce/label/c.Please_enter_valid_Email';
|
||
import Please_enter_Site_URL from '@salesforce/label/c.Please_enter_Site_URL';
|
||
import Please_enter_Shopify_Store_URL from '@salesforce/label/c.Please_enter_Shopify_Store_URL';
|
||
import Please_enter_ApiAccessToken from '@salesforce/label/c.Please_enter_ApiAccessToken';
|
||
import Please_enter_PriceBook from '@salesforce/label/c.Please_enter_PriceBook';
|
||
import Email_must_be_less_than_or_equal_to_38_characters from '@salesforce/label/c.Email_must_be_less_than_or_equal_to_38_characters';
|
||
import You_cannot_enter_Duplicate_Email from '@salesforce/label/c.You_cannot_enter_Duplicate_Email';
|
||
import Please_authorize_Access_Token from '@salesforce/label/c.Please_authorize_Access_Token';
|
||
import Please_check_the_credentials_first from '@salesforce/label/c.Please_check_the_credentials_first';
|
||
import Please_enter_shopify_store_name from '@salesforce/label/c.Please_enter_shopify_store_name';
|
||
import Please_enter_admin_api_access_token from '@salesforce/label/c.Please_enter_admin_api_access_token';
|
||
import Store_is_Not_Authorized from '@salesforce/label/c.Store_is_Not_Authorized';
|
||
import Please_enter_store_name from '@salesforce/label/c.Please_enter_store_name';
|
||
import Please_enter_password from '@salesforce/label/c.Please_enter_password';
|
||
import Store_is_Not_having_Access from '@salesforce/label/c.Store_is_Not_having_Access';
|
||
import You_do_not_have_access_of_selected_object from '@salesforce/label/c.You_do_not_have_access_of_selected_object';
|
||
import Create_Remote_site_Setting from '@salesforce/label/c.Create_Remote_site_Setting';
|
||
import Manual_Configuration from '@salesforce/label/c.Manual_Configuration';
|
||
import Auto_Configuration from '@salesforce/label/c.Auto_Configuration';
|
||
import Check_Credential from '@salesforce/label/c.Check_Credential';
|
||
import Generate_Access_Token from '@salesforce/label/c.Generate_Access_Token';
|
||
import You_have_changed_the_pricebook from '@salesforce/label/c.You_have_changed_the_pricebook';
|
||
import You_have_changed_the_currency from '@salesforce/label/c.You_have_changed_the_currency';
|
||
import Select_Site_URL from '@salesforce/label/c.Select_Site_URL';
|
||
import Enter_Store_Name from '@salesforce/label/c.Enter_Store_Name';
|
||
import Enter_Access_token from '@salesforce/label/c.Enter_Access_token';
|
||
import Select_Pricebook from '@salesforce/label/c.Select_Pricebook';
|
||
import Automatic_Configuration from '@salesforce/label/c.Automatic_Configuration';
|
||
|
||
|
||
|
||
|
||
// SHOW TOAST
|
||
import { ShowToastEvent } from "lightning/platformShowToastEvent";
|
||
export default class ShopifyCredentials extends LightningElement {
|
||
|
||
// Custom in-component toast state (replaces ShowToastEvent) — see showNotification.
|
||
@track toasts = [];
|
||
_toastSeq = 0;
|
||
|
||
// GLOBAL VARIABLES
|
||
@api siteurlHelptext = '/lightning/setup/CustomDomain/home';
|
||
StoreLogo = StoreLogo;
|
||
SFLogo = SFLogo;
|
||
|
||
// storeName is bound from home.js via store-name={storeName}. Converted from a
|
||
// plain @api field to a getter/setter: home.js's own storeName only updates on
|
||
// Save & Next (see childSaveEvent), so while a user is typing a name for a brand
|
||
// new store, home.js is still passing down '' on every parent re-render (spinner
|
||
// ticks, polling, etc.), which was wiping out what was just typed and kept
|
||
// disableGenerateAccessToken stuck on. Now an empty/falsy value from the parent
|
||
// is ignored instead of stomping the locally-typed value. Real logic (validation,
|
||
// save, disable rules) is unchanged.
|
||
_storeName = '';
|
||
@api
|
||
get storeName() {
|
||
return this._storeName;
|
||
}
|
||
set storeName(value) {
|
||
if (value) {
|
||
this._storeName = value;
|
||
}
|
||
}
|
||
|
||
@api storeSelectionType;
|
||
@api showAuthorize = false;
|
||
@api showCheck = false;
|
||
@api shopifyCredentialMultiStore = false;
|
||
@api isAutomatic;
|
||
//Add a new property
|
||
@track defaultSiteUrl = '';
|
||
|
||
// LOCAL VARIABLES
|
||
@track siteURL = '';
|
||
@track storeUrl = '';
|
||
@track storeUrlSelected;
|
||
@track apiAccessToken;
|
||
@track disableInfo = false;
|
||
@track priceBook = '';
|
||
@track defaultCurrencyType = '';
|
||
@track priceBookList = [];
|
||
@track siteURLList = [];
|
||
@track configObject = {};
|
||
@track batchProcessingObject = {};
|
||
addEmailAddress = true;
|
||
stopEmailError = false;
|
||
keyIndex = 1;
|
||
@track checkIndexNo = 0;
|
||
@track imageChange = true;
|
||
@track showProcessModel = false;
|
||
@track deleteEmailIds = '';
|
||
@track authorizedStore = false;
|
||
@track authorizedDone = false;
|
||
@track storeulgetinfo = storeulgetinfo;
|
||
@track storepassword = storepassword;
|
||
@track multiStoreModel = true;
|
||
@track isChanged = false;
|
||
@track warringMessage = [];
|
||
@track oldCurrencyISOCode = '';
|
||
@track emaiList = "";
|
||
@track Emailvalue = "";
|
||
@track currencyISOCode = [];
|
||
@track currencyCodeList = [];
|
||
@track currencyCodeListNew = [];
|
||
@track showCurrencyCode = false;
|
||
@track storeSelectedCurrency = "";
|
||
|
||
// DISPLAY-ONLY: server-formatted "Last order synced X ago" text ('' = none).
|
||
@track _lastSyncedText = '';
|
||
_syncTimerId = null;
|
||
@track storeSelectedCurrencyDefault = "";
|
||
@track multiStoreSelectionValue = '';
|
||
@track productSyncValue = true;
|
||
@track customerSyncValue = true;
|
||
@track collectionSyncValue = true;
|
||
@track orderSyncValue = true;
|
||
@track draftSyncValue = true;
|
||
@track inventorySyncValue = true;
|
||
@track collectionSyncChanged = false;
|
||
@track orderSyncChanged = false;
|
||
@track draftSyncChanged = false;
|
||
@track inventorySyncChanged = false;
|
||
@track storedUsername;
|
||
@track storedPassword;
|
||
@track addButtonVisible = true;
|
||
|
||
@track storeNameSelected = false;
|
||
@track accessTokenAvailable = false;
|
||
@track currencyCodeChanged = false;
|
||
@track openPricebookCurrencyModal = false;
|
||
@track oldPricebook = '';
|
||
@track newPricebook = '';
|
||
@track oldCurrency = '';
|
||
@track isPricebookOrCurrencyChanged = false;
|
||
@track isPricebookChanged = false;
|
||
@track isCurrencyChanged = false;
|
||
@track changePricebookMessage = '';
|
||
@track changeCurrencyMessage = '';
|
||
@track listAccessScopeError = [];
|
||
@track listShopfyObjectAccessError;
|
||
@track AccessScope = false;
|
||
@track isLoading = false;
|
||
@track isRemoteSiteCreated = false;
|
||
@track emailListChanged = false;
|
||
@track checkCredential = false;
|
||
@track isStoreAuthorized = false;
|
||
@track isSiteSupportedOrg = true;
|
||
@track isSitePresentOrg = true;
|
||
@track currentUserEmail;
|
||
@track userId = USER_ID;
|
||
organizationName;
|
||
|
||
// pageLoadingGif = pageLoadingGif;
|
||
pageLoadingGif = SMEPageLoadingGIF2;
|
||
@track remoteSiteForStore = false;
|
||
|
||
// Generate Access Token modal state
|
||
@track showGenerateTokenModal = false;
|
||
@track generatorClientId = '';
|
||
@track generatorClientSecret = '';
|
||
@track isGeneratingToken = false;
|
||
@track clientFieldsReadOnly = true; // readonly-until-focus to defeat browser autofill
|
||
checkAccessTokenInterval;
|
||
tokenGenTimeout; // hard cap so the generating spinner never runs forever
|
||
countdownInterval; // ticks the visible countdown
|
||
popupWatcher; // polls until the Shopify OAuth popup is closed
|
||
@track tokenGenCountdown = 30; // seconds remaining before auto-close
|
||
@track countdownStarted = false; // true only after the OAuth popup has closed
|
||
syncSpinnerLogo = syncLogo1; // SME logo shown spinning while waiting for the token
|
||
generateTokenHeaderLogo = SMELOGO; // Sync Made Easy logo shown in the Generate Token modal header
|
||
|
||
//Custom Labels
|
||
@track label = {
|
||
Product, Customer, Draft_Order, Order, Collection, Location, Inventory, Generate_Access_Token_Info,
|
||
Please_enter_your_store_name, Get_your_Shopify_password_from_private_app,
|
||
THIS_ACTION_REQUIRES_MERCHANT_APPROVAL, Select_a_Site_URL, Salesforce_Sites_enables_you_to_create_public,
|
||
click_here, Shopify_Store_Name, The_standard_price_book_is_the_master_list, Select_a_Currency,
|
||
Multicurrency, Default_Currency, Default_Organization_Currency_ISO_Code, Real_time_Sync,
|
||
Some_objects_are_mandatory, Please_add_email_for_notifications, You_can_add_upto_3_emails,
|
||
Automatic_Configuration,
|
||
Close, About_Credentials, Attention, Select_a_Pricebook, Admin_API_access_token, Error,
|
||
Please_enter_Email, Please_enter_valid_Email,
|
||
Please_enter_Site_URL,
|
||
Please_enter_Shopify_Store_URL,
|
||
Please_enter_ApiAccessToken,
|
||
Please_enter_PriceBook,
|
||
Email_must_be_less_than_or_equal_to_38_characters,
|
||
You_cannot_enter_Duplicate_Email,
|
||
Please_authorize_Access_Token,
|
||
Please_check_the_credentials_first,
|
||
Please_enter_shopify_store_name,
|
||
Please_enter_admin_api_access_token,
|
||
Store_is_Not_Authorized,
|
||
Please_enter_store_name,
|
||
Please_enter_password,
|
||
Store_is_Not_having_Access,
|
||
You_do_not_have_access_of_selected_object,
|
||
Create_Remote_site_Setting, Auto_Configuration, Manual_Configuration, Check_Credential, Generate_Access_Token,
|
||
Proceed, You_have_changed_the_pricebook, You_have_changed_the_currency, Select_Site_URL,
|
||
Enter_Store_Name, Enter_Access_token, Select_Pricebook
|
||
};
|
||
|
||
//Email List to add emails entered by the user
|
||
@track emailList = [{
|
||
index: this.keyIndex,
|
||
value: '',
|
||
addEmail: this.addEmailAddress,
|
||
exists: false,
|
||
isEditable: true,
|
||
isValid: false,
|
||
isDeleteDisabled: true
|
||
}];
|
||
|
||
|
||
@wire(getRecord, { recordId: '$userId', fields: [USER_EMAIL] })
|
||
currentUserInfo({ error, data }) {
|
||
if (data) {
|
||
this.currentUserEmail = data.fields.Email.value;
|
||
console.log('>>> This is the current user email :: ' + this.currentUserEmail);
|
||
} else if (error) {
|
||
console.log('>>> Error in email fetching ' + JSON.stringify(error));
|
||
// this.error = error;
|
||
}
|
||
}
|
||
|
||
changeConfigurationType() {
|
||
this.isAutomatic = !this.isAutomatic;
|
||
|
||
this.dispatchEvent(new CustomEvent('automation', { detail: this.isAutomatic }));
|
||
}
|
||
|
||
//Connected Callback
|
||
connectedCallback() {
|
||
console.log('SYNC MADE EASY SME: Credential Connected Callback' + this.isStoreAuthorized);
|
||
this.showSpinnerHandler();
|
||
if (this.storeName && !this.shopifyCredentialMultiStore) {
|
||
this.multiStoreModel = false;
|
||
this.storeNameSelected = true;
|
||
this.accessTokenAvailable = true;
|
||
this.storeSelectionType = 'Existing Store';
|
||
this.getconfigrationForDynamicRow();
|
||
} else if (!this.storeName) {
|
||
this.getStores();
|
||
}
|
||
this.getActiveCurrenciesList();
|
||
this.getActivePricebookList();
|
||
this.showAuthorize = false;
|
||
this.getErrorMailListMethod();
|
||
this.fetchDefaultSiteUrl();
|
||
this.loadLastOrderSyncTime();
|
||
// Refresh the server-computed "X ago" label every 60s.
|
||
this._syncTimerId = setInterval(() => { this.loadLastOrderSyncTime(); }, 60000);
|
||
}
|
||
|
||
/**
|
||
* This Method is used to get all the stores which are present in org.
|
||
* */
|
||
getStores() {
|
||
console.log('SYNC MADE EASY: Credential => Getting List of Stores');
|
||
this.multiStoreModel = false;
|
||
getConfigrations().then((result) => {
|
||
if (result) {
|
||
this.storeList = JSON.parse(result);
|
||
if (!this.storeList || this.storeList.length === 0) {
|
||
this.multiStoreModel = false;
|
||
this.multiStoreHandlerEvent('New');
|
||
} else if (this.storeList && this.storeList.length === 1) {
|
||
this.storeName = this.storeList[0].Name;
|
||
this.multiStoreHandlerEvent('Existing Store');
|
||
} else {
|
||
this.multiStoreModel = true;
|
||
}
|
||
} else {
|
||
this.multiStoreModel = false;
|
||
this.multiStoreHandlerEvent('New');
|
||
}
|
||
}).catch((error) => {
|
||
//Error occured
|
||
this.closeSpinnerHandler();
|
||
});
|
||
}
|
||
|
||
//This method will get all the Currency ISO
|
||
getActiveCurrenciesList() {
|
||
console.log('SYNC MADE EASY: FETCH CURRENCIES');
|
||
getActiveCurrencies().then((result) => {
|
||
this.storeSelectedCurrencyDefault = result[0];
|
||
if (result[1] != null && result[1] != '') {
|
||
this.currencyCodeList = JSON.parse(result[1]).map(activecurrency => {
|
||
return ({ label: activecurrency.ISOCode, value: activecurrency.ISOCode })
|
||
});
|
||
this.showCurrencyCode = true;
|
||
}
|
||
this.handleDefaultSelectedCurrency();
|
||
|
||
}).catch((error) => {
|
||
});
|
||
}
|
||
|
||
//Getting store records
|
||
getconfigrationForDynamicRow() {
|
||
console.log('SYNC MADE EASY: Credential => Fetching Store Data ' + this.storeName);
|
||
getConfigrationForDynamicRow({ store: this.storeName })
|
||
.then((result) => {
|
||
this.configObject = JSON.parse(result);
|
||
if (this.configObject.CurrencyIsoCode) {
|
||
this.storeSelectedCurrency = this.configObject.CurrencyIsoCode;
|
||
this.handleDefaultSelectedCurrency();
|
||
}
|
||
this.productSyncValue = this.configObject.HIC_ShopifySync__Product_Sync__c;
|
||
this.customerSyncValue = this.configObject.HIC_ShopifySync__Customer_Sync__c;
|
||
this.collectionSyncValue = this.configObject.HIC_ShopifySync__Collection_Sync__c;
|
||
this.orderSyncValue = this.configObject.HIC_ShopifySync__Order_Sync__c;
|
||
this.draftSyncValue = this.configObject.HIC_ShopifySync__Draft_Order_Sync__c;
|
||
this.inventorySyncValue = this.configObject.HIC_ShopifySync__Inventory_Location__c;
|
||
this.siteURL = this.defaultSiteUrl || this.configObject.HIC_ShopifySync__Salesforce_Site_URL__c;
|
||
// To this — keep the default always:
|
||
this.siteURL = this.defaultSiteUrl || this.siteURL;
|
||
this.storeUrl = this.configObject.HIC_ShopifySync__Shopify_URL__c;
|
||
this.apiAccessToken = this.configObject.HIC_ShopifySync__Private_Key_Password__c;
|
||
this.priceBook = this.configObject.HIC_ShopifySync__Salesforce_Pricebook__c;
|
||
if (this.priceBookList.length > 0) {
|
||
let isPriceBookExists = false;
|
||
for (let price of this.priceBookList) {
|
||
if (this.priceBook === price.value) {
|
||
isPriceBookExists = true;
|
||
}
|
||
}
|
||
if (!isPriceBookExists) {
|
||
this.isPricebookChanged = true;
|
||
this.priceBook = this.priceBookList[0].value;
|
||
}
|
||
}
|
||
|
||
if (this.configObject.HIC_ShopifySync__Product_Sync__c == null) {
|
||
this.configObject.HIC_ShopifySync__Product_Sync__c = false;
|
||
}
|
||
|
||
if (this.isSiteSupportedOrg && this.storeSelectionType === 'Existing Store') {
|
||
// Checking Remote site Setting for Existing Store
|
||
this.createStoreUrlRemoteSite(this.storeUrl, this.apiAccessToken);
|
||
|
||
} else if (!this.isSiteSupportedOrg && this.storeSelectionType === 'Existing Store') {
|
||
this.checkStoreNameRemoteSiteSetting();
|
||
}
|
||
this.closeSpinnerHandler();
|
||
}).catch((error) => {
|
||
this.closeSpinnerHandler();
|
||
})
|
||
}
|
||
|
||
handleDefaultSelectedCurrency() {
|
||
let currencySelectedExists = false;
|
||
let currencyDefaultExists = false;
|
||
if (this.currencyCodeList.length > 0 && this.storeSelectedCurrencyDefault != '') {
|
||
for (let currencyCode of this.currencyCodeList) {
|
||
if (this.storeSelectedCurrency === currencyCode.value) {
|
||
currencySelectedExists = true;
|
||
}
|
||
if (this.storeSelectedCurrencyDefault === currencyCode.value) {
|
||
currencyDefaultExists = true;
|
||
}
|
||
}
|
||
if (currencySelectedExists == false && currencyDefaultExists == true) {
|
||
this.isCurrencyChanged = true;
|
||
this.storeSelectedCurrency = this.storeSelectedCurrencyDefault;
|
||
} else if (currencySelectedExists == false && currencyDefaultExists == false) {
|
||
this.isCurrencyChanged = true;
|
||
this.storeSelectedCurrency = this.currencyCodeList[0].value;
|
||
}
|
||
} else if (this.storeSelectedCurrencyDefault != '') {
|
||
this.isCurrencyChanged = true;
|
||
this.storeSelectedCurrency = this.storeSelectedCurrencyDefault;
|
||
}
|
||
}
|
||
|
||
//This method saves the true/false value of Product toggle
|
||
changeProductSync(event) {
|
||
this.productSyncValue = event.target.checked;
|
||
}
|
||
|
||
//his method saves the true/false value of Customer toggle
|
||
changeCustomerSync(event) {
|
||
this.customerSyncValue = event.target.checked;
|
||
}
|
||
|
||
//This method saves the true/false value of Collection toggle
|
||
changeCollectionSync(event) {
|
||
this.collectionSyncValue = event.target.checked;
|
||
if (this.configObject.HIC_ShopifySync__Collection_Sync__c !== this.collectionSyncValue) {
|
||
this.collectionSyncChanged = true;
|
||
} else {
|
||
this.collectionSyncChanged = false;
|
||
}
|
||
this.handleAccessScope();
|
||
}
|
||
|
||
//This method saves the true/false value of Order toggle
|
||
changeOrderSync(event) {
|
||
this.orderSyncValue = event.target.checked;
|
||
if (this.configObject.HIC_ShopifySync__Order_Sync__c !== this.orderSyncValue) {
|
||
this.orderSyncChanged = true;
|
||
} else {
|
||
this.orderSyncChanged = false;
|
||
}
|
||
this.handleAccessScope();
|
||
}
|
||
|
||
//This method saves the true/false value of Draft toggle
|
||
changeDraftSync(event) {
|
||
const newValue = event.target.checked;
|
||
const oldValue = this.draftSyncValue;
|
||
this.draftSyncValue = newValue;
|
||
this.draftSyncChanged =
|
||
this.configObject.HIC_ShopifySync__Draft_Order_Sync__c !== newValue;
|
||
|
||
if (!oldValue && newValue) {
|
||
this.configObject.HIC_ShopifySync__Enable_Draft_Order__c = true;
|
||
this.configObject.HIC_ShopifySync__Enable_Opportunity_for_SDO__c = true;
|
||
}
|
||
if (oldValue && !newValue) {
|
||
this.configObject.HIC_ShopifySync__Enable_Draft_Order__c = false;
|
||
this.configObject.HIC_ShopifySync__Enable_Opportunity_for_SDO__c = false;
|
||
}
|
||
this.previousDraftSyncValue = newValue;
|
||
this.handleAccessScope();
|
||
}
|
||
|
||
//This method saves the true/false value of Inventory toggle
|
||
changeInventorySync(event) {
|
||
this.inventorySyncValue = event.target.checked;
|
||
if (this.configObject.HIC_ShopifySync__Inventory_Location__c !== this.inventorySyncValue) {
|
||
this.inventorySyncChanged = true;
|
||
} else {
|
||
this.inventorySyncChanged = false;
|
||
}
|
||
this.handleAccessScope();
|
||
}
|
||
|
||
//This method controls the closing of multistore modal
|
||
closeMultiStoreModal(event) {
|
||
if (event.detail == 'closeModal') {
|
||
this.multiStoreModel = false;
|
||
}
|
||
}
|
||
|
||
//This method is getting the store value
|
||
// getStore() {
|
||
// console.log('SYNC MADE EASY: Credential => Fetching Configuration');
|
||
// getConfigration().then((result) => {
|
||
// this.configObject = JSON.parse(result);
|
||
// this.siteURL = this.configObject.HIC_ShopifySync__Salesforce_Site_URL__c;
|
||
// this.storeUrl = this.configObject.HIC_ShopifySync__Shopify_URL__c;
|
||
// this.apiAccessToken = this.configObject.HIC_ShopifySync__Private_Key_Password__c;
|
||
// this.priceBook = this.configObject.HIC_ShopifySync__Salesforce_Pricebook__c;
|
||
// }).catch((error) => {
|
||
// //Error occured
|
||
// this.closeSpinnerHandler();
|
||
// });
|
||
// }
|
||
|
||
//This method is getting the site URL
|
||
getSiteUrlList() {
|
||
console.log('SYNC MADE EASY: Credential => Fetching Sites');
|
||
return getSiteURLs().then((result) => {
|
||
if (result != null) {
|
||
if (result == 'No access to site.') {
|
||
this.closeSpinnerHandler();
|
||
this.isSiteSupportedOrg = false;
|
||
this.isSitePresentOrg = false;
|
||
console.log('checking..');
|
||
} else {
|
||
// this.isSitePresentOrg = true;
|
||
this.siteURLList = [];
|
||
for (let url of JSON.parse(result)) {
|
||
this.siteURLList.push({ label: url.siteUrl, value: url.siteUrl });
|
||
}
|
||
this.closeSpinnerHandler();
|
||
}
|
||
}
|
||
}).catch((error) => {
|
||
//Error occured
|
||
this.closeSpinnerHandler();
|
||
});
|
||
}
|
||
|
||
// his method is getting all the Pricebooks
|
||
getActivePricebookList() {
|
||
console.log('SYNC MADE EASY: Credential => Fetching Pricebooks');
|
||
getActivePricebook().then((result) => {
|
||
//console.log('SYNC MADE EASY: Credential => Fetching Pricebooks : '+result);
|
||
this.priceBookList = [];
|
||
for (let pricebook of JSON.parse(result)) {
|
||
this.priceBookList.push({ label: pricebook.Name, value: pricebook.Id, IsStandard: pricebook.IsStandard });
|
||
}
|
||
if (this.priceBook) {
|
||
if (this.priceBookList) {
|
||
let isPriceBookExists = false;
|
||
for (let price of this.priceBookList) {
|
||
if (this.priceBook === price.value) {
|
||
isPriceBookExists = true;
|
||
}
|
||
}
|
||
if (!isPriceBookExists) {
|
||
this.isPricebookChanged = true;
|
||
this.priceBook = this.priceBookList[0].value;
|
||
}
|
||
}
|
||
}
|
||
this.closeSpinnerHandler();
|
||
}).catch((error) => {
|
||
this.closeSpinnerHandler();
|
||
});
|
||
}
|
||
|
||
|
||
//This method is handling Email Add button and adding the emails to the list
|
||
addEmail(event) {
|
||
const emailRegex = /^([A-Za-z0-9_\-\.])+\@([A-Za-z0-9_\-\.])+\.([A-Za-z]{2,4})$/;
|
||
if (this.emailList.length < 3) {
|
||
if (this.emailList[this.checkIndexNo].value.match(emailRegex)) {
|
||
let indexNumber = ++this.keyIndex;
|
||
this.addEmailAddress = false;
|
||
let newItem = {
|
||
index: indexNumber,
|
||
value: '',
|
||
addEmail: this.addEmailAddress,
|
||
exists: false,
|
||
isEditable: true,
|
||
isValid: false
|
||
};
|
||
|
||
this.emailList = [...this.emailList, newItem];
|
||
++this.checkIndexNo;
|
||
|
||
}
|
||
else if (this.emailList[this.checkIndexNo].value === '') {
|
||
this.showNotification(this.label.Error, 'Error', this.label.Please_enter_Email);
|
||
}
|
||
else {
|
||
this.showNotification(this.label.Error, 'Error', this.label.Please_enter_valid_Email);
|
||
}
|
||
}
|
||
|
||
if (this.emailList.length > 2) {
|
||
this.addButtonVisible = false;
|
||
}
|
||
this.updateDeleteState();
|
||
|
||
}
|
||
|
||
//This method is handling deleteting emails from the email list
|
||
deleteEmail(event) {
|
||
this.emailListChanged = true;
|
||
if (this.checkIndexNo <= 0) {
|
||
this.checkIndexNo = 1;
|
||
}
|
||
this.addButtonVisible = true;
|
||
if (event.target.dataset.id) {
|
||
this.deleteEmailIds = this.deleteEmailIds + ',' + event.target.dataset.id;
|
||
}
|
||
let indexNumber = 0;
|
||
let updatedEmailList = this.emailList.map((email) => {
|
||
if (email.index != event.target.dataset.id) {
|
||
email.index = ++indexNumber;
|
||
return email;
|
||
} else {
|
||
return null;
|
||
}
|
||
});
|
||
updatedEmailList = updatedEmailList.filter((obj) => obj !== null);
|
||
this.emailList = updatedEmailList;
|
||
this.keyIndex--;
|
||
this.checkIndexNo--;
|
||
this.updateDeleteState();
|
||
|
||
}
|
||
get showDelete() {
|
||
return this.emailList.length > 1;
|
||
}
|
||
|
||
//This method is getting list of Emails to send email notifications after Batch processing is completed
|
||
getErrorMailListMethod() {
|
||
getErrorMailList()
|
||
.then((result) => {
|
||
|
||
if (result) {
|
||
this.emailList = [];
|
||
let eIndex = 0;
|
||
for (let email of JSON.parse(result)) {
|
||
++eIndex;
|
||
this.emailList.push({
|
||
index: eIndex,
|
||
value: email.Name,
|
||
addEmail: this.addEmailAddress,
|
||
exists: false,
|
||
isEditable: false,
|
||
isValid: true
|
||
});
|
||
this.addEmailAddress = false;
|
||
|
||
if (this.emailList.length > 2) {
|
||
this.addButtonVisible = false;
|
||
}
|
||
}
|
||
|
||
this.keyIndex = eIndex;
|
||
this.updateDeleteState();
|
||
|
||
}
|
||
|
||
this.closeSpinnerHandler();
|
||
}).catch((error) => {
|
||
//Error occured
|
||
this.closeSpinnerHandler();
|
||
})
|
||
}
|
||
|
||
//This method is handling the emails entered/updated from the UI
|
||
handleEmailValue(event) {
|
||
this.emailListChanged = true;
|
||
const emailRegex = /^([A-Za-z0-9_\-\.])+\@([A-Za-z0-9_\-\.])+\.([A-Za-z]{2,4})$/;
|
||
|
||
for (let email of this.emailList) {
|
||
if (email.index == event.target.dataset.id) {
|
||
email.value = event.target.value;
|
||
email.isValid = emailRegex.test(email.value);
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
|
||
//??? Check the functionality, not calling anywhere
|
||
OpenSliderPopupFirst() {
|
||
this.showProcessModel = true;
|
||
this.imageChange = true;
|
||
}
|
||
|
||
//This method is used to show helptext to get access token
|
||
OpenSliderPopupSecond() {
|
||
this.showProcessModel = true;
|
||
this.imageChange = false;
|
||
}
|
||
|
||
//This method handles closing of 'About Credentials' modal
|
||
closeWarningModal(event) {
|
||
this.showProcessModel = false;
|
||
}
|
||
|
||
//
|
||
handleNext(event) {
|
||
this.imageChange = false;
|
||
}
|
||
|
||
handleBack(event) {
|
||
this.imageChange = true;
|
||
}
|
||
|
||
handleFinish(event) {
|
||
this.showProcessModel = false;
|
||
}
|
||
|
||
//This method handles the type of store selected
|
||
multiStoreHandler(event) {
|
||
let subject = event.detail.subject;
|
||
this.storeSelectionType = event.detail.subject;
|
||
this.storeName = event.detail.storeName;
|
||
this.multiStoreHandlerEvent(subject);
|
||
this.loadLastOrderSyncTime();
|
||
}
|
||
|
||
//This method handles the functionality based on the type of store selected
|
||
multiStoreHandlerEvent(subject) {
|
||
if (subject == 'Edit') {
|
||
this.getconfigrationForDynamicRow();
|
||
this.showAuthorize = true;
|
||
this.multiStoreModel = false;
|
||
this.storeNameSelected = false;
|
||
this.accessTokenAvailable = false;
|
||
this.storeSelectionType = subject;
|
||
this.checkCredential = true;
|
||
this.handlestoreselection(false);
|
||
} else if (subject == 'Existing Store') {
|
||
this.showSpinnerHandler();
|
||
this.getconfigrationForDynamicRow();
|
||
|
||
this.multiStoreModel = false;
|
||
this.showAuthorize = false;
|
||
this.storeNameSelected = true;
|
||
this.accessTokenAvailable = true;
|
||
this.storeSelectionType = subject;
|
||
this.checkCredential = false;
|
||
this.handlestoreselection(true);
|
||
} else if (subject == 'New') {
|
||
|
||
// if (this.isSiteSupportedOrg && this.siteURLList != null && this.siteURLList.length > 0) {
|
||
|
||
// this.siteURL = this.siteURLList[0].value;
|
||
// }
|
||
this.storeUrl = '';
|
||
this.apiAccessToken = '';
|
||
// Clear via the backing field directly: the storeName setter now ignores
|
||
// empty values (so parent re-renders can't stomp on what's being typed),
|
||
// but starting a brand new store must still wipe any leftover name.
|
||
this._storeName = '';
|
||
this.showAuthorize = true;
|
||
|
||
if (this.priceBookList) {
|
||
for (let price of this.priceBookList) {
|
||
if (price.IsStandard) {
|
||
this.priceBook = price.value;
|
||
}
|
||
}
|
||
if (!this.priceBook) {
|
||
this.priceBook = this.priceBookList[0].value;
|
||
}
|
||
}
|
||
|
||
|
||
this.storeSelectedCurrency = this.storeSelectedCurrencyDefault;
|
||
this.multiStoreModel = false;
|
||
this.storeNameSelected = false;
|
||
this.accessTokenAvailable = true;
|
||
this.storeSelectionType = subject;
|
||
this.checkCredential = true;
|
||
this.handlestoreselection(false);
|
||
} else if (subject == 'Delete') {
|
||
this.storeUrl = '';
|
||
this.apiAccessToken = '';
|
||
this._storeName = '';
|
||
this.showAuthorize = false;
|
||
this.multiStoreModel = true;
|
||
this.storeNameSelected = false;
|
||
this.accessTokenAvailable = true;
|
||
}
|
||
}
|
||
|
||
// handle multistore selection, edit or new
|
||
handlestoreselection(isExistingStore) {
|
||
const customEvent = new CustomEvent('existingmultistore', {
|
||
detail: {
|
||
subject: isExistingStore,
|
||
storeName: this.storeName,
|
||
storeSelectType: this.storeSelectionType
|
||
}
|
||
});
|
||
this.dispatchEvent(customEvent);
|
||
}
|
||
|
||
// Save button on Shopify Credentials page
|
||
@api async handleSaveShopifyCredentials() {
|
||
this.openPricebookCurrencyModal = this.isPricebookOrCurrencyChanged;
|
||
if (!this.openPricebookCurrencyModal) {
|
||
this.showSpinnerHandler();
|
||
this.saveShopifyCredentials();
|
||
}
|
||
}
|
||
|
||
saveShopifyCredentials() {
|
||
// Show spinner while it is going to the next page
|
||
this.showSpinnerHandler();
|
||
let emailsValid = this.validateEmails();
|
||
console.log('>>> emailsValid =>' + emailsValid);
|
||
if (!emailsValid) {
|
||
return null;
|
||
}
|
||
console.log('>>> this.siteURL ' + this.siteURL);
|
||
console.log('>>> this.isSitePresentOrg ' + this.isSitePresentOrg);
|
||
console.log('>>> this.storeUrl ' + this.storeUrl);
|
||
console.log('>>> this.apiAccessToken ' + this.apiAccessToken);
|
||
console.log('>>> this.priceBook ' + this.priceBook);
|
||
if ((this.siteURL == null || this.siteURL.length == 0) && this.siteURLList != null && this.siteURLList.length > 0) {
|
||
this.siteURL = this.siteURLList[0].value;
|
||
console.log('Site url Fixed :: ' + this.siteURL);
|
||
}
|
||
|
||
//Validations for Input fields : Site URL, Shopify URL, Access Token, Pricebook
|
||
if (this.isSitePresentOrg && (this.siteURL == null || this.siteURL.length == 0)) {
|
||
console.log('>>> Professional Edition');
|
||
this.showNotification(this.label.Error, 'Error', this.label.Please_enter_Site_URL);
|
||
this.checkStatusSpinner = false;
|
||
this.closeSpinnerHandler();
|
||
}
|
||
else if (this.storeUrl == null || this.storeUrl == '') {
|
||
this.showNotification(this.label.Error, 'Error', this.label.Please_enter_Shopify_Store_URL);
|
||
}
|
||
else if (this.apiAccessToken == null || this.apiAccessToken == '') {
|
||
this.showNotification(this.label.Error, 'Error', this.label.Please_enter_ApiAccessToken);
|
||
}
|
||
else if (this.priceBook == null || this.priceBook == '') {
|
||
this.showNotification(this.label.Error, 'Error', this.label.Please_enter_PriceBook);
|
||
|
||
}
|
||
console.log('>>> this.siteURL 1 ' + this.siteURL);
|
||
console.log('>>> this.isSitePresentOrg ' + this.isSitePresentOrg);
|
||
console.log('>>> this.storeUrl ' + this.storeUrl);
|
||
console.log('>>> this.apiAccessToken ' + this.apiAccessToken);
|
||
console.log('>>> this.priceBook ' + this.priceBook);
|
||
|
||
if (((this.siteURL != null && this.siteURL != '') || !this.isSitePresentOrg) &&
|
||
this.storeUrl != null && this.storeUrl != '' &&
|
||
this.apiAccessToken != null && this.apiAccessToken != '' &&
|
||
this.priceBook != null && this.priceBook != '') {
|
||
console.log('@@@@ THIS IS PRCEED AUTHORIZATION');
|
||
|
||
this.proceedAuthorization(this.storeUrl, this.apiAccessToken);
|
||
let storename = this.configObject.HIC_ShopifySync__Shopify_URL__c.replaceAll('https://', '').replace('.myshopify.com', '').replace('/', '');
|
||
this.configObject.Name = storename;
|
||
this.configObject.HIC_ShopifySync__Store__c = storename;
|
||
}
|
||
}
|
||
|
||
//METHOD TO Validate Email
|
||
validateEmails() {
|
||
const emailRegex = /^([A-Za-z0-9_\-\.])+\@([A-Za-z0-9_\-\.])+\.([A-Za-z]{2,4})$/;
|
||
// Check if any email field is blank
|
||
for (let email of this.emailList) {
|
||
console.log('>>> THIS IS EMAIL LENGTH : ' + email.value.length);
|
||
if (!email.value.trim()) {
|
||
this.showNotification(this.label.Error, 'Error', this.label.Please_enter_Email);
|
||
return false;
|
||
} else if (email.value.length >= 39) {
|
||
|
||
this.showNotification(this.label.Error, 'Error', this.label.Email_must_be_less_than_or_equal_to_38_characters);
|
||
return false;
|
||
}
|
||
}
|
||
if (this.emailList.length < 1 && this.isAutomatic) {
|
||
this.emailList.push(this.currentUserEmail);
|
||
console.log('>>> Validated User Email in Automatic Mode :: ' + JSON.stringify(this.emailList));
|
||
}
|
||
if (this.emailList.length > 0) {
|
||
let emailString = [];
|
||
for (let i = this.emailList.length - 1; i >= 0; i--) {
|
||
if (this.emailList[0].value === '') {
|
||
this.addButtonVisible = true;
|
||
this.showNotification(this.label.Error, 'Error', this.label.Please_enter_Email);
|
||
return false;
|
||
}
|
||
else if (!this.emailList[i].value.match(emailRegex) && this.emailList[i].value != '') {
|
||
this.showNotification(this.label.Error, 'Error', this.label.Please_enter_valid_Email);
|
||
return false;
|
||
}
|
||
else {
|
||
if (this.emailList[i].value === null || this.emailList[i].value === '') {
|
||
this.emailList.splice(i, 1);
|
||
}
|
||
else if (!this.emailList[i].value.match(emailRegex)) {
|
||
this.showNotification(this.label.Error, 'Error', this.label.Please_enter_valid_Email);
|
||
return false;
|
||
}
|
||
// CHECK THE DUPLICATE EMAIL
|
||
else if (this.emailList[i].exists == true) {
|
||
this.showNotification(this.label.Error, 'Error', this.label.You_cannot_enter_Duplicate_Email);
|
||
return false;
|
||
}
|
||
|
||
}
|
||
}
|
||
|
||
emailString = this.emailList.map((email) => {
|
||
return {
|
||
'Name': email.value,
|
||
'HIC_ShopifySync__Email_Id__c': email.value
|
||
};
|
||
});
|
||
if (emailString.length >= 0 && this.emailListChanged) {
|
||
saveEmailSettings({ emaiList: JSON.stringify(emailString) }).then((result) => {
|
||
//Success
|
||
}).catch((error) => {
|
||
//Error occured
|
||
this.closeSpinnerHandler();
|
||
});
|
||
}
|
||
// THIS NEED A LOOK AGAIN NOT USING AUTHORIZE BUTTON
|
||
if ((this.storeSelectionType === 'New' && this.showAuthorize) || this.showCheck || !this.apiAccessToken || (!this.isSiteSupportedOrg && this.remoteSiteForStore)) {
|
||
this.showNotification(this.label.Error, 'Error', this.label.Please_authorize_Access_Token);
|
||
return;
|
||
}
|
||
}
|
||
return true;
|
||
}
|
||
|
||
//Value stored on onchange Store URL
|
||
changeStoreUrl(event) {
|
||
const typedValue = event.target.value.trim();
|
||
|
||
if (typedValue === '') {
|
||
// Explicit user clear — write the backing field directly so the
|
||
// storeName setter's "ignore falsy" guard (meant only to block the
|
||
// parent from stomping a typed value on re-render) doesn't also
|
||
// swallow this intentional clear and leave the old name stuck.
|
||
this._storeName = '';
|
||
this.storeUrl = null;
|
||
}
|
||
else {
|
||
this.storeName = typedValue;
|
||
if (this.storeName.includes("https://") || this.storeName.includes(".myshopify.com") || this.storeName.includes('http://')) {
|
||
this.storeUrl = this.storeName;
|
||
this._storeName = this.storeName.replace('https://', '').replace('http://', '').replace('.myshopify.com', '');
|
||
} else {
|
||
this.storeUrl = "https://" + this.storeName + ".myshopify.com";
|
||
}
|
||
}
|
||
if (this.storeName) {
|
||
this.accessTokenAvailable = false;
|
||
} else {
|
||
this.accessTokenAvailable = true;
|
||
}
|
||
}
|
||
|
||
//Value stored on onchange Access Token
|
||
changeAccessToken(event) {
|
||
this.apiAccessToken = event.target.value;
|
||
}
|
||
|
||
//Value stored on onchange ISO Currency Code
|
||
// METHOD TO HANDLE CURRENCY CHANGE MESSAGE
|
||
changeCurrencyISOCode(event) {
|
||
this.storeSelectedCurrency = event.target.value;
|
||
this.currencyCodeChanged = true;
|
||
if (this.storeSelectionType == 'New') {
|
||
this.oldCurrency = this.storeSelectedCurrencyDefault;
|
||
} else {
|
||
this.oldCurrency = this.configObject.CurrencyIsoCode;
|
||
}
|
||
this.changeCurrencyMessage = '';
|
||
|
||
if ((this.storeSelectedCurrency != this.oldCurrency)) {
|
||
this.isCurrencyChanged = true;
|
||
this.isPricebookOrCurrencyChanged = true;
|
||
this.changeCurrencyMessage = `${this.label.You_have_changed_the_currency} ${this.oldCurrency} to ${this.storeSelectedCurrency}`;
|
||
|
||
} else {
|
||
this.isCurrencyChanged = false;
|
||
this.isPricebookOrCurrencyChanged = false;
|
||
}
|
||
}
|
||
|
||
// This Method is for Changing Site URL
|
||
changeSiteUrl(event) {
|
||
this.siteURL = event.detail.value;
|
||
}
|
||
|
||
// METHOD TO HANDLE THE PRICEBOOK CHANGE MESSAGE
|
||
changePriceBook(event) {
|
||
this.priceBook = event.detail.value;
|
||
let oldPricebookValues = [];
|
||
let newPricebookValues = [];
|
||
let showpricebooklist = this.priceBookList;
|
||
|
||
oldPricebookValues = showpricebooklist.map((obj) => {
|
||
if (this.configObject.HIC_ShopifySync__Salesforce_Pricebook__c && obj.value == this.configObject.HIC_ShopifySync__Salesforce_Pricebook__c) {
|
||
this.oldPricebook = obj.label;
|
||
} else if (!this.configObject.HIC_ShopifySync__Salesforce_Pricebook__c) {
|
||
this.oldPricebook = this.priceBookList[0].label;
|
||
}
|
||
|
||
});
|
||
|
||
newPricebookValues = showpricebooklist.map((obj1) => {
|
||
if (obj1.value == this.priceBook) {
|
||
this.newPricebook = obj1.label;
|
||
}
|
||
});
|
||
|
||
this.changePricebookMessage = '';
|
||
if (this.newPricebook != this.oldPricebook) {
|
||
this.isPricebookChanged = true;
|
||
this.isPricebookOrCurrencyChanged = true;
|
||
this.changePricebookMessage = `${this.label.You_have_changed_the_pricebook} ${this.oldPricebook} to ${this.newPricebook}`;
|
||
} else {
|
||
this.isPricebookChanged = false;
|
||
this.isPricebookOrCurrencyChanged = false;
|
||
this.changePricebookMessage = '';
|
||
}
|
||
}
|
||
|
||
/**
|
||
* This Method is used to close the Modal of pricebook and currency change warning
|
||
*/
|
||
closePricebookCurrencyModal(event) {
|
||
this.openPricebookCurrencyModal = false;
|
||
}
|
||
|
||
//This method is used to permit to configure the values if each of them is not null
|
||
proceedAuthorization(shopifyUrl, PASSWORD) {
|
||
console.log('>>> proceedAuthorization');
|
||
if ((!this.showAuthorize && this.isSiteSupportedOrg) || (!this.isSiteSupportedOrg && !this.remoteSiteForStore)) {
|
||
this.saveConfigrationFunction();
|
||
} else {
|
||
this.showNotification(this.label.Error, 'Error', Please_check_the_credentials_first);
|
||
}
|
||
|
||
}
|
||
|
||
//Creating remote site when credentials are valid and store is existing store
|
||
createStoreUrlRemoteSite(shopifyUrl, PASSWORD) {
|
||
this.showSpinnerHandler();
|
||
console.log('SYNC MADE EASY: Credential => Creating Remote site for: ' + shopifyUrl);
|
||
createRemoteSiteSettings({ 'URL': shopifyUrl }).then((result) => {
|
||
console.log('SYNC MADE EASY: Credential => Remote site result: ' + result);
|
||
if (result === 'Success' || result === 'Remote Site setting') {
|
||
console.log('SYNC MADE EASY: Credential => Remote site OK, storeSelectionType: ' + this.storeSelectionType + ', isStoreAuthorized: ' + this.isStoreAuthorized);
|
||
// this.validCreds = true;
|
||
if (this.storeSelectionType !== 'Existing Store' && !this.isStoreAuthorized) {
|
||
console.log('SYNC MADE EASY: Credential => Calling checkCredentials');
|
||
this.checkCredentials(shopifyUrl, PASSWORD);
|
||
} else {
|
||
console.log('SYNC MADE EASY: Credential => Calling validateCredentials');
|
||
this.validateCredentials();
|
||
}
|
||
this.isRemoteSiteCreated = true;
|
||
} else {
|
||
console.log('SYNC MADE EASY: Credential => Remote site FAILED - spinner will be stuck here');
|
||
this.isRemoteSiteCreated = false;
|
||
}
|
||
}).catch((error) => {
|
||
console.log('SYNC MADE EASY: Credential => Remote site catch error: ' + JSON.stringify(error));
|
||
this.closeSpinnerHandler();
|
||
});
|
||
}
|
||
|
||
// Opens the Generate Token modal (validates store name first)
|
||
handleGenerateAccessToken() {
|
||
if (!this.storeName) {
|
||
this.showNotification('Error', 'Error', 'Please enter Shopify store name first');
|
||
return;
|
||
}
|
||
this.generatorClientId = '';
|
||
this.generatorClientSecret = '';
|
||
this.clientFieldsReadOnly = true; // keep readonly so the browser won't autofill
|
||
this.showGenerateTokenModal = true;
|
||
this.lockBackgroundScroll();
|
||
// Make sure the modal opens with its header visible instead of
|
||
// wherever the overlay's scroll happened to land (see the
|
||
// .sw-modal-overlay overflow-y fix in the CSS for why the overlay
|
||
// needs its own scroll at all).
|
||
requestAnimationFrame(() => {
|
||
const overlay = this.refs.genModalOverlay;
|
||
if (overlay) {
|
||
overlay.scrollTop = 0;
|
||
}
|
||
});
|
||
}
|
||
|
||
// Unlock the credential fields only once the user actively focuses them.
|
||
// Chrome/Edge skip readonly inputs during their autofill pass, so the fields stay empty.
|
||
handleCredentialFocus() {
|
||
this.clientFieldsReadOnly = false;
|
||
}
|
||
|
||
closeGenerateTokenModal() {
|
||
this.showGenerateTokenModal = false;
|
||
this.generatorClientId = '';
|
||
this.generatorClientSecret = '';
|
||
this.clientFieldsReadOnly = true;
|
||
// Stop any in-progress generation/polling when the user cancels/closes
|
||
this.isGeneratingToken = false;
|
||
this.countdownStarted = false;
|
||
this.clearTokenGenTimers();
|
||
this.unlockBackgroundScroll();
|
||
}
|
||
|
||
// NOTE: an earlier version of this fix moved the modal's DOM node to
|
||
// document.body ("portalling") to escape a suspected transformed
|
||
// ancestor breaking position:fixed. That was reverted — it caused a
|
||
// worse regression (Client ID / Client Secret stopped registering what
|
||
// was typed, since LWC's re-render reconciliation doesn't expect its
|
||
// own template nodes to have been moved externally). The centering/
|
||
// zoom-level issue may still need a proper fix (e.g. migrating this
|
||
// modal to the official lightning/modal base component), but a broken
|
||
// form is worse than an imperfectly-centered one, so this was rolled
|
||
// back to keep the modal in its normal place in the template.
|
||
|
||
// Presentation-only helpers: the modal overlay is position:fixed and covers
|
||
// the viewport, but mouse-wheel/touch scroll can still scroll the page
|
||
// behind it unless the body itself is locked. Reused by any modal that
|
||
// needs the same "background frozen while open" behavior.
|
||
lockBackgroundScroll() {
|
||
if (typeof document !== 'undefined' && document.body) {
|
||
document.body.style.overflow = 'hidden';
|
||
}
|
||
}
|
||
unlockBackgroundScroll() {
|
||
if (typeof document !== 'undefined' && document.body) {
|
||
document.body.style.overflow = '';
|
||
}
|
||
}
|
||
|
||
// Generate Token button stays blurred/disabled until BOTH Client ID and
|
||
// Client Secret have something typed in — matches the "blur until
|
||
// filled, then enable" requirement.
|
||
get disableGenerateTokenSubmit() {
|
||
const clientIdEmpty = !this.generatorClientId || !this.generatorClientId.trim();
|
||
const clientSecretEmpty = !this.generatorClientSecret || !this.generatorClientSecret.trim();
|
||
return clientIdEmpty || clientSecretEmpty;
|
||
}
|
||
|
||
get generateTokenSubmitClass() {
|
||
return this.disableGenerateTokenSubmit
|
||
? 'btn-success gen-token-submit-btn gen-token-submit-btn_disabled'
|
||
: 'btn-success gen-token-submit-btn';
|
||
}
|
||
|
||
handleGeneratorClientIdChange(event) {
|
||
this.generatorClientId = event.target.value;
|
||
}
|
||
|
||
handleGeneratorClientSecretChange(event) {
|
||
this.generatorClientSecret = event.target.value;
|
||
}
|
||
|
||
// Redirect URI the user must register in their Shopify app.
|
||
// Uses the @RestResource REST endpoint — works in every subscriber org
|
||
// without any manual Site page configuration.
|
||
get generatorRedirectUri() {
|
||
let base = this.siteURL || '';
|
||
if (base.endsWith('/')) base = base.slice(0, -1);
|
||
return base
|
||
? base + '/services/apexrest/HIC_ShopifySync/AuthenticateAllApexClass'
|
||
: '(Set a Site URL above first)';
|
||
}
|
||
|
||
// Full Shopify store URL shown (and copyable) in the Generate Token banner.
|
||
// Strips any protocol/suffix the user may have typed so it never doubles up.
|
||
get storeShopifyUrl() {
|
||
if (!this.storeName) {
|
||
return '';
|
||
}
|
||
const name = this.storeName
|
||
.replace('https://', '')
|
||
.replace('http://', '')
|
||
.replace('.myshopify.com', '')
|
||
.replace('/', '');
|
||
return 'https://' + name + '.myshopify.com';
|
||
}
|
||
|
||
// Copies the full Shopify store URL from the Generate Token banner
|
||
copyStoreUrl() {
|
||
const url = this.storeShopifyUrl;
|
||
if (!url) {
|
||
this.showNotification('Error', 'Error', 'Please enter a store name first');
|
||
return;
|
||
}
|
||
if (navigator.clipboard && navigator.clipboard.writeText) {
|
||
navigator.clipboard.writeText(url)
|
||
.then(() => this.showNotification('Success', 'Success', 'Store URL copied to clipboard'))
|
||
.catch(() => this.fallbackCopy(url, 'Store URL'));
|
||
} else {
|
||
this.fallbackCopy(url, 'Store URL');
|
||
}
|
||
}
|
||
|
||
copyRedirectUri() {
|
||
const uri = this.generatorRedirectUri;
|
||
if (!uri || uri.indexOf('http') !== 0) {
|
||
this.showNotification('Error', 'Error', 'Please select a Site URL first');
|
||
return;
|
||
}
|
||
// Modern clipboard API works only in a secure context; falls back to execCommand
|
||
// because the Salesforce Lightning iframe often blocks navigator.clipboard.
|
||
if (navigator.clipboard && navigator.clipboard.writeText) {
|
||
navigator.clipboard.writeText(uri)
|
||
.then(() => this.showNotification('Success', 'Success', 'Redirect URI copied to clipboard'))
|
||
.catch(() => this.fallbackCopy(uri));
|
||
} else {
|
||
this.fallbackCopy(uri);
|
||
}
|
||
}
|
||
|
||
// Fallback copy using a temporary textarea + execCommand (works inside the LWC iframe)
|
||
fallbackCopy(text, label) {
|
||
const what = label || 'Redirect URI';
|
||
try {
|
||
const ta = document.createElement('textarea');
|
||
ta.value = text;
|
||
ta.setAttribute('readonly', '');
|
||
ta.style.position = 'fixed';
|
||
ta.style.top = '0';
|
||
ta.style.left = '-9999px';
|
||
document.body.appendChild(ta);
|
||
ta.focus();
|
||
ta.select();
|
||
const ok = document.execCommand('copy');
|
||
document.body.removeChild(ta);
|
||
if (ok) {
|
||
this.showNotification('Success', 'Success', what + ' copied to clipboard');
|
||
} else {
|
||
this.showNotification('Error', 'Error', 'Could not copy automatically. Please copy it manually.');
|
||
}
|
||
} catch (e) {
|
||
this.showNotification('Error', 'Error', 'Could not copy automatically. Please copy it manually.');
|
||
}
|
||
}
|
||
|
||
// Called when user clicks "Generate Token" inside the modal
|
||
async handleGenerateTokenSubmit() {
|
||
// Re-sync from the actual DOM value right before validating: the fields
|
||
// were reported as showing "required" errors even with visible text
|
||
// typed in, which means this.generatorClientId/Secret can get out of
|
||
// sync with what's really in the box (e.g. autofill doesn't always
|
||
// fire a normal input event). Reading the live value here guarantees
|
||
// whatever the user can see typed is exactly what gets validated and
|
||
// sent, regardless of that gap.
|
||
if (this.refs.clientIdInput) {
|
||
this.generatorClientId = this.refs.clientIdInput.value;
|
||
}
|
||
if (this.refs.clientSecretInput) {
|
||
this.generatorClientSecret = this.refs.clientSecretInput.value;
|
||
}
|
||
|
||
const clientIdEmpty = !this.generatorClientId || !this.generatorClientId.trim();
|
||
const clientSecretEmpty = !this.generatorClientSecret || !this.generatorClientSecret.trim();
|
||
|
||
if (clientIdEmpty && clientSecretEmpty) {
|
||
this.showNotification('Error', 'Error', 'Client ID and Client Secret are required. Please fill out both fields.');
|
||
return;
|
||
}
|
||
if (clientIdEmpty) {
|
||
this.showNotification('Error', 'Error', 'Please enter the Client ID');
|
||
return;
|
||
}
|
||
if (clientSecretEmpty) {
|
||
this.showNotification('Error', 'Error', 'Please enter the Client Secret');
|
||
return;
|
||
}
|
||
|
||
const redirectUri = this.generatorRedirectUri;
|
||
if (!this.siteURL) {
|
||
this.showNotification('Error', 'Error', 'Please select a Site URL before generating a token');
|
||
return;
|
||
}
|
||
|
||
this.isGeneratingToken = true;
|
||
|
||
try {
|
||
// Ensure a Remote Site Setting exists for the Shopify store — the OAuth
|
||
// callback page needs it to make the server-side token-exchange callout.
|
||
const shopifyUrl = 'https://' + this.storeName + '.myshopify.com';
|
||
await createRemoteSiteSettings({ URL: shopifyUrl });
|
||
|
||
// Verify it actually persisted; otherwise the callback callout will be
|
||
// blocked as "Unauthorized endpoint". Fail fast with a clear message.
|
||
const remoteSiteReady = await checkStoreNameRemoteSite({ shopifyStoreUrl: shopifyUrl });
|
||
if (!remoteSiteReady) {
|
||
this.isGeneratingToken = false;
|
||
this.showNotification(
|
||
'Error',
|
||
'Error',
|
||
'Could not authorize the store endpoint automatically. Please add a Remote Site Setting for ' +
|
||
shopifyUrl + ' (Setup → Remote Site Settings), then try Generate Token again.'
|
||
);
|
||
return;
|
||
}
|
||
|
||
// Apex builds the auth URL and stores credentials server-side
|
||
const authUrl = await initShopifyTokenGeneration({
|
||
storeName: this.storeName,
|
||
clientId: this.generatorClientId.trim(),
|
||
clientSecret: this.generatorClientSecret.trim(),
|
||
redirectUri: redirectUri
|
||
});
|
||
|
||
// Open Shopify OAuth in a popup, centered on the CURRENT browser window
|
||
// (dual-monitor aware — screen.width alone lands off-center on secondary displays).
|
||
// IMPORTANT: the window name must be UNIQUE per open — browsers reuse the
|
||
// geometry of an existing same-named window and ignore top/left/width/height,
|
||
// which made the popup reopen half-screen/docked after being snapped once.
|
||
const width = 800, height = 700;
|
||
const dualLeft = window.screenLeft !== undefined ? window.screenLeft : (window.screenX || 0);
|
||
const dualTop = window.screenTop !== undefined ? window.screenTop : (window.screenY || 0);
|
||
const winWidth = window.outerWidth || window.innerWidth || screen.availWidth;
|
||
const winHeight = window.outerHeight || window.innerHeight || screen.availHeight;
|
||
const left = Math.max(0, Math.round(dualLeft + (winWidth - width) / 2));
|
||
const top = Math.max(0, Math.round(dualTop + (winHeight - height) / 2));
|
||
const popup = window.open(
|
||
authUrl,
|
||
'ShopifyOAuth_' + Date.now(),
|
||
`popup=yes,width=${width},height=${height},top=${top},left=${left},toolbar=no,menubar=no,scrollbars=yes,resizable=yes`
|
||
);
|
||
|
||
// Reset any prior timers/watchers.
|
||
this.clearTokenGenTimers();
|
||
this.countdownStarted = false;
|
||
|
||
// Watch the Shopify popup. The token is DISPLAYED in that window (it is
|
||
// never saved by the callback page — no guest-user DML). Once the user
|
||
// copies it and closes the popup, prompt them to paste it manually.
|
||
let waited = 0;
|
||
this.popupWatcher = setInterval(() => {
|
||
waited += 500;
|
||
if (!popup || popup.closed) {
|
||
clearInterval(this.popupWatcher);
|
||
this.finishTokenGeneration();
|
||
} else if (waited >= 300000) {
|
||
// Safety net: popup left open for 5 minutes — give up.
|
||
clearInterval(this.popupWatcher);
|
||
if (this.isGeneratingToken) {
|
||
this.showNotification('Error', 'Error', 'Authorization was not completed. Please try again.');
|
||
this.closeGenerateTokenModal();
|
||
}
|
||
}
|
||
}, 500);
|
||
|
||
} catch (error) {
|
||
console.error('Generate token error:', error);
|
||
const msg = (error.body && error.body.message) ? error.body.message : 'Failed to initiate token generation';
|
||
this.showNotification('Error', 'Error', msg);
|
||
this.isGeneratingToken = false;
|
||
}
|
||
}
|
||
|
||
// Called once the Shopify OAuth popup has closed. The token was displayed in
|
||
// the popup for the user to copy — close the modal and prompt them to paste
|
||
// it into the Admin API access token field, then run Check Credential.
|
||
finishTokenGeneration() {
|
||
if (!this.isGeneratingToken) {
|
||
return;
|
||
}
|
||
this.isGeneratingToken = false;
|
||
this.countdownStarted = false;
|
||
this.clearTokenGenTimers();
|
||
this.showGenerateTokenModal = false;
|
||
this.generatorClientId = '';
|
||
this.generatorClientSecret = '';
|
||
this.clientFieldsReadOnly = true;
|
||
this.showNotification('Success', 'Success',
|
||
'Paste the copied access token into the "Admin API access token" field, then click "Check Credential".');
|
||
}
|
||
|
||
// Clears every timer/interval/watcher used by the token generation flow.
|
||
clearTokenGenTimers() {
|
||
if (this.checkAccessTokenInterval) { clearInterval(this.checkAccessTokenInterval); }
|
||
if (this.countdownInterval) { clearInterval(this.countdownInterval); }
|
||
if (this.tokenGenTimeout) { clearTimeout(this.tokenGenTimeout); }
|
||
if (this.popupWatcher) { clearInterval(this.popupWatcher); }
|
||
}
|
||
|
||
// Handling button 'Check Credentials'
|
||
checkStoreCredentials() {
|
||
console.log('this.siteURL-->>', this.siteURL);
|
||
console.log('this.storeName-->>', this.storeName);
|
||
console.log('this.storeUrl-->>', this.storeUrl);
|
||
if (!this.storeName && !this.storeUrl) {
|
||
this.showNotification(this.label.Error, 'Error', this.label.Please_enter_shopify_store_name);
|
||
}
|
||
else if (this.isSiteSupportedOrg) {
|
||
this.createStoreUrlRemoteSite(this.storeUrl, this.apiAccessToken);
|
||
} else {
|
||
this.checkStoreNameRemoteSiteSetting();
|
||
}
|
||
}
|
||
|
||
//VALIDATE CREDENTIALS WHEN STORE IS EDIT OR NEW
|
||
validateCredentials() {
|
||
if (this.storeUrl && this.apiAccessToken) {
|
||
console.log('SYNC MADE EASY: Credential => Checking Credentials');
|
||
checkCreds({ 'shopifyUrl': this.storeUrl, 'PASSWORD': this.apiAccessToken }).then((result) => {
|
||
|
||
if (result == true) {
|
||
this.accessTokenAvailable = true;
|
||
this.storeNameSelected = true;
|
||
this.isStoreAuthorized = true;
|
||
this.showAuthorize = false;
|
||
this.checkAccessScope(this.storeUrl, this.apiAccessToken);
|
||
} else {
|
||
this.showNotification(this.label.Error, 'Error', this.label.Store_is_Not_Authorized);
|
||
this.showAuthorize = true;
|
||
this.isStoreAuthorized = false;
|
||
this.accessTokenAvailable = false;
|
||
this.storeNameSelected = false;
|
||
this.storeSelectionType = 'Edit';
|
||
|
||
}
|
||
}).catch((error) => {
|
||
this.closeSpinnerHandler();
|
||
});
|
||
} else {
|
||
if (!this.storeUrl) {
|
||
this.showNotification(this.label.Error, 'Error', this.label.Please_enter_store_name);
|
||
}
|
||
else if (!this.apiAccessToken) {
|
||
this.showNotification(this.label.Error, 'Error', this.label.Please_enter_password);
|
||
}
|
||
}
|
||
}
|
||
|
||
//CHECK CREDENTIALS WHEN STORE IS EDIT OR NEW
|
||
checkCredentials(shopifyUrl, PASSWORD) {
|
||
console.log('SYNC MADE EASY: Credential => checkCredentials called, storeSelectionType: ' + this.storeSelectionType + ', isStoreAuthorized: ' + this.isStoreAuthorized + ', AccessScope: ' + this.AccessScope);
|
||
if (this.storeSelectionType === 'Existing Store' && !this.AccessScope) {
|
||
console.log('SYNC MADE EASY: Credential => Existing Store path => saveConfigrationFunction');
|
||
this.saveConfigrationFunction();
|
||
} else if (!this.isStoreAuthorized) {
|
||
if (!shopifyUrl) {
|
||
this.showNotification(this.label.Error, 'Error', this.label.Please_enter_store_name);
|
||
}
|
||
else if (!PASSWORD) {
|
||
this.showNotification(this.label.Error, 'Error', this.label.Please_enter_password);
|
||
}
|
||
if (shopifyUrl && PASSWORD) {
|
||
console.log('SYNC MADE EASY: Credential => Calling checkCreds Apex');
|
||
checkCreds({ 'shopifyUrl': shopifyUrl, 'PASSWORD': PASSWORD }).then((result) => {
|
||
console.log('SYNC MADE EASY: Credential => checkCreds result: ' + result);
|
||
if (result == true) {
|
||
this.accessTokenAvailable = true;
|
||
this.storeNameSelected = true;
|
||
this.isStoreAuthorized = true;
|
||
this.showAuthorize = false;
|
||
this.remoteSiteForStore = false;
|
||
this.checkAccessScope(shopifyUrl, PASSWORD);
|
||
} else {
|
||
console.log('SYNC MADE EASY: Credential => checkCreds returned false - spinner will be stuck here');
|
||
// open modal
|
||
this.showNotification(this.label.Error, 'Error', this.label.Store_is_Not_Authorized);
|
||
this.showAuthorize = true;
|
||
this.isStoreAuthorized = false;
|
||
this.accessTokenAvailable = false;
|
||
this.storeNameSelected = false;
|
||
this.storeSelectionType = 'Edit';
|
||
}
|
||
}).catch((error) => {
|
||
console.log('SYNC MADE EASY: Credential => checkCreds catch: ' + JSON.stringify(error));
|
||
this.closeSpinnerHandler();
|
||
});
|
||
}
|
||
} else {
|
||
console.log('SYNC MADE EASY: Credential => isStoreAuthorized=true path => checkAccessScope');
|
||
this.checkAccessScope(shopifyUrl, PASSWORD);
|
||
}
|
||
}
|
||
|
||
//CHECK ACCESS SCOPE
|
||
checkAccessScope(shopifyUrl, PASSWORD) {
|
||
console.log('SYNC MADE EASY: Credential => Checking Access Scope');
|
||
checkAccesScope({ 'shopifyUrl': shopifyUrl, 'PASSWORD': PASSWORD }).then((result) => {
|
||
this.listShopfyObjectAccessError = result;
|
||
this.handleAccessScope();
|
||
|
||
}).catch((error) => {
|
||
this.closeSpinnerHandler();
|
||
this.showNotification(this.label.Error, 'Error', this.label.Store_is_Not_having_Access);
|
||
});
|
||
}
|
||
|
||
handleAccessScope() {
|
||
this.closeSpinnerHandler();
|
||
//this.showAuthorize = false;
|
||
this.listAccessScopeError = [];
|
||
this.AccessScope = false;
|
||
if (this.listShopfyObjectAccessError) {
|
||
if (this.productSyncValue && !this.listShopfyObjectAccessError.includes('read_products')) {
|
||
this.AccessScope = true;
|
||
if (!this.listAccessScopeError.includes('Product')) {
|
||
this.listAccessScopeError.push('Product');
|
||
}
|
||
}
|
||
if (this.collectionSyncValue && !this.listShopfyObjectAccessError.includes('read_products')) {
|
||
this.AccessScope = true;
|
||
if (!this.listAccessScopeError.includes('collection')) {
|
||
this.listAccessScopeError.push('collection');
|
||
}
|
||
}
|
||
if (this.inventorySyncValue && (!this.listShopfyObjectAccessError.includes('read_inventory') || !this.listShopfyObjectAccessError.includes('read_locations'))) {
|
||
this.AccessScope = true;
|
||
if (!this.listAccessScopeError.includes('Location/Inventory')) {
|
||
this.listAccessScopeError.push('Location/Inventory');
|
||
}
|
||
}
|
||
if (this.customerSyncValue && !this.listShopfyObjectAccessError.includes('read_customers')) {
|
||
this.AccessScope = true;
|
||
if (!this.listAccessScopeError.includes('Customer')) {
|
||
this.listAccessScopeError.push('Customer');
|
||
}
|
||
}
|
||
if (this.orderSyncValue && !this.listShopfyObjectAccessError.includes('read_orders')) {
|
||
this.AccessScope = true;
|
||
if (!this.listAccessScopeError.includes('Orders')) {
|
||
this.listAccessScopeError.push('Orders');
|
||
}
|
||
}
|
||
if (this.draftSyncValue && !this.listShopfyObjectAccessError.includes('read_draft_orders')) {
|
||
this.AccessScope = true;
|
||
if (!this.listAccessScopeError.includes('Draft Orders')) {
|
||
this.listAccessScopeError.push('Draft Orders');
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
// if (!this.AccessScope) {
|
||
// this.apiAccessToken = PASSWORD;
|
||
// } else {
|
||
// if (this.storeSelectionType !== 'Existing Store') {
|
||
// this.showNotification(this.label.Error, 'Error', 'You have no Access in Shopify Org');
|
||
// }
|
||
// }
|
||
}
|
||
|
||
//Updating store settings
|
||
saveConfigrationFunction() {
|
||
console.log('>>> saveConfigrationFunction');
|
||
console.log('SYNC MADE EASY: SAVE CONFIGURATIONS');
|
||
|
||
if (this.siteURL) {
|
||
this.configObject.HIC_ShopifySync__Salesforce_Site_URL__c = this.siteURL;
|
||
}
|
||
|
||
if (this.storeUrl) {
|
||
this.configObject.HIC_ShopifySync__Shopify_URL__c = this.storeUrl;
|
||
}
|
||
|
||
if ((this.storapiAccessTokeneUrl && this.storapiAccessTokeneUrl != '') ||
|
||
(this.apiAccessToken && this.apiAccessToken != '')) {
|
||
this.configObject.HIC_ShopifySync__Private_Key_Password__c = this.apiAccessToken;
|
||
}
|
||
|
||
if (this.priceBook) {
|
||
this.configObject.HIC_ShopifySync__Salesforce_Pricebook__c = this.priceBook;
|
||
}
|
||
|
||
this.configObject.HIC_ShopifySync__Product_Sync__c = this.productSyncValue;
|
||
this.configObject.HIC_ShopifySync__Customer_Sync__c = this.customerSyncValue;
|
||
this.configObject.HIC_ShopifySync__Collection_Sync__c = this.collectionSyncValue;
|
||
this.configObject.HIC_ShopifySync__Collection__c = this.collectionSyncValue;
|
||
this.configObject.HIC_ShopifySync__Inventory_Location__c = this.inventorySyncValue;
|
||
this.configObject.HIC_ShopifySync__Inventory_Location_Sync__c = this.inventorySyncValue;
|
||
this.configObject.HIC_ShopifySync__Order_Sync__c = this.orderSyncValue;
|
||
|
||
// Order mapping handling (UNCHANGED)
|
||
if (!this.orderSyncValue) {
|
||
this.configObject.HIC_ShopifySync__Order__c = false;
|
||
this.configObject.HIC_ShopifySync__Opportunity__c = false;
|
||
this.configObject.HIC_ShopifySync__Order_Line_Item__c = false;
|
||
}
|
||
|
||
if (
|
||
this.orderSyncValue &&
|
||
((!this.configObject.hasOwnProperty('HIC_ShopifySync__Order__c') ||
|
||
!this.configObject.HIC_ShopifySync__Order__c) &&
|
||
(!this.configObject.hasOwnProperty('HIC_ShopifySync__Opportunity__c') ||
|
||
!this.configObject.HIC_ShopifySync__Opportunity__c))
|
||
) {
|
||
this.configObject.HIC_ShopifySync__Order__c = true;
|
||
this.configObject.HIC_ShopifySync__Opportunity__c = true;
|
||
this.configObject.HIC_ShopifySync__Order_Line_Item__c = true;
|
||
}
|
||
|
||
// Opportunity Product
|
||
if (this.configObject.HIC_ShopifySync__Opportunity__c === false) {
|
||
this.configObject.HIC_ShopifySync__Opportunity_Line_Item__c = false;
|
||
} else if (
|
||
this.configObject.HIC_ShopifySync__Order__c === false &&
|
||
this.configObject.HIC_ShopifySync__Opportunity__c
|
||
) {
|
||
this.configObject.HIC_ShopifySync__Opportunity_Line_Item__c = true;
|
||
} else if (this.configObject.HIC_ShopifySync__Order_Sync__c === true) {
|
||
this.configObject.HIC_ShopifySync__Opportunity_Line_Item__c = true;
|
||
}
|
||
|
||
// Order fulfillment
|
||
if (this.orderSyncChanged) {
|
||
this.configObject.HIC_ShopifySync__Order_Fulfillment__c = this.orderSyncValue;
|
||
}
|
||
|
||
// Draft Order – SAVE ONLY (FIXED)
|
||
this.configObject.HIC_ShopifySync__Draft_Order_Sync__c = this.draftSyncValue;
|
||
|
||
// Safety only: Draft OFF → children OFF
|
||
if (!this.draftSyncValue) {
|
||
this.configObject.HIC_ShopifySync__Enable_Draft_Order__c = false;
|
||
this.configObject.HIC_ShopifySync__Enable_Opportunity_for_SDO__c = false;
|
||
this.configObject.HIC_ShopifySync__Specify_Close_Date__c = false;
|
||
}
|
||
|
||
// Draft Order Opportunity (New store rule – unchanged)
|
||
if (this.storeSelectionType === 'New') {
|
||
this.configObject.HIC_ShopifySync__Enable_Opportunity_for_SDO__c = true;
|
||
this.configObject.HIC_ShopifySync__Specify_Close_Date__c = false;
|
||
this.configObject.HIC_ShopifySync__Enable_Draft_Order__c = false;
|
||
this.configObject.HIC_ShopifySync__Order__c = true;
|
||
this.configObject.HIC_ShopifySync__Order_Line_Item__c = true;
|
||
this.configObject.HIC_ShopifySync__Opportunity__c = false;
|
||
this.configObject.HIC_ShopifySync__Opportunity_Line_Item__c = false;
|
||
this.configObject.HIC_ShopifySync__Order_Fulfillment__c = false;
|
||
}
|
||
|
||
this.configObject.CurrencyIsoCode = this.storeSelectedCurrency;
|
||
|
||
this.updatePriceBookCurrencyOpportunityStage();
|
||
}
|
||
|
||
//Updating pricebook, Currency, Opportunity stage name
|
||
updatePriceBookCurrencyOpportunityStage() {
|
||
console.log('SYNC MADE EASY: SAVE ALL DETAILS');
|
||
let priceBookId = null;
|
||
let currencyCode = null;
|
||
let stageName = null;
|
||
let configObject = JSON.stringify(this.configObject);
|
||
if (this.isPricebookChanged || this.storeSelectionType === 'New') {
|
||
priceBookId = this.priceBook;
|
||
}
|
||
if (this.isCurrencyChanged || this.storeSelectionType === 'New') {
|
||
currencyCode = this.storeSelectedCurrency;
|
||
}
|
||
if (this.storeSelectionType === 'New') {
|
||
stageName = 'New';
|
||
}
|
||
if (this.storeSelectionType === 'Existing Store' && !this.collectionSyncChanged && !this.orderSyncChanged &&
|
||
!this.draftSyncChanged && !this.inventorySyncChanged && !this.isPricebookChanged && !this.isCurrencyChanged) {
|
||
configObject = null;
|
||
}
|
||
if (this.AccessScope) {
|
||
checkAccesScope({ 'shopifyUrl': this.storeUrl, 'PASSWORD': this.apiAccessToken }).then((result) => {
|
||
this.listShopfyObjectAccessError = result;
|
||
this.handleAccessScope();
|
||
|
||
if (!this.AccessScope) {
|
||
if (priceBookId || currencyCode || stageName || configObject) {
|
||
updatePriceBookCurrencyStage({ storeName: this.storeName, defaultStageName: stageName, priceBookId: priceBookId, currencyCode: currencyCode, configObj: configObject }).then((result) => {
|
||
console.log('>>> ACCESS SCOPE REUSLT :: ' + JSON.stringify(result));
|
||
let proceedNext = true;
|
||
if (stageName && !result.OppStageName) {
|
||
proceedNext = false;
|
||
}
|
||
if (priceBookId && !result.PriceBook) {
|
||
proceedNext = false;
|
||
}
|
||
if (currencyCode && !result.Currency) {
|
||
proceedNext = false;
|
||
}
|
||
if (result.hasOwnProperty('ConfigObj') && !result.ConfigObj) {
|
||
proceedNext = false;
|
||
}
|
||
|
||
if (proceedNext) {
|
||
const selectStore = new CustomEvent('save', {
|
||
detail: this.storeName
|
||
});
|
||
this.dispatchEvent(selectStore);
|
||
} else {
|
||
this.closeSpinnerHandler();
|
||
}
|
||
|
||
}).catch((error) => {
|
||
this.closeSpinnerHandler();
|
||
});
|
||
} else {
|
||
const selectStore = new CustomEvent('save', {
|
||
detail: this.storeName
|
||
});
|
||
this.dispatchEvent(selectStore);
|
||
}
|
||
} else {
|
||
this.showNotification(this.label.Error, 'Error', this.label.You_do_not_have_access_of_selected_object);
|
||
|
||
}
|
||
}).catch((error) => {
|
||
this.closeSpinnerHandler();
|
||
this.showNotification(this.label.Error, 'Error', this.label.Store_is_Not_having_Access);
|
||
});
|
||
} else {
|
||
if (priceBookId || currencyCode || stageName || configObject) {
|
||
updatePriceBookCurrencyStage({ storeName: this.storeName, defaultStageName: stageName, priceBookId: priceBookId, currencyCode: currencyCode, configObj: configObject }).then((result) => {
|
||
console.log('>>> THIS IS PRICEBOOK RESULT :: ' + JSON.stringify(result));
|
||
let proceedNext = true;
|
||
if (stageName && !result.OppStageName) {
|
||
proceedNext = false;
|
||
}
|
||
if (priceBookId && !result.PriceBook) {
|
||
proceedNext = false;
|
||
}
|
||
if (currencyCode && !result.Currency) {
|
||
proceedNext = false;
|
||
}
|
||
if (result.hasOwnProperty('ConfigObj') && !result.ConfigObj) {
|
||
proceedNext = false;
|
||
}
|
||
|
||
if (proceedNext) {
|
||
console.log('>>> ELSE PART STORE NAME :: ' + this.storeName);
|
||
const selectStore = new CustomEvent('save', {
|
||
detail: this.storeName
|
||
});
|
||
this.dispatchEvent(selectStore);
|
||
} else {
|
||
this.closeSpinnerHandler();
|
||
}
|
||
|
||
}).catch((error) => {
|
||
this.closeSpinnerHandler();
|
||
});
|
||
} else {
|
||
console.log('>>> ELSE PART STORE NAME :: ' + this.storeName);
|
||
const selectStore = new CustomEvent('save', {
|
||
detail: this.storeName
|
||
});
|
||
this.dispatchEvent(selectStore);
|
||
}
|
||
}
|
||
}
|
||
|
||
/*
|
||
* Helps to Show a Toast Message
|
||
*/
|
||
// Custom gradient toast (success = green→teal, error = red) instead of
|
||
// ShowToastEvent. Same (title, variant, message) signature/callers.
|
||
showNotification(title, variant, message) {
|
||
this.closeSpinnerHandler();
|
||
const v = (variant || '').toString().toLowerCase();
|
||
const kind = v === 'error' ? 'error' : v === 'success' ? 'success' : v === 'warning' ? 'warning' : 'info';
|
||
const id = 't' + (this._toastSeq++);
|
||
this.toasts = [...this.toasts, {
|
||
id, title, message,
|
||
cssClass: 'sw-toast sw-toast-' + kind,
|
||
iconLabel: kind === 'error' ? '⚠' : kind === 'success' ? '✓' : kind === 'warning' ? '!' : 'ℹ'
|
||
}];
|
||
if (kind !== 'error') {
|
||
// eslint-disable-next-line @lwc/lwc/no-async-operation
|
||
setTimeout(() => this._dismissToast(id), 4000);
|
||
}
|
||
}
|
||
|
||
_dismissToast(id) {
|
||
this.toasts = this.toasts.filter((t) => t.id !== id);
|
||
}
|
||
|
||
handleToastClose(event) {
|
||
this._dismissToast(event.currentTarget.dataset.id);
|
||
}
|
||
|
||
//This Method is used to close spinner
|
||
closeSpinnerHandler() {
|
||
this.isLoading = false;
|
||
const stopSpinner = new CustomEvent('closespinner', {
|
||
detail: 'Stop Spinner'
|
||
});
|
||
this.dispatchEvent(stopSpinner);
|
||
}
|
||
|
||
//This Method is used to show spinner
|
||
showSpinnerHandler() {
|
||
this.isLoading = true;
|
||
const startSpinner = new CustomEvent('showspinner', {
|
||
detail: 'Start Spinner'
|
||
});
|
||
this.dispatchEvent(startSpinner);
|
||
}
|
||
|
||
//This method controls multistore modal visibility
|
||
@api
|
||
openMultiStoreController() {
|
||
this.multiStoreModel = true;
|
||
}
|
||
|
||
//Edit Existing Store
|
||
deleteStore() {
|
||
console.log('SYNC MADE EASY: Credential => Deleting Store');
|
||
RemoveStore({ store: this.storeName }).then((result) => {
|
||
if (result) {
|
||
//Success
|
||
}
|
||
}).catch((error) => {
|
||
//Error occured
|
||
this.closeSpinnerHandler();
|
||
});
|
||
|
||
}
|
||
|
||
createRemoteSiteForStore() {
|
||
if (!this.storeName && !this.storeUrl) {
|
||
this.showNotification(this.label.Error, 'Error', this.label.Please_enter_shopify_store_name);
|
||
}
|
||
else if (!this.apiAccessToken) {
|
||
this.showNotification(this.label.Error, 'Error', this.label.Please_enter_admin_api_access_token);
|
||
} else {
|
||
this.showAuthorize = false;
|
||
this.showSpinnerHandler();
|
||
checkStoreNameRemoteSite({ shopifyStoreUrl: this.storeUrl }).then((result) => {
|
||
if (result) {
|
||
this.remoteSiteForStore = false;
|
||
if (this.storeSelectionType !== 'Existing Store' && !this.isStoreAuthorized) {
|
||
|
||
this.checkCredentials(this.storeUrl, this.apiAccessToken);
|
||
} else {
|
||
this.validateCredentials();
|
||
}
|
||
this.isRemoteSiteCreated = true;
|
||
} else {
|
||
this.closeSpinnerHandler();
|
||
this.isRemoteSiteCreated = false;
|
||
this.remoteSiteForStore = true;
|
||
|
||
|
||
this.showNotification(this.label.Error, 'Error', this.label.Create_Remote_site_Setting);
|
||
var currentDateTime = new Date();
|
||
var datetime = currentDateTime.getFullYear() +
|
||
("0" + (currentDateTime.getMonth() + 1)).slice(-2) +
|
||
("0" + currentDateTime.getDate()).slice(-2) +
|
||
("0" + currentDateTime.getHours()).slice(-2) +
|
||
("0" + currentDateTime.getMinutes()).slice(-2) +
|
||
("0" + currentDateTime.getSeconds()).slice(-2);
|
||
var siteName = 'HIC_SME_' + datetime;
|
||
var serverUrlLink = 'https://' + this.storeName + '.myshopify.com';
|
||
window.open("/lightning/setup/SecurityRemoteProxy/page?address=/0rp/e?SiteName=" + siteName + "%26EndpointUrl=" + serverUrlLink, "_blank");
|
||
}
|
||
console.log('@@@ checkStoreNameRemoteSite : ' + result);
|
||
}).catch((error) => {
|
||
this.closeSpinnerHandler();
|
||
console.log('@@@ Error checkStoreNameRemoteSite : ' + error);
|
||
});
|
||
}
|
||
|
||
}
|
||
|
||
//Called on Check credentials button for not site supported orgs
|
||
checkStoreNameRemoteSiteSetting() {
|
||
this.showSpinnerHandler();
|
||
console.log('@@@ this.storeUrl checkStoreNameRemoteSite: ' + this.storeUrl);
|
||
checkStoreNameRemoteSite({ shopifyStoreUrl: this.storeUrl }).then((result) => {
|
||
if (result) {
|
||
this.remoteSiteForStore = false;
|
||
if (this.storeSelectionType !== 'Existing Store' && !this.isStoreAuthorized) {
|
||
|
||
this.checkCredentials(this.storeUrl, this.apiAccessToken);
|
||
} else {
|
||
this.validateCredentials();
|
||
}
|
||
this.isRemoteSiteCreated = true;
|
||
} else {
|
||
this.closeSpinnerHandler();
|
||
this.isRemoteSiteCreated = false;
|
||
this.remoteSiteForStore = false;
|
||
this.showAuthorize = true;
|
||
this.showNotification(this.label.Error, 'Error', this.label.Create_Remote_site_Setting);
|
||
}
|
||
console.log('@@@ checkStoreNameRemoteSite : ' + result);
|
||
}).catch((error) => {
|
||
this.closeSpinnerHandler();
|
||
console.log('@@@ Error checkStoreNameRemoteSite : ' + error);
|
||
});
|
||
}
|
||
|
||
lockEmail(event) {
|
||
const index = event.target.dataset.id;
|
||
|
||
this.emailList = this.emailList.map(e =>
|
||
e.index == index && e.isValid
|
||
? { ...e, isEditable: false }
|
||
: e
|
||
);
|
||
}
|
||
|
||
editEmail(event) {
|
||
const index = event.target.dataset.id;
|
||
|
||
this.emailList = this.emailList.map(e =>
|
||
e.index == index
|
||
? { ...e, isEditable: true }
|
||
: e
|
||
);
|
||
}
|
||
get showEmailLimitText() {
|
||
return this.emailList.length < 3;
|
||
}
|
||
|
||
updateDeleteState() {
|
||
const canDelete = this.emailList.length > 1;
|
||
|
||
this.emailList = this.emailList.map(email => {
|
||
return {
|
||
...email,
|
||
canDelete: canDelete
|
||
};
|
||
});
|
||
}
|
||
|
||
// Show credential action buttons only during first-time setup
|
||
// (i.e. store name or access token not yet saved)
|
||
get showCredentialActions() {
|
||
return !(this.storeNameSelected && this.accessTokenAvailable);
|
||
}
|
||
|
||
// Generate Access Token button
|
||
get disableGenerateAccessToken() {
|
||
// Enabled only when store name exists AND access token is empty
|
||
let checking = !this.storeName || !!this.apiAccessToken;
|
||
this.disableInfo = !checking;
|
||
|
||
return checking;
|
||
}
|
||
|
||
|
||
// Check Credential button
|
||
get disableCheckCredential() {
|
||
// Enabled only when BOTH exist
|
||
return !(this.storeName && this.apiAccessToken);
|
||
}
|
||
|
||
fetchDefaultSiteUrl() {
|
||
console.log('SYNC MADE EASY: Fetching Default Site URL from Configuration');
|
||
getDefaultSiteUrl()
|
||
.then((result) => {
|
||
if (result) {
|
||
this.defaultSiteUrl = result;
|
||
this.siteURL = result;
|
||
console.log('>>> Default Site URL fetched: ' + result);
|
||
} else {
|
||
console.log('>>> No Default Site URL found in configuration');
|
||
this.siteURL = '';
|
||
}
|
||
})
|
||
.catch((error) => {
|
||
console.error('>>> Error fetching default site URL: ' + JSON.stringify(error));
|
||
this.siteURL = '';
|
||
});
|
||
}
|
||
|
||
// ========================================================================
|
||
// NEW — presentation-only members added for the reference-mockup restyle.
|
||
// Nothing above this line was changed from the original component. These
|
||
// do not touch any Apex call or saved record.
|
||
// ========================================================================
|
||
|
||
// Show/hide toggle for the Access Token field (mockup has an eye icon;
|
||
// the original component only ever rendered type="password").
|
||
@track showToken = false;
|
||
|
||
get tokenInputType() {
|
||
return this.showToken ? 'text' : 'password';
|
||
}
|
||
|
||
toggleTokenVisibility() {
|
||
this.showToken = !this.showToken;
|
||
}
|
||
|
||
// Dynamic "You can add up to N more email(s)" hint — showEmailLimitText
|
||
// (real getter above) only decides *whether* to show this text; the
|
||
// label it used to point at was a static string that always said "3"
|
||
// no matter how many were already in the list. This recomputes the
|
||
// real remaining count off the same real emailList array.
|
||
get emailLimitHintText() {
|
||
this.stopEmailError = false;
|
||
const remaining = 3 - this.emailList.length;
|
||
if (remaining <= 0) {
|
||
this.stopEmailError =true;
|
||
return 'Maximum of 3 emails reached.';
|
||
}
|
||
return `You can add up to ${remaining} more email${remaining === 1 ? '' : 's'}.`;
|
||
}
|
||
|
||
// Reference-matching "Add email address" behavior — mirrors the
|
||
// reference mockup's onAddEmail exactly: the only gate is the total
|
||
// count (max 3), full stop. No extra "confirm it first" requirement —
|
||
// that was tried in an earlier pass and it backfired (a row left as
|
||
// the literal placeholder text, or one the user never bothered to
|
||
// open the pencil on, would permanently block adding a 3rd address).
|
||
// Drops the new row in already in "view" mode with a placeholder
|
||
// address, same as the mockup; the user clicks the pencil (editEmail)
|
||
// to actually type their address. Keeps the same emailList/keyIndex/
|
||
// checkIndexNo bookkeeping the real save flow (validateEmails/
|
||
// saveEmailSettings) depends on — just changes how the row first
|
||
// appears.
|
||
handleAddEmailRow() {
|
||
if (this.emailList.length >= 3) {
|
||
return;
|
||
}
|
||
const indexNumber = ++this.keyIndex;
|
||
this.addEmailAddress = false;
|
||
const newItem = {
|
||
index: indexNumber,
|
||
value: 'new.address@example.com',
|
||
addEmail: this.addEmailAddress,
|
||
exists: false,
|
||
isEditable: false,
|
||
isValid: true
|
||
};
|
||
this.emailList = [...this.emailList, newItem];
|
||
this.checkIndexNo = this.emailList.length - 1;
|
||
if (this.emailList.length > 2) {
|
||
this.addButtonVisible = false;
|
||
}
|
||
this.emailListChanged = true;
|
||
this.updateDeleteState();
|
||
}
|
||
|
||
// Commits the typed address AND locks the row back to view mode in one
|
||
// step, computing validity fresh off the field's live value at blur
|
||
// time. (The original two-handler approach — onchange updating a
|
||
// separately-tracked isValid flag, onblur reading that flag — could
|
||
// race: if blur fired before the change handler's state update landed,
|
||
// the row would never lock and the input would stay open even after
|
||
// clicking elsewhere on the page.)
|
||
handleEmailBlur(event) {
|
||
const index = event.target.dataset.id;
|
||
const value = (event.target.value || '').trim();
|
||
const emailRegex = /^([A-Za-z0-9_\-\.])+\@([A-Za-z0-9_\-\.])+\.([A-Za-z]{2,4})$/;
|
||
const isValid = emailRegex.test(value);
|
||
this.emailListChanged = true;
|
||
this.emailList = this.emailList.map((e) => {
|
||
if (e.index == index) {
|
||
return { ...e, value, isValid, isEditable: !isValid };
|
||
}
|
||
return e;
|
||
});
|
||
}
|
||
|
||
// Manual vs Auto Configuration — isAutomatic is the real @api property
|
||
// already wired to changeConfigurationType()/the `automation` event.
|
||
get isManual() {
|
||
return !this.isAutomatic;
|
||
}
|
||
|
||
// Whether to show the Check Credential / Generate Access Token row.
|
||
// checkCredential is the real, pre-existing flag from multiStoreHandlerEvent:
|
||
// true when adding a New/Edit store (no valid saved credentials yet, so
|
||
// checking/generating makes sense), false for an already-connected
|
||
// Existing Store (there's nothing to check/generate — it's already
|
||
// authorized). isManual keeps this scoped to Manual setup only, same as
|
||
// before. This just restores that existing gating to the template —
|
||
// no change to how/when checkCredential itself gets set.
|
||
get showCheckCredentialRow() {
|
||
return this.isManual && this.checkCredential;
|
||
}
|
||
|
||
// DISPLAY-ONLY: resolves the currently-selected pricebook Id (this.priceBook)
|
||
// to its human-readable Name from priceBookList, so the Auto Configuration
|
||
// summary reflects the real selection instead of a hardcoded "Standard".
|
||
get selectedPricebookLabel() {
|
||
const match = (this.priceBookList || []).find(p => p.value === this.priceBook);
|
||
return match ? match.label : 'Standard';
|
||
}
|
||
|
||
// ------------------------------------------------------------------
|
||
// Custom toggle switches (Auto Configuration header + the 4 live
|
||
// Object Sync rows). lightning-input type="toggle" was tried first —
|
||
// it briefly flashed blue on click before settling to a plain grey/
|
||
// default look, because SLDS's own toggle styling tokens don't
|
||
// reliably follow the --slds-c-checkbox-* overrides across every org/
|
||
// browser. These plain button+span switches give full, guaranteed
|
||
// control over the on/off colors (green/grey, matching the reference
|
||
// mockup exactly) with no dependency on SLDS's internal styling hooks.
|
||
// Each click calls the REAL, untouched change handler (changeDraftSync
|
||
// etc.) with a hand-built { target: { checked } } object — the same
|
||
// shape lightning-input's onchange would have passed — so none of the
|
||
// underlying Apex-tied logic changes, only how the switch is drawn.
|
||
// ------------------------------------------------------------------
|
||
get autoToggleTrackClass() {
|
||
return this.isAutomatic ? 'obj-toggle-track obj-toggle-track_on' : 'obj-toggle-track';
|
||
}
|
||
|
||
get draftToggleTrackClass() {
|
||
return this.draftSyncValue ? 'obj-toggle-track obj-toggle-track_on' : 'obj-toggle-track';
|
||
}
|
||
|
||
get orderToggleTrackClass() {
|
||
return this.orderSyncValue ? 'obj-toggle-track obj-toggle-track_on' : 'obj-toggle-track';
|
||
}
|
||
|
||
get collectionToggleTrackClass() {
|
||
return this.collectionSyncValue ? 'obj-toggle-track obj-toggle-track_on' : 'obj-toggle-track';
|
||
}
|
||
|
||
get inventoryToggleTrackClass() {
|
||
return this.inventorySyncValue ? 'obj-toggle-track obj-toggle-track_on' : 'obj-toggle-track';
|
||
}
|
||
|
||
handleToggleDraftSync() {
|
||
this.changeDraftSync({ target: { checked: !this.draftSyncValue } });
|
||
}
|
||
|
||
handleToggleOrderSync() {
|
||
this.changeOrderSync({ target: { checked: !this.orderSyncValue } });
|
||
}
|
||
|
||
handleToggleCollectionSync() {
|
||
this.changeCollectionSync({ target: { checked: !this.collectionSyncValue } });
|
||
}
|
||
|
||
handleToggleInventorySync() {
|
||
this.changeInventorySync({ target: { checked: !this.inventorySyncValue } });
|
||
}
|
||
|
||
// Connection status pill in the hero card — derived the same way the
|
||
// reference mockup derives connLabel/connDot (connected == automatic),
|
||
// but here it also reflects real authorization state once a store is
|
||
// actually connected, so it's not purely decorative.
|
||
get isConnected() {
|
||
return this.isAutomatic || this.isStoreAuthorized || this.accessTokenAvailable;
|
||
}
|
||
|
||
get connectionPillClass() {
|
||
return this.isConnected ? 'conn-pill conn-pill_on' : 'conn-pill conn-pill_off';
|
||
}
|
||
|
||
get connectionLabel() {
|
||
return this.isConnected ? 'Connected & syncing' : 'Not connected yet';
|
||
}
|
||
|
||
// Header pill (top-right, next to the Auto Configuration toggle) —
|
||
// this one reflects Manual vs Auto mode specifically, matching the
|
||
// reference mockup's connLabel/connDot exactly ("Manual setup" in
|
||
// amber when off, "Auto-configured" in green when on). Distinct from
|
||
// connectionPillClass/connectionLabel above, which drive the hero
|
||
// card's "Connected & syncing" pill and reflect actual store
|
||
// authorization instead.
|
||
get autoConfigPillClass() {
|
||
return this.isAutomatic ? 'conn-pill conn-pill_on' : 'conn-pill conn-pill_off';
|
||
}
|
||
|
||
get autoConfigPillLabel() {
|
||
return this.isAutomatic ? 'Auto-configured' : 'Manual setup';
|
||
}
|
||
|
||
// DISPLAY-ONLY: fetches the ready-formatted "Last order synced X ago" text
|
||
// for the current store. The time math is done server-side in Apex using
|
||
// Datetime.now(), so there's no JS timezone ambiguity.
|
||
loadLastOrderSyncTime() {
|
||
if (!this.storeName) return;
|
||
getLastOrderSyncTime({ store: this.storeName })
|
||
.then((text) => {
|
||
this._lastSyncedText = text || '';
|
||
})
|
||
.catch((error) => {
|
||
console.log('SME: getLastOrderSyncTime error', JSON.stringify(error));
|
||
this._lastSyncedText = '';
|
||
});
|
||
}
|
||
|
||
// Show the "Last order synced …" line ONLY when this store has actually
|
||
// synced a paid order/opportunity into this org (Apex returned text).
|
||
get showLastSynced() {
|
||
return !!this._lastSyncedText;
|
||
}
|
||
|
||
get lastSyncedText() {
|
||
return this._lastSyncedText || '';
|
||
}
|
||
|
||
// ------------------------------------------------------------------
|
||
// "Check Credential" result modal — reference mockup shows a
|
||
// "Checking credential…" state followed by a "Credential is valid"
|
||
// confirmation. The reference's own version just fakes this with a
|
||
// setTimeout; here it wraps the REAL checkStoreCredentials() flow
|
||
// (verbatim, untouched — createStoreUrlRemoteSite -> validateCredentials
|
||
// -> checkCreds Apex -> checkAccessScope), so the result shown is real.
|
||
// Polling isLoading is a non-invasive way to know when that real chain
|
||
// has finished without changing anything inside it: every path through
|
||
// it (success or failure) ends by calling closeSpinnerHandler(), which
|
||
// flips isLoading back to false.
|
||
// ------------------------------------------------------------------
|
||
@track credModalOpen = false;
|
||
@track credChecking = false;
|
||
@track credResultSuccess = false;
|
||
credCheckPollInterval;
|
||
|
||
// The real checkStoreCredentials() chain flips the real `isLoading`
|
||
// flag on and off via showSpinnerHandler()/closeSpinnerHandler() —
|
||
// same as it does for Save & Next, initial store load, etc. Normally
|
||
// that swaps this whole component's body to the big logo loading
|
||
// view. But while the Check Credential modal is open, that swap was
|
||
// ALSO happening underneath the modal at the same time (the modal is
|
||
// a sibling at the template root, not nested inside the isLoading
|
||
// check) — showing both the modal AND the blurred logo card behind
|
||
// it. This getter is what the body-level loading view is actually
|
||
// gated on now: suppress it specifically while credModalOpen owns the
|
||
// loading state, but leave isLoading itself completely untouched so
|
||
// every other flow (Save & Next, initial load) behaves exactly as
|
||
// before.
|
||
get showBodyLoading() {
|
||
return this.isLoading && !this.credModalOpen;
|
||
}
|
||
|
||
handleCheckCredentialClick() {
|
||
if (this.disableCheckCredential) {
|
||
return;
|
||
}
|
||
this.credModalOpen = true;
|
||
this.credChecking = true;
|
||
this.credResultSuccess = false;
|
||
|
||
// Kick off the real, untouched credential check.
|
||
this.checkStoreCredentials();
|
||
|
||
this.clearCredCheckPoll();
|
||
const startedAt = Date.now();
|
||
this.credCheckPollInterval = setInterval(() => {
|
||
const timedOut = Date.now() - startedAt > 20000; // safety net only
|
||
if (!this.isLoading || timedOut) {
|
||
this.clearCredCheckPoll();
|
||
this.credChecking = false;
|
||
this.credResultSuccess = !!this.isStoreAuthorized;
|
||
}
|
||
}, 250);
|
||
}
|
||
|
||
clearCredCheckPoll() {
|
||
if (this.credCheckPollInterval) {
|
||
clearInterval(this.credCheckPollInterval);
|
||
this.credCheckPollInterval = null;
|
||
}
|
||
}
|
||
|
||
closeCredCheckModal() {
|
||
this.credModalOpen = false;
|
||
this.clearCredCheckPoll();
|
||
}
|
||
|
||
// "{{ objCount }} of 6 on" badge on the Object Sync Settings card.
|
||
get objOnCount() {
|
||
return [
|
||
this.productSyncValue,
|
||
this.customerSyncValue,
|
||
this.draftSyncValue,
|
||
this.orderSyncValue,
|
||
this.collectionSyncValue,
|
||
this.inventorySyncValue
|
||
].filter(Boolean).length;
|
||
}
|
||
|
||
// Check Credential / Generate Access Token button emphasis — the real
|
||
// enablement rules already live in disableCheckCredential /
|
||
// disableGenerateAccessToken; these just pick which one gets the
|
||
// green "primary" treatment vs the muted outline treatment.
|
||
get checkCredButtonClass() {
|
||
return this.disableCheckCredential ? 'sw-btn sw-btn_muted' : 'sw-btn sw-btn_primary';
|
||
}
|
||
|
||
get genTokenButtonClass() {
|
||
return this.disableGenerateAccessToken ? 'sw-btn sw-btn_muted' : 'sw-btn sw-btn_primary';
|
||
}
|
||
|
||
// Back button in the new footer — this component previously had no
|
||
// in-page Back control (the parent's own bottom Back button is hidden
|
||
// for this step via showButtons(false, true, false, true) in home.js).
|
||
// Rather than duplicate home.js's real previousPage() logic, just
|
||
// bubble a plain event and let home.js handle it with that same,
|
||
// untouched method — wired via onback={previousPage} on the
|
||
// <c-shopify-credentials> tag in home.html.
|
||
handleBackStep() {
|
||
this.dispatchEvent(new CustomEvent('back'));
|
||
}
|
||
|
||
// Stop every timer the Generate Token flow may have started if this
|
||
// component is ever torn down mid-flow (e.g. user jumps to another
|
||
// step via the left nav while the OAuth popup is open).
|
||
disconnectedCallback() {
|
||
this.clearTokenGenTimers();
|
||
this.clearCredCheckPoll();
|
||
this.unlockBackgroundScroll();
|
||
if (this._syncTimerId) {
|
||
clearInterval(this._syncTimerId);
|
||
this._syncTimerId = null;
|
||
}
|
||
}
|
||
|
||
@wire(getOrganizationName)
|
||
wiredOrganizationName({ data, error }) {
|
||
if (data) {
|
||
this.organizationName = data;
|
||
} else if (error) {
|
||
console.error('Error fetching organization name:', error);
|
||
}
|
||
}
|
||
|
||
} |