Browse Source

data and UI customization enhancements

pull/457/head
Shaun Walker 7 years ago
parent
commit
445101b376
72 changed files with 2215 additions and 454 deletions
  1. +5
    -1
      docker-compose.override.yml
  2. +9
    -0
      src/Services/Catalog/Catalog.API/Catalog.API.csproj
  3. +2
    -0
      src/Services/Catalog/Catalog.API/CatalogSettings.cs
  4. +4
    -1
      src/Services/Catalog/Catalog.API/Controllers/CatalogController.cs
  5. +48
    -6
      src/Services/Catalog/Catalog.API/Controllers/PicController.cs
  6. +50
    -0
      src/Services/Catalog/Catalog.API/Extensions/LinqSelectExtensions.cs
  7. +231
    -20
      src/Services/Catalog/Catalog.API/Infrastructure/CatalogContextSeed.cs
  8. +8
    -0
      src/Services/Catalog/Catalog.API/Setup/CatalogBrands.csv
  9. +13
    -0
      src/Services/Catalog/Catalog.API/Setup/CatalogItems.csv
  10. +7
    -0
      src/Services/Catalog/Catalog.API/Setup/CatalogTypes.csv
  11. BIN
      src/Services/Catalog/Catalog.API/Setup/Catalogitems.zip
  12. +3
    -3
      src/Services/Catalog/Catalog.API/Startup.cs
  13. +1
    -0
      src/Services/Catalog/Catalog.API/settings.json
  14. +1
    -0
      src/Services/Identity/Identity.API/AppSettings.cs
  15. +137
    -19
      src/Services/Identity/Identity.API/Data/ApplicationContextSeed.cs
  16. +50
    -0
      src/Services/Identity/Identity.API/Extensions/LinqSelectExtensions.cs
  17. +10
    -1
      src/Services/Identity/Identity.API/Identity.API.csproj
  18. +2
    -0
      src/Services/Identity/Identity.API/Setup/Users.csv
  19. +1
    -1
      src/Services/Identity/Identity.API/Startup.cs
  20. +1
    -0
      src/Services/Identity/Identity.API/appsettings.json
  21. +50
    -0
      src/Services/Ordering/Ordering.API/Extensions/LinqSelectExtensions.cs
  22. +145
    -10
      src/Services/Ordering/Ordering.API/Infrastructure/OrderingContextSeed.cs
  23. +6
    -0
      src/Services/Ordering/Ordering.API/Ordering.API.csproj
  24. +7
    -0
      src/Services/Ordering/Ordering.API/OrderingSettings.cs
  25. +5
    -0
      src/Services/Ordering/Ordering.API/Setup/CardTypes.csv
  26. +7
    -0
      src/Services/Ordering/Ordering.API/Setup/OrderStatus.csv
  27. +5
    -3
      src/Services/Ordering/Ordering.API/Startup.cs
  28. +1
    -0
      src/Services/Ordering/Ordering.API/settings.json
  29. +1
    -0
      src/Web/WebMVC/AppSettings.cs
  30. +97
    -0
      src/Web/WebMVC/Infrastructure/WebContextSeed.cs
  31. BIN
      src/Web/WebMVC/Setup/images.zip
  32. +6
    -0
      src/Web/WebMVC/Setup/override.css
  33. +4
    -0
      src/Web/WebMVC/Startup.cs
  34. +3
    -1
      src/Web/WebMVC/Views/Shared/_Layout.cshtml
  35. +19
    -0
      src/Web/WebMVC/WebMVC.csproj
  36. +1
    -0
      src/Web/WebMVC/appsettings.json
  37. +42
    -0
      src/Web/WebMVC/compilerconfig.json
  38. +49
    -0
      src/Web/WebMVC/compilerconfig.json.defaults
  39. +58
    -0
      src/Web/WebMVC/wwwroot/css/_variables.scss
  40. +9
    -15
      src/Web/WebMVC/wwwroot/css/app.component.css
  41. +23
    -0
      src/Web/WebMVC/wwwroot/css/app.component.scss
  42. +28
    -27
      src/Web/WebMVC/wwwroot/css/basket/basket-status/basket-status.component.css
  43. +41
    -0
      src/Web/WebMVC/wwwroot/css/basket/basket-status/basket-status.component.scss
  44. +40
    -39
      src/Web/WebMVC/wwwroot/css/basket/basket.component.css
  45. +89
    -0
      src/Web/WebMVC/wwwroot/css/basket/basket.component.scss
  46. +110
    -108
      src/Web/WebMVC/wwwroot/css/catalog/catalog.component.css
  47. +154
    -0
      src/Web/WebMVC/wwwroot/css/catalog/catalog.component.scss
  48. +21
    -20
      src/Web/WebMVC/wwwroot/css/orders/orders-detail/orders-detail.component.css
  49. +56
    -0
      src/Web/WebMVC/wwwroot/css/orders/orders-detail/orders-detail.component.scss
  50. +55
    -50
      src/Web/WebMVC/wwwroot/css/orders/orders-new/orders-new.component.css
  51. +101
    -0
      src/Web/WebMVC/wwwroot/css/orders/orders-new/orders-new.component.scss
  52. +44
    -43
      src/Web/WebMVC/wwwroot/css/orders/orders.component.css
  53. +81
    -0
      src/Web/WebMVC/wwwroot/css/orders/orders.component.scss
  54. +6
    -0
      src/Web/WebMVC/wwwroot/css/override.css
  55. +12
    -25
      src/Web/WebMVC/wwwroot/css/shared/components/header/header.css
  56. +21
    -0
      src/Web/WebMVC/wwwroot/css/shared/components/header/header.scss
  57. +31
    -31
      src/Web/WebMVC/wwwroot/css/shared/components/identity/identity.css
  58. +56
    -0
      src/Web/WebMVC/wwwroot/css/shared/components/identity/identity.scss
  59. +21
    -20
      src/Web/WebMVC/wwwroot/css/shared/components/pager/pager.css
  60. +36
    -0
      src/Web/WebMVC/wwwroot/css/shared/components/pager/pager.scss
  61. +1
    -1
      src/Web/WebMVC/wwwroot/css/site.min.css
  62. BIN
      src/Web/WebMVC/wwwroot/images/main_footer_text.png
  63. +1
    -0
      src/Web/WebSPA/AppSettings.cs
  64. BIN
      src/Web/WebSPA/Client/assets/images/main_footer_text.png
  65. +5
    -0
      src/Web/WebSPA/Client/modules/_variables.scss
  66. +1
    -1
      src/Web/WebSPA/Client/modules/app.component.html
  67. +0
    -6
      src/Web/WebSPA/Client/modules/app.component.scss
  68. +2
    -2
      src/Web/WebSPA/Client/modules/catalog/catalog.component.scss
  69. +73
    -0
      src/Web/WebSPA/Server/Infrastructure/WebContextSeed.cs
  70. +4
    -0
      src/Web/WebSPA/Startup.cs
  71. +3
    -0
      src/Web/WebSPA/WebSPA.csproj
  72. +1
    -0
      src/Web/WebSPA/appsettings.json

+ 5
- 1
docker-compose.override.yml View File

@ -29,6 +29,7 @@ services:
- ConnectionString=Server=sql.data;Database=Microsoft.eShopOnContainers.Services.CatalogDb;User Id=sa;Password=Pass@word
- ExternalCatalogBaseUrl=http://${ESHOP_EXTERNAL_DNS_NAME_OR_IP}:5101 #Local: You need to open your local dev-machine firewall at range 5100-5105. at range 5100-5105.
- EventBusConnection=rabbitmq
- UseCustomizationData=True
ports:
- "5101:80"
@ -40,6 +41,7 @@ services:
- XamarinCallback=http://${ESHOP_PROD_EXTERNAL_DNS_NAME_OR_IP}:5105/xamarincallback #localhost do not work for UWP login, so we have to use "external" IP always
- ConnectionStrings__DefaultConnection=Server=sql.data;Database=Microsoft.eShopOnContainers.Service.IdentityDb;User Id=sa;Password=Pass@word
- MvcClient=http://${ESHOP_EXTERNAL_DNS_NAME_OR_IP}:5100 #Local: You need to open your local dev-machine firewall at range 5100-5105.
- UseCustomizationData=True
ports:
- "5105:80"
@ -50,6 +52,7 @@ services:
- ConnectionString=Server=sql.data;Database=Microsoft.eShopOnContainers.Services.OrderingDb;User Id=sa;Password=Pass@word
- identityUrl=http://identity.api #Local: You need to open your local dev-machine firewall at range 5100-5105. at range 5100-5105.
- EventBusConnection=rabbitmq
- UseCustomizationData=True
ports:
- "5102:80"
@ -77,6 +80,7 @@ services:
- OrderingUrlHC=http://ordering.api/hc
- IdentityUrlHC=http://identity.api/hc #Local: Use ${ESHOP_PROD_EXTERNAL_DNS_NAME_OR_IP}, if using external IP or DNS name from browser.
- BasketUrlHC=http://basket.api/hc
- UseCustomizationData=True
ports:
- "5104:80"
@ -88,7 +92,7 @@ services:
- OrderingUrl=http://ordering.api
- BasketUrl=http://basket.api
- IdentityUrl=http://10.0.75.1:5105 #Local: Use 10.0.75.1 in a "Docker for Windows" environment, if using "localhost" from browser.
#Remote: Use ${ESHOP_EXTERNAL_DNS_NAME_OR_IP} if using external IP or DNS name from browser.
- UseCustomizationData=True #Remote: Use ${ESHOP_EXTERNAL_DNS_NAME_OR_IP} if using external IP or DNS name from browser.
ports:
- "5100:80"


+ 9
- 0
src/Services/Catalog/Catalog.API/Catalog.API.csproj View File

@ -21,6 +21,11 @@
<Content Include="Pics\**\*;">
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
</Content>
<Content Include="Setup\**\*;">
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
</Content>
<Content Remove="Setup\Catalogitems - Copy.zip" />
<None Remove="Setup\Catalogitems - Copy.zip" />
<Compile Include="IntegrationEvents\EventHandling\AnyFutureIntegrationEventHandler.cs.txt" />
<Content Update="web.config;">
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
@ -50,6 +55,7 @@
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="1.1.2" />
<PackageReference Include="Newtonsoft.Json" Version="10.0.2" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="1.0.0" />
<PackageReference Include="System.IO.Compression.ZipFile" Version="4.3.0" />
</ItemGroup>
<ItemGroup>
@ -72,6 +78,9 @@
<None Update="Pics\*">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Setup\*">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>

+ 2
- 0
src/Services/Catalog/Catalog.API/CatalogSettings.cs View File

