Initial commit: Korea Tourism Agency website with AdminJS

- Full-stack Node.js/Express application with PostgreSQL
- Modern ES modules architecture
- AdminJS admin panel with Sequelize ORM
- Tourism routes, guides, articles, bookings management
- Responsive Bootstrap 5 frontend
- Docker containerization with docker-compose
- Complete database schema with migrations
- Authentication system for admin panel
- Dynamic placeholder images for tour categories
This commit is contained in:
2025-11-29 18:13:17 +09:00
commit 409e6c146b
53 changed files with 16195 additions and 0 deletions

111
views/about.ejs Normal file
View File

@@ -0,0 +1,111 @@
<!-- Hero Section -->
<section class="hero-section bg-primary text-white text-center py-5">
<div class="container">
<div class="row">
<div class="col-lg-8 mx-auto">
<h1 class="display-4 fw-bold mb-4">О нашей компании</h1>
<p class="lead">Мы специализируемся на организации незабываемых путешествий по Южной Корее</p>
</div>
</div>
</div>
</section>
<!-- About Content -->
<section class="py-5">
<div class="container">
<div class="row align-items-center mb-5">
<div class="col-lg-6">
<h2 class="mb-4">Наша миссия</h2>
<p class="lead">Корея Тур Агентство основано с целью показать туристам настоящую Корею - её культуру, природу, традиции и современность.</p>
<p>Мы предлагаем широкий спектр туров: от городских экскурсий по историческим дворцам Сеула до горных походов в национальные парки и захватывающей морской рыбалки у берегов Пусана.</p>
<p>Наша команда состоит из профессиональных гидов, которые говорят на русском языке и имеют глубокие знания о корейской культуре и истории.</p>
</div>
<div class="col-lg-6">
<img src="/images/about-us.jpg" class="img-fluid rounded" alt="О нас">
</div>
</div>
<div class="row">
<div class="col-md-4 text-center mb-4">
<div class="card h-100">
<div class="card-body">
<i class="fas fa-users text-primary" style="font-size: 3rem;"></i>
<h5 class="card-title mt-3">Опытная команда</h5>
<p class="card-text">Более 50 проведенных туров и сотни довольных клиентов</p>
</div>
</div>
</div>
<div class="col-md-4 text-center mb-4">
<div class="card h-100">
<div class="card-body">
<i class="fas fa-language text-primary" style="font-size: 3rem;"></i>
<h5 class="card-title mt-3">Русскоязычные гиды</h5>
<p class="card-text">Все наши гиды свободно говорят по-русски и корейски</p>
</div>
</div>
</div>
<div class="col-md-4 text-center mb-4">
<div class="card h-100">
<div class="card-body">
<i class="fas fa-shield-alt text-primary" style="font-size: 3rem;"></i>
<h5 class="card-title mt-3">Безопасность</h5>
<p class="card-text">Полная страховка и соблюдение всех мер безопасности</p>
</div>
</div>
</div>
</div>
</div>
</section>
<!-- Team Section -->
<section class="bg-light py-5">
<div class="container">
<h2 class="text-center mb-5">Почему выбирают нас?</h2>
<div class="row">
<div class="col-lg-3 col-md-6 text-center mb-4">
<div class="mb-3">
<i class="fas fa-star text-warning" style="font-size: 2rem;"></i>
</div>
<h5>Высокие рейтинги</h5>
<p class="text-muted">4.8/5 средний рейтинг от наших клиентов</p>
</div>
<div class="col-lg-3 col-md-6 text-center mb-4">
<div class="mb-3">
<i class="fas fa-route text-primary" style="font-size: 2rem;"></i>
</div>
<h5>Уникальные маршруты</h5>
<p class="text-muted">Эксклюзивные места, недоступные массовому туризму</p>
</div>
<div class="col-lg-3 col-md-6 text-center mb-4">
<div class="mb-3">
<i class="fas fa-headset text-success" style="font-size: 2rem;"></i>
</div>
<h5>Поддержка 24/7</h5>
<p class="text-muted">Круглосуточная поддержка во время путешествия</p>
</div>
<div class="col-lg-3 col-md-6 text-center mb-4">
<div class="mb-3">
<i class="fas fa-handshake text-info" style="font-size: 2rem;"></i>
</div>
<h5>Индивидуальный подход</h5>
<p class="text-muted">Каждый тур адаптируется под ваши потребности</p>
</div>
</div>
</div>
</section>
<!-- Call to Action -->
<section class="py-5">
<div class="container text-center">
<h2 class="mb-4">Готовы исследовать Корею?</h2>
<p class="lead text-muted mb-4">Свяжитесь с нами и начните планировать ваше незабываемое путешествие уже сегодня!</p>
<div class="d-flex flex-wrap justify-content-center gap-3">
<a href="/routes" class="btn btn-primary btn-lg">
<i class="fas fa-route me-1"></i>Посмотреть туры
</a>
<a href="/contact" class="btn btn-outline-primary btn-lg">
<i class="fas fa-envelope me-1"></i>Связаться с нами
</a>
</div>
</div>
</section>

195
views/articles/detail.ejs Normal file
View File

@@ -0,0 +1,195 @@
<!-- Hero Section -->
<section class="hero-section bg-primary text-white py-5">
<div class="container">
<div class="row">
<div class="col-lg-8">
<nav aria-label="breadcrumb" class="mb-3">
<ol class="breadcrumb">
<li class="breadcrumb-item"><a href="/" class="text-light">Главная</a></li>
<li class="breadcrumb-item"><a href="/articles" class="text-light">Статьи</a></li>
<li class="breadcrumb-item active" aria-current="page"><%= article.title %></li>
</ol>
</nav>
<% if (article.category) { %>
<span class="badge bg-light text-dark mb-3">
<%= getCategoryLabel(article.category) %>
</span>
<% } %>
<h1 class="display-5 fw-bold mb-3"><%= article.title %></h1>
<% if (article.excerpt) { %>
<p class="lead mb-4"><%= article.excerpt %></p>
<% } %>
<div class="d-flex align-items-center text-light-50">
<i class="fas fa-calendar-alt me-2"></i>
<span class="me-4"><%= formatDate(article.created_at) %></span>
<i class="fas fa-eye me-2"></i>
<span><%= article.views %> просмотров</span>
</div>
</div>
</div>
</div>
</section>
<!-- Article Content -->
<section class="py-5">
<div class="container">
<div class="row">
<div class="col-lg-8">
<article class="mb-5">
<div class="mb-4">
<img src="<%= (article.image_url && article.image_url.trim()) ? article.image_url : '/images/placeholder.jpg' %>" alt="<%= article.title %>" class="img-fluid rounded shadow">
</div>
<div class="article-content">
<%- article.content %>
</div>
</article>
<!-- Social Share -->
<div class="border-top pt-4 mb-5">
<h5 class="mb-3">Поделиться статьей:</h5>
<div class="d-flex gap-2">
<a href="#" class="btn btn-outline-primary btn-sm" onclick="shareToFacebook()">
<i class="fab fa-facebook-f me-1"></i>Facebook
</a>
<a href="#" class="btn btn-outline-info btn-sm" onclick="shareToTwitter()">
<i class="fab fa-twitter me-1"></i>Twitter
</a>
<a href="#" class="btn btn-outline-success btn-sm" onclick="copyLink()">
<i class="fas fa-link me-1"></i>Копировать ссылку
</a>
</div>
</div>
</div>
<!-- Sidebar -->
<div class="col-lg-4">
<div class="card">
<div class="card-header">
<h5 class="card-title mb-0">
<i class="fas fa-newspaper me-2"></i>Похожие статьи
</h5>
</div>
<div class="card-body">
<% if (relatedArticles && relatedArticles.length > 0) { %>
<% relatedArticles.forEach(related => { %>
<div class="mb-3 pb-3 border-bottom">
<div class="mb-2">
<img src="<%= (related.image_url && related.image_url.trim()) ? related.image_url : '/images/placeholder.jpg' %>" alt="<%= related.title %>"
class="img-fluid rounded" style="height: 80px; width: 100%; object-fit: cover;">
</div>
<h6 class="mb-1">
<a href="/articles/<%= related.id %>" class="text-decoration-none">
<%= related.title %>
</a>
</h6>
<% if (related.excerpt) { %>
<p class="text-muted small mb-1"><%= truncateText(related.excerpt, 80) %></p>
<% } %>
<small class="text-muted">
<i class="fas fa-calendar-alt me-1"></i>
<%= formatDateShort(related.created_at) %>
</small>
</div>
<% }); %>
<% } else { %>
<p class="text-muted mb-0">Нет похожих статей</p>
<% } %>
</div>
</div>
<!-- Back to Articles -->
<div class="mt-4">
<a href="/articles" class="btn btn-outline-primary w-100">
<i class="fas fa-arrow-left me-1"></i>Вернуться к статьям
</a>
</div>
</div>
</div>
</div>
</section>
<!-- CTA Section -->
<section class="bg-light py-5">
<div class="container text-center">
<h2 class="mb-4">Готовы посетить Корею?</h2>
<p class="lead text-muted mb-4">Ознакомьтесь с нашими турами и найдите идеальное путешествие для себя!</p>
<a href="/routes" class="btn btn-primary btn-lg me-3">
<i class="fas fa-route me-1"></i>Посмотреть туры
</a>
<a href="/contact" class="btn btn-outline-primary btn-lg">
<i class="fas fa-envelope me-1"></i>Свяжитесь с нами
</a>
</div>
</section>
<script>
function shareToFacebook() {
const url = encodeURIComponent(window.location.href);
const title = encodeURIComponent('<%= article.title %>');
window.open(`https://www.facebook.com/sharer/sharer.php?u=${url}`, '_blank', 'width=600,height=400');
}
function shareToTwitter() {
const url = encodeURIComponent(window.location.href);
const title = encodeURIComponent('<%= article.title %>');
window.open(`https://twitter.com/intent/tweet?url=${url}&text=${title}`, '_blank', 'width=600,height=400');
}
function copyLink() {
navigator.clipboard.writeText(window.location.href).then(() => {
alert('Ссылка скопирована в буфер обмена!');
});
}
</script>
<style>
.article-content {
font-size: 1.1rem;
line-height: 1.7;
}
.article-content h1,
.article-content h2,
.article-content h3,
.article-content h4,
.article-content h5,
.article-content h6 {
margin-top: 2rem;
margin-bottom: 1rem;
color: #2c3e50;
}
.article-content p {
margin-bottom: 1.5rem;
}
.article-content img {
max-width: 100%;
height: auto;
margin: 1.5rem 0;
border-radius: 8px;
}
.article-content blockquote {
background-color: #f8f9fa;
border-left: 4px solid #007bff;
padding: 1rem 1.5rem;
margin: 2rem 0;
font-style: italic;
}
.article-content ul,
.article-content ol {
padding-left: 2rem;
margin-bottom: 1.5rem;
}
.article-content li {
margin-bottom: 0.5rem;
}
</style>

