0
0
mirror of https://github.com/Shchoholiev/shopping-assistant-web-client.git synced 2025-06-29 18:21:09 +00:00

Merge pull request #14 from Shchoholiev/feature/SA-195-Fix-initial-message-freeze

bugs/SA195 fix initial message freeze
This commit is contained in:
Mykhailo Bilodid 2023-11-22 22:26:30 +02:00 committed by GitHub
commit 7da4c1ed7e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
46 changed files with 1566 additions and 571 deletions

12
.vscode/settings.json vendored
View File

@ -1,7 +1,7 @@
{ {
"explorer.fileNesting.enabled": true, "explorer.fileNesting.enabled": true,
"explorer.fileNesting.patterns": { "explorer.fileNesting.patterns": {
"*.cshtml": "${capture}.cshtml, ${capture}.cshtml.cs, ${capture}.cshtml.css", "*.cshtml": "${capture}.cshtml, ${capture}.cshtml.cs, ${capture}.cshtml.css",
"*.razor": "${capture}.razor, ${capture}.razor.css,${capture}.razor.cs" "*.razor": "${capture}.razor, ${capture}.razor.css,${capture}.razor.cs"
} }
} }

View File

@ -13,7 +13,7 @@ public class GlobalUserMiddleware
this._next = next; this._next = next;
} }
public async Task InvokeAsync(HttpContext httpContext, AuthenticationService authenticationService, ApiClient apiClient) public async Task InvokeAsync(HttpContext httpContext, AuthenticationService authenticationService, ApiClient apiClient)
{ {
if (httpContext.Request.Path != "/login") if (httpContext.Request.Path != "/login")
{ {

View File

@ -1,12 +1,12 @@
namespace ShoppingAssistantWebClient.Web.Models; namespace ShoppingAssistantWebClient.Web.Models;
public class Wishlist public class Wishlist
{ {
public required string Id {get; set;} public required string Id {get; set;}
public required string Name {get; set;} public required string Name {get; set;}
public required string Type {get; set;} public required string Type {get; set;}
public required string CreateById {get; set;} public required string CreateById {get; set;}
} }

View File

@ -0,0 +1,147 @@
@page "/cards/{wishlistName}/{chatId}"
@inject IJSRuntime JSRuntime
@inject NavigationManager navigationManager;
<head>
<link rel="stylesheet" href="css/Cards.css" />
</head>
<script>
function adjustButtonContainerPosition() {
var cardElements = document.querySelectorAll('.card, .back-card, .card-text');
var buttonContainer = document.querySelector('.buttons-container');
if (cardElements.length > 0 && buttonContainer) {
var totalHeight = Array.from(cardElements).reduce((max, card) => {
return Math.max(max, card.offsetHeight);
}, 0);
if (window.matchMedia('(max-width: 440px)').matches) {
buttonContainer.style.top = `calc(20% + ${totalHeight}px + 60px)`;
} else if (window.matchMedia('(max-width: 769px)').matches) {
buttonContainer.style.top = `calc(12% + ${totalHeight}px + 60px)`;
} else if (window.matchMedia('(max-width: 992px)').matches) {
buttonContainer.style.top = `calc(8% + ${totalHeight}px + 60px)`;
} else {
buttonContainer.style.top = `calc(3% + ${totalHeight}px + 60px)`;
}
}
}
window.addEventListener('load', adjustButtonContainerPosition);
window.addEventListener('resize', adjustButtonContainerPosition);
</script>
<div class="card-page">
<button class="back-button button-animation" @onclick="NavigateToMain"></button>
<div class="container">
<div class="head">
<p class="header-text">@wishlistName</p>
</div>
<div class="content" data-generalTop="3%" data-largeTop="8%" mediumTop="12%" smallTop="20%">
<div class="back-card"></div>
@if (Products != null && Products.Count != 0 && currentProduct != Products.Count && currentProduct >= 0) {
<div class="card">
<div class="slider-container">
<div class="slider">
@for(int i = 0; i < Products[currentProduct].ImagesUrls.Length && i < 3; i++) {
string image = Products[currentProduct].ImagesUrls[i];
if (currentImage == image) {
<img src="@image" class="slider-image" alt="product image"/>
}
}
<img class="next-image" src="/images/next-image.png" alt="next image" @onclick="(() => ChangeImage(currentImage))"/>
<img class="prev-image" src="/images/prev-image.png" alt="previous image" @onclick="(() => ChangeImage(currentImage))"/>
</div>
<div class="dots">
@for (var i = 0; i < Products[currentProduct].ImagesUrls.Length && i < 3; i++) {
var dotIndex = i;
<div class="dot @(i == currentIndex ? "active-dot" : "")" @onclick="(() => ChangeImageDot(dotIndex))"></div>
}
</div>
</div>
<div class="product-info">
<p class="name">@Products[currentProduct].Name</p>
<p class="description">@Products[currentProduct].Description</p>
<div class="rating-price-row">
<label class="rating">@Products[currentProduct].Rating</label>
@{
int whole = (int)Math.Floor(Products[currentProduct].Rating);
double fractal = Products[currentProduct].Rating - whole;
}
@for(int i = 0; i < 5; i++) {
if(i < whole) {
<img class="star" src="/images/star-cards.png" alt="star">
continue;
}
if(fractal != 0.0) {
<img class="star" src="/images/half-star.png" alt="star">
fractal -= fractal;
}
else {
<img class="star" src="/images/empty-star.png" alt="star">
}
}
<label class="price-label">@Products[currentProduct].Price</label>
</div>
</div>
</div>
<div class="buttons-container">
<div class="buttons-row">
<button class="cancel-button button-animation" @onclick="(() => LoadNextProduct())"></button>
<button class="return-button button-animation" @onclick="(() => LoadPreviousProduct())"></button>
<button class="like-button button-animation" @onclick="(() => LoadNextProduct())"></button>
</div>
</div>
if(!isProductsNull) {
isProductsNull = true;
StateHasChanged();
}
}
else {
<style>
.back-card {
display: none;
}
</style>
<div class="card-text">
<label class="bold-text">The cards ended</label>
<label class="more-text">Click <img class="plus-image" src="/images/load-more-small.png" alt="plus"/> to see more<br> or <img src="/images/return-small.png" alt="return"/> to exit</label>
</div>
<div class="buttons-container">
<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>
</div>
</div>
if(isProductsNull) {
isProductsNull = false;
StateHasChanged();
}
}
</div>
</div>
</div>
@code {
[Parameter] public string wishlistName { get; set; }
[Parameter] public string chatId {get; set;}
protected override async Task OnAfterRenderAsync(bool firstRender)
{
await JSRuntime.InvokeVoidAsync("adjustButtonContainerPosition");
}
private void NavigateToMain() {
navigationManager.NavigateTo($"/chat/{chatId}");
}
}

View File

@ -0,0 +1,90 @@
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;
public partial class Cards
{
[Inject]
private ApiClient _apiClient { get; set; }
[Inject]
SearchService _searchService { get; set; }
private int currentIndex = 0;
private int currentProduct = 0;
private string currentImage {get; set;}
private bool isProductsNull = false;
//private static string[] Images = {
// "/images/image2.png",
// "/images/image1.png",
// "/images/return-card.png",
// "/images/amazon.png",
// "/images/avatar.png"
//};
//public List<Product> Products = new()
//{
// new Product {Id = "0", Url = "some link", Name = "Belkin USB C to VGA + Charge Adapter - USB C to VGA Cable for MacBook",
// Description = "The USB C to VGA + Charge Adapter connects to your laptop or tablet via USB-C port, giving you both a VGA port for video display and a USB-C port for power", Rating = 3.8, Price = 120, ImagesUrls = Images, WasOpened = false, WishlistId = "0"},
// new Product {Id = "1", Url = "some link", Name = "Second product",
// Description = "Test description", Rating = 4.2, Price = 30, ImagesUrls = Images, WasOpened = false, WishlistId = "0"}
//};
public List<Product> Products {get; set;}
public List<String> productsNames {get; set;}
protected override async Task OnInitializedAsync()
{
if (Products != null) {
currentImage = Products[currentProduct].ImagesUrls[currentIndex];
}
else {
productsNames = _searchService.Products;
currentImage = "";
isProductsNull = true;
}
}
private void ChangeImage(string image)
{
currentIndex = Array.IndexOf(Products[currentProduct].ImagesUrls, image);
currentIndex = (currentIndex + 1) % Products[currentProduct].ImagesUrls.Length;
currentIndex = currentIndex >= 3 ? 0 : currentIndex;
currentImage = Products[currentProduct].ImagesUrls[currentIndex];
StateHasChanged();
}
private void ChangeImageDot(int index)
{
if (index >= 0 && index < Products[currentProduct].ImagesUrls.Length) {
currentIndex = index;
currentImage = Products[currentProduct].ImagesUrls[currentIndex];
StateHasChanged();
}
}
private async void LoadNextProduct()
{
currentProduct += 1;
StateHasChanged();
}
private async void LoadPreviousProduct() {
currentProduct -= 1;
StateHasChanged();
}
private void LoadMoreProducts() {
}
}

View File

@ -1,24 +1,62 @@
@page "/cart" @page "/cart/{currentWishlistId}"
<h1 style="text-align: center; margin-bottom: 50px">Cart</h1> @inject NavigationManager navigationManager;
<div class="container">
@foreach (var product in Products) { <head>
<div class="product-div"> <link rel="stylesheet" href="css/Cart.css" />
<img class="product-img" src="https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcSe7MHcuWUvuNJsttl3MEVxEayUGVNH4tA2Ha0K4szpgKvH7QYuiaBa_hCzUgW96I6LXqM&usqp=CAU"> </head>
<label class="product-description">@product.Description</label> <div class="cart">
<div class="button-row"> <div class="head">
<img class="star" src="~/assets/star.png"> <div id="button_open" class="open_menu">
<label class="rating">@product.Rating</label> <a class="button_open_menu">
@{ <span></span>
string price = @product.Price.ToString(); <span></span>
} <span></span>
<label class="price-label">@price</label> </a>
</div> </div>
<form class="button"> <p class="header-text">Cart</p>
<button class="button-amazon"> </div>
<img src="~/assets/amazon.png"> <div class="container">
</button> @if (!isError) {
</form> @if (Products != null) {
</div> @foreach (var product in Products) {
} <div class="product-div">
</div> <img class="product-img" src="https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcSe7MHcuWUvuNJsttl3MEVxEayUGVNH4tA2Ha0K4szpgKvH7QYuiaBa_hCzUgW96I6LXqM&usqp=CAU">
<label class="product-description">@product.Description</label>
<div class="button-row">
<img class="star" src="/images/star.png">
<label class="rating">@product.Rating</label>
<label class="price-label">@product.Price</label>
</div>
<button class="button-amazon">
<img src="/images/amazon.png">
</button>
</div>
}
}
else{
<p class="error">Cart is empty</p>
}
}
else
{
<p class="error">Something went wrong<br>We are having some trouble loading the cart</p>
}
</div>
</div>
<script>
document.getElementById('button_open').addEventListener('click', changetyle);
</script>
@code
{
[Parameter] public string currentWishlistId {get; set; }
[Parameter] public string chatId {get; set; }
private void NavigateToMainPage()
{
navigationManager.NavigateTo($"/chat/{chatId}");
}
}

View File

@ -1,19 +1,68 @@
using Microsoft.AspNetCore.Components; using GraphQL;
using ShoppingAssistantWebClient.Web.Models; using Newtonsoft.Json;
using Microsoft.AspNetCore.Components;
namespace ShoppingAssistantWebClient.Web.Pages; using ShoppingAssistantWebClient.Web.Models;
using ShoppingAssistantWebClient.Web.Network;
public partial class Cart : ComponentBase
{ namespace ShoppingAssistantWebClient.Web.Pages;
public List<Product> Products = new()
{ public partial class Cart : ComponentBase
new Product {Id = "0", Url = "some link", Name = "HDMI", Description = "super mega hdmi cabel", Rating = 3.8, Price = 300, ImagesUrls = new string[] {"link"}, WasOpened = false, WishlistId = "0"}, {
new Product {Id = "1", Url = "some link", Name = "HDMI", Description = "super mega hdmi cabel", Rating = 3.8, Price = 111, ImagesUrls = new string[] {"link"}, WasOpened = false, WishlistId = "1"},
new Product {Id = "2", Url = "some link", Name = "HDMI", Description = "super mega hdmi cabel", Rating = 3.8, Price = 50, ImagesUrls = new string[] {"link"}, WasOpened = false, WishlistId = "2"} [Inject]
}; private ApiClient _apiClient { get; set; }
protected override async Task OnInitializedAsync() public List<Product> Products { get; set; }
{
// Get data from Back-end public bool isError = false;
}
} protected override async Task OnInitializedAsync()
{
await GetData();
}
private async Task GetData()
{
try {
var request = new GraphQLRequest {
Query = @"query ProductsPageFromPersonalWishlist($wishlistId: String!, $pageNumber: Int!, $pageSize: Int!) {
productsPageFromPersonalWishlist(wishlistId: $wishlistId, pageNumber: $pageNumber, pageSize: $pageSize) {
items {
id
url
name
description
rating
price
imagesUrls
wasOpened
wishlistId
}
}
}",
Variables = new {
wishlistId = currentWishlistId,
pageNumber = 1,
pageSize = 10,
}
};
var response = await _apiClient.QueryAsync(request);
var responseData = response.Data;
if (response.Errors != null && response.Errors.Any()) {
isError = true;
}
else
{
var jsonCategoriesResponse = JsonConvert.SerializeObject(responseData.productsPageFromPersonalWishlist.items);
this.Products = JsonConvert.DeserializeObject<List<Product>>(jsonCategoriesResponse);
}
}
catch(Exception ex)
{
Console.WriteLine(ex);
isError = true;
}
}
}

View File

@ -1,21 +1,64 @@
.cart {
border-radius: 0.6em;
position: relative;
height: 100%;
width: 100%;
padding: 20px;
border: 1px solid #0052CC;
overflow: auto;
-ms-overflow-style: none;
scrollbar-width: none;
}
.cart::-webkit-scrollbar {
display: none;
}
.head {
display: flex;
justify-content: center;
align-items: center;
font-size: large;
}
.header-text {
text-align: center;
font-size: 12;
color: rgba(0, 82, 204, 0.8);
}
.back-button {
width: 15px;
height: 15px;
background-image: url("/images/back-button.png");
background-size: cover;
border: none;
background-color: transparent;
position: absolute;
left: 20px;
}
.container { .container {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
justify-content: center; justify-content: center;
max-width: 720px;
overflow: auto;
} }
.product-div { .product-div {
height: 310px; height: 190px;
width: 200px; width: 130px;
border-radius: 10px; border-radius: 10px;
box-shadow: 1px 1px 5px rgba(0, 0, 0, 0.1); box-shadow: 1px 1px 10px rgba(0, 0, 0, 0.3);
position: relative; position: relative;
padding: 0 10px 0 10px; padding: 0 10px 0 10px;
margin: 30px; margin: 15px 15px 15px 15px;
} }
.product-img { .product-img {
height: 130px; height: 70px;
width: 100%; width: 100%;
align-self: center; align-self: center;
display: block; display: block;
@ -27,6 +70,7 @@
-webkit-line-clamp: 2; -webkit-line-clamp: 2;
-webkit-box-orient: vertical; -webkit-box-orient: vertical;
overflow: hidden; overflow: hidden;
font-size: 12px;
} }
.button-amazon { .button-amazon {
@ -37,24 +81,26 @@
border: none; border: none;
border-radius: 10px; border-radius: 10px;
position: absolute; position: absolute;
width: 90%; width: 85%;
height: 40px; height: 27px;
bottom: 10px; bottom: 10px;
} }
.star { .star {
position: absolute; position: absolute;
bottom: 60px; bottom: 45px;
} }
.rating { .rating {
position: absolute; position: absolute;
bottom: 60px; bottom: 45px;
left: 35px; left: 26px;
height: 16px; height: 9px;
width: 9px;
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
font-size: 12px;
} }
.button-row { .button-row {
@ -65,10 +111,61 @@
position: absolute; position: absolute;
right: 10px; right: 10px;
font-weight: bold; font-weight: bold;
font-size: larger; font-size: 14px;
height: 16px; height: 16px;
bottom: 60px; bottom: 45px;
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
}
.error {
font-size: x-large;
color: darkgrey;
text-align: center;
line-height: 1.5;
}
.error br {
display: block;
content: " ";
margin-bottom: 1em;
}
.right-frame {
position: absolute;
right: 1.25em;
left: 23.25em;
top: 1.25em;
bottom: 1.25em;
transition: 1s;
}
.button_open_menu {
z-index: 2;
width: 1.43em;
height: 1.23em;
position: absolute;
top: 1.56em;
left: 1.56em;
cursor: pointer;
visibility: hidden;
}
.button_open_menu span {
width: 20px;
height: 1.5px;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background-color: #4E4E4E;
}
.button_open_menu span:nth-of-type(2) {
top: calc(50% - 5px);
}
.button_open_menu span:nth-of-type(3) {
top: calc(50% + 5px);
} }

View File

@ -85,7 +85,7 @@
<div class="chat_input"> <div class="chat_input">
<input @onkeydown="Enter" @oninput="InputChanged" class="input_messages" type="text" id="chatInput" <input @onkeydown="Enter" @oninput="InputChanged" class="input_messages" type="text" id="chatInput"
placeholder="Describe what you are looking for...." autocomplete="off"> placeholder="Describe what you are looking for...." autocomplete="off">
<img @onclick="AddNewMessage" class="button_sende" src="/images/send.svg" alt="Send message"> <img @onclick="() => AddNewMessage(inputValue)" class="button_sende" src="/images/send.svg" alt="Send message">
</div> </div>
</div> </div>
@ -101,10 +101,12 @@
} }
}; };
window.clearInput = () => { window.clearInput = () => {
// Отримати елемент вводу за його ідентифікатором і обнулити його значення
document.getElementById('chatInput').value = ''; document.getElementById('chatInput').value = '';
}; };
function myJavaScriptFunction(wishlistId) {
UpdateMenu(wishlistId);
}
document.getElementById('button_open').addEventListener('click', changetyle); document.getElementById('button_open').addEventListener('click', changetyle);
</script> </script>
@ -112,10 +114,13 @@
[Parameter] public string chatId { get; set; } [Parameter] public string chatId { get; set; }
public string inputValue = "";
protected override async Task OnParametersSetAsync() protected override async Task OnParametersSetAsync()
{ {
await LoadMessages(); await LoadMessages();
} }
private void InputChanged(ChangeEventArgs e) private void InputChanged(ChangeEventArgs e)
{ {
inputValue = e.Value.ToString(); inputValue = e.Value.ToString();
@ -126,9 +131,7 @@
{ {
if (e.Code == "Enter" || e.Code == "NumpadEnter") if (e.Code == "Enter" || e.Code == "NumpadEnter")
{ {
AddNewMessage(); AddNewMessage(inputValue);
} }
} }
@ -139,7 +142,12 @@
await JSRuntime.InvokeVoidAsync("scrollToBottom", chatMessageRef); await JSRuntime.InvokeVoidAsync("scrollToBottom", chatMessageRef);
} }
private async Task UpdateSideMenu(string wishlistId)
{
await JSRuntime.InvokeVoidAsync("myJavaScriptFunction", wishlistId);
}
private void ClickOption(string item) private void ClickOption(string item)
{ {
inputValue = item; inputValue = item;

View File

@ -6,6 +6,7 @@ using ShoppingAssistantWebClient.Web.Network;
using ShoppingAssistantWebClient.Web.Models.Input; using ShoppingAssistantWebClient.Web.Models.Input;
using ShoppingAssistantWebClient.Web.Models.Enums; using ShoppingAssistantWebClient.Web.Models.Enums;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using ShoppingAssistantWebClient.Web.Services;
using Microsoft.JSInterop; using Microsoft.JSInterop;
namespace ShoppingAssistantWebClient.Web.Pages; namespace ShoppingAssistantWebClient.Web.Pages;
@ -18,22 +19,63 @@ public partial class Chat : ComponentBase
private ApiClient _apiClient { get; set; } private ApiClient _apiClient { get; set; }
[Inject] [Inject]
private NavigationManager Navigation { get; set; } 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> Suggestion { get; set; } = new List<String>(); public List<String> Suggestion { get; set; } = new List<String>();
public Messages Message { get; set; } public Messages Message { get; set; }
public Messages MessageBot { get; set; }
private CancellationTokenSource cancelTokenSource; private CancellationTokenSource cancelTokenSource;
private bool isWaitingForResponse = false;
private MessageCreateDto messageCreateDto; private MessageCreateDto messageCreateDto;
private bool isWaitingForResponse = false;
public bool isLoading = true; public bool isLoading = true;
private string inputValue = "";
private string name = ""; private string name = "";
protected override async Task OnInitializedAsync() protected override async Task OnInitializedAsync()
{ {
await LoadMessages(); try{
var input = _searchServise.firstMassage;
if (input!=null){
await LoadMessages();
await AddNewMessage(input);
string wishlistId = chatId;
var request = new GraphQLRequest
{
Query = @"mutation GenerateNameForPersonalWishlist($wishlistId: String!) {
generateNameForPersonalWishlist(wishlistId: $wishlistId) {
id
name
}
}",
Variables = new
{
wishlistId
}
};
var response = await _apiClient.QueryAsync(request);
_searchServise.SetFirstMassage(null);
isLoading = false;
await UpdateSideMenu(wishlistId);
StateHasChanged();
}else{
await LoadMessages();
}
}catch(Exception ex){
Console.WriteLine($"Error OnInitializedAsync: {ex.Message}");
}
} }
@ -98,85 +140,86 @@ public partial class Chat : ComponentBase
Console.WriteLine($"Error : {ex.Message}"); Console.WriteLine($"Error : {ex.Message}");
} }
} }
private async Task AddNewMessage() private async Task AddNewMessage(string inputMessage)
{ {
if (!isWaitingForResponse && !string.IsNullOrWhiteSpace(inputMessage))
if (!isWaitingForResponse && !string.IsNullOrWhiteSpace(inputValue))
{ {
JSRuntime.InvokeVoidAsync("clearInput"); JSRuntime.InvokeVoidAsync("clearInput");
isWaitingForResponse = true; isWaitingForResponse = true;
try{ try{
messageCreateDto = new MessageCreateDto { Text = inputMessage };;
messageCreateDto = new MessageCreateDto { Text = inputValue };; Message = new Messages();
Message = new Messages(); Message.Text = inputMessage;
Message.Text = inputValue; Message.Role = "User";
Message.Role = "User"; Message.Id = "";
Message.Id = ""; Message.CreatedById = "";
Message.CreatedById = "";
inputValue = "";
Suggestion = new List<String>();
Messages.Add(Message);
StateHasChanged();
cancelTokenSource = new CancellationTokenSource(); Suggestion = new List<String>();
var cancellationToken = cancelTokenSource.Token; Products = new List<String>();
Messages.Add(Message);
StateHasChanged();
var serverSentEvent = _apiClient.GetServerSentEventStreamed($"ProductsSearch/search/{chatId}", messageCreateDto, cancellationToken); cancelTokenSource = new CancellationTokenSource();
bool first = true; var cancellationToken = cancelTokenSource.Token;
await foreach (var sseEvent in serverSentEvent.WithCancellation(cancellationToken)) var serverSentEvent = _apiClient.GetServerSentEventStreamed($"ProductsSearch/search/{chatId}", messageCreateDto, cancellationToken);
{ bool first = true;
Console.WriteLine($"Received SSE Event: {sseEvent.Event}, Data: {sseEvent.Data}");
MessageBot = new Messages();
MessageBot.Role = "bot";
MessageBot.Id = "";
MessageBot.CreatedById = "";
MessageBot.Text = "Waiting for response";
Messages.Add(MessageBot);
var lengt = Messages.Count();
StateHasChanged();
if(sseEvent.Event == SearchEventType.Message){ await foreach (var sseEvent in serverSentEvent.WithCancellation(cancellationToken))
{
Console.WriteLine($"Received SSE Event: {sseEvent.Event}, Data: {sseEvent.Data}");
string input = sseEvent.Data; string input = sseEvent.Data;
Regex regex = new Regex("\"(.*?)\""); Regex regex = new Regex("\"(.*?)\"");
Match match = regex.Match(input); Match match = regex.Match(input);
string result = match.Groups[1].Value; string result = match.Groups[1].Value;
if(sseEvent.Event == SearchEventType.Message){
Message = new Messages();
Message.Text = result;
Message.Role = "bot";
Message.Id = "";
Message.CreatedById = "";
if (first) if (first)
{ {
Messages.Add(Message); Messages[lengt-1].Text = result;
first = false; first = false;
} }
else else
{ {
var lengt = Messages.Count(); Messages[lengt-1].Text += result;
Messages[lengt-1].Text += Message.Text;
} }
StateHasChanged(); StateHasChanged();
}else if(sseEvent.Event == SearchEventType.Product){ } else if(sseEvent.Event == SearchEventType.Product){
var url = $"/chat/{chatId}/product"; Products.Add(result);
Navigation.NavigateTo(url);
}else if(sseEvent.Event == SearchEventType.Suggestion){ } else if(sseEvent.Event == SearchEventType.Suggestion){
Suggestion.Add(sseEvent.Data);
}
Suggestion.Add(result);
} }
isWaitingForResponse = false;
}catch(Exception ex){
Console.WriteLine($"Error : {ex.Message}");
}
} }
if(Products.Count!=0) {
string n = name;
_searchServise.SetProducts(Products);
var url = $"/cards/{name}/{chatId}";
Navigation.NavigateTo(url);
}
isWaitingForResponse = false;
} catch(Exception ex){
Console.WriteLine($"Error : {ex.Message}");
}
}
} }
} }