@ -5,5 +5,7 @@
public string ExternalCatalogBaseUrl {get;set;}
public string EventBusConnection { get; set; }
public bool UseCustomizationData { get; set; }
}
}

+ 4
- 1
src/Services/Catalog/Catalog.API/Controllers/CatalogController.cs View File

@ -235,7 +235,10 @@ namespace Microsoft.eShopOnContainers.Services.Catalog.API.Controllers
items.ForEach(x =>
{
x.PictureUri = x.PictureUri.Replace("http://externalcatalogbaseurltobereplaced", baseUri);
if (!x.PictureUri.Contains('/'))
{
x.PictureUri = $"{baseUri}/api/v1/pic/{x.PictureUri}";
}
});
return items;


+ 48
- 6
src/Services/Catalog/Catalog.API/Controllers/PicController.cs View File

@ -15,16 +15,58 @@ namespace Microsoft.eShopOnContainers.Services.Catalog.API.Controllers
_env = env;
}
[HttpGet("{id}")]
[HttpGet("{filename}")]
// GET: /<controller>/
public IActionResult GetImage(int id)
public IActionResult GetImage(string filename)
{
var webRoot = _env.WebRootPath;
var path = Path.Combine(webRoot, id + ".png");
var path = Path.Combine(webRoot, filename);
var buffer = System.IO.File.ReadAllBytes(path);
return File(buffer, "image/png");
string imageFileExtension = Path.GetExtension(filename);
string mimetype = GetImageMimeTypeFromImageFileExtension(imageFileExtension);
var buffer = System.IO.File.ReadAllBytes(path);
return File(buffer, mimetype);
}
private string GetImageMimeTypeFromImageFileExtension(string extension)
{
string mimetype;
switch (extension)
{
case "png":
mimetype = "image/png";
break;
case "gif":
mimetype = "image/gif";
break;
case "jpg":
case "jpeg":
mimetype = "image/jpeg";
break;
case "bmp":
mimetype = "image/bmp";
break;
case "tiff":
mimetype = "image/tiff";
break;
case "wmf":
mimetype = "image/wmf";
break;
case "jp2":
mimetype = "image/jp2";
break;
case "svg":
mimetype = "image/svg+xml";
break;
default:
mimetype = "application/octet-stream";
break;
}
return mimetype;
}
}
}

+ 50
- 0
src/Services/Catalog/Catalog.API/Extensions/LinqSelectExtensions.cs View File

@ -0,0 +1,50 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace Catalog.API.Extensions
{
public static class LinqSelectExtensions
{
public static IEnumerable<SelectTryResult<TSource, TResult>> SelectTry<TSource, TResult>(this IEnumerable<TSource> enumerable, Func<TSource, TResult> selector)
{
foreach (TSource element in enumerable)
{
SelectTryResult<TSource, TResult> returnedValue;
try
{
returnedValue = new SelectTryResult<TSource, TResult>(element, selector(element), null);
}
catch (Exception ex)
{
returnedValue = new SelectTryResult<TSource, TResult>(element, default(TResult), ex);
}
yield return returnedValue;
}
}
public static IEnumerable<TResult> OnCaughtException<TSource, TResult>(this IEnumerable<SelectTryResult<TSource, TResult>> enumerable, Func<Exception, TResult> exceptionHandler)
{
return enumerable.Select(x => x.CaughtException == null ? x.Result : exceptionHandler(x.CaughtException));
}
public static IEnumerable<TResult> OnCaughtException<TSource, TResult>(this IEnumerable<SelectTryResult<TSource, TResult>> enumerable, Func<TSource, Exception, TResult> exceptionHandler)
{
return enumerable.Select(x => x.CaughtException == null ? x.Result : exceptionHandler(x.Source, x.CaughtException));
}
public class SelectTryResult<TSource, TResult>
{
internal SelectTryResult(TSource source, TResult result, Exception exception)
{
Source = source;
Result = result;
CaughtException = exception;
}
public TSource Source { get; private set; }
public TResult Result { get; private set; }
public Exception CaughtException { get; private set; }
}
}
}

+ 231
- 20
src/Services/Catalog/Catalog.API/Infrastructure/CatalogContextSeed.cs View File

