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