diff --git a/src/events/catalog/CatalogPostMarketplaceOfferEvent.ts b/src/events/catalog/CatalogPostMarketplaceOfferEvent.ts new file mode 100644 index 00000000..837f1cff --- /dev/null +++ b/src/events/catalog/CatalogPostMarketplaceOfferEvent.ts @@ -0,0 +1,20 @@ +import { CatalogEvent } from '.'; +import { FurnitureItem } from '../../views/inventory/common/FurnitureItem'; + +export class CatalogPostMarketplaceOfferEvent extends CatalogEvent +{ + public static readonly POST_MARKETPLACE = 'CE_POST_MARKETPLACE'; + + private _item: FurnitureItem; + + constructor(item: FurnitureItem) + { + super(CatalogPostMarketplaceOfferEvent.POST_MARKETPLACE); + this._item = item; + } + + public get item(): FurnitureItem + { + return this._item; + } +} diff --git a/src/views/catalog/CatalogView.tsx b/src/views/catalog/CatalogView.tsx index d45ed9e3..cbe704c8 100644 --- a/src/views/catalog/CatalogView.tsx +++ b/src/views/catalog/CatalogView.tsx @@ -14,6 +14,7 @@ import { CatalogReducer, initialCatalog } from './reducers/CatalogReducer'; import { CatalogGiftView } from './views/gift/CatalogGiftView'; import { ACTIVE_PAGES, CatalogNavigationView } from './views/navigation/CatalogNavigationView'; import { CatalogPageView } from './views/page/CatalogPageView'; +import { MarketplacePostOfferView } from './views/page/layout/marketplace/post-offer/MarketplacePostOfferView'; export const CatalogView: FC = props => { @@ -220,6 +221,7 @@ export const CatalogView: FC = props => } + ); } diff --git a/src/views/catalog/views/page/layout/CatalogLayout.scss b/src/views/catalog/views/page/layout/CatalogLayout.scss index 69926aec..3302b3b8 100644 --- a/src/views/catalog/views/page/layout/CatalogLayout.scss +++ b/src/views/catalog/views/page/layout/CatalogLayout.scss @@ -2,3 +2,4 @@ @import './info-loyalty/CatalogLayoutInfoLoyaltyView.scss'; @import './vip-buy/CatalogLayoutVipBuyView'; @import './marketplace/marketplace-item/MarketplaceItemView'; +@import './marketplace/post-offer/MarketplacePostOfferView'; 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 f99dcb1d..58cf189c 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,9 +1,13 @@ -import { CancelMarketplaceOfferMessageComposer } from '@nitrots/nitro-renderer'; +import { BuyMarketplaceOfferMessageComposer, CancelMarketplaceOfferMessageComposer } from '@nitrots/nitro-renderer'; import { FC, useCallback } from 'react'; import { GetRoomEngine, LocalizeText } from '../../../../../../../api'; import { SendMessageHook } from '../../../../../../../hooks'; import { NitroCardGridItemView } from '../../../../../../../layout'; +import { NotificationAlertType } from '../../../../../../notification-center/common/NotificationAlertType'; +import { NotificationUtilities } from '../../../../../../notification-center/common/NotificationUtilities'; +import { GetCurrencyAmount } from '../../../../../../purse/common/CurrencyHelper'; import { MarketplaceOfferData } from '../common/MarketplaceOfferData'; +import { MarketPlaceOfferState } from '../common/MarketplaceOfferState'; export const OWN_OFFER = 1; export const PUBLIC_OFFER = 2; @@ -73,7 +77,18 @@ export const MarketplaceItemView: FC = props => const takeItemBack = useCallback(() => { SendMessageHook(new CancelMarketplaceOfferMessageComposer(offerData.offerId)); - }, [offerData.offerId]) + }, [offerData.offerId]); + + const buyOffer = useCallback(() => + { + if(offerData.price > GetCurrencyAmount(-1)) + { + NotificationUtilities.simpleAlert(LocalizeText('catalog.alert.notenough.credits.description'), NotificationAlertType.DEFAULT, null, null, LocalizeText('catalog.alert.notenough.title')); + return; + } + + SendMessageHook(new BuyMarketplaceOfferMessageComposer(offerData.offerId)); + }, [offerData.offerId, offerData.price]); return ( @@ -94,9 +109,9 @@ export const MarketplaceItemView: FC = props => }
- { type === OWN_OFFER && } + { (type === OWN_OFFER && offerData.status !== MarketPlaceOfferState.SOLD) && } { 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 fb288e32..38f5509a 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,9 +1,11 @@ -import { GetMarketplaceOwnOffersMessageComposer, MarketplaceOwnOffersEvent, RedeemMarketplaceOfferCreditsMessageComposer } from '@nitrots/nitro-renderer'; +import { GetMarketplaceOwnOffersMessageComposer, MarketplaceCancelOfferResultEvent, MarketplaceOwnOffersEvent, RedeemMarketplaceOfferCreditsMessageComposer } from '@nitrots/nitro-renderer'; import { FC, useCallback, useState } from 'react'; import { LocalizeText } from '../../../../../../../api'; import { BatchUpdates, CreateMessageHook, SendMessageHook, UseMountEffect } from '../../../../../../../hooks'; import { NitroCardGridView } from '../../../../../../../layout'; import { NitroLayoutBase } from '../../../../../../../layout/base'; +import { NotificationAlertType } from '../../../../../../notification-center/common/NotificationAlertType'; +import { NotificationUtilities } from '../../../../../../notification-center/common/NotificationUtilities'; import { CatalogLayoutProps } from '../../CatalogLayout.types'; import { MarketplaceOfferData } from '../common/MarketplaceOfferData'; import { MarketPlaceOfferState } from '../common/MarketplaceOfferState'; @@ -45,7 +47,28 @@ export const CatalogLayoutMarketplaceOwnItemsView: FC + { + const parser = event.getParser(); + + if(!parser) return; + + if(!parser.success) + { + NotificationUtilities.simpleAlert(LocalizeText('catalog.marketplace.cancel_failed'), NotificationAlertType.DEFAULT, null, null, LocalizeText('catalog.marketplace.operation_failed.topic')); + return; + } + + setOffers( prev => + { + const newVal = new Map(prev); + newVal.delete(parser.offerId); + return newVal; + }); + }, []); + CreateMessageHook(MarketplaceOwnOffersEvent, onMarketPlaceOwnOffersEvent); + CreateMessageHook(MarketplaceCancelOfferResultEvent, onMarketplaceCancelOfferResultEvent); const redeemSoldOffers = useCallback(() => { @@ -77,7 +100,7 @@ export const CatalogLayoutMarketplaceOwnItemsView: FC { (creditsWaiting <= 0) && {LocalizeText('catalog.marketplace.redeem.no_sold_items')}} - { (creditsWaiting > 0) && {LocalizeText('catalog.marketplace.redeem.get_credits', ['count', 'credits'], ['0', creditsWaiting.toString()])}} + { (creditsWaiting > 0) && {LocalizeText('catalog.marketplace.redeem.get_credits', ['count', 'credits'], [Array.from(offers.values()).filter(value => value.status === MarketPlaceOfferState.SOLD).length.toString(), creditsWaiting.toString()])}} diff --git a/src/views/catalog/views/page/layout/marketplace/post-offer/MarketplacePostOfferView.scss b/src/views/catalog/views/page/layout/marketplace/post-offer/MarketplacePostOfferView.scss new file mode 100644 index 00000000..46306202 --- /dev/null +++ b/src/views/catalog/views/page/layout/marketplace/post-offer/MarketplacePostOfferView.scss @@ -0,0 +1,12 @@ +.nitro-marketplace-post-offer { + width: 300px; + height: 365px; + + .item-image-container { + width: 75px; + height: 75px; + background-repeat: no-repeat; + background-position: center; + overflow: hidden; + } +} diff --git a/src/views/catalog/views/page/layout/marketplace/post-offer/MarketplacePostOfferView.tsx b/src/views/catalog/views/page/layout/marketplace/post-offer/MarketplacePostOfferView.tsx new file mode 100644 index 00000000..fdd5c22b --- /dev/null +++ b/src/views/catalog/views/page/layout/marketplace/post-offer/MarketplacePostOfferView.tsx @@ -0,0 +1,107 @@ +import { ImageResult, MakeOfferMessageComposer, Vector3d } from '@nitrots/nitro-renderer'; +import { FC, useCallback, useState } from 'react'; +import { GetRoomEngine, LocalizeText } from '../../../../../../../api'; +import { CatalogPostMarketplaceOfferEvent } from '../../../../../../../events/catalog/CatalogPostMarketplaceOfferEvent'; +import { SendMessageHook, useUiEvent } from '../../../../../../../hooks'; +import { NitroCardContentView, NitroCardHeaderView, NitroCardView, NitroLayoutFlex } from '../../../../../../../layout'; +import { FurnitureItem } from '../../../../../../inventory/common/FurnitureItem'; + +export const MarketplacePostOfferView : FC<{}> = props => +{ + const [ item, setItem ] = useState(null); + const [ askingPrice, setAskingPrice ] = useState(0); + + const close = useCallback(() => + { + setItem(null); + setAskingPrice(0); + }, []); + + const onCatalogPostMarketplaceOfferEvent = useCallback( (event: CatalogPostMarketplaceOfferEvent) => + { + setItem(event.item); + }, []); + + useUiEvent(CatalogPostMarketplaceOfferEvent.POST_MARKETPLACE, onCatalogPostMarketplaceOfferEvent); + + const getItemImage = useCallback( () => + { + if(!item) return ''; + + let object: ImageResult; + + if(!item.isWallItem) + { + object = GetRoomEngine().getFurnitureFloorImage(item.type, new Vector3d(90,0,0), 64, this, 4293848814, item.extra.toString()); + } + else + { + object = GetRoomEngine().getFurnitureWallImage(item.type, new Vector3d(90,0,0), 64, this, 4293848814, item.extra.toString()); + } + + if(object) + { + const image = object.getImage(); + + if(image) return image.src; + } + return ''; + }, [item]); + + const getFurniTitle = useCallback( () => + { + if(!item) return ''; + + const localizationKey = item.isWallItem ? 'wallItem.name.' + item.type : 'roomItem.name.' + item.type; + + return LocalizeText(localizationKey); + }, [item]); + + const getFurniDescription = useCallback( () => + { + if(!item) return ''; + + const localizationKey = item.isWallItem ? 'wallItem.desc.' + item.type : 'roomItem.desc.' + item.type; + + return LocalizeText(localizationKey); + }, [item]); + + const postItem = useCallback( () => + { + if(isNaN(askingPrice) || askingPrice <= 0) return; + + SendMessageHook(new MakeOfferMessageComposer(askingPrice, item.isWallItem ? 2 : 1, item.id)); + + setItem(null); + }, [askingPrice, item]); + + return ( item && + + + + +
+
+
{getFurniTitle()}
+
{getFurniDescription()}
+
+ +
+ { LocalizeText('inventory.marketplace.make_offer.expiration_info') } +
+
+
{ LocalizeText('inventory.marketplace.make_offer.price_request') }
+ setAskingPrice(event.target.valueAsNumber) } /> +
+
+ { askingPrice <= 0 || isNaN(askingPrice) ? LocalizeText('inventory.marketplace.make_offer.min_price', ['minprice'], ['1']) : LocalizeText('inventory.marketplace.make_offer.final_price', ['commission', 'finalprice'], ['1', (askingPrice + 1).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 c217941b..217970be 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,8 +1,10 @@ -import { GetMarketplaceOffersMessageComposer, MarketPlaceOffersEvent } from '@nitrots/nitro-renderer'; +import { GetMarketplaceOffersMessageComposer, MarketplaceBuyOfferResultEvent, 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 { NotificationAlertType } from '../../../../../../notification-center/common/NotificationAlertType'; +import { NotificationUtilities } from '../../../../../../notification-center/common/NotificationUtilities'; import { CatalogLayoutProps } from '../../CatalogLayout.types'; import { IMarketplaceSearchOptions } from '../common/IMarketplaceSearchOptions'; import { MarketplaceOfferData } from '../common/MarketplaceOfferData'; @@ -23,9 +25,11 @@ export const CatalogLayoutMarketplacePublicItemsView: FC()); + const [ lastSearch, setLastSearch ] = useState({ minPrice: -1, maxPrice: -1, query: '', type: 3 }); const requestOffers = useCallback((options: IMarketplaceSearchOptions) => { + setLastSearch(options); SendMessageHook(new GetMarketplaceOffersMessageComposer(options.minPrice, options.maxPrice, options.query, options.type)) }, []); @@ -65,7 +69,57 @@ export const CatalogLayoutMarketplacePublicItemsView: FC + { + const parser = event.getParser(); + + if(!parser) return; + + switch(parser.result) + { + case 1: + requestOffers(lastSearch); + break; + case 2: + setOffers(prev => + { + const newVal = new Map(prev); + newVal.delete(parser.requestedOfferId); + return newVal; + }); + NotificationUtilities.simpleAlert(LocalizeText('catalog.marketplace.not_available_header'), NotificationAlertType.DEFAULT, null, null, LocalizeText('catalog.marketplace.not_available_title')); + break; + case 3: + // our shit was updated + // todo: some dialogue modal + setOffers( prev => + { + const newVal = new Map(prev); + + const item = newVal.get(parser.requestedOfferId); + if(item) + { + item.offerId = parser.offerId; + item.price = parser.newPrice; + item.offerCount--; + newVal.set(item.offerId, item); + } + + newVal.delete(parser.requestedOfferId); + return newVal; + }); + + // for now just let user know + NotificationUtilities.simpleAlert(LocalizeText('catalog.marketplace.confirm_higher_header'), NotificationAlertType.DEFAULT, null, null, LocalizeText('catalog.marketplace.confirm_higher_title')); + break; + case 4: + NotificationUtilities.simpleAlert(LocalizeText('catalog.alert.notenough.credits.description'), NotificationAlertType.DEFAULT, null, null, LocalizeText('catalog.alert.notenough.title')); + break; + } + }, [lastSearch, requestOffers]); + CreateMessageHook(MarketPlaceOffersEvent, onMarketPlaceOffersEvent); + CreateMessageHook(MarketplaceBuyOfferResultEvent, onMarketplaceBuyOfferResultEvent); return (<>
diff --git a/src/views/inventory/common/FurnitureUtilities.ts b/src/views/inventory/common/FurnitureUtilities.ts index ef727f4e..46ba4b7d 100644 --- a/src/views/inventory/common/FurnitureUtilities.ts +++ b/src/views/inventory/common/FurnitureUtilities.ts @@ -1,6 +1,7 @@ import { FurnitureListItemParser, FurniturePlacePaintComposer, IObjectData, RoomObjectCategory, RoomObjectPlacementSource } from '@nitrots/nitro-renderer'; import { GetRoomEngine } from '../../../api'; import { InventoryEvent } from '../../../events'; +import { CatalogPostMarketplaceOfferEvent } from '../../../events/catalog/CatalogPostMarketplaceOfferEvent'; import { dispatchUiEvent } from '../../../hooks/events/ui/ui-event'; import { SendMessageHook } from '../../../hooks/messages/message-event'; import { FurniCategory } from './FurniCategory'; @@ -55,6 +56,17 @@ export function attemptItemPlacement(groupItem: GroupItem, flag: boolean = false return true; } +export function attemptPlaceMarketplaceOffer(groupItem: GroupItem): boolean +{ + const item = groupItem.getLastItem(); + + if(!item) return false; + + if(!item.sellable) return false; + + dispatchUiEvent(new CatalogPostMarketplaceOfferEvent(item)); +} + function cancelRoomObjectPlacement(): void { if(getPlacingItemId() === -1) return; diff --git a/src/views/inventory/common/GroupItem.ts b/src/views/inventory/common/GroupItem.ts index 62c9f853..2b3598a4 100644 --- a/src/views/inventory/common/GroupItem.ts +++ b/src/views/inventory/common/GroupItem.ts @@ -442,6 +442,13 @@ export class GroupItem return (item ? item.isGroupable : false); } + public get isSellable(): boolean + { + const item = this.getItemByIndex(0); + + return (item ? item.sellable : false); + } + public get items(): FurnitureItem[] { return this._items; diff --git a/src/views/inventory/views/furniture/InventoryFurnitureView.tsx b/src/views/inventory/views/furniture/InventoryFurnitureView.tsx index bd216d0f..3edc27fb 100644 --- a/src/views/inventory/views/furniture/InventoryFurnitureView.tsx +++ b/src/views/inventory/views/furniture/InventoryFurnitureView.tsx @@ -11,7 +11,7 @@ import { LimitedEditionCompactPlateView } from '../../../shared/limited-edition/ import { RarityLevelView } from '../../../shared/rarity-level/RarityLevelView'; import { RoomPreviewerView } from '../../../shared/room-previewer/RoomPreviewerView'; import { FurniCategory } from '../../common/FurniCategory'; -import { attemptItemPlacement } from '../../common/FurnitureUtilities'; +import { attemptItemPlacement, attemptPlaceMarketplaceOffer } from '../../common/FurnitureUtilities'; import { GroupItem } from '../../common/GroupItem'; import { useInventoryContext } from '../../context/InventoryContext'; import { InventoryFurnitureActions } from '../../reducers/InventoryFurnitureReducer'; @@ -127,10 +127,15 @@ export const InventoryFurnitureView: FC = props => { groupItem && { groupItem.name } - { !!roomSession && + { roomSession && attemptItemPlacement(groupItem) }> { LocalizeText('inventory.furni.placetoroom') } } + { (groupItem && groupItem.isSellable) && + attemptPlaceMarketplaceOffer(groupItem) }> + { LocalizeText('inventory.marketplace.sell') } + + } } diff --git a/src/views/inventory/views/trade/InventoryTradeView.tsx b/src/views/inventory/views/trade/InventoryTradeView.tsx index 2c96182c..8ab902e1 100644 --- a/src/views/inventory/views/trade/InventoryTradeView.tsx +++ b/src/views/inventory/views/trade/InventoryTradeView.tsx @@ -8,6 +8,8 @@ import { NitroCardGridItemView } from '../../../../layout/card/grid/item/NitroCa import { NitroCardGridView } from '../../../../layout/card/grid/NitroCardGridView'; import { NitroLayoutGridColumn } from '../../../../layout/grid/column/NitroLayoutGridColumn'; import { NitroLayoutGrid } from '../../../../layout/grid/NitroLayoutGrid'; +import { NotificationAlertType } from '../../../notification-center/common/NotificationAlertType'; +import { NotificationUtilities } from '../../../notification-center/common/NotificationUtilities'; import { FurniCategory } from '../../common/FurniCategory'; import { GroupItem } from '../../common/GroupItem'; import { IFurnitureItem } from '../../common/IFurnitureItem'; @@ -113,7 +115,7 @@ export const InventoryTradeView: FC = props => } else { - //this._notificationService.alert('${trading.items.too_many_items.desc}', '${trading.items.too_many_items.title}'); + NotificationUtilities.simpleAlert(LocalizeText('trading.items.too_many_items.desc'), NotificationAlertType.DEFAULT, null, null, LocalizeText('trading.items.too_many_items.title')); } }, [ groupItem, tradeData, canTradeItem ]);