View File

@ -1,170 +1,170 @@
.right_frame { .right_frame {
position: relative; position: relative;
border: 0.09em solid; border: 0.09em solid;
border-color: #0052CC; border-color: #0052CC;
border-radius: 0.6em; border-radius: 0.6em;
height: 100%; height: 100%;
width: 100%; width: 100%;
} }
.button_open_menu { .button_open_menu {
z-index: 2; z-index: 2;
width: 1.43em; width: 1.43em;
height: 1.23em; height: 1.23em;
position: absolute; position: absolute;
top: 1.56em; top: 1.56em;
left: 1.56em; left: 1.56em;
cursor: pointer; cursor: pointer;
visibility: hidden; visibility: hidden;
} }
.button_open_menu span { .button_open_menu span {
width: 20px; width: 20px;
height: 1.5px; height: 1.5px;
position: absolute; position: absolute;
top: 50%; top: 50%;
left: 50%; left: 50%;
transform: translate(-50%, -50%); transform: translate(-50%, -50%);
background-color: #4E4E4E; background-color: #4E4E4E;
} }
.button_open_menu span:nth-of-type(2) { .button_open_menu span:nth-of-type(2) {
top: calc(50% - 5px); top: calc(50% - 5px);
} }
.button_open_menu span:nth-of-type(3) { .button_open_menu span:nth-of-type(3) {
top: calc(50% + 5px); top: calc(50% + 5px);
} }
.title_one_frame { .title_one_frame {
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: 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;
} }
.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;
} }
.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: 0em 0.6em;
flex: 1; flex: 1;
text-align: center; text-align: center;
cursor: pointer; cursor: pointer;
} }
.input_messages { .input_messages {
width: 100%; width: 100%;
height: 2.5em; height: 2.5em;
font-size: 1.0625em; font-size: 1.0625em;
background-color: #EAEAEA; background-color: #EAEAEA;
color: #4E4E4E; color: #4E4E4E;
border-radius: 0.6em; border-radius: 0.6em;
border: none; border: none;
padding: 0.625em 1.25em; padding: 0.625em 1.25em;
outline: none; outline: none;
} }
.button_sende { .button_sende {
float: right; float: right;
cursor: pointer; cursor: pointer;
line-height: 2.5em; line-height: 2.5em;
margin-right: 0.8em; margin-right: 0.8em;
width: 1.5em; width: 1.5em;
height: 1.4em; height: 1.4em;
} }
.new_chat { .new_chat {
padding-top: 0.5em; padding-top: 0.5em;
position: relative; position: relative;
height: 100%; height: 100%;
width: 100%; width: 100%;
} }
.chat_message { .chat_message {
position: relative; position: relative;
overflow-y: auto; overflow-y: auto;
height: calc(100% - 8em); height: calc(100% - 8em);
width: 100%; width: 100%;
} }
.chat_message::-webkit-scrollbar { .chat_message::-webkit-scrollbar {
border-radius: 20px; border-radius: 20px;
width: 0.2em; width: 0.2em;
} }
.chat_message::-webkit-scrollbar-thumb { .chat_message::-webkit-scrollbar-thumb {
background-color: #0052CC; background-color: #0052CC;
/* Колір позиції покажчика */ /* Колір позиції покажчика */
border-radius: 10px; border-radius: 10px;
/* Закруглення країв позиції покажчика */ /* Закруглення країв позиції покажчика */
width: 0.2em; width: 0.2em;
} }
.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;
} }
.chat_outgoing{ .chat_outgoing{
display: flex; display: flex;
} }
.chat_incoming{ .chat_incoming{
display: flex; display: flex;
} }
.chat_box .chat_outgoing p { .chat_box .chat_outgoing p {
margin-left: auto; margin-left: auto;
background-color: #009FFF; background-color: #009FFF;
border-radius: 10px; border-radius: 10px;
color: white; color: white;
padding: 10px; padding: 10px;
max-width: 60%; max-width: 60%;
} }
.chat_box .chat_incoming p { .chat_box .chat_incoming p {
background-color: #EAEAEA; background-color: #EAEAEA;
border-radius: 10px; border-radius: 10px;
color: black; color: black;
padding: 10px; padding: 10px;
width: 60%; width: 60%;
margin-bottom: 20px ; margin-bottom: 20px ;
margin-top: 20px ; margin-top: 20px ;
} }

