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