This commit is contained in:
Bill 2022-04-03 22:45:47 -04:00
parent 6a65398cc6
commit c697a9aa30
54 changed files with 374 additions and 470 deletions

View File

@ -35,11 +35,17 @@
"brace-style": [ "error", "allman" ],
"template-curly-spacing": [ "error", "always" ],
"no-multi-spaces": [ "error" ],
"react/prop-types": [ "off" ],
"jsx-quotes": [ "error" ],
"react/jsx-curly-spacing": [ "error", "always" ],
"react/prop-types": [ "off" ],
"react/jsx-curly-spacing": [ "error", { "when": "always", "children": true } ],
"react/jsx-equals-spacing": [ "error" ],
"@typescript-eslint/object-curly-spacing": [ "error", "always", { "arraysInObjects": true, "objectsInObjects": false } ],
"react/jsx-newline": [ "error", { "prevent": true } ],
"@typescript-eslint/object-curly-spacing": [ "error", "always",
{
"arraysInObjects": true,
"objectsInObjects": false
}
],
"@typescript-eslint/ban-types": [
"error",
{

View File

@ -0,0 +1,11 @@
const DEFAULT_BADGE: string = 'HC1';
const BADGES: string[] = [ 'ACH_VipHC1', 'ACH_VipHC2', 'ACH_VipHC3', 'ACH_VipHC4', 'ACH_VipHC5', 'HC1', 'HC2', 'HC3', 'HC4', 'HC5' ];
export const GetClubBadge = (badgeCodes: string[]) =>
{
let badgeCode: string = null;
BADGES.forEach(badge => ((badgeCodes.indexOf(badge) > -1) && (badgeCode = badge)));
return (badgeCode || DEFAULT_BADGE);
}

View File

@ -0,0 +1,2 @@
export * from './ClubStatus';
export * from './GetClubBadge';

View File

@ -6,6 +6,7 @@ export * from './friends';
export * from './GetRendererVersion';
export * from './GetUIVersion';
export * from './groups';
export * from './hc-center';
export * from './inventory';
export * from './inventory/unseen';
export * from './navigator';
@ -19,6 +20,7 @@ export * from './nitro/room/widgets/handlers';
export * from './nitro/room/widgets/messages';
export * from './nitro/session';
export * from './notification';
export * from './purse';
export * from './user';
export * from './utils';
export * from './wired';

View File

@ -1,5 +1,5 @@
import { HabboClubLevelEnum } from '@nitrots/nitro-renderer';
import { GetNitroInstance } from '../../../api';
import { GetNitroInstance } from '..';
import { IPurse } from './IPurse';
export class Purse implements IPurse

2
src/api/purse/index.ts Normal file
View File

@ -0,0 +1,2 @@
export * from './IPurse';
export * from './Purse';

View File

@ -45,7 +45,7 @@ export const TransitionAnimation: FC<TransitionAnimationProps> = props =>
<div className={ (className ?? '') + ' animate__animated' } style={ { ...getTransitionAnimationStyle(type, state, timeout) } }>
{ isChildrenVisible && children }
</div>
)}
) }
</Transition>
);
}

View File

@ -296,7 +296,7 @@ export const AvatarEditorView: FC<{}> = props =>
{ LocalizeText(`avatareditor.category.${ category }`) }
</NitroCardTabsItemView>
);
})}
}) }
<NitroCardTabsItemView isActive={ isWardrobeVisible } onClick={ event => setIsWardrobeVisible(true) }>
{ LocalizeText('avatareditor.category.wardrobe') }
</NitroCardTabsItemView>

View File

@ -76,7 +76,7 @@ export const AvatarEditorModelView: FC<AvatarEditorModelViewProps> = props =>
<AvatarEditorIcon icon={ category.name } selected={ (activeCategory === category) } />
</Flex>
);
})}
}) }
</Column>
<Column size={ 5 } overflow="hidden">
<AvatarEditorFigureSetView model={ model } category={ activeCategory } setMaxPaletteCount={ setMaxPaletteCount } />

View File

@ -102,7 +102,7 @@ export const CampaignView: FC<{}> = props =>
return (
<>
{(calendarData && isCalendarOpen) &&
{ (calendarData && isCalendarOpen) &&
<CalendarView close={ onCalendarClose } campaignName={ calendarData.campaignName } currentDay={ calendarData.currentDay } numDays={ calendarData.campaignDays } openedDays={ calendarData.openedDays } missedDays={ calendarData.missedDays } openPackage={ openPackage } receivedProducts={ receivedProducts } />
}
</>

View File

@ -1,4 +1,4 @@
import { ApproveNameMessageEvent, CatalogPageMessageEvent, CatalogPagesListEvent, ClubGiftInfoEvent, GiftReceiverNotFoundEvent, GiftWrappingConfigurationEvent, HabboClubOffersMessageEvent, LimitedEditionSoldOutEvent, MarketplaceMakeOfferResult, NodeData, ProductOfferEvent, PurchaseErrorMessageEvent, PurchaseNotAllowedMessageEvent, PurchaseOKMessageEvent, SellablePetPalettesMessageEvent, UserSubscriptionEvent } from '@nitrots/nitro-renderer';
import { ApproveNameMessageEvent, CatalogPageMessageEvent, CatalogPagesListEvent, ClubGiftInfoEvent, GiftReceiverNotFoundEvent, GiftWrappingConfigurationEvent, HabboClubOffersMessageEvent, LimitedEditionSoldOutEvent, MarketplaceMakeOfferResult, NodeData, ProductOfferEvent, PurchaseErrorMessageEvent, PurchaseNotAllowedMessageEvent, PurchaseOKMessageEvent, SellablePetPalettesMessageEvent } from '@nitrots/nitro-renderer';
import { GuildMembershipsMessageEvent } from '@nitrots/nitro-renderer/src/nitro/communication/messages/incoming/user/GuildMembershipsMessageEvent';
import { FC, useCallback } from 'react';
import { GetFurnitureData, GetProductDataForLocalization, LocalizeText, NotificationAlertType, NotificationUtilities, ProductTypeEnum } from '../../api';
@ -15,7 +15,6 @@ import { IPurchasableOffer } from './common/IPurchasableOffer';
import { Offer } from './common/Offer';
import { PageLocalization } from './common/PageLocalization';
import { Product } from './common/Product';
import { SubscriptionInfo } from './common/SubscriptionInfo';
export const CatalogMessageHandler: FC<{}> = props =>
{
@ -227,23 +226,6 @@ export const CatalogMessageHandler: FC<{}> = props =>
});
}, [ setCatalogOptions ]);
const onUserSubscriptionEvent = useCallback((event: UserSubscriptionEvent) =>
{
const parser = event.getParser();
setCatalogOptions(prevValue =>
{
const subscriptionInfo = new SubscriptionInfo(
Math.max(0, parser.daysToPeriodEnd),
Math.max(0, parser.periodsSubscribedAhead),
parser.isVip,
parser.pastClubDays,
parser.pastVipDays);
return { ...prevValue, subscriptionInfo };
});
}, [ setCatalogOptions ]);
const onGiftWrappingConfigurationEvent = useCallback((event: GiftWrappingConfigurationEvent) =>
{
const parser = event.getParser();
@ -301,7 +283,6 @@ export const CatalogMessageHandler: FC<{}> = props =>
UseMessageEventHook(ApproveNameMessageEvent, onApproveNameMessageEvent);
UseMessageEventHook(GiftReceiverNotFoundEvent, onGiftReceiverNotFoundEvent);
UseMessageEventHook(HabboClubOffersMessageEvent, onHabboClubOffersMessageEvent);
UseMessageEventHook(UserSubscriptionEvent, onUserSubscriptionEvent);
UseMessageEventHook(GiftWrappingConfigurationEvent, onGiftWrappingConfigurationEvent);
UseMessageEventHook(ClubGiftInfoEvent, onClubGiftInfoEvent);
UseMessageEventHook(MarketplaceMakeOfferResult, onMarketplaceMakeOfferResult);

View File

@ -1,7 +1,6 @@
import { ClubGiftInfoParser, ClubOfferData, HabboGroupEntryData, MarketplaceConfigurationMessageParser } from '@nitrots/nitro-renderer';
import { CatalogPetPalette } from './CatalogPetPalette';
import { GiftWrappingConfiguration } from './GiftWrappingConfiguration';
import { SubscriptionInfo } from './SubscriptionInfo';
export interface ICatalogOptions
{
@ -9,7 +8,6 @@ export interface ICatalogOptions
petPalettes?: CatalogPetPalette[];
clubOffers?: ClubOfferData[];
clubGifts?: ClubGiftInfoParser;
subscriptionInfo?: SubscriptionInfo;
giftConfiguration?: GiftWrappingConfiguration;
marketplaceConfiguration?: MarketplaceConfigurationMessageParser;
}

View File

@ -25,7 +25,7 @@ export const CatalogNavigationView: FC<CatalogNavigationViewProps> = props =>
{ searchResult && (searchResult.filteredNodes.length > 0) && searchResult.filteredNodes.map((n, index) =>
{
return <CatalogNavigationItemView key={ index } node={ n } />;
})}
}) }
{ !searchResult &&
<CatalogNavigationSetView node={ node } /> }
</AutoGrid>

View File

