mirror of
https://github.com/Shchoholiev/shopping-assistant-web-client.git
synced 2025-04-11 01:18:50 +00:00
commit
928b3aed92
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();
|
await SetAuthenticationAsync();
|
||||||
var count = 0; //
|
var count = 0; //
|
||||||
var requestUrl = $"{_httpClient.BaseAddress}{url}";
|
var requestUrl = $"{_httpClient.BaseAddress}{url}";
|
||||||
var response = await _httpClient.PostAsJsonAsync(requestUrl, obj);
|
var jsonBody = JsonConvert.SerializeObject(obj);
|
||||||
using var responseStream = await response.Content.ReadAsStreamAsync();
|
|
||||||
using var reader = new StreamReader(responseStream, Encoding.UTF8);
|
|
||||||
|
|
||||||
SearchEventType eventType = SearchEventType.Message;
|
var body = new StringContent(jsonBody, Encoding.UTF8, "application/json");
|
||||||
while (!cancellationToken.IsCancellationRequested)
|
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; //
|
count += 1; //
|
||||||
if (count >=5 ){ //
|
if (count >=5 ){ //
|
||||||
break; //
|
yield break; //
|
||||||
}; //
|
}; //
|
||||||
if (jsonChunk == null) continue;
|
if (jsonChunk == null) continue;
|
||||||
if (jsonChunk.StartsWith("event: "))
|
if (jsonChunk.StartsWith("event: "))
|
||||||
|
@ -96,7 +96,7 @@
|
|||||||
<div class="buttons-row">
|
<div class="buttons-row">
|
||||||
<button class="exit-button button-animation" @onclick="(() => NavigateToMain())"></button>
|
<button class="exit-button button-animation" @onclick="(() => NavigateToMain())"></button>
|
||||||
<button class="return-button button-animation" @onclick="(() => { LoadPreviousProduct(); })"></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>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
@ -2,8 +2,6 @@ using Microsoft.AspNetCore.Components;
|
|||||||
using ShoppingAssistantWebClient.Web.Models;
|
using ShoppingAssistantWebClient.Web.Models;
|
||||||
using ShoppingAssistantWebClient.Web.Network;
|
using ShoppingAssistantWebClient.Web.Network;
|
||||||
using GraphQL;
|
using GraphQL;
|
||||||
using Newtonsoft.Json;
|
|
||||||
using Microsoft.JSInterop;
|
|
||||||
using ShoppingAssistantWebClient.Web.Services;
|
using ShoppingAssistantWebClient.Web.Services;
|
||||||
|
|
||||||
namespace ShoppingAssistantWebClient.Web.Pages;
|
namespace ShoppingAssistantWebClient.Web.Pages;
|
||||||
|
@ -19,6 +19,8 @@
|
|||||||
<div class="new_chat">
|
<div class="new_chat">
|
||||||
|
|
||||||
<div class="title_one_frame">@name</div>
|
<div class="title_one_frame">@name</div>
|
||||||
|
|
||||||
|
<div class="gradient"></div>
|
||||||
|
|
||||||
<div class="chat_message" @ref="chatMessageRef">
|
<div class="chat_message" @ref="chatMessageRef">
|
||||||
|
|
||||||
@ -33,9 +35,27 @@
|
|||||||
if (item.Role != "User")
|
if (item.Role != "User")
|
||||||
{
|
{
|
||||||
|
|
||||||
<li class=" chat_incoming">
|
if (@item.Text == "Waiting for response")
|
||||||
<p>@item.Text</p>
|
{
|
||||||
</li>
|
|
||||||
|
<div class=" chat_incoming_wait">Waiting for response
|
||||||
|
|
||||||
|
<div class="loading-spinner"></div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
|
||||||
|
<li class=" chat_incoming">
|
||||||
|
@item.Text
|
||||||
|
</li>
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@ -52,34 +72,34 @@
|
|||||||
|
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="possible_options">
|
||||||
|
|
||||||
</div>
|
@if (Suggestion.Count != 0)
|
||||||
|
{
|
||||||
|
|
||||||
|
<div class="tite_options">Several possible options</div>
|
||||||
|
|
||||||
<div class="possible_options">
|
<div class="options">
|
||||||
|
|
||||||
@if (Suggestion.Count != 0)
|
|
||||||
{
|
|
||||||
|
|
||||||
<div class="tite_options">Several possible options</div>
|
|
||||||
|
|
||||||
<div class="options">
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@foreach (var item in Suggestion)
|
@foreach (var item in Suggestion)
|
||||||
{
|
{
|
||||||
|
|
||||||
<div @onclick="() => ClickOption(item)" class="topic_options">
|
<div @onclick="() => ClickOption(item)" class="topic_options">
|
||||||
@item
|
@item
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
<div class="chat_input">
|
<div class="chat_input">
|
||||||
@ -105,16 +125,31 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
function myJavaScriptFunction(wishlistId) {
|
function myJavaScriptFunction(wishlistId) {
|
||||||
UpdateMenu(wishlistId);
|
UpdateMenu(wishlistId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
document.getElementById('button_open').addEventListener('click', changetyle);
|
document.getElementById('button_open').addEventListener('click', changetyle);
|
||||||
|
|
||||||
</script>
|
</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 {
|
@code {
|
||||||
|
|
||||||
[Parameter] public string chatId { get; set; }
|
[Parameter] public string chatId { get; set; }
|
||||||
|
|
||||||
public string inputValue = "";
|
public string inputValue = "";
|
||||||
|
|
||||||
protected override async Task OnParametersSetAsync()
|
protected override async Task OnParametersSetAsync()
|
||||||
{
|
{
|
||||||
@ -139,7 +174,9 @@
|
|||||||
|
|
||||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||||
{
|
{
|
||||||
|
await JSRuntime.InvokeVoidAsync("myJavaScriptHeight");
|
||||||
await JSRuntime.InvokeVoidAsync("scrollToBottom", chatMessageRef);
|
await JSRuntime.InvokeVoidAsync("scrollToBottom", chatMessageRef);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task UpdateSideMenu(string wishlistId)
|
private async Task UpdateSideMenu(string wishlistId)
|
||||||
@ -151,6 +188,7 @@
|
|||||||
private void ClickOption(string item)
|
private void ClickOption(string item)
|
||||||
{
|
{
|
||||||
inputValue = item;
|
inputValue = item;
|
||||||
|
AddNewMessage(inputValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -15,100 +15,107 @@ namespace ShoppingAssistantWebClient.Web.Pages;
|
|||||||
public partial class Chat : ComponentBase
|
public partial class Chat : ComponentBase
|
||||||
{
|
{
|
||||||
|
|
||||||
[Inject]
|
[Inject]
|
||||||
private ApiClient _apiClient { get; set; }
|
private ApiClient _apiClient { get; set; }
|
||||||
[Inject]
|
[Inject]
|
||||||
private NavigationManager Navigation { get; set; }
|
private NavigationManager Navigation { get; set; }
|
||||||
[Inject]
|
[Inject]
|
||||||
private SearchService _searchServise { get; set; }
|
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 List<String> Suggestion { get; set; } = new List<String>();
|
||||||
|
|
||||||
public Messages Message { get; set; }
|
|
||||||
public Messages MessageBot { get; set; }
|
|
||||||
|
|
||||||
private CancellationTokenSource cancelTokenSource;
|
public Messages Message { get; set; }
|
||||||
private bool isWaitingForResponse = false;
|
public Messages MessageBot { get; set; }
|
||||||
private MessageCreateDto messageCreateDto;
|
|
||||||
public bool isLoading = true;
|
private CancellationTokenSource cancelTokenSource;
|
||||||
private string name = "";
|
private bool isWaitingForResponse = false;
|
||||||
protected override async Task OnInitializedAsync()
|
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;
|
string wishlistId = chatId;
|
||||||
var request = new GraphQLRequest
|
var request = new GraphQLRequest
|
||||||
{
|
{
|
||||||
Query = @"mutation GenerateNameForPersonalWishlist($wishlistId: String!) {
|
Query = @"mutation GenerateNameForPersonalWishlist($wishlistId: String!) {
|
||||||
generateNameForPersonalWishlist(wishlistId: $wishlistId) {
|
generateNameForPersonalWishlist(wishlistId: $wishlistId) {
|
||||||
id
|
id
|
||||||
name
|
name
|
||||||
}
|
}
|
||||||
}",
|
}",
|
||||||
Variables = new
|
Variables = new
|
||||||
{
|
{
|
||||||
wishlistId
|
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;
|
||||||
string wishlistId = chatId;
|
|
||||||
|
var request = new GraphQLRequest
|
||||||
var request = new GraphQLRequest
|
{
|
||||||
{
|
Query = @"query PersonalWishlist( $wishlistId: String!) {
|
||||||
Query = @"query PersonalWishlist( $wishlistId: String!) {
|
|
||||||
personalWishlist(wishlistId: $wishlistId) {
|
personalWishlist(wishlistId: $wishlistId) {
|
||||||
name
|
name
|
||||||
}
|
}
|
||||||
}",
|
}",
|
||||||
|
|
||||||
Variables = new
|
Variables = new
|
||||||
{
|
{
|
||||||
wishlistId,
|
wishlistId,
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var response = await _apiClient.QueryAsync(request);
|
var response = await _apiClient.QueryAsync(request);
|
||||||
var responseData = response.Data;
|
var responseData = response.Data;
|
||||||
name = responseData.personalWishlist.name;
|
name = responseData.personalWishlist.name;
|
||||||
|
|
||||||
|
|
||||||
isLoading = true;
|
isLoading = true;
|
||||||
int pageNumber = 1;
|
int pageNumber = 1;
|
||||||
request = new GraphQLRequest
|
request = new GraphQLRequest
|
||||||
{
|
{
|
||||||
Query = @"query MessagesPageFromPersonalWishlist($wishlistId: String!, $pageNumber: Int!, $pageSize: Int!) {
|
Query = @"query MessagesPageFromPersonalWishlist($wishlistId: String!, $pageNumber: Int!, $pageSize: Int!) {
|
||||||
messagesPageFromPersonalWishlist( wishlistId: $wishlistId, pageNumber: $pageNumber, pageSize: $pageSize)
|
messagesPageFromPersonalWishlist( wishlistId: $wishlistId, pageNumber: $pageNumber, pageSize: $pageSize)
|
||||||
{
|
{
|
||||||
items {
|
items {
|
||||||
@ -120,112 +127,125 @@ public partial class Chat : ComponentBase
|
|||||||
}
|
}
|
||||||
}",
|
}",
|
||||||
|
|
||||||
Variables = new
|
Variables = new
|
||||||
{
|
{
|
||||||
wishlistId,
|
wishlistId,
|
||||||
pageNumber,
|
pageNumber,
|
||||||
pageSize = 200
|
pageSize = 200
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
response = await _apiClient.QueryAsync(request);
|
response = await _apiClient.QueryAsync(request);
|
||||||
responseData = response.Data;
|
responseData = response.Data;
|
||||||
var jsonCategoriesResponse = JsonConvert.SerializeObject(responseData.messagesPageFromPersonalWishlist.items);
|
var jsonCategoriesResponse = JsonConvert.SerializeObject(responseData.messagesPageFromPersonalWishlist.items);
|
||||||
this.Messages = JsonConvert.DeserializeObject<List<Messages>>(jsonCategoriesResponse);
|
this.Messages = JsonConvert.DeserializeObject<List<Messages>>(jsonCategoriesResponse);
|
||||||
Messages.Reverse();
|
Messages.Reverse();
|
||||||
isLoading = false;
|
isLoading = false;
|
||||||
|
|
||||||
}catch(Exception ex){
|
|
||||||
Console.WriteLine($"Error : {ex.Message}");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"Error : {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
private async Task AddNewMessage(string inputMessage)
|
private async Task AddNewMessage(string inputMessage)
|
||||||
{
|
{
|
||||||
|
|
||||||
if (!isWaitingForResponse && !string.IsNullOrWhiteSpace(inputMessage))
|
if (!isWaitingForResponse && !string.IsNullOrWhiteSpace(inputMessage))
|
||||||
{
|
{
|
||||||
JSRuntime.InvokeVoidAsync("clearInput");
|
JSRuntime.InvokeVoidAsync("clearInput");
|
||||||
isWaitingForResponse = true;
|
isWaitingForResponse = true;
|
||||||
|
|
||||||
try{
|
try
|
||||||
messageCreateDto = new MessageCreateDto { Text = inputMessage };;
|
{
|
||||||
Message = new Messages();
|
messageCreateDto = new MessageCreateDto { Text = inputMessage }; ;
|
||||||
Message.Text = inputMessage;
|
Message = new Messages();
|
||||||
Message.Role = "User";
|
Message.Text = inputMessage;
|
||||||
Message.Id = "";
|
Message.Role = "User";
|
||||||
Message.CreatedById = "";
|
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
Suggestion = new List<String>();
|
||||||
|
Products = new List<String>();
|
||||||
|
Messages.Add(Message);
|
||||||
StateHasChanged();
|
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);
|
|
||||||
}
|
}
|
||||||
|
catch (Exception ex)
|
||||||
}
|
{
|
||||||
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){
|
|
||||||
Console.WriteLine($"Error : {ex.Message}");
|
Console.WriteLine($"Error : {ex.Message}");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.button_open_menu {
|
.button_open_menu {
|
||||||
z-index: 2;
|
z-index: 2;
|
||||||
width: 1.43em;
|
width: 1.43em;
|
||||||
@ -17,6 +17,10 @@
|
|||||||
left: 1.56em;
|
left: 1.56em;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
visibility: hidden;
|
visibility: hidden;
|
||||||
|
|
||||||
|
@media screen and (max-width: 900px) {
|
||||||
|
visibility: visible;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.button_open_menu span {
|
.button_open_menu span {
|
||||||
@ -38,52 +42,85 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.title_one_frame {
|
.title_one_frame {
|
||||||
|
white-space: nowrap; /* Запобігає переносу тексту на новий рядок */
|
||||||
|
overflow: hidden; /* Сховує текст, який не влазить в блок */
|
||||||
|
text-overflow: ellipsis; /* Додає три крапки на кінці обрізаного тексту */
|
||||||
|
margin-left: 4em;
|
||||||
|
margin-right: 4em;
|
||||||
padding-top: 1.25em;
|
padding-top: 1.25em;
|
||||||
color: #0052CC;
|
color: #0052CC;
|
||||||
font-size: 1.0625em;
|
font-size: 1.0625em;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.chat_input {
|
.chat_input {
|
||||||
background-color: #EAEAEA;
|
background-color: #EAEAEA;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
display: flex;
|
display: inline-flex;
|
||||||
|
/* Использовать inline-flex-контейнер */
|
||||||
align-items: center;
|
align-items: center;
|
||||||
bottom: 2em;
|
bottom: 2em;
|
||||||
margin-left: 25%;
|
margin-left: 25%;
|
||||||
width: 50%;
|
width: 50%;
|
||||||
border-radius: 0.6em;
|
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 {
|
.possible_options {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
bottom: 5.5em;
|
bottom: 5.5em;
|
||||||
margin-left: 25%;
|
margin-left: 25%;
|
||||||
width: 50%;
|
width: 50%;
|
||||||
border-radius: 0.6em;
|
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;
|
font-size: 0.9em;
|
||||||
color: #ADADAD;
|
color: #ADADAD;
|
||||||
margin-bottom: 0.5em;
|
margin-bottom: 0.5em;
|
||||||
|
|
||||||
}
|
}
|
||||||
.options{
|
|
||||||
|
.options {
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
font-size: 1em;
|
font-size: 1em;
|
||||||
}
|
}
|
||||||
.topic_options
|
|
||||||
{
|
.topic_options {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
padding: 0.5em;
|
padding: 0.5em;
|
||||||
border: 0.09em solid;
|
border: 0.09em solid;
|
||||||
border-color: #009FFF;
|
border-color: #009FFF;
|
||||||
border-radius: 0.6em;
|
border-radius: 0.6em;
|
||||||
margin: 0em 0.6em;
|
margin: 0.2em 0.2em;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.input_messages {
|
.input_messages {
|
||||||
@ -117,9 +154,10 @@
|
|||||||
.chat_message {
|
.chat_message {
|
||||||
position: relative;
|
position: relative;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
height: calc(100% - 8em);
|
height: calc(100% - 8.5em);
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.chat_message::-webkit-scrollbar {
|
.chat_message::-webkit-scrollbar {
|
||||||
border-radius: 20px;
|
border-radius: 20px;
|
||||||
width: 0.2em;
|
width: 0.2em;
|
||||||
@ -134,37 +172,99 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.chat_box{
|
.chat_box {
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
margin-left: 25%;
|
margin-left: 25%;
|
||||||
margin-top: 35px;
|
margin-top: 35px;
|
||||||
width: 50%;
|
width: 50%;
|
||||||
list-style: none;
|
list-style: none;
|
||||||
padding:0;
|
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;
|
100% {
|
||||||
background-color: #009FFF;
|
transform: rotate(360deg);
|
||||||
border-radius: 10px;
|
|
||||||
color: white;
|
|
||||||
padding: 10px;
|
|
||||||
max-width: 60%;
|
|
||||||
|
|
||||||
}
|
}
|
||||||
.chat_box .chat_incoming p {
|
}
|
||||||
background-color: #EAEAEA;
|
|
||||||
border-radius: 10px;
|
.loading-spinner {
|
||||||
color: black;
|
border: 4px solid rgba(0, 82, 204, 0.1);
|
||||||
padding: 10px;
|
border-top: 4px solid #0052CC;
|
||||||
width: 60%;
|
border-radius: 50%;
|
||||||
margin-bottom: 20px ;
|
width: 20px;
|
||||||
margin-top: 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>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="new_chat">
|
<div class="new_chat">
|
||||||
<div class="chat_message">
|
<div class="chat_message">
|
||||||
<div class="title_one_frame">New chat</div>
|
<div class="title_one_frame">New chat</div>
|
||||||
<div class="title_two_frame">What you're looking for</div>
|
<div class="title_two_frame">What you're looking for</div>
|
||||||
|
|
||||||
<div class="switch">
|
<div class="switch">
|
||||||
<div @onclick="Сhoose_product"class="switch_product" id="choose_product">
|
<button @onclick="Сhoose_product" class="switch_product" id="choose_product">
|
||||||
Product
|
Product
|
||||||
</div>
|
</button>
|
||||||
<div @onclick="Сhoose_gift" class="switch_gift" id="choose_gift">
|
<button text="Gift" hover-text="Сoming soon" onmouseover="showMessage()" onmouseout="hideMessage()"
|
||||||
Gift
|
class="switch_gift" id="choose_gift">
|
||||||
</div>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
@ -30,33 +30,36 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="topic">
|
<div class="topic">
|
||||||
<div class="topic_one">
|
<div @onclick="() => ClickTopic(1)" class="topic_one">
|
||||||
<a class="button_topic_one">
|
<a class="button_topic_one">
|
||||||
Date
|
Date
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="topic_two">
|
<div @onclick="() => ClickTopic(2)" class="topic_two">
|
||||||
<a class="button_topic_two">
|
<a class="button_topic_two">
|
||||||
🎃 Halloween gift
|
🎃 Halloween gift
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="topic_three">
|
|
||||||
|
<div @onclick="() => ClickTopic(3)" class="topic_three">
|
||||||
<a class="button_topic_three">
|
<a class="button_topic_three">
|
||||||
🎁 Birthday gift
|
🎁 Birthday gift
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="chat_input">
|
<div class="chat_input">
|
||||||
<input @bind="inputValue" @onkeydown="Enter" @oninput="InputChanged" class="input_messages" type="text" id="chatInput"
|
<input @bind="inputValue" @onkeydown="Enter" @oninput="InputChanged" class="input_messages" type="text"
|
||||||
placeholder="Describe what you are looking for...." autocomplete="off">
|
id="chatInput" placeholder="Describe what you are looking for...." autocomplete="off">
|
||||||
<img @onclick="CreateNewChat" class="button_sende" src="/images/send.svg" alt="Send message">
|
<img @onclick="CreateNewChat" class="button_sende" src="/images/send.svg" alt="Send message">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -70,11 +73,11 @@
|
|||||||
var choose = "Product";
|
var choose = "Product";
|
||||||
|
|
||||||
function switchGift() {
|
function switchGift() {
|
||||||
choose_gift.style.backgroundColor = "#0052CC";
|
// choose_gift.style.backgroundColor = "#0052CC";
|
||||||
choose_product.style.backgroundColor = "transparent";
|
// choose_product.style.backgroundColor = "transparent";
|
||||||
switchGi.style.color = "white";
|
// switchGi.style.color = "white";
|
||||||
switchProd.style.color = "#202124";
|
//switchProd.style.color = "#202124";
|
||||||
choose = "Gift";
|
//choose = "Gift";
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -88,17 +91,10 @@
|
|||||||
}
|
}
|
||||||
function myJavaScriptFunction(wishlistId) {
|
function myJavaScriptFunction(wishlistId) {
|
||||||
|
|
||||||
UpdateMenu(wishlistId);
|
UpdateMenu(wishlistId);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
document.getElementById('choose_gift').addEventListener('click', switchGift);
|
document.getElementById('choose_gift').addEventListener('click', switchGift);
|
||||||
document.getElementById('choose_product').addEventListener('click', switchProduct);
|
document.getElementById('choose_product').addEventListener('click', switchProduct);
|
||||||
|
|
||||||
@ -110,17 +106,31 @@
|
|||||||
private void Сhoose_product() {
|
private void Сhoose_product() {
|
||||||
selectedChoice = "Product";
|
selectedChoice = "Product";
|
||||||
}
|
}
|
||||||
private void Сhoose_gift() {
|
private void Сhoose_gift() {
|
||||||
selectedChoice = "Gift";
|
selectedChoice = "Gift";
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task UpdateSideMenu(string wishlistId)
|
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)
|
private void InputChanged(ChangeEventArgs e)
|
||||||
{
|
{
|
||||||
// Оновіть значення поля введення при кожному введенні тексту
|
// Оновіть значення поля введення при кожному введенні тексту
|
||||||
|
@ -26,7 +26,6 @@ namespace ShoppingAssistantWebClient.Web.Pages
|
|||||||
private MessageCreateDto messageCreateDto;
|
private MessageCreateDto messageCreateDto;
|
||||||
private string inputValue = "";
|
private string inputValue = "";
|
||||||
|
|
||||||
|
|
||||||
private async Task CreateNewChat() {
|
private async Task CreateNewChat() {
|
||||||
|
|
||||||
try
|
try
|
||||||
|
@ -16,7 +16,6 @@
|
|||||||
left: 1.56em;
|
left: 1.56em;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
visibility: hidden;
|
visibility: hidden;
|
||||||
|
|
||||||
@media screen and (max-width: 900px) {
|
@media screen and (max-width: 900px) {
|
||||||
visibility: visible;
|
visibility: visible;
|
||||||
}
|
}
|
||||||
@ -62,6 +61,11 @@
|
|||||||
font-size: 2.5em;
|
font-size: 2.5em;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
|
@media screen and (max-width: 480px) {
|
||||||
|
|
||||||
|
font-size: 1.7125em;
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.title_three_frame {
|
.title_three_frame {
|
||||||
@ -71,6 +75,13 @@
|
|||||||
font-size: 1.25em;
|
font-size: 1.25em;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
|
@media screen and (max-width: 480px) {
|
||||||
|
|
||||||
|
font-size: 1.0125em;
|
||||||
|
padding-left: 4%;
|
||||||
|
padding-right: 4%;
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.topic {
|
.topic {
|
||||||
@ -80,6 +91,12 @@
|
|||||||
color: #009FFF;
|
color: #009FFF;
|
||||||
width: 15.625em;
|
width: 15.625em;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
|
@media screen and (max-width: 480px) {
|
||||||
|
|
||||||
|
width: 11.875em;
|
||||||
|
font-size: 0.9125em;
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.topic div {
|
.topic div {
|
||||||
@ -106,6 +123,11 @@
|
|||||||
border-radius: 0.6em;
|
border-radius: 0.6em;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
@media screen and (max-width: 480px) {
|
||||||
|
height: 2.4125em;
|
||||||
|
width: 16.875em;
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.switch_product {
|
.switch_product {
|
||||||
@ -137,7 +159,41 @@
|
|||||||
transition: 0.8s;
|
transition: 0.8s;
|
||||||
color: #202124;
|
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 {
|
.chat_message {
|
||||||
position: relative;
|
position: relative;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
@ -148,14 +204,25 @@
|
|||||||
.chat_input {
|
.chat_input {
|
||||||
background-color: #EAEAEA;
|
background-color: #EAEAEA;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
display: flex;
|
display: inline-flex; /* Использовать inline-flex-контейнер */
|
||||||
align-items: center;
|
align-items: center;
|
||||||
bottom: 2em;
|
bottom: 2em;
|
||||||
margin-left: 25%;
|
margin-left: 25%;
|
||||||
width: 50%;
|
width: 50%;
|
||||||
border-radius: 0.6em;
|
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 {
|
.input_messages {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 2.5em;
|
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 rel="stylesheet" href="css/bootstrap/bootstrap.min.css" />
|
||||||
<link href="css/site.css" rel="stylesheet" />
|
<link href="css/site.css" rel="stylesheet" />
|
||||||
<link href="ShoppingAssistantWebClient.Web.styles.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"/>
|
<link rel="icon" type="image/png" href="favicon.ico"/>
|
||||||
<component type="typeof(HeadOutlet)" render-mode="ServerPrerendered" />
|
<component type="typeof(HeadOutlet)" render-mode="ServerPrerendered" />
|
||||||
</head>
|
</head>
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
|
using Blazored.Modal;
|
||||||
using GraphQL.Client.Http;
|
using GraphQL.Client.Http;
|
||||||
using ShoppingAssistantWebClient.Web.Configurations;
|
using ShoppingAssistantWebClient.Web.Configurations;
|
||||||
using ShoppingAssistantWebClient.Web.Data;
|
using ShoppingAssistantWebClient.Web.Data;
|
||||||
using ShoppingAssistantWebClient.Web.Network;
|
using ShoppingAssistantWebClient.Web.Network;
|
||||||
using ShoppingAssistantWebClient.Web.Services;
|
using ShoppingAssistantWebClient.Web.Services;
|
||||||
|
using Blazored.Modal;
|
||||||
|
|
||||||
var builder = WebApplication.CreateBuilder(args);
|
var builder = WebApplication.CreateBuilder(args);
|
||||||
|
|
||||||
@ -13,6 +15,8 @@ builder.Services.AddServerSideBlazor().AddCircuitOptions(options => { options.De
|
|||||||
builder.Services.AddSingleton<WeatherForecastService>();
|
builder.Services.AddSingleton<WeatherForecastService>();
|
||||||
builder.Services.AddApiClient(builder.Configuration);
|
builder.Services.AddApiClient(builder.Configuration);
|
||||||
builder.Services.AddSingleton<SearchService>();
|
builder.Services.AddSingleton<SearchService>();
|
||||||
|
builder.Services.AddBlazoredModal();
|
||||||
|
|
||||||
|
|
||||||
var app = builder.Build();
|
var app = builder.Build();
|
||||||
|
|
||||||
|
@ -1,10 +1,14 @@
|
|||||||
@inherits LayoutComponentBase
|
@inherits LayoutComponentBase
|
||||||
@using ShoppingAssistantWebClient.Web.Pages
|
@using ShoppingAssistantWebClient.Web.Pages
|
||||||
|
@using Blazored.Modal
|
||||||
|
|
||||||
<PageTitle>CARTAID</PageTitle>
|
<PageTitle>CARTAID</PageTitle>
|
||||||
<head>
|
<head>
|
||||||
<link rel="stylesheet" href="css/MainLayout.css" />
|
<link rel="stylesheet" href="css/MainLayout.css" />
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
|
<CascadingBlazoredModal/>
|
||||||
|
|
||||||
<div class="page">
|
<div class="page">
|
||||||
<div class="sidebar-menu">
|
<div class="sidebar-menu">
|
||||||
<NavMenu/>
|
<NavMenu/>
|
||||||
|
@ -2,8 +2,10 @@
|
|||||||
@using System.Linq
|
@using System.Linq
|
||||||
@using Microsoft.AspNetCore.Components
|
@using Microsoft.AspNetCore.Components
|
||||||
@using Microsoft.JSInterop
|
@using Microsoft.JSInterop
|
||||||
|
@using ShoppingAssistantWebClient.Web.Pages
|
||||||
@inject NavigationManager Navigation
|
@inject NavigationManager Navigation
|
||||||
@inject IJSRuntime JSRuntime;
|
@inject IJSRuntime JSRuntime;
|
||||||
|
@inject IModalService Modal;
|
||||||
|
|
||||||
<div id="leftframe" class="left_frame">
|
<div id="leftframe" class="left_frame">
|
||||||
|
|
||||||
@ -39,8 +41,10 @@
|
|||||||
|
|
||||||
<section class="cont_wishlist @(selectedWishlistId == item.Id ? "selected_wishlist" : "")">
|
<section class="cont_wishlist @(selectedWishlistId == item.Id ? "selected_wishlist" : "")">
|
||||||
<div class="wishlist_name" @onclick="() => RedirectToPage(item.Id)">@item.Name</div>
|
<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">
|
<div class="wishlist_buttons">
|
||||||
<img class="button_open_card" @onclick="() => RedirectToCart(item.Id)" src="/images/icon_open_card.svg" alt="Card open">
|
<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>
|
</section>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -51,10 +55,9 @@
|
|||||||
|
|
||||||
<div class="line"></div>
|
<div class="line"></div>
|
||||||
<div class="elements">
|
<div class="elements">
|
||||||
<div class="info_user">
|
<div class="info_user" @onclick="ShowModal" style="cursor: pointer;">
|
||||||
<img src="/images/avatar.jpg" alt="Avatar user">
|
<img src="/images/avatar.svg" alt="Avatar user">
|
||||||
<!-- Change to name -->
|
<span class="user_name">User Settings</span>
|
||||||
<span class="user_name">@GlobalUser.Id</span>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
@ -76,42 +79,57 @@
|
|||||||
var button_open = document.querySelector('.button_open_menu');
|
var button_open = document.querySelector('.button_open_menu');
|
||||||
|
|
||||||
if (transformValue === 'matrix(1, 0, 0, 1, 0, 0)') {
|
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';
|
if (window.innerWidth < 1100) {
|
||||||
amazonAssociate.style.left ='calc(50% - 12.5em)';
|
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 {
|
} else {
|
||||||
left_frame.style.transform = 'translateX(0)';
|
|
||||||
button_open.style.visibility = 'hidden';
|
// меню відкрите
|
||||||
right_frame.style. left = '23.25em';
|
if (window.innerWidth < 1100) {
|
||||||
amazonAssociate.style.left ='50%';
|
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);
|
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>
|
||||||
|
|
||||||
|
|
||||||
<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)
|
function UpdateMenu(wishlistId)
|
||||||
{
|
{
|
||||||
|
|
||||||
@ -119,6 +137,8 @@
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@code {
|
@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]
|
[JSInvokable]
|
||||||
public static void Update(string wishlistId)
|
public static void Update(string wishlistId)
|
||||||
{
|
{
|
||||||
@ -159,8 +190,17 @@
|
|||||||
}
|
}
|
||||||
private async void DeleteWishlist(string itemId) {
|
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;
|
private int savedScrollTop = 0;
|
||||||
@ -183,6 +223,4 @@
|
|||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
@ -11,6 +11,10 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding-bottom: 1.5%;
|
padding-bottom: 1.5%;
|
||||||
|
background-color: white;
|
||||||
|
margin-left: 0.3em;
|
||||||
|
border-radius: 0.6em 0 0em 0em;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.menu {
|
.menu {
|
||||||
@ -22,6 +26,7 @@
|
|||||||
border-color: #0052CC;
|
border-color: #0052CC;
|
||||||
border-radius: 0.6em;
|
border-radius: 0.6em;
|
||||||
padding-top: 1.5%;
|
padding-top: 1.5%;
|
||||||
|
background-color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
.elements_wishlisht {
|
.elements_wishlisht {
|
||||||
@ -46,7 +51,6 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
border-radius: 0 0 0.6em 0.6em;
|
border-radius: 0 0 0.6em 0.6em;
|
||||||
background-color: rgb(255, 255, 255);
|
|
||||||
height: 3.6em;
|
height: 3.6em;
|
||||||
left: 0;
|
left: 0;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
@ -55,6 +59,9 @@
|
|||||||
padding-right: 1.25em;
|
padding-right: 1.25em;
|
||||||
border-top: 1px solid #0165FF;
|
border-top: 1px solid #0165FF;
|
||||||
}
|
}
|
||||||
|
.info_user:hover{
|
||||||
|
background-color: #e6e6e6;
|
||||||
|
}
|
||||||
|
|
||||||
.logo_name {
|
.logo_name {
|
||||||
padding-top: 0.5em;
|
padding-top: 0.5em;
|
||||||
@ -76,15 +83,17 @@
|
|||||||
.wishlist_name {
|
.wishlist_name {
|
||||||
padding-left: 0.7em;
|
padding-left: 0.7em;
|
||||||
padding-right: 5em;
|
padding-right: 5em;
|
||||||
width: 10.5em;
|
width: 15.5em;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
height: 2.5em;
|
height: 2.5em;
|
||||||
line-height: 2.5em;
|
line-height: 2.5em;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
z-index: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.cont_wishlist {
|
.cont_wishlist {
|
||||||
margin-top: 0.4375em;
|
margin-top: 0.4375em;
|
||||||
margin-bottom: 0.4375em;
|
margin-bottom: 0.4375em;
|
||||||
@ -93,7 +102,7 @@
|
|||||||
font-size: 1.1em;
|
font-size: 1.1em;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 2.5em;
|
height: 2.5em;
|
||||||
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cont_wishlist:hover {
|
.cont_wishlist:hover {
|
||||||
@ -119,7 +128,7 @@
|
|||||||
font-size: 1.1em;
|
font-size: 1.1em;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 2.5em;
|
height: 2.5em;
|
||||||
background-color: #e6e6e6;
|
background-color: #e6e6e6;
|
||||||
transition: 0.2s;
|
transition: 0.2s;
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -165,11 +174,27 @@
|
|||||||
margin-top: 0.55em;
|
margin-top: 0.55em;
|
||||||
margin-right: 1em;
|
margin-right: 1em;
|
||||||
float: right;
|
float: right;
|
||||||
visibility: hidden;
|
|
||||||
height: 1.4em;
|
height: 1.4em;
|
||||||
width: 1.5em;
|
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 {
|
.wishlist_names::-webkit-scrollbar {
|
||||||
border-radius: 20px;
|
border-radius: 20px;
|
||||||
width: 0.2em;
|
width: 0.2em;
|
||||||
@ -177,9 +202,7 @@
|
|||||||
|
|
||||||
.wishlist_names::-webkit-scrollbar-thumb {
|
.wishlist_names::-webkit-scrollbar-thumb {
|
||||||
background-color: #0052CC;
|
background-color: #0052CC;
|
||||||
/* Колір позиції покажчика */
|
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
/* Закруглення країв позиції покажчика */
|
|
||||||
width: 0.2em;
|
width: 0.2em;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -211,17 +234,18 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.user_name {
|
.user_name {
|
||||||
padding-left: 0.4375em;
|
padding-left: 0.4em;
|
||||||
font-size: 1em;
|
font-size: 1.2em;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.info_user img {
|
.info_user img {
|
||||||
float: left;
|
float: left;
|
||||||
border-radius: 50%;
|
width: 2.4em;
|
||||||
width: 2.3em;
|
height: 2.4em;
|
||||||
height: 2.3em;
|
margin-left: 3.3em;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.button_close_menu {
|
.button_close_menu {
|
||||||
|
@ -7,6 +7,7 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Blazored.Modal" Version="7.1.0" />
|
||||||
<PackageReference Include="GraphQL.Client" Version="6.0.1" />
|
<PackageReference Include="GraphQL.Client" Version="6.0.1" />
|
||||||
<PackageReference Include="GraphQL.Client.Serializer.Newtonsoft" Version="6.0.1" />
|
<PackageReference Include="GraphQL.Client.Serializer.Newtonsoft" Version="6.0.1" />
|
||||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||||
|
@ -8,3 +8,6 @@
|
|||||||
@using Microsoft.JSInterop
|
@using Microsoft.JSInterop
|
||||||
@using ShoppingAssistantWebClient.Web
|
@using ShoppingAssistantWebClient.Web
|
||||||
@using ShoppingAssistantWebClient.Web.Shared
|
@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 {
|
.sidebar-menu {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
width: 20em;
|
width: 20em;
|
||||||
top: 1.25em;
|
top: 1.33em;
|
||||||
bottom: 0em;
|
bottom: 0em;
|
||||||
margin-right: 1.5em;
|
margin-right: 1.5em;
|
||||||
|
|
||||||
transition: 1s;
|
transition: 1s;
|
||||||
|
border-radius: 0.7em;
|
||||||
|
|
||||||
@media screen and (max-width: 900px) {
|
@media screen and (max-width: 900px) {
|
||||||
transform: translateX(-110%);
|
transform: translateX(-110%);
|
||||||
|
Binary file not shown.
Before Width: | Height: | 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 Width: | Height: | Size: 3.3 KiB |
Loading…
Reference in New Issue
Block a user