diff --git a/HabitatHome.Commerce.sln b/HabitatHome.Commerce.sln index bb2c6440e..411f984f1 100644 --- a/HabitatHome.Commerce.sln +++ b/HabitatHome.Commerce.sln @@ -124,6 +124,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Promotions", "Promotions", EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sitecore.HabitatHome.Foundation.Promotions.Website", "src\Foundation\Promotions\website\Sitecore.HabitatHome.Foundation.Promotions.Website.csproj", "{A5FE2C5D-8A6F-4ACF-9F4F-03B7F8F7F94F}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ProductRelatedContent", "ProductRelatedContent", "{28FE62EE-EC31-4D80-83B3-ECE2EC62856E}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sitecore.HabitatHome.Feature.ProductRelatedContent.Website", "src\Feature\ProductRelatedContent\website\Sitecore.HabitatHome.Feature.ProductRelatedContent.Website.csproj", "{4230C56A-B706-4070-832F-B9E1CADBFDBB}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -236,6 +240,10 @@ Global {A5FE2C5D-8A6F-4ACF-9F4F-03B7F8F7F94F}.Debug|Any CPU.Build.0 = Debug|Any CPU {A5FE2C5D-8A6F-4ACF-9F4F-03B7F8F7F94F}.Release|Any CPU.ActiveCfg = Release|Any CPU {A5FE2C5D-8A6F-4ACF-9F4F-03B7F8F7F94F}.Release|Any CPU.Build.0 = Release|Any CPU + {4230C56A-B706-4070-832F-B9E1CADBFDBB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4230C56A-B706-4070-832F-B9E1CADBFDBB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4230C56A-B706-4070-832F-B9E1CADBFDBB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4230C56A-B706-4070-832F-B9E1CADBFDBB}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -294,6 +302,8 @@ Global {EFF50A81-0722-4537-892D-F0522495BFD6} = {054C0EDB-3E7C-4CF6-8B85-7D6F55397147} {F11F18D0-C25F-4130-8320-DDE62AA33EDB} = {1B6FFBEA-35BD-4381-A2BE-4FA54E3940E1} {A5FE2C5D-8A6F-4ACF-9F4F-03B7F8F7F94F} = {F11F18D0-C25F-4130-8320-DDE62AA33EDB} + {28FE62EE-EC31-4D80-83B3-ECE2EC62856E} = {05829E0F-331A-44AF-BEB8-3AD54EE35694} + {4230C56A-B706-4070-832F-B9E1CADBFDBB} = {28FE62EE-EC31-4D80-83B3-ECE2EC62856E} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {092E4BB9-63E2-476F-91D9-389412140FC4} diff --git a/src/Feature/Cart/website/App_Config/Include/Feature.Overrides/Commerce/Feature.Cart.config b/src/Feature/Cart/website/App_Config/Include/Feature.Overrides/Commerce/Feature.Cart.config index e877cc9e0..f4c61d04e 100644 --- a/src/Feature/Cart/website/App_Config/Include/Feature.Overrides/Commerce/Feature.Cart.config +++ b/src/Feature/Cart/website/App_Config/Include/Feature.Overrides/Commerce/Feature.Cart.config @@ -21,11 +21,18 @@ patch:instead="file[@name='CxaCheckoutDeliveryModel']" path="/Scripts/Commerce/Feature/Cart/habitathome.feature.cart.delivery.model.js" order="203"/> - + + + + + diff --git a/src/Feature/Cart/website/Scripts/Commerce/Feature/Cart/habitathome.feature.cart.billing.model.js b/src/Feature/Cart/website/Scripts/Commerce/Feature/Cart/habitathome.feature.cart.billing.model.js new file mode 100644 index 000000000..b202f6ea0 --- /dev/null +++ b/src/Feature/Cart/website/Scripts/Commerce/Feature/Cart/habitathome.feature.cart.billing.model.js @@ -0,0 +1,627 @@ +var mustEqual = function (val, other) { + + if (val) { + return val === other; + } + + return true; +}; + +// -----[GIFT CARD PAYMENT MODEL]----- +function GiftCardPaymentViewModel(billingViewModel, card) { + var self = this; + var populate = card != null; + + self.billingViewModel = billingViewModel; + + self.isAdded = ko.observable(false); + self.balance = ko.observable(0); + self.formattedBalance = ko.observable(""); + self.enableAddCard = ko.observable(false); + + self.giftCardNumber = populate ? ko.validatedObservable(card.PaymentMethodID).extend({ required: true }) : ko.validatedObservable("").extend({ required: true }); + self.giftCardNumber.subscribe(function (cn) { + self.balance(0); + self.formattedBalance(""); + + self.reEnableAddCard(); + //self.enableAddCard(cn.length > 0 && self.giftCardAmount().length > 0 && self.isAdded() === false); + }.bind(this)); + + self.reload = function (data) { + self.giftCardNumber(data.ExternalId); + self.formattedBalance(data.FormattedBalance); + self.balance(data.Balance); + //self.enableAddCard(false); + self.reEnableAddCard(); + } + + self.reEnableAddCard = function (ca) { + if (!ca) { + ca = self.giftCardAmount(); + } + + self.enableAddCard(ca.length > 0 && self.giftCardNumber().length > 0 && self.isAdded() === false && self.giftCardAmount.isValid() && self.balance() > 0); + } + + self.getBalance = function () { + MessageContext.ClearAllMessages(); + var $btn = $('.git-card-payment-get-balance-btn').button('loading'); + var data = {}; + data.GiftCardId = self.giftCardNumber(); + AjaxService.Post('/api/cxa/checkout/getgiftcardbalance', data, function (data, success, sender) { + if (success && data.Success) { + self.reload(data); + } + + $('.git-card-payment-get-balance-btn').button('reset'); + }, this); + }; + + self.applyBalance = function () { + MessageContext.ClearAllMessages(); + var balance = self.balance(); + if (balance <= 0) { + return; + } + + var orderTotal = self.billingViewModel.cart().totalAmount(); + if (balance > orderTotal) { + self.giftCardAmountRawValue(orderTotal); + } + else if (balance <= orderTotal) { + self.giftCardAmountRawValue(balance); + } + + self.enableAddCard(true); + }; + + self.addCard = function () { + MessageContext.ClearAllMessages(); + self.isAdded(true); + self.enableAddCard(false); + self.revalueAmounts(); + }; + + self.removeCard = function () { + MessageContext.ClearAllMessages(); + self.isAdded(false); + self.giftCardNumber(""); + self.formattedBalance(""); + self.giftCardAmountRawValue(0.00); + self.balance(0.00); + }; + + self.revalueAmounts = function () { + if (!self.isAdded()) { + return; + } + + var ccIsAdded = self.billingViewModel.creditCardPayment().isAdded(); + if (!ccIsAdded) { + return; + } + + var ccAmount = parseFloat(self.billingViewModel.creditCardPayment().creditCardAmount()); + var total = parseFloat(self.billingViewModel.cart().totalAmount()); + var aTotal = parseFloat(parseFloat(self.giftCardAmountRawValue()) + ccAmount); + + if (aTotal > total) { + var count = 1; + var diff = (aTotal - total) / count; + ccAmount = ccIsAdded ? ccAmount - diff : 0; + self.billingViewModel.creditCardPayment().creditCardAmount((ccAmount).toFixed(2)); + } + }; + + self.giftCardAmount = ko.validatedObservable(0.00).extend({ required: true, number: true }); + + self.giftCardAmountRawValue = ko.computed({ + read: function () { + value = self.giftCardAmount(); + + if (self.billingViewModel) { + value = value.toString(); + value = value.replace(self.billingViewModel.currencyDecimalSeparator(), "."); + } + + return value; + }, + write: function (value) { + if (self.billingViewModel) { + value = value.toString(); + value = value.replace(".", self.billingViewModel.currencyDecimalSeparator()); + } + + self.giftCardAmount(value); + } + }); + + self.giftCardAmount.subscribe(function (ca) { + self.reEnableAddCard(ca); + self.revalueAmounts(); + }.bind(this)); + + self.addGiftCard = function (cardData) { + self.giftCardNumber(cardData.GiftCardNumber != null ? cardData.GiftCardNumber : ""); + self.giftCardAmountRawValue(cardData.Amount); + self.isAdded(true); + } +} + +// -----[CREDITCARD PAYMENT MODEL]----- +function CreditCardPaymentViewModel(card, checkoutViewModel) { + var self = this; + var populate = card != null; + + self.checkoutViewModel = checkoutViewModel; + + self.isAdded = ko.observable(false); + self.creditCardNumber = ko.validatedObservable().extend({ required: true, number: true }); + self.creditCardNumberMasked = ko.observable(); + self.creditCardNumber.subscribe(function (number) { + self.creditCardNumberMasked("XXXX XXXX XXXX " + number.substr(number.length - 4)); + }); + + self.description = ko.observable(); + + self.existingPaymentAmount = ko.observable(populate ? card.Amount : 0); + + self.paymentMethodID = populate ? ko.validatedObservable(card.PaymentMethodID).extend({ required: true }) : ko.validatedObservable().extend({ required: true }); + self.paymentMethodID.subscribe(function (methodId) { + if (methodId !== "0") { + self.isAdded(true); + + var paymentMethod = ko.utils.arrayFirst(self.checkoutViewModel.paymentMethods(), function (a) { + if (a.ExternalId === methodId) { + return a; + } + + return null; + }); + + self.description(paymentMethod ? paymentMethod.Description : ""); + self.checkoutViewModel.creditCardEnable(true); + self.checkoutViewModel.billingAddressEnable(true); + } else { + self.isAdded(false); + self.checkoutViewModel.creditCardEnable(false); + self.checkoutViewModel.billingAddressEnable(false); + } + }); + + self.changePayment = ko.observable(false); + self.displayExistingPaymentMessage = ko.computed({ + read: function () { + if (self.checkoutViewModel.editMode() && self.existingPaymentAmount() == self.creditCardAmount() && !self.changePayment()) { + return true; + } + else { + return false; + } + }, + write: function () { } + }); + + self.mustRevalidatePaymentMessage = ko.observable(populate ? card.CartAmountDifferentThanPaymentMessage : ""); + self.mustRevalidatePayment = ko.computed({ + read: function () { + if (self.checkoutViewModel.editMode() && self.existingPaymentAmount() != self.creditCardAmount()) { + return true; + } + else { + return false; + } + }, + write: function () { } + }); + + self.creditCardAmount = ko.computed({ + read: function () { + if (!self.isAdded()) { + return 0; + } + + var total = self.checkoutViewModel.cart() ? self.checkoutViewModel.cart().totalAmount() : 0; + var gcAmount = self.checkoutViewModel.giftCardPayment() ? self.checkoutViewModel.giftCardPayment().giftCardAmountRawValue() : 0; + var ccAmount = (parseFloat(total) - parseFloat(gcAmount)).toFixed(2); + + if (ccAmount <= 0) { + self.isAdded(false); + } + + return ccAmount; + }, + write: function () { } + }); + + self.originalExistingPaymentMessage = ko.observable(populate ? card.ExistingPaymentMessage : ""); + self.existingPaymentMessage = ko.computed({ + read: function () { + if (self.creditCardAmount() != null) { + return self.originalExistingPaymentMessage().format(self.creditCardAmount()); + } + else { + return ""; + } + }, + write: function () { } + }); + + self.partyID = populate ? ko.observable(card.PartyID) : ko.observable(); +} + +// -----[BILLING DATA MODEL]----- +function BillingDataViewModel() { + var self = this; + + var PaymentTypes = { + GiftCard: 3, + FederatedPayment: 4, + }; + + self.cart = ko.observable(""); + + self.editMode = ko.observable(false); + + var Country = function (name, code) { + this.country = name; + this.code = code; + }; + self.countries = ko.observableArray(); + + self.isAuthenticated = false; + self.userEmail = ""; + + self.isShipAll = ko.observable(false); + + self.currencyDecimalSeparator = ko.observable(); + self.currencyGroupSeparator = ko.observable(); + + self.userAddresses = ko.observableArray(); + self.hasDigitalProductsInCart = false; + self.userAddresses.push(new AddressViewModel({ "ExternalId": "UseOther", "FullAddress": $("#OtherAddressLabel").val() })); + + self.billingEmail = ko.validatedObservable(self.userEmail).extend({ required: true, email: true }); + self.billingConfirmEmail = ko.validatedObservable(self.userEmail).extend({ required: true, validation: { validator: mustEqual, message: $("#EmailsMustMatchMessage").val(), params: self.billingEmail } }); + + self.payFederatedPayment = ko.observable(false); + self.payGiftCard = ko.observable(false); + self.cardPaymentAcceptPageUrl = ko.observable(''); + self.cardPaymentResultAccessCode = ""; + self.cardPaymentAcceptCardPrefix = ""; + self.CARDPAYMENTACCEPTPAGEHEIGHT = "msax-cc-height"; + self.CARDPAYMENTACCEPTPAGEERROR = "msax-cc-error"; + self.CARDPAYMENTACCEPTPAGERESULT = "msax-cc-result"; + self.CARDPAYMENTACCEPTPAGESUBMIT = "msax-cc-submit"; + self.CARDPAYMENTACCEPTCARDPREFIX = "msax-cc-cardprefix"; + + var PaymentMethod = function (externalId, description) { + this.ExternalId = externalId; + this.Description = description; + }; + + self.paymentMethods = ko.observableArray(); + self.paymentClientToken = ko.observable(""); + + self.giftCardPayment = ko.validatedObservable(new GiftCardPaymentViewModel(self)); + self.creditCardPayment = ko.validatedObservable(new CreditCardPaymentViewModel(null, self)); + self.creditCardEnable = ko.observable(false); + self.billingAddress = ko.validatedObservable(new AddressViewModel({ "ExternalId": "1" })); + self.billingAddressEnable = ko.observable(false); + self.shippingAddress = ko.observable(); + self.selectedBillingAddress = ko.observable("UseOther"); + self.selectedBillingAddress.subscribe(function (id) { + if (id === "UseShipping") { + self.billingAddressEnable(false); + self.billingAddress(self.shippingAddress()); + } else { + var match = self.getAddress(id); + if (match != null) { + self.billingAddressEnable(false); + self.billingAddress(match); + } else { + self.billingAddressEnable(true); + self.billingAddress(new AddressViewModel({ "ExternalId": "1" })); + } + } + + $(".select-billing-address").prop("disabled", false); + }); + + self.paymentTotal = ko.computed({ + read: function () { + var ccIsAdded = self.creditCardPayment().isAdded(); + var gcIsAdded = self.giftCardPayment().isAdded(); + if (!ccIsAdded && !gcIsAdded) { + return 0; + } + + var ccAmount = ccIsAdded ? self.creditCardPayment().creditCardAmount() : 0; + var gcAmount = gcIsAdded ? self.giftCardPayment().giftCardAmountRawValue() : 0; + return (parseFloat(ccAmount) + parseFloat(gcAmount)).toFixed(2); + }, + write: function () { } + }); + + self.enableToConfirm = ko.computed({ + read: function () { + if (self.cart().length == 0) { + return false; + } + + var paymentTotalIsValid = parseFloat(self.paymentTotal()) === parseFloat(self.cart().totalAmount()); + if (!paymentTotalIsValid) { + return false; + } + + var paymentsAreValid = false; + if (self.giftCardPayment().isAdded()) { + paymentsAreValid = self.giftCardPayment.isValid(); + } + + if (self.creditCardPayment().isAdded()) { + if (!self.payFederatedPayment()) { + paymentsAreValid = self.creditCardPayment.isValid() && self.billingAddress.isValid(); + } + else { + paymentsAreValid = !self.creditCardPayment().mustRevalidatePayment() && self.billingAddress.isValid(); + } + } + + if (paymentsAreValid) { + ko.validation.group(self).showAllMessages(true); + } + + return paymentsAreValid && self.billingEmail.isValid() && self.billingConfirmEmail.isValid(); + }, + write: function (value) { + return Boolean(value); + } + }); + + self.setPaymentMethods = function () { + var data = "{"; + + data = data + '"BillingItemPath":"' + $("#BillingItemPath").val() + '",' + data = data + '"UserEmail":' + '"' + self.billingEmail() + '"'; + + if (self.creditCardPayment().isAdded()) { + var cc = self.creditCardPayment(); + if (self.payFederatedPayment) { + var creditCard = { + "CardToken": self.cardPaymentResultAccessCode, + "Amount": cc.creditCardAmount(), + "CardPaymentAcceptCardPrefix": self.cardPaymentAcceptCardPrefix + }; + + if (data.length > 1) { + data += ","; + } + + data += '"FederatedPayment":' + JSON.stringify(creditCard); + if (self.cardPaymentAcceptCardPrefix === "paypal") { + var ba = self.billingAddress(); + var billingAddress = + { + "Name": ba.name(), + "Address1": ba.address1(), + "Country": ba.country(), + "City": ba.city(), + "State": ba.state(), + "ZipPostalCode": ba.zipPostalCode(), + "ExternalId": ba.externalId(), + "PartyId": ba.externalId() + }; + + if (data.length > 1) { + data += ","; + } + + data += '"BillingAddress":' + JSON.stringify(billingAddress); + } + } + } + + if (self.giftCardPayment().isAdded()) { + var giftCard = { + "PaymentMethodID": self.giftCardPayment().giftCardNumber(), + "Amount": self.giftCardPayment().giftCardAmountRawValue() + }; + + if (data.length > 1) { + data += ","; + } + + data += '"GiftCardPayment":' + JSON.stringify(giftCard); + } + + data += "}"; + + $(".to-confirm-button").button('loading'); + MessageContext.ClearAllMessages(); + + AjaxService.Post("/api/cxa/checkout/SetPaymentMethods", JSON.parse(data), function (data, success, sender) { + if (data.Success && success) { + window.location.href = data.NextPageLink + } + $(".to-confirm-button").button('reset'); + }, $(this)); + } + + self.setupPage = function () { + $('.existing-payment-reset-link').on('click', function (event) { + self.creditCardPayment().changePayment(true); + }); + } + + self.goToNextPageClick = function () { + self.setPaymentMethods(); + } + + self.getAddress = function (id) { + var match = ko.utils.arrayFirst(self.userAddresses(), function (a) { + if (a.externalId() === id && id !== "UseOther") { + return a; + } + + return null; + }); + + return match; + }; + + self.load = function () { + AjaxService.Post("/api/cxa/checkout/GetBillingData", null, function (data, success, sender) { + if (success && data.Success) { + console.log(data); + if (data.Countries != null) { + $.each(data.Countries, function (index, value) { + self.countries.push(new Country(value, index)); + }); + } + + self.cart(new CartViewModel(data.Cart)); + + // Set the shipping address. Needed for same as shipping. + if (self.cart().shipments.length == 1) { + var partyId = self.cart().shipments[0].partyId; + var party = ko.utils.arrayFirst(self.cart().parties, function (a) { + if (a.externalId() === partyId) { + return a; + } + + return null; + }); + + if (party != null) { + self.shippingAddress(party); + } + } + + self.paymentClientToken(data.PaymentClientToken); + + if (data.IsUserAuthenticated === true && data.UserAddresses.Addresses != null) { + $.each(data.UserAddresses.Addresses, function () { + self.userAddresses.push(new AddressViewModel(this)); + }); + + self.isAuthenticated = true; + } + + // Checking if the cart contains a digital product "HasDigitalProduct" + // if true then the "Same as shipping address" will not be available + // else it will be added to nilling address list + self.hasDigitalProductsInCart = data.HasDigitalProduct; + if (!self.hasDigitalProductsInCart) { + self.userAddresses.push(new AddressViewModel({ "ExternalId": "UseShipping", "FullAddress": $(".select-billing-address").attr("title") })); + } + + self.userEmail = data.UserEmail; + self.billingEmail(data.UserEmail); + self.billingConfirmEmail(data.UserEmail); + + self.currencyDecimalSeparator(data.CurrencyDecimalSeparator); + self.currencyGroupSeparator(data.CurrencyGroupSeparator) + + //if (self.currencyDecimalSeparator() != ".") { + //} + + currentNumberValidator = ko.validation.rules.number.validator; + ko.validation.rules.number.validator = function (n, t) { + n = n.replace(self.currencyDecimalSeparator(), "."); + + return currentNumberValidator(n, t) && $.isNumeric(n); + } + + if (data.PaymentOptions != null) { + $.each(data.PaymentOptions, function (index, value) { + if (value.PaymentOptionTypeName === "PayFederatedPayment") { + self.payFederatedPayment(true); + } + if (value.PaymentOptionTypeName === "PayGiftCard") { + self.payGiftCard(true); + } + }); + } + + if (data.PaymentMethods != null) { + self.paymentMethods.push(new PaymentMethod("0", $("#PaymentMethods").attr("title"))); + $.each(data.PaymentMethods, function (index, value) { + self.paymentMethods.push(new PaymentMethod(value.ExternalId, value.Description)); + }); + } + + self.isShipAll((data.Cart != null && data.Cart.Shipments != null && data.Cart.Shipments.length == 1 && data.Cart.Shipments[0].ShippingMethodName != "Pickup From Store") ? true : false); + + if (self.paymentClientToken() != null) { + var clientToken = self.paymentClientToken(); + if (clientToken.length > 0) { + braintree.setup(clientToken, 'dropin', { + container: 'dropin-container', + paymentMethodNonceReceived: function (event, nonce) { + if (nonce.length > 0) { + self.cardPaymentResultAccessCode = nonce; + self.cardPaymentAcceptCardPrefix = "paypal"; + + if (self.creditCardPayment() != null && self.editMode()) { + self.creditCardPayment().existingPaymentAmount(self.creditCardPayment().creditCardAmount()); + self.creditCardPayment().changePayment(false); + } + } + } + }); + } + } + + if (data.Cart != null && data.Cart.AccountingParty != null) { + var partyId = data.Cart.AccountingParty.PartyID; + var party = ko.utils.arrayFirst(self.cart().parties, function (a) { + if (a.externalId() === partyId) { + return a; + } + + return null; + }); + + if (party != null) { + self.billingAddress(party); + } + } + + if (data.Cart != null && data.Cart.Payments != null && data.Cart.Payments.length > 0) { + var giftCardAdded = false; + var fedPaymentAdded = false; + $.each(data.Cart.Payments, function (index, Value) { + if (Value.PaymentType == PaymentTypes.FederatedPayment) { + self.creditCardPayment(new CreditCardPaymentViewModel(Value, self)); + self.payFederatedPayment(true); + self.cardPaymentResultAccessCode = Value.CardToken; + self.cardPaymentAcceptCardPrefix = "paypal"; + fedPaymentAdded = true; + } + + if (Value.PaymentType == PaymentTypes.GiftCard) { + self.giftCardPayment().addGiftCard(Value); + self.payGiftCard(true); + giftCardAdded = true; + } + }); + + self.editMode(fedPaymentAdded || giftCardAdded); + + if (fedPaymentAdded) { + $(".ccpayment").trigger("click"); + } + + if (giftCardAdded) { + $(".giftCardPayment").trigger("click"); + } + } + + self.setupPage(); + } + } + ) + }; +} \ No newline at end of file diff --git a/src/Feature/Cart/website/Sitecore.HabitatHome.Feature.Cart.Website.csproj b/src/Feature/Cart/website/Sitecore.HabitatHome.Feature.Cart.Website.csproj index a7e1cae67..9ae93f1d0 100644 --- a/src/Feature/Cart/website/Sitecore.HabitatHome.Feature.Cart.Website.csproj +++ b/src/Feature/Cart/website/Sitecore.HabitatHome.Feature.Cart.Website.csproj @@ -156,6 +156,7 @@ + diff --git a/src/Feature/Catalog/website/App_Config/Include/Feature/Commerce/Feature.Catalog.config b/src/Feature/Catalog/website/App_Config/Include/Feature/Commerce/Feature.Catalog.config index 2c2afcb88..4acb9a465 100644 --- a/src/Feature/Catalog/website/App_Config/Include/Feature/Commerce/Feature.Catalog.config +++ b/src/Feature/Catalog/website/App_Config/Include/Feature/Commerce/Feature.Catalog.config @@ -2,7 +2,7 @@ - + diff --git a/src/Feature/Catalog/website/App_Config/Include/z.Feature.Overrides/Feature.Catalog.config b/src/Feature/Catalog/website/App_Config/Include/z.Feature.Overrides/Feature.Catalog.config new file mode 100644 index 000000000..453153b65 --- /dev/null +++ b/src/Feature/Catalog/website/App_Config/Include/z.Feature.Overrides/Feature.Catalog.config @@ -0,0 +1,10 @@ + + + + + + + + + \ No newline at end of file diff --git a/src/Feature/Catalog/website/Repositories/BreadcrumbRepository.cs b/src/Feature/Catalog/website/Repositories/BreadcrumbRepository.cs new file mode 100644 index 000000000..e450982f3 --- /dev/null +++ b/src/Feature/Catalog/website/Repositories/BreadcrumbRepository.cs @@ -0,0 +1,109 @@ +using Sitecore.Commerce.XA.Foundation.Common.Providers; +using Sitecore.Data.Items; +using Sitecore.XA.Feature.Navigation.Models; +using Sitecore.XA.Foundation.Mvc.Repositories.Base; +using System.Collections.Generic; +using System.Linq; +using Sitecore.Commerce.XA.Foundation.Common; + +namespace Sitecore.HabitatHome.Feature.Catalog.Repositories +{ + public class BreadcrumbRepository : Sitecore.XA.Feature.Navigation.Repositories.Breadcrumb.BreadcrumbRepository + { + + private readonly IItemTypeProvider _itemTypeProvider; + private readonly ISiteContext _siteContext; + private readonly IStorefrontContext _storefrontContext; + + public BreadcrumbRepository(IItemTypeProvider itemTypeProvider, ISiteContext siteContext, IStorefrontContext storefrontContext) : base() + { + _itemTypeProvider = itemTypeProvider; + _siteContext = siteContext; + _storefrontContext = storefrontContext; + } + + public override IEnumerable GetBreadcrumbItems(Item currentItem, Item rootItem) + { + var breadcrumbItems = BuildBreadcrumb(currentItem, rootItem).Where(ShowInBreadcrumb).ToList(); + int count = breadcrumbItems.Count; + + // Null is passed in for the Children parameter as it doesn't seem relevant for commerce catalog + return breadcrumbItems.Select((item, index) => CreateBreadcrumbModel(item, index, count, null, null)); + } + + protected override BreadcrumbRenderingModel CreateBreadcrumbModel(Item item, int index, int count, IEnumerable children, string Name) + { + var cssClasses = new List(); + if (index == 0) + { + cssClasses.Add("home"); + } + + return new BreadcrumbRenderingModel(item) + { + Name = Name, + Children = children, + CssClasses = cssClasses, + VariantFields = VariantFields, + Separator = Rendering.Parameters[Sitecore.XA.Feature.Navigation.Constants.Separator] + }; + } + + protected override bool ShowInBreadcrumb(Item item) + { + var itemType = _itemTypeProvider.GetItemType(item); + if (itemType == Sitecore.Commerce.XA.Foundation.Common.Constants.ItemTypes.Category || + itemType == Sitecore.Commerce.XA.Foundation.Common.Constants.ItemTypes.Product) + { + return true; + } + + return base.ShowInBreadcrumb(item); + } + + public override IRenderingModelBase GetModel() + { + IEnumerable breadcrumb; + + Item rootItem = Context.Database.GetItem(Rendering.Parameters[Sitecore.XA.Feature.Navigation.Constants.BreadcrumbRoot] ?? string.Empty); + + Item commerceRootItem = Context.Database.GetItem(_storefrontContext.CurrentStorefront.GetStartNavigationCategory()); + if (_siteContext.IsCategory || _siteContext.IsProduct) + { + // It's a catalog item so we need to concatenate the ancestors for the catalog with the ancestors on the site itself + breadcrumb = GetBreadcrumbItems(CurrentItem, rootItem) + .Concat(GetBreadcrumbItems(_siteContext.CurrentCatalogItem, commerceRootItem)) + .Where(a => a.Item.ID != commerceRootItem.ID); + } + else + { + breadcrumb = GetBreadcrumbItems(CurrentItem, rootItem); + } + + BreadcrumbRenderingModel model = new BreadcrumbRenderingModel + { + Separator = Rendering.Parameters[Sitecore.XA.Feature.Navigation.Constants.Separator] + }; + + FillBaseProperties(model); + + var breadcrumbModels = breadcrumb as BreadcrumbRenderingModel[] ?? breadcrumb.ToArray(); + if (!breadcrumbModels.Any()) + { + var fakeNav = Enumerable.Empty(); + if (Context.PageMode.IsExperienceEditor) + { + fakeNav = new FakeBreadcrumbRepository().GetBreadcrumbItems(null, null); + } + model.IsFake = true; + model.Children = fakeNav; + } + else + { + model.Children = breadcrumbModels; + } + + return model; + } + } +} \ No newline at end of file diff --git a/src/Feature/Catalog/website/Repositories/FakeBreadcrumbRepository.cs b/src/Feature/Catalog/website/Repositories/FakeBreadcrumbRepository.cs new file mode 100644 index 000000000..845cc484f --- /dev/null +++ b/src/Feature/Catalog/website/Repositories/FakeBreadcrumbRepository.cs @@ -0,0 +1,21 @@ +using System.Collections.Generic; +using Sitecore.Data.Items; +using Sitecore.XA.Feature.Navigation.Models; + +namespace Sitecore.HabitatHome.Feature.Catalog.Repositories +{ + public class FakeBreadcrumbRepository : Sitecore.XA.Feature.Navigation.Repositories.Breadcrumb.BreadcrumbRepository + { + public override IEnumerable GetBreadcrumbItems(Item currentItem, Item rootItem) + { + var fakeBreadcrumb = new List(); + for (int i = 0; i < 3; i++) + { + BreadcrumbRenderingModel fakeModel = CreateBreadcrumbModel(null, i, 2, null, $"Tag Level {i}"); + fakeBreadcrumb.Add(fakeModel); + } + + return fakeBreadcrumb; + } + } +} \ No newline at end of file diff --git a/src/Feature/Catalog/website/ServiceCollection/RegisterCatalogServices.cs b/src/Feature/Catalog/website/ServiceCollection/RegisterCatalogServices.cs new file mode 100644 index 000000000..1be437857 --- /dev/null +++ b/src/Feature/Catalog/website/ServiceCollection/RegisterCatalogServices.cs @@ -0,0 +1,31 @@ +using System.Linq; +using Microsoft.Extensions.DependencyInjection; +using Sitecore.DependencyInjection; +using Sitecore.HabitatHome.Feature.Catalog.Repositories; + +namespace Sitecore.HabitatHome.Feature.Catalog.ServiceCollection +{ + public class RegisterCatalogServices : IServicesConfigurator + { + public void Configure(IServiceCollection serviceCollection) + { + Replace(serviceCollection, ServiceLifetime.Transient); + } + + public IServiceCollection Replace( + IServiceCollection services, + ServiceLifetime lifetime) + where TService : class + where TImplementation : class, TService + { + + var descriptorToRemove = services.FirstOrDefault(d => d.ServiceType == typeof(TService)); + services.Remove(descriptorToRemove); + + var descriptorToAdd = new ServiceDescriptor(typeof(TService), typeof(TImplementation), lifetime); + services.Add(descriptorToAdd); + + return services; + } + } +} \ No newline at end of file diff --git a/src/Feature/Catalog/website/Sitecore.HabitatHome.Feature.Catalog.Website.csproj b/src/Feature/Catalog/website/Sitecore.HabitatHome.Feature.Catalog.Website.csproj index 9fcebed7c..1e8f4fc35 100644 --- a/src/Feature/Catalog/website/Sitecore.HabitatHome.Feature.Catalog.Website.csproj +++ b/src/Feature/Catalog/website/Sitecore.HabitatHome.Feature.Catalog.Website.csproj @@ -95,15 +95,32 @@ ..\..\..\..\packages\Sitecore.Mvc.NoReferences.9.0.171219\lib\NET462\Sitecore.Mvc.dll + + ..\..\..\..\lib\Modules\SXA\Sitecore.XA.Feature.Navigation.dll + + + False + ..\..\..\..\lib\Modules\SXA\Sitecore.XA.Foundation.IoC.dll + ..\..\..\..\lib\Modules\SXA\Sitecore.XA.Foundation.MarkupDecorator.dll + + False + ..\..\..\..\lib\Modules\SXA\Sitecore.XA.Foundation.Multisite.dll + ..\..\..\..\lib\Modules\SXA\Sitecore.XA.Foundation.Mvc.dll + + ..\..\..\..\lib\Modules\SXA\Sitecore.XA.Foundation.RenderingVariants.dll + ..\..\..\..\lib\Modules\SXA\Sitecore.XA.Foundation.SitecoreExtensions.dll + + ..\..\..\..\lib\Modules\SXA\Sitecore.XA.Foundation.Variants.Abstractions.dll + @@ -141,6 +158,7 @@ + @@ -161,8 +179,11 @@ + + + diff --git a/src/Feature/NearestStore/website/Views/NearestStore/NearestStore.cshtml b/src/Feature/NearestStore/website/Views/NearestStore/NearestStore.cshtml index 723483c29..6cf5d064f 100644 --- a/src/Feature/NearestStore/website/Views/NearestStore/NearestStore.cshtml +++ b/src/Feature/NearestStore/website/Views/NearestStore/NearestStore.cshtml @@ -5,48 +5,65 @@ @using Sitecore.Data.Items; @using Sitecore.Mvc -@{ +@if (!Sitecore.Context.PageMode.IsExperienceEditor) +{ + var currentItem = Html.Sitecore().CurrentItem; Item relatedProduct = Model.CatalogItem; bool isKitOrBundle = relatedProduct["Tags"] != null && !String.IsNullOrEmpty(relatedProduct["Tags"]) ? relatedProduct["Tags"].Split('|').Any(t => t.ToLower() == "kit" || t.ToLower() == "bundle") : false; -} -@if (isKitOrBundle) -{ -
-
-
+ + if (isKitOrBundle) + { +
+
+
+
-
+ } + else + { +
+
+ +
+
+
+ +
+ + +
+ + + + +
+ + + +
+
+
+ +
+
+ } } else { -
+
-
- -
- - -
- - - - -
- - - + +
+
+
- -
- -
} \ No newline at end of file diff --git a/src/Feature/Orders/website/Views/Orders/OrderReview.cshtml b/src/Feature/Orders/website/Views/Orders/OrderReview.cshtml index c82661f68..84575870a 100644 --- a/src/Feature/Orders/website/Views/Orders/OrderReview.cshtml +++ b/src/Feature/Orders/website/Views/Orders/OrderReview.cshtml @@ -63,86 +63,7 @@ var deliveryPageLocation = Model.DeliveryPageLocation; var billingPageLocation = Model.BillingPageLocation; -
- -
+

