Browse Source

Login UI followup done

pull/13/head^2
Benjamin Arhen 1 year ago
parent
commit
b28f52831a
  1. 2
      Client/Elements/Footer.razor
  2. 10
      Client/Elements/Footer.razor.cs
  3. 24
      Client/Elements/Headbar.razor
  4. 32
      Client/Elements/Headbar.razor.cs
  5. 42
      Client/Elements/Headbar.razor.css
  6. 2
      Client/Elements/Sidebar.razor
  7. 8
      Client/Elements/Sidebar.razor.cs
  8. 9
      Client/Layouts/MainLayout.razor
  9. 11
      Client/Layouts/MainLayout.razor.cs
  10. 2
      Client/Models/NavItem.cs
  11. 4
      Client/Pages/Auth/Components/TabContainer.razor
  12. 31
      Client/Pages/Auth/Components/UsernameTab.razor
  13. 142
      Client/Pages/Auth/Components/UsernameTab.razor.cs
  14. 11
      Client/Pages/Auth/Components/UsernameTab.razor.css
  15. 15
      Client/Pages/Auth/Login.razor
  16. 11
      Client/Pages/Auth/Login.razor.css
  17. 2
      Shared/Interfaces/ISearchService.cs
  18. 5
      Shared/ServiceRepo/SearchService.cs
  19. 1
      Shared/ServiceRepo/TokenService.cs

2
Client/Elements/Footer.razor

@ -1,4 +1,4 @@
<footer class="content-footer footer bg-footer-theme" style="position:relative;"> <footer class="content-footer footer bg-footer-theme" style="position:relative;" @onclick=OnClickCallback>
<div class="container-xxl d-flex flex-wrap justify-content-between py-2 flex-md-row flex-column"> <div class="container-xxl d-flex flex-wrap justify-content-between py-2 flex-md-row flex-column">
<div class="mb-2 mb-md-0"> <div class="mb-2 mb-md-0">
Biskilog © @DateTime.Now.Year Biskilog © @DateTime.Now.Year

10
Client/Elements/Footer.razor.cs

@ -0,0 +1,10 @@
using Microsoft.AspNetCore.Components;
namespace Biskilog_Accounting.Client.Elements
{
public partial class Footer
{
[Parameter]
public EventCallback OnClickCallback { get; set; }
}
}

24
Client/Elements/Headbar.razor

@ -1,6 +1,8 @@
@using Biskilog_Accounting.Shared.Interfaces; @using Biskilog_Accounting.Shared.Interfaces;
@inject ISearchService m_searchService @inject ISearchService m_searchService
@inject IJSRuntime JSRuntime; @inject IJSRuntime JSRuntime
@inject ITokenService m_tokenService
@inject NavigationManager m_navigationManager
<!-- Navbar --> <!-- Navbar -->
<nav class="layout-navbar container-xxl navbar navbar-expand-xl navbar-detached align-items-center bg-navbar-theme" <nav class="layout-navbar container-xxl navbar navbar-expand-xl navbar-detached align-items-center bg-navbar-theme"
@ -13,23 +15,29 @@
<div class="navbar-nav-right d-flex align-items-center" id="navbar-collapse"> <div class="navbar-nav-right d-flex align-items-center" id="navbar-collapse">
<!-- Search --> <!-- Search -->
<div class="navbar-nav align-items-center" style="width:90%;"> <div class="navbar-nav align-items-center" style="width:100%;">
<div class="nav-item d-flex align-items-center" style="width:90%;"> <div class="nav-item d-flex align-items-center" style="width:100%;">
<i class="bx bx-search fs-4 lh-0"></i> <i class="bx bx-search fs-4 lh-0"></i>
<input type="text" <input type="text"
id="mainSearch" id="mainSearch"
class="form-control border-0 shadow-none" class="form-control border-0 shadow-none"
placeholder="Search..." placeholder="Search..."
aria-label="Search..." aria-label="Search..."
@oninput="@(args => SearchInput(args.Value.ToString()))"/> @oninput="@(args => SearchInput(args.Value.ToString()))" />
</div> </div>
</div> <div id="Profile" @onclick="@ToggleDropdown">
<!-- /Search --> <i class="bx bxs-user fs-4 lh-0"></i>
<ul class="navbar-nav flex-row align-items-center ms-auto">
<ul class=@m_dropDownClass style="width: 150px;">
<li><a href="#">Profile</a></li>
<li><a href="#">Account</a></li>
<li><a href="#">Settings</a></li>
<li><a id="logOutBtn" href="javascript:void(0)" @onclick=@LogOut>Log Out</a></li>
</ul> </ul>
</div> </div>
</div>
</div>
</nav> </nav>
<!-- / Navbar --> <!-- / Navbar -->

