More catalog updates

This commit is contained in:
Bill 2022-01-06 01:32:44 -05:00
parent 9a2a9df3b1
commit 35efd87188
22 changed files with 518 additions and 501 deletions

View File

@ -17,7 +17,7 @@ import { CatalogReducer, initialCatalog } from './reducers/CatalogReducer';
import { CatalogGiftView } from './views/gift/CatalogGiftView'; import { CatalogGiftView } from './views/gift/CatalogGiftView';
import { ACTIVE_PAGES, CatalogNavigationView } from './views/navigation/CatalogNavigationView'; import { ACTIVE_PAGES, CatalogNavigationView } from './views/navigation/CatalogNavigationView';
import { CatalogPageView } from './views/page/CatalogPageView'; import { CatalogPageView } from './views/page/CatalogPageView';
import { MarketplacePostOfferView } from './views/page/layout/marketplace/post-offer/MarketplacePostOfferView'; import { MarketplacePostOfferView } from './views/page/layout/marketplace/MarketplacePostOfferView';
export const CatalogView: FC<{}> = props => export const CatalogView: FC<{}> = props =>
{ {

View File

@ -1,7 +1,12 @@
import { CatalogPageMessageParser } from '@nitrots/nitro-renderer';
import { FC } from 'react'; import { FC } from 'react';
import { NitroLayoutFlexColumn } from '../../../../layout'; import { NitroLayoutFlexColumn } from '../../../../layout';
import { GetCatalogPageImage, GetCatalogPageText } from '../../common/CatalogUtilities'; import { GetCatalogPageImage, GetCatalogPageText } from '../../common/CatalogUtilities';
import { CatalogPageDetailsViewProps } from './CatalogPageDetailsView.types';
export interface CatalogPageDetailsViewProps
{
pageParser: CatalogPageMessageParser;
}
export const CatalogPageDetailsView: FC<CatalogPageDetailsViewProps> = props => export const CatalogPageDetailsView: FC<CatalogPageDetailsViewProps> = props =>
{ {

View File

@ -1,6 +0,0 @@
import { CatalogPageMessageParser } from '@nitrots/nitro-renderer';
export interface CatalogPageDetailsViewProps
{
pageParser: CatalogPageMessageParser;
}

View File

@ -28,5 +28,15 @@
} }
} }
@import './marketplace/marketplace-item/MarketplaceItemView'; .nitro-catalog-layout-marketplace-grid {
@import './marketplace/post-offer/MarketplacePostOfferView';
.layout-grid-item {
height: 75px !important;
max-height: 75px !important;
}
}
.nitro-catalog-layout-marketplace-post-offer {
width: $marketplace-post-offer-width;
height: $marketplace-post-offer-height;
}

View File

@ -6,8 +6,8 @@ import { CatalogLayouGuildCustomFurniView } from './guild-custom-furni/CatalogLa
import { CatalogLayouGuildForumView } from './guild-forum/CatalogLayoutGuildForumView'; import { CatalogLayouGuildForumView } from './guild-forum/CatalogLayoutGuildForumView';
import { CatalogLayouGuildFrontpageView } from './guild-frontpage/CatalogLayoutGuildFrontpageView'; import { CatalogLayouGuildFrontpageView } from './guild-frontpage/CatalogLayoutGuildFrontpageView';
import { CatalogLayoutInfoLoyaltyView } from './info-loyalty/CatalogLayoutInfoLoyaltyView'; import { CatalogLayoutInfoLoyaltyView } from './info-loyalty/CatalogLayoutInfoLoyaltyView';
import { CatalogLayoutMarketplaceOwnItemsView } from './marketplace/own-items/CatalogLayoutMarketplaceOwnItemsView'; import { CatalogLayoutMarketplaceOwnItemsView } from './marketplace/CatalogLayoutMarketplaceOwnItemsView';
import { CatalogLayoutMarketplacePublicItemsView } from './marketplace/public-items/CatalogLayoutMarketplacePublicItemsView'; import { CatalogLayoutMarketplacePublicItemsView } from './marketplace/CatalogLayoutMarketplacePublicItemsView';
import { CatalogLayoutPetView } from './pets/CatalogLayoutPetView'; import { CatalogLayoutPetView } from './pets/CatalogLayoutPetView';
import { CatalogLayoutPets2View } from './pets2/CatalogLayoutPets2View'; import { CatalogLayoutPets2View } from './pets2/CatalogLayoutPets2View';
import { CatalogLayoutPets3View } from './pets3/CatalogLayoutPets3View'; import { CatalogLayoutPets3View } from './pets3/CatalogLayoutPets3View';

View File

