Cleaning up separate WebRazorPages proj
This commit is contained in:
@@ -1,3 +0,0 @@
|
|||||||
@{
|
|
||||||
Layout = "/Pages/Shared/_Layout.cshtml";
|
|
||||||
}
|
|
||||||
@@ -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";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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"]
|
|
||||||
@@ -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>.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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>
|
|
||||||
@@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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" />
|
|
||||||
}
|
|
||||||
@@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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" />
|
|
||||||
}
|
|
||||||
@@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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" />
|
|
||||||
}
|
|
||||||
@@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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>
|
|
||||||
@@ -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");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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&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" />
|
|
||||||
}
|
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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> </text><code>@Model.RecoveryCodes[row + 1]</code><br />
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
@@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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" />
|
|
||||||
}
|
|
||||||
@@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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>
|
|
||||||
@@ -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");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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" />
|
|
||||||
}
|
|
||||||
@@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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" />
|
|
||||||
}
|
|
||||||
@@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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)
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -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>
|
|
||||||
|
|
||||||
@@ -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">×</span></button>
|
|
||||||
@Model
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
@using Microsoft.eShopWeb.RazorPages.Pages.Account.Manage
|
|
||||||
@@ -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"> REGISTER </button>
|
|
||||||
</div>
|
|
||||||
<p>
|
|
||||||
Note that for demo purposes you don't need to register! Use the credentials shown below the
|
|
||||||
<a asp-action="signin">login screen</a>.
|
|
||||||
</p>
|
|
||||||
</form>
|
|
||||||
</section>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
@section Scripts {
|
|
||||||
@{ await Html.RenderPartialAsync("_ValidationScriptsPartial"); }
|
|
||||||
}
|
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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"> LOG IN </button>
|
|
||||||
</div>
|
|
||||||
<p>
|
|
||||||
<a asp-page="/Account/Register"
|
|
||||||
asp-route-returnurl="@ViewData["ReturnUrl"]" class="text">Register as a new user?</a>
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
Note that for demo purposes you don't need to register and can login with these credentials:
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
User: <b>demouser@microsoft.com</b>
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
Password: <b>Pass@word1</b>
|
|
||||||
</p>
|
|
||||||
</form>
|
|
||||||
</section>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
@section Scripts {
|
|
||||||
@{ await Html.RenderPartialAsync("_ValidationScriptsPartial"); }
|
|
||||||
}
|
|
||||||
@@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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>
|
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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>
|
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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>
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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>
|
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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>
|
|
||||||
@@ -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()
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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>
|
|
||||||
@@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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>
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
|
||||||
|
|
||||||
namespace Microsoft.eShopWeb.RazorPages.Pages
|
|
||||||
{
|
|
||||||
public class PrivacyModel : PageModel
|
|
||||||
{
|
|
||||||
public void OnGet()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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>
|
|
||||||
@@ -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>
|
|
||||||
}
|
|
||||||
@@ -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>
|
|
||||||
@@ -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>
|
|
||||||
}
|
|
||||||
@@ -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>
|
|
||||||
@@ -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>
|
|
||||||
|
|
||||||
@@ -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>
|
|
||||||
@@ -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
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
@{
|
|
||||||
Layout = "_Layout";
|
|
||||||
}
|
|
||||||
@@ -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>();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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>()
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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());
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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>
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
{
|
|
||||||
"Logging": {
|
|
||||||
"LogLevel": {
|
|
||||||
"Default": "Debug",
|
|
||||||
"System": "Information",
|
|
||||||
"Microsoft": "Information"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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": "*"
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
]
|
|
||||||
@@ -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';
|
|
||||||
@@ -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; }
|
|
||||||
|
|
||||||
@@ -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}
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
1
src/WebRazorPages/wwwroot/css/app.min.css
vendored
1
src/WebRazorPages/wwwroot/css/app.min.css
vendored
@@ -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}
|
|
||||||
@@ -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; }
|
|
||||||
|
|
||||||
@@ -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}
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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; }
|
|
||||||
|
|
||||||
@@ -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}
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -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: '$'; }
|
|
||||||
|
|
||||||
@@ -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:'$'}
|
|
||||||
@@ -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: '$';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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; }
|
|
||||||
|
|
||||||
@@ -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
Reference in New Issue
Block a user