ваше сообщение коммита
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -19,3 +19,21 @@
|
||||
{"address":"0xf45aa4917b3775ba37f48aeb3dc1a943561e9e0b","contract":"0x4B294265720B09ca39BFBA18c7E368413c0f68eB","error":"Unknown error","level":"error","message":"Error getting balance for bsc:","timestamp":"2025-04-21T08:02:13.552Z"}
|
||||
{"address":"0xf45aa4917b3775ba37f48aeb3dc1a943561e9e0b","contract":"0xdce769b847a0a697239777d0b1c7dd33b6012ba0","error":"Unknown error","level":"error","message":"Error getting balance for arbitrum:","timestamp":"2025-04-21T08:02:13.567Z"}
|
||||
{"address":"0xf45aa4917b3775ba37f48aeb3dc1a943561e9e0b","contract":"0x351f59de4fedbdf7601f5592b93db3b9330c1c1d","error":"Unknown error","level":"error","message":"Error getting balance for polygon:","timestamp":"2025-04-21T08:02:13.580Z"}
|
||||
{"level":"error","message":"Provider for eth is not available: Network check timeout","timestamp":"2025-04-21T08:39:48.816Z"}
|
||||
{"level":"error","message":"Provider for bsc is not available: Network check timeout","timestamp":"2025-04-21T08:39:48.817Z"}
|
||||
{"level":"error","message":"Provider for arbitrum is not available: Network check timeout","timestamp":"2025-04-21T08:39:48.817Z"}
|
||||
{"level":"error","message":"Provider for polygon is not available: Network check timeout","timestamp":"2025-04-21T08:39:48.817Z"}
|
||||
{"level":"error","message":"All network checks for 0xf45aa4917b3775ba37f48aeb3dc1a943561e9e0b failed. Cannot verify admin status.","timestamp":"2025-04-21T08:39:48.818Z"}
|
||||
{"level":"error","message":"Provider for arbitrum is not available: Network check timeout","timestamp":"2025-04-21T08:39:51.831Z"}
|
||||
{"level":"error","message":"Provider for polygon is not available: Network check timeout","timestamp":"2025-04-21T08:40:02.425Z"}
|
||||
{"level":"error","message":"Provider for eth is not available: Network check timeout","timestamp":"2025-04-21T08:40:05.437Z"}
|
||||
{"level":"error","message":"Provider for polygon is not available: Network check timeout","timestamp":"2025-04-21T11:54:11.804Z"}
|
||||
{"level":"error","message":"Provider for polygon is not available: Network check timeout","timestamp":"2025-04-21T12:08:10.148Z"}
|
||||
{"level":"error","message":"Provider for bsc is not available: Network check timeout","timestamp":"2025-04-21T12:13:07.439Z"}
|
||||
{"level":"error","message":"Provider for arbitrum is not available: Network check timeout","timestamp":"2025-04-21T12:13:12.799Z"}
|
||||
{"level":"error","message":"Provider for eth is not available: Network check timeout","timestamp":"2025-04-21T12:23:30.146Z"}
|
||||
{"level":"error","message":"Provider for bsc is not available: Network check timeout","timestamp":"2025-04-21T12:23:30.147Z"}
|
||||
{"level":"error","message":"Provider for arbitrum is not available: Network check timeout","timestamp":"2025-04-21T12:23:30.147Z"}
|
||||
{"level":"error","message":"Provider for polygon is not available: Network check timeout","timestamp":"2025-04-21T12:36:03.317Z"}
|
||||
{"level":"error","message":"Provider for bsc is not available: Network check timeout","timestamp":"2025-04-21T13:48:41.033Z"}
|
||||
{"level":"error","message":"Provider for polygon is not available: Network check timeout","timestamp":"2025-04-21T13:48:48.026Z"}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import globals from 'globals';
|
||||
import * as vueParser from 'vue-eslint-parser';
|
||||
import vuePlugin from 'eslint-plugin-vue';
|
||||
import prettierPlugin from 'eslint-plugin-prettier';
|
||||
import prettierConfig from '@vue/eslint-config-prettier';
|
||||
import eslintConfigPrettier from 'eslint-config-prettier';
|
||||
|
||||
export default [
|
||||
{
|
||||
@@ -35,29 +36,37 @@ export default [
|
||||
...globals.browser,
|
||||
...globals.es2021,
|
||||
},
|
||||
parser: vuePlugin.parser,
|
||||
parser: vueParser,
|
||||
parserOptions: {
|
||||
ecmaFeatures: {
|
||||
jsx: true,
|
||||
},
|
||||
sourceType: 'module',
|
||||
ecmaVersion: 2022,
|
||||
},
|
||||
},
|
||||
plugins: {
|
||||
vue: vuePlugin,
|
||||
prettier: prettierPlugin,
|
||||
},
|
||||
processor: vuePlugin.processors['.vue'],
|
||||
rules: {
|
||||
...prettierConfig.rules,
|
||||
...vuePlugin.configs.base.rules,
|
||||
...vuePlugin.configs['vue3-essential'].rules,
|
||||
...vuePlugin.configs['vue3-strongly-recommended'].rules,
|
||||
...vuePlugin.configs['vue3-recommended'].rules,
|
||||
...eslintConfigPrettier.rules,
|
||||
'prettier/prettier': 'warn',
|
||||
'vue/comment-directive': 'off',
|
||||
'vue/multi-word-component-names': 'off',
|
||||
'vue/no-unused-vars': 'warn',
|
||||
'vue/html-self-closing': ['warn', {
|
||||
'vue/no-v-html': 'off',
|
||||
'vue/html-self-closing': [
|
||||
'warn',
|
||||
{
|
||||
html: {
|
||||
void: 'always',
|
||||
normal: 'always',
|
||||
component: 'always'
|
||||
}
|
||||
}],
|
||||
component: 'always',
|
||||
},
|
||||
},
|
||||
],
|
||||
'vue/component-name-in-template-casing': ['warn', 'PascalCase'],
|
||||
},
|
||||
},
|
||||
|
||||
@@ -1,137 +1,28 @@
|
||||
<template>
|
||||
<div id="app">
|
||||
<router-view />
|
||||
<div v-if="isLoading" class="loading-overlay">
|
||||
<div class="loading-spinner" />
|
||||
</div>
|
||||
|
||||
<RouterView />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { onMounted, ref, provide, computed } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import axios from 'axios';
|
||||
import { ref, watch } from 'vue';
|
||||
import { RouterView } from 'vue-router';
|
||||
import { useAuth } from './composables/useAuth';
|
||||
import './assets/styles/home.css';
|
||||
|
||||
console.log('App.vue: Version with auth check loaded');
|
||||
// Состояние загрузки
|
||||
const isLoading = ref(false);
|
||||
|
||||
const router = useRouter();
|
||||
// Использование composable для аутентификации
|
||||
const { isAuthenticated } = useAuth();
|
||||
|
||||
// Создаем реактивное состояние с помощью ref
|
||||
const authState = ref({
|
||||
isAuthenticated: false,
|
||||
userRole: null,
|
||||
address: null
|
||||
});
|
||||
|
||||
// Предоставляем состояние аутентификации всем компонентам
|
||||
const auth = {
|
||||
// Используем computed для реактивности
|
||||
isAuthenticated: computed(() => authState.value.isAuthenticated),
|
||||
userRole: computed(() => authState.value.userRole),
|
||||
address: computed(() => authState.value.address),
|
||||
async checkAuth() {
|
||||
try {
|
||||
const response = await axios.get('/api/auth/check');
|
||||
console.log('Auth check response:', response.data);
|
||||
authState.value = {
|
||||
isAuthenticated: response.data.authenticated,
|
||||
userRole: response.data.role,
|
||||
address: response.data.address
|
||||
};
|
||||
console.log('Auth state updated:', authState.value);
|
||||
} catch (error) {
|
||||
console.error('Auth check failed:', error);
|
||||
watch(isAuthenticated, (newValue, oldValue) => {
|
||||
if (newValue !== oldValue) {
|
||||
console.log('Состояние аутентификации изменилось:', newValue);
|
||||
}
|
||||
},
|
||||
async disconnect() {
|
||||
try {
|
||||
await axios.post('/api/auth/logout');
|
||||
authState.value = {
|
||||
isAuthenticated: false,
|
||||
userRole: null,
|
||||
address: null
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Logout failed:', error);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
provide('auth', auth);
|
||||
|
||||
onMounted(async () => {
|
||||
await auth.checkAuth();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: 'Roboto', 'Helvetica Neue', Arial, sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
color: #333;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
#app {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.main-content {
|
||||
flex: 1;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
button {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.btn {
|
||||
padding: 0.5rem 1rem;
|
||||
border-radius: 4px;
|
||||
font-weight: 500;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background-color: #3498db;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background-color: #95a5a6;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-danger {
|
||||
background-color: #e74c3c;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.loading-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba(255, 255, 255, 0.8);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
z-index: 9999;
|
||||
}
|
||||
|
||||
.loading-spinner {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
border: 5px solid #f3f3f3;
|
||||
border-top: 5px solid #3498db;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -15,8 +15,8 @@ const api = axios.create({
|
||||
baseURL: getBaseUrl(),
|
||||
withCredentials: true,
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
|
||||
// Перехватчик запросов
|
||||
@@ -50,7 +50,7 @@ const sendGuestMessageToServer = async (messageText) => {
|
||||
try {
|
||||
await axios.post('/api/chat/guest-message', {
|
||||
message: messageText,
|
||||
language: userLanguage.value
|
||||
// language: userLanguage.value, // TODO: Реализовать получение языка пользователя
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Ошибка при отправке гостевого сообщения на сервер:', error);
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -3,7 +3,7 @@
|
||||
<div class="conversation-list">
|
||||
<div class="list-header">
|
||||
<h3>Диалоги</h3>
|
||||
<button @click="createNewConversation" class="new-conversation-btn">
|
||||
<button class="new-conversation-btn" @click="createNewConversation">
|
||||
<span>+</span> Новый диалог
|
||||
</button>
|
||||
</div>
|
||||
@@ -52,12 +52,15 @@ const loading = ref(true);
|
||||
const selectedConversationId = ref(null);
|
||||
|
||||
// Следим за изменением статуса аутентификации
|
||||
watch(() => isAuthenticated.value, (authenticated) => {
|
||||
watch(
|
||||
() => isAuthenticated.value,
|
||||
(authenticated) => {
|
||||
if (!authenticated) {
|
||||
conversations.value = []; // Очищаем список бесед при отключении
|
||||
selectedConversationId.value = null;
|
||||
}
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
// Загрузка списка диалогов
|
||||
const fetchConversations = async () => {
|
||||
@@ -144,101 +147,3 @@ defineExpose({
|
||||
fetchConversations,
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.conversation-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 300px;
|
||||
border-right: 1px solid #e0e0e0;
|
||||
background-color: #f9f9f9;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.list-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 1rem;
|
||||
border-bottom: 1px solid #e0e0e0;
|
||||
}
|
||||
|
||||
.list-header h3 {
|
||||
margin: 0;
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
.new-conversation-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background-color: #4caf50;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
padding: 0.5rem 0.75rem;
|
||||
cursor: pointer;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.new-conversation-btn span {
|
||||
font-size: 1.2rem;
|
||||
margin-right: 0.25rem;
|
||||
}
|
||||
|
||||
.loading,
|
||||
.empty-list {
|
||||
padding: 2rem;
|
||||
text-align: center;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.empty-list p {
|
||||
margin: 0.5rem 0;
|
||||
}
|
||||
|
||||
.conversations {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.conversation-item {
|
||||
padding: 1rem;
|
||||
border-bottom: 1px solid #e0e0e0;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
|
||||
.conversation-item:hover {
|
||||
background-color: #f0f0f0;
|
||||
}
|
||||
|
||||
.conversation-item.active {
|
||||
background-color: #e8f5e9;
|
||||
color: #4caf50;
|
||||
}
|
||||
|
||||
.conversation-title {
|
||||
font-weight: 500;
|
||||
margin-bottom: 0.5rem;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.conversation-meta {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
font-size: 0.8rem;
|
||||
color: #888;
|
||||
}
|
||||
|
||||
.time {
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
.connect-wallet-prompt {
|
||||
text-align: center;
|
||||
padding: 2rem;
|
||||
color: #666;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
<template>
|
||||
<div class="message-input">
|
||||
<textarea
|
||||
ref="textareaRef"
|
||||
v-model="message"
|
||||
placeholder="Введите сообщение..."
|
||||
@keydown.enter.prevent="handleEnter"
|
||||
ref="textareaRef"
|
||||
:disabled="sending"
|
||||
></textarea>
|
||||
@keydown.enter.prevent="handleEnter"
|
||||
/>
|
||||
|
||||
<button @click="sendMessage" class="send-button" :disabled="!message.trim() || sending">
|
||||
<button class="send-button" :disabled="!message.trim() || sending" @click="sendMessage">
|
||||
<span v-if="sending">Отправка...</span>
|
||||
<span v-else>Отправить</span>
|
||||
</button>
|
||||
@@ -51,7 +51,7 @@ const sendMessage = async () => {
|
||||
id: Date.now(),
|
||||
content: messageText,
|
||||
role: auth.isAuthenticated ? 'user' : 'guest',
|
||||
timestamp: new Date().toISOString()
|
||||
timestamp: new Date().toISOString(),
|
||||
};
|
||||
|
||||
messages.value.push(userMessage);
|
||||
@@ -60,12 +60,12 @@ const sendMessage = async () => {
|
||||
// Логируем параметры запроса
|
||||
console.log('Sending message to Ollama:', {
|
||||
message: messageText,
|
||||
language: userLanguage.value
|
||||
language: userLanguage.value,
|
||||
});
|
||||
|
||||
const response = await axios.post('/api/chat/message', {
|
||||
message: messageText,
|
||||
language: userLanguage.value
|
||||
language: userLanguage.value,
|
||||
});
|
||||
|
||||
// Логируем ответ от Ollama
|
||||
@@ -76,7 +76,7 @@ const sendMessage = async () => {
|
||||
id: Date.now() + 1,
|
||||
content: response.data.message,
|
||||
role: 'assistant',
|
||||
timestamp: new Date().toISOString()
|
||||
timestamp: new Date().toISOString(),
|
||||
});
|
||||
|
||||
// Очищаем поле ввода
|
||||
@@ -115,7 +115,7 @@ const sendGuestMessage = async (messageText) => {
|
||||
content: messageText,
|
||||
role: 'user',
|
||||
timestamp: new Date().toISOString(),
|
||||
isGuest: true
|
||||
isGuest: true,
|
||||
};
|
||||
|
||||
// Добавляем сообщение пользователя в локальную историю
|
||||
@@ -146,7 +146,7 @@ const sendGuestMessage = async (messageText) => {
|
||||
role: 'assistant',
|
||||
timestamp: new Date().toISOString(),
|
||||
isGuest: true,
|
||||
showAuthOptions: true // Указываем, что нужно показать кнопки аутентификации
|
||||
showAuthOptions: true, // Указываем, что нужно показать кнопки аутентификации
|
||||
};
|
||||
|
||||
messages.value.push(authMessage);
|
||||
@@ -162,53 +162,3 @@ const sendGuestMessage = async (messageText) => {
|
||||
isLoading.value = false;
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.message-input {
|
||||
display: flex;
|
||||
padding: 1rem;
|
||||
border-top: 1px solid #e0e0e0;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
textarea {
|
||||
flex: 1;
|
||||
min-height: 40px;
|
||||
max-height: 120px;
|
||||
padding: 0.75rem;
|
||||
border: 1px solid #e0e0e0;
|
||||
border-radius: 4px;
|
||||
resize: none;
|
||||
font-family: inherit;
|
||||
font-size: 0.9rem;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
textarea:focus {
|
||||
outline: none;
|
||||
border-color: #4caf50;
|
||||
box-shadow: 0 0 0 2px rgba(76, 175, 80, 0.2);
|
||||
}
|
||||
|
||||
.send-button {
|
||||
margin-left: 0.5rem;
|
||||
padding: 0 1rem;
|
||||
height: 40px;
|
||||
background-color: #4caf50;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-weight: 500;
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
|
||||
.send-button:hover:not(:disabled) {
|
||||
background-color: #43a047;
|
||||
}
|
||||
|
||||
.send-button:disabled {
|
||||
background-color: #9e9e9e;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div v-if="isAuthenticated">
|
||||
<div class="message-thread" ref="threadContainer">
|
||||
<div ref="threadContainer" class="message-thread">
|
||||
<div v-if="loading" class="loading">Загрузка сообщений...</div>
|
||||
|
||||
<div v-else-if="messages.length === 0" class="empty-thread">
|
||||
@@ -8,7 +8,11 @@
|
||||
</div>
|
||||
|
||||
<div v-else class="messages">
|
||||
<div v-for="message in messages" :key="message.id" :class="['message', message.sender_type]">
|
||||
<div
|
||||
v-for="message in messages"
|
||||
:key="message.id"
|
||||
:class="['message', message.sender_type]"
|
||||
>
|
||||
<div class="message-content">{{ message.content }}</div>
|
||||
<div class="message-meta">
|
||||
<span class="time">{{ formatTime(message.created_at) }}</span>
|
||||
@@ -111,11 +115,14 @@ watch(
|
||||
);
|
||||
|
||||
// Следим за изменением статуса аутентификации
|
||||
watch(() => isAuthenticated.value, (authenticated) => {
|
||||
watch(
|
||||
() => isAuthenticated.value,
|
||||
(authenticated) => {
|
||||
if (!authenticated) {
|
||||
messages.value = []; // Очищаем сообщения при отключении
|
||||
}
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
// Загрузка сообщений при монтировании компонента
|
||||
onMounted(() => {
|
||||
@@ -130,84 +137,3 @@ defineExpose({
|
||||
addMessages,
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.message-thread {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: 1rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.loading,
|
||||
.empty-thread {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.messages {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.message {
|
||||
max-width: 80%;
|
||||
padding: 0.75rem 1rem;
|
||||
border-radius: 8px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.message.user {
|
||||
align-self: flex-end;
|
||||
background-color: #e3f2fd;
|
||||
border: 1px solid #bbdefb;
|
||||
}
|
||||
|
||||
.message.ai {
|
||||
align-self: flex-start;
|
||||
background-color: #f5f5f5;
|
||||
border: 1px solid #e0e0e0;
|
||||
}
|
||||
|
||||
.message.admin {
|
||||
align-self: flex-start;
|
||||
background-color: #fff3e0;
|
||||
border: 1px dashed #ffb74d;
|
||||
}
|
||||
|
||||
.message-content {
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.message-meta {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 0.5rem;
|
||||
margin-top: 0.25rem;
|
||||
font-size: 0.7rem;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.time {
|
||||
font-size: 0.7rem;
|
||||
}
|
||||
|
||||
.channel {
|
||||
font-size: 0.7rem;
|
||||
padding: 0.1rem 0.3rem;
|
||||
border-radius: 3px;
|
||||
background-color: #f0f0f0;
|
||||
}
|
||||
|
||||
.connect-wallet-prompt {
|
||||
text-align: center;
|
||||
padding: 2rem;
|
||||
color: #666;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,41 +1,18 @@
|
||||
<template>
|
||||
<div class="email-connection">
|
||||
<div v-if="!showVerification" class="email-form">
|
||||
<input
|
||||
v-model="email"
|
||||
type="email"
|
||||
placeholder="Введите email"
|
||||
class="email-input"
|
||||
/>
|
||||
<button
|
||||
@click="requestCode"
|
||||
:disabled="isLoading || !isValidEmail"
|
||||
class="email-btn"
|
||||
>
|
||||
<input v-model="email" type="email" placeholder="Введите email" class="email-input" />
|
||||
<button :disabled="isLoading || !isValidEmail" class="email-btn" @click="requestCode">
|
||||
{{ isLoading ? 'Отправка...' : 'Получить код' }}
|
||||
</button>
|
||||
</div>
|
||||
<div v-else class="verification-form">
|
||||
<p class="verification-info">Код отправлен на {{ email }}</p>
|
||||
<input
|
||||
v-model="code"
|
||||
type="text"
|
||||
placeholder="Введите код"
|
||||
class="code-input"
|
||||
/>
|
||||
<button
|
||||
@click="verifyCode"
|
||||
:disabled="isLoading || !code"
|
||||
class="verify-btn"
|
||||
>
|
||||
<input v-model="code" type="text" placeholder="Введите код" class="code-input" />
|
||||
<button :disabled="isLoading || !code" class="verify-btn" @click="verifyCode">
|
||||
{{ isLoading ? 'Проверка...' : 'Подтвердить' }}
|
||||
</button>
|
||||
<button
|
||||
@click="resetForm"
|
||||
class="reset-btn"
|
||||
>
|
||||
Изменить email
|
||||
</button>
|
||||
<button class="reset-btn" @click="resetForm">Изменить email</button>
|
||||
</div>
|
||||
<div v-if="error" class="error">{{ error }}</div>
|
||||
</div>
|
||||
@@ -65,7 +42,7 @@ const requestCode = async () => {
|
||||
error.value = '';
|
||||
|
||||
const response = await axios.post('/api/auth/email/request-verification', {
|
||||
email: email.value
|
||||
email: email.value,
|
||||
});
|
||||
|
||||
if (response.data.success) {
|
||||
@@ -87,7 +64,7 @@ const verifyCode = async () => {
|
||||
|
||||
const response = await axios.post('/api/auth/email/verify', {
|
||||
email: email.value,
|
||||
code: code.value
|
||||
code: code.value,
|
||||
});
|
||||
|
||||
if (response.data.success) {
|
||||
@@ -111,63 +88,3 @@ const resetForm = () => {
|
||||
showVerification.value = false;
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.email-connection {
|
||||
padding: 20px;
|
||||
max-width: 400px;
|
||||
}
|
||||
|
||||
.email-form,
|
||||
.verification-form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.email-input,
|
||||
.code-input {
|
||||
padding: 8px 12px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.email-btn,
|
||||
.verify-btn,
|
||||
.reset-btn {
|
||||
padding: 10px;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.email-btn,
|
||||
.verify-btn {
|
||||
background-color: #48bb78;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.reset-btn {
|
||||
background-color: #e2e8f0;
|
||||
color: #4a5568;
|
||||
}
|
||||
|
||||
.verification-info {
|
||||
color: #4a5568;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.error {
|
||||
color: #e53e3e;
|
||||
margin-top: 5px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
button:disabled {
|
||||
opacity: 0.7;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<div class="telegram-connect">
|
||||
<div v-if="!showQR" class="intro">
|
||||
<p>Подключите свой аккаунт Telegram для быстрой авторизации</p>
|
||||
<button @click="startConnection" class="connect-button" :disabled="loading">
|
||||
<button class="connect-button" :disabled="loading" @click="startConnection">
|
||||
<span class="telegram-icon">📱</span>
|
||||
{{ loading ? 'Загрузка...' : 'Подключить Telegram' }}
|
||||
</button>
|
||||
@@ -10,14 +10,11 @@
|
||||
|
||||
<div v-else class="qr-section">
|
||||
<p>Отсканируйте QR-код в приложении Telegram</p>
|
||||
<div class="qr-container" v-html="qrCode"></div>
|
||||
<!-- eslint-disable-next-line vue/no-v-html -->
|
||||
<div class="qr-container" v-html="qrCode" />
|
||||
<p class="or-divider">или</p>
|
||||
<a :href="botLink" target="_blank" class="bot-link">
|
||||
Открыть бота в Telegram
|
||||
</a>
|
||||
<button @click="resetConnection" class="reset-button">
|
||||
Отмена
|
||||
</button>
|
||||
<a :href="botLink" target="_blank" class="bot-link"> Открыть бота в Telegram </a>
|
||||
<button class="reset-button" @click="resetConnection">Отмена</button>
|
||||
</div>
|
||||
|
||||
<div v-if="error" class="error">{{ error }}</div>
|
||||
@@ -71,7 +68,7 @@ const startConnection = async () => {
|
||||
const checkConnection = async () => {
|
||||
try {
|
||||
const response = await axios.post('/api/auth/telegram/check-connection', {
|
||||
token: connectionToken.value
|
||||
token: connectionToken.value,
|
||||
});
|
||||
|
||||
if (response.data.success && response.data.telegramId) {
|
||||
@@ -109,99 +106,3 @@ onUnmounted(() => {
|
||||
stopPolling();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.telegram-connect {
|
||||
padding: 20px;
|
||||
max-width: 400px;
|
||||
}
|
||||
|
||||
.intro,
|
||||
.qr-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 15px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.connect-button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 10px 20px;
|
||||
background-color: #0088cc;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 16px;
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
|
||||
.connect-button:hover:not(:disabled) {
|
||||
background-color: #0077b5;
|
||||
}
|
||||
|
||||
.telegram-icon {
|
||||
margin-right: 10px;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.qr-container {
|
||||
background: white;
|
||||
padding: 10px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.qr-container img {
|
||||
max-width: 200px;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.or-divider {
|
||||
color: #666;
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
.bot-link {
|
||||
color: #0088cc;
|
||||
text-decoration: none;
|
||||
padding: 8px 16px;
|
||||
border: 1px solid #0088cc;
|
||||
border-radius: 4px;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.bot-link:hover {
|
||||
background-color: #0088cc;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.reset-button {
|
||||
padding: 8px 16px;
|
||||
background-color: #e2e8f0;
|
||||
color: #4a5568;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
|
||||
.reset-button:hover {
|
||||
background-color: #cbd5e0;
|
||||
}
|
||||
|
||||
.error {
|
||||
color: #e53e3e;
|
||||
margin-top: 10px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
button:disabled {
|
||||
opacity: 0.7;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
@@ -2,11 +2,7 @@
|
||||
<div class="wallet-connection">
|
||||
<div v-if="!isConnected" class="connect-section">
|
||||
<p>Подключите свой кошелек для доступа к расширенным функциям</p>
|
||||
<button
|
||||
@click="connectWallet"
|
||||
:disabled="isLoading"
|
||||
class="wallet-btn"
|
||||
>
|
||||
<button :disabled="isLoading" class="wallet-btn" @click="connectWallet">
|
||||
<span class="wallet-icon">💳</span>
|
||||
{{ isLoading ? 'Подключение...' : 'Подключить кошелек' }}
|
||||
</button>
|
||||
@@ -65,61 +61,3 @@ const connectWallet = async () => {
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.wallet-connection {
|
||||
padding: 20px;
|
||||
max-width: 400px;
|
||||
}
|
||||
|
||||
.connect-section,
|
||||
.status-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 15px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.wallet-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 10px 20px;
|
||||
background-color: #4a5568;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 16px;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.wallet-btn:hover:not(:disabled) {
|
||||
background-color: #2d3748;
|
||||
}
|
||||
|
||||
.wallet-icon {
|
||||
margin-right: 10px;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.address {
|
||||
font-family: monospace;
|
||||
background-color: #f7fafc;
|
||||
padding: 8px 12px;
|
||||
border-radius: 4px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.error {
|
||||
color: #e53e3e;
|
||||
margin-top: 10px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
button:disabled {
|
||||
opacity: 0.7;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
</style>
|
||||
@@ -2,8 +2,4 @@ import TelegramConnect from './TelegramConnect.vue';
|
||||
import WalletConnection from './WalletConnection.vue';
|
||||
import EmailConnect from './EmailConnect.vue';
|
||||
|
||||
export {
|
||||
TelegramConnect,
|
||||
WalletConnection,
|
||||
EmailConnect
|
||||
};
|
||||
export { TelegramConnect, WalletConnection, EmailConnect };
|
||||
|
||||
@@ -22,10 +22,10 @@ export function useAuth() {
|
||||
if (response.data.success) {
|
||||
// Фильтруем идентификаторы: убираем гостевые и оставляем только уникальные
|
||||
const filteredIdentities = response.data.identities
|
||||
.filter(identity => identity.provider !== 'guest')
|
||||
.filter((identity) => identity.provider !== 'guest')
|
||||
.reduce((acc, identity) => {
|
||||
// Для каждого типа провайдера оставляем только один идентификатор
|
||||
const existingIdentity = acc.find(i => i.provider === identity.provider);
|
||||
const existingIdentity = acc.find((i) => i.provider === identity.provider);
|
||||
if (!existingIdentity) {
|
||||
acc.push(identity);
|
||||
}
|
||||
@@ -69,7 +69,15 @@ export function useAuth() {
|
||||
}
|
||||
};
|
||||
|
||||
const updateAuth = async ({ authenticated, authType: newAuthType, userId: newUserId, address: newAddress, telegramId: newTelegramId, isAdmin: newIsAdmin, email: newEmail }) => {
|
||||
const updateAuth = async ({
|
||||
authenticated,
|
||||
authType: newAuthType,
|
||||
userId: newUserId,
|
||||
address: newAddress,
|
||||
telegramId: newTelegramId,
|
||||
isAdmin: newIsAdmin,
|
||||
email: newEmail,
|
||||
}) => {
|
||||
const wasAuthenticated = isAuthenticated.value;
|
||||
const previousUserId = userId.value;
|
||||
|
||||
@@ -80,7 +88,7 @@ export function useAuth() {
|
||||
newAddress,
|
||||
newTelegramId,
|
||||
newIsAdmin,
|
||||
newEmail
|
||||
newEmail,
|
||||
});
|
||||
|
||||
// Убедимся, что переменные являются реактивными
|
||||
@@ -93,15 +101,18 @@ export function useAuth() {
|
||||
email.value = newEmail || null;
|
||||
|
||||
// Кэшируем данные аутентификации
|
||||
localStorage.setItem('authData', JSON.stringify({
|
||||
localStorage.setItem(
|
||||
'authData',
|
||||
JSON.stringify({
|
||||
authenticated,
|
||||
authType: newAuthType,
|
||||
userId: newUserId,
|
||||
address: newAddress,
|
||||
telegramId: newTelegramId,
|
||||
isAdmin: newIsAdmin,
|
||||
email: newEmail
|
||||
}));
|
||||
email: newEmail,
|
||||
})
|
||||
);
|
||||
|
||||
// Если аутентификация через кошелек, проверяем баланс токенов только при изменении адреса
|
||||
if (authenticated && newAuthType === 'wallet' && newAddress && newAddress !== address.value) {
|
||||
@@ -123,7 +134,7 @@ export function useAuth() {
|
||||
address: address.value,
|
||||
telegramId: telegramId.value,
|
||||
email: email.value,
|
||||
isAdmin: isAdmin.value
|
||||
isAdmin: isAdmin.value,
|
||||
});
|
||||
|
||||
// Если пользователь только что аутентифицировался или сменил аккаунт,
|
||||
@@ -149,14 +160,14 @@ export function useAuth() {
|
||||
return {
|
||||
success: true,
|
||||
message: 'No new guest IDs to process',
|
||||
processedIds: processedGuestIds.value
|
||||
processedIds: processedGuestIds.value,
|
||||
};
|
||||
}
|
||||
|
||||
// Создаем объект с идентификаторами для передачи на сервер
|
||||
const identifiersData = {
|
||||
userId: userId.value,
|
||||
guestId: localGuestId
|
||||
guestId: localGuestId,
|
||||
};
|
||||
|
||||
// Добавляем все доступные идентификаторы
|
||||
@@ -181,11 +192,13 @@ export function useAuth() {
|
||||
// В качестве запасного варианта также обрабатываем старый формат ответа
|
||||
else if (response.data.results && Array.isArray(response.data.results)) {
|
||||
const newProcessedIds = response.data.results
|
||||
.filter(result => result.guestId)
|
||||
.map(result => result.guestId);
|
||||
.filter((result) => result.guestId)
|
||||
.map((result) => result.guestId);
|
||||
|
||||
if (newProcessedIds.length > 0) {
|
||||
processedGuestIds.value = [...new Set([...processedGuestIds.value, ...newProcessedIds])];
|
||||
processedGuestIds.value = [
|
||||
...new Set([...processedGuestIds.value, ...newProcessedIds]),
|
||||
];
|
||||
console.log('Updated processed guest IDs from results:', processedGuestIds.value);
|
||||
}
|
||||
}
|
||||
@@ -196,14 +209,14 @@ export function useAuth() {
|
||||
|
||||
return {
|
||||
success: true,
|
||||
processedIds: processedGuestIds.value
|
||||
processedIds: processedGuestIds.value,
|
||||
};
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error linking messages:', error);
|
||||
return {
|
||||
success: false,
|
||||
error: error.message
|
||||
error: error.message,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -232,7 +245,7 @@ export function useAuth() {
|
||||
address: response.data.address,
|
||||
telegramId: response.data.telegramId,
|
||||
email: response.data.email,
|
||||
isAdmin: response.data.isAdmin
|
||||
isAdmin: response.data.isAdmin,
|
||||
});
|
||||
|
||||
// Если пользователь аутентифицирован, обновляем список идентификаторов и связываем сообщения
|
||||
@@ -285,7 +298,7 @@ export function useAuth() {
|
||||
address: null,
|
||||
telegramId: null,
|
||||
email: null,
|
||||
isAdmin: false
|
||||
isAdmin: false,
|
||||
});
|
||||
|
||||
// Обновляем отображение отключенного состояния
|
||||
@@ -400,7 +413,7 @@ export function useAuth() {
|
||||
|
||||
const response = await axios.post('/api/auth/identities/link', {
|
||||
type: provider,
|
||||
value: providerId
|
||||
value: providerId,
|
||||
});
|
||||
|
||||
if (response.data.success) {
|
||||
@@ -426,7 +439,7 @@ export function useAuth() {
|
||||
console.error('Ошибка при связывании идентификатора:', error);
|
||||
return {
|
||||
success: false,
|
||||
error: error.response?.data?.error || error.message
|
||||
error: error.response?.data?.error || error.message,
|
||||
};
|
||||
}
|
||||
};
|
||||
@@ -449,6 +462,6 @@ export function useAuth() {
|
||||
updateIdentities,
|
||||
updateProcessedGuestIds,
|
||||
updateConnectionDisplay,
|
||||
linkIdentity
|
||||
linkIdentity,
|
||||
};
|
||||
}
|
||||
@@ -8,7 +8,8 @@ import axios from 'axios';
|
||||
|
||||
// Настройка axios
|
||||
// В Docker контейнере localhost:8000 не работает, поэтому используем явное значение
|
||||
const apiUrl = window.location.hostname === 'localhost' ? 'http://localhost:8000' : import.meta.env.VITE_API_URL;
|
||||
const apiUrl =
|
||||
window.location.hostname === 'localhost' ? 'http://localhost:8000' : import.meta.env.VITE_API_URL;
|
||||
axios.defaults.baseURL = apiUrl;
|
||||
axios.defaults.withCredentials = true;
|
||||
|
||||
|
||||
@@ -8,13 +8,13 @@ const routes = [
|
||||
{
|
||||
path: '/',
|
||||
name: 'home',
|
||||
component: HomeView
|
||||
}
|
||||
component: HomeView,
|
||||
},
|
||||
];
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHistory(),
|
||||
routes
|
||||
routes,
|
||||
});
|
||||
|
||||
console.log('router/index.js: Router created');
|
||||
@@ -27,7 +27,7 @@ router.beforeEach(async (to, from, next) => {
|
||||
}
|
||||
|
||||
// Проверяем аутентификацию, если маршрут требует авторизации
|
||||
if (to.matched.some(record => record.meta.requiresAuth)) {
|
||||
if (to.matched.some((record) => record.meta.requiresAuth)) {
|
||||
try {
|
||||
const response = await axios.get('/api/auth/check');
|
||||
if (response.data.authenticated) {
|
||||
|
||||
@@ -3,25 +3,25 @@ import api from '../api/axios';
|
||||
// Адреса смарт-контрактов токенов HB3A
|
||||
export const TOKEN_CONTRACTS = {
|
||||
eth: {
|
||||
address: "0xd95a45fc46a7300e6022885afec3d618d7d3f27c",
|
||||
symbol: "HB3A",
|
||||
network: "Ethereum"
|
||||
address: '0xd95a45fc46a7300e6022885afec3d618d7d3f27c',
|
||||
symbol: 'HB3A',
|
||||
network: 'Ethereum',
|
||||
},
|
||||
bsc: {
|
||||
address: "0x1d47f12ffA279BFE59Ab16d56fBb10d89AECdD5D",
|
||||
symbol: "HB3A",
|
||||
network: "BSC"
|
||||
address: '0x1d47f12ffA279BFE59Ab16d56fBb10d89AECdD5D',
|
||||
symbol: 'HB3A',
|
||||
network: 'BSC',
|
||||
},
|
||||
arbitrum: {
|
||||
address: "0xdce769b847a0a697239777d0b1c7dd33b6012ba0",
|
||||
symbol: "HB3A",
|
||||
network: "Arbitrum"
|
||||
address: '0xdce769b847a0a697239777d0b1c7dd33b6012ba0',
|
||||
symbol: 'HB3A',
|
||||
network: 'Arbitrum',
|
||||
},
|
||||
polygon: {
|
||||
address: "0x351f59de4fedbdf7601f5592b93db3b9330c1c1d",
|
||||
symbol: "HB3A",
|
||||
network: "Polygon"
|
||||
}
|
||||
address: '0x351f59de4fedbdf7601f5592b93db3b9330c1c1d',
|
||||
symbol: 'HB3A',
|
||||
network: 'Polygon',
|
||||
},
|
||||
};
|
||||
|
||||
// Получение балансов токенов
|
||||
@@ -35,7 +35,7 @@ export const fetchTokenBalances = async () => {
|
||||
eth: '0',
|
||||
bsc: '0',
|
||||
arbitrum: '0',
|
||||
polygon: '0'
|
||||
polygon: '0',
|
||||
};
|
||||
}
|
||||
};
|
||||
@@ -45,7 +45,7 @@ export async function connectWithWallet() {
|
||||
version: '1',
|
||||
chainId: 1,
|
||||
nonce,
|
||||
resources: [`${origin}/api/auth/verify`]
|
||||
resources: [`${origin}/api/auth/verify`],
|
||||
});
|
||||
|
||||
const message = siweMessage.prepareMessage();
|
||||
@@ -55,7 +55,7 @@ export async function connectWithWallet() {
|
||||
console.log('Requesting signature...');
|
||||
const signature = await window.ethereum.request({
|
||||
method: 'personal_sign',
|
||||
params: [message, address]
|
||||
params: [message, address],
|
||||
});
|
||||
|
||||
console.log('Got signature:', signature);
|
||||
@@ -65,7 +65,7 @@ export async function connectWithWallet() {
|
||||
const verificationResponse = await axios.post('/api/auth/verify', {
|
||||
message,
|
||||
signature,
|
||||
address
|
||||
address,
|
||||
});
|
||||
|
||||
console.log('Verification response:', verificationResponse.data);
|
||||
|
||||
@@ -11,7 +11,8 @@ export const connectWallet = async () => {
|
||||
console.error('No Ethereum provider (like MetaMask) detected!');
|
||||
return {
|
||||
success: false,
|
||||
error: 'Не найден кошелек MetaMask или другой Ethereum провайдер. Пожалуйста, установите расширение MetaMask.'
|
||||
error:
|
||||
'Не найден кошелек MetaMask или другой Ethereum провайдер. Пожалуйста, установите расширение MetaMask.',
|
||||
};
|
||||
}
|
||||
|
||||
@@ -24,7 +25,7 @@ export const connectWallet = async () => {
|
||||
if (!accounts || accounts.length === 0) {
|
||||
return {
|
||||
success: false,
|
||||
error: 'Не удалось получить доступ к аккаунтам. Пожалуйста, разрешите доступ в MetaMask.'
|
||||
error: 'Не удалось получить доступ к аккаунтам. Пожалуйста, разрешите доступ в MetaMask.',
|
||||
};
|
||||
}
|
||||
|
||||
@@ -43,7 +44,7 @@ export const connectWallet = async () => {
|
||||
if (!nonce) {
|
||||
return {
|
||||
success: false,
|
||||
error: 'Не удалось получить nonce от сервера.'
|
||||
error: 'Не удалось получить nonce от сервера.',
|
||||
};
|
||||
}
|
||||
|
||||
@@ -65,7 +66,7 @@ export const connectWallet = async () => {
|
||||
chainId: 1, // Ethereum mainnet
|
||||
nonce: nonce,
|
||||
issuedAt: new Date().toISOString(),
|
||||
resources: [`${origin}/api/auth/verify`]
|
||||
resources: [`${origin}/api/auth/verify`],
|
||||
});
|
||||
|
||||
// Получаем строку сообщения для подписи
|
||||
@@ -79,7 +80,7 @@ export const connectWallet = async () => {
|
||||
if (!signature) {
|
||||
return {
|
||||
success: false,
|
||||
error: 'Подпись не была получена. Пожалуйста, подпишите сообщение в MetaMask.'
|
||||
error: 'Подпись не была получена. Пожалуйста, подпишите сообщение в MetaMask.',
|
||||
};
|
||||
}
|
||||
|
||||
@@ -90,7 +91,7 @@ export const connectWallet = async () => {
|
||||
const verifyResponse = await axios.post('/api/auth/verify', {
|
||||
address: normalizedAddress,
|
||||
signature,
|
||||
nonce
|
||||
nonce,
|
||||
});
|
||||
|
||||
// Обновляем интерфейс для отображения подключенного состояния
|
||||
@@ -118,12 +119,12 @@ export const connectWallet = async () => {
|
||||
success: true,
|
||||
address: normalizedAddress,
|
||||
userId: verifyResponse.data.userId,
|
||||
isAdmin: verifyResponse.data.isAdmin
|
||||
isAdmin: verifyResponse.data.isAdmin,
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
success: false,
|
||||
error: verifyResponse.data.error || 'Ошибка верификации на сервере.'
|
||||
error: verifyResponse.data.error || 'Ошибка верификации на сервере.',
|
||||
};
|
||||
}
|
||||
} catch (error) {
|
||||
@@ -142,7 +143,7 @@ export const connectWallet = async () => {
|
||||
|
||||
return {
|
||||
success: false,
|
||||
error: errorMessage
|
||||
error: errorMessage,
|
||||
};
|
||||
}
|
||||
};
|
||||
@@ -8,25 +8,41 @@
|
||||
<h1 class="title">✌️HB3 - Accelerator DLE</h1>
|
||||
<p class="subtitle">Венчурный фонд и поставщик программного обеспечения</p>
|
||||
</div>
|
||||
<button class="nav-btn header-wallet-btn" @click="toggleWalletSidebar" :class="{ active: showWalletSidebar }">
|
||||
<div class="hamburger-line"></div>
|
||||
<div class="nav-btn-number">7</div>
|
||||
<div class="nav-btn-text">{{ showWalletSidebar ? 'Скрыть панель' : 'Подключиться' }}</div>
|
||||
<button
|
||||
class="header-wallet-btn"
|
||||
:class="{ active: showWalletSidebar }"
|
||||
@click="toggleWalletSidebar"
|
||||
>
|
||||
<div class="hamburger-line" />
|
||||
<div class="nav-btn-text">
|
||||
{{ showWalletSidebar ? 'Скрыть панель' : 'Подключиться' }}
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="chat-container">
|
||||
<div class="chat-messages" ref="messagesContainer">
|
||||
<div v-for="message in messages" :key="message.id"
|
||||
:class="['message',
|
||||
message.sender_type === 'assistant' || message.role === 'assistant' ? 'ai-message' :
|
||||
message.sender_type === 'system' || message.role === 'system' ? 'system-message' : 'user-message',
|
||||
<div ref="messagesContainer" class="chat-messages">
|
||||
<div
|
||||
v-for="message in messages"
|
||||
:key="message.id"
|
||||
:class="[
|
||||
'message',
|
||||
message.sender_type === 'assistant' || message.role === 'assistant'
|
||||
? 'ai-message'
|
||||
: message.sender_type === 'system' || message.role === 'system'
|
||||
? 'system-message'
|
||||
: 'user-message',
|
||||
message.isLocal ? 'is-local' : '',
|
||||
message.hasError ? 'has-error' : '']">
|
||||
<div class="message-content" v-html="formatMessage(message.content)"></div>
|
||||
message.hasError ? 'has-error' : '',
|
||||
]"
|
||||
>
|
||||
<!-- eslint-disable-next-line vue/no-v-html -->
|
||||
<div class="message-content" v-html="formatMessage(message.content)" />
|
||||
<div class="message-meta">
|
||||
<div class="message-time">{{ formatTime(message.timestamp || message.created_at) }}</div>
|
||||
<div class="message-time">
|
||||
{{ formatTime(message.timestamp || message.created_at) }}
|
||||
</div>
|
||||
<div v-if="message.isLocal" class="message-status">
|
||||
<span class="sending-indicator">Отправка...</span>
|
||||
</div>
|
||||
@@ -39,21 +55,21 @@
|
||||
|
||||
<div class="chat-input">
|
||||
<textarea
|
||||
ref="messageInput"
|
||||
v-model="newMessage"
|
||||
placeholder="Введите сообщение..."
|
||||
:disabled="isLoading"
|
||||
rows="3"
|
||||
autofocus
|
||||
@keydown.enter.prevent="handleMessage(newMessage)"
|
||||
@focus="handleFocus"
|
||||
@blur="handleBlur"
|
||||
:disabled="isLoading"
|
||||
ref="messageInput"
|
||||
rows="3"
|
||||
autofocus
|
||||
></textarea>
|
||||
/>
|
||||
<div class="chat-buttons">
|
||||
<button @click="handleMessage(newMessage)" :disabled="isLoading || !newMessage.trim()">
|
||||
<button :disabled="isLoading || !newMessage.trim()" @click="handleMessage(newMessage)">
|
||||
{{ isLoading ? 'Отправка...' : 'Отправить' }}
|
||||
</button>
|
||||
<button @click="clearGuestMessages" class="clear-btn" :disabled="isLoading">
|
||||
<button class="clear-btn" :disabled="isLoading" @click="clearGuestMessages">
|
||||
Очистить
|
||||
</button>
|
||||
</div>
|
||||
@@ -62,33 +78,67 @@
|
||||
</div>
|
||||
|
||||
<!-- Правая панель с информацией о кошельке -->
|
||||
<div class="wallet-sidebar" v-if="showWalletSidebar">
|
||||
<transition name="sidebar-slide">
|
||||
<div v-if="showWalletSidebar" class="wallet-sidebar">
|
||||
<div class="wallet-sidebar-content">
|
||||
<!-- Блок для неавторизованных пользователей -->
|
||||
<div v-if="!isAuthenticated" class="auth-container">
|
||||
<div class="wallet-header">
|
||||
<button v-if="!isAuthenticated" @click="handleWalletAuth" class="wallet-connect-btn-header">
|
||||
Подключить кошелек
|
||||
</button>
|
||||
<button v-if="isAuthenticated" @click="disconnectWallet" class="wallet-disconnect-btn-header">
|
||||
Отключить
|
||||
</button>
|
||||
<div class="wallet-header-buttons">
|
||||
<button class="close-wallet-sidebar" @click="toggleWalletSidebar">×</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Добавляем дополнительные кнопки авторизации -->
|
||||
<div v-if="!isAuthenticated" class="auth-buttons">
|
||||
<h3>Авторизация через:</h3>
|
||||
<div v-if="!telegramAuth.showVerification" class="auth-btn-container">
|
||||
<button @click="handleTelegramAuth" class="auth-btn telegram-btn">
|
||||
<div class="auth-buttons-wrapper">
|
||||
<button
|
||||
v-if="
|
||||
!telegramAuth.showVerification &&
|
||||
!emailAuth.showForm &&
|
||||
!emailAuth.showVerification
|
||||
"
|
||||
class="auth-btn connect-wallet-btn"
|
||||
@click="handleWalletAuth"
|
||||
>
|
||||
Подключить кошелек
|
||||
</button>
|
||||
|
||||
<button
|
||||
v-if="
|
||||
!telegramAuth.showVerification &&
|
||||
!emailAuth.showForm &&
|
||||
!emailAuth.showVerification
|
||||
"
|
||||
class="auth-btn telegram-btn"
|
||||
@click="handleTelegramAuth"
|
||||
>
|
||||
Подключить Telegram
|
||||
</button>
|
||||
|
||||
<button
|
||||
v-if="
|
||||
!telegramAuth.showVerification &&
|
||||
!emailAuth.showForm &&
|
||||
!emailAuth.showVerification
|
||||
"
|
||||
class="auth-btn email-btn"
|
||||
@click="handleEmailAuth"
|
||||
>
|
||||
Подключить Email
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div v-if="telegramAuth.showVerification" class="verification-block">
|
||||
<div class="verification-code">
|
||||
<span>Код верификации:</span>
|
||||
<code @click="copyCode(telegramAuth.verificationCode)">{{ telegramAuth.verificationCode }}</code>
|
||||
<code @click="copyCode(telegramAuth.verificationCode)">{{
|
||||
telegramAuth.verificationCode
|
||||
}}</code>
|
||||
<span v-if="codeCopied" class="copied-message">Скопировано!</span>
|
||||
</div>
|
||||
<a :href="telegramAuth.botLink" target="_blank" class="bot-link">Открыть бота Telegram</a>
|
||||
<button @click="cancelTelegramAuth" class="cancel-btn">Отмена</button>
|
||||
<a :href="telegramAuth.botLink" target="_blank" class="bot-link"
|
||||
>Открыть бота Telegram</a
|
||||
>
|
||||
<button class="cancel-btn" @click="cancelTelegramAuth">Отмена</button>
|
||||
</div>
|
||||
|
||||
<!-- Сообщение об ошибке в Telegram -->
|
||||
@@ -97,12 +147,6 @@
|
||||
<button class="close-error" @click="telegramAuth.error = ''">×</button>
|
||||
</div>
|
||||
|
||||
<div v-if="!emailAuth.showForm && !emailAuth.showVerification" class="auth-btn-container">
|
||||
<button @click="handleEmailAuth" class="auth-btn email-btn">
|
||||
Подключить Email
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Форма для Email верификации -->
|
||||
<div v-if="emailAuth.showForm" class="email-form">
|
||||
<p>Введите ваш email для получения кода подтверждения:</p>
|
||||
@@ -114,19 +158,28 @@
|
||||
class="email-input"
|
||||
:class="{ 'email-input-error': emailAuth.formatError }"
|
||||
/>
|
||||
<button @click="sendEmailVerification" class="send-email-btn" :disabled="emailAuth.isLoading">
|
||||
<button
|
||||
class="send-email-btn"
|
||||
:disabled="emailAuth.isLoading"
|
||||
@click="sendEmailVerification"
|
||||
>
|
||||
{{ emailAuth.isLoading ? 'Отправка...' : 'Отправить код' }}
|
||||
</button>
|
||||
</div>
|
||||
<div class="form-actions">
|
||||
<button @click="cancelEmailAuth" class="cancel-btn">Отмена</button>
|
||||
<p v-if="emailAuth.formatError" class="email-format-error">Пожалуйста, введите корректный email</p>
|
||||
<button class="cancel-btn" @click="cancelEmailAuth">Отмена</button>
|
||||
<p v-if="emailAuth.formatError" class="email-format-error">
|
||||
Пожалуйста, введите корректный email
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Форма для ввода кода верификации Email -->
|
||||
<div v-if="emailAuth.showVerification" class="email-verification-form">
|
||||
<p>На ваш email <strong>{{ emailAuth.verificationEmail }}</strong> отправлен код подтверждения.</p>
|
||||
<p>
|
||||
На ваш email <strong>{{ emailAuth.verificationEmail }}</strong> отправлен код
|
||||
подтверждения.
|
||||
</p>
|
||||
<div class="email-form-container">
|
||||
<input
|
||||
v-model="emailAuth.verificationCode"
|
||||
@@ -135,12 +188,15 @@
|
||||
maxlength="6"
|
||||
class="email-input"
|
||||
/>
|
||||
<button @click="verifyEmailCode" class="send-email-btn" :disabled="emailAuth.isVerifying">
|
||||
<button
|
||||
class="send-email-btn"
|
||||
:disabled="emailAuth.isVerifying"
|
||||
@click="verifyEmailCode"
|
||||
>
|
||||
{{ emailAuth.isVerifying ? 'Проверка...' : 'Подтвердить' }}
|
||||
</button>
|
||||
</div>
|
||||
<button @click="cancelEmailAuth" class="cancel-btn">Отмена</button>
|
||||
</div>
|
||||
<button class="cancel-btn" @click="cancelEmailAuth">Отмена</button>
|
||||
</div>
|
||||
|
||||
<!-- Сообщение об ошибке в Email -->
|
||||
@@ -148,16 +204,28 @@
|
||||
{{ emailAuth.error }}
|
||||
<button class="close-error" @click="emailAuth.error = ''">×</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Блок для авторизованных пользователей -->
|
||||
<div v-if="isAuthenticated" class="auth-buttons-container">
|
||||
<div class="wallet-header">
|
||||
<div class="wallet-header-buttons">
|
||||
<button class="auth-btn disconnect-wallet-btn" @click="disconnectWallet">
|
||||
Отключить
|
||||
</button>
|
||||
<button class="close-wallet-sidebar" @click="toggleWalletSidebar">×</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Блок информации о пользователе -->
|
||||
<div v-if="isAuthenticated" class="user-info">
|
||||
<div class="user-info">
|
||||
<h3>Идентификаторы:</h3>
|
||||
<div class="user-info-item">
|
||||
<span class="user-info-label">Кошелек:</span>
|
||||
<span v-if="hasIdentityType('wallet')" class="user-info-value">
|
||||
{{ truncateAddress(getIdentityValue('wallet')) }}
|
||||
</span>
|
||||
<button v-else @click="handleWalletAuth" class="connect-btn">
|
||||
<button v-else class="connect-btn" @click="handleWalletAuth">
|
||||
Подключить кошелек
|
||||
</button>
|
||||
</div>
|
||||
@@ -166,7 +234,7 @@
|
||||
<span v-if="hasIdentityType('telegram')" class="user-info-value">
|
||||
{{ getIdentityValue('telegram') }}
|
||||
</span>
|
||||
<button v-else @click="handleTelegramAuth" class="connect-btn">
|
||||
<button v-else class="connect-btn" @click="handleTelegramAuth">
|
||||
Подключить Telegram
|
||||
</button>
|
||||
</div>
|
||||
@@ -175,14 +243,21 @@
|
||||
<span v-if="hasIdentityType('email')" class="user-info-value">
|
||||
{{ getIdentityValue('email') }}
|
||||
</span>
|
||||
<button v-else @click="handleEmailAuth" class="connect-btn">
|
||||
<button v-else class="connect-btn" @click="handleEmailAuth">
|
||||
Подключить Email
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Блок форм подключения для аутентифицированных пользователей -->
|
||||
<div v-if="isAuthenticated && (emailAuth.showForm || telegramAuth.showVerification || emailAuth.showVerification)" class="connect-forms">
|
||||
<div
|
||||
v-if="
|
||||
isAuthenticated &&
|
||||
(emailAuth.showForm || telegramAuth.showVerification || emailAuth.showVerification)
|
||||
"
|
||||
class="connect-forms"
|
||||
>
|
||||
<!-- Форма для Email верификации -->
|
||||
<div v-if="emailAuth.showForm" class="email-form">
|
||||
<p>Введите ваш email для получения кода подтверждения:</p>
|
||||
@@ -194,19 +269,28 @@
|
||||
class="email-input"
|
||||
:class="{ 'email-input-error': emailAuth.formatError }"
|
||||
/>
|
||||
<button @click="sendEmailVerification" class="send-email-btn" :disabled="emailAuth.isLoading">
|
||||
<button
|
||||
class="send-email-btn"
|
||||
:disabled="emailAuth.isLoading"
|
||||
@click="sendEmailVerification"
|
||||
>
|
||||
{{ emailAuth.isLoading ? 'Отправка...' : 'Отправить код' }}
|
||||
</button>
|
||||
</div>
|
||||
<div class="form-actions">
|
||||
<button @click="cancelEmailAuth" class="cancel-btn">Отмена</button>
|
||||
<p v-if="emailAuth.formatError" class="email-format-error">Пожалуйста, введите корректный email</p>
|
||||
<button class="cancel-btn" @click="cancelEmailAuth">Отмена</button>
|
||||
<p v-if="emailAuth.formatError" class="email-format-error">
|
||||
Пожалуйста, введите корректный email
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Форма для ввода кода верификации Email -->
|
||||
<div v-if="emailAuth.showVerification" class="email-verification-form">
|
||||
<p>На ваш email <strong>{{ emailAuth.verificationEmail }}</strong> отправлен код подтверждения.</p>
|
||||
<p>
|
||||
На ваш email <strong>{{ emailAuth.verificationEmail }}</strong> отправлен код
|
||||
подтверждения.
|
||||
</p>
|
||||
<div class="email-form-container">
|
||||
<input
|
||||
v-model="emailAuth.verificationCode"
|
||||
@@ -215,22 +299,30 @@
|
||||
maxlength="6"
|
||||
class="email-input"
|
||||
/>
|
||||
<button @click="verifyEmailCode" class="send-email-btn" :disabled="emailAuth.isVerifying">
|
||||
<button
|
||||
class="send-email-btn"
|
||||
:disabled="emailAuth.isVerifying"
|
||||
@click="verifyEmailCode"
|
||||
>
|
||||
{{ emailAuth.isVerifying ? 'Проверка...' : 'Подтвердить' }}
|
||||
</button>
|
||||
</div>
|
||||
<button @click="cancelEmailAuth" class="cancel-btn">Отмена</button>
|
||||
<button class="cancel-btn" @click="cancelEmailAuth">Отмена</button>
|
||||
</div>
|
||||
|
||||
<!-- Форма для Telegram верификации -->
|
||||
<div v-if="telegramAuth.showVerification" class="verification-block">
|
||||
<div class="verification-code">
|
||||
<span>Код верификации:</span>
|
||||
<code @click="copyCode(telegramAuth.verificationCode)">{{ telegramAuth.verificationCode }}</code>
|
||||
<code @click="copyCode(telegramAuth.verificationCode)">{{
|
||||
telegramAuth.verificationCode
|
||||
}}</code>
|
||||
<span v-if="codeCopied" class="copied-message">Скопировано!</span>
|
||||
</div>
|
||||
<a :href="telegramAuth.botLink" target="_blank" class="bot-link">Открыть бота Telegram</a>
|
||||
<button @click="cancelTelegramAuth" class="cancel-btn">Отмена</button>
|
||||
<a :href="telegramAuth.botLink" target="_blank" class="bot-link"
|
||||
>Открыть бота Telegram</a
|
||||
>
|
||||
<button class="cancel-btn" @click="cancelTelegramAuth">Отмена</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -249,7 +341,9 @@
|
||||
</div>
|
||||
<div class="token-balance">
|
||||
<span class="token-name">ARB:</span>
|
||||
<span class="token-amount">{{ Number(tokenBalances.arbitrum).toLocaleString() }}</span>
|
||||
<span class="token-amount">{{
|
||||
Number(tokenBalances.arbitrum).toLocaleString()
|
||||
}}</span>
|
||||
<span class="token-symbol">{{ TOKEN_CONTRACTS.arbitrum.symbol }}</span>
|
||||
</div>
|
||||
<div class="token-balance">
|
||||
@@ -260,6 +354,8 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
@@ -384,7 +480,7 @@ const formatTime = (timestamp) => {
|
||||
month: 'short',
|
||||
day: 'numeric',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit'
|
||||
minute: '2-digit',
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error formatting time:', error, timestamp);
|
||||
@@ -424,7 +520,7 @@ const telegramAuth = ref({
|
||||
verificationCode: '',
|
||||
botLink: '',
|
||||
checkInterval: null,
|
||||
error: ''
|
||||
error: '',
|
||||
});
|
||||
|
||||
// Объединяем состояния для Email аутентификации
|
||||
@@ -437,13 +533,13 @@ const emailAuth = ref({
|
||||
formatError: false,
|
||||
isLoading: false,
|
||||
isVerifying: false,
|
||||
error: ''
|
||||
error: '',
|
||||
});
|
||||
|
||||
// Состояния для уведомлений
|
||||
const notifications = ref({
|
||||
successMessage: '',
|
||||
showSuccess: false
|
||||
showSuccess: false,
|
||||
});
|
||||
|
||||
// Состояния для пагинации и загрузки сообщений
|
||||
@@ -453,7 +549,7 @@ const messageLoading = ref({
|
||||
offset: 0,
|
||||
limit: 30,
|
||||
isInProgress: false,
|
||||
isLinkingGuest: false
|
||||
isLinkingGuest: false,
|
||||
});
|
||||
|
||||
// Состояние для балансов токенов
|
||||
@@ -461,7 +557,7 @@ const tokenBalances = ref({
|
||||
eth: '0',
|
||||
bsc: '0',
|
||||
arbitrum: '0',
|
||||
polygon: '0'
|
||||
polygon: '0',
|
||||
});
|
||||
|
||||
// Переменная для хранения интервала обновления балансов
|
||||
@@ -489,7 +585,7 @@ const guestIdValue = computed(() => {
|
||||
const filteredIdentities = computed(() => {
|
||||
if (!auth.identities || !auth.identities.value) return [];
|
||||
|
||||
return auth.identities.value.filter(identity => {
|
||||
return auth.identities.value.filter((identity) => {
|
||||
if (identity.provider === 'wallet' && auth.address?.value === identity.provider_id) {
|
||||
return false;
|
||||
}
|
||||
@@ -507,8 +603,9 @@ const filteredIdentities = computed(() => {
|
||||
* Определяет, нужно ли загружать историю сообщений
|
||||
*/
|
||||
const shouldLoadHistory = computed(() => {
|
||||
return isAuthenticated.value ||
|
||||
(getFromStorage('guestId') && getFromStorage('guestId').length > 0);
|
||||
return (
|
||||
isAuthenticated.value || (getFromStorage('guestId') && getFromStorage('guestId').length > 0)
|
||||
);
|
||||
});
|
||||
|
||||
/**
|
||||
@@ -518,10 +615,10 @@ const shouldLoadHistory = computed(() => {
|
||||
*/
|
||||
const formatIdentityProvider = (provider) => {
|
||||
const providers = {
|
||||
'wallet': 'Кошелек',
|
||||
'email': 'Email',
|
||||
'telegram': 'Telegram',
|
||||
'guest': 'Гость'
|
||||
wallet: 'Кошелек',
|
||||
email: 'Email',
|
||||
telegram: 'Telegram',
|
||||
guest: 'Гость',
|
||||
};
|
||||
return providers[provider] || provider;
|
||||
};
|
||||
@@ -544,18 +641,22 @@ const loadMessages = async (options = {}) => {
|
||||
messageLoading.value.isInProgress = true;
|
||||
if (!silent) isLoading.value = true;
|
||||
|
||||
console.log(`Загрузка истории сообщений${authType ? ` после ${authType} аутентификации` : ''}...`);
|
||||
console.log(
|
||||
`Загрузка истории сообщений${authType ? ` после ${authType} аутентификации` : ''}...`
|
||||
);
|
||||
|
||||
// Если это загрузка после аутентификации, немного ждем для завершения привязки сообщений
|
||||
if (authType) {
|
||||
console.log(`Ожидание завершения привязки гостевых сообщений после ${authType} аутентификации...`);
|
||||
console.log(
|
||||
`Ожидание завершения привязки гостевых сообщений после ${authType} аутентификации...`
|
||||
);
|
||||
|
||||
// Задержка для гарантии, что сервер успеет обработать сессию
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
await new Promise((resolve) => setTimeout(resolve, 1000));
|
||||
|
||||
// Дополнительно проверяем, что процесс связывания сообщений завершился
|
||||
if (messageLoading.value.isLinkingGuest) {
|
||||
await new Promise(resolve => {
|
||||
await new Promise((resolve) => {
|
||||
const checkInterval = setInterval(() => {
|
||||
if (!messageLoading.value.isLinkingGuest) {
|
||||
clearInterval(checkInterval);
|
||||
@@ -596,7 +697,7 @@ const loadMessages = async (options = {}) => {
|
||||
|
||||
// Запрашиваем общее количество сообщений
|
||||
const countResponse = await axios.get('/api/chat/history', {
|
||||
params: { count_only: true }
|
||||
params: { count_only: true },
|
||||
});
|
||||
|
||||
if (!countResponse.data.success) {
|
||||
@@ -616,8 +717,8 @@ const loadMessages = async (options = {}) => {
|
||||
const response = await axios.get('/api/chat/history', {
|
||||
params: {
|
||||
offset: effectiveOffset,
|
||||
limit: messageLoading.value.limit
|
||||
}
|
||||
limit: messageLoading.value.limit,
|
||||
},
|
||||
});
|
||||
|
||||
if (response.data.success) {
|
||||
@@ -651,9 +752,11 @@ const loadMessages = async (options = {}) => {
|
||||
|
||||
// Уведомляем о загрузке сообщений
|
||||
if (authType) {
|
||||
window.dispatchEvent(new CustomEvent('messages-updated', {
|
||||
detail: { count: messages.value.length }
|
||||
}));
|
||||
window.dispatchEvent(
|
||||
new CustomEvent('messages-updated', {
|
||||
detail: { count: messages.value.length },
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
// Прокручиваем к последнему сообщению
|
||||
@@ -688,7 +791,7 @@ const handleMessage = async (text) => {
|
||||
role: 'user',
|
||||
isLocal: true,
|
||||
isGuest: !auth.isAuthenticated.value,
|
||||
timestamp: new Date().toISOString()
|
||||
timestamp: new Date().toISOString(),
|
||||
};
|
||||
|
||||
// Добавляем сообщение в чат
|
||||
@@ -708,12 +811,12 @@ const handleMessage = async (text) => {
|
||||
// Отправляем сообщение как авторизованный пользователь
|
||||
const response = await axios.post('/api/chat/message', {
|
||||
message: userMessageContent,
|
||||
language: userLanguage.value
|
||||
language: userLanguage.value,
|
||||
});
|
||||
|
||||
if (response.data.success) {
|
||||
// Обновляем ID сообщения пользователя
|
||||
const userMsgIndex = messages.value.findIndex(m => m.id === tempId);
|
||||
const userMsgIndex = messages.value.findIndex((m) => m.id === tempId);
|
||||
if (userMsgIndex !== -1) {
|
||||
messages.value[userMsgIndex].id = response.data.userMessage.id;
|
||||
messages.value[userMsgIndex].isLocal = false;
|
||||
@@ -725,7 +828,7 @@ const handleMessage = async (text) => {
|
||||
content: response.data.aiMessage.content,
|
||||
sender_type: 'assistant',
|
||||
role: 'assistant',
|
||||
timestamp: response.data.aiMessage.created_at
|
||||
timestamp: response.data.aiMessage.created_at,
|
||||
});
|
||||
|
||||
// Прокручиваем к последнему сообщению
|
||||
@@ -745,14 +848,14 @@ const handleMessage = async (text) => {
|
||||
const response = await axios.post('/api/chat/guest-message', {
|
||||
content: userMessageContent,
|
||||
guestId,
|
||||
language: userLanguage.value
|
||||
language: userLanguage.value,
|
||||
});
|
||||
|
||||
if (response.data.success) {
|
||||
console.log('Гостевое сообщение отправлено:', response.data);
|
||||
|
||||
// Обновляем ID сообщения пользователя
|
||||
const userMsgIndex = messages.value.findIndex(m => m.id === tempId);
|
||||
const userMsgIndex = messages.value.findIndex((m) => m.id === tempId);
|
||||
if (userMsgIndex !== -1) {
|
||||
messages.value[userMsgIndex].id = response.data.messageId;
|
||||
messages.value[userMsgIndex].isLocal = false;
|
||||
@@ -767,7 +870,7 @@ const handleMessage = async (text) => {
|
||||
sender_type: 'user',
|
||||
role: 'user',
|
||||
isGuest: true,
|
||||
timestamp: new Date().toISOString()
|
||||
timestamp: new Date().toISOString(),
|
||||
});
|
||||
setToStorage('guestMessages', JSON.stringify(storedMessages));
|
||||
setToStorage('hasUserSentMessage', 'true');
|
||||
@@ -787,7 +890,7 @@ const handleMessage = async (text) => {
|
||||
console.error('Ошибка отправки сообщения:', error);
|
||||
|
||||
// Помечаем сообщение как ошибочное
|
||||
const userMsgIndex = messages.value.findIndex(m => m.id === tempId);
|
||||
const userMsgIndex = messages.value.findIndex((m) => m.id === tempId);
|
||||
if (userMsgIndex !== -1) {
|
||||
messages.value[userMsgIndex].hasError = true;
|
||||
}
|
||||
@@ -798,7 +901,7 @@ const handleMessage = async (text) => {
|
||||
content: 'Произошла ошибка при отправке сообщения. Пожалуйста, попробуйте еще раз.',
|
||||
sender_type: 'system',
|
||||
role: 'system',
|
||||
timestamp: new Date().toISOString()
|
||||
timestamp: new Date().toISOString(),
|
||||
});
|
||||
scrollToBottom();
|
||||
} finally {
|
||||
@@ -839,7 +942,7 @@ const scrollToBottom = () => {
|
||||
const clearGuestMessages = () => {
|
||||
removeFromStorage('guestMessages');
|
||||
console.log('Гостевые сообщения очищены');
|
||||
messages.value = messages.value.filter(m => !m.isGuest);
|
||||
messages.value = messages.value.filter((m) => !m.isGuest);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -1013,14 +1116,16 @@ const handleTelegramAuth = async () => {
|
||||
const linkResult = await auth.linkIdentity('telegram', telegramId);
|
||||
|
||||
if (linkResult.success) {
|
||||
notifications.value.successMessage = 'Telegram успешно подключен к вашему аккаунту!';
|
||||
notifications.value.successMessage =
|
||||
'Telegram успешно подключен к вашему аккаунту!';
|
||||
notifications.value.showSuccess = true;
|
||||
|
||||
setTimeout(() => {
|
||||
notifications.value.showSuccess = false;
|
||||
}, 3000);
|
||||
} else {
|
||||
notifications.value.errorMessage = linkResult.error || 'Не удалось подключить Telegram';
|
||||
notifications.value.errorMessage =
|
||||
linkResult.error || 'Не удалось подключить Telegram';
|
||||
notifications.value.showError = true;
|
||||
|
||||
setTimeout(() => {
|
||||
@@ -1045,7 +1150,8 @@ const handleTelegramAuth = async () => {
|
||||
}
|
||||
}, 2000); // Проверяем каждые 2 секунды
|
||||
} else {
|
||||
telegramAuth.value.error = response.data.error || 'Ошибка при инициализации авторизации Telegram';
|
||||
telegramAuth.value.error =
|
||||
response.data.error || 'Ошибка при инициализации авторизации Telegram';
|
||||
telegramAuth.value.showVerification = false;
|
||||
}
|
||||
} catch (error) {
|
||||
@@ -1128,9 +1234,13 @@ const sendEmailVerification = async () => {
|
||||
emailAuth.value.verificationEmail = emailAuth.value.email;
|
||||
emailAuth.value.verificationCode = '';
|
||||
|
||||
console.log('Отображаем форму ввода кода верификации для email:', emailAuth.value.verificationEmail);
|
||||
console.log(
|
||||
'Отображаем форму ввода кода верификации для email:',
|
||||
emailAuth.value.verificationEmail
|
||||
);
|
||||
} else {
|
||||
emailAuth.value.error = response.data.error || 'Ошибка инициализации аутентификации по email';
|
||||
emailAuth.value.error =
|
||||
response.data.error || 'Ошибка инициализации аутентификации по email';
|
||||
emailAuth.value.showForm = true;
|
||||
emailAuth.value.showVerification = false;
|
||||
}
|
||||
@@ -1164,7 +1274,7 @@ const verifyEmailCode = async () => {
|
||||
|
||||
const response = await axios.post('/api/auth/email/verify-code', {
|
||||
email: emailAuth.value.verificationEmail,
|
||||
code: emailAuth.value.verificationCode
|
||||
code: emailAuth.value.verificationCode,
|
||||
});
|
||||
|
||||
if (response.data.success) {
|
||||
@@ -1178,7 +1288,10 @@ const verifyEmailCode = async () => {
|
||||
if (auth.isAuthenticated.value && emailAuth.value.verificationEmail) {
|
||||
// Если пользователь уже авторизован, связываем email
|
||||
if (auth.authType.value !== 'email') {
|
||||
console.log('Связывание Email с существующим аккаунтом:', emailAuth.value.verificationEmail);
|
||||
console.log(
|
||||
'Связывание Email с существующим аккаунтом:',
|
||||
emailAuth.value.verificationEmail
|
||||
);
|
||||
const linkResult = await auth.linkIdentity('email', emailAuth.value.verificationEmail);
|
||||
|
||||
if (linkResult.success) {
|
||||
@@ -1297,7 +1410,7 @@ const disconnectWallet = async () => {
|
||||
*/
|
||||
const hasIdentityType = (type) => {
|
||||
if (!auth.identities?.value) return false;
|
||||
return auth.identities.value.some(identity => identity.provider === type);
|
||||
return auth.identities.value.some((identity) => identity.provider === type);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -1307,7 +1420,7 @@ const hasIdentityType = (type) => {
|
||||
*/
|
||||
const getIdentityValue = (type) => {
|
||||
if (!auth.identities?.value) return null;
|
||||
const identity = auth.identities.value.find(identity => identity.provider === type);
|
||||
const identity = auth.identities.value.find((identity) => identity.provider === type);
|
||||
return identity ? identity.provider_id : null;
|
||||
};
|
||||
|
||||
@@ -1315,12 +1428,14 @@ const getIdentityValue = (type) => {
|
||||
* Отслеживает изменения в аутентификации
|
||||
*/
|
||||
const watchAuthChanges = () => {
|
||||
watch(() => auth.isAuthenticated.value, async (newValue, oldValue) => {
|
||||
watch(
|
||||
() => auth.isAuthenticated.value,
|
||||
async (newValue, oldValue) => {
|
||||
console.log('Изменение аутентификации:', {
|
||||
from: oldValue,
|
||||
to: newValue,
|
||||
userId: auth.userId.value,
|
||||
authType: auth.authType.value
|
||||
authType: auth.authType.value,
|
||||
});
|
||||
|
||||
// Обновляем отображение аутентификации
|
||||
@@ -1330,7 +1445,7 @@ const watchAuthChanges = () => {
|
||||
address: auth.address.value,
|
||||
email: auth.email.value,
|
||||
telegramId: auth.telegramId.value,
|
||||
telegramUsername: auth.telegramUsername
|
||||
telegramUsername: auth.telegramUsername,
|
||||
});
|
||||
|
||||
if (newValue && !oldValue) {
|
||||
@@ -1344,7 +1459,9 @@ const watchAuthChanges = () => {
|
||||
messageLoading.value.offset = 0;
|
||||
messageLoading.value.hasMoreMessages = true;
|
||||
}
|
||||
}, { immediate: true });
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -1356,9 +1473,9 @@ const updateAuthDisplay = (state) => {
|
||||
|
||||
if (state.isAuthenticated) {
|
||||
const authTypeLabels = {
|
||||
'wallet': 'Кошелек',
|
||||
'email': 'Email',
|
||||
'telegram': 'Telegram'
|
||||
wallet: 'Кошелек',
|
||||
email: 'Email',
|
||||
telegram: 'Telegram',
|
||||
};
|
||||
|
||||
let authLabel = authTypeLabels[state.authType] || 'Аккаунт';
|
||||
@@ -1430,7 +1547,7 @@ const updateBalances = async () => {
|
||||
eth: balances.eth || '0',
|
||||
bsc: balances.bsc || '0',
|
||||
arbitrum: balances.arbitrum || '0',
|
||||
polygon: balances.polygon || '0'
|
||||
polygon: balances.polygon || '0',
|
||||
};
|
||||
|
||||
console.log('Обновленные балансы в интерфейсе:', tokenBalances.value);
|
||||
@@ -1520,15 +1637,20 @@ const handleBlur = () => {
|
||||
// =====================================================================
|
||||
|
||||
// Наблюдаем за изменением адреса кошелька для обновления балансов
|
||||
watch(() => auth.address?.value, (newAddress) => {
|
||||
watch(
|
||||
() => auth.address?.value,
|
||||
(newAddress) => {
|
||||
if (newAddress) {
|
||||
console.log('Адрес кошелька изменился, обновляем балансы');
|
||||
updateBalances();
|
||||
}
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
// Наблюдаем за изменениями в сообщениях для сортировки и прокрутки
|
||||
watch(() => messages.value.length, (newLength) => {
|
||||
watch(
|
||||
() => messages.value.length,
|
||||
(newLength) => {
|
||||
if (newLength > 0) {
|
||||
// Сортируем сообщения по дате/времени
|
||||
messages.value.sort((a, b) => {
|
||||
@@ -1542,7 +1664,8 @@ watch(() => messages.value.length, (newLength) => {
|
||||
scrollToBottom();
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
// =====================================================================
|
||||
// 8. ЖИЗНЕННЫЙ ЦИКЛ КОМПОНЕНТА
|
||||
@@ -1556,7 +1679,7 @@ onMounted(async () => {
|
||||
console.log('Состояние аутентификации при загрузке:', {
|
||||
isAuthenticated: auth.isAuthenticated.value,
|
||||
authType: auth.authType.value,
|
||||
telegramId: auth.telegramId.value
|
||||
telegramId: auth.telegramId.value,
|
||||
});
|
||||
|
||||
// Загружаем сохраненное состояние боковой панели
|
||||
|
||||
@@ -45,8 +45,8 @@ export default defineConfig({
|
||||
changeOrigin: true,
|
||||
secure: false,
|
||||
credentials: true,
|
||||
rewrite: (path) => path
|
||||
}
|
||||
}
|
||||
rewrite: (path) => path,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user