This commit is contained in:
41
apps/web/src/app/brand/[id]/catalog/BrandCatalogContent.tsx
Normal file
41
apps/web/src/app/brand/[id]/catalog/BrandCatalogContent.tsx
Normal file
@@ -0,0 +1,41 @@
|
||||
import { notFound } from 'next/navigation';
|
||||
import { filterProducts, parseSearchParams } from '@/lib/filterProducts';
|
||||
import { CatalogView } from '@/components/CatalogView/CatalogView';
|
||||
import { getBrands, getProducts } from '@/lib/api';
|
||||
|
||||
export async function BrandCatalogContent({
|
||||
id,
|
||||
params,
|
||||
}: {
|
||||
id: string;
|
||||
params: Record<string, string | string[] | undefined>;
|
||||
}) {
|
||||
const [brands, products] = await Promise.all([
|
||||
getBrands(),
|
||||
getProducts(),
|
||||
]);
|
||||
const brand = brands.find((b) => b.id === Number(id));
|
||||
if (!brand) notFound();
|
||||
|
||||
const filters = parseSearchParams(params);
|
||||
const brandProducts = products.filter((p) => p.brand === brand.name);
|
||||
const filtered = filterProducts(brandProducts, filters);
|
||||
|
||||
const basePath = `/brand/${id}/catalog`;
|
||||
|
||||
const breadcrumbItems = [
|
||||
{ label: 'Главная', href: '/' },
|
||||
{ label: 'Каталог', href: '/catalog' },
|
||||
{ label: brand.name, href: `/brand/${id}` },
|
||||
{ label: 'Каталог' },
|
||||
];
|
||||
|
||||
return (
|
||||
<CatalogView
|
||||
products={filtered}
|
||||
breadcrumbItems={breadcrumbItems}
|
||||
basePath={basePath}
|
||||
hiddenFilters={['brand']}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
import { notFound } from 'next/navigation';
|
||||
import { filterProducts, parseSearchParams } from '@/lib/filterProducts';
|
||||
import { CatalogView } from '@/components/CatalogView/CatalogView';
|
||||
import { getBrands, getProducts, getCategories } from '@/lib/api';
|
||||
import { Suspense } from 'react';
|
||||
import { getBrands } from '@/lib/api';
|
||||
import { CatalogSkeleton } from '@/components/Skeleton/Skeleton';
|
||||
import { BrandCatalogContent } from './BrandCatalogContent';
|
||||
|
||||
export async function generateMetadata({
|
||||
params,
|
||||
@@ -26,37 +26,12 @@ export default async function BrandCatalogPage({
|
||||
searchParams: Promise<Record<string, string | string[] | undefined>>;
|
||||
}) {
|
||||
const { id } = await params;
|
||||
const [brands, products, categories] = await Promise.all([
|
||||
getBrands(),
|
||||
getProducts(),
|
||||
getCategories(),
|
||||
]);
|
||||
const brand = brands.find((b) => b.id === Number(id));
|
||||
if (!brand) notFound();
|
||||
|
||||
const sp = await searchParams;
|
||||
const filters = parseSearchParams(sp);
|
||||
const brandProducts = products.filter((p) => p.brand === brand.name);
|
||||
const filtered = filterProducts(brandProducts, filters);
|
||||
|
||||
const basePath = `/brand/${id}/catalog`;
|
||||
|
||||
const breadcrumbItems = [
|
||||
{ label: 'Главная', href: '/' },
|
||||
{ label: 'Каталог', href: '/catalog' },
|
||||
{ label: brand.name, href: `/brand/${id}` },
|
||||
{ label: 'Каталог' },
|
||||
];
|
||||
const key = JSON.stringify(sp);
|
||||
|
||||
return (
|
||||
<CatalogView
|
||||
products={filtered}
|
||||
breadcrumbItems={breadcrumbItems}
|
||||
basePath={basePath}
|
||||
hiddenFilters={['brand']}
|
||||
brands={brands}
|
||||
categories={categories}
|
||||
allProducts={brandProducts}
|
||||
/>
|
||||
<Suspense key={key} fallback={<CatalogSkeleton />}>
|
||||
<BrandCatalogContent id={id} params={sp} />
|
||||
</Suspense>
|
||||
);
|
||||
}
|
||||
|
||||
5
apps/web/src/app/brand/[id]/loading.tsx
Normal file
5
apps/web/src/app/brand/[id]/loading.tsx
Normal file
@@ -0,0 +1,5 @@
|
||||
import { BrandPageSkeleton } from '@/components/Skeleton/Skeleton';
|
||||
|
||||
export default function Loading() {
|
||||
return <BrandPageSkeleton />;
|
||||
}
|
||||
25
apps/web/src/app/catalog/CatalogContent.tsx
Normal file
25
apps/web/src/app/catalog/CatalogContent.tsx
Normal file
@@ -0,0 +1,25 @@
|
||||
import { filterProducts, parseSearchParams } from '@/lib/filterProducts';
|
||||
import { CatalogView } from '@/components/CatalogView/CatalogView';
|
||||
import { getProducts } from '@/lib/api';
|
||||
|
||||
export async function CatalogContent({
|
||||
params,
|
||||
}: {
|
||||
params: Record<string, string | string[] | undefined>;
|
||||
}) {
|
||||
const products = await getProducts();
|
||||
const filters = parseSearchParams(params);
|
||||
const filtered = filterProducts(products, filters);
|
||||
|
||||
const breadcrumbItems = [
|
||||
{ label: 'Главная', href: '/' },
|
||||
{ label: 'Каталог' },
|
||||
];
|
||||
|
||||
return (
|
||||
<CatalogView
|
||||
products={filtered}
|
||||
breadcrumbItems={breadcrumbItems}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Suspense } from 'react';
|
||||
import { categoryNameMap, CategorySlug } from '@/types';
|
||||
import { filterProducts, parseSearchParams } from '@/lib/filterProducts';
|
||||
import { CatalogView } from '@/components/CatalogView/CatalogView';
|
||||
import { getProducts, getBrands, getCategories } from '@/lib/api';
|
||||
import { CatalogSkeleton } from '@/components/Skeleton/Skeleton';
|
||||
import { CatalogContent } from './CatalogContent';
|
||||
|
||||
export async function generateMetadata({
|
||||
searchParams,
|
||||
@@ -27,26 +27,11 @@ export default async function CatalogPage({
|
||||
searchParams: Promise<Record<string, string | string[] | undefined>>;
|
||||
}) {
|
||||
const params = await searchParams;
|
||||
const [products, brands, categories] = await Promise.all([
|
||||
getProducts(),
|
||||
getBrands(),
|
||||
getCategories(),
|
||||
]);
|
||||
const filters = parseSearchParams(params);
|
||||
const filtered = filterProducts(products, filters);
|
||||
|
||||
const breadcrumbItems = [
|
||||
{ label: 'Главная', href: '/' },
|
||||
{ label: 'Каталог' },
|
||||
];
|
||||
const key = JSON.stringify(params);
|
||||
|
||||
return (
|
||||
<CatalogView
|
||||
products={filtered}
|
||||
breadcrumbItems={breadcrumbItems}
|
||||
brands={brands}
|
||||
categories={categories}
|
||||
allProducts={products}
|
||||
/>
|
||||
<Suspense key={key} fallback={<CatalogSkeleton />}>
|
||||
<CatalogContent params={params} />
|
||||
</Suspense>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,15 +1,24 @@
|
||||
import { Suspense } from 'react';
|
||||
import { Hero } from '@/components/Hero/Hero';
|
||||
import { Directions } from '@/components/Directions/Directions';
|
||||
import { TrustStrip } from '@/components/TrustStrip/TrustStrip';
|
||||
import { BrandLogos } from '@/components/BrandLogos/BrandLogos';
|
||||
import {
|
||||
DirectionsSkeleton,
|
||||
BrandLogosSkeleton,
|
||||
} from '@/components/Skeleton/Skeleton';
|
||||
|
||||
export default function HomePage() {
|
||||
return (
|
||||
<>
|
||||
<Hero />
|
||||
<Suspense fallback={<DirectionsSkeleton />}>
|
||||
<Directions />
|
||||
</Suspense>
|
||||
<TrustStrip />
|
||||
<Suspense fallback={<BrandLogosSkeleton />}>
|
||||
<BrandLogos />
|
||||
</Suspense>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
5
apps/web/src/app/product/[sku]/loading.tsx
Normal file
5
apps/web/src/app/product/[sku]/loading.tsx
Normal file
@@ -0,0 +1,5 @@
|
||||
import { ProductPageSkeleton } from '@/components/Skeleton/Skeleton';
|
||||
|
||||
export default function Loading() {
|
||||
return <ProductPageSkeleton />;
|
||||
}
|
||||
@@ -1,27 +1,41 @@
|
||||
'use client';
|
||||
|
||||
import { useEffect, useState } from 'react';
|
||||
import { Brand, Category, Product } from '@/types';
|
||||
import { Icon } from '@/components/Icon/Icon';
|
||||
import { useCatalogFilters } from '@/hooks/useCatalogFilters';
|
||||
import { getSubcategories } from '@/lib/getSubcategories';
|
||||
import styles from './CatalogSidebar.module.scss';
|
||||
|
||||
const API_URL = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3000/api';
|
||||
|
||||
interface CatalogSidebarProps {
|
||||
basePath?: string;
|
||||
hiddenFilters?: string[];
|
||||
brands: Brand[];
|
||||
categories: Category[];
|
||||
products: Product[];
|
||||
}
|
||||
|
||||
export function CatalogSidebar({
|
||||
basePath = '/catalog',
|
||||
hiddenFilters = [],
|
||||
brands,
|
||||
categories,
|
||||
products,
|
||||
}: CatalogSidebarProps) {
|
||||
const { filters, setFilter } = useCatalogFilters(basePath);
|
||||
const [brands, setBrands] = useState<Brand[]>([]);
|
||||
const [categories, setCategories] = useState<Category[]>([]);
|
||||
const [products, setProducts] = useState<Product[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
Promise.all([
|
||||
fetch(`${API_URL}/brands`).then((r) => r.json()),
|
||||
fetch(`${API_URL}/categories`).then((r) => r.json()),
|
||||
fetch(`${API_URL}/products`).then((r) => r.json()),
|
||||
])
|
||||
.then(([b, c, p]) => {
|
||||
setBrands(b);
|
||||
setCategories(c);
|
||||
setProducts(p);
|
||||
})
|
||||
.catch(() => {});
|
||||
}, []);
|
||||
|
||||
const toggleFilter = (
|
||||
key: 'category' | 'brand' | 'subcategory' | 'sort',
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { Suspense } from 'react';
|
||||
import { Product, Brand, Category } from '@/types';
|
||||
import { Product } from '@/types';
|
||||
import { Breadcrumb } from '@/components/Breadcrumb/Breadcrumb';
|
||||
import { CatalogSidebar } from '@/components/CatalogSidebar/CatalogSidebar';
|
||||
import { ProductCard } from '@/components/ProductCard/ProductCard';
|
||||
@@ -15,9 +14,6 @@ interface CatalogViewProps {
|
||||
breadcrumbItems: BreadcrumbItem[];
|
||||
basePath?: string;
|
||||
hiddenFilters?: string[];
|
||||
brands: Brand[];
|
||||
categories: Category[];
|
||||
allProducts?: Product[];
|
||||
}
|
||||
|
||||
export function CatalogView({
|
||||
@@ -25,21 +21,10 @@ export function CatalogView({
|
||||
breadcrumbItems,
|
||||
basePath = '/catalog',
|
||||
hiddenFilters,
|
||||
brands,
|
||||
categories,
|
||||
allProducts,
|
||||
}: CatalogViewProps) {
|
||||
return (
|
||||
<div className={styles.layout}>
|
||||
<Suspense fallback={null}>
|
||||
<CatalogSidebar
|
||||
basePath={basePath}
|
||||
hiddenFilters={hiddenFilters}
|
||||
brands={brands}
|
||||
categories={categories}
|
||||
products={allProducts ?? products}
|
||||
/>
|
||||
</Suspense>
|
||||
<CatalogSidebar basePath={basePath} hiddenFilters={hiddenFilters} />
|
||||
<div className={styles.main}>
|
||||
<div className={styles.header}>
|
||||
<Breadcrumb items={breadcrumbItems} />
|
||||
|
||||
@@ -24,7 +24,7 @@ export function Header() {
|
||||
fetch(`${API_URL}/products`)
|
||||
.then((res) => res.json())
|
||||
.then(setProducts)
|
||||
.catch(() => {});
|
||||
.catch((error) => console.error(error));
|
||||
}, []);
|
||||
|
||||
const suggestions =
|
||||
|
||||
@@ -31,7 +31,7 @@ export function useCatalogFilters(basePath = '/catalog') {
|
||||
const qs = params.toString();
|
||||
return qs ? `${basePath}?${qs}` : basePath;
|
||||
},
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
|
||||
[basePath, searchParams]
|
||||
);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user