diff --git a/.dockerignore b/.dockerignore
new file mode 100644
index 0000000..1ea673d
--- /dev/null
+++ b/.dockerignore
@@ -0,0 +1,11 @@
+**/.git
+**/.vs
+**/.vscode
+**/bin
+**/obj
+**/node_modules
+**/.dockerignore
+**/Dockerfile
+**/*.md
+**/Migrations/**.Designer.cs
+*.db
diff --git a/BudgetApp.slnx b/BudgetApp.slnx
new file mode 100644
index 0000000..7efbdea
--- /dev/null
+++ b/BudgetApp.slnx
@@ -0,0 +1,3 @@
+
Loading...
+} +else if (accounts.Count == 0) +{ +Loading...
+} +else if (categories.Count == 0) +{ +
+ Request ID: @RequestId
+
+ Swapping to Development environment will display more detailed information about the error that occurred. +
++ The Development environment shouldn't be enabled for deployed applications. + It can result in displaying sensitive information from exceptions to end users. + For local debugging, enable the Development environment by setting the ASPNETCORE_ENVIRONMENT environment variable to Development + and restarting the app. +
+ +@code{ + [CascadingParameter] + private HttpContext? HttpContext { get; set; } + + private string? RequestId { get; set; } + private bool ShowRequestId => !string.IsNullOrEmpty(RequestId); + + protected override void OnInitialized() => + RequestId = Activity.Current?.Id ?? HttpContext?.TraceIdentifier; +} diff --git a/BudgetApp/Components/Pages/Home.razor b/BudgetApp/Components/Pages/Home.razor new file mode 100644 index 0000000..77df351 --- /dev/null +++ b/BudgetApp/Components/Pages/Home.razor @@ -0,0 +1,650 @@ +๏ปฟ@page "/" +@attribute [Authorize] +@inject DashboardService DashboardService +@inject TransactionService TransactionService +@inject AccountService AccountService +@inject AuthenticationStateProvider AuthStateProvider +@inject IJSRuntime JS +@implements IAsyncDisposable +@rendermode InteractiveServer + +Loading...
+} +else if (data.Accounts.Count == 0) +{ +Get started by adding an account and then recording transactions.
+No recent transactions.
+ } + else + { +No upcoming transactions.
+ } + else + { ++ Don't have an account? Register +
+ +@code { + [SupplyParameterFromForm] + private LoginModel Model { get; set; } = new(); + + private string? errorMessage; + + private async Task HandleLogin() + { + errorMessage = null; + + var result = await SignInManager.PasswordSignInAsync( + Model.Email, Model.Password, isPersistent: false, lockoutOnFailure: false); + + if (result.Succeeded) + { + Navigation.NavigateTo("/", forceLoad: true); + } + else + { + errorMessage = "Invalid email or password."; + } + } + + private sealed class LoginModel + { + [System.ComponentModel.DataAnnotations.Required(ErrorMessage = "Email is required")] + [System.ComponentModel.DataAnnotations.EmailAddress] + public string Email { get; set; } = string.Empty; + + [System.ComponentModel.DataAnnotations.Required(ErrorMessage = "Password is required")] + public string Password { get; set; } = string.Empty; + } +} diff --git a/BudgetApp/Components/Pages/Logout.razor b/BudgetApp/Components/Pages/Logout.razor new file mode 100644 index 0000000..be0c24c --- /dev/null +++ b/BudgetApp/Components/Pages/Logout.razor @@ -0,0 +1,12 @@ +@page "/logout" +@using Microsoft.AspNetCore.Identity +@inject SignInManagerSorry, the content you are looking for does not exist.
\ No newline at end of file diff --git a/BudgetApp/Components/Pages/Register.razor b/BudgetApp/Components/Pages/Register.razor new file mode 100644 index 0000000..a6dc449 --- /dev/null +++ b/BudgetApp/Components/Pages/Register.razor @@ -0,0 +1,83 @@ +@page "/register" +@layout AuthLayout +@using Microsoft.AspNetCore.Identity +@inject UserManager+ Already have an account? Sign in +
+ +@code { + [SupplyParameterFromForm] + private RegisterModel Model { get; set; } = new(); + + private string? errorMessage; + + private async Task HandleRegister() + { + errorMessage = null; + + var user = new ApplicationUser { UserName = Model.Email, Email = Model.Email }; + var result = await UserManager.CreateAsync(user, Model.Password); + + if (result.Succeeded) + { + await SignInManager.SignInAsync(user, isPersistent: false); + Navigation.NavigateTo("/", forceLoad: true); + } + else + { + errorMessage = string.Join(" ", result.Errors.Select(e => e.Description)); + } + } + + private sealed class RegisterModel + { + [System.ComponentModel.DataAnnotations.Required(ErrorMessage = "Email is required")] + [System.ComponentModel.DataAnnotations.EmailAddress(ErrorMessage = "Invalid email address")] + public string Email { get; set; } = string.Empty; + + [System.ComponentModel.DataAnnotations.Required(ErrorMessage = "Password is required")] + [System.ComponentModel.DataAnnotations.MinLength(8, ErrorMessage = "Password must be at least 8 characters")] + public string Password { get; set; } = string.Empty; + + [System.ComponentModel.DataAnnotations.Required(ErrorMessage = "Please confirm your password")] + [System.ComponentModel.DataAnnotations.Compare(nameof(Password), ErrorMessage = "Passwords do not match")] + public string ConfirmPassword { get; set; } = string.Empty; + } +} diff --git a/BudgetApp/Components/Pages/Settings.razor b/BudgetApp/Components/Pages/Settings.razor new file mode 100644 index 0000000..9c3e2e0 --- /dev/null +++ b/BudgetApp/Components/Pages/Settings.razor @@ -0,0 +1,185 @@ +@page "/settings" +@attribute [Authorize] +@using Microsoft.AspNetCore.Identity +@using Microsoft.EntityFrameworkCore +@inject UserManager+ This will permanently delete your account and all associated data (accounts, transactions, and categories). This action cannot be undone. +
+ + @if (!string.IsNullOrEmpty(deleteError)) + { +Loading...
+} +else if (transactions.Count == 0) +{ +| SetSort("date")'>Date @SortIndicator("date") | +SetSort("description")'>Description @SortIndicator("description") | +Account | +Category | +Recurrence | +SetSort("amount")'>Amount @SortIndicator("amount") | ++ |
|---|---|---|---|---|---|---|
| @t.Date.ToString("MMM dd, yyyy") | +@t.Description | ++ + @t.Account.Name + + | ++ @if (t.Category is not null) + { + @if (!string.IsNullOrEmpty(t.Category.Icon)) + { + @t.Category.Icon + } + @t.Category.Name + } + else + { + โ + } + | ++ @if (t.RecurrenceType != RecurrenceType.None) + { + ๐ @t.RecurrenceType + } + else if (t.ParentTransactionId is not null) + { + ๐ @(t.ParentTransaction?.RecurrenceType.ToString() ?? "Recurring") + } + | ++ @(t.Type == TransactionType.Income ? "+" : "-")@t.Account.FormatAmount(t.Amount) + | ++ + | +
This component demonstrates showing data.
+ +@if (forecasts == null) +{ +Loading...
+} +else +{ +| Date | +Temp. (C) | +Temp. (F) | +Summary | +
|---|---|---|---|
| @forecast.Date.ToShortDateString() | +@forecast.TemperatureC | +@forecast.TemperatureF | +@forecast.Summary | +