feat(sharing): implement room link sharing functionality and update sharing guide
This commit is contained in:
parent
79267cb3d2
commit
ca4e1fd087
|
|
@ -0,0 +1,76 @@
|
||||||
|
# 🔗 Руководство по шарингу комнат
|
||||||
|
|
||||||
|
## Как пригласить друзей в игру
|
||||||
|
|
||||||
|
### Способ 1: Копирование ссылки
|
||||||
|
1. Создайте комнату или зайдите в существующую
|
||||||
|
2. В лобби комнаты нажмите на кнопку **📋** рядом со ссылкой
|
||||||
|
3. Отправьте скопированную ссылку друзьям любым удобным способом
|
||||||
|
|
||||||
|
### Способ 2: Прямой шаринг в Telegram
|
||||||
|
1. В лобби комнаты нажмите кнопку **📱 Telegram**
|
||||||
|
2. Выберите чат или контакт для отправки
|
||||||
|
3. Ваш друг получит ссылку с приглашением
|
||||||
|
|
||||||
|
### Способ 3: Прямой шаринг в WhatsApp
|
||||||
|
1. В лобби комнаты нажмите кнопку **💬 WhatsApp**
|
||||||
|
2. Выберите чат или контакт для отправки
|
||||||
|
3. Ваш друг получит ссылку с приглашением
|
||||||
|
|
||||||
|
### Способ 4: Универсальный шаринг (на мобильных устройствах)
|
||||||
|
1. В лобби комнаты нажмите кнопку **🔗 Поделиться**
|
||||||
|
2. Выберите приложение для отправки (доступно на Android/iOS)
|
||||||
|
3. Отправьте ссылку через выбранное приложение
|
||||||
|
|
||||||
|
## Как присоединиться по ссылке
|
||||||
|
|
||||||
|
### Вариант A: Автоматическое присоединение
|
||||||
|
1. Перейдите по полученной ссылке
|
||||||
|
2. Войдите в игру или зарегистрируйтесь
|
||||||
|
3. Вы автоматически присоединитесь к комнате
|
||||||
|
|
||||||
|
### Вариант B: Ручное присоединение
|
||||||
|
1. Войдите в игру
|
||||||
|
2. Перейдите в раздел "Мультиплеер"
|
||||||
|
3. Найдите комнату в списке и присоединитесь
|
||||||
|
|
||||||
|
## Формат ссылки
|
||||||
|
|
||||||
|
Ссылка на комнату имеет вид:
|
||||||
|
```
|
||||||
|
http://localhost:3000/?room=room_1234567890_abc123
|
||||||
|
```
|
||||||
|
|
||||||
|
Где `room_1234567890_abc123` - уникальный ID комнаты.
|
||||||
|
|
||||||
|
## Особенности
|
||||||
|
|
||||||
|
- ✅ Ссылка работает до тех пор, пока комната активна
|
||||||
|
- ✅ По одной ссылке может присоединиться несколько игроков
|
||||||
|
- ✅ Комната закрывается, когда все игроки выходят
|
||||||
|
- ✅ Максимальное количество игроков устанавливается при создании комнаты
|
||||||
|
|
||||||
|
## Советы
|
||||||
|
|
||||||
|
1. **Быстрое копирование**: Кликните на поле со ссылкой - текст выделится автоматически
|
||||||
|
2. **Проверка ссылки**: Ссылка обновляется автоматически при изменении комнаты
|
||||||
|
3. **Мобильные устройства**: Используйте кнопку "Поделиться" для быстрого шаринга
|
||||||
|
4. **Telegram/WhatsApp**: Работает как на десктопе, так и на мобильных устройствах
|
||||||
|
|
||||||
|
## Техническая информация
|
||||||
|
|
||||||
|
### Поддерживаемые браузеры
|
||||||
|
- Chrome/Edge: все функции
|
||||||
|
- Firefox: все функции
|
||||||
|
- Safari (iOS): все функции включая Web Share API
|
||||||
|
- Мобильные браузеры: полная поддержка
|
||||||
|
|
||||||
|
### API
|
||||||
|
- **Clipboard API** - для копирования ссылки
|
||||||
|
- **Web Share API** - для универсального шаринга (мобильные устройства)
|
||||||
|
- **URL Parameters** - для передачи ID комнаты
|
||||||
|
|
||||||
|
### Безопасность
|
||||||
|
- ID комнаты генерируется на сервере
|
||||||
|
- Невозможно подделать или угадать ID
|
||||||
|
- Комната доступна только активным пользователям
|
||||||
|
|
@ -40,6 +40,9 @@ async function initDatabase() {
|
||||||
// Создаём админа по умолчанию
|
// Создаём админа по умолчанию
|
||||||
await createDefaultAdmin();
|
await createDefaultAdmin();
|
||||||
|
|
||||||
|
// Всегда проверяем и инициализируем админские настройки
|
||||||
|
initDefaultAdminSettings();
|
||||||
|
|
||||||
return db;
|
return db;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -150,9 +153,6 @@ async function createDefaultAdmin() {
|
||||||
saveDatabase();
|
saveDatabase();
|
||||||
console.log('👑 Создан администратор по умолчанию: admin / admin123');
|
console.log('👑 Создан администратор по умолчанию: admin / admin123');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Инициализируем дефолтные админские настройки
|
|
||||||
initDefaultAdminSettings();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -188,6 +188,11 @@ function onAuthSuccess() {
|
||||||
if (!isGuest && authToken) {
|
if (!isGuest && authToken) {
|
||||||
loadUserSettingsFromServer();
|
loadUserSettingsFromServer();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Проверяем наличие комнаты в URL для автоматического присоединения
|
||||||
|
if (typeof checkRoomInUrl === 'function') {
|
||||||
|
checkRoomInUrl();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -198,6 +203,7 @@ function updateUserDisplay() {
|
||||||
const badgeEl = document.getElementById('user-role-badge');
|
const badgeEl = document.getElementById('user-role-badge');
|
||||||
const adminBtn = document.getElementById('admin-btn');
|
const adminBtn = document.getElementById('admin-btn');
|
||||||
const serverUrlGroup = document.getElementById('server-url-group');
|
const serverUrlGroup = document.getElementById('server-url-group');
|
||||||
|
const adminLlmButtonGroup = document.getElementById('admin-llm-button-group');
|
||||||
|
|
||||||
if (currentUser) {
|
if (currentUser) {
|
||||||
nameEl.textContent = currentUser.username;
|
nameEl.textContent = currentUser.username;
|
||||||
|
|
@ -207,10 +213,12 @@ function updateUserDisplay() {
|
||||||
badgeEl.textContent = 'ADMIN';
|
badgeEl.textContent = 'ADMIN';
|
||||||
if (adminBtn) adminBtn.style.display = 'block';
|
if (adminBtn) adminBtn.style.display = 'block';
|
||||||
if (serverUrlGroup) serverUrlGroup.style.display = 'block';
|
if (serverUrlGroup) serverUrlGroup.style.display = 'block';
|
||||||
|
if (adminLlmButtonGroup) adminLlmButtonGroup.style.display = 'block';
|
||||||
} else {
|
} else {
|
||||||
badgeEl.style.display = 'none';
|
badgeEl.style.display = 'none';
|
||||||
if (adminBtn) adminBtn.style.display = 'none';
|
if (adminBtn) adminBtn.style.display = 'none';
|
||||||
if (serverUrlGroup) serverUrlGroup.style.display = 'none';
|
if (serverUrlGroup) serverUrlGroup.style.display = 'none';
|
||||||
|
if (adminLlmButtonGroup) adminLlmButtonGroup.style.display = 'none';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Обновляем поля имени в формах
|
// Обновляем поля имени в формах
|
||||||
|
|
@ -363,7 +371,12 @@ function switchAdminTab(tab) {
|
||||||
* Загрузить админские настройки
|
* Загрузить админские настройки
|
||||||
*/
|
*/
|
||||||
async function loadAdminSettings() {
|
async function loadAdminSettings() {
|
||||||
if (!authToken || currentUser?.role !== 'admin') return;
|
if (!authToken || currentUser?.role !== 'admin') {
|
||||||
|
console.log('❌ Нет прав для загрузки админ настроек');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('📥 Загрузка админских настроек...');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch('/api/settings/admin', {
|
const response = await fetch('/api/settings/admin', {
|
||||||
|
|
@ -374,14 +387,18 @@ async function loadAdminSettings() {
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
const s = data.settings;
|
const s = data.settings;
|
||||||
|
|
||||||
|
console.log('✅ Получены настройки:', s);
|
||||||
|
|
||||||
document.getElementById('admin-llm-enabled').checked = s.llmEnabled;
|
document.getElementById('admin-llm-enabled').checked = s.llmEnabled;
|
||||||
document.getElementById('admin-llm-provider').value = s.llmProvider || 'ollama';
|
document.getElementById('admin-llm-provider').value = s.llmProvider || 'ollama';
|
||||||
document.getElementById('admin-llm-url').value = s.llmApiUrl || 'http://localhost:11434';
|
document.getElementById('admin-llm-url').value = s.llmApiUrl || 'http://localhost:11434';
|
||||||
document.getElementById('admin-llm-model').value = s.llmModel || 'llama3.2';
|
document.getElementById('admin-llm-model').value = s.llmModel || 'llama3.2';
|
||||||
document.getElementById('admin-llm-apikey').value = s.llmApiKey || '';
|
document.getElementById('admin-llm-apikey').value = s.llmApiKey || '';
|
||||||
|
} else {
|
||||||
|
console.error('❌ Ошибка ответа сервера:', response.status);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Ошибка загрузки админ настроек:', error);
|
console.error('❌ Ошибка загрузки админ настроек:', error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -399,6 +416,8 @@ async function saveAdminSettings() {
|
||||||
llmApiKey: document.getElementById('admin-llm-apikey').value
|
llmApiKey: document.getElementById('admin-llm-apikey').value
|
||||||
};
|
};
|
||||||
|
|
||||||
|
console.log('💾 Сохранение админских настроек:', settings);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch('/api/settings/admin', {
|
const response = await fetch('/api/settings/admin', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
|
|
@ -411,9 +430,14 @@ async function saveAdminSettings() {
|
||||||
|
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
showNotification('Настройки сохранены', 'success');
|
showNotification('Настройки сохранены', 'success');
|
||||||
|
// Обновляем статус LLM для отображения в настройках
|
||||||
|
loadLLMStatus();
|
||||||
|
} else {
|
||||||
|
showNotification('Ошибка сохранения', 'error');
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
showNotification('Ошибка сохранения', 'error');
|
showNotification('Ошибка сохранения', 'error');
|
||||||
|
console.error('Ошибка:', error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -221,6 +221,27 @@
|
||||||
<button class="btn-back" onclick="leaveRoom()">← Выйти</button>
|
<button class="btn-back" onclick="leaveRoom()">← Выйти</button>
|
||||||
<h2 id="lobby-room-name">Комната</h2>
|
<h2 id="lobby-room-name">Комната</h2>
|
||||||
|
|
||||||
|
<!-- Блок для шаринга ссылки на комнату -->
|
||||||
|
<div class="room-share-section">
|
||||||
|
<div class="room-link-display">
|
||||||
|
<input type="text" id="room-link-input" class="input" readonly onclick="this.select()">
|
||||||
|
<button class="btn btn-secondary btn-icon-only" onclick="copyRoomLink()" title="Копировать ссылку">
|
||||||
|
📋
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="share-buttons">
|
||||||
|
<button class="btn btn-outline btn-small" onclick="shareToTelegram()">
|
||||||
|
<span>📱 Telegram</span>
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-outline btn-small" onclick="shareToWhatsApp()">
|
||||||
|
<span>💬 WhatsApp</span>
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-outline btn-small" onclick="shareGeneric()" id="share-generic-btn">
|
||||||
|
<span>🔗 Поделиться</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="lobby-players" id="lobby-players">
|
<div class="lobby-players" id="lobby-players">
|
||||||
<!-- Игроки будут добавлены динамически -->
|
<!-- Игроки будут добавлены динамически -->
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -461,6 +482,13 @@
|
||||||
<span id="llm-status-text">LLM чат: загрузка...</span>
|
<span id="llm-status-text">LLM чат: загрузка...</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Кнопка настройки LLM для админа -->
|
||||||
|
<div class="admin-only" id="admin-llm-button-group" style="display: none; margin-top: 12px;">
|
||||||
|
<button class="btn btn-primary" onclick="showScreen('admin-screen')">
|
||||||
|
👑 Настроить LLM (Админ-панель)
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div style="margin-top: 16px;"></div>
|
<div style="margin-top: 16px;"></div>
|
||||||
<button class="btn btn-outline" onclick="resetSettings()">Сбросить настройки</button>
|
<button class="btn btn-outline" onclick="resetSettings()">Сбросить настройки</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
141
public/main.js
141
public/main.js
|
|
@ -495,6 +495,9 @@ function showRoomLobby(room) {
|
||||||
document.getElementById('lobby-room-name').textContent = room.name;
|
document.getElementById('lobby-room-name').textContent = room.name;
|
||||||
document.getElementById('lobby-blinds').textContent = `${room.smallBlind}/${room.bigBlind}`;
|
document.getElementById('lobby-blinds').textContent = `${room.smallBlind}/${room.bigBlind}`;
|
||||||
|
|
||||||
|
// Генерируем и отображаем ссылку на комнату
|
||||||
|
updateRoomLink(room.id);
|
||||||
|
|
||||||
updateLobbyPlayers(room);
|
updateLobbyPlayers(room);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -519,6 +522,11 @@ function updateLobbyPlayers(room) {
|
||||||
const startBtn = document.getElementById('start-game-btn');
|
const startBtn = document.getElementById('start-game-btn');
|
||||||
startBtn.style.display = room.players[0]?.id === currentPlayerId ? 'block' : 'none';
|
startBtn.style.display = room.players[0]?.id === currentPlayerId ? 'block' : 'none';
|
||||||
startBtn.disabled = room.players.length < 2;
|
startBtn.disabled = room.players.length < 2;
|
||||||
|
|
||||||
|
// Обновляем ссылку на комнату (если ID изменился)
|
||||||
|
if (room.id) {
|
||||||
|
updateRoomLink(room.id);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -2000,3 +2008,136 @@ function showNotification(message, type = 'info') {
|
||||||
notification.remove();
|
notification.remove();
|
||||||
}, 3000);
|
}, 3000);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// ШАРИНГ КОМНАТЫ
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Обновить ссылку на комнату
|
||||||
|
*/
|
||||||
|
function updateRoomLink(roomId) {
|
||||||
|
const baseUrl = window.location.origin + window.location.pathname;
|
||||||
|
const roomUrl = `${baseUrl}?room=${roomId}`;
|
||||||
|
|
||||||
|
const input = document.getElementById('room-link-input');
|
||||||
|
if (input) {
|
||||||
|
input.value = roomUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Проверяем поддержку Web Share API
|
||||||
|
const shareBtn = document.getElementById('share-generic-btn');
|
||||||
|
if (shareBtn) {
|
||||||
|
if (navigator.share) {
|
||||||
|
shareBtn.style.display = 'inline-flex';
|
||||||
|
} else {
|
||||||
|
shareBtn.style.display = 'none';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Копировать ссылку на комнату
|
||||||
|
*/
|
||||||
|
async function copyRoomLink() {
|
||||||
|
const input = document.getElementById('room-link-input');
|
||||||
|
const url = input.value;
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (navigator.clipboard) {
|
||||||
|
await navigator.clipboard.writeText(url);
|
||||||
|
showNotification('Ссылка скопирована!', 'success');
|
||||||
|
} else {
|
||||||
|
// Запасной вариант для старых браузеров
|
||||||
|
input.select();
|
||||||
|
document.execCommand('copy');
|
||||||
|
showNotification('Ссылка скопирована!', 'success');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Ошибка копирования:', error);
|
||||||
|
showNotification('Не удалось скопировать ссылку', 'error');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Поделиться в Telegram
|
||||||
|
*/
|
||||||
|
function shareToTelegram() {
|
||||||
|
const input = document.getElementById('room-link-input');
|
||||||
|
const url = input.value;
|
||||||
|
const roomName = document.getElementById('lobby-room-name').textContent;
|
||||||
|
|
||||||
|
const text = `Присоединяйся к игре в покер "${roomName}"!`;
|
||||||
|
const telegramUrl = `https://t.me/share/url?url=${encodeURIComponent(url)}&text=${encodeURIComponent(text)}`;
|
||||||
|
|
||||||
|
window.open(telegramUrl, '_blank');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Поделиться в WhatsApp
|
||||||
|
*/
|
||||||
|
function shareToWhatsApp() {
|
||||||
|
const input = document.getElementById('room-link-input');
|
||||||
|
const url = input.value;
|
||||||
|
const roomName = document.getElementById('lobby-room-name').textContent;
|
||||||
|
|
||||||
|
const text = `Присоединяйся к игре в покер "${roomName}"!\n${url}`;
|
||||||
|
const whatsappUrl = `https://wa.me/?text=${encodeURIComponent(text)}`;
|
||||||
|
|
||||||
|
window.open(whatsappUrl, '_blank');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Поделиться через Web Share API
|
||||||
|
*/
|
||||||
|
async function shareGeneric() {
|
||||||
|
const input = document.getElementById('room-link-input');
|
||||||
|
const url = input.value;
|
||||||
|
const roomName = document.getElementById('lobby-room-name').textContent;
|
||||||
|
|
||||||
|
if (navigator.share) {
|
||||||
|
try {
|
||||||
|
await navigator.share({
|
||||||
|
title: 'Texas Hold\'em Poker',
|
||||||
|
text: `Присоединяйся к игре в покер "${roomName}"!`,
|
||||||
|
url: url
|
||||||
|
});
|
||||||
|
showNotification('Ссылка отправлена!', 'success');
|
||||||
|
} catch (error) {
|
||||||
|
if (error.name !== 'AbortError') {
|
||||||
|
console.error('Ошибка шаринга:', error);
|
||||||
|
showNotification('Не удалось поделиться', 'error');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
showNotification('Ваш браузер не поддерживает эту функцию', 'info');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Проверить и присоединиться к комнате из URL
|
||||||
|
*/
|
||||||
|
function checkRoomInUrl() {
|
||||||
|
const urlParams = new URLSearchParams(window.location.search);
|
||||||
|
const roomId = urlParams.get('room');
|
||||||
|
|
||||||
|
if (roomId && currentPlayerId) {
|
||||||
|
// Если есть ID комнаты в URL, пытаемся присоединиться
|
||||||
|
showNotification('Подключение к комнате...', 'info');
|
||||||
|
|
||||||
|
// Переходим в мультиплеер и подключаемся
|
||||||
|
setTimeout(() => {
|
||||||
|
showScreen('multiplayer-menu');
|
||||||
|
|
||||||
|
// Ждём подключения WebSocket
|
||||||
|
if (ws && ws.readyState === WebSocket.OPEN) {
|
||||||
|
joinRoom(roomId);
|
||||||
|
} else {
|
||||||
|
connectWebSocket().then(() => {
|
||||||
|
setTimeout(() => joinRoom(roomId), 500);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, 500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -568,6 +568,40 @@ body::before {
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Блок шаринга комнаты */
|
||||||
|
.room-share-section {
|
||||||
|
margin-bottom: 24px;
|
||||||
|
padding: 16px;
|
||||||
|
background: var(--bg-secondary);
|
||||||
|
border-radius: var(--border-radius);
|
||||||
|
border: 1px solid var(--glass-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.room-link-display {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.room-link-display .input {
|
||||||
|
flex: 1;
|
||||||
|
font-size: 13px;
|
||||||
|
font-family: monospace;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.share-buttons {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
justify-content: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.share-buttons .btn {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 100px;
|
||||||
|
}
|
||||||
|
|
||||||
.lobby-chat {
|
.lobby-chat {
|
||||||
margin-top: 24px;
|
margin-top: 24px;
|
||||||
border-top: 1px solid var(--glass-border);
|
border-top: 1px solid var(--glass-border);
|
||||||
|
|
|
||||||
|
|
@ -122,6 +122,7 @@ app.post('/api/settings/user', authMiddleware, (req, res) => {
|
||||||
// Получить админские настройки
|
// Получить админские настройки
|
||||||
app.get('/api/settings/admin', authMiddleware, adminMiddleware, (req, res) => {
|
app.get('/api/settings/admin', authMiddleware, adminMiddleware, (req, res) => {
|
||||||
const settings = database.getAdminSettings();
|
const settings = database.getAdminSettings();
|
||||||
|
console.log('📤 Отправка админских настроек:', settings);
|
||||||
res.json({ settings });
|
res.json({ settings });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue