feat(catalog): complete UI redesign with admin mode & favorites

- Modern card-based layout with vertical icon rail, breadcrumb nav, inline search
- Admin mode: edit/create/delete pages and offers, drag & drop reorder via HK API
- Favorites system: heart on furni, star on pages, localStorage persistence
- Redesigned product card with price pills, dynamic quantity spinner
- Upgraded trophies (filter tabs, parchment textarea), pets (breed/color flow),
  custom prefix (dynamic color boxes)
- Font fix: Ubuntu Regular, proper @font-face declarations
- New Tailwind design tokens and CatalogTexts.json for localization
This commit is contained in:
simoleo89
2026-03-21 16:49:35 +01:00
parent d18742d294
commit 74dce1d55d
25 changed files with 1891 additions and 296 deletions
@@ -2,11 +2,9 @@ import { GetSessionDataManager, IFurnitureData } from '@nitrots/nitro-renderer';
import { FC, useEffect, useState } from 'react';
import { FaSearch, FaTimes } from 'react-icons/fa';
import { CatalogPage, CatalogType, FilterCatalogNode, FurnitureOffer, GetOfferNodes, ICatalogNode, ICatalogPage, IPurchasableOffer, LocalizeText, PageLocalization, SearchResult } from '../../../../../api';
import { Button, Flex } from '../../../../../common';
import { useCatalog } from '../../../../../hooks';
import { NitroInput } from '../../../../../layout';
export const CatalogSearchView: FC<{}> = props =>
export const CatalogSearchView: FC<{}> = () =>
{
const [ searchValue, setSearchValue ] = useState('');
const { currentType = null, rootNode = null, offersToNodes = null, searchResult = null, setSearchResult = null, setCurrentPage = null } = useCatalog();
@@ -78,29 +76,22 @@ export const CatalogSearchView: FC<{}> = props =>
}, [ offersToNodes, currentType, rootNode, searchValue, setCurrentPage, setSearchResult ]);
return (
<div className="flex gap-1">
<Flex fullWidth alignItems="center" position="relative">
<NitroInput
placeholder={ LocalizeText('generic.search') }
value={ searchValue }
onChange={ event => setSearchValue(event.target.value) } />
</Flex>
{ (!searchValue || !searchValue.length) &&
<Button className="catalog-search-button" variant="primary">
<FaSearch className="fa-icon" />
</Button> }
{ searchValue && !!searchValue.length &&
<Button className="catalog-search-button" variant="primary" onClick={ event => setSearchValue('') }>
<FaTimes className="fa-icon" />
</Button> }
<div className="relative w-full">
<FaSearch className="absolute left-2 top-1/2 -translate-y-1/2 text-[9px] text-muted pointer-events-none" />
<input
className="w-full pl-6 pr-6 py-[3px] text-[11px] rounded border-2 border-card-grid-item-border bg-white text-dark placeholder-muted focus:outline-none focus:border-primary transition-colors"
placeholder={ LocalizeText('generic.search') }
type="text"
value={ searchValue }
onChange={ e => setSearchValue(e.target.value) }
/>
{ searchValue && searchValue.length > 0 &&
<button
className="absolute right-1.5 top-1/2 -translate-y-1/2 text-[9px] text-muted hover:text-danger cursor-pointer transition-colors"
onClick={ () => setSearchValue('') }
>
<FaTimes />
</button> }
</div>
);
};