mirror of
https://github.com/Shchoholiev/shopping-assistant-web-client.git
synced 2025-04-06 17:49:42 +00:00
commit
928b3aed92
ShoppingAssistantWebClient.Web
Models
Network
Pages
Cards.razorCards.razor.csChat.razorChat.razor.csChat.razor.cssConfirmationModal.razorConfirmationModal.razor.cssIndex.razorIndex.razor.csIndex.razor.cssLogin.cshtmlLogin.cshtml.csLogin.razorLogin.razor.csSettings.razorSettings.razor.csSettings.razor.css_Host.cshtml
Program.csShared
ShoppingAssistantWebClient.Web.csproj_Imports.razorwwwroot
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; }
|
||||
}
|
||||
}
|
@ -101,17 +101,24 @@ public class ApiClient
|
||||
await SetAuthenticationAsync();
|
||||
var count = 0; //
|
||||
var requestUrl = $"{_httpClient.BaseAddress}{url}";
|
||||
var response = await _httpClient.PostAsJsonAsync(requestUrl, obj);
|
||||
using var responseStream = await response.Content.ReadAsStreamAsync();
|
||||
using var reader = new StreamReader(responseStream, Encoding.UTF8);
|
||||
var jsonBody = JsonConvert.SerializeObject(obj);
|
||||
|
||||
SearchEventType eventType = SearchEventType.Message;
|
||||
while (!cancellationToken.IsCancellationRequested)
|
||||
var body = new StringContent(jsonBody, Encoding.UTF8, "application/json");
|
||||
var request = new HttpRequestMessage(HttpMethod.Post, requestUrl)
|
||||
{
|
||||
var jsonChunk = await reader.ReadLineAsync(cancellationToken);
|
||||
Content = body
|
||||
};
|
||||
request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("text/event-stream"));
|
||||
|
||||
using var httpResponse = await _httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken);
|
||||
using var streamReader = new StreamReader(await httpResponse.Content.ReadAsStreamAsync(cancellationToken));
|
||||
var eventType = SearchEventType.Message;
|
||||
while (!streamReader.EndOfStream)
|
||||
{
|
||||
var jsonChunk = await streamReader.ReadLineAsync(cancellationToken);
|
||||
count += 1; //
|
||||
if (count >=5 ){ //
|
||||
break; //
|
||||
yield break; //
|
||||
}; //
|
||||
if (jsonChunk == null) continue;
|
||||
if (jsonChunk.StartsWith("event: "))
|
||||
|
@ -96,7 +96,7 @@
|
||||
<div class="buttons-row">
|
||||
<button class="exit-button button-animation" @onclick="(() => NavigateToMain())"></button>
|
||||
<button class="return-button button-animation" @onclick="(() => { LoadPreviousProduct(); })"></button>
|
||||
<button class="more-button button-animation" @onclick="(() => LoadMoreProducts())"></button>
|
||||
<button class="more-button button-animation" @onclick="(() => NavigateToMain())"></button>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -19,6 +19,8 @@
|
||||
<div class="new_chat">
|
||||
|
||||
<div class="title_one_frame">@name</div>
|
||||
|
||||
<div class="gradient"></div>
|
||||
|
||||
<div class="chat_message" @ref="chatMessageRef">
|
||||
|
||||
@ -33,9 +35,27 @@
|
||||
if (item.Role != "User")
|
||||
{
|
||||
|
||||
<li class=" chat_incoming">
|
||||
<p>@item.Text</p>
|
||||
</li>
|
||||
if (@item.Text == "Waiting for response")
|
||||
{
|
||||
|
||||
<div class=" chat_incoming_wait">Waiting for response
|
||||
|
||||
<div class="loading-spinner"></div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
<li class=" chat_incoming">
|
||||
@item.Text
|
||||
</li>
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
else
|
||||
@ -52,34 +72,34 @@
|
||||
|
||||
</ul>
|
||||
|
||||
</div>
|
||||
<div class="possible_options">
|
||||
|
||||
</div>
|
||||
@if (Suggestion.Count != 0)
|
||||
{
|
||||
|
||||
<div class="tite_options">Several possible options</div>
|
||||
|
||||
<div class="possible_options">
|
||||
|
||||
@if (Suggestion.Count != 0)
|
||||
{
|
||||
|
||||
<div class="tite_options">Several possible options</div>
|
||||
|
||||
<div class="options">
|
||||
<div class="options">
|
||||
|
||||
|
||||
|
||||
@foreach (var item in Suggestion)
|
||||
{
|
||||
@foreach (var item in Suggestion)
|
||||
{
|
||||
|
||||
<div @onclick="() => ClickOption(item)" class="topic_options">
|
||||
@item
|
||||
</div>
|
||||
}
|
||||
<div @onclick="() => ClickOption(item)" class="topic_options">
|
||||
@item
|
||||
</div>
|
||||
}
|
||||
|
||||
|
||||
</div>
|
||||
}
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
}
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<div class="chat_input">
|
||||
@ -105,16 +125,31 @@
|
||||
};
|
||||
|
||||
function myJavaScriptFunction(wishlistId) {
|
||||
UpdateMenu(wishlistId);
|
||||
UpdateMenu(wishlistId);
|
||||
}
|
||||
|
||||
|
||||
|
||||
document.getElementById('button_open').addEventListener('click', changetyle);
|
||||
|
||||
</script>
|
||||
|
||||
<script>
|
||||
function myJavaScriptHeight() {
|
||||
|
||||
var myDiv = document.querySelector('.possible_options');
|
||||
|
||||
var posth = document.querySelector('.chat_message');
|
||||
var height = myDiv.offsetHeight*0.063
|
||||
posth.style.height = 'calc(100% - ' + (8.5 + height) + 'em)';
|
||||
}
|
||||
document.getElementById('button_open').addEventListener('click', myJavaScriptHeight);
|
||||
</script>
|
||||
@code {
|
||||
|
||||
[Parameter] public string chatId { get; set; }
|
||||
|
||||
public string inputValue = "";
|
||||
public string inputValue = "";
|
||||
|
||||
protected override async Task OnParametersSetAsync()
|
||||
{
|
||||
@ -139,7 +174,9 @@
|
||||
|
||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||
{
|
||||
await JSRuntime.InvokeVoidAsync("myJavaScriptHeight");
|
||||
await JSRuntime.InvokeVoidAsync("scrollToBottom", chatMessageRef);
|
||||
|
||||
}
|
||||
|
||||
private async Task UpdateSideMenu(string wishlistId)
|
||||
@ -151,6 +188,7 @@
|
||||
private void ClickOption(string item)
|
||||
{
|
||||
inputValue = item;
|
||||
AddNewMessage(inputValue);
|
||||
}
|
||||
|
||||
}
|
@ -15,100 +15,107 @@ namespace ShoppingAssistantWebClient.Web.Pages;
|
||||
public partial class Chat : ComponentBase
|
||||
{
|
||||
|
||||
[Inject]
|
||||
private ApiClient _apiClient { get; set; }
|
||||
[Inject]
|
||||
private NavigationManager Navigation { get; set; }
|
||||
[Inject]
|
||||
private SearchService _searchServise { get; set; }
|
||||
[Inject]
|
||||
private ApiClient _apiClient { get; set; }
|
||||
[Inject]
|
||||
private NavigationManager Navigation { get; set; }
|
||||
[Inject]
|
||||
private SearchService _searchServise { get; set; }
|
||||
|
||||
public List<Messages> Messages { get; set; }
|
||||
public List<Messages> Messages { get; set; }
|
||||
|
||||
|
||||
public List<String> Products { get; set; } = new List<string>();
|
||||
public List<String> Products { get; set; } = new List<string>();
|
||||
|
||||
public List<String> Suggestion { get; set; } = new List<String>();
|
||||
|
||||
public Messages Message { get; set; }
|
||||
public Messages MessageBot { get; set; }
|
||||
public List<String> Suggestion { get; set; } = new List<String>();
|
||||
|
||||
private CancellationTokenSource cancelTokenSource;
|
||||
private bool isWaitingForResponse = false;
|
||||
private MessageCreateDto messageCreateDto;
|
||||
public bool isLoading = true;
|
||||
private string name = "";
|
||||
protected override async Task OnInitializedAsync()
|
||||
public Messages Message { get; set; }
|
||||
public Messages MessageBot { get; set; }
|
||||
|
||||
private CancellationTokenSource cancelTokenSource;
|
||||
private bool isWaitingForResponse = false;
|
||||
private MessageCreateDto messageCreateDto;
|
||||
public bool isLoading = true;
|
||||
private string name = "";
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
try{
|
||||
var input = _searchServise.FirstMessage;
|
||||
var input = _searchServise.FirstMessage;
|
||||
|
||||
if (input!=null){
|
||||
if (input != null)
|
||||
{
|
||||
|
||||
await LoadMessages();
|
||||
await LoadMessages();
|
||||
|
||||
await AddNewMessage(input);
|
||||
await AddNewMessage(input);
|
||||
|
||||
string wishlistId = chatId;
|
||||
var request = new GraphQLRequest
|
||||
{
|
||||
Query = @"mutation GenerateNameForPersonalWishlist($wishlistId: String!) {
|
||||
string wishlistId = chatId;
|
||||
var request = new GraphQLRequest
|
||||
{
|
||||
Query = @"mutation GenerateNameForPersonalWishlist($wishlistId: String!) {
|
||||
generateNameForPersonalWishlist(wishlistId: $wishlistId) {
|
||||
id
|
||||
name
|
||||
}
|
||||
}",
|
||||
Variables = new
|
||||
{
|
||||
wishlistId
|
||||
Variables = new
|
||||
{
|
||||
wishlistId
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
var response = await _apiClient.QueryAsync(request);
|
||||
_searchServise.SetFirstMessage(null);
|
||||
isLoading = false;
|
||||
await UpdateSideMenu(wishlistId);
|
||||
StateHasChanged();
|
||||
|
||||
}else{
|
||||
await LoadMessages();
|
||||
}
|
||||
}catch(Exception ex){
|
||||
Console.WriteLine($"Error OnInitializedAsync: {ex.Message}");
|
||||
};
|
||||
|
||||
var response = await _apiClient.QueryAsync(request);
|
||||
_searchServise.SetFirstMessage(null);
|
||||
isLoading = false;
|
||||
await UpdateSideMenu(wishlistId);
|
||||
StateHasChanged();
|
||||
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
await LoadMessages();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"Error OnInitializedAsync: {ex.Message}");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private async Task LoadMessages()
|
||||
|
||||
private async Task LoadMessages()
|
||||
{
|
||||
try
|
||||
{
|
||||
try{
|
||||
string wishlistId = chatId;
|
||||
|
||||
var request = new GraphQLRequest
|
||||
{
|
||||
Query = @"query PersonalWishlist( $wishlistId: String!) {
|
||||
string wishlistId = chatId;
|
||||
|
||||
var request = new GraphQLRequest
|
||||
{
|
||||
Query = @"query PersonalWishlist( $wishlistId: String!) {
|
||||
personalWishlist(wishlistId: $wishlistId) {
|
||||
name
|
||||
}
|
||||
}",
|
||||
|
||||
Variables = new
|
||||
{
|
||||
wishlistId,
|
||||
}
|
||||
};
|
||||
Variables = new
|
||||
{
|
||||
wishlistId,
|
||||
}
|
||||
};
|
||||
|
||||
var response = await _apiClient.QueryAsync(request);
|
||||
var responseData = response.Data;
|
||||
name = responseData.personalWishlist.name;
|
||||
|
||||
|
||||
isLoading = true;
|
||||
int pageNumber = 1;
|
||||
request = new GraphQLRequest
|
||||
{
|
||||
Query = @"query MessagesPageFromPersonalWishlist($wishlistId: String!, $pageNumber: Int!, $pageSize: Int!) {
|
||||
isLoading = true;
|
||||
int pageNumber = 1;
|
||||
request = new GraphQLRequest
|
||||
{
|
||||
Query = @"query MessagesPageFromPersonalWishlist($wishlistId: String!, $pageNumber: Int!, $pageSize: Int!) {
|
||||
messagesPageFromPersonalWishlist( wishlistId: $wishlistId, pageNumber: $pageNumber, pageSize: $pageSize)
|
||||
{
|
||||
items {
|
||||
@ -120,112 +127,125 @@ public partial class Chat : ComponentBase
|
||||
}
|
||||
}",
|
||||
|
||||
Variables = new
|
||||
{
|
||||
wishlistId,
|
||||
pageNumber,
|
||||
pageSize = 200
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
response = await _apiClient.QueryAsync(request);
|
||||
responseData = response.Data;
|
||||
var jsonCategoriesResponse = JsonConvert.SerializeObject(responseData.messagesPageFromPersonalWishlist.items);
|
||||
this.Messages = JsonConvert.DeserializeObject<List<Messages>>(jsonCategoriesResponse);
|
||||
Messages.Reverse();
|
||||
isLoading = false;
|
||||
Variables = new
|
||||
{
|
||||
wishlistId,
|
||||
pageNumber,
|
||||
pageSize = 200
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
response = await _apiClient.QueryAsync(request);
|
||||
responseData = response.Data;
|
||||
var jsonCategoriesResponse = JsonConvert.SerializeObject(responseData.messagesPageFromPersonalWishlist.items);
|
||||
this.Messages = JsonConvert.DeserializeObject<List<Messages>>(jsonCategoriesResponse);
|
||||
Messages.Reverse();
|
||||
isLoading = false;
|
||||
|
||||
}catch(Exception ex){
|
||||
Console.WriteLine($"Error : {ex.Message}");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"Error : {ex.Message}");
|
||||
}
|
||||
}
|
||||
private async Task AddNewMessage(string inputMessage)
|
||||
{
|
||||
|
||||
if (!isWaitingForResponse && !string.IsNullOrWhiteSpace(inputMessage))
|
||||
if (!isWaitingForResponse && !string.IsNullOrWhiteSpace(inputMessage))
|
||||
{
|
||||
JSRuntime.InvokeVoidAsync("clearInput");
|
||||
isWaitingForResponse = true;
|
||||
|
||||
try{
|
||||
messageCreateDto = new MessageCreateDto { Text = inputMessage };;
|
||||
Message = new Messages();
|
||||
Message.Text = inputMessage;
|
||||
Message.Role = "User";
|
||||
Message.Id = "";
|
||||
Message.CreatedById = "";
|
||||
|
||||
Suggestion = new List<String>();
|
||||
Products = new List<String>();
|
||||
Messages.Add(Message);
|
||||
StateHasChanged();
|
||||
|
||||
cancelTokenSource = new CancellationTokenSource();
|
||||
var cancellationToken = cancelTokenSource.Token;
|
||||
|
||||
var serverSentEvent = _apiClient.GetServerSentEventStreamed($"ProductsSearch/search/{chatId}", messageCreateDto, cancellationToken);
|
||||
bool first = true;
|
||||
|
||||
MessageBot = new Messages();
|
||||
MessageBot.Role = "bot";
|
||||
MessageBot.Id = "";
|
||||
MessageBot.CreatedById = "";
|
||||
MessageBot.Text = "Waiting for response";
|
||||
Messages.Add(MessageBot);
|
||||
var lengt = Messages.Count();
|
||||
StateHasChanged();
|
||||
|
||||
await foreach (var sseEvent in serverSentEvent.WithCancellation(cancellationToken))
|
||||
{
|
||||
Console.WriteLine($"Received SSE Event: {sseEvent.Event}, Data: {sseEvent.Data}");
|
||||
|
||||
string input = sseEvent.Data;
|
||||
Regex regex = new Regex("\"(.*?)\"");
|
||||
Match match = regex.Match(input);
|
||||
string result = match.Groups[1].Value;
|
||||
|
||||
if(sseEvent.Event == SearchEventType.Message){
|
||||
|
||||
|
||||
if (first)
|
||||
{
|
||||
Messages[lengt-1].Text = result;
|
||||
first = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
Messages[lengt-1].Text += result;
|
||||
}
|
||||
try
|
||||
{
|
||||
messageCreateDto = new MessageCreateDto { Text = inputMessage }; ;
|
||||
Message = new Messages();
|
||||
Message.Text = inputMessage;
|
||||
Message.Role = "User";
|
||||
Message.Id = "";
|
||||
Message.CreatedById = "";
|
||||
|
||||
Suggestion = new List<String>();
|
||||
Products = new List<String>();
|
||||
Messages.Add(Message);
|
||||
StateHasChanged();
|
||||
|
||||
} else if(sseEvent.Event == SearchEventType.Product){
|
||||
|
||||
string pattern = "[\\\\\"]";
|
||||
|
||||
input = Regex.Replace(input, pattern, "");
|
||||
cancelTokenSource = new CancellationTokenSource();
|
||||
var cancellationToken = cancelTokenSource.Token;
|
||||
|
||||
Products.Add(input);
|
||||
var serverSentEvent = _apiClient.GetServerSentEventStreamed($"ProductsSearch/search/{chatId}", messageCreateDto, cancellationToken);
|
||||
bool first = true;
|
||||
|
||||
} else if(sseEvent.Event == SearchEventType.Suggestion){
|
||||
MessageBot = new Messages();
|
||||
MessageBot.Role = "bot";
|
||||
MessageBot.Id = "";
|
||||
MessageBot.CreatedById = "";
|
||||
MessageBot.Text = "Waiting for response";
|
||||
Messages.Add(MessageBot);
|
||||
var lengt = Messages.Count();
|
||||
StateHasChanged();
|
||||
|
||||
await foreach (var sseEvent in serverSentEvent.WithCancellation(cancellationToken))
|
||||
{
|
||||
Console.WriteLine($"Received SSE Event: {sseEvent.Event}, Data: {sseEvent.Data}");
|
||||
|
||||
string input = sseEvent.Data;
|
||||
Regex regex = new Regex("\"(.*?)\"");
|
||||
Match match = regex.Match(input);
|
||||
string result = match.Groups[1].Value;
|
||||
|
||||
if (sseEvent.Event == SearchEventType.Message)
|
||||
{
|
||||
if (first)
|
||||
{
|
||||
Messages[lengt - 1].Text = result;
|
||||
first = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
Messages[lengt - 1].Text += result;
|
||||
}
|
||||
|
||||
StateHasChanged();
|
||||
|
||||
}
|
||||
else if (sseEvent.Event == SearchEventType.Product)
|
||||
{
|
||||
|
||||
string pattern = "[\\\\\"]";
|
||||
|
||||
input = Regex.Replace(input, pattern, "");
|
||||
|
||||
Products.Add(input);
|
||||
|
||||
}
|
||||
else if (sseEvent.Event == SearchEventType.Suggestion)
|
||||
{
|
||||
if (Suggestion.Count < 3)
|
||||
{
|
||||
Suggestion.Add(result);
|
||||
StateHasChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (Products.Count != 0)
|
||||
{
|
||||
string n = name;
|
||||
_searchServise.SetProducts(Products);
|
||||
Products = null;
|
||||
var url = $"/cards/{name}/{chatId}";
|
||||
Navigation.NavigateTo(url);
|
||||
}
|
||||
isWaitingForResponse = false;
|
||||
|
||||
Suggestion.Add(result);
|
||||
}
|
||||
|
||||
}
|
||||
if(Products.Count!=0) {
|
||||
string n = name;
|
||||
_searchServise.SetProducts(Products);
|
||||
Products = null;
|
||||
var url = $"/cards/{name}/{chatId}";
|
||||
Navigation.NavigateTo(url);
|
||||
}
|
||||
isWaitingForResponse = false;
|
||||
} catch(Exception ex){
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"Error : {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -7,7 +7,7 @@
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.button_open_menu {
|
||||
z-index: 2;
|
||||
width: 1.43em;
|
||||
@ -17,6 +17,10 @@
|
||||
left: 1.56em;
|
||||
cursor: pointer;
|
||||
visibility: hidden;
|
||||
|
||||
@media screen and (max-width: 900px) {
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
|
||||
.button_open_menu span {
|
||||
@ -38,52 +42,85 @@
|
||||
}
|
||||
|
||||
.title_one_frame {
|
||||
white-space: nowrap; /* Запобігає переносу тексту на новий рядок */
|
||||
overflow: hidden; /* Сховує текст, який не влазить в блок */
|
||||
text-overflow: ellipsis; /* Додає три крапки на кінці обрізаного тексту */
|
||||
margin-left: 4em;
|
||||
margin-right: 4em;
|
||||
padding-top: 1.25em;
|
||||
color: #0052CC;
|
||||
font-size: 1.0625em;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
|
||||
.chat_input {
|
||||
background-color: #EAEAEA;
|
||||
position: absolute;
|
||||
display: flex;
|
||||
display: inline-flex;
|
||||
/* Использовать inline-flex-контейнер */
|
||||
align-items: center;
|
||||
bottom: 2em;
|
||||
margin-left: 25%;
|
||||
width: 50%;
|
||||
border-radius: 0.6em;
|
||||
|
||||
@media screen and (max-width: 750px) {
|
||||
margin-left: 15%;
|
||||
width: 70%;
|
||||
|
||||
}
|
||||
|
||||
@media screen and (max-width: 480px) {
|
||||
margin-left: 2%;
|
||||
width: 96%;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.possible_options {
|
||||
position: absolute;
|
||||
bottom: 5.5em;
|
||||
margin-left: 25%;
|
||||
width: 50%;
|
||||
border-radius: 0.6em;
|
||||
|
||||
@media screen and (max-width: 750px) {
|
||||
margin-left: 15%;
|
||||
width: 70%;
|
||||
|
||||
}
|
||||
|
||||
@media screen and (max-width: 480px) {
|
||||
margin-left: 2%;
|
||||
width: 96%;
|
||||
|
||||
}
|
||||
}
|
||||
.tite_options{
|
||||
|
||||
.tite_options {
|
||||
font-size: 0.9em;
|
||||
color: #ADADAD;
|
||||
margin-bottom: 0.5em;
|
||||
|
||||
}
|
||||
.options{
|
||||
|
||||
.options {
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
font-size: 1em;
|
||||
}
|
||||
.topic_options
|
||||
{
|
||||
display: inline-block;
|
||||
padding: 0.5em;
|
||||
border: 0.09em solid;
|
||||
border-color: #009FFF;
|
||||
border-radius: 0.6em;
|
||||
margin: 0em 0.6em;
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
|
||||
.topic_options {
|
||||
display: inline-block;
|
||||
padding: 0.5em;
|
||||
border: 0.09em solid;
|
||||
border-color: #009FFF;
|
||||
border-radius: 0.6em;
|
||||
margin: 0.2em 0.2em;
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.input_messages {
|
||||
@ -117,9 +154,10 @@
|
||||
.chat_message {
|
||||
position: relative;
|
||||
overflow-y: auto;
|
||||
height: calc(100% - 8em);
|
||||
height: calc(100% - 8.5em);
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.chat_message::-webkit-scrollbar {
|
||||
border-radius: 20px;
|
||||
width: 0.2em;
|
||||
@ -134,37 +172,99 @@
|
||||
}
|
||||
|
||||
|
||||
.chat_box{
|
||||
border-radius: 10px;
|
||||
position: absolute;
|
||||
margin-left: 25%;
|
||||
margin-top: 35px;
|
||||
width: 50%;
|
||||
list-style: none;
|
||||
padding:0;
|
||||
|
||||
.chat_box {
|
||||
border-radius: 10px;
|
||||
position: absolute;
|
||||
margin-left: 25%;
|
||||
margin-top: 35px;
|
||||
width: 50%;
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
|
||||
@media screen and (max-width: 750px) {
|
||||
margin-left: 15%;
|
||||
width: 70%;
|
||||
|
||||
}
|
||||
.chat_outgoing{
|
||||
display: flex;
|
||||
|
||||
@media screen and (max-width: 480px) {
|
||||
margin-left: 4%;
|
||||
width: 92%;
|
||||
|
||||
}
|
||||
.chat_incoming{
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.chat_outgoing {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.chat_incoming {
|
||||
display: inline-flex;
|
||||
/* Использовать inline-flex-контейнер */
|
||||
align-items: center;
|
||||
/* Выравнивание по центру */
|
||||
background-color: #EAEAEA;
|
||||
border-radius: 10px;
|
||||
color: black;
|
||||
padding: 10px;
|
||||
margin-bottom: 20px;
|
||||
margin-top: 20px;
|
||||
max-width: 70%;
|
||||
/* Максимальная ширина по вашему усмотрению */
|
||||
min-width: 155px;
|
||||
/* Максимальная ширина по вашему усмотрению */
|
||||
}
|
||||
|
||||
.chat_incoming_wait {
|
||||
display: inline-flex; /* Использовать inline-flex-контейнер */
|
||||
align-items: center; /* Выравнивание по центру */
|
||||
background-color: #EAEAEA;
|
||||
border-radius: 10px;
|
||||
color: black;
|
||||
padding: 10px;
|
||||
margin-bottom: 20px;
|
||||
margin-top: 20px;
|
||||
max-width: 70%; /* Максимальная ширина по вашему усмотрению */
|
||||
min-width: 155px; /* Максимальная ширина по вашему усмотрению */
|
||||
}
|
||||
|
||||
.chat_box .chat_outgoing p {
|
||||
margin-left: auto;
|
||||
background-color: #009FFF;
|
||||
border-radius: 10px;
|
||||
color: white;
|
||||
padding: 10px;
|
||||
max-width: 60%;
|
||||
|
||||
}
|
||||
|
||||
|
||||
@keyframes spin {
|
||||
0% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
.chat_box .chat_outgoing p {
|
||||
margin-left: auto;
|
||||
background-color: #009FFF;
|
||||
border-radius: 10px;
|
||||
color: white;
|
||||
padding: 10px;
|
||||
max-width: 60%;
|
||||
|
||||
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
.chat_box .chat_incoming p {
|
||||
background-color: #EAEAEA;
|
||||
border-radius: 10px;
|
||||
color: black;
|
||||
padding: 10px;
|
||||
width: 60%;
|
||||
margin-bottom: 20px ;
|
||||
margin-top: 20px ;
|
||||
}
|
||||
}
|
||||
|
||||
.loading-spinner {
|
||||
border: 4px solid rgba(0, 82, 204, 0.1);
|
||||
border-top: 4px solid #0052CC;
|
||||
border-radius: 50%;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
animation: spin 1s linear infinite;
|
||||
margin-left: 10px;
|
||||
/* Добавлен отступ для разделения текста и загрузки */
|
||||
}
|
||||
|
||||
.gradient {
|
||||
background: linear-gradient(rgb(255, 255, 255), rgba(0, 0, 0, 0));
|
||||
position: absolute;
|
||||
height: 50px;
|
||||
width: 100%;
|
||||
z-index: 1;
|
||||
margin-top: -0.2em;
|
||||
}
|
24
ShoppingAssistantWebClient.Web/Pages/ConfirmationModal.razor
Normal file
24
ShoppingAssistantWebClient.Web/Pages/ConfirmationModal.razor
Normal file
@ -0,0 +1,24 @@
|
||||
<div class="modal fade show d-block" tabindex="-1" role="dialog">
|
||||
<div class="modal-backdrop fade show" @onclick="Cancel"></div>
|
||||
<div class="modal-dialog modal-dialog-centered modal-sm" style="z-index: 1050">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title medium-text">Are you sure you want to delete this chat?</h5>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-sm btn-yes" @onclick="ConfirmDelete">Yes</button>
|
||||
<button type="button" class="btn btn-secondary btn-sm" @onclick="Cancel">No</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@code {
|
||||
[CascadingParameter] BlazoredModalInstance BlazoredModal { get; set; } = default!;
|
||||
|
||||
private async Task Close() => await BlazoredModal.CloseAsync(ModalResult.Ok(true));
|
||||
private async Task ConfirmDelete() => await BlazoredModal.CloseAsync(ModalResult.Ok(true));
|
||||
private async Task Cancel() => await BlazoredModal.CloseAsync(ModalResult.Cancel());
|
||||
|
||||
}
|
@ -0,0 +1,48 @@
|
||||
.modal-dialog.modal-sm {
|
||||
max-width: 300px;
|
||||
}
|
||||
|
||||
.medium-text {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.btn-sm {
|
||||
font-size: 0.9rem;
|
||||
padding: 6px 12px;
|
||||
}
|
||||
|
||||
.btn-yes {
|
||||
color: #FFFFFF;
|
||||
background-color: #FF0000;
|
||||
}
|
||||
|
||||
.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 {
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
.modal-dialog {
|
||||
max-width: 100%;
|
||||
margin: 10px auto;
|
||||
}
|
||||
.modal-content {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.close {
|
||||
background: none;
|
||||
border: none;
|
||||
outline: none;
|
||||
}
|
@ -11,18 +11,18 @@
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="new_chat">
|
||||
<div class="new_chat">
|
||||
<div class="chat_message">
|
||||
<div class="title_one_frame">New chat</div>
|
||||
<div class="title_two_frame">What you're looking for</div>
|
||||
|
||||
<div class="switch">
|
||||
<div @onclick="Сhoose_product"class="switch_product" id="choose_product">
|
||||
<button @onclick="Сhoose_product" class="switch_product" id="choose_product">
|
||||
Product
|
||||
</div>
|
||||
<div @onclick="Сhoose_gift" class="switch_gift" id="choose_gift">
|
||||
Gift
|
||||
</div>
|
||||
</button>
|
||||
<button text="Gift" hover-text="Сoming soon" onmouseover="showMessage()" onmouseout="hideMessage()"
|
||||
class="switch_gift" id="choose_gift">
|
||||
</button>
|
||||
</div>
|
||||
|
||||
|
||||
@ -30,33 +30,36 @@
|
||||
</div>
|
||||
|
||||
<div class="topic">
|
||||
<div class="topic_one">
|
||||
<div @onclick="() => ClickTopic(1)" class="topic_one">
|
||||
<a class="button_topic_one">
|
||||
Date
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="topic_two">
|
||||
<div @onclick="() => ClickTopic(2)" class="topic_two">
|
||||
<a class="button_topic_two">
|
||||
🎃 Halloween gift
|
||||
</a>
|
||||
</div>
|
||||
<div class="topic_three">
|
||||
|
||||
<div @onclick="() => ClickTopic(3)" class="topic_three">
|
||||
<a class="button_topic_three">
|
||||
🎁 Birthday gift
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<div class="chat_input">
|
||||
<input @bind="inputValue" @onkeydown="Enter" @oninput="InputChanged" class="input_messages" type="text" id="chatInput"
|
||||
placeholder="Describe what you are looking for...." autocomplete="off">
|
||||
<input @bind="inputValue" @onkeydown="Enter" @oninput="InputChanged" class="input_messages" type="text"
|
||||
id="chatInput" placeholder="Describe what you are looking for...." autocomplete="off">
|
||||
<img @onclick="CreateNewChat" class="button_sende" src="/images/send.svg" alt="Send message">
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
@ -70,11 +73,11 @@
|
||||
var choose = "Product";
|
||||
|
||||
function switchGift() {
|
||||
choose_gift.style.backgroundColor = "#0052CC";
|
||||
choose_product.style.backgroundColor = "transparent";
|
||||
switchGi.style.color = "white";
|
||||
switchProd.style.color = "#202124";
|
||||
choose = "Gift";
|
||||
// choose_gift.style.backgroundColor = "#0052CC";
|
||||
// choose_product.style.backgroundColor = "transparent";
|
||||
// switchGi.style.color = "white";
|
||||
//switchProd.style.color = "#202124";
|
||||
//choose = "Gift";
|
||||
|
||||
}
|
||||
|
||||
@ -88,17 +91,10 @@
|
||||
}
|
||||
function myJavaScriptFunction(wishlistId) {
|
||||
|
||||
UpdateMenu(wishlistId);
|
||||
UpdateMenu(wishlistId);
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
document.getElementById('choose_gift').addEventListener('click', switchGift);
|
||||
document.getElementById('choose_product').addEventListener('click', switchProduct);
|
||||
|
||||
@ -110,17 +106,31 @@
|
||||
private void Сhoose_product() {
|
||||
selectedChoice = "Product";
|
||||
}
|
||||
private void Сhoose_gift() {
|
||||
private void Сhoose_gift() {
|
||||
selectedChoice = "Gift";
|
||||
}
|
||||
|
||||
private async Task UpdateSideMenu(string wishlistId)
|
||||
{
|
||||
|
||||
await JSRuntime.InvokeVoidAsync("myJavaScriptFunction", wishlistId);
|
||||
await JSRuntime.InvokeVoidAsync("myJavaScriptFunction", wishlistId);
|
||||
|
||||
}
|
||||
|
||||
|
||||
public void ClickTopic(int input)
|
||||
{
|
||||
if (input == 1){
|
||||
inputValue ="I need a present for a date";
|
||||
}
|
||||
if (input == 2){
|
||||
inputValue ="I need a present for halloween";
|
||||
}
|
||||
if (input == 3){
|
||||
inputValue ="I need a present for a birthday";
|
||||
}
|
||||
CreateNewChat();
|
||||
}
|
||||
private void InputChanged(ChangeEventArgs e)
|
||||
{
|
||||
// Оновіть значення поля введення при кожному введенні тексту
|
||||
|
@ -26,7 +26,6 @@ namespace ShoppingAssistantWebClient.Web.Pages
|
||||
private MessageCreateDto messageCreateDto;
|
||||
private string inputValue = "";
|
||||
|
||||
|
||||
private async Task CreateNewChat() {
|
||||
|
||||
try
|
||||
|
@ -16,7 +16,6 @@
|
||||
left: 1.56em;
|
||||
cursor: pointer;
|
||||
visibility: hidden;
|
||||
|
||||
@media screen and (max-width: 900px) {
|
||||
visibility: visible;
|
||||
}
|
||||
@ -62,6 +61,11 @@
|
||||
font-size: 2.5em;
|
||||
text-align: center;
|
||||
font-weight: 600;
|
||||
@media screen and (max-width: 480px) {
|
||||
|
||||
font-size: 1.7125em;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
.title_three_frame {
|
||||
@ -71,6 +75,13 @@
|
||||
font-size: 1.25em;
|
||||
text-align: center;
|
||||
font-weight: 400;
|
||||
@media screen and (max-width: 480px) {
|
||||
|
||||
font-size: 1.0125em;
|
||||
padding-left: 4%;
|
||||
padding-right: 4%;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
.topic {
|
||||
@ -80,6 +91,12 @@
|
||||
color: #009FFF;
|
||||
width: 15.625em;
|
||||
margin: 0 auto;
|
||||
@media screen and (max-width: 480px) {
|
||||
|
||||
width: 11.875em;
|
||||
font-size: 0.9125em;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
.topic div {
|
||||
@ -106,6 +123,11 @@
|
||||
border-radius: 0.6em;
|
||||
text-align: center;
|
||||
position: relative;
|
||||
@media screen and (max-width: 480px) {
|
||||
height: 2.4125em;
|
||||
width: 16.875em;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
.switch_product {
|
||||
@ -137,7 +159,41 @@
|
||||
transition: 0.8s;
|
||||
color: #202124;
|
||||
}
|
||||
button::before {
|
||||
content: attr(text);
|
||||
|
||||
}
|
||||
|
||||
button:hover::before {
|
||||
content: attr(hover-text);
|
||||
}
|
||||
button {
|
||||
outline: none; /* Для синий ободки */
|
||||
border: 0;
|
||||
background: transparent;
|
||||
}
|
||||
.message {
|
||||
right: 0;
|
||||
position: absolute;
|
||||
border-radius: 0.6em;
|
||||
margin: 0.3125em;
|
||||
width: calc(50% - 0.625em);
|
||||
height: calc(100% - 0.625em);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
transition: 0.8s;
|
||||
color: #202124;
|
||||
padding: 4px; /* Добавьте подходящий отступ, если необходимо */
|
||||
}
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.show {
|
||||
display: block;
|
||||
}
|
||||
.chat_message {
|
||||
position: relative;
|
||||
overflow-y: auto;
|
||||
@ -148,14 +204,25 @@
|
||||
.chat_input {
|
||||
background-color: #EAEAEA;
|
||||
position: absolute;
|
||||
display: flex;
|
||||
display: inline-flex; /* Использовать inline-flex-контейнер */
|
||||
align-items: center;
|
||||
bottom: 2em;
|
||||
margin-left: 25%;
|
||||
width: 50%;
|
||||
border-radius: 0.6em;
|
||||
@media screen and (max-width: 750px) {
|
||||
margin-left: 15%;
|
||||
width: 70%;
|
||||
|
||||
}
|
||||
@media screen and (max-width: 480px) {
|
||||
margin-left: 2%;
|
||||
width: 96%;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.input_messages {
|
||||
width: 100%;
|
||||
height: 2.5em;
|
||||
|
@ -1,8 +0,0 @@
|
||||
@page "/login"
|
||||
@model ShoppingAssistantWebClient.Web.Pages.LoginModel
|
||||
|
||||
<h1>Login</h1>
|
||||
|
||||
@{
|
||||
|
||||
}
|
@ -1,12 +0,0 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||
|
||||
namespace ShoppingAssistantWebClient.Web.Pages
|
||||
{
|
||||
public class LoginModel : PageModel
|
||||
{
|
||||
public void OnGet()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
82
ShoppingAssistantWebClient.Web/Pages/Login.razor
Normal file
82
ShoppingAssistantWebClient.Web/Pages/Login.razor
Normal file
@ -0,0 +1,82 @@
|
||||
@page "/login"
|
||||
|
||||
@using System.Text.RegularExpressions
|
||||
@using Microsoft.AspNetCore.Components.Forms
|
||||
@using ShoppingAssistantWebClient.Web.Models.Input
|
||||
@using Models.GlobalInstances
|
||||
|
||||
|
||||
<head>
|
||||
<link rel="stylesheet" href="css/Login.css" />
|
||||
</head>
|
||||
|
||||
|
||||
<div class="login-container">
|
||||
<div class="login-form">
|
||||
@if (!string.IsNullOrWhiteSpace(errorMessage))
|
||||
{
|
||||
<div class="error-message-container">
|
||||
<span class="validation-message error">@errorMessage</span>
|
||||
</div>
|
||||
}
|
||||
<input type="tel" @bind="LoginInput.Phone" placeholder="Phone Number" pattern="\+?[0-9]{10,15}" required @bind:event="oninput" @onchange="ValidatePhone"/>
|
||||
<span class="validation-message">@phoneValidationMessage</span>
|
||||
<div class="or">or</div>
|
||||
<input type="email" @bind="LoginInput.Email" placeholder="Email" required @bind:event="oninput" @onchange="ValidateEmail"/>
|
||||
<span class="validation-message">@emailValidationMessage</span>
|
||||
<input type="password" @bind="LoginInput.Password" placeholder="Password" />
|
||||
<button class="login-button" @onclick="HandleLogin">Login</button>
|
||||
<button class="back-button" @onclick="RedirectToNewChat">Back</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@code {
|
||||
private string phoneValidationMessage = "";
|
||||
private string emailValidationMessage = "";
|
||||
private bool isPhoneInvalid = false;
|
||||
private bool isEmailInvalid = false;
|
||||
|
||||
private LoginInputModel LoginInput = new LoginInputModel();
|
||||
|
||||
private void ValidatePhone()
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(LoginInput.Phone) && !Regex.IsMatch(LoginInput.Phone, @"^\+[0-9]{1,15}$"))
|
||||
{
|
||||
phoneValidationMessage = "Please enter a valid phone number";
|
||||
isPhoneInvalid = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
phoneValidationMessage = "";
|
||||
isPhoneInvalid = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void ValidateEmail()
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(LoginInput.Email) && !Regex.IsMatch(LoginInput.Email, @"^[^@\s]+@[^@\s]+\.[^@\s]+$"))
|
||||
{
|
||||
emailValidationMessage = "Please enter a valid email address";
|
||||
isEmailInvalid = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
emailValidationMessage = "";
|
||||
isEmailInvalid = false;
|
||||
}
|
||||
}
|
||||
|
||||
private bool HasValidationErrors()
|
||||
{
|
||||
return !string.IsNullOrWhiteSpace(phoneValidationMessage) || !string.IsNullOrWhiteSpace(emailValidationMessage);
|
||||
}
|
||||
private async Task HandleLogin()
|
||||
{
|
||||
if (HasValidationErrors())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
await LoginUser(LoginInput);
|
||||
}
|
||||
}
|
43
ShoppingAssistantWebClient.Web/Pages/Login.razor.cs
Normal file
43
ShoppingAssistantWebClient.Web/Pages/Login.razor.cs
Normal file
@ -0,0 +1,43 @@
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using ShoppingAssistantWebClient.Web.Network;
|
||||
using ShoppingAssistantWebClient.Web.Models.Input;
|
||||
|
||||
|
||||
namespace ShoppingAssistantWebClient.Web.Pages;
|
||||
|
||||
public partial class Login : ComponentBase
|
||||
{
|
||||
|
||||
[Inject]
|
||||
NavigationManager NavigationManager { get; set; }
|
||||
|
||||
[Inject]
|
||||
private AuthenticationService _authenticationService { get; set; }
|
||||
|
||||
private string errorMessage = "";
|
||||
|
||||
|
||||
private void RedirectToNewChat() {
|
||||
var url = $"/";
|
||||
NavigationManager.NavigateTo(url);
|
||||
}
|
||||
|
||||
public async Task LoginUser(LoginInputModel login) {
|
||||
if (login.IsEmailOrPhoneProvided)
|
||||
{
|
||||
try
|
||||
{
|
||||
await _authenticationService.LoginAsync(login);
|
||||
RedirectToNewChat();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
errorMessage = "Login failed. Please try again.";
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
errorMessage = "Please provide an email or phone number.";
|
||||
}
|
||||
}
|
||||
}
|
158
ShoppingAssistantWebClient.Web/Pages/Settings.razor
Normal file
158
ShoppingAssistantWebClient.Web/Pages/Settings.razor
Normal file
@ -0,0 +1,158 @@
|
||||
@using System.Text.RegularExpressions
|
||||
@using Models.GlobalInstances
|
||||
@using ShoppingAssistantWebClient.Web.Models
|
||||
|
||||
@inject IHttpContextAccessor httpContextAccessor;
|
||||
@inject NavigationManager NavigationManager;
|
||||
|
||||
<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" value="@phone">
|
||||
<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" value="@email">
|
||||
<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" @onclick="RedirectToLogin">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 void RedirectToLogin() {
|
||||
var url = $"/login";
|
||||
NavigationManager.NavigateTo(url);
|
||||
}
|
||||
|
||||
private string ShowErrorDivClass()
|
||||
{
|
||||
return string.IsNullOrEmpty(errorMessage) ? "hidden" : "alert alert-danger";
|
||||
}
|
||||
|
||||
private string ShowUpdateDivClass() {
|
||||
return string.IsNullOrEmpty(updateMessage) ? "hidden" : "alert alert-success";
|
||||
}
|
||||
}
|
122
ShoppingAssistantWebClient.Web/Pages/Settings.razor.cs
Normal file
122
ShoppingAssistantWebClient.Web/Pages/Settings.razor.cs
Normal file
@ -0,0 +1,122 @@
|
||||
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"];
|
||||
|
||||
this.phone = user.Phone;
|
||||
this.email = user.Email;
|
||||
StateHasChanged();
|
||||
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
@ -12,6 +12,8 @@
|
||||
<link rel="stylesheet" href="css/bootstrap/bootstrap.min.css" />
|
||||
<link href="css/site.css" rel="stylesheet" />
|
||||
<link href="ShoppingAssistantWebClient.Web.styles.css" rel="stylesheet" />
|
||||
<link href="_content/Blazored.Modal/blazored-modal.css" rel="stylesheet" />
|
||||
<link href="css/Settings.css" rel="stylesheet" />
|
||||
<link rel="icon" type="image/png" href="favicon.ico"/>
|
||||
<component type="typeof(HeadOutlet)" render-mode="ServerPrerendered" />
|
||||
</head>
|
||||
|
@ -1,8 +1,10 @@
|
||||
using Blazored.Modal;
|
||||
using GraphQL.Client.Http;
|
||||
using ShoppingAssistantWebClient.Web.Configurations;
|
||||
using ShoppingAssistantWebClient.Web.Data;
|
||||
using ShoppingAssistantWebClient.Web.Network;
|
||||
using ShoppingAssistantWebClient.Web.Services;
|
||||
using Blazored.Modal;
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
@ -13,6 +15,8 @@ builder.Services.AddServerSideBlazor().AddCircuitOptions(options => { options.De
|
||||
builder.Services.AddSingleton<WeatherForecastService>();
|
||||
builder.Services.AddApiClient(builder.Configuration);
|
||||
builder.Services.AddSingleton<SearchService>();
|
||||
builder.Services.AddBlazoredModal();
|
||||
|
||||
|
||||
var app = builder.Build();
|
||||
|
||||
|
@ -1,10 +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/>
|
||||
|
@ -2,8 +2,10 @@
|
||||
@using System.Linq
|
||||
@using Microsoft.AspNetCore.Components
|
||||
@using Microsoft.JSInterop
|
||||
@using ShoppingAssistantWebClient.Web.Pages
|
||||
@inject NavigationManager Navigation
|
||||
@inject IJSRuntime JSRuntime;
|
||||
@inject IModalService Modal;
|
||||
|
||||
<div id="leftframe" class="left_frame">
|
||||
|
||||
@ -39,8 +41,10 @@
|
||||
|
||||
<section class="cont_wishlist @(selectedWishlistId == item.Id ? "selected_wishlist" : "")">
|
||||
<div class="wishlist_name" @onclick="() => RedirectToPage(item.Id)">@item.Name</div>
|
||||
<img class="button_delete_chat" @onclick="() => DeleteWishlist(item.Id)" src="/images/icon_delete.svg" alt="Delete wishlist">
|
||||
<img class="button_open_card" @onclick="() => RedirectToCart(item.Id)" src="/images/icon_open_card.svg" alt="Card open">
|
||||
<div class="wishlist_buttons">
|
||||
<img class="button_delete_chat" @onclick="() => DeleteWishlist(item.Id)" src="/images/icon_delete.svg" alt="Delete wishlist">
|
||||
<img class="button_open_card" @onclick="() => RedirectToCart(item.Id)" src="/images/icon_open_card.svg" alt="Card open">
|
||||
</div>
|
||||
</section>
|
||||
}
|
||||
}
|
||||
@ -51,10 +55,9 @@
|
||||
|
||||
<div class="line"></div>
|
||||
<div class="elements">
|
||||
<div class="info_user">
|
||||
<img src="/images/avatar.jpg" alt="Avatar user">
|
||||
<!-- Change to name -->
|
||||
<span class="user_name">@GlobalUser.Id</span>
|
||||
<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>
|
||||
|
||||
</div>
|
||||
@ -76,42 +79,57 @@
|
||||
var button_open = document.querySelector('.button_open_menu');
|
||||
|
||||
if (transformValue === 'matrix(1, 0, 0, 1, 0, 0)') {
|
||||
left_frame.style.transform = 'translateX(-110%)';
|
||||
button_open.style.visibility = 'visible';
|
||||
right_frame.style.left = '1.25em';
|
||||
amazonAssociate.style.left ='calc(50% - 12.5em)';
|
||||
|
||||
// меню закрите
|
||||
if (window.innerWidth < 1100) {
|
||||
right_frame.style.left = '1.25em';
|
||||
amazonAssociate.style.left ='calc(50% - 12.5em)';
|
||||
right_frame.style.zIndex = '0';
|
||||
left_frame.style.transform = 'translateX(-110%)';
|
||||
button_open.style.visibility = 'visible';
|
||||
}else{
|
||||
amazonAssociate.style.left ='calc(50% - 12.5em)';
|
||||
left_frame.style.transform = 'translateX(-110%)';
|
||||
button_open.style.visibility = 'visible';
|
||||
right_frame.style.left = '1.25em';
|
||||
}
|
||||
|
||||
|
||||
} else {
|
||||
left_frame.style.transform = 'translateX(0)';
|
||||
button_open.style.visibility = 'hidden';
|
||||
right_frame.style. left = '23.25em';
|
||||
amazonAssociate.style.left ='50%';
|
||||
|
||||
// меню відкрите
|
||||
if (window.innerWidth < 1100) {
|
||||
right_frame.style.left = '1.25em';
|
||||
amazonAssociate.style.left ='calc(50% - 12.5em)';
|
||||
right_frame.style.zIndex = '0';
|
||||
left_frame.style.zIndex = '1';
|
||||
left_frame.style.transform = 'translateX(0)';
|
||||
button_open.style.visibility = 'hidden';
|
||||
}else{
|
||||
amazonAssociate.style.left ='50%';
|
||||
left_frame.style.transform = 'translateX(0)';
|
||||
button_open.style.visibility = 'hidden';
|
||||
right_frame.style. left = '23.25em';
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
document.getElementById('button_close').addEventListener('click', changetyle);
|
||||
|
||||
/*
|
||||
window.getScrollTop = function (element) {
|
||||
return element.scrollTop;
|
||||
};
|
||||
|
||||
window.getOffsetHeight = function (element) {
|
||||
return element.offsetHeight;
|
||||
};
|
||||
|
||||
window.getScrollHeight = function (element) {
|
||||
return element.scrollHeight;
|
||||
};
|
||||
|
||||
window.setScrollTop = function (element, scrollTop) {
|
||||
element.scrollTop = scrollTop;
|
||||
};
|
||||
*/
|
||||
</script>
|
||||
|
||||
|
||||
<script>
|
||||
|
||||
var left_frame = document.querySelector('.sidebar-menu');
|
||||
var right_frame = document.querySelector('.right-frame');
|
||||
var amazonAssociate = document.querySelector('.amazon-associate');
|
||||
|
||||
var computedStyles = getComputedStyle(left_frame);
|
||||
var transformValue = computedStyles.transform;
|
||||
var button_open = document.querySelector('.button_open_menu');
|
||||
|
||||
|
||||
function UpdateMenu(wishlistId)
|
||||
{
|
||||
|
||||
@ -119,6 +137,8 @@
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
@code {
|
||||
@ -137,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)
|
||||
{
|
||||
@ -159,8 +190,17 @@
|
||||
}
|
||||
private async void DeleteWishlist(string itemId) {
|
||||
|
||||
await DeleteWish(itemId);
|
||||
var options = new ModalOptions
|
||||
{
|
||||
UseCustomLayout = true
|
||||
};
|
||||
|
||||
var parameters = new ModalParameters();
|
||||
|
||||
var result = await Modal.Show<ConfirmationModal>("", parameters, options).Result;
|
||||
|
||||
if (!result.Cancelled)
|
||||
await DeleteWish(itemId);
|
||||
}
|
||||
/*
|
||||
private int savedScrollTop = 0;
|
||||
@ -183,6 +223,4 @@
|
||||
}
|
||||
*/
|
||||
|
||||
|
||||
|
||||
}
|
@ -11,6 +11,10 @@
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding-bottom: 1.5%;
|
||||
background-color: white;
|
||||
margin-left: 0.3em;
|
||||
border-radius: 0.6em 0 0em 0em;
|
||||
|
||||
}
|
||||
|
||||
.menu {
|
||||
@ -22,6 +26,7 @@
|
||||
border-color: #0052CC;
|
||||
border-radius: 0.6em;
|
||||
padding-top: 1.5%;
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
.elements_wishlisht {
|
||||
@ -46,7 +51,6 @@
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border-radius: 0 0 0.6em 0.6em;
|
||||
background-color: rgb(255, 255, 255);
|
||||
height: 3.6em;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
@ -55,6 +59,9 @@
|
||||
padding-right: 1.25em;
|
||||
border-top: 1px solid #0165FF;
|
||||
}
|
||||
.info_user:hover{
|
||||
background-color: #e6e6e6;
|
||||
}
|
||||
|
||||
.logo_name {
|
||||
padding-top: 0.5em;
|
||||
@ -76,15 +83,17 @@
|
||||
.wishlist_name {
|
||||
padding-left: 0.7em;
|
||||
padding-right: 5em;
|
||||
width: 10.5em;
|
||||
width: 15.5em;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
position: absolute;
|
||||
height: 2.5em;
|
||||
line-height: 2.5em;
|
||||
cursor: pointer;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
|
||||
.cont_wishlist {
|
||||
margin-top: 0.4375em;
|
||||
margin-bottom: 0.4375em;
|
||||
@ -93,7 +102,7 @@
|
||||
font-size: 1.1em;
|
||||
width: 100%;
|
||||
height: 2.5em;
|
||||
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.cont_wishlist:hover {
|
||||
@ -119,7 +128,7 @@
|
||||
font-size: 1.1em;
|
||||
width: 100%;
|
||||
height: 2.5em;
|
||||
background-color: #e6e6e6;
|
||||
background-color: #e6e6e6;
|
||||
transition: 0.2s;
|
||||
|
||||
}
|
||||
@ -165,11 +174,27 @@
|
||||
margin-top: 0.55em;
|
||||
margin-right: 1em;
|
||||
float: right;
|
||||
visibility: hidden;
|
||||
height: 1.4em;
|
||||
width: 1.5em;
|
||||
}
|
||||
|
||||
|
||||
.wishlist_buttons {
|
||||
display: none;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
width: 4.9em;
|
||||
height: 2.5em;
|
||||
background-color: #e6e6e6;
|
||||
border-radius: 0.6em;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.cont_wishlist:hover .wishlist_buttons
|
||||
{
|
||||
display: block;
|
||||
}
|
||||
|
||||
.wishlist_names::-webkit-scrollbar {
|
||||
border-radius: 20px;
|
||||
width: 0.2em;
|
||||
@ -177,9 +202,7 @@
|
||||
|
||||
.wishlist_names::-webkit-scrollbar-thumb {
|
||||
background-color: #0052CC;
|
||||
/* Колір позиції покажчика */
|
||||
border-radius: 10px;
|
||||
/* Закруглення країв позиції покажчика */
|
||||
width: 0.2em;
|
||||
}
|
||||
|
||||
@ -211,17 +234,18 @@
|
||||
}
|
||||
|
||||
.user_name {
|
||||
padding-left: 0.4375em;
|
||||
font-size: 1em;
|
||||
padding-left: 0.4em;
|
||||
font-size: 1.2em;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.info_user img {
|
||||
float: left;
|
||||
border-radius: 50%;
|
||||
width: 2.3em;
|
||||
height: 2.3em;
|
||||
width: 2.4em;
|
||||
height: 2.4em;
|
||||
margin-left: 3.3em;
|
||||
|
||||
}
|
||||
|
||||
.button_close_menu {
|
||||
|
@ -7,6 +7,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Blazored.Modal" Version="7.1.0" />
|
||||
<PackageReference Include="GraphQL.Client" Version="6.0.1" />
|
||||
<PackageReference Include="GraphQL.Client.Serializer.Newtonsoft" Version="6.0.1" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
|
@ -8,3 +8,6 @@
|
||||
@using Microsoft.JSInterop
|
||||
@using ShoppingAssistantWebClient.Web
|
||||
@using ShoppingAssistantWebClient.Web.Shared
|
||||
@using Blazored
|
||||
@using Blazored.Modal
|
||||
@using Blazored.Modal.Services
|
112
ShoppingAssistantWebClient.Web/wwwroot/css/Login.css
Normal file
112
ShoppingAssistantWebClient.Web/wwwroot/css/Login.css
Normal file
@ -0,0 +1,112 @@
|
||||
.page {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 96vh;
|
||||
border: 1.5% solid;
|
||||
padding: 1.25em;
|
||||
background-color: #f0f0f0;
|
||||
}
|
||||
|
||||
.sidebar-menu {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.right-frame {
|
||||
position: absolute;
|
||||
right: 1.25em;
|
||||
left: 1.25em;
|
||||
top: 1.25em;
|
||||
bottom: 0em;
|
||||
transition: 1s;
|
||||
|
||||
@media screen and (max-width: 900px) {
|
||||
left: 1.25em;
|
||||
}
|
||||
}
|
||||
|
||||
footer {
|
||||
height: 4vh;
|
||||
background-color: #f0f0f0;
|
||||
}
|
||||
|
||||
.amazon-associate {
|
||||
position: relative;
|
||||
left: calc(50% - 12.5em);
|
||||
transform: translateX(-50%);
|
||||
bottom: 0;
|
||||
font-size: 0.7em;
|
||||
color: #000;
|
||||
text-decoration: none;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: 0.5s;
|
||||
background-color: #f0f0f0;
|
||||
}
|
||||
|
||||
.login-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 96vh;
|
||||
background-color: #f0f0f0;
|
||||
}
|
||||
|
||||
.login-form {
|
||||
max-width: 570px;
|
||||
background-color: white;
|
||||
padding: 20px;
|
||||
border-radius: 5px;
|
||||
box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.login-form input[type="tel"],
|
||||
.login-form input[type="email"],
|
||||
.login-form input[type="password"] {
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
margin: 10px 0;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.or {
|
||||
text-align: center;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.login-button,
|
||||
.back-button {
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
margin: 10px 0;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
background-color: #007bff;
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.back-button {
|
||||
background-color: #ccc;
|
||||
}
|
||||
|
||||
.login-button:hover,
|
||||
.back-button:hover {
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
.login-form {
|
||||
width: 90%;
|
||||
}
|
||||
}
|
||||
|
||||
.error-message-container {
|
||||
text-align: center;
|
||||
margin: 10px 0;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.validation-message.error {
|
||||
color: red;
|
||||
}
|
@ -9,10 +9,12 @@
|
||||
.sidebar-menu {
|
||||
position: absolute;
|
||||
width: 20em;
|
||||
top: 1.25em;
|
||||
top: 1.33em;
|
||||
bottom: 0em;
|
||||
margin-right: 1.5em;
|
||||
|
||||
transition: 1s;
|
||||
border-radius: 0.7em;
|
||||
|
||||
@media screen and (max-width: 900px) {
|
||||
transform: translateX(-110%);
|
||||
|
Binary file not shown.
Before ![]() (image error) Size: 44 KiB |
7
ShoppingAssistantWebClient.Web/wwwroot/images/avatar.svg
Normal file
7
ShoppingAssistantWebClient.Web/wwwroot/images/avatar.svg
Normal file
@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="512px" height="512px" style="shape-rendering:geometricPrecision; text-rendering:geometricPrecision; image-rendering:optimizeQuality; fill-rule:evenodd; clip-rule:evenodd" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<g><path style="opacity:0.981" fill="#0051cb" d="M 250.5,45.5 C 303.26,46.953 338.094,72.6197 355,122.5C 364.709,171.065 349.209,209.232 308.5,237C 261.156,261.469 218.322,255.302 180,218.5C 148.541,180.815 144.207,140.149 167,96.5C 186.63,66.0302 214.464,49.0302 250.5,45.5 Z M 244.5,81.5 C 278.623,78.4668 303.123,92.1335 318,122.5C 328.631,152.688 322.464,178.854 299.5,201C 272.183,220.457 244.183,221.457 215.5,204C 189.315,182.099 181.815,154.933 193,122.5C 202.941,99.7223 220.108,86.0557 244.5,81.5 Z"/></g>
|
||||
<g><path style="opacity:0.992" fill="#009eff" d="M 357.5,258.5 C 368.188,257.262 375.688,261.596 380,271.5C 380.499,277.491 380.666,283.491 380.5,289.5C 387.692,291.177 394.525,293.843 401,297.5C 406.198,292.635 411.698,288.135 417.5,284C 422.44,282.959 427.106,283.626 431.5,286C 441.171,292.708 443.337,301.208 438,311.5C 433.984,315.348 430.151,319.348 426.5,323.5C 430.303,329.764 432.97,336.43 434.5,343.5C 451.043,339.931 461.71,346.097 466.5,362C 461.89,378.151 451.223,384.317 434.5,380.5C 433.209,387.705 430.542,394.371 426.5,400.5C 429.818,404.318 433.318,407.985 437,411.5C 443.644,422.594 441.478,431.761 430.5,439C 425.918,441.323 421.251,441.656 416.5,440C 410.727,436.064 405.394,431.564 400.5,426.5C 394.314,430.174 387.814,433.174 381,435.5C 383.986,451.555 377.82,461.888 362.5,466.5C 345.984,462.162 339.65,451.496 343.5,434.5C 336.43,432.97 329.764,430.303 323.5,426.5C 318.606,431.564 313.273,436.064 307.5,440C 302.696,441.97 298.03,441.637 293.5,439C 291,436.5 288.5,434 286,431.5C 282.667,425.5 282.667,419.5 286,413.5C 290.214,409.622 294.048,405.455 297.5,401C 293.843,394.525 291.177,387.692 289.5,380.5C 283.491,380.666 277.491,380.499 271.5,380C 260.155,374.814 256.321,366.314 260,354.5C 267.151,344.109 276.985,340.442 289.5,343.5C 291.091,336.559 293.758,330.059 297.5,324C 293.347,319.178 289.18,314.345 285,309.5C 281.307,299.714 283.807,291.881 292.5,286C 297.988,283.085 303.655,282.752 309.5,285C 314.345,289.18 319.178,293.347 324,297.5C 330.059,293.758 336.559,291.091 343.5,289.5C 340.414,275.356 345.081,265.023 357.5,258.5 Z M 361.5,323.5 C 385.544,325.182 398.544,338.016 400.5,362C 399,386.167 386.167,399 362,400.5C 338.51,398.343 325.677,385.51 323.5,362C 325.677,338.657 338.344,325.823 361.5,323.5 Z"/></g>
|
||||
<g><path style="opacity:0.979" fill="#0051cb" d="M 180.5,280.5 C 199.836,280.333 219.17,280.5 238.5,281C 250.834,286.335 254.334,295.168 249,307.5C 246.408,310.762 243.241,313.262 239.5,315C 221.5,315.333 203.5,315.667 185.5,316C 138.041,321.793 110.541,348.293 103,395.5C 100.63,406.374 103.13,415.874 110.5,424C 113.554,426.194 116.888,427.861 120.5,429C 160.213,429.023 199.88,429.69 239.5,431C 252.873,439.14 254.54,449.14 244.5,461C 241.874,463.146 238.874,464.479 235.5,465C 198.167,465.667 160.833,465.667 123.5,465C 95.9524,461.123 77.7858,445.956 69,419.5C 64.2087,370.919 81.3754,332.086 120.5,303C 138.754,290.64 158.754,283.14 180.5,280.5 Z"/></g>
|
||||
</svg>
|
After (image error) Size: 3.3 KiB |
Loading…
Reference in New Issue
Block a user