Separated Profile and ProfilePicture, added FullName form

This commit is contained in:
Mia Rose Winter 2024-01-16 22:27:17 +01:00
parent 611980d660
commit 1eeb1e3c13
Signed by: miawinter
GPG key ID: 4B6F6A83178F595E
6 changed files with 126 additions and 81 deletions

View file

@ -1,43 +1,48 @@
@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>
<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"> <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>
@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> <section>
<h2 class="text-2xl lg:text-4xl mb-3">Permissions</h2> <h2 class="text-2xl lg:text-4xl mb-3">Permissions</h2>
@ -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() { private sealed class InputModel {
ImageId = guid.Value [MaxLength(64)]
}; public string? FullName { get; set; }
context.Update(User);
await context.SaveChangesAsync();
if (imageToDelete is not null)
ImageService.Delete(imageToDelete.Value);
await InvokeAsync(StateHasChanged);
} }
} }

View 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);
}
}

View file

@ -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 {

View file

@ -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="@id" /> <ProfilePictureComponent ProfileId="@context.User.FindFirst("Id")!.Value" />
</div> </div>
}
</NavLink> </NavLink>
</li> </li>
<li> <li>

View file

@ -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>();

File diff suppressed because one or more lines are too long