@ -0,0 +1,102 @@
import { FC, useCallback, useMemo } from 'react';
import { GetRoomEngine, LocalizeText } from '../../../../../../api';
import { Button } from '../../../../../../common/Button';
import { Column } from '../../../../../../common/Column';
import { LayoutGridItem } from '../../../../../../common/layout/LayoutGridItem';
import { LayoutImage } from '../../../../../../common/layout/LayoutImage';
import { Text } from '../../../../../../common/Text';
import { MarketplaceOfferData } from './common/MarketplaceOfferData';
import { MarketPlaceOfferState } from './common/MarketplaceOfferState';
export interface MarketplaceItemViewProps
{
offerData: MarketplaceOfferData;
type?: number;
onClick(offerData: MarketplaceOfferData): void;
}
export const OWN_OFFER = 1;
export const PUBLIC_OFFER = 2;
export const CatalogLayoutMarketplaceItemView: FC<MarketplaceItemViewProps> = props =>
{
const { offerData = null, type = PUBLIC_OFFER, onClick = null } = props;
const getImageUrlForOffer = useCallback( () =>
{
if(!offerData) return '';
switch(offerData.furniType)
{
case MarketplaceOfferData.TYPE_FLOOR:
return GetRoomEngine().getFurnitureFloorIconUrl(offerData.furniId);
case MarketplaceOfferData.TYPE_WALL:
return GetRoomEngine().getFurnitureWallIconUrl(offerData.furniId, offerData.extraData);
}
return '';
}, [offerData]);
const getMarketplaceOfferTitle = useMemo(() =>
{
if(!offerData) return '';
// desc
return LocalizeText(((offerData.furniType === 2) ? 'wallItem' : 'roomItem') + `.name.${ offerData.furniId }`);
}, [ offerData ]);
const offerTime = useCallback( () =>
{
if(!offerData) return '';
if(offerData.status === MarketPlaceOfferState.SOLD) return LocalizeText('catalog.marketplace.offer.sold');
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);
let text = minutes + ' ' + LocalizeText('catalog.marketplace.offer.minutes');
if(hours > 0)
{
text = hours + ' ' + LocalizeText('catalog.marketplace.offer.hours') + ' ' + text;
}
return LocalizeText('catalog.marketplace.offer.time_left', ['time'], [text] );
}, [offerData]);
return (
<LayoutGridItem center={ false } column={ false } alignItems="center" className="p-1">
<LayoutImage imageUrl={ getImageUrlForOffer() } fit={ false } style={ { width: 50, height: 50 } } />
<Column grow gap={ 0 }>
<Text fontWeight="bold">{ getMarketplaceOfferTitle }</Text>
{ (type === OWN_OFFER) &&
<>
<Text>{ LocalizeText('catalog.marketplace.offer.price_own_item', [ 'price' ], [ offerData.price.toString() ]) }</Text>
<Text>{ offerTime() }</Text>
</> }
{ (type === PUBLIC_OFFER) &&
<>
<Text>{ LocalizeText('catalog.marketplace.offer.price_public_item', ['price', 'average'], [offerData.price.toString(), offerData.averagePrice.toString() ]) }</Text>
<Text>{ LocalizeText('catalog.marketplace.offer_count', ['count'], [offerData.offerCount.toString()]) }</Text>
</> }
</Column>
<Column gap={ 1 }>
{ ((type === OWN_OFFER) && (offerData.status !== MarketPlaceOfferState.SOLD)) &&
<Button variant="secondary" size="sm" onClick={ () => onClick(offerData) }>
{ LocalizeText('catalog.marketplace.offer.pick') }
</Button> }
{ type === PUBLIC_OFFER &&
<>
<Button variant="secondary" size="sm" onClick={ () => onClick(offerData) }>
{ LocalizeText('buy') }
</Button>
<Button variant="secondary" size="sm" disabled>
{ LocalizeText('catalog.marketplace.view_more') }
</Button>
</> }
</Column>
</LayoutGridItem>
);
}

View File

