Browse Source
* In progress copying code into new RP project Cleaning up namespaces and whitespace in original Web project * Cleaning up some more namespaces * Removing unused page. * Index page loads correctly. * Fixing up paging. * Moving views; getting ready to convert to RPs * Auto stash before merge of "master" and "origin/master" Basket and Checkout pages wired up * WIP on Account pages * Working on signin/signout * Working on auth * Getting order history working Fixing auth bug * Fixing Checkout issue * Fixing linkmain
committed by
GitHub
155 changed files with 29464 additions and 40 deletions
@ -0,0 +1,3 @@ |
|||||
|
{ |
||||
|
"directory": "wwwroot/lib" |
||||
|
} |
||||
@ -0,0 +1,8 @@ |
|||||
|
namespace Microsoft.eShopWeb.RazorPages |
||||
|
{ |
||||
|
public static class Constants |
||||
|
{ |
||||
|
public const string BASKET_COOKIENAME = "eShop"; |
||||
|
public const int ITEMS_PER_PAGE = 10; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,15 @@ |
|||||
|
using Microsoft.eShopWeb.RazorPages.ViewModels; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Threading.Tasks; |
||||
|
|
||||
|
namespace Microsoft.eShopWeb.RazorPages.Interfaces |
||||
|
{ |
||||
|
public interface IBasketService |
||||
|
{ |
||||
|
Task<BasketViewModel> GetOrCreateBasketForUser(string userName); |
||||
|
Task TransferBasketAsync(string anonymousId, string userName); |
||||
|
Task AddItemToBasket(int basketId, int catalogItemId, decimal price, int quantity); |
||||
|
Task SetQuantities(int basketId, Dictionary<string, int> quantities); |
||||
|
Task DeleteBasketAsync(int basketId); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,14 @@ |
|||||
|
using Microsoft.AspNetCore.Mvc.Rendering; |
||||
|
using Microsoft.eShopWeb.RazorPages.ViewModels; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Threading.Tasks; |
||||
|
|
||||
|
namespace Microsoft.eShopWeb.RazorPages.Interfaces |
||||
|
{ |
||||
|
public interface ICatalogService |
||||
|
{ |
||||
|
Task<CatalogIndexViewModel> GetCatalogItems(int pageIndex, int itemsPage, int? brandId, int? typeId); |
||||
|
Task<IEnumerable<SelectListItem>> GetBrands(); |
||||
|
Task<IEnumerable<SelectListItem>> GetTypes(); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,54 @@ |
|||||
|
@page |
||||
|
@model RegisterModel |
||||
|
@{ |
||||
|
ViewData["Title"] = "Register"; |
||||
|
} |
||||
|
<div class="brand-header-block"> |
||||
|
<ul class="container"> |
||||
|
<li class="active" style="margin-right: 65px;">Already have an account? |
||||
|
<a asp-page="/Account/Signin">LOGIN</a></li> |
||||
|
</ul> |
||||
|
</div> |
||||
|
<div class="container account-login-container"> |
||||
|
<div class="row"> |
||||
|
<div class="col-md-12"> |
||||
|
<section> |
||||
|
<form asp-route-returnurl="@ViewData["ReturnUrl"]" method="post" class="form-horizontal"> |
||||
|
<div asp-validation-summary="All" class="text-danger"></div> |
||||
|
<div class="form-group"> |
||||
|
<label asp-for="UserDetails.Email" class="col-md-2 control-label"></label> |
||||
|
<div class="col-md-10"> |
||||
|
<input asp-for="UserDetails.Email" class="form-control" /> |
||||
|
<span asp-validation-for="UserDetails.Email" class="text-danger"></span> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="form-group"> |
||||
|
<label asp-for="UserDetails.Password" class="col-md-2 control-label"></label> |
||||
|
<div class="col-md-10"> |
||||
|
<input asp-for="UserDetails.Password" class="form-control" /> |
||||
|
<span asp-validation-for="UserDetails.Password" class="text-danger"></span> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="form-group"> |
||||
|
<label asp-for="UserDetails.ConfirmPassword" class="col-md-2 control-label"></label> |
||||
|
<div class="col-md-10"> |
||||
|
<input asp-for="UserDetails.ConfirmPassword" class="form-control" /> |
||||
|
<span asp-validation-for="UserDetails.ConfirmPassword" class="text-danger"></span> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="form-group"> |
||||
|
<button type="submit" class="btn btn-default btn-brand btn-brand-big"> REGISTER </button> |
||||
|
</div> |
||||
|
<p> |
||||
|
Note that for demo purposes you don't need to register! Use the credentials shown below the |
||||
|
<a asp-action="signin">login screen</a>. |
||||
|
</p> |
||||
|
</form> |
||||
|
</section> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
@section Scripts { |
||||
|
@{ await Html.RenderPartialAsync("_ValidationScriptsPartial"); } |
||||
|
} |
||||
@ -0,0 +1,53 @@ |
|||||
|
using System.Threading.Tasks; |
||||
|
using Microsoft.AspNetCore.Mvc; |
||||
|
using Microsoft.AspNetCore.Mvc.RazorPages; |
||||
|
using Microsoft.eShopWeb.RazorPages.ViewModels; |
||||
|
using Microsoft.AspNetCore.Identity; |
||||
|
using Infrastructure.Identity; |
||||
|
|
||||
|
namespace Microsoft.eShopWeb.RazorPages.Pages.Account |
||||
|
{ |
||||
|
public class RegisterModel : PageModel |
||||
|
{ |
||||
|
private readonly SignInManager<ApplicationUser> _signInManager; |
||||
|
private readonly UserManager<ApplicationUser> _userManager; |
||||
|
|
||||
|
public RegisterModel(SignInManager<ApplicationUser> signInManager, |
||||
|
UserManager<ApplicationUser> userManager |
||||
|
) |
||||
|
{ |
||||
|
_signInManager = signInManager; |
||||
|
_userManager = userManager; |
||||
|
} |
||||
|
|
||||
|
[BindProperty] |
||||
|
public RegisterViewModel UserDetails { get; set; } |
||||
|
|
||||
|
|
||||
|
public async Task<IActionResult> OnPost(string returnUrl = "/Index") |
||||
|
{ |
||||
|
if (ModelState.IsValid) |
||||
|
{ |
||||
|
var user = new ApplicationUser { UserName = UserDetails.Email, Email = UserDetails.Email }; |
||||
|
var result = await _userManager.CreateAsync(user, UserDetails.Password); |
||||
|
if (result.Succeeded) |
||||
|
{ |
||||
|
await _signInManager.SignInAsync(user, isPersistent: false); |
||||
|
return LocalRedirect(returnUrl); |
||||
|
} |
||||
|
AddErrors(result); |
||||
|
} |
||||
|
return Page(); |
||||
|
|
||||
|
} |
||||
|
|
||||
|
private void AddErrors(IdentityResult result) |
||||
|
{ |
||||
|
foreach (var error in result.Errors) |
||||
|
{ |
||||
|
ModelState.AddModelError("", error.Description); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,64 @@ |
|||||
|
@page |
||||
|
@using System.Collections.Generic |
||||
|
@using Microsoft.AspNetCore.Http |
||||
|
@using Microsoft.AspNetCore.Http.Authentication |
||||
|
@model SigninModel |
||||
|
@{ |
||||
|
ViewData["Title"] = "Log in"; |
||||
|
} |
||||
|
<div class="brand-header-block"> |
||||
|
<ul class="container"> |
||||
|
@*<li><a asp-area="" asp-controller="Account" asp-action="Register">REGISTER</a></li>*@ |
||||
|
<li class="active" style="margin-right: 65px;">LOGIN</li> |
||||
|
</ul> |
||||
|
</div> |
||||
|
<div class="container account-login-container"> |
||||
|
<div class="row"> |
||||
|
<div class="col-md-12"> |
||||
|
<section> |
||||
|
<form asp-route-returnurl="@ViewData["ReturnUrl"]" method="post" class="form-horizontal"> |
||||
|
<h4>ARE YOU REGISTERED?</h4> |
||||
|
<div asp-validation-summary="All" class="text-danger"></div> |
||||
|
<div class="form-group"> |
||||
|
<label asp-for="LoginDetails.Email" class="control-label form-label"></label> |
||||
|
<input asp-for="LoginDetails.Email" class="form-control form-input form-input-center" /> |
||||
|
<span asp-validation-for="LoginDetails.Email" class="text-danger"></span> |
||||
|
</div> |
||||
|
<div class="form-group"> |
||||
|
<label asp-for="LoginDetails.Password" class="control-label form-label"></label> |
||||
|
<input asp-for="LoginDetails.Password" class="form-control form-input form-input-center" /> |
||||
|
<span asp-validation-for="LoginDetails.Password" class="text-danger"></span> |
||||
|
</div> |
||||
|
<div class="form-group"> |
||||
|
<div class="checkbox"> |
||||
|
<label asp-for="LoginDetails.RememberMe"> |
||||
|
<input asp-for="LoginDetails.RememberMe" /> |
||||
|
@Html.DisplayNameFor(m => m.LoginDetails.RememberMe) |
||||
|
</label> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="form-group"> |
||||
|
<button type="submit" class="btn btn-default btn-brand btn-brand-big"> LOG IN </button> |
||||
|
</div> |
||||
|
<p> |
||||
|
<a asp-page="/Account/Register" |
||||
|
asp-route-returnurl="@ViewData["ReturnUrl"]" class="text">Register as a new user?</a> |
||||
|
</p> |
||||
|
<p> |
||||
|
Note that for demo purposes you don't need to register and can login with these credentials: |
||||
|
</p> |
||||
|
<p> |
||||
|
User: <b>demouser@microsoft.com</b> |
||||
|
</p> |
||||
|
<p> |
||||
|
Password: <b>Pass@word1</b> |
||||
|
</p> |
||||
|
</form> |
||||
|
</section> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
@section Scripts { |
||||
|
@{ await Html.RenderPartialAsync("_ValidationScriptsPartial"); } |
||||
|
} |
||||
@ -0,0 +1,80 @@ |
|||||
|
using System.Threading.Tasks; |
||||
|
using Microsoft.AspNetCore.Mvc; |
||||
|
using Microsoft.AspNetCore.Mvc.RazorPages; |
||||
|
using Microsoft.eShopWeb.RazorPages.ViewModels; |
||||
|
using Microsoft.eShopWeb.RazorPages.Interfaces; |
||||
|
using Microsoft.AspNetCore.Authorization; |
||||
|
using Microsoft.AspNetCore.Identity; |
||||
|
using Infrastructure.Identity; |
||||
|
using Microsoft.AspNetCore.Authentication; |
||||
|
using System; |
||||
|
using System.ComponentModel.DataAnnotations; |
||||
|
|
||||
|
namespace Microsoft.eShopWeb.RazorPages.Pages.Account |
||||
|
{ |
||||
|
public class SigninModel : PageModel |
||||
|
{ |
||||
|
private readonly SignInManager<ApplicationUser> _signInManager; |
||||
|
private readonly IBasketService _basketService; |
||||
|
|
||||
|
public SigninModel(SignInManager<ApplicationUser> signInManager, |
||||
|
IBasketService basketService) |
||||
|
{ |
||||
|
_signInManager = signInManager; |
||||
|
_basketService = basketService; |
||||
|
} |
||||
|
|
||||
|
[BindProperty] |
||||
|
public LoginViewModel LoginDetails { get; set; } = new LoginViewModel(); |
||||
|
|
||||
|
public class LoginViewModel |
||||
|
{ |
||||
|
[Required] |
||||
|
[EmailAddress] |
||||
|
public string Email { get; set; } |
||||
|
|
||||
|
[Required] |
||||
|
[DataType(DataType.Password)] |
||||
|
public string Password { get; set; } |
||||
|
|
||||
|
[Display(Name = "Remember me?")] |
||||
|
public bool RememberMe { get; set; } |
||||
|
} |
||||
|
|
||||
|
|
||||
|
public async Task OnGet(string returnUrl = null) |
||||
|
{ |
||||
|
await HttpContext.SignOutAsync(IdentityConstants.ExternalScheme); |
||||
|
|
||||
|
ViewData["ReturnUrl"] = returnUrl; |
||||
|
if (!String.IsNullOrEmpty(returnUrl) && |
||||
|
returnUrl.IndexOf("checkout", StringComparison.OrdinalIgnoreCase) >= 0) |
||||
|
{ |
||||
|
ViewData["ReturnUrl"] = "/Basket/Index"; |
||||
|
} |
||||
|
} |
||||
|
public async Task<IActionResult> OnPost(string returnUrl = null) |
||||
|
{ |
||||
|
if (!ModelState.IsValid) |
||||
|
{ |
||||
|
return Page(); |
||||
|
} |
||||
|
ViewData["ReturnUrl"] = returnUrl; |
||||
|
|
||||
|
var result = await _signInManager.PasswordSignInAsync(LoginDetails.Email, |
||||
|
LoginDetails.Password, LoginDetails.RememberMe, lockoutOnFailure: false); |
||||
|
if (result.Succeeded) |
||||
|
{ |
||||
|
string anonymousBasketId = Request.Cookies[Constants.BASKET_COOKIENAME]; |
||||
|
if (!String.IsNullOrEmpty(anonymousBasketId)) |
||||
|
{ |
||||
|
await _basketService.TransferBasketAsync(anonymousBasketId, LoginDetails.Email); |
||||
|
Response.Cookies.Delete(Constants.BASKET_COOKIENAME); |
||||
|
} |
||||
|
return RedirectToPage(returnUrl ?? "/Index"); |
||||
|
} |
||||
|
ModelState.AddModelError(string.Empty, "Invalid login attempt."); |
||||
|
return Page(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,6 @@ |
|||||
|
@page |
||||
|
@model SignoutModel |
||||
|
@{ |
||||
|
ViewData["Title"] = "Signing out"; |
||||
|
} |
||||
|
<h2>Signing out...</h2> |
||||
@ -0,0 +1,32 @@ |
|||||
|
using System.Threading.Tasks; |
||||
|
using Microsoft.AspNetCore.Mvc.RazorPages; |
||||
|
using Microsoft.AspNetCore.Identity; |
||||
|
using Infrastructure.Identity; |
||||
|
using Microsoft.AspNetCore.Mvc; |
||||
|
|
||||
|
namespace Microsoft.eShopWeb.RazorPages.Pages.Account |
||||
|
{ |
||||
|
public class SignoutModel : PageModel |
||||
|
{ |
||||
|
private readonly SignInManager<ApplicationUser> _signInManager; |
||||
|
|
||||
|
public SignoutModel(SignInManager<ApplicationUser> signInManager) |
||||
|
{ |
||||
|
_signInManager = signInManager; |
||||
|
} |
||||
|
|
||||
|
public async Task<IActionResult> OnGet() |
||||
|
{ |
||||
|
await _signInManager.SignOutAsync(); |
||||
|
|
||||
|
return RedirectToPage("/Index"); |
||||
|
} |
||||
|
|
||||
|
public async Task<IActionResult> OnPost() |
||||
|
{ |
||||
|
await _signInManager.SignOutAsync(); |
||||
|
|
||||
|
return RedirectToPage("/Index"); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,15 @@ |
|||||
|
@page |
||||
|
@{ |
||||
|
ViewData["Title"] = "Checkout Complete"; |
||||
|
} |
||||
|
<section class="esh-catalog-hero"> |
||||
|
<div class="container"> |
||||
|
<img class="esh-catalog-title" src="../images/main_banner_text.png" /> |
||||
|
</div> |
||||
|
</section> |
||||
|
|
||||
|
<div class="container"> |
||||
|
<h1>Thanks for your Order!</h1> |
||||
|
|
||||
|
<a asp-page="/Index">Continue Shopping...</a> |
||||
|
</div> |
||||
@ -0,0 +1,86 @@ |
|||||
|
@page "{handler?}" |
||||
|
@model IndexModel |
||||
|
@{ |
||||
|
ViewData["Title"] = "Basket"; |
||||
|
} |
||||
|
<section class="esh-catalog-hero"> |
||||
|
<div class="container"> |
||||
|
<img class="esh-catalog-title" src="../images/main_banner_text.png" /> |
||||
|
</div> |
||||
|
</section> |
||||
|
|
||||
|
<div class="container"> |
||||
|
|
||||
|
@if (Model.BasketModel.Items.Any()) |
||||
|
{ |
||||
|
<form method="post"> |
||||
|
<article class="esh-basket-titles row"> |
||||
|
<br /> |
||||
|
<section class="esh-basket-title col-xs-3">Product</section> |
||||
|
<section class="esh-basket-title col-xs-3 hidden-lg-down"></section> |
||||
|
<section class="esh-basket-title col-xs-2">Price</section> |
||||
|
<section class="esh-basket-title col-xs-2">Quantity</section> |
||||
|
<section class="esh-basket-title col-xs-2">Cost</section> |
||||
|
</article> |
||||
|
<div class="esh-catalog-items row"> |
||||
|
@for (int i=0; i< Model.BasketModel.Items.Count; i++) |
||||
|
{ |
||||
|
var item = Model.BasketModel.Items[i]; |
||||
|
<article class="esh-basket-items row"> |
||||
|
<div> |
||||
|
<section class="esh-basket-item esh-basket-item--middle col-lg-3 hidden-lg-down"> |
||||
|
<img class="esh-basket-image" src="@item.PictureUrl" /> |
||||
|
</section> |
||||
|
<section class="esh-basket-item esh-basket-item--middle col-xs-3">@item.ProductName</section> |
||||
|
<section class="esh-basket-item esh-basket-item--middle col-xs-2">$ @item.UnitPrice.ToString("N2")</section> |
||||
|
<section class="esh-basket-item esh-basket-item--middle col-xs-2"> |
||||
|
<input type="hidden" name="@("Items[" + i + "].Key")" value="@item.Id" /> |
||||
|
<input type="number" class="esh-basket-input" min="1" name="@("Items[" + i + "].Value")" value="@item.Quantity" /> |
||||
|
</section> |
||||
|
<section class="esh-basket-item esh-basket-item--middle esh-basket-item--mark col-xs-2">$ @Math.Round(item.Quantity * item.UnitPrice, 2).ToString("N2")</section> |
||||
|
</div> |
||||
|
<div class="row"> |
||||
|
|
||||
|
</div> |
||||
|
</article> |
||||
|
@*<div class="esh-catalog-item col-md-4"> |
||||
|
@item.ProductId |
||||
|
</div>*@ |
||||
|
|
||||
|
<div class="container"> |
||||
|
<article class="esh-basket-titles esh-basket-titles--clean row"> |
||||
|
<section class="esh-basket-title col-xs-10"></section> |
||||
|
<section class="esh-basket-title col-xs-2">Total</section> |
||||
|
</article> |
||||
|
|
||||
|
<article class="esh-basket-items row"> |
||||
|
<section class="esh-basket-item col-xs-10"></section> |
||||
|
<section class="esh-basket-item esh-basket-item--mark col-xs-2">$ @Model.BasketModel.Total()</section> |
||||
|
</article> |
||||
|
|
||||
|
<article class="esh-basket-items row"> |
||||
|
<section class="esh-basket-item col-xs-7"></section> |
||||
|
<section class="esh-basket-item col-xs-2"> |
||||
|
@*<button class="btn esh-basket-checkout" name="name" value="" type="submit">[ Update ]</button>*@ |
||||
|
</section> |
||||
|
</article> |
||||
|
</div> |
||||
|
} |
||||
|
<section class="esh-basket-item col-xs-push-8 col-xs-4"> |
||||
|
<button class="btn esh-basket-checkout" name="updatebutton" value="" type="submit" |
||||
|
asp-page-handler="Update">[ Update ]</button> |
||||
|
<input type="submit" asp-page-handler="Checkout" |
||||
|
class="btn esh-basket-checkout" |
||||
|
value="[ Checkout ]" name="action" /> |
||||
|
</section> |
||||
|
|
||||
|
</div> |
||||
|
</form> |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
<div class="esh-catalog-items row"> |
||||
|
Basket is empty. |
||||
|
</div> |
||||
|
} |
||||
|
</div> |
||||
@ -0,0 +1,109 @@ |
|||||
|
using System.Threading.Tasks; |
||||
|
using Microsoft.AspNetCore.Mvc; |
||||
|
using Microsoft.AspNetCore.Mvc.RazorPages; |
||||
|
using Microsoft.eShopWeb.RazorPages.ViewModels; |
||||
|
using Microsoft.eShopWeb.RazorPages.Interfaces; |
||||
|
using ApplicationCore.Interfaces; |
||||
|
using Microsoft.AspNetCore.Identity; |
||||
|
using Infrastructure.Identity; |
||||
|
using System; |
||||
|
using Microsoft.AspNetCore.Http; |
||||
|
using System.Collections.Generic; |
||||
|
using ApplicationCore.Entities.OrderAggregate; |
||||
|
|
||||
|
namespace Microsoft.eShopWeb.RazorPages.Pages.Basket |
||||
|
{ |
||||
|
public class IndexModel : PageModel |
||||
|
{ |
||||
|
private readonly IBasketService _basketService; |
||||
|
private const string _basketSessionKey = "basketId"; |
||||
|
private readonly IUriComposer _uriComposer; |
||||
|
private readonly SignInManager<ApplicationUser> _signInManager; |
||||
|
private readonly IAppLogger<IndexModel> _logger; |
||||
|
private readonly IOrderService _orderService; |
||||
|
private string _username = null; |
||||
|
|
||||
|
public IndexModel(IBasketService basketService, |
||||
|
IUriComposer uriComposer, |
||||
|
SignInManager<ApplicationUser> signInManager, |
||||
|
IAppLogger<IndexModel> logger, |
||||
|
IOrderService orderService) |
||||
|
{ |
||||
|
_basketService = basketService; |
||||
|
_uriComposer = uriComposer; |
||||
|
_signInManager = signInManager; |
||||
|
_logger = logger; |
||||
|
_orderService = orderService; |
||||
|
} |
||||
|
|
||||
|
public BasketViewModel BasketModel { get; set; } = new BasketViewModel(); |
||||
|
|
||||
|
public async Task OnGet() |
||||
|
{ |
||||
|
await SetBasketModelAsync(); |
||||
|
} |
||||
|
|
||||
|
public async Task<IActionResult> OnPost(CatalogItemViewModel productDetails) |
||||
|
{ |
||||
|
if (productDetails?.Id == null) |
||||
|
{ |
||||
|
return RedirectToPage("/Index"); |
||||
|
} |
||||
|
await SetBasketModelAsync(); |
||||
|
|
||||
|
await _basketService.AddItemToBasket(BasketModel.Id, productDetails.Id, productDetails.Price, 1); |
||||
|
|
||||
|
await SetBasketModelAsync(); |
||||
|
|
||||
|
return RedirectToPage(); |
||||
|
} |
||||
|
|
||||
|
public async Task OnPostUpdate(Dictionary<string,int> items) |
||||
|
{ |
||||
|
await SetBasketModelAsync(); |
||||
|
await _basketService.SetQuantities(BasketModel.Id, items); |
||||
|
|
||||
|
await SetBasketModelAsync(); |
||||
|
} |
||||
|
|
||||
|
public async Task<IActionResult> OnPostCheckout(Dictionary<string,int> items) |
||||
|
{ |
||||
|
await SetBasketModelAsync(); |
||||
|
|
||||
|
await _basketService.SetQuantities(BasketModel.Id, items); |
||||
|
|
||||
|
await _orderService.CreateOrderAsync(BasketModel.Id, new Address("123 Main St.", "Kent", "OH", "United States", "44240")); |
||||
|
|
||||
|
await _basketService.DeleteBasketAsync(BasketModel.Id); |
||||
|
|
||||
|
return RedirectToPage("/Basket/CheckoutComplete"); |
||||
|
} |
||||
|
|
||||
|
private async Task SetBasketModelAsync() |
||||
|
{ |
||||
|
if (_signInManager.IsSignedIn(HttpContext.User)) |
||||
|
{ |
||||
|
BasketModel = await _basketService.GetOrCreateBasketForUser(User.Identity.Name); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
GetOrSetBasketCookieAndUserName(); |
||||
|
BasketModel = await _basketService.GetOrCreateBasketForUser(_username); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private void GetOrSetBasketCookieAndUserName() |
||||
|
{ |
||||
|
if (Request.Cookies.ContainsKey(Constants.BASKET_COOKIENAME)) |
||||
|
{ |
||||
|
_username = Request.Cookies[Constants.BASKET_COOKIENAME]; |
||||
|
} |
||||
|
if (_username != null) return; |
||||
|
|
||||
|
_username = Guid.NewGuid().ToString(); |
||||
|
var cookieOptions = new CookieOptions(); |
||||
|
cookieOptions.Expires = DateTime.Today.AddYears(10); |
||||
|
Response.Cookies.Append(Constants.BASKET_COOKIENAME, _username, cookieOptions); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,15 @@ |
|||||
|
@model BasketComponentViewModel |
||||
|
|
||||
|
@{ |
||||
|
ViewData["Title"] = "My Basket"; |
||||
|
} |
||||
|
|
||||
|
<a class="esh-basketstatus " |
||||
|
asp-page="/Basket/Index"> |
||||
|
<div class="esh-basketstatus-image"> |
||||
|
<img src="~/images/cart.png" /> |
||||
|
</div> |
||||
|
<div class="esh-basketstatus-badge"> |
||||
|
@Model.ItemsCount |
||||
|
</div> |
||||
|
</a> |
||||
@ -0,0 +1,23 @@ |
|||||
|
@page |
||||
|
@model ErrorModel |
||||
|
@{ |
||||
|
ViewData["Title"] = "Error"; |
||||
|
} |
||||
|
|
||||
|
<h1 class="text-danger">Error.</h1> |
||||
|
<h2 class="text-danger">An error occurred while processing your request.</h2> |
||||
|
|
||||
|
@if (Model.ShowRequestId) |
||||
|
{ |
||||
|
<p> |
||||
|
<strong>Request ID:</strong> <code>@Model.RequestId</code> |
||||
|
</p> |
||||
|
} |
||||
|
|
||||
|
<h3>Development Mode</h3> |
||||
|
<p> |
||||
|
Swapping to <strong>Development</strong> environment will display more detailed information about the error that occurred. |
||||
|
</p> |
||||
|
<p> |
||||
|
<strong>Development environment should not be enabled in deployed applications</strong>, as it can result in sensitive information from exceptions being displayed to end users. For local debugging, development environment can be enabled by setting the <strong>ASPNETCORE_ENVIRONMENT</strong> environment variable to <strong>Development</strong>, and restarting the application. |
||||
|
</p> |
||||
@ -0,0 +1,17 @@ |
|||||
|
using System.Diagnostics; |
||||
|
using Microsoft.AspNetCore.Mvc.RazorPages; |
||||
|
|
||||
|
namespace Microsoft.eShopWeb.RazorPages.Pages |
||||
|
{ |
||||
|
public class ErrorModel : PageModel |
||||
|
{ |
||||
|
public string RequestId { get; set; } |
||||
|
|
||||
|
public bool ShowRequestId => !string.IsNullOrEmpty(RequestId); |
||||
|
|
||||
|
public void OnGet() |
||||
|
{ |
||||
|
RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,49 @@ |
|||||
|
@page "{pageId?}" |
||||
|
@{ |
||||
|
ViewData["Title"] = "Catalog"; |
||||
|
@model IndexModel |
||||
|
} |
||||
|
<section class="esh-catalog-hero"> |
||||
|
<div class="container"> |
||||
|
<img class="esh-catalog-title" src="../images/main_banner_text.png" /> |
||||
|
</div> |
||||
|
</section> |
||||
|
|
||||
|
<section class="esh-catalog-filters"> |
||||
|
<div class="container"> |
||||
|
<form method="get"> |
||||
|
<label class="esh-catalog-label" data-title="brand"> |
||||
|
<select asp-for="@Model.CatalogModel.BrandFilterApplied" asp-items="@Model.CatalogModel.Brands" class="esh-catalog-filter"></select> |
||||
|
</label> |
||||
|
<label class="esh-catalog-label" data-title="type"> |
||||
|
<select asp-for="@Model.CatalogModel.TypesFilterApplied" asp-items="@Model.CatalogModel.Types" class="esh-catalog-filter"></select> |
||||
|
</label> |
||||
|
<input class="esh-catalog-send" type="image" src="images/arrow-right.svg" /> |
||||
|
</form> |
||||
|
</div> |
||||
|
</section> |
||||
|
|
||||
|
<div class="container"> |
||||
|
|
||||
|
@if (Model.CatalogModel.CatalogItems.Any()) |
||||
|
{ |
||||
|
@Html.Partial("_pagination", Model.CatalogModel.PaginationInfo) |
||||
|
|
||||
|
<div class="esh-catalog-items row"> |
||||
|
@foreach (var catalogItem in Model.CatalogModel.CatalogItems) |
||||
|
{ |
||||
|
<div class="esh-catalog-item col-md-4"> |
||||
|
@Html.Partial("_product", catalogItem) |
||||
|
</div> |
||||
|
} |
||||
|
</div> |
||||
|
|
||||
|
@Html.Partial("_pagination", Model.CatalogModel.PaginationInfo) |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
<div class="esh-catalog-items row"> |
||||
|
THERE ARE NO RESULTS THAT MATCH YOUR SEARCH |
||||
|
</div> |
||||
|
} |
||||
|
</div> |
||||
@ -0,0 +1,25 @@ |
|||||
|
using System.Threading.Tasks; |
||||
|
using Microsoft.AspNetCore.Mvc; |
||||
|
using Microsoft.AspNetCore.Mvc.RazorPages; |
||||
|
using Microsoft.eShopWeb.RazorPages.ViewModels; |
||||
|
using Microsoft.eShopWeb.RazorPages.Interfaces; |
||||
|
|
||||
|
namespace Microsoft.eShopWeb.RazorPages.Pages |
||||
|
{ |
||||
|
public class IndexModel : PageModel |
||||
|
{ |
||||
|
private readonly ICatalogService _catalogService; |
||||
|
|
||||
|
public IndexModel(ICatalogService catalogService) |
||||
|
{ |
||||
|
_catalogService = catalogService; |
||||
|
} |
||||
|
|
||||
|
public CatalogIndexViewModel CatalogModel { get; set; } = new CatalogIndexViewModel(); |
||||
|
|
||||
|
public async Task OnGet(CatalogIndexViewModel catalogModel, int? pageId) |
||||
|
{ |
||||
|
CatalogModel = await _catalogService.GetCatalogItems(pageId ?? 0, Constants.ITEMS_PER_PAGE, catalogModel.BrandFilterApplied, catalogModel.TypesFilterApplied); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,88 @@ |
|||||
|
@page |
||||
|
@model DetailModel |
||||
|
@{ |
||||
|
ViewData["Title"] = "My Order History"; |
||||
|
} |
||||
|
@{ |
||||
|
ViewData["Title"] = "Order Detail"; |
||||
|
} |
||||
|
|
||||
|
<div class="esh-orders_detail"> |
||||
|
<div class="container"> |
||||
|
<section class="esh-orders_detail-section"> |
||||
|
<article class="esh-orders_detail-titles row"> |
||||
|
<section class="esh-orders_detail-title col-xs-3">Order number</section> |
||||
|
<section class="esh-orders_detail-title col-xs-3">Date</section> |
||||
|
<section class="esh-orders_detail-title col-xs-3">Total</section> |
||||
|
<section class="esh-orders_detail-title col-xs-3">Status</section> |
||||
|
</article> |
||||
|
|
||||
|
<article class="esh-orders_detail-items row"> |
||||
|
<section class="esh-orders_detail-item col-xs-3">@Model.OrderDetails.OrderNumber</section> |
||||
|
<section class="esh-orders_detail-item col-xs-3">@Model.OrderDetails.OrderDate</section> |
||||
|
<section class="esh-orders_detail-item col-xs-3">$@Model.OrderDetails.Total</section> |
||||
|
<section class="esh-orders_detail-title col-xs-3">@Model.OrderDetails.Status</section> |
||||
|
</article> |
||||
|
</section> |
||||
|
|
||||
|
@*<section class="esh-orders_detail-section"> |
||||
|
<article class="esh-orders_detail-titles row"> |
||||
|
<section class="esh-orders_detail-title col-xs-12">Description</section> |
||||
|
</article> |
||||
|
|
||||
|
<article class="esh-orders_detail-items row"> |
||||
|
<section class="esh-orders_detail-item col-xs-12">@Model.Description</section> |
||||
|
</article> |
||||
|
</section>*@ |
||||
|
|
||||
|
<section class="esh-orders_detail-section"> |
||||
|
<article class="esh-orders_detail-titles row"> |
||||
|
<section class="esh-orders_detail-title col-xs-12">Shipping Address</section> |
||||
|
</article> |
||||
|
|
||||
|
<article class="esh-orders_detail-items row"> |
||||
|
<section class="esh-orders_detail-item col-xs-12">@Model.OrderDetails.ShippingAddress.Street</section> |
||||
|
</article> |
||||
|
|
||||
|
<article class="esh-orders_detail-items row"> |
||||
|
<section class="esh-orders_detail-item col-xs-12">@Model.OrderDetails.ShippingAddress.City</section> |
||||
|
</article> |
||||
|
|
||||
|
<article class="esh-orders_detail-items row"> |
||||
|
<section class="esh-orders_detail-item col-xs-12">@Model.OrderDetails.ShippingAddress.Country</section> |
||||
|
</article> |
||||
|
</section> |
||||
|
|
||||
|
<section class="esh-orders_detail-section"> |
||||
|
<article class="esh-orders_detail-titles row"> |
||||
|
<section class="esh-orders_detail-title col-xs-12">ORDER DETAILS</section> |
||||
|
</article> |
||||
|
|
||||
|
@for (int i = 0; i < Model.OrderDetails.OrderItems.Count; i++) |
||||
|
{ |
||||
|
var item = Model.OrderDetails.OrderItems[i]; |
||||
|
<article class="esh-orders_detail-items esh-orders_detail-items--border row"> |
||||
|
<section class="esh-orders_detail-item col-md-4 hidden-md-down"> |
||||
|
<img class="esh-orders_detail-image" src="@item.PictureUrl"> |
||||
|
</section> |
||||
|
<section class="esh-orders_detail-item esh-orders_detail-item--middle col-xs-4">@item.ProductName</section> |
||||
|
<section class="esh-orders_detail-item esh-orders_detail-item--middle col-xs-1">$ @item.UnitPrice.ToString("N2")</section> |
||||
|
<section class="esh-orders_detail-item esh-orders_detail-item--middle col-xs-1">@item.Units</section> |
||||
|
<section class="esh-orders_detail-item esh-orders_detail-item--middle col-xs-2">$ @Math.Round(item.Units * item.UnitPrice, 2).ToString("N2")</section> |
||||
|
</article> |
||||
|
} |
||||
|
</section> |
||||
|
|
||||
|
<section class="esh-orders_detail-section esh-orders_detail-section--right"> |
||||
|
<article class="esh-orders_detail-titles esh-basket-titles--clean row"> |
||||
|
<section class="esh-orders_detail-title col-xs-9"></section> |
||||
|
<section class="esh-orders_detail-title col-xs-2">TOTAL</section> |
||||
|
</article> |
||||
|
|
||||
|
<article class="esh-orders_detail-items row"> |
||||
|
<section class="esh-orders_detail-item col-xs-9"></section> |
||||
|
<section class="esh-orders_detail-item esh-orders_detail-item--mark col-xs-2">$ @Model.OrderDetails.Total</section> |
||||
|
</article> |
||||
|
</section> |
||||
|
</div> |
||||
|
</div> |
||||
@ -0,0 +1,43 @@ |
|||||
|
using System.Threading.Tasks; |
||||
|
using Microsoft.AspNetCore.Mvc.RazorPages; |
||||
|
using Microsoft.eShopWeb.RazorPages.ViewModels; |
||||
|
using ApplicationCore.Interfaces; |
||||
|
using System.Linq; |
||||
|
|
||||
|
namespace Microsoft.eShopWeb.RazorPages.Pages.Order |
||||
|
{ |
||||
|
public class DetailModel : PageModel |
||||
|
{ |
||||
|
private readonly IOrderRepository _orderRepository; |
||||
|
|
||||
|
public DetailModel(IOrderRepository orderRepository) |
||||
|
{ |
||||
|
_orderRepository = orderRepository; |
||||
|
} |
||||
|
|
||||
|
public OrderViewModel OrderDetails { get; set; } = new OrderViewModel(); |
||||
|
|
||||
|
|
||||
|
public async Task OnGet(int orderId) |
||||
|
{ |
||||
|
var order = await _orderRepository.GetByIdWithItemsAsync(orderId); |
||||
|
OrderDetails = new OrderViewModel() |
||||
|
{ |
||||
|
OrderDate = order.OrderDate, |
||||
|
OrderItems = order.OrderItems.Select(oi => new OrderItemViewModel() |
||||
|
{ |
||||
|
Discount = 0, |
||||
|
PictureUrl = oi.ItemOrdered.PictureUri, |
||||
|
ProductId = oi.ItemOrdered.CatalogItemId, |
||||
|
ProductName = oi.ItemOrdered.ProductName, |
||||
|
UnitPrice = oi.UnitPrice, |
||||
|
Units = oi.Units |
||||
|
}).ToList(), |
||||
|
OrderNumber = order.Id, |
||||
|
ShippingAddress = order.ShipToAddress, |
||||
|
Status = "Pending", |
||||
|
Total = order.Total() |
||||
|
}; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,40 @@ |
|||||
|
@page |
||||
|
@using System.Linq; |
||||
|
@model IndexModel |
||||
|
@{ |
||||
|
ViewData["Title"] = "My Order History"; |
||||
|
} |
||||
|
|
||||
|
<div class="esh-orders"> |
||||
|
<div class="container"> |
||||
|
<h1>@ViewData["Title"]</h1> |
||||
|
<article class="esh-orders-titles row"> |
||||
|
<section class="esh-orders-title col-xs-2">Order number</section> |
||||
|
<section class="esh-orders-title col-xs-4">Date</section> |
||||
|
<section class="esh-orders-title col-xs-2">Total</section> |
||||
|
<section class="esh-orders-title col-xs-2">Status</section> |
||||
|
<section class="esh-orders-title col-xs-2"></section> |
||||
|
</article> |
||||
|
@if (Model.Orders != null && Model.Orders.Any()) |
||||
|
{ |
||||
|
@foreach (var item in Model.Orders) |
||||
|
{ |
||||
|
<article class="esh-orders-items row"> |
||||
|
<section class="esh-orders-item col-xs-2">@Html.DisplayFor(modelItem => item.OrderNumber)</section> |
||||
|
<section class="esh-orders-item col-xs-4">@Html.DisplayFor(modelItem => item.OrderDate)</section> |
||||
|
<section class="esh-orders-item col-xs-2">$ @Html.DisplayFor(modelItem => item.Total)</section> |
||||
|
<section class="esh-orders-item col-xs-2">@Html.DisplayFor(modelItem => item.Status)</section> |
||||
|
<section class="esh-orders-item col-xs-1"> |
||||
|
<a class="esh-orders-link" asp-page="Detail" asp-route-orderId="@item.OrderNumber">Detail</a> |
||||
|
</section> |
||||
|
<section class="esh-orders-item col-xs-1"> |
||||
|
@if (item.Status.ToLower() == "submitted") |
||||
|
{ |
||||
|
<a class="esh-orders-link" asp-page="Cancel" asp-route-orderId="@item.OrderNumber">Cancel</a> |
||||
|
} |
||||
|
</section> |
||||
|
</article> |
||||
|
} |
||||
|
} |
||||
|
</div> |
||||
|
</div> |
||||
@ -0,0 +1,48 @@ |
|||||
|
using System.Threading.Tasks; |
||||
|
using Microsoft.AspNetCore.Mvc.RazorPages; |
||||
|
using Microsoft.eShopWeb.RazorPages.ViewModels; |
||||
|
using ApplicationCore.Interfaces; |
||||
|
using ApplicationCore.Specifications; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
|
||||
|
namespace Microsoft.eShopWeb.RazorPages.Pages.Order |
||||
|
{ |
||||
|
public class IndexModel : PageModel |
||||
|
{ |
||||
|
private readonly IOrderRepository _orderRepository; |
||||
|
|
||||
|
public IndexModel(IOrderRepository orderRepository) |
||||
|
{ |
||||
|
_orderRepository = orderRepository; |
||||
|
} |
||||
|
|
||||
|
public List<OrderViewModel> Orders { get; set; } = new List<OrderViewModel>(); |
||||
|
|
||||
|
|
||||
|
public async Task OnGet() |
||||
|
{ |
||||
|
var orders = await _orderRepository.ListAsync(new CustomerOrdersWithItemsSpecification(User.Identity.Name)); |
||||
|
|
||||
|
Orders = orders |
||||
|
.Select(o => new OrderViewModel() |
||||
|
{ |
||||
|
OrderDate = o.OrderDate, |
||||
|
OrderItems = o.OrderItems?.Select(oi => new OrderItemViewModel() |
||||
|
{ |
||||
|
Discount = 0, |
||||
|
PictureUrl = oi.ItemOrdered.PictureUri, |
||||
|
ProductId = oi.ItemOrdered.CatalogItemId, |
||||
|
ProductName = oi.ItemOrdered.ProductName, |
||||
|
UnitPrice = oi.UnitPrice, |
||||
|
Units = oi.Units |
||||
|
}).ToList(), |
||||
|
OrderNumber = o.Id, |
||||
|
ShippingAddress = o.ShipToAddress, |
||||
|
Status = "Pending", |
||||
|
Total = o.Total() |
||||
|
|
||||
|
}).ToList(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,76 @@ |
|||||
|
<!DOCTYPE html> |
||||
|
<html> |
||||
|
<head> |
||||
|
<meta charset="utf-8" /> |
||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> |
||||
|
<title>@ViewData["Title"] - Microsoft.eShopOnWeb</title> |
||||
|
|
||||
|
<environment names="Development"> |
||||
|
<link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.css" /> |
||||
|
<link rel="stylesheet" href="~/css/app.css" /> |
||||
|
</environment> |
||||
|
<environment names="Staging,Production"> |
||||
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.0.0-alpha.5/css/bootstrap.min.css" |
||||
|
asp-fallback-href="~/lib/bootstrap/dist/css/bootstrap.min.css" |
||||
|
asp-fallback-test-class="sr-only" asp-fallback-test-property="position" asp-fallback-test-value="absolute" /> |
||||
|
<link rel="stylesheet" href="~/css/app.min.css" asp-append-version="true" /> |
||||
|
</environment> |
||||
|
<link rel="stylesheet" href="~/css/app.component.css" /> |
||||
|
<link rel="stylesheet" href="~/css/basket/basket.component.css" /> |
||||
|
<link rel="stylesheet" href="~/css/catalog/pager.css" /> |
||||
|
<link rel="stylesheet" href="~/css/catalog/catalog.component.css" /> |
||||
|
<link rel="stylesheet" href="~/css/basket/basket-status/basket-status.component.css" /> |
||||
|
<link rel="stylesheet" href="~/css/orders/orders.component.css" /> |
||||
|
</head> |
||||
|
<body> |
||||
|
<header class="navbar navbar-light navbar-static-top"> |
||||
|
<div class="container"> |
||||
|
<article class="row"> |
||||
|
|
||||
|
<section class="col-lg-7 col-md-6 col-xs-12"> |
||||
|
<a asp-page="/Index" class="navbar-brand"> |
||||
|
<img src="../images/brand.png" alt="eShop On Web" /> |
||||
|
</a> |
||||
|
</section> |
||||
|
@await Html.PartialAsync("_LoginPartial") |
||||
|
</article> |
||||
|
</div> |
||||
|
</header> |
||||
|
|
||||
|
@RenderBody() |
||||
|
|
||||
|
|
||||
|
<footer class="esh-app-footer"> |
||||
|
<div class="container"> |
||||
|
<article class="row"> |
||||
|
|
||||
|
<section class="col-sm-6"></section> |
||||
|
|
||||
|
<section class="col-sm-6"> |
||||
|
<div class="esh-app-footer-text hidden-xs"> e-ShopOnWeb. All rights reserved </div> |
||||
|
</section> |
||||
|
|
||||
|
</article> |
||||
|
</div> |
||||
|
</footer> |
||||
|
|
||||
|
<environment names="Development"> |
||||
|
<script src="~/lib/jquery/dist/jquery.js"></script> |
||||
|
<script src="~/lib/bootstrap/dist/js/bootstrap.js"></script> |
||||
|
<script src="~/js/site.js" asp-append-version="true"></script> |
||||
|
</environment> |
||||
|
<environment names="Staging,Production"> |
||||
|
<script src="https://ajax.aspnetcdn.com/ajax/jquery/jquery-2.2.0.min.js" |
||||
|
asp-fallback-src="~/lib/jquery/dist/jquery.min.js" |
||||
|
asp-fallback-test="window.jQuery"> |
||||
|
</script> |
||||
|
<script src="https://ajax.aspnetcdn.com/ajax/bootstrap/3.3.6/bootstrap.min.js" |
||||
|
asp-fallback-src="~/lib/bootstrap/dist/js/bootstrap.min.js" |
||||
|
asp-fallback-test="window.jQuery && window.jQuery.fn && window.jQuery.fn.modal"> |
||||
|
</script> |
||||
|
<script src="~/js/site.min.js" asp-append-version="true"></script> |
||||
|
</environment> |
||||
|
|
||||
|
@RenderSection("scripts", required: false) |
||||
|
</body> |
||||
|
</html> |
||||
@ -0,0 +1,55 @@ |
|||||
|
@using Microsoft.AspNetCore.Identity |
||||
|
|
||||
|
@if (Context.User.Identity.IsAuthenticated) |
||||
|
{ |
||||
|
<section class="col-lg-4 col-md-5 col-xs-12"> |
||||
|
<div class="esh-identity"> |
||||
|
<form asp-page="/Account/Signout" method="post" |
||||
|
id="logoutForm" class="navbar-right"> |
||||
|
<section class="esh-identity-section"> |
||||
|
@*<div class="esh-identity-name">@User.FindFirst(x => x.Type == "preferred_username").Value</div>*@ |
||||
|
<img class="esh-identity-image" src="~/images/arrow-down.png"> |
||||
|
</section> |
||||
|
|
||||
|
<section class="esh-identity-drop"> |
||||
|
<a class="esh-identity-item" |
||||
|
asp-page="/Order/Index"> |
||||
|
|
||||
|
<div class="esh-identity-name esh-identity-name--upper">My orders</div> |
||||
|
<img class="esh-identity-image" src="~/images/my_orders.png"> |
||||
|
</a> |
||||
|
|
||||
|
<a class="esh-identity-item" |
||||
|
href="javascript:document.getElementById('logoutForm').submit()"> |
||||
|
<div class="esh-identity-name esh-identity-name--upper">Log Out</div> |
||||
|
<img class="esh-identity-image" src="~/images/logout.png"> |
||||
|
</a> |
||||
|
</section> |
||||
|
</form> |
||||
|
</div> |
||||
|
</section> |
||||
|
|
||||
|
<section class="col-lg-1 col-xs-12"> |
||||
|
@await Component.InvokeAsync("Basket", User.Identity.Name) |
||||
|
</section> |
||||
|
|
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
<section class="col-lg-1 col-lg-offset-3 col-md-3 col-xs-6"> |
||||
|
<div class="esh-identity"> |
||||
|
<section class="esh-identity-section"> |
||||
|
<div class="esh-identity-item"> |
||||
|
|
||||
|
<a asp-page="/Account/Signin" class="esh-identity-name esh-identity-name--upper"> |
||||
|
Login |
||||
|
</a> |
||||
|
</div> |
||||
|
</section> |
||||
|
</div> |
||||
|
</section> |
||||
|
|
||||
|
<section class="col-lg-1 col-xs-12"> |
||||
|
@await Component.InvokeAsync("Basket") |
||||
|
</section> |
||||
|
} |
||||
@ -0,0 +1,18 @@ |
|||||
|
<environment include="Development"> |
||||
|
<script src="~/lib/jquery-validation/dist/jquery.validate.js"></script> |
||||
|
<script src="~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.js"></script> |
||||
|
</environment> |
||||
|
<environment exclude="Development"> |
||||
|
<script src="https://ajax.aspnetcdn.com/ajax/jquery.validate/1.14.0/jquery.validate.min.js" |
||||
|
asp-fallback-src="~/lib/jquery-validation/dist/jquery.validate.min.js" |
||||
|
asp-fallback-test="window.jQuery && window.jQuery.validator" |
||||
|
crossorigin="anonymous" |
||||
|
integrity="sha384-Fnqn3nxp3506LP/7Y3j/25BlWeA3PXTyT1l78LjECcPaKCV12TsZP7yyMxOe/G/k"> |
||||
|
</script> |
||||
|
<script src="https://ajax.aspnetcdn.com/ajax/jquery.validation.unobtrusive/3.2.6/jquery.validate.unobtrusive.min.js" |
||||
|
asp-fallback-src="~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js" |
||||
|
asp-fallback-test="window.jQuery && window.jQuery.validator && window.jQuery.validator.unobtrusive" |
||||
|
crossorigin="anonymous" |
||||
|
integrity="sha384-JrXK+k53HACyavUKOsL+NkmSesD2P+73eDMrbTtTk0h4RmOF8hF8apPlkp26JlyH"> |
||||
|
</script> |
||||
|
</environment> |
||||
@ -0,0 +1,4 @@ |
|||||
|
@using Microsoft.eShopWeb.RazorPages |
||||
|
@using Microsoft.eShopWeb.RazorPages.ViewModels |
||||
|
@namespace Microsoft.eShopWeb.RazorPages.Pages |
||||
|
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers |
||||
@ -0,0 +1,3 @@ |
|||||
|
@{ |
||||
|
Layout = "_Layout"; |
||||
|
} |
||||
@ -0,0 +1,34 @@ |
|||||
|
@model PaginationInfoViewModel |
||||
|
|
||||
|
<div class="esh-pager"> |
||||
|
<div class="container-fluid"> |
||||
|
<article class="esh-pager-wrapper row"> |
||||
|
<nav> |
||||
|
<div class="col-md-2 col-xs-12"> |
||||
|
<a class="esh-pager-item-left esh-pager-item--navigable @Model.Previous" |
||||
|
id="Previous" |
||||
|
asp-route-pageid="@(Model.ActualPage - 1)" |
||||
|
aria-label="Previous"> |
||||
|
Previous |
||||
|
</a> |
||||
|
</div> |
||||
|
|
||||
|
<div class="col-md-8 col-xs-12"> |
||||
|
<span class="esh-pager-item"> |
||||
|
Showing @Model.ItemsPerPage of @Model.TotalItems products - Page @(Model.ActualPage + 1) - @Model.TotalPages |
||||
|
</span> |
||||
|
</div> |
||||
|
|
||||
|
<div class="col-md-2 col-xs-12"> |
||||
|
<a class="esh-pager-item-right esh-pager-item--navigable @Model.Next" |
||||
|
id="Next" |
||||
|
asp-route-pageid="@(Model.ActualPage + 1)" |
||||
|
aria-label="Next"> |
||||
|
Next |
||||
|
</a> |
||||
|
</div> |
||||
|
</nav> |
||||
|
</article> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
@ -0,0 +1,24 @@ |
|||||
|
@model CatalogItemViewModel |
||||
|
|
||||
|
|
||||
|
<form asp-page="/Basket/Index" method="post"> |
||||
|
|
||||
|
<img class="esh-catalog-thumbnail" src="@Model.PictureUri" /> |
||||
|
<input class="esh-catalog-button" type="submit" value="[ ADD TO BASKET ]" /> |
||||
|
|
||||
|
<div class="esh-catalog-name"> |
||||
|
<span>@Model.Name</span> |
||||
|
</div> |
||||
|
<div class="esh-catalog-price"> |
||||
|
<span>@Model.Price.ToString("N2")</span> |
||||
|
</div> |
||||
|
@*<input type="hidden" asp-for="@Model.CatalogBrand" name="brand" /> |
||||
|
<input type="hidden" asp-for="@Model.CatalogBrandId" name="brandId" /> |
||||
|
<input type="hidden" asp-for="@Model.CatalogType" name="type" /> |
||||
|
<input type="hidden" asp-for="@Model.CatalogTypeId" name="typeId" /> |
||||
|
<input type="hidden" asp-for="@Model.Description" name="description" />*@ |
||||
|
<input type="hidden" asp-for="@Model.Id" name="id" /> |
||||
|
<input type="hidden" asp-for="@Model.Name" name="name" /> |
||||
|
<input type="hidden" asp-for="@Model.PictureUri" name="pictureUri" /> |
||||
|
<input type="hidden" asp-for="@Model.Price" name="price" /> |
||||
|
</form> |
||||
@ -0,0 +1,47 @@ |
|||||
|
using System; |
||||
|
using Microsoft.AspNetCore; |
||||
|
using Microsoft.AspNetCore.Hosting; |
||||
|
using Microsoft.Extensions.Logging; |
||||
|
using Infrastructure.Data; |
||||
|
using Infrastructure.Identity; |
||||
|
using Microsoft.Extensions.DependencyInjection; |
||||
|
using Microsoft.AspNetCore.Identity; |
||||
|
|
||||
|
namespace Microsoft.eShopWeb.RazorPages |
||||
|
{ |
||||
|
public class Program |
||||
|
{ |
||||
|
public static void Main(string[] args) |
||||
|
{ |
||||
|
var host = BuildWebHost(args); |
||||
|
|
||||
|
using (var scope = host.Services.CreateScope()) |
||||
|
{ |
||||
|
var services = scope.ServiceProvider; |
||||
|
var loggerFactory = services.GetRequiredService<ILoggerFactory>(); |
||||
|
try |
||||
|
{ |
||||
|
var catalogContext = services.GetRequiredService<CatalogContext>(); |
||||
|
CatalogContextSeed.SeedAsync(catalogContext, loggerFactory) |
||||
|
.Wait(); |
||||
|
|
||||
|
var userManager = services.GetRequiredService<UserManager<ApplicationUser>>(); |
||||
|
AppIdentityDbContextSeed.SeedAsync(userManager).Wait(); |
||||
|
} |
||||
|
catch (Exception ex) |
||||
|
{ |
||||
|
var logger = loggerFactory.CreateLogger<Program>(); |
||||
|
logger.LogError(ex, "An error occurred seeding the DB."); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
host.Run(); |
||||
|
} |
||||
|
|
||||
|
public static IWebHost BuildWebHost(string[] args) => |
||||
|
WebHost.CreateDefaultBuilder(args) |
||||
|
.UseUrls("http://0.0.0.0:5106") |
||||
|
.UseStartup<Startup>() |
||||
|
.Build(); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,27 @@ |
|||||
|
{ |
||||
|
"iisSettings": { |
||||
|
"windowsAuthentication": false, |
||||
|
"anonymousAuthentication": true, |
||||
|
"iisExpress": { |
||||
|
"applicationUrl": "http://localhost:28655/", |
||||
|
"sslPort": 0 |
||||
|
} |
||||
|
}, |
||||
|
"profiles": { |
||||
|
"IIS Express": { |
||||
|
"commandName": "IISExpress", |
||||
|
"launchBrowser": true, |
||||
|
"environmentVariables": { |
||||
|
"ASPNETCORE_ENVIRONMENT": "Development" |
||||
|
} |
||||
|
}, |
||||
|
"WebRazorPages": { |
||||
|
"commandName": "Project", |
||||
|
"launchBrowser": true, |
||||
|
"environmentVariables": { |
||||
|
"ASPNETCORE_ENVIRONMENT": "Development" |
||||
|
}, |
||||
|
"applicationUrl": "http://localhost:28656/" |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,118 @@ |
|||||
|
using ApplicationCore.Interfaces; |
||||
|
using System.Threading.Tasks; |
||||
|
using Microsoft.eShopWeb.ApplicationCore.Entities; |
||||
|
using System.Linq; |
||||
|
using System.Collections.Generic; |
||||
|
using ApplicationCore.Specifications; |
||||
|
using Microsoft.eShopWeb.RazorPages.Interfaces; |
||||
|
using Microsoft.eShopWeb.RazorPages.ViewModels; |
||||
|
|
||||
|
namespace Microsoft.eShopWeb.RazorPages.Services |
||||
|
{ |
||||
|
public class BasketService : IBasketService |
||||
|
{ |
||||
|
private readonly IAsyncRepository<Basket> _basketRepository; |
||||
|
private readonly IUriComposer _uriComposer; |
||||
|
private readonly IAppLogger<BasketService> _logger; |
||||
|
private readonly IRepository<CatalogItem> _itemRepository; |
||||
|
|
||||
|
public BasketService(IAsyncRepository<Basket> basketRepository, |
||||
|
IRepository<CatalogItem> itemRepository, |
||||
|
IUriComposer uriComposer, |
||||
|
IAppLogger<BasketService> logger) |
||||
|
{ |
||||
|
_basketRepository = basketRepository; |
||||
|
_uriComposer = uriComposer; |
||||
|
this._logger = logger; |
||||
|
_itemRepository = itemRepository; |
||||
|
} |
||||
|
|
||||
|
public async Task<BasketViewModel> GetOrCreateBasketForUser(string userName) |
||||
|
{ |
||||
|
var basketSpec = new BasketWithItemsSpecification(userName); |
||||
|
var basket = (await _basketRepository.ListAsync(basketSpec)).FirstOrDefault(); |
||||
|
|
||||
|
if(basket == null) |
||||
|
{ |
||||
|
return await CreateBasketForUser(userName); |
||||
|
} |
||||
|
return CreateViewModelFromBasket(basket); |
||||
|
} |
||||
|
|
||||
|
private BasketViewModel CreateViewModelFromBasket(Basket basket) |
||||
|
{ |
||||
|
var viewModel = new BasketViewModel(); |
||||
|
viewModel.Id = basket.Id; |
||||
|
viewModel.BuyerId = basket.BuyerId; |
||||
|
viewModel.Items = basket.Items.Select(i => |
||||
|
{ |
||||
|
var itemModel = new BasketItemViewModel() |
||||
|
{ |
||||
|
Id = i.Id, |
||||
|
UnitPrice = i.UnitPrice, |
||||
|
Quantity = i.Quantity, |
||||
|
CatalogItemId = i.CatalogItemId |
||||
|
|
||||
|
}; |
||||
|
var item = _itemRepository.GetById(i.CatalogItemId); |
||||
|
itemModel.PictureUrl = _uriComposer.ComposePicUri(item.PictureUri); |
||||
|
itemModel.ProductName = item.Name; |
||||
|
return itemModel; |
||||
|
}) |
||||
|
.ToList(); |
||||
|
return viewModel; |
||||
|
} |
||||
|
|
||||
|
public async Task<BasketViewModel> CreateBasketForUser(string userId) |
||||
|
{ |
||||
|
var basket = new Basket() { BuyerId = userId }; |
||||
|
await _basketRepository.AddAsync(basket); |
||||
|
|
||||
|
return new BasketViewModel() |
||||
|
{ |
||||
|
BuyerId = basket.BuyerId, |
||||
|
Id = basket.Id, |
||||
|
Items = new List<BasketItemViewModel>() |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
public async Task AddItemToBasket(int basketId, int catalogItemId, decimal price, int quantity) |
||||
|
{ |
||||
|
var basket = await _basketRepository.GetByIdAsync(basketId); |
||||
|
|
||||
|
basket.AddItem(catalogItemId, price, quantity); |
||||
|
|
||||
|
await _basketRepository.UpdateAsync(basket); |
||||
|
} |
||||
|
|
||||
|
public async Task SetQuantities(int basketId, Dictionary<string,int> quantities) |
||||
|
{ |
||||
|
var basket = await _basketRepository.GetByIdAsync(basketId); |
||||
|
foreach (var item in basket.Items) |
||||
|
{ |
||||
|
if (quantities.TryGetValue(item.Id.ToString(), out var quantity)) |
||||
|
{ |
||||
|
_logger.LogWarning($"Updating quantity of item ID:{item.Id} to {quantity}."); |
||||
|
item.Quantity = quantity; |
||||
|
} |
||||
|
} |
||||
|
await _basketRepository.UpdateAsync(basket); |
||||
|
} |
||||
|
|
||||
|
public async Task DeleteBasketAsync(int basketId) |
||||
|
{ |
||||
|
var basket = await _basketRepository.GetByIdAsync(basketId); |
||||
|
|
||||
|
await _basketRepository.DeleteAsync(basket); |
||||
|
} |
||||
|
|
||||
|
public async Task TransferBasketAsync(string anonymousId, string userName) |
||||
|
{ |
||||
|
var basketSpec = new BasketWithItemsSpecification(anonymousId); |
||||
|
var basket = (await _basketRepository.ListAsync(basketSpec)).FirstOrDefault(); |
||||
|
if (basket == null) return; |
||||
|
basket.BuyerId = userName; |
||||
|
await _basketRepository.UpdateAsync(basket); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,55 @@ |
|||||
|
using Microsoft.AspNetCore.Mvc.Rendering; |
||||
|
using Microsoft.eShopWeb.RazorPages.Interfaces; |
||||
|
using Microsoft.eShopWeb.RazorPages.ViewModels; |
||||
|
using Microsoft.Extensions.Caching.Memory; |
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Threading.Tasks; |
||||
|
|
||||
|
namespace Microsoft.eShopWeb.RazorPages.Services |
||||
|
{ |
||||
|
public class CachedCatalogService : ICatalogService |
||||
|
{ |
||||
|
private readonly IMemoryCache _cache; |
||||
|
private readonly CatalogService _catalogService; |
||||
|
private static readonly string _brandsKey = "brands"; |
||||
|
private static readonly string _typesKey = "types"; |
||||
|
private static readonly string _itemsKeyTemplate = "items-{0}-{1}-{2}-{3}"; |
||||
|
private static readonly TimeSpan _defaultCacheDuration = TimeSpan.FromSeconds(30); |
||||
|
|
||||
|
public CachedCatalogService(IMemoryCache cache, |
||||
|
CatalogService catalogService) |
||||
|
{ |
||||
|
_cache = cache; |
||||
|
_catalogService = catalogService; |
||||
|
} |
||||
|
|
||||
|
public async Task<IEnumerable<SelectListItem>> GetBrands() |
||||
|
{ |
||||
|
return await _cache.GetOrCreateAsync(_brandsKey, async entry => |
||||
|
{ |
||||
|
entry.SlidingExpiration = _defaultCacheDuration; |
||||
|
return await _catalogService.GetBrands(); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
public async Task<CatalogIndexViewModel> GetCatalogItems(int pageIndex, int itemsPage, int? brandID, int? typeId) |
||||
|
{ |
||||
|
string cacheKey = String.Format(_itemsKeyTemplate, pageIndex, itemsPage, brandID, typeId); |
||||
|
return await _cache.GetOrCreateAsync(cacheKey, async entry => |
||||
|
{ |
||||
|
entry.SlidingExpiration = _defaultCacheDuration; |
||||
|
return await _catalogService.GetCatalogItems(pageIndex, itemsPage, brandID, typeId); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
public async Task<IEnumerable<SelectListItem>> GetTypes() |
||||
|
{ |
||||
|
return await _cache.GetOrCreateAsync(_typesKey, async entry => |
||||
|
{ |
||||
|
entry.SlidingExpiration = _defaultCacheDuration; |
||||
|
return await _catalogService.GetTypes(); |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,117 @@ |
|||||
|
using ApplicationCore.Interfaces; |
||||
|
using ApplicationCore.Specifications; |
||||
|
using Microsoft.AspNetCore.Mvc.Rendering; |
||||
|
using Microsoft.eShopWeb.ApplicationCore.Entities; |
||||
|
using Microsoft.eShopWeb.RazorPages.Interfaces; |
||||
|
using Microsoft.eShopWeb.RazorPages.ViewModels; |
||||
|
using Microsoft.Extensions.Logging; |
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
using System.Threading.Tasks; |
||||
|
|
||||
|
namespace Microsoft.eShopWeb.RazorPages.Services |
||||
|
{ |
||||
|
public class CatalogService : ICatalogService |
||||
|
{ |
||||
|
private readonly ILogger<CatalogService> _logger; |
||||
|
private readonly IRepository<CatalogItem> _itemRepository; |
||||
|
private readonly IAsyncRepository<CatalogBrand> _brandRepository; |
||||
|
private readonly IAsyncRepository<CatalogType> _typeRepository; |
||||
|
private readonly IUriComposer _uriComposer; |
||||
|
|
||||
|
public CatalogService( |
||||
|
ILoggerFactory loggerFactory, |
||||
|
IRepository<CatalogItem> itemRepository, |
||||
|
IAsyncRepository<CatalogBrand> brandRepository, |
||||
|
IAsyncRepository<CatalogType> typeRepository, |
||||
|
IUriComposer uriComposer) |
||||
|
{ |
||||
|
_logger = loggerFactory.CreateLogger<CatalogService>(); |
||||
|
_itemRepository = itemRepository; |
||||
|
_brandRepository = brandRepository; |
||||
|
_typeRepository = typeRepository; |
||||
|
_uriComposer = uriComposer; |
||||
|
} |
||||
|
|
||||
|
public async Task<CatalogIndexViewModel> GetCatalogItems(int pageIndex, int itemsPage, int? brandId, int? typeId) |
||||
|
{ |
||||
|
_logger.LogInformation("GetCatalogItems called."); |
||||
|
|
||||
|
var filterSpecification = new CatalogFilterSpecification(brandId, typeId); |
||||
|
var root = _itemRepository.List(filterSpecification); |
||||
|
|
||||
|
var totalItems = root.Count(); |
||||
|
|
||||
|
var itemsOnPage = root |
||||
|
.Skip(itemsPage * pageIndex) |
||||
|
.Take(itemsPage) |
||||
|
.ToList(); |
||||
|
|
||||
|
itemsOnPage.ForEach(x => |
||||
|
{ |
||||
|
x.PictureUri = _uriComposer.ComposePicUri(x.PictureUri); |
||||
|
}); |
||||
|
|
||||
|
var vm = new CatalogIndexViewModel() |
||||
|
{ |
||||
|
CatalogItems = itemsOnPage.Select(i => new CatalogItemViewModel() |
||||
|
{ |
||||
|
Id = i.Id, |
||||
|
Name = i.Name, |
||||
|
PictureUri = i.PictureUri, |
||||
|
Price = i.Price |
||||
|
}), |
||||
|
Brands = await GetBrands(), |
||||
|
Types = await GetTypes(), |
||||
|
BrandFilterApplied = brandId ?? 0, |
||||
|
TypesFilterApplied = typeId ?? 0, |
||||
|
PaginationInfo = new PaginationInfoViewModel() |
||||
|
{ |
||||
|
ActualPage = pageIndex, |
||||
|
ItemsPerPage = itemsOnPage.Count, |
||||
|
TotalItems = totalItems, |
||||
|
TotalPages = int.Parse(Math.Ceiling(((decimal)totalItems / itemsPage)).ToString()) |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
vm.PaginationInfo.Next = (vm.PaginationInfo.ActualPage == vm.PaginationInfo.TotalPages - 1) ? "is-disabled" : ""; |
||||
|
vm.PaginationInfo.Previous = (vm.PaginationInfo.ActualPage == 0) ? "is-disabled" : ""; |
||||
|
|
||||
|
return vm; |
||||
|
} |
||||
|
|
||||
|
public async Task<IEnumerable<SelectListItem>> GetBrands() |
||||
|
{ |
||||
|
_logger.LogInformation("GetBrands called."); |
||||
|
var brands = await _brandRepository.ListAllAsync(); |
||||
|
|
||||
|
var items = new List<SelectListItem> |
||||
|
{ |
||||
|
new SelectListItem() { Value = null, Text = "All", Selected = true } |
||||
|
}; |
||||
|
foreach (CatalogBrand brand in brands) |
||||
|
{ |
||||
|
items.Add(new SelectListItem() { Value = brand.Id.ToString(), Text = brand.Brand }); |
||||
|
} |
||||
|
|
||||
|
return items; |
||||
|
} |
||||
|
|
||||
|
public async Task<IEnumerable<SelectListItem>> GetTypes() |
||||
|
{ |
||||
|
_logger.LogInformation("GetTypes called."); |
||||
|
var types = await _typeRepository.ListAllAsync(); |
||||
|
var items = new List<SelectListItem> |
||||
|
{ |
||||
|
new SelectListItem() { Value = null, Text = "All", Selected = true } |
||||
|
}; |
||||
|
foreach (CatalogType type in types) |
||||
|
{ |
||||
|
items.Add(new SelectListItem() { Value = type.Id.ToString(), Text = type.Type }); |
||||
|
} |
||||
|
|
||||
|
return items; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,156 @@ |
|||||
|
using ApplicationCore.Interfaces; |
||||
|
using ApplicationCore.Services; |
||||
|
using Infrastructure.Data; |
||||
|
using Infrastructure.Identity; |
||||
|
using Infrastructure.Logging; |
||||
|
using Infrastructure.Services; |
||||
|
using Microsoft.AspNetCore.Builder; |
||||
|
using Microsoft.AspNetCore.Hosting; |
||||
|
using Microsoft.AspNetCore.Http; |
||||
|
using Microsoft.AspNetCore.Identity; |
||||
|
using Microsoft.EntityFrameworkCore; |
||||
|
using Microsoft.eShopWeb.RazorPages.Interfaces; |
||||
|
using Microsoft.eShopWeb.RazorPages.Services; |
||||
|
using Microsoft.Extensions.Configuration; |
||||
|
using Microsoft.Extensions.DependencyInjection; |
||||
|
using System; |
||||
|
using System.Text; |
||||
|
|
||||
|
namespace Microsoft.eShopWeb.RazorPages |
||||
|
{ |
||||
|
public class Startup |
||||
|
{ |
||||
|
private IServiceCollection _services; |
||||
|
public Startup(IConfiguration configuration) |
||||
|
{ |
||||
|
Configuration = configuration; |
||||
|
} |
||||
|
|
||||
|
public IConfiguration Configuration { get; } |
||||
|
|
||||
|
public void ConfigureDevelopmentServices(IServiceCollection services) |
||||
|
{ |
||||
|
// use in-memory database
|
||||
|
ConfigureTestingServices(services); |
||||
|
|
||||
|
// use real database
|
||||
|
// ConfigureProductionServices(services);
|
||||
|
|
||||
|
} |
||||
|
public void ConfigureTestingServices(IServiceCollection services) |
||||
|
{ |
||||
|
// use in-memory database
|
||||
|
services.AddDbContext<CatalogContext>(c => |
||||
|
c.UseInMemoryDatabase("Catalog")); |
||||
|
|
||||
|
// Add Identity DbContext
|
||||
|
services.AddDbContext<AppIdentityDbContext>(options => |
||||
|
options.UseInMemoryDatabase("Identity")); |
||||
|
|
||||
|
ConfigureServices(services); |
||||
|
} |
||||
|
|
||||
|
public void ConfigureProductionServices(IServiceCollection services) |
||||
|
{ |
||||
|
// use real database
|
||||
|
services.AddDbContext<CatalogContext>(c => |
||||
|
{ |
||||
|
try |
||||
|
{ |
||||
|
// Requires LocalDB which can be installed with SQL Server Express 2016
|
||||
|
// https://www.microsoft.com/en-us/download/details.aspx?id=54284
|
||||
|
c.UseSqlServer(Configuration.GetConnectionString("CatalogConnection")); |
||||
|
} |
||||
|
catch (System.Exception ex) |
||||
|
{ |
||||
|
var message = ex.Message; |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
// Add Identity DbContext
|
||||
|
services.AddDbContext<AppIdentityDbContext>(options => |
||||
|
options.UseSqlServer(Configuration.GetConnectionString("IdentityConnection"))); |
||||
|
|
||||
|
ConfigureServices(services); |
||||
|
} |
||||
|
|
||||
|
public void ConfigureServices(IServiceCollection services) |
||||
|
{ |
||||
|
services.AddIdentity<ApplicationUser, IdentityRole>() |
||||
|
.AddEntityFrameworkStores<AppIdentityDbContext>() |
||||
|
.AddDefaultTokenProviders(); |
||||
|
|
||||
|
services.ConfigureApplicationCookie(options => |
||||
|
{ |
||||
|
options.Cookie.HttpOnly = true; |
||||
|
options.ExpireTimeSpan = TimeSpan.FromHours(1); |
||||
|
options.LoginPath = "/Account/Signin"; |
||||
|
options.LogoutPath = "/Account/Signout"; |
||||
|
}); |
||||
|
|
||||
|
services.AddScoped(typeof(IRepository<>), typeof(EfRepository<>)); |
||||
|
services.AddScoped(typeof(IAsyncRepository<>), typeof(EfRepository<>)); |
||||
|
|
||||
|
services.AddScoped<ICatalogService, CachedCatalogService>(); |
||||
|
services.AddScoped<IBasketService, BasketService>(); |
||||
|
services.AddScoped<IOrderService, OrderService>(); |
||||
|
services.AddScoped<IOrderRepository, OrderRepository>(); |
||||
|
services.AddScoped<CatalogService>(); |
||||
|
services.Configure<CatalogSettings>(Configuration); |
||||
|
services.AddSingleton<IUriComposer>(new UriComposer(Configuration.Get<CatalogSettings>())); |
||||
|
|
||||
|
services.AddScoped(typeof(IAppLogger<>), typeof(LoggerAdapter<>)); |
||||
|
|
||||
|
// Add memory cache services
|
||||
|
services.AddMemoryCache(); |
||||
|
|
||||
|
services.AddMvc(); |
||||
|
|
||||
|
_services = services; |
||||
|
} |
||||
|
|
||||
|
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
|
||||
|
public void Configure(IApplicationBuilder app, |
||||
|
IHostingEnvironment env) |
||||
|
{ |
||||
|
if (env.IsDevelopment()) |
||||
|
{ |
||||
|
app.UseDeveloperExceptionPage(); |
||||
|
app.UseBrowserLink(); |
||||
|
ListAllRegisteredServices(app); |
||||
|
app.UseDatabaseErrorPage(); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
app.UseExceptionHandler("/Catalog/Error"); |
||||
|
} |
||||
|
|
||||
|
app.UseStaticFiles(); |
||||
|
app.UseAuthentication(); |
||||
|
|
||||
|
app.UseMvc(); |
||||
|
} |
||||
|
|
||||
|
private void ListAllRegisteredServices(IApplicationBuilder app) |
||||
|
{ |
||||
|
app.Map("/allservices", builder => builder.Run(async context => |
||||
|
{ |
||||
|
var sb = new StringBuilder(); |
||||
|
sb.Append("<h1>All Services</h1>"); |
||||
|
sb.Append("<table><thead>"); |
||||
|
sb.Append("<tr><th>Type</th><th>Lifetime</th><th>Instance</th></tr>"); |
||||
|
sb.Append("</thead><tbody>"); |
||||
|
foreach (var svc in _services) |
||||
|
{ |
||||
|
sb.Append("<tr>"); |
||||
|
sb.Append($"<td>{svc.ServiceType.FullName}</td>"); |
||||
|
sb.Append($"<td>{svc.Lifetime}</td>"); |
||||
|
sb.Append($"<td>{svc.ImplementationType?.FullName}</td>"); |
||||
|
sb.Append("</tr>"); |
||||
|
} |
||||
|
sb.Append("</tbody></table>"); |
||||
|
await context.Response.WriteAsync(sb.ToString()); |
||||
|
})); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,51 @@ |
|||||
|
using Infrastructure.Identity; |
||||
|
using Microsoft.AspNetCore.Http; |
||||
|
using Microsoft.AspNetCore.Identity; |
||||
|
using Microsoft.AspNetCore.Mvc; |
||||
|
using Microsoft.eShopWeb.RazorPages.Interfaces; |
||||
|
using Microsoft.eShopWeb.RazorPages.ViewModels; |
||||
|
using System.Linq; |
||||
|
using System.Threading.Tasks; |
||||
|
|
||||
|
namespace Microsoft.eShopWeb.RazorPages.ViewComponents |
||||
|
{ |
||||
|
public class Basket : ViewComponent |
||||
|
{ |
||||
|
private readonly IBasketService _basketService; |
||||
|
private readonly SignInManager<ApplicationUser> _signInManager; |
||||
|
|
||||
|
public Basket(IBasketService basketService, |
||||
|
SignInManager<ApplicationUser> signInManager) |
||||
|
{ |
||||
|
_basketService = basketService; |
||||
|
_signInManager = signInManager; |
||||
|
} |
||||
|
|
||||
|
public async Task<IViewComponentResult> InvokeAsync(string userName) |
||||
|
{ |
||||
|
var vm = new BasketComponentViewModel(); |
||||
|
vm.ItemsCount = (await GetBasketViewModelAsync()).Items.Sum(i => i.Quantity); |
||||
|
return View(vm); |
||||
|
} |
||||
|
|
||||
|
private async Task<BasketViewModel> GetBasketViewModelAsync() |
||||
|
{ |
||||
|
if (_signInManager.IsSignedIn(HttpContext.User)) |
||||
|
{ |
||||
|
return await _basketService.GetOrCreateBasketForUser(User.Identity.Name); |
||||
|
} |
||||
|
string anonymousId = GetBasketIdFromCookie(); |
||||
|
if (anonymousId == null) return new BasketViewModel(); |
||||
|
return await _basketService.GetOrCreateBasketForUser(anonymousId); |
||||
|
} |
||||
|
|
||||
|
private string GetBasketIdFromCookie() |
||||
|
{ |
||||
|
if (Request.Cookies.ContainsKey(Constants.BASKET_COOKIENAME)) |
||||
|
{ |
||||
|
return Request.Cookies[Constants.BASKET_COOKIENAME]; |
||||
|
} |
||||
|
return null; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,7 @@ |
|||||
|
namespace Microsoft.eShopWeb.RazorPages.ViewModels |
||||
|
{ |
||||
|
public class BasketComponentViewModel |
||||
|
{ |
||||
|
public int ItemsCount { get; set; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,14 @@ |
|||||
|
namespace Microsoft.eShopWeb.RazorPages.ViewModels |
||||
|
{ |
||||
|
|
||||
|
public class BasketItemViewModel |
||||
|
{ |
||||
|
public int Id { get; set; } |
||||
|
public int CatalogItemId { get; set; } |
||||
|
public string ProductName { get; set; } |
||||
|
public decimal UnitPrice { get; set; } |
||||
|
public decimal OldUnitPrice { get; set; } |
||||
|
public int Quantity { get; set; } |
||||
|
public string PictureUrl { get; set; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,19 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
|
||||
|
namespace Microsoft.eShopWeb.RazorPages.ViewModels |
||||
|
{ |
||||
|
|
||||
|
public class BasketViewModel |
||||
|
{ |
||||
|
public int Id { get; set; } |
||||
|
public List<BasketItemViewModel> Items { get; set; } = new List<BasketItemViewModel>(); |
||||
|
public string BuyerId { get; set; } |
||||
|
|
||||
|
public decimal Total() |
||||
|
{ |
||||
|
return Math.Round(Items.Sum(x => x.UnitPrice * x.Quantity), 2); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,15 @@ |
|||||
|
using Microsoft.AspNetCore.Mvc.Rendering; |
||||
|
using System.Collections.Generic; |
||||
|
|
||||
|
namespace Microsoft.eShopWeb.RazorPages.ViewModels |
||||
|
{ |
||||
|
public class CatalogIndexViewModel |
||||
|
{ |
||||
|
public IEnumerable<CatalogItemViewModel> CatalogItems { get; set; } |
||||
|
public IEnumerable<SelectListItem> Brands { get; set; } |
||||
|
public IEnumerable<SelectListItem> Types { get; set; } |
||||
|
public int? BrandFilterApplied { get; set; } |
||||
|
public int? TypesFilterApplied { get; set; } |
||||
|
public PaginationInfoViewModel PaginationInfo { get; set; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,11 @@ |
|||||
|
namespace Microsoft.eShopWeb.RazorPages.ViewModels |
||||
|
{ |
||||
|
|
||||
|
public class CatalogItemViewModel |
||||
|
{ |
||||
|
public int Id { get; set; } |
||||
|
public string Name { get; set; } |
||||
|
public string PictureUri { get; set; } |
||||
|
public decimal Price { get; set; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,18 @@ |
|||||
|
namespace Microsoft.eShopWeb.RazorPages.ViewModels |
||||
|
{ |
||||
|
public class OrderItemViewModel |
||||
|
{ |
||||
|
public int ProductId { get; set; } |
||||
|
|
||||
|
public string ProductName { get; set; } |
||||
|
|
||||
|
public decimal UnitPrice { get; set; } |
||||
|
|
||||
|
public decimal Discount { get; set; } |
||||
|
|
||||
|
public int Units { get; set; } |
||||
|
|
||||
|
public string PictureUrl { get; set; } |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,18 @@ |
|||||
|
using ApplicationCore.Entities.OrderAggregate; |
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
|
||||
|
namespace Microsoft.eShopWeb.RazorPages.ViewModels |
||||
|
{ |
||||
|
public class OrderViewModel |
||||
|
{ |
||||
|
public int OrderNumber { get; set; } |
||||
|
public DateTimeOffset OrderDate { get; set; } |
||||
|
public decimal Total { get; set; } |
||||
|
public string Status { get; set; } |
||||
|
|
||||
|
public Address ShippingAddress { get; set; } |
||||
|
|
||||
|
public List<OrderItemViewModel> OrderItems { get; set; } = new List<OrderItemViewModel>(); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,12 @@ |
|||||
|
namespace Microsoft.eShopWeb.RazorPages.ViewModels |
||||
|
{ |
||||
|
public class PaginationInfoViewModel |
||||
|
{ |
||||
|
public int TotalItems { get; set; } |
||||
|
public int ItemsPerPage { get; set; } |
||||
|
public int ActualPage { get; set; } |
||||
|
public int TotalPages { get; set; } |
||||
|
public string Previous { get; set; } |
||||
|
public string Next { get; set; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,24 @@ |
|||||
|
using System; |
||||
|
using System.ComponentModel.DataAnnotations; |
||||
|
|
||||
|
namespace Microsoft.eShopWeb.RazorPages.ViewModels |
||||
|
{ |
||||
|
public class RegisterViewModel |
||||
|
{ |
||||
|
[Required] |
||||
|
[EmailAddress] |
||||
|
[Display(Name = "Email")] |
||||
|
public string Email { get; set; } |
||||
|
|
||||
|
[Required] |
||||
|
[StringLength(100, ErrorMessage = "The {0} must be at least {2} characters long.", MinimumLength = 6)] |
||||
|
[DataType(DataType.Password)] |
||||
|
[Display(Name = "Password")] |
||||
|
public string Password { get; set; } |
||||
|
|
||||
|
[DataType(DataType.Password)] |
||||
|
[Display(Name = "Confirm password")] |
||||
|
[Compare("Password", ErrorMessage = "The password and confirmation password do not match.")] |
||||
|
public string ConfirmPassword { get; set; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,17 @@ |
|||||
|
<Project Sdk="Microsoft.NET.Sdk.Web"> |
||||
|
<PropertyGroup> |
||||
|
<TargetFramework>netcoreapp2.0</TargetFramework> |
||||
|
<RootNamespace>Microsoft.eShopWeb.RazorPages</RootNamespace> |
||||
|
<AssemblyName>Microsoft.eShopWeb.RazorPages</AssemblyName> |
||||
|
</PropertyGroup> |
||||
|
<ItemGroup> |
||||
|
<PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.0" /> |
||||
|
</ItemGroup> |
||||
|
<ItemGroup> |
||||
|
<DotNetCliToolReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Tools" Version="2.0.0" /> |
||||
|
</ItemGroup> |
||||
|
<ItemGroup> |
||||
|
<ProjectReference Include="..\ApplicationCore\ApplicationCore.csproj" /> |
||||
|
<ProjectReference Include="..\Infrastructure\Infrastructure.csproj" /> |
||||
|
</ItemGroup> |
||||
|
</Project> |
||||
@ -0,0 +1,10 @@ |
|||||
|
{ |
||||
|
"Logging": { |
||||
|
"IncludeScopes": false, |
||||
|
"LogLevel": { |
||||
|
"Default": "Debug", |
||||
|
"System": "Information", |
||||
|
"Microsoft": "Information" |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,8 @@ |
|||||
|
{ |
||||
|
"Logging": { |
||||
|
"IncludeScopes": false, |
||||
|
"LogLevel": { |
||||
|
"Default": "Warning" |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,10 @@ |
|||||
|
{ |
||||
|
"name": "asp.net", |
||||
|
"private": true, |
||||
|
"dependencies": { |
||||
|
"bootstrap": "3.3.7", |
||||
|
"jquery": "2.2.0", |
||||
|
"jquery-validation": "1.14.0", |
||||
|
"jquery-validation-unobtrusive": "3.2.6" |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,24 @@ |
|||||
|
// Configure bundling and minification for the project. |
||||
|
// More info at https://go.microsoft.com/fwlink/?LinkId=808241 |
||||
|
[ |
||||
|
{ |
||||
|
"outputFileName": "wwwroot/css/site.min.css", |
||||
|
// An array of relative input file paths. Globbing patterns supported |
||||
|
"inputFiles": [ |
||||
|
"wwwroot/css/site.css" |
||||
|
] |
||||
|
}, |
||||
|
{ |
||||
|
"outputFileName": "wwwroot/js/site.min.js", |
||||
|
"inputFiles": [ |
||||
|
"wwwroot/js/site.js" |
||||
|
], |
||||
|
// Optionally specify minification options |
||||
|
"minify": { |
||||
|
"enabled": true, |
||||
|
"renameLocals": true |
||||
|
}, |
||||
|
// Optionally generate .map file |
||||
|
"sourceMap": false |
||||
|
} |
||||
|
] |
||||
@ -0,0 +1,65 @@ |
|||||
|
// Colors |
||||
|
$color-brand: #00A69C; |
||||
|
$color-brand-dark: darken($color-brand, 10%); |
||||
|
$color-brand-darker: darken($color-brand, 20%); |
||||
|
$color-brand-bright: lighten($color-brand, 10%); |
||||
|
$color-brand-brighter: lighten($color-brand, 20%); |
||||
|
|
||||
|
$color-secondary: #83D01B; |
||||
|
$color-secondary-dark: darken($color-secondary, 5%); |
||||
|
$color-secondary-darker: darken($color-secondary, 20%); |
||||
|
$color-secondary-bright: lighten($color-secondary, 10%); |
||||
|
$color-secondary-brighter: lighten($color-secondary, 20%); |
||||
|
|
||||
|
$color-warning: #ff0000; |
||||
|
$color-warning-dark: darken($color-warning, 5%); |
||||
|
$color-warning-darker: darken($color-warning, 20%); |
||||
|
$color-warning-bright: lighten($color-warning, 10%); |
||||
|
$color-warning-brighter: lighten($color-warning, 20%); |
||||
|
|
||||
|
|
||||
|
$color-background-dark: #333333; |
||||
|
$color-background-darker: #000000; |
||||
|
$color-background-bright: #EEEEFF; |
||||
|
$color-background-brighter: #FFFFFF; |
||||
|
|
||||
|
$color-foreground-dark: #333333; |
||||
|
$color-foreground-darker: #000000; |
||||
|
$color-foreground-bright: #EEEEEE; |
||||
|
$color-foreground-brighter: #FFFFFF; |
||||
|
|
||||
|
// Animations |
||||
|
$animation-speed-default: .35s; |
||||
|
$animation-speed-slow: .5s; |
||||
|
$animation-speed-fast: .15s; |
||||
|
|
||||
|
// Fonts |
||||
|
$font-weight-light: 200; |
||||
|
$font-weight-semilight: 300; |
||||
|
$font-weight-normal: 400; |
||||
|
$font-weight-semibold: 600; |
||||
|
$font-weight-bold: 700; |
||||
|
|
||||
|
$font-size-xs: .65rem; // 10.4px |
||||
|
$font-size-s: .85rem; // 13.6px |
||||
|
$font-size-m: 1rem; // 16px |
||||
|
$font-size-l: 1.25rem; // 20px |
||||
|
$font-size-xl: 1.5rem; // 24px |
||||
|
|
||||
|
// Medias |
||||
|
$media-screen-xxs: 360px; |
||||
|
$media-screen-xs: 640px; |
||||
|
$media-screen-s: 768px; |
||||
|
$media-screen-m: 1024px; |
||||
|
$media-screen-l: 1280px; |
||||
|
$media-screen-xl: 1440px; |
||||
|
$media-screen-xxl: 1680px; |
||||
|
$media-screen-xxxl: 1920px; |
||||
|
|
||||
|
// Borders |
||||
|
$border-light: 1px; |
||||
|
|
||||
|
// Images |
||||
|
$image_path: '../../images/'; |
||||
|
$image-main_banner: '#{$image_path}main_banner.png'; |
||||
|
$image-arrow_down: '#{$image_path}arrow-down.png'; |
||||
@ -0,0 +1,11 @@ |
|||||
|
.esh-app-footer { |
||||
|
background-color: #000000; |
||||
|
border-top: 1px solid #EEEEEE; |
||||
|
margin-top: 2.5rem; |
||||
|
padding-bottom: 2.5rem; |
||||
|
padding-top: 2.5rem; |
||||
|
width: 100%; } |
||||
|
.esh-app-footer-brand { |
||||
|
height: 50px; |
||||
|
width: 230px; } |
||||
|
|
||||
@ -0,0 +1 @@ |
|||||
|
.esh-app-footer{background-color:#000;border-top:1px solid #eee;margin-top:2.5rem;padding-bottom:2.5rem;padding-top:2.5rem;width:100%}.esh-app-footer-brand{height:50px;width:230px} |
||||
@ -0,0 +1,23 @@ |
|||||
|
@import './variables'; |
||||
|
|
||||
|
.esh-app { |
||||
|
&-footer { |
||||
|
$margin: 2.5rem; |
||||
|
$padding: 2.5rem; |
||||
|
|
||||
|
background-color: $color-background-darker; |
||||
|
border-top: $border-light solid $color-foreground-bright; |
||||
|
margin-top: $margin; |
||||
|
padding-bottom: $padding; |
||||
|
padding-top: $padding; |
||||
|
width: 100%; |
||||
|
|
||||
|
$height: 50px; |
||||
|
|
||||
|
&-brand { |
||||
|
height: $height; |
||||
|
width: 230px; |
||||
|
} |
||||
|
|
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,86 @@ |
|||||
|
@font-face { |
||||
|
font-family: Montserrat; |
||||
|
font-weight: 400; |
||||
|
src: url(".../fonts/Montserrat-Regular.eot?") format("eot"), url("../fonts/Montserrat-Regular.woff") format("woff"), url("../fonts/Montserrat-Regular.ttf") format("truetype"), url("../fonts/Montserrat-Regular.svg#Montserrat") format("svg"); |
||||
|
} |
||||
|
|
||||
|
@font-face { |
||||
|
font-family: Montserrat; |
||||
|
font-weight: 700; |
||||
|
src: url("../fonts/Montserrat-Bold.eot?") format("eot"), url("../fonts/Montserrat-Bold.woff") format("woff"), url("../fonts/Montserrat-Bold.ttf") format("truetype"), url("../fonts/Montserrat-Bold.svg#Montserrat") format("svg"); |
||||
|
} |
||||
|
|
||||
|
html, |
||||
|
body { |
||||
|
font-family: Montserrat, sans-serif; |
||||
|
font-size: 16px; |
||||
|
font-weight: 400; |
||||
|
z-index: 10; |
||||
|
} |
||||
|
|
||||
|
*, |
||||
|
*::after, |
||||
|
*::before { |
||||
|
box-sizing: border-box; |
||||
|
} |
||||
|
|
||||
|
.preloading { |
||||
|
color: #00A69C; |
||||
|
display: block; |
||||
|
font-size: 1.5rem; |
||||
|
left: 50%; |
||||
|
position: fixed; |
||||
|
top: 50%; |
||||
|
transform: translate(-50%, -50%); |
||||
|
} |
||||
|
|
||||
|
select::-ms-expand { |
||||
|
display: none; |
||||
|
} |
||||
|
|
||||
|
@media screen and (min-width: 992px) { |
||||
|
.form-input { |
||||
|
max-width: 360px; |
||||
|
width: 360px; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.form-input { |
||||
|
border-radius: 0; |
||||
|
height: 45px; |
||||
|
padding: 10px; |
||||
|
} |
||||
|
|
||||
|
.form-input-small { |
||||
|
max-width: 100px !important; |
||||
|
} |
||||
|
|
||||
|
.form-input-medium { |
||||
|
width: 150px !important; |
||||
|
} |
||||
|
|
||||
|
.alert { |
||||
|
padding-left: 0; |
||||
|
} |
||||
|
|
||||
|
.alert-danger { |
||||
|
background-color: transparent; |
||||
|
border: 0; |
||||
|
color: #FB0D0D; |
||||
|
font-size: 12px; |
||||
|
} |
||||
|
|
||||
|
a, |
||||
|
a:active, |
||||
|
a:hover, |
||||
|
a:visited { |
||||
|
color: #000; |
||||
|
text-decoration: none; |
||||
|
transition: color 0.35s; |
||||
|
} |
||||
|
|
||||
|
a:hover, |
||||
|
a:active { |
||||
|
color: #75B918; |
||||
|
transition: color 0.35s; |
||||
|
} |
||||
@ -0,0 +1 @@ |
|||||
|
@font-face{font-family:Montserrat;font-weight:400;src:url("../fonts/Montserrat-Regular.eot?") format("eot"),url("../fonts/Montserrat-Regular.woff") format("woff"),url("../fonts/Montserrat-Regular.ttf") format("truetype"),url("../fonts/Montserrat-Regular.svg#Montserrat") format("svg")}@font-face{font-family:Montserrat;font-weight:700;src:url("../fonts/Montserrat-Bold.eot?") format("eot"),url("../fonts/Montserrat-Bold.woff") format("woff"),url("../fonts/Montserrat-Bold.ttf") format("truetype"),url("../fonts/Montserrat-Bold.svg#Montserrat") format("svg")}html,body{font-family:Montserrat,sans-serif;font-size:16px;font-weight:400;z-index:10}*,*::after,*::before{box-sizing:border-box}.preloading{color:#00a69c;display:block;font-size:1.5rem;left:50%;position:fixed;top:50%;transform:translate(-50%,-50%)}select::-ms-expand{display:none}@media screen and (min-width:992px){.form-input{max-width:360px;width:360px}}.form-input{border-radius:0;height:45px;padding:10px}.form-input-small{max-width:100px !important}.form-input-medium{width:150px !important}.alert{padding-left:0}.alert-danger{background-color:transparent;border:0;color:#fb0d0d;font-size:12px}a,a:active,a:hover,a:visited{color:#000;text-decoration:none;transition:color .35s}a:hover,a:active{color:#75b918;transition:color .35s} |
||||
@ -0,0 +1,43 @@ |
|||||
|
.esh-basketstatus { |
||||
|
cursor: pointer; |
||||
|
display: inline-block; |
||||
|
float: right; |
||||
|
position: relative; |
||||
|
transition: all 0.35s; } |
||||
|
.esh-basketstatus.is-disabled { |
||||
|
opacity: .5; |
||||
|
pointer-events: none; } |
||||
|
.esh-basketstatus-image { |
||||
|
height: 36px; |
||||
|
margin-top: .5rem; } |
||||
|
.esh-basketstatus-badge { |
||||
|
background-color: #83D01B; |
||||
|
border-radius: 50%; |
||||
|
color: #FFFFFF; |
||||
|
display: block; |
||||
|
height: 1.5rem; |
||||
|
left: 50%; |
||||
|
position: absolute; |
||||
|
text-align: center; |
||||
|
top: 0; |
||||
|
transform: translateX(-38%); |
||||
|
transition: all 0.35s; |
||||
|
width: 1.5rem; } |
||||
|
.esh-basketstatus-badge-inoperative { |
||||
|
background-color: #ff0000; |
||||
|
border-radius: 50%; |
||||
|
color: #FFFFFF; |
||||
|
display: block; |
||||
|
height: 1.5rem; |
||||
|
left: 50%; |
||||
|
position: absolute; |
||||
|
text-align: center; |
||||
|
top: 0; |
||||
|
transform: translateX(-38%); |
||||
|
transition: all 0.35s; |
||||
|
width: 1.5rem; } |
||||
|
.esh-basketstatus:hover .esh-basketstatus-badge { |
||||
|
background-color: transparent; |
||||
|
color: #75b918; |
||||
|
transition: all 0.35s; } |
||||
|
|
||||
@ -0,0 +1 @@ |
|||||
|
.esh-basketstatus{cursor:pointer;display:inline-block;float:right;position:relative;transition:all .35s}.esh-basketstatus.is-disabled{opacity:.5;pointer-events:none}.esh-basketstatus-image{height:36px;margin-top:.5rem}.esh-basketstatus-badge{background-color:#83d01b;border-radius:50%;color:#fff;display:block;height:1.5rem;left:50%;position:absolute;text-align:center;top:0;transform:translateX(-38%);transition:all .35s;width:1.5rem}.esh-basketstatus-badge-inoperative{background-color:#f00;border-radius:50%;color:#fff;display:block;height:1.5rem;left:50%;position:absolute;text-align:center;top:0;transform:translateX(-38%);transition:all .35s;width:1.5rem}.esh-basketstatus:hover .esh-basketstatus-badge{background-color:transparent;color:#75b918;transition:all .35s} |
||||
@ -0,0 +1,57 @@ |
|||||
|
@import '../../variables'; |
||||
|
|
||||
|
.esh-basketstatus { |
||||
|
cursor: pointer; |
||||
|
display: inline-block; |
||||
|
float: right; |
||||
|
position: relative; |
||||
|
transition: all $animation-speed-default; |
||||
|
|
||||
|
&.is-disabled { |
||||
|
opacity: .5; |
||||
|
pointer-events: none; |
||||
|
} |
||||
|
|
||||
|
&-image { |
||||
|
height: 36px; |
||||
|
margin-top: .5rem; |
||||
|
} |
||||
|
|
||||
|
&-badge { |
||||
|
$size: 1.5rem; |
||||
|
background-color: $color-secondary; |
||||
|
border-radius: 50%; |
||||
|
color: $color-foreground-brighter; |
||||
|
display: block; |
||||
|
height: $size; |
||||
|
left: 50%; |
||||
|
position: absolute; |
||||
|
text-align: center; |
||||
|
top: 0; |
||||
|
transform: translateX(-38%); |
||||
|
transition: all $animation-speed-default; |
||||
|
width: $size; |
||||
|
} |
||||
|
|
||||
|
&-badge-inoperative { |
||||
|
$size: 1.5rem; |
||||
|
background-color: $color-warning; |
||||
|
border-radius: 50%; |
||||
|
color: $color-foreground-brighter; |
||||
|
display: block; |
||||
|
height: $size; |
||||
|
left: 50%; |
||||
|
position: absolute; |
||||
|
text-align: center; |
||||
|
top: 0; |
||||
|
transform: translateX(-38%); |
||||
|
transition: all $animation-speed-default; |
||||
|
width: $size; |
||||
|
} |
||||
|
|
||||
|
&:hover &-badge { |
||||
|
background-color: transparent; |
||||
|
color: $color-secondary-dark; |
||||
|
transition: all $animation-speed-default; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,49 @@ |
|||||
|
.esh-basket { |
||||
|
min-height: 80vh; } |
||||
|
.esh-basket-titles { |
||||
|
padding-bottom: 1rem; |
||||
|
padding-top: 2rem; } |
||||
|
.esh-basket-titles--clean { |
||||
|
padding-bottom: 0; |
||||
|
padding-top: 0; } |
||||
|
.esh-basket-title { |
||||
|
text-transform: uppercase; } |
||||
|
.esh-basket-items--border { |
||||
|
border-bottom: 1px solid #EEEEEE; |
||||
|
padding: .5rem 0; } |
||||
|
.esh-basket-items--border:last-of-type { |
||||
|
border-color: transparent; } |
||||
|
.esh-basket-items-margin-left1 { |
||||
|
margin-left: 1px; } |
||||
|
.esh-basket-item { |
||||
|
font-size: 1rem; |
||||
|
font-weight: 300; } |
||||
|
.esh-basket-item--middle { |
||||
|
line-height: 8rem; } |
||||
|
@media screen and (max-width: 1024px) { |
||||
|
.esh-basket-item--middle { |
||||
|
line-height: 1rem; } } |
||||
|
.esh-basket-item--mark { |
||||
|
color: #00A69C; } |
||||
|
.esh-basket-image { |
||||
|
height: 8rem; } |
||||
|
.esh-basket-input { |
||||
|
line-height: 1rem; |
||||
|
width: 100%; } |
||||
|
.esh-basket-checkout { |
||||
|
background-color: #83D01B; |
||||
|
border: 0; |
||||
|
border-radius: 0; |
||||
|
color: #FFFFFF; |
||||
|
display: inline-block; |
||||
|
font-size: 1rem; |
||||
|
font-weight: 400; |
||||
|
margin-top: 1rem; |
||||
|
padding: 1rem 1.5rem; |
||||
|
text-align: center; |
||||
|
text-transform: uppercase; |
||||
|
transition: all 0.35s; } |
||||
|
.esh-basket-checkout:hover { |
||||
|
background-color: #4a760f; |
||||
|
transition: all 0.35s; } |
||||
|
|
||||
@ -0,0 +1 @@ |
|||||
|
.esh-basket{min-height:80vh}.esh-basket-titles{padding-bottom:1rem;padding-top:2rem}.esh-basket-titles--clean{padding-bottom:0;padding-top:0}.esh-basket-title{text-transform:uppercase}.esh-basket-items--border{border-bottom:1px solid #eee;padding:.5rem 0}.esh-basket-items--border:last-of-type{border-color:transparent}.esh-basket-items-margin-left1{margin-left:1px}.esh-basket-item{font-size:1rem;font-weight:300}.esh-basket-item--middle{line-height:8rem}@media screen and (max-width:1024px){.esh-basket-item--middle{line-height:1rem}}.esh-basket-item--mark{color:#00a69c}.esh-basket-image{height:8rem}.esh-basket-input{line-height:1rem;width:100%}.esh-basket-checkout{background-color:#83d01b;border:0;border-radius:0;color:#fff;display:inline-block;font-size:1rem;font-weight:400;margin-top:1rem;padding:1rem 1.5rem;text-align:center;text-transform:uppercase;transition:all .35s}.esh-basket-checkout:hover{background-color:#4a760f;transition:all .35s} |
||||
@ -0,0 +1,89 @@ |
|||||
|
@import '../variables'; |
||||
|
|
||||
|
@mixin margin-left($distance) { |
||||
|
margin-left: $distance; |
||||
|
} |
||||
|
|
||||
|
.esh-basket { |
||||
|
min-height: 80vh; |
||||
|
|
||||
|
&-titles { |
||||
|
padding-bottom: 1rem; |
||||
|
padding-top: 2rem; |
||||
|
|
||||
|
&--clean { |
||||
|
padding-bottom: 0; |
||||
|
padding-top: 0; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
&-title { |
||||
|
text-transform: uppercase; |
||||
|
} |
||||
|
|
||||
|
&-items { |
||||
|
&--border { |
||||
|
border-bottom: $border-light solid $color-foreground-bright; |
||||
|
padding: .5rem 0; |
||||
|
|
||||
|
&:last-of-type { |
||||
|
border-color: transparent; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
&-margin-left1 { |
||||
|
@include margin-left(1px); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
$item-height: 8rem; |
||||
|
|
||||
|
&-item { |
||||
|
font-size: $font-size-m; |
||||
|
font-weight: $font-weight-semilight; |
||||
|
|
||||
|
&--middle { |
||||
|
line-height: $item-height; |
||||
|
|
||||
|
@media screen and (max-width: $media-screen-m) { |
||||
|
line-height: $font-size-m; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
&--mark { |
||||
|
color: $color-brand; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
&-image { |
||||
|
height: $item-height; |
||||
|
} |
||||
|
|
||||
|
&-input { |
||||
|
line-height: 1rem; |
||||
|
width: 100%; |
||||
|
} |
||||
|
|
||||
|
&-checkout { |
||||
|
background-color: $color-secondary; |
||||
|
border: 0; |
||||
|
border-radius: 0; |
||||
|
color: $color-foreground-brighter; |
||||
|
display: inline-block; |
||||
|
font-size: 1rem; |
||||
|
font-weight: $font-weight-normal; |
||||
|
margin-top: 1rem; |
||||
|
padding: 1rem 1.5rem; |
||||
|
text-align: center; |
||||
|
text-transform: uppercase; |
||||
|
transition: all $animation-speed-default; |
||||
|
|
||||
|
&:hover { |
||||
|
background-color: $color-secondary-darker; |
||||
|
transition: all $animation-speed-default; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
|
||||
|
|
||||
@ -0,0 +1,117 @@ |
|||||
|
.esh-catalog-hero { |
||||
|
background-image: url("../../images/main_banner.png"); |
||||
|
background-size: cover; |
||||
|
height: 260px; |
||||
|
width: 100%; } |
||||
|
|
||||
|
.esh-catalog-title { |
||||
|
position: relative; |
||||
|
top: 74.28571px; } |
||||
|
|
||||
|
.esh-catalog-filters { |
||||
|
background-color: #00A69C; |
||||
|
height: 65px; } |
||||
|
|
||||
|
.esh-catalog-filter { |
||||
|
-webkit-appearance: none; |
||||
|
background-color: transparent; |
||||
|
border-color: #00d9cc; |
||||
|
color: #FFFFFF; |
||||
|
cursor: pointer; |
||||
|
margin-right: 1rem; |
||||
|
margin-top: .5rem; |
||||
|
min-width: 140px; |
||||
|
outline-color: #83D01B; |
||||
|
padding-bottom: 0; |
||||
|
padding-left: 0.5rem; |
||||
|
padding-right: 0.5rem; |
||||
|
padding-top: 1.5rem; } |
||||
|
.esh-catalog-filter option { |
||||
|
background-color: #00A69C; } |
||||
|
|
||||
|
.esh-catalog-label { |
||||
|
display: inline-block; |
||||
|
position: relative; |
||||
|
z-index: 0; } |
||||
|
.esh-catalog-label::before { |
||||
|
color: rgba(255, 255, 255, 0.5); |
||||
|
content: attr(data-title); |
||||
|
font-size: 0.65rem; |
||||
|
margin-left: 0.5rem; |
||||
|
margin-top: 0.65rem; |
||||
|
position: absolute; |
||||
|
text-transform: uppercase; |
||||
|
z-index: 1; } |
||||
|
.esh-catalog-label::after { |
||||
|
background-image: url("../../images/arrow-down.png"); |
||||
|
content: ''; |
||||
|
height: 7px; |
||||
|
position: absolute; |
||||
|
right: 1.5rem; |
||||
|
top: 2.5rem; |
||||
|
width: 10px; |
||||
|
z-index: 1; } |
||||
|
|
||||
|
.esh-catalog-send { |
||||
|
background-color: #83D01B; |
||||
|
color: #FFFFFF; |
||||
|
cursor: pointer; |
||||
|
font-size: 1rem; |
||||
|
margin-top: -1.5rem; |
||||
|
padding: 0.5rem; |
||||
|
transition: all 0.35s; } |
||||
|
.esh-catalog-send:hover { |
||||
|
background-color: #4a760f; |
||||
|
transition: all 0.35s; } |
||||
|
|
||||
|
.esh-catalog-items { |
||||
|
margin-top: 1rem; } |
||||
|
|
||||
|
.esh-catalog-item { |
||||
|
margin-bottom: 1.5rem; |
||||
|
text-align: center; |
||||
|
width: 33%; |
||||
|
display: inline-block; |
||||
|
float: none !important; } |
||||
|
@media screen and (max-width: 1024px) { |
||||
|
.esh-catalog-item { |
||||
|
width: 50%; } } |
||||
|
@media screen and (max-width: 768px) { |
||||
|
.esh-catalog-item { |
||||
|
width: 100%; } } |
||||
|
|
||||
|
.esh-catalog-thumbnail { |
||||
|
max-width: 370px; |
||||
|
width: 100%; } |
||||
|
|
||||
|
.esh-catalog-button { |
||||
|
background-color: #83D01B; |
||||
|
border: 0; |
||||
|
color: #FFFFFF; |
||||
|
cursor: pointer; |
||||
|
font-size: 1rem; |
||||
|
height: 3rem; |
||||
|
margin-top: 1rem; |
||||
|
transition: all 0.35s; |
||||
|
width: 80%; } |
||||
|
.esh-catalog-button.is-disabled { |
||||
|
opacity: .5; |
||||
|
pointer-events: none; } |
||||
|
.esh-catalog-button:hover { |
||||
|
background-color: #4a760f; |
||||
|
transition: all 0.35s; } |
||||
|
|
||||
|
.esh-catalog-name { |
||||
|
font-size: 1rem; |
||||
|
font-weight: 300; |
||||
|
margin-top: .5rem; |
||||
|
text-align: center; |
||||
|
text-transform: uppercase; } |
||||
|
|
||||
|
.esh-catalog-price { |
||||
|
font-size: 28px; |
||||
|
font-weight: 900; |
||||
|
text-align: center; } |
||||
|
.esh-catalog-price::before { |
||||
|
content: '$'; } |
||||
|
|
||||
@ -0,0 +1 @@ |
|||||
|
.esh-catalog-hero{background-image:url("../../images/main_banner.png");background-size:cover;height:260px;width:100%}.esh-catalog-title{position:relative;top:74.28571px}.esh-catalog-filters{background-color:#00a69c;height:65px}.esh-catalog-filter{-webkit-appearance:none;background-color:transparent;border-color:#00d9cc;color:#fff;cursor:pointer;margin-right:1rem;margin-top:.5rem;min-width:140px;outline-color:#83d01b;padding-bottom:0;padding-left:.5rem;padding-right:.5rem;padding-top:1.5rem}.esh-catalog-filter option{background-color:#00a69c}.esh-catalog-label{display:inline-block;position:relative;z-index:0}.esh-catalog-label::before{color:rgba(255,255,255,.5);content:attr(data-title);font-size:.65rem;margin-left:.5rem;margin-top:.65rem;position:absolute;text-transform:uppercase;z-index:1}.esh-catalog-label::after{background-image:url("../../images/arrow-down.png");content:'';height:7px;position:absolute;right:1.5rem;top:2.5rem;width:10px;z-index:1}.esh-catalog-send{background-color:#83d01b;color:#fff;cursor:pointer;font-size:1rem;margin-top:-1.5rem;padding:.5rem;transition:all .35s}.esh-catalog-send:hover{background-color:#4a760f;transition:all .35s}.esh-catalog-items{margin-top:1rem}.esh-catalog-item{margin-bottom:1.5rem;text-align:center;width:33%;display:inline-block;float:none !important}@media screen and (max-width:1024px){.esh-catalog-item{width:50%}}@media screen and (max-width:768px){.esh-catalog-item{width:100%}}.esh-catalog-thumbnail{max-width:370px;width:100%}.esh-catalog-button{background-color:#83d01b;border:0;color:#fff;cursor:pointer;font-size:1rem;height:3rem;margin-top:1rem;transition:all .35s;width:80%}.esh-catalog-button.is-disabled{opacity:.5;pointer-events:none}.esh-catalog-button:hover{background-color:#4a760f;transition:all .35s}.esh-catalog-name{font-size:1rem;font-weight:300;margin-top:.5rem;text-align:center;text-transform:uppercase}.esh-catalog-price{font-size:28px;font-weight:900;text-align:center}.esh-catalog-price::before{content:'$'} |
||||
@ -0,0 +1,154 @@ |
|||||
|
@import '../variables'; |
||||
|
|
||||
|
.esh-catalog { |
||||
|
$banner-height: 260px; |
||||
|
|
||||
|
&-hero { |
||||
|
background-image: url($image-main_banner); |
||||
|
background-size: cover; |
||||
|
height: $banner-height; |
||||
|
width: 100%; |
||||
|
} |
||||
|
|
||||
|
&-title { |
||||
|
position: relative; |
||||
|
top: $banner-height / 3.5; |
||||
|
} |
||||
|
|
||||
|
$filter-height: 65px; |
||||
|
|
||||
|
&-filters { |
||||
|
background-color: $color-brand; |
||||
|
height: $filter-height; |
||||
|
} |
||||
|
|
||||
|
$filter-padding: .5rem; |
||||
|
|
||||
|
&-filter { |
||||
|
-webkit-appearance: none; |
||||
|
background-color: transparent; |
||||
|
border-color: $color-brand-bright; |
||||
|
color: $color-foreground-brighter; |
||||
|
cursor: pointer; |
||||
|
margin-right: 1rem; |
||||
|
margin-top: .5rem; |
||||
|
min-width: 140px; |
||||
|
outline-color: $color-secondary; |
||||
|
padding-bottom: 0; |
||||
|
padding-left: $filter-padding; |
||||
|
padding-right: $filter-padding; |
||||
|
padding-top: $filter-padding * 3; |
||||
|
|
||||
|
option { |
||||
|
background-color: $color-brand; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
&-label { |
||||
|
display: inline-block; |
||||
|
position: relative; |
||||
|
z-index: 0; |
||||
|
|
||||
|
&::before { |
||||
|
color: rgba($color-foreground-brighter, .5); |
||||
|
content: attr(data-title); |
||||
|
font-size: $font-size-xs; |
||||
|
margin-left: $filter-padding; |
||||
|
margin-top: $font-size-xs; |
||||
|
position: absolute; |
||||
|
text-transform: uppercase; |
||||
|
z-index: 1; |
||||
|
} |
||||
|
|
||||
|
&::after { |
||||
|
background-image: url($image-arrow_down); |
||||
|
content: ''; |
||||
|
height: 7px; //png height |
||||
|
position: absolute; |
||||
|
right: $filter-padding * 3; |
||||
|
top: $filter-padding * 5; |
||||
|
width: 10px; //png width |
||||
|
z-index: 1; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
&-send { |
||||
|
background-color: $color-secondary; |
||||
|
color: $color-foreground-brighter; |
||||
|
cursor: pointer; |
||||
|
font-size: $font-size-m; |
||||
|
margin-top: -$filter-padding * 3; |
||||
|
padding: $filter-padding; |
||||
|
transition: all $animation-speed-default; |
||||
|
|
||||
|
&:hover { |
||||
|
background-color: $color-secondary-darker; |
||||
|
transition: all $animation-speed-default; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
&-items { |
||||
|
margin-top: 1rem; |
||||
|
} |
||||
|
|
||||
|
&-item { |
||||
|
margin-bottom: 1.5rem; |
||||
|
text-align: center; |
||||
|
width: 33%; |
||||
|
display: inline-block; |
||||
|
float: none !important; |
||||
|
|
||||
|
@media screen and (max-width: $media-screen-m) { |
||||
|
width: 50%; |
||||
|
} |
||||
|
|
||||
|
@media screen and (max-width: $media-screen-s) { |
||||
|
width: 100%; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
&-thumbnail { |
||||
|
max-width: 370px; |
||||
|
width: 100%; |
||||
|
} |
||||
|
|
||||
|
&-button { |
||||
|
background-color: $color-secondary; |
||||
|
border: 0; |
||||
|
color: $color-foreground-brighter; |
||||
|
cursor: pointer; |
||||
|
font-size: $font-size-m; |
||||
|
height: 3rem; |
||||
|
margin-top: 1rem; |
||||
|
transition: all $animation-speed-default; |
||||
|
width: 80%; |
||||
|
|
||||
|
&.is-disabled { |
||||
|
opacity: .5; |
||||
|
pointer-events: none; |
||||
|
} |
||||
|
|
||||
|
&:hover { |
||||
|
background-color: $color-secondary-darker; |
||||
|
transition: all $animation-speed-default; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
&-name { |
||||
|
font-size: $font-size-m; |
||||
|
font-weight: $font-weight-semilight; |
||||
|
margin-top: .5rem; |
||||
|
text-align: center; |
||||
|
text-transform: uppercase; |
||||
|
} |
||||
|
|
||||
|
&-price { |
||||
|
font-size: 28px; |
||||
|
font-weight: 900; |
||||
|
text-align: center; |
||||
|
|
||||
|
&::before { |
||||
|
content: '$'; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,38 @@ |
|||||
|
.esh-pager-wrapper { |
||||
|
padding-top: 1rem; |
||||
|
text-align: center; |
||||
|
} |
||||
|
|
||||
|
.esh-pager-item-left { |
||||
|
float: left; |
||||
|
} |
||||
|
|
||||
|
.esh-pager-item-right { |
||||
|
float: right; |
||||
|
} |
||||
|
|
||||
|
.esh-pager-item--navigable { |
||||
|
display: inline-block; |
||||
|
cursor: pointer; |
||||
|
} |
||||
|
|
||||
|
.esh-pager-item--navigable.is-disabled { |
||||
|
opacity: 0; |
||||
|
pointer-events: none; |
||||
|
} |
||||
|
|
||||
|
.esh-pager-item--navigable:hover { |
||||
|
color: #83D01B; |
||||
|
} |
||||
|
|
||||
|
@media screen and (max-width: 1280px) { |
||||
|
.esh-pager-item { |
||||
|
font-size: 0.85rem; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@media screen and (max-width: 1024px) { |
||||
|
.esh-pager-item { |
||||
|
margin: 0 4vw; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,50 @@ |
|||||
|
.esh-orders { |
||||
|
min-height: 80vh; |
||||
|
overflow-x: hidden; } |
||||
|
.esh-orders-header { |
||||
|
background-color: #00A69C; |
||||
|
height: 4rem; } |
||||
|
.esh-orders-back { |
||||
|
color: rgba(255, 255, 255, 0.4); |
||||
|
line-height: 4rem; |
||||
|
text-decoration: none; |
||||
|
text-transform: uppercase; |
||||
|
transition: color 0.35s; } |
||||
|
.esh-orders-back:hover { |
||||
|
color: #FFFFFF; |
||||
|
transition: color 0.35s; } |
||||
|
.esh-orders-titles { |
||||
|
padding-bottom: 1rem; |
||||
|
padding-top: 2rem; } |
||||
|
.esh-orders-title { |
||||
|
text-transform: uppercase; } |
||||
|
.esh-orders-items { |
||||
|
height: 2rem; |
||||
|
line-height: 2rem; |
||||
|
position: relative; } |
||||
|
.esh-orders-items:nth-of-type(2n + 1):before { |
||||
|
background-color: #EEEEFF; |
||||
|
content: ''; |
||||
|
height: 100%; |
||||
|
left: 0; |
||||
|
margin-left: -100vw; |
||||
|
position: absolute; |
||||
|
top: 0; |
||||
|
width: 200vw; |
||||
|
z-index: -1; } |
||||
|
.esh-orders-item { |
||||
|
font-weight: 300; } |
||||
|
.esh-orders-item--hover { |
||||
|
opacity: 0; |
||||
|
pointer-events: none; } |
||||
|
.esh-orders-items:hover .esh-orders-item--hover { |
||||
|
opacity: 1; |
||||
|
pointer-events: all; } |
||||
|
.esh-orders-link { |
||||
|
color: #83D01B; |
||||
|
text-decoration: none; |
||||
|
transition: color 0.35s; } |
||||
|
.esh-orders-link:hover { |
||||
|
color: #75b918; |
||||
|
transition: color 0.35s; } |
||||
|
|
||||
@ -0,0 +1 @@ |
|||||
|
.esh-orders{min-height:80vh;overflow-x:hidden}.esh-orders-header{background-color:#00a69c;height:4rem}.esh-orders-back{color:rgba(255,255,255,.4);line-height:4rem;text-decoration:none;text-transform:uppercase;transition:color .35s}.esh-orders-back:hover{color:#fff;transition:color .35s}.esh-orders-titles{padding-bottom:1rem;padding-top:2rem}.esh-orders-title{text-transform:uppercase}.esh-orders-items{height:2rem;line-height:2rem;position:relative}.esh-orders-items:nth-of-type(2n+1):before{background-color:#eef;content:'';height:100%;left:0;margin-left:-100vw;position:absolute;top:0;width:200vw;z-index:-1}.esh-orders-item{font-weight:300}.esh-orders-item--hover{opacity:0;pointer-events:none}.esh-orders-items:hover .esh-orders-item--hover{opacity:1;pointer-events:all}.esh-orders-link{color:#83d01b;text-decoration:none;transition:color .35s}.esh-orders-link:hover{color:#75b918;transition:color .35s} |
||||
@ -0,0 +1,91 @@ |
|||||
|
@import '../variables'; |
||||
|
|
||||
|
.esh-orders { |
||||
|
min-height: 80vh; |
||||
|
overflow-x: hidden; |
||||
|
$header-height: 4rem; |
||||
|
&-header |
||||
|
|
||||
|
{ |
||||
|
background-color: #00A69C; |
||||
|
height: $header-height; |
||||
|
} |
||||
|
|
||||
|
&-back { |
||||
|
color: rgba($color-foreground-brighter, .4); |
||||
|
line-height: $header-height; |
||||
|
text-decoration: none; |
||||
|
text-transform: uppercase; |
||||
|
transition: color $animation-speed-default; |
||||
|
&:hover |
||||
|
|
||||
|
{ |
||||
|
color: $color-foreground-brighter; |
||||
|
transition: color $animation-speed-default; |
||||
|
} |
||||
|
|
||||
|
} |
||||
|
|
||||
|
&-titles { |
||||
|
padding-bottom: 1rem; |
||||
|
padding-top: 2rem; |
||||
|
} |
||||
|
|
||||
|
&-title { |
||||
|
text-transform: uppercase; |
||||
|
} |
||||
|
|
||||
|
&-items { |
||||
|
$height: 2rem; |
||||
|
height: $height; |
||||
|
line-height: $height; |
||||
|
position: relative; |
||||
|
&:nth-of-type(2n + 1) |
||||
|
|
||||
|
{ |
||||
|
&:before |
||||
|
|
||||
|
{ |
||||
|
background-color: $color-background-bright; |
||||
|
content: ''; |
||||
|
height: 100%; |
||||
|
left: 0; |
||||
|
margin-left: -100vw; |
||||
|
position: absolute; |
||||
|
top: 0; |
||||
|
width: 200vw; |
||||
|
z-index: -1; |
||||
|
} |
||||
|
|
||||
|
} |
||||
|
} |
||||
|
|
||||
|
&-item { |
||||
|
font-weight: $font-weight-semilight; |
||||
|
&--hover |
||||
|
|
||||
|
{ |
||||
|
opacity: 0; |
||||
|
pointer-events: none; |
||||
|
} |
||||
|
|
||||
|
} |
||||
|
|
||||
|
&-items:hover &-item--hover { |
||||
|
opacity: 1; |
||||
|
pointer-events: all; |
||||
|
} |
||||
|
|
||||
|
&-link { |
||||
|
color: $color-secondary; |
||||
|
text-decoration: none; |
||||
|
transition: color $animation-speed-default; |
||||
|
&:hover |
||||
|
|
||||
|
{ |
||||
|
color: $color-secondary-dark; |
||||
|
transition: color $animation-speed-default; |
||||
|
} |
||||
|
|
||||
|
} |
||||
|
} |
||||
|
After Width: | Height: | Size: 31 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 111 KiB |
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
After Width: | Height: | Size: 104 KiB |
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
After Width: | Height: | Size: 1.0 KiB |
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue