From 977d822522285ef93ced86f4bdd1f5486c11b1c9 Mon Sep 17 00:00:00 2001 From: carlos-kryha <97608823+carlos-kryha@users.noreply.github.com> Date: Fri, 10 Nov 2023 09:28:24 +0100 Subject: [PATCH] Fix frontend price filter (#87) QA surfaced several problems with the price filter components: - [x] Slider input is not usable with ranges > ~1000, since the steps are too big to find specific numbers > Fix: removed slider input component, added todo with potential solutions, and adjusted wrapper ui - [x] Min/max input components missing `value` property > Fix: added value property - [x] Price metrics affected by price filters > Fix: calculate price metrics using full market instead of filtered - [x] Min/max input used uIST, while all prices show IST > Fix: refactor filters to use IST - [x] Clear all does not clear price filter > Fix: refactor reset mechanism to work with clear all feature --------- Co-authored-by: Pandelis Symeonidis --- .../asset-character-filters.tsx | 6 ++- .../asset-item-filters/asset-item-filters.tsx | 2 +- .../src/components/input-fields/styles.ts | 2 +- .../price-range-slider/price-range-slider.tsx | 45 +++++++++---------- frontend/src/constants.ts | 2 +- frontend/src/context/filter-context.tsx | 5 ++- frontend/src/pages/shop/characters-shop.tsx | 8 ++-- frontend/src/pages/shop/items-shop.tsx | 6 ++- frontend/src/util/filter.ts | 20 ++++++--- 9 files changed, 56 insertions(+), 40 deletions(-) diff --git a/frontend/src/components/asset-character-filters/asset-character-filters.tsx b/frontend/src/components/asset-character-filters/asset-character-filters.tsx index d941dfe47..fe52a5a95 100644 --- a/frontend/src/components/asset-character-filters/asset-character-filters.tsx +++ b/frontend/src/components/asset-character-filters/asset-character-filters.tsx @@ -18,10 +18,14 @@ export const AssetCharacterFilters: FC = ({ section }) => { const { title, origin, sort, reset, setOrigin, setTitle, setCharacterPrice, setSort, setColors, onReset } = useFilters(); const [filterId, setFilterId] = useState(""); const [prices, fetched] = useGetCharacterMarketPrices(); + const openFilter = (id: string) => { setFilterId(id !== filterId ? id : ""); }; + const handlePriceFilter = (range: {min: number, max: number}) => { + setCharacterPrice(range); + } return ( <> @@ -45,7 +49,7 @@ export const AssetCharacterFilters: FC = ({ section }) => { {section === SECTION.SHOP && ( - {fetched && } + {fetched && } )} diff --git a/frontend/src/components/asset-item-filters/asset-item-filters.tsx b/frontend/src/components/asset-item-filters/asset-item-filters.tsx index aae1a8c18..2c11521a5 100644 --- a/frontend/src/components/asset-item-filters/asset-item-filters.tsx +++ b/frontend/src/components/asset-item-filters/asset-item-filters.tsx @@ -62,7 +62,7 @@ export const AssetItemFilters: FC = ({ section }) => { {section === SECTION.SHOP && ( - {fetched && } + {fetched && } )} diff --git a/frontend/src/components/input-fields/styles.ts b/frontend/src/components/input-fields/styles.ts index cbc01b92f..e80f13d8b 100644 --- a/frontend/src/components/input-fields/styles.ts +++ b/frontend/src/components/input-fields/styles.ts @@ -101,7 +101,7 @@ export const ButtonContainer = styled.div` export const RangeContainer = styled.div` display: flex; flex-direction: column; - margin-bottom: 40px; + margin-bottom: 0px; width: 100%; ${SecondaryButton} { width: 40px; diff --git a/frontend/src/components/price-range-slider/price-range-slider.tsx b/frontend/src/components/price-range-slider/price-range-slider.tsx index 1e88ac8e2..4d5cd0de7 100644 --- a/frontend/src/components/price-range-slider/price-range-slider.tsx +++ b/frontend/src/components/price-range-slider/price-range-slider.tsx @@ -1,5 +1,4 @@ -import React, { ChangeEvent, FC, useState } from "react"; -import { Handles, Slider, Tracks } from "react-compound-slider"; +import React, { ChangeEvent, FC, useEffect, useState } from "react"; import { useViewport } from "../../hooks"; import { ButtonContainer, @@ -9,8 +8,6 @@ import { MaxInput, MinInput, RangeContainer, - SliderContainer, - SliderTrack, TextLabel, } from "../input-fields/styles"; import { BoldLabel, ButtonText, SecondaryButton } from "../atoms"; @@ -21,12 +18,12 @@ import { color } from "../../design"; interface Props { prices: number[]; setPrice: (value: { min: number; max: number }) => void; + reset: boolean; } -export const PriceRangeSlider: FC = ({ prices, setPrice }) => { - const range = [Math.min(...prices), Math.max(...prices)]; +export const PriceRangeSlider: FC = ({ prices, setPrice, reset }) => { + const range = [uISTToIST(Math.min(...prices)), uISTToIST(Math.max(...prices))]; const { height } = useViewport(); const [domain] = useState(range); - const [values, setValues] = useState(range); const [, setUpdate] = useState(range); const [inputValues, setInputValues] = useState(domain); @@ -36,37 +33,36 @@ export const PriceRangeSlider: FC = ({ prices, setPrice }) => { }; const onValuesChange = (newValues: readonly number[]) => { - const min = newValues[0]; - const max = newValues[1]; - setValues(newValues); + const min = (newValues[0]); + const max = (newValues[1]); setPrice({ min, max }); }; const onStartPriceChange = (event: ChangeEvent) => { const value = Number(event.target.value); const newState = [value, inputValues[1]]; - setInputValues(newState); - if (value && value >= domain[0]) { - setValues(newState); - } + onUpdate(newState); + onValuesChange(newState); }; const onMaxPriceChange = (event: ChangeEvent) => { const value = Number(event.target.value); const newState = [inputValues[0], value]; - setInputValues(newState); - if (value && value <= domain[1] && value >= values[0]) { - setValues(newState); - } + onUpdate(newState); + onValuesChange(newState); }; const onReset = () => { - setValues(domain); setUpdate(domain); setInputValues(domain); setPrice({ min: domain[0], max: domain[1] }); }; + useEffect(()=> onReset(), []); + useEffect(()=>{ + if(reset) onReset(); + }, [reset]); + return ( @@ -74,17 +70,20 @@ export const PriceRangeSlider: FC = ({ prices, setPrice }) => { {text.store.min} - + {text.store.max} - + - + {/* Disaled until we decide how to handle large ranges, consider: + 1. only show slider when the range is lower than 200 IST, or whatever max range we deem functional + 2. increase the steps exponentially to fit larger ranges + = ({ prices, setPrice }) => { )} - + */} diff --git a/frontend/src/constants.ts b/frontend/src/constants.ts index 86114108b..2b6fac679 100644 --- a/frontend/src/constants.ts +++ b/frontend/src/constants.ts @@ -3,7 +3,7 @@ import { Category, Rarity } from "./interfaces"; export const GO_BACK = -1 as const; export const BASE_URL = import.meta.env.VITE_BASE_URL || "http://localhost:5001"; export const MIN_PRICE = 0 as const; -export const MAX_PRICE = 1000000000000 as const; +export const MAX_PRICE = 100000 as const; export const INFORMATION_STEP = 0 as const; export const WALLET_INTERACTION_STEP = 1 as const; export const CONFIRMATION_STEP = 2 as const; diff --git a/frontend/src/context/filter-context.tsx b/frontend/src/context/filter-context.tsx index 2d30648c4..0d1df527e 100644 --- a/frontend/src/context/filter-context.tsx +++ b/frontend/src/context/filter-context.tsx @@ -2,6 +2,7 @@ import React, { createContext, FC, useMemo, useState } from "react"; import { useAndRequireContext } from "../hooks"; import { Category, Origin, Rarity, Title } from "../interfaces"; import { useGetCharacterMarketPrices, useGetItemMarketPrices } from "../service"; +import { uISTToIST } from "../util"; interface Context { title: Title[]; @@ -46,11 +47,11 @@ export const FiltersContextProvider: FC = ({ children }) => { const [itemPrice, setItemPrice] = useState<{ min: number; max: number; - }>({ min: Math.min(...pricesOfItems), max: Math.max(...pricesOfItems) }); + }>({ min: uISTToIST(Math.min(...pricesOfItems)), max: uISTToIST(Math.max(...pricesOfItems)) }); const [characterPrice, setCharacterPrice] = useState<{ min: number; max: number; - }>({ min: Math.min(...pricesOfCharacters), max: Math.max(...pricesOfCharacters) }); + }>({ min: uISTToIST(Math.min(...pricesOfCharacters)), max: uISTToIST(Math.max(...pricesOfCharacters)) }); const [rarity, setRarity] = useState([]); const [equippedTo, setEquippedTo] = useState(""); diff --git a/frontend/src/pages/shop/characters-shop.tsx b/frontend/src/pages/shop/characters-shop.tsx index c6dc3827f..88d301f84 100644 --- a/frontend/src/pages/shop/characters-shop.tsx +++ b/frontend/src/pages/shop/characters-shop.tsx @@ -12,11 +12,13 @@ import { AssetFilterCount, AssetHeader, AssetHeaderContainer } from "../../compo import { color } from "../../design"; import { findAverageValue, findMinimumValue, toTwoDecimals, uISTToIST } from "../../util"; import { MarketplaceMetrics } from "../../components/marketplace-metrics/marketplace-metrics"; +import { useCharacterMarketState } from "../../context/character-shop-context"; export const CharactersShop: FC = () => { const [selectedId, setSelectedId] = useState(""); const [characters, isLoading] = useGetCharactersInShop(); + const {characters: allMarketCharacters} = useCharacterMarketState(); const metrics = useGetCharacterMarketMetrics(); const [character] = useGetCharacterInShopById(selectedId); const assetsCount = characters.length; @@ -27,9 +29,9 @@ export const CharactersShop: FC = () => { let charAverage = 0; let charMinimum = 0; - if (characters.length != 0) { - charMinimum = findMinimumValue(characters.map((x) => uISTToIST(Number(x.sell.price)))); - charAverage = findAverageValue(characters.map((x) => uISTToIST(Number(x.sell.price)))); + if (allMarketCharacters.length != 0) { + charMinimum = findMinimumValue(allMarketCharacters.map((x) => uISTToIST(Number(x.sell.price)))); + charAverage = findAverageValue(allMarketCharacters.map((x) => uISTToIST(Number(x.sell.price)))); } metricsData = [ diff --git a/frontend/src/pages/shop/items-shop.tsx b/frontend/src/pages/shop/items-shop.tsx index da96fe502..df2525143 100644 --- a/frontend/src/pages/shop/items-shop.tsx +++ b/frontend/src/pages/shop/items-shop.tsx @@ -13,10 +13,12 @@ import { color } from "../../design"; import { MarketplaceMetrics } from "../../components/marketplace-metrics/marketplace-metrics"; import { findAverageValue, findMinimumValue, toTwoDecimals, uISTToIST } from "../../util"; import { ItemInMarket } from "../../interfaces"; +import { useItemMarketState } from "../../context/item-shop-context"; export const ItemsShop: FC = () => { const [selectedId, setSelectedId] = useState(""); const [items, fetched] = useGetItemsInShop(); + const { items: allMarketItems } = useItemMarketState(); const metrics = useGetItemMarketMetrics(); const [item] = useGetItemInShopById(selectedId); @@ -41,8 +43,8 @@ export const ItemsShop: FC = () => { let itemMinimum = 0; if (filteredItems.length != 0) { - itemMinimum = findMinimumValue(filteredItems.map((x) => uISTToIST(Number(x.sell.price)))); - itemAverage = findAverageValue(filteredItems.map((x) => uISTToIST(Number(x.sell.price)))); + itemMinimum = findMinimumValue(allMarketItems.map((x) => uISTToIST(Number(x.sell.price)))); + itemAverage = findAverageValue(allMarketItems.map((x) => uISTToIST(Number(x.sell.price)))); } return [ diff --git a/frontend/src/util/filter.ts b/frontend/src/util/filter.ts index a68696243..8138ddba1 100644 --- a/frontend/src/util/filter.ts +++ b/frontend/src/util/filter.ts @@ -2,6 +2,7 @@ import { CharacterInMarket, ExtendedCharacter, Item, ItemInMarket, Origin, Title import { sortCharacters, sortCharactersMarket, sortItems, sortItemsMarket } from "./sort"; import { getRarityString } from "../service"; import { useFilters } from "../context/filter-context"; +import { ISTTouIST } from "./math"; export interface OfferFilters { description?: string; @@ -36,7 +37,10 @@ export const useFilterItems = (items: Item[]): Item[] => { export const useFilterItemsInShop = (items: ItemInMarket[]): ItemInMarket[] => { const { origin, categories, rarity, itemPrice, colors, sort } = useFilters(); if (items.length === 0) return []; - + const priceFilterRange = { + max: ISTTouIST(itemPrice.max), + min: ISTTouIST(itemPrice.min) + } const filteredOrigins = origin.length > 0 ? items.filter((item) => origin.includes(item.item.origin.toLowerCase())) : items; const filteredCategories = categories.length > 0 ? items.filter((item) => categories.includes(item.item.category)) : items; const filteredRarity = rarity.length > 0 ? items.filter((item) => rarity.includes(getRarityString(item.item.rarity))) : items; @@ -44,7 +48,7 @@ export const useFilterItemsInShop = (items: ItemInMarket[]): ItemInMarket[] => { const filteredPrice = itemPrice ? items.filter(({ sell }) => { const priceValue = Number(sell.price + sell.royalty + sell.platformFee); - return priceValue >= itemPrice.min && priceValue <= itemPrice.max; + return priceValue >= priceFilterRange.min && priceValue <= priceFilterRange.max; }) : items; @@ -77,13 +81,17 @@ export const useFilterCharactersMarket = (characters: CharacterInMarket[]): Char const { origin, title, sort, characterPrice } = useFilters(); if (characters.length === 0) return []; + const priceFilterRange = { + max: ISTTouIST(characterPrice.max), + min: ISTTouIST(characterPrice.min) + } const filteredOrigins = - origin.length > 0 ? characters.filter((character) => origin.includes(character.character.origin.toLowerCase())) : characters; + origin.length > 0 ? characters.filter((character) => origin.includes(character.character.origin.toLowerCase())) : characters; const filteredTitles = title.length > 0 ? characters.filter((character) => title.includes(character.character.title)) : characters; const filteredPrice = characterPrice - ? characters.filter(({ sell }) => { - const priceValue = Number(sell.price + sell.royalty + sell.platformFee); - return priceValue >= characterPrice.min && priceValue <= characterPrice.max; + ? characters.filter(({ sell }) => { + const priceValue = Number(sell.price + sell.royalty + sell.platformFee); + return priceValue >= priceFilterRange.min && priceValue <= priceFilterRange.max; }) : characters;