@ -2,45 +2,113 @@
{
using EntityFrameworkCore;
using Extensions.Logging;
using global::Catalog.API.Extensions;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Model;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
public class CatalogContextSeed
{
public static async Task SeedAsync(IApplicationBuilder applicationBuilder, ILoggerFactory loggerFactory, int? retry = 0)
public static async Task SeedAsync(IApplicationBuilder applicationBuilder, IHostingEnvironment env, ILoggerFactory loggerFactory, int? retry = 0)
{
var log = loggerFactory.CreateLogger("catalog seed");
var context = (CatalogContext)applicationBuilder
.ApplicationServices.GetService(typeof(CatalogContext));
context.Database.Migrate();
var settings = (CatalogSettings)applicationBuilder
.ApplicationServices.GetRequiredService<IOptions<CatalogSettings>>().Value;
var useCustomizationData = settings.UseCustomizationData;
var contentRootPath = env.ContentRootPath;
var picturePath = env.WebRootPath;
if (!context.CatalogBrands.Any())
{
context.CatalogBrands.AddRange(
GetPreconfiguredCatalogBrands());
context.CatalogBrands.AddRange(useCustomizationData
? GetCatalogBrandsFromFile(contentRootPath, log)
: GetPreconfiguredCatalogBrands()
);
await context.SaveChangesAsync();
}
if (!context.CatalogTypes.Any())
{
context.CatalogTypes.AddRange(
GetPreconfiguredCatalogTypes());
context.CatalogTypes.AddRange(useCustomizationData
? GetCatalogTypesFromFile(contentRootPath, log)
: GetPreconfiguredCatalogTypes()
);
await context.SaveChangesAsync();
}
if (!context.CatalogItems.Any())
{
context.CatalogItems.AddRange(
GetPreconfiguredItems());
context.CatalogItems.AddRange(useCustomizationData
? GetCatalogItemsFromFile(contentRootPath, context, log)
: GetPreconfiguredItems()
);
await context.SaveChangesAsync();
GetCatalogItemPictures(contentRootPath, picturePath);
}
}
static IEnumerable<CatalogBrand> GetCatalogBrandsFromFile(string contentRootPath, ILogger log)
{
string csvFileCatalogBrands = Path.Combine(contentRootPath, "Setup", "CatalogBrands.csv");
if (!File.Exists(csvFileCatalogBrands))
{
return GetPreconfiguredCatalogBrands();
}
string[] csvheaders;
try
{
string[] requiredHeaders = { "catalogbrand" };
csvheaders = GetHeaders(requiredHeaders, csvFileCatalogBrands);
}
catch (Exception ex)
{
log.LogError(ex.Message);
return GetPreconfiguredCatalogBrands();
}
return File.ReadAllLines(csvFileCatalogBrands)
.Skip(1) // skip header row
.SelectTry(x => CreateCatalogBrand(x))
.OnCaughtException(ex => { log.LogError(ex.Message); return null; })
.Where(x => x != null);
}
static CatalogBrand CreateCatalogBrand(string brand)
{
brand = brand.Trim();
if (String.IsNullOrEmpty(brand))
{
throw new Exception("catalog Brand Name is empty");
}
return new CatalogBrand
{
Brand = brand,
};
}
static IEnumerable<CatalogBrand> GetPreconfiguredCatalogBrands()
@ -50,11 +118,52 @@
new CatalogBrand() { Brand = "Azure"},
new CatalogBrand() { Brand = ".NET" },
new CatalogBrand() { Brand = "Visual Studio" },
new CatalogBrand() { Brand = "SQL Server" },
new CatalogBrand() { Brand = "SQL Server" },
new CatalogBrand() { Brand = "Other" }
};
}
static IEnumerable<CatalogType> GetCatalogTypesFromFile(string contentRootPath, ILogger log)
{
string csvFileCatalogTypes = Path.Combine(contentRootPath, "Setup", "CatalogTypes.csv");
if (!File.Exists(csvFileCatalogTypes))
{
return GetPreconfiguredCatalogTypes();
}
string[] csvheaders;
try
{
string[] requiredHeaders = { "catalogtype" };
csvheaders = GetHeaders(requiredHeaders, csvFileCatalogTypes);
}
catch (Exception ex)
{
log.LogError(ex.Message);
return GetPreconfiguredCatalogTypes();
}
return File.ReadAllLines(csvFileCatalogTypes)
.Skip(1) // skip header row
.SelectTry(x => CreateCatalogType(x))
.OnCaughtException(ex => { log.LogError(ex.Message); return null; })
.Where(x => x != null);
}
static CatalogType CreateCatalogType(string type)
{
if (String.IsNullOrEmpty(type))
{
throw new Exception("catalog Type Name is empty");
}
return new CatalogType
{
Type = type,
};
}
static IEnumerable<CatalogType> GetPreconfiguredCatalogTypes()
{
return new List<CatalogType>()
@ -66,23 +175,125 @@
};
}
static IEnumerable<CatalogItem> GetCatalogItemsFromFile(string contentRootPath, CatalogContext context, ILogger log)
{
string csvFileCatalogItems = Path.Combine(contentRootPath, "Setup", "CatalogItems.csv");
if (!File.Exists(csvFileCatalogItems))
{
return GetPreconfiguredItems();
}
string[] csvheaders;
try
{
string[] requiredHeaders = { "catalogtypename", "catalogbrandname", "description", "name", "price", "pictureuri" };
csvheaders = GetHeaders(requiredHeaders, csvFileCatalogItems);
}
catch (Exception ex)
{
log.LogError(ex.Message);
return GetPreconfiguredItems();
}
var catalogTypeIdLookup = context.CatalogTypes.ToDictionary(ct => ct.Type, ct => ct.Id);
var catalogBrandIdLookup = context.CatalogBrands.ToDictionary(ct => ct.Brand, ct => ct.Id);
return File.ReadAllLines(csvFileCatalogItems)
.Skip(1) // skip header row
.Select(row => Regex.Split(row, ",(?=(?:[^\"]*\"[^\"]*\")*[^\"]*$)") )
.SelectTry(column => CreateCatalogItem(column, csvheaders, catalogTypeIdLookup, catalogBrandIdLookup))
.OnCaughtException(ex => { log.LogError(ex.Message); return null; })
.Where(x => x != null);
}
static CatalogItem CreateCatalogItem(string[] column, string[] headers, Dictionary<String, int> catalogTypeIdLookup, Dictionary<String, int> catalogBrandIdLookup)
{
if (column.Count() != headers.Count())
{
throw new Exception($"column count '{column.Count()}' not the same as headers count'{headers.Count()}'");
}
string catalogTypeName = column[Array.IndexOf(headers, "catalogtypename")].Trim();
if (!catalogTypeIdLookup.ContainsKey(catalogTypeName))
{
throw new Exception($"type={catalogTypeName} does not exist in catalogTypes");
}
string catalogBrandName = column[Array.IndexOf(headers, "catalogbrandname")].Trim();
if (!catalogBrandIdLookup.ContainsKey(catalogBrandName))
{
throw new Exception($"type={catalogTypeName} does not exist in catalogTypes");
}
string priceString = column[Array.IndexOf(headers, "price")];
if (!Decimal.TryParse(priceString, NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out Decimal price)) // TODO: number styles
{
throw new Exception($"price={priceString}is not a valid decimal number");
}
return new CatalogItem()
{
CatalogTypeId = catalogTypeIdLookup[catalogTypeName],
CatalogBrandId = catalogBrandIdLookup[catalogBrandName],
Description = column[Array.IndexOf(headers, "description")],
Name = column[Array.IndexOf(headers, "name")],
Price = price,
PictureUri = column[Array.IndexOf(headers, "pictureuri")]
};
}
static IEnumerable<CatalogItem> GetPreconfiguredItems()
{
return new List<CatalogItem>()
{
new CatalogItem() { CatalogTypeId=2,CatalogBrandId=2, Description = ".NET Bot Black Hoodie", Name = ".NET Bot Black Hoodie", Price = 19.5M, PictureUri = "http://externalcatalogbaseurltobereplaced/api/v1/pic/1", AvailableStock = 100},
new CatalogItem() { CatalogTypeId=1,CatalogBrandId=2, Description = ".NET Black & White Mug", Name = ".NET Black & White Mug", Price= 8.50M, PictureUri = "http://externalcatalogbaseurltobereplaced/api/v1/pic/2", AvailableStock = 100 },
new CatalogItem() { CatalogTypeId=2,CatalogBrandId=5, Description = "Prism White T-Shirt", Name = "Prism White T-Shirt", Price = 12, PictureUri = "http://externalcatalogbaseurltobereplaced/api/v1/pic/3", AvailableStock = 100 },
new CatalogItem() { CatalogTypeId=2,CatalogBrandId=2, Description = ".NET Foundation T-shirt", Name = ".NET Foundation T-shirt", Price = 12, PictureUri = "http://externalcatalogbaseurltobereplaced/api/v1/pic/4", AvailableStock = 100 },
new CatalogItem() { CatalogTypeId=3,CatalogBrandId=5, Description = "Roslyn Red Sheet", Name = "Roslyn Red Sheet", Price = 8.5M, PictureUri = "http://externalcatalogbaseurltobereplaced/api/v1/pic/5", AvailableStock = 100 },
new CatalogItem() { CatalogTypeId=2,CatalogBrandId=2, Description = ".NET Blue Hoodie", Name = ".NET Blue Hoodie", Price = 12, PictureUri = "http://externalcatalogbaseurltobereplaced/api/v1/pic/6", AvailableStock = 100 },
new CatalogItem() { CatalogTypeId=2,CatalogBrandId=5, Description = "Roslyn Red T-Shirt", Name = "Roslyn Red T-Shirt", Price = 12, PictureUri = "http://externalcatalogbaseurltobereplaced/api/v1/pic/7", AvailableStock = 100 },
new CatalogItem() { CatalogTypeId=2,CatalogBrandId=5, Description = "Kudu Purple Hoodie", Name = "Kudu Purple Hoodie", Price = 8.5M, PictureUri = "http://externalcatalogbaseurltobereplaced/api/v1/pic/8", AvailableStock = 100 },
new CatalogItem() { CatalogTypeId=1,CatalogBrandId=5, Description = "Cup<T> White Mug", Name = "Cup<T> White Mug", Price = 12, PictureUri = "http://externalcatalogbaseurltobereplaced/api/v1/pic/9", AvailableStock = 100 },
new CatalogItem() { CatalogTypeId=3,CatalogBrandId=2, Description = ".NET Foundation Sheet", Name = ".NET Foundation Sheet", Price = 12, PictureUri = "http://externalcatalogbaseurltobereplaced/api/v1/pic/10", AvailableStock = 100 },
new CatalogItem() { CatalogTypeId=3,CatalogBrandId=2, Description = "Cup<T> Sheet", Name = "Cup<T> Sheet", Price = 8.5M, PictureUri = "http://externalcatalogbaseurltobereplaced/api/v1/pic/11", AvailableStock = 100 },
new CatalogItem() { CatalogTypeId=2,CatalogBrandId=5, Description = "Prism White TShirt", Name = "Prism White TShirt", Price = 12, PictureUri = "http://externalcatalogbaseurltobereplaced/api/v1/pic/12", AvailableStock = 100 }
new CatalogItem() { CatalogTypeId=2,CatalogBrandId=2, Description = ".NET Bot Black Hoodie", Name = ".NET Bot Black Hoodie", Price = 19.5M, PictureUri = "1.png" },
new CatalogItem() { CatalogTypeId=1,CatalogBrandId=2, Description = ".NET Black & White Mug", Name = ".NET Black & White Mug", Price= 8.50M, PictureUri = "2.png" },
new CatalogItem() { CatalogTypeId=2,CatalogBrandId=5, Description = "Prism White T-Shirt", Name = "Prism White T-Shirt", Price = 12, PictureUri = "3.png" },
new CatalogItem() { CatalogTypeId=2,CatalogBrandId=2, Description = ".NET Foundation T-shirt", Name = ".NET Foundation T-shirt", Price = 12, PictureUri = "4.png" },
new CatalogItem() { CatalogTypeId=3,CatalogBrandId=5, Description = "Roslyn Red Sheet", Name = "Roslyn Red Sheet", Price = 8.5M, PictureUri = "5.png" },
new CatalogItem() { CatalogTypeId=2,CatalogBrandId=2, Description = ".NET Blue Hoodie", Name = ".NET Blue Hoodie", Price = 12, PictureUri = "6.png" },
new CatalogItem() { CatalogTypeId=2,CatalogBrandId=5, Description = "Roslyn Red T-Shirt", Name = "Roslyn Red T-Shirt", Price = 12, PictureUri = "7.png" },
new CatalogItem() { CatalogTypeId=2,CatalogBrandId=5, Description = "Kudu Purple Hoodie", Name = "Kudu Purple Hoodie", Price = 8.5M, PictureUri = "8.png" },
new CatalogItem() { CatalogTypeId=1,CatalogBrandId=5, Description = "Cup<T> White Mug", Name = "Cup<T> White Mug", Price = 12, PictureUri = "9.png" },
new CatalogItem() { CatalogTypeId=3,CatalogBrandId=2, Description = ".NET Foundation Sheet", Name = ".NET Foundation Sheet", Price = 12, PictureUri = "10.png" },
new CatalogItem() { CatalogTypeId=3,CatalogBrandId=2, Description = "Cup<T> Sheet", Name = "Cup<T> Sheet", Price = 8.5M, PictureUri = "11.png" },
new CatalogItem() { CatalogTypeId=2,CatalogBrandId=5, Description = "Prism White TShirt", Name = "Prism White TShirt", Price = 12, PictureUri = "12.png" }
};
}
static string[] GetHeaders(string[] requiredHeaders, string csvfile)
{
string[] csvheaders = File.ReadLines(csvfile).First().ToLowerInvariant().Split(',');
if (csvheaders.Count() != requiredHeaders.Count())
{
throw new Exception($"requiredHeader count '{ requiredHeaders.Count()}' is different then read header '{csvheaders.Count()}'");
}
foreach (var requiredHeader in requiredHeaders)
{
if (!csvheaders.Contains(requiredHeader))
{
throw new Exception($"does not contain required header '{requiredHeader}'");
}
}
return csvheaders;
}
static void GetCatalogItemPictures(string contentRootPath, string picturePath)
{
DirectoryInfo directory = new DirectoryInfo(picturePath);
foreach (FileInfo file in directory.GetFiles())
{
file.Delete();
}
string zipFileCatalogItemPictures = Path.Combine(contentRootPath, "Setup", "CatalogItems.zip");
ZipFile.ExtractToDirectory(zipFileCatalogItemPictures, picturePath);
}
}
}

+ 8
- 0
src/Services/Catalog/Catalog.API/Setup/CatalogBrands.csv View File

@ -0,0 +1,8 @@
CatalogBrand
Azure
.NET
Visual Studio
SQL Server
Other
CatalogBrandTestOne
CatalogBrandTestTwo

+ 13
- 0
src/Services/Catalog/Catalog.API/Setup/CatalogItems.csv View File

@ -0,0 +1,13 @@
CatalogTypeName,CatalogBrandName,Description,Name,Price,PictureUri
T-Shirt,.NET,".NET Bot Black Hoodie, and more",.NET Bot Black Hoodie,19.5,1.png
Mug,.NET,.NET Black & White Mug,.NET Black & White Mug,8.50,2.png
T-Shirt,Other,Prism White T-Shirt,Prism White T-Shirt,12,3.png
T-Shirt,.NET,.NET Foundation T-shirt,.NET Foundation T-shirt,12,4.png
Sheet,Other,Roslyn Red Sheet,Roslyn Red Sheet,8.5,5.png
T-Shirt,.NET,.NET Blue Hoodie,.NET Blue Hoodie,12,6.png
T-Shirt,Other,Roslyn Red T-Shirt,Roslyn Red T-Shirt",12,7.png
T-Shirt,Other,Kudu Purple Hoodie,Kudu Purple Hoodie,8.5,8.png
Mug,Other,Cup<T> White Mug,Cup<T> White Mug,12,9.png
Sheet,.NET,.NET Foundation Sheet,.NET Foundation Sheet,12,10.png
Sheet,.NET,Cup<T> Sheet,Cup<T> Sheet,8.5,11.png
T-Shirt,Other,Prism White TShirt,Prism White TShirt,12,12.png

+ 7
- 0
src/Services/Catalog/Catalog.API/Setup/CatalogTypes.csv View File

@ -0,0 +1,7 @@
CatalogType
Mug
T-Shirt
Sheet
USB Memory Stick
CatalogTypeTestOne
CatalogTypeTestTwo

BIN
src/Services/Catalog/Catalog.API/Setup/Catalogitems.zip View File


+ 3
- 3
src/Services/Catalog/Catalog.API/Startup.cs View File