@ -0,0 +1,115 @@
import { CancelMarketplaceOfferMessageComposer, GetMarketplaceOwnOffersMessageComposer, MarketplaceCancelOfferResultEvent, MarketplaceOwnOffersEvent, RedeemMarketplaceOfferCreditsMessageComposer } from '@nitrots/nitro-renderer';
import { FC, useCallback, useMemo, useState } from 'react';
import { LocalizeText } from '../../../../../../api';
import { Button } from '../../../../../../common/Button';
import { Column } from '../../../../../../common/Column';
import { Grid } from '../../../../../../common/Grid';
import { Text } from '../../../../../../common/Text';
import { BatchUpdates, CreateMessageHook, SendMessageHook, UseMountEffect } from '../../../../../../hooks';
import { NotificationAlertType } from '../../../../../../views/notification-center/common/NotificationAlertType';
import { NotificationUtilities } from '../../../../../../views/notification-center/common/NotificationUtilities';
import { CatalogLayoutProps } from '../CatalogLayout.types';
import { CatalogLayoutMarketplaceItemView, OWN_OFFER } from './CatalogLayoutMarketplaceItemView';
import { MarketplaceOfferData } from './common/MarketplaceOfferData';
import { MarketPlaceOfferState } from './common/MarketplaceOfferState';
export const CatalogLayoutMarketplaceOwnItemsView: FC<CatalogLayoutProps> = props =>
{
const [ creditsWaiting, setCreditsWaiting ] = useState(0);
const [ offers, setOffers ] = useState<MarketplaceOfferData[]>([]);
const onMarketPlaceOwnOffersEvent = useCallback((event: MarketplaceOwnOffersEvent) =>
{
const parser = event.getParser();
if(!parser) return;
const offers = parser.offers.map(offer =>
{
const newOffer = new MarketplaceOfferData(offer.offerId, offer.furniId, offer.furniType, offer.extraData, offer.stuffData, offer.price, offer.status, offer.averagePrice, offer.offerCount);
newOffer.timeLeftMinutes = offer.timeLeftMinutes;
return newOffer;
});
BatchUpdates(() =>
{
setCreditsWaiting(parser.creditsWaiting);
setOffers(offers);
});
}, []);
CreateMessageHook(MarketplaceOwnOffersEvent, onMarketPlaceOwnOffersEvent);
const onMarketplaceCancelOfferResultEvent = useCallback((event:MarketplaceCancelOfferResultEvent) =>
{
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(prevValue => prevValue.filter(value => (value.offerId !== parser.offerId)));
}, []);
CreateMessageHook(MarketplaceCancelOfferResultEvent, onMarketplaceCancelOfferResultEvent);
const soldOffers = useMemo(() =>
{
return offers.filter(value => (value.status === MarketPlaceOfferState.SOLD));
}, [ offers ]);
const redeemSoldOffers = useCallback(() =>
{
setOffers(prevValue =>
{
const idsToDelete = soldOffers.map(value => value.offerId);
return prevValue.filter(value => (idsToDelete.indexOf(value.offerId) === -1));
})
SendMessageHook(new RedeemMarketplaceOfferCreditsMessageComposer());
}, [ soldOffers ]);
const takeItemBack = (offerData: MarketplaceOfferData) =>
{
SendMessageHook(new CancelMarketplaceOfferMessageComposer(offerData.offerId));
};
UseMountEffect(() =>
{
SendMessageHook(new GetMarketplaceOwnOffersMessageComposer());
});
return (
<Column>
{ (creditsWaiting <= 0) &&
<Text center className="bg-muted rounded p-1">
{ LocalizeText('catalog.marketplace.redeem.no_sold_items') }
</Text> }
{ (creditsWaiting > 0) &&
<Column center gap={ 1 } className="bg-muted rounded p-2">
<Text>
{ LocalizeText('catalog.marketplace.redeem.get_credits', ['count', 'credits'], [ soldOffers.length.toString(), creditsWaiting.toString() ]) }
</Text>
<Button size="sm" className="mt-1" onClick={ redeemSoldOffers }>
{ LocalizeText('catalog.marketplace.offer.redeem') }
</Button>
</Column> }
<Column gap={ 1 } overflow="hidden">
<Text truncate shrink fontWeight="bold">
{ LocalizeText('catalog.marketplace.items_found', [ 'count' ], [ offers.length.toString() ]) }
</Text>
<Grid columnCount={ 1 } overflow="auto" className="nitro-catalog-layout-marketplace-grid">
{ (offers.length > 0) && offers.map(offer => <CatalogLayoutMarketplaceItemView key={ offer.offerId } offerData={ offer } type={ OWN_OFFER } onClick={ takeItemBack } />) }
</Grid>
</Column>
</Column>
);
}

View File

@ -1,17 +1,21 @@
import { BuyMarketplaceOfferMessageComposer, GetMarketplaceOffersMessageComposer, MarketplaceBuyOfferResultEvent, MarketPlaceOffersEvent } from '@nitrots/nitro-renderer'; import { BuyMarketplaceOfferMessageComposer, GetMarketplaceOffersMessageComposer, MarketplaceBuyOfferResultEvent, MarketPlaceOffersEvent } from '@nitrots/nitro-renderer';
import { FC, useCallback, useMemo, useState } from 'react'; import { FC, useCallback, useMemo, useState } from 'react';
import { LocalizeText } from '../../../../../../../api'; import { LocalizeText } from '../../../../../../api';
import { BatchUpdates, CreateMessageHook, SendMessageHook } from '../../../../../../../hooks'; import { Button } from '../../../../../../common/Button';
import { NitroCardGridView } from '../../../../../../../layout'; import { ButtonGroup } from '../../../../../../common/ButtonGroup';
import { NotificationAlertType } from '../../../../../../../views/notification-center/common/NotificationAlertType'; import { Column } from '../../../../../../common/Column';
import { NotificationUtilities } from '../../../../../../../views/notification-center/common/NotificationUtilities'; import { Grid } from '../../../../../../common/Grid';
import { GetCurrencyAmount } from '../../../../../../../views/purse/common/CurrencyHelper'; import { Text } from '../../../../../../common/Text';
import { CatalogLayoutProps } from '../../CatalogLayout.types'; import { BatchUpdates, CreateMessageHook, SendMessageHook } from '../../../../../../hooks';
import { IMarketplaceSearchOptions } from '../common/IMarketplaceSearchOptions'; import { NotificationAlertType } from '../../../../../../views/notification-center/common/NotificationAlertType';
import { MarketplaceOfferData } from '../common/MarketplaceOfferData'; import { NotificationUtilities } from '../../../../../../views/notification-center/common/NotificationUtilities';
import { MarketplaceSearchType } from '../common/MarketplaceSearchType'; import { GetCurrencyAmount } from '../../../../../../views/purse/common/CurrencyHelper';
import { MarketplaceItemView, PUBLIC_OFFER } from '../marketplace-item/MarketplaceItemView'; import { CatalogLayoutProps } from '../CatalogLayout.types';
import { SearchFormView } from './SearchFormView'; import { CatalogLayoutMarketplaceItemView, PUBLIC_OFFER } from './CatalogLayoutMarketplaceItemView';
import { SearchFormView } from './CatalogLayoutMarketplaceSearchFormView';
import { IMarketplaceSearchOptions } from './common/IMarketplaceSearchOptions';
import { MarketplaceOfferData } from './common/MarketplaceOfferData';
import { MarketplaceSearchType } from './common/MarketplaceSearchType';
const SORT_TYPES_VALUE = [1, 2]; const SORT_TYPES_VALUE = [1, 2];
const SORT_TYPES_ACTIVITY = [3, 4, 5, 6]; const SORT_TYPES_ACTIVITY = [3, 4, 5, 6];
@ -141,26 +145,30 @@ export const CatalogLayoutMarketplacePublicItemsView: FC<CatalogLayoutMarketplac
CreateMessageHook(MarketPlaceOffersEvent, onMarketPlaceOffersEvent); CreateMessageHook(MarketPlaceOffersEvent, onMarketPlaceOffersEvent);
CreateMessageHook(MarketplaceBuyOfferResultEvent, onMarketplaceBuyOfferResultEvent); CreateMessageHook(MarketplaceBuyOfferResultEvent, onMarketplaceBuyOfferResultEvent);
return (<> return (
<div className="btn-group" role="group"> <>
<button type="button" className={`btn btn-primary ${searchType === MarketplaceSearchType.BY_ACTIVITY ? 'active' : ''}`} onClick={() => setSearchType(MarketplaceSearchType.BY_ACTIVITY)}> <ButtonGroup>
<Button size="sm" active={ (searchType === MarketplaceSearchType.BY_ACTIVITY) } onClick={ () => setSearchType(MarketplaceSearchType.BY_ACTIVITY) }>
{ LocalizeText('catalog.marketplace.search_by_activity') } { LocalizeText('catalog.marketplace.search_by_activity') }
</button> </Button>
<button type="button" className={`btn btn-primary ${searchType === MarketplaceSearchType.BY_VALUE ? 'active' : ''}`} onClick={() => setSearchType(MarketplaceSearchType.BY_VALUE)}> <Button size="sm" active={ (searchType === MarketplaceSearchType.BY_VALUE) } onClick={ () => setSearchType(MarketplaceSearchType.BY_VALUE) }>
{ LocalizeText('catalog.marketplace.search_by_value') } { LocalizeText('catalog.marketplace.search_by_value') }
</button> </Button>
<button type="button" className={`btn btn-primary ${searchType === MarketplaceSearchType.ADVANCED ? 'active' : ''}`} onClick={() => setSearchType(MarketplaceSearchType.ADVANCED)}> <Button size="sm" active={ (searchType === MarketplaceSearchType.ADVANCED) } onClick={ () => setSearchType(MarketplaceSearchType.ADVANCED) }>
{ LocalizeText('catalog.marketplace.search_advanced') } { LocalizeText('catalog.marketplace.search_advanced') }
</button> </Button>
</div> </ButtonGroup>
<SearchFormView sortTypes={ getSortTypes } searchType={ searchType } onSearch={ requestOffers } />
<SearchFormView sortTypes={ getSortTypes } searchType={ searchType } onSearch={ requestOffers }/> <Column gap={ 1 } overflow="hidden">
<Text truncate shrink fontWeight="bold">
<div className='text-black'>{LocalizeText('catalog.marketplace.items_found', ['count'], [offers.size.toString()])}</div> { LocalizeText('catalog.marketplace.items_found', [ 'count' ], [ offers.size.toString() ]) }
<NitroCardGridView columns={1} className='text-black'> </Text>
<Grid columnCount={ 1 } className="nitro-catalog-layout-marketplace-grid" overflow="auto">
{ {
Array.from(offers.values()).map( (entry, index) => <MarketplaceItemView key={ index } offerData={ entry } type={ PUBLIC_OFFER } onClick={purchaseItem} />) Array.from(offers.values()).map( (entry, index) => <CatalogLayoutMarketplaceItemView key={ index } offerData={ entry } type={ PUBLIC_OFFER } onClick={purchaseItem} />)
} }
</NitroCardGridView> </Grid>
</>); </Column>
</>
);
} }

View File

@ -0,0 +1,76 @@
import { FC, useCallback, useEffect, useState } from 'react';
import { LocalizeText } from '../../../../../../api';
import { Button } from '../../../../../../common/Button';
import { Column } from '../../../../../../common/Column';
import { Flex } from '../../../../../../common/Flex';
import { Text } from '../../../../../../common/Text';
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 (
<Column gap={ 1 }>
<Flex alignItems="center" gap={ 1 }>
<Text className="col-3">{ LocalizeText('catalog.marketplace.sort_order') }</Text>
<select className="form-select form-select-sm" value={ sortType } onChange={ event => onSortTypeChange(parseInt(event.target.value)) }>
{ sortTypes.map(type => <option key={ type } value={ type }>{ LocalizeText(`catalog.marketplace.sort.${ type }`) }</option>) }
</select>
</Flex>
{ searchType === MarketplaceSearchType.ADVANCED &&
<>
<Flex alignItems="center" gap={ 1 }>
<Text className="col-3">{ LocalizeText('catalog.marketplace.search_name') }</Text>
<input className="form-control form-control-sm" type="text" value={ searchQuery } onChange={ event => setSearchQuery(event.target.value) }/>
</Flex>
<Flex alignItems="center" gap={ 1 }>
<Text className="col-3">{ LocalizeText('catalog.marketplace.search_price') }</Text>
<Flex fullWidth gap={ 1 }>
<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) } />
</Flex>
</Flex>
<Button variant="secondary" size="sm" className="mx-auto" onClick={ onClickSearch }>{ LocalizeText('generic.search') }</Button>
</> }
</Column>
);
}

View File

