Cleaning up separate WebRazorPages proj

This commit is contained in:
Steve Smith
2019-02-07 09:11:57 -05:00
parent 9bc226d257
commit e971dc2ea8
168 changed files with 0 additions and 31616 deletions

View File

@@ -1,3 +0,0 @@
@{
Layout = "/Pages/Shared/_Layout.cshtml";
}

View File

@@ -1,9 +0,0 @@
namespace Microsoft.eShopWeb.RazorPages
{
public static class Constants
{
public const string BASKET_COOKIENAME = "eShop";
public const int ITEMS_PER_PAGE = 10;
public const string DEFAULT_USERNAME = "Guest";
}
}

View File

@@ -1,31 +0,0 @@
using Microsoft.eShopWeb.ApplicationCore.Interfaces;
using Microsoft.eShopWeb.Infrastructure.Identity;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using System.Threading.Tasks;
namespace Microsoft.eShopWeb.RazorPages.Controllers
{
[Route("[controller]/[action]")]
public class AccountController : Controller
{
private readonly SignInManager<ApplicationUser> _signInManager;
private readonly IAppLogger<AccountController> _logger;
public AccountController(SignInManager<ApplicationUser> signInManager,
IAppLogger<AccountController> logger)
{
_signInManager = signInManager;
_logger = logger;
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Logout()
{
await _signInManager.SignOutAsync();
_logger.LogInformation("User logged out.");
return RedirectToPage("/Index");
}
}
}

View File

@@ -1,16 +0,0 @@
using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
namespace WebRazorPages.Data
{
public class ApplicationDbContext : IdentityDbContext
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
: base(options)
{
}
}
}

View File

@@ -1,236 +0,0 @@
// <auto-generated />
using System;
using WebRazorPages.Data;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
namespace WebRazorPages.Data.Migrations
{
[DbContext(typeof(ApplicationDbContext))]
[Migration("00000000000000_CreateIdentitySchema")]
partial class CreateIdentitySchema
{
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "2.2.0-preview1")
.HasAnnotation("Relational:MaxIdentifierLength", 128)
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b =>
{
b.Property<string>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken();
b.Property<string>("Name")
.HasMaxLength(256);
b.Property<string>("NormalizedName")
.HasMaxLength(256);
b.HasKey("Id");
b.HasIndex("NormalizedName")
.IsUnique()
.HasName("RoleNameIndex")
.HasFilter("[NormalizedName] IS NOT NULL");
b.ToTable("AspNetRoles");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
b.Property<string>("ClaimType");
b.Property<string>("ClaimValue");
b.Property<string>("RoleId")
.IsRequired();
b.HasKey("Id");
b.HasIndex("RoleId");
b.ToTable("AspNetRoleClaims");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUser", b =>
{
b.Property<string>("Id")
.ValueGeneratedOnAdd();
b.Property<int>("AccessFailedCount");
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken();
b.Property<string>("Email")
.HasMaxLength(256);
b.Property<bool>("EmailConfirmed");
b.Property<bool>("LockoutEnabled");
b.Property<DateTimeOffset?>("LockoutEnd");
b.Property<string>("NormalizedEmail")
.HasMaxLength(256);
b.Property<string>("NormalizedUserName")
.HasMaxLength(256);
b.Property<string>("PasswordHash");
b.Property<string>("PhoneNumber");
b.Property<bool>("PhoneNumberConfirmed");
b.Property<string>("SecurityStamp");
b.Property<bool>("TwoFactorEnabled");
b.Property<string>("UserName")
.HasMaxLength(256);
b.HasKey("Id");
b.HasIndex("NormalizedEmail")
.HasName("EmailIndex");
b.HasIndex("NormalizedUserName")
.IsUnique()
.HasName("UserNameIndex")
.HasFilter("[NormalizedUserName] IS NOT NULL");
b.ToTable("AspNetUsers");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
b.Property<string>("ClaimType");
b.Property<string>("ClaimValue");
b.Property<string>("UserId")
.IsRequired();
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("AspNetUserClaims");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
{
b.Property<string>("LoginProvider")
.HasMaxLength(128);
b.Property<string>("ProviderKey")
.HasMaxLength(128);
b.Property<string>("ProviderDisplayName");
b.Property<string>("UserId")
.IsRequired();
b.HasKey("LoginProvider", "ProviderKey");
b.HasIndex("UserId");
b.ToTable("AspNetUserLogins");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
{
b.Property<string>("UserId");
b.Property<string>("RoleId");
b.HasKey("UserId", "RoleId");
b.HasIndex("RoleId");
b.ToTable("AspNetUserRoles");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
{
b.Property<string>("UserId");
b.Property<string>("LoginProvider")
.HasMaxLength(128);
b.Property<string>("Name")
.HasMaxLength(128);
b.Property<string>("Value");
b.HasKey("UserId", "LoginProvider", "Name");
b.ToTable("AspNetUserTokens");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole")
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole")
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade);
});
#pragma warning restore 612, 618
}
}
}

View File

