Error executing template "/Designs/Swift/Paragraph/Custom_Swift_ProductDetailsInfo.cshtml"
System.ArgumentOutOfRangeException: Index was out of range. Must be non-negative and less than the size of the collection.
Parameter name: index
at System.ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument argument, ExceptionResource resource)
at System.Collections.Generic.List`1.get_Item(Int32 index)
at CompiledRazorTemplates.Dynamic.RazorEngine_08250693537f4c34bf63bb76f5c6eaf1.Execute() in D:\dynamicweb.net\Solutions\Novicell\danitech.cloud.dynamicweb-cms.com\Files\Templates\\Designs\Swift\Paragraph\Custom_Swift_ProductDetailsInfo.cshtml:line 232
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.ViewModelTemplate<Dynamicweb.Frontend.ParagraphViewModel>
2 @using Dynamicweb.Ecommerce.ProductCatalog
3 @using Dynamicweb.Ecommerce.CustomerExperienceCenter.Favorites
4 @using Dynamicweb.Ecommerce.Products.FieldDisplayGroups
5 @using Dynamicweb.Frontend
6 @using System.Drawing
7 @using System.Text.RegularExpressions
8 @using Danitech.Website.CustomModules.Helpers
9 @using System.Configuration;
10 @using Newtonsoft.Json
11
12 @functions {
13 //Find contrast color (white, black)
14 public static string GetContrastColor(string hexString)
15 {
16 System.Drawing.Color bg = System.Drawing.ColorTranslator.FromHtml(hexString);
17
18 int nThreshold = 105;
19 int bgDelta = Convert.ToInt32((bg.R * 0.299) + (bg.G * 0.587) +
20 (bg.B * 0.114));
21
22 string foreColor = (255 - bgDelta < nThreshold) ? "#333" : "#fff";
23 return foreColor;
24 }
25 }
26
27 @functions{
28 private dynamic GetQuantityDiscount(string productNumber)
29 {
30 if (Dynamicweb.Environment.CookieManager.GetCookie(ConfigurationManager.AppSettings["DanitechAPIJWTTokenName"]) == null)
31 {
32 return null;
33 }
34
35 var client = new System.Net.Http.HttpClient();
36 var request = new System.Net.Http.HttpRequestMessage
37 {
38 Method = System.Net.Http.HttpMethod.Get,
39 RequestUri = new Uri(ConfigurationManager.AppSettings["DanitechAPIURL"] + "User/discount/getquantitydiscounts?productNumbers=" + productNumber),
40 Headers =
41 {
42 { "Authorization", $"Bearer {Dynamicweb.Environment.CookieManager.GetCookie(ConfigurationManager.AppSettings["DanitechAPIJWTTokenName"]).Value}" },
43 },
44 };
45
46 string result = "";
47 using (var response = client.SendAsync(request).Result)
48 {
49 if (response.IsSuccessStatusCode)
50 {
51 result = response.Content.ReadAsStringAsync().Result;
52 }
53 }
54
55 if (string.IsNullOrEmpty(result))
56 {
57 return null;
58 }
59 else
60 {
61 return JsonConvert.DeserializeObject<dynamic>(result);
62 }
63 }
64 }
65
66 @functions{
67 private string PriceFormatted(string price)
68 {
69 if (decimal.TryParse(price, out decimal result))
70 {
71 return result.ToString("N2");
72 }
73
74 return "-";
75 }
76 }
77
78 @{
79 ProductViewModel product = new ProductViewModel();
80
81 if (Dynamicweb.Context.Current.Items.Contains("ProductDetails"))
82 {
83 product = (ProductViewModel)Dynamicweb.Context.Current.Items["ProductDetails"];
84 }
85
86 if (Pageview.User == null || string.IsNullOrEmpty(Pageview.User.CustomerNumber))
87 {
88 StockHelper stockHelper = new StockHelper();
89 double? stock = stockHelper.GetStock(product.Number).GetAwaiter().GetResult();
90
91 if (stock != null)
92 {
93 product.StockLevel = stock;
94 }
95 }
96
97 string anonymousUsersLimitations = Pageview.AreaSettings.GetRawValueString("AnonymousUsers", "");
98 bool anonymousUser = Pageview.User == null;
99 bool hideAddToCart = anonymousUsersLimitations.Contains("cart") && anonymousUser;
100 hideAddToCart = product.VariantInfo.VariantInfo != null && Model.Item.GetBoolean("HideVariantSelector") ? true : hideAddToCart;
101 bool hidePrice = anonymousUsersLimitations.Contains("price") && anonymousUser;
102 bool hideFavoritesSelector = !string.IsNullOrEmpty(Model.Item.GetString("HideFavoritesSelector")) ? Model.Item.GetBoolean("HideFavoritesSelector") : false;
103
104 bool isDiscontinued = product.Discontinued;
105 bool IsNeverOutOfStock = product.NeverOutOfstock;
106 string[] variantId = product.VariantId.Split('.');
107 string disableAddToCart = (product.StockLevel <= 0) ? "disabled" : "";
108 disableAddToCart = isDiscontinued ? "disabled" : disableAddToCart;
109 disableAddToCart = IsNeverOutOfStock ? "" : disableAddToCart;
110
111 // Does product has a expected delivery data
112 bool hasExpectedDelivery = product.ExpectedDelivery != null && product.ExpectedDelivery > DateTime.Now;
113 string expectedDeliveryDate = product.ExpectedDelivery?.ToShortDateString() ?? "";
114
115 string url = "/Default.aspx?ID=" + (GetPageIdByNavigationTag("CartService"));
116 if (!url.Contains("LayoutTemplate"))
117 {
118 url += url.Contains("?") ? "&LayoutTemplate=Swift_MiniCart.cshtml" : "?LayoutTemplate=Swift_MiniCart.cshtml";
119 }
120
121 IEnumerable<string> selectedDisplayGroups = Model.Item.GetRawValueString("MainFeatures").Split(',').ToList();
122 List<CategoryFieldViewModel> mainFeatures = new List<CategoryFieldViewModel>();
123
124 foreach (var selection in selectedDisplayGroups)
125 {
126 foreach (CategoryFieldViewModel group in product.FieldDisplayGroups.Values)
127 {
128 if (selection == group.Id)
129 {
130 mainFeatures.Add(group);
131 }
132 }
133 }
134
135 string theme = !string.IsNullOrWhiteSpace(Model.Item.GetRawValueString("Theme")) ? " theme " + Model.Item.GetRawValueString("Theme").Replace(" ", "").Trim().ToLower() : "";
136
137 string titleFontSize = Model.Item.GetRawValueString("TitleFontSize", "display-6");
138
139 string contentPadding = Model.Item.GetRawValueString("ContentPadding", "");
140 contentPadding = contentPadding == "small" ? "p-2 p-md-3" : contentPadding;
141 contentPadding = contentPadding == "large" ? "p-4 p-md-5" : contentPadding;
142
143 string quantityPricesLayout = Model.Item.GetRawValueString("QuantityPricesLayout", "list");
144
145 string minQty = product.PurchaseMinimumQuantity != 1 ? "min=\"" + product.PurchaseMinimumQuantity.ToString() + "\"" : "min=\"1\"";
146 string stepQty = product.PurchaseQuantityStep > 1 ? product.PurchaseQuantityStep.ToString() : "1";
147 string valueQty = product.PurchaseMinimumQuantity > product.PurchaseQuantityStep ? product.PurchaseMinimumQuantity.ToString() : stepQty;
148 string qtyValidCheck = stepQty != "1" ? "onkeyup=\"swift.Cart.QuantityValidate(event)\"" : "";
149
150 string showPricesWithVat = Pageview.Area.EcomPricesWithVat.ToLower();
151 bool neverShowVat = string.IsNullOrEmpty(showPricesWithVat);
152
153 string priceMin = "";
154 string priceMax = "";
155
156 var favoriteParameters = new Dictionary<string, object>();
157 if (!anonymousUser && !hideFavoritesSelector)
158 {
159 IEnumerable<FavoriteList> favoreiteLists = Pageview.User.GetFavoriteLists();
160 int defaultFavoriteListId = 0;
161
162 if (favoreiteLists.Count() == 1)
163 {
164 foreach (FavoriteList list in favoreiteLists)
165 {
166 defaultFavoriteListId = list.ListId;
167 }
168 }
169
170 favoriteParameters.Add("ListId", defaultFavoriteListId);
171 }
172
173 var badgeParms = new Dictionary<string, object>();
174 badgeParms.Add("size", "h7");
175 badgeParms.Add("saleBadgeType", Model.Item.GetRawValue("SaleBadgeType"));
176 badgeParms.Add("saleBadgeCssClassName", Model.Item.GetRawValue("SaleBadgeDesign"));
177 badgeParms.Add("newBadgeCssClassName", Model.Item.GetRawValue("NewBadgeDesign"));
178 badgeParms.Add("newPublicationDays", Model.Item.GetInt32("NewPublicationDays"));
179 badgeParms.Add("campaignBadgesValues", Model.Item.GetRawValueString("CampaignBadges"));
180
181 bool saleBadgeEnabled = !string.IsNullOrWhiteSpace(Model.Item.GetRawValueString("SaleBadgeDesign")) && Model.Item.GetRawValueString("SaleBadgeDesign") != "none" ? true : false;
182 bool newBadgeEnabled = !string.IsNullOrWhiteSpace(Model.Item.GetRawValueString("NewBadgeDesign")) && Model.Item.GetRawValueString("NewBadgeDesign") != "none" ? true : false;
183 DateTime createdDate = product.Created.Value;
184 bool showBadges = saleBadgeEnabled && product.Discount.Price != 0 ? true : false;
185 showBadges = (newBadgeEnabled && Model.Item.GetInt32("NewPublicationDays") == 0) || (newBadgeEnabled && (createdDate.AddDays(Model.Item.GetInt32("NewPublicationDays")) > DateTime.Now)) ? true : showBadges;
186 showBadges = !string.IsNullOrEmpty(Model.Item.GetRawValueString("CampaignBadges")) ? true : showBadges;
187
188 dynamic quantityDiscounts = null;
189 bool quantityDiscountsHasValues = false;
190
191 if (!anonymousUser)
192 {
193 quantityDiscounts = GetQuantityDiscount(product.Number);
194 quantityDiscountsHasValues = quantityDiscounts != null && quantityDiscounts.productDiscounts != null && quantityDiscounts.productDiscounts.HasValues && quantityDiscounts.productDiscounts[0].quantityDiscounts.Count > 0;
195 }
196 }
197 <div class="h-100 @(contentPadding) @(theme) item_@Model.Item.SystemName.ToLower()">
198 <div class="d-flex flex-column gap-4 js-product">
199 @if (showBadges)
200 {
201 <div class="swift_badge-collection">
202 @RenderPartial("Components/EcommerceBadge.cshtml", product, badgeParms)
203 </div>
204 }
205
206 @RenderProductNameAndManufacturerLogo(product, titleFontSize, Model.Item.GetBoolean("HideProductNumber"))
207
208 @if (!hidePrice && !isDiscontinued)
209 {
210 <div>
211 <div class="h4" itemprop="offers" itemscope itemtype="https://schema.org/Offer">
212 <span itemprop="priceCurrency" content="@product.Price.CurrencyCode" class="d-none"></span>
213
214 <div>@Translate("SRP")</div>
215
216 @if (showPricesWithVat == "false" && !neverShowVat)
217 {
218 var itemPropPrice = 0d;
219 var srpPriceFormatted = "-";
220
221 if (product.Prices.Count > 1)
222 {
223 var minPrice = product.Prices.Min(p => p.Price);
224 var maxPrice = product.Prices.Max(p => p.Price);
225
226 srpPriceFormatted = minPrice != maxPrice ? minPrice.PriceWithoutVatFormatted + " - " + maxPrice.PriceWithoutVatFormatted : minPrice.PriceWithoutVatFormatted;
227
228 itemPropPrice = minPrice.PriceWithoutVat;
229 }
230 else
231 {
232 srpPriceFormatted = product.Prices[0].Price.PriceWithoutVatFormatted;
233
234 itemPropPrice = product.Prices[0].Price.PriceWithoutVat;
235 }
236
237 <span itemprop="price" content="@itemPropPrice" class="d-none"></span>
238 <span class="text-price">@srpPriceFormatted</span>
239 }
240 else
241 {
242 var itemPropPrice = 0d;
243 var srpPriceFormatted = "-";
244
245 if (product.Prices.Count > 1)
246 {
247 var minPrice = product.Prices.Min(p => p.Price);
248 var maxPrice = product.Prices.Max(p => p.Price);
249
250 srpPriceFormatted = minPrice != maxPrice ? minPrice.PriceFormatted + " - " + maxPrice.PriceFormatted : minPrice.PriceFormatted;
251 itemPropPrice = minPrice.Price;
252 }
253 else
254 {
255 srpPriceFormatted = product.Prices[0].Price.PriceFormatted;
256 itemPropPrice = product.Prices[0].Price.Price;
257 }
258
259 <span itemprop="price" content="@itemPropPrice" class="d-none"></span>
260 <span class="text-price">@srpPriceFormatted</span>
261 }
262 </div>
263 @if (showPricesWithVat == "false" && !neverShowVat)
264 {
265 var price = "-";
266
267 if (product.Prices.Count > 1)
268 {
269 var minPrice = product.Prices.Min(p => p.Price);
270 var maxPrice = product.Prices.Max(p => p.Price);
271
272 price = minPrice != maxPrice ? minPrice.PriceWithVatFormatted + " - " + maxPrice.PriceWithVatFormatted : minPrice.PriceWithVatFormatted;
273 }
274 else
275 {
276 price = product.Prices[0].Price.PriceWithVatFormatted;
277 }
278
279 <small class="opacity-85 fst-normal">@price @Translate("Incl. VAT")</small>
280 }
281
282 @if (quantityDiscountsHasValues)
283 {
284 <div class="standalone__quantitydiscount">
285 <h2>@Translate("Your prices")</h2>
286
287 @if (quantityPricesLayout == "list")
288 {
289 <div class="mt-3">
290 @foreach (var quantityPrice in quantityDiscounts.productDiscounts[0].quantityDiscounts)
291 {
292 string quantityLabel = Translate("PCS");
293
294 <small class="d-block opacity-75"><span>@quantityPrice.minimumQuantity @quantityLabel</span> - <span>@quantityPrice.discountPercent<text>%</text></span> - <span class="fw-bold">@PriceFormatted(quantityPrice.discountedPrice.ToString()) @quantityPrice.currencyCode</span></small>
295 }
296 </div>
297 }
298 else if (quantityPricesLayout == "table")
299 {
300 <div class="grid">
301 <table class="table table-striped mt-3 g-col-12 g-col-lg-6">
302 <thead>
303 <tr>
304 <th>@Translate("Min quantity")</th>
305 <th>@Translate("Discount percent")</th>
306 <th>@Translate("PCS price excl VAT")</th>
307 </tr>
308 </thead>
309 <tbody>
310 @foreach (var quantityPrice in quantityDiscounts.productDiscounts[0].quantityDiscounts)
311 {
312 <tr>
313 <td>@quantityPrice.minimumQuantity</td>
314 <td>@quantityPrice.discountPercent<text>%</text></td>
315 <td>@quantityPrice.discountedPrice @quantityPrice.currencyCode</td>
316 </tr>
317 }
318 </tbody>
319 </table>
320 </div>
321 }
322 </div>
323 }
324 </div>
325 }
326
327 @if (!string.IsNullOrEmpty(product.ShortDescription))
328 {
329 <div class="mb-0-last-child" itemprop="disambiguatingDescription">
330 @product.ShortDescription
331 </div>
332 }
333
334 @if (mainFeatures.Count > 0)
335 {
336 foreach (CategoryFieldViewModel mainFeatureGroup in mainFeatures)
337 {
338 <dl class="grid gap-0">
339 @foreach (var field in mainFeatureGroup.Fields)
340 {
341 @RenderField(field.Value)
342 }
343 </dl>
344 }
345 }
346
347 @if (product.VariantInfo.VariantInfo != null && !Model.Item.GetBoolean("HideVariantSelector"))
348 {
349 int groupNumber = 1;
350
351 <form class="mb-3 js-variant-selector" data-combinations="@string.Join(",", product.VariantCombinations())" id="VariantSelector_@Model.ID">
352 <input type="hidden" name="variantid" />
353
354 @foreach (var variantGroup in product.VariantGroups())
355 {
356 VariantGroupViewModel group = variantGroup;
357
358 <h3 class="h6">@group.Name</h3>
359 <div class="mb-3 js-variant-group" data-group-id="@groupNumber">
360 @foreach (var option in group.Options)
361 {
362 string active = variantId.Contains(option.Id) ? "active" : "";
363
364 if (!string.IsNullOrEmpty(option.Color))
365 {
366 string contrastColor = GetContrastColor(option.Color);
367 <button type="button" class="btn colorbox rounded-circle me-1 mb-2 d-inline-block variant-option border js-variant-option @active" style="background-color: @option.Color --variantoption-check-color: @contrastColor" onclick="swift.VariantSelector.OptionClick(event)" data-variant-id="@option.Id" id="@(product.Id)_@(option.Id)_@Pageview.CurrentParagraph.ID"></button>
368 }
369 else if (!string.IsNullOrEmpty(option.Color) && !string.IsNullOrEmpty(option.Image.Value))
370 {
371 <button type="button" class="btn p-0 d-inline-block mb-2 variant-option border js-variant-option @active" onclick="swift.VariantSelector.OptionClick(event)" data-variant-id="@option.Id" id="@(product.Id)_@(option.Id)_@Pageview.CurrentParagraph.ID">
372 <img src="/Admin/Public/GetImage.ashx?image=@(option.Image.Value)&width=42&Format=WebP&Quality=70" />
373 </button>
374 }
375 else
376 {
377 <button type="button" class="btn btn-secondary d-inline-block mb-2 variant-option js-variant-option @active" onclick="swift.VariantSelector.OptionClick(event)" data-variant-id="@option.Id" id="@(product.Id)_@(option.Id)_@Pageview.CurrentParagraph.ID">
378 @option.Name
379 </button>
380 }
381 }
382 </div>
383
384 groupNumber++;
385 }
386 </form>
387 }
388
389 <div class="d-flex flex-row flex-nowrap gap-2" id="AddToCart_@Model.ID">
390 @if (!hideAddToCart && !isDiscontinued)
391 {
392 <form method="post" action="@url" class="flex-fill">
393 <input type="hidden" name="redirect" value="false" />
394 <input type="hidden" name="ProductId" value="@product.Id" />
395 <input type="hidden" name="cartcmd" value="add" />
396
397 @if (!string.IsNullOrEmpty(product.VariantId))
398 {
399 <input type="hidden" name="VariantId" value="@product.VariantId" />
400 }
401 @if (!Model.Item.GetBoolean("QuantitySelector"))
402 {
403 <input id="Quantity_@product.Id" name="Quantity" value="@valueQty" type="hidden">
404 <button type="button" onclick="swift.Cart.Update(event)" class="btn btn-primary w-100 js-add-to-cart-button @disableAddToCart" @disableAddToCart title="@Translate("Add to cart")" id="AddToCartButton@(product.Id)_@Pageview.CurrentParagraph.ID">@Translate("Add to cart")</button>
405 }
406 else
407 {
408 <div class="input-group input-primary-button-group js-input-group d-flex flex-row flex-nowrap">
409 <label for="Quantity_@(product.Id)" class="visually-hidden">@Translate("Quantity")</label>
410 <input id="Quantity_@product.Id" name="Quantity" value="@valueQty" step="@stepQty" @minQty class="form-control" style="max-width: 96px; min-width:64px;" type="number">
411 <button type="button" onclick="swift.Cart.Update(event)" class="btn btn-primary flex-fill js-add-to-cart-button @disableAddToCart" @disableAddToCart title="@Translate("Add to cart")" id="AddToCartButton@(product.Id)_@Pageview.CurrentParagraph.ID">@Translate("Add to cart")</button>
412 </div>
413
414 if (stepQty != "1")
415 {
416 <div class="invalid-feedback d-none">
417 @Translate("Please select a quantity that is dividable by") @stepQty
418 </div>
419 }
420 }
421 </form>
422 if (!anonymousUser && !hideFavoritesSelector)
423 {
424 @RenderPartial("Components/ToggleFavorite.cshtml", product, favoriteParameters)
425 }
426 }
427 else if (!anonymousUser && !hideFavoritesSelector && !isDiscontinued)
428 {
429 <div class="flex-fill" id="AddToFavorites_@Model.ID">
430 @Translate("Add to favorites") @RenderPartial("Components/ToggleFavorite.cshtml", product, favoriteParameters)
431 </div>
432 }
433
434 @if (isDiscontinued && product.ReplacementProduct != null)
435 {
436 List<ProductInfoViewModel> replacementProductList = new List<ProductInfoViewModel>();
437 replacementProductList.Add(product.ReplacementProduct);
438 var replacementProduct = replacementProductList.GetProducts().FirstOrDefault();
439
440 if ((product.DiscontinuedAction == 0 || product.DiscontinuedAction == 1) && product?.ReplacementProduct.ProductId != null)
441 {
442 var parms = new Dictionary<string, object>();
443 parms.Add("cssClass", "d-block mw-100 mh-100 m-auto");
444 parms.Add("fullwidth", true);
445 parms.Add("columns", Model.GridRowColumnCount);
446
447 string imagePath = replacementProduct?.DefaultImage?.Value != null ? replacementProduct.DefaultImage.Value : "";
448
449 var defaultGroupId = replacementProduct.PrimaryOrDefaultGroup.Id;
450 var selectedDetailPage = Dynamicweb.Ecommerce.Services.ProductGroups.GetGroup(defaultGroupId)?.Meta.PrimaryPage ?? string.Empty;
451
452 string link = string.IsNullOrEmpty(selectedDetailPage) ? $"/Default.aspx?ID={Pageview.Page.ID}&groupid={defaultGroupId}" : selectedDetailPage;
453 link += "&productid=" + replacementProduct.Id;
454 link += !string.IsNullOrEmpty(replacementProduct.VariantId) ? "&variantid=" + replacementProduct.VariantId : "";
455
456 <div class="w-100">
457 <div class="fw-bold w-100">@Translate("Sorry, this product is no longer available").</div>
458 <div>@Translate("We recommend this replacement product instead"):</div>
459
460 <a href="@link">
461 @RenderPartial("Components/Image.cshtml", new FileViewModel { Path = imagePath }, parms)
462 </a>
463
464 <div>@replacementProduct.Name</div>
465
466 @if (!hidePrice)
467 {
468 <div class="mb-3">
469 <div class="h4" itemprop="offers" itemscope itemtype="https://schema.org/Offer">
470 @if (showPricesWithVat == "false" && !neverShowVat)
471 {
472 string beforePrice = product.PriceBeforeDiscount.PriceWithoutVatFormatted;
473
474 <span itemprop="price" content="@product.Price.PriceWithoutVat" class="d-none"></span>
475 if (product.Price.Price != product.PriceBeforeDiscount.Price)
476 {
477 <span class="text-decoration-line-through opacity-75 me-3">@beforePrice</span>
478 }
479 }
480 else
481 {
482 string beforePrice = product.PriceBeforeDiscount.PriceFormatted;
483
484 <span itemprop="price" content="@product.Price.Price" class="d-none"></span>
485 if (product.Price.Price != product.PriceBeforeDiscount.Price)
486 {
487 <span class="text-decoration-line-through opacity-75 me-3">@beforePrice</span>
488 }
489 }
490
491 @if (showPricesWithVat == "false" && !neverShowVat)
492 {
493 string price = product.Price.PriceWithoutVatFormatted;
494 if (product?.VariantInfo?.VariantInfo != null)
495 {
496 priceMin = product?.VariantInfo?.PriceMin?.PriceWithoutVatFormatted != null ? product.VariantInfo.PriceMin.PriceWithoutVatFormatted : "";
497 priceMax = product?.VariantInfo?.PriceMax?.PriceWithoutVatFormatted != null ? product.VariantInfo.PriceMax.PriceWithoutVatFormatted : "";
498 }
499 if (priceMin != priceMax)
500 {
501 price = priceMin + " - " + priceMax;
502 }
503 <span class="text-price">@price</span>
504 }
505 else
506 {
507 string price = product.Price.PriceFormatted;
508 if (product?.VariantInfo?.VariantInfo != null)
509 {
510 priceMin = product?.VariantInfo?.PriceMin?.PriceFormatted != null ? product.VariantInfo.PriceMin.PriceFormatted : "";
511 priceMax = product?.VariantInfo?.PriceMax?.PriceFormatted != null ? product.VariantInfo.PriceMax.PriceFormatted : "";
512 }
513 if (priceMin != priceMax)
514 {
515 price = priceMin + " - " + priceMax;
516 }
517 <span class="text-price">@price</span>
518 }
519 </div>
520
521 @if (showPricesWithVat == "false" && !neverShowVat)
522 {
523 string price = product.Price.PriceWithVatFormatted;
524 if (product?.VariantInfo?.VariantInfo != null)
525 {
526 priceMin = product?.VariantInfo?.PriceMin?.PriceWithVatFormatted != null ? product.VariantInfo.PriceMin.PriceWithVatFormatted : "";
527 priceMax = product?.VariantInfo?.PriceMax?.PriceWithVatFormatted != null ? product.VariantInfo.PriceMax.PriceWithVatFormatted : "";
528 }
529 if (priceMin != priceMax)
530 {
531 price = priceMin + " - " + priceMax;
532 }
533 <small class="opacity-85 fst-normal">@price @Translate("Incl. VAT")</small>
534 }
535 </div>
536 }
537
538 <a href="@link" class="btn btn-primary w-100">@Translate("Go to the replacement")</a>
539 </div>
540 }
541 }
542 </div>
543 </div>
544 @if (!Model.Item.GetBoolean("HideStockState"))
545 {
546 if (!IsNeverOutOfStock && !isDiscontinued)
547 {
548 <div class="mt-3 js-stock-state">
549
550 @if (product.StockLevel > 0)
551 {
552 if (!Model.Item.GetBoolean("HideInventory"))
553 {
554 <p class="small text-success m-0">@product.StockLevel @Translate("Products available in stock")</p>
555 }
556 else
557 {
558 <p class="small text-success m-0">@Translate("Available in stock")</p>
559 }
560 }
561
562 else
563 {
564 <p class="small text-danger m-0">@Translate("Out of Stock")</p>
565 }
566
567 @if (hasExpectedDelivery)
568 {
569 <p>
570 <span>@Translate("Expected back in stock:")</span>
571 <span>@expectedDeliveryDate</span>
572 </p>
573 }
574
575 </div>
576 }
577 }
578 </div>
579
580 @helper RenderField(FieldValueViewModel field)
581 {
582 string fieldValue = field?.Value != null ? field.Value.ToString() : "";
583 bool noValues = false;
584
585 if (!string.IsNullOrEmpty(fieldValue))
586 {
587 if (field.Value.GetType() == typeof(System.Collections.Generic.List<FieldOptionValueViewModel>))
588 {
589 System.Collections.Generic.List<FieldOptionValueViewModel> values = field.Value as System.Collections.Generic.List<FieldOptionValueViewModel>;
590 noValues = values.Count > 0 ? false : true;
591 }
592 }
593
594 if (!string.IsNullOrEmpty(fieldValue) && noValues == false)
595 {
596 <dt class="g-col-12 g-col-sm-4 g-col-lg-12 fw-bold m-0">@field.Name</dt>
597 <dd class="g-col-12 g-col-sm-8 g-col-lg-12 mb-3">
598 @RenderFieldValue(field)
599 </dd>
600 }
601 }
602
603 @helper RenderFieldValue(FieldValueViewModel field)
604 {
605 string fieldValue = field?.Value != null ? field.Value.ToString() : "";
606
607 fieldValue = fieldValue == "False" ? Translate("No") : fieldValue;
608 fieldValue = fieldValue == "True" ? Translate("Yes") : fieldValue;
609
610 bool isColor = false;
611
612 if (field.Value.GetType() == typeof(System.Collections.Generic.List<Dynamicweb.Ecommerce.ProductCatalog.FieldOptionValueViewModel>))
613 {
614 int valueCount = 0;
615 System.Collections.Generic.List<FieldOptionValueViewModel> values = field.Value as System.Collections.Generic.List<FieldOptionValueViewModel>;
616 int totalValues = values.Count;
617
618 foreach (FieldOptionValueViewModel option in values)
619 {
620 if (option.Value.Substring(0, 1) == "#")
621 {
622 isColor = true;
623 }
624
625 if (!isColor)
626 {
627 @option.Name
628 }
629 else
630 {
631 <span class="colorbox-sm" style="background-color: @option.Value" title="@option.Value"></span>
632 }
633
634 if (valueCount != totalValues && valueCount < (totalValues - 1))
635 {
636 if (isColor)
637 {
638 <text> </text>
639 }
640 else
641 {
642 <text>, </text>
643 }
644 }
645 valueCount++;
646 }
647 }
648 else
649 {
650 if (fieldValue.Substring(0, 1) == "#")
651 {
652 isColor = true;
653 }
654
655 if (!isColor)
656 {
657 @fieldValue
658 }
659 else
660 {
661 <span class="colorbox-sm" style="background-color: @fieldValue" title="@fieldValue"></span>
662 }
663 }
664 }
665
666 @if (product.VariantInfo.VariantInfo != null)
667 {
668 <script type="module">
669 swift.VariantSelector.init();
670 </script>
671 }
672
673
674 @helper RenderProductNameAndManufacturerLogo(ProductViewModel product, string titleFontSize, bool hideProductNumber)
675 {
676 <div>
677 @if (product.ProductFields != null && product.ProductFields.ContainsKey("BrandLogo"))
678 {
679 var brandLogo = product.ProductFields["BrandLogo"];
680 if (brandLogo != null && !string.IsNullOrEmpty(brandLogo.Value.ToString()) && Regex.IsMatch(brandLogo.Value.ToString(), @"(.png|.jpg|.jpeg)$", RegexOptions.IgnoreCase))
681 {
682 <div class="d-flex flex-row-reverse">
683 <div class="p-2">
684 <img src="/Admin/Public/GetImage.ashx?image=@brandLogo.Value&width=100&format=webp" />
685 </div>
686 </div>
687 }
688 }
689 <div>
690 <h1 class="@titleFontSize" itemprop="name">@product.Name</h1>
691 @if (!hideProductNumber)
692 {
693 <div class="opacity-85">@product.Number</div>
694 }
695 </div>
696 </div>
697 }