This commit is contained in:
MyNameIsBatman 2021-09-12 00:16:09 -03:00
parent bdc3211fce
commit 52e9e3208a
12 changed files with 331 additions and 6 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 795 B

View File

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@ -10,5 +10,6 @@ export class CatalogEvent extends NitroEvent
public static SOLD_OUT: string = 'CE_SOLD_OUT';
public static APPROVE_NAME_RESULT: string = 'CE_APPROVE_NAME_RESULT';
public static PURCHASE_APPROVED: string = 'CE_PURCHASE_APPROVED';
public static INIT_GIFT: string = 'CE_INIT_GIFT';
public static CATALOG_RESET: string = 'CE_RESET';
}

View File

@ -0,0 +1,32 @@
import { CatalogEvent } from './CatalogEvent';
export class CatalogInitGiftEvent extends CatalogEvent
{
private _pageId: number;
private _offerId: number;
private _extraData: string;
constructor(pageId: number, offerId: number, extraData: string)
{
super(CatalogEvent.INIT_GIFT);
this._pageId = pageId;
this._offerId = offerId;
this._extraData = extraData;
}
public get pageId(): number
{
return this._pageId;
}
public get offerId(): number
{
return this._offerId;
}
public get extraData(): string
{
return this._extraData;
}
}

View File

@ -10,6 +10,7 @@ import { CatalogMode, CatalogViewProps } from './CatalogView.types';
import { BuildCatalogPageTree } from './common/CatalogUtilities';
import { CatalogContextProvider } from './context/CatalogContext';
import { CatalogReducer, initialCatalog } from './reducers/CatalogReducer';
import { CatalogPageGiftView } from './views/gift/CatalogPageGiftView';
import { ACTIVE_PAGES, CatalogNavigationView } from './views/navigation/CatalogNavigationView';
import { CatalogPageView } from './views/page/CatalogPageView';
@ -213,6 +214,7 @@ export const CatalogView: FC<CatalogViewProps> = props =>
</div>
</NitroCardContentView>
</NitroCardView> }
<CatalogPageGiftView />
</CatalogContextProvider>
);
}

View File

@ -2,3 +2,4 @@
@import './navigation/CatalogNavigationView';
@import './page/CatalogPageView';
@import './search/CatalogSearchView';
@import './gift/CatalogPageGiftView';

View File

@ -0,0 +1,56 @@
.nitro-catalog-gift {
.gift-tag {
width: 306px;
height: 159px;
background: url(../../../../assets/images/catalog/gift/gift_tag.png) center no-repeat;
}
.gift-face {
width: 65px;
.gift-incognito {
width: 37px;
height: 48px;
background: url(../../../../assets/images/catalog/gift/incognito.png) center no-repeat;
}
.gift-avatar {
position: relative;
overflow: hidden;
width: 40px;
height: 50px;
.avatar-image {
position: absolute;
left: -25px;
top: -20px;
}
}
}
.gift-message {
width: 100%;
min-width: 100%;
max-width: 100%;
height: 90px;
min-height: 90px;
max-height: 90px;
border: none;
resize: none;
outline: none;
line-height: 17px;
}
.gift-preview {
width: 80px;
height: 80px;
overflow: hidden;
}
.gift-color {
width: 15px;
height: 15px;
border-radius: $border-radius;
}
}

View File

@ -0,0 +1,227 @@
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 } from '../../../../layout';
import { AvatarImageView } from '../../../shared/avatar-image/AvatarImageView';
import { CurrencyIcon } from '../../../shared/currency-icon/CurrencyIcon';
import { FurniImageView } from '../../../shared/furni-image/FurniImageView';
import { useCatalogContext } from '../../context/CatalogContext';
export const CatalogPageGiftView: 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);
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)}` });
}
setSelectedColorId(newColors[0].id);
setColors(newColors);
}, [ giftConfiguration ]);
const close = useCallback(() =>
{
setIsVisible(false);
setPageId(0);
setOfferId(0);
setReceiverName('');
setShowMyFace(true);
setMessage('');
setSelectedBoxIndex(0);
setSelectedRibbonIndex(0);
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;
}
}, [ close ]);
useUiEvent(CatalogEvent.PURCHASE_SUCCESS, onCatalogEvent);
useUiEvent(CatalogEvent.INIT_GIFT, 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':
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" value={ receiverName } onChange={ (e) => setReceiverName(e.target.value) } />
</div>
<div className="gift-tag d-flex mt-2">
<div className="d-flex align-items-center justify-content-center gift-face flex-shrink-0">
{ !showMyFace && <div className="gift-incognito"></div> }
{ showMyFace && <div className="gift-avatar">
<AvatarImageView figure={ GetSessionDataManager().figure } direction={ 2 } headOnly={ true } />
</div> }
</div>
<div className="d-flex flex-column w-100 pt-4 pb-4 pe-4 ps-3">
<textarea className="gift-message" maxLength={ 140 } value={ message } onChange={ (e) => setMessage(e.target.value) } placeholder={ LocalizeText('catalog.gift_wrapping_new.message_hint') }></textarea>
{ showMyFace && <div className="mt-auto text-end fst-italic">{ LocalizeText('catalog.gift_wrapping_new.message_from', ['name'], [GetSessionDataManager().userName]) }</div> }
</div>
</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 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

@ -36,7 +36,6 @@ export const CatalogPurchaseButtonView: FC<CatalogPurchaseButtonViewProps> = pro
const purchase = useCallback(() =>
{
console.log(pageId, offer.offerId, extra, quantity);
SendMessageHook(new PurchaseFromCatalogComposer(pageId, offer.offerId, extra, quantity));
}, [ pageId, offer, extra, quantity ]);

View File

@ -1,12 +1,19 @@
import { FC } from 'react';
import { FC, useCallback } from 'react';
import { LocalizeText } from '../../../../../../api';
import { CatalogInitGiftEvent } from '../../../../../../events/catalog/CatalogInitGiftEvent';
import { dispatchUiEvent } from '../../../../../../hooks';
import { CatalogPurchaseGiftButtonViewProps } from './CatalogPurchaseGiftButtonView.types';
export const CatalogPurchaseGiftButtonView: FC<CatalogPurchaseGiftButtonViewProps> = props =>
{
const { className = '', offer = null, pageId = -1, extra = null, quantity = 1, isPurchaseAllowed = true, beforePurchase = null } = props;
const initGift = useCallback(() =>
{
dispatchUiEvent(new CatalogInitGiftEvent(pageId, offer.offerId, extra));
}, [ extra, offer, pageId ]);
return (
<button type="button" className={ 'btn btn-secondary ' + className }>{ LocalizeText('catalog.purchase_confirmation.gift') }</button>
<button type="button" className={ 'btn btn-secondary ' + className } onClick={ initGift }>{ LocalizeText('catalog.purchase_confirmation.gift') }</button>
);
}

View File

@ -1,6 +1,6 @@
.nitro-currency-icon {
background-position: center;
background-repeat: no-repeat;
width: 20px;
height: 20px;
width: 15px;
height: 15px;
}

View File

@ -41,7 +41,7 @@ export const FurniImageView: FC<FurniImageViewProps> = props =>
if(imageResult)
{
const image = imageResult.getImage();
image.onload = () => setImageElement(image);
}
}, [ type, spriteId, direction, extras ]);