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_daeef553ecf14af9930f27f5cbfdf9c7.<>c__DisplayClass3_0.<renderProduct>b__0(TextWriter __razor_helper_writer)
   at CompiledRazorTemplates.Dynamic.RazorEngine_daeef553ecf14af9930f27f5cbfdf9c7.Execute()
   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 if(hasDescription || hasReviews || hasDownloads) 960 { 961 <section id="pdp-tabs" class="pdp-tabs"> 962 <div class="container pdp-tabs__container"> 963 964 <div class="pdp-tabs__navigation"> 965 @if(hasSpecifications) 966 { 967 <a class='pdp-tabnavigation__item @(activeItem == "specifications" ? "pdp-tabnavigation__item--active" : "")' href="javascript:void(0);" data-tab="specifications"> 968 @Translate("Productdetail.SpecificationsTab", "Specifications") 969 </a> 970 } 971 @if(hasPrestaties) 972 { 973 <a class='pdp-tabnavigation__item @(activeItem == "prestaties" ? "pdp-tabnavigation__item--active" : "")' href="javascript:void(0);" data-tab="prestaties"> 974 @Translate("Productdetail.PrestatiesTab", "Prestaties") 975 </a> 976 } 977 @if(hasDescription) 978 { 979 <a class='pdp-tabnavigation__item @(activeItem == "description" ? "pdp-tabnavigation__item--active" : "")' href="javascript:void(0);" data-tab="description"> 980 @Translate("Productdetail.DescriptionTab", "Description") 981 </a> 982 } 983 @if(hasReviews) 984 { 985 <a class='pdp-tabnavigation__item @(activeItem == "reviews" ? "pdp-tabnavigation__item--active" : "")' href="javascript:void(0);" data-tab="reviews"> 986 @Translate("Productdetail.ReviewsTab", "Reviews") 987 </a> 988 } 989 @if(hasDownloads) 990 { 991 <a class="pdp-tabnavigation__item" href="javascript:void(0);" data-tab="downloads"> 992 @Translate("Productdetail.DownloadsTab", "Downloads") 993 </a> 994 } 995 @if(hasProductSheet) 996 { 997 <a class="pdp-tabnavigation__productsheetdownload btn btn__secondary" href="@downloadProductsheet" target="_blank"> 998 <span class="btn__text">@Translate("Productdetail.DownloadProductsheetLabel", "Download productsheet")</span> 999 <i class="btn__icon fal fa-file-pdf"></i> 1000 </a> 1001 } 1002 </div> 1003 1004 <div class="pdp-tabs__content"> 1005 @if(hasSpecifications) 1006 { 1007 Regex valueRegex = new Regex(@"\[(.+?)\]", RegexOptions.IgnoreCase); 1008 string[] hiddenFields = {"ProductPromidataMediaGallery", "ProductImagesDownload", "ProductLeaflets", "ProductManuals"}; 1009 Dictionary<string, string> customFields = new Dictionary<string, string>(); 1010 1011 if(!string.IsNullOrWhiteSpace(GetString("Ecom:Product.EAN"))) 1012 { 1013 string eanLabelTranslation = Translate("ProductDetailSpecification.EAN", "EAN artikel"); 1014 customFields[eanLabelTranslation] = GetString("Ecom:Product.EAN"); 1015 } 1016 1017 if(!string.IsNullOrWhiteSpace(GetString("Ecom:Product:Field.ProductMaterialen.Value"))) 1018 { 1019 foreach(var i in GetLoop("CustomFields")) { 1020 <!-- This loop needs to be included in order to get the labels instead of values --> 1021 var fixToGetLabels = GetLoop("ProductMaterialen.Options"); 1022 } 1023 string materiaalLabelTranslation = Translate("ProductDetailSpecification.Materiaal", "Materiaal"); 1024 customFields[materiaalLabelTranslation] = GetString("Ecom:Product:Field.ProductMaterialen.Label"); 1025 } 1026 1027 if(!string.IsNullOrWhiteSpace(GetString("Ecom:Product:Field.ProductKleurenList.Label"))) 1028 { 1029 foreach(var i in GetLoop("CustomFields")) { 1030 <!-- This loop needs to be included in order to get the labels instead of values --> 1031 var fixToGetLabels = GetLoop("ProductKleurenList.Options"); 1032 } 1033 string kleurLabelTranslation = Translate("ProductDetailSpecification.Kleur", "Kleur"); 1034 customFields[kleurLabelTranslation] = GetString("Ecom:Product:Field.ProductKleurenList.Label"); 1035 } 1036 1037 foreach(LoopItem categorie in specificationGroups) 1038 { 1039 1040 if(categorie.GetLoop("ProductCategoryFields").Any(d => !string.IsNullOrWhiteSpace(d.GetString("Ecom:Product.CategoryField.Value")))) 1041 { 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 decimal inhoudValueLiters = (decimal)inhoudValue / 1000; 1054 1055 if (inhoudValueLiters % 1 == 0) 1056 { 1057 customFields[inhoudLabel] = ((int)inhoudValueLiters).ToString(); 1058 } 1059 else 1060 { 1061 customFields[inhoudLabel] = inhoudValueLiters.ToString("0.0"); 1062 } 1063 } 1064 else 1065 { 1066 customFields[inhoudLabel] = inhoudValue.ToString(); 1067 } 1068 } 1069 else 1070 { 1071 if(!string.IsNullOrWhiteSpace(categoryField.GetString("Ecom:Product.CategoryField.OptionLabel"))) 1072 { 1073 customFields[categoryField.GetString("Ecom:Product.CategoryField.Label")] = categoryField.GetString("Ecom:Product.CategoryField.OptionLabel"); 1074 } 1075 else 1076 { 1077 customFields[categoryField.GetString("Ecom:Product.CategoryField.Label")] = categoryField.GetString("Ecom:Product.CategoryField.Value"); 1078 } 1079 } 1080 } 1081 } 1082 1083 } 1084 } 1085 1086 <a class='pdp-tabnavigation__item pdp-tabnavigation__item--mobile @(activeItem == "specifications" ? "pdp-tabnavigation__item--active" : "")' href="javascript:void(0);" data-tab="specifications"> 1087 @Translate("Productdetail.SpecificationsTab", "Specifications") 1088 </a> 1089 <section class='pdp-tabcontent__item @(activeItem == "specifications" ? "pdp-tabcontent__item--active" : "")' data-tab-content="specifications"> 1090 <div class="pdp-specifications-info__body" style="flex-direction: column;"> 1091 <section class="product-specifications__category"> 1092 <ul class="product-specifications__list"> 1093 @foreach (KeyValuePair<string, string> customField in customFields.OrderBy(s => s.Key)) 1094 { 1095 Match unitMatch = valueRegex.Match(customField.Key); 1096 string unit = unitMatch.Groups[1].Value; 1097 string value = customField.Value; 1098 1099 <li class="product-specifications__list-item"> 1100 <span class="product-specifications__label">@valueRegex.Replace(customField.Key, "")</span> 1101 <span class="product-specifications__value"> 1102 @if(value == "True") 1103 { 1104 <i class="fas fa-check" title="@Translate(value, value)"></i> 1105 } else { 1106 @value @unit 1107 } 1108 </span> 1109 </li> 1110 } 1111 </ul> 1112 </section> 1113 </div> 1114 </section> 1115 } 1116 @if(hasPrestaties) 1117 { 1118 Regex valueRegex = new Regex(@"\[(.+?)\]", RegexOptions.IgnoreCase); 1119 string[] hiddenFields = {}; 1120 Dictionary<string, string> customFields = new Dictionary<string, string>(); 1121 1122 foreach(LoopItem categorie in prestatiesGroups) 1123 { 1124 if(categorie.GetLoop("ProductCategoryFields").Any(d => !string.IsNullOrWhiteSpace(d.GetString("Ecom:Product.CategoryField.Value")))) 1125 { 1126 foreach (var categoryField in categorie.GetLoop("ProductCategoryFields")) 1127 { 1128 if (!hiddenFields.Contains(categoryField.GetString("Ecom:Product.CategoryField.Label")) && !string.IsNullOrWhiteSpace(categoryField.GetString("Ecom:Product.CategoryField.Value")) && categoryField.GetString("Ecom:Product.CategoryField.Value") != "False") 1129 { 1130 if(!string.IsNullOrWhiteSpace(categoryField.GetString("Ecom:Product.CategoryField.OptionLabel"))) 1131 { 1132 customFields[categoryField.GetString("Ecom:Product.CategoryField.Label")] = categoryField.GetString("Ecom:Product.CategoryField.OptionLabel"); 1133 } 1134 else 1135 { 1136 customFields[categoryField.GetString("Ecom:Product.CategoryField.Label")] = categoryField.GetString("Ecom:Product.CategoryField.Value"); 1137 } 1138 } 1139 } 1140 } 1141 } 1142 1143 <a class='pdp-tabnavigation__item pdp-tabnavigation__item--mobile @(activeItem == "prestaties" ? "pdp-tabnavigation__item--active" : "")' href="javascript:void(0);" data-tab="prestaties"> 1144 @Translate("Productdetail.PrestatiesTab", "Prestaties") 1145 </a> 1146 <section class='pdp-tabcontent__item @(activeItem == "prestaties" ? "pdp-tabcontent__item--active" : "")' data-tab-content="prestaties"> 1147 <div class="pdp-specifications-info__body" style="flex-direction: column;"> 1148 <section class="product-specifications__category"> 1149 <ul class="product-specifications__list"> 1150 @foreach (KeyValuePair<string, string> customField in customFields.OrderBy(s => s.Key)) 1151 { 1152 Match unitMatch = valueRegex.Match(customField.Key); 1153 string unit = unitMatch.Groups[1].Value; 1154 string value = customField.Value; 1155 1156 <li class="product-specifications__list-item"> 1157 <span class="product-specifications__label">@valueRegex.Replace(customField.Key, "")</span> 1158 <span class="product-specifications__value"> 1159 @if(value == "True") 1160 { 1161 <i class="fas fa-check" title="@Translate(value, value)"></i> 1162 } else { 1163 @value @unit 1164 } 1165 </span> 1166 </li> 1167 } 1168 </ul> 1169 </section> 1170 </div> 1171 </section> 1172 } 1173 @if(hasDescription) 1174 { 1175 <a class='pdp-tabnavigation__item pdp-tabnavigation__item--mobile @(activeItem == "description" ? "pdp-tabnavigation__item--active" : "")' href="javascript:void(0);" data-tab="description"> 1176 @Translate("Productdetail.DescriptionTab", "Description") 1177 </a> 1178 <section class='pdp-tabcontent__item @(activeItem == "description" ? "pdp-tabcontent__item--active" : "")' data-tab-content="description"> 1179 @RenderLongDescription() 1180 </section> 1181 } 1182 @if(hasReviews) 1183 { 1184 <a class='pdp-tabnavigation__item pdp-tabnavigation__item--mobile @(activeItem == "reviews" ? "pdp-tabnavigation__item--active" : "")' href="javascript:void(0);" data-tab="reviews"> 1185 @Translate("Productdetail.ReviewsTab", "Reviews") 1186 </a> 1187 <section class='pdp-tabcontent__item @(activeItem == "reviews" ? "pdp-tabcontent__item--active" : "")' data-tab-content="reviews"> 1188 <div class="pdp-specifications-info__body"> 1189 @RenderSnippet("PdpReviews") 1190 </div> 1191 </section> 1192 } 1193 @if(hasDownloads) 1194 { 1195 string imagesDownload = GetString("Ecom:Product.CategoryField.ProductProperties.ProductImagesDownload.Value.Clean"); 1196 string leafletDownload = GetString("Ecom:Product.CategoryField.ProductProperties.ProductLeaflets.Value.Clean"); 1197 string manualDownload = GetString("Ecom:Product.CategoryField.ProductProperties.ProductManuals.Value.Clean"); 1198 1199 <a class="pdp-tabnavigation__item pdp-tabnavigation__item--mobile" href="javascript:void(0);" data-tab="downloads"> 1200 @Translate("Productdetail.DownloadsTab", "Downloads") 1201 </a> 1202 <section class="pdp-tabcontent__item" data-tab-content="downloads"> 1203 <div class="pdp-specifications-info__body"> 1204 <h3>@Translate("Productdetail.DownloadsTabTitle", "Downloads")</h3> 1205 1206 <div> 1207 @if(!string.IsNullOrWhiteSpace(GetString("Ecom:Product.Number"))) 1208 { 1209 string fileName = GetString("Ecom:Product.Number") + ".zip"; 1210 var absolutePath = System.Web.HttpContext.Current.Server.MapPath("~/Files/files/Downloads/Images/" + fileName); 1211 if(System.IO.File.Exists(absolutePath)) 1212 { 1213 <p> 1214 <a href="/Files/files/Downloads/Images/@fileName" download target="_blank" class="download__item"> 1215 <i class="fas fa-download"></i> 1216 <span>@Translate("Productdetail.Downloads.Zip", "Product- en sfeerafbeeldingen")</span> 1217 </a> 1218 </p> 1219 } 1220 } 1221 @if(!string.IsNullOrWhiteSpace(leafletDownload)) 1222 { 1223 var absolutePath = System.Web.HttpContext.Current.Server.MapPath("~/Files/files/Downloads/Leaflets/" + leafletDownload); 1224 if(System.IO.File.Exists(absolutePath)) 1225 { 1226 <p> 1227 <a href="/Files/files/Downloads/Leaflets/@leafletDownload" download target="_blank" class="download__item"> 1228 <i class="fas fa-download"></i> 1229 <span>@Translate("Productdetail.Downloads.Leaflet", "Product leaflet")</span> 1230 </a> 1231 </p> 1232 } 1233 } 1234 @if(!string.IsNullOrWhiteSpace(manualDownload)) 1235 { 1236 var absolutePath = System.Web.HttpContext.Current.Server.MapPath("~/Files/files/Downloads/Handleidingen/" + manualDownload); 1237 if(System.IO.File.Exists(absolutePath)) 1238 { 1239 <p> 1240 <a href="/Files/files/Downloads/Handleidingen/@manualDownload" download target="_blank" class="download__item"> 1241 <i class="fas fa-download"></i> 1242 <span>@Translate("Productdetail.Downloads.Manual", "Handleiding")</span> 1243 </a> 1244 </p> 1245 } 1246 } 1247 </div> 1248 </div> 1249 </section> 1250 } 1251 </div> 1252 1253 </div> 1254 </section> 1255 } 1256 } 1257 1258 @inherits Dynamicweb.Rendering.RazorTemplateBase<Dynamicweb.Rendering.RazorTemplateModel<Dynamicweb.Rendering.Template>> 1259 @using Dynamicweb; 1260 @using Bluedesk.DynamicWeb.ItemTypes.Pages; 1261 @using Bluedesk.DynamicWeb.ItemTypes.BaseSolution; 1262 @using Bluedesk.Tools.DynamicWeb.ExtensionMethods; 1263 @using System.Linq; 1264 @using Dynamicweb.Content; 1265 @using Dynamicweb.Ecommerce.Prices; 1266 @using System.Text.RegularExpressions; 1267 1268 @SnippetStart("PdpHeader") 1269 <header class="pdp-header container"> 1270 @if(!showTitleAboveInfoBlock) 1271 { 1272 <h1 class="pdp-header__title"> 1273 @if(!string.IsNullOrWhiteSpace(Manufacturer)) 1274 { 1275 <span class="pdp-header__manufacturer">@Manufacturer </span> 1276 } 1277 <span class="pdp-header__productname">@productName</span> 1278 </h1> 1279 } 1280 1281 @* Snippet PdpReviewIndicator *@ 1282 @if(GetInteger("Comments.TotalCount") > 0) { 1283 <div class="pdp-review-indicator"> 1284 @renderReviewIndicator("#86C440", GetInteger("Comments.Rating"), "row", true, GetInteger("Comments.Count")) 1285 </div> 1286 } 1287 </header> 1288 @SnippetEnd("PdpHeader") 1289 1290 @SnippetStart("PdpInfo") 1291 <section class="pdp-info"> 1292 @* Snippet PdpActionButtons *@ 1293 @if (enableProductCompare || enableProductFavorites) 1294 { 1295 <div class="pdp-actions po-block__actions"> 1296 @if (enableProductCompare) 1297 { 1298 @renderProductCompareButton(productid, productName, defaultImage) 1299 } 1300 @if (enableProductFavorites) 1301 { 1302 @renderProductFavoriteButton(productid, productVariantId, productName, productNumber, gtmDiscount, gtmPrice, gtmValue, productCategoryName, Manufacturer, listId, listName) 1303 } 1304 </div> 1305 } 1306 1307 @if(showTitleAboveInfoBlock) 1308 { 1309 <h1 class="pdp-header__title"> 1310 @if(!string.IsNullOrWhiteSpace(Manufacturer)) 1311 { 1312 <span class="pdp-header__manufacturer">@Manufacturer </span> 1313 } 1314 <span class="pdp-header__productname">@productName</span> 1315 </h1> 1316 } 1317 1318 1319 @* Snippet PdpProductNumber *@ 1320 @if (!string.IsNullOrWhiteSpace(productNumber)) 1321 { 1322 <p class="pdp-articlenumber"> 1323 @Translate("Productdetail.ArticleNumber.Prefix", "Article number:") @productNumber 1324 </p> 1325 } 1326 1327 @if(showVariantInfo && hasVariants) 1328 { 1329 <p class="pdp-variant-info"> 1330 <span class="pdp-variant-info__label"><strong>@Translate("ProductDetailVariantInfo.Variants", "Variants:")</strong></span> 1331 <span class="pdp-variant-info__amount"><strong>@Translate("ProductDetailVariantInfo.Amount", "Amount")</strong> @GetLoop("VariantCombinations").Count</span> 1332 <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> 1333 </p> 1334 } 1335 1336 @* Snippet PdpPriceBlock *@ 1337 @if(displayPrice && displayProductPrice) 1338 { 1339 <div class="pdp-price__container"> 1340 @if(hasVariants) { 1341 <section class="pdp-price__subcontainer"> 1342 @if(minPrice != maxPrice) 1343 { 1344 <span class="pdp-price">@minPriceFormatted - @maxPriceFormatted</span> 1345 } 1346 else 1347 { 1348 <span class="pdp-price">@minPriceFormatted</span> 1349 } 1350 <span class="pdp-price--suffix">@ProductdetailPriceSuffix</span> 1351 </section> 1352 } 1353 else 1354 { 1355 1356 if (hasDiscount) 1357 { 1358 <p class="pdp-price--original">@originalProductPrice</p> 1359 <section class="pdp-price__subcontainer"> 1360 <span class="pdp-price">@discountProductPrice</span> 1361 <span class="pdp-price--suffix">@ProductdetailPriceSuffix</span> 1362 <span class="pdp-price__percentage">@discountPercentage%</span> 1363 </section> 1364 } 1365 else 1366 { 1367 <section class="pdp-price__subcontainer"> 1368 <span class="pdp-price">@originalProductPrice</span> 1369 <span class="pdp-price--suffix">@ProductdetailPriceSuffix</span> 1370 </section> 1371 } 1372 } 1373 <p class="pdp-price--consumer"> 1374 @if(Pageview.User != null) 1375 { 1376 if(!string.IsNullOrWhiteSpace(retailPrice)) 1377 { 1378 @String.Format(Translate("ProductBlockTitle.AVP", "AVP: {0}"), retailPrice); 1379 } 1380 } 1381 else 1382 { 1383 @Translate("ProductBlockTitle.AVP.Guests", "Adviesprijs"); 1384 } 1385 </p> 1386 @if (yourProfitValue > 0) 1387 { 1388 <p class="pdp-price__yourprofit"> 1389 @Translate("Productdetail.YourProfitLabel", "Your profit:") @yourProfitValueFormatted 1390 </p> 1391 } 1392 </div> 1393 } 1394 1395 @* Snippet PdpStockState *@ 1396 @if(enableProductStock && !string.IsNullOrWhiteSpace(stockFormat)) 1397 { 1398 <div class="pdp-stockstate__wrapper"> 1399 @if (stockFormat == "text") 1400 { 1401 if (!string.IsNullOrWhiteSpace(stockText)) 1402 { 1403 <p class="pdp-stockstate @stockStateClass">@string.Format(stockText, stock)</p> 1404 } 1405 } 1406 else 1407 { 1408 string translationTag = ""; 1409 if (neverOutOfStock) 1410 { 1411 translationTag = Translate("ProductBlockStockInfo.AmountInStock", "In stock"); 1412 } 1413 else if (stockSize == 1) 1414 { 1415 translationTag = Translate("ProductBlockStockInfo.AmountInStockSingle", "{0} product in stock"); 1416 } 1417 else if (stockSize > 1) 1418 { 1419 translationTag = Translate("ProductBlockStockInfo.AmountInStockMultiple", "{0} products in stock"); 1420 } 1421 else if (!inStock && hasExpectedStock) 1422 { 1423 translationTag = Translate("ProductBlockStockInfo.AmountOutOfStockButExpected", "Soon back in stock"); 1424 } 1425 else if (!inStock) 1426 { 1427 translationTag = Translate("ProductBlockStockInfoPDP.AmountOutOfStock", "Out of stock"); 1428 } 1429 1430 <p class="pdp-stockstate @stockStateClass">@string.Format(translationTag, stock)</p> 1431 } 1432 1433 @if (!isInWrongGroup) 1434 { 1435 if (!string.IsNullOrWhiteSpace(quantityInOptionsFormatted) && quantityInOptionsFormatted != "0") 1436 { 1437 <p class="pdp-quantityinoptions">@String.Format(Translate("PDP.QuantityInOptions", "+ {0} In optie. Mogelijk binnenkort meer beschikbaar"), quantityInOptionsFormatted)</p> 1438 } 1439 <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> 1440 } 1441 1442 @if (hasExpectedStock) 1443 { 1444 double total = stockSize; 1445 1446 <div class="pdp-expected-stock"> 1447 <h3 class="expected-stock__title">@Translate("Productdetail.ExpectedStock", "Expected stock")</h3> 1448 <div class="expected-stock__table"> 1449 <div class="expected-stock__head expected-stock__row"> 1450 <p>@Translate("Productdetail.ExpectedDate", "Expected date")</p> 1451 <p>@Translate("Productdetail.Amount", "Amount")</p> 1452 <p>@Translate("Productdetail.Total", "Total")</p> 1453 </div> 1454 <div class="expected-stock__row"> 1455 <p>@Translate("Productdetail.Today", "Today")</p> 1456 <p>@Translate("Productdetail.NA", "N/A")</p> 1457 <p>@stock</p> 1458 </div> 1459 @foreach (FutureStockModel stockItem in expectedStock) 1460 { 1461 total += stockItem.Balance; 1462 1463 <div class="expected-stock__row"> 1464 <p>@stockItem.ExpectedReceiptDate.ToString("dd-MM-yyyy")</p> 1465 <p>@stockItem.Balance.ToString("N0", CultureInfo.GetCultureInfo(Pageview.Area.CultureInfo.TwoLetterISOLanguageName))</p> 1466 <p>@total.ToString("N0", CultureInfo.GetCultureInfo(Pageview.Area.CultureInfo.TwoLetterISOLanguageName))</p> 1467 </div> 1468 } 1469 </div> 1470 1471 </div> 1472 } 1473 1474 </div> 1475 } 1476 1477 @* Snippet BackInStockNotification *@ 1478 @if(!inStock && displayBackInStockNotifications) 1479 { 1480 bool notificationRegistered = GetBoolean("Ecom:Product.NotificationRegistered"); 1481 1482 <div class="pdp-back-in-stock-notification"> 1483 @if(!notificationRegistered && Pageview.User != null) 1484 { 1485 <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"> 1486 <span class="btn__text">@Translate("BackInStock.EmailMe", "Email me when back in stock")</span> 1487 <i class="btn__icon far fa-bell"></i> 1488 </a> 1489 } else if (!notificationRegistered && (Pageview.User == null && allowBackInStockNotificationsForGuests)) { 1490 <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")'> 1491 <h3 class="pdp-back-in-stock-notification__header">@Translate("BackInStock.FormTitle", "Email me when back in stock")</h3> 1492 <input type="hidden" name="ProductID" id="ProductID" value='@GetString("Ecom:Product.ID")' /> 1493 <input type="hidden" name="VariantID" id="VariantID" value='@GetString("Ecom:Product.VariantID")' /> 1494 <input type="hidden" name="LanguageID" id="LanguageID" value='@GetString("Ecom:Product.LanguageID")' /> 1495 <input type="hidden" name="CartCmd" id="CartCmd" value="createnotificationforthisproduct" /> 1496 1497 <section class="form__item"> 1498 <div class="input__group form-item__input form-group"> 1499 <label class="form__input-label input__label" for="NotificationEmail">@Translate("BackInStock.Email", "Email *")</label> 1500 <input name="NotificationEmail" 1501 type="email" 1502 class="form__input input input--text" 1503 id="NotificationEmail" 1504 required /> 1505 </div> 1506 </section> 1507 1508 <button type="submit" class="btn default-btn"> 1509 <span class="btn__text">@Translate("BackInStock.CreateNotification", "Create notification")</span> 1510 <i class="btn__icon far fa-bell"></i> 1511 </button> 1512 </form> 1513 } else if (notificationRegistered && Pageview.User != null) { 1514 <p class="pdp-back-in-stock-notification__success"> 1515 <i class="far fa-bell-on"></i> 1516 @Translate("BackInStock.NotificationSuccess", "You will be notified when this product is back in stock.") 1517 </p> 1518 } else if (notificationRegistered && (Pageview.User == null && allowBackInStockNotificationsForGuests)) { 1519 <p class="pdp-back-in-stock-notification__success"> 1520 <i class="far fa-bell-on"></i> 1521 @Translate("BackInStock.NotificationSuccess", "You will be notified when this product is back in stock.") 1522 </p> 1523 } 1524 </div> 1525 } 1526 1527 @* Snippet PdpTaglines *@ 1528 @if (!string.IsNullOrWhiteSpace(productTagline) || !string.IsNullOrWhiteSpace(productDetailPageTagline)) 1529 { 1530 <div class="pdp-tagline__container"> 1531 @if (!string.IsNullOrWhiteSpace(productTagline)) 1532 { 1533 <p class="pdp-tagline"> 1534 @productTagline 1535 @if (!string.IsNullOrWhiteSpace(productTaglineInfo)) 1536 { 1537 <span class="pdp-tagline__infoicon" data-tippy-content="@productTaglineInfo"> 1538 <i class="fal fa-info-circle"></i> 1539 </span> 1540 } 1541 </p> 1542 } 1543 1544 @if (!string.IsNullOrWhiteSpace(productDetailPageTagline)) 1545 { 1546 <p class="pdp-tagline"> 1547 @productDetailPageTagline 1548 @if (!string.IsNullOrWhiteSpace(productDetailPageTaglineInfo)) 1549 { 1550 <span class="pdp-tagline__infoicon" data-tippy-content="@productDetailPageTaglineInfo"> 1551 <i class="fal fa-info-circle"></i> 1552 </span> 1553 } 1554 </p> 1555 } 1556 </div> 1557 } 1558 1559 @* Snippet PdpVariantSelector *@ 1560 @if (GetLoop("VariantGroups").Count > 0){ 1561 string pageId = GetGlobalValue("Global:Page.ID").ToString(); 1562 string variantSelection = productVariantId.Replace(".", ","); 1563 1564 var variantCombinationsObject = new List<Array>(); 1565 foreach (LoopItem variantcomb in GetLoop("VariantStockCombinations")) 1566 { 1567 string[] combinations = variantcomb.GetString("Ecom:VariantStockCombination.VariantID").Split('.'); 1568 variantCombinationsObject.Add(combinations); 1569 } 1570 string combinationsJson = Newtonsoft.Json.JsonConvert.SerializeObject(variantCombinationsObject).Replace("\"", "\'"); 1571 1572 var variantGroupsObject = new List<List<String>>(); 1573 foreach (LoopItem variantGroup in GetLoop("VariantGroups")) 1574 { 1575 var variantsObject = new List<String>(); 1576 foreach (LoopItem variantOption in variantGroup.GetLoop("VariantAvailableOptions")) 1577 { 1578 variantsObject.Add(variantOption.GetString("Ecom:VariantOption.ID")); 1579 } 1580 variantGroupsObject.Add(variantsObject); 1581 } 1582 string variantsJson = Newtonsoft.Json.JsonConvert.SerializeObject(variantGroupsObject).Replace("\"", "\'"); 1583 1584 <div class="pdp-variants"> 1585 <div class="product-variants__wrapper"> 1586 <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"> 1587 @foreach (LoopItem variantGroup in GetLoop("VariantGroups")) 1588 { 1589 bool containsImage = variantGroup.GetLoop("VariantAvailableOptions").Any(v => !string.IsNullOrEmpty(v.GetString("Ecom:VariantOption.ImgSmall.Clean"))); 1590 string groupId = variantGroup.GetString("Ecom:VariantGroup.ID"); 1591 1592 <div class="product-variants__block product-variants__block--@groupId"> 1593 @if (containsImage) 1594 { 1595 if(!string.IsNullOrWhiteSpace(variantGroup.GetString("Ecom:VariantGroup.Label"))) 1596 { 1597 <p class="product-variants__title">@variantGroup.GetString("Ecom:VariantGroup.Label")</p> 1598 } 1599 1600 <div class="product-variants__options-wrapper"> 1601 @foreach (LoopItem variantOption in variantGroup.GetLoop("VariantAvailableOptions")) 1602 { 1603 string selected = variantOption.GetBoolean("Ecom:VariantOption.Selected") ? "product-variants__btn--checked" : ""; 1604 1605 if (!string.IsNullOrEmpty(variantOption.GetString("Ecom:VariantOption.ImgSmall.Clean"))) 1606 { 1607 string variantImage = "/Files/" + variantOption.GetString("Ecom:VariantOption.ImgSmall.Clean"); 1608 1609 <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"> 1610 <img src="@variantImage" alt="@variantOption.GetString("Ecom:VariantOption.Name")" title="@variantOption.GetString("Ecom:VariantOption.Name")" /> 1611 </div> 1612 } 1613 else 1614 { 1615 <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> 1616 } 1617 } 1618 </div> 1619 } 1620 else 1621 { 1622 if(!string.IsNullOrWhiteSpace(variantGroup.GetString("Ecom:VariantGroup.Name"))) 1623 { 1624 <p class="product-variants__title">@variantGroup.GetString("Ecom:VariantGroup.Name")</p> 1625 } 1626 1627 <div class="product-variants__dropdown"> 1628 <div class="product-variants__dropdown-wrapper"> 1629 <button class="product-variants__toggle"> 1630 <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> 1631 <i class="fal fa-chevron-down"></i> 1632 </button> 1633 <div class="product-variants__options-wrapper product-variants__options-wrapper--dropdown product-variants__dropdown-options-wrapper"> 1634 @foreach (LoopItem variantOption in variantGroup.GetLoop("VariantAvailableOptions")) 1635 { 1636 string selected = variantOption.GetBoolean("Ecom:VariantOption.Selected") ? "product-variants__btn--checked" : ""; 1637 1638 <button type="button" data-variant-id="@variantOption.GetString("Ecom:VariantOption.ID")" data-variant-group="@groupId" class="js-variant-option product-variants__btn--dropdown @selected"> 1639 @variantOption.GetString("Ecom:VariantOption.Name") 1640 </button> 1641 } 1642 </div> 1643 </div> 1644 </div> 1645 } 1646 </div> 1647 } 1648 </div> 1649 </div> 1650 </div> 1651 } 1652 1653 @* Snippet PdpVolumePrices *@ 1654 @if(displayPrice && displayProductPrice && GetLoop("Product.Prices").Any()) { 1655 var priceList = new List<object>(); 1656 1657 foreach (LoopItem volumePrice in GetLoop("Product.Prices")) 1658 { 1659 int volumePriceQuantity = volumePrice.GetInteger("Ecom:Product.Prices.Quantity"); 1660 1661 if (volumePriceQuantity != 0) 1662 { 1663 double newVolumePrice = Math.Round(volumePrice.GetDouble("Ecom:Product.Prices.Price" + WithVATSuffix) * volumePrice.GetDouble("Ecom:Product.Prices.Quantity"), 2); 1664 string newVolumePriceFormatted = WithVATBool ? new PriceInfo { PriceWithVAT = newVolumePrice }.PriceWithVATFormatted : new PriceInfo { PriceWithoutVAT = newVolumePrice }.PriceWithoutVATFormatted; 1665 1666 double diffVolumePrice = Math.Round((discountProductPriceDouble - volumePrice.GetDouble("Ecom:Product.Prices.Price")) * volumePrice.GetDouble("Ecom:Product.Prices.Quantity"), 2); 1667 string diffVolumePriceFormatted = WithVATBool ? new PriceInfo { PriceWithVAT = diffVolumePrice }.PriceWithVATFormatted : new PriceInfo { PriceWithoutVAT = diffVolumePrice }.PriceWithoutVATFormatted; 1668 1669 if(!FormattedBool) 1670 { 1671 newVolumePriceFormatted = newVolumePriceFormatted.Replace(volumePrice.GetString("Ecom:Product.Prices.Currency.Symbol"), ""); 1672 diffVolumePriceFormatted = diffVolumePriceFormatted.Replace(volumePrice.GetString("Ecom:Product.Prices.Currency.Symbol"), ""); 1673 } 1674 1675 var priceObj = new 1676 { 1677 discountId = volumePrice.GetValue("Product.Prices.LoopCounter"), 1678 newVolumePriceFormatted = newVolumePriceFormatted, 1679 diffVolumePriceFormatted = diffVolumePriceFormatted, 1680 quantity = volumePriceQuantity 1681 }; 1682 priceList.Add(priceObj); 1683 } 1684 } 1685 1686 string pricesJson = Newtonsoft.Json.JsonConvert.SerializeObject(priceList); 1687 1688 <div class="app-volumeprices" data-price-list='@pricesJson'></div> 1689 } 1690 1691 @* Snippet PdpAddToCartBox *@ 1692 @if (!hasVariants && enableShoppingCart && enableProductShoppingCart) { 1693 <div class="pdp-add-to-cart pdp-add-to-cart__container"> 1694 <add-to-cart class="app-addtocart" 1695 data-prodid="@productid" 1696 data-variantid="@productVariantId" 1697 data-min-quantity="@minimumQuantity" 1698 data-step="@quantityStep" 1699 data-list-id="product_detail" 1700 data-list-name="Product detail"> 1701 <a class="pdp__info-btn--add-to-shoppingcart">@inShoppingCartLabel<i class="btn__icon fal fa-shopping-cart"></i></a> 1702 </add-to-cart> 1703 </div> 1704 } 1705 else 1706 { 1707 if (QuotePageID > 0) 1708 { 1709 <div class="pdp-request-quote__container"> 1710 <a href="/Default.aspx?ID=@QuotePageID&ProdID=@productid&VarID=@productVariantId" class="btn product-detailpage__info-btn--request-quote"> 1711 <span class="btn__text">@Translate("ProductDetail.QuoteButton.Text", "Vraag een offerte aan")</span> 1712 <i class="btn__icon @buttonIconClass"></i> 1713 </a> 1714 </div> 1715 } 1716 1717 if(!string.IsNullOrWhiteSpace(GetString("Ecom:Product.ReplacementProductId"))) 1718 { 1719 var repProd = Dynamicweb.Ecommerce.Services.Products.GetProductById(GetString("Ecom:Product.ReplacementProductId"), GetString("Ecom:Product.ReplacementVariantId"), Pageview.Area.EcomLanguageId); 1720 1721 if(repProd != null) 1722 { 1723 int productDetailPageId = GetPageIdByNavigationTag("ProductOverview"); 1724 string productUrl = $"Default.aspx?ID={productDetailPageId}&GroupID={repProd.DefaultGroup.Id}&ProductID={repProd.Id}"; 1725 if(!string.IsNullOrWhiteSpace(repProd.VariantId)) 1726 { 1727 productUrl = $"{productUrl}?VariantID={repProd.VariantId}"; 1728 } 1729 1730 <div class="pdp-replacement__container"> 1731 @if(!string.IsNullOrWhiteSpace(Translate("ProductDetail.ReplacementProd.Intro"))) 1732 { 1733 <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> 1734 } 1735 <a href="@productUrl" class="btn default-btn"> 1736 <span class="btn__text">@string.Format(Translate("ProductDetail.ReplacementProd.Btn", "View replacement product"), repProd.Name)</span> 1737 <i class="btn__icon @buttonIconClass"></i> 1738 </a> 1739 </div> 1740 } 1741 } 1742 } 1743 1744 @* TheGift Sample Request Button *@ 1745 @{ 1746 bool enableSampleRequest = Pageview.User != null; 1747 int sampleRequestPageId = GetPageIdByNavigationTag("SampleRequest"); 1748 1749 if (enableSampleRequest && sampleRequestPageId > 0) 1750 { 1751 <div class="pdp-sample-request__container"> 1752 <a href="/Default.aspx?ID=@sampleRequestPageId&Productnummers=@productNumber" class="btn"> 1753 <span class="btn__text">@Translate("ProductDetail.SampleRequestButton.Text", "Vraag een sample aan")</span> 1754 <i class="btn__icon @buttonIconClass"></i> 1755 </a> 1756 </div> 1757 } 1758 } 1759 1760 @* TheGift VariantSelector *@ 1761 @foreach(var variantRelatedGroups in GetLoop("ProductRelatedGroups").Where(gr => gr.GetString("Ecom:Product:RelatedGroup.GroupID") == "RELGRP7")) { 1762 string relatedGroupName = variantRelatedGroups.GetString("Ecom:Product:RelatedGroup.Name"); 1763 1764 if(variantRelatedGroups.GetLoop("Products").Any()) 1765 { 1766 <section class="variantgroup"> 1767 <h3 class="variantgroup__title">@Translate("ProductRelatedGroup." + relatedGroupName.Replace(" ", ""), relatedGroupName)</h3> 1768 <div class="variantgroup__products"> 1769 @foreach (var varProduct in variantRelatedGroups.GetLoop("Products")) 1770 { 1771 string image = varProduct.GetString("Ecom:Product.ImageDefault.Clean"); 1772 1773 <a href="@varProduct.GetString("Ecom:Product.VariantLinkGroup.Clean")" class="variantgroup__item" title="@varProduct.GetString("Ecom:Product.Name")"> 1774 <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" /> 1775 </a> 1776 } 1777 </div> 1778 </section> 1779 } 1780 } 1781 1782 @* Snippet PdpPaymentLogos *@ 1783 @if(selectedPaymentLogos != null) 1784 { 1785 <section class="pdp-paymentlogos paymentlogos--small"> 1786 <div class="footer-paymentoptions" data-paymentmethods="@selectedPaymentLogos"></div> 1787 </section> 1788 } 1789 1790 @* Snippet PdpPageUspList *@ 1791 @if (productDetailUSPList.Any()) 1792 { 1793 <ul class="pdp-usplist"> 1794 @foreach (ProductDetailUSP item in productDetailUSPList) 1795 { 1796 @RenderProductDetailUSP(item.USP, item.Tooltip) 1797 } 1798 </ul> 1799 } 1800 1801 @* Snippet PdpProductUspList *@ 1802 @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"))) 1803 { 1804 <ul class="pdp-usplist"> 1805 @RenderProductUSP(GetString("Ecom:Product:Field.ProductUSP1.Value"), GetString("Ecom:Product:Field.ProductUSP1Info.Value")) 1806 @RenderProductUSP(GetString("Ecom:Product:Field.ProductUSP2.Value"), GetString("Ecom:Product:Field.ProductUSP2Info.Value")) 1807 @RenderProductUSP(GetString("Ecom:Product:Field.ProductUSP3.Value"), GetString("Ecom:Product:Field.ProductUSP3Info.Value")) 1808 @RenderProductUSP(GetString("Ecom:Product:Field.ProductUSP4.Value"), GetString("Ecom:Product:Field.ProductUSP4Info.Value")) 1809 @RenderProductUSP(GetString("Ecom:Product:Field.ProductUSP5.Value"), GetString("Ecom:Product:Field.ProductUSP5Info.Value")) 1810 </ul> 1811 } 1812 1813 @* Snippet PdpProductShortDescription *@ 1814 @if (hasShortDescription) 1815 { 1816 <div class="pdp-short-description"> 1817 @productShortDescription 1818 </div> 1819 } 1820 1821 @* @if((hasShortDescription && hasLongDescription) || hasSpecifications) 1822 { 1823 <div class="pdp-action-buttons"> 1824 @if (hasShortDescription && hasLongDescription) 1825 { 1826 <p class="pdp-short-description__readmore"> 1827 <a href="#pdp-tabs">@Translate("Productdetail.Readmore", "Read more")</a> 1828 </p> 1829 } 1830 @if (hasSpecifications && enableSpecificationsSidebar) 1831 { 1832 <a href="javascript:void(0);" class="toggle-of-canvas-menu" data-offcanvas-target="specifications"> 1833 @Translate("Productdetail.ViewSpecifications", "View product specifications") 1834 </a> 1835 } 1836 </div> 1837 } *@ 1838 1839 @* Snippet LoginForMoreInfo *@ 1840 @if(Pageview.User == null) 1841 { 1842 int createAccountPageId = GetPageIdByNavigationTag("CreateAccount"); 1843 1844 <div class="pdp-login-seemore"> 1845 <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> 1846 </div> 1847 } 1848 1849 </section> 1850 @SnippetEnd("PdpInfo") 1851 1852 @SnippetStart("PdpImage") 1853 <section class="product-detailpage__images pdp__images-container"> 1854 1855 <section class="productimages pdp__images"> 1856 1857 @{ 1858 int videoThumbPosition = 1; 1859 var SetViaThumbInt = 0; 1860 var SetViaImageInt = 0; 1861 string panoramicPhoto = GetString("Ecom:Product:Field.PanoramicPhoto"); 1862 } 1863 1864 @if ((productImages.Count > 1 || hasYoutubeVideo || !string.IsNullOrWhiteSpace(panoramicPhoto)) && Pageview.User != null) 1865 { 1866 1867 if (videoThumbPosition > productImages.Count) 1868 { 1869 videoThumbPosition = productImages.Count; 1870 } 1871 1872 <section class="productimages__wrapper"> 1873 1874 @if (!string.IsNullOrWhiteSpace(productRibbon)) 1875 { 1876 <p class="product-detailpage__ribbon product-detailpage__ribbon--big product-detailpage__ribbon--@productRibbonStyle"><span>@productRibbon</span></p> 1877 } 1878 1879 @if (!string.IsNullOrWhiteSpace(manufacturerLogo)) 1880 { 1881 <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" /> 1882 } 1883 1884 <div class="productimages__carousel-big hidden"> 1885 1886 @if (!string.IsNullOrWhiteSpace(panoramicPhoto)) 1887 { 1888 <div class="productimages__item"> 1889 <iframe src="@panoramicPhoto" width="100%" height="100%" scrolling="no" marginwidth="0" marginheight="0" frameborder="0" vspace="0" hspace="0" allow="fullscreen" webkitallowfullscreen mozallowfullscreen allowfullscreen> 1890 Could not load @panoramicPhoto 1891 </iframe> 1892 </div> 1893 } 1894 1895 1896 @foreach (var image in productImages) 1897 { 1898 1899 SetViaImageInt++; 1900 1901 <div class="productimages__item"> 1902 <img class="w-auto" src="/Admin/Public/GetImage.ashx?Image=@image&Format=webp&Quality=-1&width=800&height=800" alt="@productName" /> 1903 </div> 1904 1905 if (SetViaImageInt == videoThumbPosition && hasYoutubeVideo) 1906 { 1907 <div class="productimages__item"> 1908 <div class="productimages__carousel__video-container"> 1909 <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> 1910 </div> 1911 </div> 1912 } 1913 } 1914 1915 </div> 1916 1917 </section> 1918 1919 <div class="productimages__carousel-thumbnails hidden"> 1920 @if (!string.IsNullOrWhiteSpace(panoramicPhoto)) 1921 { 1922 <div class="productimages__thumbnail"> 1923 <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" /> 1924 </div> 1925 } 1926 @foreach (var image in productImages) 1927 { 1928 SetViaThumbInt++; 1929 1930 <div class="productimages__thumbnail"> 1931 <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" /> 1932 </div> 1933 1934 if (SetViaThumbInt == videoThumbPosition && hasYoutubeVideo) 1935 { 1936 <div class="productimages__thumbnail productimages__thumbnail--video"> 1937 <img src="https://i.ytimg.com/vi_webp/@YoutubeProductVideo/hqdefault.webp" alt="Video preview @productName" width="100" height="100" /> 1938 <span class="video-icon"> 1939 <i class="fas fa-play"></i> 1940 </span> 1941 </div> 1942 } 1943 } 1944 </div> 1945 } 1946 else if (productImages.Count == 1) 1947 { 1948 var img = productImages.First(); 1949 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"; 1950 1951 <div id="productimages__big" class="productimages__big"> 1952 @if (!string.IsNullOrWhiteSpace(productRibbon)) 1953 { 1954 <p class="product-detailpage__ribbon product-detailpage__ribbon--big product-detailpage__ribbon--@productRibbonStyle"><span>@productRibbon</span></p> 1955 } 1956 @if (!string.IsNullOrWhiteSpace(manufacturerLogo)) 1957 { 1958 <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" /> 1959 } 1960 <img id="product-image" class="product-image" src="@img" alt="@productName" /> 1961 </div> 1962 } 1963 </section> 1964 </section> 1965 @SnippetEnd("PdpImage") 1966 1967 @SnippetStart("PdpHelp") 1968 @if (!string.IsNullOrWhiteSpace(webshopPage.HelpBannerHeader) || !string.IsNullOrWhiteSpace(webshopPage.HelpBannerBody) || !string.IsNullOrWhiteSpace(webshopPage.HelpBannerBody)) 1969 { 1970 <section class="pdp-paragraph pdp-paragraph__container"> 1971 <div class="container pdp-paragraph__innerwrapper"> 1972 @if(!string.IsNullOrWhiteSpace(webshopPage.HelpBannerImage)) 1973 { 1974 <figure class="pdp-paragraph__image"> 1975 <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" /> 1976 </figure> 1977 } 1978 <div class="pdp-paragraph__body"> 1979 @if(!string.IsNullOrWhiteSpace(webshopPage.HelpBannerHeader)) 1980 { 1981 <h2 class="pdp-paragraph__header">@webshopPage.HelpBannerHeader</h2> 1982 } 1983 <div class="pdp-paragraph__content"> 1984 @webshopPage.HelpBannerBody 1985 </div> 1986 @if(!string.IsNullOrWhiteSpace(webshopPage.HelpBannerCTAButtonLink)) 1987 { 1988 <a href="@webshopPage.HelpBannerCTAButtonLink" class="btn default-btn"> 1989 <span class="btn__text">@webshopPage.HelpBannerCTAButtonLabel</span> 1990 <i class="btn__icon fa-chevron-right"></i> 1991 </a> 1992 } 1993 </div> 1994 </div> 1995 </section> 1996 } 1997 @SnippetEnd("PdpHelp") 1998 1999 @SnippetStart("PdpBanner") 2000 @if (!string.IsNullOrWhiteSpace(webshopPage.BannerHeader) || !string.IsNullOrWhiteSpace(webshopPage.BannerBody) || !string.IsNullOrWhiteSpace(webshopPage.BannerCTAButtonLabel)) 2001 { 2002 <section class="pdp-paragraph pdp-paragraph--image-right pdp-paragraph__container"> 2003 <div class="container pdp-paragraph__innerwrapper"> 2004 @if(!string.IsNullOrWhiteSpace(webshopPage.BannerImage)) 2005 { 2006 <figure class="pdp-paragraph__image"> 2007 <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" /> 2008 </figure> 2009 } 2010 <div class="pdp-paragraph__body"> 2011 @if(!string.IsNullOrWhiteSpace(webshopPage.HelpBannerImage)) 2012 { 2013 <h2 class="pdp-paragraph__header">@webshopPage.BannerHeader</h2> 2014 } 2015 <div class="pdp-paragraph__content"> 2016 @if(!string.IsNullOrWhiteSpace(webshopPage.BannerBody)) { 2017 @webshopPage.BannerBody 2018 } 2019 </div> 2020 @if(!string.IsNullOrWhiteSpace(webshopPage.BannerCTAButtonLink)) { 2021 <a href="@webshopPage.BannerCTAButtonLink" class="btn default-btn"> 2022 <span class="btn__text">@webshopPage.BannerCTAButtonLabel</span> 2023 <i class="btn__icon fa-chevron-right"></i> 2024 </a> 2025 } 2026 </div> 2027 </div> 2028 </section> 2029 } 2030 @SnippetEnd("PdpBanner") 2031 2032 @* @SnippetStart("PdpSpecification") 2033 <section class="pdp-specifications" id="specs"> 2034 <div class="container pdp-specifications__container"> 2035 2036 @RenderPDPTabs() 2037 2038 <div class="pdp-specifications__innerwrapper"> 2039 <div class="pdp-specifications__long-description" id="information"> 2040 @RenderInfoContentElement("Ecom:Product.LongDescription", "Productinformatie", "productinformation") 2041 @RenderInfoContentElement("Ecom:Product:Field.ProductDetailSpecs", "Specificaties", "specifications") 2042 @RenderInfoContentElement("Ecom:Product:Field.ProductDetailDownloads", "Downloads", "downloads") 2043 </div> 2044 @if (hasYoutubeVideo) 2045 { 2046 <div class="pdp-specifications__video-wrapper"> 2047 <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> 2048 </div> 2049 } 2050 </div> 2051 </div> 2052 </section> 2053 @SnippetEnd("PdpSpecification") *@ 2054 2055 @SnippetStart("PdpReviews") 2056 <div id="reviews" class="review-paragraph"> 2057 <div class="container review-paragraph__container"> 2058 <div class="review-paragraph__summary"> 2059 <div class="review-summary__column"> 2060 <h2 class="review-summary__title">@Translate("Reviews.Title", "Customer reviews")</h2> 2061 <div class="review-summary__container"> 2062 <div class="review-summary__indicator"> 2063 @renderReviewIndicator("#86C440", GetInteger("Comments.Rating"), "row", true, GetInteger("Comments.TotalCount")) 2064 </div> 2065 </div> 2066 </div> 2067 2068 <div class="review-summary__column"> 2069 @renderReviewDistrubition(GetLoop("Comments")) 2070 </div> 2071 2072 <div class="review-summary__column"> 2073 @if(Dynamicweb.Context.Current.Request["reviewcmd"] != null && Dynamicweb.Context.Current.Request["reviewcmd"] == "created") { 2074 <h2 class="review-summary__title">@Translate("Reviews.Submitted.Title", "Thanks for your review")</h2> 2075 <p>@Translate("Reviews.Submitted.Text", "Thank you for submitting your review.")</p> 2076 } else { 2077 <h2 class="review-summary__title">@Translate("Reviews.Create.Text", "Write a review")</h2> 2078 <p>@Translate("Reviews.Create.Text", "Share your thoughts about this product with other customers.")</p> 2079 <div class="review-summary__btn-wrapper"> 2080 <a href="#" class="btn btn__primary toggle-of-canvas-menu" data-offcanvas-target="reviewcreate"> 2081 <span class="btn__text">@Translate("Reviews.Create.Button", "Write a review")</span> 2082 <i class="btn__icon fal fa-plus"></i> 2083 </a> 2084 </div> 2085 } 2086 </div> 2087 </div> 2088 <div class="review-paragraph__comments"> 2089 @foreach (LoopItem Comment in GetLoop("Comments.Newfirst").Take(3)) 2090 { 2091 <section class="review-comment__list-item"> 2092 <div class="review-comment__header"> 2093 <div class="review-comment__rating-wrapper"> 2094 @renderReviewIndicator("#86C440", Comment.GetInteger("Rating"), "row", false, 0) 2095 </div> 2096 </div> 2097 <div class="review-comment__message"> 2098 <p>@Comment.GetString("Text")</p> 2099 </div> 2100 <p class="review-comment__meta">@Comment.GetString("Name") | @Comment.GetDate("CreatedDate").ToString("d MMMM yyyy")</p> 2101 </section> 2102 } 2103 @if(GetLoop("Comments.Newfirst").Count > 3) { 2104 <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> 2105 } 2106 </div> 2107 </div> 2108 </div> 2109 @SnippetEnd("PdpReviews") 2110 2111 @SnippetStart("PdpRelatedGroups") 2112 @foreach (var relatedGroup in GetLoop("ProductRelatedGroups")) 2113 { 2114 string relatedGroupId = relatedGroup.GetString("Ecom:Product:RelatedGroup.GroupID"); 2115 string relatedGroupName = relatedGroup.GetString("Ecom:Product:RelatedGroup.Name"); 2116 int RelatedGroupsInt = relatedGroup.GetLoop("Products").Count; 2117 string ClassIgniteCarousel = RelatedGroupsInt > 4 ? "products-module__container--carousel" : ""; 2118 2119 if (relatedGroupId != "RELGRP6" && relatedGroupId != "RELGRP7") 2120 { 2121 <section class="products-module__container @ClassIgniteCarousel" id="@relatedGroupId"> 2122 <div class="container"> 2123 2124 <header class="products-module__header"> 2125 <h2 class="products-module__title">@Translate("ProductRelatedGroup." + relatedGroupName.Replace(" ", ""), relatedGroupName)</h2> 2126 </header> 2127 2128 <ul class="products-module__slider"> 2129 @foreach (var Product in relatedGroup.GetLoop("Products")) 2130 { 2131 @renderProduct(Product, ProductdetailPriceSuffix, WithVATSuffix, FormattedSuffix, enableShoppingCart, displayPrice, enableProductStock, relatedGroupId, relatedGroupName, EcommerceConfiguration); 2132 } 2133 </ul> 2134 2135 </div> 2136 </section> 2137 2138 @renderProductListEnhancedEcom(relatedGroupId, relatedGroupName, relatedGroup.GetLoop("Products"), WithVATSuffix); 2139 } 2140 } 2141 @SnippetEnd("PdpRelatedGroups") 2142 2143 @SnippetStart("PdpSecondaryInfo") 2144 <section class="pdp-secondary-info"> 2145 <div class="container pdp-secondary-info__container"> 2146 <div class="pdp-secondary-info__column"> 2147 @if(!string.IsNullOrWhiteSpace(defaultImage)) 2148 { 2149 <figure class="pdp-secondary-info__image-container"> 2150 <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" /> 2151 </figure> 2152 } 2153 </div> 2154 <div class="pdp-secondary-info__column"> 2155 2156 <h2 class="pdp-header__title"> 2157 @if (!string.IsNullOrWhiteSpace(Manufacturer)) 2158 { 2159 <span class="pdp-header__manufacturer">@Manufacturer</span> 2160 } 2161 <span class="pdp-header__productname"> 2162 @productName 2163 </span> 2164 </h2> 2165 2166 @if (GetInteger("Comments.TotalCount") > 0) 2167 { 2168 <div class="pdp-review-indicator"> 2169 @renderReviewIndicator("#86C440", GetInteger("Comments.Rating"), "row", true, GetInteger("Comments.Count")) 2170 </div> 2171 } 2172 2173 @if (!string.IsNullOrWhiteSpace(productNumber)) 2174 { 2175 <p class="pdp-articlenumber"> 2176 @Translate("Productdetail.ArticleNumber.Prefix", "Article number:") @productNumber 2177 </p> 2178 } 2179 2180 @if(enableProductStock && !string.IsNullOrWhiteSpace(stockFormat)) 2181 { 2182 <div class="pdp-stockstate__wrapper"> 2183 @if(stockFormat == "text") { 2184 if (!string.IsNullOrWhiteSpace(stockText)) 2185 { 2186 <p class="pdp-stockstate @stockStateClass">@string.Format(stockText, stock)</p> 2187 } 2188 } 2189 else 2190 { 2191 string translationTag = ""; 2192 if(neverOutOfStock) 2193 { 2194 translationTag = Translate("ProductBlockStockInfo.AmountInStock", "In stock"); 2195 } 2196 else if(stockSize == 1) 2197 { 2198 translationTag = Translate("ProductBlockStockInfo.AmountInStockSingle", "{0} product in stock"); 2199 } 2200 else if(stockSize > 1) 2201 { 2202 translationTag = Translate("ProductBlockStockInfo.AmountInStockMultiple", "{0} products in stock"); 2203 } 2204 else if(!inStock) 2205 { 2206 translationTag = Translate("ProductBlockStockInfoPDP.AmountOutOfStock", "Out of stock"); 2207 } 2208 2209 <p class="pdp-stockstate @stockStateClass">@string.Format(translationTag, stock)</p> 2210 } 2211 </div> 2212 } 2213 2214 @if (!string.IsNullOrWhiteSpace(productTagline) || !string.IsNullOrWhiteSpace(productDetailPageTagline)) 2215 { 2216 <div class="pdp-tagline__container"> 2217 @if (!string.IsNullOrWhiteSpace(productTagline)) 2218 { 2219 <p class="pdp-tagline"> 2220 @productTagline 2221 @if (!string.IsNullOrWhiteSpace(productTaglineInfo)) 2222 { 2223 <span class="pdp-tagline__infoicon" data-tippy-content="@productTaglineInfo"> 2224 <i class="fal fa-info-circle"></i> 2225 </span> 2226 } 2227 </p> 2228 } 2229 2230 @if (!string.IsNullOrWhiteSpace(productDetailPageTagline)) 2231 { 2232 <p class="pdp-tagline"> 2233 @productDetailPageTagline 2234 @if (!string.IsNullOrWhiteSpace(productDetailPageTaglineInfo)) 2235 { 2236 <span class="pdp-tagline__infoicon" data-tippy-content="@productDetailPageTaglineInfo"> 2237 <i class="fal fa-info-circle"></i> 2238 </span> 2239 } 2240 </p> 2241 } 2242 </div> 2243 } 2244 </div> 2245 <div class="pdp-secondary-info__column"> 2246 2247 @if (displayPrice && displayProductPrice) 2248 { 2249 <div class="pdp-price__container"> 2250 @if(hasVariants) { 2251 <section class="pdp-price__subcontainer"> 2252 @if(minPrice != maxPrice) 2253 { 2254 <span class="pdp-price">@minPriceFormatted - @maxPriceFormatted</span> 2255 } 2256 else 2257 { 2258 <span class="pdp-price">@minPriceFormatted</span> 2259 } 2260 <span class="pdp-price--suffix">@ProductdetailPriceSuffix</span> 2261 </section> 2262 } else { 2263 if (hasDiscount) 2264 { 2265 <p class="pdp-price--original">@originalProductPrice</p> 2266 <section class="pdp-price__subcontainer"> 2267 <span class="pdp-price">@discountProductPrice</span> 2268 <span class="pdp-price--suffix">@ProductdetailPriceSuffix</span> 2269 <span class="pdp-price__percentage">@discountPercentage%</span> 2270 </section> 2271 } 2272 else 2273 { 2274 <section class="pdp-price__subcontainer"> 2275 <span class="pdp-price">@originalProductPrice</span> 2276 <span class="pdp-price--suffix">@ProductdetailPriceSuffix</span> 2277 </section> 2278 } 2279 if (!string.IsNullOrWhiteSpace(retailPrice)) 2280 { 2281 <p class="pdp-price__retail-price"> 2282 @String.Format(Translate("ProductBlockTitle.RetailPrice", "Retail price: {0}"), retailPrice) 2283 </p> 2284 } 2285 if (yourProfitValue > 0) 2286 { 2287 <p class="pdp-price__yourprofit"> 2288 @Translate("Productdetail.YourProfitLabel", "Your profit:") @yourProfitValueFormatted 2289 </p> 2290 } 2291 } 2292 </div> 2293 } 2294 2295 @if (GetLoop("VariantGroups").Count > 0){ 2296 string pageId = GetGlobalValue("Global:Page.ID").ToString(); 2297 string variantSelection = productVariantId.Replace(".", ","); 2298 2299 var variantCombinationsObject = new List<Array>(); 2300 foreach (LoopItem variantcomb in GetLoop("VariantStockCombinations")) 2301 { 2302 string[] combinations = variantcomb.GetString("Ecom:VariantStockCombination.VariantID").Split('.'); 2303 variantCombinationsObject.Add(combinations); 2304 } 2305 string combinationsJson = Newtonsoft.Json.JsonConvert.SerializeObject(variantCombinationsObject).Replace("\"", "\'"); 2306 2307 var variantGroupsObject = new List<List<String>>(); 2308 foreach (LoopItem variantGroup in GetLoop("VariantGroups")) 2309 { 2310 var variantsObject = new List<String>(); 2311 foreach (LoopItem variantOption in variantGroup.GetLoop("VariantAvailableOptions")) 2312 { 2313 variantsObject.Add(variantOption.GetString("Ecom:VariantOption.ID")); 2314 } 2315 variantGroupsObject.Add(variantsObject); 2316 } 2317 string variantsJson = Newtonsoft.Json.JsonConvert.SerializeObject(variantGroupsObject).Replace("\"", "\'"); 2318 2319 <div class="pdp-variants"> 2320 <div class="product-variants__wrapper"> 2321 <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"> 2322 @foreach (LoopItem variantGroup in GetLoop("VariantGroups")) 2323 { 2324 bool containsImage = variantGroup.GetLoop("VariantAvailableOptions").Any(v => !string.IsNullOrEmpty(v.GetString("Ecom:VariantOption.ImgSmall.Clean"))); 2325 string groupId = variantGroup.GetString("Ecom:VariantGroup.ID"); 2326 2327 <div class="product-variants__block product-variants__block--@groupId"> 2328 @if (containsImage) 2329 { 2330 if(!string.IsNullOrWhiteSpace(variantGroup.GetString("Ecom:VariantGroup.Label"))) 2331 { 2332 <p class="product-variants__title">@variantGroup.GetString("Ecom:VariantGroup.Label")</p> 2333 } 2334 2335 <div class="product-variants__options-wrapper"> 2336 @foreach (LoopItem variantOption in variantGroup.GetLoop("VariantAvailableOptions")) 2337 { 2338 string selected = variantOption.GetBoolean("Ecom:VariantOption.Selected") ? "product-variants__btn--checked" : ""; 2339 2340 if (!string.IsNullOrEmpty(variantOption.GetString("Ecom:VariantOption.ImgSmall.Clean"))) 2341 { 2342 string variantImage = "/Files/" + variantOption.GetString("Ecom:VariantOption.ImgSmall.Clean"); 2343 <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"> 2344 <img src="@variantImage" alt="@variantOption.GetString("Ecom:VariantOption.Name")" title="@variantOption.GetString("Ecom:VariantOption.Name")" /> 2345 </div> 2346 } 2347 else 2348 { 2349 <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> 2350 } 2351 } 2352 </div> 2353 } 2354 else 2355 { 2356 if(!string.IsNullOrWhiteSpace(variantGroup.GetString("Ecom:VariantGroup.Name"))) 2357 { 2358 <p class="product-variants__title">@variantGroup.GetString("Ecom:VariantGroup.Name")</p> 2359 } 2360 2361 <div class="product-variants__dropdown"> 2362 <div class="product-variants__dropdown-wrapper"> 2363 2364 <button class="product-variants__toggle"> 2365 <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> 2366 <i class="fal fa-chevron-down"></i> 2367 </button> 2368 <div class="product-variants__options-wrapper product-variants__options-wrapper--dropdown product-variants__dropdown-options-wrapper"> 2369 @foreach (LoopItem variantOption in variantGroup.GetLoop("VariantAvailableOptions")) { 2370 string selected = variantOption.GetBoolean("Ecom:VariantOption.Selected") ? "product-variants__btn--checked" : ""; 2371 2372 <button type="button" data-variant-id="@variantOption.GetString("Ecom:VariantOption.ID")" data-variant-group="@groupId" class="js-variant-option product-variants__btn--dropdown @selected"> 2373 @variantOption.GetString("Ecom:VariantOption.Name") 2374 </button> 2375 } 2376 </div> 2377 </div> 2378 </div> 2379 } 2380 </div> 2381 } 2382 </div> 2383 </div> 2384 </div> 2385 } 2386 2387 @if (!hasVariants && enableShoppingCart && enableProductShoppingCart) 2388 { 2389 <div class="pdp-add-to-cart pdp-add-to-cart__container"> 2390 <add-to-cart class="app-addtocart" 2391 data-prodid="@productid" 2392 data-variantid="@productVariantId" 2393 data-min-quantity="@minimumQuantity" 2394 data-step="@quantityStep" 2395 data-list-id="product_detail" 2396 data-list-name="Product detail"> 2397 <a class="pdp__info-btn--add-to-shoppingcart">@inShoppingCartLabel<i class="btn__icon fal fa-shopping-cart"></i></a> 2398 </add-to-cart> 2399 </div> 2400 } 2401 else 2402 { 2403 if (QuotePageID > 0) 2404 { 2405 <div class="pdp-request-quote__container"> 2406 <a href="/Default.aspx?ID=@QuotePageID&ProdID=@productid&VarID=@productVariantId" class="btn product-detailpage__info-btn--request-quote"> 2407 <span class="btn__text">@Translate("ProductDetail.QuoteButton.Text", "Vraag een offerte aan")</span> 2408 <i class="btn__icon @buttonIconClass"></i> 2409 </a> 2410 </div> 2411 } 2412 2413 if(!string.IsNullOrWhiteSpace(GetString("Ecom:Product.ReplacementProductId"))) 2414 { 2415 var repProd = Dynamicweb.Ecommerce.Services.Products.GetProductById(GetString("Ecom:Product.ReplacementProductId"), GetString("Ecom:Product.ReplacementVariantId"), Pageview.Area.EcomLanguageId); 2416 2417 if(repProd != null) 2418 { 2419 int productDetailPageId = GetPageIdByNavigationTag("ProductOverview"); 2420 string productUrl = $"Default.aspx?ID={productDetailPageId}&GroupID={repProd.DefaultGroup.Id}&ProductID={repProd.Id}"; 2421 if(!string.IsNullOrWhiteSpace(repProd.VariantId)) 2422 { 2423 productUrl = $"{productUrl}?VariantID={repProd.VariantId}"; 2424 } 2425 2426 <div class="pdp-replacement__container"> 2427 @if(!string.IsNullOrWhiteSpace(Translate("ProductDetail.ReplacementProd.Intro"))) 2428 { 2429 <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> 2430 } 2431 <a href="@productUrl" class="btn default-btn"> 2432 <span class="btn__text">@string.Format(Translate("ProductDetail.ReplacementProd.Btn", "View replacement product"), repProd.Name)</span> 2433 <i class="btn__icon @buttonIconClass"></i> 2434 </a> 2435 </div> 2436 } 2437 } 2438 } 2439 2440 @if(selectedPaymentLogos != null) 2441 { 2442 <section class="pdp-paymentlogos paymentlogos--small"> 2443 <div class="footer-paymentoptions" data-paymentmethods="@selectedPaymentLogos"></div> 2444 </section> 2445 } 2446 </div> 2447 </div> 2448 </section> 2449 @SnippetEnd("PdpSecondaryInfo") 2450 2451 @SnippetStart("PdpRecentViewedProducts") 2452 @if (GetInteger("eCom:Related.YouHaveSeenTheseProducts.Count") > 0) 2453 { 2454 var groupTitle = Translate("Productdetail.RecentlyViewedArticles", "Recently viewed articles"); 2455 var recentlyViewedProducts = GetLoop("eCom:Related.YouHaveSeenTheseProducts"); 2456 recentlyViewedProducts.Reverse(); 2457 recentlyViewedProducts = recentlyViewedProducts.Where(p => !string.IsNullOrWhiteSpace(p.GetString("Ecom:Product.ID")) && p.GetString("Ecom:Product.ID") != productid).Take(4).ToList(); 2458 2459 if(recentlyViewedProducts.Count > 0) 2460 { 2461 <section class="products-module__container"> 2462 <div class="container"> 2463 2464 <header class="products-module__header"> 2465 <h2 class="products-module__title">@groupTitle</h2> 2466 </header> 2467 2468 <ul class="products-module__slider"> 2469 @foreach (var Product in recentlyViewedProducts) 2470 { 2471 @renderProduct(Product, ProductdetailPriceSuffix, WithVATSuffix, FormattedSuffix, enableShoppingCart, displayPrice, enableProductStock, "recently_viewed_articles", groupTitle, EcommerceConfiguration); 2472 } 2473 </ul> 2474 2475 </div> 2476 </section> 2477 2478 @renderProductListEnhancedEcom("recently_viewed_articles", groupTitle, recentlyViewedProducts, WithVATSuffix); 2479 } 2480 } 2481 @SnippetEnd("PdpRecentViewedProducts") 2482 2483 <!-- ***** END PDP ***** --> 2484 2485 @SnippetStart("ProductDetailHeaderDesktop") 2486 <!-- BEGIN Stickymenu --> 2487 <section class="stickymenu__replaceable"> 2488 2489 <div class="stickymenu_product-info-wrapper"> 2490 @if (!string.IsNullOrWhiteSpace(defaultImage)) 2491 { 2492 <figure class="stickymenu__product-image"> 2493 <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"> 2494 </figure> 2495 } 2496 <div class="stickymenu_product-info"> 2497 <p class="stickymenu__product-name"> 2498 @if(!string.IsNullOrWhiteSpace(Manufacturer)) 2499 { 2500 <span class="stickymenu__product-name--manufacturer">@Manufacturer </span> 2501 } 2502 <span class="stickymenu__product-name--product">@productName</span> 2503 </p> 2504 2505 @if (displayPrice && displayProductPrice) 2506 { 2507 if (hasDiscount) 2508 { 2509 <div class="stickymenu_product__price__wrapper"> 2510 <span class="stickymenu_product__price--old">@originalProductPrice</span> 2511 <span class="stickymenu_product__price--current">@discountProductPrice</span> 2512 <span class="stickymenu_product__price--suffix">@ProductdetailPriceSuffix</span> 2513 <span class="stickymenu_product__price--profit">@Translate("Productdetail.YourProfitLabel", "Your profit:") @yourProfitValueFormatted</span> 2514 </div> 2515 } 2516 else 2517 { 2518 <div class="stickymenu_product__price__wrapper"> 2519 <span class="stickymenu_product__price--current">@originalProductPrice</span> 2520 <span class="stickymenu_product__price--suffix">@ProductdetailPriceSuffix</span> 2521 </div> 2522 } 2523 <p class="pdp-price--consumer"> 2524 @if(Pageview.User != null) 2525 { 2526 if(!string.IsNullOrWhiteSpace(retailPrice)) 2527 { 2528 @String.Format(Translate("ProductBlockTitle.AVP", "AVP: {0}"), retailPrice); 2529 } 2530 } 2531 else 2532 { 2533 @Translate("ProductBlockTitle.AVP.Guests", "Adviesprijs"); 2534 } 2535 </p> 2536 } 2537 2538 </div> 2539 </div> 2540 2541 @if (enableShoppingCart && enableProductShoppingCart) 2542 { 2543 <add-to-cart class="app-addtocart" 2544 data-prodid="@productid" 2545 data-variantid="@productVariantId" 2546 data-min-quantity="@minimumQuantity" 2547 data-step="@quantityStep" 2548 data-list-id="product_detail" 2549 data-list-name="Product detail"> 2550 <!-- Fall Back button for Add to Cart--> 2551 <a class="pdp__add-to-cart-btn">@inShoppingCartLabel <i class="btn__icon fal fa-shopping-cart"></i></a> 2552 </add-to-cart> 2553 } 2554 else 2555 { 2556 if (QuotePageID > 0) 2557 { 2558 <div class="py-4"> 2559 <a href="/Default.aspx?ID=@QuotePageID&ProdID=@productid&VarID=@productVariantId" class="btn product-detailpage__info-btn--request-quote"> 2560 <span class="btn__text">@Translate("ProductDetail.QuoteButton.Text", "Vraag een offerte aan")</span> 2561 <i class="btn__icon @buttonIconClass"></i> 2562 </a> 2563 </div> 2564 } 2565 } 2566 2567 </section> 2568 <!-- END Stickymenu --> 2569 @SnippetEnd("ProductDetailHeaderDesktop") 2570 2571 @inherits Dynamicweb.Rendering.RazorTemplateBase<Dynamicweb.Rendering.RazorTemplateModel<Dynamicweb.Rendering.Template>> 2572 @using Dynamicweb; 2573 @using Bluedesk.DynamicWeb.ItemTypes.Pages; 2574 @using Bluedesk.Tools.DynamicWeb.ExtensionMethods; 2575 @using System.Linq; 2576 @using Dynamicweb.Content; 2577 2578 @{ 2579 2580 } 2581 2582 <style> 2583 .products-module__product-btn--add-to-shoppingcart { 2584 background-color: @AddToCartButtonBackgroundColor; 2585 } 2586 </style> 2587 2588 2589 @using System.Text.RegularExpressions; 2590 2591 @if(enableSpecificationsSidebar) 2592 { 2593 <div class="offcanvas__backdrop" name="specifications"></div> 2594 2595 <aside class="offcanvas" name="specifications"> 2596 <header class="offcanvas__header"> 2597 <span class="offcanvas__title">@Translate("OffCanvasMenu.SpecificationsTitle", "Specifications")</span> 2598 <button class="offcanvas__close" aria-label="@Translate("OffCanvasMenu.Close", "Close menu")"> 2599 <i class="fal fa-times"></i> 2600 </button> 2601 </header> 2602 <div class="offcanvas__body"> 2603 @foreach(LoopItem categorie in GetLoop("ProductCategories")) 2604 { 2605 string prodCategoryId = categorie.GetString("Ecom:Product.Category.ID"); 2606 string prodCategoryName = categorie.GetString("Ecom:Product.Category.Name"); 2607 Regex valueRegex = new Regex(@"\[(.+?)\]", RegexOptions.IgnoreCase); 2608 2609 if(categorie.GetLoop("ProductCategoryFields").Any(d => !string.IsNullOrWhiteSpace(d.GetString("Ecom:Product.CategoryField.Value")))) 2610 { 2611 <section class="product-specifications__category"> 2612 <button class="product-specifications__header product-specifications__toggle" aria-selected="true" aria-controls="@prodCategoryId"> 2613 <p class="product-specifications__title">@Translate("SpecificationsList." + prodCategoryName.Replace(" ", ""), prodCategoryName)</p> 2614 <i class="fal fa-chevron-down"></i> 2615 </button> 2616 <ul id="@prodCategoryId" class="product-specifications__list"> 2617 @foreach (var categoryField in categorie.GetLoop("ProductCategoryFields")) 2618 { 2619 if (!string.IsNullOrWhiteSpace(categoryField.GetString("Ecom:Product.CategoryField.Value"))) 2620 { 2621 Match unitMatch = valueRegex.Match(categoryField.GetString("Ecom:Product.CategoryField.Label")); 2622 string unit = unitMatch.Groups[1].Value; 2623 2624 <li class="product-specifications__list-item"> 2625 <span class="product-specifications__label">@valueRegex.Replace(categoryField.GetString("Ecom:Product.CategoryField.Label"), "")</span> 2626 <span class="product-specifications__value">@categoryField.GetString("Ecom:Product.CategoryField.Value") @unit</span> 2627 </li> 2628 } 2629 } 2630 </ul> 2631 </section> 2632 } 2633 } 2634 2635 </div> 2636 </aside> 2637 } 2638 @using System.Text.RegularExpressions; 2639 2640 <div class="offcanvas__backdrop" name="reviews"></div> 2641 2642 <aside class="offcanvas" name="reviews"> 2643 <header class="offcanvas__header"> 2644 <span class="offcanvas__title">@Translate("OffCanvasMenu.ReviewTitle", "Customer reviews") (@GetLoop("Comments.Newfirst").Count)</span> 2645 <button class="offcanvas__close" aria-label="@Translate("OffCanvasMenu.Close", "Close menu")"> 2646 <i class="fal fa-times"></i> 2647 </button> 2648 </header> 2649 <div class="offcanvas__body"> 2650 @foreach (LoopItem Comment in GetLoop("Comments.Newfirst")) 2651 { 2652 <section class="review-comment__list-item"> 2653 <div class="review-comment__header"> 2654 <div class="review-comment__rating-wrapper"> 2655 @renderReviewIndicator("#86C440", Comment.GetInteger("Rating"), "row", false, 0) 2656 </div> 2657 </div> 2658 <div class="review-comment__message"> 2659 <p>@Comment.GetString("Text")</p> 2660 </div> 2661 <p class="review-comment__meta">@Comment.GetString("Name") | @Comment.GetDate("CreatedDate").ToString("d MMMM yyyy")</p> 2662 </section> 2663 } 2664 </div> 2665 </aside> 2666 @using System.Text.RegularExpressions; 2667 2668 @{ 2669 @* var pageView = Dynamicweb.Frontend.PageView.Current(); *@ 2670 var isLoggedIn = Dynamicweb.Security.UserManagement.User.IsExtranetUserLoggedIn(); 2671 var currentUser = Dynamicweb.Security.UserManagement.User.GetCurrentExtranetUser(); 2672 var ecomLanguageId = GetString("Ecom:Product.LanguageID"); 2673 2674 string namePrefill = currentUser != null ? currentUser.Name : ""; 2675 string emailPrefill = currentUser != null ? currentUser.Email : ""; 2676 } 2677 2678 <script> 2679 var errorMessagesJSON = 2680 { 2681 'StrErrorMsgTxtName': { 2682 '_default' : '@Translate("form.error.text", "It looks like this field is empty or too short")', 2683 '_validate' : '@Translate("form.error.text.notvalid", "It looks like this field is not valid")' 2684 }, 2685 'StrErrorMsgTxtEmail': { 2686 '_default' : '@Translate("form.error.emaildefault", "It seems that your e-mail address has not been completed")', 2687 '_validate' : '@Translate("form.error.emailnotvalid", "It seems that this is not a valid e-mail address")' 2688 } 2689 }; 2690 </script> 2691 2692 <div class="offcanvas__backdrop" name="reviewcreate"></div> 2693 2694 <aside class="offcanvas" name="reviewcreate"> 2695 2696 <header class="offcanvas__header"> 2697 <span class="offcanvas__title">@Translate("OffCanvasMenu.ReviewCreateTitle", "Rate this product")</span> 2698 <button class="offcanvas__close" aria-label="@Translate("OffCanvasMenu.Close", "Close menu")"> 2699 <i class="fal fa-times"></i> 2700 </button> 2701 </header> 2702 2703 <div class="offcanvas__body"> 2704 <form method="post" action="/Default.aspx?ID=@Pageview.ID" id="commentform" class="default-contact-form"> 2705 <input type="hidden" name="Comment.Command" id="Comment.Command" value="create" /> 2706 <input type="hidden" name="Comment.Active" value="false" /> 2707 <input type="hidden" name="Comment.ItemType" value="ecomProduct" /> 2708 <input type="hidden" name="Comment.ItemID" value="@productid" /> 2709 <input type="hidden" name="Comment.LangID" value="@ecomLanguageId" /> 2710 <input type="hidden" name="Comment.Continue" value="@Pageview.SearchFriendlyUrl?reviewcmd=created#pdp-tabs" /> 2711 2712 <div class="flex flex-row flex-wrap"> 2713 <div class="w-full"> 2714 @renderDefaultField("Name", "Review.Form", true, "Comment.Name", namePrefill, "text", 2715 new Dictionary<string, string>() { 2716 { "erroralias", "StrErrorMsgTxtName" }, 2717 { "verplicht", "verplicht" }, 2718 { "minchar", "1" }, 2719 } 2720 ) 2721 </div> 2722 </div> 2723 2724 <div class="flex flex-row flex-wrap"> 2725 <div class="w-full"> 2726 @renderDefaultField("Email", "Review.Form", true, "Comment.Email", emailPrefill, "email", 2727 new Dictionary<string, string>() { 2728 { "erroralias", "StrErrorMsgTxtEmail" }, 2729 { "verplicht", "verplicht" }, 2730 { "minchar", "1" }, 2731 } 2732 ) 2733 </div> 2734 </div> 2735 2736 <div class="flex flex-row flex-wrap"> 2737 <div class="w-full"> 2738 @renderStarRatingField("Rating", "Review.Form", true, "Comment.Rating") 2739 </div> 2740 </div> 2741 2742 <div class="flex flex-row flex-wrap"> 2743 <div class="w-full"> 2744 @renderTextArea("Comment", "Review.Form", true, "Comment.Text", "", 2745 new Dictionary<string, string>() { 2746 { "erroralias", "StrErrorMsgTxtName" }, 2747 { "verplicht", "verplicht" }, 2748 { "minchar", "1" }, 2749 { "rows", "10" } 2750 } 2751 ) 2752 </div> 2753 </div> 2754 2755 </form> 2756 </div> 2757 2758 <footer class="offcanvas__footer"> 2759 <button type="submit" form="commentform" class="btn btn__primary"> 2760 <span class="btn__text">@Translate("Review.Form.Submit", "Submit review")</span> 2761 <i class="far fa-chevron-right btn__icon"></i> 2762 </button> 2763 </footer> 2764 2765 </aside> 2766 2767 @helper renderInputLabel(string prefix, string label, bool isRequired, string inputId) { 2768 var labelCode = string.Format("{0}.{1}", prefix, label.Replace(" ", "_")); 2769 <label class="form__input-label" for="@inputId">@Translate(labelCode, label) @(isRequired ? "*" : "")</label> 2770 } 2771 2772 @helper renderDefaultField(string label, string labelPrefix, bool isRequired, string fieldId, string fieldValue, string fieldType, Dictionary<string, string> customattributes) 2773 { 2774 string attrs = ""; 2775 foreach (var item in customattributes) 2776 { 2777 attrs += item.Key + "=\"" + item.Value + "\" "; 2778 } 2779 2780 <section class="form__item form__item--default"> 2781 <div class="input__group form-item__input"> 2782 @renderInputLabel(labelPrefix, label, isRequired, fieldId) 2783 <input 2784 name="@fieldId" 2785 id="@fieldId" 2786 class="input form__input" 2787 type="@fieldType" 2788 value="@fieldValue" 2789 @attrs 2790 /> 2791 <i class="fal fa-check checkmark"></i> 2792 <i class="fal fa-times cross"></i> 2793 </div> 2794 <div class="errormessage"></div> 2795 </section> 2796 } 2797 2798 @helper renderTextArea(string label, string labelPrefix, bool isRequired, string fieldId, string fieldValue, Dictionary<string, string> customattributes) 2799 { 2800 string attrs = ""; 2801 foreach (var item in customattributes) 2802 { 2803 attrs += item.Key + "=\"" + item.Value + "\" "; 2804 } 2805 2806 <section class="form__item"> 2807 <div class="input__group form-item__input form-group"> 2808 @renderInputLabel(labelPrefix, label, isRequired, fieldId) 2809 <textarea class="input--text form__input form__input--textarea" 2810 id="@fieldId" 2811 name="@fieldId" 2812 @attrs 2813 >@fieldValue</textarea> 2814 </div> 2815 <div class="errormessage"></div> 2816 </section> 2817 } 2818 2819 @helper renderStarRatingField(string label, string labelPrefix, bool isRequired, string fieldId) { 2820 <section class="form__item form__item--default form__item--starrating"> 2821 @renderInputLabel(labelPrefix, label, isRequired, fieldId) 2822 <div class="star-rating"> 2823 <div class="star-rating__wrap"> 2824 <input class="star-rating__input" id="star-rating-5" type="radio" name="@fieldId" value="5"> 2825 <label class="star-rating__ico far fa-star" for="star-rating-5" title="5 out of 5 stars"></label> 2826 <input class="star-rating__input" id="star-rating-4" type="radio" name="@fieldId" value="4"> 2827 <label class="star-rating__ico far fa-star" for="star-rating-4" title="4 out of 5 stars"></label> 2828 <input class="star-rating__input" id="star-rating-3" type="radio" name="@fieldId" value="3"> 2829 <label class="star-rating__ico far fa-star" for="star-rating-3" title="3 out of 5 stars"></label> 2830 <input class="star-rating__input" id="star-rating-2" type="radio" name="@fieldId" value="2"> 2831 <label class="star-rating__ico far fa-star" for="star-rating-2" title="2 out of 5 stars"></label> 2832 <input class="star-rating__input" id="star-rating-1" type="radio" name="@fieldId" value="1"> 2833 <label class="star-rating__ico far fa-star" for="star-rating-1" title="1 out of 5 stars"></label> 2834 </div> 2835 </div> 2836 <div class="errormessage"></div> 2837 </section> 2838 } 2839 2840 2841 <script> 2842 window.globals.productId = '@productid'; 2843 window.globals.productVariantId = '@productVariantId'; 2844 </script> 2845 2846 @if(enableShoppingCart && enableProductShoppingCart) 2847 { 2848 <section class="w-full stickymenu__replaceable__wrapper"> 2849 <div class="container"> 2850 @RenderSnippet("ProductDetailHeaderDesktop") 2851 </div> 2852 </section> 2853 2854 <script> 2855 window.addEventListener('DOMContentLoaded', function (event) { 2856 var stickyAddToCartEl = document.querySelector('.stickymenu__replaceable__wrapper'); 2857 var pdpAddToCartElement = document.querySelector('.pdp-add-to-cart'); 2858 var scrollToTopEl = document.querySelector('#scroll-to-top'); 2859 2860 if(pdpAddToCartElement) { 2861 var observer = new IntersectionObserver((entries, observer) => { 2862 entries.forEach(entry => { 2863 if(entry.intersectionRatio != 1 && window.scrollY >= entry.boundingClientRect.top) { 2864 // Out of view 2865 stickyAddToCartEl.classList.add('stickymenu__replaceable__wrapper--active'); 2866 scrollToTopEl.classList.add('scroll-to-top--shown--lg'); 2867 scrollToTopEl.style.setProperty('--sticky-bottom-offset', stickyAddToCartEl.offsetHeight + 'px'); 2868 document.body.style.paddingBottom = stickyAddToCartEl.offsetHeight + 'px'; 2869 } else { 2870 // In view 2871 stickyAddToCartEl.classList.remove('stickymenu__replaceable__wrapper--active'); 2872 scrollToTopEl.classList.remove('scroll-to-top--shown--lg'); 2873 scrollToTopEl.style.setProperty('--sticky-bottom-offset', '0px'); 2874 document.body.style.paddingBottom = null; 2875 } 2876 }); 2877 }, { 2878 threshold: 1 2879 }); 2880 2881 observer.observe(pdpAddToCartElement); 2882 } 2883 2884 }); 2885 </script> 2886 } 2887 2888 <main class="pdp"> 2889 2890 <div class="container pdp-info__container"> 2891 <div class="pdp-info__imagecolumn"> 2892 @RenderSnippet("PdpHeader") 2893 @RenderSnippet("PdpImage") 2894 </div> 2895 @RenderSnippet("PdpInfo") 2896 </div> 2897 2898 @RenderPdpTabs(showReviewTab, enableSpecificationsSidebar) 2899 @* @RenderSnippet("PdpSpecification") *@ 2900 @* @RenderSnippet("PdpBanner") *@ 2901 @* @RenderSnippet("PdpReviews") *@ 2902 @RenderSnippet("PdpRelatedGroups") 2903 @RenderSnippet("PdpHelp") 2904 @if(enableShoppingCart && enableProductShoppingCart) 2905 { 2906 @* @RenderSnippet("PdpSecondaryInfo"); *@ 2907 } 2908 @RenderSnippet("PdpRecentViewedProducts") 2909 2910 </main> 2911 2912 @using System.Globalization; 2913 2914 @{ 2915 var httpdomain = Dynamicweb.Environment.Helpers.LinkHelper.GetHttpDomain(); 2916 var productAvailability = GetInteger("Ecom:Product.Stock") > 0 ? "https://schema.org/InStock" : "https://schema.org/OutOfStock"; 2917 double productPrice = GetBoolean("Ecom:Product.HaveDiscount") ? GetDouble("Ecom:Product.Discount.Price.PriceWithVAT.Value") : GetDouble("Ecom:Product.Price.PriceWithVAT.Value"); 2918 2919 //Dynamicweb gives the values with comma notation, while google expects a dot notation and then converts it into a comma notation on frontend 2920 string formattedProductPrice = Math.Round(productPrice, 2, MidpointRounding.AwayFromZero).ToString(specifier, culture); 2921 2922 //Rating is set at the website setting, not product setting. If it's not defined, the rating won't be rendered in the review 2923 double rating = GetDouble("Ecom:Product.Rating"); 2924 bool renderRating = rating > 0; 2925 2926 //Must be at least 1, or reviews won't be rendered 2927 int reviewCount = Math.Max(GetInteger("Comments.RepliesCount"), 1); 2928 } 2929 2930 <script type="application/ld+json"> 2931 { 2932 "@@context": "https://schema.org/", 2933 "@@type": "Product", 2934 "name": "@GetString("Ecom:Product.Name")", 2935 "image": "@httpdomain@GetString("Ecom:Product.ImageLarge.Clean")", 2936 "description": "@GetString("Ecom:Product.ShortDescription.Raw")", 2937 "sku": "@GetString("Ecom:Product:Field.EAN.Value.Clean")", 2938 "mpn": "@GetString("Ecom:Product.ID")", 2939 "url": "@httpdomain/@GetString("Ecom:Product.VariantLinkGroup.Clean")", 2940 @if(!string.IsNullOrWhiteSpace(GetString("Ecom:Manufacturer.Name"))) 2941 { 2942 <text> 2943 "brand": { 2944 "@@type": "Brand", 2945 "name": "@GetString("Ecom:Manufacturer.Name")" 2946 }, 2947 </text> 2948 } 2949 @if(renderRating) 2950 { 2951 <text> 2952 "aggregateRating": { 2953 "@@type": "AggregateRating", 2954 "ratingValue": "@rating", 2955 "reviewCount": "@reviewCount" 2956 }, 2957 </text> 2958 } 2959 "offers": { 2960 "@@type": "Offer", 2961 "availability": "@productAvailability", 2962 "itemCondition": "https://schema.org/NewCondition", 2963 "price": "@formattedProductPrice", 2964 "priceCurrency": "@GetString("Ecom:Product.Price.Currency.Code")", 2965 "url": "@httpdomain/@GetString("Ecom:Product.VariantLinkGroup.Clean")" 2966 } 2967 } 2968 </script> 2969 2970 2971 @SnippetStart("DataLayer") 2972 @{ 2973 var categoryObject = Dynamicweb.Ecommerce.Services.ProductGroups.GetGroup(GetString("Ecom:Product.PrimaryOrFirstGroupID")); 2974 string categoryName = categoryObject != null ? categoryObject.Name : ""; 2975 } 2976 2977 <script> 2978 if(window.dataLayer) { 2979 dataLayer.push({ 2980 'event': 'view_item', 2981 'ecommerce': { 2982 'currency': '@Dynamicweb.Ecommerce.Common.Context.Currency.Code', 2983 'value': @GetDouble("Ecom:Product.Discount.Price.Price.Value").ToString(specifier, culture), 2984 'items': [{ 2985 'item_id': '@GetString("Ecom:Product.ID")', 2986 'item_name': '@GetString("Ecom:Product.Name").Replace("''", "\\\"").Replace("'", "")', 2987 'item_number': '@GetString("Ecom:Product.Number")', 2988 'discount': @GetDouble("Ecom:Product.Discount.TotalAmount.Price.Value").ToString(specifier, culture), 2989 'index': 1, 2990 'item_brand': '@GetString("Ecom:Manufacturer.Name").Replace("''", "\\\"").Replace("'", "")', 2991 'item_category': '@categoryName.Replace("''", "\\\"").Replace("'", "")', 2992 'item_list_id': '@listId', 2993 'item_list_name': '@listName', 2994 'item_variant': '@GetString("Ecom:Product.VariantID")', 2995 'price': @GetDouble("Ecom:Product.Price.Price.Value").ToString(specifier, culture), 2996 'quantity': 1 2997 }] 2998 } 2999 }); 3000 } 3001 </script> 3002 3003 @SnippetEnd("DataLayer") 3004 3005 3006 @SnippetStart("ProductDetailHeaderMobile") 3007 <!-- BEGIN Stickymenu --> 3008 <section class="stickymenu__replaceable flex flex-1 md:justify-between items-center"> 3009 <div class="stickymenu_product-info-wrapper flex items-center mr-auto"> 3010 @if (!string.IsNullOrWhiteSpace(defaultImage)) 3011 { 3012 <div class="stickymenu__product-image mr-2" style="background-image: url('@defaultImage');"></div> 3013 } 3014 <div class="flex flex-col leading-tight"> 3015 <p class="h5 sm:text-l md:text-xl font-bold m-0 stickymenu__product-name">@productName</p> 3016 3017 @if (displayPrice) 3018 { 3019 if (!pricezero) 3020 { 3021 if (hasDiscount) 3022 { 3023 <div class="stickymenu_product__price__wrapper flex items-center"> 3024 <span class="stickymenu_product__price--old">@priceFormatted</span> 3025 <span class="stickymenu_product__price--current">@discountPriceValue</span> 3026 <span class="stickymenu_product__price--profit">@yourProfitLabel @yourProfitValue</span> 3027 </div> 3028 } 3029 else 3030 { 3031 <p>@priceFormatted</p> 3032 } 3033 } 3034 } 3035 3036 </div> 3037 </div> 3038 </section> 3039 <!-- END Stickymenu --> 3040 3041 @SnippetEnd("ProductDetailHeaderMobile") 3042
To Top