80
views/articles/index.ejs Normal file
View File

@@ -0,0 +1,80 @@
<!-- Hero Section -->
<section class="hero-section bg-primary text-white text-center py-5">
<div class="container">
<div class="row">
<div class="col-lg-8 mx-auto">
<h1 class="display-4 fw-bold mb-4">Полезные статьи</h1>
<p class="lead">Советы путешественникам и интересная информация о Корее</p>
</div>
</div>
</div>
</section>
<!-- Articles Grid -->
<section class="py-5">
<div class="container">
<% if (articles.length === 0) { %>
<div class="text-center py-5">
<i class="fas fa-newspaper text-muted" style="font-size: 4rem;"></i>
<h3 class="mt-3 text-muted">Статьи не найдены</h3>
<p class="text-muted">В данный момент нет опубликованных статей.</p>
</div>
<% } else { %>
<div class="row">
<% articles.forEach(article => { %>
<div class="col-lg-4 col-md-6 mb-4">
<div class="card h-100 shadow-sm">
<% if (article.image_url && article.image_url.trim()) { %>
<img src="<%= article.image_url %>" class="card-img-top" alt="<%= article.title %>" style="height: 200px; object-fit: cover;">
<% } else { %>
<img src="/images/placeholder.jpg" class="card-img-top" alt="<%= article.title %>" style="height: 200px; object-fit: cover;">
<% } %>
<div class="card-body d-flex flex-column">
<div class="mb-2">
<span class="badge <%= article.category === 'travel-tips' ? 'bg-success' : article.category === 'food' ? 'bg-warning' : article.category === 'culture' ? 'bg-info' : article.category === 'nature' ? 'bg-success' : article.category === 'history' ? 'bg-secondary' : 'bg-primary' %>">
<%= getCategoryLabel(article.category) %>
</span>
</div>
<h5 class="card-title"><%= article.title %></h5>
<p class="card-text text-muted flex-grow-1"><%= article.excerpt || truncateText(article.content, 120) %></p>
<div class="mt-auto">
<div class="d-flex justify-content-between align-items-center mb-3">
<small class="text-muted">
<i class="fas fa-eye me-1"></i><%= article.views || 0 %> просмотров
</small>
<small class="text-muted">
<i class="fas fa-calendar me-1"></i><%= formatDate(article.created_at) %>
</small>
</div>
<a href="/articles/<%= article.id %>" class="btn btn-primary w-100">
<i class="fas fa-book-open me-1"></i>Читать
</a>
</div>
</div>
</div>
</div>
<% }); %>
</div>
<% } %>
</div>
</section>
<!-- Newsletter Signup -->
<section class="bg-light py-5">
<div class="container text-center">
<h2 class="mb-4">Не пропустите новые статьи!</h2>
<p class="lead text-muted mb-4">Подпишитесь на нашу рассылку и получайте самые интересные материалы о Корее</p>
<div class="row justify-content-center">
<div class="col-md-6">
<form class="d-flex">
<input type="email" class="form-control me-2" placeholder="Ваш email адрес">
<button class="btn btn-primary" type="submit">Подписаться</button>
</form>
</div>
</div>
</div>
</section>

191
views/contact.ejs Normal file
View File

