This commit is contained in:
dank074 2021-12-22 02:59:42 -06:00
parent 1546a0446b
commit 70c1690e39
7 changed files with 216 additions and 20 deletions

View File

@ -0,0 +1,6 @@
export interface IMarketplaceSearchOptions {
query: string;
type: number;
minPrice: number;
maxPrice: number;
}

View File

@ -2,8 +2,8 @@ import { IObjectData } from '@nitrots/nitro-renderer';
export class MarketplaceOfferData export class MarketplaceOfferData
{ {
public static TYPE_LANDSCAPE: number = 1; public static readonly TYPE_FLOOR: number = 1;
public static TYPE_FLOOR: number = 2; public static readonly TYPE_WALL: number = 2;
private _offerId: number; private _offerId: number;
private _furniId: number; private _furniId: number;

View File

@ -0,0 +1,6 @@
export class MarketplaceSearchType
{
public static readonly BY_ACTIVITY = 1;
public static readonly BY_VALUE = 2;
public static readonly ADVANCED = 3;
}

View File

@ -1,10 +1,12 @@
import { CancelMarketplaceOfferMessageComposer } from '@nitrots/nitro-renderer';
import { FC, useCallback } from 'react'; import { FC, useCallback } from 'react';
import { GetRoomEngine, LocalizeText } from '../../../../../../../api'; import { GetRoomEngine, LocalizeText } from '../../../../../../../api';
import { SendMessageHook } from '../../../../../../../hooks';
import { NitroCardGridItemView } from '../../../../../../../layout'; import { NitroCardGridItemView } from '../../../../../../../layout';
import { MarketplaceOfferData } from '../common/MarketplaceOfferData'; import { MarketplaceOfferData } from '../common/MarketplaceOfferData';
export const OWN_OFFER = 1; export const OWN_OFFER = 1;
export const OTHER_OFFER = 2; export const PUBLIC_OFFER = 2;
export interface MarketplaceItemViewProps export interface MarketplaceItemViewProps
{ {
@ -14,7 +16,7 @@ export interface MarketplaceItemViewProps
export const MarketplaceItemView: FC<MarketplaceItemViewProps> = props => export const MarketplaceItemView: FC<MarketplaceItemViewProps> = props =>
{ {
const { offerData = null, type = OTHER_OFFER } = props; const { offerData = null, type = PUBLIC_OFFER } = props;
const getImageUrlForOffer = useCallback( () => const getImageUrlForOffer = useCallback( () =>
{ {
@ -22,9 +24,9 @@ export const MarketplaceItemView: FC<MarketplaceItemViewProps> = props =>
switch(offerData.furniType) switch(offerData.furniType)
{ {
case 1: case MarketplaceOfferData.TYPE_FLOOR:
return GetRoomEngine().getFurnitureFloorIconUrl(offerData.furniId); return GetRoomEngine().getFurnitureFloorIconUrl(offerData.furniId);
case 2: case MarketplaceOfferData.TYPE_WALL:
return GetRoomEngine().getFurnitureWallIconUrl(offerData.furniId, offerData.extraData); return GetRoomEngine().getFurnitureWallIconUrl(offerData.furniId, offerData.extraData);
} }
@ -53,6 +55,8 @@ export const MarketplaceItemView: FC<MarketplaceItemViewProps> = props =>
{ {
if(!offerData) return ''; if(!offerData) return '';
if(offerData.timeLeftMinutes <= 0) return LocalizeText('catalog.marketplace.offer.expired');
const time = Math.max(1, offerData.timeLeftMinutes); const time = Math.max(1, offerData.timeLeftMinutes);
const hours = Math.floor(time / 60); const hours = Math.floor(time / 60);
const minutes = time - (hours * 60); const minutes = time - (hours * 60);
@ -66,10 +70,15 @@ export const MarketplaceItemView: FC<MarketplaceItemViewProps> = props =>
return LocalizeText('catalog.marketplace.offer.time_left', ['time'], [text] ); return LocalizeText('catalog.marketplace.offer.time_left', ['time'], [text] );
}, [offerData]); }, [offerData]);
const takeItemBack = useCallback(() =>
{
SendMessageHook(new CancelMarketplaceOfferMessageComposer(offerData.offerId));
}, [offerData.offerId])
return ( return (
<NitroCardGridItemView className='w-100 marketplace-item'> <NitroCardGridItemView className='w-100 marketplace-item align-items-center'>
<img src={ getImageUrlForOffer() } className='mx-3'/> <img src={ getImageUrlForOffer() } className='mx-3' alt='' />
<div className='h-100 flex-grow-1 lh-1'> <div className='h-100 flex-grow-1 justify-content-center '>
<div className='fw-bold'>{getMarketplaceOfferTitle()}</div> <div className='fw-bold'>{getMarketplaceOfferTitle()}</div>
<div className='fst-italic fs-6'>{getMarketplaceOfferDescription()}</div> <div className='fst-italic fs-6'>{getMarketplaceOfferDescription()}</div>
@ -78,14 +87,18 @@ export const MarketplaceItemView: FC<MarketplaceItemViewProps> = props =>
<div>{ offerTime() }</div> <div>{ offerTime() }</div>
</> </>
} }
{ type === OTHER_OFFER && <> { type === PUBLIC_OFFER && <>
<div>{ LocalizeText('catalog.marketplace.offer.price_public_item', ['price', 'average'], [offerData.price.toString(), offerData.averagePrice.toString() ]) }</div> <div>{ LocalizeText('catalog.marketplace.offer.price_public_item', ['price', 'average'], [offerData.price.toString(), offerData.averagePrice.toString() ]) }</div>
<div>{ LocalizeText('catalog.marketplace.offer_count', ['count'], [offerData.offerCount.toString()]) }</div> <div>{ LocalizeText('catalog.marketplace.offer_count', ['count'], [offerData.offerCount.toString()]) }</div>
</> </>
} }
</div> </div>
<div className='btn-group-vertical mx-1'> <div className='btn-group-vertical mx-1 gap-2'>
{ type === OWN_OFFER && <button className='btn btn-secondary btn-sm'>{LocalizeText('catalog.marketplace.offer.pick')}</button>} { type === OWN_OFFER && <button className='btn btn-secondary btn-sm' onClick={ takeItemBack }>{ LocalizeText('catalog.marketplace.offer.pick') }</button>}
{ type === PUBLIC_OFFER && <>
<button className='btn btn-secondary btn-sm'>{ LocalizeText('buy') }</button>
<button className='btn btn-secondary btn-sm' disabled={true}>{ LocalizeText('catalog.marketplace.view_more') }</button>
</>}
</div> </div>
</NitroCardGridItemView>) </NitroCardGridItemView>)
} }

View File

@ -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 { FC, useCallback, useState } from 'react';
import { LocalizeText } from '../../../../../../../api'; import { LocalizeText } from '../../../../../../../api';
import { BatchUpdates, CreateMessageHook, SendMessageHook, UseMountEffect } from '../../../../../../../hooks'; import { BatchUpdates, CreateMessageHook, SendMessageHook, UseMountEffect } from '../../../../../../../hooks';
@ -6,6 +6,7 @@ import { NitroCardGridView } from '../../../../../../../layout';
import { NitroLayoutBase } from '../../../../../../../layout/base'; import { NitroLayoutBase } from '../../../../../../../layout/base';
import { CatalogLayoutProps } from '../../CatalogLayout.types'; import { CatalogLayoutProps } from '../../CatalogLayout.types';
import { MarketplaceOfferData } from '../common/MarketplaceOfferData'; import { MarketplaceOfferData } from '../common/MarketplaceOfferData';
import { MarketPlaceOfferState } from '../common/MarketplaceOfferState';
import { MarketplaceItemView, OWN_OFFER } from '../marketplace-item/MarketplaceItemView'; import { MarketplaceItemView, OWN_OFFER } from '../marketplace-item/MarketplaceItemView';
export interface CatalogLayoutMarketplaceOwnItemsViewProps extends CatalogLayoutProps export interface CatalogLayoutMarketplaceOwnItemsViewProps extends CatalogLayoutProps
@ -46,13 +47,39 @@ export const CatalogLayoutMarketplaceOwnItemsView: FC<CatalogLayoutMarketplaceOw
CreateMessageHook(MarketplaceOwnOffersEvent, onMarketPlaceOwnOffersEvent); CreateMessageHook(MarketplaceOwnOffersEvent, onMarketPlaceOwnOffersEvent);
const redeemSoldOffers = useCallback(() =>
{
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 ( return (
<> <>
{ (creditsWaiting <= 0) && <NitroLayoutBase className='text-black'>{LocalizeText('catalog.marketplace.redeem.no_sold_items')}</NitroLayoutBase>} { (creditsWaiting <= 0) && <NitroLayoutBase className='text-black'>{LocalizeText('catalog.marketplace.redeem.no_sold_items')}</NitroLayoutBase>}
{ (creditsWaiting > 0) && <NitroLayoutBase className='text-black'>{LocalizeText('catalog.marketplace.redeem.get_credits', ['count', 'credits'], ['0', creditsWaiting.toString()])}</NitroLayoutBase>} { (creditsWaiting > 0) && <NitroLayoutBase className='text-black'>{LocalizeText('catalog.marketplace.redeem.get_credits', ['count', 'credits'], ['0', creditsWaiting.toString()])}</NitroLayoutBase>}
<button className='btn btn-primary btn-sm mx-auto' disabled={creditsWaiting <= 0}>{LocalizeText('catalog.marketplace.offer.redeem')}</button> <button className='btn btn-primary btn-sm mx-auto' disabled={creditsWaiting <= 0} onClick={redeemSoldOffers}>{LocalizeText('catalog.marketplace.offer.redeem')}</button>
<div className='text-black'>{LocalizeText('catalog.marketplace.items_found', ['count'], [offers.size.toString()])}</div> <div className='text-black'>{LocalizeText('catalog.marketplace.items_found', ['count'], [offers.size.toString()])}</div>
<NitroCardGridView columns={1} className='text-black'> <NitroCardGridView columns={1} className='text-black'>

View File

@ -1,7 +1,18 @@
import { FC } from 'react'; import { GetMarketplaceOffersMessageComposer, MarketPlaceOffersEvent } from '@nitrots/nitro-renderer';
import { UseMountEffect } from '../../../../../../../hooks'; 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 { 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 export interface CatalogLayoutMarketplacePublicItemsViewProps extends CatalogLayoutProps
{ {
@ -9,10 +20,73 @@ export interface CatalogLayoutMarketplacePublicItemsViewProps extends CatalogLay
export const CatalogLayoutMarketplacePublicItemsView: FC<CatalogLayoutMarketplacePublicItemsViewProps> = props => export const CatalogLayoutMarketplacePublicItemsView: FC<CatalogLayoutMarketplacePublicItemsViewProps> = props =>
{ {
UseMountEffect(() => const [ searchType, setSearchType ] = useState(MarketplaceSearchType.BY_ACTIVITY);
{ const [ totalItemsFound, setTotalItemsFound ] = useState(0);
//request stuff const [ offers, setOffers ] = useState(new Map<number, MarketplaceOfferData>());
});
return null; const requestOffers = useCallback((options: IMarketplaceSearchOptions) =>
{
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<number, MarketplaceOfferData>();
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 (<>
<div className="btn-group" role="group">
<button type="button" className={`btn btn-primary ${searchType === MarketplaceSearchType.BY_ACTIVITY ? 'active' : ''}`} onClick={() => setSearchType(MarketplaceSearchType.BY_ACTIVITY)}>
{ LocalizeText('catalog.marketplace.search_by_activity') }
</button>
<button type="button" className={`btn btn-primary ${searchType === MarketplaceSearchType.BY_VALUE ? 'active' : ''}`} onClick={() => setSearchType(MarketplaceSearchType.BY_VALUE)}>
{ LocalizeText('catalog.marketplace.search_by_value') }
</button>
<button type="button" className={`btn btn-primary ${searchType === MarketplaceSearchType.ADVANCED ? 'active' : ''}`} onClick={() => setSearchType(MarketplaceSearchType.ADVANCED)}>
{ LocalizeText('catalog.marketplace.search_advanced') }
</button>
</div>
<SearchFormView sortTypes={ getSortTypes } searchType={ searchType } onSearch={ requestOffers }/>
<div className='text-black'>{LocalizeText('catalog.marketplace.items_found', ['count'], [offers.size.toString()])}</div>
<NitroCardGridView columns={1} className='text-black'>
{
Array.from(offers.values()).map( (entry, index) => <MarketplaceItemView key={ index } offerData={ entry } type={ PUBLIC_OFFER } />)
}
</NitroCardGridView>
</>);
} }

View File

@ -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<SearchFormViewProps> = 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 (<>
<div className="d-flex flex-row text-black">
<div className="mr-2 align-self-center col-4" style={ { whiteSpace: 'nowrap' } }>{ LocalizeText('catalog.marketplace.sort_order') }</div>
<select className="form-control form-control-sm" value={sortType} onChange={ (event) => onSortTypeChange(parseInt(event.target.value)) }>
{ sortTypes.map( (type, index) => <option key={index} value={type}>{ LocalizeText(`catalog.marketplace.sort.${type}`) }</option>)}
</select>
</div>
{ searchType === MarketplaceSearchType.ADVANCED && <>
<div className="d-flex flex-row text-black">
<div className="mr-2 align-self-center col-4" style={ { whiteSpace: 'nowrap' } }>{ LocalizeText('catalog.marketplace.search_name') }</div>
<input className="form-control form-control-sm" type="text" value={ searchQuery} onChange={event => setSearchQuery(event.target.value)}/>
</div>
<div className="d-flex flex-row text-black">
<div className="mr-2 align-self-center col-4" style={ { whiteSpace: 'nowrap' } }>{ LocalizeText('catalog.marketplace.search_price') }</div>
<input className="form-control form-control-sm" type="number" min={0} value={ min } onChange={ event => setMin(event.target.valueAsNumber) } />
<input className="form-control form-control-sm" type="number" min={0} value={ max } onChange={ event => setMax(event.target.valueAsNumber) } />
</div>
<button className="btn btn-secondary btn-sm float-end mx-auto" onClick={onClickSearch}>{ LocalizeText('generic.search') }</button>
</>
}
</>);
}