ntktajy
All checks were successful
Deploy hotel / deploy-kdo (push) Successful in 36s

This commit is contained in:
2026-07-19 12:37:33 +05:00
parent 38117f003d
commit be15f61d8c
5 changed files with 252 additions and 13 deletions

View File

@@ -6,7 +6,10 @@ let db;
const DEFAULT_SETTINGS = {
review_code: 'GUEST2026',
review_min_length: '20',
review_ip_cooldown_minutes: '5'
review_ip_cooldown_minutes: '5',
hotel_phone_primary: '89409270080',
hotel_phone_additional: '',
hotel_max_link: 'kalugin66'
};
function init(database) {
@@ -201,6 +204,49 @@ function setupRoutes(app, authenticateToken, requireAdmin) {
});
});
});
app.get('/api/admin/settings/phones', authenticateToken, requireAdmin, (req, res) => {
getAll((err, settings) => {
if (err) return res.status(500).json({ error: 'Database error' });
res.json({
primary: settings.hotel_phone_primary || '',
additional: settings.hotel_phone_additional || '',
maxLink: settings.hotel_max_link || ''
});
});
});
app.put('/api/admin/settings/phones', authenticateToken, requireAdmin, (req, res) => {
const { primary, additional, maxLink } = req.body;
if (!primary || primary.length < 5) {
return res.status(400).json({ error: 'Основной номер должен содержать минимум 5 цифр' });
}
const updates = [];
updates.push(['hotel_phone_primary', primary]);
updates.push(['hotel_phone_additional', additional || '']);
updates.push(['hotel_max_link', maxLink || '']);
let processed = 0;
updates.forEach(([key, value]) => {
set(key, value, (err) => {
if (err) console.error('Phone settings update error:', err);
processed++;
if (processed === updates.length) {
res.json({ message: 'Настройки телефонов сохранены' });
}
});
});
});
app.get('/api/config', (req, res) => {
getAll((err, settings) => {
if (err) return res.status(500).json({ error: 'Database error' });
res.json({
phone_primary: settings.hotel_phone_primary || '89409270080',
phone_additional: settings.hotel_phone_additional || '',
max_link: settings.hotel_max_link || 'kalugin66'
});
});
});
}
module.exports = { init, get, set, getAll, getReviewCode, setReviewCode, checkIpCooldown, setupRoutes };

View File

@@ -184,6 +184,9 @@ const translations = {
'hero.subtitle': 'Ваш идеальный отдых на берегу Чёрного моря в самом сердце Абхазии. Комфорт, природа и гостеприимство — всё, что нужно для незабываемого отпуска.',
'hero.btn_rooms': 'Выбрать номер',
'hero.btn_call': 'Позвонить',
'hero.btn_whatsapp': 'Написать в WhatsApp',
'hero.btn_telegram': 'Написать в Telegram',
'hero.btn_max': 'Написать в Max',
'hero.stat_sea': 'мин до моря',
'hero.stat_categories': 'категории',
'hero.stat_guests': 'довольных гостей',
@@ -507,6 +510,9 @@ const translations = {
'hero.subtitle': 'Your perfect getaway on the Black Sea coast in the heart of Abkhazia. Comfort, nature and hospitality — everything you need for an unforgettable vacation.',
'hero.btn_rooms': 'Choose a Room',
'hero.btn_call': 'Call Us',
'hero.btn_whatsapp': 'Write on WhatsApp',
'hero.btn_telegram': 'Write on Telegram',
'hero.btn_max': 'Write on Max',
'hero.stat_sea': 'min to the sea',
'hero.stat_categories': 'categories',
'hero.stat_guests': 'happy guests',
@@ -833,6 +839,9 @@ const translations = {
'hero.subtitle': 'Уара уидеалтә ԥсынчыра Амшын Еиқәаҵәа аԥшаҳәаҿы Аԥсны агәҭаны. Акомфорт, аԥсабареи асасӡреи — ахымԥада-ҳәада аусумҭазы иахәҭоу зегьы.',
'hero.btn_rooms': 'Апалаҭа алхразы',
'hero.btn_call': 'Аҭелефон асра',
'hero.btn_whatsapp': 'WhatsApp ала аҩра',
'hero.btn_telegram': 'Telegram ала аҩра',
'hero.btn_max': 'Max ала аҩра',
'hero.stat_sea': 'мин амшын ахь',
'hero.stat_categories': 'акатегориақәа',
'hero.stat_guests': 'изызҳауа асасцәа',

View File

@@ -468,11 +468,38 @@ tr.row-checkout-today { background: #fef2f2 !important; border-left: 4px solid #
<tr><td colspan="5" class="text-center text-muted">Загрузка...</td></tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
<div class="card" id="phoneCard">
<div class="card-header-custom"><h3><i class="fas fa-phone-alt me-2"></i>Телефоны и контакты</h3></div>
<div class="card-body-custom">
<p style="color: #64748b; margin-bottom: 20px;">
<i class="fas fa-info-circle me-2"></i>
Основной номер используется для звонков, WhatsApp и Telegram. Дополнительный отображается через запятую рядом с основным.
</p>
<div class="row g-3">
<div class="col-md-4">
<label class="form-label">Основной номер</label>
<input type="text" class="form-control" id="phonePrimary" placeholder="89409270080">
</div>
<div class="col-md-4">
<label class="form-label">Дополнительный номер</label>
<input type="text" class="form-control" id="phoneAdditional" placeholder="Необязательно">
</div>
<div class="col-md-4">
<label class="form-label">Ссылка Max (username)</label>
<input type="text" class="form-control" id="phoneMaxLink" placeholder="kalugin66">
</div>
</div>
<div class="mt-3">
<button class="btn-gold btn-sm" onclick="savePhoneSettings()"><i class="fas fa-save me-1"></i>Сохранить</button>
</div>
</div>
</div>
</div>
</div>
<div class="card">
<div class="card-header-custom"><h3>Кодовое слово для отзывов</h3></div>
@@ -2472,6 +2499,32 @@ async function sendTestEmail() {
} catch(err) { showToast(err.message, 'error'); }
}
// Phone settings
async function loadPhoneSettings() {
try {
const data = await api('/api/admin/settings/phones');
document.getElementById('phonePrimary').value = data.primary || '';
document.getElementById('phoneAdditional').value = data.additional || '';
document.getElementById('phoneMaxLink').value = data.maxLink || '';
} catch(e) {}
}
async function savePhoneSettings() {
const data = {
primary: document.getElementById('phonePrimary').value.trim(),
additional: document.getElementById('phoneAdditional').value.trim(),
maxLink: document.getElementById('phoneMaxLink').value.trim()
};
if (!data.primary) {
showToast('Основной номер обязателен', 'error');
return;
}
try {
await api('/api/admin/settings/phones', { method: 'PUT', body: JSON.stringify(data) });
showToast('Настройки телефонов сохранены');
} catch(err) { showToast(err.message, 'error'); }
}
// Export
async function exportCSV() {
const status = document.getElementById('filterStatus').value;
@@ -2501,6 +2554,7 @@ async function loadSettings() {
} catch(e) {}
loadSecurityKeys();
loadEmailSettings();
loadPhoneSettings();
}
// Show room modal with images

View File

@@ -298,6 +298,83 @@ h1, h2, h3, h4 {
border-color: #fff;
transform: translateY(-3px);
}
.btn-whatsapp {
background: #25D366;
color: #fff;
border: none;
padding: clamp(10px, 2vw, 15px) clamp(20px, 4vw, 40px);
border-radius: 50px;
font-weight: 700;
font-size: clamp(0.85rem, 1.2vw, 1rem);
letter-spacing: 0.5px;
transition: all 0.3s ease;
text-decoration: none;
display: inline-flex;
align-items: center;
gap: 8px;
}
.btn-whatsapp:hover {
background: #1ebe57;
transform: translateY(-3px);
box-shadow: 0 10px 30px rgba(37,211,102,0.4);
color: #fff;
}
.btn-telegram {
background: #0088cc;
color: #fff;
border: none;
padding: clamp(10px, 2vw, 15px) clamp(20px, 4vw, 40px);
border-radius: 50px;
font-weight: 700;
font-size: clamp(0.85rem, 1.2vw, 1rem);
letter-spacing: 0.5px;
transition: all 0.3s ease;
text-decoration: none;
display: inline-flex;
align-items: center;
gap: 8px;
}
.btn-telegram:hover {
background: #0077b5;
transform: translateY(-3px);
box-shadow: 0 10px 30px rgba(0,136,204,0.4);
color: #fff;
}
.btn-max {
background: #471AFF;
color: #fff;
border: none;
padding: clamp(10px, 2vw, 15px) clamp(20px, 4vw, 40px);
border-radius: 50px;
font-weight: 700;
font-size: clamp(0.85rem, 1.2vw, 1rem);
letter-spacing: 0.5px;
transition: all 0.3s ease;
text-decoration: none;
display: inline-flex;
align-items: center;
gap: 8px;
}
.btn-max:hover {
background: #3b15d9;
transform: translateY(-3px);
box-shadow: 0 10px 30px rgba(71,26,255,0.4);
color: #fff;
}
.max-icon-circle {
display: inline-flex;
align-items: center;
justify-content: center;
width: 1.4em;
height: 1.4em;
border-radius: 50%;
background: #fff;
color: #471AFF;
font-weight: 800;
font-size: 0.85em;
line-height: 1;
flex-shrink: 0;
}
.hero-stats {
position: absolute;
bottom: clamp(20px, 5vw, 60px);
@@ -1380,7 +1457,7 @@ h1, h2, h3, h4 {
font-size: 0.65rem;
padding: 5px 12px;
}
.btn-gold, .btn-outline-white {
.btn-gold, .btn-outline-white, .btn-whatsapp, .btn-telegram, .btn-max {
padding: 10px 18px;
font-size: 0.8rem;
}

View File

@@ -60,7 +60,10 @@
<p class="hero-subtitle" data-i18n="hero.subtitle">Ваш идеальный отдых на берегу Чёрного моря в самом сердце Абхазии. Комфорт, природа и гостеприимство — всё, что нужно для незабываемого отпуска.</p>
<div class="hero-buttons">
<a href="#rooms" class="btn-gold"><i class="fas fa-bed"></i> <span data-i18n="hero.btn_rooms">Выбрать номер</span></a>
<a href="tel:89409270080" class="btn-outline-white"><i class="fas fa-phone"></i> <span data-i18n="hero.btn_call">Позвонить</span></a>
<a href="tel:89409270080" class="btn-outline-white" data-phone="tel"><i class="fas fa-phone"></i> <span data-i18n="hero.btn_call">Позвонить</span></a>
<a href="https://wa.me/89409270080" class="btn-whatsapp" target="_blank" title="Написать в WhatsApp" data-phone="wa"><i class="fab fa-whatsapp"></i> W</a>
<a href="https://t.me/+89409270080" class="btn-telegram" target="_blank" title="Написать в Telegram" data-phone="tg"><i class="fab fa-telegram"></i> T</a>
<a href="https://max.ru/kalugin66" class="btn-max" target="_blank" title="Написать в Max" data-phone="max"><span class="max-icon-circle">M</span> M</a>
</div>
</div>
</div>
@@ -537,7 +540,7 @@
<div class="cta-banner animate-on-scroll">
<h2 data-i18n="cta.title">Забронируйте номер прямо сейчас!</h2>
<p data-i18n="cta.subtitle">Лучшие цены гарантированы при бронировании напрямую. Позвоните Рауфу Алексеевичу или оставьте заявку.</p>
<a href="tel:89409270080" class="btn-dark-cta"><i class="fas fa-phone"></i> 8 (940) 927-00-80</a>
<a href="tel:89409270080" class="btn-dark-cta" data-phone="tel" data-phone-text><i class="fas fa-phone"></i> 8 (940) 927-00-80</a>
</div>
</div>
</section>
@@ -591,7 +594,7 @@
<p style="font-size: 1rem; color: #666; line-height: 1.7; margin-bottom: 25px;" data-i18n="host.desc2">
Домашняя кухня — отдельная достопримечательность гостиницы. Свежие местные продукты, абхазские и русские блюда, приготовленные с душой — абиц, ачапа, хачапури, шашлык и многое другое. Чистые номера, искренняя улыбка и внимание к каждой детали — это то, что делает Hotel 777 особенным.
</p>
<a href="tel:89409270080" class="host-contact">
<a href="tel:89409270080" class="host-contact" data-phone="tel" data-phone-text>
<i class="fas fa-phone"></i> 8 (940) 927-00-80
</a>
</div>
@@ -628,7 +631,7 @@
<div class="contact-icon"><i class="fas fa-phone"></i></div>
<div>
<div class="contact-label"><span data-i18n="contact.phone_label">Телефон</span></div>
<div class="contact-value"><a href="tel:89409270080">8 (940) 927-00-80</a></div>
<div class="contact-value"><a href="tel:89409270080" data-phone="tel" data-phone-text>8 (940) 927-00-80</a></div>
</div>
</div>
<div class="contact-info-item">
@@ -646,7 +649,7 @@
</div>
</div>
<div class="mt-4">
<a href="https://wa.me/89409270080" class="btn-gold" target="_blank" style="text-decoration: none;">
<a href="https://wa.me/89409270080" class="btn-gold" target="_blank" style="text-decoration: none;" data-phone="wa">
<i class="fab fa-whatsapp"></i> <span data-i18n="contact.whatsapp">Написать Рауфу в WhatsApp</span>
</a>
</div>
@@ -680,7 +683,7 @@
<a href="#"><i class="fab fa-whatsapp"></i></a>
<a href="#"><i class="fab fa-telegram"></i></a>
<a href="#"><i class="fab fa-instagram"></i></a>
<a href="tel:89409270080"><i class="fas fa-phone"></i></a>
<a href="tel:89409270080" data-phone="tel"><i class="fas fa-phone"></i></a>
</div>
</div>
<div class="col-lg-2 col-md-4">
@@ -709,7 +712,7 @@
<div class="col-lg-3 col-md-4">
<div class="footer-title" data-i18n="footer.contact_title">Контакты</div>
<ul class="footer-links">
<li><a href="tel:89409270080"><i class="fas fa-phone me-2"></i>8 (940) 927-00-80</a></li>
<li><a href="tel:89409270080" data-phone="tel" data-phone-text><i class="fas fa-phone me-2"></i>8 (940) 927-00-80</a></li>
<li><a href="#"><i class="fas fa-user me-2"></i>Рауф Алексеевич</a></li>
<li><a href="#"><i class="fas fa-map-marker-alt me-2"></i>с. Мгудзырхуа</a></li>
<li><a href="#"><i class="fas fa-water me-2"></i>Набережная ул., 1</a></li>
@@ -724,7 +727,7 @@
</footer>
<!-- Floating Phone Button -->
<a href="tel:89409270080" class="float-phone">
<a href="tel:89409270080" class="float-phone" data-phone="tel">
<i class="fas fa-phone"></i>
</a>
@@ -810,7 +813,7 @@
</button>
</div>
<p class="text-center mt-3" style="font-size: 0.8rem; color: #999;">
Или позвоните Рауфу Алексеевичу: <a href="tel:89409270080" style="color: var(--primary); font-weight: 600;">8 (940) 927-00-80</a>
Или позвоните Рауфу Алексеевичу: <a href="tel:89409270080" style="color: var(--primary); font-weight: 600;" data-phone="tel" data-phone-text>8 (940) 927-00-80</a>
</p>
</form>
</div>
@@ -823,6 +826,56 @@
<script src="js/main.js?v=20260511b"></script>
<script src="js/i18n.js"></script>
<script src="js/reviews.js"></script>
<script>
(function() {
function formatPhone(raw) {
if (!raw || raw.length < 5) return raw;
var d = raw.replace(/\D/g, '');
if (d.length === 11 && d[0] === '8') {
return '8 (' + d.substring(1, 4) + ') ' + d.substring(4, 7) + '-' + d.substring(7, 9) + '-' + d.substring(9, 11);
}
if (d.length === 10) {
return '8 (' + d.substring(0, 3) + ') ' + d.substring(3, 6) + '-' + d.substring(6, 8) + '-' + d.substring(8, 10);
}
return raw;
}
fetch('/api/config')
.then(function(r) { return r.json(); })
.then(function(cfg) {
var primary = cfg.phone_primary || '89409270080';
var additional = cfg.phone_additional || '';
var maxLink = cfg.max_link || 'kalugin66';
var displayText = formatPhone(primary);
if (additional) displayText += ', ' + formatPhone(additional);
document.querySelectorAll('[data-phone]').forEach(function(el) {
var type = el.getAttribute('data-phone');
var href = '#';
if (type === 'tel') href = 'tel:' + primary;
else if (type === 'wa') href = 'https://wa.me/' + primary;
else if (type === 'tg') href = 'https://t.me/+' + primary;
else if (type === 'max') href = 'https://max.ru/' + maxLink;
el.setAttribute('href', href);
});
document.querySelectorAll('[data-phone-text]').forEach(function(el) {
var t = el.textContent.trim();
var icon = el.querySelector('i');
if (t.match(/^8\s*\(?\d{3}\)?\s*\d{3}[\s-]*\d{2}[\s-]*\d{2}$/) || t.match(/^8\s*\(?\d{3}\)?/)) {
if (icon) {
el.innerHTML = '';
el.appendChild(icon.cloneNode(true));
el.appendChild(document.createTextNode(' ' + displayText));
} else {
el.textContent = displayText;
}
}
});
})
.catch(function() {});
})();
</script>
<!-- Review Modal -->
<div class="review-modal" id="reviewModal">