diff --git a/Layout/MainLayout.razor.css b/DungeonMasterDashboard/Layout/MainLayout.razor.css
similarity index 94%
rename from Layout/MainLayout.razor.css
rename to DungeonMasterDashboard/Layout/MainLayout.razor.css
index ecf25e5..baef3ee 100644
--- a/Layout/MainLayout.razor.css
+++ b/DungeonMasterDashboard/Layout/MainLayout.razor.css
@@ -1,77 +1,77 @@
-.page {
- position: relative;
- display: flex;
- flex-direction: column;
-}
-
-main {
- flex: 1;
-}
-
-.sidebar {
- background-image: linear-gradient(180deg, rgb(5, 39, 103) 0%, #3a0647 70%);
-}
-
-.top-row {
- background-color: #f7f7f7;
- border-bottom: 1px solid #d6d5d5;
- justify-content: flex-end;
- height: 3.5rem;
- display: flex;
- align-items: center;
-}
-
- .top-row ::deep a, .top-row ::deep .btn-link {
- white-space: nowrap;
- margin-left: 1.5rem;
- text-decoration: none;
- }
-
- .top-row ::deep a:hover, .top-row ::deep .btn-link:hover {
- text-decoration: underline;
- }
-
- .top-row ::deep a:first-child {
- overflow: hidden;
- text-overflow: ellipsis;
- }
-
-@media (max-width: 640.98px) {
- .top-row {
- justify-content: space-between;
- }
-
- .top-row ::deep a, .top-row ::deep .btn-link {
- margin-left: 0;
- }
-}
-
-@media (min-width: 641px) {
- .page {
- flex-direction: row;
- }
-
- .sidebar {
- width: 250px;
- height: 100vh;
- position: sticky;
- top: 0;
- }
-
- .top-row {
- position: sticky;
- top: 0;
- z-index: 1;
- }
-
- .top-row.auth ::deep a:first-child {
- flex: 1;
- text-align: right;
- width: 0;
- }
-
- .top-row, article {
- padding-left: 2rem !important;
- padding-right: 1.5rem !important;
- }
-}
+.page {
+ position: relative;
+ display: flex;
+ flex-direction: column;
+}
+
+main {
+ flex: 1;
+}
+
+.sidebar {
+ background-image: linear-gradient(180deg, rgb(5, 39, 103) 0%, #3a0647 70%);
+}
+
+.top-row {
+ background-color: #f7f7f7;
+ border-bottom: 1px solid #d6d5d5;
+ justify-content: flex-end;
+ height: 3.5rem;
+ display: flex;
+ align-items: center;
+}
+
+ .top-row ::deep a, .top-row ::deep .btn-link {
+ white-space: nowrap;
+ margin-left: 1.5rem;
+ text-decoration: none;
+ }
+
+ .top-row ::deep a:hover, .top-row ::deep .btn-link:hover {
+ text-decoration: underline;
+ }
+
+ .top-row ::deep a:first-child {
+ overflow: hidden;
+ text-overflow: ellipsis;
+ }
+
+@media (max-width: 640.98px) {
+ .top-row {
+ justify-content: space-between;
+ }
+
+ .top-row ::deep a, .top-row ::deep .btn-link {
+ margin-left: 0;
+ }
+}
+
+@media (min-width: 641px) {
+ .page {
+ flex-direction: row;
+ }
+
+ .sidebar {
+ width: 250px;
+ height: 100vh;
+ position: sticky;
+ top: 0;
+ }
+
+ .top-row {
+ position: sticky;
+ top: 0;
+ z-index: 1;
+ }
+
+ .top-row.auth ::deep a:first-child {
+ flex: 1;
+ text-align: right;
+ width: 0;
+ }
+
+ .top-row, article {
+ padding-left: 2rem !important;
+ padding-right: 1.5rem !important;
+ }
+}
diff --git a/DungeonMasterDashboard/Layout/NavMenu.razor b/DungeonMasterDashboard/Layout/NavMenu.razor
new file mode 100644
index 0000000..6e14523
--- /dev/null
+++ b/DungeonMasterDashboard/Layout/NavMenu.razor
@@ -0,0 +1,49 @@
+
+
+
+
+@code {
+ private bool collapseNavMenu = true;
+
+ private string? NavMenuCssClass => collapseNavMenu ? "collapse" : null;
+
+ private void ToggleNavMenu()
+ {
+ collapseNavMenu = !collapseNavMenu;
+ }
+}
diff --git a/Layout/NavMenu.razor.css b/DungeonMasterDashboard/Layout/NavMenu.razor.css
similarity index 96%
rename from Layout/NavMenu.razor.css
rename to DungeonMasterDashboard/Layout/NavMenu.razor.css
index 617b89c..6724fd1 100644
--- a/Layout/NavMenu.razor.css
+++ b/DungeonMasterDashboard/Layout/NavMenu.razor.css
@@ -1,83 +1,83 @@
-.navbar-toggler {
- background-color: rgba(255, 255, 255, 0.1);
-}
-
-.top-row {
- min-height: 3.5rem;
- background-color: rgba(0,0,0,0.4);
-}
-
-.navbar-brand {
- font-size: 1.1rem;
-}
-
-.bi {
- display: inline-block;
- position: relative;
- width: 1.25rem;
- height: 1.25rem;
- margin-right: 0.75rem;
- top: -1px;
- background-size: cover;
-}
-
-.bi-house-door-fill-nav-menu {
- background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-house-door-fill' viewBox='0 0 16 16'%3E%3Cpath d='M6.5 14.5v-3.505c0-.245.25-.495.5-.495h2c.25 0 .5.25.5.5v3.5a.5.5 0 0 0 .5.5h4a.5.5 0 0 0 .5-.5v-7a.5.5 0 0 0-.146-.354L13 5.793V2.5a.5.5 0 0 0-.5-.5h-1a.5.5 0 0 0-.5.5v1.293L8.354 1.146a.5.5 0 0 0-.708 0l-6 6A.5.5 0 0 0 1.5 7.5v7a.5.5 0 0 0 .5.5h4a.5.5 0 0 0 .5-.5Z'/%3E%3C/svg%3E");
-}
-
-.bi-plus-square-fill-nav-menu {
- background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-plus-square-fill' viewBox='0 0 16 16'%3E%3Cpath d='M2 0a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2H2zm6.5 4.5v3h3a.5.5 0 0 1 0 1h-3v3a.5.5 0 0 1-1 0v-3h-3a.5.5 0 0 1 0-1h3v-3a.5.5 0 0 1 1 0z'/%3E%3C/svg%3E");
-}
-
-.bi-list-nested-nav-menu {
- background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-list-nested' viewBox='0 0 16 16'%3E%3Cpath fill-rule='evenodd' d='M4.5 11.5A.5.5 0 0 1 5 11h10a.5.5 0 0 1 0 1H5a.5.5 0 0 1-.5-.5zm-2-4A.5.5 0 0 1 3 7h10a.5.5 0 0 1 0 1H3a.5.5 0 0 1-.5-.5zm-2-4A.5.5 0 0 1 1 3h10a.5.5 0 0 1 0 1H1a.5.5 0 0 1-.5-.5z'/%3E%3C/svg%3E");
-}
-
-.nav-item {
- font-size: 0.9rem;
- padding-bottom: 0.5rem;
-}
-
- .nav-item:first-of-type {
- padding-top: 1rem;
- }
-
- .nav-item:last-of-type {
- padding-bottom: 1rem;
- }
-
- .nav-item ::deep a {
- color: #d7d7d7;
- border-radius: 4px;
- height: 3rem;
- display: flex;
- align-items: center;
- line-height: 3rem;
- }
-
-.nav-item ::deep a.active {
- background-color: rgba(255,255,255,0.37);
- color: white;
-}
-
-.nav-item ::deep a:hover {
- background-color: rgba(255,255,255,0.1);
- color: white;
-}
-
-@media (min-width: 641px) {
- .navbar-toggler {
- display: none;
- }
-
- .collapse {
- /* Never collapse the sidebar for wide screens */
- display: block;
- }
-
- .nav-scrollable {
- /* Allow sidebar to scroll for tall menus */
- height: calc(100vh - 3.5rem);
- overflow-y: auto;
- }
-}
+.navbar-toggler {
+ background-color: rgba(255, 255, 255, 0.1);
+}
+
+.top-row {
+ min-height: 3.5rem;
+ background-color: rgba(0,0,0,0.4);
+}
+
+.navbar-brand {
+ font-size: 1.1rem;
+}
+
+.bi {
+ display: inline-block;
+ position: relative;
+ width: 1.25rem;
+ height: 1.25rem;
+ margin-right: 0.75rem;
+ top: -1px;
+ background-size: cover;
+}
+
+.bi-house-door-fill-nav-menu {
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-house-door-fill' viewBox='0 0 16 16'%3E%3Cpath d='M6.5 14.5v-3.505c0-.245.25-.495.5-.495h2c.25 0 .5.25.5.5v3.5a.5.5 0 0 0 .5.5h4a.5.5 0 0 0 .5-.5v-7a.5.5 0 0 0-.146-.354L13 5.793V2.5a.5.5 0 0 0-.5-.5h-1a.5.5 0 0 0-.5.5v1.293L8.354 1.146a.5.5 0 0 0-.708 0l-6 6A.5.5 0 0 0 1.5 7.5v7a.5.5 0 0 0 .5.5h4a.5.5 0 0 0 .5-.5Z'/%3E%3C/svg%3E");
+}
+
+.bi-plus-square-fill-nav-menu {
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-plus-square-fill' viewBox='0 0 16 16'%3E%3Cpath d='M2 0a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2H2zm6.5 4.5v3h3a.5.5 0 0 1 0 1h-3v3a.5.5 0 0 1-1 0v-3h-3a.5.5 0 0 1 0-1h3v-3a.5.5 0 0 1 1 0z'/%3E%3C/svg%3E");
+}
+
+.bi-list-nested-nav-menu {
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-list-nested' viewBox='0 0 16 16'%3E%3Cpath fill-rule='evenodd' d='M4.5 11.5A.5.5 0 0 1 5 11h10a.5.5 0 0 1 0 1H5a.5.5 0 0 1-.5-.5zm-2-4A.5.5 0 0 1 3 7h10a.5.5 0 0 1 0 1H3a.5.5 0 0 1-.5-.5zm-2-4A.5.5 0 0 1 1 3h10a.5.5 0 0 1 0 1H1a.5.5 0 0 1-.5-.5z'/%3E%3C/svg%3E");
+}
+
+.nav-item {
+ font-size: 0.9rem;
+ padding-bottom: 0.5rem;
+}
+
+ .nav-item:first-of-type {
+ padding-top: 1rem;
+ }
+
+ .nav-item:last-of-type {
+ padding-bottom: 1rem;
+ }
+
+ .nav-item ::deep a {
+ color: #d7d7d7;
+ border-radius: 4px;
+ height: 3rem;
+ display: flex;
+ align-items: center;
+ line-height: 3rem;
+ }
+
+.nav-item ::deep a.active {
+ background-color: rgba(255,255,255,0.37);
+ color: white;
+}
+
+.nav-item ::deep a:hover {
+ background-color: rgba(255,255,255,0.1);
+ color: white;
+}
+
+@media (min-width: 641px) {
+ .navbar-toggler {
+ display: none;
+ }
+
+ .collapse {
+ /* Never collapse the sidebar for wide screens */
+ display: block;
+ }
+
+ .nav-scrollable {
+ /* Allow sidebar to scroll for tall menus */
+ height: calc(100vh - 3.5rem);
+ overflow-y: auto;
+ }
+}
diff --git a/DungeonMasterDashboard/Layout/RedirectToLogin.razor b/DungeonMasterDashboard/Layout/RedirectToLogin.razor
new file mode 100644
index 0000000..bf01957
--- /dev/null
+++ b/DungeonMasterDashboard/Layout/RedirectToLogin.razor
@@ -0,0 +1,8 @@
+@inject NavigationManager Navigation
+
+@code {
+ protected override void OnInitialized()
+ {
+ Navigation.NavigateTo("auth/login");
+ }
+}
diff --git a/DungeonMasterDashboard/Models/RegisterDto.cs b/DungeonMasterDashboard/Models/RegisterDto.cs
new file mode 100644
index 0000000..50ad32a
--- /dev/null
+++ b/DungeonMasterDashboard/Models/RegisterDto.cs
@@ -0,0 +1,8 @@
+namespace DungeonMasterDashboard.Models
+{
+ public class RegisterDto
+ {
+ public string Email { get; set; } = "";
+ public string Password { get; set; } = "";
+ }
+}
diff --git a/DungeonMasterDashboard/Models/UserProfile.cs b/DungeonMasterDashboard/Models/UserProfile.cs
new file mode 100644
index 0000000..8b4b0d2
--- /dev/null
+++ b/DungeonMasterDashboard/Models/UserProfile.cs
@@ -0,0 +1,11 @@
+namespace DungeonMasterDashboard.Models
+{
+ public class UserProfile
+ {
+ public string Id { get; set; } = "";
+ public string Name { get; set; } = "";
+ public string Email { get; set; } = "";
+ public string PhoneNumber { get; set; } = "";
+
+ }
+}
diff --git a/DungeonMasterDashboard/Pages/Authentication.razor b/DungeonMasterDashboard/Pages/Authentication.razor
new file mode 100644
index 0000000..183b5cf
--- /dev/null
+++ b/DungeonMasterDashboard/Pages/Authentication.razor
@@ -0,0 +1,149 @@
+@page "/auth/{action}"
+
+DMD @Action
+
+@if (Action == "login")
+{
+
+
Login
+
+
+ @if (error.Length > 0)
+ {
+
+ @error
+
+
+ }
+
+ @*Log In Form*@
+ @*EMAIL*@
+
+
+
+
+
+ @*PASSWORD*@
+
+
+
+
+
+ @* LOG IN BUTTON AND CANCEL
+
*@
+
+} else if (Action == "register")
+{
+
+
+
+
Register
+
+
+ @if (errors.Length > 0)
+ {
+
+
+ @foreach (var error in errors)
+ {
+ - @error
+ }
+
+
+ }
+
+ @*Register Form*@
+ @*EMAIL*@
+
+
+ @*PASSWORD*@
+
+
+ @*CONFIRM PASSWORD*@
+
+
+
+
+
+
+
+ @* REGISTER BUTTON & CANCEL
+
*@
+
+
+
+}
+
+@* @inject AuthenticationStateProvider provider *@
+@inject NavigationManager navManager
+@code{
+ [Parameter] public string? Action { get; set; }
+
+ private string email = "";
+ private string password = "";
+ private string error = "";
+
+ // Registration fields
+ private string[] errors = [];
+ private string confirmPassword = "";
+
+ // private async Task LoginAsync()
+ // {
+ // var authStateProvider = (CustomAuthenticationStateProvider)provider;
+ // var formResult = await authStateProvider.LoginAsync(email, password);
+ // if (formResult.Success)
+ // {
+ // navManager.NavigateTo("/");
+ // } else
+ // {
+ // error = formResult.Errors[0];
+ // }
+ // }
+
+ // private async Task RegisterAsync()
+ // {
+ // if (password != confirmPassword)
+ // {
+ // errors = ["Password and Confirm Password do not match!"];
+ // return;
+ // }
+ // var authStateProvider = (CustomAuthenticationStateProvider)provider;
+ // var registerDto = new RegisterDto
+ // {
+ // Email = email,
+ // Password = password
+ // };
+ // var formResult = await authStateProvider.RegisterAsync(registerDto);
+ // if (formResult.Success)
+ // {
+ // navManager.NavigateTo("/");
+ // } else
+ // {
+ // errors = formResult.Errors;
+ // }
+ // }
+}
diff --git a/Pages/Counter.razor b/DungeonMasterDashboard/Pages/Counter.razor
similarity index 93%
rename from Pages/Counter.razor
rename to DungeonMasterDashboard/Pages/Counter.razor
index 9340be8..625853e 100644
--- a/Pages/Counter.razor
+++ b/DungeonMasterDashboard/Pages/Counter.razor
@@ -1,6 +1,5 @@
@page "/counter"
@using Microsoft.AspNetCore.Authorization
-@attribute [Authorize]
Counter
diff --git a/DungeonMasterDashboard/Pages/Home.razor b/DungeonMasterDashboard/Pages/Home.razor
new file mode 100644
index 0000000..9001e0b
--- /dev/null
+++ b/DungeonMasterDashboard/Pages/Home.razor
@@ -0,0 +1,7 @@
+@page "/"
+
+Home
+
+Hello, world!
+
+Welcome to your new app.
diff --git a/Pages/NotFound.razor b/DungeonMasterDashboard/Pages/NotFound.razor
similarity index 100%
rename from Pages/NotFound.razor
rename to DungeonMasterDashboard/Pages/NotFound.razor
diff --git a/DungeonMasterDashboard/Pages/Profile.razor b/DungeonMasterDashboard/Pages/Profile.razor
new file mode 100644
index 0000000..b6f6acb
--- /dev/null
+++ b/DungeonMasterDashboard/Pages/Profile.razor
@@ -0,0 +1,53 @@
+@page "/profile"
+
+DMD Profile
+
+@if (user == null)
+{
+ @message
+}
+else
+{
+ Profile
+
+
+
+
+
+
+}
+
+@inject HttpClient httpClient
+@code {
+ private UserProfile? user = null;
+ private string message = "";
+
+ protected override async Task OnInitializedAsync()
+ {
+ try
+ {
+ message = "Loading. . .";
+
+ user = await httpClient.GetFromJsonAsync("api/Account/Profile");
+ }
+ catch
+ {
+ message = "Cannot read user profile";
+ }
+ }
+}
diff --git a/Program.cs b/DungeonMasterDashboard/Program.cs
similarity index 59%
rename from Program.cs
rename to DungeonMasterDashboard/Program.cs
index c845e22..9ea7066 100644
--- a/Program.cs
+++ b/DungeonMasterDashboard/Program.cs
@@ -3,14 +3,15 @@
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
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.AddOidcAuthentication(options =>
+// If you don't have an API yet, just point to the same origin.
+// This avoids requiring WebApiAddress.
+builder.Services.AddScoped(_ => new HttpClient
{
- builder.Configuration.Bind("Auth0", options.ProviderOptions);
+ BaseAddress = new Uri(builder.HostEnvironment.BaseAddress)
});
await builder.Build().RunAsync();
diff --git a/Properties/launchSettings.json b/DungeonMasterDashboard/Properties/launchSettings.json
similarity index 96%
rename from Properties/launchSettings.json
rename to DungeonMasterDashboard/Properties/launchSettings.json
index 4b4f942..c6569c4 100644
--- a/Properties/launchSettings.json
+++ b/DungeonMasterDashboard/Properties/launchSettings.json
@@ -1,15 +1,15 @@
-{
- "$schema": "https://json.schemastore.org/launchsettings.json",
- "profiles": {
- "https": {
- "commandName": "Project",
- "dotnetRunMessages": true,
- "launchBrowser": true,
- "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}",
- "applicationUrl": "https://localhost:7277;http://localhost:5095",
- "environmentVariables": {
- "ASPNETCORE_ENVIRONMENT": "Development"
- }
- }
- }
-}
+{
+ "$schema": "https://json.schemastore.org/launchsettings.json",
+ "profiles": {
+ "https": {
+ "commandName": "Project",
+ "dotnetRunMessages": true,
+ "launchBrowser": true,
+ "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}",
+ "applicationUrl": "https://localhost:7277;http://localhost:5095",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ }
+ }
+ }
+}
diff --git a/DungeonMasterDashboard/Services/CustomAuthenticationStateProvider.cs b/DungeonMasterDashboard/Services/CustomAuthenticationStateProvider.cs
new file mode 100644
index 0000000..241a989
--- /dev/null
+++ b/DungeonMasterDashboard/Services/CustomAuthenticationStateProvider.cs
@@ -0,0 +1,150 @@
+using Microsoft.AspNetCore.Components.Authorization;
+using System.Net.Http.Json;
+using System.Security.Claims;
+using System.Text.Json.Nodes;
+using System.Net.Http.Headers;
+using Blazored.LocalStorage;
+using DungeonMasterDashboard.Models;
+
+namespace DungeonMasterDashboard.Services
+{
+ public class CustomAuthenticationStateProvider : AuthenticationStateProvider
+ {
+ private readonly HttpClient httpClient;
+ private readonly ISyncLocalStorageService localStorage;
+
+ public CustomAuthenticationStateProvider(HttpClient httpClient, ISyncLocalStorageService localStorage)
+ {
+ this.httpClient = httpClient;
+ this.localStorage = localStorage;
+
+ var accessToken = localStorage.GetItem("accessToken");
+ if (accessToken != null)
+ {
+ this.httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
+ }
+ }
+
+ public override async Task GetAuthenticationStateAsync()
+ {
+ /*
+ //var user = new ClaimsPrincipal(new ClaimsIdentity()); // non-authenticated user
+ var claims = new List { new Claim(ClaimTypes.Name, "John") };
+ var identity = new ClaimsIdentity(claims, "ANY");
+ var user = new ClaimsPrincipal(identity);
+
+ return Task.FromResult(new AuthenticationState(user));
+ */
+
+ var user = new ClaimsPrincipal(new ClaimsIdentity()); // non-authenticated user
+
+ try
+ {
+ var response = await httpClient.GetAsync("manage/info");
+ if (response.IsSuccessStatusCode)
+ {
+ var strResponse = await response.Content.ReadAsStringAsync();
+ var jsonResponse = JsonNode.Parse(strResponse);
+ var email = jsonResponse!["email"]!.ToString();
+
+ var claims = new List
+ {
+ new(ClaimTypes.Name, email),
+ new(ClaimTypes.Email, email)
+ };
+
+ // Set the principal
+ var identity = new ClaimsIdentity(claims, "Token");
+ user = new ClaimsPrincipal(identity);
+ return new AuthenticationState(user);
+ }
+ }
+ catch
+ {
+
+ }
+
+ return new AuthenticationState(user);
+ }
+
+ public async Task LoginAsync(string email, string password)
+ {
+ try
+ {
+ var response = await httpClient.PostAsJsonAsync("/login", new { email, password });
+ if (response.IsSuccessStatusCode)
+ {
+ var strResponse = await response.Content.ReadAsStringAsync();
+ var jsonResponse = JsonNode.Parse(strResponse);
+ var accessToken = jsonResponse?["accessToken"]?.ToString();
+ var refreshToken = jsonResponse?["refreshToken"]?.ToString();
+
+ localStorage.SetItem("accessToken", accessToken);
+ localStorage.SetItem("refreshToken", refreshToken);
+
+ httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
+
+ // Refresh auth state
+ NotifyAuthenticationStateChanged(GetAuthenticationStateAsync());
+
+ // Success!
+ return new FormResult { Success = true };
+ }
+ else
+ {
+ return new FormResult { Success = false, Errors = ["Bad Email or Password"] };
+ }
+ } catch { }
+
+ return new FormResult { Success = false, Errors = [ "Connection Error" ] };
+ }
+
+ public void Logout()
+ {
+ // delete tokens from local storage
+ localStorage.RemoveItem("accessToken");
+ localStorage.RemoveItem("refreshToken");
+ httpClient.DefaultRequestHeaders.Authorization = null;
+ NotifyAuthenticationStateChanged(GetAuthenticationStateAsync());
+ }
+
+ public async Task RegisterAsync(RegisterDto registerDto)
+ {
+ try
+ {
+ var response = await httpClient.PostAsJsonAsync("/register", registerDto);
+ if (response.IsSuccessStatusCode)
+ {
+ var loginResponse = await LoginAsync(registerDto.Email, registerDto.Password);
+ return loginResponse;
+ }
+
+ // Registration Errors
+ var strResponse = await response.Content.ReadAsStringAsync();
+ Console.WriteLine(strResponse);
+ var jsonResponse = JsonNode.Parse(strResponse);
+ var errorsObject = jsonResponse!["errors"]!.AsObject();
+ var errorsList = new List();
+ foreach (var error in errorsObject)
+ {
+ errorsList.Add(error.Value![0]!.ToString());
+ }
+
+ var formResult = new FormResult
+ {
+ Success = false,
+ Errors = errorsList.ToArray()
+ };
+
+ return formResult;
+ } catch { }
+ return new FormResult { Success = false, Errors = ["Connection Error"] };
+ }
+ }
+
+ public class FormResult
+ {
+ public bool Success { get; set; }
+ public string[] Errors { get; set; } = [];
+ }
+}
diff --git a/_Imports.razor b/DungeonMasterDashboard/_Imports.razor
similarity index 83%
rename from _Imports.razor
rename to DungeonMasterDashboard/_Imports.razor
index cc17512..393c71a 100644
--- a/_Imports.razor
+++ b/DungeonMasterDashboard/_Imports.razor
@@ -1,6 +1,5 @@
@using System.Net.Http
@using System.Net.Http.Json
-@using Microsoft.AspNetCore.Components.Authorization
@using Microsoft.AspNetCore.Components.Forms
@using Microsoft.AspNetCore.Components.Routing
@using Microsoft.AspNetCore.Components.Web
@@ -9,3 +8,6 @@
@using Microsoft.JSInterop
@using DungeonMasterDashboard
@using DungeonMasterDashboard.Layout
+
+@using DungeonMasterDashboard.Services
+@using DungeonMasterDashboard.Models
diff --git a/wwwroot/appsettings.Development.json b/DungeonMasterDashboard/wwwroot/appsettings.Development.json
similarity index 95%
rename from wwwroot/appsettings.Development.json
rename to DungeonMasterDashboard/wwwroot/appsettings.Development.json
index fdae94a..5b0f66f 100644
--- a/wwwroot/appsettings.Development.json
+++ b/DungeonMasterDashboard/wwwroot/appsettings.Development.json
@@ -1,6 +1,6 @@
-{
- "Local": {
- "Authority": "https://login.microsoftonline.com/",
- "ClientId": "33333333-3333-3333-33333333333333333"
- }
-}
+{
+ "Local": {
+ "Authority": "https://login.microsoftonline.com/",
+ "ClientId": "33333333-3333-3333-33333333333333333"
+ }
+}
diff --git a/DungeonMasterDashboard/wwwroot/appsettings.json b/DungeonMasterDashboard/wwwroot/appsettings.json
new file mode 100644
index 0000000..3872340
--- /dev/null
+++ b/DungeonMasterDashboard/wwwroot/appsettings.json
@@ -0,0 +1,10 @@
+{
+ "Logging": {
+ "LogLevel": {
+ "Default": "Information",
+ "Microsoft.Application": "Warning"
+ }
+ },
+ "AllowedHosts": "*",
+ "WebApiAddress": "https://localhost:7090"
+}
diff --git a/wwwroot/css/app.css b/DungeonMasterDashboard/wwwroot/css/app.css
similarity index 97%
rename from wwwroot/css/app.css
rename to DungeonMasterDashboard/wwwroot/css/app.css
index fe44b1d..08b5997 100644
--- a/wwwroot/css/app.css
+++ b/DungeonMasterDashboard/wwwroot/css/app.css
@@ -1,115 +1,115 @@
-html, body {
- font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
-}
-
-h1:focus {
- outline: none;
-}
-
-a, .btn-link {
- color: #0071c1;
-}
-
-.btn-primary {
- color: #fff;
- background-color: #1b6ec2;
- border-color: #1861ac;
-}
-
-.btn:focus, .btn:active:focus, .btn-link.nav-link:focus, .form-control:focus, .form-check-input:focus {
- box-shadow: 0 0 0 0.1rem white, 0 0 0 0.25rem #258cfb;
-}
-
-.content {
- padding-top: 1.1rem;
-}
-
-.valid.modified:not([type=checkbox]) {
- outline: 1px solid #26b050;
-}
-
-.invalid {
- outline: 1px solid red;
-}
-
-.validation-message {
- color: red;
-}
-
-#blazor-error-ui {
- color-scheme: light only;
- background: lightyellow;
- bottom: 0;
- box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2);
- box-sizing: border-box;
- display: none;
- left: 0;
- padding: 0.6rem 1.25rem 0.7rem 1.25rem;
- position: fixed;
- width: 100%;
- z-index: 1000;
-}
-
- #blazor-error-ui .dismiss {
- cursor: pointer;
- position: absolute;
- right: 0.75rem;
- top: 0.5rem;
- }
-
-.blazor-error-boundary {
- background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTYiIGhlaWdodD0iNDkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIG92ZXJmbG93PSJoaWRkZW4iPjxkZWZzPjxjbGlwUGF0aCBpZD0iY2xpcDAiPjxyZWN0IHg9IjIzNSIgeT0iNTEiIHdpZHRoPSI1NiIgaGVpZ2h0PSI0OSIvPjwvY2xpcFBhdGg+PC9kZWZzPjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMCkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMzUgLTUxKSI+PHBhdGggZD0iTTI2My41MDYgNTFDMjY0LjcxNyA1MSAyNjUuODEzIDUxLjQ4MzcgMjY2LjYwNiA1Mi4yNjU4TDI2Ny4wNTIgNTIuNzk4NyAyNjcuNTM5IDUzLjYyODMgMjkwLjE4NSA5Mi4xODMxIDI5MC41NDUgOTIuNzk1IDI5MC42NTYgOTIuOTk2QzI5MC44NzcgOTMuNTEzIDI5MSA5NC4wODE1IDI5MSA5NC42NzgyIDI5MSA5Ny4wNjUxIDI4OS4wMzggOTkgMjg2LjYxNyA5OUwyNDAuMzgzIDk5QzIzNy45NjMgOTkgMjM2IDk3LjA2NTEgMjM2IDk0LjY3ODIgMjM2IDk0LjM3OTkgMjM2LjAzMSA5NC4wODg2IDIzNi4wODkgOTMuODA3MkwyMzYuMzM4IDkzLjAxNjIgMjM2Ljg1OCA5Mi4xMzE0IDI1OS40NzMgNTMuNjI5NCAyNTkuOTYxIDUyLjc5ODUgMjYwLjQwNyA1Mi4yNjU4QzI2MS4yIDUxLjQ4MzcgMjYyLjI5NiA1MSAyNjMuNTA2IDUxWk0yNjMuNTg2IDY2LjAxODNDMjYwLjczNyA2Ni4wMTgzIDI1OS4zMTMgNjcuMTI0NSAyNTkuMzEzIDY5LjMzNyAyNTkuMzEzIDY5LjYxMDIgMjU5LjMzMiA2OS44NjA4IDI1OS4zNzEgNzAuMDg4N0wyNjEuNzk1IDg0LjAxNjEgMjY1LjM4IDg0LjAxNjEgMjY3LjgyMSA2OS43NDc1QzI2Ny44NiA2OS43MzA5IDI2Ny44NzkgNjkuNTg3NyAyNjcuODc5IDY5LjMxNzkgMjY3Ljg3OSA2Ny4xMTgyIDI2Ni40NDggNjYuMDE4MyAyNjMuNTg2IDY2LjAxODNaTTI2My41NzYgODYuMDU0N0MyNjEuMDQ5IDg2LjA1NDcgMjU5Ljc4NiA4Ny4zMDA1IDI1OS43ODYgODkuNzkyMSAyNTkuNzg2IDkyLjI4MzcgMjYxLjA0OSA5My41Mjk1IDI2My41NzYgOTMuNTI5NSAyNjYuMTE2IDkzLjUyOTUgMjY3LjM4NyA5Mi4yODM3IDI2Ny4zODcgODkuNzkyMSAyNjcuMzg3IDg3LjMwMDUgMjY2LjExNiA4Ni4wNTQ3IDI2My41NzYgODYuMDU0N1oiIGZpbGw9IiNGRkU1MDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=) no-repeat 1rem/1.8rem, #b32121;
- padding: 1rem 1rem 1rem 3.7rem;
- color: white;
-}
-
- .blazor-error-boundary::after {
- content: "An error has occurred."
- }
-
-.loading-progress {
- position: absolute;
- display: block;
- width: 8rem;
- height: 8rem;
- inset: 20vh 0 auto 0;
- margin: 0 auto 0 auto;
-}
-
- .loading-progress circle {
- fill: none;
- stroke: #e0e0e0;
- stroke-width: 0.6rem;
- transform-origin: 50% 50%;
- transform: rotate(-90deg);
- }
-
- .loading-progress circle:last-child {
- stroke: #1b6ec2;
- stroke-dasharray: calc(3.141 * var(--blazor-load-percentage, 0%) * 0.8), 500%;
- transition: stroke-dasharray 0.05s ease-in-out;
- }
-
-.loading-progress-text {
- position: absolute;
- text-align: center;
- font-weight: bold;
- inset: calc(20vh + 3.25rem) 0 auto 0.2rem;
-}
-
- .loading-progress-text:after {
- content: var(--blazor-load-percentage-text, "Loading");
- }
-
-code {
- color: #c02d76;
-}
-
-.form-floating > .form-control-plaintext::placeholder, .form-floating > .form-control::placeholder {
- color: var(--bs-secondary-color);
- text-align: end;
-}
-
-.form-floating > .form-control-plaintext:focus::placeholder, .form-floating > .form-control:focus::placeholder {
- text-align: start;
+html, body {
+ font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
+}
+
+h1:focus {
+ outline: none;
+}
+
+a, .btn-link {
+ color: #0071c1;
+}
+
+.btn-primary {
+ color: #fff;
+ background-color: #1b6ec2;
+ border-color: #1861ac;
+}
+
+.btn:focus, .btn:active:focus, .btn-link.nav-link:focus, .form-control:focus, .form-check-input:focus {
+ box-shadow: 0 0 0 0.1rem white, 0 0 0 0.25rem #258cfb;
+}
+
+.content {
+ padding-top: 1.1rem;
+}
+
+.valid.modified:not([type=checkbox]) {
+ outline: 1px solid #26b050;
+}
+
+.invalid {
+ outline: 1px solid red;
+}
+
+.validation-message {
+ color: red;
+}
+
+#blazor-error-ui {
+ color-scheme: light only;
+ background: lightyellow;
+ bottom: 0;
+ box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2);
+ box-sizing: border-box;
+ display: none;
+ left: 0;
+ padding: 0.6rem 1.25rem 0.7rem 1.25rem;
+ position: fixed;
+ width: 100%;
+ z-index: 1000;
+}
+
+ #blazor-error-ui .dismiss {
+ cursor: pointer;
+ position: absolute;
+ right: 0.75rem;
+ top: 0.5rem;
+ }
+
+.blazor-error-boundary {
+ background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTYiIGhlaWdodD0iNDkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIG92ZXJmbG93PSJoaWRkZW4iPjxkZWZzPjxjbGlwUGF0aCBpZD0iY2xpcDAiPjxyZWN0IHg9IjIzNSIgeT0iNTEiIHdpZHRoPSI1NiIgaGVpZ2h0PSI0OSIvPjwvY2xpcFBhdGg+PC9kZWZzPjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMCkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMzUgLTUxKSI+PHBhdGggZD0iTTI2My41MDYgNTFDMjY0LjcxNyA1MSAyNjUuODEzIDUxLjQ4MzcgMjY2LjYwNiA1Mi4yNjU4TDI2Ny4wNTIgNTIuNzk4NyAyNjcuNTM5IDUzLjYyODMgMjkwLjE4NSA5Mi4xODMxIDI5MC41NDUgOTIuNzk1IDI5MC42NTYgOTIuOTk2QzI5MC44NzcgOTMuNTEzIDI5MSA5NC4wODE1IDI5MSA5NC42NzgyIDI5MSA5Ny4wNjUxIDI4OS4wMzggOTkgMjg2LjYxNyA5OUwyNDAuMzgzIDk5QzIzNy45NjMgOTkgMjM2IDk3LjA2NTEgMjM2IDk0LjY3ODIgMjM2IDk0LjM3OTkgMjM2LjAzMSA5NC4wODg2IDIzNi4wODkgOTMuODA3MkwyMzYuMzM4IDkzLjAxNjIgMjM2Ljg1OCA5Mi4xMzE0IDI1OS40NzMgNTMuNjI5NCAyNTkuOTYxIDUyLjc5ODUgMjYwLjQwNyA1Mi4yNjU4QzI2MS4yIDUxLjQ4MzcgMjYyLjI5NiA1MSAyNjMuNTA2IDUxWk0yNjMuNTg2IDY2LjAxODNDMjYwLjczNyA2Ni4wMTgzIDI1OS4zMTMgNjcuMTI0NSAyNTkuMzEzIDY5LjMzNyAyNTkuMzEzIDY5LjYxMDIgMjU5LjMzMiA2OS44NjA4IDI1OS4zNzEgNzAuMDg4N0wyNjEuNzk1IDg0LjAxNjEgMjY1LjM4IDg0LjAxNjEgMjY3LjgyMSA2OS43NDc1QzI2Ny44NiA2OS43MzA5IDI2Ny44NzkgNjkuNTg3NyAyNjcuODc5IDY5LjMxNzkgMjY3Ljg3OSA2Ny4xMTgyIDI2Ni40NDggNjYuMDE4MyAyNjMuNTg2IDY2LjAxODNaTTI2My41NzYgODYuMDU0N0MyNjEuMDQ5IDg2LjA1NDcgMjU5Ljc4NiA4Ny4zMDA1IDI1OS43ODYgODkuNzkyMSAyNTkuNzg2IDkyLjI4MzcgMjYxLjA0OSA5My41Mjk1IDI2My41NzYgOTMuNTI5NSAyNjYuMTE2IDkzLjUyOTUgMjY3LjM4NyA5Mi4yODM3IDI2Ny4zODcgODkuNzkyMSAyNjcuMzg3IDg3LjMwMDUgMjY2LjExNiA4Ni4wNTQ3IDI2My41NzYgODYuMDU0N1oiIGZpbGw9IiNGRkU1MDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=) no-repeat 1rem/1.8rem, #b32121;
+ padding: 1rem 1rem 1rem 3.7rem;
+ color: white;
+}
+
+ .blazor-error-boundary::after {
+ content: "An error has occurred."
+ }
+
+.loading-progress {
+ position: absolute;
+ display: block;
+ width: 8rem;
+ height: 8rem;
+ inset: 20vh 0 auto 0;
+ margin: 0 auto 0 auto;
+}
+
+ .loading-progress circle {
+ fill: none;
+ stroke: #e0e0e0;
+ stroke-width: 0.6rem;
+ transform-origin: 50% 50%;
+ transform: rotate(-90deg);
+ }
+
+ .loading-progress circle:last-child {
+ stroke: #1b6ec2;
+ stroke-dasharray: calc(3.141 * var(--blazor-load-percentage, 0%) * 0.8), 500%;
+ transition: stroke-dasharray 0.05s ease-in-out;
+ }
+
+.loading-progress-text {
+ position: absolute;
+ text-align: center;
+ font-weight: bold;
+ inset: calc(20vh + 3.25rem) 0 auto 0.2rem;
+}
+
+ .loading-progress-text:after {
+ content: var(--blazor-load-percentage-text, "Loading");
+ }
+
+code {
+ color: #c02d76;
+}
+
+.form-floating > .form-control-plaintext::placeholder, .form-floating > .form-control::placeholder {
+ color: var(--bs-secondary-color);
+ text-align: end;
+}
+
+.form-floating > .form-control-plaintext:focus::placeholder, .form-floating > .form-control:focus::placeholder {
+ text-align: start;
}
\ No newline at end of file
diff --git a/wwwroot/favicon.png b/DungeonMasterDashboard/wwwroot/favicon.png
similarity index 100%
rename from wwwroot/favicon.png
rename to DungeonMasterDashboard/wwwroot/favicon.png
diff --git a/wwwroot/icon-192.png b/DungeonMasterDashboard/wwwroot/icon-192.png
similarity index 100%
rename from wwwroot/icon-192.png
rename to DungeonMasterDashboard/wwwroot/icon-192.png
diff --git a/wwwroot/icon-512.png b/DungeonMasterDashboard/wwwroot/icon-512.png
similarity index 100%
rename from wwwroot/icon-512.png
rename to DungeonMasterDashboard/wwwroot/icon-512.png
diff --git a/wwwroot/index.html b/DungeonMasterDashboard/wwwroot/index.html
similarity index 84%
rename from wwwroot/index.html
rename to DungeonMasterDashboard/wwwroot/index.html
index 2a99601..2a22fdd 100644
--- a/wwwroot/index.html
+++ b/DungeonMasterDashboard/wwwroot/index.html
@@ -31,8 +31,7 @@
Reload
🗙
-
-
+