@ -153,7 +153,7 @@
var context = (CatalogContext)app
.ApplicationServices.GetService(typeof(CatalogContext));
WaitForSqlAvailabilityAsync(context, loggerFactory, app).Wait();
WaitForSqlAvailabilityAsync(context, loggerFactory, app, env).Wait();
ConfigureEventBus(app);
@ -165,13 +165,13 @@
integrationEventLogContext.Database.Migrate();
}
private async Task WaitForSqlAvailabilityAsync(CatalogContext ctx, ILoggerFactory loggerFactory, IApplicationBuilder app, int retries = 0)
private async Task WaitForSqlAvailabilityAsync(CatalogContext ctx, ILoggerFactory loggerFactory, IApplicationBuilder app, IHostingEnvironment env, int retries = 0)
{
var logger = loggerFactory.CreateLogger(nameof(Startup));
var policy = CreatePolicy(retries, logger, nameof (WaitForSqlAvailabilityAsync));
await policy.ExecuteAsync(async () =>
{
await CatalogContextSeed.SeedAsync(app, loggerFactory);
await CatalogContextSeed.SeedAsync(app, env, loggerFactory);
});
}


+ 1
- 0
src/Services/Catalog/Catalog.API/settings.json View File

@ -1,6 +1,7 @@
{
"ConnectionString": "Server=tcp:127.0.0.1,5433;Initial Catalog=Microsoft.eShopOnContainers.Services.CatalogDb;User Id=sa;Password=Pass@word",
"ExternalCatalogBaseUrl": "http://localhost:5101",
"UseCustomizationData": true,
"Logging": {
"IncludeScopes": false,
"LogLevel": {


+ 1
- 0
src/Services/Identity/Identity.API/AppSettings.cs View File

@ -8,5 +8,6 @@ namespace eShopOnContainers.Identity
public class AppSettings
{
public string MvcClient { get; set; }
public bool UseCustomizationData { get; set; }
}
}

+ 137
- 19
src/Services/Identity/Identity.API/Data/ApplicationContextSeed.cs View File

@ -3,13 +3,20 @@
using AspNetCore.Identity;
using EntityFrameworkCore;
using Extensions.Logging;
using global::eShopOnContainers.Identity;
using global::Identity.API.Data;
using global::Identity.API.Models;
using Identity.API.Extensions;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
public class ApplicationContextSeed
@ -21,20 +28,29 @@
_passwordHasher = passwordHasher;
}
public async Task SeedAsync(IApplicationBuilder applicationBuilder, ILoggerFactory loggerFactory, int? retry = 0)
public async Task SeedAsync(IApplicationBuilder applicationBuilder, IHostingEnvironment env, ILoggerFactory loggerFactory, int? retry = 0)
{
int retryForAvaiability = retry.Value;
try
{
var log = loggerFactory.CreateLogger("application seed");
var context = (ApplicationDbContext)applicationBuilder
.ApplicationServices.GetService(typeof(ApplicationDbContext));
context.Database.Migrate();
var settings = (AppSettings)applicationBuilder
.ApplicationServices.GetRequiredService<IOptions<AppSettings>>().Value;
var useCustomizationData = settings.UseCustomizationData;
var contentRootPath = env.ContentRootPath;
if (!context.Users.Any())
{
context.Users.AddRange(
GetDefaultUser());
context.Users.AddRange(useCustomizationData
? GetUsersFromFile(contentRootPath, log)
: GetDefaultUser());
await context.SaveChangesAsync();
}
@ -46,14 +62,93 @@
retryForAvaiability++;
var log = loggerFactory.CreateLogger("catalog seed");
log.LogError(ex.Message);
await SeedAsync(applicationBuilder, loggerFactory, retryForAvaiability);
await SeedAsync(applicationBuilder, env, loggerFactory, retryForAvaiability);
}
}
}
private ApplicationUser GetDefaultUser()
private IEnumerable<ApplicationUser> GetUsersFromFile(string contentRootPath, ILogger log)
{
string csvFileUsers = Path.Combine(contentRootPath, "Setup", "Users.csv");
if (!File.Exists(csvFileUsers))
{
return GetDefaultUser();
}
string[] csvheaders;
try
{
string[] requiredHeaders = {
"cardholdername", "cardnumber", "cardtype", "city", "country",
"email", "expiration", "lastname", "name", "phonenumber",
"username", "zipcode", "state", "street", "securitynumber",
"normalizedemail", "normalizedusername", "password"
};
csvheaders = GetHeaders(requiredHeaders, csvFileUsers);
}
catch (Exception ex)
{
log.LogError(ex.Message);
return GetDefaultUser();
}
List<ApplicationUser> users = File.ReadAllLines(csvFileUsers)
.Skip(1) // skip header column
.Select(row => Regex.Split(row, ",(?=(?:[^\"]*\"[^\"]*\")*[^\"]*$)") )
.SelectTry(column => CreateApplicationUser(column, csvheaders))
.OnCaughtException(ex => { log.LogError(ex.Message); return null; })
.Where(x => x != null)
.ToList();
return users;
}
private ApplicationUser CreateApplicationUser(string[] column, string[] headers)
{
var user =
if (column.Count() != headers.Count())
{
throw new Exception($"column count '{column.Count()}' not the same as headers count'{headers.Count()}'");
}
string cardtypeString = column[Array.IndexOf(headers, "cardtype")].Trim();
if (!int.TryParse(cardtypeString, out int cardtype))
{
throw new Exception($"cardtype='{cardtypeString}' is not a number");
}
var user = new ApplicationUser
{
CardHolderName = column[Array.IndexOf(headers, "cardholdername")].Trim(),
CardNumber = column[Array.IndexOf(headers, "cardnumber")].Trim(),
CardType = cardtype,
City = column[Array.IndexOf(headers, "city")].Trim(),
Country = column[Array.IndexOf(headers, "country")].Trim(),
Email = column[Array.IndexOf(headers, "email")].Trim(),
Expiration = column[Array.IndexOf(headers, "expiration")].Trim(),
Id = Guid.NewGuid().ToString(),
LastName = column[Array.IndexOf(headers, "lastname")].Trim(),
Name = column[Array.IndexOf(headers, "name")].Trim(),
PhoneNumber = column[Array.IndexOf(headers, "phonenumber")].Trim(),
UserName = column[Array.IndexOf(headers, "username")].Trim(),
ZipCode = column[Array.IndexOf(headers, "zipcode")].Trim(),
State = column[Array.IndexOf(headers, "state")].Trim(),
Street = column[Array.IndexOf(headers, "street")].Trim(),
SecurityNumber = column[Array.IndexOf(headers, "securitynumber")].Trim(),
NormalizedEmail = column[Array.IndexOf(headers, "normalizedemail")].Trim(),
NormalizedUserName = column[Array.IndexOf(headers, "normalizedusername")].Trim(),
SecurityStamp = Guid.NewGuid().ToString("D"),
PasswordHash = column[Array.IndexOf(headers, "password")].Trim(), // Note: This is the password
};
user.PasswordHash = _passwordHasher.HashPassword(user, user.PasswordHash);
return user;
}
private IEnumerable<ApplicationUser> GetDefaultUser()
{
var user =
new ApplicationUser()
{
CardHolderName = "DemoUser",
@ -63,23 +158,46 @@
Country = "U.S.",
Email = "demouser@microsoft.com",
Expiration = "12/20",
Id = Guid.NewGuid().ToString(),
LastName = "DemoLastName",
Name = "DemoUser",
PhoneNumber = "1234567890",
UserName = "demouser@microsoft.com",
ZipCode = "98052",
State = "WA",
Street = "15703 NE 61st Ct",
SecurityNumber = "535",
NormalizedEmail = "DEMOUSER@MICROSOFT.COM",
NormalizedUserName = "DEMOUSER@MICROSOFT.COM",
SecurityStamp = Guid.NewGuid().ToString("D")
Id = Guid.NewGuid().ToString(),
LastName = "DemoLastName",
Name = "DemoUser",
PhoneNumber = "1234567890",
UserName = "demouser@microsoft.com",
ZipCode = "98052",
State = "WA",
Street = "15703 NE 61st Ct",
SecurityNumber = "535",
NormalizedEmail = "DEMOUSER@MICROSOFT.COM",
NormalizedUserName = "DEMOUSER@MICROSOFT.COM",
SecurityStamp = Guid.NewGuid().ToString("D"),
};
user.PasswordHash = _passwordHasher.HashPassword(user, "Pass@word1");
return user;
return new List<ApplicationUser>()
{
user
};
}
static string[] GetHeaders(string[] requiredHeaders, string csvfile)
{
string[] csvheaders = File.ReadLines(csvfile).First().ToLowerInvariant().Split(',');
if (csvheaders.Count() != requiredHeaders.Count())
{
throw new Exception($"requiredHeader count '{ requiredHeaders.Count()}' is different then read header '{csvheaders.Count()}'");
}
foreach (var requiredHeader in requiredHeaders)
{
if (!csvheaders.Contains(requiredHeader))
{
throw new Exception($"does not contain required header '{requiredHeader}'");
}
}
return csvheaders;
}
}
}

+ 50
- 0
src/Services/Identity/Identity.API/Extensions/LinqSelectExtensions.cs View File

@ -0,0 +1,50 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace Identity.API.Extensions
{
public static class LinqSelectExtensions
{
public static IEnumerable<SelectTryResult<TSource, TResult>> SelectTry<TSource, TResult>(this IEnumerable<TSource> enumerable, Func<TSource, TResult> selector)
{
foreach (TSource element in enumerable)
{
SelectTryResult<TSource, TResult> returnedValue;
try
{
returnedValue = new SelectTryResult<TSource, TResult>(element, selector(element), null);
}
catch (Exception ex)
{
returnedValue = new SelectTryResult<TSource, TResult>(element, default(TResult), ex);
}
yield return returnedValue;
}
}
public static IEnumerable<TResult> OnCaughtException<TSource, TResult>(this IEnumerable<SelectTryResult<TSource, TResult>> enumerable, Func<Exception, TResult> exceptionHandler)
{
return enumerable.Select(x => x.CaughtException == null ? x.Result : exceptionHandler(x.CaughtException));
}
public static IEnumerable<TResult> OnCaughtException<TSource, TResult>(this IEnumerable<SelectTryResult<TSource, TResult>> enumerable, Func<TSource, Exception, TResult> exceptionHandler)
{
return enumerable.Select(x => x.CaughtException == null ? x.Result : exceptionHandler(x.Source, x.CaughtException));
}
public class SelectTryResult<TSource, TResult>
{
internal SelectTryResult(TSource source, TResult result, Exception exception)
{
Source = source;
Result = result;
CaughtException = exception;
}
public TSource Source { get; private set; }
public TResult Result { get; private set; }
public Exception CaughtException { get; private set; }
}
}
}

+ 10
- 1
src/Services/Identity/Identity.API/Identity.API.csproj View File