@@ -1,220 +0,0 @@
using System;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
namespace WebRazorPages.Data.Migrations
{
public partial class CreateIdentitySchema : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "AspNetRoles",
columns: table => new
{
Id = table.Column<string>(nullable: false),
Name = table.Column<string>(maxLength: 256, nullable: true),
NormalizedName = table.Column<string>(maxLength: 256, nullable: true),
ConcurrencyStamp = table.Column<string>(nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_AspNetRoles", x => x.Id);
});
migrationBuilder.CreateTable(
name: "AspNetUsers",
columns: table => new
{
Id = table.Column<string>(nullable: false),
UserName = table.Column<string>(maxLength: 256, nullable: true),
NormalizedUserName = table.Column<string>(maxLength: 256, nullable: true),
Email = table.Column<string>(maxLength: 256, nullable: true),
NormalizedEmail = table.Column<string>(maxLength: 256, nullable: true),
EmailConfirmed = table.Column<bool>(nullable: false),
PasswordHash = table.Column<string>(nullable: true),
SecurityStamp = table.Column<string>(nullable: true),
ConcurrencyStamp = table.Column<string>(nullable: true),
PhoneNumber = table.Column<string>(nullable: true),
PhoneNumberConfirmed = table.Column<bool>(nullable: false),
TwoFactorEnabled = table.Column<bool>(nullable: false),
LockoutEnd = table.Column<DateTimeOffset>(nullable: true),
LockoutEnabled = table.Column<bool>(nullable: false),
AccessFailedCount = table.Column<int>(nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_AspNetUsers", x => x.Id);
});
migrationBuilder.CreateTable(
name: "AspNetRoleClaims",
columns: table => new
{
Id = table.Column<int>(nullable: false)
.Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn),
RoleId = table.Column<string>(nullable: false),
ClaimType = table.Column<string>(nullable: true),
ClaimValue = table.Column<string>(nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_AspNetRoleClaims", x => x.Id);
table.ForeignKey(
name: "FK_AspNetRoleClaims_AspNetRoles_RoleId",
column: x => x.RoleId,
principalTable: "AspNetRoles",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "AspNetUserClaims",
columns: table => new
{
Id = table.Column<int>(nullable: false)
.Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn),
UserId = table.Column<string>(nullable: false),
ClaimType = table.Column<string>(nullable: true),
ClaimValue = table.Column<string>(nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_AspNetUserClaims", x => x.Id);
table.ForeignKey(
name: "FK_AspNetUserClaims_AspNetUsers_UserId",
column: x => x.UserId,
principalTable: "AspNetUsers",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "AspNetUserLogins",
columns: table => new
{
LoginProvider = table.Column<string>(maxLength: 128, nullable: false),
ProviderKey = table.Column<string>(maxLength: 128, nullable: false),
ProviderDisplayName = table.Column<string>(nullable: true),
UserId = table.Column<string>(nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_AspNetUserLogins", x => new { x.LoginProvider, x.ProviderKey });
table.ForeignKey(
name: "FK_AspNetUserLogins_AspNetUsers_UserId",
column: x => x.UserId,
principalTable: "AspNetUsers",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "AspNetUserRoles",
columns: table => new
{
UserId = table.Column<string>(nullable: false),
RoleId = table.Column<string>(nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_AspNetUserRoles", x => new { x.UserId, x.RoleId });
table.ForeignKey(
name: "FK_AspNetUserRoles_AspNetRoles_RoleId",
column: x => x.RoleId,
principalTable: "AspNetRoles",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "FK_AspNetUserRoles_AspNetUsers_UserId",
column: x => x.UserId,
principalTable: "AspNetUsers",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "AspNetUserTokens",
columns: table => new
{
UserId = table.Column<string>(nullable: false),
LoginProvider = table.Column<string>(maxLength: 128, nullable: false),
Name = table.Column<string>(maxLength: 128, nullable: false),
Value = table.Column<string>(nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_AspNetUserTokens", x => new { x.UserId, x.LoginProvider, x.Name });
table.ForeignKey(
name: "FK_AspNetUserTokens_AspNetUsers_UserId",
column: x => x.UserId,
principalTable: "AspNetUsers",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateIndex(
name: "IX_AspNetRoleClaims_RoleId",
table: "AspNetRoleClaims",
column: "RoleId");
migrationBuilder.CreateIndex(
name: "RoleNameIndex",
table: "AspNetRoles",
column: "NormalizedName",
unique: true,
filter: "[NormalizedName] IS NOT NULL");
migrationBuilder.CreateIndex(
name: "IX_AspNetUserClaims_UserId",
table: "AspNetUserClaims",
column: "UserId");
migrationBuilder.CreateIndex(
name: "IX_AspNetUserLogins_UserId",
table: "AspNetUserLogins",
column: "UserId");
migrationBuilder.CreateIndex(
name: "IX_AspNetUserRoles_RoleId",
table: "AspNetUserRoles",
column: "RoleId");
migrationBuilder.CreateIndex(
name: "EmailIndex",
table: "AspNetUsers",
column: "NormalizedEmail");
migrationBuilder.CreateIndex(
name: "UserNameIndex",
table: "AspNetUsers",
column: "NormalizedUserName",
unique: true,
filter: "[NormalizedUserName] IS NOT NULL");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "AspNetRoleClaims");
migrationBuilder.DropTable(
name: "AspNetUserClaims");
migrationBuilder.DropTable(
name: "AspNetUserLogins");
migrationBuilder.DropTable(
name: "AspNetUserRoles");
migrationBuilder.DropTable(
name: "AspNetUserTokens");
migrationBuilder.DropTable(
name: "AspNetRoles");
migrationBuilder.DropTable(
name: "AspNetUsers");
}
}
}

View File

@@ -1,234 +0,0 @@
// <auto-generated />
using System;
using WebRazorPages.Data;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
namespace WebRazorPages.Data.Migrations
{
[DbContext(typeof(ApplicationDbContext))]
partial class ApplicationDbContextModelSnapshot : ModelSnapshot
{
protected override void BuildModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "2.2.0-preview1")
.HasAnnotation("Relational:MaxIdentifierLength", 128)
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b =>
{
b.Property<string>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken();
b.Property<string>("Name")
.HasMaxLength(256);
b.Property<string>("NormalizedName")
.HasMaxLength(256);
b.HasKey("Id");
b.HasIndex("NormalizedName")
.IsUnique()
.HasName("RoleNameIndex")
.HasFilter("[NormalizedName] IS NOT NULL");
b.ToTable("AspNetRoles");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
b.Property<string>("ClaimType");
b.Property<string>("ClaimValue");
b.Property<string>("RoleId")
.IsRequired();
b.HasKey("Id");
b.HasIndex("RoleId");
b.ToTable("AspNetRoleClaims");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUser", b =>
{
b.Property<string>("Id")
.ValueGeneratedOnAdd();
b.Property<int>("AccessFailedCount");
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken();
b.Property<string>("Email")
.HasMaxLength(256);
b.Property<bool>("EmailConfirmed");
b.Property<bool>("LockoutEnabled");
b.Property<DateTimeOffset?>("LockoutEnd");
b.Property<string>("NormalizedEmail")
.HasMaxLength(256);
b.Property<string>("NormalizedUserName")
.HasMaxLength(256);
b.Property<string>("PasswordHash");
b.Property<string>("PhoneNumber");
b.Property<bool>("PhoneNumberConfirmed");
b.Property<string>("SecurityStamp");
b.Property<bool>("TwoFactorEnabled");
b.Property<string>("UserName")
.HasMaxLength(256);
b.HasKey("Id");
b.HasIndex("NormalizedEmail")
.HasName("EmailIndex");
b.HasIndex("NormalizedUserName")
.IsUnique()
.HasName("UserNameIndex")
.HasFilter("[NormalizedUserName] IS NOT NULL");
b.ToTable("AspNetUsers");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
b.Property<string>("ClaimType");
b.Property<string>("ClaimValue");
b.Property<string>("UserId")
.IsRequired();
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("AspNetUserClaims");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
{
b.Property<string>("LoginProvider")
.HasMaxLength(128);
b.Property<string>("ProviderKey")
.HasMaxLength(128);
b.Property<string>("ProviderDisplayName");
b.Property<string>("UserId")
.IsRequired();
b.HasKey("LoginProvider", "ProviderKey");
b.HasIndex("UserId");
b.ToTable("AspNetUserLogins");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
{
b.Property<string>("UserId");
b.Property<string>("RoleId");
b.HasKey("UserId", "RoleId");
b.HasIndex("RoleId");
b.ToTable("AspNetUserRoles");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
{
b.Property<string>("UserId");
b.Property<string>("LoginProvider")
.HasMaxLength(128);
b.Property<string>("Name")
.HasMaxLength(128);
b.Property<string>("Value");
b.HasKey("UserId", "LoginProvider", "Name");
b.ToTable("AspNetUserTokens");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole")
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole")
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade);
});
#pragma warning restore 612, 618
}
}
}

View File

@@ -1,27 +0,0 @@
# RUN BOTH CONTAINERS FROM ROOT (folder with .sln file):
# docker-compose build
# docker-compose up
#
# RUN JUST THIS CONTAINER FROM ROOT (folder with .sln file):
# docker build --pull -t webrazor -f src/WebRazorPages/Dockerfile .
#
# RUN COMMAND
# docker run --name eshopweb --rm -it -p 5107:5107 webrazor
FROM microsoft/dotnet:2.2-sdk AS build
WORKDIR /app
COPY *.sln .
COPY . .
WORKDIR /app/src/WebRazorPages
RUN dotnet restore
RUN dotnet publish -c Release -o out
FROM microsoft/dotnet:2.2-aspnetcore-runtime AS runtime
WORKDIR /app
COPY --from=build /app/src/WebRazorPages/out ./
# Optional: Set this here if not setting it from docker-compose.yml
# ENV ASPNETCORE_ENVIRONMENT Development
ENTRYPOINT ["dotnet", "Microsoft.eShopWeb.RazorPages.dll"]

View File

@@ -1,22 +0,0 @@
using Microsoft.eShopWeb.ApplicationCore.Interfaces;
using System.Text.Encodings.Web;
using System.Threading.Tasks;
namespace Microsoft.AspNetCore.Mvc
{
public static class EmailSenderExtensions
{
public static Task SendEmailConfirmationAsync(this IEmailSender emailSender, string email, string link)
{
return emailSender.SendEmailAsync(email, "Confirm your email",
$"Please confirm your account by <a href='{HtmlEncoder.Default.Encode(link)}'>clicking here</a>.");
}
public static Task SendResetPasswordAsync(this IEmailSender emailSender, string email, string callbackUrl)
{
return emailSender.SendEmailAsync(email, "Reset Password",
$"Please reset your password by <a href='{HtmlEncoder.Default.Encode(callbackUrl)}'>clicking here</a>.");
}
}
}

View File

@@ -1,33 +0,0 @@
namespace Microsoft.AspNetCore.Mvc
{
public static class UrlHelperExtensions
{
public static string GetLocalUrl(this IUrlHelper urlHelper, string localUrl)
{
if (!urlHelper.IsLocalUrl(localUrl))
{
return urlHelper.Page("/Index");
}
return localUrl;
}
public static string EmailConfirmationLink(this IUrlHelper urlHelper, string userId, string code, string scheme)
{
return urlHelper.Page(
"/Account/ConfirmEmail",
pageHandler: null,
values: new { userId, code },
protocol: scheme);
}
public static string ResetPasswordCallbackLink(this IUrlHelper urlHelper, string userId, string code, string scheme)
{
return urlHelper.Page(
"/Account/ResetPassword",
pageHandler: null,
values: new { userId, code },
protocol: scheme);
}
}
}

View File

@@ -1,10 +0,0 @@
using Microsoft.eShopWeb.RazorPages.ViewModels;
using System.Threading.Tasks;
namespace Microsoft.eShopWeb.RazorPages.Interfaces
{
public interface IBasketViewModelService
{
Task<BasketViewModel> GetOrCreateBasketForUser(string userName);
}
}

View File

@@ -1,14 +0,0 @@
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.eShopWeb.RazorPages.ViewModels;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace Microsoft.eShopWeb.RazorPages.Interfaces
{
public interface ICatalogService
{
Task<CatalogIndexViewModel> GetCatalogItems(int pageIndex, int itemsPage, int? brandId, int? typeId);
Task<IEnumerable<SelectListItem>> GetBrands();
Task<IEnumerable<SelectListItem>> GetTypes();
}
}

View File

@@ -1,13 +0,0 @@

@page
@model ConfirmEmailModel
@{
ViewData["Title"] = "Confirm email";
}
<h2>@ViewData["Title"]</h2>
<div>
<p>
Thank you for confirming your email.
</p>
</div>

View File

@@ -1,41 +0,0 @@
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.eShopWeb.Infrastructure.Identity;
namespace Microsoft.eShopWeb.RazorPages.Pages.Account
{
public class ConfirmEmailModel : PageModel
{
private readonly UserManager<ApplicationUser> _userManager;
public ConfirmEmailModel(UserManager<ApplicationUser> userManager)
{
_userManager = userManager;
}
public async Task<IActionResult> OnGetAsync(string userId, string code)
{
if (userId == null || code == null)
{
return RedirectToPage("/Index");
}
var user = await _userManager.FindByIdAsync(userId);
if (user == null)
{
throw new ApplicationException($"Unable to load user with ID '{userId}'.");
}
var result = await _userManager.ConfirmEmailAsync(user, code);
if (!result.Succeeded)
{
throw new ApplicationException($"Error confirming email for user with ID '{userId}':");
}
return Page();
}
}
}

View File

@@ -1,41 +0,0 @@
@page
@model LoginWith2faModel
@{
ViewData["Title"] = "Two-factor authentication";
}
<h2>@ViewData["Title"]</h2>
<hr />
<p>Your login is protected with an authenticator app. Enter your authenticator code below.</p>
<div class="row">
<div class="col-md-4">
<form method="post" asp-route-returnUrl="@Model.ReturnUrl">
<input asp-for="RememberMe" type="hidden" />
<div asp-validation-summary="All" class="text-danger"></div>
<div class="form-group">
<label asp-for="Input.TwoFactorCode"></label>
<input asp-for="Input.TwoFactorCode" class="form-control" autocomplete="off" />
<span asp-validation-for="Input.TwoFactorCode" class="text-danger"></span>
</div>
<div class="form-group">
<div class="checkbox">
<label asp-for="Input.RememberMachine">
<input asp-for="Input.RememberMachine" />
@Html.DisplayNameFor(m => m.Input.RememberMachine)
</label>
</div>
</div>
<div class="form-group">
<button type="submit" class="btn btn-default">Log in</button>
</div>
</form>
</div>
</div>
<p>
Don't have access to your authenticator device? You can
<a asp-page="./LoginWithRecoveryCode" asp-route-returnUrl="@Model.ReturnUrl">log in with a recovery code</a>.
</p>
@section Scripts {
<partial name="_ValidationScriptsPartial" />
}

View File

@@ -1,94 +0,0 @@
using Microsoft.eShopWeb.ApplicationCore.Interfaces;
using Microsoft.eShopWeb.Infrastructure.Identity;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using System;
using System.ComponentModel.DataAnnotations;
using System.Threading.Tasks;
namespace Microsoft.eShopWeb.RazorPages.Pages.Account
{
public class LoginWith2faModel : PageModel
{
private readonly SignInManager<ApplicationUser> _signInManager;
private readonly IAppLogger<LoginWith2faModel> _logger;
public LoginWith2faModel(SignInManager<ApplicationUser> signInManager,
IAppLogger<LoginWith2faModel> logger)
{
_signInManager = signInManager;
_logger = logger;
}
[BindProperty]
public InputModel Input { get; set; }
public bool RememberMe { get; set; }
public string ReturnUrl { get; set; }
public class InputModel
{
[Required]
[StringLength(7, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)]
[DataType(DataType.Text)]
[Display(Name = "Authenticator code")]
public string TwoFactorCode { get; set; }
[Display(Name = "Remember this machine")]
public bool RememberMachine { get; set; }
}
public async Task<IActionResult> OnGetAsync(bool rememberMe, string returnUrl = null)
{
// Ensure the user has gone through the username & password screen first
var user = await _signInManager.GetTwoFactorAuthenticationUserAsync();
if (user == null)
{
throw new ApplicationException($"Unable to load two-factor authentication user.");
}
ReturnUrl = returnUrl;
RememberMe = rememberMe;
return Page();
}
public async Task<IActionResult> OnPostAsync(bool rememberMe, string returnUrl = null)
{
if (!ModelState.IsValid)
{
return Page();
}
var user = await _signInManager.GetTwoFactorAuthenticationUserAsync();
if (user == null)
{
throw new ApplicationException($"Unable to load two-factor authentication user.");
}
var authenticatorCode = Input.TwoFactorCode.Replace(" ", string.Empty).Replace("-", string.Empty);
var result = await _signInManager.TwoFactorAuthenticatorSignInAsync(authenticatorCode, rememberMe, Input.RememberMachine);
if (result.Succeeded)
{
_logger.LogInformation("User with ID '{UserId}' logged in with 2fa.", user.Id);
return LocalRedirect(Url.GetLocalUrl(returnUrl));
}
else if (result.IsLockedOut)
{
_logger.LogWarning("User with ID '{UserId}' account locked out.", user.Id);
return RedirectToPage("./Lockout");
}
else
{
_logger.LogWarning("Invalid authenticator code entered for user with ID '{UserId}'.", user.Id);
ModelState.AddModelError(string.Empty, "Invalid authenticator code.");
return Page();
}
}
}
}

View File

@@ -1,29 +0,0 @@
@page
@model LoginWithRecoveryCodeModel
@{
ViewData["Title"] = "Recovery code verification";
}
<h2>@ViewData["Title"]</h2>
<hr />
<p>
You have requested to log in with a recovery code. This login will not be remembered until you provide
an authenticator app code at log in or disable 2FA and log in again.
</p>
<div class="row">
<div class="col-md-4">
<form method="post">
<div asp-validation-summary="All" class="text-danger"></div>
<div class="form-group">
<label asp-for="Input.RecoveryCode"></label>
<input asp-for="Input.RecoveryCode" class="form-control" autocomplete="off" />
<span asp-validation-for="Input.RecoveryCode" class="text-danger"></span>
</div>
<button type="submit" class="btn btn-default">Log in</button>
</form>
</div>
</div>
@section Scripts {
<partial name="_ValidationScriptsPartial" />
}

View File

@@ -1,87 +0,0 @@
using Microsoft.eShopWeb.ApplicationCore.Interfaces;
using Microsoft.eShopWeb.Infrastructure.Identity;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using System;
using System.ComponentModel.DataAnnotations;
using System.Threading.Tasks;
namespace Microsoft.eShopWeb.RazorPages.Pages.Account
{
public class LoginWithRecoveryCodeModel : PageModel
{
private readonly SignInManager<ApplicationUser> _signInManager;
private readonly IAppLogger<LoginWithRecoveryCodeModel> _logger;
public LoginWithRecoveryCodeModel(SignInManager<ApplicationUser> signInManager,
IAppLogger<LoginWithRecoveryCodeModel> logger)
{
_signInManager = signInManager;
_logger = logger;
}
[BindProperty]
public InputModel Input { get; set; }
public string ReturnUrl { get; set; }
public class InputModel
{
[BindProperty]
[Required]
[DataType(DataType.Text)]
[Display(Name = "Recovery Code")]
public string RecoveryCode { get; set; }
}
public async Task<IActionResult> OnGetAsync(string returnUrl = null)
{
// Ensure the user has gone through the username & password screen first
var user = await _signInManager.GetTwoFactorAuthenticationUserAsync();
if (user == null)
{
throw new ApplicationException($"Unable to load two-factor authentication user.");
}
ReturnUrl = returnUrl;
return Page();
}
public async Task<IActionResult> OnPostAsync(string returnUrl = null)
{
if (!ModelState.IsValid)
{
return Page();
}
var user = await _signInManager.GetTwoFactorAuthenticationUserAsync();
if (user == null)
{
throw new ApplicationException($"Unable to load two-factor authentication user.");
}
var recoveryCode = Input.RecoveryCode.Replace(" ", string.Empty);
var result = await _signInManager.TwoFactorRecoveryCodeSignInAsync(recoveryCode);
if (result.Succeeded)
{
_logger.LogInformation("User with ID '{UserId}' logged in with a recovery code.", user.Id);
return LocalRedirect(Url.GetLocalUrl(returnUrl));
}
if (result.IsLockedOut)
{
_logger.LogWarning("User with ID '{UserId}' account locked out.", user.Id);
return RedirectToPage("./Lockout");
}
else
{
_logger.LogWarning("Invalid recovery code entered for user with ID '{UserId}' ", user.Id);
ModelState.AddModelError(string.Empty, "Invalid recovery code entered.");
return Page();
}
}
}
}

View File

@@ -1,35 +0,0 @@
@page
@model ChangePasswordModel
@{
ViewData["Title"] = "Change password";
}
<h4>@ViewData["Title"]</h4>
<partial name="_StatusMessage" for="StatusMessage" />
<div class="row">
<div class="col-md-6">
<form method="post">
<div asp-validation-summary="All" class="text-danger"></div>
<div class="form-group">
<label asp-for="Input.OldPassword"></label>
<input asp-for="Input.OldPassword" class="form-control" />
<span asp-validation-for="Input.OldPassword" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Input.NewPassword"></label>
<input asp-for="Input.NewPassword" class="form-control" />
<span asp-validation-for="Input.NewPassword" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Input.ConfirmPassword"></label>
<input asp-for="Input.ConfirmPassword" class="form-control" />
<span asp-validation-for="Input.ConfirmPassword" class="text-danger"></span>
</div>
<button type="submit" class="btn btn-default">Update password</button>
</form>
</div>
</div>
@section Scripts {
<partial name="_ValidationScriptsPartial" />
}

View File

@@ -1,100 +0,0 @@
using Microsoft.eShopWeb.Infrastructure.Identity;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Extensions.Logging;
using System;
using System.ComponentModel.DataAnnotations;
using System.Threading.Tasks;
namespace Microsoft.eShopWeb.RazorPages.Pages.Account.Manage
{
public class ChangePasswordModel : PageModel
{
private readonly UserManager<ApplicationUser> _userManager;
private readonly SignInManager<ApplicationUser> _signInManager;
private readonly ILogger<ChangePasswordModel> _logger;
public ChangePasswordModel(
UserManager<ApplicationUser> userManager,
SignInManager<ApplicationUser> signInManager,
ILogger<ChangePasswordModel> logger)
{
_userManager = userManager;
_signInManager = signInManager;
_logger = logger;
}
[BindProperty]
public InputModel Input { get; set; }
[TempData]
public string StatusMessage { get; set; }
public class InputModel
{
[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; }
}
public async Task<IActionResult> OnGetAsync()
{
var user = await _userManager.GetUserAsync(User);
if (user == null)
{
throw new ApplicationException($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
}
var hasPassword = await _userManager.HasPasswordAsync(user);
if (!hasPassword)
{
return RedirectToPage("./SetPassword");
}
return Page();
}
public async Task<IActionResult> OnPostAsync()
{
if (!ModelState.IsValid)
{
return Page();
}
var user = await _userManager.GetUserAsync(User);
if (user == null)
{
throw new ApplicationException($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
}
var changePasswordResult = await _userManager.ChangePasswordAsync(user, Input.OldPassword, Input.NewPassword);
if (!changePasswordResult.Succeeded)
{
foreach (var error in changePasswordResult.Errors)
{
ModelState.AddModelError(string.Empty, error.Description);
}
return Page();
}
await _signInManager.SignInAsync(user, isPersistent: false);
_logger.LogInformation("User changed their password successfully.");
StatusMessage = "Your password has been changed.";
return RedirectToPage();
}
}
}

View File

@@ -1,25 +0,0 @@
@page
@model Disable2faModel
@{
ViewData["Title"] = "Disable two-factor authentication (2FA)";
ViewData["ActivePage"] = "TwoFactorAuthentication";
}
<h2>@ViewData["Title"]</h2>
<div class="alert alert-warning" role="alert">
<p>
<span class="glyphicon glyphicon-warning-sign"></span>
<strong>This action only disables 2FA.</strong>
</p>
<p>
Disabling 2FA does not change the keys used in authenticator apps. If you wish to change the key
used in an authenticator app you should <a asp-page="./ResetAuthenticator">reset your authenticator keys.</a>
</p>
</div>
<div>
<form method="post" class="form-group">
<button class="btn btn-danger" type="submit">Disable 2FA</button>
</form>
</div>

View File

@@ -1,59 +0,0 @@
using Microsoft.eShopWeb.Infrastructure.Identity;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Extensions.Logging;
using System;
using System.Threading.Tasks;
namespace Microsoft.eShopWeb.RazorPages.Pages.Account.Manage
{
public class Disable2faModel : PageModel
{
private readonly UserManager<ApplicationUser> _userManager;
private readonly ILogger<Disable2faModel> _logger;
public Disable2faModel(
UserManager<ApplicationUser> userManager,
ILogger<Disable2faModel> logger)
{
_userManager = userManager;
_logger = logger;
}
public async Task<IActionResult> OnGet()
{
var user = await _userManager.GetUserAsync(User);
if (user == null)
{
throw new ApplicationException($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
}
if (!await _userManager.GetTwoFactorEnabledAsync(user))
{
throw new ApplicationException($"Cannot disable 2FA for user with ID '{_userManager.GetUserId(User)}' as it's not currently enabled.");
}
return Page();
}
public async Task<IActionResult> OnPostAsync()
{
var user = await _userManager.GetUserAsync(User);
if (user == null)
{
throw new ApplicationException($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
}
var disable2faResult = await _userManager.SetTwoFactorEnabledAsync(user, false);
if (!disable2faResult.Succeeded)
{
throw new ApplicationException($"Unexpected error occurred disabling 2FA for user with ID '{_userManager.GetUserId(User)}'.");
}
_logger.LogInformation("User with ID '{UserId}' has disabled 2fa.", _userManager.GetUserId(User));
return RedirectToPage("./TwoFactorAuthentication");
}
}
}

View File

@@ -1,53 +0,0 @@
@page
@model EnableAuthenticatorModel
@{
ViewData["Title"] = "Configure authenticator app";
ViewData["ActivePage"] = "TwoFactorAuthentication";
}
<h4>@ViewData["Title"]</h4>
<div>
<p>To use an authenticator app go through the following steps:</p>
<ol class="list">
<li>
<p>
Download a two-factor authenticator app like Microsoft Authenticator for
<a href="https://go.microsoft.com/fwlink/?Linkid=825071">Windows Phone</a>,
<a href="https://go.microsoft.com/fwlink/?Linkid=825072">Android</a> and
<a href="https://go.microsoft.com/fwlink/?Linkid=825073">iOS</a> or
Google Authenticator for
<a href="https://play.google.com/store/apps/details?id=com.google.android.apps.authenticator2&amp;hl=en">Android</a> and
<a href="https://itunes.apple.com/us/app/google-authenticator/id388497605?mt=8">iOS</a>.
</p>
</li>
<li>
<p>Scan the QR Code or enter this key <kbd>@Model.SharedKey</kbd> into your two factor authenticator app. Spaces and casing do not matter.</p>
<div class="alert alert-info">To enable QR code generation please read our <a href="https://go.microsoft.com/fwlink/?Linkid=852423">documentation</a>.</div>
<div id="qrCode"></div>
<div id="qrCodeData" data-url="@Html.Raw(Model.AuthenticatorUri)"></div>
</li>
<li>
<p>
Once you have scanned the QR code or input the key above, your two factor authentication app will provide you
with a unique code. Enter the code in the confirmation box below.
</p>
<div class="row">
<div class="col-md-6">
<form method="post">
<div class="form-group">
<label asp-for="Input.Code" class="control-label">Verification Code</label>
<input asp-for="Input.Code" class="form-control" autocomplete="off" />
<span asp-validation-for="Input.Code" class="text-danger"></span>
</div>
<button type="submit" class="btn btn-default">Verify</button>
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
</form>
</div>
</div>
</li>
</ol>
</div>
@section Scripts {
<partial name="_ValidationScriptsPartial" />
}

View File

@@ -1,135 +0,0 @@
using Microsoft.eShopWeb.Infrastructure.Identity;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Extensions.Logging;
using System;
using System.ComponentModel.DataAnnotations;
using System.Text;
using System.Text.Encodings.Web;
using System.Threading.Tasks;
namespace Microsoft.eShopWeb.RazorPages.Pages.Account.Manage
{
public class EnableAuthenticatorModel : PageModel
{
private readonly UserManager<ApplicationUser> _userManager;
private readonly ILogger<EnableAuthenticatorModel> _logger;
private readonly UrlEncoder _urlEncoder;
private const string AuthenicatorUriFormat = "otpauth://totp/{0}:{1}?secret={2}&issuer={0}&digits=6";
public EnableAuthenticatorModel(
UserManager<ApplicationUser> userManager,
ILogger<EnableAuthenticatorModel> logger,
UrlEncoder urlEncoder)
{
_userManager = userManager;
_logger = logger;
_urlEncoder = urlEncoder;
}
public string SharedKey { get; set; }
public string AuthenticatorUri { get; set; }
[BindProperty]
public InputModel Input { get; set; }
public class InputModel
{
[Required]
[StringLength(7, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)]
[DataType(DataType.Text)]
[Display(Name = "Verification Code")]
public string Code { get; set; }
}
public async Task<IActionResult> OnGetAsync()
{
var user = await _userManager.GetUserAsync(User);
if (user == null)
{
throw new ApplicationException($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
}
await LoadSharedKeyAndQrCodeUriAsync(user);
if (string.IsNullOrEmpty(SharedKey))
{
await _userManager.ResetAuthenticatorKeyAsync(user);
await LoadSharedKeyAndQrCodeUriAsync(user);
}
return Page();
}
public async Task<IActionResult> OnPostAsync()
{
var user = await _userManager.GetUserAsync(User);
if (user == null)
{
throw new ApplicationException($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
}
if (!ModelState.IsValid)
{
await LoadSharedKeyAndQrCodeUriAsync(user);
return Page();
}
// Strip spaces and hypens
var verificationCode = Input.Code.Replace(" ", string.Empty).Replace("-", string.Empty);
var is2faTokenValid = await _userManager.VerifyTwoFactorTokenAsync(
user, _userManager.Options.Tokens.AuthenticatorTokenProvider, verificationCode);
if (!is2faTokenValid)
{
ModelState.AddModelError("Input.Code", "Verification code is invalid.");
await LoadSharedKeyAndQrCodeUriAsync(user);
return Page();
}
await _userManager.SetTwoFactorEnabledAsync(user, true);
_logger.LogInformation("User with ID '{UserId}' has enabled 2FA with an authenticator app.", user.Id);
return RedirectToPage("./GenerateRecoveryCodes");
}
private async Task LoadSharedKeyAndQrCodeUriAsync(ApplicationUser user)
{
// Load the authenticator key & QR code URI to display on the form
var unformattedKey = await _userManager.GetAuthenticatorKeyAsync(user);
if (!string.IsNullOrEmpty(unformattedKey))
{
SharedKey = FormatKey(unformattedKey);
AuthenticatorUri = GenerateQrCodeUri(user.Email, unformattedKey);
}
}
private string FormatKey(string unformattedKey)
{
var result = new StringBuilder();
int currentPosition = 0;
while (currentPosition + 4 < unformattedKey.Length)
{
result.Append(unformattedKey.Substring(currentPosition, 4)).Append(" ");
currentPosition += 4;
}
if (currentPosition < unformattedKey.Length)
{
result.Append(unformattedKey.Substring(currentPosition));
}
return result.ToString().ToLowerInvariant();
}
private string GenerateQrCodeUri(string email, string unformattedKey)
{
return string.Format(
AuthenicatorUriFormat,
_urlEncoder.Encode("RazorPagesAuthSample2"),
_urlEncoder.Encode(email),
unformattedKey);
}
}
}

View File

@@ -1,25 +0,0 @@
@page
@model GenerateRecoveryCodesModel
@{
ViewData["Title"] = "Recovery codes";
ViewData["ActivePage"] = "TwoFactorAuthentication";
}
<h4>@ViewData["Title"]</h4>
<div class="alert alert-warning" role="alert">
<p>
<span class="glyphicon glyphicon-warning-sign"></span>
<strong>Put these codes in a safe place.</strong>
</p>
<p>
If you lose your device and don't have the recovery codes you will lose access to your account.
</p>
</div>
<div class="row">
<div class="col-md-12">
@for (var row = 0; row < Model.RecoveryCodes.Count(); row += 2)
{
<code>@Model.RecoveryCodes[row]</code><text>&nbsp;</text><code>@Model.RecoveryCodes[row + 1]</code><br />
}
</div>
</div>

View File

@@ -1,48 +0,0 @@
using Microsoft.eShopWeb.Infrastructure.Identity;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Extensions.Logging;
using System;
using System.Linq;
using System.Threading.Tasks;
namespace Microsoft.eShopWeb.RazorPages.Pages.Account.Manage
{
public class GenerateRecoveryCodesModel : PageModel
{
private readonly UserManager<ApplicationUser> _userManager;
private readonly ILogger<GenerateRecoveryCodesModel> _logger;
public GenerateRecoveryCodesModel(
UserManager<ApplicationUser> userManager,
ILogger<GenerateRecoveryCodesModel> logger)
{
_userManager = userManager;
_logger = logger;
}
public string[] RecoveryCodes { get; set; }
public async Task<IActionResult> OnGetAsync()
{
var user = await _userManager.GetUserAsync(User);
if (user == null)
{
throw new ApplicationException($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
}
if (!user.TwoFactorEnabled)
{
throw new ApplicationException($"Cannot generate recovery codes for user with ID '{user.Id}' as they do not have 2FA enabled.");
}
var recoveryCodes = await _userManager.GenerateNewTwoFactorRecoveryCodesAsync(user, 10);
RecoveryCodes = recoveryCodes.ToArray();
_logger.LogInformation("User with ID '{UserId}' has generated new 2FA recovery codes.", user.Id);
return Page();
}
}
}

View File

@@ -1,45 +0,0 @@
@page
@model IndexModel
@{
ViewData["Title"] = "Profile";
}
<h4>@ViewData["Title"]</h4>
<partial name="_StatusMessage" for="StatusMessage" />
<div class="row">
<div class="col-md-6">
<form method="post">
<div asp-validation-summary="All" class="text-danger"></div>
<div class="form-group">
<label asp-for="Username"></label>
<input asp-for="Username" class="form-control" disabled />
</div>
<div class="form-group">
<label asp-for="Input.Email"></label>
@if (Model.IsEmailConfirmed)
{
<div class="input-group">
<input asp-for="Input.Email" class="form-control" />
<span class="input-group-addon" aria-hidden="true"><span class="glyphicon glyphicon-ok text-success"></span></span>
</div>
}
else
{
<input asp-for="Input.Email" class="form-control" />
<button asp-page-handler="SendVerificationEmail" class="btn btn-link">Send verification email</button>
}
<span asp-validation-for="Input.Email" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Input.PhoneNumber"></label>
<input asp-for="Input.PhoneNumber" class="form-control" />
<span asp-validation-for="Input.PhoneNumber" class="text-danger"></span>
</div>
<button type="submit" class="btn btn-default">Save</button>
</form>
</div>
</div>
@section Scripts {
<partial name="_ValidationScriptsPartial" />
}

View File

@@ -1,125 +0,0 @@
using Microsoft.eShopWeb.ApplicationCore.Interfaces;
using Microsoft.eShopWeb.Infrastructure.Identity;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using System;
using System.ComponentModel.DataAnnotations;
using System.Threading.Tasks;
namespace Microsoft.eShopWeb.RazorPages.Pages.Account.Manage
{
public partial class IndexModel : PageModel
{
private readonly UserManager<ApplicationUser> _userManager;
private readonly SignInManager<ApplicationUser> _signInManager;
private readonly IEmailSender _emailSender;
public IndexModel(
UserManager<ApplicationUser> userManager,
SignInManager<ApplicationUser> signInManager,
IEmailSender emailSender)
{
_userManager = userManager;
_signInManager = signInManager;
_emailSender = emailSender;
}
public string Username { get; set; }
public bool IsEmailConfirmed { get; set; }
[TempData]
public string StatusMessage { get; set; }
[BindProperty]
public InputModel Input { get; set; }
public class InputModel
{
[Required]
[EmailAddress]
public string Email { get; set; }
[Phone]
[Display(Name = "Phone number")]
public string PhoneNumber { get; set; }
}
public async Task<IActionResult> OnGetAsync()
{
var user = await _userManager.GetUserAsync(User);
if (user == null)
{
throw new ApplicationException($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
}
Username = user.UserName;
Input = new InputModel
{
Email = user.Email,
PhoneNumber = user.PhoneNumber
};
IsEmailConfirmed = await _userManager.IsEmailConfirmedAsync(user);
return Page();
}
public async Task<IActionResult> OnPostAsync()
{
if (!ModelState.IsValid)
{
return Page();
}
var user = await _userManager.GetUserAsync(User);
if (user == null)
{
throw new ApplicationException($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
}
if (Input.Email != user.Email)
{
var setEmailResult = await _userManager.SetEmailAsync(user, Input.Email);
if (!setEmailResult.Succeeded)
{
throw new ApplicationException($"Unexpected error occurred setting email for user with ID '{user.Id}'.");
}
}
if (Input.PhoneNumber != user.PhoneNumber)
{
var setPhoneResult = await _userManager.SetPhoneNumberAsync(user, Input.PhoneNumber);
if (!setPhoneResult.Succeeded)
{
throw new ApplicationException($"Unexpected error occurred setting phone number for user with ID '{user.Id}'.");
}
}
StatusMessage = "Your profile has been updated";
return RedirectToPage();
}
public async Task<IActionResult> OnPostSendVerificationEmailAsync()
{
if (!ModelState.IsValid)
{
return Page();
}
var user = await _userManager.GetUserAsync(User);
if (user == null)
{
throw new ApplicationException($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
}
var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
var callbackUrl = Url.EmailConfirmationLink(user.Id, code, Request.Scheme);
await _emailSender.SendEmailConfirmationAsync(user.Email, callbackUrl);
StatusMessage = "Verification email sent. Please check your email.";
return RedirectToPage();
}
}
}

View File

@@ -1,31 +0,0 @@
using Microsoft.AspNetCore.Mvc.Rendering;
using System;
namespace Microsoft.eShopWeb.RazorPages.Pages.Account.Manage
{
public static class ManageNavPages
{
public static string Index => "Index";
public static string ChangePassword => "ChangePassword";
public static string ExternalLogins => "ExternalLogins";
public static string TwoFactorAuthentication => "TwoFactorAuthentication";
public static string IndexNavClass(ViewContext viewContext) => PageNavClass(viewContext, Index);
public static string ChangePasswordNavClass(ViewContext viewContext) => PageNavClass(viewContext, ChangePassword);
public static string ExternalLoginsNavClass(ViewContext viewContext) => PageNavClass(viewContext, ExternalLogins);
public static string TwoFactorAuthenticationNavClass(ViewContext viewContext) => PageNavClass(viewContext, TwoFactorAuthentication);
public static string PageNavClass(ViewContext viewContext, string page)
{
var activePage = viewContext.ViewData["ActivePage"] as string
?? System.IO.Path.GetFileNameWithoutExtension(viewContext.ActionDescriptor.DisplayName);
return string.Equals(activePage, page, StringComparison.OrdinalIgnoreCase) ? "active" : null;
}
}
}

View File

@@ -1,23 +0,0 @@
@page
@model ResetAuthenticatorModel
@{
ViewData["Title"] = "Reset authenticator key";
ViewData["ActivePage"] = "TwoFactorAuthentication";
}
<h4>@ViewData["Title"]</h4>
<div class="alert alert-warning" role="alert">
<p>
<span class="glyphicon glyphicon-warning-sign"></span>
<strong>If you reset your authenticator key your authenticator app will not work until you reconfigure it.</strong>
</p>
<p>
This process disables 2FA until you verify your authenticator app and will also reset your 2FA recovery codes.
If you do not complete your authenticator app configuration you may lose access to your account.
</p>
</div>
<div>
<form method="post" class="form-group">
<button class="btn btn-danger" type="submit">Reset authenticator key</button>
</form>
</div>

View File

@@ -1,49 +0,0 @@
using Microsoft.eShopWeb.Infrastructure.Identity;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Extensions.Logging;
using System;
using System.Threading.Tasks;
namespace Microsoft.eShopWeb.RazorPages.Pages.Account.Manage
{
public class ResetAuthenticatorModel : PageModel
{
UserManager<ApplicationUser> _userManager;
ILogger<ResetAuthenticatorModel> _logger;
public ResetAuthenticatorModel(
UserManager<ApplicationUser> userManager,
ILogger<ResetAuthenticatorModel> logger)
{
_userManager = userManager;
_logger = logger;
}
public async Task<IActionResult> OnGet()
{
var user = await _userManager.GetUserAsync(User);
if (user == null)
{
throw new ApplicationException($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
}
return Page();
}
public async Task<IActionResult> OnPostAsync()
{
var user = await _userManager.GetUserAsync(User);
if (user == null)
{
throw new ApplicationException($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
}
await _userManager.SetTwoFactorEnabledAsync(user, false);
await _userManager.ResetAuthenticatorKeyAsync(user);
_logger.LogInformation("User with ID '{UserId}' has reset their authentication app key.", user.Id);
return RedirectToPage("./EnableAuthenticator");
}
}
}

View File

@@ -1,35 +0,0 @@
@page
@model SetPasswordModel
@{
ViewData["Title"] = "Set password";
ViewData["ActivePage"] = "ChangePassword";
}
<h4>Set your password</h4>
<partial name="_StatusMessage" for="StatusMessage" />
<p class="text-info">
You do not have a local username/password for this site. Add a local
account so you can log in without an external login.
</p>
<div class="row">
<div class="col-md-6">
<form method="post">
<div asp-validation-summary="All" class="text-danger"></div>
<div class="form-group">
<label asp-for="Input.NewPassword"></label>
<input asp-for="Input.NewPassword" class="form-control" />
<span asp-validation-for="Input.NewPassword" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Input.ConfirmPassword"></label>
<input asp-for="Input.ConfirmPassword" class="form-control" />
<span asp-validation-for="Input.ConfirmPassword" class="text-danger"></span>
</div>
<button type="submit" class="btn btn-default">Set password</button>
</form>
</div>
</div>
@section Scripts {
<partial name="_ValidationScriptsPartial" />
}

View File

@@ -1,91 +0,0 @@
using Microsoft.eShopWeb.Infrastructure.Identity;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using System;
using System.ComponentModel.DataAnnotations;
using System.Threading.Tasks;
namespace Microsoft.eShopWeb.RazorPages.Pages.Account.Manage
{
public class SetPasswordModel : PageModel
{
private readonly UserManager<ApplicationUser> _userManager;
private readonly SignInManager<ApplicationUser> _signInManager;
public SetPasswordModel(
UserManager<ApplicationUser> userManager,
SignInManager<ApplicationUser> signInManager)
{
_userManager = userManager;
_signInManager = signInManager;
}
[BindProperty]
public InputModel Input { get; set; }
[TempData]
public string StatusMessage { get; set; }
public class InputModel
{
[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; }
}
public async Task<IActionResult> OnGetAsync()
{
var user = await _userManager.GetUserAsync(User);
if (user == null)
{
throw new ApplicationException($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
}
var hasPassword = await _userManager.HasPasswordAsync(user);
if (hasPassword)
{
return RedirectToPage("./ChangePassword");
}
return Page();
}
public async Task<IActionResult> OnPostAsync()
{
if (!ModelState.IsValid)
{
return Page();
}
var user = await _userManager.GetUserAsync(User);
if (user == null)
{
throw new ApplicationException($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
}
var addPasswordResult = await _userManager.AddPasswordAsync(user, Input.NewPassword);
if (!addPasswordResult.Succeeded)
{
foreach (var error in addPasswordResult.Errors)
{
ModelState.AddModelError(string.Empty, error.Description);
}
return Page();
}
await _signInManager.SignInAsync(user, isPersistent: false);
StatusMessage = "Your password has been set.";
return RedirectToPage();
}
}
}

View File

@@ -1,49 +0,0 @@
@page
@model TwoFactorAuthenticationModel
@{
ViewData["Title"] = "Two-factor authentication (2FA)";
}
<h4>@ViewData["Title"]</h4>
@if (Model.Is2faEnabled)
{
if (Model.RecoveryCodesLeft == 0)
{
<div class="alert alert-danger">
<strong>You have no recovery codes left.</strong>
<p>You must <a asp-page="./GenerateRecoveryCodes">generate a new set of recovery codes</a> before you can log in with a recovery code.</p>
</div>
}
else if (Model.RecoveryCodesLeft == 1)
{
<div class="alert alert-danger">
<strong>You have 1 recovery code left.</strong>
<p>You can <a asp-page="./GenerateRecoveryCodes">generate a new set of recovery codes</a>.</p>
</div>
}
else if (Model.RecoveryCodesLeft <= 3)
{
<div class="alert alert-warning">
<strong>You have @Model.RecoveryCodesLeft recovery codes left.</strong>
<p>You should <a asp-page="./GenerateRecoveryCodes">generate a new set of recovery codes</a>.</p>
</div>
}
<a asp-page="./Disable2fa" class="btn btn-default">Disable 2FA</a>
<a asp-page="./GenerateRecoveryCodes" class="btn btn-default">Reset recovery codes</a>
}
<h5>Authenticator app</h5>
@if (!Model.HasAuthenticator)
{
<a asp-page="./EnableAuthenticator" class="btn btn-default">Add authenticator app</a>
}
else
{
<a asp-page="./EnableAuthenticator" class="btn btn-default">Configure authenticator app</a>
<a asp-page="./ResetAuthenticator" class="btn btn-default">Reset authenticator app</a>
}
@section Scripts {
<partial name="_ValidationScriptsPartial" />
}

View File

@@ -1,51 +0,0 @@
using Microsoft.eShopWeb.Infrastructure.Identity;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Extensions.Logging;
using System;
using System.Threading.Tasks;
namespace Microsoft.eShopWeb.RazorPages.Pages.Account.Manage
{
public class TwoFactorAuthenticationModel : PageModel
{
private const string AuthenicatorUriFormat = "otpauth://totp/{0}:{1}?secret={2}&issuer={0}";
private readonly UserManager<ApplicationUser> _userManager;
private readonly SignInManager<ApplicationUser> _signInManager;
private readonly ILogger<TwoFactorAuthenticationModel> _logger;
public TwoFactorAuthenticationModel(
UserManager<ApplicationUser> userManager,
SignInManager<ApplicationUser> signInManager,
ILogger<TwoFactorAuthenticationModel> logger)
{
_userManager = userManager;
_signInManager = signInManager;
_logger = logger;
}
public bool HasAuthenticator { get; set; }
public int RecoveryCodesLeft { get; set; }
[BindProperty]
public bool Is2faEnabled { get; set; }
public async Task<IActionResult> OnGet()
{
var user = await _userManager.GetUserAsync(User);
if (user == null)
{
throw new ApplicationException($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
}
HasAuthenticator = await _userManager.GetAuthenticatorKeyAsync(user) != null;
Is2faEnabled = await _userManager.GetTwoFactorEnabledAsync(user);
RecoveryCodesLeft = await _userManager.CountRecoveryCodesAsync(user);
return Page();
}
}
}

View File

@@ -1,23 +0,0 @@
@{
Layout = "/Pages/Shared/_Layout.cshtml";
}
<h2>Manage your account</h2>
<div>
<h4>Change your account settings</h4>
<hr />
<div class="row">
<div class="col-md-3">
<partial name="_ManageNav" />
</div>
<div class="col-md-9">
@RenderBody()
</div>
</div>
</div>
@section Scripts {
@RenderSection("Scripts", required: false)
}

View File

@@ -1,6 +0,0 @@
<ul class="nav nav-pills nav-stacked">
<li class="@ManageNavPages.IndexNavClass(ViewContext)"><a asp-page="./Index">Profile</a></li>
<li class="@ManageNavPages.ChangePasswordNavClass(ViewContext)"><a asp-page="./ChangePassword">Password</a></li>
<li class="@ManageNavPages.TwoFactorAuthenticationNavClass(ViewContext)"><a asp-page="./TwoFactorAuthentication">Two-factor authentication</a></li>
</ul>

View File

@@ -1,10 +0,0 @@
@model string
@if (!String.IsNullOrEmpty(Model))
{
var statusMessageClass = Model.StartsWith("Error") ? "danger" : "success";
<div class="alert alert-@statusMessageClass alert-dismissible" role="alert">
<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">&times;</span></button>
@Model
</div>
}

View File

@@ -1 +0,0 @@
@using Microsoft.eShopWeb.RazorPages.Pages.Account.Manage

View File

@@ -1,54 +0,0 @@
@page
@model RegisterModel
@{
ViewData["Title"] = "Register";
}
<div class="brand-header-block">
<ul class="container">
<li class="active" style="margin-right: 65px;">Already have an account?
<a asp-page="/Account/Signin">LOGIN</a></li>
</ul>
</div>
<div class="container account-login-container">
<div class="row">
<div class="col-md-12">
<section>
<form asp-route-returnurl="@ViewData["ReturnUrl"]" method="post" class="form-horizontal">
<div asp-validation-summary="All" class="text-danger"></div>
<div class="form-group">
<label asp-for="UserDetails.Email" class="col-md-2 control-label"></label>
<div class="col-md-10">
<input asp-for="UserDetails.Email" class="form-control" />
<span asp-validation-for="UserDetails.Email" class="text-danger"></span>
</div>
</div>
<div class="form-group">
<label asp-for="UserDetails.Password" class="col-md-2 control-label"></label>
<div class="col-md-10">
<input asp-for="UserDetails.Password" class="form-control" />
<span asp-validation-for="UserDetails.Password" class="text-danger"></span>
</div>
</div>
<div class="form-group">
<label asp-for="UserDetails.ConfirmPassword" class="col-md-2 control-label"></label>
<div class="col-md-10">
<input asp-for="UserDetails.ConfirmPassword" class="form-control" />
<span asp-validation-for="UserDetails.ConfirmPassword" class="text-danger"></span>
</div>
</div>
<div class="form-group">
<button type="submit" class="btn btn-default btn-brand btn-brand-big">&nbsp;REGISTER&nbsp;</button>
</div>
<p>
Note that for demo purposes you don't need to register! Use the credentials shown below the
<a asp-action="signin">login screen</a>.
</p>
</form>
</section>
</div>
</div>
</div>
@section Scripts {
@{ await Html.RenderPartialAsync("_ValidationScriptsPartial"); }
}

View File

@@ -1,69 +0,0 @@
using Microsoft.eShopWeb.Infrastructure.Identity;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using System.ComponentModel.DataAnnotations;
using System.Threading.Tasks;
namespace Microsoft.eShopWeb.RazorPages.Pages.Account
{
public class RegisterModel : PageModel
{
private readonly SignInManager<ApplicationUser> _signInManager;
private readonly UserManager<ApplicationUser> _userManager;
public RegisterModel(SignInManager<ApplicationUser> signInManager,
UserManager<ApplicationUser> userManager
)
{
_signInManager = signInManager;
_userManager = userManager;
}
[BindProperty]
public RegisterViewModel UserDetails { get; set; }
public class RegisterViewModel
{
[Required]
[EmailAddress]
[Display(Name = "Email")]
public string Email { get; set; }
[Required]
[StringLength(100, ErrorMessage = "The {0} must be at least {2} characters long.", MinimumLength = 6)]
[DataType(DataType.Password)]
[Display(Name = "Password")]
public string Password { get; set; }
[DataType(DataType.Password)]
[Display(Name = "Confirm password")]
[Compare("Password", ErrorMessage = "The password and confirmation password do not match.")]
public string ConfirmPassword { get; set; }
}
public async Task<IActionResult> OnPost(string returnUrl = "/Index")
{
if (ModelState.IsValid)
{
var user = new ApplicationUser { UserName = UserDetails.Email, Email = UserDetails.Email };
var result = await _userManager.CreateAsync(user, UserDetails.Password);
if (result.Succeeded)
{
await _signInManager.SignInAsync(user, isPersistent: false);
return LocalRedirect(returnUrl);
}
AddErrors(result);
}
return Page();
}
private void AddErrors(IdentityResult result)
{
foreach (var error in result.Errors)
{
ModelState.AddModelError("", error.Description);
}
}
}
}

View File

@@ -1,61 +0,0 @@
@page
@model SigninModel
@{
ViewData["Title"] = "Log in";
}
<div class="brand-header-block">
<ul class="container">
@*<li><a asp-area="" asp-controller="Account" asp-action="Register">REGISTER</a></li>*@
<li class="active" style="margin-right: 65px;">LOGIN</li>
</ul>
</div>
<div class="container account-login-container">
<div class="row">
<div class="col-md-12">
<section>
<form asp-route-returnurl="@ViewData["ReturnUrl"]" method="post" class="form-horizontal">
<h4>ARE YOU REGISTERED?</h4>
<div asp-validation-summary="All" class="text-danger"></div>
<div class="form-group">
<label asp-for="LoginDetails.Email" class="control-label form-label"></label>
<input asp-for="LoginDetails.Email" class="form-control form-input form-input-center" />
<span asp-validation-for="LoginDetails.Email" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="LoginDetails.Password" class="control-label form-label"></label>
<input asp-for="LoginDetails.Password" class="form-control form-input form-input-center" />
<span asp-validation-for="LoginDetails.Password" class="text-danger"></span>
</div>
<div class="form-group">
<div class="checkbox">
<label asp-for="LoginDetails.RememberMe">
<input asp-for="LoginDetails.RememberMe" />
@Html.DisplayNameFor(m => m.LoginDetails.RememberMe)
</label>
</div>
</div>
<div class="form-group">
<button type="submit" class="btn btn-default btn-brand btn-brand-big">&nbsp;LOG IN&nbsp;</button>
</div>
<p>
<a asp-page="/Account/Register"
asp-route-returnurl="@ViewData["ReturnUrl"]" class="text">Register as a new user?</a>
</p>
<p>
Note that for demo purposes you don't need to register and can login with these credentials:
</p>
<p>
User: <b>demouser@microsoft.com</b>
</p>
<p>
Password: <b>Pass@word1</b>
</p>
</form>
</section>
</div>
</div>
</div>
@section Scripts {
@{ await Html.RenderPartialAsync("_ValidationScriptsPartial"); }
}

View File

@@ -1,82 +0,0 @@
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.AspNetCore.Identity;
using Microsoft.eShopWeb.Infrastructure.Identity;
using Microsoft.AspNetCore.Authentication;
using System;
using System.ComponentModel.DataAnnotations;
using Microsoft.eShopWeb.ApplicationCore.Interfaces;
namespace Microsoft.eShopWeb.RazorPages.Pages.Account
{
public class SigninModel : PageModel
{
private readonly SignInManager<ApplicationUser> _signInManager;
private readonly IBasketService _basketService;
public SigninModel(SignInManager<ApplicationUser> signInManager,
IBasketService basketService)
{
_signInManager = signInManager;
_basketService = basketService;
}
[BindProperty]
public LoginViewModel LoginDetails { get; set; } = new LoginViewModel();
public class LoginViewModel
{
[Required]
[EmailAddress]
public string Email { get; set; }
[Required]
[DataType(DataType.Password)]
public string Password { get; set; }
[Display(Name = "Remember me?")]
public bool RememberMe { get; set; }
}
public async Task OnGet(string returnUrl = null)
{
await HttpContext.SignOutAsync(IdentityConstants.ExternalScheme);
ViewData["ReturnUrl"] = returnUrl;
if (!String.IsNullOrEmpty(returnUrl) &&
returnUrl.IndexOf("checkout", StringComparison.OrdinalIgnoreCase) >= 0)
{
ViewData["ReturnUrl"] = "/Basket/Index";
}
}
public async Task<IActionResult> OnPost(string returnUrl = null)
{
if (!ModelState.IsValid)
{
return Page();
}
ViewData["ReturnUrl"] = returnUrl;
var result = await _signInManager.PasswordSignInAsync(LoginDetails.Email,
LoginDetails.Password, LoginDetails.RememberMe, lockoutOnFailure: false);
if (result.Succeeded)
{
string anonymousBasketId = Request.Cookies[Constants.BASKET_COOKIENAME];
if (!String.IsNullOrEmpty(anonymousBasketId))
{
await _basketService.TransferBasketAsync(anonymousBasketId, LoginDetails.Email);
Response.Cookies.Delete(Constants.BASKET_COOKIENAME);
}
return RedirectToPage(returnUrl ?? "/Index");
}
if (result.RequiresTwoFactor)
{
return RedirectToPage("./LoginWith2fa", new { ReturnUrl = returnUrl, RememberMe = LoginDetails.RememberMe });
}
ModelState.AddModelError(string.Empty, "Invalid login attempt.");
return Page();
}
}
}

View File

@@ -1,16 +0,0 @@
@page
@model CheckoutModel
@{
ViewData["Title"] = "Checkout Complete";
}
<section class="esh-catalog-hero">
<div class="container">
<img class="esh-catalog-title" src="~/images/main_banner_text.png" />
</div>
</section>
<div class="container">
<h1>Thanks for your Order!</h1>
<a asp-page="/Index">Continue Shopping...</a>
</div>

View File

@@ -1,85 +0,0 @@
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.eShopWeb.RazorPages.ViewModels;
using Microsoft.eShopWeb.RazorPages.Interfaces;
using Microsoft.eShopWeb.ApplicationCore.Interfaces;
using Microsoft.AspNetCore.Identity;
using Microsoft.eShopWeb.Infrastructure.Identity;
using System;
using Microsoft.AspNetCore.Http;
using System.Collections.Generic;
using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate;
namespace Microsoft.eShopWeb.RazorPages.Pages.Basket
{
public class CheckoutModel : PageModel
{
private readonly IBasketService _basketService;
private readonly IUriComposer _uriComposer;
private readonly SignInManager<ApplicationUser> _signInManager;
private readonly IOrderService _orderService;
private string _username = null;
private readonly IBasketViewModelService _basketViewModelService;
public CheckoutModel(IBasketService basketService,
IBasketViewModelService basketViewModelService,
IUriComposer uriComposer,
SignInManager<ApplicationUser> signInManager,
IOrderService orderService)
{
_basketService = basketService;
_uriComposer = uriComposer;
_signInManager = signInManager;
_orderService = orderService;
_basketViewModelService = basketViewModelService;
}
public BasketViewModel BasketModel { get; set; } = new BasketViewModel();
public void OnGet()
{
}
public async Task<IActionResult> OnPost(Dictionary<string,int> items)
{
await SetBasketModelAsync();
await _basketService.SetQuantities(BasketModel.Id, items);
await _orderService.CreateOrderAsync(BasketModel.Id, new Address("123 Main St.", "Kent", "OH", "United States", "44240"));
await _basketService.DeleteBasketAsync(BasketModel.Id);
return RedirectToPage();
}
private async Task SetBasketModelAsync()
{
if (_signInManager.IsSignedIn(HttpContext.User))
{
BasketModel = await _basketViewModelService.GetOrCreateBasketForUser(User.Identity.Name);
}
else
{
GetOrSetBasketCookieAndUserName();
BasketModel = await _basketViewModelService.GetOrCreateBasketForUser(_username);
}
}
private void GetOrSetBasketCookieAndUserName()
{
if (Request.Cookies.ContainsKey(Constants.BASKET_COOKIENAME))
{
_username = Request.Cookies[Constants.BASKET_COOKIENAME];
}
if (_username != null) return;
_username = Guid.NewGuid().ToString();
var cookieOptions = new CookieOptions();
cookieOptions.Expires = DateTime.Today.AddYears(10);
Response.Cookies.Append(Constants.BASKET_COOKIENAME, _username, cookieOptions);
}
}
}

View File

@@ -1,90 +0,0 @@
@page "{handler?}"
@model IndexModel
@{
ViewData["Title"] = "Basket";
}
<section class="esh-catalog-hero">
<div class="container">
<img class="esh-catalog-title" src="~/images/main_banner_text.png" />
</div>
</section>
<div class="container">
@if (Model.BasketModel.Items.Any())
{
<form method="post">
<article class="esh-basket-titles row">
<br />
<section class="esh-basket-title col-xs-3">Product</section>
<section class="esh-basket-title col-xs-3 hidden-lg-down"></section>
<section class="esh-basket-title col-xs-2">Price</section>
<section class="esh-basket-title col-xs-2">Quantity</section>
<section class="esh-basket-title col-xs-2">Cost</section>
</article>
<div class="esh-catalog-items row">
@for (int i = 0; i < Model.BasketModel.Items.Count; i++)
{
var item = Model.BasketModel.Items[i];
<article class="esh-basket-items row">
<div>
<section class="esh-basket-item esh-basket-item--middle col-lg-3 hidden-lg-down">
<img class="esh-basket-image" src="@item.PictureUrl" />
</section>
<section class="esh-basket-item esh-basket-item--middle col-xs-3">@item.ProductName</section>
<section class="esh-basket-item esh-basket-item--middle col-xs-2">$ @item.UnitPrice.ToString("N2")</section>
<section class="esh-basket-item esh-basket-item--middle col-xs-2">
<input type="hidden" name="@("Items[" + i + "].Key")" value="@item.Id" />
<input type="number" class="esh-basket-input" min="1" name="@("Items[" + i + "].Value")" value="@item.Quantity" />
</section>
<section class="esh-basket-item esh-basket-item--middle esh-basket-item--mark col-xs-2">$ @Math.Round(item.Quantity * item.UnitPrice, 2).ToString("N2")</section>
</div>
<div class="row">
</div>
</article>
@*<div class="esh-catalog-item col-md-4">
@item.ProductId
</div>*@
}
<div class="container">
<article class="esh-basket-titles esh-basket-titles--clean row">
<section class="esh-basket-title col-xs-10"></section>
<section class="esh-basket-title col-xs-2">Total</section>
</article>
<article class="esh-basket-items row">
<section class="esh-basket-item col-xs-10"></section>
<section class="esh-basket-item esh-basket-item--mark col-xs-2">$ @Model.BasketModel.Total().ToString("N2")</section>
</article>
<article class="esh-basket-items row">
<section class="esh-basket-item col-xs-7"></section>
<section class="esh-basket-item col-xs-2">
@*<button class="btn esh-basket-checkout" name="name" value="" type="submit">[ Update ]</button>*@
</section>
</article>
</div>
<section class="esh-basket-item col-xs-push-8 col-xs-4">
<button class="btn esh-basket-checkout" name="updatebutton" value="" type="submit"
asp-page-handler="Update">
[ Update ]
</button>
<input type="submit" asp-page="Checkout"
class="btn esh-basket-checkout"
value="[ Checkout ]" name="action" />
</section>
</div>
</form>
}
else
{
<div class="esh-catalog-items row">
Basket is empty.
</div>
}
</div>

View File

@@ -1,92 +0,0 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.eShopWeb.ApplicationCore.Interfaces;
using Microsoft.eShopWeb.Infrastructure.Identity;
using Microsoft.eShopWeb.RazorPages.Interfaces;
using Microsoft.eShopWeb.RazorPages.ViewModels;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace Microsoft.eShopWeb.RazorPages.Pages.Basket
{
public class IndexModel : PageModel
{
private readonly IBasketService _basketService;
private const string _basketSessionKey = "basketId";
private readonly IUriComposer _uriComposer;
private readonly SignInManager<ApplicationUser> _signInManager;
private string _username = null;
private readonly IBasketViewModelService _basketViewModelService;
public IndexModel(IBasketService basketService,
IBasketViewModelService basketViewModelService,
IUriComposer uriComposer,
SignInManager<ApplicationUser> signInManager)
{
_basketService = basketService;
_uriComposer = uriComposer;
_signInManager = signInManager;
_basketViewModelService = basketViewModelService;
}
public BasketViewModel BasketModel { get; set; } = new BasketViewModel();
public async Task OnGet()
{
await SetBasketModelAsync();
}
public async Task<IActionResult> OnPost(CatalogItemViewModel productDetails)
{
if (productDetails?.Id == null)
{
return RedirectToPage("/Index");
}
await SetBasketModelAsync();
await _basketService.AddItemToBasket(BasketModel.Id, productDetails.Id, productDetails.Price, 1);
await SetBasketModelAsync();
return RedirectToPage();
}
public async Task OnPostUpdate(Dictionary<string, int> items)
{
await SetBasketModelAsync();
await _basketService.SetQuantities(BasketModel.Id, items);
await SetBasketModelAsync();
}
private async Task SetBasketModelAsync()
{
if (_signInManager.IsSignedIn(HttpContext.User))
{
BasketModel = await _basketViewModelService.GetOrCreateBasketForUser(User.Identity.Name);
}
else
{
GetOrSetBasketCookieAndUserName();
BasketModel = await _basketViewModelService.GetOrCreateBasketForUser(_username);
}
}
private void GetOrSetBasketCookieAndUserName()
{
if (Request.Cookies.ContainsKey(Constants.BASKET_COOKIENAME))
{
_username = Request.Cookies[Constants.BASKET_COOKIENAME];
}
if (_username != null) return;
_username = Guid.NewGuid().ToString();
var cookieOptions = new CookieOptions { IsEssential = true };
cookieOptions.Expires = DateTime.Today.AddYears(10);
Response.Cookies.Append(Constants.BASKET_COOKIENAME, _username, cookieOptions);
}
}
}

View File

@@ -1,26 +0,0 @@
@page
@model ErrorModel
@{
ViewData["Title"] = "Error";
}
<h1 class="text-danger">Error.</h1>
<h2 class="text-danger">An error occurred while processing your request.</h2>
@if (Model.ShowRequestId)
{
<p>
<strong>Request ID:</strong> <code>@Model.RequestId</code>
</p>
}
<h3>Development Mode</h3>
<p>
Swapping to the <strong>Development</strong> environment displays detailed information about the error that occurred.
</p>
<p>
<strong>The Development environment shouldn't be enabled for deployed applications.</strong>
It can result in displaying sensitive information from exceptions to end users.
For local debugging, enable the <strong>Development</strong> environment by setting the <strong>ASPNETCORE_ENVIRONMENT</strong> environment variable to <strong>Development</strong>
and restarting the app.
</p>

View File

@@ -1,19 +0,0 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using System.Diagnostics;
namespace Microsoft.eShopWeb.RazorPages.Pages
{
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
public class ErrorModel : PageModel
{
public string RequestId { get; set; }
public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);
public void OnGet()
{
RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier;
}
}
}

View File

@@ -1,48 +0,0 @@
@page
@{
ViewData["Title"] = "Catalog";
@model IndexModel
}
<section class="esh-catalog-hero">
<div class="container">
<img class="esh-catalog-title" src="~/images/main_banner_text.png" />
</div>
</section>
<section class="esh-catalog-filters">
<div class="container">
<form method="get">
<label class="esh-catalog-label" data-title="brand">
<select asp-for="@Model.CatalogModel.BrandFilterApplied" asp-items="@Model.CatalogModel.Brands" class="esh-catalog-filter"></select>
</label>
<label class="esh-catalog-label" data-title="type">
<select asp-for="@Model.CatalogModel.TypesFilterApplied" asp-items="@Model.CatalogModel.Types" class="esh-catalog-filter"></select>
</label>
<input class="esh-catalog-send" type="image" src="images/arrow-right.svg" />
</form>
</div>
</section>
<div class="container">
@if (Model.CatalogModel.CatalogItems.Any())
{
<partial name="_pagination" for="CatalogModel.PaginationInfo" />
<div class="esh-catalog-items row">
@foreach (var catalogItem in Model.CatalogModel.CatalogItems)
{
<div class="esh-catalog-item col-md-4">
<partial name="_product" for="@catalogItem" />
</div>
}
</div>
<partial name="_pagination" for="CatalogModel.PaginationInfo" />
}
else
{
<div class="esh-catalog-items row">
THERE ARE NO RESULTS THAT MATCH YOUR SEARCH
</div>
}
</div>

View File

@@ -1,24 +0,0 @@
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.eShopWeb.RazorPages.ViewModels;
using Microsoft.eShopWeb.RazorPages.Interfaces;
namespace Microsoft.eShopWeb.RazorPages.Pages
{
public class IndexModel : PageModel
{
private readonly ICatalogService _catalogService;
public IndexModel(ICatalogService catalogService)
{
_catalogService = catalogService;
}
public CatalogIndexViewModel CatalogModel { get; set; } = new CatalogIndexViewModel();
public async Task OnGet(CatalogIndexViewModel catalogModel, int? pageId)
{
CatalogModel = await _catalogService.GetCatalogItems(pageId ?? 0, Constants.ITEMS_PER_PAGE, catalogModel.BrandFilterApplied, catalogModel.TypesFilterApplied);
}
}
}

View File

@@ -1,88 +0,0 @@
@page
@model DetailModel
@{
ViewData["Title"] = "My Order History";
}
@{
ViewData["Title"] = "Order Detail";
}
<div class="esh-orders_detail">
<div class="container">
<section class="esh-orders_detail-section">
<article class="esh-orders_detail-titles row">
<section class="esh-orders_detail-title col-xs-3">Order number</section>
<section class="esh-orders_detail-title col-xs-3">Date</section>
<section class="esh-orders_detail-title col-xs-3">Total</section>
<section class="esh-orders_detail-title col-xs-3">Status</section>
</article>
<article class="esh-orders_detail-items row">
<section class="esh-orders_detail-item col-xs-3">@Model.OrderDetails.OrderNumber</section>
<section class="esh-orders_detail-item col-xs-3">@Model.OrderDetails.OrderDate</section>
<section class="esh-orders_detail-item col-xs-3">$@Model.OrderDetails.Total</section>
<section class="esh-orders_detail-title col-xs-3">@Model.OrderDetails.Status</section>
</article>
</section>
@*<section class="esh-orders_detail-section">
<article class="esh-orders_detail-titles row">
<section class="esh-orders_detail-title col-xs-12">Description</section>
</article>
<article class="esh-orders_detail-items row">
<section class="esh-orders_detail-item col-xs-12">@Model.Description</section>
</article>
</section>*@
<section class="esh-orders_detail-section">
<article class="esh-orders_detail-titles row">
<section class="esh-orders_detail-title col-xs-12">Shipping Address</section>
</article>
<article class="esh-orders_detail-items row">
<section class="esh-orders_detail-item col-xs-12">@Model.OrderDetails.ShippingAddress.Street</section>
</article>
<article class="esh-orders_detail-items row">
<section class="esh-orders_detail-item col-xs-12">@Model.OrderDetails.ShippingAddress.City</section>
</article>
<article class="esh-orders_detail-items row">
<section class="esh-orders_detail-item col-xs-12">@Model.OrderDetails.ShippingAddress.Country</section>
</article>
</section>
<section class="esh-orders_detail-section">
<article class="esh-orders_detail-titles row">
<section class="esh-orders_detail-title col-xs-12">ORDER DETAILS</section>
</article>
@for (int i = 0; i < Model.OrderDetails.OrderItems.Count; i++)
{
var item = Model.OrderDetails.OrderItems[i];
<article class="esh-orders_detail-items esh-orders_detail-items--border row">
<section class="esh-orders_detail-item col-md-4 hidden-md-down">
<img class="esh-orders_detail-image" src="@item.PictureUrl">
</section>
<section class="esh-orders_detail-item esh-orders_detail-item--middle col-xs-4">@item.ProductName</section>
<section class="esh-orders_detail-item esh-orders_detail-item--middle col-xs-1">$ @item.UnitPrice.ToString("N2")</section>
<section class="esh-orders_detail-item esh-orders_detail-item--middle col-xs-1">@item.Units</section>
<section class="esh-orders_detail-item esh-orders_detail-item--middle col-xs-2">$ @Math.Round(item.Units * item.UnitPrice, 2).ToString("N2")</section>
</article>
}
</section>
<section class="esh-orders_detail-section esh-orders_detail-section--right">
<article class="esh-orders_detail-titles esh-basket-titles--clean row">
<section class="esh-orders_detail-title col-xs-9"></section>
<section class="esh-orders_detail-title col-xs-2">TOTAL</section>
</article>
<article class="esh-orders_detail-items row">
<section class="esh-orders_detail-item col-xs-9"></section>
<section class="esh-orders_detail-item esh-orders_detail-item--mark col-xs-2">$ @Model.OrderDetails.Total</section>
</article>
</section>
</div>
</div>

View File

@@ -1,66 +0,0 @@
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.eShopWeb.ApplicationCore.Interfaces;
using System.Linq;
using System;
using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate;
using System.Collections.Generic;
namespace Microsoft.eShopWeb.RazorPages.Pages.Order
{
public class DetailModel : PageModel
{
private readonly IOrderRepository _orderRepository;
public DetailModel(IOrderRepository orderRepository)
{
_orderRepository = orderRepository;
}
public OrderViewModel OrderDetails { get; set; } = new OrderViewModel();
public class OrderViewModel
{
public int OrderNumber { get; set; }
public DateTimeOffset OrderDate { get; set; }
public decimal Total { get; set; }
public string Status { get; set; }
public Address ShippingAddress { get; set; }
public List<OrderItemViewModel> OrderItems { get; set; } = new List<OrderItemViewModel>();
}
public class OrderItemViewModel
{
public int ProductId { get; set; }
public string ProductName { get; set; }
public decimal UnitPrice { get; set; }
public decimal Discount { get; set; }
public int Units { get; set; }
public string PictureUrl { get; set; }
}
public async Task OnGet(int orderId)
{
var order = await _orderRepository.GetByIdWithItemsAsync(orderId);
OrderDetails = new OrderViewModel()
{
OrderDate = order.OrderDate,
OrderItems = order.OrderItems.Select(oi => new OrderItemViewModel()
{
Discount = 0,
PictureUrl = oi.ItemOrdered.PictureUri,
ProductId = oi.ItemOrdered.CatalogItemId,
ProductName = oi.ItemOrdered.ProductName,
UnitPrice = oi.UnitPrice,
Units = oi.Units
}).ToList(),
OrderNumber = order.Id,
ShippingAddress = order.ShipToAddress,
Status = "Pending",
Total = order.Total()
};
}
}
}

View File

@@ -1,39 +0,0 @@
@page
@model IndexModel
@{
ViewData["Title"] = "My Order History";
}
<div class="esh-orders">
<div class="container">
<h1>@ViewData["Title"]</h1>
<article class="esh-orders-titles row">
<section class="esh-orders-title col-xs-2">Order number</section>
<section class="esh-orders-title col-xs-4">Date</section>
<section class="esh-orders-title col-xs-2">Total</section>
<section class="esh-orders-title col-xs-2">Status</section>
<section class="esh-orders-title col-xs-2"></section>
</article>
@if (Model.Orders != null && Model.Orders.Any())
{
@foreach (var item in Model.Orders)
{
<article class="esh-orders-items row">
<section class="esh-orders-item col-xs-2">@Html.DisplayFor(modelItem => item.OrderNumber)</section>
<section class="esh-orders-item col-xs-4">@Html.DisplayFor(modelItem => item.OrderDate)</section>
<section class="esh-orders-item col-xs-2">$ @Html.DisplayFor(modelItem => item.Total)</section>
<section class="esh-orders-item col-xs-2">@Html.DisplayFor(modelItem => item.Status)</section>
<section class="esh-orders-item col-xs-1">
<a class="esh-orders-link" asp-page="Detail" asp-route-orderId="@item.OrderNumber">Detail</a>
</section>
<section class="esh-orders-item col-xs-1">
@if (item.Status.ToLower() == "submitted")
{
<a class="esh-orders-link" asp-page="Cancel" asp-route-orderId="@item.OrderNumber">Cancel</a>
}
</section>
</article>
}
}
</div>
</div>

View File

@@ -1,45 +0,0 @@
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.eShopWeb.ApplicationCore.Interfaces;
using Microsoft.eShopWeb.ApplicationCore.Specifications;
using System.Collections.Generic;
using System.Linq;
using System;
namespace Microsoft.eShopWeb.RazorPages.Pages.Order
{
public class IndexModel : PageModel
{
private readonly IOrderRepository _orderRepository;
public IndexModel(IOrderRepository orderRepository)
{
_orderRepository = orderRepository;
}
public List<OrderSummary> Orders { get; set; } = new List<OrderSummary>();
public class OrderSummary
{
public int OrderNumber { get; set; }
public DateTimeOffset OrderDate { get; set; }
public decimal Total { get; set; }
public string Status { get; set; }
}
public async Task OnGet()
{
var orders = await _orderRepository.ListAsync(new CustomerOrdersWithItemsSpecification(User.Identity.Name));
Orders = orders
.Select(o => new OrderSummary()
{
OrderDate = o.OrderDate,
OrderNumber = o.Id,
Status = "Pending",
Total = o.Total()
}).ToList();
}
}
}

View File

@@ -1,8 +0,0 @@
@page
@model PrivacyModel
@{
ViewData["Title"] = "Privacy Policy";
}
<h1>@ViewData["Title"]</h1>
<p>Use this page to detail your site's privacy policy.</p>

View File

@@ -1,11 +0,0 @@
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace Microsoft.eShopWeb.RazorPages.Pages
{
public class PrivacyModel : PageModel
{
public void OnGet()
{
}
}
}

View File

@@ -1,16 +0,0 @@
@using Microsoft.eShopWeb.RazorPages.ViewComponents
@model Basket.BasketComponentViewModel
@{
ViewData["Title"] = "My Basket";
}
<a class="esh-basketstatus "
asp-page="/Basket/Index">
<div class="esh-basketstatus-image">
<img src="~/images/cart.png" />
</div>
<div class="esh-basketstatus-badge">
@Model.ItemsCount
</div>
</a>

View File

@@ -1,25 +0,0 @@
@using Microsoft.AspNetCore.Http.Features
@{
var consentFeature = Context.Features.Get<ITrackingConsentFeature>();
var showBanner = !consentFeature?.CanTrack ?? false;
var cookieString = consentFeature?.CreateConsentCookie();
}
@if (showBanner)
{
<div id="cookieConsent" class="alert alert-info alert-dismissible fade show" role="alert">
Use this space to summarize your privacy and cookie use policy. <a asp-page="/Privacy">Learn More</a>.
<button type="button" class="accept-policy close" data-dismiss="alert" aria-label="Close" data-cookie-string="@cookieString">
<span aria-hidden="true">Accept</span>
</button>
</div>
<script>
(function () {
var button = document.querySelector("#cookieConsent button[data-cookie-string]");
button.addEventListener("click", function (event) {
document.cookie = button.dataset.cookieString;
}, false);
})();
</script>
}

View File

@@ -1,79 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>@ViewData["Title"] - WebRazorPages</title>
<environment include="Development">
<link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.css" />
<link rel="stylesheet" href="~/css/app.css" />
</environment>
<environment exclude="Development">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.1.3/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"
crossorigin="anonymous"
integrity="sha256-eSi1q2PG6J7g7ib17yAaWMcrr5GrtohYChqibrV7PBE=" />
<link rel="stylesheet" href="~/css/app.min.css" asp-append-version="true" />
</environment>
<link rel="stylesheet" href="~/css/app.component.css" />
<link rel="stylesheet" href="~/css/basket/basket.component.css" />
<link rel="stylesheet" href="~/css/catalog/pager.css" />
<link rel="stylesheet" href="~/css/catalog/catalog.component.css" />
<link rel="stylesheet" href="~/css/basket/basket-status/basket-status.component.css" />
<link rel="stylesheet" href="~/css/orders/orders.component.css" />
</head>
<body>
<header class="navbar navbar-light navbar-static-top">
<div class="container">
<article class="row">
<section class="col-lg-7 col-md-6 col-xs-12">
<a asp-page="/Index" class="navbar-brand">
<img src="~/images/brand.png" alt="eShop On Web" />
</a>
</section>
<partial name="_LoginPartial" />
</article>
</div>
</header>
<partial name="_CookieConsentPartial" />
@RenderBody()
<footer class="esh-app-footer">
<div class="container">
<article class="row">
<section class="col-sm-6"></section>
<section class="col-sm-6">
<div class="esh-app-footer-text hidden-xs"> e-ShopOnWeb. All rights reserved </div>
</section>
</article>
</div>
</footer>
<environment include="Development">
<script src="~/lib/jquery/dist/jquery.js"></script>
@*<script src="~/lib/bootstrap/dist/js/bootstrap.bundle.js"></script>*@
<script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.1.3/js/bootstrap.bundle.min.js"
asp-fallback-src="~/lib/bootstrap/dist/js/bootstrap.bundle.min.js"
asp-fallback-test="window.jQuery && window.jQuery.fn && window.jQuery.fn.modal"
crossorigin="anonymous"
integrity="sha256-E/V4cWE4qvAeO5MOhjtGtqDzPndRO1LBk8lJ/PR7CA4=">
</script>
</environment>
<environment exclude="Development">
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"
asp-fallback-src="~/lib/jquery/dist/jquery.min.js"
asp-fallback-test="window.jQuery"
crossorigin="anonymous"
integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8=">
</script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.1.3/js/bootstrap.bundle.min.js"
asp-fallback-src="~/lib/bootstrap/dist/js/bootstrap.bundle.min.js"
asp-fallback-test="window.jQuery && window.jQuery.fn && window.jQuery.fn.modal"
crossorigin="anonymous"
integrity="sha256-E/V4cWE4qvAeO5MOhjtGtqDzPndRO1LBk8lJ/PR7CA4=">
</script>
</environment>
<script src="~/js/site.js" asp-append-version="true"></script>
@RenderSection("Scripts", required: false)
</body>
</html>

View File

@@ -1,56 +0,0 @@
@inject SignInManager<ApplicationUser> SignInManager
@inject UserManager<ApplicationUser> UserManager
@if (Context.User.Identity.IsAuthenticated)
{
<section class="col-lg-4 col-md-5 col-xs-12">
<div class="esh-identity">
<form asp-controller="Account" asp-action="Logout" method="post"
id="logoutForm" class="navbar-right">
<section class="esh-identity-section">
@*<div class="esh-identity-name">@User.FindFirst(x => x.Type == "preferred_username").Value</div>*@
<img class="esh-identity-image" src="~/images/arrow-down.png">
</section>
<section class="esh-identity-drop">
<a class="esh-identity-item"
asp-page="/Order/Index">
<div class="esh-identity-name esh-identity-name--upper">My orders</div>
<img class="esh-identity-image" src="~/images/my_orders.png">
</a>
<a class="esh-identity-item"
asp-page="/Account/Manage/Index">
<div class="esh-identity-name esh-identity-name--upper">My account</div>
<img class="esh-identity-image" src="~/images/my_orders.png">
</a>
<a class="esh-identity-item"
href="javascript:document.getElementById('logoutForm').submit()">
<div class="esh-identity-name esh-identity-name--upper">Log Out</div>
<img class="esh-identity-image" src="~/images/logout.png">
</a>
</section>
</form>
</div>
</section>
<section class="col-lg-1 col-xs-12">
@await Component.InvokeAsync("Basket")
</section>
}
else
{
<section class="col-lg-1 col-lg-offset-3 col-md-3 col-xs-6">
<div class="esh-identity">
<section class="esh-identity-section">
<div class="esh-identity-item">
<a asp-page="/Account/Signin" class="esh-identity-name esh-identity-name--upper">
Login
</a>
</div>
</section>
</div>
</section>
<section class="col-lg-1 col-xs-12">
@await Component.InvokeAsync("Basket")
</section>
}

View File

@@ -1,18 +0,0 @@
<environment include="Development">
<script src="~/lib/jquery-validation/dist/jquery.validate.js"></script>
<script src="~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.js"></script>
</environment>
<environment exclude="Development">
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-validate/1.17.0/jquery.validate.min.js"
asp-fallback-src="~/lib/jquery-validation/dist/jquery.validate.min.js"
asp-fallback-test="window.jQuery && window.jQuery.validator"
crossorigin="anonymous"
integrity="sha256-F6h55Qw6sweK+t7SiOJX+2bpSAa3b/fnlrVCJvmEj1A=">
</script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-validation-unobtrusive/3.2.11/jquery.validate.unobtrusive.min.js"
asp-fallback-src="~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js"
asp-fallback-test="window.jQuery && window.jQuery.validator && window.jQuery.validator.unobtrusive"
crossorigin="anonymous"
integrity="sha256-9GycpJnliUjJDVDqP0UEu/bsm9U+3dnQUH8+3W10vkY=">
</script>
</environment>

View File

@@ -1,34 +0,0 @@
@model PaginationInfoViewModel
<div class="esh-pager">
<div class="container-fluid">
<article class="esh-pager-wrapper row">
<nav>
<div class="col-md-2 col-xs-12">
<a class="esh-pager-item-left esh-pager-item--navigable @Model.Previous"
id="Previous"
asp-route-pageid="@(Model.ActualPage - 1)"
aria-label="Previous">
Previous
</a>
</div>
<div class="col-md-8 col-xs-12">
<span class="esh-pager-item">
Showing @Model.ItemsPerPage of @Model.TotalItems products - Page @(Model.ActualPage + 1) - @Model.TotalPages
</span>
</div>
<div class="col-md-2 col-xs-12">
<a class="esh-pager-item-right esh-pager-item--navigable @Model.Next"
id="Next"
asp-route-pageid="@(Model.ActualPage + 1)"
aria-label="Next">
Next
</a>
</div>
</nav>
</article>
</div>
</div>

View File

@@ -1,24 +0,0 @@
@model CatalogItemViewModel
<form asp-page="/Basket/Index" method="post">
<img class="esh-catalog-thumbnail" src="@Model.PictureUri" />
<input class="esh-catalog-button" type="submit" value="[ ADD TO BASKET ]" />
<div class="esh-catalog-name">
<span>@Model.Name</span>
</div>
<div class="esh-catalog-price">
<span>@Model.Price.ToString("N2")</span>
</div>
@*<input type="hidden" asp-for="@Model.CatalogBrand" name="brand" />
<input type="hidden" asp-for="@Model.CatalogBrandId" name="brandId" />
<input type="hidden" asp-for="@Model.CatalogType" name="type" />
<input type="hidden" asp-for="@Model.CatalogTypeId" name="typeId" />
<input type="hidden" asp-for="@Model.Description" name="description" />*@
<input type="hidden" asp-for="@Model.Id" name="id" />
<input type="hidden" asp-for="@Model.Name" name="name" />
<input type="hidden" asp-for="@Model.PictureUri" name="pictureUri" />
<input type="hidden" asp-for="@Model.Price" name="price" />
</form>

View File

@@ -1,6 +0,0 @@
@using Microsoft.eShopWeb.RazorPages
@using Microsoft.eShopWeb.RazorPages.ViewModels
@using Microsoft.AspNetCore.Identity
@using Microsoft.eShopWeb.Infrastructure.Identity
@namespace Microsoft.eShopWeb.RazorPages.Pages
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

View File

@@ -1,3 +0,0 @@
@{
Layout = "_Layout";
}

View File

@@ -1,46 +0,0 @@
using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Identity;
using Microsoft.eShopWeb.Infrastructure.Data;
using Microsoft.eShopWeb.Infrastructure.Identity;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using System;
namespace Microsoft.eShopWeb.RazorPages
{
public class Program
{
public static void Main(string[] args)
{
var host = CreateWebHostBuilder(args)
.Build();
using (var scope = host.Services.CreateScope())
{
var services = scope.ServiceProvider;
var loggerFactory = services.GetRequiredService<ILoggerFactory>();
try
{
var catalogContext = services.GetRequiredService<CatalogContext>();
CatalogContextSeed.SeedAsync(catalogContext, loggerFactory)
.Wait();
var userManager = services.GetRequiredService<UserManager<ApplicationUser>>();
AppIdentityDbContextSeed.SeedAsync(userManager).Wait();
}
catch (Exception ex)
{
var logger = loggerFactory.CreateLogger<Program>();
logger.LogError(ex, "An error occurred seeding the DB.");
}
}
host.Run();
}
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>();
}
}

View File

@@ -1,27 +0,0 @@
{
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:17930",
"sslPort": 44394
}
},
"profiles": {
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"WebRazorPages": {
"commandName": "Project",
"launchBrowser": true,
"applicationUrl": "https://localhost:5001;http://localhost:5000",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}

View File

@@ -1,77 +0,0 @@
using Microsoft.eShopWeb.ApplicationCore.Interfaces;
using System.Threading.Tasks;
using Microsoft.eShopWeb.ApplicationCore.Entities;
using System.Linq;
using System.Collections.Generic;
using Microsoft.eShopWeb.ApplicationCore.Specifications;
using Microsoft.eShopWeb.RazorPages.Interfaces;
using Microsoft.eShopWeb.RazorPages.ViewModels;
using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate;
namespace Microsoft.eShopWeb.RazorPages.Services
{
public class BasketViewModelService : IBasketViewModelService
{
private readonly IAsyncRepository<Basket> _basketRepository;
private readonly IUriComposer _uriComposer;
private readonly IRepository<CatalogItem> _itemRepository;
public BasketViewModelService(IAsyncRepository<Basket> basketRepository,
IRepository<CatalogItem> itemRepository,
IUriComposer uriComposer)
{
_basketRepository = basketRepository;
_uriComposer = uriComposer;
_itemRepository = itemRepository;
}
public async Task<BasketViewModel> GetOrCreateBasketForUser(string userName)
{
var basketSpec = new BasketWithItemsSpecification(userName);
var basket = (await _basketRepository.ListAsync(basketSpec)).FirstOrDefault();
if (basket == null)
{
return await CreateBasketForUser(userName);
}
return CreateViewModelFromBasket(basket);
}
private BasketViewModel CreateViewModelFromBasket(Basket basket)
{
var viewModel = new BasketViewModel();
viewModel.Id = basket.Id;
viewModel.BuyerId = basket.BuyerId;
viewModel.Items = basket.Items.Select(i =>
{
var itemModel = new BasketItemViewModel()
{
Id = i.Id,
UnitPrice = i.UnitPrice,
Quantity = i.Quantity,
CatalogItemId = i.CatalogItemId
};
var item = _itemRepository.GetById(i.CatalogItemId);
itemModel.PictureUrl = _uriComposer.ComposePicUri(item.PictureUri);
itemModel.ProductName = item.Name;
return itemModel;
})
.ToList();
return viewModel;
}
private async Task<BasketViewModel> CreateBasketForUser(string userId)
{
var basket = new Basket() { BuyerId = userId };
await _basketRepository.AddAsync(basket);
return new BasketViewModel()
{
BuyerId = basket.BuyerId,
Id = basket.Id,
Items = new List<BasketItemViewModel>()
};
}
}
}

View File

@@ -1,55 +0,0 @@
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.eShopWeb.RazorPages.Interfaces;
using Microsoft.eShopWeb.RazorPages.ViewModels;
using Microsoft.Extensions.Caching.Memory;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace Microsoft.eShopWeb.RazorPages.Services
{
public class CachedCatalogService : ICatalogService
{
private readonly IMemoryCache _cache;
private readonly CatalogService _catalogService;
private static readonly string _brandsKey = "brands";
private static readonly string _typesKey = "types";
private static readonly string _itemsKeyTemplate = "items-{0}-{1}-{2}-{3}";
private static readonly TimeSpan _defaultCacheDuration = TimeSpan.FromSeconds(30);
public CachedCatalogService(IMemoryCache cache,
CatalogService catalogService)
{
_cache = cache;
_catalogService = catalogService;
}
public async Task<IEnumerable<SelectListItem>> GetBrands()
{
return await _cache.GetOrCreateAsync(_brandsKey, async entry =>
{
entry.SlidingExpiration = _defaultCacheDuration;
return await _catalogService.GetBrands();
});
}
public async Task<CatalogIndexViewModel> GetCatalogItems(int pageIndex, int itemsPage, int? brandID, int? typeId)
{
string cacheKey = String.Format(_itemsKeyTemplate, pageIndex, itemsPage, brandID, typeId);
return await _cache.GetOrCreateAsync(cacheKey, async entry =>
{
entry.SlidingExpiration = _defaultCacheDuration;
return await _catalogService.GetCatalogItems(pageIndex, itemsPage, brandID, typeId);
});
}
public async Task<IEnumerable<SelectListItem>> GetTypes()
{
return await _cache.GetOrCreateAsync(_typesKey, async entry =>
{
entry.SlidingExpiration = _defaultCacheDuration;
return await _catalogService.GetTypes();
});
}
}
}

View File

@@ -1,119 +0,0 @@
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.eShopWeb.ApplicationCore.Entities;
using Microsoft.eShopWeb.ApplicationCore.Interfaces;
using Microsoft.eShopWeb.ApplicationCore.Specifications;
using Microsoft.eShopWeb.RazorPages.Interfaces;
using Microsoft.eShopWeb.RazorPages.ViewModels;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Microsoft.eShopWeb.RazorPages.Services
{
/// <summary>
/// This is a UI-specific service so belongs in UI project. It does not contain any business logic and works
/// with UI-specific types (view models and SelectListItem types).
/// </summary>
public class CatalogService : ICatalogService
{
private readonly ILogger<CatalogService> _logger;
private readonly IRepository<CatalogItem> _itemRepository;
private readonly IAsyncRepository<CatalogBrand> _brandRepository;
private readonly IAsyncRepository<CatalogType> _typeRepository;
private readonly IUriComposer _uriComposer;
public CatalogService(
ILoggerFactory loggerFactory,
IRepository<CatalogItem> itemRepository,
IAsyncRepository<CatalogBrand> brandRepository,
IAsyncRepository<CatalogType> typeRepository,
IUriComposer uriComposer)
{
_logger = loggerFactory.CreateLogger<CatalogService>();
_itemRepository = itemRepository;
_brandRepository = brandRepository;
_typeRepository = typeRepository;
_uriComposer = uriComposer;
}
public async Task<CatalogIndexViewModel> GetCatalogItems(int pageIndex, int itemsPage, int? brandId, int? typeId)
{
_logger.LogInformation("GetCatalogItems called.");
var filterSpecification = new CatalogFilterSpecification(brandId, typeId);
var filterPaginatedSpecification =
new CatalogFilterPaginatedSpecification(itemsPage * pageIndex, itemsPage, brandId, typeId);
// the implementation below using ForEach and Count. We need a List.
var itemsOnPage = _itemRepository.List(filterPaginatedSpecification).ToList();
var totalItems = _itemRepository.Count(filterSpecification);
itemsOnPage.ForEach(x =>
{
x.PictureUri = _uriComposer.ComposePicUri(x.PictureUri);
});
var vm = new CatalogIndexViewModel()
{
CatalogItems = itemsOnPage.Select(i => new CatalogItemViewModel()
{
Id = i.Id,
Name = i.Name,
PictureUri = i.PictureUri,
Price = i.Price
}),
Brands = await GetBrands(),
Types = await GetTypes(),
BrandFilterApplied = brandId ?? 0,
TypesFilterApplied = typeId ?? 0,
PaginationInfo = new PaginationInfoViewModel()
{
ActualPage = pageIndex,
ItemsPerPage = itemsOnPage.Count,
TotalItems = totalItems,
TotalPages = int.Parse(Math.Ceiling(((decimal)totalItems / itemsPage)).ToString())
}
};
vm.PaginationInfo.Next = (vm.PaginationInfo.ActualPage == vm.PaginationInfo.TotalPages - 1) ? "is-disabled" : "";
vm.PaginationInfo.Previous = (vm.PaginationInfo.ActualPage == 0) ? "is-disabled" : "";
return vm;
}
public async Task<IEnumerable<SelectListItem>> GetBrands()
{
_logger.LogInformation("GetBrands called.");
var brands = await _brandRepository.ListAllAsync();
var items = new List<SelectListItem>
{
new SelectListItem() { Value = null, Text = "All", Selected = true }
};
foreach (CatalogBrand brand in brands)
{
items.Add(new SelectListItem() { Value = brand.Id.ToString(), Text = brand.Brand });
}
return items;
}
public async Task<IEnumerable<SelectListItem>> GetTypes()
{
_logger.LogInformation("GetTypes called.");
var types = await _typeRepository.ListAllAsync();
var items = new List<SelectListItem>
{
new SelectListItem() { Value = null, Text = "All", Selected = true }
};
foreach (CatalogType type in types)
{
items.Add(new SelectListItem() { Value = type.Id.ToString(), Text = type.Type });
}
return items;
}
}
}

View File

@@ -1,185 +0,0 @@
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.UI;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Microsoft.eShopWeb.ApplicationCore.Interfaces;
using Microsoft.eShopWeb.ApplicationCore.Services;
using Microsoft.eShopWeb.Infrastructure.Data;
using Microsoft.eShopWeb.Infrastructure.Identity;
using Microsoft.eShopWeb.Infrastructure.Logging;
using Microsoft.eShopWeb.Infrastructure.Services;
using Microsoft.eShopWeb.RazorPages.Interfaces;
using Microsoft.eShopWeb.RazorPages.Services;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Text;
namespace Microsoft.eShopWeb.RazorPages
{
public class Startup
{
private IServiceCollection _services;
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureDevelopmentServices(IServiceCollection services)
{
// use in-memory database
ConfigureTestingServices(services);
// use real database
// ConfigureProductionServices(services);
}
public void ConfigureTestingServices(IServiceCollection services)
{
// use in-memory database
services.AddDbContext<CatalogContext>(c =>
c.UseInMemoryDatabase("Catalog"));
// Add Identity DbContext
services.AddDbContext<AppIdentityDbContext>(options =>
options.UseInMemoryDatabase("Identity"));
ConfigureServices(services);
}
public void ConfigureProductionServices(IServiceCollection services)
{
// use real database
services.AddDbContext<CatalogContext>(c =>
{
try
{
// Requires LocalDB which can be installed with SQL Server Express 2016
// https://www.microsoft.com/en-us/download/details.aspx?id=54284
c.UseSqlServer(Configuration.GetConnectionString("CatalogConnection"));
}
catch (System.Exception ex)
{
var message = ex.Message;
}
});
// Add Identity DbContext
services.AddDbContext<AppIdentityDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("IdentityConnection")));
ConfigureServices(services);
}
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
ConfigureCookieOptions(services);
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddDefaultUI(UIFramework.Bootstrap4)
.AddEntityFrameworkStores<AppIdentityDbContext>()
.AddDefaultTokenProviders();
services.AddScoped(typeof(IRepository<>), typeof(EfRepository<>));
services.AddScoped(typeof(IAsyncRepository<>), typeof(EfRepository<>));
services.AddScoped<ICatalogService, CachedCatalogService>();
services.AddScoped<IBasketService, BasketService>();
services.AddScoped<IBasketViewModelService, BasketViewModelService>();
services.AddScoped<IOrderService, OrderService>();
services.AddScoped<IOrderRepository, OrderRepository>();
services.AddScoped<CatalogService>();
services.Configure<CatalogSettings>(Configuration);
services.AddSingleton<IUriComposer>(new UriComposer(Configuration.Get<CatalogSettings>()));
services.AddScoped(typeof(IAppLogger<>), typeof(LoggerAdapter<>));
services.AddTransient<IEmailSender, EmailSender>();
// Add memory cache services
services.AddMemoryCache();
services.AddMvc()
.SetCompatibilityVersion(CompatibilityVersion.Version_2_2)
.AddRazorPagesOptions(options =>
{
options.Conventions.AuthorizeFolder("/Order");
options.Conventions.AuthorizePage("/Basket/Checkout");
});
_services = services;
}
private static void ConfigureCookieOptions(IServiceCollection services)
{
services.Configure<CookiePolicyOptions>(options =>
{
// This lambda determines whether user consent for non-essential cookies is needed for a given request.
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.None;
});
services.ConfigureApplicationCookie(options =>
{
options.Cookie.HttpOnly = true;
options.ExpireTimeSpan = TimeSpan.FromHours(1);
options.LoginPath = "/Account/Signin";
options.LogoutPath = "/Account/Signout";
});
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
ListAllRegisteredServices(app);
app.UseDatabaseErrorPage();
}
else
{
app.UseExceptionHandler("/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseCookiePolicy();
app.UseAuthentication();
app.UseMvc();
}
private void ListAllRegisteredServices(IApplicationBuilder app)
{
app.Map("/allservices", builder => builder.Run(async context =>
{
var sb = new StringBuilder();
sb.Append("<h1>All Services</h1>");
sb.Append("<table><thead>");
sb.Append("<tr><th>Type</th><th>Lifetime</th><th>Instance</th></tr>");
sb.Append("</thead><tbody>");
foreach (var svc in _services)
{
sb.Append("<tr>");
sb.Append($"<td>{svc.ServiceType.FullName}</td>");
sb.Append($"<td>{svc.Lifetime}</td>");
sb.Append($"<td>{svc.ImplementationType?.FullName}</td>");
sb.Append("</tr>");
}
sb.Append("</tbody></table>");
await context.Response.WriteAsync(sb.ToString());
}));
}
}
}

View File

@@ -1,52 +0,0 @@
using Microsoft.eShopWeb.ApplicationCore.Interfaces;
using Microsoft.eShopWeb.Infrastructure.Identity;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using System.Threading.Tasks;
namespace Microsoft.eShopWeb.RazorPages.ViewComponents
{
public class Basket : ViewComponent
{
private readonly IBasketService _basketService;
private readonly SignInManager<ApplicationUser> _signInManager;
public Basket(IBasketService basketService,
SignInManager<ApplicationUser> signInManager)
{
_basketService = basketService;
_signInManager = signInManager;
}
public async Task<IViewComponentResult> InvokeAsync()
{
var vm = new BasketComponentViewModel();
string userName = GetUsername();
vm.ItemsCount = (await _basketService.GetBasketItemCountAsync(userName));
return View(vm);
}
public class BasketComponentViewModel
{
public int ItemsCount { get; set; }
}
private string GetUsername()
{
if (_signInManager.IsSignedIn(HttpContext.User))
{
return User.Identity.Name;
}
return GetBasketIdFromCookie() ?? Constants.DEFAULT_USERNAME;
}
private string GetBasketIdFromCookie()
{
if (Request.Cookies.ContainsKey(Constants.BASKET_COOKIENAME))
{
return Request.Cookies[Constants.BASKET_COOKIENAME];
}
return null;
}
}
}

View File

@@ -1,14 +0,0 @@
namespace Microsoft.eShopWeb.RazorPages.ViewModels
{
public class BasketItemViewModel
{
public int Id { get; set; }
public int CatalogItemId { get; set; }
public string ProductName { get; set; }
public decimal UnitPrice { get; set; }
public decimal OldUnitPrice { get; set; }
public int Quantity { get; set; }
public string PictureUrl { get; set; }
}
}

View File

@@ -1,19 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace Microsoft.eShopWeb.RazorPages.ViewModels
{
public class BasketViewModel
{
public int Id { get; set; }
public List<BasketItemViewModel> Items { get; set; } = new List<BasketItemViewModel>();
public string BuyerId { get; set; }
public decimal Total()
{
return Math.Round(Items.Sum(x => x.UnitPrice * x.Quantity), 2);
}
}
}

View File

@@ -1,15 +0,0 @@
using Microsoft.AspNetCore.Mvc.Rendering;
using System.Collections.Generic;
namespace Microsoft.eShopWeb.RazorPages.ViewModels
{
public class CatalogIndexViewModel
{
public IEnumerable<CatalogItemViewModel> CatalogItems { get; set; }
public IEnumerable<SelectListItem> Brands { get; set; }
public IEnumerable<SelectListItem> Types { get; set; }
public int? BrandFilterApplied { get; set; }
public int? TypesFilterApplied { get; set; }
public PaginationInfoViewModel PaginationInfo { get; set; }
}
}

View File

@@ -1,11 +0,0 @@
namespace Microsoft.eShopWeb.RazorPages.ViewModels
{
public class CatalogItemViewModel
{
public int Id { get; set; }
public string Name { get; set; }
public string PictureUri { get; set; }
public decimal Price { get; set; }
}
}

View File

@@ -1,12 +0,0 @@
namespace Microsoft.eShopWeb.RazorPages.ViewModels
{
public class PaginationInfoViewModel
{
public int TotalItems { get; set; }
public int ItemsPerPage { get; set; }
public int ActualPage { get; set; }
public int TotalPages { get; set; }
public string Previous { get; set; }
public string Next { get; set; }
}
}

View File

@@ -1,31 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>netcoreapp2.2</TargetFramework>
<RootNamespace>Microsoft.eShopWeb.RazorPages</RootNamespace>
<AssemblyName>Microsoft.eShopWeb.RazorPages</AssemblyName>
<DockerTargetOS>Linux</DockerTargetOS>
<UserSecretsId>aspnet-WebRazorPages-6B1EFE4D-B682-4543-93F5-69EE95000B1D</UserSecretsId>
<AspNetCoreHostingModel>InProcess</AspNetCoreHostingModel>
</PropertyGroup>
<ItemGroup>
<None Include="Pages\Shared\Components\Basket\Default.cshtml" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.App" />
<PackageReference Include="Microsoft.AspNetCore.Razor.Design" Version="2.2.0" PrivateAssets="All" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\ApplicationCore\ApplicationCore.csproj" />
<ProjectReference Include="..\Infrastructure\Infrastructure.csproj" />
</ItemGroup>
<ItemGroup>
<Content Update="Pages\Shared\_LoginPartial.cshtml">
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
</Content>
</ItemGroup>
</Project>

View File

@@ -1,9 +0,0 @@
{
"Logging": {
"LogLevel": {
"Default": "Debug",
"System": "Information",
"Microsoft": "Information"
}
}
}

View File

@@ -1,11 +0,0 @@
{
"ConnectionStrings": {
"DefaultConnection": "Server=(localdb)\\mssqllocaldb;Database=aspnet-WebRazorPages-53bc9b9d-9d6a-45d4-8429-2a2761773502;Trusted_Connection=True;MultipleActiveResultSets=true"
},
"Logging": {
"LogLevel": {
"Default": "Warning"
}
},
"AllowedHosts": "*"
}

View File

@@ -1,24 +0,0 @@
// Configure bundling and minification for the project.
// More info at https://go.microsoft.com/fwlink/?LinkId=808241
[
{
"outputFileName": "wwwroot/css/site.min.css",
// An array of relative input file paths. Globbing patterns supported
"inputFiles": [
"wwwroot/css/site.css"
]
},
{
"outputFileName": "wwwroot/js/site.min.js",
"inputFiles": [
"wwwroot/js/site.js"
],
// Optionally specify minification options
"minify": {
"enabled": true,
"renameLocals": true
},
// Optionally generate .map file
"sourceMap": false
}
]

View File

@@ -1,65 +0,0 @@
// Colors
$color-brand: #00A69C;
$color-brand-dark: darken($color-brand, 10%);
$color-brand-darker: darken($color-brand, 20%);
$color-brand-bright: lighten($color-brand, 10%);
$color-brand-brighter: lighten($color-brand, 20%);
$color-secondary: #83D01B;
$color-secondary-dark: darken($color-secondary, 5%);
$color-secondary-darker: darken($color-secondary, 20%);
$color-secondary-bright: lighten($color-secondary, 10%);
$color-secondary-brighter: lighten($color-secondary, 20%);
$color-warning: #ff0000;
$color-warning-dark: darken($color-warning, 5%);
$color-warning-darker: darken($color-warning, 20%);
$color-warning-bright: lighten($color-warning, 10%);
$color-warning-brighter: lighten($color-warning, 20%);
$color-background-dark: #333333;
$color-background-darker: #000000;
$color-background-bright: #EEEEFF;
$color-background-brighter: #FFFFFF;
$color-foreground-dark: #333333;
$color-foreground-darker: #000000;
$color-foreground-bright: #EEEEEE;
$color-foreground-brighter: #FFFFFF;
// Animations
$animation-speed-default: .35s;
$animation-speed-slow: .5s;
$animation-speed-fast: .15s;
// Fonts
$font-weight-light: 200;
$font-weight-semilight: 300;
$font-weight-normal: 400;
$font-weight-semibold: 600;
$font-weight-bold: 700;
$font-size-xs: .65rem; // 10.4px
$font-size-s: .85rem; // 13.6px
$font-size-m: 1rem; // 16px
$font-size-l: 1.25rem; // 20px
$font-size-xl: 1.5rem; // 24px
// Medias
$media-screen-xxs: 360px;
$media-screen-xs: 640px;
$media-screen-s: 768px;
$media-screen-m: 1024px;
$media-screen-l: 1280px;
$media-screen-xl: 1440px;
$media-screen-xxl: 1680px;
$media-screen-xxxl: 1920px;
// Borders
$border-light: 1px;
// Images
$image_path: '../../images/';
$image-main_banner: '#{$image_path}main_banner.png';
$image-arrow_down: '#{$image_path}arrow-down.png';

View File

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

View File

@@ -1 +0,0 @@
.esh-app-footer{background-color:#000;border-top:1px solid #eee;margin-top:2.5rem;padding-bottom:2.5rem;padding-top:2.5rem;width:100%}.esh-app-footer-brand{height:50px;width:230px}

View File

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

View File

@@ -1,86 +0,0 @@
@font-face {
font-family: Montserrat;
font-weight: 400;
src: url(".../fonts/Montserrat-Regular.eot?") format("eot"), url("../fonts/Montserrat-Regular.woff") format("woff"), url("../fonts/Montserrat-Regular.ttf") format("truetype"), url("../fonts/Montserrat-Regular.svg#Montserrat") format("svg");
}
@font-face {
font-family: Montserrat;
font-weight: 700;
src: url("../fonts/Montserrat-Bold.eot?") format("eot"), url("../fonts/Montserrat-Bold.woff") format("woff"), url("../fonts/Montserrat-Bold.ttf") format("truetype"), url("../fonts/Montserrat-Bold.svg#Montserrat") format("svg");
}
html,
body {
font-family: Montserrat, sans-serif;
font-size: 16px;
font-weight: 400;
z-index: 10;
}
*,
*::after,
*::before {
box-sizing: border-box;
}
.preloading {
color: #00A69C;
display: block;
font-size: 1.5rem;
left: 50%;
position: fixed;
top: 50%;
transform: translate(-50%, -50%);
}
select::-ms-expand {
display: none;
}
@media screen and (min-width: 992px) {
.form-input {
max-width: 360px;
width: 360px;
}
}
.form-input {
border-radius: 0;
height: 45px;
padding: 10px;
}
.form-input-small {
max-width: 100px !important;
}
.form-input-medium {
width: 150px !important;
}
.alert {
padding-left: 0;
}
.alert-danger {
background-color: transparent;
border: 0;
color: #FB0D0D;
font-size: 12px;
}
a,
a:active,
a:hover,
a:visited {
color: #000;
text-decoration: none;
transition: color 0.35s;
}
a:hover,
a:active {
color: #75B918;
transition: color 0.35s;
}

View File

@@ -1 +0,0 @@
@font-face{font-family:Montserrat;font-weight:400;src:url("../fonts/Montserrat-Regular.eot?") format("eot"),url("../fonts/Montserrat-Regular.woff") format("woff"),url("../fonts/Montserrat-Regular.ttf") format("truetype"),url("../fonts/Montserrat-Regular.svg#Montserrat") format("svg")}@font-face{font-family:Montserrat;font-weight:700;src:url("../fonts/Montserrat-Bold.eot?") format("eot"),url("../fonts/Montserrat-Bold.woff") format("woff"),url("../fonts/Montserrat-Bold.ttf") format("truetype"),url("../fonts/Montserrat-Bold.svg#Montserrat") format("svg")}html,body{font-family:Montserrat,sans-serif;font-size:16px;font-weight:400;z-index:10}*,*::after,*::before{box-sizing:border-box}.preloading{color:#00a69c;display:block;font-size:1.5rem;left:50%;position:fixed;top:50%;transform:translate(-50%,-50%)}select::-ms-expand{display:none}@media screen and (min-width:992px){.form-input{max-width:360px;width:360px}}.form-input{border-radius:0;height:45px;padding:10px}.form-input-small{max-width:100px !important}.form-input-medium{width:150px !important}.alert{padding-left:0}.alert-danger{background-color:transparent;border:0;color:#fb0d0d;font-size:12px}a,a:active,a:hover,a:visited{color:#000;text-decoration:none;transition:color .35s}a:hover,a:active{color:#75b918;transition:color .35s}

View File

@@ -1,43 +0,0 @@
.esh-basketstatus {
cursor: pointer;
display: inline-block;
float: right;
position: relative;
transition: all 0.35s; }
.esh-basketstatus.is-disabled {
opacity: .5;
pointer-events: none; }
.esh-basketstatus-image {
height: 36px;
margin-top: .5rem; }
.esh-basketstatus-badge {
background-color: #83D01B;
border-radius: 50%;
color: #FFFFFF;
display: block;
height: 1.5rem;
left: 50%;
position: absolute;
text-align: center;
top: 0;
transform: translateX(-38%);
transition: all 0.35s;
width: 1.5rem; }
.esh-basketstatus-badge-inoperative {
background-color: #ff0000;
border-radius: 50%;
color: #FFFFFF;
display: block;
height: 1.5rem;
left: 50%;
position: absolute;
text-align: center;
top: 0;
transform: translateX(-38%);
transition: all 0.35s;
width: 1.5rem; }
.esh-basketstatus:hover .esh-basketstatus-badge {
background-color: transparent;
color: #75b918;
transition: all 0.35s; }

View File

@@ -1 +0,0 @@
.esh-basketstatus{cursor:pointer;display:inline-block;float:right;position:relative;transition:all .35s}.esh-basketstatus.is-disabled{opacity:.5;pointer-events:none}.esh-basketstatus-image{height:36px;margin-top:.5rem}.esh-basketstatus-badge{background-color:#83d01b;border-radius:50%;color:#fff;display:block;height:1.5rem;left:50%;position:absolute;text-align:center;top:0;transform:translateX(-38%);transition:all .35s;width:1.5rem}.esh-basketstatus-badge-inoperative{background-color:#f00;border-radius:50%;color:#fff;display:block;height:1.5rem;left:50%;position:absolute;text-align:center;top:0;transform:translateX(-38%);transition:all .35s;width:1.5rem}.esh-basketstatus:hover .esh-basketstatus-badge{background-color:transparent;color:#75b918;transition:all .35s}

View File

@@ -1,57 +0,0 @@
@import '../../variables';
.esh-basketstatus {
cursor: pointer;
display: inline-block;
float: right;
position: relative;
transition: all $animation-speed-default;
&.is-disabled {
opacity: .5;
pointer-events: none;
}
&-image {
height: 36px;
margin-top: .5rem;
}
&-badge {
$size: 1.5rem;
background-color: $color-secondary;
border-radius: 50%;
color: $color-foreground-brighter;
display: block;
height: $size;
left: 50%;
position: absolute;
text-align: center;
top: 0;
transform: translateX(-38%);
transition: all $animation-speed-default;
width: $size;
}
&-badge-inoperative {
$size: 1.5rem;
background-color: $color-warning;
border-radius: 50%;
color: $color-foreground-brighter;
display: block;
height: $size;
left: 50%;
position: absolute;
text-align: center;
top: 0;
transform: translateX(-38%);
transition: all $animation-speed-default;
width: $size;
}
&:hover &-badge {
background-color: transparent;
color: $color-secondary-dark;
transition: all $animation-speed-default;
}
}

View File

@@ -1,49 +0,0 @@
.esh-basket {
min-height: 80vh; }
.esh-basket-titles {
padding-bottom: 1rem;
padding-top: 2rem; }
.esh-basket-titles--clean {
padding-bottom: 0;
padding-top: 0; }
.esh-basket-title {
text-transform: uppercase; }
.esh-basket-items--border {
border-bottom: 1px solid #EEEEEE;
padding: .5rem 0; }
.esh-basket-items--border:last-of-type {
border-color: transparent; }
.esh-basket-items-margin-left1 {
margin-left: 1px; }
.esh-basket-item {
font-size: 1rem;
font-weight: 300; }
.esh-basket-item--middle {
line-height: 8rem; }
@media screen and (max-width: 1024px) {
.esh-basket-item--middle {
line-height: 1rem; } }
.esh-basket-item--mark {
color: #00A69C; }
.esh-basket-image {
height: 8rem; }
.esh-basket-input {
line-height: 1rem;
width: 100%; }
.esh-basket-checkout {
background-color: #83D01B;
border: 0;
border-radius: 0;
color: #FFFFFF;
display: inline-block;
font-size: 1rem;
font-weight: 400;
margin-top: 1rem;
padding: 1rem 1.5rem;
text-align: center;
text-transform: uppercase;
transition: all 0.35s; }
.esh-basket-checkout:hover {
background-color: #4a760f;
transition: all 0.35s; }

View File

@@ -1 +0,0 @@
.esh-basket{min-height:80vh}.esh-basket-titles{padding-bottom:1rem;padding-top:2rem}.esh-basket-titles--clean{padding-bottom:0;padding-top:0}.esh-basket-title{text-transform:uppercase}.esh-basket-items--border{border-bottom:1px solid #eee;padding:.5rem 0}.esh-basket-items--border:last-of-type{border-color:transparent}.esh-basket-items-margin-left1{margin-left:1px}.esh-basket-item{font-size:1rem;font-weight:300}.esh-basket-item--middle{line-height:8rem}@media screen and (max-width:1024px){.esh-basket-item--middle{line-height:1rem}}.esh-basket-item--mark{color:#00a69c}.esh-basket-image{height:8rem}.esh-basket-input{line-height:1rem;width:100%}.esh-basket-checkout{background-color:#83d01b;border:0;border-radius:0;color:#fff;display:inline-block;font-size:1rem;font-weight:400;margin-top:1rem;padding:1rem 1.5rem;text-align:center;text-transform:uppercase;transition:all .35s}.esh-basket-checkout:hover{background-color:#4a760f;transition:all .35s}

View File

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

View File

@@ -1,117 +0,0 @@
.esh-catalog-hero {
background-image: url("../../images/main_banner.png");
background-size: cover;
height: 260px;
width: 100%; }
.esh-catalog-title {
position: relative;
top: 74.28571px; }
.esh-catalog-filters {
background-color: #00A69C;
height: 65px; }
.esh-catalog-filter {
-webkit-appearance: none;
background-color: transparent;
border-color: #00d9cc;
color: #FFFFFF;
cursor: pointer;
margin-right: 1rem;
margin-top: .5rem;
min-width: 140px;
outline-color: #83D01B;
padding-bottom: 0;
padding-left: 0.5rem;
padding-right: 0.5rem;
padding-top: 1.5rem; }
.esh-catalog-filter option {
background-color: #00A69C; }
.esh-catalog-label {
display: inline-block;
position: relative;
z-index: 0; }
.esh-catalog-label::before {
color: rgba(255, 255, 255, 0.5);
content: attr(data-title);
font-size: 0.65rem;
margin-left: 0.5rem;
margin-top: 0.65rem;
position: absolute;
text-transform: uppercase;
z-index: 1; }
.esh-catalog-label::after {
background-image: url("../../images/arrow-down.png");
content: '';
height: 7px;
position: absolute;
right: 1.5rem;
top: 2.5rem;
width: 10px;
z-index: 1; }
.esh-catalog-send {
background-color: #83D01B;
color: #FFFFFF;
cursor: pointer;
font-size: 1rem;
margin-top: -1.5rem;
padding: 0.5rem;
transition: all 0.35s; }
.esh-catalog-send:hover {
background-color: #4a760f;
transition: all 0.35s; }
.esh-catalog-items {
margin-top: 1rem; }
.esh-catalog-item {
margin-bottom: 1.5rem;
text-align: center;
width: 33%;
display: inline-block;
float: none !important; }
@media screen and (max-width: 1024px) {
.esh-catalog-item {
width: 50%; } }
@media screen and (max-width: 768px) {
.esh-catalog-item {
width: 100%; } }
.esh-catalog-thumbnail {
max-width: 370px;
width: 100%; }
.esh-catalog-button {
background-color: #83D01B;
border: 0;
color: #FFFFFF;
cursor: pointer;
font-size: 1rem;
height: 3rem;
margin-top: 1rem;
transition: all 0.35s;
width: 80%; }
.esh-catalog-button.is-disabled {
opacity: .5;
pointer-events: none; }
.esh-catalog-button:hover {
background-color: #4a760f;
transition: all 0.35s; }
.esh-catalog-name {
font-size: 1rem;
font-weight: 300;
margin-top: .5rem;
text-align: center;
text-transform: uppercase; }
.esh-catalog-price {
font-size: 28px;
font-weight: 900;
text-align: center; }
.esh-catalog-price::before {
content: '$'; }

View File

@@ -1 +0,0 @@
.esh-catalog-hero{background-image:url("../../images/main_banner.png");background-size:cover;height:260px;width:100%}.esh-catalog-title{position:relative;top:74.28571px}.esh-catalog-filters{background-color:#00a69c;height:65px}.esh-catalog-filter{-webkit-appearance:none;background-color:transparent;border-color:#00d9cc;color:#fff;cursor:pointer;margin-right:1rem;margin-top:.5rem;min-width:140px;outline-color:#83d01b;padding-bottom:0;padding-left:.5rem;padding-right:.5rem;padding-top:1.5rem}.esh-catalog-filter option{background-color:#00a69c}.esh-catalog-label{display:inline-block;position:relative;z-index:0}.esh-catalog-label::before{color:rgba(255,255,255,.5);content:attr(data-title);font-size:.65rem;margin-left:.5rem;margin-top:.65rem;position:absolute;text-transform:uppercase;z-index:1}.esh-catalog-label::after{background-image:url("../../images/arrow-down.png");content:'';height:7px;position:absolute;right:1.5rem;top:2.5rem;width:10px;z-index:1}.esh-catalog-send{background-color:#83d01b;color:#fff;cursor:pointer;font-size:1rem;margin-top:-1.5rem;padding:.5rem;transition:all .35s}.esh-catalog-send:hover{background-color:#4a760f;transition:all .35s}.esh-catalog-items{margin-top:1rem}.esh-catalog-item{margin-bottom:1.5rem;text-align:center;width:33%;display:inline-block;float:none !important}@media screen and (max-width:1024px){.esh-catalog-item{width:50%}}@media screen and (max-width:768px){.esh-catalog-item{width:100%}}.esh-catalog-thumbnail{max-width:370px;width:100%}.esh-catalog-button{background-color:#83d01b;border:0;color:#fff;cursor:pointer;font-size:1rem;height:3rem;margin-top:1rem;transition:all .35s;width:80%}.esh-catalog-button.is-disabled{opacity:.5;pointer-events:none}.esh-catalog-button:hover{background-color:#4a760f;transition:all .35s}.esh-catalog-name{font-size:1rem;font-weight:300;margin-top:.5rem;text-align:center;text-transform:uppercase}.esh-catalog-price{font-size:28px;font-weight:900;text-align:center}.esh-catalog-price::before{content:'$'}

View File

@@ -1,154 +0,0 @@
@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: '$';
}
}
}

View File

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

View File

@@ -1,50 +0,0 @@
.esh-orders {
min-height: 80vh;
overflow-x: hidden; }
.esh-orders-header {
background-color: #00A69C;
height: 4rem; }
.esh-orders-back {
color: rgba(255, 255, 255, 0.4);
line-height: 4rem;
text-decoration: none;
text-transform: uppercase;
transition: color 0.35s; }
.esh-orders-back:hover {
color: #FFFFFF;
transition: color 0.35s; }
.esh-orders-titles {
padding-bottom: 1rem;
padding-top: 2rem; }
.esh-orders-title {
text-transform: uppercase; }
.esh-orders-items {
height: 2rem;
line-height: 2rem;
position: relative; }
.esh-orders-items:nth-of-type(2n + 1):before {
background-color: #EEEEFF;
content: '';
height: 100%;
left: 0;
margin-left: -100vw;
position: absolute;
top: 0;
width: 200vw;
z-index: -1; }
.esh-orders-item {
font-weight: 300; }
.esh-orders-item--hover {
opacity: 0;
pointer-events: none; }
.esh-orders-items:hover .esh-orders-item--hover {
opacity: 1;
pointer-events: all; }
.esh-orders-link {
color: #83D01B;
text-decoration: none;
transition: color 0.35s; }
.esh-orders-link:hover {
color: #75b918;
transition: color 0.35s; }

View File

@@ -1 +0,0 @@
.esh-orders{min-height:80vh;overflow-x:hidden}.esh-orders-header{background-color:#00a69c;height:4rem}.esh-orders-back{color:rgba(255,255,255,.4);line-height:4rem;text-decoration:none;text-transform:uppercase;transition:color .35s}.esh-orders-back:hover{color:#fff;transition:color .35s}.esh-orders-titles{padding-bottom:1rem;padding-top:2rem}.esh-orders-title{text-transform:uppercase}.esh-orders-items{height:2rem;line-height:2rem;position:relative}.esh-orders-items:nth-of-type(2n+1):before{background-color:#eef;content:'';height:100%;left:0;margin-left:-100vw;position:absolute;top:0;width:200vw;z-index:-1}.esh-orders-item{font-weight:300}.esh-orders-item--hover{opacity:0;pointer-events:none}.esh-orders-items:hover .esh-orders-item--hover{opacity:1;pointer-events:all}.esh-orders-link{color:#83d01b;text-decoration:none;transition:color .35s}.esh-orders-link:hover{color:#75b918;transition:color .35s}

Some files were not shown because too many files have changed in this diff Show More