From 6d943db9366cf9aa2b2cebd718fa841ee950ed04 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=94=D0=BC=D0=B8=D1=82=D1=80=D0=B8=D0=B9?= Date: Mon, 22 Jun 2026 19:19:58 +0300 Subject: [PATCH] =?UTF-8?q?feat(support):=20=D0=B2=D0=B8=D0=B4=D0=B6=D0=B5?= =?UTF-8?q?=D1=82=20=D0=BE=D0=BD=D0=BB=D0=B0=D0=B9=D0=BD-=D1=87=D0=B0?= =?UTF-8?q?=D1=82=D0=B0=20JivoSite=20(=D1=81=D0=BF=D0=B8=D1=82=20=D0=B1?= =?UTF-8?q?=D0=B5=D0=B7=20=D0=BA=D0=BB=D1=8E=D1=87=D0=B0)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - JivoWidget.vue: подгружает скрипт Jivo при заданном VITE_JIVO_WIDGET_ID (по образцу SmartCaptchaWidget); ключ пуст → ничего не грузит, виджет спит. - Смонтирован в AppLayout (клиентский портал). +VITE_JIVO_WIDGET_ID в vite-env.d.ts. - TDD: JivoWidget.spec (грузит при ключе / спит без) GREEN; AppLayout.spec 16/16; eslint 0. - Активируется вставкой VITE_JIVO_WIDGET_ID (твой ID канала Jivo). На прод не выкачено. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../js/components/support/JivoWidget.vue | 30 +++++++++++++++++++ app/resources/js/layouts/AppLayout.vue | 2 ++ app/resources/js/vite-env.d.ts | 1 + app/tests/Frontend/JivoWidget.spec.ts | 27 +++++++++++++++++ 4 files changed, 60 insertions(+) create mode 100644 app/resources/js/components/support/JivoWidget.vue create mode 100644 app/tests/Frontend/JivoWidget.spec.ts diff --git a/app/resources/js/components/support/JivoWidget.vue b/app/resources/js/components/support/JivoWidget.vue new file mode 100644 index 00000000..6f558ed0 --- /dev/null +++ b/app/resources/js/components/support/JivoWidget.vue @@ -0,0 +1,30 @@ + + + diff --git a/app/resources/js/layouts/AppLayout.vue b/app/resources/js/layouts/AppLayout.vue index 4d1e8fd9..e25064a6 100644 --- a/app/resources/js/layouts/AppLayout.vue +++ b/app/resources/js/layouts/AppLayout.vue @@ -20,6 +20,7 @@ import AppSidebar from '../components/layout/AppSidebar.vue'; import AppTopbar from '../components/layout/AppTopbar.vue'; import DevIndexBadge from '../components/DevIndexBadge.vue'; import CommandPalette from '../components/layout/CommandPalette.vue'; +import JivoWidget from '../components/support/JivoWidget.vue'; import BalanceFrozenBanner from '../components/billing/BalanceFrozenBanner.vue'; import ImpersonationSessionBanner from '../components/admin/ImpersonationSessionBanner.vue'; @@ -88,6 +89,7 @@ usePolling(loadBalanceStatus, { intervalMs: POLLING_REMINDERS_INTERVAL_MS, enabl + diff --git a/app/resources/js/vite-env.d.ts b/app/resources/js/vite-env.d.ts index 53911541..68901b6d 100644 --- a/app/resources/js/vite-env.d.ts +++ b/app/resources/js/vite-env.d.ts @@ -2,6 +2,7 @@ interface ImportMetaEnv { readonly VITE_YANDEX_SMARTCAPTCHA_SITEKEY?: string; + readonly VITE_JIVO_WIDGET_ID?: string; } interface ImportMeta { diff --git a/app/tests/Frontend/JivoWidget.spec.ts b/app/tests/Frontend/JivoWidget.spec.ts new file mode 100644 index 00000000..98a7a6f7 --- /dev/null +++ b/app/tests/Frontend/JivoWidget.spec.ts @@ -0,0 +1,27 @@ +import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; +import { mount } from '@vue/test-utils'; +import JivoWidget from '../../resources/js/components/support/JivoWidget.vue'; + +beforeEach(() => { + document.querySelectorAll('#jivosite-widget-js').forEach((n) => n.remove()); +}); + +afterEach(() => { + vi.unstubAllEnvs(); +}); + +describe('JivoWidget', () => { + it('подгружает скрипт Jivo, когда задан VITE_JIVO_WIDGET_ID', () => { + vi.stubEnv('VITE_JIVO_WIDGET_ID', 'abc123'); + mount(JivoWidget); + const script = document.getElementById('jivosite-widget-js') as HTMLScriptElement | null; + expect(script).not.toBeNull(); + expect(script?.src).toContain('code.jivosite.com/widget/abc123'); + }); + + it('ничего не грузит, когда ключ пуст (виджет спит)', () => { + vi.stubEnv('VITE_JIVO_WIDGET_ID', ''); + mount(JivoWidget); + expect(document.getElementById('jivosite-widget-js')).toBeNull(); + }); +});