32
Client/Elements/Headbar.razor.cs

@ -1,15 +1,40 @@
using Microsoft.JSInterop; using Microsoft.AspNetCore.Components;
using Microsoft.JSInterop;
namespace Biskilog_Accounting.Client.Elements namespace Biskilog_Accounting.Client.Elements
{ {
public partial class Headbar public partial class Headbar
{ {
private const string c_show = "show";
private const string c_hide = "hidden";
private string m_dropDownClass = c_hide;
protected override void OnInitialized() protected override void OnInitialized()
{ {
m_searchService.ClearTextBox += ClearTextBox; m_searchService.ClearTextBox += ClearTextBox;
m_searchService.CloseMenus += HideProfileDropdown;
base.OnInitialized(); base.OnInitialized();
} }
private void HideProfileDropdown(string a_sender)
{
if (a_sender != "Profile")
{
m_dropDownClass = c_hide;
StateHasChanged();
}
}
private void ToggleDropdown()
{
if (m_dropDownClass == c_hide)
{
m_dropDownClass = c_show;
}
else
{
m_dropDownClass = c_hide;
}
}
private void ClearTextBox() private void ClearTextBox()
{ {
ClearInputField("mainSearch"); ClearInputField("mainSearch");
@ -22,5 +47,10 @@ namespace Biskilog_Accounting.Client.Elements
{ {
m_searchService.PerformSearch(a_key); m_searchService.PerformSearch(a_key);
} }
private async Task LogOut()
{
await m_tokenService.ClearToken();
m_navigationManager.NavigateTo("/login");
}
} }
} }

42
Client/Elements/Headbar.razor.css

@ -0,0 +1,42 @@
ul {
flex-direction: column;
background: #3f7672;
top: 1rem;
border-radius: 10px;
list-style-type: none;
margin: 0;
padding: 0;
position: absolute;
box-shadow: 0 8px 16px rgba(0, 0, 0, 0.1);
z-index: 1;
}
li {
padding: 8px;
border-bottom: 1px solid #ffffff; /* Border between items */
}
li:last-child {
border-bottom: none; /* Remove border from the last item */
}
a {
color: #ffffff; /* Dropdown item text color */
text-decoration: none;
display: block;
padding: 8px;
cursor:pointer;
}
a:hover {
color:#003445; /* Hover background color */
}
/* Show the dropdown when the container is clicked */
.show ul {
display: block;
}
.hidden {
display: none;
}

2
Client/Elements/Sidebar.razor

@ -1,7 +1,7 @@
@using Biskilog_Accounting.Client.Models; @using Biskilog_Accounting.Client.Models;
<aside id="layout-menu" class="@m_class menu-vertical menu bg-menu-theme"> <aside id="layout-menu" class="@m_class menu-vertical menu bg-menu-theme">
<div class="app-brand"> <div class="app-brand">
<a href="index.html" class="app-brand-link"> <a href="index.html" class="app-brand-link" @onclick=OnClickCallback>
<span class="app-brand-logo"> <span class="app-brand-logo">
<img src="../icon-512.png" alt="BISKILOG Logo" style="height:60px;"> <img src="../icon-512.png" alt="BISKILOG Logo" style="height:60px;">
</span> </span>

8
Client/Elements/Sidebar.razor.cs

@ -1,17 +1,23 @@
namespace Biskilog_Accounting.Client.Elements using Microsoft.AspNetCore.Components;
namespace Biskilog_Accounting.Client.Elements
{ {
public partial class Sidebar public partial class Sidebar
{ {
[Parameter]
public EventCallback OnClickCallback { get; set; }
private double m_activeId = 1; private double m_activeId = 1;
private string m_class { get; set; } = "layout-menu"; private string m_class { get; set; } = "layout-menu";
private void HandleMenuClick(double a_selectedId) private void HandleMenuClick(double a_selectedId)
{ {
m_activeId = a_selectedId; m_activeId = a_selectedId;
OnClickCallback.InvokeAsync();
StateHasChanged(); StateHasChanged();
} }
private void Collapse() private void Collapse()
{ {
OnClickCallback.InvokeAsync();
//if (m_class == "layout-menu") //if (m_class == "layout-menu")
//{ //{
// m_class = "layout-menu-collapsed"; // m_class = "layout-menu-collapsed";

9
Client/Layouts/MainLayout.razor

@ -11,6 +11,7 @@
@inject ICompanyInfo m_companyInfo @inject ICompanyInfo m_companyInfo
@inject IUser m_userService @inject IUser m_userService
@inject ICustomer m_customerService @inject ICustomer m_customerService
@inject ISearchService m_searchSearch
<RadzenDialog /> <RadzenDialog />
<RadzenContextMenu /> <RadzenContextMenu />
@ -18,15 +19,15 @@
<div class="layout-wrapper layout-content-navbar"> <div class="layout-wrapper layout-content-navbar">
<div class="layout-container"> <div class="layout-container">
<!-- Menu --> <!-- Menu -->
<Sidebar /> <Sidebar OnClickCallback=@(() => Click("sidebar")) />
<!-- / Menu --> <!-- / Menu -->
<!-- Body Layout container --> <!-- Body Layout container -->
<div style="width: 100vh;display: flex;flex: 1 1 auto;flex-direction: column;flex-wrap: nowrap;"> <div style="width: 100vh;display: flex;flex: 1 1 auto;flex-direction: column;flex-wrap: nowrap;">
<div class="layout-page"> <div class="layout-page">
<Headbar /> <Headbar/>
<!-- Content wrapper --> <!-- Content wrapper -->
<div class="content-wrapper"> <div class="content-wrapper">
<div class="container-xxl flex-grow-1 container-p-y"> <div class="container-xxl flex-grow-1 container-p-y" @onclick=@(() => Click("body"))>
<!-- Content --> <!-- Content -->
@Body @Body
<!-- / Content --> <!-- / Content -->
@ -35,7 +36,7 @@
<!-- Content wrapper --> <!-- Content wrapper -->
</div> </div>
<!-- / Layout page --> <!-- / Layout page -->
<Footer /> <Footer OnClickCallback=@(() => Click("footer")) />
</div> </div>
</div> </div>
</div> </div>

11
Client/Layouts/MainLayout.razor.cs

@ -1,4 +1,5 @@
using Biskilog_Accounting.Client.Pages.Transactions.Elements; using Biskilog_Accounting.Client.Pages.Transactions.Elements;
using Biskilog_Accounting.Shared.Enums;
using Radzen; using Radzen;
using System.Net.Http.Headers; using System.Net.Http.Headers;
@ -55,6 +56,12 @@ namespace Biskilog_Accounting.Client.Layouts
else else
{ {
string token = await m_tokenService.GetToken(); string token = await m_tokenService.GetToken();
if (m_tokenService.ValidateToken(token) != AuthEnums.Valid)
{
m_navigationManager.NavigateTo("/login");
}
var authHeader = new AuthenticationHeaderValue("Bearer", token.Substring(6).Trim()); var authHeader = new AuthenticationHeaderValue("Bearer", token.Substring(6).Trim());
m_http.DefaultRequestHeaders.Authorization = authHeader; m_http.DefaultRequestHeaders.Authorization = authHeader;
} }
@ -68,5 +75,9 @@ namespace Biskilog_Accounting.Client.Layouts
ShowMask = true ShowMask = true
}); });
} }
private void Click(string a_sender)
{
m_searchSearch.FireCloseMenus(a_sender);
}
} }
} }