View File

@ -1,42 +0,0 @@
@page
@model ShoppingAssistantWebClient.Web.Pages.ErrorModel
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<title>Error</title>
<link href="~/css/bootstrap/bootstrap.min.css" rel="stylesheet" />
<link href="~/css/site.css" rel="stylesheet" asp-append-version="true" />
</head>
<body>
<div class="main">
<div class="content px-4">
<h1 class="text-danger">Error.</h1>
<h2 class="text-danger">An error occurred while processing your request.</h2>
@if (Model.ShowRequestId)
{
<p>
<strong>Request ID:</strong> <code>@Model.RequestId</code>
</p>
}
<h3>Development Mode</h3>
<p>
Swapping to the <strong>Development</strong> environment displays detailed information about the error that occurred.
</p>
<p>
<strong>The Development environment shouldn't be enabled for deployed applications.</strong>
It can result in displaying sensitive information from exceptions to end users.
For local debugging, enable the <strong>Development</strong> environment by setting the <strong>ASPNETCORE_ENVIRONMENT</strong> environment variable to <strong>Development</strong>
and restarting the app.
</p>
</div>
</div>
</body>
</html>

View File

@ -1,26 +0,0 @@
using System.Diagnostics;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace ShoppingAssistantWebClient.Web.Pages;
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
[IgnoreAntiforgeryToken]
public class ErrorModel : PageModel
{
public string? RequestId { get; set; }
public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);
private readonly ILogger<ErrorModel> _logger;
public ErrorModel(ILogger<ErrorModel> logger)
{
_logger = logger;
}
public void OnGet()
{
RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier;
}
}