@@ -0,0 +1,191 @@
<!-- Hero Section -->
<section class="hero-section bg-primary text-white text-center py-5">
<div class="container">
<div class="row">
<div class="col-lg-8 mx-auto">
<h1 class="display-4 fw-bold mb-4">Свяжитесь с нами</h1>
<p class="lead">Мы готовы ответить на все ваши вопросы о турах по Корее</p>
</div>
</div>
</div>
</section>
<!-- Contact Form and Info -->
<section class="py-5">
<div class="container">
<div class="row">
<div class="col-lg-8 mb-4">
<div class="card">
<div class="card-body">
<h3 class="card-title mb-4">Напишите нам</h3>
<form id="contactForm">
<div class="row">
<div class="col-md-6 mb-3">
<label for="name" class="form-label">Ваше имя *</label>
<input type="text" class="form-control" id="name" name="name" required>
</div>
<div class="col-md-6 mb-3">
<label for="email" class="form-label">Email *</label>
<input type="email" class="form-control" id="email" name="email" required>
</div>
</div>
<div class="mb-3">
<label for="phone" class="form-label">Телефон</label>
<input type="tel" class="form-control" id="phone" name="phone">
</div>
<div class="mb-3">
<label for="subject" class="form-label">Тема сообщения</label>
<select class="form-select" id="subject" name="subject">
<option value="">Выберите тему...</option>
<option value="booking">Бронирование тура</option>
<option value="info">Информация о турах</option>
<option value="custom">Индивидуальный тур</option>
<option value="feedback">Отзыв</option>
<option value="other">Другое</option>
</select>
</div>
<div class="mb-3">
<label for="message" class="form-label">Сообщение *</label>
<textarea class="form-control" id="message" name="message" rows="5" required></textarea>
</div>
<button type="submit" class="btn btn-primary">
<i class="fas fa-paper-plane me-1"></i>Отправить сообщение
</button>
</form>
</div>
</div>
</div>
<div class="col-lg-4">
<!-- Contact Info -->
<div class="card mb-4">
<div class="card-body">
<h5 class="card-title">Контактная информация</h5>
<div class="mb-3">
<i class="fas fa-map-marker-alt text-primary me-2"></i>
<strong>Адрес:</strong><br>
<small class="text-muted">Москва, ул. Примерная, д. 123</small>
</div>
<div class="mb-3">
<i class="fas fa-phone text-primary me-2"></i>
<strong>Телефон:</strong><br>
<small class="text-muted">+7 (495) 123-45-67</small>
</div>
<div class="mb-3">
<i class="fas fa-envelope text-primary me-2"></i>
<strong>Email:</strong><br>
<small class="text-muted">info@koreatour.ru</small>
</div>
<div class="mb-3">
<i class="fas fa-clock text-primary me-2"></i>
<strong>Время работы:</strong><br>
<small class="text-muted">Пн-Пт: 9:00-18:00<br>Сб: 10:00-16:00<br>Вс: выходной</small>
</div>
</div>
</div>
<!-- Social Media -->
<div class="card">
<div class="card-body">
<h5 class="card-title">Мы в соцсетях</h5>
<div class="d-flex gap-2">
<a href="#" class="btn btn-outline-primary btn-sm">
<i class="fab fa-facebook-f"></i>
</a>
<a href="#" class="btn btn-outline-primary btn-sm">
<i class="fab fa-instagram"></i>
</a>
<a href="#" class="btn btn-outline-primary btn-sm">
<i class="fab fa-youtube"></i>
</a>
<a href="#" class="btn btn-outline-primary btn-sm">
<i class="fab fa-telegram"></i>
</a>
</div>
</div>
</div>
</div>
</div>
</div>
</section>
<!-- FAQ Section -->
<section class="bg-light py-5">
<div class="container">
<h2 class="text-center mb-5">Часто задаваемые вопросы</h2>
<div class="row">
<div class="col-lg-8 mx-auto">
<div class="accordion" id="faqAccordion">
<div class="accordion-item">
<h2 class="accordion-header" id="faq1">
<button class="accordion-button" type="button" data-bs-toggle="collapse" data-bs-target="#collapse1">
Нужна ли виза для поездки в Корею?
</button>
</h2>
<div id="collapse1" class="accordion-collapse collapse show" data-bs-parent="#faqAccordion">
<div class="accordion-body">
Граждане России могут находиться в Южной Корее без визы до 60 дней для туристических целей. Необходим загранпаспорт, действующий минимум 6 месяцев.
</div>
</div>
</div>
<div class="accordion-item">
<h2 class="accordion-header" id="faq2">
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapse2">
Какая валюта используется в Корее?
</button>
</h2>
<div id="collapse2" class="accordion-collapse collapse" data-bs-parent="#faqAccordion">
<div class="accordion-body">
В Корее используется корейская вона (KRW или ₩). Обменять деньги можно в банках, обменных пунктах или снять в банкоматах.
</div>
</div>
</div>
<div class="accordion-item">
<h2 class="accordion-header" id="faq3">
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapse3">
Включена ли страховка в стоимость тура?
</button>
</h2>
<div id="collapse3" class="accordion-collapse collapse" data-bs-parent="#faqAccordion">
<div class="accordion-body">
Да, все наши туры включают базовую туристическую страховку. По желанию можно оформить расширенную страховку.
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</section>
<script>
document.getElementById('contactForm').addEventListener('submit', async (e) => {
e.preventDefault();
const formData = new FormData(e.target);
const data = Object.fromEntries(formData.entries());
try {
const response = await fetch('/contact', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(data)
});
const result = await response.json();
if (result.success) {
alert('Спасибо! Ваше сообщение отправлено. Мы свяжемся с вами в ближайшее время.');
e.target.reset();
} else {
alert('Произошла ошибка при отправке сообщения. Попробуйте еще раз.');
}
} catch (error) {
alert('Произошла ошибка при отправке сообщения. Попробуйте еще раз.');
}
});
</script>

34
views/error.ejs Normal file
View File

@@ -0,0 +1,34 @@
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Ошибка - Корея Тур Агентство</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
</head>
<body class="bg-light">
<div class="container py-5">
<div class="row justify-content-center">
<div class="col-md-6">
<div class="card border-0 shadow">
<div class="card-body text-center p-5">
<i class="fas fa-exclamation-triangle text-warning display-1 mb-4"></i>
<h2 class="mb-3"><%= title || 'Произошла ошибка' %></h2>
<p class="text-muted mb-4"><%= message || 'При загрузке страницы произошла ошибка.' %></p>
<% if (error && error.status === 404) { %>
<p class="small text-muted mb-4">Страница, которую вы ищете, не существует или была перемещена.</p>
<% } %>
<a href="/" class="btn btn-primary">
<i class="fas fa-home me-1"></i>Вернуться на главную
</a>
<a href="javascript:history.back()" class="btn btn-outline-secondary ms-2">
<i class="fas fa-arrow-left me-1"></i>Назад
</a>
</div>
</div>
</div>
</div>
</div>
</body>
</html>

108
views/guides/index.ejs Normal file
View File

@@ -0,0 +1,108 @@
<!-- Hero Section -->
<section class="hero-section bg-primary text-white text-center py-5">
<div class="container">
<div class="row">
<div class="col-lg-8 mx-auto">
<h1 class="display-4 fw-bold mb-4">Наши гиды</h1>
<p class="lead">Профессиональные и опытные гиды сделают ваше путешествие незабываемым</p>
</div>
</div>
</div>
</section>
<!-- Guides Grid -->
<section class="py-5">
<div class="container">
<% if (guides.length === 0) { %>
<div class="text-center py-5">
<i class="fas fa-user-tie text-muted" style="font-size: 4rem;"></i>
<h3 class="mt-3 text-muted">Гиды не найдены</h3>
<p class="text-muted">В данный момент нет доступных гидов.</p>
</div>
<% } else { %>
<div class="row">
<% guides.forEach(guide => { %>
<div class="col-lg-4 col-md-6 mb-4">
<div class="card h-100 shadow-sm">
<% if (guide.image_url && guide.image_url.trim()) { %>
<img src="<%= guide.image_url %>" class="card-img-top" alt="<%= guide.name %>" style="height: 250px; object-fit: cover;">
<% } else { %>
<img src="/images/placeholder.jpg" class="card-img-top" alt="<%= guide.name %>" style="height: 250px; object-fit: cover;">
<% } %>
<div class="card-body d-flex flex-column">
<h5 class="card-title"><%= guide.name %></h5>
<div class="mb-3">
<% if (guide.specialization === 'city') { %>
<span class="badge bg-info">
<i class="fas fa-city me-1"></i>Городские туры
</span>
<% } else if (guide.specialization === 'mountain') { %>
<span class="badge bg-success">
<i class="fas fa-mountain me-1"></i>Горные походы
</span>
<% } else if (guide.specialization === 'fishing') { %>
<span class="badge bg-primary">
<i class="fas fa-fish me-1"></i>Рыбалка
</span>
<% } %>
</div>
<% if (guide.bio) { %>
<p class="card-text text-muted flex-grow-1"><%= truncateText(guide.bio, 120) %></p>
<% } %>
<div class="mt-auto">
<% if (guide.languages) { %>
<div class="mb-2">
<small class="text-muted">
<i class="fas fa-language me-1"></i>
<%= guide.languages %>
</small>
</div>
<% } %>
<% if (guide.avg_rating) { %>
<div class="mb-2">
<%- generateStars(guide.avg_rating) %>
<small class="text-muted ms-2">(<%= parseFloat(guide.avg_rating).toFixed(1) %>)</small>
</div>
<% } %>
<div class="d-flex justify-content-between align-items-center mb-3">
<div>
<small class="text-muted">
<i class="fas fa-calendar me-1"></i><%= guide.experience %> лет опыта
</small>
</div>
<div>
<small class="text-muted">
<i class="fas fa-route me-1"></i><%= guide.route_count || 0 %> туров
</small>
</div>
</div>
<a href="/guides/<%= guide.id %>" class="btn btn-primary w-100">
<i class="fas fa-user me-1"></i>Подробнее
</a>
</div>
</div>
</div>
</div>
<% }); %>
</div>
<% } %>
</div>
</section>
<!-- CTA Section -->
<section class="bg-light py-5">
<div class="container text-center">
<h2 class="mb-4">Нужен индивидуальный подход?</h2>
<p class="lead text-muted mb-4">Наши гиды готовы создать уникальный маршрут специально для вас!</p>
<a href="/contact" class="btn btn-primary btn-lg">
<i class="fas fa-envelope me-1"></i>Связаться с нами
</a>
</div>
</section>

