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