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,44 +1,49 @@
@page "/Account/Manage"
@using Microsoft.AspNetCore.Identity
@using Microsoft.EntityFrameworkCore
@using Wave.Data
@using Wave.Services
@using System.ComponentModel.DataAnnotations
@rendermode InteractiveServer
@inject NavigationManager Navigation
@inject IdentityUserAccessor UserAccessor
@inject UserManager<ApplicationUser> UserManager
@inject IDbContextFactory<ApplicationDbContext> ContextFactory
@inject ImageService ImageService
@inject SignInManager<ApplicationUser> SignInManager
@inject IStringLocalizer<Index> Localizer
<PageTitle>Profile</PageTitle>
<StatusMessage Message="@Message" />
<div class="flex gap-4 flex-wrap">
<section class="max-w-xs" Model="Input" FormName="profile" OnValidSubmit="OnValidSubmitAsync" method="post">
<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">
<span class="label-text">Username</span>
</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/>
</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>
<h2 class="text-2xl lg:text-4xl mb-3">Permissions</h2>
<ul>
@ -107,43 +112,30 @@
</div>
@code {
private ApplicationUser? User { get; set; }
private string? Username { get; set; }
private string? Message { get; set; }
[SupplyParameterFromForm]
private InputModel Model { get; set; } = new();
[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() {
if (AuthenticationState is not null) {
var state = await AuthenticationState;
User = await UserManager.GetUserAsync(state.User);
Username = User.UserName;
await using var context = await ContextFactory.CreateDbContextAsync();
await context.Entry(User).Reference(u => u.ProfilePicture).LoadAsync();
}
UserName = UserManager.GetUserName(HttpContext.User)!;
User = await UserAccessor.GetRequiredUserAsync(HttpContext);
Model.FullName ??= User.FullName;
}
private async Task ProfilePictureChanged(string tempFilePath) {
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();
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 async Task SubmitFullNameUpdate() {
User.FullName = Model.FullName?.Trim();
await UserManager.UpdateAsync(User);
await SignInManager.RefreshSignInAsync(User);
Message = Localizer["FullName_Success"];
}
private sealed class InputModel {
[MaxLength(64)]
public string? FullName { get; set; }
}
}

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
<ul class="menu" role="navigation">
<li class="nav-item">
<NavLink href="Account/Manage" Match="NavLinkMatch.All">Profile</NavLink>
</li>
<li class="nav-item">
<NavLink href="Account/Manage/Email">Email</NavLink>
</li>
<li class="nav-item">
<NavLink href="Account/Manage/ChangePassword">Password</NavLink>
</li>
<li><NavLink href="Account/Manage" Match="NavLinkMatch.All">Profile</NavLink></li>
<li><NavLink href="Account/Manage/ProfilePicture">Profile Picture</NavLink></li>
<li><NavLink href="Account/Manage/Email">Email</NavLink></li>
<li><NavLink href="Account/Manage/ChangePassword">Password</NavLink></li>
@if (_hasExternalLogins) {
<li class="nav-item">
<NavLink href="Account/Manage/ExternalLogins">External logins</NavLink>
</li>
<li><NavLink href="Account/Manage/ExternalLogins">External logins</NavLink></li>
}
<li class="nav-item">
<NavLink href="Account/Manage/TwoFactorAuthentication">Two-factor authentication</NavLink>
</li>
<li class="nav-item">
<NavLink href="Account/Manage/PersonalData">Personal data</NavLink>
</li>
<li><NavLink href="Account/Manage/TwoFactorAuthentication">Two-factor authentication</NavLink></li>
<li><NavLink href="Account/Manage/PersonalData">Personal data</NavLink></li>
</ul>
@code {

View file

@ -28,12 +28,10 @@
<Authorized>
<li class="flex content-center gap-2">
<NavLink href="Account/Manage">
@context.User.Identity?.Name
@if (context.User.FindFirst(ClaimTypes.NameIdentifier)?.Value is { } id) {
<div class="w-8">
<ProfilePictureComponent ProfileId="@id" />
</div>
}
@context.User.FindFirst("FullName")!.Value
<div class="w-8">
<ProfilePictureComponent ProfileId="@context.User.FindFirst("Id")!.Value" />
</div>
</NavLink>
</li>
<li>

View file

@ -48,7 +48,8 @@
.AddRoles<IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddSignInManager()
.AddDefaultTokenProviders();
.AddDefaultTokenProviders()
.AddClaimsPrincipalFactory<UserClaimsFactory>();
builder.Services.AddSingleton<IEmailSender<ApplicationUser>, IdentityNoOpEmailSender>();

File diff suppressed because one or more lines are too long