260
views/guides/profile.ejs Normal file
View File

@@ -0,0 +1,260 @@
<!-- Hero Section -->
<section class="hero-section bg-primary text-white py-5">
<div class="container">
<div class="row align-items-center">
<div class="col-lg-4 text-center text-lg-start">
<% if (guide.image_url && guide.image_url.trim()) { %>
<img src="<%= guide.image_url %>" alt="<%= guide.name %>"
class="img-fluid rounded-circle shadow-lg mb-4 mb-lg-0"
style="width: 200px; height: 200px; object-fit: cover;">
<% } else { %>
<img src="/images/placeholder.jpg" alt="<%= guide.name %>"
class="img-fluid rounded-circle shadow-lg mb-4 mb-lg-0"
style="width: 200px; height: 200px; object-fit: cover;">
<% } %>
</div>
<div class="col-lg-8">
<h1 class="display-4 fw-bold mb-3"><%= guide.name %></h1>
<div class="mb-4">
<% if (guide.specialization === 'city') { %>
<span class="badge bg-info fs-6 me-2">
<i class="fas fa-city me-1"></i>Городские туры
</span>
<% } else if (guide.specialization === 'mountain') { %>
<span class="badge bg-success fs-6 me-2">
<i class="fas fa-mountain me-1"></i>Горные походы
</span>
<% } else if (guide.specialization === 'fishing') { %>
<span class="badge bg-info fs-6 me-2">
<i class="fas fa-fish me-1"></i>Рыбалка
</span>
<% } %>
<span class="badge bg-warning text-dark fs-6">
<i class="fas fa-star me-1"></i>
<%= guide.experience %> лет опыта
</span>
</div>
<% if (guide.languages) { %>
<p class="mb-3">
<strong>Языки:</strong> <%= guide.languages %>
</p>
<% } %>
<% if (guide.avg_rating) { %>
<div class="mb-3">
<span class="me-2">Рейтинг:</span>
<%- generateStars(guide.avg_rating) %>
<span class="ms-2">(<%= parseFloat(guide.avg_rating).toFixed(1) %>/5)</span>
<% if (guide.review_count) { %>
<small class="text-light-50 ms-2"><%= guide.review_count %> отзывов</small>
<% } %>
</div>
<% } %>
<div class="row">
<div class="col-md-6">
<p class="mb-1">
<i class="fas fa-route me-2"></i>
<strong><%= guide.route_count || 0 %></strong> туров
</p>
</div>
<% if (guide.phone) { %>
<div class="col-md-6">
<p class="mb-1">
<i class="fas fa-phone me-2"></i>
<a href="tel:<%= guide.phone %>" class="text-light"><%= guide.phone %></a>
</p>
</div>
<% } %>
</div>
</div>
</div>
</div>
</section>
<!-- Guide Details -->
<section class="py-5">
<div class="container">
<div class="row">
<div class="col-lg-8">
<!-- Bio -->
<% if (guide.bio) { %>
<div class="card mb-4">
<div class="card-header">
<h4 class="card-title mb-0">
<i class="fas fa-user me-2"></i>О гиде
</h4>
</div>
<div class="card-body">
<p class="mb-0"><%= guide.bio %></p>
</div>
</div>
<% } %>
<!-- Tours -->
<div class="card mb-4">
<div class="card-header">
<h4 class="card-title mb-0">
<i class="fas fa-route me-2"></i>Туры с этим гидом
</h4>
</div>
<div class="card-body">
<% if (routes && routes.length > 0) { %>
<div class="row">
<% routes.forEach(route => { %>
<div class="col-md-6 mb-3">
<div class="card h-100">
<%
let placeholderImage = '/images/placeholder.jpg';
if (route.type === 'city') {
placeholderImage = '/images/city-tour-placeholder.webp';
} else if (route.type === 'mountain') {
placeholderImage = '/images/mountain-placeholder.jpg';
} else if (route.type === 'fishing') {
placeholderImage = '/images/fish-placeholder.jpg';
}
%>
<% if (route.image_url && route.image_url.trim()) { %>
<img src="<%= route.image_url %>" class="card-img-top"
alt="<%= route.title %>" style="height: 150px; object-fit: cover;">
<% } else { %>
<img src="<%= placeholderImage %>" class="card-img-top"
alt="<%= route.title %>" style="height: 150px; object-fit: cover;">
<% } %>
<div class="card-body">
<h6 class="card-title"><%= route.title %></h6>
<p class="card-text text-muted small"><%= truncateText(route.description, 80) %></p>
<div class="d-flex justify-content-between align-items-center">
<strong class="text-primary">₩<%= formatCurrency(route.price) %></strong>
<small class="text-muted"><%= route.duration %> дн.</small>
</div>
<a href="/routes/<%= route.id %>" class="btn btn-outline-primary btn-sm mt-2 w-100">
Подробнее
</a>
</div>
</div>
</div>
<% }); %>
</div>
<% } else { %>
<p class="text-muted mb-0">В данный момент у этого гида нет активных туров.</p>
<% } %>
</div>
</div>
<!-- Reviews -->
<div class="card">
<div class="card-header">
<h4 class="card-title mb-0">
<i class="fas fa-star me-2"></i>Отзывы
</h4>
</div>
<div class="card-body">
<% if (reviews && reviews.length > 0) { %>
<% reviews.forEach(review => { %>
<div class="border-bottom pb-3 mb-3">
<div class="d-flex justify-content-between align-items-start mb-2">
<div>
<h6 class="mb-1"><%= review.author_name %></h6>
<% if (review.route_title) { %>
<small class="text-muted">Тур: <%= review.route_title %></small>
<% } %>
</div>
<div class="text-end">
<% if (review.rating) { %>
<div class="mb-1">
<%- generateStars(review.rating) %>
</div>
<% } %>
<small class="text-muted"><%= formatDateShort(review.created_at) %></small>
</div>
</div>
<p class="mb-0"><%= review.comment %></p>
</div>
<% }); %>
<% } else { %>
<p class="text-muted mb-0">Пока нет отзывов об этом гиде.</p>
<% } %>
</div>
</div>
</div>
<!-- Sidebar -->
<div class="col-lg-4">
<!-- Contact Card -->
<div class="card mb-4">
<div class="card-header">
<h5 class="card-title mb-0">
<i class="fas fa-address-card me-2"></i>Контактная информация
</h5>
</div>
<div class="card-body">
<% if (guide.phone) { %>
<p class="mb-2">
<i class="fas fa-phone me-2"></i>
<a href="tel:<%= guide.phone %>"><%= guide.phone %></a>
</p>
<% } %>
<% if (guide.email) { %>
<p class="mb-2">
<i class="fas fa-envelope me-2"></i>
<a href="mailto:<%= guide.email %>"><%= guide.email %></a>
</p>
<% } %>
<% if (guide.languages) { %>
<p class="mb-2">
<i class="fas fa-language me-2"></i>
<%= guide.languages %>
</p>
<% } %>
<a href="/contact" class="btn btn-primary w-100 mt-3">
<i class="fas fa-envelope me-1"></i>Связаться с гидом
</a>
</div>
</div>
<!-- Stats Card -->
<div class="card">
<div class="card-header">
<h5 class="card-title mb-0">
<i class="fas fa-chart-bar me-2"></i>Статистика
</h5>
</div>
<div class="card-body">
<div class="row text-center">
<div class="col-6 mb-3">
<div class="border-end">
<h4 class="text-primary mb-1"><%= guide.route_count || 0 %></h4>
<small class="text-muted">Туров</small>
</div>
</div>
<div class="col-6 mb-3">
<h4 class="text-success mb-1"><%= guide.experience %></h4>
<small class="text-muted">Лет опыта</small>
</div>
</div>
<% if (guide.avg_rating) { %>
<div class="text-center">
<h4 class="text-warning mb-1"><%= parseFloat(guide.avg_rating).toFixed(1) %></h4>
<small class="text-muted">Средний рейтинг</small>
</div>
<% } %>
</div>
</div>
<!-- Back Button -->
<div class="mt-4">
<a href="/guides" class="btn btn-outline-primary w-100">
<i class="fas fa-arrow-left me-1"></i>Все гиды
</a>
</div>
</div>
</div>
</div>
</section>