@ -3,9 +3,7 @@ import { FC, useCallback, useEffect, useMemo, useState } from 'react';
import { LocalizeText, SendMessageComposer } from '../../../../../api';
import { AutoGrid, Button, Column, Flex, Grid, LayoutCurrencyIcon, LayoutGridItem, LayoutLoadingSpinnerView, Text } from '../../../../../common';
import { CatalogEvent, CatalogPurchasedEvent, CatalogPurchaseFailureEvent } from '../../../../../events';
import { UseUiEvent } from '../../../../../hooks';
import { GetCurrencyAmount } from '../../../../purse/common/CurrencyHelper';
import { GLOBAL_PURSE } from '../../../../purse/PurseView';
import { usePurse, UseUiEvent } from '../../../../../hooks';
import { useCatalogContext } from '../../../CatalogContext';
import { CatalogPurchaseState } from '../../../common/CatalogPurchaseState';
import { CatalogLayoutProps } from './CatalogLayout.types';
@ -15,7 +13,8 @@ export const CatalogLayoutVipBuyView: FC<CatalogLayoutProps> = props =>
const [ pendingOffer, setPendingOffer ] = useState<ClubOfferData>(null);
const [ purchaseState, setPurchaseState ] = useState(CatalogPurchaseState.NONE);
const { currentPage = null, catalogOptions = null } = useCatalogContext();
const { clubOffers = null, subscriptionInfo = null } = catalogOptions;
const { purse = null, getCurrencyAmount = null } = usePurse();
const { clubOffers = null } = catalogOptions;
const onCatalogEvent = useCallback((event: CatalogEvent) =>
{
@ -54,8 +53,6 @@ export const CatalogLayoutVipBuyView: FC<CatalogLayoutProps> = props =>
const getPurchaseHeader = useCallback(() =>
{
const purse = GLOBAL_PURSE;
if(!purse) return '';
const extensionOrSubscription = (purse.clubDays > 0 || purse.clubPeriods > 0) ? 'extension.' : 'subscription.';
@ -64,7 +61,7 @@ export const CatalogLayoutVipBuyView: FC<CatalogLayoutProps> = props =>
const locale = LocalizeText('catalog.vip.buy.confirm.' + extensionOrSubscription + daysOrMonths);
return locale.replace('%NUM_' + daysOrMonths.toUpperCase() + '%', daysOrMonthsText.toString());
}, [ pendingOffer ]);
}, [ pendingOffer, purse ]);
const getPurchaseValidUntil = useCallback(() =>
{
@ -79,14 +76,12 @@ export const CatalogLayoutVipBuyView: FC<CatalogLayoutProps> = props =>
const getSubscriptionDetails = useMemo(() =>
{
if(!subscriptionInfo) return '';
const clubDays = subscriptionInfo.clubDays;
const clubPeriods = subscriptionInfo.clubPeriods;
const clubDays = purse.clubDays;
const clubPeriods = purse.clubPeriods;
const totalDays = (clubPeriods * 31) + clubDays;
return LocalizeText('catalog.vip.extend.info', [ 'days' ], [ totalDays.toString() ]);
}, [ subscriptionInfo ]);
}, [ purse ]);
const purchaseSubscription = useCallback(() =>
{
@ -106,12 +101,12 @@ export const CatalogLayoutVipBuyView: FC<CatalogLayoutProps> = props =>
{
if(!pendingOffer) return null;
if(pendingOffer.priceCredits > GetCurrencyAmount(-1))
if(pendingOffer.priceCredits > getCurrencyAmount(-1))
{
return <Button fullWidth variant="danger">{ LocalizeText('catalog.alert.notenough.title') }</Button>;
}
if(pendingOffer.priceActivityPoints > GetCurrencyAmount(pendingOffer.priceActivityPointsType))
if(pendingOffer.priceActivityPoints > getCurrencyAmount(pendingOffer.priceActivityPointsType))
{
return <Button fullWidth variant="danger">{ LocalizeText('catalog.alert.notenough.activitypoints.title.' + pendingOffer.priceActivityPointsType) }</Button>;
}
@ -128,7 +123,7 @@ export const CatalogLayoutVipBuyView: FC<CatalogLayoutProps> = props =>
default:
return <Button fullWidth variant="success" onClick={ () => setPurchaseState(CatalogPurchaseState.CONFIRM) }>{ LocalizeText('buy') }</Button>;
}
}, [ pendingOffer, purchaseState, purchaseSubscription ]);
}, [ pendingOffer, purchaseState, purchaseSubscription, getCurrencyAmount ]);
useEffect(() =>
{

View File

@ -2,8 +2,7 @@ import { BuyMarketplaceOfferMessageComposer, GetMarketplaceOffersMessageComposer
import { FC, useCallback, useMemo, useState } from 'react';
import { LocalizeText, NotificationAlertType, NotificationUtilities, SendMessageComposer } from '../../../../../../api';
import { Button, ButtonGroup, Column, Text } from '../../../../../../common';
import { UseMessageEventHook } from '../../../../../../hooks';
import { GetCurrencyAmount } from '../../../../../purse/common/CurrencyHelper';
import { UseMessageEventHook, usePurse } from '../../../../../../hooks';
import { CatalogLayoutProps } from '../CatalogLayout.types';
import { CatalogLayoutMarketplaceItemView, PUBLIC_OFFER } from './CatalogLayoutMarketplaceItemView';
import { SearchFormView } from './CatalogLayoutMarketplaceSearchFormView';
@ -25,6 +24,7 @@ export const CatalogLayoutMarketplacePublicItemsView: FC<CatalogLayoutMarketplac
const [ totalItemsFound, setTotalItemsFound ] = useState(0);
const [ offers, setOffers ] = useState(new Map<number, MarketplaceOfferData>());
const [ lastSearch, setLastSearch ] = useState<IMarketplaceSearchOptions>({ minPrice: -1, maxPrice: -1, query: '', type: 3 });
const { getCurrencyAmount = null } = usePurse();
const requestOffers = useCallback((options: IMarketplaceSearchOptions) =>
{
@ -48,18 +48,20 @@ export const CatalogLayoutMarketplacePublicItemsView: FC<CatalogLayoutMarketplac
const purchaseItem = useCallback((offerData: MarketplaceOfferData) =>
{
if(offerData.price > GetCurrencyAmount(-1))
if(offerData.price > getCurrencyAmount(-1))
{
NotificationUtilities.simpleAlert(LocalizeText('catalog.alert.notenough.credits.description'), NotificationAlertType.DEFAULT, null, null, LocalizeText('catalog.alert.notenough.title'));
return;
}
const offerId = offerData.offerId;
NotificationUtilities.confirm(LocalizeText('catalog.marketplace.confirm_header'), () =>
{
SendMessageComposer(new BuyMarketplaceOfferMessageComposer(offerId));
},
null, null, null, LocalizeText('catalog.marketplace.confirm_title'));
},[]);
}, [ getCurrencyAmount ]);
const onMarketPlaceOffersEvent = useCallback( (event: MarketPlaceOffersEvent) =>
{

View File

@ -60,9 +60,9 @@ export const CatalogLayoutPetPurchaseView: FC<CatalogLayoutPetPurchaseViewProps>
</Flex>
<Column gap={ 1 }>
<CatalogPurchaseWidgetView />
{/* <CatalogPurchaseButtonView offer={ offer } pageId={ pageId } extra={ extraData } quantity={ 1 } isPurchaseAllowed={ nameApproved } beforePurchase={ beforePurchase } />
{ /* <CatalogPurchaseButtonView offer={ offer } pageId={ pageId } extra={ extraData } quantity={ 1 } isPurchaseAllowed={ nameApproved } beforePurchase={ beforePurchase } />
{ offer.giftable &&
<CatalogPurchaseGiftButtonView offer={ offer } pageId={ pageId } extra={ extraData } disabled={ nameApproved } /> } */}
<CatalogPurchaseGiftButtonView offer={ offer } pageId={ pageId } extra={ extraData } disabled={ nameApproved } /> } */ }
</Column>
</Column>
);

View File

@ -206,7 +206,7 @@ export const CatalogLayoutPetView: FC<CatalogLayoutProps> = props =>
<LayoutPetImageView typeId={ petIndex } paletteId={ palette.paletteId } direction={ 2 } headOnly={ true } />
</LayoutGridItem>
);
})}
}) }
{ colorsShowing && (sellableColors.length > 0) && sellableColors.map((colorSet, index) => <LayoutGridItem itemHighlight key={ index } itemActive={ (selectedColorIndex === index) } itemColor={ ColorConverter.int2rgb(colorSet[0]) } className="clear-bg" onClick={ event => setSelectedColorIndex(index) } />) }
</AutoGrid>
</Column>

View File

@ -2,14 +2,16 @@ import { SelectClubGiftComposer } from '@nitrots/nitro-renderer';
import { FC, useCallback } from 'react';
import { LocalizeText, NotificationUtilities, SendMessageComposer } from '../../../../../../api';
import { AutoGrid, Text } from '../../../../../../common';
import { usePurse } from '../../../../../../hooks';
import { useCatalogContext } from '../../../../CatalogContext';
import { CatalogLayoutProps } from '../CatalogLayout.types';
import { VipGiftItem } from './VipGiftItemView';
export const CatalogLayoutVipGiftsView: FC<CatalogLayoutProps> = props =>
{
const { purse = null } = usePurse();
const { catalogOptions = null, setCatalogOptions = null } = useCatalogContext();
const { clubGifts = null, subscriptionInfo = null } = catalogOptions;
const { clubGifts = null } = catalogOptions;
const giftsAvailable = useCallback(() =>
{
@ -19,10 +21,10 @@ export const CatalogLayoutVipGiftsView: FC<CatalogLayoutProps> = props =>
if(clubGifts.daysUntilNextGift > 0) return LocalizeText('catalog.club_gift.days_until_next', [ 'days' ], [ clubGifts.daysUntilNextGift.toString() ]);
if(subscriptionInfo.isVip) return LocalizeText('catalog.club_gift.not_available');
if(purse.isVip) return LocalizeText('catalog.club_gift.not_available');
return LocalizeText('catalog.club_gift.no_club');
}, [ clubGifts, subscriptionInfo ]);
}, [ clubGifts, purse ]);
const selectGift = useCallback((localizationId: string) =>
{

View File

@ -3,8 +3,7 @@ import { FC, useCallback, useEffect, useMemo, useState } from 'react';
import { CreateLinkEvent, GetClubMemberLevel, LocalizeText, SendMessageComposer } from '../../../../../api';
import { Button, LayoutLoadingSpinnerView } from '../../../../../common';
import { CatalogEvent, CatalogInitGiftEvent, CatalogInitPurchaseEvent, CatalogPurchasedEvent, CatalogPurchaseFailureEvent, CatalogPurchaseNotAllowedEvent, CatalogPurchaseSoldOutEvent, CatalogWidgetEvent } from '../../../../../events';
import { DispatchUiEvent, UseUiEvent } from '../../../../../hooks';
import { GetCurrencyAmount } from '../../../../purse/common/CurrencyHelper';
import { DispatchUiEvent, usePurse, UseUiEvent } from '../../../../../hooks';
import { useCatalogContext } from '../../../CatalogContext';
import { CatalogPurchaseState } from '../../../common/CatalogPurchaseState';
import { Offer } from '../../../common/Offer';
@ -21,6 +20,7 @@ export const CatalogPurchaseWidgetView: FC<CatalogPurchaseWidgetViewProps> = pro
const [ purchaseWillBeGift, setPurchaseWillBeGift ] = useState(false);
const [ purchaseState, setPurchaseState ] = useState(CatalogPurchaseState.NONE);
const { currentOffer = null, currentPage = null, purchaseOptions = null, setPurchaseOptions = null } = useCatalogContext();
const { getCurrencyAmount = null } = usePurse();
const onCatalogInitPurchaseEvent = useCallback((event: CatalogInitPurchaseEvent) =>
{
@ -148,9 +148,9 @@ export const CatalogPurchaseWidgetView: FC<CatalogPurchaseWidgetViewProps> = pro
if(isLimitedSoldOut) return <Button variant="danger" disabled>{ LocalizeText('catalog.alert.limited_edition_sold_out.title') }</Button>;
if(priceCredits > GetCurrencyAmount(-1)) return <Button variant="danger" disabled>{ LocalizeText('catalog.alert.notenough.title') }</Button>;
if(priceCredits > getCurrencyAmount(-1)) return <Button variant="danger" disabled>{ LocalizeText('catalog.alert.notenough.title') }</Button>;
if(pricePoints > GetCurrencyAmount(currentOffer.activityPointType)) return <Button variant="danger" disabled>{ LocalizeText('catalog.alert.notenough.activitypoints.title.' + currentOffer.activityPointType) }</Button>;
if(pricePoints > getCurrencyAmount(currentOffer.activityPointType)) return <Button variant="danger" disabled>{ LocalizeText('catalog.alert.notenough.activitypoints.title.' + currentOffer.activityPointType) }</Button>;
switch(purchaseState)
{

View File

@ -124,7 +124,7 @@ export const FriendsMessengerView: FC<{}> = props =>
</Flex>
</LayoutGridItem>
);
})}
}) }
</Column>
</Column>
<Column size={ 8 } overflow="hidden">

View File

@ -1,42 +1,31 @@
import { BadgesEvent, ClubGiftInfoEvent, FriendlyTime, GetClubGiftInfo, ILinkEventTracker, RequestBadgesComposer, ScrGetKickbackInfoMessageComposer, ScrKickbackData, ScrSendKickbackInfoMessageEvent, UserSubscriptionEvent } from '@nitrots/nitro-renderer';
import { ClubGiftInfoEvent, FriendlyTime, GetClubGiftInfo, ILinkEventTracker, ScrGetKickbackInfoMessageComposer, ScrKickbackData, ScrSendKickbackInfoMessageEvent } from '@nitrots/nitro-renderer';
import { FC, useCallback, useEffect, useState } from 'react';
import { OverlayTrigger, Popover } from 'react-bootstrap';
import { AddEventLinkTracker, CreateLinkEvent, GetConfiguration, LocalizeText, RemoveLinkEventTracker, SendMessageComposer } from '../../api';
import { AddEventLinkTracker, ClubStatus, CreateLinkEvent, GetClubBadge, GetConfiguration, LocalizeText, RemoveLinkEventTracker, SendMessageComposer } from '../../api';
import { Base, Button, Column, Flex, LayoutAvatarImageView, LayoutBadgeImageView, NitroCardContentView, NitroCardHeaderView, NitroCardView, Text } from '../../common';
import { UseMessageEventHook, useSessionInfo } from '../../hooks';
import { BadgeResolver } from './common/BadgeResolver';
import { ClubStatus } from './common/ClubStatus';
import { useInventoryBadges, UseMessageEventHook, usePurse, useSessionInfo } from '../../hooks';
export const HcCenterView: FC<{}> = props =>
{
const [ isVisible, setIsVisible ] = useState(false);
const [ kickbackData, setKickbackData ] = useState<ScrKickbackData>(null);
const [ clubDays, setClubDays ] = useState(0);
const [ pastClubDays, setPastClubDays ] = useState(0);
const [ clubPeriods, setPastClubPeriods ] = useState(0);
const [ minsTillExpire, setMinsTillExpire ] = useState(0);
const [ clubStatus, setClubStatus ] = useState(ClubStatus.NONE);
const [ unclaimedGifts, setUnclaimedGifts ] = useState(0);
const [ badgeCode, setBadgeCode ] = useState(BadgeResolver.default_badge);
const [ badgeCode, setBadgeCode ] = useState(null);
const { userFigure = null } = useSessionInfo();
const { purse = null, clubStatus = null } = usePurse();
const { badgeCodes = [], activate = null, deactivate = null } = useInventoryBadges();
const getClubText = () =>
{
const totalDays = ((clubPeriods * 31) + clubDays);
const minutesUntilExpiration = minsTillExpire;
if(clubStatus !== ClubStatus.ACTIVE)
if(purse.clubDays <= 0) return LocalizeText('purse.clubdays.zero.amount.text');
if((purse.minutesUntilExpiration > -1) && (purse.minutesUntilExpiration < (60 * 24)))
{
return LocalizeText('purse.clubdays.zero.amount.text');
return FriendlyTime.shortFormat(purse.minutesUntilExpiration * 60);
}
if((minutesUntilExpiration > -1) && (minutesUntilExpiration < (60 * 24)))
{
return FriendlyTime.shortFormat(minutesUntilExpiration * 60);
}
return FriendlyTime.shortFormat(totalDays * 86400);
return FriendlyTime.shortFormat(((purse.clubPeriods * 31) + purse.clubDays) * 86400);
}
const getInfoText = () =>
@ -52,16 +41,8 @@ export const HcCenterView: FC<{}> = props =>
}
}
const getHcPaydayTime = () =>
{
if(kickbackData.timeUntilPayday < 60) return LocalizeText('hccenter.special.time.soon');
return FriendlyTime.shortFormat(kickbackData.timeUntilPayday * 60);
}
const getHcPaydayAmount = () =>
{
return LocalizeText('hccenter.special.sum', [ 'credits' ], [ (kickbackData.creditRewardForStreakBonus + kickbackData.creditRewardForMonthlySpent).toString() ]);
}
const getHcPaydayTime = () => (kickbackData.timeUntilPayday < 60) ? LocalizeText('hccenter.special.time.soon') : FriendlyTime.shortFormat(kickbackData.timeUntilPayday * 60);
const getHcPaydayAmount = () => LocalizeText('hccenter.special.sum', [ 'credits' ], [ (kickbackData.creditRewardForStreakBonus + kickbackData.creditRewardForMonthlySpent).toString() ]);
const onClubGiftInfoEvent = useCallback((event: ClubGiftInfoEvent) =>
{
@ -81,87 +62,56 @@ export const HcCenterView: FC<{}> = props =>
UseMessageEventHook(ScrSendKickbackInfoMessageEvent, onScrSendKickbackInfo);
const onBadges = useCallback((event: BadgesEvent) =>
{
const parser = event.getParser();
setBadgeCode(BadgeResolver.getClubBadge(parser.getAllBadgeCodes()));
}, []);
UseMessageEventHook(BadgesEvent, onBadges);
const onUserSubscriptionEvent = useCallback((event: UserSubscriptionEvent) =>
{
const parser = event.getParser();
const productName = parser.productName;
if((productName !== 'club_habbo') && (productName !== 'habbo_club')) return;
setClubDays(Math.max(0, parser.daysToPeriodEnd));
setPastClubPeriods(Math.max(0, parser.periodsSubscribedAhead));
setPastClubDays(Math.max(0, parser.pastClubDays));
setMinsTillExpire(Math.max(0, parser.minutesUntilExpiration));
}, []);
UseMessageEventHook(UserSubscriptionEvent, onUserSubscriptionEvent);
const linkReceived = useCallback((url: string) =>
{
const parts = url.split('/');
if(parts.length < 2) return;
switch(parts[1])
{
case 'open':
if(parts.length > 2)
{
switch(parts[2])
{
case 'hccenter':
setIsVisible(true);
break;
}
}
return;
}
}, []);
useEffect(() =>
{
const linkTracker: ILinkEventTracker = {
linkReceived,
linkReceived: (url: string) =>
{
const parts = url.split('/');
if(parts.length < 2) return;
switch(parts[1])
{
case 'open':
if(parts.length > 2)
{
switch(parts[2])
{
case 'hccenter':
setIsVisible(true);
break;
}
}
return;
}
},
eventUrlPrefix: 'habboUI/'
};
AddEventLinkTracker(linkTracker);
return () => RemoveLinkEventTracker(linkTracker);
}, [ linkReceived ]);
}, []);
useEffect(() =>
{
if(clubDays > 0)
{
setClubStatus(ClubStatus.ACTIVE);
setBadgeCode(GetClubBadge(badgeCodes));
}, [ badgeCodes ]);
return;
}
useEffect(() =>
{
if(!isVisible) return;
if(pastClubDays > 0)
{
setClubStatus(ClubStatus.EXPIRED);
const id = activate();
return;
}
}, [ clubDays, pastClubDays ]);
return () => deactivate(id);
}, [ isVisible, activate, deactivate ]);
useEffect(() =>
{
SendMessageComposer(new GetClubGiftInfo());
SendMessageComposer(new ScrGetKickbackInfoMessageComposer());
SendMessageComposer(new RequestBadgesComposer());
}, []);
if(!isVisible) return null;
@ -169,18 +119,14 @@ export const HcCenterView: FC<{}> = props =>
const popover = (
<Popover id="popover-basic">
<Popover.Body className="text-black py-2 px-3">
<h5>{LocalizeText('hccenter.breakdown.title')}</h5>
<div>{LocalizeText('hccenter.breakdown.creditsspent', [ 'credits' ], [ kickbackData.totalCreditsSpent.toString() ])}</div>
<div>{LocalizeText('hccenter.breakdown.paydayfactor.percent', [ 'percent' ], [ (kickbackData.kickbackPercentage * 100).toString() ])}</div>
<div>{LocalizeText('hccenter.breakdown.streakbonus', [ 'credits' ], [ kickbackData.creditRewardForStreakBonus.toString() ])}</div>
<h5>{ LocalizeText('hccenter.breakdown.title') }</h5>
<div>{ LocalizeText('hccenter.breakdown.creditsspent', [ 'credits' ], [ kickbackData.totalCreditsSpent.toString() ]) }</div>
<div>{ LocalizeText('hccenter.breakdown.paydayfactor.percent', [ 'percent' ], [ (kickbackData.kickbackPercentage * 100).toString() ]) }</div>
<div>{ LocalizeText('hccenter.breakdown.streakbonus', [ 'credits' ], [ kickbackData.creditRewardForStreakBonus.toString() ]) }</div>
<hr className="w-100 text-black my-1" />
<div>{LocalizeText('hccenter.breakdown.total', [ 'credits', 'actual' ], [ getHcPaydayAmount(),
((((kickbackData.kickbackPercentage * kickbackData.totalCreditsSpent) + kickbackData.creditRewardForStreakBonus) * 100) / 100).toString() ])}</div>
<div className="btn btn-link text-primary p-0" onClick={ () =>
{
CreateLinkEvent('habbopages/' + GetConfiguration('hc.center')['payday.habbopage'])
} }>{
LocalizeText('hccenter.special.infolink')}
<div>{ LocalizeText('hccenter.breakdown.total', [ 'credits', 'actual' ], [ getHcPaydayAmount(), ((((kickbackData.kickbackPercentage * kickbackData.totalCreditsSpent) + kickbackData.creditRewardForStreakBonus) * 100) / 100).toString() ]) }</div>
<div className="btn btn-link text-primary p-0" onClick={ () => CreateLinkEvent('habbopages/' + GetConfiguration('hc.center')['payday.habbopage']) }>
{ LocalizeText('hccenter.special.infolink') }
</div>
</Popover.Body>
</Popover>
@ -193,10 +139,7 @@ export const HcCenterView: FC<{}> = props =>
<Column gap={ 1 }>
<div className="hc-logo" />
<Flex>
<Button variant="success" onClick={ event =>
{
CreateLinkEvent('catalog/open/' + GetConfiguration('catalog.links')['hc.buy_hc'])
} }>
<Button variant="success" onClick={ event => CreateLinkEvent('catalog/open/' + GetConfiguration('catalog.links')['hc.buy_hc']) }>
{ LocalizeText((clubStatus === ClubStatus.ACTIVE) ? 'hccenter.btn.extend' : 'hccenter.btn.buy') }
</Button>
</Flex>
@ -215,58 +158,50 @@ export const HcCenterView: FC<{}> = props =>
</Flex>
{ GetConfiguration('hc.center')['payday.info'] &&
<Flex alignItems="center">
<Column className="rounded-start bg-primary p-2 payday-special mb-1">
<h4 className="mb-1">{LocalizeText('hccenter.special.title')}</h4>
<div>{LocalizeText('hccenter.special.info')}</div>
<div className="btn btn-link text-white p-0 mt-auto align-self-baseline" onClick={ () =>
{
CreateLinkEvent('habbopages/' + GetConfiguration('hc.center')['payday.habbopage'])
} }>{LocalizeText('hccenter.special.infolink')}</div>
<h4 className="mb-1">{ LocalizeText('hccenter.special.title') }</h4>
<div>{ LocalizeText('hccenter.special.info') }</div>
<div className="btn btn-link text-white p-0 mt-auto align-self-baseline" onClick={ () => CreateLinkEvent('habbopages/' + GetConfiguration('hc.center')['payday.habbopage']) }>{ LocalizeText('hccenter.special.infolink') }</div>
</Column>
<div className="payday flex-shrink-0 p-2">
<h5 className="mb-2 ms-2">{LocalizeText('hccenter.special.time.title')}</h5>
<h5 className="mb-2 ms-2">{ LocalizeText('hccenter.special.time.title') }</h5>
<div className="d-flex flex-row mb-2">
<div className="clock me-2" />
<h6 className="mb-0 align-self-center">{getHcPaydayTime()}</h6>
<h6 className="mb-0 align-self-center">{ getHcPaydayTime() }</h6>
</div>
{clubStatus === ClubStatus.ACTIVE &&
{ clubStatus === ClubStatus.ACTIVE &&
<div className="pe-3">
<h5 className="ms-2 mb-1 bolder">{LocalizeText('hccenter.special.amount.title')}</h5>
<h5 className="ms-2 mb-1 bolder">{ LocalizeText('hccenter.special.amount.title') }</h5>
<div className="d-flex flex-column">
<div className="w-100 text-center ms-4n">{getHcPaydayAmount()}</div>
<div className="w-100 text-center ms-4n">{ getHcPaydayAmount() }</div>
<OverlayTrigger trigger={ [ 'hover', 'focus' ] } placement="left" overlay={ popover }>
<div className="btn btn-link align-self-end text-primary">
{LocalizeText('hccenter.breakdown.infolink')}
{ LocalizeText('hccenter.breakdown.infolink') }
</div>
</OverlayTrigger>
</div>
</div>
}
</div> }
</div>
</Flex>
}
{GetConfiguration('hc.center')['gift.info'] &&
</Flex> }
{ GetConfiguration('hc.center')['gift.info'] &&
<div className="rounded bg-success p-2 d-flex flex-row mb-0">
<div>
<h4 className="mb-1">{LocalizeText('hccenter.gift.title')}</h4>
<h4 className="mb-1">{ LocalizeText('hccenter.gift.title') }</h4>
<div dangerouslySetInnerHTML={ { __html: unclaimedGifts > 0 ? LocalizeText('hccenter.unclaimedgifts', [ 'unclaimedgifts' ], [ unclaimedGifts.toString() ]) : LocalizeText('hccenter.gift.info') } }></div>
</div>
<button className="btn btn-primary btn-lg align-self-center ms-auto" onClick={ () =>
{
CreateLinkEvent('catalog/open/' + GetConfiguration('catalog.links')['hc.hc_gifts'])
} }>{LocalizeText(clubStatus === ClubStatus.ACTIVE ? 'hccenter.btn.gifts.redeem' : 'hccenter.btn.gifts.view')}</button>
</div>
}
{GetConfiguration('hc.center')['benefits.info'] &&
<button className="btn btn-primary btn-lg align-self-center ms-auto" onClick={ () => CreateLinkEvent('catalog/open/' + GetConfiguration('catalog.links')['hc.hc_gifts']) }>
{ LocalizeText(clubStatus === ClubStatus.ACTIVE ? 'hccenter.btn.gifts.redeem' : 'hccenter.btn.gifts.view') }
</button>
</div> }
{ GetConfiguration('hc.center')['benefits.info'] &&
<div className="benefits text-black py-2">
<h5 className="mb-1 text-primary">{LocalizeText('hccenter.general.title')}</h5>
<h5 className="mb-1 text-primary">{ LocalizeText('hccenter.general.title') }</h5>
<div className="mb-2" dangerouslySetInnerHTML={ { __html: LocalizeText('hccenter.general.info') } } />
<button className="btn btn-link p-0 text-primary" onClick={ () =>
{
CreateLinkEvent('habbopages/' + GetConfiguration('hc.center')['benefits.habbopage'])
} }>{LocalizeText('hccenter.general.infolink')}</button>
</div>
}
<button className="btn btn-link p-0 text-primary" onClick={ () => CreateLinkEvent('habbopages/' + GetConfiguration('hc.center')['benefits.habbopage']) }>
{ LocalizeText('hccenter.general.infolink') }
</button>
</div> }
</NitroCardContentView>
</NitroCardView>
);

View File

@ -1,23 +0,0 @@
export class BadgeResolver
{
public static default_badge: string = 'HC1';
public static badges: string[] = [ 'ACH_VipHC1', 'ACH_VipHC2', 'ACH_VipHC3', 'ACH_VipHC4', 'ACH_VipHC5', 'HC1', 'HC2', 'HC3', 'HC4', 'HC5' ];
public static getClubBadge(k: string[]): string
{
var badgeCode: string = null;
this.badges.forEach(badge =>
{
if (k.indexOf(badge) > -1)
{
badgeCode = badge;
}
});
return badgeCode || this.default_badge;
}
}

View File

@ -58,28 +58,28 @@ export const SanctionSatusView:FC<{}> = props =>
</Column>
<Column justifyContent="between" size={ 7 } overflow="hidden">
{ (sanctionInfo.sanctionReason === 'cfh.reason.EMPTY')
? <div className="col-12 fw-bold">{LocalizeText('help.sanction.current.none')}</div>
? <div className="col-12 fw-bold">{ LocalizeText('help.sanction.current.none') }</div>
: <>
{((sanctionInfo.probationHoursLeft > 0) || (sanctionInfo.isSanctionActive)) &&
<div className="col-12 fw-bold">{LocalizeText('help.sanction.probation.reminder')}</div>
{ ((sanctionInfo.probationHoursLeft > 0) || (sanctionInfo.isSanctionActive)) &&
<div className="col-12 fw-bold">{ LocalizeText('help.sanction.probation.reminder') }</div>
}
<div className={ `col-12 fw-bold ${ sanctionInfo.isSanctionNew ? 'text-danger' : '' }` }>
{LocalizeText('help.sanction.last.sanction')} {sanctionLocalization('current', sanctionInfo.sanctionName, sanctionInfo.sanctionLengthHours)}
{ LocalizeText('help.sanction.last.sanction') } { sanctionLocalization('current', sanctionInfo.sanctionName, sanctionInfo.sanctionLengthHours) }
</div>
<div className="col-12">{LocalizeText('generic.start.time')} {sanctionInfo.sanctionCreationTime}</div>
<div className="col-12">{LocalizeText('generic.reason')} {sanctionInfo.sanctionReason}</div>
<div className="col-12">{LocalizeText('help.sanction.probation.days.left')} {Math.trunc((sanctionInfo.probationHoursLeft / 24)) + 1}</div>
<div className="col-12">{ LocalizeText('generic.start.time') } { sanctionInfo.sanctionCreationTime }</div>
<div className="col-12">{ LocalizeText('generic.reason') } { sanctionInfo.sanctionReason }</div>
<div className="col-12">{ LocalizeText('help.sanction.probation.days.left') } { Math.trunc((sanctionInfo.probationHoursLeft / 24)) + 1 }</div>
</>
}
{ ((sanctionInfo.hasCustomMute) && (!(sanctionInfo.isSanctionActive))) &&
<div className="col-12 fw-bold">{LocalizeText('help.sanction.custom.mute')}</div>
<div className="col-12 fw-bold">{ LocalizeText('help.sanction.custom.mute') }</div>
}
{ (sanctionInfo.tradeLockExpiryTime && sanctionInfo.tradeLockExpiryTime.length > 0) &&
<div className="col-12 fw-bold">{LocalizeText('trade.locked.until')} {sanctionInfo.tradeLockExpiryTime}</div>
<div className="col-12 fw-bold">{ LocalizeText('trade.locked.until') } { sanctionInfo.tradeLockExpiryTime }</div>
}
<div className="col-12">{sanctionLocalization('next', sanctionInfo.nextSanctionName, sanctionInfo.nextSanctionLengthHours)}</div>
<Button variant="success" onClick={ event => setSanctionInfo(null) }>{LocalizeText('habbo.way.ok.button')}</Button>
<div className="col-12">{ sanctionLocalization('next', sanctionInfo.nextSanctionName, sanctionInfo.nextSanctionLengthHours) }</div>
<Button variant="success" onClick={ event => setSanctionInfo(null) }>{ LocalizeText('habbo.way.ok.button') }</Button>
</Column>
</Grid>
</NitroCardContentView>

View File

@ -97,11 +97,11 @@ export const HotelView: FC<{}> = props =>
<div className="left position-absolute" style={ (left && left.length) ? { backgroundImage: `url(${ left })` } : {} } />
<div className="right-repeat position-absolute" style={ (rightRepeat && rightRepeat.length) ? { backgroundImage: `url(${ rightRepeat })` } : {} } />
<div className="right position-absolute" style={ (right && right.length) ? { backgroundImage: `url(${ right })` } : {} } />
{GetConfiguration('hotelview')['show.avatar'] && (
{ GetConfiguration('hotelview')['show.avatar'] && (
<div className="avatar-image">
<LayoutAvatarImageView figure={ userFigure } direction={ 2 } />
</div>
)}
) }
</div>
);
}

