Separated Profile and ProfilePicture, added FullName form
This commit is contained in:
parent
611980d660
commit
1eeb1e3c13
|
@ -1,44 +1,49 @@
|
||||||
@page "/Account/Manage"
|
@page "/Account/Manage"
|
||||||
|
|
||||||
@using Microsoft.AspNetCore.Identity
|
@using Microsoft.AspNetCore.Identity
|
||||||
@using Microsoft.EntityFrameworkCore
|
|
||||||
@using Wave.Data
|
@using Wave.Data
|
||||||
@using Wave.Services
|
@using System.ComponentModel.DataAnnotations
|
||||||
|
|
||||||
@rendermode InteractiveServer
|
@inject IdentityUserAccessor UserAccessor
|
||||||
@inject NavigationManager Navigation
|
|
||||||
@inject UserManager<ApplicationUser> UserManager
|
@inject UserManager<ApplicationUser> UserManager
|
||||||
@inject IDbContextFactory<ApplicationDbContext> ContextFactory
|
@inject SignInManager<ApplicationUser> SignInManager
|
||||||
@inject ImageService ImageService
|
@inject IStringLocalizer<Index> Localizer
|
||||||
|
|
||||||
<PageTitle>Profile</PageTitle>
|
<PageTitle>Profile</PageTitle>
|
||||||
|
|
||||||
|
<StatusMessage Message="@Message" />
|
||||||
|
|
||||||
<div class="flex gap-4 flex-wrap">
|
<div class="flex gap-4 flex-wrap">
|
||||||
<section class="max-w-xs" Model="Input" FormName="profile" OnValidSubmit="OnValidSubmitAsync" method="post">
|
<section class="max-w-xs" Model="Input" FormName="profile" OnValidSubmit="OnValidSubmitAsync" method="post">
|
||||||
<h2 class="text-2xl lg:text-4xl mb-3">Profile</h2>
|
<h2 class="text-2xl lg:text-4xl mb-3">Profile</h2>
|
||||||
|
|
||||||
<label class="form-control w-full">
|
<EditForm FormName="update-profile" Model="@Model" OnValidSubmit="@SubmitFullNameUpdate" method="post" Enhance="false">
|
||||||
|
<DataAnnotationsValidator />
|
||||||
|
<label class="form-control w-full">
|
||||||
|
<div class="label">
|
||||||
|
<span class="label-text">@Localizer["FullName_Label"]</span>
|
||||||
|
</div>
|
||||||
|
<InputText class="input input-bordered w-full" maxlength="64" autocomplete="name"
|
||||||
|
@bind-Value="@Model.FullName" placeholder="@Localizer["FullName_Placeholder"]" />
|
||||||
|
<div class="label">
|
||||||
|
<span class="label-text-alt text-error">
|
||||||
|
<ValidationMessage For="() => Model.FullName" />
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</label>
|
||||||
|
<button type="submit" class="btn btn-primary w-full">
|
||||||
|
@Localizer["FullName_Submit"]
|
||||||
|
</button>
|
||||||
|
</EditForm>
|
||||||
|
|
||||||
|
<label class="form-control w-full">
|
||||||
<div class="label">
|
<div class="label">
|
||||||
<span class="label-text">Username</span>
|
<span class="label-text">Username</span>
|
||||||
</div>
|
</div>
|
||||||
<input class="input input-bordered w-full" type="text" value="@Username"
|
<input class="input input-bordered w-full" type="text" value="@UserName"
|
||||||
placeholder="Please choose your username." disabled/>
|
placeholder="Please choose your username." disabled/>
|
||||||
</label>
|
</label>
|
||||||
|
</section>
|
||||||
@if (User?.ProfilePicture is not null) {
|
|
||||||
<div class="avatar w-24 my-3">
|
|
||||||
<div class="rounded">
|
|
||||||
<img src="/images/@(User.ProfilePicture.ImageId)" alt="" loading="lazy"/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
<label class="form-control w-full">
|
|
||||||
<div class="label">
|
|
||||||
<span class="label-text">Profile Picture</span>
|
|
||||||
</div>
|
|
||||||
<FileUploadComponent FileUploadedCallback="ProfilePictureChanged"/>
|
|
||||||
</label>
|
|
||||||
</section>
|
|
||||||
<section>
|
<section>
|
||||||
<h2 class="text-2xl lg:text-4xl mb-3">Permissions</h2>
|
<h2 class="text-2xl lg:text-4xl mb-3">Permissions</h2>
|
||||||
<ul>
|
<ul>
|
||||||
|
@ -107,43 +112,30 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
private ApplicationUser? User { get; set; }
|
private string? Message { get; set; }
|
||||||
private string? Username { get; set; }
|
[SupplyParameterFromForm]
|
||||||
|
private InputModel Model { get; set; } = new();
|
||||||
[CascadingParameter]
|
[CascadingParameter]
|
||||||
private Task<AuthenticationState>? AuthenticationState { get; set; }
|
private HttpContext HttpContext { get; set; } = default!;
|
||||||
|
|
||||||
|
private ApplicationUser User { get; set; } = default!;
|
||||||
|
private string UserName { get; set; } = string.Empty;
|
||||||
|
|
||||||
protected override async Task OnInitializedAsync() {
|
protected override async Task OnInitializedAsync() {
|
||||||
if (AuthenticationState is not null) {
|
UserName = UserManager.GetUserName(HttpContext.User)!;
|
||||||
var state = await AuthenticationState;
|
User = await UserAccessor.GetRequiredUserAsync(HttpContext);
|
||||||
User = await UserManager.GetUserAsync(state.User);
|
Model.FullName ??= User.FullName;
|
||||||
Username = User.UserName;
|
|
||||||
await using var context = await ContextFactory.CreateDbContextAsync();
|
|
||||||
await context.Entry(User).Reference(u => u.ProfilePicture).LoadAsync();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task ProfilePictureChanged(string tempFilePath) {
|
private async Task SubmitFullNameUpdate() {
|
||||||
var guid = await ImageService.StoreImageAsync(tempFilePath);
|
User.FullName = Model.FullName?.Trim();
|
||||||
if (!guid.HasValue) throw new ApplicationException("Processing Image failed.");
|
await UserManager.UpdateAsync(User);
|
||||||
|
await SignInManager.RefreshSignInAsync(User);
|
||||||
Guid? imageToDelete = null;
|
Message = Localizer["FullName_Success"];
|
||||||
await using var context = await ContextFactory.CreateDbContextAsync();
|
|
||||||
if (User.ProfilePicture is not null) {
|
|
||||||
imageToDelete = User.ProfilePicture.ImageId;
|
|
||||||
context.Remove(User.ProfilePicture);
|
|
||||||
}
|
|
||||||
|
|
||||||
User.ProfilePicture = new ProfilePicture() {
|
|
||||||
ImageId = guid.Value
|
|
||||||
};
|
|
||||||
|
|
||||||
context.Update(User);
|
|
||||||
await context.SaveChangesAsync();
|
|
||||||
|
|
||||||
if (imageToDelete is not null)
|
|
||||||
ImageService.Delete(imageToDelete.Value);
|
|
||||||
await InvokeAsync(StateHasChanged);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private sealed class InputModel {
|
||||||
|
[MaxLength(64)]
|
||||||
|
public string? FullName { get; set; }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
65
Wave/Components/Account/Pages/Manage/ProfilePicture.razor
Normal file
65
Wave/Components/Account/Pages/Manage/ProfilePicture.razor
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
@page "/Account/Manage/ProfilePicture"
|
||||||
|
@using Wave.Data
|
||||||
|
@using Wave.Services
|
||||||
|
@using Microsoft.EntityFrameworkCore
|
||||||
|
@using Microsoft.AspNetCore.Identity
|
||||||
|
|
||||||
|
@rendermode InteractiveServer
|
||||||
|
@inject NavigationManager Navigation
|
||||||
|
@inject UserManager<ApplicationUser> UserManager
|
||||||
|
@inject IDbContextFactory<ApplicationDbContext> ContextFactory
|
||||||
|
@inject ImageService ImageService
|
||||||
|
@inject IStringLocalizer<ProfilePicture> Localizer
|
||||||
|
|
||||||
|
<PageTitle>@Localizer["Title"]</PageTitle>
|
||||||
|
|
||||||
|
<section class="max-w-xs" Model="Input" FormName="profile" OnValidSubmit="OnValidSubmitAsync" method="post">
|
||||||
|
<h2 class="text-2xl lg:text-4xl mb-3">@Localizer["Title"]</h2>
|
||||||
|
|
||||||
|
<div class="w-24 h-24">
|
||||||
|
<ProfilePictureComponent ProfileId="@User?.Id" />
|
||||||
|
</div>
|
||||||
|
<label class="form-control w-full">
|
||||||
|
<div class="label">
|
||||||
|
<span class="label-text">@Localizer["ProfilePicture_Label"]</span>
|
||||||
|
</div>
|
||||||
|
<FileUploadComponent FileUploadedCallback="ProfilePictureChanged"/>
|
||||||
|
</label>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
@code {
|
||||||
|
private ApplicationUser? User { get; set; }
|
||||||
|
[CascadingParameter]
|
||||||
|
private Task<AuthenticationState> AuthenticationState { get; set; } = default!;
|
||||||
|
|
||||||
|
protected override async Task OnInitializedAsync() {
|
||||||
|
var state = await AuthenticationState;
|
||||||
|
User = await UserManager.GetUserAsync(state.User);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task ProfilePictureChanged(string tempFilePath) {
|
||||||
|
if (User is null) return;
|
||||||
|
|
||||||
|
var guid = await ImageService.StoreImageAsync(tempFilePath);
|
||||||
|
if (!guid.HasValue) throw new ApplicationException("Processing Image failed.");
|
||||||
|
|
||||||
|
Guid? imageToDelete = null;
|
||||||
|
await using var context = await ContextFactory.CreateDbContextAsync();
|
||||||
|
await context.Entry(User).Reference(u => u.ProfilePicture).LoadAsync();
|
||||||
|
if (User.ProfilePicture is not null) {
|
||||||
|
imageToDelete = User.ProfilePicture.ImageId;
|
||||||
|
context.Remove(User.ProfilePicture);
|
||||||
|
}
|
||||||
|
|
||||||
|
User.ProfilePicture = new Data.ProfilePicture() {
|
||||||
|
ImageId = guid.Value
|
||||||
|
};
|
||||||
|
|
||||||
|
context.Update(User);
|
||||||
|
await context.SaveChangesAsync();
|
||||||
|
|
||||||
|
if (imageToDelete is not null)
|
||||||
|
ImageService.Delete(imageToDelete.Value);
|
||||||
|
Navigation.Refresh(true);
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,26 +4,15 @@
|
||||||
@inject SignInManager<ApplicationUser> SignInManager
|
@inject SignInManager<ApplicationUser> SignInManager
|
||||||
|
|
||||||
<ul class="menu" role="navigation">
|
<ul class="menu" role="navigation">
|
||||||
<li class="nav-item">
|
<li><NavLink href="Account/Manage" Match="NavLinkMatch.All">Profile</NavLink></li>
|
||||||
<NavLink href="Account/Manage" Match="NavLinkMatch.All">Profile</NavLink>
|
<li><NavLink href="Account/Manage/ProfilePicture">Profile Picture</NavLink></li>
|
||||||
</li>
|
<li><NavLink href="Account/Manage/Email">Email</NavLink></li>
|
||||||
<li class="nav-item">
|
<li><NavLink href="Account/Manage/ChangePassword">Password</NavLink></li>
|
||||||
<NavLink href="Account/Manage/Email">Email</NavLink>
|
|
||||||
</li>
|
|
||||||
<li class="nav-item">
|
|
||||||
<NavLink href="Account/Manage/ChangePassword">Password</NavLink>
|
|
||||||
</li>
|
|
||||||
@if (_hasExternalLogins) {
|
@if (_hasExternalLogins) {
|
||||||
<li class="nav-item">
|
<li><NavLink href="Account/Manage/ExternalLogins">External logins</NavLink></li>
|
||||||
<NavLink href="Account/Manage/ExternalLogins">External logins</NavLink>
|
|
||||||
</li>
|
|
||||||
}
|
}
|
||||||
<li class="nav-item">
|
<li><NavLink href="Account/Manage/TwoFactorAuthentication">Two-factor authentication</NavLink></li>
|
||||||
<NavLink href="Account/Manage/TwoFactorAuthentication">Two-factor authentication</NavLink>
|
<li><NavLink href="Account/Manage/PersonalData">Personal data</NavLink></li>
|
||||||
</li>
|
|
||||||
<li class="nav-item">
|
|
||||||
<NavLink href="Account/Manage/PersonalData">Personal data</NavLink>
|
|
||||||
</li>
|
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
|
|
|
@ -28,12 +28,10 @@
|
||||||
<Authorized>
|
<Authorized>
|
||||||
<li class="flex content-center gap-2">
|
<li class="flex content-center gap-2">
|
||||||
<NavLink href="Account/Manage">
|
<NavLink href="Account/Manage">
|
||||||
@context.User.Identity?.Name
|
@context.User.FindFirst("FullName")!.Value
|
||||||
@if (context.User.FindFirst(ClaimTypes.NameIdentifier)?.Value is { } id) {
|
<div class="w-8">
|
||||||
<div class="w-8">
|
<ProfilePictureComponent ProfileId="@context.User.FindFirst("Id")!.Value" />
|
||||||
<ProfilePictureComponent ProfileId="@id" />
|
</div>
|
||||||
</div>
|
|
||||||
}
|
|
||||||
</NavLink>
|
</NavLink>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
|
|
|
@ -48,7 +48,8 @@
|
||||||
.AddRoles<IdentityRole>()
|
.AddRoles<IdentityRole>()
|
||||||
.AddEntityFrameworkStores<ApplicationDbContext>()
|
.AddEntityFrameworkStores<ApplicationDbContext>()
|
||||||
.AddSignInManager()
|
.AddSignInManager()
|
||||||
.AddDefaultTokenProviders();
|
.AddDefaultTokenProviders()
|
||||||
|
.AddClaimsPrincipalFactory<UserClaimsFactory>();
|
||||||
|
|
||||||
builder.Services.AddSingleton<IEmailSender<ApplicationUser>, IdentityNoOpEmailSender>();
|
builder.Services.AddSingleton<IEmailSender<ApplicationUser>, IdentityNoOpEmailSender>();
|
||||||
|
|
||||||
|
|
2
Wave/wwwroot/css/main.min.css
vendored
2
Wave/wwwroot/css/main.min.css
vendored
File diff suppressed because one or more lines are too long
Loading…
Reference in a new issue