283
views/index.ejs Normal file
View File

@@ -0,0 +1,283 @@
<!-- Hero Section -->
<section class="hero-section position-relative overflow-hidden">
<div class="hero-background"></div>
<div class="hero-overlay"></div>
<div class="container position-relative">
<div class="row align-items-center min-vh-100 py-5">
<div class="col-lg-6" data-aos="fade-right">
<h1 class="hero-title display-3 fw-bold text-white mb-4">
Откройте красоту
<span class="text-gradient">Кореи</span>
</h1>
<p class="hero-subtitle fs-5 text-white-50 mb-5">
Погрузитесь в аутентичную корейскую культуру, насладитесь захватывающими пейзажами и
незабываемыми приключениями с нашими экспертными местными гидами.
</p>
<div class="hero-buttons d-flex flex-wrap gap-3">
<a href="/routes" class="btn btn-primary btn-lg px-4 py-3 rounded-pill">
<i class="fas fa-route me-2"></i>Исследовать туры
</a>
<a href="/guides" class="btn btn-outline-light btn-lg px-4 py-3 rounded-pill">
<i class="fas fa-user-tie me-2"></i>Наши гиды
</a>
</div>
</div>
<div class="col-lg-6" data-aos="fade-left" data-aos-delay="200">
<div class="hero-image-container">
<img src="/images/korea-hero.jpg" alt="Красивая Корея" class="img-fluid rounded-4 shadow-lg">
<div class="floating-card position-absolute">
<div class="card border-0 shadow-lg">
<div class="card-body p-4">
<div class="d-flex align-items-center">
<div class="icon-circle bg-primary text-white me-3">
<i class="fas fa-users"></i>
</div>
<div>
<h6 class="fw-bold mb-0">1000+ довольных туристов</h6>
<small class="text-muted">Присоединяйтесь к нам</small>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</section>
<!-- Quick Search Section -->
<section class="search-section py-5 bg-light">
<div class="container">
<div class="row justify-content-center">
<div class="col-lg-8">
<div class="card border-0 shadow-lg" data-aos="fade-up">
<div class="card-body p-4">
<h4 class="text-center mb-4 fw-bold">Найдите своё идеальное корейское приключение</h4>
<form class="search-form">
<div class="row g-3">
<div class="col-md-4">
<select class="form-select form-select-lg" name="type">
<option value="">Тип тура</option>
<option value="city">Городские туры</option>
<option value="mountain">Горные походы</option>
<option value="fishing">Рыбалка</option>
</select>
</div>
<div class="col-md-4">
<input type="date" class="form-control form-control-lg" name="date">
</div>
<div class="col-md-4">
<button type="submit" class="btn btn-primary btn-lg w-100">
<i class="fas fa-search me-2"></i>Найти туры
</button>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
</section>
<!-- Featured Tours -->
<section class="featured-tours py-5">
<div class="container">
<div class="text-center mb-5" data-aos="fade-up">
<h2 class="section-title display-5 fw-bold mb-3">Рекомендуемые туры</h2>
<p class="section-subtitle fs-5 text-muted">Отобранные впечатления, которые показывают лучшее в Корее</p>
</div>
<div class="row g-4">
<% if (featuredRoutes && featuredRoutes.length > 0) { %>
<% featuredRoutes.forEach(function(route, index) { %>
<div class="col-lg-4 col-md-6" data-aos="fade-up" data-aos-delay="<%= index * 100 %>">
<div class="tour-card card border-0 shadow-lg h-100 overflow-hidden">
<div class="position-relative">
<%
let placeholderImage = '/images/placeholder.jpg';
if (route.type === 'city') {
placeholderImage = '/images/city-tour-placeholder.webp';
} else if (route.type === 'mountain') {
placeholderImage = '/images/mountain-placeholder.jpg';
} else if (route.type === 'fishing') {
placeholderImage = '/images/fish-placeholder.jpg';
}
%>
<img src="<%= (route.image_url && route.image_url.trim()) ? route.image_url : placeholderImage %>"
alt="<%= route.title %>"
class="card-img-top tour-image">
<div class="tour-overlay position-absolute top-0 start-0 w-100 h-100 d-flex align-items-end">
<div class="tour-price p-3">
<span class="badge bg-primary fs-6 px-3 py-2">
<%= formatCurrency(route.price) %>
</span>
</div>
</div>
<div class="tour-type position-absolute top-0 end-0 m-3">
<% if (route.type === 'city') { %>
<span class="badge bg-info"><i class="fas fa-city me-1"></i>Город</span>
<% } else if (route.type === 'mountain') { %>
<span class="badge bg-success"><i class="fas fa-mountain me-1"></i>Горы</span>
<% } else if (route.type === 'fishing') { %>
<span class="badge bg-warning"><i class="fas fa-fish me-1"></i>Рыбалка</span>
<% } %>
</div>
</div>
<div class="card-body p-4">
<h5 class="card-title fw-bold mb-3">
<a href="/routes/<%= route.id %>" class="text-decoration-none text-dark">
<%= route.title %>
</a>
</h5>
<p class="card-text text-muted mb-3">
<%= truncateText(route.description, 100) %>
</p>
<div class="tour-meta d-flex justify-content-between align-items-center mb-3">
<span class="text-muted">
<i class="fas fa-clock me-1"></i><%= route.duration %>h
</span>
<span class="text-muted">
<i class="fas fa-users me-1"></i>Max 10 people
</span>
</div>
<a href="/routes/<%= route.id %>" class="btn btn-outline-primary w-100">
View Details
</a>
</div>
</div>
</div>
<% }); %>
<% } else { %>
<div class="col-12 text-center py-5">
<p class="text-muted fs-5">No featured tours available at the moment.</p>
<a href="/routes" class="btn btn-primary">Посмотреть все туры</a>
</div>
<% } %>
</div>
<div class="text-center mt-5" data-aos="fade-up">
<a href="/routes" class="btn btn-primary btn-lg px-5 py-3 rounded-pill">
<i class="fas fa-eye me-2"></i>Посмотреть все туры
</a>
</div>
</div>
</section>
<!-- Guide Stats -->
<section class="stats-section py-5 bg-gradient-primary text-white">
<div class="container">
<div class="row text-center g-4">
<div class="col-lg-3 col-md-6" data-aos="fade-up" data-aos-delay="0">
<div class="stat-item">
<div class="stat-icon mb-3">
<i class="fas fa-route display-4"></i>
</div>
<h3 class="stat-number fw-bold display-5 mb-2">50+</h3>
<p class="stat-label fs-5">Unique Tours</p>
</div>
</div>
<div class="col-lg-3 col-md-6" data-aos="fade-up" data-aos-delay="100">
<div class="stat-item">
<div class="stat-icon mb-3">
<i class="fas fa-user-tie display-4"></i>
</div>
<h3 class="stat-number fw-bold display-5 mb-2"><%= guideStats.total_guides || 0 %></h3>
<p class="stat-label fs-5">Экспертные гиды</p>
</div>
</div>
<div class="col-lg-3 col-md-6" data-aos="fade-up" data-aos-delay="200">
<div class="stat-item">
<div class="stat-icon mb-3">
<i class="fas fa-users display-4"></i>
</div>
<h3 class="stat-number fw-bold display-5 mb-2">1000+</h3>
<p class="stat-label fs-5">Happy Travelers</p>
</div>
</div>
<div class="col-lg-3 col-md-6" data-aos="fade-up" data-aos-delay="300">
<div class="stat-item">
<div class="stat-icon mb-3">
<i class="fas fa-star display-4"></i>
</div>
<h3 class="stat-number fw-bold display-5 mb-2">4.9</h3>
<p class="stat-label fs-5">Average Rating</p>
</div>
</div>
</div>
</div>
</section>
<!-- Recent Articles -->
<section class="articles-section py-5">
<div class="container">
<div class="text-center mb-5" data-aos="fade-up">
<h2 class="section-title display-5 fw-bold mb-3">Полезная информация о путешествиях</h2>
<p class="section-subtitle fs-5 text-muted">Tips, guides, and stories from Korea</p>
</div>
<div class="row g-4">
<% if (recentArticles && recentArticles.length > 0) { %>
<% recentArticles.forEach(function(article, index) { %>
<div class="col-lg-4 col-md-6" data-aos="fade-up" data-aos-delay="<%= index * 100 %>">
<article class="article-card card border-0 shadow-lg h-100">
<img src="<%= (article.image_url && article.image_url.trim()) ? article.image_url : '/images/placeholder.jpg' %>"
alt="<%= article.title %>"
class="card-img-top article-image">
<div class="card-body p-4">
<h5 class="card-title fw-bold mb-3">
<a href="/articles/<%= article.id %>" class="text-decoration-none text-dark">
<%= article.title %>
</a>
</h5>
<p class="card-text text-muted mb-3">
<%= truncateText(article.excerpt, 120) %>
</p>
<div class="article-meta d-flex justify-content-between align-items-center">
<small class="text-muted">
<i class="fas fa-calendar me-1"></i>
<%= formatDate(article.created_at) %>
</small>
<a href="/articles/<%= article.id %>" class="btn btn-sm btn-outline-primary">
Подробнее
</a>
</div>
</div>
</article>
</div>
<% }); %>
<% } else { %>
<div class="col-12 text-center py-5">
<p class="text-muted fs-5">No articles available at the moment.</p>
<a href="/articles" class="btn btn-primary">Browse All Articles</a>
</div>
<% } %>
</div>
<div class="text-center mt-5" data-aos="fade-up">
<a href="/articles" class="btn btn-outline-primary btn-lg px-5 py-3 rounded-pill">
<i class="fas fa-newspaper me-2"></i>Читать больше статей
</a>
</div>
</div>
</section>
<!-- CTA Section -->
<section class="cta-section py-5 bg-dark text-white">
<div class="container">
<div class="row align-items-center">
<div class="col-lg-8" data-aos="fade-right">
<h2 class="display-5 fw-bold mb-3">Готовы к вашему корейскому приключению?</h2>
<p class="fs-5 text-white-50 mb-0">
Join thousands of travelers who have discovered the magic of Korea with our expert guides.
</p>
</div>
<div class="col-lg-4 text-lg-end" data-aos="fade-left">
<a href="/contact" class="btn btn-primary btn-lg px-5 py-3 rounded-pill">
<i class="fas fa-envelope me-2"></i>Get in Touch
</a>
</div>
</div>
</div>
</section>

