diff --git a/src/api/catalog/BuilderFurniPlaceableStatus.ts b/src/api/catalog/BuilderFurniPlaceableStatus.ts index 40eb6f6..14a6607 100644 --- a/src/api/catalog/BuilderFurniPlaceableStatus.ts +++ b/src/api/catalog/BuilderFurniPlaceableStatus.ts @@ -7,4 +7,5 @@ export class BuilderFurniPlaceableStatus public static NOT_ROOM_OWNER: number = 4; public static GUILD_ROOM: number = 5; public static VISITORS_IN_ROOM: number = 6; + public static NOT_GROUP_ADMIN: number = 7; } diff --git a/src/api/catalog/ICatalogOptions.ts b/src/api/catalog/ICatalogOptions.ts index 2035694..e8676e5 100644 --- a/src/api/catalog/ICatalogOptions.ts +++ b/src/api/catalog/ICatalogOptions.ts @@ -7,6 +7,7 @@ export interface ICatalogOptions groups?: HabboGroupEntryData[]; petPalettes?: CatalogPetPalette[]; clubOffers?: ClubOfferData[]; + clubOffersByWindowId?: Record; clubGifts?: ClubGiftInfoParser; giftConfiguration?: GiftWrappingConfiguration; marketplaceConfiguration?: MarketplaceConfigurationMessageParser; diff --git a/src/api/utils/FriendlyTime.ts b/src/api/utils/FriendlyTime.ts index 7acb39c..315758e 100644 --- a/src/api/utils/FriendlyTime.ts +++ b/src/api/utils/FriendlyTime.ts @@ -42,6 +42,8 @@ export class FriendlyTime public static getLocalization(key: string, amount: number): string { - return LocalizeText(key, [ 'amount' ], [ amount.toString() ]); + const amountValue = amount.toString(); + + return LocalizeText(key, [ 'amount', 'AMOUNT' ], [ amountValue, amountValue ]); } } diff --git a/src/common/Base.tsx b/src/common/Base.tsx index 1e18c0c..c8ed509 100644 --- a/src/common/Base.tsx +++ b/src/common/Base.tsx @@ -24,6 +24,8 @@ export interface BaseProps extends DetailedHTMLProps> = props => { const { ref = null, innerRef = null, display = null, fit = false, fitV = false, grow = false, shrink = false, fullWidth = false, fullHeight = false, overflow = null, position = null, float = null, pointer = false, visible = null, textColor = null, classNames = [], className = '', style = {}, children = null, ...rest } = props; + const safeClassNames = Array.isArray(classNames) ? classNames : []; + const safeClassName = (typeof className === 'string') ? className : ''; const getClassNames = useMemo(() => { @@ -53,19 +55,19 @@ export const Base: FC> = props => if(textColor) newClassNames.push('text-' + textColor); - if(classNames.length) newClassNames.push(...classNames); + if(safeClassNames.length) newClassNames.push(...safeClassNames); return newClassNames; - }, [ display, fit, fitV, grow, shrink, fullWidth, fullHeight, overflow, position, float, pointer, visible, textColor, classNames ]); + }, [ display, fit, fitV, grow, shrink, fullWidth, fullHeight, overflow, position, float, pointer, visible, textColor, safeClassNames ]); const getClassName = useMemo(() => { let newClassName = getClassNames.join(' '); - if(className.length) newClassName += (' ' + className); + if(safeClassName.length) newClassName += (' ' + safeClassName); return newClassName.trim(); - }, [ getClassNames, className ]); + }, [ getClassNames, safeClassName ]); const getStyle = useMemo(() => { diff --git a/src/common/card/NitroCardHeaderView.tsx b/src/common/card/NitroCardHeaderView.tsx index ab993f6..3acaf3e 100644 --- a/src/common/card/NitroCardHeaderView.tsx +++ b/src/common/card/NitroCardHeaderView.tsx @@ -15,9 +15,7 @@ interface NitroCardHeaderViewProps extends ColumnProps export const NitroCardHeaderView: FC = props => { - const { headerText = null, isGalleryPhoto = false, noCloseButton = false, isInfoToHabboPages = false, onReportPhoto = null, onClickInfoHabboPages = null, onCloseClick = null, justifyContent = 'center', alignItems = 'center', classNames = [], children = null, ...rest } = props; - - + const { headerText = null, isGalleryPhoto = false, noCloseButton = false, isInfoToHabboPages = false, onReportPhoto = null, onClickInfoHabboPages = null, onCloseClick = null, justifyContent = 'center', alignItems = 'center', classNames = [], className = '', children = null, ...rest } = props; const onMouseDown = (event: MouseEvent) => { @@ -26,7 +24,11 @@ export const NitroCardHeaderView: FC = props => }; return ( - + { headerText } { isGalleryPhoto && diff --git a/src/components/catalog/CatalogAdminContext.tsx b/src/components/catalog/CatalogAdminContext.tsx index 5a5fe18..b057ae0 100644 --- a/src/components/catalog/CatalogAdminContext.tsx +++ b/src/components/catalog/CatalogAdminContext.tsx @@ -1,13 +1,14 @@ import { CatalogAdminCreateOfferComposer, CatalogAdminCreatePageComposer, CatalogAdminDeleteOfferComposer, CatalogAdminDeletePageComposer, CatalogAdminMoveOfferComposer, CatalogAdminMovePageComposer, CatalogAdminPublishComposer, CatalogAdminResultEvent, CatalogAdminSaveOfferComposer, CatalogAdminSavePageComposer } from '@nitrots/nitro-renderer'; import { createContext, FC, ReactNode, useCallback, useContext, useEffect, useRef, useState } from 'react'; import { ICatalogNode, IPurchasableOffer, NotificationAlertType, SendMessageComposer } from '../../api'; -import { useMessageEvent, useNotification } from '../../hooks'; +import { useCatalog, useMessageEvent, useNotification } from '../../hooks'; export interface IPageEditData { pageId?: number; caption: string; parentId: number; + catalogMode: string; pageLayout: string; enabled: string; visible: string; @@ -75,6 +76,7 @@ export const useCatalogAdmin = () => useContext(CatalogAdminContext); export const CatalogAdminProvider: FC<{ children: ReactNode }> = ({ children }) => { + const { currentType } = useCatalog(); const [ adminMode, setAdminMode ] = useState(false); const [ editingOffer, setEditingOffer ] = useState(null); const [ editingPageData, setEditingPageData ] = useState(false); @@ -175,9 +177,9 @@ export const CatalogAdminProvider: FC<{ children: ReactNode }> = ({ children }) data.pageId || 0, data.caption, data.caption, data.pageLayout, 0, data.minRank, data.visible === '1', data.enabled === '1', data.orderNum, data.parentId, - data.pageHeadline || '', data.pageTeaser || '', data.pageTextDetails || '' + data.pageHeadline || '', data.pageTeaser || '', data.pageTextDetails || '', currentType, data.catalogMode )); - }, []); + }, [ currentType ]); const createPage = useCallback((data: IPageEditData) => { @@ -187,17 +189,17 @@ export const CatalogAdminProvider: FC<{ children: ReactNode }> = ({ children }) SendMessageComposer(new CatalogAdminCreatePageComposer( data.caption, data.caption, data.pageLayout, 0, data.minRank, data.visible === '1', data.enabled === '1', - data.orderNum, data.parentId + data.orderNum, data.parentId, currentType, data.catalogMode )); - }, []); + }, [ currentType ]); const deletePage = useCallback((pageId: number) => { setLoading(true); setLastError(null); pendingActionRef.current = 'deletePage'; - SendMessageComposer(new CatalogAdminDeletePageComposer(pageId)); - }, []); + SendMessageComposer(new CatalogAdminDeletePageComposer(pageId, currentType)); + }, [ currentType ]); const saveOffer = useCallback((data: IOfferEditData) => { @@ -208,9 +210,9 @@ export const CatalogAdminProvider: FC<{ children: ReactNode }> = ({ children }) data.offerId || 0, data.pageId, parseInt(data.itemIds) || 0, data.catalogName, data.costCredits, data.costPoints, data.pointsType, data.amount, data.clubOnly === '1' ? 1 : 0, data.extradata, - data.haveOffer === '1', data.offerId_group, data.limitedStack, data.orderNumber + data.haveOffer === '1', data.offerId_group, data.limitedStack, data.orderNumber, currentType )); - }, []); + }, [ currentType ]); const createOffer = useCallback((data: IOfferEditData) => { @@ -221,17 +223,17 @@ export const CatalogAdminProvider: FC<{ children: ReactNode }> = ({ children }) data.pageId, parseInt(data.itemIds) || 0, data.catalogName, data.costCredits, data.costPoints, data.pointsType, data.amount, data.clubOnly === '1' ? 1 : 0, data.extradata, - data.haveOffer === '1', data.offerId_group, data.limitedStack, data.orderNumber + data.haveOffer === '1', data.offerId_group, data.limitedStack, data.orderNumber, currentType )); - }, []); + }, [ currentType ]); const deleteOffer = useCallback((offerId: number) => { setLoading(true); setLastError(null); pendingActionRef.current = 'deleteOffer'; - SendMessageComposer(new CatalogAdminDeleteOfferComposer(offerId)); - }, []); + SendMessageComposer(new CatalogAdminDeleteOfferComposer(offerId, currentType)); + }, [ currentType ]); const reorderOffers = useCallback((orders: { id: number; orderNumber: number }[]) => { @@ -241,33 +243,33 @@ export const CatalogAdminProvider: FC<{ children: ReactNode }> = ({ children }) for(const order of orders) { - SendMessageComposer(new CatalogAdminMoveOfferComposer(order.id, order.orderNumber)); + SendMessageComposer(new CatalogAdminMoveOfferComposer(order.id, order.orderNumber, currentType)); } - }, []); + }, [ currentType ]); const reorderPage = useCallback((pageId: number, newParentId: number, newIndex: number) => { setLoading(true); setLastError(null); pendingActionRef.current = 'movePage'; - SendMessageComposer(new CatalogAdminMovePageComposer(pageId, newParentId, newIndex)); - }, []); + SendMessageComposer(new CatalogAdminMovePageComposer(pageId, newParentId, newIndex, currentType)); + }, [ currentType ]); const togglePageEnabled = useCallback((pageId: number) => { setLoading(true); setLastError(null); pendingActionRef.current = 'toggleEnabled'; - SendMessageComposer(new CatalogAdminMovePageComposer(pageId, -1, -1)); - }, []); + SendMessageComposer(new CatalogAdminMovePageComposer(pageId, -1, -1, currentType)); + }, [ currentType ]); const togglePageVisible = useCallback((pageId: number) => { setLoading(true); setLastError(null); pendingActionRef.current = 'toggleVisible'; - SendMessageComposer(new CatalogAdminMovePageComposer(pageId, -2, -1)); - }, []); + SendMessageComposer(new CatalogAdminMovePageComposer(pageId, -2, -1, currentType)); + }, [ currentType ]); const publishCatalog = useCallback(() => { diff --git a/src/components/catalog/CatalogClassicView.tsx b/src/components/catalog/CatalogClassicView.tsx index e86b384..2daeb3d 100644 --- a/src/components/catalog/CatalogClassicView.tsx +++ b/src/components/catalog/CatalogClassicView.tsx @@ -1,12 +1,13 @@ import { AddLinkEventTracker, GetSessionDataManager, ILinkEventTracker, RemoveLinkEventTracker } from '@nitrots/nitro-renderer'; import { FC, useEffect } from 'react'; import { FaCog, FaEdit, FaEye, FaEyeSlash, FaPlus, FaTrash } from 'react-icons/fa'; -import { GetConfigurationValue, LocalizeText } from '../../api'; +import { CatalogType, GetConfigurationValue, LocalizeText } from '../../api'; import { Column, Grid, NitroCardContentView, NitroCardHeaderView, NitroCardTabsItemView, NitroCardTabsView, NitroCardView } from '../../common'; import { useCatalog } from '../../hooks'; import { CatalogAdminProvider, useCatalogAdmin } from './CatalogAdminContext'; import { CatalogAdminOfferEditView } from './views/admin/CatalogAdminOfferEditView'; import { CatalogAdminPageEditView } from './views/admin/CatalogAdminPageEditView'; +import { CatalogBuildersClubStatusView } from './views/catalog-header/CatalogBuildersClubStatusView'; import { CatalogIconView } from './views/catalog-icon/CatalogIconView'; import { CatalogGiftView } from './views/gift/CatalogGiftView'; import { CatalogNavigationView } from './views/navigation/CatalogNavigationView'; @@ -15,7 +16,7 @@ import { MarketplacePostOfferView } from './views/page/layout/marketplace/Market const CatalogClassicViewInner: FC<{}> = () => { - const { isVisible = false, setIsVisible = null, rootNode = null, currentPage = null, navigationHidden = false, setNavigationHidden = null, activeNodes = [], searchResult = null, setSearchResult = null, openPageByName = null, openPageByOfferId = null, activateNode = null } = useCatalog(); + const { isVisible = false, setIsVisible = null, rootNode = null, currentPage = null, navigationHidden = false, setNavigationHidden = null, activeNodes = [], searchResult = null, setSearchResult = null, openPageByName = null, openPageByOfferId = null, activateNode = null, openCatalogByType = null, toggleCatalogByType = null, currentType = CatalogType.NORMAL } = useCatalog(); const catalogAdmin = useCatalogAdmin(); const adminMode = catalogAdmin?.adminMode ?? false; const setAdminMode = catalogAdmin?.setAdminMode ?? (() => {}); @@ -24,9 +25,26 @@ const CatalogClassicViewInner: FC<{}> = () => const loading = catalogAdmin?.loading ?? false; const isMod = GetSessionDataManager().isModerator; + const buildersClubHeaderStyle = (currentType === CatalogType.BUILDER) + ? { borderColor: '#d79d2e', borderBottomColor: '#000', background: 'linear-gradient(180deg, #d89f2d 0%, #c68515 100%)' } + : undefined; useEffect(() => { + const getCatalogTypeFromLink = (type?: string) => + { + switch((type || '').toLowerCase()) + { + case 'bc': + case 'builder': + case 'buildersclub': + case 'builders_club': + return CatalogType.BUILDER; + default: + return CatalogType.NORMAL; + } + }; + const linkTracker: ILinkEventTracker = { linkReceived: (url: string) => { @@ -37,12 +55,26 @@ const CatalogClassicViewInner: FC<{}> = () => switch(parts[1]) { case 'show': + if(parts.length > 2) + { + openCatalogByType(getCatalogTypeFromLink(parts[2])); + + return; + } + setIsVisible(true); return; case 'hide': setIsVisible(false); return; case 'toggle': + if(parts.length > 2) + { + toggleCatalogByType(getCatalogTypeFromLink(parts[2])); + + return; + } + setIsVisible(prevValue => !prevValue); return; case 'open': @@ -76,13 +108,13 @@ const CatalogClassicViewInner: FC<{}> = () => AddLinkEventTracker(linkTracker); return () => RemoveLinkEventTracker(linkTracker); - }, [ setIsVisible, openPageByOfferId, openPageByName ]); + }, [ setIsVisible, openPageByOfferId, openPageByName, openCatalogByType, toggleCatalogByType ]); return ( <> { isVisible && - setIsVisible(false) } /> + setIsVisible(false) } style={ buildersClubHeaderStyle } /> { /* Admin banner */ } { adminMode &&
@@ -134,13 +166,14 @@ const CatalogClassicViewInner: FC<{}> = () => } + { /* Admin: add new root category */ } { adminMode && rootNode &&
} -
+ +
{ /* === LEFT SIDEBAR === */ }
@@ -129,7 +162,7 @@ const CatalogModernViewInner: FC<{}> = () =>
+
+ + { currentType === CatalogType.BUILDER + ?
Builders Club
+ : } +