View File

@ -36,7 +36,7 @@ export const BonusRareWidgetView: FC<BonusRareWidgetViewProps> = props =>
<div className="bonus-rare widget d-flex">
{ productType }
<div className="bg-light-dark rounded overflow-hidden position-relative bonus-bar-container">
<div className="d-flex justify-content-center align-items-center w-100 h-100 position-absolute small top-0">{(totalCoinsForBonus - coinsStillRequiredToBuy) + '/' + totalCoinsForBonus}</div>
<div className="d-flex justify-content-center align-items-center w-100 h-100 position-absolute small top-0">{ (totalCoinsForBonus - coinsStillRequiredToBuy) + '/' + totalCoinsForBonus }</div>
<div className="small bg-info rounded position-absolute top-0 h-100" style={ { width: ((totalCoinsForBonus - coinsStillRequiredToBuy) / totalCoinsForBonus) * 100 + '%' } }></div>
</div>
</div>

View File

@ -19,7 +19,7 @@ export const HallOfFameItemView: FC<HallOfFameItemViewProps> = props =>
<div className="hof-header">
{ level }. { data.userName } <UserProfileIconView userId={ data.userId } />
</div>
<div className="small text-center text-white">{ LocalizeText('landing.view.competition.hof.points', [ 'points' ], [ LocalizeFormattedNumber(data.currentScore).toString() ])}</div>
<div className="small text-center text-white">{ LocalizeText('landing.view.competition.hof.points', [ 'points' ], [ LocalizeFormattedNumber(data.currentScore).toString() ]) }</div>
</div>
<LayoutAvatarImageView figure={ data.figure } direction={ 2 } />
</div>

View File

@ -33,7 +33,7 @@ export const HallOfFameWidgetView: FC<HallOfFameWidgetViewProps> = props =>
{
return <HallOfFameItemView key={ index } data={ entry } level={ (index + 1) } />;
}
)}
) }
</div>
);
}

View File

