1 @inherits Dynamicweb.Rendering.ViewModelTemplate<Dynamicweb.Frontend.ParagraphViewModel>
2 @using Dynamicweb.Ecommerce.ProductCatalog
3 @using Dynamicweb.Core.Encoders
4 @using System.Globalization
5
6 @functions {
7 string? DoubleToString(double? value)
8 {
9 if (value.HasValue)
10 {
11 return value.Value.ToString(CultureInfo.InvariantCulture);
12 }
13 return null;
14 }
15 }
16
17 @{
18 bool isProductDetailPage = Dynamicweb.Context.Current.Request.QueryString.AllKeys?.Contains("ProductId") ?? false;
19 ProductViewModel product = null;
20 if (Dynamicweb.Context.Current.Items.Contains("ProductDetails"))
21 {
22 product = (ProductViewModel)Dynamicweb.Context.Current.Items["ProductDetails"];
23 }
24 else if (!string.IsNullOrEmpty(Pageview.Page.Item["DummyProduct"]?.ToString()) && Pageview.IsVisualEditorMode)
25 {
26 var pageViewModel = Dynamicweb.Frontend.ContentViewModelFactory.CreatePageInfoViewModel(Pageview.Page);
27 ProductListViewModel productList = pageViewModel.Item.GetValue("DummyProduct") != null ? pageViewModel.Item.GetValue("DummyProduct") as ProductListViewModel : new ProductListViewModel();
28
29 if (productList?.Products is object)
30 {
31 product = productList.Products[0];
32 }
33 }
34 else if (Pageview.IsVisualEditorMode)
35 {
36 product = new ProductViewModel();
37 product.Id = "1";
38 product.VariantId = "394041";
39 product.PurchaseMinimumQuantity = 1;
40 product.PurchaseQuantityStep = 1;
41 product.StockLevel = 10;
42 product.DefaultUnitId = "1";
43 product.ProductType = Dynamicweb.Ecommerce.Products.ProductType.Stock;
44 product.NeverOutOfstock = false;
45 product.Discontinued = false;
46 product.Price = new PriceViewModel()
47 {
48 Price = 99,
49 PriceFormatted = "99 " + Pageview.Area.EcomCurrencyId,
50 PriceWithoutVat = 99,
51 PriceWithoutVatFormatted = "99 " + Pageview.Area.EcomCurrencyId,
52 PriceWithVat = 99,
53 PriceWithVatFormatted = "99 " + Pageview.Area.EcomCurrencyId
54 };
55 }
56
57 string anonymousUsersLimitations = Pageview.AreaSettings.GetRawValueString("AnonymousUsers", "");
58 bool anonymousUser = Pageview.User == null;
59
60 bool hideAddToCart = anonymousUsersLimitations.Contains("cart") && anonymousUser;
61 hideAddToCart = Pageview.IsVisualEditorMode ? false : hideAddToCart;
62
63 bool hasEcoLabel = true;
64 string[] ecoLabelKeys = {
65 "EcoLabelShoppingWithCare1",
66 "EcoLabelShoppingWithCare2",
67 "EcoLabelShoppingWithCare3",
68 "EcoLabelShoppingWithCare4",
69 "EcoLabelShoppingWithCare5"
70 };
71 if (!Pageview.IsVisualEditorMode)
72 {
73 hasEcoLabel = ecoLabelKeys.Any(key =>
74 product.ProductFields.TryGetValue(key, out var field) &&
75 field?.Value?.ToString() == "1"
76 );
77 }
78 }
79 @if (product is object && hideAddToCart)
80 {
81 string link = product.GetProductLink(GetPageIdByNavigationTag("Shop"), false);
82 string ReplacementProduct = !string.IsNullOrEmpty(product.ReplacementProduct?.ProductId) ? product.ReplacementProduct.GetProduct()?.GetProductLink(GetPageIdByNavigationTag("Shop"), false) ?? "" : ""; link = !string.IsNullOrEmpty(ReplacementProduct) ? ReplacementProduct : link;
83 @if (!isProductDetailPage)
84 {
85 <div class="d-flex align-items-center p-0">
86 @if (hasEcoLabel)
87 {
88 <img class="eco-label mx-3" src="/Files/Images/EcoLabel/EcoLabel.png" alt="EcoLable" />
89 }
90 <a href="@link" class="btn btn-secondary" data-dw-button="secondary" style="white-space: nowrap" title="@Translate("Se mere")" id="AddToCartButton@(product.Id)
[email protected]">
91 <span class="text-nowrap d-flex align-items-center justify-content-center gap-2">
92 @Translate("Se mere")
93 </span>
94 </a>
95 </div>
96 }
97 }
98 @if (product is object && !hideAddToCart)
99 {
100 string horizontalAlign = Model.Item.GetRawValueString("HorizontalAlignment", "");
101 horizontalAlign = horizontalAlign == "center" ? "justify-content-center" : horizontalAlign;
102 horizontalAlign = horizontalAlign == "end" ? "justify-content-end" : horizontalAlign;
103 horizontalAlign = horizontalAlign == "full" ? "" : horizontalAlign;
104
105 bool favoritesSelector = !string.IsNullOrEmpty(Model.Item.GetString("ShowAddToFavorites")) ? Model.Item.GetBoolean("ShowAddToFavorites") : false;
106 bool quantitySelector = !string.IsNullOrEmpty(Model.Item.GetString("ShowQuantitySelector")) ? Model.Item.GetBoolean("ShowQuantitySelector") : false;
107 bool unitsSelector = !string.IsNullOrEmpty(Model.Item.GetString("ShowUnitsSelector")) ? Model.Item.GetBoolean("ShowUnitsSelector") : false;
108 bool hideInventory = !string.IsNullOrEmpty(Model.Item.GetString("HideInventory")) ? Model.Item.GetBoolean("HideInventory") : false;
109 bool hideStockState = !string.IsNullOrEmpty(Model.Item.GetString("HideStockState")) ? Model.Item.GetBoolean("HideStockState") : false;
110
111 string iconPath = "/Files/Images/Icons/";
112 string url = "/Default.aspx?ID=" + (GetPageIdByNavigationTag("CartService"));
113 if (!url.Contains("LayoutTemplate"))
114 {
115 url += url.Contains("?") ? "&LayoutTemplate=Swift-v2_MiniCart.cshtml" : "?LayoutTemplate=Swift-v2_MiniCart.cshtml";
116 }
117
118 string whenVariantsExist = Model.Item.GetRawValueString("WhenVariantsExist", "hide");
119 string flexFill = Model.Item.GetRawValueString("HorizontalAlignment", "") == "full" ? "flex-fill" : "";
120 string fullWidth = Model.Item.GetRawValueString("HorizontalAlignment", "") == "full" ? "w-100" : "";
121 string addToCartIcon = Model.Item.GetRawValueString("Icon", iconPath + "cart-shopping.svg");
122 string addToCartLabel = !addToCartIcon.Contains("_none") ? $"<span class=\"icon-2\">{ReadFile(addToCartIcon)}</span>" : "";
123 addToCartLabel += !addToCartIcon.Contains("_none") && !Model.Item.GetBoolean("HideButtonText") ? " " : "";
124 addToCartLabel += !Model.Item.GetBoolean("HideButtonText") ? $"<span class=\"d-none d-md-inline\">{Translate("Add to cart")}</span><span class=\"d-inline d-md-none\">{Translate("Add")}</span>" : "";
125 bool userHasPendingQuote = Dynamicweb.Ecommerce.Common.Context.Cart != null && Dynamicweb.Ecommerce.Common.Context.Cart.IsQuote;
126
127 bool hasReplacementProduct = !string.IsNullOrEmpty(product.ReplacementProduct?.ProductId);
128 string replacementProductLink = "";
129 if (hasReplacementProduct)
130 {
131 replacementProductLink = product.ReplacementProduct.GetProduct().GetProductLink(GetPageIdByNavigationTag("Shop"), false);
132 }
133
134 if (product.VariantInfo?.VariantInfo == null || whenVariantsExist == "disable")
135 {
136 string unitId = !string.IsNullOrEmpty(Dynamicweb.Context.Current.Request.Form.Get("UnitId")) ? Dynamicweb.Context.Current.Request.Form.Get("UnitId") : product.DefaultUnitId;
137 if (string.IsNullOrEmpty(unitId) && product?.UnitOptions != null)
138 {
139 if (product.UnitOptions?.FirstOrDefault<UnitOptionViewModel>() != null)
140 {
141 unitId = product.UnitOptions.FirstOrDefault<UnitOptionViewModel>().Id;
142 }
143 }
144
145 double? stepQty = product.PurchaseQuantityStep > 0 ? product.PurchaseQuantityStep : 1;
146 double? minQty = product.PurchaseMinimumQuantity > 0 ? product.PurchaseMinimumQuantity : 1;
147 double? valueQty = minQty > stepQty ? minQty : stepQty;
148 string? disableAddToCart = null;
149 double? maxQty = null;
150
151 if (Dynamicweb.Context.Current.Items.Contains("ProductQuantity"))
152 {
153 valueQty = Convert.ToDouble(Dynamicweb.Context.Current.Items["ProductQuantity"].ToString());
154 }
155
156 if (product.ProductType == Dynamicweb.Ecommerce.Products.ProductType.Stock && !product.NeverOutOfstock)
157 {
158 disableAddToCart = product.StockLevel <= 0 ? "" : disableAddToCart;
159 maxQty = int.MaxValue; // Default to max int value product.StockLevel
160 }
161
162 disableAddToCart = whenVariantsExist == "disable" && product.VariantInfo?.VariantInfo != null && string.IsNullOrEmpty(product.VariantId) ? "disabled" : disableAddToCart;
163 disableAddToCart = product.Discontinued ? "disabled" : disableAddToCart;
164
165 if (unitsSelector && product.UnitOptions?.Count > 0)
166 {
167 <form method="post" action="/Default.aspx?ID=@(Pageview.Page.ID)&
[email protected]" id="UnitSelectorForm_@(product.Id)_@(product.VariantId?.Replace(".", "_"))
[email protected]">
168 <input type="hidden" name="redirect" value="false">
169 <input type="hidden" name="VariantID" value="@product.VariantId">
170 <input type="hidden" name="UnitID" class="js-unit-id" value="@unitId">
171 </form>
172 }
173
174 if (hasReplacementProduct && !string.IsNullOrEmpty(replacementProductLink))
175 {
176 <div class="d-flex @fullWidth js-input-group
[email protected]()">
177 <div class="d-flex @fullWidth @horizontalAlign flex-wrap flex-lg-nowrap gap-2">
178 <a href="@replacementProductLink"
179 class="btn btn-secondary btn-sm p-3 small btn-nordenta-blue @flexFill"
180 data-dw-button="secondary"
181 style="white-space: nowrap"
182 title="@Translate("The product is discontinued, see alternative")"
183 id="ReplacementProductButton@(product.Id)
[email protected]">
184 <span class="text-nowrap d-flex align-items-center justify-content-center gap-2">
185 @Translate("The product is discontinued, see alternative")
186 </span>
187 </a>
188 </div>
189 </div>
190 }
191 else
192 {
193 <div class="d-flex @fullWidth js-input-group
[email protected]()">
194 <div class="d-flex @fullWidth @horizontalAlign flex-wrap flex-lg-nowrap gap-2">
195 <form method="post" action="@url" class="@fullWidth" style="z-index: 1">
196 <input type="hidden" name="redirect" value="false">
197 <input type="hidden" name="ProductId" value="@product.Id">
198 <input type="hidden" name="ProductName" value="@HtmlEncoder.HtmlEncode(product.Name)">
199 <input type="hidden" name="ProductVariantName" value="@product.VariantName">
200 <input type="hidden" name="ProductCurrency" value="@Dynamicweb.Ecommerce.Common.Context.Currency.Code">
201 <input type="hidden" name="ProductPrice" value="@product.Price.ToStringInvariant()">
202 <input type="hidden" name="ProductDiscount" value="@product.Discount.ToStringInvariant()">
203 <input type="hidden" name="ProductReferer" value="component_ProductAddToCart">
204 <input type="hidden" name="cartcmd" value="add">
205 <input type="submit" class="d-none" onclick="event.preventDefault(); swift.Cart.Update(event)"> @* Fix for enterKey should not redirect to minicart page *@
206
207 @if (!string.IsNullOrEmpty(product.VariantId))
208 {
209 <input type="hidden" name="VariantId" value="@product.VariantId">
210 }
211
212 <template class="js-step-quantity-warning">
213 <div class="modal-header">
214 <h1 class="modal-title fs-5">@Translate("The quantity is not valid")</h1>
215 <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
216 </div>
217 <div class="modal-body">
218 @Translate("Please select a quantity that is dividable by") @stepQty
219 </div>
220 </template>
221
222 <template class="js-min-quantity-warning">
223 <div class="modal-header">
224 <h1 class="modal-title fs-5">@Translate("The product could not be added to the cart")</h1>
225 <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
226 </div>
227 <div class="modal-body">
228 @Translate("The quantity is not valid. You must buy at least") @product.PurchaseMinimumQuantity
229 </div>
230 </template>
231
232 <template class="js-value-missing-warning">
233 <div class="modal-header">
234 <h1 class="modal-title fs-5">@Translate("No amount specified")</h1>
235 <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
236 </div>
237 <div class="modal-body">
238 @Translate("Specify an amount to add to the cart")
239 </div>
240 </template>
241
242 @if (userHasPendingQuote)
243 {
244 <input type="hidden" name="PendingQuote" value="true">
245
246 <template class="js-pending-quote-notice">
247 <div class="modal-header">
248 <h1 class="modal-title fs-5">@Translate("Pending Quote")</h1>
249 <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="@Translate("Close")"></button>
250 </div>
251 <div class="modal-body">
252 @Translate("You need to complete your current quote or empty the cart before adding this product to cart.")
253 </div>
254 </template>
255 }
256
257 @if (quantitySelector || (!anonymousUser && product.VariantInfo?.VariantInfo != null) || (!anonymousUser && favoritesSelector))
258 {
259 <input type="hidden" id="Unit_@(product.Id)
[email protected]?.Replace(".", "_")" name="UnitID" value="@unitId" />
260 }
261
262 <div class="d-flex flex-wrap gap-2">
263 @if (!quantitySelector)
264 {
265 <input id="Quantity_@(product.Id)
[email protected]?.Replace(".", "_")" class="swift_quantity_field" name="Quantity" value="@valueQty" type="hidden" @disableAddToCart>
266 }
267
268 @if (unitsSelector && product.UnitOptions?.Count > 0)
269 {
270 string selectedUnitName = !string.IsNullOrEmpty(unitId) && product?.UnitOptions != null ? unitId : product.UnitOptions.FirstOrDefault<UnitOptionViewModel>().Name;
271
272 foreach (var unitOption in product.UnitOptions)
273 {
274 if (unitOption.Id == unitId)
275 {
276 selectedUnitName = unitOption.Name;
277 }
278 }
279
280 <button class="btn btn-secondary @flexFill dropdown-toggle" data-dw-button="secondary" type="button" data-bs-toggle="dropdown" aria-expanded="false">
281 @selectedUnitName
282 </button>
283
284 <ul class="dropdown-menu swift_unit-field">
285 @foreach (var unitOption in product.UnitOptions)
286 {
287 var selectedUnit = unitOption.Id == unitId ? "selected" : "";
288
289 <li>
290 <button type="button" class="btn dropdown-item" data-value="@unitOption.Id" onclick="document.querySelector('#UnitSelectorForm_@(product.Id)_@(product.VariantId.Replace(".", "_"))
[email protected]').querySelector('.js-unit-id').value = this.getAttribute('data-value');
291 document.querySelector('#Unit_@(product.Id)
[email protected]?.Replace(".", "_")').value = this.getAttribute('data-value');
292 swift.PageUpdater.Update(document.querySelector('#UnitSelectorForm_@(product.Id)_@(product.VariantId?.Replace(".", "_"))
[email protected]'))">
293 <span>@unitOption.Name</span>
294 <span>
295 @if (unitOption.StockLevel > 0 || unitOption.NeverOutOfStock)
296 {
297 if (!Model.Item.GetBoolean("HideInventory") && !unitOption.NeverOutOfStock)
298 {
299 <span class="small text-success">@unitOption.StockLevel @Translate("In stock")</span>
300 }
301 else
302 {
303 <span class="small text-success">@Translate("In stock")</span>
304 }
305 }
306 else
307 {
308 <span class="small text-danger">@Translate("Out of Stock")</span>
309 }
310 </span>
311 </button>
312 </li>
313 }
314 </ul>
315 }
316 @if (quantitySelector)
317 {
318 @RenderPartial("Components/QuantitySelector.cshtml", product)
319 }
320 <button type="button" onclick="swift.Cart.Update(event)" data-stock="@product.StockLevel" class="btn btn-primary @flexFill js-add-to-cart-button" data-dw-button="primary" style="white-space: nowrap" @disableAddToCart title="@Translate("Add to cart")" id="AddToCartButton@(product.Id)
[email protected]">
321 @if (!Model.Item.GetBoolean("HideButtonText"))
322 {
323 <span class="text-nowrap d-flex align-items-center justify-content-center gap-2">
324 @addToCartLabel
325 </span>
326 }
327 else
328 {
329 @addToCartLabel
330 }
331 </button>
332 </div>
333 </form>
334 @RenderPartial("Components/OutOfStockModal.cshtml", product)
335
336 @if (!anonymousUser && favoritesSelector)
337 {
338 @RenderPartial("Components/ToggleFavorite.cshtml", product)
339 }
340 </div>
341 </div>
342 }
343 }
344 else if (whenVariantsExist == "modal")
345 {
346 string ButtonShape = Model.Item.GetRawValueString("VariantButtonShape", "square");
347 string buttonAspectRatio = Model.Item.GetRawValueString("VariantImageAspectRatio", "56%");
348
349 string buttonText = Translate("Select");
350 string variantId = !string.IsNullOrWhiteSpace(product.VariantId) ? product.VariantId : product.DefaultVariantId;
351
352 string variantSelectorServicePageId = !string.IsNullOrEmpty(Model.Item.GetString("VariantSelectorServicePageId")) ? Model.Item.GetLink("VariantSelectorServicePageId").PageId.ToString() : "";
353 variantSelectorServicePageId = variantSelectorServicePageId != "" ? variantSelectorServicePageId : GetPageIdByNavigationTag("VariantSelectorService").ToString();
354
355 <div class="d-flex @horizontalAlign w-100
[email protected]()">
356 @if (!anonymousUser && favoritesSelector)
357 {
358 @RenderPartial("Components/ToggleFavorite.cshtml", product)
359 }
360 <form action="/Default.aspx?ID=@variantSelectorServicePageId" data-response-target-element="DynamicModalContent" data-preloader="inline" style="z-index: 1" class="@fullWidth">
361 <input type="hidden" name="ProductID" value="@product.Id">
362 <input type="hidden" name="VariantID" value="@variantId">
363 <input type="hidden" name="QuantitySelector" value="@quantitySelector.ToString()">
364 <input type="hidden" name="HideInventory" value="@hideInventory.ToString()">
365 <input type="hidden" name="HideStockState" value="@hideStockState.ToString()">
366 <input type="hidden" name="ButtonLayout" value="@ButtonShape">
367 <input type="hidden" name="ButtonAspectRatio" value="@buttonAspectRatio">
368 <input type="hidden" name="VariantSelectorServicePage" value="@variantSelectorServicePageId">
369 <input type="hidden" name="ViewType" value="ModalContent">
370
371 <button type="button" onclick="swift.PageUpdater.Update(event)" class="btn btn-primary @fullWidth" data-dw-button="primary" title="@Translate("Select")" data-bs-toggle="modal" data-bs-target="#DynamicModal" id="OpenVariantSelectorModal@(product.Id)
[email protected]">@buttonText</button>
372 </form>
373 </div>
374 }
375 }
376