Reflex/src/views/DeviceBlockedPage.vue

666 lines
16 KiB
Vue
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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.

<template>
<div class="device-blocked-page">
<div class="blocked-container">
<div class="blocked-icon">
<svg viewBox="0 0 24 24" width="80" height="80" fill="currentColor">
<path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zM13 17h-2v-6h2v6zm0-8h-2V7h2v2z"/>
</svg>
</div>
<div class="blocked-content">
<h1>Устройство заблокировано</h1>
<div class="block-info">
<p class="main-message">
Ваше устройство было заблокировано администрацией сервиса.
</p>
<div v-if="blockInfo" class="block-details">
<div class="detail-item">
<strong>Причина блокировки:</strong>
<span>{{ blockInfo.reason || 'Нарушение правил сервиса' }}</span>
</div>
<div v-if="blockInfo.blockedAt" class="detail-item">
<strong>Дата блокировки:</strong>
<span>{{ formatDate(blockInfo.blockedAt) }}</span>
</div>
<div v-if="blockInfo.deviceId" class="detail-item">
<strong>ID устройства:</strong>
<code>{{ blockInfo.deviceId.slice(-8) }}</code>
</div>
</div>
</div>
<div class="help-section">
<h3>Что это означает?</h3>
<ul>
<li>Ваше устройство заблокировано для использования сервиса</li>
<li>Доступ к функциям приложения ограничен</li>
<li>Блокировка может быть связана с нарушением правил</li>
</ul>
</div>
<div class="contact-section">
<h3>Как обжаловать блокировку?</h3>
<p>
Если вы считаете, что блокировка была применена ошибочно,
вы можете обратиться в службу поддержки:
</p>
<div class="contact-methods">
<div class="contact-item">
<strong>Email:</strong>
<a href="mailto:support@datingapp.com">support@datingapp.com</a>
</div>
<div class="contact-item">
<strong>Telegram:</strong>
<a href="https://t.me/datingapp_support" target="_blank">@datingapp_support</a>
</div>
</div>
<div class="appeal-form">
<h4>Отправить обращение</h4>
<form @submit.prevent="submitAppeal" class="appeal-form-content">
<div class="form-group">
<label for="email">Ваш email:</label>
<input
type="email"
id="email"
v-model="appealForm.email"
required
placeholder="your@email.com"
>
</div>
<div class="form-group">
<label for="message">Сообщение:</label>
<textarea
id="message"
v-model="appealForm.message"
required
placeholder="Опишите ситуацию и почему считаете блокировку ошибочной..."
rows="4"
></textarea>
</div>
<button
type="submit"
:disabled="appealLoading"
class="btn btn-primary"
>
{{ appealLoading ? 'Отправка...' : 'Отправить обращение' }}
</button>
</form>
<div v-if="appealSent" class="appeal-success">
<p> Ваше обращение отправлено! Мы рассмотрим его в ближайшее время.</p>
</div>
</div>
</div>
<div class="additional-info">
<h3>Дополнительная информация</h3>
<div class="info-grid">
<div class="info-card">
<h4>Правила сервиса</h4>
<p>Ознакомьтесь с правилами использования нашего сервиса</p>
<a href="/terms" class="link">Читать правила</a>
</div>
<div class="info-card">
<h4>Безопасность</h4>
<p>Узнайте больше о мерах безопасности</p>
<a href="/security" class="link">О безопасности</a>
</div>
</div>
</div>
<div class="bypass-warning">
<div class="warning-icon"></div>
<div class="warning-text">
<strong>Внимание!</strong>
Попытки обойти блокировку устройства могут привести к
дополнительным ограничениям и продлению срока блокировки.
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import { ref, reactive, onMounted } from 'vue';
import deviceSecurityService from '@/services/deviceSecurityService';
export default {
name: 'DeviceBlockedPage',
setup() {
const blockInfo = ref(null);
const appealSent = ref(false);
const appealLoading = ref(false);
const appealForm = reactive({
email: '',
message: ''
});
// Загрузка информации о блокировке
const loadBlockInfo = () => {
try {
// Получаем информацию о блокировке из deviceSecurityService
if (deviceSecurityService && typeof deviceSecurityService.getBlockInfo === 'function') {
const info = deviceSecurityService.getBlockInfo();
blockInfo.value = info;
console.log('[DeviceBlockedPage] Информация о блокировке:', info);
}
// Также проверяем localStorage
const storedBlockInfo = localStorage.getItem('deviceBlockInfo');
if (storedBlockInfo) {
try {
const parsedInfo = JSON.parse(storedBlockInfo);
if (!blockInfo.value) {
blockInfo.value = parsedInfo;
}
} catch (e) {
console.error('[DeviceBlockedPage] Ошибка парсинга данных блокировки:', e);
}
}
// Если нет информации, создаем базовую
if (!blockInfo.value) {
blockInfo.value = {
reason: 'Нарушение правил сервиса',
blockedAt: new Date().toISOString(),
deviceId: 'unknown'
};
}
} catch (error) {
console.error('[DeviceBlockedPage] Ошибка загрузки информации о блокировке:', error);
}
};
// Отправка обращения
const submitAppeal = async () => {
if (!appealForm.email || !appealForm.message) {
alert('Пожалуйста, заполните все поля');
return;
}
appealLoading.value = true;
try {
// Здесь можно отправить обращение на email или в систему
// Пока что симулируем отправку
console.log('[DeviceBlockedPage] Отправка обращения:', appealForm);
// Симуляция отправки
await new Promise(resolve => setTimeout(resolve, 2000));
// Сохраняем информацию о том, что обращение отправлено
localStorage.setItem('appealSent', JSON.stringify({
email: appealForm.email,
message: appealForm.message,
sentAt: new Date().toISOString(),
deviceId: blockInfo.value?.deviceId
}));
appealSent.value = true;
// Очищаем форму
appealForm.email = '';
appealForm.message = '';
} catch (error) {
console.error('[DeviceBlockedPage] Ошибка отправки обращения:', error);
alert('Ошибка при отправке обращения. Попробуйте позже.');
} finally {
appealLoading.value = false;
}
};
// Форматирование даты
const formatDate = (dateString) => {
if (!dateString) return 'Неизвестно';
try {
return new Date(dateString).toLocaleString('ru-RU', {
year: 'numeric',
month: 'long',
day: 'numeric',
hour: '2-digit',
minute: '2-digit'
});
} catch (err) {
return 'Неверная дата';
}
};
// Проверка, было ли уже отправлено обращение
const checkAppealStatus = () => {
try {
const storedAppeal = localStorage.getItem('appealSent');
if (storedAppeal) {
const appealData = JSON.parse(storedAppeal);
// Проверяем, было ли обращение отправлено в последние 24 часа
const sentAt = new Date(appealData.sentAt);
const now = new Date();
const hoursDiff = (now - sentAt) / (1000 * 60 * 60);
if (hoursDiff < 24) {
appealSent.value = true;
appealForm.email = appealData.email;
}
}
} catch (error) {
console.error('[DeviceBlockedPage] Ошибка проверки статуса обращения:', error);
}
};
onMounted(() => {
loadBlockInfo();
checkAppealStatus();
// Отправляем событие аналитики
if (typeof gtag !== 'undefined') {
gtag('event', 'device_blocked_page_view', {
'event_category': 'security',
'event_label': 'device_blocked'
});
}
});
return {
blockInfo,
appealForm,
appealSent,
appealLoading,
submitAppeal,
formatDate
};
}
};
</script>
<style scoped>
.device-blocked-page {
min-height: 100vh;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
display: flex;
justify-content: center;
align-items: center;
padding: 20px;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
}
.blocked-container {
background: white;
border-radius: 16px;
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1);
max-width: 700px;
width: 100%;
overflow: hidden;
}
.blocked-icon {
text-align: center;
padding: 40px 20px 20px;
background: linear-gradient(135deg, #ff6b6b, #ee5a52);
color: white;
}
.blocked-content {
padding: 30px;
}
.blocked-content h1 {
text-align: center;
color: #333;
margin: 0 0 25px 0;
font-size: 28px;
font-weight: 700;
}
.block-info {
background: #f8f9fa;
border-radius: 12px;
padding: 20px;
margin-bottom: 30px;
border-left: 4px solid #dc3545;
}
.main-message {
font-size: 16px;
color: #555;
margin: 0 0 20px 0;
line-height: 1.6;
}
.block-details {
border-top: 1px solid #dee2e6;
padding-top: 15px;
}
.detail-item {
display: flex;
margin-bottom: 10px;
flex-wrap: wrap;
gap: 10px;
}
.detail-item strong {
color: #333;
min-width: 140px;
}
.detail-item code {
background: #e9ecef;
padding: 2px 6px;
border-radius: 4px;
font-family: monospace;
font-size: 12px;
}
.help-section {
margin-bottom: 30px;
}
.help-section h3 {
color: #333;
margin: 0 0 15px 0;
font-size: 20px;
}
.help-section ul {
list-style: none;
padding: 0;
margin: 0;
}
.help-section li {
padding: 8px 0;
color: #555;
position: relative;
padding-left: 20px;
}
.help-section li::before {
content: '•';
color: #dc3545;
font-size: 18px;
position: absolute;
left: 0;
}
.contact-section {
background: #f8f9fa;
border-radius: 12px;
padding: 25px;
margin-bottom: 30px;
}
.contact-section h3 {
color: #333;
margin: 0 0 15px 0;
font-size: 20px;
}
.contact-methods {
margin: 20px 0;
}
.contact-item {
display: flex;
align-items: center;
margin-bottom: 10px;
gap: 10px;
flex-wrap: wrap;
}
.contact-item strong {
color: #333;
min-width: 80px;
}
.contact-item a {
color: #007bff;
text-decoration: none;
}
.contact-item a:hover {
text-decoration: underline;
}
.appeal-form {
margin-top: 25px;
border-top: 1px solid #dee2e6;
padding-top: 20px;
}
.appeal-form h4 {
color: #333;
margin: 0 0 20px 0;
font-size: 18px;
}
.appeal-form-content {
max-width: 100%;
}
.form-group {
margin-bottom: 20px;
}
.form-group label {
display: block;
margin-bottom: 8px;
font-weight: 600;
color: #333;
}
.form-group input,
.form-group textarea {
width: 100%;
padding: 12px;
border: 1px solid #ddd;
border-radius: 8px;
font-size: 14px;
transition: border-color 0.3s;
box-sizing: border-box;
}
.form-group input:focus,
.form-group textarea:focus {
outline: none;
border-color: #007bff;
box-shadow: 0 0 0 3px rgba(0, 123, 255, 0.1);
}
.form-group textarea {
resize: vertical;
min-height: 100px;
}
.appeal-success {
background: #d4edda;
color: #155724;
padding: 15px;
border-radius: 8px;
margin-top: 20px;
border: 1px solid #c3e6cb;
}
.additional-info {
margin-bottom: 30px;
}
.additional-info h3 {
color: #333;
margin: 0 0 20px 0;
font-size: 20px;
}
.info-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 20px;
}
.info-card {
background: #f8f9fa;
border-radius: 12px;
padding: 20px;
text-align: center;
}
.info-card h4 {
color: #333;
margin: 0 0 10px 0;
font-size: 16px;
}
.info-card p {
color: #666;
margin: 0 0 15px 0;
line-height: 1.5;
}
.link {
color: #007bff;
text-decoration: none;
font-weight: 500;
}
.link:hover {
text-decoration: underline;
}
.bypass-warning {
background: #fff3cd;
border: 1px solid #ffeaa7;
border-radius: 12px;
padding: 20px;
display: flex;
align-items: flex-start;
gap: 15px;
}
.warning-icon {
font-size: 24px;
flex-shrink: 0;
}
.warning-text {
color: #856404;
line-height: 1.5;
}
.warning-text strong {
color: #721c24;
}
.btn {
background: #007bff;
color: white;
border: none;
padding: 12px 24px;
border-radius: 8px;
cursor: pointer;
font-size: 14px;
font-weight: 500;
transition: all 0.3s;
display: inline-block;
text-decoration: none;
}
.btn:hover:not(:disabled) {
background: #0056b3;
transform: translateY(-1px);
}
.btn:disabled {
opacity: 0.6;
cursor: not-allowed;
transform: none;
}
.btn-primary {
background: #007bff;
}
.btn-primary:hover:not(:disabled) {
background: #0056b3;
}
/* Responsive design */
@media (max-width: 768px) {
.device-blocked-page {
padding: 10px;
}
.blocked-content {
padding: 20px;
}
.blocked-content h1 {
font-size: 24px;
}
.detail-item {
flex-direction: column;
gap: 5px;
}
.detail-item strong {
min-width: auto;
}
.contact-item {
flex-direction: column;
align-items: flex-start;
gap: 5px;
}
.contact-item strong {
min-width: auto;
}
.info-grid {
grid-template-columns: 1fr;
}
.bypass-warning {
flex-direction: column;
gap: 10px;
}
}
@media (max-width: 480px) {
.blocked-container {
border-radius: 8px;
}
.blocked-icon {
padding: 30px 20px 15px;
}
.blocked-content {
padding: 15px;
}
.block-info,
.contact-section {
padding: 15px;
}
.form-group input,
.form-group textarea {
padding: 10px;
}
}
</style>