This commit is contained in:
Kirill
2026-05-23 18:47:35 +05:00
parent bd9bdc0352
commit d0d7eab77e
16 changed files with 889 additions and 65 deletions
+2
View File
@@ -10,6 +10,7 @@ import { Link as RouterLink } from 'react-router-dom'
import { AppHeader } from '@/app/layout/AppHeader'
import vkLogoSrc from '@/shared/assets/vk-logo.svg'
import { STORE_EMAIL, STORE_NAME, STORE_PHONE, VK_URL } from '@/shared/config'
import { CookieConsentBanner } from '@/shared/ui/CookieConsentBanner'
import { ScrollOnNavigate } from '@/shared/ui/ScrollOnNavigate'
import { ScrollToTop } from '@/shared/ui/ScrollToTop'
@@ -118,6 +119,7 @@ export function MainLayout({ children }: PropsWithChildren) {
</Box>
</Container>
</Box>
<CookieConsentBanner />
</Box>
)
}
@@ -1,10 +1,13 @@
import Button from '@mui/material/Button'
import InputAdornment from '@mui/material/InputAdornment'
import Link from '@mui/material/Link'
import Stack from '@mui/material/Stack'
import TextField from '@mui/material/TextField'
import Typography from '@mui/material/Typography'
import { useMutation } from '@tanstack/react-query'
import { Mail } from 'lucide-react'
import { useForm } from 'react-hook-form'
import { Link as RouterLink } from 'react-router-dom'
import { apiClient } from '@/shared/api/client'
import { getApiErrorMessage } from '@/shared/lib/get-api-error-message'
import { tokenSet } from '@/shared/model/auth'
@@ -95,6 +98,18 @@ export function AuthCodeForm({ onSuccess }: Props) {
sx={{ display: 'none' }}
/>
)}
<Typography variant="caption" color="text.secondary" sx={{ textAlign: 'center' }}>
Нажимая «Войти», вы принимаете{' '}
<Link component={RouterLink} to="/terms" underline="hover">
пользовательское соглашение
</Link>{' '}
и{' '}
<Link component={RouterLink} to="/privacy" underline="hover">
политику конфиденциальности
</Link>
.
</Typography>
</Stack>
)
}
@@ -1,10 +1,13 @@
import Button from '@mui/material/Button'
import InputAdornment from '@mui/material/InputAdornment'
import Link from '@mui/material/Link'
import Stack from '@mui/material/Stack'
import TextField from '@mui/material/TextField'
import Typography from '@mui/material/Typography'
import { useMutation } from '@tanstack/react-query'
import { Lock, Mail } from 'lucide-react'
import { useForm } from 'react-hook-form'
import { Link as RouterLink } from 'react-router-dom'
import { apiClient } from '@/shared/api/client'
import { getApiErrorMessage } from '@/shared/lib/get-api-error-message'
import { tokenSet } from '@/shared/model/auth'
@@ -185,6 +188,18 @@ export function AuthPasswordForm({ isRegister, onRegisterChange, onSuccess }: Pr
sx={{ display: 'none' }}
/>
)}
<Typography variant="caption" color="text.secondary" sx={{ textAlign: 'center' }}>
Нажимая «{isRegister ? 'Зарегистрироваться' : 'Войти'}», вы принимаете{' '}
<Link component={RouterLink} to="/terms" underline="hover">
пользовательское соглашение
</Link>{' '}
и{' '}
<Link component={RouterLink} to="/privacy" underline="hover">
политику конфиденциальности
</Link>
.
</Typography>
</Stack>
)
}
@@ -19,12 +19,7 @@ function ReviewItem({ rv }: { rv: PublicProductReviewItem }) {
<Paper variant="outlined" sx={{ p: 1.5, borderRadius: 2 }}>
<Stack spacing={0.75}>
<Stack direction="row" spacing={1.5} sx={{ alignItems: 'center' }}>
<UserAvatar
userId={rv.authorId}
avatarUrl={rv.authorAvatar}
avatarStyle={rv.authorAvatarStyle}
size={32}
/>
<UserAvatar userId={rv.authorId} avatarUrl={rv.authorAvatar} avatarStyle={rv.authorAvatarStyle} size={32} />
<Box sx={{ flexGrow: 1 }}>
<Typography sx={{ fontWeight: 700 }}>{rv.authorDisplay}</Typography>
</Box>
@@ -0,0 +1,97 @@
import { useState } from 'react'
import Button from '@mui/material/Button'
import Dialog from '@mui/material/Dialog'
import DialogActions from '@mui/material/DialogActions'
import DialogContent from '@mui/material/DialogContent'
import DialogContentText from '@mui/material/DialogContentText'
import DialogTitle from '@mui/material/DialogTitle'
import Divider from '@mui/material/Divider'
import Stack from '@mui/material/Stack'
import Typography from '@mui/material/Typography'
import { useMutation, useQuery } from '@tanstack/react-query'
import { useNavigate } from 'react-router-dom'
import { fetchMyOrders } from '@/entities/order'
import { apiClient } from '@/shared/api/client'
import { getApiErrorMessage } from '@/shared/lib/get-api-error-message'
import { logout, tokenSet } from '@/shared/model/auth'
const ACTIVE_STATUSES = ['DRAFT', 'PENDING_PAYMENT', 'PAID', 'IN_PROGRESS', 'SHIPPED', 'READY_FOR_PICKUP']
export function DeleteAccountSection() {
const navigate = useNavigate()
const [dialogOpen, setDialogOpen] = useState(false)
const ordersQuery = useQuery({
queryKey: ['my-orders'],
queryFn: fetchMyOrders,
staleTime: 30_000,
})
const activeOrders = ordersQuery.data?.items.filter((o) => ACTIVE_STATUSES.includes(o.status)) ?? []
const hasActiveOrders = activeOrders.length > 0
const deleteMutation = useMutation({
mutationFn: async () => {
await apiClient.delete('me')
},
onSuccess: () => {
tokenSet(null)
logout()
setDialogOpen(false)
navigate('/')
},
})
return (
<>
<Divider />
<Stack spacing={2}>
<Typography variant="h6" color="error">
Удаление аккаунта
</Typography>
<Button
variant="outlined"
color="error"
onClick={() => setDialogOpen(true)}
sx={{ alignSelf: 'start' }}
>
Удалить аккаунт
</Button>
{deleteMutation.error && (
<Typography variant="caption" color="error">
{getApiErrorMessage(deleteMutation.error) || 'Не удалось удалить аккаунт'}
</Typography>
)}
</Stack>
<Dialog open={dialogOpen} onClose={() => setDialogOpen(false)}>
<DialogTitle>Удаление аккаунта</DialogTitle>
<DialogContent>
{hasActiveOrders && (
<DialogContentText sx={{ mb: 1, fontWeight: 500 }}>
У вас есть {activeOrders.length} незавершённых заказ
{activeOrders.length === 1 ? '' : activeOrders.length < 5 ? 'а' : 'ов'}
. После удаления аккаунта отслеживание заказов станет недоступным.
</DialogContentText>
)}
<DialogContentText>
Вы уверены? Все данные будут безвозвратно удалены. Восстановить аккаунт будет невозможно.
</DialogContentText>
</DialogContent>
<DialogActions>
<Button onClick={() => setDialogOpen(false)}>Отмена</Button>
<Button
onClick={() => deleteMutation.mutate()}
variant="contained"
color="error"
disabled={deleteMutation.isPending}
>
Удалить
</Button>
</DialogActions>
</Dialog>
</>
)
}
@@ -7,6 +7,7 @@ import { useUnit } from 'effector-react'
import { $user } from '@/shared/model/auth'
import { AuthMethodsSection } from './AuthMethodsSection'
import { AvatarSection } from './AvatarSection'
import { DeleteAccountSection } from './DeleteAccountSection'
import { ProfileSection } from './ProfileSection'
export function SettingsPage() {
@@ -35,6 +36,7 @@ export function SettingsPage() {
<AuthMethodsSection />
</>
)}
<DeleteAccountSection />
</Stack>
</Box>
)
@@ -1,46 +1,48 @@
import Box from '@mui/material/Box'
import Paper from '@mui/material/Paper'
import Typography from '@mui/material/Typography'
import { STORE_EMAIL } from '@/shared/config'
import {
STORE_EMAIL,
STORE_OP_NAME,
STORE_OP_TYPE,
STORE_OP_INN,
STORE_OP_ADDR,
STORE_PUBLIC_SITE_URL,
} 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 SITE_URL = STORE_PUBLIC_SITE_URL || (typeof window !== 'undefined' ? window.location.origin : '')
const OP_FULL = `${STORE_OP_NAME} (${STORE_OP_TYPE})`
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}`,
`1.1. Настоящая Политика конфиденциальности (далее — Политика) действует в отношении всех персональных данных, которые ${OP_FULL} (далее — Оператор) может получить от Пользователя во время использования сайта ${SITE_URL}.`,
`1.2. ИНН Оператора: ${STORE_OP_INN}`,
`1.3. Адрес Оператора: ${STORE_OP_ADDR}`,
`1.4. Контактный email: ${STORE_EMAIL}`,
],
},
{
title: '2. Персональные данные, которые обрабатывает Оператор',
items: [
'2.1. Оператор обрабатывает следующие персональные данные Пользователей:',
'— фамилия, имя, отчество;',
'— адрес электронной почты;',
'— номер телефона;',
'— данные файлов cookie;',
'— данные о действиях на сайте (аналитика);',
'— адрес доставки и геолокационные координаты.',
'— имя (отображаемое имя, может быть указано Пользователем добровольно);',
'— номер телефона (указывается Пользователем добровольно при оформлении доставки);',
'— адрес доставки и геолокационные координаты (указываются Пользователем при оформлении заказа);',
'— сессионные cookie-файлы (исключительно для поддержания входа в Личный кабинет).',
],
},
{
title: '3. Цели обработки персональных данных',
items: [
'3.1. Оператор обрабатывает персональные данные в следующих целях:',
'— идентификация Пользователя;',
'— оказание услуг / продажа товаров;',
'— направление уведомлений и информационных сообщений;',
'— улучшение качества работы сайта;',
'— построение персонализированных предложений и рекомендаций.',
'— идентификация и аутентификация Пользователя;',
'— оказание услуг / продажа товаров и оформление доставки;',
'— направление транзакционных уведомлений о статусе заказов и информационных сообщений;',
'— улучшение качества работы сайта.',
],
},
{
@@ -56,9 +58,9 @@ const sections = [
{
title: '5. Порядок и условия обработки',
items: [
'5.1. Обработка осуществляется путём сбора, записи, систематизации, накопления, хранения, уточнения, извлечения, использования, передачи, обезличивания, блокирования, удаления и уничтожения персональных данных.',
'5.2. Обработка осуществляется автоматизированным и неавтоматизированным способами.',
'5.3. Срок хранения персональных данных: не более 7 лет с момента последнего обращения Пользователя либо до момента отзыва согласия на обработку.',
'5.1. Обработка осуществляется путём сбора, записи, систематизации, накопления, хранения, уточнения, извлечения, использования, передачи, блокирования, удаления и уничтожения персональных данных.',
'5.2. Обработка осуществляется автоматизированным способом с использованием программных средств Сайта.',
'5.3. Срок хранения персональных данных: до достижения целей обработки либо до момента отзыва Пользователем согласия на обработку.',
],
},
{
@@ -67,14 +69,23 @@ const sections = [
'6.1. Оператор может передать персональные данные третьим лицам в следующих случаях:',
'— с согласия субъекта;',
'— по требованию законодательства РФ;',
'— для выполнения договорных обязательств (перечень третьих лиц): службы доставки, платёжные агрегаторы, сервисы аналитики (Яндекс.Метрика).',
'— для выполнения договорных обязательств (перечень третьих лиц): службы доставки, платёжный сервис (ЮKassa).',
],
},
{
title: '7. Права субъекта персональных данных',
title: '7. Использование cookie-файлов',
items: [
'7.1. Пользователь имеет право на доступ к своим данным, их уточнение, блокирование или уничтожение.',
'7.2. Для реализации своих прав Пользователь может направить запрос на электронный адрес: ' + STORE_EMAIL,
'7.1. Оператор использует сессионные cookie-файлы исключительно для поддержания аутентификации Пользователя в Личном кабинете.',
'7.2. Сайт не использует cookie для сбора статистики, отслеживания действий Пользователя или показа рекламы.',
'7.3. Пользователь может запретить использование cookie в настройках браузера, однако это приведёт к невозможности входа в Личный кабинет.',
],
},
{
title: '8. Права субъекта персональных данных',
items: [
'8.1. Пользователь имеет право на доступ к своим данным, их уточнение или уничтожение.',
`8.2. Для реализации своих прав Пользователь может направить запрос на электронный адрес: ${STORE_EMAIL}.`,
'8.3. Пользователь вправе самостоятельно удалить свою учётную запись через Настройки Личного кабинета. При наличии активных (незавершённых) заказов система предупредит об этом перед удалением.',
],
},
]
@@ -96,7 +107,7 @@ export function PrivacyPolicyPage() {
color="text.secondary"
sx={{ mb: 4, pb: 3, borderBottom: '1px solid', borderColor: 'divider' }}
>
Политика в отношении обработки персональных данных.
Последнее обновление: 23 мая 2026 г.
</Typography>
<Box component="section" sx={{ display: 'flex', flexDirection: 'column', gap: 3 }}>
+29 -27
View File
@@ -1,14 +1,19 @@
import Box from '@mui/material/Box'
import Paper from '@mui/material/Paper'
import Typography from '@mui/material/Typography'
import { STORE_EMAIL, STORE_PHONE, STORE_PUBLIC_SITE_URL } from '@/shared/config'
import {
STORE_EMAIL,
STORE_PHONE,
STORE_PUBLIC_SITE_URL,
STORE_OP_NAME,
STORE_OP_TYPE,
STORE_OP_INN,
STORE_OP_ADDR,
} from '@/shared/config'
const SITE_URL = STORE_PUBLIC_SITE_URL || (typeof window !== 'undefined' ? window.location.origin : '')
const OP_NAME = 'Индивидуальный предприниматель Новоселова Наталия Владимировна'
const OP_INN = '402900832341'
const OP_OGRN = '305402922700051'
const OP_ADDR = '248000, Россия, г. Калуга, ул. Никитина, д. 12А'
const OP_FULL = `${STORE_OP_NAME} (${STORE_OP_TYPE})`
const sections = [
{
@@ -17,9 +22,9 @@ const sections = [
`1.1. Настоящее Пользовательское соглашение (далее — «Соглашение») определяет порядок и условия использования материалов и сервисов, размещённых в сети Интернет по адресу ${SITE_URL} (далее — «Сайт»), Пользователями данного Сайта.`,
`1.2. Использование Пользователями Сайта означает, что они безоговорочно принимают и обязуются соблюдать все условия настоящего Соглашения.`,
'1.3. В настоящем Соглашении используются следующие термины:',
`— Администратор — ${OP_NAME}, ИНН ${OP_INN}, ОГРН ${OP_OGRN}, адрес: ${OP_ADDR}, которому принадлежат все соответствующие права на Сайт.`,
`— Администратор — ${OP_FULL}, ИНН ${STORE_OP_INN}, адрес: ${STORE_OP_ADDR}, которому принадлежат все соответствующие права на Сайт.`,
`— Акцепт — полное и безоговорочное принятие условий настоящего Соглашения, размещённого на Сайте по адресу ${SITE_URL}/terms, осуществляемое путём совершения Пользователем любых действий по использованию Сайта.`,
'— Аутентификационные данные Пользователя — адрес электронной почты Пользователя и пароль (код доступа), которые в совокупности признаются простой электронной подписью Пользователя.',
'— Аутентификационные данные Пользователя — адрес электронной почты и пароль (код доступа), либо данные, полученные через сервисы авторизации третьих лиц (VK ID, Яндекс ID), либо одноразовый код, направляемый на электронную почту. Совокупность аутентификационных данных признаётся простой электронной подписью Пользователя.',
'— Пользователь — лицо, осуществляющее доступ к Сайту и использующее материалы и сервисы, размещённые на Сайте.',
'— Контент — любое информационно значимое наполнение Сайта, включая фото, текст и иные медиаматериалы.',
'— Личный кабинет — персонализированная часть Сайта, посредством которой обеспечивается обмен информацией между Пользователем и Сайтом.',
@@ -53,7 +58,7 @@ const sections = [
'3.4. Пользователь самостоятельно несёт ответственность за сохранность своих Аутентификационных данных и за все действия, совершённые с их использованием.',
'3.5. Пользователь обязан незамедлительно уведомить Администратора о любом случае несанкционированного доступа к Личному кабинету.',
'3.6. Пользователь не вправе воспроизводить, копировать, продавать и использовать в коммерческих целях Сайт или его Контент без разрешения Администратора.',
'3.7. При регистрации Пользователь даёт согласие на получение информационных и рекламных сообщений от Администратора на указанный адрес электронной почты.',
'3.7. При регистрации Пользователь даёт согласие на получение транзакционных уведомлений (статус заказа, сообщения в чате заказа, статус оплаты) на указанный адрес электронной почты.',
],
},
{
@@ -79,12 +84,13 @@ const sections = [
'5.6. Пользователь не вправе нарушать нормальную работу отдельных сервисов и Сайта в целом.',
'5.7. Пользователь обязан самостоятельно отслеживать внесение изменений в настоящее Соглашение.',
'5.8. Пользователь вправе прекратить доступ к Личному кабинету, направив уведомление Администратору.',
'5.9. Пользователь вправе самостоятельно удалить свою учётную запись через Настройки Личного кабинета.',
],
},
{
title: '6. Ограничение ответственности',
items: [
'6.1. Администратор гарантирует достоверность и полноту только той информации, которую он разместил на Сайте самостоятельно.',
'6.1. Администратор прилагает разумные усилия для обеспечения достоверности и полноты информации, размещённой на Сайте, однако не даёт явных гарантий точности такой информации.',
'6.2. Администратор не несёт ответственности за недостоверность информации, размещённой третьими лицами, в том числе Пользователями.',
'6.3. Администратор не гарантирует, что Сайт будет соответствовать требованиям Пользователя, работать непрерывно и без ошибок.',
'6.4. Администратор не несёт ответственности перед Пользователем за любые убытки, включая упущенную выгоду, потерю данных, вред деловой репутации, причинённые в связи с использованием Сайта.',
@@ -95,21 +101,17 @@ const sections = [
{
title: '7. Доступ к ресурсам третьих лиц',
items: [
'7.1. Доступ Пользователя к Сайту может вызывать обращение к интернет-ресурсам третьих лиц (реклама, сбор статистики).',
'7.2. Владельцы таких ресурсов имеют техническую возможность собирать информацию о Пользователях и самостоятельно определяют условия её использования.',
'7.1. Для обеспечения функциональности Сайта используются сервисы третьих лиц: платёжный сервис ЮKassa (для обработки онлайн-платежей), картографический сервис OpenStreetMap/Nominatim (для выбора адреса доставки).',
'7.2. Владельцы указанных ресурсов имеют собственную политику конфиденциальности и самостоятельно определяют условия обработки получаемой информации.',
'7.3. При переходе на сторонние ресурсы Пользователи самостоятельно определяют пределы использования своей информации согласно правилам соответствующих ресурсов.',
],
},
{
title: '8. Информация, хранящаяся на стороне браузера',
items: [
'8.1. Администратор использует cookie-файлы для определения уникального идентификатора доступа Пользователя к Сайту.',
'8.2. Цели использования cookie:',
'— поддержка функциональности Сайта, требующей использования cookie;',
'— измерение аудитории Сайта;',
'— определение статистических предпочтений Пользователей;',
'— исследование корреляции статистических данных.',
'8.3. Пользователь может запретить использование cookie в настройках браузера, однако это может привести к частичной или полной потере функциональности Сайта.',
'8.1. Администратор использует сессионные cookie-файлы исключительно для поддержания аутентификации Пользователя в Личном кабинете.',
'8.2. Сайт не использует cookie для сбора статистики, отслеживания действий Пользователя или показа рекламы.',
'8.3. Пользователь может запретить использование cookie в настройках браузера, однако это приведёт к невозможности входа в Личный кабинет и использования функций, требующих аутентификации.',
],
},
{
@@ -117,16 +119,16 @@ const sections = [
items: [
`9.1. Обработка персональных данных Пользователей осуществляется Администратором в соответствии с Политикой конфиденциальности, размещённой по адресу ${SITE_URL}/privacy.`,
'9.2. Передавая свои персональные данные при регистрации или заполнении форм на Сайте, Пользователь даёт согласие на их обработку Администратором.',
'9.3. Администратор обрабатывает следующие персональные данные: Ф. И. О., адрес электронной почты, номер телефона, IP-адрес, тип браузера, данные о действиях на Сайте.',
'9.4. Цели обработки персональных данных: обеспечение функционирования Сайта, оказание информационной поддержки, предоставление персонализированных сервисов, направление информационных сообщений.',
`9.5. Согласие на обработку персональных данных может быть отозвано Пользователем путём направления заявления на адрес электронной почты: ${STORE_EMAIL}.`,
'9.3. Администратор обрабатывает следующие персональные данные: адрес электронной почты, имя (при добровольном указании), номер телефона (при оформлении доставки), адрес доставки.',
'9.4. Цели обработки персональных данных: обеспечение функционирования Сайта, аутентификация Пользователя, оформление и доставка заказов, направление транзакционных уведомлений.',
`9.5. Пользователь вправе отозвать согласие на обработку персональных данных путём удаления учётной записи через Настройки Личного кабинета либо путём направления заявления на адрес электронной почты: ${STORE_EMAIL}.`,
],
},
{
title: '10. Изменение условий и расторжение соглашения',
items: [
'10.1. Соглашение может быть расторгнуто в любое время по инициативе любой из сторон. Администратор уведомляет о расторжении путём размещения информации на Сайте.',
`10.2. Пользователь может расторгнуть Соглашение, направив уведомление на адрес электронной почты: ${STORE_EMAIL}.`,
`10.2. Пользователь может расторгнуть Соглашение, удалив учётную запись через Личный кабинет, либо направив уведомление на адрес электронной почты: ${STORE_EMAIL}.`,
'10.3. Администратор вправе в одностороннем порядке изменять условия Соглашения. Новая редакция вступает в силу с момента размещения на Сайте.',
'10.4. Продолжение использования Сайта после изменения условий означает согласие Пользователя с новой редакцией. При несогласии Пользователь обязуется прекратить использование Сайта.',
],
@@ -134,10 +136,10 @@ const sections = [
{
title: '11. Информация об Администраторе',
items: [
`${OP_NAME}`,
`ИНН: ${OP_INN}`,
`ОГРН: ${OP_OGRN}`,
`Адрес: ${OP_ADDR}`,
STORE_OP_NAME,
`Статус: ${STORE_OP_TYPE}`,
`ИНН: ${STORE_OP_INN}`,
`Адрес: ${STORE_OP_ADDR}`,
`Телефон: ${STORE_PHONE}`,
`Email: ${STORE_EMAIL}`,
],
@@ -161,7 +163,7 @@ export function TermsPage() {
color="text.secondary"
sx={{ mb: 4, pb: 3, borderBottom: '1px solid', borderColor: 'divider' }}
>
Последнее обновление: 19 мая 2026 г.
Последнее обновление: 23 мая 2026 г.
</Typography>
<Box component="section" sx={{ display: 'flex', flexDirection: 'column', gap: 3 }}>
+10 -2
View File
@@ -3,16 +3,24 @@ export const apiBaseURL = import.meta.env.VITE_API_URL ?? '/api'
export const STORE_NAME = 'Любимый Креатив'
/** Канонический URL сайта для политики конфиденциальности и т.п.; в dev без env — `window.location.origin`. */
/** Канонический URL сайта для юридических текстов (политика конфиденциальности и т.п.). */
export const STORE_PUBLIC_SITE_URL = (() => {
const raw =
typeof import.meta.env.VITE_PUBLIC_SITE_URL === 'string' ? import.meta.env.VITE_PUBLIC_SITE_URL.trim() : ''
if (raw) return raw.replace(/\/$/, '')
if (typeof window !== 'undefined') return window.location.origin
return ''
return 'https://любимыйкреатив.рф'
})()
/** Демо-контакты для футера; при необходимости задайте через VITE_* в `.env`. */
export const STORE_EMAIL = import.meta.env.VITE_STORE_EMAIL ?? 'larisa8502@yandex.ru'
export const STORE_PHONE = import.meta.env.VITE_STORE_PHONE ?? '+7 (952) 318-16-24'
export const VK_URL = import.meta.env.VITE_VK_URL ?? 'https://vk.com/club158395871'
/** Данные оператора для юридических документов. */
export const STORE_OP_NAME = 'Комарова Лариса Николаевна'
export const STORE_OP_TYPE = 'самозанятый'
/** Тестовый ИНН — заменить на реальный. */
export const STORE_OP_INN = '402900832341'
export const STORE_OP_ADDR =
'618909, Россия, Пермский край, Лысьвенский муниципальный округ, Лысьва, улица Мира, 34, кв. 24'
+1 -1
View File
@@ -3,4 +3,4 @@ export const PICKUP_COORDINATES = { lat: 58.09898000206914, lng: 57.813169680997
/** Полная строка адреса для текстовых блоков. */
export const PICKUP_ADDRESS_FULL =
'34, улица Мира, Лысьва, Лысьвенский муниципальный округ, Пермский край, Приволжский федеральный округ, 618909, Россия'
'618909, Россия, Пермский край, Лысьвенский муниципальный округ, Лысьва, улица Мира, 34'
@@ -0,0 +1,71 @@
import { useState } from 'react'
import Box from '@mui/material/Box'
import Button from '@mui/material/Button'
import Link from '@mui/material/Link'
import Typography from '@mui/material/Typography'
import { Link as RouterLink } from 'react-router-dom'
const STORAGE_KEY = 'cookie-consent-accepted'
function wasAccepted(): boolean {
try {
return localStorage.getItem(STORAGE_KEY) === '1'
} catch {
return false
}
}
function markAccepted() {
try {
localStorage.setItem(STORAGE_KEY, '1')
} catch {
// ignore
}
}
export function CookieConsentBanner() {
const [visible, setVisible] = useState(!wasAccepted())
if (!visible) return null
return (
<Box
sx={{
position: 'fixed',
bottom: 0,
left: 0,
right: 0,
zIndex: 1300,
bgcolor: 'background.paper',
borderTop: 1,
borderColor: 'divider',
px: 2,
py: 1.5,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
gap: 2,
flexWrap: 'wrap',
boxShadow: 3,
}}
>
<Typography variant="body2" color="text.secondary">
Мы используем cookie для поддержания сессии. Продолжая использовать сайт, вы принимаете{' '}
<Link component={RouterLink} to="/privacy" underline="hover">
Политику обработки персональных данных
</Link>
.
</Typography>
<Button
variant="contained"
size="small"
onClick={() => {
markAccepted()
setVisible(false)
}}
>
Понятно
</Button>
</Box>
)
}