@orderTotalLabel

diff --git a/src/Feature/ProductBundle/website/Views/ProductBundle/AddBundleToCart.cshtml b/src/Feature/ProductBundle/website/Views/ProductBundle/AddBundleToCart.cshtml index 194eef46a..27172eced 100644 --- a/src/Feature/ProductBundle/website/Views/ProductBundle/AddBundleToCart.cshtml +++ b/src/Feature/ProductBundle/website/Views/ProductBundle/AddBundleToCart.cshtml @@ -9,106 +9,133 @@ @using Sitecore.Data.Items @using Sitecore.Commerce.XA.Foundation.Common; @model CatalogItemRenderingModel -@{ + +@if (!Sitecore.Context.PageMode.IsExperienceEditor) +{ Item relatedProduct = Model.CatalogItem; bool isBundle = relatedProduct["Tags"] != null && !String.IsNullOrEmpty(relatedProduct["Tags"]) ? relatedProduct["Tags"].Split('|').Any(t => t.ToLower() == "bundle") : false; -} -@if (isBundle) -{ -
- @if (!String.IsNullOrWhiteSpace(Model.ErrorMessage)) - { -
- @Model.ErrorMessage -
- } - else - { - if (Model == null) + + if (isBundle) + { +
+ @if (!String.IsNullOrWhiteSpace(Model.ErrorMessage)) { - return; +
+ @Model.ErrorMessage +
} + else + { + if (Model == null) + { + return; + } - var quantityLabel = Html.Sitecore().Field("Quantity Label", Html.Sitecore().CurrentItem); - var addToCartLabel = Html.Sitecore().Field("Add to Cart Button Label", Html.Sitecore().CurrentItem); - var addingToCartLabel = Html.Sitecore().CurrentItem["Add to Cart Button In-progress Label"]; + var quantityLabel = Html.Sitecore().Field("Quantity Label", Html.Sitecore().CurrentItem); + var addToCartLabel = Html.Sitecore().Field("Add to Cart Button Label", Html.Sitecore().CurrentItem); + var addingToCartLabel = Html.Sitecore().CurrentItem["Add to Cart Button In-progress Label"]; -
- @using (Ajax.BeginForm("AddBundleCartLine", "AddBundleToCart", null, new AjaxOptions { OnSuccess = "AddToCartForm.OnSuccess", HttpMethod = "Post" }, new { id = "AddBundleForm" })) - { - @Html.ValidationSummary(true) - @Html.Hidden("addtocart_productid", Model.ProductId) - @Html.Hidden("addtocart_variantid", string.Empty) - @Html.Hidden("addtocart_catalogname", Model.CatalogName) - @Html.Hidden("addtocart_relatedvariantids", string.Empty) - @Html.AntiForgeryToken() +
+ @using (Ajax.BeginForm("AddBundleCartLine", "AddBundleToCart", null, new AjaxOptions { OnSuccess = "AddToCartForm.OnSuccess", HttpMethod = "Post" }, new { id = "AddBundleForm" })) + { + @Html.ValidationSummary(true) + @Html.Hidden("addtocart_productid", Model.ProductId) + @Html.Hidden("addtocart_variantid", string.Empty) + @Html.Hidden("addtocart_catalogname", Model.CatalogName) + @Html.Hidden("addtocart_relatedvariantids", string.Empty) + @Html.AntiForgeryToken() -
- -
- - @Html.TextBoxFor(model => model.Quantity, new { @type = "number", @min = "1", @max = "100", @required = "required", @Value = "1", @class = "add-to-cart-qty-input" }) - +
+ +
+ + @Html.TextBoxFor(model => model.Quantity, new { @type = "number", @min = "1", @max = "100", @required = "required", @Value = "1", @class = "add-to-cart-qty-input" }) + +
-
- @Html.ValidationMessageFor(model => model.Quantity) -
- -
- } -
- } -
-} -else -{ - -
- @if (!String.IsNullOrWhiteSpace(Model.ErrorMessage)) - { -
- @Model.ErrorMessage -
- } - else - { - if (Model == null) + @Html.ValidationMessageFor(model => model.Quantity) +
+ +
+ } +
+ } +
+ } + else + { +
+ @if (!String.IsNullOrWhiteSpace(Model.ErrorMessage)) { - return; +
+ @Model.ErrorMessage +
} + else + { + if (Model == null) + { + return; + } - var quantityLabel = Html.Sitecore().Field("Quantity Label", Html.Sitecore().CurrentItem); - var addToCartLabel = Html.Sitecore().Field("Add to Cart Button Label", Html.Sitecore().CurrentItem); - var addingToCartLabel = Html.Sitecore().CurrentItem["Add to Cart Button In-progress Label"]; + var quantityLabel = Html.Sitecore().Field("Quantity Label", Html.Sitecore().CurrentItem); + var addToCartLabel = Html.Sitecore().Field("Add to Cart Button Label", Html.Sitecore().CurrentItem); + var addingToCartLabel = Html.Sitecore().CurrentItem["Add to Cart Button In-progress Label"]; -
- @using (Ajax.BeginForm("AddCartLine", "Cart", null, new AjaxOptions { OnSuccess = "AddToCartForm.OnSuccess", HttpMethod = "Post" })) - { - @Html.ValidationSummary(true) - @Html.Hidden("addtocart_productid", Model.ProductId) - @Html.Hidden("addtocart_variantid", string.Empty) - @Html.Hidden("addtocart_catalogname", Model.CatalogName) - @Html.AntiForgeryToken() +
+ @using (Ajax.BeginForm("AddCartLine", "Cart", null, new AjaxOptions { OnSuccess = "AddToCartForm.OnSuccess", HttpMethod = "Post" })) + { + @Html.ValidationSummary(true) + @Html.Hidden("addtocart_productid", Model.ProductId) + @Html.Hidden("addtocart_variantid", string.Empty) + @Html.Hidden("addtocart_catalogname", Model.CatalogName) + @Html.AntiForgeryToken() -
- -
- - @Html.TextBoxFor(model => model.Quantity, new { @type = "number", @min = "1", @max = "100", @required = "required", @Value = "1", @class = "add-to-cart-qty-input" }) - +
+ +
+ + @Html.TextBoxFor(model => model.Quantity, new { @type = "number", @min = "1", @max = "100", @required = "required", @Value = "1", @class = "add-to-cart-qty-input" }) + +
+ @Html.ValidationMessageFor(model => model.Quantity) +
+ +
+ } +
+ } +
+ } +} +else +{ + var quantityLabel = Html.Sitecore().Field("Quantity Label", Html.Sitecore().CurrentItem); + var addToCartLabel = Html.Sitecore().Field("Add to Cart Button Label", Html.Sitecore().CurrentItem); + var addingToCartLabel = Html.Sitecore().CurrentItem["Add to Cart Button In-progress Label"]; +
+ +
+
+ +
+ + @Html.TextBoxFor(model => model.Quantity, new { @type = "number", @min = "1", @max = "100", @required = "required", @Value = "1", @class = "add-to-cart-qty-input" }) +
- @Html.ValidationMessageFor(model => model.Quantity) -
- -
- } +
+
+ +
- }
-} + } + \ No newline at end of file diff --git a/src/Feature/ProductBundle/website/Views/ProductBundle/ProductBundle.cshtml b/src/Feature/ProductBundle/website/Views/ProductBundle/ProductBundle.cshtml index 6045d817e..ed753e41e 100644 --- a/src/Feature/ProductBundle/website/Views/ProductBundle/ProductBundle.cshtml +++ b/src/Feature/ProductBundle/website/Views/ProductBundle/ProductBundle.cshtml @@ -5,50 +5,53 @@ @using Sitecore.Mvc @using Sitecore.Data.Items @model CatalogItemRenderingModel -@{ + +@if (!Sitecore.Context.PageMode.IsExperienceEditor) +{ + Item currentItem = Html.Sitecore().CurrentItem; Item relatedProduct = Model.CatalogItem; bool hasTag = relatedProduct["Tags"] != null && !String.IsNullOrEmpty(relatedProduct["Tags"]) ? relatedProduct["Tags"].Split('|').Any(t => t.ToLower() == "bundle") : false; -} -@if (hasTag) -{ -
- - -
- - - + if (hasTag) + { +
+ + + +
+ + + + +
+ +