Catalog updates

This commit is contained in:
Bill 2022-01-05 22:07:33 -05:00
parent 43867f9f38
commit c1e9576050
134 changed files with 1321 additions and 1464 deletions

View File

@ -7,13 +7,12 @@ import { CatalogPurchasedEvent } from '../../events/catalog/CatalogPurchasedEven
import { CatalogPurchaseSoldOutEvent } from '../../events/catalog/CatalogPurchaseSoldOutEvent'; import { CatalogPurchaseSoldOutEvent } from '../../events/catalog/CatalogPurchaseSoldOutEvent';
import { dispatchUiEvent } from '../../hooks/events/ui/ui-event'; import { dispatchUiEvent } from '../../hooks/events/ui/ui-event';
import { CreateMessageHook } from '../../hooks/messages/message-event'; import { CreateMessageHook } from '../../hooks/messages/message-event';
import { CatalogMessageHandlerProps } from './CatalogMessageHandler.types';
import { CatalogPetPalette } from './common/CatalogPetPalette'; import { CatalogPetPalette } from './common/CatalogPetPalette';
import { SubscriptionInfo } from './common/SubscriptionInfo'; import { SubscriptionInfo } from './common/SubscriptionInfo';
import { useCatalogContext } from './context/CatalogContext'; import { useCatalogContext } from './context/CatalogContext';
import { CatalogActions } from './reducers/CatalogReducer'; import { CatalogActions } from './reducers/CatalogReducer';
export const CatalogMessageHandler: FC<CatalogMessageHandlerProps> = props => export const CatalogMessageHandler: FC<{}> = props =>
{ {
const { catalogState = null, dispatchCatalogState = null } = useCatalogContext(); const { catalogState = null, dispatchCatalogState = null } = useCatalogContext();

View File

@ -9,7 +9,7 @@ import { useUiEvent } from '../../hooks/events/ui/ui-event';
import { SendMessageHook } from '../../hooks/messages/message-event'; import { SendMessageHook } from '../../hooks/messages/message-event';
import { NitroCardContentView, NitroCardHeaderView, NitroCardTabsItemView, NitroCardTabsView, NitroCardView } from '../../layout'; import { NitroCardContentView, NitroCardHeaderView, NitroCardTabsItemView, NitroCardTabsView, NitroCardView } from '../../layout';
import { CatalogMessageHandler } from './CatalogMessageHandler'; import { CatalogMessageHandler } from './CatalogMessageHandler';
import { CatalogMode, CatalogViewProps } from './CatalogView.types'; import { CatalogMode } from './common/CatalogMode';
import { BuildCatalogPageTree } from './common/CatalogUtilities'; import { BuildCatalogPageTree } from './common/CatalogUtilities';
import { CatalogContextProvider } from './context/CatalogContext'; import { CatalogContextProvider } from './context/CatalogContext';
import { CatalogReducer, initialCatalog } from './reducers/CatalogReducer'; import { CatalogReducer, initialCatalog } from './reducers/CatalogReducer';
@ -18,7 +18,7 @@ import { ACTIVE_PAGES, CatalogNavigationView } from './views/navigation/CatalogN
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/post-offer/MarketplacePostOfferView';
export const CatalogView: FC<CatalogViewProps> = props => export const CatalogView: FC<{}> = props =>
{ {
const [ isVisible, setIsVisible ] = useState(false); const [ isVisible, setIsVisible ] = useState(false);
const [ roomPreviewer, setRoomPreviewer ] = useState<RoomPreviewer>(null); const [ roomPreviewer, setRoomPreviewer ] = useState<RoomPreviewer>(null);
@ -215,10 +215,10 @@ export const CatalogView: FC<CatalogViewProps> = props =>
<NitroCardContentView> <NitroCardContentView>
<Grid> <Grid>
{ currentNavigationPage && !navigationHidden && { currentNavigationPage && !navigationHidden &&
<Column size={ 3 }> <Column size={ 3 } overflow="hidden">
<CatalogNavigationView page={ currentNavigationPage } pendingTree={ pendingTree } setPendingTree={ setPendingTree } /> <CatalogNavigationView page={ currentNavigationPage } pendingTree={ pendingTree } setPendingTree={ setPendingTree } />
</Column> } </Column> }
<Column size={ (navigationHidden ? 12 : 9) }> <Column size={ (navigationHidden ? 12 : 9) } overflow="hidden">
<CatalogPageView roomPreviewer={ roomPreviewer } /> <CatalogPageView roomPreviewer={ roomPreviewer } />
</Column> </Column>
</Grid> </Grid>

View File

@ -1,8 +1,3 @@
export interface CatalogViewProps
{
}
export class CatalogMode export class CatalogMode
{ {
public static MODE_NORMAL: string = 'NORMAL'; public static MODE_NORMAL: string = 'NORMAL';

View File

@ -0,0 +1,10 @@
export class CatalogPurchaseState
{
public static NONE = 0;
public static CONFIRM = 1;
public static PURCHASE = 2;
public static NO_CREDITS = 3;
public static NO_POINTS = 4;
public static SOLD_OUT = 5;
public static FAILED = 6;
}

View File

@ -1,4 +1,10 @@
@import './catalog-icon/CatalogIconView'; .catalog-icon-image {
width: 20px;
height: 20px;
min-width: 20px;
min-height: 20px;
}
@import './gift/CatalogGiftView'; @import './gift/CatalogGiftView';
@import './navigation/CatalogNavigationView'; @import './navigation/CatalogNavigationView';
@import './page/CatalogPageView'; @import './page/CatalogPageView';

View File

@ -0,0 +1,20 @@
import { FC, useMemo } from 'react';
import { GetConfiguration } from '../../../../api';
import { LayoutImage } from '../../../../common/layout/LayoutImage';
export interface CatalogIconViewProps
{
icon: number;
}
export const CatalogIconView: FC<CatalogIconViewProps> = props =>
{
const { icon = 0 } = props;
const getIconUrl = useMemo(() =>
{
return ((GetConfiguration<string>('catalog.asset.icon.url')).replace('%name%', icon.toString()));
}, [ icon ]);
return <LayoutImage imageUrl={ getIconUrl } fit={ false } className="catalog-icon-image" />;
}

View File

@ -3,8 +3,8 @@ import { FC, useCallback, useRef } from 'react';
import { GetRoomEngine } from '../../../../api'; import { GetRoomEngine } from '../../../../api';
import { CatalogEvent } from '../../../../events'; import { CatalogEvent } from '../../../../events';
import { useUiEvent } from '../../../../hooks'; import { useUiEvent } from '../../../../hooks';
import { RoomPreviewerView } from '../../../shared/room-previewer/RoomPreviewerView'; import { RoomPreviewerView } from '../../../../views/shared/room-previewer/RoomPreviewerView';
import { RoomPreviewerViewProps } from '../../../shared/room-previewer/RoomPreviewerView.types'; import { RoomPreviewerViewProps } from '../../../../views/shared/room-previewer/RoomPreviewerView.types';
export const CatalogRoomPreviewerView: FC<RoomPreviewerViewProps> = props => export const CatalogRoomPreviewerView: FC<RoomPreviewerViewProps> = props =>
{ {

View File

@ -0,0 +1,241 @@
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { PurchaseFromCatalogAsGiftComposer } from '@nitrots/nitro-renderer';
import classNames from 'classnames';
import { FC, useCallback, useEffect, useMemo, useState } from 'react';
import { GetSessionDataManager, LocalizeText } from '../../../../api';
import { Button } from '../../../../common/Button';
import { ButtonGroup } from '../../../../common/ButtonGroup';
import { Column } from '../../../../common/Column';
import { Flex } from '../../../../common/Flex';
import { FormGroup } from '../../../../common/FormGroup';
import { Text } from '../../../../common/Text';
import { CatalogEvent } from '../../../../events';
import { CatalogInitGiftEvent } from '../../../../events/catalog/CatalogInitGiftEvent';
import { BatchUpdates, SendMessageHook, useUiEvent } from '../../../../hooks';
import { NitroCardContentView, NitroCardHeaderView, NitroCardView, NitroLayoutGiftCardView } from '../../../../layout';
import { CurrencyIcon } from '../../../../views/shared/currency-icon/CurrencyIcon';
import { FurniImageView } from '../../../../views/shared/furni-image/FurniImageView';
import { useCatalogContext } from '../../context/CatalogContext';
export const CatalogGiftView: FC<{}> = props =>
{
const [ isVisible, setIsVisible ] = useState<boolean>(false);
const [ pageId, setPageId ] = useState<number>(0);
const [ offerId, setOfferId ] = useState<number>(0);
const [ receiverName, setReceiverName ] = useState<string>('');
const [ showMyFace, setShowMyFace ] = useState<boolean>(true);
const [ message, setMessage ] = useState<string>('');
const [ colors, setColors ] = useState<{ id: number, color: string }[]>([]);
const [ selectedBoxIndex, setSelectedBoxIndex ] = useState<number>(0);
const [ selectedRibbonIndex, setSelectedRibbonIndex ] = useState<number>(0);
const [ selectedColorId, setSelectedColorId ] = useState<number>(0);
const [ maxBoxIndex, setMaxBoxIndex ] = useState<number>(0);
const [ maxRibbonIndex, setMaxRibbonIndex ] = useState<number>(0);
const [ receiverNotFound, setReceiverNotFound ] = useState<boolean>(false);
const { catalogState = null } = useCatalogContext();
const { giftConfiguration = null } = catalogState;
const close = useCallback(() =>
{
BatchUpdates(() =>
{
setIsVisible(false);
setPageId(0);
setOfferId(0);
setReceiverName('');
setShowMyFace(true);
setMessage('');
setSelectedBoxIndex(0);
setSelectedRibbonIndex(0);
if(colors.length) setSelectedColorId(colors[0].id);
});
}, [ colors ]);
const onCatalogEvent = useCallback((event: CatalogEvent) =>
{
switch(event.type)
{
case CatalogEvent.PURCHASE_SUCCESS:
close();
return;
case CatalogEvent.INIT_GIFT:
const castedEvent = (event as CatalogInitGiftEvent);
BatchUpdates(() =>
{
close();
setPageId(castedEvent.pageId);
setOfferId(castedEvent.offerId);
setIsVisible(true);
});
return;
case CatalogEvent.GIFT_RECEIVER_NOT_FOUND:
setReceiverNotFound(true);
return;
}
}, [ close ]);
useUiEvent(CatalogEvent.PURCHASE_SUCCESS, onCatalogEvent);
useUiEvent(CatalogEvent.INIT_GIFT, onCatalogEvent);
useUiEvent(CatalogEvent.GIFT_RECEIVER_NOT_FOUND, onCatalogEvent);
const isBoxDefault = useMemo(() =>
{
return giftConfiguration ? (giftConfiguration.defaultStuffTypes.findIndex(s => (s === giftConfiguration.boxTypes[selectedBoxIndex])) > -1) : true;
}, [ giftConfiguration, selectedBoxIndex ]);
const extraData = useMemo(() =>
{
if(!giftConfiguration) return '';
return ((giftConfiguration.boxTypes[selectedBoxIndex] * 1000) + giftConfiguration.ribbonTypes[selectedRibbonIndex]).toString();
}, [ giftConfiguration, selectedBoxIndex, selectedRibbonIndex ]);
const isColorable = useMemo(() =>
{
if(!giftConfiguration) return false;
const boxType = giftConfiguration.boxTypes[selectedBoxIndex];
return (boxType === 8 || (boxType >= 3 && boxType <= 6)) ? false : true;
}, [ giftConfiguration, selectedBoxIndex ]);
const handleAction = useCallback((action: string) =>
{
switch(action)
{
case 'prev_box':
setSelectedBoxIndex(value => (value === 0 ? maxBoxIndex : value - 1));
return;
case 'next_box':
setSelectedBoxIndex(value => (value === maxBoxIndex ? 0 : value + 1));
return;
case 'prev_ribbon':
setSelectedRibbonIndex(value => (value === 0 ? maxRibbonIndex : value - 1));
return;
case 'next_ribbon':
setSelectedRibbonIndex(value => (value === maxRibbonIndex ? 0 : value + 1));
return;
case 'buy':
if(!receiverName || (receiverName.length === 0))
{
setReceiverNotFound(true);
return;
}
SendMessageHook(new PurchaseFromCatalogAsGiftComposer(pageId, offerId, extraData, receiverName, message, selectedColorId, selectedBoxIndex, selectedRibbonIndex, showMyFace));
return;
}
}, [ extraData, maxBoxIndex, maxRibbonIndex, message, offerId, pageId, receiverName, selectedBoxIndex, selectedColorId, selectedRibbonIndex, showMyFace ]);
useEffect(() =>
{
setReceiverNotFound(false);
}, [ receiverName ]);
useEffect(() =>
{
if(!giftConfiguration) return;
const newColors: { id: number, color: string }[] = [];
for(const colorId of giftConfiguration.stuffTypes)
{
const giftData = GetSessionDataManager().getFloorItemData(colorId);
if(!giftData) continue;
if(giftData.colors && giftData.colors.length > 0) newColors.push({ id: colorId, color: `#${giftData.colors[0].toString(16)}` });
}
BatchUpdates(() =>
{
setMaxBoxIndex(giftConfiguration.boxTypes.length - 1);
setMaxRibbonIndex(giftConfiguration.ribbonTypes.length - 1);
if(newColors.length)
{
setSelectedColorId(newColors[0].id);
setColors(newColors);
}
});
}, [ giftConfiguration ]);
if(!giftConfiguration || !giftConfiguration.isEnabled || !isVisible) return null;
const boxName = 'catalog.gift_wrapping_new.box.' + (isBoxDefault ? 'default' : selectedBoxIndex);
const ribbonName = `catalog.gift_wrapping_new.ribbon.${ selectedRibbonIndex }`;
const priceText = 'catalog.gift_wrapping_new.' + (isBoxDefault ? 'freeprice' : 'price');
return (
<NitroCardView uniqueKey="catalog-gift" className="nitro-catalog-gift" simple={ true }>
<NitroCardHeaderView headerText={ LocalizeText('catalog.gift_wrapping.title') } onCloseClick={ close } />
<NitroCardContentView className="text-black">
<FormGroup column>
<Text>{ LocalizeText('catalog.gift_wrapping.receiver') }</Text>
<input type="text" className={ 'form-control form-control-sm' + classNames({ ' is-invalid': receiverNotFound }) } value={ receiverName } onChange={ (e) => setReceiverName(e.target.value) } />
{ receiverNotFound && <div className="invalid-feedback">{ LocalizeText('catalog.gift_wrapping.receiver_not_found.title') }</div> }
</FormGroup>
<NitroLayoutGiftCardView figure={ GetSessionDataManager().figure } userName={ GetSessionDataManager().userName } message={ message } editable={ true } onChange={ (value) => setMessage(value) } />
<div className="form-check">
<input className="form-check-input" type="checkbox" name="showMyFace" checked={ showMyFace } onChange={ (e) => setShowMyFace(value => !value) } />
<label className="form-check-label">{ LocalizeText('catalog.gift_wrapping.show_face.title') }</label>
</div>
<Flex alignItems="center" gap={ 2 }>
{ selectedColorId &&
<div className="gift-preview">
<FurniImageView spriteId={ selectedColorId } type="s" extras={ extraData } />
</div> }
<Column gap={ 1 }>
<Flex gap={ 2 }>
<ButtonGroup>
<Button variant="primary" onClick={ () => handleAction('prev_box') }>
<FontAwesomeIcon icon="chevron-left" />
</Button>
<Button variant="primary" onClick={ () => handleAction('next_box') }>
<FontAwesomeIcon icon="chevron-right" />
</Button>
</ButtonGroup>
<Column gap={ 1 }>
<Text fontWeight="bold">{ LocalizeText(boxName) }</Text>
<Flex alignItems="center" gap={ 1 }>
{ LocalizeText(priceText, ['price'], [giftConfiguration.price.toString()]) }
<CurrencyIcon type={ -1 } />
</Flex>
</Column>
</Flex>
<Flex alignItems="center" gap={ 2 }>
<ButtonGroup>
<Button variant="primary" onClick={ () => handleAction('prev_ribbon') }>
<FontAwesomeIcon icon="chevron-left" />
</Button>
<Button variant="primary" onClick={ () => handleAction('next_ribbon') }>
<FontAwesomeIcon icon="chevron-right" />
</Button>
</ButtonGroup>
<Text fontWeight="bold">{ LocalizeText(ribbonName) }</Text>
</Flex>
</Column>
</Flex>
<Column gap={ 1 }>
<Text fontWeight="bold">
{ LocalizeText('catalog.gift_wrapping.pick_color') }
</Text>
<ButtonGroup fullWidth>
{ colors.map(color => <Button key={ color.id } variant="dark" size="sm" active={ (color.id === selectedColorId) } disabled={ !isColorable } style={{ backgroundColor: color.color }} onClick={ () => setSelectedColorId(color.id) } />) }
</ButtonGroup>
</Column>
<Flex justifyContent="between" alignItems="center">
<Button variant="link" onClick={ close } className="text-black">
{ LocalizeText('cancel') }
</Button>
<Button variant="success" onClick={ () => handleAction('buy') }>
{ LocalizeText('catalog.gift_wrapping.give_gift') }
</Button>
</Flex>
</NitroCardContentView>
</NitroCardView>
);
};

View File

@ -1,12 +1,22 @@
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { GetCatalogPageComposer, INodeData } from '@nitrots/nitro-renderer'; import { GetCatalogPageComposer, INodeData } from '@nitrots/nitro-renderer';
import { FC, useCallback, useEffect, useState } from 'react'; import { Dispatch, FC, SetStateAction, useCallback, useEffect, useState } from 'react';
import { SendMessageHook } from '../../../../../hooks/messages/message-event'; import { LayoutGridItem } from '../../../../common/layout/LayoutGridItem';
import { NitroCardGridItemView } from '../../../../../layout'; import { Text } from '../../../../common/Text';
import { CatalogMode } from '../../../CatalogView.types'; import { SendMessageHook } from '../../../../hooks/messages/message-event';
import { CatalogIconView } from '../../catalog-icon/CatalogIconView'; import { CatalogMode } from '../../common/CatalogMode';
import { ACTIVE_PAGES } from '../CatalogNavigationView'; import { CatalogIconView } from '../catalog-icon/CatalogIconView';
import { CatalogNavigationSetView } from '../set/CatalogNavigationSetView'; import { CatalogNavigationSetView } from './CatalogNavigationSetView';
import { CatalogNavigationItemViewProps } from './CatalogNavigationItemView.types'; import { ACTIVE_PAGES } from './CatalogNavigationView';
export interface CatalogNavigationItemViewProps
{
page: INodeData;
isActive: boolean;
pendingTree: INodeData[];
setPendingTree: Dispatch<SetStateAction<INodeData[]>>;
setActiveChild: Dispatch<SetStateAction<INodeData>>;
}
export const CatalogNavigationItemView: FC<CatalogNavigationItemViewProps> = props => export const CatalogNavigationItemView: FC<CatalogNavigationItemViewProps> = props =>
{ {
@ -72,11 +82,12 @@ export const CatalogNavigationItemView: FC<CatalogNavigationItemViewProps> = pro
return ( return (
<> <>
<NitroCardGridItemView itemActive={ isActive } onClick={ event => select(page) }> <LayoutGridItem column={ false } itemActive={ isActive } onClick={ event => select(page) }>
<CatalogIconView icon={ page.icon } /> <CatalogIconView icon={ page.icon } />
<div className="flex-grow-1 text-black text-truncate">{ page.localization }</div> <Text grow truncate>{ page.localization }</Text>
{ (page.children.length > 0) && <i className={ 'fas fa-caret-' + (isExpanded ? 'up' : 'down') } /> } { (page.children.length > 0) &&
</NitroCardGridItemView> <FontAwesomeIcon icon={ isExpanded ? 'caret-up' : 'caret-down' } /> }
</LayoutGridItem>
{ isActive && isExpanded && page.children && (page.children.length > 0) && { isActive && isExpanded && page.children && (page.children.length > 0) &&
<CatalogNavigationSetView page={ page } pendingTree={ pendingTree } setPendingTree={ setPendingTree } /> } <CatalogNavigationSetView page={ page } pendingTree={ pendingTree } setPendingTree={ setPendingTree } /> }
</> </>

View File

@ -1,7 +1,14 @@
import { INodeData } from '@nitrots/nitro-renderer'; import { INodeData } from '@nitrots/nitro-renderer';
import { FC, useEffect, useState } from 'react'; import { Dispatch, FC, SetStateAction, useEffect, useState } from 'react';
import { CatalogNavigationItemView } from '../item/CatalogNavigationItemView'; import { CatalogNavigationItemView } from './CatalogNavigationItemView';
import { CatalogNavigationSetViewProps } from './CatalogNavigationSetView.types';
export interface CatalogNavigationSetViewProps
{
page: INodeData;
isFirstSet?: boolean;
pendingTree: INodeData[];
setPendingTree: Dispatch<SetStateAction<INodeData[]>>;
}
export const CatalogNavigationSetView: FC<CatalogNavigationSetViewProps> = props => export const CatalogNavigationSetView: FC<CatalogNavigationSetViewProps> = props =>
{ {

View File

@ -4,7 +4,7 @@
background-color: #CDD3D9; background-color: #CDD3D9;
border: 2px solid; border: 2px solid;
.grid-item { .layout-grid-item {
font-size: $font-size-sm; font-size: $font-size-sm;
height: 23px !important; height: 23px !important;
border-color: unset !important; border-color: unset !important;
@ -12,7 +12,7 @@
border: 0 !important; border: 0 !important;
padding: 1px 3px; padding: 1px 3px;
i.fas { .svg-inline--fa {
color: $black; color: $black;
font-size: 10px; font-size: 10px;
padding: 1px; padding: 1px;

View File

@ -1,9 +1,16 @@
import { INodeData } from '@nitrots/nitro-renderer'; import { INodeData } from '@nitrots/nitro-renderer';
import { FC, useEffect } from 'react'; import { Dispatch, FC, SetStateAction, useEffect } from 'react';
import { NitroCardGridView, NitroLayoutFlexColumn } from '../../../../layout'; import { Column } from '../../../../common/Column';
import { Grid } from '../../../../common/Grid';
import { CatalogSearchView } from '../search/CatalogSearchView'; import { CatalogSearchView } from '../search/CatalogSearchView';
import { CatalogNavigationViewProps } from './CatalogNavigationView.types'; import { CatalogNavigationSetView } from './CatalogNavigationSetView';
import { CatalogNavigationSetView } from './set/CatalogNavigationSetView';
export interface CatalogNavigationViewProps
{
page: INodeData;
pendingTree: INodeData[];
setPendingTree: Dispatch<SetStateAction<INodeData[]>>;
}
export let ACTIVE_PAGES: INodeData[] = []; export let ACTIVE_PAGES: INodeData[] = [];
@ -24,13 +31,13 @@ export const CatalogNavigationView: FC<CatalogNavigationViewProps> = props =>
}, [ page ]); }, [ page ]);
return ( return (
<NitroLayoutFlexColumn className="h-100" gap={ 2 } overflow="auto"> <>
<CatalogSearchView /> <CatalogSearchView />
<NitroLayoutFlexColumn className="nitro-catalog-navigation-grid-container p-1 h-100" overflow="hidden"> <Column fullHeight className="nitro-catalog-navigation-grid-container p-1" overflow="hidden">
<NitroCardGridView columns={ 1 } gap={ 1 }> <Grid grow columnCount={ 1 } gap={ 1 } overflow="auto">
<CatalogNavigationSetView page={ page } isFirstSet={ true } pendingTree={ pendingTree } setPendingTree={ setPendingTree } /> <CatalogNavigationSetView page={ page } isFirstSet={ true } pendingTree={ pendingTree } setPendingTree={ setPendingTree } />
</NitroCardGridView> </Grid>
</NitroLayoutFlexColumn> </Column>
</NitroLayoutFlexColumn> </>
); );
} }

View File

@ -1,4 +1,4 @@
import { CatalogPageMessageOfferData, IObjectData, Vector3d } from '@nitrots/nitro-renderer'; import { CatalogPageMessageOfferData, IObjectData, RoomPreviewer, Vector3d } from '@nitrots/nitro-renderer';
import { FC, useCallback, useEffect, useState } from 'react'; import { FC, useCallback, useEffect, useState } from 'react';
import { GetAvatarRenderManager, GetFurnitureDataForProductOffer, GetSessionDataManager } from '../../../../api'; import { GetAvatarRenderManager, GetFurnitureDataForProductOffer, GetSessionDataManager } from '../../../../api';
import { SetRoomPreviewerStuffDataEvent } from '../../../../events'; import { SetRoomPreviewerStuffDataEvent } from '../../../../events';
@ -6,10 +6,14 @@ import { useUiEvent } from '../../../../hooks';
import { FurniCategory } from '../../common/FurniCategory'; import { FurniCategory } from '../../common/FurniCategory';
import { ProductTypeEnum } from '../../common/ProductTypeEnum'; import { ProductTypeEnum } from '../../common/ProductTypeEnum';
import { useCatalogContext } from '../../context/CatalogContext'; import { useCatalogContext } from '../../context/CatalogContext';
import { CatalogPageViewProps } from './CatalogPageView.types';
import { GetCatalogLayout } from './layout/GetCatalogLayout'; import { GetCatalogLayout } from './layout/GetCatalogLayout';
import { CatalogLayoutSearchResultView } from './search-result/CatalogLayoutSearchResultView'; import { CatalogLayoutSearchResultView } from './search-result/CatalogLayoutSearchResultView';
export interface CatalogPageViewProps
{
roomPreviewer: RoomPreviewer;
}
export const CatalogPageView: FC<CatalogPageViewProps> = props => export const CatalogPageView: FC<CatalogPageViewProps> = props =>
{ {
const { roomPreviewer = null } = props; const { roomPreviewer = null } = props;
@ -22,6 +26,9 @@ export const CatalogPageView: FC<CatalogPageViewProps> = props =>
if(!offer || !roomPreviewer) return; if(!offer || !roomPreviewer) return;
const product = offer.products[0]; const product = offer.products[0];
if(!product) return;
const furniData = GetFurnitureDataForProductOffer(product); const furniData = GetFurnitureDataForProductOffer(product);
if(!furniData && (product.productType !== ProductTypeEnum.ROBOT)) return; if(!furniData && (product.productType !== ProductTypeEnum.ROBOT)) return;

View File

@ -0,0 +1,32 @@
.nitro-catalog-layout-info-loyalty {
.info-loyalty-content {
background-repeat: no-repeat;
background-position: top right;
background-image: url('../../../../../assets/images/catalog/diamond_info_illustration.gif');
padding-right:123px;
}
.info-image {
width: 123px;
height:350px;
background-image: url('../../../../../assets/images/catalog/diamond_info_illustration.gif');
}
}
.nitro-catalog-layout-vip-buy-grid {
.layout-grid-item {
height: 50px !important;
max-height: 50px !important;
.icon-hc-banner {
width: 68px;
height: 40px;
background: url("../../../../../assets/images/catalog/hc_big.png") center no-repeat;
}
}
}
@import './marketplace/marketplace-item/MarketplaceItemView';
@import './marketplace/post-offer/MarketplacePostOfferView';

View File

@ -1,28 +1,26 @@
import { StringDataType } from '@nitrots/nitro-renderer'; import { StringDataType } from '@nitrots/nitro-renderer';
import { FC, useCallback, useEffect, useState } from 'react'; import { FC, useCallback, useEffect, useState } from 'react';
import { LocalizeText } from '../../../../../../api'; import { LocalizeText } from '../../../../../../api';
import { Column } from '../../../../../../common/Column';
import { Grid } from '../../../../../../common/Grid';
import { LayoutGridItem } from '../../../../../../common/layout/LayoutGridItem';
import { Text } from '../../../../../../common/Text';
import { InventoryBadgesUpdatedEvent, SetRoomPreviewerStuffDataEvent } from '../../../../../../events'; import { InventoryBadgesUpdatedEvent, SetRoomPreviewerStuffDataEvent } from '../../../../../../events';
import { InventoryBadgesRequestEvent } from '../../../../../../events/inventory/InventoryBadgesRequestEvent'; import { InventoryBadgesRequestEvent } from '../../../../../../events/inventory/InventoryBadgesRequestEvent';
import { dispatchUiEvent, useUiEvent } from '../../../../../../hooks'; import { dispatchUiEvent, useUiEvent } from '../../../../../../hooks';
import { NitroLayoutFlexColumn, NitroLayoutGrid, NitroLayoutGridColumn } from '../../../../../../layout'; import { BadgeImageView } from '../../../../../../views/shared/badge-image/BadgeImageView';
import { NitroLayoutBase } from '../../../../../../layout/base';
import { NitroCardGridItemView } from '../../../../../../layout/card/grid/item/NitroCardGridItemView';
import { NitroCardGridView } from '../../../../../../layout/card/grid/NitroCardGridView';
import { BadgeImageView } from '../../../../../shared/badge-image/BadgeImageView';
import { useCatalogContext } from '../../../../context/CatalogContext'; import { useCatalogContext } from '../../../../context/CatalogContext';
import { CatalogPageOffersView } from '../../offers/CatalogPageOffersView'; import { CatalogPageOffersView } from '../../offers/CatalogPageOffersView';
import { CatalogProductPreviewView } from '../../product-preview/CatalogProductPreviewView'; import { CatalogProductPreviewView } from '../../product-preview/CatalogProductPreviewView';
import { CatalogLayoutBadgeDisplayViewProps } from './CatalogLayoutBadgeDisplayView.types'; import { CatalogLayoutProps } from '../CatalogLayout.types';
export const CatalogLayoutBadgeDisplayView: FC<CatalogLayoutBadgeDisplayViewProps> = props => export const CatalogLayoutBadgeDisplayView: FC<CatalogLayoutProps> = props =>
{ {
const { roomPreviewer = null, pageParser = null } = props; const { roomPreviewer = null, pageParser = null } = props;
const { catalogState = null, dispatchCatalogState = null } = useCatalogContext();
const { activeOffer = null } = catalogState;
const [ badges, setBadges ] = useState<string[]>([]); const [ badges, setBadges ] = useState<string[]>([]);
const [ currentBadge, setCurrentBadge ] = useState<string>(null); const [ currentBadge, setCurrentBadge ] = useState<string>(null);
const { catalogState = null } = useCatalogContext();
const product = ((activeOffer && activeOffer.products[0]) || null); const { activeOffer = null } = catalogState;
const onInventoryBadgesUpdatedEvent = useCallback((event: InventoryBadgesUpdatedEvent) => const onInventoryBadgesUpdatedEvent = useCallback((event: InventoryBadgesUpdatedEvent) =>
{ {
@ -55,26 +53,26 @@ export const CatalogLayoutBadgeDisplayView: FC<CatalogLayoutBadgeDisplayViewProp
}, [ currentBadge, activeOffer, roomPreviewer ]); }, [ currentBadge, activeOffer, roomPreviewer ]);
return ( return (
<NitroLayoutGrid> <Grid>
<NitroLayoutGridColumn size={ 7 }> <Column size={ 7 } overflow="hidden">
<CatalogPageOffersView className="flex-shrink-0" offers={ pageParser.offers } /> <CatalogPageOffersView shrink offers={ pageParser.offers } />
<NitroLayoutFlexColumn gap={ 1 } overflow="hidden"> <Column gap={ 1 } overflow="hidden">
<NitroLayoutBase className="flex-shrink-0 fw-bold text-black text-truncate">{ LocalizeText('catalog_selectbadge') }</NitroLayoutBase> <Text truncate shrink fontWeight="bold">{ LocalizeText('catalog_selectbadge') }</Text>
<NitroCardGridView> <Grid grow columnCount={ 5 } overflow="auto">
{ badges && (badges.length > 0) && badges.map(code => { badges && (badges.length > 0) && badges.map(code =>
{ {
return ( return (
<NitroCardGridItemView key={ code } itemActive={ (currentBadge === code) } onMouseDown={ event => setCurrentBadge(code) }> <LayoutGridItem key={ code } itemActive={ (currentBadge === code) } onMouseDown={ event => setCurrentBadge(code) }>
<BadgeImageView badgeCode={ code } /> <BadgeImageView badgeCode={ code } />
</NitroCardGridItemView> </LayoutGridItem>
); );
}) } }) }
</NitroCardGridView> </Grid>
</NitroLayoutFlexColumn> </Column>
</NitroLayoutGridColumn> </Column>
<NitroLayoutGridColumn size={ 5 }> <Column size={ 5 } overflow="hidden">
<CatalogProductPreviewView pageParser={ pageParser } activeOffer={ activeOffer } roomPreviewer={ roomPreviewer } extra={ currentBadge } disabled={ !currentBadge } /> <CatalogProductPreviewView pageParser={ pageParser } activeOffer={ activeOffer } roomPreviewer={ roomPreviewer } extra={ currentBadge } disabled={ !currentBadge } />
</NitroLayoutGridColumn> </Column>
</NitroLayoutGrid> </Grid>
); );
} }

View File

@ -1,26 +1,25 @@
import { FC } from 'react'; import { FC } from 'react';
import { NitroLayoutGrid, NitroLayoutGridColumn } from '../../../../../../layout'; import { Column } from '../../../../../../common/Column';
import { Grid } from '../../../../../../common/Grid';
import { useCatalogContext } from '../../../../context/CatalogContext'; import { useCatalogContext } from '../../../../context/CatalogContext';
import { CatalogPageOffersView } from '../../offers/CatalogPageOffersView'; import { CatalogPageOffersView } from '../../offers/CatalogPageOffersView';
import { CatalogProductPreviewView } from '../../product-preview/CatalogProductPreviewView'; import { CatalogProductPreviewView } from '../../product-preview/CatalogProductPreviewView';
import { CatalogLayoutDefaultViewProps } from './CatalogLayoutDefaultView.types'; import { CatalogLayoutProps } from '../CatalogLayout.types';
export const CatalogLayoutDefaultView: FC<CatalogLayoutDefaultViewProps> = props => export const CatalogLayoutDefaultView: FC<CatalogLayoutProps> = props =>
{ {
const { roomPreviewer = null, pageParser = null } = props; const { roomPreviewer = null, pageParser = null } = props;
const { catalogState = null } = useCatalogContext(); const { catalogState = null } = useCatalogContext();
const { activeOffer = null } = catalogState; const { activeOffer = null } = catalogState;
const product = ((activeOffer && activeOffer.products[0]) || null);
return ( return (
<NitroLayoutGrid> <Grid>
<NitroLayoutGridColumn size={ 7 }> <Column size={ 7 } overflow="hidden">
<CatalogPageOffersView offers={ pageParser.offers } /> <CatalogPageOffersView offers={ pageParser.offers } />
</NitroLayoutGridColumn> </Column>
<NitroLayoutGridColumn size={ 5 }> <Column size={ 5 } overflow="hidden">
<CatalogProductPreviewView pageParser={ pageParser } activeOffer={ activeOffer } roomPreviewer={ roomPreviewer } /> <CatalogProductPreviewView pageParser={ pageParser } activeOffer={ activeOffer } roomPreviewer={ roomPreviewer } />
</NitroLayoutGridColumn> </Column>
</NitroLayoutGrid> </Grid>
); );
} }

View File

@ -0,0 +1,37 @@
import { FrontPageItem } from '@nitrots/nitro-renderer';
import { FC, useMemo } from 'react';
import { GetConfiguration } from '../../../../../../api';
import { LayoutImage, LayoutImageProps } from '../../../../../../common/layout/LayoutImage';
import { Text } from '../../../../../../common/Text';
export interface CatalogLayoutFrontPageItemViewProps extends LayoutImageProps
{
item: FrontPageItem;
}
export const CatalogLayoutFrontPageItemView: FC<CatalogLayoutFrontPageItemViewProps> = props =>
{
const { item = null, position = 'relative', pointer = true, overflow = 'hidden', fullHeight = true, classNames = [], children = null, ...rest } = props;
const getClassNames = useMemo(() =>
{
const newClassNames: string[] = [ 'rounded', 'nitro-front-page-item' ];
if(classNames.length) newClassNames.push(...classNames);
return newClassNames;
}, [ classNames ]);
if(!item) return null;
const imageUrl = (GetConfiguration<string>('image.library.url') + item.itemPromoImage);
return (
<LayoutImage imageUrl={ imageUrl } classNames={ getClassNames } position={ position } fullHeight={ fullHeight } pointer={ pointer } overflow={ overflow } { ...rest }>
<Text position="absolute" variant="white" className="bg-dark rounded p-2 m-2 bottom-0">
{ item.itemName }
</Text>
{ children }
</LayoutImage>
);
}

View File

@ -1,22 +1,17 @@
import { FrontPageItem } from '@nitrots/nitro-renderer'; import { FrontPageItem } from '@nitrots/nitro-renderer';
import { FC, useCallback, useMemo } from 'react'; import { FC, useCallback } from 'react';
import { CreateLinkEvent, GetConfiguration } from '../../../../../../api'; import { CreateLinkEvent } from '../../../../../../api';
import { NitroLayoutGrid, NitroLayoutGridColumn } from '../../../../../../layout'; import { Column } from '../../../../../../common/Column';
import { Grid } from '../../../../../../common/Grid';
import { GetCatalogPageText } from '../../../../common/CatalogUtilities'; import { GetCatalogPageText } from '../../../../common/CatalogUtilities';
import { CatalogRedeemVoucherView } from '../../redeem-voucher/CatalogRedeemVoucherView'; import { CatalogRedeemVoucherView } from '../../redeem-voucher/CatalogRedeemVoucherView';
import { CatalogLayoutFrontpage4ViewProps } from './CatalogLayoutFrontpage4View.types'; import { CatalogLayoutProps } from '../CatalogLayout.types';
import { CatalogLayoutFrontPageItemView } from './item/CatalogLayoutFrontPageItemView'; import { CatalogLayoutFrontPageItemView } from './CatalogLayoutFrontPageItemView';
export const CatalogLayoutFrontpage4View: FC<CatalogLayoutFrontpage4ViewProps> = props => export const CatalogLayoutFrontpage4View: FC<CatalogLayoutProps> = props =>
{ {
const { pageParser = null } = props; const { pageParser = null } = props;
const imageLibraryUrl = useMemo(() =>
{
console.log(pageParser);
return GetConfiguration<string>('image.library.url');
}, []);
const selectItem = useCallback((item: FrontPageItem) => const selectItem = useCallback((item: FrontPageItem) =>
{ {
switch(item.type) switch(item.type)
@ -30,15 +25,13 @@ export const CatalogLayoutFrontpage4View: FC<CatalogLayoutFrontpage4ViewProps> =
} }
}, []); }, []);
if(!pageParser) return null;
return ( return (
<NitroLayoutGrid> <Grid>
<NitroLayoutGridColumn size={ 4 }> <Column size={ 4 }>
{ pageParser.frontPageItems[0] && { pageParser.frontPageItems[0] &&
<CatalogLayoutFrontPageItemView item={ pageParser.frontPageItems[0] } onClick={ event => selectItem(pageParser.frontPageItems[0]) } /> } <CatalogLayoutFrontPageItemView item={ pageParser.frontPageItems[0] } onClick={ event => selectItem(pageParser.frontPageItems[0]) } /> }
</NitroLayoutGridColumn> </Column>
<NitroLayoutGridColumn size={ 8 }> <Column size={ 8 }>
{ pageParser.frontPageItems[1] && { pageParser.frontPageItems[1] &&
<CatalogLayoutFrontPageItemView item={ pageParser.frontPageItems[1] } onClick={ event => selectItem(pageParser.frontPageItems[1]) } /> } <CatalogLayoutFrontPageItemView item={ pageParser.frontPageItems[1] } onClick={ event => selectItem(pageParser.frontPageItems[1]) } /> }
{ pageParser.frontPageItems[2] && { pageParser.frontPageItems[2] &&
@ -46,7 +39,7 @@ export const CatalogLayoutFrontpage4View: FC<CatalogLayoutFrontpage4ViewProps> =
{ pageParser.frontPageItems[3] && { pageParser.frontPageItems[3] &&
<CatalogLayoutFrontPageItemView item={ pageParser.frontPageItems[3] } onClick={ event => selectItem(pageParser.frontPageItems[3]) } /> } <CatalogLayoutFrontPageItemView item={ pageParser.frontPageItems[3] } onClick={ event => selectItem(pageParser.frontPageItems[3]) } /> }
<CatalogRedeemVoucherView text={ GetCatalogPageText(pageParser, 1) } /> <CatalogRedeemVoucherView text={ GetCatalogPageText(pageParser, 1) } />
</NitroLayoutGridColumn> </Column>
</NitroLayoutGrid> </Grid>
); );
} }

View File

@ -1,16 +1,17 @@
import { CatalogGroupsComposer, StringDataType } from '@nitrots/nitro-renderer'; import { CatalogGroupsComposer, StringDataType } from '@nitrots/nitro-renderer';
import { FC, useEffect, useMemo, useState } from 'react'; import { FC, useEffect, useMemo, useState } from 'react';
import { Column } from '../../../../../../common/Column';
import { Grid } from '../../../../../../common/Grid';
import { SetRoomPreviewerStuffDataEvent } from '../../../../../../events'; import { SetRoomPreviewerStuffDataEvent } from '../../../../../../events';
import { dispatchUiEvent } from '../../../../../../hooks'; import { dispatchUiEvent } from '../../../../../../hooks';
import { SendMessageHook } from '../../../../../../hooks/messages'; import { SendMessageHook } from '../../../../../../hooks/messages';
import { NitroLayoutGrid, NitroLayoutGridColumn } from '../../../../../../layout';
import { useCatalogContext } from '../../../../context/CatalogContext'; import { useCatalogContext } from '../../../../context/CatalogContext';
import { CatalogSelectGroupView } from '../../../select-group/CatalogSelectGroupView'; import { CatalogSelectGroupView } from '../../../select-group/CatalogSelectGroupView';
import { CatalogPageOffersView } from '../../offers/CatalogPageOffersView'; import { CatalogPageOffersView } from '../../offers/CatalogPageOffersView';
import { CatalogProductPreviewView } from '../../product-preview/CatalogProductPreviewView'; import { CatalogProductPreviewView } from '../../product-preview/CatalogProductPreviewView';
import { CatalogLayoutGuildCustomFurniViewProps } from './CatalogLayoutGuildCustomFurniView.types'; import { CatalogLayoutProps } from '../CatalogLayout.types';
export const CatalogLayouGuildCustomFurniView: FC<CatalogLayoutGuildCustomFurniViewProps> = props => export const CatalogLayouGuildCustomFurniView: FC<CatalogLayoutProps> = props =>
{ {
const { roomPreviewer = null, pageParser = null } = props; const { roomPreviewer = null, pageParser = null } = props;
const [ selectedGroupIndex, setSelectedGroupIndex ] = useState<number>(0); const [ selectedGroupIndex, setSelectedGroupIndex ] = useState<number>(0);
@ -49,15 +50,15 @@ export const CatalogLayouGuildCustomFurniView: FC<CatalogLayoutGuildCustomFurniV
if(!groups) return null; if(!groups) return null;
return ( return (
<NitroLayoutGrid> <Grid>
<NitroLayoutGridColumn size={ 7 }> <Column size={ 7 } overflow="hidden">
<CatalogPageOffersView offers={ pageParser.offers } /> <CatalogPageOffersView offers={ pageParser.offers } />
</NitroLayoutGridColumn> </Column>
<NitroLayoutGridColumn size={ 5 }> <Column size={ 5 } overflow="hidden">
<CatalogProductPreviewView pageParser={ pageParser } activeOffer={ activeOffer } roomPreviewer={ roomPreviewer } badgeCode={ ((selectedGroup && selectedGroup.badgeCode) || null) } extra={ groups[selectedGroupIndex] ? groups[selectedGroupIndex].groupId.toString() : '' } disabled={ !(!!groups[selectedGroupIndex]) }> <CatalogProductPreviewView pageParser={ pageParser } activeOffer={ activeOffer } roomPreviewer={ roomPreviewer } badgeCode={ ((selectedGroup && selectedGroup.badgeCode) || null) } extra={ groups[selectedGroupIndex] ? groups[selectedGroupIndex].groupId.toString() : '' } disabled={ !(!!groups[selectedGroupIndex]) }>
<CatalogSelectGroupView selectedGroupIndex={ selectedGroupIndex } setSelectedGroupIndex={ setSelectedGroupIndex } /> <CatalogSelectGroupView selectedGroupIndex={ selectedGroupIndex } setSelectedGroupIndex={ setSelectedGroupIndex } />
</CatalogProductPreviewView> </CatalogProductPreviewView>
</NitroLayoutGridColumn> </Column>
</NitroLayoutGrid> </Grid>
); );
} }

View File

@ -1,26 +1,23 @@
import { CatalogGroupsComposer } from '@nitrots/nitro-renderer'; import { CatalogGroupsComposer } from '@nitrots/nitro-renderer';
import { FC, useEffect, useState } from 'react'; import { FC, useEffect, useState } from 'react';
import { Base } from '../../../../../../common/Base';
import { Column } from '../../../../../../common/Column';
import { Grid } from '../../../../../../common/Grid';
import { SendMessageHook } from '../../../../../../hooks/messages'; import { SendMessageHook } from '../../../../../../hooks/messages';
import { NitroLayoutGrid, NitroLayoutGridColumn } from '../../../../../../layout';
import { NitroLayoutBase } from '../../../../../../layout/base';
import { GetCatalogPageText } from '../../../../common/CatalogUtilities'; import { GetCatalogPageText } from '../../../../common/CatalogUtilities';
import { useCatalogContext } from '../../../../context/CatalogContext'; import { useCatalogContext } from '../../../../context/CatalogContext';
import { CatalogActions } from '../../../../reducers/CatalogReducer'; import { CatalogActions } from '../../../../reducers/CatalogReducer';
import { CatalogSelectGroupView } from '../../../select-group/CatalogSelectGroupView'; import { CatalogSelectGroupView } from '../../../select-group/CatalogSelectGroupView';
import { CatalogProductPreviewView } from '../../product-preview/CatalogProductPreviewView'; import { CatalogProductPreviewView } from '../../product-preview/CatalogProductPreviewView';
import { CatalogLayoutGuildForumViewProps } from './CatalogLayoutGuildForumView.types'; import { CatalogLayoutProps } from '../CatalogLayout.types';
export const CatalogLayouGuildForumView: FC<CatalogLayoutGuildForumViewProps> = props => export const CatalogLayouGuildForumView: FC<CatalogLayoutProps> = props =>
{ {
const { pageParser = null } = props; const { pageParser = null } = props;
const [ selectedGroupIndex, setSelectedGroupIndex ] = useState<number>(0);
const { catalogState = null, dispatchCatalogState = null } = useCatalogContext(); const { catalogState = null, dispatchCatalogState = null } = useCatalogContext();
const { activeOffer = null, groups = null } = catalogState; const { activeOffer = null, groups = null } = catalogState;
const [ selectedGroupIndex, setSelectedGroupIndex ] = useState<number>(0);
const product = ((activeOffer && activeOffer.products[0]) || null);
useEffect(() => useEffect(() =>
{ {
SendMessageHook(new CatalogGroupsComposer()); SendMessageHook(new CatalogGroupsComposer());
@ -37,15 +34,15 @@ export const CatalogLayouGuildForumView: FC<CatalogLayoutGuildForumViewProps> =
}, [ dispatchCatalogState, pageParser ]); }, [ dispatchCatalogState, pageParser ]);
return ( return (
<NitroLayoutGrid> <Grid>
<NitroLayoutGridColumn className="bg-muted rounded p-2 text-black overflow-hidden" size={ 7 }> <Column className="bg-muted rounded p-2 text-black" size={ 7 } overflow="hidden">
<NitroLayoutBase className="overflow-auto" dangerouslySetInnerHTML={ { __html: GetCatalogPageText(pageParser, 1) } } /> <Base className="overflow-auto" dangerouslySetInnerHTML={ { __html: GetCatalogPageText(pageParser, 1) } } />
</NitroLayoutGridColumn> </Column>
<NitroLayoutGridColumn size={ 5 }> <Column size={ 5 } overflow="hidden">
<CatalogProductPreviewView pageParser={ pageParser } activeOffer={ activeOffer } roomPreviewer={ null } extra={ groups[selectedGroupIndex] ? groups[selectedGroupIndex].groupId.toString() : '' } disabled={ !(!!groups[selectedGroupIndex]) }> <CatalogProductPreviewView pageParser={ pageParser } activeOffer={ activeOffer } roomPreviewer={ null } extra={ groups[selectedGroupIndex] ? groups[selectedGroupIndex].groupId.toString() : '' } disabled={ !(!!groups[selectedGroupIndex]) }>
<CatalogSelectGroupView selectedGroupIndex={ selectedGroupIndex } setSelectedGroupIndex={ setSelectedGroupIndex } /> <CatalogSelectGroupView selectedGroupIndex={ selectedGroupIndex } setSelectedGroupIndex={ setSelectedGroupIndex } />
</CatalogProductPreviewView> </CatalogProductPreviewView>
</NitroLayoutGridColumn> </Column>
</NitroLayoutGrid> </Grid>
); );
} }

View File

@ -0,0 +1,30 @@
import { FC } from 'react';
import { CreateLinkEvent, 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 { GetCatalogPageImage, GetCatalogPageText } from '../../../../common/CatalogUtilities';
import { CatalogLayoutProps } from '../CatalogLayout.types';
export const CatalogLayouGuildFrontpageView: FC<CatalogLayoutProps> = props =>
{
const { pageParser = null } = props;
return (
<Grid>
<Column size={ 7 } overflow="hidden" className="bg-muted rounded p-2 text-black">
<Base dangerouslySetInnerHTML={ { __html: GetCatalogPageText(pageParser, 2) } } />
<Base overflow="auto" dangerouslySetInnerHTML={ { __html: GetCatalogPageText(pageParser, 0) } } />
<Base dangerouslySetInnerHTML={ { __html: GetCatalogPageText(pageParser, 1) } } />
</Column>
<Column center size={ 5 } overflow="hidden">
<LayoutImage imageUrl={ GetCatalogPageImage(pageParser, 1) } />
<Button onClick={ () => CreateLinkEvent('groups/create') }>
{ LocalizeText('catalog.start.guild.purchase.button') }
</Button>
</Column>
</Grid>
);
}

View File

@ -1,8 +1,8 @@
import { FC } from 'react'; import { FC } from 'react';
import { GetCatalogPageText } from '../../../../common/CatalogUtilities'; import { GetCatalogPageText } from '../../../../common/CatalogUtilities';
import { CatalogLayoutInfoLoyaltyViewProps } from './CatalogLayoutInfoLoyaltyView.types'; import { CatalogLayoutProps } from '../CatalogLayout.types';
export const CatalogLayoutInfoLoyaltyView: FC<CatalogLayoutInfoLoyaltyViewProps> = props => export const CatalogLayoutInfoLoyaltyView: FC<CatalogLayoutProps> = props =>
{ {
const { pageParser = null } = props; const { pageParser = null } = props;

View File

@ -4,8 +4,8 @@ import { LocalizeText } from '../../../../../../../api';
import { BatchUpdates, CreateMessageHook, SendMessageHook, UseMountEffect } from '../../../../../../../hooks'; import { BatchUpdates, CreateMessageHook, SendMessageHook, UseMountEffect } from '../../../../../../../hooks';
import { NitroCardGridView } from '../../../../../../../layout'; import { NitroCardGridView } from '../../../../../../../layout';
import { NitroLayoutBase } from '../../../../../../../layout/base'; import { NitroLayoutBase } from '../../../../../../../layout/base';
import { NotificationAlertType } from '../../../../../../notification-center/common/NotificationAlertType'; import { NotificationAlertType } from '../../../../../../../views/notification-center/common/NotificationAlertType';
import { NotificationUtilities } from '../../../../../../notification-center/common/NotificationUtilities'; import { NotificationUtilities } from '../../../../../../../views/notification-center/common/NotificationUtilities';
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 { MarketPlaceOfferState } from '../common/MarketplaceOfferState';

View File

@ -1,11 +1,11 @@
import { ImageResult, MakeOfferMessageComposer, Vector3d } from '@nitrots/nitro-renderer'; import { ImageResult, MakeOfferMessageComposer, Vector3d } from '@nitrots/nitro-renderer';
import { FC, useCallback, useState } from 'react'; import { FC, useCallback, useState } from 'react';
import { GetRoomEngine, LocalizeText } from '../../../../../../../api'; import { GetRoomEngine, LocalizeText } from '../../../../../../../api';
import { FurnitureItem } from '../../../../../../../components/inventory/common/FurnitureItem';
import { CatalogPostMarketplaceOfferEvent } from '../../../../../../../events/catalog/CatalogPostMarketplaceOfferEvent'; import { CatalogPostMarketplaceOfferEvent } from '../../../../../../../events/catalog/CatalogPostMarketplaceOfferEvent';
import { SendMessageHook, useUiEvent } from '../../../../../../../hooks'; import { SendMessageHook, useUiEvent } from '../../../../../../../hooks';
import { NitroCardContentView, NitroCardHeaderView, NitroCardView, NitroLayoutFlex } from '../../../../../../../layout'; import { NitroCardContentView, NitroCardHeaderView, NitroCardView, NitroLayoutFlex } from '../../../../../../../layout';
import { NotificationUtilities } from '../../../../../../notification-center/common/NotificationUtilities'; import { NotificationUtilities } from '../../../../../../../views/notification-center/common/NotificationUtilities';
import { FurnitureItem } from '../../../../../../inventory/common/FurnitureItem';
export const MarketplacePostOfferView : FC<{}> = props => export const MarketplacePostOfferView : FC<{}> = props =>
{ {

View File

@ -3,9 +3,9 @@ import { FC, useCallback, useMemo, useState } from 'react';
import { LocalizeText } from '../../../../../../../api'; import { LocalizeText } from '../../../../../../../api';
import { BatchUpdates, CreateMessageHook, SendMessageHook } from '../../../../../../../hooks'; import { BatchUpdates, CreateMessageHook, SendMessageHook } from '../../../../../../../hooks';
import { NitroCardGridView } from '../../../../../../../layout'; import { NitroCardGridView } from '../../../../../../../layout';
import { NotificationAlertType } from '../../../../../../notification-center/common/NotificationAlertType'; import { NotificationAlertType } from '../../../../../../../views/notification-center/common/NotificationAlertType';
import { NotificationUtilities } from '../../../../../../notification-center/common/NotificationUtilities'; import { NotificationUtilities } from '../../../../../../../views/notification-center/common/NotificationUtilities';
import { GetCurrencyAmount } from '../../../../../../purse/common/CurrencyHelper'; import { GetCurrencyAmount } from '../../../../../../../views/purse/common/CurrencyHelper';
import { CatalogLayoutProps } from '../../CatalogLayout.types'; import { CatalogLayoutProps } from '../../CatalogLayout.types';
import { IMarketplaceSearchOptions } from '../common/IMarketplaceSearchOptions'; import { IMarketplaceSearchOptions } from '../common/IMarketplaceSearchOptions';
import { MarketplaceOfferData } from '../common/MarketplaceOfferData'; import { MarketplaceOfferData } from '../common/MarketplaceOfferData';

View File

@ -0,0 +1,75 @@
import { ApproveNameMessageComposer, CatalogPageMessageOfferData } from '@nitrots/nitro-renderer';
import { FC, useCallback, useState } from 'react';
import { LocalizeText } from '../../../../../../api';
import { Column } from '../../../../../../common/Column';
import { Flex } from '../../../../../../common/Flex';
import { Text } from '../../../../../../common/Text';
import { CatalogEvent } from '../../../../../../events';
import { useUiEvent } from '../../../../../../hooks/events/ui/ui-event';
import { SendMessageHook } from '../../../../../../hooks/messages/message-event';
import { CurrencyIcon } from '../../../../../../views/shared/currency-icon/CurrencyIcon';
import { CatalogPurchaseButtonView } from '../../purchase/CatalogPurchaseButtonView';
import { CatalogPurchaseGiftButtonView } from '../../purchase/CatalogPurchaseGiftButtonView';
import { CatalogPetNameApprovalView } from './CatalogPetNameApprovalView';
export interface CatalogLayoutPetPurchaseViewProps
{
offer: CatalogPageMessageOfferData;
pageId: number;
extra?: string;
}
export const CatalogLayoutPetPurchaseView: FC<CatalogLayoutPetPurchaseViewProps> = props =>
{
const { offer = null, pageId = -1, extra = '' } = props;
const [ petNameValue, setPetNameValue ] = useState('');
const [ nameApproved, setNameApproved ] = useState(false);
const onCatalogEvent = useCallback((event: CatalogEvent) =>
{
switch(event.type)
{
case CatalogEvent.PURCHASE_SUCCESS:
setNameApproved(false);
return;
}
}, []);
useUiEvent(CatalogEvent.PURCHASE_SUCCESS, onCatalogEvent);
const beforePurchase = useCallback(() =>
{
SendMessageHook(new ApproveNameMessageComposer(petNameValue, 1));
}, [ petNameValue ]);
const extraData = `${ petNameValue }\n${ extra }`;
return (
<Column fullWidth grow justifyContent="end">
<div className="d-flex flex-grow-1 justify-content-center align-items-center">
<CatalogPetNameApprovalView petNameValue={ petNameValue } setPetNameValue={ setPetNameValue } nameApproved={ nameApproved } setNameApproved={ setNameApproved } />
</div>
<Flex alignItems="end">
<div className="flex-grow-1 align-items-end">
<Text>{ LocalizeText('catalog.bundlewidget.price') }</Text>
</div>
<Column>
{ (offer.priceCredits > 0) &&
<Flex alignItems="center" justifyContent="end" gap={ 1 }>
<Text>{ offer.priceCredits }</Text>
<CurrencyIcon type={ -1 } />
</Flex> }
{ (offer.priceActivityPoints > 0) &&
<Flex alignItems="center" justifyContent="end" gap={ 1 }>
<Text>{ offer.priceActivityPoints }</Text>
<CurrencyIcon type={ offer.priceActivityPointsType } />
</Flex> }
</Column>
</Flex>
<Column gap={ 1 }>
<CatalogPurchaseButtonView className="btn-sm w-100" 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 } /> }
</Column>
</Column>
);
}

View File

@ -1,89 +1,35 @@
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { ColorConverter, GetSellablePetPalettesComposer, SellablePetPaletteData } from '@nitrots/nitro-renderer'; import { ColorConverter, GetSellablePetPalettesComposer, SellablePetPaletteData } from '@nitrots/nitro-renderer';
import { FC, useEffect, useMemo, useState } from 'react'; import { FC, useEffect, useMemo, useState } from 'react';
import { GetProductDataForLocalization, LocalizeText } from '../../../../../../api'; import { GetProductDataForLocalization, LocalizeText } from '../../../../../../api';
import { Base } from '../../../../../../common/Base';
import { Button } from '../../../../../../common/Button';
import { Column } from '../../../../../../common/Column';
import { Grid } from '../../../../../../common/Grid';
import { LayoutGridItem } from '../../../../../../common/layout/LayoutGridItem';
import { Text } from '../../../../../../common/Text';
import { BatchUpdates } from '../../../../../../hooks';
import { SendMessageHook } from '../../../../../../hooks/messages/message-event'; import { SendMessageHook } from '../../../../../../hooks/messages/message-event';
import { NitroCardGridItemView, NitroCardGridView, NitroLayoutFlexColumn, NitroLayoutGrid, NitroLayoutGridColumn } from '../../../../../../layout'; import { PetImageView } from '../../../../../../views/shared/pet-image/PetImageView';
import { NitroLayoutBase } from '../../../../../../layout/base';
import { PetImageView } from '../../../../../shared/pet-image/PetImageView';
import { GetPetAvailableColors, GetPetIndexFromLocalization } from '../../../../common/CatalogUtilities'; import { GetPetAvailableColors, GetPetIndexFromLocalization } from '../../../../common/CatalogUtilities';
import { useCatalogContext } from '../../../../context/CatalogContext'; import { useCatalogContext } from '../../../../context/CatalogContext';
import { CatalogActions } from '../../../../reducers/CatalogReducer'; import { CatalogActions } from '../../../../reducers/CatalogReducer';
import { CatalogRoomPreviewerView } from '../../../catalog-room-previewer/CatalogRoomPreviewerView'; import { CatalogRoomPreviewerView } from '../../../catalog-room-previewer/CatalogRoomPreviewerView';
import { CatalogPageDetailsView } from '../../../page-details/CatalogPageDetailsView'; import { CatalogPageDetailsView } from '../../../page-details/CatalogPageDetailsView';
import { CatalogLayoutPetViewProps } from './CatalogLayoutPetView.types'; import { CatalogLayoutProps } from '../CatalogLayout.types';
import { CatalogLayoutPetPurchaseView } from './purchase/CatalogLayoutPetPurchaseView'; import { CatalogLayoutPetPurchaseView } from './CatalogLayoutPetPurchaseView';
export const CatalogLayoutPetView: FC<CatalogLayoutPetViewProps> = props => export const CatalogLayoutPetView: FC<CatalogLayoutProps> = props =>
{ {
const { roomPreviewer = null, pageParser = null } = props; const { roomPreviewer = null, pageParser = null } = props;
const { catalogState = null, dispatchCatalogState = null } = useCatalogContext();
const { activeOffer = null, petPalettes = [] } = catalogState;
const [ petIndex, setPetIndex ] = useState(-1); const [ petIndex, setPetIndex ] = useState(-1);
const [ sellablePalettes, setSellablePalettes ] = useState<SellablePetPaletteData[]>([]); const [ sellablePalettes, setSellablePalettes ] = useState<SellablePetPaletteData[]>([]);
const [ selectedPaletteIndex, setSelectedPaletteIndex ] = useState(-1); const [ selectedPaletteIndex, setSelectedPaletteIndex ] = useState(-1);
const [ sellableColors, setSellableColors ] = useState<number[][]>([]); const [ sellableColors, setSellableColors ] = useState<number[][]>([]);
const [ selectedColorIndex, setSelectedColorIndex ] = useState(-1); const [ selectedColorIndex, setSelectedColorIndex ] = useState(-1);
const [ colorsShowing, setColorsShowing ] = useState(false); const [ colorsShowing, setColorsShowing ] = useState(false);
const { catalogState = null, dispatchCatalogState = null } = useCatalogContext();
useEffect(() => const { activeOffer = null, petPalettes = [] } = catalogState;
{
if(!pageParser || !pageParser.offers.length) return;
const offer = pageParser.offers[0];
dispatchCatalogState({
type: CatalogActions.SET_CATALOG_ACTIVE_OFFER,
payload: {
activeOffer: offer
}
});
setPetIndex(GetPetIndexFromLocalization(offer.localizationId));
setColorsShowing(false);
}, [ pageParser, dispatchCatalogState ]);
useEffect(() =>
{
if(!activeOffer) return;
const productData = GetProductDataForLocalization(activeOffer.localizationId);
if(!productData) return;
for(const paletteData of petPalettes)
{
if(paletteData.breed !== productData.type) continue;
const palettes: SellablePetPaletteData[] = [];
for(const palette of paletteData.palettes)
{
if(!palette.sellable) continue;
palettes.push(palette);
}
setSelectedPaletteIndex((palettes.length ? 0 : -1));
setSellablePalettes(palettes);
return;
}
setSelectedPaletteIndex(-1);
setSellablePalettes([]);
SendMessageHook(new GetSellablePetPalettesComposer(productData.type));
}, [ activeOffer, petPalettes ]);
useEffect(() =>
{
if(petIndex === -1) return;
const colors = GetPetAvailableColors(petIndex, sellablePalettes);
setSelectedColorIndex((colors.length ? 0 : -1));
setSellableColors(colors);
}, [ petIndex, sellablePalettes ]);
const getColor = useMemo(() => const getColor = useMemo(() =>
{ {
@ -92,21 +38,6 @@ export const CatalogLayoutPetView: FC<CatalogLayoutPetViewProps> = props =>
return sellableColors[selectedColorIndex][0]; return sellableColors[selectedColorIndex][0];
}, [ sellableColors, selectedColorIndex ]); }, [ sellableColors, selectedColorIndex ]);
useEffect(() =>
{
if(!roomPreviewer) return;
roomPreviewer && roomPreviewer.reset(false);
if((petIndex === -1) || !sellablePalettes.length || (selectedPaletteIndex === -1)) return;
let petFigureString = `${ petIndex } ${ sellablePalettes[selectedPaletteIndex].paletteId }`;
if(petIndex <= 7) petFigureString += ` ${ getColor.toString(16) }`;
roomPreviewer.addPetIntoRoom(petFigureString);
}, [ roomPreviewer, petIndex, sellablePalettes, selectedPaletteIndex, getColor ]);
const petBreedName = useMemo(() => const petBreedName = useMemo(() =>
{ {
if((petIndex === -1) || !sellablePalettes.length || (selectedPaletteIndex === -1)) return ''; if((petIndex === -1) || !sellablePalettes.length || (selectedPaletteIndex === -1)) return '';
@ -131,56 +62,135 @@ export const CatalogLayoutPetView: FC<CatalogLayoutPetViewProps> = props =>
let colorString = color.toString(16).toUpperCase(); let colorString = color.toString(16).toUpperCase();
while(colorString.length < 6) while(colorString.length < 6) colorString = ('0' + colorString);
{
colorString = ('0' + colorString);
}
return `${ paletteId }\n${ colorString }`; return `${ paletteId }\n${ colorString }`;
}, [ sellablePalettes, selectedPaletteIndex, petIndex, sellableColors, selectedColorIndex ]); }, [ sellablePalettes, selectedPaletteIndex, petIndex, sellableColors, selectedColorIndex ]);
useEffect(() =>
{
if(!pageParser || !pageParser.offers.length) return;
const offer = pageParser.offers[0];
dispatchCatalogState({
type: CatalogActions.SET_CATALOG_ACTIVE_OFFER,
payload: {
activeOffer: offer
}
});
BatchUpdates(() =>
{
setPetIndex(GetPetIndexFromLocalization(offer.localizationId));
setColorsShowing(false);
});
}, [ pageParser, dispatchCatalogState ]);
useEffect(() =>
{
if(!activeOffer) return;
const productData = GetProductDataForLocalization(activeOffer.localizationId);
if(!productData) return;
for(const paletteData of petPalettes)
{
if(paletteData.breed !== productData.type) continue;
const palettes: SellablePetPaletteData[] = [];
for(const palette of paletteData.palettes)
{
if(!palette.sellable) continue;
palettes.push(palette);
}
BatchUpdates(() =>
{
setSelectedPaletteIndex((palettes.length ? 0 : -1));
setSellablePalettes(palettes);
});
return;
}
BatchUpdates(() =>
{
setSelectedPaletteIndex(-1);
setSellablePalettes([]);
});
SendMessageHook(new GetSellablePetPalettesComposer(productData.type));
}, [ activeOffer, petPalettes ]);
useEffect(() =>
{
if(petIndex === -1) return;
const colors = GetPetAvailableColors(petIndex, sellablePalettes);
BatchUpdates(() =>
{
setSelectedColorIndex((colors.length ? 0 : -1));
setSellableColors(colors);
});
}, [ petIndex, sellablePalettes ]);
useEffect(() =>
{
if(!roomPreviewer) return;
roomPreviewer && roomPreviewer.reset(false);
if((petIndex === -1) || !sellablePalettes.length || (selectedPaletteIndex === -1)) return;
let petFigureString = `${ petIndex } ${ sellablePalettes[selectedPaletteIndex].paletteId }`;
if(petIndex <= 7) petFigureString += ` ${ getColor.toString(16) }`;
roomPreviewer.addPetIntoRoom(petFigureString);
}, [ roomPreviewer, petIndex, sellablePalettes, selectedPaletteIndex, getColor ]);
if(!activeOffer) return null; if(!activeOffer) return null;
return ( return (
<NitroLayoutGrid> <Grid>
<NitroLayoutGridColumn size={ 7 }> <Column size={ 7 } overflow="hidden">
<NitroCardGridView> <Grid grow columnCount={ 5 } overflow="auto">
{ !colorsShowing && (sellablePalettes.length > 0) && sellablePalettes.map((palette, index) => { !colorsShowing && (sellablePalettes.length > 0) && sellablePalettes.map((palette, index) =>
{ {
return ( return (
<NitroCardGridItemView key={ index } itemActive={ (selectedPaletteIndex === index) } onClick={ event => setSelectedPaletteIndex(index) }> <LayoutGridItem key={ index } itemActive={ (selectedPaletteIndex === index) } onClick={ event => setSelectedPaletteIndex(index) }>
<PetImageView typeId={ petIndex } paletteId={ palette.paletteId } direction={ 2 } headOnly={ true } /> <PetImageView typeId={ petIndex } paletteId={ palette.paletteId } direction={ 2 } headOnly={ true } />
</NitroCardGridItemView> </LayoutGridItem>
); );
})} })}
{ colorsShowing && (sellableColors.length > 0) && sellableColors.map((colorSet, index) => { colorsShowing && (sellableColors.length > 0) && sellableColors.map((colorSet, index) => <LayoutGridItem key={ index } itemActive={ (selectedColorIndex === index) } itemColor={ ColorConverter.int2rgb(colorSet[0]) } onClick={ event => setSelectedColorIndex(index) } />) }
{ </Grid>
return ( </Column>
<NitroCardGridItemView key={ index } itemActive={ (selectedColorIndex === index) } itemColor={ ColorConverter.int2rgb(colorSet[0]) } onClick={ event => setSelectedColorIndex(index) } /> <Column size={ 5 } overflow="hidden">
);
})}
</NitroCardGridView>
</NitroLayoutGridColumn>
<NitroLayoutGridColumn size={ 5 }>
{ (petIndex === -1) && { (petIndex === -1) &&
<CatalogPageDetailsView pageParser={ pageParser } /> } <CatalogPageDetailsView pageParser={ pageParser } /> }
{ (petIndex >= 0) && { (petIndex >= 0) &&
<> <>
<NitroLayoutFlexColumn overflow="hidden" position="relative"> <Column overflow="hidden" position="relative" gap={ 0 }>
{ roomPreviewer && <CatalogRoomPreviewerView roomPreviewer={ roomPreviewer } height={ 140 } /> } { roomPreviewer && <CatalogRoomPreviewerView roomPreviewer={ roomPreviewer } height={ 140 } /> }
{ (petIndex > -1 && petIndex <= 7) && { (petIndex > -1 && petIndex <= 7) &&
<NitroLayoutBase className="start-2 bottom-2" position="absolute"> <Base position="absolute" className="start-1 bottom-1">
<button type="button" className= { 'btn btn-primary btn-sm color-button ' + (colorsShowing ? 'active ' : '') } onClick={ event => setColorsShowing(!colorsShowing) }> <Button size="sm" active={ colorsShowing } onClick={ event => setColorsShowing(!colorsShowing) }>
<i className="fas fa-fill-drip" /> <FontAwesomeIcon icon="fill-drip" />
</button> </Button>
</NitroLayoutBase> } </Base> }
</NitroLayoutFlexColumn> </Column>
<NitroLayoutFlexColumn className="flex-grow-1" gap={ 2 }> <Column grow>
<NitroLayoutBase className="flex-grow-1 text-black text-truncate">{ petBreedName }</NitroLayoutBase> <Text grow truncate>{ petBreedName }</Text>
<CatalogLayoutPetPurchaseView offer={ activeOffer } pageId={ pageParser.pageId } extra={ petPurchaseString } /> <CatalogLayoutPetPurchaseView offer={ activeOffer } pageId={ pageParser.pageId } extra={ petPurchaseString } />
</NitroLayoutFlexColumn> </Column>
</> } </> }
</NitroLayoutGridColumn> </Column>
</NitroLayoutGrid> </Grid>
); );
} }

View File

@ -1,19 +1,21 @@
import { FC, useCallback, useEffect, useMemo, useState } from 'react'; import { Dispatch, FC, SetStateAction, useCallback, useEffect, useState } from 'react';
import { LocalizeText } from '../../../../../../../api'; import { LocalizeText } from '../../../../../../api';
import { CatalogEvent, CatalogNameResultEvent } from '../../../../../../../events'; import { CatalogEvent, CatalogNameResultEvent } from '../../../../../../events';
import { useUiEvent } from '../../../../../../../hooks/events/ui/ui-event'; import { useUiEvent } from '../../../../../../hooks/events/ui/ui-event';
import { CatalogPetNameApprovalViewProps } from './CatalogPetNameApprovalView.types';
export interface CatalogPetNameApprovalViewProps
{
petNameValue: string;
setPetNameValue: Dispatch<SetStateAction<string>>;
nameApproved: boolean;
setNameApproved: Dispatch<SetStateAction<boolean>>;
}
export const CatalogPetNameApprovalView: FC<CatalogPetNameApprovalViewProps> = props => export const CatalogPetNameApprovalView: FC<CatalogPetNameApprovalViewProps> = props =>
{ {
const { petNameValue = null, setPetNameValue = null, nameApproved = false, setNameApproved = null } = props; const { petNameValue = null, setPetNameValue = null, nameApproved = false, setNameApproved = null } = props;
const [ validationResult, setValidationResult ] = useState(-1); const [ validationResult, setValidationResult ] = useState(-1);
useEffect(() =>
{
setValidationResult(-1);
}, [ petNameValue ]);
const onCatalogNameResultEvent = useCallback((event: CatalogNameResultEvent) => const onCatalogNameResultEvent = useCallback((event: CatalogNameResultEvent) =>
{ {
if(event.result === 0) if(event.result === 0)
@ -28,7 +30,7 @@ export const CatalogPetNameApprovalView: FC<CatalogPetNameApprovalViewProps> = p
useUiEvent(CatalogEvent.APPROVE_NAME_RESULT, onCatalogNameResultEvent); useUiEvent(CatalogEvent.APPROVE_NAME_RESULT, onCatalogNameResultEvent);
const validationErrorMessage = useMemo(() => const validationErrorMessage = () =>
{ {
let key: string = ''; let key: string = '';
@ -49,7 +51,12 @@ export const CatalogPetNameApprovalView: FC<CatalogPetNameApprovalViewProps> = p
} }
return LocalizeText(key); return LocalizeText(key);
}, [ validationResult ]); }
useEffect(() =>
{
setValidationResult(-1);
}, [ petNameValue ]);
return ( return (
<div className="input-group has-validation"> <div className="input-group has-validation">

View File

@ -1,8 +1,8 @@
import { FC } from 'react'; import { FC } from 'react';
import { CatalogLayoutProps } from '../CatalogLayout.types';
import { CatalogLayoutPets3View } from '../pets3/CatalogLayoutPets3View'; import { CatalogLayoutPets3View } from '../pets3/CatalogLayoutPets3View';
import { CatalogLayoutPets2ViewProps } from './CatalogLayoutPets2View.types';
export const CatalogLayoutPets2View: FC<CatalogLayoutPets2ViewProps> = props => export const CatalogLayoutPets2View: FC<CatalogLayoutProps> = props =>
{ {
return <CatalogLayoutPets3View { ...props } /> return <CatalogLayoutPets3View { ...props } />
} }

View File

@ -0,0 +1,28 @@
import { FC } from 'react';
import { Base } from '../../../../../../common/Base';
import { Column } from '../../../../../../common/Column';
import { Flex } from '../../../../../../common/Flex';
import { GetCatalogPageImage, GetCatalogPageText } from '../../../../common/CatalogUtilities';
import { CatalogLayoutProps } from '../CatalogLayout.types';
export const CatalogLayoutPets3View: FC<CatalogLayoutProps> = props =>
{
const { pageParser = null } = props;
const imageUrl = GetCatalogPageImage(pageParser, 1);
return (
<Column grow className="bg-muted rounded text-black p-2" overflow="hidden">
<Flex alignItems="center" gap={ 2 }>
{ imageUrl && <img alt="" src={ GetCatalogPageImage(pageParser, 1) } /> }
<Base className="fs-5" dangerouslySetInnerHTML={ { __html: GetCatalogPageText(pageParser, 1) } } />
</Flex>
<Column grow alignItems="center" overflow="auto">
<Base dangerouslySetInnerHTML={ { __html: GetCatalogPageText(pageParser, 2) } } />
</Column>
<Flex alignItems="center">
<Base className="fw-bold" dangerouslySetInnerHTML={ { __html: GetCatalogPageText(pageParser, 3) } } />
</Flex>
</Column>
);
}

View File

@ -1,31 +1,29 @@
import { FC } from 'react'; import { FC } from 'react';
import { NitroCardGridView, NitroLayoutGrid, NitroLayoutGridColumn } from '../../../../../../layout'; import { Column } from '../../../../../../common/Column';
import { Grid } from '../../../../../../common/Grid';
import { useCatalogContext } from '../../../../context/CatalogContext'; import { useCatalogContext } from '../../../../context/CatalogContext';
import { CatalogPageDetailsView } from '../../../page-details/CatalogPageDetailsView'; import { CatalogPageDetailsView } from '../../../page-details/CatalogPageDetailsView';
import { CatalogProductView } from '../../product/CatalogProductView'; import { CatalogProductView } from '../../product/CatalogProductView';
import { CatalogPurchaseView } from '../../purchase/CatalogPurchaseView'; import { CatalogPurchaseView } from '../../purchase/CatalogPurchaseView';
import { CatalogLayoutSingleBundleViewProps } from './CatalogLayoutSingleBundleView.types'; import { CatalogLayoutProps } from '../CatalogLayout.types';
export const CatalogLayoutSingleBundleView: FC<CatalogLayoutSingleBundleViewProps> = props => export const CatalogLayoutSingleBundleView: FC<CatalogLayoutProps> = props =>
{ {
const { roomPreviewer = null, pageParser = null } = props; const { roomPreviewer = null, pageParser = null } = props;
const { catalogState = null, dispatchCatalogState = null } = useCatalogContext(); const { catalogState = null, dispatchCatalogState = null } = useCatalogContext();
const { activeOffer = null } = catalogState; const { activeOffer = null } = catalogState;
return ( return (
<NitroLayoutGrid> <Grid>
<NitroLayoutGridColumn size={ 7 }> <Column size={ 7 } overflow="hidden">
<NitroCardGridView> <Grid grow overflow="auto">
{ activeOffer && activeOffer.products && (activeOffer.products.length > 0) && activeOffer.products.map((product, index) => { activeOffer && activeOffer.products && (activeOffer.products.length > 0) && activeOffer.products.map((product, index) => <CatalogProductView key={ index } itemActive={ false } product={ product } />)}
{ </Grid>
return <CatalogProductView key={ index } isActive={ false } product={ product } /> </Column>
}) } <Column size={ 5 } overflow="hidden">
</NitroCardGridView>
</NitroLayoutGridColumn>
<NitroLayoutGridColumn size={ 5 }>
<CatalogPageDetailsView pageParser={ pageParser } /> <CatalogPageDetailsView pageParser={ pageParser } />
{ activeOffer && <CatalogPurchaseView offer={ activeOffer } pageId={ pageParser.pageId } /> } { activeOffer && <CatalogPurchaseView offer={ activeOffer } pageId={ pageParser.pageId } /> }
</NitroLayoutGridColumn> </Column>
</NitroLayoutGrid> </Grid>
); );
} }

View File

@ -1,14 +1,17 @@
import { CatalogPageMessageOfferData, IFurnitureData } from '@nitrots/nitro-renderer'; import { CatalogPageMessageOfferData, IFurnitureData } from '@nitrots/nitro-renderer';
import { FC, useEffect, useState } from 'react'; import { FC, useEffect, useState } from 'react';
import { GetSessionDataManager, LocalizeText } from '../../../../../../api'; import { GetSessionDataManager, LocalizeText } from '../../../../../../api';
import { NitroLayoutGrid, NitroLayoutGridColumn } from '../../../../../../layout'; import { Button } from '../../../../../../common/Button';
import { ButtonGroup } from '../../../../../../common/ButtonGroup';
import { Column } from '../../../../../../common/Column';
import { Grid } from '../../../../../../common/Grid';
import { ProductTypeEnum } from '../../../../common/ProductTypeEnum'; import { ProductTypeEnum } from '../../../../common/ProductTypeEnum';
import { useCatalogContext } from '../../../../context/CatalogContext'; import { useCatalogContext } from '../../../../context/CatalogContext';
import { CatalogPageOffersView } from '../../offers/CatalogPageOffersView'; import { CatalogPageOffersView } from '../../offers/CatalogPageOffersView';
import { CatalogProductPreviewView } from '../../product-preview/CatalogProductPreviewView'; import { CatalogProductPreviewView } from '../../product-preview/CatalogProductPreviewView';
import { CatalogLayoutSpacesViewProps } from './CatalogLayoutSpacesView.types'; import { CatalogLayoutProps } from '../CatalogLayout.types';
export const CatalogLayoutSpacesView: FC<CatalogLayoutSpacesViewProps> = props => export const CatalogLayoutSpacesView: FC<CatalogLayoutProps> = props =>
{ {
const { roomPreviewer = null, pageParser = null } = props; const { roomPreviewer = null, pageParser = null } = props;
const [ groups, setGroups ] = useState<CatalogPageMessageOfferData[][]>([]); const [ groups, setGroups ] = useState<CatalogPageMessageOfferData[][]>([]);
@ -67,19 +70,16 @@ export const CatalogLayoutSpacesView: FC<CatalogLayoutSpacesViewProps> = props =
const product = ((activeOffer && activeOffer.products[0]) || null); const product = ((activeOffer && activeOffer.products[0]) || null);
return ( return (
<NitroLayoutGrid> <Grid>
<NitroLayoutGridColumn size={ 7 }> <Column size={ 7 } overflow="hidden">
<div className="btn-group w-100"> <ButtonGroup>
{ groupNames.map((name, index) => { groupNames.map((name, index) => <Button key={ index } size="sm" active={ (activeGroupIndex === index) } onClick={ event => setActiveGroupIndex(index) }>{ LocalizeText(`catalog.spaces.tab.${ name }`) }</Button>)}
{ </ButtonGroup>
return <button key={ index } type="button" className={ 'btn btn-primary btn-sm ' + ((activeGroupIndex === index) ? 'active ' : '' )} onClick={ event => setActiveGroupIndex(index) }>{ LocalizeText(`catalog.spaces.tab.${ name }`) }</button>
})}
</div>
<CatalogPageOffersView offers={ groups[activeGroupIndex] } /> <CatalogPageOffersView offers={ groups[activeGroupIndex] } />
</NitroLayoutGridColumn> </Column>
<NitroLayoutGridColumn size={ 5 }> <Column size={ 5 } overflow="hidden">
<CatalogProductPreviewView pageParser={ pageParser } activeOffer={ activeOffer } roomPreviewer={ roomPreviewer } /> <CatalogProductPreviewView pageParser={ pageParser } activeOffer={ activeOffer } roomPreviewer={ roomPreviewer } />
</NitroLayoutGridColumn> </Column>
</NitroLayoutGrid> </Grid>
); );
} }

View File

@ -1,11 +1,12 @@
import { FC, useState } from 'react'; import { FC, useState } from 'react';
import { NitroLayoutGrid, NitroLayoutGridColumn } from '../../../../../../layout'; import { Column } from '../../../../../../common/Column';
import { Grid } from '../../../../../../common/Grid';
import { useCatalogContext } from '../../../../context/CatalogContext'; import { useCatalogContext } from '../../../../context/CatalogContext';
import { CatalogPageOffersView } from '../../offers/CatalogPageOffersView'; import { CatalogPageOffersView } from '../../offers/CatalogPageOffersView';
import { CatalogProductPreviewView } from '../../product-preview/CatalogProductPreviewView'; import { CatalogProductPreviewView } from '../../product-preview/CatalogProductPreviewView';
import { CatalogLayoutTrophiesViewProps } from './CatalogLayoutTrophiesView.types'; import { CatalogLayoutProps } from '../CatalogLayout.types';
export const CatalogLayoutTrophiesView: FC<CatalogLayoutTrophiesViewProps> = props => export const CatalogLayoutTrophiesView: FC<CatalogLayoutProps> = props =>
{ {
const { roomPreviewer = null, pageParser = null } = props; const { roomPreviewer = null, pageParser = null } = props;
const { catalogState = null, dispatchCatalogState = null } = useCatalogContext(); const { catalogState = null, dispatchCatalogState = null } = useCatalogContext();
@ -15,14 +16,14 @@ export const CatalogLayoutTrophiesView: FC<CatalogLayoutTrophiesViewProps> = pro
const product = ((activeOffer && activeOffer.products[0]) || null); const product = ((activeOffer && activeOffer.products[0]) || null);
return ( return (
<NitroLayoutGrid> <Grid>
<NitroLayoutGridColumn size={ 7 }> <Column size={ 7 } overflow="hidden">
<CatalogPageOffersView offers={ pageParser.offers } /> <CatalogPageOffersView offers={ pageParser.offers } />
<textarea className="flex-grow-1 form-control w-100" defaultValue={ trophyText || '' } onChange={ event => setTrophyText(event.target.value) } /> <textarea className="flex-grow-1 form-control w-100" defaultValue={ trophyText || '' } onChange={ event => setTrophyText(event.target.value) } />
</NitroLayoutGridColumn> </Column>
<NitroLayoutGridColumn size={ 5 }> <Column size={ 5 } overflow="hidden">
<CatalogProductPreviewView pageParser={ pageParser } activeOffer={ activeOffer } roomPreviewer={ roomPreviewer } extra={ trophyText } /> <CatalogProductPreviewView pageParser={ pageParser } activeOffer={ activeOffer } roomPreviewer={ roomPreviewer } extra={ trophyText } />
</NitroLayoutGridColumn> </Column>
</NitroLayoutGrid> </Grid>
); );
} }

View File

@ -1,29 +1,30 @@
import { ClubOfferData, GetClubOffersMessageComposer, PurchaseFromCatalogComposer } from '@nitrots/nitro-renderer'; import { ClubOfferData, GetClubOffersMessageComposer, PurchaseFromCatalogComposer } from '@nitrots/nitro-renderer';
import { FC, useCallback, useEffect, useMemo, useState } from 'react'; import { FC, useCallback, useEffect, useMemo, useState } from 'react';
import { LocalizeText } from '../../../../../../api'; import { LocalizeText } from '../../../../../../api';
import { Button } from '../../../../../../common/Button';
import { Column } from '../../../../../../common/Column';
import { Flex } from '../../../../../../common/Flex';
import { Grid } from '../../../../../../common/Grid';
import { LayoutGridItem } from '../../../../../../common/layout/LayoutGridItem';
import { Text } from '../../../../../../common/Text';
import { CatalogEvent } from '../../../../../../events/catalog/CatalogEvent'; import { CatalogEvent } from '../../../../../../events/catalog/CatalogEvent';
import { useUiEvent } from '../../../../../../hooks'; import { useUiEvent } from '../../../../../../hooks';
import { SendMessageHook } from '../../../../../../hooks/messages/message-event'; import { SendMessageHook } from '../../../../../../hooks/messages/message-event';
import { NitroLayoutFlexColumn, NitroLayoutGrid, NitroLayoutGridColumn } from '../../../../../../layout';
import { NitroLayoutBase } from '../../../../../../layout/base';
import { NitroCardGridItemView } from '../../../../../../layout/card/grid/item/NitroCardGridItemView';
import { NitroCardGridView } from '../../../../../../layout/card/grid/NitroCardGridView';
import { LoadingSpinnerView } from '../../../../../../layout/loading-spinner/LoadingSpinnerView'; import { LoadingSpinnerView } from '../../../../../../layout/loading-spinner/LoadingSpinnerView';
import { GetCurrencyAmount } from '../../../../../purse/common/CurrencyHelper'; import { GetCurrencyAmount } from '../../../../../../views/purse/common/CurrencyHelper';
import { GLOBAL_PURSE } from '../../../../../purse/PurseView'; import { GLOBAL_PURSE } from '../../../../../../views/purse/PurseView';
import { CurrencyIcon } from '../../../../../shared/currency-icon/CurrencyIcon'; import { CurrencyIcon } from '../../../../../../views/shared/currency-icon/CurrencyIcon';
import { CatalogPurchaseState } from '../../../../common/CatalogPurchaseState';
import { GetCatalogPageImage } from '../../../../common/CatalogUtilities'; import { GetCatalogPageImage } from '../../../../common/CatalogUtilities';
import { useCatalogContext } from '../../../../context/CatalogContext'; import { useCatalogContext } from '../../../../context/CatalogContext';
import { CatalogPurchaseState } from '../../purchase/purchase-button/CatalogPurchaseButtonView.types'; import { CatalogLayoutProps } from '../CatalogLayout.types';
import { CatalogLayoutVipBuyViewProps } from './CatalogLayoutVipBuyView.types';
export const CatalogLayoutVipBuyView: FC<CatalogLayoutVipBuyViewProps> = props => export const CatalogLayoutVipBuyView: FC<CatalogLayoutProps> = props =>
{ {
const { catalogState = null } = useCatalogContext();
const { pageParser = null, clubOffers = null, subscriptionInfo = null } = catalogState;
const [ pendingOffer, setPendingOffer ] = useState<ClubOfferData>(null); const [ pendingOffer, setPendingOffer ] = useState<ClubOfferData>(null);
const [ purchaseState, setPurchaseState ] = useState(CatalogPurchaseState.NONE); const [ purchaseState, setPurchaseState ] = useState(CatalogPurchaseState.NONE);
const { catalogState = null } = useCatalogContext();
const { pageParser = null, clubOffers = null, subscriptionInfo = null } = catalogState;
const onCatalogEvent = useCallback((event: CatalogEvent) => const onCatalogEvent = useCallback((event: CatalogEvent) =>
{ {
@ -104,11 +105,6 @@ export const CatalogLayoutVipBuyView: FC<CatalogLayoutVipBuyViewProps> = props =
SendMessageHook(new PurchaseFromCatalogComposer(pageParser.pageId, pendingOffer.offerId, null, 1)); SendMessageHook(new PurchaseFromCatalogComposer(pageParser.pageId, pendingOffer.offerId, null, 1));
}, [ pendingOffer, pageParser ]); }, [ pendingOffer, pageParser ]);
useEffect(() =>
{
if(clubOffers === null) SendMessageHook(new GetClubOffersMessageComposer(1));
}, [ clubOffers ]);
const setOffer = useCallback((offer: ClubOfferData) => const setOffer = useCallback((offer: ClubOfferData) =>
{ {
setPurchaseState(CatalogPurchaseState.NONE); setPurchaseState(CatalogPurchaseState.NONE);
@ -121,85 +117,91 @@ export const CatalogLayoutVipBuyView: FC<CatalogLayoutVipBuyViewProps> = props =
if(pendingOffer.priceCredits > GetCurrencyAmount(-1)) if(pendingOffer.priceCredits > GetCurrencyAmount(-1))
{ {
return <button className="btn btn-danger btn-sm w-100">{ LocalizeText('catalog.alert.notenough.title') }</button>; return <Button fullWidth variant="danger" size="sm">{ LocalizeText('catalog.alert.notenough.title') }</Button>;
} }
if(pendingOffer.priceActivityPoints > GetCurrencyAmount(pendingOffer.priceActivityPointsType)) if(pendingOffer.priceActivityPoints > GetCurrencyAmount(pendingOffer.priceActivityPointsType))
{ {
return <button className="btn btn-danger btn-sm w-100">{ LocalizeText('catalog.alert.notenough.activitypoints.title.' + pendingOffer.priceActivityPointsType) }</button>; return <Button fullWidth variant="danger" size="sm">{ LocalizeText('catalog.alert.notenough.activitypoints.title.' + pendingOffer.priceActivityPointsType) }</Button>;
} }
switch(purchaseState) switch(purchaseState)
{ {
case CatalogPurchaseState.CONFIRM: case CatalogPurchaseState.CONFIRM:
return <button type="button" className="btn btn-warning w-100" onClick={ purchaseSubscription }>{ LocalizeText('catalog.marketplace.confirm_title') }</button>; return <Button fullWidth variant="warning" size="sm" onClick={ purchaseSubscription }>{ LocalizeText('catalog.marketplace.confirm_title') }</Button>;
case CatalogPurchaseState.PURCHASE: case CatalogPurchaseState.PURCHASE:
return <button type="button" className="btn btn-primary w-100" disabled><LoadingSpinnerView /></button>; return <Button fullWidth variant="primary" size="sm" disabled><LoadingSpinnerView /></Button>;
case CatalogPurchaseState.FAILED: case CatalogPurchaseState.FAILED:
return <button type="button" className="btn btn-danger w-100" disabled>{ LocalizeText('generic.failed') }</button>; return <Button fullWidth variant="danger" size="sm" disabled>{ LocalizeText('generic.failed') }</Button>;
case CatalogPurchaseState.NONE: case CatalogPurchaseState.NONE:
default: default:
return <button type="button" className="btn btn-success w-100" onClick={ () => setPurchaseState(CatalogPurchaseState.CONFIRM) }>{ LocalizeText('buy') }</button>; return <Button fullWidth variant="success" size="sm" onClick={ () => setPurchaseState(CatalogPurchaseState.CONFIRM) }>{ LocalizeText('buy') }</Button>;
} }
}, [ pendingOffer, purchaseState, purchaseSubscription ]); }, [ pendingOffer, purchaseState, purchaseSubscription ]);
useEffect(() =>
{
if(!clubOffers) SendMessageHook(new GetClubOffersMessageComposer(1));
}, [ clubOffers ]);
return ( return (
<NitroLayoutGrid> <Grid>
<NitroLayoutGridColumn size={ 7 }> <Column fullHeight size={ 7 } overflow="hidden">
<NitroCardGridView columns={ 1 } className="vip-buy-grid"> <Grid grow columnCount={ 1 } className="nitro-catalog-layout-vip-buy-grid" overflow="auto">
{ clubOffers && (clubOffers.length > 0) && clubOffers.map((offer, index) => { clubOffers && (clubOffers.length > 0) && clubOffers.map((offer, index) =>
{ {
return ( return (
<NitroCardGridItemView key={ index } className="justify-content-between py-1 px-2 text-black" itemActive={ pendingOffer === offer } onClick={ () => setOffer(offer) }> <LayoutGridItem key={ index } column={ false } center={ false } alignItems="center" justifyContent="between" itemActive={ pendingOffer === offer } className="p-1" onClick={ () => setOffer(offer) }>
<i className="icon icon-hc-banner" /> <i className="icon-hc-banner" />
<div className="fw-bold"> <Column justifyContent="end" gap={ 0 }>
<div className="text-end">{ getOfferText(offer) }</div> <Text textEnd>{ getOfferText(offer) }</Text>
<div className="d-flex gap-2 justify-content-end"> <Flex justifyContent="end" gap={ 1 }>
{ (offer.priceCredits > 0) && { (offer.priceCredits > 0) &&
<div className="d-flex align-items-center justify-content-end gap-1"> <Flex alignItems="center" justifyContent="end" gap={ 1 }>
<span className="text-black">{ offer.priceCredits }</span> <Text>{ offer.priceCredits }</Text>
<CurrencyIcon type={ -1 } /> <CurrencyIcon type={ -1 } />
</div> } </Flex> }
{ (offer.priceActivityPoints > 0) && { (offer.priceActivityPoints > 0) &&
<div className="d-flex align-items-center justify-content-end gap-1"> <Flex alignItems="center" justifyContent="end" gap={ 1 }>
<span className="text-black">{ offer.priceActivityPoints }</span> <Text>{ offer.priceActivityPoints }</Text>
<CurrencyIcon type={ offer.priceActivityPointsType } /> <CurrencyIcon type={ offer.priceActivityPointsType } />
</div> } </Flex> }
</div> </Flex>
</div> </Column>
</NitroCardGridItemView> </LayoutGridItem>
); );
})} })}
<div className="mt-auto text-black">{ LocalizeText('catalog.vip.buy.hccenter') }</div> <Text dangerouslySetInnerHTML={{ __html: LocalizeText('catalog.vip.buy.hccenter') }}></Text>
</NitroCardGridView> </Grid>
</NitroLayoutGridColumn> </Column>
<NitroLayoutGridColumn size={ 5 }> <Column size={ 5 } overflow="hidden">
<NitroLayoutFlexColumn className="justify-content-center align-items-center h-100" overflow="hidden" gap={ 2 }> <Column fullHeight center overflow="hidden">
{ GetCatalogPageImage(pageParser, 1) && <img className="" alt="" src={ GetCatalogPageImage(pageParser, 1) } /> } { GetCatalogPageImage(pageParser, 1) && <img alt="" src={ GetCatalogPageImage(pageParser, 1) } /> }
<NitroLayoutBase className="text-center text-black" overflow="auto" dangerouslySetInnerHTML={{ __html: getSubscriptionDetails }} /> <Text center overflow="auto" dangerouslySetInnerHTML={{ __html: getSubscriptionDetails }} />
</NitroLayoutFlexColumn> </Column>
{ pendingOffer && <div className="mt-auto w-100 text-black"> { pendingOffer &&
<div className="d-flex gap-2 mb-2 align-items-center"> <Column fullWidth grow justifyContent="end">
<div className="w-100"> <Flex alignItems="end">
<div className="fw-bold">{ getPurchaseHeader() }</div> <Column grow gap={ 0 }>
<div className="small">{ getPurchaseValidUntil() }</div> <Text fontWeight="bold">{ getPurchaseHeader() }</Text>
</div> <Text>{ getPurchaseValidUntil() }</Text>
<div> </Column>
<Column gap={ 1 }>
{ (pendingOffer.priceCredits > 0) && { (pendingOffer.priceCredits > 0) &&
<div className="d-flex align-items-center justify-content-end gap-1"> <Flex alignItems="center" justifyContent="end" gap={ 1 }>
<span className="text-black">{ pendingOffer.priceCredits }</span> <Text>{ pendingOffer.priceCredits }</Text>
<CurrencyIcon type={ -1 } /> <CurrencyIcon type={ -1 } />
</div> } </Flex> }
{ (pendingOffer.priceActivityPoints > 0) && { (pendingOffer.priceActivityPoints > 0) &&
<div className="d-flex align-items-center justify-content-end gap-1"> <Flex alignItems="center" justifyContent="end" gap={ 1 }>
<span className="text-black">{ pendingOffer.priceActivityPoints }</span> <Text>{ pendingOffer.priceActivityPoints }</Text>
<CurrencyIcon type={ pendingOffer.priceActivityPointsType } /> <CurrencyIcon type={ pendingOffer.priceActivityPointsType } />
</div> } </Flex> }
</div> </Column>
</div> </Flex>
{ getPurchaseButton() } { getPurchaseButton() }
</div> } </Column> }
</NitroLayoutGridColumn> </Column>
</NitroLayoutGrid> </Grid>
); );
} }

View File

@ -1,9 +1,14 @@
import { MouseEventType } from '@nitrots/nitro-renderer'; import { CatalogPageMessageOfferData, MouseEventType } from '@nitrots/nitro-renderer';
import { FC, MouseEvent, useCallback, useState } from 'react'; import { FC, MouseEvent, useCallback, useState } from 'react';
import { useCatalogContext } from '../../../context/CatalogContext'; import { useCatalogContext } from '../../../context/CatalogContext';
import { CatalogActions } from '../../../reducers/CatalogReducer'; import { CatalogActions } from '../../../reducers/CatalogReducer';
import { CatalogProductView } from '../product/CatalogProductView'; import { CatalogProductView } from '../product/CatalogProductView';
import { CatalogPageOfferViewProps } from './CatalogPageOfferView.types';
export interface CatalogPageOfferViewProps
{
isActive: boolean;
offer: CatalogPageMessageOfferData;
}
export const CatalogPageOfferView: FC<CatalogPageOfferViewProps> = props => export const CatalogPageOfferView: FC<CatalogPageOfferViewProps> = props =>
{ {
@ -41,5 +46,5 @@ export const CatalogPageOfferView: FC<CatalogPageOfferViewProps> = props =>
if(!product) return null; if(!product) return null;
return <CatalogProductView isActive={ isActive } product={ product } onClick={ onMouseEvent } onMouseDown={ onMouseEvent } onMouseUp={ onMouseEvent } onMouseOut={ onMouseEvent } /> return <CatalogProductView itemActive={ isActive } product={ product } onClick={ onMouseEvent } onMouseDown={ onMouseEvent } onMouseUp={ onMouseEvent } onMouseOut={ onMouseEvent } />
} }

View File

@ -0,0 +1,24 @@
import { CatalogPageMessageOfferData } from '@nitrots/nitro-renderer';
import { FC } from 'react';
import { Grid, GridProps } from '../../../../../common/Grid';
import { useCatalogContext } from '../../../context/CatalogContext';
import { CatalogPageOfferView } from './CatalogPageOfferView';
export interface CatalogPageOffersViewProps extends GridProps
{
offers: CatalogPageMessageOfferData[];
}
export const CatalogPageOffersView: FC<CatalogPageOffersViewProps> = props =>
{
const { offers = [], children = null, ...rest } = props;
const { catalogState = null } = useCatalogContext();
const { activeOffer = null } = catalogState;
return (
<Grid grow columnCount={ 5 } overflow="auto" { ...rest }>
{ offers && (offers.length > 0) && offers.map((offer, index) => <CatalogPageOfferView key={ index } isActive={ (activeOffer === offer) } offer={ offer } />) }
{ children }
</Grid>
);
}

View File

@ -0,0 +1,51 @@
import { CatalogPageMessageOfferData, CatalogPageMessageParser, RoomPreviewer } from '@nitrots/nitro-renderer';
import { FC } from 'react';
import { Base } from '../../../../../common/Base';
import { Column } from '../../../../../common/Column';
import { Text } from '../../../../../common/Text';
import { BadgeImageView } from '../../../../../views/shared/badge-image/BadgeImageView';
import { LimitedEditionCompletePlateView } from '../../../../../views/shared/limited-edition/LimitedEditionCompletePlateView';
import { GetOfferName } from '../../../common/CatalogUtilities';
import { CatalogRoomPreviewerView } from '../../catalog-room-previewer/CatalogRoomPreviewerView';
import { CatalogPageDetailsView } from '../../page-details/CatalogPageDetailsView';
import { CatalogPurchaseView } from '../purchase/CatalogPurchaseView';
export interface CatalogProductPreviewViewProps
{
pageParser: CatalogPageMessageParser;
activeOffer: CatalogPageMessageOfferData;
roomPreviewer: RoomPreviewer;
badgeCode?: string;
extra?: string;
disabled?: boolean;
}
export const CatalogProductPreviewView: FC<CatalogProductPreviewViewProps> = props =>
{
const { pageParser = null, activeOffer = null, roomPreviewer = null, badgeCode = null, extra = '', disabled = false, children = null } = props;
const product = ((activeOffer && activeOffer.products[0]) || null);
if(!product) return <CatalogPageDetailsView pageParser={ pageParser } />;
return (
<>
<Column overflow="hidden" position="relative" gap={ 0 }>
{ roomPreviewer && <CatalogRoomPreviewerView roomPreviewer={ roomPreviewer } height={ 140 } /> }
{ product.uniqueLimitedItem &&
<Base fullWidth position="absolute" className="top-1">
<LimitedEditionCompletePlateView className="mx-auto" uniqueLimitedItemsLeft={ product.uniqueLimitedItemsLeft } uniqueLimitedSeriesSize={ product.uniqueLimitedSeriesSize } />
</Base> }
{ badgeCode && badgeCode.length &&
<Base position="absolute" className="top-1 end-1">
<BadgeImageView badgeCode={ badgeCode } isGroup={ true } />
</Base> }
</Column>
<Column grow>
<Text grow truncate>{ GetOfferName(activeOffer) }</Text>
{ children }
<CatalogPurchaseView offer={ activeOffer } pageId={ pageParser.pageId } extra={ extra } disabled={ disabled } />
</Column>
</>
);
}

View File

@ -0,0 +1,27 @@
import { CatalogPageMessageProductData } from '@nitrots/nitro-renderer';
import { FC } from 'react';
import { LayoutGridItem, LayoutGridItemProps } from '../../../../../common/layout/LayoutGridItem';
import { AvatarImageView } from '../../../../../views/shared/avatar-image/AvatarImageView';
import { GetProductIconUrl } from '../../../common/GetProuductIconUrl';
import { ProductTypeEnum } from '../../../common/ProductTypeEnum';
export interface CatalogProductViewProps extends LayoutGridItemProps
{
product: CatalogPageMessageProductData;
}
export const CatalogProductView: FC<CatalogProductViewProps> = props =>
{
const { product = null, ...rest } = props;
if(!product) return null;
const iconUrl = GetProductIconUrl(product.furniClassId, product.productType, product.extraParam);
return (
<LayoutGridItem itemImage={ iconUrl } itemCount={ product.productCount } itemUniqueSoldout={ (product.uniqueLimitedSeriesSize && !product.uniqueLimitedItemsLeft) } itemUniqueNumber={ product.uniqueLimitedSeriesSize } { ...rest }>
{ (product.productType === ProductTypeEnum.ROBOT) &&
<AvatarImageView figure={ product.extraParam } direction={ 3 } headOnly={ true } /> }
</LayoutGridItem>
);
}

View File

@ -1,16 +1,28 @@
import { PurchaseFromCatalogComposer } from '@nitrots/nitro-renderer'; import { CatalogPageMessageOfferData, PurchaseFromCatalogComposer } from '@nitrots/nitro-renderer';
import { FC, useCallback, useEffect, useState } from 'react'; import { FC, useCallback, useEffect, useState } from 'react';
import { LocalizeText } from '../../../../../../api'; import { LocalizeText } from '../../../../../api';
import { CatalogEvent } from '../../../../../../events'; import { Button, ButtonProps } from '../../../../../common/Button';
import { useUiEvent } from '../../../../../../hooks/events/ui/ui-event'; import { CatalogEvent } from '../../../../../events';
import { SendMessageHook } from '../../../../../../hooks/messages/message-event'; import { BatchUpdates } from '../../../../../hooks';
import { LoadingSpinnerView } from '../../../../../../layout'; import { useUiEvent } from '../../../../../hooks/events/ui/ui-event';
import { GetCurrencyAmount } from '../../../../../purse/common/CurrencyHelper'; import { SendMessageHook } from '../../../../../hooks/messages/message-event';
import { CatalogPurchaseButtonViewProps, CatalogPurchaseState } from './CatalogPurchaseButtonView.types'; import { LoadingSpinnerView } from '../../../../../layout';
import { GetCurrencyAmount } from '../../../../../views/purse/common/CurrencyHelper';
import { CatalogPurchaseState } from '../../../common/CatalogPurchaseState';
export interface CatalogPurchaseButtonViewProps extends ButtonProps
{
offer: CatalogPageMessageOfferData;
pageId: number;
extra?: string;
quantity?: number;
isPurchaseAllowed?: boolean;
beforePurchase?: () => void;
}
export const CatalogPurchaseButtonView: FC<CatalogPurchaseButtonViewProps> = props => export const CatalogPurchaseButtonView: FC<CatalogPurchaseButtonViewProps> = props =>
{ {
const { className = '', offer = null, pageId = -1, extra = null, quantity = 1, isPurchaseAllowed = true, disabled = false, beforePurchase = null } = props; const { offer = null, pageId = -1, extra = null, quantity = 1, isPurchaseAllowed = true, beforePurchase = null, ...rest } = props;
const [ purchaseState, setPurchaseState ] = useState(CatalogPurchaseState.NONE); const [ purchaseState, setPurchaseState ] = useState(CatalogPurchaseState.NONE);
const [ pendingApproval, setPendingApproval ] = useState(false); const [ pendingApproval, setPendingApproval ] = useState(false);
@ -46,9 +58,12 @@ export const CatalogPurchaseButtonView: FC<CatalogPurchaseButtonViewProps> = pro
if(beforePurchase) beforePurchase(); if(beforePurchase) beforePurchase();
if(!isPurchaseAllowed) if(!isPurchaseAllowed)
{
BatchUpdates(() =>
{ {
setPendingApproval(true); setPendingApproval(true);
setPurchaseState(CatalogPurchaseState.NONE); setPurchaseState(CatalogPurchaseState.NONE);
});
return; return;
} }
@ -77,31 +92,31 @@ export const CatalogPurchaseButtonView: FC<CatalogPurchaseButtonViewProps> = pro
if(product && product.uniqueLimitedItem && !product.uniqueLimitedItemsLeft) if(product && product.uniqueLimitedItem && !product.uniqueLimitedItemsLeft)
{ {
return <button type="button" className={ 'btn btn-danger ' + className } disabled>{ LocalizeText('catalog.alert.limited_edition_sold_out.title') }</button>; return <Button variant="danger" size="sm" disabled>{ LocalizeText('catalog.alert.limited_edition_sold_out.title') }</Button>;
} }
if((offer.priceCredits * quantity) > GetCurrencyAmount(-1)) if((offer.priceCredits * quantity) > GetCurrencyAmount(-1))
{ {
return <button type="button" className={ 'btn btn-danger ' + className } disabled>{ LocalizeText('catalog.alert.notenough.title') }</button>; return <Button variant="danger" size="sm" disabled>{ LocalizeText('catalog.alert.notenough.title') }</Button>;
} }
if((offer.priceActivityPoints * quantity) > GetCurrencyAmount(offer.priceActivityPointsType)) if((offer.priceActivityPoints * quantity) > GetCurrencyAmount(offer.priceActivityPointsType))
{ {
return <button type="button" className={ 'btn btn-danger ' + className } disabled>{ LocalizeText('catalog.alert.notenough.activitypoints.title.' + offer.priceActivityPointsType) }</button>; return <Button variant="danger" size="sm" disabled>{ LocalizeText('catalog.alert.notenough.activitypoints.title.' + offer.priceActivityPointsType) }</Button>;
} }
switch(purchaseState) switch(purchaseState)
{ {
case CatalogPurchaseState.CONFIRM: case CatalogPurchaseState.CONFIRM:
return <button type="button" className={ 'btn btn-warning ' + className } onClick={ attemptPurchase }>{ LocalizeText('catalog.marketplace.confirm_title') }</button>; return <Button variant="warning" size="sm" onClick={ attemptPurchase } { ...rest }>{ LocalizeText('catalog.marketplace.confirm_title') }</Button>;
case CatalogPurchaseState.PURCHASE: case CatalogPurchaseState.PURCHASE:
return <button type="button" className={ 'btn btn-primary ' + className } disabled><LoadingSpinnerView /></button>; return <Button variant="primary" size="sm" disabled { ...rest }><LoadingSpinnerView /></Button>;
case CatalogPurchaseState.SOLD_OUT: case CatalogPurchaseState.SOLD_OUT:
return <button type="button" className={ 'btn btn-danger ' + className } disabled>{ LocalizeText('generic.failed') + ' - ' + LocalizeText('catalog.alert.limited_edition_sold_out.title') }</button>; return <Button variant="danger" size="sm" disabled { ...rest }>{ LocalizeText('generic.failed') + ' - ' + LocalizeText('catalog.alert.limited_edition_sold_out.title') }</Button>;
case CatalogPurchaseState.FAILED: case CatalogPurchaseState.FAILED:
return <button type="button" className={ 'btn btn-danger ' + className } disabled>{ 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 type="button" className={ 'btn btn-success ' + className } disabled={ disabled } onClick={ event => setPurchaseState(CatalogPurchaseState.CONFIRM) }>{ LocalizeText('buy') }</button> return <Button variant="success" size="sm" onClick={ event => setPurchaseState(CatalogPurchaseState.CONFIRM) }>{ LocalizeText('buy') }</Button>
} }
} }

View File

@ -0,0 +1,25 @@
import { CatalogPageMessageOfferData } from '@nitrots/nitro-renderer';
import { FC } from 'react';
import { LocalizeText } from '../../../../../api';
import { Button, ButtonProps } from '../../../../../common/Button';
import { CatalogInitGiftEvent } from '../../../../../events/catalog/CatalogInitGiftEvent';
import { dispatchUiEvent } from '../../../../../hooks';
export interface CatalogPurchaseGiftButtonViewProps extends ButtonProps
{
offer: CatalogPageMessageOfferData;
pageId: number;
extra?: string;
}
export const CatalogPurchaseGiftButtonView: FC<CatalogPurchaseGiftButtonViewProps> = props =>
{
const { offer = null, pageId = -1, extra = null, ...rest } = props;
const initGift = () =>
{
dispatchUiEvent(new CatalogInitGiftEvent(pageId, offer.offerId, extra));
}
return <Button variant="secondary" onClick={ initGift } { ...rest }>{ LocalizeText('catalog.purchase_confirmation.gift') }</Button>;
}

View File

@ -0,0 +1,90 @@
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { CatalogPageMessageOfferData } from '@nitrots/nitro-renderer';
import { FC, useEffect, useState } from 'react';
import { LocalizeText } from '../../../../../api';
import { Column } from '../../../../../common/Column';
import { Flex } from '../../../../../common/Flex';
import { Text } from '../../../../../common/Text';
import { CurrencyIcon } from '../../../../../views/shared/currency-icon/CurrencyIcon';
import { CatalogPurchaseButtonView } from './CatalogPurchaseButtonView';
import { CatalogPurchaseGiftButtonView } from './CatalogPurchaseGiftButtonView';
export interface CatalogPurchaseViewProps
{
offer: CatalogPageMessageOfferData;
pageId: number;
extra?: string;
disabled?: boolean;
}
export const CatalogPurchaseView: FC<CatalogPurchaseViewProps> = props =>
{
const { offer = null, pageId = -1, extra = '', disabled = false } = props;
const [ quantity, setQuantity ] = useState(1);
const increaseQuantity = () =>
{
let newQuantity = quantity + 1;
if(newQuantity > 99) newQuantity = 99
setQuantity(newQuantity);
}
const decreaseQuantity = () =>
{
let newQuantity = quantity - 1;
if(newQuantity <= 0) newQuantity = 1;
setQuantity(newQuantity);
}
const updateQuantity = (amount: number) =>
{
if(isNaN(amount) || (amount <= 0)) amount = 1;
if(amount > 99) amount = 99;
setQuantity(amount);
}
useEffect(() =>
{
setQuantity(1);
}, [ offer ]);
const extraData = ((extra && extra.length) ? extra : (offer?.products[0]?.extraParam || null));
return (
<Column fullWidth grow justifyContent="end">
<Flex alignItems="end">
<div className="flex-grow-1 align-items-end">
<Text>{ LocalizeText('catalog.bundlewidget.price') }</Text>
{ offer.bundlePurchaseAllowed &&
<Flex alignItems="center" gap={ 1 }>
<FontAwesomeIcon icon="caret-left" className="text-black cursor-pointer" onClick={ decreaseQuantity } />
<input type="number" className="form-control form-control-sm quantity-input" value={ quantity } onChange={ event => updateQuantity(event.target.valueAsNumber)} />
<FontAwesomeIcon icon="caret-right" className="text-black cursor-pointer" onClick={ increaseQuantity } />
</Flex> }
</div>
<Column gap={ 1 }>
{ (offer.priceCredits > 0) &&
<Flex alignItems="center" justifyContent="end" gap={ 1 }>
<Text>{ offer.priceCredits * quantity }</Text>
<CurrencyIcon type={ -1 } />
</Flex> }
{ (offer.priceActivityPoints > 0) &&
<Flex alignItems="center" justifyContent="end" gap={ 1 }>
<Text>{ offer.priceActivityPoints * quantity }</Text>
<CurrencyIcon type={ offer.priceActivityPointsType } />
</Flex> }
</Column>
</Flex>
<Column gap={ 1 }>
<CatalogPurchaseButtonView className="btn-sm w-100" 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 } /> }
</Column>
</Column>
);
}

View File

@ -0,0 +1,68 @@
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { RedeemVoucherMessageComposer, VoucherRedeemErrorMessageEvent, VoucherRedeemOkMessageEvent } from '@nitrots/nitro-renderer';
import { FC, useCallback, useState } from 'react';
import { LocalizeText } from '../../../../../api';
import { Button } from '../../../../../common/Button';
import { Flex } from '../../../../../common/Flex';
import { BatchUpdates, CreateMessageHook, SendMessageHook } from '../../../../../hooks';
import { NotificationUtilities } from '../../../../../views/notification-center/common/NotificationUtilities';
export interface CatalogRedeemVoucherViewProps
{
text: string;
}
export const CatalogRedeemVoucherView: FC<CatalogRedeemVoucherViewProps> = props =>
{
const { text = null } = props;
const [ voucher, setVoucher ] = useState<string>('');
const [ isWaiting, setIsWaiting ] = useState(false);
const redeemVoucher = () =>
{
if(!voucher || !voucher.length || isWaiting) return;
SendMessageHook(new RedeemVoucherMessageComposer(voucher));
setIsWaiting(true);
}
const onVoucherRedeemOkMessageEvent = useCallback((event: VoucherRedeemOkMessageEvent) =>
{
const parser = event.getParser();
let message = LocalizeText('catalog.alert.voucherredeem.ok.description');
if(parser.productName) message = LocalizeText('catalog.alert.voucherredeem.ok.description.furni', [ 'productName', 'productDescription' ], [ parser.productName, parser.productDescription ]);
NotificationUtilities.simpleAlert(message, null, null, null, LocalizeText('catalog.alert.voucherredeem.ok.title'));
BatchUpdates(() =>
{
setIsWaiting(false);
setVoucher('');
});
}, []);
CreateMessageHook(VoucherRedeemOkMessageEvent, onVoucherRedeemOkMessageEvent);
const onVoucherRedeemErrorMessageEvent = useCallback((event: VoucherRedeemErrorMessageEvent) =>
{
const parser = event.getParser();
NotificationUtilities.simpleAlert(LocalizeText(`catalog.alert.voucherredeem.error.description.${ parser.errorCode }`), null, null, null, LocalizeText('catalog.alert.voucherredeem.error.title'));
setIsWaiting(false);
}, []);
CreateMessageHook(VoucherRedeemErrorMessageEvent, onVoucherRedeemErrorMessageEvent);
return (
<Flex gap={ 1 }>
<input type="text" className="form-control form-control-sm" placeholder={ text } value={ voucher } onChange={ event => setVoucher(event.target.value) } />
<Button variant="primary" size="sm" onClick={ redeemVoucher } disabled={ isWaiting }>
<FontAwesomeIcon icon="tag" />
</Button>
</Flex>
);
}

View File

@ -0,0 +1,49 @@
import { IFurnitureData, RoomPreviewer } from '@nitrots/nitro-renderer';
import { FC } from 'react';
import { Base } from '../../../../../common/Base';
import { Column } from '../../../../../common/Column';
import { Grid } from '../../../../../common/Grid';
import { Text } from '../../../../../common/Text';
import { LimitedEditionCompletePlateView } from '../../../../../views/shared/limited-edition/LimitedEditionCompletePlateView';
import { GetOfferName } from '../../../common/CatalogUtilities';
import { useCatalogContext } from '../../../context/CatalogContext';
import { CatalogRoomPreviewerView } from '../../catalog-room-previewer/CatalogRoomPreviewerView';
import { CatalogPurchaseView } from '../purchase/CatalogPurchaseView';
import { CatalogSearchResultOffersView } from './CatalogSearchResultOffersView';
export interface CatalogLayoutSearchResultViewProps
{
roomPreviewer: RoomPreviewer;
furnitureDatas: IFurnitureData[];
}
export const CatalogLayoutSearchResultView: FC<CatalogLayoutSearchResultViewProps> = props =>
{
const { roomPreviewer = null, furnitureDatas = null } = props;
const { catalogState } = useCatalogContext();
const { activeOffer = null } = catalogState;
const product = ((activeOffer && activeOffer.products[0]) || null);
return (
<Grid>
<Column size={ 7 } overflow="hidden">
<CatalogSearchResultOffersView offers={ furnitureDatas } />
</Column>
{ product &&
<Column size={ 5 } overflow="hidden">
<Column overflow="hidden" position="relative" gap={ 0 }>
{ roomPreviewer && <CatalogRoomPreviewerView roomPreviewer={ roomPreviewer } height={ 140 } /> }
{ product.uniqueLimitedItem &&
<Base fullWidth position="absolute" className="top-1">
<LimitedEditionCompletePlateView className="mx-auto" uniqueLimitedItemsLeft={ product.uniqueLimitedItemsLeft } uniqueLimitedSeriesSize={ product.uniqueLimitedSeriesSize } />
</Base> }
</Column>
<Column grow>
<Text grow truncate>{ GetOfferName(activeOffer) }</Text>
<CatalogPurchaseView offer={ activeOffer } pageId={ -1 } />
</Column>
</Column> }
</Grid>
);
}

View File

@ -0,0 +1,38 @@
import { GetProductOfferComposer, IFurnitureData, MouseEventType } from '@nitrots/nitro-renderer';
import { FC, MouseEvent, useCallback } from 'react';
import { LayoutGridItem, LayoutGridItemProps } from '../../../../../common/layout/LayoutGridItem';
import { SendMessageHook } from '../../../../../hooks/messages/message-event';
import { AvatarImageView } from '../../../../../views/shared/avatar-image/AvatarImageView';
import { GetProductIconUrl } from '../../../common/GetProuductIconUrl';
import { ProductTypeEnum } from '../../../common/ProductTypeEnum';
export interface CatalogSearchResultOfferViewProps extends LayoutGridItemProps
{
offer: IFurnitureData;
}
export const CatalogSearchResultOfferView: FC<CatalogSearchResultOfferViewProps> = props =>
{
const { offer = null, ...rest } = props;
const onMouseEvent = useCallback((event: MouseEvent) =>
{
switch(event.type)
{
case MouseEventType.MOUSE_DOWN:
SendMessageHook(new GetProductOfferComposer(offer.purchaseOfferId));
return;
}
}, [ offer ]);
if(!offer) return null;
const iconUrl = GetProductIconUrl(offer.id, offer.type, offer.customParams);
return (
<LayoutGridItem itemImage={ iconUrl } onMouseDown={ onMouseEvent } { ...rest }>
{ (offer.type === ProductTypeEnum.ROBOT) &&
<AvatarImageView figure={ offer.customParams } direction={ 3 } headOnly={ true } /> }
</LayoutGridItem>
);
}

View File

@ -1,14 +1,18 @@
import { GetProductOfferComposer } from '@nitrots/nitro-renderer'; import { GetProductOfferComposer, IFurnitureData } from '@nitrots/nitro-renderer';
import { FC, useEffect } from 'react'; import { FC, useEffect } from 'react';
import { SendMessageHook } from '../../../../../../hooks/messages/message-event'; import { Grid, GridProps } from '../../../../../common/Grid';
import { NitroCardGridView } from '../../../../../../layout'; import { SendMessageHook } from '../../../../../hooks/messages/message-event';
import { useCatalogContext } from '../../../../context/CatalogContext'; import { useCatalogContext } from '../../../context/CatalogContext';
import { CatalogSearchResultOfferView } from '../offer/CatalogSearchResultOfferView'; import { CatalogSearchResultOfferView } from './CatalogSearchResultOfferView';
import { CatalogSearchResultOffersViewProps } from './CatalogSearchResultOffersView.types';
export interface CatalogSearchResultOffersViewProps extends GridProps
{
offers: IFurnitureData[];
}
export const CatalogSearchResultOffersView: FC<CatalogSearchResultOffersViewProps> = props => export const CatalogSearchResultOffersView: FC<CatalogSearchResultOffersViewProps> = props =>
{ {
const { offers = [], ...rest } = props; const { offers = [], children = null, ...rest } = props;
const { catalogState = null } = useCatalogContext(); const { catalogState = null } = useCatalogContext();
const { activeOffer = null } = catalogState; const { activeOffer = null } = catalogState;
@ -20,13 +24,14 @@ export const CatalogSearchResultOffersView: FC<CatalogSearchResultOffersViewProp
}, [ offers ]); }, [ offers ]);
return ( return (
<NitroCardGridView { ...rest }> <Grid grow columnCount={ 5 } overflow="auto" { ...rest }>
{ offers && (offers.length > 0) && offers.map((offer, index) => { offers && (offers.length > 0) && offers.map((offer, index) =>
{ {
const isActive = (activeOffer && (activeOffer.products[0].furniClassId === offer.id)); const isActive = (activeOffer && (activeOffer.products[0].furniClassId === offer.id));
return <CatalogSearchResultOfferView key={ index } isActive={ isActive } offer={ offer } /> return <CatalogSearchResultOfferView key={ index } itemActive={ isActive } offer={ offer } />
})} })}
</NitroCardGridView> { children }
</Grid>
); );
} }

View File

@ -1,12 +1,14 @@
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { IFurnitureData, INodeData } from '@nitrots/nitro-renderer'; import { IFurnitureData, INodeData } from '@nitrots/nitro-renderer';
import { FC, useCallback, useEffect, useState } from 'react'; import { FC, useCallback, useEffect, useState } from 'react';
import { GetSessionDataManager, LocalizeText } from '../../../../api'; import { GetSessionDataManager, LocalizeText } from '../../../../api';
import { Button } from '../../../../common/Button';
import { Flex } from '../../../../common/Flex';
import { GetOfferNodes } from '../../common/CatalogUtilities'; import { GetOfferNodes } from '../../common/CatalogUtilities';
import { useCatalogContext } from '../../context/CatalogContext'; import { useCatalogContext } from '../../context/CatalogContext';
import { CatalogActions } from '../../reducers/CatalogReducer'; import { CatalogActions } from '../../reducers/CatalogReducer';
import { CatalogSearchViewProps } from './CatalogSearchView.types';
export const CatalogSearchView: FC<CatalogSearchViewProps> = props => export const CatalogSearchView: FC<{}> = props =>
{ {
const [ searchValue, setSearchValue ] = useState(''); const [ searchValue, setSearchValue ] = useState('');
const { catalogState = null, dispatchCatalogState = null } = useCatalogContext(); const { catalogState = null, dispatchCatalogState = null } = useCatalogContext();
@ -110,15 +112,11 @@ export const CatalogSearchView: FC<CatalogSearchViewProps> = props =>
}, [ searchValue, processSearch ]); }, [ searchValue, processSearch ]);
return ( return (
<div className="d-flex"> <Flex gap={ 1 }>
<div className="d-flex flex-grow-1 me-1">
<input type="text" className="form-control form-control-sm" placeholder={ LocalizeText('generic.search') } value={ searchValue } onChange={ event => setSearchValue(event.target.value) } /> <input type="text" className="form-control form-control-sm" placeholder={ LocalizeText('generic.search') } value={ searchValue } onChange={ event => setSearchValue(event.target.value) } />
</div> <Button variant="primary" size="sm">
<div className="d-flex"> <FontAwesomeIcon icon="search" />
<button type="button" className="btn btn-primary btn-sm"> </Button>
<i className="fas fa-search"></i> </Flex>
</button>
</div>
</div>
); );
} }

View File

@ -1,4 +0,0 @@
export interface CatalogMessageHandlerProps
{
}

View File

@ -1,8 +0,0 @@
.catalog-icon-image {
width: 20px;
height: 20px;
min-width: 20px;
min-height: 20px;
background-position: center;
background-repeat: no-repeat;
}

View File

@ -1,17 +0,0 @@
import { FC } from 'react';
import { GetConfiguration } from '../../../../api';
import { CatalogIconViewProps } from './CatalogIconView.types';
export const CatalogIconView: FC<CatalogIconViewProps> = props =>
{
const { icon = 0 } = props;
function getIconUrl(): string
{
return ((GetConfiguration<string>('catalog.asset.icon.url')).replace('%name%', icon.toString()));
}
const url = `url('${ getIconUrl() }')`;
return <div className="catalog-icon-image" style={ (url && url.length) ? { backgroundImage: url } : {} }></div>;
}

View File

@ -1,4 +0,0 @@
export interface CatalogIconViewProps
{
icon: number;
}

View File

@ -1,239 +0,0 @@
import { PurchaseFromCatalogAsGiftComposer } from '@nitrots/nitro-renderer';
import classNames from 'classnames';
import { FC, useCallback, useEffect, useMemo, useState } from 'react';
import { GetSessionDataManager, LocalizeText } from '../../../../api';
import { CatalogEvent } from '../../../../events';
import { CatalogInitGiftEvent } from '../../../../events/catalog/CatalogInitGiftEvent';
import { SendMessageHook, useUiEvent } from '../../../../hooks';
import { NitroCardContentView, NitroCardHeaderView, NitroCardView, NitroLayoutGiftCardView } from '../../../../layout';
import { CurrencyIcon } from '../../../shared/currency-icon/CurrencyIcon';
import { FurniImageView } from '../../../shared/furni-image/FurniImageView';
import { useCatalogContext } from '../../context/CatalogContext';
export const CatalogGiftView: FC<{}> = props =>
{
const { catalogState = null } = useCatalogContext();
const { giftConfiguration = null } = catalogState;
const [ isVisible, setIsVisible ] = useState<boolean>(false);
const [ pageId, setPageId ] = useState<number>(0);
const [ offerId, setOfferId ] = useState<number>(0);
const [ receiverName, setReceiverName ] = useState<string>('');
const [ showMyFace, setShowMyFace ] = useState<boolean>(true);
const [ message, setMessage ] = useState<string>('');
const [ colors, setColors ] = useState<{ id: number, color: string }[]>([]);
const [ selectedBoxIndex, setSelectedBoxIndex ] = useState<number>(0);
const [ selectedRibbonIndex, setSelectedRibbonIndex ] = useState<number>(0);
const [ selectedColorId, setSelectedColorId ] = useState<number>(0);
const [ maxBoxIndex, setMaxBoxIndex ] = useState<number>(0);
const [ maxRibbonIndex, setMaxRibbonIndex ] = useState<number>(0);
const [ receiverNotFound, setReceiverNotFound ] = useState<boolean>(false);
useEffect(() =>
{
setReceiverNotFound(false);
}, [ receiverName ]);
useEffect(() =>
{
if(!giftConfiguration) return;
setMaxBoxIndex(giftConfiguration.boxTypes.length - 1);
setMaxRibbonIndex(giftConfiguration.ribbonTypes.length - 1);
const newColors: { id: number, color: string }[] = [];
for(const colorId of giftConfiguration.stuffTypes)
{
const giftData = GetSessionDataManager().getFloorItemData(colorId);
if(!giftData) continue;
if(giftData.colors && giftData.colors.length > 0) newColors.push({ id: colorId, color: `#${giftData.colors[0].toString(16)}` });
}
if(newColors.length)
{
setSelectedColorId(newColors[0].id);
setColors(newColors);
}
}, [ giftConfiguration ]);
const close = useCallback(() =>
{
setIsVisible(false);
setPageId(0);
setOfferId(0);
setReceiverName('');
setShowMyFace(true);
setMessage('');
setSelectedBoxIndex(0);
setSelectedRibbonIndex(0);
if(colors.length) setSelectedColorId(colors[0].id);
}, [ colors ]);
const onCatalogEvent = useCallback((event: CatalogEvent) =>
{
switch(event.type)
{
case CatalogEvent.PURCHASE_SUCCESS:
close();
return;
case CatalogEvent.INIT_GIFT:
const castedEvent = (event as CatalogInitGiftEvent);
close();
setPageId(castedEvent.pageId);
setOfferId(castedEvent.offerId);
setIsVisible(true);
return;
case CatalogEvent.GIFT_RECEIVER_NOT_FOUND:
setReceiverNotFound(true);
return;
}
}, [ close ]);
useUiEvent(CatalogEvent.PURCHASE_SUCCESS, onCatalogEvent);
useUiEvent(CatalogEvent.INIT_GIFT, onCatalogEvent);
useUiEvent(CatalogEvent.GIFT_RECEIVER_NOT_FOUND, onCatalogEvent);
const isBoxDefault = useMemo(() =>
{
return giftConfiguration ? giftConfiguration.defaultStuffTypes.findIndex(s => s === giftConfiguration.boxTypes[selectedBoxIndex]) > -1 : true;
}, [ giftConfiguration, selectedBoxIndex ]);
const boxName = useMemo(() =>
{
return isBoxDefault ? 'catalog.gift_wrapping_new.box.default' : `catalog.gift_wrapping_new.box.${selectedBoxIndex}`;
}, [ isBoxDefault, selectedBoxIndex ]);
const ribbonName = useMemo(() =>
{
return `catalog.gift_wrapping_new.ribbon.${selectedRibbonIndex}`;
}, [ selectedRibbonIndex ]);
const priceText = useMemo(() =>
{
return isBoxDefault ? 'catalog.gift_wrapping_new.freeprice' : 'catalog.gift_wrapping_new.price';
}, [ isBoxDefault ]);
const extraData = useMemo(() =>
{
if(!giftConfiguration) return '';
return ((giftConfiguration.boxTypes[selectedBoxIndex] * 1000) + giftConfiguration.ribbonTypes[selectedRibbonIndex]).toString();
}, [ giftConfiguration, selectedBoxIndex, selectedRibbonIndex ]);
const isColorable = useMemo(() =>
{
if(!giftConfiguration) return false;
const boxType = giftConfiguration.boxTypes[selectedBoxIndex];
return (boxType === 8 || (boxType >= 3 && boxType <= 6)) ? false : true;
}, [ giftConfiguration, selectedBoxIndex ]);
const handleAction = useCallback((action: string) =>
{
switch(action)
{
case 'prev_box':
setSelectedBoxIndex(value =>
{
return (value === 0 ? maxBoxIndex : value - 1);
});
return;
case 'next_box':
setSelectedBoxIndex(value =>
{
return (value === maxBoxIndex ? 0 : value + 1);
});
return;
case 'prev_ribbon':
setSelectedRibbonIndex(value =>
{
return (value === 0 ? maxRibbonIndex : value - 1);
});
return;
case 'next_ribbon':
setSelectedRibbonIndex(value =>
{
return (value === maxRibbonIndex ? 0 : value + 1);
});
return;
case 'buy':
if(!receiverName || receiverName.length === 0)
{
setReceiverNotFound(true);
return;
}
SendMessageHook(new PurchaseFromCatalogAsGiftComposer(pageId, offerId, extraData, receiverName, message, selectedColorId, selectedBoxIndex, selectedRibbonIndex, showMyFace));
return;
}
}, [ extraData, maxBoxIndex, maxRibbonIndex, message, offerId, pageId, receiverName, selectedBoxIndex, selectedColorId, selectedRibbonIndex, showMyFace ]);
if(!giftConfiguration || !giftConfiguration.isEnabled || !isVisible) return null;
return (
<NitroCardView uniqueKey="catalog-gift" className="nitro-catalog-gift" simple={ true }>
<NitroCardHeaderView headerText={ LocalizeText('catalog.gift_wrapping.title') } onCloseClick={ close } />
<NitroCardContentView className="text-black">
<div className="form-group">
<label>{ LocalizeText('catalog.gift_wrapping.receiver') }</label>
<input type="text" className={ 'form-control form-control-sm' + classNames({ ' is-invalid': receiverNotFound }) } value={ receiverName } onChange={ (e) => setReceiverName(e.target.value) } />
{ receiverNotFound && <div className="invalid-feedback">{ LocalizeText('catalog.gift_wrapping.receiver_not_found.title') }</div> }
</div>
<div className="mt-2">
<NitroLayoutGiftCardView figure={ GetSessionDataManager().figure } userName={ GetSessionDataManager().userName } message={ message } editable={ true } onChange={ (value) => setMessage(value) } />
</div>
<div className="form-check mt-1">
<input className="form-check-input" type="checkbox" name="showMyFace" checked={ showMyFace } onChange={ (e) => setShowMyFace(value => !value) } />
<label className="form-check-label">{ LocalizeText('catalog.gift_wrapping.show_face.title') }</label>
</div>
<div className="d-flex gap-2 mt-1 align-items-center">
<div className="gift-preview">
{ selectedColorId && <FurniImageView spriteId={ selectedColorId } type="s" extras={ extraData } /> }
</div>
<div className="d-flex flex-column gap-2">
<div className="d-flex gap-2">
<div className="btn-group">
<button className="btn btn-primary" onClick={ () => handleAction('prev_box') }><i className="fas fa-chevron-left" /></button>
<button className="btn btn-primary" onClick={ () => handleAction('next_box') }><i className="fas fa-chevron-right" /></button>
</div>
<div>
<div className="fw-bold">{ LocalizeText(boxName) }</div>
<div className="d-flex align-items-center gap-1">{ LocalizeText(priceText, ['price'], [giftConfiguration.price.toString()]) }<CurrencyIcon type={ -1 } /></div>
</div>
</div>
<div className="d-flex gap-2 align-items-center">
<div className="btn-group">
<button className="btn btn-primary" onClick={ () => handleAction('prev_ribbon') }><i className="fas fa-chevron-left" /></button>
<button className="btn btn-primary" onClick={ () => handleAction('next_ribbon') }><i className="fas fa-chevron-right" /></button>
</div>
<div>
<div className="fw-bold">{ LocalizeText(ribbonName) }</div>
</div>
</div>
</div>
</div>
<div className="mt-1">
<div className="fw-bold">{ LocalizeText('catalog.gift_wrapping.pick_color') }</div>
<div className="btn-group w-100">
{ colors.map(color =>
{
return <button key={ color.id } className={ 'btn btn-dark btn-sm' + classNames({ ' active': color.id === selectedColorId }) } disabled={ !isColorable } style={{ backgroundColor: color.color }} onClick={ () => setSelectedColorId(color.id) }></button>
}) }
</div>
</div>
<div className="d-flex justify-content-between align-items-center mt-2">
<div className="text-decoration-underline cursor-pointer" onClick={ close }>{ LocalizeText('cancel') }</div>
<button className="btn btn-success" onClick={ () => handleAction('buy') }>{ LocalizeText('catalog.gift_wrapping.give_gift') }</button>
</div>
</NitroCardContentView>
</NitroCardView>
);
};

View File

@ -1,9 +0,0 @@
import { INodeData } from '@nitrots/nitro-renderer';
import { Dispatch, SetStateAction } from 'react';
export interface CatalogNavigationViewProps
{
page: INodeData;
pendingTree: INodeData[];
setPendingTree: Dispatch<SetStateAction<INodeData[]>>;
}

View File

@ -1,11 +0,0 @@
import { INodeData } from '@nitrots/nitro-renderer';
import { Dispatch, SetStateAction } from 'react';
export interface CatalogNavigationItemViewProps
{
page: INodeData;
isActive: boolean;
pendingTree: INodeData[];
setPendingTree: Dispatch<SetStateAction<INodeData[]>>;
setActiveChild: Dispatch<SetStateAction<INodeData>>;
}

View File

@ -1,10 +0,0 @@
import { INodeData } from '@nitrots/nitro-renderer';
import { Dispatch, SetStateAction } from 'react';
export interface CatalogNavigationSetViewProps
{
page: INodeData;
isFirstSet?: boolean;
pendingTree: INodeData[];
setPendingTree: Dispatch<SetStateAction<INodeData[]>>;
}

View File

@ -1,6 +0,0 @@
import { RoomPreviewer } from '@nitrots/nitro-renderer';
export interface CatalogPageViewProps
{
roomPreviewer: RoomPreviewer;
}

View File

@ -1,5 +0,0 @@
@import './frontpage4/CatalogLayoutFrontpage4View';
@import './info-loyalty/CatalogLayoutInfoLoyaltyView.scss';
@import './vip-buy/CatalogLayoutVipBuyView';
@import './marketplace/marketplace-item/MarketplaceItemView';
@import './marketplace/post-offer/MarketplacePostOfferView';

View File

@ -1,6 +0,0 @@
import { CatalogLayoutProps } from '../CatalogLayout.types';
export interface CatalogLayoutBadgeDisplayViewProps extends CatalogLayoutProps
{
}

View File

@ -1,6 +0,0 @@
import { CatalogLayoutProps } from '../CatalogLayout.types';
export interface CatalogLayoutDefaultViewProps extends CatalogLayoutProps
{
}

View File

@ -1,17 +0,0 @@
.nitro-front-page-item {
background-position: center;
background-repeat: no-repeat;
cursor: pointer;
.front-page-item-caption {
position: absolute;
background: rgba(0, 0, 0, .5);
color: #fff;
font-size: 16px;
border-radius: 5px;
margin: 10px;
padding: 5px 15px;
bottom: 0;
text-shadow: 2px 2px rgba(0, 0, 0, .2);
}
}

View File

@ -1,6 +0,0 @@
import { CatalogLayoutProps } from '../CatalogLayout.types';
export interface CatalogLayoutFrontpage4ViewProps extends CatalogLayoutProps
{
}

View File

@ -1,35 +0,0 @@
import { FC, useMemo } from 'react';
import { GetConfiguration } from '../../../../../../../api';
import { NitroLayoutBase } from '../../../../../../../layout/base';
import { CatalogLayoutFrontPageItemViewProps } from './CatalogLayoutFrontPageItemView.types';
export const CatalogLayoutFrontPageItemView: FC<CatalogLayoutFrontPageItemViewProps> = props =>
{
const { item = null, className = '', style = null, ...rest } = props;
const getClassName = useMemo(() =>
{
let newClassName = 'position-relative rounded h-100 nitro-front-page-item';
if(className && className.length) newClassName += ' ' + className;
return newClassName;
}, [ className ]);
const getStyle = useMemo(() =>
{
const newStyle = { ...style };
newStyle.backgroundImage = `url('${ GetConfiguration<string>('image.library.url') }${ item.itemPromoImage }')`;
return newStyle;
}, [ style, item ]);
if(!item) return null;
return (
<NitroLayoutBase className={ getClassName } style={ getStyle } { ...rest }>
<div className="front-page-item-caption">{ item.itemName }</div>
</NitroLayoutBase>
);
}

View File

@ -1,7 +0,0 @@
import { FrontPageItem } from '@nitrots/nitro-renderer';
import { DetailedHTMLProps, HTMLAttributes } from 'react';
export interface CatalogLayoutFrontPageItemViewProps extends DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>
{
item: FrontPageItem;
}

View File

@ -1,4 +0,0 @@
import { CatalogLayoutProps } from '../CatalogLayout.types';
export interface CatalogLayoutGuildCustomFurniViewProps extends CatalogLayoutProps
{}

View File

@ -1,4 +0,0 @@
import { CatalogLayoutProps } from '../CatalogLayout.types';
export interface CatalogLayoutGuildForumViewProps extends CatalogLayoutProps
{}

View File

@ -1,29 +0,0 @@
import { FC } from 'react';
import { CreateLinkEvent, LocalizeText } from '../../../../../../api';
import { NitroLayoutGrid, NitroLayoutGridColumn } from '../../../../../../layout';
import { NitroLayoutBase } from '../../../../../../layout/base';
import { GetCatalogPageImage, GetCatalogPageText } from '../../../../common/CatalogUtilities';
import { CatalogLayoutGuildFrontpageViewProps } from './CatalogLayoutGuildFrontpageView.types';
export const CatalogLayouGuildFrontpageView: FC<CatalogLayoutGuildFrontpageViewProps> = props =>
{
const { pageParser = null } = props;
return (
<NitroLayoutGrid>
<NitroLayoutGridColumn className="bg-muted rounded p-2 text-black overflow-hidden" size={ 7 }>
<NitroLayoutBase dangerouslySetInnerHTML={ { __html: GetCatalogPageText(pageParser, 2) } } />
<NitroLayoutBase className="overflow-auto" dangerouslySetInnerHTML={ { __html: GetCatalogPageText(pageParser, 0) } } />
<NitroLayoutBase dangerouslySetInnerHTML={ { __html: GetCatalogPageText(pageParser, 1) } } />
</NitroLayoutGridColumn>
<NitroLayoutGridColumn size={ 5 }>
<div className="d-block mb-2">
<img alt="" src={ GetCatalogPageImage(pageParser, 1) } />
</div>
<div className="col position-relative d-flex flex-column justify-content-center" onClick={ () => CreateLinkEvent('groups/create') }>
<button className="btn btn-sm btn-primary mt-1">{ LocalizeText('catalog.start.guild.purchase.button') }</button>
</div>
</NitroLayoutGridColumn>
</NitroLayoutGrid>
);
}

View File

@ -1,4 +0,0 @@
import { CatalogLayoutProps } from '../CatalogLayout.types';
export interface CatalogLayoutGuildFrontpageViewProps extends CatalogLayoutProps
{}

Some files were not shown because too many files have changed in this diff Show More