View File

@ -11,8 +11,6 @@
</a> </a>
</div> </div>
@if(isLoading == false){
<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>
@ -58,16 +56,6 @@
</div> </div>
</div> </div>
}else{
<div class="new_chat">
<img class="loading" src="/images/loading.svg" alt="Loading chat">
</div>
}
</div> </div>

View File

@ -7,6 +7,7 @@ using Newtonsoft.Json;
using ShoppingAssistantWebClient.Web.Network; using ShoppingAssistantWebClient.Web.Network;
using System; using System;
using Microsoft.JSInterop; using Microsoft.JSInterop;
using ShoppingAssistantWebClient.Web.Services;
namespace ShoppingAssistantWebClient.Web.Pages namespace ShoppingAssistantWebClient.Web.Pages
{ {
@ -20,13 +21,10 @@ namespace ShoppingAssistantWebClient.Web.Pages
private NavigationManager Navigation { get; set; } private NavigationManager Navigation { get; set; }
[Inject] [Inject]
protected IJSRuntime JSRuntime { get; set; } protected IJSRuntime JSRuntime { get; set; }
[Inject]
private SearchService _searchServise { get; set; }
private MessageCreateDto messageCreateDto; private MessageCreateDto messageCreateDto;
private CancellationTokenSource cancelTokenSource;
private string inputValue = ""; private string inputValue = "";
public bool isLoading;
private async Task CreateNewChat() { private async Task CreateNewChat() {
@ -38,7 +36,6 @@ namespace ShoppingAssistantWebClient.Web.Pages
return; return;
} }
isLoading = true;
StateHasChanged(); StateHasChanged();
messageCreateDto = new MessageCreateDto { Text = inputValue }; messageCreateDto = new MessageCreateDto { Text = inputValue };
var type = selectedChoice; var type = selectedChoice;
@ -62,58 +59,22 @@ namespace ShoppingAssistantWebClient.Web.Pages
var response = await _apiClient.QueryAsync(request); var response = await _apiClient.QueryAsync(request);
var responseData = response.Data; var responseData = response.Data;
var chatId = responseData?.startPersonalWishlist?.id; var chatId = responseData?.startPersonalWishlist?.id;
string wishlistId1 = chatId;
var text = inputValue;
cancelTokenSource = new CancellationTokenSource();
var cancellationToken = cancelTokenSource.Token;
var serverSentEvent = _apiClient.GetServerSentEventStreamed($"ProductsSearch/search/{chatId}", messageCreateDto, cancellationToken);
await foreach (var sseEvent in serverSentEvent.WithCancellation(cancellationToken))
{
// Handle each ServerSentEvent as needed
Console.WriteLine($"Received SSE Event: {sseEvent.Event}, Data: {sseEvent.Data}");
}
string wishlistId = chatId; string wishlistId = chatId;
request = new GraphQLRequest
{
Query = @"mutation GenerateNameForPersonalWishlist($wishlistId: String!) {
generateNameForPersonalWishlist(wishlistId: $wishlistId) {
id
name
}
}",
Variables = new
{
wishlistId
}
};
response = await _apiClient.QueryAsync(request); _searchServise.SetFirstMassage(inputValue);
await UpdateSideMenu(wishlistId);
isLoading = false;
StateHasChanged();
await UpdateSideMenu(wishlistId1);
var url = $"/chat/{chatId}"; var url = $"/chat/{chatId}";
Navigation.NavigateTo(url); Navigation.NavigateTo(url);
} }
catch (Exception ex) catch (Exception ex)
{ {
// Handle exceptions appropriately // Handle exceptions appropriately
Console.WriteLine($"Error in CreateNewChat: {ex.Message}"); Console.WriteLine($"Error in CreateNewChat: {ex.Message}");
} }
finally
{
isLoading = false;
cancelTokenSource?.Dispose();
}
} }
} }

