mirror of https://github.com/Squidex/squidex.git
23 changed files with 535 additions and 73 deletions
@ -0,0 +1,21 @@ |
|||
// ==========================================================================
|
|||
// ErrorController.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using Microsoft.AspNetCore.Mvc; |
|||
|
|||
namespace Squidex.Controllers.UI.Error |
|||
{ |
|||
public class ErrorController : Controller |
|||
{ |
|||
[Route("error")] |
|||
public IActionResult Error() |
|||
{ |
|||
return View(); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,24 @@ |
|||
// ==========================================================================
|
|||
// ChangePasswordModel.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System.ComponentModel.DataAnnotations; |
|||
|
|||
namespace Squidex.Controllers.UI.Profile |
|||
{ |
|||
public class ChangePasswordModel |
|||
{ |
|||
[Required(ErrorMessage = "Old Password is required.")] |
|||
public string OldPassword { get; set; } |
|||
|
|||
[Required(ErrorMessage = "Password is required.")] |
|||
public string Password { get; set; } |
|||
|
|||
[Compare(nameof(Password), ErrorMessage = "Passwords must be identitical.")] |
|||
public string PasswordConfirm { get; set; } |
|||
} |
|||
} |
|||
@ -0,0 +1,22 @@ |
|||
// ==========================================================================
|
|||
// ChangeProfileModel.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System.ComponentModel.DataAnnotations; |
|||
|
|||
namespace Squidex.Controllers.UI.Profile |
|||
{ |
|||
public class ChangeProfileModel |
|||
{ |
|||
[Required(ErrorMessage = "Email is required.")] |
|||
[EmailAddress(ErrorMessage = "Email is not valid.")] |
|||
public string Email { get; set; } |
|||
|
|||
[Required(ErrorMessage = "DisplayName is required.")] |
|||
public string DisplayName { get; set; } |
|||
} |
|||
} |
|||
@ -0,0 +1,123 @@ |
|||
// ==========================================================================
|
|||
// ProfileController.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Linq; |
|||
using System.Threading.Tasks; |
|||
using Microsoft.AspNetCore.Authorization; |
|||
using Microsoft.AspNetCore.Identity; |
|||
using Microsoft.AspNetCore.Mvc; |
|||
using Microsoft.Extensions.Options; |
|||
using NSwag.Annotations; |
|||
using Squidex.Config.Identity; |
|||
using Squidex.Infrastructure.Reflection; |
|||
using Squidex.Read.Users; |
|||
|
|||
namespace Squidex.Controllers.UI.Profile |
|||
{ |
|||
[Authorize] |
|||
[SwaggerIgnore] |
|||
public class ProfileController : Controller |
|||
{ |
|||
private readonly UserManager<IUser> userManager; |
|||
private readonly IOptions<MyIdentityOptions> identityOptions; |
|||
|
|||
public ProfileController(UserManager<IUser> userManager, IOptions<MyIdentityOptions> identityOptions) |
|||
{ |
|||
this.userManager = userManager; |
|||
this.identityOptions = identityOptions; |
|||
} |
|||
|
|||
[HttpGet] |
|||
[Route("/account/profile")] |
|||
public async Task<IActionResult> Profile(string successMessage = null) |
|||
{ |
|||
var user = await userManager.GetUserAsync(User); |
|||
|
|||
ViewBag.SuccessMessage = successMessage; |
|||
|
|||
return View(await GetProfileVM(user)); |
|||
} |
|||
|
|||
[HttpPost] |
|||
[Route("/account/profile")] |
|||
public Task<IActionResult> Profile(ChangeProfileModel model) |
|||
{ |
|||
return MakeChangeAsync(async user => |
|||
{ |
|||
user.UpdateEmail(model.Email); |
|||
user.SetDisplayName(model.DisplayName); |
|||
|
|||
return await userManager.UpdateAsync(user); |
|||
}, "Account updated successfully. Please logout and login again to see the changes."); |
|||
} |
|||
|
|||
[HttpPost] |
|||
[Route("/account/setpassword")] |
|||
public Task<IActionResult> SetPassword(SetPasswordModel model) |
|||
{ |
|||
return MakeChangeAsync(user => userManager.AddPasswordAsync(user, model.Password), |
|||
"Password set successfully."); |
|||
} |
|||
|
|||
[HttpPost] |
|||
[Route("/account/changepassword")] |
|||
public Task<IActionResult> ChangePassword(ChangePasswordModel model) |
|||
{ |
|||
return MakeChangeAsync(user => userManager.ChangePasswordAsync(user, model.OldPassword, model.Password), |
|||
"Password changed successfully."); |
|||
} |
|||
|
|||
private async Task<IActionResult> MakeChangeAsync(Func<IUser, Task<IdentityResult>> action, string successMessage, ChangeProfileModel model = null) |
|||
{ |
|||
var user = await userManager.GetUserAsync(User); |
|||
|
|||
if (!ModelState.IsValid) |
|||
{ |
|||
return View("Profile", await GetProfileVM(user, model)); |
|||
} |
|||
|
|||
try |
|||
{ |
|||
var result = await action(user); |
|||
|
|||
if (result.Succeeded) |
|||
{ |
|||
return RedirectToAction(nameof(Profile), new { successMessage }); |
|||
} |
|||
|
|||
ViewBag.ErrorMessage = string.Join(". ", result.Errors.Select(x => x.Description)); |
|||
} |
|||
catch |
|||
{ |
|||
ViewBag.ErrorMessage = "An unexpected exception occurred."; |
|||
} |
|||
|
|||
return View("Profile", await GetProfileVM(user, model)); |
|||
} |
|||
|
|||
private async Task<ProfileVM> GetProfileVM(IUser user, ChangeProfileModel model = null) |
|||
{ |
|||
var result = new ProfileVM |
|||
{ |
|||
Email = user.Email, |
|||
DisplayName = user.DisplayName(), |
|||
PictureUrl = user.PictureUrl(), |
|||
HasPassword = await userManager.HasPasswordAsync(user), |
|||
HasPasswordAuth = identityOptions.Value.AllowPasswordAuth |
|||
}; |
|||
|
|||
if (model != null) |
|||
{ |
|||
SimpleMapper.Map(model, result); |
|||
} |
|||
|
|||
return result; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,23 @@ |
|||
// ==========================================================================
|
|||
// ProfileVM.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
namespace Squidex.Controllers.UI.Profile |
|||
{ |
|||
public sealed class ProfileVM |
|||
{ |
|||
public string Email { get; set; } |
|||
|
|||
public string DisplayName { get; set; } |
|||
|
|||
public string PictureUrl { get; set; } |
|||
|
|||
public bool HasPassword { get; set; } |
|||
|
|||
public bool HasPasswordAuth { get; set; } |
|||
} |
|||
} |
|||
@ -0,0 +1,21 @@ |
|||
// ==========================================================================
|
|||
// SetPasswordModel.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System.ComponentModel.DataAnnotations; |
|||
|
|||
namespace Squidex.Controllers.UI.Profile |
|||
{ |
|||
public class SetPasswordModel |
|||
{ |
|||
[Required(ErrorMessage = "Password is required.")] |
|||
public string Password { get; set; } |
|||
|
|||
[Compare(nameof(Password), ErrorMessage = "Passwords must be identitical.")] |
|||
public string PasswordConfirm { get; set; } |
|||
} |
|||
} |
|||
@ -1,9 +0,0 @@ |
|||
@{ |
|||
ViewBag.Title = "Login failed"; |
|||
} |
|||
|
|||
<h1 class="splash-h1">Login failed</h1> |
|||
|
|||
<p class="splash-text"> |
|||
We are really sorry, something went wrong when you tried to login. |
|||
</p> |
|||
@ -0,0 +1,9 @@ |
|||
@{ |
|||
ViewBag.Title = "Operation failed"; |
|||
} |
|||
|
|||
<h1 class="splash-h1">Operation failed</h1> |
|||
|
|||
<p class="splash-text"> |
|||
We are really sorry that something went wrong. |
|||
</p> |
|||
@ -0,0 +1,155 @@ |
|||
@using System.Collections.Generic |
|||
@using Microsoft.AspNetCore.Http |
|||
@using Microsoft.AspNetCore.Http.Authentication |
|||
@model Squidex.Controllers.UI.Profile.ProfileVM |
|||
|
|||
@{ |
|||
ViewBag.Title = "Profile"; |
|||
} |
|||
|
|||
<div class="profile profile-lg"> |
|||
<img class="profile-logo" src="~/images/logo-small.png" /> |
|||
|
|||
<div> |
|||
<h4 class="profile-title">Welcome, @Model.DisplayName</h4> |
|||
</div> |
|||
|
|||
@if (!string.IsNullOrWhiteSpace(ViewBag.SuccessMessage)) |
|||
{ |
|||
<div class="form-success"> |
|||
@ViewBag.SuccessMessage |
|||
</div> |
|||
} |
|||
|
|||
@if (!string.IsNullOrWhiteSpace(ViewBag.ErrorMessage)) |
|||
{ |
|||
<div class="form-error"> |
|||
@ViewBag.ErrorMessage |
|||
</div> |
|||
} |
|||
|
|||
<div class="row"> |
|||
<div class="col profile-picture-col"> |
|||
<img class="profile-picture" src="@Model.PictureUrl" /> |
|||
</div> |
|||
<div class="col"> |
|||
<form class="profile-form" asp-controller="Profile" asp-action="Profile" method="post"> |
|||
<div class="form-group"> |
|||
<label for="email">Email</label> |
|||
|
|||
@if (ViewContext.ViewData.ModelState["Email"]?.ValidationState == Microsoft.AspNetCore.Mvc.ModelBinding.ModelValidationState.Invalid) |
|||
{ |
|||
<div class="errors-box"> |
|||
<span asp-validation-for="Email" class="errors"></span> |
|||
</div> |
|||
} |
|||
|
|||
<input type="email" ap class="form-control" asp-for="Email" name="email" id="email" /> |
|||
</div> |
|||
|
|||
<div class="form-group"> |
|||
<label for="displayName">Display Name</label> |
|||
|
|||
|
|||
@if (ViewContext.ViewData.ModelState["DisplayName"]?.ValidationState == Microsoft.AspNetCore.Mvc.ModelBinding.ModelValidationState.Invalid) |
|||
{ |
|||
<div class="errors-box"> |
|||
<span asp-validation-for="DisplayName" class="errors"></span> |
|||
</div> |
|||
} |
|||
|
|||
<input type="text" class="form-control" asp-for="DisplayName" name="displayName" id="displayName"/> |
|||
</div> |
|||
|
|||
<button type="submit" class="btn btn-primary">Save</button> |
|||
</form> |
|||
</div> |
|||
</div> |
|||
|
|||
@if (Model.HasPasswordAuth) |
|||
{ |
|||
<h5 class="profile-section">Password</h5> |
|||
|
|||
@if (Model.HasPassword) |
|||
{ |
|||
<form class="profile-form" asp-controller="Profile" asp-action="ChangePassword" method="post"> |
|||
<div class="form-group"> |
|||
<label for="oldPassword">Old Password</label> |
|||
|
|||
@if (ViewContext.ViewData.ModelState["OldPassword"]?.ValidationState == Microsoft.AspNetCore.Mvc.ModelBinding.ModelValidationState.Invalid) |
|||
{ |
|||
<div class="errors-box"> |
|||
<span class="errors">@Html.ValidationMessage("OldPassword")</span> |
|||
</div> |
|||
} |
|||
|
|||
<input type="password" class="form-control" name="oldPassword" id="oldPassword"/> |
|||
</div> |
|||
|
|||
<div class="form-group"> |
|||
<label for="password">Password</label> |
|||
|
|||
@if (ViewContext.ViewData.ModelState["Password"]?.ValidationState == Microsoft.AspNetCore.Mvc.ModelBinding.ModelValidationState.Invalid) |
|||
{ |
|||
<div class="errors-box"> |
|||
<span class="errors">@Html.ValidationMessage("Password")</span> |
|||
</div> |
|||
} |
|||
|
|||
<input type="password" class="form-control" name="password" id="password"/> |
|||
</div> |
|||
|
|||
<div class="form-group"> |
|||
<label for="passwordConfirm">Confirm</label> |
|||
|
|||
@if (ViewContext.ViewData.ModelState["PasswordConfirm"]?.ValidationState == Microsoft.AspNetCore.Mvc.ModelBinding.ModelValidationState.Invalid) |
|||
{ |
|||
<div class="errors-box"> |
|||
<span class="errors">@Html.ValidationMessage("PasswordConfirm")</span> |
|||
</div> |
|||
} |
|||
|
|||
<input type="password" class="form-control" name="passwordConfirm" id="passwordConfirm"/> |
|||
</div> |
|||
|
|||
<div class="form-group"> |
|||
<button type="submit" class="btn btn-primary">Change Password</button> |
|||
</div> |
|||
</form> |
|||
} |
|||
else |
|||
{ |
|||
<form class="profile-form" asp-controller="Profile" asp-action="SetPassword" method="post"> |
|||
<div class="form-group"> |
|||
<label for="password">Password</label> |
|||
|
|||
@if (ViewContext.ViewData.ModelState["Password"]?.ValidationState == Microsoft.AspNetCore.Mvc.ModelBinding.ModelValidationState.Invalid) |
|||
{ |
|||
<div class="errors-box"> |
|||
<span class="errors">@Html.ValidationMessage("Password")</span> |
|||
</div> |
|||
} |
|||
|
|||
<input type="password" class="form-control" name="password" id="password"/> |
|||
</div> |
|||
|
|||
<div class="form-group"> |
|||
<label for="passwordConfirm">Confirm</label> |
|||
|
|||
@if (ViewContext.ViewData.ModelState["PasswordConfirm"]?.ValidationState == Microsoft.AspNetCore.Mvc.ModelBinding.ModelValidationState.Invalid) |
|||
{ |
|||
<div class="errors-box"> |
|||
<span class="errors">@Html.ValidationMessage("PasswordConfirm")</span> |
|||
</div> |
|||
} |
|||
|
|||
<input type="password" class="form-control" name="passwordConfirm" id="passwordConfirm"/> |
|||
</div> |
|||
|
|||
<div class="form-group"> |
|||
<button type="submit" class="btn btn-primary">Set Password</button> |
|||
</div> |
|||
</form> |
|||
} |
|||
} |
|||
</div> |
|||
@ -0,0 +1,23 @@ |
|||
/* |
|||
* Squidex Headless CMS |
|||
* |
|||
* @license |
|||
* Copyright (c) Sebastian Stehle. All rights reserved |
|||
*/ |
|||
|
|||
import { Directive, HostListener, Input } from '@angular/core'; |
|||
|
|||
@Directive({ |
|||
selector: '[sqxPopupLink]' |
|||
}) |
|||
export class PopupLinkDirective { |
|||
@Input('sqxPopupLink') |
|||
public url: string; |
|||
|
|||
@HostListener('click') |
|||
public onClick(): boolean { |
|||
window.open(this.url, '_target', 'location=no,toolbar=no,width=500,height=500,left=100,top=100;'); |
|||
|
|||
return false; |
|||
} |
|||
} |
|||
Loading…
Reference in new issue