Ardalis/upgrade1 (#44)
* Upgrading to netcore 2.0 Updating repository to support async options and refactoring to use it. * Starting work on tracking customer orders feature. * Cleaning up some bugs Working on basket view component implementation * Fixing up styles, especially for basket in header.
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard1.4</TargetFramework>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
26
src/ApplicationCore/Entities/BuyerAggregate/Buyer.cs
Normal file
26
src/ApplicationCore/Entities/BuyerAggregate/Buyer.cs
Normal file
@@ -0,0 +1,26 @@
|
||||
using ApplicationCore.Interfaces;
|
||||
using Microsoft.eShopWeb.ApplicationCore.Entities;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace ApplicationCore.Entities.BuyerAggregate
|
||||
{
|
||||
public class Buyer : BaseEntity, IAggregateRoot
|
||||
{
|
||||
public string IdentityGuid { get; private set; }
|
||||
|
||||
private List<PaymentMethod> _paymentMethods = new List<PaymentMethod>();
|
||||
|
||||
public IEnumerable<PaymentMethod> PaymentMethods => _paymentMethods.AsReadOnly();
|
||||
|
||||
protected Buyer()
|
||||
{
|
||||
}
|
||||
|
||||
public Buyer(string identity) : this()
|
||||
{
|
||||
IdentityGuid = !string.IsNullOrWhiteSpace(identity) ? identity : throw new ArgumentNullException(nameof(identity));
|
||||
}
|
||||
}
|
||||
}
|
||||
11
src/ApplicationCore/Entities/BuyerAggregate/PaymentMethod.cs
Normal file
11
src/ApplicationCore/Entities/BuyerAggregate/PaymentMethod.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
using Microsoft.eShopWeb.ApplicationCore.Entities;
|
||||
|
||||
namespace ApplicationCore.Entities.BuyerAggregate
|
||||
{
|
||||
public class PaymentMethod : BaseEntity
|
||||
{
|
||||
public string Alias { get; set; }
|
||||
public string CardId { get; set; } // actual card data must be stored in a PCI compliant system, like Stripe
|
||||
public string Last4 { get; set; }
|
||||
}
|
||||
}
|
||||
39
src/ApplicationCore/Entities/OrderAggregate/Address.cs
Normal file
39
src/ApplicationCore/Entities/OrderAggregate/Address.cs
Normal file
@@ -0,0 +1,39 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace ApplicationCore.Entities.OrderAggregate
|
||||
{
|
||||
public class Address // ValueObject
|
||||
{
|
||||
public String Street { get; private set; }
|
||||
|
||||
public String City { get; private set; }
|
||||
|
||||
public String State { get; private set; }
|
||||
|
||||
public String Country { get; private set; }
|
||||
|
||||
public String ZipCode { get; private set; }
|
||||
|
||||
private Address() { }
|
||||
|
||||
public Address(string street, string city, string state, string country, string zipcode)
|
||||
{
|
||||
Street = street;
|
||||
City = city;
|
||||
State = state;
|
||||
Country = country;
|
||||
ZipCode = zipcode;
|
||||
}
|
||||
|
||||
//protected override IEnumerable<object> GetAtomicValues()
|
||||
//{
|
||||
// yield return Street;
|
||||
// yield return City;
|
||||
// yield return State;
|
||||
// yield return Country;
|
||||
// yield return ZipCode;
|
||||
//}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
namespace ApplicationCore.Entities.OrderAggregate
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the item that was ordered. If catalog item details change, details of
|
||||
/// the item that was part of a completed order should not change.
|
||||
/// </summary>
|
||||
public class CatalogItemOrdered // ValueObject
|
||||
{
|
||||
public string CatalogItemId { get; private set; }
|
||||
public string ProductName { get; private set; }
|
||||
public string PictureUri { get; private set; }
|
||||
}
|
||||
}
|
||||
44
src/ApplicationCore/Entities/OrderAggregate/Order.cs
Normal file
44
src/ApplicationCore/Entities/OrderAggregate/Order.cs
Normal file
@@ -0,0 +1,44 @@
|
||||
using ApplicationCore.Interfaces;
|
||||
using Microsoft.eShopWeb.ApplicationCore.Entities;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace ApplicationCore.Entities.OrderAggregate
|
||||
{
|
||||
public class Order : BaseEntity, IAggregateRoot
|
||||
{
|
||||
public DateTimeOffset OrderDate { get; private set; } = DateTimeOffset.Now;
|
||||
public Address ShipToAddress { get; private set; }
|
||||
|
||||
// DDD Patterns comment
|
||||
// Using a private collection field, better for DDD Aggregate's encapsulation
|
||||
// so OrderItems cannot be added from "outside the AggregateRoot" directly to the collection,
|
||||
// but only through the method OrderAggrergateRoot.AddOrderItem() which includes behaviour.
|
||||
private readonly List<OrderItem> _orderItems;
|
||||
|
||||
public IReadOnlyCollection<OrderItem> OrderItems => _orderItems;
|
||||
// Using List<>.AsReadOnly()
|
||||
// This will create a read only wrapper around the private list so is protected against "external updates".
|
||||
// It's much cheaper than .ToList() because it will not have to copy all items in a new collection. (Just one heap alloc for the wrapper instance)
|
||||
//https://msdn.microsoft.com/en-us/library/e78dcd75(v=vs.110).aspx
|
||||
|
||||
}
|
||||
|
||||
public class OrderItem : BaseEntity
|
||||
{
|
||||
public CatalogItemOrdered ItemOrdered { get; private set; }
|
||||
public decimal UnitPrice { get; private set; }
|
||||
public int Units { get; private set; }
|
||||
|
||||
protected OrderItem()
|
||||
{
|
||||
}
|
||||
public OrderItem(CatalogItemOrdered itemOrdered, decimal unitPrice, int units)
|
||||
{
|
||||
ItemOrdered = itemOrdered;
|
||||
UnitPrice = unitPrice;
|
||||
Units = units;
|
||||
}
|
||||
}
|
||||
}
|
||||
5
src/ApplicationCore/Interfaces/IAggregateRoot.cs
Normal file
5
src/ApplicationCore/Interfaces/IAggregateRoot.cs
Normal file
@@ -0,0 +1,5 @@
|
||||
namespace ApplicationCore.Interfaces
|
||||
{
|
||||
public interface IAggregateRoot
|
||||
{ }
|
||||
}
|
||||
16
src/ApplicationCore/Interfaces/IAsyncRepository.cs
Normal file
16
src/ApplicationCore/Interfaces/IAsyncRepository.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
using Microsoft.eShopWeb.ApplicationCore.Entities;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace ApplicationCore.Interfaces
|
||||
{
|
||||
public interface IAsyncRepository<T> where T : BaseEntity
|
||||
{
|
||||
Task<T> GetByIdAsync(int id);
|
||||
Task<List<T>> ListAllAsync();
|
||||
Task<List<T>> ListAsync(ISpecification<T> spec);
|
||||
Task<T> AddAsync(T entity);
|
||||
Task UpdateAsync(T entity);
|
||||
Task DeleteAsync(T entity);
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
namespace ApplicationCore.Interfaces
|
||||
{
|
||||
public interface IImageService
|
||||
{
|
||||
byte[] GetImageBytesById(int id);
|
||||
}
|
||||
}
|
||||
@@ -1,16 +1,13 @@
|
||||
using Microsoft.eShopWeb.ApplicationCore.Entities;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq.Expressions;
|
||||
|
||||
namespace ApplicationCore.Interfaces
|
||||
{
|
||||
|
||||
public interface IRepository<T> where T : BaseEntity
|
||||
{
|
||||
T GetById(int id);
|
||||
List<T> List();
|
||||
List<T> List(ISpecification<T> spec);
|
||||
IEnumerable<T> ListAll();
|
||||
IEnumerable<T> List(ISpecification<T> spec);
|
||||
T Add(T entity);
|
||||
void Update(T entity);
|
||||
void Delete(T entity);
|
||||
|
||||
@@ -10,39 +10,38 @@ namespace Infrastructure.Data
|
||||
{
|
||||
public class CatalogContextSeed
|
||||
{
|
||||
public static async Task SeedAsync(IApplicationBuilder applicationBuilder, ILoggerFactory loggerFactory, int? retry = 0)
|
||||
public static async Task SeedAsync(IApplicationBuilder applicationBuilder,
|
||||
CatalogContext catalogContext,
|
||||
ILoggerFactory loggerFactory, int? retry = 0)
|
||||
{
|
||||
int retryForAvailability = retry.Value;
|
||||
try
|
||||
{
|
||||
var context = (CatalogContext)applicationBuilder
|
||||
.ApplicationServices.GetService(typeof(CatalogContext));
|
||||
|
||||
// TODO: Only run this if using a real database
|
||||
// context.Database.Migrate();
|
||||
|
||||
if (!context.CatalogBrands.Any())
|
||||
if (!catalogContext.CatalogBrands.Any())
|
||||
{
|
||||
context.CatalogBrands.AddRange(
|
||||
catalogContext.CatalogBrands.AddRange(
|
||||
GetPreconfiguredCatalogBrands());
|
||||
|
||||
await context.SaveChangesAsync();
|
||||
await catalogContext.SaveChangesAsync();
|
||||
}
|
||||
|
||||
if (!context.CatalogTypes.Any())
|
||||
if (!catalogContext.CatalogTypes.Any())
|
||||
{
|
||||
context.CatalogTypes.AddRange(
|
||||
catalogContext.CatalogTypes.AddRange(
|
||||
GetPreconfiguredCatalogTypes());
|
||||
|
||||
await context.SaveChangesAsync();
|
||||
await catalogContext.SaveChangesAsync();
|
||||
}
|
||||
|
||||
if (!context.CatalogItems.Any())
|
||||
if (!catalogContext.CatalogItems.Any())
|
||||
{
|
||||
context.CatalogItems.AddRange(
|
||||
catalogContext.CatalogItems.AddRange(
|
||||
GetPreconfiguredItems());
|
||||
|
||||
await context.SaveChangesAsync();
|
||||
await catalogContext.SaveChangesAsync();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -50,9 +49,9 @@ namespace Infrastructure.Data
|
||||
if (retryForAvailability < 10)
|
||||
{
|
||||
retryForAvailability++;
|
||||
var log = loggerFactory.CreateLogger("catalog seed");
|
||||
var log = loggerFactory.CreateLogger<CatalogContextSeed>();
|
||||
log.LogError(ex.Message);
|
||||
await SeedAsync(applicationBuilder, loggerFactory, retryForAvailability);
|
||||
await SeedAsync(applicationBuilder, catalogContext, loggerFactory, retryForAvailability);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,10 +3,16 @@ using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.eShopWeb.ApplicationCore.Entities;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Infrastructure.Data
|
||||
{
|
||||
public class EfRepository<T> : IRepository<T> where T : BaseEntity
|
||||
/// <summary>
|
||||
/// "There's some repetition here - couldn't we have some the sync methods call the async?"
|
||||
/// https://blogs.msdn.microsoft.com/pfxteam/2012/04/13/should-i-expose-synchronous-wrappers-for-asynchronous-methods/
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
public class EfRepository<T> : IRepository<T>, IAsyncRepository<T> where T : BaseEntity
|
||||
{
|
||||
private readonly CatalogContext _dbContext;
|
||||
|
||||
@@ -17,22 +23,41 @@ namespace Infrastructure.Data
|
||||
|
||||
public T GetById(int id)
|
||||
{
|
||||
return _dbContext.Set<T>().SingleOrDefault(e => e.Id == id);
|
||||
return _dbContext.Set<T>().Find(id);
|
||||
}
|
||||
|
||||
public List<T> List()
|
||||
public async Task<T> GetByIdAsync(int id)
|
||||
{
|
||||
return _dbContext.Set<T>().ToList();
|
||||
return await _dbContext.Set<T>().FindAsync(id);
|
||||
}
|
||||
|
||||
public List<T> List(ISpecification<T> spec)
|
||||
public IEnumerable<T> ListAll()
|
||||
{
|
||||
return _dbContext.Set<T>().AsEnumerable();
|
||||
}
|
||||
|
||||
public async Task<List<T>> ListAllAsync()
|
||||
{
|
||||
return await _dbContext.Set<T>().ToListAsync();
|
||||
}
|
||||
|
||||
public IEnumerable<T> List(ISpecification<T> spec)
|
||||
{
|
||||
var queryableResultWithIncludes = spec.Includes
|
||||
.Aggregate(_dbContext.Set<T>().AsQueryable(),
|
||||
.Aggregate(_dbContext.Set<T>().AsQueryable(),
|
||||
(current, include) => current.Include(include));
|
||||
return queryableResultWithIncludes
|
||||
.Where(spec.Criteria)
|
||||
.ToList();
|
||||
.AsEnumerable();
|
||||
}
|
||||
public async Task<List<T>> ListAsync(ISpecification<T> spec)
|
||||
{
|
||||
var queryableResultWithIncludes = spec.Includes
|
||||
.Aggregate(_dbContext.Set<T>().AsQueryable(),
|
||||
(current, include) => current.Include(include));
|
||||
return await queryableResultWithIncludes
|
||||
.Where(spec.Criteria)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
public T Add(T entity)
|
||||
@@ -43,10 +68,12 @@ namespace Infrastructure.Data
|
||||
return entity;
|
||||
}
|
||||
|
||||
public void Delete(T entity)
|
||||
public async Task<T> AddAsync(T entity)
|
||||
{
|
||||
_dbContext.Set<T>().Remove(entity);
|
||||
_dbContext.SaveChanges();
|
||||
_dbContext.Set<T>().Add(entity);
|
||||
await _dbContext.SaveChangesAsync();
|
||||
|
||||
return entity;
|
||||
}
|
||||
|
||||
public void Update(T entity)
|
||||
@@ -54,6 +81,21 @@ namespace Infrastructure.Data
|
||||
_dbContext.Entry(entity).State = EntityState.Modified;
|
||||
_dbContext.SaveChanges();
|
||||
}
|
||||
public async Task UpdateAsync(T entity)
|
||||
{
|
||||
_dbContext.Entry(entity).State = EntityState.Modified;
|
||||
await _dbContext.SaveChangesAsync();
|
||||
}
|
||||
|
||||
public void Delete(T entity)
|
||||
{
|
||||
_dbContext.Set<T>().Remove(entity);
|
||||
_dbContext.SaveChanges();
|
||||
}
|
||||
public async Task DeleteAsync(T entity)
|
||||
{
|
||||
_dbContext.Set<T>().Remove(entity);
|
||||
await _dbContext.SaveChangesAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
using ApplicationCore.Exceptions;
|
||||
using ApplicationCore.Interfaces;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using System.IO;
|
||||
|
||||
namespace Infrastructure.FileSystem
|
||||
{
|
||||
public class LocalFileImageService : IImageService
|
||||
{
|
||||
private readonly IHostingEnvironment _env;
|
||||
|
||||
public LocalFileImageService(IHostingEnvironment env)
|
||||
{
|
||||
_env = env;
|
||||
}
|
||||
public byte[] GetImageBytesById(int id)
|
||||
{
|
||||
try
|
||||
{
|
||||
var contentRoot = _env.ContentRootPath + "//Pics";
|
||||
var path = Path.Combine(contentRoot, id + ".png");
|
||||
return File.ReadAllBytes(path);
|
||||
}
|
||||
catch (FileNotFoundException ex)
|
||||
{
|
||||
throw new CatalogImageMissingException(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
|
||||
|
||||
namespace Infrastructure.Identity
|
||||
|
||||
@@ -1,26 +1,19 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard1.4</TargetFramework>
|
||||
<TargetFramework>netcoreapp2.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore" Version="1.1.2" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore" Version="1.1.2" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="1.1.2" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="1.1.2" PrivateAssets="All" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="1.1.2" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer.Design" Version="1.1.2" PrivateAssets="All" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="1.1.1" PrivateAssets="All" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="1.1.2" />
|
||||
<PackageReference Include="StructureMap.Microsoft.DependencyInjection" Version="1.3.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.0" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="2.0.0" PrivateAssets="All" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\ApplicationCore\ApplicationCore.csproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<DotNetCliToolReference Include="Microsoft.EntityFrameworkCore.Tools" Version="1.1.1" />
|
||||
<DotNetCliToolReference Include="Microsoft.EntityFrameworkCore.Tools.DotNet" Version="1.0.1" />
|
||||
<DotNetCliToolReference Include="Microsoft.EntityFrameworkCore.Design" Version="1.1.2" />
|
||||
<DotNetCliToolReference Include="Microsoft.EntityFrameworkCore.Tools" Version="2.0.0" />
|
||||
<DotNetCliToolReference Include="Microsoft.EntityFrameworkCore.Tools.DotNet" Version="2.0.0" />
|
||||
<DotNetCliToolReference Include="Microsoft.EntityFrameworkCore.Design" Version="2.0.0" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Folder Include="Data\Migrations\" />
|
||||
|
||||
@@ -3,12 +3,11 @@ using Microsoft.AspNetCore.Mvc;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Infrastructure.Identity;
|
||||
using System;
|
||||
using Microsoft.eShopWeb.ApplicationCore.Entities;
|
||||
using ApplicationCore.Interfaces;
|
||||
using Web;
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
|
||||
namespace Microsoft.eShopWeb.Controllers
|
||||
{
|
||||
@@ -17,18 +16,15 @@ namespace Microsoft.eShopWeb.Controllers
|
||||
{
|
||||
private readonly UserManager<ApplicationUser> _userManager;
|
||||
private readonly SignInManager<ApplicationUser> _signInManager;
|
||||
private readonly string _externalCookieScheme;
|
||||
private readonly IBasketService _basketService;
|
||||
|
||||
public AccountController(
|
||||
UserManager<ApplicationUser> userManager,
|
||||
SignInManager<ApplicationUser> signInManager,
|
||||
IOptions<IdentityCookieOptions> identityCookieOptions,
|
||||
IBasketService basketService)
|
||||
{
|
||||
_userManager = userManager;
|
||||
_signInManager = signInManager;
|
||||
_externalCookieScheme = identityCookieOptions.Value.ExternalCookieAuthenticationScheme;
|
||||
_basketService = basketService;
|
||||
}
|
||||
|
||||
@@ -37,7 +33,7 @@ namespace Microsoft.eShopWeb.Controllers
|
||||
[AllowAnonymous]
|
||||
public async Task<IActionResult> SignIn(string returnUrl = null)
|
||||
{
|
||||
await HttpContext.Authentication.SignOutAsync(_externalCookieScheme);
|
||||
await HttpContext.SignOutAsync(IdentityConstants.ExternalScheme);
|
||||
|
||||
ViewData["ReturnUrl"] = returnUrl;
|
||||
return View();
|
||||
@@ -61,7 +57,7 @@ namespace Microsoft.eShopWeb.Controllers
|
||||
string anonymousBasketId = Request.Cookies[Constants.BASKET_COOKIENAME];
|
||||
if (!String.IsNullOrEmpty(anonymousBasketId))
|
||||
{
|
||||
_basketService.TransferBasket(anonymousBasketId, model.Email);
|
||||
await _basketService.TransferBasketAsync(anonymousBasketId, model.Email);
|
||||
Response.Cookies.Delete(Constants.BASKET_COOKIENAME);
|
||||
}
|
||||
return RedirectToLocal(returnUrl);
|
||||
@@ -74,7 +70,6 @@ namespace Microsoft.eShopWeb.Controllers
|
||||
[ValidateAntiForgeryToken]
|
||||
public async Task<ActionResult> SignOut()
|
||||
{
|
||||
HttpContext.Session.Clear();
|
||||
await _signInManager.SignOutAsync();
|
||||
|
||||
return RedirectToAction(nameof(CatalogController.Index), "Catalog");
|
||||
|
||||
@@ -6,7 +6,7 @@ namespace ApplicationCore.Interfaces
|
||||
public interface IBasketService
|
||||
{
|
||||
Task<BasketViewModel> GetOrCreateBasketForUser(string userName);
|
||||
Task TransferBasket(string anonymousId, string userName);
|
||||
Task TransferBasketAsync(string anonymousId, string userName);
|
||||
Task AddItemToBasket(int basketId, int catalogItemId, decimal price, int quantity);
|
||||
Task Checkout(int basketId);
|
||||
}
|
||||
|
||||
@@ -1,28 +1,20 @@
|
||||
using System.IO;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore;
|
||||
|
||||
namespace Microsoft.eShopWeb
|
||||
{
|
||||
public class Program
|
||||
{
|
||||
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
var host = new WebHostBuilder()
|
||||
.UseKestrel()
|
||||
.UseUrls("http://0.0.0.0:5106")
|
||||
.UseContentRoot(Directory.GetCurrentDirectory())
|
||||
.ConfigureLogging(factory =>
|
||||
{
|
||||
factory.AddConsole(LogLevel.Warning);
|
||||
factory.AddDebug();
|
||||
})
|
||||
.UseIISIntegration()
|
||||
.UseStartup<Startup>()
|
||||
.UseApplicationInsights()
|
||||
.Build();
|
||||
|
||||
host.Run();
|
||||
BuildWebHost(args).Run();
|
||||
}
|
||||
|
||||
public static IWebHost BuildWebHost(string[] args) =>
|
||||
WebHost.CreateDefaultBuilder(args)
|
||||
.UseUrls("http://0.0.0.0:5106")
|
||||
.UseStartup<Startup>()
|
||||
.Build();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using ApplicationCore.Interfaces;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.eShopWeb.ApplicationCore.Entities;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Microsoft.eShopWeb.ViewModels;
|
||||
using System.Collections.Generic;
|
||||
@@ -11,11 +10,11 @@ namespace Web.Services
|
||||
{
|
||||
public class BasketService : IBasketService
|
||||
{
|
||||
private readonly IRepository<Basket> _basketRepository;
|
||||
private readonly IAsyncRepository<Basket> _basketRepository;
|
||||
private readonly IUriComposer _uriComposer;
|
||||
private readonly IRepository<CatalogItem> _itemRepository;
|
||||
|
||||
public BasketService(IRepository<Basket> basketRepository,
|
||||
public BasketService(IAsyncRepository<Basket> basketRepository,
|
||||
IRepository<CatalogItem> itemRepository,
|
||||
IUriComposer uriComposer)
|
||||
{
|
||||
@@ -27,7 +26,7 @@ namespace Web.Services
|
||||
public async Task<BasketViewModel> GetOrCreateBasketForUser(string userName)
|
||||
{
|
||||
var basketSpec = new BasketWithItemsSpecification(userName);
|
||||
var basket = _basketRepository.List(basketSpec).FirstOrDefault();
|
||||
var basket = (await _basketRepository.ListAsync(basketSpec)).FirstOrDefault();
|
||||
|
||||
if(basket == null)
|
||||
{
|
||||
@@ -63,7 +62,7 @@ namespace Web.Services
|
||||
public async Task<BasketViewModel> CreateBasketForUser(string userId)
|
||||
{
|
||||
var basket = new Basket() { BuyerId = userId };
|
||||
_basketRepository.Add(basket);
|
||||
await _basketRepository.AddAsync(basket);
|
||||
|
||||
return new BasketViewModel()
|
||||
{
|
||||
@@ -75,30 +74,29 @@ namespace Web.Services
|
||||
|
||||
public async Task AddItemToBasket(int basketId, int catalogItemId, decimal price, int quantity)
|
||||
{
|
||||
var basket = _basketRepository.GetById(basketId);
|
||||
var basket = await _basketRepository.GetByIdAsync(basketId);
|
||||
|
||||
basket.AddItem(catalogItemId, price, quantity);
|
||||
|
||||
_basketRepository.Update(basket);
|
||||
await _basketRepository.UpdateAsync(basket);
|
||||
}
|
||||
|
||||
public async Task Checkout(int basketId)
|
||||
{
|
||||
var basket = _basketRepository.GetById(basketId);
|
||||
var basket = await _basketRepository.GetByIdAsync(basketId);
|
||||
|
||||
// TODO: Actually Process the order
|
||||
|
||||
_basketRepository.Delete(basket);
|
||||
await _basketRepository.DeleteAsync(basket);
|
||||
}
|
||||
|
||||
public Task TransferBasket(string anonymousId, string userName)
|
||||
public async Task TransferBasketAsync(string anonymousId, string userName)
|
||||
{
|
||||
var basketSpec = new BasketWithItemsSpecification(anonymousId);
|
||||
var basket = _basketRepository.List(basketSpec).FirstOrDefault();
|
||||
if (basket == null) return Task.CompletedTask;
|
||||
var basket = (await _basketRepository.ListAsync(basketSpec)).FirstOrDefault();
|
||||
if (basket == null) return;
|
||||
basket.BuyerId = userName;
|
||||
_basketRepository.Update(basket);
|
||||
return Task.CompletedTask;
|
||||
await _basketRepository.UpdateAsync(basket);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,15 +15,15 @@ namespace Microsoft.eShopWeb.Services
|
||||
{
|
||||
private readonly ILogger<CatalogService> _logger;
|
||||
private readonly IRepository<CatalogItem> _itemRepository;
|
||||
private readonly IRepository<CatalogBrand> _brandRepository;
|
||||
private readonly IRepository<CatalogType> _typeRepository;
|
||||
private readonly IAsyncRepository<CatalogBrand> _brandRepository;
|
||||
private readonly IAsyncRepository<CatalogType> _typeRepository;
|
||||
private readonly IUriComposer _uriComposer;
|
||||
|
||||
public CatalogService(
|
||||
ILoggerFactory loggerFactory,
|
||||
IRepository<CatalogItem> itemRepository,
|
||||
IRepository<CatalogBrand> brandRepository,
|
||||
IRepository<CatalogType> typeRepository,
|
||||
IAsyncRepository<CatalogBrand> brandRepository,
|
||||
IAsyncRepository<CatalogType> typeRepository,
|
||||
IUriComposer uriComposer)
|
||||
{
|
||||
_logger = loggerFactory.CreateLogger<CatalogService>();
|
||||
@@ -83,7 +83,7 @@ namespace Microsoft.eShopWeb.Services
|
||||
public async Task<IEnumerable<SelectListItem>> GetBrands()
|
||||
{
|
||||
_logger.LogInformation("GetBrands called.");
|
||||
var brands = _brandRepository.List();
|
||||
var brands = await _brandRepository.ListAllAsync();
|
||||
|
||||
var items = new List<SelectListItem>
|
||||
{
|
||||
@@ -100,7 +100,7 @@ namespace Microsoft.eShopWeb.Services
|
||||
public async Task<IEnumerable<SelectListItem>> GetTypes()
|
||||
{
|
||||
_logger.LogInformation("GetTypes called.");
|
||||
var types = _typeRepository.List();
|
||||
var types = await _typeRepository.ListAllAsync();
|
||||
var items = new List<SelectListItem>
|
||||
{
|
||||
new SelectListItem() { Value = null, Text = "All", Selected = true }
|
||||
|
||||
@@ -1,41 +1,32 @@
|
||||
using Microsoft.eShopWeb.Services;
|
||||
using ApplicationCore.Interfaces;
|
||||
using ApplicationCore.Services;
|
||||
using Infrastructure.Data;
|
||||
using Infrastructure.Identity;
|
||||
using Infrastructure.Logging;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.eShopWeb.Services;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Infrastructure.Identity;
|
||||
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
|
||||
using System.Text;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using ApplicationCore.Interfaces;
|
||||
using Infrastructure.FileSystem;
|
||||
using Infrastructure.Logging;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Web.Services;
|
||||
using ApplicationCore.Services;
|
||||
using Infrastructure.Data;
|
||||
|
||||
namespace Microsoft.eShopWeb
|
||||
{
|
||||
public class Startup
|
||||
{
|
||||
private IServiceCollection _services;
|
||||
public Startup(IHostingEnvironment env)
|
||||
public Startup(IConfiguration configuration)
|
||||
{
|
||||
var builder = new ConfigurationBuilder()
|
||||
.SetBasePath(env.ContentRootPath)
|
||||
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
|
||||
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
|
||||
.AddEnvironmentVariables();
|
||||
Configuration = builder.Build();
|
||||
Configuration = configuration;
|
||||
}
|
||||
|
||||
public IConfigurationRoot Configuration { get; }
|
||||
public IConfiguration Configuration { get; }
|
||||
|
||||
// This method gets called by the runtime. Use this method to add services to the container.
|
||||
public void ConfigureServices(IServiceCollection services)
|
||||
{
|
||||
// Requires LocalDB which can be installed with SQL Server Express 2016
|
||||
@@ -46,11 +37,6 @@ namespace Microsoft.eShopWeb
|
||||
{
|
||||
c.UseInMemoryDatabase("Catalog");
|
||||
//c.UseSqlServer(Configuration.GetConnectionString("CatalogConnection"));
|
||||
c.ConfigureWarnings(wb =>
|
||||
{
|
||||
//By default, in this application, we don't want to have client evaluations
|
||||
wb.Log(RelationalEventId.QueryClientEvaluationWarning);
|
||||
});
|
||||
}
|
||||
catch (System.Exception ex )
|
||||
{
|
||||
@@ -68,6 +54,7 @@ namespace Microsoft.eShopWeb
|
||||
.AddDefaultTokenProviders();
|
||||
|
||||
services.AddScoped(typeof(IRepository<>), typeof(EfRepository<>));
|
||||
services.AddScoped(typeof(IAsyncRepository<>), typeof(EfRepository<>));
|
||||
|
||||
services.AddMemoryCache();
|
||||
services.AddScoped<ICatalogService, CachedCatalogService>();
|
||||
@@ -76,12 +63,8 @@ namespace Microsoft.eShopWeb
|
||||
services.Configure<CatalogSettings>(Configuration);
|
||||
services.AddSingleton<IUriComposer>(new UriComposer(Configuration.Get<CatalogSettings>()));
|
||||
|
||||
// TODO: Remove
|
||||
services.AddSingleton<IImageService, LocalFileImageService>();
|
||||
|
||||
services.AddScoped(typeof(IAppLogger<>), typeof(LoggerAdapter<>));
|
||||
|
||||
|
||||
// Add memory cache services
|
||||
services.AddMemoryCache();
|
||||
|
||||
@@ -98,25 +81,8 @@ namespace Microsoft.eShopWeb
|
||||
{
|
||||
app.UseDeveloperExceptionPage();
|
||||
app.UseBrowserLink();
|
||||
|
||||
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());
|
||||
}));
|
||||
ListAllRegisteredServices(app);
|
||||
app.UseDatabaseErrorPage();
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -124,20 +90,43 @@ namespace Microsoft.eShopWeb
|
||||
}
|
||||
|
||||
app.UseStaticFiles();
|
||||
app.UseIdentity();
|
||||
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());
|
||||
}));
|
||||
}
|
||||
|
||||
public void ConfigureDevelopment(IApplicationBuilder app,
|
||||
IHostingEnvironment env,
|
||||
ILoggerFactory loggerFactory,
|
||||
UserManager<ApplicationUser> userManager)
|
||||
UserManager<ApplicationUser> userManager,
|
||||
CatalogContext catalogContext)
|
||||
{
|
||||
Configure(app, env);
|
||||
|
||||
//Seed Data
|
||||
CatalogContextSeed.SeedAsync(app, loggerFactory)
|
||||
CatalogContextSeed.SeedAsync(app, catalogContext, loggerFactory)
|
||||
.Wait();
|
||||
|
||||
var defaultUser = new ApplicationUser { UserName = "demouser@microsoft.com", Email = "demouser@microsoft.com" };
|
||||
@@ -155,17 +144,17 @@ namespace Microsoft.eShopWeb
|
||||
public void ConfigureProduction(IApplicationBuilder app,
|
||||
IHostingEnvironment env,
|
||||
ILoggerFactory loggerFactory,
|
||||
UserManager<ApplicationUser> userManager)
|
||||
UserManager<ApplicationUser> userManager,
|
||||
CatalogContext catalogContext)
|
||||
{
|
||||
Configure(app, env);
|
||||
|
||||
//Seed Data
|
||||
CatalogContextSeed.SeedAsync(app, loggerFactory)
|
||||
CatalogContextSeed.SeedAsync(app, catalogContext, loggerFactory)
|
||||
.Wait();
|
||||
|
||||
var defaultUser = new ApplicationUser { UserName = "demouser@microsoft.com", Email = "demouser@microsoft.com" };
|
||||
userManager.CreateAsync(defaultUser, "Pass@word1").Wait();
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
27
src/Web/ViewComponents/Basket.cs
Normal file
27
src/Web/ViewComponents/Basket.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
using ApplicationCore.Interfaces;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.eShopWeb.ViewModels;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Web.ViewComponents
|
||||
{
|
||||
public class Basket : ViewComponent
|
||||
{
|
||||
private readonly IBasketService _cartSvc;
|
||||
|
||||
public Basket(IBasketService cartSvc) => _cartSvc = cartSvc;
|
||||
|
||||
public async Task<IViewComponentResult> InvokeAsync(string userName)
|
||||
{
|
||||
var vm = new BasketComponentViewModel();
|
||||
var itemsInCart = await ItemsInBasketAsync(userName);
|
||||
vm.ItemsCount = itemsInCart;
|
||||
return View(vm);
|
||||
}
|
||||
private async Task<int> ItemsInBasketAsync(string userName)
|
||||
{
|
||||
var basket = await _cartSvc.GetOrCreateBasketForUser(userName);
|
||||
return basket.Items.Count;
|
||||
}
|
||||
}
|
||||
}
|
||||
7
src/Web/ViewModels/BasketComponentViewModel.cs
Normal file
7
src/Web/ViewModels/BasketComponentViewModel.cs
Normal file
@@ -0,0 +1,7 @@
|
||||
namespace Microsoft.eShopWeb.ViewModels
|
||||
{
|
||||
public class BasketComponentViewModel
|
||||
{
|
||||
public int ItemsCount { get; set; }
|
||||
}
|
||||
}
|
||||
17
src/Web/Views/Shared/Components/Basket/Default.cshtml
Normal file
17
src/Web/Views/Shared/Components/Basket/Default.cshtml
Normal file
@@ -0,0 +1,17 @@
|
||||
@model BasketComponentViewModel
|
||||
|
||||
@{
|
||||
ViewData["Title"] = "My Basket";
|
||||
}
|
||||
|
||||
<a class="esh-basketstatus "
|
||||
asp-area=""
|
||||
asp-controller="Cart"
|
||||
asp-action="Index">
|
||||
<div class="esh-basketstatus-image">
|
||||
<img src="~/images/cart.png" />
|
||||
</div>
|
||||
<div class="esh-basketstatus-badge">
|
||||
@Model.ItemsCount
|
||||
</div>
|
||||
</a>
|
||||
@@ -7,7 +7,19 @@
|
||||
|
||||
<environment names="Development">
|
||||
<link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.css" />
|
||||
<link rel="stylesheet" href="~/css/app.css" />
|
||||
<link rel="stylesheet" href="~/css/app.css" />
|
||||
<link rel="stylesheet" href="~/css/app.component.css" />
|
||||
@*<link rel="stylesheet" href="~/css/shared/components/header/header.css" />
|
||||
<link rel="stylesheet" href="~/css/shared/components/identity/identity.css" />
|
||||
<link rel="stylesheet" href="~/css/campaigns/campaigns.component.css" />
|
||||
<link rel="stylesheet" href="~/css/shared/components/pager/pager.css" />*@
|
||||
<link rel="stylesheet" href="~/css/basket/basket.component.css" />
|
||||
<link rel="stylesheet" href="~/css/basket/basket-status/basket-status.component.css" />
|
||||
<link rel="stylesheet" href="~/css/catalog/catalog.component.css" />
|
||||
@*<link rel="stylesheet" href="~/css/orders/orders.component.css" />
|
||||
<link rel="stylesheet" href="~/css/orders/orders-detail/orders-detail.component.css" />
|
||||
<link rel="stylesheet" href="~/css/orders/orders-new/orders-new.component.css" />
|
||||
<link rel="stylesheet" href="~/css/override.css" type="text/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"
|
||||
@@ -15,8 +27,8 @@
|
||||
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/catalog/pager.css" />
|
||||
<link rel="stylesheet" href="~/css/catalog/catalog.component.css" />
|
||||
<link rel="stylesheet" href="~/css/catalog/pager.css" />
|
||||
<link rel="stylesheet" href="~/css/catalog/catalog.component.css" />
|
||||
</head>
|
||||
<body>
|
||||
<header class="navbar navbar-light navbar-static-top">
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
@using Microsoft.AspNetCore.Identity
|
||||
@*@inject IIdentityParser<ApplicationUser> UserManager*@
|
||||
|
||||
@if (Context.User.Identity.IsAuthenticated)
|
||||
{
|
||||
@@ -14,13 +13,13 @@
|
||||
|
||||
<section class="esh-identity-drop">
|
||||
|
||||
@*<a class="esh-identity-item"
|
||||
<a class="esh-identity-item"
|
||||
asp-controller="Order"
|
||||
asp-action="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>
|
||||
|
||||
<a class="esh-identity-item"
|
||||
href="javascript:document.getElementById('logoutForm').submit()">
|
||||
@@ -33,9 +32,9 @@
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@*<section class="col-lg-1 col-xs-12">
|
||||
@await Component.InvokeAsync("Basket", new { user = UserManager.Parse(User) })
|
||||
</section>*@
|
||||
<section class="col-lg-1 col-xs-12">
|
||||
@await Component.InvokeAsync("Basket", User.Identity.Name)
|
||||
</section>
|
||||
|
||||
}
|
||||
else
|
||||
@@ -53,5 +52,6 @@ else
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@*<section class="col-lg-1 col-xs-12"></section>*@
|
||||
<section class="col-lg-1 col-xs-12">@await Component.InvokeAsync("Basket")
|
||||
</section>
|
||||
}
|
||||
|
||||
@@ -1,41 +1,21 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp1.1</TargetFramework>
|
||||
<TargetFramework>netcoreapp2.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<PackageTargetFallback>$(PackageTargetFallback);portable-net45+win8+wp8+wpa81;</PackageTargetFallback>
|
||||
<RuntimeFrameworkVersion>1.1.0</RuntimeFrameworkVersion>
|
||||
<DockerComposeProjectPath>..\docker-compose.dcproj</DockerComposeProjectPath>
|
||||
<RuntimeFrameworkVersion>2.0.0</RuntimeFrameworkVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Remove="Pics\**" />
|
||||
<Content Remove="Pics\**" />
|
||||
<EmbeddedResource Remove="Pics\**" />
|
||||
<None Remove="Pics\**" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Dapper" Version="1.50.2" />
|
||||
<PackageReference Include="Microsoft.ApplicationInsights.AspNetCore" Version="2.0.1" />
|
||||
<PackageReference Include="Microsoft.AspNetCore" Version="1.1.2" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc" Version="1.1.3" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Session" Version="1.1.2" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.StaticFiles" Version="1.1.2" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="1.1.2" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="1.1.2" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="1.1.2" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="1.1.2" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="1.1.2" />
|
||||
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="1.1.2" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="1.1.2" />
|
||||
<PackageReference Include="Microsoft.VisualStudio.Web.BrowserLink" Version="1.1.2" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.0" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="2.0.0" PrivateAssets="All" />
|
||||
<PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="2.0.0" PrivateAssets="All" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Folder Include="Views\Catalog\" />
|
||||
<Folder Include="wwwroot\css\catalog\" />
|
||||
<Folder Include="wwwroot\fonts\" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
@@ -43,9 +23,9 @@
|
||||
<ProjectReference Include="..\Infrastructure\Infrastructure.csproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<DotNetCliToolReference Include="Microsoft.EntityFrameworkCore.Tools" Version="1.1.1" />
|
||||
<DotNetCliToolReference Include="Microsoft.EntityFrameworkCore.Tools.DotNet" Version="1.0.1" />
|
||||
<DotNetCliToolReference Include="Microsoft.EntityFrameworkCore.Design" Version="1.1.2" />
|
||||
<DotNetCliToolReference Include="Microsoft.EntityFrameworkCore.Tools.DotNet" Version="2.0.0" />
|
||||
<DotNetCliToolReference Include="Microsoft.Extensions.SecretManager.Tools" Version="2.0.0" />
|
||||
<DotNetCliToolReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Tools" Version="2.0.0" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="wwwroot\images\products\1.png" />
|
||||
|
||||
42
src/Web/compilerconfig.json
Normal file
42
src/Web/compilerconfig.json
Normal file
@@ -0,0 +1,42 @@
|
||||
[
|
||||
//{
|
||||
// "outputFile": "wwwroot/css/orders/orders.component.css",
|
||||
// "inputFile": "wwwroot/css/orders/orders.component.scss"
|
||||
//},
|
||||
//{
|
||||
// "outputFile": "wwwroot/css/orders/orders-new/orders-new.component.css",
|
||||
// "inputFile": "wwwroot/css/orders/orders-new/orders-new.component.scss"
|
||||
//},
|
||||
//{
|
||||
// "outputFile": "wwwroot/css/orders/orders-detail/orders-detail.component.css",
|
||||
// "inputFile": "wwwroot/css/orders/orders-detail/orders-detail.component.scss"
|
||||
//},
|
||||
{
|
||||
"outputFile": "wwwroot/css/catalog/catalog.component.css",
|
||||
"inputFile": "wwwroot/css/catalog/catalog.component.scss"
|
||||
},
|
||||
{
|
||||
"outputFile": "wwwroot/css/basket/basket.component.css",
|
||||
"inputFile": "wwwroot/css/basket/basket.component.scss"
|
||||
},
|
||||
{
|
||||
"outputFile": "wwwroot/css/basket/basket-status/basket-status.component.css",
|
||||
"inputFile": "wwwroot/css/basket/basket-status/basket-status.component.scss"
|
||||
},
|
||||
//{
|
||||
// "outputFile": "wwwroot/css/shared/components/header/header.css",
|
||||
// "inputFile": "wwwroot/css/shared/components/header/header.scss"
|
||||
//},
|
||||
//{
|
||||
// "outputFile": "wwwroot/css/shared/components/identity/identity.css",
|
||||
// "inputFile": "wwwroot/css/shared/components/identity/identity.scss"
|
||||
//},
|
||||
//{
|
||||
// "outputFile": "wwwroot/css/shared/components/pager/pager.css",
|
||||
// "inputFile": "wwwroot/css/shared/components/pager/pager.scss"
|
||||
//},
|
||||
{
|
||||
"outputFile": "wwwroot/css/app.component.css",
|
||||
"inputFile": "wwwroot/css/app.component.scss"
|
||||
}
|
||||
]
|
||||
65
src/Web/wwwroot/css/_variables.scss
Normal file
65
src/Web/wwwroot/css/_variables.scss
Normal file
@@ -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';
|
||||
11
src/Web/wwwroot/css/app.component.css
Normal file
11
src/Web/wwwroot/css/app.component.css
Normal file
@@ -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; }
|
||||
|
||||
1
src/Web/wwwroot/css/app.component.min.css
vendored
Normal file
1
src/Web/wwwroot/css/app.component.min.css
vendored
Normal file
@@ -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}
|
||||
23
src/Web/wwwroot/css/app.component.scss
Normal file
23
src/Web/wwwroot/css/app.component.scss
Normal file
@@ -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,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; }
|
||||
|
||||
1
src/Web/wwwroot/css/basket/basket-status/basket-status.component.min.css
vendored
Normal file
1
src/Web/wwwroot/css/basket/basket-status/basket-status.component.min.css
vendored
Normal file
@@ -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;
|
||||
}
|
||||
}
|
||||
49
src/Web/wwwroot/css/basket/basket.component.css
Normal file
49
src/Web/wwwroot/css/basket/basket.component.css
Normal file
@@ -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; }
|
||||
|
||||
1
src/Web/wwwroot/css/basket/basket.component.min.css
vendored
Normal file
1
src/Web/wwwroot/css/basket/basket.component.min.css
vendored
Normal file
@@ -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}
|
||||
89
src/Web/wwwroot/css/basket/basket.component.scss
Normal file
89
src/Web/wwwroot/css/basket/basket.component.scss
Normal file
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,228 +1,117 @@
|
||||
.esh-catalog-hero {
|
||||
background-image: url("../../images/main_banner.png");
|
||||
background-size: cover;
|
||||
height: 260px;
|
||||
width: 100%;
|
||||
}
|
||||
.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;
|
||||
}
|
||||
position: relative;
|
||||
top: 74.28571px; }
|
||||
|
||||
.esh-catalog-filters {
|
||||
background-color: #00A69C;
|
||||
height: 65px;
|
||||
}
|
||||
background-color: #00A69C;
|
||||
height: 65px; }
|
||||
|
||||
.esh-catalog-filter {
|
||||
background-color: transparent;
|
||||
border-color: #00d9cc;
|
||||
color: #FFFFFF;
|
||||
cursor: pointer;
|
||||
margin-right: 1rem;
|
||||
margin-top: .5rem;
|
||||
outline-color: #83D01B;
|
||||
padding-bottom: 0;
|
||||
padding-left: 0.5rem;
|
||||
padding-right: 0.5rem;
|
||||
padding-top: 1.5rem;
|
||||
min-width: 140px;
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
|
||||
.esh-catalog-filter option {
|
||||
background-color: #00A69C;
|
||||
}
|
||||
-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-top: 0.65rem;
|
||||
margin-left: 0.5rem;
|
||||
position: absolute;
|
||||
text-transform: uppercase;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.esh-catalog-label::after {
|
||||
background-image: url("../../images/arrow-down.png");
|
||||
height: 7px;
|
||||
content: '';
|
||||
position: absolute;
|
||||
right: 1.5rem;
|
||||
top: 2.5rem;
|
||||
width: 10px;
|
||||
z-index: 1;
|
||||
}
|
||||
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;
|
||||
transform: translateY(.5rem);
|
||||
padding: 0.5rem;
|
||||
transition: all 0.35s;
|
||||
}
|
||||
|
||||
.esh-catalog-send:hover {
|
||||
background-color: #4a760f;
|
||||
transition: all 0.35s;
|
||||
}
|
||||
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;
|
||||
}
|
||||
margin-top: 1rem; }
|
||||
|
||||
.esh-catalog-item {
|
||||
text-align: center;
|
||||
margin-bottom: 1.5rem;
|
||||
width: 33%;
|
||||
display: inline-block;
|
||||
float: none !important;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 1024px) {
|
||||
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) {
|
||||
width: 50%; } }
|
||||
@media screen and (max-width: 768px) {
|
||||
.esh-catalog-item {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
width: 100%; } }
|
||||
|
||||
.esh-catalog-thumbnail {
|
||||
max-width: 370px;
|
||||
width: 100%;
|
||||
}
|
||||
max-width: 370px;
|
||||
width: 100%; }
|
||||
|
||||
.esh-catalog-button {
|
||||
background-color: #83D01B;
|
||||
border: none;
|
||||
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;
|
||||
}
|
||||
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;
|
||||
}
|
||||
font-size: 1rem;
|
||||
font-weight: 300;
|
||||
margin-top: .5rem;
|
||||
text-align: center;
|
||||
text-transform: uppercase; }
|
||||
|
||||
.esh-catalog-price {
|
||||
text-align: center;
|
||||
font-weight: 900;
|
||||
font-size: 28px;
|
||||
}
|
||||
font-size: 28px;
|
||||
font-weight: 900;
|
||||
text-align: center; }
|
||||
.esh-catalog-price::before {
|
||||
content: '$'; }
|
||||
|
||||
.esh-catalog-price::before {
|
||||
content: '$';
|
||||
}
|
||||
|
||||
|
||||
|
||||
.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;
|
||||
}
|
||||
1
src/Web/wwwroot/css/catalog/catalog.component.min.css
vendored
Normal file
1
src/Web/wwwroot/css/catalog/catalog.component.min.css
vendored
Normal file
@@ -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:'$'}
|
||||
154
src/Web/wwwroot/css/catalog/catalog.component.scss
Normal file
154
src/Web/wwwroot/css/catalog/catalog.component.scss
Normal file
@@ -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: '$';
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,14 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp1.1</TargetFramework>
|
||||
<TargetFramework>netcoreapp2.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.3.0" />
|
||||
<PackageReference Include="xunit" Version="2.2.0" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.2.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.TestHost" Version="1.1.2" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.TestHost" Version="2.0.0" />
|
||||
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
@@ -1,47 +0,0 @@
|
||||
using Infrastructure.FileSystem;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using System.IO;
|
||||
using Xunit;
|
||||
using Moq;
|
||||
|
||||
namespace IntegrationTests.Infrastructure.File
|
||||
{
|
||||
public class LocalFileImageServiceGetImageBytesById
|
||||
{
|
||||
private byte[] _testBytes = new byte[] { 0x01, 0x02, 0x03 };
|
||||
private readonly Mock<IHostingEnvironment> _mockEnvironment = new Mock<IHostingEnvironment>();
|
||||
private int _testImageId = 123;
|
||||
private string _testFileName = "123.png";
|
||||
|
||||
public LocalFileImageServiceGetImageBytesById()
|
||||
{
|
||||
// create folder if necessary
|
||||
Directory.CreateDirectory(Path.Combine(GetFileDirectory(), "Pics"));
|
||||
|
||||
string filePath = GetFilePath(_testFileName);
|
||||
System.IO.File.WriteAllBytes(filePath, _testBytes);
|
||||
_mockEnvironment.SetupGet<string>(m => m.ContentRootPath).Returns(GetFileDirectory());
|
||||
}
|
||||
|
||||
private string GetFilePath(string fileName)
|
||||
{
|
||||
return Path.Combine(GetFileDirectory(), "Pics", fileName);
|
||||
}
|
||||
|
||||
private string GetFileDirectory()
|
||||
{
|
||||
var location = System.Reflection.Assembly.GetEntryAssembly().Location;
|
||||
return Path.GetDirectoryName(location);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ReturnsFileContentResultGivenValidId()
|
||||
{
|
||||
var fileService = new LocalFileImageService(_mockEnvironment.Object);
|
||||
|
||||
var result = fileService.GetImageBytesById(_testImageId);
|
||||
|
||||
Assert.Equal(_testBytes, result);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,14 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp1.1</TargetFramework>
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp2.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.3.0" />
|
||||
<PackageReference Include="xunit" Version="2.2.0" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.2.0" />
|
||||
<PackageReference Include="Moq" Version="4.7.49" />
|
||||
<PackageReference Include="Moq" Version="4.7.99" />
|
||||
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
@@ -1,15 +1,14 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp1.1</TargetFramework>
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp2.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore" Version="1.1.2" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc" Version="1.1.3" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc" Version="2.0.0" />
|
||||
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.3.0" />
|
||||
<PackageReference Include="Moq" Version="4.7.49" />
|
||||
<PackageReference Include="Moq" Version="4.7.99" />
|
||||
<PackageReference Include="xunit" Version="2.2.0" />
|
||||
<PackageReference Include="xunit.runner.console" Version="2.2.0" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.2.0" />
|
||||
|
||||
Reference in New Issue
Block a user