@ -0,0 +1,134 @@
import { ImageResult, MakeOfferMessageComposer, Vector3d } from '@nitrots/nitro-renderer';
import { FC, useCallback, useState } from 'react';
import { GetRoomEngine, LocalizeText } from '../../../../../../api';
import { Base } from '../../../../../../common/Base';
import { Button } from '../../../../../../common/Button';
import { Column } from '../../../../../../common/Column';
import { Grid } from '../../../../../../common/Grid';
import { LayoutImage } from '../../../../../../common/layout/LayoutImage';
import { Text } from '../../../../../../common/Text';
import { CatalogPostMarketplaceOfferEvent } from '../../../../../../events/catalog/CatalogPostMarketplaceOfferEvent';
import { BatchUpdates, SendMessageHook, useUiEvent } from '../../../../../../hooks';
import { NitroCardContentView, NitroCardHeaderView, NitroCardView } from '../../../../../../layout';
import { NotificationUtilities } from '../../../../../../views/notification-center/common/NotificationUtilities';
import { FurnitureItem } from '../../../../../inventory/common/FurnitureItem';
import { useCatalogContext } from '../../../../context/CatalogContext';
export const MarketplacePostOfferView : FC<{}> = props =>
{
const [ item, setItem ] = useState<FurnitureItem>(null);
const [ askingPrice, setAskingPrice ] = useState(0);
const { catalogState = null, dispatchCatalogState = null } = useCatalogContext();
const close = useCallback(() =>
{
BatchUpdates(() =>
{
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 || !item) return;
NotificationUtilities.confirm(LocalizeText('inventory.marketplace.confirm_offer.info', ['furniname', 'price'], [getFurniTitle(), askingPrice.toString()]), () =>
{
SendMessageHook(new MakeOfferMessageComposer(askingPrice, item.isWallItem ? 2 : 1, item.id));
setItem(null);
},
() => { setItem(null)}, null, null, LocalizeText('inventory.marketplace.confirm_offer.title'));
}, [askingPrice, getFurniTitle, item]);
return ( item &&
<NitroCardView className="nitro-catalog-layout-marketplace-post-offer" simple={ true }>
<NitroCardHeaderView headerText={ LocalizeText('inventory.marketplace.make_offer.title') } onCloseClick={ close } />
<NitroCardContentView overflow="hidden">
<Grid fullHeight>
<Column center className="bg-muted rounded p-2" size={ 4 } overflow="hidden">
<LayoutImage imageUrl={ getItemImage() } />
</Column>
<Column size={ 8 } justifyContent="between" overflow="hidden">
<Column grow gap={ 1 }>
<Text fontWeight="bold">{ getFurniTitle() }</Text>
<Text truncate shrink>{ getFurniDescription() }</Text>
</Column>
<Column overflow="auto">
<Text italics>
{ LocalizeText('inventory.marketplace.make_offer.expiration_info', ['time'], [catalogState.marketplaceConfiguration.offerTime.toString()]) }
</Text>
<div className="input-group has-validation">
<input className="form-control form-control-sm" type="number" min={ 0 } value={ askingPrice } onChange={ event => setAskingPrice(event.target.valueAsNumber) } placeholder={ LocalizeText('inventory.marketplace.make_offer.price_request') } />
{ ((askingPrice < catalogState.marketplaceConfiguration.minimumPrice) || isNaN(askingPrice)) &&
<Base className="invalid-feedback d-block">
{ LocalizeText('inventory.marketplace.make_offer.min_price', [ 'minprice' ], [ catalogState.marketplaceConfiguration.minimumPrice.toString() ]) }
</Base> }
{ ((askingPrice > catalogState.marketplaceConfiguration.maximumPrice) && !isNaN(askingPrice)) &&
<Base className="invalid-feedback d-block">
{ LocalizeText('inventory.marketplace.make_offer.max_price', [ 'maxprice' ], [ catalogState.marketplaceConfiguration.maximumPrice.toString() ]) }
</Base> }
{ (!((askingPrice < catalogState.marketplaceConfiguration.minimumPrice) || (askingPrice > catalogState.marketplaceConfiguration.maximumPrice) || isNaN(askingPrice))) &&
<Base className="invalid-feedback d-block">
{ LocalizeText('inventory.marketplace.make_offer.final_price', [ 'commission', 'finalprice' ], [ catalogState.marketplaceConfiguration.commission.toString(), (askingPrice + catalogState.marketplaceConfiguration.commission).toString() ]) }
</Base> }
</div>
<Button size="sm" disabled={ ((askingPrice < catalogState.marketplaceConfiguration.minimumPrice) || (askingPrice > catalogState.marketplaceConfiguration.maximumPrice) || isNaN(askingPrice)) } onClick={ postItem }>
{ LocalizeText('inventory.marketplace.make_offer.post') }
</Button>
</Column>
</Column>
</Grid>
</NitroCardContentView>
</NitroCardView>
)
}

View File

@ -1,4 +0,0 @@
.marketplace-item {
max-height: 70px;
height: 70px;
}

View File

@ -1,101 +0,0 @@
import { FC, useCallback } from 'react';
import { GetRoomEngine, LocalizeText } from '../../../../../../../api';
import { NitroCardGridItemView } from '../../../../../../../layout';
import { MarketplaceOfferData } from '../common/MarketplaceOfferData';
import { MarketPlaceOfferState } from '../common/MarketplaceOfferState';
export const OWN_OFFER = 1;
export const PUBLIC_OFFER = 2;
export interface MarketplaceItemViewProps
{
offerData: MarketplaceOfferData;
type?: number;
onClick(offerData: MarketplaceOfferData): void;
}
export const MarketplaceItemView: FC<MarketplaceItemViewProps> = props =>
{
const { offerData = null, type = PUBLIC_OFFER, onClick = null } = props;
const getImageUrlForOffer = useCallback( () =>
{
if(!offerData) return '';
switch(offerData.furniType)
{
case MarketplaceOfferData.TYPE_FLOOR:
return GetRoomEngine().getFurnitureFloorIconUrl(offerData.furniId);
case MarketplaceOfferData.TYPE_WALL:
return GetRoomEngine().getFurnitureWallIconUrl(offerData.furniId, offerData.extraData);
}
return '';
}, [offerData]);
const getMarketplaceOfferTitle = useCallback(() =>
{
if(!offerData) return '';
const localizationKey = offerData.furniType === 2 ? 'wallItem.name.' + offerData.furniId: 'roomItem.name.' + offerData.furniId;
return LocalizeText(localizationKey);
}, [offerData]);
const getMarketplaceOfferDescription = useCallback( () =>
{
if(!offerData) return '';
const localizationKey = offerData.furniType === 2 ? 'wallItem.desc.' + offerData.furniId : 'roomItem.desc.' + offerData.furniId;
return LocalizeText(localizationKey);
}, [offerData]);
const offerTime = useCallback( () =>
{
if(!offerData) return '';
if(offerData.status === MarketPlaceOfferState.SOLD) return LocalizeText('catalog.marketplace.offer.sold');
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);
let text = minutes + ' ' + LocalizeText('catalog.marketplace.offer.minutes');
if(hours > 0)
{
text = hours + ' ' + LocalizeText('catalog.marketplace.offer.hours') + ' ' + text;
}
return LocalizeText('catalog.marketplace.offer.time_left', ['time'], [text] );
}, [offerData]);
return (
<NitroCardGridItemView className='w-100 marketplace-item align-items-center'>
<img src={ getImageUrlForOffer() } className='mx-3' alt='' />
<div className='h-100 flex-grow-1 justify-content-center '>
<div className='fw-bold'>{getMarketplaceOfferTitle()}</div>
<div className='fst-italic fs-6'>{getMarketplaceOfferDescription()}</div>
{ type === OWN_OFFER && <>
<div>{ LocalizeText('catalog.marketplace.offer.price_own_item', ['price'], [offerData.price.toString()])}</div>
<div>{ offerTime() }</div>
</>
}
{ 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_count', ['count'], [offerData.offerCount.toString()]) }</div>
</>
}
</div>
<div className='btn-group-vertical mx-1 gap-2'>
{ (type === OWN_OFFER && offerData.status !== MarketPlaceOfferState.SOLD) && <button className='btn btn-secondary btn-sm' onClick={ () => onClick(offerData) }>{ LocalizeText('catalog.marketplace.offer.pick') }</button>}
{ type === PUBLIC_OFFER && <>
<button className='btn btn-secondary btn-sm' onClick={ () => onClick(offerData) } >{ LocalizeText('buy') }</button>
<button className='btn btn-secondary btn-sm' disabled={true}>{ LocalizeText('catalog.marketplace.view_more') }</button>
</>}
</div>
</NitroCardGridItemView>)
}

