Описание изменений
This commit is contained in:
@@ -8,34 +8,26 @@
|
||||
import { onMounted } from 'vue';
|
||||
import { useAuthStore } from './stores/auth';
|
||||
import { useRouter } from 'vue-router';
|
||||
import axios from 'axios';
|
||||
|
||||
console.log('App.vue: Version with auth check loaded');
|
||||
|
||||
const authStore = useAuthStore();
|
||||
const router = useRouter();
|
||||
|
||||
onMounted(async () => {
|
||||
console.log('App.vue: onMounted - checking auth');
|
||||
|
||||
async function checkAuth() {
|
||||
try {
|
||||
// Проверяем аутентификацию на сервере
|
||||
const result = await authStore.checkAuth();
|
||||
console.log('Auth check result:', result.authenticated);
|
||||
const response = await axios.get('/api/auth/check');
|
||||
|
||||
if (result.authenticated) {
|
||||
// Если пользователь аутентифицирован, восстанавливаем состояние
|
||||
console.log('Session restored from server');
|
||||
|
||||
// Загружаем историю чата, если мы на странице чата
|
||||
if (router.currentRoute.value.name === 'home') {
|
||||
console.log('Loading chat history after session restore');
|
||||
// Здесь можно вызвать метод для загрузки истории чата
|
||||
}
|
||||
if (response.data.authenticated) {
|
||||
authStore.setAuth(response.data);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error checking auth:', error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
onMounted(checkAuth);
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
@@ -2,43 +2,30 @@ import axios from 'axios';
|
||||
import { useAuthStore } from '../stores/auth';
|
||||
|
||||
// Создаем экземпляр axios с базовым URL
|
||||
const instance = axios.create({
|
||||
baseURL: '/',
|
||||
const api = axios.create({
|
||||
baseURL: '', // Убираем baseURL
|
||||
withCredentials: true,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
});
|
||||
|
||||
// Добавляем перехватчик для добавления заголовка авторизации
|
||||
instance.interceptors.request.use(
|
||||
// Перехватчик запросов
|
||||
api.interceptors.request.use(
|
||||
(config) => {
|
||||
console.log('Axios interceptor running');
|
||||
const authStore = useAuthStore();
|
||||
config.withCredentials = true; // Важно для каждого запроса
|
||||
|
||||
// Логируем параметры запроса
|
||||
console.log('Request parameters:', config);
|
||||
|
||||
// Если уже есть заголовок Authorization, не перезаписываем его
|
||||
if (config.headers.Authorization) {
|
||||
return config;
|
||||
}
|
||||
|
||||
// Если пользователь аутентифицирован и есть адрес кошелька
|
||||
const authStore = useAuthStore();
|
||||
if (authStore.isAuthenticated && authStore.address) {
|
||||
console.log('Adding Authorization header:', `Bearer ${authStore.address}`);
|
||||
config.headers.Authorization = `Bearer ${authStore.address}`;
|
||||
}
|
||||
|
||||
return config;
|
||||
},
|
||||
(error) => {
|
||||
return Promise.reject(error);
|
||||
}
|
||||
(error) => Promise.reject(error)
|
||||
);
|
||||
|
||||
// Добавляем перехватчик для обработки ответов
|
||||
instance.interceptors.response.use(
|
||||
api.interceptors.response.use(
|
||||
(response) => {
|
||||
console.log('Response from server:', response.data);
|
||||
return response;
|
||||
@@ -69,4 +56,4 @@ const sendGuestMessageToServer = async (messageText) => {
|
||||
}
|
||||
};
|
||||
|
||||
export default instance;
|
||||
export default api;
|
||||
@@ -1,141 +0,0 @@
|
||||
<template>
|
||||
<div class="telegram-auth">
|
||||
<button v-if="!showVerification" class="auth-btn telegram-btn" @click="startTelegramAuth">
|
||||
<span class="auth-icon">📱</span> Подключить Telegram
|
||||
</button>
|
||||
|
||||
<div v-else class="verification-form">
|
||||
<input
|
||||
type="text"
|
||||
v-model="verificationCode"
|
||||
placeholder="Введите код из Telegram"
|
||||
/>
|
||||
<button class="auth-btn verify-btn" @click="verifyCode">Подтвердить</button>
|
||||
<button class="auth-btn cancel-btn" @click="cancelVerification">Отмена</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue';
|
||||
import { useAuthStore } from '../stores/auth';
|
||||
import axios from '../api/axios';
|
||||
|
||||
const auth = useAuthStore();
|
||||
const showVerification = ref(false);
|
||||
const verificationCode = ref('');
|
||||
|
||||
const startTelegramAuth = () => {
|
||||
// Открываем Telegram бота в новом окне
|
||||
window.open('https://t.me/your_bot_username', '_blank');
|
||||
showVerification.value = true;
|
||||
};
|
||||
|
||||
const verifyCode = async () => {
|
||||
try {
|
||||
const response = await axios.post('/api/auth/telegram/verify', {
|
||||
code: verificationCode.value
|
||||
});
|
||||
|
||||
if (response.data.success) {
|
||||
auth.setTelegramAuth(response.data);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error verifying Telegram code:', error);
|
||||
}
|
||||
};
|
||||
|
||||
const cancelVerification = () => {
|
||||
showVerification.value = false;
|
||||
verificationCode.value = '';
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.telegram-auth {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.telegram-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: #0088cc;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
padding: 8px 16px;
|
||||
font-weight: bold;
|
||||
cursor: pointer;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.auth-icon {
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.auth-progress {
|
||||
background-color: #f8f8f8;
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.auth-actions {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
.cancel-btn {
|
||||
background-color: #f5f5f5;
|
||||
color: #333;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
padding: 8px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.check-btn {
|
||||
background-color: #0088cc;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
padding: 8px;
|
||||
cursor: pointer;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.error-message {
|
||||
color: #ff4d4f;
|
||||
margin-top: 10px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.auth-code {
|
||||
font-family: monospace;
|
||||
font-size: 16px;
|
||||
padding: 12px;
|
||||
background-color: #f1f1f1;
|
||||
border-radius: 4px;
|
||||
margin: 15px 0;
|
||||
white-space: nowrap;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.copy-btn {
|
||||
background-color: #4CAF50;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
padding: 8px 12px;
|
||||
cursor: pointer;
|
||||
font-weight: bold;
|
||||
display: block;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.copy-btn:hover {
|
||||
background-color: #45a049;
|
||||
}
|
||||
</style>
|
||||
@@ -1,203 +0,0 @@
|
||||
<template>
|
||||
<div class="wallet-connection">
|
||||
<div v-if="error" class="error-message">
|
||||
{{ error }}
|
||||
</div>
|
||||
|
||||
<div v-if="!authStore.isAuthenticated">
|
||||
<button @click="connectWallet" class="connect-button" :disabled="loading">
|
||||
<div v-if="loading" class="spinner"></div>
|
||||
{{ loading ? 'Подключение...' : 'Подключить кошелек' }}
|
||||
</button>
|
||||
</div>
|
||||
<div v-else class="wallet-info">
|
||||
<span class="address">{{ formatAddress(authStore.user?.address) }}</span>
|
||||
<button @click="disconnectWallet" class="disconnect-btn">Выйти</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted } from 'vue';
|
||||
import { connectWithWallet } 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('');
|
||||
const isConnecting = ref(false);
|
||||
|
||||
const props = defineProps({
|
||||
onWalletAuth: {
|
||||
type: Function,
|
||||
required: true
|
||||
},
|
||||
isAuthenticated: {
|
||||
type: Boolean,
|
||||
required: true
|
||||
}
|
||||
});
|
||||
|
||||
// Форматирование адреса кошелька
|
||||
const formatAddress = (address) => {
|
||||
if (!address) return '';
|
||||
return `${address.substring(0, 6)}...${address.substring(address.length - 4)}`;
|
||||
};
|
||||
|
||||
// Функция для подключения кошелька
|
||||
const connectWallet = async () => {
|
||||
loading.value = true;
|
||||
error.value = '';
|
||||
|
||||
try {
|
||||
await props.onWalletAuth();
|
||||
} catch (err) {
|
||||
console.error('Ошибка при подключении кошелька:', err);
|
||||
error.value = 'Ошибка подключения кошелька';
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// Автоматическое подключение при загрузке компонента
|
||||
onMounted(async () => {
|
||||
console.log('WalletConnection mounted, checking auth state...');
|
||||
|
||||
// Проверяем аутентификацию на сервере
|
||||
const authState = await authStore.checkAuth();
|
||||
console.log('Auth state after check:', authState);
|
||||
|
||||
// Если пользователь уже аутентифицирован, не нужно ничего делать
|
||||
if (authState.authenticated) {
|
||||
console.log('User is already authenticated, no need to reconnect');
|
||||
return;
|
||||
}
|
||||
|
||||
// Проверяем, есть ли сохраненный адрес кошелька
|
||||
const savedAddress = localStorage.getItem('walletAddress');
|
||||
|
||||
if (savedAddress && window.ethereum) {
|
||||
console.log('Found saved wallet address:', savedAddress);
|
||||
|
||||
try {
|
||||
// Проверяем, разблокирован ли MetaMask, но не запрашиваем разрешение
|
||||
const accounts = await window.ethereum.request({
|
||||
method: 'eth_accounts' // Используем eth_accounts вместо eth_requestAccounts
|
||||
});
|
||||
|
||||
if (accounts && accounts.length > 0) {
|
||||
console.log('MetaMask is unlocked, connected accounts:', accounts);
|
||||
|
||||
// Если кошелек разблокирован и есть доступные аккаунты, проверяем совпадение адреса
|
||||
if (accounts[0].toLowerCase() === savedAddress.toLowerCase()) {
|
||||
console.log('Current account matches saved address');
|
||||
|
||||
// Не вызываем handleConnectWallet() автоматически,
|
||||
// просто показываем пользователю, что он может подключиться
|
||||
} else {
|
||||
console.log('Current account does not match saved address');
|
||||
localStorage.removeItem('walletAddress');
|
||||
}
|
||||
} else {
|
||||
console.log('MetaMask is locked or no accounts available');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error checking MetaMask state:', error);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Функция для отключения кошелька
|
||||
const disconnectWallet = async () => {
|
||||
try {
|
||||
// Сначала отключаем MetaMask
|
||||
if (window.ethereum) {
|
||||
try {
|
||||
// Просто очищаем слушатели событий
|
||||
window.ethereum.removeAllListeners();
|
||||
} catch (error) {
|
||||
console.error('Error disconnecting MetaMask:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Затем выполняем выход из системы
|
||||
await authStore.disconnect(router);
|
||||
} catch (error) {
|
||||
console.error('Error disconnecting wallet:', error);
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.wallet-connection {
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
.connect-button {
|
||||
background-color: #1976d2;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 10px 20px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 16px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.connect-button:disabled {
|
||||
background-color: #ccc;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.error-message {
|
||||
color: #d32f2f;
|
||||
margin-bottom: 10px;
|
||||
padding: 10px;
|
||||
background-color: #ffebee;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.spinner {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border: 2px solid rgba(255, 255, 255, 0.3);
|
||||
border-radius: 50%;
|
||||
border-top-color: white;
|
||||
animation: spin 1s linear infinite;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.wallet-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 10px;
|
||||
background-color: #f5f5f5;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.address {
|
||||
font-family: monospace;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.disconnect-btn {
|
||||
background-color: #f44336;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 5px 10px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,195 +1,117 @@
|
||||
<template>
|
||||
<div class="email-connect">
|
||||
<p>Подключите свой email для быстрой авторизации.</p>
|
||||
|
||||
<div class="email-form">
|
||||
<div class="email-connection">
|
||||
<div v-if="!showVerification">
|
||||
<input
|
||||
type="email"
|
||||
v-model="email"
|
||||
placeholder="Введите ваш email"
|
||||
:disabled="loading || verificationSent"
|
||||
v-model="email"
|
||||
type="email"
|
||||
placeholder="Введите email"
|
||||
class="email-input"
|
||||
/>
|
||||
<button
|
||||
@click="sendVerification"
|
||||
class="connect-button"
|
||||
:disabled="!isValidEmail || loading || verificationSent"
|
||||
@click="requestCode"
|
||||
:disabled="isLoading || !isValidEmail"
|
||||
class="email-btn"
|
||||
>
|
||||
<span class="email-icon">✉️</span> {{ verificationSent ? 'Код отправлен' : 'Отправить код' }}
|
||||
Получить код
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div v-if="verificationSent" class="verification-form">
|
||||
<div v-else>
|
||||
<input
|
||||
type="text"
|
||||
v-model="verificationCode"
|
||||
placeholder="Введите код подтверждения"
|
||||
:disabled="loading"
|
||||
v-model="code"
|
||||
type="text"
|
||||
placeholder="Введите код"
|
||||
class="code-input"
|
||||
/>
|
||||
<button
|
||||
@click="verifyEmail"
|
||||
class="verify-button"
|
||||
:disabled="!verificationCode || loading"
|
||||
@click="verifyCode"
|
||||
:disabled="isLoading"
|
||||
class="verify-btn"
|
||||
>
|
||||
Подтвердить
|
||||
</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);
|
||||
const props = defineProps({
|
||||
onEmailAuth: {
|
||||
type: Function,
|
||||
required: true
|
||||
}
|
||||
});
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
const email = ref('');
|
||||
const code = ref('');
|
||||
const error = ref('');
|
||||
const isLoading = ref(false);
|
||||
const showVerification = ref(false);
|
||||
|
||||
async function verifyEmail() {
|
||||
if (!verificationCode.value) return;
|
||||
|
||||
const isValidEmail = computed(() => {
|
||||
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email.value);
|
||||
});
|
||||
|
||||
const requestCode = async () => {
|
||||
try {
|
||||
loading.value = true;
|
||||
isLoading.value = true;
|
||||
await props.onEmailAuth(email.value);
|
||||
showVerification.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 = 'Ошибка при проверке кода подтверждения';
|
||||
error.value = err.message || 'Ошибка отправки кода';
|
||||
} finally {
|
||||
loading.value = false;
|
||||
isLoading.value = false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const verifyCode = async () => {
|
||||
try {
|
||||
isLoading.value = true;
|
||||
await props.onEmailAuth(email.value, code.value);
|
||||
error.value = '';
|
||||
} catch (err) {
|
||||
error.value = err.message || 'Неверный код';
|
||||
} finally {
|
||||
isLoading.value = false;
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.email-connect {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 15px;
|
||||
.email-connection {
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
.email-form, .verification-form {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
input {
|
||||
flex: 1;
|
||||
padding: 10px;
|
||||
.email-input,
|
||||
.code-input {
|
||||
padding: 8px;
|
||||
margin-right: 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;
|
||||
.email-btn,
|
||||
.verify-btn {
|
||||
padding: 10px 20px;
|
||||
background-color: #48bb78;
|
||||
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;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.error {
|
||||
background-color: #f8d7da;
|
||||
color: #721c24;
|
||||
color: #e53e3e;
|
||||
margin-top: 5px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.success {
|
||||
background-color: #d4edda;
|
||||
color: #155724;
|
||||
button:disabled {
|
||||
opacity: 0.7;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,48 +0,0 @@
|
||||
<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>
|
||||
84
frontend/src/components/identity/WalletConnection.vue
Normal file
84
frontend/src/components/identity/WalletConnection.vue
Normal file
@@ -0,0 +1,84 @@
|
||||
<template>
|
||||
<div class="wallet-connection">
|
||||
<button
|
||||
@click="connectWallet"
|
||||
:disabled="isLoading"
|
||||
class="wallet-btn"
|
||||
>
|
||||
{{ isAuthenticated ? 'Подключено' : 'Подключить кошелек' }}
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue';
|
||||
import { connectWithWallet } from '../../services/wallet';
|
||||
|
||||
// Определяем props
|
||||
const props = defineProps({
|
||||
isAuthenticated: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
});
|
||||
|
||||
// Определяем состояние
|
||||
const isLoading = ref(false);
|
||||
|
||||
const emit = defineEmits(['connect']);
|
||||
|
||||
// Метод подключения кошелька
|
||||
const connectWallet = async () => {
|
||||
if (isLoading.value) return;
|
||||
|
||||
try {
|
||||
isLoading.value = true;
|
||||
// Получаем адрес кошелька
|
||||
const accounts = await window.ethereum.request({ method: 'eth_requestAccounts' });
|
||||
const address = accounts[0];
|
||||
|
||||
// Получаем nonce
|
||||
const nonceResponse = await api.get(`/api/auth/nonce?address=${address}`);
|
||||
const nonce = nonceResponse.data.nonce;
|
||||
|
||||
// Подписываем сообщение
|
||||
const message = `${window.location.host} wants you to sign in with your Ethereum account:\n${address.slice(0, 42)}...`;
|
||||
const signature = await window.ethereum.request({
|
||||
method: 'personal_sign',
|
||||
params: [message, address]
|
||||
});
|
||||
|
||||
emit('connect', { address, signature, message });
|
||||
} catch (error) {
|
||||
console.error('Error connecting wallet:', error);
|
||||
} finally {
|
||||
isLoading.value = false;
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.wallet-connection {
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
.wallet-btn {
|
||||
padding: 10px 20px;
|
||||
background-color: #4a5568;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
|
||||
.wallet-btn:hover:not(:disabled) {
|
||||
background-color: #2d3748;
|
||||
}
|
||||
|
||||
.wallet-btn:disabled {
|
||||
opacity: 0.7;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
</style>
|
||||
9
frontend/src/components/identity/index.js
Normal file
9
frontend/src/components/identity/index.js
Normal file
@@ -0,0 +1,9 @@
|
||||
import TelegramConnect from './TelegramConnect.vue';
|
||||
import WalletConnection from './WalletConnection.vue';
|
||||
import EmailConnect from './EmailConnect.vue';
|
||||
|
||||
export {
|
||||
TelegramConnect,
|
||||
WalletConnection,
|
||||
EmailConnect
|
||||
};
|
||||
@@ -1,56 +0,0 @@
|
||||
import { ref } from 'vue';
|
||||
import { ethers } from 'ethers';
|
||||
|
||||
export function useEthereum() {
|
||||
const address = ref('');
|
||||
const isConnected = ref(false);
|
||||
const provider = ref(null);
|
||||
const signer = ref(null);
|
||||
|
||||
async function connect() {
|
||||
if (window.ethereum) {
|
||||
try {
|
||||
// Запрашиваем доступ к кошельку
|
||||
await window.ethereum.request({ method: 'eth_requestAccounts' });
|
||||
// Используем синтаксис ethers.js v6
|
||||
provider.value = new ethers.BrowserProvider(window.ethereum);
|
||||
signer.value = await provider.value.getSigner();
|
||||
address.value = await signer.value.getAddress();
|
||||
isConnected.value = true;
|
||||
|
||||
console.log('Подключение успешно:', address.value);
|
||||
return { success: true, address: address.value };
|
||||
} catch (error) {
|
||||
console.error('Ошибка подключения к кошельку:', error);
|
||||
return { success: false, error: error.message };
|
||||
}
|
||||
} else {
|
||||
console.error('Ethereum wallet not found. Please install MetaMask.');
|
||||
return { success: false, error: 'Ethereum wallet not found. Please install MetaMask.' };
|
||||
}
|
||||
}
|
||||
|
||||
async function getContract(contractAddress, contractABI) {
|
||||
if (!signer.value) {
|
||||
console.error('Подключите кошелек перед получением контракта.');
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
// Используем синтаксис ethers.js v6
|
||||
const contract = new ethers.Contract(contractAddress, contractABI, signer.value);
|
||||
console.log('Контракт получен:', contract);
|
||||
return contract;
|
||||
} catch (error) {
|
||||
console.error('Ошибка получения контракта:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
address,
|
||||
isConnected,
|
||||
connect,
|
||||
getContract,
|
||||
};
|
||||
}
|
||||
@@ -10,7 +10,6 @@ const routes = [
|
||||
name: 'home',
|
||||
component: HomeView
|
||||
}
|
||||
// Другие маршруты можно добавить позже, когда будут созданы соответствующие компоненты
|
||||
];
|
||||
|
||||
const router = createRouter({
|
||||
@@ -32,11 +31,10 @@ router.beforeEach(async (to, from, next) => {
|
||||
// Проверяем аутентификацию, если маршрут требует авторизации
|
||||
if (to.matched.some(record => record.meta.requiresAuth)) {
|
||||
if (!authStore.isAuthenticated) {
|
||||
// Если пользователь не авторизован, перенаправляем на главную
|
||||
return next({ name: 'home' });
|
||||
}
|
||||
|
||||
// Проверяем права администратора, если маршрут требует прав администратора
|
||||
// Проверяем права администратора
|
||||
if (to.matched.some(record => record.meta.requiresAdmin) && !authStore.isAdmin) {
|
||||
return next({ name: 'home' });
|
||||
}
|
||||
|
||||
80
frontend/src/services/wallet.js
Normal file
80
frontend/src/services/wallet.js
Normal file
@@ -0,0 +1,80 @@
|
||||
import { ethers } from 'ethers';
|
||||
import api from '../api/axios';
|
||||
import { useAuthStore } from '../stores/auth';
|
||||
|
||||
export async function connectWithWallet() {
|
||||
try {
|
||||
console.log('Starting wallet connection...');
|
||||
// Проверяем наличие MetaMask
|
||||
if (!window.ethereum) {
|
||||
throw new Error('MetaMask не установлен. Пожалуйста, установите MetaMask');
|
||||
}
|
||||
|
||||
console.log('MetaMask detected, requesting accounts...');
|
||||
const accounts = await window.ethereum.request({
|
||||
method: 'eth_requestAccounts'
|
||||
});
|
||||
|
||||
console.log('Got accounts:', accounts);
|
||||
if (!accounts || accounts.length === 0) {
|
||||
throw new Error('Нет доступных аккаунтов. Пожалуйста, разблокируйте MetaMask');
|
||||
}
|
||||
|
||||
const address = ethers.getAddress(accounts[0]);
|
||||
console.log('Normalized address:', address);
|
||||
|
||||
console.log('Requesting nonce...');
|
||||
const { data: { nonce } } = await api.get('/api/auth/nonce', {
|
||||
params: { address }
|
||||
});
|
||||
console.log('Got nonce:', nonce);
|
||||
|
||||
// Формируем сообщение в формате SIWE (Sign-In with Ethereum)
|
||||
const domain = window.location.host;
|
||||
const origin = window.location.origin;
|
||||
const statement = "Sign in with Ethereum to the app.";
|
||||
const message = [
|
||||
`${domain} wants you to sign in with your Ethereum account:`,
|
||||
address,
|
||||
"",
|
||||
statement,
|
||||
"",
|
||||
`URI: ${origin}`,
|
||||
"Version: 1",
|
||||
"Chain ID: 1",
|
||||
`Nonce: ${nonce}`,
|
||||
`Issued At: ${new Date().toISOString()}`,
|
||||
"Resources:",
|
||||
`- ${origin}/api/auth/verify`
|
||||
].join("\n");
|
||||
|
||||
console.log('SIWE message:', message);
|
||||
|
||||
console.log('Requesting signature...');
|
||||
const signature = await window.ethereum.request({
|
||||
method: 'personal_sign',
|
||||
params: [message, address]
|
||||
});
|
||||
console.log('Got signature:', signature);
|
||||
|
||||
console.log('Sending verification request...');
|
||||
const response = await api.post('/api/auth/verify', {
|
||||
address,
|
||||
signature,
|
||||
message
|
||||
});
|
||||
console.log('Verification response:', response.data);
|
||||
|
||||
const authStore = useAuthStore();
|
||||
if (response.data.authenticated) {
|
||||
authStore.setAuth(response.data);
|
||||
}
|
||||
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
// Форматируем ошибку для пользователя
|
||||
const message = error.message || 'Ошибка подключения кошелька';
|
||||
console.error('Error connecting wallet:', message);
|
||||
throw new Error(message);
|
||||
}
|
||||
}
|
||||
@@ -1,37 +1,22 @@
|
||||
import { defineStore } from 'pinia';
|
||||
import axios from '../api/axios';
|
||||
|
||||
const loadAuthState = () => {
|
||||
const savedAuth = localStorage.getItem('auth');
|
||||
if (savedAuth) {
|
||||
try {
|
||||
return JSON.parse(savedAuth);
|
||||
} catch (e) {
|
||||
console.error('Error parsing saved auth state:', e);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
export const useAuthStore = defineStore('auth', {
|
||||
state: () => {
|
||||
const savedState = loadAuthState();
|
||||
return {
|
||||
user: null,
|
||||
isAuthenticated: savedState?.isAuthenticated || false,
|
||||
isAdmin: savedState?.isAdmin || false,
|
||||
authType: savedState?.authType || null,
|
||||
identities: {},
|
||||
loading: false,
|
||||
error: null,
|
||||
messages: [],
|
||||
address: null,
|
||||
wallet: null,
|
||||
telegramId: savedState?.telegramId || null,
|
||||
email: null,
|
||||
userId: savedState?.userId || null
|
||||
};
|
||||
},
|
||||
state: () => ({
|
||||
user: null,
|
||||
isAuthenticated: false,
|
||||
isAdmin: false,
|
||||
authType: null,
|
||||
identities: {},
|
||||
loading: false,
|
||||
error: null,
|
||||
messages: [],
|
||||
address: null,
|
||||
wallet: null,
|
||||
telegramId: null,
|
||||
email: null,
|
||||
userId: null
|
||||
}),
|
||||
|
||||
actions: {
|
||||
async connectWallet(address, signature, message) {
|
||||
@@ -442,24 +427,22 @@ export const useAuthStore = defineStore('auth', {
|
||||
}
|
||||
},
|
||||
|
||||
async disconnect(router) {
|
||||
// Проверяем, действительно ли нужно выходить
|
||||
if (!this.isAuthenticated) {
|
||||
console.log('Already logged out, skipping disconnect');
|
||||
return;
|
||||
}
|
||||
|
||||
async disconnect() {
|
||||
try {
|
||||
// Сначала пробуем очистить сессию на сервере
|
||||
// Очищаем сессию на сервере
|
||||
await axios.post('/api/auth/clear-session');
|
||||
await axios.post('/api/auth/logout');
|
||||
|
||||
// Очищаем состояние только после успешного выхода
|
||||
this.clearState();
|
||||
|
||||
if (router) router.push('/');
|
||||
// Очищаем состояние
|
||||
this.isAuthenticated = false;
|
||||
this.userId = null;
|
||||
this.address = null;
|
||||
this.isAdmin = false;
|
||||
this.authType = null;
|
||||
|
||||
// Очищаем локальное хранилище
|
||||
localStorage.removeItem('auth');
|
||||
} catch (error) {
|
||||
console.error('Error during logout:', error);
|
||||
console.error('Error during disconnect:', error);
|
||||
}
|
||||
},
|
||||
|
||||
@@ -509,29 +492,23 @@ export const useAuthStore = defineStore('auth', {
|
||||
setAuth(authData) {
|
||||
console.log('Setting auth state:', authData);
|
||||
|
||||
// Обновляем все поля состояния
|
||||
// Обновляем только состояние в памяти
|
||||
this.isAuthenticated = authData.authenticated || authData.isAuthenticated;
|
||||
this.user = {
|
||||
id: authData.userId,
|
||||
address: authData.address
|
||||
};
|
||||
this.userId = authData.userId;
|
||||
this.isAdmin = authData.isAdmin;
|
||||
this.authType = authData.authType;
|
||||
this.address = authData.address;
|
||||
|
||||
// Сохраняем состояние в localStorage
|
||||
const stateToSave = {
|
||||
isAuthenticated: this.isAuthenticated,
|
||||
userId: this.userId,
|
||||
isAdmin: this.isAdmin,
|
||||
authType: this.authType,
|
||||
address: this.address
|
||||
};
|
||||
|
||||
localStorage.setItem('auth', JSON.stringify(stateToSave));
|
||||
|
||||
console.log('Auth state updated:', {
|
||||
isAuthenticated: this.isAuthenticated,
|
||||
userId: this.userId,
|
||||
authType: this.authType,
|
||||
address: this.address
|
||||
address: this.address,
|
||||
isAdmin: this.isAdmin
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,90 +0,0 @@
|
||||
import { ethers } from 'ethers';
|
||||
import axios from '../api/axios';
|
||||
import { useAuthStore } from '../stores/auth';
|
||||
|
||||
// Переименовываем функцию для соответствия импорту
|
||||
export async function connectWithWallet() {
|
||||
try {
|
||||
// Проверяем, доступен ли MetaMask
|
||||
if (!window.ethereum) {
|
||||
throw new Error('MetaMask не установлен');
|
||||
}
|
||||
|
||||
// Запрашиваем доступ к кошельку
|
||||
const provider = new ethers.BrowserProvider(window.ethereum);
|
||||
const signer = await provider.getSigner();
|
||||
const address = await signer.getAddress();
|
||||
|
||||
// Получаем nonce для подписи
|
||||
const nonceResponse = await axios.get(`/api/auth/nonce?address=${address}`);
|
||||
const nonce = nonceResponse.data.nonce;
|
||||
|
||||
// Формируем сообщение для подписи
|
||||
const message = `Подпишите это сообщение для аутентификации в DApp for Business. Nonce: ${nonce}`;
|
||||
|
||||
// Подписываем сообщение
|
||||
const signature = await signer.signMessage(message);
|
||||
|
||||
// Верифицируем подпись на сервере
|
||||
const response = await axios.post('/api/auth/verify', {
|
||||
address,
|
||||
signature,
|
||||
message: nonce
|
||||
});
|
||||
|
||||
console.log('Wallet verification response:', response.data);
|
||||
|
||||
// Обновляем состояние в хранилище auth
|
||||
const authStore = useAuthStore();
|
||||
authStore.isAuthenticated = response.data.authenticated;
|
||||
authStore.user = {
|
||||
id: response.data.userId,
|
||||
address: response.data.address
|
||||
};
|
||||
authStore.isAdmin = response.data.isAdmin;
|
||||
authStore.authType = 'wallet';
|
||||
|
||||
// Сохраняем адрес кошелька в локальном хранилище
|
||||
localStorage.setItem('walletAddress', address);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
authenticated: response.data.authenticated,
|
||||
userId: response.data.userId,
|
||||
address: response.data.address,
|
||||
isAdmin: response.data.isAdmin,
|
||||
authType: 'wallet'
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Error connecting wallet:', error);
|
||||
return { success: false, error: error.message };
|
||||
}
|
||||
}
|
||||
|
||||
async function disconnectWallet() {
|
||||
try {
|
||||
// Отправляем запрос на выход
|
||||
await axios.post(
|
||||
'/api/auth/logout',
|
||||
{},
|
||||
{
|
||||
withCredentials: true,
|
||||
}
|
||||
);
|
||||
|
||||
// Удаляем адрес кошелька из локального хранилища
|
||||
localStorage.removeItem('walletAddress');
|
||||
|
||||
// Обновляем состояние в хранилище auth
|
||||
const authStore = useAuthStore();
|
||||
authStore.isAuthenticated = false;
|
||||
authStore.user = null;
|
||||
authStore.isAdmin = false;
|
||||
authStore.authType = null;
|
||||
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
console.error('Ошибка при отключении кошелька:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
@@ -5,29 +5,39 @@
|
||||
<div class="auth-section" v-if="!auth.isAuthenticated">
|
||||
<h3>Венчурный фонд и поставщик программного обеспечения</h3>
|
||||
</div>
|
||||
|
||||
<div class="chat-container">
|
||||
<div class="chat-header">
|
||||
<WalletConnection
|
||||
:onWalletAuth="handleWalletAuth"
|
||||
:isAuthenticated="auth.isAuthenticated"
|
||||
/>
|
||||
<div class="user-info" v-if="auth.isAuthenticated">
|
||||
<!-- Используем тот же компонент, что и в сообщениях -->
|
||||
<div v-if="!auth.isAuthenticated" class="auth-buttons">
|
||||
<button class="auth-btn wallet-btn" @click="handleWalletAuth">
|
||||
<span class="auth-icon">👛</span> Подключить кошелек
|
||||
</button>
|
||||
</div>
|
||||
<div v-else class="wallet-info">
|
||||
<span>{{ truncateAddress(auth.address) }}</span>
|
||||
<button class="disconnect-btn" @click="disconnectWallet">
|
||||
Отключить кошелек
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Кнопка загрузки предыдущих сообщений -->
|
||||
<div v-if="hasMoreMessages" class="load-more-container">
|
||||
<button @click="loadMoreMessages" class="load-more-btn" :disabled="isLoadingMore">
|
||||
<div v-if="auth.isAuthenticated && hasMoreMessages" class="load-more">
|
||||
<button @click="loadMoreMessages" :disabled="isLoadingMore">
|
||||
{{ isLoadingMore ? 'Загрузка...' : 'Показать предыдущие сообщения' }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="chat-messages" ref="messagesContainer">
|
||||
<div v-if="isLoadingMore" class="loading">
|
||||
Загрузка...
|
||||
</div>
|
||||
<div v-for="message in messages" :key="message.id" :class="['message', message.role === 'assistant' ? 'ai-message' : 'user-message']">
|
||||
<div class="message-content">
|
||||
{{ message.content }}
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Кнопки аутентификации -->
|
||||
<div v-if="message.showAuthButtons && !auth.isAuthenticated" class="auth-buttons">
|
||||
<button class="auth-btn wallet-btn" @click="handleWalletAuth">
|
||||
@@ -39,32 +49,32 @@
|
||||
<button class="auth-btn email-btn" @click="handleEmailAuth">
|
||||
<span class="auth-icon">✉️</span> Подключить Email
|
||||
</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Email форма -->
|
||||
<div v-if="showEmailForm" class="auth-form">
|
||||
<input
|
||||
<input
|
||||
v-model="emailInput"
|
||||
type="email"
|
||||
placeholder="Введите ваш email"
|
||||
type="email"
|
||||
placeholder="Введите ваш email"
|
||||
class="auth-input"
|
||||
/>
|
||||
/>
|
||||
<button @click="submitEmail" class="auth-btn">
|
||||
Отправить код
|
||||
</button>
|
||||
</div>
|
||||
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Форма верификации email -->
|
||||
<div v-if="showEmailVerification" class="auth-form">
|
||||
<input
|
||||
<input
|
||||
v-model="emailCode"
|
||||
type="text"
|
||||
type="text"
|
||||
placeholder="Введите код из email"
|
||||
class="auth-input"
|
||||
/>
|
||||
/>
|
||||
<button @click="verifyEmailCode" class="auth-btn">
|
||||
Подтвердить
|
||||
</button>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Telegram верификация -->
|
||||
@@ -77,9 +87,9 @@
|
||||
/>
|
||||
<button @click="verifyTelegramCode" class="auth-btn">
|
||||
Подтвердить
|
||||
</button>
|
||||
</div>
|
||||
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div v-if="emailError" class="error-message">
|
||||
{{ emailError }}
|
||||
</div>
|
||||
@@ -106,17 +116,18 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, onMounted, watch, nextTick } from 'vue';
|
||||
import { ref, computed, onMounted, watch, nextTick, onBeforeUnmount } from 'vue';
|
||||
import { useAuthStore } from '../stores/auth';
|
||||
import WalletConnection from '../components/WalletConnection.vue';
|
||||
import TelegramConnect from '../components/TelegramConnect.vue';
|
||||
import axios from '../api/axios';
|
||||
import { connectWithWallet } from '../utils/wallet';
|
||||
import WalletConnection from '../components/identity/WalletConnection.vue';
|
||||
import TelegramConnect from '../components/identity/TelegramConnect.vue';
|
||||
import api from '../api/axios';
|
||||
import { connectWithWallet } from '../services/wallet';
|
||||
|
||||
console.log('HomeView.vue: Version with chat loaded');
|
||||
|
||||
const auth = useAuthStore();
|
||||
const messages = ref([]);
|
||||
const guestMessages = ref([]);
|
||||
const newMessage = ref('');
|
||||
const isLoading = ref(false);
|
||||
const messagesContainer = ref(null);
|
||||
@@ -124,7 +135,6 @@ const userLanguage = ref('ru');
|
||||
const email = ref('');
|
||||
const isValidEmail = ref(true);
|
||||
const hasShownAuthMessage = ref(false);
|
||||
const guestMessages = ref([]);
|
||||
const hasShownAuthOptions = ref(false);
|
||||
|
||||
// Email аутентификация
|
||||
@@ -144,8 +154,10 @@ const emailError = ref('');
|
||||
const PAGE_SIZE = 2; // Показываем только последнее сообщение и ответ
|
||||
const allMessages = ref([]); // Все загруженные сообщения
|
||||
const currentPage = ref(1); // Текущая страница
|
||||
const hasMoreMessages = ref(false); // Есть ли еще сообщения
|
||||
const hasMoreMessages = ref(true); // Есть ли еще сообщения
|
||||
const isLoadingMore = ref(false); // Загружаются ли дополнительные сообщения
|
||||
const offset = ref(0);
|
||||
const limit = ref(20);
|
||||
|
||||
// Вычисляемое свойство для отображаемых сообщений
|
||||
const displayedMessages = computed(() => {
|
||||
@@ -153,48 +165,30 @@ const displayedMessages = computed(() => {
|
||||
return allMessages.value.slice(startIndex);
|
||||
});
|
||||
|
||||
// Функция загрузки истории чата
|
||||
const loadChatHistory = async () => {
|
||||
try {
|
||||
if (!auth.isAuthenticated || !auth.userId) {
|
||||
return;
|
||||
}
|
||||
// Функция для сокращения адреса кошелька
|
||||
const truncateAddress = (address) => {
|
||||
if (!address) return '';
|
||||
return `${address.slice(0, 6)}...${address.slice(-4)}`;
|
||||
};
|
||||
|
||||
const response = await axios.get('/api/chat/history', {
|
||||
headers: { Authorization: `Bearer ${auth.address}` },
|
||||
params: { limit: PAGE_SIZE, offset: 0 }
|
||||
});
|
||||
|
||||
if (response.data.success) {
|
||||
messages.value = response.data.messages.map(msg => ({
|
||||
id: msg.id,
|
||||
content: msg.content,
|
||||
role: msg.role || (msg.sender_type === 'assistant' ? 'assistant' : 'user'),
|
||||
timestamp: msg.created_at,
|
||||
showAuthOptions: false
|
||||
}));
|
||||
|
||||
hasMoreMessages.value = response.data.total > PAGE_SIZE;
|
||||
|
||||
await nextTick();
|
||||
scrollToBottom();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error loading chat history:', error);
|
||||
// Функция прокрутки к последнему сообщению
|
||||
const scrollToBottom = () => {
|
||||
if (messagesContainer.value) {
|
||||
messagesContainer.value.scrollTop = messagesContainer.value.scrollHeight;
|
||||
}
|
||||
};
|
||||
|
||||
// Функция загрузки дополнительных сообщений
|
||||
// Загрузка сообщений
|
||||
const loadMoreMessages = async () => {
|
||||
if (isLoadingMore.value) return;
|
||||
if (!auth.isAuthenticated) return;
|
||||
|
||||
try {
|
||||
isLoadingMore.value = true;
|
||||
const offset = messages.value.length;
|
||||
|
||||
const response = await axios.get('/api/chat/history', {
|
||||
headers: { Authorization: `Bearer ${auth.address}` },
|
||||
params: { limit: PAGE_SIZE, offset }
|
||||
const response = await api.get('/api/chat/history', {
|
||||
params: {
|
||||
limit: limit.value,
|
||||
offset: offset.value
|
||||
}
|
||||
});
|
||||
|
||||
if (response.data.success) {
|
||||
@@ -205,83 +199,80 @@ const loadMoreMessages = async () => {
|
||||
timestamp: msg.created_at,
|
||||
showAuthOptions: false
|
||||
}));
|
||||
|
||||
messages.value = [...newMessages, ...messages.value];
|
||||
|
||||
messages.value = [...messages.value, ...newMessages];
|
||||
hasMoreMessages.value = response.data.total > messages.value.length;
|
||||
offset.value += newMessages.length;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error loading more messages:', error);
|
||||
console.error('Error loading chat history:', error);
|
||||
} finally {
|
||||
isLoadingMore.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// Функция прокрутки к последнему сообщению
|
||||
const scrollToBottom = () => {
|
||||
if (messagesContainer.value) {
|
||||
messagesContainer.value.scrollTop = messagesContainer.value.scrollHeight;
|
||||
}
|
||||
};
|
||||
|
||||
// Инициализация при монтировании
|
||||
onMounted(async () => {
|
||||
console.log('HomeView.vue: onMounted called');
|
||||
console.log('Auth state:', auth.isAuthenticated);
|
||||
|
||||
// Определяем язык
|
||||
const cyrillicPattern = /[а-яА-ЯёЁ]/;
|
||||
userLanguage.value = cyrillicPattern.test(newMessage.value) ? 'ru' : 'en';
|
||||
console.log('Detected language:', userLanguage.value);
|
||||
|
||||
// Если пользователь уже аутентифицирован, загружаем историю
|
||||
if (auth.isAuthenticated && auth.userId) {
|
||||
console.log('User authenticated, loading chat history...');
|
||||
await loadChatHistory();
|
||||
// Загружаем сообщения при изменении аутентификации
|
||||
watch(() => auth.isAuthenticated, async (newValue) => {
|
||||
if (newValue) {
|
||||
messages.value = [];
|
||||
offset.value = 0;
|
||||
hasMoreMessages.value = true;
|
||||
|
||||
try {
|
||||
// Сначала загружаем историю из messages
|
||||
await loadMoreMessages();
|
||||
|
||||
// Связываем гостевые сообщения (копируем из guest_messages в messages)
|
||||
await api.post('/api/chat/link-guest-messages');
|
||||
console.log('Guest messages linked to authenticated user');
|
||||
|
||||
// Перезагружаем сообщения, чтобы получить все, включая перенесенные
|
||||
messages.value = [];
|
||||
offset.value = 0;
|
||||
await loadMoreMessages();
|
||||
|
||||
await nextTick();
|
||||
scrollToBottom();
|
||||
} catch (linkError) {
|
||||
console.error('Error linking guest messages:', linkError);
|
||||
}
|
||||
} else {
|
||||
messages.value = [];
|
||||
offset.value = 0;
|
||||
hasMoreMessages.value = true;
|
||||
}
|
||||
});
|
||||
|
||||
// Наблюдатель за изменением состояния аутентификации
|
||||
watch(() => auth.isAuthenticated, async (newValue, oldValue) => {
|
||||
console.log('Auth state changed in HomeView:', newValue);
|
||||
|
||||
if (newValue && auth.userId) {
|
||||
// Пользователь только что аутентифицировался
|
||||
await loadChatHistory();
|
||||
} else {
|
||||
// Пользователь вышел из системы
|
||||
messages.value = []; // Очищаем историю сообщений
|
||||
hasMoreMessages.value = false; // Сбрасываем флаг наличия дополнительных сообщений
|
||||
console.log('Chat history cleared after logout');
|
||||
}
|
||||
}, { immediate: true });
|
||||
|
||||
// Функция для подключения кошелька
|
||||
const handleWalletAuth = async () => {
|
||||
try {
|
||||
const result = await connectWithWallet();
|
||||
|
||||
if (result.success) {
|
||||
console.log('Wallet auth result:', result);
|
||||
// Сохраняем гостевые сообщения перед очисткой
|
||||
const guestMessages = [...messages.value];
|
||||
messages.value = [];
|
||||
offset.value = 0;
|
||||
hasMoreMessages.value = true;
|
||||
|
||||
// Обновляем состояние аутентификации
|
||||
auth.setAuth({
|
||||
authenticated: true,
|
||||
isAuthenticated: true,
|
||||
userId: result.userId,
|
||||
address: result.address,
|
||||
isAdmin: result.isAdmin,
|
||||
authType: 'wallet'
|
||||
});
|
||||
|
||||
// Добавляем задержку для синхронизации сессии
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
|
||||
// Загружаем историю чата
|
||||
await loadChatHistory();
|
||||
try {
|
||||
await api.post('/api/chat/link-guest-messages');
|
||||
console.log('Guest messages linked to authenticated user');
|
||||
await loadMoreMessages();
|
||||
|
||||
const filteredGuestMessages = guestMessages
|
||||
.filter(msg => !msg.showAuthButtons)
|
||||
.reverse();
|
||||
messages.value = [...messages.value, ...filteredGuestMessages];
|
||||
|
||||
await nextTick();
|
||||
scrollToBottom();
|
||||
} catch (linkError) {
|
||||
console.error('Error linking guest messages:', linkError);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
} catch (error) {
|
||||
console.error('Error connecting wallet:', error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -295,7 +286,7 @@ const saveGuestMessagesToServer = async () => {
|
||||
|
||||
// Отправляем каждое сообщение на сервер
|
||||
for (const msg of userMessages) {
|
||||
await axios.post('/api/chat/message', {
|
||||
await api.post('/api/chat/message', {
|
||||
message: msg.content,
|
||||
language: userLanguage.value
|
||||
});
|
||||
@@ -311,7 +302,7 @@ const saveGuestMessagesToServer = async () => {
|
||||
async function connectTelegram() {
|
||||
try {
|
||||
// Отправляем запрос на получение ссылки для авторизации через Telegram
|
||||
const response = await axios.get('/api/auth/telegram', {
|
||||
const response = await api.get('/api/auth/telegram', {
|
||||
withCredentials: true
|
||||
});
|
||||
|
||||
@@ -376,7 +367,7 @@ async function requestEmailCode() {
|
||||
// Функция проверки кода
|
||||
const verifyEmailCode = async () => {
|
||||
try {
|
||||
const response = await axios.post('/api/auth/email/verify-code', {
|
||||
const response = await api.post('/api/auth/email/verify-code', {
|
||||
email: emailInput.value,
|
||||
code: emailCode.value
|
||||
});
|
||||
@@ -387,7 +378,7 @@ const verifyEmailCode = async () => {
|
||||
emailError.value = '';
|
||||
|
||||
// Загружаем историю чата после успешной аутентификации
|
||||
await loadChatHistory();
|
||||
await loadMoreMessages();
|
||||
} else {
|
||||
emailError.value = response.data.error || 'Неверный код';
|
||||
}
|
||||
@@ -404,12 +395,6 @@ function cancelEmailVerification() {
|
||||
emailErrorMessage.value = '';
|
||||
}
|
||||
|
||||
// Добавьте эту функцию в <script setup>
|
||||
const formatAddress = (address) => {
|
||||
if (!address) return '';
|
||||
return address.substring(0, 6) + '...' + address.substring(address.length - 4);
|
||||
};
|
||||
|
||||
// Форматирование времени
|
||||
const formatTime = (timestamp) => {
|
||||
if (!timestamp) return '';
|
||||
@@ -438,101 +423,73 @@ const formatTime = (timestamp) => {
|
||||
};
|
||||
|
||||
// Функция для отправки сообщения
|
||||
const handleMessage = async (messageText) => {
|
||||
if (!messageText.trim() || isLoading.value) return;
|
||||
|
||||
console.log('Handling message:', messageText);
|
||||
isLoading.value = true;
|
||||
|
||||
const handleMessage = async (text) => {
|
||||
try {
|
||||
const messageContent = text.trim();
|
||||
if (!messageContent) return;
|
||||
|
||||
newMessage.value = '';
|
||||
isLoading.value = true;
|
||||
|
||||
if (!auth.isAuthenticated) {
|
||||
await sendGuestMessage(messageText);
|
||||
// Сохраняем в таблицу guest_messages
|
||||
const response = await api.post('/api/chat/guest-message', {
|
||||
message: messageContent,
|
||||
language: userLanguage.value
|
||||
});
|
||||
|
||||
if (response.data.success) {
|
||||
const userMessage = {
|
||||
id: response.data.messageId,
|
||||
content: messageContent,
|
||||
role: 'user',
|
||||
timestamp: new Date().toISOString(),
|
||||
showAuthButtons: false
|
||||
};
|
||||
messages.value.push(userMessage);
|
||||
|
||||
messages.value.push({
|
||||
id: Date.now() + 1,
|
||||
content: 'Для получения ответа от ассистента, пожалуйста, авторизуйтесь одним из способов:',
|
||||
role: 'assistant',
|
||||
timestamp: new Date().toISOString(),
|
||||
showAuthButtons: true
|
||||
});
|
||||
}
|
||||
} else {
|
||||
await sendMessage(messageText);
|
||||
// Для авторизованного пользователя сохраняем в messages
|
||||
const response = await api.post('/api/chat/message', {
|
||||
message: messageContent,
|
||||
language: userLanguage.value
|
||||
});
|
||||
|
||||
if (response.data.success) {
|
||||
const message = {
|
||||
id: response.data.messageId,
|
||||
content: messageContent,
|
||||
role: 'user',
|
||||
timestamp: new Date().toISOString(),
|
||||
hasResponse: true
|
||||
};
|
||||
messages.value.push(message);
|
||||
|
||||
const aiMessage = {
|
||||
id: response.data.aiMessageId,
|
||||
content: response.data.message,
|
||||
role: 'assistant',
|
||||
timestamp: new Date().toISOString()
|
||||
};
|
||||
messages.value.push(aiMessage);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error handling message:', error);
|
||||
console.error('Error sending message:', error);
|
||||
messages.value.push({
|
||||
id: Date.now(),
|
||||
content: 'Произошла ошибка при отправке сообщения.',
|
||||
role: 'assistant',
|
||||
timestamp: new Date().toISOString()
|
||||
});
|
||||
} finally {
|
||||
newMessage.value = '';
|
||||
isLoading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// Функция для отправки сообщения аутентифицированного пользователя
|
||||
const sendMessage = async (messageText) => {
|
||||
try {
|
||||
const userMessage = {
|
||||
id: Date.now(),
|
||||
content: messageText,
|
||||
role: 'user',
|
||||
timestamp: new Date().toISOString()
|
||||
};
|
||||
messages.value.push(userMessage);
|
||||
|
||||
const response = await axios.post('/api/chat/message', {
|
||||
message: messageText,
|
||||
language: userLanguage.value
|
||||
});
|
||||
|
||||
if (response.data.success) {
|
||||
messages.value.push({
|
||||
id: Date.now() + 1,
|
||||
content: response.data.message,
|
||||
role: 'assistant',
|
||||
timestamp: new Date().toISOString()
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error sending message:', error);
|
||||
}
|
||||
};
|
||||
|
||||
// Функция для отправки гостевого сообщения
|
||||
const sendGuestMessage = async (messageText) => {
|
||||
try {
|
||||
// Добавляем сообщение пользователя
|
||||
const userMessage = {
|
||||
id: Date.now(),
|
||||
content: messageText,
|
||||
role: 'user',
|
||||
timestamp: new Date().toISOString(),
|
||||
showAuthButtons: false
|
||||
};
|
||||
messages.value.push(userMessage);
|
||||
|
||||
// Очищаем поле ввода
|
||||
newMessage.value = '';
|
||||
|
||||
// Сохраняем сообщение на сервере без получения ответа от Ollama
|
||||
await axios.post('/api/chat/guest-message', {
|
||||
message: messageText,
|
||||
language: userLanguage.value
|
||||
});
|
||||
|
||||
// Добавляем сообщение с кнопками аутентификации
|
||||
messages.value.push({
|
||||
id: Date.now() + 1,
|
||||
content: 'Для получения ответа, пожалуйста, авторизуйтесь одним из способов:',
|
||||
role: 'assistant',
|
||||
timestamp: new Date().toISOString(),
|
||||
showAuthButtons: true
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error sending guest message:', error);
|
||||
messages.value.push({
|
||||
id: Date.now() + 2,
|
||||
content: 'Произошла ошибка. Пожалуйста, попробуйте позже.',
|
||||
role: 'assistant',
|
||||
timestamp: new Date().toISOString(),
|
||||
showAuthButtons: true
|
||||
});
|
||||
} finally {
|
||||
isLoading.value = false;
|
||||
}
|
||||
@@ -554,7 +511,7 @@ const handleEmailAuth = async () => {
|
||||
// Функция отправки email
|
||||
const submitEmail = async () => {
|
||||
try {
|
||||
const response = await axios.post('/api/auth/email/request', {
|
||||
const response = await api.post('/api/auth/email/request', {
|
||||
email: emailInput.value
|
||||
});
|
||||
|
||||
@@ -573,7 +530,7 @@ const submitEmail = async () => {
|
||||
// Функция верификации кода Telegram
|
||||
const verifyTelegramCode = async () => {
|
||||
try {
|
||||
const response = await axios.post('/api/auth/telegram/verify', {
|
||||
const response = await api.post('/api/auth/telegram/verify', {
|
||||
code: telegramCode.value
|
||||
});
|
||||
|
||||
@@ -602,7 +559,7 @@ const verifyTelegramCode = async () => {
|
||||
|
||||
// Загружаем историю чата после небольшой задержки
|
||||
setTimeout(async () => {
|
||||
await loadChatHistory();
|
||||
await loadMoreMessages();
|
||||
}, 100);
|
||||
} else {
|
||||
messages.value.push({
|
||||
@@ -622,6 +579,43 @@ const verifyTelegramCode = async () => {
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const disconnectWallet = async () => {
|
||||
try {
|
||||
await auth.disconnect();
|
||||
messages.value = [];
|
||||
offset.value = 0;
|
||||
hasMoreMessages.value = true;
|
||||
} catch (error) {
|
||||
console.error('Error disconnecting wallet:', error);
|
||||
}
|
||||
};
|
||||
|
||||
// Обработка прокрутки
|
||||
const handleScroll = async () => {
|
||||
const element = messagesContainer.value;
|
||||
if (
|
||||
!isLoadingMore.value &&
|
||||
hasMoreMessages.value &&
|
||||
element.scrollTop === 0
|
||||
) {
|
||||
await loadMoreMessages();
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
// Добавляем слушатель прокрутки
|
||||
if (messagesContainer.value) {
|
||||
messagesContainer.value.addEventListener('scroll', handleScroll);
|
||||
}
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
// Удаляем слушатель
|
||||
if (messagesContainer.value) {
|
||||
messagesContainer.value.removeEventListener('scroll', handleScroll);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
@@ -665,64 +659,49 @@ h1 {
|
||||
}
|
||||
|
||||
.chat-header {
|
||||
padding: 1rem;
|
||||
border-bottom: 1px solid #ddd;
|
||||
background-color: #f8f9fa;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
padding: 10px 20px;
|
||||
background-color: #f0f0f0;
|
||||
border-bottom: 1px solid #ccc;
|
||||
}
|
||||
|
||||
/* Адаптивный заголовок чата */
|
||||
@media (max-width: 768px) {
|
||||
.chat-header {
|
||||
padding: 8px 12px;
|
||||
}
|
||||
|
||||
.chat-header h2 {
|
||||
font-size: 1.2rem;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.user-info {
|
||||
.wallet-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
font-size: 0.9rem;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
/* Адаптивная информация о пользователе */
|
||||
@media (max-width: 768px) {
|
||||
.user-info {
|
||||
font-size: 0.7rem;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
.user-info span {
|
||||
max-width: 120px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
|
||||
.logout-btn {
|
||||
padding: 5px 10px;
|
||||
background-color: #f44336;
|
||||
.disconnect-btn {
|
||||
padding: 0.5rem 1rem;
|
||||
background-color: #ff4444;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
/* Адаптивная кнопка выхода */
|
||||
@media (max-width: 768px) {
|
||||
.logout-btn {
|
||||
padding: 4px 8px;
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
.disconnect-btn:hover {
|
||||
background-color: #cc0000;
|
||||
}
|
||||
|
||||
.load-more {
|
||||
text-align: center;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.load-more button {
|
||||
padding: 0.5rem 1rem;
|
||||
background-color: #007bff;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.load-more button:hover {
|
||||
background-color: #0056b3;
|
||||
}
|
||||
|
||||
.chat-messages {
|
||||
@@ -915,32 +894,27 @@ h1 {
|
||||
}
|
||||
|
||||
.auth-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0.75rem 1rem;
|
||||
padding: 8px 16px;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 1rem;
|
||||
border: none;
|
||||
width: 100%;
|
||||
font-weight: 500;
|
||||
transition: opacity 0.2s;
|
||||
box-sizing: border-box;
|
||||
font-size: 14px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.auth-btn:hover {
|
||||
opacity: 0.9;
|
||||
.wallet-btn {
|
||||
background-color: #4a5568;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.auth-btn:disabled {
|
||||
opacity: 0.7;
|
||||
cursor: not-allowed;
|
||||
.wallet-btn:hover {
|
||||
background-color: #2d3748;
|
||||
}
|
||||
|
||||
.auth-icon {
|
||||
margin-right: 0.75rem;
|
||||
font-size: 1.2rem;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.telegram-btn {
|
||||
@@ -1080,4 +1054,48 @@ h1 {
|
||||
background-color: #cbd5e0;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.wallet-section {
|
||||
margin-top: 20px;
|
||||
padding: 20px;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 8px;
|
||||
background-color: #f9f9f9;
|
||||
}
|
||||
|
||||
.wallet-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.disconnect-btn {
|
||||
padding: 0.5rem 1rem;
|
||||
background-color: #ff4444;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.disconnect-btn:hover {
|
||||
background-color: #cc0000;
|
||||
}
|
||||
|
||||
.chat-history {
|
||||
height: 60vh;
|
||||
overflow-y: auto;
|
||||
padding: 1rem;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
/* Добавим индикатор загрузки */
|
||||
.loading {
|
||||
text-align: center;
|
||||
padding: 1rem;
|
||||
color: #666;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,138 +0,0 @@
|
||||
<template>
|
||||
<div class="profile">
|
||||
<h1>Профиль пользователя</h1>
|
||||
|
||||
<div class="profile-info">
|
||||
<div class="profile-section">
|
||||
<h2>Основная информация</h2>
|
||||
<div v-if="loading">Загрузка...</div>
|
||||
<div v-else-if="error">{{ error }}</div>
|
||||
<div v-else>
|
||||
<p><strong>ID:</strong> {{ profile.id }}</p>
|
||||
<p><strong>Имя пользователя:</strong> {{ profile.username || 'Не указано' }}</p>
|
||||
<p><strong>Роль:</strong> {{ profile.role === 'admin' ? 'Администратор' : 'Пользователь' }}</p>
|
||||
<p><strong>Язык интерфейса:</strong>
|
||||
<select v-model="selectedLanguage" @change="updateLanguage">
|
||||
<option value="ru">Русский</option>
|
||||
<option value="en">English</option>
|
||||
</select>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="profile-section">
|
||||
<h2>Связанные аккаунты</h2>
|
||||
<LinkedAccounts />
|
||||
</div>
|
||||
|
||||
<div class="profile-section" v-if="isAdmin">
|
||||
<h2>Управление ролями</h2>
|
||||
<RoleManager />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref, onMounted, computed } from 'vue';
|
||||
import axios from 'axios';
|
||||
import LinkedAccounts from '../components/LinkedAccounts.vue';
|
||||
import RoleManager from '../components/RoleManager.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
LinkedAccounts,
|
||||
RoleManager
|
||||
},
|
||||
|
||||
setup() {
|
||||
const profile = ref({});
|
||||
const loading = ref(true);
|
||||
const error = ref(null);
|
||||
const selectedLanguage = ref('ru');
|
||||
const isAdmin = ref(false);
|
||||
|
||||
// Загрузка профиля пользователя
|
||||
const loadProfile = async () => {
|
||||
try {
|
||||
loading.value = true;
|
||||
const response = await axios.get('/api/users/profile', {
|
||||
withCredentials: true
|
||||
});
|
||||
profile.value = response.data;
|
||||
selectedLanguage.value = response.data.preferred_language || 'ru';
|
||||
isAdmin.value = response.data.role === 'admin';
|
||||
} catch (err) {
|
||||
console.error('Error loading profile:', err);
|
||||
error.value = 'Ошибка при загрузке профиля';
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// Обновление языка пользователя
|
||||
const updateLanguage = async () => {
|
||||
try {
|
||||
await axios.post('/api/users/update-language', {
|
||||
language: selectedLanguage.value
|
||||
}, {
|
||||
withCredentials: true
|
||||
});
|
||||
// Обновляем язык в профиле
|
||||
profile.value.preferred_language = selectedLanguage.value;
|
||||
} catch (err) {
|
||||
console.error('Error updating language:', err);
|
||||
error.value = 'Ошибка при обновлении языка';
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(async () => {
|
||||
await loadProfile();
|
||||
});
|
||||
|
||||
return {
|
||||
profile,
|
||||
loading,
|
||||
error,
|
||||
selectedLanguage,
|
||||
isAdmin,
|
||||
updateLanguage
|
||||
};
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.profile {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.profile-info {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.profile-info {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
.profile-section {
|
||||
background-color: #f5f5f5;
|
||||
padding: 20px;
|
||||
border-radius: 5px;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
h1, h2 {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
select {
|
||||
padding: 5px;
|
||||
border-radius: 3px;
|
||||
border: 1px solid #ccc;
|
||||
}
|
||||
</style>
|
||||
@@ -1,75 +0,0 @@
|
||||
<template>
|
||||
<div class="token-access">
|
||||
<h1>Настройка прав доступа</h1>
|
||||
<div>
|
||||
<label for="blockchain">Выберите блокчейн:</label>
|
||||
<select v-model="selectedBlockchain" id="blockchain">
|
||||
<option v-for="(blockchain, index) in blockchains" :key="index" :value="blockchain.value">
|
||||
{{ blockchain.name }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
<form @submit.prevent="checkTokenBalance">
|
||||
<div>
|
||||
<label for="contractAddress">Адрес смарт-контракта:</label>
|
||||
<input v-model="contractAddress" id="contractAddress" placeholder="Введите адрес смарт-контракта" required />
|
||||
</div>
|
||||
<div>
|
||||
<label for="requiredAmount">Объем токенов:</label>
|
||||
<input v-model="requiredAmount" type="number" id="requiredAmount" placeholder="Введите объем токенов" required />
|
||||
</div>
|
||||
<button type="submit">Проверить баланс</button>
|
||||
</form>
|
||||
<p v-if="message">{{ message }}</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
selectedBlockchain: 'mainnet', // Значение по умолчанию
|
||||
blockchains: [
|
||||
{ name: 'Ethereum', value: 'mainnet' },
|
||||
{ name: 'Polygon', value: 'polygon' },
|
||||
{ name: 'Binance Smart Chain', value: 'bsc' },
|
||||
{ name: 'Arbitrum', value: 'arbitrum' }
|
||||
],
|
||||
contractAddress: '',
|
||||
requiredAmount: '',
|
||||
message: ''
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
async checkTokenBalance() {
|
||||
const response = await fetch(`/api/admin/check-balance`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
walletAddress: this.$store.state.walletAddress, // Предполагается, что адрес кошелька хранится в хранилище
|
||||
contractAddress: this.contractAddress,
|
||||
requiredAmount: this.requiredAmount,
|
||||
blockchain: this.selectedBlockchain
|
||||
})
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
this.message = data.message;
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.token-access {
|
||||
max-width: 400px;
|
||||
margin: auto;
|
||||
padding: 20px;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 5px;
|
||||
background-color: #f9f9f9;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -38,11 +38,15 @@ export default defineConfig({
|
||||
},
|
||||
server: {
|
||||
port: 5173,
|
||||
host: 'localhost',
|
||||
proxy: {
|
||||
'/api': {
|
||||
target: 'http://localhost:8000',
|
||||
changeOrigin: true
|
||||
changeOrigin: true,
|
||||
secure: false,
|
||||
credentials: true,
|
||||
rewrite: (path) => path
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user