View File

@ -1,42 +0,0 @@
@page "/wishlists"
<h1 style="text-align: center; margin-bottom: 50px">My Wishlist</h1>
<div class="table-container">
@if(WishlistList != null)
{
<table class="styled-table">
<thead>
<tr>
<th> </th>
<th style="text-align: left;">Chat name</th>
<th>Type</th>
<th>CreatedById</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach (var item in WishlistList)
{
<tr>
<form method="post" asp-page-handler="Delete" asp-route-id="@item.Id">
<td><input type="image" src="~/assets/x-button.png"></td>
</form>
<td style="text-align: left">@item.Name</td>
<td>@item.Type</td>
<td>@item.CreateById</td>
<form method="post" asp-page="Wishlist">
<td><input type="image" src="~/assets/shopping-cart.png" asp-page-handler="MoveToChat"></td>
</form>
</tr>
}
</tbody>
</table>
}
else
{
<h3>You don't have a wishlist</h3>
}
</div>

View File

@ -1,14 +0,0 @@
using Microsoft.AspNetCore.Components;
using ShoppingAssistantWebClient.Web.Models;
namespace ShoppingAssistantWebClient.Web.Pages;
public partial class Wishlists : ComponentBase
{
public List<Wishlist> WishlistList = new()
{
new Wishlist {Id = "0", Name = "Chat1", Type="product", CreateById="0"},
new Wishlist {Id = "1", Name = "Chat2", Type="gift", CreateById="1"},
new Wishlist {Id = "2", Name = "Chat3", Type="product", CreateById="2"}
};
}

View File

