MVC: Finish Orders integration (detail), validations, some refactor ...
Orders: Detail Query, new field in OrderDetail.. Identity: Validations in Register View, ensure all claims are returned in user end point..
This commit is contained in:
parent
0ac9526ffe
commit
31753fc057
@ -9,15 +9,15 @@ version: '2'
|
||||
|
||||
services:
|
||||
|
||||
webmvc:
|
||||
environment:
|
||||
- CatalogUrl=http://catalog.api
|
||||
- OrderingUrl=http://ordering.api:5102
|
||||
# webmvc:
|
||||
# environment:
|
||||
# - CatalogUrl=http://catalog.api
|
||||
# - OrderingUrl=http://ordering.api:5102
|
||||
#- IdentityUrl=http://104.40.62.65:5105 #Remote: VM Needs to have public access at 5105.
|
||||
- IdentityUrl=http://identity.service:5105 #Local: You need a entry in windows host file to run identity in local docker.
|
||||
- BasketUrl=http://basket.api:5103
|
||||
ports:
|
||||
- "5100:5100"
|
||||
# - IdentityUrl=http://identity.service:5105 #Local: You need a entry in windows host file to run identity in local docker.
|
||||
# - BasketUrl=http://basket.api:5103
|
||||
# ports:
|
||||
# - "5100:5100"
|
||||
|
||||
webspa:
|
||||
environment:
|
||||
@ -39,38 +39,31 @@ services:
|
||||
|
||||
catalog.api:
|
||||
environment:
|
||||
- ConnectionString=Server=business.data;Database=CatalogDB;User Id=sa;Password=Pass@word
|
||||
- ConnectionString=Server=sql.data;Database=CatalogDB;User Id=sa;Password=Pass@word
|
||||
ports:
|
||||
- "5101:80"
|
||||
|
||||
ordering.api:
|
||||
environment:
|
||||
- ConnectionString=Server=business.data;Database=Microsoft.eShopOnContainers.Services.OrderingDb;User Id=sa;Password=Pass@word
|
||||
- identityUrl=http://identity.service:5105 #local
|
||||
# ordering.api:
|
||||
# environment:
|
||||
# - ConnectionString=Server=sql.data;Database=Microsoft.eShopOnContainers.Services.OrderingDb;User Id=sa;Password=Pass@word
|
||||
# - identityUrl=http://identity.service:5105 #local
|
||||
#- identityUrl=http://104.40.62.65:5105 #remote
|
||||
ports:
|
||||
- "5102:5102"
|
||||
# ports:
|
||||
# - "5102:5102"
|
||||
|
||||
identity.service:
|
||||
environment:
|
||||
- SpaClient=http://localhost:5104
|
||||
- ConnectionStrings__DefaultConnection=Server=identity.data;Database=aspnet-Microsoft.eShopOnContainers;User Id=sa;Password=Pass@word
|
||||
- ConnectionStrings__DefaultConnection=Server=sql.data;Database=aspnet-Microsoft.eShopOnContainers;User Id=sa;Password=Pass@word
|
||||
#- MvcClient=http://104.40.62.65:5100 #Remote: VM Needs to have public access at 5105.
|
||||
- MvcClient=http://localhost:5100 #Local: You need a entry in windows host file to run identity in local docker.
|
||||
#10.0.75.1:5105 CCE/TODO: try to avoid host entry.
|
||||
ports:
|
||||
- "5105:5105"
|
||||
|
||||
identity.data:
|
||||
sql.data:
|
||||
environment:
|
||||
- SA_PASSWORD=Pass@word
|
||||
- ACCEPT_EULA=Y
|
||||
ports:
|
||||
- "5433:1433"
|
||||
|
||||
business.data:
|
||||
environment:
|
||||
- SA_PASSWORD=Pass@word
|
||||
- ACCEPT_EULA=Y
|
||||
ports:
|
||||
- "5434:1433"
|
@ -7,11 +7,11 @@
|
||||
version: '2'
|
||||
|
||||
services:
|
||||
webmvc:
|
||||
image: eshop/web
|
||||
depends_on:
|
||||
- identity.service
|
||||
- basket.api
|
||||
# webmvc:
|
||||
# image: eshop/web
|
||||
# depends_on:
|
||||
# - identity.service
|
||||
# - basket.api
|
||||
|
||||
webspa:
|
||||
image: eshop/webspa
|
||||
@ -28,22 +28,19 @@ services:
|
||||
catalog.api:
|
||||
image: eshop/catalog.api
|
||||
depends_on:
|
||||
- business.data
|
||||
- sql.data
|
||||
|
||||
ordering.api:
|
||||
image: eshop/ordering.api
|
||||
depends_on:
|
||||
- business.data
|
||||
# ordering.api:
|
||||
# image: eshop/ordering.api
|
||||
# depends_on:
|
||||
# - sql.data
|
||||
|
||||
identity.service:
|
||||
image: eshop/identity
|
||||
depends_on:
|
||||
- identity.data
|
||||
- sql.data
|
||||
|
||||
identity.data:
|
||||
image: microsoft/mssql-server-linux
|
||||
|
||||
business.data:
|
||||
sql.data:
|
||||
image: microsoft/mssql-server-linux
|
||||
|
||||
basket.data:
|
||||
|
@ -1,4 +1,4 @@
|
||||
{
|
||||
{
|
||||
"Logging": {
|
||||
"IncludeScopes": false,
|
||||
"LogLevel": {
|
||||
@ -7,6 +7,6 @@
|
||||
"Microsoft": "Information"
|
||||
}
|
||||
},
|
||||
"IdentityUrl": "http://localhost:5000",
|
||||
"IdentityUrl": "http://identity.service:5105",
|
||||
"ConnectionString": "127.0.0.1"
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
{
|
||||
"ConnectionString": "Server=tcp:127.0.0.1,5434;Initial Catalog=CatalogDB;User Id=sa;Password=Pass@word",
|
||||
"ConnectionString": "Server=tcp:127.0.0.1,5433;Initial Catalog=CatalogDB;User Id=sa;Password=Pass@word",
|
||||
"Logging": {
|
||||
"IncludeScopes": false,
|
||||
"LogLevel": {
|
||||
|
@ -99,7 +99,7 @@ namespace IdentityServer4.Quickstart.UI.Controllers
|
||||
props = new AuthenticationProperties
|
||||
{
|
||||
IsPersistent = true,
|
||||
ExpiresUtc = DateTimeOffset.UtcNow.AddMonths(1)
|
||||
ExpiresUtc = DateTimeOffset.UtcNow.AddYears(10)
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -61,7 +61,6 @@
|
||||
CardType = 1,
|
||||
City = "Redmond",
|
||||
Country = "U.S.",
|
||||
CountryCode = "91",
|
||||
Email = "demouser@microsoft.com",
|
||||
Expiration = "12/20",
|
||||
Id = Guid.NewGuid().ToString(),
|
||||
|
@ -49,14 +49,11 @@ namespace WebMVC.Migrations
|
||||
City = table.Column<string>(nullable: true),
|
||||
ConcurrencyStamp = table.Column<string>(nullable: true),
|
||||
Country = table.Column<string>(nullable: true),
|
||||
CountryCode = table.Column<string>(nullable: true),
|
||||
Email = table.Column<string>(maxLength: 256, nullable: true),
|
||||
EmailConfirmed = table.Column<bool>(nullable: false),
|
||||
Expiration = table.Column<string>(nullable: true),
|
||||
Latitude = table.Column<double>(nullable: false),
|
||||
LockoutEnabled = table.Column<bool>(nullable: false),
|
||||
LockoutEnd = table.Column<DateTimeOffset>(nullable: true),
|
||||
Longitude = table.Column<double>(nullable: false),
|
||||
NormalizedEmail = table.Column<string>(maxLength: 256, nullable: true),
|
||||
NormalizedUserName = table.Column<string>(maxLength: 256, nullable: true),
|
||||
PasswordHash = table.Column<string>(nullable: true),
|
||||
@ -65,7 +62,6 @@ namespace WebMVC.Migrations
|
||||
SecurityNumber = table.Column<string>(nullable: true),
|
||||
SecurityStamp = table.Column<string>(nullable: true),
|
||||
State = table.Column<string>(nullable: true),
|
||||
StateCode = table.Column<string>(nullable: true),
|
||||
Street = table.Column<string>(nullable: true),
|
||||
TwoFactorEnabled = table.Column<bool>(nullable: false),
|
||||
UserName = table.Column<string>(maxLength: 256, nullable: true),
|
||||
|
@ -1,16 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Security.Principal;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace eShopOnContainers.Identity.Extensions
|
||||
{
|
||||
//public static class PrincipalExtensions
|
||||
//{
|
||||
// public static string GetSubjectId(this IPrincipal principal)
|
||||
// {
|
||||
// return principal.Identity.GetSubjectId();
|
||||
// }
|
||||
//}
|
||||
}
|
@ -10,21 +10,29 @@ namespace eShopOnContainers.Identity.Models
|
||||
// Add profile data for application users by adding properties to the ApplicationUser class
|
||||
public class ApplicationUser : IdentityUser
|
||||
{
|
||||
[Required]
|
||||
public string CardNumber { get; set; }
|
||||
[Required]
|
||||
public string SecurityNumber { get; set; }
|
||||
[Required]
|
||||
[RegularExpression(@"(0[1-9]|1[0-2])\/[0-9]{2}", ErrorMessage = "Expiration should match a valid MM/YY value")]
|
||||
public string Expiration { get; set; }
|
||||
[Required]
|
||||
public string CardHolderName { get; set; }
|
||||
public int CardType { get; set; }
|
||||
[Required]
|
||||
public string Street { get; set; }
|
||||
[Required]
|
||||
public string City { get; set; }
|
||||
[Required]
|
||||
public string State { get; set; }
|
||||
public string StateCode { get; set; }
|
||||
[Required]
|
||||
public string Country { get; set; }
|
||||
public string CountryCode { get; set; }
|
||||
[Required]
|
||||
public string ZipCode { get; set; }
|
||||
public double Latitude { get; set; }
|
||||
public double Longitude { get; set; }
|
||||
[Required]
|
||||
public string Name { get; set; }
|
||||
[Required]
|
||||
public string LastName { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -11,7 +11,7 @@
|
||||
"IIS Express": {
|
||||
"commandName": "IISExpress",
|
||||
"launchBrowser": true,
|
||||
"launchUrl": "http://localhost:5000",
|
||||
"launchUrl": "http://localhost:5105",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
|
@ -88,6 +88,9 @@ namespace eShopOnContainers.Identity.Services
|
||||
if (!string.IsNullOrWhiteSpace(user.SecurityNumber))
|
||||
claims.Add(new Claim("card_security_number", user.SecurityNumber));
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(user.Expiration))
|
||||
claims.Add(new Claim("card_expiration", user.Expiration));
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(user.City))
|
||||
claims.Add(new Claim("address_city", user.City));
|
||||
|
||||
|
@ -1,8 +1,8 @@
|
||||
{
|
||||
{
|
||||
"ConnectionStrings": {
|
||||
"DefaultConnection": "Server=127.0.0.1,5433;Database=aspnet-Microsoft.eShopOnContainers;User Id=sa;Password=Pass@word"
|
||||
},
|
||||
"MvcClient": "http://localhost:5001",
|
||||
"MvcClient": "http://localhost:5100",
|
||||
"SpaClient": "http://localhost:5104",
|
||||
"Logging": {
|
||||
"IncludeScopes": false,
|
||||
|
@ -7,6 +7,7 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Models;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
[Route("api/v1/[controller]")]
|
||||
@ -34,38 +35,14 @@
|
||||
|
||||
[Route("new")]
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> AddOrder([FromBody]NewOrderViewModel order)
|
||||
public async Task<IActionResult> AddOrder([FromBody]NewOrderRequest order)
|
||||
{
|
||||
if (order.CardExpiration == DateTime.MinValue)
|
||||
order.CardExpiration = DateTime.Now;
|
||||
|
||||
var newOrderRequest = new NewOrderRequest()
|
||||
{
|
||||
Buyer = GetUserName(),
|
||||
CardTypeId = 1, //TODO
|
||||
CardHolderName = order.CardHolderName,
|
||||
CardNumber = order.CardNumber,
|
||||
CardExpiration = order.CardExpiration,
|
||||
CardSecurityNumber = order.CardSecurityNumber,
|
||||
State = order.ShippingState,
|
||||
City = order.ShippingCity,
|
||||
Country = order.ShippingCountry,
|
||||
Street = order.ShippingStreet
|
||||
};
|
||||
|
||||
foreach (var orderItem in order.Items)
|
||||
{
|
||||
newOrderRequest.AddOrderItem(new Domain.OrderItem() {
|
||||
Discount = orderItem.Discount,
|
||||
ProductId = orderItem.ProductId,
|
||||
UnitPrice = orderItem.UnitPrice,
|
||||
ProductName = orderItem.ProductName,
|
||||
Units = orderItem.Units
|
||||
});
|
||||
}
|
||||
|
||||
var added = await _mediator.SendAsync(newOrderRequest);
|
||||
order.Buyer = GetUserName();
|
||||
|
||||
var added = await _mediator.SendAsync(order);
|
||||
if (added)
|
||||
{
|
||||
return Ok();
|
||||
@ -78,9 +55,15 @@
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> GetOrder(int orderId)
|
||||
{
|
||||
var order = await _orderQueries.GetOrder(orderId);
|
||||
|
||||
return Ok(order);
|
||||
try
|
||||
{
|
||||
var order = await _orderQueries.GetOrder(orderId);
|
||||
return Ok(order);
|
||||
}
|
||||
catch (KeyNotFoundException)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
}
|
||||
|
||||
[Route("")]
|
||||
|
@ -172,6 +172,7 @@ namespace Ordering.API.Infrastructure.Migrations
|
||||
OrderId = table.Column<int>(nullable: false),
|
||||
ProductId = table.Column<int>(nullable: false),
|
||||
ProductName = table.Column<string>(nullable: false),
|
||||
PictureUrl = table.Column<string>(nullable: false),
|
||||
UnitPrice = table.Column<decimal>(nullable: false),
|
||||
Units = table.Column<int>(nullable: false, defaultValue: 1)
|
||||
},
|
||||
|
@ -11,5 +11,7 @@
|
||||
public decimal Discount { get; set; }
|
||||
|
||||
public int Units { get; set; }
|
||||
|
||||
public string PictureUrl { get; set; }
|
||||
}
|
||||
}
|
@ -15,9 +15,9 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.API
|
||||
var host = new WebHostBuilder()
|
||||
.UseKestrel()
|
||||
.UseContentRoot(Directory.GetCurrentDirectory())
|
||||
//.UseIISIntegration()
|
||||
.UseStartup<Startup>()
|
||||
.UseUrls("http://0.0.0.0:5102")
|
||||
.UseIISIntegration()
|
||||
.UseStartup<Startup>()
|
||||
.Build();
|
||||
|
||||
host.Run();
|
||||
|
@ -3,7 +3,7 @@
|
||||
"windowsAuthentication": false,
|
||||
"anonymousAuthentication": true,
|
||||
"iisExpress": {
|
||||
"applicationUrl": "http://localhost:5002/",
|
||||
"applicationUrl": "http://localhost:5102/",
|
||||
"sslPort": 0
|
||||
}
|
||||
},
|
||||
|
@ -1,10 +0,0 @@
|
||||
{
|
||||
"Logging": {
|
||||
"IncludeScopes": false,
|
||||
"LogLevel": {
|
||||
"Default": "Debug",
|
||||
"System": "Information",
|
||||
"Microsoft": "Information"
|
||||
}
|
||||
}
|
||||
}
|
@ -1,4 +1,12 @@
|
||||
{
|
||||
"ConnectionString": "Server=tcp:127.0.0.1,5434;Database=Microsoft.eShopOnContainers.Services.OrderingDb;User Id=sa;Password=Pass@word;",
|
||||
"IdentityUrl": "http://localhost:5105"
|
||||
"ConnectionString": "Server=tcp:127.0.0.1,5433;Database=Microsoft.eShopOnContainers.Services.OrderingDb;User Id=sa;Password=Pass@word;",
|
||||
"IdentityUrl": "http://identity.service:5105",
|
||||
"Logging": {
|
||||
"IncludeScopes": false,
|
||||
"LogLevel": {
|
||||
"Default": "Debug",
|
||||
"System": "Information",
|
||||
"Microsoft": "Information"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -5,6 +5,8 @@
|
||||
using System.Data.SqlClient;
|
||||
using System.Threading.Tasks;
|
||||
using System;
|
||||
using System.Dynamic;
|
||||
using System.Collections.Generic;
|
||||
|
||||
public class OrderQueries
|
||||
:IOrderQueries
|
||||
@ -16,14 +18,29 @@
|
||||
_connectionString = configuration["ConnectionString"];
|
||||
}
|
||||
|
||||
|
||||
|
||||
public async Task<dynamic> GetOrder(int id)
|
||||
{
|
||||
using (var connection = new SqlConnection(_connectionString))
|
||||
{
|
||||
connection.Open();
|
||||
|
||||
return await connection.QueryAsync<dynamic>("SELECT * FROM ordering.Orders where Id=@id",new { id });
|
||||
var result = await connection.QueryAsync<dynamic>(
|
||||
@"select o.[Id] as ordernumber,o.OrderDate as date, os.Name as status,
|
||||
oi.ProductName as productname, oi.Units as units, oi.UnitPrice as unitprice, oi.PictureUrl as pictureurl,
|
||||
oa.Street as street, oa.City as city, oa.Country as country, oa.State as state, oa.ZipCode as zipcode
|
||||
FROM ordering.Orders o
|
||||
LEFT JOIN ordering.Orderitems oi ON o.Id = oi.orderid
|
||||
LEFT JOIN ordering.orderstatus os on o.StatusId = os.Id
|
||||
LEFT JOIN ordering.address oa on o.ShippingAddressId = oa.Id
|
||||
WHERE o.Id=@id"
|
||||
, new { id }
|
||||
);
|
||||
|
||||
if (result.AsList().Count == 0)
|
||||
throw new KeyNotFoundException();
|
||||
|
||||
return MapOrderItems(result);
|
||||
}
|
||||
}
|
||||
|
||||
@ -50,5 +67,35 @@
|
||||
return await connection.QueryAsync<dynamic>("SELECT * FROM ordering.cardtypes");
|
||||
}
|
||||
}
|
||||
|
||||
private dynamic MapOrderItems(dynamic result)
|
||||
{
|
||||
dynamic order = new ExpandoObject();
|
||||
|
||||
order.ordernumber = result[0].ordernumber;
|
||||
order.date = result[0].date;
|
||||
order.status = result[0].status;
|
||||
order.street = result[0].street;
|
||||
order.city = result[0].city;
|
||||
order.zipcode = result[0].zipcode;
|
||||
order.country = result[0].country;
|
||||
|
||||
order.orderitems = new List<dynamic>();
|
||||
order.total = 0;
|
||||
|
||||
foreach (dynamic item in result)
|
||||
{
|
||||
dynamic orderitem = new ExpandoObject();
|
||||
orderitem.productname = item.productname;
|
||||
orderitem.units = item.units;
|
||||
orderitem.unitprice = item.unitprice;
|
||||
orderitem.pictureurl = item.pictureurl;
|
||||
|
||||
order.total += item.units * item.unitprice;
|
||||
order.orderitems.Add(orderitem);
|
||||
}
|
||||
|
||||
return order;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -10,6 +10,8 @@
|
||||
|
||||
public string ProductName { get; set; }
|
||||
|
||||
public string PictureUrl { get; set; }
|
||||
|
||||
public int OrderId { get; set; }
|
||||
|
||||
public decimal UnitPrice { get; set; }
|
||||
|
@ -6,7 +6,7 @@ using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.eShopOnContainers.WebMVC.Services;
|
||||
using Microsoft.eShopOnContainers.WebMVC.Models;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.eShopOnContainers.WebMVC.Models.OrderViewModels;
|
||||
using System.Net.Http;
|
||||
|
||||
namespace Microsoft.eShopOnContainers.WebMVC.Controllers
|
||||
{
|
||||
@ -25,31 +25,22 @@ namespace Microsoft.eShopOnContainers.WebMVC.Controllers
|
||||
|
||||
public async Task<IActionResult> Create()
|
||||
{
|
||||
var vm = new CreateOrderViewModel();
|
||||
var user = _appUserParser.Parse(HttpContext.User);
|
||||
var basket = await _basketSvc.GetBasket(user);
|
||||
var order = _basketSvc.MapBasketToOrder(basket);
|
||||
vm.Order = _orderSvc.MapUserInfoIntoOrder(user, order);
|
||||
var vm = _orderSvc.MapUserInfoIntoOrder(user, order);
|
||||
vm.CardExpirationShortFormat();
|
||||
|
||||
return View(vm);
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> Create(CreateOrderViewModel model, Dictionary<string, int> quantities, string action)
|
||||
public async Task<IActionResult> Create(Order model, string action)
|
||||
{
|
||||
var user = _appUserParser.Parse(HttpContext.User);
|
||||
var basket = await _basketSvc.SetQuantities(user, quantities);
|
||||
basket = await _basketSvc.UpdateBasket(basket);
|
||||
var order = _basketSvc.MapBasketToOrder(basket);
|
||||
|
||||
|
||||
// override if user has changed some shipping address or payment info data.
|
||||
_orderSvc.OverrideUserInfoIntoOrder(model.Order, order);
|
||||
|
||||
if (action == "[ Place Order ]")
|
||||
{
|
||||
|
||||
await _orderSvc.CreateOrder(user, order);
|
||||
await _orderSvc.CreateOrder(model);
|
||||
|
||||
//Empty basket for current user.
|
||||
await _basketSvc.CleanBasket(user);
|
||||
@ -61,11 +52,11 @@ namespace Microsoft.eShopOnContainers.WebMVC.Controllers
|
||||
return View(model);
|
||||
}
|
||||
|
||||
public IActionResult Detail(string orderId)
|
||||
public async Task<IActionResult> Detail(string orderId)
|
||||
{
|
||||
var user = _appUserParser.Parse(HttpContext.User);
|
||||
var order = _orderSvc.GetOrder(user, orderId);
|
||||
|
||||
var order = await _orderSvc.GetOrder(user, orderId);
|
||||
return View(order);
|
||||
}
|
||||
|
||||
|
@ -1,15 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.eShopOnContainers.WebMVC.Models.AccountViewModels
|
||||
{
|
||||
public class ExternalLoginConfirmationViewModel
|
||||
{
|
||||
[Required]
|
||||
[EmailAddress]
|
||||
public string Email { get; set; }
|
||||
}
|
||||
}
|
@ -1,15 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.eShopOnContainers.WebMVC.Models.AccountViewModels
|
||||
{
|
||||
public class ForgotPasswordViewModel
|
||||
{
|
||||
[Required]
|
||||
[EmailAddress]
|
||||
public string Email { get; set; }
|
||||
}
|
||||
}
|
@ -1,22 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.eShopOnContainers.WebMVC.Models.AccountViewModels
|
||||
{
|
||||
public class LoginViewModel
|
||||
{
|
||||
[Required]
|
||||
[EmailAddress]
|
||||
public string Email { get; set; }
|
||||
|
||||
[Required]
|
||||
[DataType(DataType.Password)]
|
||||
public string Password { get; set; }
|
||||
|
||||
[Display(Name = "Remember me?")]
|
||||
public bool RememberMe { get; set; }
|
||||
}
|
||||
}
|
@ -1,29 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.eShopOnContainers.WebMVC.Models.AccountViewModels
|
||||
{
|
||||
public class RegisterViewModel
|
||||
{
|
||||
[Required]
|
||||
[EmailAddress]
|
||||
[Display(Name = "Email")]
|
||||
public string Email { get; set; }
|
||||
|
||||
[Required]
|
||||
[StringLength(100, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)]
|
||||
[DataType(DataType.Password)]
|
||||
[Display(Name = "Password")]
|
||||
public string Password { get; set; }
|
||||
|
||||
[DataType(DataType.Password)]
|
||||
[Display(Name = "Confirm password")]
|
||||
[Compare("Password", ErrorMessage = "The password and confirmation password do not match.")]
|
||||
public string ConfirmPassword { get; set; }
|
||||
|
||||
public ApplicationUser User { get; set; }
|
||||
}
|
||||
}
|
@ -1,27 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.eShopOnContainers.WebMVC.Models.AccountViewModels
|
||||
{
|
||||
public class ResetPasswordViewModel
|
||||
{
|
||||
[Required]
|
||||
[EmailAddress]
|
||||
public string Email { get; set; }
|
||||
|
||||
[Required]
|
||||
[StringLength(100, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)]
|
||||
[DataType(DataType.Password)]
|
||||
public string Password { get; set; }
|
||||
|
||||
[DataType(DataType.Password)]
|
||||
[Display(Name = "Confirm password")]
|
||||
[Compare("Password", ErrorMessage = "The password and confirmation password do not match.")]
|
||||
public string ConfirmPassword { get; set; }
|
||||
|
||||
public string Code { get; set; }
|
||||
}
|
||||
}
|
@ -1,19 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Mvc.Rendering;
|
||||
|
||||
namespace Microsoft.eShopOnContainers.WebMVC.Models.AccountViewModels
|
||||
{
|
||||
public class SendCodeViewModel
|
||||
{
|
||||
public string SelectedProvider { get; set; }
|
||||
|
||||
public ICollection<SelectListItem> Providers { get; set; }
|
||||
|
||||
public string ReturnUrl { get; set; }
|
||||
|
||||
public bool RememberMe { get; set; }
|
||||
}
|
||||
}
|
@ -1,25 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.eShopOnContainers.WebMVC.Models.AccountViewModels
|
||||
{
|
||||
public class VerifyCodeViewModel
|
||||
{
|
||||
[Required]
|
||||
public string Provider { get; set; }
|
||||
|
||||
[Required]
|
||||
public string Code { get; set; }
|
||||
|
||||
public string ReturnUrl { get; set; }
|
||||
|
||||
[Display(Name = "Remember this browser?")]
|
||||
public bool RememberBrowser { get; set; }
|
||||
|
||||
[Display(Name = "Remember me?")]
|
||||
public bool RememberMe { get; set; }
|
||||
}
|
||||
}
|
@ -1,21 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.eShopOnContainers.WebMVC.Models
|
||||
{
|
||||
public class Address
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
public string Street { get; set; }
|
||||
public string City { get; set; }
|
||||
public string State { get; set; }
|
||||
public string StateCode { get; set; }
|
||||
public string Country { get; set; }
|
||||
public string CountryCode { get; set; }
|
||||
public string ZipCode { get; set; }
|
||||
public double Latitude { get; set; }
|
||||
public double Longitude { get; set; }
|
||||
}
|
||||
}
|
24
src/Web/WebMVC/Models/Annotations/CardExpiration.cs
Normal file
24
src/Web/WebMVC/Models/Annotations/CardExpiration.cs
Normal file
@ -0,0 +1,24 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.eShopOnContainers.WebMVC.Models.Annotations
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = true)]
|
||||
public class CardExpirationAttribute : ValidationAttribute
|
||||
{
|
||||
public override bool IsValid(object value)
|
||||
{
|
||||
if (value == null)
|
||||
return false;
|
||||
|
||||
var month = value.ToString().Split('/')[0];
|
||||
var year = $"20{value.ToString().Split('/')[1]}";
|
||||
DateTime d = new DateTime(int.Parse(year), int.Parse(month), 1);
|
||||
|
||||
return d > DateTime.UtcNow;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,16 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.eShopOnContainers.WebMVC.Models.ManageViewModels
|
||||
{
|
||||
public class AddPhoneNumberViewModel
|
||||
{
|
||||
[Required]
|
||||
[Phone]
|
||||
[Display(Name = "Phone number")]
|
||||
public string PhoneNumber { get; set; }
|
||||
}
|
||||
}
|
@ -1,27 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.eShopOnContainers.WebMVC.Models.ManageViewModels
|
||||
{
|
||||
public class ChangePasswordViewModel
|
||||
{
|
||||
[Required]
|
||||
[DataType(DataType.Password)]
|
||||
[Display(Name = "Current password")]
|
||||
public string OldPassword { get; set; }
|
||||
|
||||
[Required]
|
||||
[StringLength(100, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)]
|
||||
[DataType(DataType.Password)]
|
||||
[Display(Name = "New password")]
|
||||
public string NewPassword { get; set; }
|
||||
|
||||
[DataType(DataType.Password)]
|
||||
[Display(Name = "Confirm new password")]
|
||||
[Compare("NewPassword", ErrorMessage = "The new password and confirmation password do not match.")]
|
||||
public string ConfirmPassword { get; set; }
|
||||
}
|
||||
}
|
@ -1,15 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Mvc.Rendering;
|
||||
|
||||
namespace Microsoft.eShopOnContainers.WebMVC.Models.ManageViewModels
|
||||
{
|
||||
public class ConfigureTwoFactorViewModel
|
||||
{
|
||||
public string SelectedProvider { get; set; }
|
||||
|
||||
public ICollection<SelectListItem> Providers { get; set; }
|
||||
}
|
||||
}
|
@ -1,12 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.eShopOnContainers.WebMVC.Models.ManageViewModels
|
||||
{
|
||||
public class FactorViewModel
|
||||
{
|
||||
public string Purpose { get; set; }
|
||||
}
|
||||
}
|
@ -1,23 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
|
||||
namespace Microsoft.eShopOnContainers.WebMVC.Models.ManageViewModels
|
||||
{
|
||||
public class IndexViewModel
|
||||
{
|
||||
public bool HasPassword { get; set; }
|
||||
|
||||
public IList<UserLoginInfo> Logins { get; set; }
|
||||
|
||||
public string PhoneNumber { get; set; }
|
||||
|
||||
public bool TwoFactor { get; set; }
|
||||
|
||||
public bool BrowserRemembered { get; set; }
|
||||
|
||||
public ApplicationUser User { get; set; }
|
||||
}
|
||||
}
|
@ -1,16 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http.Authentication;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
|
||||
namespace Microsoft.eShopOnContainers.WebMVC.Models.ManageViewModels
|
||||
{
|
||||
public class ManageLoginsViewModel
|
||||
{
|
||||
public IList<UserLoginInfo> CurrentLogins { get; set; }
|
||||
|
||||
public IList<AuthenticationDescription> OtherLogins { get; set; }
|
||||
}
|
||||
}
|
@ -1,14 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.eShopOnContainers.WebMVC.Models.ManageViewModels
|
||||
{
|
||||
public class RemoveLoginViewModel
|
||||
{
|
||||
public string LoginProvider { get; set; }
|
||||
public string ProviderKey { get; set; }
|
||||
}
|
||||
}
|
@ -1,22 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.eShopOnContainers.WebMVC.Models.ManageViewModels
|
||||
{
|
||||
public class SetPasswordViewModel
|
||||
{
|
||||
[Required]
|
||||
[StringLength(100, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)]
|
||||
[DataType(DataType.Password)]
|
||||
[Display(Name = "New password")]
|
||||
public string NewPassword { get; set; }
|
||||
|
||||
[DataType(DataType.Password)]
|
||||
[Display(Name = "Confirm new password")]
|
||||
[Compare("NewPassword", ErrorMessage = "The new password and confirmation password do not match.")]
|
||||
public string ConfirmPassword { get; set; }
|
||||
}
|
||||
}
|
@ -1,19 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.eShopOnContainers.WebMVC.Models.ManageViewModels
|
||||
{
|
||||
public class VerifyPhoneNumberViewModel
|
||||
{
|
||||
[Required]
|
||||
public string Code { get; set; }
|
||||
|
||||
[Required]
|
||||
[Phone]
|
||||
[Display(Name = "Phone number")]
|
||||
public string PhoneNumber { get; set; }
|
||||
}
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using Microsoft.eShopOnContainers.WebMVC.Models.Annotations;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
@ -8,40 +9,61 @@ namespace Microsoft.eShopOnContainers.WebMVC.Models
|
||||
{
|
||||
public class Order
|
||||
{
|
||||
public Order()
|
||||
{
|
||||
public Order() {
|
||||
OrderItems = new List<OrderItem>();
|
||||
ShippingAddress = new Address();
|
||||
PaymentInfo = new PaymentInfo();
|
||||
}
|
||||
|
||||
public string Id;
|
||||
public List<OrderItem> OrderItems { get; set; }
|
||||
public string OrderNumber
|
||||
public string OrderNumber {get;set;}
|
||||
|
||||
public DateTime Date {get;set;}
|
||||
|
||||
public string Status { get; set; }
|
||||
|
||||
public decimal Total {get;set;}
|
||||
[Required]
|
||||
public string City { get; set; }
|
||||
[Required]
|
||||
public string Street { get; set; }
|
||||
[Required]
|
||||
public string State { get; set; }
|
||||
[Required]
|
||||
public string Country { get; set; }
|
||||
|
||||
public string ZipCode { get; set; }
|
||||
[Required]
|
||||
public string CardNumber { get; set; }
|
||||
[Required]
|
||||
public string CardHolderName { get; set; }
|
||||
|
||||
public DateTime CardExpiration { get; set; }
|
||||
[RegularExpression(@"(0[1-9]|1[0-2])\/[0-9]{2}", ErrorMessage = "Expiration should match a valid MM/YY value")]
|
||||
[CardExpiration(ErrorMessage = "Should't be expired"), Required]
|
||||
public string CardExpirationShort { get; set; }
|
||||
[Required]
|
||||
public string CardSecurityNumber { get; set; }
|
||||
|
||||
public int CardTypeId { get; set; }
|
||||
|
||||
public string Buyer { get; set; }
|
||||
|
||||
public List<OrderItem> OrderItems { get; }
|
||||
|
||||
public void CardExpirationShortFormat()
|
||||
{
|
||||
get
|
||||
{
|
||||
return string.Format("{0}/{1}-{2}", OrderDate.Year, OrderDate.Month, SequenceNumber);
|
||||
}
|
||||
CardExpirationShort = CardExpiration.ToString("MM/yy");
|
||||
}
|
||||
public int SequenceNumber { get; set; }
|
||||
public virtual string BuyerId { get; set; }
|
||||
public virtual Address ShippingAddress { get; set; }
|
||||
public virtual PaymentInfo PaymentInfo { get; set; }
|
||||
public virtual DateTime OrderDate { get; set; }
|
||||
public OrderState State { get; set; }
|
||||
|
||||
public decimal Total() {
|
||||
return OrderItems.Sum(x => x.Quantity * x.UnitPrice);
|
||||
}
|
||||
public void CardExpirationApiFormat()
|
||||
{
|
||||
var month = CardExpirationShort.Split('/')[0];
|
||||
var year = $"20{CardExpirationShort.Split('/')[1]}";
|
||||
|
||||
//(CCE) public virtual Address BillingAddress { get; set; }
|
||||
//(CDLTLL) public virtual OrderStatus Status { get; set; }
|
||||
CardExpiration = new DateTime(int.Parse(year), int.Parse(month), 1);
|
||||
}
|
||||
}
|
||||
|
||||
public enum OrderState:int
|
||||
|
||||
public enum CardType
|
||||
{
|
||||
InProcess = 0,
|
||||
Delivered = 1
|
||||
AMEX = 1
|
||||
}
|
||||
}
|
||||
|
@ -7,18 +7,16 @@ namespace Microsoft.eShopOnContainers.WebMVC.Models
|
||||
{
|
||||
public class OrderItem
|
||||
{
|
||||
public string Id;
|
||||
public string ProductId { get; set; }
|
||||
public string OrderId { get; set; }
|
||||
public string ProductName { get; set; }
|
||||
public decimal UnitPrice { get; set; }
|
||||
public int Quantity { get; set; }
|
||||
public decimal Discount { get; set; }
|
||||
public string PictureUrl { get; set; }
|
||||
public override string ToString()
|
||||
{
|
||||
return String.Format("Product Id: {0}, Quantity: {1}", this.Id, this.Quantity);
|
||||
}
|
||||
}
|
||||
public int ProductId { get; set; }
|
||||
|
||||
public string ProductName { get; set; }
|
||||
|
||||
public decimal UnitPrice { get; set; }
|
||||
|
||||
public decimal Discount { get; set; }
|
||||
|
||||
public int Units { get; set; }
|
||||
|
||||
public string PictureUrl { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -1,40 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.eShopOnContainers.WebMVC.Models
|
||||
{
|
||||
public class OrderRequest
|
||||
{
|
||||
public OrderRequest() {
|
||||
Items = new List<OrderRequestItem>();
|
||||
}
|
||||
|
||||
public string City { get; set; }
|
||||
|
||||
public string Street { get; set; }
|
||||
|
||||
public string State { get; set; }
|
||||
|
||||
public string Country { get; set; }
|
||||
|
||||
public string ZipCode { get; set; }
|
||||
|
||||
public string CardNumber { get; set; }
|
||||
|
||||
public string CardHolderName { get; set; }
|
||||
|
||||
public DateTime CardExpiration { get; set; }
|
||||
|
||||
public string CardSecurityNumber { get; set; }
|
||||
|
||||
public int CardTypeId { get; set; }
|
||||
|
||||
public string Buyer { get; set; }
|
||||
|
||||
public List<OrderRequestItem> Items { get; }
|
||||
|
||||
|
||||
}
|
||||
}
|
@ -1,20 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.eShopOnContainers.WebMVC.Models
|
||||
{
|
||||
public class OrderRequestItem
|
||||
{
|
||||
public int ProductId { get; set; }
|
||||
|
||||
public string ProductName { get; set; }
|
||||
|
||||
public decimal UnitPrice { get; set; }
|
||||
|
||||
public decimal Discount { get; set; }
|
||||
|
||||
public int Units { get; set; }
|
||||
}
|
||||
}
|
@ -1,12 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.eShopOnContainers.WebMVC.Models.OrderViewModels
|
||||
{
|
||||
public class CreateOrderViewModel
|
||||
{
|
||||
public Order Order { get; set; }
|
||||
}
|
||||
}
|
@ -1,25 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.eShopOnContainers.WebMVC.Models
|
||||
{
|
||||
public class PaymentInfo
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
public string CardNumber {get;set;}
|
||||
public string SecurityNumber { get; set; }
|
||||
public int ExpirationMonth { get; set; } //CCE: I would simplify with a string Expiration field so I guess we are not going to validate with real data. It's a demo..
|
||||
public int ExpirationYear { get; set; } //CCE: Idem.
|
||||
public string CardHolderName { get; set; }
|
||||
public CardType CardType { get; set; } //CCE: Discuss with team if this is needed for a demo.
|
||||
public string Expiration { get; set; } //CCE: Added to simplify..
|
||||
}
|
||||
|
||||
public enum CardType:int
|
||||
{
|
||||
AMEX,
|
||||
VISA
|
||||
}
|
||||
}
|
@ -3,7 +3,7 @@
|
||||
"windowsAuthentication": false,
|
||||
"anonymousAuthentication": true,
|
||||
"iisExpress": {
|
||||
"applicationUrl": "http://localhost:5001",
|
||||
"applicationUrl": "http://localhost:5100",
|
||||
"sslPort": 0
|
||||
}
|
||||
},
|
||||
|
@ -79,23 +79,21 @@ namespace Microsoft.eShopOnContainers.WebMVC.Services
|
||||
|
||||
public Order MapBasketToOrder(Basket basket)
|
||||
{
|
||||
var order = new Order()
|
||||
{
|
||||
Id = Guid.NewGuid().ToString(),
|
||||
BuyerId = basket.BuyerId
|
||||
};
|
||||
var order = new Order();
|
||||
order.Total = 0;
|
||||
|
||||
basket.Items.ForEach(x =>
|
||||
{
|
||||
order.OrderItems.Add(new OrderItem()
|
||||
{
|
||||
ProductId = x.ProductId,
|
||||
OrderId = order.Id,
|
||||
ProductId = int.Parse(x.ProductId),
|
||||
|
||||
PictureUrl = x.PictureUrl,
|
||||
ProductName = x.ProductName,
|
||||
Quantity = x.Quantity,
|
||||
Units = x.Quantity,
|
||||
UnitPrice = x.UnitPrice
|
||||
});
|
||||
order.Total += (x.Quantity * x.UnitPrice);
|
||||
});
|
||||
|
||||
return order;
|
||||
@ -109,8 +107,7 @@ namespace Microsoft.eShopOnContainers.WebMVC.Services
|
||||
basket = new Basket()
|
||||
{
|
||||
BuyerId = user.Id,
|
||||
//Id = Guid.NewGuid().ToString(),
|
||||
Items = new List<Models.BasketItem>()
|
||||
Items = new List<BasketItem>()
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -10,7 +10,7 @@ namespace Microsoft.eShopOnContainers.WebMVC.Services
|
||||
{
|
||||
Task<List<Order>> GetMyOrders(ApplicationUser user);
|
||||
Task<Order> GetOrder(ApplicationUser user, string orderId);
|
||||
Task CreateOrder(ApplicationUser user, Order order);
|
||||
Task CreateOrder(Order order);
|
||||
Order MapUserInfoIntoOrder(ApplicationUser user, Order order);
|
||||
void OverrideUserInfoIntoOrder(Order original, Order destination);
|
||||
}
|
||||
|
@ -23,45 +23,6 @@ namespace Microsoft.eShopOnContainers.WebMVC.Services
|
||||
_remoteServiceBaseUrl = $"{settings.Value.OrderingUrl}/api/v1/orders";
|
||||
_settings = settings;
|
||||
_httpContextAccesor = httpContextAccesor;
|
||||
|
||||
#region fake items
|
||||
//_orders = new List<Order>()
|
||||
//{
|
||||
// new Order()
|
||||
// {
|
||||
// Id = Guid.NewGuid().ToString(),
|
||||
// BuyerId = new Guid("ebcbcb4c-b032-4baa-834b-7fd66d37bc95").ToString(),
|
||||
// OrderDate = DateTime.Now,
|
||||
// State = OrderState.InProcess,
|
||||
// OrderItems = new List<OrderItem>()
|
||||
// {
|
||||
// new OrderItem() { UnitPrice = 12.40m, PictureUrl = "https://fakeimg.pl/370x240/EEEEEE/000/?text=RoslynRedT-Shirt", Quantity = 1, ProductName="Roslyn Red T-Shirt" }
|
||||
// }
|
||||
// },
|
||||
// new Order()
|
||||
// {
|
||||
// Id = Guid.NewGuid().ToString(),
|
||||
// BuyerId = new Guid("ebcbcb4c-b032-4baa-834b-7fd66d37bc95").ToString(),
|
||||
// OrderDate = DateTime.Now,
|
||||
// State = OrderState.InProcess,
|
||||
// OrderItems = new List<OrderItem>()
|
||||
// {
|
||||
// new OrderItem() { UnitPrice = 12.00m, PictureUrl = "https://fakeimg.pl/370x240/EEEEEE/000/?text=RoslynRedT-Shirt", Quantity = 1, ProductName="Roslyn Red T-Shirt" }
|
||||
// }
|
||||
// },
|
||||
// new Order()
|
||||
// {
|
||||
// Id = Guid.NewGuid().ToString(),
|
||||
// BuyerId = new Guid("ebcbcb4c-b032-4baa-834b-7fd66d37bc95").ToString(),
|
||||
// OrderDate = DateTime.Now,
|
||||
// State = OrderState.Delivered,
|
||||
// OrderItems = new List<OrderItem>()
|
||||
// {
|
||||
// new OrderItem() { UnitPrice = 12.05m, PictureUrl = "https://fakeimg.pl/370x240/EEEEEE/000/?text=RoslynRedT-Shirt", Quantity = 1, ProductName="Roslyn Red T-Shirt" }
|
||||
// }
|
||||
// }
|
||||
//};
|
||||
#endregion
|
||||
}
|
||||
|
||||
async public Task<Order> GetOrder(ApplicationUser user, string Id)
|
||||
@ -74,6 +35,7 @@ namespace Microsoft.eShopOnContainers.WebMVC.Services
|
||||
|
||||
var ordersUrl = $"{_remoteServiceBaseUrl}/{Id}";
|
||||
var dataString = await _apiClient.GetStringAsync(ordersUrl);
|
||||
|
||||
var response = JsonConvert.DeserializeObject<Order>(dataString);
|
||||
|
||||
return response;
|
||||
@ -96,50 +58,20 @@ namespace Microsoft.eShopOnContainers.WebMVC.Services
|
||||
|
||||
public Order MapUserInfoIntoOrder(ApplicationUser user, Order order)
|
||||
{
|
||||
order.ShippingAddress.City = user.City;
|
||||
order.ShippingAddress.Street = user.Street;
|
||||
order.ShippingAddress.State = user.State;
|
||||
order.ShippingAddress.Country = user.Country;
|
||||
order.City = user.City;
|
||||
order.Street = user.Street;
|
||||
order.State = user.State;
|
||||
order.Country = user.Country;
|
||||
|
||||
order.PaymentInfo.CardNumber = user.CardNumber;
|
||||
order.PaymentInfo.CardHolderName = user.CardHolderName;
|
||||
order.PaymentInfo.Expiration = user.Expiration;
|
||||
order.PaymentInfo.SecurityNumber = user.SecurityNumber;
|
||||
order.CardNumber = user.CardNumber;
|
||||
order.CardHolderName = user.CardHolderName;
|
||||
//order.CardExpiration = user.Expiration;
|
||||
order.CardSecurityNumber = user.SecurityNumber;
|
||||
|
||||
return order;
|
||||
}
|
||||
|
||||
public OrderRequest MapOrderIntoOrderRequest(Order order)
|
||||
{
|
||||
var od = new OrderRequest()
|
||||
{
|
||||
CardHolderName = order.PaymentInfo.CardHolderName,
|
||||
CardNumber = order.PaymentInfo.CardNumber,
|
||||
CardSecurityNumber = order.PaymentInfo.SecurityNumber,
|
||||
CardTypeId = (int)order.PaymentInfo.CardType,
|
||||
City = order.ShippingAddress.City,
|
||||
Country = order.ShippingAddress.Country,
|
||||
State = order.ShippingAddress.State,
|
||||
Street = order.ShippingAddress.Street,
|
||||
ZipCode = order.ShippingAddress.ZipCode,
|
||||
};
|
||||
|
||||
foreach (var item in order.OrderItems)
|
||||
{
|
||||
od.Items.Add(new OrderRequestItem()
|
||||
{
|
||||
Discount = item.Discount,
|
||||
ProductId = int.Parse(item.ProductId),
|
||||
ProductName = item.ProductName,
|
||||
UnitPrice = item.UnitPrice,
|
||||
Units = item.Quantity
|
||||
});
|
||||
}
|
||||
|
||||
return od;
|
||||
}
|
||||
|
||||
async public Task CreateOrder(ApplicationUser user, Order order)
|
||||
async public Task CreateOrder(Order order)
|
||||
{
|
||||
var context = _httpContextAccesor.HttpContext;
|
||||
var token = await context.Authentication.GetTokenAsync("access_token");
|
||||
@ -148,9 +80,9 @@ namespace Microsoft.eShopOnContainers.WebMVC.Services
|
||||
_apiClient.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token);
|
||||
|
||||
var ordersUrl = $"{_remoteServiceBaseUrl}/new";
|
||||
order.PaymentInfo.CardType = CardType.AMEX;
|
||||
OrderRequest request = MapOrderIntoOrderRequest(order);
|
||||
StringContent content = new StringContent(JsonConvert.SerializeObject(request), System.Text.Encoding.UTF8, "application/json");
|
||||
order.CardTypeId = 1;
|
||||
order.CardExpirationApiFormat();
|
||||
StringContent content = new StringContent(JsonConvert.SerializeObject(order), System.Text.Encoding.UTF8, "application/json");
|
||||
|
||||
var response = await _apiClient.PostAsync(ordersUrl, content);
|
||||
|
||||
@ -160,15 +92,15 @@ namespace Microsoft.eShopOnContainers.WebMVC.Services
|
||||
|
||||
public void OverrideUserInfoIntoOrder(Order original, Order destination)
|
||||
{
|
||||
destination.ShippingAddress.City = original.ShippingAddress.City;
|
||||
destination.ShippingAddress.Street = original.ShippingAddress.Street;
|
||||
destination.ShippingAddress.State = original.ShippingAddress.State;
|
||||
destination.ShippingAddress.Country = original.ShippingAddress.Country;
|
||||
destination.City = original.City;
|
||||
destination.Street = original.Street;
|
||||
destination.State = original.State;
|
||||
destination.Country = original.Country;
|
||||
|
||||
destination.PaymentInfo.CardNumber = original.PaymentInfo.CardNumber;
|
||||
destination.PaymentInfo.CardHolderName = original.PaymentInfo.CardHolderName;
|
||||
destination.PaymentInfo.Expiration = original.PaymentInfo.Expiration;
|
||||
destination.PaymentInfo.SecurityNumber = original.PaymentInfo.SecurityNumber;
|
||||
destination.CardNumber = original.CardNumber;
|
||||
destination.CardHolderName = original.CardHolderName;
|
||||
destination.CardExpiration = original.CardExpiration;
|
||||
destination.CardSecurityNumber = original.CardSecurityNumber;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
@using Microsoft.eShopOnContainers.WebMVC.Services
|
||||
@model Microsoft.eShopOnContainers.WebMVC.Models.OrderViewModels.CreateOrderViewModel
|
||||
@model Microsoft.eShopOnContainers.WebMVC.Models.Order
|
||||
@inject IIdentityParser<ApplicationUser> UserManager
|
||||
|
||||
@{
|
||||
@ -15,24 +15,24 @@
|
||||
<h4 class="order-create-section-title">SHIPPING ADDRESS</h4>
|
||||
<div class="form-horizontal row">
|
||||
<div class="form-group col-sm-6">
|
||||
<label asp-for="Order.ShippingAddress.Street" class="control-label form-label">Address</label>
|
||||
<input asp-for="Order.ShippingAddress.Street" class="form-control form-input" />
|
||||
<span asp-validation-for="Order.ShippingAddress.Street" class="text-danger" />
|
||||
<label asp-for="Street" class="control-label form-label">Address</label>
|
||||
<input asp-for="Street" class="form-control form-input" />
|
||||
<span asp-validation-for="Street" class="text-danger" />
|
||||
</div>
|
||||
<div class="form-group col-sm-6">
|
||||
<label asp-for="Order.ShippingAddress.City" class="control-label form-label">City</label>
|
||||
<input asp-for="Order.ShippingAddress.City" class="form-control form-input" />
|
||||
<span asp-validation-for="Order.ShippingAddress.City" class="text-danger" />
|
||||
<label asp-for="City" class="control-label form-label">City</label>
|
||||
<input asp-for="City" class="form-control form-input" />
|
||||
<span asp-validation-for="City" class="text-danger" />
|
||||
</div>
|
||||
<div class="form-group col-sm-6">
|
||||
<label asp-for="Order.ShippingAddress.State" class="control-label form-label">State</label>
|
||||
<input asp-for="Order.ShippingAddress.State" class="form-control form-input" />
|
||||
<span asp-validation-for="Order.ShippingAddress.State" class="text-danger" />
|
||||
<label asp-for="State" class="control-label form-label">State</label>
|
||||
<input asp-for="State" class="form-control form-input" />
|
||||
<span asp-validation-for="State" class="text-danger" />
|
||||
</div>
|
||||
<div class="form-group col-sm-6">
|
||||
<label asp-for="Order.ShippingAddress.Country" class="control-label form-label">Country</label>
|
||||
<input asp-for="Order.ShippingAddress.Country" class="form-control form-input" />
|
||||
<span asp-validation-for="Order.ShippingAddress.Country" class="text-danger" />
|
||||
<label asp-for="Country" class="control-label form-label">Country</label>
|
||||
<input asp-for="Country" class="form-control form-input" />
|
||||
<span asp-validation-for="Country" class="text-danger" />
|
||||
</div>
|
||||
</div>
|
||||
<br /><br />
|
||||
@ -40,33 +40,34 @@
|
||||
<h4 class="order-create-section-title">PAYMENT METHOD</h4>
|
||||
<div class="form-horizontal row">
|
||||
<div class="form-group col-sm-6">
|
||||
<label asp-for="Order.PaymentInfo.CardNumber" class="control-label form-label">Card Number</label>
|
||||
<input asp-for="Order.PaymentInfo.CardNumber" class="form-control form-input" />
|
||||
<span asp-validation-for="Order.PaymentInfo.CardNumber" class="text-danger" />
|
||||
<label asp-for="CardNumber" class="control-label form-label">Card Number</label>
|
||||
<input asp-for="CardNumber" class="form-control form-input" />
|
||||
<span asp-validation-for="CardNumber" class="text-danger" />
|
||||
</div>
|
||||
<div class="form-group col-sm-6">
|
||||
<label asp-for="Order.PaymentInfo.CardHolderName" class="control-label form-label">Cardholder Name</label>
|
||||
<input asp-for="Order.PaymentInfo.CardHolderName" class="form-control form-input" />
|
||||
<span asp-validation-for="Order.PaymentInfo.CardHolderName" class="text-danger" />
|
||||
<label asp-for="CardHolderName" class="control-label form-label">Cardholder Name</label>
|
||||
<input asp-for="CardHolderName" class="form-control form-input" />
|
||||
<span asp-validation-for="CardHolderName" class="text-danger" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-horizontal row">
|
||||
<div class="form-group col-sm-3">
|
||||
<label asp-for="Order.PaymentInfo.Expiration" class="control-label form-label">Expiration Date</label>
|
||||
<input asp-for="Order.PaymentInfo.Expiration" placeholder="MM/YY" class="form-control form-select" />
|
||||
<span asp-validation-for="Order.PaymentInfo.Expiration" class="text-danger" />
|
||||
<label asp-for="CardExpirationShort" class="control-label form-label">Expiration Date</label>
|
||||
<input asp-for="CardExpirationShort" placeholder="MM/YY" class="form-control form-select" />
|
||||
<span asp-validation-for="CardExpirationShort" class="text-danger" />
|
||||
</div>
|
||||
<div class="form-group col-sm-3">
|
||||
<label asp-for="Order.PaymentInfo.SecurityNumber" class="control-label form-label">Security Code</label>
|
||||
<input asp-for="Order.PaymentInfo.SecurityNumber" class="form-control form-input form-input-small" />
|
||||
<span asp-validation-for="Order.PaymentInfo.SecurityNumber" class="text-danger" />
|
||||
<label asp-for="CardSecurityNumber" class="control-label form-label">Security Code</label>
|
||||
<input asp-for="CardSecurityNumber" class="form-control form-input form-input-small" />
|
||||
<span asp-validation-for="CardSecurityNumber" class="text-danger" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<br /><br />
|
||||
<div class="col-md-12 order-create-section-items">
|
||||
<section>
|
||||
@await Component.InvokeAsync("CartList", new { user = UserManager.Parse(User) })
|
||||
@*@await Component.InvokeAsync("CartList", new { user = UserManager.Parse(User) })*@
|
||||
@await Html.PartialAsync("_OrderItems")
|
||||
</section>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
|
@ -1,5 +1,4 @@
|
||||
@model Microsoft.eShopOnContainers.WebMVC.Models.Order
|
||||
@inject UserManager<ApplicationUser> UserManager
|
||||
|
||||
@{
|
||||
ViewData["Title"] = "Order Detail";
|
||||
@ -7,7 +6,7 @@
|
||||
|
||||
<div class="brand-header-block">
|
||||
<ul class="container">
|
||||
<li class="brand-header-back"><a asp-area="" asp-controller="Catalog" asp-action="Index">Back to list</a></li>
|
||||
<li class="brand-header-back"><a asp-area="" asp-controller="Order" asp-action="Index">Back to list</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
@ -20,24 +19,24 @@
|
||||
</div>
|
||||
<div class="col-sm-3">
|
||||
<span>DATE</span><br />
|
||||
<span>@Model.OrderDate</span>
|
||||
<span>@Model.Date</span>
|
||||
</div>
|
||||
<div class="col-sm-3">
|
||||
<span>TOTAL</span><br />
|
||||
<span>$ @Model.Total()</span>
|
||||
<span>$ @Model.Total</span>
|
||||
</div>
|
||||
<div class="col-sm-3">
|
||||
<span>STATUS</span><br />
|
||||
<span>@Model.State</span>
|
||||
<span>@Model.Status</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row order-detail-section">
|
||||
<div class="col-sm-3">
|
||||
<span>SHIPING ADDRESS</span><br />
|
||||
<span>@Model.ShippingAddress.Street</span><br />
|
||||
<span>@Model.ShippingAddress.City</span><br />
|
||||
<span>@Model.Street</span><br />
|
||||
<span>@Model.City</span><br />
|
||||
@*<span>@Model.ShippingAddress.ZipCode</span><br />*@
|
||||
<span>@Model.ShippingAddress.Country</span><br />
|
||||
<span>@Model.Country</span><br />
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
@ -54,8 +53,8 @@
|
||||
<td class="cart-product-column">@item.ProductName</td>
|
||||
<td class="cart-product-column">ROSLYN</td>
|
||||
<td class="cart-product-column">$ @Math.Round(item.UnitPrice, 2)</td>
|
||||
<td class="cart-product-column">@item.Quantity</td>
|
||||
<td class="cart-product-column cart-total-value">$ @Math.Round(item.Quantity * item.UnitPrice,2)</td>
|
||||
<td class="cart-product-column">@item.Units</td>
|
||||
<td class="cart-product-column cart-total-value">$ @Math.Round(item.Units * item.UnitPrice,2)</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
@ -68,17 +67,9 @@
|
||||
</section>
|
||||
</div>
|
||||
<div class="col-md-offset-9 col-md-3">
|
||||
@*<input type="submit"
|
||||
class="btn btn-default cart-refresh-button"
|
||||
value=""
|
||||
name="action" />*@
|
||||
<div class="order-section-total">
|
||||
@*<div class="cart-subtotal-label"><span>SUBTOTAL</span></div>
|
||||
<div class="cart-subtotal-value"><span>$ @Model.Total()</span></div>
|
||||
<div class="cart-subtotal-label"><span>TAX</span></div>
|
||||
<div class="cart-subtotal-value"><span>$ 4.20</span></div>*@
|
||||
<div class="cart-total-label"><span>TOTAL</span></div>
|
||||
<div class="cart-total-value"><span>$ @Model.Total()</span></div>
|
||||
<div class="cart-total-value"><span>$ @Model.Total</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -34,16 +34,16 @@
|
||||
@Html.DisplayFor(modelItem => item.OrderNumber)
|
||||
</td>
|
||||
<td>
|
||||
@Html.DisplayFor(modelItem => item.OrderDate)
|
||||
@Html.DisplayFor(modelItem => item.Date)
|
||||
</td>
|
||||
<td>
|
||||
$ @item.Total()
|
||||
$ @Html.DisplayFor(modelItem => item.Total)
|
||||
</td>
|
||||
<td>
|
||||
@Html.DisplayFor(modelItem => item.State)
|
||||
@Html.DisplayFor(modelItem => item.Status)
|
||||
</td>
|
||||
<td class="order-detail-button">
|
||||
<a asp-controller="Order" asp-action="Detail" asp-route-orderId="@item.Id">Detail</a>
|
||||
<a asp-controller="Order" asp-action="Detail" asp-route-orderId="@item.OrderNumber">Detail</a>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
|
73
src/Web/WebMVC/Views/Order/_OrderItems.cshtml
Normal file
73
src/Web/WebMVC/Views/Order/_OrderItems.cshtml
Normal file
@ -0,0 +1,73 @@
|
||||
@model Microsoft.eShopOnContainers.WebMVC.Models.Order
|
||||
|
||||
<div class="col-md-12">
|
||||
<section>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="cart-product-column">
|
||||
PRODUCT
|
||||
</th>
|
||||
<th>
|
||||
|
||||
</th>
|
||||
<th>
|
||||
BRAND
|
||||
</th>
|
||||
<th>
|
||||
PRICE
|
||||
</th>
|
||||
<th>
|
||||
QUANTITY
|
||||
</th>
|
||||
<th>
|
||||
COST
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@for (int i = 0; i < Model.OrderItems.Count; i++)
|
||||
{
|
||||
var item = Model.OrderItems[i];
|
||||
|
||||
<tr>
|
||||
<td class="cart-product-column">
|
||||
<img class="cart-product-image" src="@item.PictureUrl" />
|
||||
<input type="hidden" value="@item.PictureUrl" name=@("orderitems[" + i + "].PictureUrl") />
|
||||
</td>
|
||||
<td class="cart-product-column">
|
||||
<span class="cart-product-column-name">@item.ProductName</span>
|
||||
<input type="hidden" value="@item.ProductName" name=@("orderitems[" + i + "].ProductName") />
|
||||
</td>
|
||||
<td class="cart-product-column">ROSLYN</td>
|
||||
<td class="cart-product-column">$ @item.UnitPrice
|
||||
<input type="hidden" value="@item.UnitPrice" name=@("orderitems[" + i + "].UnitPrice") />
|
||||
</td>
|
||||
<td class="cart-product-column">@item.Units
|
||||
<input type="hidden" value="@item.Units" name=@("orderitems[" + i + "].Units") />
|
||||
</td>
|
||||
<td class="cart-product-column cart-total-value">$ @Math.Round(item.Units * item.UnitPrice, 2)</td>
|
||||
</tr>
|
||||
}
|
||||
<tr class="cart-totals">
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td>
|
||||
<input type="submit"
|
||||
class="btn btn-default cart-refresh-button"
|
||||
value=""
|
||||
name="action" />
|
||||
</td>
|
||||
<td>
|
||||
<div class="cart-total-value">
|
||||
<div class="cart-total-label"><span>TOTAL</span></div>
|
||||
<span>$ @Model.Total</span>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</section>
|
||||
</div>
|
@ -68,15 +68,5 @@
|
||||
</table>
|
||||
</section>
|
||||
</div>
|
||||
@*<div class="col-md-offset-8 col-md-4">
|
||||
<div class="cart-section-total">
|
||||
<div class="cart-subtotal-label"><span>SUBTOTAL</span></div>
|
||||
<div class="cart-subtotal-value"><span>$ @Model.Total()</span></div>
|
||||
<div class="cart-subtotal-label"><span>TAX</span></div>
|
||||
<div class="cart-subtotal-value"><span>$ 4.20</span></div>
|
||||
<div class="cart-total-label"><span>TOTAL</span></div>
|
||||
<div class="cart-total-value"><span>$ @Model.Total()</span></div>
|
||||
</div>
|
||||
</div>*@
|
||||
|
||||
|
||||
|
@ -2,13 +2,15 @@
|
||||
ViewData["Title"] = "Error";
|
||||
}
|
||||
|
||||
<h1 class="text-danger">Error.</h1>
|
||||
<h2 class="text-danger">An error occurred while processing your request.</h2>
|
||||
<div class="container">
|
||||
<h1 class="text-danger">Error.</h1>
|
||||
<h2 class="text-danger">An error occurred while processing your request.</h2>
|
||||
|
||||
<h3>Development Mode</h3>
|
||||
<p>
|
||||
Swapping to <strong>Development</strong> environment will display more detailed information about the error that occurred.
|
||||
</p>
|
||||
<p>
|
||||
<strong>Development environment should not be enabled in deployed applications</strong>, as it can result in sensitive information from exceptions being displayed to end users. For local debugging, development environment can be enabled by setting the <strong>ASPNETCORE_ENVIRONMENT</strong> environment variable to <strong>Development</strong>, and restarting the application.
|
||||
</p>
|
||||
<h3>Development Mode</h3>
|
||||
<p>
|
||||
Swapping to <strong>Development</strong> environment will display more detailed information about the error that occurred.
|
||||
</p>
|
||||
<p>
|
||||
<strong>Development environment should not be enabled in deployed applications</strong>, as it can result in sensitive information from exceptions being displayed to end users. For local debugging, development environment can be enabled by setting the <strong>ASPNETCORE_ENVIRONMENT</strong> environment variable to <strong>Development</strong>, and restarting the application.
|
||||
</p>
|
||||
</div>
|
||||
|
@ -1,7 +1,5 @@
|
||||
@using Microsoft.eShopOnContainers.WebMVC
|
||||
@using Microsoft.eShopOnContainers.WebMVC.Models
|
||||
@using Microsoft.eShopOnContainers.WebMVC.Models.AccountViewModels
|
||||
@using Microsoft.eShopOnContainers.WebMVC.Models.ManageViewModels
|
||||
@using Microsoft.AspNetCore.Identity
|
||||
|
||||
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
|
||||
|
@ -2,7 +2,7 @@
|
||||
"CatalogUrl": "http://localhost:5101",
|
||||
"OrderingUrl": "http://localhost:5102",
|
||||
"BasketUrl": "http://localhost:5103",
|
||||
"IdentityUrl": "http://localhost:5105",
|
||||
"IdentityUrl": "http://identity.service:5105",
|
||||
"CallBackUrl": "http://localhost:5100/",
|
||||
"Logging": {
|
||||
"IncludeScopes": false,
|
||||
|
@ -699,7 +699,7 @@ form .col-md-4 {
|
||||
|
||||
.order-section-total {
|
||||
margin-bottom: 5px;
|
||||
margin-left: 40px;
|
||||
margin-left: 20px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user