mirror of
https://github.com/duckietm/Nitro-V3.git
synced 2026-06-20 07:26:19 +00:00
Take #2 Desktop cacta 100%
This commit is contained in:
@@ -10,6 +10,7 @@ export const CatalogAdminOfferEditView: FC<{}> = () =>
|
||||
const { currentPage = null } = useCatalogData();
|
||||
const catalogAdmin = useCatalogAdmin();
|
||||
const editingOffer = catalogAdmin?.editingOffer ?? null;
|
||||
const editingOfferDetails = catalogAdmin?.editingOfferDetails ?? null;
|
||||
const setEditingOffer = catalogAdmin?.setEditingOffer;
|
||||
const saveOffer = catalogAdmin?.saveOffer;
|
||||
const deleteOffer = catalogAdmin?.deleteOffer;
|
||||
@@ -62,12 +63,21 @@ export const CatalogAdminOfferEditView: FC<{}> = () =>
|
||||
setClubOnly(editingOffer.clubLevel > 0 ? '1' : '0');
|
||||
setExtradata(editingOffer.product?.extraParam || '');
|
||||
setHaveOffer(editingOffer.haveOffer ? '1' : '0');
|
||||
setOfferIdGroup(editingOffer.offerId || -1);
|
||||
setOfferIdGroup(0);
|
||||
setLimitedStack(0);
|
||||
setOrderNumber(0);
|
||||
}
|
||||
}, [ editingOffer ]);
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
if(!editingOfferDetails) return;
|
||||
|
||||
setOfferIdGroup(editingOfferDetails.offerIdGroup);
|
||||
setLimitedStack(editingOfferDetails.limitedStack);
|
||||
setOrderNumber(editingOfferDetails.orderNumber);
|
||||
}, [ editingOfferDetails ]);
|
||||
|
||||
if(!editingOffer) return null;
|
||||
|
||||
const handleSave = async () =>
|
||||
|
||||
@@ -28,6 +28,8 @@ export const CatalogAdminPageEditView: FC<{}> = () =>
|
||||
const editingPageData = catalogAdmin?.editingPageData ?? false;
|
||||
const editingRootPage = catalogAdmin?.editingRootPage ?? false;
|
||||
const editingPageNode = catalogAdmin?.editingPageNode ?? null;
|
||||
const editingPageDetails = catalogAdmin?.editingPageDetails ?? null;
|
||||
const requestPageDetails = catalogAdmin?.requestPageDetails;
|
||||
const loading = catalogAdmin?.loading ?? false;
|
||||
|
||||
const [ caption, setCaption ] = useState('');
|
||||
@@ -67,21 +69,22 @@ export const CatalogAdminPageEditView: FC<{}> = () =>
|
||||
{
|
||||
if(!editingPageData || !targetNode) return;
|
||||
|
||||
// The server appends " (pageId)" to the caption for mods/admins (see
|
||||
// CatalogPagesListComposer). Strip that exact suffix before seeding the
|
||||
// edit field, otherwise saving folds the id back into the stored
|
||||
// caption and it multiplies on every edit ("Wired (1114) (1114) ...").
|
||||
const rawCaption = (targetNode.localization || '').replace(new RegExp(`\\s*\\(${ targetNode.pageId }\\)\\s*$`), '');
|
||||
// Don't read the decorated caption out of the catalog index -
|
||||
// the gameserver appends " (id)" when ACC_CATALOG_IDS is on and
|
||||
// we don't want that round-tripping back into the DB. Wait for
|
||||
// the admin page-details event to land instead; it carries the
|
||||
// raw caption / caption_save / min_rank / order_num / enabled.
|
||||
setCaption('');
|
||||
setCaptionSave('');
|
||||
setMinRank(1);
|
||||
setOrderNum(0);
|
||||
setEnabled('1');
|
||||
|
||||
setCaption(rawCaption);
|
||||
setCaptionSave(targetNode.pageName || rawCaption);
|
||||
setCatalogMode(currentType === CatalogType.BUILDER ? 'BUILDER' : 'NORMAL');
|
||||
setPageLayout(currentPage?.layoutCode || 'default_3x3');
|
||||
setIconImage(targetNode.iconId ?? 0);
|
||||
setVisible(targetNode.isVisible ? '1' : '0');
|
||||
setEnabled('1');
|
||||
setMinRank(1);
|
||||
setOrderNum(0);
|
||||
|
||||
const matchesLoadedPage = currentPage && targetPageId === currentPage.pageId;
|
||||
const existingText1 = matchesLoadedPage && currentPage.localization
|
||||
? currentPage.localization.getText(0)
|
||||
@@ -94,7 +97,22 @@ export const CatalogAdminPageEditView: FC<{}> = () =>
|
||||
setParentId(typeof wireParentId === 'number' && wireParentId !== -1
|
||||
? wireParentId
|
||||
: (targetNode.parent ? targetNode.parent.pageId : -1));
|
||||
}, [ editingPageData, targetNode, currentPage, currentType ]);
|
||||
|
||||
if(targetPageId != null && targetPageId >= 0) requestPageDetails?.(targetPageId);
|
||||
}, [ editingPageData, targetNode, currentPage, currentType, targetPageId, requestPageDetails ]);
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
if(!editingPageDetails) return;
|
||||
if(targetPageId != null && editingPageDetails.pageId !== targetPageId) return;
|
||||
|
||||
setCaption(editingPageDetails.caption);
|
||||
setCaptionSave(editingPageDetails.captionSave);
|
||||
setMinRank(editingPageDetails.minRank);
|
||||
setOrderNum(editingPageDetails.orderNum);
|
||||
setVisible(editingPageDetails.visible ? '1' : '0');
|
||||
setEnabled(editingPageDetails.enabled ? '1' : '0');
|
||||
}, [ editingPageDetails, targetPageId ]);
|
||||
|
||||
if(!editingPageData || !targetNode) return null;
|
||||
|
||||
@@ -168,7 +186,7 @@ export const CatalogAdminPageEditView: FC<{}> = () =>
|
||||
const handleDelete = async () =>
|
||||
{
|
||||
if(!catalogAdmin?.deletePage || isRoot) return;
|
||||
if(!confirm(LocalizeText('catalog.admin.delete.page.confirm', [ 'name' ], [ targetNode.localization ]))) return;
|
||||
if(!confirm(LocalizeText('catalog.admin.delete.page.confirm', [ 'name' ], [ editingPageDetails?.caption ?? '' ]))) return;
|
||||
|
||||
catalogAdmin.deletePage(targetPageId);
|
||||
|
||||
@@ -179,7 +197,7 @@ export const CatalogAdminPageEditView: FC<{}> = () =>
|
||||
<div className="bg-white rounded border-2 border-card-grid-item-border p-2.5 mb-2">
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<span className="text-[11px] font-bold text-primary uppercase tracking-wide">
|
||||
{ isRoot ? LocalizeText('catalog.admin.edit.root') : `${ LocalizeText('catalog.admin.edit') } ${ targetNode.localization }` }
|
||||
{ isRoot ? LocalizeText('catalog.admin.edit.root') : `${ LocalizeText('catalog.admin.edit') } ${ editingPageDetails?.caption ?? '' }` }
|
||||
</span>
|
||||
<FaTimes className="text-muted cursor-pointer hover:text-danger text-[10px]" onClick={ closeForm } />
|
||||
</div>
|
||||
|
||||
@@ -23,6 +23,10 @@ export const CatalogNavigationItemView: FC<CatalogNavigationItemViewProps> = pro
|
||||
const isFav = node ? isFavoritePage(node.pageId) : false;
|
||||
const [ isDragOver, setIsDragOver ] = useState(false);
|
||||
const dragRef = useRef<HTMLDivElement>(null);
|
||||
// Strip SWF-style suffixes like "(BC)" or "(Hot)" but keep the
|
||||
// pageId hint the gameserver appends when the viewer has
|
||||
// ACC_CATALOG_IDS - that's a pure-numeric "(6)" trailer.
|
||||
const swfLabel = (node?.localization || '').replace(/\s*\(\D[^)]*\)\s*$/g, '').trim();
|
||||
|
||||
const handleDragStart = useCallback((e: React.DragEvent) =>
|
||||
{
|
||||
@@ -90,7 +94,7 @@ export const CatalogNavigationItemView: FC<CatalogNavigationItemViewProps> = pro
|
||||
<div className="nitro-catalog-classic-navigation-icon">
|
||||
<CatalogIconView icon={ node.iconId } />
|
||||
</div>
|
||||
<span className="nitro-catalog-classic-navigation-label" title={ adminMode ? `Page ID: ${ node.pageId }` : undefined }>{ node.localization }</span>
|
||||
<span className="nitro-catalog-classic-navigation-label" title={ adminMode ? `Page ID: ${ node.pageId }` : undefined }>{ swfLabel }</span>
|
||||
{ adminMode &&
|
||||
<div className="nitro-catalog-classic-navigation-admin flex items-center gap-1 opacity-0 group-hover/nav:opacity-100 transition-opacity">
|
||||
<FaPlus
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { MouseEventType } from '@nitrots/nitro-renderer';
|
||||
import { FC, MouseEvent, useMemo, useState } from 'react';
|
||||
import { FaHeart } from 'react-icons/fa';
|
||||
import { CatalogType, IPurchasableOffer, Offer, ProductTypeEnum } from '../../../../../api';
|
||||
import { CatalogType, GetConfigurationValue, IPurchasableOffer, Offer, ProductTypeEnum } from '../../../../../api';
|
||||
import { LayoutAvatarImageView, LayoutGridItem, LayoutGridItemProps } from '../../../../../common';
|
||||
import { useCatalogActions, useCatalogFavorites, useCatalogUiState, useInventoryFurni } from '../../../../../hooks';
|
||||
|
||||
@@ -30,9 +30,50 @@ export const CatalogGridOfferView: FC<CatalogGridOfferViewProps> = props =>
|
||||
return null;
|
||||
}
|
||||
|
||||
return offer.product?.getIconUrl(offer) ?? null;
|
||||
const product = offer.product;
|
||||
|
||||
if(!product) return null;
|
||||
|
||||
if((product.productType === ProductTypeEnum.FLOOR) || (product.productType === ProductTypeEnum.WALL))
|
||||
{
|
||||
const className = product.furnitureData?.className;
|
||||
|
||||
if(className?.length)
|
||||
{
|
||||
const param = (product.productType === ProductTypeEnum.WALL && product.extraParam?.length) ? `_${ product.extraParam }` : '';
|
||||
const configuredIconUrl = GetConfigurationValue<string>('furni.asset.icon.url', '');
|
||||
|
||||
if(configuredIconUrl?.length)
|
||||
{
|
||||
return configuredIconUrl
|
||||
.replace('%libname%', className)
|
||||
.replace('%param%', param);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return product.getIconUrl(offer) ?? null;
|
||||
}, [ offer ]);
|
||||
|
||||
const prices = useMemo(() =>
|
||||
{
|
||||
if(!offer) return [];
|
||||
|
||||
const values: { amount: number; type: number }[] = [];
|
||||
|
||||
if(offer.priceInCredits > 0) values.push({ amount: offer.priceInCredits, type: -1 });
|
||||
if(offer.priceInActivityPoints > 0) values.push({ amount: offer.priceInActivityPoints, type: offer.activityPointType });
|
||||
|
||||
return values;
|
||||
}, [ offer ]);
|
||||
|
||||
const getCurrencyIconUrl = (type: number) =>
|
||||
{
|
||||
const configuredCurrencyUrl = GetConfigurationValue<string>('currency.asset.icon.url', '');
|
||||
|
||||
return configuredCurrencyUrl.replace('%type%', type.toString());
|
||||
};
|
||||
|
||||
const onMouseEvent = (event: MouseEvent) =>
|
||||
{
|
||||
switch(event.type)
|
||||
@@ -74,9 +115,30 @@ export const CatalogGridOfferView: FC<CatalogGridOfferViewProps> = props =>
|
||||
{ ...rest }
|
||||
>
|
||||
{ iconUrl && !(offer.product.productType === ProductTypeEnum.ROBOT) &&
|
||||
<div className="nitro-catalog-classic-grid-offer-icon" style={ { backgroundImage: `url(${ iconUrl })` } } /> }
|
||||
<img
|
||||
className="nitro-catalog-classic-grid-offer-icon"
|
||||
src={ iconUrl }
|
||||
draggable={ false }
|
||||
onError={ event =>
|
||||
{
|
||||
const fallbackIconUrl = product.getIconUrl(offer);
|
||||
|
||||
if(fallbackIconUrl && (event.currentTarget.src !== fallbackIconUrl)) event.currentTarget.src = fallbackIconUrl;
|
||||
} } /> }
|
||||
{ (offer.product.productType === ProductTypeEnum.ROBOT) &&
|
||||
<LayoutAvatarImageView direction={ 2 } figure={ offer.product.extraParam } fit /> }
|
||||
{ (prices.length > 0) &&
|
||||
<span className={ `nitro-catalog-classic-grid-price ${ prices.length > 1 ? 'is-multi-price' : 'is-single-price' }` }>
|
||||
{ prices.map((price, index) =>
|
||||
<span key={ `${ price.type }-${ index }` } className="nitro-catalog-classic-grid-price-entry">
|
||||
{ index > 0 && <span className="nitro-catalog-classic-grid-price-plus">+</span> }
|
||||
<span className="nitro-catalog-classic-grid-price-amount">{ price.amount }</span>
|
||||
<img
|
||||
className="nitro-catalog-classic-grid-price-currency"
|
||||
src={ getCurrencyIconUrl(price.type) }
|
||||
draggable={ false } />
|
||||
</span>) }
|
||||
</span> }
|
||||
<div
|
||||
className={ `absolute top-0 right-0 z-10 p-0.5 cursor-pointer transition-opacity duration-100 ${ isFav ? 'opacity-100' : 'opacity-0 group-hover/tile:opacity-100' }` }
|
||||
onClick={ e =>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { FC } from 'react';
|
||||
import { FaEdit, FaPlus, FaPowerOff, FaSyncAlt } from 'react-icons/fa';
|
||||
import { FaEdit, FaExchangeAlt, FaPlus, FaSyncAlt } from 'react-icons/fa';
|
||||
import { GetConfigurationValue, LocalizeText, ProductTypeEnum, SanitizeHtml } from '../../../../../api';
|
||||
import { Text } from '../../../../../common';
|
||||
import { useCatalogData } from '../../../../../hooks';
|
||||
@@ -20,6 +20,7 @@ export const CatalogLayoutDefaultView: FC<CatalogLayoutProps> = props =>
|
||||
const { currentOffer = null, currentPage = null, roomPreviewer = null } = useCatalogData();
|
||||
const catalogAdmin = useCatalogAdmin();
|
||||
const adminMode = catalogAdmin?.adminMode ?? false;
|
||||
const offerName = currentOffer?.localizationName?.replace(/\s*\([^)]*\)\s*$/g, '');
|
||||
|
||||
return (
|
||||
<div className="nitro-catalog-classic-default-layout flex flex-col h-full gap-2">
|
||||
@@ -40,63 +41,87 @@ export const CatalogLayoutDefaultView: FC<CatalogLayoutProps> = props =>
|
||||
>
|
||||
<FaPlus className="text-[10px]" /> { LocalizeText('catalog.admin.offer.new') }
|
||||
</button>
|
||||
{ currentOffer &&
|
||||
<button
|
||||
className="flex items-center gap-1 text-[10px] text-primary hover:text-dark transition-colors cursor-pointer"
|
||||
title={ `${ LocalizeText('catalog.admin.offer.edit') } - Class ${ currentOffer.product.productClassId } / Offer ${ currentOffer.offerId }` }
|
||||
onClick={ () => catalogAdmin.setEditingOffer(currentOffer) }
|
||||
>
|
||||
<FaEdit className="text-[10px]" /> { LocalizeText('catalog.admin.offer.edit') }
|
||||
<span className="font-mono text-[9px] text-dark font-semibold">#{ currentOffer.product.productClassId }/{ currentOffer.offerId }</span>
|
||||
</button> }
|
||||
</div> }
|
||||
{ currentOffer &&
|
||||
<div className="nitro-catalog-classic-offer-panel flex gap-0 shrink-0">
|
||||
<div className="nitro-catalog-classic-offer-preview relative flex items-center justify-center">
|
||||
{ (currentOffer.product.productType !== ProductTypeEnum.BADGE) &&
|
||||
<>
|
||||
<button className="nitro-catalog-classic-preview-btn nitro-catalog-classic-preview-rotate" onClick={ () => roomPreviewer?.changeRoomObjectDirection() }>
|
||||
<FaSyncAlt /> Rotate
|
||||
</button>
|
||||
<button className="nitro-catalog-classic-preview-btn nitro-catalog-classic-preview-state" onClick={ () => roomPreviewer?.changeRoomObjectState() }>
|
||||
<FaPowerOff /> Toggle State
|
||||
</button>
|
||||
<CatalogViewProductWidgetView />
|
||||
<CatalogAddOnBadgeWidgetView className="bg-muted rounded bottom-1 right-1 absolute" />
|
||||
</> }
|
||||
{ (currentOffer.product.productType === ProductTypeEnum.BADGE) &&
|
||||
<CatalogAddOnBadgeWidgetView className="scale-2" /> }
|
||||
</div>
|
||||
<div className="nitro-catalog-classic-offer-info flex flex-col flex-1 min-w-0 gap-2">
|
||||
<div>
|
||||
<div className="flex items-start justify-between gap-2">
|
||||
<Text className="text-[13px]! font-bold text-dark leading-tight">{ currentOffer.localizationName }</Text>
|
||||
<div className="nitro-catalog-classic-product-view">
|
||||
{ currentOffer &&
|
||||
<div className="nitro-catalog-classic-offer-panel flex gap-0">
|
||||
<div className="nitro-catalog-classic-offer-preview relative flex items-center justify-center">
|
||||
<Text className="nitro-catalog-classic-preview-title">{ offerName }</Text>
|
||||
{ (currentOffer.product.productType !== ProductTypeEnum.BADGE) &&
|
||||
<>
|
||||
<button className="nitro-catalog-classic-preview-btn nitro-catalog-classic-preview-rotate" onClick={ () => roomPreviewer?.changeRoomObjectDirection() }>
|
||||
<FaSyncAlt />
|
||||
</button>
|
||||
<button className="nitro-catalog-classic-preview-btn nitro-catalog-classic-preview-state" onClick={ () => roomPreviewer?.changeRoomObjectState() }>
|
||||
<FaExchangeAlt />
|
||||
</button>
|
||||
<CatalogViewProductWidgetView />
|
||||
<CatalogAddOnBadgeWidgetView className="bg-muted rounded bottom-1 right-1 absolute" />
|
||||
</> }
|
||||
{ (currentOffer.product.productType === ProductTypeEnum.BADGE) &&
|
||||
<CatalogAddOnBadgeWidgetView className="scale-2" /> }
|
||||
</div>
|
||||
<div className="nitro-catalog-classic-offer-info flex flex-col flex-1 min-w-0 gap-2">
|
||||
<div>
|
||||
<div className="flex items-start justify-between gap-2">
|
||||
<Text className="text-[13px]! font-bold text-dark leading-tight">{ offerName }</Text>
|
||||
{ adminMode &&
|
||||
<FaEdit
|
||||
className="text-primary text-[11px] cursor-pointer hover:text-dark transition-colors shrink-0 mt-0.5"
|
||||
title={ LocalizeText('catalog.admin.offer.edit') }
|
||||
onClick={ () => catalogAdmin.setEditingOffer(currentOffer) }
|
||||
/> }
|
||||
</div>
|
||||
{ adminMode &&
|
||||
<FaEdit
|
||||
className="text-primary text-[11px] cursor-pointer hover:text-dark transition-colors shrink-0 mt-0.5"
|
||||
title={ LocalizeText('catalog.admin.offer.edit') }
|
||||
onClick={ () => catalogAdmin.setEditingOffer(currentOffer) }
|
||||
/> }
|
||||
<div className="flex items-center gap-1 mt-1 flex-wrap">
|
||||
<span className="text-[8px] font-mono text-white bg-gray-600 px-1 py-px rounded">ID: { currentOffer.product.productClassId }</span>
|
||||
<span className="text-[8px] font-mono text-white bg-primary px-1 py-px rounded">Offer: { currentOffer.offerId }</span>
|
||||
<span className="text-[8px] font-mono text-white bg-secondary px-1 py-px rounded">{ currentOffer.product.productType.toUpperCase() }</span>
|
||||
</div> }
|
||||
<CatalogLimitedItemWidgetView />
|
||||
</div>
|
||||
{ adminMode &&
|
||||
<div className="flex items-center gap-1 mt-1 flex-wrap">
|
||||
<span className="text-[8px] font-mono text-white bg-gray-600 px-1 py-px rounded">ID: { currentOffer.product.productClassId }</span>
|
||||
<span className="text-[8px] font-mono text-white bg-primary px-1 py-px rounded">Offer: { currentOffer.offerId }</span>
|
||||
<span className="text-[8px] font-mono text-white bg-secondary px-1 py-px rounded">{ currentOffer.product.productType.toUpperCase() }</span>
|
||||
</div> }
|
||||
<CatalogLimitedItemWidgetView />
|
||||
</div>
|
||||
<CatalogTotalPriceWidget />
|
||||
<CatalogSpinnerWidgetView />
|
||||
<div className="nitro-catalog-classic-offer-actions flex gap-1.5">
|
||||
<CatalogPurchaseWidgetView />
|
||||
</div>
|
||||
</div>
|
||||
</div> }
|
||||
</div> }
|
||||
|
||||
{ !currentOffer &&
|
||||
<div className="nitro-catalog-classic-welcome flex items-center gap-3 shrink-0">
|
||||
{ !!page.localization.getImage(1) &&
|
||||
<img className="w-[70px] h-[70px] object-contain rounded shrink-0" src={ page.localization.getImage(1) } /> }
|
||||
<Text className="text-[11px]! text-muted" dangerouslySetInnerHTML={ { __html: SanitizeHtml(page.localization.getText(0)) } } />
|
||||
</div> }
|
||||
{ !currentOffer &&
|
||||
<div className="nitro-catalog-classic-welcome flex items-center gap-3">
|
||||
{ !!page.localization.getImage(1) &&
|
||||
<img className="w-[70px] h-[70px] object-contain rounded shrink-0" src={ page.localization.getImage(1) } /> }
|
||||
<Text className="text-[11px]! text-muted" dangerouslySetInnerHTML={ { __html: SanitizeHtml(page.localization.getText(0)) } } />
|
||||
</div> }
|
||||
</div>
|
||||
|
||||
<div className="nitro-catalog-classic-grid-shell flex-1 overflow-auto min-h-0">
|
||||
{ GetConfigurationValue('catalog.headers') &&
|
||||
<CatalogHeaderView imageUrl={ currentPage.localization.getImage(0) } /> }
|
||||
<CatalogItemGridWidgetView className="nitro-catalog-classic-grid" columnCount={ 7 } columnMinHeight={ currentPage.layoutCode === 'bots' ? 65 : 50 } columnMinWidth={ currentPage.layoutCode === 'bots' ? 65 : 50 } />
|
||||
<CatalogItemGridWidgetView className="nitro-catalog-classic-grid" columnCount={ 7 } columnMinHeight={ 70 } columnMinWidth={ 45 } />
|
||||
</div>
|
||||
|
||||
{ currentOffer &&
|
||||
<div className="nitro-catalog-classic-price-row flex items-center justify-between gap-2">
|
||||
<div className="nitro-catalog-classic-spinner-slot">
|
||||
<CatalogSpinnerWidgetView />
|
||||
</div>
|
||||
<div className="nitro-catalog-classic-total-price-slot">
|
||||
<CatalogTotalPriceWidget />
|
||||
</div>
|
||||
</div> }
|
||||
|
||||
{ currentOffer &&
|
||||
<div className="nitro-catalog-classic-purchase-row flex items-start justify-end">
|
||||
<div className="nitro-catalog-classic-offer-actions flex gap-1.5">
|
||||
<CatalogPurchaseWidgetView />
|
||||
</div>
|
||||
</div> }
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -17,14 +17,14 @@ export const CatalogLayouGuildCustomFurniView: FC<CatalogLayoutProps> = props =>
|
||||
|
||||
return (
|
||||
<Grid>
|
||||
<Column overflow="hidden" size={ 7 }>
|
||||
<CatalogItemGridWidgetView />
|
||||
<Column overflow="hidden" size={ 8 }>
|
||||
<CatalogItemGridWidgetView columnMinWidth={ 36 } />
|
||||
</Column>
|
||||
<Column center={ !currentOffer } overflow="hidden" size={ 5 }>
|
||||
<Column center={ !currentOffer } overflow="hidden" size={ 4 }>
|
||||
{ !currentOffer &&
|
||||
<>
|
||||
{ !!page.localization.getImage(1) && <img alt="" src={ page.localization.getImage(1) } /> }
|
||||
<Text center dangerouslySetInnerHTML={ { __html: SanitizeHtml(page.localization.getText(0)) } } />
|
||||
{ !!page.localization.getImage(1) && <img alt="" className="max-w-full object-contain" src={ page.localization.getImage(1) } /> }
|
||||
<Text center bold dangerouslySetInnerHTML={ { __html: SanitizeHtml(page.localization.getText(0)) } } />
|
||||
</> }
|
||||
{ currentOffer &&
|
||||
<>
|
||||
@@ -33,7 +33,7 @@ export const CatalogLayouGuildCustomFurniView: FC<CatalogLayoutProps> = props =>
|
||||
<CatalogGuildBadgeWidgetView className="bottom-1 inset-e-1" position="absolute" />
|
||||
</div>
|
||||
<Column grow gap={ 1 }>
|
||||
<Text truncate>{ currentOffer.localizationName }</Text>
|
||||
<Text bold className="leading-tight">{ currentOffer.localizationName }</Text>
|
||||
<div className="grow!">
|
||||
<CatalogGuildSelectorWidgetView />
|
||||
</div>
|
||||
|
||||
@@ -20,6 +20,7 @@ export const CatalogLayoutRoomAdsView: FC<CatalogLayoutProps> = props =>
|
||||
const { categories } = useNavigatorData();
|
||||
const { setIsVisible = null } = useCatalogUiState();
|
||||
const { promoteInformation, isExtended, setIsExtended } = useRoomPromote();
|
||||
const promoteData = promoteInformation?.data ?? null;
|
||||
|
||||
const { data: availableRooms = [] } = useNitroQuery<RoomAdPurchaseInfoEvent, RoomEntryData[]>({
|
||||
key: [ 'nitro', 'catalog', 'room-ad-purchase-info' ],
|
||||
@@ -31,17 +32,17 @@ export const CatalogLayoutRoomAdsView: FC<CatalogLayoutProps> = props =>
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
if(isExtended)
|
||||
if(isExtended && promoteData)
|
||||
{
|
||||
setRoomId(promoteInformation.data.flatId);
|
||||
setEventName(promoteInformation.data.eventName);
|
||||
setEventDesc(promoteInformation.data.eventDescription);
|
||||
setCategoryId(promoteInformation.data.categoryId);
|
||||
setRoomId(promoteData.flatId);
|
||||
setEventName(promoteData.eventName);
|
||||
setEventDesc(promoteData.eventDescription);
|
||||
setCategoryId(promoteData.categoryId);
|
||||
setExtended(isExtended); // This is for sending to packet
|
||||
setIsExtended(false); // This is from hook useRoomPromotte
|
||||
}
|
||||
|
||||
}, [ isExtended, eventName, eventDesc, categoryId, promoteInformation.data, setIsExtended ]);
|
||||
}, [ isExtended, promoteData, setIsExtended ]);
|
||||
|
||||
const resetData = () =>
|
||||
{
|
||||
|
||||
@@ -28,7 +28,7 @@ export const CatalogLayoutSingleBundleView: FC<CatalogLayoutProps> = props =>
|
||||
<Text center small overflow="auto">{ page.localization.getText(1) }</Text> }
|
||||
<Column grow gap={ 0 } overflow="hidden" position="relative">
|
||||
{ !!page.localization.getImage(1) &&
|
||||
<img alt="" className="grow!" src={ page.localization.getImage(1) } /> }
|
||||
<img alt="" className="grow! min-h-0 w-full h-full object-contain object-center" src={ page.localization.getImage(1) } /> }
|
||||
<CatalogAddOnBadgeWidgetView className="bg-muted rounded bottom-0 inset-s-0" position="absolute" />
|
||||
<CatalogSimplePriceWidgetView />
|
||||
</Column>
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { ICatalogPage } from '../../../../../api';
|
||||
import { CatalogLayoutProps } from './CatalogLayout.types';
|
||||
import { CatalogLayoutBadgeDisplayView } from './CatalogLayoutBadgeDisplayView';
|
||||
import { CatalogLayoutBcInfoView } from './CatalogLayoutBcInfoView';
|
||||
import { CatalogLayoutBuildersClubBuyView } from './CatalogLayoutBuildersClubBuyView';
|
||||
import { CatalogLayoutColorGroupingView } from './CatalogLayoutColorGroupingView';
|
||||
import { CatalogLayoutCustomPrefixView } from './CatalogLayoutCustomPrefixView';
|
||||
@@ -35,8 +34,6 @@ export const GetCatalogLayout = (page: ICatalogPage, hideNavigation: () => void)
|
||||
{
|
||||
case 'frontpage_featured':
|
||||
return null;
|
||||
case 'info_duckets':
|
||||
return <CatalogLayoutBcInfoView { ...layoutProps } />;
|
||||
case 'frontpage4':
|
||||
return <CatalogLayoutFrontpage4View { ...layoutProps } />;
|
||||
case 'pets':
|
||||
|
||||
@@ -206,7 +206,7 @@ export const CatalogLayoutPetView: FC<CatalogLayoutProps> = props =>
|
||||
</div> }
|
||||
|
||||
{ /* Top card: preview + name + purchase */ }
|
||||
<div className="flex gap-3 p-2.5 bg-white rounded border-2 border-card-grid-item-border">
|
||||
<div className="nitro-catalog-classic-pet-card flex gap-3 p-2.5 bg-white rounded border-2 border-card-grid-item-border">
|
||||
{ /* Pet preview */ }
|
||||
<div className="w-[160px] min-w-[160px] h-[140px] rounded overflow-hidden bg-card-grid-item relative flex items-center justify-center border border-card-grid-item-border">
|
||||
<CatalogViewProductWidgetView />
|
||||
@@ -240,12 +240,12 @@ export const CatalogLayoutPetView: FC<CatalogLayoutProps> = props =>
|
||||
<span className="text-[8px] font-mono text-white bg-primary px-1 py-px rounded">Offer: { currentOffer.offerId }</span>
|
||||
</div> }
|
||||
{ !!page.localization.getText(0) &&
|
||||
<p className="text-[10px] text-muted mt-0.5" dangerouslySetInnerHTML={ { __html: SanitizeHtml(page.localization.getText(0)) } } /> }
|
||||
<p className="text-[10px] text-dark mt-0.5" dangerouslySetInnerHTML={ { __html: SanitizeHtml(page.localization.getText(0)) } } /> }
|
||||
</div>
|
||||
|
||||
{ /* Name input */ }
|
||||
<div className="flex flex-col gap-1 mt-2">
|
||||
<label className="text-[9px] text-muted uppercase font-bold">{ LocalizeText('widgets.petpackage.name.title') }</label>
|
||||
<label className="text-[9px] text-dark uppercase font-bold">{ LocalizeText('widgets.petpackage.name.title') }</label>
|
||||
<div className="relative">
|
||||
<input
|
||||
className={ `w-full text-[11px] border-2 rounded px-2 py-1.5 focus:outline-none transition-colors ${ approvalResult > 0 ? 'border-danger bg-danger/5' : approvalResult === 0 ? 'border-success bg-success/5' : 'border-card-grid-item-border focus:border-primary bg-white' }` }
|
||||
@@ -267,7 +267,7 @@ export const CatalogLayoutPetView: FC<CatalogLayoutProps> = props =>
|
||||
<div className="flex items-center justify-between mt-2">
|
||||
<CatalogTotalPriceWidget />
|
||||
<button
|
||||
className="px-3 py-1 rounded text-[11px] font-bold bg-primary text-white hover:bg-secondary transition-colors cursor-pointer disabled:opacity-50"
|
||||
className="nitro-catalog-swf-button nitro-catalog-swf-buy-button"
|
||||
disabled={ !petName.length || (approvalResult > 0) }
|
||||
onClick={ purchasePet }
|
||||
>
|
||||
@@ -280,7 +280,7 @@ export const CatalogLayoutPetView: FC<CatalogLayoutProps> = props =>
|
||||
{ /* Breed/Color grid */ }
|
||||
<div className="flex-1 overflow-auto min-h-0">
|
||||
<div className="flex items-center gap-1.5 mb-1.5">
|
||||
<span className="text-[10px] font-bold text-muted uppercase tracking-wide">
|
||||
<span className="text-[10px] font-bold text-dark uppercase tracking-wide">
|
||||
{ colorsShowing ? LocalizeText('catalog.pets.choose.color') : LocalizeText('catalog.pets.choose.breed') }
|
||||
</span>
|
||||
{ colorsShowing &&
|
||||
|
||||
@@ -47,7 +47,7 @@ export const CatalogGuildSelectorWidgetView: FC<{}> = props =>
|
||||
return (
|
||||
<div className="bg-muted rounded p-1 text-black text-center">
|
||||
{ LocalizeText('catalog.guild_selector.members_only') }
|
||||
<Button className="mt-1">
|
||||
<Button fullWidth classNames={ [ 'mt-1', 'nitro-catalog-swf-button', 'nitro-catalog-swf-buy-button', 'whitespace-normal!', 'text-[10px]!', 'leading-tight!', 'py-1!' ] }>
|
||||
{ LocalizeText('catalog.guild_selector.find_groups') }
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -19,17 +19,17 @@ export const CatalogPriceDisplayWidgetView: FC<CatalogPriceDisplayWidgetViewProp
|
||||
if(!offer) return null;
|
||||
|
||||
return (
|
||||
<div className="flex items-center gap-1.5">
|
||||
<div className="nitro-catalog-swf-price-display">
|
||||
{ (offer.priceInCredits > 0) &&
|
||||
<div className="flex items-center gap-1 bg-warning/15 border border-warning/40 rounded-full px-2 py-0.5">
|
||||
<Text className="text-[11px]! font-bold text-dark">{ (offer.priceInCredits * quantity) }</Text>
|
||||
<div className="nitro-catalog-swf-price-pill">
|
||||
<Text className="nitro-catalog-swf-price-text">{ (offer.priceInCredits * quantity) }</Text>
|
||||
<LayoutCurrencyIcon type={ -1 } />
|
||||
</div> }
|
||||
{ separator && (offer.priceInCredits > 0) && (offer.priceInActivityPoints > 0) &&
|
||||
<FaPlus className="text-[7px] text-muted" /> }
|
||||
<FaPlus className="nitro-catalog-swf-price-plus" /> }
|
||||
{ (offer.priceInActivityPoints > 0) &&
|
||||
<div className="flex items-center gap-1 bg-purple/15 border border-purple/40 rounded-full px-2 py-0.5">
|
||||
<Text className="text-[11px]! font-bold text-dark">{ (offer.priceInActivityPoints * quantity) }</Text>
|
||||
<div className="nitro-catalog-swf-price-pill">
|
||||
<Text className="nitro-catalog-swf-price-text">{ (offer.priceInActivityPoints * quantity) }</Text>
|
||||
<LayoutCurrencyIcon type={ offer.activityPointType } />
|
||||
</div> }
|
||||
</div>
|
||||
|
||||
@@ -171,6 +171,8 @@ export const CatalogPurchaseWidgetView: FC<CatalogPurchaseWidgetViewProps> = pro
|
||||
|
||||
const PurchaseButton = () =>
|
||||
{
|
||||
const swfButtonClassNames = [ 'nitro-catalog-swf-button' ];
|
||||
|
||||
if(isBuildersClubPlaceable)
|
||||
{
|
||||
const hasMissingExtraParam = (purchaseOptions.extraParamRequired && (!purchaseOptions.extraData || !purchaseOptions.extraData.length));
|
||||
@@ -198,10 +200,10 @@ export const CatalogPurchaseWidgetView: FC<CatalogPurchaseWidgetViewProps> = pro
|
||||
return (
|
||||
<div className="flex flex-col gap-1.5 items-start">
|
||||
<div className="flex gap-1.5 flex-wrap">
|
||||
<Button disabled={ isDisabled } onClick={ () => startBuilderPlacement(true) }>
|
||||
<Button classNames={ swfButtonClassNames } disabled={ isDisabled } onClick={ () => startBuilderPlacement(true) }>
|
||||
{ LocalizeText('builder.placement_widget.place_many') }
|
||||
</Button>
|
||||
<Button disabled={ isDisabled } onClick={ () => startBuilderPlacement(false) } style={ buildersClubPlaceOneButtonStyle }>
|
||||
<Button classNames={ swfButtonClassNames } disabled={ isDisabled } onClick={ () => startBuilderPlacement(false) } style={ buildersClubPlaceOneButtonStyle }>
|
||||
{ LocalizeText('builder.placement_widget.place_one') }
|
||||
</Button>
|
||||
</div>
|
||||
@@ -220,37 +222,37 @@ export const CatalogPurchaseWidgetView: FC<CatalogPurchaseWidgetViewProps> = pro
|
||||
const priceCredits = (currentOffer.priceInCredits * purchaseOptions.quantity);
|
||||
const pricePoints = (currentOffer.priceInActivityPoints * purchaseOptions.quantity);
|
||||
|
||||
if(GetClubMemberLevel() < currentOffer.clubLevel) return <Button disabled variant="danger">{ LocalizeText('catalog.alert.hc.required') }</Button>;
|
||||
if(GetClubMemberLevel() < currentOffer.clubLevel) return <Button classNames={ swfButtonClassNames } disabled variant="danger">{ LocalizeText('catalog.alert.hc.required') }</Button>;
|
||||
|
||||
if(isLimitedSoldOut) return <Button disabled variant="danger">{ LocalizeText('catalog.alert.limited_edition_sold_out.title') }</Button>;
|
||||
if(isLimitedSoldOut) return <Button classNames={ swfButtonClassNames } disabled variant="danger">{ LocalizeText('catalog.alert.limited_edition_sold_out.title') }</Button>;
|
||||
|
||||
if(priceCredits > getCurrencyAmount(-1)) return <Button disabled variant="danger">{ LocalizeText('catalog.alert.notenough.title') }</Button>;
|
||||
if(priceCredits > getCurrencyAmount(-1)) return <Button classNames={ swfButtonClassNames } disabled variant="danger">{ LocalizeText('catalog.alert.notenough.title') }</Button>;
|
||||
|
||||
if(pricePoints > getCurrencyAmount(currentOffer.activityPointType)) return <Button disabled variant="danger">{ LocalizeText('catalog.alert.notenough.activitypoints.title.' + currentOffer.activityPointType) }</Button>;
|
||||
if(pricePoints > getCurrencyAmount(currentOffer.activityPointType)) return <Button classNames={ swfButtonClassNames } disabled variant="danger">{ LocalizeText('catalog.alert.notenough.activitypoints.title.' + currentOffer.activityPointType) }</Button>;
|
||||
|
||||
switch(purchaseState)
|
||||
{
|
||||
case CatalogPurchaseState.CONFIRM:
|
||||
return <Button variant="warning" onClick={ event => purchase() }>{ LocalizeText('catalog.marketplace.confirm_title') }</Button>;
|
||||
return <Button classNames={ swfButtonClassNames } variant="warning" onClick={ event => purchase() }>{ LocalizeText('catalog.marketplace.confirm_title') }</Button>;
|
||||
case CatalogPurchaseState.PURCHASE:
|
||||
return <Button disabled><LayoutLoadingSpinnerView /></Button>;
|
||||
return <Button classNames={ swfButtonClassNames } disabled><LayoutLoadingSpinnerView /></Button>;
|
||||
case CatalogPurchaseState.FAILED:
|
||||
return <Button variant="danger">{ LocalizeText('generic.failed') }</Button>;
|
||||
return <Button classNames={ swfButtonClassNames } variant="danger">{ LocalizeText('generic.failed') }</Button>;
|
||||
case CatalogPurchaseState.SOLD_OUT:
|
||||
return <Button variant="danger">{ LocalizeText('generic.failed') + ' - ' + LocalizeText('catalog.alert.limited_edition_sold_out.title') }</Button>;
|
||||
return <Button classNames={ swfButtonClassNames } variant="danger">{ LocalizeText('generic.failed') + ' - ' + LocalizeText('catalog.alert.limited_edition_sold_out.title') }</Button>;
|
||||
case CatalogPurchaseState.NONE:
|
||||
default:
|
||||
return <Button variant="success" disabled={ (purchaseOptions.extraParamRequired && (!purchaseOptions.extraData || !purchaseOptions.extraData.length)) } onClick={ event => setPurchaseState(CatalogPurchaseState.CONFIRM) }>{ LocalizeText('catalog.purchase_confirmation.' + (currentOffer.isRentOffer ? 'rent' : 'buy')) }</Button>;
|
||||
return <Button classNames={ [ ...swfButtonClassNames, 'nitro-catalog-swf-buy-button' ] } variant="success" disabled={ (purchaseOptions.extraParamRequired && (!purchaseOptions.extraData || !purchaseOptions.extraData.length)) } onClick={ event => setPurchaseState(CatalogPurchaseState.CONFIRM) }>{ LocalizeText('catalog.purchase_confirmation.' + (currentOffer.isRentOffer ? 'rent' : 'buy')) }</Button>;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<PurchaseButton />
|
||||
{ (!isBuildersClubOffer && !noGiftOption && !currentOffer.isRentOffer) &&
|
||||
<Button disabled={ ((purchaseOptions.quantity > 1) || !currentOffer.giftable || isLimitedSoldOut || (purchaseOptions.extraParamRequired && (!purchaseOptions.extraData || !purchaseOptions.extraData.length))) } onClick={ event => purchase(true) }>
|
||||
<Button classNames={ [ 'nitro-catalog-swf-button', 'nitro-catalog-swf-gift-button' ] } disabled={ ((purchaseOptions.quantity > 1) || !currentOffer.giftable || isLimitedSoldOut || (purchaseOptions.extraParamRequired && (!purchaseOptions.extraData || !purchaseOptions.extraData.length))) } onClick={ event => purchase(true) }>
|
||||
{ LocalizeText('catalog.purchase_confirmation.gift') }
|
||||
</Button> }
|
||||
<PurchaseButton />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -34,26 +34,26 @@ export const CatalogSpinnerWidgetView: FC<{}> = props =>
|
||||
if(!currentOffer || !currentOffer.bundlePurchaseAllowed) return null;
|
||||
|
||||
return (
|
||||
<div className="flex items-center gap-1.5">
|
||||
<span className="text-[10px] text-muted whitespace-nowrap">{ LocalizeText('catalog.bundlewidget.spinner.select.amount') }</span>
|
||||
<div className="flex items-center rounded overflow-hidden border-2 border-card-grid-item-border">
|
||||
<div className="nitro-catalog-swf-spinner">
|
||||
<span className="nitro-catalog-swf-spinner-label">{ LocalizeText('catalog.bundlewidget.quantity') }</span>
|
||||
<div className="nitro-catalog-swf-spinner-control">
|
||||
<button
|
||||
className="w-[24px] h-[24px] flex items-center justify-center bg-card-grid-item hover:bg-card-grid-item-active transition-colors cursor-pointer border-r border-card-grid-item-border"
|
||||
className="nitro-catalog-swf-spinner-button nitro-catalog-swf-spinner-button-less"
|
||||
onClick={ event => updateQuantity(quantity - 1) }
|
||||
>
|
||||
<FaMinus className="text-[7px] text-dark" />
|
||||
<FaMinus />
|
||||
</button>
|
||||
<input
|
||||
className="w-[40px] h-[24px] text-center text-[11px] font-bold bg-white border-x border-card-grid-item-border [appearance:textfield] [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none focus:outline-none"
|
||||
className="nitro-catalog-swf-spinner-input"
|
||||
type="number"
|
||||
value={ quantity }
|
||||
onChange={ event => updateQuantity(event.target.valueAsNumber) }
|
||||
/>
|
||||
<button
|
||||
className="w-[24px] h-[24px] flex items-center justify-center bg-card-grid-item hover:bg-card-grid-item-active transition-colors cursor-pointer border-l border-card-grid-item-border"
|
||||
className="nitro-catalog-swf-spinner-button nitro-catalog-swf-spinner-button-more"
|
||||
onClick={ event => updateQuantity(quantity + 1) }
|
||||
>
|
||||
<FaPlus className="text-[7px] text-dark" />
|
||||
<FaPlus />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -119,5 +119,5 @@ export const CatalogViewProductWidgetView: FC<{}> = props =>
|
||||
);
|
||||
}
|
||||
|
||||
return <LayoutRoomPreviewerView height={ 140 } roomPreviewer={ roomPreviewer } />;
|
||||
return <LayoutRoomPreviewerView height={ 240 } roomPreviewer={ roomPreviewer } />;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user