Files
portal/app/resources/js/views/HelpView.vue
T
Дмитрий 9ff19f3cc3 @
feat(bot): всеобъемлющая инструкция (36 статей) + 31 узкая экскурсия, которая открывает окна

Живой урок владельца 12.07.2026: «дубли чистятся в 2-х местах, а бот выдумал третье»,
«стыдно такого бота показывать», «видео показываем полное, а я спрашиваю про одно —
надо резать», «надо не только кнопку показывать, а и что за ней».

База знаний: 17 обзорных статей → 36 узких по темам. Портал обойдён целиком
(5 агентов): все экраны, окна, подсказки «(?)», тексты ошибок, внутренние правила.
Новое, чего бот не знал: дубли в ДВУХ местах (Поле и Предложения) + окно «источник
уже есть»; правило 18:00; почему приходит меньше лимита; оплата по счёту и акты;
списания; безопасность и 2FA; отчёты; массовые действия; регистрация.

Экскурсии: 15 → 31, каждая на свою тему (3–7 шагов). Раннер GuidedTour научен
шагу `open` — сам открывает окно/вкладку и подсвечивает то, что ВНУТРИ
(окно дублей, форму подбора, диалог пополнения, панель тарифов, карточку сделки).
99 новых якорей data-tour расставлено по всему порталу (4 агента).

Поиск: веса в tsvector (заголовок A / синонимы B / текст C) — иначе на «чистка
дублей» первой всплывала статья «Списания» из-за мимоходного упоминания «поля».

Тесты: Pest бот 39/39, Vitest 1222/1222. Новые сторожа: у каждой статьи есть
экскурсия; экскурсия не короче 3 и не длиннее 7 шагов; каждая цель экскурсии
реально существует в разметке; экскурсии про действия обязаны открывать окно.
Живая проверка в браузере: экскурсия про дубли сама открыла окно объединения
и перешла на второе место чистки.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@
2026-07-12 12:56:22 +03:00

