Error executing template "Designs/ProNails_generated/eCom7/CartV2/Step/ShoppingCart.cshtml" System.NullReferenceException: Object reference not set to an instance of an object. at CompiledRazorTemplates.Dynamic.RazorEngine_026f599b085248d2902169eeb7602e56.<>c__DisplayClass14_0.<renderProductBlock>b__0(TextWriter __razor_helper_writer) in D:\dynamicweb.net\Solutions\Bluedesk\pronails.cloud.dynamicweb-cms.com\files\Templates\Designs\ProNails_generated\eCom7\CartV2\Step\ShoppingCart.cshtml:line 2813 at CompiledRazorTemplates.Dynamic.RazorEngine_026f599b085248d2902169eeb7602e56.Execute() in D:\dynamicweb.net\Solutions\Bluedesk\pronails.cloud.dynamicweb-cms.com\files\Templates\Designs\ProNails_generated\eCom7\CartV2\Step\ShoppingCart.cshtml:line 2407 at RazorEngine.Templating.TemplateBase.RazorEngine.Templating.ITemplate.Run(ExecuteContext context, TextWriter reader) at RazorEngine.Templating.RazorEngineService.RunCompile(ITemplateKey key, TextWriter writer, Type modelType, Object model, DynamicViewBag viewBag) at RazorEngine.Templating.RazorEngineServiceExtensions.<>c__DisplayClass16_0.<RunCompile>b__0(TextWriter writer) at RazorEngine.Templating.RazorEngineServiceExtensions.WithWriter(Action`1 withWriter) at Dynamicweb.Rendering.RazorTemplateRenderingProvider.Render(Template template) at Dynamicweb.Rendering.TemplateRenderingService.Render(Template template) at Dynamicweb.Rendering.Template.RenderRazorTemplate()
1 @inherits Dynamicweb.Rendering.RazorTemplateBase<Dynamicweb.Rendering.RazorTemplateModel<Dynamicweb.Rendering.Template>> 2 @using Dynamicweb; 3 @using System.Globalization; 4 @using Dynamicweb.Ecommerce.Products; 5 @using Dynamicweb.Ecommerce.ProductCatalog; 6 @using Bluedesk.DynamicWeb.ItemTypes.Settings; 7 8 @{ 9 string listId = "shopping_cart"; 10 string listName = "Shopping cart"; 11 } 12 13 @inherits Dynamicweb.Rendering.RazorTemplateBase<Dynamicweb.Rendering.RazorTemplateModel<Dynamicweb.Rendering.Template>> 14 @using Dynamicweb; 15 @using Dynamicweb.Content.Items; 16 @using Bluedesk.DynamicWeb.ItemTypes.BaseSolution; 17 @using Bluedesk.DynamicWeb.ItemTypes.Settings.Configuration; 18 19 @{ 20 21 var colorService = new ColorSwatchService(); 22 23 /*** Masterconfig ***/ 24 25 var master_configuration = Dynamicweb.Services.Pages.GetPageByNavigationTag(Pageview.AreaID, "MasterConfiguration"); 26 MasterConfig mc = master_configuration.Item.ToCodeFirstItem<MasterConfig>(); 27 28 EcomConfig EcommerceConfiguration = mc.EcomConfiguration; 29 ProductDetailConfig ProductDetailConfiguration = mc.ProductDetailConfiguration; 30 ProductOverviewConfig ProductOverviewConfiguration = mc.ProductOverviewConfiguration; 31 32 /*** BEGIN Webshop Variables ***/ 33 34 WebshopPage webshopPage = Pageview.Page.Item.ToCodeFirstItem<WebshopPage>(); 35 36 /*** END Webshop Variables ***/ 37 38 /*** BEGIN Configuration Bools ***/ 39 40 bool FormattedBool = EcommerceConfiguration.FormattedPrices; 41 bool WithVATBool = Pageview.Area.EcomPricesWithVat == "True"; 42 bool pricesWithoutVatForUsers = EcommerceConfiguration.ShowPricesWithoutVatForUsers; 43 bool pricesWithoutVatForValidVat = EcommerceConfiguration.ShowPricesWithoutVatWhenValidVatNumber; 44 if(pricesWithoutVatForUsers && !pricesWithoutVatForValidVat && Pageview.User != null) { 45 WithVATBool = false; 46 } 47 if(pricesWithoutVatForValidVat && Pageview.User != null && !string.IsNullOrWhiteSpace(Pageview.User.VatRegNumber)) { 48 WithVATBool = false; 49 } 50 bool hidePricesForGuests = EcommerceConfiguration.HidePricesForGuests; 51 bool hideShoppingCartForGuests = EcommerceConfiguration.HideShoppingCartForGuests; 52 bool hideStockForGuests = EcommerceConfiguration.HideStockForGuests; 53 bool hideZeroPrices = EcommerceConfiguration.HideZeroPrices; 54 bool enableAddToCartForZeroPrices = EcommerceConfiguration.AddToCartAllowZeroPrices; 55 bool enableAddToCartForOutOfStock = EcommerceConfiguration.AddToCartAllowOutOfStock; 56 string stockFormat = EcommerceConfiguration.StockFormat; 57 bool showReviewTab = ProductDetailConfiguration.ShowProductDetailReviewTab; 58 bool allowBackInStockNotifications = EcommerceConfiguration.AllowBackInStockNotifications; 59 bool allowBackInStockNotificationsForGuests = EcommerceConfiguration.AllowBackInStockNotificationsForGuests; 60 bool displayBackInStockNotifications = !allowBackInStockNotificationsForGuests && Pageview.User == null ? false : allowBackInStockNotifications; 61 62 63 /*** END Configuration Bools ***/ 64 var p = Dynamicweb.Ecommerce.Services.Products.GetProductById(GetString("Ecom:Product.ID"), GetString("Ecom:Product.VariantID"), Pageview.Area.EcomLanguageId); 65 66 System.Web.HttpContext.Current.Session["productid"] = GetString("Ecom:Product.ID"); 67 68 var designRoot = "/Files/Templates/Designs/" + Pageview.Area.Layout.Design.Name; 69 70 bool isVariant = !string.IsNullOrWhiteSpace(GetString("Ecom:Product.VariantID")); 71 bool hasVariants = !isVariant ? GetLoop("VariantCombinations").Count > 0 : false; 72 73 string Manufacturer = GetString("Ecom:Manufacturer.Name"); 74 75 string productName = GetString("Ecom:Product.Name"); 76 System.Web.HttpContext.Current.Items["CurrentProductName"] = productName; 77 78 string productNumber = GetString("Ecom:Product.Number"); 79 80 string productShortDescription = GetString("Ecom:Product.ShortDescription"); 81 string productLongDescription = GetString("Ecom:Product.LongDescription"); 82 bool hasShortDescription = !string.IsNullOrWhiteSpace(productShortDescription); 83 bool hasLongDescription = !string.IsNullOrWhiteSpace(productLongDescription); 84 85 bool hasSpecifications = GetLoop("ProductCategories").Any(c => c.GetLoop("ProductCategoryFields").Any(d => !string.IsNullOrWhiteSpace(d.GetString("Ecom:Product.CategoryField.Value")))); 86 87 string manufacturerLogo = GetString("Ecom:Manufacturer.Logo"); 88 89 string price = GetString("Ecom:Product.Price"); 90 string priceFormatted = GetString("Ecom:Product.Price.PriceFormatted"); 91 bool hasDiscount = GetBoolean("Ecom:Product.HaveDiscount"); 92 bool pricezero = GetBoolean("Ecom:Product.Price.IsZero"); 93 94 string WithVATSuffix = WithVATBool ? "WithVAT" : "WithoutVAT"; 95 string FormattedSuffix = FormattedBool ? "Formatted" : ""; 96 97 string ProductdetailPriceSuffixWithVAT = Translate("Productdetail.Price.Suffix.WithVAT", "Incl. VAT"); 98 string ProductdetailPriceSuffixWithoutVAT = Translate("Productdetail.Price.Suffix.WithoutVAT", "Excl. VAT"); 99 100 string ProductdetailPriceSuffix = WithVATBool ? ProductdetailPriceSuffixWithVAT : ProductdetailPriceSuffixWithoutVAT; 101 102 string discountPriceValue = GetString("Ecom:Product.Discount.Price"); 103 double yourProfitValue = GetDouble("Ecom:Product.Discount.TotalAmount.Price.Value"); 104 string yourProfitValueFormatted = GetString("Ecom:Product.Discount.TotalAmount.Price" + WithVATSuffix + FormattedSuffix); 105 106 double productRating = GetDouble("Comments.Rating"); 107 var Comments = GetLoop("Comments"); 108 int productRatingCount = GetInteger("Comments.Count"); 109 var productReviewPercentage = (productRating * 10) * 2 + "%"; 110 111 double stockSize = hasVariants ? GetLoop("VariantInfos").Sum(item => item.GetDouble("Stock")) : GetDouble("Ecom:Product.Stock"); 112 string stock = stockSize.ToString(stockSize % 1 == 0 ? "N0" : "N2", CultureInfo.GetCultureInfo(Pageview.Area.CultureInfo.TwoLetterISOLanguageName)) ?? ""; 113 string stockText = GetString("Ecom:Product:Stock.Text"); 114 bool neverOutOfStock = GetBoolean("Ecom:Product.NeverOutOfStock"); 115 bool inStock = stockSize > 0 || neverOutOfStock; 116 string stockStateClass = inStock ? "pdp-stockstate--instock" : "pdp-stockstate--outofstock"; 117 118 string productTagline = GetString("Ecom:Product:Field.ProductTagline.Value"); 119 string productTaglineInfo = GetString("Ecom:Product:Field.ProductTaglineInfo.Value"); 120 string productDetailPageTagline = webshopPage.Tagline; 121 string productDetailPageTaglineInfo = webshopPage.TaglineInfo; 122 123 var productDetailUSPList = webshopPage.ProductDetailUSPs; 124 125 string defaultImage = Dynamicweb.Ecommerce.Services.ProductImages.GetImagePath(p); 126 var productid = GetString("Ecom:Product.ID"); 127 string groupid = GetString("Ecom:Product.PrimaryOrFirstGroupID"); 128 string productVariantId = GetString("Ecom:Product.VariantID"); 129 string addToCartLink = string.Format("/products/checkout?CartCmd=Add&ProductID={0}&Redirect=/products/checkout", productid); 130 List<LoopItem> detailImages = GetLoop("Details"); 131 List<LoopItem> productDiscounts = GetLoop("ProductDiscounts"); 132 List<string> productImages = new List<string>(); 133 134 if(!string.IsNullOrWhiteSpace(defaultImage)) 135 { 136 productImages.Add(defaultImage); 137 } 138 139 string YoutubeProductVideo = GetString("Ecom:Product:Field.YoutubeProductVideo.Value"); 140 bool hasYoutubeVideo = !string.IsNullOrWhiteSpace(YoutubeProductVideo) && YoutubeProductVideo != "Files/"; 141 142 var PerfionThumbnails = GetString("Ecom:Product:Field.AdditionalImages.Value").Split(';'); 143 144 foreach (var PerfionImage in PerfionThumbnails) 145 { 146 if (!string.IsNullOrWhiteSpace(PerfionImage)) 147 { 148 productImages.Add("Files/" + PerfionImage); 149 } 150 } 151 152 foreach (var image in detailImages) 153 { 154 var isImageAsset = image.GetString("Ecom:Product:Detail.Image"); 155 var link = image.GetString("Ecom:Product:Detail.Image.Clean"); 156 if (!string.IsNullOrWhiteSpace(isImageAsset) && !string.IsNullOrWhiteSpace(link) && !productImages.Contains(link)) 157 { 158 productImages.Add(link); 159 } 160 } 161 162 System.Web.HttpContext.Current.Items["productDetailActive"] = true; 163 164 var productNumberLabel = Translate("Productdetail.ArticleNumber.Prefix", "Artikelnummer:"); 165 var originalPriceLabel = Translate("Productdetail.OriginalPriceLabel", "Adviesprijs"); 166 var yourProfitLabel = Translate("Productdetail.YourProfitLabel", "Uw voordeel:"); 167 var inShoppingCartLabel = Translate("ProductDetail.InShoppingCart", "In winkelwagen"); 168 169 // Payment provider logo's 170 var selectedPaymentLogos = Pageview.Area.Item["FooterPaymentLogos"]; 171 172 bool enableShoppingCart = hideShoppingCartForGuests && Pageview.User == null ? false : Pageview.Area.Item["ConfigModuleShoppingCart"] != null ? (bool)Pageview.Area.Item["ConfigModuleShoppingCart"] : false; 173 bool displayPrice = hidePricesForGuests ? Pageview.User != null : true; 174 bool enableProductCompare = Pageview.Area.Item["ConfigModuleProductCompare"] != null ? (bool)Pageview.Area.Item["ConfigModuleProductCompare"] : false; 175 bool enableProductFavorites = Pageview.User == null ? false : Pageview.Area.Item["ConfigModuleFavoriteLists"] != null ? (bool)Pageview.Area.Item["ConfigModuleFavoriteLists"] : false; 176 bool enableProductStock = hideStockForGuests ? Pageview.User != null : true; 177 178 bool enableProductShoppingCart = enableShoppingCart; 179 if(!enableAddToCartForZeroPrices && pricezero) 180 { 181 enableProductShoppingCart = false; 182 } 183 if(!enableAddToCartForOutOfStock && !inStock) 184 { 185 enableProductShoppingCart = false; 186 } 187 if(GetBoolean("Ecom:Product.Discontinued")) 188 { 189 enableProductShoppingCart = false; 190 } 191 192 string originalProductPrice = GetString("Ecom:Product.Price.Price" + WithVATSuffix + FormattedSuffix); 193 string discountProductPrice = GetString("Ecom:Product.Discount.Price.Price" + WithVATSuffix + FormattedSuffix); 194 double discountProductPriceDouble = GetDouble("Ecom:Product.Discount.Price.Price"); 195 string gtmPrice = GetString("Ecom:Product.Price.Price.Value").Replace(",", "."); 196 string gtmDiscount = GetString("Ecom:Product.Discount.TotalAmount.Price.Value").Replace(",", "."); 197 string gtmValue = GetString("Ecom:Product.Discount.Price.Value").Replace(",", "."); 198 double discountPercentage = Math.Round((((GetDouble("Ecom:Product.Discount.Price.Price" + WithVATSuffix) - GetDouble("Ecom:Product.Price.Price" + WithVATSuffix)) / GetDouble("Ecom:Product.Price.Price" + WithVATSuffix)) * 100)); 199 200 string informativePrice = GetString("Ecom:Product.InformativePrice.Price" + WithVATSuffix + FormattedSuffix); 201 if (EcommerceConfiguration.UseInformativePriceAsFromPrice && !string.IsNullOrWhiteSpace(informativePrice)) 202 { 203 double informativePriceValue = GetDouble("Ecom:Product.InformativePrice.Price" + WithVATSuffix); 204 hasDiscount = discountProductPriceDouble < informativePriceValue; 205 if (hasDiscount) 206 { 207 originalProductPrice = informativePrice; 208 discountPercentage = Math.Round((((GetDouble("Ecom:Product.Price.Price" + WithVATSuffix) - informativePriceValue) / informativePriceValue) * 100)); 209 yourProfitValue = informativePriceValue - GetDouble("Ecom:Product.Price.Price" + WithVATSuffix); 210 yourProfitValueFormatted = WithVATBool ? new PriceInfo { PriceWithVAT = yourProfitValue }.PriceWithVATFormatted : new PriceInfo { PriceWithoutVAT = yourProfitValue }.PriceWithoutVATFormatted; 211 } 212 } 213 214 string retailPrice = ""; 215 bool displayRetailPrice = EcommerceConfiguration.DisplayRetailPrice; 216 if(EcommerceConfiguration.DisplayRetailPriceForUsers && Pageview.User == null) { 217 displayRetailPrice = false; 218 } 219 if(displayRetailPrice) { 220 string priceFieldName = EcommerceConfiguration.RetailPriceField; 221 if(EcommerceConfiguration.RetailPriceIsDbPrice) { 222 PriceRaw customerPriceRaw = new PriceRaw(GetDouble("Ecom:Product.DBPrice"), Dynamicweb.Ecommerce.Common.Context.Currency); 223 PriceCalculated customerPrice = new Dynamicweb.Ecommerce.Prices.PriceCalculated(customerPriceRaw); 224 if(customerPrice.Price > 0) { 225 retailPrice = WithVATBool ? customerPrice.PriceWithVATFormatted : customerPrice.PriceWithoutVATFormatted; 226 } 227 } else if(!string.IsNullOrWhiteSpace(priceFieldName)) { 228 double customerPriceValue = GetDouble($"Ecom:Product:Field.{priceFieldName}.Value"); 229 if(customerPriceValue > 0) { 230 retailPrice = WithVATBool ? new PriceInfo { PriceWithVAT = customerPriceValue }.PriceWithVATFormatted : new PriceInfo { PriceWithoutVAT = customerPriceValue }.PriceWithoutVATFormatted; 231 } 232 } 233 } 234 235 PriceInfo minPrice = null; 236 string minPriceFormatted = ""; 237 PriceInfo maxPrice = null; 238 string maxPriceFormatted = ""; 239 240 if(hasVariants) 241 { 242 minPrice = p.VariantCombinations.OrderBy(c => c.Product.Price.Price).FirstOrDefault().Product.Price; 243 maxPrice = p.VariantCombinations.OrderByDescending(c => c.Product.Price.Price).FirstOrDefault().Product.Price; 244 minPriceFormatted = WithVATBool ? minPrice.PriceWithVATFormatted : minPrice.PriceWithoutVATFormatted; 245 maxPriceFormatted = WithVATBool ? maxPrice.PriceWithVATFormatted : maxPrice.PriceWithoutVATFormatted; 246 } 247 248 bool displayProductPrice = displayPrice; 249 if(hideZeroPrices && pricezero) 250 { 251 if((hasVariants && !isVariant)) 252 { 253 if(minPrice.Price == 0 && maxPrice.Price == 0) 254 { 255 displayProductPrice = false; 256 } 257 } else { 258 displayProductPrice = false; 259 } 260 } 261 262 string productRibbon = GetString("Ecom:Product:Field.Product_Ribbon.Value"); 263 string productRibbonStyle = GetString("Ecom:Product:Field.RibbonStyle.Value"); 264 265 int QuotePageID = Bluedesk.Tools.DynamicWeb.Generic.PageHelper.GetPageIDByNavigationTag("QuoteForm", Pageview.AreaID); 266 var buttonIconClass = Pageview.Area.Item["Global_button_icon"] != null ? Pageview.Area.Item["Global_button_icon"].ToString().Replace("+", " ") : "fal fa-arrow-right"; 267 var minimumQuantity = GetDouble("Ecom:Product.PurchaseMinimumQuantity") == 0 ? 1 : GetDouble("Ecom:Product.PurchaseMinimumQuantity"); 268 var quantityStep = GetDouble("Ecom:Product.PurchaseQuantityStep") == 0 ? 1 : GetDouble("Ecom:Product.PurchaseQuantityStep"); 269 270 var productCategory = Dynamicweb.Ecommerce.Services.ProductGroups.GetGroup(GetString("Ecom:Product.PrimaryOrFirstGroupID")); 271 string productCategoryName = productCategory != null ? productCategory.Name.Replace("''", "\\\"").Replace("'", "") : ""; 272 273 string AddToCartButtonBackgroundColor = EcommerceConfiguration.AddToCartButtonBackgroundColor; 274 if (!string.IsNullOrWhiteSpace(AddToCartButtonBackgroundColor)) 275 { 276 AddToCartButtonBackgroundColor = !AddToCartButtonBackgroundColor.Contains("#") ? colorService.GetHexColor(Pageview.AreaID, AddToCartButtonBackgroundColor) : AddToCartButtonBackgroundColor; 277 } 278 279 bool enableSpecificationsSidebar = !ProductDetailConfiguration.ShowSpecificationsAsTab; 280 281 string productSubTitle = GetString("Ecom:Product:Field.ProductDetailSubtitle"); 282 283 bool showTitleAboveInfoBlock = ProductDetailConfiguration.ShowTitleAboveInfoBlock; 284 bool showVariantInfo = ProductDetailConfiguration.ShowVariantInfo; 285 286 List<LoopItem> AlternativeImages = GetLoop("Ecom:Product.AlternativeImages"); 287 288 foreach (var image in AlternativeImages) 289 { 290 var link = image.GetString("Ecom:Product.AlternativeImages.Image"); 291 if(!productImages.Contains(link)) 292 { 293 if (!string.IsNullOrWhiteSpace(link)) 294 { 295 productImages.Add(link); 296 } 297 } 298 } 299 300 //string defaultImage = GetString("Ecom:Product.ImageDefault.Default.Clean"); 301 if(!string.IsNullOrWhiteSpace(defaultImage) && !productImages.Contains(defaultImage)) 302 { 303 productImages.Add(defaultImage); 304 } 305 } 306 307 @inherits Dynamicweb.Rendering.RazorTemplateBase<Dynamicweb.Rendering.RazorTemplateModel<Dynamicweb.Rendering.Template>> 308 @using Dynamicweb; 309 @using Bluedesk.DynamicWeb.ItemTypes.Pages; 310 @using Bluedesk.Tools.DynamicWeb.ExtensionMethods; 311 @using System.Linq; 312 @using Dynamicweb.Content; 313 @using Dynamicweb.Ecommerce.Stocks; 314 @using Dynamicweb.Ecommerce.CustomerExperienceCenter.Favorites; 315 316 @helper RenderInfoContentElement(string ItemName, string header, string anchor) 317 { 318 if (!string.IsNullOrWhiteSpace(GetString(ItemName))) 319 { 320 <section id="@anchor" class="pdp-specifications-info__wrapper"> 321 @if(!string.IsNullOrWhiteSpace(header)) 322 { 323 <h2 class="pdp-specifications-info__header">@header</h2> 324 } 325 <section class="pdp-specifications-info__body"> 326 @GetString(ItemName) 327 </section> 328 <div class="pdp-specifications-info__showmore"> 329 <section class="pdp-specifications-info__showmoregradient"></section> 330 <button class="pdp-button pdp-button--inline pdp-button--showmore" type="button">@Translate("Productdetail.Info.ShowMore", "Show more")</button> 331 <button class="pdp-button pdp-button--inline pdp-button--showless" type="button" style="display: none;">@Translate("Productdetail.Info.ShowLess", "Show less")</button> 332 </div> 333 </section> 334 } 335 } 336 337 @helper RenderLongDescription() 338 { 339 string longDescription = GetString("Ecom:Product.LongDescription"); 340 string YoutubeProductVideo = GetString("Ecom:Product:Field.YoutubeProductVideo.Value"); 341 bool hasYoutubeVideo = !string.IsNullOrWhiteSpace(YoutubeProductVideo) && YoutubeProductVideo != "Files/"; 342 343 if (!string.IsNullOrWhiteSpace(longDescription)) 344 { 345 <section id="longdescription" class="pdp-specifications-info__wrapper"> 346 <section class="pdp-specifications-info__body"> 347 <div class="pdp-specifications-info__content"> 348 @longDescription 349 350 @if(!string.IsNullOrWhiteSpace(GetString("Ecom:Product:Field.WhatItIs.Value.Clean"))) 351 { 352 <h4>@GetString("Ecom:Product:Field.WhatItIs.Name")</h4> 353 @GetString("Ecom:Product:Field.WhatItIs.Value.Clean") 354 } 355 356 @if(!string.IsNullOrWhiteSpace(GetString("Ecom:Product:Field.GoodToKnow.Value.Clean"))) 357 { 358 <h4>@GetString("Ecom:Product:Field.GoodToKnow.Name")</h4> 359 @GetString("Ecom:Product:Field.GoodToKnow.Value.Clean") 360 } 361 362 @if(!string.IsNullOrWhiteSpace(GetString("Ecom:Product:Field.WhyYouLoveIt.Value.Clean"))) 363 { 364 <h4>@GetString("Ecom:Product:Field.WhyYouLoveIt.Name")</h4> 365 @GetString("Ecom:Product:Field.WhyYouLoveIt.Value.Clean") 366 } 367 368 </div> 369 @if(hasYoutubeVideo) 370 { 371 <div class="pdp-specifications-info__video"> 372 <lite-youtube videoid="@YoutubeProductVideo" params="controls=1&loop=0&playlist=@YoutubeProductVideo&playsinline=1&modestbranding=1&mute=0&rel=0&enablejsapi=1& origin=@Dynamicweb.Environment.Helpers.LinkHelper.GetHttpDomain()&disablekb=0"></lite-youtube> 373 </div> 374 } 375 </section> 376 <div class="pdp-specifications-info__showmore"> 377 <section class="pdp-specifications-info__showmoregradient"></section> 378 <button class="pdp-button pdp-button--inline pdp-button--showmore" type="button">@Translate("Productdetail.Info.ShowMore", "Show more")</button> 379 <button class="pdp-button pdp-button--inline pdp-button--showless" type="button" style="display: none;">@Translate("Productdetail.Info.ShowLess", "Show less")</button> 380 </div> 381 </section> 382 } 383 } 384 385 @helper renderReview(string name, string body, int Rating) 386 { 387 <li class="pdp-review__list-item"> 388 <div class="pdp-review-item__header"> 389 <p class="pdp-review-item__name">@name</p> 390 @renderReviewIndicator("#DB3125", Rating, "row", false, 0) 391 </div> 392 <p class="pdp-review-item__comment">@body</p> 393 </li> 394 } 395 396 @helper renderProduct(LoopItem product, string productdetailPriceSuffix, string WithVATSuffix, string FormattedSuffix, bool enableShoppingCart, bool displayPrice, bool enableProductStock, string listid, string listname, EcomConfig EcommerceConfiguration) 397 { 398 bool hideZeroPrices = EcommerceConfiguration.HideZeroPrices; 399 bool enableAddToCartForZeroPrices = EcommerceConfiguration.AddToCartAllowZeroPrices; 400 bool enableAddToCartForOutOfStock = EcommerceConfiguration.AddToCartAllowOutOfStock; 401 string stockFormat = EcommerceConfiguration.StockFormat; 402 403 int productDetailPageId = GetPageIdByNavigationTag("ProductOverview"); 404 405 string productName = product.GetString("Ecom:Product.Name"); 406 string productNumber = product.GetString("Ecom:Product.Number"); 407 string productId = product.GetString("Ecom:Product.ID"); 408 string productVariantId = product.GetString("Ecom:Product.VariantID"); 409 string productURL = product.GetString("Ecom:Product.VariantLinkGroup.Clean").Replace($"ID={Pageview.ID.ToString()}&", $"ID={productDetailPageId.ToString()}&"); 410 var p = Dynamicweb.Ecommerce.Services.Products.GetProductById(productId, productVariantId, Pageview.Area.EcomLanguageId); 411 string productImage = Dynamicweb.Ecommerce.Services.ProductImages.GetImagePath(p); 412 413 bool isVariant = !string.IsNullOrWhiteSpace(p.VariantId); 414 bool hasVariants = !isVariant ? product.GetLoop("VariantCombinations").Count > 0 : false; 415 416 var productCategory = Dynamicweb.Ecommerce.Services.ProductGroups.GetGroup(product.GetString("Ecom:Product.PrimaryOrFirstGroupID")); 417 string productCategoryName = productCategory != null ? productCategory.Name.Replace("''", "\\\"").Replace("'", "") : ""; 418 string productManufacturer = product.GetString("Ecom:Manufacturer.Name"); 419 string gtmDiscount = product.GetString("Ecom:Product.Discount.TotalAmount.Price.Value").Replace(",", "."); 420 string gtmPrice = product.GetString("Ecom:Product.Price.Price.Value").Replace(",", "."); 421 422 bool isPriceZero = product.GetDouble("Ecom:Product.Price.Price.Value") <= 0; 423 bool hasDiscount = product.GetBoolean("Ecom:Product.HaveDiscount"); 424 string OriginalPrice = product.GetString("Ecom:Product.Price.Price" + WithVATSuffix + FormattedSuffix); 425 string Price = product.GetString("Ecom:Product.Discount.Price.Price" + WithVATSuffix + FormattedSuffix); 426 string yourDiscount = product.GetString("Ecom:Product.Discount.TotalAmount.Price" + WithVATSuffix + FormattedSuffix); 427 428 string informativePrice = product.GetString("Ecom:Product.InformativePrice.Price" + WithVATSuffix + FormattedSuffix); 429 if (EcommerceConfiguration.UseInformativePriceAsFromPrice && !string.IsNullOrWhiteSpace(informativePrice)) 430 { 431 double informativePriceValue = product.GetDouble("Ecom:Product.InformativePrice.Price"); 432 hasDiscount = product.GetDouble("Ecom:Product.Price.Price") < informativePriceValue; 433 if (hasDiscount) 434 { 435 OriginalPrice = product.GetString("Ecom:Product.InformativePrice.Price" + WithVATSuffix + FormattedSuffix); 436 double yourProfitValue = product.GetDouble("Ecom:Product.InformativePrice.Price" + WithVATSuffix) - product.GetDouble("Ecom:Product.Price.Price" + WithVATSuffix); 437 yourDiscount = WithVATSuffix == "WithVAT" ? new PriceInfo { PriceWithVAT = yourProfitValue }.PriceWithVATFormatted : new PriceInfo { PriceWithoutVAT = yourProfitValue }.PriceWithoutVATFormatted; 438 } 439 } 440 441 string retailPrice = ""; 442 bool displayRetailPrice = EcommerceConfiguration.DisplayRetailPrice; 443 if(EcommerceConfiguration.DisplayRetailPriceForUsers && Pageview.User == null) { 444 displayRetailPrice = false; 445 } 446 if(displayRetailPrice) { 447 string priceFieldName = EcommerceConfiguration.RetailPriceField; 448 if(EcommerceConfiguration.RetailPriceIsDbPrice) { 449 PriceRaw customerPriceRaw = new PriceRaw(product.GetDouble("Ecom:Product.DBPrice"), Dynamicweb.Ecommerce.Common.Context.Currency); 450 PriceCalculated customerPrice = new Dynamicweb.Ecommerce.Prices.PriceCalculated(customerPriceRaw); 451 if(customerPrice.Price > 0) { 452 retailPrice = WithVATSuffix == "WithVAT" ? customerPrice.PriceWithVATFormatted : customerPrice.PriceWithoutVATFormatted; 453 } 454 } else if(!string.IsNullOrWhiteSpace(priceFieldName)) { 455 double customerPriceValue = product.GetDouble($"Ecom:Product:Field.{priceFieldName}.Value"); 456 if(customerPriceValue > 0) { 457 retailPrice = WithVATSuffix == "WithVAT" ? new PriceInfo { PriceWithVAT = customerPriceValue }.PriceWithVATFormatted : new PriceInfo { PriceWithoutVAT = customerPriceValue }.PriceWithoutVATFormatted; 458 } 459 } 460 } 461 462 PriceInfo minPrice = null; 463 string minPriceFormatted = ""; 464 PriceInfo maxPrice = null; 465 string maxPriceFormatted = ""; 466 467 if(hasVariants) 468 { 469 minPrice = p.VariantCombinations.OrderBy(c => c.Product.Price.Price).FirstOrDefault().Product.Price; 470 maxPrice = p.VariantCombinations.OrderByDescending(c => c.Product.Price.Price).FirstOrDefault().Product.Price; 471 minPriceFormatted = WithVATSuffix == "WithVAT" ? minPrice.PriceWithVATFormatted : minPrice.PriceWithoutVATFormatted; 472 maxPriceFormatted = WithVATSuffix == "WithVAT" ? maxPrice.PriceWithVATFormatted : maxPrice.PriceWithoutVATFormatted; 473 } 474 475 double stockSize = hasVariants ? product.GetLoop("VariantInfos").Sum(item => item.GetDouble("Stock")) : product.GetDouble("Ecom:Product.Stock"); 476 string stockText = product.GetString("Ecom:Product:Stock.Text"); 477 string stockSizeFormatted = stockSize.ToString(stockSize % 1 == 0 ? "N0" : "N2", System.Globalization.CultureInfo.GetCultureInfo(Pageview.Area.CultureInfo.TwoLetterISOLanguageName)); 478 bool neverOutOfStock = product.GetBoolean("Ecom:Product.NeverOutOfStock"); 479 bool isInStock = stockSize > 0 || neverOutOfStock; 480 string stockClass = isInStock ? "products-module__product-stock-state--instock" : "products-module__product-stock-state--outofstock"; 481 482 int productRating = product.GetInteger("Ecom:Product.Rating"); 483 int productCommentCount = product.GetInteger("Ecom:Product.CommentCount"); 484 string productRibbon = product.GetString("Ecom:Product:Field.Product_Ribbon.Value"); 485 string productRibbonStyle = product.GetString("Ecom:Product:Field.RibbonStyle.Value"); 486 487 string yourProfitLabel = Translate("Productdetail.YourProfitLabel", "Uw voordeel:"); 488 489 string manufacturerName = product.GetString("Ecom:Manufacturer.Name"); 490 491 bool enableProductShoppingCart = enableShoppingCart; 492 if(!enableAddToCartForZeroPrices && isPriceZero) { 493 enableProductShoppingCart = false; 494 } 495 if(!enableAddToCartForOutOfStock && !isInStock) { 496 enableProductShoppingCart = false; 497 } 498 if(product.GetBoolean("Ecom:Product.Discontinued")) { 499 enableProductShoppingCart = false; 500 } 501 502 bool displayProductPrice = displayPrice; 503 if(hideZeroPrices && isPriceZero) 504 { 505 if((hasVariants && !isVariant)) 506 { 507 if(minPrice.Price == 0 && maxPrice.Price == 0) 508 { 509 displayProductPrice = false; 510 } 511 } else { 512 displayProductPrice = false; 513 } 514 } 515 516 Dynamicweb.Ecommerce.Products.Group primaryGroup = Dynamicweb.Ecommerce.Services.ProductGroups.GetGroup(product.GetString("Ecom:Product.PrimaryOrFirstGroupID"), Pageview.Area.EcomLanguageId, true); 517 string groupid = primaryGroup?.Id; 518 519 if (primaryGroup != null && primaryGroup.ShopId != Pageview.Area.EcomShopId) 520 { 521 var newPrimaryGroup = Dynamicweb.Ecommerce.Services.ProductGroups.GetProductGroupRelations(productId).FirstOrDefault(g => Dynamicweb.Ecommerce.Services.ProductGroups.GetGroup(g.GroupId, Pageview.Area.EcomLanguageId, true)?.ShopId == Pageview.Area.EcomShopId); 522 groupid = newPrimaryGroup?.GroupId; 523 } 524 525 productURL = $"Default.aspx?ID={productDetailPageId}&GroupID={groupid}&ProductID={productId}"; 526 527 <li class="products-module__slider-cell"> 528 <div class="products-module products-module__slider-cell__innerwrapper"> 529 530 @if (!string.IsNullOrWhiteSpace(productRibbon)) 531 { 532 <p class="products-module__ribbon products-module__ribbon--@productRibbonStyle"> 533 <span class="products-module__ribbon-text">@productRibbon</span> 534 </p> 535 } 536 537 <figure class="products-module__image-container"> 538 @if(!string.IsNullOrWhiteSpace(productImage)) 539 { 540 <img src="/Admin/Public/GetImage.ashx?Image=@productImage&Crop=7&Format=webp&Quality=90&Compression=80&Height=200" alt="@productName" loading="lazy" height="200" width="300" /> 541 } 542 </figure> 543 544 <h3 class="products-module__product-name"> 545 @if(!string.IsNullOrWhiteSpace(manufacturerName)) 546 { 547 <span class="products-module__product-name--manufacturer">@manufacturerName</span> 548 } 549 <span class="products-module__product-name--product">@productName</span> 550 </h3> 551 552 @if(!string.IsNullOrWhiteSpace(productNumber)) 553 { 554 <p class="products-module__product-article-number">@productNumber</p> 555 } 556 557 @if (productCommentCount > 0) 558 { 559 <div class="products-module__product-review-indicator"> 560 @renderReviewIndicator("gold", productRating, "row", true, productCommentCount) 561 </div> 562 } 563 564 @if (displayPrice && displayProductPrice) 565 { 566 <div class="products-module__product-price-container"> 567 @if(hasVariants) { 568 <section class="pdp-price__subcontainer"> 569 @if(minPrice != maxPrice) 570 { 571 <span class="products-module__product-price">@minPriceFormatted - @maxPriceFormatted</span> 572 } 573 else 574 { 575 <span class="products-module__product-price">@minPriceFormatted</span> 576 } 577 <span class="products-module__product-price-suffix">@productdetailPriceSuffix</span> 578 </section> 579 } 580 else 581 { 582 if (hasDiscount) 583 { 584 <span class="products-module__product-price-original">@OriginalPrice</span> 585 <span class="products-module__product-price">@Price</span> 586 } 587 else 588 { 589 <span class="products-module__product-price">@OriginalPrice</span> 590 } 591 <span class="products-module__product-price-suffix">@productdetailPriceSuffix</span> 592 if (!string.IsNullOrWhiteSpace(retailPrice)) 593 { 594 <p class="products-module__product-retail-price"> 595 @String.Format(Translate("ProductBlockTitle.RetailPrice", "Retail price: {0}"), retailPrice) 596 </p> 597 } 598 if (hasDiscount) 599 { 600 <p class="products-module__product-your-discount">@Translate("Productdetail.YourProfitLabel", "Uw voordeel:") @yourDiscount</p> 601 } 602 } 603 </div> 604 } 605 606 @if(!string.IsNullOrWhiteSpace(product.GetString("Ecom:Product:Field.ProductUSP1")) || !string.IsNullOrWhiteSpace(product.GetString("Ecom:Product:Field.ProductUSP2")) || !string.IsNullOrWhiteSpace(product.GetString("Ecom:Product:Field.ProductUSP3")) || !string.IsNullOrWhiteSpace(product.GetString("Ecom:Product:Field.ProductUSP4")) || !string.IsNullOrWhiteSpace(product.GetString("Ecom:Product:Field.ProductUSP5"))) 607 { 608 <ul class="pdp-usplist products-module__product-usplist"> 609 @RenderProductModuleUSP(product.GetString("Ecom:Product:Field.ProductUSP1"), product.GetString("Ecom:Product:Field.ProductUSP1Info")) 610 @RenderProductModuleUSP(product.GetString("Ecom:Product:Field.ProductUSP2"), product.GetString("Ecom:Product:Field.ProductUSP2Info")) 611 @RenderProductModuleUSP(product.GetString("Ecom:Product:Field.ProductUSP3"), product.GetString("Ecom:Product:Field.ProductUSP3Info")) 612 @RenderProductModuleUSP(product.GetString("Ecom:Product:Field.ProductUSP4"), product.GetString("Ecom:Product:Field.ProductUSP4Info")) 613 @RenderProductModuleUSP(product.GetString("Ecom:Product:Field.ProductUSP5"), product.GetString("Ecom:Product:Field.ProductUSP5Info")) 614 </ul> 615 } 616 617 <section class="products-module__product-actions"> 618 619 620 <section class="products-module__product-actions"> 621 @if(hasVariants) 622 { 623 <div class="products-module__product-variant-info"> 624 @if(product.GetLoop("VariantCombinations").Count == 1) 625 { 626 <p>@string.Format(Translate("ProductBlockVariantInfo.VariantCount.Single", "{0} variant"), product.GetLoop("VariantCombinations").Count)</p> 627 } 628 else 629 { 630 <p>@string.Format(Translate("ProductBlockVariantInfo.VariantCount.Multiple", "{0} variants"), product.GetLoop("VariantCombinations").Count)</p> 631 } 632 </div> 633 } 634 635 @if(enableProductStock && !string.IsNullOrWhiteSpace(stockFormat)) 636 { 637 if(stockFormat == "text") { 638 if (!string.IsNullOrWhiteSpace(stockText)) 639 { 640 <p class="products-module__product-stock-state @stockClass">@string.Format(stockText, stockSize)</p> 641 } 642 } 643 else 644 { 645 string translationTag = ""; 646 if(neverOutOfStock) 647 { 648 translationTag = Translate("ProductBlockStockInfo.AmountInStock", "In stock"); 649 } 650 else if(stockSize == 1) 651 { 652 translationTag = Translate("ProductBlockStockInfo.AmountInStockSingle", "{0} product in stock"); 653 } 654 else if(stockSize > 1) 655 { 656 translationTag = Translate("ProductBlockStockInfo.AmountInStockMultiple", "{0} products in stock"); 657 } 658 else if(!isInStock) 659 { 660 translationTag = Translate("ProductBlockStockInfo.AmountOutOfStock", "Out of stock"); 661 } 662 <p class="products-module__product-stock-state @stockClass">@string.Format(translationTag, stockSizeFormatted)</p> 663 } 664 } 665 666 @if(!hasVariants && enableShoppingCart && enableProductShoppingCart) 667 { 668 <add-to-cart 669 class="app-addtocart" 670 data-prodid="@productId" 671 data-variantid="@productVariantId" 672 data-show-text="False" 673 data-min-quantity="@product.GetDouble("Ecom:Product.PurchaseMinimumQuantity")" 674 data-step="@product.GetDouble("Ecom:Product.PurchaseQuantityStep")" 675 data-list-id="@listid" 676 data-list-name="@listname"> 677 </add-to-cart> 678 } 679 </section> 680 <a href="@productURL" class="products-module__link" data-id="@productId" data-variantid="@productVariantId" data-number="@productNumber" data-name='@productName.Replace("''", "\\\"").Replace("'", "")' data-position="@product.GetInteger("Products.LoopCounter")" data-brand='@productManufacturer.Replace("''", "\\\"").Replace("'", "")' data-category='@productCategoryName' data-listid="@listid" data-listname="@listname" data-gtmprice="@gtmPrice" data-gtmdiscount="@gtmDiscount" aria-label="@productName"></a> 681 </div> 682 </li> 683 } 684 685 @helper renderProductListEnhancedEcom(string listId, string listName, List<LoopItem> products, string WithVATSuffix) { 686 <text> 687 <script> 688 if(window.dataLayer) { 689 dataLayer.push({ 690 'event': 'view_item_list', 691 'ecommerce': { 692 'item_list_id': '@listId', 693 'item_list_name': '@listName', 694 'items': [ 695 @foreach (var product in products) { 696 var relCategoryObject = Dynamicweb.Ecommerce.Services.ProductGroups.GetGroup(product.GetString("Ecom:Product.PrimaryOrFirstGroupID")); 697 string relCategoryName = relCategoryObject != null ? relCategoryObject.Name.Replace("''", "\\\"").Replace("'", "") : ""; 698 <text> 699 { 700 'item_id': '@product.GetString("Ecom:Product.ID")', 701 'item_name': '@product.GetString("Ecom:Product.Name").Replace("''", "\\\"").Replace("'", "")', 702 'item_number': '@product.GetString("Ecom:Product.Number")', 703 'discount': @product.GetString("Ecom:Product.Discount.TotalAmount.Price.Value").Replace(",", "."), 704 'index': @product.GetInteger("Products.LoopCounter"), 705 'item_brand': '@product.GetString("Ecom:Manufacturer.Name").Replace("''", "\\\"").Replace("'", "")', 706 'item_category': '@relCategoryName', 707 'item_list_id': '@listId', 708 'item_list_name': '@listName', 709 'item_variant': '@product.GetString("Ecom:Product.VariantID")', 710 'price': @product.GetString("Ecom:Product.Price.Price.Value").Replace(",", "."), 711 'quantity': 1, 712 }, 713 </text> 714 } 715 ] 716 } 717 }); 718 } 719 </script> 720 </text> 721 } 722 723 @helper renderReviewIndicator(string progressbarColor, int value, string flexDirection, bool boolReviews, int CommentCount) 724 { 725 int ratingPercentage = (100 / 5) * value; 726 727 <section style="display: flex; flex-direction: @flexDirection; position: relative; align-items: center;"> 728 <div class="reviews__indicator" style="display: flex; flex-direction: row;"> 729 <div class="reviews__indicator-progressbar" style="width: @ratingPercentage.ToString()%; background-color: @progressbarColor;"></div> 730 <ul class="reviews__indicator-star-list"> 731 <li class="reviews__indicator-star-list-item"></li> 732 <li class="reviews__indicator-star-list-item"></li> 733 <li class="reviews__indicator-star-list-item"></li> 734 <li class="reviews__indicator-star-list-item"></li> 735 <li class="reviews__indicator-star-list-item"></li> 736 </ul> 737 </div> 738 <span style="display: flex; position: relative; font-size: 12px; line-height: initial;"> 739 @value / 5 740 @if (boolReviews) 741 {<text>(@CommentCount reviews)</text>} 742 </span> 743 </section> 744 745 } 746 747 @helper RenderProductUSP(string USP, string USPInfo) 748 { 749 if (!string.IsNullOrWhiteSpace(USP)) 750 { 751 <li class="pdp-usplist__item pdp-usplist__item--product"> 752 <i class="fal fa-check"></i> 753 @USP 754 @if (!string.IsNullOrWhiteSpace(USPInfo)) 755 { 756 <span data-tippy-content="@USPInfo" class="pdp-usplist__infoicon"> 757 <i class="fal fa-info-circle"></i> 758 </span> 759 } 760 </li> 761 } 762 } 763 764 @helper RenderProductDetailUSP(string USP, string USPInfo) 765 { 766 if (!string.IsNullOrWhiteSpace(USP)) 767 { 768 <li class="pdp-usplist__item pdp-usplist__item--page"> 769 <i class="fal fa-dot-circle"></i> 770 @USP 771 @if (!string.IsNullOrWhiteSpace(USPInfo)) 772 { 773 <span data-tippy-content="@USPInfo" class="pdp-usplist__infoicon"> 774 <i class="fal fa-info-circle"></i> 775 </span> 776 } 777 </li> 778 } 779 } 780 781 @helper RenderProductModuleUSP(string USP, string USPInfo) 782 { 783 if (!string.IsNullOrWhiteSpace(USP)) 784 { 785 <li class="pdp-usplist__item pdp-usplist__item--product"> 786 <i class="fal fa-check"></i> 787 @USP 788 @if (!string.IsNullOrWhiteSpace(USPInfo)) 789 { 790 <span data-tippy-content="@USPInfo" class="pdp-usplist__infoicon"> 791 <i class="fal fa-info-circle"></i> 792 </span> 793 } 794 </li> 795 } 796 } 797 798 @* @helper RenderPDPTabs() 799 { 800 string downloadProductsheet = GetString("Ecom:Product:Field.DownloadProductsheet.Clean"); 801 802 <div class="pdp-specifications-navigation__container"> 803 804 <nav class="pdp-specifications-navigation"> 805 <a href="#information" class="pdp-specifications-navigation__tab pdp-specifications-navigation__tab--active">Productinformatie</a> 806 <a href="javascript:void(0);" class="pdp-specifications-navigation__tab toggle-of-canvas-menu" data-offcanvas-target="specifications">Specificaties</a> 807 @if (GetLoop("Comments").Count > 0) 808 { 809 <a href="#reviews" class="pdp-specifications-navigation__tab">Reviews</a> 810 } 811 </nav> 812 813 @if (!string.IsNullOrWhiteSpace(downloadProductsheet)) 814 { 815 <a class="productdetails__download btn btn--small ml-auto" href="@downloadProductsheet" target="_blank"> 816 <span class="btn__text">@Translate("Productdetail.DownloadProductsheetLabel", "Download productsheet")</span> 817 <i class="btn__icon fal fa-file-pdf"></i> 818 </a> 819 } 820 821 </div> 822 } *@ 823 824 @helper renderProductCompareButton(string productId, string name, string thumbnail) { 825 string title = Translate("ProductBlockAction.Compare", "Add to product compare"); 826 827 <button class="po-block__action-btn po-block__action-btn--compare" aria-label="@title" title="@title" data-prodid="@productId" data-imgsmall="@thumbnail" data-prodname="@name"> 828 <i class="fas fa-exchange-alt"></i> 829 </button> 830 } 831 832 @helper renderProductFavoriteButton(string productId, string variantId, string productName, string productNumber, string gtmDiscount, string gtmPrice, string gtmValue, string productCategoryName, string productManufacturer, string listId, string listName) { 833 if(string.IsNullOrWhiteSpace(variantId)) { 834 variantId = ""; 835 } 836 837 string title = Translate("ProductBlockAction.Wishlist", "Add to wish list"); 838 bool isActive = Dynamicweb.Ecommerce.CustomerExperienceCenter.Favorites.UserExtensions.IsProductInAnyFavoriteList(Pageview.User, productId, variantId); 839 840 <button class="po-block__action-btn po-block__action-btn--wishlist" aria-label="@title" title="@title" data-productid="@productId" data-variantid="@variantId" data-offcanvas-target="favoritelist" data-productid="@productId" data-variantid="@variantId" data-number="@productNumber" data-name='@productName' data-position="1" data-brand='@productManufacturer' data-category='@productCategoryName' data-listid="@listId" data-listname="@listName" data-gtmprice="@gtmPrice" data-gtmdiscount="@gtmDiscount" data-gtmvalue="@gtmValue" data-currencycode="@Dynamicweb.Ecommerce.Common.Context.Currency.Code"> 841 @if(isActive) { 842 <i class="fas fa-heart"></i> 843 } else { 844 <i class="far fa-heart"></i> 845 } 846 </button> 847 848 } 849 850 @helper renderReviewDistrubition(List<LoopItem> comments) { 851 int totalCount = comments.Count; 852 @renderReviewDistrubitionLine(5, comments.Count(c => c.GetInteger("Rating") == 5), totalCount) 853 @renderReviewDistrubitionLine(4, comments.Count(c => c.GetInteger("Rating") == 4), totalCount) 854 @renderReviewDistrubitionLine(3, comments.Count(c => c.GetInteger("Rating") == 3), totalCount) 855 @renderReviewDistrubitionLine(2, comments.Count(c => c.GetInteger("Rating") == 2), totalCount) 856 @renderReviewDistrubitionLine(1, comments.Count(c => c.GetInteger("Rating") == 1), totalCount) 857 } 858 859 @helper renderReviewDistrubitionLine(int stars, int amount, int total) { 860 double progress = ((double)amount / (double)total) * 100; 861 862 <div class="review-distribution"> 863 <span class="review-distribution__star"> 864 @for (int i = 0; i < stars; i++) 865 { 866 <i class="fa fa-star" aria-hidden="true"></i> 867 868 } 869 </span> 870 <div class="review-distribution__progress"> 871 <div class="review-distribution__progress-background"></div> 872 <div class="review-distribution__progress-fill" style="width: @progress%;"></div> 873 </div> 874 <span class="review-distribution__count">@amount reviews</span> 875 </div> 876 } 877 878 @helper RenderPdpTabs(bool showReviewTab, bool enableSpecificationsSidebar) { 879 string productLongDescription = GetString("Ecom:Product.LongDescription"); 880 string downloadProductsheet = GetString("Ecom:Product:Field.DownloadProductsheet.Clean"); 881 882 bool hasDescription = !string.IsNullOrWhiteSpace(productLongDescription); 883 bool hasSpecifications = GetLoop("ProductCategories").Any(c => c.GetLoop("ProductCategoryFields").Any(d => !string.IsNullOrWhiteSpace(d.GetString("Ecom:Product.CategoryField.Value")))); 884 hasSpecifications = false; 885 bool hasReviews = showReviewTab; 886 bool hasDownloads = false; 887 bool hasProductSheet = !string.IsNullOrWhiteSpace(downloadProductsheet); 888 889 bool hasHowItWorks = !string.IsNullOrWhiteSpace(GetString("Ecom:Product:Field.HowToUse.Value.Clean")); 890 bool hasIngredients = !string.IsNullOrWhiteSpace(GetString("Ecom:Product:Field.Ingredients.Value.Clean")); 891 892 string activeItem = hasDescription ? "description" : "reviews"; 893 if(Dynamicweb.Context.Current.Request["reviewcmd"] != null && Dynamicweb.Context.Current.Request["reviewcmd"] == "created") { 894 activeItem = "reviews"; 895 } 896 897 if (!string.IsNullOrWhiteSpace(downloadProductsheet) && downloadProductsheet.StartsWith("../")) 898 { 899 downloadProductsheet = downloadProductsheet.Replace("../", "/Files/"); 900 } 901 902 if(hasDescription || hasReviews || hasDownloads) 903 { 904 <section id="pdp-tabs" class="pdp-tabs"> 905 <div class="container pdp-tabs__container"> 906 907 <div class="pdp-tabs__navigation"> 908 @if(hasDescription) 909 { 910 <a class='pdp-tabnavigation__item @(activeItem == "description" ? "pdp-tabnavigation__item--active" : "")' href="javascript:void(0);" data-tab="description"> 911 @Translate("Productdetail.DescriptionTab", "Description") 912 </a> 913 } 914 @if(hasSpecifications) 915 { 916 if(enableSpecificationsSidebar) 917 { 918 <a class="pdp-tabnavigation__item toggle-of-canvas-menu" href="javascript:void(0);" data-offcanvas-target="specifications"> 919 @Translate("Productdetail.SpecificationsTab", "Specifications") 920 </a> 921 } else { 922 <a class='pdp-tabnavigation__item @(activeItem == "specifications" ? "pdp-tabnavigation__item--active" : "")' href="javascript:void(0);" data-tab="specifications"> 923 @Translate("Productdetail.SpecificationsTab", "Specifications") 924 </a> 925 } 926 } 927 928 @if (hasHowItWorks) 929 { 930 <a class='pdp-tabnavigation__item @(activeItem == "how-it-works" ? "pdp-tabnavigation__item--active" : "")' href="javascript:void(0);" data-tab="how-it-works"> 931 @Translate("Productdetail.HowItWorksTab", "How it works") 932 </a> 933 } 934 935 @if (hasIngredients) 936 { 937 <a class='pdp-tabnavigation__item @(activeItem == "ingredients" ? "pdp-tabnavigation__item--active" : "")' href="javascript:void(0);" data-tab="ingredients"> 938 @Translate("Productdetail.IngredientsTab", "Ingredients") 939 </a> 940 } 941 942 @if(hasReviews) 943 { 944 <a class='pdp-tabnavigation__item @(activeItem == "reviews" ? "pdp-tabnavigation__item--active" : "")' href="javascript:void(0);" data-tab="reviews"> 945 @Translate("Productdetail.ReviewsTab", "Reviews") 946 </a> 947 } 948 @if(hasDownloads) 949 { 950 <a class="pdp-tabnavigation__item" href="javascript:void(0);" data-tab="downloads"> 951 @Translate("Productdetail.DownloadsTab", "Downloads") 952 </a> 953 } 954 @if(hasProductSheet) 955 { 956 <a class="pdp-tabnavigation__productsheetdownload btn btn__secondary" href="@downloadProductsheet" target="_blank"> 957 <span class="btn__text">@Translate("Productdetail.DownloadProductsheetLabel", "Download productsheet")</span> 958 <i class="btn__icon fal fa-file-pdf"></i> 959 </a> 960 } 961 </div> 962 963 <div class="pdp-tabs__content"> 964 @if(hasDescription) 965 { 966 <a class='pdp-tabnavigation__item pdp-tabnavigation__item--mobile @(activeItem == "description" ? "pdp-tabnavigation__item--active" : "")' href="javascript:void(0);" data-tab="description"> 967 @Translate("Productdetail.DescriptionTab", "Description") 968 </a> 969 <section class='pdp-tabcontent__item @(activeItem == "description" ? "pdp-tabcontent__item--active" : "")' data-tab-content="description"> 970 @RenderLongDescription() 971 </section> 972 } 973 @if(hasSpecifications && !enableSpecificationsSidebar) 974 { 975 string[] hiddenFields = {}; 976 Dictionary<string, string> specificationFields = new Dictionary<string, string>(); 977 978 <a class='pdp-tabnavigation__item pdp-tabnavigation__item--mobile @(activeItem == "specifications" ? "pdp-tabnavigation__item--active" : "")' href="javascript:void(0);" data-tab="specifications"> 979 @Translate("Productdetail.SpecificationsTab", "Specifications") 980 </a> 981 <section class='pdp-tabcontent__item @(activeItem == "specifications" ? "pdp-tabcontent__item--active" : "")' data-tab-content="specifications"> 982 <div class="pdp-specifications-info__body" style="flex-direction: column;"> 983 @foreach(LoopItem categorie in GetLoop("ProductCategories")) 984 { 985 Regex valueRegex = new Regex(@"\[(.+?)\]", RegexOptions.IgnoreCase); 986 987 if(categorie.GetLoop("ProductCategoryFields").Any(d => !string.IsNullOrWhiteSpace(d.GetString("Ecom:Product.CategoryField.Value")))) 988 { 989 foreach (var categoryField in categorie.GetLoop("ProductCategoryFields")) 990 { 991 if (!hiddenFields.Contains(categoryField.GetString("Ecom:Product.CategoryField.TemplateTag")) && !string.IsNullOrWhiteSpace(categoryField.GetString("Ecom:Product.CategoryField.Value")) && categoryField.GetString("Ecom:Product.CategoryField.Value") != "False" && categoryField.GetString("Ecom:Product.CategoryField.Value") != "0") 992 { 993 specificationFields[categoryField.GetString("Ecom:Product.CategoryField.Label")] = categoryField.GetString("Ecom:Product.CategoryField.Value"); 994 } 995 } 996 997 <section class="product-specifications__category"> 998 <ul class="product-specifications__list"> 999 @foreach (KeyValuePair<string, string> specificationField in specificationFields.OrderBy(s => s.Key)) 1000 { 1001 Match unitMatch = valueRegex.Match(specificationField.Key); 1002 string unit = unitMatch.Groups[1].Value; 1003 string value = specificationField.Value; 1004 1005 <li class="product-specifications__list-item"> 1006 <span class="product-specifications__label">@valueRegex.Replace(specificationField.Key, "")</span> 1007 <span class="product-specifications__value"> 1008 @if(value == "True") 1009 { 1010 <i class="fas fa-check" title="@Translate(value, value)"></i> 1011 } else { 1012 @value @unit 1013 } 1014 </span> 1015 </li> 1016 } 1017 </ul> 1018 </section> 1019 } 1020 } 1021 </div> 1022 </section> 1023 } 1024 1025 @if (hasHowItWorks) 1026 { 1027 <a class='pdp-tabnavigation__item pdp-tabnavigation__item--mobile @(activeItem == "how-it-works" ? "pdp-tabnavigation__item--active" : "")' href="javascript:void(0);" data-tab="how-it-works"> 1028 @Translate("Productdetail.HowItWorksTab", "How it works") 1029 </a> 1030 <section class='pdp-tabcontent__item @(activeItem == "how-it-works" ? "pdp-tabcontent__item--active" : "")' data-tab-content="how-it-works"> 1031 <div class="pdp-specifications-info__body"> 1032 <div class="pdp-specifications-info__content"> 1033 <h4>@Translate("Productdetail.HowTowUseTab", "How To Use")</h4> 1034 @GetString("Ecom:Product:Field.HowToUse.Value.Clean") 1035 </div> 1036 </div> 1037 </section> 1038 } 1039 1040 @if (hasIngredients) 1041 { 1042 <a class='pdp-tabnavigation__item pdp-tabnavigation__item--mobile @(activeItem == "ingredients" ? "pdp-tabnavigation__item--active" : "")' href="javascript:void(0);" data-tab="ingredients"> 1043 @Translate("Productdetail.IngredientsTab", "Ingredients") 1044 </a> 1045 <section class='pdp-tabcontent__item @(activeItem == "ingredients" ? "pdp-tabcontent__item--active" : "")' data-tab-content="ingredients"> 1046 <div class="pdp-specifications-info__body"> 1047 <div class="pdp-specifications-info__content"> 1048 <h4>@Translate("Productdetail.IngredientsTab", "Ingredients")</h4> 1049 @GetString("Ecom:Product:Field.Ingredients.Value.Clean") 1050 </div> 1051 </div> 1052 </section> 1053 } 1054 @if(hasReviews) 1055 { 1056 <a class='pdp-tabnavigation__item pdp-tabnavigation__item--mobile @(activeItem == "reviews" ? "pdp-tabnavigation__item--active" : "")' href="javascript:void(0);" data-tab="reviews"> 1057 @Translate("Productdetail.ReviewsTab", "Reviews") 1058 </a> 1059 <section class='pdp-tabcontent__item @(activeItem == "reviews" ? "pdp-tabcontent__item--active" : "")' data-tab-content="reviews"> 1060 <div class="pdp-specifications-info__body"> 1061 @RenderSnippet("PdpReviews") 1062 </div> 1063 </section> 1064 } 1065 @if(hasDownloads) 1066 { 1067 <a class="pdp-tabnavigation__item pdp-tabnavigation__item--mobile" href="javascript:void(0);" data-tab="downloads"> 1068 @Translate("Productdetail.DownloadsTab", "Downloads") 1069 </a> 1070 <section class="pdp-tabcontent__item" data-tab-content="downloads"> 1071 <div class="pdp-specifications-info__body"> 1072 <h3>Downloads</h3> 1073 </div> 1074 </section> 1075 } 1076 </div> 1077 1078 </div> 1079 </section> 1080 } 1081 } 1082 1083 @inherits Dynamicweb.Rendering.RazorTemplateBase<Dynamicweb.Rendering.RazorTemplateModel<Dynamicweb.Rendering.Template>> 1084 @using Dynamicweb; 1085 @using Bluedesk.DynamicWeb.ItemTypes.Pages; 1086 @using Bluedesk.DynamicWeb.ItemTypes.BaseSolution; 1087 @using Bluedesk.Tools.DynamicWeb.ExtensionMethods; 1088 @using System.Linq; 1089 @using Dynamicweb.Content; 1090 @using Dynamicweb.Ecommerce.Prices; 1091 @using System.Text.RegularExpressions; 1092 1093 @SnippetStart("PdpHeader") 1094 <header class="pdp-header container"> 1095 @if(!showTitleAboveInfoBlock) 1096 { 1097 <h1 class="pdp-header__title"> 1098 @if(!string.IsNullOrWhiteSpace(Manufacturer)) 1099 { 1100 <span class="pdp-header__manufacturer">@Manufacturer </span> 1101 } 1102 <span class="pdp-header__productname">@productName</span> 1103 </h1> 1104 } 1105 1106 @* Snippet PdpReviewIndicator *@ 1107 @if(GetInteger("Comments.TotalCount") > 0) { 1108 <div class="pdp-review-indicator"> 1109 @renderReviewIndicator("#86C440", GetInteger("Comments.Rating"), "row", true, GetInteger("Comments.Count")) 1110 </div> 1111 } 1112 </header> 1113 @SnippetEnd("PdpHeader") 1114 1115 @SnippetStart("PdpInfo") 1116 <section class="pdp-info"> 1117 @* Snippet PdpActionButtons *@ 1118 @if (enableProductCompare || enableProductFavorites) 1119 { 1120 <div class="pdp-actions po-block__actions"> 1121 @if (enableProductCompare) 1122 { 1123 @renderProductCompareButton(productid, productName, defaultImage) 1124 } 1125 @if (enableProductFavorites) 1126 { 1127 @renderProductFavoriteButton(productid, productVariantId, productName, productNumber, gtmDiscount, gtmPrice, gtmValue, productCategoryName, Manufacturer, listId, listName) 1128 } 1129 </div> 1130 } 1131 1132 @if(showTitleAboveInfoBlock) 1133 { 1134 <h1 class="pdp-header__title"> 1135 @if(!string.IsNullOrWhiteSpace(Manufacturer)) 1136 { 1137 <span class="pdp-header__manufacturer">@Manufacturer </span> 1138 } 1139 <span class="pdp-header__productname">@productName</span> 1140 </h1> 1141 } 1142 1143 1144 @* Snippet PdpProductNumber *@ 1145 @if (!string.IsNullOrWhiteSpace(productNumber)) 1146 { 1147 <p class="pdp-articlenumber"> 1148 @Translate("Productdetail.ArticleNumber.Prefix", "Article number:") @productNumber 1149 </p> 1150 } 1151 1152 @if(showVariantInfo && hasVariants) 1153 { 1154 <p class="pdp-variant-info"> 1155 <span class="pdp-variant-info__label"><strong>@Translate("ProductDetailVariantInfo.Variants", "Variants:")</strong></span> 1156 <span class="pdp-variant-info__amount"><strong>@Translate("ProductDetailVariantInfo.Amount", "Amount")</strong> @GetLoop("VariantCombinations").Count</span> 1157 <span class="pdp-variant-info__amountinstock"><strong>@Translate("ProductDetailVariantInfo.InStock", "In stock")</strong> @GetLoop("VariantCombinations").Where(p => p.GetDouble("Ecom:Product.Stock") != null && p.GetDouble("Ecom:Product.Stock") > 0).ToList().Count</span> 1158 </p> 1159 } 1160 1161 @* Snippet PdpPriceBlock *@ 1162 @if(displayPrice && displayProductPrice) 1163 { 1164 <div class="pdp-price__container"> 1165 @if(hasVariants) { 1166 <section class="pdp-price__subcontainer"> 1167 @if(minPrice != maxPrice) 1168 { 1169 <span class="pdp-price">@minPriceFormatted - @maxPriceFormatted</span> 1170 } 1171 else 1172 { 1173 <span class="pdp-price">@minPriceFormatted</span> 1174 } 1175 <span class="pdp-price--suffix">@ProductdetailPriceSuffix</span> 1176 </section> 1177 } 1178 else 1179 { 1180 1181 if (hasDiscount) 1182 { 1183 <p class="pdp-price--original">@originalProductPrice</p> 1184 <section class="pdp-price__subcontainer"> 1185 <span class="pdp-price">@discountProductPrice</span> 1186 <span class="pdp-price--suffix">@ProductdetailPriceSuffix</span> 1187 <span class="pdp-price__percentage">@discountPercentage%</span> 1188 </section> 1189 } 1190 else 1191 { 1192 <section class="pdp-price__subcontainer"> 1193 <span class="pdp-price">@originalProductPrice</span> 1194 <span class="pdp-price--suffix">@ProductdetailPriceSuffix</span> 1195 </section> 1196 } 1197 if (!string.IsNullOrWhiteSpace(retailPrice)) 1198 { 1199 <p class="pdp-price__retail-price"> 1200 @String.Format(Translate("ProductBlockTitle.RetailPrice", "Retail price: {0}"), retailPrice) 1201 </p> 1202 } 1203 if (yourProfitValue > 0) 1204 { 1205 <p class="pdp-price__yourprofit"> 1206 @Translate("Productdetail.YourProfitLabel", "Your profit:") @yourProfitValueFormatted 1207 </p> 1208 } 1209 } 1210 </div> 1211 } 1212 1213 @* Snippet PdpStockState *@ 1214 @if(enableProductStock && !string.IsNullOrWhiteSpace(stockFormat)) 1215 { 1216 <div class="pdp-stockstate__wrapper"> 1217 @if(stockFormat == "text") { 1218 if (!string.IsNullOrWhiteSpace(stockText)) 1219 { 1220 <p class="pdp-stockstate @stockStateClass">@string.Format(stockText, stock)</p> 1221 } 1222 } 1223 else 1224 { 1225 string translationTag = ""; 1226 if(neverOutOfStock) 1227 { 1228 translationTag = Translate("ProductBlockStockInfo.AmountInStock", "In stock"); 1229 } 1230 else if(stockSize == 1) 1231 { 1232 translationTag = Translate("ProductBlockStockInfo.AmountInStockSingle", "{0} product in stock"); 1233 } 1234 else if(stockSize > 1) 1235 { 1236 translationTag = Translate("ProductBlockStockInfo.AmountInStockMultiple", "{0} products in stock"); 1237 } 1238 else if(!inStock) 1239 { 1240 translationTag = Translate("ProductBlockStockInfo.AmountOutOfStock", "Out of stock"); 1241 } 1242 1243 <p class="pdp-stockstate @stockStateClass">@string.Format(translationTag, stock)</p> 1244 } 1245 </div> 1246 } 1247 1248 @* Snippet BackInStockNotification *@ 1249 @if(!inStock && displayBackInStockNotifications) 1250 { 1251 bool notificationRegistered = GetBoolean("Ecom:Product.NotificationRegistered"); 1252 1253 <div class="pdp-back-in-stock-notification"> 1254 @if(!notificationRegistered && Pageview.User != null) 1255 { 1256 <a class="btn default-btn" href="/Default.aspx?ID=@GetString("Ecom:Product:Page.ID")&ProductID=@GetString("Ecom:Product.ID")&VariantID=@GetString("Ecom:Product.VariantID")&LanguageID=@GetString("Ecom:Product.LanguageID")&CartCmd=createnotificationforthisproduct"> 1257 <span class="btn__text">@Translate("BackInStock.EmailMe", "Email me when back in stock")</span> 1258 <i class="btn__icon far fa-bell"></i> 1259 </a> 1260 } else if (!notificationRegistered && (Pageview.User == null && allowBackInStockNotificationsForGuests)) { 1261 <form name='@GetString("Ecom:Product.ID")' id='NotificationForm_@GetString("Ecom:Product.ID")' method='post' class="no-validate default-contact-form" action='/Default.aspx?ID=@GetString("Ecom:Product:Page.ID")'> 1262 <h3 class="pdp-back-in-stock-notification__header">@Translate("BackInStock.FormTitle", "Email me when back in stock")</h3> 1263 <input type="hidden" name="ProductID" id="ProductID" value='@GetString("Ecom:Product.ID")' /> 1264 <input type="hidden" name="VariantID" id="VariantID" value='@GetString("Ecom:Product.VariantID")' /> 1265 <input type="hidden" name="LanguageID" id="LanguageID" value='@GetString("Ecom:Product.LanguageID")' /> 1266 <input type="hidden" name="CartCmd" id="CartCmd" value="createnotificationforthisproduct" /> 1267 1268 <section class="form__item"> 1269 <div class="input__group form-item__input form-group"> 1270 <label class="form__input-label input__label" for="NotificationEmail">@Translate("BackInStock.Email", "Email *")</label> 1271 <input name="NotificationEmail" 1272 type="email" 1273 class="form__input input input--text" 1274 id="NotificationEmail" 1275 required /> 1276 </div> 1277 </section> 1278 1279 <button type="submit" class="btn default-btn"> 1280 <span class="btn__text">@Translate("BackInStock.CreateNotification", "Create notification")</span> 1281 <i class="btn__icon far fa-bell"></i> 1282 </button> 1283 </form> 1284 } else if (notificationRegistered && Pageview.User != null) { 1285 <p class="pdp-back-in-stock-notification__success"> 1286 <i class="far fa-bell-on"></i> 1287 @Translate("BackInStock.NotificationSuccess", "You will be notified when this product is back in stock.") 1288 </p> 1289 } else if (notificationRegistered && (Pageview.User == null && allowBackInStockNotificationsForGuests)) { 1290 <p class="pdp-back-in-stock-notification__success"> 1291 <i class="far fa-bell-on"></i> 1292 @Translate("BackInStock.NotificationSuccess", "You will be notified when this product is back in stock.") 1293 </p> 1294 } 1295 </div> 1296 } 1297 1298 @* Snippet PdpTaglines *@ 1299 @if (!string.IsNullOrWhiteSpace(productTagline) || !string.IsNullOrWhiteSpace(productDetailPageTagline)) 1300 { 1301 <div class="pdp-tagline__container"> 1302 @if (!string.IsNullOrWhiteSpace(productTagline)) 1303 { 1304 <p class="pdp-tagline"> 1305 @productTagline 1306 @if (!string.IsNullOrWhiteSpace(productTaglineInfo)) 1307 { 1308 <span class="pdp-tagline__infoicon" data-tippy-content="@productTaglineInfo"> 1309 <i class="fal fa-info-circle"></i> 1310 </span> 1311 } 1312 </p> 1313 } 1314 1315 @if (!string.IsNullOrWhiteSpace(productDetailPageTagline)) 1316 { 1317 <p class="pdp-tagline"> 1318 @productDetailPageTagline 1319 @if (!string.IsNullOrWhiteSpace(productDetailPageTaglineInfo)) 1320 { 1321 <span class="pdp-tagline__infoicon" data-tippy-content="@productDetailPageTaglineInfo"> 1322 <i class="fal fa-info-circle"></i> 1323 </span> 1324 } 1325 </p> 1326 } 1327 </div> 1328 } 1329 1330 @* Snippet PdpVariantSelector *@ 1331 @if (GetLoop("VariantGroups").Count > 0){ 1332 string pageId = GetGlobalValue("Global:Page.ID").ToString(); 1333 string variantSelection = productVariantId.Replace(".", ","); 1334 1335 var variantCombinationsObject = new List<Array>(); 1336 foreach (LoopItem variantcomb in GetLoop("VariantStockCombinations")) 1337 { 1338 string[] combinations = variantcomb.GetString("Ecom:VariantStockCombination.VariantID").Split('.'); 1339 variantCombinationsObject.Add(combinations); 1340 } 1341 string combinationsJson = Newtonsoft.Json.JsonConvert.SerializeObject(variantCombinationsObject).Replace("\"", "\'"); 1342 1343 var variantGroupsObject = new List<List<String>>(); 1344 foreach (LoopItem variantGroup in GetLoop("VariantGroups")) 1345 { 1346 var variantsObject = new List<String>(); 1347 foreach (LoopItem variantOption in variantGroup.GetLoop("VariantAvailableOptions")) 1348 { 1349 variantsObject.Add(variantOption.GetString("Ecom:VariantOption.ID")); 1350 } 1351 variantGroupsObject.Add(variantsObject); 1352 } 1353 string variantsJson = Newtonsoft.Json.JsonConvert.SerializeObject(variantGroupsObject).Replace("\"", "\'"); 1354 1355 <div class="pdp-variants"> 1356 <div class="product-variants__wrapper"> 1357 <div class="js-variants" data-total-variant-groups="@GetLoop("VariantGroups").Count" data-combinations="@combinationsJson" data-variants="@variantsJson" data-current-page-variant="@variantSelection" data-variant-selections="@variantSelection" data-page-id="@pageId" data-product-id="@productid" data-group-id="@groupid"> 1358 @foreach (LoopItem variantGroup in GetLoop("VariantGroups")) 1359 { 1360 bool containsImage = variantGroup.GetLoop("VariantAvailableOptions").Any(v => !string.IsNullOrEmpty(v.GetString("Ecom:VariantOption.ImgSmall.Clean"))); 1361 string groupId = variantGroup.GetString("Ecom:VariantGroup.ID"); 1362 1363 <div class="product-variants__block product-variants__block--@groupId"> 1364 @if (containsImage) 1365 { 1366 if(!string.IsNullOrWhiteSpace(variantGroup.GetString("Ecom:VariantGroup.Label"))) 1367 { 1368 <p class="product-variants__title">@variantGroup.GetString("Ecom:VariantGroup.Label")</p> 1369 } 1370 1371 <div class="product-variants__options-wrapper"> 1372 @foreach (LoopItem variantOption in variantGroup.GetLoop("VariantAvailableOptions")) 1373 { 1374 string selected = variantOption.GetBoolean("Ecom:VariantOption.Selected") ? "product-variants__btn--checked" : ""; 1375 1376 if (!string.IsNullOrEmpty(variantOption.GetString("Ecom:VariantOption.ImgSmall.Clean"))) 1377 { 1378 string variantImage = "/Files/" + variantOption.GetString("Ecom:VariantOption.ImgSmall.Clean"); 1379 1380 <div data-variant-id="@variantOption.GetString("Ecom:VariantOption.ID")" data-variant-group="@groupId" class="js-variant-option product-variants__btn product-variants__btn--image @selected"> 1381 <img src="@variantImage" alt="@variantOption.GetString("Ecom:VariantOption.Name")" title="@variantOption.GetString("Ecom:VariantOption.Name")" /> 1382 </div> 1383 } 1384 else 1385 { 1386 <button type="button" data-variant-id="@variantOption.GetString("Ecom:VariantOption.ID")" data-variant-group="@groupId" class="js-variant-option product-variants__btn @selected">@variantOption.GetString("Ecom:VariantOption.Name")</button> 1387 } 1388 } 1389 </div> 1390 } 1391 else 1392 { 1393 if(!string.IsNullOrWhiteSpace(variantGroup.GetString("Ecom:VariantGroup.Name"))) 1394 { 1395 <p class="product-variants__title">@variantGroup.GetString("Ecom:VariantGroup.Name")</p> 1396 } 1397 1398 <div class="product-variants__dropdown"> 1399 <div class="product-variants__dropdown-wrapper"> 1400 <button class="product-variants__toggle"> 1401 <span data-original="@Translate(string.Format("VariantDropdown.Placeholder.{0}", variantGroup.GetString("Ecom:VariantGroup.Name")), "Select your option")">@Translate(string.Format("VariantDropdown.Placeholder.{0}", variantGroup.GetString("Ecom:VariantGroup.Name")), "Select your option")</span> 1402 <i class="fal fa-chevron-down"></i> 1403 </button> 1404 <div class="product-variants__options-wrapper product-variants__options-wrapper--dropdown product-variants__dropdown-options-wrapper"> 1405 @foreach (LoopItem variantOption in variantGroup.GetLoop("VariantAvailableOptions")) 1406 { 1407 string selected = variantOption.GetBoolean("Ecom:VariantOption.Selected") ? "product-variants__btn--checked" : ""; 1408 1409 <button type="button" data-variant-id="@variantOption.GetString("Ecom:VariantOption.ID")" data-variant-group="@groupId" class="js-variant-option product-variants__btn--dropdown @selected"> 1410 @variantOption.GetString("Ecom:VariantOption.Name") 1411 </button> 1412 } 1413 </div> 1414 </div> 1415 </div> 1416 } 1417 </div> 1418 } 1419 </div> 1420 </div> 1421 </div> 1422 } 1423 1424 @* Snippet PdpVolumePrices *@ 1425 @if(displayPrice && displayProductPrice && GetLoop("Product.Prices").Any()) { 1426 var priceList = new List<object>(); 1427 1428 foreach (LoopItem volumePrice in GetLoop("Product.Prices")) 1429 { 1430 int volumePriceQuantity = volumePrice.GetInteger("Ecom:Product.Prices.Quantity"); 1431 1432 if (volumePriceQuantity != 0) 1433 { 1434 double newVolumePrice = Math.Round(volumePrice.GetDouble("Ecom:Product.Prices.Price" + WithVATSuffix) * volumePrice.GetDouble("Ecom:Product.Prices.Quantity"), 2); 1435 string newVolumePriceFormatted = WithVATBool ? new PriceInfo { PriceWithVAT = newVolumePrice }.PriceWithVATFormatted : new PriceInfo { PriceWithoutVAT = newVolumePrice }.PriceWithoutVATFormatted; 1436 1437 double diffVolumePrice = Math.Round((discountProductPriceDouble - volumePrice.GetDouble("Ecom:Product.Prices.Price")) * volumePrice.GetDouble("Ecom:Product.Prices.Quantity"), 2); 1438 string diffVolumePriceFormatted = WithVATBool ? new PriceInfo { PriceWithVAT = diffVolumePrice }.PriceWithVATFormatted : new PriceInfo { PriceWithoutVAT = diffVolumePrice }.PriceWithoutVATFormatted; 1439 1440 if(!FormattedBool) 1441 { 1442 newVolumePriceFormatted = newVolumePriceFormatted.Replace(volumePrice.GetString("Ecom:Product.Prices.Currency.Symbol"), ""); 1443 diffVolumePriceFormatted = diffVolumePriceFormatted.Replace(volumePrice.GetString("Ecom:Product.Prices.Currency.Symbol"), ""); 1444 } 1445 1446 var priceObj = new 1447 { 1448 discountId = volumePrice.GetValue("Product.Prices.LoopCounter"), 1449 newVolumePriceFormatted = newVolumePriceFormatted, 1450 diffVolumePriceFormatted = diffVolumePriceFormatted, 1451 quantity = volumePriceQuantity 1452 }; 1453 priceList.Add(priceObj); 1454 } 1455 } 1456 1457 string pricesJson = Newtonsoft.Json.JsonConvert.SerializeObject(priceList); 1458 1459 <div class="app-volumeprices" data-price-list='@pricesJson'></div> 1460 } 1461 1462 @* Snippet PdpAddToCartBox *@ 1463 @if (!hasVariants && enableShoppingCart && enableProductShoppingCart) { 1464 <div class="pdp-add-to-cart pdp-add-to-cart__container"> 1465 <add-to-cart class="app-addtocart" 1466 data-prodid="@productid" 1467 data-variantid="@productVariantId" 1468 data-min-quantity="@minimumQuantity" 1469 data-step="@quantityStep" 1470 data-list-id="product_detail" 1471 data-list-name="Product detail"> 1472 <a class="pdp__info-btn--add-to-shoppingcart">@inShoppingCartLabel<i class="btn__icon fal fa-shopping-cart"></i></a> 1473 </add-to-cart> 1474 </div> 1475 } 1476 else 1477 { 1478 if(!enableShoppingCart) 1479 { 1480 <div class="productdetails__logincontainer"> 1481 <a href="javascript:;" class="btn productdetails__loginbtn topmenu__link--login"> 1482 <span class="btn__text">@Translate("ProductDetail.LoginButton.Text", "Log in to order")</span> 1483 <i class="fal fa-arrow-right btn__icon"></i> 1484 </a> 1485 </div> 1486 } 1487 1488 if (QuotePageID > 0) 1489 { 1490 <div class="pdp-request-quote__container"> 1491 <a href="/Default.aspx?ID=@QuotePageID&ProdID=@productid&VarID=@productVariantId" class="btn product-detailpage__info-btn--request-quote"> 1492 <span class="btn__text">@Translate("ProductDetail.QuoteButton.Text", "Vraag een offerte aan")</span> 1493 <i class="btn__icon @buttonIconClass"></i> 1494 </a> 1495 </div> 1496 } 1497 1498 if(!string.IsNullOrWhiteSpace(GetString("Ecom:Product.ReplacementProductId"))) 1499 { 1500 var repProd = Dynamicweb.Ecommerce.Services.Products.GetProductById(GetString("Ecom:Product.ReplacementProductId"), GetString("Ecom:Product.ReplacementVariantId"), Pageview.Area.EcomLanguageId); 1501 1502 if(repProd != null) 1503 { 1504 int productDetailPageId = GetPageIdByNavigationTag("ProductOverview"); 1505 string productUrl = $"Default.aspx?ID={productDetailPageId}&GroupID={repProd.DefaultGroup.Id}&ProductID={repProd.Id}"; 1506 if(!string.IsNullOrWhiteSpace(repProd.VariantId)) 1507 { 1508 productUrl = $"{productUrl}?VariantID={repProd.VariantId}"; 1509 } 1510 1511 <div class="pdp-replacement__container"> 1512 @if(!string.IsNullOrWhiteSpace(Translate("ProductDetail.ReplacementProd.Intro"))) 1513 { 1514 <p class="pdp-replacement__intro">@string.Format(Translate("ProductDetail.ReplacementProd.Intro", "This product is not available anymore. Please take a look at our selected replacement product."), repProd.Name)</p> 1515 } 1516 <a href="@productUrl" class="btn default-btn"> 1517 <span class="btn__text">@string.Format(Translate("ProductDetail.ReplacementProd.Btn", "View replacement product"), repProd.Name)</span> 1518 <i class="btn__icon @buttonIconClass"></i> 1519 </a> 1520 </div> 1521 } 1522 } 1523 } 1524 1525 @* Snippet PdpPaymentLogos *@ 1526 @if(selectedPaymentLogos != null) 1527 { 1528 <section class="pdp-paymentlogos paymentlogos--small"> 1529 <div class="footer-paymentoptions" data-paymentmethods="@selectedPaymentLogos"></div> 1530 </section> 1531 } 1532 1533 @* Snippet PdpPageUspList *@ 1534 @if (productDetailUSPList.Any()) 1535 { 1536 <ul class="pdp-usplist"> 1537 @foreach (ProductDetailUSP item in productDetailUSPList) 1538 { 1539 @RenderProductDetailUSP(item.USP, item.Tooltip) 1540 } 1541 </ul> 1542 } 1543 1544 @* Snippet PdpProductShortDescription *@ 1545 @if (hasShortDescription) 1546 { 1547 <div class="pdp-short-description shortDescriptionPadding-Top-Bottom"> 1548 @productShortDescription 1549 </div> 1550 } 1551 1552 @* Snippet PdpProductUspList *@ 1553 @if (!string.IsNullOrWhiteSpace(GetString("Ecom:Product:Field.ProductUSP1.Value")) || !string.IsNullOrWhiteSpace(GetString("Ecom:Product:Field.ProductUSP2.Value")) || !string.IsNullOrWhiteSpace(GetString("Ecom:Product:Field.ProductUSP3.Value")) || !string.IsNullOrWhiteSpace(GetString("Ecom:Product:Field.ProductUSP4.Value")) || !string.IsNullOrWhiteSpace(GetString("Ecom:Product:Field.ProductUSP5.Value"))) 1554 { 1555 <ul class="pdp-usplist"> 1556 @RenderProductUSP(GetString("Ecom:Product:Field.ProductUSP1.Value"), GetString("Ecom:Product:Field.ProductUSP1Info.Value")) 1557 @RenderProductUSP(GetString("Ecom:Product:Field.ProductUSP2.Value"), GetString("Ecom:Product:Field.ProductUSP2Info.Value")) 1558 @RenderProductUSP(GetString("Ecom:Product:Field.ProductUSP3.Value"), GetString("Ecom:Product:Field.ProductUSP3Info.Value")) 1559 @RenderProductUSP(GetString("Ecom:Product:Field.ProductUSP4.Value"), GetString("Ecom:Product:Field.ProductUSP4Info.Value")) 1560 @RenderProductUSP(GetString("Ecom:Product:Field.ProductUSP5.Value"), GetString("Ecom:Product:Field.ProductUSP5Info.Value")) 1561 </ul> 1562 } 1563 1564 1565 1566 @* 1567 @if((hasShortDescription && hasLongDescription) || hasSpecifications) 1568 { 1569 <div class="pdp-action-buttons"> 1570 @if (hasShortDescription && hasLongDescription) 1571 { 1572 <p class="pdp-short-description__readmore"> 1573 <a href="#pdp-tabs">@Translate("Productdetail.Readmore", "Read more")</a> 1574 </p> 1575 } 1576 @if (hasSpecifications && enableSpecificationsSidebar) 1577 { 1578 <a href="javascript:void(0);" class="toggle-of-canvas-menu" data-offcanvas-target="specifications"> 1579 @Translate("Productdetail.ViewSpecifications", "View product specifications") 1580 </a> 1581 } 1582 </div> 1583 } 1584 *@ 1585 1586 </section> 1587 @SnippetEnd("PdpInfo") 1588 1589 @SnippetStart("PdpImage") 1590 <section class="product-detailpage__images pdp__images-container"> 1591 1592 <section class="productimages pdp__images"> 1593 1594 @{ 1595 int videoThumbPosition = 1; 1596 var SetViaThumbInt = 0; 1597 var SetViaImageInt = 0; 1598 } 1599 1600 @if (productImages.Count > 1 || hasYoutubeVideo) 1601 { 1602 1603 if (videoThumbPosition > productImages.Count) 1604 { 1605 videoThumbPosition = productImages.Count; 1606 } 1607 1608 <section class="productimages__wrapper"> 1609 1610 @if (!string.IsNullOrWhiteSpace(productRibbon)) 1611 { 1612 <p class="product-detailpage__ribbon product-detailpage__ribbon--big product-detailpage__ribbon--@productRibbonStyle"><span>@productRibbon</span></p> 1613 } 1614 1615 @if (!string.IsNullOrWhiteSpace(manufacturerLogo)) 1616 { 1617 <img src="/Admin/Public/GetImage.ashx?Image=/Files/@manufacturerLogo&Crop=7&Format=webp&Quality=90&Compression=80&width=150" class="manufacturer__logo" alt="Manufacturer" width="150" height="50" /> 1618 } 1619 1620 <div class="productimages__carousel-big hidden"> 1621 1622 @foreach (var image in productImages) 1623 { 1624 1625 SetViaImageInt++; 1626 1627 <div class="productimages__item"> 1628 <img class="w-auto" src="/Admin/Public/GetImage.ashx?Image=@image&Format=webp&Quality=-1&width=800&height=800" alt="@productName" /> 1629 </div> 1630 1631 if (SetViaImageInt == videoThumbPosition && hasYoutubeVideo) 1632 { 1633 <div class="productimages__item"> 1634 <div class="productimages__carousel__video-container"> 1635 <lite-youtube videoid="@YoutubeProductVideo" params="controls=1&loop=0&playlist=@YoutubeProductVideo&playsinline=1&modestbranding=1&mute=0&rel=0&enablejsapi=1& origin=@Dynamicweb.Environment.Helpers.LinkHelper.GetHttpDomain()&disablekb=0"></lite-youtube> 1636 </div> 1637 </div> 1638 } 1639 } 1640 1641 </div> 1642 1643 </section> 1644 1645 <div class="productimages__carousel-thumbnails hidden"> 1646 @foreach (var image in productImages) 1647 { 1648 SetViaThumbInt++; 1649 1650 <div class="productimages__thumbnail"> 1651 <img class="product-image" src="/Admin/Public/GetImage.ashx?Image=@image&Crop=5&Format=webp&Quality=90&Compression=80&width=200&height=200" alt="Thumbnail @productName" width="100" height="100" /> 1652 </div> 1653 1654 if (SetViaThumbInt == videoThumbPosition && hasYoutubeVideo) 1655 { 1656 <div class="productimages__thumbnail productimages__thumbnail--video"> 1657 <img src="https://i.ytimg.com/vi_webp/@YoutubeProductVideo/hqdefault.webp" alt="Video preview @productName" width="100" height="100" /> 1658 <span class="video-icon"> 1659 <i class="fas fa-play"></i> 1660 </span> 1661 </div> 1662 } 1663 } 1664 </div> 1665 } 1666 else if (productImages.Count == 1) 1667 { 1668 var img = productImages.First(); 1669 img = !string.IsNullOrWhiteSpace(img) ? $"/Admin/Public/GetImage.ashx?Image={img}&Format=webp&Quality=-1&width=800&height=800" : "https://via.placeholder.com/800x400/?text=No%20Image%20Found"; 1670 1671 <div id="productimages__big" class="productimages__big"> 1672 @if (!string.IsNullOrWhiteSpace(productRibbon)) 1673 { 1674 <p class="product-detailpage__ribbon product-detailpage__ribbon--big product-detailpage__ribbon--@productRibbonStyle"><span>@productRibbon</span></p> 1675 } 1676 @if (!string.IsNullOrWhiteSpace(manufacturerLogo)) 1677 { 1678 <img src="/Admin/Public/GetImage.ashx?Image=/Files/@manufacturerLogo&Crop=7&Format=webp&Quality=90&Compression=80&width=150" class="manufacturer__logo" alt="Manufacturer" width="150" height="50" /> 1679 } 1680 <img id="product-image" class="product-image" src="@img" alt="@productName" /> 1681 </div> 1682 } 1683 </section> 1684 </section> 1685 @SnippetEnd("PdpImage") 1686 1687 @SnippetStart("PdpHelp") 1688 @if (!string.IsNullOrWhiteSpace(webshopPage.HelpBannerHeader) || !string.IsNullOrWhiteSpace(webshopPage.HelpBannerBody) || !string.IsNullOrWhiteSpace(webshopPage.HelpBannerBody)) 1689 { 1690 <section class="pdp-paragraph pdp-paragraph__container"> 1691 <div class="container pdp-paragraph__innerwrapper"> 1692 @if(!string.IsNullOrWhiteSpace(webshopPage.HelpBannerImage)) 1693 { 1694 <figure class="pdp-paragraph__image"> 1695 <img src="/Admin/Public/GetImage.ashx?Image=@webshopPage.HelpBannerImage&Crop=7&Format=webp&Quality=90&Compression=80&Width=600" alt="" loading="lazy" width="600" height="300" /> 1696 </figure> 1697 } 1698 <div class="pdp-paragraph__body"> 1699 @if(!string.IsNullOrWhiteSpace(webshopPage.HelpBannerHeader)) 1700 { 1701 <h2 class="pdp-paragraph__header">@webshopPage.HelpBannerHeader</h2> 1702 } 1703 <div class="pdp-paragraph__content"> 1704 @webshopPage.HelpBannerBody 1705 </div> 1706 @if(!string.IsNullOrWhiteSpace(webshopPage.HelpBannerCTAButtonLink)) 1707 { 1708 <a href="@webshopPage.HelpBannerCTAButtonLink" class="btn default-btn"> 1709 <span class="btn__text">@webshopPage.HelpBannerCTAButtonLabel</span> 1710 <i class="btn__icon fa-chevron-right"></i> 1711 </a> 1712 } 1713 </div> 1714 </div> 1715 </section> 1716 } 1717 @SnippetEnd("PdpHelp") 1718 1719 @SnippetStart("PdpBanner") 1720 @if (!string.IsNullOrWhiteSpace(webshopPage.BannerHeader) || !string.IsNullOrWhiteSpace(webshopPage.BannerBody) || !string.IsNullOrWhiteSpace(webshopPage.BannerCTAButtonLabel)) 1721 { 1722 <section class="pdp-paragraph pdp-paragraph--image-right pdp-paragraph__container"> 1723 <div class="container pdp-paragraph__innerwrapper"> 1724 @if(!string.IsNullOrWhiteSpace(webshopPage.BannerImage)) 1725 { 1726 <figure class="pdp-paragraph__image"> 1727 <img src="/Admin/Public/GetImage.ashx?Image=@webshopPage.BannerImage&Crop=7&Format=webp&Quality=90&Compression=80&Width=600" alt="" loading="lazy" width="600" height="300" /> 1728 </figure> 1729 } 1730 <div class="pdp-paragraph__body"> 1731 @if(!string.IsNullOrWhiteSpace(webshopPage.HelpBannerImage)) 1732 { 1733 <h2 class="pdp-paragraph__header">@webshopPage.BannerHeader</h2> 1734 } 1735 <div class="pdp-paragraph__content"> 1736 @if(!string.IsNullOrWhiteSpace(webshopPage.BannerBody)) { 1737 @webshopPage.BannerBody 1738 } 1739 </div> 1740 @if(!string.IsNullOrWhiteSpace(webshopPage.BannerCTAButtonLink)) { 1741 <a href="@webshopPage.BannerCTAButtonLink" class="btn default-btn"> 1742 <span class="btn__text">@webshopPage.BannerCTAButtonLabel</span> 1743 <i class="btn__icon fa-chevron-right"></i> 1744 </a> 1745 } 1746 </div> 1747 </div> 1748 </section> 1749 } 1750 @SnippetEnd("PdpBanner") 1751 1752 @* @SnippetStart("PdpSpecification") 1753 <section class="pdp-specifications" id="specs"> 1754 <div class="container pdp-specifications__container"> 1755 1756 @RenderPDPTabs() 1757 1758 <div class="pdp-specifications__innerwrapper"> 1759 <div class="pdp-specifications__long-description" id="information"> 1760 @RenderInfoContentElement("Ecom:Product.LongDescription", "Productinformatie", "productinformation") 1761 @RenderInfoContentElement("Ecom:Product:Field.ProductDetailSpecs", "Specificaties", "specifications") 1762 @RenderInfoContentElement("Ecom:Product:Field.ProductDetailDownloads", "Downloads", "downloads") 1763 </div> 1764 @if (hasYoutubeVideo) 1765 { 1766 <div class="pdp-specifications__video-wrapper"> 1767 <iframe class="pdp-specifications__video" width="100%" height="315" src="https://www.youtube.com/embed/@YoutubeProductVideo" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe> 1768 </div> 1769 } 1770 </div> 1771 </div> 1772 </section> 1773 @SnippetEnd("PdpSpecification") *@ 1774 1775 @SnippetStart("PdpReviews") 1776 <div id="reviews" class="review-paragraph"> 1777 <div class="container review-paragraph__container"> 1778 <div class="review-paragraph__summary"> 1779 <div class="review-summary__column"> 1780 <h2 class="review-summary__title">@Translate("Reviews.Title", "Customer reviews")</h2> 1781 <div class="review-summary__container"> 1782 <div class="review-summary__indicator"> 1783 @renderReviewIndicator("#86C440", GetInteger("Comments.Rating"), "row", true, GetInteger("Comments.TotalCount")) 1784 </div> 1785 </div> 1786 </div> 1787 1788 <div class="review-summary__column"> 1789 @renderReviewDistrubition(GetLoop("Comments")) 1790 </div> 1791 1792 <div class="review-summary__column"> 1793 @if(Dynamicweb.Context.Current.Request["reviewcmd"] != null && Dynamicweb.Context.Current.Request["reviewcmd"] == "created") { 1794 <h2 class="review-summary__title">@Translate("Reviews.Submitted.Title", "Thanks for your review")</h2> 1795 <p>@Translate("Reviews.Submitted.Text", "Thank you for submitting your review.")</p> 1796 } else { 1797 <h2 class="review-summary__title">@Translate("Reviews.Create.Text", "Write a review")</h2> 1798 <p>@Translate("Reviews.Create.Text", "Share your thoughts about this product with other customers.")</p> 1799 <div class="review-summary__btn-wrapper"> 1800 <a href="#" class="btn btn__primary toggle-of-canvas-menu" data-offcanvas-target="reviewcreate"> 1801 <span class="btn__text">@Translate("Reviews.Create.Button", "Write a review")</span> 1802 <i class="btn__icon fal fa-plus"></i> 1803 </a> 1804 </div> 1805 } 1806 </div> 1807 </div> 1808 <div class="review-paragraph__comments"> 1809 @foreach (LoopItem Comment in GetLoop("Comments.Newfirst").Take(3)) 1810 { 1811 <section class="review-comment__list-item"> 1812 <div class="review-comment__header"> 1813 <div class="review-comment__rating-wrapper"> 1814 @renderReviewIndicator("#86C440", Comment.GetInteger("Rating"), "row", false, 0) 1815 </div> 1816 </div> 1817 <div class="review-comment__message"> 1818 <p>@Comment.GetString("Text")</p> 1819 </div> 1820 <p class="review-comment__meta">@Comment.GetString("Name") | @Comment.GetDate("CreatedDate").ToString("d MMMM yyyy")</p> 1821 </section> 1822 } 1823 @if(GetLoop("Comments.Newfirst").Count > 3) { 1824 <a href="#" class="toggle-of-canvas-menu" data-offcanvas-target="reviews">@string.Format(Translate("Reviews.ReadMore", "Read all {0} reviews"), GetLoop("Comments.Newfirst").Count)</a> 1825 } 1826 </div> 1827 </div> 1828 </div> 1829 @SnippetEnd("PdpReviews") 1830 1831 @SnippetStart("PdpRelatedGroups") 1832 @foreach (var relatedGroup in GetLoop("ProductRelatedGroups")) 1833 { 1834 string relatedGroupId = relatedGroup.GetString("Ecom:Product:RelatedGroup.GroupID"); 1835 string relatedGroupName = relatedGroup.GetString("Ecom:Product:RelatedGroup.Name"); 1836 int RelatedGroupsInt = relatedGroup.GetLoop("Products").Count; 1837 string ClassIgniteCarousel = RelatedGroupsInt > 4 ? "products-module__container--carousel" : ""; 1838 1839 if (relatedGroupId != "RELGRP6") 1840 { 1841 <section class="products-module__container @ClassIgniteCarousel" id="@relatedGroupId"> 1842 <div class="container"> 1843 1844 <header class="products-module__header"> 1845 <h2 class="products-module__title">@Translate("ProductRelatedGroup." + relatedGroupName.Replace(" ", ""), relatedGroupName)</h2> 1846 </header> 1847 1848 <ul class="products-module__slider"> 1849 @foreach (var Product in relatedGroup.GetLoop("Products")) 1850 { 1851 @renderProduct(Product, ProductdetailPriceSuffix, WithVATSuffix, FormattedSuffix, enableShoppingCart, displayPrice, enableProductStock, relatedGroupId, relatedGroupName, EcommerceConfiguration); 1852 } 1853 </ul> 1854 1855 </div> 1856 </section> 1857 1858 @renderProductListEnhancedEcom(relatedGroupId, relatedGroupName, relatedGroup.GetLoop("Products"), WithVATSuffix); 1859 } 1860 } 1861 @SnippetEnd("PdpRelatedGroups") 1862 1863 @SnippetStart("PdpSecondaryInfo") 1864 <section class="pdp-secondary-info"> 1865 <div class="container pdp-secondary-info__container"> 1866 <div class="pdp-secondary-info__column"> 1867 @if(!string.IsNullOrWhiteSpace(defaultImage)) 1868 { 1869 <figure class="pdp-secondary-info__image-container"> 1870 <img class="pdp-secondary-info__image" src="/Admin/Public/GetImage.ashx?Image=@defaultImage&Crop=7&Format=webp&Quality=90&Compression=80&Height=400" alt="@productName" loading="lazy" height="400" width="600" /> 1871 </figure> 1872 } 1873 </div> 1874 <div class="pdp-secondary-info__column"> 1875 @if (!string.IsNullOrWhiteSpace(productSubTitle)) 1876 { 1877 <h3 class="pdp-header__subtitle">@productSubTitle</h3> 1878 } 1879 1880 <h2 class="pdp-header__title"> 1881 @if (!string.IsNullOrWhiteSpace(Manufacturer)) 1882 { 1883 <span class="pdp-header__manufacturer">@Manufacturer</span> 1884 } 1885 <span class="pdp-header__productname"> 1886 @productName 1887 </span> 1888 </h2> 1889 1890 @if (GetInteger("Comments.TotalCount") > 0) 1891 { 1892 <div class="pdp-review-indicator"> 1893 @renderReviewIndicator("#86C440", GetInteger("Comments.Rating"), "row", true, GetInteger("Comments.Count")) 1894 </div> 1895 } 1896 1897 @if (!string.IsNullOrWhiteSpace(productNumber)) 1898 { 1899 <p class="pdp-articlenumber"> 1900 @Translate("Productdetail.ArticleNumber.Prefix", "Article number:") @productNumber 1901 </p> 1902 } 1903 1904 @if(enableProductStock && !string.IsNullOrWhiteSpace(stockFormat)) 1905 { 1906 <div class="pdp-stockstate__wrapper"> 1907 @if(stockFormat == "text") { 1908 if (!string.IsNullOrWhiteSpace(stockText)) 1909 { 1910 <p class="pdp-stockstate @stockStateClass">@string.Format(stockText, stock)</p> 1911 } 1912 } 1913 else 1914 { 1915 string translationTag = ""; 1916 if(neverOutOfStock) 1917 { 1918 translationTag = Translate("ProductBlockStockInfo.AmountInStock", "In stock"); 1919 } 1920 else if(stockSize == 1) 1921 { 1922 translationTag = Translate("ProductBlockStockInfo.AmountInStockSingle", "{0} product in stock"); 1923 } 1924 else if(stockSize > 1) 1925 { 1926 translationTag = Translate("ProductBlockStockInfo.AmountInStockMultiple", "{0} products in stock"); 1927 } 1928 else if(!inStock) 1929 { 1930 translationTag = Translate("ProductBlockStockInfo.AmountOutOfStock", "Out of stock"); 1931 } 1932 1933 <p class="pdp-stockstate @stockStateClass">@string.Format(translationTag, stock)</p> 1934 } 1935 </div> 1936 } 1937 1938 @if (!string.IsNullOrWhiteSpace(productTagline) || !string.IsNullOrWhiteSpace(productDetailPageTagline)) 1939 { 1940 <div class="pdp-tagline__container"> 1941 @if (!string.IsNullOrWhiteSpace(productTagline)) 1942 { 1943 <p class="pdp-tagline"> 1944 @productTagline 1945 @if (!string.IsNullOrWhiteSpace(productTaglineInfo)) 1946 { 1947 <span class="pdp-tagline__infoicon" data-tippy-content="@productTaglineInfo"> 1948 <i class="fal fa-info-circle"></i> 1949 </span> 1950 } 1951 </p> 1952 } 1953 1954 @if (!string.IsNullOrWhiteSpace(productDetailPageTagline)) 1955 { 1956 <p class="pdp-tagline"> 1957 @productDetailPageTagline 1958 @if (!string.IsNullOrWhiteSpace(productDetailPageTaglineInfo)) 1959 { 1960 <span class="pdp-tagline__infoicon" data-tippy-content="@productDetailPageTaglineInfo"> 1961 <i class="fal fa-info-circle"></i> 1962 </span> 1963 } 1964 </p> 1965 } 1966 </div> 1967 } 1968 </div> 1969 <div class="pdp-secondary-info__column"> 1970 1971 @if (displayPrice && displayProductPrice) 1972 { 1973 <div class="pdp-price__container"> 1974 @if(hasVariants) { 1975 <section class="pdp-price__subcontainer"> 1976 @if(minPrice != maxPrice) 1977 { 1978 <span class="pdp-price">@minPriceFormatted - @maxPriceFormatted</span> 1979 } 1980 else 1981 { 1982 <span class="pdp-price">@minPriceFormatted</span> 1983 } 1984 <span class="pdp-price--suffix">@ProductdetailPriceSuffix</span> 1985 <span class="pdp-price__percentage">@discountPercentage%</span> 1986 </section> 1987 } else { 1988 if (hasDiscount) 1989 { 1990 <p class="pdp-price--original">@originalProductPrice</p> 1991 <section class="pdp-price__subcontainer"> 1992 <span class="pdp-price">@discountProductPrice</span> 1993 <span class="pdp-price--suffix">@ProductdetailPriceSuffix</span> 1994 <span class="pdp-price__percentage">@discountPercentage%</span> 1995 </section> 1996 } 1997 else 1998 { 1999 <section class="pdp-price__subcontainer"> 2000 <span class="pdp-price">@originalProductPrice</span> 2001 <span class="pdp-price--suffix">@ProductdetailPriceSuffix</span> 2002 </section> 2003 } 2004 if (!string.IsNullOrWhiteSpace(retailPrice)) 2005 { 2006 <p class="pdp-price__retail-price"> 2007 @String.Format(Translate("ProductBlockTitle.RetailPrice", "Retail price: {0}"), retailPrice) 2008 </p> 2009 } 2010 if (yourProfitValue > 0) 2011 { 2012 <p class="pdp-price__yourprofit"> 2013 @Translate("Productdetail.YourProfitLabel", "Your profit:") @yourProfitValueFormatted 2014 </p> 2015 } 2016 } 2017 </div> 2018 } 2019 2020 @if (GetLoop("VariantGroups").Count > 0){ 2021 string pageId = GetGlobalValue("Global:Page.ID").ToString(); 2022 string variantSelection = productVariantId.Replace(".", ","); 2023 2024 var variantCombinationsObject = new List<Array>(); 2025 foreach (LoopItem variantcomb in GetLoop("VariantStockCombinations")) 2026 { 2027 string[] combinations = variantcomb.GetString("Ecom:VariantStockCombination.VariantID").Split('.'); 2028 variantCombinationsObject.Add(combinations); 2029 } 2030 string combinationsJson = Newtonsoft.Json.JsonConvert.SerializeObject(variantCombinationsObject).Replace("\"", "\'"); 2031 2032 var variantGroupsObject = new List<List<String>>(); 2033 foreach (LoopItem variantGroup in GetLoop("VariantGroups")) 2034 { 2035 var variantsObject = new List<String>(); 2036 foreach (LoopItem variantOption in variantGroup.GetLoop("VariantAvailableOptions")) 2037 { 2038 variantsObject.Add(variantOption.GetString("Ecom:VariantOption.ID")); 2039 } 2040 variantGroupsObject.Add(variantsObject); 2041 } 2042 string variantsJson = Newtonsoft.Json.JsonConvert.SerializeObject(variantGroupsObject).Replace("\"", "\'"); 2043 2044 <div class="pdp-variants"> 2045 <div class="product-variants__wrapper"> 2046 <div class="js-variants" data-total-variant-groups="@GetLoop("VariantGroups").Count" data-combinations="@combinationsJson" data-variants="@variantsJson" data-current-page-variant="@variantSelection" data-variant-selections="@variantSelection" data-page-id="@pageId" data-product-id="@productid" data-group-id="@groupid"> 2047 @foreach (LoopItem variantGroup in GetLoop("VariantGroups")) 2048 { 2049 bool containsImage = variantGroup.GetLoop("VariantAvailableOptions").Any(v => !string.IsNullOrEmpty(v.GetString("Ecom:VariantOption.ImgSmall.Clean"))); 2050 string groupId = variantGroup.GetString("Ecom:VariantGroup.ID"); 2051 2052 <div class="product-variants__block product-variants__block--@groupId"> 2053 @if (containsImage) 2054 { 2055 if(!string.IsNullOrWhiteSpace(variantGroup.GetString("Ecom:VariantGroup.Label"))) 2056 { 2057 <p class="product-variants__title">@variantGroup.GetString("Ecom:VariantGroup.Label")</p> 2058 } 2059 2060 <div class="product-variants__options-wrapper"> 2061 @foreach (LoopItem variantOption in variantGroup.GetLoop("VariantAvailableOptions")) 2062 { 2063 string selected = variantOption.GetBoolean("Ecom:VariantOption.Selected") ? "product-variants__btn--checked" : ""; 2064 2065 if (!string.IsNullOrEmpty(variantOption.GetString("Ecom:VariantOption.ImgSmall.Clean"))) 2066 { 2067 string variantImage = "/Files/" + variantOption.GetString("Ecom:VariantOption.ImgSmall.Clean"); 2068 <div data-variant-id="@variantOption.GetString("Ecom:VariantOption.ID")" data-variant-group="@groupId" class="js-variant-option product-variants__btn product-variants__btn--image @selected"> 2069 <img src="@variantImage" alt="@variantOption.GetString("Ecom:VariantOption.Name")" title="@variantOption.GetString("Ecom:VariantOption.Name")" /> 2070 </div> 2071 } 2072 else 2073 { 2074 <button type="button" data-variant-id="@variantOption.GetString("Ecom:VariantOption.ID")" data-variant-group="@groupId" class="js-variant-option product-variants__btn @selected">@variantOption.GetString("Ecom:VariantOption.Name")</button> 2075 } 2076 } 2077 </div> 2078 } 2079 else 2080 { 2081 if(!string.IsNullOrWhiteSpace(variantGroup.GetString("Ecom:VariantGroup.Name"))) 2082 { 2083 <p class="product-variants__title">@variantGroup.GetString("Ecom:VariantGroup.Name")</p> 2084 } 2085 2086 <div class="product-variants__dropdown"> 2087 <div class="product-variants__dropdown-wrapper"> 2088 2089 <button class="product-variants__toggle"> 2090 <span data-original="@Translate(string.Format("VariantDropdown.Placeholder.{0}", variantGroup.GetString("Ecom:VariantGroup.Name")), "Select your option")">@Translate(string.Format("VariantDropdown.Placeholder.{0}", variantGroup.GetString("Ecom:VariantGroup.Name")), "Select your option")</span> 2091 <i class="fal fa-chevron-down"></i> 2092 </button> 2093 <div class="product-variants__options-wrapper product-variants__options-wrapper--dropdown product-variants__dropdown-options-wrapper"> 2094 @foreach (LoopItem variantOption in variantGroup.GetLoop("VariantAvailableOptions")) { 2095 string selected = variantOption.GetBoolean("Ecom:VariantOption.Selected") ? "product-variants__btn--checked" : ""; 2096 2097 <button type="button" data-variant-id="@variantOption.GetString("Ecom:VariantOption.ID")" data-variant-group="@groupId" class="js-variant-option product-variants__btn--dropdown @selected"> 2098 @variantOption.GetString("Ecom:VariantOption.Name") 2099 </button> 2100 } 2101 </div> 2102 </div> 2103 </div> 2104 } 2105 </div> 2106 } 2107 </div> 2108 </div> 2109 </div> 2110 } 2111 2112 @if (!hasVariants && enableShoppingCart && enableProductShoppingCart) 2113 { 2114 <div class="pdp-add-to-cart pdp-add-to-cart__container"> 2115 <add-to-cart class="app-addtocart" 2116 data-prodid="@productid" 2117 data-variantid="@productVariantId" 2118 data-min-quantity="@minimumQuantity" 2119 data-step="@quantityStep" 2120 data-list-id="product_detail" 2121 data-list-name="Product detail"> 2122 <a class="pdp__info-btn--add-to-shoppingcart">@inShoppingCartLabel<i class="btn__icon fal fa-shopping-cart"></i></a> 2123 </add-to-cart> 2124 </div> 2125 } 2126 else 2127 { 2128 if (QuotePageID > 0) 2129 { 2130 <div class="pdp-request-quote__container"> 2131 <a href="/Default.aspx?ID=@QuotePageID&ProdID=@productid&VarID=@productVariantId" class="btn product-detailpage__info-btn--request-quote"> 2132 <span class="btn__text">@Translate("ProductDetail.QuoteButton.Text", "Vraag een offerte aan")</span> 2133 <i class="btn__icon @buttonIconClass"></i> 2134 </a> 2135 </div> 2136 } 2137 2138 if(!string.IsNullOrWhiteSpace(GetString("Ecom:Product.ReplacementProductId"))) 2139 { 2140 var repProd = Dynamicweb.Ecommerce.Services.Products.GetProductById(GetString("Ecom:Product.ReplacementProductId"), GetString("Ecom:Product.ReplacementVariantId"), Pageview.Area.EcomLanguageId); 2141 2142 if(repProd != null) 2143 { 2144 int productDetailPageId = GetPageIdByNavigationTag("ProductOverview"); 2145 string productUrl = $"Default.aspx?ID={productDetailPageId}&GroupID={repProd.DefaultGroup.Id}&ProductID={repProd.Id}"; 2146 if(!string.IsNullOrWhiteSpace(repProd.VariantId)) 2147 { 2148 productUrl = $"{productUrl}?VariantID={repProd.VariantId}"; 2149 } 2150 2151 <div class="pdp-replacement__container"> 2152 @if(!string.IsNullOrWhiteSpace(Translate("ProductDetail.ReplacementProd.Intro"))) 2153 { 2154 <p class="pdp-replacement__intro">@string.Format(Translate("ProductDetail.ReplacementProd.Intro", "This product is not available anymore. Please take a look at our selected replacement product."), repProd.Name)</p> 2155 } 2156 <a href="@productUrl" class="btn default-btn"> 2157 <span class="btn__text">@string.Format(Translate("ProductDetail.ReplacementProd.Btn", "View replacement product"), repProd.Name)</span> 2158 <i class="btn__icon @buttonIconClass"></i> 2159 </a> 2160 </div> 2161 } 2162 } 2163 } 2164 2165 @if(selectedPaymentLogos != null) 2166 { 2167 <section class="pdp-paymentlogos paymentlogos--small"> 2168 <div class="footer-paymentoptions" data-paymentmethods="@selectedPaymentLogos"></div> 2169 </section> 2170 } 2171 </div> 2172 </div> 2173 </section> 2174 @SnippetEnd("PdpSecondaryInfo") 2175 2176 @SnippetStart("PdpRecentViewedProducts") 2177 @if (GetInteger("eCom:Related.YouHaveSeenTheseProducts.Count") > 0) 2178 { 2179 var groupTitle = Translate("Productdetail.RecentlyViewedArticles", "Recently viewed articles"); 2180 var recentlyViewedProducts = GetLoop("eCom:Related.YouHaveSeenTheseProducts"); 2181 recentlyViewedProducts.Reverse(); 2182 recentlyViewedProducts = recentlyViewedProducts.Where(p => !string.IsNullOrWhiteSpace(p.GetString("Ecom:Product.ID")) && p.GetString("Ecom:Product.ID") != productid).Take(4).ToList(); 2183 2184 if(recentlyViewedProducts.Count > 0) 2185 { 2186 <section class="products-module__container"> 2187 <div class="container"> 2188 2189 <header class="products-module__header"> 2190 <h2 class="products-module__title">@groupTitle</h2> 2191 </header> 2192 2193 <ul class="products-module__slider"> 2194 @foreach (var Product in recentlyViewedProducts) 2195 { 2196 @renderProduct(Product, ProductdetailPriceSuffix, WithVATSuffix, FormattedSuffix, enableShoppingCart, displayPrice, enableProductStock, "recently_viewed_articles", groupTitle, EcommerceConfiguration); 2197 } 2198 </ul> 2199 2200 </div> 2201 </section> 2202 2203 @renderProductListEnhancedEcom("recently_viewed_articles", groupTitle, recentlyViewedProducts, WithVATSuffix); 2204 } 2205 } 2206 @SnippetEnd("PdpRecentViewedProducts") 2207 2208 <!-- ***** END PDP ***** --> 2209 2210 @SnippetStart("ProductDetailHeaderDesktop") 2211 <!-- BEGIN Stickymenu --> 2212 <section class="stickymenu__replaceable"> 2213 2214 <div class="stickymenu_product-info-wrapper"> 2215 @if (!string.IsNullOrWhiteSpace(defaultImage)) 2216 { 2217 <figure class="stickymenu__product-image"> 2218 <img src="/Admin/Public/GetImage.ashx?Image=@defaultImage&Crop=5&Format=webp&Quality=90&Compression=80&Height=60&Width=60" alt="@productName" width="60" height="60"> 2219 </figure> 2220 } 2221 <div class="stickymenu_product-info"> 2222 <p class="stickymenu__product-name"> 2223 @if(!string.IsNullOrWhiteSpace(Manufacturer)) 2224 { 2225 <span class="stickymenu__product-name--manufacturer">@Manufacturer </span> 2226 } 2227 <span class="stickymenu__product-name--product">@productName</span> 2228 </p> 2229 2230 @if (displayPrice && displayProductPrice) 2231 { 2232 if (hasDiscount) 2233 { 2234 <div class="stickymenu_product__price__wrapper"> 2235 <span class="stickymenu_product__price--old">@originalProductPrice</span> 2236 <span class="stickymenu_product__price--current">@discountProductPrice</span> 2237 <span class="stickymenu_product__price--suffix">@ProductdetailPriceSuffix</span> 2238 <span class="stickymenu_product__price--profit">@Translate("Productdetail.YourProfitLabel", "Your profit:") @yourProfitValueFormatted</span> 2239 </div> 2240 } 2241 else 2242 { 2243 <div class="stickymenu_product__price__wrapper"> 2244 <span class="stickymenu_product__price--current">@originalProductPrice</span> 2245 <span class="stickymenu_product__price--suffix">@ProductdetailPriceSuffix</span> 2246 </div> 2247 } 2248 } 2249 2250 </div> 2251 </div> 2252 2253 @if (enableShoppingCart && enableProductShoppingCart) 2254 { 2255 <add-to-cart class="app-addtocart" 2256 data-prodid="@productid" 2257 data-variantid="@productVariantId" 2258 data-min-quantity="@minimumQuantity" 2259 data-step="@quantityStep" 2260 data-list-id="product_detail" 2261 data-list-name="Product detail"> 2262 <!-- Fall Back button for Add to Cart--> 2263 <a class="pdp__add-to-cart-btn">@inShoppingCartLabel <i class="btn__icon fal fa-shopping-cart"></i></a> 2264 </add-to-cart> 2265 } 2266 else 2267 { 2268 if (QuotePageID > 0) 2269 { 2270 <div class="py-4"> 2271 <a href="/Default.aspx?ID=@QuotePageID&ProdID=@productid&VarID=@productVariantId" class="btn product-detailpage__info-btn--request-quote"> 2272 <span class="btn__text">@Translate("ProductDetail.QuoteButton.Text", "Vraag een offerte aan")</span> 2273 <i class="btn__icon @buttonIconClass"></i> 2274 </a> 2275 </div> 2276 } 2277 } 2278 2279 </section> 2280 <!-- END Stickymenu --> 2281 @SnippetEnd("ProductDetailHeaderDesktop") 2282 2283 @inherits Dynamicweb.Rendering.RazorTemplateBase<Dynamicweb.Rendering.RazorTemplateModel<Dynamicweb.Rendering.Template>> 2284 @using Dynamicweb; 2285 @using Bluedesk.DynamicWeb.ItemTypes.Pages; 2286 @using Bluedesk.Tools.DynamicWeb.ExtensionMethods; 2287 @using System.Linq; 2288 @using Dynamicweb.Content; 2289 2290 @{ 2291 2292 } 2293 2294 <style> 2295 .products-module__product-btn--add-to-shoppingcart { 2296 background-color: @AddToCartButtonBackgroundColor; 2297 } 2298 </style> 2299 2300 2301 @{ 2302 CheckoutConfig CheckoutConfiguration = mc.CheckoutConfiguration; 2303 bool showProductNumber = CheckoutConfiguration.CheckoutShowProductNumber; 2304 bool showShippingPaymentStep = !CheckoutConfiguration.HidePaymentStep; 2305 bool showSummaryStep = !CheckoutConfiguration.HideSummaryStep; 2306 bool showChosenShippingMethod = CheckoutConfiguration.CheckoutHideChosenShippingMethod; 2307 bool showChosenPaymentMethod = CheckoutConfiguration.CheckoutHideChosenPaymentMethod; 2308 bool displayStock = mc.EcomConfiguration.HideStockForGuests ? Pageview.User != null : true; 2309 2310 bool enableFreeShippingThreshold = mc.EcomConfiguration.EnableFreeShippingThreshold; 2311 string freeShippingBackgroundColor = enableFreeShippingThreshold ? colorService.GetHexColor(Pageview.AreaID, mc.EcomConfiguration.FreeShippingThresholdBackgroundColor.ToString()) : ""; 2312 string freeShippingBorderColor = enableFreeShippingThreshold ? colorService.GetHexColor(Pageview.AreaID, mc.EcomConfiguration.FreeShippingThresholdBorderColor.ToString()) : ""; 2313 string freeShippingTextColor = enableFreeShippingThreshold ? colorService.GetHexColor(Pageview.AreaID, mc.EcomConfiguration.FreeShippingThresholdTextColor.ToString()) : ""; 2314 string freeShippingIcon = mc.EcomConfiguration.FreeShippingThresholdIcon; 2315 2316 List<LoopItem> upsellProducts = new List<LoopItem>(); 2317 string upsellProductsListId = ""; 2318 var overviewPageId = GetPageIdByNavigationTag("ProductOverview"); 2319 2320 foreach (LoopItem order in GetLoop("OrderLines")) { 2321 foreach (LoopItem relatedProducts in order.GetLoop("ProductRelatedGroups")) { 2322 if(relatedProducts.GetString("Ecom:Product:RelatedGroup.Name") == "Upsell products") { 2323 upsellProductsListId = relatedProducts.GetString("Ecom:Product:RelatedGroup.GroupID"); 2324 foreach (LoopItem product in relatedProducts.GetLoop("Products")) { 2325 if (!upsellProducts.Any(p => p.GetString("Ecom:Product.ID") == product.GetString("Ecom:Product.ID"))) 2326 { 2327 upsellProducts.Add(product); 2328 } 2329 } 2330 } 2331 } 2332 } 2333 2334 if(!enableAddToCartForZeroPrices) { 2335 upsellProducts = upsellProducts.Where(p => !p.GetBoolean("Ecom:Product.Price.IsZero")).ToList(); 2336 } 2337 2338 if(!enableAddToCartForOutOfStock) { 2339 upsellProducts = upsellProducts.Where(p => p.GetInteger("Ecom:Product.Stock") > 0 || p.GetBoolean("Ecom:Product.NeverOutOfStock")).ToList(); 2340 } 2341 2342 int checkoutPageId = Bluedesk.Tools.DynamicWeb.Generic.PageHelper.GetPageIDByNavigationTag("Checkout", Pageview.AreaID); 2343 string checkoutPageUrl = Dynamicweb.Frontend.SearchEngineFriendlyURLs.GetFriendlyUrl($"Default.aspx?ID={checkoutPageId}"); 2344 2345 bool isImpersonating = GetLoop("DWExtranetSecondaryUsers").Count > 0 || !string.IsNullOrWhiteSpace(GetGlobalValue("Global:Extranet.SecondaryUser.UserID")); 2346 2347 bool canCompleteOrder = true; 2348 string disableNextButton = ""; 2349 2350 var paymentCountryCode = GetString("Ecom:Order.Customer.Country.Code"); 2351 var shippingCountryCode = GetString("Ecom:Order.Delivery.Country.Code"); 2352 var paymentCountryIsSupported = !string.IsNullOrEmpty(paymentCountryCode); 2353 var shippingCountryIsSupported = string.IsNullOrEmpty(shippingCountryCode) ? paymentCountryIsSupported : !string.IsNullOrEmpty(shippingCountryCode); 2354 2355 canCompleteOrder = paymentCountryIsSupported && shippingCountryIsSupported; 2356 } 2357 2358 <div class="container checkout__shoppingcart"> 2359 <h1 class="checkout__shoppingcart-title">@Translate("Checkout.CheckoutPageTitle", "Checkout")</h1> 2360 2361 <form name="ordersubmit" id="ordersubmit" method="post" class="default-contact-form no-validate" action="@checkoutPageUrl"> 2362 <div id="checkout-pages-root" data-shownumber="@showProductNumber" data-enable-step-shippingpayment="@showShippingPaymentStep" data-enable-step-summary="@showSummaryStep" data-enable-stock="@displayStock" data-stock-format="@stockFormat" data-enable-free-shipping-threshold="@enableFreeShippingThreshold" data-free-shipping-icon="@freeShippingIcon" data-show-payment="@showChosenPaymentMethod" data-show-shipping="@showChosenShippingMethod" data-pageid="@Pageview.Page.ID" data-is-impersonating="@isImpersonating"></div> 2363 @if(!canCompleteOrder && !string.IsNullOrEmpty(GetString("UserManagement:User.CountryCode"))) { 2364 <input type="hidden" name="EcomOrderCustomerCountry" id="EcomOrderCustomerCountry" value="@GetString("UserManagement:User.CountryCode")" /> 2365 } 2366 </form> 2367 2368 @if(upsellProducts.Count == 0) 2369 { 2370 var websitesettings = Dynamicweb.Content.Services.Items.GetItemById<WebsiteSettings>(Pageview.Area.Item.Id); 2371 string defaultUpsellIds = websitesettings.DefaultUpsellProducts; 2372 string[] defaultUpsellProductsIds = string.IsNullOrWhiteSpace(defaultUpsellIds) ? Array.Empty<string>() : defaultUpsellIds.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Select(id => id.Replace("p_", "").Replace(":", "")).ToArray(); 2373 2374 ProductViewModelSettings productSetting = new ProductViewModelSettings 2375 { 2376 LanguageId = Dynamicweb.Ecommerce.Common.Context.LanguageID, 2377 CurrencyCode = Dynamicweb.Ecommerce.Common.Context.Currency.Code, 2378 CountryCode = Dynamicweb.Ecommerce.Common.Context.Country.Code2, 2379 ShopId = Pageview.Area.EcomShopId, 2380 UserId = Pageview.User?.ID ?? 0, 2381 }; 2382 2383 List<ProductViewModel> defaultUpsellProducts = new List<ProductViewModel>(); 2384 2385 foreach(var productId in defaultUpsellProductsIds) { 2386 ProductViewModel prod = ViewModelFactory.CreateView(productSetting, productId, ""); 2387 if(prod != null) 2388 { 2389 defaultUpsellProducts.Add(prod); 2390 } 2391 } 2392 2393 if(defaultUpsellProducts.Count > 0) 2394 { 2395 var index = 1; 2396 string ClassIgniteCarousel = defaultUpsellProducts.Count() > 4 ? "products-module__container--carousel" : ""; 2397 var groupTitle = Translate("ShoppingCart.UpsellTitle", "Aanbevolen bij jouw product"); 2398 2399 <section class="products-module__container products-module__container--upsell @ClassIgniteCarousel"> 2400 <div class="products-module__innerwrapper"> 2401 <header class="products-module__header"> 2402 <h2 class="products-module__title">@groupTitle</h2> 2403 </header> 2404 <ul class="products-module__slider"> 2405 @foreach (ProductViewModel prod in defaultUpsellProducts) 2406 { 2407 @renderProductBlock(prod, groupTitle, "upsell", index); 2408 index++; 2409 } 2410 </ul> 2411 </div> 2412 </section> 2413 } 2414 } 2415 2416 @RenderSnippet("RenderUpsellProducts") 2417 </div> 2418 2419 <style> 2420 @if(enableFreeShippingThreshold) 2421 { 2422 <text> 2423 .shoppingcart-sidebar .shoppingcart__shippingthreshold-wrapper { 2424 --Color: @freeShippingTextColor; 2425 --BackgroundColor: @freeShippingBackgroundColor; 2426 --BorderColor: @freeShippingBorderColor; 2427 } 2428 </text> 2429 } 2430 </style> 2431 2432 @SnippetStart("DataLayer") 2433 @if(GetLoop("OrderLines").Count() > 0) { 2434 <text> 2435 <script> 2436 if(window.dataLayer) { 2437 dataLayer.push({ 2438 'event': 'view_cart', 2439 'ecommerce': { 2440 'currency': '@GetString("Ecom:Order.Price.Currency.Code")', 2441 'value': @GetString("Ecom:Order.Price.Price.Value").Replace(",", "."), 2442 'items': [ 2443 @foreach (LoopItem orderline in GetLoop("OrderLines")) 2444 { 2445 if(orderline.GetBoolean("Ecom:Order:OrderLine.IsProduct")) 2446 { 2447 var groupObject = Dynamicweb.Ecommerce.Services.ProductGroups.GetGroup(orderline.GetString("Ecom:Product.PrimaryOrFirstGroupID")); 2448 string groupObjectName = groupObject != null ? groupObject.Name.Replace("''", "\\\"").Replace("'", "") : ""; 2449 <text> 2450 { 2451 'item_id': '@orderline.GetString("Ecom:Product.ID")', 2452 'item_name': '@orderline.GetString("Ecom:Order:OrderLine.ProductName").Replace("''", "\\\"").Replace("'", "")', 2453 'item_number': '@orderline.GetString("Ecom:Product.Number")', 2454 'coupon': '@GetString("Ecom:Order.Customer.VoucherCode")', 2455 'discount': @orderline.GetString("Ecom:Product.Discount.TotalAmount.Price.Value").Replace(",", "."), 2456 'index': @orderline.GetInteger("OrderLines.LoopCounter"), 2457 'item_brand': '@orderline.GetString("Ecom:Manufacturer.Name").Replace("''", "\\\"").Replace("'", "")', 2458 'item_category': '@groupObjectName', 2459 'item_list_id': '@listId', 2460 'item_list_name': '@listName', 2461 'price': @orderline.GetString("Ecom:Order:OrderLine.UnitPrice.Price.Value").Replace(",", "."), 2462 'quantity': @orderline.GetInteger("Ecom:Order:OrderLine.Quantity"), 2463 }, 2464 </text> 2465 } 2466 } 2467 ] 2468 } 2469 }); 2470 } 2471 </script> 2472 </text> 2473 2474 } 2475 @SnippetEnd("DataLayer") 2476 2477 @SnippetStart("RenderUpsellProducts") 2478 @if(upsellProducts.Count > 0) { 2479 2480 string relCategoryTitle = Translate("ShoppingCart.UpsellTitle", "Aanbevolen bij jouw product"); 2481 string ClassIgniteCarousel = upsellProducts.Count > 3 ? "products-module__container--carousel" : ""; 2482 2483 <section class="products-module__container products-module__container--upsell @ClassIgniteCarousel"> 2484 <div class="products-module__innerwrapper"> 2485 <header class="products-module__header"> 2486 <h2 class="products-module__title">@relCategoryTitle</h2> 2487 </header> 2488 2489 <ul class="products-module__slider"> 2490 @foreach (var Product in upsellProducts) 2491 { 2492 @renderProduct(Product, ProductdetailPriceSuffix, WithVATSuffix, FormattedSuffix, enableShoppingCart, displayPrice, enableProductStock, upsellProductsListId, relCategoryTitle, EcommerceConfiguration); 2493 } 2494 </ul> 2495 </div> 2496 </section> 2497 2498 <text> 2499 <script> 2500 if(window.dataLayer) { 2501 dataLayer.push({ 2502 'event': 'view_item_list', 2503 'ecommerce': { 2504 'item_list_id': '@upsellProductsListId', 2505 'item_list_name': '@relCategoryTitle', 2506 'items': [ 2507 @foreach (var product in upsellProducts) { 2508 var relCategoryObject = Dynamicweb.Ecommerce.Services.ProductGroups.GetGroup(product.GetString("Ecom:Product.PrimaryOrFirstGroupID")); 2509 string relCategoryName = relCategoryObject != null ? relCategoryObject.Name.Replace("''", "\\\"").Replace("'", "") : ""; 2510 <text> 2511 { 2512 'item_id': '@product.GetString("Ecom:Product.ID")', 2513 'item_name': '@product.GetString("Ecom:Product.Name").Replace("''", "\\\"").Replace("'", "")', 2514 'item_number': '@product.GetString("Ecom:Product.Number")', 2515 'discount': @product.GetString("Ecom:Product.Discount.TotalAmount.Price.Value").Replace(",", "."), 2516 'index': @product.GetInteger("Products.LoopCounter"), 2517 'item_brand': '@product.GetString("Ecom:Manufacturer.Name").Replace("''", "\\\"").Replace("'", "")', 2518 'item_category': '@relCategoryName', 2519 'item_list_id': '@upsellProductsListId', 2520 'item_list_name': '@relCategoryTitle', 2521 'item_variant': '@product.GetString("Ecom:Product.VariantID")', 2522 'price': @product.GetString("Ecom:Product.Price.Price.Value").Replace(",", "."), 2523 'quantity': 1, 2524 }, 2525 </text> 2526 } 2527 ] 2528 } 2529 }); 2530 } 2531 </script> 2532 </text> 2533 } 2534 @SnippetEnd("RenderUpsellProducts") 2535 2536 @helper renderProductBlock(Dynamicweb.Ecommerce.ProductCatalog.ProductViewModel product, string listId, string listName, int index) 2537 { 2538 var master_configuration = Dynamicweb.Services.Pages.GetPageByNavigationTag(Pageview.AreaID, "MasterConfiguration"); 2539 MasterConfig mc = master_configuration.Item.ToCodeFirstItem<MasterConfig>(); 2540 EcomConfig EcommerceConfiguration = mc.EcomConfiguration; 2541 2542 bool isVariant = product.VariantId != ""; 2543 bool hasVariants = !isVariant ? product?.VariantInfo?.VariantInfo?.Count > 0 : false; 2544 2545 string productName = product.Name; 2546 string productImage = product.DefaultImage?.Value ?? ""; 2547 2548 if (string.IsNullOrWhiteSpace(productImage)) 2549 { 2550 var prodService = new Dynamicweb.Ecommerce.Products.ProductService(); 2551 var p = Dynamicweb.Ecommerce.Services.Products.GetProductById(product.Id, product.VariantId, Pageview.Area.EcomLanguageId); 2552 productImage = Dynamicweb.Ecommerce.Services.ProductImages.GetImagePath(p); 2553 2554 if (string.IsNullOrWhiteSpace(productImage)) 2555 { 2556 try 2557 { 2558 var perfionImage = prodService.GetProductFieldValue(p, "AdditionalImages"); 2559 productImage = $"Files/" + perfionImage.ToString().Split(';')[0]; 2560 } 2561 catch (Exception) { } 2562 } 2563 } 2564 2565 bool WithVATBool = Pageview.Area.EcomPricesWithVat == "True"; 2566 bool pricesWithoutVatForUsers = EcommerceConfiguration.ShowPricesWithoutVatForUsers; 2567 bool pricesWithoutVatForValidVat = EcommerceConfiguration.ShowPricesWithoutVatWhenValidVatNumber; 2568 if(pricesWithoutVatForUsers && !pricesWithoutVatForValidVat && Pageview.User != null) { 2569 WithVATBool = false; 2570 } 2571 if(pricesWithoutVatForValidVat && Pageview.User != null && !string.IsNullOrWhiteSpace(Pageview.User.VatRegNumber)) { 2572 WithVATBool = false; 2573 } 2574 2575 int productDetailPageId = GetPageIdByNavigationTag("ProductOverview"); 2576 string currentPageUrl = "/Default.aspx?ID=" + Pageview.Page.ID; 2577 2578 string productNumber = product.Number; 2579 string productId = product.Id; 2580 string productVariantId = !string.IsNullOrWhiteSpace(product.VariantId) ? product.VariantId : ""; 2581 string productURL = $"Default.aspx?ID={productDetailPageId}&GroupID={product.PrimaryOrDefaultGroup.Id}&ProductID={product.Id}"; 2582 productURL += !string.IsNullOrWhiteSpace(product.VariantId) ? "&VariantID=" + product.VariantId : ""; 2583 string productManufacturer = product.Manufacturer?.Name ?? ""; 2584 2585 bool isPriceZero = product.Price.Price <= 0; 2586 bool hasDiscount = product.Discount.Price > 0; 2587 string OriginalPrice = WithVATBool ? product.PriceBeforeDiscount.PriceWithVatFormatted : product.PriceBeforeDiscount.PriceWithoutVatFormatted; 2588 string Price = WithVATBool ? product.Price.PriceWithVatFormatted : product.Price.PriceWithoutVatFormatted; 2589 string yourDiscount = WithVATBool ? product.Discount.PriceWithVatFormatted : product.Discount.PriceWithoutVatFormatted; 2590 double gtmDiscount = WithVATBool ? product.Discount.PriceWithVat : product.Discount.PriceWithoutVat; 2591 double gtmPrice = WithVATBool ? product.PriceBeforeDiscount.PriceWithVat : product.PriceBeforeDiscount.PriceWithoutVat; 2592 double gtmValue = WithVATBool ? product.Price.PriceWithVat : product.Price.PriceWithoutVat; 2593 string ProductdetailPriceSuffixWithVAT = Translate("Productdetail.Price.Suffix.WithVAT", "Incl. VAT"); 2594 string ProductdetailPriceSuffixWithoutVAT = Translate("Productdetail.Price.Suffix.WithoutVAT", "Excl. VAT"); 2595 string ProductdetailPriceSuffix = WithVATBool ? ProductdetailPriceSuffixWithVAT : ProductdetailPriceSuffixWithoutVAT; 2596 string gtmBrand = !string.IsNullOrWhiteSpace(product.Manufacturer.Name) ? product.Manufacturer.Name.Replace("''", "\\\"").Replace("'", "") : ""; 2597 2598 bool hideStockForGuests = EcommerceConfiguration.HideStockForGuests; 2599 string stockFormat = EcommerceConfiguration.StockFormat; 2600 bool enableProductStock = hideStockForGuests ? Pageview.User != null : true; 2601 string stockText = product.StockStatus; 2602 double stockSize = (double)product.StockLevel; 2603 bool neverOutOfStock = product.NeverOutOfstock; 2604 bool isInStock = stockSize > 0 || neverOutOfStock; 2605 string stockClass = isInStock ? "products-module__product-stock-state--instock" : "products-module__product-stock-state--outofstock"; 2606 string stockSizeFormatted = stockSize.ToString(stockSize % 1 == 0 ? "N0" : "N2", System.Globalization.CultureInfo.GetCultureInfo(Pageview.Area.CultureInfo.TwoLetterISOLanguageName)); 2607 2608 bool hideZeroPrices = EcommerceConfiguration.HideZeroPrices; 2609 bool enableAddToCartForZeroPrices = EcommerceConfiguration.AddToCartAllowZeroPrices; 2610 bool enableAddToCartForOutOfStock = EcommerceConfiguration.AddToCartAllowOutOfStock; 2611 2612 bool displayPrice = true; 2613 bool enableShoppingCart = true; 2614 2615 bool enableProductShoppingCart = enableShoppingCart; 2616 if(!enableAddToCartForZeroPrices && isPriceZero) { 2617 // enableProductShoppingCart = false; 2618 } 2619 2620 if(!enableAddToCartForOutOfStock && !isInStock) { 2621 enableProductShoppingCart = false; 2622 } 2623 2624 if(product.Discontinued) { 2625 enableProductShoppingCart = false; 2626 } 2627 2628 bool displayProductPrice = displayPrice; 2629 if(hideZeroPrices && isPriceZero) { 2630 // displayProductPrice = false; 2631 } 2632 2633 string informativePrice = WithVATBool ? product.PriceInformative.PriceWithVatFormatted : product.PriceInformative.PriceWithoutVatFormatted; 2634 if (EcommerceConfiguration.UseInformativePriceAsFromPrice && !string.IsNullOrWhiteSpace(informativePrice)) 2635 { 2636 double informativePriceValue = WithVATBool ? product.PriceInformative.PriceWithVat : product.PriceInformative.PriceWithoutVat; 2637 hasDiscount = gtmValue < informativePriceValue; 2638 if (hasDiscount) 2639 { 2640 OriginalPrice = informativePrice; 2641 double yourProfitValue = informativePriceValue - gtmValue; 2642 yourDiscount = WithVATBool ? new PriceInfo { PriceWithVAT = yourProfitValue }.PriceWithVATFormatted : new PriceInfo { PriceWithoutVAT = yourProfitValue }.PriceWithoutVATFormatted; 2643 } 2644 } 2645 2646 string retailPrice = ""; 2647 bool displayRetailPrice = EcommerceConfiguration.DisplayRetailPrice; 2648 if(EcommerceConfiguration.DisplayRetailPriceForUsers && Pageview.User == null) { 2649 displayRetailPrice = false; 2650 } 2651 if(displayRetailPrice) { 2652 string priceFieldName = EcommerceConfiguration.RetailPriceField; 2653 if(EcommerceConfiguration.RetailPriceIsDbPrice) { 2654 var prodService = new Dynamicweb.Ecommerce.Products.ProductService(); 2655 var p = Dynamicweb.Ecommerce.Services.Products.GetProductById(product.Id, product.VariantId, Pageview.Area.EcomLanguageId); 2656 PriceContext customerPriceContext = new PriceContext(Dynamicweb.Ecommerce.Common.Context.Currency, Dynamicweb.Ecommerce.Common.Context.Country, null, null, Dynamicweb.Ecommerce.Common.Context.ReverseChargeForVatEnabled, DateTime.Now); 2657 var customerPrice = p?.GetPrice(customerPriceContext); 2658 if(customerPrice.Price > 0) { 2659 retailPrice = WithVATBool ? customerPrice.PriceWithVATFormatted : customerPrice.PriceWithoutVATFormatted; 2660 } 2661 } else if(!string.IsNullOrWhiteSpace(priceFieldName)) { 2662 double customerPriceValue = 0.0; 2663 if (product.ProductFields.TryGetValue(priceFieldName, out var fieldValue) && fieldValue.Value is double) { 2664 customerPriceValue = (double)fieldValue.Value; 2665 } 2666 if(customerPriceValue > 0) { 2667 retailPrice = WithVATBool ? new PriceInfo { PriceWithVAT = customerPriceValue }.PriceWithVATFormatted : new PriceInfo { PriceWithoutVAT = customerPriceValue }.PriceWithoutVATFormatted; 2668 } 2669 } 2670 } 2671 2672 string yourProfitLabel = Translate("Productdetail.YourProfitLabel", "Uw voordeel:"); 2673 2674 List<FieldOptionValueViewModel> productRibbons = null; 2675 if (product.ProductFields["ProductRibbon"] != null) 2676 { 2677 productRibbons = (List<FieldOptionValueViewModel>)product.ProductFields["ProductRibbon"].Value; 2678 } 2679 2680 <li class="products-module__slider-cell"> 2681 <div class="products-module products-module__slider-cell__innerwrapper"> 2682 2683 @foreach(var rib in productRibbons) { 2684 <p class="products-module__ribbon"> 2685 <span class="products-module__ribbon-text">@rib.Value</span> 2686 </p> 2687 } 2688 2689 <figure class="products-module__image-container"> 2690 @if(!string.IsNullOrWhiteSpace(productImage)) 2691 { 2692 <img src="/Admin/Public/GetImage.ashx?Image=@productImage&Crop=7&Format=webp&Quality=90&Compression=80&Height=200" alt="@productName" loading="lazy" height="200" width="300" /> 2693 } 2694 </figure> 2695 2696 <h3 class="products-module__product-name"> 2697 @if(!string.IsNullOrWhiteSpace(productManufacturer)) 2698 { 2699 <span class="products-module__product-name--manufacturer">@productManufacturer</span> 2700 } 2701 <span class="products-module__product-name--product">@productName</span> 2702 </h3> 2703 2704 @if(!string.IsNullOrWhiteSpace(productNumber)) 2705 { 2706 <p class="products-module__product-article-number">@productNumber</p> 2707 } 2708 2709 @if (displayPrice && displayProductPrice) 2710 { 2711 <div class="products-module__product-price-container"> 2712 @if(hasVariants) 2713 { 2714 string minPrice = WithVATBool ? product.VariantInfo.PriceMin.PriceWithVatFormatted : product.VariantInfo.PriceMin.PriceWithoutVatFormatted; 2715 string maxPrice = WithVATBool ? product.VariantInfo.PriceMax.PriceWithVatFormatted : product.VariantInfo.PriceMax.PriceWithoutVatFormatted; 2716 if(product?.VariantInfo?.PriceMax.Price != product?.VariantInfo?.PriceMin.Price) 2717 { 2718 <span class="products-module__product-price">@minPrice - @maxPrice</span> 2719 } 2720 else 2721 { 2722 <span class="products-module__product-price">@minPrice</span> 2723 } 2724 <span class="products-module__product-price-suffix">@ProductdetailPriceSuffix</span> 2725 } 2726 else 2727 { 2728 if (hasDiscount) 2729 { 2730 <span class="products-module__product-price-original">@OriginalPrice</span> 2731 <span class="products-module__product-price">@Price</span> 2732 } 2733 else 2734 { 2735 <span class="products-module__product-price">@OriginalPrice</span> 2736 } 2737 <span class="products-module__product-price-suffix">@ProductdetailPriceSuffix</span> 2738 if (!string.IsNullOrWhiteSpace(retailPrice)) 2739 { 2740 <p class="products-module__product-retail-price"> 2741 @String.Format(Translate("ProductBlockTitle.RetailPrice", "Retail price: {0}"), retailPrice) 2742 </p> 2743 } 2744 if (hasDiscount) 2745 { 2746 <p class="products-module__product-your-discount">@Translate("Productdetail.YourProfitLabel", "Uw voordeel:") @yourDiscount</p> 2747 } 2748 } 2749 </div> 2750 } 2751 2752 <section class="products-module__product-actions"> 2753 @if(hasVariants) 2754 { 2755 <div class="products-module__product-variant-info"> 2756 @if(product.VariantInfo.VariantInfo.Count == 1) 2757 { 2758 <p>@string.Format(Translate("ProductBlockVariantInfo.VariantCount.Single", "{0} variant"), product.VariantInfo.VariantInfo.Count)</p> 2759 } 2760 else 2761 { 2762 <p>@string.Format(Translate("ProductBlockVariantInfo.VariantCount.Multiple", "{0} variants"), product.VariantInfo.VariantInfo.Count)</p> 2763 } 2764 </div> 2765 } 2766 2767 @if (enableProductStock && !string.IsNullOrWhiteSpace(stockFormat)) 2768 { 2769 if(stockFormat == "text") { 2770 if (!string.IsNullOrWhiteSpace(stockText)) 2771 { 2772 <p class="products-module__product-stock-state @stockClass">@string.Format(stockText, stockSize)</p> 2773 } 2774 } 2775 else 2776 { 2777 string translationTag = ""; 2778 if(neverOutOfStock) 2779 { 2780 translationTag = Translate("ProductBlockStockInfo.AmountInStock", "In stock"); 2781 } 2782 else if(stockSize == 1) 2783 { 2784 translationTag = Translate("ProductBlockStockInfo.AmountInStockSingle", "{0} product in stock"); 2785 } 2786 else if(stockSize > 1) 2787 { 2788 translationTag = Translate("ProductBlockStockInfo.AmountInStockMultiple", "{0} products in stock"); 2789 } 2790 else if(!isInStock) 2791 { 2792 translationTag = Translate("ProductBlockStockInfo.AmountOutOfStock", "Out of stock"); 2793 } 2794 <p class="products-module__product-stock-state @stockClass">@string.Format(translationTag, stockSizeFormatted)</p> 2795 } 2796 } 2797 2798 @if(!hasVariants && enableShoppingCart && enableProductShoppingCart) 2799 { 2800 <add-to-cart 2801 class="app-addtocart" 2802 data-prodid="@productId" 2803 data-variantid="@productVariantId" 2804 data-show-text="False" 2805 data-min-quantity="@product.PurchaseMinimumQuantity" 2806 data-step="@product.PurchaseQuantityStep" 2807 data-list-id="@listId" 2808 data-list-name="@listName"> 2809 </add-to-cart> 2810 } 2811 </section> 2812 2813 <a href="@productURL" class="products-module__link" data-id="@productId" data-variantid="@productVariantId" data-number="@productNumber" data-name='@productName.Replace("''", "\\\"").Replace("'", "")' data-position="@index" data-brand='@gtmBrand' data-category='@product.PrimaryOrDefaultGroup.Name.Replace("''", "\\\"").Replace("'", "")' data-listid="@listId" data-listname="@listName" data-gtmprice="@gtmPrice" data-gtmdiscount="@gtmDiscount" data-gtmvalue="@gtmValue" data-currencycode="@Dynamicweb.Ecommerce.Common.Context.Currency.Code" aria-label="@productName"></a> 2814 </div> 2815 </li> 2816 }