refactor
This commit is contained in:
161
dist/assets/index-BVKNc3Mv.js
vendored
Normal file
161
dist/assets/index-BVKNc3Mv.js
vendored
Normal file
File diff suppressed because one or more lines are too long
161
dist/assets/index-BYdWOI3m.js
vendored
161
dist/assets/index-BYdWOI3m.js
vendored
File diff suppressed because one or more lines are too long
1
dist/assets/index-BtHpZOfG.css
vendored
1
dist/assets/index-BtHpZOfG.css
vendored
File diff suppressed because one or more lines are too long
1
dist/assets/index-pOqaElwW.css
vendored
Normal file
1
dist/assets/index-pOqaElwW.css
vendored
Normal file
File diff suppressed because one or more lines are too long
4
dist/index.html
vendored
4
dist/index.html
vendored
@@ -5,8 +5,8 @@
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>ЭКСА — Ваш мост в мир цифровых активов</title>
|
||||
<script type="module" crossorigin src="/assets/index-BYdWOI3m.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-BtHpZOfG.css">
|
||||
<script type="module" crossorigin src="/assets/index-BVKNc3Mv.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-pOqaElwW.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
|
||||
18
src/app/providers/ROUTES_REFACTOR.md
Normal file
18
src/app/providers/ROUTES_REFACTOR.md
Normal file
@@ -0,0 +1,18 @@
|
||||
# Рефакторинг: чек-лист
|
||||
|
||||
`[x]` — отрефакторено.
|
||||
|
||||
## Страницы
|
||||
|
||||
- [x] **LegalConverterPage** — вынести бизнес-логику (расчёт комиссии, упаковку имени/контакта в `comment` и парсинг) из страницы в `features`/`model`. → чистая логика в `features/b2b/lib/purchaseRequest.ts`, форма-хук в `widgets/legal-converter/model`, тонкий UI `LegalConverterSection`. Док: `pages/converter/README.md`.
|
||||
- [x] **Юридические страницы** (`publichnaya-oferta`, `politika-personalnyh-dannyh`, `politika-cookie`, `soglasie-personalnyh-dannyh`, `reestr-pd-rkn`) — статичный текст вынести в данные, рендерить общим шаблоном. → текст в `pages/<slug>/model/content.ts` (`LegalDoc`-объекты), единый шаблон+стиль `entities/legal-doc` (`LegalDoc`), 5 старых `*.module.css` удалены. Док: `entities/legal-doc/README.md`.
|
||||
|
||||
## Виджеты
|
||||
|
||||
- [ ] **swap-form** / **bridge-form** — бизнес-логику (quote-payload, мутации, инвалидация кэша) вынести в `model`-хуки.
|
||||
- [ ] **bridge-form** / **token-table** — убрать межвиджетные импорты, общие части опустить в `features`/`entities`/`shared`.
|
||||
- [ ] **Метаданные сетей** (`CHAIN_ID`, `RELAY_CHAIN_ID`, `NATIVE_ADDR`, `CHAIN_META`, `TICKER_TO_CHAIN`) — свести к единому источнику в `entities`/`shared`.
|
||||
- [ ] **Утилиты** (`formatUsd`, `toBaseUnits`, разбор ошибок) — убрать локальные дубли, использовать `shared/lib/utils`.
|
||||
- [ ] **send-modal** — логику (base-units, разбор ошибок) в `model`; инлайн-SVG и дропдауны в отдельные компоненты.
|
||||
- [ ] **staking/StakeCard**, **pools/LpDepositCard** — финансовые расчёты вынести из UI в `model`-хук.
|
||||
- [ ] **token-table** — разбить `renderRow`/`renderCard` на компоненты, убрать локальный `formatUsd` и инлайн-SVG.
|
||||
109
src/entities/legal-doc/README.md
Normal file
109
src/entities/legal-doc/README.md
Normal file
@@ -0,0 +1,109 @@
|
||||
# Юридические страницы (`entities/legal-doc`)
|
||||
|
||||
Общий механизм для **пяти статичных юридических страниц**. Раньше каждая была
|
||||
самостоятельным `.tsx` с захардкоженной разметкой (200–300 строк JSX) и почти
|
||||
идентичным собственным `.module.css`. После рефакторинга весь текст вынесен в
|
||||
**данные** (`LegalDoc`-объекты), а рендерит их **один шаблон** `LegalDoc` с единым
|
||||
стилем. Результат пункта чек-листа `app/providers/ROUTES_REFACTOR.md` → «Юридические
|
||||
страницы … статичный текст вынести в данные, рендерить общим шаблоном».
|
||||
|
||||
## Какие страницы
|
||||
|
||||
| Маршрут (`ROUTES`) | Страница (`pages/*`) | Данные | h1 |
|
||||
|---|---|---|---|
|
||||
| `/publichnaya-oferta` | `publichnaya-oferta` | `model/content.ts → publichnayaOfertaDoc` | ПУБЛИЧНЫЙ ДОГОВОР ОФЕРТЫ |
|
||||
| `/politika-personalnyh-dannyh` | `politika-personalnyh-dannyh` | `politikaPersonalnyhDannyhDoc` | ПОЛИТИКА ОБРАБОТКИ ПД |
|
||||
| `/politika-cookie` | `politika-cookie` | `politikaCookieDoc` | ПОЛИТИКА COOKIE |
|
||||
| `/soglasie-personalnyh-dannyh` | `soglasie-personalnyh-dannyh` | `soglasieDoc` | СОГЛАСИЕ НА ОБРАБОТКУ ПД |
|
||||
| `/reestr-pd-rkn` | `reestr-pd-rkn` | `reestrDoc` | Реестр операторов ПД |
|
||||
|
||||
Все пять — **публичные** (вне `ProtectedRoute`/`GuestRoute`), подключены в
|
||||
`app/providers/RouterProvider.tsx`.
|
||||
|
||||
## Архитектура (FSD: `pages → entities`)
|
||||
|
||||
```
|
||||
entities/legal-doc/ ← общий, переиспользуемый механизм (без контента)
|
||||
model/types.ts ← типы LegalDoc / LegalSection / LegalBlock
|
||||
ui/LegalDoc.tsx ← единый рендерер (main→container→title→sections→blocks)
|
||||
ui/LegalDoc.module.css ← единый стиль (слит из 5 старых модулей)
|
||||
index.ts ← export { LegalDoc }, типы (LegalDocData = тип данных)
|
||||
README.md ← этот файл
|
||||
|
||||
pages/<slug>/ ← каждая страница = контент + тонкая обёртка
|
||||
model/content.ts ← <slug>Doc: LegalDocData — ВЕСЬ текст страницы
|
||||
ui/<Name>Page.tsx ← <Header/> <LegalDoc doc={<slug>Doc}/> <Footer/>
|
||||
index.ts ← export { <Name>Page } (не менялся)
|
||||
```
|
||||
|
||||
> **Почему `entities`, а не `widgets`.** `Header`/`Footer` — это виджеты. Если бы шаблон
|
||||
> рендерил их сам, он стал бы виджетом, импортирующим другие виджеты (межвиджетный импорт,
|
||||
> который другой пункт чек-листа просит как раз убирать). Поэтому `LegalDoc` рендерит
|
||||
> **только тело** документа (`<main>…`), а `Header`/`Footer` добавляет страница-обёртка.
|
||||
> Слой `entities` ниже `widgets` — нарушения порядка слоёв нет. Структура повторяет
|
||||
> соседний `entities/commission` (`model/` + `ui/` + barrel).
|
||||
|
||||
## Модель данных (`model/types.ts`)
|
||||
|
||||
```ts
|
||||
LegalDoc = { title: string; subtitle?: string; sections: LegalSection[] }
|
||||
LegalSection = { title?: string; blocks: LegalBlock[] } // title → h3; нет title → секция без заголовка
|
||||
```
|
||||
|
||||
`LegalBlock` — размеченный union (поле `kind`). Порядок блоков в массиве = порядок на
|
||||
экране (подзаголовки и абзацы чередуются как в исходнике).
|
||||
|
||||
| `kind` | Поля | Рендер | Где используется |
|
||||
|---|---|---|---|
|
||||
| `text` | `text`, `variant?` | `<p class=text>` | везде |
|
||||
| `subheading` | `text` | `<h4 class=subheading>` (был `subSectionTitle`) | политики, согласие, cookie |
|
||||
| `list` | `items: LegalListItem[]` | `<ul class=list>` | везде |
|
||||
| `definitions` | `items: {term,text}[]` | боксы с левым акцентом | оферта, политика ПД |
|
||||
| `goals` | `items: {title,items[]}[]` | боксы «заголовок + вложенный список» | политика ПД (раздел «Цели сбора») |
|
||||
| `infoBox` | `lines: LegalListItem[]` | бокс из `<p>`-строк | реквизиты, контакты, данные оператора |
|
||||
| `link` | `href`, `text` | центрированная кнопка-ссылка (`target=_blank`) | только реестр РКН |
|
||||
|
||||
`variant` у `text`: `'example'` (курсивный акцент-бокс), `'warning'` (жёлтый бокс),
|
||||
`'emphasis'` (полужирный абзац, был `.confirmation`). Без `variant` — обычный абзац.
|
||||
|
||||
`LegalListItem` = либо строка, либо `{ strong, text? }` (жирный зачин + остаток).
|
||||
Используется в `list` и в `infoBox`.
|
||||
|
||||
### Важный нюанс: разделитель (`—`, `–`, `:`) хранится в данных
|
||||
|
||||
Рендерер `{strong,text}` выводит как `<strong>{strong}</strong> {text}` — **без** своего
|
||||
разделителя. Поэтому тире/двоеточие пишется прямо в данных:
|
||||
- cookie: `{ strong: 'Добровольность', text: '— согласие дается…' }` → «**Добровольность** — …»
|
||||
- реестр: `{ strong: 'ИНН:', text: '9810001062' }` → «**ИНН:** 9810001062»
|
||||
|
||||
Аналогично у `definitions` тире входит в `text` (`{ term: 'Агент', text: '– юридическое лицо…' }`),
|
||||
т.к. в оферте `–` (en-dash), а в политике ПД `—` (em-dash) — единый разделитель в коде
|
||||
исказил бы исходник.
|
||||
|
||||
## Что изменилось визуально (унификация стилей)
|
||||
|
||||
Пять старых `.module.css` были на 90% одинаковы. При слиянии разнобойные боксы
|
||||
`requisites` / `info` / `contacts` / `operatorInfo` сведены к одному классу `.infoBox`
|
||||
(фон-стекло + левый акцент). Единственное реальное изменение: у **реквизитов оферты**
|
||||
раньше была сплошная рамка, теперь — левый акцент, как у остальных боксов.
|
||||
|
||||
> Сохранён как было (намеренно, чтобы рефакторинг не менял поведение): `text variant=warning`
|
||||
> — светло-жёлтый фон `#fff3cd` с белым текстом (низкий контраст). Это исторический баг
|
||||
> исходного cookie-CSS; чинить — отдельной задачей, не в рамках этого пункта.
|
||||
|
||||
## Как добавить / отредактировать страницу
|
||||
|
||||
- **Поправить текст** — только `pages/<slug>/model/content.ts`. Шаблон и стиль не трогать.
|
||||
- **Новая юридическая страница** — добавить `ROUTES`, `<Route>` в `RouterProvider`,
|
||||
папку `pages/<slug>` с `model/content.ts` (объект `LegalDocData`) и тонкой обёрткой
|
||||
`<Header/> <LegalDoc doc={…}/> <Footer/>`. Новых CSS-файлов не нужно.
|
||||
- **Новый тип блока** — добавить вариант в `LegalBlock` (`model/types.ts`), ветку в
|
||||
`Block` (`ui/LegalDoc.tsx`) и стиль в `ui/LegalDoc.module.css`.
|
||||
|
||||
## Проверка
|
||||
|
||||
```bash
|
||||
npm run build # tsc -b + vite build (тестов в проекте нет)
|
||||
npm run lint
|
||||
npm run dev # ручная проверка пяти /publichnaya-oferta, /politika-*, /soglasie-*, /reestr-pd-rkn
|
||||
```
|
||||
2
src/entities/legal-doc/index.ts
Normal file
2
src/entities/legal-doc/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export { LegalDoc } from './ui/LegalDoc'
|
||||
export type { LegalDoc as LegalDocData, LegalSection, LegalBlock, LegalListItem } from './model/types'
|
||||
38
src/entities/legal-doc/model/types.ts
Normal file
38
src/entities/legal-doc/model/types.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
/**
|
||||
* Модель данных для юридических страниц (оферта, политики, согласие, реестр).
|
||||
* Весь статичный текст описывается этими типами и рендерится одним шаблоном
|
||||
* `LegalDoc` (см. `../ui/LegalDoc.tsx`). README — в корне `entities/legal-doc`.
|
||||
*/
|
||||
|
||||
/** Элемент списка: простая строка или «жирный зачин + остаток». */
|
||||
export type LegalListItem = string | { strong: string; text?: string }
|
||||
|
||||
/** Блок контента внутри секции. Порядок блоков в секции сохраняется как есть. */
|
||||
export type LegalBlock =
|
||||
/** Абзац. variant управляет оформлением (пример / предупреждение / акцент). */
|
||||
| { kind: 'text'; text: string; variant?: 'example' | 'warning' | 'emphasis' }
|
||||
/** Подзаголовок внутри секции (был `h4.subSectionTitle`). */
|
||||
| { kind: 'subheading'; text: string }
|
||||
/** Маркированный список. */
|
||||
| { kind: 'list'; items: LegalListItem[] }
|
||||
/** Список определений (термин + расшифровка) в виде боксов. */
|
||||
| { kind: 'definitions'; items: { term: string; text: string }[] }
|
||||
/** Список целей: заголовок + вложенный список — в виде боксов. */
|
||||
| { kind: 'goals'; items: { title: string; items: string[] }[] }
|
||||
/** Информационный бокс со строками (реквизиты, контакты, данные оператора). */
|
||||
| { kind: 'infoBox'; lines: LegalListItem[] }
|
||||
/** Кнопка-ссылка (внешний ресурс), по центру в отдельном блоке. */
|
||||
| { kind: 'link'; href: string; text: string }
|
||||
|
||||
/** Секция документа: необязательный заголовок (h3) + блоки контента. */
|
||||
export interface LegalSection {
|
||||
title?: string
|
||||
blocks: LegalBlock[]
|
||||
}
|
||||
|
||||
/** Документ целиком: заголовок (h1), необязательный подзаголовок (h2), секции. */
|
||||
export interface LegalDoc {
|
||||
title: string
|
||||
subtitle?: string
|
||||
sections: LegalSection[]
|
||||
}
|
||||
@@ -1,3 +1,6 @@
|
||||
/* Единый стиль юридических страниц. Слит из пяти почти одинаковых *.module.css.
|
||||
Боксы info/contacts/operatorInfo/requisites унифицированы в .infoBox. */
|
||||
|
||||
.main {
|
||||
padding: 40px 20px;
|
||||
max-width: 1200px;
|
||||
@@ -39,7 +42,7 @@
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
.subSectionTitle {
|
||||
.subheading {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
margin-top: 20px;
|
||||
@@ -47,10 +50,58 @@
|
||||
color: var(--text-primary, #ffffff);
|
||||
}
|
||||
|
||||
.text {
|
||||
line-height: 1.7;
|
||||
color: var(--text-primary, #ffffff);
|
||||
margin: 0 0 12px;
|
||||
}
|
||||
|
||||
/* Вариант абзаца: пример (курсивный акцент-бокс). */
|
||||
.example {
|
||||
padding: 10px 15px;
|
||||
background: var(--glass-bg, rgba(255, 255, 255, 0.04));
|
||||
border-left: 3px solid var(--interactive, #4a6dff);
|
||||
border-radius: 4px;
|
||||
font-style: italic;
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
/* Вариант абзаца: предупреждение.
|
||||
NB: исторический контраст (светлый фон + белый текст) сохранён как было. */
|
||||
.warning {
|
||||
padding: 15px;
|
||||
background: #fff3cd;
|
||||
border-left: 4px solid #ffc107;
|
||||
border-radius: 4px;
|
||||
margin: 15px 0;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* Вариант абзаца: акцент (был `.confirmation`). */
|
||||
.emphasis {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.list {
|
||||
list-style: disc;
|
||||
margin: 0 0 12px 20px;
|
||||
line-height: 1.8;
|
||||
color: var(--text-primary, #ffffff);
|
||||
}
|
||||
|
||||
.list li {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.list strong {
|
||||
color: var(--text-primary, #ffffff);
|
||||
}
|
||||
|
||||
.definitions {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 15px;
|
||||
margin: 0 0 12px;
|
||||
}
|
||||
|
||||
.definition {
|
||||
@@ -61,21 +112,11 @@
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.list {
|
||||
list-style: disc;
|
||||
margin-left: 20px;
|
||||
line-height: 1.8;
|
||||
color: var(--text-primary, #ffffff);
|
||||
}
|
||||
|
||||
.list li {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.goalsList {
|
||||
.goals {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
margin: 0 0 12px;
|
||||
}
|
||||
|
||||
.goal {
|
||||
@@ -102,15 +143,49 @@
|
||||
color: var(--text-primary, #ffffff);
|
||||
}
|
||||
|
||||
.contacts {
|
||||
.infoBox {
|
||||
padding: 20px;
|
||||
background: var(--glass-bg, rgba(255, 255, 255, 0.04));
|
||||
border-left: 4px solid var(--interactive, #4a6dff);
|
||||
border-radius: 4px;
|
||||
border: 1px solid var(--glass-border, rgba(255, 255, 255, 0.08));
|
||||
margin: 15px 0;
|
||||
line-height: 1.8;
|
||||
}
|
||||
|
||||
.infoBox p {
|
||||
margin: 8px 0;
|
||||
color: var(--text-primary, #ffffff);
|
||||
}
|
||||
|
||||
.infoBox strong {
|
||||
color: var(--text-primary, #ffffff);
|
||||
}
|
||||
|
||||
.linkBlock {
|
||||
text-align: center;
|
||||
padding: 30px;
|
||||
background: var(--glass-bg, rgba(255, 255, 255, 0.04));
|
||||
border-radius: 8px;
|
||||
margin: 30px 0;
|
||||
}
|
||||
|
||||
.button {
|
||||
display: inline-block;
|
||||
padding: 15px 40px;
|
||||
background: var(--interactive, #4a6dff);
|
||||
color: #fff;
|
||||
text-decoration: none;
|
||||
border-radius: 6px;
|
||||
font-weight: 600;
|
||||
transition: all 0.3s ease;
|
||||
border: 2px solid var(--interactive, #4a6dff);
|
||||
}
|
||||
|
||||
.button:hover {
|
||||
background: transparent;
|
||||
color: var(--interactive, #4a6dff);
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.container {
|
||||
padding: 20px;
|
||||
@@ -128,11 +203,24 @@
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.subSectionTitle {
|
||||
.subheading {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.list {
|
||||
margin-left: 15px;
|
||||
}
|
||||
|
||||
.infoBox {
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.linkBlock {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.button {
|
||||
padding: 12px 30px;
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
101
src/entities/legal-doc/ui/LegalDoc.tsx
Normal file
101
src/entities/legal-doc/ui/LegalDoc.tsx
Normal file
@@ -0,0 +1,101 @@
|
||||
import type { ReactNode } from 'react'
|
||||
import type { LegalBlock, LegalDoc as LegalDocData, LegalListItem } from '../model/types'
|
||||
import styles from './LegalDoc.module.css'
|
||||
|
||||
/** «Жирный зачин + остаток» либо простая строка. Разделитель (—, :) пишется в text. */
|
||||
function renderItem(item: LegalListItem): ReactNode {
|
||||
if (typeof item === 'string') return item
|
||||
return (
|
||||
<>
|
||||
<strong>{item.strong}</strong>
|
||||
{item.text ? ` ${item.text}` : null}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
function Block({ block }: { block: LegalBlock }) {
|
||||
switch (block.kind) {
|
||||
case 'text':
|
||||
return (
|
||||
<p className={block.variant ? `${styles.text} ${styles[block.variant]}` : styles.text}>
|
||||
{block.text}
|
||||
</p>
|
||||
)
|
||||
case 'subheading':
|
||||
return <h4 className={styles.subheading}>{block.text}</h4>
|
||||
case 'list':
|
||||
return (
|
||||
<ul className={styles.list}>
|
||||
{block.items.map((item, i) => (
|
||||
<li key={i}>{renderItem(item)}</li>
|
||||
))}
|
||||
</ul>
|
||||
)
|
||||
case 'definitions':
|
||||
return (
|
||||
<div className={styles.definitions}>
|
||||
{block.items.map((d, i) => (
|
||||
<div key={i} className={styles.definition}>
|
||||
<strong>{d.term}</strong> {d.text}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
case 'goals':
|
||||
return (
|
||||
<div className={styles.goals}>
|
||||
{block.items.map((g, i) => (
|
||||
<div key={i} className={styles.goal}>
|
||||
<strong>{g.title}</strong>
|
||||
<ul>
|
||||
{g.items.map((t, j) => (
|
||||
<li key={j}>{t}</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
case 'infoBox':
|
||||
return (
|
||||
<div className={styles.infoBox}>
|
||||
{block.lines.map((line, i) => (
|
||||
<p key={i}>{renderItem(line)}</p>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
case 'link':
|
||||
return (
|
||||
<div className={styles.linkBlock}>
|
||||
<a href={block.href} target="_blank" rel="noopener noreferrer" className={styles.button}>
|
||||
{block.text}
|
||||
</a>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Единый шаблон юридической страницы. Получает данные документа (`LegalDoc`) и
|
||||
* рендерит `main → container → title/subtitle → sections → blocks`.
|
||||
* Header/Footer добавляет страница-обёртка, не этот компонент (entities < widgets).
|
||||
*/
|
||||
export function LegalDoc({ doc }: { doc: LegalDocData }) {
|
||||
return (
|
||||
<main className={styles.main}>
|
||||
<div className={styles.container}>
|
||||
<h1 className={styles.title}>{doc.title}</h1>
|
||||
{doc.subtitle && <h2 className={styles.subtitle}>{doc.subtitle}</h2>}
|
||||
|
||||
{doc.sections.map((section, i) => (
|
||||
<section key={i} className={styles.section}>
|
||||
{section.title && <h3 className={styles.sectionTitle}>{section.title}</h3>}
|
||||
{section.blocks.map((block, j) => (
|
||||
<Block key={j} block={block} />
|
||||
))}
|
||||
</section>
|
||||
))}
|
||||
</div>
|
||||
</main>
|
||||
)
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
export { registrationStart, registrationComplete, loginStart, loginComplete } from './api/registrationApi'
|
||||
export { getMe, uploadAvatar, updatePhone } from './api/profileApi'
|
||||
export type { MeResponse, UploadAvatarPayload } from './api/profileApi'
|
||||
export { getFullName, getDisplayName, isLegalEntity } from './lib/profile'
|
||||
export { useMe } from './hooks/useMe'
|
||||
export { useUploadAvatar } from './hooks/useUploadAvatar'
|
||||
export { useUpdatePhone } from './hooks/useUpdatePhone'
|
||||
|
||||
24
src/features/auth/lib/profile.ts
Normal file
24
src/features/auth/lib/profile.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import type { MeResponse } from '../api/profileApi'
|
||||
|
||||
const capitalize = (s: string | null) =>
|
||||
s ? s[0].toUpperCase() + s.slice(1).toLowerCase() : ''
|
||||
|
||||
// True for any non-individual account (legal entity). Organization data then
|
||||
// lives in the nested `legal_entity` object.
|
||||
export function isLegalEntity(data: MeResponse): boolean {
|
||||
return data.account_type !== 'individual'
|
||||
}
|
||||
|
||||
// "Фамилия Имя Отчество" with each part capitalized; empty parts are dropped.
|
||||
export function getFullName(data: MeResponse): string {
|
||||
return [data.last_name, data.first_name, data.middle_name]
|
||||
.filter(Boolean)
|
||||
.map(capitalize)
|
||||
.join(' ')
|
||||
}
|
||||
|
||||
// Name shown in the profile header: organization name for legal accounts,
|
||||
// full person name otherwise.
|
||||
export function getDisplayName(data: MeResponse): string {
|
||||
return isLegalEntity(data) ? (data.legal_entity?.name ?? '') : getFullName(data)
|
||||
}
|
||||
@@ -10,3 +10,15 @@ export type {
|
||||
OrgWalletBalance,
|
||||
OrgBalancesResponse,
|
||||
} from './api/b2bApi'
|
||||
export {
|
||||
MIN_ORDER,
|
||||
TERM_OPTIONS,
|
||||
formatRub,
|
||||
dayLabel,
|
||||
termRate,
|
||||
calcQuote,
|
||||
buildPurchaseComment,
|
||||
parseContactFromComment,
|
||||
latestPurchaseRequest,
|
||||
} from './lib/purchaseRequest'
|
||||
export type { Quote, PurchaseCommentParts } from './lib/purchaseRequest'
|
||||
|
||||
100
src/features/b2b/lib/purchaseRequest.ts
Normal file
100
src/features/b2b/lib/purchaseRequest.ts
Normal file
@@ -0,0 +1,100 @@
|
||||
import type { B2bPurchaseRequest } from '../api/b2bApi'
|
||||
|
||||
// Минимальный объём заявки, ₽.
|
||||
export const MIN_ORDER = 500_000
|
||||
|
||||
// Чем дольше клиент готов ждать исполнения, тем ниже ставка комиссии сервиса.
|
||||
// days — срок ожидания операции, rate — доля комиссии (0.05 = 5 %).
|
||||
export const TERM_OPTIONS = [
|
||||
{ days: 3, rate: 0.05 },
|
||||
{ days: 4, rate: 0.04636 },
|
||||
{ days: 5, rate: 0.04273 },
|
||||
{ days: 6, rate: 0.03909 },
|
||||
{ days: 7, rate: 0.03545 },
|
||||
{ days: 8, rate: 0.03182 },
|
||||
{ days: 9, rate: 0.02818 },
|
||||
{ days: 10, rate: 0.02455 },
|
||||
{ days: 11, rate: 0.02091 },
|
||||
{ days: 12, rate: 0.01727 },
|
||||
{ days: 13, rate: 0.01364 },
|
||||
{ days: 14, rate: 0.01 },
|
||||
] as const
|
||||
|
||||
// Целое число с разделителями групп в русской локали: 500000 → "500 000".
|
||||
export const formatRub = (n: number) =>
|
||||
n.toLocaleString('ru-RU', { maximumFractionDigits: 0 })
|
||||
|
||||
// Русское склонение слова «день» для срока ожидания.
|
||||
export function dayLabel(days: number): string {
|
||||
const mod10 = days % 10
|
||||
const mod100 = days % 100
|
||||
if (mod10 === 1 && mod100 !== 11) return `${days} день`
|
||||
if (mod10 >= 2 && mod10 <= 4 && (mod100 < 10 || mod100 >= 20)) return `${days} дня`
|
||||
return `${days} дней`
|
||||
}
|
||||
|
||||
// Ставка комиссии (доля) для выбранного срока. Фоллбэк — ставка первого срока.
|
||||
export function termRate(days: number): number {
|
||||
return TERM_OPTIONS.find((o) => o.days === days)?.rate ?? TERM_OPTIONS[0].rate
|
||||
}
|
||||
|
||||
export interface Quote {
|
||||
rate: number // доля комиссии (0.05 = 5 %)
|
||||
commission: number // сумма комиссии, ₽
|
||||
total: number // сумма к оплате, ₽
|
||||
}
|
||||
|
||||
// Расчёт комиссии и итога по объёму заявки и сроку ожидания.
|
||||
export function calcQuote(amount: number, days: number): Quote {
|
||||
const rate = termRate(days)
|
||||
const commission = amount * rate
|
||||
return { rate, commission, total: amount + commission }
|
||||
}
|
||||
|
||||
export interface PurchaseCommentParts {
|
||||
name: string
|
||||
contact: string
|
||||
days: number
|
||||
amount: number
|
||||
commission: number
|
||||
total: number
|
||||
}
|
||||
|
||||
// У заявки нет отдельных полей под имя/контакт/срок/суммы в ₽ — всё, что нужно
|
||||
// менеджеру, упаковывается в человекочитаемый comment. Обратная операция —
|
||||
// parseContactFromComment.
|
||||
export function buildPurchaseComment(parts: PurchaseCommentParts): string {
|
||||
const { name, contact, days, amount, commission, total } = parts
|
||||
return [
|
||||
name && `Имя: ${name}`,
|
||||
contact && `Контакт: ${contact}`,
|
||||
`Срок ожидания: ${dayLabel(days)}`,
|
||||
`Сумма: ${formatRub(amount)} ₽`,
|
||||
`Комиссия: ≈${formatRub(commission)} ₽`,
|
||||
`Итого: ≈${formatRub(total)} ₽`,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join('; ')
|
||||
}
|
||||
|
||||
// Достаёт имя и контакт из comment последней заявки для автозаполнения формы.
|
||||
export function parseContactFromComment(comment: string | null): {
|
||||
name: string
|
||||
contact: string
|
||||
} {
|
||||
return {
|
||||
name: comment?.match(/Имя:\s*([^;]+)/)?.[1]?.trim() ?? '',
|
||||
contact: comment?.match(/Контакт:\s*([^;]+)/)?.[1]?.trim() ?? '',
|
||||
}
|
||||
}
|
||||
|
||||
// Последняя по времени создания заявка на аккаунте, либо null.
|
||||
export function latestPurchaseRequest(
|
||||
items: B2bPurchaseRequest[],
|
||||
): B2bPurchaseRequest | null {
|
||||
return items.reduce<B2bPurchaseRequest | null>(
|
||||
(latest, r) =>
|
||||
!latest || (r.created_at ?? '') > (latest.created_at ?? '') ? r : latest,
|
||||
null,
|
||||
)
|
||||
}
|
||||
154
src/pages/converter/README.md
Normal file
154
src/pages/converter/README.md
Normal file
@@ -0,0 +1,154 @@
|
||||
# Страница конвертера (`/converter`)
|
||||
|
||||
Покупка/конвертация валюты. Маршрут `ROUTES.CONVERTER` (`/converter`), **защищён**
|
||||
`ProtectedRoute` (только для авторизованных) и обёрнут в `WalletLayout footer center`
|
||||
(см. `app/providers/RouterProvider.tsx`).
|
||||
|
||||
## Главная идея
|
||||
|
||||
`ConverterPage` — **тонкий роутер по типу аккаунта**. Сам по себе он почти ничего не
|
||||
рендерит: показывает заглушку / спиннер и выбирает один из двух виджетов-конвертеров.
|
||||
Вся логика и презентация живут в виджетах и нижних слоях (`features/*`, `shared`).
|
||||
Это результат рефакторинга под FSD (`app → pages → widgets → features → entities →
|
||||
shared`; нижние слои не импортируют верхние).
|
||||
|
||||
```
|
||||
ConverterPage
|
||||
├─ PURCHASE_ENABLED === false → заглушка «Покупка временно недоступна»
|
||||
├─ useMe() ещё грузится → <Spinner/>
|
||||
└─ по account_type:
|
||||
├─ legal (юрлицо) → <LegalConverterSection/> (@widgets/legal-converter)
|
||||
└─ individual (физлицо)→ <ConverterSection/> (@widgets/converter-page)
|
||||
```
|
||||
|
||||
> ⚠️ Сейчас `PURCHASE_ENABLED = false` в `ConverterPage.tsx` — оба конвертера
|
||||
> скрыты заглушкой. Поставьте `true`, чтобы вернуть рабочий контент.
|
||||
|
||||
Развилка по аккаунту: `isLegal = data.account_type !== 'individual'`. Тот же критерий,
|
||||
что `isLegalEntity()` в `features/auth/lib/profile.ts` (профиль). Здесь оставлен инлайн,
|
||||
т.к. `ConverterPage` не импортирует профильные хелперы; при желании можно свести к
|
||||
одному источнику.
|
||||
|
||||
## Карта файлов
|
||||
|
||||
```
|
||||
pages/converter/
|
||||
ui/ConverterPage.tsx ← роутер по account_type (заглушка/спиннер/виджет)
|
||||
ui/ConverterPage.module.css ← стили только заглушки
|
||||
index.ts ← export { ConverterPage }
|
||||
README.md ← этот файл
|
||||
|
||||
widgets/legal-converter/ ← ВЕТКА ЮРЛИЦ: форма B2B-заявки
|
||||
model/useLegalConverterForm.ts← вся логика формы (стейт, расчёт, submit)
|
||||
ui/LegalConverterForm.tsx ← разметка (LegalConverterSection), без логики
|
||||
ui/LegalConverterForm.module.css
|
||||
index.ts ← export { LegalConverterSection }
|
||||
|
||||
widgets/converter-page/ ← ВЕТКА ФИЗЛИЦ: обычный конвертер (не трогался)
|
||||
model/useConverterSection.ts
|
||||
ui/ConverterSection.tsx
|
||||
...
|
||||
|
||||
features/b2b/ ← домен B2B-заявок
|
||||
lib/purchaseRequest.ts ← чистая доменная логика (без React) — см. ниже
|
||||
hooks/useCreatePurchaseRequest.ts ← мутация POST /purchase-requests
|
||||
hooks/useMyPurchaseRequests.ts ← список заявок аккаунта (queryKey b2b/purchase-requests)
|
||||
api/b2bApi.ts ← fetch + типы B2bPurchaseRequest и т.д.
|
||||
```
|
||||
|
||||
`LegalConverterForm.tsx` экспортирует компонент `LegalConverterSection` (имя
|
||||
по аналогии с `ConverterSection` физлиц). Файл назван `*Form`, компонент — `*Section`.
|
||||
|
||||
## Ветка юрлиц: где что лежит
|
||||
|
||||
Раньше всё (стейт формы + доменная математика + разметка) жило одним файлом
|
||||
`pages/converter/ui/LegalConverterPage.tsx`. После рефакторинга:
|
||||
|
||||
| Слой | Файл | Ответственность |
|
||||
|---|---|---|
|
||||
| `features/b2b/lib` | `purchaseRequest.ts` | **чистая** логика без React: расчёт комиссии, упаковка/парсинг comment, склонения, форматирование ₽ |
|
||||
| `widgets/legal-converter/model` | `useLegalConverterForm.ts` | стейт полей формы, автозаполнение, submit-мутация, нотификация |
|
||||
| `widgets/legal-converter/ui` | `LegalConverterForm.tsx` | только разметка, дёргает хук |
|
||||
|
||||
### `features/b2b/lib/purchaseRequest.ts`
|
||||
|
||||
| Экспорт | Что делает |
|
||||
|---|---|
|
||||
| `MIN_ORDER` | минимальный объём заявки (500 000 ₽) |
|
||||
| `TERM_OPTIONS` | таблица «срок ожидания → ставка комиссии». Чем дольше ждать, тем ниже ставка (3 дня → 5 %, 14 дней → 1 %) |
|
||||
| `termRate(days)` | ставка (доля) для срока; фоллбэк — первая строка таблицы |
|
||||
| `calcQuote(amount, days)` | `{ rate, commission, total }` — **расчёт комиссии и итога** |
|
||||
| `buildPurchaseComment(parts)` | **упаковка** имени/контакта/срока/сумм в строку `comment` |
|
||||
| `parseContactFromComment(comment)` | **парсинг** имени и контакта обратно из `comment` |
|
||||
| `latestPurchaseRequest(items)` | последняя по `created_at` заявка аккаунта (или `null`) |
|
||||
| `dayLabel(days)` | русское склонение: `1 день` / `2 дня` / `5 дней` |
|
||||
| `formatRub(n)` | целое с разделителями групп: `500000 → "500 000"` |
|
||||
|
||||
## Ключевой нюанс: имя/контакт упакованы в `comment`
|
||||
|
||||
У API B2B-заявки (`B2bPurchaseRequest`, `api/b2bApi.ts`) **нет отдельных полей** под
|
||||
имя, контакт, срок ожидания и суммы в ₽. Есть только `usdt_amount` и свободный
|
||||
`comment`. Поэтому всё, что нужно менеджеру, кодируется в человекочитаемый `comment`:
|
||||
|
||||
```
|
||||
Имя: Иван; Контакт: +7 900 000-00-00; Срок ожидания: 3 дня; Сумма: 500 000 ₽; Комиссия: ≈25 000 ₽; Итого: ≈525 000 ₽
|
||||
```
|
||||
|
||||
- **Запись:** `buildPurchaseComment()` собирает строку при отправке.
|
||||
- **Чтение:** `parseContactFromComment()` достаёт `Имя:`/`Контакт:` регулярками — это
|
||||
обратная операция к записи. Используется для **автозаполнения** формы из последней
|
||||
заявки аккаунта (один раз, по флагу `prefilled` в хуке).
|
||||
|
||||
> Связка хрупкая по контракту: меняете формат строки в `buildPurchaseComment` —
|
||||
> синхронно правьте регулярки в `parseContactFromComment`, иначе автозаполнение
|
||||
> молча перестанет находить поля.
|
||||
|
||||
## Поток данных (ветка юрлиц)
|
||||
|
||||
| Источник | Откуда | Что даёт |
|
||||
|---|---|---|
|
||||
| `useMe()` | `@features/auth` | `account_type` для выбора ветки (в `ConverterPage`) |
|
||||
| `useMyPurchaseRequests()` | `@features/b2b` | список прошлых заявок → автозаполнение |
|
||||
| `useCreatePurchaseRequest()` | `@features/b2b` | мутация создания заявки; на успех инвалидирует `['b2b','purchase-requests']` |
|
||||
|
||||
Числовая сумма берётся из строки ввода: `Number(amount.replace(/\D/g, ''))`. Поле
|
||||
форматируется на лету (`onAmountChange` → `formatRub`), поэтому в стейте лежит
|
||||
строка с пробелами, а в API уходит число `numAmount`.
|
||||
|
||||
## Правила при доработке
|
||||
|
||||
1. **Страница остаётся роутером.** Новый вид конвертера = новый виджет +
|
||||
ветка в `ConverterPage`. Не тащите стейт/хуки данных в `ConverterPage.tsx`.
|
||||
2. **Чистая доменная логика** (расчёты, упаковка/парсинг comment, форматирование) →
|
||||
`features/b2b/lib/purchaseRequest.ts`. Без React, чтобы переиспользовать и тестировать.
|
||||
3. **Стейт формы и мутации** → `widgets/legal-converter/model`. UI-компонент только рендерит.
|
||||
4. **`comment` пишется и читается одной парой функций** (`build*`/`parse*`) — не
|
||||
разносите формат по разным местам.
|
||||
5. Соблюдайте порядок слоёв: виджет импортирует из `features`/`shared`, не наоборот.
|
||||
|
||||
## Известные точки расширения / TODO
|
||||
|
||||
- `PURCHASE_ENABLED = false` — глобальный выключатель в `ConverterPage.tsx`. Пока `false`,
|
||||
оба конвертера не показываются.
|
||||
- `formatRub` живёт в `features/b2b/lib` (локально для домена). Если понадобится
|
||||
форматирование ₽ в других местах — кандидат на переезд в `shared/lib/utils`
|
||||
(рядом с `formatUsd`).
|
||||
- Хрупкость `comment`-контракта (см. выше). При появлении в API нормальных полей под
|
||||
имя/контакт/срок — `build/parseComment` можно выкинуть.
|
||||
|
||||
## Проверка
|
||||
|
||||
```bash
|
||||
npm run build # tsc -b + vite build (тестов в проекте нет)
|
||||
npm run lint
|
||||
npm run dev # ручная проверка /converter (под legal-аккаунтом, PURCHASE_ENABLED=true)
|
||||
```
|
||||
|
||||
Ручной чек-лист `/converter` (legal-аккаунт, `PURCHASE_ENABLED = true`):
|
||||
- ввод объёма форматируется (`500000 → 500 000`); подсказка при объёме < 500 000 ₽;
|
||||
- выбор срока меняет ставку комиссии и пересчитывает «Сумму комиссии» / «Итого»;
|
||||
- кнопка неактивна при пустой/слишком малой сумме и во время отправки;
|
||||
- успешная отправка → тост «Заявка отправлена», форма очищается;
|
||||
- при повторном заходе имя/контакт подставляются из последней заявки;
|
||||
- individual-аккаунт по тому же адресу видит обычный `ConverterSection`.
|
||||
```
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useMe } from '@features/auth'
|
||||
import { Spinner } from '@shared/ui'
|
||||
import { ConverterSection } from '@widgets/converter-page'
|
||||
import { LegalConverterPage } from './LegalConverterPage'
|
||||
import { LegalConverterSection } from '@widgets/legal-converter'
|
||||
import styles from './ConverterPage.module.css'
|
||||
|
||||
// Пока покупка валюты недоступна — показываем заглушку вместо конвертера.
|
||||
@@ -28,5 +28,5 @@ export function ConverterPage() {
|
||||
}
|
||||
|
||||
const isLegal = !!data && data.account_type !== 'individual'
|
||||
return isLegal ? <LegalConverterPage /> : <ConverterSection />
|
||||
return isLegal ? <LegalConverterSection /> : <ConverterSection />
|
||||
}
|
||||
|
||||
@@ -1,221 +0,0 @@
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useCreatePurchaseRequest, useMyPurchaseRequests } from '@features/b2b'
|
||||
import type { B2bPurchaseRequest } from '@features/b2b'
|
||||
import { FormField, Notification, Select } from '@shared/ui'
|
||||
import styles from './LegalConverterPage.module.css'
|
||||
|
||||
const MIN_ORDER = 500_000
|
||||
|
||||
// Чем дольше пользователь готов ждать, тем ниже комиссия сервиса.
|
||||
const TERM_OPTIONS = [
|
||||
{ days: 3, rate: 0.05 },
|
||||
{ days: 4, rate: 0.04636 },
|
||||
{ days: 5, rate: 0.04273 },
|
||||
{ days: 6, rate: 0.03909 },
|
||||
{ days: 7, rate: 0.03545 },
|
||||
{ days: 8, rate: 0.03182 },
|
||||
{ days: 9, rate: 0.02818 },
|
||||
{ days: 10, rate: 0.02455 },
|
||||
{ days: 11, rate: 0.02091 },
|
||||
{ days: 12, rate: 0.01727 },
|
||||
{ days: 13, rate: 0.01364 },
|
||||
{ days: 14, rate: 0.01 },
|
||||
] as const
|
||||
|
||||
const ru = (n: number) => n.toLocaleString('ru-RU', { maximumFractionDigits: 0 })
|
||||
|
||||
// Имя и контакт у заявки отдельных полей не имеют — они упакованы в comment
|
||||
// (см. handleSubmit). Достаём их обратно для автозаполнения формы.
|
||||
const parseContactFromComment = (comment: string | null) => ({
|
||||
name: comment?.match(/Имя:\s*([^;]+)/)?.[1]?.trim() ?? '',
|
||||
contact: comment?.match(/Контакт:\s*([^;]+)/)?.[1]?.trim() ?? '',
|
||||
})
|
||||
|
||||
// Последняя по времени создания заявка на аккаунте, либо null.
|
||||
const latestRequest = (items: B2bPurchaseRequest[]): B2bPurchaseRequest | null =>
|
||||
items.reduce<B2bPurchaseRequest | null>(
|
||||
(latest, r) => (!latest || (r.created_at ?? '') > (latest.created_at ?? '') ? r : latest),
|
||||
null,
|
||||
)
|
||||
|
||||
const dayLabel = (days: number) => {
|
||||
const mod10 = days % 10
|
||||
const mod100 = days % 100
|
||||
if (mod10 === 1 && mod100 !== 11) return `${days} день`
|
||||
if (mod10 >= 2 && mod10 <= 4 && (mod100 < 10 || mod100 >= 20)) return `${days} дня`
|
||||
return `${days} дней`
|
||||
}
|
||||
|
||||
export function LegalConverterPage() {
|
||||
const [amount, setAmount] = useState('')
|
||||
const [name, setName] = useState('')
|
||||
const [contact, setContact] = useState('')
|
||||
const [days, setDays] = useState<number>(TERM_OPTIONS[0].days)
|
||||
const [notice, setNotice] = useState<'success' | 'error' | null>(null)
|
||||
|
||||
const { mutate: submitRequest, isPending } = useCreatePurchaseRequest()
|
||||
const { data: requests } = useMyPurchaseRequests()
|
||||
|
||||
// Автозаполнение имени и контакта из последней заявки аккаунта (один раз).
|
||||
const [prefilled, setPrefilled] = useState(false)
|
||||
useEffect(() => {
|
||||
if (prefilled || !requests) return
|
||||
const last = latestRequest(requests.items)
|
||||
if (!last) return
|
||||
const parsed = parseContactFromComment(last.comment)
|
||||
if (parsed.name) setName(parsed.name)
|
||||
if (parsed.contact) setContact(parsed.contact)
|
||||
setPrefilled(true)
|
||||
}, [prefilled, requests])
|
||||
|
||||
const numAmount = Number(amount.replace(/\D/g, '')) || 0
|
||||
const belowMin = numAmount > 0 && numAmount < MIN_ORDER
|
||||
|
||||
const rate = TERM_OPTIONS.find((o) => o.days === days)?.rate ?? TERM_OPTIONS[0].rate
|
||||
const commission = numAmount * rate
|
||||
const total = numAmount + commission
|
||||
|
||||
const handleAmountChange = (value: string) => {
|
||||
const digits = value.replace(/\D/g, '')
|
||||
setAmount(digits ? ru(Number(digits)) : '')
|
||||
}
|
||||
|
||||
const handleSubmit = (e: React.FormEvent) => {
|
||||
e.preventDefault()
|
||||
if (numAmount === 0 || belowMin || isPending) return
|
||||
|
||||
// Отдельных полей под имя/контакт/срок/сумму в ₽ у API нет — всё уходит в comment.
|
||||
const comment = [
|
||||
name && `Имя: ${name}`,
|
||||
contact && `Контакт: ${contact}`,
|
||||
`Срок ожидания: ${dayLabel(days)}`,
|
||||
`Сумма: ${ru(numAmount)} ₽`,
|
||||
`Комиссия: ≈${ru(commission)} ₽`,
|
||||
`Итого: ≈${ru(total)} ₽`,
|
||||
].filter(Boolean).join('; ')
|
||||
|
||||
submitRequest(
|
||||
{ usdt_amount: numAmount, comment },
|
||||
{
|
||||
onSuccess: () => {
|
||||
setNotice('success')
|
||||
setAmount('')
|
||||
setName('')
|
||||
setContact('')
|
||||
setDays(TERM_OPTIONS[0].days)
|
||||
},
|
||||
onError: () => setNotice('error'),
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<form className={styles.wrap} onSubmit={handleSubmit}>
|
||||
<div className={styles.header}>
|
||||
<h1 className={styles.title}>Оставить заявку</h1>
|
||||
<p className={styles.subtitle}>
|
||||
Конвертация крупных объёмов по индивидуальному курсу. Оставьте заявку —
|
||||
менеджер свяжется с вами, подтвердит актуальный курс и сопроводит сделку.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className={styles.body}>
|
||||
<div className={styles.formCol}>
|
||||
<FormField
|
||||
label="Объём заявки, ₽"
|
||||
type="text"
|
||||
value={amount}
|
||||
onChange={handleAmountChange}
|
||||
placeholder="от 500 000"
|
||||
/>
|
||||
{belowMin && (
|
||||
<p className={styles.hint}>
|
||||
Минимальный объём заявки — {ru(MIN_ORDER)} ₽
|
||||
</p>
|
||||
)}
|
||||
|
||||
<Select
|
||||
id="term"
|
||||
label="Срок ожидания операции"
|
||||
value={days}
|
||||
onChange={setDays}
|
||||
options={TERM_OPTIONS.map((o) => ({
|
||||
value: o.days,
|
||||
label: `${dayLabel(o.days)} — комиссия ${(o.rate * 100).toFixed(3)} %`,
|
||||
}))}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
label="Как к вам обращаться"
|
||||
type="text"
|
||||
value={name}
|
||||
onChange={setName}
|
||||
placeholder="Имя"
|
||||
/>
|
||||
|
||||
<FormField
|
||||
label="Email или телефон для связи"
|
||||
type="text"
|
||||
value={contact}
|
||||
onChange={setContact}
|
||||
placeholder="example@mail.ru / +7 900 000-00-00"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className={styles.infoCol}>
|
||||
<div className={styles.infoTitle}>УСЛОВИЯ</div>
|
||||
|
||||
<div className={styles.infoRow}>
|
||||
<span className={styles.infoLabel}>Минимальный объём</span>
|
||||
<span className={styles.infoValue}>{ru(MIN_ORDER)} ₽</span>
|
||||
</div>
|
||||
|
||||
<div className={styles.infoRow}>
|
||||
<span className={styles.infoLabel}>Срок ожидания</span>
|
||||
<span className={styles.infoValue}>{dayLabel(days)}</span>
|
||||
</div>
|
||||
|
||||
<div className={styles.infoRow}>
|
||||
<span className={styles.infoLabel}>Ставка комиссии</span>
|
||||
<span className={styles.infoValue}>{(rate * 100).toFixed(3)} %</span>
|
||||
</div>
|
||||
|
||||
<div className={styles.infoRow}>
|
||||
<span className={styles.infoLabel}>Сумма комиссии</span>
|
||||
<span className={styles.infoValue}>
|
||||
{numAmount > 0 ? `≈ ${ru(commission)} ₽` : '—'}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className={styles.infoRow} data-accent>
|
||||
<span className={styles.infoLabel}>Итого к оплате</span>
|
||||
<span className={styles.infoValue}>
|
||||
{numAmount > 0 ? `≈ ${ru(total)} ₽` : '—'}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<p className={styles.note}>
|
||||
Итоговая комиссия рассчитывается индивидуально и зависит от объёма,
|
||||
валюты и направления сделки.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
className={styles.submitBtn}
|
||||
disabled={belowMin || numAmount === 0 || isPending}
|
||||
>
|
||||
{isPending ? 'Отправляем...' : 'Оставить заявку'}
|
||||
</button>
|
||||
|
||||
{notice && (
|
||||
<Notification
|
||||
status={notice}
|
||||
message={notice === 'success' ? 'Заявка отправлена' : 'Не удалось отправить заявку'}
|
||||
onClose={() => setNotice(null)}
|
||||
/>
|
||||
)}
|
||||
</form>
|
||||
)
|
||||
}
|
||||
338
src/pages/politika-cookie/model/content.ts
Normal file
338
src/pages/politika-cookie/model/content.ts
Normal file
@@ -0,0 +1,338 @@
|
||||
import type { LegalDocData } from '@entities/legal-doc'
|
||||
|
||||
export const politikaCookieDoc: LegalDocData = {
|
||||
title: 'ПОЛИТИКА ИСПОЛЬЗОВАНИЯ ФАЙЛОВ COOKIE',
|
||||
sections: [
|
||||
{
|
||||
title: 'Общие положения и терминология',
|
||||
blocks: [
|
||||
{
|
||||
kind: 'text',
|
||||
text: 'Настоящая Политика использования файлов cookie устанавливает порядок обработки файлов cookie и содержащихся в них персональных данных ООО «БИТФОРС» при использовании пользователями интернет-ресурса https://bitforce-foundation.ru.',
|
||||
},
|
||||
{
|
||||
kind: 'text',
|
||||
text: 'Файлы cookie — это текстовые файлы небольшого размера, которые устанавливаются на пользовательское устройство при посещении интернет-ресурса или совершении на нем определенных действий. Файлы cookie остаются сохраненными на устройстве даже после покидания ресурса, что позволяет «узнавать» пользователя при последующих посещениях.',
|
||||
},
|
||||
{
|
||||
kind: 'text',
|
||||
text: 'К персональным данным относится не сам файл cookie, а его содержимое — уникальные идентификаторы, IP-адреса, информация о предпочтениях пользователя и другие данные, позволяющие прямо или косвенно идентифицировать физическое лицо.',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Оператор персональных данных',
|
||||
blocks: [
|
||||
{ kind: 'text', text: 'Оператором персональных данных, содержащихся в файлах cookie, является:' },
|
||||
{
|
||||
kind: 'infoBox',
|
||||
lines: [
|
||||
'ООО «БИТФОРС»',
|
||||
'ИНН: 9810001062',
|
||||
'ОГРН: 1257800060990',
|
||||
'Юридический адрес: 196246, город Санкт-Петербург, Московское ш, д. 25 к. 1 литера В, помещ. 3-н',
|
||||
],
|
||||
},
|
||||
{
|
||||
kind: 'text',
|
||||
text: 'Оператор определяет цели обработки персональных данных, их состав, а также действия с персональными данными, включая случаи использования сторонних файлов cookie.',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Категории файлов cookie и их назначение',
|
||||
blocks: [
|
||||
{ kind: 'subheading', text: '1. Строго необходимые (технические) файлы cookie' },
|
||||
{
|
||||
kind: 'text',
|
||||
text: 'Данные файлы обеспечивают работу интернет-ресурса и предоставление необходимого уровня сервиса: авторизацию, навигацию, отображение контента в соответствии с параметрами устройства, обеспечение безопасности.',
|
||||
},
|
||||
{
|
||||
kind: 'text',
|
||||
text: 'Обработка таких файлов cookie осуществляется на основании п. 5 ч. 1 ст. 6 ФЗ № 152 (заключение и исполнение договора). Согласие на использование строго необходимых файлов cookie не требуется.',
|
||||
},
|
||||
{
|
||||
kind: 'text',
|
||||
variant: 'example',
|
||||
text: 'Примеры: файлы сессий (PHPSESSID), настройки безопасности, файлы аутентификации.',
|
||||
},
|
||||
{ kind: 'subheading', text: '2. Функциональные файлы cookie' },
|
||||
{
|
||||
kind: 'text',
|
||||
text: 'Используются для запоминания пользовательских предпочтений и персонализации взаимодействия с сайтом: сохранение выбранного языка, региона, настроек отображения, размера шрифта.',
|
||||
},
|
||||
{
|
||||
kind: 'text',
|
||||
text: 'Обработка осуществляется на основании согласия субъекта персональных данных, поскольку данная обработка не является строго необходимой для функционирования сайта.',
|
||||
},
|
||||
{
|
||||
kind: 'text',
|
||||
variant: 'example',
|
||||
text: 'Примеры: настройки языка интерфейса, предпочтения отображения, настройки доступности.',
|
||||
},
|
||||
{ kind: 'subheading', text: '3. Аналитические файлы cookie' },
|
||||
{
|
||||
kind: 'text',
|
||||
text: 'Собирают информацию о взаимодействии пользователей с интернет-ресурсом для анализа его использования, выявления популярных разделов, обнаружения ошибок и улучшения пользовательского опыта. Могут содержать персональные данные, включая IP-адреса пользователей.',
|
||||
},
|
||||
{ kind: 'text', text: 'Обработка осуществляется на основании согласия субъекта персональных данных.' },
|
||||
{ kind: 'subheading', text: '4. Маркетинговые файлы cookie' },
|
||||
{
|
||||
kind: 'text',
|
||||
text: 'Используются для отслеживания пользователей в целях персонализированной рекламы, анализа эффективности рекламных кампаний, ретаргетинга.',
|
||||
},
|
||||
{
|
||||
kind: 'text',
|
||||
text: 'Обработка осуществляется исключительно на основании согласия субъекта персональных данных.',
|
||||
},
|
||||
{
|
||||
kind: 'text',
|
||||
variant: 'example',
|
||||
text: 'Примеры: пиксели социальных сетей, рекламные идентификаторы, файлы ретаргетинга.',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Правовые основания обработки персональных данных',
|
||||
blocks: [
|
||||
{
|
||||
kind: 'text',
|
||||
text: 'Обработка персональных данных, содержащихся в файлах cookie, осуществляется на следующих правовых основаниях:',
|
||||
},
|
||||
{
|
||||
kind: 'list',
|
||||
items: [
|
||||
{
|
||||
strong: 'Согласие субъекта персональных данных',
|
||||
text: '— для функциональных, аналитических и маркетинговых файлов cookie',
|
||||
},
|
||||
{
|
||||
strong: 'Заключение и исполнение договора',
|
||||
text: '— для строго необходимых файлов cookie, обеспечивающих работу интернет-ресурса',
|
||||
},
|
||||
{
|
||||
strong: 'Законные интересы оператора',
|
||||
text: '— в исключительных случаях, когда отсутствуют иные основания',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Порядок получения согласия',
|
||||
blocks: [
|
||||
{ kind: 'subheading', text: 'Принципы получения согласия:' },
|
||||
{
|
||||
kind: 'list',
|
||||
items: [
|
||||
'Согласие должно быть получено до начала обработки персональных данных',
|
||||
'Информация об использовании файлов cookie размещается на первом уровне интернет-ресурса',
|
||||
'Предоставляется возможность выбора категорий файлов cookie',
|
||||
'Используются активные формулировки вместо пассивных',
|
||||
],
|
||||
},
|
||||
{ kind: 'subheading', text: 'Критерии действительного согласия:' },
|
||||
{
|
||||
kind: 'list',
|
||||
items: [
|
||||
{ strong: 'Добровольность', text: '— согласие дается по свободной воле субъекта' },
|
||||
{ strong: 'Конкретность', text: '— четко определены цели обработки' },
|
||||
{ strong: 'Информированность', text: '— предоставлена полная информация об обработке' },
|
||||
{ strong: 'Однозначность', text: '— согласие выражено в недвусмысленной форме' },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Сторонние файлы cookie',
|
||||
blocks: [
|
||||
{ kind: 'subheading', text: 'Использование сторонних сервисов:' },
|
||||
{ kind: 'text', text: 'Наш интернет-ресурс использует файлы cookie сторонних сервисов, включая:' },
|
||||
{
|
||||
kind: 'list',
|
||||
items: ['Яндекс.Метрика (ООО «ЯНДЕКС», Россия)', 'Социальные сети и сервисы интеграции'],
|
||||
},
|
||||
{ kind: 'subheading', text: 'Обеспечение защиты:' },
|
||||
{
|
||||
kind: 'list',
|
||||
items: [
|
||||
'Получено согласие на передачу',
|
||||
'Применяются дополнительные меры защиты данных',
|
||||
'Контролируется соблюдение принципов обработки персональных данных получателями',
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Сроки обработки и хранения',
|
||||
blocks: [
|
||||
{ kind: 'subheading', text: 'Категории по срокам хранения:' },
|
||||
{
|
||||
kind: 'list',
|
||||
items: [
|
||||
'Сеансовые cookie — удаляются автоматически при закрытии браузера',
|
||||
'Постоянные cookie — хранятся установленный период или до удаления пользователем',
|
||||
],
|
||||
},
|
||||
{ kind: 'subheading', text: 'Конкретные сроки:' },
|
||||
{
|
||||
kind: 'list',
|
||||
items: [
|
||||
'Необходимые файлы cookie — до 12 месяцев',
|
||||
'Функциональные файлы cookie — до 12 месяцев',
|
||||
'Аналитические файлы cookie — до 24 месяцев',
|
||||
'Маркетинговые файлы cookie — до 24 месяцев',
|
||||
],
|
||||
},
|
||||
{
|
||||
kind: 'text',
|
||||
text: 'По истечении установленных сроков файлы cookie удаляются автоматически. Пользователь может удалить файлы cookie досрочно через настройки браузера или отозвать согласие на их обработку.',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Права субъектов персональных данных',
|
||||
blocks: [
|
||||
{ kind: 'subheading', text: 'Право на информацию:' },
|
||||
{
|
||||
kind: 'list',
|
||||
items: [
|
||||
'Получение информации о обработке персональных данных',
|
||||
'Сведения о правовых основаниях и целях обработки',
|
||||
'Информация о сроках обработки и составе данных',
|
||||
],
|
||||
},
|
||||
{ kind: 'subheading', text: 'Право на доступ:' },
|
||||
{
|
||||
kind: 'list',
|
||||
items: [
|
||||
'Получение подтверждения факта обработки',
|
||||
'Ознакомление с обрабатываемыми персональными данными',
|
||||
'Получение информации об источниках персональных данных',
|
||||
],
|
||||
},
|
||||
{ kind: 'subheading', text: 'Право на уточнение, блокирование, удаление:' },
|
||||
{
|
||||
kind: 'list',
|
||||
items: [
|
||||
'Требование уточнения неточных данных',
|
||||
'Блокирование недостоверных данных',
|
||||
'Удаление незаконно полученных данных',
|
||||
],
|
||||
},
|
||||
{ kind: 'subheading', text: 'Право на отзыв согласия:' },
|
||||
{
|
||||
kind: 'list',
|
||||
items: [
|
||||
'Отзыв согласия в любое время',
|
||||
'Прекращение обработки после отзыва согласия',
|
||||
'Сохранение права на обжалование действий оператора',
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Способы управления файлами cookie',
|
||||
blocks: [
|
||||
{ kind: 'subheading', text: 'Управление через настройки сайта:' },
|
||||
{
|
||||
kind: 'list',
|
||||
items: [
|
||||
'Использование баннера согласия на файлы cookie',
|
||||
'Изменение настроек в любое время через интерфейс сайта',
|
||||
'Отзыв согласия на использование отдельных категорий файлов cookie',
|
||||
],
|
||||
},
|
||||
{ kind: 'subheading', text: 'Управление через браузер:' },
|
||||
{ kind: 'text', text: 'Большинство браузеров позволяют контролировать файлы cookie:' },
|
||||
{
|
||||
kind: 'list',
|
||||
items: [
|
||||
'Блокировка — запрет установки новых файлов cookie',
|
||||
'Удаление — очистка существующих файлов cookie',
|
||||
'Уведомления — получение предупреждений при установке файлов cookie',
|
||||
'Селективная настройка — разрешение файлов cookie только для определенных сайтов',
|
||||
],
|
||||
},
|
||||
{ kind: 'subheading', text: 'Инструкции для популярных браузеров:' },
|
||||
{
|
||||
kind: 'list',
|
||||
items: [
|
||||
'Google Chrome: Настройки → Конфиденциальность и безопасность → Файлы cookie',
|
||||
'Mozilla Firefox: Настройки → Приватность и Защита → Файлы cookie',
|
||||
'Safari: Настройки → Конфиденциальность → Файлы cookie',
|
||||
'Microsoft Edge: Настройки → Файлы cookie и разрешения сайтов',
|
||||
],
|
||||
},
|
||||
{
|
||||
kind: 'text',
|
||||
variant: 'warning',
|
||||
text: 'Блокировка необходимых файлов cookie может привести к ограничению функциональности интернет-ресурса.',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Меры безопасности',
|
||||
blocks: [
|
||||
{
|
||||
kind: 'text',
|
||||
text: 'Оператор применяет правовые, организационные и технические меры для защиты персональных данных:',
|
||||
},
|
||||
{ kind: 'subheading', text: 'Правовые меры:' },
|
||||
{
|
||||
kind: 'list',
|
||||
items: [
|
||||
'Назначение ответственного за организацию обработки персональных данных',
|
||||
'Ознакомление сотрудников с требованиями законодательства',
|
||||
'Заключение соглашений о неразглашении персональных данных',
|
||||
],
|
||||
},
|
||||
{ kind: 'subheading', text: 'Организационные меры:' },
|
||||
{
|
||||
kind: 'list',
|
||||
items: [
|
||||
'Определение перечня лиц, допущенных к обработке персональных данных',
|
||||
'Установление правил доступа к персональным данным',
|
||||
'Контроль за соблюдением требований по защите персональных данных',
|
||||
],
|
||||
},
|
||||
{ kind: 'subheading', text: 'Технические меры:' },
|
||||
{
|
||||
kind: 'list',
|
||||
items: [
|
||||
'Использование средств защиты информации',
|
||||
'Применение криптографических средств защиты',
|
||||
'Обеспечение целостности и доступности персональных данных',
|
||||
'Регулярное обновление систем защиты информации',
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Контактная информация и обращения',
|
||||
blocks: [
|
||||
{ kind: 'text', text: 'Для реализации прав субъекта персональных данных обращайтесь к нам:' },
|
||||
{
|
||||
kind: 'infoBox',
|
||||
lines: [
|
||||
'ООО «БИТФОРС»',
|
||||
'ИНН: 9810001062',
|
||||
'ОГРН: 1257800060990',
|
||||
'Юридический адрес: 196246, город Санкт-Петербург, Московское ш, д. 25 к. 1 литера В, помещ. 3-н',
|
||||
'Email компании: company@bitforcefoundation.ru',
|
||||
],
|
||||
},
|
||||
{ kind: 'subheading', text: 'Порядок рассмотрения обращений:' },
|
||||
{
|
||||
kind: 'list',
|
||||
items: [
|
||||
'Срок рассмотрения обращений — до 30 дней с момента получения',
|
||||
'Обращения рассматриваются в письменной форме',
|
||||
'Ответ направляется способом, указанным в обращении',
|
||||
'При отказе в удовлетворении требований указываются мотивированные основания',
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
@@ -1,115 +0,0 @@
|
||||
.main {
|
||||
padding: 40px 20px;
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.container {
|
||||
background: var(--bg-mid, #1b1547);
|
||||
padding: 40px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 28px;
|
||||
font-weight: 700;
|
||||
margin-bottom: 30px;
|
||||
text-align: center;
|
||||
color: var(--text-primary, #ffffff);
|
||||
}
|
||||
|
||||
.section {
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
|
||||
.sectionTitle {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
margin-bottom: 15px;
|
||||
color: var(--text-primary, #ffffff);
|
||||
border-bottom: 2px solid var(--interactive, #4a6dff);
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
.subSectionTitle {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
margin-top: 20px;
|
||||
margin-bottom: 10px;
|
||||
color: var(--text-primary, #ffffff);
|
||||
}
|
||||
|
||||
.list {
|
||||
list-style: disc;
|
||||
margin-left: 20px;
|
||||
line-height: 1.8;
|
||||
color: var(--text-primary, #ffffff);
|
||||
}
|
||||
|
||||
.list li {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.list strong {
|
||||
color: var(--text-primary, #ffffff);
|
||||
}
|
||||
|
||||
.info {
|
||||
padding: 20px;
|
||||
background: var(--glass-bg, rgba(255, 255, 255, 0.04));
|
||||
border-left: 4px solid var(--interactive, #4a6dff);
|
||||
border-radius: 4px;
|
||||
margin: 15px 0;
|
||||
line-height: 1.8;
|
||||
}
|
||||
|
||||
.info p {
|
||||
margin: 5px 0;
|
||||
color: var(--text-primary, #ffffff);
|
||||
}
|
||||
|
||||
.example {
|
||||
padding: 10px 15px;
|
||||
background: var(--glass-bg, rgba(255, 255, 255, 0.04));
|
||||
border-left: 3px solid var(--interactive, #4a6dff);
|
||||
border-radius: 4px;
|
||||
font-style: italic;
|
||||
color: var(--text-primary, #ffffff);
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
.warning {
|
||||
padding: 15px;
|
||||
background: #fff3cd;
|
||||
border-left: 4px solid #ffc107;
|
||||
border-radius: 4px;
|
||||
color: var(--text-primary, #ffffff);
|
||||
margin: 15px 0;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.container {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 22px;
|
||||
}
|
||||
|
||||
.sectionTitle {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.subSectionTitle {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.list {
|
||||
margin-left: 15px;
|
||||
}
|
||||
|
||||
.info {
|
||||
padding: 15px;
|
||||
}
|
||||
}
|
||||
@@ -1,273 +1,13 @@
|
||||
import { LegalDoc } from '@entities/legal-doc'
|
||||
import { Footer } from '@widgets/footer'
|
||||
import { Header } from '@widgets/header'
|
||||
import styles from './PolitikaCookiePage.module.css'
|
||||
import { politikaCookieDoc } from '../model/content'
|
||||
|
||||
export function PolitikaCookiePage() {
|
||||
return (
|
||||
<>
|
||||
<Header />
|
||||
<main className={styles.main}>
|
||||
<div className={styles.container}>
|
||||
<h1 className={styles.title}>ПОЛИТИКА ИСПОЛЬЗОВАНИЯ ФАЙЛОВ COOKIE</h1>
|
||||
|
||||
<section className={styles.section}>
|
||||
<h3 className={styles.sectionTitle}>Общие положения и терминология</h3>
|
||||
<p>
|
||||
Настоящая Политика использования файлов cookie устанавливает порядок обработки файлов cookie и содержащихся в них персональных данных ООО «БИТФОРС» при использовании пользователями интернет-ресурса https://bitforce-foundation.ru.
|
||||
</p>
|
||||
<p>
|
||||
Файлы cookie — это текстовые файлы небольшого размера, которые устанавливаются на пользовательское устройство при посещении интернет-ресурса или совершении на нем определенных действий. Файлы cookie остаются сохраненными на устройстве даже после покидания ресурса, что позволяет «узнавать» пользователя при последующих посещениях.
|
||||
</p>
|
||||
<p>
|
||||
К персональным данным относится не сам файл cookie, а его содержимое — уникальные идентификаторы, IP-адреса, информация о предпочтениях пользователя и другие данные, позволяющие прямо или косвенно идентифицировать физическое лицо.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section className={styles.section}>
|
||||
<h3 className={styles.sectionTitle}>Оператор персональных данных</h3>
|
||||
<p>Оператором персональных данных, содержащихся в файлах cookie, является:</p>
|
||||
<div className={styles.info}>
|
||||
<p>ООО «БИТФОРС»</p>
|
||||
<p>ИНН: 9810001062</p>
|
||||
<p>ОГРН: 1257800060990</p>
|
||||
<p>Юридический адрес: 196246, город Санкт-Петербург, Московское ш, д. 25 к. 1 литера В, помещ. 3-н</p>
|
||||
</div>
|
||||
<p>
|
||||
Оператор определяет цели обработки персональных данных, их состав, а также действия с персональными данными, включая случаи использования сторонних файлов cookie.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section className={styles.section}>
|
||||
<h3 className={styles.sectionTitle}>Категории файлов cookie и их назначение</h3>
|
||||
|
||||
<h4 className={styles.subSectionTitle}>1. Строго необходимые (технические) файлы cookie</h4>
|
||||
<p>
|
||||
Данные файлы обеспечивают работу интернет-ресурса и предоставление необходимого уровня сервиса: авторизацию, навигацию, отображение контента в соответствии с параметрами устройства, обеспечение безопасности.
|
||||
</p>
|
||||
<p>
|
||||
Обработка таких файлов cookie осуществляется на основании п. 5 ч. 1 ст. 6 ФЗ № 152 (заключение и исполнение договора). Согласие на использование строго необходимых файлов cookie не требуется.
|
||||
</p>
|
||||
<p className={styles.example}>Примеры: файлы сессий (PHPSESSID), настройки безопасности, файлы аутентификации.</p>
|
||||
|
||||
<h4 className={styles.subSectionTitle}>2. Функциональные файлы cookie</h4>
|
||||
<p>
|
||||
Используются для запоминания пользовательских предпочтений и персонализации взаимодействия с сайтом: сохранение выбранного языка, региона, настроек отображения, размера шрифта.
|
||||
</p>
|
||||
<p>
|
||||
Обработка осуществляется на основании согласия субъекта персональных данных, поскольку данная обработка не является строго необходимой для функционирования сайта.
|
||||
</p>
|
||||
<p className={styles.example}>Примеры: настройки языка интерфейса, предпочтения отображения, настройки доступности.</p>
|
||||
|
||||
<h4 className={styles.subSectionTitle}>3. Аналитические файлы cookie</h4>
|
||||
<p>
|
||||
Собирают информацию о взаимодействии пользователей с интернет-ресурсом для анализа его использования, выявления популярных разделов, обнаружения ошибок и улучшения пользовательского опыта. Могут содержать персональные данные, включая IP-адреса пользователей.
|
||||
</p>
|
||||
<p>
|
||||
Обработка осуществляется на основании согласия субъекта персональных данных.
|
||||
</p>
|
||||
|
||||
<h4 className={styles.subSectionTitle}>4. Маркетинговые файлы cookie</h4>
|
||||
<p>
|
||||
Используются для отслеживания пользователей в целях персонализированной рекламы, анализа эффективности рекламных кампаний, ретаргетинга.
|
||||
</p>
|
||||
<p>
|
||||
Обработка осуществляется исключительно на основании согласия субъекта персональных данных.
|
||||
</p>
|
||||
<p className={styles.example}>Примеры: пиксели социальных сетей, рекламные идентификаторы, файлы ретаргетинга.</p>
|
||||
</section>
|
||||
|
||||
<section className={styles.section}>
|
||||
<h3 className={styles.sectionTitle}>Правовые основания обработки персональных данных</h3>
|
||||
<p>Обработка персональных данных, содержащихся в файлах cookie, осуществляется на следующих правовых основаниях:</p>
|
||||
<ul className={styles.list}>
|
||||
<li>
|
||||
<strong>Согласие субъекта персональных данных</strong> — для функциональных, аналитических и маркетинговых файлов cookie
|
||||
</li>
|
||||
<li>
|
||||
<strong>Заключение и исполнение договора</strong> — для строго необходимых файлов cookie, обеспечивающих работу интернет-ресурса
|
||||
</li>
|
||||
<li>
|
||||
<strong>Законные интересы оператора</strong> — в исключительных случаях, когда отсутствуют иные основания
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section className={styles.section}>
|
||||
<h3 className={styles.sectionTitle}>Порядок получения согласия</h3>
|
||||
<h4 className={styles.subSectionTitle}>Принципы получения согласия:</h4>
|
||||
<ul className={styles.list}>
|
||||
<li>Согласие должно быть получено до начала обработки персональных данных</li>
|
||||
<li>Информация об использовании файлов cookie размещается на первом уровне интернет-ресурса</li>
|
||||
<li>Предоставляется возможность выбора категорий файлов cookie</li>
|
||||
<li>Используются активные формулировки вместо пассивных</li>
|
||||
</ul>
|
||||
|
||||
<h4 className={styles.subSectionTitle}>Критерии действительного согласия:</h4>
|
||||
<ul className={styles.list}>
|
||||
<li>
|
||||
<strong>Добровольность</strong> — согласие дается по свободной воле субъекта
|
||||
</li>
|
||||
<li>
|
||||
<strong>Конкретность</strong> — четко определены цели обработки
|
||||
</li>
|
||||
<li>
|
||||
<strong>Информированность</strong> — предоставлена полная информация об обработке
|
||||
</li>
|
||||
<li>
|
||||
<strong>Однозначность</strong> — согласие выражено в недвусмысленной форме
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section className={styles.section}>
|
||||
<h3 className={styles.sectionTitle}>Сторонние файлы cookie</h3>
|
||||
<h4 className={styles.subSectionTitle}>Использование сторонних сервисов:</h4>
|
||||
<p>Наш интернет-ресурс использует файлы cookie сторонних сервисов, включая:</p>
|
||||
<ul className={styles.list}>
|
||||
<li>Яндекс.Метрика (ООО «ЯНДЕКС», Россия)</li>
|
||||
<li>Социальные сети и сервисы интеграции</li>
|
||||
</ul>
|
||||
|
||||
<h4 className={styles.subSectionTitle}>Обеспечение защиты:</h4>
|
||||
<ul className={styles.list}>
|
||||
<li>Получено согласие на передачу</li>
|
||||
<li>Применяются дополнительные меры защиты данных</li>
|
||||
<li>Контролируется соблюдение принципов обработки персональных данных получателями</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section className={styles.section}>
|
||||
<h3 className={styles.sectionTitle}>Сроки обработки и хранения</h3>
|
||||
<h4 className={styles.subSectionTitle}>Категории по срокам хранения:</h4>
|
||||
<ul className={styles.list}>
|
||||
<li>Сеансовые cookie — удаляются автоматически при закрытии браузера</li>
|
||||
<li>Постоянные cookie — хранятся установленный период или до удаления пользователем</li>
|
||||
</ul>
|
||||
|
||||
<h4 className={styles.subSectionTitle}>Конкретные сроки:</h4>
|
||||
<ul className={styles.list}>
|
||||
<li>Необходимые файлы cookie — до 12 месяцев</li>
|
||||
<li>Функциональные файлы cookie — до 12 месяцев</li>
|
||||
<li>Аналитические файлы cookie — до 24 месяцев</li>
|
||||
<li>Маркетинговые файлы cookie — до 24 месяцев</li>
|
||||
</ul>
|
||||
|
||||
<p>
|
||||
По истечении установленных сроков файлы cookie удаляются автоматически. Пользователь может удалить файлы cookie досрочно через настройки браузера или отозвать согласие на их обработку.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section className={styles.section}>
|
||||
<h3 className={styles.sectionTitle}>Права субъектов персональных данных</h3>
|
||||
<h4 className={styles.subSectionTitle}>Право на информацию:</h4>
|
||||
<ul className={styles.list}>
|
||||
<li>Получение информации о обработке персональных данных</li>
|
||||
<li>Сведения о правовых основаниях и целях обработки</li>
|
||||
<li>Информация о сроках обработки и составе данных</li>
|
||||
</ul>
|
||||
|
||||
<h4 className={styles.subSectionTitle}>Право на доступ:</h4>
|
||||
<ul className={styles.list}>
|
||||
<li>Получение подтверждения факта обработки</li>
|
||||
<li>Ознакомление с обрабатываемыми персональными данными</li>
|
||||
<li>Получение информации об источниках персональных данных</li>
|
||||
</ul>
|
||||
|
||||
<h4 className={styles.subSectionTitle}>Право на уточнение, блокирование, удаление:</h4>
|
||||
<ul className={styles.list}>
|
||||
<li>Требование уточнения неточных данных</li>
|
||||
<li>Блокирование недостоверных данных</li>
|
||||
<li>Удаление незаконно полученных данных</li>
|
||||
</ul>
|
||||
|
||||
<h4 className={styles.subSectionTitle}>Право на отзыв согласия:</h4>
|
||||
<ul className={styles.list}>
|
||||
<li>Отзыв согласия в любое время</li>
|
||||
<li>Прекращение обработки после отзыва согласия</li>
|
||||
<li>Сохранение права на обжалование действий оператора</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section className={styles.section}>
|
||||
<h3 className={styles.sectionTitle}>Способы управления файлами cookie</h3>
|
||||
<h4 className={styles.subSectionTitle}>Управление через настройки сайта:</h4>
|
||||
<ul className={styles.list}>
|
||||
<li>Использование баннера согласия на файлы cookie</li>
|
||||
<li>Изменение настроек в любое время через интерфейс сайта</li>
|
||||
<li>Отзыв согласия на использование отдельных категорий файлов cookie</li>
|
||||
</ul>
|
||||
|
||||
<h4 className={styles.subSectionTitle}>Управление через браузер:</h4>
|
||||
<p>Большинство браузеров позволяют контролировать файлы cookie:</p>
|
||||
<ul className={styles.list}>
|
||||
<li>Блокировка — запрет установки новых файлов cookie</li>
|
||||
<li>Удаление — очистка существующих файлов cookie</li>
|
||||
<li>Уведомления — получение предупреждений при установке файлов cookie</li>
|
||||
<li>Селективная настройка — разрешение файлов cookie только для определенных сайтов</li>
|
||||
</ul>
|
||||
|
||||
<h4 className={styles.subSectionTitle}>Инструкции для популярных браузеров:</h4>
|
||||
<ul className={styles.list}>
|
||||
<li>Google Chrome: Настройки → Конфиденциальность и безопасность → Файлы cookie</li>
|
||||
<li>Mozilla Firefox: Настройки → Приватность и Защита → Файлы cookie</li>
|
||||
<li>Safari: Настройки → Конфиденциальность → Файлы cookie</li>
|
||||
<li>Microsoft Edge: Настройки → Файлы cookie и разрешения сайтов</li>
|
||||
</ul>
|
||||
|
||||
<p className={styles.warning}>
|
||||
Блокировка необходимых файлов cookie может привести к ограничению функциональности интернет-ресурса.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section className={styles.section}>
|
||||
<h3 className={styles.sectionTitle}>Меры безопасности</h3>
|
||||
<p>
|
||||
Оператор применяет правовые, организационные и технические меры для защиты персональных данных:
|
||||
</p>
|
||||
<h4 className={styles.subSectionTitle}>Правовые меры:</h4>
|
||||
<ul className={styles.list}>
|
||||
<li>Назначение ответственного за организацию обработки персональных данных</li>
|
||||
<li>Ознакомление сотрудников с требованиями законодательства</li>
|
||||
<li>Заключение соглашений о неразглашении персональных данных</li>
|
||||
</ul>
|
||||
|
||||
<h4 className={styles.subSectionTitle}>Организационные меры:</h4>
|
||||
<ul className={styles.list}>
|
||||
<li>Определение перечня лиц, допущенных к обработке персональных данных</li>
|
||||
<li>Установление правил доступа к персональным данным</li>
|
||||
<li>Контроль за соблюдением требований по защите персональных данных</li>
|
||||
</ul>
|
||||
|
||||
<h4 className={styles.subSectionTitle}>Технические меры:</h4>
|
||||
<ul className={styles.list}>
|
||||
<li>Использование средств защиты информации</li>
|
||||
<li>Применение криптографических средств защиты</li>
|
||||
<li>Обеспечение целостности и доступности персональных данных</li>
|
||||
<li>Регулярное обновление систем защиты информации</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section className={styles.section}>
|
||||
<h3 className={styles.sectionTitle}>Контактная информация и обращения</h3>
|
||||
<p>Для реализации прав субъекта персональных данных обращайтесь к нам:</p>
|
||||
<div className={styles.info}>
|
||||
<p>ООО «БИТФОРС»</p>
|
||||
<p>ИНН: 9810001062</p>
|
||||
<p>ОГРН: 1257800060990</p>
|
||||
<p>Юридический адрес: 196246, город Санкт-Петербург, Московское ш, д. 25 к. 1 литера В, помещ. 3-н</p>
|
||||
<p>Email компании: company@bitforcefoundation.ru</p>
|
||||
</div>
|
||||
|
||||
<h4 className={styles.subSectionTitle}>Порядок рассмотрения обращений:</h4>
|
||||
<ul className={styles.list}>
|
||||
<li>Срок рассмотрения обращений — до 30 дней с момента получения</li>
|
||||
<li>Обращения рассматриваются в письменной форме</li>
|
||||
<li>Ответ направляется способом, указанным в обращении</li>
|
||||
<li>При отказе в удовлетворении требований указываются мотивированные основания</li>
|
||||
</ul>
|
||||
</section>
|
||||
</div>
|
||||
</main>
|
||||
<LegalDoc doc={politikaCookieDoc} />
|
||||
<Footer />
|
||||
</>
|
||||
)
|
||||
|
||||
361
src/pages/politika-personalnyh-dannyh/model/content.ts
Normal file
361
src/pages/politika-personalnyh-dannyh/model/content.ts
Normal file
@@ -0,0 +1,361 @@
|
||||
import type { LegalDocData } from '@entities/legal-doc'
|
||||
|
||||
export const politikaPersonalnyhDannyhDoc: LegalDocData = {
|
||||
title: 'ПОЛИТИКА ОБРАБОТКИ ПЕРСОНАЛЬНЫХ ДАННЫХ',
|
||||
subtitle: 'ООО «БИТФОРС»',
|
||||
sections: [
|
||||
{
|
||||
title: '1. Общие положения',
|
||||
blocks: [
|
||||
{
|
||||
kind: 'text',
|
||||
text: 'Настоящая Политика обработки персональных данных разработана в соответствии с Федеральным законом от 27.07.2006 № 152-ФЗ «О персональных данных» и определяет порядок обработки персональных данных и меры по обеспечению безопасности персональных данных, предпринимаемые ООО «БИТФОРС».',
|
||||
},
|
||||
{
|
||||
kind: 'text',
|
||||
text: 'Оператор ставит своей важнейшей целью и условием осуществления своей деятельности соблюдение прав и свобод человека и гражданина при обработке его персональных данных, в том числе защиты права на неприкосновенность частной жизни, личную и семейную тайну.',
|
||||
},
|
||||
{
|
||||
kind: 'text',
|
||||
text: 'Настоящая Политика действует в отношении всех персональных данных, которые обрабатываются Оператором с использованием средств автоматизации и без использования таких средств.',
|
||||
},
|
||||
{ kind: 'subheading', text: '1.4. Основные понятия' },
|
||||
{
|
||||
kind: 'definitions',
|
||||
items: [
|
||||
{
|
||||
term: 'Автоматизированная обработка персональных данных',
|
||||
text: '— обработка персональных данных с помощью средств вычислительной техники.',
|
||||
},
|
||||
{
|
||||
term: 'Обработка персональных данных',
|
||||
text: '— любое действие или совокупность действий, совершаемых с использованием средств автоматизации или без использования таких средств с персональными данными, включая сбор, запись, систематизацию, накопление, хранение, уточнение, извлечение, использование, передачу, обезличивание, блокирование, удаление, уничтожение.',
|
||||
},
|
||||
{
|
||||
term: 'Оператор',
|
||||
text: '— юридическое или физическое лицо, организующие и осуществляющие обработку персональных данных.',
|
||||
},
|
||||
{
|
||||
term: 'Персональные данные',
|
||||
text: '— любая информация, относящаяся к прямо или косвенно определенному или определяемому физическому лицу.',
|
||||
},
|
||||
{
|
||||
term: 'Пользователь',
|
||||
text: '— любой посетитель веб-сайта https://bitforce-foundation.ru.',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: '2. Сведения об операторе',
|
||||
blocks: [
|
||||
{
|
||||
kind: 'list',
|
||||
items: [
|
||||
'Полное наименование: Общество с ограниченной ответственностью «БИТФОРС»',
|
||||
'Сокращенное наименование: ООО «БИТФОРС»',
|
||||
'ИНН: 9810001062',
|
||||
'ОГРН: 1257800060990',
|
||||
'Юридический адрес: 196246, город Санкт-Петербург, Московское шоссе, дом 25, корпус 1, литера В, помещение 3-н',
|
||||
'Электронная почта: company@bitforcefoundation.ru',
|
||||
'Веб-сайт: https://bitforce-foundation.ru',
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: '3. Общие цели обработки персональных данных',
|
||||
blocks: [
|
||||
{ kind: 'subheading', text: '3.1.1. Основная деятельность:' },
|
||||
{
|
||||
kind: 'list',
|
||||
items: [
|
||||
'Предоставление услуг по конвертации иного имущества',
|
||||
'Осуществление операций на криптовалютных рынках',
|
||||
'Предоставление услуг в области блокчейн технологий',
|
||||
'Обеспечение функционирования интернет-платформы и мобильных приложений',
|
||||
],
|
||||
},
|
||||
{ kind: 'subheading', text: '3.1.2. Обеспечение безопасности:' },
|
||||
{
|
||||
kind: 'list',
|
||||
items: [
|
||||
'Предотвращение мошенничества и отмывания денежных средств',
|
||||
'Обеспечение безопасности платежных операций',
|
||||
'Выполнение требований по противодействию легализации доходов',
|
||||
'Идентификация и верификация клиентов',
|
||||
],
|
||||
},
|
||||
{ kind: 'subheading', text: '3.1.3. Соблюдение законодательства:' },
|
||||
{
|
||||
kind: 'list',
|
||||
items: [
|
||||
'Исполнение требований российского и международного законодательства',
|
||||
'Взаимодействие с контролирующими и правоохранительными органами',
|
||||
'Ведение обязательной отчетности и документооборота',
|
||||
'Соблюдение налогового законодательства',
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: '4. Цели сбора персональных данных',
|
||||
blocks: [
|
||||
{
|
||||
kind: 'goals',
|
||||
items: [
|
||||
{
|
||||
title: 'Регистрация и идентификация пользователей:',
|
||||
items: [
|
||||
'Создание учетной записи на веб-сайте',
|
||||
'Верификация личности в соответствии с требованиями законодательства',
|
||||
'Подтверждение права на осуществление операций',
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Обработка платежей и финансовых операций:',
|
||||
items: [
|
||||
'Осуществление операций по конвертации криптовалют',
|
||||
'Проведение расчетов и переводов денежных средств',
|
||||
'Ведение учета и истории транзакций',
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Коммуникация с клиентами:',
|
||||
items: [
|
||||
'Предоставление технической поддержки',
|
||||
'Уведомления о состоянии операций и счетов',
|
||||
'Информирование об изменениях в условиях предоставления услуг',
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: '5. Правовые основания обработки персональных данных',
|
||||
blocks: [
|
||||
{ kind: 'subheading', text: '5.1.1. Согласие субъекта персональных данных:' },
|
||||
{
|
||||
kind: 'list',
|
||||
items: [
|
||||
'Обработка персональных данных в маркетинговых целях',
|
||||
'Использование файлов cookie и метрик',
|
||||
'Персонализация сервисов и предложений',
|
||||
],
|
||||
},
|
||||
{ kind: 'subheading', text: '5.1.2. Необходимость исполнения договора:' },
|
||||
{
|
||||
kind: 'list',
|
||||
items: [
|
||||
'Регистрация и ведение учетных записей пользователей',
|
||||
'Осуществление финансовых операций и переводов',
|
||||
'Предоставление доступа к платформе и сервисам',
|
||||
'Техническая поддержка и обслуживание клиентов',
|
||||
],
|
||||
},
|
||||
{ kind: 'subheading', text: '5.1.3. Соблюдение правовой обязанности:' },
|
||||
{
|
||||
kind: 'list',
|
||||
items: [
|
||||
'Выполнение требований валютного законодательства',
|
||||
'Противодействие легализации доходов, полученных преступным путем',
|
||||
'Соблюдение требований по налоговому учету и отчетности',
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: '6. Объем и категории обрабатываемых персональных данных',
|
||||
blocks: [
|
||||
{ kind: 'subheading', text: '6.1.1. Пользователи веб-сайта и мобильного приложения:' },
|
||||
{
|
||||
kind: 'list',
|
||||
items: [
|
||||
'Зарегистрированные пользователи',
|
||||
'Посетители сайта без регистрации',
|
||||
'Потенциальные клиенты',
|
||||
'Бывшие клиенты',
|
||||
],
|
||||
},
|
||||
{ kind: 'subheading', text: '6.2.1. Идентификационные данные:' },
|
||||
{ kind: 'list', items: ['Фамилия, имя, отчество', 'Дата рождения', 'Гражданство'] },
|
||||
{ kind: 'subheading', text: '6.2.3. Контактная информация:' },
|
||||
{
|
||||
kind: 'list',
|
||||
items: ['Номера телефонов (мобильный, домашний, рабочий)', 'Адреса электронной почты'],
|
||||
},
|
||||
{ kind: 'subheading', text: '6.2.4. Финансовая информация:' },
|
||||
{
|
||||
kind: 'list',
|
||||
items: [
|
||||
'Номера банковских счетов и карт',
|
||||
'Реквизиты кошельков криптовалют',
|
||||
'История операций и транзакций',
|
||||
'Данные о доходах и источниках средств',
|
||||
],
|
||||
},
|
||||
{ kind: 'subheading', text: '6.2.5. Техническая информация:' },
|
||||
{
|
||||
kind: 'list',
|
||||
items: [
|
||||
'IP-адреса устройств',
|
||||
'Данные о браузере и операционной системе',
|
||||
'Файлы cookie и локальное хранилище',
|
||||
'Логи действий на сайте',
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: '7. Порядок и условия обработки персональных данных',
|
||||
blocks: [
|
||||
{ kind: 'subheading', text: '7.1. Принципы обработки персональных данных:' },
|
||||
{
|
||||
kind: 'list',
|
||||
items: [
|
||||
'Обработка осуществляется на законной и справедливой основе',
|
||||
'Обработка ограничивается достижением конкретных, заранее определенных целей',
|
||||
'Содержание и объем данных соответствуют заявленным целям',
|
||||
'Обрабатываемые персональные данные являются точными и актуальными',
|
||||
],
|
||||
},
|
||||
{ kind: 'subheading', text: '7.4. Сроки обработки персональных данных:' },
|
||||
{
|
||||
kind: 'list',
|
||||
items: [
|
||||
'Персональные данные обрабатываются в течение времени, необходимого для достижения целей',
|
||||
'После достижения целей персональные данные подлежат уничтожению или обезличиванию',
|
||||
'Сроки хранения определяются требованиями законодательства',
|
||||
],
|
||||
},
|
||||
{ kind: 'subheading', text: '7.5. Места обработки персональных данных:' },
|
||||
{
|
||||
kind: 'list',
|
||||
items: [
|
||||
'Основные серверы и хранилища данных расположены на территории Российской Федерации',
|
||||
'Резервные копии могут храниться в дата-центрах на территории РФ',
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: '8. Актуализация, исправление, удаление и уничтожение персональных данных',
|
||||
blocks: [
|
||||
{ kind: 'subheading', text: '8.2.2. Процедура исправления:' },
|
||||
{
|
||||
kind: 'list',
|
||||
items: [
|
||||
'Рассмотрение запроса в течение 30 дней',
|
||||
'Проверка обоснованности требования об исправлении',
|
||||
'Внесение изменений во все информационные системы',
|
||||
'Уведомление субъекта о проведенных исправлениях',
|
||||
],
|
||||
},
|
||||
{ kind: 'subheading', text: '8.3.2. Процедура удаления:' },
|
||||
{
|
||||
kind: 'list',
|
||||
items: [
|
||||
'Проверка наличия законных оснований для продолжения обработки',
|
||||
'Удаление из всех информационных систем и баз данных',
|
||||
'Удаление резервных копий (кроме архивных)',
|
||||
'Уведомление субъекта о выполненном удалении',
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: '9. Ответы на запросы субъектов персональных данных',
|
||||
blocks: [
|
||||
{ kind: 'subheading', text: '9.1.1. Право на информацию:' },
|
||||
{
|
||||
kind: 'list',
|
||||
items: [
|
||||
'Подтверждение факта обработки персональных данных',
|
||||
'Правовые основания и цели обработки',
|
||||
'Применяемые способы обработки',
|
||||
'Наименование и местонахождение оператора',
|
||||
'Лица, имеющие доступ к персональным данным',
|
||||
],
|
||||
},
|
||||
{ kind: 'subheading', text: '9.2.2. Сроки рассмотрения:' },
|
||||
{
|
||||
kind: 'list',
|
||||
items: [
|
||||
'Срок рассмотрения запроса составляет 30 дней с момента получения',
|
||||
'Срок может быть продлен на 30 дней при большом объеме информации',
|
||||
'О продлении срока субъект уведомляется в течение 30 дней',
|
||||
],
|
||||
},
|
||||
{ kind: 'subheading', text: '9.4. Плата за предоставление информации:' },
|
||||
{
|
||||
kind: 'list',
|
||||
items: [
|
||||
'Первый запрос в течение года обрабатывается бесплатно',
|
||||
'За повторные запросы может взиматься плата в размере расходов',
|
||||
'Субъект уведомляется о размере платы до предоставления информации',
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: '10. Обеспечение безопасности персональных данных',
|
||||
blocks: [
|
||||
{ kind: 'subheading', text: '10.1. Правовые меры:' },
|
||||
{
|
||||
kind: 'list',
|
||||
items: [
|
||||
'Назначение ответственного за организацию обработки персональных данных',
|
||||
'Принятие локальных актов по вопросам обработки персональных данных',
|
||||
'Ознакомление работников с требованиями законодательства',
|
||||
'Применение мер ответственности за нарушение требований',
|
||||
],
|
||||
},
|
||||
{ kind: 'subheading', text: '10.3. Технические меры:' },
|
||||
{
|
||||
kind: 'list',
|
||||
items: [
|
||||
'Предотвращение несанкционированного доступа к персональным данным',
|
||||
'Своевременное обнаружение фактов несанкционированного доступа',
|
||||
'Возможность незамедлительного восстановления персональных данных',
|
||||
'Постоянный контроль за обеспечением уровня защищенности',
|
||||
],
|
||||
},
|
||||
{ kind: 'subheading', text: '10.4. Конкретные технические решения:' },
|
||||
{
|
||||
kind: 'list',
|
||||
items: [
|
||||
'Использование сертифицированных средств защиты информации',
|
||||
'Шифрование персональных данных при передаче и хранении',
|
||||
'Применение межсетевых экранов и систем обнаружения вторжений',
|
||||
'Резервное копирование и обеспечение отказоустойчивости',
|
||||
'Антивирусная защита и обновление программного обеспечения',
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: '12. Заключительные положения',
|
||||
blocks: [
|
||||
{ kind: 'subheading', text: '12.2. Жалобы и обращения:' },
|
||||
{
|
||||
kind: 'list',
|
||||
items: [
|
||||
'Субъекты персональных данных могут обратиться к Оператору по вопросам обработки',
|
||||
'Жалобы рассматриваются в установленном законом порядке',
|
||||
'При неурегулировании разногласий возможно обращение в Роскомнадзор или суд',
|
||||
],
|
||||
},
|
||||
{ kind: 'subheading', text: '12.4. Контактная информация для обращений:' },
|
||||
{
|
||||
kind: 'infoBox',
|
||||
lines: [
|
||||
'Почтовый адрес: 196246, г. Санкт-Петербург, Московское ш., д. 25, к. 1, лит. В, пом. 3-н',
|
||||
'Электронная почта: company@bitforcefoundation.ru',
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
@@ -1,299 +1,13 @@
|
||||
import { LegalDoc } from '@entities/legal-doc'
|
||||
import { Footer } from '@widgets/footer'
|
||||
import { Header } from '@widgets/header'
|
||||
import styles from './PolitikaPage.module.css'
|
||||
import { politikaPersonalnyhDannyhDoc } from '../model/content'
|
||||
|
||||
export function PolitikaPage() {
|
||||
return (
|
||||
<>
|
||||
<Header />
|
||||
<main className={styles.main}>
|
||||
<div className={styles.container}>
|
||||
<h1 className={styles.title}>ПОЛИТИКА ОБРАБОТКИ ПЕРСОНАЛЬНЫХ ДАННЫХ</h1>
|
||||
<h2 className={styles.subtitle}>ООО «БИТФОРС»</h2>
|
||||
|
||||
<section className={styles.section}>
|
||||
<h3 className={styles.sectionTitle}>1. Общие положения</h3>
|
||||
<p>
|
||||
Настоящая Политика обработки персональных данных разработана в соответствии с Федеральным законом от 27.07.2006 № 152-ФЗ «О персональных данных» и определяет порядок обработки персональных данных и меры по обеспечению безопасности персональных данных, предпринимаемые ООО «БИТФОРС».
|
||||
</p>
|
||||
<p>
|
||||
Оператор ставит своей важнейшей целью и условием осуществления своей деятельности соблюдение прав и свобод человека и гражданина при обработке его персональных данных, в том числе защиты права на неприкосновенность частной жизни, личную и семейную тайну.
|
||||
</p>
|
||||
<p>
|
||||
Настоящая Политика действует в отношении всех персональных данных, которые обрабатываются Оператором с использованием средств автоматизации и без использования таких средств.
|
||||
</p>
|
||||
|
||||
<h4 className={styles.subSectionTitle}>1.4. Основные понятия</h4>
|
||||
<div className={styles.definitions}>
|
||||
<div className={styles.definition}>
|
||||
<strong>Автоматизированная обработка персональных данных</strong> — обработка персональных данных с помощью средств вычислительной техники.
|
||||
</div>
|
||||
<div className={styles.definition}>
|
||||
<strong>Обработка персональных данных</strong> — любое действие или совокупность действий, совершаемых с использованием средств автоматизации или без использования таких средств с персональными данными, включая сбор, запись, систематизацию, накопление, хранение, уточнение, извлечение, использование, передачу, обезличивание, блокирование, удаление, уничтожение.
|
||||
</div>
|
||||
<div className={styles.definition}>
|
||||
<strong>Оператор</strong> — юридическое или физическое лицо, организующие и осуществляющие обработку персональных данных.
|
||||
</div>
|
||||
<div className={styles.definition}>
|
||||
<strong>Персональные данные</strong> — любая информация, относящаяся к прямо или косвенно определенному или определяемому физическому лицу.
|
||||
</div>
|
||||
<div className={styles.definition}>
|
||||
<strong>Пользователь</strong> — любой посетитель веб-сайта https://bitforce-foundation.ru.
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className={styles.section}>
|
||||
<h3 className={styles.sectionTitle}>2. Сведения об операторе</h3>
|
||||
<ul className={styles.list}>
|
||||
<li>Полное наименование: Общество с ограниченной ответственностью «БИТФОРС»</li>
|
||||
<li>Сокращенное наименование: ООО «БИТФОРС»</li>
|
||||
<li>ИНН: 9810001062</li>
|
||||
<li>ОГРН: 1257800060990</li>
|
||||
<li>Юридический адрес: 196246, город Санкт-Петербург, Московское шоссе, дом 25, корпус 1, литера В, помещение 3-н</li>
|
||||
<li>Электронная почта: company@bitforcefoundation.ru</li>
|
||||
<li>Веб-сайт: https://bitforce-foundation.ru</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section className={styles.section}>
|
||||
<h3 className={styles.sectionTitle}>3. Общие цели обработки персональных данных</h3>
|
||||
<h4 className={styles.subSectionTitle}>3.1.1. Основная деятельность:</h4>
|
||||
<ul className={styles.list}>
|
||||
<li>Предоставление услуг по конвертации иного имущества</li>
|
||||
<li>Осуществление операций на криптовалютных рынках</li>
|
||||
<li>Предоставление услуг в области блокчейн технологий</li>
|
||||
<li>Обеспечение функционирования интернет-платформы и мобильных приложений</li>
|
||||
</ul>
|
||||
|
||||
<h4 className={styles.subSectionTitle}>3.1.2. Обеспечение безопасности:</h4>
|
||||
<ul className={styles.list}>
|
||||
<li>Предотвращение мошенничества и отмывания денежных средств</li>
|
||||
<li>Обеспечение безопасности платежных операций</li>
|
||||
<li>Выполнение требований по противодействию легализации доходов</li>
|
||||
<li>Идентификация и верификация клиентов</li>
|
||||
</ul>
|
||||
|
||||
<h4 className={styles.subSectionTitle}>3.1.3. Соблюдение законодательства:</h4>
|
||||
<ul className={styles.list}>
|
||||
<li>Исполнение требований российского и международного законодательства</li>
|
||||
<li>Взаимодействие с контролирующими и правоохранительными органами</li>
|
||||
<li>Ведение обязательной отчетности и документооборота</li>
|
||||
<li>Соблюдение налогового законодательства</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section className={styles.section}>
|
||||
<h3 className={styles.sectionTitle}>4. Цели сбора персональных данных</h3>
|
||||
<div className={styles.goalsList}>
|
||||
<div className={styles.goal}>
|
||||
<strong>Регистрация и идентификация пользователей:</strong>
|
||||
<ul>
|
||||
<li>Создание учетной записи на веб-сайте</li>
|
||||
<li>Верификация личности в соответствии с требованиями законодательства</li>
|
||||
<li>Подтверждение права на осуществление операций</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div className={styles.goal}>
|
||||
<strong>Обработка платежей и финансовых операций:</strong>
|
||||
<ul>
|
||||
<li>Осуществление операций по конвертации криптовалют</li>
|
||||
<li>Проведение расчетов и переводов денежных средств</li>
|
||||
<li>Ведение учета и истории транзакций</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div className={styles.goal}>
|
||||
<strong>Коммуникация с клиентами:</strong>
|
||||
<ul>
|
||||
<li>Предоставление технической поддержки</li>
|
||||
<li>Уведомления о состоянии операций и счетов</li>
|
||||
<li>Информирование об изменениях в условиях предоставления услуг</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className={styles.section}>
|
||||
<h3 className={styles.sectionTitle}>5. Правовые основания обработки персональных данных</h3>
|
||||
<h4 className={styles.subSectionTitle}>5.1.1. Согласие субъекта персональных данных:</h4>
|
||||
<ul className={styles.list}>
|
||||
<li>Обработка персональных данных в маркетинговых целях</li>
|
||||
<li>Использование файлов cookie и метрик</li>
|
||||
<li>Персонализация сервисов и предложений</li>
|
||||
</ul>
|
||||
|
||||
<h4 className={styles.subSectionTitle}>5.1.2. Необходимость исполнения договора:</h4>
|
||||
<ul className={styles.list}>
|
||||
<li>Регистрация и ведение учетных записей пользователей</li>
|
||||
<li>Осуществление финансовых операций и переводов</li>
|
||||
<li>Предоставление доступа к платформе и сервисам</li>
|
||||
<li>Техническая поддержка и обслуживание клиентов</li>
|
||||
</ul>
|
||||
|
||||
<h4 className={styles.subSectionTitle}>5.1.3. Соблюдение правовой обязанности:</h4>
|
||||
<ul className={styles.list}>
|
||||
<li>Выполнение требований валютного законодательства</li>
|
||||
<li>Противодействие легализации доходов, полученных преступным путем</li>
|
||||
<li>Соблюдение требований по налоговому учету и отчетности</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section className={styles.section}>
|
||||
<h3 className={styles.sectionTitle}>6. Объем и категории обрабатываемых персональных данных</h3>
|
||||
<h4 className={styles.subSectionTitle}>6.1.1. Пользователи веб-сайта и мобильного приложения:</h4>
|
||||
<ul className={styles.list}>
|
||||
<li>Зарегистрированные пользователи</li>
|
||||
<li>Посетители сайта без регистрации</li>
|
||||
<li>Потенциальные клиенты</li>
|
||||
<li>Бывшие клиенты</li>
|
||||
</ul>
|
||||
|
||||
<h4 className={styles.subSectionTitle}>6.2.1. Идентификационные данные:</h4>
|
||||
<ul className={styles.list}>
|
||||
<li>Фамилия, имя, отчество</li>
|
||||
<li>Дата рождения</li>
|
||||
<li>Гражданство</li>
|
||||
</ul>
|
||||
|
||||
<h4 className={styles.subSectionTitle}>6.2.3. Контактная информация:</h4>
|
||||
<ul className={styles.list}>
|
||||
<li>Номера телефонов (мобильный, домашний, рабочий)</li>
|
||||
<li>Адреса электронной почты</li>
|
||||
</ul>
|
||||
|
||||
<h4 className={styles.subSectionTitle}>6.2.4. Финансовая информация:</h4>
|
||||
<ul className={styles.list}>
|
||||
<li>Номера банковских счетов и карт</li>
|
||||
<li>Реквизиты кошельков криптовалют</li>
|
||||
<li>История операций и транзакций</li>
|
||||
<li>Данные о доходах и источниках средств</li>
|
||||
</ul>
|
||||
|
||||
<h4 className={styles.subSectionTitle}>6.2.5. Техническая информация:</h4>
|
||||
<ul className={styles.list}>
|
||||
<li>IP-адреса устройств</li>
|
||||
<li>Данные о браузере и операционной системе</li>
|
||||
<li>Файлы cookie и локальное хранилище</li>
|
||||
<li>Логи действий на сайте</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section className={styles.section}>
|
||||
<h3 className={styles.sectionTitle}>7. Порядок и условия обработки персональных данных</h3>
|
||||
<h4 className={styles.subSectionTitle}>7.1. Принципы обработки персональных данных:</h4>
|
||||
<ul className={styles.list}>
|
||||
<li>Обработка осуществляется на законной и справедливой основе</li>
|
||||
<li>Обработка ограничивается достижением конкретных, заранее определенных целей</li>
|
||||
<li>Содержание и объем данных соответствуют заявленным целям</li>
|
||||
<li>Обрабатываемые персональные данные являются точными и актуальными</li>
|
||||
</ul>
|
||||
|
||||
<h4 className={styles.subSectionTitle}>7.4. Сроки обработки персональных данных:</h4>
|
||||
<ul className={styles.list}>
|
||||
<li>Персональные данные обрабатываются в течение времени, необходимого для достижения целей</li>
|
||||
<li>После достижения целей персональные данные подлежат уничтожению или обезличиванию</li>
|
||||
<li>Сроки хранения определяются требованиями законодательства</li>
|
||||
</ul>
|
||||
|
||||
<h4 className={styles.subSectionTitle}>7.5. Места обработки персональных данных:</h4>
|
||||
<ul className={styles.list}>
|
||||
<li>Основные серверы и хранилища данных расположены на территории Российской Федерации</li>
|
||||
<li>Резервные копии могут храниться в дата-центрах на территории РФ</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section className={styles.section}>
|
||||
<h3 className={styles.sectionTitle}>8. Актуализация, исправление, удаление и уничтожение персональных данных</h3>
|
||||
<h4 className={styles.subSectionTitle}>8.2.2. Процедура исправления:</h4>
|
||||
<ul className={styles.list}>
|
||||
<li>Рассмотрение запроса в течение 30 дней</li>
|
||||
<li>Проверка обоснованности требования об исправлении</li>
|
||||
<li>Внесение изменений во все информационные системы</li>
|
||||
<li>Уведомление субъекта о проведенных исправлениях</li>
|
||||
</ul>
|
||||
|
||||
<h4 className={styles.subSectionTitle}>8.3.2. Процедура удаления:</h4>
|
||||
<ul className={styles.list}>
|
||||
<li>Проверка наличия законных оснований для продолжения обработки</li>
|
||||
<li>Удаление из всех информационных систем и баз данных</li>
|
||||
<li>Удаление резервных копий (кроме архивных)</li>
|
||||
<li>Уведомление субъекта о выполненном удалении</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section className={styles.section}>
|
||||
<h3 className={styles.sectionTitle}>9. Ответы на запросы субъектов персональных данных</h3>
|
||||
<h4 className={styles.subSectionTitle}>9.1.1. Право на информацию:</h4>
|
||||
<ul className={styles.list}>
|
||||
<li>Подтверждение факта обработки персональных данных</li>
|
||||
<li>Правовые основания и цели обработки</li>
|
||||
<li>Применяемые способы обработки</li>
|
||||
<li>Наименование и местонахождение оператора</li>
|
||||
<li>Лица, имеющие доступ к персональным данным</li>
|
||||
</ul>
|
||||
|
||||
<h4 className={styles.subSectionTitle}>9.2.2. Сроки рассмотрения:</h4>
|
||||
<ul className={styles.list}>
|
||||
<li>Срок рассмотрения запроса составляет 30 дней с момента получения</li>
|
||||
<li>Срок может быть продлен на 30 дней при большом объеме информации</li>
|
||||
<li>О продлении срока субъект уведомляется в течение 30 дней</li>
|
||||
</ul>
|
||||
|
||||
<h4 className={styles.subSectionTitle}>9.4. Плата за предоставление информации:</h4>
|
||||
<ul className={styles.list}>
|
||||
<li>Первый запрос в течение года обрабатывается бесплатно</li>
|
||||
<li>За повторные запросы может взиматься плата в размере расходов</li>
|
||||
<li>Субъект уведомляется о размере платы до предоставления информации</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section className={styles.section}>
|
||||
<h3 className={styles.sectionTitle}>10. Обеспечение безопасности персональных данных</h3>
|
||||
<h4 className={styles.subSectionTitle}>10.1. Правовые меры:</h4>
|
||||
<ul className={styles.list}>
|
||||
<li>Назначение ответственного за организацию обработки персональных данных</li>
|
||||
<li>Принятие локальных актов по вопросам обработки персональных данных</li>
|
||||
<li>Ознакомление работников с требованиями законодательства</li>
|
||||
<li>Применение мер ответственности за нарушение требований</li>
|
||||
</ul>
|
||||
|
||||
<h4 className={styles.subSectionTitle}>10.3. Технические меры:</h4>
|
||||
<ul className={styles.list}>
|
||||
<li>Предотвращение несанкционированного доступа к персональным данным</li>
|
||||
<li>Своевременное обнаружение фактов несанкционированного доступа</li>
|
||||
<li>Возможность незамедлительного восстановления персональных данных</li>
|
||||
<li>Постоянный контроль за обеспечением уровня защищенности</li>
|
||||
</ul>
|
||||
|
||||
<h4 className={styles.subSectionTitle}>10.4. Конкретные технические решения:</h4>
|
||||
<ul className={styles.list}>
|
||||
<li>Использование сертифицированных средств защиты информации</li>
|
||||
<li>Шифрование персональных данных при передаче и хранении</li>
|
||||
<li>Применение межсетевых экранов и систем обнаружения вторжений</li>
|
||||
<li>Резервное копирование и обеспечение отказоустойчивости</li>
|
||||
<li>Антивирусная защита и обновление программного обеспечения</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section className={styles.section}>
|
||||
<h3 className={styles.sectionTitle}>12. Заключительные положения</h3>
|
||||
<h4 className={styles.subSectionTitle}>12.2. Жалобы и обращения:</h4>
|
||||
<ul className={styles.list}>
|
||||
<li>Субъекты персональных данных могут обратиться к Оператору по вопросам обработки</li>
|
||||
<li>Жалобы рассматриваются в установленном законом порядке</li>
|
||||
<li>При неурегулировании разногласий возможно обращение в Роскомнадзор или суд</li>
|
||||
</ul>
|
||||
|
||||
<h4 className={styles.subSectionTitle}>12.4. Контактная информация для обращений:</h4>
|
||||
<p className={styles.contacts}>
|
||||
Почтовый адрес: 196246, г. Санкт-Петербург, Московское ш., д. 25, к. 1, лит. В, пом. 3-н<br />
|
||||
Электронная почта: company@bitforcefoundation.ru
|
||||
</p>
|
||||
</section>
|
||||
</div>
|
||||
</main>
|
||||
<LegalDoc doc={politikaPersonalnyhDannyhDoc} />
|
||||
<Footer />
|
||||
</>
|
||||
)
|
||||
|
||||
137
src/pages/profile/README.md
Normal file
137
src/pages/profile/README.md
Normal file
@@ -0,0 +1,137 @@
|
||||
# Страница профиля (`/profile`)
|
||||
|
||||
Личный кабинет пользователя: показывает данные аккаунта, баланс, адреса кошельков
|
||||
и даёт доступ к мнемонике. Маршрут `ROUTES.PROFILE` (`/profile`), защищён
|
||||
`ProtectedRoute` (только для авторизованных).
|
||||
|
||||
## Главная идея
|
||||
|
||||
Страница — **тонкая композиция виджетов**. В `ProfilePage.tsx` нет ни хуков данных,
|
||||
ни стейта, ни дериваций — только разметка. Вся логика и презентация живут в слайсе
|
||||
`widgets/profile` и в нижних слоях (`features/auth`, `features/wallet`, `shared`).
|
||||
Это сознательный результат рефакторинга под FSD (`app → pages → widgets → features →
|
||||
entities → shared`; нижние слои не импортируют верхние).
|
||||
|
||||
Ключевой приём: **каждый под-виджет сам тянет свои данные** через хуки. Прокидывать
|
||||
`data` пропсами со страницы не нужно — `useMe` использует `queryKey: ['me']` со
|
||||
`staleTime/gcTime: Infinity`, поэтому вызов `useMe()` в нескольких компонентах
|
||||
дедуплицируется в один сетевой запрос. Та же логика, что в `WalletPage`.
|
||||
|
||||
## Карта файлов
|
||||
|
||||
```
|
||||
pages/profile/
|
||||
ui/ProfilePage.tsx ← композиция (без логики)
|
||||
ui/ProfilePage.module.css ← только внешний каркас: .page .main .sections
|
||||
index.ts ← export { ProfilePage }
|
||||
README.md ← этот файл
|
||||
|
||||
widgets/profile/ ← ВСЁ содержимое страницы живёт здесь
|
||||
model/usePhoneField.ts ← логика редактирования телефона + стейт нотификации
|
||||
ui/
|
||||
ProfileSummary.tsx ← аватар + имя + баланс (self-fetch: useMe, usePortfolio)
|
||||
ProfileDetails.tsx ← выбирает ветку individual/legal (self-fetch: useMe)
|
||||
IndividualFields.tsx ← поля физлица; зовёт usePhoneField + рендерит Notification
|
||||
LegalEntityFields.tsx ← поля юрлица (read-only, из data.legal_entity)
|
||||
SecuritySection.tsx ← адреса кошельков (self-fetch: useWalletAddresses)
|
||||
MnemonicSection.tsx ← кнопка → ROUTES.SEED_PHRASE
|
||||
KycBanner.tsx ← баннер, если !kyc_verified (self-fetch: useMe)
|
||||
ProfileAvatar.tsx ← загрузка/кроп аватара (был раньше)
|
||||
AvatarCropModal.tsx ← модалка кропа (был раньше)
|
||||
ProfileSection.tsx ← обёртка-карточка: title + children + actions (был раньше)
|
||||
fieldsGrid.module.css ← общие .grid2/.grid1 (для Individual/Legal/Security)
|
||||
*.module.css ← по одному на компонент с собственными стилями
|
||||
index.ts ← барель: экспортит верхнеуровневые компоненты
|
||||
|
||||
features/auth/lib/profile.ts ← getFullName / getDisplayName / isLegalEntity (над MeResponse)
|
||||
shared/lib/utils/formatUsd.ts← formatUsd(value) → "$1,234.56" | "$—"
|
||||
```
|
||||
|
||||
`IndividualFields` и `LegalEntityFields` — **внутренние** для виджета: в барель
|
||||
(`widgets/profile/index.ts`) не выносятся, их использует только `ProfileDetails`
|
||||
относительным импортом.
|
||||
|
||||
## Поток данных
|
||||
|
||||
| Источник | Откуда | Что даёт |
|
||||
|---|---|---|
|
||||
| `useMe()` | `@features/auth` | `MeResponse`: ФИО, email, паспорт, ИНН, `account_type`, `kyc_verified`, `legal_entity` |
|
||||
| `usePortfolio()` | `@features/wallet` | `portfolio.totalUsd` для баланса |
|
||||
| `useWalletAddresses()` | `@features/wallet` | массив `{ chain, address }` |
|
||||
| `useUpdatePhone()` | `@features/auth` | мутация сохранения телефона (внутри `usePhoneField`) |
|
||||
|
||||
`MeResponse` определён в `features/auth/api/profileApi.ts`. Намеренно **не** перенесён
|
||||
в пустой `entities/user` — это была бы отдельная миграция (затрагивает все импорты
|
||||
типа). Если когда-нибудь будете наполнять `entities/user`, тип и хелперы из
|
||||
`features/auth/lib/profile.ts` — первые кандидаты на переезд вниз.
|
||||
|
||||
## Развилка individual / legal
|
||||
|
||||
`ProfileDetails` решает по `isLegalEntity(data)` (`account_type !== 'individual'`):
|
||||
- **individual** → `IndividualFields`: ФИО/email/паспорт/телефон + ИНН/ID. Телефон
|
||||
**редактируемый**.
|
||||
- **legal** → `LegalEntityFields`: данные организации/адреса/контакты из
|
||||
`data.legal_entity` (если `legal_entity` пуст → рендерит `null`). Всё **read-only** —
|
||||
данные юрлица управляются на стороне админа.
|
||||
|
||||
## Логика телефона (`usePhoneField`)
|
||||
|
||||
Единственная нетривиальная логика на странице, живёт в `widgets/profile/model/usePhoneField.ts`
|
||||
(по образцу `widgets/login-form/model/useLoginForm.ts`). Хук владеет:
|
||||
- локальным `phone`, синхронизируемым с серверным значением через `useEffect`;
|
||||
- санитизацией ввода (`onPhoneChange` — regex оставляет только `\d + пробел () -`);
|
||||
- сохранением **по blur** (`onPhoneBlur`) с guard'ами: не сохраняет, если значение не
|
||||
менялось или мутация уже в полёте;
|
||||
- стейтом нотификации (успех/ошибка) — она побочный эффект мутации, вне хука смысла нет.
|
||||
|
||||
Рендер `<Notification>` — в `IndividualFields` (хук владеет стейтом, компонент —
|
||||
рендером). Это тост из `@shared/ui`, позиционируется фиксированно.
|
||||
|
||||
## CSS
|
||||
|
||||
- `ProfilePage.module.css` — только внешний каркас страницы (flex-колонка, центрирование
|
||||
`.main`, отступы, брейкпоинты 1023/639px для паддингов).
|
||||
- Стили секций — рядом со своими компонентами в `widgets/profile/ui/*.module.css`.
|
||||
- `fieldsGrid.module.css` — единственный осознанно общий стиль (`.grid2` 2 колонки →
|
||||
1 колонка на <640px; `.grid1`), импортится из Individual/Legal/Security. Лежит
|
||||
**внутри виджета**, поэтому ни один компонент не лезет в стиль страницы.
|
||||
- Брейкпоинты по проекту: 1023 (десктоп→планшет), 649/639 (телефон), 549 (узкий телефон).
|
||||
|
||||
## Правила при доработке
|
||||
|
||||
1. **Страница остаётся тонкой.** Новый блок профиля = новый компонент в
|
||||
`widgets/profile/ui/` + экспорт в барель + вставка в `ProfilePage.tsx`. Не тащите
|
||||
хуки/стейт обратно в страницу.
|
||||
2. **Данные — через self-fetch хуки**, не через пропсы со страницы (дедуп по queryKey).
|
||||
3. **Деривации над `MeResponse`** → `features/auth/lib/profile.ts` (там же тип). Не
|
||||
дублируйте форматирование имени/`isLegal` в компонентах.
|
||||
4. **Форматирование `$`-сумм** → `formatUsd` из `@shared/lib/utils/formatUsd`.
|
||||
5. Соблюдайте порядок слоёв: виджеты импортируют из features/shared, не наоборот.
|
||||
|
||||
## Известные точки расширения / TODO
|
||||
|
||||
- `ProfileSummary`: закомментирован рублёвый баланс (`.userBalanceRub`, «≈ … ₽») —
|
||||
ждёт реального источника курса.
|
||||
- `SecuritySection`: кнопки «⚠️ Посмотреть приватный ключ» и «СОХРАНИТЬ» сейчас
|
||||
**без обработчиков** (заглушки из исходной вёрстки). При реализации навесить логику.
|
||||
- `formatUsd` дублируется инлайном ещё в 5 местах (BalanceCard, WalletHeader,
|
||||
TokenTable, useChainTokenRows, PoolsTable) — отдельная задача на дедупликацию.
|
||||
Внимание: часть из них использует вариант с 4–6 знаками, а не фиксированные 2 —
|
||||
при миграции, возможно, понадобится расширить `formatUsd` опциями точности.
|
||||
|
||||
## Проверка
|
||||
|
||||
```bash
|
||||
npm run build # tsc -b + vite build (тестов в проекте нет)
|
||||
npm run lint
|
||||
npm run dev # ручная проверка /profile
|
||||
```
|
||||
|
||||
Ручной чек-лист `/profile`:
|
||||
- individual: поля видны; ввод телефона санитизируется; по blur сохраняется и
|
||||
появляется тост «Номер телефона обновлён» (и тост ошибки при сбое);
|
||||
- legal: секции организации/адресов/контактов; телефон read-only;
|
||||
- баланс через `formatUsd` (и `$—` при загрузке);
|
||||
- KYC-баннер виден только при `!kyc_verified`;
|
||||
- «Мнемоника» ведёт на `ROUTES.SEED_PHRASE`;
|
||||
- адаптив на брейкпоинтах 1023/649/639/549 не сломан.
|
||||
@@ -5,17 +5,6 @@
|
||||
background: var(--bg-deep);
|
||||
}
|
||||
|
||||
.kycBanner {
|
||||
width: 100%;
|
||||
padding: 10px 24px;
|
||||
background: var(--error, #ff4466);
|
||||
color: #fff;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
text-align: center;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.main {
|
||||
max-width: 1024px;
|
||||
width: 100%;
|
||||
@@ -25,15 +14,6 @@
|
||||
gap: 32px;
|
||||
}
|
||||
|
||||
/* Desktop: avatar is left column, sections grow right */
|
||||
.profileTop {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.userInfo {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.sections {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
@@ -41,127 +21,18 @@
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.grid2 {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.grid1 {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.mnemonicRow {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.mnemonicInfo {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.mnemonicIcon {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.mnemonicText {
|
||||
color: var(--text-secondary);
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
/* < 1024px: column layout, avatar + userInfo side by side at top */
|
||||
/* < 1024px: avatar + sections stack into a single column */
|
||||
@media (max-width: 1023px) {
|
||||
.main {
|
||||
flex-direction: column;
|
||||
padding: 24px 20px 40px;
|
||||
gap: 24px;
|
||||
}
|
||||
|
||||
.grid2 {
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.profileTop {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: flex-start;
|
||||
gap: 24px;
|
||||
}
|
||||
|
||||
.userInfo {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.userName {
|
||||
font-size: 28px;
|
||||
font-weight: 700;
|
||||
color: var(--text-primary);
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.userBalance {
|
||||
font-size: 32px;
|
||||
font-weight: 800;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.userBalanceRub {
|
||||
font-size: 13px;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
}
|
||||
|
||||
/* < 650px: card padding 16px (в ProfileSection.module.css),
|
||||
кнопка мнемоники — 100% ширины */
|
||||
@media (max-width: 649px) {
|
||||
.mnemonicRow {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
gap: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
/* < 640px: инпуты в колонку */
|
||||
/* < 640px: tighter page padding */
|
||||
@media (max-width: 639px) {
|
||||
.main {
|
||||
padding: 16px 16px 40px;
|
||||
}
|
||||
|
||||
.grid2 {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
/* < 550px: меньше текст и аватар */
|
||||
@media (max-width: 549px) {
|
||||
.profileTop {
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.userInfo {
|
||||
padding-top: 8px;
|
||||
}
|
||||
|
||||
.userName {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.userBalance {
|
||||
font-size: 22px;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.userBalanceRub {
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
@@ -1,136 +1,20 @@
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
import { useMe, useUpdatePhone } from '@features/auth'
|
||||
import { usePortfolio, useWalletAddresses } from '@features/wallet'
|
||||
import { ROUTES } from '@shared/config/routes'
|
||||
import { Button, FormField, Notification } from '@shared/ui'
|
||||
import { WalletHeader } from '@widgets/wallet-header'
|
||||
import { ProfileAvatar, ProfileSection } from '@widgets/profile'
|
||||
import { IndividualFields } from './IndividualFields'
|
||||
import { LegalEntityFields } from './LegalEntityFields'
|
||||
import { KycBanner, ProfileSummary, ProfileDetails, SecuritySection, MnemonicSection } from '@widgets/profile'
|
||||
import styles from './ProfilePage.module.css'
|
||||
|
||||
export function ProfilePage() {
|
||||
const { data } = useMe()
|
||||
const { data: portfolio, isLoading: isPortfolioLoading } = usePortfolio()
|
||||
const { data: walletAddresses } = useWalletAddresses()
|
||||
const updatePhone = useUpdatePhone()
|
||||
const navigate = useNavigate()
|
||||
|
||||
const [phone, setPhone] = useState('')
|
||||
const [savedPhone, setSavedPhone] = useState('')
|
||||
const [notification, setNotification] = useState<{ message: string; status: 'success' | 'error' } | null>(null)
|
||||
|
||||
useEffect(() => {
|
||||
if (data?.phone != null) {
|
||||
setPhone(data.phone)
|
||||
setSavedPhone(data.phone)
|
||||
}
|
||||
}, [data?.phone])
|
||||
|
||||
function handlePhoneChange(value: string) {
|
||||
setPhone(value.replace(/[^\d+\s()-]/g, ''))
|
||||
}
|
||||
|
||||
function handlePhoneBlur() {
|
||||
const next = phone.trim()
|
||||
if (next === savedPhone || updatePhone.isPending) return
|
||||
updatePhone.mutate(next, {
|
||||
onSuccess: () => {
|
||||
setSavedPhone(next)
|
||||
setNotification({ status: 'success', message: 'Номер телефона обновлён' })
|
||||
},
|
||||
onError: () => {
|
||||
setNotification({ status: 'error', message: 'Не удалось обновить номер телефона' })
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
const capitalize = (s: string | null) => (s ? s[0].toUpperCase() + s.slice(1).toLowerCase() : '')
|
||||
const fullName = data
|
||||
? [data.last_name, data.first_name, data.middle_name].filter(Boolean).map(capitalize).join(' ')
|
||||
: ''
|
||||
|
||||
const isLegal = !!data && data.account_type !== 'individual'
|
||||
const displayName = isLegal ? (data?.legal_entity?.name ?? '') : fullName
|
||||
|
||||
const userBalance =
|
||||
isPortfolioLoading || !portfolio || portfolio.totalUsd == null
|
||||
? '$—'
|
||||
: `$${portfolio.totalUsd.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`
|
||||
|
||||
return (
|
||||
<div className={styles.page}>
|
||||
<WalletHeader />
|
||||
{data && !data.kyc_verified && (
|
||||
<div className={styles.kycBanner}>
|
||||
Для полного доступа к платформе необходимо пройти верификацию личности (KYC)
|
||||
</div>
|
||||
)}
|
||||
<KycBanner />
|
||||
<main className={styles.main}>
|
||||
<div className={styles.profileTop}>
|
||||
<ProfileAvatar />
|
||||
<div className={styles.userInfo}>
|
||||
<span className={styles.userName}>{displayName}</span>
|
||||
<span className={styles.userBalance}>{userBalance}</span>
|
||||
{/* <span className={styles.userBalanceRub}>≈ 22 340,50 ₽</span> */}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ProfileSummary />
|
||||
<div className={styles.sections}>
|
||||
{data && (isLegal ? (
|
||||
<LegalEntityFields data={data} />
|
||||
) : (
|
||||
<IndividualFields
|
||||
data={data}
|
||||
fullName={fullName}
|
||||
phone={phone}
|
||||
onPhoneChange={handlePhoneChange}
|
||||
onPhoneBlur={handlePhoneBlur}
|
||||
/>
|
||||
))}
|
||||
|
||||
<ProfileSection
|
||||
title="Безопасность"
|
||||
actions={
|
||||
<>
|
||||
<Button variant="danger">⚠️ Посмотреть приватный ключ</Button>
|
||||
<Button variant="primary">СОХРАНИТЬ</Button>
|
||||
</>
|
||||
}
|
||||
>
|
||||
<div className={styles.grid1}>
|
||||
{walletAddresses?.map(({ chain, address }) => (
|
||||
<FormField
|
||||
key={chain}
|
||||
label={`Адрес ${chain}`}
|
||||
readOnly
|
||||
icon="lock"
|
||||
value={address}
|
||||
placeholder="—"
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</ProfileSection>
|
||||
|
||||
<ProfileSection title="Мнемоника">
|
||||
<div className={styles.mnemonicRow}>
|
||||
<div className={styles.mnemonicInfo}>
|
||||
<span className={styles.mnemonicIcon}>🔑</span>
|
||||
<span className={styles.mnemonicText}>Сид-фраза из 12 слов для восстановления кошелька</span>
|
||||
</div>
|
||||
<Button variant="danger" onClick={() => navigate(ROUTES.SEED_PHRASE)}>⚠ Показать мнемонику</Button>
|
||||
</div>
|
||||
</ProfileSection>
|
||||
<ProfileDetails />
|
||||
<SecuritySection />
|
||||
<MnemonicSection />
|
||||
</div>
|
||||
</main>
|
||||
{notification && (
|
||||
<Notification
|
||||
status={notification.status}
|
||||
message={notification.message}
|
||||
onClose={() => setNotification(null)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
240
src/pages/publichnaya-oferta/model/content.ts
Normal file
240
src/pages/publichnaya-oferta/model/content.ts
Normal file
@@ -0,0 +1,240 @@
|
||||
import type { LegalDocData } from '@entities/legal-doc'
|
||||
|
||||
export const publichnayaOfertaDoc: LegalDocData = {
|
||||
title: 'ПУБЛИЧНЫЙ ДОГОВОР ОФЕРТЫ',
|
||||
subtitle: 'ООО БИТФОРС',
|
||||
sections: [
|
||||
{
|
||||
title: 'Агентский договор',
|
||||
blocks: [
|
||||
{
|
||||
kind: 'text',
|
||||
text: 'Настоящая оферта на заключение агентского договора (далее – Оферта, Договор) является публичным предложением Общества с ограниченной ответственностью «БИТФОРС», заключить договор на условиях и в порядке, определенных настоящей Офертой.',
|
||||
},
|
||||
{
|
||||
kind: 'text',
|
||||
text: 'Акцепт оферты производится в соответствии с пунктом 2 статьи 437 Гражданского кодекса Российской Федерации и равносилен заключению агентского договора в письменной форме.',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Основные понятия и определения действующего договора',
|
||||
blocks: [
|
||||
{
|
||||
kind: 'definitions',
|
||||
items: [
|
||||
{
|
||||
term: 'Агент',
|
||||
text: '– юридическое лицо или индивидуальный предприниматель, зарегистрированный на территории Российской Федерации, в установленном действующим законодательством порядке.',
|
||||
},
|
||||
{
|
||||
term: 'Принципал',
|
||||
text: '– сторона агентского договора, по поручению которой агент осуществляет юридические и иные действия от своего имени, но за счет принципала либо от имени и за счет принципала.',
|
||||
},
|
||||
{
|
||||
term: 'Агентский договор',
|
||||
text: '– соглашение, по которому агент обязуется за вознаграждение совершать по поручению принципала юридические и иные действия от своего имени, но за счет принципала либо от имени и за счет принципала в соответствии с п. 1 ст. 1005 Гражданского Кодекса Российской Федерации.',
|
||||
},
|
||||
{
|
||||
term: 'Личный кабинета Агента',
|
||||
text: '– ресурс, размещенный на сайте Принципала, предназначенный для взаимодействия Агента и Принципала.',
|
||||
},
|
||||
{
|
||||
term: 'Отчетный период',
|
||||
text: '– период для взаиморасчетов с Агентом, равный одному календарному кварталу с даты активации любой из услуг, предоставляемой Принципалу.',
|
||||
},
|
||||
{
|
||||
term: 'Отчет о сумме начислений (Отчет)',
|
||||
text: '– отчет, формируемый в Личном кабинете Агента на основании данных систем учета Принципала.',
|
||||
},
|
||||
{
|
||||
term: 'Оферта (Договор)',
|
||||
text: '– настоящий документ, который отражает предложение и намерение ООО «БИТФОРС» считать заключенным договор с лицом, которым будет принято предложение на условиях, изложенных ниже.',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: '1. Акцепт оферты и заключение агентского договора',
|
||||
blocks: [
|
||||
{
|
||||
kind: 'text',
|
||||
text: 'Акцепт настоящей Оферты и заключение Агентского договора осуществляется Принципалом в процессе регистрации в Личном кабинете Принципала (на сайте Агента), при прочтении текста настоящей Оферты, путем проставления специальной отметки (галочки) напротив фразы «Я ознакомился с Офертой и принимаю ее условия» и нажатия кнопки «Подписать».',
|
||||
},
|
||||
{
|
||||
kind: 'text',
|
||||
text: 'Особый порядок принятия условий Оферты путем проставления специальной отметки (галочки) определяется интерфейсом Личного кабинета Принципала. Принципал не может зарегистрироваться в Личном кабинете и получить к нему доступ без подтверждения принятия условий Оферты.',
|
||||
},
|
||||
{
|
||||
kind: 'text',
|
||||
text: 'Принимая Оферту, Принципал подтверждает, что прочел и полностью согласен с документами, размещенными на сайте в разделе, предназначенном для Принципала, которые являются неотъемлемой частью настоящей Оферты (Договора) и обязательны для исполнения Сторонами.',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: '2. Общие положения',
|
||||
blocks: [
|
||||
{
|
||||
kind: 'text',
|
||||
text: 'Публикуемые на сайте Агента документы (формы, требования, правила и т.п.), устанавливающие порядок и условия выполнения действий, предусмотренных настоящим Договором, являются неотъемлемой частью настоящего Договора и обязательны для исполнения Сторонами. Принципал обязан использовать формы документов, утвержденных Агентом, и не вправе вносить в них какие-либо изменения или дополнения.',
|
||||
},
|
||||
{
|
||||
kind: 'text',
|
||||
text: 'Агент обязуется уведомлять Принципала обо всех изменениях в документах, связанных с исполнением настоящего Договора, путем направления электронных сообщений (через Личный кабинет или на электронную почту Принципала) или размещением уведомлений об изменениях на сайте Агентов в разделе, предназначенном для размещения объявлений.',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: '3. Предмет договора',
|
||||
blocks: [
|
||||
{
|
||||
kind: 'text',
|
||||
text: 'По настоящему Договору Принципал поручает, а Агент принимает на себя обязательство совершать от имени и за счет Принципала указанные действия, а Принципал обязуется выплатить Агенту вознаграждение за совершенные действия.',
|
||||
},
|
||||
{ kind: 'text', text: 'По настоящему Договору Агент совершает следующие действия:' },
|
||||
{
|
||||
kind: 'list',
|
||||
items: [
|
||||
'Консультирование Принципала об услугах Агента, включая, помимо прочего, порядок активации и оказания услуг, работу в Личном кабинете Принципала и иные дополнительные услуги, оказываемые Агентом;',
|
||||
'Совершение сделок и иных юридических действий Агентом от своего имени, но за счёт Принципала.',
|
||||
],
|
||||
},
|
||||
{ kind: 'text', text: 'Настоящий Договор действует на территории Российской Федерации и иного иностранного государства.' },
|
||||
{
|
||||
kind: 'text',
|
||||
text: 'Права и обязанности по сделкам, совершенным Агентом во исполнение настоящего Договора, возникают непосредственно у Принципала.',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: '4. Права и обязанности сторон',
|
||||
blocks: [
|
||||
{
|
||||
kind: 'text',
|
||||
text: 'Агент обязуется совершать действия, составляющие предмет настоящего Договора, в соответствии с законными интересами Принципала, сообщать Принципалу по его требованию все сведения о ходе исполнения настоящего Договора, передавать Принципалу в течение 7 рабочих дней имущество, полученное по сделкам.',
|
||||
},
|
||||
{
|
||||
kind: 'text',
|
||||
text: 'Агент несет ответственность за сохранность документов и персональных данных, переданных ему Принципалом для исполнения настоящего Договора.',
|
||||
},
|
||||
{
|
||||
kind: 'text',
|
||||
text: 'Принципал обязан без промедления принять отчет Агента, все предоставленные им документы, обеспечить Агента документами и материалами, необходимыми для выполнения настоящего Договора, возместить Агенту понесенные расходы и выплатить обусловленное Договором агентское вознаграждение.',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: '5. Агентское вознаграждение и порядок оплаты',
|
||||
blocks: [
|
||||
{ kind: 'text', text: 'Сумма вознаграждения Агента по настоящему Договору составляет:' },
|
||||
{
|
||||
kind: 'list',
|
||||
items: [
|
||||
'8% от 5 000 до 30 000 рублей',
|
||||
'6% от 30 000 до 100 000 рублей',
|
||||
'4% от 100 000 до 600 000 рублей',
|
||||
],
|
||||
},
|
||||
{
|
||||
kind: 'text',
|
||||
text: 'Вознаграждение выплачивается Агенту с момента подписания настоящего Договора об исполнении поручения Агентом от своего имени, но за счет Принципала.',
|
||||
},
|
||||
{
|
||||
kind: 'text',
|
||||
text: 'Принципал возмещает следующие расходы Агента в сумме не более 30 000 рублей на оплату банковских услуг и иных комиссий.',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: '6. Ответственность сторон',
|
||||
blocks: [
|
||||
{
|
||||
kind: 'text',
|
||||
text: 'В случае нарушения Агентом сроков, установленных Договором для передачи Принципалу полученного имущества, Принципал вправе предъявить требование об уплате неустойки в размере 0,1% от непереданной суммы за каждый день просрочки.',
|
||||
},
|
||||
{
|
||||
kind: 'text',
|
||||
text: 'В случае нарушения Принципалом сроков уплаты вознаграждения или возмещения расходов, Агент вправе предъявить требование об уплате неустойки в размере 0,1% от не уплаченной в срок суммы за каждый день просрочки.',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: '7. Форс-мажор',
|
||||
blocks: [
|
||||
{
|
||||
kind: 'text',
|
||||
text: 'Стороны освобождаются от ответственности за частичное или полное неисполнение обязательств по настоящему Договору, если это неисполнение явилось следствием возникших после заключения настоящего Договора обстоятельств непреодолимой силы.',
|
||||
},
|
||||
{
|
||||
kind: 'text',
|
||||
text: 'При наступлении форс-мажорных обстоятельств каждая Сторона должна без промедления известить о них в письменном виде другую Сторону с указанием характера обстоятельств и их влияния на исполнение обязательств.',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: '8. Конфиденциальность',
|
||||
blocks: [
|
||||
{
|
||||
kind: 'text',
|
||||
text: 'Стороны принимают все необходимые меры для того, чтобы их сотрудники, агенты, правопреемники без предварительного согласия другой Стороны не информировали третьих лиц о конфиденциальной информации и персональных данных Сторон настоящего Договора.',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: '9. Изменение и прекращение договора',
|
||||
blocks: [
|
||||
{
|
||||
kind: 'text',
|
||||
text: 'Настоящий договор вступает в силу с момента его подписания и действует до момента исполнения сторонами своих обязательств по настоящему договору.',
|
||||
},
|
||||
{
|
||||
kind: 'text',
|
||||
text: 'Настоящий Договор может быть изменен или прекращен по письменному соглашению Сторон, а также в других случаях, предусмотренных законодательством Российской Федерации.',
|
||||
},
|
||||
{
|
||||
kind: 'text',
|
||||
text: 'Принципал вправе в любое время отказаться от исполнения настоящего Договора путем направления письменного уведомления Агенту за 3 рабочих дня.',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: '10. Заключительные положения',
|
||||
blocks: [
|
||||
{
|
||||
kind: 'text',
|
||||
text: 'Ни одна из сторон не вправе передавать свои права и обязанности по настоящему договору третьим лицам без согласия другой стороны.',
|
||||
},
|
||||
{
|
||||
kind: 'text',
|
||||
text: 'Сообщения Стороны могут направлять по факсу, электронной почте или другим способом связи при условии, что он позволяет достоверно установить, от кого исходило сообщение и кому оно адресовано.',
|
||||
},
|
||||
{
|
||||
kind: 'text',
|
||||
text: 'Споры, вытекающие из настоящего Договора, разрешаются в досудебном порядке. При неурегулировании возникших разногласий спор разрешается в Арбитражном суде г. Санкт–Петербурга и Ленинградской области с обязательным соблюдением претензионного порядка.',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Реквизиты сторон',
|
||||
blocks: [
|
||||
{
|
||||
kind: 'infoBox',
|
||||
lines: [
|
||||
'Общество с ограниченной ответственностью «БИТФОРС»',
|
||||
'196246, г. Санкт-Петербург, Московский р-н, Московское шоссе, д.25к1 литера в, помещ. 3-Н',
|
||||
'ИНН / КПП: 9810001062 / 781001001',
|
||||
'ОГРН: 1257800060990',
|
||||
'ОКПО / ОКАТО / ОКТМО: 68342261 / 40284000000 / 40377000000',
|
||||
'Руководитель: Кленин Михаил Васильевич',
|
||||
'Электронная почта: company@bitforcefoundation.ru',
|
||||
'Наименование банка: ФИЛИАЛ "САНКТ-ПЕТЕРБУРГСКИЙ" АО "АЛЬФА-БАНК"',
|
||||
'Корреспондентский счет: 30101810600000000786',
|
||||
'БИК: 044030786',
|
||||
'Расчетный счет: 40702810632250004861',
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
@@ -1,89 +0,0 @@
|
||||
.main {
|
||||
padding: 40px 20px;
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.container {
|
||||
background: var(--bg-mid, #1b1547);
|
||||
padding: 40px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 28px;
|
||||
font-weight: 700;
|
||||
margin-bottom: 20px;
|
||||
text-align: center;
|
||||
color: var(--text-primary, #ffffff);
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
font-size: 22px;
|
||||
font-weight: 600;
|
||||
margin-bottom: 30px;
|
||||
text-align: center;
|
||||
color: var(--text-secondary, #b5b0cc);
|
||||
}
|
||||
|
||||
.section {
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
|
||||
.sectionTitle {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
margin-bottom: 15px;
|
||||
color: var(--text-primary, #ffffff);
|
||||
border-bottom: 2px solid var(--interactive, #4a6dff);
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
.definitions {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.definition {
|
||||
padding: 15px;
|
||||
background: var(--glass-bg, rgba(255, 255, 255, 0.04));
|
||||
border-left: 4px solid var(--interactive, #4a6dff);
|
||||
border-radius: 4px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.requisites {
|
||||
padding: 20px;
|
||||
background: var(--glass-bg, rgba(255, 255, 255, 0.04));
|
||||
border-radius: 4px;
|
||||
border: 1px solid var(--glass-border, rgba(255, 255, 255, 0.08));
|
||||
line-height: 1.8;
|
||||
}
|
||||
|
||||
.requisites p {
|
||||
margin: 5px 0;
|
||||
color: var(--text-primary, #ffffff);
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.container {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 22px;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.sectionTitle {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.requisites {
|
||||
padding: 15px;
|
||||
}
|
||||
}
|
||||
@@ -1,199 +1,13 @@
|
||||
import { LegalDoc } from '@entities/legal-doc'
|
||||
import { Footer } from '@widgets/footer'
|
||||
import { Header } from '@widgets/header'
|
||||
import styles from './PublichnayaOfertaPage.module.css'
|
||||
import { publichnayaOfertaDoc } from '../model/content'
|
||||
|
||||
export function PublichnayaOfertaPage() {
|
||||
return (
|
||||
<>
|
||||
<Header />
|
||||
<main className={styles.main}>
|
||||
<div className={styles.container}>
|
||||
<h1 className={styles.title}>ПУБЛИЧНЫЙ ДОГОВОР ОФЕРТЫ</h1>
|
||||
|
||||
<h2 className={styles.subtitle}>ООО БИТФОРС</h2>
|
||||
|
||||
<section className={styles.section}>
|
||||
<h3 className={styles.sectionTitle}>Агентский договор</h3>
|
||||
<p>
|
||||
Настоящая оферта на заключение агентского договора (далее – Оферта, Договор) является публичным предложением Общества с ограниченной ответственностью «БИТФОРС», заключить договор на условиях и в порядке, определенных настоящей Офертой.
|
||||
</p>
|
||||
<p>
|
||||
Акцепт оферты производится в соответствии с пунктом 2 статьи 437 Гражданского кодекса Российской Федерации и равносилен заключению агентского договора в письменной форме.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section className={styles.section}>
|
||||
<h3 className={styles.sectionTitle}>Основные понятия и определения действующего договора</h3>
|
||||
<div className={styles.definitions}>
|
||||
<div className={styles.definition}>
|
||||
<strong>Агент</strong> – юридическое лицо или индивидуальный предприниматель, зарегистрированный на территории Российской Федерации, в установленном действующим законодательством порядке.
|
||||
</div>
|
||||
<div className={styles.definition}>
|
||||
<strong>Принципал</strong> – сторона агентского договора, по поручению которой агент осуществляет юридические и иные действия от своего имени, но за счет принципала либо от имени и за счет принципала.
|
||||
</div>
|
||||
<div className={styles.definition}>
|
||||
<strong>Агентский договор</strong> – соглашение, по которому агент обязуется за вознаграждение совершать по поручению принципала юридические и иные действия от своего имени, но за счет принципала либо от имени и за счет принципала в соответствии с п. 1 ст. 1005 Гражданского Кодекса Российской Федерации.
|
||||
</div>
|
||||
<div className={styles.definition}>
|
||||
<strong>Личный кабинета Агента</strong> – ресурс, размещенный на сайте Принципала, предназначенный для взаимодействия Агента и Принципала.
|
||||
</div>
|
||||
<div className={styles.definition}>
|
||||
<strong>Отчетный период</strong> – период для взаиморасчетов с Агентом, равный одному календарному кварталу с даты активации любой из услуг, предоставляемой Принципалу.
|
||||
</div>
|
||||
<div className={styles.definition}>
|
||||
<strong>Отчет о сумме начислений (Отчет)</strong> – отчет, формируемый в Личном кабинете Агента на основании данных систем учета Принципала.
|
||||
</div>
|
||||
<div className={styles.definition}>
|
||||
<strong>Оферта (Договор)</strong> – настоящий документ, который отражает предложение и намерение ООО «БИТФОРС» считать заключенным договор с лицом, которым будет принято предложение на условиях, изложенных ниже.
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className={styles.section}>
|
||||
<h3 className={styles.sectionTitle}>1. Акцепт оферты и заключение агентского договора</h3>
|
||||
<p>
|
||||
Акцепт настоящей Оферты и заключение Агентского договора осуществляется Принципалом в процессе регистрации в Личном кабинете Принципала (на сайте Агента), при прочтении текста настоящей Оферты, путем проставления специальной отметки (галочки) напротив фразы «Я ознакомился с Офертой и принимаю ее условия» и нажатия кнопки «Подписать».
|
||||
</p>
|
||||
<p>
|
||||
Особый порядок принятия условий Оферты путем проставления специальной отметки (галочки) определяется интерфейсом Личного кабинета Принципала. Принципал не может зарегистрироваться в Личном кабинете и получить к нему доступ без подтверждения принятия условий Оферты.
|
||||
</p>
|
||||
<p>
|
||||
Принимая Оферту, Принципал подтверждает, что прочел и полностью согласен с документами, размещенными на сайте в разделе, предназначенном для Принципала, которые являются неотъемлемой частью настоящей Оферты (Договора) и обязательны для исполнения Сторонами.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section className={styles.section}>
|
||||
<h3 className={styles.sectionTitle}>2. Общие положения</h3>
|
||||
<p>
|
||||
Публикуемые на сайте Агента документы (формы, требования, правила и т.п.), устанавливающие порядок и условия выполнения действий, предусмотренных настоящим Договором, являются неотъемлемой частью настоящего Договора и обязательны для исполнения Сторонами. Принципал обязан использовать формы документов, утвержденных Агентом, и не вправе вносить в них какие-либо изменения или дополнения.
|
||||
</p>
|
||||
<p>
|
||||
Агент обязуется уведомлять Принципала обо всех изменениях в документах, связанных с исполнением настоящего Договора, путем направления электронных сообщений (через Личный кабинет или на электронную почту Принципала) или размещением уведомлений об изменениях на сайте Агентов в разделе, предназначенном для размещения объявлений.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section className={styles.section}>
|
||||
<h3 className={styles.sectionTitle}>3. Предмет договора</h3>
|
||||
<p>
|
||||
По настоящему Договору Принципал поручает, а Агент принимает на себя обязательство совершать от имени и за счет Принципала указанные действия, а Принципал обязуется выплатить Агенту вознаграждение за совершенные действия.
|
||||
</p>
|
||||
<p>
|
||||
По настоящему Договору Агент совершает следующие действия:
|
||||
</p>
|
||||
<ul>
|
||||
<li>Консультирование Принципала об услугах Агента, включая, помимо прочего, порядок активации и оказания услуг, работу в Личном кабинете Принципала и иные дополнительные услуги, оказываемые Агентом;</li>
|
||||
<li>Совершение сделок и иных юридических действий Агентом от своего имени, но за счёт Принципала.</li>
|
||||
</ul>
|
||||
<p>
|
||||
Настоящий Договор действует на территории Российской Федерации и иного иностранного государства.
|
||||
</p>
|
||||
<p>
|
||||
Права и обязанности по сделкам, совершенным Агентом во исполнение настоящего Договора, возникают непосредственно у Принципала.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section className={styles.section}>
|
||||
<h3 className={styles.sectionTitle}>4. Права и обязанности сторон</h3>
|
||||
<p>
|
||||
Агент обязуется совершать действия, составляющие предмет настоящего Договора, в соответствии с законными интересами Принципала, сообщать Принципалу по его требованию все сведения о ходе исполнения настоящего Договора, передавать Принципалу в течение 7 рабочих дней имущество, полученное по сделкам.
|
||||
</p>
|
||||
<p>
|
||||
Агент несет ответственность за сохранность документов и персональных данных, переданных ему Принципалом для исполнения настоящего Договора.
|
||||
</p>
|
||||
<p>
|
||||
Принципал обязан без промедления принять отчет Агента, все предоставленные им документы, обеспечить Агента документами и материалами, необходимыми для выполнения настоящего Договора, возместить Агенту понесенные расходы и выплатить обусловленное Договором агентское вознаграждение.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section className={styles.section}>
|
||||
<h3 className={styles.sectionTitle}>5. Агентское вознаграждение и порядок оплаты</h3>
|
||||
<p>
|
||||
Сумма вознаграждения Агента по настоящему Договору составляет:
|
||||
</p>
|
||||
<ul>
|
||||
<li>8% от 5 000 до 30 000 рублей</li>
|
||||
<li>6% от 30 000 до 100 000 рублей</li>
|
||||
<li>4% от 100 000 до 600 000 рублей</li>
|
||||
</ul>
|
||||
<p>
|
||||
Вознаграждение выплачивается Агенту с момента подписания настоящего Договора об исполнении поручения Агентом от своего имени, но за счет Принципала.
|
||||
</p>
|
||||
<p>
|
||||
Принципал возмещает следующие расходы Агента в сумме не более 30 000 рублей на оплату банковских услуг и иных комиссий.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section className={styles.section}>
|
||||
<h3 className={styles.sectionTitle}>6. Ответственность сторон</h3>
|
||||
<p>
|
||||
В случае нарушения Агентом сроков, установленных Договором для передачи Принципалу полученного имущества, Принципал вправе предъявить требование об уплате неустойки в размере 0,1% от непереданной суммы за каждый день просрочки.
|
||||
</p>
|
||||
<p>
|
||||
В случае нарушения Принципалом сроков уплаты вознаграждения или возмещения расходов, Агент вправе предъявить требование об уплате неустойки в размере 0,1% от не уплаченной в срок суммы за каждый день просрочки.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section className={styles.section}>
|
||||
<h3 className={styles.sectionTitle}>7. Форс-мажор</h3>
|
||||
<p>
|
||||
Стороны освобождаются от ответственности за частичное или полное неисполнение обязательств по настоящему Договору, если это неисполнение явилось следствием возникших после заключения настоящего Договора обстоятельств непреодолимой силы.
|
||||
</p>
|
||||
<p>
|
||||
При наступлении форс-мажорных обстоятельств каждая Сторона должна без промедления известить о них в письменном виде другую Сторону с указанием характера обстоятельств и их влияния на исполнение обязательств.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section className={styles.section}>
|
||||
<h3 className={styles.sectionTitle}>8. Конфиденциальность</h3>
|
||||
<p>
|
||||
Стороны принимают все необходимые меры для того, чтобы их сотрудники, агенты, правопреемники без предварительного согласия другой Стороны не информировали третьих лиц о конфиденциальной информации и персональных данных Сторон настоящего Договора.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section className={styles.section}>
|
||||
<h3 className={styles.sectionTitle}>9. Изменение и прекращение договора</h3>
|
||||
<p>
|
||||
Настоящий договор вступает в силу с момента его подписания и действует до момента исполнения сторонами своих обязательств по настоящему договору.
|
||||
</p>
|
||||
<p>
|
||||
Настоящий Договор может быть изменен или прекращен по письменному соглашению Сторон, а также в других случаях, предусмотренных законодательством Российской Федерации.
|
||||
</p>
|
||||
<p>
|
||||
Принципал вправе в любое время отказаться от исполнения настоящего Договора путем направления письменного уведомления Агенту за 3 рабочих дня.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section className={styles.section}>
|
||||
<h3 className={styles.sectionTitle}>10. Заключительные положения</h3>
|
||||
<p>
|
||||
Ни одна из сторон не вправе передавать свои права и обязанности по настоящему договору третьим лицам без согласия другой стороны.
|
||||
</p>
|
||||
<p>
|
||||
Сообщения Стороны могут направлять по факсу, электронной почте или другим способом связи при условии, что он позволяет достоверно установить, от кого исходило сообщение и кому оно адресовано.
|
||||
</p>
|
||||
<p>
|
||||
Споры, вытекающие из настоящего Договора, разрешаются в досудебном порядке. При неурегулировании возникших разногласий спор разрешается в Арбитражном суде г. Санкт–Петербурга и Ленинградской области с обязательным соблюдением претензионного порядка.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section className={styles.section}>
|
||||
<h3 className={styles.sectionTitle}>Реквизиты сторон</h3>
|
||||
<div className={styles.requisites}>
|
||||
<p>Общество с ограниченной ответственностью «БИТФОРС»</p>
|
||||
<p>196246, г. Санкт-Петербург, Московский р-н, Московское шоссе, д.25к1 литера в, помещ. 3-Н</p>
|
||||
<p>ИНН / КПП: 9810001062 / 781001001</p>
|
||||
<p>ОГРН: 1257800060990</p>
|
||||
<p>ОКПО / ОКАТО / ОКТМО: 68342261 / 40284000000 / 40377000000</p>
|
||||
<p>Руководитель: Кленин Михаил Васильевич</p>
|
||||
<p>Электронная почта: company@bitforcefoundation.ru</p>
|
||||
<p>Наименование банка: ФИЛИАЛ "САНКТ-ПЕТЕРБУРГСКИЙ" АО "АЛЬФА-БАНК"</p>
|
||||
<p>Корреспондентский счет: 30101810600000000786</p>
|
||||
<p>БИК: 044030786</p>
|
||||
<p>Расчетный счет: 40702810632250004861</p>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</main>
|
||||
<LegalDoc doc={publichnayaOfertaDoc} />
|
||||
<Footer />
|
||||
</>
|
||||
)
|
||||
|
||||
60
src/pages/reestr-pd-rkn/model/content.ts
Normal file
60
src/pages/reestr-pd-rkn/model/content.ts
Normal file
@@ -0,0 +1,60 @@
|
||||
import type { LegalDocData } from '@entities/legal-doc'
|
||||
|
||||
export const reestrDoc: LegalDocData = {
|
||||
title: 'Реестр операторов персональных данных',
|
||||
subtitle: 'ООО «БИТФОРС»',
|
||||
sections: [
|
||||
{
|
||||
blocks: [
|
||||
{
|
||||
kind: 'text',
|
||||
text: 'Информация об операторе персональных данных размещена в реестре операторов персональных данных Федеральной службы по надзору в сфере связи, информационных технологий и массовых коммуникаций (Роскомнадзор).',
|
||||
},
|
||||
{
|
||||
kind: 'text',
|
||||
text: 'Вы можете просмотреть информацию об операторе в реестре Роскомнадзора, перейдя по ссылке ниже:',
|
||||
},
|
||||
{
|
||||
kind: 'link',
|
||||
href: 'https://pd.rkn.gov.ru/operators-registry/operators-list/?act=search&name_full=%D0%91%D0%B8%D1%82%D1%84%D0%BE%D1%80%D1%81&inn=9810001062®n=',
|
||||
text: 'Открыть реестр Роскомнадзора',
|
||||
},
|
||||
{
|
||||
kind: 'text',
|
||||
text: 'Реестр содержит информацию об операторах персональных данных, включая сведения о целях и методах обработки персональных данных, а также меры по обеспечению безопасности персональных данных.',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Информация об операторе',
|
||||
blocks: [
|
||||
{
|
||||
kind: 'infoBox',
|
||||
lines: [
|
||||
{ strong: 'Наименование:', text: 'ООО «БИТФОРС»' },
|
||||
{ strong: 'ИНН:', text: '9810001062' },
|
||||
{ strong: 'ОГРН:', text: '1257800060990' },
|
||||
{
|
||||
strong: 'Юридический адрес:',
|
||||
text: '196246, город Санкт-Петербург, Московское шоссе, дом 25, корпус 1, литера В, помещение 3-н',
|
||||
},
|
||||
{ strong: 'Контактная информация:', text: 'company@bitforcefoundation.ru' },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'О Роскомнадзоре',
|
||||
blocks: [
|
||||
{
|
||||
kind: 'text',
|
||||
text: 'Федеральная служба по надзору в сфере связи, информационных технологий и массовых коммуникаций (Роскомнадзор) — это федеральный орган исполнительной власти, осуществляющий функции по контролю и надзору в области персональных данных.',
|
||||
},
|
||||
{
|
||||
kind: 'text',
|
||||
text: 'Роскомнадзор ведет реестр операторов персональных данных в соответствии с требованиями Федерального закона «О персональных данных». Реестр является открытой информационной системой и доступен всем заинтересованным лицам.',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
@@ -1,115 +0,0 @@
|
||||
.main {
|
||||
padding: 40px 20px;
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.container {
|
||||
background: var(--bg-mid, #1b1547);
|
||||
padding: 40px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 28px;
|
||||
font-weight: 700;
|
||||
margin-bottom: 20px;
|
||||
text-align: center;
|
||||
color: var(--text-primary, #ffffff);
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
font-size: 22px;
|
||||
font-weight: 600;
|
||||
margin-bottom: 30px;
|
||||
text-align: center;
|
||||
color: var(--text-secondary, #b5b0cc);
|
||||
}
|
||||
|
||||
.section {
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
|
||||
.sectionTitle {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
margin-bottom: 15px;
|
||||
color: var(--text-primary, #ffffff);
|
||||
border-bottom: 2px solid var(--interactive, #4a6dff);
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
.description {
|
||||
font-size: 16px;
|
||||
line-height: 1.8;
|
||||
color: var(--text-primary, #ffffff);
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.info {
|
||||
font-size: 16px;
|
||||
line-height: 1.8;
|
||||
color: var(--text-primary, #ffffff);
|
||||
margin: 15px 0;
|
||||
}
|
||||
|
||||
.linkBlock {
|
||||
text-align: center;
|
||||
padding: 30px;
|
||||
background: var(--glass-bg, rgba(255, 255, 255, 0.04));
|
||||
border-radius: 8px;
|
||||
margin: 30px 0;
|
||||
}
|
||||
|
||||
.button {
|
||||
display: inline-block;
|
||||
padding: 15px 40px;
|
||||
background: var(--interactive, #4a6dff);
|
||||
color: #fff;
|
||||
text-decoration: none;
|
||||
border-radius: 6px;
|
||||
font-weight: 600;
|
||||
transition: all 0.3s ease;
|
||||
border: 2px solid var(--interactive, #4a6dff);
|
||||
}
|
||||
|
||||
.button:hover {
|
||||
background: transparent;
|
||||
color: var(--interactive, #4a6dff);
|
||||
}
|
||||
|
||||
.operatorInfo {
|
||||
padding: 20px;
|
||||
background: var(--glass-bg, rgba(255, 255, 255, 0.04));
|
||||
border-left: 4px solid var(--interactive, #4a6dff);
|
||||
border-radius: 4px;
|
||||
line-height: 1.8;
|
||||
}
|
||||
|
||||
.operatorInfo p {
|
||||
margin: 10px 0;
|
||||
color: var(--text-primary, #ffffff);
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.container {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 22px;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.linkBlock {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.button {
|
||||
padding: 12px 30px;
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
@@ -1,73 +1,13 @@
|
||||
import { LegalDoc } from '@entities/legal-doc'
|
||||
import { Footer } from '@widgets/footer'
|
||||
import { Header } from '@widgets/header'
|
||||
import styles from './ReestryPage.module.css'
|
||||
import { reestrDoc } from '../model/content'
|
||||
|
||||
export function ReestryPage() {
|
||||
return (
|
||||
<>
|
||||
<Header />
|
||||
<main className={styles.main}>
|
||||
<div className={styles.container}>
|
||||
<h1 className={styles.title}>Реестр операторов персональных данных</h1>
|
||||
<h2 className={styles.subtitle}>ООО «БИТФОРС»</h2>
|
||||
|
||||
<section className={styles.section}>
|
||||
<p className={styles.description}>
|
||||
Информация об операторе персональных данных размещена в реестре операторов персональных данных Федеральной службы по надзору в сфере связи, информационных технологий и массовых коммуникаций (Роскомнадзор).
|
||||
</p>
|
||||
|
||||
<p className={styles.info}>
|
||||
Вы можете просмотреть информацию об операторе в реестре Роскомнадзора, перейдя по ссылке ниже:
|
||||
</p>
|
||||
|
||||
<div className={styles.linkBlock}>
|
||||
<a
|
||||
href="https://pd.rkn.gov.ru/operators-registry/operators-list/?act=search&name_full=%D0%91%D0%B8%D1%82%D1%84%D0%BE%D1%80%D1%81&inn=9810001062®n="
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className={styles.button}
|
||||
>
|
||||
Открыть реестр Роскомнадзора
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<p className={styles.info}>
|
||||
Реестр содержит информацию об операторах персональных данных, включая сведения о целях и методах обработки персональных данных, а также меры по обеспечению безопасности персональных данных.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section className={styles.section}>
|
||||
<h3 className={styles.sectionTitle}>Информация об операторе</h3>
|
||||
<div className={styles.operatorInfo}>
|
||||
<p>
|
||||
<strong>Наименование:</strong> ООО «БИТФОРС»
|
||||
</p>
|
||||
<p>
|
||||
<strong>ИНН:</strong> 9810001062
|
||||
</p>
|
||||
<p>
|
||||
<strong>ОГРН:</strong> 1257800060990
|
||||
</p>
|
||||
<p>
|
||||
<strong>Юридический адрес:</strong> 196246, город Санкт-Петербург, Московское шоссе, дом 25, корпус 1, литера В, помещение 3-н
|
||||
</p>
|
||||
<p>
|
||||
<strong>Контактная информация:</strong> company@bitforcefoundation.ru
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className={styles.section}>
|
||||
<h3 className={styles.sectionTitle}>О Роскомнадзоре</h3>
|
||||
<p>
|
||||
Федеральная служба по надзору в сфере связи, информационных технологий и массовых коммуникаций (Роскомнадзор) — это федеральный орган исполнительной власти, осуществляющий функции по контролю и надзору в области персональных данных.
|
||||
</p>
|
||||
<p>
|
||||
Роскомнадзор ведет реестр операторов персональных данных в соответствии с требованиями Федерального закона «О персональных данных». Реестр является открытой информационной системой и доступен всем заинтересованным лицам.
|
||||
</p>
|
||||
</section>
|
||||
</div>
|
||||
</main>
|
||||
<LegalDoc doc={reestrDoc} />
|
||||
<Footer />
|
||||
</>
|
||||
)
|
||||
|
||||
346
src/pages/soglasie-personalnyh-dannyh/model/content.ts
Normal file
346
src/pages/soglasie-personalnyh-dannyh/model/content.ts
Normal file
@@ -0,0 +1,346 @@
|
||||
import type { LegalDocData } from '@entities/legal-doc'
|
||||
|
||||
export const soglasieDoc: LegalDocData = {
|
||||
title: 'СОГЛАСИЕ НА ОБРАБОТКУ ПЕРСОНАЛЬНЫХ ДАННЫХ',
|
||||
subtitle: 'ООО «БИТФОРС»',
|
||||
sections: [
|
||||
{
|
||||
title: 'Преамбула',
|
||||
blocks: [
|
||||
{
|
||||
kind: 'text',
|
||||
text: 'Я, субъект персональных данных, действуя своей волей и в своем интересе, в соответствии с требованиями Федерального закона от 27.07.2006 № 152-ФЗ «О персональных данных», предоставляю ООО «БИТФОРС» согласие на обработку моих персональных данных на условиях и для целей, определенных настоящим Согласием.',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: '1. Сведения об операторе',
|
||||
blocks: [
|
||||
{
|
||||
kind: 'infoBox',
|
||||
lines: [
|
||||
'Полное наименование: Общество с ограниченной ответственностью «БИТФОРС»',
|
||||
'ИНН: 9810001062',
|
||||
'ОГРН: 1257800060990',
|
||||
'Юридический адрес: 196246, город Санкт-Петербург, Московское шоссе, дом 25, корпус 1, литера В, помещение 3-н',
|
||||
'Электронная почта: company@bitforcefoundation.ru',
|
||||
'Веб-сайт: https://bitforce-foundation.ru',
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: '2. Правовые основания обработки',
|
||||
blocks: [
|
||||
{
|
||||
kind: 'text',
|
||||
text: 'Настоящее согласие предоставляется на основании пункта 1 части 1 статьи 6 Федерального закона «О персональных данных» и является правовым основанием для обработки персональных данных Оператором.',
|
||||
},
|
||||
{ kind: 'text', text: 'Согласие дается добровольно, своей волей и в своих интересах.' },
|
||||
{
|
||||
kind: 'text',
|
||||
text: 'Субъект персональных данных понимает последствия предоставления согласия, включая возможные риски, связанные с обработкой персональных данных.',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: '3. Цели обработки персональных данных',
|
||||
blocks: [
|
||||
{ kind: 'subheading', text: '3.1. Основные цели:' },
|
||||
{
|
||||
kind: 'list',
|
||||
items: [
|
||||
'Регистрация и ведение учетной записи на веб-сайте и в мобильном приложении',
|
||||
'Идентификация и верификация личности в соответствии с требованиями законодательства',
|
||||
'Предоставление услуг по обмену криптовалют и электронных денежных средств',
|
||||
'Проведение финансовых операций, переводов и расчетов',
|
||||
'Ведение учета и истории операций',
|
||||
],
|
||||
},
|
||||
{ kind: 'subheading', text: '3.2. Дополнительные цели:' },
|
||||
{
|
||||
kind: 'list',
|
||||
items: [
|
||||
'Обеспечение безопасности операций и предотвращение мошенничества',
|
||||
'Выполнение требований по противодействию легализации доходов',
|
||||
'Соблюдение требований валютного, налогового и иного применимого законодательства',
|
||||
'Предоставление технической поддержки и клиентского сервиса',
|
||||
'Рассылка уведомлений о состоянии операций и изменениях в условиях',
|
||||
],
|
||||
},
|
||||
{ kind: 'subheading', text: '3.3. Маркетинговые цели (при дополнительном согласии):' },
|
||||
{
|
||||
kind: 'list',
|
||||
items: [
|
||||
'Направление информационных и рекламных материалов',
|
||||
'Проведение маркетинговых исследований и опросов',
|
||||
'Персонализация предложений и услуг',
|
||||
'Анализ предпочтений и поведения для улучшения сервисов',
|
||||
],
|
||||
},
|
||||
{ kind: 'subheading', text: '3.4. Аналитические цели:' },
|
||||
{
|
||||
kind: 'list',
|
||||
items: [
|
||||
'Анализ использования веб-сайта и мобильного приложения',
|
||||
'Улучшение качества предоставляемых услуг',
|
||||
'Разработка новых продуктов и сервисов',
|
||||
'Создание статистических отчетов в обезличенном виде',
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: '4. Перечень персональных данных',
|
||||
blocks: [
|
||||
{ kind: 'subheading', text: '4.1. Идентификационные данные:' },
|
||||
{ kind: 'list', items: ['Фамилия, имя, отчество', 'Дата рождения', 'Гражданство'] },
|
||||
{ kind: 'subheading', text: '4.2. Документы, удостоверяющие личность:' },
|
||||
{
|
||||
kind: 'list',
|
||||
items: [
|
||||
'Серия и номер паспорта гражданина Российской Федерации',
|
||||
'Дата выдачи и код подразделения',
|
||||
'Адрес регистрации по месту жительства',
|
||||
'Цифровые копии (сканы) документов',
|
||||
],
|
||||
},
|
||||
{ kind: 'subheading', text: '4.3. Контактная информация:' },
|
||||
{
|
||||
kind: 'list',
|
||||
items: [
|
||||
'Номера телефонов (мобильный, домашний, рабочий)',
|
||||
'Адреса электронной почты',
|
||||
'Почтовые адреса (фактического проживания, для корреспонденции)',
|
||||
],
|
||||
},
|
||||
{ kind: 'subheading', text: '4.4. Финансовая информация:' },
|
||||
{
|
||||
kind: 'list',
|
||||
items: [
|
||||
'Номера банковских счетов и реквизиты банковских карт',
|
||||
'Реквизиты криптовалютных кошельков и адресов',
|
||||
'Информация о доходах и источниках происхождения денежных средств',
|
||||
'История финансовых операций и транзакций',
|
||||
],
|
||||
},
|
||||
{ kind: 'subheading', text: '4.5. Техническая информация:' },
|
||||
{
|
||||
kind: 'list',
|
||||
items: [
|
||||
'IP-адреса устройств, с которых осуществляется доступ к сервисам',
|
||||
'Информация о браузере, операционной системе и устройстве',
|
||||
'Файлы cookie и данные локального хранилища',
|
||||
'Логи действий и история использования сервисов',
|
||||
],
|
||||
},
|
||||
{ kind: 'subheading', text: '4.6. Дополнительная информация:' },
|
||||
{
|
||||
kind: 'list',
|
||||
items: [
|
||||
'Фотографии для процедур верификации',
|
||||
'Видеозаписи процедур видеоидентификации',
|
||||
'Биометрические данные (при использовании соответствующих технологий)',
|
||||
'Информация о семейном положении и профессиональной деятельности',
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: '5. Перечень действий с персональными данными',
|
||||
blocks: [
|
||||
{
|
||||
kind: 'text',
|
||||
text: 'Согласие распространяется на следующие действия (операции) с персональными данными:',
|
||||
},
|
||||
{
|
||||
kind: 'list',
|
||||
items: [
|
||||
'Сбор, запись и первичная обработка персональных данных',
|
||||
'Накопление и систематизация в базах данных',
|
||||
'Создание резервных копий и архивирование',
|
||||
'Извлечение, использование и анализ данных',
|
||||
'Уточнение, обновление и актуализация информации',
|
||||
'Передача данных третьим лицам',
|
||||
'Обезличивание и удаление данных',
|
||||
'Автоматизированная обработка и профилирование',
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: '6. Лица, которым могут быть переданы персональные данные',
|
||||
blocks: [
|
||||
{ kind: 'subheading', text: '6.1. Сотрудники Оператора:' },
|
||||
{
|
||||
kind: 'list',
|
||||
items: [
|
||||
'Уполномоченные сотрудники, непосредственно участвующие в обработке',
|
||||
'Сотрудники службы безопасности и комплаенса',
|
||||
'Сотрудники технической поддержки',
|
||||
'Руководящий состав в рамках их полномочий',
|
||||
],
|
||||
},
|
||||
{ kind: 'subheading', text: '6.2. Государственные и муниципальные органы:' },
|
||||
{
|
||||
kind: 'list',
|
||||
items: [
|
||||
'Федеральная служба по финансовому мониторингу',
|
||||
'Федеральная налоговая служба',
|
||||
'Правоохранительные органы (при наличии законных требований)',
|
||||
'Суды и органы исполнения судебных решений',
|
||||
],
|
||||
},
|
||||
{ kind: 'subheading', text: '6.3. Партнеры и контрагенты:' },
|
||||
{
|
||||
kind: 'list',
|
||||
items: [
|
||||
'Банки и платежные системы',
|
||||
'Операторы электронных денежных средств',
|
||||
'Поставщики технологических решений',
|
||||
'Аудиторские и консалтинговые организации',
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: '7. Сроки обработки персональных данных',
|
||||
blocks: [
|
||||
{ kind: 'subheading', text: '7.1. Общие принципы:' },
|
||||
{
|
||||
kind: 'text',
|
||||
text: 'Персональные данные обрабатываются в течение времени, необходимого для достижения целей обработки. После достижения целей данные подлежат уничтожению или обезличиванию.',
|
||||
},
|
||||
{ kind: 'subheading', text: '7.2. Конкретные сроки обработки:' },
|
||||
{
|
||||
kind: 'list',
|
||||
items: [
|
||||
{
|
||||
strong: 'Данные активных клиентов:',
|
||||
text: 'в течение всего периода отношений плюс 5 лет после прекращения',
|
||||
},
|
||||
{ strong: 'Данные для идентификации:', text: '5 лет с момента прекращения отношений' },
|
||||
{ strong: 'Финансовая информация:', text: '5 лет с даты совершения операции' },
|
||||
{ strong: 'Маркетинговые данные:', text: 'до отзыва согласия, но не более 3 лет' },
|
||||
{ strong: 'Техническая информация:', text: '1 год для безопасности, 6 месяцев для логов' },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: '8. Права субъекта персональных данных',
|
||||
blocks: [
|
||||
{ kind: 'subheading', text: '8.1. Право на информацию:' },
|
||||
{
|
||||
kind: 'list',
|
||||
items: [
|
||||
'Получение подтверждения факта обработки персональных данных',
|
||||
'Получение информации о целях и способах обработки',
|
||||
'Информация о сроках обработки и составе данных',
|
||||
'Сведения о лицах, которым передаются данные',
|
||||
],
|
||||
},
|
||||
{ kind: 'subheading', text: '8.2. Право на доступ:' },
|
||||
{
|
||||
kind: 'list',
|
||||
items: [
|
||||
'Получение копий обрабатываемых персональных данных',
|
||||
'Ознакомление с историей обработки и изменений',
|
||||
'Получение информации об источниках персональных данных',
|
||||
],
|
||||
},
|
||||
{ kind: 'subheading', text: '8.3. Право на исправление и удаление:' },
|
||||
{
|
||||
kind: 'list',
|
||||
items: [
|
||||
'Требование исправления неточных или неполных данных',
|
||||
'Требование удаления персональных данных при наличии оснований',
|
||||
'Удаление данных после отзыва согласия',
|
||||
],
|
||||
},
|
||||
{ kind: 'subheading', text: '8.4. Право на отзыв согласия:' },
|
||||
{
|
||||
kind: 'list',
|
||||
items: [
|
||||
'Согласие может быть отозвано в любое время',
|
||||
'Отзыв оформляется в письменной форме',
|
||||
'После отзыва обработка прекращается в разумные сроки',
|
||||
],
|
||||
},
|
||||
{ kind: 'subheading', text: '8.5. Право на обжалование:' },
|
||||
{
|
||||
kind: 'list',
|
||||
items: [
|
||||
'Обращение к Оператору с жалобами на действия по обработке данных',
|
||||
'Обращение в Роскомнадзор или его территориальные органы',
|
||||
'Обращение в суд для защиты нарушенных прав',
|
||||
],
|
||||
},
|
||||
{ kind: 'subheading', text: '8.6. Порядок реализации прав:' },
|
||||
{
|
||||
kind: 'list',
|
||||
items: [
|
||||
'Обращения направляются на адрес: company@bitforcefoundation.ru',
|
||||
'Обращения рассматриваются в течение 30 дней',
|
||||
'При необходимости срок может быть продлен на 30 дней',
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: '9. Заключительные положения',
|
||||
blocks: [
|
||||
{ kind: 'subheading', text: '9.1. Действие согласия:' },
|
||||
{
|
||||
kind: 'list',
|
||||
items: [
|
||||
'Согласие действует с момента его предоставления',
|
||||
'Согласие действует до его отзыва или до достижения целей обработки',
|
||||
'При существенных изменениях целей требуется новое согласие',
|
||||
],
|
||||
},
|
||||
{ kind: 'subheading', text: '9.2. Форма предоставления согласия:' },
|
||||
{
|
||||
kind: 'list',
|
||||
items: [
|
||||
'Согласие может быть предоставлено в письменной форме',
|
||||
'Согласие может быть предоставлено в электронной форме',
|
||||
'Согласие может выражаться путем совершения конклюдентных действий',
|
||||
],
|
||||
},
|
||||
{ kind: 'subheading', text: '9.3. Последствия непредоставления согласия:' },
|
||||
{
|
||||
kind: 'list',
|
||||
items: [
|
||||
'Отказ в предоставлении согласия может повлечь невозможность регистрации',
|
||||
'Отказ может ограничить доступ к отдельным услугам',
|
||||
'Отказ в согласии на маркетинг не влияет на основные услуги',
|
||||
'Субъект вправе предоставить частичное согласие',
|
||||
],
|
||||
},
|
||||
{ kind: 'subheading', text: '9.4. Контактная информация:' },
|
||||
{
|
||||
kind: 'infoBox',
|
||||
lines: [
|
||||
'Почтовый адрес: 196246, г. Санкт-Петербург, Московское ш., д. 25, к. 1, лит. В, пом. 3-н',
|
||||
'Электронная почта: company@bitforcefoundation.ru',
|
||||
'Ответственное лицо: Кленин Михаил Васильевич',
|
||||
'Официальный сайт: https://bitforce-foundation.ru',
|
||||
],
|
||||
},
|
||||
{ kind: 'subheading', text: '9.5. Подтверждение понимания:' },
|
||||
{ kind: 'text', variant: 'emphasis', text: 'Предоставляя настоящее согласие, я подтверждаю, что:' },
|
||||
{
|
||||
kind: 'list',
|
||||
items: [
|
||||
'Ознакомлен с содержанием согласия и понимаю его значение',
|
||||
'Понимаю цели и способы обработки моих персональных данных',
|
||||
'Знаю о своих правах и способах их реализации',
|
||||
'Согласие предоставляется добровольно и осознанно',
|
||||
'Имею возможность отозвать согласие в любое время',
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
@@ -1,127 +0,0 @@
|
||||
.main {
|
||||
padding: 40px 20px;
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.container {
|
||||
background: var(--bg-mid, #1b1547);
|
||||
padding: 40px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 28px;
|
||||
font-weight: 700;
|
||||
margin-bottom: 20px;
|
||||
text-align: center;
|
||||
color: var(--text-primary, #ffffff);
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
font-size: 22px;
|
||||
font-weight: 600;
|
||||
margin-bottom: 30px;
|
||||
text-align: center;
|
||||
color: var(--text-secondary, #b5b0cc);
|
||||
}
|
||||
|
||||
.section {
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
|
||||
.sectionTitle {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
margin-bottom: 15px;
|
||||
color: var(--text-primary, #ffffff);
|
||||
border-bottom: 2px solid var(--interactive, #4a6dff);
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
.subSectionTitle {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
margin-top: 20px;
|
||||
margin-bottom: 10px;
|
||||
color: var(--text-primary, #ffffff);
|
||||
}
|
||||
|
||||
.list {
|
||||
list-style: disc;
|
||||
margin-left: 20px;
|
||||
line-height: 1.8;
|
||||
color: var(--text-primary, #ffffff);
|
||||
}
|
||||
|
||||
.list li {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.list strong {
|
||||
color: var(--text-primary, #ffffff);
|
||||
}
|
||||
|
||||
.info {
|
||||
padding: 20px;
|
||||
background: var(--glass-bg, rgba(255, 255, 255, 0.04));
|
||||
border-left: 4px solid var(--interactive, #4a6dff);
|
||||
border-radius: 4px;
|
||||
margin: 15px 0;
|
||||
line-height: 1.8;
|
||||
}
|
||||
|
||||
.info p {
|
||||
margin: 8px 0;
|
||||
color: var(--text-primary, #ffffff);
|
||||
}
|
||||
|
||||
.contacts {
|
||||
padding: 20px;
|
||||
background: var(--glass-bg, rgba(255, 255, 255, 0.04));
|
||||
border-left: 4px solid var(--interactive, #4a6dff);
|
||||
border-radius: 4px;
|
||||
line-height: 1.8;
|
||||
}
|
||||
|
||||
.contacts p {
|
||||
margin: 8px 0;
|
||||
color: var(--text-primary, #ffffff);
|
||||
}
|
||||
|
||||
.confirmation {
|
||||
padding: 10px 0;
|
||||
color: var(--text-primary, #ffffff);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.container {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 22px;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.sectionTitle {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.subSectionTitle {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.list {
|
||||
margin-left: 15px;
|
||||
}
|
||||
|
||||
.info,
|
||||
.contacts {
|
||||
padding: 15px;
|
||||
}
|
||||
}
|
||||
@@ -1,299 +1,13 @@
|
||||
import { LegalDoc } from '@entities/legal-doc'
|
||||
import { Footer } from '@widgets/footer'
|
||||
import { Header } from '@widgets/header'
|
||||
import styles from './SoglasiePage.module.css'
|
||||
import { soglasieDoc } from '../model/content'
|
||||
|
||||
export function SoglasiePage() {
|
||||
return (
|
||||
<>
|
||||
<Header />
|
||||
<main className={styles.main}>
|
||||
<div className={styles.container}>
|
||||
<h1 className={styles.title}>СОГЛАСИЕ НА ОБРАБОТКУ ПЕРСОНАЛЬНЫХ ДАННЫХ</h1>
|
||||
<h2 className={styles.subtitle}>ООО «БИТФОРС»</h2>
|
||||
|
||||
<section className={styles.section}>
|
||||
<h3 className={styles.sectionTitle}>Преамбула</h3>
|
||||
<p>
|
||||
Я, субъект персональных данных, действуя своей волей и в своем интересе, в соответствии с требованиями Федерального закона от 27.07.2006 № 152-ФЗ «О персональных данных», предоставляю ООО «БИТФОРС» согласие на обработку моих персональных данных на условиях и для целей, определенных настоящим Согласием.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section className={styles.section}>
|
||||
<h3 className={styles.sectionTitle}>1. Сведения об операторе</h3>
|
||||
<div className={styles.info}>
|
||||
<p>Полное наименование: Общество с ограниченной ответственностью «БИТФОРС»</p>
|
||||
<p>ИНН: 9810001062</p>
|
||||
<p>ОГРН: 1257800060990</p>
|
||||
<p>Юридический адрес: 196246, город Санкт-Петербург, Московское шоссе, дом 25, корпус 1, литера В, помещение 3-н</p>
|
||||
<p>Электронная почта: company@bitforcefoundation.ru</p>
|
||||
<p>Веб-сайт: https://bitforce-foundation.ru</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className={styles.section}>
|
||||
<h3 className={styles.sectionTitle}>2. Правовые основания обработки</h3>
|
||||
<p>
|
||||
Настоящее согласие предоставляется на основании пункта 1 части 1 статьи 6 Федерального закона «О персональных данных» и является правовым основанием для обработки персональных данных Оператором.
|
||||
</p>
|
||||
<p>Согласие дается добровольно, своей волей и в своих интересах.</p>
|
||||
<p>
|
||||
Субъект персональных данных понимает последствия предоставления согласия, включая возможные риски, связанные с обработкой персональных данных.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section className={styles.section}>
|
||||
<h3 className={styles.sectionTitle}>3. Цели обработки персональных данных</h3>
|
||||
|
||||
<h4 className={styles.subSectionTitle}>3.1. Основные цели:</h4>
|
||||
<ul className={styles.list}>
|
||||
<li>Регистрация и ведение учетной записи на веб-сайте и в мобильном приложении</li>
|
||||
<li>Идентификация и верификация личности в соответствии с требованиями законодательства</li>
|
||||
<li>Предоставление услуг по обмену криптовалют и электронных денежных средств</li>
|
||||
<li>Проведение финансовых операций, переводов и расчетов</li>
|
||||
<li>Ведение учета и истории операций</li>
|
||||
</ul>
|
||||
|
||||
<h4 className={styles.subSectionTitle}>3.2. Дополнительные цели:</h4>
|
||||
<ul className={styles.list}>
|
||||
<li>Обеспечение безопасности операций и предотвращение мошенничества</li>
|
||||
<li>Выполнение требований по противодействию легализации доходов</li>
|
||||
<li>Соблюдение требований валютного, налогового и иного применимого законодательства</li>
|
||||
<li>Предоставление технической поддержки и клиентского сервиса</li>
|
||||
<li>Рассылка уведомлений о состоянии операций и изменениях в условиях</li>
|
||||
</ul>
|
||||
|
||||
<h4 className={styles.subSectionTitle}>3.3. Маркетинговые цели (при дополнительном согласии):</h4>
|
||||
<ul className={styles.list}>
|
||||
<li>Направление информационных и рекламных материалов</li>
|
||||
<li>Проведение маркетинговых исследований и опросов</li>
|
||||
<li>Персонализация предложений и услуг</li>
|
||||
<li>Анализ предпочтений и поведения для улучшения сервисов</li>
|
||||
</ul>
|
||||
|
||||
<h4 className={styles.subSectionTitle}>3.4. Аналитические цели:</h4>
|
||||
<ul className={styles.list}>
|
||||
<li>Анализ использования веб-сайта и мобильного приложения</li>
|
||||
<li>Улучшение качества предоставляемых услуг</li>
|
||||
<li>Разработка новых продуктов и сервисов</li>
|
||||
<li>Создание статистических отчетов в обезличенном виде</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section className={styles.section}>
|
||||
<h3 className={styles.sectionTitle}>4. Перечень персональных данных</h3>
|
||||
|
||||
<h4 className={styles.subSectionTitle}>4.1. Идентификационные данные:</h4>
|
||||
<ul className={styles.list}>
|
||||
<li>Фамилия, имя, отчество</li>
|
||||
<li>Дата рождения</li>
|
||||
<li>Гражданство</li>
|
||||
</ul>
|
||||
|
||||
<h4 className={styles.subSectionTitle}>4.2. Документы, удостоверяющие личность:</h4>
|
||||
<ul className={styles.list}>
|
||||
<li>Серия и номер паспорта гражданина Российской Федерации</li>
|
||||
<li>Дата выдачи и код подразделения</li>
|
||||
<li>Адрес регистрации по месту жительства</li>
|
||||
<li>Цифровые копии (сканы) документов</li>
|
||||
</ul>
|
||||
|
||||
<h4 className={styles.subSectionTitle}>4.3. Контактная информация:</h4>
|
||||
<ul className={styles.list}>
|
||||
<li>Номера телефонов (мобильный, домашний, рабочий)</li>
|
||||
<li>Адреса электронной почты</li>
|
||||
<li>Почтовые адреса (фактического проживания, для корреспонденции)</li>
|
||||
</ul>
|
||||
|
||||
<h4 className={styles.subSectionTitle}>4.4. Финансовая информация:</h4>
|
||||
<ul className={styles.list}>
|
||||
<li>Номера банковских счетов и реквизиты банковских карт</li>
|
||||
<li>Реквизиты криптовалютных кошельков и адресов</li>
|
||||
<li>Информация о доходах и источниках происхождения денежных средств</li>
|
||||
<li>История финансовых операций и транзакций</li>
|
||||
</ul>
|
||||
|
||||
<h4 className={styles.subSectionTitle}>4.5. Техническая информация:</h4>
|
||||
<ul className={styles.list}>
|
||||
<li>IP-адреса устройств, с которых осуществляется доступ к сервисам</li>
|
||||
<li>Информация о браузере, операционной системе и устройстве</li>
|
||||
<li>Файлы cookie и данные локального хранилища</li>
|
||||
<li>Логи действий и история использования сервисов</li>
|
||||
</ul>
|
||||
|
||||
<h4 className={styles.subSectionTitle}>4.6. Дополнительная информация:</h4>
|
||||
<ul className={styles.list}>
|
||||
<li>Фотографии для процедур верификации</li>
|
||||
<li>Видеозаписи процедур видеоидентификации</li>
|
||||
<li>Биометрические данные (при использовании соответствующих технологий)</li>
|
||||
<li>Информация о семейном положении и профессиональной деятельности</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section className={styles.section}>
|
||||
<h3 className={styles.sectionTitle}>5. Перечень действий с персональными данными</h3>
|
||||
<p>Согласие распространяется на следующие действия (операции) с персональными данными:</p>
|
||||
<ul className={styles.list}>
|
||||
<li>Сбор, запись и первичная обработка персональных данных</li>
|
||||
<li>Накопление и систематизация в базах данных</li>
|
||||
<li>Создание резервных копий и архивирование</li>
|
||||
<li>Извлечение, использование и анализ данных</li>
|
||||
<li>Уточнение, обновление и актуализация информации</li>
|
||||
<li>Передача данных третьим лицам</li>
|
||||
<li>Обезличивание и удаление данных</li>
|
||||
<li>Автоматизированная обработка и профилирование</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section className={styles.section}>
|
||||
<h3 className={styles.sectionTitle}>6. Лица, которым могут быть переданы персональные данные</h3>
|
||||
|
||||
<h4 className={styles.subSectionTitle}>6.1. Сотрудники Оператора:</h4>
|
||||
<ul className={styles.list}>
|
||||
<li>Уполномоченные сотрудники, непосредственно участвующие в обработке</li>
|
||||
<li>Сотрудники службы безопасности и комплаенса</li>
|
||||
<li>Сотрудники технической поддержки</li>
|
||||
<li>Руководящий состав в рамках их полномочий</li>
|
||||
</ul>
|
||||
|
||||
<h4 className={styles.subSectionTitle}>6.2. Государственные и муниципальные органы:</h4>
|
||||
<ul className={styles.list}>
|
||||
<li>Федеральная служба по финансовому мониторингу</li>
|
||||
<li>Федеральная налоговая служба</li>
|
||||
<li>Правоохранительные органы (при наличии законных требований)</li>
|
||||
<li>Суды и органы исполнения судебных решений</li>
|
||||
</ul>
|
||||
|
||||
<h4 className={styles.subSectionTitle}>6.3. Партнеры и контрагенты:</h4>
|
||||
<ul className={styles.list}>
|
||||
<li>Банки и платежные системы</li>
|
||||
<li>Операторы электронных денежных средств</li>
|
||||
<li>Поставщики технологических решений</li>
|
||||
<li>Аудиторские и консалтинговые организации</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section className={styles.section}>
|
||||
<h3 className={styles.sectionTitle}>7. Сроки обработки персональных данных</h3>
|
||||
|
||||
<h4 className={styles.subSectionTitle}>7.1. Общие принципы:</h4>
|
||||
<p>
|
||||
Персональные данные обрабатываются в течение времени, необходимого для достижения целей обработки. После достижения целей данные подлежат уничтожению или обезличиванию.
|
||||
</p>
|
||||
|
||||
<h4 className={styles.subSectionTitle}>7.2. Конкретные сроки обработки:</h4>
|
||||
<ul className={styles.list}>
|
||||
<li>
|
||||
<strong>Данные активных клиентов:</strong> в течение всего периода отношений плюс 5 лет после прекращения
|
||||
</li>
|
||||
<li>
|
||||
<strong>Данные для идентификации:</strong> 5 лет с момента прекращения отношений
|
||||
</li>
|
||||
<li>
|
||||
<strong>Финансовая информация:</strong> 5 лет с даты совершения операции
|
||||
</li>
|
||||
<li>
|
||||
<strong>Маркетинговые данные:</strong> до отзыва согласия, но не более 3 лет
|
||||
</li>
|
||||
<li>
|
||||
<strong>Техническая информация:</strong> 1 год для безопасности, 6 месяцев для логов
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section className={styles.section}>
|
||||
<h3 className={styles.sectionTitle}>8. Права субъекта персональных данных</h3>
|
||||
|
||||
<h4 className={styles.subSectionTitle}>8.1. Право на информацию:</h4>
|
||||
<ul className={styles.list}>
|
||||
<li>Получение подтверждения факта обработки персональных данных</li>
|
||||
<li>Получение информации о целях и способах обработки</li>
|
||||
<li>Информация о сроках обработки и составе данных</li>
|
||||
<li>Сведения о лицах, которым передаются данные</li>
|
||||
</ul>
|
||||
|
||||
<h4 className={styles.subSectionTitle}>8.2. Право на доступ:</h4>
|
||||
<ul className={styles.list}>
|
||||
<li>Получение копий обрабатываемых персональных данных</li>
|
||||
<li>Ознакомление с историей обработки и изменений</li>
|
||||
<li>Получение информации об источниках персональных данных</li>
|
||||
</ul>
|
||||
|
||||
<h4 className={styles.subSectionTitle}>8.3. Право на исправление и удаление:</h4>
|
||||
<ul className={styles.list}>
|
||||
<li>Требование исправления неточных или неполных данных</li>
|
||||
<li>Требование удаления персональных данных при наличии оснований</li>
|
||||
<li>Удаление данных после отзыва согласия</li>
|
||||
</ul>
|
||||
|
||||
<h4 className={styles.subSectionTitle}>8.4. Право на отзыв согласия:</h4>
|
||||
<ul className={styles.list}>
|
||||
<li>Согласие может быть отозвано в любое время</li>
|
||||
<li>Отзыв оформляется в письменной форме</li>
|
||||
<li>После отзыва обработка прекращается в разумные сроки</li>
|
||||
</ul>
|
||||
|
||||
<h4 className={styles.subSectionTitle}>8.5. Право на обжалование:</h4>
|
||||
<ul className={styles.list}>
|
||||
<li>Обращение к Оператору с жалобами на действия по обработке данных</li>
|
||||
<li>Обращение в Роскомнадзор или его территориальные органы</li>
|
||||
<li>Обращение в суд для защиты нарушенных прав</li>
|
||||
</ul>
|
||||
|
||||
<h4 className={styles.subSectionTitle}>8.6. Порядок реализации прав:</h4>
|
||||
<ul className={styles.list}>
|
||||
<li>Обращения направляются на адрес: company@bitforcefoundation.ru</li>
|
||||
<li>Обращения рассматриваются в течение 30 дней</li>
|
||||
<li>При необходимости срок может быть продлен на 30 дней</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section className={styles.section}>
|
||||
<h3 className={styles.sectionTitle}>9. Заключительные положения</h3>
|
||||
|
||||
<h4 className={styles.subSectionTitle}>9.1. Действие согласия:</h4>
|
||||
<ul className={styles.list}>
|
||||
<li>Согласие действует с момента его предоставления</li>
|
||||
<li>Согласие действует до его отзыва или до достижения целей обработки</li>
|
||||
<li>При существенных изменениях целей требуется новое согласие</li>
|
||||
</ul>
|
||||
|
||||
<h4 className={styles.subSectionTitle}>9.2. Форма предоставления согласия:</h4>
|
||||
<ul className={styles.list}>
|
||||
<li>Согласие может быть предоставлено в письменной форме</li>
|
||||
<li>Согласие может быть предоставлено в электронной форме</li>
|
||||
<li>Согласие может выражаться путем совершения конклюдентных действий</li>
|
||||
</ul>
|
||||
|
||||
<h4 className={styles.subSectionTitle}>9.3. Последствия непредоставления согласия:</h4>
|
||||
<ul className={styles.list}>
|
||||
<li>Отказ в предоставлении согласия может повлечь невозможность регистрации</li>
|
||||
<li>Отказ может ограничить доступ к отдельным услугам</li>
|
||||
<li>Отказ в согласии на маркетинг не влияет на основные услуги</li>
|
||||
<li>Субъект вправе предоставить частичное согласие</li>
|
||||
</ul>
|
||||
|
||||
<h4 className={styles.subSectionTitle}>9.4. Контактная информация:</h4>
|
||||
<div className={styles.contacts}>
|
||||
<p>Почтовый адрес: 196246, г. Санкт-Петербург, Московское ш., д. 25, к. 1, лит. В, пом. 3-н</p>
|
||||
<p>Электронная почта: company@bitforcefoundation.ru</p>
|
||||
<p>Ответственное лицо: Кленин Михаил Васильевич</p>
|
||||
<p>Официальный сайт: https://bitforce-foundation.ru</p>
|
||||
</div>
|
||||
|
||||
<h4 className={styles.subSectionTitle}>9.5. Подтверждение понимания:</h4>
|
||||
<p className={styles.confirmation}>
|
||||
Предоставляя настоящее согласие, я подтверждаю, что:
|
||||
</p>
|
||||
<ul className={styles.list}>
|
||||
<li>Ознакомлен с содержанием согласия и понимаю его значение</li>
|
||||
<li>Понимаю цели и способы обработки моих персональных данных</li>
|
||||
<li>Знаю о своих правах и способах их реализации</li>
|
||||
<li>Согласие предоставляется добровольно и осознанно</li>
|
||||
<li>Имею возможность отозвать согласие в любое время</li>
|
||||
</ul>
|
||||
</section>
|
||||
</div>
|
||||
</main>
|
||||
<LegalDoc doc={soglasieDoc} />
|
||||
<Footer />
|
||||
</>
|
||||
)
|
||||
|
||||
6
src/shared/lib/utils/formatUsd.ts
Normal file
6
src/shared/lib/utils/formatUsd.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
// Formats a numeric USD value as "$1,234.56". Returns "$—" when the value is
|
||||
// missing (loading / unavailable), so callers don't repeat the fallback.
|
||||
export function formatUsd(value: number | null | undefined): string {
|
||||
if (value == null) return '$—'
|
||||
return `$${value.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`
|
||||
}
|
||||
1
src/widgets/legal-converter/index.ts
Normal file
1
src/widgets/legal-converter/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { LegalConverterSection } from './ui/LegalConverterForm'
|
||||
103
src/widgets/legal-converter/model/useLegalConverterForm.ts
Normal file
103
src/widgets/legal-converter/model/useLegalConverterForm.ts
Normal file
@@ -0,0 +1,103 @@
|
||||
import { useEffect, useState, type FormEvent } from 'react'
|
||||
import {
|
||||
useCreatePurchaseRequest,
|
||||
useMyPurchaseRequests,
|
||||
TERM_OPTIONS,
|
||||
MIN_ORDER,
|
||||
formatRub,
|
||||
calcQuote,
|
||||
buildPurchaseComment,
|
||||
parseContactFromComment,
|
||||
latestPurchaseRequest,
|
||||
} from '@features/b2b'
|
||||
|
||||
// Вся логика формы B2B-заявки: стейт полей, расчёт комиссии (через calcQuote),
|
||||
// упаковка comment и отправка. По образцу widgets/converter-page/model.
|
||||
export function useLegalConverterForm() {
|
||||
const [amount, setAmount] = useState('')
|
||||
const [name, setName] = useState('')
|
||||
const [contact, setContact] = useState('')
|
||||
const [days, setDays] = useState<number>(TERM_OPTIONS[0].days)
|
||||
const [notice, setNotice] = useState<'success' | 'error' | null>(null)
|
||||
|
||||
const { mutate: submitRequest, isPending } = useCreatePurchaseRequest()
|
||||
const { data: requests } = useMyPurchaseRequests()
|
||||
|
||||
// Автозаполнение имени и контакта из последней заявки аккаунта (один раз).
|
||||
const [prefilled, setPrefilled] = useState(false)
|
||||
useEffect(() => {
|
||||
if (prefilled || !requests) return
|
||||
const last = latestPurchaseRequest(requests.items)
|
||||
if (!last) return
|
||||
const parsed = parseContactFromComment(last.comment)
|
||||
if (parsed.name) setName(parsed.name)
|
||||
if (parsed.contact) setContact(parsed.contact)
|
||||
setPrefilled(true)
|
||||
}, [prefilled, requests])
|
||||
|
||||
const numAmount = Number(amount.replace(/\D/g, '')) || 0
|
||||
const belowMin = numAmount > 0 && numAmount < MIN_ORDER
|
||||
|
||||
const { rate, commission, total } = calcQuote(numAmount, days)
|
||||
|
||||
const onAmountChange = (value: string) => {
|
||||
const digits = value.replace(/\D/g, '')
|
||||
setAmount(digits ? formatRub(Number(digits)) : '')
|
||||
}
|
||||
|
||||
const canSubmit = numAmount > 0 && !belowMin && !isPending
|
||||
|
||||
const onSubmit = (e: FormEvent) => {
|
||||
e.preventDefault()
|
||||
if (!canSubmit) return
|
||||
|
||||
const comment = buildPurchaseComment({
|
||||
name,
|
||||
contact,
|
||||
days,
|
||||
amount: numAmount,
|
||||
commission,
|
||||
total,
|
||||
})
|
||||
|
||||
submitRequest(
|
||||
{ usdt_amount: numAmount, comment },
|
||||
{
|
||||
onSuccess: () => {
|
||||
setNotice('success')
|
||||
setAmount('')
|
||||
setName('')
|
||||
setContact('')
|
||||
setDays(TERM_OPTIONS[0].days)
|
||||
},
|
||||
onError: () => setNotice('error'),
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
return {
|
||||
// значения полей
|
||||
amount,
|
||||
name,
|
||||
contact,
|
||||
days,
|
||||
// сеттеры
|
||||
setName,
|
||||
setContact,
|
||||
setDays,
|
||||
onAmountChange,
|
||||
// деривации для панели условий
|
||||
numAmount,
|
||||
belowMin,
|
||||
rate,
|
||||
commission,
|
||||
total,
|
||||
// отправка
|
||||
onSubmit,
|
||||
isPending,
|
||||
canSubmit,
|
||||
// нотификация
|
||||
notice,
|
||||
dismissNotice: () => setNotice(null),
|
||||
}
|
||||
}
|
||||
136
src/widgets/legal-converter/ui/LegalConverterForm.tsx
Normal file
136
src/widgets/legal-converter/ui/LegalConverterForm.tsx
Normal file
@@ -0,0 +1,136 @@
|
||||
import { FormField, Notification, Select } from '@shared/ui'
|
||||
import { TERM_OPTIONS, MIN_ORDER, formatRub, dayLabel } from '@features/b2b'
|
||||
import { useLegalConverterForm } from '../model/useLegalConverterForm'
|
||||
import styles from './LegalConverterForm.module.css'
|
||||
|
||||
// Форма B2B-заявки для юрлиц (legal-аккаунты). Близнец ConverterSection из
|
||||
// widgets/converter-page для физлиц; рендерится из ConverterPage по account_type.
|
||||
// Вся логика — в useLegalConverterForm, здесь только разметка.
|
||||
export function LegalConverterSection() {
|
||||
const {
|
||||
amount,
|
||||
name,
|
||||
contact,
|
||||
days,
|
||||
setName,
|
||||
setContact,
|
||||
setDays,
|
||||
onAmountChange,
|
||||
numAmount,
|
||||
belowMin,
|
||||
rate,
|
||||
commission,
|
||||
total,
|
||||
onSubmit,
|
||||
isPending,
|
||||
canSubmit,
|
||||
notice,
|
||||
dismissNotice,
|
||||
} = useLegalConverterForm()
|
||||
|
||||
return (
|
||||
<form className={styles.wrap} onSubmit={onSubmit}>
|
||||
<div className={styles.header}>
|
||||
<h1 className={styles.title}>Оставить заявку</h1>
|
||||
<p className={styles.subtitle}>
|
||||
Конвертация крупных объёмов по индивидуальному курсу. Оставьте заявку —
|
||||
менеджер свяжется с вами, подтвердит актуальный курс и сопроводит сделку.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className={styles.body}>
|
||||
<div className={styles.formCol}>
|
||||
<FormField
|
||||
label="Объём заявки, ₽"
|
||||
type="text"
|
||||
value={amount}
|
||||
onChange={onAmountChange}
|
||||
placeholder="от 500 000"
|
||||
/>
|
||||
{belowMin && (
|
||||
<p className={styles.hint}>
|
||||
Минимальный объём заявки — {formatRub(MIN_ORDER)} ₽
|
||||
</p>
|
||||
)}
|
||||
|
||||
<Select
|
||||
id="term"
|
||||
label="Срок ожидания операции"
|
||||
value={days}
|
||||
onChange={setDays}
|
||||
options={TERM_OPTIONS.map((o) => ({
|
||||
value: o.days,
|
||||
label: `${dayLabel(o.days)} — комиссия ${(o.rate * 100).toFixed(3)} %`,
|
||||
}))}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
label="Как к вам обращаться"
|
||||
type="text"
|
||||
value={name}
|
||||
onChange={setName}
|
||||
placeholder="Имя"
|
||||
/>
|
||||
|
||||
<FormField
|
||||
label="Email или телефон для связи"
|
||||
type="text"
|
||||
value={contact}
|
||||
onChange={setContact}
|
||||
placeholder="example@mail.ru / +7 900 000-00-00"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className={styles.infoCol}>
|
||||
<div className={styles.infoTitle}>УСЛОВИЯ</div>
|
||||
|
||||
<div className={styles.infoRow}>
|
||||
<span className={styles.infoLabel}>Минимальный объём</span>
|
||||
<span className={styles.infoValue}>{formatRub(MIN_ORDER)} ₽</span>
|
||||
</div>
|
||||
|
||||
<div className={styles.infoRow}>
|
||||
<span className={styles.infoLabel}>Срок ожидания</span>
|
||||
<span className={styles.infoValue}>{dayLabel(days)}</span>
|
||||
</div>
|
||||
|
||||
<div className={styles.infoRow}>
|
||||
<span className={styles.infoLabel}>Ставка комиссии</span>
|
||||
<span className={styles.infoValue}>{(rate * 100).toFixed(3)} %</span>
|
||||
</div>
|
||||
|
||||
<div className={styles.infoRow}>
|
||||
<span className={styles.infoLabel}>Сумма комиссии</span>
|
||||
<span className={styles.infoValue}>
|
||||
{numAmount > 0 ? `≈ ${formatRub(commission)} ₽` : '—'}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className={styles.infoRow} data-accent>
|
||||
<span className={styles.infoLabel}>Итого к оплате</span>
|
||||
<span className={styles.infoValue}>
|
||||
{numAmount > 0 ? `≈ ${formatRub(total)} ₽` : '—'}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<p className={styles.note}>
|
||||
Итоговая комиссия рассчитывается индивидуально и зависит от объёма,
|
||||
валюты и направления сделки.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button type="submit" className={styles.submitBtn} disabled={!canSubmit}>
|
||||
{isPending ? 'Отправляем...' : 'Оставить заявку'}
|
||||
</button>
|
||||
|
||||
{notice && (
|
||||
<Notification
|
||||
status={notice}
|
||||
message={notice === 'success' ? 'Заявка отправлена' : 'Не удалось отправить заявку'}
|
||||
onClose={dismissNotice}
|
||||
/>
|
||||
)}
|
||||
</form>
|
||||
)
|
||||
}
|
||||
@@ -1,2 +1,7 @@
|
||||
export { ProfileAvatar } from './ui/ProfileAvatar'
|
||||
export { ProfileSection } from './ui/ProfileSection'
|
||||
export { ProfileSummary } from './ui/ProfileSummary'
|
||||
export { ProfileDetails } from './ui/ProfileDetails'
|
||||
export { SecuritySection } from './ui/SecuritySection'
|
||||
export { MnemonicSection } from './ui/MnemonicSection'
|
||||
export { KycBanner } from './ui/KycBanner'
|
||||
|
||||
47
src/widgets/profile/model/usePhoneField.ts
Normal file
47
src/widgets/profile/model/usePhoneField.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useUpdatePhone } from '@features/auth'
|
||||
|
||||
type PhoneNotification = { message: string; status: 'success' | 'error' }
|
||||
|
||||
// Owns the editable phone field: local value synced from the server, input
|
||||
// sanitization, save-on-blur, and the resulting success/error notification.
|
||||
// The notification has no meaning outside this flow, so it lives here too.
|
||||
export function usePhoneField(serverPhone: string | null) {
|
||||
const updatePhone = useUpdatePhone()
|
||||
const [phone, setPhone] = useState('')
|
||||
const [savedPhone, setSavedPhone] = useState('')
|
||||
const [notification, setNotification] = useState<PhoneNotification | null>(null)
|
||||
|
||||
useEffect(() => {
|
||||
if (serverPhone != null) {
|
||||
setPhone(serverPhone)
|
||||
setSavedPhone(serverPhone)
|
||||
}
|
||||
}, [serverPhone])
|
||||
|
||||
function onPhoneChange(value: string) {
|
||||
setPhone(value.replace(/[^\d+\s()-]/g, ''))
|
||||
}
|
||||
|
||||
function onPhoneBlur() {
|
||||
const next = phone.trim()
|
||||
if (next === savedPhone || updatePhone.isPending) return
|
||||
updatePhone.mutate(next, {
|
||||
onSuccess: () => {
|
||||
setSavedPhone(next)
|
||||
setNotification({ status: 'success', message: 'Номер телефона обновлён' })
|
||||
},
|
||||
onError: () => {
|
||||
setNotification({ status: 'error', message: 'Не удалось обновить номер телефона' })
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
phone,
|
||||
onPhoneChange,
|
||||
onPhoneBlur,
|
||||
notification,
|
||||
dismissNotification: () => setNotification(null),
|
||||
}
|
||||
}
|
||||
@@ -1,17 +1,17 @@
|
||||
import type { MeResponse } from '@features/auth'
|
||||
import { FormField } from '@shared/ui'
|
||||
import { ProfileSection } from '@widgets/profile'
|
||||
import styles from './ProfilePage.module.css'
|
||||
import { type MeResponse, getFullName } from '@features/auth'
|
||||
import { FormField, Notification } from '@shared/ui'
|
||||
import { ProfileSection } from './ProfileSection'
|
||||
import { usePhoneField } from '../model/usePhoneField'
|
||||
import styles from './fieldsGrid.module.css'
|
||||
|
||||
interface Props {
|
||||
data: MeResponse
|
||||
fullName: string
|
||||
phone: string
|
||||
onPhoneChange: (value: string) => void
|
||||
onPhoneBlur: () => void
|
||||
}
|
||||
|
||||
export function IndividualFields({ data, fullName, phone, onPhoneChange, onPhoneBlur }: Props) {
|
||||
export function IndividualFields({ data }: Props) {
|
||||
const fullName = getFullName(data)
|
||||
const { phone, onPhoneChange, onPhoneBlur, notification, dismissNotification } = usePhoneField(data.phone)
|
||||
|
||||
return (
|
||||
<>
|
||||
<ProfileSection title="Личные данные">
|
||||
@@ -29,6 +29,10 @@ export function IndividualFields({ data, fullName, phone, onPhoneChange, onPhone
|
||||
<FormField label="ID аккаунта" value={data.id ?? ''} readOnly icon="lock" placeholder="ECSA-00000000" />
|
||||
</div>
|
||||
</ProfileSection>
|
||||
|
||||
{notification && (
|
||||
<Notification status={notification.status} message={notification.message} onClose={dismissNotification} />
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
10
src/widgets/profile/ui/KycBanner.module.css
Normal file
10
src/widgets/profile/ui/KycBanner.module.css
Normal file
@@ -0,0 +1,10 @@
|
||||
.kycBanner {
|
||||
width: 100%;
|
||||
padding: 10px 24px;
|
||||
background: var(--error, #ff4466);
|
||||
color: #fff;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
text-align: center;
|
||||
line-height: 1.4;
|
||||
}
|
||||
14
src/widgets/profile/ui/KycBanner.tsx
Normal file
14
src/widgets/profile/ui/KycBanner.tsx
Normal file
@@ -0,0 +1,14 @@
|
||||
import { useMe } from '@features/auth'
|
||||
import styles from './KycBanner.module.css'
|
||||
|
||||
// Shown until the account passes identity verification (KYC).
|
||||
export function KycBanner() {
|
||||
const { data } = useMe()
|
||||
if (!data || data.kyc_verified) return null
|
||||
|
||||
return (
|
||||
<div className={styles.kycBanner}>
|
||||
Для полного доступа к платформе необходимо пройти верификацию личности (KYC)
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { MeResponse } from '@features/auth'
|
||||
import { FormField } from '@shared/ui'
|
||||
import { ProfileSection } from '@widgets/profile'
|
||||
import styles from './ProfilePage.module.css'
|
||||
import { ProfileSection } from './ProfileSection'
|
||||
import styles from './fieldsGrid.module.css'
|
||||
|
||||
interface Props {
|
||||
data: MeResponse
|
||||
31
src/widgets/profile/ui/MnemonicSection.module.css
Normal file
31
src/widgets/profile/ui/MnemonicSection.module.css
Normal file
@@ -0,0 +1,31 @@
|
||||
.mnemonicRow {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.mnemonicInfo {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.mnemonicIcon {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.mnemonicText {
|
||||
color: var(--text-secondary);
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
/* < 650px: кнопка мнемоники — 100% ширины */
|
||||
@media (max-width: 649px) {
|
||||
.mnemonicRow {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
gap: 16px;
|
||||
}
|
||||
}
|
||||
21
src/widgets/profile/ui/MnemonicSection.tsx
Normal file
21
src/widgets/profile/ui/MnemonicSection.tsx
Normal file
@@ -0,0 +1,21 @@
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
import { ROUTES } from '@shared/config/routes'
|
||||
import { Button } from '@shared/ui'
|
||||
import { ProfileSection } from './ProfileSection'
|
||||
import styles from './MnemonicSection.module.css'
|
||||
|
||||
export function MnemonicSection() {
|
||||
const navigate = useNavigate()
|
||||
|
||||
return (
|
||||
<ProfileSection title="Мнемоника">
|
||||
<div className={styles.mnemonicRow}>
|
||||
<div className={styles.mnemonicInfo}>
|
||||
<span className={styles.mnemonicIcon}>🔑</span>
|
||||
<span className={styles.mnemonicText}>Сид-фраза из 12 слов для восстановления кошелька</span>
|
||||
</div>
|
||||
<Button variant="danger" onClick={() => navigate(ROUTES.SEED_PHRASE)}>⚠ Показать мнемонику</Button>
|
||||
</div>
|
||||
</ProfileSection>
|
||||
)
|
||||
}
|
||||
12
src/widgets/profile/ui/ProfileDetails.tsx
Normal file
12
src/widgets/profile/ui/ProfileDetails.tsx
Normal file
@@ -0,0 +1,12 @@
|
||||
import { useMe, isLegalEntity } from '@features/auth'
|
||||
import { IndividualFields } from './IndividualFields'
|
||||
import { LegalEntityFields } from './LegalEntityFields'
|
||||
|
||||
// Picks the right field set for the account type. Phone editing lives inside
|
||||
// IndividualFields (the only branch with an editable phone).
|
||||
export function ProfileDetails() {
|
||||
const { data } = useMe()
|
||||
if (!data) return null
|
||||
|
||||
return isLegalEntity(data) ? <LegalEntityFields data={data} /> : <IndividualFields data={data} />
|
||||
}
|
||||
67
src/widgets/profile/ui/ProfileSummary.module.css
Normal file
67
src/widgets/profile/ui/ProfileSummary.module.css
Normal file
@@ -0,0 +1,67 @@
|
||||
/* Desktop: avatar is the left column, sections grow to its right */
|
||||
.profileTop {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.userInfo {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* < 1024px: avatar + userInfo side by side at the top */
|
||||
@media (max-width: 1023px) {
|
||||
.profileTop {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: flex-start;
|
||||
gap: 24px;
|
||||
}
|
||||
|
||||
.userInfo {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.userName {
|
||||
font-size: 28px;
|
||||
font-weight: 700;
|
||||
color: var(--text-primary);
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.userBalance {
|
||||
font-size: 32px;
|
||||
font-weight: 800;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.userBalanceRub {
|
||||
font-size: 13px;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
}
|
||||
|
||||
/* < 550px: меньше текст и аватар */
|
||||
@media (max-width: 549px) {
|
||||
.profileTop {
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.userInfo {
|
||||
padding-top: 8px;
|
||||
}
|
||||
|
||||
.userName {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.userBalance {
|
||||
font-size: 22px;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.userBalanceRub {
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
23
src/widgets/profile/ui/ProfileSummary.tsx
Normal file
23
src/widgets/profile/ui/ProfileSummary.tsx
Normal file
@@ -0,0 +1,23 @@
|
||||
import { useMe, getDisplayName } from '@features/auth'
|
||||
import { usePortfolio } from '@features/wallet'
|
||||
import { formatUsd } from '@shared/lib/utils/formatUsd'
|
||||
import { ProfileAvatar } from './ProfileAvatar'
|
||||
import styles from './ProfileSummary.module.css'
|
||||
|
||||
export function ProfileSummary() {
|
||||
const { data } = useMe()
|
||||
const { data: portfolio } = usePortfolio()
|
||||
|
||||
const displayName = data ? getDisplayName(data) : ''
|
||||
|
||||
return (
|
||||
<div className={styles.profileTop}>
|
||||
<ProfileAvatar />
|
||||
<div className={styles.userInfo}>
|
||||
<span className={styles.userName}>{displayName}</span>
|
||||
<span className={styles.userBalance}>{formatUsd(portfolio?.totalUsd)}</span>
|
||||
{/* <span className={styles.userBalanceRub}>≈ 22 340,50 ₽</span> */}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
33
src/widgets/profile/ui/SecuritySection.tsx
Normal file
33
src/widgets/profile/ui/SecuritySection.tsx
Normal file
@@ -0,0 +1,33 @@
|
||||
import { useWalletAddresses } from '@features/wallet'
|
||||
import { Button, FormField } from '@shared/ui'
|
||||
import { ProfileSection } from './ProfileSection'
|
||||
import styles from './fieldsGrid.module.css'
|
||||
|
||||
export function SecuritySection() {
|
||||
const { data: walletAddresses } = useWalletAddresses()
|
||||
|
||||
return (
|
||||
<ProfileSection
|
||||
title="Безопасность"
|
||||
actions={
|
||||
<>
|
||||
<Button variant="danger">⚠️ Посмотреть приватный ключ</Button>
|
||||
<Button variant="primary">СОХРАНИТЬ</Button>
|
||||
</>
|
||||
}
|
||||
>
|
||||
<div className={styles.grid1}>
|
||||
{walletAddresses?.map(({ chain, address }) => (
|
||||
<FormField
|
||||
key={chain}
|
||||
label={`Адрес ${chain}`}
|
||||
readOnly
|
||||
icon="lock"
|
||||
value={address}
|
||||
placeholder="—"
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</ProfileSection>
|
||||
)
|
||||
}
|
||||
25
src/widgets/profile/ui/fieldsGrid.module.css
Normal file
25
src/widgets/profile/ui/fieldsGrid.module.css
Normal file
@@ -0,0 +1,25 @@
|
||||
.grid2 {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.grid1 {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
/* < 1024px: tighter gap */
|
||||
@media (max-width: 1023px) {
|
||||
.grid2 {
|
||||
gap: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
/* < 640px: инпуты в колонку */
|
||||
@media (max-width: 639px) {
|
||||
.grid2 {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user