@ -1,28 +0,0 @@
.table-container {
width: 95%;
height: 95%;
display: flex;
justify-content: center;
align-items: center;
margin: auto;
}
.styled-table {
border-collapse: collapse;
width: 100%;
max-width: 100%;
}
.styled-table th {
border-bottom: 1px solid #ddd;
text-align: center;
font-size: larger;
background-color: rgba(0, 159, 255, 0.1);
}
.styled-table td {
border-bottom: 5px solid #FFF;
border-top: 5px solid #FFF;
text-align: center;
font-size: large;
padding: 20px;
background-color: rgba(248, 248, 255, 1);
}

View File

@ -1,15 +1,18 @@
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;
var builder = WebApplication.CreateBuilder(args); var builder = WebApplication.CreateBuilder(args);
// Add services to the container. // Add services to the container.
builder.Services.AddRazorPages(); builder.Services.AddRazorPages();
builder.Services.AddServerSideBlazor(); builder.Services.AddServerSideBlazor();
builder.Services.AddServerSideBlazor().AddCircuitOptions(options => { options.DetailedErrors = true; });
builder.Services.AddSingleton<WeatherForecastService>(); builder.Services.AddSingleton<WeatherForecastService>();
builder.Services.AddApiClient(builder.Configuration); builder.Services.AddApiClient(builder.Configuration);
builder.Services.AddSingleton<SearchService>();
var app = builder.Build(); var app = builder.Build();

View File

@ -0,0 +1,17 @@
namespace ShoppingAssistantWebClient.Web.Services;
public class SearchService
{
public List<String> Products { get; set; }
public string firstMassage { get; set; }
public void SetProducts(List<String> products) {
Products = products;
}
public void SetFirstMassage(string massage) {
firstMassage = massage;
}
}

View File

@ -1,6 +1,9 @@
@inherits LayoutComponentBase @inherits LayoutComponentBase
@using ShoppingAssistantWebClient.Web.Pages
<PageTitle>CARTAID</PageTitle> <PageTitle>CARTAID</PageTitle>
<head>
<link rel="stylesheet" href="css/MainLayout.css" />
</head>
<div class="page"> <div class="page">
<div class="sidebar-menu"> <div class="sidebar-menu">
@ -9,4 +12,4 @@
<div class="right-frame"> <div class="right-frame">
@Body @Body
</div> </div>
</div> </div>

View File

@ -40,7 +40,7 @@
<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"> <img class="button_delete_chat" @onclick="() => DeleteWishlist(item.Id)" src="/images/icon_delete.svg" alt="Delete wishlist">
<img class="button_open_card" @onclick="() => RedirectToCard(item.Id)" src="/images/icon_open_card.svg" alt="Card open"> <img class="button_open_card" @onclick="() => RedirectToCart(item.Id)" src="/images/icon_open_card.svg" alt="Card open">
</section> </section>
} }
} }
@ -149,8 +149,8 @@
var url = $"/"; var url = $"/";
Navigation.NavigateTo(url); Navigation.NavigateTo(url);
} }
private void RedirectToCard(string itemId) { private void RedirectToCart(string itemId) {
var url = $"/chat/{itemId}/cart"; var url = $"/cart/{itemId}";
Navigation.NavigateTo(url); Navigation.NavigateTo(url);
} }
private async void DeleteWishlist(string itemId) { private async void DeleteWishlist(string itemId) {
@ -181,4 +181,4 @@
} }

View File