View File

@ -1,120 +0,0 @@
import { CancelMarketplaceOfferMessageComposer, 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 '../../../../../../../views/notification-center/common/NotificationAlertType';
import { NotificationUtilities } from '../../../../../../../views/notification-center/common/NotificationUtilities';
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
{
}
export const CatalogLayoutMarketplaceOwnItemsView: FC<CatalogLayoutMarketplaceOwnItemsViewProps> = props =>
{
const [ creditsWaiting, setCreditsWaiting ] = useState(0);
const [ offers, setOffers ] = useState(new Map<number, MarketplaceOfferData>());
UseMountEffect(() =>
{
SendMessageHook(new GetMarketplaceOwnOffersMessageComposer());
});
const onMarketPlaceOwnOffersEvent = useCallback((event: MarketplaceOwnOffersEvent) =>
{
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(() =>
{
setCreditsWaiting(parser.creditsWaiting);
setOffers(latestOffers);
});
}, []);
const onMarketplaceCancelOfferResultEvent = useCallback((event:MarketplaceCancelOfferResultEvent) =>
{
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(() =>
{
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());
}, []);
const takeItemBack = useCallback( (offerData: MarketplaceOfferData) =>
{
SendMessageHook(new CancelMarketplaceOfferMessageComposer(offerData.offerId));
}, []);
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.get_credits', ['count', 'credits'], [Array.from(offers.values()).filter(value => value.status === MarketPlaceOfferState.SOLD).length.toString(), creditsWaiting.toString()])}</NitroLayoutBase>}
<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>
<NitroCardGridView columns={1} className='text-black'>
{
Array.from(offers.values()).map( (entry, index) => <MarketplaceItemView key={ index } offerData={ entry } type={ OWN_OFFER } onClick={takeItemBack} />)
}
</NitroCardGridView>
</>
);
}

View File

@ -1,12 +0,0 @@
.nitro-marketplace-post-offer {
width: 300px;
height: 365px;
.item-image-container {
width: 75px;
height: 75px;
background-repeat: no-repeat;
background-position: center;
overflow: hidden;
}
}

View File

@ -1,117 +0,0 @@
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 { NotificationUtilities } from '../../../../../../../views/notification-center/common/NotificationUtilities';
import { FurnitureItem } from '../../../../../../inventory/common/FurnitureItem';
import { useCatalogContext } from '../../../../../context/CatalogContext';
export const MarketplacePostOfferView : FC<{}> = props =>
{
const [ item, setItem ] = useState<FurnitureItem>(null);
const [ askingPrice, setAskingPrice ] = useState(0);
const { catalogState = null, dispatchCatalogState = null } = useCatalogContext();
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 || !item) return;
NotificationUtilities.confirm(LocalizeText('inventory.marketplace.confirm_offer.info', ['furniname', 'price'], [getFurniTitle(), askingPrice.toString()]), () =>
{
SendMessageHook(new MakeOfferMessageComposer(askingPrice, item.isWallItem ? 2 : 1, item.id));
setItem(null);
},
() => { setItem(null)}, null, null, LocalizeText('inventory.marketplace.confirm_offer.title'));
}, [askingPrice, getFurniTitle, item]);
return ( item &&
<NitroCardView uniqueKey="catalog-mp-post-offer" className="nitro-marketplace-post-offer" simple={ true }>
<NitroCardHeaderView headerText={ LocalizeText('inventory.marketplace.make_offer.title') } onCloseClick={ close } />
<NitroCardContentView className="text-black">
<NitroLayoutFlex>
<div className="item-image-container mx-3" style={{ backgroundImage: `url(${getItemImage()})` }} />
<div className='h-100 flex-grow-1 justify-content-center '>
<div className='fw-bold'>{getFurniTitle()}</div>
<div className='fs-6'>{getFurniDescription()}</div>
</div>
</NitroLayoutFlex>
<div className='mx-2 fst-italic text-break mb-3'>
{ LocalizeText('inventory.marketplace.make_offer.expiration_info', ['time'], [catalogState.marketplaceConfiguration.offerTime.toString()]) }
</div>
<div className="d-flex flex-row text-black mb-3">
<div className="mr-2 align-self-center fw-bold" style={ { whiteSpace: 'nowrap' } }>{ LocalizeText('inventory.marketplace.make_offer.price_request') }</div>
<input className="form-control form-control-sm" type="number" min={0} value={ askingPrice } onChange={ event => setAskingPrice(event.target.valueAsNumber) } />
</div>
<div className="alert alert-light" role="alert">
{ (askingPrice < catalogState.marketplaceConfiguration.minimumPrice || isNaN(askingPrice)) && LocalizeText('inventory.marketplace.make_offer.min_price', ['minprice'], [catalogState.marketplaceConfiguration.minimumPrice.toString()]) }
{ askingPrice > catalogState.marketplaceConfiguration.maximumPrice && !isNaN(askingPrice) &&
LocalizeText('inventory.marketplace.make_offer.max_price', ['maxprice'], [catalogState.marketplaceConfiguration.maximumPrice.toString()])
}
{ !(askingPrice < catalogState.marketplaceConfiguration.minimumPrice || askingPrice > catalogState.marketplaceConfiguration.maximumPrice || isNaN(askingPrice)) && LocalizeText('inventory.marketplace.make_offer.final_price', ['commission', 'finalprice'], [catalogState.marketplaceConfiguration.commission.toString(), (askingPrice + catalogState.marketplaceConfiguration.commission).toString()])}
</div>
<div className="btn-group btn-group-sm mt-3" role="group">
<button className='btn btn-primary' disabled={askingPrice < catalogState.marketplaceConfiguration.minimumPrice || askingPrice > catalogState.marketplaceConfiguration.maximumPrice || isNaN(askingPrice)} onClick={ postItem }>
{ LocalizeText('inventory.marketplace.make_offer.post') }
</button>
</div>
</NitroCardContentView>
</NitroCardView>
)
}

