diff --git a/Start/Client/Components/Bookmark.razor b/Start/Client/Components/Bookmark.razor index 193daea..e2ae192 100644 --- a/Start/Client/Components/Bookmark.razor +++ b/Start/Client/Components/Bookmark.razor @@ -1,4 +1,4 @@ -
  • +
  • @if (!String.IsNullOrEmpty(this.Model.Notes)) {
    diff --git a/Start/Client/Components/BookmarkGroup.razor b/Start/Client/Components/BookmarkGroup.razor index e50c064..6f32d1a 100644 --- a/Start/Client/Components/BookmarkGroup.razor +++ b/Start/Client/Components/BookmarkGroup.razor @@ -2,6 +2,7 @@ @using Fluxor @using Start.Client.Store.State @using Start.Client.Store.Features.DeleteGroup +@using Start.Client.Store.Features.CreateBookmark @inherits Fluxor.Blazor.Web.Components.FluxorComponent @@ -33,7 +34,7 @@

    No Bookmarks

    -
    @@ -89,6 +90,7 @@ protected void OnCreateBookmarkClicked() { - // Placeholder + dispatch.Dispatch(new ShowCreateBookmarkFormAction(this.Group.BookmarkGroupId, + this.Group.Title)); } } diff --git a/Start/Client/Components/CreateBookmark.razor b/Start/Client/Components/CreateBookmark.razor new file mode 100644 index 0000000..5c9ba90 --- /dev/null +++ b/Start/Client/Components/CreateBookmark.razor @@ -0,0 +1,91 @@ +@using Start.Client.Store.Features.CreateBookmark +@using Fluxor + +@inherits Fluxor.Blazor.Web.Components.FluxorComponent + +@inject IActionSubscriber actionSubscriber +@inject IDispatcher dispatch +@inject IState state + + + + + + @if (this.state.Value.CreateBookmarkErrorMessage != null) + { + + @this.state.Value.CreateBookmarkErrorMessage + + } + + + +
    +
    +
    +
    + + +
    +
    +
    +
    + + +
    +
    +
    +
    + + +
    +
    +
    +
    +
    +
    + @if (this.state.Value.IsLoadingCreateBookmark) + { + + } + else + { + + } +
    +
    +
    +
    +
    +
    + +@code { + protected BookmarkDto Model { get; set; } = new BookmarkDto("", "", null, 0); + + protected override void OnInitialized() + { + base.OnInitialized(); + + this.Model = new BookmarkDto("", "", null, this.state.Value.GroupId); + + actionSubscriber.SubscribeToAction(this, + a => this.Model.BookmarkGroupId = a.GroupId); + } + + protected void OnSubmit() + { + dispatch.Dispatch(new SubmitCreateBookmarkAction(this.Model)); + } + + protected void OnDialogClose() + { + dispatch.Dispatch(new HideCreateBookmarkFormAction()); + } +} diff --git a/Start/Client/Pages/Index.razor b/Start/Client/Pages/Index.razor index c13ebbb..227fcd6 100644 --- a/Start/Client/Pages/Index.razor +++ b/Start/Client/Pages/Index.razor @@ -80,6 +80,8 @@ else + + } diff --git a/Start/Client/Sass/app.scss b/Start/Client/Sass/app.scss index 9d520ed..08c1eb0 100644 --- a/Start/Client/Sass/app.scss +++ b/Start/Client/Sass/app.scss @@ -191,6 +191,32 @@ ul.bookmarks { list-style: none; margin: 0; + margin-block: 0; + + .bookmark { + margin-top: 0; + + a { + display: inline-block; + width: 100%; + padding: 0.5em 0.83em; + + &:hover { + text-decoration: none; + background-color: $bg-color-dark; + } + } + + &:last-child { + a { + border-radius: 0 0 0.4em 0.4em; + } + } + + &:not(:last-child) { + border-bottom: solid 1px $border-color; + } + } li.noBookmarksItem { margin-top: 0; diff --git a/Start/Client/Sass/dark-mode.scss b/Start/Client/Sass/dark-mode.scss index 6e4d3ca..700dc81 100644 --- a/Start/Client/Sass/dark-mode.scss +++ b/Start/Client/Sass/dark-mode.scss @@ -74,6 +74,10 @@ $dark-border-color: darken($dark-color, 10%); // Cards .card { border-color: $dark-border-color; + + .card-body { + background-color: $dark-bg-color-dark; + } } @@ -124,4 +128,20 @@ $dark-border-color: darken($dark-color, 10%); #sidebar .accountActions { border-top-color: $dark-border-color; } + + ul.bookmarks { + .bookmark { + &:not(:last-child) { + border-bottom: solid 1px $dark-border-color; + } + + a, a:visited { + color: $dark-primary-color; + } + + a:hover { + background-color: $dark-bg-color; + } + } + } } diff --git a/Start/Client/Start.Client.csproj b/Start/Client/Start.Client.csproj index 43324b0..56ef6d0 100644 --- a/Start/Client/Start.Client.csproj +++ b/Start/Client/Start.Client.csproj @@ -130,6 +130,7 @@ + @@ -147,5 +148,6 @@ + diff --git a/Start/Client/Store/Features/CreateBookmark/CreateBookmarkActions.cs b/Start/Client/Store/Features/CreateBookmark/CreateBookmarkActions.cs new file mode 100644 index 0000000..f82e5cc --- /dev/null +++ b/Start/Client/Store/Features/CreateBookmark/CreateBookmarkActions.cs @@ -0,0 +1,35 @@ +using Start.Shared; + +namespace Start.Client.Store.Features.CreateBookmark { + public class ShowCreateBookmarkFormAction { + public int GroupId { get; init; } + public string GroupTitle { get; init; } + + public ShowCreateBookmarkFormAction(int groupId, string groupTitle) { + this.GroupId = groupId; + this.GroupTitle = groupTitle; + } + } + + public class HideCreateBookmarkFormAction { } + + public class FetchCreateBookmarkAction { } + + public class ReceivedCreateBookmarkAction { } + + public class ErrorFetchingCreateBookmarkAction { + public string ErrorMessage { get; init; } + + public ErrorFetchingCreateBookmarkAction(string errorMessage) { + this.ErrorMessage = errorMessage; + } + } + + public class SubmitCreateBookmarkAction { + public BookmarkDto NewBookmark { get; init; } + + public SubmitCreateBookmarkAction(BookmarkDto newBookmark) { + this.NewBookmark = newBookmark; + } + } +} diff --git a/Start/Client/Store/Features/CreateBookmark/CreateBookmarkEffects.cs b/Start/Client/Store/Features/CreateBookmark/CreateBookmarkEffects.cs new file mode 100644 index 0000000..b0436a8 --- /dev/null +++ b/Start/Client/Store/Features/CreateBookmark/CreateBookmarkEffects.cs @@ -0,0 +1,45 @@ +using System.Threading.Tasks; +using Fluxor; +using Microsoft.AspNetCore.Components.WebAssembly.Authentication; +using Start.Shared.Api; +using Start.Client.Store.Features.CurrentContainer; + +namespace Start.Client.Store.Features.CreateBookmark { + public class CreateBookmarkEffects { + public IBookmarksApi BookmarksApi { get; init; } + + public CreateBookmarkEffects(IBookmarksApi bookmarksApi) { + this.BookmarksApi = bookmarksApi; + } + + [EffectMethod] + public async Task SubmitCreateBookmark(SubmitCreateBookmarkAction action, + IDispatcher dispatch) { + dispatch.Dispatch(new FetchCreateBookmarkAction()); + + try { + Refit.ApiResponse? apiResponse = await this.BookmarksApi + .CreateBookmark(action.NewBookmark.Title, action.NewBookmark.Url, + action.NewBookmark.Notes, action.NewBookmark.BookmarkGroupId); + + if (!apiResponse.IsSuccessStatusCode) { + dispatch.Dispatch(new ErrorFetchingCreateBookmarkAction( + "Error creating bookmark group: Status code " + apiResponse.StatusCode.ToString())); + return; + } + + if (apiResponse.Content == null) { + dispatch.Dispatch(new ErrorFetchingCreateBookmarkAction( + "Error creating bookmark group")); + return; + } + + dispatch.Dispatch(new AddBookmarkAction(apiResponse.Content)); + dispatch.Dispatch(new ReceivedCreateBookmarkAction()); + dispatch.Dispatch(new HideCreateBookmarkFormAction()); + } catch (AccessTokenNotAvailableException e) { + e.Redirect(); + } + } + } +} diff --git a/Start/Client/Store/Features/CreateBookmark/CreateBookmarkFeature.cs b/Start/Client/Store/Features/CreateBookmark/CreateBookmarkFeature.cs new file mode 100644 index 0000000..25f470f --- /dev/null +++ b/Start/Client/Store/Features/CreateBookmark/CreateBookmarkFeature.cs @@ -0,0 +1,11 @@ +using Fluxor; + +namespace Start.Client.Store.Features.CreateBookmark { + public class CreateBookmarkFeature : Feature { + public override string GetName() => "Create Bookmark"; + + protected override CreateBookmarkState GetInitialState() { + return new CreateBookmarkState(); + } + } +} diff --git a/Start/Client/Store/Features/CreateBookmark/CreateBookmarkReducers.cs b/Start/Client/Store/Features/CreateBookmark/CreateBookmarkReducers.cs new file mode 100644 index 0000000..f6c6867 --- /dev/null +++ b/Start/Client/Store/Features/CreateBookmark/CreateBookmarkReducers.cs @@ -0,0 +1,41 @@ +using Fluxor; + +namespace Start.Client.Store.Features.CreateBookmark { + public static class CreateBookmarkReducers { + [ReducerMethod] + public static CreateBookmarkState ShowCreateBookmarkForm(CreateBookmarkState state, + ShowCreateBookmarkFormAction action) { + return state with { + ShowCreateBookmarkForm = true, + GroupId = action.GroupId, + GroupTitle = action.GroupTitle, + IsLoadingCreateBookmark = false, + CreateBookmarkErrorMessage = null + }; + } + + [ReducerMethod(typeof(HideCreateBookmarkFormAction))] + public static CreateBookmarkState HideCreateBookmarkForm(CreateBookmarkState state) { + return state with { + ShowCreateBookmarkForm = false + }; + } + + [ReducerMethod(typeof(FetchCreateBookmarkAction))] + public static CreateBookmarkState FetchCreateBookmark(CreateBookmarkState state) { + return state with { + IsLoadingCreateBookmark = true, + CreateBookmarkErrorMessage = null + }; + } + + [ReducerMethod] + public static CreateBookmarkState ErrorFetchingCreateBookmark(CreateBookmarkState state, + ErrorFetchingCreateBookmarkAction action) { + return state with { + CreateBookmarkErrorMessage = action.ErrorMessage, + IsLoadingCreateBookmark = false + }; + } + } +} diff --git a/Start/Client/Store/Features/CreateBookmark/CreateBookmarkState.cs b/Start/Client/Store/Features/CreateBookmark/CreateBookmarkState.cs new file mode 100644 index 0000000..929f454 --- /dev/null +++ b/Start/Client/Store/Features/CreateBookmark/CreateBookmarkState.cs @@ -0,0 +1,15 @@ +using Start.Client.Store.State; + +namespace Start.Client.Store.Features.CreateBookmark { + public record CreateBookmarkState : RootState { + public bool ShowCreateBookmarkForm { get; init; } + public int GroupId { get; init; } + public string GroupTitle { get; init; } + public bool IsLoadingCreateBookmark { get; init; } + public string? CreateBookmarkErrorMessage { get; init; } + + public CreateBookmarkState() { + this.GroupTitle = ""; + } + } +} diff --git a/Start/Client/Store/Features/CreateGroup/CreateGroupEffects.cs b/Start/Client/Store/Features/CreateGroup/CreateGroupEffects.cs index ed1ef00..d5329b7 100644 --- a/Start/Client/Store/Features/CreateGroup/CreateGroupEffects.cs +++ b/Start/Client/Store/Features/CreateGroup/CreateGroupEffects.cs @@ -33,7 +33,13 @@ namespace Start.Client.Store.Features.CreateGroup { return; } - dispatch.Dispatch(new AddBookmarkGroupAction(action.NewGroup)); + if (apiResponse.Content == null) { + dispatch.Dispatch(new ErrorFetchingCreateGroupAction( + "Error creating bookmark group")); + return; + } + + dispatch.Dispatch(new AddBookmarkGroupAction(apiResponse.Content)); dispatch.Dispatch(new RecievedCreateGroupAction()); dispatch.Dispatch(new HideCreateGroupFormAction()); } catch (AccessTokenNotAvailableException e) { diff --git a/Start/Client/Store/Features/CurrentContainer/CurrentContainerActions.cs b/Start/Client/Store/Features/CurrentContainer/CurrentContainerActions.cs index a8a5724..e00d3a6 100644 --- a/Start/Client/Store/Features/CurrentContainer/CurrentContainerActions.cs +++ b/Start/Client/Store/Features/CurrentContainer/CurrentContainerActions.cs @@ -44,4 +44,20 @@ namespace Start.Client.Store.Features.CurrentContainer { this.BookmarkGroupId = bookmarkGroupId; } } + + public class AddBookmarkAction { + public BookmarkDto Bookmark { get; init; } + + public AddBookmarkAction(BookmarkDto bookmark) { + this.Bookmark = bookmark; + } + } + + public class RemoveBookmarkAction { + public int BookmarkId { get; init; } + + public RemoveBookmarkAction(int bookmarkId) { + this.BookmarkId = bookmarkId; + } + } } diff --git a/Start/Client/Store/Features/CurrentContainer/CurrentContainerReducers.cs b/Start/Client/Store/Features/CurrentContainer/CurrentContainerReducers.cs index f4341c5..23b05c5 100644 --- a/Start/Client/Store/Features/CurrentContainer/CurrentContainerReducers.cs +++ b/Start/Client/Store/Features/CurrentContainer/CurrentContainerReducers.cs @@ -78,5 +78,56 @@ namespace Start.Client.Store.Features.CurrentContainer { } }; } + + [ReducerMethod] + public static RootState AddBookmark(RootState state, AddBookmarkAction action) { + BookmarkContainerDto? container = state.CurrentContainerState.Container; + + if (container == null) + return state; + + List? groups = container.BookmarkGroups + ?.Select(bg => { + if (bg.BookmarkGroupId == action.Bookmark.BookmarkGroupId) { + return new BookmarkGroupDto(bg.BookmarkGroupId, bg.Title, bg.Color, + bg.BookmarkContainerId, + bg.Bookmarks? + .Concat(new List { action.Bookmark }) + .ToList()); + } + + return bg; + }) + .ToList(); + + return state with { + CurrentContainerState = state.CurrentContainerState with { + Container = new BookmarkContainerDto(container.BookmarkContainerId, + container.Title, groups) + } + }; + } + + [ReducerMethod] + public static RootState RemoveBookmark(RootState state, RemoveBookmarkAction action) { + BookmarkContainerDto? container = state.CurrentContainerState.Container; + + if (container == null) + return state; + + List? groups = container.BookmarkGroups + ?.Select(bg => new BookmarkGroupDto(bg.BookmarkGroupId, bg.Title, bg.Color, + bg.BookmarkContainerId, bg.Bookmarks + ?.Where(b => b.BookmarkId != action.BookmarkId) + .ToList())) + .ToList(); + + return state with { + CurrentContainerState = state.CurrentContainerState with { + Container = new BookmarkContainerDto(container.BookmarkContainerId, + container.Title, groups) + } + }; + } } } diff --git a/Start/Client/Store/Features/DeleteGroup/DeleteGroupState.cs b/Start/Client/Store/Features/DeleteGroup/DeleteGroupState.cs index 7dbd809..4aebb30 100644 --- a/Start/Client/Store/Features/DeleteGroup/DeleteGroupState.cs +++ b/Start/Client/Store/Features/DeleteGroup/DeleteGroupState.cs @@ -1,6 +1,7 @@ -using System; +using Start.Client.Store.State; + namespace Start.Client.Store.Features.DeleteGroup { - public record DeleteGroupState { + public record DeleteGroupState : RootState { public bool ShowDeleteGroupForm { get; init; } public int BookmarkGroupIdToDelete { get; init; } public string BookmarkGroupTitleToDelete { get; init; } diff --git a/Start/Server/Extensions/BookmarkMaps.cs b/Start/Server/Extensions/BookmarkMaps.cs index 89860ac..63f771d 100644 --- a/Start/Server/Extensions/BookmarkMaps.cs +++ b/Start/Server/Extensions/BookmarkMaps.cs @@ -6,7 +6,7 @@ namespace Start.Server.Extensions { public static class BookmarkMaps { public static BookmarkDto MapToDto(this Bookmark bookmark) { return new BookmarkDto(bookmark.BookmarkId, bookmark.Title, bookmark.Url, - bookmark.Notes); + bookmark.Notes, bookmark.BookmarkGroupId); } public static BookmarkGroupDto MapToDto(this BookmarkGroup bookmarkGroup) { diff --git a/Start/Shared/Api/IBookmarksApi.cs b/Start/Shared/Api/IBookmarksApi.cs index a9d1387..352f684 100644 --- a/Start/Shared/Api/IBookmarksApi.cs +++ b/Start/Shared/Api/IBookmarksApi.cs @@ -4,7 +4,7 @@ using System.Net.Http; namespace Start.Shared.Api { public interface IBookmarksApi { - [Get("{bookmarkId}")] + [Get("/{bookmarkId}")] Task> GetBookmark(int bookmarkId); [Post("/Create")] diff --git a/Start/Shared/BookmarkDto.cs b/Start/Shared/BookmarkDto.cs index 04ce10d..3468191 100644 --- a/Start/Shared/BookmarkDto.cs +++ b/Start/Shared/BookmarkDto.cs @@ -1,5 +1,5 @@ -using System; -using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations; +using System.Text.Json.Serialization; namespace Start.Shared { public class BookmarkDto { @@ -11,14 +11,19 @@ namespace Start.Shared { [StringLength(5000)] public string? Notes { get; set; } - public BookmarkDto(string title, string url, string? notes) { + public int BookmarkGroupId { get; set; } + + public BookmarkDto(string title, string url, string? notes, int bookmarkGroupId) { this.Title = title; this.Url = url; this.Notes = notes; + this.BookmarkGroupId = bookmarkGroupId; } - public BookmarkDto(int bookmarkId, string title, string url, string? notes) - : this(title, url, notes) { + [JsonConstructor] + public BookmarkDto(int bookmarkId, string title, string url, string? notes, + int bookmarkGroupId) + : this(title, url, notes, bookmarkGroupId) { this.BookmarkId = bookmarkId; } }