@ -8,7 +8,13 @@
<PackageTargetFallback>$(PackageTargetFallback);dotnet5.6;portable-net45+win8</PackageTargetFallback>
<DockerComposeProjectPath>..\..\..\..\docker-compose.dcproj</DockerComposeProjectPath>
</PropertyGroup>
<ItemGroup>
<Content Include="Setup\**\*;">
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
</Content>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Autofac.Extensions.DependencyInjection" Version="4.1.0" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.Cookies" Version="1.1.2" />
@ -70,6 +76,9 @@
<None Update="Dockerfile">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="Setup\*">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
<ItemGroup>


+ 2
- 0
src/Services/Identity/Identity.API/Setup/Users.csv View File

@ -0,0 +1,2 @@
CardHolderName,CardNumber,CardType,City,Country,Email,Expiration,LastName,Name,PhoneNumber,UserName,ZipCode,State,Street,SecurityNumber,NormalizedEmail,NormalizedUserName,Password
DemoUser,4012888888881881,1,Redmond,U.S.,demouser@microsoft.com,12/20,DemoLastName,DemoUser,1234567890,demouser@microsoft.com,98052,WA,15703 NE 61st Ct,535,DEMOUSER@MICROSOFT.COM,DEMOUSER@MICROSOFT.COM,Pass@word1

+ 1
- 1
src/Services/Identity/Identity.API/Startup.cs View File

@ -153,7 +153,7 @@ namespace eShopOnContainers.Identity
//Seed Data
var hasher = new PasswordHasher<ApplicationUser>();
new ApplicationContextSeed(hasher).SeedAsync(app, loggerFactory).Wait();
new ApplicationContextSeed(hasher).SeedAsync(app, env, loggerFactory).Wait();
}
private async Task InitializeGrantStoreAndConfiguration(IApplicationBuilder app)


+ 1
- 0
src/Services/Identity/Identity.API/appsettings.json View File

@ -6,6 +6,7 @@
"MvcClient": "http://localhost:5100",
"SpaClient": "http://localhost:5104",
"XamarinCallback": "http://localhost:5105/xamarincallback",
"UseCustomizationData": true,
"Logging": {
"IncludeScopes": false,
"LogLevel": {


+ 50
- 0
src/Services/Ordering/Ordering.API/Extensions/LinqSelectExtensions.cs View File

@ -0,0 +1,50 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace Ordering.API.Extensions
{
public static class LinqSelectExtensions
{
public static IEnumerable<SelectTryResult<TSource, TResult>> SelectTry<TSource, TResult>(this IEnumerable<TSource> enumerable, Func<TSource, TResult> selector)
{
foreach (TSource element in enumerable)
{
SelectTryResult<TSource, TResult> returnedValue;
try
{
returnedValue = new SelectTryResult<TSource, TResult>(element, selector(element), null);
}
catch (Exception ex)
{
returnedValue = new SelectTryResult<TSource, TResult>(element, default(TResult), ex);
}
yield return returnedValue;
}
}
public static IEnumerable<TResult> OnCaughtException<TSource, TResult>(this IEnumerable<SelectTryResult<TSource, TResult>> enumerable, Func<Exception, TResult> exceptionHandler)
{
return enumerable.Select(x => x.CaughtException == null ? x.Result : exceptionHandler(x.CaughtException));
}
public static IEnumerable<TResult> OnCaughtException<TSource, TResult>(this IEnumerable<SelectTryResult<TSource, TResult>> enumerable, Func<TSource, Exception, TResult> exceptionHandler)
{
return enumerable.Select(x => x.CaughtException == null ? x.Result : exceptionHandler(x.Source, x.CaughtException));
}
public class SelectTryResult<TSource, TResult>
{
internal SelectTryResult(TSource source, TResult result, Exception exception)
{
Source = source;
Result = result;
CaughtException = exception;
}
public TSource Source { get; private set; }
public TResult Result { get; private set; }
public Exception CaughtException { get; private set; }
}
}
}

+ 145
- 10
src/Services/Ordering/Ordering.API/Infrastructure/OrderingContextSeed.cs View File

@ -8,40 +8,175 @@
using System.Threading.Tasks;
using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.BuyerAggregate;
using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate;
using System.Collections.Generic;
using Microsoft.AspNetCore.Hosting;
using System.IO;
using Microsoft.Extensions.Options;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using System;
using global::Ordering.API.Extensions;
public class OrderingContextSeed
{
public static async Task SeedAsync(IApplicationBuilder applicationBuilder)
public static async Task SeedAsync(IApplicationBuilder applicationBuilder, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
var log = loggerFactory.CreateLogger("ordering seed");
var context = (OrderingContext)applicationBuilder
.ApplicationServices.GetService(typeof(OrderingContext));
var settings = applicationBuilder
.ApplicationServices.GetRequiredService<IOptions<OrderingSettings>>().Value;
var useCustomizationData = settings.UseCustomizationData;
var contentRootPath = env.ContentRootPath;
using (context)
{
context.Database.Migrate();
if (!context.CardTypes.Any())
{
context.CardTypes.Add(CardType.Amex);
context.CardTypes.Add(CardType.Visa);
context.CardTypes.Add(CardType.MasterCard);
context.CardTypes.AddRange(useCustomizationData
? GetCardTypesFromFile(contentRootPath, log)
: GetPredefinedCardTypes());
await context.SaveChangesAsync();
}
if (!context.OrderStatus.Any())
{
context.OrderStatus.Add(OrderStatus.Submitted);
context.OrderStatus.Add(OrderStatus.AwaitingValidation);
context.OrderStatus.Add(OrderStatus.StockConfirmed);
context.OrderStatus.Add(OrderStatus.Paid);
context.OrderStatus.Add(OrderStatus.Shipped);
context.OrderStatus.Add(OrderStatus.Cancelled);
context.OrderStatus.AddRange(useCustomizationData
? GetOrderStatusFromFile(contentRootPath, log)
: GetPredefinedOrderStatus());
}
await context.SaveChangesAsync();
}
}
static IEnumerable<CardType> GetCardTypesFromFile(string contentRootPath, ILogger log)
{
string csvFileCardTypes = Path.Combine(contentRootPath, "Setup", "CardTypes.csv");
if (!File.Exists(csvFileCardTypes))
{
return GetPredefinedCardTypes();
}
string[] csvheaders;
try
{
string[] requiredHeaders = { "CardType" };
csvheaders = GetHeaders(requiredHeaders, csvFileCardTypes);
}
catch (Exception ex)
{
log.LogError(ex.Message);
return GetPredefinedCardTypes();
}
int id = 1;
return File.ReadAllLines(csvFileCardTypes)
.Skip(1) // skip header column
.SelectTry(x => CreateCardType(x, ref id))
.OnCaughtException(ex => { log.LogError(ex.Message); return null; })
.Where(x => x != null);
}
static CardType CreateCardType(string value, ref int id)
{
if (String.IsNullOrEmpty(value))
{
throw new Exception("Orderstatus is null or empty");
}
return new CardType(id++, value.Trim());
}
private static IEnumerable<CardType> GetPredefinedCardTypes()
{
return new List<CardType>()
{
CardType.Amex,
CardType.Visa,
CardType.MasterCard
};
}
static IEnumerable<OrderStatus> GetOrderStatusFromFile(string contentRootPath, ILogger log)
{
string csvFileOrderStatus = Path.Combine(contentRootPath, "Setup", "OrderStatus.csv");
if (!File.Exists(csvFileOrderStatus))
{
return GetPredefinedOrderStatus();
}
string[] csvheaders;
try
{
string[] requiredHeaders = { "OrderStatus" };
csvheaders = GetHeaders(requiredHeaders, csvFileOrderStatus);
}
catch (Exception ex)
{
log.LogError(ex.Message);
return GetPredefinedOrderStatus();
}
int id = 1;
return File.ReadAllLines(csvFileOrderStatus)
.Skip(1) // skip header row
.SelectTry(x => CreateOrderStatus(x, ref id))
.OnCaughtException(ex => { log.LogError(ex.Message); return null; })
.Where(x => x != null);
}
static OrderStatus CreateOrderStatus(string value, ref int id)
{
if (String.IsNullOrEmpty(value))
{
throw new Exception("Orderstatus is null or empty");
}
return new OrderStatus(id++, value.Trim().ToLowerInvariant());
}
static IEnumerable<OrderStatus> GetPredefinedOrderStatus()
{
return new List<OrderStatus>()
{
OrderStatus.Submitted,
OrderStatus.AwaitingValidation,
OrderStatus.StockConfirmed,
OrderStatus.Paid,
OrderStatus.Shipped,
OrderStatus.Cancelled
};
}
static string[] GetHeaders(string[] requiredHeaders, string csvfile)
{
string[] csvheaders = File.ReadLines(csvfile).First().ToLowerInvariant().Split(',');
if (csvheaders.Count() != requiredHeaders.Count())
{
throw new Exception($"requiredHeader count '{ requiredHeaders.Count()}' is different then read header '{csvheaders.Count()}'");
}
foreach (var requiredHeader in requiredHeaders)
{
if (!csvheaders.Contains(requiredHeader))
{
throw new Exception($"does not contain required header '{requiredHeader}'");
}
}
return csvheaders;
}
}
}

+ 6
- 0
src/Services/Ordering/Ordering.API/Ordering.API.csproj View File

@ -16,6 +16,9 @@
<Content Include=".dockerignore;">
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
</Content>
<Content Include="Setup\**\*;">
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
</Content>
</ItemGroup>
<ItemGroup>
@ -73,6 +76,9 @@
<None Update="Dockerfile">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="Setup\*">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
<ItemGroup>


+ 7
- 0
src/Services/Ordering/Ordering.API/OrderingSettings.cs View File

@ -0,0 +1,7 @@
namespace Microsoft.eShopOnContainers.Services.Ordering.API
{
public class OrderingSettings
{
public bool UseCustomizationData { get; set; }
}
}

+ 5
- 0
src/Services/Ordering/Ordering.API/Setup/CardTypes.csv View File

@ -0,0 +1,5 @@
CardType
Amex
Visa
MasterCard
Capital One

+ 7
- 0
src/Services/Ordering/Ordering.API/Setup/OrderStatus.csv View File

@ -0,0 +1,7 @@
OrderStatus
Submitted
AwaitingValidation
StockConfirmed
Paid
Shipped
Cancelled

+ 5
- 3
src/Services/Ordering/Ordering.API/Startup.cs View File

@ -87,6 +87,8 @@
ServiceLifetime.Scoped //Showing explicitly that the DbContext is shared across the HTTP request scope (graph of objects started in the HTTP request)
);
services.Configure<OrderingSettings>(Configuration);
services.AddSwaggerGen(options =>
{
options.DescribeAllEnumsAsStrings();
@ -159,7 +161,7 @@
c.SwaggerEndpoint("/swagger/v1/swagger.json", "My API V1");
});
WaitForSqlAvailabilityAsync(loggerFactory, app).Wait();
WaitForSqlAvailabilityAsync(loggerFactory, app, env).Wait();
ConfigureEventBus(app);
var integrationEventLogContext = new IntegrationEventLogContext(
@ -200,13 +202,13 @@
}
private async Task WaitForSqlAvailabilityAsync(ILoggerFactory loggerFactory, IApplicationBuilder app, int retries = 0)
private async Task WaitForSqlAvailabilityAsync(ILoggerFactory loggerFactory, IApplicationBuilder app, IHostingEnvironment env, int retries = 0)
{
var logger = loggerFactory.CreateLogger(nameof(Startup));
var policy = CreatePolicy(retries, logger, nameof(WaitForSqlAvailabilityAsync));
await policy.ExecuteAsync(async () =>
{
await OrderingContextSeed.SeedAsync(app);
await OrderingContextSeed.SeedAsync(app, env, loggerFactory);
});
}


