Описание изменений

This commit is contained in:
2025-03-19 17:18:03 +03:00
parent 2831527544
commit 87bad93eac
75 changed files with 2103 additions and 4861 deletions

View File

@@ -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>

View File

@@ -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;

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View 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>

View File

@@ -0,0 +1,9 @@
import TelegramConnect from './TelegramConnect.vue';
import WalletConnection from './WalletConnection.vue';
import EmailConnect from './EmailConnect.vue';
export {
TelegramConnect,
WalletConnection,
EmailConnect
};

View File

@@ -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,
};
}

View File

@@ -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' });
}

View 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);
}
}

View File

@@ -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
});
}
}

View File

@@ -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;
}
}

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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
}
},
}
},
});