@ -30,15 +30,15 @@ export const PromoArticleWidgetView: FC<{}> = props =>
<hr className="w-100 my-0"/>
</div>
<div className="d-flex flex-row mb-1">
{articles && (articles.length > 0) && articles.map((article, ind) =>
{ articles && (articles.length > 0) && articles.map((article, ind) =>
<div className={ 'promo-articles-bullet cursor-pointer ' + (article === articles[index] ? 'promo-articles-bullet-active' : '') } key={ article.id } onClick={ event => setIndex(ind) } />
)}
) }
</div>
{articles && articles[index] &&
{ articles && articles[index] &&
<div className="promo-article d-flex flex-row row mx-0">
<div className="promo-article-image" style={ { backgroundImage: `url(${ articles[index].imageUrl })` } }/>
<div className="col-3 d-flex flex-column h-100">
<h3 className="my-0">{articles[index].title}</h3>
<h3 className="my-0">{ articles[index].title }</h3>
<b>{ articles[index].bodyText }</b>
<button className="btn btn-sm mt-auto btn-gainsboro" onClick={ event => NotificationUtilities.openUrl(articles[index].linkContent) }>{ articles[index].buttonText }</button>
</div>

View File

@ -29,7 +29,7 @@ export const WidgetContainerView: FC<WidgetContainerViewProps> = props =>
<div className="widgetcontainer widget d-flex flex-row overflow-hidden">
<div className="widgetcontainer-image flex-shrink-0" style={ { backgroundImage: `url(${ getOption('image') })` } } />
<div className="d-flex flex-column align-self-center">
<h3 className="my-0">{LocalizeText(`landing.view.${ getOption('texts') }.header`)}</h3>
<h3 className="my-0">{ LocalizeText(`landing.view.${ getOption('texts') }.header`) }</h3>
<i>{ LocalizeText(`landing.view.${ getOption('texts') }.body`) }</i>
<button className="btn btn-sm btn-gainsboro align-self-start px-3 mt-auto" onClick={ event => NotificationUtilities.openUrl(getOption('btnLink')) }>{ LocalizeText(`landing.view.${ getOption('texts') }.button`) }</button>
</div>

View File

@ -27,7 +27,7 @@ export const LoadingView: FC<LoadingViewProps> = props =>
<Base className="connecting-duck" />
<Column size={ 6 } className="text-center py-4">
{ isError && (message && message.length) ?
<Base className="fs-4 text-shadow">{message}</Base>
<Base className="fs-4 text-shadow">{ message }</Base>
:
<>
<Text fontSize={ 4 } variant="white" className="text-shadow">{ percent.toFixed() }%</Text>

View File

@ -36,7 +36,7 @@ export const CfhChatlogView: FC<CfhChatlogViewProps> = props =>
<NitroCardView className="nitro-mod-tools-chatlog" theme="primary-slim">
<NitroCardHeaderView headerText={ 'Issue Chatlog' } onCloseClick={ onCloseClick } />
<NitroCardContentView className="text-black">
{ chatlogData && <ChatlogView records={ [ chatlogData.chatRecord ] } />}
{ chatlogData && <ChatlogView records={ [ chatlogData.chatRecord ] } /> }
</NitroCardContentView>
</NitroCardView>
);

View File

@ -155,7 +155,7 @@ export const ModToolsUserModActionView: FC<ModToolsUserModActionViewProps> = pro
<NitroCardContentView className="text-black">
<select className="form-select form-select-sm" value={ selectedTopic } onChange={ event => setSelectedTopic(parseInt(event.target.value)) }>
<option value={ -1 } disabled>CFH Topic</option>
{ topics.map((topic, index) => <option key={ index } value={ index }>{LocalizeText('help.cfh.topic.' + topic.id)}</option>) }
{ topics.map((topic, index) => <option key={ index } value={ index }>{ LocalizeText('help.cfh.topic.' + topic.id) }</option>) }
</select>
<select className="form-select form-select-sm" value={ selectedAction } onChange={ event => setSelectedAction(parseInt(event.target.value)) }>
<option value={ -1 } disabled>Sanction Type</option>

View File

@ -68,7 +68,7 @@ export const NavigatorRoomSettingsModTabView: FC<NavigatorRoomSettingsTabViewPro
</Column>
</Flex>
<Button disabled={ (selectedUserId <= 0) } onClick={ event => unBanUser(selectedUserId) }>
{ LocalizeText('navigator.roomsettings.moderation.unban') } {selectedUserId > 0 && bannedUsers.find(user => (user.userId === selectedUserId))?.userName }
{ LocalizeText('navigator.roomsettings.moderation.unban') } { selectedUserId > 0 && bannedUsers.find(user => (user.userId === selectedUserId))?.userName }
</Button>
</Column>
<Column size={ 6 }>
@ -80,7 +80,7 @@ export const NavigatorRoomSettingsModTabView: FC<NavigatorRoomSettingsTabViewPro
</Flex>
</Column>
<Column gap={ 1 }>
<Text bold>{LocalizeText('navigator.roomsettings.moderation.kick.header')}</Text>
<Text bold>{ LocalizeText('navigator.roomsettings.moderation.kick.header') }</Text>
<Flex alignItems="center" gap={ 1 }>
<input className="form-check-input" type="checkbox" checked={ (roomData.moderationSettings.allowKick === 0) } onChange={ event => handleChange('moderation_kick', (event.target.checked ? 0 : 2)) } />
<Text>{ LocalizeText('navigator.roomsettings.moderation.all') }</Text>
@ -91,7 +91,7 @@ export const NavigatorRoomSettingsModTabView: FC<NavigatorRoomSettingsTabViewPro
</Flex>
</Column>
<Column gap={ 1 }>
<Text bold>{LocalizeText('navigator.roomsettings.moderation.ban.header')}</Text>
<Text bold>{ LocalizeText('navigator.roomsettings.moderation.ban.header') }</Text>
<Flex alignItems="center" gap={ 1 }>
<input className="form-check-input" type="checkbox" checked={ (roomData.moderationSettings.allowBan === 1) } onChange={ event => handleChange('moderation_ban', (event.target.checked ? 1 : 0)) } />
<Text>{ LocalizeText('navigator.roomsettings.moderation.rights') }</Text>

View File

@ -22,13 +22,13 @@ export const NavigatorRoomSettingsVipChatTabView: FC<NavigatorRoomSettingsTabVie
return (
<>
<Column gap={ 1 }>
<Text bold>{LocalizeText('navigator.roomsettings.vip.caption')}</Text>
<Text>{LocalizeText('navigator.roomsettings.vip.info')}</Text>
<Text bold>{ LocalizeText('navigator.roomsettings.vip.caption') }</Text>
<Text>{ LocalizeText('navigator.roomsettings.vip.info') }</Text>
</Column>
<Grid overflow="auto">
<Column size={ 6 } gap={ 1 }>
<Text bold>{LocalizeText('navigator.roomsettings.chat_settings')}</Text>
<Text>{LocalizeText('navigator.roomsettings.chat_settings.info')}</Text>
<Text bold>{ LocalizeText('navigator.roomsettings.chat_settings') }</Text>
<Text>{ LocalizeText('navigator.roomsettings.chat_settings.info') }</Text>
<select className="form-select form-select-sm" value={ roomData.chatSettings.mode } onChange={ event => handleChange('bubble_mode', event.target.value) }>
<option value={ RoomChatSettings.CHAT_MODE_FREE_FLOW }>{ LocalizeText('navigator.roomsettings.chat.mode.free.flow') }</option>
<option value={ RoomChatSettings.CHAT_MODE_LINE_BY_LINE }>{ LocalizeText('navigator.roomsettings.chat.mode.line.by.line') }</option>
@ -52,10 +52,10 @@ export const NavigatorRoomSettingsVipChatTabView: FC<NavigatorRoomSettingsTabVie
<input type="number" min="0" className="form-control form-control-sm" value={ chatDistance } onChange={ event => setChatDistance(event.target.valueAsNumber) } onBlur={ event => handleChange('chat_distance', chatDistance) } />
</Column>
<Column size={ 6 } gap={ 1 }>
<Text bold>{LocalizeText('navigator.roomsettings.vip_settings')}</Text>
<Text bold>{ LocalizeText('navigator.roomsettings.vip_settings') }</Text>
<Flex alignItems="center" gap={ 1 }>
<input className="form-check-input" type="checkbox" checked={ roomData.hideWalls } onChange={ event => handleChange('hide_walls', event.target.checked) } />
<Text>{LocalizeText('navigator.roomsettings.hide_walls')}</Text>
<Text>{ LocalizeText('navigator.roomsettings.hide_walls') }</Text>
</Flex>
<select className="form-select form-select-sm" value={ roomData.wallThickness } onChange={ event => handleChange('wall_thickness', event.target.value) }>
<option value="0">{ LocalizeText('navigator.roomsettings.wall_thickness.normal') }</option>

View File

@ -69,30 +69,30 @@ export const NavigatorSearchResultItemInfoView: FC<NavigatorSearchResultItemInfo
<NitroCardContentView overflow="hidden" className="room-info bg-transparent">
<Flex gap={ 2 } overflow="hidden">
<LayoutRoomThumbnailView roomId={ roomData.roomId } customUrl={ roomData.officialRoomPicRef } className="d-flex flex-column align-items-center justify-content-end mb-1">
{roomData.habboGroupId > 0 && (
<LayoutBadgeImageView badgeCode={ roomData.groupBadgeCode } isGroup={ true } className={ 'position-absolute top-0 start-0 m-1 ' }/>)}
{roomData.doorMode !== RoomDataParser.OPEN_STATE && (
<i className={ 'position-absolute end-0 mb-1 me-1 icon icon-navigator-room-' + (roomData.doorMode === RoomDataParser.DOORBELL_STATE ? 'locked' : roomData.doorMode === RoomDataParser.PASSWORD_STATE ? 'password' : roomData.doorMode === RoomDataParser.INVISIBLE_STATE ? 'invisible' : '') }/> )}
{ roomData.habboGroupId > 0 && (
<LayoutBadgeImageView badgeCode={ roomData.groupBadgeCode } isGroup={ true } className={ 'position-absolute top-0 start-0 m-1 ' }/>) }
{ roomData.doorMode !== RoomDataParser.OPEN_STATE && (
<i className={ 'position-absolute end-0 mb-1 me-1 icon icon-navigator-room-' + (roomData.doorMode === RoomDataParser.DOORBELL_STATE ? 'locked' : roomData.doorMode === RoomDataParser.PASSWORD_STATE ? 'password' : roomData.doorMode === RoomDataParser.INVISIBLE_STATE ? 'invisible' : '') }/> ) }
</LayoutRoomThumbnailView>
<Column gap={ 1 }>
<Text bold truncate className="flex-grow-1" style={ { maxHeight: 13 } }>
{roomData.roomName}
{ roomData.roomName }
</Text>
<Flex gap={ 1 }>
<Text italics variant="muted">
{LocalizeText('navigator.roomownercaption')}
{ LocalizeText('navigator.roomownercaption') }
</Text>
<UserProfileIconView
userId={ roomData.ownerId }
/>
<Text italics>{roomData.ownerName}</Text>
<Text italics>{ roomData.ownerName }</Text>
</Flex>
<Text className="flex-grow-1">
{roomData.description}
{ roomData.description }
</Text>
<Flex className={ 'badge p-1 position-absolute m-1 bottom-0 end-0 m-2 ' + getUserCounterColor() } gap={ 1 }>
<FontAwesomeIcon icon="user" />
{roomData.userCount}
{ roomData.userCount }
</Flex>
</Column>
</Flex>

View File

@ -92,7 +92,7 @@ export const NavigatorSearchResultItemView: FC<NavigatorSearchResultItemViewProp
<i className={ ('position-absolute end-0 mb-1 me-1 icon icon-navigator-room-' + ((roomData.doorMode === RoomDataParser.DOORBELL_STATE) ? 'locked' : (roomData.doorMode === RoomDataParser.PASSWORD_STATE) ? 'password' : (roomData.doorMode === RoomDataParser.INVISIBLE_STATE) ? 'invisible' : '')) } /> }
</LayoutRoomThumbnailView>
<Flex className="w-100">
<Text truncate className="flex-grow-1">{roomData.roomName}</Text>
<Text truncate className="flex-grow-1">{ roomData.roomName }</Text>
<Flex reverse alignItems="center" gap={ 1 }>
<NavigatorSearchResultItemInfoView roomData={ roomData } />
</Flex>

View File

@ -56,11 +56,11 @@ export const NavigatorSearchResultView: FC<NavigatorSearchResultViewProps> = pro
<Text>{ LocalizeText(getResultTitle()) }</Text>
</Flex>
<FontAwesomeIcon icon={ ((displayMode === NavigatorSearchResultViewDisplayMode.LIST) ? 'th' : (displayMode >= NavigatorSearchResultViewDisplayMode.THUMBNAILS) ? 'bars' : null) } className="text-secondary" onClick={ toggleDisplayMode } />
</Flex> {isExtended &&
</Flex> { isExtended &&
<>
{
gridHasTwoColumns ? <AutoGrid columnCount={ 3 } { ...rest } columnMinWidth={ 110 } columnMinHeight={ 130 } className="mx-2">
{searchResult.rooms.length > 0 && searchResult.rooms.map((room, index) => <NavigatorSearchResultItemView key={ index } roomData={ room } thumbnail={ true } />) }
{ searchResult.rooms.length > 0 && searchResult.rooms.map((room, index) => <NavigatorSearchResultItemView key={ index } roomData={ room } thumbnail={ true } />) }
</AutoGrid> : <Grid columnCount={ 1 } className="navigator-grid" gap={ 0 }>
{ searchResult.rooms.length > 0 && searchResult.rooms.map((room, index) => <NavigatorSearchResultItemView key={ index } roomData={ room } />) }
</Grid>

View File

@ -25,30 +25,30 @@ export const NotificationDefaultAlertView: FC<NotificationDefaultAlertViewProps>
return (
<LayoutNotificationAlertView title={ title } close={ close } { ...rest } type={ hasFrank ? NotificationAlertType.DEFAULT : item.alertType }>
<Flex fullHeight overflow="auto" gap={ hasFrank || (item.imageUrl && !imageFailed) ? 2 : 0 }>
{hasFrank && !item.imageUrl && <Base className="notification-frank flex-shrink-0" /> }
{item.imageUrl && !imageFailed && <img src={ item.imageUrl } alt={ item.title } onError={ () =>
{ hasFrank && !item.imageUrl && <Base className="notification-frank flex-shrink-0" /> }
{ item.imageUrl && !imageFailed && <img src={ item.imageUrl } alt={ item.title } onError={ () =>
{
setImageFailed(true)
} } className="align-self-baseline" />}
} } className="align-self-baseline" /> }
<Base classNames={ [ 'notification-text overflow-y-auto d-flex flex-column w-100', (item.clickUrl && !hasFrank) ? 'justify-content-center' : '' ] }>
{ (item.messages.length > 0) && item.messages.map((message, index) =>
{
const htmlText = message.replace(/\r\n|\r|\n/g, '<br />');
return <Base key={ index } dangerouslySetInnerHTML={ { __html: htmlText } } />;
})}
{item.clickUrl && (item.clickUrl.length > 0) && (item.imageUrl && !imageFailed) && <>
}) }
{ item.clickUrl && (item.clickUrl.length > 0) && (item.imageUrl && !imageFailed) && <>
<hr className="my-2 w-100" />
<Button onClick={ visitUrl } className="align-self-center px-3">{LocalizeText(item.clickUrlText)}</Button>
</>}
<Button onClick={ visitUrl } className="align-self-center px-3">{ LocalizeText(item.clickUrlText) }</Button>
</> }
</Base>
</Flex>
{ (!item.imageUrl || (item.imageUrl && imageFailed)) && <>
<Column alignItems="center" center gap={ 0 }>
<hr className="my-2 w-100" />
{ !item.clickUrl &&
<Button onClick={ close }>{LocalizeText('generic.close')}</Button>}
{ item.clickUrl && (item.clickUrl.length > 0) && <Button onClick={ visitUrl }>{LocalizeText(item.clickUrlText)}</Button> }
<Button onClick={ close }>{ LocalizeText('generic.close') }</Button> }
{ item.clickUrl && (item.clickUrl.length > 0) && <Button onClick={ visitUrl }>{ LocalizeText(item.clickUrlText) }</Button> }
</Column>
</> }
</LayoutNotificationAlertView>

View File

@ -42,10 +42,10 @@ export const NotificationSeachAlertView: FC<NotificationDefaultAlertViewProps> =
</Flex>
<Column fullHeight className="py-1" overflow="hidden">
<AutoGrid gap={ 1 } columnCount={ 1 }>
{results && results.map((n, index) =>
{ results && results.map((n, index) =>
{
return <span key={ index }>{ n }</span>
})}
}) }
</AutoGrid>
</Column>
<hr className="my-2"/>