2
Client/Models/NavItem.cs

@ -119,7 +119,7 @@
{ {
Id = 4, Id = 4,
Description = "Customers", Description = "Customers",
Icon = "bxs-user", Icon = "bxs-group",
Link = "customers", Link = "customers",
Title = "Customers" Title = "Customers"
}, },

4
Client/Pages/Auth/Components/TabContainer.razor

@ -1,7 +1,7 @@
 
<div class="tab"> <div class="tab">
<button class="@(GetTabClass(0))">Email or User ID</button> <button class="@(GetTabClass(0))" onclick="@(() => SwitchTabs(0))">Email or User ID</button>
<button class="@(GetTabClass(1))">Phone</button> <button class="@(GetTabClass(1))" onclick="@(() => SwitchTabs(1))">Phone</button>
</div> </div>
@if (activeTab == 0) @if (activeTab == 0)

31
Client/Pages/Auth/Components/UsernameTab.razor

@ -1,14 +1,17 @@
@page "/emailusernametab" @using Biskilog_Accounting.Shared.Interfaces
@using Biskilog_Accounting.Client.Pages.Auth.Components
@inject NavigationManager m_navigationManager
@layout AuthLayout
@inject HttpClient m_http
@inject ITokenService m_tokenService
<form> <form>
<div class="input-div"> <div class="input-div">
<input type="text" id="emailId" class="input-box" placeholder="Email or User ID" autocomplete="username email" /><br /> <input type="text" id="emailId" class="input-box" placeholder="Email or User ID" autocomplete="username email" @oninput="@(args => usernameInput(args.Value.ToString()))" @onkeydown="@Enter" /><br />
<span class="hidden">x</span> <span class="@m_usernameErrorClass">x</span>
</div> </div>
<div class="input-div"> <div class="input-div">
<input type="password" id="password" class="input-box" placeholder="Password" autocomplete="current-password" /><br /> <input type="password" id="password" class="input-box" placeholder="Password" autocomplete="current-password" @oninput="@(args => passwordInput(args.Value.ToString()))" @onkeydown="@Enter" /><br />
<span class="hidden">x</span> <span class="@m_passwordErrorClass">x</span>
</div> </div>
<div class="input-div-check"> <div class="input-div-check">
<input type="checkbox" id="rememberme-checkbox" name="rememberme-checkbox" class="input-box" /><br /> <input type="checkbox" id="rememberme-checkbox" name="rememberme-checkbox" class="input-box" /><br />
@ -22,17 +25,5 @@
<span class="sign-in-gbutton-title">Sign In</span> <span class="sign-in-gbutton-title">Sign In</span>
</div> </div>
</button> </button>
<a class="reset-link" @onclick="SwitchToSignUp">Sign Up</a> <a class="reset-link">Forgot your password?</a>
@code {
private void Authenticate()
{
}
private void SwitchToSignUp()
{
// You can use navigation logic to switch to the sign-up page.
}
}

