mirror of
https://github.com/Shchoholiev/shopping-assistant-web-client.git
synced 2025-04-04 16:49:36 +00:00
Merge branch 'develop' into SA-217-deliting-chat-confirmation
This commit is contained in:
commit
8a3c9be4a0
14
ShoppingAssistantWebClient.Web/Models/Role.cs
Normal file
14
ShoppingAssistantWebClient.Web/Models/Role.cs
Normal file
@ -0,0 +1,14 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace ShoppingAssistantWebClient.Web.Models
|
||||
{
|
||||
public class Role
|
||||
{
|
||||
public string Id { get; set; }
|
||||
|
||||
public string Name { get; set; }
|
||||
}
|
||||
}
|
17
ShoppingAssistantWebClient.Web/Models/User.cs
Normal file
17
ShoppingAssistantWebClient.Web/Models/User.cs
Normal file
@ -0,0 +1,17 @@
|
||||
using ShoppingAssistantWebClient.Web.Models;
|
||||
|
||||
namespace ShoppingAssistantWebClient.Web.Models
|
||||
{
|
||||
public class User
|
||||
{
|
||||
public string Id { get; set; }
|
||||
|
||||
public string GuestId { get; set; }
|
||||
|
||||
public List<Role>? Roles { get; set; } = new List<Role>();
|
||||
|
||||
public string Email { get; set; }
|
||||
|
||||
public string Phone { get; set; }
|
||||
}
|
||||
}
|
@ -2,8 +2,6 @@ using Microsoft.AspNetCore.Components;
|
||||
using ShoppingAssistantWebClient.Web.Models;
|
||||
using ShoppingAssistantWebClient.Web.Network;
|
||||
using GraphQL;
|
||||
using Newtonsoft.Json;
|
||||
using Microsoft.JSInterop;
|
||||
using ShoppingAssistantWebClient.Web.Services;
|
||||
|
||||
namespace ShoppingAssistantWebClient.Web.Pages;
|
||||
|
152
ShoppingAssistantWebClient.Web/Pages/Settings.razor
Normal file
152
ShoppingAssistantWebClient.Web/Pages/Settings.razor
Normal file
@ -0,0 +1,152 @@
|
||||
@using System.Text.RegularExpressions
|
||||
@using Models.GlobalInstances
|
||||
@using ShoppingAssistantWebClient.Web.Models
|
||||
|
||||
@inject IHttpContextAccessor httpContextAccessor;
|
||||
|
||||
<div class="modal fade show d-block" tabindex="-1" role="dialog">
|
||||
<div class="modal-backdrop fade show"></div>
|
||||
<div class="modal-dialog" style="z-index: 1050">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">User Settings</h5>
|
||||
<button type="button" class="close" aria-label="Close" @onclick="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="@ShowErrorDivClass()" role="alert">
|
||||
@errorMessage
|
||||
</div>
|
||||
<div class="@ShowUpdateDivClass()" role="alert">
|
||||
@updateMessage
|
||||
</div>
|
||||
<form>
|
||||
<div class="form-group">
|
||||
<label for="phone">Phone</label>
|
||||
<input type="tel" class="form-control" id="phone" placeholder="Enter new phone" pattern="\+?[0-9]{10,15}" required @onchange="ValidatePhone" data-toggle="tooltip" data-placement="top" title="Use format: +xxxxxxxx">
|
||||
<div class="validation-message @(isPhoneInvalid ? "active" : "")" id="phone-validation" style="color: red;">@phoneValidationMessage</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="email">Email</label>
|
||||
<input type="email" class="form-control" id="email" placeholder="Enter new email" required @onchange="ValidateEmail" data-toggle="tooltip" data-placement="top" title="Use format: example@domain.com">
|
||||
<div class="validation-message @(isEmailInvalid ? "active" : "")" id="email-validation" style="color: red;">@emailValidationMessage</div>
|
||||
</div>
|
||||
|
||||
@if (!user.Roles.Any(role => role.Name == "User"))
|
||||
{
|
||||
<div class="form-group">
|
||||
<label for="password">New Password</label>
|
||||
<input type="password" class="form-control" id="password" placeholder="Enter new password" @onchange="OnPasswordInput">
|
||||
</div>
|
||||
}
|
||||
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
|
||||
<button type="button" class="btn btn-primary log-in-button-left">Log In</button>
|
||||
<button type="submit" class="btn btn-primary" disabled="@isApplyDisabled" @onclick="Apply">Apply</button>
|
||||
<button type="button" class="btn btn-secondary" @onclick="Close">Cancle</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
$(document).ready(function(){
|
||||
$('[data-toggle="tooltip"]').tooltip();
|
||||
});
|
||||
</script>
|
||||
|
||||
@code {
|
||||
[CascadingParameter] BlazoredModalInstance BlazoredModal { get; set; }
|
||||
|
||||
private string phoneValidationMessage = "";
|
||||
private string emailValidationMessage = "";
|
||||
private bool isPhoneInvalid = false;
|
||||
private bool isEmailInvalid = false;
|
||||
private bool isApplyDisabled = true;
|
||||
private string phone = "";
|
||||
private string email = "";
|
||||
private string password = "";
|
||||
|
||||
private void ValidatePhone(ChangeEventArgs e)
|
||||
{
|
||||
errorMessage = "";
|
||||
phone = e.Value.ToString();
|
||||
if (!string.IsNullOrWhiteSpace(phone) && !Regex.IsMatch(phone, @"^\+[0-9]{1,15}$"))
|
||||
{
|
||||
phoneValidationMessage = "Please enter a valid phone number";
|
||||
isPhoneInvalid = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
phoneValidationMessage = "";
|
||||
isPhoneInvalid = false;
|
||||
}
|
||||
|
||||
UpdateApplyButtonState();
|
||||
}
|
||||
|
||||
private void ValidateEmail(ChangeEventArgs e)
|
||||
{
|
||||
errorMessage = "";
|
||||
email = e.Value.ToString();
|
||||
if (!string.IsNullOrWhiteSpace(email) && !Regex.IsMatch(email, @"^[^@\s]+@[^@\s]+\.[^@\s]+$"))
|
||||
{
|
||||
emailValidationMessage = "Please enter a valid email address.";
|
||||
isEmailInvalid = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
emailValidationMessage = "";
|
||||
isEmailInvalid = false;
|
||||
}
|
||||
UpdateApplyButtonState();
|
||||
}
|
||||
|
||||
private void UpdateApplyButtonState()
|
||||
{
|
||||
if(user.Roles.Any(role => role.Name == "User"))
|
||||
isApplyDisabled = (string.IsNullOrWhiteSpace(phone) && string.IsNullOrWhiteSpace(email)) || isPhoneInvalid || isEmailInvalid;
|
||||
else
|
||||
isApplyDisabled = string.IsNullOrWhiteSpace(password) || (string.IsNullOrWhiteSpace(phone) && string.IsNullOrWhiteSpace(email)) || isPhoneInvalid || isEmailInvalid;
|
||||
}
|
||||
|
||||
private void OnPasswordInput(ChangeEventArgs e)
|
||||
{
|
||||
errorMessage = "";
|
||||
password = e.Value.ToString();
|
||||
UpdateApplyButtonState();
|
||||
}
|
||||
|
||||
private async Task Close() => await BlazoredModal.CloseAsync(ModalResult.Ok(true));
|
||||
private async Task Cancel() => await BlazoredModal.CancelAsync();
|
||||
|
||||
private async Task Apply() {
|
||||
|
||||
await UpdateUser();
|
||||
isApplyDisabled = true;
|
||||
await GetUser();
|
||||
StateHasChanged();
|
||||
|
||||
if(user.Roles.Any(role => role.Name == "User")) {
|
||||
await Task.Delay(3000);
|
||||
await InvokeAsync(() => {
|
||||
updateMessage = "";
|
||||
StateHasChanged();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private string ShowErrorDivClass()
|
||||
{
|
||||
return string.IsNullOrEmpty(errorMessage) ? "hidden" : "alert alert-danger";
|
||||
}
|
||||
|
||||
private string ShowUpdateDivClass() {
|
||||
return string.IsNullOrEmpty(updateMessage) ? "hidden" : "alert alert-success";
|
||||
}
|
||||
}
|
118
ShoppingAssistantWebClient.Web/Pages/Settings.razor.cs
Normal file
118
ShoppingAssistantWebClient.Web/Pages/Settings.razor.cs
Normal file
@ -0,0 +1,118 @@
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using ShoppingAssistantWebClient.Web.Network;
|
||||
using ShoppingAssistantWebClient.Web.Models;
|
||||
using ShoppingAssistantWebClient.Web.Models.GlobalInstances;
|
||||
using GraphQL;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
|
||||
namespace ShoppingAssistantWebClient.Web.Pages;
|
||||
|
||||
public partial class Settings : ComponentBase
|
||||
{
|
||||
[Inject]
|
||||
private ApiClient _apiClient { get; set; }
|
||||
|
||||
[Inject]
|
||||
private IHttpContextAccessor _httpContextAccessor { get; set; }
|
||||
|
||||
public User user = new User();
|
||||
|
||||
private string errorMessage = "";
|
||||
|
||||
private string updateMessage = "";
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
await GetUser();
|
||||
}
|
||||
|
||||
public async Task GetUser() {
|
||||
try {
|
||||
var request = new GraphQLRequest {
|
||||
Query = @"
|
||||
query User($id: String!) {
|
||||
user(id: $id) {
|
||||
roles {
|
||||
id
|
||||
name
|
||||
}
|
||||
phone
|
||||
email
|
||||
}
|
||||
}",
|
||||
Variables = new
|
||||
{
|
||||
id = GlobalUser.Id,
|
||||
}
|
||||
};
|
||||
|
||||
var response = await _apiClient.QueryAsync(request);
|
||||
var responseData = response.Data;
|
||||
//System.Console.WriteLine(responseData);
|
||||
|
||||
var jsonCategoriesResponse = JsonConvert.SerializeObject(responseData.user);
|
||||
this.user = JsonConvert.DeserializeObject<User>(jsonCategoriesResponse);
|
||||
user.GuestId = _httpContextAccessor.HttpContext.Request.Cookies["guestId"];
|
||||
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
Console.WriteLine($"Error in GetUser: {ex}");
|
||||
}
|
||||
}
|
||||
|
||||
public async Task UpdateUser()
|
||||
{
|
||||
try {
|
||||
if(user.Roles.Any(role => role.Name == "User"))
|
||||
{
|
||||
updateMessage = "Your data has been successfully updated";
|
||||
}
|
||||
if(phone == "") {
|
||||
phone = user.Phone;
|
||||
}
|
||||
if(email == "") {
|
||||
email = user.Email;
|
||||
}
|
||||
var request = new GraphQLRequest
|
||||
{
|
||||
Query = @"
|
||||
mutation UpdateUser($userDto: UserDtoInput!) {
|
||||
updateUser(userDto: $userDto) {
|
||||
tokens { accessToken, refreshToken },
|
||||
user { email, phone }
|
||||
}
|
||||
}",
|
||||
Variables = new
|
||||
{
|
||||
userDto = new
|
||||
{
|
||||
id = GlobalUser.Id,
|
||||
guestId = user.GuestId,
|
||||
roles = user.Roles.Select(r => new { id = r.Id, name = r.Name }),
|
||||
email = email,
|
||||
phone = phone,
|
||||
password = password
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var response = await _apiClient.QueryAsync(request);
|
||||
var responseData = response.Data;
|
||||
System.Console.WriteLine(responseData);
|
||||
errorMessage = "";
|
||||
phone = "";
|
||||
email = "";
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
if (ex.Message.Contains("The HTTP request failed with status code InternalServerError")) {
|
||||
errorMessage = "This user is already registered.";
|
||||
} else {
|
||||
errorMessage = "Something went wrong, please try again.";
|
||||
}
|
||||
Console.WriteLine($"Error in UpdateUser: {ex}");
|
||||
}
|
||||
}
|
||||
}
|
260
ShoppingAssistantWebClient.Web/Pages/Settings.razor.css
Normal file
260
ShoppingAssistantWebClient.Web/Pages/Settings.razor.css
Normal file
@ -0,0 +1,260 @@
|
||||
.modal-dialog {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 100vh;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.close {
|
||||
background: none;
|
||||
border: none;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.close span {
|
||||
display: block;
|
||||
color: #000;
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
background-color: white;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.form-group label {
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.form-control {
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.btn {
|
||||
width: 120px;
|
||||
height: 32px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
#phone, #email {
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.validation-message {
|
||||
position: absolute;
|
||||
bottom: -20px;
|
||||
left: 0;
|
||||
color: red;
|
||||
font-size: 0.8rem;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.validation-message.active {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background-color: #007bff;
|
||||
border-color: #007bff;
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background-color: #6c757d;
|
||||
border-color: #6c757d;
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
background-color: #0056b3;
|
||||
}
|
||||
|
||||
.btn-secondary:hover {
|
||||
background-color: #545b62;
|
||||
}
|
||||
|
||||
.btn:active {
|
||||
transform: scale(0.95);
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.btn:focus {
|
||||
outline: none !important;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
|
||||
.modal-header, .modal-footer, .modal-body {
|
||||
padding: 16px !important;
|
||||
}
|
||||
|
||||
.log-in-button-left {
|
||||
margin-right: auto;
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.log-in-button-left-hidden {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.modal-footer {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media (max-width: 320px) {
|
||||
.modal-dialog {
|
||||
max-width: 90%;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.modal-header, .modal-footer, .modal-body {
|
||||
padding: 6px !important;
|
||||
}
|
||||
|
||||
.form-control {
|
||||
font-size: 0.85rem;
|
||||
padding: 0.375rem 0.75rem;
|
||||
}
|
||||
|
||||
.btn {
|
||||
font-size: 0.85rem;
|
||||
width: 80px;
|
||||
height: 28px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
#phone, #email {
|
||||
margin-bottom: 0.8rem;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 321px) and (max-width: 376px) {
|
||||
.modal-dialog {
|
||||
max-width: 300px;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.modal-header, .modal-footer, .modal-body {
|
||||
padding: 8px !important;
|
||||
}
|
||||
|
||||
.form-control {
|
||||
font-size: 0.85rem;
|
||||
padding: 0.375rem 0.75rem;
|
||||
}
|
||||
|
||||
.btn {
|
||||
font-size: 0.85rem;
|
||||
width: 70px;
|
||||
height: 28px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
#phone, #email {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
#phone-validation, #email-validation{
|
||||
margin-top: -1rem;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@media (min-width: 376px) and (max-width: 426px) {
|
||||
.modal-dialog {
|
||||
max-width: 300px;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.modal-header, .modal-footer, .modal-body {
|
||||
padding: 10px !important;
|
||||
}
|
||||
|
||||
.form-control {
|
||||
font-size: 0.9rem;
|
||||
padding: 0.375rem 0.75rem;
|
||||
}
|
||||
|
||||
.btn {
|
||||
font-size: 0.9rem;
|
||||
width: 80px;
|
||||
height: 28px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
#phone, #email {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
#phone-validation, #email-validation{
|
||||
margin-top: -1rem;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 426px) and (max-width: 768px) {
|
||||
.modal-dialog {
|
||||
width: 60%;
|
||||
max-width: 400px;
|
||||
min-width: 330px;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.modal-header, .modal-footer, .modal-body {
|
||||
padding: 12px !important;
|
||||
}
|
||||
|
||||
.form-control {
|
||||
font-size: 0.95rem;
|
||||
padding: 0.375rem 0.75rem;
|
||||
}
|
||||
|
||||
.btn {
|
||||
font-size: 0.95rem;
|
||||
width: 100px;
|
||||
height: 30px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
#phone, #email {
|
||||
margin-bottom: 1.3rem;
|
||||
}
|
||||
|
||||
#phone-validation, #email-validation{
|
||||
margin-top: -1.3rem;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 426px) and (max-width: 582px) {
|
||||
.btn {
|
||||
font-size: 0.95rem;
|
||||
width: 80px !important;
|
||||
height: 28px !important;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
line-height: 1;
|
||||
}
|
||||
}
|
@ -4,6 +4,7 @@ using ShoppingAssistantWebClient.Web.Configurations;
|
||||
using ShoppingAssistantWebClient.Web.Data;
|
||||
using ShoppingAssistantWebClient.Web.Network;
|
||||
using ShoppingAssistantWebClient.Web.Services;
|
||||
using Blazored.Modal;
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
@ -16,6 +17,7 @@ builder.Services.AddApiClient(builder.Configuration);
|
||||
builder.Services.AddSingleton<SearchService>();
|
||||
builder.Services.AddBlazoredModal();
|
||||
|
||||
|
||||
var app = builder.Build();
|
||||
|
||||
// Configure the HTTP request pipeline.
|
||||
|
@ -1,12 +1,14 @@
|
||||
@inherits LayoutComponentBase
|
||||
@using ShoppingAssistantWebClient.Web.Pages
|
||||
@using Blazored.Modal
|
||||
|
||||
<PageTitle>CARTAID</PageTitle>
|
||||
<head>
|
||||
<link rel="stylesheet" href="css/MainLayout.css" />
|
||||
</head>
|
||||
|
||||
<CascadingBlazoredModal/>
|
||||
|
||||
<div class="page">
|
||||
<div class="sidebar-menu">
|
||||
<NavMenu/>
|
||||
|
@ -55,7 +55,7 @@
|
||||
|
||||
<div class="line"></div>
|
||||
<div class="elements">
|
||||
<div class="info_user">
|
||||
<div class="info_user" @onclick="ShowModal" style="cursor: pointer;">
|
||||
<img src="/images/avatar.svg" alt="Avatar user">
|
||||
<span class="user_name">User Settings</span>
|
||||
</div>
|
||||
@ -157,6 +157,17 @@
|
||||
|
||||
}
|
||||
|
||||
private async Task ShowModal()
|
||||
{
|
||||
var options = new ModalOptions()
|
||||
{
|
||||
DisableBackgroundCancel = true,
|
||||
UseCustomLayout = true
|
||||
};
|
||||
|
||||
var modalRef = Modal.Show<Settings>("Settings", options);
|
||||
}
|
||||
|
||||
[JSInvokable]
|
||||
public static void Update(string wishlistId)
|
||||
{
|
||||
|
Loading…
Reference in New Issue
Block a user