1a0c9f5c8d
UI-аудит раунд 2 (Playwright, протыкивание форм): - vuetify.ts: +13 mdi→Lucide маппингов — bulk-бар проектов / импорт / экспорт отчётов и сделок / помощь / действия админки больше не падают в HelpCircle-fallback «?» - config/services.php + ErrorMeta/ErrorView/HelpView: support@liderra.app → support@liderra.ru (домен продукта .ru); status.liderra.app → status.liderra.ru - dealsApiMapper: ветка deal.commented — текст комментария в активности без служебного ключа «text:» - KanbanCard: costKopecks null-aware — «—» вместо врущего «0 ₽» (как в drawer) - DealsView: подзаголовок «crm.bp» → «crm.bp-gr.ru» (как в импорте/админке) Верификация: type-check ✓, build ✓, переоткрыто в Playwright локально (иконки/почта/комментарий/карточка/подзаголовок). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
182 lines
6.6 KiB
Vue
182 lines
6.6 KiB
Vue
<script setup lang="ts">
|
||
/**
|
||
* Универсальный экран ошибки для 404 / 403 / 500. Конфигурация через
|
||
* `route.meta.errorCode` ('404' | '403' | '500').
|
||
*
|
||
* Источник дизайна: liderra_v8_handoff/concepts/v8_errors.html.
|
||
*
|
||
* Layout: meta.layout='error' — AppShell рендерит ErrorView напрямую без
|
||
* AppLayout/AuthLayout (full-bleed теало-нуар bg + центрированный card).
|
||
*
|
||
* Sprint 4 Phase B/3 — split на ErrorBrand + ErrorIllustration + ErrorActions
|
||
* + ErrorMeta (audit O-refactor-04 закрытие). State (config, errorCode) — в parent
|
||
* ради единого route.meta-driven flow.
|
||
*
|
||
* Не входит в этот коммит:
|
||
* - Реальный requestId/incidentId из backend (сейчас mock).
|
||
* - Status-pills из API /api/health (сейчас static на 500).
|
||
* - Sentry capture при mount 500 (для production).
|
||
*/
|
||
import { computed } from 'vue';
|
||
import { useRoute, useRouter } from 'vue-router';
|
||
import ErrorBrand from '../../components/errors/ErrorBrand.vue';
|
||
import ErrorIllustration from '../../components/errors/ErrorIllustration.vue';
|
||
import ErrorActions from '../../components/errors/ErrorActions.vue';
|
||
import ErrorMeta from '../../components/errors/ErrorMeta.vue';
|
||
import DevIndexBadge from '../../components/DevIndexBadge.vue';
|
||
|
||
interface ErrorAction {
|
||
label: string;
|
||
icon: string;
|
||
to?: string;
|
||
href?: string;
|
||
onClick?: () => void;
|
||
}
|
||
|
||
interface ErrorConfig {
|
||
code: '404' | '403' | '500';
|
||
title: string;
|
||
description: string;
|
||
primaryAction: ErrorAction;
|
||
secondaryAction?: ErrorAction;
|
||
showRequestId?: boolean;
|
||
requestIdLabel?: string;
|
||
requestId?: string;
|
||
showStatusList?: boolean;
|
||
}
|
||
|
||
const route = useRoute();
|
||
const router = useRouter();
|
||
|
||
const errorCode = computed<'404' | '403' | '500'>(
|
||
() => (route.meta.errorCode as '404' | '403' | '500' | undefined) ?? '404',
|
||
);
|
||
|
||
const config = computed<ErrorConfig>(() => {
|
||
if (errorCode.value === '403') {
|
||
return {
|
||
code: '403',
|
||
title: 'У вас нет доступа',
|
||
description:
|
||
'Эта страница принадлежит другому тенанту, либо ваша роль не позволяет её увидеть. Если вы считаете, что это ошибка — обратитесь к администратору вашей команды или в поддержку.',
|
||
primaryAction: {
|
||
label: 'На дашборд',
|
||
icon: 'mdi-view-dashboard-outline',
|
||
to: '/dashboard',
|
||
},
|
||
secondaryAction: {
|
||
label: 'Написать в поддержку',
|
||
icon: 'mdi-email-outline',
|
||
href: 'mailto:support@liderra.ru',
|
||
},
|
||
// Реальный request-id пока не проводится с бэкенда → блок скрыт
|
||
// (раньше показывался хардкод «REQ-3F8A2-0007» как настоящий).
|
||
showRequestId: false,
|
||
};
|
||
}
|
||
if (errorCode.value === '500') {
|
||
return {
|
||
code: '500',
|
||
title: 'Что-то пошло не так',
|
||
description:
|
||
'Внутренняя ошибка — мы уже занимаемся. Команда получила уведомление. Большинство сбоев чинятся за 5–10 минут. Можно вернуться через минуту, или открыть страницу статуса.',
|
||
primaryAction: {
|
||
label: 'Попробовать снова',
|
||
icon: 'mdi-refresh',
|
||
onClick: () => location.reload(),
|
||
},
|
||
secondaryAction: {
|
||
label: 'Статус сервиса',
|
||
icon: 'mdi-pulse',
|
||
href: 'https://status.liderra.ru',
|
||
},
|
||
// Реальный incident-id и статус сервисов пока не проводятся с бэкенда →
|
||
// блоки скрыты (раньше — хардкод «INC-2026-0507-0034» + фейк-список
|
||
// статусов с Telegram/YooKassa, которых нет в стеке).
|
||
showRequestId: false,
|
||
showStatusList: false,
|
||
};
|
||
}
|
||
// default 404
|
||
return {
|
||
code: '404',
|
||
title: 'Страница не найдена',
|
||
description:
|
||
'Похоже, такой страницы нет — её удалили, переименовали или вы ввели адрес с опечаткой. Все рабочие экраны Лидерра доступны через дашборд.',
|
||
primaryAction: {
|
||
label: 'На дашборд',
|
||
icon: 'mdi-view-dashboard-outline',
|
||
to: '/dashboard',
|
||
},
|
||
secondaryAction: {
|
||
label: 'Назад',
|
||
icon: 'mdi-arrow-left',
|
||
onClick: () => router.back(),
|
||
},
|
||
};
|
||
});
|
||
</script>
|
||
|
||
<template>
|
||
<v-app>
|
||
<v-main class="error-main">
|
||
<ErrorBrand />
|
||
|
||
<div class="error-content">
|
||
<ErrorIllustration :code="config.code" />
|
||
<h2 class="err-title">{{ config.title }}</h2>
|
||
<p class="err-desc">{{ config.description }}</p>
|
||
|
||
<ErrorActions :primary="config.primaryAction" :secondary="config.secondaryAction" />
|
||
|
||
<ErrorMeta
|
||
:code="config.code"
|
||
:show-status-list="config.showStatusList"
|
||
:show-request-id="config.showRequestId"
|
||
:request-id-label="config.requestIdLabel"
|
||
:request-id="config.requestId"
|
||
/>
|
||
</div>
|
||
</v-main>
|
||
<DevIndexBadge :index="route.meta.devIndex" :label="route.meta.devLabel" />
|
||
</v-app>
|
||
</template>
|
||
|
||
<style scoped>
|
||
.error-main {
|
||
background: #012019;
|
||
color: #fff;
|
||
min-height: 100vh;
|
||
display: flex;
|
||
flex-direction: column;
|
||
}
|
||
|
||
.error-content {
|
||
flex: 1;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
padding: 24px 32px 80px;
|
||
text-align: center;
|
||
max-width: 560px;
|
||
margin: 0 auto;
|
||
}
|
||
|
||
.err-title {
|
||
font-size: 28px;
|
||
font-weight: 600;
|
||
font-variation-settings: 'opsz' 28;
|
||
letter-spacing: -0.018em;
|
||
margin: 0 0 12px;
|
||
color: #fff;
|
||
}
|
||
|
||
.err-desc {
|
||
color: #b1c2bd;
|
||
line-height: 1.55;
|
||
font-size: 15px;
|
||
margin: 0 0 24px;
|
||
}
|
||
</style>
|