Реализация реферальной системы и улучшение системы авторизации
- Добавлена полная реферальная система с GraphQL резолверами и UI компонентами - Улучшена система регистрации с поддержкой ВКонтакте и реферальных ссылок - Обновлена схема Prisma для поддержки реферальной системы - Добавлены новые файлы документации правил системы - Улучшена система партнерства и контрагентов - Обновлены компоненты авторизации для поддержки новых функций - Удален устаревший server.log 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
500
seller-ui-rules.md
Normal file
500
seller-ui-rules.md
Normal file
@ -0,0 +1,500 @@
|
||||
# ПРАВИЛА UI/UX КАБИНЕТА СЕЛЛЕРА (SELLER)
|
||||
|
||||
> ⚠️ **ВАЖНО**: Это файл с UI/UX деталями кабинета селлера.
|
||||
> Общие бизнес-правила находятся в **[rules-complete.md](./rules-complete.md)**
|
||||
|
||||
## Когда использовать этот файл:
|
||||
|
||||
- Работа с компонентами `/supplies`, `/my-supplies`
|
||||
- UI/UX специфика кабинета селлера
|
||||
- Интерфейсы создания поставок
|
||||
- Визуальные правила компонентов селлера
|
||||
|
||||
## 1. 🛍️ СТРУКТУРА КАБИНЕТА СЕЛЛЕРА
|
||||
|
||||
### 1.1 Основные разделы
|
||||
|
||||
**СЕЛЛЕР (`SELLER`)** имеет доступ к следующим разделам:
|
||||
|
||||
- **Мои поставки** (`/my-supplies`) - управление поставками
|
||||
- **Маркет** (`/market`) - просмотр глобального каталога
|
||||
- **Партнеры** (`/partners`) - управление контрагентами
|
||||
- **Мессенджер** (`/messenger`) - связь с партнерами
|
||||
- **Настройки** (`/settings`) - профиль и настройки
|
||||
- **Экономика** (`/economics`) - финансовая аналитика
|
||||
|
||||
### 1.2 Навигация и роутинг
|
||||
|
||||
#### При входе в систему:
|
||||
|
||||
```typescript
|
||||
switch (user?.organization?.type) {
|
||||
case 'SELLER':
|
||||
router.push('/my-supplies') // Направляем на страницу поставок
|
||||
break
|
||||
}
|
||||
```
|
||||
|
||||
#### Специальная логика роутинга:
|
||||
|
||||
> 📖 **Бизнес-логика роутинга**: См. [rules-complete.md#4-система-ролей-и-доступов](./rules-complete.md#4--система-ролей-и-доступов)
|
||||
|
||||
## 2. 🎨 UI/UX КОМПОНЕНТЫ
|
||||
|
||||
### 2.1 Dashboard компоненты
|
||||
|
||||
#### Основные компоненты кабинета:
|
||||
|
||||
- `SellerHomePage` - главный компонент селлера
|
||||
- `SellerEconomicsPage` - экономическая аналитика
|
||||
- `MySuppliesDashboard` - управление поставками
|
||||
|
||||
#### Wrapper-компоненты:
|
||||
|
||||
- `HomePageWrapper` - маршрутизация по типам организаций
|
||||
- `EconomicsPageWrapper` - адаптивная экономика по кабинетам
|
||||
|
||||
### 2.2 Детальные правила горизонтального скролла поставщиков
|
||||
|
||||
**СТРУКТУРА И ОТОБРАЖЕНИЕ:**
|
||||
|
||||
- **Источник данных**: Партнеры типа `WHOLESALE` из раздела "Партнеры"
|
||||
- **Контейнер**: Фиксированная высота 176px (h-44) с горизонтальным скроллом
|
||||
- **Блок поставщиков**: Общая высота 180px, включает заголовок + контейнер скролла
|
||||
- **Направление**: Слева направо (LTR)
|
||||
- **Поведение**: Плавный скролл с автоскрытием полосы прокрутки
|
||||
|
||||
**РАЗМЕРЫ И АДАПТИВНОСТЬ:**
|
||||
|
||||
- **Десктоп**: Карточка 216×92px, отступы 12px между карточками, 16px от краев
|
||||
- **Планшет**: Карточка 200×92px, отступы 12px между карточками
|
||||
- **Мобильный**: Карточка 184×92px, отступы 12px между карточками
|
||||
- **Высота блока**: 180px фиксированная для всего блока поставщиков
|
||||
|
||||
**ВЗАИМОДЕЙСТВИЕ:**
|
||||
|
||||
- **Навигация**: Колесо мыши (Shift+скролл), стрелки клавиатуры, свайп на тач
|
||||
- **Выбор**: Клик по карточке → активная рамка + загрузка товаров в блок 2
|
||||
- **Состояния**: Default, Hover (box-shadow), Active (цветная рамка), Loading (скелетон)
|
||||
|
||||
**ГРАНИЧНЫЕ СЛУЧАИ:**
|
||||
|
||||
- **1-4 карточки**: Выравнивание по левому краю, скролл неактивен
|
||||
- **5+ карточек**: Полный горизонтальный скролл
|
||||
- **Нет партнеров**: Заглушка с ссылкой на раздел "Партнеры"
|
||||
|
||||
**ТЕХНИЧЕСКАЯ РЕАЛИЗАЦИЯ:**
|
||||
|
||||
**Критическая Flex-архитектура:**
|
||||
|
||||
```css
|
||||
.parent-container {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.left-block {
|
||||
flex: 1;
|
||||
min-width: 0; /* КРИТИЧЕСКИ ВАЖНО для overflow */
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.suppliers-container {
|
||||
height: 180px; /* Общая высота блока */
|
||||
flex-shrink: 0;
|
||||
min-width: 0; /* Предотвращает растяжение */
|
||||
}
|
||||
|
||||
.right-block {
|
||||
width: 384px; /* w-96 */
|
||||
flex-shrink: 0; /* Защита от сжатия */
|
||||
}
|
||||
```
|
||||
|
||||
**Контейнер скролла:**
|
||||
|
||||
```css
|
||||
.suppliers-block {
|
||||
display: flex;
|
||||
overflow-x: auto;
|
||||
scroll-behavior: smooth;
|
||||
gap: 12px;
|
||||
padding: 0 16px 8px 16px; /* px-4 pb-2 */
|
||||
height: 176px; /* h-44 */
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: #64748b33 transparent;
|
||||
}
|
||||
|
||||
.suppliers-block:hover {
|
||||
scrollbar-color: #cbd5e0 #64748b22;
|
||||
}
|
||||
|
||||
.supplier-card {
|
||||
flex-shrink: 0;
|
||||
width: 216px; /* Десктоп */
|
||||
height: 92px; /* Фиксированная высота */
|
||||
padding: 8px; /* p-2 */
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
```
|
||||
|
||||
**СОДЕРЖАНИЕ КАРТОЧКИ ПОСТАВЩИКА:**
|
||||
|
||||
**Структура (3 строки в 92px высоты):**
|
||||
|
||||
- **Строка 1**: Название + рейтинг (справа, если есть)
|
||||
- **Строка 2**: ИНН (формат "ИНН: 1234567890")
|
||||
- **Строка 3**: Бейдж рынка (отдельная строка)
|
||||
|
||||
**Элементы:**
|
||||
|
||||
- **Аватар**: Размер xs, слева с gap-2
|
||||
- **Текст**: text-xs для компактности
|
||||
- **Отступы**: mb-1 между строками 1-2, mb-0.5 между строками 2-3
|
||||
- **Padding карточки**: 8px (p-2)
|
||||
|
||||
**ЦВЕТОВАЯ СХЕМА РЫНКОВ:**
|
||||
|
||||
- **"Садовод"** (sadovod): Зеленый `bg-green-500/20 text-green-300 border-green-500/30`
|
||||
- **"ТЯК Москва"** (tyak-moscow): Синий `bg-blue-500/20 text-blue-300 border-blue-500/30`
|
||||
- **Другие/не указан**: Серый `bg-gray-500/20 text-gray-300 border-gray-500/30`
|
||||
|
||||
**ДОСТУПНОСТЬ:**
|
||||
|
||||
- `role="tablist"` для контейнера
|
||||
- `role="tab"` для карточек
|
||||
- `aria-selected="true/false"` для выбранной карточки
|
||||
- `tabindex="0"` для активной, `-1` для неактивных
|
||||
|
||||
### 2.3 Правила блока "Карточки товаров" (Блок 2)
|
||||
|
||||
**НАЗНАЧЕНИЕ И ЛОГИКА:**
|
||||
|
||||
- **Источник данных**: Товары выбранного поставщика из Блока 1
|
||||
- **Триггер отображения**: Клик на карточку поставщика → загрузка карточек товаров
|
||||
- **Взаимодействие**: Клик на карточку товара → добавление в Блок 3 "Товары поставщика"
|
||||
- **Поведение**: Горизонтальный скролл при множестве товаров
|
||||
|
||||
**АРХИТЕКТУРА И РАЗМЕРЫ:**
|
||||
|
||||
- **Внешний контейнер**: bg-white/10 backdrop-blur-xl border border-white/20 rounded-2xl flex-shrink-0
|
||||
- **Внутренний контейнер скролла**: flex gap-3 overflow-x-auto p-4
|
||||
- **Стилизация скролла**: scrollbarWidth: 'thin' для тонкой полосы прокрутки
|
||||
- **Отступы**: padding: 16px (p-4) внутри, gap: 12px (gap-3) между карточками
|
||||
- **Адаптивная высота**: по содержимому карточек (БЕЗ фиксированной высоты)
|
||||
- **Визуальное единство**: стеклянный эффект как у других блоков системы
|
||||
- **БЕЗ заголовков/иконок**: только чистые карточки товаров в контейнере
|
||||
|
||||
**РАЗМЕРЫ КАРТОЧЕК ТОВАРОВ:**
|
||||
|
||||
- **Компактная карточка**: 80×112px (w-20 h-28), соотношение 5:7
|
||||
- **Адаптивность**: фиксированный размер для всех устройств
|
||||
|
||||
**СОДЕРЖАНИЕ КАРТОЧКИ ТОВАРА:**
|
||||
|
||||
- **ТОЛЬКО изображение товара**: 80×112px, object-cover
|
||||
- **Минималистичный дизайн**: БЕЗ текста, названий, цен, иконок
|
||||
- **Состояния**: Default, Selected, Active (БЕЗ Hover-эффектов)
|
||||
- **Рамка**: border-white/10, при выборе border-white/30
|
||||
- **Фон**: bg-white/5 полупрозрачный
|
||||
|
||||
**ДЕЙСТВИЕ:**
|
||||
Клик на карточку → добавление товара в Блок 3 (детальный каталог)
|
||||
|
||||
### 2.4 ПРАВИЛА КОРЗИНЫ - ЕДИНЫЙ СТАНДАРТ
|
||||
|
||||
**КРИТИЧЕСКИ ВАЖНО**: Все корзины в системе должны следовать единому стандарту дизайна и функциональности.
|
||||
|
||||
#### **2.4.1 Размеры и позиционирование**
|
||||
|
||||
```tsx
|
||||
<div className="w-72 flex-shrink-0">
|
||||
<div className="bg-white/10 backdrop-blur border-white/20 p-3 sticky top-0 rounded-2xl">
|
||||
```
|
||||
|
||||
**ОБЯЗАТЕЛЬНЫЕ ПАРАМЕТРЫ**:
|
||||
|
||||
- **Ширина**: `w-72` (288px) - фиксированная ширина для всех корзин
|
||||
- **Флекс**: `flex-shrink-0` - корзина не сжимается
|
||||
- **Позиция**: `sticky top-0` - прилипает к верху при прокрутке
|
||||
- **Стиль**: Glass morphism эффект с `backdrop-blur` и `bg-white/10`
|
||||
|
||||
#### **2.4.2 Автодобавление товаров**
|
||||
|
||||
**ПРАВИЛО AUTO-ADD**: При вводе количества товар автоматически добавляется в корзину.
|
||||
|
||||
```tsx
|
||||
// ОБЯЗАТЕЛЬНАЯ РЕАЛИЗАЦИЯ:
|
||||
const handleQuantityChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const inputValue = e.target.value
|
||||
const newQuantity = inputValue === '' ? 0 : Math.max(0, parseInt(inputValue) || 0)
|
||||
|
||||
if (newQuantity > 0) {
|
||||
// Автоматически добавляем товар в корзину
|
||||
updateProductQuantity(product.id, newQuantity)
|
||||
} else {
|
||||
// Удаляем товар из корзины при количестве 0
|
||||
removeFromCart(product.id)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**ДЕФОЛТНОЕ ЗНАЧЕНИЕ**: Пустой инпут (`value={''}`) вместо `value={0}`
|
||||
|
||||
#### **2.4.3 Структура корзины**
|
||||
|
||||
**ОБЯЗАТЕЛЬНЫЕ ЭЛЕМЕНТЫ**:
|
||||
|
||||
1. **Заголовок**: "Корзина (X шт)" с иконкой корзины
|
||||
2. **Список товаров**:
|
||||
- Название товара (БЕЗ суффикса "(с рецептурой)")
|
||||
- Цена за единицу × количество
|
||||
- Кнопка удаления (X справа)
|
||||
3. **Мета-информация**: Дата поставки, фулфилмент-центр, логистика
|
||||
4. **Итого**: Общая сумма с выделением зелёным цветом
|
||||
5. **Кнопка действия**: "Создать поставку" с градиентом
|
||||
|
||||
**ЗАПРЕЩЕНО**: Отображать текст "(с рецептурой)" в названиях товаров в корзине
|
||||
|
||||
#### **2.4.4 Единая функция расчета стоимости**
|
||||
|
||||
**КРИТИЧЕСКИ ВАЖНО**: Использовать единую функцию расчета для избежания расхождений:
|
||||
|
||||
```tsx
|
||||
const getProductTotalWithRecipe = (productId: string, quantity: number) => {
|
||||
const product = products.find((p) => p.id === productId)
|
||||
if (!product) return 0
|
||||
|
||||
// Базовая цена товара
|
||||
let total = (product.pricePerUnit || 0) * quantity
|
||||
|
||||
// Добавляем услуги
|
||||
if (product.services && product.services.length > 0) {
|
||||
const servicesTotal = product.services.reduce((sum, service) => {
|
||||
return sum + (service.pricePerUnit || 0) * quantity
|
||||
}, 0)
|
||||
total += servicesTotal
|
||||
}
|
||||
|
||||
// Добавляем FF расходники (используем .price, НЕ .pricePerUnit!)
|
||||
if (product.ffConsumables && product.ffConsumables.length > 0) {
|
||||
const ffConsumablesTotal = product.ffConsumables.reduce((sum, consumable) => {
|
||||
return sum + (consumable.price || 0) * quantity // ВАЖНО: .price!
|
||||
}, 0)
|
||||
total += ffConsumablesTotal
|
||||
}
|
||||
|
||||
// Добавляем расходники продавца
|
||||
if (product.sellerConsumables && product.sellerConsumables.length > 0) {
|
||||
const sellerConsumablesTotal = product.sellerConsumables.reduce((sum, consumable) => {
|
||||
return sum + (consumable.pricePerUnit || 0) * quantity
|
||||
}, 0)
|
||||
total += sellerConsumablesTotal
|
||||
}
|
||||
|
||||
return total
|
||||
}
|
||||
```
|
||||
|
||||
#### **2.4.5 Синхронизация данных между блоками**
|
||||
|
||||
**ПРАВИЛО СИНХРОНИЗАЦИИ**: Данные в корзине должны отражать выборы из всех блоков формы:
|
||||
|
||||
1. **Дата поставки**: Из Блока 3 (дата пикер)
|
||||
2. **Фулфилмент-центр**: Название выбранного FF (реальные данные!)
|
||||
3. **Логистическая компания**: Только партнеры типа `'LOGIST'`
|
||||
|
||||
**ПОРЯДОК ОТОБРАЖЕНИЯ В КОРЗИНЕ**:
|
||||
|
||||
```
|
||||
Дата поставки: 08.08.2025
|
||||
Фулфилмент-центр: ФУЛФИЛМЕНТ РУ
|
||||
Логистическая компания: [Выпадающий список]
|
||||
```
|
||||
|
||||
#### **2.4.6 Критические требования**
|
||||
|
||||
🚨 **БЕЗОПАСНОСТЬ ТИПОВ**:
|
||||
|
||||
- Всегда проверять на `null/undefined`: `selectedSupplier?.id || ''`
|
||||
- Использовать optional chaining для всех вложенных объектов
|
||||
|
||||
🚨 **ПРОИЗВОДИТЕЛЬНОСТЬ**:
|
||||
|
||||
- Мемоизация расчетов: `useMemo` для дорогих вычислений
|
||||
- Debounce для инпутов количества
|
||||
|
||||
🚨 **UX КОНСИСТЕНТНОСТЬ**:
|
||||
|
||||
- Единые стили для всех корзин в системе
|
||||
- Одинаковое поведение auto-add во всех формах
|
||||
- Синхронная валидация данных
|
||||
|
||||
### 2.5 Трёхблочная архитектура страницы "Мои поставки"
|
||||
|
||||
**ПРИНЦИП**: Страница состоит из трёх визуально разделённых блоков
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────┐
|
||||
│ 1. БЛОК ТАБОВ (навигация) │
|
||||
│ - Фиксированная высота │
|
||||
│ - Glass-эффект │
|
||||
│ - Иерархическая структура │
|
||||
├─────────────────────────────────────────┤
|
||||
│ 2. БЛОК СТАТИСТИКИ (метрики) │
|
||||
│ - Контекстные данные │
|
||||
│ - 4 карточки в ряд (desktop) │
|
||||
│ - Динамическое обновление │
|
||||
├─────────────────────────────────────────┤
|
||||
│ 3. ОСНОВНОЙ БЛОК (контент) │
|
||||
│ - Сохраняет весь функционал │
|
||||
│ - Таблицы, фильтры, действия │
|
||||
│ - Высота до низа sidebar │
|
||||
└─────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
**ПРАВИЛО**: Статистика меняется в зависимости от выбранных табов
|
||||
|
||||
**Для путей "Фулфилмент → Товар → Карточки/Поставщики":**
|
||||
|
||||
- Всего поставок
|
||||
- Активных поставок
|
||||
- Сумма активных поставок
|
||||
- В пути
|
||||
|
||||
**ФОРМУЛА РАСЧЕТА ВЫСОТЫ**:
|
||||
|
||||
```css
|
||||
height: calc(100vh - headerHeight - tabsHeight - statsHeight - margins);
|
||||
```
|
||||
|
||||
**ПРАВИЛО ВЫРАВНИВАНИЯ**:
|
||||
|
||||
- Нижняя граница основного блока должна быть на одном уровне с нижней границей sidebar
|
||||
- При изменении размера окна высота пересчитывается
|
||||
- Внутренний скролл: `overflow-y-auto`
|
||||
|
||||
### 2.6 Четырёхблочная архитектура создания поставки расходников
|
||||
|
||||
#### **Структура страницы**:
|
||||
|
||||
**БЛОК 1: ПОСТАВЩИКИ** _(обязательный, 180px)_:
|
||||
|
||||
- Карточки поставщиков из раздела "Партнеры"
|
||||
- Горизонтальный скролл при превышении ширины
|
||||
- Выбор только одного поставщика одновременно
|
||||
|
||||
**БЛОК 2: КАРТОЧКИ ТОВАРОВ** _(адаптивная высота - НОВЫЙ БЛОК)_:
|
||||
|
||||
- ТОЛЬКО минималистичные карточки товаров 80×112px
|
||||
- ТОЛЬКО изображение товара, БЕЗ текста/названий/цен
|
||||
- Горизонтальный скролл при множестве товаров
|
||||
- Клик добавляет товар в блок 3
|
||||
|
||||
**БЛОК 3: ТОВАРЫ ПОСТАВЩИКА** _(flex-1, детальный каталог)_:
|
||||
|
||||
- Детальные карточки выбранных товаров
|
||||
- Управление количеством и параметрами поставки
|
||||
|
||||
**БЛОК 4: КОРЗИНА И НАСТРОЙКИ** _(правая панель, 384px)_:
|
||||
|
||||
- Корзина поставки с выбранными товарами
|
||||
- Настройки поставки (фулфилмент-центр, дата, логистика)
|
||||
- Сортировка: цена, название, категория
|
||||
- Фильтры: категория, ценовой диапазон
|
||||
- Карточка с полем ввода количества и кнопками +/-
|
||||
|
||||
**БЛОК 3: КОРЗИНА** _(правая часть)_:
|
||||
|
||||
- **РАСПОЛОЖЕНИЕ**: Правая часть экрана
|
||||
- **СОДЕРЖАНИЕ**:
|
||||
- Счетчик видов расходников
|
||||
- Детализация по каждому расходнику (название, количество, цена, сумма)
|
||||
- Общая сумма всех расходников
|
||||
- **УПРАВЛЕНИЕ**:
|
||||
- Изменение количества (с валидацией остатков)
|
||||
- Удаление позиций
|
||||
- **ОБЯЗАТЕЛЬНЫЕ ПОЛЯ**:
|
||||
- Выбор фулфилмент-центра (из партнеров)
|
||||
- Дата поставки (не прошедшая, по умолчанию - текущая)
|
||||
|
||||
### 2.7 Многоуровневая таблица поставок
|
||||
|
||||
#### **ПЕРВЫЙ УРОВЕНЬ** _(основной список)_:
|
||||
|
||||
- **СОРТИРОВКА**: Номер поставки от большего к меньшему
|
||||
- **ОБЯЗАТЕЛЬНЫЕ КОЛОНКИ**:
|
||||
- Порядковый номер поставки
|
||||
- Количество видов расходников
|
||||
- Стоимость всей поставки
|
||||
- Количество категорий
|
||||
- Статус поставки
|
||||
|
||||
#### **ВТОРОЙ УРОВЕНЬ** _(детализация по клику)_:
|
||||
|
||||
- **АКТИВАЦИЯ**: По клику на строку первого уровня
|
||||
- **СОДЕРЖАНИЕ**:
|
||||
- Название расходника
|
||||
- Количество
|
||||
- Цена
|
||||
- Категория
|
||||
- Поставщик
|
||||
- **ОГРАНИЧЕНИЯ**: Только просмотр, редактирование запрещено
|
||||
|
||||
## 3. 🛠️ ГРАФИЧЕСКИЙ ИНТЕРФЕЙС И КОМПОНЕНТЫ
|
||||
|
||||
### 3.1 React компоненты селлера
|
||||
|
||||
**Основные компоненты:**
|
||||
- `SellerHomePage` - главная страница селлера (4 типо-зависимых компонента)
|
||||
- `SellerEconomicsPage` - экономическая аналитика селлера
|
||||
|
||||
### 3.2 Роутинг для селлера
|
||||
|
||||
```typescript
|
||||
switch (user?.organization?.type) {
|
||||
case 'SELLER':
|
||||
router.push('/supplies')
|
||||
break
|
||||
}
|
||||
```
|
||||
|
||||
### 3.3 Структура разделов кабинета
|
||||
|
||||
**🛍️ СЕЛЛЕР (`SELLER`):**
|
||||
|
||||
- Мои поставки (`/supplies`) - управление заказами товаров
|
||||
- WB Интеграция (`/wb-integration`) - связь с Wildberries
|
||||
|
||||
## 4. 🛠️ GRAPHQL API
|
||||
|
||||
### 4.1 Основные запросы (Queries)
|
||||
|
||||
#### Получение карточек селлера:
|
||||
|
||||
```graphql
|
||||
query GetSellerCards {
|
||||
myMarketplaceCards {
|
||||
id
|
||||
title
|
||||
marketplace
|
||||
article
|
||||
linkedProductId # null если свободна
|
||||
linkedProduct {
|
||||
# для отображения занятости
|
||||
id
|
||||
name
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
**Последнее обновление**: Август 2025
|
||||
**Связанные файлы**:
|
||||
|
||||
- [rules-complete.md](./rules-complete.md) - Общие бизнес-правила
|
||||
- [visual-design-rules.md](./visual-design-rules.md) - Визуальные правила
|
Reference in New Issue
Block a user