View File

@ -1,18 +0,0 @@
import { createContext, FC, ProviderProps, useContext } from 'react';
import { IPurse } from './common/IPurse';
interface IPurseContext
{
purse: IPurse;
}
const PurseContext = createContext<IPurseContext>({
purse: null
});
export const PurseContextProvider: FC<ProviderProps<IPurseContext>> = props =>
{
return <PurseContext.Provider value={ props.value }>{ props.children }</PurseContext.Provider>
}
export const usePurseContext = () => useContext(PurseContext);

View File

@ -1,21 +1,14 @@
import { ActivityPointNotificationMessageEvent, FriendlyTime, HabboClubLevelEnum, UserCreditsEvent, UserCurrencyComposer, UserCurrencyEvent, UserSubscriptionComposer, UserSubscriptionEvent, UserSubscriptionParser } from '@nitrots/nitro-renderer';
import { FC, useCallback, useEffect, useMemo, useState } from 'react';
import { CreateLinkEvent, GetConfiguration, LocalizeText, PlaySound, SendMessageComposer, SoundNames } from '../../api';
import { FriendlyTime, HabboClubLevelEnum } from '@nitrots/nitro-renderer';
import { FC, useCallback, useMemo } from 'react';
import { CreateLinkEvent, GetConfiguration, LocalizeText } from '../../api';
import { Column, Flex, Grid, LayoutCurrencyIcon, Text } from '../../common';
import { UseMessageEventHook } from '../../hooks';
import { IPurse } from './common/IPurse';
import { Purse } from './common/Purse';
import { PurseContextProvider } from './PurseContext';
import { usePurse } from '../../hooks';
import { CurrencyView } from './views/CurrencyView';
import { SeasonalView } from './views/SeasonalView';
export let GLOBAL_PURSE: IPurse = null;
export const PurseView: FC<{}> = props =>
{
const [ purse, setPurse ] = useState<IPurse>(new Purse());
const hcDisabled = useMemo(() => GetConfiguration<boolean>('hc.disabled', false), []);
const { purse = null, hcDisabled = false } = usePurse();
const displayedCurrencies = useMemo(() => GetConfiguration<number[]>('system.currency.types', []), []);
const currencyDisplayNumberShort = useMemo(() => GetConfiguration<boolean>('currency.display.number.short', false), []);
@ -65,135 +58,32 @@ export const PurseView: FC<{}> = props =>
return elements;
}, [ purse, displayedCurrencies, currencyDisplayNumberShort ]);
const onUserCreditsEvent = useCallback((event: UserCreditsEvent) =>
{
const parser = event.getParser();
setPurse(prevValue =>
{
const newValue = Purse.from(prevValue as Purse);
newValue.credits = parseFloat(parser.credits);
if(prevValue.credits !== newValue.credits) PlaySound(SoundNames.CREDITS);
return newValue;
});
}, []);
UseMessageEventHook(UserCreditsEvent, onUserCreditsEvent);
const onUserCurrencyEvent = useCallback((event: UserCurrencyEvent) =>
{
const parser = event.getParser();
setPurse(prevValue =>
{
const newValue = Purse.from(prevValue as Purse);
newValue.activityPoints = parser.currencies;
return newValue;
});
}, []);
UseMessageEventHook(UserCurrencyEvent, onUserCurrencyEvent);
const onActivityPointNotificationMessageEvent = useCallback((event: ActivityPointNotificationMessageEvent) =>
{
const parser = event.getParser();
setPurse(prevValue =>
{
const newValue = Purse.from(prevValue as Purse);
newValue.activityPoints = new Map(newValue.activityPoints);
newValue.activityPoints.set(parser.type, parser.amount);
if(parser.type === 0) PlaySound(SoundNames.DUCKETS)
return newValue;
});
}, []);
UseMessageEventHook(ActivityPointNotificationMessageEvent, onActivityPointNotificationMessageEvent);
const onUserSubscriptionEvent = useCallback((event: UserSubscriptionEvent) =>
{
const parser = event.getParser();
const productName = parser.productName;
if((productName !== 'club_habbo') && (productName !== 'habbo_club')) return;
setPurse(prevValue =>
{
const newValue = Purse.from(prevValue as Purse);
newValue.clubDays = Math.max(0, parser.daysToPeriodEnd);
newValue.clubPeriods = Math.max(0, parser.periodsSubscribedAhead);
newValue.isVip = parser.isVip;
newValue.pastClubDays = parser.pastClubDays;
newValue.pastVipDays = parser.pastVipDays;
newValue.isExpiring = ((parser.responseType === UserSubscriptionParser.RESPONSE_TYPE_DISCOUNT_AVAILABLE) ? true : false);
newValue.minutesUntilExpiration = parser.minutesUntilExpiration;
newValue.minutesSinceLastModified = parser.minutesSinceLastModified;
return newValue;
});
}, []);
UseMessageEventHook(UserSubscriptionEvent, onUserSubscriptionEvent);
useEffect(() =>
{
GLOBAL_PURSE = purse;
}, [ purse ]);
useEffect(() =>
{
if(hcDisabled) return;
SendMessageComposer(new UserSubscriptionComposer('habbo_club'));
const interval = setInterval(() => SendMessageComposer(new UserSubscriptionComposer('habbo_club')), 50000);
return () => clearInterval(interval);
}, [ hcDisabled ]);
useEffect(() =>
{
SendMessageComposer(new UserCurrencyComposer());
}, []);
if(!purse) return null;
return (
<PurseContextProvider value={ { purse } }>
<Column alignItems="end" className="nitro-purse-container" gap={ 1 }>
<Flex className="nitro-purse rounded-bottom p-1">
<Grid fullWidth gap={ 1 }>
<Column justifyContent="center" size={ hcDisabled ? 10 : 6 } gap={ 0 }>
<CurrencyView type={ -1 } amount={ purse.credits } short={ currencyDisplayNumberShort } />
{ getCurrencyElements(0, 2) }
</Column>
{ !hcDisabled &&
<Column center pointer size={ 4 } gap={ 1 } className="nitro-purse-subscription rounded" onClick={ event => CreateLinkEvent('habboUI/open/hccenter') }>
<LayoutCurrencyIcon type="hc" />
<Text variant="white">{ getClubText }</Text>
</Column> }
<Column justifyContent="center" size={ 2 } gap={ 0 }>
<Flex center pointer fullHeight className="nitro-purse-button p-1 rounded" onClick={ event => CreateLinkEvent('help/show') }>
<i className="icon icon-help"/>
</Flex>
<Flex center pointer fullHeight className="nitro-purse-button p-1 rounded" onClick={ event => CreateLinkEvent('user-settings/toggle') } >
<i className="icon icon-cog"/>
</Flex>
</Column>
</Grid>
</Flex>
{ getCurrencyElements(2, -1, true) }
</Column>
</PurseContextProvider>
<Column alignItems="end" className="nitro-purse-container" gap={ 1 }>
<Flex className="nitro-purse rounded-bottom p-1">
<Grid fullWidth gap={ 1 }>
<Column justifyContent="center" size={ hcDisabled ? 10 : 6 } gap={ 0 }>
<CurrencyView type={ -1 } amount={ purse.credits } short={ currencyDisplayNumberShort } />
{ getCurrencyElements(0, 2) }
</Column>
{ !hcDisabled &&
<Column center pointer size={ 4 } gap={ 1 } className="nitro-purse-subscription rounded" onClick={ event => CreateLinkEvent('habboUI/open/hccenter') }>
<LayoutCurrencyIcon type="hc" />
<Text variant="white">{ getClubText }</Text>
</Column> }
<Column justifyContent="center" size={ 2 } gap={ 0 }>
<Flex center pointer fullHeight className="nitro-purse-button p-1 rounded" onClick={ event => CreateLinkEvent('help/show') }>
<i className="icon icon-help"/>
</Flex>
<Flex center pointer fullHeight className="nitro-purse-button p-1 rounded" onClick={ event => CreateLinkEvent('user-settings/toggle') } >
<i className="icon icon-cog"/>
</Flex>
</Column>
</Grid>
</Flex>
{ getCurrencyElements(2, -1, true) }
</Column>
);
}

View File

@ -1,17 +0,0 @@
import { GLOBAL_PURSE } from '../PurseView';
export function GetCurrencyAmount(type: number): number
{
const purse = GLOBAL_PURSE;
if(type === -1) return purse.credits;
for(const [ key, value ] of purse.activityPoints.entries())
{
if(key !== type) continue;
return value;
}
return 0;
}

View File