@ -1,4 +1,4 @@
using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components;
using ShoppingAssistantWebClient.Web.Models; using ShoppingAssistantWebClient.Web.Models;
using GraphQL; using GraphQL;
using Newtonsoft.Json; using Newtonsoft.Json;
@ -25,6 +25,7 @@ namespace ShoppingAssistantWebClient.Web.Shared
await LoadMenus(currentPage, pageSize); await LoadMenus(currentPage, pageSize);
} }
public async Task LoadMenus(int pageNumber, int pageSize ) public async Task LoadMenus(int pageNumber, int pageSize )
{ {
try{ try{

View File

@ -0,0 +1,701 @@
.page {
position: relative;
width: 100%;
height: 100vh;
border: 1.5% solid;
padding: 1.25em;
}
.card-page {
border-radius: 10px;
border: 1px solid #0052CC;
position: fixed;
left: 2%;
right: 2%;
top: 2%;
bottom: 2%;
padding: 20px;
position: fixed;
}
.container {
display: flex;
flex-direction: column;
height: 100%;
overflow: auto;
position: relative;
}
.content {
display: flex;
flex-direction: column;
flex-grow: 1;
justify-content: center;
align-items: center;
height: 100%;
position: relative;
}
.head {
display: flex;
justify-content: center;
align-items: center;
}
.header-text {
text-align: center;
font-size: 24px;
color: rgba(0, 82, 204, 0.8);
}
.back-button {
width: 20px;
height: 20px;
background-image: url("/images/back-button.png");
background-size: cover;
border: none;
background-color: transparent;
position: absolute;
left: 2%;
top: 30px;
}
.back-card,
.card,
.card-text {
max-width: 280px;
width: 30%;
aspect-ratio: 1 / 1.6;
border-radius: 15px;
position: absolute;
top: 3%;
}
@media only screen and (max-width: 992px) {
.back-card,
.card,
.card-text {
max-width: 250px;
width: 40%;
aspect-ratio: 1 / 1.6;
border-radius: 15px;
position: absolute;
top: 8% !important;
}
.card,
.card-text {
box-shadow: 0 0 10px rgba(1, 101, 255, 1);
padding: 15px 10px 30px 10px !important;
z-index: 2;
}
.slider-container {
position: relative;
height: 200px;
width: 100%;
margin: 0 auto;
overflow: hidden;
border-radius: 15px;
}
.slider-image {
width: 100%;
height: 190px !important;
object-fit: cover;
}
.buttons-container {
max-width: 270px;
width: 40% !important;
position: absolute;
z-index: 3;
}
.cancel-button {
width: 25px !important;
height: 25px !important;
background-image: url("/images/cancel-button.png");
background-size: cover;
border: none;
background-color: transparent;
}
.return-button {
width: 25px !important;
height: 25px !important;
background-image: url("/images/return-card.png");
background-size: cover;
border: none;
background-color: transparent;
}
.like-button {
width: 25px !important;
height: 25px !important;
background-image: url("/images/like-button.png");
background-size: cover;
border: none;
background-color: transparent;
}
.exit-button {
width: 25px !important;
height: 25px !important;
background-image: url("/images/exit.png");
background-size: cover;
border: none;
background-color: transparent;
}
.more-button {
width: 25px !important;
height: 25px !important;
background-image: url("/images/load-more.png");
background-size: cover;
border: none;
background-color: transparent;
}
.name {
max-height: 60px;
font-weight: bold;
font-size: 0.85em !important;
-webkit-line-clamp: 2;
}
.description {
max-height: 70px;
font-size: 0.75em !important;
-webkit-line-clamp: 3;
position: absolute;
top: 280px !important;
left: 10px !important;
right: 10px !important;
}
.name {
max-height: 60px;
font-weight: bold;
font-size: 0.85em !important;
-webkit-line-clamp: 2;
}
.description {
max-height: 70px;
font-size: 0.75em !important;
-webkit-line-clamp: 3;
position: absolute;
top: 280px !important;
left: 8px !important;
right: 8px !important;
}
.next-image,
.prev-image {
position: absolute;
top: 50%;
transform: translateY(-50%);
width: 15px !important;
height: 15px !important;
z-index: 100;
opacity: 0;
transition: opacity 0.5s ease;
}
}
@media only screen and (max-width: 769px) {
.back-card,
.card,
.card-text {
max-width: 230px;
min-width: 210px !important;
width: 50vw !important;
aspect-ratio: 1 / 1.6;
border-radius: 15px;
position: absolute;
top: 12% !important;
}
.card,
.card-text {
box-shadow: 0 0 10px rgba(1, 101, 255, 1);
padding: 12px 8px 24px 8px !important;
z-index: 2;
}
.slider-container {
position: relative;
width: 100%;
height: 170px !important;
margin: 0 auto;
overflow: hidden;
border-radius: 15px;
}
.slider-image {
height: 150px !important;
width: 100%;
object-fit: cover;
}
.dot {
width: 8px !important;
height: 8px !important;
background-color: rgba(234, 234, 234, 1);
border-radius: 50%;
margin: 0 8px;
cursor: pointer;
}
.buttons-container {
max-width: 250px;
min-width: 230px;
width: 50vw !important;
position: absolute;
z-index: 3;
}
.cancel-button {
width: 25px !important;
height: 25px !important;
background-image: url("/images/cancel-button.png");
background-size: cover;
border: none;
background-color: transparent;
}
.return-button {
width: 25px !important;
height: 25px !important;
background-image: url("/images/return-card.png");
background-size: cover;
border: none;
background-color: transparent;
}
.like-button {
width: 25px !important;
height: 25px !important;
background-image: url("/images/like-button.png");
background-size: cover;
border: none;
background-color: transparent;
}
.exit-button {
width: 25px !important;
height: 25px !important;
background-image: url("/images/exit.png");
background-size: cover;
border: none;
background-color: transparent;
}
.more-button {
width: 25px !important;
height: 25px !important;
background-image: url("/images/load-more.png");
background-size: cover;
border: none;
background-color: transparent;
}
.name {
max-height: 60px;
font-weight: bold;
font-size: 0.85em !important;
-webkit-line-clamp: 2;
}
.description {
max-height: 70px;
font-size: 0.75em !important;
-webkit-line-clamp: 3;
position: absolute;
top: 240px !important;
left: 8px !important;
right: 8px !important;
}
.next-image,
.prev-image {
position: absolute;
top: 50%;
transform: translateY(-50%);
width: 12px !important;
height: 12px !important;
z-index: 100;
opacity: 0;
transition: opacity 0.5s ease;
}
}
@media only screen and (max-width: 440px) {
.back-card,
.card,
.card-text {
min-width: 200px !important;
width: 70vw;
aspect-ratio: 1 / 1.6;
border-radius: 15px;
position: absolute;
top: 20% !important;
}
.card,
.card-text {
box-shadow: 0 0 10px rgba(1, 101, 255, 1);
padding: 10px 5px 18px 5px !important;
z-index: 2;
}
.slider-container {
position: relative;
width: 100%;
height: 160px !important;
margin: 0 auto;
overflow: hidden;
border-radius: 15px;
}
.slider-image {
height: 140px !important;
width: 100%;
object-fit: cover;
}
.dot {
width: 6px !important;
height: 6px !important;
background-color: rgba(234, 234, 234, 1);
border-radius: 50%;
margin: 0 8px;
cursor: pointer;
}
.buttons-container {
min-width: 210px;
width: 70vw;
position: absolute;
z-index: 3;
}
.cancel-button {
width: 20px !important;
height: 20px !important;
background-image: url("/images/cancel-button.png");
background-size: cover;
border: none;
background-color: transparent;
}
.return-button {
width: 20px !important;
height: 20px !important;
background-image: url("/images/return-card.png");
background-size: cover;
border: none;
background-color: transparent;
}
.like-button {
width: 20px !important;
height: 20px !important;
background-image: url("/images/like-button.png");
background-size: cover;
border: none;
background-color: transparent;
}
.exit-button {
width: 20px !important;
height: 20px !important;
background-image: url("/images/exit.png");
background-size: cover;
border: none;
background-color: transparent;
}
.more-button {
width: 20px !important;
height: 20px !important;
background-image: url("/images/load-more.png");
background-size: cover;
border: none;
background-color: transparent;
}
.name {
max-height: 60px;
font-weight: bold;
font-size: 0.8em !important;
-webkit-line-clamp: 2;
}
.description {
max-height: 70px;
font-size: 0.75em !important;
-webkit-line-clamp: 3;
position: absolute;
top: 220px !important;
left: 5px !important;
right: 5px !important;
}
.bold-text {
font-size: 1.2em !important;
font-weight: bold;
}
.add-text {
font-size: 0.85em !important;
}
.exit-text {
font-size: 0.85em !important;
}
.next-image,
.prev-image {
position: absolute;
top: 50%;
transform: translateY(-50%);
width: 10px !important;
height: 10px !important;
z-index: 100;
opacity: 0;
transition: opacity 0.5s ease;
}
}
.back-card {
transform: rotate(-5deg);
box-shadow: 0 0 10px rgba(0, 82, 204, 1);
z-index: 1;
}
.card,
.card-text {
box-shadow: 0 0 10px rgba(1, 101, 255, 1);
padding: 30px 20px 40px 20px;
z-index: 2;
}
.slider-container {
position: relative;
width: 100%;
height: 210px;
margin: 0 auto;
overflow: hidden;
border-radius: 15px;
}
.slider {
display: flex;
transition: transform 0.3s ease-in-out;
}
.next-image,
.prev-image {
position: absolute;
top: 50%;
transform: translateY(-50%);
width: 20px;
height: 20px;
z-index: 100;
opacity: 0;
transition: opacity 0.5s ease;
}
.next-image {
right: 0;
}
.slider:hover .next-image,
.slider:hover .prev-image {
opacity: 1;
}
.slider-image {
height: 190px;
width: 100%;
object-fit: cover;
}
.dots {
display: flex;
justify-content: center;
margin-top: 2%;
}
.dot {
width: 10px;
height: 10px;
background-color: rgba(234, 234, 234, 1);
border-radius: 50%;
margin: 0 8px;
cursor: pointer;
}
.active-dot {
background-color: rgba(50, 50, 50, 1);
}
.product-info {
flex: 1;
display: flex;
flex-direction: column;
}
.name,
.description {
overflow: hidden;
display: -webkit-box;
-webkit-box-orient: vertical;
text-overflow: ellipsis;
}
.name {
max-height: 60px;
font-weight: bold;
font-size: 15px;
-webkit-line-clamp: 2;
}
.description {
max-height: 70px;
font-size: 13px;
-webkit-line-clamp: 3;
position: absolute;
top: 300px;
right: 20px;
left: 20px;
}
.star {
margin-right: 5px;
}
.rating {
margin-right: 15px;
}
.rating-price-row {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: auto;
}
.price-label {
margin-left: auto;
font-weight: bolder;
}
.buttons-container {
width: 300px;
position: absolute;
z-index: 3;
}
.buttons-row {
display: flex;
justify-content: space-between;
position: absolute;
bottom: 15px;
left: 20px;
right: 20px;
}
.button-animation {
transition: transform 0.3s ease;
}
.button-animation:active {
transform: scale(0.55);
}
.cancel-button {
width: 30px;
height: 30px;
background-image: url("/images/cancel-button.png");
background-size: cover;
border: none;
background-color: transparent;
}
.return-button {
width: 30px;
height: 30px;
background-image: url("/images/return-card.png");
background-size: cover;
border: none;
background-color: transparent;
}
.like-button {
width: 30px;
height: 30px;
background-image: url("/images/like-button.png");
background-size: cover;
border: none;
background-color: transparent;
}
.exit-button {
width: 30px;
height: 30px;
background-image: url("/images/exit.png");
background-size: cover;
border: none;
background-color: transparent;
}
.more-button {
width: 30px;
height: 30px;
background-image: url("/images/load-more.png");
background-size: cover;
border: none;
background-color: transparent;
}
.bold-text {
font-size: 24px;
font-weight: bold;
}
.add-text {
font-size: 12px;
}
.exit-text {
font-size: 12px;
}
.card-text {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
text-align: left;
}
.sidebar-menu {
display: none;
}
.right-frame {
position: absolute;
right: 1.25em;
left: 1.25em;
top: 1.25em;
bottom: 1.25em;
transition: 1s;
}
.plus-image {
vertical-align: baseline;
}

View File

@ -1,24 +1,25 @@
.page { .page {
position: relative; position: relative;
width: 100%; width: 100%;
height: 100vh; height: 100vh;
padding: 1.25em; border: 1.5% solid;
} padding: 1.25em;
}
.sidebar-menu {
position: absolute; .sidebar-menu {
width: 20em; position: absolute;
top: 1.25em; width: 20em;
bottom: 1.25em; top: 1.25em;
margin-right: 1.5em; bottom: 1.25em;
transition: 1s; margin-right: 1.5em;
} transition: 1s;
}
.right-frame {
position: absolute; .right-frame {
right: 1.25em; position: absolute;
left: 23.25em; right: 1.25em;
top: 1.25em; left: 23.25em;
bottom: 1.25em; top: 1.25em;
transition: 1s; bottom: 1.25em;
transition: 1s;
} }

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 546 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 300 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 367 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 406 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 577 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 419 B

View File

@ -1,3 +1,3 @@
<svg width="17" height="17" viewBox="0 0 17 17" fill="none" xmlns="http://www.w3.org/2000/svg"> <svg width="17" height="17" viewBox="0 0 17 17" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M14.1667 3.54169C14.3545 3.54169 14.5347 3.61631 14.6675 3.74915C14.8004 3.88199 14.875 4.06216 14.875 4.25002C14.875 4.43788 14.8004 4.61805 14.6675 4.75089C14.5347 4.88373 14.3545 4.95835 14.1667 4.95835H13.4583L13.4562 5.00865L12.7953 14.2673C12.7699 14.6247 12.61 14.9592 12.3477 15.2034C12.0855 15.4476 11.7405 15.5834 11.3822 15.5834H5.61708C5.25877 15.5834 4.91376 15.4476 4.65154 15.2034C4.38933 14.9592 4.2294 14.6247 4.20396 14.2673L3.54308 5.00935C3.54201 4.99238 3.54154 4.97536 3.54167 4.95835H2.83333C2.64547 4.95835 2.4653 4.88373 2.33247 4.75089C2.19963 4.61805 2.125 4.43788 2.125 4.25002C2.125 4.06216 2.19963 3.88199 2.33247 3.74915C2.4653 3.61631 2.64547 3.54169 2.83333 3.54169H14.1667ZM12.0395 4.95835H4.96046L5.61779 14.1667H11.3822L12.0395 4.95835ZM9.91667 1.41669C10.1045 1.41669 10.2847 1.49131 10.4175 1.62415C10.5504 1.75699 10.625 1.93716 10.625 2.12502C10.625 2.31288 10.5504 2.49305 10.4175 2.62589C10.2847 2.75873 10.1045 2.83335 9.91667 2.83335H7.08333C6.89547 2.83335 6.7153 2.75873 6.58247 2.62589C6.44963 2.49305 6.375 2.31288 6.375 2.12502C6.375 1.93716 6.44963 1.75699 6.58247 1.62415C6.7153 1.49131 6.89547 1.41669 7.08333 1.41669H9.91667Z" fill="#202124"/> <path d="M14.1667 3.54169C14.3545 3.54169 14.5347 3.61631 14.6675 3.74915C14.8004 3.88199 14.875 4.06216 14.875 4.25002C14.875 4.43788 14.8004 4.61805 14.6675 4.75089C14.5347 4.88373 14.3545 4.95835 14.1667 4.95835H13.4583L13.4562 5.00865L12.7953 14.2673C12.7699 14.6247 12.61 14.9592 12.3477 15.2034C12.0855 15.4476 11.7405 15.5834 11.3822 15.5834H5.61708C5.25877 15.5834 4.91376 15.4476 4.65154 15.2034C4.38933 14.9592 4.2294 14.6247 4.20396 14.2673L3.54308 5.00935C3.54201 4.99238 3.54154 4.97536 3.54167 4.95835H2.83333C2.64547 4.95835 2.4653 4.88373 2.33247 4.75089C2.19963 4.61805 2.125 4.43788 2.125 4.25002C2.125 4.06216 2.19963 3.88199 2.33247 3.74915C2.4653 3.61631 2.64547 3.54169 2.83333 3.54169H14.1667ZM12.0395 4.95835H4.96046L5.61779 14.1667H11.3822L12.0395 4.95835ZM9.91667 1.41669C10.1045 1.41669 10.2847 1.49131 10.4175 1.62415C10.5504 1.75699 10.625 1.93716 10.625 2.12502C10.625 2.31288 10.5504 2.49305 10.4175 2.62589C10.2847 2.75873 10.1045 2.83335 9.91667 2.83335H7.08333C6.89547 2.83335 6.7153 2.75873 6.58247 2.62589C6.44963 2.49305 6.375 2.31288 6.375 2.12502C6.375 1.93716 6.44963 1.75699 6.58247 1.62415C6.7153 1.49131 6.89547 1.41669 7.08333 1.41669H9.91667Z" fill="#202124"/>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -1,4 +1,4 @@
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg"> <svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M2.96249 11.8808C2.36416 9.08668 2.06416 7.68918 2.81499 6.76168C3.56499 5.83334 4.99416 5.83334 7.85166 5.83334H12.1483C15.0067 5.83334 16.4342 5.83334 17.185 6.76168C17.935 7.69001 17.6358 9.08668 17.0375 11.8808L16.68 13.5475C16.2742 15.4417 16.0717 16.3883 15.3842 16.9442C14.6967 17.5 13.7283 17.5 11.7917 17.5H8.20833C6.27166 17.5 5.30333 17.5 4.61666 16.9442C3.92833 16.3883 3.72499 15.4417 3.31999 13.5475L2.96249 11.8808Z" stroke="black" stroke-width="1.5"/> <path d="M2.96249 11.8808C2.36416 9.08668 2.06416 7.68918 2.81499 6.76168C3.56499 5.83334 4.99416 5.83334 7.85166 5.83334H12.1483C15.0067 5.83334 16.4342 5.83334 17.185 6.76168C17.935 7.69001 17.6358 9.08668 17.0375 11.8808L16.68 13.5475C16.2742 15.4417 16.0717 16.3883 15.3842 16.9442C14.6967 17.5 13.7283 17.5 11.7917 17.5H8.20833C6.27166 17.5 5.30333 17.5 4.61666 16.9442C3.92833 16.3883 3.72499 15.4417 3.31999 13.5475L2.96249 11.8808Z" stroke="black" stroke-width="1.5"/>
<path d="M2.5 9.16667H17.5M8.33333 11.6667H11.6667M15 7.5L12.5 2.5M5 7.5L7.5 2.5" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/> <path d="M2.5 9.16667H17.5M8.33333 11.6667H11.6667M15 7.5L12.5 2.5M5 7.5L7.5 2.5" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 745 B

After

Width:  |  Height:  |  Size: 749 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 559 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 242 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 387 B

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 9.6 KiB

After

Width:  |  Height:  |  Size: 9.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 264 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 242 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 424 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 440 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 348 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 249 B