From 3604c066e77abb4650d520ee161e433fcdd941bf Mon Sep 17 00:00:00 2001 From: Mateusz Skoczek Date: Sun, 3 Nov 2024 23:01:34 +0100 Subject: [PATCH] profile picture and basic info editors added --- ...equest.cs => AccountProfileInfoRequest.cs} | 21 ++++- .../Accounts/AccountProfilePictureRequest.cs | 33 +++++++ .../AccountsController.cs | 23 +++-- .../AccountsControllerService.cs | 50 ++++++++++- .../IAccountsControllerService.cs | 4 +- ... => AccountProfileInfoRequestValidator.cs} | 8 +- .../AccountProfilePictureRequestValidator.cs | 13 +++ .../Accounts/AccountsClientService.cs | 35 +++++++- .../Accounts/IAccountsClientService.cs | 4 +- .../Model/Accounts.cs | 6 +- WatchIt.Website/WatchIt.Website/App.razor | 4 +- .../Panels/PictureEditorPanelComponent.razor | 2 +- .../PictureEditorPanelComponent.razor.cs | 4 + .../AccountPictureComponent.razor | 8 +- .../AccountPictureComponent.razor.cs | 25 +++--- .../Subcomponents/PictureComponent.razor | 2 +- .../Subcomponents/PictureComponent.razor.cs | 8 ++ .../ProfileEditFormPanelComponent.razor | 60 +++++++++++++ .../ProfileEditFormPanelComponent.razor.cs | 86 +++++++++++++++++++ .../ProfileEditHeaderPanelComponent.razor | 5 ++ .../ProfileEditHeaderPanelComponent.razor.css | 0 .../UserEditPageHeaderPanelComponent.razor | 9 ++ .../UserEditPageHeaderPanelComponent.razor.cs | 38 ++++++++ ...UserEditPageHeaderPanelComponent.razor.css | 9 ++ .../Panels/UserPageHeaderPanelComponent.razor | 22 ++--- .../UserPageHeaderPanelComponent.razor.cs | 4 +- .../WatchIt.Website/Layout/MainLayout.razor | 4 +- .../Layout/MainLayout.razor.cs | 17 ++++ .../WatchIt.Website/Pages/UserEditPage.razor | 83 +++++++++++++++++- .../Pages/UserEditPage.razor.cs | 61 +++++++++++++ .../WatchIt.Website/Pages/UserPage.razor | 2 +- .../WatchIt.Website/Pages/UserPage.razor.cs | 3 +- .../WatchIt.Website/appsettings.json | 8 +- .../WatchIt.Website/wwwroot/css/panel.css | 10 ++- 34 files changed, 607 insertions(+), 64 deletions(-) rename WatchIt.Common/WatchIt.Common.Model/Accounts/{AccountRequest.cs => AccountProfileInfoRequest.cs} (50%) create mode 100644 WatchIt.Common/WatchIt.Common.Model/Accounts/AccountProfilePictureRequest.cs rename WatchIt.WebAPI/WatchIt.WebAPI.Validators/Accounts/{AccountRequestValidator.cs => AccountProfileInfoRequestValidator.cs} (52%) create mode 100644 WatchIt.WebAPI/WatchIt.WebAPI.Validators/Accounts/AccountProfilePictureRequestValidator.cs create mode 100644 WatchIt.Website/WatchIt.Website/Components/Pages/UserEditPage/Panels/ProfileEditFormPanelComponent.razor create mode 100644 WatchIt.Website/WatchIt.Website/Components/Pages/UserEditPage/Panels/ProfileEditFormPanelComponent.razor.cs create mode 100644 WatchIt.Website/WatchIt.Website/Components/Pages/UserEditPage/Panels/ProfileEditHeaderPanelComponent.razor create mode 100644 WatchIt.Website/WatchIt.Website/Components/Pages/UserEditPage/Panels/ProfileEditHeaderPanelComponent.razor.css create mode 100644 WatchIt.Website/WatchIt.Website/Components/Pages/UserEditPage/Panels/UserEditPageHeaderPanelComponent.razor create mode 100644 WatchIt.Website/WatchIt.Website/Components/Pages/UserEditPage/Panels/UserEditPageHeaderPanelComponent.razor.cs create mode 100644 WatchIt.Website/WatchIt.Website/Components/Pages/UserEditPage/Panels/UserEditPageHeaderPanelComponent.razor.css diff --git a/WatchIt.Common/WatchIt.Common.Model/Accounts/AccountRequest.cs b/WatchIt.Common/WatchIt.Common.Model/Accounts/AccountProfileInfoRequest.cs similarity index 50% rename from WatchIt.Common/WatchIt.Common.Model/Accounts/AccountRequest.cs rename to WatchIt.Common/WatchIt.Common.Model/Accounts/AccountProfileInfoRequest.cs index 30b1d7b..e26f615 100644 --- a/WatchIt.Common/WatchIt.Common.Model/Accounts/AccountRequest.cs +++ b/WatchIt.Common/WatchIt.Common.Model/Accounts/AccountProfileInfoRequest.cs @@ -2,9 +2,12 @@ using System.Text.Json.Serialization; namespace WatchIt.Common.Model.Accounts; -public class AccountRequest : Account +public class AccountProfileInfoRequest { #region PROPERTIES + + [JsonPropertyName("description")] + public string? Description { get; set; } [JsonPropertyName("gender_id")] public short? GenderId { get; set; } @@ -13,12 +16,24 @@ public class AccountRequest : Account + #region CONSTRUCTORS + + public AccountProfileInfoRequest() { } + + public AccountProfileInfoRequest(AccountResponse accountResponse) + { + Description = accountResponse.Description; + GenderId = accountResponse.Gender?.Id; + } + + #endregion + + + #region PUBLIC METHODS public void UpdateAccount(Database.Model.Account.Account account) { - account.Username = Username; - account.Email = Email; account.Description = Description; account.GenderId = GenderId; } diff --git a/WatchIt.Common/WatchIt.Common.Model/Accounts/AccountProfilePictureRequest.cs b/WatchIt.Common/WatchIt.Common.Model/Accounts/AccountProfilePictureRequest.cs new file mode 100644 index 0000000..1fbea18 --- /dev/null +++ b/WatchIt.Common/WatchIt.Common.Model/Accounts/AccountProfilePictureRequest.cs @@ -0,0 +1,33 @@ +using System.Diagnostics.CodeAnalysis; + +namespace WatchIt.Common.Model.Accounts; + +public class AccountProfilePictureRequest : AccountProfilePicture +{ + #region CONSTRUCTORS + + public AccountProfilePictureRequest() {} + + [SetsRequiredMembers] + public AccountProfilePictureRequest(Picture image) + { + Image = image.Image; + MimeType = image.MimeType; + } + + #endregion + + + public Database.Model.Account.AccountProfilePicture CreateMediaPosterImage() => new Database.Model.Account.AccountProfilePicture + { + Image = Image, + MimeType = MimeType, + }; + + public void UpdateMediaPosterImage(Database.Model.Account.AccountProfilePicture item) + { + item.Image = Image; + item.MimeType = MimeType; + item.UploadDate = DateTime.UtcNow; + } +} \ No newline at end of file diff --git a/WatchIt.WebAPI/WatchIt.WebAPI.Controllers/AccountsController.cs b/WatchIt.WebAPI/WatchIt.WebAPI.Controllers/AccountsController.cs index 3bcb30d..02fa953 100644 --- a/WatchIt.WebAPI/WatchIt.WebAPI.Controllers/AccountsController.cs +++ b/WatchIt.WebAPI/WatchIt.WebAPI.Controllers/AccountsController.cs @@ -27,7 +27,7 @@ public class AccountsController(IAccountsControllerService accountsControllerSer [ProducesResponseType(typeof(void), StatusCodes.Status401Unauthorized)] public async Task Authenticate([FromBody]AuthenticateRequest body) => await accountsControllerService.Authenticate(body); - [HttpPost("authenticate-refresh")] + [HttpPost("authenticate_refresh")] [Authorize(AuthenticationSchemes = "refresh")] [ProducesResponseType(typeof(AuthenticateResponse), StatusCodes.Status200OK)] [ProducesResponseType(typeof(void), StatusCodes.Status401Unauthorized)] @@ -39,25 +39,38 @@ public class AccountsController(IAccountsControllerService accountsControllerSer [ProducesResponseType(typeof(void), StatusCodes.Status204NoContent)] public async Task Logout() => await accountsControllerService.Logout(); - [HttpGet("{id}/profile-picture")] + [HttpGet("{id}/profile_picture")] [AllowAnonymous] [ProducesResponseType(typeof(AccountProfilePictureResponse), StatusCodes.Status200OK)] [ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status400BadRequest)] [ProducesResponseType(StatusCodes.Status404NotFound)] public async Task GetAccountProfilePicture([FromRoute(Name = "id")]long id) => await accountsControllerService.GetAccountProfilePicture(id); + [HttpPut("profile_picture")] + [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] + [ProducesResponseType(typeof(AccountProfilePictureResponse), StatusCodes.Status200OK)] + [ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status400BadRequest)] + [ProducesResponseType(typeof(void), StatusCodes.Status401Unauthorized)] + public async Task PutAccountProfilePicture([FromBody]AccountProfilePictureRequest body) => await accountsControllerService.PutAccountProfilePicture(body); + + [HttpDelete("profile_picture")] + [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] + [ProducesResponseType(typeof(void), StatusCodes.Status204NoContent)] + [ProducesResponseType(typeof(void), StatusCodes.Status401Unauthorized)] + public async Task DeleteAccountProfilePicture() => await accountsControllerService.DeleteAccountProfilePicture(); + [HttpGet("{id}/info")] [AllowAnonymous] [ProducesResponseType(typeof(AccountResponse), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] public async Task GetAccountInfo([FromRoute]long id) => await accountsControllerService.GetAccountInfo(id); - [HttpPut("info")] + [HttpPut("profile_info")] [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] - [ProducesResponseType(typeof(AccountResponse), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status401Unauthorized)] [ProducesResponseType(StatusCodes.Status404NotFound)] - public async Task PutAccountInfo([FromBody]AccountRequest data) => await accountsControllerService.PutAccountInfo(data); + public async Task PutAccountProfileInfo([FromBody]AccountProfileInfoRequest data) => await accountsControllerService.PutAccountProfileInfo(data); [HttpGet("{id}/movies")] [AllowAnonymous] diff --git a/WatchIt.WebAPI/WatchIt.WebAPI.Services/WatchIt.WebAPI.Services.Controllers/WatchIt.WebAPI.Services.Controllers.Accounts/AccountsControllerService.cs b/WatchIt.WebAPI/WatchIt.WebAPI.Services/WatchIt.WebAPI.Services.Controllers/WatchIt.WebAPI.Services.Controllers.Accounts/AccountsControllerService.cs index a4151d7..da1d7a9 100644 --- a/WatchIt.WebAPI/WatchIt.WebAPI.Services/WatchIt.WebAPI.Services.Controllers/WatchIt.WebAPI.Services.Controllers.Accounts/AccountsControllerService.cs +++ b/WatchIt.WebAPI/WatchIt.WebAPI.Services/WatchIt.WebAPI.Services.Controllers/WatchIt.WebAPI.Services.Controllers.Accounts/AccountsControllerService.cs @@ -142,6 +142,48 @@ public class AccountsControllerService( AccountProfilePictureResponse picture = new AccountProfilePictureResponse(account.ProfilePicture); return RequestResult.Ok(picture); } + + public async Task PutAccountProfilePicture(AccountProfilePictureRequest data) + { + Account account = await database.Accounts.FirstAsync(x => x.Id == userService.GetUserId()); + Database.Model.Account.AccountProfilePicture? picture = account.ProfilePicture; + + if (picture is null) + { + picture = data.CreateMediaPosterImage(); + await database.AccountProfilePictures.AddAsync(picture); + await database.SaveChangesAsync(); + + account.ProfilePictureId = picture.Id; + } + else + { + data.UpdateMediaPosterImage(picture); + } + + await database.SaveChangesAsync(); + + AccountProfilePictureResponse returnData = new AccountProfilePictureResponse(picture); + return RequestResult.Ok(returnData); + } + + public async Task DeleteAccountProfilePicture() + { + Account account = await database.Accounts.FirstAsync(x => x.Id == userService.GetUserId()); + Database.Model.Account.AccountProfilePicture? picture = account.ProfilePicture; + + if (picture is not null) + { + account.ProfilePictureId = null; + await database.SaveChangesAsync(); + + database.AccountProfilePictures.Attach(picture); + database.AccountProfilePictures.Remove(picture); + await database.SaveChangesAsync(); + } + + return RequestResult.NoContent(); + } public async Task GetAccountInfo(long id) { @@ -151,11 +193,11 @@ public class AccountsControllerService( return RequestResult.NotFound(); } - AccountResponse response = new AccountResponse(account); - return RequestResult.Ok(response); + AccountResponse profileInfoResponse = new AccountResponse(account); + return RequestResult.Ok(profileInfoResponse); } - public async Task PutAccountInfo(AccountRequest data) + public async Task PutAccountProfileInfo(AccountProfileInfoRequest data) { Account? account = await database.Accounts.FirstOrDefaultAsync(x => x.Id == userService.GetUserId()); if (account is null) @@ -164,6 +206,8 @@ public class AccountsControllerService( } data.UpdateAccount(account); + await database.SaveChangesAsync(); + return RequestResult.Ok(); } diff --git a/WatchIt.WebAPI/WatchIt.WebAPI.Services/WatchIt.WebAPI.Services.Controllers/WatchIt.WebAPI.Services.Controllers.Accounts/IAccountsControllerService.cs b/WatchIt.WebAPI/WatchIt.WebAPI.Services/WatchIt.WebAPI.Services.Controllers/WatchIt.WebAPI.Services.Controllers.Accounts/IAccountsControllerService.cs index 500b256..4fb51a0 100644 --- a/WatchIt.WebAPI/WatchIt.WebAPI.Services/WatchIt.WebAPI.Services.Controllers/WatchIt.WebAPI.Services.Controllers.Accounts/IAccountsControllerService.cs +++ b/WatchIt.WebAPI/WatchIt.WebAPI.Services/WatchIt.WebAPI.Services.Controllers/WatchIt.WebAPI.Services.Controllers.Accounts/IAccountsControllerService.cs @@ -14,8 +14,10 @@ public interface IAccountsControllerService Task AuthenticateRefresh(); Task Logout(); Task GetAccountProfilePicture(long id); + Task PutAccountProfilePicture(AccountProfilePictureRequest data); + Task DeleteAccountProfilePicture(); Task GetAccountInfo(long id); - Task PutAccountInfo(AccountRequest data); + Task PutAccountProfileInfo(AccountProfileInfoRequest data); Task GetAccountRatedMovies(long id, MovieRatedQueryParameters query); Task GetAccountRatedSeries(long id, SeriesRatedQueryParameters query); Task GetAccountRatedPersons(long id, PersonRatedQueryParameters query); diff --git a/WatchIt.WebAPI/WatchIt.WebAPI.Validators/Accounts/AccountRequestValidator.cs b/WatchIt.WebAPI/WatchIt.WebAPI.Validators/Accounts/AccountProfileInfoRequestValidator.cs similarity index 52% rename from WatchIt.WebAPI/WatchIt.WebAPI.Validators/Accounts/AccountRequestValidator.cs rename to WatchIt.WebAPI/WatchIt.WebAPI.Validators/Accounts/AccountProfileInfoRequestValidator.cs index a5b3265..aa20074 100644 --- a/WatchIt.WebAPI/WatchIt.WebAPI.Validators/Accounts/AccountRequestValidator.cs +++ b/WatchIt.WebAPI/WatchIt.WebAPI.Validators/Accounts/AccountProfileInfoRequestValidator.cs @@ -4,14 +4,10 @@ using WatchIt.Database; namespace WatchIt.WebAPI.Validators.Accounts; -public class AccountRequestValidator : AbstractValidator +public class AccountProfileInfoRequestValidator : AbstractValidator { - public AccountRequestValidator(DatabaseContext database) + public AccountProfileInfoRequestValidator(DatabaseContext database) { - RuleFor(x => x.Username).NotEmpty() - .MaximumLength(50); - RuleFor(x => x.Email).EmailAddress() - .MaximumLength(320); RuleFor(x => x.Description).MaximumLength(1000); When(x => x.GenderId.HasValue, () => { diff --git a/WatchIt.WebAPI/WatchIt.WebAPI.Validators/Accounts/AccountProfilePictureRequestValidator.cs b/WatchIt.WebAPI/WatchIt.WebAPI.Validators/Accounts/AccountProfilePictureRequestValidator.cs new file mode 100644 index 0000000..873766c --- /dev/null +++ b/WatchIt.WebAPI/WatchIt.WebAPI.Validators/Accounts/AccountProfilePictureRequestValidator.cs @@ -0,0 +1,13 @@ +using FluentValidation; +using WatchIt.Common.Model.Accounts; + +namespace WatchIt.WebAPI.Validators.Accounts; + +public class AccountProfilePictureRequestValidator : AbstractValidator +{ + public AccountProfilePictureRequestValidator() + { + RuleFor(x => x.Image).NotEmpty(); + RuleFor(x => x.MimeType).Matches(@"\w+/.+").WithMessage("Incorrect mimetype"); + } +} \ No newline at end of file diff --git a/WatchIt.Website/WatchIt.Website.Services/WatchIt.Website.Services.Client/Accounts/AccountsClientService.cs b/WatchIt.Website/WatchIt.Website.Services/WatchIt.Website.Services.Client/Accounts/AccountsClientService.cs index c246c03..4438636 100644 --- a/WatchIt.Website/WatchIt.Website.Services/WatchIt.Website.Services.Client/Accounts/AccountsClientService.cs +++ b/WatchIt.Website/WatchIt.Website.Services/WatchIt.Website.Services.Client/Accounts/AccountsClientService.cs @@ -71,7 +71,7 @@ public class AccountsClientService(IHttpClientService httpClientService, IConfig public async Task GetAccountProfilePicture(long id, Action? successAction = null, Action>? badRequestAction = null, Action? notFoundAction = null) { - string url = GetUrl(EndpointsConfiguration.Accounts.GetProfilePicture, id); + string url = GetUrl(EndpointsConfiguration.Accounts.GetAccountProfilePicture, id); HttpRequest request = new HttpRequest(HttpMethodType.Get, url); HttpResponse response = await httpClientService.SendRequestAsync(request); @@ -80,6 +80,34 @@ public class AccountsClientService(IHttpClientService httpClientService, IConfig .RegisterActionFor404NotFound(notFoundAction) .ExecuteAction(); } + + public async Task PutAccountProfilePicture(AccountProfilePictureRequest data, Action? successAction = null, Action>? badRequestAction = null, Action? unauthorizedAction = null) + { + string url = GetUrl(EndpointsConfiguration.Accounts.PutAccountProfilePicture); + + HttpRequest request = new HttpRequest(HttpMethodType.Put, url) + { + Body = data + }; + + HttpResponse response = await httpClientService.SendRequestAsync(request); + response.RegisterActionFor2XXSuccess(successAction) + .RegisterActionFor400BadRequest(badRequestAction) + .RegisterActionFor401Unauthorized(unauthorizedAction) + .ExecuteAction(); + } + + public async Task DeleteAccountProfilePicture(Action? successAction = null, Action? unauthorizedAction = null) + { + string url = GetUrl(EndpointsConfiguration.Accounts.DeleteAccountProfilePicture); + + HttpRequest request = new HttpRequest(HttpMethodType.Delete, url); + + HttpResponse response = await httpClientService.SendRequestAsync(request); + response.RegisterActionFor2XXSuccess(successAction) + .RegisterActionFor401Unauthorized(unauthorizedAction) + .ExecuteAction(); + } public async Task GetAccountInfo(long id, Action? successAction = null, Action? notFoundAction = null) { @@ -92,9 +120,9 @@ public class AccountsClientService(IHttpClientService httpClientService, IConfig .ExecuteAction(); } - public async Task PutAccountInfo(AccountRequest data, Action? successAction = null, Action>? badRequestAction = null, Action? unauthorizedAction = null, Action? notFoundAction = null) + public async Task PutAccountProfileInfo(AccountProfileInfoRequest data, Action? successAction = null, Action>? badRequestAction = null, Action? unauthorizedAction = null) { - string url = GetUrl(EndpointsConfiguration.Accounts.PutAccountInfo); + string url = GetUrl(EndpointsConfiguration.Accounts.PutAccountProfileInfo); HttpRequest request = new HttpRequest(HttpMethodType.Put, url) { Body = data, @@ -104,7 +132,6 @@ public class AccountsClientService(IHttpClientService httpClientService, IConfig response.RegisterActionFor2XXSuccess(successAction) .RegisterActionFor400BadRequest(badRequestAction) .RegisterActionFor401Unauthorized(unauthorizedAction) - .RegisterActionFor404NotFound(notFoundAction) .ExecuteAction(); } diff --git a/WatchIt.Website/WatchIt.Website.Services/WatchIt.Website.Services.Client/Accounts/IAccountsClientService.cs b/WatchIt.Website/WatchIt.Website.Services/WatchIt.Website.Services.Client/Accounts/IAccountsClientService.cs index 6f16640..bb45c37 100644 --- a/WatchIt.Website/WatchIt.Website.Services/WatchIt.Website.Services.Client/Accounts/IAccountsClientService.cs +++ b/WatchIt.Website/WatchIt.Website.Services/WatchIt.Website.Services.Client/Accounts/IAccountsClientService.cs @@ -12,8 +12,10 @@ public interface IAccountsClientService Task AuthenticateRefresh(Action? successAction = null, Action? unauthorizedAction = null, Action? forbiddenAction = null); Task Logout(Action? successAction = null); Task GetAccountProfilePicture(long id, Action? successAction = null, Action>? badRequestAction = null, Action? notFoundAction = null); + Task PutAccountProfilePicture(AccountProfilePictureRequest data, Action? successAction = null, Action>? badRequestAction = null, Action? unauthorizedAction = null); + Task DeleteAccountProfilePicture(Action? successAction = null, Action? unauthorizedAction = null); Task GetAccountInfo(long id, Action? successAction = null, Action? notFoundAction = null); - Task PutAccountInfo(AccountRequest data, Action? successAction = null, Action>? badRequestAction = null, Action? unauthorizedAction = null, Action? notFoundAction = null); + Task PutAccountProfileInfo(AccountProfileInfoRequest data, Action? successAction = null, Action>? badRequestAction = null, Action? unauthorizedAction = null); Task GetAccountRatedMovies(long id, MovieRatedQueryParameters query, Action>? successAction = null, Action? notFoundAction = null); Task GetAccountRatedSeries(long id, SeriesRatedQueryParameters query, Action>? successAction = null, Action? notFoundAction = null); Task GetAccountRatedPersons(long id, PersonRatedQueryParameters query, Action>? successAction = null, Action? notFoundAction = null); diff --git a/WatchIt.Website/WatchIt.Website.Services/WatchIt.Website.Services.Configuration/Model/Accounts.cs b/WatchIt.Website/WatchIt.Website.Services/WatchIt.Website.Services.Configuration/Model/Accounts.cs index 5731d34..02ac602 100644 --- a/WatchIt.Website/WatchIt.Website.Services/WatchIt.Website.Services.Configuration/Model/Accounts.cs +++ b/WatchIt.Website/WatchIt.Website.Services/WatchIt.Website.Services.Configuration/Model/Accounts.cs @@ -7,9 +7,11 @@ public class Accounts public string Authenticate { get; set; } public string AuthenticateRefresh { get; set; } public string Logout { get; set; } - public string GetProfilePicture { get; set; } + public string GetAccountProfilePicture { get; set; } + public string PutAccountProfilePicture { get; set; } + public string DeleteAccountProfilePicture { get; set; } public string GetAccountInfo { get; set; } - public string PutAccountInfo { get; set; } + public string PutAccountProfileInfo { get; set; } public string GetAccountRatedMovies { get; set; } public string GetAccountRatedSeries { get; set; } public string GetAccountRatedPersons { get; set; } diff --git a/WatchIt.Website/WatchIt.Website/App.razor b/WatchIt.Website/WatchIt.Website/App.razor index 5ceca05..66ac768 100644 --- a/WatchIt.Website/WatchIt.Website/App.razor +++ b/WatchIt.Website/WatchIt.Website/App.razor @@ -10,10 +10,10 @@ - + - + diff --git a/WatchIt.Website/WatchIt.Website/Components/Common/Panels/PictureEditorPanelComponent.razor b/WatchIt.Website/WatchIt.Website/Components/Common/Panels/PictureEditorPanelComponent.razor index 2264955..37613f4 100644 --- a/WatchIt.Website/WatchIt.Website/Components/Common/Panels/PictureEditorPanelComponent.razor +++ b/WatchIt.Website/WatchIt.Website/Components/Common/Panels/PictureEditorPanelComponent.razor @@ -2,7 +2,7 @@ @if (_loaded) {
- + @if (_pictureChanged || _pictureSaved is not null) { diff --git a/WatchIt.Website/WatchIt.Website/Components/Common/Panels/PictureEditorPanelComponent.razor.cs b/WatchIt.Website/WatchIt.Website/Components/Common/Panels/PictureEditorPanelComponent.razor.cs index 3bd56b1..1949048 100644 --- a/WatchIt.Website/WatchIt.Website/Components/Common/Panels/PictureEditorPanelComponent.razor.cs +++ b/WatchIt.Website/WatchIt.Website/Components/Common/Panels/PictureEditorPanelComponent.razor.cs @@ -11,10 +11,12 @@ public partial class PictureEditorPanelComponent : ComponentBase [Parameter] public long? Id { get; set; } [Parameter] public int ContentWidth { get; set; } = 300; [Parameter] public required string PicturePlaceholder { get; set; } + [Parameter] public bool Circle { get; set; } [Parameter] public string Class { get; set; } = string.Empty; [Parameter] public required Func, Task> PictureGetTask { get; set; } [Parameter] public required Func, Task> PicturePutTask { get; set; } [Parameter] public required Func PictureDeleteTask { get; set; } + [Parameter] public Action? OnPictureChanged { get; set; } #endregion @@ -92,6 +94,7 @@ public partial class PictureEditorPanelComponent : ComponentBase _pictureSelected = data; _pictureChanged = false; _pictureSaving = false; + OnPictureChanged?.Invoke(data); } _pictureSaving = true; @@ -112,6 +115,7 @@ public partial class PictureEditorPanelComponent : ComponentBase _pictureSelected = null; _pictureChanged = false; _pictureDeleting = false; + OnPictureChanged?.Invoke(null); } _pictureDeleting = true; diff --git a/WatchIt.Website/WatchIt.Website/Components/Common/Subcomponents/AccountPictureComponent.razor b/WatchIt.Website/WatchIt.Website/Components/Common/Subcomponents/AccountPictureComponent.razor index ee75eef..6b532d5 100644 --- a/WatchIt.Website/WatchIt.Website/Components/Common/Subcomponents/AccountPictureComponent.razor +++ b/WatchIt.Website/WatchIt.Website/Components/Common/Subcomponents/AccountPictureComponent.razor @@ -1 +1,7 @@ -avatar \ No newline at end of file + \ No newline at end of file diff --git a/WatchIt.Website/WatchIt.Website/Components/Common/Subcomponents/AccountPictureComponent.razor.cs b/WatchIt.Website/WatchIt.Website/Components/Common/Subcomponents/AccountPictureComponent.razor.cs index 21e0402..42d51f8 100644 --- a/WatchIt.Website/WatchIt.Website/Components/Common/Subcomponents/AccountPictureComponent.razor.cs +++ b/WatchIt.Website/WatchIt.Website/Components/Common/Subcomponents/AccountPictureComponent.razor.cs @@ -30,6 +30,18 @@ public partial class AccountPictureComponent : ComponentBase private AccountProfilePictureResponse? _picture; #endregion + + + + #region PUBLIC METHODS + + public async Task Reload() + { + await AccountsClientService.GetAccountProfilePicture(Id, data => _picture = data, notFoundAction: () => _picture = null); + StateHasChanged(); + } + + #endregion @@ -39,18 +51,7 @@ public partial class AccountPictureComponent : ComponentBase { if (firstRender) { - List endTasks = new List(); - - // STEP 0 - endTasks.AddRange( - [ - AccountsClientService.GetAccountProfilePicture(Id, data => _picture = data) - ]); - - // END - await Task.WhenAll(endTasks); - - StateHasChanged(); + await Reload(); } } diff --git a/WatchIt.Website/WatchIt.Website/Components/Common/Subcomponents/PictureComponent.razor b/WatchIt.Website/WatchIt.Website/Components/Common/Subcomponents/PictureComponent.razor index 398c4d6..358a074 100644 --- a/WatchIt.Website/WatchIt.Website/Components/Common/Subcomponents/PictureComponent.razor +++ b/WatchIt.Website/WatchIt.Website/Components/Common/Subcomponents/PictureComponent.razor @@ -1 +1 @@ -@(AlternativeText) \ No newline at end of file +@(AlternativeText) \ No newline at end of file diff --git a/WatchIt.Website/WatchIt.Website/Components/Common/Subcomponents/PictureComponent.razor.cs b/WatchIt.Website/WatchIt.Website/Components/Common/Subcomponents/PictureComponent.razor.cs index 213b9c2..08800d4 100644 --- a/WatchIt.Website/WatchIt.Website/Components/Common/Subcomponents/PictureComponent.razor.cs +++ b/WatchIt.Website/WatchIt.Website/Components/Common/Subcomponents/PictureComponent.razor.cs @@ -14,6 +14,8 @@ public partial class PictureComponent : ComponentBase [Parameter] public string Class { get; set; } = string.Empty; [Parameter] public int? Height { get; set; } [Parameter] public int? Width { get; set; } + [Parameter] public bool Circle { get; set; } + [Parameter] public bool Shadow { get; set; } = true; #endregion @@ -40,6 +42,11 @@ public partial class PictureComponent : ComponentBase { _attributes.Add("width", Width.Value); } + + if (Circle) + { + AspectRatio = PictureComponentAspectRatio.Square; + } } #endregion @@ -71,6 +78,7 @@ public partial class PictureComponent : ComponentBase public static readonly PictureComponentAspectRatio Default = new PictureComponentAspectRatio(); public static readonly PictureComponentAspectRatio Photo = new PictureComponentAspectRatio(16, 9); + public static readonly PictureComponentAspectRatio Square = new PictureComponentAspectRatio(1, 1); #endregion diff --git a/WatchIt.Website/WatchIt.Website/Components/Pages/UserEditPage/Panels/ProfileEditFormPanelComponent.razor b/WatchIt.Website/WatchIt.Website/Components/Pages/UserEditPage/Panels/ProfileEditFormPanelComponent.razor new file mode 100644 index 0000000..438fa9c --- /dev/null +++ b/WatchIt.Website/WatchIt.Website/Components/Pages/UserEditPage/Panels/ProfileEditFormPanelComponent.razor @@ -0,0 +1,60 @@ +@using WatchIt.Common.Model.Genders + + + +
+ @if (_loaded) + { +
+

Basic profile info

+ + +
+
+ +
+ +
+
+
+ +
+ + + @foreach (GenderResponse gender in _genders) + { + + } + +
+
+
+
+ @if (!string.IsNullOrWhiteSpace(_error)) + { + @(_error) + } +
+
+ +
+
+
+
+
+ } + else + { + + } +
\ No newline at end of file diff --git a/WatchIt.Website/WatchIt.Website/Components/Pages/UserEditPage/Panels/ProfileEditFormPanelComponent.razor.cs b/WatchIt.Website/WatchIt.Website/Components/Pages/UserEditPage/Panels/ProfileEditFormPanelComponent.razor.cs new file mode 100644 index 0000000..59806dd --- /dev/null +++ b/WatchIt.Website/WatchIt.Website/Components/Pages/UserEditPage/Panels/ProfileEditFormPanelComponent.razor.cs @@ -0,0 +1,86 @@ +using Microsoft.AspNetCore.Components; +using WatchIt.Common.Model.Accounts; +using WatchIt.Common.Model.Genders; +using WatchIt.Website.Services.Client.Accounts; +using WatchIt.Website.Services.Client.Genders; + +namespace WatchIt.Website.Components.Pages.UserEditPage.Panels; + +public partial class ProfileEditFormPanelComponent : ComponentBase +{ + #region SERVICES + + [Inject] private NavigationManager NavigationManager { get; set; } = default!; + [Inject] private IAccountsClientService AccountsClientService { get; set; } = default!; + [Inject] private IGendersClientService GendersClientService { get; set; } = default!; + + #endregion + + + + #region PARAMETERS + + [Parameter] public long Id { get; set; } + [Parameter] public string Class { get; set; } = string.Empty; + + #endregion + + + + #region FIELDS + + private bool _loaded; + private bool _saving; + private string? _error; + + private IEnumerable _genders = []; + + private AccountProfileInfoRequest _accountProfileInfo = new AccountProfileInfoRequest(); + + #endregion + + + + #region PRIVATE METHODS + + protected override async Task OnAfterRenderAsync(bool firstRender) + { + if (firstRender) + { + await Task.WhenAll( + [ + GendersClientService.GetAllGenders(successAction: data => _genders = data), + AccountsClientService.GetAccountInfo(Id, data => _accountProfileInfo = new AccountProfileInfoRequest(data)) + ]); + + _loaded = true; + StateHasChanged(); + } + } + + private async Task Save() + { + void Success() + { + _error = null; + _saving = false; + } + + void BadRequest(IDictionary errors) + { + _error = errors.SelectMany(x => x.Value).FirstOrDefault() ?? "Unknown error"; + _saving = false; + } + + void AuthError() + { + _error = "Authentication error"; + _saving = false; + } + + _saving = true; + await AccountsClientService.PutAccountProfileInfo(_accountProfileInfo, Success, BadRequest, AuthError); + } + + #endregion +} \ No newline at end of file diff --git a/WatchIt.Website/WatchIt.Website/Components/Pages/UserEditPage/Panels/ProfileEditHeaderPanelComponent.razor b/WatchIt.Website/WatchIt.Website/Components/Pages/UserEditPage/Panels/ProfileEditHeaderPanelComponent.razor new file mode 100644 index 0000000..6385348 --- /dev/null +++ b/WatchIt.Website/WatchIt.Website/Components/Pages/UserEditPage/Panels/ProfileEditHeaderPanelComponent.razor @@ -0,0 +1,5 @@ +
+
+

Profile settings

+
+
\ No newline at end of file diff --git a/WatchIt.Website/WatchIt.Website/Components/Pages/UserEditPage/Panels/ProfileEditHeaderPanelComponent.razor.css b/WatchIt.Website/WatchIt.Website/Components/Pages/UserEditPage/Panels/ProfileEditHeaderPanelComponent.razor.css new file mode 100644 index 0000000..e69de29 diff --git a/WatchIt.Website/WatchIt.Website/Components/Pages/UserEditPage/Panels/UserEditPageHeaderPanelComponent.razor b/WatchIt.Website/WatchIt.Website/Components/Pages/UserEditPage/Panels/UserEditPageHeaderPanelComponent.razor new file mode 100644 index 0000000..2b2c701 --- /dev/null +++ b/WatchIt.Website/WatchIt.Website/Components/Pages/UserEditPage/Panels/UserEditPageHeaderPanelComponent.razor @@ -0,0 +1,9 @@ +
+
+ +
+

@(User.Username)

+ User settings +
+
+
\ No newline at end of file diff --git a/WatchIt.Website/WatchIt.Website/Components/Pages/UserEditPage/Panels/UserEditPageHeaderPanelComponent.razor.cs b/WatchIt.Website/WatchIt.Website/Components/Pages/UserEditPage/Panels/UserEditPageHeaderPanelComponent.razor.cs new file mode 100644 index 0000000..9b44909 --- /dev/null +++ b/WatchIt.Website/WatchIt.Website/Components/Pages/UserEditPage/Panels/UserEditPageHeaderPanelComponent.razor.cs @@ -0,0 +1,38 @@ +using Microsoft.AspNetCore.Components; +using WatchIt.Website.Components.Common.Subcomponents; +using WatchIt.Website.Services.Authentication; + +namespace WatchIt.Website.Components.Pages.UserEditPage.Panels; + +public partial class UserEditPageHeaderPanelComponent : ComponentBase +{ + #region SERVICES + + [Inject] public NavigationManager NavigationManager { get; set; } = default!; + + #endregion + + + + #region PARAMETERS + + [Parameter] public required User User { get; set; } + + #endregion + + + + #region FIELDS + + private AccountPictureComponent _accountPicture = default!; + + #endregion + + + + #region PUBLIC METHODS + + public async Task ReloadPicture() => await _accountPicture.Reload(); + + #endregion +} \ No newline at end of file diff --git a/WatchIt.Website/WatchIt.Website/Components/Pages/UserEditPage/Panels/UserEditPageHeaderPanelComponent.razor.css b/WatchIt.Website/WatchIt.Website/Components/Pages/UserEditPage/Panels/UserEditPageHeaderPanelComponent.razor.css new file mode 100644 index 0000000..7044add --- /dev/null +++ b/WatchIt.Website/WatchIt.Website/Components/Pages/UserEditPage/Panels/UserEditPageHeaderPanelComponent.razor.css @@ -0,0 +1,9 @@ +/* IDS */ + +#username { + margin-top: -8px !important; +} + +#secondaryText { + color: lightgray !important; +} \ No newline at end of file diff --git a/WatchIt.Website/WatchIt.Website/Components/Pages/UserPage/Panels/UserPageHeaderPanelComponent.razor b/WatchIt.Website/WatchIt.Website/Components/Pages/UserPage/Panels/UserPageHeaderPanelComponent.razor index 86e41f1..28059b2 100644 --- a/WatchIt.Website/WatchIt.Website/Components/Pages/UserPage/Panels/UserPageHeaderPanelComponent.razor +++ b/WatchIt.Website/WatchIt.Website/Components/Pages/UserPage/Panels/UserPageHeaderPanelComponent.razor @@ -1,26 +1,26 @@
- +
-

@(AccountData.Username)

+

@(AccountProfileInfoData.Username)

- @if (!string.IsNullOrWhiteSpace(AccountData.Description)) + @if (!string.IsNullOrWhiteSpace(AccountProfileInfoData.Description)) { - - @(AccountData.Description) + + @(AccountProfileInfoData.Description) }
-
+
@Body
diff --git a/WatchIt.Website/WatchIt.Website/Layout/MainLayout.razor.cs b/WatchIt.Website/WatchIt.Website/Layout/MainLayout.razor.cs index 10a09d8..a73056b 100644 --- a/WatchIt.Website/WatchIt.Website/Layout/MainLayout.razor.cs +++ b/WatchIt.Website/WatchIt.Website/Layout/MainLayout.razor.cs @@ -2,6 +2,7 @@ using System.Net; using Microsoft.AspNetCore.Components; using WatchIt.Common.Model.Accounts; using WatchIt.Common.Model.Photos; +using WatchIt.Website.Components.Common.Subcomponents; using WatchIt.Website.Services.Authentication; using WatchIt.Website.Services.Tokens; using WatchIt.Website.Services.Client.Accounts; @@ -27,6 +28,8 @@ public partial class MainLayout : LayoutComponentBase #region FIELDS + private AccountPictureComponent? _profilePicture; + private bool _loaded; private User? _user; @@ -53,6 +56,20 @@ public partial class MainLayout : LayoutComponentBase } #endregion + + + + #region PUBLIC METHODS + + public async Task ReloadProfilePicture() + { + if (_profilePicture is not null) + { + await _profilePicture.Reload(); + } + } + + #endregion diff --git a/WatchIt.Website/WatchIt.Website/Pages/UserEditPage.razor b/WatchIt.Website/WatchIt.Website/Pages/UserEditPage.razor index 64a2378..c9e0aac 100644 --- a/WatchIt.Website/WatchIt.Website/Pages/UserEditPage.razor +++ b/WatchIt.Website/WatchIt.Website/Pages/UserEditPage.razor @@ -1 +1,82 @@ -@page "/user/edit" \ No newline at end of file +@using System.Text +@using WatchIt.Common.Model +@using WatchIt.Website.Components.Pages.UserEditPage.Panels + +@page "/user/edit" + +@{ + StringBuilder sb = new StringBuilder(" - WatchIt"); + + if (_user is null) sb.Insert(0, "Loading..."); + else sb.Insert(0, "User settings"); + + @(sb.ToString()) +} + + + +
+ @if (_user is not null) + { +
+
+ +
+
+
+
+ + + Profile + Account + + + +
+
+
+
+ +
+
+
+
+ +
+
+ +
+
+
+
+
+ + + +
+
+
+
+ } + else + { +
+
+
+ +
+
+
+ } +
\ No newline at end of file diff --git a/WatchIt.Website/WatchIt.Website/Pages/UserEditPage.razor.cs b/WatchIt.Website/WatchIt.Website/Pages/UserEditPage.razor.cs index d5a489f..b3ada08 100644 --- a/WatchIt.Website/WatchIt.Website/Pages/UserEditPage.razor.cs +++ b/WatchIt.Website/WatchIt.Website/Pages/UserEditPage.razor.cs @@ -1,7 +1,68 @@ +using System.Net; using Microsoft.AspNetCore.Components; +using WatchIt.Common.Model; +using WatchIt.Website.Components.Pages.UserEditPage.Panels; +using WatchIt.Website.Layout; +using WatchIt.Website.Services.Authentication; +using WatchIt.Website.Services.Client.Accounts; namespace WatchIt.Website.Pages; public partial class UserEditPage : ComponentBase { + #region SERVICES + + [Inject] private NavigationManager NavigationManager { get; set; } = default!; + [Inject] private IAuthenticationService AuthenticationService { get; set; } = default!; + [Inject] private IAccountsClientService AccountsClientService { get; set; } = default!; + + #endregion + + + + #region PARAMETERS + + [CascadingParameter] public MainLayout Layout { get; set; } = default!; + + #endregion + + + + #region FIELDS + + private User? _user; + + private UserEditPageHeaderPanelComponent _header = default!; + + #endregion + + + + #region PRIVATE METHODS + + protected override async Task OnAfterRenderAsync(bool firstRender) + { + if (firstRender) + { + Layout.BackgroundPhoto = null; + + _user = await AuthenticationService.GetUserAsync(); + if (_user is null) + { + NavigationManager.NavigateTo($"/auth?redirect_to={WebUtility.UrlEncode("/user/edit")}"); + } + else + { + StateHasChanged(); + } + } + } + + private async Task PictureChanged() => await Task.WhenAll( + [ + _header.ReloadPicture(), + Layout.ReloadProfilePicture() + ]); + + #endregion } \ No newline at end of file diff --git a/WatchIt.Website/WatchIt.Website/Pages/UserPage.razor b/WatchIt.Website/WatchIt.Website/Pages/UserPage.razor index 12503ca..ff65c65 100644 --- a/WatchIt.Website/WatchIt.Website/Pages/UserPage.razor +++ b/WatchIt.Website/WatchIt.Website/Pages/UserPage.razor @@ -48,7 +48,7 @@ {
- +
diff --git a/WatchIt.Website/WatchIt.Website/Pages/UserPage.razor.cs b/WatchIt.Website/WatchIt.Website/Pages/UserPage.razor.cs index 2a2985c..4de839e 100644 --- a/WatchIt.Website/WatchIt.Website/Pages/UserPage.razor.cs +++ b/WatchIt.Website/WatchIt.Website/Pages/UserPage.razor.cs @@ -1,3 +1,4 @@ +using System.Net; using Microsoft.AspNetCore.Components; using WatchIt.Common.Model.Accounts; using WatchIt.Website.Layout; @@ -75,7 +76,7 @@ public partial class UserPage : ComponentBase { if (user is null) { - NavigationManager.NavigateTo("/auth"); + NavigationManager.NavigateTo($"/auth?redirect_to={WebUtility.UrlEncode("/user")}"); _redirection = true; return; } diff --git a/WatchIt.Website/WatchIt.Website/appsettings.json b/WatchIt.Website/WatchIt.Website/appsettings.json index 9d0355d..2d4a0f1 100644 --- a/WatchIt.Website/WatchIt.Website/appsettings.json +++ b/WatchIt.Website/WatchIt.Website/appsettings.json @@ -19,11 +19,13 @@ "Base": "/accounts", "Register": "/register", "Authenticate": "/authenticate", - "AuthenticateRefresh": "/authenticate-refresh", + "AuthenticateRefresh": "/authenticate_refresh", "Logout": "/logout", - "GetProfilePicture": "/{0}/profile-picture", + "GetAccountProfilePicture": "/{0}/profile_picture", + "PutAccountProfilePicture": "/profile_picture", + "DeleteAccountProfilePicture": "/profile_picture", "GetAccountInfo": "/{0}/info", - "PutAccountInfo": "/info", + "PutAccountProfileInfo": "/profile_info", "GetAccountRatedMovies": "/{0}/movies", "GetAccountRatedSeries": "/{0}/series", "GetAccountRatedPersons": "/{0}/persons" diff --git a/WatchIt.Website/WatchIt.Website/wwwroot/css/panel.css b/WatchIt.Website/WatchIt.Website/wwwroot/css/panel.css index 0e200f3..6dcb155 100644 --- a/WatchIt.Website/WatchIt.Website/wwwroot/css/panel.css +++ b/WatchIt.Website/WatchIt.Website/wwwroot/css/panel.css @@ -28,7 +28,7 @@ gap: 1rem; padding: 1rem 1.5rem !important; - background-color: rgba(0, 0, 0, 0.8); + background-color: rgba(0, 0, 0, 0.7); } .panel-menu > li > a { @@ -52,6 +52,14 @@ +/* SECTION HEADER */ + +.panel-section-header { + padding: 0.75rem 1.5rem; +} + + + /* BACKGROUNDS */ .panel-background-gold {