Files
yslootahrobotics/src/app/robots/CatalogClient.tsx
T
NajjarandClaude Opus 4.8 873026a4a9
CI/CD / test-and-build (push) Waiting to run
CI/CD / deploy (push) Blocked by required conditions
feat: monochrome black & white rebrand + new Pudu robots
- Convert site from royal-blue theme to black/white monochrome (desaturate
  all blue-hued colors; keep WhatsApp green and error red)
- Swap blue circular brand logo for mono YS mark; regenerate favicon/PWA icons
- Add 6 Pudu robots (CC1 Pro, MT1 Max, MT1 Vac, T600, T600 Underride, BG1 Pro)
- Fix favicon.ico to RGBA (Next 16 Turbopack decode) and Stripe apiVersion type

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-06 13:20:26 +04:00

126 lines
4.3 KiB
TypeScript

'use client';
import { Suspense, useMemo, useState } from 'react';
import { useSearchParams, useRouter, usePathname } from 'next/navigation';
import { ROBOTS, CATEGORY_LABELS, BRANDS, type RobotCategory, type RobotBrand } from '@/data/robots';
import { RobotProductCard } from '@/components/robotics/RobotProductCard';
import { ProductFilterTabs, type Tab } from '@/components/robotics/ProductFilterTabs';
type Filter = { brand: RobotBrand | 'all'; category: RobotCategory | 'all' };
function CatalogInner() {
const params = useSearchParams();
const router = useRouter();
const pathname = usePathname();
const initialBrand = (params.get('brand') as RobotBrand) || 'all';
const initialCategory = (params.get('category') as RobotCategory) || 'all';
const [filter, setFilter] = useState<Filter>({ brand: initialBrand, category: initialCategory });
const filtered = useMemo(() => {
return ROBOTS.filter((r) => {
if (filter.brand !== 'all' && r.brand !== filter.brand) return false;
if (filter.category !== 'all' && !r.categories.includes(filter.category)) return false;
return true;
});
}, [filter]);
const updateFilter = (next: Partial<Filter>) => {
const newFilter = { ...filter, ...next };
setFilter(newFilter);
const sp = new URLSearchParams();
if (newFilter.brand !== 'all') sp.set('brand', newFilter.brand);
if (newFilter.category !== 'all') sp.set('category', newFilter.category);
const qs = sp.toString();
router.replace(qs ? `${pathname}?${qs}` : pathname, { scroll: false });
};
const brandTabs: Tab[] = [
{ id: 'all', label: 'All brands' },
...(Object.keys(BRANDS) as RobotBrand[]).map((b) => ({
id: b,
label: BRANDS[b].name.split(' ')[0],
accent: BRANDS[b].accent,
})),
];
const categoryTabs: Tab[] = [
{ id: 'all', label: 'All' },
...(Object.keys(CATEGORY_LABELS) as RobotCategory[]).map((c) => ({
id: c,
label: CATEGORY_LABELS[c],
})),
];
return (
<div style={{ display: 'flex', flexDirection: 'column', gap: '1.5rem' }}>
<div className="glass" style={{ padding: '1rem 1.25rem', borderRadius: '1.25rem', display: 'flex', flexWrap: 'wrap', gap: '1.5rem' }}>
<ProductFilterTabs
title="Brand"
tabs={brandTabs}
activeId={filter.brand}
onChange={(id) => updateFilter({ brand: id as RobotBrand | 'all' })}
/>
<ProductFilterTabs
title="Category"
tabs={categoryTabs}
activeId={filter.category}
onChange={(id) => updateFilter({ category: id as RobotCategory | 'all' })}
/>
</div>
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', flexWrap: 'wrap', gap: '0.5rem' }}>
<span style={{ color: '#a8a8a8', fontSize: '0.85rem' }}>
{filtered.length} robot{filtered.length === 1 ? '' : 's'} found
</span>
{(filter.brand !== 'all' || filter.category !== 'all') && (
<button
type="button"
onClick={() => updateFilter({ brand: 'all', category: 'all' })}
style={{
background: 'transparent',
border: 'none',
color: '#e7e7e7',
fontSize: '0.78rem',
letterSpacing: '0.14em',
textTransform: 'uppercase',
fontWeight: 600,
cursor: 'pointer',
}}
>
Clear filters
</button>
)}
</div>
{filtered.length === 0 ? (
<div className="card" style={{ padding: '2rem', textAlign: 'center', color: '#e7e7e7' }}>
No robots match these filters yet. Try a different brand or category, or{' '}
<a href="/contact/" style={{ color: '#e7e7e7', textDecoration: 'none' }}>contact us</a> for tailored options.
</div>
) : (
<div
style={{
display: 'grid',
gap: '1.25rem',
gridTemplateColumns: 'repeat(auto-fit, minmax(min(280px, 100%), 1fr))',
}}
>
{filtered.map((r) => (
<RobotProductCard key={r.id} robot={r} />
))}
</div>
)}
</div>
);
}
export function CatalogClient() {
return (
<Suspense fallback={<div className="card" style={{ padding: '2rem', color: '#e7e7e7' }}>Loading catalog…</div>}>
<CatalogInner />
</Suspense>
);
}