+ 1
- 0
src/Services/Ordering/Ordering.API/settings.json View File

@ -1,6 +1,7 @@
{
"ConnectionString": "Server=tcp:127.0.0.1,5433;Database=Microsoft.eShopOnContainers.Services.OrderingDb;User Id=sa;Password=Pass@word;",
"IdentityUrl": "http://localhost:5105",
"UseCustomizationData": true,
"Logging": {
"IncludeScopes": false,
"LogLevel": {


+ 1
- 0
src/Web/WebMVC/AppSettings.cs View File

@ -12,6 +12,7 @@ namespace Microsoft.eShopOnContainers.WebMVC
public string OrderingUrl { get; set; }
public string BasketUrl { get; set; }
public Logging Logging { get; set; }
public bool UseCustomizationData { get; set; }
}
public class Connectionstrings


+ 97
- 0
src/Web/WebMVC/Infrastructure/WebContextSeed.cs View File

@ -0,0 +1,97 @@
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.eShopOnContainers.WebMVC;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using System;
using System.IO;
using System.IO.Compression;
using System.Linq;
namespace WebMVC.Infrastructure
{
public class WebContextSeed
{
public static void Seed(IApplicationBuilder applicationBuilder, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
var log = loggerFactory.CreateLogger("WebMVC seed");
var settings = (AppSettings)applicationBuilder
.ApplicationServices.GetRequiredService<IOptions<AppSettings>>().Value;
var useCustomizationData = settings.UseCustomizationData;
var contentRootPath = env.ContentRootPath;
var webroot = env.WebRootPath;
if (useCustomizationData)
{
GetPreconfiguredImages(contentRootPath, webroot, log);
GetPreconfiguredCSS(contentRootPath, webroot, log);
}
}
static void GetPreconfiguredCSS(string contentRootPath, string webroot, ILogger log)
{
try
{
string overrideCssFile = Path.Combine(contentRootPath, "Setup", "override.css");
if (!File.Exists(overrideCssFile))
{
log.LogError($" override css file '{overrideCssFile}' does not exists.");
return;
}
string destinationFilename = Path.Combine(webroot, "css", "override.css");
File.Copy(overrideCssFile, destinationFilename, true );
}
catch (Exception ex)
{
log.LogError($"Exception in method GetPreconfiguredCSS WebMVC. Exception Message={ex.Message}");
}
}
static void GetPreconfiguredImages(string contentRootPath, string webroot, ILogger log)
{
try
{
string imagesZipFile = Path.Combine(contentRootPath, "Setup", "images.zip");
if (!File.Exists(imagesZipFile))
{
log.LogError($" zip file '{imagesZipFile}' does not exists.");
return;
}
string imagePath = Path.Combine(webroot, "images");
string[] imageFiles = Directory.GetFiles(imagePath).Select(file => Path.GetFileName(file)).ToArray();
using (ZipArchive zip = ZipFile.Open(imagesZipFile, ZipArchiveMode.Read))
{
foreach (ZipArchiveEntry entry in zip.Entries)
{
if (imageFiles.Contains(entry.Name))
{
string destinationFilename = Path.Combine(imagePath, entry.Name);
if (File.Exists(destinationFilename))
{
File.Delete(destinationFilename);
}
entry.ExtractToFile(destinationFilename);
}
else
{
log.LogWarning($"Skip file '{entry.Name}' in zipfile '{imagesZipFile}'");
}
}
}
}
catch ( Exception ex )
{
log.LogError($"Exception in method GetPreconfiguredImages WebMVC. Exception Message={ex.Message}");
}
}
}
}

BIN
src/Web/WebMVC/Setup/images.zip View File


+ 6
- 0
src/Web/WebMVC/Setup/override.css View File

@ -0,0 +1,6 @@
.esh-catalog-button {
background-color: #FF001b;
border: 2px;
color: #fff;
cursor: pointer;
}

+ 4
- 0
src/Web/WebMVC/Startup.cs View File

@ -12,6 +12,7 @@ using Microsoft.Extensions.HealthChecks;
using Microsoft.Extensions.Logging;
using System;
using System.IdentityModel.Tokens.Jwt;
using WebMVC.Infrastructure;
namespace Microsoft.eShopOnContainers.WebMVC
{
@ -128,6 +129,9 @@ namespace Microsoft.eShopOnContainers.WebMVC
Scope = { "openid", "profile", "orders", "basket" }
};
//Seed Data
WebContextSeed.Seed(app, env, loggerFactory);
//Wait untill identity service is ready on compose.
app.UseOpenIdConnectAuthentication(oidcOptions);


+ 3
- 1
src/Web/WebMVC/Views/Shared/_Layout.cshtml View File

@ -18,12 +18,14 @@
<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"
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/site.min.css" asp-append-version="true" />
<link rel="stylesheet" href="~/css/override.css" type="text/css" />
</environment>
</head>
<body>
@ -55,7 +57,7 @@
</section>
<section class="col-sm-6">
<div class="esh-app-footer-text hidden-xs"> e-ShoponContainers. By Microsoft Corp. </div>
<img class="esh-app-footer-text hidden-xs" src="~/images/main_footer_text.png" width="335" height="26" alt="footer text image" />
</section>
</article>


+ 19
- 0
src/Web/WebMVC/WebMVC.csproj View File

@ -9,6 +9,25 @@
<DockerComposeProjectPath>..\..\..\docker-compose.dcproj</DockerComposeProjectPath>
</PropertyGroup>
<ItemGroup>
<Compile Remove="Extensions\LinqSelectExtensions.cs" />
<Compile Remove="Services\CustomUIService.cs" />
<Compile Remove="Services\ICustomUIService.cs" />
</ItemGroup>
<ItemGroup>
<Content Remove="Views\Shared\_Footer.cshtml" />
</ItemGroup>
<ItemGroup>
<Content Include="Setup\images.zip">
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
</Content>
<Content Include="Setup\override.css">
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
</Content>
</ItemGroup>
<!--<ItemGroup>
<Compile Remove="wwwroot\lib\bootstrap\**" />
<Content Remove="wwwroot\lib\bootstrap\**" />


+ 1
- 0
src/Web/WebMVC/appsettings.json View File

@ -6,6 +6,7 @@
"CallBackUrl": "http://localhost:5100/",
"IsClusterEnv": "False",
"UseResilientHttp": "True",
"UseCustomizationData": true,
"Logging": {
"IncludeScopes": false,
"LogLevel": {


+ 42
- 0
src/Web/WebMVC/compilerconfig.json View 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"
}
]

+ 49
- 0
src/Web/WebMVC/compilerconfig.json.defaults View File

@ -0,0 +1,49 @@
{
"compilers": {
"less": {
"autoPrefix": "",
"cssComb": "none",
"ieCompat": true,
"strictMath": false,
"strictUnits": false,
"relativeUrls": true,
"rootPath": "",
"sourceMapRoot": "",
"sourceMapBasePath": "",
"sourceMap": false
},
"sass": {
"includePath": "",
"indentType": "space",
"indentWidth": 2,
"outputStyle": "expanded",
"Precision": 5,
"relativeUrls": true,
"sourceMapRoot": "",
"sourceMap": false
},
"stylus": {
"sourceMap": false
},
"babel": {
"sourceMap": false
},
"coffeescript": {
"bare": false,
"runtimeMode": "node",
"sourceMap": false
}
},
"minifiers": {
"css": {
"enabled": true,
"termSemicolons": true,
"gzip": false
},
"javascript": {
"enabled": true,
"termSemicolons": true,
"gzip": false
}
}
}

+ 58
- 0
src/Web/WebMVC/wwwroot/css/_variables.scss View File

@ -0,0 +1,58 @@
// 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-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';

+ 9
- 15
src/Web/WebMVC/wwwroot/css/app.component.css View File

@ -1,20 +1,14 @@
.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 {
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;
height: 50px;
width: 230px;
}
.esh-app-footer-text {
color: #83D01B;
line-height: 50px;
text-align: right;
width: 100%;
}

+ 23
- 0
src/Web/WebMVC/wwwroot/css/app.component.scss View 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;
}
}
}

+ 28
- 27
src/Web/WebMVC/wwwroot/css/basket/basket-status/basket-status.component.css View File

@ -1,38 +1,39 @@
.esh-basketstatus {
cursor: pointer;
display: inline-block;
float: right;
position: relative;
transition: all 0.35s;
.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.is-disabled {
opacity: .5;
pointer-events: none;
}
.esh-basketstatus-image {
height: 36px;
margin-top: .5rem;
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;
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:hover .esh-basketstatus-badge {
background-color: transparent;
color: #75b918;
transition: all 0.35s;
background-color: transparent;
color: #75b918;
transition: all 0.35s;
}

+ 41
- 0
src/Web/WebMVC/wwwroot/css/basket/basket-status/basket-status.component.scss View File

@ -0,0 +1,41 @@
@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;
}
&:hover &-badge {
background-color: transparent;
color: $color-secondary-dark;
transition: all $animation-speed-default;
}
}

+ 40
- 39
src/Web/WebMVC/wwwroot/css/basket/basket.component.css View File

