This commit is contained in:
@@ -1,9 +1,10 @@
|
||||
import Link from 'next/link';
|
||||
import { Icon } from '@/components/Icon/Icon';
|
||||
import { brands } from '@/data/brands';
|
||||
import { getBrands } from '@/lib/api';
|
||||
import styles from './BrandLogos.module.scss';
|
||||
|
||||
export function BrandLogos() {
|
||||
export async function BrandLogos() {
|
||||
const brands = await getBrands();
|
||||
return (
|
||||
<section className={styles.section}>
|
||||
<div className={styles.header}>
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
'use client';
|
||||
|
||||
import { Brand, Category, Product } from '@/types';
|
||||
import { Icon } from '@/components/Icon/Icon';
|
||||
import { categories } from '@/data/categories';
|
||||
import { brands } from '@/data/brands';
|
||||
import { products } from '@/data/products';
|
||||
import { useCatalogFilters } from '@/hooks/useCatalogFilters';
|
||||
import { getSubcategories } from '@/lib/getSubcategories';
|
||||
import styles from './CatalogSidebar.module.scss';
|
||||
@@ -11,11 +9,17 @@ import styles from './CatalogSidebar.module.scss';
|
||||
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);
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Suspense } from 'react';
|
||||
import { Product } from '@/types';
|
||||
import { Product, Brand, Category } from '@/types';
|
||||
import { Breadcrumb } from '@/components/Breadcrumb/Breadcrumb';
|
||||
import { CatalogSidebar } from '@/components/CatalogSidebar/CatalogSidebar';
|
||||
import { ProductCard } from '@/components/ProductCard/ProductCard';
|
||||
@@ -15,6 +15,9 @@ interface CatalogViewProps {
|
||||
breadcrumbItems: BreadcrumbItem[];
|
||||
basePath?: string;
|
||||
hiddenFilters?: string[];
|
||||
brands: Brand[];
|
||||
categories: Category[];
|
||||
allProducts?: Product[];
|
||||
}
|
||||
|
||||
export function CatalogView({
|
||||
@@ -22,11 +25,20 @@ export function CatalogView({
|
||||
breadcrumbItems,
|
||||
basePath = '/catalog',
|
||||
hiddenFilters,
|
||||
brands,
|
||||
categories,
|
||||
allProducts,
|
||||
}: CatalogViewProps) {
|
||||
return (
|
||||
<div className={styles.layout}>
|
||||
<Suspense fallback={null}>
|
||||
<CatalogSidebar basePath={basePath} hiddenFilters={hiddenFilters} />
|
||||
<CatalogSidebar
|
||||
basePath={basePath}
|
||||
hiddenFilters={hiddenFilters}
|
||||
brands={brands}
|
||||
categories={categories}
|
||||
products={allProducts ?? products}
|
||||
/>
|
||||
</Suspense>
|
||||
<div className={styles.main}>
|
||||
<div className={styles.header}>
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import Link from 'next/link';
|
||||
import { categories } from '@/data/categories';
|
||||
import { getCategories } from '@/lib/api';
|
||||
import styles from './Directions.module.scss';
|
||||
|
||||
export function Directions() {
|
||||
export async function Directions() {
|
||||
const categories = await getCategories();
|
||||
return (
|
||||
<section className={styles.section}>
|
||||
<h2 className={styles.title}>Направления</h2>
|
||||
|
||||
@@ -1,22 +1,32 @@
|
||||
'use client';
|
||||
|
||||
import { useState, useRef } from 'react';
|
||||
import { useState, useRef, useEffect } from 'react';
|
||||
import Link from 'next/link';
|
||||
import { usePathname } from 'next/navigation';
|
||||
import { Icon } from '@/components/Icon/Icon';
|
||||
import { useCart } from '@/components/CartProvider/CartProvider';
|
||||
import { headerNav } from '@/data/navigation';
|
||||
import { products } from '@/data/products';
|
||||
import { Product } from '@/types';
|
||||
import styles from './Header.module.scss';
|
||||
|
||||
const API_URL = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3000/api';
|
||||
|
||||
export function Header() {
|
||||
const pathname = usePathname();
|
||||
const { cart } = useCart();
|
||||
const [searchQuery, setSearchQuery] = useState('');
|
||||
const [searchFocused, setSearchFocused] = useState(false);
|
||||
const [mobileMenu, setMobileMenu] = useState(false);
|
||||
const [products, setProducts] = useState<Product[]>([]);
|
||||
const searchRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
fetch(`${API_URL}/products`)
|
||||
.then((res) => res.json())
|
||||
.then(setProducts)
|
||||
.catch(() => {});
|
||||
}, []);
|
||||
|
||||
const suggestions =
|
||||
searchQuery.length > 1
|
||||
? products
|
||||
|
||||
147
apps/web/src/components/Skeleton/Skeleton.module.scss
Normal file
147
apps/web/src/components/Skeleton/Skeleton.module.scss
Normal file
@@ -0,0 +1,147 @@
|
||||
@keyframes shimmer {
|
||||
0% {
|
||||
background-position: -200% 0;
|
||||
}
|
||||
100% {
|
||||
background-position: 200% 0;
|
||||
}
|
||||
}
|
||||
|
||||
.skeleton {
|
||||
background: linear-gradient(
|
||||
90deg,
|
||||
var(--color-slate-100) 25%,
|
||||
var(--color-slate-200) 50%,
|
||||
var(--color-slate-100) 75%
|
||||
);
|
||||
background-size: 200% 100%;
|
||||
animation: shimmer 1.5s ease-in-out infinite;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.directionsGrid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
gap: var(--space-lg);
|
||||
padding: var(--space-2xl) var(--space-xl);
|
||||
|
||||
@media (max-width: 768px) {
|
||||
grid-template-columns: 1fr 1fr;
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
.directionsCard {
|
||||
height: 160px;
|
||||
}
|
||||
|
||||
.brandsGrid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(6, 1fr);
|
||||
gap: var(--space-md);
|
||||
padding: var(--space-2xl) var(--space-xl);
|
||||
|
||||
@media (max-width: 768px) {
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
.brandsCard {
|
||||
height: 100px;
|
||||
}
|
||||
|
||||
.catalogGrid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
|
||||
gap: var(--space-md);
|
||||
padding: var(--space-md) 0;
|
||||
}
|
||||
|
||||
.catalogCard {
|
||||
height: 180px;
|
||||
}
|
||||
|
||||
.catalogLayout {
|
||||
display: flex;
|
||||
gap: var(--space-xl);
|
||||
max-width: 1280px;
|
||||
margin: 0 auto;
|
||||
padding: var(--space-xl);
|
||||
}
|
||||
|
||||
.catalogSidebar {
|
||||
width: 240px;
|
||||
height: 400px;
|
||||
flex-shrink: 0;
|
||||
|
||||
@media (max-width: 768px) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.catalogMain {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.titleBar {
|
||||
height: 24px;
|
||||
width: 200px;
|
||||
margin-bottom: var(--space-lg);
|
||||
}
|
||||
|
||||
.pageContainer {
|
||||
max-width: 1280px;
|
||||
margin: 0 auto;
|
||||
padding: var(--space-xl);
|
||||
}
|
||||
|
||||
.brandHeader {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-lg);
|
||||
margin-bottom: var(--space-xl);
|
||||
}
|
||||
|
||||
.brandLogo {
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
.brandTitle {
|
||||
height: 28px;
|
||||
width: 200px;
|
||||
margin-bottom: var(--space-sm);
|
||||
}
|
||||
|
||||
.brandMeta {
|
||||
height: 16px;
|
||||
width: 120px;
|
||||
}
|
||||
|
||||
.cardGrid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
gap: var(--space-md);
|
||||
margin-bottom: var(--space-xl);
|
||||
|
||||
@media (max-width: 768px) {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
.statCard {
|
||||
height: 80px;
|
||||
}
|
||||
|
||||
.productDetail {
|
||||
height: 400px;
|
||||
margin-top: var(--space-lg);
|
||||
}
|
||||
74
apps/web/src/components/Skeleton/Skeleton.tsx
Normal file
74
apps/web/src/components/Skeleton/Skeleton.tsx
Normal file
@@ -0,0 +1,74 @@
|
||||
import styles from './Skeleton.module.scss';
|
||||
|
||||
function Box({ className }: { className?: string }) {
|
||||
return <div className={`${styles.skeleton} ${className ?? ''}`} />;
|
||||
}
|
||||
|
||||
export function DirectionsSkeleton() {
|
||||
return (
|
||||
<section>
|
||||
<div className={styles.directionsGrid}>
|
||||
{Array.from({ length: 4 }).map((_, i) => (
|
||||
<Box key={i} className={styles.directionsCard} />
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
export function BrandLogosSkeleton() {
|
||||
return (
|
||||
<section>
|
||||
<div className={styles.brandsGrid}>
|
||||
{Array.from({ length: 12 }).map((_, i) => (
|
||||
<Box key={i} className={styles.brandsCard} />
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
export function CatalogSkeleton() {
|
||||
return (
|
||||
<div className={styles.catalogLayout}>
|
||||
<Box className={styles.catalogSidebar} />
|
||||
<div className={styles.catalogMain}>
|
||||
<Box className={styles.titleBar} />
|
||||
<div className={styles.catalogGrid}>
|
||||
{Array.from({ length: 6 }).map((_, i) => (
|
||||
<Box key={i} className={styles.catalogCard} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function BrandPageSkeleton() {
|
||||
return (
|
||||
<div className={styles.pageContainer}>
|
||||
<Box className={styles.titleBar} />
|
||||
<div className={styles.brandHeader}>
|
||||
<Box className={styles.brandLogo} />
|
||||
<div>
|
||||
<Box className={styles.brandTitle} />
|
||||
<Box className={styles.brandMeta} />
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.cardGrid}>
|
||||
{Array.from({ length: 4 }).map((_, i) => (
|
||||
<Box key={i} className={styles.statCard} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function ProductPageSkeleton() {
|
||||
return (
|
||||
<div className={styles.pageContainer}>
|
||||
<Box className={styles.titleBar} />
|
||||
<Box className={styles.productDetail} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user