View File

@ -1,70 +0,0 @@
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>
</>
}
</>);
}

View File

@ -67,8 +67,8 @@ export const CatalogLayoutPetPurchaseView: FC<CatalogLayoutPetPurchaseViewProps>
</Column> </Column>
</Flex> </Flex>
<Column gap={ 1 }> <Column gap={ 1 }>
<CatalogPurchaseButtonView className="btn-sm w-100" offer={ offer } pageId={ pageId } extra={ extraData } quantity={ 1 } isPurchaseAllowed={ nameApproved } beforePurchase={ beforePurchase } /> <CatalogPurchaseButtonView offer={ offer } pageId={ pageId } extra={ extraData } quantity={ 1 } isPurchaseAllowed={ nameApproved } beforePurchase={ beforePurchase } />
{ offer.giftable && <CatalogPurchaseGiftButtonView className="btn-sm w-100 mt-1" offer={ offer } pageId={ pageId } extra={ extraData } disabled={ nameApproved } /> } { offer.giftable && <CatalogPurchaseGiftButtonView offer={ offer } pageId={ pageId } extra={ extraData } disabled={ nameApproved } /> }
</Column> </Column>
</Column> </Column>
); );

View File

@ -117,6 +117,6 @@ export const CatalogPurchaseButtonView: FC<CatalogPurchaseButtonViewProps> = pro
return <Button variant="danger" size="sm" disabled { ...rest }>{ LocalizeText('generic.failed') }</Button>; return <Button variant="danger" size="sm" disabled { ...rest }>{ LocalizeText('generic.failed') }</Button>;
case CatalogPurchaseState.NONE: case CatalogPurchaseState.NONE:
default: default:
return <Button variant="success" size="sm" onClick={ event => setPurchaseState(CatalogPurchaseState.CONFIRM) }>{ LocalizeText('buy') }</Button> return <Button variant="success" size="sm" onClick={ event => setPurchaseState(CatalogPurchaseState.CONFIRM) } { ...rest }>{ LocalizeText('buy') }</Button>
} }
} }

