mirror of
https://github.com/duckietm/Nitro-V3.git
synced 2026-06-20 07:26:19 +00:00
㊙️ Security Fixes
- XSS fix: Created SanitizeHtml.ts utility using DOMPurify (already in package.json but never used). Wrapped all 21 dangerouslySetInnerHTML calls in catalog views with SanitizeHtml() — only allows safe tags (b, i, u, br, span, div, p, a, strong, em, img) - Race condition fix: Added 10-second timeout fallbacks on purchase flags in CatalogPurchaseWidgetView and CatalogGiftView so the flag auto-resets even if the server never responds
This commit is contained in:
@@ -0,0 +1,10 @@
|
|||||||
|
import DOMPurify from 'dompurify';
|
||||||
|
|
||||||
|
export const SanitizeHtml = (html: string): string =>
|
||||||
|
{
|
||||||
|
return DOMPurify.sanitize(html, {
|
||||||
|
ALLOWED_TAGS: [ 'b', 'i', 'u', 'br', 'span', 'div', 'p', 'a', 'strong', 'em', 'img' ],
|
||||||
|
ALLOWED_ATTR: [ 'href', 'target', 'class', 'style', 'src', 'alt' ],
|
||||||
|
ALLOW_DATA_ATTR: false
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -15,6 +15,7 @@ export * from './PrefixUtils';
|
|||||||
export * from './ProductImageUtility';
|
export * from './ProductImageUtility';
|
||||||
export * from './Randomizer';
|
export * from './Randomizer';
|
||||||
export * from './RoomChatFormatter';
|
export * from './RoomChatFormatter';
|
||||||
|
export * from './SanitizeHtml';
|
||||||
export * from './SetLocalStorage';
|
export * from './SetLocalStorage';
|
||||||
export * from './SoundNames';
|
export * from './SoundNames';
|
||||||
export * from './WindowSaveOptions';
|
export * from './WindowSaveOptions';
|
||||||
|
|||||||
@@ -228,7 +228,7 @@ const CatalogModernViewInner: FC<{}> = () =>
|
|||||||
<FaStar className="text-[9px] text-primary shrink-0" />
|
<FaStar className="text-[9px] text-primary shrink-0" />
|
||||||
{ activeNodes && activeNodes.length > 0
|
{ activeNodes && activeNodes.length > 0
|
||||||
? activeNodes.map((node, i) => (
|
? activeNodes.map((node, i) => (
|
||||||
<span key={ node.pageId } className="flex items-center gap-1 min-w-0">
|
<span key={ `${ node.pageId }-${ i }` } className="flex items-center gap-1 min-w-0">
|
||||||
{ i > 0 && <span className="text-[8px] opacity-30">›</span> }
|
{ i > 0 && <span className="text-[8px] opacity-30">›</span> }
|
||||||
<span className={ `truncate ${ i === activeNodes.length - 1 ? 'font-bold text-dark' : 'cursor-pointer hover:text-primary' }` }
|
<span className={ `truncate ${ i === activeNodes.length - 1 ? 'font-bold text-dark' : 'cursor-pointer hover:text-primary' }` }
|
||||||
onClick={ i < activeNodes.length - 1 ? () => activateNode(node) : undefined }>
|
onClick={ i < activeNodes.length - 1 ? () => activateNode(node) : undefined }>
|
||||||
|
|||||||
@@ -128,6 +128,7 @@ export const CatalogGiftView: FC<{}> = props =>
|
|||||||
if(isBuyingGift) return;
|
if(isBuyingGift) return;
|
||||||
|
|
||||||
isBuyingGift = true;
|
isBuyingGift = true;
|
||||||
|
setTimeout(() => { isBuyingGift = false; }, 10000);
|
||||||
|
|
||||||
SendMessageComposer(new PurchaseFromCatalogAsGiftComposer(pageId, offerId, extraData, receiverName, message, colourId, selectedBoxIndex, selectedRibbonIndex, showMyFace));
|
SendMessageComposer(new PurchaseFromCatalogAsGiftComposer(pageId, offerId, extraData, receiverName, message, colourId, selectedBoxIndex, selectedRibbonIndex, showMyFace));
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { FC } from 'react';
|
import { FC } from 'react';
|
||||||
import { LocalizeText } from '../../../../../api';
|
import { LocalizeText, SanitizeHtml } from '../../../../../api';
|
||||||
import { Column, Grid, Text } from '../../../../../common';
|
import { Column, Grid, Text } from '../../../../../common';
|
||||||
import { useCatalog } from '../../../../../hooks';
|
import { useCatalog } from '../../../../../hooks';
|
||||||
import { CatalogBadgeSelectorWidgetView } from '../widgets/CatalogBadgeSelectorWidgetView';
|
import { CatalogBadgeSelectorWidgetView } from '../widgets/CatalogBadgeSelectorWidgetView';
|
||||||
@@ -31,7 +31,7 @@ export const CatalogLayoutBadgeDisplayView: FC<CatalogLayoutProps> = props =>
|
|||||||
{ !currentOffer &&
|
{ !currentOffer &&
|
||||||
<>
|
<>
|
||||||
{ !!page.localization.getImage(1) && <img alt="" src={ page.localization.getImage(1) } /> }
|
{ !!page.localization.getImage(1) && <img alt="" src={ page.localization.getImage(1) } /> }
|
||||||
<Text center dangerouslySetInnerHTML={ { __html: page.localization.getText(0) } } />
|
<Text center dangerouslySetInnerHTML={ { __html: SanitizeHtml(page.localization.getText(0)) } } />
|
||||||
</> }
|
</> }
|
||||||
{ currentOffer &&
|
{ currentOffer &&
|
||||||
<>
|
<>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { ColorConverter } from '@nitrots/nitro-renderer';
|
import { ColorConverter } from '@nitrots/nitro-renderer';
|
||||||
import { FC, useMemo, useState } from 'react';
|
import { FC, useMemo, useState } from 'react';
|
||||||
import { FaFillDrip } from 'react-icons/fa';
|
import { FaFillDrip } from 'react-icons/fa';
|
||||||
import { IPurchasableOffer } from '../../../../../api';
|
import { IPurchasableOffer, SanitizeHtml } from '../../../../../api';
|
||||||
import { AutoGrid, Button, Column, Grid, LayoutGridItem, Text } from '../../../../../common';
|
import { AutoGrid, Button, Column, Grid, LayoutGridItem, Text } from '../../../../../common';
|
||||||
import { useCatalog } from '../../../../../hooks';
|
import { useCatalog } from '../../../../../hooks';
|
||||||
import { CatalogGridOfferView } from '../common/CatalogGridOfferView';
|
import { CatalogGridOfferView } from '../common/CatalogGridOfferView';
|
||||||
@@ -146,7 +146,7 @@ export const CatalogLayoutColorGroupingView: FC<CatalogLayoutColorGroupViewProps
|
|||||||
{ !currentOffer &&
|
{ !currentOffer &&
|
||||||
<>
|
<>
|
||||||
{ !!page.localization.getImage(1) && <img alt="" src={ page.localization.getImage(1) } /> }
|
{ !!page.localization.getImage(1) && <img alt="" src={ page.localization.getImage(1) } /> }
|
||||||
<Text center dangerouslySetInnerHTML={ { __html: page.localization.getText(0) } } />
|
<Text center dangerouslySetInnerHTML={ { __html: SanitizeHtml(page.localization.getText(0)) } } />
|
||||||
</> }
|
</> }
|
||||||
{ currentOffer &&
|
{ currentOffer &&
|
||||||
<>
|
<>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { PurchasePrefixComposer } from '@nitrots/nitro-renderer';
|
import { PurchasePrefixComposer } from '@nitrots/nitro-renderer';
|
||||||
import { FC, useEffect, useMemo, useRef, useState } from 'react';
|
import { FC, useEffect, useMemo, useRef, useState } from 'react';
|
||||||
import { LocalizeText, SendMessageComposer, PRESET_PREFIX_EFFECTS, parsePrefixColors, getPrefixEffectStyle, PREFIX_EFFECT_KEYFRAMES } from '../../../../../api';
|
import { LocalizeText, SanitizeHtml, SendMessageComposer, PRESET_PREFIX_EFFECTS, parsePrefixColors, getPrefixEffectStyle, PREFIX_EFFECT_KEYFRAMES } from '../../../../../api';
|
||||||
import { CatalogLayoutProps } from './CatalogLayout.types';
|
import { CatalogLayoutProps } from './CatalogLayout.types';
|
||||||
import data from '@emoji-mart/data';
|
import data from '@emoji-mart/data';
|
||||||
import Picker from '@emoji-mart/react';
|
import Picker from '@emoji-mart/react';
|
||||||
@@ -137,7 +137,7 @@ export const CatalogLayoutCustomPrefixView: FC<CatalogLayoutProps> = props =>
|
|||||||
{ page.localization.getImage(0) &&
|
{ page.localization.getImage(0) &&
|
||||||
<img alt="" className="w-full rounded" src={ page.localization.getImage(0) } /> }
|
<img alt="" className="w-full rounded" src={ page.localization.getImage(0) } /> }
|
||||||
{ page.localization.getText(0) &&
|
{ page.localization.getText(0) &&
|
||||||
<div className="text-sm mb-1" dangerouslySetInnerHTML={ { __html: page.localization.getText(0) } } /> }
|
<div className="text-sm mb-1" dangerouslySetInnerHTML={ { __html: SanitizeHtml(page.localization.getText(0)) } } /> }
|
||||||
|
|
||||||
{ /* Live Preview */ }
|
{ /* Live Preview */ }
|
||||||
<div className="relative flex items-center justify-center p-4 rounded-lg min-h-[56px]"
|
<div className="relative flex items-center justify-center p-4 rounded-lg min-h-[56px]"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { FC } from 'react';
|
import { FC } from 'react';
|
||||||
import { FaEdit, FaPlus } from 'react-icons/fa';
|
import { FaEdit, FaPlus } from 'react-icons/fa';
|
||||||
import { GetConfigurationValue, LocalizeText, ProductTypeEnum } from '../../../../../api';
|
import { GetConfigurationValue, LocalizeText, ProductTypeEnum, SanitizeHtml } from '../../../../../api';
|
||||||
import { Text } from '../../../../../common';
|
import { Text } from '../../../../../common';
|
||||||
import { useCatalog } from '../../../../../hooks';
|
import { useCatalog } from '../../../../../hooks';
|
||||||
import { useCatalogAdmin } from '../../../CatalogAdminContext';
|
import { useCatalogAdmin } from '../../../CatalogAdminContext';
|
||||||
@@ -90,7 +90,7 @@ export const CatalogLayoutDefaultView: FC<CatalogLayoutProps> = props =>
|
|||||||
<div className="flex items-center gap-3 p-2.5 bg-white rounded border-2 border-card-grid-item-border">
|
<div className="flex items-center gap-3 p-2.5 bg-white rounded border-2 border-card-grid-item-border">
|
||||||
{ !!page.localization.getImage(1) &&
|
{ !!page.localization.getImage(1) &&
|
||||||
<img className="w-[70px] h-[70px] object-contain rounded shrink-0" src={ page.localization.getImage(1) } /> }
|
<img className="w-[70px] h-[70px] object-contain rounded shrink-0" src={ page.localization.getImage(1) } /> }
|
||||||
<Text className="text-[11px]! text-muted" dangerouslySetInnerHTML={ { __html: page.localization.getText(0) } } />
|
<Text className="text-[11px]! text-muted" dangerouslySetInnerHTML={ { __html: SanitizeHtml(page.localization.getText(0)) } } />
|
||||||
</div> }
|
</div> }
|
||||||
|
|
||||||
{ /* Item grid */ }
|
{ /* Item grid */ }
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { FC } from 'react';
|
import { FC } from 'react';
|
||||||
|
import { SanitizeHtml } from '../../../../../api';
|
||||||
import { Column, Grid, Text } from '../../../../../common';
|
import { Column, Grid, Text } from '../../../../../common';
|
||||||
import { useCatalog } from '../../../../../hooks';
|
import { useCatalog } from '../../../../../hooks';
|
||||||
import { CatalogGuildBadgeWidgetView } from '../widgets/CatalogGuildBadgeWidgetView';
|
import { CatalogGuildBadgeWidgetView } from '../widgets/CatalogGuildBadgeWidgetView';
|
||||||
@@ -23,7 +24,7 @@ export const CatalogLayouGuildCustomFurniView: FC<CatalogLayoutProps> = props =>
|
|||||||
{ !currentOffer &&
|
{ !currentOffer &&
|
||||||
<>
|
<>
|
||||||
{ !!page.localization.getImage(1) && <img alt="" src={ page.localization.getImage(1) } /> }
|
{ !!page.localization.getImage(1) && <img alt="" src={ page.localization.getImage(1) } /> }
|
||||||
<Text center dangerouslySetInnerHTML={ { __html: page.localization.getText(0) } } />
|
<Text center dangerouslySetInnerHTML={ { __html: SanitizeHtml(page.localization.getText(0)) } } />
|
||||||
</> }
|
</> }
|
||||||
{ currentOffer &&
|
{ currentOffer &&
|
||||||
<>
|
<>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
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 { SendMessageComposer } from '../../../../../api';
|
import { SanitizeHtml, SendMessageComposer } from '../../../../../api';
|
||||||
import { Column, Grid, Text } from '../../../../../common';
|
import { Column, Grid, Text } from '../../../../../common';
|
||||||
import { useCatalog } from '../../../../../hooks';
|
import { useCatalog } from '../../../../../hooks';
|
||||||
import { CatalogFirstProductSelectorWidgetView } from '../widgets/CatalogFirstProductSelectorWidgetView';
|
import { CatalogFirstProductSelectorWidgetView } from '../widgets/CatalogFirstProductSelectorWidgetView';
|
||||||
@@ -26,7 +26,7 @@ export const CatalogLayouGuildForumView: FC<CatalogLayoutProps> = props =>
|
|||||||
<CatalogFirstProductSelectorWidgetView />
|
<CatalogFirstProductSelectorWidgetView />
|
||||||
<Grid>
|
<Grid>
|
||||||
<Column className="bg-muted rounded p-2 text-black" overflow="hidden" size={ 7 }>
|
<Column className="bg-muted rounded p-2 text-black" overflow="hidden" size={ 7 }>
|
||||||
<div className="overflow-auto" dangerouslySetInnerHTML={ { __html: page.localization.getText(1) } } />
|
<div className="overflow-auto" dangerouslySetInnerHTML={ { __html: SanitizeHtml(page.localization.getText(1)) } } />
|
||||||
</Column>
|
</Column>
|
||||||
<Column gap={ 1 } overflow="hidden" size={ 5 }>
|
<Column gap={ 1 } overflow="hidden" size={ 5 }>
|
||||||
{ !!currentOffer &&
|
{ !!currentOffer &&
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { CreateLinkEvent } from '@nitrots/nitro-renderer';
|
import { CreateLinkEvent } from '@nitrots/nitro-renderer';
|
||||||
import { FC } from 'react';
|
import { FC } from 'react';
|
||||||
import { LocalizeText } from '../../../../../api';
|
import { LocalizeText, SanitizeHtml } from '../../../../../api';
|
||||||
import { Button } from '../../../../../common/Button';
|
import { Button } from '../../../../../common/Button';
|
||||||
import { Column } from '../../../../../common/Column';
|
import { Column } from '../../../../../common/Column';
|
||||||
import { Grid } from '../../../../../common/Grid';
|
import { Grid } from '../../../../../common/Grid';
|
||||||
@@ -14,9 +14,9 @@ export const CatalogLayouGuildFrontpageView: FC<CatalogLayoutProps> = props =>
|
|||||||
return (
|
return (
|
||||||
<Grid>
|
<Grid>
|
||||||
<Column className="bg-muted rounded p-2 text-black" overflow="hidden" size={ 7 }>
|
<Column className="bg-muted rounded p-2 text-black" overflow="hidden" size={ 7 }>
|
||||||
<div dangerouslySetInnerHTML={ { __html: page.localization.getText(2) } } />
|
<div dangerouslySetInnerHTML={ { __html: SanitizeHtml(page.localization.getText(2)) } } />
|
||||||
<div className="overflow-auto" dangerouslySetInnerHTML={ { __html: page.localization.getText(0) } } />
|
<div className="overflow-auto" dangerouslySetInnerHTML={ { __html: SanitizeHtml(page.localization.getText(0)) } } />
|
||||||
<div dangerouslySetInnerHTML={ { __html: page.localization.getText(1) } } />
|
<div dangerouslySetInnerHTML={ { __html: SanitizeHtml(page.localization.getText(1)) } } />
|
||||||
</Column>
|
</Column>
|
||||||
<Column center overflow="hidden" size={ 5 }>
|
<Column center overflow="hidden" size={ 5 }>
|
||||||
<LayoutImage imageUrl={ page.localization.getImage(1) } />
|
<LayoutImage imageUrl={ page.localization.getImage(1) } />
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { FC } from 'react';
|
import { FC } from 'react';
|
||||||
|
import { SanitizeHtml } from '../../../../../api';
|
||||||
import { CatalogLayoutProps } from './CatalogLayout.types';
|
import { CatalogLayoutProps } from './CatalogLayout.types';
|
||||||
|
|
||||||
export const CatalogLayoutInfoLoyaltyView: FC<CatalogLayoutProps> = props =>
|
export const CatalogLayoutInfoLoyaltyView: FC<CatalogLayoutProps> = props =>
|
||||||
@@ -8,7 +9,7 @@ export const CatalogLayoutInfoLoyaltyView: FC<CatalogLayoutProps> = props =>
|
|||||||
return (
|
return (
|
||||||
<div className="h-full nitro-catalog-layout-info-loyalty text-black flex flex-row">
|
<div className="h-full nitro-catalog-layout-info-loyalty text-black flex flex-row">
|
||||||
<div className="overflow-auto h-full flex flex-col info-loyalty-content">
|
<div className="overflow-auto h-full flex flex-col info-loyalty-content">
|
||||||
<div dangerouslySetInnerHTML={ { __html: page.localization.getText(0) } } />
|
<div dangerouslySetInnerHTML={ { __html: SanitizeHtml(page.localization.getText(0)) } } />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { FC } from 'react';
|
import { FC } from 'react';
|
||||||
import { FaPaw } from 'react-icons/fa';
|
import { FaPaw } from 'react-icons/fa';
|
||||||
|
import { SanitizeHtml } from '../../../../../api';
|
||||||
import { CatalogLayoutProps } from './CatalogLayout.types';
|
import { CatalogLayoutProps } from './CatalogLayout.types';
|
||||||
|
|
||||||
export const CatalogLayoutPets3View: FC<CatalogLayoutProps> = props =>
|
export const CatalogLayoutPets3View: FC<CatalogLayoutProps> = props =>
|
||||||
@@ -16,20 +17,20 @@ export const CatalogLayoutPets3View: FC<CatalogLayoutProps> = props =>
|
|||||||
<div>
|
<div>
|
||||||
<div className="flex items-center gap-1.5 mb-0.5">
|
<div className="flex items-center gap-1.5 mb-0.5">
|
||||||
<FaPaw className="text-primary text-xs" />
|
<FaPaw className="text-primary text-xs" />
|
||||||
<span className="text-sm font-bold" dangerouslySetInnerHTML={ { __html: page.localization.getText(1) } } />
|
<span className="text-sm font-bold" dangerouslySetInnerHTML={ { __html: SanitizeHtml(page.localization.getText(1)) } } />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{ /* Content */ }
|
{ /* Content */ }
|
||||||
<div className="flex-1 overflow-auto bg-white rounded border-2 border-card-grid-item-border p-3">
|
<div className="flex-1 overflow-auto bg-white rounded border-2 border-card-grid-item-border p-3">
|
||||||
<div className="text-[11px] leading-relaxed" dangerouslySetInnerHTML={ { __html: page.localization.getText(2) } } />
|
<div className="text-[11px] leading-relaxed" dangerouslySetInnerHTML={ { __html: SanitizeHtml(page.localization.getText(2)) } } />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{ /* Footer */ }
|
{ /* Footer */ }
|
||||||
{ !!page.localization.getText(3) &&
|
{ !!page.localization.getText(3) &&
|
||||||
<div className="p-2 bg-card-grid-item rounded border border-card-grid-item-border">
|
<div className="p-2 bg-card-grid-item rounded border border-card-grid-item-border">
|
||||||
<span className="text-[11px] font-bold" dangerouslySetInnerHTML={ { __html: page.localization.getText(3) } } />
|
<span className="text-[11px] font-bold" dangerouslySetInnerHTML={ { __html: SanitizeHtml(page.localization.getText(3)) } } />
|
||||||
</div> }
|
</div> }
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { FC } from 'react';
|
import { FC } from 'react';
|
||||||
|
import { SanitizeHtml } from '../../../../../api';
|
||||||
import { Column, Grid, Text } from '../../../../../common';
|
import { Column, Grid, Text } from '../../../../../common';
|
||||||
import { CatalogAddOnBadgeWidgetView } from '../widgets/CatalogAddOnBadgeWidgetView';
|
import { CatalogAddOnBadgeWidgetView } from '../widgets/CatalogAddOnBadgeWidgetView';
|
||||||
import { CatalogBundleGridWidgetView } from '../widgets/CatalogBundleGridWidgetView';
|
import { CatalogBundleGridWidgetView } from '../widgets/CatalogBundleGridWidgetView';
|
||||||
@@ -17,7 +18,7 @@ export const CatalogLayoutRoomBundleView: FC<CatalogLayoutProps> = props =>
|
|||||||
<Grid>
|
<Grid>
|
||||||
<Column overflow="hidden" size={ 7 }>
|
<Column overflow="hidden" size={ 7 }>
|
||||||
{ !!page.localization.getText(2) &&
|
{ !!page.localization.getText(2) &&
|
||||||
<Text dangerouslySetInnerHTML={ { __html: page.localization.getText(2) } } /> }
|
<Text dangerouslySetInnerHTML={ { __html: SanitizeHtml(page.localization.getText(2)) } } /> }
|
||||||
<Column grow className="bg-muted p-2 rounded" overflow="hidden">
|
<Column grow className="bg-muted p-2 rounded" overflow="hidden">
|
||||||
<CatalogBundleGridWidgetView fullWidth className="nitro-catalog-layout-bundle-grid" />
|
<CatalogBundleGridWidgetView fullWidth className="nitro-catalog-layout-bundle-grid" />
|
||||||
</Column>
|
</Column>
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { FC } from 'react';
|
import { FC } from 'react';
|
||||||
|
import { SanitizeHtml } from '../../../../../api';
|
||||||
import { Column, Grid, Text } from '../../../../../common';
|
import { Column, Grid, Text } from '../../../../../common';
|
||||||
import { CatalogAddOnBadgeWidgetView } from '../widgets/CatalogAddOnBadgeWidgetView';
|
import { CatalogAddOnBadgeWidgetView } from '../widgets/CatalogAddOnBadgeWidgetView';
|
||||||
import { CatalogBundleGridWidgetView } from '../widgets/CatalogBundleGridWidgetView';
|
import { CatalogBundleGridWidgetView } from '../widgets/CatalogBundleGridWidgetView';
|
||||||
@@ -17,7 +18,7 @@ export const CatalogLayoutSingleBundleView: FC<CatalogLayoutProps> = props =>
|
|||||||
<Grid>
|
<Grid>
|
||||||
<Column overflow="hidden" size={ 7 }>
|
<Column overflow="hidden" size={ 7 }>
|
||||||
{ !!page.localization.getText(2) &&
|
{ !!page.localization.getText(2) &&
|
||||||
<Text dangerouslySetInnerHTML={ { __html: page.localization.getText(2) } } /> }
|
<Text dangerouslySetInnerHTML={ { __html: SanitizeHtml(page.localization.getText(2)) } } /> }
|
||||||
<Column grow className="bg-muted p-2 rounded" overflow="hidden">
|
<Column grow className="bg-muted p-2 rounded" overflow="hidden">
|
||||||
<CatalogBundleGridWidgetView fullWidth className="nitro-catalog-layout-bundle-grid" />
|
<CatalogBundleGridWidgetView fullWidth className="nitro-catalog-layout-bundle-grid" />
|
||||||
</Column>
|
</Column>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { GetOfficialSongIdMessageComposer, GetSoundManager, MusicPriorities, OfficialSongIdMessageEvent } from '@nitrots/nitro-renderer';
|
import { GetOfficialSongIdMessageComposer, GetSoundManager, MusicPriorities, OfficialSongIdMessageEvent } from '@nitrots/nitro-renderer';
|
||||||
import { FC, useEffect, useState } from 'react';
|
import { FC, useEffect, useState } from 'react';
|
||||||
import { GetConfigurationValue, LocalizeText, ProductTypeEnum, SendMessageComposer } from '../../../../../api';
|
import { GetConfigurationValue, LocalizeText, ProductTypeEnum, SanitizeHtml, SendMessageComposer } from '../../../../../api';
|
||||||
import { Button, Column, Grid, LayoutImage, Text } from '../../../../../common';
|
import { Button, Column, Grid, LayoutImage, Text } from '../../../../../common';
|
||||||
import { useCatalog, useMessageEvent } from '../../../../../hooks';
|
import { useCatalog, useMessageEvent } from '../../../../../hooks';
|
||||||
import { CatalogHeaderView } from '../../catalog-header/CatalogHeaderView';
|
import { CatalogHeaderView } from '../../catalog-header/CatalogHeaderView';
|
||||||
@@ -80,7 +80,7 @@ export const CatalogLayoutSoundMachineView: FC<CatalogLayoutProps> = props =>
|
|||||||
<>
|
<>
|
||||||
{ !!page.localization.getImage(1) &&
|
{ !!page.localization.getImage(1) &&
|
||||||
<LayoutImage imageUrl={ page.localization.getImage(1) } /> }
|
<LayoutImage imageUrl={ page.localization.getImage(1) } /> }
|
||||||
<Text center dangerouslySetInnerHTML={ { __html: page.localization.getText(0) } } />
|
<Text center dangerouslySetInnerHTML={ { __html: SanitizeHtml(page.localization.getText(0)) } } />
|
||||||
</> }
|
</> }
|
||||||
{ currentOffer &&
|
{ currentOffer &&
|
||||||
<>
|
<>
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { FC, useEffect } from 'react';
|
import { FC, useEffect } from 'react';
|
||||||
|
import { SanitizeHtml } from '../../../../../api';
|
||||||
import { Column, Grid, Text } from '../../../../../common';
|
import { Column, Grid, Text } from '../../../../../common';
|
||||||
import { useCatalog } from '../../../../../hooks';
|
import { useCatalog } from '../../../../../hooks';
|
||||||
import { CatalogPurchaseWidgetView } from '../widgets/CatalogPurchaseWidgetView';
|
import { CatalogPurchaseWidgetView } from '../widgets/CatalogPurchaseWidgetView';
|
||||||
@@ -26,7 +27,7 @@ export const CatalogLayoutSpacesView: FC<CatalogLayoutProps> = props =>
|
|||||||
{ !currentOffer &&
|
{ !currentOffer &&
|
||||||
<>
|
<>
|
||||||
{ !!page.localization.getImage(1) && <img alt="" src={ page.localization.getImage(1) } /> }
|
{ !!page.localization.getImage(1) && <img alt="" src={ page.localization.getImage(1) } /> }
|
||||||
<Text center dangerouslySetInnerHTML={ { __html: page.localization.getText(0) } } />
|
<Text center dangerouslySetInnerHTML={ { __html: SanitizeHtml(page.localization.getText(0)) } } />
|
||||||
</> }
|
</> }
|
||||||
{ currentOffer &&
|
{ currentOffer &&
|
||||||
<>
|
<>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { FC, useEffect, useState } from 'react';
|
import { FC, useEffect, useState } from 'react';
|
||||||
import { FaEdit, FaPen, FaPlus, FaTrophy } from 'react-icons/fa';
|
import { FaEdit, FaPen, FaPlus, FaTrophy } from 'react-icons/fa';
|
||||||
import { LocalizeText, ProductTypeEnum } from '../../../../../api';
|
import { LocalizeText, ProductTypeEnum, SanitizeHtml } from '../../../../../api';
|
||||||
import { Text } from '../../../../../common';
|
import { Text } from '../../../../../common';
|
||||||
import { useCatalog } from '../../../../../hooks';
|
import { useCatalog } from '../../../../../hooks';
|
||||||
import { useCatalogAdmin } from '../../../CatalogAdminContext';
|
import { useCatalogAdmin } from '../../../CatalogAdminContext';
|
||||||
@@ -99,7 +99,7 @@ export const CatalogLayoutTrophiesView: FC<CatalogLayoutProps> = props =>
|
|||||||
<FaTrophy className="text-warning text-[11px]" />
|
<FaTrophy className="text-warning text-[11px]" />
|
||||||
<span className="text-[12px] font-bold">{ LocalizeText('catalog.trophies.title') }</span>
|
<span className="text-[12px] font-bold">{ LocalizeText('catalog.trophies.title') }</span>
|
||||||
</div>
|
</div>
|
||||||
<Text className="text-[10px]! text-muted leading-relaxed" dangerouslySetInnerHTML={ { __html: page.localization.getText(0) } } />
|
<Text className="text-[10px]! text-muted leading-relaxed" dangerouslySetInnerHTML={ { __html: SanitizeHtml(page.localization.getText(0)) } } />
|
||||||
</div>
|
</div>
|
||||||
</div> }
|
</div> }
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { ClubOfferData, GetClubOffersMessageComposer, PurchaseFromCatalogComposer } from '@nitrots/nitro-renderer';
|
import { ClubOfferData, GetClubOffersMessageComposer, PurchaseFromCatalogComposer } from '@nitrots/nitro-renderer';
|
||||||
import { FC, useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
import { FC, useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||||
import { CatalogPurchaseState, LocalizeText, SendMessageComposer } from '../../../../../api';
|
import { CatalogPurchaseState, LocalizeText, SanitizeHtml, SendMessageComposer } from '../../../../../api';
|
||||||
import { AutoGrid, Button, Column, Flex, Grid, LayoutCurrencyIcon, LayoutGridItem, LayoutLoadingSpinnerView, Text } from '../../../../../common';
|
import { AutoGrid, Button, Column, Flex, Grid, LayoutCurrencyIcon, LayoutGridItem, LayoutLoadingSpinnerView, Text } from '../../../../../common';
|
||||||
import { CatalogEvent, CatalogPurchaseFailureEvent, CatalogPurchasedEvent } from '../../../../../events';
|
import { CatalogEvent, CatalogPurchaseFailureEvent, CatalogPurchasedEvent } from '../../../../../events';
|
||||||
import { useCatalog, usePurse, useUiEvent } from '../../../../../hooks';
|
import { useCatalog, usePurse, useUiEvent } from '../../../../../hooks';
|
||||||
@@ -160,12 +160,12 @@ export const CatalogLayoutVipBuyView: FC<CatalogLayoutProps> = props =>
|
|||||||
);
|
);
|
||||||
}) }
|
}) }
|
||||||
</AutoGrid>
|
</AutoGrid>
|
||||||
<Text center dangerouslySetInnerHTML={ { __html: LocalizeText('catalog.vip.buy.hccenter') } }></Text>
|
<Text center dangerouslySetInnerHTML={ { __html: SanitizeHtml(LocalizeText('catalog.vip.buy.hccenter')) } }></Text>
|
||||||
</Column>
|
</Column>
|
||||||
<Column overflow="hidden" size={ 5 }>
|
<Column overflow="hidden" size={ 5 }>
|
||||||
<Column center fullHeight overflow="hidden">
|
<Column center fullHeight overflow="hidden">
|
||||||
{ currentPage.localization.getImage(1) && <img alt="" src={ currentPage.localization.getImage(1) } /> }
|
{ currentPage.localization.getImage(1) && <img alt="" src={ currentPage.localization.getImage(1) } /> }
|
||||||
<Text center dangerouslySetInnerHTML={ { __html: getSubscriptionDetails } } overflow="auto" />
|
<Text center dangerouslySetInnerHTML={ { __html: SanitizeHtml(getSubscriptionDetails) } } overflow="auto" />
|
||||||
</Column>
|
</Column>
|
||||||
{ pendingOffer &&
|
{ pendingOffer &&
|
||||||
<Column fullWidth grow justifyContent="end">
|
<Column fullWidth grow justifyContent="end">
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { ApproveNameMessageComposer, ApproveNameMessageEvent, ColorConverter, GetSellablePetPalettesComposer, PurchaseFromCatalogComposer, SellablePetPaletteData } from '@nitrots/nitro-renderer';
|
import { ApproveNameMessageComposer, ApproveNameMessageEvent, ColorConverter, GetSellablePetPalettesComposer, PurchaseFromCatalogComposer, SellablePetPaletteData } from '@nitrots/nitro-renderer';
|
||||||
import { FC, useCallback, useEffect, useMemo, useState } from 'react';
|
import { FC, useCallback, useEffect, useMemo, useState } from 'react';
|
||||||
import { FaCheck, FaEdit, FaFillDrip, FaPaw, FaPlus, FaTimes } from 'react-icons/fa';
|
import { FaCheck, FaEdit, FaFillDrip, FaPaw, FaPlus, FaTimes } from 'react-icons/fa';
|
||||||
import { DispatchUiEvent, GetPetAvailableColors, GetPetIndexFromLocalization, LocalizeText, SendMessageComposer } from '../../../../../../api';
|
import { DispatchUiEvent, GetPetAvailableColors, GetPetIndexFromLocalization, LocalizeText, SanitizeHtml, SendMessageComposer } from '../../../../../../api';
|
||||||
import { LayoutGridItem, LayoutPetImageView } from '../../../../../../common';
|
import { LayoutGridItem, LayoutPetImageView } from '../../../../../../common';
|
||||||
import { CatalogPurchaseFailureEvent } from '../../../../../../events';
|
import { CatalogPurchaseFailureEvent } from '../../../../../../events';
|
||||||
import { useCatalog, useMessageEvent } from '../../../../../../hooks';
|
import { useCatalog, useMessageEvent } from '../../../../../../hooks';
|
||||||
@@ -249,7 +249,7 @@ export const CatalogLayoutPetView: FC<CatalogLayoutProps> = props =>
|
|||||||
<span className="text-[8px] font-mono text-white bg-primary px-1 py-px rounded">Offer: { currentOffer.offerId }</span>
|
<span className="text-[8px] font-mono text-white bg-primary px-1 py-px rounded">Offer: { currentOffer.offerId }</span>
|
||||||
</div> }
|
</div> }
|
||||||
{ !!page.localization.getText(0) &&
|
{ !!page.localization.getText(0) &&
|
||||||
<p className="text-[10px] text-muted mt-0.5" dangerouslySetInnerHTML={ { __html: page.localization.getText(0) } } /> }
|
<p className="text-[10px] text-muted mt-0.5" dangerouslySetInnerHTML={ { __html: SanitizeHtml(page.localization.getText(0)) } } /> }
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{ /* Name input */ }
|
{ /* Name input */ }
|
||||||
|
|||||||
@@ -87,6 +87,8 @@ export const CatalogPurchaseWidgetView: FC<CatalogPurchaseWidgetViewProps> = pro
|
|||||||
isPurchasingCatalogItem = true;
|
isPurchasingCatalogItem = true;
|
||||||
setPurchaseState(CatalogPurchaseState.PURCHASE);
|
setPurchaseState(CatalogPurchaseState.PURCHASE);
|
||||||
|
|
||||||
|
setTimeout(() => { isPurchasingCatalogItem = false; }, 10000);
|
||||||
|
|
||||||
if(purchaseCallback)
|
if(purchaseCallback)
|
||||||
{
|
{
|
||||||
purchaseCallback();
|
purchaseCallback();
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { GetTargetedOfferComposer, PurchaseTargetedOfferComposer, TargetedOfferData } from '@nitrots/nitro-renderer';
|
import { GetTargetedOfferComposer, PurchaseTargetedOfferComposer, TargetedOfferData } from '@nitrots/nitro-renderer';
|
||||||
import { Dispatch, SetStateAction, useMemo, useState } from 'react';
|
import { Dispatch, SetStateAction, useMemo, useState } from 'react';
|
||||||
import { FriendlyTime, GetConfigurationValue, LocalizeText, SendMessageComposer } from '../../../../api';
|
import { FriendlyTime, GetConfigurationValue, LocalizeText, SanitizeHtml, SendMessageComposer } from '../../../../api';
|
||||||
import { Button, Column, Flex, LayoutCurrencyIcon, NitroCardContentView, NitroCardHeaderView, NitroCardView, Text } from '../../../../common';
|
import { Button, Column, Flex, LayoutCurrencyIcon, NitroCardContentView, NitroCardHeaderView, NitroCardView, Text } from '../../../../common';
|
||||||
import { usePurse } from '../../../../hooks';
|
import { usePurse } from '../../../../hooks';
|
||||||
|
|
||||||
@@ -63,7 +63,7 @@ export const OfferWindowView = (props: { offer: TargetedOfferData, setOpen: Disp
|
|||||||
<h4>
|
<h4>
|
||||||
{ LocalizeText(offer.title) }
|
{ LocalizeText(offer.title) }
|
||||||
</h4>
|
</h4>
|
||||||
<div dangerouslySetInnerHTML={ { __html: offer.description } } />
|
<div dangerouslySetInnerHTML={ { __html: SanitizeHtml(offer.description) } } />
|
||||||
</Column>
|
</Column>
|
||||||
<Flex alignItems="center" alignSelf="center" gap={ 2 } justifyContent="center">
|
<Flex alignItems="center" alignSelf="center" gap={ 2 } justifyContent="center">
|
||||||
{ offer.purchaseLimit > 1 &&
|
{ offer.purchaseLimit > 1 &&
|
||||||
|
|||||||
Reference in New Issue
Block a user