168 changed files with 0 additions and 31616 deletions
@ -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 +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 changed in this diff
Loading…
Reference in new issue