205
views/layout.ejs Normal file
View File

@@ -0,0 +1,205 @@
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><%= title || siteName %></title>
<meta name="description" content="<%= siteDescription %>">
<!-- Favicon -->
<link rel="icon" href="/images/favicon.ico">
<!-- Google Fonts -->
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+KR:wght@300;400;500;700&family=Playfair+Display:wght@400;600;700&display=swap" rel="stylesheet">
<!-- Bootstrap CSS -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<!-- Font Awesome -->
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" rel="stylesheet">
<!-- AOS Animation -->
<link href="https://cdn.jsdelivr.net/npm/aos@2.3.4/dist/aos.css" rel="stylesheet">
<!-- Swiper CSS -->
<link href="https://cdn.jsdelivr.net/npm/swiper@10/swiper-bundle.min.css" rel="stylesheet">
<!-- Custom CSS -->
<link href="/css/main.css" rel="stylesheet">
<!-- Open Graph Meta Tags -->
<meta property="og:title" content="<%= title || siteName %>">
<meta property="og:description" content="<%= siteDescription %>">
<meta property="og:type" content="website">
<meta property="og:url" content="https://koreatour.ru">
<meta property="og:image" content="/images/korea-tourism-og.jpg">
<!-- Korean Language Tags -->
<meta name="keywords" content="Корея, туры, путешествие, гид, Korea travel, Korean tourism">
</head>
<body>
<!-- Navigation -->
<nav class="navbar navbar-expand-lg navbar-dark bg-primary fixed-top">
<div class="container">
<a class="navbar-brand" href="/">
<img src="/images/korea-logo.png" alt="Korea Tourism" height="40" class="me-2">
<span class="fw-bold"><%= siteName %></span>
</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav ms-auto">
<li class="nav-item">
<a class="nav-link <%= page === 'home' ? 'active' : '' %>" href="/">
<i class="fas fa-home me-1"></i>Главная
</a>
</li>
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle <%= page === 'routes' ? 'active' : '' %>" href="#" data-bs-toggle="dropdown">
<i class="fas fa-route me-1"></i>Туры
</a>
<ul class="dropdown-menu">
<li><a class="dropdown-item" href="/routes?type=city">
<i class="fas fa-city me-2"></i>Городские туры
</a></li>
<li><a class="dropdown-item" href="/routes?type=mountain">
<i class="fas fa-mountain me-2"></i>Горные походы
</a></li>
<li><a class="dropdown-item" href="/routes?type=fishing">
<i class="fas fa-fish me-2"></i>Морская рыбалка
</a></li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" href="/routes">
<i class="fas fa-list me-2"></i>Все туры
</a></li>
</ul>
</li>
<li class="nav-item">
<a class="nav-link <%= page === 'guides' ? 'active' : '' %>" href="/guides">
<i class="fas fa-user-tie me-1"></i>Гиды
</a>
</li>
<li class="nav-item">
<a class="nav-link <%= page === 'articles' ? 'active' : '' %>" href="/articles">
<i class="fas fa-newspaper me-1"></i>Статьи
</a>
</li>
<li class="nav-item">
<a class="nav-link <%= page === 'about' ? 'active' : '' %>" href="/about">
<i class="fas fa-info-circle me-1"></i>О нас
</a>
</li>
<li class="nav-item">
<a class="nav-link <%= page === 'contact' ? 'active' : '' %>" href="/contact">
<i class="fas fa-envelope me-1"></i>Контакты
</a>
</li>
</ul>
</div>
</div>
</nav>
<!-- Main Content -->
<main>
<%- body %>
</main>
<!-- Footer -->
<footer class="bg-dark text-white pt-5 pb-3">
<div class="container">
<div class="row">
<div class="col-lg-4 mb-4">
<h5 class="fw-bold mb-3">
<img src="/images/korea-logo.png" alt="Korea Tourism" height="30" class="me-2">
<%= siteName %>
</h5>
<p class="text-muted"><%= siteDescription %></p>
<div class="social-links">
<a href="#" class="text-white me-3"><i class="fab fa-facebook-f"></i></a>
<a href="#" class="text-white me-3"><i class="fab fa-instagram"></i></a>
<a href="#" class="text-white me-3"><i class="fab fa-youtube"></i></a>
<a href="#" class="text-white"><i class="fab fa-twitter"></i></a>
</div>
</div>
<div class="col-lg-2 mb-4">
<h6 class="fw-bold mb-3">Туры</h6>
<ul class="list-unstyled">
<li><a href="/routes?type=city" class="text-muted text-decoration-none">Городские туры</a></li>
<li><a href="/routes?type=mountain" class="text-muted text-decoration-none">Горные походы</a></li>
<li><a href="/routes?type=fishing" class="text-muted text-decoration-none">Морская рыбалка</a></li>
</ul>
</div>
<div class="col-lg-2 mb-4">
<h6 class="fw-bold mb-3">Информация</h6>
<ul class="list-unstyled">
<li><a href="/guides" class="text-muted text-decoration-none">Наши гиды</a></li>
<li><a href="/articles" class="text-muted text-decoration-none">Советы путешественникам</a></li>
<li><a href="/about" class="text-muted text-decoration-none">О компании</a></li>
</ul>
</div>
<div class="col-lg-4 mb-4">
<h6 class="fw-bold mb-3">Контакты</h6>
<p class="text-muted mb-2">
<i class="fas fa-map-marker-alt me-2"></i>
Москва, Россия
</p>
<p class="text-muted mb-2">
<i class="fas fa-phone me-2"></i>
+7 (495) 123-45-67
</p>
<p class="text-muted mb-2">
<i class="fas fa-envelope me-2"></i>
info@koreatour.ru
</p>
<div class="mt-3">
<h6 class="fw-bold mb-2">Подписка на новости</h6>
<form class="d-flex">
<input type="email" class="form-control me-2" placeholder="Ваш email">
<button class="btn btn-primary" type="submit">Подписаться</button>
</form>
</div>
</div>
</div>
<hr class="my-4">
<div class="row align-items-center">
<div class="col-md-6">
<p class="mb-0 text-muted">&copy; 2024 <%= siteName %>. Все права защищены.</p>
</div>
<div class="col-md-6 text-md-end">
<small class="text-muted">
Сделано с <i class="fas fa-heart text-danger"></i> для туризма по Корее
</small>
</div>
</div>
</div>
</footer>
<!-- Bootstrap JS -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<!-- AOS Animation -->
<script src="https://cdn.jsdelivr.net/npm/aos@2.3.4/dist/aos.js"></script>
<!-- Swiper JS -->
<script src="https://cdn.jsdelivr.net/npm/swiper@10/swiper-bundle.min.js"></script>
<!-- Custom JS -->
<script src="/js/main.js"></script>
<!-- Initialize AOS -->
<script>
AOS.init({
duration: 800,
once: true,
offset: 100
});
</script>
<% if (typeof pageScripts !== 'undefined') { %>
<%- pageScripts %>
<% } %>
</body>
</html>

