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:
111
views/about.ejs
Normal file
111
views/about.ejs
Normal 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
195
views/articles/detail.ejs
Normal 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
80
views/articles/index.ejs
Normal 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
191
views/contact.ejs
Normal 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
34
views/error.ejs
Normal 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
108
views/guides/index.ejs
Normal 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
260
views/guides/profile.ejs
Normal 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
283
views/index.ejs
Normal 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
205
views/layout.ejs
Normal 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">© 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
289
views/routes/detail.ejs
Normal 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
131
views/routes/index.ejs
Normal 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>
|
||||
Reference in New Issue
Block a user