Описание изменений
This commit is contained in:
@@ -1,188 +0,0 @@
|
||||
<template>
|
||||
<div class="access-control">
|
||||
<div v-if="error" class="error-message">
|
||||
{{ error }}
|
||||
</div>
|
||||
<div v-else-if="loading" class="loading-message">Загрузка...</div>
|
||||
<div v-else>
|
||||
<div v-if="!isConnected" class="alert alert-warning">
|
||||
Подключите ваш кошелек для проверки доступа
|
||||
</div>
|
||||
<div v-else-if="accessInfo.hasAccess" class="alert alert-success">
|
||||
<strong>Доступ разрешен!</strong>
|
||||
<div>Токен: {{ accessInfo.token }}</div>
|
||||
<div>Роль: {{ accessInfo.role }}</div>
|
||||
<div>Истекает: {{ formatDate(accessInfo.expiresAt) }}</div>
|
||||
</div>
|
||||
<div v-else class="alert alert-danger">
|
||||
<strong>Доступ запрещен!</strong>
|
||||
<p>У вас нет активного токена доступа.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted, watch } from 'vue';
|
||||
import axios from 'axios';
|
||||
import { useAuthStore } from '../stores/auth';
|
||||
|
||||
const authStore = useAuthStore();
|
||||
const address = ref('');
|
||||
const isConnected = ref(true);
|
||||
const loading = ref(false);
|
||||
const error = ref(null);
|
||||
const accessInfo = ref({
|
||||
hasAccess: false,
|
||||
token: '',
|
||||
role: '',
|
||||
expiresAt: null,
|
||||
});
|
||||
|
||||
// Форматирование даты
|
||||
function formatDate(timestamp) {
|
||||
if (!timestamp) return 'Н/Д';
|
||||
return new Date(timestamp).toLocaleString();
|
||||
}
|
||||
|
||||
// Проверка доступа
|
||||
async function checkAccess() {
|
||||
if (!isConnected.value || !address.value) return;
|
||||
|
||||
loading.value = true;
|
||||
error.value = null;
|
||||
|
||||
try {
|
||||
const response = await axios.get('/access/check', {
|
||||
headers: {
|
||||
'x-wallet-address': address.value,
|
||||
},
|
||||
});
|
||||
|
||||
accessInfo.value = response.data;
|
||||
} catch (err) {
|
||||
console.error('Ошибка проверки доступа:', err);
|
||||
error.value = err.response?.data?.error || 'Ошибка проверки доступа';
|
||||
accessInfo.value = { hasAccess: false };
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Проверяем доступ при изменении адреса
|
||||
watch(
|
||||
() => address.value,
|
||||
() => {
|
||||
checkAccess();
|
||||
}
|
||||
);
|
||||
|
||||
// Проверяем доступ при монтировании компонента
|
||||
onMounted(() => {
|
||||
if (isConnected.value && address.value) {
|
||||
checkAccess();
|
||||
}
|
||||
});
|
||||
|
||||
async function loadTokens() {
|
||||
try {
|
||||
console.log('Загрузка токенов...');
|
||||
loading.value = true;
|
||||
|
||||
// Добавляем withCredentials для передачи куки с сессией
|
||||
const response = await axios.get('/api/access/tokens', {
|
||||
withCredentials: true,
|
||||
});
|
||||
|
||||
console.log('Ответ API:', response.data);
|
||||
|
||||
if (response.data && response.data.length > 0) {
|
||||
// Если есть токены, берем первый активный
|
||||
const activeToken = response.data.find((token) => {
|
||||
const expiresAt = new Date(token.expires_at);
|
||||
return expiresAt > new Date();
|
||||
});
|
||||
|
||||
if (activeToken) {
|
||||
accessInfo.value = {
|
||||
hasAccess: true,
|
||||
token: activeToken.id,
|
||||
role: activeToken.role,
|
||||
expiresAt: activeToken.expires_at,
|
||||
};
|
||||
} else {
|
||||
accessInfo.value = { hasAccess: false };
|
||||
}
|
||||
} else {
|
||||
accessInfo.value = { hasAccess: false };
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Ошибка при загрузке токенов:', error);
|
||||
error.value = 'Ошибка при проверке доступа: ' + (error.response?.data?.error || error.message);
|
||||
accessInfo.value = { hasAccess: false };
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
console.log('Компонент AccessControl загружен');
|
||||
console.log('isAdmin:', authStore.isAdmin);
|
||||
await loadTokens();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.access-control {
|
||||
margin: 20px 0;
|
||||
padding: 15px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.alert {
|
||||
padding: 10px 15px;
|
||||
margin-bottom: 10px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.alert-warning {
|
||||
background-color: #fff3cd;
|
||||
border: 1px solid #ffeeba;
|
||||
color: #856404;
|
||||
}
|
||||
|
||||
.alert-info {
|
||||
background-color: #d1ecf1;
|
||||
border: 1px solid #bee5eb;
|
||||
color: #0c5460;
|
||||
}
|
||||
|
||||
.alert-danger {
|
||||
background-color: #f8d7da;
|
||||
border: 1px solid #f5c6cb;
|
||||
color: #721c24;
|
||||
}
|
||||
|
||||
.alert-success {
|
||||
background-color: #d4edda;
|
||||
border: 1px solid #c3e6cb;
|
||||
color: #155724;
|
||||
}
|
||||
|
||||
.error-message {
|
||||
color: #721c24;
|
||||
background-color: #f8d7da;
|
||||
padding: 10px;
|
||||
border-radius: 4px;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.loading-message {
|
||||
color: #0c5460;
|
||||
background-color: #d1ecf1;
|
||||
padding: 10px;
|
||||
border-radius: 4px;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
</style>
|
||||
@@ -1,161 +0,0 @@
|
||||
<template>
|
||||
<div class="access-token-manager">
|
||||
<h3>Управление токенами доступа</h3>
|
||||
<div class="token-actions">
|
||||
<button @click="mintNewToken">Выпустить новый токен</button>
|
||||
<button @click="loadTokens">Обновить список</button>
|
||||
</div>
|
||||
|
||||
<div v-if="loading">Загрузка...</div>
|
||||
|
||||
<table v-else-if="tokens.length > 0" class="tokens-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>Владелец</th>
|
||||
<th>Роль</th>
|
||||
<th>Действия</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="token in tokens" :key="token.id">
|
||||
<td>{{ token.id }}</td>
|
||||
<td>{{ token.owner }}</td>
|
||||
<td>{{ getRoleName(token.role) }}</td>
|
||||
<td>
|
||||
<button @click="revokeToken(token.id)">Отозвать</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div v-else>Нет доступных токенов</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted } from 'vue';
|
||||
import axios from 'axios';
|
||||
|
||||
const tokens = ref([]);
|
||||
const loading = ref(false);
|
||||
|
||||
const roles = {
|
||||
0: 'Администратор',
|
||||
1: 'Модератор',
|
||||
2: 'Пользователь',
|
||||
};
|
||||
|
||||
function getRoleName(roleId) {
|
||||
return roles[roleId] || 'Неизвестная роль';
|
||||
}
|
||||
|
||||
async function loadTokens() {
|
||||
try {
|
||||
console.log('Загрузка токенов...');
|
||||
loading.value = true;
|
||||
|
||||
// Добавляем withCredentials для передачи куки с сессией
|
||||
const response = await axios.get('/api/access/tokens', {
|
||||
withCredentials: true,
|
||||
});
|
||||
|
||||
console.log('Ответ API:', response.data);
|
||||
tokens.value = response.data;
|
||||
} catch (error) {
|
||||
console.error('Ошибка при загрузке токенов:', error);
|
||||
if (error.response) {
|
||||
console.error('Статус ошибки:', error.response.status);
|
||||
console.error('Данные ошибки:', error.response.data);
|
||||
} else if (error.request) {
|
||||
console.error('Запрос без ответа:', error.request);
|
||||
} else {
|
||||
console.error('Ошибка настройки запроса:', error.message);
|
||||
}
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function mintNewToken() {
|
||||
try {
|
||||
const walletAddress = prompt('Введите адрес получателя:');
|
||||
if (!walletAddress) return;
|
||||
|
||||
const role = prompt('Введите роль (ADMIN, MODERATOR, USER):');
|
||||
if (!role) return;
|
||||
|
||||
const expiresInDays = prompt('Введите срок действия в днях:');
|
||||
if (!expiresInDays) return;
|
||||
|
||||
// Используем правильные имена параметров
|
||||
await axios.post(
|
||||
'/api/access/mint',
|
||||
{
|
||||
walletAddress,
|
||||
role,
|
||||
expiresInDays,
|
||||
},
|
||||
{
|
||||
withCredentials: true,
|
||||
}
|
||||
);
|
||||
|
||||
await loadTokens();
|
||||
} catch (error) {
|
||||
console.error('Ошибка при выпуске токена:', error);
|
||||
if (error.response) {
|
||||
console.error('Статус ошибки:', error.response.status);
|
||||
console.error('Данные ошибки:', error.response.data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function revokeToken(tokenId) {
|
||||
try {
|
||||
if (!confirm(`Вы уверены, что хотите отозвать токен #${tokenId}?`)) return;
|
||||
|
||||
await axios.post('/api/access/revoke', { tokenId });
|
||||
await loadTokens();
|
||||
} catch (error) {
|
||||
console.error('Ошибка при отзыве токена:', error);
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
await loadTokens();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.access-token-manager {
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
.token-actions {
|
||||
margin: 15px 0;
|
||||
}
|
||||
|
||||
.tokens-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
.tokens-table th,
|
||||
.tokens-table td {
|
||||
border: 1px solid #ddd;
|
||||
padding: 8px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.tokens-table th {
|
||||
background-color: #f2f2f2;
|
||||
}
|
||||
|
||||
button {
|
||||
margin-right: 5px;
|
||||
padding: 5px 10px;
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
@@ -1,33 +1,30 @@
|
||||
<template>
|
||||
<nav class="main-nav">
|
||||
<div class="nav-brand">
|
||||
<router-link to="/">DApp for Business</router-link>
|
||||
<nav class="navbar">
|
||||
<div class="navbar-brand">
|
||||
<router-link to="/" class="navbar-logo">DApp for Business</router-link>
|
||||
</div>
|
||||
|
||||
<div class="nav-links">
|
||||
<router-link to="/" class="nav-link">Главная</router-link>
|
||||
<router-link to="/chat" class="nav-link">Чат</router-link>
|
||||
<router-link v-if="authStore.isAdmin" to="/admin" class="nav-link admin-link">
|
||||
Админ-панель
|
||||
</router-link>
|
||||
</div>
|
||||
|
||||
<div class="nav-auth">
|
||||
<template v-if="authStore.isAuthenticated">
|
||||
<div class="user-info">
|
||||
<span class="user-address">{{ formatAddress(authStore.address) }}</span>
|
||||
<span v-if="authStore.isAdmin" class="admin-badge">Админ</span>
|
||||
|
||||
<div class="navbar-menu">
|
||||
<div class="navbar-start">
|
||||
<router-link to="/" class="navbar-item">Главная</router-link>
|
||||
<router-link v-if="isAuthenticated" to="/chat" class="navbar-item">Чат</router-link>
|
||||
</div>
|
||||
|
||||
<div class="navbar-end">
|
||||
<div v-if="isAuthenticated" class="navbar-item user-info">
|
||||
<span v-if="userAddress" class="user-address">{{ formatAddress(userAddress) }}</span>
|
||||
<button @click="logout" class="logout-btn">Выйти</button>
|
||||
</div>
|
||||
<button @click="logout" class="btn-logout">Выйти</button>
|
||||
</template>
|
||||
<template v-else>
|
||||
<wallet-connection />
|
||||
</template>
|
||||
<div v-else class="navbar-item">
|
||||
<WalletConnection />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { useAuthStore } from '../stores/auth';
|
||||
import WalletConnection from './WalletConnection.vue';
|
||||
@@ -35,10 +32,13 @@ import WalletConnection from './WalletConnection.vue';
|
||||
const router = useRouter();
|
||||
const authStore = useAuthStore();
|
||||
|
||||
const isAuthenticated = computed(() => authStore.isAuthenticated);
|
||||
const userAddress = computed(() => authStore.user?.address);
|
||||
|
||||
// Форматирование адреса кошелька
|
||||
function formatAddress(address) {
|
||||
if (!address) return '';
|
||||
return address.substring(0, 6) + '...' + address.substring(address.length - 4);
|
||||
return `${address.substring(0, 6)}...${address.substring(address.length - 4)}`;
|
||||
}
|
||||
|
||||
// Выход из системы
|
||||
@@ -49,85 +49,108 @@ async function logout() {
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.main-nav {
|
||||
.navbar {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 1rem;
|
||||
padding: 0.5rem 1rem;
|
||||
background-color: #fff;
|
||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.nav-brand a {
|
||||
.navbar-brand {
|
||||
font-weight: bold;
|
||||
font-size: 1.25rem;
|
||||
font-weight: 700;
|
||||
color: #3498db;
|
||||
}
|
||||
|
||||
.navbar-logo {
|
||||
color: #1976d2;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.nav-links {
|
||||
.navbar-menu {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
justify-content: space-between;
|
||||
flex: 1;
|
||||
margin-left: 1rem;
|
||||
}
|
||||
|
||||
.nav-link {
|
||||
color: #333;
|
||||
text-decoration: none;
|
||||
padding: 0.5rem;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.nav-link:hover {
|
||||
background-color: #f0f0f0;
|
||||
}
|
||||
|
||||
.nav-link.router-link-active {
|
||||
color: #3498db;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.admin-link {
|
||||
color: #e74c3c;
|
||||
}
|
||||
|
||||
.nav-auth {
|
||||
.navbar-start, .navbar-end {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.navbar-item {
|
||||
padding: 0.5rem 0.75rem;
|
||||
color: #333;
|
||||
text-decoration: none;
|
||||
margin: 0 0.25rem;
|
||||
}
|
||||
|
||||
.navbar-item:hover {
|
||||
color: #1976d2;
|
||||
}
|
||||
|
||||
.user-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.user-address {
|
||||
font-family: monospace;
|
||||
background-color: #f0f0f0;
|
||||
background-color: #f5f5f5;
|
||||
padding: 0.25rem 0.5rem;
|
||||
border-radius: 4px;
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
|
||||
.admin-badge {
|
||||
background-color: #e74c3c;
|
||||
.logout-btn {
|
||||
background-color: #f44336;
|
||||
color: white;
|
||||
padding: 0.1rem 0.3rem;
|
||||
border-radius: 4px;
|
||||
font-size: 0.8rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.btn-logout {
|
||||
padding: 0.5rem 1rem;
|
||||
border-radius: 4px;
|
||||
background-color: #f0f0f0;
|
||||
color: #333;
|
||||
border: none;
|
||||
padding: 0.25rem 0.5rem;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
.btn-logout:hover {
|
||||
background-color: #e0e0e0;
|
||||
.logout-btn:hover {
|
||||
background-color: #d32f2f;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.navbar {
|
||||
flex-direction: column;
|
||||
padding: 0.5rem;
|
||||
}
|
||||
|
||||
.navbar-menu {
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
margin-left: 0;
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
|
||||
.navbar-start, .navbar-end {
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.navbar-item {
|
||||
padding: 0.5rem;
|
||||
margin: 0.25rem 0;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.user-info {
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.user-address {
|
||||
margin-right: 0;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -4,47 +4,62 @@
|
||||
{{ error }}
|
||||
</div>
|
||||
|
||||
<button @click="connect" class="connect-button" :disabled="loading">
|
||||
<button @click="connectWallet" class="connect-button" :disabled="loading">
|
||||
<div v-if="loading" class="spinner"></div>
|
||||
{{ loading ? 'Подключение...' : 'Подключить кошелек' }}
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
<script>
|
||||
import { ref } from 'vue';
|
||||
import { connectWallet } from '../utils/wallet';
|
||||
import { useAuthStore } from '../stores/auth';
|
||||
import { useRouter } from 'vue-router';
|
||||
|
||||
const authStore = useAuthStore();
|
||||
const router = useRouter();
|
||||
const loading = ref(false);
|
||||
const error = ref('');
|
||||
export default {
|
||||
setup() {
|
||||
const authStore = useAuthStore();
|
||||
const router = useRouter();
|
||||
const loading = ref(false);
|
||||
const error = ref('');
|
||||
|
||||
async function connect() {
|
||||
console.log('Нажата кнопка "Подключить кошелек"');
|
||||
|
||||
if (loading.value) return;
|
||||
|
||||
loading.value = true;
|
||||
error.value = '';
|
||||
|
||||
try {
|
||||
const authResult = await connectWallet();
|
||||
console.log('Результат подключения:', authResult);
|
||||
|
||||
if (authResult && authResult.authenticated) {
|
||||
authStore.updateAuthState(authResult);
|
||||
router.push({ name: 'home' });
|
||||
} else {
|
||||
error.value = 'Не удалось подключить кошелек';
|
||||
return {
|
||||
authStore,
|
||||
router,
|
||||
loading,
|
||||
error
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async connectWallet() {
|
||||
console.log('Нажата кнопка "Подключить кошелек"');
|
||||
|
||||
if (this.loading) return;
|
||||
|
||||
this.loading = true;
|
||||
this.error = '';
|
||||
|
||||
try {
|
||||
const authResult = await connectWallet();
|
||||
console.log('Результат подключения:', authResult);
|
||||
|
||||
if (authResult && authResult.authenticated) {
|
||||
this.authStore.isAuthenticated = authResult.authenticated;
|
||||
this.authStore.user = { address: authResult.address };
|
||||
this.authStore.isAdmin = authResult.isAdmin;
|
||||
this.authStore.authType = authResult.authType;
|
||||
this.router.push({ name: 'home' });
|
||||
} else {
|
||||
this.error = 'Не удалось подключить кошелек';
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Ошибка при подключении кошелька:', error);
|
||||
this.error = error.message || 'Ошибка при подключении кошелька';
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Ошибка при подключении кошелька:', error);
|
||||
error.value = error.message || 'Ошибка при подключении кошелька';
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -0,0 +1,195 @@
|
||||
<template>
|
||||
<div class="email-connect">
|
||||
<p>Подключите свой email для быстрой авторизации.</p>
|
||||
|
||||
<div class="email-form">
|
||||
<input
|
||||
type="email"
|
||||
v-model="email"
|
||||
placeholder="Введите ваш email"
|
||||
:disabled="loading || verificationSent"
|
||||
/>
|
||||
<button
|
||||
@click="sendVerification"
|
||||
class="connect-button"
|
||||
:disabled="!isValidEmail || loading || verificationSent"
|
||||
>
|
||||
<span class="email-icon">✉️</span> {{ verificationSent ? 'Код отправлен' : 'Отправить код' }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div v-if="verificationSent" class="verification-form">
|
||||
<input
|
||||
type="text"
|
||||
v-model="verificationCode"
|
||||
placeholder="Введите код подтверждения"
|
||||
:disabled="loading"
|
||||
/>
|
||||
<button
|
||||
@click="verifyEmail"
|
||||
class="verify-button"
|
||||
:disabled="!verificationCode || loading"
|
||||
>
|
||||
Подтвердить
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div v-if="loading" class="loading">Загрузка...</div>
|
||||
<div v-if="error" class="error">{{ error }}</div>
|
||||
<div v-if="success" class="success">{{ success }}</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed } from 'vue';
|
||||
import axios from 'axios';
|
||||
|
||||
const email = ref('');
|
||||
const verificationCode = ref('');
|
||||
const loading = ref(false);
|
||||
const error = ref('');
|
||||
const success = ref('');
|
||||
const verificationSent = ref(false);
|
||||
|
||||
const isValidEmail = computed(() => {
|
||||
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||
return emailRegex.test(email.value);
|
||||
});
|
||||
|
||||
async function sendVerification() {
|
||||
if (!isValidEmail.value) return;
|
||||
|
||||
try {
|
||||
loading.value = true;
|
||||
error.value = '';
|
||||
success.value = '';
|
||||
|
||||
// Запрос на отправку кода подтверждения
|
||||
const response = await axios.post('/api/auth/email', {
|
||||
email: email.value
|
||||
}, {
|
||||
withCredentials: true
|
||||
});
|
||||
|
||||
if (response.data.error) {
|
||||
error.value = `Ошибка: ${response.data.error}`;
|
||||
return;
|
||||
}
|
||||
|
||||
verificationSent.value = true;
|
||||
success.value = `Код подтверждения отправлен на ${email.value}`;
|
||||
} catch (err) {
|
||||
console.error('Error sending verification code:', err);
|
||||
error.value = 'Ошибка при отправке кода подтверждения';
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function verifyEmail() {
|
||||
if (!verificationCode.value) return;
|
||||
|
||||
try {
|
||||
loading.value = true;
|
||||
error.value = '';
|
||||
success.value = '';
|
||||
|
||||
// Запрос на проверку кода
|
||||
const response = await axios.post('/api/auth/email/verify', {
|
||||
email: email.value,
|
||||
code: verificationCode.value
|
||||
}, {
|
||||
withCredentials: true
|
||||
});
|
||||
|
||||
if (response.data.error) {
|
||||
error.value = `Ошибка: ${response.data.error}`;
|
||||
return;
|
||||
}
|
||||
|
||||
success.value = 'Email успешно подтвержден';
|
||||
|
||||
// Сбрасываем форму
|
||||
setTimeout(() => {
|
||||
email.value = '';
|
||||
verificationCode.value = '';
|
||||
verificationSent.value = false;
|
||||
success.value = '';
|
||||
}, 3000);
|
||||
} catch (err) {
|
||||
console.error('Error verifying email:', err);
|
||||
error.value = 'Ошибка при проверке кода подтверждения';
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.email-connect {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.email-form, .verification-form {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
input {
|
||||
flex: 1;
|
||||
padding: 10px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.connect-button, .verify-button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 10px 15px;
|
||||
background-color: #4caf50;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 16px;
|
||||
transition: background-color 0.2s;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.connect-button:hover, .verify-button:hover {
|
||||
background-color: #45a049;
|
||||
}
|
||||
|
||||
.connect-button:disabled, .verify-button:disabled {
|
||||
background-color: #cccccc;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.email-icon {
|
||||
margin-right: 10px;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.loading, .error, .success {
|
||||
padding: 10px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.loading {
|
||||
background-color: #f8f9fa;
|
||||
}
|
||||
|
||||
.error {
|
||||
background-color: #f8d7da;
|
||||
color: #721c24;
|
||||
}
|
||||
|
||||
.success {
|
||||
background-color: #d4edda;
|
||||
color: #155724;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
<script setup>
|
||||
import { ref, onMounted } from 'vue';
|
||||
import { useAuthStore } from '../../stores/auth';
|
||||
|
||||
const authStore = useAuthStore();
|
||||
const identities = ref({});
|
||||
const newIdentity = ref({ type: 'email', value: '' });
|
||||
const loading = ref(false);
|
||||
const error = ref(null);
|
||||
|
||||
onMounted(async () => {
|
||||
try {
|
||||
loading.value = true;
|
||||
const response = await fetch('/api/access/tokens', {
|
||||
credentials: 'include'
|
||||
});
|
||||
const data = await response.json();
|
||||
identities.value = data.identities || {};
|
||||
} catch (err) {
|
||||
error.value = 'Ошибка при загрузке идентификаторов';
|
||||
console.error(err);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
});
|
||||
|
||||
async function addIdentity() {
|
||||
try {
|
||||
loading.value = true;
|
||||
error.value = null;
|
||||
|
||||
const success = await authStore.linkIdentity(
|
||||
newIdentity.value.type,
|
||||
newIdentity.value.value
|
||||
);
|
||||
|
||||
if (success) {
|
||||
identities.value = authStore.identities;
|
||||
newIdentity.value.value = '';
|
||||
}
|
||||
} catch (err) {
|
||||
error.value = 'Ошибка при добавлении идентификатора';
|
||||
console.error(err);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
<template>
|
||||
<div class="telegram-connect">
|
||||
<p>Подключите свой аккаунт Telegram для быстрой авторизации.</p>
|
||||
<button @click="connectTelegram" class="connect-button">
|
||||
<span class="telegram-icon">📱</span> Подключить Telegram
|
||||
</button>
|
||||
|
||||
<div v-if="loading" class="loading">Загрузка...</div>
|
||||
<div v-if="error" class="error">{{ error }}</div>
|
||||
<div v-if="success" class="success">{{ success }}</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue';
|
||||
import axios from 'axios';
|
||||
|
||||
const loading = ref(false);
|
||||
const error = ref('');
|
||||
const success = ref('');
|
||||
|
||||
async function connectTelegram() {
|
||||
try {
|
||||
loading.value = true;
|
||||
error.value = '';
|
||||
success.value = '';
|
||||
|
||||
// Запрос на получение ссылки для авторизации через Telegram
|
||||
const response = await axios.get('/api/auth/telegram', {
|
||||
withCredentials: true
|
||||
});
|
||||
|
||||
if (response.data.error) {
|
||||
error.value = `Ошибка при подключении Telegram: ${response.data.error}`;
|
||||
return;
|
||||
}
|
||||
|
||||
if (response.data.authUrl) {
|
||||
success.value = 'Перейдите по ссылке для авторизации через Telegram';
|
||||
window.open(response.data.authUrl, '_blank');
|
||||
} else {
|
||||
error.value = 'Не удалось получить ссылку для авторизации';
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Error connecting Telegram:', err);
|
||||
error.value = 'Ошибка при подключении Telegram';
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.telegram-connect {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.connect-button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 10px 15px;
|
||||
background-color: #0088cc;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 16px;
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
|
||||
.connect-button:hover {
|
||||
background-color: #0077b5;
|
||||
}
|
||||
|
||||
.telegram-icon {
|
||||
margin-right: 10px;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.loading, .error, .success {
|
||||
padding: 10px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.loading {
|
||||
background-color: #f8f9fa;
|
||||
}
|
||||
|
||||
.error {
|
||||
background-color: #f8d7da;
|
||||
color: #721c24;
|
||||
}
|
||||
|
||||
.success {
|
||||
background-color: #d4edda;
|
||||
color: #155724;
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user