142
Client/Pages/Auth/Components/UsernameTab.razor.cs

@ -0,0 +1,142 @@
using Biskilog_Accounting.Shared.ClientContractModels;
using Microsoft.AspNetCore.Components.Web;
using Microsoft.AspNetCore.Components;
using static System.Net.WebRequestMethods;
using System.Net.Http.Headers;
using System.Net.Http.Json;
namespace Biskilog_Accounting.Client.Pages.Auth.Components
{
public partial class UsernameTab
{
private string m_email, m_password;
private bool m_remember { get; set; }
protected bool IsVisible { get; set; }
private string m_usernameErrorClass = "hidden";
private string m_passwordErrorClass = "hidden";
//NotificationMessage notificationMessage = new NotificationMessage();
private Userauth authenticatedUser;
/// <summary>
/// Handles the click or press event of the enter key
/// </summary>
/// <param name="e"></param>
public async void Enter(KeyboardEventArgs e)
{
if (e.Code == "Enter" || e.Code == "NumpadEnter")
{
FieldValidation();
if (!string.IsNullOrEmpty(m_email) && !string.IsNullOrEmpty(m_password))
{
await Authenticate();
}
}
}
/// <summary>
/// Validates the username and password input fields and shows the appropriate indicators
/// </summary>
private void FieldValidation()
{
if (string.IsNullOrEmpty(m_email))
{
m_usernameErrorClass = "";
}
else
{
m_usernameErrorClass = "hidden";
}
if (string.IsNullOrEmpty(m_password))
{
m_passwordErrorClass = "";
}
else
{
m_passwordErrorClass = "hidden";
}
}
/// <summary>
/// Authenticates the user and determines the type of page layout to show
/// </summary>
/// <returns></returns>
async Task Authenticate()
{
FieldValidation();
ShowSpinner();
try
{
authenticatedUser = new Userauth
{
UserId = 0,
Username = m_email,
Email = m_email,
Passsword = m_password
};
var responseMain = await m_http.PostAsJsonAsync("api/authentication/type-a", authenticatedUser);
if (responseMain.IsSuccessStatusCode)
{
string token = await responseMain.Content.ReadAsStringAsync();
await m_tokenService.SetToken(token, m_remember);
var authHeader = new AuthenticationHeaderValue("Bearer", token);
m_http.DefaultRequestHeaders.Authorization = authHeader;
m_navigationManager.NavigateTo("/");
}
else if (responseMain.StatusCode == System.Net.HttpStatusCode.BadRequest)
{
}
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}
HideSpinner();
}
/// <summary>
/// Shows the loading spinner
/// </summary>
public void ShowSpinner()
{
IsVisible = true;
StateHasChanged();
}
/// <summary>
/// Hides the loading spinner
/// </summary>
public void HideSpinner()
{
IsVisible = false;
StateHasChanged();
}
/// <summary>
/// Shows a notification message
/// </summary>
/// <param name="message"></param>
/// <returns></returns>
//async Task ShowNotification(NotificationMessage message)
//{
// notificationService.Notify(message);
// await InvokeAsync(() => { StateHasChanged(); });
//}
/// <summary>
/// Sets the username value
/// </summary>
/// <param name="value"></param>
void usernameInput(string value)
{
m_email = value;
StateHasChanged();
}
/// <summary>
/// Sets the password value
/// </summary>
/// <param name="value"></param>
void passwordInput(string value)
{
m_password = value;
StateHasChanged();
}
}
}

