diff --git a/src/assets/images/catalog/gift/gift_tag.png b/src/assets/images/catalog/gift/gift_tag.png new file mode 100644 index 00000000..3c24813e Binary files /dev/null and b/src/assets/images/catalog/gift/gift_tag.png differ diff --git a/src/assets/images/catalog/incognito.png b/src/assets/images/catalog/gift/incognito.png similarity index 100% rename from src/assets/images/catalog/incognito.png rename to src/assets/images/catalog/gift/incognito.png diff --git a/src/events/catalog/CatalogEvent.ts b/src/events/catalog/CatalogEvent.ts index f6d6bf72..818289f0 100644 --- a/src/events/catalog/CatalogEvent.ts +++ b/src/events/catalog/CatalogEvent.ts @@ -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'; } diff --git a/src/events/catalog/CatalogInitGiftEvent.ts b/src/events/catalog/CatalogInitGiftEvent.ts new file mode 100644 index 00000000..fd9c9264 --- /dev/null +++ b/src/events/catalog/CatalogInitGiftEvent.ts @@ -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; + } +} diff --git a/src/views/catalog/CatalogView.tsx b/src/views/catalog/CatalogView.tsx index 95a2f1d9..d49b167e 100644 --- a/src/views/catalog/CatalogView.tsx +++ b/src/views/catalog/CatalogView.tsx @@ -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 = props => } + ); } diff --git a/src/views/catalog/views/CatalogViews.scss b/src/views/catalog/views/CatalogViews.scss index c8a97e7c..d5d8464b 100644 --- a/src/views/catalog/views/CatalogViews.scss +++ b/src/views/catalog/views/CatalogViews.scss @@ -2,3 +2,4 @@ @import './navigation/CatalogNavigationView'; @import './page/CatalogPageView'; @import './search/CatalogSearchView'; +@import './gift/CatalogPageGiftView'; diff --git a/src/views/catalog/views/gift/CatalogPageGiftView.scss b/src/views/catalog/views/gift/CatalogPageGiftView.scss new file mode 100644 index 00000000..a4a05f64 --- /dev/null +++ b/src/views/catalog/views/gift/CatalogPageGiftView.scss @@ -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; + } +} diff --git a/src/views/catalog/views/gift/CatalogPageGiftView.tsx b/src/views/catalog/views/gift/CatalogPageGiftView.tsx new file mode 100644 index 00000000..473e8785 --- /dev/null +++ b/src/views/catalog/views/gift/CatalogPageGiftView.tsx @@ -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(false); + const [ pageId, setPageId ] = useState(0); + const [ offerId, setOfferId ] = useState(0); + + const [ receiverName, setReceiverName ] = useState(''); + const [ showMyFace, setShowMyFace ] = useState(true); + const [ message, setMessage ] = useState(''); + const [ colors, setColors ] = useState<{ id: number, color: string }[]>([]); + const [ selectedBoxIndex, setSelectedBoxIndex ] = useState(0); + const [ selectedRibbonIndex, setSelectedRibbonIndex ] = useState(0); + const [ selectedColorId, setSelectedColorId ] = useState(0); + const [ maxBoxIndex, setMaxBoxIndex ] = useState(0); + const [ maxRibbonIndex, setMaxRibbonIndex ] = useState(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 ( + + + +
+ + setReceiverName(e.target.value) } /> +
+
+
+ { !showMyFace &&
} + { showMyFace &&
+ +
} +
+
+ + { showMyFace &&
{ LocalizeText('catalog.gift_wrapping_new.message_from', ['name'], [GetSessionDataManager().userName]) }
} +
+
+
+ setShowMyFace(value => !value) } /> + +
+
+
+ { selectedColorId && } +
+
+
+
+ + +
+
+
{ LocalizeText(boxName) }
+
{ LocalizeText(priceText, ['price'], [giftConfiguration.price.toString()]) }
+
+
+
+
+ + +
+
+
{ LocalizeText(ribbonName) }
+
+
+
+
+
+
{ LocalizeText('catalog.gift_wrapping.pick_color') }
+
+ { colors.map(color => + { + return + }) } +
+
+
+
{ LocalizeText('cancel') }
+ +
+
+
+ ); +}; diff --git a/src/views/catalog/views/page/purchase/purchase-button/CatalogPurchaseButtonView.tsx b/src/views/catalog/views/page/purchase/purchase-button/CatalogPurchaseButtonView.tsx index a1f50704..acc9bafb 100644 --- a/src/views/catalog/views/page/purchase/purchase-button/CatalogPurchaseButtonView.tsx +++ b/src/views/catalog/views/page/purchase/purchase-button/CatalogPurchaseButtonView.tsx @@ -36,7 +36,6 @@ export const CatalogPurchaseButtonView: FC = pro const purchase = useCallback(() => { - console.log(pageId, offer.offerId, extra, quantity); SendMessageHook(new PurchaseFromCatalogComposer(pageId, offer.offerId, extra, quantity)); }, [ pageId, offer, extra, quantity ]); diff --git a/src/views/catalog/views/page/purchase/purchase-gift-button/CatalogPurchaseGiftButtonView.tsx b/src/views/catalog/views/page/purchase/purchase-gift-button/CatalogPurchaseGiftButtonView.tsx index 21acd90d..34ae59d5 100644 --- a/src/views/catalog/views/page/purchase/purchase-gift-button/CatalogPurchaseGiftButtonView.tsx +++ b/src/views/catalog/views/page/purchase/purchase-gift-button/CatalogPurchaseGiftButtonView.tsx @@ -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 = 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 ( - + ); } diff --git a/src/views/shared/currency-icon/CurrencyIcon.scss b/src/views/shared/currency-icon/CurrencyIcon.scss index 4134543b..88462829 100644 --- a/src/views/shared/currency-icon/CurrencyIcon.scss +++ b/src/views/shared/currency-icon/CurrencyIcon.scss @@ -1,6 +1,6 @@ .nitro-currency-icon { background-position: center; background-repeat: no-repeat; - width: 20px; - height: 20px; + width: 15px; + height: 15px; } diff --git a/src/views/shared/furni-image/FurniImageView.tsx b/src/views/shared/furni-image/FurniImageView.tsx index e73bfa84..63c006ca 100644 --- a/src/views/shared/furni-image/FurniImageView.tsx +++ b/src/views/shared/furni-image/FurniImageView.tsx @@ -41,7 +41,7 @@ export const FurniImageView: FC = props => if(imageResult) { const image = imageResult.getImage(); - + image.onload = () => setImageElement(image); } }, [ type, spriteId, direction, extras ]);