289
views/routes/detail.ejs Normal file
View File

@@ -0,0 +1,289 @@
<!-- Hero Section -->
<section class="hero-section bg-primary text-white">
<div class="container py-5">
<div class="row align-items-center">
<div class="col-lg-8">
<h1 class="display-4 fw-bold mb-3"><%= route.title %></h1>
<p class="lead mb-4"><%= route.description %></p>
<div class="d-flex flex-wrap gap-2 mb-4">
<% if (route.type === 'city') { %>
<span class="badge bg-info fs-6">
<i class="fas fa-city me-1"></i>Городской тур
</span>
<% } else if (route.type === 'mountain') { %>
<span class="badge bg-success fs-6">
<i class="fas fa-mountain me-1"></i>Горный поход
</span>
<% } else if (route.type === 'fishing') { %>
<span class="badge bg-info fs-6">
<i class="fas fa-fish me-1"></i>Рыбалка
</span>
<% } %>
<span class="badge <%= route.difficulty_level === 'easy' ? 'bg-success' : route.difficulty_level === 'moderate' ? 'bg-warning' : 'bg-danger' %> fs-6">
<%= route.difficulty_level === 'easy' ? 'Легко' : route.difficulty_level === 'moderate' ? 'Средне' : 'Сложно' %>
</span>
</div>
</div>
<div class="col-lg-4">
<div class="bg-white text-dark p-4 rounded shadow">
<h4 class="text-primary mb-3">Детали тура</h4>
<div class="mb-3">
<strong class="d-block">Цена</strong>
<span class="h4 text-primary">₩<%= formatCurrency(route.price) %></span>
</div>
<div class="mb-3">
<strong class="d-block">Продолжительность</strong>
<span><%= route.duration %> дней</span>
</div>
<div class="mb-3">
<strong class="d-block">Максимум участников</strong>
<span><%= route.max_group_size %> человек</span>
</div>
<a href="/contact" class="btn btn-primary w-100">
<i class="fas fa-calendar-plus me-1"></i>Забронировать
</a>
</div>
</div>
</div>
</div>
</section>
<div class="container py-5">
<div class="row">
<div class="col-lg-8">
<!-- Route Image -->
<%
let placeholderImage = '/images/placeholder.jpg';
if (route.type === 'city') {
placeholderImage = '/images/city-tour-placeholder.webp';
} else if (route.type === 'mountain') {
placeholderImage = '/images/mountain-placeholder.jpg';
} else if (route.type === 'fishing') {
placeholderImage = '/images/fish-placeholder.jpg';
}
%>
<% if (route.image_url && route.image_url.trim()) { %>
<img src="<%= route.image_url %>" class="img-fluid rounded mb-4" alt="<%= route.title %>">
<% } else { %>
<img src="<%= placeholderImage %>" class="img-fluid rounded mb-4" alt="<%= route.title %>">
<% } %>
<!-- Route Content -->
<div class="mb-5">
<h2 class="mb-3">Описание маршрута</h2>
<div class="content">
<%= route.content || 'Подробное описание маршрута будет доступно в ближайшее время.' %>
</div>
</div>
<!-- Included Services -->
<% if (route.included_services && route.included_services.length > 0) { %>
<div class="mb-5">
<h3 class="mb-3">Что включено</h3>
<div class="row">
<% route.included_services.forEach(service => { %>
<div class="col-md-6 mb-2">
<i class="fas fa-check text-success me-2"></i><%= service %>
</div>
<% }); %>
</div>
</div>
<% } %>
<!-- Reviews Section -->
<div class="mb-5">
<h3 class="mb-4">Отзывы (<%= reviews.length %>)</h3>
<!-- Review Form -->
<div class="card mb-4">
<div class="card-header">
<h5 class="mb-0">Оставить отзыв</h5>
</div>
<div class="card-body">
<form id="reviewForm">
<input type="hidden" name="route_id" value="<%= route.id %>">
<% if (route.guide_id) { %>
<input type="hidden" name="guide_id" value="<%= route.guide_id %>">
<% } %>
<div class="row">
<div class="col-md-6 mb-3">
<label for="customer_name" class="form-label">Ваше имя *</label>
<input type="text" class="form-control" id="customer_name" name="customer_name" required>
</div>
<div class="col-md-6 mb-3">
<label for="customer_email" class="form-label">Email</label>
<input type="email" class="form-control" id="customer_email" name="customer_email">
</div>
</div>
<div class="mb-3">
<label for="rating" class="form-label">Оценка *</label>
<div class="star-rating">
<input type="radio" id="star5" name="rating" value="5">
<label for="star5" title="Отлично"><i class="fas fa-star"></i></label>
<input type="radio" id="star4" name="rating" value="4">
<label for="star4" title="Хорошо"><i class="fas fa-star"></i></label>
<input type="radio" id="star3" name="rating" value="3">
<label for="star3" title="Нормально"><i class="fas fa-star"></i></label>
<input type="radio" id="star2" name="rating" value="2">
<label for="star2" title="Плохо"><i class="fas fa-star"></i></label>
<input type="radio" id="star1" name="rating" value="1">
<label for="star1" title="Очень плохо"><i class="fas fa-star"></i></label>
</div>
</div>
<div class="mb-3">
<label for="comment" class="form-label">Комментарий *</label>
<textarea class="form-control" id="comment" name="comment" rows="4"
placeholder="Поделитесь впечатлениями о туре..." required></textarea>
</div>
<button type="submit" class="btn btn-primary">
<i class="fas fa-paper-plane me-1"></i>Отправить отзыв
</button>
</form>
</div>
</div>
<!-- Reviews List -->
<% if (reviews.length === 0) { %>
<p class="text-muted">Пока нет отзывов об этом туре. Будьте первым!</p>
<% } else { %>
<% reviews.forEach(review => { %>
<div class="card mb-3">
<div class="card-body">
<div class="d-flex justify-content-between align-items-start mb-2">
<h6 class="mb-0"><%= review.customer_name %></h6>
<div>
<%- generateStars(review.rating) %>
</div>
</div>
<p class="card-text"><%= review.comment %></p>
<small class="text-muted"><%= formatDate(review.created_at) %></small>
</div>
</div>
<% }); %>
<% } %>
</div>
</div>
<!-- Sidebar -->
<div class="col-lg-4">
<!-- Guide Info -->
<% if (route.guide_name) { %>
<div class="card mb-4">
<div class="card-body">
<h5 class="card-title">Ваш гид</h5>
<% if (route.guide_image) { %>
<img src="<%= route.guide_image %>" class="rounded-circle mb-3" width="80" height="80" style="object-fit: cover;" alt="<%= route.guide_name %>">
<% } %>
<h6><%= route.guide_name %></h6>
<% if (route.guide_bio) { %>
<p class="text-muted small"><%= route.guide_bio %></p>
<% } %>
</div>
</div>
<% } %>
<!-- Contact Card -->
<div class="card">
<div class="card-body">
<h5 class="card-title">Нужна помощь?</h5>
<p class="card-text">Свяжитесь с нами для получения дополнительной информации о туре.</p>
<div class="mb-3">
<strong class="d-block">Телефон</strong>
<span>+7 (495) 123-45-67</span>
</div>
<div class="mb-3">
<strong class="d-block">Email</strong>
<span>info@koreatour.ru</span>
</div>
<a href="/contact" class="btn btn-outline-primary w-100">
<i class="fas fa-envelope me-1"></i>Написать нам
</a>
</div>
</div>
</div>
</div>
</div>
<!-- Similar Tours -->
<section class="bg-light py-5">
<div class="container">
<h2 class="text-center mb-5">Похожие туры</h2>
<div class="text-center">
<a href="/routes?type=<%= route.type %>" class="btn btn-primary">
Посмотреть все туры категории
</a>
</div>
</div>
</section>
<style>
.star-rating {
display: flex;
flex-direction: row-reverse;
justify-content: flex-end;
}
.star-rating input {
display: none;
}
.star-rating label {
color: #ddd;
font-size: 1.5rem;
margin: 0 2px;
cursor: pointer;
transition: color 0.2s;
}
.star-rating input:checked ~ label,
.star-rating label:hover,
.star-rating label:hover ~ label {
color: #ffc107;
}
</style>
<script>
// Review form submission
document.getElementById('reviewForm').addEventListener('submit', async function(e) {
e.preventDefault();
const formData = new FormData(this);
const data = Object.fromEntries(formData);
// Validate rating
if (!data.rating) {
alert('Пожалуйста, выберите оценку');
return;
}
try {
const response = await fetch('/api/reviews', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(data)
});
const result = await response.json();
if (result.success) {
alert(result.message);
this.reset();
// Optionally reload the page to show new review
// location.reload();
} else {
alert(result.message);
}
} catch (error) {
console.error('Error submitting review:', error);
alert('Произошла ошибка при отправке отзыва');
}
});
</script>

