🆙 Security Fix - Will not go into details

This commit is contained in:
duckietm
2026-03-18 16:52:32 +01:00
parent 6d768c92b1
commit bffaccf6a3
18 changed files with 194 additions and 28 deletions
+28 -9
View File
@@ -67,11 +67,20 @@ export const LayoutBadgeImageView: FC<LayoutBadgeImageViewProps> = props =>
{
if(event.badgeId !== badgeCode) return;
const element = await TextureUtils.generateImage(new NitroSprite(event.image));
console.log ('boe');
if(isGroup)
{
const element = await TextureUtils.generateImage(new NitroSprite(event.image));
element.onload = () => setImageElement(element);
element.onload = () => setImageElement(element);
}
else
{
const badgeUrl = GetConfigurationValue<string>('badge.asset.url').replace('%badgename%', badgeCode.toString());
const img = new Image();
img.onload = () => setImageElement(img);
img.src = badgeUrl;
}
didSetBadge = true;
@@ -84,13 +93,23 @@ export const LayoutBadgeImageView: FC<LayoutBadgeImageViewProps> = props =>
if(texture && !didSetBadge)
{
(async () =>
if(isGroup)
{
const element = await TextureUtils.generateImage(new NitroSprite(texture));
(async () =>
{
const element = await TextureUtils.generateImage(new NitroSprite(texture));
element.onload = () => setImageElement(element);
})();
element.onload = () => setImageElement(element);
})();
}
else
{
const badgeUrl = GetConfigurationValue<string>('badge.asset.url').replace('%badgename%', badgeCode.toString());
const img = new Image();
img.onload = () => setImageElement(img);
img.src = badgeUrl;
}
}
return () => GetEventDispatcher().removeEventListener(BadgeImageReadyEvent.IMAGE_READY, onBadgeImageReadyEvent);
+1 -1
View File
@@ -85,7 +85,7 @@ export const MainView: FC<{}> = props =>
<AnimatePresence>
{ landingViewVisible &&
<motion.div
className="w-full h-full"
className="w-full h-full"
initial={ { opacity: 0 }}
animate={ { opacity: 1 }}
exit={ { opacity: 0 }}>
@@ -7,6 +7,8 @@ import { CatalogEvent, CatalogInitGiftEvent, CatalogPurchasedEvent } from '../..
import { useCatalog, useFriends, useMessageEvent, useUiEvent } from '../../../../hooks';
import { classNames } from '../../../../layout';
let isBuyingGift = false;
export const CatalogGiftView: FC<{}> = props =>
{
const [ isVisible, setIsVisible ] = useState<boolean>(false);
@@ -32,6 +34,7 @@ export const CatalogGiftView: FC<{}> = props =>
const onClose = useCallback(() =>
{
isBuyingGift = false;
setIsVisible(false);
setPageId(0);
setOfferId(0);
@@ -122,6 +125,10 @@ export const CatalogGiftView: FC<{}> = props =>
return;
}
if(isBuyingGift) return;
isBuyingGift = true;
SendMessageComposer(new PurchaseFromCatalogAsGiftComposer(pageId, offerId, extraData, receiverName, message, colourId, selectedBoxIndex, selectedRibbonIndex, showMyFace));
return;
}
@@ -136,6 +143,7 @@ export const CatalogGiftView: FC<{}> = props =>
switch(event.type)
{
case CatalogPurchasedEvent.PURCHASE_SUCCESS:
isBuyingGift = false;
onClose();
return;
case CatalogEvent.INIT_GIFT:
@@ -6,6 +6,8 @@ import { useCatalog, useMessageEvent, useNavigator, useRoomPromote } from '../..
import { NitroInput } from '../../../../../layout';
import { CatalogLayoutProps } from './CatalogLayout.types';
let isPurchasingAd = false;
export const CatalogLayoutRoomAdsView: FC<CatalogLayoutProps> = props =>
{
const { page = null } = props;
@@ -45,6 +47,10 @@ export const CatalogLayoutRoomAdsView: FC<CatalogLayoutProps> = props =>
const purchaseAd = () =>
{
if(isPurchasingAd) return;
isPurchasingAd = true;
const pageId = page.pageId;
const offerId = page.offers.length >= 1 ? page.offers[0].offerId : -1;
const flatId = roomId;
@@ -1,5 +1,5 @@
import { ClubOfferData, GetClubOffersMessageComposer, PurchaseFromCatalogComposer } from '@nitrots/nitro-renderer';
import { FC, useCallback, useEffect, useMemo, useState } from 'react';
import { FC, useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { CatalogPurchaseState, LocalizeText, SendMessageComposer } from '../../../../../api';
import { AutoGrid, Button, Column, Flex, Grid, LayoutCurrencyIcon, LayoutGridItem, LayoutLoadingSpinnerView, Text } from '../../../../../common';
import { CatalogEvent, CatalogPurchaseFailureEvent, CatalogPurchasedEvent } from '../../../../../events';
@@ -13,15 +13,18 @@ export const CatalogLayoutVipBuyView: FC<CatalogLayoutProps> = props =>
const { currentPage = null, catalogOptions = null } = useCatalog();
const { purse = null, getCurrencyAmount = null } = usePurse();
const { clubOffers = null } = catalogOptions;
const isPurchasingRef = useRef<boolean>(false);
const onCatalogEvent = useCallback((event: CatalogEvent) =>
{
switch(event.type)
{
case CatalogPurchasedEvent.PURCHASE_SUCCESS:
isPurchasingRef.current = false;
setPurchaseState(CatalogPurchaseState.NONE);
return;
case CatalogPurchaseFailureEvent.PURCHASE_FAILED:
isPurchasingRef.current = false;
setPurchaseState(CatalogPurchaseState.FAILED);
return;
}
@@ -83,8 +86,9 @@ export const CatalogLayoutVipBuyView: FC<CatalogLayoutProps> = props =>
const purchaseSubscription = useCallback(() =>
{
if(!pendingOffer) return;
if(!pendingOffer || isPurchasingRef.current) return;
isPurchasingRef.current = true;
setPurchaseState(CatalogPurchaseState.PURCHASE);
SendMessageComposer(new PurchaseFromCatalogComposer(currentPage.pageId, pendingOffer.offerId, null, 1));
}, [ pendingOffer, currentPage ]);
@@ -1,5 +1,5 @@
import { CancelMarketplaceOfferMessageComposer, GetMarketplaceOwnOffersMessageComposer, MarketplaceCancelOfferResultEvent, MarketplaceOwnOffersEvent, RedeemMarketplaceOfferCreditsMessageComposer } from '@nitrots/nitro-renderer';
import { FC, useCallback, useEffect, useMemo, useState } from 'react';
import { FC, useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { LocalizeText, MarketplaceOfferData, MarketPlaceOfferState, NotificationAlertType, SendMessageComposer } from '../../../../../../api';
import { Button, Column, Text } from '../../../../../../common';
import { useMessageEvent, useNotification } from '../../../../../../hooks';
@@ -11,6 +11,8 @@ export const CatalogLayoutMarketplaceOwnItemsView: FC<CatalogLayoutProps> = prop
const [ creditsWaiting, setCreditsWaiting ] = useState(0);
const [ offers, setOffers ] = useState<MarketplaceOfferData[]>([]);
const { simpleAlert = null } = useNotification();
const isRedeemingRef = useRef<boolean>(false);
const pendingCancelsRef = useRef<Set<number>>(new Set());
useMessageEvent<MarketplaceOwnOffersEvent>(MarketplaceOwnOffersEvent, event =>
{
@@ -54,6 +56,10 @@ export const CatalogLayoutMarketplaceOwnItemsView: FC<CatalogLayoutProps> = prop
const redeemSoldOffers = useCallback(() =>
{
if(isRedeemingRef.current) return;
isRedeemingRef.current = true;
setOffers(prevValue =>
{
const idsToDelete = soldOffers.map(value => value.offerId);
@@ -62,11 +68,19 @@ export const CatalogLayoutMarketplaceOwnItemsView: FC<CatalogLayoutProps> = prop
});
SendMessageComposer(new RedeemMarketplaceOfferCreditsMessageComposer());
setTimeout(() => isRedeemingRef.current = false, 3000);
}, [ soldOffers ]);
const takeItemBack = (offerData: MarketplaceOfferData) =>
{
if(pendingCancelsRef.current.has(offerData.offerId)) return;
pendingCancelsRef.current.add(offerData.offerId);
SendMessageComposer(new CancelMarketplaceOfferMessageComposer(offerData.offerId));
setTimeout(() => pendingCancelsRef.current.delete(offerData.offerId), 2000);
};
useEffect(() =>
@@ -1,5 +1,5 @@
import { BuyMarketplaceOfferMessageComposer, GetMarketplaceOffersMessageComposer, MarketplaceBuyOfferResultEvent, MarketPlaceOffersEvent } from '@nitrots/nitro-renderer';
import { FC, useCallback, useMemo, useState } from 'react';
import { FC, useCallback, useMemo, useRef, useState } from 'react';
import { IMarketplaceSearchOptions, LocalizeText, MarketplaceOfferData, MarketplaceSearchType, NotificationAlertType, SendMessageComposer } from '../../../../../../api';
import { Button, Column, Text } from '../../../../../../common';
import { useMessageEvent, useNotification, usePurse } from '../../../../../../hooks';
@@ -23,6 +23,7 @@ export const CatalogLayoutMarketplacePublicItemsView: FC<CatalogLayoutMarketplac
const [ lastSearch, setLastSearch ] = useState<IMarketplaceSearchOptions>({ minPrice: -1, maxPrice: -1, query: '', type: 3 });
const { getCurrencyAmount = null } = usePurse();
const { simpleAlert = null, showConfirm = null } = useNotification();
const isBuyingRef = useRef<boolean>(false);
const requestOffers = useCallback((options: IMarketplaceSearchOptions) =>
{
@@ -56,6 +57,9 @@ export const CatalogLayoutMarketplacePublicItemsView: FC<CatalogLayoutMarketplac
showConfirm(LocalizeText('catalog.marketplace.confirm_header'), () =>
{
if(isBuyingRef.current) return;
isBuyingRef.current = true;
SendMessageComposer(new BuyMarketplaceOfferMessageComposer(offerId));
},
null, null, null, LocalizeText('catalog.marketplace.confirm_title'));
@@ -83,6 +87,8 @@ export const CatalogLayoutMarketplacePublicItemsView: FC<CatalogLayoutMarketplac
{
const parser = event.getParser();
isBuyingRef.current = false;
if(!parser) return;
switch(parser.result)
@@ -6,6 +6,8 @@ import { CatalogPostMarketplaceOfferEvent } from '../../../../../../events';
import { useCatalog, useMessageEvent, useNotification, useUiEvent } from '../../../../../../hooks';
import { NitroInput } from '../../../../../../layout';
let isPostingMarketplaceOffer = false;
export const MarketplacePostOfferView: FC<{}> = props =>
{
const [ item, setItem ] = useState<FurnitureItem>(null);
@@ -65,10 +67,15 @@ export const MarketplacePostOfferView: FC<{}> = props =>
const postItem = () =>
{
if(!item || (askingPrice < marketplaceConfiguration.minimumPrice)) return;
if(!item || (askingPrice < marketplaceConfiguration.minimumPrice) || isPostingMarketplaceOffer) return;
showConfirm(LocalizeText('inventory.marketplace.confirm_offer.info', [ 'furniname', 'price' ], [ getFurniTitle, askingPrice.toString() ]), () =>
{
if(isPostingMarketplaceOffer) return;
isPostingMarketplaceOffer = true;
setTimeout(() => isPostingMarketplaceOffer = false, 5000);
SendMessageComposer(new MakeOfferMessageComposer(askingPrice, item.isWallItem ? 2 : 1, item.id));
setItem(null);
},
@@ -6,6 +6,8 @@ import { useCatalog, useNotification, usePurse } from '../../../../../../hooks';
import { CatalogLayoutProps } from '../CatalogLayout.types';
import { VipGiftItem } from './VipGiftItemView';
let isSelectingGift = false;
export const CatalogLayoutVipGiftsView: FC<CatalogLayoutProps> = props =>
{
const { purse = null } = usePurse();
@@ -30,6 +32,10 @@ export const CatalogLayoutVipGiftsView: FC<CatalogLayoutProps> = props =>
{
showConfirm(LocalizeText('catalog.club_gift.confirm'), () =>
{
if(isSelectingGift) return;
isSelectingGift = true;
SendMessageComposer(new SelectClubGiftComposer(localizationId));
setCatalogOptions(prevValue =>
@@ -38,6 +44,8 @@ export const CatalogLayoutVipGiftsView: FC<CatalogLayoutProps> = props =>
return { ...prevValue };
});
setTimeout(() => isSelectingGift = false, 5000);
}, null);
}, [ setCatalogOptions, showConfirm ]);
@@ -11,6 +11,8 @@ interface CatalogPurchaseWidgetViewProps
purchaseCallback?: () => void;
}
let isPurchasingCatalogItem = false;
export const CatalogPurchaseWidgetView: FC<CatalogPurchaseWidgetViewProps> = props =>
{
const { noGiftOption = false, purchaseCallback = null } = props;
@@ -25,15 +27,19 @@ export const CatalogPurchaseWidgetView: FC<CatalogPurchaseWidgetViewProps> = pro
switch(event.type)
{
case CatalogPurchasedEvent.PURCHASE_SUCCESS:
isPurchasingCatalogItem = false;
setPurchaseState(CatalogPurchaseState.NONE);
return;
case CatalogPurchaseFailureEvent.PURCHASE_FAILED:
isPurchasingCatalogItem = false;
setPurchaseState(CatalogPurchaseState.FAILED);
return;
case CatalogPurchaseNotAllowedEvent.NOT_ALLOWED:
isPurchasingCatalogItem = false;
setPurchaseState(CatalogPurchaseState.FAILED);
return;
case CatalogPurchaseSoldOutEvent.SOLD_OUT:
isPurchasingCatalogItem = false;
setPurchaseState(CatalogPurchaseState.SOLD_OUT);
return;
}
@@ -62,7 +68,7 @@ export const CatalogPurchaseWidgetView: FC<CatalogPurchaseWidgetViewProps> = pro
const purchase = (isGift: boolean = false) =>
{
if(!currentOffer) return;
if(!currentOffer || isPurchasingCatalogItem) return;
if(GetClubMemberLevel() < currentOffer.clubLevel)
{
@@ -78,6 +84,7 @@ export const CatalogPurchaseWidgetView: FC<CatalogPurchaseWidgetViewProps> = pro
return;
}
isPurchasingCatalogItem = true;
setPurchaseState(CatalogPurchaseState.PURCHASE);
if(purchaseCallback)
@@ -4,6 +4,8 @@ import { FriendlyTime, GetConfigurationValue, LocalizeText, SendMessageComposer
import { Button, Column, Flex, LayoutCurrencyIcon, NitroCardContentView, NitroCardHeaderView, NitroCardView, Text } from '../../../../common';
import { usePurse } from '../../../../hooks';
let isBuyingOffer = false;
export const OfferWindowView = (props: { offer: TargetedOfferData, setOpen: Dispatch<SetStateAction<boolean>> }) =>
{
const { offer = null, setOpen = null } = props;
@@ -37,8 +39,14 @@ export const OfferWindowView = (props: { offer: TargetedOfferData, setOpen: Disp
const buyOffer = () =>
{
if(isBuyingOffer) return;
isBuyingOffer = true;
SendMessageComposer(new PurchaseTargetedOfferComposer(offer.id, amount));
SendMessageComposer(new GetTargetedOfferComposer());
setTimeout(() => isBuyingOffer = false, 5000);
};
if(!offer) return;
@@ -15,6 +15,8 @@ interface GroupCreatorViewProps
const TABS: number[] = [ 1, 2, 3, 4 ];
let isBuyingGroup = false;
export const GroupCreatorView: FC<GroupCreatorViewProps> = props =>
{
const { onClose = null } = props;
@@ -34,7 +36,10 @@ export const GroupCreatorView: FC<GroupCreatorViewProps> = props =>
const buyGroup = () =>
{
if(!groupData) return;
if(!groupData || isBuyingGroup) return;
isBuyingGroup = true;
setTimeout(() => isBuyingGroup = false, 5000);
const badge = [];
@@ -1,5 +1,5 @@
import { AddLinkEventTracker, GetSessionDataManager, GroupAdminGiveComposer, GroupAdminTakeComposer, GroupConfirmMemberRemoveEvent, GroupConfirmRemoveMemberComposer, GroupMemberParser, GroupMembersComposer, GroupMembersEvent, GroupMembershipAcceptComposer, GroupMembershipDeclineComposer, GroupMembersParser, GroupRank, GroupRemoveMemberComposer, ILinkEventTracker, RemoveLinkEventTracker } from '@nitrots/nitro-renderer';
import { FC, useCallback, useEffect, useState } from 'react';
import { FC, useCallback, useEffect, useRef, useState } from 'react';
import { FaChevronLeft, FaChevronRight } from 'react-icons/fa';
import { GetUserProfile, LocalizeText, SendMessageComposer } from '../../../api';
import { Button, Column, Flex, Grid, LayoutAvatarImageView, LayoutBadgeImageView, NitroCardContentView, NitroCardHeaderView, NitroCardView, Text } from '../../../common';
@@ -16,6 +16,7 @@ export const GroupMembersView: FC<{}> = props =>
const [ searchQuery, setSearchQuery ] = useState<string>('');
const [ removingMemberName, setRemovingMemberName ] = useState<string>(null);
const { showConfirm = null } = useNotification();
const pendingActionsRef = useRef<Set<string>>(new Set());
const getRankDescription = (member: GroupMemberParser) =>
{
@@ -42,6 +43,11 @@ export const GroupMembersView: FC<{}> = props =>
{
if(!membersData.admin || (member.rank === GroupRank.OWNER)) return;
const key = `admin_${member.id}`;
if(pendingActionsRef.current.has(key)) return;
pendingActionsRef.current.add(key);
setTimeout(() => pendingActionsRef.current.delete(key), 2000);
if(member.rank !== GroupRank.ADMIN) SendMessageComposer(new GroupAdminGiveComposer(membersData.groupId, member.id));
else SendMessageComposer(new GroupAdminTakeComposer(membersData.groupId, member.id));
@@ -52,6 +58,11 @@ export const GroupMembersView: FC<{}> = props =>
{
if(!membersData.admin || (member.rank !== GroupRank.REQUESTED)) return;
const key = `accept_${member.id}`;
if(pendingActionsRef.current.has(key)) return;
pendingActionsRef.current.add(key);
setTimeout(() => pendingActionsRef.current.delete(key), 2000);
SendMessageComposer(new GroupMembershipAcceptComposer(membersData.groupId, member.id));
refreshMembers();
@@ -61,6 +72,11 @@ export const GroupMembersView: FC<{}> = props =>
{
if(!membersData.admin) return;
const key = `remove_${member.id}`;
if(pendingActionsRef.current.has(key)) return;
pendingActionsRef.current.add(key);
setTimeout(() => pendingActionsRef.current.delete(key), 2000);
if(member.rank === GroupRank.REQUESTED)
{
SendMessageComposer(new GroupMembershipDeclineComposer(membersData.groupId, member.id));
@@ -1,5 +1,5 @@
import { IssueMessageData, ReleaseIssuesMessageComposer } from '@nitrots/nitro-renderer';
import { FC } from 'react';
import { FC, useRef } from 'react';
import { SendMessageComposer } from '../../../../api';
import { Button, Column, Grid } from '../../../../common';
@@ -12,6 +12,17 @@ interface ModToolsMyIssuesTabViewProps
export const ModToolsMyIssuesTabView: FC<ModToolsMyIssuesTabViewProps> = props =>
{
const { myIssues = null, handleIssue = null } = props;
const pendingReleasesRef = useRef<Set<number>>(new Set());
const releaseIssue = (issueId: number) =>
{
if(pendingReleasesRef.current.has(issueId)) return;
pendingReleasesRef.current.add(issueId);
SendMessageComposer(new ReleaseIssuesMessageComposer([ issueId ]));
setTimeout(() => pendingReleasesRef.current.delete(issueId), 2000);
};
return (
<Column gap={ 0 } overflow="hidden">
@@ -36,7 +47,7 @@ export const ModToolsMyIssuesTabView: FC<ModToolsMyIssuesTabViewProps> = props =
<Button variant="primary" onClick={ event => handleIssue(issue.issueId) }>Handle</Button>
</div>
<div className="col-span-2">
<Button variant="danger" onClick={ event => SendMessageComposer(new ReleaseIssuesMessageComposer([ issue.issueId ])) }>Release</Button>
<Button variant="danger" onClick={ () => releaseIssue(issue.issueId) }>Release</Button>
</div>
</Grid>
);
@@ -1,5 +1,5 @@
import { IssueMessageData, PickIssuesMessageComposer } from '@nitrots/nitro-renderer';
import { FC } from 'react';
import { FC, useRef } from 'react';
import { SendMessageComposer } from '../../../../api';
import { Button, Column, Grid } from '../../../../common';
@@ -11,6 +11,17 @@ interface ModToolsOpenIssuesTabViewProps
export const ModToolsOpenIssuesTabView: FC<ModToolsOpenIssuesTabViewProps> = props =>
{
const { openIssues = null } = props;
const pendingPicksRef = useRef<Set<number>>(new Set());
const pickIssue = (issueId: number) =>
{
if(pendingPicksRef.current.has(issueId)) return;
pendingPicksRef.current.add(issueId);
SendMessageComposer(new PickIssuesMessageComposer([ issueId ], false, 0, 'pick issue button'));
setTimeout(() => pendingPicksRef.current.delete(issueId), 2000);
};
return (
<Column gap={ 0 } overflow="hidden">
@@ -31,7 +42,7 @@ export const ModToolsOpenIssuesTabView: FC<ModToolsOpenIssuesTabViewProps> = pro
<div className="col-span-3">{ issue.reportedUserName }</div>
<div className="col-span-4">{ new Date(Date.now() - issue.issueAgeInMilliseconds).toLocaleTimeString() }</div>
<div className="col-span-3">
<Button variant="success" onClick={ event => SendMessageComposer(new PickIssuesMessageComposer([ issue.issueId ], false, 0, 'pick issue button')) }>Pick Issue</Button>
<Button variant="success" onClick={ () => pickIssue(issue.issueId) }>Pick Issue</Button>
</div>
</Grid>
);
@@ -1,5 +1,5 @@
import { CallForHelpTopicData, DefaultSanctionMessageComposer, ModAlertMessageComposer, ModBanMessageComposer, ModKickMessageComposer, ModMessageMessageComposer, ModMuteMessageComposer, ModTradingLockMessageComposer } from '@nitrots/nitro-renderer';
import { FC, useMemo, useState } from 'react';
import { FC, useMemo, useRef, useState } from 'react';
import { ISelectedUser, LocalizeText, ModActionDefinition, NotificationAlertType, SendMessageComposer } from '../../../../api';
import { Button, DraggableWindowPosition, Flex, NitroCardContentView, NitroCardHeaderView, NitroCardView, Text } from '../../../../common';
import { useModTools, useNotification } from '../../../../hooks';
@@ -33,6 +33,7 @@ export const ModToolsUserModActionView: FC<ModToolsUserModActionViewProps> = pro
const [ message, setMessage ] = useState<string>('');
const { cfhCategories = null, settings = null } = useModTools();
const { simpleAlert = null } = useNotification();
const isSendingRef = useRef<boolean>(false);
const topics = useMemo(() =>
{
@@ -53,6 +54,8 @@ export const ModToolsUserModActionView: FC<ModToolsUserModActionViewProps> = pro
const sendDefaultSanction = () =>
{
if(isSendingRef.current) return;
let errorMessage: string = null;
const category = topics[selectedTopic];
@@ -63,6 +66,8 @@ export const ModToolsUserModActionView: FC<ModToolsUserModActionViewProps> = pro
const messageOrDefault = (message.trim().length === 0) ? LocalizeText(`help.cfh.topic.${ category.id }`) : message;
isSendingRef.current = true;
SendMessageComposer(new DefaultSanctionMessageComposer(user.userId, selectedTopic, messageOrDefault));
onCloseClick();
@@ -70,6 +75,8 @@ export const ModToolsUserModActionView: FC<ModToolsUserModActionViewProps> = pro
const sendSanction = () =>
{
if(isSendingRef.current) return;
let errorMessage: string = null;
const category = topics[selectedTopic];
@@ -145,6 +152,8 @@ export const ModToolsUserModActionView: FC<ModToolsUserModActionViewProps> = pro
}
}
isSendingRef.current = true;
onCloseClick();
};
@@ -6,6 +6,9 @@ import { Button, Flex, Grid, LayoutCurrencyIcon, LayoutGridItem, Text } from '..
import { useNavigator } from '../../../hooks';
import { NitroInput } from '../../../layout';
let isCreatingRoom = false;
let createRoomTimeout: ReturnType<typeof setTimeout> = null;
export const NavigatorRoomCreatorView: FC<{}> = props =>
{
const [ maxVisitorsList, setMaxVisitorsList ] = useState<number[]>(null);
@@ -16,6 +19,7 @@ export const NavigatorRoomCreatorView: FC<{}> = props =>
const [ tradesSetting, setTradesSetting ] = useState<number>(0);
const [ roomModels, setRoomModels ] = useState<IRoomModel[]>([]);
const [ selectedModelName, setSelectedModelName ] = useState<string>('');
const [ isCreating, setIsCreating ] = useState<boolean>(isCreatingRoom);
const { categories = null } = useNavigator();
const hcDisabled = GetConfigurationValue<boolean>('hc.disabled', false);
@@ -31,7 +35,19 @@ export const NavigatorRoomCreatorView: FC<{}> = props =>
const createRoom = () =>
{
if(isCreatingRoom) return;
isCreatingRoom = true;
setIsCreating(true);
SendMessageComposer(new CreateFlatMessageComposer(name, description, 'model_' + selectedModelName, Number(category), Number(visitorsCount), tradesSetting));
if(createRoomTimeout) clearTimeout(createRoomTimeout);
createRoomTimeout = setTimeout(() =>
{
isCreatingRoom = false;
setIsCreating(false);
}, 5000);
};
useEffect(() =>
@@ -117,7 +133,7 @@ export const NavigatorRoomCreatorView: FC<{}> = props =>
}
</div>
</Grid>
<Button fullWidth disabled={ (!name || (name.length < 3)) } variant={ (!name || (name.length < 3)) ? 'danger' : 'success' } onClick={ createRoom }>{ LocalizeText('navigator.createroom.create') }</Button>
<Button fullWidth disabled={ isCreating || !name || (name.length < 3) } variant={ (isCreating || !name || (name.length < 3)) ? 'danger' : 'success' } onClick={ createRoom }>{ LocalizeText('navigator.createroom.create') }</Button>
</div>
);
};
@@ -1,5 +1,5 @@
import { FlatControllerAddedEvent, FlatControllerRemovedEvent, FlatControllersEvent, RemoveAllRightsMessageComposer, RoomGiveRightsComposer, RoomTakeRightsComposer, RoomUsersWithRightsComposer } from '@nitrots/nitro-renderer';
import { FC, useEffect, useState } from 'react';
import { FC, useEffect, useRef, useState } from 'react';
import { IRoomData, LocalizeText, SendMessageComposer } from '../../../../api';
import { Button, Column, Flex, Grid, Text, UserProfileIconView } from '../../../../common';
import { useFriends, useMessageEvent } from '../../../../hooks';
@@ -18,6 +18,17 @@ export const NavigatorRoomSettingsRightsTabView: FC<NavigatorRoomSettingsTabView
const { roomData = null } = props;
const [ usersWithRights, setUsersWithRights ] = useState<Map<number, string>>(new Map());
const { onlineFriends = [], offlineFriends = [] } = useFriends();
const pendingActionsRef = useRef<Set<string>>(new Set());
const guardedSend = (key: string, composer: any) =>
{
if(pendingActionsRef.current.has(key)) return;
pendingActionsRef.current.add(key);
SendMessageComposer(composer);
setTimeout(() => pendingActionsRef.current.delete(key), 2000);
};
const allFriendsRaw = [ ...onlineFriends, ...offlineFriends ];
@@ -115,7 +126,7 @@ export const NavigatorRoomSettingsRightsTabView: FC<NavigatorRoomSettingsTabView
<Text
pointer
grow
onClick={ () => SendMessageComposer(new RoomTakeRightsComposer(id)) }>
onClick={ () => guardedSend(`take_${id}`, new RoomTakeRightsComposer(id)) }>
{ name }
</Text>
</Flex>
@@ -127,7 +138,7 @@ export const NavigatorRoomSettingsRightsTabView: FC<NavigatorRoomSettingsTabView
<Button
variant="danger"
disabled={ !filteredUsersWithRights.size }
onClick={ () => roomData && SendMessageComposer(new RemoveAllRightsMessageComposer(roomData.roomId)) }>
onClick={ () => roomData && guardedSend('removeAll', new RemoveAllRightsMessageComposer(roomData.roomId)) }>
{ LocalizeText('navigator.flatctrls.clear') }
</Button>
</Column>
@@ -154,7 +165,7 @@ export const NavigatorRoomSettingsRightsTabView: FC<NavigatorRoomSettingsTabView
<Text
pointer
grow
onClick={ () => SendMessageComposer(new RoomGiveRightsComposer(friend.id)) }>
onClick={ () => guardedSend(`give_${friend.id}`, new RoomGiveRightsComposer(friend.id)) }>
{ friend.name }
</Text>
</Flex>