diff --git a/Coronado.Web/BlazorApp/BlazorApp.csproj b/Coronado.Web/BlazorApp/BlazorApp.csproj
new file mode 100644
index 0000000..9ee5d80
--- /dev/null
+++ b/Coronado.Web/BlazorApp/BlazorApp.csproj
@@ -0,0 +1,14 @@
+
+
+
+ net7.0
+ BlazorApp
+
+
+
+
+
+
+
+
+
diff --git a/Coronado.Web/BlazorApp/Pages/AccountPage.razor b/Coronado.Web/BlazorApp/Pages/AccountPage.razor
new file mode 100644
index 0000000..07af5e0
--- /dev/null
+++ b/Coronado.Web/BlazorApp/Pages/AccountPage.razor
@@ -0,0 +1,63 @@
+@page "/account"
+@using Coronado.Web.Controllers.Api
+@using Coronado.Web.Controllers.Dtos
+@inject HttpClient Http
+@inject NavigationManager Navigation
+@inject ISnackbar Snackbar
+
+
+
+ Accounts
+
+
+ Account Name
+ Current Balance
+ Currency
+ Actions
+
+
+ @context.Name
+ @context.CurrentBalance
+ @context.Currency
+
+ Edit
+ Delete
+
+
+
+ Add Account
+
+
+
+@code {
+ private List _accounts = new List();
+
+ protected override async Task OnInitializedAsync()
+ {
+ _accounts = await Http.GetFromJsonAsync>("api/accounts");
+ }
+
+ private void AddAccount()
+ {
+ Navigation.NavigateTo("/add-account");
+ }
+
+ private void EditAccount(AccountWithBalance account)
+ {
+ Navigation.NavigateTo($"/edit-account/{account.AccountId}");
+ }
+
+ private async Task DeleteAccount(Guid accountId)
+ {
+ var response = await Http.DeleteAsync($"api/accounts/{accountId}");
+ if (response.IsSuccessStatusCode)
+ {
+ Snackbar.Add("Account deleted successfully", Severity.Success);
+ _accounts = await Http.GetFromJsonAsync>("api/accounts");
+ }
+ else
+ {
+ Snackbar.Add("Failed to delete account", Severity.Error);
+ }
+ }
+}
diff --git a/Coronado.Web/BlazorApp/Pages/CategoryPage.razor b/Coronado.Web/BlazorApp/Pages/CategoryPage.razor
new file mode 100644
index 0000000..32509fe
--- /dev/null
+++ b/Coronado.Web/BlazorApp/Pages/CategoryPage.razor
@@ -0,0 +1,110 @@
+@page "/categories"
+@using Coronado.Web.Controllers.Api
+@inject HttpClient Http
+@inject NavigationManager Navigation
+@implements IDisposable
+
+Categories
+
+Categories
+Add Category
+
+
+
+ Name
+ Type
+ Parent
+ Actions
+
+
+ @context.Name
+ @context.Type
+ @GetParentCategoryName(context.ParentCategoryId)
+
+ Edit
+ Delete
+
+
+
+
+
+
+ @(_isNewCategory ? "Add Category" : "Edit Category")
+
+
+
+ None
+ @foreach (var parentCategory in _categories)
+ {
+ @parentCategory.Name
+ }
+
+
+
+ Save
+ Cancel
+
+
+
+@code {
+ private List _categories;
+ private Category _category = new Category();
+ private bool _isNewCategory;
+ private MudDialogInstance _dialog;
+
+ protected override async Task OnInitializedAsync()
+ {
+ _categories = await Http.GetFromJsonAsync>("api/categories");
+ }
+
+ private void AddCategory()
+ {
+ _category = new Category();
+ _isNewCategory = true;
+ _dialog.Show();
+ }
+
+ private async Task EditCategory(Category category)
+ {
+ _category = category;
+ _isNewCategory = false;
+ _dialog.Show();
+ }
+
+ private async Task SaveCategory()
+ {
+ if (_isNewCategory)
+ {
+ await Http.PostAsJsonAsync("api/categories", _category);
+ }
+ else
+ {
+ await Http.PutAsJsonAsync($"api/categories/{_category.CategoryId}", _category);
+ }
+ _categories = await Http.GetFromJsonAsync>("api/categories");
+ _dialog.Hide();
+ }
+
+ private async Task DeleteCategory(Guid categoryId)
+ {
+ await Http.DeleteAsync($"api/categories/{categoryId}");
+ _categories = await Http.GetFromJsonAsync>("api/categories");
+ }
+
+ private void Cancel()
+ {
+ _dialog.Hide();
+ }
+
+ private string GetParentCategoryName(Guid? parentCategoryId)
+ {
+ if (parentCategoryId == null) return "None";
+ var parentCategory = _categories.FirstOrDefault(c => c.CategoryId == parentCategoryId);
+ return parentCategory?.Name ?? "None";
+ }
+
+ public void Dispose()
+ {
+ _dialog?.Dispose();
+ }
+}
diff --git a/Coronado.Web/BlazorApp/Pages/TransactionPage.razor b/Coronado.Web/BlazorApp/Pages/TransactionPage.razor
new file mode 100644
index 0000000..f19b237
--- /dev/null
+++ b/Coronado.Web/BlazorApp/Pages/TransactionPage.razor
@@ -0,0 +1,69 @@
+@page "/transactions"
+@using Coronado.Web.Controllers.Api
+@using Coronado.Web.Controllers.Dtos
+@inject HttpClient Http
+@inject NavigationManager Navigation
+@inject ISnackbar Snackbar
+
+
+
+ Transactions
+
+
+ Date
+ Vendor
+ Category
+ Description
+ Debit
+ Credit
+ Actions
+
+
+ @context.TransactionDate.ToShortDateString()
+ @context.Vendor
+ @context.CategoryDisplay
+ @context.Description
+ @context.Debit
+ @context.Credit
+
+ Edit
+ Delete
+
+
+
+ Add Transaction
+
+
+
+@code {
+ private List _transactions = new List();
+
+ protected override async Task OnInitializedAsync()
+ {
+ _transactions = await Http.GetFromJsonAsync>("api/transactions");
+ }
+
+ private void AddTransaction()
+ {
+ Navigation.NavigateTo("/add-transaction");
+ }
+
+ private void EditTransaction(TransactionForDisplay transaction)
+ {
+ Navigation.NavigateTo($"/edit-transaction/{transaction.TransactionId}");
+ }
+
+ private async Task DeleteTransaction(Guid transactionId)
+ {
+ var response = await Http.DeleteAsync($"api/transactions/{transactionId}");
+ if (response.IsSuccessStatusCode)
+ {
+ Snackbar.Add("Transaction deleted successfully", Severity.Success);
+ _transactions = await Http.GetFromJsonAsync>("api/transactions");
+ }
+ else
+ {
+ Snackbar.Add("Failed to delete transaction", Severity.Error);
+ }
+ }
+}
diff --git a/Coronado.Web/BlazorApp/Program.cs b/Coronado.Web/BlazorApp/Program.cs
new file mode 100644
index 0000000..8638472
--- /dev/null
+++ b/Coronado.Web/BlazorApp/Program.cs
@@ -0,0 +1,13 @@
+using Microsoft.AspNetCore.Components.Web;
+using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
+using MudBlazor.Services;
+using BlazorApp;
+
+var builder = WebAssemblyHostBuilder.CreateDefault(args);
+builder.RootComponents.Add("#app");
+builder.RootComponents.Add("head::after");
+
+builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
+builder.Services.AddMudServices();
+
+await builder.Build().RunAsync();
diff --git a/Coronado.Web/BlazorApp/Shared/MainLayout.razor b/Coronado.Web/BlazorApp/Shared/MainLayout.razor
new file mode 100644
index 0000000..2f91dbb
--- /dev/null
+++ b/Coronado.Web/BlazorApp/Shared/MainLayout.razor
@@ -0,0 +1,20 @@
+@inherits LayoutComponentBase
+
+
+
+ Coronado
+
+
+
+
+ Home
+ Accounts
+ Categories
+ Transactions
+
+
+
+
+ @Body
+
+
diff --git a/Coronado.Web/BlazorApp/Startup.cs b/Coronado.Web/BlazorApp/Startup.cs
new file mode 100644
index 0000000..1b3ee2f
--- /dev/null
+++ b/Coronado.Web/BlazorApp/Startup.cs
@@ -0,0 +1,50 @@
+using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Hosting;
+using MudBlazor.Services;
+
+namespace BlazorApp
+{
+ public class Startup
+ {
+ public Startup(IConfiguration configuration)
+ {
+ Configuration = configuration;
+ }
+
+ public IConfiguration Configuration { get; }
+
+ public void ConfigureServices(IServiceCollection services)
+ {
+ services.AddRazorPages();
+ services.AddServerSideBlazor();
+ services.AddMudServices();
+ }
+
+ public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
+ {
+ if (env.IsDevelopment())
+ {
+ app.UseDeveloperExceptionPage();
+ }
+ else
+ {
+ app.UseExceptionHandler("/Error");
+ app.UseHsts();
+ }
+
+ app.UseHttpsRedirection();
+ app.UseStaticFiles();
+
+ app.UseRouting();
+
+ app.UseEndpoints(endpoints =>
+ {
+ endpoints.MapBlazorHub();
+ endpoints.MapFallbackToPage("/_Host");
+ });
+ }
+ }
+}
diff --git a/Coronado.Web/Coronado.Web.csproj b/Coronado.Web/Coronado.Web.csproj
index d3ca729..d35a73c 100644
--- a/Coronado.Web/Coronado.Web.csproj
+++ b/Coronado.Web/Coronado.Web.csproj
@@ -39,6 +39,11 @@
+
+
+
+
+
diff --git a/Coronado.Web/Startup.cs b/Coronado.Web/Startup.cs
index fbe39d2..643d18b 100644
--- a/Coronado.Web/Startup.cs
+++ b/Coronado.Web/Startup.cs
@@ -13,6 +13,7 @@
using Microsoft.IdentityModel.Tokens;
using System.Text;
using Coronado.Web.Controllers.Api;
+using MudBlazor.Services;
namespace Coronado.Web
{
@@ -86,6 +87,10 @@ public void ConfigureServices(IServiceCollection services) {
services.AddMvc().AddNewtonsoftJson(
options => options.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore
);
+
+ services.AddRazorPages();
+ services.AddServerSideBlazor();
+ services.AddMudServices();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
@@ -116,6 +121,8 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILoggerF
endpoints.MapControllerRoute(
name:"admin",
pattern: "/admin/{controller=Home}/{action=Index}/{id?}");
+ endpoints.MapBlazorHub();
+ endpoints.MapFallbackToPage("/_Host");
});
app.UseSpa(spa => {
spa.Options.SourcePath = "ClientApp";