@ -1,78 +1,79 @@
.esh-basket {
min-height: 80vh;
.esh-basket {
min-height: 80vh;
}
.esh-basket-titles {
padding-bottom: 1rem;
padding-top: 2rem;
padding-bottom: 1rem;
padding-top: 2rem;
}
.esh-basket-titles--clean {
padding-bottom: 0;
padding-top: 0;
padding-bottom: 0;
padding-top: 0;
}
.esh-basket-title {
text-transform: uppercase;
text-transform: uppercase;
}
.esh-basket-items--border {
border-bottom: 1px solid #EEEEEE;
padding: .5rem 0;
border-bottom: 1px solid #EEEEEE;
padding: .5rem 0;
}
.esh-basket-items--border:last-of-type {
border-color: transparent;
}
.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;
font-size: 1rem;
font-weight: 300;
}
.esh-basket-item--middle {
line-height: 8rem;
line-height: 8rem;
}
@media screen and (max-width: 1024px) {
.esh-basket-item--middle {
line-height: 1rem;
}
.esh-basket-item--middle {
line-height: 1rem;
}
}
.esh-basket-item--mark {
color: #00A69C;
color: #00A69C;
}
.esh-basket-image {
height: 8rem;
height: 8rem;
}
.esh-basket-input {
line-height: 1rem;
width: 100%;
line-height: 1rem;
width: 100%;
}
.esh-basket-checkout {
border: none;
border-radius: 0;
background-color: #83D01B;
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;
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;
background-color: #4a760f;
transition: all 0.35s;
}
.esh-basket-margin12{
margin-left: 12px;
}

+ 89
- 0
src/Web/WebMVC/wwwroot/css/basket/basket.component.scss View 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;
}
}
}

+ 110
- 108
src/Web/WebMVC/wwwroot/css/catalog/catalog.component.css View File

@ -1,147 +1,149 @@
.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;
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-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;
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%;
}
.esh-catalog-item {
width: 50%;
}
}
@media screen and (max-width: 768px) {
.esh-catalog-item {
width: 100%;
}
.esh-catalog-item {
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: '$';
}

+ 154
- 0
src/Web/WebMVC/wwwroot/css/catalog/catalog.component.scss View 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: '$';
}
}
}

+ 21
- 20
src/Web/WebMVC/wwwroot/css/orders/orders-detail/orders-detail.component.css View File

@ -1,52 +1,53 @@
.esh-orders_detail {
min-height: 80vh;
.esh-orders_detail {
min-height: 80vh;
}
.esh-orders_detail-section {
padding: 1rem 0;
padding: 1rem 0;
}
.esh-orders_detail-section--right {
text-align: right;
text-align: right;
}
.esh-orders_detail-titles {
padding-bottom: 1rem;
padding-top: 2rem;
padding-bottom: 1rem;
padding-top: 2rem;
}
.esh-orders_detail-title {
text-transform: uppercase;
text-transform: uppercase;
}
.esh-orders_detail-items--border {
border-bottom: 1px solid #EEEEEE;
padding: .5rem 0;
border-bottom: 1px solid #EEEEEE;
padding: .5rem 0;
}
.esh-orders_detail-items--border:last-of-type {
border-color: transparent;
}
.esh-orders_detail-items--border:last-of-type {
border-color: transparent;
}
.esh-orders_detail-item {
font-size: 1rem;
font-weight: 300;
font-size: 1rem;
font-weight: 300;
}
.esh-orders_detail-item--middle {
line-height: 8rem;
line-height: 8rem;
}
@media screen and (max-width: 768px) {
.esh-orders_detail-item--middle {
line-height: 1rem;
}
.esh-orders_detail-item--middle {
line-height: 1rem;
}
}
.esh-orders_detail-item--mark {
color: #83D01B;
color: #83D01B;
}
.esh-orders_detail-image {
height: 8rem;
height: 8rem;
}

+ 56
- 0
src/Web/WebMVC/wwwroot/css/orders/orders-detail/orders-detail.component.scss View File

@ -0,0 +1,56 @@
@import '../../variables';
.esh-orders_detail {
min-height: 80vh;
&-section {
padding: 1rem 0;
&--right {
text-align: right;
}
}
&-titles {
padding-bottom: 1rem;
padding-top: 2rem;
}
&-title {
text-transform: uppercase;
}
&-items {
&--border {
border-bottom: $border-light solid $color-foreground-bright;
padding: .5rem 0;
&:last-of-type {
border-color: transparent;
}
}
}
$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-s) {
line-height: $font-size-m;
}
}
&--mark {
color: $color-secondary;
}
}
&-image {
height: $item-height;
}
}

+ 55
- 50
src/Web/WebMVC/wwwroot/css/orders/orders-new/orders-new.component.css View File

@ -1,91 +1,96 @@
.esh-orders_new {
min-height: 80vh;
.esh-orders_new {
min-height: 80vh;
}
.esh-orders_new-header {
background-color: #00A69C;
height: 4rem;
background-color: #00A69C;
height: 4rem;
}
.esh-orders_new-back {
color: rgba(255, 255, 255, 0.4);
line-height: 4rem;
text-decoration: none;
text-transform: uppercase;
transition: color 0.35s;
color: rgba(255, 255, 255, 0.4);
line-height: 4rem;
text-decoration: none;
text-transform: uppercase;
transition: color 0.35s;
}
.esh-orders_new-back:hover {
color: #FFFFFF;
transition: color 0.35s;
}
.esh-orders_new-back:hover {
color: #FFFFFF;
transition: color 0.35s;
}
.esh-orders_new-section {
padding: 1rem 0;
padding: 1rem 0;
}
.esh-orders_new-section--right {
text-align: right;
text-align: right;
}
.esh-orders_new-placeOrder {
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-orders_new-placeOrder:hover {
background-color: #4a760f;
transition: all 0.35s;
}
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-orders_new-placeOrder:hover {
background-color: #4a760f;
transition: all 0.35s;
}
.esh-orders_new-titles {
padding-bottom: 1rem;
padding-top: 2rem;
padding-bottom: 1rem;
padding-top: 2rem;
}
.esh-orders_new-title {
font-size: 1.25rem;
text-transform: uppercase;
font-size: 1.25rem;
text-transform: uppercase;
}
.esh-orders_new-items--border {
border-bottom: 1px solid #EEEEEE;
padding: .5rem 0;
border-bottom: 1px solid #EEEEEE;
padding: .5rem 0;
}
.esh-orders_new-items--border:last-of-type {
border-color: transparent;
}
.esh-orders_new-items--border:last-of-type {
border-color: transparent;
}
.esh-orders_new-item {
font-size: 1rem;
font-weight: 300;
font-size: 1rem;
font-weight: 300;
}
.esh-orders_new-item--middle {
line-height: 8rem;
line-height: 8rem;
}
@media screen and (max-width: 768px) {
.esh-orders_new-item--middle {
line-height: 1rem;
}
.esh-orders_new-item--middle {
line-height: 1rem;
}
}
.esh-orders_new-item--mark {
color: #83D01B;
color: #83D01B;
}
.esh-orders_new-image {
height: 8rem;
height: 8rem;
}
.esh-orders_new-alert {
margin-top: 10px;
}

+ 101
- 0
src/Web/WebMVC/wwwroot/css/orders/orders-new/orders-new.component.scss View File

@ -0,0 +1,101 @@
@import '../../variables';
.esh-orders_new {
min-height: 80vh;
$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;
}
}
&-section {
padding: 1rem 0;
&--right {
text-align: right;
}
}
&-placeOrder {
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;
}
}
&-titles {
padding-bottom: 1rem;
padding-top: 2rem;
}
&-title {
font-size: $font-size-l;
text-transform: uppercase;
}
&-items {
&--border {
border-bottom: $border-light solid $color-foreground-bright;
padding: .5rem 0;
&:last-of-type {
border-color: transparent;
}
}
}
$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-s) {
line-height: $font-size-m;
}
}
&--mark {
color: $color-secondary;
}
}
&-image {
height: $item-height;
}
&-alert {
margin-top: 10px;
}
}

+ 44
- 43
src/Web/WebMVC/wwwroot/css/orders/orders.component.css View File

@ -1,74 +1,75 @@
.esh-orders {
min-height: 80vh;
overflow-x: hidden;
.esh-orders {
min-height: 80vh;
overflow-x: hidden;
}
.esh-orders-header {
background-color: #00A69C;
height: 4rem;
background-color: #00A69C;
height: 4rem;
}
.esh-orders-back {
color: rgba(255, 255, 255, 0.4);
line-height: 4rem;
text-transform: uppercase;
text-decoration: none;
transition: color 0.35s;
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-back:hover {
color: #FFFFFF;
transition: color 0.35s;
}
.esh-orders-titles {
padding-bottom: 1rem;
padding-top: 2rem;
padding-bottom: 1rem;
padding-top: 2rem;
}
.esh-orders-title {
text-transform: uppercase;
text-transform: uppercase;
}
.esh-orders-items {
height: 2rem;
line-height: 2rem;
position: relative;
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-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;
font-weight: 300;
}
.esh-orders-item--hover {
opacity: 0;
pointer-events: none;
opacity: 0;
pointer-events: none;
}
.esh-orders-items:hover .esh-orders-item--hover {
opacity: 1;
pointer-events: all;
opacity: 1;
pointer-events: all;
}
.esh-orders-link {
color: #83D01B;
text-decoration: none;
transition: color 0.35s;
color: #83D01B;
text-decoration: none;
transition: color 0.35s;
}
.esh-orders-link:hover {
color: #75b918;
transition: color 0.35s;
}
.esh-orders-link:hover {
color: #75b918;
transition: color 0.35s;
}

+ 81
- 0
src/Web/WebMVC/wwwroot/css/orders/orders.component.scss View File

@ -0,0 +1,81 @@
@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;
}
}
}

+ 6
- 0
src/Web/WebMVC/wwwroot/css/override.css View File

@ -0,0 +1,6 @@
.esh-catalog-button {
background-color: #FF001b;
border: 2px;
color: #fff;
cursor: pointer;
}

+ 12
- 25
src/Web/WebMVC/wwwroot/css/shared/components/header/header.css View File