11
Client/Pages/Auth/Components/UsernameTab.razor.css

@ -182,3 +182,14 @@
color: rgb(132, 134, 139); color: rgb(132, 134, 139);
text-align: center; text-align: center;
} }
.auth-success {
color: green;
}
.auth-failure {
color: red;
}
.hidden {
display: none;
}

15
Client/Pages/Auth/Login.razor

@ -7,19 +7,19 @@
@page "/login" @page "/login"
<section class="title-section"> <section class="title-section">
<a href="index.html"> <a href="javascrivpt:void(0)">
<img class="logo-image" src="@iconImage" alt="The logo of Biskilog pos" /> <img class="logo-image" src="../icon-512.png" alt="The logo of Biskilog pos" />
<span>BISKILOG</span> <span>BISKILOG</span>
</a> </a>
</section> </section>
<section class="form-section"> <section class="form-section">
<h2>Sign in</h2> <h2>Sign in</h2>
<span style="font-family: sans-serif; color: rgb(141, 144, 150);"> <span style="font-family: sans-serif; color: rgb(141, 144, 150);">
Enter your credentials to use <a href="index.html">Biskilog</a> accounting Enter your credentials to use <a href="javascrivpt:void(0)">Biskilog</a> accounting
</span> </span>
<button type="button" class="sign-in-gbutton"> <button type="button" class="sign-in-gbutton">
<div style="display: inline-flex;"> <div style="display: inline-flex;">
<img src="@googleIcon" class="sign-in-gbutton-image" alt="Google logo" /> <img src="../assets/img/google-image.png" class="sign-in-gbutton-image" alt="Google logo" />
<span class="sign-in-gbutton-title">Sign in with Google</span> <span class="sign-in-gbutton-title">Sign in with Google</span>
</div> </div>
</button> </button>
@ -36,10 +36,3 @@
Invisible reCAPTCHA by Google <a href="https://www.google.com/intl/en/policies/privacy/">Privacy Policy</a> and <a href="https://www.google.com/intl/en/policies/terms/">Terms of Use</a>. Invisible reCAPTCHA by Google <a href="https://www.google.com/intl/en/policies/privacy/">Privacy Policy</a> and <a href="https://www.google.com/intl/en/policies/terms/">Terms of Use</a>.
</div> </div>
</section> </section>
@code {
private string iconImage = "../icon-512.png";
private string googleIcon = "../assets/img/google-image.png";
// Add any other necessary code or logic here
}

11
Client/Pages/Auth/Login.razor.css

@ -183,14 +183,3 @@ span > a {
font: 0.9em sans-serif; font: 0.9em sans-serif;
} }
.auth-success {
color: green;
}
.auth-failure {
color: red;
}
.hidden {
display: none;
}

2
Shared/Interfaces/ISearchService.cs

@ -10,7 +10,9 @@ namespace Biskilog_Accounting.Shared.Interfaces
{ {
public event Action<string> SearchValueChanged; public event Action<string> SearchValueChanged;
public event Action ClearTextBox; public event Action ClearTextBox;
public event Action<string> CloseMenus;
void PerformSearch(string a_searchKey); void PerformSearch(string a_searchKey);
void FireCloseMenus(string a_sender);
void Clear(); void Clear();
} }
} }

5
Shared/ServiceRepo/SearchService.cs

@ -11,6 +11,7 @@ namespace Biskilog_Accounting.Shared.ServiceRepo
{ {
public event Action<string> SearchValueChanged; public event Action<string> SearchValueChanged;
public event Action ClearTextBox; public event Action ClearTextBox;
public event Action<string> CloseMenus;
// Method that raises the event // Method that raises the event
protected virtual void OnSearchValueChanged(string a_searchKey) protected virtual void OnSearchValueChanged(string a_searchKey)
@ -28,6 +29,10 @@ namespace Biskilog_Accounting.Shared.ServiceRepo
{ {
ClearTextBox?.Invoke(); ClearTextBox?.Invoke();
} }
public void FireCloseMenus(string a_sender)
{
CloseMenus?.Invoke(a_sender);
}
} }
} }

1
Shared/ServiceRepo/TokenService.cs

@ -41,7 +41,6 @@ namespace Biskilog_Accounting.ServiceRepo
} }
catch (Exception ex) catch (Exception ex)
{ {
Console.WriteLine(ex.Message);
return AuthEnums.Invalid; return AuthEnums.Invalid;
} }
} }

Loading…
Cancel
Save