131
views/routes/index.ejs Normal file
View File

@@ -0,0 +1,131 @@
<!-- Hero Section -->
<section class="hero-section bg-primary text-white text-center py-5">
<div class="container">
<div class="row align-items-center">
<div class="col-lg-8 mx-auto">
<h1 class="display-4 fw-bold mb-4">Туры по Корее</h1>
<p class="lead mb-4">Откройте для себя удивительную Корею с нашими профессиональными гидами</p>
<!-- Filter Buttons -->
<div class="btn-group mb-4" role="group">
<a href="/routes" class="btn <%= currentType === 'all' ? 'btn-light' : 'btn-outline-light' %>">
<i class="fas fa-list me-1"></i>Все туры
</a>
<a href="/routes?type=city" class="btn <%= currentType === 'city' ? 'btn-light' : 'btn-outline-light' %>">
<i class="fas fa-city me-1"></i>Городские туры
</a>
<a href="/routes?type=mountain" class="btn <%= currentType === 'mountain' ? 'btn-light' : 'btn-outline-light' %>">
<i class="fas fa-mountain me-1"></i>Горные походы
</a>
<a href="/routes?type=fishing" class="btn <%= currentType === 'fishing' ? 'btn-light' : 'btn-outline-light' %>">
<i class="fas fa-fish me-1"></i>Морская рыбалка
</a>
</div>
</div>
</div>
</div>
</section>
<!-- Routes Grid -->
<section class="py-5">
<div class="container">
<% if (routes.length === 0) { %>
<div class="text-center py-5">
<i class="fas fa-map text-muted" style="font-size: 4rem;"></i>
<h3 class="mt-3 text-muted">Туры не найдены</h3>
<p class="text-muted">В данной категории пока нет доступных туров.</p>
<a href="/routes" class="btn btn-primary">Посмотреть все туры</a>
</div>
<% } else { %>
<div class="row">
<% routes.forEach(route => { %>
<div class="col-lg-4 col-md-6 mb-4">
<div class="card h-100 shadow-sm">
<%
let placeholderImage = '/images/placeholder.jpg';
if (route.type === 'city') {
placeholderImage = '/images/city-tour-placeholder.webp';
} else if (route.type === 'mountain') {
placeholderImage = '/images/mountain-placeholder.jpg';
} else if (route.type === 'fishing') {
placeholderImage = '/images/fish-placeholder.jpg';
}
%>
<% if (route.image_url && route.image_url.trim()) { %>
<img src="<%= route.image_url %>" class="card-img-top" alt="<%= route.title %>" style="height: 250px; object-fit: cover;">
<% } else { %>
<img src="<%= placeholderImage %>" class="card-img-top" alt="<%= route.title %>" style="height: 250px; object-fit: cover;">
<% } %>
<!-- Featured Badge -->
<% if (route.is_featured) { %>
<span class="badge bg-warning text-dark position-absolute top-0 start-0 m-2">
<i class="fas fa-star me-1"></i>Рекомендуем
</span>
<% } %>
<div class="card-body d-flex flex-column">
<div class="mb-2">
<% if (route.type === 'city') { %>
<span class="badge bg-info text-dark">
<i class="fas fa-city me-1"></i>Городской тур
</span>
<% } else if (route.type === 'mountain') { %>
<span class="badge bg-success">
<i class="fas fa-mountain me-1"></i>Горный поход
</span>
<% } else if (route.type === 'fishing') { %>
<span class="badge bg-primary">
<i class="fas fa-fish me-1"></i>Рыбалка
</span>
<% } %>
<% if (route.difficulty_level) { %>
<span class="badge <%= route.difficulty_level === 'easy' ? 'bg-success' : route.difficulty_level === 'moderate' ? 'bg-warning' : 'bg-danger' %>">
<%= route.difficulty_level === 'easy' ? 'Легко' : route.difficulty_level === 'moderate' ? 'Средне' : 'Сложно' %>
</span>
<% } %>
</div>
<h5 class="card-title"><%= route.title %></h5>
<p class="card-text text-muted flex-grow-1"><%= route.description %></p>
<div class="mt-auto">
<div class="d-flex justify-content-between align-items-center mb-3">
<div>
<strong class="text-primary h5">₩<%= formatCurrency(route.price) %></strong>
</div>
<small class="text-muted">
<i class="fas fa-clock me-1"></i><%= route.duration %> дн.
</small>
</div>
<% if (route.guide_name) { %>
<p class="text-muted mb-2">
<i class="fas fa-user-tie me-1"></i>Гид: <%= route.guide_name %>
</p>
<% } %>
<a href="/routes/<%= route.id %>" class="btn btn-primary w-100">
<i class="fas fa-info-circle me-1"></i>Подробнее
</a>
</div>
</div>
</div>
</div>
<% }); %>
</div>
<% } %>
</div>
</section>
<!-- CTA Section -->
<section class="bg-light py-5">
<div class="container text-center">
<h2 class="mb-4">Не нашли подходящий тур?</h2>
<p class="lead text-muted mb-4">Мы можем организовать индивидуальный тур специально для вас!</p>
<a href="/contact" class="btn btn-primary btn-lg">
<i class="fas fa-envelope me-1"></i>Свяжитесь с нами
</a>
</div>
</section>