@ -215,7 +215,7 @@ export const AvatarInfoWidgetAvatarView: FC<AvatarInfoWidgetAvatarViewProps> = p
return (
<ContextMenuView objectId={ userData.roomIndex } category={ RoomObjectCategory.UNIT } userType={ userData.userType } close={ close }>
<ContextMenuHeaderView className="cursor-pointer" onClick={ event => GetUserProfile(userData.webID) }>
{userData.name}
{ userData.name }
</ContextMenuHeaderView>
{ (mode === MODE_NORMAL) &&
<>
@ -232,10 +232,10 @@ export const AvatarInfoWidgetAvatarView: FC<AvatarInfoWidgetAvatarViewProps> = p
{ (respectsLeft > 0) &&
<ContextMenuListItemView onClick={ event => processAction('respect') }>
{ LocalizeText('infostand.button.respect', [ 'count' ], [ respectsLeft.toString() ]) }
</ContextMenuListItemView>}
</ContextMenuListItemView> }
{ !canRequestFriend(userData.webID) &&
<ContextMenuListItemView onClick={ event => processAction('relationship') }>
{LocalizeText('infostand.link.relationship')}
{ LocalizeText('infostand.link.relationship') }
<FontAwesomeIcon icon="chevron-right" className="right" />
</ContextMenuListItemView> }
{ !userData.isIgnored &&
@ -356,7 +356,7 @@ export const AvatarInfoWidgetAvatarView: FC<AvatarInfoWidgetAvatarViewProps> = p
<FontAwesomeIcon icon="chevron-left" className="left" />
{ LocalizeText('generic.back') }
</ContextMenuListItemView>
</>}
</> }
{ (mode === MODE_RELATIONSHIP) &&
<>
<Flex className="menu-list-split-3">

View File

@ -362,7 +362,7 @@ export const ChatInputView: FC<{}> = props =>
{ !floodBlocked &&
<input ref={ inputRef } type="text" className="chat-input" placeholder={ LocalizeText('widgets.chatinput.default') } value={ chatValue } maxLength={ maxChatLength } onChange={ event => updateChatInput(event.target.value) } onMouseDown={ event => setInputFocus() } /> }
{ floodBlocked &&
<Text variant="danger">{ LocalizeText('chat.input.alert.flood', [ 'time' ], [ floodBlockedSeconds.toString() ]) } </Text>}
<Text variant="danger">{ LocalizeText('chat.input.alert.flood', [ 'time' ], [ floodBlockedSeconds.toString() ]) } </Text> }
</div>
<ChatInputStyleSelectorView chatStyleId={ chatStyleId } chatStyleIds={ chatStyleIds } selectChatStyleId={ selectChatStyleId } />
</div>, document.getElementById('toolbar-chat-input-container'))

View File

@ -236,7 +236,7 @@ export const ChatWidgetView: FC<{}> = props =>
return (
<div ref={ elementRef } className="nitro-chat-widget">
{chatMessages.map(chat => <ChatWidgetMessageView key={ chat.id } chat={ chat } makeRoom={ makeRoom } onChatClicked={ onChatClicked } bubbleWidth={ chatSettings.weight } />)}
{ chatMessages.map(chat => <ChatWidgetMessageView key={ chat.id } chat={ chat } makeRoom={ makeRoom } onChatClicked={ onChatClicked } bubbleWidth={ chatSettings.weight } />) }
</div>
);
}

View File

@ -95,7 +95,7 @@ export const FurnitureHighScoreView: FC<{}> = props =>
</Text>
</Flex>
);
})}
}) }
</Column>
</ContextMenuListView>
</Column>

View File

@ -121,7 +121,7 @@ export const FurnitureStickieView: FC<{}> = props =>
{ STICKIE_COLORS.map(color =>
{
return <div key={ color } className="stickie-color ms-1" onClick={ event => processAction('changeColor', color) } style={ { backgroundColor: ColorUtils.makeColorHex(color) } } />
})}
}) }
</> }
</div>
<div className="d-flex align-items-center nitro-stickie-image stickie-close header-close" onClick={ event => processAction('close') }></div>

View File

@ -208,11 +208,11 @@ export const FurnitureYoutubeDisplayView: FC<{}> = props =>
<NitroCardContentView>
<div className="row w-100 h-100">
<div className="youtube-video-container col-9">
{(videoId && videoId.length > 0) &&
{ (videoId && videoId.length > 0) &&
<YouTube videoId={ videoId } opts={ getYoutubeOpts as Options } onReady={ onReady } onStateChange={ onStateChange } containerClassName={ 'youtubeContainer' } />
}
{(!videoId || videoId.length === 0) &&
<div className="empty-video w-100 h-100 justify-content-center align-items-center d-flex">{LocalizeText('widget.furni.video_viewer.no_videos')}</div>
{ (!videoId || videoId.length === 0) &&
<div className="empty-video w-100 h-100 justify-content-center align-items-center d-flex">{ LocalizeText('widget.furni.video_viewer.no_videos') }</div>
}
</div>
<div className="playlist-container col-3">
@ -220,16 +220,16 @@ export const FurnitureYoutubeDisplayView: FC<{}> = props =>
<i className="icon icon-youtube-prev cursor-pointer" onClick={ () => processAction('playlist_prev') } />
<i className="icon icon-youtube-next cursor-pointer" onClick={ () => processAction('playlist_next') } />
</span>
<div className="mb-1">{LocalizeText('widget.furni.video_viewer.playlists')}</div>
<div className="mb-1">{ LocalizeText('widget.furni.video_viewer.playlists') }</div>
<Grid columnCount={ 1 } className="playlist-grid">
{playlists && playlists.map((entry, index) =>
{ playlists && playlists.map((entry, index) =>
{
return (
<LayoutGridItem key={ index } onClick={ () => processAction(entry.video) } itemActive={ entry.video === selectedItem }>
<b>{entry.title}</b> - {entry.description}
<b>{ entry.title }</b> - { entry.description }
</LayoutGridItem>
)
})}
}) }
</Grid>
</div>
</div>

View File

@ -369,7 +369,7 @@ export const InfoStandWidgetFurniView: FC<InfoStandWidgetFurniViewProps> = props
{ canUse &&
<Button variant="dark" onClick={ event => processButtonAction('use') }>
{ LocalizeText('infostand.button.use') }
</Button>}
</Button> }
{ ((furniKeys.length > 0 && furniValues.length > 0) && (furniKeys.length === furniValues.length)) &&
<Button variant="dark" onClick={ () => processButtonAction('save_branding_configuration') }>
{ LocalizeText('save') }

View File

@ -7,6 +7,7 @@ export * from './friends';
export * from './inventory';
export * from './messages';
export * from './navigator';
export * from './purse';
export * from './session';
export * from './UseMountEffect';
export * from './useSharedVisibility';

1
src/hooks/purse/index.ts Normal file
View File

@ -0,0 +1 @@
export * from './usePurse';

134
src/hooks/purse/usePurse.ts Normal file
View File

@ -0,0 +1,134 @@
import { ActivityPointNotificationMessageEvent, UserCreditsEvent, UserCurrencyComposer, UserCurrencyEvent, UserSubscriptionComposer, UserSubscriptionEvent, UserSubscriptionParser } from '@nitrots/nitro-renderer';
import { useCallback, useEffect, useMemo, useState } from 'react';
import { useBetween } from 'use-between';
import { CloneObject, ClubStatus, GetConfiguration, IPurse, PlaySound, Purse, SendMessageComposer, SoundNames } from '../../api';
import { UseMessageEventHook } from '../messages';
const usePurseState = () =>
{
const [ purse, setPurse ] = useState<IPurse>(new Purse());
const hcDisabled = useMemo(() => GetConfiguration<boolean>('hc.disabled', false), []);
const clubStatus = useMemo(() =>
{
if(hcDisabled || (purse.clubDays > 0)) return ClubStatus.ACTIVE;
if((purse.pastVipDays > 0) || (purse.pastVipDays > 0)) return ClubStatus.EXPIRED;
return ClubStatus.NONE;
}, [ purse, hcDisabled ]);
const getCurrencyAmount = (type: number) =>
{
if(type === -1) return purse.credits;
for(const [ key, value ] of purse.activityPoints.entries())
{
if(key !== type) continue;
return value;
}
return 0;
}
const onUserCreditsEvent = useCallback((event: UserCreditsEvent) =>
{
const parser = event.getParser();
setPurse(prevValue =>
{
const newValue = CloneObject(prevValue);
newValue.credits = parseFloat(parser.credits);
if(prevValue.credits !== newValue.credits) PlaySound(SoundNames.CREDITS);
return newValue;
});
}, []);
UseMessageEventHook(UserCreditsEvent, onUserCreditsEvent);
const onUserCurrencyEvent = useCallback((event: UserCurrencyEvent) =>
{
const parser = event.getParser();
setPurse(prevValue =>
{
const newValue = CloneObject(prevValue);
newValue.activityPoints = parser.currencies;
return newValue;
});
}, []);
UseMessageEventHook(UserCurrencyEvent, onUserCurrencyEvent);
const onActivityPointNotificationMessageEvent = useCallback((event: ActivityPointNotificationMessageEvent) =>
{
const parser = event.getParser();
setPurse(prevValue =>
{
const newValue = CloneObject(prevValue);
newValue.activityPoints = new Map(newValue.activityPoints);
newValue.activityPoints.set(parser.type, parser.amount);
if(parser.type === 0) PlaySound(SoundNames.DUCKETS)
return newValue;
});
}, []);
UseMessageEventHook(ActivityPointNotificationMessageEvent, onActivityPointNotificationMessageEvent);
const onUserSubscriptionEvent = useCallback((event: UserSubscriptionEvent) =>
{
const parser = event.getParser();
const productName = parser.productName;
if((productName !== 'club_habbo') && (productName !== 'habbo_club')) return;
setPurse(prevValue =>
{
const newValue = CloneObject(prevValue);
newValue.clubDays = Math.max(0, parser.daysToPeriodEnd);
newValue.clubPeriods = Math.max(0, parser.periodsSubscribedAhead);
newValue.isVip = parser.isVip;
newValue.pastClubDays = parser.pastClubDays;
newValue.pastVipDays = parser.pastVipDays;
newValue.isExpiring = ((parser.responseType === UserSubscriptionParser.RESPONSE_TYPE_DISCOUNT_AVAILABLE) ? true : false);
newValue.minutesUntilExpiration = parser.minutesUntilExpiration;
newValue.minutesSinceLastModified = parser.minutesSinceLastModified;
return newValue;
});
}, []);
UseMessageEventHook(UserSubscriptionEvent, onUserSubscriptionEvent);
useEffect(() =>
{
if(hcDisabled) return;
SendMessageComposer(new UserSubscriptionComposer('habbo_club'));
const interval = setInterval(() => SendMessageComposer(new UserSubscriptionComposer('habbo_club')), 50000);
return () => clearInterval(interval);
}, [ hcDisabled ]);
useEffect(() =>
{
SendMessageComposer(new UserCurrencyComposer());
}, []);
return { purse, hcDisabled, clubStatus, getCurrencyAmount };
}
export const usePurse = () => useBetween(usePurseState);