@ -1,31 +1,18 @@
.esh-header {
background-color: #00A69C;
height: 4rem;
.esh-header {
background-color: #00A69C;
height: 4rem;
}
.esh-header-title {
color: rgba(255, 255, 255, 0.5) !important;
line-height: 4rem;
text-transform: uppercase;
text-decoration: none;
transition: color 0.35s;
margin-right: 15px;
.esh-header-back {
color: rgba(255, 255, 255, 0.5);
line-height: 4rem;
text-decoration: none;
text-transform: uppercase;
transition: color 0.35s;
}
.esh-header-title:hover {
color: #FFFFFF !important;
transition: color 0.35s;
}
.esh-header-back {
color: rgba(255, 255, 255, 0.5) !important;
line-height: 4rem;
text-transform: uppercase;
text-decoration: none;
transition: color 0.35s;
.esh-header-back:hover {
color: #FFFFFF;
transition: color 0.35s;
}
.esh-header-back:hover {
color: #FFFFFF !important;
transition: color 0.35s;
}

+ 21
- 0
src/Web/WebMVC/wwwroot/css/shared/components/header/header.scss View File

@ -0,0 +1,21 @@
@import '../../../variables';
.esh-header {
$header-height: 4rem;
background-color: $color-brand;
height: $header-height;
&-back {
color: rgba($color-foreground-brighter, .5);
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;
}
}
}

+ 31
- 31
src/Web/WebMVC/wwwroot/css/shared/components/identity/identity.css View File

@ -1,57 +1,57 @@
.esh-identity {
line-height: 3rem;
position: relative;
text-align: right;
.esh-identity {
line-height: 3rem;
position: relative;
text-align: right;
}
.esh-identity-section {
display: inline-block;
width: 100%;
display: inline-block;
width: 100%;
}
.esh-identity-name {
display: inline-block;
display: inline-block;
}
.esh-identity-name--upper {
text-transform: uppercase;
text-transform: uppercase;
}
@media screen and (max-width: 768px) {
.esh-identity-name {
font-size: 0.85rem;
}
.esh-identity-name {
font-size: 0.85rem;
}
}
.esh-identity-image {
display: inline-block;
display: inline-block;
}
.esh-identity-drop {
background: #FFFFFF;
height: 0;
min-width: 14rem;
right: 0;
overflow: hidden;
padding: .5rem;
position: absolute;
top: 2.5rem;
transition: height 0.35s;
background: #FFFFFF;
height: 0;
min-width: 14rem;
overflow: hidden;
padding: .5rem;
position: absolute;
right: 0;
top: 2.5rem;
transition: height 0.35s;
}
.esh-identity:hover .esh-identity-drop {
border: 1px solid #EEEEEE;
height: 7rem;
transition: height 0.35s;
border: 1px solid #EEEEEE;
height: 7rem;
transition: height 0.35s;
}
.esh-identity-item {
cursor: pointer;
display: block;
transition: color 0.35s;
cursor: pointer;
transition: color 0.35s;
}
.esh-identity-item:hover {
color: #75b918;
transition: color 0.35s;
}
.esh-identity-item:hover {
color: #75b918;
transition: color 0.35s;
}

+ 56
- 0
src/Web/WebMVC/wwwroot/css/shared/components/identity/identity.scss View File

@ -0,0 +1,56 @@
@import '../../../variables';
.esh-identity {
line-height: 3rem;
position: relative;
text-align: right;
&-section {
display: inline-block;
width: 100%;
}
&-name {
display: inline-block;
&--upper {
text-transform: uppercase;
}
@media screen and (max-width: $media-screen-s) {
font-size: $font-size-s;
}
}
&-image {
display: inline-block;
}
&-drop {
background: $color-background-brighter;
height: 0;
min-width: 14rem;
overflow: hidden;
padding: .5rem;
position: absolute;
right: 0;
top: 2.5rem;
transition: height $animation-speed-default;
}
&:hover &-drop {
border: $border-light solid $color-foreground-bright;
height: 7rem;
transition: height $animation-speed-default;
}
&-item {
cursor: pointer;
transition: color $animation-speed-default;
&:hover {
color: $color-secondary-dark;
transition: color $animation-speed-default;
}
}
}

+ 21
- 20
src/Web/WebMVC/wwwroot/css/shared/components/pager/pager.css View File

@ -1,34 +1,35 @@
.esh-pager-wrapper {
padding-top: 1rem;
text-align: center;
.esh-pager-wrapper {
padding-top: 1rem;
text-align: center;
}
.esh-pager-item {
margin: 0 5vw;
margin: 0 5vw;
}
.esh-pager-item--navigable {
display: inline-block;
cursor: pointer;
.esh-pager-item.is-disabled {
opacity: 0;
pointer-events: none;
}
.esh-pager-item--navigable.is-disabled {
opacity: 0;
pointer-events: none;
}
.esh-pager-item--navigable {
cursor: pointer;
display: inline-block;
}
.esh-pager-item--navigable:hover {
color: #83D01B;
}
.esh-pager-item--navigable:hover {
color: #83D01B;
}
@media screen and (max-width: 1280px) {
.esh-pager-item {
font-size: 0.85rem;
}
.esh-pager-item {
font-size: 0.85rem;
}
}
@media screen and (max-width: 1024px) {
.esh-pager-item {
margin: 0 4vw;
}
.esh-pager-item {
margin: 0 2.5vw;
}
}

+ 36
- 0
src/Web/WebMVC/wwwroot/css/shared/components/pager/pager.scss View File

@ -0,0 +1,36 @@
@import '../../../variables';
.esh-pager {
&-wrapper {
padding-top: 1rem;
text-align: center;
}
&-item {
$margin: 5vw;
margin: 0 $margin;
&.is-disabled {
opacity: 0;
pointer-events: none;
}
&--navigable {
cursor: pointer;
display: inline-block;
&:hover {
color: $color-secondary;
}
}
@media screen and (max-width: $media-screen-l) {
font-size: $font-size-s;
}
@media screen and (max-width: $media-screen-m) {
margin: 0 $margin / 2;
}
}
}

+ 1
- 1
src/Web/WebMVC/wwwroot/css/site.min.css
File diff suppressed because it is too large
View File


BIN
src/Web/WebMVC/wwwroot/images/main_footer_text.png View File

Before After
Width: 335  |  Height: 26  |  Size: 3.1 KiB

+ 1
- 0
src/Web/WebSPA/AppSettings.cs View File

@ -12,5 +12,6 @@ namespace eShopOnContainers.WebSPA
public string OrderingUrl { get; set; }
public string IdentityUrl { get; set; }
public string BasketUrl { get; set; }
public bool UseCustomizationData { get; set; }
}
}

BIN
src/Web/WebSPA/Client/assets/images/main_footer_text.png View File

Before After
Width: 335  |  Height: 26  |  Size: 3.1 KiB

+ 5
- 0
src/Web/WebSPA/Client/modules/_variables.scss View File

@ -51,3 +51,8 @@ $media-screen-xxxl: 1920px;
// Borders
$border-light: 1px;
// Images
$image_path: '../../assets/images/';
$image-main_banner: '#{$image_path}main_banner.png';
$image-arrow_down: '#{$image_path}arrow-down.png';

+ 1
- 1
src/Web/WebSPA/Client/modules/app.component.html View File

@ -32,7 +32,7 @@
</section>
<section class="col-sm-6">
<div class="esh-app-footer-text hidden-xs"> e-ShoponContainers. All right reserved </div>
<img class="esh-app-footer-text hidden-xs" src="assets/images/main_footer_text.png" width="335" height="26" alt="footer text image" />
</section>
</article>


+ 0
- 6
src/Web/WebSPA/Client/modules/app.component.scss View File

@ -19,11 +19,5 @@
width: 230px;
}
&-text {
color: $color-secondary;
line-height: $height;
text-align: right;
width: 100%;
}
}
}

+ 2
- 2
src/Web/WebSPA/Client/modules/catalog/catalog.component.scss View File

@ -4,7 +4,7 @@
$banner-height: 260px;
&-hero {
background-image: url('../../assets/images/main_banner.png');
background-image: url($image-main_banner);
background-size: cover;
height: $banner-height;
width: 100%;
@ -61,7 +61,7 @@
}
&::after {
background-image: url('../../assets/images/arrow-down.png');
background-image: url($image-arrow_down);
content: '';
height: 7px; //png height
position: absolute;


+ 73
- 0
src/Web/WebSPA/Server/Infrastructure/WebContextSeed.cs View File

@ -0,0 +1,73 @@
using eShopOnContainers.WebSPA;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using System;
using System.IO;
using System.IO.Compression;
using System.Linq;
namespace WebSPA.Infrastructure
{
public class WebContextSeed
{
public static void Seed(IApplicationBuilder applicationBuilder, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
var log = loggerFactory.CreateLogger("WebSPA seed");
var settings = (AppSettings)applicationBuilder
.ApplicationServices.GetRequiredService<IOptions<AppSettings>>().Value;
var useCustomizationData = settings.UseCustomizationData;
var contentRootPath = env.ContentRootPath;
var webroot = env.WebRootPath;
if (useCustomizationData)
{
GetPreconfiguredImages(contentRootPath, webroot, log);
}
}
static void GetPreconfiguredImages(string contentRootPath, string webroot, ILogger log)
{
try
{
string imagesZipFile = Path.Combine(contentRootPath, "Setup", "images.zip");
if (!File.Exists(imagesZipFile))
{
log.LogError($" zip file '{imagesZipFile}' does not exists.");
return;
}
string imagePath = Path.Combine(webroot, "assets", "images");
string[] imageFiles = Directory.GetFiles(imagePath).Select(file => Path.GetFileName(file)).ToArray();
using (ZipArchive zip = ZipFile.Open(imagesZipFile, ZipArchiveMode.Read))
{
foreach (ZipArchiveEntry entry in zip.Entries)
{
if (imageFiles.Contains(entry.Name))
{
string destinationFilename = Path.Combine(imagePath, entry.Name);
if (File.Exists(destinationFilename))
{
File.Delete(destinationFilename);
}
entry.ExtractToFile(destinationFilename);
}
else
{
log.LogWarning($"Skip file '{entry.Name}' in zipfile '{imagesZipFile}'");
}
}
}
}
catch (Exception ex)
{
log.LogError($"Exception in method GetPreconfiguredImages WebSPA. Exception Message={ex.Message}");
}
}
}
}

+ 4
- 0
src/Web/WebSPA/Startup.cs View File

@ -11,6 +11,7 @@ using Microsoft.Extensions.HealthChecks;
using Newtonsoft.Json.Serialization;
using eShopOnContainers.WebSPA;
using Microsoft.eShopOnContainers.BuildingBlocks;
using WebSPA.Infrastructure;
namespace eShopConContainers.WebSPA
{
@ -99,6 +100,9 @@ namespace eShopConContainers.WebSPA
// await next.Invoke();
// });
//Seed Data
WebContextSeed.Seed(app, env, loggerFactory);
app.Use(async (context, next) =>
{
await next();


+ 3
- 0
src/Web/WebSPA/WebSPA.csproj View File

@ -16,6 +16,9 @@
<ItemGroup>
<Compile Remove="node_modules\**\*;Client\**\*" />
<Content Include="Setup\images.zip">
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
</Content>
<Content Update="appsettings.json;">
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
</Content>


+ 1
- 0
src/Web/WebSPA/appsettings.json View File

@ -4,6 +4,7 @@
"BasketUrl": "http://localhost:5103",
"IdentityUrl": "http://localhost:5105",
"CallBackUrl": "http://localhost:5104/",
"UseCustomizationData": true,
"IsClusterEnv": "False",
"Logging": {
"IncludeScopes": false,


Loading…
Cancel
Save