diff --git a/src/views/catalog/views/page/layout/marketplace/common/IMarketplaceSearchOptions.ts b/src/views/catalog/views/page/layout/marketplace/common/IMarketplaceSearchOptions.ts new file mode 100644 index 00000000..77734c3a --- /dev/null +++ b/src/views/catalog/views/page/layout/marketplace/common/IMarketplaceSearchOptions.ts @@ -0,0 +1,6 @@ +export interface IMarketplaceSearchOptions { + query: string; + type: number; + minPrice: number; + maxPrice: number; +} diff --git a/src/views/catalog/views/page/layout/marketplace/common/MarketplaceOfferData.ts b/src/views/catalog/views/page/layout/marketplace/common/MarketplaceOfferData.ts index 669ae001..ba1fa88b 100644 --- a/src/views/catalog/views/page/layout/marketplace/common/MarketplaceOfferData.ts +++ b/src/views/catalog/views/page/layout/marketplace/common/MarketplaceOfferData.ts @@ -2,8 +2,8 @@ import { IObjectData } from '@nitrots/nitro-renderer'; export class MarketplaceOfferData { - public static TYPE_LANDSCAPE: number = 1; - public static TYPE_FLOOR: number = 2; + public static readonly TYPE_FLOOR: number = 1; + public static readonly TYPE_WALL: number = 2; private _offerId: number; private _furniId: number; diff --git a/src/views/catalog/views/page/layout/marketplace/common/MarketplaceSearchType.ts b/src/views/catalog/views/page/layout/marketplace/common/MarketplaceSearchType.ts new file mode 100644 index 00000000..ac7a7019 --- /dev/null +++ b/src/views/catalog/views/page/layout/marketplace/common/MarketplaceSearchType.ts @@ -0,0 +1,6 @@ +export class MarketplaceSearchType +{ + public static readonly BY_ACTIVITY = 1; + public static readonly BY_VALUE = 2; + public static readonly ADVANCED = 3; +} diff --git a/src/views/catalog/views/page/layout/marketplace/marketplace-item/MarketplaceItemView.tsx b/src/views/catalog/views/page/layout/marketplace/marketplace-item/MarketplaceItemView.tsx index 41cbcb4f..f99dcb1d 100644 --- a/src/views/catalog/views/page/layout/marketplace/marketplace-item/MarketplaceItemView.tsx +++ b/src/views/catalog/views/page/layout/marketplace/marketplace-item/MarketplaceItemView.tsx @@ -1,10 +1,12 @@ +import { CancelMarketplaceOfferMessageComposer } from '@nitrots/nitro-renderer'; import { FC, useCallback } from 'react'; import { GetRoomEngine, LocalizeText } from '../../../../../../../api'; +import { SendMessageHook } from '../../../../../../../hooks'; import { NitroCardGridItemView } from '../../../../../../../layout'; import { MarketplaceOfferData } from '../common/MarketplaceOfferData'; export const OWN_OFFER = 1; -export const OTHER_OFFER = 2; +export const PUBLIC_OFFER = 2; export interface MarketplaceItemViewProps { @@ -14,7 +16,7 @@ export interface MarketplaceItemViewProps export const MarketplaceItemView: FC = props => { - const { offerData = null, type = OTHER_OFFER } = props; + const { offerData = null, type = PUBLIC_OFFER } = props; const getImageUrlForOffer = useCallback( () => { @@ -22,9 +24,9 @@ export const MarketplaceItemView: FC = props => switch(offerData.furniType) { - case 1: + case MarketplaceOfferData.TYPE_FLOOR: return GetRoomEngine().getFurnitureFloorIconUrl(offerData.furniId); - case 2: + case MarketplaceOfferData.TYPE_WALL: return GetRoomEngine().getFurnitureWallIconUrl(offerData.furniId, offerData.extraData); } @@ -53,6 +55,8 @@ export const MarketplaceItemView: FC = props => { if(!offerData) return ''; + if(offerData.timeLeftMinutes <= 0) return LocalizeText('catalog.marketplace.offer.expired'); + const time = Math.max(1, offerData.timeLeftMinutes); const hours = Math.floor(time / 60); const minutes = time - (hours * 60); @@ -66,10 +70,15 @@ export const MarketplaceItemView: FC = props => return LocalizeText('catalog.marketplace.offer.time_left', ['time'], [text] ); }, [offerData]); + const takeItemBack = useCallback(() => + { + SendMessageHook(new CancelMarketplaceOfferMessageComposer(offerData.offerId)); + }, [offerData.offerId]) + return ( - - -
+ + +
{getMarketplaceOfferTitle()}
{getMarketplaceOfferDescription()}
@@ -78,14 +87,18 @@ export const MarketplaceItemView: FC = props =>
{ offerTime() }
} - { type === OTHER_OFFER && <> + { type === PUBLIC_OFFER && <>
{ LocalizeText('catalog.marketplace.offer.price_public_item', ['price', 'average'], [offerData.price.toString(), offerData.averagePrice.toString() ]) }
{ LocalizeText('catalog.marketplace.offer_count', ['count'], [offerData.offerCount.toString()]) }
}
-
- { type === OWN_OFFER && } +
+ { type === OWN_OFFER && } + { type === PUBLIC_OFFER && <> + + + }
) } diff --git a/src/views/catalog/views/page/layout/marketplace/own-items/CatalogLayoutMarketplaceOwnItemsView.tsx b/src/views/catalog/views/page/layout/marketplace/own-items/CatalogLayoutMarketplaceOwnItemsView.tsx index bc397ecb..fb288e32 100644 --- a/src/views/catalog/views/page/layout/marketplace/own-items/CatalogLayoutMarketplaceOwnItemsView.tsx +++ b/src/views/catalog/views/page/layout/marketplace/own-items/CatalogLayoutMarketplaceOwnItemsView.tsx @@ -1,4 +1,4 @@ -import { GetMarketplaceOwnOffersMessageComposer, MarketplaceOwnOffersEvent } from '@nitrots/nitro-renderer'; +import { GetMarketplaceOwnOffersMessageComposer, MarketplaceOwnOffersEvent, RedeemMarketplaceOfferCreditsMessageComposer } from '@nitrots/nitro-renderer'; import { FC, useCallback, useState } from 'react'; import { LocalizeText } from '../../../../../../../api'; import { BatchUpdates, CreateMessageHook, SendMessageHook, UseMountEffect } from '../../../../../../../hooks'; @@ -6,6 +6,7 @@ import { NitroCardGridView } from '../../../../../../../layout'; import { NitroLayoutBase } from '../../../../../../../layout/base'; import { CatalogLayoutProps } from '../../CatalogLayout.types'; import { MarketplaceOfferData } from '../common/MarketplaceOfferData'; +import { MarketPlaceOfferState } from '../common/MarketplaceOfferState'; import { MarketplaceItemView, OWN_OFFER } from '../marketplace-item/MarketplaceItemView'; export interface CatalogLayoutMarketplaceOwnItemsViewProps extends CatalogLayoutProps @@ -46,13 +47,39 @@ export const CatalogLayoutMarketplaceOwnItemsView: FC + { + setOffers(prev => + { + const newVal = new Map(prev); + + const idsToDelete = []; + + for(const offer of newVal.values()) + { + if(offer.status === MarketPlaceOfferState.SOLD) + { + idsToDelete.push(offer.offerId); + } + } + + for(const offerId of idsToDelete) + { + newVal.delete(offerId); + } + return newVal; + }) + + SendMessageHook(new RedeemMarketplaceOfferCreditsMessageComposer()); + }, []); + return ( <> { (creditsWaiting <= 0) && {LocalizeText('catalog.marketplace.redeem.no_sold_items')}} { (creditsWaiting > 0) && {LocalizeText('catalog.marketplace.redeem.get_credits', ['count', 'credits'], ['0', creditsWaiting.toString()])}} - +
{LocalizeText('catalog.marketplace.items_found', ['count'], [offers.size.toString()])}
diff --git a/src/views/catalog/views/page/layout/marketplace/public-items/CatalogLayoutMarketplacePublicItemsView.tsx b/src/views/catalog/views/page/layout/marketplace/public-items/CatalogLayoutMarketplacePublicItemsView.tsx index 435be430..c217941b 100644 --- a/src/views/catalog/views/page/layout/marketplace/public-items/CatalogLayoutMarketplacePublicItemsView.tsx +++ b/src/views/catalog/views/page/layout/marketplace/public-items/CatalogLayoutMarketplacePublicItemsView.tsx @@ -1,7 +1,18 @@ -import { FC } from 'react'; -import { UseMountEffect } from '../../../../../../../hooks'; +import { GetMarketplaceOffersMessageComposer, MarketPlaceOffersEvent } from '@nitrots/nitro-renderer'; +import { FC, useCallback, useMemo, useState } from 'react'; +import { LocalizeText } from '../../../../../../../api'; +import { BatchUpdates, CreateMessageHook, SendMessageHook } from '../../../../../../../hooks'; +import { NitroCardGridView } from '../../../../../../../layout'; import { CatalogLayoutProps } from '../../CatalogLayout.types'; +import { IMarketplaceSearchOptions } from '../common/IMarketplaceSearchOptions'; +import { MarketplaceOfferData } from '../common/MarketplaceOfferData'; +import { MarketplaceSearchType } from '../common/MarketplaceSearchType'; +import { MarketplaceItemView, PUBLIC_OFFER } from '../marketplace-item/MarketplaceItemView'; +import { SearchFormView } from './SearchFormView'; +const SORT_TYPES_VALUE = [1, 2]; +const SORT_TYPES_ACTIVITY = [3, 4, 5, 6]; +const SORT_TYPES_ADVANCED = [1, 2, 3, 4, 5, 6]; export interface CatalogLayoutMarketplacePublicItemsViewProps extends CatalogLayoutProps { @@ -9,10 +20,73 @@ export interface CatalogLayoutMarketplacePublicItemsViewProps extends CatalogLay export const CatalogLayoutMarketplacePublicItemsView: FC = props => { - UseMountEffect(() => + const [ searchType, setSearchType ] = useState(MarketplaceSearchType.BY_ACTIVITY); + const [ totalItemsFound, setTotalItemsFound ] = useState(0); + const [ offers, setOffers ] = useState(new Map()); + + const requestOffers = useCallback((options: IMarketplaceSearchOptions) => { - //request stuff - }); + SendMessageHook(new GetMarketplaceOffersMessageComposer(options.minPrice, options.maxPrice, options.query, options.type)) + }, []); + + const getSortTypes = useMemo( () => + { + switch(searchType) + { + case MarketplaceSearchType.BY_ACTIVITY: + return SORT_TYPES_ACTIVITY; + case MarketplaceSearchType.BY_VALUE: + return SORT_TYPES_VALUE; + case MarketplaceSearchType.ADVANCED: + return SORT_TYPES_ADVANCED; + } + return []; + }, [searchType]); + + const onMarketPlaceOffersEvent = useCallback( (event: MarketPlaceOffersEvent) => + { + const parser = event.getParser(); + + if(!parser) return; + + const latestOffers = new Map(); + parser.offers.forEach(entry => + { + const offerEntry = new MarketplaceOfferData(entry.offerId, entry.furniId, entry.furniType, entry.extraData, entry.stuffData, entry.price, entry.status, entry.averagePrice, entry.offerCount); + offerEntry.timeLeftMinutes = entry.timeLeftMinutes; + latestOffers.set(entry.offerId, offerEntry); + }); + + BatchUpdates(() => + { + setTotalItemsFound(parser.totalItemsFound); + setOffers(latestOffers); + }); + + }, []); + + CreateMessageHook(MarketPlaceOffersEvent, onMarketPlaceOffersEvent); - return null; + return (<> +
+ + + +
+ + + +
{LocalizeText('catalog.marketplace.items_found', ['count'], [offers.size.toString()])}
+ + { + Array.from(offers.values()).map( (entry, index) => ) + } + + ); } diff --git a/src/views/catalog/views/page/layout/marketplace/public-items/SearchFormView.tsx b/src/views/catalog/views/page/layout/marketplace/public-items/SearchFormView.tsx new file mode 100644 index 00000000..3fe0d3e6 --- /dev/null +++ b/src/views/catalog/views/page/layout/marketplace/public-items/SearchFormView.tsx @@ -0,0 +1,70 @@ +import { FC, useCallback, useEffect, useState } from 'react'; +import { LocalizeText } from '../../../../../../../api'; +import { IMarketplaceSearchOptions } from '../common/IMarketplaceSearchOptions'; +import { MarketplaceSearchType } from '../common/MarketplaceSearchType'; + +export interface SearchFormViewProps +{ + searchType: number; + sortTypes: number[]; + onSearch(options: IMarketplaceSearchOptions): void; +} + +export const SearchFormView: FC = props => +{ + const { searchType = null, sortTypes = null, onSearch = null } = props; + const [ sortType, setSortType ] = useState(sortTypes ? sortTypes[0] : 3); // first item of SORT_TYPES_ACTIVITY + const [ searchQuery, setSearchQuery ] = useState(''); + const [ min, setMin ] = useState(0); + const [ max, setMax ] = useState(0); + + const onSortTypeChange = useCallback((sortType: number) => + { + setSortType(sortType); + if(searchType === MarketplaceSearchType.BY_ACTIVITY || searchType === MarketplaceSearchType.BY_VALUE) + onSearch({ minPrice: -1, maxPrice: -1, query: '', type: sortType }); + }, [onSearch, searchType]); + + const onClickSearch = useCallback(() => + { + const minPrice = min > 0 ? min : -1; + const maxPrice = max > 0 ? max : -1; + + onSearch({ minPrice: minPrice, maxPrice: maxPrice, type: sortType, query: searchQuery }) + }, [max, min, onSearch, searchQuery, sortType]); + + useEffect( () => + { + if(!sortTypes || !sortTypes.length) return; + + const sortType = sortTypes[0]; + setSortType(sortType); + + if(searchType === MarketplaceSearchType.BY_ACTIVITY || MarketplaceSearchType.BY_VALUE === searchType) + onSearch({ minPrice: -1, maxPrice: -1, query: '', type: sortType }); + }, [onSearch, searchType, sortTypes]); + + return (<> +
+
{ LocalizeText('catalog.marketplace.sort_order') }
+ +
+ { searchType === MarketplaceSearchType.ADVANCED && <> +
+
{ LocalizeText('catalog.marketplace.search_name') }
+ setSearchQuery(event.target.value)}/> +
+ +
+
{ LocalizeText('catalog.marketplace.search_price') }
+ setMin(event.target.valueAsNumber) } /> + setMax(event.target.valueAsNumber) } /> +
+ + + + } + ); +}