121 lines
7.8 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script setup lang="ts">
import { ref } from 'vue';
import { useAuthStore } from '../stores/auth';
import { submitSupportRequest } from '../api/support';
const auth = useAuthStore();
// FAQ (Фаза 2, UX-аудит 25.06): самопомощь до обращения в поддержку. Простым языком.
// Тексты — статически в коде (YAGNI: CMS не нужна), правятся обычным деплоем.
const faqItems: { q: string; a: string }[] = [
{ q: 'Что такое «заявка» (лид)?', a: 'Заявка — это контакт человека, который интересуется услугой в вашей нише. Мы находим таких людей и передаём их вам. За каждую переданную заявку списывается фиксированная цена.' },
{ q: 'Сколько стоит одна заявка?', a: 'Цена зависит от объёма: чем больше заявок в месяц, тем дешевле каждая (7 ступеней). Текущую цену видно на странице «Биллинг» в блоке «Цены за лид».' },
{ q: 'Как начать получать заявки?', a: 'Три шага: 1) пополните баланс; 2) создайте проект — укажите, по какому сайту/телефону собирать клиентов и сколько заявок в день нужно; 3) готово, проект встаёт в сбор и заявки идут со следующего дня.' },
{ q: 'Как пополнить баланс?', a: '«Биллинг» → «Пополнить» → выберите сумму (минимум 100 ₽). Из баланса списывается оплата за заявки.' },
{ q: 'Почему с меня списали деньги?', a: 'За каждую переданную заявку списывается её цена. Все списания видно в «Биллинг» → «Списания»: дата, заявка, тариф, сумма. Скрытых комиссий нет.' },
{ q: 'Что значит «хватит на N дней»?', a: 'Это прогноз: на сколько дней хватит баланса при дневном заказе ваших проектов. Если активных проектов нет — прогноз не считается.' },
{ q: 'Почему приём заявок «на паузе»?', a: 'Чаще всего закончился баланс — приём приостанавливается, чтобы вы не ушли в минус. Пополните баланс — приём включится автоматически.' },
{ q: 'Что такое «лимит заявок в день»?', a: 'Сколько заявок в день вы готовы принимать и оплачивать по проекту. Лимит можно менять в любой момент.' },
{ q: 'Чем «Список» отличается от «Доски» (Канбан)?', a: 'Это одни и те же ваши заявки в разном виде: «Список» — таблицей, «Доска» — карточками по статусам. Выбирайте, как удобнее.' },
{ q: 'Можно ли оплатить картой онлайн?', a: 'Онлайн-оплата картой подключается в ближайшее время. Пока по вопросам оплаты пишите в поддержку.' },
];
const supportEmail =
document.querySelector('meta[name="support-email"]')?.getAttribute('content') ?? 'support@liderra.ru';
const name = ref(
[auth.user?.first_name, auth.user?.last_name].filter(Boolean).join(' ') || '',
);
const contact = ref(auth.user?.email ?? '');
const message = ref('');
const loading = ref(false);
const sent = ref(false);
const errorMsg = ref('');
const fieldErrors = ref<Record<string, string[]>>({});
async function submit() {
errorMsg.value = '';
fieldErrors.value = {};
if (!name.value.trim() || !contact.value.trim() || !message.value.trim()) {
errorMsg.value = 'Заполните все поля.';
return;
}
loading.value = true;
try {
await submitSupportRequest({ name: name.value, contact: contact.value, message: message.value });
sent.value = true;
message.value = '';
} catch (e: unknown) {
const err = e as { response?: { status?: number; data?: { errors?: Record<string, string[]> } } };
if (err.response?.status === 422 && err.response.data?.errors) {
fieldErrors.value = err.response.data.errors;
} else {
errorMsg.value = 'Не удалось отправить. Попробуйте ещё раз или напишите на почту.';
}
} finally {
loading.value = false;
}
}
</script>
<template>
<div class="help-view pa-6" data-testid="help-view">
<h1 class="text-h5 mb-1">Помощь</h1>
<p class="text-body-2 text-medium-emphasis mb-6">
Напишите нам ответим на ваш контакт. Можно по почте, через форму ниже или в чат справа.
</p>
<v-card variant="outlined" class="pa-5 mb-4" max-width="640" data-testid="faq-card" data-tour="help-faq">
<h3 class="text-subtitle-2 mb-3">Частые вопросы</h3>
<v-expansion-panels variant="accordion" data-testid="faq-panels">
<v-expansion-panel v-for="(item, i) in faqItems" :key="i" data-tour="help-faq-item">
<v-expansion-panel-title>{{ item.q }}</v-expansion-panel-title>
<v-expansion-panel-text>{{ item.a }}</v-expansion-panel-text>
</v-expansion-panel>
</v-expansion-panels>
</v-card>
<v-card variant="outlined" class="pa-5 mb-4" max-width="640" data-tour="help-email">
<h3 class="text-subtitle-2 mb-2">Почта техподдержки</h3>
<a :href="`mailto:${supportEmail}`" class="text-primary" data-testid="support-email">{{ supportEmail }}</a>
</v-card>
<v-card variant="outlined" class="pa-5" max-width="640" data-tour="help-form">
<h3 class="text-subtitle-2 mb-3">Оставить заявку</h3>
<v-alert v-if="sent" type="success" variant="tonal" class="mb-4" data-testid="support-sent">
Заявка отправлена. Мы свяжемся с вами по указанному контакту.
</v-alert>
<v-alert v-if="errorMsg" type="error" variant="tonal" class="mb-4">{{ errorMsg }}</v-alert>
<v-text-field
v-model="name"
label="Имя"
:error-messages="fieldErrors.name"
density="comfortable"
class="mb-2"
data-testid="support-name"
/>
<v-text-field
v-model="contact"
label="Контакт (телефон или email)"
:error-messages="fieldErrors.contact"
density="comfortable"
class="mb-2"
data-testid="support-contact"
/>
<v-textarea
v-model="message"
label="Сообщение"
:error-messages="fieldErrors.message"
rows="4"
density="comfortable"
class="mb-3"
data-testid="support-message"
/>
<v-btn color="primary" :loading="loading" data-testid="support-submit" @click="submit">Отправить</v-btn>
</v-card>
</div>
</template>