ваше сообщение коммита
This commit is contained in:
@@ -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', {
|
||||
html: {
|
||||
void: 'always',
|
||||
normal: 'always',
|
||||
component: 'always'
|
||||
}
|
||||
}],
|
||||
'vue/no-v-html': 'off',
|
||||
'vue/html-self-closing': [
|
||||
'warn',
|
||||
{
|
||||
html: {
|
||||
void: 'always',
|
||||
normal: '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,15 +15,15 @@ const api = axios.create({
|
||||
baseURL: getBaseUrl(),
|
||||
withCredentials: true,
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
|
||||
// Перехватчик запросов
|
||||
api.interceptors.request.use(
|
||||
(config) => {
|
||||
config.withCredentials = true; // Важно для каждого запроса
|
||||
|
||||
|
||||
return config;
|
||||
},
|
||||
(error) => Promise.reject(error)
|
||||
@@ -50,11 +50,11 @@ 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);
|
||||
}
|
||||
};
|
||||
|
||||
export default api;
|
||||
export default api;
|
||||
|
||||
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>
|
||||
@@ -40,205 +40,110 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted, computed, defineEmits, watch, inject } from 'vue';
|
||||
import axios from 'axios';
|
||||
import { ref, onMounted, computed, defineEmits, watch, inject } from 'vue';
|
||||
import axios from 'axios';
|
||||
|
||||
const emit = defineEmits(['select-conversation']);
|
||||
const auth = inject('auth');
|
||||
const isAuthenticated = computed(() => auth.isAuthenticated.value);
|
||||
const emit = defineEmits(['select-conversation']);
|
||||
const auth = inject('auth');
|
||||
const isAuthenticated = computed(() => auth.isAuthenticated.value);
|
||||
|
||||
const conversations = ref([]);
|
||||
const loading = ref(true);
|
||||
const selectedConversationId = ref(null);
|
||||
const conversations = ref([]);
|
||||
const loading = ref(true);
|
||||
const selectedConversationId = ref(null);
|
||||
|
||||
// Следим за изменением статуса аутентификации
|
||||
watch(() => isAuthenticated.value, (authenticated) => {
|
||||
if (!authenticated) {
|
||||
conversations.value = []; // Очищаем список бесед при отключении
|
||||
selectedConversationId.value = null;
|
||||
}
|
||||
});
|
||||
|
||||
// Загрузка списка диалогов
|
||||
const fetchConversations = async () => {
|
||||
try {
|
||||
loading.value = true;
|
||||
const response = await axios.get('/api/messages/conversations');
|
||||
conversations.value = response.data;
|
||||
|
||||
// Если есть диалоги и не выбран ни один, выбираем первый
|
||||
if (conversations.value.length > 0 && !selectedConversationId.value) {
|
||||
selectConversation(conversations.value[0].conversation_id);
|
||||
// Следим за изменением статуса аутентификации
|
||||
watch(
|
||||
() => isAuthenticated.value,
|
||||
(authenticated) => {
|
||||
if (!authenticated) {
|
||||
conversations.value = []; // Очищаем список бесед при отключении
|
||||
selectedConversationId.value = null;
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error fetching conversations:', error);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
);
|
||||
|
||||
// Выбор диалога
|
||||
const selectConversation = (conversationId) => {
|
||||
selectedConversationId.value = conversationId;
|
||||
emit('select-conversation', conversationId);
|
||||
};
|
||||
// Загрузка списка диалогов
|
||||
const fetchConversations = async () => {
|
||||
try {
|
||||
loading.value = true;
|
||||
const response = await axios.get('/api/messages/conversations');
|
||||
conversations.value = response.data;
|
||||
|
||||
// Создание нового диалога
|
||||
const createNewConversation = async () => {
|
||||
try {
|
||||
const response = await axios.post('/api/messages/conversations', {
|
||||
title: 'Новый диалог',
|
||||
});
|
||||
// Если есть диалоги и не выбран ни один, выбираем первый
|
||||
if (conversations.value.length > 0 && !selectedConversationId.value) {
|
||||
selectConversation(conversations.value[0].conversation_id);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error fetching conversations:', error);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// Добавляем новый диалог в список
|
||||
const newConversation = {
|
||||
conversation_id: response.data.id,
|
||||
title: response.data.title,
|
||||
username: authStore.username,
|
||||
address: authStore.address,
|
||||
message_count: 0,
|
||||
last_activity: response.data.created_at,
|
||||
created_at: response.data.created_at,
|
||||
};
|
||||
// Выбор диалога
|
||||
const selectConversation = (conversationId) => {
|
||||
selectedConversationId.value = conversationId;
|
||||
emit('select-conversation', conversationId);
|
||||
};
|
||||
|
||||
conversations.value.unshift(newConversation);
|
||||
// Создание нового диалога
|
||||
const createNewConversation = async () => {
|
||||
try {
|
||||
const response = await axios.post('/api/messages/conversations', {
|
||||
title: 'Новый диалог',
|
||||
});
|
||||
|
||||
// Выбираем новый диалог
|
||||
selectConversation(newConversation.conversation_id);
|
||||
} catch (error) {
|
||||
console.error('Error creating conversation:', error);
|
||||
}
|
||||
};
|
||||
// Добавляем новый диалог в список
|
||||
const newConversation = {
|
||||
conversation_id: response.data.id,
|
||||
title: response.data.title,
|
||||
username: authStore.username,
|
||||
address: authStore.address,
|
||||
message_count: 0,
|
||||
last_activity: response.data.created_at,
|
||||
created_at: response.data.created_at,
|
||||
};
|
||||
|
||||
// Форматирование времени
|
||||
const formatTime = (timestamp) => {
|
||||
if (!timestamp) return '';
|
||||
conversations.value.unshift(newConversation);
|
||||
|
||||
const date = new Date(timestamp);
|
||||
const now = new Date();
|
||||
const diffDays = Math.floor((now - date) / (1000 * 60 * 60 * 24));
|
||||
// Выбираем новый диалог
|
||||
selectConversation(newConversation.conversation_id);
|
||||
} catch (error) {
|
||||
console.error('Error creating conversation:', error);
|
||||
}
|
||||
};
|
||||
|
||||
if (diffDays === 0) {
|
||||
// Сегодня - показываем только время
|
||||
return date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
|
||||
} else if (diffDays === 1) {
|
||||
// Вчера
|
||||
return 'Вчера';
|
||||
} else if (diffDays < 7) {
|
||||
// В течение недели - показываем день недели
|
||||
const days = ['Вс', 'Пн', 'Вт', 'Ср', 'Чт', 'Пт', 'Сб'];
|
||||
return days[date.getDay()];
|
||||
} else {
|
||||
// Более недели назад - показываем дату
|
||||
return date.toLocaleDateString();
|
||||
}
|
||||
};
|
||||
// Форматирование времени
|
||||
const formatTime = (timestamp) => {
|
||||
if (!timestamp) return '';
|
||||
|
||||
// Загрузка диалогов при монтировании компонента
|
||||
onMounted(() => {
|
||||
fetchConversations();
|
||||
});
|
||||
const date = new Date(timestamp);
|
||||
const now = new Date();
|
||||
const diffDays = Math.floor((now - date) / (1000 * 60 * 60 * 24));
|
||||
|
||||
// Экспорт методов для использования в родительском компоненте
|
||||
defineExpose({
|
||||
fetchConversations,
|
||||
});
|
||||
if (diffDays === 0) {
|
||||
// Сегодня - показываем только время
|
||||
return date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
|
||||
} else if (diffDays === 1) {
|
||||
// Вчера
|
||||
return 'Вчера';
|
||||
} else if (diffDays < 7) {
|
||||
// В течение недели - показываем день недели
|
||||
const days = ['Вс', 'Пн', 'Вт', 'Ср', 'Чт', 'Пт', 'Сб'];
|
||||
return days[date.getDay()];
|
||||
} else {
|
||||
// Более недели назад - показываем дату
|
||||
return date.toLocaleDateString();
|
||||
}
|
||||
};
|
||||
|
||||
// Загрузка диалогов при монтировании компонента
|
||||
onMounted(() => {
|
||||
fetchConversations();
|
||||
});
|
||||
|
||||
// Экспорт методов для использования в родительском компоненте
|
||||
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>
|
||||
@@ -16,199 +16,149 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, defineEmits, nextTick } from 'vue';
|
||||
import axios from 'axios';
|
||||
import { ref, defineEmits, nextTick } from 'vue';
|
||||
import axios from 'axios';
|
||||
|
||||
const props = defineProps({
|
||||
conversationId: {
|
||||
type: [Number, String],
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
const props = defineProps({
|
||||
conversationId: {
|
||||
type: [Number, String],
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits(['message-sent']);
|
||||
const message = ref('');
|
||||
const sending = ref(false);
|
||||
const textareaRef = ref(null);
|
||||
const emit = defineEmits(['message-sent']);
|
||||
const message = ref('');
|
||||
const sending = ref(false);
|
||||
const textareaRef = ref(null);
|
||||
|
||||
// Обработка нажатия Enter
|
||||
const handleEnter = (event) => {
|
||||
// Если нажат Shift+Enter, добавляем перенос строки
|
||||
if (event.shiftKey) {
|
||||
return;
|
||||
}
|
||||
// Обработка нажатия Enter
|
||||
const handleEnter = (event) => {
|
||||
// Если нажат Shift+Enter, добавляем перенос строки
|
||||
if (event.shiftKey) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Иначе отправляем сообщение
|
||||
sendMessage();
|
||||
};
|
||||
|
||||
// Отправка сообщения
|
||||
const sendMessage = async () => {
|
||||
const messageText = message.value.trim();
|
||||
if (!messageText) return;
|
||||
|
||||
const userMessage = {
|
||||
id: Date.now(),
|
||||
content: messageText,
|
||||
role: auth.isAuthenticated ? 'user' : 'guest',
|
||||
timestamp: new Date().toISOString()
|
||||
// Иначе отправляем сообщение
|
||||
sendMessage();
|
||||
};
|
||||
|
||||
messages.value.push(userMessage);
|
||||
// Отправка сообщения
|
||||
const sendMessage = async () => {
|
||||
const messageText = message.value.trim();
|
||||
if (!messageText) return;
|
||||
|
||||
try {
|
||||
// Логируем параметры запроса
|
||||
console.log('Sending message to Ollama:', {
|
||||
message: messageText,
|
||||
language: userLanguage.value
|
||||
});
|
||||
const userMessage = {
|
||||
id: Date.now(),
|
||||
content: messageText,
|
||||
role: auth.isAuthenticated ? 'user' : 'guest',
|
||||
timestamp: new Date().toISOString(),
|
||||
};
|
||||
|
||||
const response = await axios.post('/api/chat/message', {
|
||||
message: messageText,
|
||||
language: userLanguage.value
|
||||
});
|
||||
messages.value.push(userMessage);
|
||||
|
||||
// Логируем ответ от Ollama
|
||||
console.log('Response from Ollama:', response.data);
|
||||
try {
|
||||
// Логируем параметры запроса
|
||||
console.log('Sending message to Ollama:', {
|
||||
message: messageText,
|
||||
language: userLanguage.value,
|
||||
});
|
||||
|
||||
// Обработка ответа
|
||||
messages.value.push({
|
||||
id: Date.now() + 1,
|
||||
content: response.data.message,
|
||||
role: 'assistant',
|
||||
timestamp: new Date().toISOString()
|
||||
});
|
||||
const response = await axios.post('/api/chat/message', {
|
||||
message: messageText,
|
||||
language: userLanguage.value,
|
||||
});
|
||||
|
||||
// Логируем ответ от Ollama
|
||||
console.log('Response from Ollama:', response.data);
|
||||
|
||||
// Обработка ответа
|
||||
messages.value.push({
|
||||
id: Date.now() + 1,
|
||||
content: response.data.message,
|
||||
role: 'assistant',
|
||||
timestamp: new Date().toISOString(),
|
||||
});
|
||||
|
||||
// Очищаем поле ввода
|
||||
message.value = '';
|
||||
|
||||
// Фокусируемся на поле ввода
|
||||
nextTick(() => {
|
||||
textareaRef.value.focus();
|
||||
});
|
||||
|
||||
// Уведомляем родительский компонент о новых сообщениях
|
||||
emit('message-sent', [response.data.userMessage, response.data.aiMessage]);
|
||||
} catch (error) {
|
||||
console.error('Ошибка при отправке сообщения:', error);
|
||||
} finally {
|
||||
sending.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// Сброс поля ввода
|
||||
const resetInput = () => {
|
||||
message.value = '';
|
||||
};
|
||||
|
||||
// Экспорт методов для использования в родительском компоненте
|
||||
defineExpose({
|
||||
resetInput,
|
||||
focus: () => textareaRef.value?.focus(),
|
||||
});
|
||||
|
||||
const sendGuestMessage = async (messageText) => {
|
||||
if (!messageText.trim()) return;
|
||||
|
||||
const userMessage = {
|
||||
id: Date.now(),
|
||||
content: messageText,
|
||||
role: 'user',
|
||||
timestamp: new Date().toISOString(),
|
||||
isGuest: true,
|
||||
};
|
||||
|
||||
// Добавляем сообщение пользователя в локальную историю
|
||||
messages.value.push(userMessage);
|
||||
|
||||
// Сохраняем сообщение в массиве гостевых сообщений
|
||||
guestMessages.value.push(userMessage);
|
||||
|
||||
// Сохраняем гостевые сообщения в localStorage
|
||||
localStorage.setItem('guestMessages', JSON.stringify(guestMessages.value));
|
||||
|
||||
// Очищаем поле ввода
|
||||
message.value = '';
|
||||
newMessage.value = '';
|
||||
|
||||
// Фокусируемся на поле ввода
|
||||
nextTick(() => {
|
||||
textareaRef.value.focus();
|
||||
});
|
||||
// Прокрутка вниз
|
||||
await nextTick();
|
||||
if (messagesContainer.value) {
|
||||
messagesContainer.value.scrollTop = messagesContainer.value.scrollHeight;
|
||||
}
|
||||
|
||||
// Уведомляем родительский компонент о новых сообщениях
|
||||
emit('message-sent', [response.data.userMessage, response.data.aiMessage]);
|
||||
} catch (error) {
|
||||
console.error('Ошибка при отправке сообщения:', error);
|
||||
} finally {
|
||||
sending.value = false;
|
||||
}
|
||||
};
|
||||
// Устанавливаем состояние загрузки
|
||||
isLoading.value = true;
|
||||
|
||||
// Сброс поля ввода
|
||||
const resetInput = () => {
|
||||
message.value = '';
|
||||
};
|
||||
// Вместо отправки запроса к Ollama, отправляем сообщение с кнопками для аутентификации
|
||||
const authMessage = {
|
||||
id: Date.now() + 1,
|
||||
content: 'Чтобы продолжить, пожалуйста, аутентифицируйтесь.',
|
||||
role: 'assistant',
|
||||
timestamp: new Date().toISOString(),
|
||||
isGuest: true,
|
||||
showAuthOptions: true, // Указываем, что нужно показать кнопки аутентификации
|
||||
};
|
||||
|
||||
// Экспорт методов для использования в родительском компоненте
|
||||
defineExpose({
|
||||
resetInput,
|
||||
focus: () => textareaRef.value?.focus(),
|
||||
});
|
||||
messages.value.push(authMessage);
|
||||
guestMessages.value.push(authMessage);
|
||||
localStorage.setItem('guestMessages', JSON.stringify(guestMessages.value));
|
||||
|
||||
const sendGuestMessage = async (messageText) => {
|
||||
if (!messageText.trim()) return;
|
||||
// Прокрутка вниз
|
||||
await nextTick();
|
||||
if (messagesContainer.value) {
|
||||
messagesContainer.value.scrollTop = messagesContainer.value.scrollHeight;
|
||||
}
|
||||
|
||||
const userMessage = {
|
||||
id: Date.now(),
|
||||
content: messageText,
|
||||
role: 'user',
|
||||
timestamp: new Date().toISOString(),
|
||||
isGuest: true
|
||||
isLoading.value = false;
|
||||
};
|
||||
|
||||
// Добавляем сообщение пользователя в локальную историю
|
||||
messages.value.push(userMessage);
|
||||
|
||||
// Сохраняем сообщение в массиве гостевых сообщений
|
||||
guestMessages.value.push(userMessage);
|
||||
|
||||
// Сохраняем гостевые сообщения в localStorage
|
||||
localStorage.setItem('guestMessages', JSON.stringify(guestMessages.value));
|
||||
|
||||
// Очищаем поле ввода
|
||||
newMessage.value = '';
|
||||
|
||||
// Прокрутка вниз
|
||||
await nextTick();
|
||||
if (messagesContainer.value) {
|
||||
messagesContainer.value.scrollTop = messagesContainer.value.scrollHeight;
|
||||
}
|
||||
|
||||
// Устанавливаем состояние загрузки
|
||||
isLoading.value = true;
|
||||
|
||||
// Вместо отправки запроса к Ollama, отправляем сообщение с кнопками для аутентификации
|
||||
const authMessage = {
|
||||
id: Date.now() + 1,
|
||||
content: 'Чтобы продолжить, пожалуйста, аутентифицируйтесь.',
|
||||
role: 'assistant',
|
||||
timestamp: new Date().toISOString(),
|
||||
isGuest: true,
|
||||
showAuthOptions: true // Указываем, что нужно показать кнопки аутентификации
|
||||
};
|
||||
|
||||
messages.value.push(authMessage);
|
||||
guestMessages.value.push(authMessage);
|
||||
localStorage.setItem('guestMessages', JSON.stringify(guestMessages.value));
|
||||
|
||||
// Прокрутка вниз
|
||||
await nextTick();
|
||||
if (messagesContainer.value) {
|
||||
messagesContainer.value.scrollTop = messagesContainer.value.scrollHeight;
|
||||
}
|
||||
|
||||
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>
|
||||
@@ -26,188 +30,110 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted, watch, nextTick, defineExpose } from 'vue';
|
||||
import axios from 'axios';
|
||||
import { ref, onMounted, watch, nextTick, defineExpose } from 'vue';
|
||||
import axios from 'axios';
|
||||
|
||||
const props = defineProps({
|
||||
conversationId: {
|
||||
type: [Number, String],
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
|
||||
const messages = ref([]);
|
||||
const loading = ref(true);
|
||||
const threadContainer = ref(null);
|
||||
const isAuthenticated = ref(false);
|
||||
|
||||
// Загрузка сообщений диалога
|
||||
const fetchMessages = async () => {
|
||||
try {
|
||||
loading.value = true;
|
||||
const response = await axios.get(
|
||||
`/api/messages/conversations/${props.conversationId}/messages`
|
||||
);
|
||||
messages.value = response.data;
|
||||
|
||||
// Прокрутка к последнему сообщению
|
||||
await nextTick();
|
||||
scrollToBottom();
|
||||
} catch (error) {
|
||||
console.error('Error fetching messages:', error);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// Добавление новых сообщений
|
||||
const addMessages = (newMessages) => {
|
||||
if (Array.isArray(newMessages)) {
|
||||
messages.value = [...messages.value, ...newMessages];
|
||||
} else {
|
||||
messages.value.push(newMessages);
|
||||
}
|
||||
|
||||
// Прокрутка к последнему сообщению
|
||||
nextTick(() => {
|
||||
scrollToBottom();
|
||||
const props = defineProps({
|
||||
conversationId: {
|
||||
type: [Number, String],
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
// Прокрутка к последнему сообщению
|
||||
const scrollToBottom = () => {
|
||||
if (threadContainer.value) {
|
||||
threadContainer.value.scrollTop = threadContainer.value.scrollHeight;
|
||||
}
|
||||
};
|
||||
const messages = ref([]);
|
||||
const loading = ref(true);
|
||||
const threadContainer = ref(null);
|
||||
const isAuthenticated = ref(false);
|
||||
|
||||
// Форматирование времени
|
||||
const formatTime = (timestamp) => {
|
||||
if (!timestamp) return '';
|
||||
// Загрузка сообщений диалога
|
||||
const fetchMessages = async () => {
|
||||
try {
|
||||
loading.value = true;
|
||||
const response = await axios.get(
|
||||
`/api/messages/conversations/${props.conversationId}/messages`
|
||||
);
|
||||
messages.value = response.data;
|
||||
|
||||
const date = new Date(timestamp);
|
||||
return date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
|
||||
};
|
||||
|
||||
// Получение названия канала
|
||||
const channelName = (channel) => {
|
||||
const channels = {
|
||||
web: 'Веб',
|
||||
telegram: 'Telegram',
|
||||
email: 'Email',
|
||||
// Прокрутка к последнему сообщению
|
||||
await nextTick();
|
||||
scrollToBottom();
|
||||
} catch (error) {
|
||||
console.error('Error fetching messages:', error);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
return channels[channel] || channel;
|
||||
};
|
||||
// Добавление новых сообщений
|
||||
const addMessages = (newMessages) => {
|
||||
if (Array.isArray(newMessages)) {
|
||||
messages.value = [...messages.value, ...newMessages];
|
||||
} else {
|
||||
messages.value.push(newMessages);
|
||||
}
|
||||
|
||||
// Наблюдение за изменением ID диалога
|
||||
watch(
|
||||
() => props.conversationId,
|
||||
(newId, oldId) => {
|
||||
if (newId && newId !== oldId) {
|
||||
// Прокрутка к последнему сообщению
|
||||
nextTick(() => {
|
||||
scrollToBottom();
|
||||
});
|
||||
};
|
||||
|
||||
// Прокрутка к последнему сообщению
|
||||
const scrollToBottom = () => {
|
||||
if (threadContainer.value) {
|
||||
threadContainer.value.scrollTop = threadContainer.value.scrollHeight;
|
||||
}
|
||||
};
|
||||
|
||||
// Форматирование времени
|
||||
const formatTime = (timestamp) => {
|
||||
if (!timestamp) return '';
|
||||
|
||||
const date = new Date(timestamp);
|
||||
return date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
|
||||
};
|
||||
|
||||
// Получение названия канала
|
||||
const channelName = (channel) => {
|
||||
const channels = {
|
||||
web: 'Веб',
|
||||
telegram: 'Telegram',
|
||||
email: 'Email',
|
||||
};
|
||||
|
||||
return channels[channel] || channel;
|
||||
};
|
||||
|
||||
// Наблюдение за изменением ID диалога
|
||||
watch(
|
||||
() => props.conversationId,
|
||||
(newId, oldId) => {
|
||||
if (newId && newId !== oldId) {
|
||||
fetchMessages();
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// Следим за изменением статуса аутентификации
|
||||
watch(
|
||||
() => isAuthenticated.value,
|
||||
(authenticated) => {
|
||||
if (!authenticated) {
|
||||
messages.value = []; // Очищаем сообщения при отключении
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// Загрузка сообщений при монтировании компонента
|
||||
onMounted(() => {
|
||||
if (props.conversationId) {
|
||||
fetchMessages();
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
// Следим за изменением статуса аутентификации
|
||||
watch(() => isAuthenticated.value, (authenticated) => {
|
||||
if (!authenticated) {
|
||||
messages.value = []; // Очищаем сообщения при отключении
|
||||
}
|
||||
});
|
||||
|
||||
// Загрузка сообщений при монтировании компонента
|
||||
onMounted(() => {
|
||||
if (props.conversationId) {
|
||||
fetchMessages();
|
||||
}
|
||||
});
|
||||
|
||||
// Экспорт методов для использования в родительском компоненте
|
||||
defineExpose({
|
||||
fetchMessages,
|
||||
addMessages,
|
||||
});
|
||||
// Экспорт методов для использования в родительском компоненте
|
||||
defineExpose({
|
||||
fetchMessages,
|
||||
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,173 +1,90 @@
|
||||
<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>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed } from 'vue';
|
||||
import axios from '@/api/axios';
|
||||
import { useAuth } from '@/composables/useAuth';
|
||||
import { ref, computed } from 'vue';
|
||||
import axios from '@/api/axios';
|
||||
import { useAuth } from '@/composables/useAuth';
|
||||
|
||||
const emit = defineEmits(['close']);
|
||||
const { linkIdentity } = useAuth();
|
||||
const emit = defineEmits(['close']);
|
||||
const { linkIdentity } = useAuth();
|
||||
|
||||
const email = ref('');
|
||||
const code = ref('');
|
||||
const error = ref('');
|
||||
const isLoading = ref(false);
|
||||
const showVerification = ref(false);
|
||||
const email = ref('');
|
||||
const code = ref('');
|
||||
const error = ref('');
|
||||
const isLoading = ref(false);
|
||||
const showVerification = ref(false);
|
||||
|
||||
const isValidEmail = computed(() => {
|
||||
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email.value);
|
||||
});
|
||||
const isValidEmail = computed(() => {
|
||||
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email.value);
|
||||
});
|
||||
|
||||
const requestCode = async () => {
|
||||
try {
|
||||
isLoading.value = true;
|
||||
error.value = '';
|
||||
|
||||
const response = await axios.post('/api/auth/email/request-verification', {
|
||||
email: email.value
|
||||
});
|
||||
|
||||
if (response.data.success) {
|
||||
showVerification.value = true;
|
||||
} else {
|
||||
error.value = response.data.error || 'Ошибка отправки кода';
|
||||
const requestCode = async () => {
|
||||
try {
|
||||
isLoading.value = true;
|
||||
error.value = '';
|
||||
|
||||
const response = await axios.post('/api/auth/email/request-verification', {
|
||||
email: email.value,
|
||||
});
|
||||
|
||||
if (response.data.success) {
|
||||
showVerification.value = true;
|
||||
} else {
|
||||
error.value = response.data.error || 'Ошибка отправки кода';
|
||||
}
|
||||
} catch (err) {
|
||||
error.value = err.response?.data?.error || 'Ошибка отправки кода';
|
||||
} finally {
|
||||
isLoading.value = false;
|
||||
}
|
||||
} catch (err) {
|
||||
error.value = err.response?.data?.error || 'Ошибка отправки кода';
|
||||
} finally {
|
||||
isLoading.value = false;
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
const verifyCode = async () => {
|
||||
try {
|
||||
isLoading.value = true;
|
||||
error.value = '';
|
||||
|
||||
const response = await axios.post('/api/auth/email/verify', {
|
||||
email: email.value,
|
||||
code: code.value
|
||||
});
|
||||
|
||||
if (response.data.success) {
|
||||
// Связываем email с текущим пользователем
|
||||
await linkIdentity('email', email.value);
|
||||
emit('close');
|
||||
} else {
|
||||
error.value = response.data.error || 'Неверный код';
|
||||
const verifyCode = async () => {
|
||||
try {
|
||||
isLoading.value = true;
|
||||
error.value = '';
|
||||
|
||||
const response = await axios.post('/api/auth/email/verify', {
|
||||
email: email.value,
|
||||
code: code.value,
|
||||
});
|
||||
|
||||
if (response.data.success) {
|
||||
// Связываем email с текущим пользователем
|
||||
await linkIdentity('email', email.value);
|
||||
emit('close');
|
||||
} else {
|
||||
error.value = response.data.error || 'Неверный код';
|
||||
}
|
||||
} catch (err) {
|
||||
error.value = err.response?.data?.error || 'Ошибка проверки кода';
|
||||
} finally {
|
||||
isLoading.value = false;
|
||||
}
|
||||
} catch (err) {
|
||||
error.value = err.response?.data?.error || 'Ошибка проверки кода';
|
||||
} finally {
|
||||
isLoading.value = false;
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
const resetForm = () => {
|
||||
email.value = '';
|
||||
code.value = '';
|
||||
error.value = '';
|
||||
showVerification.value = false;
|
||||
};
|
||||
const resetForm = () => {
|
||||
email.value = '';
|
||||
code.value = '';
|
||||
error.value = '';
|
||||
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>
|
||||
@@ -25,183 +22,87 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted, onUnmounted } from 'vue';
|
||||
import axios from '@/api/axios';
|
||||
import { useAuth } from '@/composables/useAuth';
|
||||
import QRCode from 'qrcode';
|
||||
import { ref, onMounted, onUnmounted } from 'vue';
|
||||
import axios from '@/api/axios';
|
||||
import { useAuth } from '@/composables/useAuth';
|
||||
import QRCode from 'qrcode';
|
||||
|
||||
const emit = defineEmits(['close']);
|
||||
const { linkIdentity } = useAuth();
|
||||
const emit = defineEmits(['close']);
|
||||
const { linkIdentity } = useAuth();
|
||||
|
||||
const loading = ref(false);
|
||||
const error = ref('');
|
||||
const showQR = ref(false);
|
||||
const qrCode = ref('');
|
||||
const botLink = ref('');
|
||||
const pollInterval = ref(null);
|
||||
const connectionToken = ref('');
|
||||
const loading = ref(false);
|
||||
const error = ref('');
|
||||
const showQR = ref(false);
|
||||
const qrCode = ref('');
|
||||
const botLink = ref('');
|
||||
const pollInterval = ref(null);
|
||||
const connectionToken = ref('');
|
||||
|
||||
const startConnection = async () => {
|
||||
try {
|
||||
loading.value = true;
|
||||
const startConnection = async () => {
|
||||
try {
|
||||
loading.value = true;
|
||||
error.value = '';
|
||||
|
||||
const response = await axios.post('/api/auth/telegram/start-connection');
|
||||
|
||||
if (response.data.success) {
|
||||
connectionToken.value = response.data.token;
|
||||
botLink.value = `https://t.me/${response.data.botUsername}?start=${connectionToken.value}`;
|
||||
|
||||
// Генерируем QR-код
|
||||
const qr = await QRCode.toDataURL(botLink.value);
|
||||
qrCode.value = `<img src="${qr}" alt="Telegram QR Code" />`;
|
||||
|
||||
showQR.value = true;
|
||||
startPolling();
|
||||
} else {
|
||||
error.value = response.data.error || 'Не удалось начать процесс подключения';
|
||||
}
|
||||
} catch (err) {
|
||||
error.value = err.response?.data?.error || 'Ошибка при подключении Telegram';
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const checkConnection = async () => {
|
||||
try {
|
||||
const response = await axios.post('/api/auth/telegram/check-connection', {
|
||||
token: connectionToken.value,
|
||||
});
|
||||
|
||||
if (response.data.success && response.data.telegramId) {
|
||||
// Связываем Telegram с текущим пользователем
|
||||
await linkIdentity('telegram', response.data.telegramId);
|
||||
stopPolling();
|
||||
emit('close');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error checking connection:', error);
|
||||
}
|
||||
};
|
||||
|
||||
const startPolling = () => {
|
||||
pollInterval.value = setInterval(checkConnection, 2000);
|
||||
};
|
||||
|
||||
const stopPolling = () => {
|
||||
if (pollInterval.value) {
|
||||
clearInterval(pollInterval.value);
|
||||
pollInterval.value = null;
|
||||
}
|
||||
};
|
||||
|
||||
const resetConnection = () => {
|
||||
stopPolling();
|
||||
showQR.value = false;
|
||||
error.value = '';
|
||||
|
||||
const response = await axios.post('/api/auth/telegram/start-connection');
|
||||
|
||||
if (response.data.success) {
|
||||
connectionToken.value = response.data.token;
|
||||
botLink.value = `https://t.me/${response.data.botUsername}?start=${connectionToken.value}`;
|
||||
|
||||
// Генерируем QR-код
|
||||
const qr = await QRCode.toDataURL(botLink.value);
|
||||
qrCode.value = `<img src="${qr}" alt="Telegram QR Code" />`;
|
||||
|
||||
showQR.value = true;
|
||||
startPolling();
|
||||
} else {
|
||||
error.value = response.data.error || 'Не удалось начать процесс подключения';
|
||||
}
|
||||
} catch (err) {
|
||||
error.value = err.response?.data?.error || 'Ошибка при подключении Telegram';
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
qrCode.value = '';
|
||||
botLink.value = '';
|
||||
connectionToken.value = '';
|
||||
};
|
||||
|
||||
const checkConnection = async () => {
|
||||
try {
|
||||
const response = await axios.post('/api/auth/telegram/check-connection', {
|
||||
token: connectionToken.value
|
||||
});
|
||||
|
||||
if (response.data.success && response.data.telegramId) {
|
||||
// Связываем Telegram с текущим пользователем
|
||||
await linkIdentity('telegram', response.data.telegramId);
|
||||
stopPolling();
|
||||
emit('close');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error checking connection:', error);
|
||||
}
|
||||
};
|
||||
|
||||
const startPolling = () => {
|
||||
pollInterval.value = setInterval(checkConnection, 2000);
|
||||
};
|
||||
|
||||
const stopPolling = () => {
|
||||
if (pollInterval.value) {
|
||||
clearInterval(pollInterval.value);
|
||||
pollInterval.value = null;
|
||||
}
|
||||
};
|
||||
|
||||
const resetConnection = () => {
|
||||
stopPolling();
|
||||
showQR.value = false;
|
||||
error.value = '';
|
||||
qrCode.value = '';
|
||||
botLink.value = '';
|
||||
connectionToken.value = '';
|
||||
};
|
||||
|
||||
onUnmounted(() => {
|
||||
stopPolling();
|
||||
});
|
||||
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>
|
||||
@@ -20,106 +16,48 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed } from 'vue';
|
||||
import { useAuth } from '@/composables/useAuth';
|
||||
import { connectWithWallet } from '@/services/wallet';
|
||||
import { ref, computed } from 'vue';
|
||||
import { useAuth } from '@/composables/useAuth';
|
||||
import { connectWithWallet } from '@/services/wallet';
|
||||
|
||||
const emit = defineEmits(['close']);
|
||||
const { linkIdentity } = useAuth();
|
||||
const emit = defineEmits(['close']);
|
||||
const { linkIdentity } = useAuth();
|
||||
|
||||
const isLoading = ref(false);
|
||||
const error = ref('');
|
||||
const address = ref('');
|
||||
const isLoading = ref(false);
|
||||
const error = ref('');
|
||||
const address = ref('');
|
||||
|
||||
const isConnected = computed(() => !!address.value);
|
||||
const isConnected = computed(() => !!address.value);
|
||||
|
||||
const formatAddress = (addr) => {
|
||||
if (!addr) return '';
|
||||
return `${addr.slice(0, 6)}...${addr.slice(-4)}`;
|
||||
};
|
||||
const formatAddress = (addr) => {
|
||||
if (!addr) return '';
|
||||
return `${addr.slice(0, 6)}...${addr.slice(-4)}`;
|
||||
};
|
||||
|
||||
const connectWallet = async () => {
|
||||
if (isLoading.value) return;
|
||||
|
||||
try {
|
||||
isLoading.value = true;
|
||||
error.value = '';
|
||||
|
||||
// Подключаем кошелек
|
||||
const result = await connectWithWallet();
|
||||
|
||||
if (result.success) {
|
||||
address.value = result.address;
|
||||
|
||||
// Связываем кошелек с текущим пользователем
|
||||
await linkIdentity('wallet', result.address);
|
||||
emit('close');
|
||||
} else {
|
||||
error.value = result.error || 'Не удалось подключить кошелек';
|
||||
const connectWallet = async () => {
|
||||
if (isLoading.value) return;
|
||||
|
||||
try {
|
||||
isLoading.value = true;
|
||||
error.value = '';
|
||||
|
||||
// Подключаем кошелек
|
||||
const result = await connectWithWallet();
|
||||
|
||||
if (result.success) {
|
||||
address.value = result.address;
|
||||
|
||||
// Связываем кошелек с текущим пользователем
|
||||
await linkIdentity('wallet', result.address);
|
||||
emit('close');
|
||||
} else {
|
||||
error.value = result.error || 'Не удалось подключить кошелек';
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Error connecting wallet:', err);
|
||||
error.value = err.message || 'Произошла ошибка при подключении кошелька';
|
||||
} finally {
|
||||
isLoading.value = false;
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Error connecting wallet:', err);
|
||||
error.value = err.message || 'Произошла ошибка при подключении кошелька';
|
||||
} finally {
|
||||
isLoading.value = false;
|
||||
}
|
||||
};
|
||||
};
|
||||
</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 };
|
||||
|
||||
@@ -12,26 +12,26 @@ export function useAuth() {
|
||||
const processedGuestIds = ref([]);
|
||||
const identities = ref([]);
|
||||
const tokenBalances = ref([]);
|
||||
|
||||
|
||||
// Функция для обновления списка идентификаторов
|
||||
const updateIdentities = async () => {
|
||||
if (!isAuthenticated.value || !userId.value) return;
|
||||
|
||||
|
||||
try {
|
||||
const response = await axios.get('/api/auth/identities');
|
||||
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);
|
||||
}
|
||||
return acc;
|
||||
}, []);
|
||||
|
||||
|
||||
identities.value = filteredIdentities;
|
||||
console.log('User identities updated:', identities.value);
|
||||
}
|
||||
@@ -39,7 +39,7 @@ export function useAuth() {
|
||||
console.error('Error fetching user identities:', error);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// Периодическое обновление идентификаторов
|
||||
let identitiesInterval;
|
||||
|
||||
@@ -54,7 +54,7 @@ export function useAuth() {
|
||||
identitiesInterval = null;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
const checkTokenBalances = async (address) => {
|
||||
try {
|
||||
const response = await axios.get(`/api/auth/check-tokens/${address}`);
|
||||
@@ -68,21 +68,29 @@ export function useAuth() {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
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;
|
||||
|
||||
console.log('updateAuth called with:', {
|
||||
authenticated,
|
||||
newAuthType,
|
||||
newUserId,
|
||||
newAddress,
|
||||
newTelegramId,
|
||||
newIsAdmin,
|
||||
newEmail
|
||||
|
||||
console.log('updateAuth called with:', {
|
||||
authenticated,
|
||||
newAuthType,
|
||||
newUserId,
|
||||
newAddress,
|
||||
newTelegramId,
|
||||
newIsAdmin,
|
||||
newEmail,
|
||||
});
|
||||
|
||||
|
||||
// Убедимся, что переменные являются реактивными
|
||||
isAuthenticated.value = authenticated === true;
|
||||
authType.value = newAuthType || null;
|
||||
@@ -91,23 +99,26 @@ export function useAuth() {
|
||||
telegramId.value = newTelegramId || null;
|
||||
isAdmin.value = newIsAdmin === true;
|
||||
email.value = newEmail || null;
|
||||
|
||||
|
||||
// Кэшируем данные аутентификации
|
||||
localStorage.setItem('authData', JSON.stringify({
|
||||
authenticated,
|
||||
authType: newAuthType,
|
||||
userId: newUserId,
|
||||
address: newAddress,
|
||||
telegramId: newTelegramId,
|
||||
isAdmin: newIsAdmin,
|
||||
email: newEmail
|
||||
}));
|
||||
|
||||
localStorage.setItem(
|
||||
'authData',
|
||||
JSON.stringify({
|
||||
authenticated,
|
||||
authType: newAuthType,
|
||||
userId: newUserId,
|
||||
address: newAddress,
|
||||
telegramId: newTelegramId,
|
||||
isAdmin: newIsAdmin,
|
||||
email: newEmail,
|
||||
})
|
||||
);
|
||||
|
||||
// Если аутентификация через кошелек, проверяем баланс токенов только при изменении адреса
|
||||
if (authenticated && newAuthType === 'wallet' && newAddress && newAddress !== address.value) {
|
||||
await checkTokenBalances(newAddress);
|
||||
}
|
||||
|
||||
|
||||
// Обновляем идентификаторы при любом изменении аутентификации
|
||||
if (authenticated) {
|
||||
await updateIdentities();
|
||||
@@ -116,63 +127,63 @@ export function useAuth() {
|
||||
stopIdentitiesPolling();
|
||||
identities.value = [];
|
||||
}
|
||||
|
||||
console.log('Auth updated:', {
|
||||
|
||||
console.log('Auth updated:', {
|
||||
authenticated: isAuthenticated.value,
|
||||
userId: userId.value,
|
||||
address: address.value,
|
||||
telegramId: telegramId.value,
|
||||
telegramId: telegramId.value,
|
||||
email: email.value,
|
||||
isAdmin: isAdmin.value
|
||||
isAdmin: isAdmin.value,
|
||||
});
|
||||
|
||||
// Если пользователь только что аутентифицировался или сменил аккаунт,
|
||||
|
||||
// Если пользователь только что аутентифицировался или сменил аккаунт,
|
||||
// пробуем связать сообщения
|
||||
if (authenticated && (!wasAuthenticated || (previousUserId && previousUserId !== newUserId))) {
|
||||
console.log('Auth change detected, linking messages');
|
||||
linkMessages();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// Функция для связывания сообщений после успешной авторизации
|
||||
const linkMessages = async () => {
|
||||
try {
|
||||
if (isAuthenticated.value) {
|
||||
console.log('Linking messages after authentication');
|
||||
|
||||
|
||||
// Проверка, есть ли гостевой ID для обработки
|
||||
const localGuestId = localStorage.getItem('guestId');
|
||||
|
||||
|
||||
// Если гостевого ID нет или он уже был обработан, пропускаем запрос
|
||||
if (!localGuestId || processedGuestIds.value.includes(localGuestId)) {
|
||||
console.log('No new guest IDs to process or already processed');
|
||||
return {
|
||||
success: true,
|
||||
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,
|
||||
};
|
||||
|
||||
|
||||
// Добавляем все доступные идентификаторы
|
||||
if (address.value) identifiersData.address = address.value;
|
||||
if (email.value) identifiersData.email = email.value;
|
||||
if (telegramId.value) identifiersData.telegramId = telegramId.value;
|
||||
|
||||
|
||||
console.log('Sending link-guest-messages request with data:', identifiersData);
|
||||
|
||||
|
||||
try {
|
||||
// Отправляем запрос на связывание сообщений
|
||||
const response = await axios.post('/api/auth/link-guest-messages', identifiersData);
|
||||
|
||||
|
||||
if (response.data.success) {
|
||||
console.log('Messages linked successfully:', response.data);
|
||||
|
||||
|
||||
// Обновляем список обработанных guestIds из ответа сервера
|
||||
if (response.data.processedIds && Array.isArray(response.data.processedIds)) {
|
||||
processedGuestIds.value = [...response.data.processedIds];
|
||||
@@ -181,49 +192,51 @@ 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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Очищаем гостевые сообщения из localStorage после успешного связывания
|
||||
localStorage.removeItem('guestMessages');
|
||||
localStorage.removeItem('guestId');
|
||||
|
||||
|
||||
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,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return { success: false, message: 'Not authenticated' };
|
||||
} catch (error) {
|
||||
console.error('Error in linkMessages:', error);
|
||||
return { success: false, error: error.message };
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
const checkAuth = async () => {
|
||||
try {
|
||||
const response = await axios.get('/api/auth/check');
|
||||
console.log('Auth check response:', response.data);
|
||||
|
||||
|
||||
const wasAuthenticated = isAuthenticated.value;
|
||||
const previousUserId = userId.value;
|
||||
const previousAuthType = authType.value;
|
||||
|
||||
|
||||
// Обновляем данные авторизации через updateAuth вместо прямого изменения
|
||||
await updateAuth({
|
||||
authenticated: response.data.authenticated,
|
||||
@@ -232,21 +245,21 @@ export function useAuth() {
|
||||
address: response.data.address,
|
||||
telegramId: response.data.telegramId,
|
||||
email: response.data.email,
|
||||
isAdmin: response.data.isAdmin
|
||||
isAdmin: response.data.isAdmin,
|
||||
});
|
||||
|
||||
|
||||
// Если пользователь аутентифицирован, обновляем список идентификаторов и связываем сообщения
|
||||
if (response.data.authenticated) {
|
||||
// Сначала обновляем идентификаторы, чтобы иметь актуальные данные
|
||||
await updateIdentities();
|
||||
|
||||
|
||||
// Если пользователь только что аутентифицировался или сменил аккаунт,
|
||||
// связываем гостевые сообщения с его аккаунтом
|
||||
if (!wasAuthenticated || (previousUserId && previousUserId !== response.data.userId)) {
|
||||
// Немедленно связываем сообщения
|
||||
const linkResult = await linkMessages();
|
||||
console.log('Link messages result on auth change:', linkResult);
|
||||
|
||||
|
||||
// Если пользователь только что аутентифицировался через Telegram,
|
||||
// обновляем историю чата без перезагрузки страницы
|
||||
if (response.data.authType === 'telegram' && previousAuthType !== 'telegram') {
|
||||
@@ -255,14 +268,14 @@ export function useAuth() {
|
||||
window.dispatchEvent(new CustomEvent('load-chat-history'));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Обновляем отображение подключенного состояния в UI
|
||||
updateConnectionDisplay(true, response.data.authType, response.data);
|
||||
} else {
|
||||
// Обновляем отображение отключенного состояния
|
||||
updateConnectionDisplay(false);
|
||||
}
|
||||
|
||||
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error('Error checking auth:', error);
|
||||
@@ -271,30 +284,30 @@ export function useAuth() {
|
||||
return { authenticated: false };
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
const disconnect = async () => {
|
||||
try {
|
||||
// Удаляем все идентификаторы перед выходом
|
||||
await axios.post('/api/auth/logout');
|
||||
|
||||
|
||||
// Обновляем состояние в памяти
|
||||
updateAuth({
|
||||
authenticated: false,
|
||||
authType: null,
|
||||
userId: null,
|
||||
address: null,
|
||||
updateAuth({
|
||||
authenticated: false,
|
||||
authType: null,
|
||||
userId: null,
|
||||
address: null,
|
||||
telegramId: null,
|
||||
email: null,
|
||||
isAdmin: false
|
||||
isAdmin: false,
|
||||
});
|
||||
|
||||
|
||||
// Обновляем отображение отключенного состояния
|
||||
updateConnectionDisplay(false);
|
||||
|
||||
|
||||
// Очищаем списки идентификаторов
|
||||
identities.value = [];
|
||||
processedGuestIds.value = [];
|
||||
|
||||
|
||||
// Очищаем localStorage полностью
|
||||
localStorage.removeItem('isAuthenticated');
|
||||
localStorage.removeItem('userId');
|
||||
@@ -304,38 +317,38 @@ export function useAuth() {
|
||||
localStorage.removeItem('guestMessages');
|
||||
localStorage.removeItem('telegramId');
|
||||
localStorage.removeItem('email');
|
||||
|
||||
|
||||
// Удаляем класс подключенного кошелька
|
||||
document.body.classList.remove('wallet-connected');
|
||||
|
||||
|
||||
console.log('User disconnected successfully and all identifiers cleared');
|
||||
|
||||
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
console.error('Error disconnecting:', error);
|
||||
return { success: false, error: error.message };
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// Обновляем список обработанных guestIds
|
||||
const updateProcessedGuestIds = (ids) => {
|
||||
if (Array.isArray(ids)) {
|
||||
processedGuestIds.value = [...new Set([...processedGuestIds.value, ...ids])];
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// Функция для обновления отображения подключения в UI
|
||||
const updateConnectionDisplay = (isConnected, authType, authData = {}) => {
|
||||
try {
|
||||
console.log('Updating connection display:', { isConnected, authType, authData });
|
||||
|
||||
|
||||
if (isConnected) {
|
||||
document.body.classList.add('wallet-connected');
|
||||
|
||||
|
||||
const authDisplayEl = document.getElementById('auth-display');
|
||||
if (authDisplayEl) {
|
||||
let displayText = 'Подключено';
|
||||
|
||||
|
||||
if (authType === 'wallet' && authData.address) {
|
||||
const shortAddress = `${authData.address.substring(0, 6)}...${authData.address.substring(authData.address.length - 4)}`;
|
||||
displayText = `Кошелек: <strong>${shortAddress}</strong>`;
|
||||
@@ -344,30 +357,30 @@ export function useAuth() {
|
||||
} else if (authType === 'telegram' && authData.telegramId) {
|
||||
displayText = `Telegram: <strong>${authData.telegramUsername || authData.telegramId}</strong>`;
|
||||
}
|
||||
|
||||
|
||||
authDisplayEl.innerHTML = displayText;
|
||||
authDisplayEl.style.display = 'inline-block';
|
||||
}
|
||||
|
||||
|
||||
// Скрываем кнопки авторизации и показываем кнопку выхода
|
||||
const authButtonsEl = document.getElementById('auth-buttons');
|
||||
const logoutButtonEl = document.getElementById('logout-button');
|
||||
|
||||
|
||||
if (authButtonsEl) authButtonsEl.style.display = 'none';
|
||||
if (logoutButtonEl) logoutButtonEl.style.display = 'inline-block';
|
||||
} else {
|
||||
document.body.classList.remove('wallet-connected');
|
||||
|
||||
|
||||
// Скрываем отображение аутентификации
|
||||
const authDisplayEl = document.getElementById('auth-display');
|
||||
if (authDisplayEl) {
|
||||
authDisplayEl.style.display = 'none';
|
||||
}
|
||||
|
||||
|
||||
// Показываем кнопки авторизации и скрываем кнопку выхода
|
||||
const authButtonsEl = document.getElementById('auth-buttons');
|
||||
const logoutButtonEl = document.getElementById('logout-button');
|
||||
|
||||
|
||||
if (authButtonsEl) authButtonsEl.style.display = 'flex';
|
||||
if (logoutButtonEl) logoutButtonEl.style.display = 'none';
|
||||
}
|
||||
@@ -375,7 +388,7 @@ export function useAuth() {
|
||||
console.error('Error updating connection display:', error);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
onMounted(async () => {
|
||||
await checkAuth();
|
||||
});
|
||||
@@ -384,7 +397,7 @@ export function useAuth() {
|
||||
onUnmounted(() => {
|
||||
stopIdentitiesPolling();
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* Связывает новый идентификатор с текущим аккаунтом пользователя
|
||||
* @param {string} provider - Тип идентификатора (wallet, email, telegram)
|
||||
@@ -397,12 +410,12 @@ export function useAuth() {
|
||||
console.error('Невозможно связать идентификатор: пользователь не аутентифицирован');
|
||||
return { success: false, error: 'Пользователь не аутентифицирован' };
|
||||
}
|
||||
|
||||
|
||||
const response = await axios.post('/api/auth/identities/link', {
|
||||
type: provider,
|
||||
value: providerId
|
||||
value: providerId,
|
||||
});
|
||||
|
||||
|
||||
if (response.data.success) {
|
||||
// Обновляем локальные данные при необходимости
|
||||
if (provider === 'wallet') {
|
||||
@@ -413,24 +426,24 @@ export function useAuth() {
|
||||
} else if (provider === 'email') {
|
||||
email.value = providerId;
|
||||
}
|
||||
|
||||
|
||||
// Обновляем список идентификаторов
|
||||
await updateIdentities();
|
||||
|
||||
|
||||
console.log(`Идентификатор ${provider} успешно связан с аккаунтом`);
|
||||
return { success: true };
|
||||
}
|
||||
|
||||
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error('Ошибка при связывании идентификатора:', error);
|
||||
return {
|
||||
success: false,
|
||||
error: error.response?.data?.error || error.message
|
||||
return {
|
||||
success: false,
|
||||
error: error.response?.data?.error || error.message,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
return {
|
||||
isAuthenticated,
|
||||
authType,
|
||||
@@ -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');
|
||||
@@ -25,9 +25,9 @@ router.beforeEach(async (to, from, next) => {
|
||||
if (!to.matched.length) {
|
||||
return next({ name: 'home' });
|
||||
}
|
||||
|
||||
|
||||
// Проверяем аутентификацию, если маршрут требует авторизации
|
||||
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',
|
||||
};
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
@@ -4,39 +4,39 @@ import { SiweMessage } from 'siwe';
|
||||
|
||||
export async function connectWithWallet() {
|
||||
console.log('Starting wallet connection...');
|
||||
|
||||
|
||||
try {
|
||||
// Проверяем наличие MetaMask
|
||||
if (!window.ethereum) {
|
||||
throw new Error('MetaMask not detected. Please install 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('No accounts found. Please unlock MetaMask.');
|
||||
}
|
||||
|
||||
|
||||
// Берем первый аккаунт
|
||||
const address = ethers.getAddress(accounts[0]);
|
||||
console.log('Normalized address:', address);
|
||||
|
||||
|
||||
// Запрашиваем nonce с сервера
|
||||
console.log('Requesting nonce...');
|
||||
const nonceResponse = await axios.get(`/api/auth/nonce?address=${address}`);
|
||||
const nonce = nonceResponse.data.nonce;
|
||||
console.log('Got nonce:', nonce);
|
||||
|
||||
|
||||
// Создаем сообщение для подписи
|
||||
const domain = window.location.host;
|
||||
const origin = window.location.origin;
|
||||
const statement = 'Sign in with Ethereum to the app.';
|
||||
|
||||
|
||||
const siweMessage = new SiweMessage({
|
||||
domain,
|
||||
address,
|
||||
@@ -45,31 +45,31 @@ export async function connectWithWallet() {
|
||||
version: '1',
|
||||
chainId: 1,
|
||||
nonce,
|
||||
resources: [`${origin}/api/auth/verify`]
|
||||
resources: [`${origin}/api/auth/verify`],
|
||||
});
|
||||
|
||||
|
||||
const message = siweMessage.prepareMessage();
|
||||
console.log('SIWE message:', message);
|
||||
|
||||
|
||||
// Запрашиваем подпись
|
||||
console.log('Requesting signature...');
|
||||
const signature = await window.ethereum.request({
|
||||
method: 'personal_sign',
|
||||
params: [message, address]
|
||||
params: [message, address],
|
||||
});
|
||||
|
||||
|
||||
console.log('Got signature:', signature);
|
||||
|
||||
|
||||
// Отправляем подпись на сервер для верификации
|
||||
console.log('Sending verification request...');
|
||||
const verificationResponse = await axios.post('/api/auth/verify', {
|
||||
message,
|
||||
signature,
|
||||
address
|
||||
address,
|
||||
});
|
||||
|
||||
|
||||
console.log('Verification response:', verificationResponse.data);
|
||||
|
||||
|
||||
// Обновляем состояние аутентификации
|
||||
if (verificationResponse.data.success) {
|
||||
// Обновляем состояние аутентификации в localStorage
|
||||
@@ -78,10 +78,10 @@ export async function connectWithWallet() {
|
||||
localStorage.setItem('address', verificationResponse.data.address);
|
||||
localStorage.setItem('isAdmin', verificationResponse.data.isAdmin);
|
||||
}
|
||||
|
||||
|
||||
return verificationResponse.data;
|
||||
} catch (error) {
|
||||
console.error('Error connecting wallet:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,56 +5,57 @@ import { SiweMessage } from 'siwe';
|
||||
export const connectWallet = async () => {
|
||||
try {
|
||||
console.log('Starting wallet connection...');
|
||||
|
||||
|
||||
// Проверяем наличие MetaMask или другого Ethereum провайдера
|
||||
if (!window.ethereum) {
|
||||
console.error('No Ethereum provider (like MetaMask) detected!');
|
||||
return {
|
||||
success: false,
|
||||
error: 'Не найден кошелек MetaMask или другой Ethereum провайдер. Пожалуйста, установите расширение MetaMask.'
|
||||
return {
|
||||
success: false,
|
||||
error:
|
||||
'Не найден кошелек MetaMask или другой Ethereum провайдер. Пожалуйста, установите расширение 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) {
|
||||
return {
|
||||
success: false,
|
||||
error: 'Не удалось получить доступ к аккаунтам. Пожалуйста, разрешите доступ в MetaMask.'
|
||||
return {
|
||||
success: false,
|
||||
error: 'Не удалось получить доступ к аккаунтам. Пожалуйста, разрешите доступ в MetaMask.',
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
// Берем первый аккаунт в списке
|
||||
const address = accounts[0];
|
||||
// Нормализуем адрес (приводим к нижнему регистру для последующих сравнений)
|
||||
const normalizedAddress = ethers.utils.getAddress(address);
|
||||
console.log('Normalized address:', normalizedAddress);
|
||||
|
||||
|
||||
// Запрашиваем nonce с сервера
|
||||
console.log('Requesting nonce...');
|
||||
const nonceResponse = await axios.get(`/api/auth/nonce?address=${normalizedAddress}`);
|
||||
const nonce = nonceResponse.data.nonce;
|
||||
console.log('Got nonce:', nonce);
|
||||
|
||||
|
||||
if (!nonce) {
|
||||
return {
|
||||
success: false,
|
||||
error: 'Не удалось получить nonce от сервера.'
|
||||
return {
|
||||
success: false,
|
||||
error: 'Не удалось получить nonce от сервера.',
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
// Создаем провайдер Ethers
|
||||
const provider = new ethers.providers.Web3Provider(window.ethereum);
|
||||
const signer = provider.getSigner();
|
||||
|
||||
|
||||
// Создаем сообщение для подписи
|
||||
const domain = window.location.host;
|
||||
const origin = window.location.origin;
|
||||
|
||||
|
||||
// Создаем SIWE сообщение
|
||||
const message = new SiweMessage({
|
||||
domain,
|
||||
@@ -65,37 +66,37 @@ export const connectWallet = async () => {
|
||||
chainId: 1, // Ethereum mainnet
|
||||
nonce: nonce,
|
||||
issuedAt: new Date().toISOString(),
|
||||
resources: [`${origin}/api/auth/verify`]
|
||||
resources: [`${origin}/api/auth/verify`],
|
||||
});
|
||||
|
||||
|
||||
// Получаем строку сообщения для подписи
|
||||
const messageToSign = message.prepareMessage();
|
||||
console.log('SIWE message:', messageToSign);
|
||||
|
||||
|
||||
// Запрашиваем подпись
|
||||
console.log('Requesting signature...');
|
||||
const signature = await signer.signMessage(messageToSign);
|
||||
|
||||
|
||||
if (!signature) {
|
||||
return {
|
||||
success: false,
|
||||
error: 'Подпись не была получена. Пожалуйста, подпишите сообщение в MetaMask.'
|
||||
return {
|
||||
success: false,
|
||||
error: 'Подпись не была получена. Пожалуйста, подпишите сообщение в MetaMask.',
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
console.log('Got signature:', signature);
|
||||
|
||||
|
||||
// Отправляем верификацию на сервер
|
||||
console.log('Sending verification request...');
|
||||
const verifyResponse = await axios.post('/api/auth/verify', {
|
||||
address: normalizedAddress,
|
||||
signature,
|
||||
nonce
|
||||
nonce,
|
||||
});
|
||||
|
||||
|
||||
// Обновляем интерфейс для отображения подключенного состояния
|
||||
document.body.classList.add('wallet-connected');
|
||||
|
||||
|
||||
// Обновляем отображение адреса кошелька в UI
|
||||
const authDisplayEl = document.getElementById('auth-display');
|
||||
if (authDisplayEl) {
|
||||
@@ -103,35 +104,35 @@ export const connectWallet = async () => {
|
||||
authDisplayEl.innerHTML = `Кошелек: <strong>${shortAddress}</strong>`;
|
||||
authDisplayEl.style.display = 'inline-block';
|
||||
}
|
||||
|
||||
|
||||
// Скрываем кнопки авторизации и показываем кнопку выхода
|
||||
const authButtonsEl = document.getElementById('auth-buttons');
|
||||
const logoutButtonEl = document.getElementById('logout-button');
|
||||
|
||||
|
||||
if (authButtonsEl) authButtonsEl.style.display = 'none';
|
||||
if (logoutButtonEl) logoutButtonEl.style.display = 'inline-block';
|
||||
|
||||
|
||||
console.log('Verification response:', verifyResponse.data);
|
||||
|
||||
|
||||
if (verifyResponse.data.success) {
|
||||
return {
|
||||
success: true,
|
||||
return {
|
||||
success: true,
|
||||
address: normalizedAddress,
|
||||
userId: verifyResponse.data.userId,
|
||||
isAdmin: verifyResponse.data.isAdmin
|
||||
isAdmin: verifyResponse.data.isAdmin,
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
success: false,
|
||||
error: verifyResponse.data.error || 'Ошибка верификации на сервере.'
|
||||
return {
|
||||
success: false,
|
||||
error: verifyResponse.data.error || 'Ошибка верификации на сервере.',
|
||||
};
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error connecting wallet:', error);
|
||||
|
||||
|
||||
// Формируем понятное сообщение об ошибке
|
||||
let errorMessage = 'Произошла ошибка при подключении кошелька.';
|
||||
|
||||
|
||||
if (error.code === 4001) {
|
||||
errorMessage = 'Вы отклонили запрос на подпись в MetaMask.';
|
||||
} else if (error.response && error.response.data && error.response.data.error) {
|
||||
@@ -139,10 +140,10 @@ export const connectWallet = async () => {
|
||||
} else if (error.message) {
|
||||
errorMessage = error.message;
|
||||
}
|
||||
|
||||
return {
|
||||
success: false,
|
||||
error: errorMessage
|
||||
|
||||
return {
|
||||
success: false,
|
||||
error: errorMessage,
|
||||
};
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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