View File

@ -21,5 +21,5 @@ export const CatalogPurchaseGiftButtonView: FC<CatalogPurchaseGiftButtonViewProp
dispatchUiEvent(new CatalogInitGiftEvent(pageId, offer.offerId, extra)); dispatchUiEvent(new CatalogInitGiftEvent(pageId, offer.offerId, extra));
} }
return <Button variant="secondary" onClick={ initGift } { ...rest }>{ LocalizeText('catalog.purchase_confirmation.gift') }</Button>; return <Button variant="secondary" size="sm" onClick={ initGift } { ...rest }>{ LocalizeText('catalog.purchase_confirmation.gift') }</Button>;
} }

View File

@ -82,8 +82,8 @@ export const CatalogPurchaseView: FC<CatalogPurchaseViewProps> = props =>
</Column> </Column>
</Flex> </Flex>
<Column gap={ 1 }> <Column gap={ 1 }>
<CatalogPurchaseButtonView className="btn-sm w-100" offer={ offer } pageId={ pageId } extra={ extraData } quantity={ quantity } disabled={ disabled } /> <CatalogPurchaseButtonView offer={ offer } pageId={ pageId } extra={ extraData } quantity={ quantity } disabled={ disabled } />
{ offer.giftable && <CatalogPurchaseGiftButtonView className="btn-sm w-100 mt-1" offer={ offer } pageId={ pageId } extra={ extraData } disabled={ disabled } /> } { offer.giftable && <CatalogPurchaseGiftButtonView offer={ offer } pageId={ pageId } extra={ extraData } disabled={ disabled } /> }
</Column> </Column>
</Column> </Column>
); );

View File

@ -1,9 +1,16 @@
import { FC } from 'react'; import { Dispatch, FC, SetStateAction } from 'react';
import { LocalizeText } from '../../../../api'; import { LocalizeText } from '../../../../api';
import { NitroLayoutButton, NitroLayoutFlex } from '../../../../layout'; import { Base } from '../../../../common/Base';
import { NitroLayoutBase } from '../../../../layout/base'; import { Button } from '../../../../common/Button';
import { Flex } from '../../../../common/Flex';
import { Text } from '../../../../common/Text';
import { useCatalogContext } from '../../context/CatalogContext'; import { useCatalogContext } from '../../context/CatalogContext';
import { CatalogSelectGroupViewProps } from './CatalogSelectGroupView.types';
export interface CatalogSelectGroupViewProps
{
selectedGroupIndex: number;
setSelectedGroupIndex: Dispatch<SetStateAction<number>>;
}
export const CatalogSelectGroupView: FC<CatalogSelectGroupViewProps> = props => export const CatalogSelectGroupView: FC<CatalogSelectGroupViewProps> = props =>
{ {
@ -14,27 +21,24 @@ export const CatalogSelectGroupView: FC<CatalogSelectGroupViewProps> = props =>
if(!groups || !groups.length) if(!groups || !groups.length)
{ {
return ( return (
<NitroLayoutBase className="flex-grow-1 bg-muted rounded text-black text-center p-1"> <Text grow center className="bg-muted rounded p-1">
{ LocalizeText('catalog.guild_selector.members_only') } { LocalizeText('catalog.guild_selector.members_only') }
<NitroLayoutButton className="mt-1" variant="primary" size="sm"> <Button variant="primary" size="sm" className="mt-1">
{ LocalizeText('catalog.guild_selector.find_groups') } { LocalizeText('catalog.guild_selector.find_groups') }
</NitroLayoutButton> </Button>
</NitroLayoutBase> </Text>
); );
} }
return ( return (
<NitroLayoutFlex> <Flex gap={ 1 }>
<NitroLayoutFlex className="rounded border me-1" overflow="hidden"> <Flex overflow="hidden" className="rounded border">
<NitroLayoutBase className="h-100" style={ { width: '20px', backgroundColor: '#' + groups[selectedGroupIndex].colorA } } /> <Base fullHeight style={ { width: '20px', backgroundColor: '#' + groups[selectedGroupIndex].colorA } } />
<NitroLayoutBase className="h-100" style={ { width: '20px', backgroundColor: '#' + groups[selectedGroupIndex].colorB } } /> <Base fullHeight style={ { width: '20px', backgroundColor: '#' + groups[selectedGroupIndex].colorB } } />
</NitroLayoutFlex> </Flex>
<select className="form-select form-select-sm" value={ selectedGroupIndex } onChange={ event => setSelectedGroupIndex(parseInt(event.target.value)) }> <select className="form-select form-select-sm" value={ selectedGroupIndex } onChange={ event => setSelectedGroupIndex(parseInt(event.target.value)) }>
{ groups.map((group, index) => { groups.map((group, index) => <option key={ index } value={ index }>{ group.groupName }</option>) }
{
return <option key={ index } value={ index }>{ group.groupName }</option>;
}) }
</select> </select>
</NitroLayoutFlex> </Flex>
); );
} }

View File

@ -1,7 +0,0 @@
import { Dispatch, SetStateAction } from 'react';
export interface CatalogSelectGroupViewProps
{
selectedGroupIndex: number;
setSelectedGroupIndex: Dispatch<SetStateAction<number>>;
}