feat: добавлена функциональность политики конфиденциальности и согласия на обработку ПД

Основные изменения:

Backend:
- Добавлена миграция для поля privacy_consented_at в таблицу telecart_customers
- Создан PrivacyPolicyHandler с методами:
  * checkIsUserPrivacyConsented - проверка наличия согласия пользователя
  * userPrivacyConsent - сохранение согласия пользователя
- Обновлен TelegramService для извлечения userId из initData
- Обновлен TelegramServiceProvider для внедрения зависимостей
- Добавлены новые маршруты в routes.php
- Обновлен SettingsHandler для возврата privacy_policy_link
- Обновлен TelegramCustomersHandler для включения privacy_consented_at в ответы
- Обновлены тесты TelegramServiceTest

Frontend (SPA):
- Создан компонент PrivacyPolicy.vue для отображения запроса согласия
- Добавлена проверка согласия при инициализации приложения (main.js)
- Обновлен App.vue для отображения компонента PrivacyPolicy
- Добавлены функции checkIsUserPrivacyConsented и userPrivacyConsent в ftch.js
- Обновлен SettingsStore для хранения privacy_policy_link и is_privacy_consented

Frontend (Admin):
- Добавлено поле privacy_policy_link в настройки (settings.js)
- Добавлена настройка ссылки на политику конфиденциальности в GeneralView.vue
- Обновлен CustomersView.vue:
  * Добавлена колонка privacy_consented_at с отображением даты согласия
  * Добавлена поддержка help-текста для колонок с иконкой вопроса и tooltip
  * Добавлены help-тексты для колонок last_seen_at, privacy_consented_at, created_at
  * Улучшено форматирование кода
This commit is contained in:
2025-11-23 23:17:21 +03:00
committed by Nikita Kiselev
parent 9a93cc7342
commit 7a5eebec91
16 changed files with 378 additions and 52 deletions

View File

@@ -17,6 +17,8 @@
</KeepAlive>
</RouterView>
<PrivacyPolicy v-if="! settings.is_privacy_consented"/>
<CartButton v-if="settings.store_enabled"/>
<Dock v-if="isAppDockShown"/>
</section>
@@ -42,8 +44,8 @@ import {useSettingsStore} from "@/stores/SettingsStore.js";
import {useProductFiltersStore} from "@/stores/ProductFiltersStore.js";
import CartButton from "@/components/CartButton.vue";
import Dock from "@/components/Dock.vue";
import Navbar from "@/components/Navbar.vue";
import AppDebugMessage from "@/components/AppDebugMessage.vue";
import PrivacyPolicy from "@/components/PrivacyPolicy.vue";
const tg = useMiniApp();
const platform = ref();

View File

@@ -0,0 +1,42 @@
<template>
<div v-if="isShown" class="toast toast-center bottom-20 z-50">
<div class="alert alert-info">
<span>
Используя магазин, вы соглашаетесь с
<a v-if="settings.privacy_policy_link"
href="#" class="underline"
@click.prevent="showPrivacyPolicy"
>обработкой персональных данных</a>
<span v-else>обработкой персональных данных</span>.
</span>
<button
class="btn btn-outline"
@click="privacyConsent"
>
OK
</button>
</div>
</div>
</template>
<script setup>
import {userPrivacyConsent} from "@/utils/ftch.js";
import {ref} from "vue";
import {useSettingsStore} from "@/stores/SettingsStore.js";
const isShown = ref(true);
const settings = useSettingsStore();
async function privacyConsent() {
isShown.value = false;
await userPrivacyConsent();
}
function showPrivacyPolicy() {
if (settings.privacy_policy_link) {
window.Telegram.WebApp.openLink(settings.privacy_policy_link, {
try_instant_view: true,
});
}
}
</script>

View File

@@ -8,7 +8,7 @@ import {useSettingsStore} from "@/stores/SettingsStore.js";
import ApplicationError from "@/ApplicationError.vue";
import AppMetaInitializer from "@/utils/AppMetaInitializer.ts";
import {injectYaMetrika} from "@/utils/yaMetrika.js";
import {saveTelegramCustomer} from "@/utils/ftch.js";
import {checkIsUserPrivacyConsented, saveTelegramCustomer} from "@/utils/ftch.js";
import {register} from 'swiper/element/bundle';
import 'swiper/element/bundle';
@@ -59,6 +59,22 @@ settings.load()
}
}
})
.then(() => {
(async () => {
try {
const response = await checkIsUserPrivacyConsented();
settings.is_privacy_consented = response?.data?.is_privacy_consented;
if (settings.is_privacy_consented) {
console.info('[Init] Privacy Policy consent given by user.');
} else {
console.info('[Init] Privacy Policy consent NOT given by user.');
}
} catch (error) {
console.error('[Init] Failed to check Telegram user consent.');
settings.is_privacy_consented = false;
}
})();
})
.then(() => blocks.processBlocks(settings.mainpage_blocks))
.then(async () => {
console.debug('Load default filters for the main page');

View File

@@ -29,6 +29,8 @@ export const useSettingsStore = defineStore('settings', {
text_order_created_success: 'Заказ успешно оформлен.',
},
mainpage_blocks: [],
is_privacy_consented: false,
privacy_policy_link: false,
}),
actions: {
@@ -53,6 +55,7 @@ export const useSettingsStore = defineStore('settings', {
this.currency_code = settings.currency_code;
this.texts = settings.texts;
this.mainpage_blocks = settings.mainpage_blocks;
this.privacy_policy_link = settings.privacy_policy_link;
}
}
});

View File

@@ -107,4 +107,12 @@ export async function saveTelegramCustomer(userData) {
});
}
export async function checkIsUserPrivacyConsented() {
return await ftch('checkIsUserPrivacyConsented');
}
export async function userPrivacyConsent() {
return await ftch('userPrivacyConsent');
}
export default ftch;