diff --git a/client/src/app/routes/index.tsx b/client/src/app/routes/index.tsx index 5e9177e..1f34896 100644 --- a/client/src/app/routes/index.tsx +++ b/client/src/app/routes/index.tsx @@ -11,12 +11,15 @@ import { NotFoundPage } from '@/pages/not-found' import { PrivacyPolicyPage } from '@/pages/privacy-policy' import { ProductPage } from '@/pages/product' import { TermsPage } from '@/pages/terms' +import { usePageTitleReset } from '@/shared/lib/use-page-title' import { SkeletonPage } from '@/shared/ui/SkeletonPage' const AdminLayoutPage = lazy(() => import('@/pages/admin-layout').then((m) => ({ default: m.AdminLayoutPage }))) const MeLayoutPage = lazy(() => import('@/pages/me').then((m) => ({ default: m.MeLayoutPage }))) export function AppRoutes() { + usePageTitleReset() + return ( diff --git a/client/src/entities/product/ui/ProductCard.tsx b/client/src/entities/product/ui/ProductCard.tsx index 3e1aa31..0322d3a 100644 --- a/client/src/entities/product/ui/ProductCard.tsx +++ b/client/src/entities/product/ui/ProductCard.tsx @@ -1,13 +1,14 @@ import type { ReactNode } from 'react' import { useCallback, useMemo, useRef } from 'react' +import { useMediaQuery } from '@mui/material' import Box from '@mui/material/Box' import Card from '@mui/material/Card' -import CardContent from '@mui/material/CardContent' import CardMedia from '@mui/material/CardMedia' import Chip from '@mui/material/Chip' import Stack from '@mui/material/Stack' import Typography from '@mui/material/Typography' import { useNavigate } from 'react-router-dom' +import { Autoplay } from 'swiper/modules' import { Swiper, SwiperSlide } from 'swiper/react' import 'swiper/css' import type { Product } from '@/entities/product/model/types' @@ -19,6 +20,7 @@ type Props = { product: Product; mediaHeight?: number; actions?: ReactNode } export function ProductCard({ product, mediaHeight = 200, actions }: Props) { const navigate = useNavigate() + const isMobile = useMediaQuery('(max-width:600px)') const swiperRef = useRef(null) const imageUrls = useMemo(() => { const fromImages = (product.images ?? []) @@ -76,12 +78,14 @@ export function ProductCard({ product, mediaHeight = 200, actions }: Props) { > {imageUrls.length ? ( - + { swiperRef.current = s }} - allowTouchMove={false} + modules={isMobile ? [Autoplay] : undefined} + autoplay={isMobile ? { delay: 3000, disableOnInteraction: false, pauseOnMouseEnter: true } : undefined} + allowTouchMove={!isMobile} style={{ width: '100%', height: mediaHeight }} > {imageUrls.map((url) => ( @@ -150,8 +154,8 @@ export function ProductCard({ product, mediaHeight = 200, actions }: Props) { )} - - + + {product.category && ( {product.shortDescription ?? 'Описание появится позже.'} - - - - {formatPriceRub(product.priceCents)} - - {actions} - - + + + + {formatPriceRub(product.priceCents)} + + {actions} + + ) } diff --git a/client/src/pages/about/ui/AboutPage.tsx b/client/src/pages/about/ui/AboutPage.tsx index bfeac6f..1e2e25b 100644 --- a/client/src/pages/about/ui/AboutPage.tsx +++ b/client/src/pages/about/ui/AboutPage.tsx @@ -7,6 +7,7 @@ import * as maplibregl from 'maplibre-gl' import Map, { Marker } from 'react-map-gl/maplibre' import { STORE_EMAIL, STORE_PHONE, VK_URL } from '@/shared/config' import { PICKUP_ADDRESS_FULL, PICKUP_COORDINATES } from '@/shared/constants/pickup-point' +import { usePageTitle } from '@/shared/lib/use-page-title' const rasterStyle = { version: 8 as const, @@ -22,6 +23,7 @@ const rasterStyle = { } export function AboutPage() { + usePageTitle('О нас') const { lat, lng } = PICKUP_COORDINATES return ( diff --git a/client/src/pages/cart/ui/CartPage.tsx b/client/src/pages/cart/ui/CartPage.tsx index b849e78..fe4ece2 100644 --- a/client/src/pages/cart/ui/CartPage.tsx +++ b/client/src/pages/cart/ui/CartPage.tsx @@ -12,9 +12,11 @@ import { Minus, Plus, Trash2 } from 'lucide-react' import { Link as RouterLink } from 'react-router-dom' import { fetchMyCart, removeCartItem, setCartQty } from '@/entities/cart/api/cart-api' import { formatPriceRub } from '@/shared/lib/format-price' +import { usePageTitle } from '@/shared/lib/use-page-title' import { $user } from '@/shared/model/auth' export function CartPage() { + usePageTitle('Корзина') const user = useUnit($user) const qc = useQueryClient() diff --git a/client/src/pages/info/ui/InfoPage.tsx b/client/src/pages/info/ui/InfoPage.tsx index 7f47023..2f66c95 100644 --- a/client/src/pages/info/ui/InfoPage.tsx +++ b/client/src/pages/info/ui/InfoPage.tsx @@ -3,6 +3,7 @@ import Container from '@mui/material/Container' import Divider from '@mui/material/Divider' import Stack from '@mui/material/Stack' import Typography from '@mui/material/Typography' +import { usePageTitle } from '@/shared/lib/use-page-title' import { DeliverySection } from './sections/DeliverySection' import { HowToOrderSection } from './sections/HowToOrderSection' import { OrderStatusesSection } from './sections/OrderStatusesSection' @@ -10,6 +11,7 @@ import { PaymentSection } from './sections/PaymentSection' import { ReturnsSection } from './sections/ReturnsSection' export function InfoPage() { + usePageTitle('Информация для покупателей') return ( {/* Hero */} diff --git a/client/src/pages/me/ui/sections/AddressesPage.tsx b/client/src/pages/me/ui/sections/AddressesPage.tsx index 8535e09..16a834f 100644 --- a/client/src/pages/me/ui/sections/AddressesPage.tsx +++ b/client/src/pages/me/ui/sections/AddressesPage.tsx @@ -16,6 +16,7 @@ import { } from '@/entities/user/api/address-api' import type { ShippingAddress } from '@/entities/user/model/types' import { AddressFormDialog, type AddressFormValues } from '@/features/address-form' +import { usePageTitle } from '@/shared/lib/use-page-title' const defaultAddressForm = (isDefault: boolean): AddressFormValues => ({ label: '', @@ -29,6 +30,7 @@ const defaultAddressForm = (isDefault: boolean): AddressFormValues => ({ }) export function AddressesPage() { + usePageTitle('Адреса доставки') const queryClient = useQueryClient() const [open, setOpen] = useState(false) const [editing, setEditing] = useState(null) diff --git a/client/src/pages/me/ui/sections/MessagesPage.tsx b/client/src/pages/me/ui/sections/MessagesPage.tsx index f225f78..b558264 100644 --- a/client/src/pages/me/ui/sections/MessagesPage.tsx +++ b/client/src/pages/me/ui/sections/MessagesPage.tsx @@ -16,6 +16,7 @@ import { fetchMyOrder, postOrderMessage } from '@/entities/order/api/order-api' import { fetchMyConversations, markOrderMessagesRead } from '@/entities/user/api/messages-api' import { fetchAdminAvatar } from '@/entities/user/api/user-api' import { orderStatusLabelRu } from '@/shared/lib/order-status-labels' +import { usePageTitle } from '@/shared/lib/use-page-title' import { $user } from '@/shared/model/auth' import { ChatMessageBubble } from '@/shared/ui/ChatMessageBubble' import { OrderMessageBody } from '@/shared/ui/OrderMessageBody' @@ -24,6 +25,7 @@ import { RichTextMessageEditor } from '@/shared/ui/RichTextMessageEditor' import { UserAvatar } from '@/shared/ui/UserAvatar' export function MessagesPage() { + usePageTitle('Сообщения') const qc = useQueryClient() const [selectedId, setSelectedId] = useState(null) const [text, setText] = useState('') diff --git a/client/src/pages/me/ui/sections/NotificationsPage.tsx b/client/src/pages/me/ui/sections/NotificationsPage.tsx index a9c5819..a255904 100644 --- a/client/src/pages/me/ui/sections/NotificationsPage.tsx +++ b/client/src/pages/me/ui/sections/NotificationsPage.tsx @@ -13,6 +13,7 @@ import { } from '@/entities/notification/api/notifications-api' import type { UserNotificationSettings } from '@/entities/notification/api/notifications-api' import { isSyntheticEmail } from '@/shared/lib/is-synthetic-email' +import { usePageTitle } from '@/shared/lib/use-page-title' import { $user } from '@/shared/model/auth' function isOrderStatusChangesOn(s: UserNotificationSettings): boolean { @@ -27,6 +28,7 @@ const orderStatusChangesPayload = (on: boolean) => ({ }) export function NotificationsPage() { + usePageTitle('Уведомления') const queryClient = useQueryClient() const [error, setError] = useState(null) const user = useUnit($user) diff --git a/client/src/pages/me/ui/sections/OrdersPage.tsx b/client/src/pages/me/ui/sections/OrdersPage.tsx index fc248af..c6f8351 100644 --- a/client/src/pages/me/ui/sections/OrdersPage.tsx +++ b/client/src/pages/me/ui/sections/OrdersPage.tsx @@ -12,8 +12,10 @@ import { ORDER_STATUSES } from '@/shared/constants/order' import { formatPriceRub } from '@/shared/lib/format-price' import { groupOrdersByStatus } from '@/shared/lib/group-orders-by-status' import { orderStatusLabelRu } from '@/shared/lib/order-status-labels' +import { usePageTitle } from '@/shared/lib/use-page-title' export function OrdersPage() { + usePageTitle('Заказы') const ordersQuery = useQuery({ queryKey: ['me', 'orders'], queryFn: fetchMyOrders, diff --git a/client/src/pages/me/ui/sections/SettingsPage.tsx b/client/src/pages/me/ui/sections/SettingsPage.tsx index 70b5ea5..79c4a2a 100644 --- a/client/src/pages/me/ui/sections/SettingsPage.tsx +++ b/client/src/pages/me/ui/sections/SettingsPage.tsx @@ -4,6 +4,7 @@ import Divider from '@mui/material/Divider' import Stack from '@mui/material/Stack' import Typography from '@mui/material/Typography' import { useUnit } from 'effector-react' +import { usePageTitle } from '@/shared/lib/use-page-title' import { $user } from '@/shared/model/auth' import { AuthMethodsSection } from './AuthMethodsSection' import { AvatarSection } from './AvatarSection' @@ -11,6 +12,7 @@ import { DeleteAccountSection } from './DeleteAccountSection' import { ProfileSection } from './ProfileSection' export function SettingsPage() { + usePageTitle('Настройки') const user = useUnit($user) if (!user) { diff --git a/client/src/pages/privacy-policy/ui/PrivacyPolicyPage.tsx b/client/src/pages/privacy-policy/ui/PrivacyPolicyPage.tsx index 4901f71..c418bab 100644 --- a/client/src/pages/privacy-policy/ui/PrivacyPolicyPage.tsx +++ b/client/src/pages/privacy-policy/ui/PrivacyPolicyPage.tsx @@ -9,6 +9,7 @@ import { STORE_OP_ADDR, STORE_PUBLIC_SITE_URL, } from '@/shared/config' +import { usePageTitle } from '@/shared/lib/use-page-title' const SITE_URL = STORE_PUBLIC_SITE_URL || (typeof window !== 'undefined' ? window.location.origin : '') @@ -90,6 +91,7 @@ const sections = [ ] export function PrivacyPolicyPage() { + usePageTitle('Политика конфиденциальности') return ( { const p = productQuery.data if (!p) return [] diff --git a/client/src/pages/terms/ui/TermsPage.tsx b/client/src/pages/terms/ui/TermsPage.tsx index be64128..2c40c61 100644 --- a/client/src/pages/terms/ui/TermsPage.tsx +++ b/client/src/pages/terms/ui/TermsPage.tsx @@ -10,6 +10,7 @@ import { STORE_OP_INN, STORE_OP_ADDR, } from '@/shared/config' +import { usePageTitle } from '@/shared/lib/use-page-title' const SITE_URL = STORE_PUBLIC_SITE_URL || (typeof window !== 'undefined' ? window.location.origin : '') @@ -147,6 +148,7 @@ const sections = [ ] export function TermsPage() { + usePageTitle('Пользовательское соглашение') return ( { + currentTitle = title ? `${title} — Любимый Креатив` : BASE_TITLE + document.title = currentTitle + }, [title]) +} + +export function usePageTitleReset() { + const location = useLocation() + + useEffect(() => { + document.title = BASE_TITLE + currentTitle = BASE_TITLE + }, [location.pathname]) +} diff --git a/server/prisma/prisma/dev.db b/server/prisma/prisma/dev.db index 72c18a1..58f2109 100644 Binary files a/server/prisma/prisma/dev.db and b/server/prisma/prisma/dev.db differ