deploy
This commit is contained in:
@@ -41,6 +41,9 @@ export function MainLayout({ children }: PropsWithChildren) {
|
||||
<Link component={RouterLink} to="/" color="inherit" underline="hover" variant="body2">
|
||||
Каталог
|
||||
</Link>
|
||||
<Link component={RouterLink} to="/privacy" color="inherit" underline="hover" variant="body2">
|
||||
Политика конфиденциальности
|
||||
</Link>
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
Изделия ручной работы: вещи с характером и вниманием к деталям.
|
||||
</Typography>
|
||||
@@ -60,9 +63,6 @@ export function MainLayout({ children }: PropsWithChildren) {
|
||||
<Link component={RouterLink} to="/about" color="inherit" underline="hover" variant="body2">
|
||||
О нас
|
||||
</Link>
|
||||
<Link component={RouterLink} to="/privacy" color="inherit" underline="hover" variant="body2">
|
||||
Политика конфиденциальности
|
||||
</Link>
|
||||
</Stack>
|
||||
</Grid>
|
||||
<Grid size={{ xs: 12, sm: 4 }}>
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
import type { ReactNode } from 'react'
|
||||
import { useMemo, useRef } from 'react'
|
||||
import { useCallback, useMemo, useRef } from 'react'
|
||||
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 Link from '@mui/material/Link'
|
||||
import Stack from '@mui/material/Stack'
|
||||
import Typography from '@mui/material/Typography'
|
||||
import { Link as RouterLink } from 'react-router-dom'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
import { Swiper, SwiperSlide } from 'swiper/react'
|
||||
import 'swiper/css'
|
||||
import type { Product } from '@/entities/product/model/types'
|
||||
@@ -18,6 +17,7 @@ import type { Swiper as SwiperType } from 'swiper/types'
|
||||
type Props = { product: Product; mediaHeight?: number; actions?: ReactNode }
|
||||
|
||||
export function ProductCard({ product, mediaHeight = 200, actions }: Props) {
|
||||
const navigate = useNavigate()
|
||||
const swiperRef = useRef<SwiperType | null>(null)
|
||||
const imageUrls = useMemo(() => {
|
||||
const fromImages = (product.images ?? [])
|
||||
@@ -40,6 +40,10 @@ export function ProductCard({ product, mediaHeight = 200, actions }: Props) {
|
||||
swiperRef.current.slideTo(idx, 0)
|
||||
}
|
||||
|
||||
const goToProduct = useCallback(() => {
|
||||
navigate(`/products/${product.id}`)
|
||||
}, [navigate, product.id])
|
||||
|
||||
const stockLabel =
|
||||
product.inStock && product.quantity === 0
|
||||
? { label: 'Нет в наличии', color: 'default' as const }
|
||||
@@ -49,7 +53,9 @@ export function ProductCard({ product, mediaHeight = 200, actions }: Props) {
|
||||
|
||||
return (
|
||||
<Card
|
||||
onClick={goToProduct}
|
||||
sx={{
|
||||
cursor: 'pointer',
|
||||
height: '100%',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
@@ -64,6 +70,7 @@ export function ProductCard({ product, mediaHeight = 200, actions }: Props) {
|
||||
boxShadow: '0 8px 30px rgba(0,0,0,0.10)',
|
||||
},
|
||||
'&:hover .product-card__media': { transform: 'scale(1.06)' },
|
||||
'&:hover .product-card__title': { color: 'primary.main' },
|
||||
'@media (prefers-reduced-motion: reduce)': {
|
||||
transition: 'none',
|
||||
'&:hover': { transform: 'none' },
|
||||
@@ -71,13 +78,7 @@ export function ProductCard({ product, mediaHeight = 200, actions }: Props) {
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Link
|
||||
component={RouterLink}
|
||||
to={`/products/${product.id}`}
|
||||
underline="none"
|
||||
color="inherit"
|
||||
sx={{ display: 'block', position: 'relative' }}
|
||||
>
|
||||
<Box sx={{ position: 'relative' }}>
|
||||
{imageUrls.length ? (
|
||||
<Box onMouseMove={onMouseMove} sx={{ height: mediaHeight, overflow: 'hidden' }}>
|
||||
<Swiper
|
||||
@@ -144,7 +145,7 @@ export function ProductCard({ product, mediaHeight = 200, actions }: Props) {
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Link>
|
||||
</Box>
|
||||
|
||||
<CardContent sx={{ flexGrow: 1, p: 2, '&:last-child': { pb: 2 } }}>
|
||||
<Stack spacing={1.25}>
|
||||
@@ -159,15 +160,13 @@ export function ProductCard({ product, mediaHeight = 200, actions }: Props) {
|
||||
|
||||
<Typography
|
||||
variant="subtitle1"
|
||||
component={RouterLink}
|
||||
to={`/products/${product.id}`}
|
||||
className="product-card__title"
|
||||
sx={{
|
||||
textDecoration: 'none',
|
||||
color: 'text.primary',
|
||||
fontWeight: 600,
|
||||
lineHeight: 1.3,
|
||||
transition: 'color 150ms ease',
|
||||
'&:hover': { color: 'primary.main' },
|
||||
}}
|
||||
>
|
||||
{product.title}
|
||||
|
||||
@@ -1,22 +1,146 @@
|
||||
import Box from '@mui/material/Box'
|
||||
import Paper from '@mui/material/Paper'
|
||||
import Typography from '@mui/material/Typography'
|
||||
import { STORE_EMAIL, STORE_PUBLIC_SITE_URL } from '@/shared/config'
|
||||
import template from '../content/privacy-policy.template.txt?raw'
|
||||
import { STORE_EMAIL } from '@/shared/config'
|
||||
|
||||
const OP_NAME = 'Индивидуальный предприниматель Новоселова Наталия Владимировна'
|
||||
const OP_INN = '402900832341'
|
||||
const OP_OGRN = '305402922700051'
|
||||
const OP_ADDR = '248000, Россия, г. Калуга, ул. Никитина, д. 12А'
|
||||
const SITE_URL = window.location.origin
|
||||
|
||||
const sections = [
|
||||
{
|
||||
title: '1. Общие положения',
|
||||
items: [
|
||||
`1.1. Настоящая Политика конфиденциальности (далее — Политика) действует в отношении всех персональных данных, которые ${OP_NAME} (далее — Оператор) может получить от Пользователя во время использования сайта ${SITE_URL}.`,
|
||||
`1.2. ИНН Оператора: ${OP_INN}`,
|
||||
`1.3. ОГРН/ОГРНИП Оператора: ${OP_OGRN}`,
|
||||
`1.4. Адрес Оператора: ${OP_ADDR}`,
|
||||
`1.5. Контактный email: ${STORE_EMAIL}`,
|
||||
],
|
||||
},
|
||||
{
|
||||
title: '2. Персональные данные, которые обрабатывает Оператор',
|
||||
items: [
|
||||
'2.1. Оператор обрабатывает следующие персональные данные Пользователей:',
|
||||
'— фамилия, имя, отчество;',
|
||||
'— адрес электронной почты;',
|
||||
'— номер телефона;',
|
||||
'— данные файлов cookie;',
|
||||
'— данные о действиях на сайте (аналитика);',
|
||||
'— адрес доставки и геолокационные координаты.',
|
||||
],
|
||||
},
|
||||
{
|
||||
title: '3. Цели обработки персональных данных',
|
||||
items: [
|
||||
'3.1. Оператор обрабатывает персональные данные в следующих целях:',
|
||||
'— идентификация Пользователя;',
|
||||
'— оказание услуг / продажа товаров;',
|
||||
'— направление уведомлений и информационных сообщений;',
|
||||
'— улучшение качества работы сайта;',
|
||||
'— построение персонализированных предложений и рекомендаций.',
|
||||
],
|
||||
},
|
||||
{
|
||||
title: '4. Правовые основания обработки',
|
||||
items: [
|
||||
'4.1. Оператор обрабатывает персональные данные на основании:',
|
||||
'— Федерального закона от 27.07.2006 № 152-ФЗ «О персональных данных»;',
|
||||
'— согласия субъекта персональных данных;',
|
||||
'— договора, стороной которого является субъект;',
|
||||
'— договорных отношений с контрагентами и исполнителями услуг (доставка, платёжные сервисы).',
|
||||
],
|
||||
},
|
||||
{
|
||||
title: '5. Порядок и условия обработки',
|
||||
items: [
|
||||
'5.1. Обработка осуществляется путём сбора, записи, систематизации, накопления, хранения, уточнения, извлечения, использования, передачи, обезличивания, блокирования, удаления и уничтожения персональных данных.',
|
||||
'5.2. Обработка осуществляется автоматизированным и неавтоматизированным способами.',
|
||||
'5.3. Срок хранения персональных данных: не более 7 лет с момента последнего обращения Пользователя либо до момента отзыва согласия на обработку.',
|
||||
],
|
||||
},
|
||||
{
|
||||
title: '6. Передача персональных данных третьим лицам',
|
||||
items: [
|
||||
'6.1. Оператор может передать персональные данные третьим лицам в следующих случаях:',
|
||||
'— с согласия субъекта;',
|
||||
'— по требованию законодательства РФ;',
|
||||
'— для выполнения договорных обязательств (перечень третьих лиц): службы доставки, платёжные агрегаторы, сервисы аналитики (Яндекс.Метрика).',
|
||||
],
|
||||
},
|
||||
{
|
||||
title: '7. Права субъекта персональных данных',
|
||||
items: [
|
||||
'7.1. Пользователь имеет право на доступ к своим данным, их уточнение, блокирование или уничтожение.',
|
||||
'7.2. Для реализации своих прав Пользователь может направить запрос на электронный адрес: ' + STORE_EMAIL,
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
export function PrivacyPolicyPage() {
|
||||
const body = template.replaceAll('{{SITE_URL}}', STORE_PUBLIC_SITE_URL).replaceAll('{{STORE_EMAIL}}', STORE_EMAIL)
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Typography variant="h4" gutterBottom>
|
||||
<Box sx={{ maxWidth: 800, mx: 'auto', py: { xs: 3, md: 5 }, px: { xs: 2, md: 0 } }}>
|
||||
<Typography
|
||||
variant="h4"
|
||||
component="h1"
|
||||
gutterBottom
|
||||
sx={{ fontWeight: 700, fontSize: { xs: '1.5rem', md: '2rem' } }}
|
||||
>
|
||||
Политика конфиденциальности
|
||||
</Typography>
|
||||
<Typography color="text.secondary" sx={{ mb: 2 }}>
|
||||
|
||||
<Typography
|
||||
variant="body2"
|
||||
color="text.secondary"
|
||||
sx={{ mb: 4, pb: 3, borderBottom: '1px solid', borderColor: 'divider' }}
|
||||
>
|
||||
Политика в отношении обработки персональных данных.
|
||||
</Typography>
|
||||
<Typography component="div" variant="body2" sx={{ whiteSpace: 'pre-wrap' }}>
|
||||
{body}
|
||||
</Typography>
|
||||
|
||||
<Box component="section" sx={{ display: 'flex', flexDirection: 'column', gap: 3 }}>
|
||||
{sections.map((section) => (
|
||||
<Paper
|
||||
key={section.title}
|
||||
variant="outlined"
|
||||
sx={{
|
||||
p: { xs: 2, md: 3 },
|
||||
borderRadius: 3,
|
||||
bgcolor: 'background.paper',
|
||||
borderLeft: '4px solid',
|
||||
borderLeftColor: 'primary.main',
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
variant="h6"
|
||||
component="h2"
|
||||
gutterBottom
|
||||
sx={{ fontWeight: 600, fontSize: '1rem', color: 'primary.main' }}
|
||||
>
|
||||
{section.title}
|
||||
</Typography>
|
||||
|
||||
<Box component="ul" sx={{ m: 0, p: 0, listStyle: 'none' }}>
|
||||
{section.items.map((item, idx) => (
|
||||
<Box
|
||||
component="li"
|
||||
key={idx}
|
||||
sx={{
|
||||
color: 'text.primary',
|
||||
fontSize: '0.9rem',
|
||||
lineHeight: 1.65,
|
||||
mb: 0.5,
|
||||
'&:last-child': { mb: 0 },
|
||||
}}
|
||||
>
|
||||
{item}
|
||||
</Box>
|
||||
))}
|
||||
</Box>
|
||||
</Paper>
|
||||
))}
|
||||
</Box>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user